2026-08-09 01:22:23 -07:00
# socktop-swipe
2026-09-09 13:08:42 -07:00
A rack-mounted touchscreen you swipe through to see what your machines are doing.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00

2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
<video controls src="media/swipe-demo.mp4"></video>
2026-08-28 09:53:39 -07:00
2026-09-09 13:08:42 -07:00
Built for a 9" panel bolted into a 19" server rack, driven by a low-power x86 box
running i3 on X11.
2026-08-28 10:33:26 -07:00
2026-09-09 13:08:42 -07:00
## What you're looking at
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-31 15:56:35 -07:00
2026-09-09 13:08:42 -07:00
Everything on it is a terminal program. socktop-swipe does not draw anything
itself — it decides what is on screen and reads your finger.
2026-08-31 15:56:35 -07:00
2026-09-09 13:08:42 -07:00
```
unifly -1x0
(UniFi network)
↕
┌──────────────────────────────┐ ┌──────────────────┐
│ 4 Raspberry Pis │→ │ 2 more machines │ row 0
│ tiled, then each full-screen│ │ │
└──────────────────────────────┘ └──────────────────┘
0x0 0x1
↕
uptime kuma 1x0
(service status)
```
2026-08-31 15:56:35 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-31 15:56:35 -07:00
2026-09-09 13:08:42 -07:00
## 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 |
2026-08-31 15:56:35 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
## Install
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
``` sh
curl -fsSL https://gt.wittyoneoff.com/jason/socktop-swipe/raw/branch/main/install.sh | sh
```
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
It checks everything first and prints the whole cost — packages, toolchain, disk
— before changing anything. Then it asks, one thing at a time:
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
- 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
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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.
<details>
<summary>From a checkout instead</summary>
2026-08-09 01:22:23 -07:00
``` sh
git clone https://gt.wittyoneoff.com/jason/socktop-swipe
cd socktop-swipe
2026-09-09 13:08:42 -07:00
./install.sh
2026-08-09 01:22:23 -07:00
```
2026-09-09 13:08:42 -07:00
</details>
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
## Your first config
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
The config lives at `~/.config/socktop-swipe/config.yaml` . The installer writes a
starting point; this section builds one up from nothing.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
**Check every edit ** with:
2026-08-09 01:22:23 -07:00
``` sh
2026-09-09 13:08:42 -07:00
socktop-swipe validate
2026-08-09 01:22:23 -07:00
```
2026-09-09 13:08:42 -07:00
which resolves the grid, prints the map, and tells you if a program it would run
is not actually there.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### One group of hosts
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
``` 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]
2026-08-09 01:22:23 -07:00
```
2026-09-09 13:08:42 -07:00
That is five screens: the four hosts tiled, then each one full-screen.
2026-08-09 02:29:36 -07:00
2026-09-09 13:08:42 -07:00
```
0x0 tiled → rpi-master → rpi-worker-1 → rpi-worker-2 → rpi-worker-3
2026-08-28 09:53:39 -07:00
```
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
> **Quote the coordinate.** Unquoted, YAML reads `0x0` as the hexadecimal number
> zero. `socktop-swipe validate` will tell you so if you forget.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### Something above it
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
``` yaml
- at : "-1x0"
type : unifly
2026-08-09 01:22:23 -07:00
```
2026-09-09 13:08:42 -07:00
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.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### Something below, and more hosts alongside
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
``` yaml
- at : "0x1"
type : socktop
socktop_group : "orangepi, trixie"
layout : even-vertical
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
- at : "1x0"
type : uptime-kuma-status
url : https://status.example.com/status/mine
```
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
`0x1` sits to the right of `0x0` , so you reach it by swiping forward past the
last Pi. `1x0` is below.
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
Full worked example with every option commented:
[`config.example.yaml` ](config.example.yaml ).
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
## How the grid works
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
**Row increases downward. ** `-1x0` is above `0x0` ; `1x0` is below it. Column
increases rightward. You start at `0x0` .
2026-08-28 12:14:32 -07:00
2026-09-09 13:08:42 -07:00
**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.
2026-08-28 12:14:32 -07:00
2026-09-09 13:08:42 -07:00
**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.
2026-08-28 12:14:32 -07:00
2026-09-09 13:08:42 -07:00
**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.
2026-08-28 12:14:32 -07:00
2026-09-09 13:08:42 -07:00
**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.
2026-08-28 12:14:32 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-28 12:14:32 -07:00
2026-09-09 13:08:42 -07:00
## Monitor types
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
Every type also accepts `command:` to replace the generated command outright, and
`title:` to change the label on the pane border.
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
| `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
2026-08-10 00:47:36 -07:00
```
2026-09-09 13:08:42 -07:00
## Configuration reference
2026-08-10 00:47:36 -07:00
2026-09-09 13:08:42 -07:00
| 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:
2026-08-09 01:22:23 -07:00
``` sh
2026-09-09 13:08:42 -07:00
socktop-swipe doctor
2026-08-09 01:22:23 -07:00
```
2026-09-09 13:08:42 -07:00
It watches the panel and says what it saw in English — direction, contact count,
and why a gesture was ignored.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### Swipes are detected but nothing happens
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
`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.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### Nothing is detected at all
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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?
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### Swipes work, but the terminal also reacts — text zooming, panes resizing
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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:
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
``` 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
```
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
Adjust `MatchProduct` to your panel, and restart X.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### A pane shows `[... exited: status 1]`
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
### The dashboard is on a screen that isn't there
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
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 ).
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
## Uninstall
2026-08-09 02:29:36 -07:00
2026-09-09 13:08:42 -07:00
``` sh
./uninstall.sh # binary, config, udev rule, X snippets, autologin
./uninstall.sh --keep-config # keep your settings
```
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
The Rust toolchain, tmux, the monitor programs and your `input` group membership
are left alone; the script prints how to remove each.
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
## Notes
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
Design decisions and the reasoning behind them are in [`notes/` ](notes ):
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
- [`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 )
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
## Related
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
- [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
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
## License
2026-08-09 01:22:23 -07:00
2026-09-09 13:08:42 -07:00
Apache-2.0.