using System.Text.Json; using System.Text.Json.Nodes; namespace WindowsBridge; /// /// JSON-RPC over stdio bridge for Windows native APIs. /// Reads JSON requests from stdin (one per line), dispatches to the appropriate bridge, /// and writes JSON responses to stdout (one per line). /// Mirrors the protocol of the macOS Swift bridge exactly. /// class Program { private static readonly AppManagement _appManagement = new(); private static readonly UIAutomationBridge _uiAutomation = new(); private static readonly InputBridge _input = new(); private static readonly ScreenCapture _screenCapture = new(); private static readonly object _outputLock = new(); private static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false, }; static void Main(string[] args) { Console.InputEncoding = System.Text.Encoding.UTF8; Console.OutputEncoding = System.Text.Encoding.UTF8; string? line; while ((line = Console.ReadLine()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; try { var request = JsonSerializer.Deserialize(line, _jsonOptions); if (request == null) { WriteError(0, -32700, "Parse error: null request"); continue; } try { var result = Dispatch(request.Method, request.Params); WriteResult(request.Id, result); } catch (BridgeException ex) { WriteError(request.Id, -1, ex.Message); } catch (Exception ex) { WriteError(request.Id, -1, ex.Message); } } catch (Exception ex) { WriteError(0, -32700, $"Parse error: {ex.Message}"); } } } private static object Dispatch(string method, JsonObject? p) { return method switch { // Lifecycle "ping" => new Dictionary { ["pong"] = true, ["pid"] = Environment.ProcessId, ["accessible"] = true, // UI Automation doesn't need special permissions on Windows }, "check_permissions" => new Dictionary { ["trusted"] = true, // No special permissions needed on Windows for UIA }, // App Management "app.launch" => _appManagement.LaunchApp(RequiredParam(p, "bundleId")), "app.focus" => _appManagement.FocusApp(RequiredParam(p, "bundleId")), "app.list" => _appManagement.ListRunningApps(), "app.windows" => _appManagement.ListWindows(), "app.frontmost" => _appManagement.FrontmostApp(), // UI Automation (Accessibility equivalent) "ax.findElement" => _uiAutomation.FindElement( RequiredParam(p, "pid"), Param(p, "role"), Param(p, "title"), Param(p, "value"), Param(p, "identifier"), Param(p, "exact") ?? true), "ax.getElementTree" => _uiAutomation.GetElementTree( RequiredParam(p, "pid"), Param(p, "maxDepth") ?? 5), "ax.performAction" => _uiAutomation.PerformAction( RequiredParam(p, "pid"), RequiredParam(p, "elementPath"), Param(p, "action") ?? "AXPress"), "ax.setElementValue" => _uiAutomation.SetElementValue( RequiredParam(p, "pid"), RequiredParam(p, "elementPath"), RequiredParam(p, "value")), "ax.getElementValue" => _uiAutomation.GetElementValue( RequiredParam(p, "pid"), RequiredParam(p, "elementPath")), "ax.menuClick" => _uiAutomation.MenuClick( RequiredParam(p, "pid"), RequiredParam(p, "menuPath")), // Observer (stub — Windows UIA events could be added later) "observer.start" => new Dictionary { ["ok"] = true, ["stub"] = true, ["message"] = "UI Automation event observation not yet implemented on Windows", }, "observer.stop" => new Dictionary { ["ok"] = true, ["stub"] = true, ["message"] = "UI Automation event observation not yet implemented on Windows", }, // Input (CoreGraphics equivalent) "cg.mouseClick" => _input.MouseClick( RequiredParam(p, "x"), RequiredParam(p, "y"), Param(p, "button") ?? "left", Param(p, "clickCount") ?? 1), "cg.mouseMove" => _input.MouseMove( RequiredParam(p, "x"), RequiredParam(p, "y")), "cg.mouseDrag" => _input.MouseDrag( RequiredParam(p, "fromX"), RequiredParam(p, "fromY"), RequiredParam(p, "toX"), RequiredParam(p, "toY")), "cg.mouseFlick" => _input.MouseDrag( // Map flick to fast drag on Windows RequiredParam(p, "fromX"), RequiredParam(p, "fromY"), RequiredParam(p, "toX"), RequiredParam(p, "toY")), "cg.keyCombo" => _input.KeyCombo(RequiredParam(p, "keys")), "cg.typeText" => _input.TypeText(RequiredParam(p, "text")), "cg.scroll" => _input.Scroll( RequiredParam(p, "x"), RequiredParam(p, "y"), Param(p, "deltaX") ?? 0, Param(p, "deltaY") ?? 0), "cg.captureScreen" => _screenCapture.CaptureScreen( Param>(p, "region")), "cg.captureWindow" => _screenCapture.CaptureWindow( RequiredParam(p, "windowId")), "cg.captureWindowBuffer" => _screenCapture.CaptureWindowBuffer( RequiredParam(p, "windowId")), // Vision (OCR) "vision.findText" => _screenCapture.FindText( RequiredParam(p, "imagePath"), Param(p, "searchText")), "vision.ocr" => _screenCapture.Ocr( RequiredParam(p, "imagePath")), "vision.ocrRegion" => _screenCapture.OcrRegion( RequiredParam(p, "windowId"), RequiredParam>(p, "region")), _ => throw new BridgeException($"Unknown method: {method}"), }; } // Parameter helpers (mirror Swift's param/requiredParam) private static T? Param(JsonObject? p, string key) { if (p == null || !p.ContainsKey(key) || p[key] == null) return default; var node = p[key]!; // Handle numeric coercion if (typeof(T) == typeof(double) && node is JsonValue jv) { if (jv.TryGetValue(out var d)) return (T)(object)d; if (jv.TryGetValue(out var i)) return (T)(object)(double)i; if (jv.TryGetValue(out var l)) return (T)(object)(double)l; } if (typeof(T) == typeof(int) && node is JsonValue jv2) { if (jv2.TryGetValue(out var i)) return (T)(object)i; if (jv2.TryGetValue(out var d)) return (T)(object)(int)d; if (jv2.TryGetValue(out var l)) return (T)(object)(int)l; } try { return node.Deserialize(_jsonOptions); } catch { return default; } } private static T RequiredParam(JsonObject? p, string key) { var value = Param(p, key); if (value == null) throw new BridgeException($"Missing required parameter: {key}"); return value; } // Output helpers private static void WriteResult(int id, object result) { var response = new Dictionary { ["id"] = id, ["result"] = result, ["error"] = null, }; WriteLine(response); } private static void WriteError(int id, int code, string message) { var response = new Dictionary { ["id"] = id, ["result"] = null, ["error"] = new Dictionary { ["code"] = code, ["message"] = message }, }; WriteLine(response); } public static void WriteEvent(Dictionary eventData) { var wrapped = new Dictionary { ["id"] = 0, ["event"] = eventData, }; WriteLine(wrapped); } private static void WriteLine(object obj) { var json = JsonSerializer.Serialize(obj, _jsonOptions); lock (_outputLock) { Console.WriteLine(json); Console.Out.Flush(); } } } // JSON-RPC types class JsonRpcRequest { public int Id { get; set; } public string Method { get; set; } = ""; public JsonObject? Params { get; set; } } class BridgeException : Exception { public BridgeException(string message) : base(message) { } }