# 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`](https://github.com/jovanlanik/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: 1. **Meson** compiles GResources from `assets/ahfail.gresource.xml` into C, invokes Cargo to produce `libahfail_module.a`, then links everything into `ahfail-module.so`. 2. **Cargo** handles only the Rust crate — it produces a `staticlib` that Meson links. Always use Meson for the final shared object; `cargo build` alone does not produce the loadable module. ```bash # 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 a `PixbufSimpleAnim`, stores them in `MODULE_STATE`. - `on_window_create` — called per monitor; calls `WindowHandler::create`, which creates a `gtk::Fixed` overlay, a pool of pre-warmed GStreamer players, and wires up a signal on `error_label` that 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` 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 `unsafe` pointer work on `*mut Window` goes through `WindowContext` — never dereference the raw pointer outside that wrapper. - `WindowData` is heap-allocated via `Box::into_raw` and reclaimed via `Box::from_raw` in `WindowContext::take_data`. Do not free it any other way. - The `module_data` flexible array field on `Window` is 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.