Refine Plasma lock screen authentication retries

This commit is contained in:
ajp_anton
2026-09-04 14:58:34 +00:00
parent 088c42bc14
commit 810c8caf74
5 changed files with 325 additions and 45 deletions
+3 -1
View File
@@ -48,7 +48,9 @@ display is turned off or the system resumes from sleep.
The package applies a narrow patch to Plasma 6.7's lock-screen QML and reapplies
it after `plasma-desktop` updates. An unknown future layout is left unchanged
instead of being modified speculatively. Removing the package restores Plasma's
original behavior.
original behavior. It retries lock-screen authentication after resume and the
automatic-lock grace period, but does not modify `kscreenlocker` authentication
code or `fprintd`.
### Plasma task-group shortcuts
+55 -41
View File
@@ -1,6 +1,6 @@
--- a/contents/lockscreen/LockScreenUi.qml
+++ b/contents/lockscreen/LockScreenUi.qml
@@ -83,6 +83,15 @@
@@ -83,20 +83,32 @@
}
}
@@ -16,7 +16,24 @@
SessionManagement {
id: sessionManagement
}
@@ -108,7 +117,6 @@
KeyboardIndicator.KeyState {
id: capsLockState
key: Qt.Key_CapsLock
}
Connections {
target: sessionManagement
function onAboutToSuspend() {
root.clearPassword();
}
+ function onResumingFromSuspend() {
+ resumeAuthenticationTimer.restart();
+ }
}
RejectPasswordAnimation {
@@ -108,7 +120,6 @@
id: lockScreenRoot
property bool uiVisible: false
@@ -24,8 +41,12 @@
property bool blockUI: containsMouse && (mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive)
x: parent.x
@@ -119,10 +127,7 @@
cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor
@@ -118,13 +129,10 @@
width: parent.width
height: parent.height
hoverEnabled: true
- cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor
+ cursorShape: Qt.ArrowCursor
drag.filterChildren: true
onPressed: uiVisible = true;
- onPositionChanged: {
@@ -36,49 +57,42 @@
onUiVisibleChanged: {
if (uiVisible) {
Window.window.requestActivate();
@@ -135,6 +140,13 @@
@@ -135,6 +143,18 @@
}
authenticator.startAuthenticating();
}
+ Timer {
+ // Keep the authentication backend active while the prompt is visible.
+ interval: 1000
+ running: parent.uiVisible
+ repeat: true
+ id: resumeAuthenticationTimer
+ interval: 1500
+ onTriggered: authenticator.startAuthenticating()
+ }
+ Timer {
+ id: postGraceAuthenticationTimer
+ // Retry once after Plasma's default five-second lock grace period.
+ interval: 6000
+ running: root.viewVisible
+ onTriggered: authenticator.startAuthenticating()
+ }
onBlockUIChanged: {
if (blockUI) {
fadeoutTimer.running = false;
@@ -143,19 +155,12 @@
fadeoutTimer.restart();
}
}
- onExited: {
- uiVisible = false;
- }
Keys.onEscapePressed: {
- // If the escape key is pressed, kscreenlocker will turn off the screen.
- // We do not want to show the password prompt in this case.
- if (uiVisible) {
- uiVisible = false;
- if (inputPanel.keyboardActive) {
- inputPanel.showHide();
- }
- root.clearPassword();
+ // KScreenLocker will turn off the screen; leave the prompt ready for wake-up.
+ if (inputPanel.keyboardActive) {
+ inputPanel.showHide();
}
+ root.clearPassword();
}
Keys.onPressed: event => {
uiVisible = true;
@@ -167,7 +172,6 @@
onTriggered: {
if (!lockScreenRoot.blockUI) {
mainBlock.mainPasswordBox.showPassword = false;
- lockScreenRoot.uiVisible = false;
}
}
}
@@ -194,6 +214,6 @@
WallpaperFader {
anchors.fill: parent
- state: lockScreenRoot.uiVisible ? "on" : "off"
+ state: "on"
source: wallpaper
mainStack: mainStack
footer: footer
@@ -211,7 +231,7 @@
samples: 15
spread: 0.2
color : Qt.rgba(0, 0, 0, 0.7)
- opacity: lockScreenRoot.uiVisible ? 0 : 1
+ opacity: 0
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.veryLongDuration * 2
@@ -265 +285 @@
- lockScreenUiVisible: lockScreenRoot.uiVisible
+ lockScreenUiVisible: true
@@ -1,6 +1,6 @@
Name: plasma-always-show-unlock
Version: 0.1.0
Release: 1%{?dist}
Release: 8%{?dist}
Summary: Always show the Plasma lock-screen unlock prompt
License: MIT AND GPL-2.0-or-later
@@ -56,5 +56,9 @@ fi
%{_datadir}/plasma-always-show-unlock/LockScreenUi.patch
%changelog
* Thu Sep 03 2026 fedora-tools contributors - 0.1.0-8
- Keep the prompt visible without continuously restarting authentication
- Retry authentication after resume and the automatic-lock grace period
* Wed Sep 02 2026 fedora-tools contributors - 0.1.0-1
- Initial package
@@ -176,3 +176,252 @@ Item {
interval: 3000
onTriggered: root.notification = ""
}
Timer {
id: graceLockTimer
interval: 3000
onTriggered: {
root.clearPassword();
authenticator.startAuthenticating();
}
}
PropertyAnimation {
id: launchAnimation
target: lockScreenRoot
property: "opacity"
from: 0
to: 1
duration: Kirigami.Units.veryLongDuration * 2
}
Component.onCompleted: launchAnimation.start();
WallpaperFader {
anchors.fill: parent
state: lockScreenRoot.uiVisible ? "on" : "off"
source: wallpaper
mainStack: mainStack
footer: footer
clock: clock
alwaysShowClock: config.alwaysShowClock && !config.hideClockWhenIdle
}
DropShadow {
id: clockShadow
anchors.fill: clock
source: clock
visible: !lockScreenUi.softwareRendering && config.alwaysShowClock
radius: 7
verticalOffset: 0.8
samples: 15
spread: 0.2
color : Qt.rgba(0, 0, 0, 0.7)
opacity: lockScreenRoot.uiVisible ? 0 : 1
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.veryLongDuration * 2
easing.type: Easing.InOutQuad
}
}
}
Clock {
id: clock
property Item shadow: clockShadow
visible: y > 0 && config.alwaysShowClock
anchors.horizontalCenter: parent.horizontalCenter
y: (mainBlock.userList.y + mainStack.y)/2 - height/2
Layout.alignment: Qt.AlignBaseline
}
ListModel {
id: users
Component.onCompleted: {
users.append({
name: kscreenlocker_userName,
realName: kscreenlocker_userName,
icon: kscreenlocker_userImage !== ""
? "file://" + kscreenlocker_userImage.split("/").map(encodeURIComponent).join("/")
: "",
})
}
}
StackView {
id: mainStack
anchors {
left: parent.left
right: parent.right
}
height: lockScreenRoot.height + Kirigami.Units.gridUnit * 3
focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it
// this isn't implicit, otherwise items still get processed for the scenegraph
visible: opacity > 0
initialItem: MainBlock {
id: mainBlock
lockScreenUiVisible: lockScreenRoot.uiVisible
showUserList: userList.y + mainStack.y > 0
enabled: !graceLockTimer.running
StackView.onStatusChanged: {
// prepare for presenting again to the user
if (StackView.status === StackView.Activating) {
mainPasswordBox.clear();
mainPasswordBox.focus = true;
root.notification = "";
}
}
userListModel: users
notificationMessage: {
const parts = [];
if (capsLockState.locked) {
parts.push(i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "Caps Lock is on"));
}
if (root.notification) {
parts.push(root.notification);
}
return parts.join(" • ");
}
onPasswordResult: password => {
authenticator.respond(password)
}
actionItems: [
ActionButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Slee&p")
icon.name: "system-suspend"
onClicked: sessionManagement.suspend()
visible: sessionManagement.canSuspend
},
ActionButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "&Hibernate")
icon.name: "system-suspend-hibernate"
onClicked: sessionManagement.hibernate()
visible: sessionManagement.canHibernate
},
ActionButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Switch &User")
icon.name: "system-switch-user"
onClicked: {
sessionManagement.switchUser();
}
visible: sessionManagement.canSwitchUser
}
]
Loader {
Layout.topMargin: Kirigami.Units.smallSpacing // some distance to the password field
Layout.fillWidth: true
Layout.preferredHeight: item ? item.implicitHeight : 0
active: config.showMediaControls
source: "MediaControls.qml"
}
}
}
VirtualKeyboardLoader {
id: inputPanel
z: 1
screenRoot: lockScreenRoot
mainStack: mainStack
mainBlock: mainBlock
passwordField: mainBlock.mainPasswordBox
}
Loader {
z: 2
active: root.viewVisible
source: "LockOsd.qml"
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: Kirigami.Units.gridUnit
}
}
// Note: Containment masks stretch clickable area of their buttons to
// the screen edges, essentially making them adhere to Fitts's law.
// Due to virtual keyboard button having an icon, buttons may have
// different heights, so fillHeight is required.
//
// Note for contributors: Keep this in sync with SDDM Main.qml footer.
RowLayout {
id: footer
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
margins: Kirigami.Units.smallSpacing
}
spacing: Kirigami.Units.smallSpacing
PlasmaComponents3.ToolButton {
id: virtualKeyboardButton
focusPolicy: Qt.TabFocus
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "Button to show/hide virtual keyboard", "Virtual Keyboard")
icon.name: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off"
onClicked: {
// Otherwise the password field loses focus and virtual keyboard
// keystrokes get eaten
mainBlock.mainPasswordBox.forceActiveFocus();
inputPanel.showHide()
}
visible: inputPanel.status === Loader.Ready
Layout.fillHeight: true
containmentMask: Item {
parent: virtualKeyboardButton
anchors.fill: parent
anchors.leftMargin: -footer.anchors.margins
anchors.bottomMargin: -footer.anchors.margins
}
}
PlasmaComponents3.ToolButton {
id: keyboardButton
focusPolicy: Qt.TabFocus
Accessible.description: i18ndc("plasma_shell_org.kde.plasma.desktop", "Button to change keyboard layout", "Switch layout")
icon.name: "input-keyboard"
PW.KeyboardLayoutSwitcher {
id: keyboardLayoutSwitcher
anchors.fill: parent
acceptedButtons: Qt.NoButton
}
text: keyboardLayoutSwitcher.layoutNames.longName
onClicked: keyboardLayoutSwitcher.keyboardLayout.switchToNextLayout()
visible: keyboardLayoutSwitcher.hasMultipleKeyboardLayouts
Layout.fillHeight: true
containmentMask: Item {
parent: keyboardButton
anchors.fill: parent
anchors.leftMargin: virtualKeyboardButton.visible ? 0 : -footer.anchors.margins
anchors.bottomMargin: -footer.anchors.margins
}
}
Item {
Layout.fillWidth: true
}
Battery {}
}
}
}
@@ -35,9 +35,20 @@ fi
run_tool apply >/dev/null
run_tool status >/dev/null
grep -q 'function onViewVisibleChanged()' "$target"
grep -q 'running: parent.uiVisible' "$target"
grep -q 'id: resumeAuthenticationTimer' "$target"
grep -q 'id: postGraceAuthenticationTimer' "$target"
grep -q 'interval: 1500' "$target"
grep -q 'interval: 6000' "$target"
grep -q 'running: root.viewVisible' "$target"
grep -q 'onTriggered: authenticator.startAuthenticating()' "$target"
if grep -q 'seenPositionChange\|lockScreenRoot.uiVisible = false' "$target"; then
grep -q 'function onResumingFromSuspend()' "$target"
[[ $(grep -c 'resumeAuthenticationTimer.restart()' "$target") -eq 1 ]]
grep -q 'cursorShape: Qt.ArrowCursor' "$target"
grep -q 'state: "on"' "$target"
grep -q 'opacity: 0' "$target"
grep -q 'lockScreenUiVisible: true' "$target"
grep -q 'lockScreenRoot.uiVisible = false' "$target"
if grep -q 'seenPositionChange\|running: parent.uiVisible\|repeat: true' "$target"; then
printf 'The patched fixture still contains prompt-hiding logic.\n' >&2
exit 1
fi