screenhand 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +458 -93
- package/dist/.audit-log.jsonl +55 -0
- package/dist/.screenhand/memory/.lock +1 -0
- package/dist/.screenhand/memory/actions.jsonl +85 -0
- package/dist/.screenhand/memory/errors.jsonl +5 -0
- package/dist/.screenhand/memory/errors.jsonl.bak +4 -0
- package/dist/.screenhand/memory/state.json +35 -0
- package/dist/.screenhand/memory/state.json.bak +35 -0
- package/dist/.screenhand/memory/strategies.jsonl +12 -0
- package/dist/agent/cli.js +73 -0
- package/dist/agent/loop.js +258 -0
- package/dist/config.js +9 -0
- package/dist/index.js +56 -0
- package/dist/logging/timeline-logger.js +29 -0
- package/dist/mcp/mcp-stdio-server.js +448 -0
- package/dist/mcp/server.js +347 -0
- package/dist/mcp-desktop.js +2731 -0
- package/dist/mcp-entry.js +59 -0
- package/dist/memory/recall.js +160 -0
- package/dist/memory/research.js +98 -0
- package/dist/memory/seeds.js +89 -0
- package/dist/memory/session.js +161 -0
- package/dist/memory/store.js +391 -0
- package/dist/memory/types.js +4 -0
- package/dist/monitor/codex-monitor.js +377 -0
- package/dist/monitor/task-queue.js +84 -0
- package/dist/monitor/types.js +49 -0
- package/dist/native/bridge-client.js +174 -0
- package/dist/native/macos-bridge-client.js +5 -0
- package/dist/npm-publish-helper.js +117 -0
- package/dist/npm-token-cdp.js +113 -0
- package/dist/npm-token-create.js +135 -0
- package/dist/npm-token-finish.js +126 -0
- package/dist/playbook/engine.js +193 -0
- package/dist/playbook/index.js +4 -0
- package/dist/playbook/recorder.js +519 -0
- package/dist/playbook/runner.js +392 -0
- package/dist/playbook/store.js +166 -0
- package/dist/playbook/types.js +4 -0
- package/dist/runtime/accessibility-adapter.js +377 -0
- package/dist/runtime/app-adapter.js +48 -0
- package/dist/runtime/applescript-adapter.js +283 -0
- package/dist/runtime/ax-role-map.js +80 -0
- package/dist/runtime/browser-adapter.js +36 -0
- package/dist/runtime/cdp-chrome-adapter.js +505 -0
- package/dist/runtime/composite-adapter.js +205 -0
- package/dist/runtime/executor.js +250 -0
- package/dist/runtime/locator-cache.js +12 -0
- package/dist/runtime/planning-loop.js +47 -0
- package/dist/runtime/service.js +372 -0
- package/dist/runtime/session-manager.js +28 -0
- package/dist/runtime/state-observer.js +105 -0
- package/dist/runtime/vision-adapter.js +208 -0
- package/dist/scripts/codex-monitor-daemon.js +335 -0
- package/dist/scripts/supervisor-daemon.js +272 -0
- package/dist/scripts/worker-daemon.js +228 -0
- package/dist/src/agent/cli.js +82 -0
- package/dist/src/agent/loop.js +274 -0
- package/{src/config.ts → dist/src/config.js} +5 -10
- package/{src/index.ts → dist/src/index.js} +32 -52
- package/dist/src/jobs/manager.js +237 -0
- package/dist/src/jobs/runner.js +683 -0
- package/dist/src/jobs/store.js +102 -0
- package/dist/src/jobs/types.js +30 -0
- package/dist/src/jobs/worker.js +97 -0
- package/dist/src/logging/timeline-logger.js +45 -0
- package/dist/src/mcp/mcp-stdio-server.js +464 -0
- package/dist/src/mcp/server.js +363 -0
- package/dist/src/mcp-entry.js +60 -0
- package/dist/src/memory/recall.js +170 -0
- package/dist/src/memory/research.js +104 -0
- package/dist/src/memory/seeds.js +101 -0
- package/dist/src/memory/service.js +421 -0
- package/dist/src/memory/session.js +169 -0
- package/dist/src/memory/store.js +422 -0
- package/dist/src/memory/types.js +17 -0
- package/dist/src/monitor/codex-monitor.js +382 -0
- package/dist/src/monitor/task-queue.js +97 -0
- package/dist/src/monitor/types.js +62 -0
- package/dist/src/native/bridge-client.js +190 -0
- package/{src/native/macos-bridge-client.ts → dist/src/native/macos-bridge-client.js} +0 -1
- package/dist/src/playbook/engine.js +201 -0
- package/dist/src/playbook/index.js +20 -0
- package/dist/src/playbook/recorder.js +535 -0
- package/dist/src/playbook/runner.js +408 -0
- package/dist/src/playbook/store.js +183 -0
- package/dist/src/playbook/types.js +17 -0
- package/dist/src/runtime/accessibility-adapter.js +393 -0
- package/dist/src/runtime/app-adapter.js +64 -0
- package/dist/src/runtime/applescript-adapter.js +299 -0
- package/dist/src/runtime/ax-role-map.js +96 -0
- package/dist/src/runtime/browser-adapter.js +52 -0
- package/dist/src/runtime/cdp-chrome-adapter.js +521 -0
- package/dist/src/runtime/composite-adapter.js +221 -0
- package/dist/src/runtime/execution-contract.js +159 -0
- package/dist/src/runtime/executor.js +266 -0
- package/{src/runtime/locator-cache.ts → dist/src/runtime/locator-cache.js} +10 -15
- package/dist/src/runtime/planning-loop.js +63 -0
- package/dist/src/runtime/service.js +388 -0
- package/dist/src/runtime/session-manager.js +60 -0
- package/dist/src/runtime/state-observer.js +121 -0
- package/dist/src/runtime/vision-adapter.js +224 -0
- package/dist/src/supervisor/locks.js +186 -0
- package/dist/src/supervisor/supervisor.js +403 -0
- package/dist/src/supervisor/types.js +30 -0
- package/dist/src/test-mcp-protocol.js +154 -0
- package/dist/src/types.js +17 -0
- package/dist/src/util/atomic-write.js +118 -0
- package/dist/test-mcp-protocol.js +138 -0
- package/dist/types.js +1 -0
- package/package.json +18 -4
- package/.claude/commands/automate.md +0 -28
- package/.claude/commands/debug-ui.md +0 -19
- package/.claude/commands/screenshot.md +0 -15
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.mcp.json +0 -8
- package/DESKTOP_MCP_GUIDE.md +0 -92
- package/SECURITY.md +0 -44
- package/docs/architecture.md +0 -47
- package/install-skills.sh +0 -19
- package/mcp-bridge.ts +0 -271
- package/mcp-desktop.ts +0 -1221
- package/native/macos-bridge/Package.swift +0 -21
- package/native/macos-bridge/Sources/AccessibilityBridge.swift +0 -261
- package/native/macos-bridge/Sources/AppManagement.swift +0 -129
- package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +0 -242
- package/native/macos-bridge/Sources/ObserverBridge.swift +0 -120
- package/native/macos-bridge/Sources/VisionBridge.swift +0 -80
- package/native/macos-bridge/Sources/main.swift +0 -345
- package/native/windows-bridge/AppManagement.cs +0 -234
- package/native/windows-bridge/InputBridge.cs +0 -436
- package/native/windows-bridge/Program.cs +0 -265
- package/native/windows-bridge/ScreenCapture.cs +0 -329
- package/native/windows-bridge/UIAutomationBridge.cs +0 -571
- package/native/windows-bridge/WindowsBridge.csproj +0 -17
- package/playbooks/devpost.json +0 -186
- package/playbooks/instagram.json +0 -41
- package/playbooks/instagram_v2.json +0 -201
- package/playbooks/x_v1.json +0 -211
- package/scripts/devpost-live-loop.mjs +0 -421
- package/src/logging/timeline-logger.ts +0 -55
- package/src/mcp/server.ts +0 -449
- package/src/memory/recall.ts +0 -191
- package/src/memory/research.ts +0 -146
- package/src/memory/seeds.ts +0 -123
- package/src/memory/session.ts +0 -201
- package/src/memory/store.ts +0 -434
- package/src/memory/types.ts +0 -69
- package/src/native/bridge-client.ts +0 -239
- package/src/runtime/accessibility-adapter.ts +0 -487
- package/src/runtime/app-adapter.ts +0 -169
- package/src/runtime/applescript-adapter.ts +0 -376
- package/src/runtime/ax-role-map.ts +0 -102
- package/src/runtime/browser-adapter.ts +0 -129
- package/src/runtime/cdp-chrome-adapter.ts +0 -676
- package/src/runtime/composite-adapter.ts +0 -274
- package/src/runtime/executor.ts +0 -396
- package/src/runtime/planning-loop.ts +0 -81
- package/src/runtime/service.ts +0 -448
- package/src/runtime/session-manager.ts +0 -50
- package/src/runtime/state-observer.ts +0 -136
- package/src/runtime/vision-adapter.ts +0 -297
- package/src/types.ts +0 -297
- package/tests/bridge-client.test.ts +0 -176
- package/tests/browser-stealth.test.ts +0 -210
- package/tests/composite-adapter.test.ts +0 -64
- package/tests/mcp-server.test.ts +0 -151
- package/tests/memory-recall.test.ts +0 -339
- package/tests/memory-research.test.ts +0 -159
- package/tests/memory-seeds.test.ts +0 -120
- package/tests/memory-store.test.ts +0 -392
- package/tests/types.test.ts +0 -92
- package/tsconfig.check.json +0 -17
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -8
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
using System.Diagnostics;
|
|
2
|
-
using System.Runtime.InteropServices;
|
|
3
|
-
using System.Text;
|
|
4
|
-
|
|
5
|
-
namespace WindowsBridge;
|
|
6
|
-
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Process and window management using Windows APIs.
|
|
9
|
-
/// Equivalent to macOS AppManagement.swift.
|
|
10
|
-
/// </summary>
|
|
11
|
-
class AppManagement
|
|
12
|
-
{
|
|
13
|
-
// P/Invoke declarations
|
|
14
|
-
[DllImport("user32.dll")]
|
|
15
|
-
private static extern IntPtr GetForegroundWindow();
|
|
16
|
-
|
|
17
|
-
[DllImport("user32.dll")]
|
|
18
|
-
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
19
|
-
|
|
20
|
-
[DllImport("user32.dll")]
|
|
21
|
-
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
|
22
|
-
|
|
23
|
-
[DllImport("user32.dll")]
|
|
24
|
-
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
25
|
-
|
|
26
|
-
[DllImport("user32.dll")]
|
|
27
|
-
private static extern bool IsWindowVisible(IntPtr hWnd);
|
|
28
|
-
|
|
29
|
-
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
30
|
-
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
|
31
|
-
|
|
32
|
-
[DllImport("user32.dll")]
|
|
33
|
-
private static extern int GetWindowTextLength(IntPtr hWnd);
|
|
34
|
-
|
|
35
|
-
[DllImport("user32.dll")]
|
|
36
|
-
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
|
37
|
-
|
|
38
|
-
[DllImport("user32.dll")]
|
|
39
|
-
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
40
|
-
|
|
41
|
-
[DllImport("user32.dll")]
|
|
42
|
-
private static extern bool AllowSetForegroundWindow(int dwProcessId);
|
|
43
|
-
|
|
44
|
-
[StructLayout(LayoutKind.Sequential)]
|
|
45
|
-
private struct RECT
|
|
46
|
-
{
|
|
47
|
-
public int Left, Top, Right, Bottom;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
51
|
-
|
|
52
|
-
private const int SW_RESTORE = 9;
|
|
53
|
-
private const int SW_SHOW = 5;
|
|
54
|
-
|
|
55
|
-
/// <summary>
|
|
56
|
-
/// List all running GUI applications.
|
|
57
|
-
/// On Windows, "bundleId" is mapped to the process name (e.g., "chrome", "notepad").
|
|
58
|
-
/// </summary>
|
|
59
|
-
public List<Dictionary<string, object>> ListRunningApps()
|
|
60
|
-
{
|
|
61
|
-
var apps = new List<Dictionary<string, object>>();
|
|
62
|
-
var seen = new HashSet<int>();
|
|
63
|
-
var foregroundHwnd = GetForegroundWindow();
|
|
64
|
-
GetWindowThreadProcessId(foregroundHwnd, out uint foregroundPid);
|
|
65
|
-
|
|
66
|
-
var processes = Process.GetProcesses()
|
|
67
|
-
.Where(p => !string.IsNullOrEmpty(p.MainWindowTitle) && p.MainWindowHandle != IntPtr.Zero)
|
|
68
|
-
.OrderBy(p => p.ProcessName);
|
|
69
|
-
|
|
70
|
-
foreach (var proc in processes)
|
|
71
|
-
{
|
|
72
|
-
try
|
|
73
|
-
{
|
|
74
|
-
if (seen.Contains(proc.Id)) continue;
|
|
75
|
-
seen.Add(proc.Id);
|
|
76
|
-
|
|
77
|
-
apps.Add(new Dictionary<string, object>
|
|
78
|
-
{
|
|
79
|
-
["name"] = proc.ProcessName,
|
|
80
|
-
["bundleId"] = proc.ProcessName.ToLowerInvariant(), // Windows equivalent
|
|
81
|
-
["pid"] = proc.Id,
|
|
82
|
-
["isActive"] = proc.Id == (int)foregroundPid,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
catch
|
|
86
|
-
{
|
|
87
|
-
// Process may have exited
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return apps;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/// <summary>
|
|
95
|
-
/// Launch an application by process name or path.
|
|
96
|
-
/// </summary>
|
|
97
|
-
public Dictionary<string, object> LaunchApp(string bundleId)
|
|
98
|
-
{
|
|
99
|
-
Process process;
|
|
100
|
-
try
|
|
101
|
-
{
|
|
102
|
-
// Try launching as a process name first (e.g., "notepad", "calc")
|
|
103
|
-
process = Process.Start(new ProcessStartInfo
|
|
104
|
-
{
|
|
105
|
-
FileName = bundleId,
|
|
106
|
-
UseShellExecute = true,
|
|
107
|
-
}) ?? throw new BridgeException($"Failed to launch: {bundleId}");
|
|
108
|
-
}
|
|
109
|
-
catch
|
|
110
|
-
{
|
|
111
|
-
throw new BridgeException($"Could not launch application: {bundleId}");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Wait briefly for the process to initialize
|
|
115
|
-
try { process.WaitForInputIdle(3000); } catch { }
|
|
116
|
-
|
|
117
|
-
return new Dictionary<string, object>
|
|
118
|
-
{
|
|
119
|
-
["appName"] = process.ProcessName,
|
|
120
|
-
["bundleId"] = process.ProcessName.ToLowerInvariant(),
|
|
121
|
-
["pid"] = process.Id,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/// <summary>
|
|
126
|
-
/// Focus/activate a window by process name.
|
|
127
|
-
/// </summary>
|
|
128
|
-
public Dictionary<string, object> FocusApp(string bundleId)
|
|
129
|
-
{
|
|
130
|
-
var name = bundleId.ToLowerInvariant().Replace(".exe", "");
|
|
131
|
-
var processes = Process.GetProcessesByName(name);
|
|
132
|
-
|
|
133
|
-
if (processes.Length == 0)
|
|
134
|
-
{
|
|
135
|
-
// Try exact match with original casing
|
|
136
|
-
processes = Process.GetProcesses()
|
|
137
|
-
.Where(p => p.ProcessName.Equals(bundleId, StringComparison.OrdinalIgnoreCase) ||
|
|
138
|
-
p.ProcessName.Equals(name, StringComparison.OrdinalIgnoreCase))
|
|
139
|
-
.ToArray();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (processes.Length == 0)
|
|
143
|
-
throw new BridgeException($"No running process found: {bundleId}");
|
|
144
|
-
|
|
145
|
-
var proc = processes.First(p => p.MainWindowHandle != IntPtr.Zero);
|
|
146
|
-
var hwnd = proc.MainWindowHandle;
|
|
147
|
-
|
|
148
|
-
if (hwnd == IntPtr.Zero)
|
|
149
|
-
throw new BridgeException($"Process {bundleId} has no visible window");
|
|
150
|
-
|
|
151
|
-
ShowWindow(hwnd, SW_RESTORE);
|
|
152
|
-
AllowSetForegroundWindow(proc.Id);
|
|
153
|
-
SetForegroundWindow(hwnd);
|
|
154
|
-
|
|
155
|
-
return new Dictionary<string, object> { ["ok"] = true };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/// <summary>
|
|
159
|
-
/// List all visible windows with their bounds.
|
|
160
|
-
/// </summary>
|
|
161
|
-
public List<Dictionary<string, object>> ListWindows()
|
|
162
|
-
{
|
|
163
|
-
var windows = new List<Dictionary<string, object>>();
|
|
164
|
-
|
|
165
|
-
EnumWindows((hWnd, _) =>
|
|
166
|
-
{
|
|
167
|
-
if (!IsWindowVisible(hWnd)) return true;
|
|
168
|
-
|
|
169
|
-
var titleLength = GetWindowTextLength(hWnd);
|
|
170
|
-
if (titleLength == 0) return true;
|
|
171
|
-
|
|
172
|
-
var titleBuilder = new StringBuilder(titleLength + 1);
|
|
173
|
-
GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity);
|
|
174
|
-
var title = titleBuilder.ToString();
|
|
175
|
-
|
|
176
|
-
GetWindowThreadProcessId(hWnd, out uint pid);
|
|
177
|
-
GetWindowRect(hWnd, out RECT rect);
|
|
178
|
-
|
|
179
|
-
string appName;
|
|
180
|
-
try
|
|
181
|
-
{
|
|
182
|
-
appName = Process.GetProcessById((int)pid).ProcessName;
|
|
183
|
-
}
|
|
184
|
-
catch
|
|
185
|
-
{
|
|
186
|
-
appName = "unknown";
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
windows.Add(new Dictionary<string, object>
|
|
190
|
-
{
|
|
191
|
-
["windowId"] = hWnd.ToInt64(),
|
|
192
|
-
["appName"] = appName,
|
|
193
|
-
["title"] = title,
|
|
194
|
-
["pid"] = (int)pid,
|
|
195
|
-
["bounds"] = new Dictionary<string, object>
|
|
196
|
-
{
|
|
197
|
-
["x"] = rect.Left,
|
|
198
|
-
["y"] = rect.Top,
|
|
199
|
-
["width"] = rect.Right - rect.Left,
|
|
200
|
-
["height"] = rect.Bottom - rect.Top,
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return true;
|
|
205
|
-
}, IntPtr.Zero);
|
|
206
|
-
|
|
207
|
-
return windows;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/// <summary>
|
|
211
|
-
/// Get the frontmost (foreground) application.
|
|
212
|
-
/// </summary>
|
|
213
|
-
public Dictionary<string, object> FrontmostApp()
|
|
214
|
-
{
|
|
215
|
-
var hwnd = GetForegroundWindow();
|
|
216
|
-
GetWindowThreadProcessId(hwnd, out uint pid);
|
|
217
|
-
|
|
218
|
-
try
|
|
219
|
-
{
|
|
220
|
-
var proc = Process.GetProcessById((int)pid);
|
|
221
|
-
return new Dictionary<string, object>
|
|
222
|
-
{
|
|
223
|
-
["name"] = proc.ProcessName,
|
|
224
|
-
["bundleId"] = proc.ProcessName.ToLowerInvariant(),
|
|
225
|
-
["pid"] = proc.Id,
|
|
226
|
-
["isActive"] = true,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
catch
|
|
230
|
-
{
|
|
231
|
-
throw new BridgeException("Could not determine frontmost application");
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
using System.Runtime.InteropServices;
|
|
2
|
-
|
|
3
|
-
namespace WindowsBridge;
|
|
4
|
-
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// Mouse and keyboard input injection using SendInput().
|
|
7
|
-
/// Equivalent to macOS CoreGraphicsBridge.swift.
|
|
8
|
-
/// </summary>
|
|
9
|
-
class InputBridge
|
|
10
|
-
{
|
|
11
|
-
// SendInput structures and constants
|
|
12
|
-
[DllImport("user32.dll", SetLastError = true)]
|
|
13
|
-
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
|
14
|
-
|
|
15
|
-
[DllImport("user32.dll")]
|
|
16
|
-
private static extern bool SetCursorPos(int X, int Y);
|
|
17
|
-
|
|
18
|
-
[DllImport("user32.dll")]
|
|
19
|
-
private static extern bool GetCursorPos(out POINT lpPoint);
|
|
20
|
-
|
|
21
|
-
[DllImport("user32.dll")]
|
|
22
|
-
private static extern int GetSystemMetrics(int nIndex);
|
|
23
|
-
|
|
24
|
-
[StructLayout(LayoutKind.Sequential)]
|
|
25
|
-
private struct POINT
|
|
26
|
-
{
|
|
27
|
-
public int X, Y;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
[StructLayout(LayoutKind.Sequential)]
|
|
31
|
-
private struct INPUT
|
|
32
|
-
{
|
|
33
|
-
public uint Type;
|
|
34
|
-
public INPUTUNION U;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
[StructLayout(LayoutKind.Explicit)]
|
|
38
|
-
private struct INPUTUNION
|
|
39
|
-
{
|
|
40
|
-
[FieldOffset(0)] public MOUSEINPUT mi;
|
|
41
|
-
[FieldOffset(0)] public KEYBDINPUT ki;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
[StructLayout(LayoutKind.Sequential)]
|
|
45
|
-
private struct MOUSEINPUT
|
|
46
|
-
{
|
|
47
|
-
public int dx, dy;
|
|
48
|
-
public uint mouseData;
|
|
49
|
-
public uint dwFlags;
|
|
50
|
-
public uint time;
|
|
51
|
-
public IntPtr dwExtraInfo;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
[StructLayout(LayoutKind.Sequential)]
|
|
55
|
-
private struct KEYBDINPUT
|
|
56
|
-
{
|
|
57
|
-
public ushort wVk;
|
|
58
|
-
public ushort wScan;
|
|
59
|
-
public uint dwFlags;
|
|
60
|
-
public uint time;
|
|
61
|
-
public IntPtr dwExtraInfo;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Input type constants
|
|
65
|
-
private const uint INPUT_MOUSE = 0;
|
|
66
|
-
private const uint INPUT_KEYBOARD = 1;
|
|
67
|
-
|
|
68
|
-
// Mouse event flags
|
|
69
|
-
private const uint MOUSEEVENTF_MOVE = 0x0001;
|
|
70
|
-
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
|
|
71
|
-
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
|
|
72
|
-
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
|
|
73
|
-
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
|
|
74
|
-
private const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020;
|
|
75
|
-
private const uint MOUSEEVENTF_MIDDLEUP = 0x0040;
|
|
76
|
-
private const uint MOUSEEVENTF_WHEEL = 0x0800;
|
|
77
|
-
private const uint MOUSEEVENTF_HWHEEL = 0x1000;
|
|
78
|
-
private const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
|
|
79
|
-
|
|
80
|
-
// Keyboard event flags
|
|
81
|
-
private const uint KEYEVENTF_KEYUP = 0x0002;
|
|
82
|
-
private const uint KEYEVENTF_UNICODE = 0x0004;
|
|
83
|
-
|
|
84
|
-
// System metrics
|
|
85
|
-
private const int SM_CXSCREEN = 0;
|
|
86
|
-
private const int SM_CYSCREEN = 1;
|
|
87
|
-
|
|
88
|
-
// Virtual key codes
|
|
89
|
-
private const ushort VK_SHIFT = 0x10;
|
|
90
|
-
private const ushort VK_CONTROL = 0x11;
|
|
91
|
-
private const ushort VK_MENU = 0x12; // Alt
|
|
92
|
-
private const ushort VK_LWIN = 0x5B;
|
|
93
|
-
private const ushort VK_RETURN = 0x0D;
|
|
94
|
-
private const ushort VK_TAB = 0x09;
|
|
95
|
-
private const ushort VK_ESCAPE = 0x1B;
|
|
96
|
-
private const ushort VK_SPACE = 0x20;
|
|
97
|
-
private const ushort VK_BACK = 0x08;
|
|
98
|
-
private const ushort VK_DELETE = 0x2E;
|
|
99
|
-
private const ushort VK_UP = 0x26;
|
|
100
|
-
private const ushort VK_DOWN = 0x28;
|
|
101
|
-
private const ushort VK_LEFT = 0x25;
|
|
102
|
-
private const ushort VK_RIGHT = 0x27;
|
|
103
|
-
private const ushort VK_HOME = 0x24;
|
|
104
|
-
private const ushort VK_END = 0x23;
|
|
105
|
-
private const ushort VK_PRIOR = 0x21; // Page Up
|
|
106
|
-
private const ushort VK_NEXT = 0x22; // Page Down
|
|
107
|
-
private const ushort VK_F1 = 0x70;
|
|
108
|
-
|
|
109
|
-
/// <summary>
|
|
110
|
-
/// Click at screen coordinates.
|
|
111
|
-
/// </summary>
|
|
112
|
-
public Dictionary<string, object> MouseClick(double x, double y, string button, int clickCount)
|
|
113
|
-
{
|
|
114
|
-
SetCursorPos((int)x, (int)y);
|
|
115
|
-
System.Threading.Thread.Sleep(10);
|
|
116
|
-
|
|
117
|
-
uint downFlag, upFlag;
|
|
118
|
-
switch (button.ToLowerInvariant())
|
|
119
|
-
{
|
|
120
|
-
case "right":
|
|
121
|
-
downFlag = MOUSEEVENTF_RIGHTDOWN;
|
|
122
|
-
upFlag = MOUSEEVENTF_RIGHTUP;
|
|
123
|
-
break;
|
|
124
|
-
case "middle":
|
|
125
|
-
downFlag = MOUSEEVENTF_MIDDLEDOWN;
|
|
126
|
-
upFlag = MOUSEEVENTF_MIDDLEUP;
|
|
127
|
-
break;
|
|
128
|
-
default: // left
|
|
129
|
-
downFlag = MOUSEEVENTF_LEFTDOWN;
|
|
130
|
-
upFlag = MOUSEEVENTF_LEFTUP;
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
for (int i = 0; i < clickCount; i++)
|
|
135
|
-
{
|
|
136
|
-
var inputs = new INPUT[]
|
|
137
|
-
{
|
|
138
|
-
new() { Type = INPUT_MOUSE, U = new INPUTUNION { mi = new MOUSEINPUT { dwFlags = downFlag } } },
|
|
139
|
-
new() { Type = INPUT_MOUSE, U = new INPUTUNION { mi = new MOUSEINPUT { dwFlags = upFlag } } },
|
|
140
|
-
};
|
|
141
|
-
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<INPUT>());
|
|
142
|
-
|
|
143
|
-
if (i < clickCount - 1)
|
|
144
|
-
System.Threading.Thread.Sleep(30);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return new Dictionary<string, object> { ["ok"] = true };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/// <summary>
|
|
151
|
-
/// Move mouse to screen coordinates.
|
|
152
|
-
/// </summary>
|
|
153
|
-
public Dictionary<string, object> MouseMove(double x, double y)
|
|
154
|
-
{
|
|
155
|
-
SetCursorPos((int)x, (int)y);
|
|
156
|
-
return new Dictionary<string, object> { ["ok"] = true };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/// <summary>
|
|
160
|
-
/// Drag from one point to another with interpolation.
|
|
161
|
-
/// </summary>
|
|
162
|
-
public Dictionary<string, object> MouseDrag(double fromX, double fromY, double toX, double toY)
|
|
163
|
-
{
|
|
164
|
-
SetCursorPos((int)fromX, (int)fromY);
|
|
165
|
-
System.Threading.Thread.Sleep(50);
|
|
166
|
-
|
|
167
|
-
// Mouse down
|
|
168
|
-
var downInput = new INPUT[]
|
|
169
|
-
{
|
|
170
|
-
new() { Type = INPUT_MOUSE, U = new INPUTUNION { mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_LEFTDOWN } } },
|
|
171
|
-
};
|
|
172
|
-
SendInput(1, downInput, Marshal.SizeOf<INPUT>());
|
|
173
|
-
System.Threading.Thread.Sleep(50);
|
|
174
|
-
|
|
175
|
-
// Interpolate movement
|
|
176
|
-
int steps = 20;
|
|
177
|
-
for (int i = 1; i <= steps; i++)
|
|
178
|
-
{
|
|
179
|
-
double t = (double)i / steps;
|
|
180
|
-
int cx = (int)(fromX + (toX - fromX) * t);
|
|
181
|
-
int cy = (int)(fromY + (toY - fromY) * t);
|
|
182
|
-
SetCursorPos(cx, cy);
|
|
183
|
-
System.Threading.Thread.Sleep(10);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Mouse up
|
|
187
|
-
var upInput = new INPUT[]
|
|
188
|
-
{
|
|
189
|
-
new() { Type = INPUT_MOUSE, U = new INPUTUNION { mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_LEFTUP } } },
|
|
190
|
-
};
|
|
191
|
-
SendInput(1, upInput, Marshal.SizeOf<INPUT>());
|
|
192
|
-
|
|
193
|
-
return new Dictionary<string, object> { ["ok"] = true };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/// <summary>
|
|
197
|
-
/// Press a key combination (e.g., ["ctrl", "c"], ["alt", "f4"]).
|
|
198
|
-
/// Maps macOS modifier names to Windows equivalents.
|
|
199
|
-
/// </summary>
|
|
200
|
-
public Dictionary<string, object> KeyCombo(string[] keys)
|
|
201
|
-
{
|
|
202
|
-
var modifiers = new List<ushort>();
|
|
203
|
-
var regularKeys = new List<ushort>();
|
|
204
|
-
|
|
205
|
-
foreach (var key in keys)
|
|
206
|
-
{
|
|
207
|
-
var vk = MapKeyToVK(key.Trim().ToLowerInvariant());
|
|
208
|
-
if (IsModifier(vk))
|
|
209
|
-
modifiers.Add(vk);
|
|
210
|
-
else
|
|
211
|
-
regularKeys.Add(vk);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
var inputs = new List<INPUT>();
|
|
215
|
-
|
|
216
|
-
// Press modifiers down
|
|
217
|
-
foreach (var mod in modifiers)
|
|
218
|
-
{
|
|
219
|
-
inputs.Add(new INPUT
|
|
220
|
-
{
|
|
221
|
-
Type = INPUT_KEYBOARD,
|
|
222
|
-
U = new INPUTUNION { ki = new KEYBDINPUT { wVk = mod } }
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Press regular keys
|
|
227
|
-
foreach (var key in regularKeys)
|
|
228
|
-
{
|
|
229
|
-
inputs.Add(new INPUT
|
|
230
|
-
{
|
|
231
|
-
Type = INPUT_KEYBOARD,
|
|
232
|
-
U = new INPUTUNION { ki = new KEYBDINPUT { wVk = key } }
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Release regular keys
|
|
237
|
-
foreach (var key in regularKeys)
|
|
238
|
-
{
|
|
239
|
-
inputs.Add(new INPUT
|
|
240
|
-
{
|
|
241
|
-
Type = INPUT_KEYBOARD,
|
|
242
|
-
U = new INPUTUNION { ki = new KEYBDINPUT { wVk = key, dwFlags = KEYEVENTF_KEYUP } }
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Release modifiers (reverse order)
|
|
247
|
-
for (int i = modifiers.Count - 1; i >= 0; i--)
|
|
248
|
-
{
|
|
249
|
-
inputs.Add(new INPUT
|
|
250
|
-
{
|
|
251
|
-
Type = INPUT_KEYBOARD,
|
|
252
|
-
U = new INPUTUNION { ki = new KEYBDINPUT { wVk = modifiers[i], dwFlags = KEYEVENTF_KEYUP } }
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
SendInput((uint)inputs.Count, inputs.ToArray(), Marshal.SizeOf<INPUT>());
|
|
257
|
-
|
|
258
|
-
return new Dictionary<string, object> { ["ok"] = true };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/// <summary>
|
|
262
|
-
/// Type text character by character using Unicode input.
|
|
263
|
-
/// </summary>
|
|
264
|
-
public Dictionary<string, object> TypeText(string text)
|
|
265
|
-
{
|
|
266
|
-
var inputs = new List<INPUT>();
|
|
267
|
-
|
|
268
|
-
foreach (var ch in text)
|
|
269
|
-
{
|
|
270
|
-
// Key down
|
|
271
|
-
inputs.Add(new INPUT
|
|
272
|
-
{
|
|
273
|
-
Type = INPUT_KEYBOARD,
|
|
274
|
-
U = new INPUTUNION
|
|
275
|
-
{
|
|
276
|
-
ki = new KEYBDINPUT
|
|
277
|
-
{
|
|
278
|
-
wVk = 0,
|
|
279
|
-
wScan = (ushort)ch,
|
|
280
|
-
dwFlags = KEYEVENTF_UNICODE,
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
// Key up
|
|
285
|
-
inputs.Add(new INPUT
|
|
286
|
-
{
|
|
287
|
-
Type = INPUT_KEYBOARD,
|
|
288
|
-
U = new INPUTUNION
|
|
289
|
-
{
|
|
290
|
-
ki = new KEYBDINPUT
|
|
291
|
-
{
|
|
292
|
-
wVk = 0,
|
|
293
|
-
wScan = (ushort)ch,
|
|
294
|
-
dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP,
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Send in batches to avoid overflow
|
|
301
|
-
const int batchSize = 50;
|
|
302
|
-
for (int i = 0; i < inputs.Count; i += batchSize)
|
|
303
|
-
{
|
|
304
|
-
var batch = inputs.Skip(i).Take(batchSize).ToArray();
|
|
305
|
-
SendInput((uint)batch.Length, batch, Marshal.SizeOf<INPUT>());
|
|
306
|
-
if (i + batchSize < inputs.Count)
|
|
307
|
-
System.Threading.Thread.Sleep(10);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return new Dictionary<string, object> { ["ok"] = true };
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/// <summary>
|
|
314
|
-
/// Scroll at a position.
|
|
315
|
-
/// </summary>
|
|
316
|
-
public Dictionary<string, object> Scroll(double x, double y, int deltaX, int deltaY)
|
|
317
|
-
{
|
|
318
|
-
SetCursorPos((int)x, (int)y);
|
|
319
|
-
System.Threading.Thread.Sleep(10);
|
|
320
|
-
|
|
321
|
-
var inputs = new List<INPUT>();
|
|
322
|
-
|
|
323
|
-
// Vertical scroll
|
|
324
|
-
if (deltaY != 0)
|
|
325
|
-
{
|
|
326
|
-
inputs.Add(new INPUT
|
|
327
|
-
{
|
|
328
|
-
Type = INPUT_MOUSE,
|
|
329
|
-
U = new INPUTUNION
|
|
330
|
-
{
|
|
331
|
-
mi = new MOUSEINPUT
|
|
332
|
-
{
|
|
333
|
-
dwFlags = MOUSEEVENTF_WHEEL,
|
|
334
|
-
mouseData = (uint)(deltaY * 120), // 120 = WHEEL_DELTA
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Horizontal scroll
|
|
341
|
-
if (deltaX != 0)
|
|
342
|
-
{
|
|
343
|
-
inputs.Add(new INPUT
|
|
344
|
-
{
|
|
345
|
-
Type = INPUT_MOUSE,
|
|
346
|
-
U = new INPUTUNION
|
|
347
|
-
{
|
|
348
|
-
mi = new MOUSEINPUT
|
|
349
|
-
{
|
|
350
|
-
dwFlags = MOUSEEVENTF_HWHEEL,
|
|
351
|
-
mouseData = (uint)(deltaX * 120),
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (inputs.Count > 0)
|
|
358
|
-
SendInput((uint)inputs.Count, inputs.ToArray(), Marshal.SizeOf<INPUT>());
|
|
359
|
-
|
|
360
|
-
return new Dictionary<string, object> { ["ok"] = true };
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// ── Key mapping ──
|
|
364
|
-
|
|
365
|
-
private ushort MapKeyToVK(string key)
|
|
366
|
-
{
|
|
367
|
-
return key switch
|
|
368
|
-
{
|
|
369
|
-
// Modifiers — map macOS names to Windows
|
|
370
|
-
"cmd" or "command" or "win" or "super" => VK_LWIN,
|
|
371
|
-
"ctrl" or "control" => VK_CONTROL,
|
|
372
|
-
"alt" or "option" or "opt" => VK_MENU,
|
|
373
|
-
"shift" => VK_SHIFT,
|
|
374
|
-
|
|
375
|
-
// Special keys
|
|
376
|
-
"enter" or "return" => VK_RETURN,
|
|
377
|
-
"tab" => VK_TAB,
|
|
378
|
-
"escape" or "esc" => VK_ESCAPE,
|
|
379
|
-
"space" => VK_SPACE,
|
|
380
|
-
"backspace" or "delete" => VK_BACK,
|
|
381
|
-
"forwarddelete" or "del" => VK_DELETE,
|
|
382
|
-
"up" or "uparrow" => VK_UP,
|
|
383
|
-
"down" or "downarrow" => VK_DOWN,
|
|
384
|
-
"left" or "leftarrow" => VK_LEFT,
|
|
385
|
-
"right" or "rightarrow" => VK_RIGHT,
|
|
386
|
-
"home" => VK_HOME,
|
|
387
|
-
"end" => VK_END,
|
|
388
|
-
"pageup" => VK_PRIOR,
|
|
389
|
-
"pagedown" => VK_NEXT,
|
|
390
|
-
|
|
391
|
-
// Function keys
|
|
392
|
-
"f1" => VK_F1,
|
|
393
|
-
"f2" => (ushort)(VK_F1 + 1),
|
|
394
|
-
"f3" => (ushort)(VK_F1 + 2),
|
|
395
|
-
"f4" => (ushort)(VK_F1 + 3),
|
|
396
|
-
"f5" => (ushort)(VK_F1 + 4),
|
|
397
|
-
"f6" => (ushort)(VK_F1 + 5),
|
|
398
|
-
"f7" => (ushort)(VK_F1 + 6),
|
|
399
|
-
"f8" => (ushort)(VK_F1 + 7),
|
|
400
|
-
"f9" => (ushort)(VK_F1 + 8),
|
|
401
|
-
"f10" => (ushort)(VK_F1 + 9),
|
|
402
|
-
"f11" => (ushort)(VK_F1 + 10),
|
|
403
|
-
"f12" => (ushort)(VK_F1 + 11),
|
|
404
|
-
|
|
405
|
-
// Single character — use virtual key code for A-Z, 0-9
|
|
406
|
-
_ when key.Length == 1 => CharToVK(key[0]),
|
|
407
|
-
|
|
408
|
-
_ => throw new BridgeException($"Unknown key: {key}"),
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
private static ushort CharToVK(char c)
|
|
413
|
-
{
|
|
414
|
-
c = char.ToUpperInvariant(c);
|
|
415
|
-
if (c >= 'A' && c <= 'Z') return (ushort)c; // VK_A..VK_Z = 0x41..0x5A = 'A'..'Z'
|
|
416
|
-
if (c >= '0' && c <= '9') return (ushort)c; // VK_0..VK_9 = 0x30..0x39 = '0'..'9'
|
|
417
|
-
return c switch
|
|
418
|
-
{
|
|
419
|
-
'-' => 0xBD, // VK_OEM_MINUS
|
|
420
|
-
'=' => 0xBB, // VK_OEM_PLUS
|
|
421
|
-
'[' => 0xDB, // VK_OEM_4
|
|
422
|
-
']' => 0xDD, // VK_OEM_6
|
|
423
|
-
'\\' => 0xDC, // VK_OEM_5
|
|
424
|
-
';' => 0xBA, // VK_OEM_1
|
|
425
|
-
'\'' => 0xDE, // VK_OEM_7
|
|
426
|
-
',' => 0xBC, // VK_OEM_COMMA
|
|
427
|
-
'.' => 0xBE, // VK_OEM_PERIOD
|
|
428
|
-
'/' => 0xBF, // VK_OEM_2
|
|
429
|
-
'`' => 0xC0, // VK_OEM_3
|
|
430
|
-
_ => 0,
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
private static bool IsModifier(ushort vk) =>
|
|
435
|
-
vk == VK_SHIFT || vk == VK_CONTROL || vk == VK_MENU || vk == VK_LWIN;
|
|
436
|
-
}
|