Skip to content

Repository files navigation

Luma

CI Release License

Luma is a secure, customizable Wayland session locker written in Rust. It uses ext-session-lock-v1 for real session locking, authenticates through PAM, and is designed first for the niri compositor.

It can capture each output before locking, blur the screenshots in memory, and render a configurable clock, date, typography, and language-neutral password feedback over an always-opaque lock surface.

Table of contents

Warning

Luma is experimental. Test it in a nested compositor before using it in your primary session, keep swaylock installed as a recovery option, and read the safe testing guide before changing automatic or suspend hooks.

Preview

Luma lock screen with a blurred wallpaper, clock, date, and password indicator

Features

  • Real Wayland session locking through ext-session-lock-v1.
  • PAM authentication with zeroizing password memory.
  • One opaque lock surface for every active output.
  • Optional cursor-free screenshot capture and asynchronous bounded software blur.
  • Configurable clock, date, colors, geometry, formats, and TTF/OTF fonts.
  • Independent fonts and colors for hours, minutes, and the date.
  • Visual loading, failure, and cooldown feedback without localized text.
  • Output hotplug handling with an opaque fallback for uncaptured outputs.
  • Backspace removes one character; Ctrl+Backspace clears the complete input.
  • Debug-only demo and smoke paths excluded from release builds.

Installation

Distribution packages use the name lumalock, while the installed executable remains luma. This keeps the package name distinctive without breaking Luma configuration, commands, or the PAM service name.

Fedora (COPR)

The official Luma COPR currently provides Fedora 44 x86_64 packages. Enable the repository and install lumalock with:

sudo dnf copr enable ryannnkl/lumalock
sudo dnf install lumalock

The package is built by Fedora COPR from the tagged source and locked, vendored Rust dependencies. It installs the executable at /usr/bin/luma and the PAM policy at /etc/pam.d/luma.

Arch Linux (AUR)

Install lumalock with an AUR helper:

yay -S lumalock

Or build the reviewed recipe directly with the standard Arch tools:

git clone https://aur.archlinux.org/lumalock.git
cd lumalock
makepkg -si

The package builds the tagged source with its locked Rust dependencies and installs the executable at /usr/bin/luma and the PAM policy at /etc/pam.d/luma.

Prebuilt release

Prebuilt releases currently support Linux x86_64. Install the latest release from GitHub with:

curl -fsSL https://raw.githubusercontent.com/Ryannnkl/luma/main/install.sh | bash

The installer:

  1. Downloads the latest binary, PAM policy, and checksums from GitHub Releases.
  2. Verifies the downloaded release assets.
  3. Installs the reviewed PAM policy at /etc/pam.d/luma, using sudo when the policy is missing or different.
  4. Atomically installs the binary at ~/.local/bin/luma.

Set LUMA_INSTALL_DIR to choose another user-writable binary directory:

curl -fsSL https://raw.githubusercontent.com/Ryannnkl/luma/main/install.sh |
  LUMA_INSTALL_DIR="$HOME/bin" bash

Luma dynamically uses PAM and libxkbcommon. On Fedora these runtime libraries can be installed with:

sudo dnf install pam-libs libxkbcommon

Uninstallation

Before uninstalling, remove Luma from automatic lock hooks such as niri, Waybar, wlogout, and swayidle. Restore another locker for before-sleep so the session is not left without automatic locking.

If Luma was installed from Fedora COPR, remove the package and disable its repository with:

sudo dnf remove lumalock
sudo dnf copr disable ryannnkl/lumalock

If Luma was installed from the AUR, remove the package with:

sudo pacman -Rns lumalock

If Luma was installed with the curl command above, remove the installed binary and PAM policy manually with:

rm -f "${LUMA_INSTALL_DIR:-$HOME/.local/bin}/luma"
sudo rm -f /etc/pam.d/luma

The user configuration is intentionally preserved. Remove it too only when its settings are no longer needed:

rm -rf "$HOME/.config/luma"

First run

Check the active compositor without locking it:

luma --check
luma --outputs

Create a user configuration from the complete example:

