feat(hypr): split device exceptions into own file with a TUI editor

Move hl.device blocks from input.lua into input-device-exceptions.lua
and add mk-device-exception.sh — a dialog-based TUI that pre-populates
fields from any existing block in the target file and replaces or
appends it on save.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-06-01 14:49:14 +02:00
parent 0742802cd8
commit 270aa841cf
3 changed files with 201 additions and 27 deletions

View File

@ -0,0 +1,28 @@
-- Device-specific input overrides — settings here take precedence over global input config.
hl.device({
name = "zsa-technology-labs-moonlander-mark-i-keyboard",
kb_layout = "de",
kb_variant = "",
kb_model = "",
kb_options = "",
kb_rules = "",
repeat_rate = 15,
repeat_delay = 750,
sensitivity = 0,
})
hl.device({
name = "logitech-pro-2-mouse-1",
sensitivity = 0,
accel_profile = "",
left_handed = false, -- left-handed mode handled on the mouse's onboard memory
natural_scroll = false,
scroll_points = "",
})
hl.device({
name = "logitech-g903-ls-1",
sensitivity = 0,
accel_profile = "",
left_handed = false,
natural_scroll = false,
scroll_points = "",
})

View File

@ -14,30 +14,5 @@ hl.config({
}, },
}, },
}) })
hl.device({
name = "zsa-technology-labs-moonlander-mark-i-keyboard", require("input-device-exceptions")
kb_layout = "de",
kb_variant = "",
kb_model = "",
kb_options = "",
kb_rules = "",
repeat_rate = 15,
repeat_delay = 750,
sensitivity = 0,
})
hl.device({
name = "logitech-pro-2-mouse-1",
sensitivity = 0,
accel_profile = "",
left_handed = false, -- left-handed mode handled on the mouse's onboard memory
natural_scroll = false,
scroll_points = "",
})
hl.device({
name = "logitech-g903-ls-1",
sensitivity = 0,
accel_profile = "",
left_handed = false,
natural_scroll = false,
scroll_points = "",
})

View File

