Skip to content

feat(input): user-session input broker MVP for service mode#129

Open
bestlux wants to merge 1 commit into
mainfrom
feat/user-session-input-broker
Open

feat(input): user-session input broker MVP for service mode#129
bestlux wants to merge 1 commit into
mainfrom
feat/user-session-input-broker

Conversation

@bestlux

@bestlux bestlux commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

The MSI-owned LocalSystem service runs in session 0 and can never observe or inject interactive desktop input, so service-mode input honestly reported service_session_unsupported and physical mouse/keyboard handoff did not work. This PR lands the smallest safe user-session input broker so normal unlocked-desktop input sharing works in service mode, while the service remains the trust/network/routing authority.

Architecture choice: tray-hosted broker (no new helper binary). The tray already runs at sign-in in the intended user's interactive session as the allowed user and already speaks the service control pipe. The service named-pipe ACL (SYSTEM, Administrators, exactly one allowed user SID) stays the trust boundary; the broker rides it and adds no new socket, ACL surface, firewall rule, relay, or transport change. Full rationale and control-path map: docs/architecture/user-session-input-broker.md.

What changed

  • daemon: InputBrokerRelay state plus AttachInputBroker / ExchangeInputBroker / DetachInputBroker control-plane RPCs. In service mode the capture backend relays broker-observed events through the unchanged edge-switch/layout/owner/per-peer-queue paths, and the inject loop leaves queued frames for the broker (owner re-checked per frame at dispatch) instead of failing them, while a fresh broker is attached.
  • tray: hosts the broker in the interactive session; captures with the shared HookInputPump (hook/raw-input translation moved from the daemon into platform-windows for reuse), injects authenticated incoming frames with SendInput, applies daemon-owned lock state, and flushes synthetic release events before detach.
  • fail closed: attach rejected outside service mode (no double capture next to a user-session daemon) and for session-0 brokers; wrong/stale/replaced tokens rejected; a silent broker reverts the runtime to service_session_unsupported within ~3s; held keys/buttons get synthetic releases on target change or broker loss.
  • diagnostics: input_capture_backend_mode reports user_session_broker only while a fresh broker is attached; tray Settings and docs/user/service-mode.md state the unlocked-desktop-only scope; new input_broker_* transport events.

Explicitly not claimed / unchanged

Lock screen, secure desktop, UAC prompts, elevated apps, and other users' sessions remain unsupported (no BND-NEXT-9C claims). Pairing/trust, firewall, transport, and layout sync are untouched; existing service-session unsupported diagnostics are preserved when no broker is attached.

Validation

  • cargo fmt --all -- --check clean
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo test --workspace: 426 passed, 0 failed (includes new state::tests::input_broker fail-closed/routing tests, broker backend readiness tests, and ipc-api event round-trip tests)
  • git diff --check clean
  • Gap: real interactive capture/injection cannot be exercised from this shell. Manual two-PC dogfood steps below are required before any release-grade claim.

Manual two-PC dogfood

  1. On PC-A install the MSI with the intended user SID (service mode registered, BoundlessService running); sign in as that user so the tray starts.
  2. boundlessctl daemon status → confirm the service owns npipe://./pipe/boundlessd-api; tray Settings should briefly show service_session_unsupported, then user_session_broker once the tray attaches.
  3. Pair PC-A and PC-B normally, apply a left/right layout.
  4. Push the cursor past the configured edge on PC-A: capture should hand off, local input lock engage, and PC-B receive mouse/keyboard; double-Ctrl escape must return control locally.
  5. Reverse direction from PC-B; verify transport events shows input_broker_attached / input_broker_inject_dispatched and no input_broker_inject_report failures.
  6. Kill the tray on PC-A mid-capture: within ~3s status must revert to service_session_unsupported and remote input must stop (fail closed); restart tray → broker re-attaches.
  7. Lock the PC-A screen: confirm input sharing does not work there (expected, documented unsupported).

Risks

  • Poll-based exchange (8 ms active / 40 ms idle) adds up to one poll interval of latency per direction; streaming is a follow-up if two-PC latency evidence warrants it.
  • Abrupt broker death can leave keys held on a remote peer until release synthesis runs on the next capture-target transition.
  • Hook-pump relocation to platform-windows is behavior-preserving (tests ported), but the in-session daemon capture path shares it — regressions there would affect non-service mode too.

🤖 Generated with Claude Code

The LocalSystem service cannot observe or inject interactive desktop
input from session 0, so service-mode input truthfully reported
service_session_unsupported and real mouse/keyboard handoff never
worked. This adds the smallest safe broker path while keeping the
MSI-owned service as the trust/network/routing authority:

- daemon: InputBrokerRelay state plus Attach/Exchange/Detach
  control-plane APIs on the existing allowed-user named pipe; the
  service-mode capture backend relays broker events through the
  unchanged edge-switch/owner/peer-queue paths, and the inject loop
  hands queued frames to the broker instead of dropping them while a
  fresh broker is attached
- tray: hosts the broker in the interactive user session; captures
  with the shared HookInputPump (moved to platform-windows), injects
  authenticated frames with SendInput, and applies daemon-owned lock
  state
- fail closed: attach rejected outside service mode and for session 0
  brokers; wrong/stale/replaced tokens rejected; a silent broker
  reverts the runtime to service_session_unsupported within seconds;
  held keys get synthetic releases
- diagnostics: capture backend mode reports user_session_broker only
  while attached; tray Settings states the unlocked-desktop-only scope

Scope is the normal unlocked desktop of the selected allowed user.
Lock screen, secure desktop, UAC prompts, and elevated apps remain
unsupported (no BND-NEXT-9C claims).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant