Fix focus-mode labels that said the opposite of what they did
Found by codex's GNOME Tweaks audit and verified against the compositor:
`hyprctl descriptions` publishes input:follow_mouse as
map: [{"separate":3},{"detached":2},{"follow":1},{"disabled":0}].
Panama labelled 0 "Never", 1 "Click to focus", 2 "Sloppy focus". So this
desktop, sitting on the shipped value of 1, has been running
focus-follows-pointer the whole time while Settings called it "Click to
focus" -- and the way to actually GET click-to-focus was to choose
"Never". Value 3 was not offered at all. hypr/input.lua carried the same
wrong claim in a comment.
The shipped VALUE is left alone. Which focus mode this desktop should
use is a behaviour decision rather than a correction, and all four are
now reachable from Settings.
Nothing could have caught this. The compositor accepts 1, reads back 1,
and the write contract passes: the value is valid, it just means
something other than the label. The only authority on what each number
MEANS is the compositor, and it publishes that. So enum-hypr-map-contract
now checks every compositor-backed enum against the published map --
that offered values exist, and that published values are offered, since
a missing one is a capability nobody can reach.
Writing it immediately found two more of the same: variable refresh rate
offered Off and fullscreen-games while the compositor publishes four
(always-on and fullscreen-only were unreachable, and fullscreen-only is
what someone wanting VRR for video rather than games wants), and direct
scanout was missing its always-on value. Both now offer everything, with
a detail line per option rather than a bare word.
Verified the contract catches the original followMouse gap and a value
outside the map.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This commit is contained in:
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Every enum backed by a Hyprland option must offer values that option accepts.
|
||||
#
|
||||
# This exists because of a bug that shipped: followMouse offered 0/1/2 labelled
|
||||
# "Never" / "Click to focus" / "Sloppy focus", while Hyprland's actual mapping
|
||||
# is disabled=0, follow=1, detached=2, separate=3. The desktop was labelled
|
||||
# "Click to focus" and was in fact following the pointer, the way to GET click
|
||||
# to focus was to choose "Never", and value 3 did not exist in the UI at all.
|
||||
#
|
||||
# Nothing detects that. The compositor accepts 1, reads back 1, and verification
|
||||
# passes -- the value is valid, it just means something else entirely. The only
|
||||
# authority on what each number MEANS is the compositor, which publishes it:
|
||||
#
|
||||
# hyprctl descriptions -> { "name": "input:follow_mouse",
|
||||
# "map": [{"separate":3},{"detached":2},...] }
|
||||
#
|
||||
# So this checks the schema's enum values against that map, and against the
|
||||
# min/max range for mapped options that have no named map.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
schema="$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml"
|
||||
|
||||
fail() {
|
||||
printf 'enum hypr map contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v hyprctl >/dev/null 2>&1 || { printf 'enum hypr map contract: SKIP (no compositor)\n'; exit 0; }
|
||||
descriptions="$(hyprctl descriptions 2>/dev/null)" || fail 'could not read hyprctl descriptions'
|
||||
jq -e 'type == "array" and length > 0' >/dev/null <<<"$descriptions" \
|
||||
|| fail 'hyprctl descriptions did not return a list'
|
||||
|
||||
# Pull every enum entry that carries a hypr option, as: key<TAB>option<TAB>values
|
||||
entries="$(python3 - "$schema" <<'PY'
|
||||
import re, sys
|
||||
|
||||
text = open(sys.argv[1]).read()
|
||||
# Each schema entry is a brace-delimited block starting with `key:`.
|
||||
for block in re.findall(r'\{\s*\n?\s*key:\s*"([^"]+)"(.*?)\n \}', text, re.S):
|
||||
name, body = block
|
||||
if 'type: "enum"' not in body:
|
||||
continue
|
||||
option = re.search(r'option:\s*"([^"]+)"', body)
|
||||
if not option:
|
||||
continue
|
||||
values = re.findall(r'value:\s*(-?\d+)', body)
|
||||
if not values:
|
||||
continue
|
||||
print(f"{name}\t{option.group(1)}\t{','.join(values)}")
|
||||
PY
|
||||
)"
|
||||
|
||||
[[ -n "$entries" ]] || fail 'found no compositor-backed enums in the schema -- this contract is not reading it correctly'
|
||||
|
||||
checked=0
|
||||
while IFS=$'\t' read -r key option values; do
|
||||
[[ -n "$key" ]] || continue
|
||||
|
||||
entry="$(jq -c --arg name "$option" '.[] | select(.name == $name)' <<<"$descriptions")"
|
||||
[[ -n "$entry" ]] || fail "$key maps to \"$option\", which the compositor does not publish"
|
||||
|
||||
map_values="$(jq -r 'if .map then (.map | map(to_entries[].value) | join(",")) else "" end' <<<"$entry")"
|
||||
|
||||
IFS=',' read -ra wanted <<<"$values"
|
||||
for value in "${wanted[@]}"; do
|
||||
if [[ -n "$map_values" ]]; then
|
||||
grep -qx "$value" <<<"$(tr ',' '\n' <<<"$map_values")" \
|
||||
|| fail "$key offers $value for $option, which the compositor's map does not contain (it publishes: $map_values). A value outside the map is accepted and read back unchanged, so nothing else notices -- it simply means something other than the label says."
|
||||
else
|
||||
min="$(jq -r '.min // empty' <<<"$entry")"
|
||||
max="$(jq -r '.max // empty' <<<"$entry")"
|
||||
if [[ -n "$min" && -n "$max" ]]; then
|
||||
(( value >= min && value <= max )) \
|
||||
|| fail "$key offers $value for $option, outside the compositor's range $min..$max"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Every value the compositor names should be offered. A missing one is a
|
||||
# capability the user simply cannot reach -- value 3 was missing here.
|
||||
if [[ -n "$map_values" ]]; then
|
||||
while read -r published; do
|
||||
[[ -n "$published" ]] || continue
|
||||
grep -qx "$published" <<<"$(tr ',' '\n' <<<"$values")" \
|
||||
|| fail "$option publishes value $published but $key does not offer it, so that behaviour is unreachable from Settings"
|
||||
done <<<"$(tr ',' '\n' <<<"$map_values")"
|
||||
fi
|
||||
|
||||
checked=$((checked + 1))
|
||||
done <<<"$entries"
|
||||
|
||||
printf 'enum hypr map contract: PASS (%d mapped enums)\n' "$checked"
|
||||
Reference in New Issue
Block a user