using System; using System.IO; using System.Text; using System.Threading; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Diagnostics; using System.Collections.Generic; namespace Scout.ComputerControl { public class ScoutDriver { [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr OpenWindowStation(string lpszWinSta, bool fInherit, uint dwDesiredAccess); [DllImport("user32.dll", SetLastError = true)] public static extern bool SetProcessWindowStation(IntPtr hWinSta); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess); [DllImport("user32.dll", SetLastError = true)] public static extern bool SetThreadDesktop(IntPtr hDesktop); [DllImport("user32.dll", SetLastError = true)] public static extern bool CloseDesktop(IntPtr hDesktop); [DllImport("user32.dll", SetLastError = true)] public static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll", SetLastError = true)] public static extern bool SetCursorPos(int X, int Y); [DllImport("user32.dll", SetLastError = true)] public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo); [DllImport("user32.dll")] public static extern int GetSystemMetrics(int nIndex); [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr hWnd); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); [StructLayout(LayoutKind.Sequential)] public struct INPUT { public uint type; public MOUSEKEYBDHARDWAREINPUT mkhi; } [StructLayout(LayoutKind.Explicit)] public struct MOUSEKEYBDHARDWAREINPUT { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; [FieldOffset(0)] public HARDWAREINPUT hi; } [StructLayout(LayoutKind.Sequential)] public struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] public struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] public struct HARDWAREINPUT { public uint uMsg; public ushort wParamL; public ushort wParamH; } public const uint WM_CLOSE = 0x0010; public const uint KEYEVENTF_EXTENDEDKEY = 0x0001; public const uint KEYEVENTF_KEYUP = 0x0002; public const uint KEYEVENTF_UNICODE = 0x0004; public const uint INPUT_KEYBOARD = 1; [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool CreateProcess( string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation ); public const uint MOUSEEVENTF_MOVE = 0x0001; public const uint MOUSEEVENTF_LEFTDOWN = 0x0002; public const uint MOUSEEVENTF_LEFTUP = 0x0004; public const uint MOUSEEVENTF_RIGHTDOWN = 0x0008; public const uint MOUSEEVENTF_RIGHTUP = 0x0010; public const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020; public const uint MOUSEEVENTF_MIDDLEUP = 0x0040; public const uint MOUSEEVENTF_WHEEL = 0x0800; [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsIconic(IntPtr hWnd); [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId(); public const int SM_CXSCREEN = 0; public const int SM_CYSCREEN = 1; public const int SM_CXVIRTUALSCREEN = 78; public const int SM_CYVIRTUALSCREEN = 79; public const int SW_SHOW = 5; public const int SW_RESTORE = 9; [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct STARTUPINFO { public int cb; public string lpReserved; public string lpDesktop; public string lpTitle; public int dwX; public int dwY; public int dwXSize; public int dwYSize; public int dwXCountChars; public int dwYCountChars; public int dwFillAttribute; public int dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } public static void AttachInputDesktop() { try { SetProcessDPIAware(); IntPtr hwinsta = OpenWindowStation("WinSta0", false, 0x10000000); if (hwinsta != IntPtr.Zero) { SetProcessWindowStation(hwinsta); } IntPtr hdesk = OpenInputDesktop(0, false, 0x10000000); if (hdesk == IntPtr.Zero) { hdesk = OpenDesktop("Default", 0, false, 0x10000000); } if (hdesk != IntPtr.Zero) { SetThreadDesktop(hdesk); } } catch { } } public static POINT GetPosition() { POINT pt = new POINT(); GetCursorPos(out pt); return pt; } public static bool Move(int x, int y) { SetCursorPos(x, y); Thread.Sleep(20); POINT actual = GetPosition(); return (Math.Abs(actual.X - x) <= 5 && Math.Abs(actual.Y - y) <= 5); } public static bool Click(string button, int x, int y, int count) { Move(x, y); Thread.Sleep(30); uint downFlag = MOUSEEVENTF_LEFTDOWN; uint upFlag = MOUSEEVENTF_LEFTUP; if (button == "right") { downFlag = MOUSEEVENTF_RIGHTDOWN; upFlag = MOUSEEVENTF_RIGHTUP; } else if (button == "middle") { downFlag = MOUSEEVENTF_MIDDLEDOWN; upFlag = MOUSEEVENTF_MIDDLEUP; } for (int i = 0; i < count; i++) { mouse_event(downFlag, 0, 0, 0, 0); Thread.Sleep(40); mouse_event(upFlag, 0, 0, 0, 0); if (count > 1 && i < count - 1) { Thread.Sleep(70); } } return true; } public static bool MouseDown(string button, int x, int y) { if (x >= 0 && y >= 0) { Move(x, y); Thread.Sleep(30); } uint downFlag = MOUSEEVENTF_LEFTDOWN; if (button == "right") downFlag = MOUSEEVENTF_RIGHTDOWN; else if (button == "middle") downFlag = MOUSEEVENTF_MIDDLEDOWN; mouse_event(downFlag, 0, 0, 0, 0); return true; } public static bool MouseUp(string button, int x, int y) { if (x >= 0 && y >= 0) { Move(x, y); Thread.Sleep(30); } uint upFlag = MOUSEEVENTF_LEFTUP; if (button == "right") upFlag = MOUSEEVENTF_RIGHTUP; else if (button == "middle") upFlag = MOUSEEVENTF_MIDDLEUP; mouse_event(upFlag, 0, 0, 0, 0); return true; } public static bool Drag(int startX, int startY, int endX, int endY) { Move(startX, startY); Thread.Sleep(50); mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); Thread.Sleep(50); int steps = 15; for (int i = 1; i <= steps; i++) { int curX = startX + (int)((endX - startX) * ((double)i / steps)); int curY = startY + (int)((endY - startY) * ((double)i / steps)); SetCursorPos(curX, curY); Thread.Sleep(15); } Move(endX, endY); Thread.Sleep(50); mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); Thread.Sleep(30); return true; } public static bool Scroll(int deltaX, int deltaY) { if (deltaY != 0) { int amount = -deltaY * 120; mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (uint)amount, 0); } return true; } public static string GetActiveWindowTitle() { try { IntPtr hwnd = GetForegroundWindow(); if (hwnd == IntPtr.Zero) return "Desktop"; StringBuilder sb = new StringBuilder(512); GetWindowText(hwnd, sb, sb.Capacity); string title = sb.ToString().Trim(); return string.IsNullOrEmpty(title) ? "Desktop" : title; } catch { return "Desktop"; } } public static List GetVisibleWindows() { List list = new List(); try { EnumWindows((hWnd, lParam) => { if (IsWindowVisible(hWnd)) { RECT rect; if (GetWindowRect(hWnd, out rect)) { int w = rect.Right - rect.Left; int h = rect.Bottom - rect.Top; if (w > 30 && h > 30) { StringBuilder sb = new StringBuilder(512); GetWindowText(hWnd, sb, sb.Capacity); string t = sb.ToString().Trim(); if (!string.IsNullOrEmpty(t) && t != "Default IME" && t != "MSCTFIME UI") { if (!list.Contains(t)) list.Add(t); } } } } return true; }, IntPtr.Zero); } catch { } return list; } public static bool FocusWindow(string titleOrApp) { try { string target = titleOrApp.ToLower().Trim(); IntPtr foundHwnd = IntPtr.Zero; // Priority 1: Match process name exactly or without .exe try { Process[] procs = Process.GetProcesses(); foreach (Process p in procs) { try { string pn = p.ProcessName.ToLower(); if ((pn == target || pn == target + ".exe" || pn.StartsWith(target + ".")) && p.MainWindowHandle != IntPtr.Zero) { foundHwnd = p.MainWindowHandle; break; } } catch { } } } catch { } // Priority 2: EnumWindows matching process name or window title if (foundHwnd == IntPtr.Zero) { EnumWindows((hWnd, lParam) => { if (IsWindowVisible(hWnd)) { StringBuilder sb = new StringBuilder(512); GetWindowText(hWnd, sb, sb.Capacity); string t = sb.ToString().Trim().ToLower(); uint pid; GetWindowThreadProcessId(hWnd, out pid); string pName = ""; try { pName = Process.GetProcessById((int)pid).ProcessName.ToLower(); } catch { } if (pName == target || pName.Contains(target) || t.StartsWith(target) || t.Contains(target)) { foundHwnd = hWnd; return false; } } return true; }, IntPtr.Zero); } if (foundHwnd != IntPtr.Zero) { if (IsIconic(foundHwnd)) { ShowWindow(foundHwnd, SW_RESTORE); } else { ShowWindow(foundHwnd, SW_SHOW); } IntPtr fgHwnd = GetForegroundWindow(); uint fgPid; uint fgThreadId = GetWindowThreadProcessId(fgHwnd, out fgPid); uint curThreadId = GetCurrentThreadId(); if (fgThreadId != 0 && fgThreadId != curThreadId) { AttachThreadInput(curThreadId, fgThreadId, true); } keybd_event(0x12, 0, 0, UIntPtr.Zero); keybd_event(0x12, 0, 0x0002, UIntPtr.Zero); SetForegroundWindow(foundHwnd); BringWindowToTop(foundHwnd); if (fgThreadId != 0 && fgThreadId != curThreadId) { AttachThreadInput(curThreadId, fgThreadId, false); } Thread.Sleep(200); return true; } } catch { } return false; } public static RECT? GetWindowBounds(string titleOrApp) { try { string target = titleOrApp.ToLower().Trim(); IntPtr foundHwnd = IntPtr.Zero; EnumWindows((hWnd, lParam) => { if (IsWindowVisible(hWnd)) { StringBuilder sb = new StringBuilder(512); GetWindowText(hWnd, sb, sb.Capacity); string t = sb.ToString().Trim().ToLower(); uint pid; GetWindowThreadProcessId(hWnd, out pid); string pName = ""; try { pName = Process.GetProcessById((int)pid).ProcessName.ToLower(); } catch { } if (t.Contains(target) || pName.Contains(target)) { foundHwnd = hWnd; return false; } } return true; }, IntPtr.Zero); if (foundHwnd != IntPtr.Zero) { RECT rect; if (GetWindowRect(foundHwnd, out rect)) { return rect; } } } catch { } return null; } public static bool LaunchApp(string appName, out int pid) { pid = 0; try { STARTUPINFO si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); si.lpDesktop = @"WinSta0\Default"; PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); string cmd = appName; if (appName.IndexOf("blender", StringComparison.OrdinalIgnoreCase) >= 0) { string[] blenderCandidates = new string[] { @"C:\Program Files\Blender Foundation\Blender 5.2\blender.exe", @"C:\Program Files\Blender Foundation\Blender 5.1\blender.exe", @"C:\Program Files\Blender Foundation\Blender 5.0\blender.exe", @"C:\Program Files\Blender Foundation\Blender 4.3\blender.exe", @"C:\Program Files\Blender Foundation\Blender 4.2\blender.exe" }; foreach (string bPath in blenderCandidates) { if (File.Exists(bPath)) { cmd = bPath; break; } } } else if (!cmd.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { cmd += ".exe"; } bool ok = CreateProcess(null, cmd, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref si, out pi); if (ok) { pid = pi.dwProcessId; return true; } // Fallback to Process.Start with UseShellExecute try { ProcessStartInfo psi = new ProcessStartInfo(appName); psi.UseShellExecute = true; Process p = Process.Start(psi); if (p != null) { pid = p.Id; return true; } } catch { } } catch { } return false; } public static string Screenshot(string savePath) { int width = GetSystemMetrics(SM_CXSCREEN); int height = GetSystemMetrics(SM_CYSCREEN); if (width <= 0) width = 1920; if (height <= 0) height = 1080; string dir = Path.GetDirectoryName(savePath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } using (Bitmap bmp = new Bitmap(width, height)) { using (Graphics g = Graphics.FromImage(bmp)) { g.CopyFromScreen(0, 0, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy); } bmp.Save(savePath, ImageFormat.Png); } return string.Format("{{\"success\":true,\"action\":\"screenshot\",\"width\":{0},\"height\":{1},\"path\":{2},\"activeWindow\":{3}}}", width, height, JsonEscape(savePath), JsonEscape(GetActiveWindowTitle())); } public static bool TypeUnicode(string text) { if (string.IsNullOrEmpty(text)) return true; try { List inputs = new List(); foreach (char c in text) { if (c == '\r') continue; if (c == '\n') { INPUT kd = new INPUT { type = INPUT_KEYBOARD }; kd.mkhi.ki.wVk = 0x0D; kd.mkhi.ki.dwFlags = 0; inputs.Add(kd); INPUT ku = new INPUT { type = INPUT_KEYBOARD }; ku.mkhi.ki.wVk = 0x0D; ku.mkhi.ki.dwFlags = KEYEVENTF_KEYUP; inputs.Add(ku); } else if (c == '\t') { INPUT kd = new INPUT { type = INPUT_KEYBOARD }; kd.mkhi.ki.wVk = 0x09; kd.mkhi.ki.dwFlags = 0; inputs.Add(kd); INPUT ku = new INPUT { type = INPUT_KEYBOARD }; ku.mkhi.ki.wVk = 0x09; ku.mkhi.ki.dwFlags = KEYEVENTF_KEYUP; inputs.Add(ku); } else { INPUT kd = new INPUT { type = INPUT_KEYBOARD }; kd.mkhi.ki.wVk = 0; kd.mkhi.ki.wScan = (ushort)c; kd.mkhi.ki.dwFlags = KEYEVENTF_UNICODE; inputs.Add(kd); INPUT ku = new INPUT { type = INPUT_KEYBOARD }; ku.mkhi.ki.wVk = 0; ku.mkhi.ki.wScan = (ushort)c; ku.mkhi.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; inputs.Add(ku); } } uint sent = SendInput((uint)inputs.Count, inputs.ToArray(), Marshal.SizeOf(typeof(INPUT))); return sent > 0; } catch { return false; } } public static bool TypeText(string text) { if (string.IsNullOrEmpty(text)) return true; if (TypeUnicode(text)) return true; try { SendKeys.SendWait(EscapeForSendKeys(text)); return true; } catch { return false; } } public static byte GetVirtualKey(string key) { if (string.IsNullOrEmpty(key)) return 0; string k = key.ToLower().Trim(); // Modifiers if (k == "ctrl" || k == "control" || k == "lctrl" || k == "rctrl") return 0x11; // VK_CONTROL if (k == "alt" || k == "alternate" || k == "menu" || k == "lalt" || k == "ralt") return 0x12; // VK_MENU if (k == "shift" || k == "lshift" || k == "rshift") return 0x10; // VK_SHIFT if (k == "win" || k == "windows" || k == "super" || k == "lwin" || k == "cmd" || k == "command") return 0x5B; // VK_LWIN if (k == "rwin") return 0x5C; // VK_RWIN // Function keys F1 - F24 if (k == "f1") return 0x70; if (k == "f2") return 0x71; if (k == "f3") return 0x72; if (k == "f4") return 0x73; if (k == "f5") return 0x74; if (k == "f6") return 0x75; if (k == "f7") return 0x76; if (k == "f8") return 0x77; if (k == "f9") return 0x78; if (k == "f10") return 0x79; if (k == "f11") return 0x7A; if (k == "f12") return 0x7B; if (k == "f13") return 0x7C; if (k == "f14") return 0x7D; if (k == "f15") return 0x7E; if (k == "f16") return 0x7F; // Common special keys if (k == "enter" || k == "return") return 0x0D; // VK_RETURN if (k == "esc" || k == "escape") return 0x1B; // VK_ESCAPE if (k == "tab") return 0x09; // VK_TAB if (k == "space" || k == "spacebar") return 0x20; // VK_SPACE if (k == "backspace" || k == "back" || k == "bksp") return 0x08; // VK_BACK if (k == "del" || k == "delete") return 0x2E; // VK_DELETE if (k == "ins" || k == "insert") return 0x2D; // VK_INSERT if (k == "home") return 0x24; // VK_HOME if (k == "end") return 0x23; // VK_END if (k == "pageup" || k == "pgup") return 0x21; // VK_PRIOR if (k == "pagedown" || k == "pgdn") return 0x22; // VK_NEXT if (k == "left") return 0x25; // VK_LEFT if (k == "up") return 0x26; // VK_UP if (k == "right") return 0x27; // VK_RIGHT if (k == "down") return 0x28; // VK_DOWN if (k == "prtscn" || k == "printscreen" || k == "snapshot") return 0x2C; // VK_SNAPSHOT // Single letters a-z if (k.Length == 1 && k[0] >= 'a' && k[0] <= 'z') { return (byte)('A' + (k[0] - 'a')); } // Single digits 0-9 if (k.Length == 1 && k[0] >= '0' && k[0] <= '9') { return (byte)k[0]; } // Punctuation and symbols if (k == "+" || k == "plus" || k == "add") return 0xBB; // VK_OEM_PLUS if (k == "-" || k == "minus" || k == "subtract") return 0xBD; // VK_OEM_MINUS if (k == "=" || k == "equal" || k == "equals") return 0xBB; if (k == "," || k == "comma") return 0xBC; // VK_OEM_COMMA if (k == "." || k == "period" || k == "dot") return 0xBE; // VK_OEM_PERIOD if (k == "/" || k == "slash") return 0xBF; // VK_OEM_2 if (k == "\\" || k == "backslash") return 0xDC; // VK_OEM_5 if (k == ";" || k == "semicolon") return 0xBA; // VK_OEM_1 if (k == "'" || k == "quote" || k == "singlequote") return 0xDE; // VK_OEM_7 if (k == "[" || k == "openbracket") return 0xDB; // VK_OEM_4 if (k == "]" || k == "closebracket") return 0xDD; // VK_OEM_6 if (k == "`" || k == "backtick") return 0xC0; // VK_OEM_3 return 0; } public static string FormatKeyForSendKeys(string key) { if (string.IsNullOrEmpty(key)) return ""; string k = key.ToLower().Trim(); if (k == "enter" || k == "return") return "{ENTER}"; if (k == "tab") return "{TAB}"; if (k == "esc" || k == "escape") return "{ESC}"; if (k == "backspace" || k == "back" || k == "bksp") return "{BACKSPACE}"; if (k == "del" || k == "delete") return "{DEL}"; if (k == "ins" || k == "insert") return "{INS}"; if (k == "up") return "{UP}"; if (k == "down") return "{DOWN}"; if (k == "left") return "{LEFT}"; if (k == "right") return "{RIGHT}"; if (k == "space" || k == "spacebar") return " "; if (k == "home") return "{HOME}"; if (k == "end") return "{END}"; if (k == "pageup" || k == "pgup") return "{PGUP}"; if (k == "pagedown" || k == "pgdn") return "{PGDN}"; if (k.Length >= 2 && k[0] == 'f') { int fnum; if (int.TryParse(k.Substring(1), out fnum) && fnum >= 1 && fnum <= 24) { return "{" + k.ToUpper() + "}"; } } if (k.Length == 1) { char c = k[0]; if (c == '+' || c == '^' || c == '%' || c == '~' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']') { return "{" + c + "}"; } return k; } return "{" + key.ToUpper() + "}"; } public static bool KeyPress(string key) { byte vk = GetVirtualKey(key); if (vk != 0) { keybd_event(vk, 0, 0, UIntPtr.Zero); Thread.Sleep(20); keybd_event(vk, 0, KEYEVENTF_KEYUP, UIntPtr.Zero); return true; } try { string sendStr = FormatKeyForSendKeys(key); SendKeys.SendWait(sendStr); return true; } catch { return false; } } public static bool Hotkey(string[] keys) { if (keys == null || keys.Length == 0) return false; List vkList = new List(); foreach (string k in keys) { byte vk = GetVirtualKey(k); if (vk != 0) vkList.Add(vk); } if (vkList.Count == keys.Length) { // Native virtual key press via keybd_event for (int i = 0; i < vkList.Count; i++) { keybd_event(vkList[i], 0, 0, UIntPtr.Zero); Thread.Sleep(20); } Thread.Sleep(40); for (int i = vkList.Count - 1; i >= 0; i--) { keybd_event(vkList[i], 0, KEYEVENTF_KEYUP, UIntPtr.Zero); Thread.Sleep(20); } return true; } // Fallback to SendKeys with proper formatting try { string prefix = ""; string mainKey = ""; foreach (string k in keys) { string lk = k.ToLower().Trim(); if (lk == "ctrl" || lk == "control") prefix += "^"; else if (lk == "alt" || lk == "alternate") prefix += "%"; else if (lk == "shift") prefix += "+"; else mainKey = k; } string sendStr = prefix + FormatKeyForSendKeys(mainKey); SendKeys.SendWait(sendStr); return true; } catch { return false; } } public static bool CloseWindow(string titleOrApp) { try { IntPtr targetHwnd = IntPtr.Zero; if (string.IsNullOrEmpty(titleOrApp)) { targetHwnd = GetForegroundWindow(); } else { string target = titleOrApp.ToLower().Trim(); EnumWindows((hWnd, lParam) => { if (IsWindowVisible(hWnd)) { StringBuilder sb = new StringBuilder(256); GetWindowText(hWnd, sb, 256); string title = sb.ToString(); if (!string.IsNullOrEmpty(title) && title.ToLower().Contains(target)) { targetHwnd = hWnd; return false; } } return true; }, IntPtr.Zero); } if (targetHwnd != IntPtr.Zero) { PostMessage(targetHwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); return true; } } catch { } return false; } public static string EscapeForSendKeys(string text) { if (string.IsNullOrEmpty(text)) return ""; StringBuilder sb = new StringBuilder(); foreach (char c in text) { if (c == '\r') continue; if (c == '\n') { sb.Append("{ENTER}"); } else if (c == '\t') { sb.Append("{TAB}"); } else if (c == '+' || c == '^' || c == '%' || c == '~' || c == '(' || c == ')' || c == '{' || c == '}') { sb.Append("{" + c + "}"); } else { sb.Append(c); } } return sb.ToString(); } public static string JsonEscape(string text) { if (text == null) return "null"; StringBuilder sb = new StringBuilder(); sb.Append("\""); foreach (char c in text) { if (c == '\\') sb.Append("\\\\"); else if (c == '\"') sb.Append("\\\""); else if (c == '\n') sb.Append("\\n"); else if (c == '\r') sb.Append("\\r"); else if (c == '\t') sb.Append("\\t"); else if (c < ' ') sb.Append(string.Format("\\u{0:x4}", (int)c)); else sb.Append(c); } sb.Append("\""); return sb.ToString(); } public static string ExecuteAction(string action, string[] args) { try { string act = action.ToLower().Trim(); if (act == "move") { int x = int.Parse(args[0]); int y = int.Parse(args[1]); bool ok = Move(x, y); POINT pos = GetPosition(); return string.Format("{{\"success\":{0},\"action\":\"move\",\"coordinates\":{{\"x\":{1},\"y\":{2}}},\"activeWindow\":{3}}}", ok ? "true" : "false", pos.X, pos.Y, JsonEscape(GetActiveWindowTitle())); } if (act == "click" || act == "left_click") { int x = int.Parse(args[0]); int y = int.Parse(args[1]); string btn = (args.Length > 2 && !string.IsNullOrEmpty(args[2])) ? args[2] : "left"; bool ok = Click(btn, x, y, 1); POINT pos = GetPosition(); return string.Format("{{\"success\":{0},\"action\":\"click\",\"button\":\"{1}\",\"coordinates\":{{\"x\":{2},\"y\":{3}}},\"activeWindow\":{4}}}", ok ? "true" : "false", btn, pos.X, pos.Y, JsonEscape(GetActiveWindowTitle())); } if (act == "right_click") { int x = int.Parse(args[0]); int y = int.Parse(args[1]); bool ok = Click("right", x, y, 1); POINT pos = GetPosition(); return string.Format("{{\"success\":{0},\"action\":\"right_click\",\"button\":\"right\",\"coordinates\":{{\"x\":{1},\"y\":{2}}},\"activeWindow\":{3}}}", ok ? "true" : "false", pos.X, pos.Y, JsonEscape(GetActiveWindowTitle())); } if (act == "double_click") { int x = int.Parse(args[0]); int y = int.Parse(args[1]); bool ok = Click("left", x, y, 2); POINT pos = GetPosition(); return string.Format("{{\"success\":{0},\"action\":\"double_click\",\"coordinates\":{{\"x\":{1},\"y\":{2}}},\"activeWindow\":{3}}}", ok ? "true" : "false", pos.X, pos.Y, JsonEscape(GetActiveWindowTitle())); } if (act == "mouse_down") { int x = args.Length > 0 ? int.Parse(args[0]) : -1; int y = args.Length > 1 ? int.Parse(args[1]) : -1; string btn = (args.Length > 2 && !string.IsNullOrEmpty(args[2])) ? args[2] : "left"; bool ok = MouseDown(btn, x, y); POINT pos = GetPosition(); return string.Format("{{\"success\":{0},\"action\":\"mouse_down\",\"button\":\"{1}\",\"coordinates\":{{\"x\":{2},\"y\":{3}}}}}", ok ? "true" : "false", btn, pos.X, pos.Y); } if (act == "mouse_up") { int x = args.Length > 0 ? int.Parse(args[0]) : -1; int y = args.Length > 1 ? int.Parse(args[1]) : -1; string btn = (args.Length > 2 && !string.IsNullOrEmpty(args[2])) ? args[2] : "left"; bool ok = MouseUp(btn, x, y); POINT pos = GetPosition(); return string.Format("{{\"success\":{0},\"action\":\"mouse_up\",\"button\":\"{1}\",\"coordinates\":{{\"x\":{2},\"y\":{3}}}}}", ok ? "true" : "false", btn, pos.X, pos.Y); } if (act == "drag") { int sx = int.Parse(args[0]); int sy = int.Parse(args[1]); int ex = int.Parse(args[2]); int ey = int.Parse(args[3]); bool ok = Drag(sx, sy, ex, ey); POINT pos = GetPosition(); return string.Format("{{\"success\":{0},\"action\":\"drag\",\"from\":{{\"x\":{1},\"y\":{2}}},\"to\":{{\"x\":{3},\"y\":{4}}},\"coordinates\":{{\"x\":{5},\"y\":{6}}}}}", ok ? "true" : "false", sx, sy, ex, ey, pos.X, pos.Y); } if (act == "scroll") { int dx = args.Length > 0 ? int.Parse(args[0]) : 0; int dy = args.Length > 1 ? int.Parse(args[1]) : 0; bool ok = Scroll(dx, dy); return string.Format("{{\"success\":{0},\"action\":\"scroll\",\"deltaX\":{1},\"deltaY\":{2}}}", ok ? "true" : "false", dx, dy); } if (act == "position") { POINT pos = GetPosition(); return string.Format("{{\"success\":true,\"action\":\"position\",\"coordinates\":{{\"x\":{0},\"y\":{1}}}}}", pos.X, pos.Y); } if (act == "screen_info") { int width = GetSystemMetrics(SM_CXSCREEN); int height = GetSystemMetrics(SM_CYSCREEN); POINT pos = GetPosition(); return string.Format("{{\"success\":true,\"width\":{0},\"height\":{1},\"cursor\":{{\"x\":{2},\"y\":{3}}},\"activeWindow\":{4}}}", width, height, pos.X, pos.Y, JsonEscape(GetActiveWindowTitle())); } if (act == "screenshot") { string path = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "screen.png"); return Screenshot(path); } if (act == "type") { string text = args.Length > 0 ? args[0] : ""; bool ok = TypeText(text); return string.Format("{{\"success\":{0},\"action\":\"type\",\"chars\":{1},\"activeWindow\":{2}}}", ok ? "true" : "false", text.Length, JsonEscape(GetActiveWindowTitle())); } if (act == "key" || act == "key_press") { string key = args.Length > 0 ? args[0] : ""; bool ok = KeyPress(key); return string.Format("{{\"success\":{0},\"action\":\"key_press\",\"key\":{1},\"activeWindow\":{2}}}", ok ? "true" : "false", JsonEscape(key), JsonEscape(GetActiveWindowTitle())); } if (act == "hotkey") { string[] keys; if (args.Length > 1) { keys = args; } else if (args.Length == 1) { keys = args[0].Split(new char[] { '+', ',' }, StringSplitOptions.RemoveEmptyEntries); } else { keys = new string[0]; } for (int i = 0; i < keys.Length; i++) { keys[i] = keys[i].Trim(); } bool ok = Hotkey(keys); return string.Format("{{\"success\":{0},\"action\":\"hotkey\",\"keys\":{1},\"activeWindow\":{2}}}", ok ? "true" : "false", JsonEscape(string.Join("+", keys)), JsonEscape(GetActiveWindowTitle())); } if (act == "active_window") { string title = GetActiveWindowTitle(); return string.Format("{{\"success\":true,\"action\":\"active_window\",\"activeWindow\":{0}}}", JsonEscape(title)); } if (act == "list_windows") { List wins = GetVisibleWindows(); StringBuilder sb = new StringBuilder(); sb.Append("["); for (int i = 0; i < wins.Count; i++) { sb.Append(JsonEscape(wins[i])); if (i < wins.Count - 1) sb.Append(","); } sb.Append("]"); return string.Format("{{\"success\":true,\"action\":\"list_windows\",\"activeWindow\":{0},\"windows\":{1}}}", JsonEscape(GetActiveWindowTitle()), sb.ToString()); } if (act == "focus_window") { string win = args.Length > 0 ? args[0] : ""; bool ok = FocusWindow(win); return string.Format("{{\"success\":{0},\"action\":\"focus_window\",\"target\":{1},\"activeWindow\":{2}}}", ok ? "true" : "false", JsonEscape(win), JsonEscape(GetActiveWindowTitle())); } if (act == "close_window" || act == "close_app") { string win = args.Length > 0 ? args[0] : ""; bool ok = CloseWindow(win); return string.Format("{{\"success\":{0},\"action\":\"close_window\",\"target\":{1},\"activeWindow\":{2}}}", ok ? "true" : "false", JsonEscape(win), JsonEscape(GetActiveWindowTitle())); } if (act == "launch" || act == "open_app") { string app = args.Length > 0 ? args[0] : ""; int pid; bool ok = LaunchApp(app, out pid); return string.Format("{{\"success\":{0},\"action\":\"launch\",\"app\":{1},\"pid\":{2}}}", ok ? "true" : "false", JsonEscape(app), pid); } if (act == "window_bounds") { string win = args.Length > 0 ? args[0] : ""; RECT? r = GetWindowBounds(win); if (r.HasValue) { int w = r.Value.Right - r.Value.Left; int h = r.Value.Bottom - r.Value.Top; return string.Format("{{\"success\":true,\"action\":\"window_bounds\",\"x\":{0},\"y\":{1},\"width\":{2},\"height\":{3}}}", r.Value.Left, r.Value.Top, w, h); } return string.Format("{{\"success\":false,\"action\":\"window_bounds\",\"error\":{0}}}", JsonEscape("Window not found: " + win)); } if (act == "diagnostic") { int width = GetSystemMetrics(SM_CXSCREEN); int height = GetSystemMetrics(SM_CYSCREEN); POINT pos = GetPosition(); string title = GetActiveWindowTitle(); List wins = GetVisibleWindows(); return string.Format("{{\"success\":true,\"action\":\"diagnostic\",\"width\":{0},\"height\":{1},\"mouse\":{{\"x\":{2},\"y\":{3}}},\"activeWindow\":{4},\"windowCount\":{5}}}", width, height, pos.X, pos.Y, JsonEscape(title), wins.Count); } return string.Format("{{\"success\":false,\"error\":{0},\"recoverable\":true}}", JsonEscape("Unknown action: " + action)); } catch (Exception ex) { return string.Format("{{\"success\":false,\"action\":{0},\"error\":{1},\"recoverable\":true}}", JsonEscape(action), JsonEscape(ex.Message)); } } public static void RunServer() { AttachInputDesktop(); string line; while ((line = Console.ReadLine()) != null) { line = line.Trim(); if (string.IsNullOrEmpty(line)) continue; if (line == "exit" || line == "quit") break; // Simple format: action arg1 arg2 ... or base64 string result = ""; try { string[] parts = ParseCommandLine(line); if (parts.Length > 0) { string action = parts[0]; string[] args = new string[parts.Length - 1]; Array.Copy(parts, 1, args, 0, args.Length); result = ExecuteAction(action, args); } else { result = "{\"success\":false,\"error\":\"Empty command\"}"; } } catch (Exception ex) { result = string.Format("{{\"success\":false,\"error\":{0}}}", JsonEscape(ex.Message)); } Console.WriteLine(result); Console.Out.Flush(); } } private static string[] ParseCommandLine(string cmdLine) { List args = new List(); bool inQuotes = false; StringBuilder current = new StringBuilder(); for (int i = 0; i < cmdLine.Length; i++) { char c = cmdLine[i]; if (c == '\"') { inQuotes = !inQuotes; } else if (c == ' ' && !inQuotes) { if (current.Length > 0) { args.Add(current.ToString()); current.Clear(); } } else { current.Append(c); } } if (current.Length > 0) { args.Add(current.ToString()); } return args.ToArray(); } [STAThread] public static void Main(string[] args) { // Thread execution with desktop attachment Thread t = new Thread(() => { AttachInputDesktop(); if (args.Length > 0 && args[0] == "--server") { RunServer(); return; } if (args.Length == 0) { Console.WriteLine(ExecuteAction("diagnostic", new string[0])); return; } string action = args[0]; string[] actionArgs = new string[args.Length - 1]; Array.Copy(args, 1, actionArgs, 0, actionArgs.Length); Console.WriteLine(ExecuteAction(action, actionArgs)); }); t.SetApartmentState(ApartmentState.STA); t.Start(); t.Join(); } } }