diff --git a/bin/omarchy-menu-keybindings b/bin/omarchy-menu-keybindings index ab13025b..cd1a9b54 100755 --- a/bin/omarchy-menu-keybindings +++ b/bin/omarchy-menu-keybindings @@ -3,6 +3,69 @@ # A script to display Hyprland keybindings defined in your configuration # using walker for an interactive search menu. +declare -A KEYCODE_SYM_MAP + +build_keymap_cache() { + local keymap + keymap="$(xkbcli compile-keymap)" || { + echo "Failed to compile keymap" >&2 + return 1 + } + + while IFS=, read -r code sym; do + [[ -z "$code" || -z "$sym" ]] && continue + KEYCODE_SYM_MAP["$code"]="$sym" + done < <( + awk ' + BEGIN { sec = "" } + /xkb_keycodes/ { sec = "codes"; next } + /xkb_symbols/ { sec = "syms"; next } + sec == "codes" { + if (match($0, /<([A-Za-z0-9_]+)>\s*=\s*([0-9]+)\s*;/, m)) code_by_name[m[1]] = m[2] + } + sec == "syms" { + if (match($0, /key\s*<([A-Za-z0-9_]+)>\s*\{\s*\[\s*([^, \]]+)/, m)) sym_by_name[m[1]] = m[2] + } + END { + for (k in code_by_name) { + c = code_by_name[k] + s = sym_by_name[k] + if (c != "" && s != "" && s != "NoSymbol") print c "," s + } + } + ' <<<"$keymap" + ) +} + +lookup_keycode_cached() { + printf '%s\n' "${KEYCODE_SYM_MAP[$1]}" +} + +parse_keycodes() { + local start end elapsed + [[ "${DEBUG:-0}" == "1" ]] && start=$(date +%s.%N) + while IFS= read -r line; do + if [[ "$line" =~ code:([0-9]+) ]]; then + code="${BASH_REMATCH[1]}" + symbol=$(lookup_keycode_cached "$code" "$XKB_KEYMAP_CACHE") + echo "${line/code:${code}/$symbol}" + else + echo "$line" + fi + done + + if [[ "$DEBUG" == "1" ]]; then + end=$(date +%s.%N) + # fall back to awk if bc is missing + if command -v bc >/dev/null 2>&1; then + elapsed=$(echo "$end - $start" | bc) + else + elapsed=$(awk -v s="$start" -v e="$end" 'BEGIN{printf "%.6f", (e - s)}') + fi + echo "[DEBUG] parse_keycodes elapsed: ${elapsed}s" >&2 + fi +} + # Fetch dynamic keybindings from Hyprland # # Also do some pre-processing: @@ -19,21 +82,7 @@ dynamic_bindings() { -e 's,uwsm app -- ,,' \ -e 's/@0//' \ -e 's/,@/,code:/' \ - -e 's/,code:10,/,1,/' \ - -e 's/,code:11,/,2,/' \ - -e 's/,code:12,/,3,/' \ - -e 's/,code:13,/,4,/' \ - -e 's/,code:14,/,5,/' \ - -e 's/,code:15,/,6,/' \ - -e 's/,code:16,/,7,/' \ - -e 's/,code:17,/,8,/' \ - -e 's/,code:18,/,9,/' \ - -e 's/,code:19,/,0,/' \ - -e 's/,code:20,/,-,/' \ - -e 's/,code:21,/,=,/' \ -e 's/^0,/,/' \ - -e 's/,mouse:272,/,MOUSE LEFT,/' \ - -e 's/,mouse:273,/,MOUSE RIGHT,/' \ -e 's/^1,/SHIFT,/' \ -e 's/^4,/CTRL,/' \ -e 's/^5,/SHIFT CTRL,/' \ @@ -60,7 +109,7 @@ dynamic_bindings() { # - Prints everything in a nicely aligned format. parse_bindings() { awk -F, ' - { +{ # Combine the modifier and key (first two fields) key_combo = $1 " + " $2; @@ -72,35 +121,38 @@ parse_bindings() { action = $3; if (action == "") { - # Reconstruct the command from the remaining fields - for (i = 4; i <= NF; i++) { - action = action $i (i < NF ? "," : ""); - } + # Reconstruct the command from the remaining fields + for (i = 4; i <= NF; i++) { + action = action $i (i < NF ? "," : ""); + } - # Clean up trailing commas, remove leading "exec, ", and trim - sub(/,$/, "", action); - gsub(/(^|,)[[:space:]]*exec[[:space:]]*,?/, "", action); - gsub(/^[ \t]+|[ \t]+$/, "", action); - gsub(/[ \t]+/, " ", key_combo); # Collapse multiple spaces to one + # Clean up trailing commas, remove leading "exec, ", and trim + sub(/,$/, "", action); + gsub(/(^|,)[[:space:]]*exec[[:space:]]*,?/, "", action); + gsub(/^[ \t]+|[ \t]+$/, "", action); + gsub(/[ \t]+/, " ", key_combo); # Collapse multiple spaces to one - # Escape XML entities - gsub(/&/, "\\&", action); - gsub(//, "\\>", action); - gsub(/"/, "\\"", action); - gsub(/'"'"'/, "\\'", action); + # Escape XML entities + gsub(/&/, "\\&", action); + gsub(//, "\\>", action); + gsub(/"/, "\\"", action); + gsub(/'"'"'/, "\\'", action); } - if (action != "") { - printf "%-35s → %s\n", key_combo, action; - } + if (action != "") { + printf "%-35s → %s\n", key_combo, action; + } }' } monitor_height=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .height') menu_height=$((monitor_height * 40 / 100)) +build_keymap_cache + dynamic_bindings | sort -u | + parse_keycodes | parse_bindings | walker --dmenu --theme keybindings -p 'Keybindings' -w 800 -h "$menu_height"