Show fingerprint on lock screen and polkit, gated by lid state
Bring the fingerprint affordance to the Quickshell lock screen and polkit dialog, matching what hyprlock did on master. Lock screen: render the md-fingerprint glyph inside the password field's right edge when a sensor is enrolled, reserving space so long passwords never run under it. Polkit dialog: show one method at a time. When a sensor is enrolled and the reader is reachable, the dialog is just the centered fingerprint icon (square card); the moment PAM asks for a password it switches to the password field. Detects pam_fprintd anywhere in the auth stack now that a gate can precede it. Lid awareness: a closed lid means the reader is unreachable, so both surfaces fall back to the password. polkit gets a pam_exec clamshell gate (auth [success=1 default=ignore] before pam_fprintd) so a shut lid drops straight to the password prompt instead of blocking on the reader for the pam_fprintd timeout; the lock screen hides the icon and skips scanning. The gate points at the fixed /usr/bin path the package always provides so it survives switching between package installs and dev-link. A migration adds the gate for existing fingerprint setups. New helper omarchy-hw-laptop-closed (pure lid state); omarchy-hw-clamshell now composes it with the external-monitor check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
39ca9135c4
commit
540e411edf
@@ -24,6 +24,9 @@ Item {
|
||||
readonly property int fieldFontSize: Math.round(Style.font.heading * 1.125)
|
||||
readonly property int passwordDotFontSize: Math.round(Style.font.heading * 1.33)
|
||||
readonly property int passwordDotLetterSpacing: Math.round(Style.font.heading * 0.19)
|
||||
// Space to keep clear on each side of the field for the fingerprint icon
|
||||
// (icon width plus a gap) so the centered dots never run under it.
|
||||
readonly property real fingerprintReserve: fingerprintConfigured ? Math.round(fingerprintIcon.implicitWidth + 12) : 0
|
||||
// Shrink the dots to fit once the password outgrows the field, so every
|
||||
// keystroke stays visible — otherwise long passwords clip with no feedback.
|
||||
readonly property real passwordDotScale: dotMetrics.advanceWidth > 0
|
||||
@@ -130,9 +133,11 @@ Item {
|
||||
id: passwordInput
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: inputField.borderTop
|
||||
anchors.rightMargin: inputField.borderRight + 18
|
||||
// Reserve the fingerprint icon's width on both sides so the centered
|
||||
// dots stay symmetric and never slide under the icon as they grow.
|
||||
anchors.rightMargin: inputField.borderRight + 18 + root.fingerprintReserve
|
||||
anchors.bottomMargin: inputField.borderBottom
|
||||
anchors.leftMargin: inputField.borderLeft + 18
|
||||
anchors.leftMargin: inputField.borderLeft + 18 + root.fingerprintReserve
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
activeFocusOnPress: true
|
||||
@@ -190,6 +195,24 @@ Item {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Fingerprint hint pinned inside the field's right edge when a sensor is
|
||||
// enrolled, so the user knows they can touch to unlock instead of typing.
|
||||
// Matches hyprlock, which draws its fingerprint icon in the same spot.
|
||||
Text {
|
||||
id: fingerprintIcon
|
||||
objectName: "fingerprintIndicator"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: inputField.borderRight + 18
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.fingerprintConfigured
|
||||
text: ""
|
||||
color: Color.lock.placeholder
|
||||
font.family: Style.font.family
|
||||
font.pixelSize: Math.round(root.fieldFontSize * 1.1)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ Item {
|
||||
property bool fingerprintAuthenticating: false
|
||||
property bool passwordPamConfigured: false
|
||||
property bool fingerprintConfigured: false
|
||||
// Lid shut → the reader is unreachable, so hide the affordance and don't
|
||||
// bother scanning; fall back to the password like the polkit dialog does.
|
||||
property bool laptopClosed: false
|
||||
property bool previewVisible: false
|
||||
property string enteredPassword: ""
|
||||
property string pendingPassword: ""
|
||||
@@ -34,6 +37,8 @@ Item {
|
||||
|
||||
readonly property bool locked: lockRequested || sessionLock.locked || sessionLock.secure
|
||||
readonly property bool authenticating: authenticatingPassword || fingerprintAuthenticating
|
||||
// Fingerprint is only offered when a sensor is enrolled and the lid is open.
|
||||
readonly property bool fingerprintAvailable: fingerprintConfigured && !laptopClosed
|
||||
|
||||
function realScreenCount() {
|
||||
var screens = Quickshell.screens || []
|
||||
@@ -82,6 +87,10 @@ Item {
|
||||
if (!fingerprintCheckProc.running) fingerprintCheckProc.running = true
|
||||
}
|
||||
|
||||
function refreshLaptopClosed() {
|
||||
if (!laptopClosedProc.running) laptopClosedProc.running = true
|
||||
}
|
||||
|
||||
function logEvent(event) {
|
||||
lastEvent = event
|
||||
lastEventAt = new Date().toISOString()
|
||||
@@ -115,6 +124,7 @@ Item {
|
||||
Qt.callLater(function() {
|
||||
root.refreshBackground()
|
||||
root.refreshFingerprintStatus()
|
||||
root.refreshLaptopClosed()
|
||||
})
|
||||
|
||||
return true
|
||||
@@ -182,7 +192,7 @@ Item {
|
||||
}
|
||||
|
||||
function startFingerprint() {
|
||||
if (!lockRequested || !sessionLock.secure || !fingerprintConfigured) return
|
||||
if (!lockRequested || !sessionLock.secure || !fingerprintAvailable) return
|
||||
if (fingerprintPam.active || fingerprintAuthenticating) return
|
||||
|
||||
fingerprintAuthenticating = true
|
||||
@@ -197,7 +207,7 @@ Item {
|
||||
if (!lockRequested) return
|
||||
if (result === PamResult.Success) {
|
||||
finishUnlock()
|
||||
} else if (fingerprintConfigured) {
|
||||
} else if (fingerprintAvailable) {
|
||||
fingerprintRetryTimer.restart()
|
||||
}
|
||||
}
|
||||
@@ -245,7 +255,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
backgroundPath: root.backgroundPath
|
||||
backgroundVersion: root.backgroundVersion
|
||||
fingerprintConfigured: root.fingerprintConfigured
|
||||
fingerprintConfigured: root.fingerprintAvailable
|
||||
authenticatingPassword: root.authenticatingPassword
|
||||
failureMessage: root.failureMessage
|
||||
failedAttempts: root.failedAttempts
|
||||
@@ -275,7 +285,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
backgroundPath: root.backgroundPath
|
||||
backgroundVersion: root.backgroundVersion
|
||||
fingerprintConfigured: root.fingerprintConfigured
|
||||
fingerprintConfigured: root.fingerprintAvailable
|
||||
authenticatingPassword: false
|
||||
failureMessage: ""
|
||||
failedAttempts: 0
|
||||
@@ -324,7 +334,7 @@ Item {
|
||||
|
||||
onError: function(error) {
|
||||
root.fingerprintAuthenticating = false
|
||||
if (root.lockRequested && root.fingerprintConfigured) fingerprintRetryTimer.restart()
|
||||
if (root.lockRequested && root.fingerprintAvailable) fingerprintRetryTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,8 +366,19 @@ Item {
|
||||
stdout: StdioCollector { id: fingerprintCheckStdout; waitForEnd: true }
|
||||
onExited: {
|
||||
root.fingerprintConfigured = String(fingerprintCheckStdout.text || "").trim() === "yes"
|
||||
if (root.lockRequested && root.fingerprintConfigured) root.startFingerprint()
|
||||
else if (!root.fingerprintConfigured && fingerprintPam.active) fingerprintPam.abort()
|
||||
if (root.lockRequested && root.fingerprintAvailable) root.startFingerprint()
|
||||
else if (!root.fingerprintAvailable && fingerprintPam.active) fingerprintPam.abort()
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: laptopClosedProc
|
||||
command: ["bash", "-c", "omarchy-hw-laptop-closed && echo closed || echo open"]
|
||||
stdout: StdioCollector { id: laptopClosedStdout; waitForEnd: true }
|
||||
onExited: {
|
||||
root.laptopClosed = String(laptopClosedStdout.text || "").trim() === "closed"
|
||||
if (root.lockRequested && root.fingerprintAvailable) root.startFingerprint()
|
||||
else if (!root.fingerprintAvailable && fingerprintPam.active) fingerprintPam.abort()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +446,7 @@ Item {
|
||||
Component.onCompleted: {
|
||||
refreshBackground()
|
||||
refreshFingerprintStatus()
|
||||
refreshLaptopClosed()
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
@@ -450,6 +472,7 @@ Item {
|
||||
realScreens: root.realScreenCount(),
|
||||
passwordPam: root.passwordPamConfigured,
|
||||
fingerprint: root.fingerprintConfigured,
|
||||
laptopClosed: root.laptopClosed,
|
||||
authenticating: root.authenticating,
|
||||
lastEvent: root.lastEvent,
|
||||
lastEventAt: root.lastEventAt
|
||||
|
||||
Reference in New Issue
Block a user