Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/tasks/62-full-usb-kvm.md
2026-08-10 17:00:23 -04:00

13 KiB

TASK 62 — full USB KVM: the phone becomes the glass and the hands

Status: the base composite and pkgrel 5 phone install are green; USB Hands and usb-signaller pkgrel 6 are source-only; cable acceptance and authority enforcement remain open. Opened 2026-08-07. Souveraine Gitea run 1237 built the receiver; Pixel3Arch run 1243 built, signed and published the full device archive. Those runs predate the persistent HID controller described below. This is not a completed task until that source is built and landed and the landing order and authority section below are accepted on hardware.

This task owns the one-cable composite from mode request through rollback and the first hardware acceptance. It does not own phone-as-host power or Type-C negotiation; TASK-58 does, and remains open.

Contract

The attached computer is the host. It sends a GUD display into the Pixel 3. The phone draws that display in a Wayland surface and returns keyboard, pointer and touch through boot-protocol HID. NCM keeps a control lane and smoo offers host-backed storage through FunctionFS/ublk.

One mode change owns all four functions:

kvm_mode = ffs.gud + ffs.smoo + ncm.usb0 + hid.keyboard + hid.pointer

usb-signaller prepares both FunctionFS mounts, starts both responders, waits for both readiness signals, then binds the UDC. Any failure tears the partial mode down and restores the previously working USB posture. The shell never writes configfs: it asks sessiond, which records an Action::UsbMode and calls the system D-Bus mechanism.

Where the work lives

Layer Canonical path State
Receiver + display + HID souveraine/src/bin/souveraine-usb-kvm.rs built ARM64
Session authority souveraine/src/sessiond/{protocol,device_state,server}.rs route + audit built; authorization is not finished
Human surface souveraine/surfaces/quickshell/ installed base leaves; conditional USB Hands controller exists only in current source
Souveraine package souveraine/packaging/arch/PKGBUILD.prebuilt receiver included on aarch64
Gadget mechanism usb-signaller/ full composite installed; persistent usb-hid-inject stream exists in package source only
Device packages Pixel3Arch/pkgs/{usb-signaller,smoo,blueline-usb-gadget} signed pkgrel 5 archive installed; usb-signaller pkgrel 6 is declared but unbuilt

The separate local souveraine-usb repo is a safety copy from the first layout attempt, not a source of record. Do not build another package from it.

The HID branch deliberately separates the mechanism from its human surface. USB control link composes or releases the keyboard, pointer and NCM functions. Agent hands now joins the existing Face surface to that wire when the screen is unlocked: the lower room exposes the current agent's text and voice paths, new/resume thread controls, a trackpad and scroll strip, explicit clicks, a summoned host keyboard with quick keys, and a held Ctrl+Alt+Delete. It remains one Souveraine conversation rather than a compact controller app beside her.

HidController.qml owns one usb-hid-inject stream process while Hands is joined. It batches pointer motion, queues text/key/click reports, consumes the helper's ready and per-command ok, writes release on close, stops the helper when UsbState raises busy for a mode switch, and stops on secure lock. The helper validates each type command before its first report so a rejected character cannot leave half that command on the host. The page never opens /dev/hidg*, and the controller never writes configfs.

That is the current source shape, not landed evidence. Pixel3Arch now declares usb-signaller 0.3.1-6 with the stream helper, and the Souveraine phone deploy path now stages tracked assets/face into ~/.souveraine/face before switching QML. Neither change has a Gitea package result, phone install/deploy, or motion-on-glass proof yet.

Verification already earned

  • Receiver unit tests: RGB565 dirty-rect conversion and HID usage mapping.
  • Receiver strict Clippy: clean.
  • ArchDev full release build: ARM64 PIE, 3,893,576 bytes, SHA-256 1fb58409cb1923599bcbcf229d1d2c1a61d4d7b2143387f21e7c6043ec44622d.
  • ArchDev sessiond build: ARM64 PIE, SHA-256 46046a53231b4f838fb47d8a8e75a51c6c8471ea3ccd2bc43bbf7ced2e38841a.
  • Mode scripts parse, the source/package copies match, and the Casey-owned runtime directory, environment file and /dev/hidg* nodes are reachable by the unprivileged receiver.

