mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
lock: fprintd support
This commit is contained in:
@@ -173,6 +173,9 @@ Singleton {
|
||||
property int dankBarPosition: SettingsData.Position.Top
|
||||
property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right
|
||||
property bool lockScreenShowPowerActions: true
|
||||
property bool enableFprint: false
|
||||
property int maxFprintTries: 3
|
||||
property bool fprintdAvailable: false
|
||||
property bool hideBrightnessSlider: false
|
||||
property string widgetBackgroundColor: "sch"
|
||||
property string surfaceBase: "s"
|
||||
@@ -440,6 +443,8 @@ Singleton {
|
||||
popupGapsManual = settings.popupGapsManual !== undefined ? settings.popupGapsManual : 4
|
||||
dankBarPosition = settings.dankBarPosition !== undefined ? settings.dankBarPosition : (settings.dankBarAtBottom !== undefined ? (settings.dankBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : (settings.topBarAtBottom !== undefined ? (settings.topBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : SettingsData.Position.Top))
|
||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
|
||||
enableFprint = settings.enableFprint !== undefined ? settings.enableFprint : false
|
||||
maxFprintTries = settings.maxFprintTries !== undefined ? settings.maxFprintTries : 3
|
||||
hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false
|
||||
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch"
|
||||
surfaceBase = settings.surfaceBase !== undefined ? settings.surfaceBase : "s"
|
||||
@@ -576,6 +581,8 @@ Singleton {
|
||||
"popupGapsManual": popupGapsManual,
|
||||
"dankBarPosition": dankBarPosition,
|
||||
"lockScreenShowPowerActions": lockScreenShowPowerActions,
|
||||
"enableFprint": enableFprint,
|
||||
"maxFprintTries": maxFprintTries,
|
||||
"hideBrightnessSlider": hideBrightnessSlider,
|
||||
"widgetBackgroundColor": widgetBackgroundColor,
|
||||
"surfaceBase": surfaceBase,
|
||||
@@ -1441,6 +1448,16 @@ Singleton {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setEnableFprint(enabled) {
|
||||
enableFprint = enabled
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setMaxFprintTries(tries) {
|
||||
maxFprintTries = tries
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
function setHideBrightnessSlider(enabled) {
|
||||
hideBrightnessSlider = enabled
|
||||
saveSettings()
|
||||
@@ -1513,6 +1530,7 @@ Singleton {
|
||||
loadSettings()
|
||||
fontCheckTimer.start()
|
||||
initializeListModels()
|
||||
fprintdDetectionProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1672,6 +1690,16 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fprintdDetectionProcess
|
||||
|
||||
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
|
||||
running: false
|
||||
onExited: exitCode => {
|
||||
fprintdAvailable = (exitCode === 0)
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function reveal(): string {
|
||||
root.setDankBarVisible(true)
|
||||
|
||||
@@ -97,6 +97,15 @@ Item {
|
||||
visible: SessionService.loginctlAvailable && SessionData.loginctlLockIntegration
|
||||
onToggled: checked => SessionData.setLockBeforeSuspend(checked)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Enable fingerprint authentication")
|
||||
description: "Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)"
|
||||
checked: SettingsData.enableFprint
|
||||
visible: SettingsData.fprintdAvailable
|
||||
onToggled: checked => SettingsData.setEnableFprint(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pam
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -253,22 +252,50 @@ Item {
|
||||
border.color: passwordField.activeFocus ? Theme.primary : Qt.rgba(1, 1, 1, 0.3)
|
||||
border.width: passwordField.activeFocus ? 2 : 1
|
||||
|
||||
DankIcon {
|
||||
id: lockIcon
|
||||
|
||||
Item {
|
||||
id: lockIconContainer
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "lock"
|
||||
size: 20
|
||||
color: passwordField.activeFocus ? Theme.primary : Theme.surfaceVariantText
|
||||
width: 20
|
||||
height: 20
|
||||
|
||||
DankIcon {
|
||||
id: lockIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (pam.fprint.tries >= SettingsData.maxFprintTries)
|
||||
return "fingerprint_off";
|
||||
if (pam.fprint.active)
|
||||
return "fingerprint";
|
||||
return "lock";
|
||||
}
|
||||
size: 20
|
||||
color: pam.fprint.tries >= SettingsData.maxFprintTries ? Theme.error : (passwordField.activeFocus ? Theme.primary : Theme.surfaceVariantText)
|
||||
opacity: pam.passwd.active ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: passwordField
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: lockIcon.width + Theme.spacingM * 2
|
||||
anchors.leftMargin: lockIconContainer.width + Theme.spacingM * 2
|
||||
anchors.rightMargin: {
|
||||
let margin = Theme.spacingM
|
||||
if (loadingSpinner.visible) {
|
||||
@@ -296,9 +323,9 @@ Item {
|
||||
}
|
||||
}
|
||||
onAccepted: {
|
||||
if (!demoMode && !pam.active) {
|
||||
if (!demoMode && !pam.passwd.active) {
|
||||
console.log("Enter pressed, starting PAM authentication")
|
||||
pam.start()
|
||||
pam.passwd.start()
|
||||
}
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
@@ -306,7 +333,7 @@ Item {
|
||||
return
|
||||
}
|
||||
|
||||
if (pam.active) {
|
||||
if (pam.passwd.active) {
|
||||
console.log("PAM is active, ignoring input")
|
||||
event.accepted = true
|
||||
return
|
||||
@@ -355,7 +382,7 @@ Item {
|
||||
StyledText {
|
||||
id: placeholder
|
||||
|
||||
anchors.left: lockIcon.right
|
||||
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.rightMargin: 2
|
||||
@@ -367,12 +394,12 @@ Item {
|
||||
if (root.unlocking) {
|
||||
return "Unlocking..."
|
||||
}
|
||||
if (pam.active) {
|
||||
if (pam.passwd.active) {
|
||||
return "Authenticating..."
|
||||
}
|
||||
return "Password..."
|
||||
}
|
||||
color: root.unlocking ? Theme.primary : (pam.active ? Theme.primary : Theme.outline)
|
||||
color: root.unlocking ? Theme.primary : (pam.passwd.active ? Theme.primary : Theme.outline)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
opacity: (demoMode || root.passwordBuffer.length === 0) ? 1 : 0
|
||||
|
||||
@@ -392,7 +419,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.left: lockIcon.right
|
||||
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.rightMargin: 2
|
||||
@@ -427,7 +454,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
||||
buttonSize: 32
|
||||
visible: !demoMode && root.passwordBuffer.length > 0 && !pam.active && !root.unlocking
|
||||
visible: !demoMode && root.passwordBuffer.length > 0 && !pam.passwd.active && !root.unlocking
|
||||
enabled: visible
|
||||
onClicked: parent.showPassword = !parent.showPassword
|
||||
}
|
||||
@@ -439,7 +466,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard"
|
||||
buttonSize: 32
|
||||
visible: !demoMode && !pam.active && !root.unlocking
|
||||
visible: !demoMode && !pam.passwd.active && !root.unlocking
|
||||
enabled: visible
|
||||
onClicked: {
|
||||
if (keyboardController.isKeyboardActive) {
|
||||
@@ -460,7 +487,7 @@ Item {
|
||||
height: 24
|
||||
radius: 12
|
||||
color: "transparent"
|
||||
visible: !demoMode && (pam.active || root.unlocking)
|
||||
visible: !demoMode && (pam.passwd.active || root.unlocking)
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -492,7 +519,7 @@ Item {
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: pam.active && !root.unlocking
|
||||
visible: pam.passwd.active && !root.unlocking
|
||||
|
||||
Rectangle {
|
||||
width: 20
|
||||
@@ -522,7 +549,7 @@ Item {
|
||||
}
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: pam.active && !root.unlocking
|
||||
running: pam.passwd.active && !root.unlocking
|
||||
loops: Animation.Infinite
|
||||
duration: Anims.durLong
|
||||
from: 0
|
||||
@@ -540,12 +567,12 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard_return"
|
||||
buttonSize: 36
|
||||
visible: (demoMode || (!pam.active && !root.unlocking))
|
||||
visible: (demoMode || (!pam.passwd.active && !root.unlocking))
|
||||
enabled: !demoMode
|
||||
onClicked: {
|
||||
if (!demoMode) {
|
||||
console.log("Enter button clicked, starting PAM authentication")
|
||||
pam.start()
|
||||
pam.passwd.start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1091,52 +1118,29 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: pamConfigWatcher
|
||||
|
||||
path: "/etc/pam.d/dankshell"
|
||||
printErrors: false
|
||||
Pam {
|
||||
id: pam
|
||||
lockSecured: !demoMode
|
||||
onUnlockRequested: {
|
||||
root.unlocking = true
|
||||
passwordField.text = ""
|
||||
root.passwordBuffer = ""
|
||||
root.unlockRequested()
|
||||
}
|
||||
onStateChanged: {
|
||||
root.pamState = state
|
||||
if (state !== "") {
|
||||
placeholderDelay.restart()
|
||||
passwordField.text = ""
|
||||
root.passwordBuffer = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PamContext {
|
||||
id: pam
|
||||
|
||||
config: pamConfigWatcher.loaded ? "dankshell" : "login"
|
||||
onResponseRequiredChanged: {
|
||||
if (demoMode)
|
||||
return
|
||||
|
||||
console.log("PAM response required:", responseRequired)
|
||||
if (!responseRequired)
|
||||
return
|
||||
|
||||
console.log("Responding to PAM with password buffer length:", root.passwordBuffer.length)
|
||||
respond(root.passwordBuffer)
|
||||
}
|
||||
onCompleted: res => {
|
||||
if (demoMode)
|
||||
return
|
||||
|
||||
console.log("PAM authentication completed with result:", res)
|
||||
if (res === PamResult.Success) {
|
||||
console.log("Authentication successful, unlocking")
|
||||
root.unlocking = true
|
||||
passwordField.text = ""
|
||||
root.passwordBuffer = ""
|
||||
root.unlockRequested()
|
||||
return
|
||||
}
|
||||
console.log("Authentication failed:", res)
|
||||
passwordField.text = ""
|
||||
root.passwordBuffer = ""
|
||||
if (res === PamResult.Error)
|
||||
root.pamState = "error"
|
||||
else if (res === PamResult.MaxTries)
|
||||
root.pamState = "max"
|
||||
else if (res === PamResult.Failed)
|
||||
root.pamState = "fail"
|
||||
placeholderDelay.restart()
|
||||
}
|
||||
Binding {
|
||||
target: pam
|
||||
property: "buffer"
|
||||
value: root.passwordBuffer
|
||||
}
|
||||
|
||||
Timer {
|
||||
|
||||
177
Modules/Lock/Pam.qml
Normal file
177
Modules/Lock/Pam.qml
Normal file
@@ -0,0 +1,177 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Pam
|
||||
import qs.Common
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
property bool lockSecured: false
|
||||
|
||||
readonly property alias passwd: passwd
|
||||
readonly property alias fprint: fprint
|
||||
property string lockMessage
|
||||
property string state
|
||||
property string fprintState
|
||||
property string buffer
|
||||
|
||||
signal flashMsg
|
||||
signal unlockRequested
|
||||
|
||||
FileView {
|
||||
id: pamConfigWatcher
|
||||
|
||||
path: "/etc/pam.d/dankshell"
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
PamContext {
|
||||
id: passwd
|
||||
|
||||
config: pamConfigWatcher.loaded ? "dankshell" : "login"
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
onCompleted: res => {
|
||||
if (res === PamResult.Success) {
|
||||
root.unlockRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
PamContext {
|
||||
id: fprint
|
||||
|
||||
property bool available
|
||||
property int tries
|
||||
property int errorTries
|
||||
|
||||
function checkAvail(): void {
|
||||
if (!available || !SettingsData.enableFprint || !root.lockSecured) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
tries = 0;
|
||||
errorTries = 0;
|
||||
start();
|
||||
}
|
||||
|
||||
config: "fprint"
|
||||
configDirectory: Quickshell.shellDir + "/assets/pam"
|
||||
|
||||
onCompleted: res => {
|
||||
if (!available)
|
||||
return;
|
||||
|
||||
if (res === PamResult.Success) {
|
||||
root.unlockRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (res === PamResult.Error) {
|
||||
root.fprintState = "error";
|
||||
errorTries++;
|
||||
if (errorTries < 5) {
|
||||
abort();
|
||||
errorRetry.restart();
|
||||
}
|
||||
} else if (res === PamResult.MaxTries) {
|
||||
tries++;
|
||||
if (tries < SettingsData.maxFprintTries) {
|
||||
root.fprintState = "fail";
|
||||
start();
|
||||
} else {
|
||||
root.fprintState = "max";
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
root.flashMsg();
|
||||
fprintStateReset.start();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: availProc
|
||||
|
||||
command: ["sh", "-c", "fprintd-list $USER"]
|
||||
onExited: code => {
|
||||
fprint.available = code === 0;
|
||||
fprint.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;
|
||||
}
|
||||
}
|
||||
|
||||
onLockSecuredChanged: {
|
||||
if (lockSecured) {
|
||||
availProc.running = true;
|
||||
root.state = "";
|
||||
root.fprintState = "";
|
||||
root.lockMessage = "";
|
||||
} else {
|
||||
fprint.abort();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
|
||||
function onEnableFprintChanged(): void {
|
||||
fprint.checkAvail();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,7 +291,7 @@ sudo pacman -S cava wl-clipboard cliphist brightnessctl qt6-multimedia
|
||||
paru -S matugen-bin dgop
|
||||
|
||||
# Fedora
|
||||
sudo dnf install cava wl-clipboard brightnessctl qt6-multimedia
|
||||
sudo dnf install cava wl-clipboard brightnessctl qt6-qtmultimedia
|
||||
sudo dnf copr enable wef/cliphist && sudo dnf install cliphist
|
||||
sudo dnf copr enable heus-sueh/packages && sudo dnf install matugen
|
||||
```
|
||||
|
||||
3
assets/pam/fprint
Normal file
3
assets/pam/fprint
Normal file
@@ -0,0 +1,3 @@
|
||||
#%PAM-1.0
|
||||
|
||||
auth required pam_fprintd.so max-tries=1
|
||||
Reference in New Issue
Block a user