Feature: MCP 远程控制(remote_open/close/keyboard/mouse/clipboard) #4
@@ -1132,8 +1132,11 @@ Json::Value BuildRemoteOpenInputSchema();
|
|||||||
Json::Value BuildRemoteOpenOutputSchema();
|
Json::Value BuildRemoteOpenOutputSchema();
|
||||||
Json::Value BuildRemoteCloseInputSchema();
|
Json::Value BuildRemoteCloseInputSchema();
|
||||||
Json::Value BuildRemoteCloseOutputSchema();
|
Json::Value BuildRemoteCloseOutputSchema();
|
||||||
|
Json::Value BuildRemoteKeyboardInputSchema();
|
||||||
|
Json::Value BuildRemoteKeyboardOutputSchema();
|
||||||
std::string BuildRemoteOpen(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
std::string BuildRemoteOpen(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
std::string BuildRemoteClose(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
std::string BuildRemoteClose(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
|
std::string BuildRemoteKeyboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent);
|
||||||
|
|
||||||
// tools/list
|
// tools/list
|
||||||
std::string BuildToolsListResult(const Json::Value& id) {
|
std::string BuildToolsListResult(const Json::Value& id) {
|
||||||
@@ -1357,6 +1360,14 @@ std::string BuildToolsListResult(const Json::Value& id) {
|
|||||||
tool["outputSchema"] = BuildRemoteCloseOutputSchema();
|
tool["outputSchema"] = BuildRemoteCloseOutputSchema();
|
||||||
tools.append(tool);
|
tools.append(tool);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
Json::Value tool(Json::objectValue);
|
||||||
|
tool["name"] = "remote_keyboard";
|
||||||
|
tool["description"] = u8"向已建立的远程控制会话注入键盘事件。action 可选 key_down / key_up / key_press / type:前三种需 key(Windows 虚拟键名,如 ENTER/TAB/F5/LEFT/CTRL/ALT/SHIFT/WIN),type 需 text(仅 ASCII 文本;非 ASCII 请用 remote_clipboard + Ctrl+V)。modifiers 可选(CTRL/ALT/SHIFT/WIN)。";
|
||||||
|
tool["inputSchema"] = BuildRemoteKeyboardInputSchema();
|
||||||
|
tool["outputSchema"] = BuildRemoteKeyboardOutputSchema();
|
||||||
|
tools.append(tool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result["tools"] = tools;
|
result["tools"] = tools;
|
||||||
@@ -2845,6 +2856,263 @@ std::string BuildRemoteClose(const Json::Value& id, const Json::Value& args, CMy
|
|||||||
return BuildResult(id, result);
|
return BuildResult(id, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== P5:remote_keyboard(键盘注入,key_down / key_up / key_press / type)=====
|
||||||
|
|
||||||
|
// 键名 → 虚拟键码(对齐 Windows VK_* 去掉前缀,见设计 §4.3)。
|
||||||
|
// 支持:单字符 A-Z/0-9、F1-F24、命名键、修饰键。未知返回 0(VK 0 非真实键)。
|
||||||
|
static int MapKeyNameToVk(const std::string& nameIn) {
|
||||||
|
std::string n;
|
||||||
|
for (char c : nameIn)
|
||||||
|
n += (c >= 'a' && c <= 'z') ? (char)(c - 'a' + 'A') : c;
|
||||||
|
|
||||||
|
if (n.size() == 1) {
|
||||||
|
unsigned char c = (unsigned char)n[0];
|
||||||
|
if (c >= 'A' && c <= 'Z') return c; // 'A'..'Z' == VK_A..VK_Z
|
||||||
|
if (c >= '0' && c <= '9') return c; // '0'..'9' == VK_0..VK_9
|
||||||
|
return 0; // 标点等请走 type
|
||||||
|
}
|
||||||
|
|
||||||
|
// F1..F24
|
||||||
|
if (n.size() >= 2 && n.size() <= 3 && n[0] == 'F') {
|
||||||
|
int fnum = atoi(n.c_str() + 1);
|
||||||
|
if (fnum >= 1 && fnum <= 24) return VK_F1 + (fnum - 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Named { const char* name; int vk; };
|
||||||
|
static const Named kNames[] = {
|
||||||
|
{"ENTER", VK_RETURN}, {"RETURN", VK_RETURN}, {"TAB", VK_TAB}, {"SPACE", VK_SPACE},
|
||||||
|
{"ESC", VK_ESCAPE}, {"ESCAPE", VK_ESCAPE}, {"BACKSPACE", VK_BACK},
|
||||||
|
{"DELETE", VK_DELETE}, {"DEL", VK_DELETE}, {"INSERT", VK_INSERT}, {"INS", VK_INSERT},
|
||||||
|
{"HOME", VK_HOME}, {"END", VK_END}, {"PAGEUP", VK_PRIOR}, {"PRIOR", VK_PRIOR},
|
||||||
|
{"PAGEDOWN", VK_NEXT}, {"NEXT", VK_NEXT},
|
||||||
|
{"LEFT", VK_LEFT}, {"RIGHT", VK_RIGHT}, {"UP", VK_UP}, {"DOWN", VK_DOWN},
|
||||||
|
{"CAPSLOCK", VK_CAPITAL}, {"CAPITAL", VK_CAPITAL}, {"NUMLOCK", VK_NUMLOCK},
|
||||||
|
{"SCROLLLOCK", VK_SCROLL}, {"PRINTSCREEN", VK_SNAPSHOT}, {"SNAPSHOT", VK_SNAPSHOT},
|
||||||
|
{"PAUSE", VK_PAUSE}, {"APPS", VK_APPS}, {"CONTEXTMENU", VK_APPS},
|
||||||
|
{"CTRL", VK_CONTROL}, {"CONTROL", VK_CONTROL}, {"ALT", VK_MENU}, {"MENU", VK_MENU},
|
||||||
|
{"SHIFT", VK_SHIFT}, {"WIN", VK_LWIN}, {"WINDOWS", VK_LWIN}, {"CMD", VK_LWIN}, {"SUPER", VK_LWIN},
|
||||||
|
};
|
||||||
|
for (const Named& e : kNames)
|
||||||
|
if (n == e.name) return e.vk;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否修饰键(modifiers 白名单:CTRL/ALT/SHIFT/WIN)
|
||||||
|
static bool IsModifierVk(int vk) {
|
||||||
|
return vk == VK_CONTROL || vk == VK_MENU || vk == VK_SHIFT || vk == VK_LWIN || vk == VK_RWIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析可选 modifiers 数组(JSON 字符串数组)→ 修饰键 VK 序列。非法返回 false 并填 err。
|
||||||
|
static bool ParseModifiers(const Json::Value& args, std::vector<int>& out, std::string& err) {
|
||||||
|
out.clear();
|
||||||
|
if (!args.isMember("modifiers")) return true;
|
||||||
|
const Json::Value& mods = args["modifiers"];
|
||||||
|
if (mods.isNull()) return true;
|
||||||
|
if (!mods.isArray()) { err = "modifiers must be an array of strings"; return false; }
|
||||||
|
for (const auto& m : mods) {
|
||||||
|
if (out.size() >= 8) { err = "too many modifiers (max 8)"; return false; }
|
||||||
|
int vk = MapKeyNameToVk(m.asString());
|
||||||
|
if (vk == 0 || !IsModifierVk(vk)) {
|
||||||
|
err = "invalid modifier: " + (m.isString() ? m.asString() : std::string("(non-string)"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.push_back(vk);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否纯 ASCII(type 走物理键事件,只能可靠注入 ASCII/ANSI;见设计 §6.4)
|
||||||
|
static bool IsAscii(const std::string& s) {
|
||||||
|
for (unsigned char c : s) if (c >= 0x80) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// type:逐字符 VkKeyScanA 映射为「(Shift↓)字符↓ 字符↑(Shift↑)」,回车/制表符映射为按键。
|
||||||
|
// 注:VkKeyScanA 在服务端按服务端线程键盘布局解析字符→虚拟键;仅限 ASCII(0..127)时
|
||||||
|
// 各布局的字母/数字/常用标点虚拟键一致,跨布局差异可忽略(非 ASCII 一律走剪贴板)。
|
||||||
|
static void BuildTypeBatch(const std::string& text, std::vector<MSG64>& batch) {
|
||||||
|
for (unsigned char ch : text) {
|
||||||
|
if (ch == '\r' || ch == '\n') {
|
||||||
|
batch.push_back(BuildKeyMsg64(VK_RETURN, true, false));
|
||||||
|
batch.push_back(BuildKeyMsg64(VK_RETURN, false, false));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch == '\t') {
|
||||||
|
batch.push_back(BuildKeyMsg64(VK_TAB, true, false));
|
||||||
|
batch.push_back(BuildKeyMsg64(VK_TAB, false, false));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SHORT scan = VkKeyScanA((char)ch);
|
||||||
|
if (scan == -1) continue; // 无法映射(控制字符等)→ 跳过
|
||||||
|
int vk = scan & 0xFF;
|
||||||
|
bool needShift = (scan >> 8) & 1;
|
||||||
|
if (needShift) batch.push_back(BuildKeyMsg64(VK_SHIFT, true, false));
|
||||||
|
batch.push_back(BuildKeyMsg64(vk, true, false));
|
||||||
|
batch.push_back(BuildKeyMsg64(vk, false, false));
|
||||||
|
if (needShift) batch.push_back(BuildKeyMsg64(VK_SHIFT, false, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildRemoteKeyboardInputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
Json::Value idProp(Json::objectValue);
|
||||||
|
idProp["type"] = "string";
|
||||||
|
idProp["description"] = u8"主机 id,取 list_online_hosts / search_hosts 返回的 id 字段";
|
||||||
|
props["id"] = idProp;
|
||||||
|
|
||||||
|
Json::Value sid(Json::objectValue);
|
||||||
|
sid["type"] = "string";
|
||||||
|
sid["description"] = u8"remote_open 返回的 session_id";
|
||||||
|
props["session_id"] = sid;
|
||||||
|
|
||||||
|
Json::Value act(Json::objectValue);
|
||||||
|
act["type"] = "string";
|
||||||
|
act["description"] = u8"动作:key_down / key_up / key_press / type";
|
||||||
|
props["action"] = act;
|
||||||
|
|
||||||
|
Json::Value key(Json::objectValue);
|
||||||
|
key["type"] = "string";
|
||||||
|
key["description"] = u8"按键(key_down/key_up/key_press 用):Windows 虚拟键名(如 ENTER/TAB/F5/LEFT/CTRL/ALT/SHIFT/WIN)或单个字母/数字";
|
||||||
|
props["key"] = key;
|
||||||
|
|
||||||
|
Json::Value text(Json::objectValue);
|
||||||
|
text["type"] = "string";
|
||||||
|
text["description"] = u8"文本(type 用,仅 ASCII;非 ASCII 请走 remote_clipboard + Ctrl+V)";
|
||||||
|
props["text"] = text;
|
||||||
|
|
||||||
|
Json::Value mods(Json::objectValue);
|
||||||
|
mods["type"] = "array";
|
||||||
|
Json::Value modItem(Json::objectValue);
|
||||||
|
modItem["type"] = "string";
|
||||||
|
mods["items"] = modItem;
|
||||||
|
mods["description"] = u8"修饰键(可选):CTRL/ALT/SHIFT/WIN";
|
||||||
|
props["modifiers"] = mods;
|
||||||
|
|
||||||
|
Json::Value schema(Json::objectValue);
|
||||||
|
schema["type"] = "object";
|
||||||
|
schema["properties"] = props;
|
||||||
|
Json::Value required(Json::arrayValue);
|
||||||
|
required.append("id");
|
||||||
|
required.append("session_id");
|
||||||
|
required.append("action");
|
||||||
|
schema["required"] = required;
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value BuildRemoteKeyboardOutputSchema() {
|
||||||
|
Json::Value props(Json::objectValue);
|
||||||
|
Json::Value schema(Json::objectValue);
|
||||||
|
schema["type"] = "object";
|
||||||
|
schema["properties"] = props;
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tools/call:remote_keyboard(键盘注入;成功返回空对象)
|
||||||
|
std::string BuildRemoteKeyboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
|
||||||
|
CMcpServer& mcp = CMcpServer::Instance();
|
||||||
|
mcp.SweepIdleScreenCtrl(kScreenCtrlIdleTimeoutSec);
|
||||||
|
|
||||||
|
uint64_t devId = 0;
|
||||||
|
context* ctx = nullptr;
|
||||||
|
std::string errJson;
|
||||||
|
if (!ResolveScreenCtrlSessionHost(id, args, parent, devId, ctx, errJson))
|
||||||
|
return errJson;
|
||||||
|
|
||||||
|
std::string sessionId = GetStringArg(args, "session_id");
|
||||||
|
if (sessionId.empty())
|
||||||
|
return BuildError(id, -32602, "Missing required parameter: session_id");
|
||||||
|
|
||||||
|
std::string action = GetStringArg(args, "action");
|
||||||
|
if (action.empty())
|
||||||
|
return BuildError(id, -32602, "Missing required parameter: action");
|
||||||
|
|
||||||
|
std::string keyName = GetStringArg(args, "key");
|
||||||
|
std::string text = GetStringArg(args, "text");
|
||||||
|
std::vector<int> mods;
|
||||||
|
{
|
||||||
|
std::string mErr;
|
||||||
|
if (!ParseModifiers(args, mods, mErr))
|
||||||
|
return BuildError(id, -32602, mErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装注入批次(按发送顺序)
|
||||||
|
std::vector<MSG64> batch;
|
||||||
|
if (action == "type") {
|
||||||
|
if (text.empty())
|
||||||
|
return BuildError(id, -32602, "Missing required parameter: text");
|
||||||
|
if (!IsAscii(text))
|
||||||
|
return BuildError(id, -32602, "text must be ASCII; non-ASCII text requires remote_clipboard + Ctrl+V");
|
||||||
|
if (text.size() > 1024)
|
||||||
|
return BuildError(id, -32602, "text too long (max 1024 bytes); use remote_clipboard + Ctrl+V for long text");
|
||||||
|
BuildTypeBatch(text, batch);
|
||||||
|
} else if (action == "key_down" || action == "key_up" || action == "key_press") {
|
||||||
|
if (keyName.empty())
|
||||||
|
return BuildError(id, -32602, "Missing required parameter: key");
|
||||||
|
int keyVk = MapKeyNameToVk(keyName);
|
||||||
|
if (keyVk == 0)
|
||||||
|
return BuildError(id, -32602, "Unknown key: " + keyName);
|
||||||
|
bool doDown = (action != "key_up");
|
||||||
|
bool doUp = (action != "key_down");
|
||||||
|
if (doDown) {
|
||||||
|
for (int m : mods) batch.push_back(BuildKeyMsg64(m, true, false));
|
||||||
|
batch.push_back(BuildKeyMsg64(keyVk, true, false));
|
||||||
|
}
|
||||||
|
if (doUp) {
|
||||||
|
batch.push_back(BuildKeyMsg64(keyVk, false, false));
|
||||||
|
for (auto it = mods.rbegin(); it != mods.rend(); ++it)
|
||||||
|
batch.push_back(BuildKeyMsg64(*it, false, false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return BuildError(id, -32602, "Unknown action: " + action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch.empty())
|
||||||
|
return BuildError(id, -32602, "No key events generated");
|
||||||
|
|
||||||
|
// 校验 session_id/busy 并置 busy(注入期间 close/sweep/断线不擦会话,镜像终端 busy 模式)。
|
||||||
|
context* subCtx = nullptr;
|
||||||
|
int screenW = 0, screenH = 0;
|
||||||
|
int r = mcp.BeginScreenCtrlAction(devId, sessionId, subCtx, screenW, screenH);
|
||||||
|
if (r == 2) return BuildError(id, -32003, "Device busy: another injection is in flight for this session");
|
||||||
|
if (r == 1) return BuildError(id, -32002, "Remote control session not found, not ready, or closed: " + sessionId);
|
||||||
|
|
||||||
|
// 组包 [COMMAND_SCREEN_CONTROL][MSG64*N],经屏幕子连接发送(主连接无效,设计 P2)。
|
||||||
|
const int len = (int)(1 + batch.size() * sizeof(MSG64));
|
||||||
|
std::vector<BYTE> packet((size_t)len);
|
||||||
|
packet[0] = COMMAND_SCREEN_CONTROL;
|
||||||
|
memcpy(packet.data() + 1, batch.data(), batch.size() * sizeof(MSG64));
|
||||||
|
bool ok = subCtx->Send2Client(packet.data(), (ULONG)len) != FALSE;
|
||||||
|
|
||||||
|
mcp.EndScreenCtrlAction(devId, sessionId);
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
return BuildError(id, -32004, "Failed to send keyboard injection");
|
||||||
|
|
||||||
|
// 审计(不可关闭)
|
||||||
|
if (parent) {
|
||||||
|
std::string audit = "host " + std::to_string(devId) + " remote-keyboard: session " + sessionId
|
||||||
|
+ " action=" + action;
|
||||||
|
if (action == "type") audit += " text=" + text;
|
||||||
|
else audit += " key=" + keyName;
|
||||||
|
parent->PostMessageA(WM_SHOWERRORMSG,
|
||||||
|
(WPARAM)new CString(ToAnsi(audit, 936).c_str()),
|
||||||
|
(LPARAM)new CString(_TR("MCP远程控制")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value result(Json::objectValue);
|
||||||
|
result["structuredContent"] = Json::Value(Json::objectValue);
|
||||||
|
Json::Value content(Json::arrayValue);
|
||||||
|
Json::Value item(Json::objectValue);
|
||||||
|
item["type"] = "text";
|
||||||
|
item["text"] = std::string(u8"ok");
|
||||||
|
content.append(item);
|
||||||
|
result["content"] = content;
|
||||||
|
result["isError"] = false;
|
||||||
|
|
||||||
|
return BuildResult(id, result);
|
||||||
|
}
|
||||||
|
|
||||||
// tools/call 分派
|
// tools/call 分派
|
||||||
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
|
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
|
||||||
const Json::Value& id = root["id"];
|
const Json::Value& id = root["id"];
|
||||||
@@ -2875,6 +3143,7 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
|
|||||||
if (toolName == "terminal_close") return BuildTerminalClose(id, args, parent);
|
if (toolName == "terminal_close") return BuildTerminalClose(id, args, parent);
|
||||||
if (toolName == "remote_open") return BuildRemoteOpen(id, args, parent);
|
if (toolName == "remote_open") return BuildRemoteOpen(id, args, parent);
|
||||||
if (toolName == "remote_close") return BuildRemoteClose(id, args, parent);
|
if (toolName == "remote_close") return BuildRemoteClose(id, args, parent);
|
||||||
|
if (toolName == "remote_keyboard") return BuildRemoteKeyboard(id, args, parent);
|
||||||
|
|
||||||
return BuildError(id, -32602,
|
return BuildError(id, -32602,
|
||||||
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
|
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
|
||||||
|
|||||||
@@ -907,41 +907,8 @@ void CWebService::HandleKey(void* ws_ptr, const std::string& msg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build MSG64 structure
|
// Build MSG64 structure(共享 helper,见 WebService.h;与 MCP remote_keyboard 复用)
|
||||||
MSG64 msg64;
|
MSG64 msg64 = BuildKeyMsg64(keyCode, isDown, altKey);
|
||||||
memset(&msg64, 0, sizeof(MSG64));
|
|
||||||
|
|
||||||
// Use WM_SYSKEYDOWN/UP for Alt combinations (same as MFC version)
|
|
||||||
if (altKey) {
|
|
||||||
msg64.message = isDown ? WM_SYSKEYDOWN : WM_SYSKEYUP;
|
|
||||||
} else {
|
|
||||||
msg64.message = isDown ? WM_KEYDOWN : WM_KEYUP;
|
|
||||||
}
|
|
||||||
msg64.wParam = keyCode;
|
|
||||||
msg64.time = GetTickCount();
|
|
||||||
|
|
||||||
// Build lParam for keyboard message:
|
|
||||||
// bits 0-15: repeat count (1)
|
|
||||||
// bits 16-23: scan code
|
|
||||||
// bit 24: extended key flag
|
|
||||||
// bit 29: context code (1 if Alt is pressed)
|
|
||||||
// bit 30: previous key state (1 for keyup)
|
|
||||||
// bit 31: transition state (1 for keyup)
|
|
||||||
UINT scanCode = MapVirtualKey(keyCode, MAPVK_VK_TO_VSC);
|
|
||||||
|
|
||||||
// Extended keys: arrows, insert, delete, home, end, page up/down, numpad enter, etc.
|
|
||||||
bool isExtended = (keyCode >= VK_PRIOR && keyCode <= VK_DOWN) || // Page Up/Down, End, Home, Arrows
|
|
||||||
keyCode == VK_INSERT || keyCode == VK_DELETE ||
|
|
||||||
keyCode == VK_NUMLOCK || keyCode == VK_RCONTROL || keyCode == VK_RMENU ||
|
|
||||||
keyCode == VK_APPS;
|
|
||||||
|
|
||||||
LPARAM lParam = 1; // repeat count = 1
|
|
||||||
lParam |= (scanCode & 0xFF) << 16;
|
|
||||||
if (isExtended) lParam |= (1 << 24);
|
|
||||||
if (altKey) lParam |= (1 << 29); // context code for Alt
|
|
||||||
if (!isDown) lParam |= (3UL << 30); // bit 30 and 31 set for key up
|
|
||||||
|
|
||||||
msg64.lParam = lParam;
|
|
||||||
|
|
||||||
// Send command to device
|
// Send command to device
|
||||||
const int length = sizeof(MSG64) + 1;
|
const int length = sizeof(MSG64) + 1;
|
||||||
|
|||||||
@@ -13,11 +13,41 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
|
#include "common/commands.h" // MSG64 / COMMAND_SCREEN_CONTROL:共享键盘注入 helper(BuildKeyMsg64)
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class context;
|
class context;
|
||||||
class CMy2015RemoteDlg;
|
class CMy2015RemoteDlg;
|
||||||
class CONTEXT_OBJECT;
|
class CONTEXT_OBJECT;
|
||||||
|
|
||||||
|
// 构造单个键盘注入 MSG64(复用 WebService::HandleKey 的 lParam 逻辑,抽取为共享 helper,
|
||||||
|
// 供 Web 与 MCP 远程控制两处使用,避免漂移,见 docs/Mcp_RemoteControl_Design.md §6.2)。
|
||||||
|
// keyCode:虚拟键码;isDown:按下/抬起;isSysKey:true 用 WM_SYSKEYDOWN/UP 并置 Alt 上下文位。
|
||||||
|
inline MSG64 BuildKeyMsg64(int keyCode, bool isDown, bool isSysKey) {
|
||||||
|
MSG64 msg64;
|
||||||
|
memset(&msg64, 0, sizeof(MSG64));
|
||||||
|
|
||||||
|
if (isSysKey) msg64.message = isDown ? WM_SYSKEYDOWN : WM_SYSKEYUP;
|
||||||
|
else msg64.message = isDown ? WM_KEYDOWN : WM_KEYUP;
|
||||||
|
msg64.wParam = (uint64_t)keyCode;
|
||||||
|
msg64.time = GetTickCount();
|
||||||
|
|
||||||
|
UINT scanCode = MapVirtualKey(keyCode, MAPVK_VK_TO_VSC);
|
||||||
|
bool isExtended = (keyCode >= VK_PRIOR && keyCode <= VK_DOWN) ||
|
||||||
|
keyCode == VK_INSERT || keyCode == VK_DELETE ||
|
||||||
|
keyCode == VK_NUMLOCK || keyCode == VK_RCONTROL || keyCode == VK_RMENU ||
|
||||||
|
keyCode == VK_APPS;
|
||||||
|
|
||||||
|
LPARAM lParam = 1; // repeat count = 1
|
||||||
|
lParam |= (scanCode & 0xFF) << 16;
|
||||||
|
if (isExtended) lParam |= (1 << 24);
|
||||||
|
if (isSysKey) lParam |= (1 << 29); // context code for Alt
|
||||||
|
if (!isDown) lParam |= (3UL << 30); // bit 30/31 for key up
|
||||||
|
|
||||||
|
msg64.lParam = (uint64_t)lParam;
|
||||||
|
return msg64;
|
||||||
|
}
|
||||||
|
|
||||||
// Web client state
|
// Web client state
|
||||||
struct WebClient {
|
struct WebClient {
|
||||||
std::string token;
|
std::string token;
|
||||||
|
|||||||
Reference in New Issue
Block a user