From bf996d32a0f88f278d18dfe3a900576fd3f8fc7e Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Mon, 27 Oct 2025 18:30:13 +0100 Subject: [PATCH 01/11] Adding howdy support --- assets/pam.d/howdy | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 assets/pam.d/howdy diff --git a/assets/pam.d/howdy b/assets/pam.d/howdy new file mode 100644 index 000000000..d4814e946 --- /dev/null +++ b/assets/pam.d/howdy @@ -0,0 +1,3 @@ +#%PAM-1.0 + +auth required pam_fprintd.so max-tries=1 From 1920073b0f6ed48d6f064d59dc6ce5175ab5098c Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Mon, 27 Oct 2025 19:56:30 +0100 Subject: [PATCH 02/11] Adding howdy lock support --- assets/pam.d/howdy | 2 +- config/LockConfig.qml | 2 + modules/lock/Center.qml | 41 ++++++++++++++++++-- modules/lock/Pam.qml | 85 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 5 deletions(-) diff --git a/assets/pam.d/howdy b/assets/pam.d/howdy index d4814e946..a73517fa4 100644 --- a/assets/pam.d/howdy +++ b/assets/pam.d/howdy @@ -1,3 +1,3 @@ #%PAM-1.0 -auth required pam_fprintd.so max-tries=1 +auth required pam_howdy.so max-tries=1 diff --git a/config/LockConfig.qml b/config/LockConfig.qml index 2af4e2cd5..9493d61d3 100644 --- a/config/LockConfig.qml +++ b/config/LockConfig.qml @@ -4,6 +4,8 @@ JsonObject { property bool recolourLogo: false property bool enableFprint: true property int maxFprintTries: 3 + property bool enableHowdy: true + property int maxHowdyTries: 2 property Sizes sizes: Sizes {} component Sizes: JsonObject { diff --git a/modules/lock/Center.qml b/modules/lock/Center.qml index 748504b7e..aebf8e84a 100644 --- a/modules/lock/Center.qml +++ b/modules/lock/Center.qml @@ -152,21 +152,27 @@ ColumnLayout { Item { implicitWidth: implicitHeight - implicitHeight: fprintIcon.implicitHeight + Appearance.padding.small * 2 + implicitHeight: authIcon.implicitHeight + Appearance.padding.small * 2 MaterialIcon { - id: fprintIcon + id: authIcon anchors.centerIn: parent animate: true text: { + if (root.lock.pam.howdy.tries >= Config.lock.maxHowdyTries) + return "visibility_off"; if (root.lock.pam.fprint.tries >= Config.lock.maxFprintTries) return "fingerprint_off"; + + if (root.lock.pam.howdy.active) + return "visibility"; if (root.lock.pam.fprint.active) return "fingerprint"; + return "lock"; } - color: root.lock.pam.fprint.tries >= Config.lock.maxFprintTries ? Colours.palette.m3error : Colours.palette.m3onSurface + color: (root.lock.pam.howdy.tries >= Config.lock.maxHowdyTries || root.lock.pam.fprint.tries >= Config.lock.maxFprintTries) ? Colours.palette.m3error : Colours.palette.m3onSurface opacity: root.lock.pam.passwd.active ? 0 : 1 Behavior on opacity { @@ -291,6 +297,8 @@ ColumnLayout { readonly property Pam pam: root.lock.pam readonly property string msg: { + if (pam.howdyState === "error") + return qsTr("Howdy ERROR: %1").arg(pam.howdy.message); if (pam.fprintState === "error") return qsTr("FP ERROR: %1").arg(pam.fprint.message); if (pam.state === "error") @@ -299,23 +307,48 @@ ColumnLayout { if (pam.lockMessage) return pam.lockMessage; + if (pam.state === "max" && pam.fprintState === "max" && pam.howdyState === "max") + return qsTr("Maximum password, fingerprint, and face attempts reached."); if (pam.state === "max" && pam.fprintState === "max") return qsTr("Maximum password and fingerprint attempts reached."); + if (pam.state === "max" && pam.howdyState === "max") + return qsTr("Maximum password and face attempts reached."); + if (pam.fprintState === "max" && pam.howdyState === "max") + return qsTr("Maximum fingerprint and face attempts reached."); + if (pam.state === "max") { + if (pam.fprint.available && pam.howdy.available) + return qsTr("Maximum password attempts reached. Please use fingerprint or face."); if (pam.fprint.available) return qsTr("Maximum password attempts reached. Please use fingerprint."); + if (pam.howdy.available) + return qsTr("Maximum password attempts reached. Please use face."); return qsTr("Maximum password attempts reached."); } - if (pam.fprintState === "max") + if (pam.fprintState === "max") { + if (pam.howdy.available) + return qsTr("Maximum fingerprint attempts reached. Please use face or password."); return qsTr("Maximum fingerprint attempts reached. Please use password."); + } + if (pam.howdyState === "max") { + if (pam.fprint.available) + return qsTr("Maximum face attempts reached. Please use fingerprint or password."); + return qsTr("Maximum face attempts reached. Please use password."); + } if (pam.state === "fail") { + if (pam.fprint.available && pam.howdy.available) + return qsTr("Incorrect password. Please try again or use fingerprint/face."); if (pam.fprint.available) return qsTr("Incorrect password. Please try again or use fingerprint."); + if (pam.howdy.available) + return qsTr("Incorrect password. Please try again or use face."); return qsTr("Incorrect password. Please try again."); } if (pam.fprintState === "fail") return qsTr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(Config.lock.maxFprintTries); + if (pam.howdyState === "fail") + return qsTr("Face not recognized (%1/%2). Please try again or use password.").arg(pam.howdy.tries).arg(Config.lock.maxHowdyTries); return ""; } diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index 0186c2f84..dfa5b2d5b 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -12,9 +12,11 @@ Scope { readonly property alias passwd: passwd readonly property alias fprint: fprint + readonly property alias howdy: howdy property string lockMessage property string state property string fprintState + property string howdyState property string buffer signal flashMsg @@ -128,6 +130,56 @@ Scope { } } + PamContext { + id: howdy + + property bool available + property int tries + property int errorTries + + function checkAvail(): void { + if (!available || !Config.lock.enableHowdy || !root.lock.secure) { + abort(); + return; + } + + tries = 0; + errorTries = 0; + start(); + } + + config: "howdy" + configDirectory: Quickshell.shellDir + "/assets/pam.d" + + onCompleted: res => { + if (!available) + return; + + if (res === PamResult.Success) + return root.lock.unlock(); + + if (res === PamResult.Error) { + root.howdyState = "error"; + errorTries++; + if (errorTries < 5) { + abort(); + howdyErrorRetry.restart(); + } + } else if (res === PamResult.MaxTries || res === PamResult.Failed) { + tries++; + if (tries < Config.lock.maxHowdyTries) { + root.howdyState = "fail"; + start(); + } else { + root.howdyState = "max"; + abort(); + } + } + + root.flashMsg(); + howdyStateReset.restart(); + } + } Process { id: availProc @@ -137,6 +189,15 @@ Scope { fprint.checkAvail(); } } + Process { + id: howdyAvailProc + + command: ["sh", "-c", "command -v howdy"] + onExited: code => { + howdy.available = code === 0; + howdy.checkAvail(); + } + } Timer { id: errorRetry @@ -165,21 +226,41 @@ Scope { } } + Timer { + id: howdyErrorRetry + + interval: 800 + onTriggered: howdy.start() + } + + Timer { + id: howdyStateReset + + interval: 4000 + onTriggered: { + root.howdyState = ""; + howdy.errorTries = 0; + } + } + Connections { target: root.lock function onSecureChanged(): void { if (root.lock.secure) { availProc.running = true; + howdyAvailProc.running = true; root.buffer = ""; root.state = ""; root.fprintState = ""; + root.howdyState = ""; root.lockMessage = ""; } } function onUnlock(): void { fprint.abort(); + howdy.abort(); } } @@ -189,5 +270,9 @@ Scope { function onEnableFprintChanged(): void { fprint.checkAvail(); } + + function onEnableHowdyChanged(): void { + howdy.checkAvail(); + } } } From 031ffb622a0c2b481d80e2bb8a35315d64aaa286 Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Mon, 27 Oct 2025 22:21:31 +0100 Subject: [PATCH 03/11] Fixing howdy for nixos --- assets/pam.d/howdy | 1 - modules/lock/Pam.qml | 15 +++++++++++---- nix/default.nix | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/assets/pam.d/howdy b/assets/pam.d/howdy index a73517fa4..0f1302ccc 100644 --- a/assets/pam.d/howdy +++ b/assets/pam.d/howdy @@ -1,3 +1,2 @@ #%PAM-1.0 - auth required pam_howdy.so max-tries=1 diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index dfa5b2d5b..2103a23c1 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -22,11 +22,18 @@ Scope { signal flashMsg function handleKey(event: KeyEvent): void { - if (passwd.active || state === "max") + if (passwd.active || state === "max" || howdy.active) return; - if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { - passwd.start(); + if (buffer.length > 0) { + // If buffer has text, start password check + passwd.start(); + } else { + // Buffer is empty, try howdy + if (howdy.available) { + howdy.start(); + } + } } else if (event.key === Qt.Key_Backspace) { if (event.modifiers & Qt.ControlModifier) { buffer = ""; @@ -145,7 +152,7 @@ Scope { tries = 0; errorTries = 0; - start(); + // start(); // Start only when press Enter } config: "howdy" diff --git a/nix/default.nix b/nix/default.nix index bd246a6a6..83bb112bb 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -126,6 +126,8 @@ in prePatch = '' substituteInPlace assets/pam.d/fprint \ --replace-fail pam_fprintd.so /run/current-system/sw/lib/security/pam_fprintd.so + substituteInPlace assets/pam.d/howdy \ + --replace-fail pam_howdy.so /run/current-system/sw/lib/security/pam_howdy.so substituteInPlace shell.qml \ --replace-fail 'ShellRoot {' 'ShellRoot { settings.watchFiles: false' ''; From 476861fbbed53b51af8044dc13acb7bdfc0b5c6a Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Mon, 27 Oct 2025 22:31:34 +0100 Subject: [PATCH 04/11] Added howdy support --- config/LockConfig.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/LockConfig.qml b/config/LockConfig.qml index 9493d61d3..fdb1c07fc 100644 --- a/config/LockConfig.qml +++ b/config/LockConfig.qml @@ -5,7 +5,7 @@ JsonObject { property bool enableFprint: true property int maxFprintTries: 3 property bool enableHowdy: true - property int maxHowdyTries: 2 + property int maxHowdyTries: 3 property Sizes sizes: Sizes {} component Sizes: JsonObject { From f8a392710ccf4805f3c31a5e35132392c6db5e00 Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Tue, 28 Oct 2025 16:34:37 +0100 Subject: [PATCH 05/11] Fix fprint crash when using pass --- modules/lock/Pam.qml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index 2103a23c1..23eec2d73 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -26,12 +26,22 @@ Scope { return; if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { if (buffer.length > 0) { - // If buffer has text, start password check + if (fprint.active) + fprint.abort(); + if (howdy.active) + howdy.abort(); + passwd.start(); } else { - // Buffer is empty, try howdy if (howdy.available) { + if (fprint.active) + fprint.abort(); howdy.start(); + } else if (fprint.available) { + // *** FIX: Abort howdy before starting fprint *** + if (howdy.active) + howdy.abort(); + fprint.start(); } } } else if (event.key === Qt.Key_Backspace) { @@ -266,8 +276,11 @@ Scope { } function onUnlock(): void { - fprint.abort(); - howdy.abort(); + if (fprint.active) + fprint.abort(); + + if (howdy.active) + howdy.abort(); } } From ee292c66810af24f76280a0f2055c1644fbd09ad Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Tue, 28 Oct 2025 16:41:40 +0100 Subject: [PATCH 06/11] Fix fprint crash when using pass --- modules/lock/Pam.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index 23eec2d73..211a430e0 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -22,7 +22,7 @@ Scope { signal flashMsg function handleKey(event: KeyEvent): void { - if (passwd.active || state === "max" || howdy.active) + if (passwd.active) return; if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { if (buffer.length > 0) { From ea75abde5f20fbef2bc7d785ea5f06594d85fb2a Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Thu, 30 Oct 2025 21:32:21 +0100 Subject: [PATCH 07/11] Fix howdy auto start --- modules/IdleMonitors.qml | 18 +++++++++++- modules/lock/Lock.qml | 1 + modules/lock/Pam.qml | 62 +++++++++++++++++++--------------------- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/modules/IdleMonitors.qml b/modules/IdleMonitors.qml index b7ce05843..c5351a090 100644 --- a/modules/IdleMonitors.qml +++ b/modules/IdleMonitors.qml @@ -45,7 +45,23 @@ Scope { enabled: root.enabled && (modelData.enabled ?? true) timeout: modelData.timeout respectInhibitors: modelData.respectInhibitors ?? true - onIsIdleChanged: root.handleIdleAction(isIdle ? modelData.idleAction : modelData.returnAction) + onIsIdleChanged: { + root.handleIdleAction(isIdle ? modelData.idleAction : modelData.returnAction); + + let idleActionString = ""; + if (typeof modelData.idleAction === "string") + idleActionString = modelData.idleAction; + else if (Array.isArray(modelData.idleAction)) + idleActionString = modelData.idleAction.join(" "); + + if (idleActionString.includes("dpms off")) { + if (root.lock.pam) { + root.lock.pam.screenIsIdle = isIdle; + } + } else { + console.log("[IdleMonitor] 'dpms off' string NOT found."); + } + } } } } diff --git a/modules/lock/Lock.qml b/modules/lock/Lock.qml index 6fd5277fe..adcdc7415 100644 --- a/modules/lock/Lock.qml +++ b/modules/lock/Lock.qml @@ -7,6 +7,7 @@ import Quickshell.Wayland Scope { property alias lock: lock + property alias pam: pam WlSessionLock { id: lock diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index 211a430e0..a856ef164 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -18,32 +18,16 @@ Scope { property string fprintState property string howdyState property string buffer + property bool screenIsIdle: false signal flashMsg function handleKey(event: KeyEvent): void { - if (passwd.active) + if (passwd.active || state === "max") return; - if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { - if (buffer.length > 0) { - if (fprint.active) - fprint.abort(); - if (howdy.active) - howdy.abort(); - passwd.start(); - } else { - if (howdy.available) { - if (fprint.active) - fprint.abort(); - howdy.start(); - } else if (fprint.available) { - // *** FIX: Abort howdy before starting fprint *** - if (howdy.active) - howdy.abort(); - fprint.start(); - } - } + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { + passwd.start(); } else if (event.key === Qt.Key_Backspace) { if (event.modifiers & Qt.ControlModifier) { buffer = ""; @@ -55,7 +39,6 @@ Scope { buffer += event.text; } } - PamContext { id: passwd @@ -159,22 +142,24 @@ Scope { abort(); return; } - tries = 0; errorTries = 0; - // start(); // Start only when press Enter + if (root.screenIsIdle) { + return; + } + start(); } config: "howdy" configDirectory: Quickshell.shellDir + "/assets/pam.d" onCompleted: res => { + console.log("[Howdy] onCompleted. Result:", res); if (!available) return; - - if (res === PamResult.Success) + if (res === PamResult.Success) { return root.lock.unlock(); - + } if (res === PamResult.Error) { root.howdyState = "error"; errorTries++; @@ -184,15 +169,11 @@ Scope { } } else if (res === PamResult.MaxTries || res === PamResult.Failed) { tries++; - if (tries < Config.lock.maxHowdyTries) { - root.howdyState = "fail"; + root.howdyState = "fail"; + if (!root.screenIsIdle) { start(); - } else { - root.howdyState = "max"; - abort(); } } - root.flashMsg(); howdyStateReset.restart(); } @@ -272,6 +253,8 @@ Scope { root.fprintState = ""; root.howdyState = ""; root.lockMessage = ""; + } else { + root.screenIsIdle = false; } } @@ -295,4 +278,19 @@ Scope { howdy.checkAvail(); } } + + Connections { + target: root + function onScreenIsIdleChanged() { + if (root.screenIsIdle) { + if (howdy.available && howdy.active) { + howdy.abort(); + } + } else { + if (howdy.available && root.lock.secure && !howdy.active) { + howdy.checkAvail(); + } + } + } + } } From 2dde7e920549157611b4f35e44cb04c84cb8a0af Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Thu, 30 Oct 2025 22:06:53 +0100 Subject: [PATCH 08/11] Fix howdy auto start --- config/LockConfig.qml | 1 - modules/IdleMonitors.qml | 7 ++-- modules/lock/Pam.qml | 72 ++++++++++++++++++---------------------- 3 files changed, 36 insertions(+), 44 deletions(-) diff --git a/config/LockConfig.qml b/config/LockConfig.qml index fdb1c07fc..d66208e4e 100644 --- a/config/LockConfig.qml +++ b/config/LockConfig.qml @@ -5,7 +5,6 @@ JsonObject { property bool enableFprint: true property int maxFprintTries: 3 property bool enableHowdy: true - property int maxHowdyTries: 3 property Sizes sizes: Sizes {} component Sizes: JsonObject { diff --git a/modules/IdleMonitors.qml b/modules/IdleMonitors.qml index c5351a090..505a09e3a 100644 --- a/modules/IdleMonitors.qml +++ b/modules/IdleMonitors.qml @@ -32,8 +32,8 @@ Scope { if (Config.general.idle.lockBeforeSleep) root.lock.lock.locked = true; } - onLockRequested: root.lock.lock.locked = true - onUnlockRequested: root.lock.lock.unlock() + onLockRequested: root.lock.locked = true + onUnlockRequested: root.lock.unlock() } Variants { @@ -45,6 +45,7 @@ Scope { enabled: root.enabled && (modelData.enabled ?? true) timeout: modelData.timeout respectInhibitors: modelData.respectInhibitors ?? true + onIsIdleChanged: { root.handleIdleAction(isIdle ? modelData.idleAction : modelData.returnAction); @@ -58,8 +59,6 @@ Scope { if (root.lock.pam) { root.lock.pam.screenIsIdle = isIdle; } - } else { - console.log("[IdleMonitor] 'dpms off' string NOT found."); } } } diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index a856ef164..a50be7f23 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -18,6 +18,7 @@ Scope { property string fprintState property string howdyState property string buffer + property bool screenIsIdle: false signal flashMsg @@ -39,38 +40,32 @@ Scope { buffer += event.text; } } + PamContext { id: passwd - config: "passwd" configDirectory: Quickshell.shellDir + "/assets/pam.d" - onMessageChanged: { if (message.startsWith("The account is locked")) root.lockMessage = message; else if (root.lockMessage && message.endsWith(" left to unlock)")) root.lockMessage += "\n" + message; } - onResponseRequiredChanged: { if (!responseRequired) return; - respond(root.buffer); root.buffer = ""; } - onCompleted: res => { if (res === PamResult.Success) return root.lock.unlock(); - if (res === PamResult.Error) root.state = "error"; else if (res === PamResult.MaxTries) root.state = "max"; else if (res === PamResult.Failed) root.state = "fail"; - root.flashMsg(); stateReset.restart(); } @@ -78,32 +73,25 @@ Scope { PamContext { id: fprint - property bool available property int tries property int errorTries - function checkAvail(): void { if (!available || !Config.lock.enableFprint || !root.lock.secure) { abort(); return; } - tries = 0; errorTries = 0; start(); } - config: "fprint" configDirectory: Quickshell.shellDir + "/assets/pam.d" - onCompleted: res => { if (!available) return; - if (res === PamResult.Success) return root.lock.unlock(); - if (res === PamResult.Error) { root.fprintState = "error"; errorTries++; @@ -112,11 +100,8 @@ Scope { errorRetry.restart(); } } else if (res === PamResult.MaxTries) { - // Isn't actually the real max tries as pam only reports completed - // when max tries is reached. tries++; if (tries < Config.lock.maxFprintTries) { - // Restart if not actually real max tries root.fprintState = "fail"; start(); } else { @@ -124,7 +109,6 @@ Scope { abort(); } } - root.flashMsg(); fprintStateReset.start(); } @@ -154,7 +138,6 @@ Scope { configDirectory: Quickshell.shellDir + "/assets/pam.d" onCompleted: res => { - console.log("[Howdy] onCompleted. Result:", res); if (!available) return; if (res === PamResult.Success) { @@ -172,7 +155,7 @@ Scope { root.howdyState = "fail"; if (!root.screenIsIdle) { start(); - } + } else {} } root.flashMsg(); howdyStateReset.restart(); @@ -180,7 +163,6 @@ Scope { } Process { id: availProc - command: ["sh", "-c", "fprintd-list $USER"] onExited: code => { fprint.available = code === 0; @@ -189,51 +171,40 @@ Scope { } Process { id: howdyAvailProc - command: ["sh", "-c", "command -v howdy"] onExited: code => { howdy.available = code === 0; howdy.checkAvail(); } } - Timer { id: errorRetry - interval: 800 onTriggered: fprint.start() } - Timer { id: stateReset - interval: 4000 onTriggered: { if (root.state !== "max") root.state = ""; } } - Timer { id: fprintStateReset - interval: 4000 onTriggered: { root.fprintState = ""; fprint.errorTries = 0; } } - Timer { id: howdyErrorRetry - interval: 800 onTriggered: howdy.start() } - Timer { id: howdyStateReset - interval: 4000 onTriggered: { root.howdyState = ""; @@ -241,13 +212,24 @@ Scope { } } + // Timer to delay the start of Howdy after unlock + Timer { + id: howdyStartDelayTimer + interval: 2000 + repeat: false + onTriggered: { + howdyAvailProc.running = true; + } + } + Connections { target: root.lock - function onSecureChanged(): void { if (root.lock.secure) { availProc.running = true; - howdyAvailProc.running = true; + + howdyStartDelayTimer.start(); + root.buffer = ""; root.state = ""; root.fprintState = ""; @@ -255,30 +237,42 @@ Scope { root.lockMessage = ""; } else { root.screenIsIdle = false; + howdyStartDelayTimer.stop(); } } - function onUnlock(): void { + howdyStartDelayTimer.stop(); + if (fprint.active) fprint.abort(); - if (howdy.active) howdy.abort(); } } - Connections { target: Config.lock - function onEnableFprintChanged(): void { fprint.checkAvail(); } - function onEnableHowdyChanged(): void { howdy.checkAvail(); } } + Connections { + target: root + function onScreenIsIdleChanged() { + if (root.screenIsIdle) { + if (howdy.available && howdy.active) { + howdy.abort(); + } + } else { + if (howdy.available && root.lock.secure && !howdy.active) { + howdy.checkAvail(); + } else {} + } + } + } Connections { target: root function onScreenIsIdleChanged() { From 6238acd8401a88fa1df564b8118776b860ef51b9 Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Thu, 30 Oct 2025 22:18:38 +0100 Subject: [PATCH 09/11] Fix howdy auto start --- modules/lock/Center.qml | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/modules/lock/Center.qml b/modules/lock/Center.qml index aebf8e84a..f112d4ed6 100644 --- a/modules/lock/Center.qml +++ b/modules/lock/Center.qml @@ -160,8 +160,8 @@ ColumnLayout { anchors.centerIn: parent animate: true text: { - if (root.lock.pam.howdy.tries >= Config.lock.maxHowdyTries) - return "visibility_off"; + // if (root.lock.pam.howdy.tries >= Config.lock.maxHowdyTries) + // return "visibility_off"; if (root.lock.pam.fprint.tries >= Config.lock.maxFprintTries) return "fingerprint_off"; @@ -307,14 +307,8 @@ ColumnLayout { if (pam.lockMessage) return pam.lockMessage; - if (pam.state === "max" && pam.fprintState === "max" && pam.howdyState === "max") - return qsTr("Maximum password, fingerprint, and face attempts reached."); if (pam.state === "max" && pam.fprintState === "max") return qsTr("Maximum password and fingerprint attempts reached."); - if (pam.state === "max" && pam.howdyState === "max") - return qsTr("Maximum password and face attempts reached."); - if (pam.fprintState === "max" && pam.howdyState === "max") - return qsTr("Maximum fingerprint and face attempts reached."); if (pam.state === "max") { if (pam.fprint.available && pam.howdy.available) @@ -330,11 +324,6 @@ ColumnLayout { return qsTr("Maximum fingerprint attempts reached. Please use face or password."); return qsTr("Maximum fingerprint attempts reached. Please use password."); } - if (pam.howdyState === "max") { - if (pam.fprint.available) - return qsTr("Maximum face attempts reached. Please use fingerprint or password."); - return qsTr("Maximum face attempts reached. Please use password."); - } if (pam.state === "fail") { if (pam.fprint.available && pam.howdy.available) @@ -347,8 +336,6 @@ ColumnLayout { } if (pam.fprintState === "fail") return qsTr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(Config.lock.maxFprintTries); - if (pam.howdyState === "fail") - return qsTr("Face not recognized (%1/%2). Please try again or use password.").arg(pam.howdy.tries).arg(Config.lock.maxHowdyTries); return ""; } From e974c94a4b711ed49f93f1338fc3ade14382bc27 Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Thu, 30 Oct 2025 23:48:56 +0100 Subject: [PATCH 10/11] Formating the code --- modules/lock/Pam.qml | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index a50be7f23..8f40e1d8c 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -43,29 +43,35 @@ Scope { PamContext { id: passwd + config: "passwd" configDirectory: Quickshell.shellDir + "/assets/pam.d" + onMessageChanged: { if (message.startsWith("The account is locked")) root.lockMessage = message; else if (root.lockMessage && message.endsWith(" left to unlock)")) root.lockMessage += "\n" + message; } + onResponseRequiredChanged: { if (!responseRequired) return; respond(root.buffer); root.buffer = ""; } + onCompleted: res => { if (res === PamResult.Success) return root.lock.unlock(); + if (res === PamResult.Error) root.state = "error"; else if (res === PamResult.MaxTries) root.state = "max"; else if (res === PamResult.Failed) root.state = "fail"; + root.flashMsg(); stateReset.restart(); } @@ -73,25 +79,32 @@ Scope { PamContext { id: fprint + property bool available property int tries property int errorTries + function checkAvail(): void { if (!available || !Config.lock.enableFprint || !root.lock.secure) { abort(); return; } + tries = 0; errorTries = 0; start(); } + config: "fprint" configDirectory: Quickshell.shellDir + "/assets/pam.d" + onCompleted: res => { if (!available) return; + if (res === PamResult.Success) return root.lock.unlock(); + if (res === PamResult.Error) { root.fprintState = "error"; errorTries++; @@ -100,8 +113,11 @@ Scope { errorRetry.restart(); } } else if (res === PamResult.MaxTries) { + // Isn't actually the real max tries as pam only reports completed + // when max tries is reached. tries++; if (tries < Config.lock.maxFprintTries) { + // Restart if not actually real max tries root.fprintState = "fail"; start(); } else { @@ -109,6 +125,7 @@ Scope { abort(); } } + root.flashMsg(); fprintStateReset.start(); } @@ -126,11 +143,14 @@ Scope { abort(); return; } + tries = 0; errorTries = 0; + if (root.screenIsIdle) { return; } + start(); } @@ -140,9 +160,11 @@ Scope { onCompleted: res => { if (!available) return; + if (res === PamResult.Success) { return root.lock.unlock(); } + if (res === PamResult.Error) { root.howdyState = "error"; errorTries++; @@ -157,31 +179,38 @@ Scope { start(); } else {} } + root.flashMsg(); howdyStateReset.restart(); } } + Process { id: availProc + command: ["sh", "-c", "fprintd-list $USER"] onExited: code => { fprint.available = code === 0; fprint.checkAvail(); } } + Process { id: howdyAvailProc + command: ["sh", "-c", "command -v howdy"] onExited: code => { howdy.available = code === 0; howdy.checkAvail(); } } + Timer { id: errorRetry interval: 800 onTriggered: fprint.start() } + Timer { id: stateReset interval: 4000 @@ -190,6 +219,7 @@ Scope { root.state = ""; } } + Timer { id: fprintStateReset interval: 4000 @@ -198,11 +228,13 @@ Scope { fprint.errorTries = 0; } } + Timer { id: howdyErrorRetry interval: 800 onTriggered: howdy.start() } + Timer { id: howdyStateReset interval: 4000 @@ -227,9 +259,7 @@ Scope { function onSecureChanged(): void { if (root.lock.secure) { availProc.running = true; - howdyStartDelayTimer.start(); - root.buffer = ""; root.state = ""; root.fprintState = ""; @@ -240,6 +270,7 @@ Scope { howdyStartDelayTimer.stop(); } } + function onUnlock(): void { howdyStartDelayTimer.stop(); @@ -249,6 +280,7 @@ Scope { howdy.abort(); } } + Connections { target: Config.lock function onEnableFprintChanged(): void { @@ -261,6 +293,7 @@ Scope { Connections { target: root + function onScreenIsIdleChanged() { if (root.screenIsIdle) { if (howdy.available && howdy.active) { @@ -275,6 +308,7 @@ Scope { } Connections { target: root + function onScreenIsIdleChanged() { if (root.screenIsIdle) { if (howdy.available && howdy.active) { From 94edf1a123632fc541da54c031640ad43c5de1ec Mon Sep 17 00:00:00 2001 From: bonnjalal Date: Thu, 30 Oct 2025 23:52:30 +0100 Subject: [PATCH 11/11] removing duplicate fun --- modules/lock/Pam.qml | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml index 8f40e1d8c..ac463dcb8 100644 --- a/modules/lock/Pam.qml +++ b/modules/lock/Pam.qml @@ -207,12 +207,14 @@ Scope { Timer { id: errorRetry + interval: 800 onTriggered: fprint.start() } Timer { id: stateReset + interval: 4000 onTriggered: { if (root.state !== "max") @@ -222,6 +224,7 @@ Scope { Timer { id: fprintStateReset + interval: 4000 onTriggered: { root.fprintState = ""; @@ -231,12 +234,14 @@ Scope { Timer { id: howdyErrorRetry + interval: 800 onTriggered: howdy.start() } Timer { id: howdyStateReset + interval: 4000 onTriggered: { root.howdyState = ""; @@ -247,6 +252,7 @@ Scope { // Timer to delay the start of Howdy after unlock Timer { id: howdyStartDelayTimer + interval: 2000 repeat: false onTriggered: { @@ -283,6 +289,7 @@ Scope { Connections { target: Config.lock + function onEnableFprintChanged(): void { fprint.checkAvail(); } @@ -291,21 +298,6 @@ Scope { } } - Connections { - target: root - - function onScreenIsIdleChanged() { - if (root.screenIsIdle) { - if (howdy.available && howdy.active) { - howdy.abort(); - } - } else { - if (howdy.available && root.lock.secure && !howdy.active) { - howdy.checkAvail(); - } else {} - } - } - } Connections { target: root