优化窗口边框的裁剪 (#864)

* feat: 优化对最大化窗口的支持

* feat: 捕捉标题栏时去除边框

* feat: 非最大化的窗口自动裁剪上边框

* perf: 性能优化

* fix: 错误修复

* fix: 修复警告

* fix: 尝试修复 Graphics Capture 的捕获区域

* feat: 适配 Win11 的捕获区域

* fix: 添加检查
This commit is contained in:
Xu 2024-03-28 20:59:44 +08:00 committed by GitHub
commit b167cd1d09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 338 additions and 140 deletions

View file

@ -126,34 +126,90 @@ void GraphicsCaptureFrameSource::OnCursorVisibilityChanged(bool isVisible, bool
// 显示光标时必须重启捕获
if (isVisible) {
_StopCapture();
SystemParametersInfo(SPI_SETCURSORS, 0, nullptr, 0);
if (!onDestory) {
if (onDestory) {
// FIXME: 这里尝试修复拖动窗口时光标不显示的问题,但有些环境下不起作用
SystemParametersInfo(SPI_SETCURSORS, 0, nullptr, 0);
} else {
_StartCapture();
}
}
}
// Graphics Capture 的捕获区域没有文档记录,这里的计算是我实验了多种窗口后得出的,
// 高度依赖实现细节,未来可能会失效
static bool CalcWindowCapturedFrameBounds(HWND hWnd, RECT& rect) noexcept {
// Win10 中捕获区域为 extended frame boundsWin11 中 DwmGetWindowAttribute
// 对最大化的窗口返回值和 Win10 不同,可能是 OS 的 bug应进一步处理
HRESULT hr = DwmGetWindowAttribute(hWnd,
DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
if (FAILED(hr)) {
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
return false;
}
if(!Win32Utils::GetOSVersion().IsWin11() || Win32Utils::GetWindowShowCmd(hWnd) != SW_SHOWMAXIMIZED) {
return true;
}
// 如果窗口禁用了非客户区域绘制则捕获区域为 extended frame bounds
BOOL hasBorder = TRUE;
hr = DwmGetWindowAttribute(hWnd, DWMWA_NCRENDERING_ENABLED, &hasBorder, sizeof(hasBorder));
if (FAILED(hr)) {
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
return false;
}
if (!hasBorder) {
return true;
}
RECT clientRect;
if (!Win32Utils::GetClientScreenRect(hWnd, clientRect)) {
Logger::Get().Error("GetClientScreenRect 失败");
return false;
}
// 有些窗口最大化后有部分客户区在屏幕外,如 UWP 和资源管理器,它们的捕获区域
// 是整个客户区。否则捕获区域不会超出屏幕
HMONITOR hMon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi{ .cbSize = sizeof(mi) };
if (!GetMonitorInfo(hMon, &mi)) {
Logger::Get().Win32Error("GetMonitorInfo 失败");
return false;
}
if (clientRect.top < mi.rcWork.top) {
rect = clientRect;
} else {
IntersectRect(&rect, &rect, &mi.rcWork);
}
return true;
}
bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* interop) noexcept {
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
// 包含边框的窗口尺寸
RECT srcFrameBounds{};
HRESULT hr = DwmGetWindowAttribute(hwndSrc,
DWMWA_EXTENDED_FRAME_BOUNDS, &srcFrameBounds, sizeof(srcFrameBounds));
if (FAILED(hr)) {
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
RECT frameBounds;
if (!CalcWindowCapturedFrameBounds(hwndSrc, frameBounds)) {
Logger::Get().Error("CalcWindowCapturedFrameBounds 失败");
return false;
}
if (_srcRect.left <= frameBounds.left || _srcRect.top <= frameBounds.top) {
Logger::Get().Error("裁剪边框错误");
return false;
}
// 在源窗口存在 DPI 缩放时有时会有一像素的偏移(取决于窗口在屏幕上的位置)
// 可能是 DwmGetWindowAttribute 的 bug
_frameBox = {
UINT(_srcRect.left - srcFrameBounds.left),
UINT(_srcRect.top - srcFrameBounds.top),
UINT(_srcRect.left - frameBounds.left),
UINT(_srcRect.top - frameBounds.top),
0,
UINT(_srcRect.right - srcFrameBounds.left),
UINT(_srcRect.bottom - srcFrameBounds.top),
UINT(_srcRect.right - frameBounds.left),
UINT(_srcRect.bottom - frameBounds.top),
1
};
@ -181,7 +237,7 @@ bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* int
// 如果窗口使用 ITaskbarList 隐藏了任务栏图标也不会出现在 Alt+Tab 列表。这种情况很罕见
_taskbarList = winrt::try_create_instance<ITaskbarList>(CLSID_TaskbarList);
if (_taskbarList && SUCCEEDED(_taskbarList->HrInit())) {
hr = _taskbarList->AddTab(hwndSrc);
HRESULT hr = _taskbarList->AddTab(hwndSrc);
if (SUCCEEDED(hr)) {
Logger::Get().Info("已添加任务栏图标");
@ -301,16 +357,19 @@ bool GraphicsCaptureFrameSource::_CaptureMonitor(IGraphicsCaptureItemInterop* in
return false;
}
// 放在屏幕左上角而不是中间可以提高帧率,这里是为了和 DesktopDuplication 保持一致
if (!_CenterWindowIfNecessary(hwndSrc, mi.rcWork)) {
Logger::Get().Error("居中源窗口失败");
return false;
}
// 最大化的窗口无需调整位置
if (Win32Utils::GetWindowShowCmd(hwndSrc) != SW_SHOWMAXIMIZED) {
// 放在屏幕左上角而不是中间可以提高帧率,这里是为了和 DesktopDuplication 保持一致
if (!_CenterWindowIfNecessary(hwndSrc, mi.rcWork)) {
Logger::Get().Error("居中源窗口失败");
return false;
}
// 重新计算捕获位置
if (!_CalcSrcRect()) {
Logger::Get().Error("_CalcSrcRect 失败");
return false;
// 重新计算捕获位置
if (!_CalcSrcRect()) {
Logger::Get().Error("_CalcSrcRect 失败");
return false;
}
}
_frameBox = {