Files
gtk-ahfail/CLAUDE.md
T
Asger Geel Weirsøe cb41117932 Document ahfail as a locker
The README leads with ahfail, ahfail test and ahfail doctor, the
platform table, idle locking and what the lock does and does not
guarantee, including pam_faillock. The old gtklock module and PAM hook
move to a short legacy section. CLAUDE.md describes the five crates.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-09-30 19:18:21 +02:00

3.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What This Is

ahfail is a screen locker. On every failed unlock it spawns an animated "Nedry" sprite at a random screen position and plays "ah ah ah, you didn't say the magic word". On Linux the ahfail binary is itself the locker (Wayland via ext-session-lock-v1, X11 via an input grab) and authenticates through its own PAM service, /etc/pam.d/ahfail (data/pam.d/ahfail). On macOS the system lock screen is used and the effect comes from the PAM hook.

The older integrations, a gtklock module and a PAM hook for other X11 lockers, are still built as "legacy integrations".

Workspace

Five crates under crates/:

Crate Output Responsibility
ahfail ahfail binary The CLI and the locker: lock (default), test (preview, no lock), doctor (environment check). wayland.rs (gtk-session-lock FFI), x11.rs (seat grab), locker.rs (shared lock UI), pam.rs (PAM client, service ahfail).
ahfail-ui rlib Shared by all: sprite animation from GResources, sprite placement with deadzone, audio player, volume save/restore, optional update check (cargo feature update-check, default on).
ahfail-gtklock libahfail_module.a → ahfail-module.so Legacy gtklock module. Meson links the staticlib with the GResources into the loadable .so.
ahfail-pam libahfail_pam.so Legacy PAM hook for X11 lockers and macOS. Spawns ahfail-display on a failed attempt, never from a root process. Always returns PAM_IGNORE.
ahfail-display ahfail-display binary Legacy standalone sprite window spawned by the PAM hook. Refuses to run as root; single-instance lock in $XDG_RUNTIME_DIR.

Assets (assets/*.png, assets/magic-word.mp3) are embedded as GResources, via Meson for the gtklock module and via build.rs for the binaries, and read at runtime as resource:///ahfail/sprites/... and resource:///ahfail/audio/magic-word.mp3.

Build

meson setup builddir --prefix=/usr   # --prefix=/usr for Arch paths
meson compile -C builddir
sudo meson install -C builddir

cargo build --release -p ahfail                             # just the locker
cargo build --release -p ahfail-display --no-default-features  # without the update check

Always use Meson for ahfail-module.so: cargo build alone produces only the staticlib.

Linux build dependencies: gtk3, gtk-session-lock, gstreamer (+ base/good plugins), pam, meson, ninja, rust.

Tests

cargo test                                   # unit + integration (some need a display: use xvfb-run)
xvfb-run meson test -C builddir --verbose    # smoke tests: module symbols, PAM module
  • crates/ahfail-gtklock/tests/: integration tests that build mock Window/GtkLock structs.
  • tests/module_test.c, tests/pam_smoke_test.c: C smoke tests built by Meson.
  • The locker itself is tested headless: X11 under Xvfb with xdotool, Wayland under sway with WLR_BACKENDS=headless and wtype.

Safety Conventions

  • ahfail and ahfail-display must never run as root; the PAM hook must never spawn anything from a root process.
  • The X11 lock is only the grab: if it cannot be taken, refuse to lock. Never show a lock screen without it.
  • The password lives in pam::Secret, which zeroes itself on drop, and PAM runs off the GTK thread.
  • gtklock module: all unsafe work on *mut Window goes through WindowContext; WindowData is created with Box::into_raw and reclaimed only in WindowContext::take_data. The #[repr(C)] structs must match include/gtklock-module.h.