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.
- Preview
- Features
- Installation
- Uninstallation
- First run
- Configuration
- niri integration
- Testing and recovery
- Development
- Maintainer releases
- Security model
- Project documentation
- License
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.
- 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.
Backspaceremoves one character;Ctrl+Backspaceclears the complete input.- Debug-only demo and smoke paths excluded from release builds.
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.
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 lumalockThe 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.
Install lumalock with an AUR
helper:
yay -S lumalockOr build the reviewed recipe directly with the standard Arch tools:
git clone https://aur.archlinux.org/lumalock.git
cd lumalock
makepkg -siThe 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 releases currently support Linux x86_64. Install the latest release from GitHub with:
curl -fsSL https://raw.githubusercontent.com/Ryannnkl/luma/main/install.sh | bashThe installer:
- Downloads the latest binary, PAM policy, and checksums from GitHub Releases.
- Verifies the downloaded release assets.
- Installs the reviewed PAM policy at
/etc/pam.d/luma, usingsudowhen the policy is missing or different. - 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" bashLuma dynamically uses PAM and libxkbcommon. On Fedora these runtime libraries can be installed with:
sudo dnf install pam-libs libxkbcommonBefore 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/lumalockIf Luma was installed from the AUR, remove the package with:
sudo pacman -Rns lumalockIf 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/lumaThe user configuration is intentionally preserved. Remove it too only when its settings are no longer needed:
rm -rf "$HOME/.config/luma"Check the active compositor without locking it:
luma --check
luma --outputsCreate 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.tomlDo 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 --lockLuma 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 = 12Important details:
capture_enabledis disabled by default. When enabled, capture failure aborts before locking rather than silently showing unexpected content.wallpaper_pathis an optional absolute path to a static PNG, JPEG, WebP, BMP, TIFF, GIF, or ICO image. It is mutually exclusive withcapture_enabled; the static image is scaled withcoverto each output.blur_radiusaccepts values from0through64;0keeps 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, anddate.colorare independent.clock.layoutacceptssingle_line(the default) orstacked; the former renders the time asHH:MMcentered 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 fromLC_ALL,LC_TIME, orLANG, in that order. - Indicators are enabled individually under
[indicators]. The default group is a vertical column centered on the right side.anchoraccepts nine named positions orcustom; custom positions use normalizedxandycoordinates.orientationacceptsverticalorhorizontal. - Available indicators are
keyboard_layout,num_lock,battery,wifi,cpu,ram,temperature, anduptime. Keyboard layout names correspond to the compositor's layout index and are configured withkeyboard_layouts. - Indicator styles are
text,circle,ring,square,rounded_square, andhexagon. Icons are built in and can be selected withicon; values can be shownbeloworinside, and labels can be hidden withshow_label. item_sizecontrols the shape size,icon_sizecontrols the icon size,background_enabledtoggles the common fill, andborder_widthplusborder_colorcontrol the common border for every shaped indicator.- Indicator
orientationacceptsvertical,horizontal,grid, orhoneycomb. Grid and honeycomb usecolumnsto control their shape. - Colors accept
#RRGGBBor#RRGGBBAA. - Positions use normalized coordinates from
0.0to1.0. input.corner_radiusandinput.border_widthuse logical pixels. Neither may exceed half of the input's shortest dimension.input.border_colorsupports alpha and is composited into the opaque lock frame.- The real authentication prompt remains visible even if
[input].enabledis configured asfalse. input.hide_when_emptyhides 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.
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.
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.shStop the isolated test early with:
./scripts/test-nested-lock.sh --stopBefore 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.
Source builds use stable Rust. Fedora development dependencies include:
sudo dnf install cargo rust pam-devel libxkbcommon-develRun the project checks with:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --locked
cargo build --locked --releaseThe debug-only visual demo is available with:
cargo run -- --demoDo 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.
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.
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.
Luma is distributed under the MIT License.
