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
+12
View File
@@ -15,6 +15,7 @@ sudo dnf config-manager addrepo \
```bash ```bash
sudo dnf install touchpad-hold-tap sudo dnf install touchpad-hold-tap
sudo dnf install plasma-always-show-unlock
``` ```
## Compatibility ## Compatibility
@@ -36,3 +37,14 @@ including double middle clicks.
The `touchpad-hold-tap` package requires libinput 1.30 or newer and a compositor The `touchpad-hold-tap` package requires libinput 1.30 or newer and a compositor
that enables libinput plugins. Log out and back in after installation or an that enables libinput plugins. Log out and back in after installation or an
update so the compositor reloads the plugin. update so the compositor reloads the plugin.
### Always-visible Plasma unlock prompt
The `plasma-always-show-unlock` package keeps Plasma's password and fingerprint
prompt visible from the moment the lock screen appears, including after the
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.
@@ -0,0 +1,84 @@
--- a/contents/lockscreen/LockScreenUi.qml
+++ b/contents/lockscreen/LockScreenUi.qml
@@ -83,6 +83,15 @@
}
}
+ Connections {
+ target: root
+ function onViewVisibleChanged() {
+ if (root.viewVisible) {
+ lockScreenRoot.uiVisible = true;
+ }
+ }
+ }
+
SessionManagement {
id: sessionManagement
}
@@ -108,7 +117,6 @@
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
@@ -119,10 +127,7 @@
cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor
drag.filterChildren: true
onPressed: uiVisible = true;
- onPositionChanged: {
- uiVisible = seenPositionChange;
- seenPositionChange = true;
- }
+ onPositionChanged: uiVisible = true;
onUiVisibleChanged: {
if (uiVisible) {
Window.window.requestActivate();
@@ -135,6 +140,13 @@
}
authenticator.startAuthenticating();
}
+ Timer {
+ // Keep the authentication backend active while the prompt is visible.
+ interval: 1000
+ running: parent.uiVisible
+ repeat: true
+ 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;
}
}
}
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
set -euo pipefail
root=${PLASMA_ALWAYS_SHOW_UNLOCK_ROOT:-/}
patch_file=${PLASMA_ALWAYS_SHOW_UNLOCK_PATCH:-/usr/share/plasma-always-show-unlock/LockScreenUi.patch}
target_dir="$root/usr/share/plasma/shells/org.kde.plasma.desktop"
target="$target_dir/contents/lockscreen/LockScreenUi.qml"
usage() {
printf 'Usage: %s {apply|revert|status}\n' "${0##*/}" >&2
}
patch_options=(
--silent
--force
--fuzz=0
--no-backup-if-mismatch
-p1
-d "$target_dir"
)
run_patch() {
patch "${patch_options[@]}" "$@" < "$patch_file"
}
check_patch() {
run_patch --dry-run "$@" >/dev/null 2>&1
}
apply_patch() {
if check_patch --reverse; then
printf 'Plasma unlock prompt patch is already applied.\n'
return
fi
if ! check_patch; then
printf 'Error: %s is not a supported unmodified Plasma lock screen.\n' "$target" >&2
return 1
fi
run_patch
printf 'Applied the Plasma unlock prompt patch.\n'
}
revert_patch() {
if check_patch; then
printf 'Plasma unlock prompt patch is not applied.\n'
return
fi
if ! check_patch --reverse; then
printf 'Error: %s is neither the supported original nor patched file.\n' "$target" >&2
return 1
fi
run_patch --reverse
printf 'Reverted the Plasma unlock prompt patch.\n'
}
if (( $# != 1 )); then
usage
exit 2
fi
if [[ ! -f $target ]]; then
printf 'Error: Plasma lock screen not found at %s.\n' "$target" >&2
exit 1
fi
if [[ ! -r $patch_file ]]; then
printf 'Error: patch file not found at %s.\n' "$patch_file" >&2
exit 1
fi
case $1 in
apply)
apply_patch
;;
revert)
revert_patch
;;
status)
if check_patch --reverse; then
printf 'Plasma unlock prompt patch is applied.\n'
elif check_patch; then
printf 'Plasma unlock prompt patch is not applied.\n'
exit 1
else
printf 'Plasma lock screen is not supported by this patch.\n' >&2
exit 2
fi
;;
*)
usage
exit 2
;;
esac
@@ -0,0 +1,60 @@
Name: plasma-always-show-unlock
Version: 0.1.0
Release: 1%{?dist}
Summary: Always show the Plasma lock-screen unlock prompt
License: MIT AND GPL-2.0-or-later
URL: https://git.ajpanton.se/ajp_anton/fedora-tools
Source0: plasma-always-show-unlock
Source1: LockScreenUi.patch
Source2: test-plasma-always-show-unlock
Source3: LockScreenUi.qml
Source4: LICENSE
Source5: README.md
BuildArch: noarch
BuildRequires: bash
BuildRequires: patch
Requires: bash
Requires: patch
Requires: plasma-desktop >= 6.7.4
%description
Keeps KDE Plasma's unlock prompt visible when the screen is locked, after the
display is turned off, and after suspend. The package reapplies its narrow QML
patch after plasma-desktop updates and leaves unknown future layouts unchanged.
%prep
%build
%check
bash %{SOURCE2} %{SOURCE0} %{SOURCE1} %{SOURCE3}
%install
install -Dpm 0755 %{SOURCE0} \
%{buildroot}%{_libexecdir}/plasma-always-show-unlock
install -Dpm 0644 %{SOURCE1} \
%{buildroot}%{_datadir}/plasma-always-show-unlock/LockScreenUi.patch
install -Dpm 0644 %{SOURCE4} \
%{buildroot}%{_licensedir}/%{name}/LICENSE
install -Dpm 0644 %{SOURCE5} \
%{buildroot}%{_docdir}/%{name}/README.md
%preun
if [ "$1" -eq 0 ]; then
%{_libexecdir}/plasma-always-show-unlock revert || :
fi
%triggerin -- plasma-desktop
%{_libexecdir}/plasma-always-show-unlock apply || :
%files
%license %{_licensedir}/%{name}/LICENSE
%doc %{_docdir}/%{name}/README.md
%{_libexecdir}/plasma-always-show-unlock
%{_datadir}/plasma-always-show-unlock/LockScreenUi.patch
%changelog
* Wed Sep 02 2026 fedora-tools contributors - 0.1.0-1
- Initial package
@@ -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'
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
tool_dir="$repo_root/plasma-always-show-unlock"
topdir="$repo_root/rpmbuild"
mkdir -p "$topdir"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
install -m 0755 \
"$tool_dir/plasma-always-show-unlock" \
"$topdir/SOURCES/plasma-always-show-unlock"
install -m 0644 \
"$tool_dir/LockScreenUi.patch" \
"$topdir/SOURCES/LockScreenUi.patch"
install -m 0755 \
"$tool_dir/tests/test-plasma-always-show-unlock" \
"$topdir/SOURCES/test-plasma-always-show-unlock"
install -m 0644 \
"$tool_dir/tests/LockScreenUi.qml" \
"$topdir/SOURCES/LockScreenUi.qml"
install -m 0644 "$repo_root/LICENSE" "$topdir/SOURCES/LICENSE"
install -m 0644 "$repo_root/README.md" "$topdir/SOURCES/README.md"
rpmbuild \
--define "_topdir $topdir" \
-bb "$tool_dir/plasma-always-show-unlock.spec"