# deck-it — design **Date:** 2026-11-09 **Status:** approved (design), pending implementation plan ## Intent An always-on tablet dashboard, stream-deck, and second monitor for this desktop: Slackware-current, Hyprland with the Lua config (hypr-theme), UDT-themed. It reuses tabdeck's plumbing (Python stdlib HTTP server, a web page in the tablet's browser over `adb reverse`, and a virtual monitor streamed with wayvnc) but replaces tabdeck's fixed stats/keys/mull surface with a module system, and swaps tabdeck's Arch/systemd assumptions for Slackware's. ## Goals 1. Always-on touch control on the Android tablet over USB; Wi-Fi optional. 2. Second monitor: virtual Hyprland output + wayvnc, shown in AVNC. 3. Stream-deck control of OBS: recording, scenes, audio channels, transitions, sources. 4. Read-only dashboards: system stats, music/volume, windows/workspaces/launch, claude-code + opencode usage (via llm-cost-dashboard), sb-builder progress. 5. Modular: a new app is a new module, with no core edits. 6. UDT-themed: the palette is read, never restated; a scheme switch recolours the tablet. ## Non-goals - Not a replacement for quickshell / rofi-scripts. They stay for the static desktop approach; this is the fast always-on surface. Existing commands (playerctl, wpctl, hyprctl, rofi scripts) are reused, not reimplemented. - Not a general automation engine. Only declared actions run. - No cloud, no account, no app-store install. - Assistant control: deferred. A module slot is reserved. ## Anti-goals (failures to avoid) - The tablet can ever run an arbitrary command. - A scheme or wallpaper change leaves the tablet stale or desynced. - Adding a module means editing the core server. - Anything requires systemd. ## Constraints - Slackware-current: SysV init, eudev, no systemd. Hyprland with the Lua config. - Python stdlib only, with one exception: `websocket-client` for obs-websocket. - Tablet: Android + AVNC + a Chromium browser. - UDT is the single source of theme truth. ## Module contract A module contributes: - **Backend** — a `make(cfg) -> Module` factory returning a `Module` with a `state()` callable (GET) and an `actions` dict of allow-listed verbs (POST). The client sends `{module, action, ...}`; the server maps ids to code and the client never sends a command. - **Frontend** — `static/modules/.js`, a plain object `{title, interval, mount(el), update(state)}` rendered into the shell grid. The core provides the HTTP server, auth/trust, the adb tunnel, theming, and the module registry. Modules are enabled in config under `[modules]`. ## Theming At startup `deckit/theme.py` reads: - `~/.config/rofi/udt/palette.rasi` — the structural palette, entries `name: #rrggbb[aa]` (base, mantle, crust, surface0-2, text, subtext0-1, overlay0-2, red, green, yellow, teal, blue, lavender, peach, mauve, termblue, scrim). - `~/.cache/udt/udt-accent.rasi` — `* { accent: #rrggbb; }`. It serves `/theme.css` with `:root { --udt-: #...; --udt-accent: #...; }`. The frontend styles only through those variables. When UDT regenerates a scheme (`install.sh`) or accent (`udt-accent` on wallpaper change), the server re-reads on an mtime poll and a page refresh picks it up. A missing palette falls back to a built-in Catppuccin Macchiato set so the page always renders. Fonts: Noto Sans for UI, Inconsolata Nerd Font Mono for numbers. ## Slackware wiring No systemd. `deck-it` is a bash launcher with `start` (default), `stop`, `pair`, `wifi`, `install`, `uninstall`, `env`, `setup-display`. - Start from Hyprland via hypr-theme's `exec-once` (the compositor must be up for the virtual output and wayvnc); `install` prints the exact line to add. - Plug-in autostart: an eudev rule in `/etc/udev/rules.d/70-deck-it.rules` with a non-blocking `RUN+` that setsids `deck-it start`. A login-time check covers an already-connected tablet. - No `loginctl`; lock/leave stays in the existing rofi powermenu. ## Second monitor Reuse tabdeck's `setup_display`: create a headless Hyprland output sized to the tablet (`hyprctl output create headless`, then the Lua `hl.monitor` table), run wayvnc with TLS + a random password in a private dir, and tunnel the VNC port to the tablet with `adb reverse tcp:5900 localfilesystem:/vnc.sock`. AVNC is opened with the login in the link. wayvnc works on Hyprland, which implements the wlroots screencopy and virtual-input protocols it needs. ## Security Reuse tabdeck's model unchanged in spirit: - Unix socket in a 0700 `$XDG_RUNTIME_DIR/deck-it`, exposed only by `adb reverse`. - Token cookie (HttpOnly, SameSite=Strict) unlocks the API. - Host/Origin checks block DNS rebinding and cross-site writes. - Client sends ids, never commands. OBS's password and the OBS connection stay server-side. ## Per-module specifics | Module | Source | Actions | |---|---|---| | stats | /proc, sysfs (Intel xe: temp, fan, VRAM) | none (read) | | music | playerctl + wpctl | play/pause, prev/next, volume ±/mute | | windows | hyprctl clients/activewindow/workspaces | workspace switch, focus/close, launch (config commands) | | obs | obs-websocket v5 client | set scene, toggle record/stream, input volume/mute, set/trigger transition, toggle source | | llm | relay llm-cost-dashboard `/api/summary`, `/api/today`, `/api/claude_limits`, `/api/claude_session`, `/api/opencode_agents` | none (read) | | sbbuilder | `ssh sbb-remote jobs` (read-only) | none (read) | | assistant | deferred | — | ## Build order 1. Core — scaffold, config, theming, module registry, server + auth, frontend shell, launcher, second monitor. 2. First modules — stats, music, windows (proves the contract). 3. OBS — the stream-deck. 4. Read-only feeds — llm + sbbuilder. 5. Assistant — deferred. ## Open calls (settled in the plan) - OBS client: `websocket-client` is the single non-stdlib dependency. - sb-builder bridge: `sbb-remote jobs` over ssh; no change to sb-builder.