docs: rewrite readme; add macOS install script
All checks were successful
Test / test (push) Successful in 6m2s
All checks were successful
Test / test (push) Successful in 6m2s
- Clarify that the sprite is the author's face on Nedry's body - Explicit Wayland (gtklock module) vs X11/macOS (PAM module) comparison table with an explanation of how each integration works - Accurate per-distro PAM paths (standard/Fedora multilib/Debian multiarch) - scripts/install-macos.sh: one-shot installer for macOS — checks Homebrew, installs brew deps, builds from source, copies binaries, and patches /etc/pam.d/screensaverui (or screensaver) after the last auth line Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
182
readme.md
182
readme.md
@@ -1,38 +1,43 @@
|
||||
# Ah ah ah, you didn't say the magic word
|
||||
|
||||
This `gtklock` module listens for failed unlock attempts and recreates Dennis Nedry’s “ah ah ah” lockout scene from Jurassic Park.
|
||||
On a failed lock-screen unlock attempt, this spawns a looping animation of **the author's face photoshopped onto Dennis Nedry's body** and plays the "ah ah ah, you didn't say the magic word" clip from Jurassic Park. Each wrong guess adds another sprite at a random screen position. Volume is forced to 100% on the first failure and restored when the screen unlocks.
|
||||
|
||||
* **Animation:** Spawns a looping "Nedry" sprite at a random location on the screen.
|
||||
* **Audio:** Plays the "ah ah ah, you didn't say the magic word" clip.
|
||||
* **Safety:** Sprites avoid overlapping a configurable "deadzone" (e.g., your login box).
|
||||
* **Performance:** Uses pre-warmed audio players for low latency
|
||||
## Platform support
|
||||
|
||||
## Requirements
|
||||
The integration method differs by display server:
|
||||
|
||||
* **Build:** Meson, Ninja, Rust (Cargo), GTK+3 development headers.
|
||||
* **Runtime:** `gtklock`, `gstreamer`, `gst-plugins-base`, `gst-plugins-good` (for audio playback).
|
||||
| Platform | Display server | Module loaded | How it works |
|
||||
|---|---|---|---|
|
||||
| Linux | **Wayland** | `ahfail-module.so` | Loaded directly by `gtklock` via its module API |
|
||||
| Linux | **X11** | `libahfail_pam.so` + `ahfail-display` | PAM cleanup hook spawns the display binary after each failed auth |
|
||||
| macOS | — | `libahfail_pam.so` + `ahfail-display` | Same PAM approach, hooks into the screensaver PAM stack |
|
||||
|
||||
## Build & Install
|
||||
The **gtklock module** is Wayland-only — it uses gtklock's internal window API to overlay sprites directly on the lock screen. The **PAM module** works on X11 and macOS by registering a cleanup callback that fires on each failed authentication attempt; it double-forks a standalone display binary (`ahfail-display`) that runs independently of the PAM stack.
|
||||
|
||||
1. **Install Dependencies:**
|
||||
```bash
|
||||
sudo pacman -S meson ninja rust gtk3 gstreamer gst-plugins-base gst-plugins-good
|
||||
```
|
||||
---
|
||||
|
||||
2. **Build:**
|
||||
```bash
|
||||
meson setup builddir
|
||||
meson compile -C builddir
|
||||
```
|
||||
## Linux — Wayland (gtklock)
|
||||
|
||||
3. **Install:**
|
||||
```bash
|
||||
sudo meson install -C builddir
|
||||
```
|
||||
### Install dependencies (Arch)
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
sudo pacman -S meson ninja rust gtk3 gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gtklock
|
||||
```
|
||||
|
||||
Run `gtklock` with the module path:
|
||||
### Build and install
|
||||
|
||||
```bash
|
||||
meson setup builddir
|
||||
meson compile -C builddir
|
||||
sudo meson install -C builddir
|
||||
```
|
||||
|
||||
Installs:
|
||||
- `/usr/lib/gtklock/ahfail-module.so` — gtklock module
|
||||
- `/usr/lib/ahfail/libahfail_pam.so` — PAM module (for X11)
|
||||
- `/usr/lib/ahfail/ahfail-display` — standalone display binary
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
gtklock -m /usr/lib/gtklock/ahfail-module.so
|
||||
@@ -40,38 +45,76 @@ gtklock -m /usr/lib/gtklock/ahfail-module.so
|
||||
|
||||
### Arguments
|
||||
|
||||
* `--deadzone=X,Y,W,H`: Defines a rectangle where sprites will *not* spawn (e.g., to keep your password field visible).
|
||||
```bash
|
||||
gtklock -m ahfail-module.so -- --deadzone=860,440,200,200
|
||||
```
|
||||
*(Note the `--` separator before module arguments)*
|
||||
|
||||
* `--audio-uri=URI`: Override the default audio clip.
|
||||
```bash
|
||||
gtklock -m ahfail-module.so -- --audio-uri=file:///home/user/custom.mp3
|
||||
```
|
||||
|
||||
## macOS (build from source)
|
||||
|
||||
### Prerequisites
|
||||
Pass module arguments after `--`:
|
||||
|
||||
```bash
|
||||
brew install gtk+3 gstreamer gst-plugins-base gst-plugins-good meson ninja rust
|
||||
gtklock -m /usr/lib/gtklock/ahfail-module.so -- --deadzone=860,440,200,200
|
||||
```
|
||||
|
||||
### Build
|
||||
- `--deadzone=X,Y,W,H` — rectangle where sprites will not spawn (keep your password field clear)
|
||||
- `--audio-uri=URI` — override the default audio clip, e.g. `file:///home/user/custom.mp3`
|
||||
|
||||
---
|
||||
|
||||
## Linux — X11 (i3lock, xscreensaver, etc.)
|
||||
|
||||
Add the PAM module to your screen locker's PAM service file. Use the full path — `$(libdir)/ahfail` is not in PAM's default search path.
|
||||
|
||||
**Arch / standard:**
|
||||
```
|
||||
auth optional /usr/lib/ahfail/libahfail_pam.so
|
||||
```
|
||||
|
||||
**Fedora / RHEL (multilib):**
|
||||
```
|
||||
auth optional /usr/lib64/ahfail/libahfail_pam.so
|
||||
```
|
||||
|
||||
**Debian / Ubuntu (multiarch):**
|
||||
```
|
||||
auth optional /usr/lib/x86_64-linux-gnu/ahfail/libahfail_pam.so
|
||||
```
|
||||
|
||||
Common service files: `/etc/pam.d/i3lock`, `/etc/pam.d/xscreensaver`, `/etc/pam.d/lightdm`.
|
||||
|
||||
Optionally pass the display binary path as an argument if it is not at the default location:
|
||||
|
||||
```
|
||||
auth optional /usr/lib/ahfail/libahfail_pam.so display_path=/usr/lib/ahfail/ahfail-display
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## macOS
|
||||
|
||||
### Quick install (recommended)
|
||||
|
||||
```bash
|
||||
git clone https://gitea.weircon.dk/agw/gtk-ahfail.git
|
||||
cd gtk-ahfail
|
||||
bash scripts/install-macos.sh
|
||||
```
|
||||
|
||||
The script installs Homebrew dependencies, builds from source, copies binaries to `/usr/local/lib/ahfail/`, and patches the screensaver PAM configuration automatically.
|
||||
|
||||
### Manual install
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
```bash
|
||||
brew install gtk+3 gstreamer gst-plugins-base gst-plugins-good meson ninja
|
||||
```
|
||||
|
||||
Rust: install via [rustup.rs](https://rustup.rs) if not already present.
|
||||
|
||||
#### Build
|
||||
|
||||
```bash
|
||||
meson setup builddir
|
||||
meson compile -C builddir
|
||||
```
|
||||
|
||||
Produces:
|
||||
- `builddir/ahfail-module.so` — gtklock module (Wayland/Linux only)
|
||||
- `builddir/libahfail_pam.so` — PAM module (macOS + X11 Linux)
|
||||
- `builddir/ahfail-display` — display binary (spawned by PAM module)
|
||||
|
||||
### Install
|
||||
#### Install
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /usr/local/lib/ahfail
|
||||
@@ -79,33 +122,38 @@ sudo cp builddir/libahfail_pam.so /usr/local/lib/ahfail/
|
||||
sudo cp builddir/ahfail-display /usr/local/lib/ahfail/
|
||||
```
|
||||
|
||||
### Configure PAM (macOS)
|
||||
#### Configure PAM
|
||||
|
||||
Find your screen locker's PAM service file. On macOS 13+ the screensaver uses `/etc/pam.d/screensaverui`; on older versions it may be `/etc/pam.d/screensaver`. Add the following line after the existing `auth` entries (requires `sudo`):
|
||||
Find the screensaver PAM service file:
|
||||
- macOS 13 (Ventura) and later: `/etc/pam.d/screensaverui`
|
||||
- macOS 12 and earlier: `/etc/pam.d/screensaver`
|
||||
|
||||
Add this line after the existing `auth` entries (requires `sudo`):
|
||||
|
||||
```
|
||||
auth optional /usr/local/lib/ahfail/libahfail_pam.so
|
||||
```
|
||||
|
||||
### Configure PAM (Linux/X11)
|
||||
|
||||
Add to `/etc/pam.d/gtklock` (or `i3lock`, `xscreensaver`, etc.). Use the full path because `$(libdir)/ahfail` is not in PAM's default search path:
|
||||
|
||||
```
|
||||
auth optional /usr/lib/ahfail/libahfail_pam.so
|
||||
```
|
||||
|
||||
On Fedora/RHEL replace `/usr/lib` with `/usr/lib64`.
|
||||
|
||||
## Development
|
||||
|
||||
* **Run Tests:** `cargo test`
|
||||
* **Benchmarks:** `cargo test --test benchmarks -- --nocapture`
|
||||
* **Linting:** `cargo clippy`
|
||||
---
|
||||
|
||||
## Customization
|
||||
|
||||
To change the default sprite or audio:
|
||||
1. Replace files in `assets/`.
|
||||
2. Update `assets/ahfail.gresource.xml`.
|
||||
3. Rebuild with Meson.
|
||||
The sprite is the author's face on Nedry's body. To use your own:
|
||||
|
||||
1. Replace the sprite frames in `assets/sprites/` with your own PNG sequence.
|
||||
2. Update `assets/ahfail.gresource.xml` if you add or remove files.
|
||||
3. Rebuild with Meson.
|
||||
|
||||
To replace the audio clip, swap `assets/audio/magic-word.mp3` and rebuild.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
cargo test # unit + integration tests (needs display)
|
||||
cargo test --test benchmarks -- --nocapture # sprite placement benchmarks
|
||||
cargo clippy # lint
|
||||
meson setup builddir && meson compile -C builddir # full build including .so
|
||||
xvfb-run meson test -C builddir --verbose # Meson tests headless
|
||||
```
|
||||
|
||||
98
scripts/install-macos.sh
Executable file
98
scripts/install-macos.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
# install-macos.sh — build and install ahfail on macOS
|
||||
# Run from the repository root: bash scripts/install-macos.sh
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_DIR="/usr/local/lib/ahfail"
|
||||
BOLD=$(tput bold 2>/dev/null || true)
|
||||
RESET=$(tput sgr0 2>/dev/null || true)
|
||||
|
||||
step() { echo "${BOLD}==> $*${RESET}"; }
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
[[ "$(uname)" == "Darwin" ]] || die "This script is for macOS only."
|
||||
|
||||
# ── 1. Homebrew ───────────────────────────────────────────────────────────────
|
||||
if ! command -v brew &>/dev/null; then
|
||||
die "Homebrew is required. Install it from https://brew.sh then re-run this script."
|
||||
fi
|
||||
|
||||
step "Installing Homebrew dependencies..."
|
||||
brew install --quiet gtk+3 gstreamer gst-plugins-base gst-plugins-good meson ninja
|
||||
|
||||
# ── 2. Rust ───────────────────────────────────────────────────────────────────
|
||||
if ! command -v cargo &>/dev/null; then
|
||||
step "Installing Rust via rustup..."
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
|
||||
# shellcheck source=/dev/null
|
||||
source "$HOME/.cargo/env"
|
||||
fi
|
||||
|
||||
# ── 3. Build ──────────────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
step "Building ahfail..."
|
||||
if [[ -d builddir ]]; then
|
||||
meson setup builddir --wipe
|
||||
else
|
||||
meson setup builddir
|
||||
fi
|
||||
meson compile -C builddir
|
||||
|
||||
# ── 4. Install binaries ───────────────────────────────────────────────────────
|
||||
step "Installing binaries to ${INSTALL_DIR}/ (requires sudo)..."
|
||||
sudo mkdir -p "$INSTALL_DIR"
|
||||
sudo cp builddir/libahfail_pam.so "$INSTALL_DIR/"
|
||||
sudo cp builddir/ahfail-display "$INSTALL_DIR/"
|
||||
sudo chmod 755 "$INSTALL_DIR/libahfail_pam.so" "$INSTALL_DIR/ahfail-display"
|
||||
|
||||
# ── 5. Configure PAM ─────────────────────────────────────────────────────────
|
||||
step "Configuring PAM..."
|
||||
|
||||
# macOS 13+ uses screensaverui; 12 and earlier uses screensaver
|
||||
if [[ -f /etc/pam.d/screensaverui ]]; then
|
||||
PAM_FILE="/etc/pam.d/screensaverui"
|
||||
elif [[ -f /etc/pam.d/screensaver ]]; then
|
||||
PAM_FILE="/etc/pam.d/screensaver"
|
||||
else
|
||||
echo ""
|
||||
echo " Could not find a screensaver PAM file. Add this line manually"
|
||||
echo " to the appropriate file in /etc/pam.d/ (after the auth entries):"
|
||||
echo ""
|
||||
echo " auth optional ${INSTALL_DIR}/libahfail_pam.so"
|
||||
echo ""
|
||||
echo "Done (PAM not configured automatically)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PAM_LINE="auth optional ${INSTALL_DIR}/libahfail_pam.so"
|
||||
|
||||
if grep -qF "$PAM_LINE" "$PAM_FILE"; then
|
||||
echo " PAM already configured in ${PAM_FILE}."
|
||||
else
|
||||
# Insert after the last 'auth' line so we stay in the auth block.
|
||||
sudo python3 - "$PAM_FILE" "$PAM_LINE" <<'PYEOF'
|
||||
import sys
|
||||
|
||||
pam_file, new_line = sys.argv[1], sys.argv[2] + "\n"
|
||||
lines = open(pam_file).readlines()
|
||||
|
||||
if new_line in lines:
|
||||
sys.exit(0)
|
||||
|
||||
# Find the last line that starts with 'auth'
|
||||
last_auth = max(
|
||||
(i for i, l in enumerate(lines) if l.strip().startswith("auth")),
|
||||
default=len(lines) - 1,
|
||||
)
|
||||
lines.insert(last_auth + 1, new_line)
|
||||
open(pam_file, "w").writelines(lines)
|
||||
PYEOF
|
||||
echo " Added to ${PAM_FILE}."
|
||||
fi
|
||||
|
||||
# ── Done ──────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "${BOLD}Done.${RESET} Lock your screen and type the wrong password to test."
|
||||
Reference in New Issue
Block a user