# Sway touch panel Phase 16 of `docs/project-plan.md`. Builds a Debian 12 live ISO for a touch-driven wall/counter panel: autologin into Sway, three fixed apps (Spotify, Home Assistant, a general web browser) switched with a finger via an always-on dock, and also controllable by Home Assistant/the local LLM over MQTT. **A different device from `../thin-client/`**, not a variant of it. The thin client is a couch-distance media station whose primary control surface is HA/MQTT and wayvnc, deliberately with no on-screen bars. This one is touched directly, so it needs the opposite: a persistent, finger-sized on-screen dock, native touchscreen input, and no gesture/remote-control machinery. It reuses the thin client's live-build toolchain and `configs/`+`agent/` directory-split convention, not its live-build tree — same relationship as `../audio-endpoint/`'s amd64 build to the thin client's. What ends up on the image: | | | |---|---| | Compositor | Sway, workspaces `1:spotify` / `2:home` / `3:web`, always-on touch dock | | Autologin | greetd, `default_session` straight into `/usr/local/bin/kiosk-session` | | Touch input | Native Wayland `wl_touch` (libinput), no pointer-emulation layer | | Music | Full Spotify GUI client (Flathub `com.spotify.Client`) — not a headless Connect receiver | | Home dashboard | Chromium in kiosk mode, `--app=$HA_URL`, auto-restart if it crashes | | Web browser | Firefox, minimal chrome (back/forward/reload/address bar), general browsing | | On-screen keyboard | wvkbd, toggled from the dock — see the caveat below, no auto-show | | Remote control (HA/LLM) | `touchpanel-agent`, a systemd service publishing HA MQTT-discovery entities | | Remote control (human) | SSH only — **no wayvnc on this image**, see below | ## Before you build > **No touch-panel hardware has been chosen.** Nothing here has been booted on real > metal or a real touchscreen. Touch-as-`wl_touch` (as opposed to some panels that only > expose an emulated-mouse HID interface) is assumed, not confirmed. Edit the `# CONFIGURATION` block at the top of [`scripts/build-touch-panel-iso.sh`](scripts/build-touch-panel-iso.sh): | Variable | What to put in it | |---|---| | `MQTT_BROKER_HOST` | LAN IP of the container host running Mosquitto (Phase 1) | | `HA_URL` | Home Assistant URL, e.g. `http://192.168.1.10:8123` — this is what loads in the Home workspace | | `KIOSK_USERNAME` | Autologin account name (`kiosk`) | | `TOUCHPANEL_NAME` / `IMAGE_HOSTNAME` | Per-panel identity; each touch panel needs its own | | `SSH_AUTHORIZED_KEY` | Optional. Password auth is disabled and there is no wayvnc, so without a key the only admin path is the local console. | ## Build ```sh sudo ./scripts/build-touch-panel-iso.sh ``` Same directory-split convention as the thin client: `configs/` and `agent/` are the human-edited, git-tracked source of truth; `live-build/config/includes.chroot/` is **generated** — wiped and rebuilt on every run, gitignored, never hand-edited. The only templated tokens are `@KIOSK_USERNAME@`/`@KEYBOARD_LAYOUT@`; everything else the in-chroot hooks need comes from `/etc/touchpanel-agent/config.env`. ## Why there's no wayvnc on this image The thin client's primary control surface is deliberately remote (HA/MQTT + wayvnc) because nobody is standing at a couch-distance media station. This panel is the opposite: it's mounted somewhere and touched directly, so remote *view/control* isn't the primary need the way it is for the thin client — SSH already covers "something's broken, let me fix it from a shell." If a real need for a remote-view channel shows up once this is on real hardware, adding wayvnc back is a small, isolated change (copy `../thin-client/configs/wayvnc/` and its two hooks) — deliberately left out for now rather than built against a guess. ## Home Assistant login The Chromium kiosk window's profile (`~/.config/touchpanel-chromium-ha`) is persistent across restarts specifically so logging into Home Assistant once survives a reboot — unlike the thin client's digest/admin kiosk Firefox profiles, which are stateless pages re-copied on every launch. Log in on first boot (tap the dock's Home button if the window doesn't already have focus) and it should stay logged in after that, **unverified** — depends on HA's own session cookie lifetime and whether Chromium's Wayland profile persistence behaves the way assumed. ## Spotify: full GUI, not a headless Connect receiver `../thin-client/` and `../audio-endpoint/` both run `spotifyd`/`librespot` — a Spotify Connect *receiver* with no interface of its own, controlled from the phone app or HA. This panel runs the real, official Spotify Linux client instead (`configs/spotify/spotify-launch`, Flathub `com.spotify.Client`), because the whole point of a touch panel is a screen you interact with directly: browsing your library, searching, picking a playlist with a finger. **Spotify Premium is required for playback either way** — same requirement as the headless receivers. Login is interactive, on the device, on first launch, and persists in the Flatpak's own data directory (`~/.var/app/com.spotify.Client`) — nothing here wipes or reseeds it. `touchpanel_agent/mpris_bridge.py` bridges Spotify's MPRIS interface (via `playerctl`) into an HA `media_player`-shaped set of entities, identical in shape to the thin client's own bridge, just pointed at `spotify` instead of `mpv`/`spotifyd` as the player name — **unverified that the Spotify Flatpak's MPRIS bus name is actually `spotify`** (some Flatpak-sandboxed apps register under a `flatpak.`-shaped name instead). If Playback state never updates, check `playerctl -l` on the booted panel and adjust `PLAYER_PRIORITY` in `mpris_bridge.py`. ## Touch dock An always-visible bar reserved at the bottom of the screen (`configs/eww/`, layer-shell `:exclusive true` so nothing ever tiles under or draws over it) with four buttons: **Spotify**, **Home**, **Web**, and **Keyboard**. The first three call `swaymsg workspace` directly — fixed constants in `eww.yuck`, nothing from MQTT/HA is ever interpolated into them, same rule as the thin client's now-playing widget. This is the touch-first equivalent of the thin client's HA **Screen** select entity — both exist, one for a finger, one for the LLM. Session-scoped (`exec_always` in `configs/sway/config`), not part of `touchpanel-agent`, for the identical reason the thin client's now-playing widget isn't: compositor UI should die with the compositor, not need a restart-independent lifecycle or open a second inbound control channel into the security-sensitive agent. **If `eww` isn't installed** (it's not in Debian bookworm main — `0500-eww-dock.hook.chroot` tries apt and otherwise leaves a documented placeholder), there is no on-screen app switcher at all. The panel still boots and shows Home; HA/ LLM-driven app switching via `touchpanel-agent`'s **Screen** select and **Show \** buttons still works either way, since that's a separate control path. ## On-screen keyboard — no auto-show, by design `configs/keyboard/toggle-keyboard` toggles `wvkbd` on and off. There is deliberately **no automatic show-on-text-field-focus** — that needs the `text-input-v3`/ `virtual-keyboard-v1` Wayland protocols wired all the way through each app via a shell component (the way Phosh does it for squeekboard), which this image doesn't run. Instead: tap the dock's Keyboard button before typing, tap it again to dismiss. This is a known, accepted limitation, not a bug to chase before the panel is otherwise useful — see the verification list below if you want to pursue proper auto-show later. `wvkbd`, not `squeekboard`, was picked specifically because it doesn't assume that shell integration exists; see `0700-onscreen-keyboard.hook.chroot`'s comment. It is also not in Debian bookworm main, so that hook has the same apt-first/documented- placeholder shape as `eww` and the thin client's `spotifyd`/`librespot` install. ## Home Assistant entities `touchpanel-agent` publishes MQTT-discovery configs on connect. Under the MQTT integration you should get one device per touch panel with: - **Show Spotify**, **Show Home**, **Show web browser** (buttons) — switch workspace and, for Spotify/Home, focus the already-running app rather than relaunching it (both are meant to stay open and stateful, unlike the thin client's stateless digest/admin kiosk pages) - **Screen** (select) — `1:spotify` / `2:home` / `3:web` - **Playback state** (sensor, with track metadata as attributes), **Volume** (number), and play/pause / next / previous / stop (buttons), bridged from Spotify's own MPRIS interface - A **media_player** discovery payload — see the thin client README's identical caveat: stock Home Assistant's MQTT integration has no `media_player` platform: the button/number/sensor entities above are what give playback control on a plain install; the HACS *MQTT Media Player* integration is what picks up this one. ### Security boundary Identical shape and reasoning to `../thin-client/README.md`'s section of the same name: `touchpanel_agent/mqtt_discovery.py` is the **entire** inbound control surface of this machine. The LLM never gets a direct network path here — the only chain is *LLM tool call → HA service call → MQTT → touchpanel-agent*. No HTTP listener, no websocket server, no exposed Sway IPC socket, no VNC (this image has none at all, see above). New control features belong as additional MQTT entities, not as a second listener. ## Manual verification still outstanding None of this has been run on hardware. In rough order: 1. The ISO builds at all (`lb build` is network-heavy and can fail on mirror hiccups). 2. greetd lands in Sway with no login prompt, on vt1, with getty@tty1 masked. 3. **Touch input actually arrives as `wl_touch`**, not an emulated pointer — the whole design (native touch in Chromium/Firefox/Spotify, no gesture-translation layer) assumes a real Wayland touchscreen driver. If it only shows up as a mouse, tapping will still mostly work by accident (a single point maps to a click) but pinch/ multi-touch in Chromium or Spotify won't. 4. `touchpanel-agent` connects to Mosquitto and the device appears in HA. 5. **Flathub app ID `com.spotify.Client` is correct** — flagged for verification in `0300-flatpak-spotify.hook.chroot`, same as the thin client's Steam Link ID. 6. Spotify login persists in `~/.var/app/com.spotify.Client` across a reboot. 7. Spotify's real MPRIS bus name — assumed `spotify`, not confirmed (see the Spotify section above); `playerctl -l` on the booted panel is the way to check. 8. **Chromium's Wayland `app_id` for a `--app=` kiosk window** — assumed to start with `chromium` (`configs/sway/config`'s `for_window` rule and `touchpanel_agent/main.py`'s `focus_criteria` both match on it). If focusing the Home workspace's window from the dock/HA stops working, check the real app_id with `swaymsg -t get_tree` and correct both places. 9. HA login persistence in the Chromium kiosk profile across a reboot (see above). 10. `eww` and `wvkbd` are not in Debian bookworm main — both hooks try apt and otherwise leave a documented placeholder (dock absent / keyboard absent respectively); neither has been checked against the real bookworm archive. 11. **The Firefox extension IDs in `configs/firefox/policies.json`** — same unverified caveat as the thin client's copy; see that README's identical item. 12. **Phase 16's own "reactive path" check**: power off the container host and confirm the panel still boots to Spotify + the browser (Home will show a connection error, which is the expected/acceptable failure mode — unlike the thin client, there's no local-media fallback to fall back to here since this device has no local media source at all). 13. Keyboard layout defaults to German (`KEYBOARD_LAYOUT="de"`) — same per-image, no-runtime-override handling as the thin client's identical setting. 14. Dock touch-target sizing (`configs/eww/eww.scss`, ~84px buttons) — a starting guess, not measured against a real finger on real glass at the real screen DPI chosen once hardware exists.