manage monitors with monique; reduce hypr-lid to lock/suspend timers
Add the monique GUI/daemon for monitor arrangement, hotplug profiles and clamshell handling (writes monitors.lua, keybind SUPER+SHIFT+M, stowed settings). hypr-lid is trimmed to hypr-lid-power: display management moved to moniqued, it now only runs the delayed lock/suspend timers when the lid is closed undocked. Update logind drop-in comments accordingly.
This commit is contained in:
@@ -7,6 +7,7 @@ All the configuration files for my current machine setup.
|
|||||||
- Ghostty
|
- Ghostty
|
||||||
- Hyprland
|
- Hyprland
|
||||||
- Local
|
- Local
|
||||||
|
- Monique
|
||||||
- NVIM
|
- NVIM
|
||||||
- SSH
|
- SSH
|
||||||
- SystemD
|
- SystemD
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ hl.bind(mainMod .. " + V", hl.dsp.window.float({ action = "toggle" }))
|
|||||||
hl.bind(mainMod .. " + space", hl.dsp.exec_cmd(menu))
|
hl.bind(mainMod .. " + space", hl.dsp.exec_cmd(menu))
|
||||||
hl.bind(SUPER_SHIFT .. " + L", hl.dsp.exec_cmd("hyprlock"))
|
hl.bind(SUPER_SHIFT .. " + L", hl.dsp.exec_cmd("hyprlock"))
|
||||||
hl.bind(SUPER_SHIFT .. " + R", hl.dsp.exec_cmd("hyprctl reload"))
|
hl.bind(SUPER_SHIFT .. " + R", hl.dsp.exec_cmd("hyprctl reload"))
|
||||||
|
-- Monique: GUI monitor arrangement/display settings
|
||||||
|
hl.bind(SUPER_SHIFT .. " + M", hl.dsp.exec_cmd("monique"))
|
||||||
hl.bind(SUPER_SHIFT .. " + D", hl.dsp.exec_cmd("hyprctl keyword monitor eDP-1, disable"))
|
hl.bind(SUPER_SHIFT .. " + D", hl.dsp.exec_cmd("hyprctl keyword monitor eDP-1, disable"))
|
||||||
hl.bind(SUPER_SHIFT .. " + F", hl.dsp.exec_cmd("hyprctl keyword monitor eDP-1, enable"))
|
hl.bind(SUPER_SHIFT .. " + F", hl.dsp.exec_cmd("hyprctl keyword monitor eDP-1, enable"))
|
||||||
|
|
||||||
@@ -65,5 +67,6 @@ hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
|
|||||||
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
||||||
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
||||||
|
|
||||||
-- Laptop lid is handled by hypr-lid.service (clamshell when docked, delayed
|
-- Laptop lid is handled by hypr-lid-power.service (delayed lock/suspend when
|
||||||
-- lock/suspend otherwise) instead of these binds; see .local/bin/hypr-lid.sh.
|
-- closed undocked; display/clamshell handling is done by moniqued) instead of
|
||||||
|
-- these binds; see .local/bin/hypr-lid-power.sh.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- External displays (docked layout). The built-in panel is managed at runtime
|
-- Managed by monique (run `monique` or SUPER+SHIFT+M to arrange). The built-in
|
||||||
-- by hypr-lid: it is enabled here, then disabled while the lid is closed with
|
-- panel is managed at runtime by moniqued's clamshell mode: enabled here, then
|
||||||
-- external displays attached (clamshell). See hyprland/.local/bin/hypr-lid.sh.
|
-- disabled while the lid is closed with external displays attached.
|
||||||
-- See https://wiki.hypr.land/Configuring/Basics/Monitors/ for more
|
-- See https://wiki.hypr.land/Configuring/Basics/Monitors/ for more
|
||||||
hl.monitor({ output = "desc:BOE 0x0BC9", mode = "preferred", position = "0x0", scale = 1.0 })
|
hl.monitor({ output = "desc:BOE 0x0BC9", mode = "preferred", position = "0x0", scale = 1.0 })
|
||||||
hl.monitor({ output = "desc:Viewteck Co. Ltd. GNV34DB", mode = "3440x1440@50.0", position = "3795x1785", scale = 1.0 })
|
hl.monitor({ output = "desc:Viewteck Co. Ltd. GNV34DB", mode = "3440x1440@50.0", position = "3795x1785", scale = 1.0 })
|
||||||
|
|||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Hyprland lid and clamshell monitor handler
|
Description=Hyprland lid lock/suspend timers (display/clamshell handled by moniqued)
|
||||||
After=graphical-session.target
|
After=graphical-session.target
|
||||||
PartOf=graphical-session.target
|
PartOf=graphical-session.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=%h/.local/bin/hypr-lid.sh
|
ExecStart=%h/.local/bin/hypr-lid-power.sh
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
|
|
||||||
Executable
+91
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# hypr-lid-power: delayed lock/suspend for the laptop lid.
|
||||||
|
#
|
||||||
|
# Display/clamshell/hotplug handling is owned by moniqued (see
|
||||||
|
# ~/.config/hypr/monitors.lua and moniqued's clamshell mode). This service only
|
||||||
|
# runs the power timers while the lid is closed with no external display
|
||||||
|
# attached:
|
||||||
|
# - lock after LOCK_SECONDS
|
||||||
|
# - suspend after SUSPEND_SECONDS
|
||||||
|
#
|
||||||
|
# Runs as a systemd user service (hypr-lid-power.service) and polls the lid +
|
||||||
|
# monitor state, so connecting/disconnecting a dock while the lid is closed is
|
||||||
|
# handled too. Lock/suspend use transient systemd timers, which pause during
|
||||||
|
# suspend and are recreated afterwards, so if the machine wakes while the lid
|
||||||
|
# stays closed it will sleep again after the full timeout.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
RUNTIME="/run/user/$(id -u)"
|
||||||
|
LOCK_SECONDS=60
|
||||||
|
SUSPEND_SECONDS=600
|
||||||
|
POLL_SECONDS=2
|
||||||
|
|
||||||
|
log() { printf '[hypr-lid-power] %s %s\n' "$(date '+%H:%M:%S')" "$*"; }
|
||||||
|
|
||||||
|
# Run hyprctl against the active Hyprland instance, if any.
|
||||||
|
hypr() {
|
||||||
|
local sig=""
|
||||||
|
for d in "$RUNTIME"/hypr/*/; do
|
||||||
|
if [ -S "$d/.socket.sock" ]; then
|
||||||
|
sig="$(basename "$d")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -n "$sig" ] || return 1
|
||||||
|
HYPRLAND_INSTANCE_SIGNATURE="$sig" command hyprctl "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
lid_closed() {
|
||||||
|
local state
|
||||||
|
state="$(cat /proc/acpi/button/lid/*/state 2>/dev/null)" || return 1
|
||||||
|
[[ "$state" == *closed* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# True when Hyprland has any output up that is not the built-in panel/headless.
|
||||||
|
has_external() {
|
||||||
|
hypr monitors -j 2>/dev/null | grep '"name": "' | grep -qvE '"(eDP|HEADLESS)-'
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_timers() {
|
||||||
|
systemctl --user stop hypr-lid-lock.timer hypr-lid-suspend.timer 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Re-create the named timer unless it is already waiting. Transient timers are
|
||||||
|
# collected after they fire, so this also restarts them after a suspend.
|
||||||
|
ensure_timer() {
|
||||||
|
local name="$1" seconds="$2"
|
||||||
|
shift 2
|
||||||
|
if ! systemctl --user --quiet is-active "hypr-lid-$name.timer"; then
|
||||||
|
systemd-run --user --quiet --unit="hypr-lid-$name" --on-active="${seconds}s" --collect "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
powering=no
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if ! hypr monitors >/dev/null 2>&1; then
|
||||||
|
# No Hyprland running (e.g. another compositor, or before first launch).
|
||||||
|
powering=no
|
||||||
|
sleep 3
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
want_power="no"
|
||||||
|
if lid_closed && ! has_external; then
|
||||||
|
want_power="yes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$want_power" != "$powering" ]; then
|
||||||
|
log "power mode: $powering -> $want_power"
|
||||||
|
if [ "$want_power" = "yes" ]; then
|
||||||
|
ensure_timer lock "$LOCK_SECONDS" loginctl lock-sessions
|
||||||
|
ensure_timer suspend "$SUSPEND_SECONDS" systemctl suspend
|
||||||
|
else
|
||||||
|
stop_timers
|
||||||
|
fi
|
||||||
|
powering="$want_power"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$POLL_SECONDS"
|
||||||
|
done
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# hypr-lid: clamshell + lid handling for Hyprland.
|
|
||||||
#
|
|
||||||
# - Lid closed + external display connected -> disable the built-in panel
|
|
||||||
# (clamshell mode); external displays keep working.
|
|
||||||
# - Lid closed + no external display -> turn built-in off, lock after
|
|
||||||
# LOCK_SECONDS, suspend after SUSPEND_SECONDS.
|
|
||||||
# - Lid open -> enable built-in, turn display on.
|
|
||||||
#
|
|
||||||
# Runs as a systemd user service (hypr-lid.service) and polls the lid + monitor
|
|
||||||
# state, so connecting/disconnecting a dock while the lid is closed is handled
|
|
||||||
# too. Lock/suspend use transient systemd timers, which pause during suspend and
|
|
||||||
# are recreated afterwards, so if the machine wakes while the lid stays closed
|
|
||||||
# it will sleep again after the full timeout.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
INTERNAL="desc:BOE 0x0BC9"
|
|
||||||
RUNTIME="/run/user/$(id -u)"
|
|
||||||
LOCK_SECONDS=60
|
|
||||||
SUSPEND_SECONDS=600
|
|
||||||
POLL_SECONDS=2
|
|
||||||
|
|
||||||
log() { printf '[hypr-lid] %s %s\n' "$(date '+%H:%M:%S')" "$*"; }
|
|
||||||
|
|
||||||
# Run hyprctl against the active Hyprland instance, if any.
|
|
||||||
hypr() {
|
|
||||||
local sig=""
|
|
||||||
for d in "$RUNTIME"/hypr/*/; do
|
|
||||||
if [ -S "$d/.socket.sock" ]; then
|
|
||||||
sig="$(basename "$d")"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
[ -n "$sig" ] || return 1
|
|
||||||
HYPRLAND_INSTANCE_SIGNATURE="$sig" command hyprctl "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
lid_closed() {
|
|
||||||
local state
|
|
||||||
state="$(cat /proc/acpi/button/lid/*/state 2>/dev/null)" || return 1
|
|
||||||
[[ "$state" == *closed* ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
# True when Hyprland has any output up that is not the built-in panel/headless.
|
|
||||||
has_external() {
|
|
||||||
hypr monitors -j 2>/dev/null | grep '"name": "' | grep -qvE '"(eDP|HEADLESS)-'
|
|
||||||
}
|
|
||||||
|
|
||||||
stop_timers() {
|
|
||||||
systemctl --user stop hypr-lid-lock.timer hypr-lid-suspend.timer 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Re-create the named timer unless it is already waiting. Transient timers are
|
|
||||||
# collected after they fire, so this also restarts them after a suspend.
|
|
||||||
ensure_timer() {
|
|
||||||
local name="$1" seconds="$2"
|
|
||||||
shift 2
|
|
||||||
if ! systemctl --user --quiet is-active "hypr-lid-$name.timer"; then
|
|
||||||
systemd-run --user --quiet --unit="hypr-lid-$name" --on-active="${seconds}s" --collect "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
mode=unknown
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
if ! hypr monitors >/dev/null 2>&1; then
|
|
||||||
# No Hyprland running (e.g. another compositor, or before first launch).
|
|
||||||
mode=unknown
|
|
||||||
sleep 3
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
new_mode="open"
|
|
||||||
if lid_closed; then
|
|
||||||
if has_external; then
|
|
||||||
new_mode="clamshell"
|
|
||||||
else
|
|
||||||
new_mode="regular"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$new_mode" != "$mode" ]; then
|
|
||||||
log "transition: $mode -> $new_mode"
|
|
||||||
case "$new_mode" in
|
|
||||||
open)
|
|
||||||
hypr keyword monitor "$INTERNAL, preferred, auto, 1"
|
|
||||||
hypr dispatch dpms on
|
|
||||||
stop_timers
|
|
||||||
;;
|
|
||||||
clamshell)
|
|
||||||
hypr keyword monitor "$INTERNAL, disabled"
|
|
||||||
stop_timers
|
|
||||||
;;
|
|
||||||
regular)
|
|
||||||
hypr keyword monitor "$INTERNAL, preferred, auto, 1"
|
|
||||||
hypr dispatch dpms off
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
mode="$new_mode"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$mode" = "regular" ]; then
|
|
||||||
ensure_timer lock "$LOCK_SECONDS" loginctl lock-sessions
|
|
||||||
ensure_timer suspend "$SUSPEND_SECONDS" systemctl suspend
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep "$POLL_SECONDS"
|
|
||||||
done
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"clamshell_mode": true,
|
||||||
|
"update_sddm": true,
|
||||||
|
"migrate_workspaces": true
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Hyprland handles the laptop lid itself via the hypr-lid service
|
# Hyprland handles the laptop lid itself: moniqued owns the display side
|
||||||
# (clamshell when external displays are attached, delayed lock/suspend
|
# (clamshell when external displays are attached) and hypr-lid-power runs the
|
||||||
# otherwise). logind must not suspend immediately on lid close.
|
# delayed lock/suspend timers otherwise. logind must not suspend immediately
|
||||||
|
# on lid close.
|
||||||
[Login]
|
[Login]
|
||||||
HandleLidSwitch=ignore
|
HandleLidSwitch=ignore
|
||||||
HandleLidSwitchExternalPower=ignore
|
HandleLidSwitchExternalPower=ignore
|
||||||
|
|||||||
Reference in New Issue
Block a user