These earned results cover the receiver, composite and pkgrel 5 one-shot HID helper. They do not cover pkgrel 6, the persistent stream protocol, HidController.qml, the joined Face controls or the new face-asset deployment path.

gud-gadget has no declared licence. Casey explicitly accepted that risk for this private build. Do not publish its binary into a public archive until that is resolved.

Not finished — Session Authority

Routing a request through sessiond is not the same thing as making sessiond the sole authority. Today the shape is correct but the gate is incomplete:

  • usb is an ambient, read-only projection and can stay that way;
  • set_usb_mode is named and audited, but currently admits every supported device-role mode;
  • attached_identity and probe_owner are stable fields whose values are still null — federation and the probe lease registry do not feed them yet;
  • the held-power surface is intentionally available while locked, so HID/KVM changes currently have no credential or known-peer gate;
  • USB Hands itself requires an unlocked screen and a deliberate Agent hands join before its same-session IPC verbs can inject. That is a useful local guard, not a substitute for peer authority or closure of the direct D-Bus bypass;
  • usb-signaller's system D-Bus method can still be called around sessiond by another locally permitted process. The authority is advisory until mutation is restricted to the attested/sessiond path.

The intended rule is not "the agent cannot operate USB." The agent owns device operation. A known federated peer may receive a scoped policy that allows the agent to compose the port without repeatedly asking. An unknown peer, a new export, or physical-access posture needs user step-up; charging_only remains an always-available safe contraction. Add these operations to TRUST-BOUNDARY-MATRIX.md, emit not_permitted/refused_by_state from real checks, and close the direct D-Bus bypass before calling the authority seam finished.

Sam's proposed privileged smoo-host D-Bus service is the host-side mirror of this boundary, not its replacement: smoo owns export mechanics; Session Authority owns whether this peer, export and moment are allowed.

Management tether — one owner, hardware proof still owed

Live inspection on 2026-08-07 found and removed the last obsolete static owner:

  • usb-gadget-net.service was disabled/inactive but still existed unowned in /etc, with an unowned /usr/local/sbin/usb-gadget-net; both were removed from the phone and the stale rootfs enable link was deleted;
  • usb-signaller is now the sole configfs gadget owner;
  • blueline-usb-gadget 1.0.0-3 owns 40-usb0.network and the one-shot stale interface flush, which ran successfully at boot;
  • commit Pixel3Arch 8c9c50f adds that previously hand-installed package to the Gitea publisher as 1.0.0-3, so the fix no longer depends on this one phone remembering it.

The phone was in charging_only during inspection, so this is ownership and boot-graph proof, not a fresh SSH-over-NCM proof. Acceptance still requires a cold boot into developer mode with exactly one live usb* interface holding 172.16.42.1, no competing link-down route, and a successful SSH connection over the cable.

Separate and still open — Type-C/TCPM

The full KVM composite keeps the phone in USB device role; its green build says nothing about phone-as-host negotiation. TASK-58 owns the qcom-pmic-typec-smb2 port, CC/TCPM negotiation, internal SMB2 OTG regulator and source VBUS. That work is expected to materially improve negotiation and unlock proper C-to-C host behaviour. Do not let successful gadget-mode acceptance close or demote it.

Phone landing — 2026-08-07

Installed with one normal pacman -Syu after Casey deliberately enabled the split-tunnel wiufph profile from the foreign 10.10.30.x network:

  • souveraine r387, usb-signaller 0.3.1-5, smoo 0.0.2rc6-1, and blueline-usb-gadget 1.0.0-3 are installed and own their expected files;
  • the shell snapshot deploy landed revision 9ceaf61; its USB state service, Power Options surface and option row hash-match the repo;
  • live mode developer_mode, data role device, charger online;
  • advertised modes include hid_mode and kvm_mode;
  • KVM and smoo responders remain inactive until a mode request.

