from pathlib import Path import subprocess,time,json,re RUST='/nix/store/zjlcmhxqkhchysmjj9sk53hks1h5wc3l-k230-shell-rust-riscv64-unknown-linux-gnu-0.1.0/bin/k230-shell-rust' PYTHON='/nix/store/v189xydz6qkcd4cbkixcmv91w8hbc560-python3-riscv64-unknown-linux-gnu-3.14.7/bin/python3' env=['runuser','-u','shell','--','env','HOME=/home/shell','XDG_RUNTIME_DIR=/run/shell','WAYLAND_DISPLAY=wayland-1','SWAYSOCK=/run/shell/sway-ipc.sock','PATH=/run/current-system/sw/bin:/run/current-system/sw/sbin'] def call(args):return subprocess.run(args,check=True,capture_output=True,text=True,timeout=15).stdout before=call(['journalctl','-u','shell-ui','--no-pager','-o','cat']) call(env+[RUST,'--surface','hide']);call(env+['swaymsg','card_shell home']);time.sleep(1) p=Path('/run/shell/home-menu-pointer-probe.py');p.write_text('import socket,struct,time\nfrom pathlib import Path\nWIDTH=568\nHEIGHT=1232\nclass RenameKeyboard:\n """A real `zwp_virtual_keyboard_v1` connection, the same technique\n `tests/card_virtual_keyboard.py::Keyboard` already uses for the Wi-Fi\n password field, extended with a second mapped key (Return, so a rename\n can actually be *committed* -- task 2: "Enter commits") -- kept local to\n this script rather than changing that shared, already-used-elsewhere\n utility\'s fixed single-key keymap."""\n\n def __init__(self, path):\n self.socket = socket.socket(socket.AF_UNIX)\n self.socket.settimeout(10)\n self.socket.connect(str(path))\n self.serial = 2\n self.globals = {}\n self.send(1, 1, struct.pack(\'=I\', 2)) # wl_display.get_registry\n self.roundtrip()\n self.bind(\'zwp_virtual_keyboard_manager_v1\', 4)\n self.bind(\'wl_seat\', 5)\n self.send(4, 0, struct.pack(\'=II\', 5, 6))\n self.serial = 6\n # xkb keycode = evdev keycode + 8. =38 (\'a\', evdev 30) and\n # =36 (Return, evdev 28) are the only two keys this probe ever\n # sends -- enough to type a distinguishable rename and commit it.\n keymap = b\'\'\'xkb_keymap {\nxkb_keycodes "probe" { minimum=8; maximum=255; =38; =36; =22; };\nxkb_types "probe" { type "ONE_LEVEL" { modifiers=None; map[None]=Level1; level_name[Level1]="Any"; }; };\nxkb_compatibility "probe" {};\nxkb_symbols "probe" { key { type="ONE_LEVEL", [ a ] }; key { type="ONE_LEVEL", [ Return ] }; key { type="ONE_LEVEL", [ BackSpace ] }; };\n};\\0\'\'\'\n fd = os.memfd_create(\'home-test-keymap\', os.MFD_CLOEXEC)\n try:\n os.write(fd, keymap)\n self.send(6, 0, struct.pack(\'=II\', 1, len(keymap)), fd)\n finally:\n os.close(fd)\n self.roundtrip()\n\n def send(self, obj, opcode, payload=b\'\', fd=None):\n message = struct.pack(\'=II\', obj, ((len(payload) + 8) << 16) | opcode) + payload\n if fd is None:\n self.socket.sendall(message)\n else:\n sent = self.socket.sendmsg([message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array(\'i\', [fd]))])\n assert sent == len(message)\n\n def read(self, n):\n data = b\'\'\n while len(data) < n:\n chunk = self.socket.recv(n - len(data))\n assert chunk, \'Wayland test connection closed\'\n data += chunk\n return data\n\n def roundtrip(self):\n self.serial += 1\n callback = self.serial\n self.send(1, 0, struct.pack(\'=I\', callback))\n while True:\n obj, size_op = struct.unpack(\'=II\', self.read(8))\n size, opcode = size_op >> 16, size_op & 0xffff\n payload = self.read(size - 8)\n assert not (obj == 1 and opcode == 0), (\'Wayland protocol error\', payload)\n if obj == 2 and opcode == 0:\n name, length = struct.unpack(\'=II\', payload[:8])\n interface = payload[8:8 + length - 1].decode()\n self.globals[interface] = name\n if obj == callback and opcode == 0:\n return\n\n def bind(self, interface, obj):\n encoded = interface.encode() + b\'\\0\'\n padded = encoded + b\'\\0\' * ((-len(encoded)) % 4)\n self.send(2, 0, struct.pack(\'=II\', self.globals[interface], len(encoded)) + padded + struct.pack(\'=II\', 1, obj))\n\n def press(self, evdev_keycode=30):\n stamp = int(time.monotonic() * 1000) & 0xffffffff\n self.send(6, 1, struct.pack(\'=III\', stamp, evdev_keycode, 1))\n self.send(6, 1, struct.pack(\'=III\', stamp, evdev_keycode, 0))\n self.roundtrip()\n\n def press_a(self):\n self.press(30)\n\n def press_enter(self):\n self.press(28)\n\n def close(self):\n self.socket.close()\n\nclass VirtualPointer(RenameKeyboard):\n """Advertise a real pointer seat capability and inject wl_pointer events.\n\n Sway cursor IPC alone can exercise compositor chrome, but it does not\n create the pointer device capability the Rust layer-shell client needs.\n """\n def __init__(self, path):\n self.socket = socket.socket(socket.AF_UNIX)\n self.socket.settimeout(10)\n self.socket.connect(str(path))\n self.serial = 2\n self.globals = {}\n self.send(1, 1, struct.pack(\'=I\', 2))\n self.roundtrip()\n self.bind(\'zwlr_virtual_pointer_manager_v1\', 4)\n self.bind(\'wl_seat\', 5)\n self.send(4, 0, struct.pack(\'=II\', 5, 6))\n self.serial = 6\n self.roundtrip()\n\n def click(self, point, button=1):\n stamp = int(time.monotonic() * 1000) & 0xffffffff\n self.send(6, 1, struct.pack(\'=IIIII\', stamp, int(point[0]), int(point[1]), WIDTH, HEIGHT))\n self.send(6, 4)\n self.roundtrip()\n # Roundtrip acknowledges Sway, not the emulated Rust client\'s loop.\n # Give pointer entry and each human click phase distinct dispatch turns.\n time.sleep(0.1)\n code = 0x111 if button == 3 else 0x110\n self.send(6, 2, struct.pack(\'=III\', stamp, code, 1))\n self.send(6, 4)\n self.roundtrip()\n time.sleep(0.1)\n self.send(6, 2, struct.pack(\'=III\', (stamp + 200) & 0xffffffff, code, 0))\n self.send(6, 4)\n self.roundtrip()\nimport sys\np=VirtualPointer(Path(\'/run/shell/wayland-1\'))\ntry:\n p.click((82,1140),3)\n time.sleep(1)\n if sys.argv[-1]==\'dismiss\':p.click((30,150))\nfinally:p.close()\n');p.chmod(0o444) try: call(env+[PYTHON,str(p),'dismiss']);time.sleep(1) after=call(['journalctl','-u','shell-ui','--no-pager','-o','cat']) scene=call(env+['swaymsg','card_shell debug-scene']) checks={'right_click_reaches_real_menu':after.count('app-menu-ready')>before.count('app-menu-ready'),'outside_click_dismisses_menu':after.count('app-menu-dismiss')>before.count('app-menu-dismiss'),'dismiss_preserves_home':'home_selected=1 ' in scene and 'drawer_mapped=0 ' in scene} print('K230_HOME_MENU_POINTER_PROBE '+json.dumps({'class':'physical-board-injected-virtual-pointer','source':'288535289172c8465c4fa408eaa8ba2c7ab8a5ac','checks':checks,'result':'PASS' if all(checks.values()) else 'FAIL','scope':'Actual board compositor/Rust delivery; no physical mouse or finger claim'})) finally: call(env+[RUST,'--surface','hide']);call(env+['swaymsg','card_shell home']);p.unlink(missing_ok=True)