diff --git a/bin/omarchy-hyprland-monitor-scaling b/bin/omarchy-hyprland-monitor-scaling index d109c97c..f90c078e 100755 --- a/bin/omarchy-hyprland-monitor-scaling +++ b/bin/omarchy-hyprland-monitor-scaling @@ -52,15 +52,32 @@ audit_scale_change() { "$grandparent_cmd" >>"$SCALE_LOG" } +# 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). +# Round the requested scale up to the nearest one, so 3x on a 1280x800 QEMU +# display becomes 3.2x instead of a red error overlay. +clean_scale() { + 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 } + BEGIN { + g = gcd(width * 120, height * 120) + k = int(scale * 120 + 0.5) + if (k > g) k = g + while (g % k != 0) k++ + printf "%g\n", k / 120 + }' +} + set_scale() { - local new_scale="$1" - local requested="${2:-$new_scale}" + local requested_scale="$1" + local requested="${2:-$requested_scale}" local monitor_info="$(hyprctl monitors -j | jq -e -c '.[] | select(.focused == true)')" local active_monitor="$(echo "$monitor_info" | jq -r '.name')" local current_scale="$(echo "$monitor_info" | jq -r '.scale')" local width="$(echo "$monitor_info" | jq -r '.width')" local height="$(echo "$monitor_info" | jq -r '.height')" local refresh_rate="$(echo "$monitor_info" | jq -r '.refreshRate')" + local new_scale="$(clean_scale "$requested_scale" "$width" "$height")" local monitor_lua="$HOME/.config/hypr/monitors.lua" hyprctl eval "hl.monitor({ output = \"$active_monitor\", mode = \"${width}x${height}@${refresh_rate}\", position = \"auto\", scale = $new_scale })" >/dev/null diff --git a/test/shell.d/monitor-scaling-test.sh b/test/shell.d/monitor-scaling-test.sh index b80cfc5c..01887c62 100644 --- a/test/shell.d/monitor-scaling-test.sh +++ b/test/shell.d/monitor-scaling-test.sh @@ -19,7 +19,8 @@ cat >"$stub_bin/hyprctl" <<'SH' #!/bin/bash if [[ $1 == "monitors" && $2 == "-j" ]]; then - printf '[{"name":"eDP-1","focused":true,"scale":%s,"width":2880,"height":1800,"refreshRate":120.0}]' "${OMARCHY_TEST_MONITOR_SCALE:-2}" + printf '[{"name":"eDP-1","focused":true,"scale":%s,"width":%s,"height":%s,"refreshRate":120.0}]' \ + "${OMARCHY_TEST_MONITOR_SCALE:-2}" "${OMARCHY_TEST_MONITOR_WIDTH:-2880}" "${OMARCHY_TEST_MONITOR_HEIGHT:-1800}" elif [[ $1 == "eval" ]]; then printf '%s\n' "$2" >"$OMARCHY_TEST_HYPRCTL_EVAL_OUT" else @@ -72,3 +73,15 @@ pass "monitor scaling explicit 3x remains available" scale=$(OMARCHY_TEST_MONITOR_SCALE=3 run_scaling) [[ $scale == "3" ]] || fail "monitor scaling reports explicit 3x scale" "actual: $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. +write_monitor_config +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 -Fx 'local omarchy_monitor_scale = 3.2' "$monitor_lua" >/dev/null || fail "monitor scaling persists snapped 3.2x" +pass "monitor scaling snaps unclean 3x up to 3.2x" + +write_monitor_config +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" +pass "monitor scaling up snaps unclean preset to 3.2x"