Screen locker for qdistro. Qt/QML UI driven by a Python
controller, talking to qdwin over the
qdwin_locker_v1 private Wayland protocol.
qdlocker owns runtime re-authentication for the whole machine. qdistro is single-tenant, so one lock covers every silo and session surface on the active compositor. qdlocker authenticates the owner through fprintd/PAM and then asks qdwin to demote the lock layer.
Boot login is deliberately separate and belongs to qdgreeter. The production session should treat qdlocker as part of the qdwin session stack, not as optional shell chrome.
Sibling to qdshell, not part of it. The original locker
lived inside qdshell as Modules/LockScreen/*.qml; this repo lifts
it out so the locker's process lifecycle is independent of the shell
(a shell crash no longer drops the screen unlocked, and a locker
crash doesn't take chrome down with it).
┌──────────────────────────────────────────────────────────┐
│ qdwin (C) │
│ │
│ qdwin_shell_v1 ──── allowed_uid filter ──── qdshell │
│ │
│ qdwin_locker_v1 ──── allowed_locker_uid ──── qdlocker │
│ │
│ shared LOCK layer + state machine (qdwin.c:4946+) │
└──────────────────────────────────────────────────────────┘
The locker:
- Binds
qdwin_locker_v1as the sole locker client. - Renders the lock UI into a
QQuickWindow; qdwin identifies that Qt toplevel as belonging to the authorized locker process and promotes it to the LOCK layer while locked. - Calls
set_locked(1)on lock trigger,set_locked(0)after auth. - Receives keystrokes via
overlay_key(qdwin grabs the keyboard while locked so the password text never reaches qdshell or any user app). - Auths via
fprintdon the system bus andpython-pamas the password fallback (mirrorsqdistro/doc/sessions.md:39-40).
The reciprocal compositor wiring is documented in
../qdwin/doc/locker.md.
qdlocker/
├── qdlocker/ Python package
│ ├── app.py QGuiApplication + QQmlApplicationEngine entry
│ ├── controller.py LockController (Python mirror of LockContext.qml)
│ ├── auth.py fprintd D-Bus + PAM
│ ├── wayland.py qdwin_locker_v1 client
│ ├── idle.py ext-idle-notify-v1 subscription
│ └── keysyms.py XKB keysym constants
├── qml/
│ ├── Main.qml Root QQuickWindow
│ └── LockUI.qml Visual layout (reuses qs.Commons + qs.Widgets)
├── protocol/
│ └── qdwin-locker-v1.xml Vendored copy of the protocol XML
├── systemd/
│ └── qdlocker.service User unit
├── tests/
│ ├── gui/ VM tests (virsh + QMP, mirrors qdwin/tests/gui/)
│ └── unit/ pytest controller/auth tests
└── pyproject.toml
The QML imports qs.Commons (Style.qml, Color.qml, Icons.qml) and
qs.Widgets (NText, NIcon, NIconButton, NBusyIndicator) from the
qdshell sibling repo. app.py:_qdshell_import_path finds qdshell
automatically when the two repos are siblings; override with
QDLOCKER_QDSHELL_PATH=/path/to/qdshell if you've laid things out
differently. The locker falls back to inline rendering for the
header if qdshell isn't on the path.
QDLOCKER_NO_WAYLAND=1 \
QDLOCKER_QDSHELL_PATH=$(pwd)/../qdshell \
python -m qdlockerThe UI comes up in a regular Wayland window; useful for iterating on the QML layout without restarting qdwin.
Install the systemd user unit and start it after qdwin/qdshell:
install -m 644 systemd/qdlocker.service ~/.config/systemd/user/
systemctl --user enable --now qdlocker.serviceThe tests under tests/gui/ are literate Markdown scenarios driven
by the same primitives qdwin uses (virsh send-key for keyboard, QMP
input-send-event for chords with modifier discipline, virsh screenshot for visual asserts, qdshell ctrl-socket for state
introspection, qdlocker's own --ctrl-socket for locker state).
Quickstart:
# Build a qdistro guest image with qdlocker installed (see
# qdistro/tier4-vm/build-guest-image.sh):
$ ../qdistro/tier4-vm/build-guest-image.sh --include qdlocker
# Launch it:
$ ../qdistro/tier4-vm/spawn-tier4.sh
# Drive the 01-lock-cycle scenario:
$ cd tests/gui
$ source qdlocker-helpers.sh
$ qdwin_set_vm "$(virsh -c qemu:///session list --name --state-running | head -1)"
$ bash run-scenario.sh 01-lock-cycle.mdThe harness lives in tests/gui/qdlocker-helpers.sh; it sources
qdwin-helpers.sh from the qdwin sibling and adds:
qdlocker_ctrl <command>— talks to qdlocker's ctrl socket (/run/user/<uid>/qdlocker.sock) to introspect state (locked flag, prompt buffer length, last error).qdlocker_wait_for_unlock— polls until the controller reportsunlocked, with a default 5s timeout.
Implemented preview:
- ✅ Protocol XML + meson generation in qdwin
- ✅ Python controller + auth backend (fprintd + PAM)
- ✅ QML UI reusing qdshell styling
- ✅ systemd unit + README
- ✅ VM test scaffolding
- ✅ qdwin C-side
bind_qdwin_locker+ locker resource handlers (see qdwin/doc/locker.md) - ✅ pywayland scanner output committed under
protocol/ - ✅ Ctrl-socket implementation for test introspection (
qdlocker/ctrl.py), wired fromapp.pyafter the QML root window is ready
See tests/gui/01-lock-cycle.md for the acceptance criterion.