Files
jasonwitty ce6acec299 v2 release prep: installer, README, packaging, notes; retire the v1 scripts
Installer rewritten around a preflight: distro, package manager, display
manager, window manager, terminal, tmux, cargo, git, screen locker, touch
device, device permissions and free disk are all checked BEFORE anything is
installed, and the total cost is printed once for a single confirmation.
Prompts read /dev/tty so they still work when the script is piped from curl,
and fall back to defaults with a notice when there is no terminal at all.

Several "[ test ] && action" statements were set -e landmines: under set -e an
AND-OR list that ends up false aborts the script, so a box with no lightdm, no
i3 or nothing to install would have exited silently partway through detection
-- which is exactly the fresh-Debian case the installer exists for. Rewritten
as if-statements and verified against a stripped PATH with no tmux, cargo, git
or package manager present. Also fixed cargo detection reporting blank instead
of NOT INSTALLED: the status of `cargo --version | cut` is cut's, and cut
succeeds on empty input, so the fallback never fired.

Device access now defaults to a udev rule matching touchscreens only, rather
than the input group, which grants access to every input device including the
keyboard and needs a full logout.

README rewritten for someone who has not seen the project: what the photo
shows, the hardware, install, then a config built up step by step, each step
with the YAML and the resulting map. Every example is verified verbatim
against the binary, and every relative link resolves. The mechanism and the
reasoning move to notes/: DESIGN.md, HARDWARE-NOTES.md, V1-BASH.md, TODO.md.

cad/README.md was a verbatim copy of the one inside
geeekpi_rack_adapter_release_v1/, so every path in it -- including the
screenshot -- was broken from where it sits. Corrected to its own level, and
it now states once that the 9-inch screen, the 10-inch mini-rack mount and the
19-inch rack are three different measurements.

The v1 shell implementation is removed; it stays recoverable at tag v1.2 and
notes/V1-BASH.md carries the setting-by-setting migration table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 13:08:42 -07:00

6.2 KiB

Design notes

Why the thing is built the way it is. The README says what to do; this says why, so neither has to carry both jobs.

There is exactly one monitor process per host. Zooming calls tmux resize-pane -Z, which makes one pane fill the window and sends SIGWINCH so the program repaints at the new size.

Three reasons that matters:

  • Half the processes. Separate full-screen instances would mean N extra processes and double the polling load on the monitored hosts. On a 1.9 GB display host that is the difference between comfortable and not.
  • No reconnect delay. Hidden panes keep running and stay connected, so swiping back shows current data, not a stale snapshot with a spinner.
  • Constant load. The monitored hosts see the same connections regardless of what is on screen.

Why the grid is sparse ordinals

The obvious design is that a coordinate names a physical slot. It falls apart on the first socktop group: a group of four hosts is five screens, so putting anything to its right means writing 0x5, and adding a fifth host means renumbering the rest of the row.

Making coordinates pure ordering removes that entirely. Only the sort order matters, so 0x1 and 0x5 are the same thing, and a group grows without disturbing its neighbours. The cost is that the file is not a literal map of the screen — which is what socktop-swipe validate is for.

Why return memory beats spatial snapping

Vertical movement had two plausible rules and they disagree. Purely spatial: from 0x1, up to -1x0, down again lands on 0x0. Return memory: it lands back on 0x1.

Return memory won because the thing you actually do with a wall display is glance away and glance back. Losing your place on every glance is the worse failure, and snapping is only ever needed to decide the first entry into a row. A layout where every row has a cell in the same column never snaps at all.

Why evdev instead of lisgd

libinput deliberately emits gesture events only for touchpads, never for touchscreens, so libinput-gestures and everything built on it cannot work here at all. Something has to read raw touch events. v1 used lisgd; v2 does it in-process.

What that bought:

  • No C toolchain in the install path. No libinput-dev, no libX11-dev, no git clone and make. On the Wyse's 8 GB of eMMC that is not a small thing.
  • grab: true replaces the X ignore rule. EVIOCGRAB takes the device exclusively, so X never sees the touches — which is what v1's Option "Ignore" InputClass was faking, except this needs no X restart and no logout. The X rule is still documented in the README as a fallback for anyone who wants touch to reach other applications.
  • One process, so the double-instance bug cannot happen. lisgd does not grab the device, so two copies made every swipe fire twice and the carousel appeared to skip. Now the second copy fails to grab and says so.
  • The contact-count workaround became honest. Instead of binding three separate lisgd gestures per direction, the peak contact count is a field on the detected swipe, and doctor prints it in English.

Averaging rather than summing the contacts' travel matters here: a panel reporting one physical finger as three contacts must not look like three times the displacement. There is a test for exactly that.

Why panes are addressed by id, and kept alive

Pane indices renumber when a pane dies. Pane ids (%12) do not, so every lookup uses them.

That leaves the question of what happens when a monitor exits — a typo in a generic command, a socktop that cannot reach its agent. tmux destroys a window when its last pane goes, which during construction breaks the next split-window with a baffling "no current target", and afterwards silently reshuffles the display.

v1 used remain-on-exit, which cannot actually do the job: it is a per-window option that new windows do not inherit, so there is always a gap between creating a window and setting it. v2 wraps each command instead:

<command>; s=$?; printf '\n[%s exited: status %s]\n' <name> "$s"; while :; do sleep 86400; done

The pane outlives the command, and the failure is visible on the wall display with its exit status — which is what a wall display is for. tmux already runs each command under sh, so this costs one shell that stays resident per pane rather than one that execs away.

Why at: "0x0" must be quoted

YAML reads an unquoted 0x0 as the hexadecimal number 0. The nasty part is that 1x0 is not valid hex and arrives as a string, so only the row-0 entries break and the failure looks arbitrary. Deserialization catches the integer case and prints the fix rather than a type error.

Multiplexer: tmux, with zellij shelved

src/session/ is a Multiplexer trait with a tmux implementation behind it, so the question is cheap to reopen. It was shelved rather than rejected, for reasons worth recording so it is not re-litigated:

  1. "Available as a crate" is not an embedding API. zellij-server, zellij-client and zellij-utils are published, but they are workspace crates for the binary, not a supported library surface. Realistic integration is the CLI or a WASM plugin — so it would still be a subprocess driven over a CLI, exactly like tmux.
  2. Addressability is what we depend on. The grid needs "focus cell 0x1, sub-screen 3, zoomed" as one deterministic call. tmux gives that directly (select-pane -t %12, resize-pane -Z). zellij's CLI is direction-oriented (move-focus left), which would mean counting relative moves and tracking state we cannot verify.
  3. Footprint runs the wrong way. zellij is a client/server async multiplexer with a wasmtime plugin runtime, heavier at idle than tmux's C implementation. On 2 GB boxes that is the binding constraint, and apt install tmux versus a zellij source build on an Atom is a much worse story for the install guide.

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 driving a CLI. Revisit only if tmux becomes the bottleneck.