All checks were successful
Check / eval (push) Successful in 2m59s
Bernardo tested the lock screen after turning `fingerprint.pam` on: sudo took a finger, hyprlock did nothing. The PAM stack was not the problem — grosshack sits in /etc/pam.d/hyprlock at order 11400 exactly like sudo's. hyprlock just never asks it: its PAM conversation runs only on submit, so a module that wants to prompt *while* polling the reader never gets a turn. hyprlock has its own fprintd-over-D-Bus backend instead, behind `auth:fingerprint:enabled`, which nothing in the distro ever set. So `fingerprint.pam = true` promised "login + sudo" and silently skipped the surface you meet most often. The second half is the one worth having: enabling the backend alone yields a reader that works while the field still reads "password…". $FPRINTPROMPT is the ONLY slot hyprlock renders the ready/scanning/failed messages into, and ours hardcoded the placeholder — proven on hardware, where a test config with the backend on unlocked by finger and said nothing whatsoever. A feature that works and cannot be discovered fails VISION's "discoverable without reading the README" either way, so the placeholder now carries $FPRINTPROMPT and the messages mirror sudo's "Enter Password or Place finger". New `nomarchy.idle.fingerprint` (default false), mirroring `nomarchy.hardware.fingerprint.pam` the way `nomarchy.keyboard.layout` mirrors `services.xserver.xkb.layout`: hyprlock is configured in standalone Home Manager, which has no `osConfig` to derive the NixOS side from. Opt-in rather than implied, because with no reader hyprlock advertises a scan that cannot happen. Documented in the template (SoT for opt-in comments) and README — option-docs caught the missing row, which is the check doing its job. Verified V2: nix flake check --no-build; checks.option-docs passes (failed first with "undocumented option", as it should). V3 pending: Bernardo applies it and confirms the field reads "password… or scan finger" and both factors work — the mechanism itself is already proven on his reader via a test config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
4.6 KiB
Nix
118 lines
4.6 KiB
Nix
# hyprlock + hypridle — screen locking and idle management, themed from
|
|
# theme-state.json. One concern, one file: hypridle drives WHEN (idle
|
|
# lock, display off, suspend, lock-before-sleep), hyprlock is the
|
|
# themed lock screen itself (also behind the power menu's Lock entry).
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.nomarchy;
|
|
t = cfg.theme;
|
|
c = t.colors;
|
|
inherit (config.nomarchy.lib) rgb;
|
|
|
|
# Exits 0 when running on AC: a mains adapter reports `online` 1 (battery
|
|
# supplies have no `online` node, so they never match). Used as
|
|
# `${onAc} || <action>` so the action is skipped while plugged in.
|
|
onAc = pkgs.writeShellScript "nomarchy-on-ac" ''
|
|
for f in /sys/class/power_supply/*/online; do
|
|
[ -r "$f" ] && [ "$(cat "$f")" = "1" ] && exit 0
|
|
done
|
|
exit 1
|
|
'';
|
|
in
|
|
{
|
|
config = lib.mkIf cfg.idle.enable {
|
|
programs.hyprlock = {
|
|
enable = true;
|
|
settings = {
|
|
general.hide_cursor = true;
|
|
|
|
background = [{
|
|
monitor = "";
|
|
color = rgb c.base;
|
|
}];
|
|
|
|
input-field = [{
|
|
monitor = "";
|
|
size = "300, 50";
|
|
outline_thickness = t.ui.borderSize;
|
|
dots_size = 0.25;
|
|
outer_color = rgb c.accent;
|
|
inner_color = rgb c.surface;
|
|
font_color = rgb c.text;
|
|
check_color = rgb c.warn;
|
|
fail_color = rgb c.bad;
|
|
rounding = t.ui.rounding;
|
|
# $FPRINTPROMPT is the ONLY place hyprlock renders the fingerprint
|
|
# ready/scanning/failed messages — there is no implicit slot for
|
|
# them. Enabling the backend without this yields a reader that
|
|
# silently works while the field still says "password…", i.e. a
|
|
# feature nobody can find (VISION: fingerprint discoverable without
|
|
# reading the README). Verified on hardware 2026-07-14: unlock by
|
|
# finger worked and said nothing at all.
|
|
placeholder_text =
|
|
if cfg.idle.fingerprint then "<i>$FPRINTPROMPT</i>" else "<i>password…</i>";
|
|
}];
|
|
|
|
label = [{
|
|
monitor = "";
|
|
text = "$TIME";
|
|
color = rgb c.text;
|
|
font_size = 64;
|
|
font_family = t.fonts.ui;
|
|
position = "0, 120";
|
|
halign = "center";
|
|
valign = "center";
|
|
}];
|
|
} // lib.optionalAttrs cfg.idle.fingerprint {
|
|
# hyprlock does NOT take a fingerprint through PAM: its PAM stack runs
|
|
# only on submit, so a parallel module never gets to poll the reader.
|
|
# This is a separate backend of its own, talking to fprintd over
|
|
# D-Bus, and `nomarchy.hardware.fingerprint.pam` does not reach it —
|
|
# which is why finger-unlock at the lock screen did nothing at all
|
|
# until this option existed, while sudo took a finger happily.
|
|
# The wording mirrors sudo's "Enter Password or Place finger" prompt.
|
|
auth.fingerprint = {
|
|
enabled = true;
|
|
ready_message = "password… or scan finger";
|
|
present_message = "scanning finger…";
|
|
};
|
|
};
|
|
};
|
|
|
|
services.hypridle = {
|
|
enable = true;
|
|
settings = {
|
|
general = {
|
|
lock_cmd = "pidof hyprlock || hyprlock";
|
|
# No before_sleep_cmd here: locking before sleep is driven
|
|
# system-side so it can fire for suspend but skip an encrypted
|
|
# hibernate — whose LUKS resume already gates the machine, so a
|
|
# hyprlock on top is a second password (and a Wayland session-lock
|
|
# can't be safely dropped after the fact — killing the locker trips
|
|
# its "go to a tty" crash failsafe). See nomarchy-lock-before-sleep
|
|
# in modules/nixos/default.nix.
|
|
after_sleep_cmd = "hyprctl dispatch dpms on";
|
|
};
|
|
listener = [
|
|
# Lock and screen-off are the same on either power source —
|
|
# they're about privacy and the panel, not battery.
|
|
{ timeout = 300; on-timeout = "loginctl lock-session"; }
|
|
{
|
|
timeout = 600;
|
|
on-timeout = "hyprctl dispatch dpms off";
|
|
on-resume = "hyprctl dispatch dpms on";
|
|
}
|
|
# Suspend only on battery, and sooner than the old fixed 30 min
|
|
# (it now only fires unplugged). Plugged in, the machine stays
|
|
# up — long builds, media, presentations aren't killed mid-idle.
|
|
# Lid close is logind's job (not hypridle): undocked lid still
|
|
# suspends (HandleLidSwitch); docked/clamshell lid is ignore
|
|
# (HandleLidSwitchDocked — modules/nixos/power.nix, #86).
|
|
{ timeout = 900; on-timeout = "${onAc} || systemctl suspend"; }
|
|
];
|
|
};
|
|
};
|
|
};
|
|
}
|