Refactor: split ScanScreen window-capture into 3 private methods

This commit is contained in:
yuanyuanxiang
2026-06-16 21:02:46 +02:00
parent 91d4c0a523
commit 66c950cecb
2 changed files with 104 additions and 97 deletions

View File

@@ -171,99 +171,17 @@ LPBYTE CScreenSpy::GetFirstScreenData(ULONG* ulFirstScreenLength)
VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight)
{
if (m_hTargetWnd || m_bDynamicForeground) {
// 动态前景模式:每帧查询当前前景窗口,切换时尺寸不变则直接复用,尺寸变化则触发重建
if (m_bDynamicForeground) {
HWND fg = GetForegroundWindow();
if (fg && fg != m_hTargetWnd) {
RECT wndRc = {}, frameRc = {};
GetWindowRect(fg, &wndRc);
frameRc = wndRc;
DwmGetWindowAttribute(fg, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc));
ULONG newW = (ULONG)(frameRc.right - frameRc.left);
ULONG newH = (ULONG)(frameRc.bottom - frameRc.top);
if (newW != m_ulFullWidth || newH != m_ulFullHeight) {
// 尺寸不同:触发重建,同时把新 HWND 写入 m_NextTargetWnd
// 让 WorkThread 在重建前同步更新 CScreenManager::m_hTargetWnd
m_NextTargetWnd = fg;
m_bNeedRestart = true;
return;
}
// 尺寸相同:直接切换,更新阴影偏移和缓存,无需重建
m_ShadowLeft = frameRc.left - wndRc.left;
m_ShadowTop = frameRc.top - wndRc.top;
m_CachedWndW = wndRc.right - wndRc.left;
m_CachedWndH = wndRc.bottom - wndRc.top;
m_PendingWndW = m_PendingWndH = 0;
m_hTargetWnd = fg;
Mprintf("CScreenSpy: 前景切换(同尺寸) -> HWND=%p\n", fg);
}
if (!m_hTargetWnd) return; // 当前无前景窗口,冻结上一帧
}
// 最小化不绘制hdcDest 保留上一帧内容(远程端看到冻结画面)
if (m_bDynamicForeground && !UpdateDynamicForeground()) return;
if (IsIconic(m_hTargetWnd)) return;
// 检测窗口尺寸变化GetWindowRect 廉价,每帧调用;
// DwmGetWindowAttribute 较重,仅在 GetWindowRect 尺寸稳定后才调用。
// 防抖策略:拖拽 resize 期间尺寸持续变化,冻结画面等待稳定 300ms 后再重建,
// 避免拖拽过程中每帧重建 DIBSection / 重发 BitmapInfo / 重置编解码器。
static const DWORD RESIZE_DEBOUNCE_MS = 300;
RECT fullRc = {};
GetWindowRect(m_hTargetWnd, &fullRc);
// 窗口每帧都可能移动,实时更新屏幕偏移,保证 PointConversion 坐标正确
m_iScreenX = fullRc.left + m_ShadowLeft;
m_iScreenY = fullRc.top + m_ShadowTop;
int w = fullRc.right - fullRc.left;
int h = fullRc.bottom - fullRc.top;
if (w != m_CachedWndW || h != m_CachedWndH) {
// 尺寸有变化:更新防抖记录
if (w != m_PendingWndW || h != m_PendingWndH) {
m_PendingWndW = w;
m_PendingWndH = h;
m_SizeChangeTick = GetTickCount();
}
// 尚未稳定:冻结当前帧,继续等待
if (GetTickCount() - m_SizeChangeTick < RESIZE_DEBOUNCE_MS)
return;
// 稳定 300ms查询 DWM 真实帧边界,决定是否重建
RECT frameRc = fullRc;
if (SUCCEEDED(DwmGetWindowAttribute(m_hTargetWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc)))) {
m_ShadowLeft = frameRc.left - fullRc.left;
m_ShadowTop = frameRc.top - fullRc.top;
} else {
m_ShadowLeft = m_ShadowTop = 0;
}
if ((ULONG)(frameRc.right - frameRc.left) != m_ulFullWidth ||
(ULONG)(frameRc.bottom - frameRc.top) != m_ulFullHeight) {
Mprintf("CScreenSpy: 窗口尺寸变化 %dx%d -> %dx%d触发重建\n",
m_ulFullWidth, m_ulFullHeight,
frameRc.right - frameRc.left, frameRc.bottom - frameRc.top);
m_bNeedRestart = true;
return;
}
m_CachedWndW = w;
m_CachedWndH = h;
}
// PrintWindow → 临时 DC → BitBlt 到目标(用缓存的阴影偏移跳过 DWM 扩展帧)
HDC hTmp = m_data.GetWindowDC();
HBITMAP hOld = (HBITMAP)SelectObject(hTmp, m_data.GetWindowBmp());
BOOL ok = PrintWindow(m_hTargetWnd, hTmp, PW_RENDERFULLCONTENT);
if (!ok) ok = PrintWindow(m_hTargetWnd, hTmp, 0);
if (ok) {
BitBlt(hdcDest, 0, 0, ulWidth, ulHeight, hTmp, m_ShadowLeft, m_ShadowTop, SRCCOPY);
}
SelectObject(hTmp, hOld);
if (!CheckWindowResize()) return;
CaptureWindowContent(hdcDest, hdcSour, ulWidth, ulHeight);
return;
}
if (m_bVirtualPaint) {
// 先用深色填充背景,避免窗口移动时留下残影
RECT rcFill = { 0, 0, (LONG)ulWidth, (LONG)ulHeight };
HBRUSH hBrush = CreateSolidBrush(RGB(30, 30, 30)); // 深灰色背景
HBRUSH hBrush = CreateSolidBrush(RGB(30, 30, 30));
FillRect(hdcDest, &rcFill, hBrush);
DeleteObject(hBrush);
int n = 0;
if (n = EnumWindowsTopToDown(NULL, EnumHwndsPrint, (LPARAM)&m_data.SetScreenDC(hdcDest))) {
Mprintf("EnumWindowsTopToDown failed: %d!!! GetLastError: %d\n", n, GetLastError());
@@ -275,18 +193,99 @@ VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHei
#if COPY_ALL
BitBlt(hdcDest, 0, 0, ulWidth, ulHeight, hdcSour, m_iScreenX, m_iScreenY, SRCCOPY);
#else
const ULONG ulJumpLine = 50;
const ULONG ulJumpSleep = ulJumpLine / 10;
for (int i = 0, ulToJump = 0; i < ulHeight; i += ulToJump) {
ULONG ulv1 = ulHeight - i;
if (ulv1 > ulJumpLine)
ulToJump = ulJumpLine;
else
ulToJump = ulv1;
BitBlt(hdcDest, 0, i, ulWidth, ulToJump, hdcSour,0, i, SRCCOPY);
const ULONG ulJumpLine = 50;
const ULONG ulJumpSleep = ulJumpLine / 10;
for (int i = 0, ulToJump = 0; i < (int)ulHeight; i += ulToJump) {
ULONG ulv1 = ulHeight - i;
ulToJump = (ulv1 > ulJumpLine) ? ulJumpLine : ulv1;
BitBlt(hdcDest, 0, i, ulWidth, ulToJump, hdcSour, 0, i, SRCCOPY);
Sleep(ulJumpSleep);
}
#endif
}
// 每帧跟踪前景窗口;切换时尺寸相同直接复用,尺寸不同触发重建
// 返回 false 表示本帧跳过(冻结)
bool CScreenSpy::UpdateDynamicForeground()
{
HWND fg = GetForegroundWindow();
if (fg && fg != m_hTargetWnd) {
RECT wndRc = {}, frameRc = {};
GetWindowRect(fg, &wndRc);
frameRc = wndRc;
DwmGetWindowAttribute(fg, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc));
ULONG newW = (ULONG)(frameRc.right - frameRc.left);
ULONG newH = (ULONG)(frameRc.bottom - frameRc.top);
if (newW != m_ulFullWidth || newH != m_ulFullHeight) {
// 尺寸不同:让 WorkThread 重建m_NextTargetWnd 传递新 HWND
m_NextTargetWnd = fg;
m_bNeedRestart = true;
return false;
}
// 尺寸相同:直接切换,更新阴影偏移缓存,无需重建
m_ShadowLeft = frameRc.left - wndRc.left;
m_ShadowTop = frameRc.top - wndRc.top;
m_CachedWndW = wndRc.right - wndRc.left;
m_CachedWndH = wndRc.bottom - wndRc.top;
m_PendingWndW = m_PendingWndH = 0;
m_hTargetWnd = fg;
Mprintf("CScreenSpy: 前景切换(同尺寸) -> HWND=%p\n", fg);
}
return m_hTargetWnd != NULL; // NULL=无前景窗口,冻结上一帧
}
// 检测窗口 resizeGetWindowRect 每帧调用DWM 查询仅在尺寸稳定 300ms 后触发
// 同时更新 m_iScreenX/Y窗口移动时坐标同步返回 false 表示本帧跳过
bool CScreenSpy::CheckWindowResize()
{
static const DWORD RESIZE_DEBOUNCE_MS = 300;
RECT fullRc = {};
GetWindowRect(m_hTargetWnd, &fullRc);
m_iScreenX = fullRc.left + m_ShadowLeft;
m_iScreenY = fullRc.top + m_ShadowTop;
int w = fullRc.right - fullRc.left;
int h = fullRc.bottom - fullRc.top;
if (w == m_CachedWndW && h == m_CachedWndH)
return true;
// 尺寸有变化:更新防抖记录
if (w != m_PendingWndW || h != m_PendingWndH) {
m_PendingWndW = w;
m_PendingWndH = h;
m_SizeChangeTick = GetTickCount();
}
if (GetTickCount() - m_SizeChangeTick < RESIZE_DEBOUNCE_MS)
return false; // 尚未稳定,冻结等待
// 稳定 300ms查询 DWM 真实帧边界,决定是否重建
RECT frameRc = fullRc;
if (SUCCEEDED(DwmGetWindowAttribute(m_hTargetWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc)))) {
m_ShadowLeft = frameRc.left - fullRc.left;
m_ShadowTop = frameRc.top - fullRc.top;
} else {
m_ShadowLeft = m_ShadowTop = 0;
}
if ((ULONG)(frameRc.right - frameRc.left) != m_ulFullWidth ||
(ULONG)(frameRc.bottom - frameRc.top) != m_ulFullHeight) {
Mprintf("CScreenSpy: 窗口尺寸变化 %dx%d -> %dx%d触发重建\n",
m_ulFullWidth, m_ulFullHeight,
frameRc.right - frameRc.left, frameRc.bottom - frameRc.top);
m_bNeedRestart = true;
return false;
}
m_CachedWndW = w;
m_CachedWndH = h;
return true;
}
// 前景模式直接 BitBlt零闪烁指定窗口模式用 PrintWindow处理遮挡
void CScreenSpy::CaptureWindowContent(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight)
{
if (m_bDynamicForeground) {
BitBlt(hdcDest, 0, 0, ulWidth, ulHeight, hdcSour, m_iScreenX, m_iScreenY, SRCCOPY);
} else {
HDC hTmp = m_data.GetWindowDC();
HBITMAP hOld = (HBITMAP)SelectObject(hTmp, m_data.GetWindowBmp());
if (PrintWindow(m_hTargetWnd, hTmp, PW_RENDERFULLCONTENT))
BitBlt(hdcDest, 0, 0, ulWidth, ulHeight, hTmp, m_ShadowLeft, m_ShadowTop, SRCCOPY);
SelectObject(hTmp, hOld);
}
}