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) { }
}