Feature: Add MCP remote_open/remote_close remote control sessions

Add M1 of MCP remote control (docs/Mcp_RemoteControl_Design.md): the
remote_open / remote_close tools plus the ScreenCtrlSession state machine.

remote_open establishes a hidden screen sub-connection by reusing
WebService::StartRemoteDesktop (COMMAND_SCREEN_SPY -> CScreenSpyDlg ->
RegisterScreenContext), polls for the sub-connection plus its physical
resolution (TOKEN_BITMAPINFO -> NotifyResolutionChange -> GetScreenSize),
then records the session (single device, single session, reverse-mapped
subCtx for OfflineProc cleanup) and returns {session_id, screen_w,
screen_h}. remote_close validates session_id and tears down the
sub-connection idempotently. A McpRemoteControl settings checkbox (default
off, requires McpReadonly=0) gates the tools; every open/close is audited
via WM_SHOWERRORMSG.

Gating: multi-monitor hosts are rejected with -32008 (phase 1 supports only
single monitor, where Observe=main screen and Act=virtual desktop coincide);
the monitor count comes from the client heartbeat RES_RESOLUTION ("N:W*H").

Known limitations (deferred to the injection milestones): mutual exclusion
with human remote-desktop viewing is one-directional in M1 (a human who
joins during an MCP session can tear it down on disconnect), and subCtx is
not yet dereferenced so no liveness re-check is needed until
remote_mouse/remote_keyboard.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-25 12:55:52 +02:00
parent 84e3565de1
commit 6045baadb8
7 changed files with 469 additions and 4 deletions

View File

@@ -157,6 +157,34 @@ public:
// idle 回收:关闭 lastActiveAt 超时且非 busy 的持久会话。返回回收数量。
int SweepIdleTerminals(time_t idleTimeoutSec);
// ===== P5MCP 远程控制remote_open / remote_close / remote_mouse / remote_keyboard=====
// 屏幕子连接由 WebService::StartRemoteDesktop 建立的隐藏 CScreenSpyDlg 持有(复用 Web
// 路径,见 docs/Mcp_RemoteControl_Design.md §6.3);会话仅记录逻辑状态 + 子连接指针。
// 注入走该子连接COMMAND_SCREEN_CONTROL与终端一样单设备单会话。
void SetRemoteControlEnabled(bool enabled) { m_remoteControlEnabled = enabled; }
bool IsRemoteControlEnabled() const { return m_remoteControlEnabled; }
// 该 host 的屏幕子连接是否被 MCP 远程控制会话持有OfflineProc 反查用)。
bool IsScreenCtrlContext(context* subCtx);
// 登记控制会话单设备单会话sessionId 由调用方生成。false = 该设备已有会话。
bool BeginScreenCtrlOpen(uint64_t device_id, const std::string& sessionId);
// 子连接就绪HasActiveSession && 分辨率到达)后调用:填 subCtx/分辨率并置 started。
// false = 会话已被并发关闭(调用方应放弃本次 open
bool MarkScreenCtrlReady(uint64_t device_id, const std::string& sessionId,
context* subCtx, int screenW, int screenH);
// 关闭控制会话(校验 sessionId擦路由 + 会话)。屏幕子连接的关闭由调用方锁外执行
// CloseWebRemoteDesktopByClientID。返回 0=已关1=不存在幂等2=sessionId 不匹配。
int CloseScreenCtrlSession(uint64_t device_id, const std::string& sessionId);
// idle 回收:关闭 lastActiveAt 超时的控制会话,并锁外关闭其隐藏对话框。返回回收数量。
int SweepIdleScreenCtrl(time_t idleTimeoutSec);
// 屏幕子连接断开OfflineProc 调用):擦除该子连接对应的会话 + 路由(幂等)。
void OnScreenControlClosed(context* subCtx);
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
void SetReadonly(bool readonly) { m_readonly = readonly; }
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
@@ -220,9 +248,23 @@ private:
std::map<uint64_t, TermSession> m_TermSessions; // device_id → 会话
std::map<context*, uint64_t> m_TermContextToDevice; // subCtx → device_id顶部路由
// ===== P5远程控制会话受 m_ScreenCtrlMutex 保护;单设备单会话)=====
struct ScreenCtrlSession {
std::string sessionId; // 会话 token每次 open 独立随机)
context* subCtx = nullptr; // 屏幕子连接上下文(就绪后填;用于注入)
bool started = false; // false=子连接建立中true=已就绪
time_t lastActiveAt = 0; // idle 回收用(秒)
int screenW = 0; // 物理捕获分辨率(来自 TOKEN_BITMAPINFO
int screenH = 0;
};
std::mutex m_ScreenCtrlMutex;
std::map<uint64_t, ScreenCtrlSession> m_ScreenCtrlSessions; // device_id → 会话
std::map<context*, uint64_t> m_ScreenCtrlContextToDevice; // subCtx → device_idOfflineProc 反查)
bool m_readonly = true;
std::string m_cmdWhitelist;
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false
bool m_terminalEnabled = false; // 持久终端开关(默认关;要求 m_readonly=false
bool m_remoteControlEnabled = false; // 远程控制开关(默认关;要求 m_readonly=false
};
// 全局访问器(仿 WebService(),见 WebService.h 末尾)