ADR-0013 — Input Delivery Architecture
Status: Accepted (2026-09-26)
Related: ADR-0007 (audio ownership), ADR-0012 (WLR_BACKENDS/libinput finding), Sprint 7 overlay architecture, Sprint 8 gamepad input, security-model.md §3 and §8, post-mvp.md (Input Service)
Refines: the Wayland-seat-only assumption in Sprint 17 and the Touch/OSK entry in post-mvp.md
Context
Sprint 17 was written as a Wayland-native input feature: touch arriving as wl_touch, forwarded by the
compositor and hit-tested with wlr_scene_node_at; text entry via zwp_text_input_v3; the OSK rendered
by the overlay. Building T1 measured what the platform actually does instead:
- the compositor's
wlr_sceneholds only a full-screen background rect, andtoplevels=0while the shell is rendering playos_overlay_v1::set_surfaceignores itssurfaceargument — it is a stub- presentation reports
present zero-copy=9, copied=0 - client input already arrives through
libplayos→ evdev: that is how the built-in controller works, and how the shell receives the Armoury button - the touch panel itself is a healthy Linux device (
hid-multitouch,/dev/input/event5,ABS_MT_*)
Input therefore does not reach today's clients through the seat, and cannot: there is no wl_surface on
that path for a seat notification to target.
Decision
Two client classes, two input paths, chosen by what the client is.
- First-party clients — the shell, the overlay, and every game built against the SDK — receive input
through
playos-platform-api's evdev backend, alongside the gamepad support that already works. Touch joins that path (ABS_MT_*), and text entry becomes an explicit PlayOS API rather than a Wayland protocol. - Foreign Wayland clients — a browser, media clients, anything not built against the SDK — receive
input through the Wayland seat: pointer/touch forwarding with scene hit-testing, and
zwp_text_input_v3for text, exactly as Sprint 17 originally specified. This becomes live when such clients exist; Sprint 20's media/browser strategy is the likely trigger.
The OSK is one system keyboard, rendered by the overlay (Sprint 7 architecture) and driven over
playos_overlay_v1 (osk_visibility, osk_commit_string) — the shape Sprint 17's T5/T6 already describe.
It does not depend on zwp_text_input_v3.
The seat keeps a policy role regardless: the reserved-button intercept in system_button.c is defence
in depth, and it runs for the first time only after the ADR-0012-era WLR_BACKENDS fix restored the
libinput backend.
Why not a single path
- Wayland-only would require re-architecting trusted-client presentation (implementing
set_surfaceas a real scene surface, plus a session) and would duplicate an input path that already works — for apps that do not yet exist. - evdev-only would abandon standard input for the foreign clients Sprint 20 anticipates, putting IME, mice, tablets and accessibility out of reach.
Consequences
- Sprint 17 is re-scoped: T1 keeps its (done and hardware-verified) kernel half, and its seat-forwarding
half is now "for foreign Wayland clients"; T2/T4 are re-scoped the same way; T3's outcome
(
GetTouchPosition) is unchanged but fed from the platform API; T5–T8 stand as written. - The platform API gains touch, and later keyboard/text, as first-class surfaces — so SDK samples and the shell share one path.
- The sandbox model is unchanged: no new device permissions and
libplayoskeeps masking reserved buttons, so games get touch without a new trust decision. - A future
playos-inputservice (post-mvp.md) is the natural home for cross-client input policy; nothing here precludes it.
Open question (resolve before further seat work)
How does the shell present its frames today? It is not an xdg client, set_surface stores nothing, and
the compositor's own renderer never references a client surface. That answer decides whether first-party
clients could become Wayland surfaces cheaply — making path 2 universal — or are deliberately outside
Wayland, in which case path 1 is the only sane route.
Direction confirmed (2026-09-26)
Product intent, stated by the maintainer: an unparalleled console experience on handheld and PC; every game and application is compiled against the PlayOS SDK; the shell is a first-class UI (LVGL, rendering through raylib) whose job is browsing and launching games and apps and managing the PlayOS Marketplace — from the shell or a separate SDK app.
That resolves the open question above, and settles this ADR's choice:
- Path 2 is not planned. There are no foreign Wayland clients by design, so seat forwarding
and
zwp_text_input_v3are not on the product's path. They stay correct for a hypothetical future client class, andplayos-compositor/src/input.cis kept as that implementation — inert today and documented as such. The seat keeps its policy role (reserved-button intercept), which is its day-job here. - Input belongs to the platform API for every client class: gamepad (shipped), touch (handheld), and keyboard and mouse — because PlayOS targets PC hardware too, where a marketplace to browse and search makes them first-class rather than optional.
- Text entry is a shell/overlay concern, not a protocol. The OSK must be rendered by the overlay, since the shell stops rendering while a game is foreground (a locked shell invariant), and it is driven by a PlayOS API. LVGL's keyboard/textarea widgets are the pragmatic implementation (see Sprint 22) instead of a hand-rolled keyboard.
- The marketplace needs no new platform surface: networking landed in Sprint 16, TLS should
come with the OpenSSL that
wpa_supplicant's WPA3 support already pulls in (verify before relying on it), storage and the A/B update engine exist, and browsing/search is shell plus platform API. - Sequencing consequence: make the LVGL decision (Sprint 22) before building more hand-drawn shell UI or a hand-rolled OSK — it supplies the widget layer and the keyboard.
Follow-ups accepted (2026-09-26)
Path 1 is chosen for the shell's UI: LVGL as the widget layer, rendering through raylib.
Raylib stays the single rendering backend (ADR-0006) — rcore_playos.c keeps owning
Wayland/EGL/GLES2 and vsync — and the game ABI is untouched. LVGL supplies layout, widgets,
resolution adaptivity and a keyboard/textarea, which is where the system OSK comes from
instead of a hand-rolled one. Path 3 (LVGL replacing the rendering backend) is revisited only
through an ADR if raylib measurably limits LVGL.
The overlay uses the same UI layer as the shell: it is the trusted UI that can render above a running game, which is where the OSK has to live.
Flagged, not yet specified: app packaging, signing and updating for the marketplace. The
A/B .playosb engine updates the system, not individual apps, so this needs its own ADR before
marketplace implementation.