Add Remote controller capture IPC to the host and both SDKs
remote.focus lets a controller keyboard-capture lease ask core to enter or leave remote focus; the reply acknowledges admission only. Core reports the actual state with remote.focus events and forwards routed transitions as remote.input events carrying the source keycode and, on fresh presses, a canonical key name. A full focus mailbox revokes the lease; revocation drains pending focus work. No native capture owner is attached yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
27017bef11
commit
2d5327adf4
10 files changed
+583
-10
No files matched your search
@@ -141,6 +141,34 @@ def valid_remote_key_request(request):
|
||||
return False
|
||||
|
||||
|
||||
def valid_remote_captured_key(key):
|
||||
"""Validate one controller capture transition from core, without desktop access."""
|
||||
try:
|
||||
_fields(key, ("code", "pressed"), ("name", "repeat"))
|
||||
code, pressed = key["code"], key["pressed"]
|
||||
name, repeat = key.get("name", ""), key.get("repeat", False)
|
||||
if type(code) is not int or not 8 <= code <= 255 or type(pressed) is not bool or type(repeat) is not bool:
|
||||
return False
|
||||
if pressed and not repeat:
|
||||
return valid_remote_key({"name": name, "pressed": True})
|
||||
return name == "" and (pressed or not repeat)
|
||||
except (ProtocolError, TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
def valid_remote_focus_request(request):
|
||||
"""Validate remote.focus parameters; capture remains owned by core."""
|
||||
try:
|
||||
_fields(request, ("lease", "epoch", "sequence", "remote"))
|
||||
return (_token(request["lease"]) and _positive(request["epoch"])
|
||||
and _positive(request["sequence"]) and type(request["remote"]) is bool)
|
||||
except (ProtocolError, TypeError, ValueError, UnicodeError):
|
||||
return False
|
||||
|
||||
|
||||
REMOTE_FOCUS_REASONS = frozenset(("requested", "escape", "local", "unavailable"))
|
||||
|
||||
|
||||
class SessionError(Exception):
|
||||
def __init__(self, code, message):
|
||||
super().__init__("Tentacle session error " + str(code))
|
||||
@@ -330,9 +358,12 @@ class SessionClient:
|
||||
waiter.put_nowait(value)
|
||||
|
||||
def _event(self, event):
|
||||
_fields(event, ("sequence", "kind", "epoch"), ("action", "lease", "role", "grants", "reason", "speech"))
|
||||
_fields(event, ("sequence", "kind", "epoch"), ("action", "lease", "role", "grants", "reason", "speech", "input", "focus"))
|
||||
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", "") != ""):
|
||||
raise ProtocolError("Invalid session event")
|
||||
if event["kind"] == "remote.action":
|
||||
grants = event.get("grants")
|
||||
role = event.get("role")
|
||||
@@ -349,6 +380,18 @@ class SessionClient:
|
||||
if ("speech" in event or not identity or event.get("role", "") != ""
|
||||
or event.get("grants", []) != [] or event.get("reason") not in ("closed", "expired", "local", "replaced")):
|
||||
raise ProtocolError("Invalid revoke event")
|
||||
elif event["kind"] == "remote.input":
|
||||
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_captured_key(event.get("input"))):
|
||||
raise ProtocolError("Invalid input event")
|
||||
elif event["kind"] == "remote.focus":
|
||||
if (not _token(event.get("lease")) or event.get("action", "") != ""
|
||||
or event.get("role", "") != "" or event.get("grants", []) != []
|
||||
or "speech" in event or event.get("focus") not in ("local", "remote")
|
||||
or event.get("reason") not in REMOTE_FOCUS_REASONS):
|
||||
raise ProtocolError("Invalid focus event")
|
||||
elif event["kind"] == "remote.output":
|
||||
if (not _token(event.get("lease")) or event.get("action", "") != ""
|
||||
or event.get("role", "") != "" or event.get("grants", []) != []
|
||||
|
||||
Reference in new issue
Block a user