Add host groups (one tmux window per group) and lightdm --autologin

SOCKTOP_GROUPS splits hosts into ';'-separated groups, each its own tmux
window with an optional @layout. The carousel walks group overview -> each
pane zoomed -> next group overview. SOCKTOP_HOSTS still works as one group.

install.sh --autologin writes a lightdm.conf.d drop-in; uninstall removes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jasonwitty
2026-08-28 09:53:39 -07:00
parent c5b8d04b84
commit 818b09762e
6 changed files with 184 additions and 51 deletions
+25 -11
View File
@@ -3,16 +3,19 @@
Swipe-to-zoom touchscreen navigation for a wall-mounted Swipe-to-zoom touchscreen navigation for a wall-mounted
[socktop](https://crates.io/crates/socktop) dashboard. [socktop](https://crates.io/crates/socktop) dashboard.
One tmux window shows every monitored host tiled together. Swiping right-to-left Hosts are arranged in **groups**. Each group is one tmux window showing its
on the panel zooms into each host full-screen, one at a time; swiping hosts tiled together. Swiping right-to-left on the panel zooms into each host
left-to-right walks back out to the overview. full-screen, one at a time, then moves on to the next group's overview; swiping
left-to-right walks back the same way.
``` ```
overview -> host 1 -> host 2 -> host 3 -> host 4 group A -> A1 -> A2 -> A3 -> A4 -> group B -> B1 -> B2
(tiled) zoomed zoomed zoomed zoomed (quarters) zoomed zoomed zoomed zoomed (stacked) zoomed zoomed
<-------------- swipe left-to-right -------------- <------------------------- swipe left-to-right -------------------------
``` ```
With a single group this is simply overview -> each host -> back.
Built for a 10" touch panel on a server rack driving four Raspberry Pis, on a Built for a 10" touch panel on a server rack driving four Raspberry Pis, on a
low-power x86 box running i3 on X11. low-power x86 box running i3 on X11.
@@ -63,6 +66,7 @@ fresh session.
| `--xignore` | Also tell X to ignore the touch panel (see below — usually wanted) | | `--xignore` | Also tell X to ignore the touch panel (see below — usually wanted) |
| `--i3` | Also add i3 autostart lines, with a backup and `i3 -C` validation | | `--i3` | Also add i3 autostart lines, with a backup and `i3 -C` validation |
| `--noblank` | Also stop the screen blanking / DPMS power-down (wall displays) | | `--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)) |
Re-running is safe: an existing config file is never overwritten, and the i3 Re-running is safe: an existing config file is never overwritten, and the i3
edit is skipped if already present. edit is skipped if already present.
@@ -92,7 +96,8 @@ blame the config change.
| Option | Default | What it does | | Option | Default | What it does |
| --- | --- | --- | | --- | --- | --- |
| `SOCKTOP_HOSTS` | four Pi profiles | socktop profile names, **in swipe order**. Any count works; layout and carousel adapt. | | `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_SESSION` | `socktop4` | tmux session name | | `SOCKTOP_SESSION` | `socktop4` | tmux session name |
| `SOCKTOP_BIN` | `$HOME/.cargo/bin/socktop` | Full path — i3 does not have `~/.cargo/bin` on `PATH` | | `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. | | `TOUCH_DEV` | ILITEK by-id path | Touch device. **Always use a `/dev/input/by-id/` path**; `eventN` numbers change on reboot. |
@@ -104,8 +109,14 @@ blame the config change.
### Adding or removing hosts ### Adding or removing hosts
Edit `SOCKTOP_HOSTS` and run `socktop-rack`. Nothing else needs touching — the Edit `SOCKTOP_GROUPS` and run `socktop-rack`. Nothing else needs touching — the
carousel derives its length from the pane count at runtime. 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"
```
## Autostart ## Autostart
@@ -152,8 +163,11 @@ autostarts. Check with:
grep -E '^autologin-(user|session)' /etc/lightdm/lightdm.conf grep -E '^autologin-(user|session)' /etc/lightdm/lightdm.conf
``` ```
No output means no autologin. Configuring it is out of scope here — it weakens No output means no autologin. `./install.sh --autologin` writes
the machine's security posture and is a deliberate choice, not a default. `/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.
## Uninstall ## Uninstall
+18 -4
View File
@@ -12,10 +12,24 @@
# What to monitor # What to monitor
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# socktop profile names (from ~/.config/socktop/profiles.json), in swipe order. # What to show, as GROUPS separated by ";". Each group is one screen of the
# The first is what you land on after one right-to-left swipe from the overview. # carousel: a tiled overview of its hosts, then each host zoomed full-screen in
# Any count works; the tiled layout and the carousel adapt automatically. # turn, then on to the next group. Host names are socktop profile names from
SOCKTOP_HOSTS="${SOCKTOP_HOSTS:-rpi-master rpi-worker-1 rpi-worker-2 rpi-worker-3}" # ~/.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:-}"
# tmux session name. Change only if it collides with something else. # tmux session name. Change only if it collides with something else.
SOCKTOP_SESSION="${SOCKTOP_SESSION:-socktop4}" SOCKTOP_SESSION="${SOCKTOP_SESSION:-socktop4}"
+32 -1
View File
@@ -5,7 +5,8 @@
# ./install.sh --xignore ...and tell X to ignore the touch panel # ./install.sh --xignore ...and tell X to ignore the touch panel
# ./install.sh --i3 ...and add i3 autostart lines # ./install.sh --i3 ...and add i3 autostart lines
# ./install.sh --noblank ...and stop the screen blanking # ./install.sh --noblank ...and stop the screen blanking
# ./install.sh --xignore --i3 --noblank full wall-display setup # ./install.sh --autologin ...and make lightdm log this user straight into i3
# ./install.sh --xignore --i3 --noblank --autologin full wall-display setup
# #
# Safe to re-run: an existing config file is never overwritten, and the i3 edit # Safe to re-run: an existing config file is never overwritten, and the i3 edit
# is skipped if already present. # is skipped if already present.
@@ -17,17 +18,20 @@ ETC="$PREFIX/etc"
CONF="$ETC/socktop-swipe.env" CONF="$ETC/socktop-swipe.env"
XCONF=/etc/X11/xorg.conf.d/99-ignore-touch-socktop-swipe.conf XCONF=/etc/X11/xorg.conf.d/99-ignore-touch-socktop-swipe.conf
BCONF=/etc/X11/xorg.conf.d/10-no-blanking-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" SRC="$HOME/src/lisgd"
here=$(cd "$(dirname "$0")" && pwd) here=$(cd "$(dirname "$0")" && pwd)
do_xignore=no do_xignore=no
do_i3=no do_i3=no
do_noblank=no do_noblank=no
do_autologin=no
for a in "$@"; do for a in "$@"; do
case "$a" in case "$a" in
--xignore) do_xignore=yes ;; --xignore) do_xignore=yes ;;
--i3) do_i3=yes ;; --i3) do_i3=yes ;;
--noblank) do_noblank=yes ;; --noblank) do_noblank=yes ;;
--autologin) do_autologin=yes ;;
*) echo "unknown option: $a" >&2; exit 2 ;; *) echo "unknown option: $a" >&2; exit 2 ;;
esac esac
done done
@@ -147,6 +151,33 @@ EOF
relogin=yes relogin=yes
fi 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'"
sudo mkdir -p /etc/lightdm/lightdm.conf.d
sudo tee "$LCONF" >/dev/null <<EOF
# Installed by socktop-swipe (--autologin). Wall display: come back unattended
# after a reboot. Remove this file to restore the login prompt.
[Seat:*]
autologin-user=$(id -un)
autologin-user-timeout=0
autologin-session=$session
EOF
echo " wrote $LCONF (takes effect at next boot)"
fi
fi
# --- optional: i3 autostart ------------------------------------------------- # --- optional: i3 autostart -------------------------------------------------
if [ "$do_i3" = yes ]; then if [ "$do_i3" = yes ]; then
i3conf="$HOME/.config/i3/config" i3conf="$HOME/.config/i3/config"
+55 -17
View File
@@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
# Build the socktop display session: one window, N tiled panes, one per host. # Build the socktop display session: one tmux window per GROUP, one pane per
# Pane index order is the swipe order. # host inside it. Window order and pane index order together are the swipe
# order (see socktop-swipe).
set -eu set -eu
# shellcheck source=config.env # shellcheck source=config.env
@@ -8,29 +9,66 @@ for c in /usr/local/etc/socktop-swipe.env "$(dirname "$0")/config.env"; do
[ -r "$c" ] && . "$c" && break [ -r "$c" ] && . "$c" && break
done done
: "${SOCKTOP_HOSTS:?no SOCKTOP_HOSTS -- is the config installed?}" : "${SOCKTOP_GROUPS:=}"
: "${SOCKTOP_HOSTS:=}"
: "${SOCKTOP_SESSION:=socktop4}" : "${SOCKTOP_SESSION:=socktop4}"
: "${SOCKTOP_BIN:=socktop}" : "${SOCKTOP_BIN:=socktop}"
WIN="$SOCKTOP_SESSION:rack" # Legacy form: SOCKTOP_HOSTS alone is a single tiled group.
if [ -z "$SOCKTOP_GROUPS" ]; then
: "${SOCKTOP_HOSTS:?no SOCKTOP_GROUPS or SOCKTOP_HOSTS -- is the config installed?}"
SOCKTOP_GROUPS=$SOCKTOP_HOSTS
fi
tmux kill-session -t "$SOCKTOP_SESSION" 2>/dev/null || true tmux kill-session -t "$SOCKTOP_SESSION" 2>/dev/null || true
first=1 # Pane titles are set on the session, so do it up front; windows inherit.
for h in $SOCKTOP_HOSTS; do build_group() {
if [ "$first" = 1 ]; then # $1 = window name, remaining args = host names and an optional @layout
tmux new-session -d -s "$SOCKTOP_SESSION" -n rack "$SOCKTOP_BIN -P $h" name=$1
first=0 shift
else layout=tiled
# Split the most recently created pane so creation order == index order. hosts=
tmux split-window -t "$WIN" "$SOCKTOP_BIN -P $h" for tok in "$@"; do
fi case "$tok" in
# Label the pane with the host it is showing. @*) layout=${tok#@} ;;
tmux select-pane -t "$WIN.$(tmux display-message -t "$WIN" -p '#{pane_index}')" -T "$h" *) hosts="$hosts $tok" ;;
esac
done done
# shellcheck disable=SC2086
set -- $hosts
[ $# -gt 0 ] || return 0
tmux select-layout -t "$WIN" tiled if ! tmux has-session -t "$SOCKTOP_SESSION" 2>/dev/null; then
tmux select-pane -t "$WIN.0" tmux new-session -d -s "$SOCKTOP_SESSION" -n "$name" "$SOCKTOP_BIN -P $1"
else
tmux new-window -d -t "$SOCKTOP_SESSION" -n "$name" "$SOCKTOP_BIN -P $1"
fi
win="$SOCKTOP_SESSION:$name"
tmux select-pane -t "$win.0" -T "$1"
shift
for h in "$@"; do
# Split the most recently created pane so creation order == index order.
tmux split-window -t "$win" "$SOCKTOP_BIN -P $h"
tmux select-pane -t "$win.$(tmux display-message -t "$win" -p '#{pane_index}')" -T "$h"
done
tmux select-layout -t "$win" "$layout"
tmux select-pane -t "$win.0"
}
n=0
old_ifs=$IFS
IFS=';'
for group in $SOCKTOP_GROUPS; do
IFS=$old_ifs
# shellcheck disable=SC2086
build_group "g$n" $group
n=$((n + 1))
IFS=';'
done
IFS=$old_ifs
tmux select-window -t "$SOCKTOP_SESSION:g0"
tmux set-option -t "$SOCKTOP_SESSION" pane-border-status top tmux set-option -t "$SOCKTOP_SESSION" pane-border-status top
tmux set-option -t "$SOCKTOP_SESSION" pane-border-format ' #{pane_title} ' tmux set-option -t "$SOCKTOP_SESSION" pane-border-format ' #{pane_title} '
+47 -17
View File
@@ -1,14 +1,15 @@
#!/bin/sh #!/bin/sh
# socktop-swipe next|prev # socktop-swipe next|prev
# #
# Walks a linear "zoom carousel" over the tiled socktop window: # Walks a linear "zoom carousel" over the socktop session. Each tmux window is
# one group of hosts; within a group the line is the tiled overview, then each
# pane zoomed full-screen in index order; then the next window's overview:
# #
# overview <-> pane 0 <-> pane 1 <-> ... <-> pane N-1 # overview(g0) <-> g0.0 <-> ... <-> g0.N <-> overview(g1) <-> g1.0 <-> ...
# (all hosts) host 1 host 2 last host
# #
# "next" moves right along that line and stops at the last pane. # "next" moves right and stops at the last pane of the last window. "prev"
# "prev" moves back and stops at the overview. Deliberately no wrap-around: # moves back and stops at the first overview. Deliberately no wrap-around: on
# on a wall display, wrapping makes it impossible to tell where you are. # a wall display, wrapping makes it impossible to tell where you are.
# #
# This is tmux pane zoom, NOT extra socktop instances. All panes keep running # This is tmux pane zoom, NOT extra socktop instances. All panes keep running
# and stay connected while hidden, so swiping back shows current data with no # and stay connected while hidden, so swiping back shows current data with no
@@ -22,10 +23,16 @@ done
dir=${1:?usage: socktop-swipe next|prev} dir=${1:?usage: socktop-swipe next|prev}
# Resolve the session's active window to its window id (@N) rather than assuming # Ordered window ids (@N) of the session, and the currently active one.
# a window name, so this works against any socktop session however it was built. wins=$(tmux list-windows -t "$SOCKTOP_SESSION" -F '#{window_id}' 2>/dev/null) || exit 0
WIN=$(tmux display-message -t "$SOCKTOP_SESSION" -p '#{window_id}' 2>/dev/null) || exit 0 [ -n "$wins" ] || exit 0
[ -n "$WIN" ] || exit 0 WIN=$(tmux display-message -t "$SOCKTOP_SESSION" -p '#{window_id}') || exit 0
prev_win= next_win= seen=no
for w in $wins; do
if [ "$seen" = yes ]; then next_win=$w; break; fi
if [ "$w" = "$WIN" ]; then seen=yes; else prev_win=$w; fi
done
state=$(tmux display-message -t "$WIN" -p '#{window_zoomed_flag} #{pane_index} #{window_panes}') || exit 0 state=$(tmux display-message -t "$WIN" -p '#{window_zoomed_flag} #{pane_index} #{window_panes}') || exit 0
zoomed=${state%% *} zoomed=${state%% *}
@@ -35,24 +42,47 @@ count=${rest##* }
last=$((count - 1)) last=$((count - 1))
zoom_to() { zoom_to() {
# $1 = window id, $2 = pane index.
# Selecting a different pane auto-unzooms, so zoom explicitly afterwards. # Selecting a different pane auto-unzooms, so zoom explicitly afterwards.
tmux select-pane -t "$WIN.$1" tmux select-window -t "$1"
tmux resize-pane -Z -t "$WIN.$1" tmux select-pane -t "$1.$2"
tmux resize-pane -Z -t "$1.$2"
}
overview() {
# Show window $1 unzoomed.
tmux select-window -t "$1"
if [ "$(tmux display-message -t "$1" -p '#{window_zoomed_flag}')" = 1 ]; then
tmux resize-pane -Z -t "$1"
fi
} }
if [ "$zoomed" = 0 ]; then if [ "$zoomed" = 0 ]; then
case "$dir" in case "$dir" in
next) zoom_to 0 ;; next) zoom_to "$WIN" 0 ;;
prev) : ;; # already at the overview; nothing further out prev)
# Back out of this group's overview onto the previous group's last pane.
if [ -n "$prev_win" ]; then
pl=$(tmux display-message -t "$prev_win" -p '#{window_panes}')
zoom_to "$prev_win" $((pl - 1))
fi
;;
esac esac
else else
case "$dir" in case "$dir" in
next) [ "$idx" -lt "$last" ] && zoom_to $((idx + 1)) || : ;; next)
if [ "$idx" -lt "$last" ]; then
zoom_to "$WIN" $((idx + 1))
elif [ -n "$next_win" ]; then
tmux resize-pane -Z -t "$WIN.$idx" # unzoom before leaving
overview "$next_win"
fi
;;
prev) prev)
if [ "$idx" -gt 0 ]; then if [ "$idx" -gt 0 ]; then
zoom_to $((idx - 1)) zoom_to "$WIN" $((idx - 1))
else else
tmux resize-pane -Z -t "$WIN.$idx" # unzoom -> overview tmux resize-pane -Z -t "$WIN.$idx" # unzoom -> this group's overview
fi fi
;; ;;
esac esac
+6
View File
@@ -12,6 +12,7 @@ BIN="$PREFIX/bin"
CONF="$PREFIX/etc/socktop-swipe.env" CONF="$PREFIX/etc/socktop-swipe.env"
XCONF=/etc/X11/xorg.conf.d/99-ignore-touch-socktop-swipe.conf XCONF=/etc/X11/xorg.conf.d/99-ignore-touch-socktop-swipe.conf
BCONF=/etc/X11/xorg.conf.d/10-no-blanking-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
keep_config=no keep_config=no
[ "${1:-}" = "--keep-config" ] && keep_config=yes [ "${1:-}" = "--keep-config" ] && keep_config=yes
@@ -42,6 +43,11 @@ if [ -e "$BCONF" ]; then
echo "removed $BCONF (screen blanking returns after the next X restart)" echo "removed $BCONF (screen blanking returns after the next X restart)"
fi fi
if [ -e "$LCONF" ]; then
sudo rm -f "$LCONF"
echo "removed $LCONF (login prompt returns at next boot)"
fi
echo echo
echo "Left in place on purpose:" echo "Left in place on purpose:"
echo " * lisgd -- remove with: sudo make -C ~/src/lisgd uninstall" echo " * lisgd -- remove with: sudo make -C ~/src/lisgd uninstall"