Files
cthulhu/tests/native_tentacle_updates.py
ak47 573d15d7b4 Add Remote session foundations and feasibility checks
Add opt-in Tentacle protocol 1.1, explicit remote grants, lifecycle IPC and expiring core leases in both SDKs. Revoke access before update preparation, including failed updates.

Add transient secret entries, shared Braille cell export and isolated Xorg recovery experiments with focused tests and documented limitations. Production consent, desktop services and live interoperability remain pending.
2026-11-03 22:33:26 -05:00

81 lines
4.2 KiB
Python

"""Private GTK update confirmation and keyboard workflow; no installed packages."""
import select
from native_x11 import wait_for
def update_snapshot():
repositoryUrl = "https://example.org/tentacles"
return {
"Entries": [{"Entry": {
"ID": "probe", "LocalID": "echo", "Name": "Update probe",
"Repository": repositoryUrl, "RepositoryID": "example.source",
"Version": "1.0", "Enabled": True, "Presentation": True,
"SessionEvents": True, "RemoteGrants": ["remote-present"],
}, "State": "active"}],
"Repositories": [{"URL": repositoryUrl, "ID": "example.source", "Name": "Example",
"Tentacles": [{"ID": "echo", "Name": "Update probe", "Version": "1.1",
"Capabilities": ["presentation", "interaction", "output-observe", "keyboard-inject"]}]}],
"Updates": [{"ID": "probe", "LocalID": "echo", "Name": "Update probe",
"InstalledVersion": "1.0", "AvailableVersion": "1.1",
"RepositoryURL": repositoryUrl, "RepositoryID": "example.source"}],
}
def check_page(pyatspi, process, window, named, walk, focus, key, request, send, initial):
tab = next(node for node in walk(window)
if node.name == "Tentacles" and node.getRole() == pyatspi.ROLE_PAGE_TAB)
assert tab.parent.querySelection().selectChild(tab.getIndexInParent()), "cannot select Tentacles tab"
table = next(node for node in walk(window)
if node.name == "Tentacles" and node.getRole() == pyatspi.ROLE_TABLE)
focus(table)
key("Home")
update = named("Update")
wait_for(lambda: update.getState().contains(pyatspi.STATE_SENSITIVE), "Update did not enable")
# Tab from the table skips disabled Install and reaches Update.
key("Tab")
wait_for(lambda: update.getState().contains(pyatspi.STATE_FOCUSED), "Tab did not reach Update")
key("Tab")
wait_for(lambda: named("Settings").getState().contains(pyatspi.STATE_FOCUSED), "Update trapped Tab")
key("shift+Tab")
wait_for(lambda: update.getState().contains(pyatspi.STATE_FOCUSED), "Shift+Tab did not reach Update")
def confirmation():
for app in pyatspi.Registry.getDesktop(0):
for node in app:
if node and node.getRole() in (pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT):
if any("Current capabilities:" in child.name for child in walk(node)):
return node
return None
key("space")
dialog = wait_for(confirmation, "keyboard activation did not open confirmation")
message = " ".join(node.name for node in walk(dialog))
assert "version 1.0 to version 1.1" in message, message
assert "Current capabilities: presentation, remote-present." in message, message
assert "Repository declares: presentation, interaction, output-observe, keyboard-inject." in message, message
assert "accept keyboard input from the remote controller" in message, message
assert "Each session requires local approval" in message, message
cancel = next(node for node in walk(dialog) if node.name == "Cancel")
focus(cancel)
key("space")
wait_for(lambda: not confirmation(), "Cancel did not close confirmation")
assert not select.select([process.stdout], [], [], 0.2)[0], "Cancel sent a management request"
# The native button action uses the same path as keyboard activation.
update.queryAction().doAction(0)
dialog = wait_for(confirmation, "button action did not open confirmation")
accept = next(node for node in walk(dialog)
if node.name == "Update" and node.getRole() == pyatspi.ROLE_PUSH_BUTTON)
focus(accept)
key("Return")
value = request()
assert value["Tentacle"] == {"Action": "upgrade", "ID": "echo", "Entry": None,
"URL": "https://example.org/tentacles"}, value["Tentacle"]
send(dict(initial, Message="Update accepted."))
wait_for(lambda: update.getState().contains(pyatspi.STATE_SENSITIVE), "response left controls busy")
focus(named("Cancel"))
key("space")
assert process.wait(timeout=5) == 0
print("PASS: Update Tab/Shift+Tab, keyboard/button activation, versions/capabilities, Cancel, upgrade request")