Files
socktop-swipe/README.md
T
2026-09-09 21:30:23 +00:00

351 lines
14 KiB
Markdown

# socktop-swipe
A rack-mounted touchscreen swipable display that monitors your servers via socktop and other status tuis.
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.
![The touch panel mounted in the rack, showing four Raspberry Pis tiled together](media/rack-display.jpg)
<video controls src="media/swipe-demo.mp4"></video>
Built for a 9" panel bolted into a 19" server rack, driven by a low-power x86 box
running i3 on X11.
## What you're looking at
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.
Everything on it is a terminal program. socktop-swipe does not draw anything
itself — it decides what is on screen and reads your finger.
```
unifly -1x0
(UniFi network)
┌──────────────────────────────┐ ┌──────────────────┐
│ 4 Raspberry Pis │→ │ 2 more machines │ row 0
│ tiled, then each full-screen│ │ │
└──────────────────────────────┘ └──────────────────┘
0x0 0x1
uptime kuma 1x0
(service status)
```
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.
## Hardware
| Part | What was used | Status |
| --- | --- | --- |
| Display host | LattePanda (Atom x5-Z8350, 1.9 GB), Debian 11, i3 on X11 | in daily use |
| Display host | Dell Wyse 3040 (Atom x5-Z8350, 2 GB), 8/16 GB eMMC, DP out | not yet tried |
| Panel | GeeekPi 9" 1280x720 3U touchscreen, ILITEK USB touch | in daily use |
| Mounting | [`cad/`](cad) — 19" rack adapter | printed and in the rack |
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.
Measured on the LattePanda (Atom x5-Z8350, 4 cores, 1.9 GB RAM, no swap), with a
toolchain already present: **108 seconds**, peaking at 1.1 GB of the 1.9 GB and
leaving a 103 MB build directory. The 946 KB binary is the only thing installed.
Add roughly 1.2 GB and a few minutes if rustup has to be fetched too. The
installer checks free space first and tells you if it will not fit.
<details>
<summary>From a checkout instead</summary>
```sh
git clone https://gt.wittyoneoff.com/jason/socktop-swipe
cd socktop-swipe
./install.sh
```
</details>
## Your first config
The config lives at `~/.config/socktop-swipe/config.yaml`. The installer writes a
starting point; this section builds one up from nothing.
**Check every edit** with:
```sh
socktop-swipe validate
```
which resolves the grid, prints the map, and tells you if a program it would run
is not actually there.
### One group of hosts
```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]
```
That is five screens: the four hosts tiled, then each one full-screen.
```
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` | `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 |
`layout` takes any tmux layout name: `tiled` (the default), `even-horizontal`,
`even-vertical`, `main-horizontal`, `main-vertical`.
```yaml
- at: "1x1"
type: generic
title: k3s
command: journalctl -f -u k3s-agent
```
## Configuration reference
| 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
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
```
Adjust `MatchProduct` to your panel, and restart X.
### A pane shows `[... exited: status 1]`
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.
### The dashboard is on a screen that isn't there
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 # binary, config, udev rule, X snippets, autologin
./uninstall.sh --keep-config # keep your settings
```
The Rust toolchain, tmux, the monitor programs and your `input` group membership
are left alone; the script prints how to remove each.
## Notes
Design decisions and the reasoning behind them are in [`notes/`](notes):
- [`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)
## Related
- [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
## License
Apache-2.0.