Publish Framework tray enhancements and idle fingerprint retry

This commit is contained in:
ajp_anton
2026-09-20 20:48:34 +00:00
parent 61ef199667
commit ddb912d4ba
25 changed files with 654 additions and 61 deletions
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
import QtQml
Timer {
id: retry
required property QtObject auth
required property int fingerprintType
property bool enabled: true
readonly property bool fingerprintActive: (auth.authenticatorTypes & fingerprintType) !== 0
property bool sawInfo: false
property bool sawError: false
interval: 6000
function reset() {
stop();
sawInfo = false;
sawError = false;
}
onEnabledChanged: {
if (!enabled) {
reset();
}
}
onFingerprintActiveChanged: {
if (fingerprintActive) {
reset();
} else if (enabled && sawInfo && !sawError) {
// An idle conversation ended. Consume the retry until another
// conversation actually supplies fingerprint instructions.
sawInfo = false;
restart();
}
}
onTriggered: {
if (enabled && !fingerprintActive) {
auth.startAuthenticating();
}
}
property Connections authenticationSignals: Connections {
target: retry.auth
function onNoninteractiveInfo(kind, source) {
if (retry.enabled && (kind & retry.fingerprintType)) {
retry.sawInfo = true;
}
}
function onNoninteractiveError(kind, source) {
if (kind & retry.fingerprintType) {
// Do not automatically replenish failed scan attempts.
retry.sawError = true;
retry.stop();
}
}
function onFailed(kind, source) {
if (kind === 0) {
retry.reset();
}
}
function onSucceeded() {
retry.reset();
}
}
}
+10 -4
View File
@@ -57,7 +57,7 @@
onUiVisibleChanged: {
if (uiVisible) {
Window.window.requestActivate();
@@ -135,6 +143,18 @@
@@ -135,6 +143,24 @@
}
authenticator.startAuthenticating();
}
@@ -72,11 +72,17 @@
+ interval: 6000
+ running: root.viewVisible
+ onTriggered: authenticator.startAuthenticating()
+ }
+ FedoraToolsFingerprintRetry {
+ auth: authenticator
+ fingerprintType: ScreenLocker.Authenticator.Fingerprint
+ enabled: root.viewVisible && !authenticator.unlocked
+ && !graceLockTimer.running && !resumeAuthenticationTimer.running
+ }
onBlockUIChanged: {
if (blockUI) {
fadeoutTimer.running = false;
@@ -194,6 +214,6 @@
@@ -194,6 +220,6 @@
WallpaperFader {
anchors.fill: parent
- state: lockScreenRoot.uiVisible ? "on" : "off"
@@ -84,7 +90,7 @@
source: wallpaper
mainStack: mainStack
footer: footer
@@ -211,7 +231,7 @@
@@ -211,7 +237,7 @@
samples: 15
spread: 0.2
color : Qt.rgba(0, 0, 0, 0.7)
@@ -93,6 +99,6 @@
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.veryLongDuration * 2
@@ -265 +285 @@
@@ -265 +291 @@
- lockScreenUiVisible: lockScreenRoot.uiVisible
+ lockScreenUiVisible: true
@@ -1,6 +1,6 @@
Name: plasma-always-show-unlock
Version: 0.1.0
Release: 10%{?dist}
Release: 11%{?dist}
Summary: Immediately show the Plasma unlock prompt without requiring input
License: MIT AND GPL-2.0-or-later
@@ -11,10 +11,13 @@ Source2: test-plasma-always-show-unlock
Source3: LockScreenUi.qml
Source4: LICENSE
Source5: README.md
Source6: FedoraToolsFingerprintRetry.qml
Source7: tst_fingerprintretry.qml
BuildArch: noarch
BuildRequires: bash
BuildRequires: patch
BuildRequires: qt6-qtdeclarative-devel
Requires: bash
Requires: patch
Requires: plasma-desktop >= 6.7.4
@@ -31,6 +34,11 @@ patch after plasma-desktop updates and leaves unknown future layouts unchanged.
%check
bash %{SOURCE2} %{SOURCE0} %{SOURCE1} %{SOURCE3}
mkdir -p qml-tests/tests
cp %{SOURCE6} qml-tests/
cp %{SOURCE7} qml-tests/tests/
QT_QPA_PLATFORM=offscreen QT_QUICK_BACKEND=software \
qmltestrunner-qt6 -input qml-tests/tests
%install
install -Dpm 0755 %{SOURCE0} \
@@ -41,6 +49,15 @@ install -Dpm 0644 %{SOURCE4} \
%{buildroot}%{_licensedir}/%{name}/LICENSE
install -Dpm 0644 %{SOURCE5} \
%{buildroot}%{_docdir}/%{name}/README.md
install -Dpm 0644 %{SOURCE6} \
%{buildroot}%{_datadir}/plasma/shells/org.kde.plasma.desktop/contents/lockscreen/FedoraToolsFingerprintRetry.qml
%pre
# Revert using the installed helper and patch, before RPM replaces either.
# The new patch must be applied to the original QML, not an older patched copy.
if [ -x %{_libexecdir}/plasma-always-show-unlock ]; then
%{_libexecdir}/plasma-always-show-unlock revert || exit 1
fi
%preun
if [ "$1" -eq 0 ]; then
@@ -55,8 +72,14 @@ fi
%doc %{_docdir}/%{name}/README.md
%{_libexecdir}/plasma-always-show-unlock
%{_datadir}/plasma-always-show-unlock/LockScreenUi.patch
%{_datadir}/plasma/shells/org.kde.plasma.desktop/contents/lockscreen/FedoraToolsFingerprintRetry.qml
%changelog
* Mon Sep 14 2026 fedora-tools contributors - 0.1.0-11
- Retry idle fingerprint conversations without polling or restarting fprintd
- Preserve failed-scan handling and test timeout recovery in QML
- Revert the installed patch before upgrading to a new revision
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-10
- Clarify the package summary
@@ -37,6 +37,8 @@ run_tool status >/dev/null
grep -q 'function onViewVisibleChanged()' "$target"
grep -q 'id: resumeAuthenticationTimer' "$target"
grep -q 'id: postGraceAuthenticationTimer' "$target"
grep -q 'FedoraToolsFingerprintRetry {' "$target"
grep -q 'fingerprintType: ScreenLocker.Authenticator.Fingerprint' "$target"
grep -q 'interval: 1500' "$target"
grep -q 'interval: 6000' "$target"
grep -q 'running: root.viewVisible' "$target"
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: MIT
import QtQml
import QtTest
import ".."
TestCase {
id: testCase
name: "FingerprintRetry"
property alias authenticator: auth
QtObject {
id: auth
property int authenticatorTypes: 0
property int starts: 0
signal noninteractiveInfo(int kind, QtObject source)
signal noninteractiveError(int kind, QtObject source)
signal failed(int kind, QtObject source)
signal succeeded()
function startAuthenticating() { ++starts; }
}
FedoraToolsFingerprintRetry {
id: retry
auth: testCase.authenticator
fingerprintType: 1
}
function initTestCase() {
compare(retry.interval, 6000);
compare(retry.repeat, false);
}
function init() {
retry.enabled = false;
auth.authenticatorTypes = 0;
auth.starts = 0;
retry.interval = 20;
retry.enabled = true;
}
function beginFingerprint() {
auth.authenticatorTypes = 1;
auth.noninteractiveInfo(1, auth);
}
function test_idleTimeoutRetriesWithoutInput() {
beginFingerprint();
wait(50);
compare(auth.starts, 0); // Never restart a working conversation.
auth.authenticatorTypes = 0;
verify(retry.running);
tryCompare(auth, "starts", 1);
wait(60);
compare(auth.starts, 1); // No polling while the reader stays unavailable.
beginFingerprint();
auth.authenticatorTypes = 0;
tryCompare(auth, "starts", 2); // Later idle timeouts can also recover.
}
function test_noReaderOrNoInstructions() {
wait(60);
compare(auth.starts, 0);
auth.authenticatorTypes = 1;
auth.authenticatorTypes = 0;
wait(60);
compare(auth.starts, 0);
}
function test_failedScansDoNotGetAutomaticNewAttempts() {
beginFingerprint();
auth.noninteractiveError(1, auth);
auth.noninteractiveInfo(1, auth); // Another instruction or timeout message.
auth.failed(1, auth);
auth.authenticatorTypes = 0;
wait(60);
compare(auth.starts, 0);
}
function test_unavailableRetryDoesNotLoop() {
beginFingerprint();
auth.authenticatorTypes = 0;
tryCompare(auth, "starts", 1);
auth.authenticatorTypes = 1; // PAM marks active before opening the reader.
auth.failed(1, auth);
auth.authenticatorTypes = 0;
wait(60);
compare(auth.starts, 1);
}
function test_smartcardDoesNotArmOrCancelFingerprintRetry() {
auth.authenticatorTypes = 2;
auth.noninteractiveInfo(2, auth);
auth.authenticatorTypes = 0;
wait(60);
compare(auth.starts, 0);
beginFingerprint();
auth.noninteractiveError(2, auth);
auth.authenticatorTypes = 2;
tryCompare(auth, "starts", 1);
}
function test_cancelPendingRetry_data() {
return [ {tag: "manual restart"}, {tag: "success"},
{tag: "password failure"}, {tag: "hidden or suspended"} ];
}
function test_cancelPendingRetry(data) {
beginFingerprint();
auth.authenticatorTypes = 0;
verify(retry.running);
switch (data.tag) {
case "manual restart": auth.authenticatorTypes = 1; break;
case "success": auth.succeeded(); break;
case "password failure": auth.failed(0, auth); break;
case "hidden or suspended": retry.enabled = false; break;
}
wait(60);
compare(auth.starts, 0);
}
function test_passwordFailureBeforeAvailabilityChange() {
beginFingerprint();
auth.failed(0, auth);
auth.authenticatorTypes = 0;
wait(60);
compare(auth.starts, 0);
}
}