Keep display scaling options consistent
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Show, set, or adjust focused Hyprland monitor scaling
|
# omarchy:summary=Show, set, or adjust focused Hyprland monitor scaling
|
||||||
# omarchy:args=[up|down|1|1.25|1.6|2|3|4]
|
# omarchy:args=[up|down|SCALE]
|
||||||
# omarchy:examples=omarchy hyprland monitor scaling | omarchy hyprland monitor scaling 1.6 | omarchy hyprland monitor scaling up | omarchy hyprland monitor scaling down
|
# omarchy:examples=omarchy hyprland monitor scaling | omarchy hyprland monitor scaling 1.6 | omarchy hyprland monitor scaling up | omarchy hyprland monitor scaling down
|
||||||
|
|
||||||
SCALES=(1 1.25 1.6 2 3 4)
|
SCALES=(1 1.25 1.6 2 3 4)
|
||||||
@@ -9,7 +9,7 @@ STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy"
|
|||||||
SCALE_LOG="$STATE_DIR/monitor-scaling.log"
|
SCALE_LOG="$STATE_DIR/monitor-scaling.log"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "Usage: omarchy-hyprland-monitor-scaling [up|down|1|1.25|1.6|2|3|4]"
|
echo "Usage: omarchy-hyprland-monitor-scaling [up|down|SCALE]"
|
||||||
}
|
}
|
||||||
|
|
||||||
focused_monitor_scale() {
|
focused_monitor_scale() {
|
||||||
@@ -54,8 +54,7 @@ audit_scale_change() {
|
|||||||
|
|
||||||
# Hyprland only accepts scales where the mode divides into whole logical
|
# Hyprland only accepts scales where the mode divides into whole logical
|
||||||
# pixels (in 1/120 steps), so clean scales are divisors of gcd(w*120, h*120).
|
# pixels (in 1/120 steps), so clean scales are divisors of gcd(w*120, h*120).
|
||||||
# Round the requested scale up to the nearest one, so 3x on a 1280x800 QEMU
|
# Round the requested scale up to the nearest clean value.
|
||||||
# display becomes 3.2x instead of a red error overlay.
|
|
||||||
clean_scale() {
|
clean_scale() {
|
||||||
awk -v scale="$1" -v width="$2" -v height="$3" '
|
awk -v scale="$1" -v width="$2" -v height="$3" '
|
||||||
function gcd(a, b, t) { while (b) { t = a % b; a = b; b = t } return a }
|
function gcd(a, b, t) { while (b) { t = a % b; a = b; b = t } return a }
|
||||||
@@ -68,6 +67,10 @@ clean_scale() {
|
|||||||
}'
|
}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalize_scale() {
|
||||||
|
awk 'NR == 1 { printf "%g\n", $0 }'
|
||||||
|
}
|
||||||
|
|
||||||
set_scale() {
|
set_scale() {
|
||||||
local requested_scale="$1"
|
local requested_scale="$1"
|
||||||
local requested="${2:-$requested_scale}"
|
local requested="${2:-$requested_scale}"
|
||||||
@@ -100,21 +103,50 @@ set_scale() {
|
|||||||
|
|
||||||
scale_from_current() {
|
scale_from_current() {
|
||||||
local direction="${1:-}"
|
local direction="${1:-}"
|
||||||
|
local width="${2:-}"
|
||||||
|
local height="${3:-}"
|
||||||
|
|
||||||
awk -v direction="$direction" -v list="${SCALES[*]}" '
|
awk -v direction="$direction" -v list="${SCALES[*]}" -v width="$width" -v height="$height" '
|
||||||
|
function gcd(a, b, t) { while (b) { t = a % b; a = b; b = t } return a }
|
||||||
|
function clean(scale, g, k) {
|
||||||
|
g = gcd(width * 120, height * 120)
|
||||||
|
k = int(scale * 120 + 0.5)
|
||||||
|
if (k > g) k = g
|
||||||
|
while (g % k != 0) k++
|
||||||
|
return k / 120
|
||||||
|
}
|
||||||
NR == 1 { scale = $0; found = 1 }
|
NR == 1 { scale = $0; found = 1 }
|
||||||
END {
|
END {
|
||||||
if (!found) exit 1
|
if (!found) exit 1
|
||||||
|
|
||||||
n = split(list, scales, " ")
|
preset_count = split(list, presets, " ")
|
||||||
|
for (i = 1; i <= preset_count; i++) {
|
||||||
|
effective = clean(presets[i])
|
||||||
|
key = sprintf("%.8f", effective)
|
||||||
|
distance = presets[i] - effective
|
||||||
|
if (distance < 0) distance = -distance
|
||||||
|
|
||||||
# Snap to the nearest preset first. Hyprland reports scales as floating
|
# Multiple presets can collapse to the same clean scale. Keep only the
|
||||||
# point values, so a scale set to 3 can come back as 3.0000000000000004.
|
# closest label so stepping always moves to a distinct effective value.
|
||||||
# Without snapping, the directional comparisons below can misidentify
|
if (!(key in effective_index)) {
|
||||||
# the current preset and refuse to step down (or up) any further.
|
effective_index[key] = ++n
|
||||||
|
effective_scales[n] = effective
|
||||||
|
scales[n] = presets[i]
|
||||||
|
distances[n] = distance
|
||||||
|
} else {
|
||||||
|
idx = effective_index[key]
|
||||||
|
if (distance < distances[idx]) {
|
||||||
|
scales[idx] = presets[i]
|
||||||
|
distances[idx] = distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Snap to the nearest effective scale first. Hyprland reports floating
|
||||||
|
# point values, so exact comparisons can otherwise get stuck.
|
||||||
best = 1; best_diff = 1e9
|
best = 1; best_diff = 1e9
|
||||||
for (i = 1; i <= n; i++) {
|
for (i = 1; i <= n; i++) {
|
||||||
diff = scale - scales[i]; if (diff < 0) diff = -diff
|
diff = scale - effective_scales[i]; if (diff < 0) diff = -diff
|
||||||
if (diff < best_diff) { best_diff = diff; best = i }
|
if (diff < best_diff) { best_diff = diff; best = i }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,22 +162,31 @@ scale_from_current() {
|
|||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
"")
|
"")
|
||||||
focused_monitor_scale | scale_from_current
|
focused_monitor_scale | normalize_scale
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
up)
|
up)
|
||||||
set_scale "$(focused_monitor_scale | scale_from_current next)" "up"
|
monitor_info=$(hyprctl monitors -j | jq -e -c '.[] | select(.focused == true)')
|
||||||
|
set_scale "$(echo "$monitor_info" | jq -r '.scale' | scale_from_current next \
|
||||||
|
"$(echo "$monitor_info" | jq -r '.width')" "$(echo "$monitor_info" | jq -r '.height')")" "up"
|
||||||
;;
|
;;
|
||||||
down)
|
down)
|
||||||
set_scale "$(focused_monitor_scale | scale_from_current previous)" "down"
|
monitor_info=$(hyprctl monitors -j | jq -e -c '.[] | select(.focused == true)')
|
||||||
|
set_scale "$(echo "$monitor_info" | jq -r '.scale' | scale_from_current previous \
|
||||||
|
"$(echo "$monitor_info" | jq -r '.width')" "$(echo "$monitor_info" | jq -r '.height')")" "down"
|
||||||
;;
|
;;
|
||||||
1 | 1.25 | 1.6 | 2 | 3 | 4)
|
1 | 1.25 | 1.6 | 2 | 3 | 4)
|
||||||
set_scale "$1" "$1"
|
set_scale "$1" "$1"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
usage >&2
|
if [[ $1 =~ ^[0-9]+([.][0-9]+)?$ ]] &&
|
||||||
exit 1
|
awk -v scale="$1" 'BEGIN { exit !(scale >= 1 && scale <= 4) }'; then
|
||||||
|
set_scale "$1" "$1"
|
||||||
|
else
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -60,10 +60,7 @@ function availableScales(scales, width, height) {
|
|||||||
var requested = Number(scales[i])
|
var requested = Number(scales[i])
|
||||||
var effective = Number(cleanScale(requested, width, height))
|
var effective = Number(cleanScale(requested, width, height))
|
||||||
|
|
||||||
// Clean scales round upward. If the mode cannot reach the requested
|
if (!isFinite(requested) || !isFinite(effective)) continue
|
||||||
// scale, cleanScale caps at its largest valid value and this preset is
|
|
||||||
// not actually available.
|
|
||||||
if (!isFinite(requested) || !isFinite(effective) || effective < requested) continue
|
|
||||||
|
|
||||||
var key = normalizeScale(effective)
|
var key = normalizeScale(effective)
|
||||||
var existing = byEffectiveScale[key]
|
var existing = byEffectiveScale[key]
|
||||||
|
|||||||
@@ -263,6 +263,15 @@ Panel {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function effectiveScale(scale) {
|
||||||
|
for (var i = 0; i < displays.length; i++) {
|
||||||
|
var display = displays[i]
|
||||||
|
if (display && display.focused)
|
||||||
|
return Model.cleanScale(scale, display.width, display.height)
|
||||||
|
}
|
||||||
|
return normalizeScale(scale)
|
||||||
|
}
|
||||||
|
|
||||||
// Playful mood-name for a given brightness percent. Bands intentionally
|
// Playful mood-name for a given brightness percent. Bands intentionally
|
||||||
// span ~10–20 points so casual tweaks change the label, while small
|
// span ~10–20 points so casual tweaks change the label, while small
|
||||||
// nudges within one band don't.
|
// nudges within one band don't.
|
||||||
@@ -781,7 +790,7 @@ Panel {
|
|||||||
required property string scaleValue
|
required property string scaleValue
|
||||||
required property int scaleIndex
|
required property int scaleIndex
|
||||||
|
|
||||||
text: scaleValue + "x"
|
text: root.effectiveScale(scaleValue) + "x"
|
||||||
fontSize: Style.font.caption
|
fontSize: Style.font.caption
|
||||||
foreground: root.bar.foreground
|
foreground: root.bar.foreground
|
||||||
fontFamily: root.bar.fontFamily
|
fontFamily: root.bar.fontFamily
|
||||||
|
|||||||
@@ -74,14 +74,42 @@ scale=$(OMARCHY_TEST_MONITOR_SCALE=3 run_scaling)
|
|||||||
[[ $scale == "3" ]] || fail "monitor scaling reports explicit 3x scale" "actual: $scale"
|
[[ $scale == "3" ]] || fail "monitor scaling reports explicit 3x scale" "actual: $scale"
|
||||||
pass "monitor scaling reports explicit 3x scale"
|
pass "monitor scaling reports explicit 3x scale"
|
||||||
|
|
||||||
# 1280x800 (QEMU virtio-gpu) can't divide cleanly by 3; expect a snap up to 3.2.
|
scale=$(OMARCHY_TEST_MONITOR_SCALE=3.2 run_scaling)
|
||||||
|
[[ $scale == "3.2" ]] || fail "monitor scaling reports the actual non-preset scale" "actual: $scale"
|
||||||
|
pass "monitor scaling reports the actual non-preset scale"
|
||||||
|
|
||||||
|
# 1280x800 approximates the 3x preset as 3.2x.
|
||||||
write_monitor_config
|
write_monitor_config
|
||||||
OMARCHY_TEST_MONITOR_SCALE=2 OMARCHY_TEST_MONITOR_WIDTH=1280 OMARCHY_TEST_MONITOR_HEIGHT=800 run_scaling 3
|
OMARCHY_TEST_MONITOR_SCALE=2 OMARCHY_TEST_MONITOR_WIDTH=1280 OMARCHY_TEST_MONITOR_HEIGHT=800 run_scaling 3
|
||||||
grep -F 'scale = 3.2' "$eval_out" >/dev/null || fail "monitor scaling snaps unclean 3x up to 3.2x"
|
grep -F 'scale = 3.2' "$eval_out" >/dev/null || fail "monitor scaling approximates explicit 3x as 3.2x"
|
||||||
grep -Fx 'local omarchy_monitor_scale = 3.2' "$monitor_lua" >/dev/null || fail "monitor scaling persists snapped 3.2x"
|
grep -Fx 'local omarchy_monitor_scale = 3.2' "$monitor_lua" >/dev/null ||
|
||||||
pass "monitor scaling snaps unclean 3x up to 3.2x"
|
fail "monitor scaling persists approximated 3.2x"
|
||||||
|
pass "monitor scaling approximates explicit 3x as 3.2x"
|
||||||
|
|
||||||
write_monitor_config
|
write_monitor_config
|
||||||
OMARCHY_TEST_MONITOR_SCALE=2 OMARCHY_TEST_MONITOR_WIDTH=1280 OMARCHY_TEST_MONITOR_HEIGHT=800 run_scaling up
|
OMARCHY_TEST_MONITOR_SCALE=2 OMARCHY_TEST_MONITOR_WIDTH=1280 OMARCHY_TEST_MONITOR_HEIGHT=800 run_scaling up
|
||||||
grep -F 'scale = 3.2' "$eval_out" >/dev/null || fail "monitor scaling up snaps unclean preset to 3.2x"
|
grep -F 'scale = 3.2' "$eval_out" >/dev/null || fail "monitor scaling up reaches approximated 3.2x"
|
||||||
pass "monitor scaling up snaps unclean preset to 3.2x"
|
pass "monitor scaling up reaches approximated 3.2x"
|
||||||
|
|
||||||
|
write_monitor_config
|
||||||
|
OMARCHY_TEST_MONITOR_SCALE=4 OMARCHY_TEST_MONITOR_WIDTH=1280 OMARCHY_TEST_MONITOR_HEIGHT=800 run_scaling down
|
||||||
|
grep -F 'scale = 3.2' "$eval_out" >/dev/null || fail "monitor scaling down reaches approximated 3.2x"
|
||||||
|
pass "monitor scaling down reaches approximated 3.2x"
|
||||||
|
|
||||||
|
write_monitor_config
|
||||||
|
OMARCHY_TEST_MONITOR_SCALE=2 OMARCHY_TEST_MONITOR_WIDTH=6016 OMARCHY_TEST_MONITOR_HEIGHT=3384 run_scaling 1.25
|
||||||
|
grep -F 'scale = 1.33333' "$eval_out" >/dev/null || fail "monitor scaling approximates explicit 1.25x"
|
||||||
|
pass "monitor scaling approximates explicit 1.25x"
|
||||||
|
|
||||||
|
write_monitor_config
|
||||||
|
OMARCHY_TEST_MONITOR_SCALE=2 OMARCHY_TEST_MONITOR_WIDTH=1280 OMARCHY_TEST_MONITOR_HEIGHT=800 run_scaling 3.2
|
||||||
|
grep -F 'scale = 3.2' "$eval_out" >/dev/null || fail "monitor scaling accepts displayed approximate values"
|
||||||
|
pass "monitor scaling accepts displayed approximate values"
|
||||||
|
|
||||||
|
# On a mode where both 3x and 4x resolve to 4x, the duplicate is one step.
|
||||||
|
write_monitor_config
|
||||||
|
OMARCHY_TEST_MONITOR_SCALE=4 OMARCHY_TEST_MONITOR_WIDTH=1280 OMARCHY_TEST_MONITOR_HEIGHT=804 run_scaling down
|
||||||
|
grep -F 'scale = 2' "$eval_out" >/dev/null || fail "monitor scaling down skips duplicate 4x approximation"
|
||||||
|
grep -Fx 'local omarchy_monitor_scale = 2' "$monitor_lua" >/dev/null ||
|
||||||
|
fail "monitor scaling down persists 2x after skipping duplicate approximation"
|
||||||
|
pass "monitor scaling down skips duplicate approximation"
|
||||||
|
|||||||
@@ -21,17 +21,27 @@ assertEqual(monitor.cleanScale(1.6, 0, 800), '', 'monitor rejects a missing disp
|
|||||||
assertEqual(
|
assertEqual(
|
||||||
monitor.matchingScaleIndex(['1', '1.25', '1.6', '2', '3', '4'], 3.2, 1280, 800),
|
monitor.matchingScaleIndex(['1', '1.25', '1.6', '2', '3', '4'], 3.2, 1280, 800),
|
||||||
4,
|
4,
|
||||||
'monitor selects requested 3x for effective VM scale'
|
'monitor selects an approximated VM scale'
|
||||||
)
|
)
|
||||||
assertEqual(
|
assertEqual(
|
||||||
monitor.matchingScaleIndex(['1', '1.25', '1.6', '2', '3', '4'], 4, 4, 4),
|
monitor.matchingScaleIndex(['1', '1.25', '1.6', '2', '3', '4'], 4, 4, 4),
|
||||||
5,
|
5,
|
||||||
'monitor selects only the closest preset when clean scales collide'
|
'monitor selects an exact preset'
|
||||||
)
|
)
|
||||||
assertDeepEqual(
|
assertDeepEqual(
|
||||||
monitor.availableScales(['1', '1.25', '1.6', '2', '3', '4'], 1280, 800),
|
monitor.availableScales(['1', '1.25', '1.6', '2', '3', '4'], 1280, 800),
|
||||||
['1', '1.25', '1.6', '2', '3', '4'],
|
['1', '1.25', '1.6', '2', '3', '4'],
|
||||||
'monitor keeps presets that produce unique reachable VM scales'
|
'monitor keeps distinct approximated VM scales'
|
||||||
|
)
|
||||||
|
assertDeepEqual(
|
||||||
|
monitor.availableScales(['1', '1.25', '1.6', '2', '3', '4'], 6016, 3384),
|
||||||
|
['1', '1.25', '1.6', '2', '3', '4'],
|
||||||
|
'monitor keeps distinct approximated physical display scales'
|
||||||
|
)
|
||||||
|
assertDeepEqual(
|
||||||
|
monitor.availableScales(['1', '1.25', '1.6', '2', '3', '4'], 1280, 804),
|
||||||
|
['1', '1.25', '2', '4'],
|
||||||
|
'monitor collapses presets with duplicate effective scales'
|
||||||
)
|
)
|
||||||
assertDeepEqual(
|
assertDeepEqual(
|
||||||
monitor.availableScales(['1', '1.25', '1.6', '2', '3', '4'], 5968, 3230),
|
monitor.availableScales(['1', '1.25', '1.6', '2', '3', '4'], 5968, 3230),
|
||||||
|
|||||||
Reference in New Issue
Block a user