feat(lock): open the lock screen on the focused monitor
Update PKGBUILD version / update-pkgver (push) Successful in 5s

gtklock shows its input form on one monitor, chosen from its own -M
priority list, while niri assigns the lock screen's keyboard focus by
cursor position. Locking from any other monitor left form and focus
apart, so typing went nowhere until the mouse was moved.

moonarch-lock resolves the focused output and passes it as -M. All five
call sites go through it; "$@" is forwarded unchanged, so -d stays the
caller's decision.
This commit is contained in:
nevaforget committed 2026-09-14 18:41:40 +02:00
1 parent 2b0132aa94
commit a172f90c1c
5 files changed
+36 -8

No files matched your search

+3 -2
View File
@@ -14,8 +14,9 @@ Reproducible Arch Linux setup based on archinstall + post-install automation.
- **XDG config, no special casing.** gtklock searches `$XDG_CONFIG_HOME/gtklock/config.ini` and `gtklock/config.ini` under every `$XDG_CONFIG_DIRS` entry, so `defaults/xdg/gtklock/config.ini` rides the generic `defaults/xdg/` → `/etc/xdg/` sweep in the PKGBUILD and takes effect through the package alone. No `install` line of its own, no home seeding, no shadowing problem — all of which swaylock needed, because its sysconfdir was not an XDG path. A user copy in `~/.config/gtklock/config.ini` still wins, which is what a user config is for.
- **Modules are where the value is.** `userinfo-module` (avatar, clock) and `powerbar-module` (suspend/reboot/poweroff) are separate packages in `extra`, named bare in `modules=` because gtklock's compiled-in module directory is `/usr/lib/gtklock`. swaylock could do none of this. `playerctl-module` is deliberately not installed: it needs playerctld, which nothing here starts. The avatar comes from AccountsService, which resolves to `~/.face`; with no such file the module warns once and falls back to `avatar-default-symbolic`, and there is no option for the path.
- **Blur is baked into the image.** gtklock cannot blur — GTK3 has no `filter` CSS property at all, and it takes no screenshot of the session, so there is nothing to blur at lock time in the first place. Same as swaylock before it, whose `effect-blur` existed only in the `swaylock-effects` fork. So `defaults/backgrounds/wallpaper-blur.jpg` ships next to the plain wallpaper, generated once with `magick wallpaper.jpg -resize 25% -blur 0x12 -resize 4717x2984! -quality 88 -strip wallpaper-blur.jpg`. Downsample-blur-upsample rather than a large-radius blur at full resolution: same look, and the file compresses to 167 KB against the original's 374 KB. Regenerate it whenever the wallpaper changes — nothing derives it automatically.
- **Two callers, both spawning it directly.** `defaults/xdg/stasis/stasis.rune` (lid close and all three `lock_screen` plans) and `PowerPopout.qml`. There is no Wayland protocol for "lock the session" — ext-session-lock-v1 is how a client *becomes* the lock, not a request a third party sends — and no portal either. logind's `Session.Lock` only emits a signal someone has to be subscribed to, and nothing here is: niri owns `org.freedesktop.ScreenSaver` with `Inhibit`/`UnInhibit` and no `Lock`, and stasis ignores the signal on purpose. `stasis trigger lock-screen` would name the locker once instead of twice, and is not used: the button would then fail silently whenever stasis is down. The plan keeps running after a manual lock anyway, because stasis takes the state from logind's `LockedHint` rather than from having started the locker.
- **`-d` is not `-f`.** swaylock forked once the screen was locked; gtklock forks in `main()` before it has read its config, so a returned `gtklock -d` means "forked", not "locked". Nothing chains off the exit, so it does not matter today. `moonarch.install` rewrites `swaylock`/`moonlock` to `gtklock` in already-seeded `~/.config/stasis/stasis.rune` on upgrade — without that an existing install keeps calling a locker that is no longer there, and a missing binary fails silently in stasis.
- **Every caller goes through `moonarch-lock`.** gtklock puts its input form on **one** monitor, chosen from its own `-M` list; niri gives the lock screen its keyboard focus by cursor position. Locking from any other monitor therefore leaves form and focus apart and typing goes nowhere. `defaults/bin/moonarch-lock` passes `niri msg --json focused-output` as `-M`, falls back to plain gtklock, and forwards `"$@"` unchanged. Rationale + why `--follow-focus` and the niri keybind route both fail: `DECISIONS.md` (2026-09-14).
- **Two callers, both spawning `moonarch-lock` directly.** `defaults/xdg/stasis/stasis.rune` (lid close and all three `lock_screen` plans) and `PowerPopout.qml`. There is no Wayland protocol for "lock the session" — ext-session-lock-v1 is how a client *becomes* the lock, not a request a third party sends — and no portal either. logind's `Session.Lock` only emits a signal someone has to be subscribed to, and nothing here is: niri owns `org.freedesktop.ScreenSaver` with `Inhibit`/`UnInhibit` and no `Lock`, and stasis ignores the signal on purpose. `stasis trigger lock-screen` would name the locker once instead of twice, and is not used: the button would then fail silently whenever stasis is down. The plan keeps running after a manual lock anyway, because stasis takes the state from logind's `LockedHint` rather than from having started the locker.
- **`-d` is not `-f`.** swaylock forked once the screen was locked; gtklock forks in `main()` before it has read its config, so a returned `gtklock -d` means "forked", not "locked". Nothing chains off the exit, so it does not matter today. Whether a caller passes it stays the caller's decision — stasis waits for the locker on its `lock_screen` steps (`lock: gtklock (await exit)` in the journal) and must not get it, the other two must not block and do. `moonarch.install` rewrites `swaylock`/`moonlock`/`gtklock` to `moonarch-lock` in already-seeded `~/.config/stasis/stasis.rune` on upgrade — without that an existing install keeps calling a locker that is no longer there, and a missing binary fails silently in stasis.
## Battery Conservation Mode
+10
View File
@@ -1,5 +1,15 @@
# Decisions
## 2026-09-14 – moonarch-lock: the input form follows the monitor you are on
- **Who**: Dominik, ClaudeCode
- **Why**: Locking from any monitor but the first one left the session untypable. gtklock puts its input form on **one** monitor, chosen from its own `-M` priority list; niri assigns the lock screen's keyboard focus by **cursor position** — `lock_surface_focus()` in `niri.rs` reads `output_under_cursor()` and falls back to `active_output()` only when the cursor is over no output, which never happens. Form and focus therefore land apart, and keystrokes go nowhere until the mouse is moved onto the monitor holding the form. Neither side is wrong on its own: with a single monitor the case does not exist, and niri has no other signal in the lock state, since there are no windows. Not a new observation either — the same report sits unanswered in gtklock issue #89, where a niri user asks whether the clock-only window could accept keyboard input too, and notes that swaylock accepted input from either monitor. The issue was closed for the Hyprland case it was filed under.
- **Tradeoffs**: (1) **`--follow-focus` is not the fix.** It exists and it works, but gtklock hangs it on `enter-notify-event` (`window.c:298`) — a mouse-only signal. It reverses the direction (the form comes to the cursor instead of the cursor going to the form) without removing the mouse from the loop. Left off. (2) **No keybind route exists.** niri executes binds while locked if they carry `allow-when-locked=true`, but the `FocusMonitor*` actions set `active_output`, which lock focus only consults as a fallback — and the IPC action list has nothing that moves the cursor. Verified in `input/mod.rs` and `niri-ipc`; the idea was dropped after checking, not before. (3) **`-M` with the focused output** is what remains, and it is enough: `focus-follows-mouse` is active in `config.kdl:47`, so the focused output already *is* the one under the cursor. (4) **A wrapper, not five call sites.** `PowerPopout.qml` spawns an argv array with no shell, so command substitution cannot live at the call site; a wrapper also keeps the next change in one file. (5) **`-d` stays the caller's.** The wrapper forwards `"$@"` untouched rather than fixing `-d` either way — stasis waits for the locker on its `lock_screen` steps (`lock: gtklock (await exit)` in the journal) and must not get it, `lid_close_action` and the power menu must not block and do. (6) **Fails open.** If `niri msg` or `jq` is unavailable the wrapper drops to plain gtklock. A lock screen that does not appear is worse than one on the wrong monitor.
- **How**: `defaults/bin/moonarch-lock` added — resolves `niri msg --json focused-output` to a name, execs `gtklock -M "$name" "$@"`, falls back to `gtklock "$@"`. It rides the existing `install -Dm755 defaults/bin/moonarch-* -t "$pkgdir/usr/bin/"` glob, so the PKGBUILD is unchanged. All five call sites moved: `stasis.rune` (`lid_close_action` plus the three `lock_screen` plans) and `PowerPopout.qml:56`. `jq` was already in `packages/official.txt`.
- **The part that fails silently if forgotten**: `moonarch.install`'s `pre_upgrade` rewrite chain, the same one that carried swaylock→gtklock. `stasis.rune` is seeded into the home once and never overwritten, so an existing install keeps calling `gtklock` directly and the fix reaches nobody who already has the file — on this machine the seeded copy even differs from the repo template (`lid_close_action` commented out, different line numbers). The chain now maps `gtklock`→`moonarch-lock`, with the `-d` variant ordered before the bare one: the bare rule would otherwise rewrite the inner quotes and the `-d` form would never match.
- **NixOS had to follow in the same breath.** moonix deploys `stasis.rune` and `PowerPopout.qml` straight out of `moonarchSrc`, so the new command reached it the moment this landed — while `pkgs/moonarch-scripts.nix` installs an explicit whitelist that `moonarch-lock` was not on. Changing these two files alone would have left NixOS calling a binary that does not exist there, silently. Fixed on that side in the same change: `moonix/DECISIONS.md` (2026-09-14).
- **The swaylock and moonlock rules were dropped with it.** Neither locker is in use on any system, and the rewrite to gtklock has shipped in every update since 2026-09-11, so both rules only ran into the void. The `moonlock` one also carried a defect nobody would have noticed: it resolved to `gtklock -d`, and `-d` is wrong for a `lock_screen` step, where stasis waits for the locker and would have treated the lock as finished immediately.
## 2026-09-12 – GTK theme: the package was never installed, and it was never Mocha
- **Who**: Dominik, ClaudeCode
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# ABOUTME: Starts gtklock on the monitor that currently holds focus.
# ABOUTME: Falls back to plain gtklock when the output name cannot be determined.
# Without -M the input form lands on gtklock's own preferred monitor while niri
# gives the lock screen its keyboard focus by cursor position -- the two end up
# apart and typing goes nowhere. Rationale: DECISIONS.md (2026-09-14).
#
# Arguments are forwarded unchanged; -d is the caller's decision.
monitor=$(niri msg --json focused-output 2>/dev/null | jq -r '.name // empty')
if [ -n "$monitor" ]; then
exec gtklock -M "$monitor" "$@"
fi
exec gtklock "$@"
@@ -17,7 +17,7 @@ PopoutPanel {
root.pending = ""
// One process for all five actions: the popout closes on activation, so two
// actions cannot be in flight at once. `gtklock -d` daemonises itself, the
// actions cannot be in flight at once. `moonarch-lock -d` daemonises itself, the
// systemctl and loginctl calls are one-shot.
Process {
id: actionProc
@@ -53,7 +53,7 @@ PopoutPanel {
"label": qsTr("Lock"),
"icon": 0xf033e, // md-lock
"confirm": false,
"cmd": ["gtklock", "-d"]
"cmd": ["moonarch-lock", "-d"]
},
{
"name": "logout",
+4 -4
View File
@@ -56,7 +56,7 @@ default:
# Lid actions (laptop only). Locks only; the suspend on lid close comes from
# logind's HandleLidSwitch default, not from here.
lid_close_action "gtklock -d"
lid_close_action "moonarch-lock -d"
lid_open_action ""
# Every binary named in a command below has to be on the PATH of the unit
@@ -80,7 +80,7 @@ default:
# is empty on a desktop, so the step would be a no-op.
lock_screen:
timeout 300 # 5 min idle
command "gtklock"
command "moonarch-lock"
end
dpms:
@@ -103,7 +103,7 @@ default:
lock_screen:
timeout 60 # 6 min idle
command "gtklock"
command "moonarch-lock"
end
dpms:
@@ -127,7 +127,7 @@ default:
lock_screen:
timeout 60 # 2 min idle
command "gtklock"
command "moonarch-lock"
end
dpms: