Add Remote Braille IPC to the host and both SDKs

remote.braille requests and events carry a typed op under a
braille-stream lease: the controller's width and gestures for a
controlled reader, rendered cells back to its child, and cells, local
geometry and display gestures for a controller. Direction is validated
per role; a full mailbox revokes the lease. No display owner is
attached yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chase CichorzandClaude Opus 5.5 committed 2026-11-06 16:06:38 -05:00
1 parent 477d860fb8
commit 2651b5e9bd
10 files changed
+476 -3

No files matched your search

+53 -2
View File
@@ -166,6 +166,48 @@ def valid_remote_focus_request(request):
return False
def valid_remote_braille(value, role=None, request=None):
"""Validate one Remote Braille operation; role/request also check direction."""
try:
_fields(value, ("op",), ("width", "gesture", "index", "cells"))
op = value["op"]
width, gesture, index = value.get("width", 0), value.get("gesture", ""), value.get("index", 0)
cells = value.get("cells")
if type(width) is not int or type(index) is not int or not isinstance(gesture, str):
return False
if op in ("size", "geometry"):
ok = 0 <= width <= 4096 and gesture == "" and index == 0 and cells is None
elif op == "gesture":
ok = (width == 0 and cells is None
and (gesture in ("pan-left", "pan-right") and index == 0
or gesture == "route" and 0 <= index < 4096))
elif op == "cells":
ok = (isinstance(cells, list) and 1 <= len(cells) <= 4096
and all(type(cell) is int and 0 <= cell <= 255 for cell in cells)
and width == 0 and gesture == "" and index == 0)
else:
return False
if not ok or role is None:
return ok
if role == "controlled":
return op in ("size", "gesture") if request else op == "cells"
if role == "controller":
return (op == "cells" or op == "geometry" and width == 0) if request else op in ("gesture", "geometry")
return False
except (ProtocolError, TypeError, ValueError):
return False
def valid_remote_braille_request(request):
"""Validate remote.braille parameters; display effects remain owned by core."""
try:
_fields(request, ("lease", "epoch", "sequence", "braille"))
return (_token(request["lease"]) and _positive(request["epoch"])
and _positive(request["sequence"]) and valid_remote_braille(request["braille"]))
except (ProtocolError, TypeError, ValueError, UnicodeError):
return False
REMOTE_FOCUS_REASONS = frozenset(("requested", "escape", "local", "unavailable"))
@@ -358,11 +400,12 @@ class SessionClient:
waiter.put_nowait(value)
def _event(self, event):
_fields(event, ("sequence", "kind", "epoch"), ("action", "lease", "role", "grants", "reason", "speech", "input", "focus"))
_fields(event, ("sequence", "kind", "epoch"), ("action", "lease", "role", "grants", "reason", "speech", "input", "focus", "braille"))
if not _positive(event["sequence"]) or not _positive(event["epoch"]):
raise ProtocolError("Invalid event sequence")
if (event["kind"] != "remote.input" and "input" in event
or event["kind"] != "remote.focus" and event.get("focus", "") != ""):
or event["kind"] != "remote.focus" and event.get("focus", "") != ""
or event["kind"] != "remote.braille" and "braille" in event):
raise ProtocolError("Invalid session event")
if event["kind"] == "remote.action":
grants = event.get("grants")
@@ -386,6 +429,14 @@ class SessionClient:
or event.get("reason", "") != "" or "speech" in event
or not valid_remote_captured_key(event.get("input"))):
raise ProtocolError("Invalid input event")
elif event["kind"] == "remote.braille":
braille = event.get("braille")
if (not _token(event.get("lease")) or event.get("action", "") != ""
or event.get("role", "") != "" or event.get("grants", []) != []
or event.get("reason", "") != "" or "speech" in event
or not (valid_remote_braille(braille, "controlled", False)
or valid_remote_braille(braille, "controller", False))):
raise ProtocolError("Invalid Braille event")
elif event["kind"] == "remote.focus":
if (not _token(event.get("lease")) or event.get("action", "") != ""
or event.get("role", "") != "" or event.get("grants", []) != []