mkdir -p ~/.config/luma
curl -fsSL https://raw.githubusercontent.com/Ryannnkl/luma/main/config.example.toml \
  -o ~/.config/luma/config.toml

Do not make Luma your automatic locker yet. Follow the nested-compositor procedure, verify normal and failed authentication, and only then run one deliberate manual trial in the primary session:

luma --lock

Configuration

Luma reads ~/.config/luma/config.toml. Missing sections use safe defaults; unknown fields and invalid values abort before the session-lock request.

A compact configuration looks like this:

[background]
capture_enabled = true
# Set capture_enabled = false and use wallpaper_path for a static image instead.
# wallpaper_path = "/home/user/Pictures/wallpaper.webp"
blur_radius = 24
dim_color = "#00000052"

[clock]
enabled = true
layout = "single_line"
hour_format = "%H"
minute_format = "%M"
hour_color = "#93e6be"
minute_color = "#f6f8f7"
# hour_font_path = "/usr/share/fonts/example/Example-Bold.ttf"
# minute_font_path = "/usr/share/fonts/example/Example-Bold.ttf"

[date]
enabled = true
format = "%A, %d de %B"
# font_path = "/usr/share/fonts/example/Example-Regular.ttf"
color = "#f6f8f7dc"

[indicators]
enabled = true
anchor = "center_right"
orientation = "vertical"
margin = 48.0
gap = 12.0
item_size = 64.0
columns = 2
font_size = 18.0
keyboard_layouts = ["us", "br"]

[indicators.keyboard_layout]
enabled = true
label = "Layout"
style = "circle"
icon = "keyboard_layout"
value_position = "below"
show_label = false

[indicators.num_lock]
enabled = true
label = "Num Lock"
style = "circle"
icon = "num_lock"
value_position = "below"
show_label = false

[indicators.battery]
enabled = true
label = "Battery"
style = "ring"
icon = "battery"
value_position = "below"
show_label = false

[indicators.wifi]
enabled = true
label = "WiFi"
style = "circle"
icon = "wifi"
value_position = "below"
show_label = false

[indicators.cpu]
enabled = true
label = "CPU"
style = "ring"
icon = "cpu"
value_position = "below"
show_label = false

[indicators.ram]
enabled = true
label = "RAM"
style = "ring"
icon = "ram"
value_position = "below"
show_label = false

[indicators.temperature]
enabled = true
label = "Temp"
style = "rounded_square"
icon = "temperature"
value_position = "below"
show_label = false

[indicators.uptime]
enabled = true
label = "Uptime"
style = "hexagon"
icon = "uptime"
value_position = "below"
show_label = false

[input]
enabled = true
hide_when_empty = true
corner_radius = 17.0
border_width = 0.0
border_color = "#ffffff30"
max_characters = 12

Important details:

  • capture_enabled is disabled by default. When enabled, capture failure aborts before locking rather than silently showing unexpected content.
  • wallpaper_path is an optional absolute path to a static PNG, JPEG, WebP, BMP, TIFF, GIF, or ICO image. It is mutually exclusive with capture_enabled; the static image is scaled with cover to each output.
  • blur_radius accepts values from 0 through 64; 0 keeps the selected background sharp.
  • Positive blur runs outside the Wayland event loop. Luma can display its opaque fallback and usable prompt immediately, then replace the background when the blurred capture is ready.
  • hour_color, minute_color, and date.color are independent.
  • clock.layout accepts single_line (the default) or stacked; the former renders the time as HH:MM centered on one line.
  • Font paths are optional, absolute TTF/OTF paths. Each configured font must be a regular valid file no larger than 16 MiB.
  • Time and date formats use Chrono/strftime directives such as %H, %M, %p, %d, %m, and %Y. Weekday and month names follow the system locale from LC_ALL, LC_TIME, or LANG, in that order.
  • Indicators are enabled individually under [indicators]. The default group is a vertical column centered on the right side. anchor accepts nine named positions or custom; custom positions use normalized x and y coordinates. orientation accepts vertical or horizontal.
  • Available indicators are keyboard_layout, num_lock, battery, wifi, cpu, ram, temperature, and uptime. Keyboard layout names correspond to the compositor's layout index and are configured with keyboard_layouts.
  • Indicator styles are text, circle, ring, square, rounded_square, and hexagon. Icons are built in and can be selected with icon; values can be shown below or inside, and labels can be hidden with show_label.
  • item_size controls the shape size, icon_size controls the icon size, background_enabled toggles the common fill, and border_width plus border_color control the common border for every shaped indicator.
  • Indicator orientation accepts vertical, horizontal, grid, or honeycomb. Grid and honeycomb use columns to control their shape.
  • Colors accept #RRGGBB or #RRGGBBAA.
  • Positions use normalized coordinates from 0.0 to 1.0.
  • input.corner_radius and input.border_width use logical pixels. Neither may exceed half of the input's shortest dimension.
  • input.border_color supports alpha and is composited into the opaque lock frame.
  • The real authentication prompt remains visible even if [input].enabled is configured as false.
  • input.hide_when_empty hides the ready-state input indicator until the first character is typed. It does not hide authentication feedback or the prompt when [input].enabled = false.

