fix(autoswitch): WorkspacesChanged output-set diff; unit paths; deploy symlinks

- niri 26.04 has no hotplug event: OutputConfigChanged is only an ACK
  (niri-ipc). Watch WorkspacesChanged, diff sorted output set, apply on
  change. Proven live: eDP-1 off/on -> 3 output-set transitions, each
  matched + applied + reloaded.
- unit: %h/.local/bin/desk-autoswitch.sh, quoted Environment, drop
  User/Group (user unit); add desk-autoswitch.sh bin symlink
- desk.sh: silence validate/load-config/notify stdout
- deploy.sh: deploy repo symlinks (find -type l), so .local/bin links land
- docs: event semantics corrected (displays diagram, log)
This commit is contained in:
Frank
2026-10-09 14:46:17 -07:00
parent 286340e786
commit abb8a2a9d0
7 changed files with 27 additions and 16 deletions
@@ -112,8 +112,8 @@ widget_order = [
[lockscreen_widgets.widget."lockscreen-login-box@eDP-1"]
box_height = 196.0
box_width = 810.0
cx = 960.0
cy = 852.5
cx = 960.000244140625
cy = 852.500244140625
output = "eDP-1"
placement_height = 1080.0
placement_width = 1920.0
@@ -5,15 +5,13 @@ ConditionHost=blankpad
[Service]
Type=simple
User=%u
Group=%u
ExecStart=/usr/bin/env bash %h/.omp/scripts/desk-autoswitch.sh watch
ExecStart=%h/.local/bin/desk-autoswitch.sh watch
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment=DESK_NIRI=/usr/bin/niri
Environment=DESK_NOTIFY=noctalia msg notification-show
Environment="DESK_NOTIFY=noctalia msg notification-show"
[Install]
WantedBy=default.target
+1
View File
@@ -0,0 +1 @@
../../../../scripts/desk-autoswitch.sh
+1 -1
View File
@@ -15,7 +15,7 @@ timestamp: 2026-08-28
Branch `blankpad-de-redesign` (worktree `wt/de-redesign-20261009`). Full sweep + redesign of the niri/noctalia/Handy stack on blankpad.
* **Desk profiles.** `cfg/display.kdl` retired to a symlink; per-desk profiles `desk-1-home.kdl` / `desk-2-portable.kdl` declare monitors by **EDID long name** (`// requires:` lines) — connector names reshuffle between docks, identity travels. `scripts/desk.sh` = list/current/switch/cycle/match/capture; apply = relink → `niri validate` (rollback on failure) → `load-config-file` → noctalia notification. `scripts/desk-autoswitch.sh` + `blankpad-desk-autoswitch.service` watch the `niri msg --json event-stream` for `OutputsChanged` and hot-apply the matching profile (debounced 1.5 s). PM1601 EDID still uncaptured (panel disconnected) — connector-keyed placeholder, `desk.sh capture` pending.
* **Desk profiles.** `cfg/display.kdl` retired to a symlink; per-desk profiles `desk-1-home.kdl` / `desk-2-portable.kdl` declare monitors by **EDID long name** (`// requires:` lines) — connector names reshuffle between docks, identity travels. `scripts/desk.sh` = list/current/switch/cycle/match/capture; apply = relink → `niri validate` (rollback on failure) → `load-config-file` → noctalia notification. `scripts/desk-autoswitch.sh` + `blankpad-desk-autoswitch.service` watch `niri msg --json event-stream`: niri 26.04 has **no hotplug event** (`OutputConfigChanged` is only an ACK that an IPC/config output change was Applied, per niri-ipc); the daemon diffs the sorted output set carried by `WorkspacesChanged` and hot-applies the matching profile (debounced 1.5 s). Proven live with an eDP-1 off/on cycle: `[DP-2 eDP-1] → [DP-2] → [DP-2 eDP-1]`, apply + reload each time. PM1601 EDID still uncaptured (panel disconnected) — connector-keyed placeholder, `desk.sh capture` pending.
* **Keybinds.** `swap-window-left/right` on Mod+CTRL+H/L; move-column demoted to Mod+CTRL+Alt+H/L; 7 column-width presets 12.5–87.5 %; Print trio (full / gui / screen) via noctalia screenshot IPC; launcher Mod+D; Alt+Tab → noctalia window-switcher; Mod+V → clipboard panel (id verified live); Mod+Shift+D → desk cycle; Mod+Shift+Space → `handy --toggle-post-process`; lid-close → `noctalia msg session lock-and-suspend` (pairs with logind `HandleLidSwitch=ignore`). Cheat sheet now GENERATED: `scripts/gen-keybinds.sh` → `omp-docs/tools/niri/keybindings.md` (119 bindings).
* **Noctalia.** `config.toml` + `settings.toml` (lives in `~/.local/state/`, not `~/.config/`) tracked; `taskbar` widget added to bar center, applied live via `noctalia msg config-reload`. Docs corrected to the v5 `noctalia msg` API.
* **Handy.** Self-autostart killed (`~/.config/autostart/Handy.desktop` → `.AGENTTRASH`; `autostart_enabled=false`); niri owns autostart (`spawn-at-startup "handy" "--start-hidden"`). Fleet post-processing wired into the live store: provider `custom` → `http://192.168.0.143:7771/v1`, model `frank-secondary`, prompt `default_improve_transcriptions`. **Open breaker:** frank-secondary answers in `reasoning_content` and leaves `content` empty; per-request cure `chat_template_kwargs.enable_thinking=false` verified, but Handy cannot send it and the server is shared with fleet compaction — dedicated no-think port proposed. 0.9.6→0.9.8 upgrade is operator-owned (`yay -Syu handy-bin`).
+1 -1
View File
@@ -56,7 +56,7 @@ the ultrawide wins when present and the laptop panel is the fallback.
```mermaid
graph LR
P[plug / unplug] --> E[niri event-stream<br/>OutputsChanged]
P[plug / unplug] --> E[niri event-stream<br/>WorkspacesChanged → output-set diff]
E --> M[desk-autoswitch.sh<br/>debounced matcher]
M --> R{profile requires<br/>subset of connected?}
R -->|best match| A[desk.sh apply:<br/>validate - relink - reload - notify]
+19 -7
View File
@@ -1,6 +1,12 @@
#!/usr/bin/env bash
# scripts/desk-autoswitch.sh — hotplug daemon: apply the matching desk profile
# whenever niri reports an OutputsChanged event.
# when the SET OF OUTPUTS changes.
#
# niri has no dedicated hotplug event (verified on 26.04):
# - WorkspacesChanged carries every workspace's `output`, so the sorted
# unique set of outputs changes exactly on connect/disconnect.
# - OutputConfigChanged is only an ACK that an IPC/config output change was
# Applied (niri-ipc), NOT a hotplug signal.
#
# Usage: desk-autoswitch.sh watch
#
@@ -8,7 +14,7 @@
# DESK_AUTO_HOSTNAME only run on this hostname (default: blankpad)
# DESK_SCRIPT path to desk.sh (default ~/.local/bin/desk.sh)
# DESK_NIRI niri binary (default niri)
# DESK_DEBOUNCE_S debounce window in seconds (default 1.5)
# DESK_DEBOUNCE_S min seconds between applies (default 1.5)
set -euo pipefail
@@ -17,8 +23,10 @@ DESK="${DESK_SCRIPT:-$HOME/.local/bin/desk.sh}"
NIRI="${DESK_NIRI:-niri}"
DEBOUNCE="${DESK_DEBOUNCE_S:-1.5}"
usage() {
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
usage() { sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; }
outputs_from_event() { # $1 = one JSON event line
jq -r '[.WorkspacesChanged.workspaces[]?.output // empty] | unique | join(" ")' <<<"$1" 2>/dev/null || true
}
watch_mode() {
@@ -26,16 +34,20 @@ watch_mode() {
echo "desk-autoswitch: initial match"
"$DESK" match --apply || true
local last=0 now
local seen="" last_seen="" last=0 now
# One JSON event per line; reconnect forever (niri restart drops the stream).
while "$NIRI" msg --json event-stream 2>/dev/null | while IFS= read -r line; do
case "$line" in
*OutputsChanged*)
*WorkspacesChanged*)
seen="$(outputs_from_event "$line")"
[ -n "$seen" ] || continue
[ "$seen" = "$last_seen" ] && continue
last_seen="$seen"
now="$(date +%s.%N)"
# swallow bursts inside the debounce window
if awk -v l="$last" -v n="$now" -v d="$DEBOUNCE" 'BEGIN { exit !(n - l >= d) }'; then
last="$now"
echo "desk-autoswitch: OutputsChanged"
echo "desk-autoswitch: outputs changed [$seen]"
"$DESK" match --apply || true
fi
;;
+1 -1
View File
@@ -61,7 +61,7 @@ requires() {
notify() {
# shellcheck disable=SC2086 # word splitting of the prefix is intended
$NOTIFY "Desk" "$1" 2>/dev/null || true
$NOTIFY "Desk" "$1" >/dev/null 2>/dev/null || true
}
# Apply a profile: relink, validate live config, roll back on failure,