Collapse keymap resolution and prune keybindings menu
The keymap cache parsed xkbcli compile-keymap into a bash associative array, serialized that array back into a string, and re-parsed it inside a second awk's BEGIN block. One awk now reads the compiled keymap directly, seeded with the fallback table so xkbcli failure still degrades gracefully. (The xkbcli child must read /dev/null or it consumes the binding records arriving on stdin.) Also gone: the never-called lookup_keycode_cached, the DEBUG timing scaffold, the redundant explicit noops in the Lua mock that the metatable already provides, the walker-era XML entity escaping that nothing renders as markup anymore, and the monitor-height computation in favor of a fixed height like every other menu. The cache key drops the double sha256 and now includes the active keymap, so switching XKB layout invalidates cached symbol names instead of serving stale ones. Binding records are byte-identical before and after: 215 of 215. 686 lines to 573. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
fef59cece8
commit
54d9cdc328
+32
-145
@@ -2,27 +2,6 @@
|
|||||||
|
|
||||||
# omarchy:summary=Display Hyprland keybindings defined in your configuration using an interactive search menu.
|
# omarchy:summary=Display Hyprland keybindings defined in your configuration using an interactive search menu.
|
||||||
|
|
||||||
declare -A KEYCODE_SYM_MAP
|
|
||||||
# Hyprland reports XKB keycodes for code: bindings. Keep a small fallback for
|
|
||||||
# common keys so the menu remains readable if xkbcli cannot resolve a symbol.
|
|
||||||
declare -A FALLBACK_KEYCODE_SYM_MAP=(
|
|
||||||
[10]="1"
|
|
||||||
[11]="2"
|
|
||||||
[12]="3"
|
|
||||||
[13]="4"
|
|
||||||
[14]="5"
|
|
||||||
[15]="6"
|
|
||||||
[16]="7"
|
|
||||||
[17]="8"
|
|
||||||
[18]="9"
|
|
||||||
[19]="0"
|
|
||||||
[20]="MINUS"
|
|
||||||
[21]="EQUAL"
|
|
||||||
[59]="COMMA"
|
|
||||||
[60]="PERIOD"
|
|
||||||
[61]="SLASH"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Hyprland's Lua config provider currently reports Lua binds as dispatcher
|
# Hyprland's Lua config provider currently reports Lua binds as dispatcher
|
||||||
# __lua and code:... binds from hl.bind() as key="" and keycode=0 in
|
# __lua and code:... binds from hl.bind() as key="" and keycode=0 in
|
||||||
# `hyprctl -j binds`. Keep a lightweight source-derived cache so the menu can
|
# `hyprctl -j binds`. Keep a lightweight source-derived cache so the menu can
|
||||||
@@ -31,74 +10,33 @@ declare -A LUA_BIND_KEY_MAP
|
|||||||
declare -A LUA_BIND_DISPATCHER_MAP
|
declare -A LUA_BIND_DISPATCHER_MAP
|
||||||
declare -A LUA_BIND_ARG_MAP
|
declare -A LUA_BIND_ARG_MAP
|
||||||
|
|
||||||
build_keymap_cache() {
|
# Hyprland reports XKB keycodes for code: bindings. Resolve them to symbols
|
||||||
local keymap
|
# via the compiled keymap, with a small fallback for common keys so the menu
|
||||||
keymap="$(xkbcli compile-keymap)" || {
|
# remains readable if xkbcli cannot resolve a symbol.
|
||||||
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() {
|
|
||||||
local symbol
|
|
||||||
symbol="${KEYCODE_SYM_MAP[$1]}"
|
|
||||||
|
|
||||||
if [[ -z $symbol ]]; then
|
|
||||||
symbol="${FALLBACK_KEYCODE_SYM_MAP[$1]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z $symbol ]]; then
|
|
||||||
symbol="code:$1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '%s\n' "${symbol^^}"
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_keycodes() {
|
parse_keycodes() {
|
||||||
local start end elapsed
|
awk '
|
||||||
[[ ${DEBUG:-0} == "1" ]] && start=$(date +%s.%N)
|
|
||||||
|
|
||||||
awk -v keymap="$(
|
|
||||||
for code in "${!KEYCODE_SYM_MAP[@]}"; do
|
|
||||||
printf '%s=%s\n' "$code" "${KEYCODE_SYM_MAP[$code]}"
|
|
||||||
done
|
|
||||||
for code in "${!FALLBACK_KEYCODE_SYM_MAP[@]}"; do
|
|
||||||
printf '%s=%s\n' "$code" "${FALLBACK_KEYCODE_SYM_MAP[$code]}"
|
|
||||||
done
|
|
||||||
)" '
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
split(keymap, entries, "\n")
|
split("10=1 11=2 12=3 13=4 14=5 15=6 16=7 17=8 18=9 19=0 20=MINUS 21=EQUAL 59=COMMA 60=PERIOD 61=SLASH", fallbacks, " ")
|
||||||
for (entry_index in entries) {
|
for (i in fallbacks) {
|
||||||
if (entries[entry_index] == "") continue
|
separator = index(fallbacks[i], "=")
|
||||||
separator = index(entries[entry_index], "=")
|
keycode_symbol[substr(fallbacks[i], 1, separator - 1)] = substr(fallbacks[i], separator + 1)
|
||||||
if (separator == 0) continue
|
}
|
||||||
code = substr(entries[entry_index], 1, separator - 1)
|
|
||||||
symbol = substr(entries[entry_index], separator + 1)
|
# </dev/null keeps xkbcli from consuming the binding records on stdin
|
||||||
if (!(code in keycode_symbol)) keycode_symbol[code] = toupper(symbol)
|
keymap_cmd = "xkbcli compile-keymap </dev/null"
|
||||||
|
section = ""
|
||||||
|
while ((keymap_cmd | getline line) > 0) {
|
||||||
|
if (line ~ /xkb_keycodes/) { section = "codes"; continue }
|
||||||
|
if (line ~ /xkb_symbols/) { section = "syms"; continue }
|
||||||
|
if (section == "codes" && match(line, /<([A-Za-z0-9_]+)>\s*=\s*([0-9]+)\s*;/, m)) code_by_name[m[1]] = m[2]
|
||||||
|
if (section == "syms" && match(line, /key\s*<([A-Za-z0-9_]+)>\s*\{\s*\[\s*([^, \]]+)/, m)) sym_by_name[m[1]] = m[2]
|
||||||
|
}
|
||||||
|
close(keymap_cmd)
|
||||||
|
|
||||||
|
for (name in code_by_name) {
|
||||||
|
code = code_by_name[name]
|
||||||
|
symbol = sym_by_name[name]
|
||||||
|
if (code != "" && symbol != "" && symbol != "NoSymbol") keycode_symbol[code] = toupper(symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
mouse_symbol["272"] = "LEFT MOUSE BUTTON"
|
mouse_symbol["272"] = "LEFT MOUSE BUTTON"
|
||||||
@@ -125,17 +63,6 @@ parse_keycodes() {
|
|||||||
print
|
print
|
||||||
}
|
}
|
||||||
'
|
'
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Supplement `hyprctl -j binds` for Lua-only binds that Hyprland currently
|
# Supplement `hyprctl -j binds` for Lua-only binds that Hyprland currently
|
||||||
@@ -266,10 +193,6 @@ noop = setmetatable({}, {
|
|||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
local function noop_fn()
|
|
||||||
return noop
|
|
||||||
end
|
|
||||||
|
|
||||||
hl = setmetatable({
|
hl = setmetatable({
|
||||||
dsp = dsp_proxy("hl.dsp"),
|
dsp = dsp_proxy("hl.dsp"),
|
||||||
bind = function(keys, bind_dispatcher, opts)
|
bind = function(keys, bind_dispatcher, opts)
|
||||||
@@ -293,20 +216,6 @@ hl = setmetatable({
|
|||||||
|
|
||||||
return noop
|
return noop
|
||||||
end,
|
end,
|
||||||
unbind = noop_fn,
|
|
||||||
config = noop_fn,
|
|
||||||
env = noop_fn,
|
|
||||||
monitor = noop_fn,
|
|
||||||
window_rule = noop_fn,
|
|
||||||
workspace_rule = noop_fn,
|
|
||||||
layer_rule = noop_fn,
|
|
||||||
gesture = noop_fn,
|
|
||||||
animation = noop_fn,
|
|
||||||
curve = noop_fn,
|
|
||||||
exec_cmd = noop_fn,
|
|
||||||
dispatch = noop_fn,
|
|
||||||
on = noop_fn,
|
|
||||||
timer = noop_fn,
|
|
||||||
get_config = function()
|
get_config = function()
|
||||||
return nil
|
return nil
|
||||||
end,
|
end,
|
||||||
@@ -441,13 +350,6 @@ parse_binding_records() {
|
|||||||
gsub(/(^|[[:space:]])uwsm(-app| app)[[:space:]]+--[[:space:]]+/, "", action);
|
gsub(/(^|[[:space:]])uwsm(-app| app)[[:space:]]+--[[:space:]]+/, "", action);
|
||||||
gsub(/^[ \t]+|[ \t]+$/, "", action);
|
gsub(/^[ \t]+|[ \t]+$/, "", action);
|
||||||
gsub(/[ \t]+/, " ", key_combo); # Collapse multiple spaces to one
|
gsub(/[ \t]+/, " ", key_combo); # Collapse multiple spaces to one
|
||||||
|
|
||||||
# Escape XML entities
|
|
||||||
gsub(/&/, "\\&", action);
|
|
||||||
gsub(/</, "\\<", action);
|
|
||||||
gsub(/>/, "\\>", action);
|
|
||||||
gsub(/"/, "\\"", action);
|
|
||||||
gsub(/'"'"'/, "\\'", action);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action != "") {
|
if (action != "") {
|
||||||
@@ -518,7 +420,6 @@ prioritize_entries() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output_binding_records_uncached() {
|
output_binding_records_uncached() {
|
||||||
build_keymap_cache
|
|
||||||
build_lua_bind_cache
|
build_lua_bind_cache
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -533,8 +434,9 @@ output_binding_records_uncached() {
|
|||||||
|
|
||||||
keybindings_cache_key() {
|
keybindings_cache_key() {
|
||||||
{
|
{
|
||||||
printf 'v3\n'
|
printf 'v4\n'
|
||||||
hyprctl -j binds 2>/dev/null | sha256sum | awk '{ print $1 }'
|
hyprctl -j devices 2>/dev/null | jq -r '.keyboards[].active_keymap' 2>/dev/null
|
||||||
|
hyprctl -j binds 2>/dev/null
|
||||||
} | sha256sum | awk '{ print $1 }'
|
} | sha256sum | awk '{ print $1 }'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,28 +458,15 @@ refresh_keybindings_cache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output_binding_records() {
|
output_binding_records() {
|
||||||
local cache_dir cache_file cache_key cache_name
|
local cache_dir cache_file cache_name
|
||||||
|
|
||||||
cache_key=$(keybindings_cache_key) || {
|
|
||||||
output_binding_records_uncached
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/omarchy"
|
cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/omarchy"
|
||||||
cache_name="keybindings-${cache_key}.records"
|
cache_name="keybindings-$(keybindings_cache_key).records"
|
||||||
cache_file="$cache_dir/$cache_name"
|
cache_file="$cache_dir/$cache_name"
|
||||||
|
|
||||||
if [[ -s $cache_file ]]; then
|
if [[ -s $cache_file ]]; then
|
||||||
cat "$cache_file"
|
cat "$cache_file"
|
||||||
return
|
elif mkdir -p "$cache_dir" 2>/dev/null && refresh_keybindings_cache "$cache_dir" "$cache_file" "$cache_name"; then
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$cache_dir" || {
|
|
||||||
output_binding_records_uncached
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if refresh_keybindings_cache "$cache_dir" "$cache_file" "$cache_name"; then
|
|
||||||
cat "$cache_file"
|
cat "$cache_file"
|
||||||
else
|
else
|
||||||
output_binding_records_uncached
|
output_binding_records_uncached
|
||||||
@@ -671,10 +560,8 @@ if [[ $1 == "--print" || $1 == "-p" ]]; then
|
|||||||
output_keybindings
|
output_keybindings
|
||||||
else
|
else
|
||||||
records=$(output_binding_records)
|
records=$(output_binding_records)
|
||||||
monitor_height=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .height')
|
|
||||||
menu_height=$((monitor_height * 40 / 100))
|
|
||||||
selection=$(cut -f1 <<<"$records" |
|
selection=$(cut -f1 <<<"$records" |
|
||||||
omarchy-menu-select 'Keybindings' -- --width 800 --height "$menu_height")
|
omarchy-menu-select 'Keybindings' -- --width 800 --height 500)
|
||||||
|
|
||||||
if [[ -n $selection ]]; then
|
if [[ -n $selection ]]; then
|
||||||
record=$(awk -F '\t' -v selection="$selection" '$1 == selection { print; exit }' <<<"$records")
|
record=$(awk -F '\t' -v selection="$selection" '$1 == selection { print; exit }' <<<"$records")
|
||||||
|
|||||||
Reference in New Issue
Block a user