That remains the last proven phone state. It contains the one-shot pkgrel 5 helper and the pre-Hands shell; the current pkgrel 6 package source and joined Face controller have not been installed or deployed.

The first restart still advertised the old mode table. The package was correct; an unowned /etc/systemd/system/usb-signaller.service.d/local-fix.conf forced the service back to an unowned July 21 /usr/local/bin/usb-signaller. Both stale files were removed. The running service now executes the package-owned /usr/bin/usb-signaller. Neither stale file existed in the canonical rootfs.

Still true from the pre-landing inspection:

  • the legacy static gadget is removed;
  • charge_control_end_threshold=99; no start-threshold attribute.

The shell's KVM button is now enabled by live feature discovery, not a version guess.

First HID cable proof — 2026-08-07

With USB HID Injection selected, the device controller reported state=configured; /run/souveraine-usb/{keyboard,pointer} resolved to the live /dev/hidg0 and /dev/hidg1 endpoints. From the unprivileged phone session, usb-hid-inject type wrote Rook was here. The wire has hands. into the focused host field exactly. That closes host enumeration and keyboard report delivery for the one-shot helper. It does not exercise usb-hid-inject stream, the Face controller, pointer motion, secure attention, or command acknowledgements. Pointer report delivery and the return to developer USB are still owed before landing-order step 3 is complete.

Landing order

  1. Base done; USB Hands pending. The receiver, smoo and usb-signaller pkgrel 5 packages are published and installed. Build, sign, publish and install usb-signaller pkgrel 6 before exercising the persistent controller. Keep Wi-Fi SSH alive; do not make a USB mode change the only recovery path.
  2. Base done; USB Hands pending. get_modes includes hid_mode,kvm_mode and the installed base files are package-owned. Deploy the joined Face/HidController shell through its declared phone path, verify the tracked face assets landed with it, and record the deployed revision.
  3. Enter HID mode first. Confirm keyboard and pointer enumerate on the host; then prove persistent text, trackpad, scroll, clicks, quick keys and held Ctrl+Alt+Delete from the joined Face. Confirm a rejected character causes no partial text within its host-input line, leaving/locking releases the reports, and developer mode restores cleanly.
  4. Enter KVM mode. Confirm host enumeration for GUD, both HIDs, NCM and smoo; then confirm a host frame reaches the phone and touch returns to the host.
  5. Pull the cable and force one responder failure. In both cases the daemon must stop services, release FunctionFS, and leave or restore a usable mode.
  6. Only after those checks make KVM a normal daily-driver choice.

No USB Hands phone mutation belongs before step 1's pkgrel 6 package exists in the signed pipeline. No flash belongs to this acceptance path.

Acceptance

  • A host frame is visible on the phone; resize redraws without a blank buffer.
  • Physical keyboard, mouse and touch in that surface control the attached host.
  • NCM remains usable and smoo reaches its ready endpoint.
  • Closing the surface, unplugging, or a failed responder leaves no stuck UDC, root-only endpoint or dead management port.
  • The held-power sheet reports the actual mode and returns to developer USB.
  • Agent hands can be joined only for an unlocked, advertised HID path; its joined Face controls work through one persistent acknowledged helper, and leaving or locking closes that helper without a held key or pointer button.
  • The deployed QML and tracked face page arrive together; no manual stage-face.sh copy is needed to make the controls appear.

Connects to

USB/README.md (exploration and host-mode record), TASK-30 (verb tables), TASK-33 (adjacent charging policy), TASK-50 (the inverse hand-on-glass path), TASK-57 (phone-as-gadget dock), TASK-58 (host-role VBUS), SAF federation (attached identity and probe ownership).