v1 is three POSIX shell scripts driving lisgd and tmux. It works and is running on
the rack display today, but it hard-codes a single linear carousel of socktop hosts,
and every new kind of screen is a special case (`SOCKTOP_AUX_CMD` is the aux screen
because there was nowhere else to put it). v2 makes the layout a first-class,
user-authored thing and rewrites the logic in Rust.
---
## 1. Decisions locked in
| Question | Decision |
| --- | --- |
| Rust scope | One Rust binary owns config, the grid model, gesture input (evdev, replacing lisgd) and drives tmux. **tmux stays the pane engine**; zellij shelved (§4.3) |
| Grid coordinates | Sparse ordinals — they define *order and relative position*, not physical slots |
| socktop group | **One cell**, regardless of host count; expands into a sub-sequence internally |
| Payload TUIs | Installer *offers* socktop / uptime-kuma-status / unifly, never requires them |
| Distribution | **Source only for now.** Clone from Gitea, `cargo build --release`. No prebuilt binaries, no apt repo yet — revisit after feeling the pain on the Wyse |
| v1 config | **Clean break.** No `.env` reader, no `migrate` command. Rack display's YAML gets hand-written once |
Each type knows how to turn its parameters into one or more tmux panes. Every type
also accepts `command:` to override the generated command outright, and `title:` to
override the pane border label — so nobody is ever blocked by a type not yet
supporting a parameter they need.
| Type | Parameters | Panes | Generated command |
| --- | --- | --- | --- |
| `socktop` | `socktop_group` (list or CSV, **required**), `layout` (default `tiled`) | one per profile, plus the tiled overview as sub-screen 0 | `<socktop> -P <profile>` |
the cursor in memory: one gesture becomes two or three tmux calls instead of six, and
the navigation logic becomes a pure function over a struct that can be unit tested
without a display, a touch panel or a tmux server. That test suite is the main reason
the grid model is worth attempting at all.
### 4.2 CLI
| Command | What it does |
| --- | --- |
| `socktop-swipe run` | Build the tmux session, spawn the terminal attached to it, run the gesture loop, exit when the session exits. **The single autostart line.** |
| `socktop-swipe daemon` | Gesture loop only, against an existing session (for a systemd-user split) |
| `socktop-swipe attach` | Build and attach the session only |
| `socktop-swipe validate` | Parse config, resolve the grid, check binaries, print the map |
| `socktop-swipe doctor` | Interactive touch diagnostics; list candidate devices; report what the panel actually sends |
| `socktop-swipe next\|back\|up\|down` | Drive a running instance over its control socket — for keybindings and testing without a panel |
`run` as one process also kills the v1 double-instance footgun by construction: the
second one fails to grab the device and exits with a clear message.
### 4.3 Multiplexer: tmux, with zellij shelved
**tmux stays.**`session/` is a `Multiplexer` trait with a tmux implementation behind
it, so the question can be reopened cheaply, but it is not being evaluated for v2.
Shelved rather than rejected. The reasoning, recorded in `notes/DESIGN.md` so it is not
re-litigated: "available as a crate" does not buy in-process integration —
`zellij-server`/`zellij-utils` are workspace crates for the binary, not a library
surface, so it would still be a subprocess driven over a CLI exactly like tmux. The
blocking issue is addressing: the grid needs *"focus cell 0x1, sub-screen 3, zoomed"* as
one deterministic call, which tmux gives directly as `select-pane -t session:window.3`
plus `resize-pane -Z`, whereas zellij's CLI is direction-oriented (`move-focus left`).
Footprint also runs the wrong way on 2 GB boxes, and a zellij source build on an Atom is
a much worse story for the install guide than `apt install tmux`.
The one idea worth keeping on the shelf: running the navigation state machine as a
**WASM plugin inside zellij** via `zellij-tile`, which would give real event
subscriptions instead of CLI polling. Revisit only if tmux becomes the bottleneck.
---
## 5. Input: evdev instead of lisgd
`input.rs` opens the event device and reads multitouch **protocol B** (`ABS_MT_SLOT`,
`ABS_MT_TRACKING_ID`, `ABS_MT_POSITION_X/Y`), tracking per-slot start and end points.
A swipe fires when the dominant axis exceeds `threshold`, the off-axis angle is within
`leniency`, and the peak simultaneous contact count is in `fingers`.
What this buys:
- **Drops the C toolchain from the install path.** No `libinput-dev`, no `libX11-dev`,
no `git clone git.sr.ht/~mil/lisgd`, no `make`. On the Wyse's eMMC that is real.
- **`grab: true` replaces `--xignore`.** `EVIOCGRAB` takes the device exclusively, so X
never sees the touches at all — which is what the `Option "Ignore"` InputClass was
faking. That removes an `/etc/X11/xorg.conf.d` file *and* the logout/login step it
required. The X rule stays documented in `notes/` as a fallback for anyone who wants
touch to reach X for other apps.
- **Compositor-agnostic for free.** Reading `/dev/input` directly means nothing in the
gesture path is X11-specific. This is not a Wayland port — the terminal and tmux
still are what they are — but it removes one of the reasons v1 could not be one.
- **The `fingers: [1,2,3]` workaround becomes honest.** Instead of binding three
separate lisgd gestures per direction, the peak contact count is just a field on the
detected swipe, and `doctor` can print it.
Still required: read access to the device. Installer offers the `input` group (needs a
relogin) or a udev rule granting the display user access to that one device (no
relogin). Prefer the udev rule and say why.
---
## 6. Installer
`install.sh` stays POSIX sh — it has to run before any Rust exists.
```sh
curl -fsSL https://gt.wittyoneoff.com/jason/socktop-swipe/raw/branch/main/install.sh | sh
```
Piping to `sh` leaves no stdin for prompts, so the script reads answers from
`/dev/tty` explicitly. If there is no tty it falls back to defaults and says so.
`--yes` takes every default non-interactively; `--no-<thing>` declines individually.
### 6.1 Dependency preflight
Every dependency is **checked before anything is installed**, and the whole report is
printed at once so you can see the full cost up front rather than discovering it one
prompt at a time. Each missing item comes with a concrete offered fix; anything with no
known fix on the detected distro is reported as such rather than silently skipped, with
the manual step spelled out.
| Checked | How | If missing |
| --- | --- | --- |
| distro + package manager | `/etc/os-release`, which of `apt-get`/`dnf`/`pacman`/`zypper` | unsupported → print manual package list and continue in degraded mode |
| `tmux` (or `zellij`, per §4.3) | `command -v` + version | offer the distro package |
| terminal emulator | probe `alacritty`, `foot`, `kitty`, `xterm` | offer to install one; explain that it must be GPU/GL-capable-ish and legible at panel size |
| `cargo` / rustup | `command -v cargo` | offer rustup, stating the disk cost (~1.2 GB toolchain + target dir) before asking |
| C toolchain | only if something still needs building | offer build-essential equivalent |
| window manager | probe i3, detect a running WM | needed only for the `--i3`-equivalent autostart; skip that step cleanly if absent |
| display manager | `/etc/lightdm`, `/etc/gdm3`, `/etc/sddm.conf` | autologin is only offered for the one detected; unknown DM → print the manual snippet |
| screen locker | probe `xss-lock`, `light-locker`, `i3lock`, `xscreensaver` | offer to disable, naming the specific unit or process found |
| touch device | enumerate `/dev/input/by-id/*event*` with touchscreen capabilities | none found → point at `socktop-swipe doctor` and stop before writing a broken config |
| device read access | try opening the chosen device | offer udev rule (default) or `input` group |
| `git` | `command -v` | needed only for the unifly source build |
| disk space | `df` on `/usr/local`, `$HOME`, and the cargo target location | warn *before* starting a build that will not fit — this is the Wyse's 8 GB eMMC case specifically |
The report ends with a single summary — *"3 things will be installed, 2 files changed,
~1.4 GB of disk used. Continue? [y/N]"* — and nothing before that point has modified
the system.
### 6.2 Flow
1.**Preflight** (§6.1). Print the full report, get one confirmation.
2.**Toolchain** — if no cargo: *"Rust is needed to build from source. Install rustup? [Y/n]"*
3.**Build and install** socktop-swipe from source to `/usr/local/bin`.
│ and: screen size and rack size are separate measurements
│ (9" screen, 10" mini-rack mount, 19" rack via the adapter)
├── V1-BASH.md what v1 did and where to find it (tag v1.2)
└── TODO.md
```
Tag the current tree `v1.2` before deleting the shell scripts, so the working bash
version stays recoverable.
---
## 10. Work order
Each milestone ends somewhere testable.
| # | Milestone | Done when |
| --- | --- | --- |
| 1 | Tag `v1.2`. Scaffold the crate. Config types + `coord.rs` + `validate` | `socktop-swipe validate` prints the rack layout map from YAML |
| 2 | `grid.rs` navigation state machine, no I/O | Unit tests cover: sparse columns, sub-sequence entry from left vs right, vertical snap, cursor persistence, both ends of every row |
| 3 | `session/tmux.rs` + `attach` | Session builds correctly from YAML on the desktop; drive it with `next`/`back`/`up`/`down` by hand — no touch panel needed |
| 4 | `input.rs` evdev + `doctor` | `doctor` correctly reports direction and contact count on the LattePanda's ILITEK panel |
| 5 | `run` wires it together | LattePanda runs the full default grid by touch, unifly above and kuma below |
| 6 | Installer rewrite | Clean-VM install works, prompts behave when piped from curl, `--yes` works |
| 7 | README + notes/ split | Reads correctly to someone who has not seen the project |
| 8 | **Wyse 3040 validation** | Debian minimal → working rack display, following only the README. Every stumble is a fix, then re-run |
| 9 | Release | Tag v2.0, `config.example.yaml` matches the rack, `cargo clippy` clean with no `#[allow]` |
Milestone 5 is the point at which the LattePanda switches over. Its v1 install stays
untouched until then — v2 uses a different config path and a different tmux session
name, so both can be installed side by side during milestone 3–5 testing.
---
## 11. Open questions
**Q1 — column tie-break on snap.** From `0x2`, if the row above has cells at `-1x1`
and `-1x3`, both are distance 1. Plan says lower column (left) wins. Fine? This only
ever fires on the first entry into a row, so it is low-stakes.
**Q2 — the indicator (§8).** Build it, or cut it from v2?
**Q3 — repo visibility.** Distribution is Gitea-source-only for now, which is settled.
But the README is being written for a public audience and the Wyse is reference
hardware for a public guide. Is the Gitea repo publicly readable without a login? If
not, the curl one-liner in the README will not work for anyone but you.