Files
SimpleRemoter/client/ScreenSpy.cpp

254 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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) :
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
}