Add always-visible Plasma unlock prompt

This commit is contained in:
ajp_anton
2026-09-02 00:28:07 +00:00
parent 70b4a6e780
commit 4a78e6b485
7 changed files with 518 additions and 0 deletions
@@ -0,0 +1,178 @@
/*
SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import org.kde.plasma.components as PlasmaComponents3
import org.kde.plasma.workspace.components as PW
import org.kde.plasma.private.keyboardindicator as KeyboardIndicator
import org.kde.kirigami as Kirigami
import org.kde.kscreenlocker as ScreenLocker
import org.kde.plasma.private.sessions
import org.kde.breeze.components
Item {
id: lockScreenUi
// If we're using software rendering, draw outlines instead of shadows
// See https://bugs.kde.org/show_bug.cgi?id=398317
readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software
function handleMessage(msg) {
if (!root.notification) {
root.notification += msg;
} else if (root.notification.includes(msg)) {
root.notificationRepeated();
} else {
root.notification += "\n" + msg
}
}
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Connections {
target: authenticator
function onFailed(kind) {
if (kind != 0) { // if this is coming from the noninteractive authenticators
return;
}
const msg = i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "Unlocking failed");
lockScreenUi.handleMessage(msg);
graceLockTimer.restart();
notificationRemoveTimer.restart();
rejectPasswordAnimation.start();
}
function onSucceeded() {
if (authenticator.hadPrompt) {
Qt.quit();
} else {
mainStack.replace(null, Qt.resolvedUrl("NoPasswordUnlock.qml"),
{
userListModel: users
},
StackView.Immediate,
);
mainStack.forceActiveFocus();
}
}
function onInfoMessageChanged() {
lockScreenUi.handleMessage(authenticator.infoMessage);
}
function onErrorMessageChanged() {
lockScreenUi.handleMessage(authenticator.errorMessage);
}
function onPromptChanged(msg) {
lockScreenUi.handleMessage(authenticator.prompt);
}
function onPromptForSecretChanged(msg) {
mainBlock.showPassword = false;
mainBlock.mainPasswordBox.forceActiveFocus();
}
}
SessionManagement {
id: sessionManagement
}
KeyboardIndicator.KeyState {
id: capsLockState
key: Qt.Key_CapsLock
}
Connections {
target: sessionManagement
function onAboutToSuspend() {
root.clearPassword();
}
}
RejectPasswordAnimation {
id: rejectPasswordAnimation
target: mainBlock
}
MouseArea {
id: lockScreenRoot
property bool uiVisible: false
property bool seenPositionChange: false
property bool blockUI: containsMouse && (mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive)
x: parent.x
y: parent.y
width: parent.width
height: parent.height
hoverEnabled: true
cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor
drag.filterChildren: true
onPressed: uiVisible = true;
onPositionChanged: {
uiVisible = seenPositionChange;
seenPositionChange = true;
}
onUiVisibleChanged: {
if (uiVisible) {
Window.window.requestActivate();
}
if (blockUI) {
fadeoutTimer.running = false;
} else if (uiVisible) {
fadeoutTimer.restart();
}
authenticator.startAuthenticating();
}
onBlockUIChanged: {
if (blockUI) {
fadeoutTimer.running = false;
uiVisible = true;
} else {
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();
}
}
Keys.onPressed: event => {
uiVisible = true;
event.accepted = false;
}
Timer {
id: fadeoutTimer
interval: 10000
onTriggered: {
if (!lockScreenRoot.blockUI) {
mainBlock.mainPasswordBox.showPassword = false;
lockScreenRoot.uiVisible = false;
}
}
}
Timer {
id: notificationRemoveTimer
interval: 3000
onTriggered: root.notification = ""
}
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
set -euo pipefail
if (( $# != 3 )); then
printf 'Usage: %s SCRIPT PATCH FIXTURE\n' "${0##*/}" >&2
exit 2
fi
script=$1
patch_file=$2
fixture=$3
tmpdir=$(mktemp -d)
trap 'rm -rf -- "$tmpdir"' EXIT
root="$tmpdir/root"
target_dir="$root/usr/share/plasma/shells/org.kde.plasma.desktop/contents/lockscreen"
target="$target_dir/LockScreenUi.qml"
mkdir -p "$target_dir"
cp "$fixture" "$target"
run_tool() {
PLASMA_ALWAYS_SHOW_UNLOCK_ROOT="$root" \
PLASMA_ALWAYS_SHOW_UNLOCK_PATCH="$patch_file" \
bash "$script" "$@"
}
if run_tool status >/dev/null 2>&1; then
printf 'Expected an unpatched fixture.\n' >&2
exit 1
fi
run_tool apply >/dev/null
run_tool status >/dev/null
grep -q 'function onViewVisibleChanged()' "$target"
grep -q 'running: parent.uiVisible' "$target"
grep -q 'onTriggered: authenticator.startAuthenticating()' "$target"
if grep -q 'seenPositionChange\|lockScreenRoot.uiVisible = false' "$target"; then
printf 'The patched fixture still contains prompt-hiding logic.\n' >&2
exit 1
fi
patched_checksum=$(sha256sum "$target")
run_tool apply >/dev/null
[[ $(sha256sum "$target") == "$patched_checksum" ]]
run_tool revert >/dev/null
cmp "$fixture" "$target"
sed -i 's/property bool seenPositionChange: false/property bool unexpectedState: false/' "$target"
unsupported_checksum=$(sha256sum "$target")
if run_tool apply >/dev/null 2>&1; then
printf 'Expected an unsupported modified fixture to be rejected.\n' >&2
exit 1
fi
[[ $(sha256sum "$target") == "$unsupported_checksum" ]]
printf 'plasma-always-show-unlock tests passed\n'