293 lines
12 KiB
C++
293 lines
12 KiB
C++
// ScreenSpy.cpp: implementation of the CScreenSpy class.
|
||
//
|
||
//////////////////////////////////////////////////////////////////////
|
||
|
||
#include "stdafx.h"
|
||
#include "ScreenSpy.h"
|
||
#include "Common.h"
|
||
#include <stdio.h>
|
||
#include <common/iniFile.h>
|
||
|
||
//////////////////////////////////////////////////////////////////////
|
||
// Construction/Destruction
|
||
//////////////////////////////////////////////////////////////////////
|
||
|
||
CScreenSpy::CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk, int gop, BOOL all, int level,
|
||
RECT rc, BOOL switchScreen, HWND hwnd, bool dynamicFg) :
|
||
ScreenCapture(ulbiBitCount, algo, all, level, rc, switchScreen)
|
||
{
|
||
m_hTargetWnd = hwnd;
|
||
m_bDynamicForeground = dynamicFg;
|
||
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)
|
||
{
|
||
if (m_hTargetWnd && IsIconic(m_hTargetWnd)) {
|
||
*ulFirstScreenLength = 0;
|
||
return nullptr;
|
||
}
|
||
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);
|
||
// H264/AV1:不发原始位图,IDR 到达后服务端自行解锁;节省每次切窗口的流量峰值
|
||
if (m_bAlgorithm == ALGORITHM_H264) {
|
||
*ulFirstScreenLength = 0;
|
||
return nullptr;
|
||
}
|
||
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 || 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 (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
|
||
}
|