GTK only honors whole-number GDK_SCALE values, so persisting 1.6 or 1.25 verbatim left GTK apps without a usable scale. Round to the nearest whole factor when writing monitors.lua. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
196 lines
6.8 KiB
Bash
Executable File
196 lines
6.8 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Show, set, or adjust focused Hyprland monitor scaling
|
|
# 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
|
|
|
|
SCALES=(1 1.25 1.6 2 3 4)
|
|
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy"
|
|
SCALE_LOG="$STATE_DIR/monitor-scaling.log"
|
|
|
|
usage() {
|
|
echo "Usage: omarchy-hyprland-monitor-scaling [up|down|SCALE]"
|
|
}
|
|
|
|
focused_monitor_scale() {
|
|
hyprctl monitors -j | jq -er '.[] | select(.focused == true) | .scale'
|
|
}
|
|
|
|
cmdline_for_pid() {
|
|
local pid="$1"
|
|
|
|
[[ -r /proc/$pid/cmdline ]] || return 0
|
|
tr '\0\t\n' ' ' <"/proc/$pid/cmdline" | sed -E 's/[[:space:]]+/ /g; s/[[:space:]]+$//'
|
|
}
|
|
|
|
audit_scale_change() {
|
|
local requested="$1"
|
|
local active_monitor="$2"
|
|
local current_scale="$3"
|
|
local new_scale="$4"
|
|
local parent_pid="$PPID"
|
|
local grandparent_pid
|
|
local parent_cmd
|
|
local grandparent_cmd
|
|
|
|
mkdir -p "$STATE_DIR" || return 0
|
|
|
|
grandparent_pid=$(ps -o ppid= -p "$parent_pid" 2>/dev/null | tr -d ' ')
|
|
parent_cmd=$(cmdline_for_pid "$parent_pid")
|
|
grandparent_cmd=$(cmdline_for_pid "$grandparent_pid")
|
|
|
|
printf 'at=%s\trequested=%s\tcurrent=%s\tnew=%s\tmonitor=%s\tpid=%s\tppid=%s\tparent=%s\tgppid=%s\tgrandparent=%s\n' \
|
|
"$(date --iso-8601=seconds)" \
|
|
"$requested" \
|
|
"$current_scale" \
|
|
"$new_scale" \
|
|
"$active_monitor" \
|
|
"$$" \
|
|
"$parent_pid" \
|
|
"$parent_cmd" \
|
|
"$grandparent_pid" \
|
|
"$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 clean value.
|
|
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
|
|
}'
|
|
}
|
|
|
|
normalize_scale() {
|
|
awk 'NR == 1 { printf "%g\n", $0 }'
|
|
}
|
|
|
|
set_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")"
|
|
# GTK only honors integer GDK_SCALE values, so persist the nearest whole
|
|
# factor even when the monitor scale itself is fractional.
|
|
local new_gdk_scale="$(awk -v scale="$new_scale" 'BEGIN { printf "%d", int(scale + 0.5) }')"
|
|
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
|
|
audit_scale_change "$requested" "$active_monitor" "$current_scale" "$new_scale"
|
|
|
|
# Persist to monitors.lua if the user still has Omarchy's generic catch-all
|
|
# defaults, so the scale survives reboots.
|
|
if [[ -f $monitor_lua ]] && grep -q '^local omarchy_monitor_scale = ' "$monitor_lua"; then
|
|
sed -i -E \
|
|
-e "s|^local omarchy_monitor_scale = .*|local omarchy_monitor_scale = ${new_scale}|" \
|
|
-e "s|^local omarchy_gdk_scale = .*|local omarchy_gdk_scale = ${new_gdk_scale}|" \
|
|
"$monitor_lua"
|
|
elif [[ -f $monitor_lua ]] && grep -Eq '^hl\.monitor\(\{ output = "", mode = "preferred", position = "auto", scale = ("auto"|[0-9.]+) \}\)' "$monitor_lua"; then
|
|
sed -i -E \
|
|
-e "s|^(hl\.monitor\(\{ output = \"\", mode = \"preferred\", position = \"auto\", scale = )([^ ]+)( \}\))|\\1${new_scale}\\3|" \
|
|
-e 's|^hl\.env\("GDK_SCALE", ".*"\)|hl.env("GDK_SCALE", "'"$new_gdk_scale"'")|' \
|
|
"$monitor_lua"
|
|
fi
|
|
}
|
|
|
|
scale_from_current() {
|
|
local direction="${1:-}"
|
|
local width="${2:-}"
|
|
local height="${3:-}"
|
|
|
|
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 }
|
|
END {
|
|
if (!found) exit 1
|
|
|
|
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
|
|
|
|
# Multiple presets can collapse to the same clean scale. Keep only the
|
|
# closest label so stepping always moves to a distinct effective value.
|
|
if (!(key in effective_index)) {
|
|
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
|
|
for (i = 1; i <= n; i++) {
|
|
diff = scale - effective_scales[i]; if (diff < 0) diff = -diff
|
|
if (diff < best_diff) { best_diff = diff; best = i }
|
|
}
|
|
|
|
if (direction == "next") {
|
|
print scales[(best < n ? best + 1 : n)]
|
|
} else if (direction == "previous") {
|
|
print scales[(best > 1 ? best - 1 : 1)]
|
|
} else {
|
|
print scales[best]
|
|
}
|
|
}'
|
|
}
|
|
|
|
case "${1:-}" in
|
|
"")
|
|
focused_monitor_scale | normalize_scale
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
;;
|
|
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)
|
|
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)
|
|
set_scale "$1" "$1"
|
|
;;
|
|
*)
|
|
if [[ $1 =~ ^[0-9]+([.][0-9]+)?$ ]] &&
|
|
awk -v scale="$1" 'BEGIN { exit !(scale >= 1 && scale <= 4) }'; then
|
|
set_scale "$1" "$1"
|
|
else
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|