From ce6acec299b9f3366afa6ef2a15e34dafc87f547 Mon Sep 17 00:00:00 2001 From: jasonwitty Date: Wed, 9 Sep 2026 13:08:42 -0700 Subject: [PATCH] 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 --- README.md | 517 ++++++++++++++------------ cad/README.md | 47 ++- config.env | 91 ----- install.sh | 597 +++++++++++++++++++++---------- notes/DESIGN.md | 127 +++++++ notes/HARDWARE-NOTES.md | 105 ++++++ notes/PLAN-v2.md | 4 +- notes/TODO.md | 31 ++ notes/V1-BASH.md | 48 +++ packaging/70-socktop-swipe.rules | 13 + packaging/socktop-swipe.service | 29 ++ socktop-gestures | 99 ----- socktop-rack | 91 ----- socktop-swipe | 121 ------- socktop-swipe.service | 17 - src/config/coord.rs | 39 +- src/config/mod.rs | 4 +- src/control.rs | 8 +- src/doctor.rs | 6 +- src/grid.rs | 61 +++- src/input.rs | 62 +++- src/main.rs | 26 +- src/monitor.rs | 20 +- src/session/tmux.rs | 79 +++- tests/tmux_session.rs | 28 +- tools/diag.sh | 56 --- tools/find-device.sh | 36 -- tools/test-foreground.sh | 13 - uninstall.sh | 102 +++--- 29 files changed, 1388 insertions(+), 1089 deletions(-) delete mode 100644 config.env create mode 100644 notes/DESIGN.md create mode 100644 notes/HARDWARE-NOTES.md create mode 100644 notes/TODO.md create mode 100644 notes/V1-BASH.md create mode 100644 packaging/70-socktop-swipe.rules create mode 100644 packaging/socktop-swipe.service delete mode 100755 socktop-gestures delete mode 100755 socktop-rack delete mode 100755 socktop-swipe delete mode 100644 socktop-swipe.service delete mode 100755 tools/diag.sh delete mode 100755 tools/find-device.sh delete mode 100755 tools/test-foreground.sh diff --git a/README.md b/README.md index 94afe95..d1ea62e 100644 --- a/README.md +++ b/README.md @@ -1,298 +1,345 @@ # socktop-swipe -Swipe-to-zoom touchscreen navigation for a wall-mounted -[socktop](https://crates.io/crates/socktop) dashboard. +A rack-mounted touchscreen you swipe through to see what your machines are doing. -Hosts are arranged in **groups**. Each group is one tmux window showing its -hosts tiled together. Swiping right-to-left on the panel zooms into each host -full-screen, one at a time, then moves on to the next group's overview; swiping -left-to-right walks back the same way. +Terminal dashboards are arranged in a grid. Swipe left and right to move along a +row; swipe up and down to move between rows. A group of hosts is one place in +that grid: it shows them tiled together first, then each one full-screen as you +keep swiping. -``` - group A -> A1 -> A2 -> A3 -> A4 -> group B -> B1 -> B2 - (quarters) zoomed zoomed zoomed zoomed (stacked) zoomed zoomed - <------------------------- swipe left-to-right ------------------------- -``` - -With a single group this is simply overview -> each host -> back. - -Optionally, swiping **down** from any screen shows a different TUI (an -[Uptime Kuma](https://github.com/louislam/uptime-kuma) status page on the rack -display), and swiping **up** returns to exactly the screen you left. - -Built for a 10" touch panel on a server rack driving four Raspberry Pis, on a -low-power x86 box running i3 on X11. - -## What it looks like - -The panel in place, showing the four-Pi group overview: - -![The touch panel mounted in the rack, showing all four Raspberry Pis tiled](media/rack-display.jpg) - -Swiping through the carousel ([media/swipe-demo.mp4](media/swipe-demo.mp4)): +![The touch panel mounted in the rack, showing four Raspberry Pis tiled together](media/rack-display.jpg) -## How it works, and why +Built for a 9" panel bolted into a 19" server rack, driven by a low-power x86 box +running i3 on X11. -**The carousel is tmux pane zoom, not extra socktop instances.** There is exactly -one socktop process per host. Zooming calls `tmux resize-pane -Z`, which makes one -pane fill the window and sends `SIGWINCH` so socktop repaints at the new size. +## What you're looking at -That matters for three reasons: +The photo is a 9" touchscreen in a 19" rack, showing four Raspberry Pis at once +via [socktop](https://github.com/jasonwitty/socktop). It is a wall display: no +keyboard, no mouse, nothing to log into. It comes back by itself after a power +cut and it never blanks. -- **Half the processes.** Separate full-screen instances would mean N extra - socktop 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 to a host shows current data, not a stale snapshot. -- **Constant load.** The monitored hosts see the same connections regardless of - what is on screen. +Everything on it is a terminal program. socktop-swipe does not draw anything +itself — it decides what is on screen and reads your finger. -**Gestures come from lisgd, below the terminal.** No Linux terminal maps -horizontal swipes to commands, so this cannot be done in terminal config. -Critically, **libinput deliberately emits gesture events only for touchpads, -never for touchscreens** — so `libinput-gestures` and similar tools cannot work -here at all. [lisgd](https://git.sr.ht/~mil/lisgd) reads raw touch events and -synthesises the swipes itself, reading `/dev/input` directly and bypassing X. +``` + unifly -1x0 + (UniFi network) + ↕ + ┌──────────────────────────────┐ ┌──────────────────┐ + │ 4 Raspberry Pis │→ │ 2 more machines │ row 0 + │ tiled, then each full-screen│ │ │ + └──────────────────────────────┘ └──────────────────┘ + 0x0 0x1 + ↕ + uptime kuma 1x0 + (service status) +``` -## Requirements +Swipe forward through row 0 and you get: all four Pis tiled, then `rpi-master` +full-screen, then each of the others, then on to the next group. Swipe up at any +point for the UniFi network view; swipe back down and you are on the exact host +you left. -- `tmux`, `socktop`, and a working `~/.config/socktop/profiles.json` -- `lisgd` (the installer builds it) -- A C toolchain plus `libinput` and `libX11` headers, to build lisgd -- Membership of the `input` group (the installer adds you; needs a relogin) +## Hardware + +| Part | What was used | Notes | +| --- | --- | --- | +| Display host | LattePanda (Atom x5-Z8350, 1.9 GB) | Debian 11, i3 on X11 | +| Display host | Dell Wyse 3040 (Atom x5-Z8350, 2 GB) | ~$35 used; 8/16 GB eMMC, DP out | +| Panel | GeeekPi 9" 1280x720 3U touchscreen | ILITEK USB touch | +| Mounting | [`cad/`](cad) — 19" rack adapter | 3D-print or laser-cut aluminium | + +Any Linux box with a USB or DSI touchscreen will do. It is deliberately modest +hardware: the whole point is a display that idles at a few watts. + +The panel is sold as a **10-inch mini-rack** part. [`cad/`](cad) has a pair of +side adapters — OpenSCAD source, STLs with tested print settings, and +SendCutSend-ready DXFs — that mount it in a standard **19-inch** rack, with three +keystone jack positions on each side. The geometry has been physically tested. ## Install +```sh +curl -fsSL https://gt.wittyoneoff.com/jason/socktop-swipe/raw/branch/main/install.sh | sh +``` + +It checks everything first and prints the whole cost — packages, toolchain, disk +— before changing anything. Then it asks, one thing at a time: + +- which touch device to use (it looks for one and offers it) +- how to grant access to it: a udev rule for touchscreens only, or the `input` + group +- whether to install socktop, uptime-kuma-status or unifly +- whether to stop the screen blanking, sleeping and locking +- whether to autostart on login +- whether to log straight in at boot, so it comes back after a power cut + +Every answer has a sensible default and nothing is required. Piping into `sh` +leaves no keyboard on standard input, so the script reads your terminal directly; +if there is no terminal at all it takes the defaults and says so. `--yes` skips +the questions. + +There are no prebuilt binaries yet — it builds from source, so a Rust toolchain +is installed if you do not have one. Budget about 1.8 GB and, on an Atom, twenty +minutes or so. + +
+From a checkout instead + ```sh git clone https://gt.wittyoneoff.com/jason/socktop-swipe cd socktop-swipe -./install.sh --xignore --i3 +./install.sh ``` +
-Then **log out and back in** — the `input` group and the X rule both need a -fresh session. +## Your first config -| Flag | Effect | -| --- | --- | -| *(none)* | Build/install lisgd, install the three scripts and the default config | -| `--xignore` | Also tell X to ignore the touch panel (see below — usually wanted) | -| `--i3` | Also add i3 autostart lines (gesture daemon, fullscreen terminal, idle-pointer hiding via `unclutter-xfixes`), with a backup and `i3 -C` validation | -| `--noblank` | Also stop the screen blanking / DPMS power-down (wall displays) | -| `--autologin` | Also make lightdm log this user straight into i3 at boot (see [security note](#related-does-the-display-come-back-by-itself)) | +The config lives at `~/.config/socktop-swipe/config.yaml`. The installer writes a +starting point; this section builds one up from nothing. -Re-running is safe: an existing config file is never overwritten, and the i3 -edit is skipped if already present. - -### Verify +**Check every edit** with: ```sh -tools/find-device.sh # confirm TOUCH_DEV is right -tools/diag.sh # stage 1: device live? stage 2: what does lisgd see? -tools/test-foreground.sh # run the real daemon, watch the display, swipe +socktop-swipe validate ``` -## Configuration +which resolves the grid, prints the map, and tells you if a program it would run +is not actually there. -Everything lives in **`/usr/local/etc/socktop-swipe.env`**. Edit it there, not in -the repo copy. After changing it, restart the daemon and rebuild the session: +### One group of hosts -```sh -socktop-gestures --replace & # restarts the daemon, running or not -socktop-rack +```yaml +touch: + device: /dev/input/by-id/usb-ILITEK_ILITEK-TOUCH-event-if00 + width: 1280 + height: 720 + +screens: + - at: "0x0" + type: socktop + socktop_group: [rpi-master, rpi-worker-1, rpi-worker-2, rpi-worker-3] ``` -Use `--replace` rather than `pkill -x lisgd && socktop-gestures`: `pkill` exits -non-zero when it matches nothing, so that one-liner silently starts **nothing** -if no daemon was running — you would swipe, get no response, and reasonably -blame the config change. +That is five screens: the four hosts tiled, then each one full-screen. -| Option | Default | What it does | +``` +0x0 tiled → rpi-master → rpi-worker-1 → rpi-worker-2 → rpi-worker-3 +``` + +> **Quote the coordinate.** Unquoted, YAML reads `0x0` as the hexadecimal number +> zero. `socktop-swipe validate` will tell you so if you forget. + +### Something above it + +```yaml + - at: "-1x0" + type: unifly +``` + +Row `-1` is above row `0`. Now swiping up from any of those five screens shows +the UniFi TUI, and swiping down returns you to the exact host you were on. + +### Something below, and more hosts alongside + +```yaml + - at: "0x1" + type: socktop + socktop_group: "orangepi, trixie" + layout: even-vertical + + - at: "1x0" + type: uptime-kuma-status + url: https://status.example.com/status/mine +``` + +`0x1` sits to the right of `0x0`, so you reach it by swiping forward past the +last Pi. `1x0` is below. + +Full worked example with every option commented: +[`config.example.yaml`](config.example.yaml). + +## How the grid works + +**Row increases downward.** `-1x0` is above `0x0`; `1x0` is below it. Column +increases rightward. You start at `0x0`. + +**Coordinates are ordering, not slots.** Only their sort order matters, so `0x1` +and `0x5` are interchangeable. You never have to count how many screens a group +produces in order to place something next to it — add a host to a group and +nothing else needs renumbering. + +**A group of hosts is one place in the grid.** Swiping forward walks its screens +— tiled overview, then each host — and only moves on to the next place after the +last one. Coming back from the right lands you on that group's *last* host, not +its overview, so the row reads as one continuous strip. + +**Swiping up or down returns you to where you were.** Each row remembers its +position. If you glance at the network view and come back, you are on the same +host, still zoomed. + +**Nothing wraps around.** Swiping past either end of a row does nothing. On a +wall display, wrapping makes it impossible to tell where you are. + +If a row has no screen in your current column, you land on the nearest one — +`0x3` swiping up with only `-1x0` above goes there. That only happens the first +time; after that the row remembers. + +## Monitor types + +Every type also accepts `command:` to replace the generated command outright, and +`title:` to change the label on the pane border. + +| `type:` | Parameters | What it runs | | --- | --- | --- | -| `SOCKTOP_GROUPS` | Pis quartered `;` Orange Pi + Debian stacked | Groups separated by `;`, each a list of socktop profile names **in swipe order**, plus an optional `@layout` (`tiled` default, `even-vertical` stacks, `even-horizontal` side-by-side, `main-*` also work). Any count of groups and hosts works. | -| `SOCKTOP_HOSTS` | empty | Legacy single-group form; used only when `SOCKTOP_GROUPS` is empty | -| `SOCKTOP_AUX_CMD` | `uptime-kuma-status …` | Command for the swipe-down "aux" screen. Full path. Empty disables the feature and its gestures. | -| `GESTURE_AUX_IN` / `GESTURE_AUX_OUT` | `UD` / `DU` | Swipe down shows aux, swipe up returns | -| `SOCKTOP_SESSION` | `socktop4` | tmux session name | -| `SOCKTOP_BIN` | `$HOME/.cargo/bin/socktop` | Full path — i3 does not have `~/.cargo/bin` on `PATH` | -| `TOUCH_DEV` | ILITEK by-id path | Touch device. **Always use a `/dev/input/by-id/` path**; `eventN` numbers change on reboot. | -| `SCREEN_W` / `SCREEN_H` | `1024` / `600` | The **panel's** resolution, not the X screen. See gotcha 2. | -| `SWIPE_THRESHOLD` | `80` | Pixels of travel before a drag counts as a swipe | -| `SWIPE_LENIENCY` | `30` | Degrees off-axis tolerated (max 45) | -| `FINGER_COUNTS` | `1 2 3` | Contact counts accepted. **The setting most likely to need changing** — see gotcha 1. | -| `GESTURE_IN` / `GESTURE_OUT` | `RL` / `LR` | Swap these to reverse swipe direction | +| `socktop` | `socktop_group` (list, or comma-separated string), `layout` | one [socktop](https://github.com/jasonwitty/socktop) per host | +| `uptime-kuma-status` | `url` | [uptime-kuma-status](https://github.com/jasonwitty/uptime-kuma-status) against a public [Uptime Kuma](https://github.com/louislam/uptime-kuma) status page | +| `unifly` | *(none yet)* | [unifly](https://github.com/jasonwitty/unifly) `tui` | +| `generic` | `command`, `title` | anything you like | -### Adding or removing hosts +`layout` takes any tmux layout name: `tiled` (the default), `even-horizontal`, +`even-vertical`, `main-horizontal`, `main-vertical`. -Edit `SOCKTOP_GROUPS` and run `socktop-rack`. Nothing else needs touching — the -carousel derives its length from the windows and panes at runtime. Every host -named must exist in `~/.config/socktop/profiles.json` on the display host. - -```sh -# four Pis as quarters, then two more boxes stacked one above the other -SOCKTOP_GROUPS="rpi-master rpi-worker-1 rpi-worker-2 rpi-worker-3 ; orangepi trixie @even-vertical" +```yaml + - at: "1x1" + type: generic + title: k3s + command: journalctl -f -u k3s-agent ``` -## Autostart +## Configuration reference -`./install.sh --i3` appends to `~/.config/i3/config`. For systemd user sessions +| Key | Default | What it does | +| --- | --- | --- | +| `session` | `socktop-swipe` | tmux session name | +| `terminal` | *(none)* | Terminal that `run` opens the dashboard in. Unset attaches in the current one | +| `indicator` | `false` | Show position — what is above, where you are, what is below — in the status line | +| `binaries.socktop` etc. | looked up on `PATH` | Full path to each program. `~/` is expanded | +| `touch.device` | — | **Always a `/dev/input/by-id/` path**; `eventN` numbers change on reboot | +| `touch.width` / `.height` | — | The **panel's** resolution, not the X screen | +| `touch.grab` | `true` | Take the panel exclusively so X never sees the touches | +| `touch.threshold` | `80` | Pixels of travel before a drag counts as a swipe | +| `touch.leniency` | `30` | Degrees off-axis tolerated, max 45 | +| `touch.fingers` | `[1, 2, 3]` | Contact counts accepted. **The setting most likely to need changing** | +| `gestures.forward` / `.back` | `RL` / `LR` | Named by finger motion. Swap to reverse | +| `gestures.up` / `.down` | `DU` / `UD` | | + +### Commands + +| Command | | +| --- | --- | +| `socktop-swipe run` | Build the session, open it in a terminal, read the panel. The one thing to autostart | +| `socktop-swipe validate` | Check the config and print the grid map | +| `socktop-swipe doctor` | Swipe, and be told what the panel reported | +| `socktop-swipe doctor --list` | List touch devices — works before any config exists | +| `socktop-swipe forward` / `back` / `up` / `down` | Move a running instance. For keybindings, or a box with no panel | +| `socktop-swipe build` / `attach` / `daemon` | The pieces of `run`, separately, for a systemd split | + +## Unattended operation + +A wall display has to survive a power cut with nobody in the room. + +**Never blank.** The installer writes an Xorg `ServerFlags` snippet with all four +timeouts at zero, which covers every session including the display manager's +greeter and survives reboots, *and* adds an `xset` line to the session autostart, +because a session can re-enable blanking after X starts. Both are needed. Check +with `xset q | grep -A1 -E 'Screen Saver|DPMS'` — you want `timeout: 0`, +`prefer blanking: no`, `DPMS is Disabled`. + +**Come back by itself.** Without autologin, a reboot leaves the panel at a login +prompt and nothing starts. The installer offers a lightdm drop-in. It is opt-in +and defaults to no, because **anyone with physical access to the panel gets that +user's session** — only do it where the account is nothing but the dashboard. +Delete `/etc/lightdm/lightdm.conf.d/50-autologin-socktop-swipe.conf` to undo it. + +**Autostart.** `socktop-swipe run` is a single process that builds the session, +opens the terminal and reads the panel, so one line in your window manager's +autostart is enough. For a systemd user session, +[`packaging/socktop-swipe.service`](packaging/socktop-swipe.service) splits it up. + +## Troubleshooting + +Start here: + +```sh +socktop-swipe doctor +``` + +It watches the panel and says what it saw in English — direction, contact count, +and why a gesture was ignored. + +### Swipes are detected but nothing happens + +`doctor` will say `saw 2 contact(s), config accepts [1]`. Many multipoint panels +report two or even three contacts for a physically one-finger swipe. Add them to +`touch.fingers`. This is the single most common problem on new hardware. + +### Nothing is detected at all + +In order: is the device path right (`socktop-swipe doctor --list`)? Can you read +it — is the udev rule installed, or are you in the `input` group and did you log +out and back in? Is the panel plugged in? + +### Swipes work, but the terminal also reacts — text zooming, panes resizing + +X is delivering touch to whatever is on screen as well. `touch.grab: true` (the +default) prevents this by taking the device exclusively. If you have set it to +`false` because you want touch in other applications, tell X to ignore the panel instead: ```sh -cp socktop-swipe.service ~/.config/systemd/user/ -systemctl --user enable --now socktop-swipe -``` - -Either way, **run exactly one gesture daemon**. lisgd does not grab the input -device exclusively, so two instances make every swipe fire twice. -`socktop-gestures` enforces this: it exits non-zero if one is already running, -unless given `--replace`. - -## Stopping the screen blanking - -A wall display must never blank. `./install.sh --noblank` applies two layers: - -1. An Xorg `ServerFlags` snippet with all four timeouts at `0`, so it covers - every X session including the display-manager greeter, and survives reboots. -2. An `xset s off s noblank -dpms` line in the i3 autostart, because a session - or DM can re-enable the screensaver after X has started. - -Both are needed. `xset` alone does not survive a reboot, and — the trap worth -knowing — **`xset` must run as the session user**. Running `sudo xset ...`, or a -`sudo bash noblank.sh` wrapper, targets root's X connection and silently does -nothing while appearing to succeed. Check the real state with: - -```sh -xset q | grep -A1 -E 'Screen Saver|DPMS' -``` - -You want `timeout: 0`, `prefer blanking: no`, and `DPMS is Disabled`. - -### Related: phantom outputs - -Some boards report a display output that has no panel behind it. The LattePanda's -onboard DSI header shows up as `DSI-1 connected 1024x600` with **zero EDID bytes** -(`xrandr` prints `0mm x 0mm`), and X happily puts workspace 1 on it. Everything -then looks healthy from ssh -- i3 has the window fullscreen on its primary -output -- while the real panel shows an empty workspace. Check with: - -```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 -``` - -Tell X to ignore the phantom and mark the real panel primary (adjust 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" +sudo tee /etc/X11/xorg.conf.d/99-ignore-touch-socktop-swipe.conf >/dev/null <<'EOF' +Section "InputClass" + Identifier "ignore touchscreen (socktop-swipe)" + MatchProduct "ILITEK" + MatchIsTouchscreen "on" + Option "Ignore" "on" EndSection EOF ``` -Then set `SCREEN_W` / `SCREEN_H` in the config to the real panel's mode. +Adjust `MatchProduct` to your panel, and restart X. -### Related: does the display come back by itself? +### A pane shows `[... exited: status 1]` -Blanking is only half of unattended operation. If the display manager has no -autologin configured, a reboot leaves the panel at a login prompt and nothing -autostarts. Check with: +That monitor program stopped. The pane is deliberately kept so the layout does +not reshuffle and you can see what happened. Check the path under `binaries:`, +and that the program works when you run it by hand. -```sh -grep -E '^autologin-(user|session)' /etc/lightdm/lightdm.conf -``` +### The dashboard is on a screen that isn't there -No output means no autologin. `./install.sh --autologin` writes -`/etc/lightdm/lightdm.conf.d/50-autologin-socktop-swipe.conf` for the current -user into the `i3` session. It is opt-in, not a default: **anyone with physical -access to the panel gets that user's session**, so only do it where the display -user is nothing but the dashboard. Delete the file to get the login prompt back. +Some boards report an output with no panel behind it, and X will happily put the +dashboard on it — everything looks fine over ssh while the real panel shows an +empty desktop. See [`notes/HARDWARE-NOTES.md`](notes/HARDWARE-NOTES.md). ## Uninstall ```sh -./uninstall.sh # scripts, config, X rule +./uninstall.sh # binary, config, udev rule, X snippets, autologin ./uninstall.sh --keep-config # keep your settings ``` -lisgd, the `input` group and your socktop profiles are deliberately left alone; -the script prints how to remove each. +The Rust toolchain, tmux, the monitor programs and your `input` group membership +are left alone; the script prints how to remove each. -## Troubleshooting +## Notes -Four gotchas account for nearly every failure. All were found the hard way. +Design decisions and the reasoning behind them are in [`notes/`](notes): -### 1. Gestures are recognised but never fire +- [`DESIGN.md`](notes/DESIGN.md) — why tmux, why evdev, why the grid works this way +- [`HARDWARE-NOTES.md`](notes/HARDWARE-NOTES.md) — panel quirks, phantom outputs, the `sudo xset` trap +- [`V1-BASH.md`](notes/V1-BASH.md) — the shell implementation this replaced, and how to migrate +- [`TODO.md`](notes/TODO.md) -Run `tools/diag.sh` and read stage 2: +## Related -``` -[swipe]: Cfg(f=1/s=3/e=0/d=0) <=> Evt(f=2/s=3/e=1/d=2) - ^ configured ^ what happened -``` +- [socktop](https://github.com/jasonwitty/socktop) — the host monitor this was built around +- [uptime-kuma-status](https://github.com/jasonwitty/uptime-kuma-status) — Uptime Kuma status pages in a terminal +- [unifly](https://github.com/jasonwitty/unifly) — UniFi network TUI -Matching `s=` with differing `f=` means **your panel reports more contacts than -you configured**. Many multipoint panels report 2 or even 3 contacts for a -physically one-finger swipe. Add them to `FINGER_COUNTS`. +## License -Direction enum: `0=DU`, `1=UD`, `2=LR`, `3=RL`. - -### 2. Swipes register, but erratically — resizing panes, zooming text - -X is also delivering touch to whatever is on screen, so a single swipe hits -several consumers at once: tmux mouse mode drags pane borders, the terminal -reads 2-contact swipes as pinch-zoom, and socktop enables its own all-motion -mouse reporting (`?1003h`). Meanwhile lisgd fires too. - -Fix with `./install.sh --xignore` and relog. lisgd is unaffected because it -reads the device directly. Trade-off: no tap or pinch-zoom on that panel — -terminal keyboard zoom (`Ctrl +` / `Ctrl -`) still works. - -Also ensure tmux mouse mode is **off**; `socktop-rack` sets this. - -### 3. Every swipe jumps two panes - -Two lisgd instances are running. `pgrep -x lisgd` should show exactly one. - -`socktop-gestures` refuses to start if a daemon is already running, so this -should not happen via the normal path; `--replace` restarts cleanly. It can -still occur if lisgd was launched by hand. - -### 4. Swipes do nothing at all - -In order: is the session running (`tmux ls`)? Is the daemon running -(`pgrep -x lisgd`)? Does `tools/diag.sh` stage 1 read bytes? Are you in the -`input` group (`id -nG | grep input`, needs a relogin)? - -Note lisgd's verbose flag is **`-v`**, not `-D`. - -### Multi-monitor note - -If the display host has a second monitor, X reports the **combined** root window -(e.g. 2304x720). lisgd would scale its maths to that, so `SCREEN_W`/`SCREEN_H` -must describe the touch panel alone. `socktop-gestures` always passes them -explicitly. - -## Known limitations - -- **~300 ms repaint on zoom-in.** Between tmux making the pane full-screen and - socktop repainting, you briefly see the old small rendering anchored top-left. - This is socktop's redraw latency, not the gesture path; nothing outside - socktop can fix it. Zooming out has no visible artifact, since panes return to - sizes they were already drawn at. -- Deliberately **no wrap-around** at the ends of the carousel — on a wall display - wrapping makes it impossible to tell where you are. -- X11 only. The lisgd build disables its Wayland backend (`WITHOUT_WAYLAND=1`). - -## Tested on - -LattePanda (Atom x5-Z8350, 1.9 GB RAM, Cherry Trail graphics), 1024x600 ILITEK -DSI touch panel, Debian 11, i3 on X11, Alacritty 0.16.1, lisgd from git, -monitoring four Raspberry Pis. +Apache-2.0. diff --git a/cad/README.md b/cad/README.md index 84e1b89..8a4f0f6 100644 --- a/cad/README.md +++ b/cad/README.md @@ -2,9 +2,13 @@ A pair of side adapters for mounting the **GeeekPi 9-inch 1280x720 3U rack-mount touchscreen monitor** in a standard **19-inch equipment rack**. +Three sizes get mentioned here and they are three different things: the **screen** +is 9 inches, the mount it ships for is the **10-inch mini-rack** standard, and +these adapters convert that to a **19-inch** equipment rack. + The design also provides **three keystone-jack positions on each side of the display**. -![Installed adapter](docs/installed.jpg) +![Installed adapter](geeekpi_rack_adapter_release_v1/docs/installed.jpg) ## Release v1 @@ -18,22 +22,26 @@ The mounting geometry has been physically tested in a standard 19-inch rack. ## Files ```text -. -├── README.md -├── 3d-print/ -│ ├── geeekpi_19in_adapter_LEFT_release-v1.stl -│ ├── geeekpi_19in_adapter_RIGHT_release-v1.stl -│ ├── geeekpi_19in_adapter_LEFT_release-v1_AnycubicMegaPro.gcode -│ └── geeekpi_19in_adapter_RIGHT_release-v1_AnycubicMegaPro.gcode -├── sendcutsend/ -│ ├── geeekpi_19in_adapter_LEFT_release-v1.dxf -│ └── geeekpi_19in_adapter_RIGHT_release-v1.dxf -├── source/ -│ └── geeekpi_19in_adapter_release-v1.scad -└── docs/ - └── installed.jpg +cad/ +├── README.md this file +├── geeekpi_rack_adapter_release_v1/ <- use this one +│ ├── 3d-print/ +│ │ ├── geeekpi_19in_adapter_LEFT_release-v1.stl +│ │ ├── geeekpi_19in_adapter_RIGHT_release-v1.stl +│ │ ├── geeekpi_19in_adapter_LEFT_release-v1_AnycubicMegaPro.gcode +│ │ └── geeekpi_19in_adapter_RIGHT_release-v1_AnycubicMegaPro.gcode +│ ├── sendcutsend/ +│ │ ├── geeekpi_19in_adapter_LEFT_release-v1.dxf +│ │ └── geeekpi_19in_adapter_RIGHT_release-v1.dxf +│ ├── source/ +│ │ └── geeekpi_19in_adapter_release-v1.scad +│ └── docs/ +│ └── installed.jpg +└── geeekpi_v25_FINAL_candidate_package/ earlier candidate, superseded by v1 ``` +Paths below are relative to `geeekpi_rack_adapter_release_v1/`. + ## Key dimensions | Feature | Dimension | @@ -70,7 +78,8 @@ Good bed leveling matters a lot for these parts because they are broad and very ## SendCutSend / metal version -The `sendcutsend/` directory contains separate left and right DXF files. +The `geeekpi_rack_adapter_release_v1/sendcutsend/` directory contains separate +left and right DXF files. A good configuration for the metal adapters is: @@ -98,7 +107,8 @@ Depending on the rack, display, and door clearance, small spacers or standoffs b ## Editing the design -The editable OpenSCAD source is in `source/`. +The editable OpenSCAD source is in +`geeekpi_rack_adapter_release_v1/source/`. To generate a side, change: @@ -108,7 +118,8 @@ side = "left"; to either `"left"` or `"right"`, then render/export the STL. -The DXF files in `sendcutsend/` are the intended files for sheet-metal cutting. +The DXF files in `geeekpi_rack_adapter_release_v1/sendcutsend/` are the intended +files for sheet-metal cutting. ## Notes diff --git a/config.env b/config.env deleted file mode 100644 index 58afafb..0000000 --- a/config.env +++ /dev/null @@ -1,91 +0,0 @@ -# socktop-swipe configuration. -# -# Installed to /usr/local/etc/socktop-swipe.env. Every script reads that file if -# it exists, so edit it there on the display host -- not this copy in the repo. -# After editing, restart the gesture daemon and rebuild the session (see README). -# -# Values use ${VAR:-default} so an environment variable of the same name always -# wins over this file. That keeps the file editable in the obvious way while -# still allowing one-off overrides, e.g. SOCKTOP_SESSION=test socktop-rack - -# --------------------------------------------------------------------------- -# What to monitor -# --------------------------------------------------------------------------- - -# What to show, as GROUPS separated by ";". Each group is one screen of the -# carousel: a tiled overview of its hosts, then each host zoomed full-screen in -# turn, then on to the next group. Host names are socktop profile names from -# ~/.config/socktop/profiles.json. -# -# overview(A) -> A1 -> A2 -> ... -> overview(B) -> B1 -> B2 -> ... -# -# An optional "@layout" token in a group sets its tmux overview layout -# (tiled, even-horizontal, even-vertical, main-horizontal, main-vertical). -# Default is tiled: 4 hosts become quarters, 2 become side-by-side. -# "@even-vertical" stacks the panes one above the other instead. -# -# The example below is the rack display: the four Pis quartered and zoomable, -# then the Orange Pi and the Debian box stacked and zoomable. -SOCKTOP_GROUPS="${SOCKTOP_GROUPS:-rpi-master rpi-worker-1 rpi-worker-2 rpi-worker-3 ; orangepi trixie @even-vertical}" - -# Legacy single-group form. Used only when SOCKTOP_GROUPS is empty. -SOCKTOP_HOSTS="${SOCKTOP_HOSTS:-}" - -# Optional "aux" screen: a different TUI reached by swiping DOWN from any -# carousel screen; swiping UP returns to exactly the screen you left. It runs in -# its own tmux window, kept alive like the socktop panes, so switching is -# instant. Leave empty to disable. Use full paths -- i3 has no ~/.cargo/bin. -SOCKTOP_AUX_CMD="${SOCKTOP_AUX_CMD:-$HOME/.cargo/bin/uptime-kuma-status https://status.wittyoneoff.com/status/wittyoneoff}" - -# tmux session name. Change only if it collides with something else. -SOCKTOP_SESSION="${SOCKTOP_SESSION:-socktop4}" - -# Absolute path to the socktop binary. i3 and non-login shells do not have -# ~/.cargo/bin on PATH, so a full path is safer than relying on lookup. -SOCKTOP_BIN="${SOCKTOP_BIN:-$HOME/.cargo/bin/socktop}" - -# --------------------------------------------------------------------------- -# Touch panel -# --------------------------------------------------------------------------- - -# The touchscreen event device. ALWAYS prefer a /dev/input/by-id/ symlink -- -# /dev/input/eventN numbers get reshuffled on reboot or USB re-enumeration. -# Find yours with: tools/find-device.sh -TOUCH_DEV="${TOUCH_DEV:-/dev/input/by-id/usb-ILITEK_ILITEK-TOUCH-event-if00}" - -# The touch panel's own resolution -- NOT the X screen size. lisgd otherwise -# asks X, which reports the full root window across all monitors (e.g. 2304x720 -# with a second display attached) and skews its edge and distance maths. -SCREEN_W="${SCREEN_W:-1024}" -SCREEN_H="${SCREEN_H:-600}" - -# --------------------------------------------------------------------------- -# Gesture tuning -# --------------------------------------------------------------------------- - -# Pixels of travel before a drag counts as a swipe. Too low and stray contact -# triggers it; too high and normal swipes are ignored. Measured on the ILITEK -# panel: real swipes land at 150-260px, accidental ones under 70px. -SWIPE_THRESHOLD="${SWIPE_THRESHOLD:-80}" - -# Degrees of leniency off the axis, max 45. Real finger swipes came in 1-25 -# degrees off true horizontal, so 30 leaves comfortable margin. -SWIPE_LENIENCY="${SWIPE_LENIENCY:-30}" - -# Contact counts to accept for one logical swipe. -# -# THIS IS THE SETTING THAT MOST OFTEN NEEDS CHANGING ON NEW HARDWARE. Many -# multipoint panels report 2 or even 3 contacts for what is physically a -# one-finger swipe (palm, or ghost contacts). If gestures are recognised but -# never fire, run tools/diag.sh and compare Cfg(f=N) against Evt(f=N). -FINGER_COUNTS="${FINGER_COUNTS:-1 2 3}" - -# Swipe directions. RL = right-to-left = zoom in; LR = left-to-right = zoom out. -# Swap these two values to reverse the direction of travel. -GESTURE_IN="${GESTURE_IN:-RL}" -GESTURE_OUT="${GESTURE_OUT:-LR}" - -# Aux screen swipes. UD = up-to-down (swipe down) shows it; DU = down-to-up -# (swipe up) returns. Only bound when SOCKTOP_AUX_CMD is set. -GESTURE_AUX_IN="${GESTURE_AUX_IN:-UD}" -GESTURE_AUX_OUT="${GESTURE_AUX_OUT:-DU}" diff --git a/install.sh b/install.sh index 6a95197..f90f37f 100755 --- a/install.sh +++ b/install.sh @@ -1,238 +1,439 @@ #!/bin/sh # socktop-swipe installer. # -# ./install.sh scripts + config + lisgd (build if missing) -# ./install.sh --xignore ...and tell X to ignore the touch panel -# ./install.sh --i3 ...and add i3 autostart lines -# ./install.sh --noblank ...and stop the screen blanking -# ./install.sh --autologin ...and make lightdm log this user straight into i3 -# ./install.sh --xignore --i3 --noblank --autologin full wall-display setup +# curl -fsSL https://gt.wittyoneoff.com/jason/socktop-swipe/raw/branch/main/install.sh | sh # -# Safe to re-run: an existing config file is never overwritten, and the i3 edit -# is skipped if already present. +# or, from a checkout: +# +# ./install.sh +# +# Everything is checked BEFORE anything is installed, and the full cost is +# printed once. Nothing touches the system before you answer the summary. +# +# --yes accept every default without asking +# --uninstall hand over to uninstall.sh +# --prefix DIR install to DIR/bin instead of /usr/local/bin set -eu -PREFIX=${PREFIX:-/usr/local} -BIN="$PREFIX/bin" -ETC="$PREFIX/etc" -CONF="$ETC/socktop-swipe.env" -XCONF=/etc/X11/xorg.conf.d/99-ignore-touch-socktop-swipe.conf -BCONF=/etc/X11/xorg.conf.d/10-no-blanking-socktop-swipe.conf -LCONF=/etc/lightdm/lightdm.conf.d/50-autologin-socktop-swipe.conf -SRC="$HOME/src/lisgd" +REPO=https://gt.wittyoneoff.com/jason/socktop-swipe +PREFIX=/usr/local +ASSUME_YES=no +SRC="" -here=$(cd "$(dirname "$0")" && pwd) -do_xignore=no -do_i3=no -do_noblank=no -do_autologin=no for a in "$@"; do case "$a" in - --xignore) do_xignore=yes ;; - --i3) do_i3=yes ;; - --noblank) do_noblank=yes ;; - --autologin) do_autologin=yes ;; + --yes | -y) ASSUME_YES=yes ;; + --prefix=*) PREFIX=${a#--prefix=} ;; + --uninstall) exec sh -c "$(dirname "$0")/uninstall.sh" ;; + -h | --help) sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "unknown option: $a" >&2; exit 2 ;; esac done -# --- lisgd ------------------------------------------------------------------ -# libinput deliberately emits gesture events only for touchpads, never for -# touchscreens, so libinput-gestures and friends cannot work here. lisgd reads -# raw touch events and synthesises the swipes itself. -if command -v lisgd >/dev/null 2>&1; then - echo "==> lisgd already installed: $(command -v lisgd)" -else - echo "==> building lisgd" - if command -v apt-get >/dev/null 2>&1; then - sudo apt-get update - sudo apt-get install -y build-essential pkg-config git libinput-dev libx11-dev - else - echo "!! Non-Debian system: ensure a C toolchain, libinput and libX11 headers" - echo "!! are present, then re-run." +BIN="$PREFIX/bin" +CONF_DIR="$HOME/.config/socktop-swipe" +CONF="$CONF_DIR/config.yaml" +UDEV=/etc/udev/rules.d/70-socktop-swipe.rules +XBLANK=/etc/X11/xorg.conf.d/10-no-blanking-socktop-swipe.conf +LIGHTDM=/etc/lightdm/lightdm.conf.d/50-autologin-socktop-swipe.conf + +# -------------------------------------------------------------------------- +# Asking +# -------------------------------------------------------------------------- +# Piping into sh leaves stdin holding the SCRIPT, not a keyboard, so prompts +# must read the terminal directly. With no terminal at all we take defaults and +# say so rather than silently guessing. +if [ -r /dev/tty ] && [ -t 1 ]; then HAVE_TTY=yes; else HAVE_TTY=no; fi + +say() { printf '%s\n' "$*"; } +step() { printf '\n==> %s\n' "$*"; } +warn() { printf ' !! %s\n' "$*" >&2; } + +# ask "question" default(y|n) -> 0 for yes +ask() { + _q=$1 + _d=$2 + if [ "$ASSUME_YES" = yes ] || [ "$HAVE_TTY" = no ]; then + [ "$_d" = y ] && return 0 || return 1 fi + [ "$_d" = y ] && _hint="[Y/n]" || _hint="[y/N]" + while :; do + printf ' %s %s ' "$_q" "$_hint" >/dev/tty + read -r _a echoes the answer +askval() { + if [ "$ASSUME_YES" = yes ] || [ "$HAVE_TTY" = no ]; then + printf '%s' "$2" + return + fi + printf ' %s [%s] ' "$1" "$2" >/dev/tty + read -r _a /dev/null 2>&1; } + +# -------------------------------------------------------------------------- +# Preflight +# -------------------------------------------------------------------------- +say "socktop-swipe installer" +say "=======================" + +OS_NAME=$(. /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-unknown}" || true) +[ -n "$OS_NAME" ] || OS_NAME=unknown +ARCH=$(uname -m) + +PKG="" +PKG_INSTALL="" +for p in apt-get dnf pacman zypper apk; do + if have $p; then + PKG=$p + break + fi +done +case "$PKG" in +apt-get) PKG_INSTALL="sudo apt-get install -y" ;; +dnf) PKG_INSTALL="sudo dnf install -y" ;; +pacman) PKG_INSTALL="sudo pacman -S --needed --noconfirm" ;; +zypper) PKG_INSTALL="sudo zypper install -y" ;; +apk) PKG_INSTALL="sudo apk add" ;; +esac + +# Display manager and window manager, for the autostart and autologin steps. +# NB: written as `if`, not `[ x ] && y`. Under `set -e` an AND-OR list that +# ends up false aborts the script, so a box with no lightdm would exit here. +DM=none +if [ -d /etc/lightdm ]; then DM=lightdm +elif [ -d /etc/gdm3 ] || [ -d /etc/gdm ]; then DM=gdm +elif [ -f /etc/sddm.conf ] || [ -d /etc/sddm.conf.d ]; then DM=sddm +fi + +WM=none +if have i3; then WM=i3; fi + +TERMINAL="" +for t in alacritty foot kitty xterm; do + if have $t; then + TERMINAL=$t + break + fi +done + +LOCKER="" +for l in xss-lock light-locker xscreensaver gnome-screensaver; do + if have $l; then + LOCKER="$LOCKER $l" + fi +done + +# Space for the toolchain and the build. Both land under $HOME unless +# CARGO_HOME says otherwise, which is exactly the 8GB-of-eMMC case. +avail_mb() { df -Pm "$1" 2>/dev/null | awk 'NR==2 {print $4}'; } +HOME_FREE=$(avail_mb "$HOME") +PREFIX_FREE=$(avail_mb "$(dirname "$PREFIX")") + +step "Found" +say " system $OS_NAME ($ARCH)" +say " packages ${PKG:-none detected}" +say " display mgr $DM" +say " window mgr $WM" +say " terminal ${TERMINAL:-none found}" +say " tmux $(tmux -V 2>/dev/null || echo 'NOT INSTALLED')" +# Not `cargo --version | cut ... || echo`: the pipeline's status is cut's, and +# cut succeeds on empty input, so the fallback would never fire. +if have cargo; then + say " cargo $(cargo --version 2>/dev/null | cut -d' ' -f1-2)" +else + say " cargo NOT INSTALLED" +fi +say " git $(git --version 2>/dev/null || echo 'NOT INSTALLED')" +say " free space ${HOME_FREE:-?} MB in \$HOME, ${PREFIX_FREE:-?} MB on $PREFIX" +if [ -n "$LOCKER" ]; then say " screen locker $LOCKER"; fi + +# What we are about to need. +NEED_PKGS="" +NEED_RUSTUP=no +COST_MB=10 + +if ! have tmux; then NEED_PKGS="$NEED_PKGS tmux"; fi +if ! have git; then NEED_PKGS="$NEED_PKGS git"; fi +if ! have cargo; then + NEED_RUSTUP=yes + COST_MB=$((COST_MB + 1200)) +fi +# The build itself: a debug-free release build of this crate and its deps. +COST_MB=$((COST_MB + 600)) + +if [ -z "$TERMINAL" ]; then + warn "no terminal emulator found. socktop-swipe can attach in an existing" + warn "terminal, but unattended autostart needs one. alacritty is the tested choice." +fi +if [ -z "$PKG" ]; then + warn "no supported package manager. Nothing will be installed for you;" + warn "make sure tmux, git and a Rust toolchain are present." +fi +if [ -n "$HOME_FREE" ] && [ "$HOME_FREE" -lt "$COST_MB" ]; then + warn "only ${HOME_FREE} MB free in \$HOME but about ${COST_MB} MB is needed." + warn "A source build will not fit. Free space first, or set CARGO_HOME and" + warn "CARGO_TARGET_DIR to somewhere with room." +fi + +step "This will" +if [ -n "$NEED_PKGS" ]; then say " install packages:$NEED_PKGS"; fi +if [ "$NEED_RUSTUP" = yes ]; then say " install the Rust toolchain via rustup (~1.2 GB)"; fi +say " build socktop-swipe from source (~600 MB of build artifacts)" +say " install the binary to $BIN" +say " write a starter config to $CONF" +say " ...then ask about the touch device, autostart, autologin and blanking." +say "" +say " About ${COST_MB} MB of disk. Nothing has been changed yet." +say "" + +if [ "$HAVE_TTY" = no ]; then + say "No terminal available, so every question takes its default." + say "Run the script directly for the interactive version:" + say " curl -fsSLO $REPO/raw/branch/main/install.sh && sh install.sh" + say "" +fi + +ask "Continue?" y || { say "Nothing done."; exit 0; } + +# -------------------------------------------------------------------------- +# Dependencies +# -------------------------------------------------------------------------- +if [ -n "$NEED_PKGS" ]; then + step "Installing:$NEED_PKGS" + if [ -z "$PKG_INSTALL" ]; then + warn "no package manager -- install$NEED_PKGS yourself and re-run." + exit 1 + fi + if [ "$PKG" = apt-get ]; then sudo apt-get update; fi + # shellcheck disable=SC2086 + $PKG_INSTALL $NEED_PKGS +fi + +if [ "$NEED_RUSTUP" = yes ]; then + step "Installing the Rust toolchain" + if ask "Install rustup now?" y; then + curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --no-modify-path + # shellcheck disable=SC1091 + . "$HOME/.cargo/env" + else + say "Install Rust yourself, then re-run this script." + exit 1 + fi +fi +if ! have cargo; then warn "cargo still not on PATH -- open a new shell and re-run."; exit 1; fi + +# -------------------------------------------------------------------------- +# Source +# -------------------------------------------------------------------------- +here=$(CDPATH='' cd -- "$(dirname -- "$0")" 2>/dev/null && pwd || echo "") +if [ -n "$here" ] && [ -f "$here/Cargo.toml" ]; then + SRC=$here + step "Building from this checkout: $SRC" +else + SRC="$HOME/.local/src/socktop-swipe" + step "Fetching the source to $SRC" mkdir -p "$(dirname "$SRC")" - if [ -d "$SRC/.git" ]; then git -C "$SRC" pull --ff-only; else - git clone https://git.sr.ht/~mil/lisgd "$SRC" + if [ -d "$SRC/.git" ]; then + git -C "$SRC" pull --ff-only + else + git clone "$REPO" "$SRC" fi - # WITHOUT_WAYLAND: lisgd builds both backends by default and would otherwise - # need libwayland-dev for code that never runs on an X11-only host. - make -C "$SRC" WITHOUT_WAYLAND=1 - sudo make -C "$SRC" install WITHOUT_WAYLAND=1 PREFIX="$PREFIX" fi -# --- scripts and config ----------------------------------------------------- -echo "==> installing scripts to $BIN" -sudo install -d "$BIN" "$ETC" -sudo install -m 755 "$here/socktop-rack" "$here/socktop-swipe" "$here/socktop-gestures" "$BIN/" +step "Building (this is the slow part on a low-power box)" +( cd "$SRC" && cargo build --release ) +sudo install -d "$BIN" +sudo install -m 755 "$SRC/target/release/socktop-swipe" "$BIN/socktop-swipe" +say " installed $BIN/socktop-swipe" -if [ -e "$CONF" ]; then - echo "==> keeping existing $CONF (not overwritten)" +# -------------------------------------------------------------------------- +# Touch device +# -------------------------------------------------------------------------- +step "Touch panel" +DEVICE="" +# Prefer the by-id path: eventN numbers get reshuffled on reboot. +for d in /dev/input/by-id/*event*; do + [ -e "$d" ] || continue + if udevadm info --query=property --name="$d" 2>/dev/null | grep -q '^ID_INPUT_TOUCHSCREEN=1'; then + say " found touchscreen: $d" + if [ -z "$DEVICE" ]; then DEVICE=$d; fi + fi +done +if [ -z "$DEVICE" ]; then + warn "no touchscreen found under /dev/input/by-id/." + warn "If the panel is not plugged in yet, that is fine -- set touch.device" + warn "in the config later. 'socktop-swipe doctor --list' will show it." + DEVICE=/dev/input/by-id/CHANGE-ME else - sudo install -m 644 "$here/config.env" "$CONF" - echo "==> installed default config to $CONF" - echo " EDIT IT before first run if your hardware differs." + DEVICE=$(askval "Use which device?" "$DEVICE") fi -# --- input group ------------------------------------------------------------ -if id -nG | tr ' ' '\n' | grep -qx input; then - echo "==> already in the 'input' group" - relogin=no -else - echo "==> adding $(id -un) to the 'input' group (lisgd reads /dev/input directly)" +step "Device access" +say " Reading touch events needs permission on the device." +say " A udev rule grants it for touchscreens only and takes effect at once." +say " The 'input' group grants access to every input device, keyboard included," +say " and needs a full logout." +RELOGIN=no +if ask "Install the udev rule?" y; then + sudo install -m 644 "$SRC/packaging/70-socktop-swipe.rules" "$UDEV" + sudo udevadm control --reload + sudo udevadm trigger --subsystem-match=input + say " wrote $UDEV" + if ! id -nG | tr ' ' '\n' | grep -qx input; then + warn "the rule uses GROUP=input and you are not in it." + if ask "Add $(id -un) to the 'input' group as well?" y; then + sudo usermod -aG input "$(id -un)" + RELOGIN=yes + fi + fi +elif ask "Add $(id -un) to the 'input' group instead?" n; then sudo usermod -aG input "$(id -un)" - relogin=yes + RELOGIN=yes fi -# --- optional: make X ignore the panel -------------------------------------- -if [ "$do_xignore" = yes ]; then - # shellcheck disable=SC1090 - . "$CONF" - product=${XIGNORE_MATCH:-ILITEK} - echo "==> telling X to ignore touch devices matching '$product'" - sudo mkdir -p /etc/X11/xorg.conf.d - sudo tee "$XCONF" >/dev/null <"$CONF" + say " wrote $CONF" + say " EDIT IT: the example screens are the author's rack, not yours." fi -# --- optional: stop the screen blanking ------------------------------------- -if [ "$do_noblank" = yes ]; then - echo "==> disabling screen blanking and DPMS" - # Two layers on purpose: - # 1. An Xorg ServerFlags snippet, so it applies to every X session including - # the display manager greeter, and survives reboots. - # 2. An xset call at i3 startup, because a session or DM can re-enable the - # screensaver after X starts. - # Note `xset` alone is NOT enough, and must run as the session user -- running - # it under sudo targets root's X connection and silently does nothing. - sudo mkdir -p /etc/X11/xorg.conf.d - sudo tee "$BCONF" >/dev/null <<'EOF' -# Installed by socktop-swipe. This is a wall display: it must never blank. -Section "ServerFlags" - Option "BlankTime" "0" - Option "StandbyTime" "0" - Option "SuspendTime" "0" - Option "OffTime" "0" -EndSection -EOF - echo " wrote $BCONF" +# -------------------------------------------------------------------------- +# Optional monitors +# -------------------------------------------------------------------------- +step "Monitor programs (all optional -- socktop-swipe runs whatever you configure)" +if ! have socktop && ask "Install socktop (the point of the project)?" y; then + cargo install socktop +fi +if ! have uptime-kuma-status && ask "Install uptime-kuma-status?" n; then + cargo install uptime-kuma-status +fi +if ! have unifly && ask "Build unifly (UniFi TUI) from source?" n; then + U="$HOME/.local/src/unifly" + mkdir -p "$(dirname "$U")" + if [ -d "$U/.git" ]; then git -C "$U" pull --ff-only; else + git clone https://github.com/jasonwitty/unifly "$U" + fi + ( cd "$U" && cargo build --profile release-small -p unifly ) + say " built $U/target/release-small/unifly" + say " put that path under 'binaries: unifly:' in $CONF" +fi - # Apply immediately too, if a session is available. - if [ -n "${DISPLAY:-}" ]; then +# -------------------------------------------------------------------------- +# Unattended operation +# -------------------------------------------------------------------------- +step "Wall-display behaviour" + +if ask "Stop the screen blanking, sleeping and locking?" y; then + sudo mkdir -p /etc/X11/xorg.conf.d + sudo tee "$XBLANK" >/dev/null <<-'EOF' + # Installed by socktop-swipe. This is a wall display: it must never blank. + Section "ServerFlags" + Option "BlankTime" "0" + Option "StandbyTime" "0" + Option "SuspendTime" "0" + Option "OffTime" "0" + EndSection + EOF + say " wrote $XBLANK" + # The Xorg snippet covers every session including the greeter and survives + # reboots; xset covers a session that re-enables blanking after X starts. + # Both are needed, and xset MUST run as the session user -- under sudo it + # targets root's X connection and silently does nothing. + if [ -n "${DISPLAY:-}" ] && have xset; then xset s off s noblank -dpms 2>/dev/null || true - echo " applied to the running session" + say " applied to the running session" fi - - i3conf="$HOME/.config/i3/config" - if [ -e "$i3conf" ] && ! grep -q "xset s off" "$i3conf"; then - bak="$i3conf.bak-$(date +%Y%m%d-%H%M%S)" - cp "$i3conf" "$bak" - printf '\n# Wall display: never blank (socktop-swipe --noblank)\nexec --no-startup-id xset s off s noblank -dpms\n' >>"$i3conf" - if i3 -C -c "$i3conf" >/dev/null 2>&1; then - echo " added xset to i3 autostart (backup: $bak)" - else - cp "$bak" "$i3conf" - echo "!! i3 rejected the config; restored $bak" >&2 - fi - fi - relogin=yes + for l in $LOCKER; do + warn "$l is installed and may still lock the screen. Disable it in your" + warn "session's autostart." + done fi -# --- optional: lightdm autologin -------------------------------------------- -if [ "$do_autologin" = yes ]; then - # A wall display must come back by itself after a power cut. Without this a - # reboot leaves the panel at the greeter with nothing autostarted. - # SECURITY: anyone with physical access to the screen gets this user's - # session. Only use it on a display whose user account is nothing but the - # dashboard. Debian's lightdm-autologin PAM stack needs no extra group. - if [ ! -d /etc/lightdm ]; then - echo "!! /etc/lightdm not found; skipping autologin (not lightdm?)" >&2 - else - session=i3 - [ -e /usr/share/xsessions/$session.desktop ] || - echo "!! /usr/share/xsessions/$session.desktop missing; check autologin-session in $LCONF" >&2 - echo "==> enabling lightdm autologin for $(id -un) into '$session'" +if [ "$WM" = i3 ] && [ -e "$HOME/.config/i3/config" ]; then + if ask "Add the i3 autostart lines?" y; then + i3conf="$HOME/.config/i3/config" + if grep -q socktop-swipe "$i3conf"; then + say " already present; leaving it alone" + else + bak="$i3conf.bak-$(date +%Y%m%d-%H%M%S)" + cp "$i3conf" "$bak" + { + printf '\n# --- socktop-swipe ---\n' + printf '# One process: builds the session, opens it in a terminal and reads the panel.\n' + printf 'exec --no-startup-id %s/socktop-swipe run\n' "$BIN" + printf '# Never blank (the Xorg snippet covers reboots; this covers this session).\n' + printf 'exec --no-startup-id xset s off s noblank -dpms\n' + if have unclutter; then + printf '# With the panel grabbed, nothing ever moves the pointer, so hide it.\n' + printf 'exec --no-startup-id unclutter --timeout 1 --ignore-scrolling\n' + fi + } >>"$i3conf" + if i3 -C -c "$i3conf" >/dev/null 2>&1; then + say " added the autostart (backup: $bak)" + else + cp "$bak" "$i3conf" + warn "i3 rejected the config; restored $bak" + fi + fi + fi +elif [ "$DM" != none ] || [ "$WM" != none ]; then + say " no i3 config found. For a systemd user session instead:" + say " cp $SRC/packaging/socktop-swipe.service ~/.config/systemd/user/" + say " systemctl --user enable --now socktop-swipe" +fi + +if [ "$DM" = lightdm ]; then + say "" + say " Autologin makes the display come back by itself after a power cut." + say " ANYONE WITH PHYSICAL ACCESS TO THE PANEL GETS THIS USER'S SESSION." + say " Only do this where the account is nothing but the dashboard." + if ask "Log $(id -un) straight into $WM at boot?" n; then sudo mkdir -p /etc/lightdm/lightdm.conf.d - sudo tee "$LCONF" >/dev/null </dev/null <<-EOF + # Installed by socktop-swipe. Delete this file to restore the login prompt. + [Seat:*] + autologin-user=$(id -un) + autologin-user-timeout=0 + autologin-session=$WM + EOF + say " wrote $LIGHTDM (takes effect at next boot)" fi +elif [ "$DM" != none ]; then + say " Autologin is only automated for lightdm; yours is $DM. Configure it there" + say " if the display must come back unattended after a power cut." fi -# --- optional: i3 autostart ------------------------------------------------- -if [ "$do_i3" = yes ]; then - i3conf="$HOME/.config/i3/config" - if ! command -v unclutter >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1; then - echo "==> installing unclutter-xfixes (hides the idle pointer on the wall display)" - sudo apt-get install -y unclutter-xfixes || echo "!! unclutter-xfixes not installed; the pointer will stay visible" >&2 - fi - if [ ! -e "$i3conf" ]; then - echo "!! $i3conf not found; skipping i3 autostart" >&2 - elif grep -q "socktop-gestures" "$i3conf"; then - echo "==> i3 autostart already present; leaving it alone" - else - bak="$i3conf.bak-$(date +%Y%m%d-%H%M%S)" - cp "$i3conf" "$bak" - cat >>"$i3conf" </dev/null 2>&1; then - echo "==> added i3 autostart (backup: $bak)" - echo " NOTE: set \$TERMINAL in your i3 config, or edit those lines" - echo " to your terminal's full path." - else - cp "$bak" "$i3conf" - echo "!! i3 rejected the config; restored $bak" >&2 - i3 -C -c "$i3conf" || true - fi - fi -fi - -echo -echo "==============================================================" -echo "Installed." -if [ "$relogin" = yes ]; then - echo "LOG OUT and back in before use (group and/or X changes)." -fi +# -------------------------------------------------------------------------- +say "" +say "==============================================================" +say "Installed." +if [ "$RELOGIN" = yes ]; then say "LOG OUT AND BACK IN before use (group membership changed)."; fi cat <