computer-helper.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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 checkWindow(HWND window) {
  68. DWORD pid = 0; GetWindowThreadProcessId(window, &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 void checkForeground() { checkWindow(GetForegroundWindow()); }
  74. // The non-inheritable job owns PowerShell and every normally spawned child.
  75. // Killing this helper, timeout, cancellation, or normal completion closes the
  76. // job and terminates descendants, even when PowerShell has already exited.
  77. static int runShell(const JsonObject& request) {
  78. auto encoded = request.GetNamedString(L"encoded"), cwd = request.GetNamedString(L"cwd");
  79. require(encoded.size() > 0 && encoded.size() < 30000, "PowerShell 命令超过长度限制");
  80. for (wchar_t ch : encoded) require(iswalnum(ch) || ch == L'+' || ch == L'/' || ch == L'=', "PowerShell 编码无效");
  81. wchar_t system[MAX_PATH]{};
  82. require(GetSystemDirectoryW(system, MAX_PATH), "无法定位 Windows PowerShell");
  83. auto shell = fs::path(system) / L"WindowsPowerShell" / L"v1.0" / L"powershell.exe";
  84. std::wstring command = L"\"" + shell.wstring() + L"\" -NoLogo -NoProfile -NonInteractive -OutputFormat Text -EncodedCommand " + std::wstring(encoded);
  85. Handle job(CreateJobObjectW(nullptr, nullptr)); require(job.value != nullptr, "无法创建命令进程组");
  86. JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{};
  87. limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
  88. require(SetInformationJobObject(job.value, JobObjectExtendedLimitInformation, &limits, sizeof(limits)), "无法设置命令进程组");
  89. STARTUPINFOW startup{}; startup.cb = sizeof(startup); startup.dwFlags = STARTF_USESTDHANDLES;
  90. startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
  91. for (HANDLE handle : {startup.hStdInput, startup.hStdOutput, startup.hStdError})
  92. require(SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT), "无法连接命令输入输出");
  93. PROCESS_INFORMATION created{};
  94. require(CreateProcessW(shell.c_str(), command.data(), nullptr, nullptr, TRUE, CREATE_SUSPENDED | CREATE_NO_WINDOW,
  95. nullptr, cwd.c_str(), &startup, &created), "无法启动 Windows PowerShell");
  96. Handle process(created.hProcess), thread(created.hThread);
  97. if (!AssignProcessToJobObject(job.value, process.value)) { TerminateProcess(process.value, 1); throw std::runtime_error("无法接管命令进程组"); }
  98. require(ResumeThread(thread.value) != static_cast<DWORD>(-1), "无法启动命令线程");
  99. require(WaitForSingleObject(process.value, INFINITE) == WAIT_OBJECT_0, "等待命令失败");
  100. DWORD code = 1; require(GetExitCodeProcess(process.value, &code), "无法读取命令退出码");
  101. return static_cast<int>(code);
  102. }
  103. static double number(const JsonObject& object, const wchar_t* key) {
  104. require(object.HasKey(key) && object.GetNamedValue(key).ValueType() == JsonValueType::Number, "电脑输入缺少数值参数");
  105. double value = object.GetNamedNumber(key); require(std::isfinite(value), "电脑输入数值无效"); return value;
  106. }
  107. static int integer(const JsonObject& object, const wchar_t* key, int fallback, int low, int high) {
  108. double value = object.HasKey(key) ? number(object, key) : fallback;
  109. require(value >= low && value <= high && std::floor(value) == value, "电脑输入参数超出范围"); return static_cast<int>(value);
  110. }
  111. static void send(std::vector<INPUT> events) {
  112. checkDesktop(); checkForeground();
  113. require(SendInput(static_cast<UINT>(events.size()), events.data(), sizeof(INPUT)) == events.size(), "Windows 未接受输入,请检查目标窗口权限");
  114. }
  115. static INPUT key(WORD code, bool up, bool unicode = false) {
  116. INPUT input{}; input.type = INPUT_KEYBOARD;
  117. input.ki.wVk = unicode ? 0 : code; input.ki.wScan = unicode ? code : 0;
  118. input.ki.dwFlags = (unicode ? KEYEVENTF_UNICODE : 0) | (up ? KEYEVENTF_KEYUP : 0);
  119. 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;
  120. return input;
  121. }
  122. static INPUT mouse(DWORD flags, LONG x = 0, LONG y = 0, DWORD data = 0) {
  123. INPUT input{}; input.type = INPUT_MOUSE; input.mi.dx = x; input.mi.dy = y; input.mi.dwFlags = flags; input.mi.mouseData = data; return input;
  124. }
  125. static POINT point(const JsonObject& action, const JsonObject& frame, const wchar_t* xKey, const wchar_t* yKey) {
  126. double width = number(frame, L"width"), height = number(frame, L"height");
  127. double x = number(action, xKey), y = number(action, yKey);
  128. require(width > 0 && height > 0 && x >= 0 && y >= 0 && x < width && y < height, "输入超出当前画面");
  129. auto bounds = frame.GetNamedObject(L"physicalBounds");
  130. double bx = number(bounds, L"x"), by = number(bounds, L"y"), bw = number(bounds, L"width"), bh = number(bounds, L"height");
  131. require(bx == 0 && by == 0 && bw == GetSystemMetrics(SM_CXSCREEN) && bh == GetSystemMetrics(SM_CYSCREEN), "显示器已变化,请刷新画面");
  132. return {static_cast<LONG>(bx + x * bw / width), static_cast<LONG>(by + y * bh / height)};
  133. }
  134. static INPUT move(POINT p) {
  135. LONG width = GetSystemMetrics(SM_CXVIRTUALSCREEN), height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
  136. require(width > 1 && height > 1, "显示器不可用");
  137. LONG x = static_cast<LONG>(std::llround((p.x - GetSystemMetrics(SM_XVIRTUALSCREEN)) * 65535.0 / (width - 1)));
  138. LONG y = static_cast<LONG>(std::llround((p.y - GetSystemMetrics(SM_YVIRTUALSCREEN)) * 65535.0 / (height - 1)));
  139. return mouse(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK, x, y);
  140. }
  141. static void input(const JsonObject& request) {
  142. auto action = request.GetNamedObject(L"action"), frame = request.GetNamedObject(L"frame");
  143. auto kind = action.GetNamedString(L"kind");
  144. if (kind == L"click") {
  145. auto p = point(action, frame, L"x", L"y"); auto button = action.GetNamedString(L"button", L"left");
  146. checkWindow(GetAncestor(WindowFromPoint(p), GA_ROOT));
  147. require(button == L"left" || button == L"right" || button == L"middle", "鼠标按键无效");
  148. DWORD down = button == L"right" ? MOUSEEVENTF_RIGHTDOWN : button == L"middle" ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_LEFTDOWN;
  149. DWORD up = button == L"right" ? MOUSEEVENTF_RIGHTUP : button == L"middle" ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_LEFTUP;
  150. int count = integer(action, L"count", 1, 1, 2);
  151. send({move(p)}); checkForeground();
  152. for (int i = 0; i < count; ++i) { send({mouse(down), mouse(up)}); Sleep(50); }
  153. } else if (kind == L"drag") {
  154. auto start = point(action, frame, L"fromX", L"fromY"), end = point(action, frame, L"toX", L"toY");
  155. checkWindow(GetAncestor(WindowFromPoint(start), GA_ROOT));
  156. checkWindow(GetAncestor(WindowFromPoint(end), GA_ROOT));
  157. send({move(start), mouse(MOUSEEVENTF_LEFTDOWN)});
  158. 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); } }
  159. catch (...) { auto up = mouse(MOUSEEVENTF_LEFTUP); SendInput(1, &up, sizeof(INPUT)); throw; }
  160. send({mouse(MOUSEEVENTF_LEFTUP)});
  161. } else if (kind == L"text") {
  162. auto text = action.GetNamedString(L"text"); require(text.size() <= 16000, "输入文本过长");
  163. std::vector<INPUT> events;
  164. for (wchar_t character : text) { events.push_back(key(character, false, true)); events.push_back(key(character, true, true)); }
  165. if (!events.empty()) send(events);
  166. } else if (kind == L"key") {
  167. 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},
  168. {L"BackSpace", VK_BACK}, {L"Backspace", VK_BACK}, {L"Escape", VK_ESCAPE}, {L"Delete", VK_DELETE}, {L"Home", VK_HOME}, {L"End", VK_END},
  169. {L"PageUp", VK_PRIOR}, {L"PageDown", VK_NEXT}, {L"Left", VK_LEFT}, {L"Right", VK_RIGHT}, {L"Up", VK_UP}, {L"Down", VK_DOWN}};
  170. std::wstring name(action.GetNamedString(L"key")); WORD code = 0;
  171. auto found = keys.find(name);
  172. if (found != keys.end()) code = found->second;
  173. 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]));
  174. require(code != 0, "暂不支持这个按键");
  175. std::vector<WORD> modifiers; std::vector<INPUT> events;
  176. if (action.HasKey(L"modifiers")) for (const auto& value : action.GetNamedArray(L"modifiers")) {
  177. 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;
  178. require(m != 0, "按键修饰符无效"); if (std::find(modifiers.begin(), modifiers.end(), m) == modifiers.end()) modifiers.push_back(m);
  179. }
  180. for (WORD m : modifiers) events.push_back(key(m, false));
  181. events.push_back(key(code, false)); events.push_back(key(code, true));
  182. for (auto m = modifiers.rbegin(); m != modifiers.rend(); ++m) events.push_back(key(*m, true));
  183. send(events);
  184. } else if (kind == L"scroll") {
  185. auto direction = action.GetNamedString(L"direction"); int amount = integer(action, L"amount", 3, 1, 50) * WHEEL_DELTA;
  186. require(direction == L"up" || direction == L"down" || direction == L"left" || direction == L"right", "滚动方向无效");
  187. if (direction == L"down" || direction == L"left") amount = -amount;
  188. send({mouse(direction == L"up" || direction == L"down" ? MOUSEEVENTF_WHEEL : MOUSEEVENTF_HWHEEL, 0, 0, static_cast<DWORD>(amount))});
  189. } else throw std::runtime_error("电脑操作无效");
  190. }
  191. int main(int argc, char** argv) {
  192. try {
  193. winrt::init_apartment();
  194. SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
  195. if (argc == 2 && std::string(argv[1]) == "--self-test") {
  196. auto value = JsonObject::Parse(L"{\"count\":2,\"text\":\"中文\"}");
  197. require(integer(value, L"count", 1, 1, 2) == 2 && value.GetNamedString(L"text") == L"中文", "JSON 自检失败");
  198. std::cout << "{\"ok\":true}"; return 0;
  199. }
  200. checkParent();
  201. std::string bytes; char chunk[4096];
  202. while (std::cin.read(chunk, sizeof(chunk)) || std::cin.gcount()) { bytes.append(chunk, static_cast<size_t>(std::cin.gcount())); require(bytes.size() <= 131072, "电脑输入过长"); }
  203. auto request = JsonObject::Parse(winrt::to_hstring(bytes));
  204. if (argc == 2 && std::string(argv[1]) == "--shell") return runShell(request);
  205. checkDesktop();
  206. auto operation = request.GetNamedString(L"operation", L"input");
  207. if (operation == L"release-drag") { auto up = mouse(MOUSEEVENTF_LEFTUP); SendInput(1, &up, sizeof(INPUT)); }
  208. else if (operation != L"probe") input(request);
  209. std::cout << "{\"ok\":true}"; return 0;
  210. } catch (const winrt::hresult_error&) { std::cerr << "电脑输入格式无效或 Windows 组件不可用\n"; }
  211. catch (const std::exception& error) { std::cerr << error.what() << '\n'; }
  212. return 1;
  213. }