fix(docking): make clamshell hotplug safe and discoverable
All checks were successful
Check / eval (push) Successful in 3m32s
All checks were successful
Check / eval (push) Successful in 3m32s
Real dock QA exposed pointer-bound menus, an absent default rescue watcher, the unsafe zero-output panel-off path, missing keyboard/audio hotplug coverage, and unpainted new outputs. Route Rofi to the focused output; replace panel disable with safe workspace migration; always run and reconnect the display watcher and repaint hotplugged outputs; expose a default per-device keyboard picker; handle audio card availability transitions. Verified: V1 — nix flake check --no-build; checks.docking-ux real Home Manager artifact build; option-docs/template-sot; Nix parse, py_compile, and git diff --check. V2 unavailable: no readable KVM. V3 pending in agent/HARDWARE-QUEUE.md.
This commit is contained in:
@@ -46,28 +46,47 @@ let
|
||||
"Output → ''${2:-$1}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Return dock-class sinks whose current port is usable. PipeWire often
|
||||
# creates the HDMI/DP node before the cable is plugged and later flips its
|
||||
# card/port availability, so presence alone is not a hotplug signal.
|
||||
dock_sinks() {
|
||||
$PACTL --format=json list sinks \
|
||||
| $JQ -r --arg re '^(${dockSinkRe})$' '
|
||||
.[]
|
||||
| select(.name | test($re))
|
||||
| select(
|
||||
((.ports // []) | length) == 0
|
||||
or any(.ports[]?; (.availability // "unknown") != "not available")
|
||||
)
|
||||
| "\(.name)\t\(.description)"'
|
||||
}
|
||||
|
||||
adopt_first_dock() {
|
||||
dock_sinks | while IFS="$TAB" read -r name desc; do
|
||||
[ -n "$name" ] || continue
|
||||
switch_to "$name" "$desc"
|
||||
break
|
||||
done
|
||||
}
|
||||
|
||||
# Startup sweep (login/relogin while already docked): adopt a dock
|
||||
# sink only when the current default is NOT one — a service restart
|
||||
# must not override a manual in-dock pick.
|
||||
if ! is_dock "$($PACTL get-default-sink)"; then
|
||||
$PACTL --format=json list sinks \
|
||||
| $JQ -r '.[] | "\(.name)\t\(.description)"' \
|
||||
| while IFS="$TAB" read -r name desc; do
|
||||
if is_dock "$name"; then switch_to "$name" "$desc"; break; fi
|
||||
done
|
||||
adopt_first_dock
|
||||
fi
|
||||
|
||||
# Event loop: `pactl subscribe` prints "Event 'new' on sink #NN".
|
||||
# Event loop: handle both a genuinely new sink and the common case where
|
||||
# an existing HDMI/DP card changes profile/port availability. Deliberately
|
||||
# ignore ordinary sink "change" events (volume/mute) and server-default
|
||||
# changes, so a manual speaker pick while docked remains sticky.
|
||||
LC_ALL=C $PACTL subscribe | while read -r _ ev _ kind id; do
|
||||
[ "$ev" = "'new'" ] && [ "$kind" = "sink" ] || continue
|
||||
id=''${id#\#}
|
||||
case "$id" in *[!0-9]*|"") continue ;; esac
|
||||
sleep 0.5 # let the node's description/routes settle
|
||||
line=$($PACTL --format=json list sinks | $JQ -r --argjson i "$id" \
|
||||
'.[] | select(.index == $i) | "\(.name)\t\(.description)"')
|
||||
[ -n "$line" ] || continue
|
||||
name=''${line%%"$TAB"*}; desc=''${line#*"$TAB"}
|
||||
if is_dock "$name"; then switch_to "$name" "$desc"; fi
|
||||
case "$ev:$kind" in
|
||||
"'new':sink"|"'new':card"|"'change':card") ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
sleep 0.75 # let PipeWire finish the profile/route transition
|
||||
adopt_first_dock
|
||||
done
|
||||
'';
|
||||
in
|
||||
|
||||
Reference in New Issue
Block a user