@ -0,0 +1,171 @@
#!/usr/bin/env bash
# TUI for creating/updating device-specific Hyprland input overrides.
# Usage: mk-device-exception.sh <target-lua-file>
set -euo pipefail
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
[[ $# -eq 1 ]] || die "Usage: ${0##*/} <target-lua-file>"
target="$1"
command -v hyprctl &>/dev/null || die "hyprctl not found"
command -v dialog &>/dev/null || die "dialog not found"
command -v jq &>/dev/null || die "jq not found"
# ---------- device selection ----------
devices=$(hyprctl devices -j)
entries=$(jq -r '
(.keyboards[]?.name | "keyboard\t" + .),
(.mice[]?.name | "mouse\t" + .)
' <<<"$devices")
[[ -z "$entries" ]] && die "No keyboard or mouse devices found"
if command -v fzf &>/dev/null; then
selected=$(printf '%s\n' "$entries" \
| fzf --prompt="Select device: " --height=~10 --with-nth=2.. --delimiter=$'\t')
else
echo "Select a device:" >&2
IFS=$'\n'
select selected in $entries; do [[ -n "$selected" ]] && break; done
unset IFS
fi
[[ -z "$selected" ]] && exit 1
type=${selected%%$'\t'*}
name=${selected#*$'\t'}
# ---------- parse existing field value from target file ----------
# Returns the Lua literal for the field (e.g. `"de"`, `15`, `false`).
get_field() {
local field="$1" default="$2"
[[ -f "$target" ]] || { printf '%s' "$default"; return; }
awk -v dev="$name" -v fld="$field" -v def="$default" '
/^hl\.device\(\{/ { blk=""; in_blk=1; matched=0 }
in_blk { blk = blk $0 "\n" }
in_blk && /name = / { if ($0 ~ "\"" dev "\"") matched=1 }
in_blk && /^\}\)/ {
if (matched) {
n = split(blk, lines, "\n")
for (i = 1; i <= n; i++) {
line = lines[i]
sub(/^[[:space:]]+/, "", line) # strip indentation
if (line ~ "^" fld "[[:space:]]*=") {
sub(/^[^=]+=/, "", line) # drop "fieldname = "
sub(/^[[:space:]]*/, "", line) # strip leading space
sub(/[[:space:]]*,.*$/, "", line) # drop trailing comma and any inline comment
print line; exit
}
}
print def; exit
}
in_blk = 0
}
' "$target"
}
# Strip surrounding Lua string quotes for display in form ("de" -> de).
unquote() { local v="$1"; v="${v#\"}"; v="${v%\"}"; printf '%s' "$v"; }
# ---------- show form and collect values ----------
case "$type" in
keyboard)
v_layout=$(unquote "$(get_field kb_layout '"de"')")
v_variant=$(unquote "$(get_field kb_variant '""')")
v_model=$(unquote "$(get_field kb_model '""')")
v_options=$(unquote "$(get_field kb_options '""')")
v_rules=$(unquote "$(get_field kb_rules '""')")
v_rate=$(get_field repeat_rate 0)
v_delay=$(get_field repeat_delay 0)
v_sens=$(get_field sensitivity 0)
# dialog writes its TUI to stdout (/dev/tty) and the result to stderr.
values=$(dialog \
--title "Keyboard exception: $name" \
--form "" 16 74 8 \
"kb_layout" 1 1 "$v_layout" 1 17 42 0 \
"kb_variant" 2 1 "$v_variant" 2 17 42 0 \
"kb_model" 3 1 "$v_model" 3 17 42 0 \
"kb_options" 4 1 "$v_options" 4 17 42 0 \
"kb_rules" 5 1 "$v_rules" 5 17 42 0 \
"repeat_rate" 6 1 "$v_rate" 6 17 10 0 \
"repeat_delay" 7 1 "$v_delay" 7 17 10 0 \
"sensitivity" 8 1 "$v_sens" 8 17 10 0 \
2>&1 >/dev/tty) || exit 0
mapfile -t vals <<< "$values"
new_block="hl.device({
name = \"$name\",
kb_layout = \"${vals[0]:-}\",
kb_variant = \"${vals[1]:-}\",
kb_model = \"${vals[2]:-}\",
kb_options = \"${vals[3]:-}\",
kb_rules = \"${vals[4]:-}\",
repeat_rate = ${vals[5]:-0},
repeat_delay = ${vals[6]:-0},
sensitivity = ${vals[7]:-0},
})"
;;
mouse)
v_sens=$(get_field sensitivity 0)
v_accel=$(unquote "$(get_field accel_profile '""')")
v_left=$(get_field left_handed false)
v_natural=$(get_field natural_scroll false)
v_scroll=$(unquote "$(get_field scroll_points '""')")
values=$(dialog \
--title "Mouse exception: $name" \
--form "" 13 74 5 \
"sensitivity" 1 1 "$v_sens" 1 20 20 0 \
"accel_profile" 2 1 "$v_accel" 2 20 20 0 \
"left_handed" 3 1 "$v_left" 3 20 10 0 \
"natural_scroll" 4 1 "$v_natural" 4 20 10 0 \
"scroll_points" 5 1 "$v_scroll" 5 20 20 0 \
2>&1 >/dev/tty) || exit 0
mapfile -t vals <<< "$values"
new_block="hl.device({
name = \"$name\",
sensitivity = ${vals[0]:-0},
accel_profile = \"${vals[1]:-}\",
left_handed = ${vals[2]:-false},
natural_scroll = ${vals[3]:-false},
scroll_points = \"${vals[4]:-}\",
})"
;;
esac
# ---------- replace or append in target file ----------
[[ -f "$target" ]] || touch "$target"
# Remove the existing block for this device (no-op if not present).
tmp=$(mktemp)
awk -v dev="$name" '
/^hl\.device\(\{/ { blk = $0 "\n"; in_blk=1; matched=0; next }
in_blk {
blk = blk $0 "\n"
if (/name = / && ($0 ~ "\"" dev "\"")) matched=1
if (/^\}\)/) {
if (!matched) printf "%s", blk
in_blk=0; blk=""
}
next
}
{ print }
' "$target" > "$tmp"
mv "$tmp" "$target"
# Append the new block, separated by a blank line if the file is non-empty.
if [[ -s "$target" ]]; then
[[ "$(tail -c 1 "$target")" != $'\n' ]] && printf '\n' >> "$target"
printf '\n' >> "$target"
fi
printf '%s\n' "$new_block" >> "$target"
printf 'Written to %s\n' "$target"