diff --git a/quickshell/Modules/Lock/LockScreenContent.qml b/quickshell/Modules/Lock/LockScreenContent.qml index be2d6c21..04ba4b3f 100644 --- a/quickshell/Modules/Lock/LockScreenContent.qml +++ b/quickshell/Modules/Lock/LockScreenContent.qml @@ -73,6 +73,10 @@ Item { return pam && (pam.u2fState === "waiting" || pam.u2fState === "insert") && !pam.u2fPending; } + function canStartSecurityKeyUnlock() { + return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking; + } + Component.onCompleted: { WeatherService.addRef(); UserInfoService.getUserInfo(); @@ -761,6 +765,9 @@ Item { if (enterButton.visible) { margin += enterButton.width + 2; } + if (securityKeyButton.visible) { + margin += securityKeyButton.width; + } if (virtualKeyboardButton.visible) { margin += virtualKeyboardButton.width; } @@ -854,7 +861,7 @@ Item { anchors.left: lockIconContainer.right anchors.leftMargin: Theme.spacingM - anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))) + anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))))) anchors.rightMargin: 2 anchors.verticalCenter: parent.verticalCenter text: { @@ -896,7 +903,7 @@ Item { StyledText { anchors.left: lockIconContainer.right anchors.leftMargin: Theme.spacingM - anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))) + anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))))) anchors.rightMargin: 2 anchors.verticalCenter: parent.verticalCenter text: { @@ -926,7 +933,7 @@ Item { DankActionButton { id: revealButton - anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)) + anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))) anchors.rightMargin: 0 anchors.verticalCenter: parent.verticalCenter iconName: parent.showPassword ? "visibility_off" : "visibility" @@ -936,10 +943,26 @@ Item { onClicked: parent.showPassword = !parent.showPassword } DankActionButton { - id: virtualKeyboardButton + id: securityKeyButton anchors.right: enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right) - anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS + anchors.rightMargin: 0 + anchors.verticalCenter: parent.verticalCenter + iconName: "passkey" + buttonSize: 32 + visible: root.canStartSecurityKeyUnlock() + enabled: visible + onClicked: { + passwordField.text = ""; + root.passwordBuffer = ""; + pam.u2f.startForAlternativeAuth(); + } + } + DankActionButton { + id: virtualKeyboardButton + + anchors.right: securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)) + anchors.rightMargin: securityKeyButton.visible || enterButton.visible ? 0 : Theme.spacingS anchors.verticalCenter: parent.verticalCenter iconName: "keyboard" buttonSize: 32 diff --git a/quickshell/Modules/Lock/Pam.qml b/quickshell/Modules/Lock/Pam.qml index 4ea8b00f..51a03533 100644 --- a/quickshell/Modules/Lock/Pam.qml +++ b/quickshell/Modules/Lock/Pam.qml @@ -20,6 +20,7 @@ Scope { property string fprintState property string u2fState property bool u2fPending: false + property string u2fPendingMode property string buffer signal flashMsg @@ -35,6 +36,7 @@ Scope { passwdActiveTimeout.running = false; unlockRequestTimeout.running = false; root.u2fPending = false; + root.u2fPendingMode = ""; root.u2fState = ""; root.unlockInProgress = false; } @@ -58,6 +60,7 @@ Scope { u2fErrorRetry.running = false; u2fPendingTimeout.running = false; root.u2fPending = false; + root.u2fPendingMode = ""; root.u2fState = ""; unlockRequestTimeout.restart(); unlockRequested(); @@ -79,6 +82,7 @@ Scope { u2fErrorRetry.running = false; u2fPendingTimeout.running = false; root.u2fPending = false; + root.u2fPendingMode = ""; root.u2fState = ""; fprint.checkAvail(); } @@ -142,6 +146,7 @@ Scope { unlockRequestTimeout.running = false; root.unlockInProgress = false; root.u2fPending = false; + root.u2fPendingMode = ""; root.u2fState = ""; u2fPendingTimeout.running = false; u2f.abort(); @@ -243,9 +248,8 @@ Scope { return; } - if (SettingsData.u2fMode === "or") { - start(); - } + if (SettingsData.u2fMode === "or") + abort(); } function startForSecondFactor(): void { @@ -255,6 +259,18 @@ Scope { } abort(); root.u2fPending = true; + root.u2fPendingMode = "and"; + root.u2fState = ""; + u2fPendingTimeout.restart(); + start(); + } + + function startForAlternativeAuth(): void { + if (!available || !SettingsData.enableU2f || SettingsData.u2fMode !== "or" || root.unlockInProgress || passwd.active || active) + return; + abort(); + root.u2fPending = true; + root.u2fPendingMode = "or"; root.u2fState = ""; u2fPendingTimeout.restart(); start(); @@ -281,9 +297,19 @@ Scope { abort(); if (root.u2fPending) { + if (root.u2fPendingMode === "or") { + root.u2fPending = false; + root.u2fPendingMode = ""; + root.u2fState = root.u2fState === "waiting" ? "" : "insert"; + u2fPendingTimeout.running = false; + fprint.checkAvail(); + return; + } + if (root.u2fState === "waiting") { // AND mode: device was found but auth failed → back to password root.u2fPending = false; + root.u2fPendingMode = ""; root.u2fState = ""; fprint.checkAvail(); } else { @@ -292,9 +318,7 @@ Scope { u2fErrorRetry.restart(); } } else { - // OR mode: prompt to insert key, silently retry root.u2fState = "insert"; - u2fErrorRetry.restart(); } } } @@ -367,6 +391,7 @@ Scope { root.fprintState = ""; root.u2fState = ""; root.u2fPending = false; + root.u2fPendingMode = ""; root.lockMessage = ""; root.resetAuthFlows(); fprint.checkAvail(); @@ -399,6 +424,7 @@ Scope { u2fPendingTimeout.running = false; unlockRequestTimeout.running = false; root.u2fPending = false; + root.u2fPendingMode = ""; root.u2fState = ""; u2f.checkAvail(); }