Feat: Support viewing active window via online-host popup menu

This commit is contained in:
yuanyuanxiang
2026-06-15 16:14:01 +02:00
parent 931492a294
commit 2765d95950
13 changed files with 118 additions and 24 deletions

View File

@@ -170,7 +170,8 @@ public:
// 感兴趣区域 (ROI)
RECT m_ROI = {0,0,0,0};
bool m_bNeedRestart = false; // 捕获对象需要重建(如窗口尺寸变化)
bool m_bNeedRestart = false; // 捕获对象需要重建(如窗口尺寸变化)
HWND m_NextTargetWnd = NULL; // 重建时应切换的目标窗口NULL=保持原 HWND
int m_nScaleSendWidth = 0;
int m_nScaleSendHeight = 0;

View File

@@ -471,11 +471,18 @@ void CScreenManager::InitScreenSpy()
DXGI = param->buffer[0];
algo = param->length > 1 ? param->buffer[1] : algo;
all = param->length > 2 ? param->buffer[2] : all;
// buffer[3..10]: HWND(uint64_t),启动时直接指定窗口捕获
if (param->length >= 3 + (int)sizeof(uint64_t) && !m_hTargetWnd) {
// buffer[3..10]: HWND(uint64_t),启动时直接指定窗口捕获
// 值为 (uint64_t)-1 时进入动态前景模式,每帧自动跟踪当前前景窗口
if (param->length >= 3 + (int)sizeof(uint64_t) && !m_hTargetWnd && !m_bDynamicForeground) {
uint64_t hwnd64 = 0;
memcpy(&hwnd64, param->buffer + 3, sizeof(uint64_t));
if (hwnd64) m_hTargetWnd = (HWND)(UINT_PTR)hwnd64;
if (hwnd64 == (uint64_t)-1) {
m_bDynamicForeground = true;
m_hTargetWnd = GetForegroundWindow();
Mprintf("CScreenManager: 动态前景窗口模式,初始 HWND=%p\n", m_hTargetWnd);
} else if (hwnd64) {
m_hTargetWnd = (HWND)(UINT_PTR)hwnd64;
}
}
}
m_pUserParam = param;
@@ -488,7 +495,7 @@ void CScreenManager::InitScreenSpy()
algo = m_QualityProfiles[level].algorithm;
}
// 窗口捕获必须走 GDIPrintWindowScreenCapturerDXGI 无窗口捕获能力
if (m_hTargetWnd && DXGI == USING_DXGI) {
if ((m_hTargetWnd || m_bDynamicForeground) && DXGI == USING_DXGI) {
DXGI = USING_GDI;
Mprintf("CScreenManager: 窗口捕获模式,强制 GDI\n");
}
@@ -542,12 +549,12 @@ void CScreenManager::InitScreenSpy()
} else {
SAFE_DELETE(s);
m_isGDI = TRUE;
m_ScreenSpyObject = new CScreenSpy(32, algo, FALSE, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen, m_hTargetWnd);
m_ScreenSpyObject = new CScreenSpy(32, algo, FALSE, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen, m_hTargetWnd, m_bDynamicForeground);
Mprintf("CScreenManager: DXGI SPY init failed!!! Using GDI instead.\n");
}
} else {
m_isGDI = TRUE;
m_ScreenSpyObject = new CScreenSpy(32, algo, DXGI == USING_VIRTUAL, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen, m_hTargetWnd);
m_ScreenSpyObject = new CScreenSpy(32, algo, DXGI == USING_VIRTUAL, DEFAULT_GOP, all, m_ScreenSettings.EncodeLevel, rect, switchScreen, m_hTargetWnd, m_bDynamicForeground);
}
}
@@ -651,10 +658,14 @@ DWORD WINAPI CScreenManager::WorkThreadProc(LPVOID lParam)
if (!This->IsConnected() && This->m_bIsWorking) This->OnReconnect();
if (!This->IsConnected()) continue;
if (!This->m_SendFirst && This->IsConnected()) {
This->m_SendFirst = TRUE;
This->SendBitMapInfo();
Sleep(50);
This->SendFirstScreen();
// 窗口捕获模式下目标窗口最小化时跳过首帧,等窗口恢复后再发
HWND _targetWnd = This->m_ScreenSpyObject ? This->m_ScreenSpyObject->GetTargetWindow() : NULL;
if (!_targetWnd || !IsIconic(_targetWnd)) {
This->m_SendFirst = TRUE;
This->SendBitMapInfo();
Sleep(50);
This->SendFirstScreen();
}
}
// 降低桌面检查频率避免频繁的DC重置导致闪屏
if (This->IsRunAsService() && !This->m_virtual) {
@@ -711,6 +722,10 @@ DWORD WINAPI CScreenManager::WorkThreadProc(LPVOID lParam)
}
// 窗口捕获:尺寸变化时在工作线程内原地重建,无需跨线程同步
if (This->m_ScreenSpyObject && This->m_ScreenSpyObject->m_bNeedRestart) {
// 动态前景模式切换了新窗口:把新 HWND 同步回 CScreenManager
// 否则 InitScreenSpy 会沿用旧 HWND导致无限重建循环
if (This->m_ScreenSpyObject->m_NextTargetWnd)
This->m_hTargetWnd = This->m_ScreenSpyObject->m_NextTargetWnd;
SAFE_DELETE(This->m_ScreenSpyObject);
This->InitScreenSpy();
This->m_SendFirst = FALSE; // 触发重发 BitmapInfo + 首帧
@@ -852,7 +867,10 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
// [mode:1][data] — mode=0x00 按标题, mode=0x01 按 HWND(uint64_t), 其余=恢复全屏
BYTE mode = (ulLength > 1) ? szBuffer[1] : 0xFF;
if (mode == 0x00 && ulLength > 2) {
const char* title = (const char*)(szBuffer + 2);
char title[512] = {};
int titleLen = ulLength - 2;
if (titleLen > (int)sizeof(title) - 1) titleLen = (int)sizeof(title) - 1;
memcpy(title, szBuffer + 2, titleLen);
m_hTargetWnd = title[0] ? FindWindowA(NULL, title) : NULL;
Mprintf("[CScreenManager] 窗口捕获(标题): '%s' -> HWND=%p\n", title, m_hTargetWnd);
} else if (mode == 0x01 && ulLength >= 2 + sizeof(uint64_t)) {
@@ -862,6 +880,11 @@ VOID CScreenManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
Mprintf("[CScreenManager] 窗口捕获(HWND): 0x%llx -> HWND=%p\n", val, m_hTargetWnd);
} else {
m_hTargetWnd = NULL;
m_bDynamicForeground = false;
// 防止 RestartScreen→InitScreenSpy 重新读取旧 HWND 再次进入窗口模式
if (m_pUserParam && m_pUserParam->length >= 3 + (int)sizeof(uint64_t)) {
memset(m_pUserParam->buffer + 3, 0, sizeof(uint64_t));
}
Mprintf("[CScreenManager] 窗口捕获取消,恢复全屏\n");
}
RestartScreen();
@@ -2087,7 +2110,8 @@ VOID CScreenManager::ProcessCommand(LPBYTE szBuffer, ULONG ulLength)
if (m_ScreenSpyObject) {
HWND hwndTarget = m_ScreenSpyObject->GetTargetWindow();
if (hwndTarget && IsWindow(hwndTarget) && !IsIconic(hwndTarget)) {
SetForegroundWindow(hwndTarget);
if (!SetForegroundWindow(hwndTarget))
return; // UIPI 等原因置前失败,不向错误窗口注入输入
}
}

View File

@@ -64,7 +64,8 @@ public:
BOOL m_bIsWorking;
BOOL m_bIsBlockInput;
RECT m_ROI = {0};
HWND m_hTargetWnd = NULL; // 窗口捕获目标NULL=全屏)
HWND m_hTargetWnd = NULL; // 窗口捕获目标NULL=全屏)
bool m_bDynamicForeground = false; // true=每帧自动跟踪前景窗口HWND sentinel=-1触发
BOOL m_SwitchScreen = TRUE;
BOOL SendClientClipboard(BOOL fast);
VOID UpdateClientClipboard(char *szBuffer, ULONG ulLength);

View File

@@ -13,10 +13,11 @@
//////////////////////////////////////////////////////////////////////
CScreenSpy::CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk, int gop, BOOL all, int level,
RECT rc, BOOL switchScreen, HWND hwnd) :
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 真实边界覆盖基类的全屏尺寸,并缓存阴影偏移量
@@ -160,7 +161,36 @@ LPBYTE CScreenSpy::GetFirstScreenData(ULONG* ulFirstScreenLength)
VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight)
{
if (m_hTargetWnd) {
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;

View File

@@ -95,7 +95,8 @@ protected:
BOOL m_bVirtualPaint;// 是否虚拟绘制
EnumHwndsPrintData m_data;
HWND m_hTargetWnd = NULL; // 窗口捕获目标NULL=全屏)
HWND m_hTargetWnd = NULL; // 窗口捕获目标NULL=全屏)
bool m_bDynamicForeground = false; // true=每帧跟踪前景窗口(由 sentinel=-1 触发)
int m_ShadowLeft = 0; // DWM 阴影左偏移frameRc.left - fullRc.left帧间缓存
int m_ShadowTop = 0; // DWM 阴影上偏移frameRc.top - fullRc.top帧间缓存
int m_CachedWndW = 0; // 上帧 GetWindowRect 宽度,用于检测 resize 无需每帧调 DWM
@@ -106,7 +107,7 @@ protected:
public:
CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk = FALSE, int gop = DEFAULT_GOP, BOOL all = FALSE,
int level = LEVEL_H264_SOFT, RECT rc = {0}, BOOL switchScreen = TRUE, HWND hwnd = NULL);
int level = LEVEL_H264_SOFT, RECT rc = {0}, BOOL switchScreen = TRUE, HWND hwnd = NULL, bool dynamicFg = false);
virtual HWND GetTargetWindow() const override { return m_hTargetWnd; }