add hypr-lid service for clamshell and delayed lid sleep

This commit is contained in:
2026-08-14 18:16:31 -06:00
parent 96c4d36714
commit db72a5a635
6 changed files with 136 additions and 5 deletions
+2 -3
View File
@@ -65,6 +65,5 @@ 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 for opening and closing -- Laptop lid is handled by hypr-lid.service (clamshell when docked, delayed
hl.bind("switch:on:Lid Switch", hl.dsp.exec_cmd("hyprctl dpms off && hyprctl dispatch exec hyprlock"), { locked = true }) -- lock/suspend otherwise) instead of these binds; see .local/bin/hypr-lid.sh.
hl.bind("switch:off:Lid Switch", hl.dsp.exec_cmd("hyprctl dispatch dpms on"), { locked = true })
+1
View File
@@ -3,6 +3,7 @@ hl.on("hyprland.start", function()
hl.exec_cmd("hyprctl setcursor catppuccin-mocha-dark-cursors 28 &") hl.exec_cmd("hyprctl setcursor catppuccin-mocha-dark-cursors 28 &")
hl.exec_cmd("systemctl --user start hyprpolkitagent &") hl.exec_cmd("systemctl --user start hyprpolkitagent &")
hl.exec_cmd("waybar & mako & hyprpaper & hypridle & wluma & hyprshell run &") hl.exec_cmd("waybar & mako & hyprpaper & hypridle & wluma & hyprshell run &")
hl.exec_cmd("systemctl --user start hypr-lid.service &")
hl.exec_cmd("blueman-applet &") hl.exec_cmd("blueman-applet &")
hl.exec_cmd("1password --silent &") hl.exec_cmd("1password --silent &")
hl.exec_cmd("op signin &") hl.exec_cmd("op signin &")
+4 -2
View File
@@ -1,6 +1,8 @@
-- Generated by nwg-displays on 2026-05-12 at 10:26:18. Do not edit manually. -- External displays (docked layout). The built-in panel is managed at runtime
-- by hypr-lid: it is enabled here, then disabled while the lid is closed with
-- external displays attached (clamshell). See hyprland/.local/bin/hypr-lid.sh.
-- 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", disabled = true }) 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 })
hl.monitor({ output = "desc:Dell Inc. DELL U2515HX GMDK46C4053L", mode = "2560x1440@59.95", position = "5488x345", scale = 1.0 }) hl.monitor({ output = "desc:Dell Inc. DELL U2515HX GMDK46C4053L", mode = "2560x1440@59.95", position = "5488x345", scale = 1.0 })
hl.monitor({ output = "desc:Dell Inc. DELL U2518D 0WG2J9C5B3WL", mode = "2560x1440@59.95", position = "2928x345", scale = 1.0 }) hl.monitor({ output = "desc:Dell Inc. DELL U2518D 0WG2J9C5B3WL", mode = "2560x1440@59.95", position = "2928x345", scale = 1.0 })
@@ -0,0 +1,13 @@
[Unit]
Description=Hyprland lid and clamshell monitor handler
After=graphical-session.target
PartOf=graphical-session.target
[Service]
Type=simple
ExecStart=%h/.local/bin/hypr-lid.sh
Restart=on-failure
RestartSec=3
[Install]
WantedBy=graphical-session.target
+109
View File
@@ -0,0 +1,109 @@
#!/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,7 @@
# Hyprland handles the laptop lid itself via the hypr-lid service
# (clamshell when external displays are attached, delayed lock/suspend
# otherwise). logind must not suspend immediately on lid close.
[Login]
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore