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
parent
0742802cd8
commit
270aa841cf
|
|
@ -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 = "",
|
||||||
|
})
|
||||||
|
|
@ -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 = "",
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
Loading…
Reference in New Issue