computer-helper.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #include <windows.h>
  2. #include <tlhelp32.h>
  3. #include <wtsapi32.h>
  4. #include <winrt/base.h>
  5. #include <winrt/Windows.Foundation.Collections.h>
  6. #include <winrt/Windows.Data.Json.h>
  7. #include <algorithm>
  8. #include <cmath>
  9. #include <filesystem>
  10. #include <iostream>
  11. #include <map>
  12. #include <stdexcept>
  13. #include <string>
  14. #include <vector>
  15. using winrt::Windows::Data::Json::JsonObject;
  16. using winrt::Windows::Data::Json::JsonValueType;
  17. namespace fs = std::filesystem;
  18. static void require(bool ok, const char* message) { if (!ok) throw std::runtime_error(message); }
  19. struct Handle {
  20. HANDLE value = nullptr;
  21. explicit Handle(HANDLE h) : value(h) {}
  22. ~Handle() { if (value && value != INVALID_HANDLE_VALUE) CloseHandle(value); }
  23. Handle(const Handle&) = delete;
  24. };
  25. static std::wstring processPath(HANDLE process) {
  26. std::wstring value(32768, L'\0'); DWORD size = static_cast<DWORD>(value.size());
  27. require(QueryFullProcessImageNameW(process, 0, value.data(), &size), "无法核对 App 身份");
  28. value.resize(size); return fs::weakly_canonical(value).wstring();
  29. }
  30. static void checkParent() {
  31. #ifndef DEVELOPMENT_HELPER
  32. Handle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
  33. require(snapshot.value != INVALID_HANDLE_VALUE, "无法核对 App 身份");
  34. PROCESSENTRY32W entry{}; entry.dwSize = sizeof(entry); DWORD parent = 0;
  35. if (Process32FirstW(snapshot.value, &entry)) do {
  36. if (entry.th32ProcessID == GetCurrentProcessId()) { parent = entry.th32ParentProcessID; break; }
  37. } while (Process32NextW(snapshot.value, &entry));
  38. Handle process(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, parent));
  39. require(process.value != nullptr, "电脑助手只允许所属 App 调用");
  40. auto own = fs::path(processPath(GetCurrentProcess()));
  41. auto expected = own.parent_path().parent_path().parent_path() / L"Yaoyao.exe";
  42. require(_wcsicmp(processPath(process.value).c_str(), expected.c_str()) == 0, "电脑助手只允许所属 App 调用");
  43. #endif
  44. }
  45. static void checkDesktop() {
  46. DWORD session = 0;
  47. require(ProcessIdToSessionId(GetCurrentProcessId(), &session), "无法读取 Windows 会话");
  48. LPWSTR info = nullptr; DWORD size = 0;
  49. require(WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session, WTSConnectState, &info, &size), "无法读取 Windows 会话");
  50. bool active = size >= sizeof(WTS_CONNECTSTATE_CLASS) && *reinterpret_cast<WTS_CONNECTSTATE_CLASS*>(info) == WTSActive;
  51. WTSFreeMemory(info); require(active, "Windows 会话已断开,请在电脑上重新登录");
  52. HDESK desktop = OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
  53. require(desktop != nullptr, "Windows 已锁屏或处于安全桌面,请在电脑上恢复后重试");
  54. wchar_t name[256]{}; DWORD needed = 0;
  55. bool regular = GetUserObjectInformationW(desktop, UOI_NAME, name, sizeof(name), &needed) && _wcsicmp(name, L"Default") == 0;
  56. CloseDesktop(desktop);
  57. require(regular, "无法控制 Windows 锁屏或 UAC 安全桌面");
  58. }
  59. static DWORD integrity(HANDLE process) {
  60. HANDLE raw = nullptr; require(OpenProcessToken(process, TOKEN_QUERY, &raw), "无法核对目标窗口权限"); Handle token(raw);
  61. DWORD size = 0; GetTokenInformation(token.value, TokenIntegrityLevel, nullptr, 0, &size);
  62. std::vector<BYTE> bytes(size);
  63. require(size && GetTokenInformation(token.value, TokenIntegrityLevel, bytes.data(), size, &size), "无法核对目标窗口权限");
  64. auto label = reinterpret_cast<TOKEN_MANDATORY_LABEL*>(bytes.data());
  65. return *GetSidSubAuthority(label->Label.Sid, *GetSidSubAuthorityCount(label->Label.Sid) - 1);
  66. }
  67. static void checkForeground() {
  68. DWORD pid = 0; GetWindowThreadProcessId(GetForegroundWindow(), &pid);
  69. if (!pid) return;
  70. Handle process(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
  71. require(process.value && integrity(process.value) <= integrity(GetCurrentProcess()), "无法操作管理员或受保护窗口,请在电脑上手动操作");
  72. }
  73. static double number(const JsonObject& object, const wchar_t* key) {
  74. require(object.HasKey(key) && object.GetNamedValue(key).ValueType() == JsonValueType::Number, "电脑输入缺少数值参数");
  75. double value = object.GetNamedNumber(key); require(std::isfinite(value), "电脑输入数值无效"); return value;
  76. }
  77. static int integer(const JsonObject& object, const wchar_t* key, int fallback, int low, int high) {
  78. double value = object.HasKey(key) ? number(object, key) : fallback;
  79. require(value >= low && value <= high && std::floor(value) == value, "电脑输入参数超出范围"); return static_cast<int>(value);
  80. }
  81. static void send(std::vector<INPUT> events) {
  82. checkDesktop(); checkForeground();
  83. require(SendInput(static_cast<UINT>(events.size()), events.data(), sizeof(INPUT)) == events.size(), "Windows 未接受输入,请检查目标窗口权限");
  84. }
  85. static INPUT key(WORD code, bool up, bool unicode = false) {
  86. INPUT input{}; input.type = INPUT_KEYBOARD;
  87. input.ki.wVk = unicode ? 0 : code; input.ki.wScan = unicode ? code : 0;
  88. input.ki.dwFlags = (unicode ? KEYEVENTF_UNICODE : 0) | (up ? KEYEVENTF_KEYUP : 0);
  89. if (!unicode && (code == VK_DELETE || code == VK_HOME || code == VK_END || code == VK_PRIOR || code == VK_NEXT || (code >= VK_LEFT && code <= VK_DOWN))) input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
  90. return input;
  91. }
  92. static INPUT mouse(DWORD flags, LONG x = 0, LONG y = 0, DWORD data = 0) {
  93. INPUT input{}; input.type = INPUT_MOUSE; input.mi.dx = x; input.mi.dy = y; input.mi.dwFlags = flags; input.mi.mouseData = data; return input;
  94. }
  95. static POINT point(const JsonObject& action, const JsonObject& frame, const wchar_t* xKey, const wchar_t* yKey) {
  96. double width = number(frame, L"width"), height = number(frame, L"height");
  97. double x = number(action, xKey), y = number(action, yKey);
  98. require(width > 0 && height > 0 && x >= 0 && y >= 0 && x < width && y < height, "输入超出当前画面");
  99. auto bounds = frame.GetNamedObject(L"physicalBounds");
  100. double bx = number(bounds, L"x"), by = number(bounds, L"y"), bw = number(bounds, L"width"), bh = number(bounds, L"height");
  101. require(bx == 0 && by == 0 && bw == GetSystemMetrics(SM_CXSCREEN) && bh == GetSystemMetrics(SM_CYSCREEN), "显示器已变化,请刷新画面");
  102. return {static_cast<LONG>(bx + x * bw / width), static_cast<LONG>(by + y * bh / height)};
  103. }
  104. static INPUT move(POINT p) {
  105. LONG width = GetSystemMetrics(SM_CXVIRTUALSCREEN), height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
  106. require(width > 1 && height > 1, "显示器不可用");
  107. LONG x = static_cast<LONG>(std::llround((p.x - GetSystemMetrics(SM_XVIRTUALSCREEN)) * 65535.0 / (width - 1)));
  108. LONG y = static_cast<LONG>(std::llround((p.y - GetSystemMetrics(SM_YVIRTUALSCREEN)) * 65535.0 / (height - 1)));
  109. return mouse(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK, x, y);
  110. }
  111. static void input(const JsonObject& request) {
  112. auto action = request.GetNamedObject(L"action"), frame = request.GetNamedObject(L"frame");
  113. auto kind = action.GetNamedString(L"kind");
  114. if (kind == L"click") {
  115. auto p = point(action, frame, L"x", L"y"); auto button = action.GetNamedString(L"button", L"left");
  116. require(button == L"left" || button == L"right" || button == L"middle", "鼠标按键无效");
  117. DWORD down = button == L"right" ? MOUSEEVENTF_RIGHTDOWN : button == L"middle" ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_LEFTDOWN;
  118. DWORD up = button == L"right" ? MOUSEEVENTF_RIGHTUP : button == L"middle" ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_LEFTUP;
  119. int count = integer(action, L"count", 1, 1, 2);
  120. send({move(p)}); checkForeground();
  121. for (int i = 0; i < count; ++i) { send({mouse(down), mouse(up)}); Sleep(50); }
  122. } else if (kind == L"drag") {
  123. auto start = point(action, frame, L"fromX", L"fromY"), end = point(action, frame, L"toX", L"toY");
  124. send({move(start), mouse(MOUSEEVENTF_LEFTDOWN)});
  125. try { for (int i = 1; i <= 12; ++i) { send({move({start.x + (end.x - start.x) * i / 12, start.y + (end.y - start.y) * i / 12})}); Sleep(12); } }
  126. catch (...) { auto up = mouse(MOUSEEVENTF_LEFTUP); SendInput(1, &up, sizeof(INPUT)); throw; }
  127. send({mouse(MOUSEEVENTF_LEFTUP)});
  128. } else if (kind == L"text") {
  129. auto text = action.GetNamedString(L"text"); require(text.size() <= 16000, "输入文本过长");
  130. std::vector<INPUT> events;
  131. for (wchar_t character : text) { events.push_back(key(character, false, true)); events.push_back(key(character, true, true)); }
  132. if (!events.empty()) send(events);
  133. } else if (kind == L"key") {
  134. const std::map<std::wstring, WORD> keys = {{L"Return", VK_RETURN}, {L"Enter", VK_RETURN}, {L"Tab", VK_TAB}, {L"Space", VK_SPACE}, {L"space", VK_SPACE},
  135. {L"BackSpace", VK_BACK}, {L"Backspace", VK_BACK}, {L"Escape", VK_ESCAPE}, {L"Delete", VK_DELETE}, {L"Home", VK_HOME}, {L"End", VK_END},
  136. {L"PageUp", VK_PRIOR}, {L"PageDown", VK_NEXT}, {L"Left", VK_LEFT}, {L"Right", VK_RIGHT}, {L"Up", VK_UP}, {L"Down", VK_DOWN}};
  137. std::wstring name(action.GetNamedString(L"key")); WORD code = 0;
  138. auto found = keys.find(name);
  139. if (found != keys.end()) code = found->second;
  140. else if (name.size() == 1 && ((name[0] >= L'a' && name[0] <= L'z') || (name[0] >= L'A' && name[0] <= L'Z') || (name[0] >= L'0' && name[0] <= L'9'))) code = static_cast<WORD>(towupper(name[0]));
  141. require(code != 0, "暂不支持这个按键");
  142. std::vector<WORD> modifiers; std::vector<INPUT> events;
  143. if (action.HasKey(L"modifiers")) for (const auto& value : action.GetNamedArray(L"modifiers")) {
  144. auto modifier = value.GetString(); WORD m = modifier == L"ctrl" ? VK_CONTROL : modifier == L"alt" ? VK_MENU : modifier == L"shift" ? VK_SHIFT : modifier == L"super" ? VK_LWIN : 0;
  145. require(m != 0, "按键修饰符无效"); if (std::find(modifiers.begin(), modifiers.end(), m) == modifiers.end()) modifiers.push_back(m);
  146. }
  147. for (WORD m : modifiers) events.push_back(key(m, false));
  148. events.push_back(key(code, false)); events.push_back(key(code, true));
  149. for (auto m = modifiers.rbegin(); m != modifiers.rend(); ++m) events.push_back(key(*m, true));
  150. send(events);
  151. } else if (kind == L"scroll") {
  152. auto direction = action.GetNamedString(L"direction"); int amount = integer(action, L"amount", 3, 1, 50) * WHEEL_DELTA;
  153. require(direction == L"up" || direction == L"down" || direction == L"left" || direction == L"right", "滚动方向无效");
  154. if (direction == L"down" || direction == L"left") amount = -amount;
  155. send({mouse(direction == L"up" || direction == L"down" ? MOUSEEVENTF_WHEEL : MOUSEEVENTF_HWHEEL, 0, 0, static_cast<DWORD>(amount))});
  156. } else throw std::runtime_error("电脑操作无效");
  157. }
  158. int main(int argc, char** argv) {
  159. try {
  160. winrt::init_apartment();
  161. SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
  162. if (argc == 2 && std::string(argv[1]) == "--self-test") {
  163. auto value = JsonObject::Parse(L"{\"count\":2,\"text\":\"中文\"}");
  164. require(integer(value, L"count", 1, 1, 2) == 2 && value.GetNamedString(L"text") == L"中文", "JSON 自检失败");
  165. std::cout << "{\"ok\":true}"; return 0;
  166. }
  167. checkParent();
  168. std::string bytes; char chunk[4096];
  169. while (std::cin.read(chunk, sizeof(chunk)) || std::cin.gcount()) { bytes.append(chunk, static_cast<size_t>(std::cin.gcount())); require(bytes.size() <= 131072, "电脑输入过长"); }
  170. auto request = JsonObject::Parse(winrt::to_hstring(bytes));
  171. checkDesktop();
  172. if (request.GetNamedString(L"operation", L"input") != L"probe") input(request);
  173. std::cout << "{\"ok\":true}"; return 0;
  174. } catch (const winrt::hresult_error&) { std::cerr << "电脑输入格式无效或 Windows 组件不可用\n"; }
  175. catch (const std::exception& error) { std::cerr << error.what() << '\n'; }
  176. return 1;
  177. }