# att_lock An Android-style **4×4 dot pattern** screen locker for `wlroots`-based Wayland compositors (Sway, Hyprland, river, labwc, …). It uses the secure [`ext-session-lock-v1`] protocol, verifies the drawn pattern against a salted hash stored in an XDG-compliant directory, and falls back to **PAM** password authentication. [`ext-session-lock-v1`]: https://wayland.app/protocols/ext-session-lock-v1 ## Features - **4×4 dot grid**, drawn by dragging with a **pointer or touch** — including Android's rule that intermediate dots on a straight line get captured. - **Rotation / any aspect ratio**: the grid is a centred square sized from the compositor-provided surface dimensions, so portrait, landscape and rotated outputs all just work. HiDPI (integer `wl_output` scale) is honoured. - **First-run setup**: with no stored pattern (or `--setup`) you draw the pattern twice to confirm it. It is stored as a salted **yescrypt** hash (falling back to sha512crypt) at `$XDG_DATA_HOME/att_lock/pattern.hash` (mode `0600`), never in plaintext. The hash uses cost factor 9 (~0.22 s per attempt) and covers your username as well as the pattern, so the same pattern on two accounts produces unrelated hashes. - **PAM password fallback**: start typing at the unlock screen to switch to a password field authenticated through PAM (for "forgot pattern"). - **Background image** via a command-line argument (PNG), scaled to cover. - Secure by construction: if the process dies while locked, the compositor keeps the session locked (a guarantee of `ext-session-lock-v1`). ## Build The build system is **Nix flakes**. ```sh # One-off build: nix build # -> ./result/bin/att_lock # Or a dev shell + meson: nix develop meson setup build ninja -C build # -> ./build/att_lock ``` Dependencies (provided by the flake): `wayland`, `wayland-scanner`, `cairo`, `libxkbcommon`, `pam` (linux-pam), `libxcrypt`, plus `meson`/`ninja`/`pkg-config`. ## Usage ``` att_lock [options] [IMAGE] -i, --image PATH Background image (PNG); also accepted positionally -p, --pam-service NAME PAM service for the password fallback (default: login) -m, --min-dots N Minimum dots in a pattern (default: 4) --setup Force (re)creation of the stored pattern -h, --help Show help ``` Examples: ```sh att_lock ~/wallpaper.png # lock with a background att_lock --setup # re-record the pattern, then unlock att_lock -p att_lock -i bg.png # use a dedicated PAM service ``` ### Unlocking - **Draw** your pattern (≥ `--min-dots` dots) and release to unlock. - **Type** any character to switch to the **password** field; press **Enter** to authenticate via PAM, **Esc** to go back to the pattern. After 4 failed attempts, further attempts are locked out for an escalating delay — 15 s, then 30 s, 60 s, 120 s, 240 s, capped at 5 minutes — with the remaining time shown on screen. The counter is shared between the pattern and the password field, so switching to the password does not sidestep an active lockout, and it is held in memory only (there is no way to restart the locker to clear it, since the compositor keeps the session locked regardless). The countdown uses a monotonic clock, so it cannot be skipped by changing the system time or by suspending the machine. ## PAM configuration The password fallback authenticates through PAM using the service named by `--pam-service` (default `login`, which exists on most systems). For a dedicated service, create one that stacks `pam_unix`: **NixOS** (`configuration.nix`): ```nix security.pam.services.att_lock = {}; # inherits sane defaults (pam_unix) ``` then run `att_lock --pam-service att_lock`. **Other distros** (`/etc/pam.d/att_lock`): ``` auth include login account include login ``` > The pattern itself is **not** checked by PAM — it is verified against the > local salted hash. PAM only backs the password fallback. ## Wiring it into your compositor Use it as your lock command, e.g. with `swayidle`: ```sh swayidle -w \ timeout 300 'att_lock ~/wallpaper.png' \ before-sleep 'att_lock ~/wallpaper.png' ``` or bind it in Sway: ``` bindsym $mod+Escape exec att_lock ~/wallpaper.png ``` ## Notes & limitations - A pattern is not a password. The lockout above throttles *online* guessing at the lock screen, but it cannot help offline: even at cost factor 9 there are only 43,680 possible 4-dot patterns, so anyone who obtains `pattern.hash` can search that space at their own pace. Raise `--min-dots` if that matters to you; each extra dot multiplies the space by roughly an order of magnitude. - Patterns are also far more shoulder-surfable than typed passwords, and on touchscreens finger smudges can reveal them. - Hash files written by versions before the `att_lock-hash-v3` format are not readable; att_lock reports this and asks you to draw a new pattern. The PAM password fallback still works in the meantime. In particular, v3 is the format written since this project was renamed from `wlpattern`: the account name and the project name are both baked into the hash input, so a pattern enrolled under `wlpattern` (in `$XDG_DATA_HOME/wlpattern/`) cannot be carried over and must be re-drawn once. - Background images must be **PNG** (loaded via Cairo). Convert other formats first (e.g. `magick in.jpg out.png`). - The pattern is rendered identically on every connected output; the live drag segment is drawn on the output receiving input. - Rendering uses `wl_shm` + Cairo (no GPU/EGL dependency). ## Layout ``` flake.nix Nix flake: package + dev shell meson.build build definition protocols/ext-session-lock-v1.xml vendored Wayland protocol src/main.c Wayland client, input, unlock/setup logic src/pattern.c grid geometry + pattern sequence model src/render.c wl_shm buffers + Cairo drawing src/auth.c hashing/storage (crypt) + PAM src/att_lock.h shared types and interfaces ``` ## License MIT. The vendored protocol XML is MIT (wlroots contributors).