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 <