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>
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
# 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.
|
||||
|
||||
## The carousel is tmux pane zoom, not extra processes
|
||||
|
||||
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](https://git.sr.ht/~mil/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:
|
||||
|
||||
```sh
|
||||
<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.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Hardware notes
|
||||
|
||||
Things about specific hardware that cost time to work out. Kept out of the README
|
||||
because they are not install steps.
|
||||
|
||||
## Screen size and rack size are different measurements
|
||||
|
||||
Three numbers get confused because they all describe "how big":
|
||||
|
||||
- **9 inch** — the GeeekPi touchscreen itself, 1280x720.
|
||||
- **10 inch** — the *mini-rack* standard its bracket is made for.
|
||||
- **19 inch** — the standard equipment rack, which is what `cad/` adapts it to.
|
||||
|
||||
So "a 9-inch screen on a 10-inch mount, adapted to a 19-inch rack" is three
|
||||
correct numbers, not a contradiction. The README uses the screen size when
|
||||
talking about the panel and the rack size when talking about the adapter.
|
||||
|
||||
## The ILITEK panel reports 2-3 contacts for one finger
|
||||
|
||||
A physically one-finger swipe arrives as two, sometimes three, simultaneous
|
||||
contacts. In v1 this was the single most expensive failure: lisgd detected the
|
||||
direction correctly every time and then rejected it on
|
||||
`Cfg(f=1) <=> Evt(f=2)`, which reads as noise unless you know what it means.
|
||||
|
||||
`touch.fingers: [1, 2, 3]` accepts all three. `socktop-swipe doctor` now reports
|
||||
it in English, and the travel is *averaged* across contacts rather than summed —
|
||||
otherwise a ghost-contact panel looks like it swiped three times as far.
|
||||
|
||||
## Phantom display outputs (LattePanda DSI-1)
|
||||
|
||||
The LattePanda's onboard DSI header shows up as `DSI-1 connected 1024x600` with
|
||||
**zero EDID bytes** (`xrandr` reports `0mm x 0mm`), and X happily puts workspace 1
|
||||
on it. Everything then looks healthy over ssh — the window is fullscreen on the
|
||||
primary output — while the real panel shows an empty workspace.
|
||||
|
||||
```sh
|
||||
xrandr | grep ' connected' # a real panel has physical mm; phantoms say 0mm x 0mm
|
||||
for c in /sys/class/drm/card0-*; do echo "$c $(cat "$c"/status) edid=$(wc -c <"$c"/edid)"; done
|
||||
```
|
||||
|
||||
Fix, adjusting the names:
|
||||
|
||||
```sh
|
||||
sudo tee /etc/X11/xorg.conf.d/20-outputs-socktop-swipe.conf >/dev/null <<'EOF'
|
||||
Section "Monitor"
|
||||
Identifier "DSI-1"
|
||||
Option "Ignore" "true"
|
||||
EndSection
|
||||
Section "Monitor"
|
||||
Identifier "HDMI-2"
|
||||
Option "Primary" "true"
|
||||
EndSection
|
||||
EOF
|
||||
```
|
||||
|
||||
Then set `touch.width`/`touch.height` to the real panel's mode. On the LattePanda
|
||||
that is **1280x720**, not the 1024x600 the phantom claims.
|
||||
|
||||
## `sudo xset` silently does nothing
|
||||
|
||||
`xset` talks to the X connection of the user running it, so `sudo xset s off`
|
||||
targets *root's* X connection and succeeds while changing nothing for your
|
||||
session. A `sudo bash noblank.sh` wrapper looks like it worked and blanking stays
|
||||
armed. It must run as the session user.
|
||||
|
||||
Also: `xset` is per-session and dies on reboot. The durable fix is the Xorg
|
||||
`ServerFlags` snippet with all four timeouts at 0 — which also covers the display
|
||||
manager's greeter — *plus* an `xset` line in the session autostart, because a
|
||||
session can re-enable the screensaver after X starts. Both, not either.
|
||||
|
||||
Check the real state:
|
||||
|
||||
```sh
|
||||
xset q | grep -A1 -E 'Screen Saver|DPMS' # want timeout 0, prefer blanking no, DPMS Disabled
|
||||
```
|
||||
|
||||
## Panel resolution is not the X screen size
|
||||
|
||||
With a second display attached, X reports the combined root window (e.g.
|
||||
2304x720). `touch.width`/`touch.height` must describe the touch panel alone or
|
||||
the edge and distance maths is scaled to the wrong thing. v1 hit this because
|
||||
lisgd asks X when not told; v2 always requires the values in the config.
|
||||
|
||||
## LattePanda specifics
|
||||
|
||||
Atom x5-Z8350 @ 1.44 GHz, 1.9 GB RAM, Cherry Trail Gen8 graphics, Debian 11,
|
||||
i3 on X11. Binaries in `~/.cargo/bin`, which is **not** on the PATH that i3 or a
|
||||
non-login ssh gives you — hence the `binaries:` block in the config.
|
||||
|
||||
Memory is the binding constraint. Prefer the lightweight option and count
|
||||
processes; reach for tmux pane zoom rather than duplicate program instances. A
|
||||
Wayland compositor with native touch gestures is not an answer here: Hyprland is
|
||||
not packaged in Debian, so it means a source build on a 1.44 GHz Atom, and it
|
||||
would not help anyway — libinput emits no gesture events for touchscreens on any
|
||||
version, so a raw-touch reader is required regardless.
|
||||
|
||||
## Wyse 3040
|
||||
|
||||
Same silicon as the LattePanda (Atom x5-Z8350, Cherry Trail), so the stack is
|
||||
already validated. Differences: 8/16 GB soldered eMMC and no M.2, so a minimal
|
||||
Debian netinst and a careful eye on disk; DisplayPort out, so a DP-to-HDMI
|
||||
adapter for the panel; roughly 3-4 W idle, 101x101x28 mm, VESA holes.
|
||||
|
||||
The eMMC is why the installer checks free space before starting a build: a Rust
|
||||
toolchain plus a target directory is around 1.8 GB.
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
# socktop-swipe v2 — release plan
|
||||
|
||||
Status: **draft for review**, 2026-09-09. Nothing implemented yet.
|
||||
Status: **implemented**, 2026-09-09. Milestones 1-7 are done on branch `v2-rust`;
|
||||
milestone 8 (the Wyse 3040 validation) is the remaining acceptance gate. Where the
|
||||
built thing differs from this plan, `notes/DESIGN.md` is authoritative.
|
||||
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# TODO
|
||||
|
||||
## Before calling v2.0 done
|
||||
|
||||
- [ ] **Wyse 3040 validation.** Debian minimal to working rack display,
|
||||
following only the README. Every stumble is a README fix, then re-run.
|
||||
This is the acceptance gate, not a nice-to-have.
|
||||
- [ ] Switch the LattePanda over from v1. Its v2 config path and session name
|
||||
differ, so both can be installed side by side while testing.
|
||||
- [ ] Add the tested-hardware table to the README once the Wyse is done.
|
||||
- [ ] Decide on the position indicator (`indicator: true`). Implemented; keep or
|
||||
cut based on whether it actually helps on the wall.
|
||||
|
||||
## Open questions from the plan
|
||||
|
||||
- **Snap tie-break.** From `0x2`, with cells at `-1x1` and `-1x3` both at
|
||||
distance 1, the lower column wins. Only fires on the first entry into a row.
|
||||
- **Indicator.** Off by default. See above.
|
||||
|
||||
## Later
|
||||
|
||||
- Prebuilt binaries. Deliberately not in v2.0: source-only until the Wyse install
|
||||
shows how bad a build on an Atom really is. `[package.metadata.deb]` is already
|
||||
in `Cargo.toml`, so `cargo deb` is the cheap next step if it turns out to hurt.
|
||||
- `unifly` parameters. `site` and `controller` are accepted by the config and
|
||||
passed as `--site` / `--controller`, but the fork does not implement them yet.
|
||||
Verify against the flags it actually grows.
|
||||
- A second monitor type that needs a multi-pane sub-sequence would prove the
|
||||
`Vec<Pane>` model generalises. Nothing needs it yet.
|
||||
- SIGTERM handling to remove the control socket on exit. Not urgent: a stale
|
||||
socket is detected and replaced on the next start, which is tested behaviour.
|
||||
@@ -0,0 +1,48 @@
|
||||
# What v1 was
|
||||
|
||||
v1 was three POSIX shell scripts plus a `.env` file, installed to
|
||||
`/usr/local/bin`. Tagged **`v1.2`** — `git show v1.2` has the whole thing, and it
|
||||
still works.
|
||||
|
||||
- `socktop-rack` built a tmux session: one window per group, one pane per host.
|
||||
- `socktop-swipe next|prev|down|up` walked a linear carousel, re-deriving its
|
||||
position from tmux on every single invocation.
|
||||
- `socktop-gestures` wrapped [lisgd](https://git.sr.ht/~mil/lisgd) with the right
|
||||
arguments and enforced a single instance.
|
||||
- `config.env` at `/usr/local/etc/socktop-swipe.env` held `SOCKTOP_GROUPS`,
|
||||
`SOCKTOP_AUX_CMD`, `TOUCH_DEV` and the gesture tuning.
|
||||
|
||||
It worked well for a year on the rack display. What made it worth replacing:
|
||||
|
||||
- **The layout was hard-coded as one line.** Groups ran left to right and that
|
||||
was the only shape available. The swipe-down "aux screen" existed because there
|
||||
was nowhere else to put a second kind of monitor — a special case bolted on,
|
||||
not a position in a layout.
|
||||
- **Every new monitor type was another special case.** `SOCKTOP_AUX_CMD` is a
|
||||
single command with no type, no parameters and no room for a second one.
|
||||
- **The navigation logic could not be tested.** It lived in a shell script that
|
||||
queried a live tmux server, so the only way to check a change was to install it
|
||||
on the display and swipe.
|
||||
|
||||
## Migration
|
||||
|
||||
There is no converter — deliberately, for a project with one known deployment.
|
||||
The mapping is direct:
|
||||
|
||||
| v1 | v2 |
|
||||
| --- | --- |
|
||||
| `SOCKTOP_GROUPS="a b ; c d @even-vertical"` | one `type: socktop` screen per group, at `"0x0"`, `"0x1"`, …, with `socktop_group` and `layout` |
|
||||
| `SOCKTOP_AUX_CMD="… uptime-kuma-status URL"` | a `type: uptime-kuma-status` screen at `"1x0"` |
|
||||
| `SOCKTOP_BIN` | `binaries: socktop:` |
|
||||
| `TOUCH_DEV` | `touch.device` |
|
||||
| `SCREEN_W` / `SCREEN_H` | `touch.width` / `touch.height` |
|
||||
| `SWIPE_THRESHOLD` / `SWIPE_LENIENCY` | `touch.threshold` / `touch.leniency` |
|
||||
| `FINGER_COUNTS="1 2 3"` | `touch.fingers: [1, 2, 3]` |
|
||||
| `GESTURE_IN` / `GESTURE_OUT` | `gestures.forward` / `gestures.back` |
|
||||
| `GESTURE_AUX_IN` / `GESTURE_AUX_OUT` | `gestures.down` / `gestures.up` |
|
||||
| the X `Option "Ignore"` rule | `touch.grab: true` |
|
||||
| `remain-on-exit` | the pane keep-alive wrapper (see DESIGN.md) |
|
||||
|
||||
Run `./uninstall.sh` from a v1 checkout first, or remove
|
||||
`/usr/local/bin/socktop-{rack,swipe,gestures}` and
|
||||
`/usr/local/etc/socktop-swipe.env` by hand.
|
||||
Reference in New Issue
Block a user