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 gtklock module. It compiles to ahfail-module.so, which gtklock loads at runtime. On each failed unlock attempt (PW_FAILURE), it spawns an animated "Nedry" sprite at a random screen location and plays an audio clip ("ah ah ah, you didn't say the magic word").
Build System
The build is a two-stage hybrid:
- Meson compiles GResources from
assets/ahfail.gresource.xmlinto C, invokes Cargo to producelibahfail_module.a, then links everything intoahfail-module.so. - Cargo handles only the Rust crate — it produces a
staticlibthat Meson links.
Always use Meson for the final shared object; cargo build alone does not produce the loadable module.
# First-time setup
meson setup builddir
# Build
meson compile -C builddir
# Run Rust tests only (no GTK display required)
cargo test
# Run Meson tests (loads the .so, requires GTK)
meson test -C builddir
# Manual integration test
gtklock -d -m builddir/ahfail-module.so -- --deadzone=X,Y,W,H
Architecture
All FFI entry points live in src/lib.rs — these are the extern "C" functions that gtklock calls (on_activation, on_window_create, on_window_destroy, on_idle_hide, etc.).
Module lifecycle:
on_activation— called once at startup; initialises GTK/GStreamer, loads all sprite frames into aPixbufSimpleAnim, stores them inMODULE_STATE.on_window_create— called per monitor; callsWindowHandler::create, which creates agtk::Fixedoverlay, a pool of pre-warmed GStreamer players, and wires up a signal onerror_labelthat fires on each failed attempt.on_window_destroy/on_idle_hide— clean up sprites and stop players.
Key types:
| Type | File | Purpose |
|---|---|---|
MODULE_STATE |
src/state.rs |
Thread-local RefCell<ModuleState> holding the shared animation, audio URI, and deadzone config |
WindowData |
src/state.rs |
Heap-allocated per-window state (sprites, player pool, GTK signal handle) |
WindowContext |
src/context.rs |
Safe wrapper around the raw *mut Window pointer passed from C |
Window / GtkLock |
src/context.rs |
#[repr(C)] mirrors of gtklock's internal structs — must match include/gtklock-module.h |
WindowHandler |
src/handler.rs |
Sprite placement logic (random position, deadzone avoidance with retries) and GStreamer player management |
ModuleConfig |
src/config.rs |
Parses the --deadzone=x,y,w,h CLI argument via glib's GOptionEntry |
Assets are embedded as GResources at build time. At runtime they are accessed via resource:///ahfail/sprites/... and resource:///ahfail/audio/magic-word.mp3.
Safety Conventions
- All
unsafepointer work on*mut Windowgoes throughWindowContext— never dereference the raw pointer outside that wrapper. WindowDatais heap-allocated viaBox::into_rawand reclaimed viaBox::from_rawinWindowContext::take_data. Do not free it any other way.- The
module_dataflexible array field onWindowis a C ABI contract with gtklock — index 0 is this module's slot.
Tests
tests/ahfail_tests.rs — integration tests that construct mock Window / GtkLock structs directly to exercise handler logic without a running gtklock process. Run with cargo test.
tests/benchmarks.rs — criterion benchmarks for sprite placement.
tests/module_test.c — C smoke test built by Meson that dlopen-style verifies the exported symbols exist.