fix(menu): case-insensitive filter on all rofi -dmenu menus
All checks were successful
Check / eval (push) Successful in 2m53s

dmenu mode is case-sensitive by default (dmenu compat), so config.rasi
case-sensitive=false fixed apps/drun but not nomarchy-menu pickers.
rofi_menu helper always passes -i; networkmanager_dmenu gets rofi -i.

V0: flake check --no-build green. V3: type sys in root Menu → System.
This commit is contained in:
2026-07-10 13:08:33 +01:00
parent 79630314aa
commit 3bc8a46927
2 changed files with 44 additions and 35 deletions

View File

@@ -16,10 +16,10 @@ QA machine), the **T14s** (webcam case).
one module (`HH:MM · Day DD Mon`). First pass: volume-high and one module (`HH:MM · Day DD Mon`). First pass: volume-high and
keyboard still tight → pad doubled. keyboard still tight → pad doubled.
- [x] **Calendar on unified clock (2026-07-10)** — PASS (Bernardo). - [x] **Calendar on unified clock (2026-07-10)** — PASS (Bernardo).
- [ ] **Rofi case-insensitive search (2026-07-10)** — SUPER+Space: type - [ ] **Rofi menu case-insensitive filter (2026-07-10)** — SUPER+M root
`ste`Steam listed (not only `Ste`/`STE`); type `sys` → System Menu: type `sys`**System** (dmenu path uses `rofi_menu` = `-i`;
menu row if testing nomarchy-menu. Config: matching normal,fuzzy + apps/drun already worked via config.rasi). Spot-check System and
case-sensitive false + case-smart false. Tools filters the same way. Network picker inherits `rofi -i`.
- [ ] **Charge-limit live apply (2026-07-10)** — on a laptop with - [ ] **Charge-limit live apply (2026-07-10)** — on a laptop with
`charge_control_end_threshold`: System Battery limit → 80%; `charge_control_end_threshold`: System Battery limit → 80%;
confirm `cat …/charge_control_end_threshold` is 80 without rebuild; confirm `cat …/charge_control_end_threshold` is 80 without rebuild;

View File

