mirror of
https://github.com/basecamp/omarchy.git
synced 2026-01-09 05:10:54 +08:00
246 lines
7.5 KiB
Bash
Executable File
246 lines
7.5 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# 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}"
|
|
elif [[ "$line" =~ mouse:([0-9]+) ]]; then
|
|
code="${BASH_REMATCH[1]}"
|
|
|
|
case "$code" in
|
|
272) symbol="LEFT MOUSE BUTTON" ;;
|
|
273) symbol="RIGHT MOUSE BUTTON" ;;
|
|
274) symbol="MIDDLE MOUSE BUTTON" ;;
|
|
*) symbol="mouse:${code}" ;;
|
|
esac
|
|
|
|
echo "${line/mouse:${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:
|
|
# - Remove standard Omarchy bin path prefix
|
|
# - Remove uwsm prefix
|
|
# - Map numeric modifier key mask to a textual rendition
|
|
# - Output comma-separated values that the parser can understand
|
|
dynamic_bindings() {
|
|
hyprctl -j binds |
|
|
jq -r '.[] | {modmask, key, keycode, description, dispatcher, arg} | "\(.modmask),\(.key)@\(.keycode),\(.description),\(.dispatcher),\(.arg)"' |
|
|
sed -r \
|
|
-e 's/null//' \
|
|
-e 's,~/.local/share/omarchy/bin/,,' \
|
|
-e 's,uwsm app -- ,,' \
|
|
-e 's,uwsm-app -- ,,' \
|
|
-e 's/@0//' \
|
|
-e 's/,@/,code:/' \
|
|
-e 's/^0,/,/' \
|
|
-e 's/^1,/SHIFT,/' \
|
|
-e 's/^4,/CTRL,/' \
|
|
-e 's/^5,/SHIFT CTRL,/' \
|
|
-e 's/^8,/ALT,/' \
|
|
-e 's/^9,/SHIFT ALT,/' \
|
|
-e 's/^12,/CTRL ALT,/' \
|
|
-e 's/^13,/SHIFT CTRL ALT,/' \
|
|
-e 's/^64,/SUPER,/' \
|
|
-e 's/^65,/SUPER SHIFT,/' \
|
|
-e 's/^68,/SUPER CTRL,/' \
|
|
-e 's/^69,/SUPER SHIFT CTRL,/' \
|
|
-e 's/^72,/SUPER ALT,/' \
|
|
-e 's/^73,/SUPER SHIFT ALT,/' \
|
|
-e 's/^76,/SUPER CTRL ALT,/' \
|
|
-e 's/^77,/SUPER SHIFT CTRL ALT,/'
|
|
}
|
|
|
|
# Hardcoded bindings, like the copy-url extension and such
|
|
static_bindings() {
|
|
echo "SHIFT ALT,L,Copy URL from Web App,extension,copy-url"
|
|
}
|
|
|
|
# Parse and format keybindings
|
|
#
|
|
# `awk` does the heavy lifting:
|
|
# - Set the field separator to a comma ','.
|
|
# - Joins the key combination (e.g., "SUPER + Q").
|
|
# - Joins the command that the key executes.
|
|
# - Prints everything in a nicely aligned format.
|
|
parse_bindings() {
|
|
awk -F, '
|
|
{
|
|
# Combine the modifier and key (first two fields)
|
|
key_combo = $1 " + " $2;
|
|
|
|
# Clean up: strip leading "+" if present, trim spaces
|
|
gsub(/^[ \t]*\+?[ \t]*/, "", key_combo);
|
|
gsub(/[ \t]+$/, "", key_combo);
|
|
|
|
# Use description, if set
|
|
action = $3;
|
|
|
|
if (action == "") {
|
|
# 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
|
|
|
|
# Escape XML entities
|
|
gsub(/&/, "\\&", action);
|
|
gsub(/</, "\\<", action);
|
|
gsub(/>/, "\\>", action);
|
|
gsub(/"/, "\\"", action);
|
|
gsub(/'"'"'/, "\\'", action);
|
|
}
|
|
|
|
if (action != "") {
|
|
printf "%-35s → %s\n", key_combo, action;
|
|
}
|
|
}'
|
|
}
|
|
|
|
prioritize_entries() {
|
|
awk '
|
|
{
|
|
line = $0
|
|
prio = 50
|
|
if (match(line, /Terminal/)) prio = 0
|
|
if (match(line, /Browser/) && !match(line, /Browser[[:space:]]*\(/)) prio = 1
|
|
if (match(line, /File manager/)) prio = 2
|
|
if (match(line, /Launch apps/)) prio = 3
|
|
if (match(line, /Omarchy menu/)) prio = 4
|
|
if (match(line, /System menu/)) prio = 5
|
|
if (match(line, /Theme menu/)) prio = 6
|
|
if (match(line, /Full screen/)) prio = 7
|
|
if (match(line, /Full width/)) prio = 8
|
|
if (match(line, /Close window/)) prio = 9
|
|
if (match(line, /Close all windows/)) prio = 10
|
|
if (match(line, /Lock system/)) prio = 11
|
|
if (match(line, /Toggle window floating/)) prio = 12
|
|
if (match(line, /Toggle window split/)) prio = 13
|
|
if (match(line, /Pop window/)) prio = 14
|
|
if (match(line, /Universal/)) prio = 15
|
|
if (match(line, /Clipboard/)) prio = 16
|
|
if (match(line, /Audio controls/)) prio = 17
|
|
if (match(line, /Bluetooth controls/)) prio = 18
|
|
if (match(line, /Wifi controls/)) prio = 19
|
|
if (match(line, /Emoji picker/)) prio = 20
|
|
if (match(line, /Color picker/)) prio = 21
|
|
if (match(line, /Screenshot/)) prio = 22
|
|
if (match(line, /Screenrecording/)) prio = 23
|
|
if (match(line, /(Switch|Next|Former|Previous).*workspace/)) prio = 24
|
|
if (match(line, /Move window to workspace/)) prio = 25
|
|
if (match(line, /Move window silently to workspace/)) prio = 26
|
|
if (match(line, /Swap window/)) prio = 27
|
|
if (match(line, /Move window focus/)) prio = 28
|
|
if (match(line, /Move window$/)) prio = 29
|
|
if (match(line, /Resize window/)) prio = 30
|
|
if (match(line, /Expand window/)) prio = 31
|
|
if (match(line, /Shrink window/)) prio = 32
|
|
if (match(line, /scratchpad/)) prio = 33
|
|
if (match(line, /notification/)) prio = 34
|
|
if (match(line, /Toggle window transparency/)) prio = 35
|
|
if (match(line, /Toggle workspace gaps/)) prio = 36
|
|
if (match(line, /Toggle nightlight/)) prio = 37
|
|
if (match(line, /Toggle locking/)) prio = 38
|
|
if (match(line, /group/)) prio = 94
|
|
if (match(line, /Scroll active workspace/)) prio = 95
|
|
if (match(line, /Cycle to/)) prio = 96
|
|
if (match(line, /Reveal active/)) prio = 97
|
|
if (match(line, /Apple Display/)) prio = 98
|
|
if (match(line, /XF86/)) prio = 99
|
|
|
|
# print "priority<TAB>line"
|
|
printf "%d\t%s\n", prio, line
|
|
}' |
|
|
sort -k1,1n -k2,2 |
|
|
cut -f2-
|
|
}
|
|
|
|
output_keybindings() {
|
|
build_keymap_cache
|
|
|
|
{
|
|
dynamic_bindings
|
|
static_bindings
|
|
} |
|
|
sort -u |
|
|
parse_keycodes |
|
|
parse_bindings |
|
|
prioritize_entries
|
|
}
|
|
|
|
if [[ "$1" == "--print" || "$1" == "-p" ]]; then
|
|
output_keybindings
|
|
else
|
|
monitor_height=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .height')
|
|
menu_height=$((monitor_height * 40 / 100))
|
|
|
|
output_keybindings |
|
|
walker --dmenu -p 'Keybindings' --width 800 --height "$menu_height"
|
|
fi
|
|
|