Open a new one with SUPER, go to the old one with SUPER+ALT
Making the plain application keys focus an existing window was the wrong call. It reads well in a demo and it is what macOS does, but it made "give me another terminal" the awkward case -- and on a tiling desktop a second terminal beside the first is the normal way to work, not an edge case. Reaching for the launcher to open a second file manager is not an improvement on anything. So the plain keys do what they always did, and SUPER+ALT is the new capability rather than a tax on the old one: go to the terminal, editor, browser, files, calculator or mail you already have, wherever it is, and start one only if there is none. ALT rather than SHIFT because SUPER+SHIFT is already the window-manipulation space -- Files, Neovim and Settings would have collided with Focus session, Taller and Shorter, and breaking two keys out of the eight-key resize set to make room is the worse trade. Also fixes a real trap found while using it. The Alt-Tab overlay commits on SUPER release, which is a compositor bind running an IPC call; if that call ever fails to land, the overlay stayed up with no keyboard focus, no Escape handler and nothing clickable, so the only way out was an IPC call typed into a terminal it was covering. Clicking outside now dismisses it, clicking a row switches to that window -- which is the obvious thing to try and did nothing -- and an abandoned switch closes itself after ten seconds. Keyboard focus still stays with the compositor, because taking it mid-switch is what would break stepping.
This commit is contained in:
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The Alt-Tab overlay, and getting out of it.
|
||||
#
|
||||
# The gesture is driven by compositor binds: SUPER+Tab steps, and a bind on
|
||||
# SUPER RELEASE commits by running `qs ipc call switcher commit`. That works,
|
||||
# but it has a failure mode nobody had thought about -- if the commit call ever
|
||||
# fails to land, the overlay stayed up with no keyboard focus, no Escape
|
||||
# handler and nothing clickable. The only way out was an IPC call typed into a
|
||||
# terminal the overlay was covering.
|
||||
#
|
||||
# So the properties here are about recovery, not about the happy path:
|
||||
#
|
||||
# 1. Clicking outside the card puts it away.
|
||||
# 2. Clicking a row switches to that window. It is the obvious thing to try
|
||||
# with a list of windows on screen, and it did nothing.
|
||||
# 3. An abandoned switch closes itself. A lost commit should cost a pause,
|
||||
# not the session.
|
||||
# 4. Keyboard focus stays with the compositor. Taking it mid-switch is the
|
||||
# one thing that would break stepping, so the fixes above must not.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
window="$repo_dir/config/dot/quickshell/modules/switcher/WindowSwitcher.qml"
|
||||
state="$repo_dir/config/dot/quickshell/services/WindowSwitcherState.qml"
|
||||
|
||||
findings=()
|
||||
note() { findings+=("$1"); }
|
||||
|
||||
[[ -r "$window" ]] || { printf 'switcher contract: %s is missing\n' "$window" >&2; exit 1; }
|
||||
|
||||
# ── 1 & 2. It responds to a pointer ─────────────────────────────────────────
|
||||
|
||||
grep -q 'onClicked: WindowSwitcherState.cancel()' "$window" \
|
||||
|| note 'clicking outside the switcher does not dismiss it'
|
||||
grep -q 'onClicked: WindowSwitcherState.selectAt' "$window" \
|
||||
|| note 'clicking a window in the list does not switch to it'
|
||||
grep -q 'function selectAt' "$state" \
|
||||
|| note 'the switcher cannot commit to a specific entry, only to the one stepping landed on'
|
||||
|
||||
# selectAt must refuse an index that is not there, or a stale click after the
|
||||
# list shrinks would activate whatever happens to be at that position.
|
||||
grep -q 'position >= root.windows.length' "$state" \
|
||||
|| note 'selectAt does not bound-check its index'
|
||||
|
||||
# ── 3. An abandoned switch closes itself ────────────────────────────────────
|
||||
|
||||
python3 - "$window" <<'PY' || note 'there is no timeout, so a lost commit leaves the overlay up forever'
|
||||
import re, sys
|
||||
text = open(sys.argv[1], encoding="utf-8").read()
|
||||
match = re.search(r"Timer\s*\{[^}]*running:\s*WindowSwitcherState\.open(.*?)\}", text, re.S)
|
||||
if not match:
|
||||
raise SystemExit(1)
|
||||
interval = re.search(r"interval:\s*(\d+)", match.group(1))
|
||||
if not interval:
|
||||
raise SystemExit(1)
|
||||
value = int(interval.group(1))
|
||||
# Long enough that a deliberate pause mid-gesture is not cut short, short
|
||||
# enough that a stuck overlay is an annoyance rather than a reason to reboot.
|
||||
if not (3000 <= value <= 30000):
|
||||
print(f"the switcher timeout is {value}ms", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
|
||||
grep -q 'onTriggered: WindowSwitcherState.cancel()' "$window" \
|
||||
|| note 'the switcher timeout does not cancel the switch'
|
||||
|
||||
# ── 4. The gesture itself is untouched ──────────────────────────────────────
|
||||
|
||||
grep -q 'WlrKeyboardFocus.None' "$window" \
|
||||
|| note 'the switcher takes keyboard focus, which breaks stepping mid-switch'
|
||||
|
||||
# ── Live ────────────────────────────────────────────────────────────────────
|
||||
|
||||
if command -v qs >/dev/null 2>&1 && qs ipc call switcher cancel >/dev/null 2>&1; then
|
||||
qs ipc call switcher next >/dev/null 2>&1
|
||||
sleep 0.6
|
||||
mapped="$(hyprctl layers -j 2>/dev/null | grep -c 'qs-switcher' || true)"
|
||||
(( mapped > 0 )) || note 'stepping did not map the switcher'
|
||||
|
||||
qs ipc call switcher commit >/dev/null 2>&1
|
||||
sleep 0.6
|
||||
mapped="$(hyprctl layers -j 2>/dev/null | grep -c 'qs-switcher' || true)"
|
||||
(( mapped == 0 )) || note 'committing did not close the switcher'
|
||||
|
||||
# Leave nothing behind whatever happened above.
|
||||
qs ipc call switcher cancel >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
if (( ${#findings[@]} > 0 )); then
|
||||
printf 'switcher contract: %d finding(s)\n' "${#findings[@]}" >&2
|
||||
printf ' - %s\n' "${findings[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'switcher contract: PASS\n'
|
||||
Reference in New Issue
Block a user