1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
# 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/<name>.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-<name>: #...; --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:<run>/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 <host> 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.
|