From 43d3dd1af7195637bbc6f9c6ffa97ae11d165855 Mon Sep 17 00:00:00 2001 From: mantisdotdev <306652121+mantisdotdev@users.noreply.github.com> Date: Tue, 1 Sep 2026 03:25:24 +0530 Subject: [PATCH] Show Apple symbols in keybinding menus --- bin/omarchy-menu-herdr-keybindings | 20 ++++++- bin/omarchy-menu-keybindings | 35 +++++++++++- bin/omarchy-menu-tmux-keybindings | 20 +++++-- test/shell.d/keybindings-menu-test.sh | 36 +++++++++--- test/shell.d/mac-keybinding-labels-test.sh | 62 +++++++++++++++++++++ test/shell.d/menu-keybindings-fkeys-test.sh | 16 +++--- 6 files changed, 162 insertions(+), 27 deletions(-) create mode 100644 test/shell.d/mac-keybinding-labels-test.sh diff --git a/bin/omarchy-menu-herdr-keybindings b/bin/omarchy-menu-herdr-keybindings index 7b293605d43..64ddbbc046e 100755 --- a/bin/omarchy-menu-herdr-keybindings +++ b/bin/omarchy-menu-herdr-keybindings @@ -93,7 +93,13 @@ function key_text(key, count, parts, i, text) { text = "" for (i = 1; i <= count; i++) { - text = text (text == "" ? "" : " + ") toupper(parts[i]) + part = toupper(parts[i]) + if (part == "SUPER") part = "⌘" + else if (part == "ALT") part = "⌥" + else if (part == "CTRL" || part == "CONTROL") part = "⌃" + else if (part == "SHIFT") part = "⇧" + + text = text (text == "" ? "" : " + ") part } return text @@ -134,6 +140,14 @@ function describe(action, text) { return text } +function padded(text, width, measured) { + measured = text + gsub(/⌘|⌥|⌃|⇧/, "x", measured) + if (length(measured) >= width) return text + + return text sprintf("%*s", width - length(measured), "") +} + function remember(action) { if (action in seen) return seen[action] = 1 @@ -200,7 +214,7 @@ FNR == NR { END { if ("prefix" in combos) { - printf "%-32s → %s\n", "PREFIX", combos["prefix"] + printf "%s → %s\n", padded("PREFIX", 32), combos["prefix"] } for (i = 1; i <= count; i++) { @@ -208,7 +222,7 @@ END { if (action == "prefix") continue if (!(action in combos)) continue - printf "%-32s → %s\n", combos[action], describe(action) + printf "%s → %s\n", padded(combos[action], 32), describe(action) } } ' - "$config_file" diff --git a/bin/omarchy-menu-keybindings b/bin/omarchy-menu-keybindings index 4a5c4cc4a5b..619f5f1af80 100755 --- a/bin/omarchy-menu-keybindings +++ b/bin/omarchy-menu-keybindings @@ -575,8 +575,39 @@ output_binding_records() { fi } +format_display_records() { + awk -F '\t' ' + BEGIN { OFS = "\t" } + { + delimiter = index($1, "→") + display = substr($1, 1, delimiter - 1) + sub(/[[:space:]]+$/, "", display) + + count = split(display, parts, /[[:space:]]+/) + display = "" + for (i = 1; i <= count; i++) { + if (parts[i] == "SUPER") parts[i] = "⌘" + else if (parts[i] == "ALT") parts[i] = "⌥" + else if (parts[i] == "CTRL" || parts[i] == "CONTROL") parts[i] = "⌃" + else if (parts[i] == "SHIFT") parts[i] = "⇧" + + display = display (display == "" ? "" : " ") parts[i] + } + + action = substr($1, delimiter + length("→")) + sub(/^[[:space:]]+/, "", action) + measured = display + gsub(/⌘|⌥|⌃|⇧/, "x", measured) + padding = 35 - length(measured) + if (padding > 0) display = display sprintf("%*s", padding, "") + + $1 = display " → " action + print + }' +} + output_keybindings() { - output_binding_records | cut -f1 + output_binding_records | format_display_records | cut -f1 } trim() { @@ -661,7 +692,7 @@ dispatch_binding() { if [[ $1 == "--print" || $1 == "-p" ]]; then output_keybindings else - records=$(output_binding_records) + records=$(output_binding_records | format_display_records) selection=$(cut -f1 <<<"$records" | omarchy-menu-select 'Keybindings' -- --width 800 --height 500) diff --git a/bin/omarchy-menu-tmux-keybindings b/bin/omarchy-menu-tmux-keybindings index 8f493ea4e8b..4aa1a8a1c7d 100755 --- a/bin/omarchy-menu-tmux-keybindings +++ b/bin/omarchy-menu-tmux-keybindings @@ -53,12 +53,12 @@ output_keybindings() ( function key_part_text(part, is_modifier, part_count) { gsub(/^\\/, "", part) - if (is_modifier && part == "C") return "CTRL" - if (is_modifier && part == "M") return "ALT" - if (is_modifier && part == "S") return "SHIFT" + if (is_modifier && part == "C") return "⌃" + if (is_modifier && part == "M") return "⌥" + if (is_modifier && part == "S") return "⇧" if (part == "Space") return "SPACE" if (part == "BSpace") return "BACKSPACE" - if (part == "BTab") return "SHIFT + TAB" + if (part == "BTab") return "⇧ + TAB" if (part == "PPage") return "PAGE UP" if (part == "NPage") return "PAGE DOWN" if (part == "DC") return "DELETE" @@ -90,6 +90,14 @@ function table_text(table) { return toupper(table) } +function padded(text, width, measured) { + measured = text + gsub(/⌘|⌥|⌃|⇧/, "x", measured) + if (length(measured) >= width) return text + + return text sprintf("%*s", width - length(measured), "") +} + $1 == "prefix" && NF == 2 { prefix = $2; next } $1 == "prefix2" && NF == 2 { prefix2 = $2; next } @@ -101,7 +109,7 @@ $1 == "prefix2" && NF == 2 { prefix2 = $2; next } prefix_description = prefix_description " / " key_text(prefix2) } - printf "%-32s → %s\n", "PREFIX", prefix_description + printf "%s → %s\n", padded("PREFIX", 32), prefix_description header_printed = 1 } @@ -116,7 +124,7 @@ $1 == "prefix2" && NF == 2 { prefix2 = $2; next } combo = table_text(table) " + " key_text(key) } - printf "%-32s → %s\n", combo, note + printf "%s → %s\n", padded(combo, 32), note }' ) diff --git a/test/shell.d/keybindings-menu-test.sh b/test/shell.d/keybindings-menu-test.sh index 3e0da2884cd..2e908bdca49 100755 --- a/test/shell.d/keybindings-menu-test.sh +++ b/test/shell.d/keybindings-menu-test.sh @@ -61,24 +61,24 @@ BINDS rendered=$(keybindings) [[ -n $rendered ]] || fail "the keybindings menu renders with a stubbed Hyprland" -grep -q 'SUPER + F *→ Full screen' <<<"$rendered" || +grep -q '⌘ + F *→ Full screen' <<<"$rendered" || fail "a chord with no alternative renders on its own" "$rendered" pass "the keybindings menu renders its entries" (( $(grep -c '→ Close window$' <<<"$rendered") == 1 )) || fail "an alternative chord joins the row of the first one" "$rendered" -grep -q 'SUPER + W / SUPER + Q *→ Close window' <<<"$rendered" || +grep -q '⌘ + W / ⌘ + Q *→ Close window' <<<"$rendered" || fail "a shared row names both chords" "$rendered" pass "an alternative chord joins the row of the first one" # Which chord leads is the whole point of keeping Hyprland's order: SUPER + W is # the documented default and SUPER + Q the alternative bound after it. -grep -q '^SUPER + W / SUPER + Q' <<<"$rendered" || +grep -q '^⌘ + W / ⌘ + Q' <<<"$rendered" || fail "the chord declared first leads a shared row" "$rendered" pass "the chord declared first leads a shared row" # Hyprland calls the key left of 1 "grave". Nobody reads their keyboard that way. -grep -q 'SUPER + S / SUPER + ~ *→ Toggle scratchpad' <<<"$rendered" || +grep -q '⌘ + S / ⌘ + ~ *→ Toggle scratchpad' <<<"$rendered" || fail "the grave key reads as the symbol printed on it" "$rendered" ! grep -q 'grave' <<<"$rendered" || fail "no entry still says grave" "$rendered" @@ -86,14 +86,14 @@ pass "the grave key reads as the symbol printed on it" # Monospace menu: every arrow sits in one column, and nothing is allowed past # it. A row that overruns pushes its own arrow out of line. -[[ $(awk -F '→' '{ print length($1) }' <<<"$rendered" | sort -u) == "36" ]] || +[[ $(sed 's/⌘/x/g; s/⌥/x/g; s/⌃/x/g; s/⇧/x/g' <<<"$rendered" | awk -F '→' '{ print length($1) }' | sort -u) == "36" ]] || fail "every entry pads its chords to the same column" "$rendered" pass "every entry pads its chords to the same column" # The menu elides a row that outgrows its card: 754px of label, 78 monospace # characters at the heading size. The longest entry Omarchy ships sits at 74, so # a row has four characters of room and no more. -(( $(awk '{ print length($0) }' <<<"$rendered" | sort -rn | head -1) <= 78 )) || +(( $(sed 's/⌘/x/g; s/⌥/x/g; s/⌃/x/g; s/⇧/x/g' <<<"$rendered" | awk '{ print length($0) }' | sort -rn | head -1) <= 78 )) || fail "no entry outgrows the width the menu gives it" "$rendered" pass "no entry outgrows the width the menu gives it" @@ -127,7 +127,7 @@ bind BINDS rendered=$(keybindings) -grep -q 'SUPER + ~ *→ Toggle scratchpad' <<<"$rendered" || +grep -q '⌘ + ~ *→ Toggle scratchpad' <<<"$rendered" || fail "a keycode resolves to the symbol printed on the key too" "$rendered" pass "a keycode resolves to the symbol printed on the key too" @@ -167,7 +167,7 @@ BINDS rendered=$(keybindings) (( $(grep -c '→ Calculator$' <<<"$rendered") == 2 )) || fail "chords too wide to share a row stay on their own" "$rendered" -[[ $(awk -F '→' '{ print length($1) }' <<<"$rendered" | sort -u) == "36" ]] || +[[ $(sed 's/⌘/x/g; s/⌥/x/g; s/⌃/x/g; s/⇧/x/g' <<<"$rendered" | awk -F '→' '{ print length($1) }' | sort -u) == "36" ]] || fail "chords too wide to share a row leave the column alone" "$rendered" pass "chords too wide to share a row stay on their own" @@ -220,3 +220,23 @@ for action in "${expected_alternatives[@]}"; do fail "every action named as having an alternative is bound twice" "$action" done pass "every action named as having an alternative is bound twice" + +# A user-defined chord can exceed the standard display column. It must remain +# intact without backwards padding, and an arrow in its description belongs to +# the description rather than changing where the display formatter splits the +# record. The dispatcher fields remain attached after the display field. +stub_hyprctl <"$stub_bin/tmux" <<'TMUX' +#!/bin/bash +cat <<'OUTPUT' +prefix C-a +prefix2 M-b +prefix C-S-x Example binding +root M-y Root binding +copy-mode-vi C-z Copy binding +copy-mode-vi BTab Reverse selection +OUTPUT +TMUX +chmod +x "$stub_bin/tmux" + +touch "$tmpdir/tmux.conf" +tmux_output=$(PATH="$stub_bin:$PATH" OMARCHY_PATH="$ROOT" \ + "$ROOT/bin/omarchy-menu-tmux-keybindings" --print --config "$tmpdir/tmux.conf") + +grep -q '^PREFIX *→ ⌃ + A / ⌥ + B$' <<<"$tmux_output" || + fail "Tmux prefix modifiers use Apple symbols" "$tmux_output" +grep -qF 'PREFIX + ⌃ + ⇧ + X' <<<"$tmux_output" || + fail "Tmux binding modifiers use Apple symbols" "$tmux_output" +grep -qF '⌥ + Y' <<<"$tmux_output" || + fail "Tmux root modifiers use Apple symbols" "$tmux_output" +grep -qF 'COPY MODE + ⇧ + TAB' <<<"$tmux_output" || + fail "Tmux BTab uses the Apple Shift symbol" "$tmux_output" +pass "Tmux keybindings use Apple modifier symbols" + +cat >"$stub_bin/herdr" <<'HERDR' +#!/bin/bash +cat <<'OUTPUT' +#[keys] +# prefix = "control+a" +# navigate_left = "super+alt+shift+h" +# close = "ctrl+w" +# toggle = ["super+control+shift+alt+a", "super+control+shift+alt+b", "super+control+shift+alt+c", "super+control+shift+alt+d"] +OUTPUT +HERDR +chmod +x "$stub_bin/herdr" + +herdr_output=$(PATH="$stub_bin:$PATH" \ + "$ROOT/bin/omarchy-menu-herdr-keybindings" --print --config /dev/null) + +grep -q '^PREFIX *→ ⌃ + A$' <<<"$herdr_output" || + fail "Herdr prefix modifiers use Apple symbols" "$herdr_output" +grep -qF 'NAVIGATE + ⌘ + ⌥ + ⇧ + H' <<<"$herdr_output" || + fail "Herdr navigation modifiers use Apple symbols" "$herdr_output" +grep -qF '⌃ + W' <<<"$herdr_output" || + fail "Herdr CTRL aliases use the Control symbol" "$herdr_output" +grep -qF '⌘ + ⌃ + ⇧ + ⌥ + A / ⌘ + ⌃ + ⇧ + ⌥ + B / ⌘ + ⌃ + ⇧ + ⌥ + C / ⌘ + ⌃ + ⇧ + ⌥ + D → Toggle' <<<"$herdr_output" || + fail "Herdr chords longer than the display column are not padded backwards" "$herdr_output" +pass "Herdr keybindings use Apple modifier symbols" diff --git a/test/shell.d/menu-keybindings-fkeys-test.sh b/test/shell.d/menu-keybindings-fkeys-test.sh index a57a08edeae..12d6cc7fb7d 100644 --- a/test/shell.d/menu-keybindings-fkeys-test.sh +++ b/test/shell.d/menu-keybindings-fkeys-test.sh @@ -98,19 +98,19 @@ EOF output=$(PATH="$mock_bin:$PATH" XDG_CACHE_HOME="$cache_dir" "$ROOT/bin/omarchy-menu-keybindings" --print) -tr -s ' ' <<<"$output" | grep -qF 'SHIFT + F2 → Keyboard brightness up' || \ - fail "SHIFT + XF86MonBrightnessUp renders as SHIFT + F2 with its description" "$output" +tr -s ' ' <<<"$output" | grep -qF '⇧ + F2 → Keyboard brightness up' || \ + fail "SHIFT + XF86MonBrightnessUp renders as ⇧ + F2 with its description" "$output" -tr -s ' ' <<<"$output" | grep -qF 'SHIFT + F1 → Keyboard brightness down' || \ - fail "SHIFT + XF86MonBrightnessDown renders as SHIFT + F1 with its description" "$output" +tr -s ' ' <<<"$output" | grep -qF '⇧ + F1 → Keyboard brightness down' || \ + fail "SHIFT + XF86MonBrightnessDown renders as ⇧ + F1 with its description" "$output" -tr -s ' ' <<<"$output" | grep -qF 'ALT + F2 → Brightness up precise' || \ - fail "modifier combos keep the F-key name (ALT + F2)" "$output" +tr -s ' ' <<<"$output" | grep -qF '⌥ + F2 → Brightness up precise' || \ + fail "modifier combos keep the F-key name (⌥ + F2)" "$output" grep -qF 'XF86MonBrightness' <<<"$output" && \ fail "raw XF86MonBrightness symbols no longer leak into the menu" "$output" -tr -s ' ' <<<"$output" | grep -qF 'SUPER + Q → Close window' || \ - fail "unrelated bindings render unchanged (SUPER + Q)" "$output" +tr -s ' ' <<<"$output" | grep -qF '⌘ + Q → Close window' || \ + fail "unrelated bindings use the Apple Command symbol (⌘ + Q)" "$output" pass "keybindings menu shows Mac physical key names (F1/F2), stale caches ignored"