Draw idle as one timeline, and let the power button answer to its owner
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -91,23 +91,159 @@ cmd_close() {
|
||||
hyprctl eval "hl.monitor({ output = \"$internal\", disable = true })" >/dev/null
|
||||
}
|
||||
|
||||
# Re-enable on open, preferring what the person chose for this panel in
|
||||
# Settings (the same displays store monitors.lua reads at startup) and
|
||||
# falling back to the panel's preferred mode.
|
||||
# The stored display record for one output, rendered as hl.monitor arguments.
|
||||
#
|
||||
# This exists because the four-key version it replaces was a clobber. Opening
|
||||
# the lid emitted mode, position="auto" and scale and nothing else -- and an
|
||||
# hl.monitor call REPLACES the rule for that output whole, so every other field
|
||||
# the person had chosen for the panel went with it. Transform, VRR, bit depth,
|
||||
# colour profile, SDR trim, mirroring and, worst of all, the position: a panel
|
||||
# arranged to the left of an external display jumped back to automatic
|
||||
# placement, taking its workspaces with it, every time the lid was opened.
|
||||
#
|
||||
# So the rule is rebuilt from the same store config/dot/hypr/monitors.lua reads
|
||||
# at startup, with the same validation, so that opening the lid produces the
|
||||
# rule a reload would have produced. Mirroring that file field for field is the
|
||||
# point; the two must not be able to disagree about what a saved record means.
|
||||
#
|
||||
# The two failure directions are deliberately different, exactly as they are
|
||||
# there:
|
||||
#
|
||||
# * mode, scale or transform unreadable -- or a half-written position, where
|
||||
# a record carries some layout fields but not a valid pair -- refuses the
|
||||
# WHOLE record. Nothing is printed and the caller falls back to the panel's
|
||||
# preferred mode. Guessing a position can strand an output where no cursor
|
||||
# can reach it.
|
||||
# * an unreadable colour, VRR, SDR or mirror value drops only itself. The
|
||||
# geometry survives, and the worst it costs is a wrong shade.
|
||||
#
|
||||
# Every value that reaches the emitted string is validated first -- modes and
|
||||
# positions against a numeric pattern, connector names against the same
|
||||
# `[%w_.-]` class monitors.lua uses, colour profiles against a fixed set -- so
|
||||
# nothing from the settings file can carry a quote into the Lua that is built
|
||||
# from it.
|
||||
monitor_rule() {
|
||||
local name="$1" settings="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json"
|
||||
[[ -r "$settings" ]] || return 0
|
||||
jq -r --arg name "$name" '
|
||||
def numeric: if type == "number" and (isnan | not) and (isinfinite | not)
|
||||
then . else null end;
|
||||
def whole: numeric | if . != null and . == floor then . else null end;
|
||||
|
||||
. as $root
|
||||
| ($root.displays[$name] // null) as $e
|
||||
| if ($e | type) != "object" then "" else
|
||||
|
||||
# ── Geometry: all of it, or none of it ───────────────────────────────
|
||||
($e.mode | if type == "string"
|
||||
and test("^[0-9]+x[0-9]+@[0-9]+(\\.[0-9]+)?$")
|
||||
then . else null end) as $mode
|
||||
| ($mode | if . == null then null
|
||||
else capture("^(?<w>[0-9]+)x(?<h>[0-9]+)@(?<r>[0-9.]+)$")
|
||||
| [(.w | tonumber), (.h | tonumber), (.r | tonumber)]
|
||||
end) as $dim
|
||||
| (if $dim == null or $dim[0] <= 0 or $dim[1] <= 0 or $dim[2] <= 0
|
||||
then null else $mode end) as $mode
|
||||
|
||||
# A scale is only valid if it divides the mode into whole logical
|
||||
# pixels; Hyprland refuses the rest, and monitors.lua refuses them here
|
||||
# first so the two agree about which records are usable.
|
||||
| ($e.scale | numeric | if . != null and . > 0 and . <= 4
|
||||
then . else null end) as $rawScale
|
||||
| (if $mode == null or $rawScale == null then null
|
||||
else ($dim[0] / $rawScale) as $lw
|
||||
| ($dim[1] / $rawScale) as $lh
|
||||
| if ((($lw - ($lw | round)) | fabs) < 0.0001)
|
||||
and ((($lh - ($lh | round)) | fabs) < 0.0001)
|
||||
then $rawScale else null end
|
||||
end) as $scale
|
||||
| ($e.transform | whole
|
||||
| if . != null and . >= 0 and . <= 3 then . else null end) as $transform
|
||||
|
||||
# Legacy records carry no layout fields at all and keep automatic
|
||||
# placement. A record that carries SOME of them and gets one wrong is
|
||||
# refused outright rather than half-honoured.
|
||||
| (($e.x != null) or ($e.y != null) or ($e.primary != null)) as $hasLayout
|
||||
| ($e.x | whole | if . != null and . >= -100000 and . <= 100000
|
||||
then . else null end) as $x
|
||||
| ($e.y | whole | if . != null and . >= -100000 and . <= 100000
|
||||
then . else null end) as $y
|
||||
| ($hasLayout
|
||||
and ($x == null or $y == null or ($e.primary | type) != "boolean")) as $layoutBroken
|
||||
|
||||
# ── Extended fields: each one drops on its own ───────────────────────
|
||||
| ($e.bitdepth | if . == 8 or . == 10 then . else null end) as $bitdepth
|
||||
| ($e.colorProfile
|
||||
| if . == "auto" or . == "srgb" or . == "wide" or . == "hdr"
|
||||
then . else null end) as $cm
|
||||
# -1 means "follow the global VRR policy", which is said by leaving the
|
||||
# key out; 3 is the global policy value and not a per-display choice.
|
||||
| ($e.vrrMode | whole | if . != null and . >= 0 and . <= 2
|
||||
then . else null end) as $vrr
|
||||
# Neutral is 1.0 and is left out rather than written: naming it pins the
|
||||
# display to it, which is not the same as leaving the trim alone.
|
||||
| ($e.sdrBrightness | numeric
|
||||
| if . != null and . >= 0.8 and . <= 2.0 and (((. - 1) | fabs) >= 0.001)
|
||||
then . else null end) as $sdrBrightness
|
||||
| ($e.sdrSaturation | numeric
|
||||
| if . != null and . >= 0.8 and . <= 1.2 and (((. - 1) | fabs) >= 0.001)
|
||||
then . else null end) as $sdrSaturation
|
||||
# A mirror needs a target that is not itself and not another mirror --
|
||||
# Hyprland has no chain to follow -- and the primary may not mirror at
|
||||
# all, since the arrangement is anchored on it.
|
||||
| ($e.mirrorOf
|
||||
| if type == "string" and . != "" and . != $name
|
||||
and test("^[A-Za-z0-9_.-]+$")
|
||||
then . else null end) as $mirrorName
|
||||
| (if $mirrorName == null or $e.primary == true then null
|
||||
else ($root.displays[$mirrorName] // null) as $target
|
||||
| if ($target | type) == "object"
|
||||
and ($target.mirrorOf | type) == "string"
|
||||
and $target.mirrorOf != ""
|
||||
then null else $mirrorName end
|
||||
end) as $mirror
|
||||
|
||||
# A mirror shows its target picture in its target place, so the saved
|
||||
# position is not ours to ask for.
|
||||
| (if $mirror != null then "auto"
|
||||
elif $hasLayout then "\($x)x\($y)"
|
||||
else "auto" end) as $position
|
||||
|
||||
| if $mode == null or $scale == null or $transform == null or $layoutBroken
|
||||
then ""
|
||||
else ([ "mode = \"\($mode)\"",
|
||||
"position = \"\($position)\"",
|
||||
"scale = \($scale)",
|
||||
"transform = \($transform)" ]
|
||||
+ (if $bitdepth == null then [] else ["bitdepth = \($bitdepth)"] end)
|
||||
+ (if $cm == null then [] else ["cm = \"\($cm)\""] end)
|
||||
+ (if $vrr == null then [] else ["vrr = \($vrr)"] end)
|
||||
+ (if $sdrBrightness == null then []
|
||||
else ["sdrbrightness = \($sdrBrightness)"] end)
|
||||
+ (if $sdrSaturation == null then []
|
||||
else ["sdrsaturation = \($sdrSaturation)"] end)
|
||||
+ (if $mirror == null then [] else ["mirror = \"\($mirror)\""] end)
|
||||
) | join(", ")
|
||||
end
|
||||
end
|
||||
' "$settings" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Re-enable on open, restoring what the person chose for this panel in Settings
|
||||
# -- the whole record, not the three fields that used to survive -- and falling
|
||||
# back to the panel's preferred mode when there is no usable record.
|
||||
cmd_open() {
|
||||
"$HW" laptop || exit 0
|
||||
local internal entry mode scale
|
||||
local internal rule
|
||||
internal="$(internal_connector)"
|
||||
[[ -n "$internal" ]] || exit 0
|
||||
entry="$(jq -c --arg name "$internal" '.displays[$name] // empty' \
|
||||
"${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json" 2>/dev/null)"
|
||||
if [[ -n "$entry" ]]; then
|
||||
mode="$(jq -r '.mode' <<<"$entry")"
|
||||
scale="$(jq -r '.scale' <<<"$entry")"
|
||||
hyprctl eval "hl.monitor({ output = \"$internal\", mode = \"$mode\", position = \"auto\", scale = $scale })" >/dev/null
|
||||
else
|
||||
hyprctl eval "hl.monitor({ output = \"$internal\", mode = \"preferred\", position = \"auto\", scale = \"auto\" })" >/dev/null
|
||||
fi
|
||||
# The connector name is interpolated into Lua too, and it comes from
|
||||
# hyprctl rather than from us. Same class monitors.lua accepts.
|
||||
[[ "$internal" =~ ^[A-Za-z0-9_.-]+$ ]] || exit 0
|
||||
|
||||
rule="$(monitor_rule "$internal")"
|
||||
[[ -n "$rule" ]] || rule='mode = "preferred", position = "auto", scale = "auto"'
|
||||
hyprctl eval "hl.monitor({ output = \"$internal\", $rule })" >/dev/null
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
@@ -124,8 +260,9 @@ usage: panama-lid [status|guard|close|open]
|
||||
connected; exits immediately on a machine that needs none
|
||||
close docked lid closed: turn the internal panel off (bound to the lid
|
||||
switch by keybinds.lua); does nothing undocked
|
||||
open lid opened: turn the internal panel back on, restoring the mode
|
||||
and scale chosen in Settings
|
||||
open lid opened: turn the internal panel back on, restoring the whole
|
||||
display record chosen in Settings -- position, mode, scale,
|
||||
transform, and the colour, VRR and mirror fields when they are set
|
||||
USAGE
|
||||
;;
|
||||
*) printf 'panama-lid: unknown command: %s\n' "$1" >&2; exit 2 ;;
|
||||
|
||||
Reference in New Issue
Block a user