Lock
A build function that calls ui.session_lock() asks the compositor to lock the session; ui.lock_surface() marks what to draw on each output.
bool locked = false; // set by a keybind, a signal, an idle timer…
app.surface([&](vw::Ui& ui) {
if (!locked) return; // draws nothing: no lock
ui.session_lock({.on_unlock = [&] { locked = false; }});
ui.lock_surface();
const vw::LockSurfaceContext output = vw::lock_surface_context();
const float w = output.width, h = output.height;
ui.rectangle({.width = w, .height = h, .color = vw::rgb(0x101419)}); // opaque first
ui.text_field({.id = "password", .placeholder = "Password", .width = 320,
.password = true, .authenticate = true, .autofocus = true,
.place = {.x = (w - 320) / 2, .y = h * 0.6f}});
});
The function runs once per output; lock_surface_context() says which one and how big it is. Draw something fully opaque first — nothing behind a lock may ever show.
Authenticate
A text field with authenticate = true checks its value with PAM when Enter is pressed. Success unlocks the session, destroys the lock surfaces and calls on_unlock; clear whatever made your build draw the lock there. Without on_unlock, unlocking ends the app.
vw::session_authentication_failed()is true after a wrong password, until the next attempt — shake the field, show an error.pam_servicedefaults toswaylockwhen/etc/pam.d/swaylockexists, otherwiselogin;pam_confcan point at a PAM file of your own.on_authenticated(bool)on the field reports every attempt.
Habits that help
- Make the first key only reveal the password field instead of typing into it — a habitual space or Enter should not land in the password.
- Clear the field after a period without input.
- Keep entrance animations on top of the opaque base, never instead of it.