diff --git a/linux/ScreenHandler.h b/linux/ScreenHandler.h index 9cfc46a..8fdcb35 100644 --- a/linux/ScreenHandler.h +++ b/linux/ScreenHandler.h @@ -651,11 +651,21 @@ public: // Double-check after acquiring lock if (m_destroyed) return; - // Prevent starting if thread is already running or joinable - if (m_captureThread.joinable()) return; + // If already running, just send TOKEN_BITMAPINFO again + // This allows server to create additional dialogs (MFC can open while Web is active) + if (m_captureThread.joinable() || m_running.load()) { + Mprintf(">>> ScreenHandler already running, sending TOKEN_BITMAPINFO for new dialog\n"); + SendBitmapInfo(); + return; + } bool expected = false; - if (!m_running.compare_exchange_strong(expected, true)) return; + if (!m_running.compare_exchange_strong(expected, true)) { + // Race condition: another thread started first, send bitmap info + Mprintf(">>> ScreenHandler race, sending TOKEN_BITMAPINFO for new dialog\n"); + SendBitmapInfo(); + return; + } m_captureThread = std::thread(&ScreenHandler::CaptureLoop, this); } diff --git a/macos/ScreenHandler.mm b/macos/ScreenHandler.mm index 6b0d30e..8e1f92b 100644 --- a/macos/ScreenHandler.mm +++ b/macos/ScreenHandler.mm @@ -156,7 +156,13 @@ bool ScreenHandler::init() void ScreenHandler::start(IOCPClient* client, uint64_t clientID) { - if (m_running) return; + // If already running, just send TOKEN_BITMAPINFO again + // This allows server to create additional dialogs (MFC can open while Web is active) + if (m_running) { + NSLog(@"ScreenHandler already running, sending TOKEN_BITMAPINFO for new dialog"); + sendBitmapInfo(); + return; + } m_client = client; m_clientID = clientID; diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index 90346b0..dad5ffe 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -3872,6 +3872,9 @@ BOOL CMy2015RemoteDlg::ShouldRemoteControl() void screenParamModifier(context* ctx, void* user) { + // Mark as MFC-triggered so dialog will be visible + WebService().SetMfcTriggered(ctx->GetClientID()); + auto version = ctx->GetClientData(ONLINELIST_VERSION); if (!IsDateGreaterOrEqual(version, "Feb 8 2026")) { char* param = (char*)user; @@ -6350,8 +6353,17 @@ LRESULT CMy2015RemoteDlg::OnOpenScreenSpyDialog(WPARAM wParam, LPARAM lParam) BYTE bToken = COMMAND_BYE; return ContextObject->Send2Client(&bToken, 1) ? 0 : 0x20260223; } - if (clientID && WebService().IsRunning() && WebService().IsWebTriggered(clientID) && WebService().GetHideWebSessions()) { - return OpenDialog(wParam, lParam); + // Check trigger source: MFC-triggered dialogs are always visible + // Note: Don't clear MfcTriggered here - let OnInitDialog check it to determine session type + if (clientID && WebService().IsRunning()) { + if (WebService().IsMfcTriggered(clientID)) { + // MFC-triggered: show dialog (flag will be cleared in OnInitDialog) + return OpenDialog(wParam, lParam); + } + if (WebService().IsWebTriggered(clientID) && WebService().GetHideWebSessions()) { + // Web-triggered: hide dialog (Web users share this hidden dialog) + return OpenDialog(wParam, lParam); + } } return OpenDialog(wParam, lParam); } @@ -7120,10 +7132,16 @@ void CMy2015RemoteDlg::OnDynamicSubMenu(UINT nID) } LeaveCriticalSection(&m_cs); } +// Mark as MFC-triggered when MFC opens remote desktop +void setMfcTriggeredCallback(context* ctx, void* user) +{ + WebService().SetMfcTriggered(ctx->GetClientID()); +} + void CMy2015RemoteDlg::OnOnlineVirtualDesktop() { BYTE bToken[32] = { COMMAND_SCREEN_SPY, 2, ALGORITHM_DIFF, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) }; - SendSelectedCommand(bToken, sizeof(bToken)); + SendSelectedCommand(bToken, sizeof(bToken), setMfcTriggeredCallback, nullptr); } @@ -7132,7 +7150,7 @@ void CMy2015RemoteDlg::OnOnlineGrayDesktop() if (!ShouldRemoteControl()) return; BYTE bToken[32] = { COMMAND_SCREEN_SPY, 0, ALGORITHM_GRAY, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) }; - SendSelectedCommand(bToken, sizeof(bToken)); + SendSelectedCommand(bToken, sizeof(bToken), setMfcTriggeredCallback, nullptr); } @@ -7141,7 +7159,7 @@ void CMy2015RemoteDlg::OnOnlineRemoteDesktop() if (!ShouldRemoteControl()) return; BYTE bToken[32] = { COMMAND_SCREEN_SPY, 1, ALGORITHM_DIFF, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) }; - SendSelectedCommand(bToken, sizeof(bToken)); + SendSelectedCommand(bToken, sizeof(bToken), setMfcTriggeredCallback, nullptr); } @@ -7150,7 +7168,7 @@ void CMy2015RemoteDlg::OnOnlineH264Desktop() if (!ShouldRemoteControl()) return; BYTE bToken[32] = { COMMAND_SCREEN_SPY, 0, ALGORITHM_H264, THIS_CFG.GetInt("settings", "MultiScreen", TRUE) }; - SendSelectedCommand(bToken, sizeof(bToken)); + SendSelectedCommand(bToken, sizeof(bToken), setMfcTriggeredCallback, nullptr); } @@ -8212,6 +8230,28 @@ void CMy2015RemoteDlg::CloseRemoteDesktopByClientID(uint64_t clientID) } } +void CMy2015RemoteDlg::CloseWebRemoteDesktopByClientID(uint64_t clientID) +{ + CScreenSpyDlg* targetDlg = nullptr; + HWND hWnd = NULL; + + EnterCriticalSection(&m_cs); + for (auto& pair : m_RemoteWnds) { + CScreenSpyDlg* dlg = dynamic_cast(pair.second); + // Only close Web session dialogs, leave MFC dialogs open + if (dlg && dlg->GetClientID() == clientID && dlg->IsWebSession()) { + targetDlg = dlg; + hWnd = dlg->GetSafeHwnd(); + break; + } + } + LeaveCriticalSection(&m_cs); + + if (targetDlg && hWnd && ::IsWindow(hWnd)) { + ::SendMessage(hWnd, WM_CLOSE, 0, 0); + } +} + void CMy2015RemoteDlg::UpdateActiveRemoteSession(CDialogBase *sess) { EnterCriticalSection(&m_cs); diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index 05bb7e4..388cd12 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -275,6 +275,7 @@ public: CDialogBase* GetRemoteWindow(CDialogBase* dlg); void RemoveRemoteWindow(HWND wnd); void CloseRemoteDesktopByClientID(uint64_t clientID); + void CloseWebRemoteDesktopByClientID(uint64_t clientID); // Only close Web session dialog CDialogBase* m_pActiveSession = nullptr; // 当前活动会话窗口指针 / NULL 表示无 void UpdateActiveRemoteSession(CDialogBase* sess); CDialogBase* GetActiveRemoteSession(); diff --git a/server/2015Remote/ScreenSpyDlg.cpp b/server/2015Remote/ScreenSpyDlg.cpp index a6fe50a..3d326a2 100644 --- a/server/2015Remote/ScreenSpyDlg.cpp +++ b/server/2015Remote/ScreenSpyDlg.cpp @@ -157,8 +157,9 @@ CScreenSpyDlg::CScreenSpyDlg(CMy2015RemoteDlg* Parent, Server* IOCPServer, CONTE if (pClientID) { m_ClientID = *((uint64_t*)pClientID); - // Notify web clients of resolution (important for clients that only send TOKEN_BITMAPINFO once) - if (WebService().IsRunning()) { + // Notify web clients of resolution (only for Web sessions, not MFC sessions) + // At this point, IsMfcTriggered is still set if MFC triggered this dialog + if (WebService().IsRunning() && !WebService().IsMfcTriggered(m_ClientID)) { int width = m_BitmapInfor_Full->bmiHeader.biWidth; int height = abs(m_BitmapInfor_Full->bmiHeader.biHeight); WebService().NotifyResolutionChange(m_ClientID, width, height); @@ -761,23 +762,43 @@ BOOL CScreenSpyDlg::OnInitDialog() if (pMain) ::PostMessage(pMain->GetSafeHwnd(), WM_SESSION_ACTIVATED, (WPARAM)this, 0); - // 注册屏幕上下文到 WebService(用于 Web 端鼠标/键盘控制) - WebService().RegisterScreenContext(m_ClientID, m_ContextObject); + // Determine session type: MFC or Web + // Must check MfcTriggered FIRST - if MFC triggered this dialog, it's NOT a web session + // even if WebTriggered is also true (happens when Web is already open for same device) + bool isMfcSession = WebService().IsMfcTriggered(m_ClientID); + bool isWebSession = false; + if (isMfcSession) { + // MFC session: clear the flag, don't register with WebService + WebService().ClearMfcTriggered(m_ClientID); + // m_bIsWebSession remains false (default) + } else { + // Check if this is a Web session + isWebSession = WebService().IsWebTriggered(m_ClientID) && WebService().GetHideWebSessions(); - // Hide window if this session was triggered by web client - if (WebService().IsWebTriggered(m_ClientID) && WebService().GetHideWebSessions()) { - m_bHide = true; - ShowWindow(SW_HIDE); + // Only register screen context for Web sessions + // MFC dialogs handle input directly via m_ContextObject, don't need WebService registry + // This prevents MFC close from deleting Web's context (they share same device_id key) + if (isWebSession) { + WebService().RegisterScreenContext(m_ClientID, m_ContextObject); + m_bHide = true; + m_bIsWebSession = true; + ShowWindow(SW_HIDE); + } } + Mprintf("[ScreenSpy] Dialog created for device %llu, isMfcSession=%d, isWebSession=%d\n", + m_ClientID, isMfcSession ? 1 : 0, isWebSession ? 1 : 0); + return TRUE; } VOID CScreenSpyDlg::OnClose() { - // 注销屏幕上下文(Web 端控制) - WebService().UnregisterScreenContext(m_ClientID); + // Only unregister if this is a Web session (we only registered for Web sessions) + if (m_bIsWebSession) { + WebService().UnregisterScreenContext(m_ClientID); + } m_bIsClosed = true; m_bIsCtrl = FALSE; @@ -964,18 +985,11 @@ VOID CScreenSpyDlg::OnReceiveComplete() PrepareDrawing(m_BitmapInfor_Full); // 分辨率切换完成,允许解码 m_bResolutionChanging = false; - // Notify web clients of resolution change - if (WebService().IsRunning()) { + // Notify web clients of resolution change (only for Web session dialogs) + if (m_bIsWebSession && WebService().IsRunning()) { int width = m_BitmapInfor_Full->bmiHeader.biWidth; int height = abs(m_BitmapInfor_Full->bmiHeader.biHeight); WebService().NotifyResolutionChange(m_ClientID, width, height); - - // Hide window if this session was triggered by web client (and hiding is enabled) - if (WebService().IsWebTriggered(m_ClientID) && WebService().GetHideWebSessions()) { - m_bHide = true; - ShowWindow(SW_HIDE); - Mprintf("[ScreenSpyDlg] Web-triggered session, hiding window for device %llu\n", m_ClientID); - } } break; } @@ -1266,8 +1280,8 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame) m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2+sizeof(POINT))[0]; if (bOldCursorIndex != m_bCursorIndex) { bChange = TRUE; - // 通知 Web 客户端光标变化 - if (WebService().IsRunning()) { + // 通知 Web 客户端光标变化 (只有 Web 会话的对话框才广播) + if (m_bIsWebSession && WebService().IsRunning()) { WebService().BroadcastCursor(m_ClientID, m_bCursorIndex); } if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构 @@ -1317,8 +1331,8 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame) bChange = TRUE; } } - // Broadcast H264 keyframe to web clients - if (NextScreenLength > 0 && WebService().IsRunning()) { + // Broadcast H264 keyframe to web clients (only for Web session dialogs) + if (m_bIsWebSession && NextScreenLength > 0 && WebService().IsRunning()) { std::vector packet(4 + 1 + 4 + NextScreenLength); uint32_t deviceIdLow = (uint32_t)(m_ClientID & 0xFFFFFFFF); uint8_t frameType = 1; // Keyframe @@ -1376,9 +1390,9 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame) bChange = TRUE; } } - // Broadcast H264 frame to web clients + // Broadcast H264 frame to web clients (only for Web session dialogs) // Format: [DeviceID:4][FrameType:1][DataLen:4][H264Data:N] - if (NextScreenLength > 0 && WebService().IsRunning()) { + if (m_bIsWebSession && NextScreenLength > 0 && WebService().IsRunning()) { // Detect H264 keyframe by checking NAL unit type // NAL type 5 = IDR slice (keyframe), NAL type 7 = SPS, NAL type 8 = PPS bool isKeyFrame = false; @@ -1463,8 +1477,8 @@ VOID CScreenSpyDlg::DrawScrollFrame() m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2 + sizeof(POINT))[0]; if (bOldCursorIndex != m_bCursorIndex) { bChange = TRUE; - // 通知 Web 客户端光标变化 - if (WebService().IsRunning()) { + // 通知 Web 客户端光标变化 (只有 Web 会话的对话框才广播) + if (m_bIsWebSession && WebService().IsRunning()) { WebService().BroadcastCursor(m_ClientID, m_bCursorIndex); } } diff --git a/server/2015Remote/ScreenSpyDlg.h b/server/2015Remote/ScreenSpyDlg.h index 9ea193e..ac588e6 100644 --- a/server/2015Remote/ScreenSpyDlg.h +++ b/server/2015Remote/ScreenSpyDlg.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "IOCPServer.h" #include "..\..\client\CursorInfo.h" #include "VideoDlg.h" @@ -153,6 +154,10 @@ public: return TRUE; } + // Check if this dialog was created by Web request (shared by Web users) + bool IsWebSession() const { return m_bIsWebSession.load(); } + void SetWebSession(bool isWeb) { m_bIsWebSession.store(isWeb); } + VOID SendNext(void); VOID OnReceiveComplete(); HDC m_hFullDC; @@ -186,6 +191,7 @@ public: int m_FrameID; HIMC m_hOldIMC = NULL; // 保存原始 IME 上下文,控制模式切换时使用 bool m_bHide = false; + std::atomic m_bIsWebSession{false}; // True if this dialog was created by Web request (atomic for thread safety) std::string m_strSaveNotice; // 截图保存路径提示 ULONGLONG m_nSaveNoticeTime = 0; // 截图提示开始时间 BOOL m_bUsingFRP = FALSE; diff --git a/server/2015Remote/WebService.cpp b/server/2015Remote/WebService.cpp index df59cc3..27bb755 100644 --- a/server/2015Remote/WebService.cpp +++ b/server/2015Remote/WebService.cpp @@ -1652,9 +1652,13 @@ bool CWebService::StartRemoteDesktop(uint64_t device_id) { context* ctx = m_pParentDlg->FindHost(device_id); if (!ctx) return false; - // Close any existing remote desktop for this device first - // This prevents duplicate dialogs when user reconnects quickly - m_pParentDlg->CloseRemoteDesktopByClientID(device_id); + // Check if there's already a Web session for this device + // Only reuse if Web has already triggered AND a Web dialog exists + // This ensures MFC and Web have independent dialogs + if (IsWebTriggered(device_id) && HasActiveSession(device_id)) { + Mprintf("[WebService] Reusing existing Web session for device %llu\n", device_id); + return true; // Web session exists, new web user joins watching + } // Mark as web-triggered (dialog should be hidden) { @@ -1663,7 +1667,8 @@ bool CWebService::StartRemoteDesktop(uint64_t device_id) { } // Send COMMAND_SCREEN_SPY with H264 algorithm - // Format: [COMMAND_SCREEN_SPY:1][DXGI:1][Algorithm:1][MultiScreen:1] + // If client is already capturing (MFC opened first), it will re-send TOKEN_BITMAPINFO + // This creates a new hidden Web dialog while MFC dialog remains visible BYTE bToken[32] = { 0 }; bToken[0] = COMMAND_SCREEN_SPY; bToken[1] = 0; // DXGI mode: 0=GDI @@ -1687,10 +1692,11 @@ void CWebService::StopRemoteDesktop(uint64_t device_id) { } } - // If no more web clients watching, close the remote desktop + // If no more web clients watching, close only the Web session dialog + // MFC dialogs remain open if (watchingCount == 0) { ClearWebTriggered(device_id); - m_pParentDlg->CloseRemoteDesktopByClientID(device_id); + m_pParentDlg->CloseWebRemoteDesktopByClientID(device_id); } } @@ -1706,10 +1712,13 @@ void CWebService::RegisterScreenContext(uint64_t device_id, CONTEXT_OBJECT* ctx) } void CWebService::UnregisterScreenContext(uint64_t device_id) { - if (!m_bRunning) return; + // Always clean up, even if WebService is stopping + // This prevents stale pointers in m_ScreenContexts std::lock_guard lock(m_ScreenContextsMutex); m_ScreenContexts.erase(device_id); - Mprintf("[WebService] Unregistered screen context for device %llu\n", device_id); + if (m_bRunning) { + Mprintf("[WebService] Unregistered screen context for device %llu\n", device_id); + } } CONTEXT_OBJECT* CWebService::GetScreenContext(uint64_t device_id) { @@ -1809,6 +1818,26 @@ void CWebService::ClearWebTriggered(uint64_t device_id) { m_WebTriggeredDevices.erase(device_id); } +void CWebService::SetMfcTriggered(uint64_t device_id) { + std::lock_guard lock(m_MfcTriggeredMutex); + m_MfcTriggeredDevices.insert(device_id); +} + +bool CWebService::IsMfcTriggered(uint64_t device_id) { + std::lock_guard lock(m_MfcTriggeredMutex); + return m_MfcTriggeredDevices.find(device_id) != m_MfcTriggeredDevices.end(); +} + +void CWebService::ClearMfcTriggered(uint64_t device_id) { + std::lock_guard lock(m_MfcTriggeredMutex); + m_MfcTriggeredDevices.erase(device_id); +} + +bool CWebService::HasActiveSession(uint64_t device_id) { + std::lock_guard lock(m_ScreenContextsMutex); + return m_ScreenContexts.find(device_id) != m_ScreenContexts.end(); +} + void CWebService::NotifyDeviceUpdate(uint64_t device_id, const std::string& rtt, const std::string& activeWindow) { if (!m_bRunning || m_bStopping) return; diff --git a/server/2015Remote/WebService.h b/server/2015Remote/WebService.h index 196f42b..b206d9f 100644 --- a/server/2015Remote/WebService.h +++ b/server/2015Remote/WebService.h @@ -227,6 +227,14 @@ public: bool IsWebTriggered(uint64_t device_id); void ClearWebTriggered(uint64_t device_id); + // MFC trigger management - MFC dialogs should always be visible + void SetMfcTriggered(uint64_t device_id); + bool IsMfcTriggered(uint64_t device_id); + void ClearMfcTriggered(uint64_t device_id); + + // Check if a remote desktop session already exists for device + bool HasActiveSession(uint64_t device_id); + // Config accessors void SetHideWebSessions(bool hide) { m_bHideWebSessions = hide; } bool GetHideWebSessions() const { return m_bHideWebSessions; } @@ -243,6 +251,10 @@ private: // Screen context registry: device_id -> ScreenManager's CONTEXT_OBJECT std::map m_ScreenContexts; std::mutex m_ScreenContextsMutex; + + // MFC triggered devices: dialogs created by MFC should always be visible + std::set m_MfcTriggeredDevices; + std::mutex m_MfcTriggeredMutex; }; // Global accessor