Resolve omarchy_monitor_scale variable reference in clamshell recovery (#6688)

A pinned internal monitor rule that referenced the omarchy_monitor_scale local
had the variable name captured as the scale, so clamshell recovery fell all the
way to the hardcoded 2.

Read the config the way Hyprland writes it: a key's value is whatever sits
between the `=` and the next separator, and a bare word resolves against a local
only when one exists, so a quoted string stays a string. Comments are cut before
anything is matched, and position gets the same resolution since it had the
identical bug.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
sergio
2026-08-11 13:58:47 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 4d0531f351
commit 27d1b6bebc
2 changed files with 298 additions and 15 deletions
+66 -15
View File
@@ -7,6 +7,7 @@ TOGGLES_DIR="$HOME/.local/state/omarchy/toggles/hypr"
CLAMSHELL_FLAG="$TOGGLES_DIR/internal-monitor-clamshell.lua"
MANUAL_DISABLE_FLAG="$TOGGLES_DIR/internal-monitor-disable.lua"
SCALE_STATE="$TOGGLES_DIR/internal-monitor-scale"
MONITOR_LUA="$HOME/.config/hypr/monitors.lua"
INTERNAL=$(omarchy-hyprland-monitor-laptop)
@@ -26,26 +27,76 @@ scales_match() {
}'
}
configured_monitor_scale() {
local monitor_lua="$HOME/.config/hypr/monitors.lua"
[[ -f $monitor_lua ]] || return 0
local scale
scale=$(configured_internal_monitor_value scale)
if [[ -z $scale ]]; then
scale=$(sed -n 's/^local omarchy_monitor_scale = //p' "$monitor_lua" | head -1)
lua_identifier() {
[[ $1 =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]
}
# Value assigned by `local <name> = ...`, without its quotes or trailing comment.
# Only a lone scalar counts: an expression like `1080 / 720` must stay unresolved
# so the caller falls back rather than applying the first half of the sum.
lua_local_value() {
local name="$1" value
lua_identifier "$name" && [[ -f $MONITOR_LUA ]] || return 0
value=$(sed -nE 's/^[[:space:]]*local[[:space:]]+'"$name"'[[:space:]]*=[[:space:]]*("[^"]*"|[^"[:space:]]+)[[:space:]]*(--.*)?$/\1/p' "$MONITOR_LUA" | head -1)
[[ $value == \"*\" ]] && value="${value:1:-1}"
printf '%s\n' "$value"
}
# A quoted capture is a Lua string and stands for itself; a bare word may instead
# name a local the rule refers to, as the shipped scale = omarchy_monitor_scale
# does. A bare word naming no local is left alone to fail validation.
lua_scalar() {
local value="$1" resolved
if [[ $value == \"*\" ]]; then
printf '%s\n' "${value:1:-1}"
return
fi
if [[ -z $scale ]]; then
scale=$(sed -nE 's/^hl\.monitor\(\{ output = "", mode = "preferred", position = "auto", scale = ([^ ]+) \}\)/\1/p' "$monitor_lua" | head -1)
fi
echo "$scale"
resolved=$(lua_local_value "$value")
printf '%s\n' "${resolved:-$value}"
}
# The config with its comments cut away, so commented-out text can pose neither
# as a rule nor as one of its keys.
monitor_rules() {
[[ -f $MONITOR_LUA ]] || return 0
sed -E -e 's/--\[\[[^]]*\]\]//g' -e 's/--.*$//' "$MONITOR_LUA"
}
monitor_rule_regex() {
printf '^[[:space:]]*hl\\.monitor\\(\\{.*output[[:space:]]*=[[:space:]]*"%s"' "$1"
}
# A key is preceded by a table separator, so a longer key cannot stand in for it,
# and its value is the whole of what sits between the `=` and the next separator.
# Anything else is an expression this cannot evaluate.
configured_monitor_value() {
local output="$1" key="$2" value
value=$(monitor_rules | sed -nE '/'"$(monitor_rule_regex "$output")"'/s/.*[{,;[:space:]]'"$key"'[[:space:]]*=[[:space:]]*("[^"]*"|[^,;}[:space:]]+)[[:space:]]*([,;}].*)?$/\1/p' | head -1)
lua_scalar "$value"
}
configured_internal_monitor_value() {
local key="$1"
local monitor_lua="$HOME/.config/hypr/monitors.lua"
[[ -n $INTERNAL && -f $monitor_lua ]] || return 0
[[ -n $INTERNAL ]] || return 0
sed -nE '/^hl\.monitor\(\{.*output = "'"$INTERNAL"'".*\}\)/s/.*'"$key"' = "?([^", }]+)"?.*/\1/p' "$monitor_lua" | head -1
configured_monitor_value "$INTERNAL" "$1"
}
configured_monitor_scale() {
local scale
scale=$(configured_internal_monitor_value scale)
# An internal rule that names a scale settles it, even when the name resolves
# to nothing usable. Only a rule that names none at all defers to the catch-all,
# which is the scale Omarchy has always applied for that config.
[[ -n $scale ]] || scale=$(configured_monitor_value "" scale)
# No rule carries a scale at all: fall back to Omarchy's own knob.
[[ -n $scale ]] || scale=$(lua_local_value omarchy_monitor_scale)
printf '%s\n' "$scale"
}
current_internal_scale() {
@@ -72,6 +72,131 @@ hl.monitor({ output = "", mode = "preferred", position = "auto-right", scale = 1
LUA
}
# A specific-output rule that references the omarchy_monitor_scale variable
# (the default template's pattern) instead of a literal value. The variable
# must be resolved, not captured as the literal string "omarchy_monitor_scale".
write_internal_monitor_var_config() {
cat >"$monitor_lua" <<'LUA'
local omarchy_gdk_scale = 1.5
local omarchy_monitor_scale = 1.5
hl.env("GDK_SCALE", tostring(omarchy_gdk_scale))
hl.monitor({ output = "eDP-1", mode = "preferred", position = "auto", scale = omarchy_monitor_scale })
LUA
}
# An explicit non-numeric scale on the internal rule, alongside an unrelated
# omarchy_monitor_scale local that must not be substituted for it.
write_internal_monitor_auto_config() {
cat >"$monitor_lua" <<'LUA'
local omarchy_monitor_scale = 2
hl.monitor({ output = "eDP-1", mode = "preferred", position = "auto", scale = "auto" })
LUA
}
# The internal rule names a local that does not exist. The catch-all rule below
# it never applies to an output that has its own rule, so it must not be read.
write_internal_monitor_unresolvable_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "eDP-1", mode = "preferred", position = "auto", scale = my_scale })
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 1 })
LUA
}
# The shipped default: no rule for the internal panel, and the catch-all rule
# references the omarchy_monitor_scale local. Quoted here, and commented below.
write_catch_all_var_config() {
cat >"$monitor_lua" <<'LUA'
local omarchy_monitor_scale = "1.5"
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = omarchy_monitor_scale })
LUA
}
write_commented_catch_all_var_config() {
cat >"$monitor_lua" <<'LUA'
local omarchy_monitor_scale = 1.25 -- HiDPI panel
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = omarchy_monitor_scale })
LUA
}
# A position that references a local, the same pattern the scale key allows.
write_internal_monitor_position_var_config() {
cat >"$monitor_lua" <<'LUA'
local omarchy_monitor_position = "0x0"
hl.monitor({ output = "eDP-1", mode = "preferred", position = omarchy_monitor_position, scale = 1.25 })
LUA
}
# An internal rule that names no scale at all, so the catch-all supplies one.
write_internal_monitor_scaleless_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "eDP-1", mode = "preferred", position = "auto", transform = 1 })
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 1 })
LUA
}
# A local holding an expression rather than a scalar. Half of it is not a scale.
write_expression_scale_config() {
cat >"$monitor_lua" <<'LUA'
local omarchy_monitor_scale = 3 / 2
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = omarchy_monitor_scale })
LUA
}
# A local that happens to be named after a quoted scale value. The quotes make
# the rule's "auto" a string, so the local must not be substituted for it.
write_shadowed_auto_config() {
cat >"$monitor_lua" <<'LUA'
local auto = 1.5
hl.monitor({ output = "eDP-1", mode = "preferred", position = "auto", scale = "auto" })
LUA
}
# An expression written straight into the rule rather than into a local.
write_expression_rule_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 3 / 2 })
LUA
}
# Commented-out text after a rule is not a rule, and not one of its keys either.
write_commented_rule_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "DP-1", mode = "preferred", position = "auto", scale = 1 }) -- output = "eDP-1"
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 1.5 })
LUA
}
write_commented_internal_rule_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "eDP-1", mode = "preferred", position = "auto", scale = 1.25 }) -- scale = 3
LUA
}
# A nested table before the keys, and semicolons for separators. Both are Lua a
# user can reasonably write, and neither ends the rule.
write_nested_table_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "eDP-1", reserved_area = { top = 24 }, position = "0x0", scale = 1.25 })
LUA
}
write_block_comment_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "eDP-1", --[[ internal panel ]] position = "0x0", scale = 1.25 })
LUA
}
write_semicolon_config() {
cat >"$monitor_lua" <<'LUA'
hl.monitor({ output = "eDP-1"; position = "0x0"; scale = 1.25; transform = 1 })
LUA
}
remember_scale() {
mkdir -p "$state_dir"
printf '%s\n' "$1" >"$scale_state"
}
run_clamshell() {
HOME="$home_dir" \
PATH="$stub_bin:$PATH" \
@@ -116,3 +241,110 @@ OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'position = "0x0"' "$eval_log" >/dev/null || fail "clamshell recovery uses configured internal position"
grep -F 'scale = 1.25' "$eval_log" >/dev/null || fail "clamshell recovery uses configured internal scale"
pass "clamshell recovery uses configured internal monitor rule"
# Regression: specific-output rule referencing the omarchy_monitor_scale
# variable must resolve to the variable's value, not fall back to the default.
write_internal_monitor_var_config
rm -f "$scale_state"
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.5' "$eval_log" >/dev/null || fail "clamshell recovery resolves omarchy_monitor_scale variable reference"
pass "clamshell recovery resolves omarchy_monitor_scale variable reference"
# An explicit "auto" on the internal rule is a value, not a variable reference,
# so it must not pick up an unrelated omarchy_monitor_scale local.
write_internal_monitor_auto_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.75' "$eval_log" >/dev/null || fail "clamshell recovery keeps auto scale off the omarchy_monitor_scale local"
pass "clamshell recovery keeps auto scale off the omarchy_monitor_scale local"
# The catch-all rule does not apply to an output that has its own rule.
write_internal_monitor_unresolvable_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.75' "$eval_log" >/dev/null || fail "clamshell recovery ignores the catch-all rule when the internal panel has its own"
pass "clamshell recovery ignores the catch-all rule when the internal panel has its own"
write_catch_all_var_config
rm -f "$scale_state"
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.5' "$eval_log" >/dev/null || fail "clamshell recovery resolves a quoted scale through the catch-all rule"
pass "clamshell recovery resolves a quoted scale through the catch-all rule"
write_commented_catch_all_var_config
rm -f "$scale_state"
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.25' "$eval_log" >/dev/null || fail "clamshell recovery ignores a trailing comment on the scale local"
pass "clamshell recovery ignores a trailing comment on the scale local"
write_internal_monitor_position_var_config
rm -f "$scale_state"
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'position = "0x0"' "$eval_log" >/dev/null || fail "clamshell recovery resolves a position variable reference"
pass "clamshell recovery resolves a position variable reference"
# An internal rule that names no scale leaves the catch-all to supply one. Only
# a rule that names a scale keeps the catch-all away from the internal panel.
write_internal_monitor_scaleless_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1' "$eval_log" >/dev/null || fail "clamshell recovery takes the catch-all scale when the internal rule omits one"
! grep -F 'scale = 1.75' "$eval_log" >/dev/null || fail "clamshell recovery prefers the catch-all scale over the remembered one"
pass "clamshell recovery takes the catch-all scale when the internal rule omits one"
# Half of `3 / 2` is not the scale the user asked for.
write_expression_scale_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.75' "$eval_log" >/dev/null || fail "clamshell recovery leaves an expression scale unresolved"
pass "clamshell recovery leaves an expression scale unresolved"
# A quoted value is a string, not a reference to a local of the same name.
write_shadowed_auto_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.75' "$eval_log" >/dev/null || fail "clamshell recovery does not resolve a quoted scale against a local"
pass "clamshell recovery does not resolve a quoted scale against a local"
# Half of `3 / 2` is no better inside the rule than inside a local.
write_expression_rule_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.75' "$eval_log" >/dev/null || fail "clamshell recovery leaves an expression scale in a rule unresolved"
pass "clamshell recovery leaves an expression scale in a rule unresolved"
# A commented-out output must not pass for a rule the internal panel owns.
write_commented_rule_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.5' "$eval_log" >/dev/null || fail "clamshell recovery does not read a rule out of a trailing comment"
pass "clamshell recovery does not read a rule out of a trailing comment"
# Nor must a commented-out key displace the real one.
write_commented_internal_rule_config
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'scale = 1.25' "$eval_log" >/dev/null || fail "clamshell recovery does not read a scale out of a trailing comment"
pass "clamshell recovery does not read a scale out of a trailing comment"
for config in nested_table semicolon block_comment; do
"write_${config}_config"
remember_scale 1.75
: >"$eval_log"
OMARCHY_TEST_INTERNAL_DISABLED=true run_clamshell
grep -F 'position = "0x0"' "$eval_log" >/dev/null || fail "clamshell recovery reads the position out of a ${config//_/ } rule"
grep -F 'scale = 1.25' "$eval_log" >/dev/null || fail "clamshell recovery reads the scale out of a ${config//_/ } rule"
pass "clamshell recovery reads a ${config//_/ } rule"
done