Skip to content

feat(hypr): cut the session over to the Quickshell locker - #3

Merged
bhdai merged 3 commits into
mainfrom
feat-lock-cutover
Jul 29, 2026
Merged

feat(hypr): cut the session over to the Quickshell locker#3
bhdai merged 3 commits into
mainfrom
feat-lock-cutover

Conversation

@bhdai

@bhdai bhdai commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Implements bhdai/quickshell_config#71 (parent spec bhdai/quickshell_config#62). This is the
cutover: it points the live session at the Quickshell locker and converges the three lock
paths onto one. All ten sub-issues of the build spec are merged, which is what this was
gated on.

What changes

config/hypr/binds.lua — the lock keybind ran hyprlock directly and bypassed logind
entirely. It becomes loginctl lock-session, so the keybind, the 300 s idle timeout and
pre-sleep all raise one dbus lock event and share one lock command and one fallback chain.
logind's LockedHint finally gets set whichever path fired. Lid close needs no config of
its own — logind's HandleLidSwitch is suspend, which runs before_sleep_cmd, which is
already loginctl lock-session.

config/hypr/hypridle.conflock_cmd asks Quickshell to lock and matches the
returned value, and inhibit_sleep is pinned to 3.

config/quickshell — submodule bumped to
1e19de2, the merged locker.
Without the bump a fresh machine still checks out the old commit, and the new lock_cmd
would find no lock target and fall through to hyprlock on every lock.

README.md — a "Session locking" section covering the above, the ~/.face one-time
step, and how to apply a change to either file.

Why value-matched, not exit status

Measured on this machine while preparing this PR:

Case stdout exit
lock raised true 0
instance alive, lock module failed to load Target not found. 0
instance alive, function missing Function not found. 0
no instance running (empty; stderr message) 255

Exit status cannot distinguish "the shell is alive but the lock module failed to load" from
success. grep -qx true can — all three failure rows fall through to hyprlock, and only
true stops the chain.

Why inhibit_sleep = 3 rather than auto

auto resolves to 1 here. hypridle chooses between 1 and 3 by detecting whether
hyprlock is launched before sleep, and this config's pre-sleep command is
loginctl lock-session — so the inhibitor would drop the moment loginctl returns, before
anything is on screen. That is precisely the race. Confirmed after the restart:

[LOG] Sleep inhibition enabled - inhibiting until the wayland session gets locked

Cost of pinning: it disables the lock/unlock hooks, neither of which is used.

hyprlock stays

config/hypr/hyprlock.conf and /etc/pam.d/hyprlock are untouched and the package stays
installed. hyprlock remains the last link in lock_cmd for the one case where a fallback is
real — the shell already dead when the lock is requested, so nothing holds the lock yet
and no compositor flag is involved. The pidof guard against a double launch is retained.
It is also the safety net while the new locker is being proven.

Acceptance criteria

  • Keybind, idle timeout and lid close all lock through logind; LockedHint is set.
  • Suspend waits for a compositor-confirmed session lock (log line above).
  • The lock command matches the returned value rather than the exit status.
  • With the shell killed, the lock command falls back to hyprlock; pidof guard retained.
  • hyprlock and its policy remain installed.
  • The four idle timeouts are unchanged — 150 / 300 / 330 / 1800 s, found 4 rules.
  • The submodule pointer is bumped.
  • One-time machine steps were already done: PAM files installed (/etc/pam.d/quickshell-lock, /etc/pam.d/quickshell-fprint), ~/.face~/Pictures/profile/bird_profile.png.
  • misc:allow_session_lock_restore is still off (bool: false, set: false).

Applied to the running session

The live session was carried onto this state while preparing the PR: submodule pulled, the
shell reloaded (qs ipc call lock isLocked now answers false instead of
Target not found.), hyprctl reload run, and hypridle restarted on the new config.

Not verified here — needs a human at the display

I have no display, so nothing below was exercised. Draft until they pass:

  1. Press the lock keybind — the Quickshell locker should appear, and
    busctl get-property org.freedesktop.login1 /org/freedesktop/login1/session/_32 org.freedesktop.login1.Session LockedHint
    should read true while locked.
  2. Unlock with the password, and separately with the fingerprint.
  3. Idle for 5 minutes untouched — the same locker, via the same path.
  4. Close the lid, wait for suspend, reopen — the machine must wake showing the locker,
    never the desktop. This is the race the inhibitor closes.
  5. Kill the shell (qs kill), then lock — hyprlock should come up instead.

bhdai added 3 commits July 29, 2026 21:06
The lock intent took three paths: the keybind ran hyprlock directly and
bypassed logind, the idle timeout went through hypridle's lock_cmd, and
pre-sleep went through loginctl. Nothing converged and LockedHint was
never set. The keybind now goes through loginctl lock-session, so all
three raise one dbus lock event and share one lock_cmd and one fallback
chain.

lock_cmd asks Quickshell to lock and matches the returned value: qs ipc
call prints `Target not found.` and exits 0 when the shell is alive but
the lock module failed to load, so exit status cannot distinguish that
from success. hyprlock stays as the last link for the one case where a
fallback is real -- the shell already dead when the lock is requested.

inhibit_sleep is pinned to 3 so suspend waits for a compositor-confirmed
lock. auto resolves to 1 here, because hypridle chooses by detecting
whether hyprlock is launched before sleep and this config's pre-sleep
command is loginctl lock-session -- dropping the inhibitor the moment
loginctl returns, which is precisely the race.

The four idle timeouts are unchanged.

Refs #71
Without the pointer bump a fresh machine still checks out the old commit,
and the lock_cmd added alongside would find no lock target and fall
through to hyprlock on every lock.

Refs #71
Observed during the cutover pull: the watcher fired part-way through the
checkout, failed on a module that had not landed yet, and then went quiet,
leaving the old generation serving with no sign of a problem.

Refs #71
@bhdai
bhdai marked this pull request as ready for review July 29, 2026 14:58
@bhdai
bhdai merged commit a14c5da into main Jul 29, 2026
3 checks passed
@bhdai
bhdai deleted the feat-lock-cutover branch July 29, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant