Python Guide
Python Guide
Section titled “Python Guide”winpane is accessible from Python (or any language) through the winpane-host CLI binary, which speaks JSON-RPC 2.0 over stdin/stdout.
Build the host binary:
cargo build -p winpane-host --releaseThis produces target/release/winpane-host.exe. Add it to your PATH or reference it by full path.
Hello world
Section titled “Hello world”import subprocessimport jsonimport time
proc = subprocess.Popen( ["winpane-host"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1,)
def rpc(method, params, req_id): msg = json.dumps({"jsonrpc": "2.0", "method": method, "params": params, "id": req_id}) proc.stdin.write(msg + "\n") proc.stdin.flush() return json.loads(proc.stdout.readline())
# Create a HUDresult = rpc("create_hud", {"placement": {"monitor": {"index": 0, "anchor": "top_left", "margin": 40}}, "width": 300, "height": 100}, 1)sid = result["result"]["surface_id"]
# Add elementsrpc("set_rect", { "surface_id": sid, "key": "bg", "x": 0, "y": 0, "width": 300, "height": 100, "fill": "#14141ec8", "corner_radius": 8,}, 2)
rpc("set_text", { "surface_id": sid, "key": "msg", "text": "Hello from Python", "x": 16, "y": 16, "font_size": 18,}, 3)
rpc("show", {"surface_id": sid}, 4)
# Keep alivetime.sleep(10)proc.terminate()The host process manages all surfaces. When the process exits (or stdin closes), all surfaces are destroyed.
How the protocol works
Section titled “How the protocol works”Each request is a single-line JSON object sent to stdin. Each response is a single-line JSON object read from stdout. Messages are newline-delimited.
Requests must include "jsonrpc": "2.0", a "method", "params", and an "id". Responses echo the id back. Event notifications arrive on stdout with a "method" field but no "id".
# Request{"jsonrpc": "2.0", "method": "create_hud", "params": {"placement": {"monitor": {"index": 0, "anchor": "top_left", "margin": 40}}, "width": 300, "height": 100}, "id": 1}
# Response{"jsonrpc": "2.0", "result": {"surface_id": "s1"}, "id": 1}
# Event notification (no id){"jsonrpc": "2.0", "method": "event", "params": {"type": "element_clicked", "surface_id": "s2", "key": "btn"}}Surface IDs
Section titled “Surface IDs”Surface IDs are strings prefixed by type: "s1", "s2" for surfaces, "t1", "t2" for trays. Pass these back in subsequent calls.
Elements
Section titled “Elements”Text:
rpc("set_text", { "surface_id": sid, "key": "label", "text": "CPU: 42%", "x": 16, "y": 50, "font_size": 14, "color": "#64dc9f", "font_family": "Consolas", # optional "bold": True, # optional "interactive": False, # optional}, 5)Rect:
rpc("set_rect", { "surface_id": sid, "key": "card", "x": 10, "y": 10, "width": 280, "height": 80, "fill": "#1e1e2ddc", "corner_radius": 6, # optional "border_color": "#505078aa", # optional "border_width": 1, # optional}, 6)Image:
rpc("set_image", { "surface_id": sid, "key": "icon", "path": "C:/icons/logo.png", # local file path "x": 10, "y": 10, "width": 32, "height": 32,}, 7)Remove:
rpc("remove_element", {"surface_id": sid, "key": "label"}, 8)Panels and events
Section titled “Panels and events”result = rpc("create_panel", { "placement": {"position": {"x": 200, "y": 200}}, "width": 260, "height": 100, "draggable": True, "drag_height": 30, "position_key": "my_panel",}, 10)panel_id = result["result"]["surface_id"]
rpc("set_rect", { "surface_id": panel_id, "key": "btn", "x": 20, "y": 40, "width": 220, "height": 40, "fill": "#32508cc8", "corner_radius": 6, "interactive": True,}, 11)rpc("show", {"surface_id": panel_id}, 12)Events arrive as notifications on stdout. You need to read lines and check whether each line is a response (has id) or an event (has method):
import selectimport sys
def read_events(): while True: line = proc.stdout.readline().strip() if not line: break msg = json.loads(line) if "id" in msg: # Response to a request continue if msg.get("method") == "event": params = msg["params"] if params["type"] == "element_clicked": print(f"Clicked: {params['key']}")For a real application, you would run the reader in a separate thread or use non-blocking I/O.
Surface operations
Section titled “Surface operations”rpc("show", {"surface_id": sid}, 20)rpc("hide", {"surface_id": sid}, 21)rpc("set_position", {"surface_id": sid, "x": 500, "y": 300}, 22)rpc("set_size", {"surface_id": sid, "width": 400, "height": 200}, 23)rpc("set_opacity", {"surface_id": sid, "opacity": 0.8}, 24)rpc("set_capture_excluded", {"surface_id": sid, "excluded": True}, 25)rpc("set_backdrop", {"surface_id": sid, "backdrop": "mica"}, 26)rpc("fade_in", {"surface_id": sid, "duration_ms": 300}, 27)rpc("fade_out", {"surface_id": sid, "duration_ms": 500}, 28)rpc("anchor_to", { "surface_id": sid, "target_hwnd": 65538, "anchor": "top_right", "offset_x": 8, "offset_y": 0,}, 29)rpc("unanchor", {"surface_id": sid}, 30)Tray icons
Section titled “Tray icons”result = rpc("create_tray", {"tooltip": "My App", "icon_path": "icon.png"}, 40)tray_id = result["result"]["surface_id"] # e.g., "t1"
rpc("set_menu", { "surface_id": tray_id, "items": [ {"id": 1, "label": "Settings"}, {"id": 99, "label": "Quit"}, ],}, 41)
# Associate a panel as popuprpc("set_popup", { "surface_id": tray_id, "panel_surface_id": panel_id,}, 42)Cleanup
Section titled “Cleanup”rpc("destroy", {"surface_id": sid}, 50)proc.terminate() # or close stdin to let the host exitThe host exits when stdin reaches EOF. All surfaces are destroyed automatically.
Limitations
Section titled “Limitations”- Custom draw is not available over JSON-RPC (requires in-process GPU access)
- Image paths must be local files accessible to the host process
- No batch operations; each request is processed individually
- Tray events do not include a tray ID (cannot distinguish which tray was clicked if multiple exist)
Next steps
Section titled “Next steps”- Protocol reference - Full JSON-RPC method documentation
- Cookbook - Recipes with JSON-RPC equivalents
- Limitations - Known constraints