#!/usr/bin/env bash # The Dock on an edge other than the bottom, and on chosen screens. # # The rules: # # 1. A dock spans the edge it lives on, which means anchoring BOTH ends of # that edge. Anchoring one leaves the surface free to collapse to its # implicit size on that axis -- the first side dock written here came out # one pixel tall, looked like nothing had rendered, and passed every static # check. So the geometry is measured, not read. # 2. Bottom is unchanged. Somebody who never touches the setting must get the # dock they already had, byte for byte in behaviour. # 3. Only one axis gets an implicit size. Setting both fights the anchors. # 4. An empty screen list means every screen. A list of names goes stale the # moment a display is unplugged, so "all" must not be spelled as one. # 5. Reordering pins never loses or duplicates an entry. # 6. The drag grip sets preventStealing. Without it the settings page's own # Flickable claims the vertical gesture and the row never moves -- which is # the exact objection this feature was refused over for a long time. # # The geometry checks launch isolated shells against a temporary config. The # real settings are read to build them and never written. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" shell_dir="$repo_dir/config/dot/quickshell" dock="$shell_dir/modules/dock/Dock.qml" body="$shell_dir/modules/dock/DockBody.qml" editor="$shell_dir/modules/settings/DockPinsEditor.qml" fail() { printf 'dock position contract: %s\n' "$1" >&2 exit 1 } for path in "$dock" "$body" "$editor"; do [[ -r "$path" ]] || fail "missing $path" done work="$(mktemp -d)" trap 'rm -rf "$work" "$shell_dir/dock-position-probe.qml"' EXIT # ── 3, 4, 6. What can be read ─────────────────────────────────────────────── grep -q 'implicitHeight: root.vertical ? 0 :' "$dock" \ || fail 'the dock sets an implicit height on both orientations, which fights the anchors' grep -q 'implicitWidth: root.vertical ? ' "$dock" \ || fail 'the dock has no implicit width for a side position' grep -q 'wanted.length === 0' "$dock" \ || fail 'an empty screen list is not treated as every screen' grep -q 'preventStealing: true' "$editor" \ || fail 'the drag grip does not set preventStealing, so the page will scroll instead of reordering' # The arrows are the keyboard-reachable path and predate the grip. A grip is not # a replacement for them. grep -q 'text: "↑"' "$editor" \ || fail 'the move-up button was removed, leaving no keyboard-reachable reorder' # ── 5. Reordering keeps every entry, exactly once ─────────────────────────── python3 - <<'PY' || fail 'reordering loses or duplicates a pinned application' def drag_to(working, dragging, target): if dragging < 0 or target == dragging: return working, dragging if target < 0 or target >= len(working): return working, dragging nxt = working[:] nxt.insert(target, nxt.pop(dragging)) return nxt, target base = ["a", "b", "c", "d", "e"] for start, targets in [(0, [1, 2, 3, 4]), (4, [3, 2, 1, 0]), (2, [3]), (2, [1]), (0, [3, 3, 3]), (1, [-1]), (3, [99])]: working, dragging = base[:], start for target in targets: working, dragging = drag_to(working, dragging, target) if sorted(working) != sorted(base) or len(working) != len(base): raise SystemExit(f'starting at {start} through {targets} produced {working}') PY # ── 1, 2. Geometry, measured ──────────────────────────────────────────────── cat >"$shell_dir/dock-position-probe.qml" <<'QML' import Quickshell import QtQuick import qs.modules.dock ShellRoot { Dock { id: probe } Timer { interval: 1200; running: true onTriggered: { console.warn("DOCKGEOM " + probe.position + " vertical=" + probe.vertical + " w=" + Math.round(probe.width) + " h=" + Math.round(probe.height)); Qt.quit(); } } } QML settings_source="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json" [[ -r "$settings_source" ]] || fail 'no settings to build a probe configuration from' measure() { local position="$1" python3 - "$settings_source" "$work/panama/settings.json" "$position" <<'PY' import json, pathlib, sys data = json.loads(pathlib.Path(sys.argv[1]).read_text()) data["dockPosition"] = sys.argv[3] out = pathlib.Path(sys.argv[2]) out.parent.mkdir(parents=True, exist_ok=True) out.write_text(json.dumps(data)) PY ( cd "$shell_dir" && XDG_CONFIG_HOME="$work" timeout 40 qs -p ./dock-position-probe.qml 2>&1 ) \ | grep -o 'DOCKGEOM .*' | head -1 } bottom="$(measure bottom)" [[ -n "$bottom" ]] || fail 'the bottom dock produced no geometry at all' left="$(measure left)" [[ -n "$left" ]] || fail 'the left dock produced no geometry at all' python3 - "$bottom" "$left" <<'PY' || fail 'a dock does not span the edge it lives on' import re, sys def read(line): m = re.search(r'DOCKGEOM (\w+) vertical=(\w+) w=(\d+) h=(\d+)', line) if not m: raise SystemExit(f'unreadable probe output: {line!r}') return m.group(1), m.group(2) == 'true', int(m.group(3)), int(m.group(4)) _, bottom_vertical, bottom_w, bottom_h = read(sys.argv[1]) _, left_vertical, left_w, left_h = read(sys.argv[2]) if bottom_vertical: raise SystemExit('the bottom dock reports itself as vertical') if not left_vertical: raise SystemExit('the left dock does not report itself as vertical') # The bug this exists for: a side dock anchored at one end only collapses to a # sliver on the axis it should span. if left_h <= bottom_h: raise SystemExit(f'the left dock is {left_h}px tall and does not span the screen') if bottom_w <= left_w: raise SystemExit(f'the bottom dock is {bottom_w}px wide and does not span the screen') if left_w >= bottom_w or bottom_h >= left_h: raise SystemExit('the two orientations are not thin on opposite axes') PY printf 'dock position contract: ok\n'