Show Herdr keybindings with SUPER + CTRL + K
Mirrors the Tmux keybindings menu on SUPER + ALT + K. Herdr has no CLI that dumps resolved bindings, so the action list and its defaults come from `herdr --default-config`, where every action appears as a commented assignment, and the user config overrides what it sets. Prose in that default config can read like an assignment, as in `# type = "popup" opens a session-modal terminal`, so a line counts only when its value is a bare string or an array of them. Both TOML quote characters open a string, and with no config file at all the listing shows Herdr's own defaults rather than the Omarchy seed config Herdr never loaded.
This commit is contained in:
Executable
+231
@@ -0,0 +1,231 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Display annotated Herdr keybindings using an interactive search menu.
|
||||
# omarchy:args=[--print|-p] [--config <path>]
|
||||
|
||||
print_only=false
|
||||
config_file="${HERDR_CONFIG_PATH:-${XDG_CONFIG_HOME:-$HOME/.config}/herdr/config.toml}"
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--print|-p)
|
||||
print_only=true
|
||||
;;
|
||||
--config)
|
||||
shift
|
||||
config_file="$1"
|
||||
;;
|
||||
*)
|
||||
config_file="$1"
|
||||
;;
|
||||
esac
|
||||
|
||||
shift
|
||||
done
|
||||
|
||||
# With no config file, Herdr runs on its own built-in defaults. Substituting the
|
||||
# Omarchy seed config here would describe bindings Herdr never loaded.
|
||||
[[ -f $config_file ]] || config_file="/dev/null"
|
||||
|
||||
# Herdr has no CLI that dumps resolved keybindings, so the action list and its
|
||||
# defaults come from `herdr --default-config`, where every action appears as a
|
||||
# commented `# action = "binding"` line, and the user config overrides those.
|
||||
output_keybindings() (
|
||||
set -o pipefail
|
||||
herdr --default-config | awk -v config_file="$config_file" '
|
||||
# TOML literal strings are as valid as basic ones, so both quote characters
|
||||
# open and close a value here.
|
||||
function strip_comment(value, i, ch, quote, out) {
|
||||
quote = ""
|
||||
out = ""
|
||||
|
||||
for (i = 1; i <= length(value); i++) {
|
||||
ch = substr(value, i, 1)
|
||||
|
||||
if (quote == "" && (ch == "\"" || ch == "'\''")) quote = ch
|
||||
else if (ch == quote) quote = ""
|
||||
if (ch == "#" && quote == "") break
|
||||
|
||||
out = out ch
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
# Removes every quoted run, leaving only what surrounds the strings. An
|
||||
# unterminated quote returns a sentinel no valid value can produce.
|
||||
function without_quoted(value, i, ch, quote, out) {
|
||||
quote = ""
|
||||
out = ""
|
||||
|
||||
for (i = 1; i <= length(value); i++) {
|
||||
ch = substr(value, i, 1)
|
||||
|
||||
if (quote == "") {
|
||||
if (ch == "\"" || ch == "'"'"'") quote = ch
|
||||
else out = out ch
|
||||
} else if (ch == quote) {
|
||||
quote = ""
|
||||
}
|
||||
}
|
||||
|
||||
return quote == "" ? out : "\001"
|
||||
}
|
||||
|
||||
# Prose in the default config can read like an assignment, as in
|
||||
# `# type = "popup" opens a session-modal terminal`, so a value counts only when
|
||||
# it is a bare string or an array of them.
|
||||
function is_binding_value(value, rest) {
|
||||
value = strip_comment(value)
|
||||
sub(/^[[:space:]]+/, "", value)
|
||||
sub(/[[:space:]]+$/, "", value)
|
||||
if (value == "") return 0
|
||||
|
||||
rest = without_quoted(value)
|
||||
|
||||
# A bare string leaves nothing behind; an array leaves only its brackets,
|
||||
# commas, and whitespace.
|
||||
return rest == "" || rest ~ /^\[[[:space:],]*\]$/
|
||||
}
|
||||
|
||||
function key_text(key, count, parts, i, text) {
|
||||
count = split(key, parts, "+")
|
||||
text = ""
|
||||
|
||||
for (i = 1; i <= count; i++) {
|
||||
text = text (text == "" ? "" : " + ") toupper(parts[i])
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
# Renders every alternate binding for one action as "A / B".
|
||||
function combo_text(value, i, ch, quote, part, text) {
|
||||
value = strip_comment(value)
|
||||
quote = ""
|
||||
part = ""
|
||||
text = ""
|
||||
|
||||
for (i = 1; i <= length(value); i++) {
|
||||
ch = substr(value, i, 1)
|
||||
|
||||
if (quote == "") {
|
||||
if (ch == "\"" || ch == "'"'"'") { quote = ch; part = "" }
|
||||
continue
|
||||
}
|
||||
|
||||
if (ch == quote) {
|
||||
quote = ""
|
||||
if (part != "") text = text (text == "" ? "" : " / ") key_text(part)
|
||||
continue
|
||||
}
|
||||
|
||||
part = part ch
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
function describe(action, text) {
|
||||
sub(/^navigate_/, "", action)
|
||||
gsub(/_/, " ", action)
|
||||
text = toupper(substr(action, 1, 1)) substr(action, 2)
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
function remember(action) {
|
||||
if (action in seen) return
|
||||
seen[action] = 1
|
||||
order[++count] = action
|
||||
}
|
||||
|
||||
function bind(action, value, combo) {
|
||||
combo = combo_text(value)
|
||||
if (combo == "") return
|
||||
|
||||
if (action ~ /^navigate_/) combo = "NAVIGATE + " combo
|
||||
remember(action)
|
||||
combos[action] = combo
|
||||
}
|
||||
|
||||
# Pass 1: default config on stdin, for the action order and default bindings.
|
||||
# Every line there is commented out, so section markers are too.
|
||||
FNR == NR {
|
||||
line = $0
|
||||
sub(/^[[:space:]]*#[[:space:]]*/, "", line)
|
||||
|
||||
if (line ~ /^\[\[keys\.command\]\]/) { in_keys = 0; next }
|
||||
if (line ~ /^\[/) { in_keys = (line ~ /^\[keys\]/); next }
|
||||
if (!in_keys) next
|
||||
if (line !~ /^[a-z_]+[[:space:]]*=/) next
|
||||
|
||||
action = line
|
||||
sub(/[[:space:]]*=.*$/, "", action)
|
||||
value = line
|
||||
sub(/^[^=]*=[[:space:]]*/, "", value)
|
||||
if (!is_binding_value(value)) next
|
||||
|
||||
# Actions Herdr leaves unbound still hold their place in the listing order,
|
||||
# for when the user config binds them.
|
||||
remember(action)
|
||||
bind(action, value)
|
||||
next
|
||||
}
|
||||
|
||||
# Pass 2: the user config, which overrides the defaults it sets.
|
||||
{
|
||||
if ($0 ~ /^\[\[keys\.command\]\]/) { in_user_keys = 0; next }
|
||||
if ($0 ~ /^\[/) { in_user_keys = ($0 ~ /^\[keys\]/); next }
|
||||
if (!in_user_keys) next
|
||||
if ($0 ~ /^[[:space:]]*#/) next
|
||||
if ($0 !~ /^[[:space:]]*[a-z_]+[[:space:]]*=/) next
|
||||
|
||||
line = $0
|
||||
sub(/^[[:space:]]*/, "", line)
|
||||
action = line
|
||||
sub(/[[:space:]]*=.*$/, "", action)
|
||||
value = line
|
||||
sub(/^[^=]*=[[:space:]]*/, "", value)
|
||||
if (!is_binding_value(value)) next
|
||||
|
||||
# An action bound to nothing in the user config is unbound, not defaulted.
|
||||
if (combo_text(value) == "") {
|
||||
delete combos[action]
|
||||
next
|
||||
}
|
||||
|
||||
bind(action, value)
|
||||
}
|
||||
|
||||
END {
|
||||
if ("prefix" in combos) {
|
||||
printf "%-32s → %s\n", "PREFIX", combos["prefix"]
|
||||
}
|
||||
|
||||
for (i = 1; i <= count; i++) {
|
||||
action = order[i]
|
||||
if (action == "prefix") continue
|
||||
if (!(action in combos)) continue
|
||||
|
||||
printf "%-32s → %s\n", combos[action], describe(action)
|
||||
}
|
||||
}
|
||||
' - "$config_file"
|
||||
)
|
||||
|
||||
if [[ $print_only == "true" ]]; then
|
||||
output_keybindings
|
||||
exit 0
|
||||
fi
|
||||
|
||||
records=$(output_keybindings)
|
||||
|
||||
monitor_height=$(hyprctl monitors -j 2>/dev/null | jq -r '.[] | select(.focused == true) | .height' 2>/dev/null)
|
||||
|
||||
if [[ ! $monitor_height =~ ^[0-9]+$ ]] || ((monitor_height <= 0)); then
|
||||
monitor_height=900
|
||||
fi
|
||||
|
||||
menu_height=$((monitor_height * 40 / 100))
|
||||
printf '%s\n' "$records" | omarchy-menu-select 'Herdr keybindings' -- --width 800 --height "$menu_height" >/dev/null || true
|
||||
@@ -9,6 +9,7 @@ o.bind("SUPER + ESCAPE", "System menu", "omarchy-menu toggle system")
|
||||
o.bind("XF86PowerOff", "Power menu", "omarchy-menu toggle system", { locked = true })
|
||||
o.bind("SUPER + K", "Show key bindings", "omarchy-menu-keybindings")
|
||||
o.bind("SUPER + ALT + K", "Show Tmux key bindings", "omarchy-menu-tmux-keybindings")
|
||||
o.bind("SUPER + CTRL + K", "Show Herdr key bindings", "omarchy-menu-herdr-keybindings")
|
||||
o.bind("SUPER + CTRL + Q", "Calculator", "omacalc")
|
||||
o.bind("XF86Calculator", "Calculator", "omacalc")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user