See config.example.toml for every available field.

niri integration

After successful nested and manual tests, the normal niri keybinding can launch Luma directly:

binds {
    Super+Alt+L hotkey-overlay-title="Lock with Luma" {
        spawn "luma" "--lock"
    }
}

Interactive launchers such as wlogout and Waybar should also run luma --lock. For swayidle, use the normal command for an idle timeout and the readiness-aware mode for before-sleep:

spawn-sh-at-startup "swayidle -w timeout 600 'luma --lock' timeout 900 'niri msg action power-off-monitors' before-sleep 'luma --lock --daemonize'"

--daemonize does not weaken or bypass the lock. Its parent waits until niri has confirmed the session lock, every current output has an opaque frame, and the Wayland connection has been flushed. It then exits so swayidle -w can allow suspend to continue while the child remains responsible for authentication. Keep swaylock installed as a manual recovery option during the initial Luma trial period, and test suspend/resume only after the other integrations work.

Testing and recovery

Real lock testing must start in a nested niri protected by the external 60-second watchdog:

git clone https://github.com/Ryannnkl/luma.git
cd luma
LUMA_ALLOW_NESTED_TEST=1 ./scripts/test-nested-lock.sh

Stop the isolated test early with:

./scripts/test-nested-lock.sh --stop

Before a primary-session trial, save open work and verify that you can access a TTY and identify the graphical session. Killing a session-lock client is not an unlock mechanism; a broken primary-session test may require terminating the graphical session and losing unsaved work.

The complete gates and recovery commands are documented in docs/TESTING.md.

Development

Source builds use stable Rust. Fedora development dependencies include:

sudo dnf install cargo rust pam-devel libxkbcommon-devel

Run the project checks with:

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --locked
cargo build --locked --release

The debug-only visual demo is available with:

cargo run -- --demo

Do not enter a real password in demo mode. It does not acquire a session lock or authenticate through PAM, and release builds do not contain it.

Maintainer releases

Once the protected release environment is configured, a maintainer can prepare and start the complete GitHub, AUR, and COPR release with:

./scripts/start-release.sh 0.4.0 "Add configurable animations"

The script updates the version metadata, creates one release-preparation commit, pushes it, and starts the protected workflow. Publishing pauses once for approval after all credential-free validation has passed. See docs/DISTRIBUTION.md for credential setup, package validation, and recovery from a partially completed release.

Security model

Luma treats security-sensitive code separately from presentation:

  • Only a successful PAM result associated with the active authentication token can authorize unlock_and_destroy.
  • Password contents are never logged or rendered and are cleared after every attempt.
  • PAM runs outside the Wayland rendering loop.
  • Authentication failure categories share the same visual feedback.
  • Release builds contain no timer, escape key, secret bypass, or crash-to-unlock path.
  • Configurations, fonts, PAM policy, and critical rendering resources are validated before requesting the session lock.
  • Screenshots remain in memory, exclude the cursor, and are dropped when Luma exits.

Read AGENTS.md for the complete safety invariants and docs/ARCHITECTURE.md for the runtime boundaries.

Project documentation

License

Luma is distributed under the MIT License.

About

A secure and customizable Wayland session locker for niri

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages