Answer "what can I press" in one keypress
The Shortcuts settings page answers "how do I change this", which is worth opening a window for. This answers the other question, the one you have with your hands already on the keyboard, so it is an overlay on SUPER + / and the same key closes it. It reads Keybinds.grouped() rather than a written-down list, so a shortcut rebound in Settings shows its new chord here with nothing kept in sync. A cheatsheet that lies is worse than none: it gets consulted exactly when somebody does not already know. Three columns, balanced by how many shortcuts each category holds. The first attempt used a Flow, which wraps into as many columns as it likes and made 120 binds across six uneven categories unreadable; it also sized the card from a child that filled it, which is a circular binding and produced a card taller than the display with its contents running off the bottom. Both were found by looking at it rather than by a test, which is the argument for looking at it. Fixes a real bug on the way past: luaChord and formatChord appended the key unconditionally, so the window switcher's modifier-only release bind became "SUPER + " with a dangling separator. That matched neither the chord keybinds.lua binds nor the one an override is keyed by, so that bind could never be rebound and had no category -- it was sitting in a seventh group of its own, which is how it was noticed.
This commit is contained in:
Executable
+125
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The cheatsheet: every shortcut, on one keypress.
|
||||
#
|
||||
# It answers a different question from the Shortcuts settings page. That page
|
||||
# is "how do I change this" and is worth opening a window for; this is "what
|
||||
# can I press", asked with your hands already on the keyboard. So the
|
||||
# properties that matter are that it is one key away, that it closes on the
|
||||
# same key, and above all that it cannot drift from the real keymap.
|
||||
#
|
||||
# That last one is why it reads Keybinds.grouped() rather than a written-down
|
||||
# list: a shortcut rebound in Settings has to show its new chord here without
|
||||
# anything being kept in sync. A cheatsheet that lies is worse than none,
|
||||
# because it is consulted precisely when somebody does not already know.
|
||||
#
|
||||
# The live half opens the real surface and checks it maps. The layout is not
|
||||
# asserted -- how many columns look right is a judgement, not a contract.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
module="$repo_dir/config/dot/quickshell/modules/cheatsheet"
|
||||
shell_qml="$repo_dir/config/dot/quickshell/shell.qml"
|
||||
state="$repo_dir/config/dot/quickshell/services/ShellState.qml"
|
||||
keybinds="$repo_dir/config/dot/hypr/keybinds.lua"
|
||||
rules="$repo_dir/config/dot/hypr/rules.lua"
|
||||
command_file="$repo_dir/config/local/share/vicinae/scripts/keyboard-shortcuts"
|
||||
|
||||
findings=()
|
||||
note() { findings+=("$1"); }
|
||||
|
||||
# ── The module ───────────────────────────────────────────────────────────────
|
||||
|
||||
for file in Cheatsheet.qml CheatsheetGroup.qml qmldir; do
|
||||
[[ -r "$module/$file" ]] || note "the cheatsheet module has no $file"
|
||||
done
|
||||
grep -q '^Cheatsheet 1.0 Cheatsheet.qml$' "$module/qmldir" \
|
||||
|| note 'Cheatsheet is not registered in its qmldir, so the shell would fail to load entirely'
|
||||
grep -q '^CheatsheetGroup 1.0 CheatsheetGroup.qml$' "$module/qmldir" \
|
||||
|| note 'CheatsheetGroup is not registered in its qmldir'
|
||||
|
||||
# ── It reads the live keymap ─────────────────────────────────────────────────
|
||||
|
||||
grep -q 'Keybinds.grouped()' "$module/Cheatsheet.qml" \
|
||||
|| note 'the cheatsheet does not read the live keymap, so it can drift from what the keys actually do'
|
||||
grep -q 'Keybinds.refresh()' "$module/Cheatsheet.qml" \
|
||||
|| note 'the cheatsheet does not re-read the keymap when opened, so a rebind would not show until the shell restarted'
|
||||
|
||||
# ── Wiring ───────────────────────────────────────────────────────────────────
|
||||
|
||||
grep -q 'import qs.modules.cheatsheet' "$shell_qml" \
|
||||
|| note 'shell.qml does not import the cheatsheet module'
|
||||
grep -qE '^\s*Cheatsheet \{\}' "$shell_qml" \
|
||||
|| note 'shell.qml never instantiates the cheatsheet'
|
||||
grep -q 'target: "cheatsheet"' "$shell_qml" \
|
||||
|| note 'there is no cheatsheet IPC target'
|
||||
grep -q 'cheatsheetOpen: activeOverlay === "cheatsheet"' "$state" \
|
||||
|| note 'ShellState does not track the cheatsheet, so it would not close when another overlay opens'
|
||||
grep -q '"cheatsheet"' <(grep 'Exactly one of these' -A2 "$state") \
|
||||
|| note 'the ShellState overlay comment does not list the cheatsheet'
|
||||
|
||||
# The namespace has to match a layer rule or the surface gets no blur. The
|
||||
# popover rule matches by prefix, which is why this name was chosen.
|
||||
grep -q 'qs-popover-cheatsheet' "$module/Cheatsheet.qml" \
|
||||
|| note 'the cheatsheet does not use a namespace beginning qs-popover, so it would be drawn without blur'
|
||||
grep -q "namespace = \"^qs-popover\"" "$rules" \
|
||||
|| note 'the qs-popover layer rule no longer matches by prefix, so the cheatsheet lost its blur'
|
||||
|
||||
# ── One key, and the same key closes it ──────────────────────────────────────
|
||||
|
||||
grep -q 'qs("cheatsheet", "toggle")' "$keybinds" \
|
||||
|| note 'no keybind opens the cheatsheet, or it does not toggle so the same key cannot close it'
|
||||
grep -qE 'bind\(mod \.\. " \+ slash".*description' "$keybinds" \
|
||||
|| note 'the cheatsheet bind carries no description, which drops it from the keymap it is meant to show'
|
||||
|
||||
[[ -x "$command_file" ]] || note 'there is no launcher command for the cheatsheet'
|
||||
|
||||
# ── Live ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
if ! command -v qs >/dev/null 2>&1 || ! qs ipc call cheatsheet status >/dev/null 2>&1; then
|
||||
if (( ${#findings[@]} > 0 )); then
|
||||
printf 'cheatsheet contract: %d finding(s)\n' "${#findings[@]}" >&2
|
||||
printf ' - %s\n' "${findings[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'cheatsheet contract: PASS (static; no running shell)\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
was_open="$(qs ipc call cheatsheet status | jq -r .open)"
|
||||
|
||||
qs ipc call cheatsheet open >/dev/null
|
||||
sleep 1
|
||||
[[ "$(qs ipc call cheatsheet status | jq -r .open)" == "true" ]] \
|
||||
|| note 'the cheatsheet did not open'
|
||||
hyprctl layers -j | grep -q 'qs-popover-cheatsheet' \
|
||||
|| note 'the cheatsheet reports open but its layer never mapped, so nothing is on screen'
|
||||
|
||||
# Toggling closes it, which is what makes the opening chord also the closing one.
|
||||
qs ipc call cheatsheet toggle >/dev/null
|
||||
sleep 1
|
||||
[[ "$(qs ipc call cheatsheet status | jq -r .open)" == "false" ]] \
|
||||
|| note 'toggling an open cheatsheet did not close it'
|
||||
|
||||
status="$(qs ipc call cheatsheet status)"
|
||||
groups="$(jq -r '.groups | length' <<<"$status")"
|
||||
(( groups >= 4 )) || note "the cheatsheet shows $groups categories; the keymap has six"
|
||||
(( groups <= 8 )) || note "the cheatsheet shows $groups categories, which suggests grouping fell back to guesswork"
|
||||
|
||||
# Every bind is categorised by hypr/keybinds.lua. One that is not falls back to
|
||||
# substring derivation and lands in a group of its own, which is how the
|
||||
# modifier-only switcher bind was found sitting in a seventh category by itself.
|
||||
uncategorised="$(jq -r '.uncategorised | join(", ")' <<<"$status")"
|
||||
[[ -z "$uncategorised" ]] \
|
||||
|| note "these binds carry no authored category: $uncategorised"
|
||||
|
||||
[[ "$was_open" == "true" ]] && qs ipc call cheatsheet open >/dev/null
|
||||
|
||||
if (( ${#findings[@]} > 0 )); then
|
||||
printf 'cheatsheet contract: %d finding(s)\n' "${#findings[@]}" >&2
|
||||
printf ' - %s\n' "${findings[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'cheatsheet contract: PASS\n'
|
||||
@@ -77,7 +77,7 @@ declare -a standalone=(
|
||||
lock-screen suspend-system log-out reboot-system power-off
|
||||
remind-me list-reminders pick-color
|
||||
switch-window force-quit-window kill-process ssh-hosts recent-files
|
||||
copy-password
|
||||
copy-password keyboard-shortcuts
|
||||
)
|
||||
|
||||
# Generated commands must match their source. A stale command dispatches to a
|
||||
|
||||
Reference in New Issue
Block a user