swadhinbiswas Initial de-omarchy: Omarchy UI layer for Arch-based distros · 8/22/2026
9f393b2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/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