diff --git a/settings/hyprland.nix b/settings/hyprland.nix index d73970c..b02c0cb 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -62,6 +62,29 @@ in libcanberra-gtk3 ]; + # Lock *before* the machine actually suspends. hypridle's + # before_sleep_cmd cannot do this: it spawns the command and releases + # its sleep inhibitor in the same breath (journal: "Executing …" then + # "Releasing the sleep inhibitor!" on the same second), so the qs + # process gets frozen mid-lock and only finishes on resume — the lid + # opens showing the pre-suspend desktop for a beat before the lock + # paints. A unit ordered Before=sleep.target genuinely blocks the + # suspend until it exits. The sleep covers the gap between quickshell + # accepting the IPC and the lock surface committing a frame. + systemd.services.lock-before-sleep = lib.mkIf isMacbook { + description = "Lock the session before suspend"; + before = [ "sleep.target" ]; + wantedBy = [ "sleep.target" ]; + serviceConfig = { + Type = "oneshot"; + User = "fred"; + Environment = "XDG_RUNTIME_DIR=/run/user/%U"; + ExecStart = "${pkgs.quickshell}/bin/qs ipc call lock lock"; + ExecStartPost = "${pkgs.coreutils}/bin/sleep 1"; + TimeoutStartSec = "5s"; + }; + }; + home-manager.users.fred = { config, lib, pkgs, inputs, ... }: let c = config.lib.stylix.colors; @@ -435,17 +458,10 @@ in settings = { general = { lock_cmd = "qs ipc call lock lock"; - # Lock directly, not via `loginctl lock-session`: hypridle waits - # for before_sleep_cmd to exit before releasing its sleep - # inhibitor, but the loginctl → logind Lock signal → lock_cmd - # detour is async and the box can suspend unlocked. Lid close - # (HandleLidSwitch=suspend) goes through here too. - # The sleep is not padding: `qs ipc call` returns once the - # handler sets locked=true, before the lock surface has - # committed a frame. Without it the box freezes mid-transition - # and the pre-lock frame flashes on resume. - before_sleep_cmd = "qs ipc call lock lock; sleep 1"; - after_sleep_cmd = "hyprctl dispatch dpms on"; + # No before_sleep_cmd: hypridle does not wait for it before + # dropping its sleep inhibitor, so the lock lands after resume. + # systemd.services.lock-before-sleep handles suspend instead. + after_sleep_cmd = "hyprctl dispatch 'hl.dsp.dpms({ action = \"on\" })'"; }; listener = [ { @@ -454,8 +470,11 @@ in } { timeout = 420; # 7 min — display off - on-timeout = unlessPlaying "hyprctl dispatch dpms off"; - on-resume = "hyprctl dispatch dpms on"; + # Lua config manager: `hyprctl dispatch X` evaluates + # `hl.dispatch(X)`, and a non-table arg parses as "toggle", + # so the action has to be spelled out in a table. + on-timeout = unlessPlaying "hyprctl dispatch 'hl.dsp.dpms({ action = \"off\" })'"; + on-resume = "hyprctl dispatch 'hl.dsp.dpms({ action = \"on\" })'"; } { timeout = 600; # 10 min — suspend