I ran into a small but surprisingly annoying problem with the KDE Plasma lock screen and the fingerprint reader on my ThinkPad.
After opening the laptop, KDE would show the usual lock screen with the wallpaper and clock. Nothing unusual here. The problem was that the password field wouldn’t appear until I pressed a key or moved the mouse.
That also meant the fingerprint reader wasn’t listening yet.
So instead of:
open the lid → put my finger on the sensor
I had to do:
open the lid → wait → press a key → put my finger on the sensor
It’s a small thing, but when you unlock your laptop with a fingerprint dozens of times a day, it gets annoying pretty quickly.
I wanted the lock screen to start accepting fingerprints immediately after it appeared.
Update (2026-09-19): the approach described below turned out to have a serious side effect — it kept the fingerprint reader (and with it the USB controller) awake for the entire suspend, which blocked the CPU’s S0ix sleep state and drained the battery overnight. I’m keeping the original post for context, but don’t apply it as-is: see the update section at the bottom for the root cause and the replacement solution.
Where the problem comes from
KDE doesn’t seem to have a setting for this. The lock screen configuration only controls things like the clock, wallpaper and media controls, so there isn’t an obvious option to make the authentication UI appear immediately.
At that point, I decided to look at the code instead.
In Plasma 6, the lock screen UI is part of plasma-workspace and, on my system, lives here:
/usr/share/plasma/shells/org.kde.plasma.desktop/contents/lockscreen/LockScreenUi.qml
This is worth mentioning because there are quite a few old guides referring to files under /usr/share/plasma/look-and-feel/.... Those paths are from older versions of Plasma.
The interesting part of LockScreenUi.qml is a MouseArea covering the whole screen:
MouseArea {
id: lockScreenRoot
property bool uiVisible: false
// ...
onPressed: uiVisible = true;
Keys.onPressed: event => {
uiVisible = true;
event.accepted = false;
}
onUiVisibleChanged: {
// ...show UI, start fadeout timer...
authenticator.startAuthenticating();
}
The logic is actually quite simple.
Initially, uiVisible is false. The lock screen shows the wallpaper and clock, while the authentication UI remains hidden.
When you press a key or click the screen, uiVisible becomes true. That triggers onUiVisibleChanged, which eventually calls:
authenticator.startAuthenticating();
And that’s the important part.
The fingerprint reader isn’t ignoring the first scan. Authentication simply hasn’t started yet. Until the first input event, fprintd isn’t being asked to verify a fingerprint.
The simple fix
There is already a piece of code that runs when the lock screen is initialized:
Component.onCompleted: launchAnimation.start();
Instead of waiting for user input to make uiVisible true, we can simply make it visible as soon as the component is loaded:
Component.onCompleted: {
launchAnimation.start();
lockScreenRoot.uiVisible = true;
}
That’s it.
Setting uiVisible triggers the existing onUiVisibleChanged handler, so the normal authentication flow starts immediately. The password field appears, and fprintd starts listening for a fingerprint without requiring any additional input.
There is no new authentication logic here. We’re just moving the start of the existing logic a little earlier.
Keeping the patch after updates
There is one obvious problem with this approach: LockScreenUi.qml belongs to the plasma-workspace package.
That means a Plasma update will overwrite the file and remove the change.
I considered keeping a complete copy of the lock screen under the user’s local Plasma shell directory, but that seemed worse. We’d have to maintain a copy of a fairly large part of the shell, and sooner or later it could get out of sync with the version shipped by Plasma.
A small patch that can be reapplied after every update seems like a better compromise.
Since I’m running Arch, I used a small script together with a pacman hook.
The patch script
I put the following script at /usr/local/bin/kde-lock-immediate:
#!/usr/bin/env bash
# Patch kscreenlocker's LockScreenUi.qml so the password UI is raised (and
# PAM/fprintd starts listening) as soon as the lock screen appears.
set -euo pipefail
QML=/usr/share/plasma/shells/org.kde.plasma.desktop/contents/lockscreen/LockScreenUi.qml
if [[ ! -f "$QML" ]]; then
echo "kde-lock-immediate: $QML not found, skipping" >&2
exit 0
fi
if grep -qF 'lockScreenRoot.uiVisible = true;' "$QML"; then
exit 0 # already patched
fi
if ! grep -qF 'Component.onCompleted: launchAnimation.start();' "$QML"; then
echo "kde-lock-immediate: patch target not found in $QML — the file changed upstream, this script needs updating" >&2
exit 1
fi
# Setting uiVisible triggers onUiVisibleChanged, which both raises the
# password UI and calls authenticator.startAuthenticating() (fprintd on).
sed -i 's|^\( *\)Component\.onCompleted: launchAnimation\.start();$|\1Component.onCompleted: {\n\1 launchAnimation.start();\n\1 lockScreenRoot.uiVisible = true;\n\1}|' "$QML"
echo "kde-lock-immediate: patched $QML"
There are a couple of things I like about this approach.
First, the script is idempotent. Running it again doesn’t modify an already patched file.
Second, it fails loudly if the expected code disappears. If a future Plasma version changes this part of LockScreenUi.qml, the pacman hook will report an error instead of silently pretending that everything is fine.
Finally, it only changes the one line we actually care about. The rest of the file remains untouched and continues to come from the installed Plasma package.
pacman hook
To automatically reapply the patch after plasma-workspace is updated, I created:
/etc/pacman.d/hooks/kde-lock-immediate.hook
with:
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = plasma-workspace
[Action]
Description = Re-applying KDE lock screen immediate-unlock patch...
When = PostTransaction
Exec = /usr/local/bin/kde-lock-immediate
Depends = bash
Depends = sed
Now every time plasma-workspace is installed or upgraded, pacman runs the script after the transaction and reapplies the change.
Installation
With both files prepared, the installation is just:
sudo install -Dm755 kde-lock-immediate /usr/local/bin/kde-lock-immediate
sudo install -Dm644 kde-lock-immediate.hook /etc/pacman.d/hooks/kde-lock-immediate.hook
sudo /usr/local/bin/kde-lock-immediate
The hook is obviously specific to pacman-based distributions such as Arch and its derivatives. On other distributions, the same patch can be used, but it needs to be reapplied after plasma-workspace updates using whatever package-manager mechanism is available.
Testing
You can lock the current session with:
$ loginctl lock-session
The lock screen should now show the authentication UI immediately instead of waiting for a key press or mouse movement.
More importantly, the fingerprint reader is already active.
The workflow is now exactly what I wanted:
close the lid → open the lid → put a finger on the sensor → unlocked
No keyboard interaction required.
A couple of caveats
The normal lock screen fade-out is still there. After about 10 seconds of inactivity, the authentication UI hides again and the screen returns to the wallpaper-and-clock state.
This doesn’t stop fingerprint authentication, though. The authentication session has already been started, so the fingerprint reader continues to work even while the UI is hidden.
There is also a theoretical race condition: if a fingerprint is recognized before PAM has displayed a prompt, the lock screen can end up showing its NoPasswordUnlock state with an “Unlock” button instead of unlocking immediately. In practice, I haven’t been able to reproduce this. PAM starts quickly enough, while fingerprint scanning takes noticeably longer.
Finally, this patch is tied to the current structure of LockScreenUi.qml. If KDE changes Component.onCompleted, renames launchAnimation, or restructures this part of the lock screen, the script will stop with an error. That’s intentional — I’d rather have the update tell me that the patch needs attention than silently leave me with a broken or unapplied modification.
And this only concerns the session lock screen. SDDM is a separate process with its own QML, so this change doesn’t affect the login screen.
Update: how this patch drained my battery overnight
A couple of weeks after setting this up, I noticed a much nastier problem: I’d close the lid in the evening with a charged battery, and in the morning the laptop would be completely dead. Not “lost 10%” dead — shut down from an empty battery dead.
The investigation (a story of its own, involving journalctl timelines and Intel’s pmc_core debug counters) led straight back to this patch. Here’s the full chain of causation.
Why the laptop died in its sleep
- Modern ThinkPads don’t have real sleep anymore. Since 12th-gen Intel, S3 (“deep” sleep) is gone — the kernel reports
ACPI: PM: (supports S0 S4 S5). The only suspend mode left iss2idle, and its power draw depends entirely on whether the platform manages to enter the S0ix low-power state. - S0ix requires the whole platform to go quiet. Among other things, the USB xHCI controller must reach D3 and the CPU package must reach the PC10 state.
- The patch starts authentication when the lock screen loads — and the lock screen loads when you close the lid, before suspend. Not after resume.
- So
fprintdstarted the moment the lid closed and held the Goodix fingerprint reader open for the entire night. An active USB device keeps the xHCI controller awake, xHCI never enters D3, the platform never enters S0ix — and “sleeping” actually means “idling with the screen off” at roughly 5 W instead of under 1 W. A 52 Wh battery doesn’t survive that until morning.
The signs were all there in hindsight:
fprintdspawning immediately onloginctl lock-session— not on wake. Easy to verify.- Repeated
usb 3-3: reset full-speed USB device(the fingerprint reader) indmesgaround every suspend/resume. sudo cat /sys/kernel/debug/pmc_core/slp_s0_residency_usecstuck at0across suspend cycles — the machine never actually entered S0ix.Package C10 : 0in/sys/kernel/debug/pmc_core/package_cstate_show.
The better fix: raise the prompt after resume, not at lock-screen load
The original goal — open the lid, touch the sensor, done — never required the prompt to appear when the lock screen loads. It only needs to be up when the machine wakes. Those are different moments, and the difference is exactly what killed the battery.
So instead of patching QML, I now simulate user activity right after resume. That triggers the stock lock screen’s own “user did something” path, which raises the prompt and starts fprintd — at the one moment it’s actually needed. No QML patch, no pacman hook, nothing to break on plasma-workspace updates.
The script (/usr/local/bin/kde-lock-raise):
#!/usr/bin/env bash
set -u
# Give kscreenlocker a moment to come up after wake.
sleep 2
# Pick the active graphical user session.
session=""
for s in $(loginctl list-sessions --no-legend | awk '$4 == "seat0" { print $1 }'); do
[ "$(loginctl show-session "$s" -p Class --value)" = "user" ] || continue
if [ "$(loginctl show-session "$s" -p State --value)" = "active" ]; then
session="$s"
break
fi
[ -z "$session" ] && session="$s"
done
[ -n "$session" ] || exit 0
user="$(loginctl show-session "$session" -p Name --value)"
uid="$(id -u "$user")"
runuser -u "$user" -- env DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" \
dbus-send --session --type=method_call --print-reply \
--dest=org.freedesktop.ScreenSaver /ScreenSaver \
org.freedesktop.ScreenSaver.SimulateUserActivity >/dev/null 2>&1 || true
And the systemd unit (/etc/systemd/system/kde-lock-raise.service) — a neat trick: the unit is pulled in by suspend.target, so its ExecStart runs before sleep, and its ExecStop runs when suspend.target deactivates, i.e. right after resume:
[Unit]
Description=Raise the KDE lock screen prompt after resume
After=suspend.target suspend-then-hibernate.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
# Fires when suspend.target deactivates, i.e. right after resume.
ExecStop=/usr/local/bin/kde-lock-raise
[Install]
WantedBy=suspend.target suspend-then-hibernate.target
Enable it, revert the QML patch (restore Component.onCompleted: launchAnimation.start();), and the UX is exactly the same as before: open the lid, touch the sensor, done. The difference is that while the machine sleeps, the fingerprint reader sleeps too.
Bonus finding: NVMe and L1.2
While chasing this, I found a second, independent blocker. My pcie_aspm.policy was set to powersave, which left the L1.2 link substates disabled on the NVMe SSD (L1SubCtl1 showed all - despite the hardware supporting them). Without L1.2 the CPU package never reaches PC10, which is a prerequisite for S0ix. Switching to powersupersave fixed that:
echo powersupersave | sudo tee /sys/module/pcie_aspm/parameters/policy
A third factor was an application (my editor) holding an ALSA playback stream open forever, which kept the audio DSP awake — visible as state: RUNNING in /proc/asound/card0/pcm0p/sub0/status.
With all three fixed, the measured suspend draw went from ~5 W to 0.94 W: about 20% of battery per 10-hour night instead of a dead laptop by morning.
Verification recipe
If you suspect your laptop doesn’t really sleep, the definitive test isn’t kernel counters but the battery itself:
E0=$(cat /sys/class/power_supply/BAT0/energy_now)
sudo rtcwake -m mem -s 300 # sleep for 5 minutes, auto-wake
E1=$(cat /sys/class/power_supply/BAT0/energy_now)
# (E0-E1) gives you the actual energy spent; healthy s2idle is well under 1 W.
Note: on my machine (13th-gen Intel, kernel 7.2) slp_s0_residency_usec stayed at 0 even after the fix, so don’t treat that counter as ground truth — the battery measurement is.