@@ -169,10 +169,11 @@ let
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" '' nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
# Nomarchy menu dispatcher thin presentation layer over # Nomarchy menu dispatcher thin presentation layer over
# `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl, # `rofi -dmenu` (via rofi_menu helper); actions delegate to
# hyprctl and friends. Menu entries carry real icons from the theme's # nomarchy-theme-sync, systemctl, hyprctl and friends. Menu entries
# icon set (Papirus) via rofi's per-row icon protocol see row() below. # carry real icons from the theme's icon set (Papirus) via rofi's
# `nomarchy-menu` with no argument shows the module picker. # per-row icon protocol see row() below. `nomarchy-menu` with no
# argument shows the module picker.
# System batteries: type=Battery, not scope=Device same filter as # System batteries: type=Battery, not scope=Device same filter as
# nomarchy-battery-notify. Name-agnostic (BAT0, CMB0, test_battery) # nomarchy-battery-notify. Name-agnostic (BAT0, CMB0, test_battery)
@@ -229,6 +230,12 @@ let
BACK=" Back" BACK=" Back"
back() { printf '%s\n' "$BACK"; } back() { printf '%s\n' "$BACK"; }
# dmenu mode is case-sensitive by default (dmenu compatibility), even
# when config.rasi has case-sensitive: false for drun/modi so
# SUPER+Space (apps) matched "ste"Steam while the main Menu filter
# did not match "sys"System. Always pass -i on menu pickers.
rofi_menu() { command rofi -dmenu -i "$@"; }
case "''${1:-}" in case "''${1:-}" in
power) power)
choice=$( { choice=$( {
@@ -239,7 +246,7 @@ let
row "Reboot" system-reboot row "Reboot" system-reboot
row "Shutdown" system-shutdown row "Shutdown" system-shutdown
back back
} | rofi -dmenu -show-icons -p Power) || exit 0 } | rofi_menu -show-icons -p Power) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" ;; "$BACK") exec "$0" ;;
*Lock) loginctl lock-session ;; *Lock) loginctl lock-session ;;
@@ -274,7 +281,7 @@ let
esac esac
done done
back back
} | rofi -dmenu -show-icons -p "profile (now: $cur)") || exit 0 } | rofi_menu -show-icons -p "profile (now: $cur)") || exit 0
[ "$choice" = "$BACK" ] && exec "$0" system [ "$choice" = "$BACK" ] && exec "$0" system
[ -n "$choice" ] && powerprofilesctl set "$choice" ;; [ -n "$choice" ] && powerprofilesctl set "$choice" ;;
@@ -313,7 +320,7 @@ let
row "Charge limit: Off (100%)" battery-100 row "Charge limit: Off (100%)" battery-100
fi fi
back back
} | rofi -dmenu -show-icons -p "Power profile: ''${curp:-n/a}, limit: ''${curc:-default}") || exit 0 } | rofi_menu -show-icons -p "Power profile: ''${curp:-n/a}, limit: ''${curc:-default}") || exit 0
# Persist in-flake + apply live to sysfs (udev makes the node # Persist in-flake + apply live to sysfs (udev makes the node
# group-writable for users Bernardo 2026-07-10). # group-writable for users Bernardo 2026-07-10).
set_limit() { set_limit() {
@@ -346,14 +353,14 @@ ${themeSlugMap}
choice=$( { choice=$( {
${themeRows} ${themeRows}
back back
} | rofi -dmenu -show-icons -p Theme -theme-str ${themeGridThemeStr}) || exit 0 } | rofi_menu -show-icons -p Theme -theme-str ${themeGridThemeStr}) || exit 0
[ "$choice" = "$BACK" ] && exec "$0" [ "$choice" = "$BACK" ] && exec "$0"
choice="''${choice% }" # drop the active marker if present choice="''${choice% }" # drop the active marker if present
slug="''${THEME_SLUG[$choice]:-}" slug="''${THEME_SLUG[$choice]:-}"
[ -n "$slug" ] && exec nomarchy-theme-sync apply "$slug" ;; [ -n "$slug" ] && exec nomarchy-theme-sync apply "$slug" ;;
clipboard) clipboard)
sel=$( { cliphist list; printf '%s\n' "$BACK"; } | rofi -dmenu -p clip) || exit 0 sel=$( { cliphist list; printf '%s\n' "$BACK"; } | rofi_menu -p clip) || exit 0
[ "$sel" = "$BACK" ] && exec "$0" tools [ "$sel" = "$BACK" ] && exec "$0" tools
printf '%s' "$sel" | cliphist decode | wl-copy ;; printf '%s' "$sel" | cliphist decode | wl-copy ;;
@@ -368,7 +375,7 @@ ${themeRows}
files) files)
sel=$( { fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \ sel=$( { fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \
| head -n 50000 | sed "s|^$HOME/||"; printf '%s\n' "$BACK"; } \ | head -n 50000 | sed "s|^$HOME/||"; printf '%s\n' "$BACK"; } \
| rofi -dmenu -p file) || exit 0 | rofi_menu -p file) || exit 0
[ "$sel" = "$BACK" ] && exec "$0" tools [ "$sel" = "$BACK" ] && exec "$0" tools
[ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;; [ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;;
@@ -379,7 +386,7 @@ ${themeRows}
exec rofi -show emoji -modi emoji ;; exec rofi -show emoji -modi emoji ;;
web) web)
q=$(rofi -dmenu -p search < /dev/null) || exit 0 q=$(rofi_menu -p search < /dev/null) || exit 0
[ -n "$q" ] || exit 0 [ -n "$q" ] || exit 0
exec xdg-open "https://www.google.com/search?q=$(urlencode "$q")" ;; exec xdg-open "https://www.google.com/search?q=$(urlencode "$q")" ;;
@@ -421,7 +428,7 @@ ${themeRows}
row "Output device" audio-volume-high row "Output device" audio-volume-high
row "Input device" audio-input-microphone row "Input device" audio-input-microphone
back back
} | rofi -dmenu -show-icons -p Audio) || exit 0 } | rofi_menu -show-icons -p Audio) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" system ;; "$BACK") exec "$0" system ;;
*Output*) exec rofi-pulse-select sink ;; *Output*) exec rofi-pulse-select sink ;;
@@ -454,7 +461,7 @@ ${themeRows}
[ -n "$profs" ] && printf 'Profiles \n' [ -n "$profs" ] && printf 'Profiles \n'
printf '%s' "$mons" | jq -r '.[] | "\(.name) · \(.width)x\(.height)@\(.refreshRate|round)Hz"' printf '%s' "$mons" | jq -r '.[] | "\(.name) · \(.width)x\(.height)@\(.refreshRate|round)Hz"'
printf '%s\n' "$BACK" printf '%s\n' "$BACK"
} | rofi -dmenu -p Display ) || exit 0 } | rofi_menu -p Display ) || exit 0
[ "$name" = "$BACK" ] && exec "$0" system [ "$name" = "$BACK" ] && exec "$0" system
[ "$name" = "Profiles " ] && exec "$0" display-profile [ "$name" = "Profiles " ] && exec "$0" display-profile
name=''${name%% ·*} # strip the " · WxH@R" hint name=''${name%% ·*} # strip the " · WxH@R" hint
@@ -474,7 +481,7 @@ ${themeRows}
printf '%s' "$mons" | jq -r --arg n "$name" '.[]|select(.name==$n)|.availableModes[]' \ printf '%s' "$mons" | jq -r --arg n "$name" '.[]|select(.name==$n)|.availableModes[]' \
| sed 's/Hz$//' | awk -F@ '{ printf "%s@%d\n", $1, $2 + 0.5 }' | sort -rV -u | sed 's/Hz$//' | awk -F@ '{ printf "%s@%d\n", $1, $2 + 0.5 }' | sort -rV -u
printf '%s\n' "$BACK" printf '%s\n' "$BACK"
} | rofi -dmenu -p "$name (now $cur)" ) || exit 0 } | rofi_menu -p "$name (now $cur)" ) || exit 0
[ "$mode" = "$BACK" ] && exec "$0" "$modeBack" [ "$mode" = "$BACK" ] && exec "$0" "$modeBack"
[ -n "$mode" ] || exit 0 [ -n "$mode" ] || exit 0
@@ -509,7 +516,7 @@ ${themeRows}
fi fi
fi fi
back back
} | rofi -dmenu -show-icons -markup-rows -p Capture) || exit 0 } | rofi_menu -show-icons -markup-rows -p Capture) || exit 0
dir="$HOME/Pictures/Screenshots" dir="$HOME/Pictures/Screenshots"
file="$dir/$(date +%Y%m%d-%H%M%S).png" file="$dir/$(date +%Y%m%d-%H%M%S).png"
case "$choice" in case "$choice" in
@@ -551,7 +558,7 @@ ${themeRows}
else row "Night light (off)" weather-clear-night else row "Night light (off)" weather-clear-night
fi fi
back back
} | rofi -dmenu -show-icons -markup-rows -p "Look & Feel") || exit 0 } | rofi_menu -show-icons -markup-rows -p "Look & Feel") || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" ;; "$BACK") exec "$0" ;;
*Theme*) exec "$0" theme ;; *Theme*) exec "$0" theme ;;
@@ -578,7 +585,7 @@ ${themeRows}
else printf 'Auto-switch (off)\n' else printf 'Auto-switch (off)\n'
fi fi
printf '%s\n' "$BACK" printf '%s\n' "$BACK"
} | rofi -dmenu -p Profiles ) || exit 0 } | rofi_menu -p Profiles ) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" display ;; "$BACK") exec "$0" display ;;
"Base layout") exec nomarchy-display-profile base ;; "Base layout") exec nomarchy-display-profile base ;;
@@ -612,7 +619,7 @@ ${themeRows}
# Enter on a row is a deliberate no-op (nothing to launch); Back # Enter on a row is a deliberate no-op (nothing to launch); Back
# returns to the root picker, which is also where it's launched. # returns to the root picker, which is also where it's launched.
sel=$( { cat ${cheatsheetFile}; printf '%s\n' "$BACK"; } \ sel=$( { cat ${cheatsheetFile}; printf '%s\n' "$BACK"; } \
| rofi -dmenu -i -p keys ) || exit 0 | rofi_menu -p keys ) || exit 0
[ "$sel" = "$BACK" ] && exec "$0" [ "$sel" = "$BACK" ] && exec "$0"
exit 0 ;; exit 0 ;;
@@ -621,7 +628,7 @@ ${themeRows}
# Pulled fresh from npm via npx rather than nixpkgs' claude-code, # Pulled fresh from npm via npx rather than nixpkgs' claude-code,
# which lags behind model releases; npx caches after first run. The # which lags behind model releases; npx caches after first run. The
# REPL stays open for follow-ups. # REPL stays open for follow-ups.
q=$(rofi -dmenu -p claude < /dev/null) || exit 0 q=$(rofi_menu -p claude < /dev/null) || exit 0
[ -n "$q" ] || exit 0 [ -n "$q" ] || exit 0
exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;; exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;;
@@ -728,7 +735,7 @@ ${themeRows}
row "Delete all" edit-delete row "Delete all" edit-delete
row "$pam_label" system-lock-screen row "$pam_label" system-lock-screen
back back
} | rofi -dmenu -show-icons -p Fingerprint) || exit 0 } | rofi_menu -show-icons -p Fingerprint) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" system ;; "$BACK") exec "$0" system ;;
*Enroll*) *Enroll*)
@@ -794,7 +801,7 @@ ${themeRows}
&& row "System files Snapshots" timeshift && row "System files Snapshots" timeshift
row "System config boot an older generation (how)" help-about row "System config boot an older generation (how)" help-about
back back
} | rofi -dmenu -show-icons -p Rollback) || exit 0 } | rofi_menu -show-icons -p Rollback) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" system ;; "$BACK") exec "$0" system ;;
"Desktop gen "*) "Desktop gen "*)
@@ -830,7 +837,7 @@ ${themeRows}
command -v hyprpicker >/dev/null 2>&1 && row "Color picker${menuHint "colorpicker"}" preferences-desktop-color command -v hyprpicker >/dev/null 2>&1 && row "Color picker${menuHint "colorpicker"}" preferences-desktop-color
row "Ask Claude${menuHint "ask"}" internet-chat row "Ask Claude${menuHint "ask"}" internet-chat
back back
} | rofi -dmenu -show-icons -markup-rows -p Tools) || exit 0 } | rofi_menu -show-icons -markup-rows -p Tools) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" ;; "$BACK") exec "$0" ;;
*"Install Nomarchy"*) *"Install Nomarchy"*)
@@ -888,7 +895,7 @@ ${themeRows}
# expose it), like Power profile above. # expose it), like Power profile above.
has_charge_threshold && row "Battery limit" battery-080 has_charge_threshold && row "Battery limit" battery-080
back back
} | rofi -dmenu -show-icons -markup-rows -p System) || exit 0 } | rofi_menu -show-icons -markup-rows -p System) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" ;; "$BACK") exec "$0" ;;
*Network*) exec "$0" network ;; *Network*) exec "$0" network ;;
@@ -926,7 +933,7 @@ ${themeRows}
row "Off charge to 100%" battery-100 row "Off charge to 100%" battery-100
row "Custom" battery row "Custom" battery
back back
} | rofi -dmenu -show-icons -p "Battery limit (now: ''${cur:-default})") || exit 0 } | rofi_menu -show-icons -p "Battery limit (now: ''${cur:-default})") || exit 0
set_limit() { set_limit() {
nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
n="$1"; [ "$n" = null ] && n=100 n="$1"; [ "$n" = null ] && n=100
@@ -945,7 +952,7 @@ ${themeRows}
"60%"*) set_limit 60 "Set to 60%" ;; "60%"*) set_limit 60 "Set to 60%" ;;
"Off"*) set_limit null "Off charges to 100%" ;; "Off"*) set_limit null "Off charges to 100%" ;;
"Custom"*) "Custom"*)
limit=$(rofi -dmenu -p "Charge limit % (50100)" < /dev/null) || exit 0 limit=$(rofi_menu -p "Charge limit % (50100)" < /dev/null) || exit 0
[ -n "$limit" ] || exit 0 [ -n "$limit" ] || exit 0
case "$limit" in case "$limit" in
*[!0-9]*) notify-send "Battery limit" "Not a whole number: $limit"; exit 0 ;; *[!0-9]*) notify-send "Battery limit" "Not a whole number: $limit"; exit 0 ;;
@@ -968,7 +975,7 @@ ${themeRows}
row "System" preferences-system row "System" preferences-system
row "Power${menuHint "power"}" system-shutdown row "Power${menuHint "power"}" system-shutdown
row "Keybindings${menuHint "keybinds"}" preferences-desktop-keyboard row "Keybindings${menuHint "keybinds"}" preferences-desktop-keyboard
} | rofi -dmenu -show-icons -markup-rows -p Menu) || exit 0 } | rofi_menu -show-icons -markup-rows -p Menu) || exit 0
case "$choice" in case "$choice" in
*Apps*) exec rofi -show drun -theme launcher ;; *Apps*) exec rofi -show drun -theme launcher ;;
*Look*) exec "$0" lookfeel ;; *Look*) exec "$0" lookfeel ;;
@@ -1020,7 +1027,7 @@ ${themeRows}
file=$( { fd -e conf -e ovpn . "$HOME" --type f --hidden \ file=$( { fd -e conf -e ovpn . "$HOME" --type f --hidden \
--exclude .git --exclude .cache 2>/dev/null \ --exclude .git --exclude .cache 2>/dev/null \
| sed "s|^$HOME/||"; printf '%s\n' "$BACK"; } \ | sed "s|^$HOME/||"; printf '%s\n' "$BACK"; } \
| rofi -dmenu -p "Import VPN config" ) || return | rofi_menu -p "Import VPN config" ) || return
{ [ -z "$file" ] || [ "$file" = "$BACK" ]; } && return { [ -z "$file" ] || [ "$file" = "$BACK" ]; } && return
case "$file" in *.ovpn) type=openvpn ;; *) type=wireguard ;; esac case "$file" in *.ovpn) type=openvpn ;; *) type=wireguard ;; esac
if nmcli connection import type "$type" file "$HOME/$file" >/dev/null 2>&1; then if nmcli connection import type "$type" file "$HOME/$file" >/dev/null 2>&1; then
@@ -1040,7 +1047,7 @@ ${themeRows}
} }
ts_exit_node() { ts_exit_node() {
node=$( { printf 'none\n'; tailscale exit-node list 2>/dev/null | awk 'NR>1 && $2 {print $2}'; \ node=$( { printf 'none\n'; tailscale exit-node list 2>/dev/null | awk 'NR>1 && $2 {print $2}'; \
printf '%s\n' "$BACK"; } | rofi -dmenu -p "Exit node" ) || return printf '%s\n' "$BACK"; } | rofi_menu -p "Exit node" ) || return
{ [ -z "$node" ] || [ "$node" = "$BACK" ]; } && return { [ -z "$node" ] || [ "$node" = "$BACK" ]; } && return
[ "$node" = none ] && node="" [ "$node" = none ] && node=""
ts_priv set --exit-node="$node" ts_priv set --exit-node="$node"
@@ -1052,7 +1059,7 @@ ${themeRows}
if [ "$st" = Running ]; then printf 'Disconnect\n'; else printf 'Connect\n'; fi if [ "$st" = Running ]; then printf 'Disconnect\n'; else printf 'Connect\n'; fi
printf 'Exit node\n' printf 'Exit node\n'
printf '%s\n' "$BACK" printf '%s\n' "$BACK"
} | rofi -dmenu -p "Tailscale ($st)" ) || return } | rofi_menu -p "Tailscale ($st)" ) || return
case "$choice" in case "$choice" in
"$BACK"|"") return ;; "$BACK"|"") return ;;
# Authed-but-down bring it up inline; otherwise it needs an # Authed-but-down bring it up inline; otherwise it needs an
@@ -1072,7 +1079,7 @@ ${themeRows}
printf 'Import config\n' printf 'Import config\n'
[ -n "$have_ts" ] && printf 'Tailscale \n' [ -n "$have_ts" ] && printf 'Tailscale \n'
printf '%s\n' "$BACK" printf '%s\n' "$BACK"
} | rofi -dmenu -p VPN ) || exit 0 } | rofi_menu -p VPN ) || exit 0
case "$choice" in case "$choice" in
"$BACK"|"") exec nomarchy-menu system ;; "$BACK"|"") exec nomarchy-menu system ;;
"Import config") import_config ;; "Import config") import_config ;;
@@ -1114,7 +1121,9 @@ in
force = true; force = true;
text = '' text = ''
[dmenu] [dmenu]
dmenu_command = rofi # -i: networkmanager_dmenu uses dmenu mode (case-sensitive by
# default); match nomarchy-menu's case-insensitive filter.
dmenu_command = rofi -i
rofi_highlight = True rofi_highlight = True
compact = True compact = True
wifi_chars = wifi_chars =