Refine Plasma lock screen authentication retries
This commit is contained in:
@@ -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
|
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
|
it after `plasma-desktop` updates. An unknown future layout is left unchanged
|
||||||
instead of being modified speculatively. Removing the package restores Plasma's
|
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
|
### Plasma task-group shortcuts
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
--- a/contents/lockscreen/LockScreenUi.qml
|
--- a/contents/lockscreen/LockScreenUi.qml
|
||||||
+++ b/contents/lockscreen/LockScreenUi.qml
|
+++ b/contents/lockscreen/LockScreenUi.qml
|
||||||
@@ -83,6 +83,15 @@
|
@@ -83,20 +83,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,7 +16,24 @@
|
|||||||
SessionManagement {
|
SessionManagement {
|
||||||
id: 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
|
id: lockScreenRoot
|
||||||
|
|
||||||
property bool uiVisible: false
|
property bool uiVisible: false
|
||||||
@@ -24,8 +41,12 @@
|
|||||||
property bool blockUI: containsMouse && (mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive)
|
property bool blockUI: containsMouse && (mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive)
|
||||||
|
|
||||||
x: parent.x
|
x: parent.x
|
||||||
@@ -119,10 +127,7 @@
|
@@ -118,13 +129,10 @@
|
||||||
cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
hoverEnabled: true
|
||||||
|
- cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor
|
||||||
|
+ cursorShape: Qt.ArrowCursor
|
||||||
drag.filterChildren: true
|
drag.filterChildren: true
|
||||||
onPressed: uiVisible = true;
|
onPressed: uiVisible = true;
|
||||||
- onPositionChanged: {
|
- onPositionChanged: {
|
||||||
@@ -36,49 +57,42 @@
|
|||||||
onUiVisibleChanged: {
|
onUiVisibleChanged: {
|
||||||
if (uiVisible) {
|
if (uiVisible) {
|
||||||
Window.window.requestActivate();
|
Window.window.requestActivate();
|
||||||
@@ -135,6 +140,13 @@
|
@@ -135,6 +143,18 @@
|
||||||
}
|
}
|
||||||
authenticator.startAuthenticating();
|
authenticator.startAuthenticating();
|
||||||
}
|
}
|
||||||
+ Timer {
|
+ Timer {
|
||||||
+ // Keep the authentication backend active while the prompt is visible.
|
+ id: resumeAuthenticationTimer
|
||||||
+ interval: 1000
|
+ interval: 1500
|
||||||
+ running: parent.uiVisible
|
+ onTriggered: authenticator.startAuthenticating()
|
||||||
+ repeat: true
|
+ }
|
||||||
|
+ Timer {
|
||||||
|
+ id: postGraceAuthenticationTimer
|
||||||
|
+ // Retry once after Plasma's default five-second lock grace period.
|
||||||
|
+ interval: 6000
|
||||||
|
+ running: root.viewVisible
|
||||||
+ onTriggered: authenticator.startAuthenticating()
|
+ onTriggered: authenticator.startAuthenticating()
|
||||||
+ }
|
+ }
|
||||||
onBlockUIChanged: {
|
onBlockUIChanged: {
|
||||||
if (blockUI) {
|
if (blockUI) {
|
||||||
fadeoutTimer.running = false;
|
fadeoutTimer.running = false;
|
||||||
@@ -143,19 +155,12 @@
|
@@ -194,6 +214,6 @@
|
||||||
fadeoutTimer.restart();
|
WallpaperFader {
|
||||||
}
|
anchors.fill: parent
|
||||||
}
|
- state: lockScreenRoot.uiVisible ? "on" : "off"
|
||||||
- onExited: {
|
+ state: "on"
|
||||||
- uiVisible = false;
|
source: wallpaper
|
||||||
- }
|
mainStack: mainStack
|
||||||
Keys.onEscapePressed: {
|
footer: footer
|
||||||
- // If the escape key is pressed, kscreenlocker will turn off the screen.
|
@@ -211,7 +231,7 @@
|
||||||
- // We do not want to show the password prompt in this case.
|
samples: 15
|
||||||
- if (uiVisible) {
|
spread: 0.2
|
||||||
- uiVisible = false;
|
color : Qt.rgba(0, 0, 0, 0.7)
|
||||||
- if (inputPanel.keyboardActive) {
|
- opacity: lockScreenRoot.uiVisible ? 0 : 1
|
||||||
- inputPanel.showHide();
|
+ opacity: 0
|
||||||
- }
|
Behavior on opacity {
|
||||||
- root.clearPassword();
|
OpacityAnimator {
|
||||||
+ // KScreenLocker will turn off the screen; leave the prompt ready for wake-up.
|
duration: Kirigami.Units.veryLongDuration * 2
|
||||||
+ if (inputPanel.keyboardActive) {
|
@@ -265 +285 @@
|
||||||
+ inputPanel.showHide();
|
- lockScreenUiVisible: lockScreenRoot.uiVisible
|
||||||
}
|
+ lockScreenUiVisible: true
|
||||||
+ root.clearPassword();
|
|
||||||
}
|
|
||||||
Keys.onPressed: event => {
|
|
||||||
uiVisible = true;
|
|
||||||
@@ -167,7 +172,6 @@
|
|
||||||
onTriggered: {
|
|
||||||
if (!lockScreenRoot.blockUI) {
|
|
||||||
mainBlock.mainPasswordBox.showPassword = false;
|
|
||||||
- lockScreenRoot.uiVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Name: plasma-always-show-unlock
|
Name: plasma-always-show-unlock
|
||||||
Version: 0.1.0
|
Version: 0.1.0
|
||||||
Release: 1%{?dist}
|
Release: 8%{?dist}
|
||||||
Summary: Always show the Plasma lock-screen unlock prompt
|
Summary: Always show the Plasma lock-screen unlock prompt
|
||||||
|
|
||||||
License: MIT AND GPL-2.0-or-later
|
License: MIT AND GPL-2.0-or-later
|
||||||
@@ -56,5 +56,9 @@ fi
|
|||||||
%{_datadir}/plasma-always-show-unlock/LockScreenUi.patch
|
%{_datadir}/plasma-always-show-unlock/LockScreenUi.patch
|
||||||
|
|
||||||
%changelog
|
%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
|
* Wed Sep 02 2026 fedora-tools contributors - 0.1.0-1
|
||||||
- Initial package
|
- Initial package
|
||||||
|
|||||||
@@ -176,3 +176,252 @@ Item {
|
|||||||
interval: 3000
|
interval: 3000
|
||||||
onTriggered: root.notification = ""
|
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 apply >/dev/null
|
||||||
run_tool status >/dev/null
|
run_tool status >/dev/null
|
||||||
grep -q 'function onViewVisibleChanged()' "$target"
|
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"
|
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
|
printf 'The patched fixture still contains prompt-hiding logic.\n' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user