Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 98 additions & 3 deletions modules/lockscreen/LockScreen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ WlSessionLockSurface {
property bool authenticating: false
property string errorMessage: ""
property int failLockSecondsLeft: 0
property bool fingerprintAvailable: false
property bool fingerprintScanning: false

// Always transparent - blur background handles the visuals
color: "transparent"
Expand Down Expand Up @@ -452,10 +454,10 @@ WlSessionLockSurface {
anchors.rightMargin: 32
spacing: 8

// User icon / Spinner
// User icon / Spinner / Fingerprint
Text {
id: userIcon
text: authenticating ? Icons.circleNotch : Icons.user
text: authenticating ? Icons.circleNotch : (fingerprintScanning ? Icons.fingerprint : Icons.user)
font.family: Icons.font
font.pixelSize: 24
color: passwordFieldBg.item
Expand All @@ -473,6 +475,7 @@ WlSessionLockSurface {
}
}

// Spinner for password auth
Timer {
id: spinnerTimer
interval: 100
Expand All @@ -483,10 +486,33 @@ WlSessionLockSurface {
}
}

// Breathing pulse for fingerprint scanning
SequentialAnimation {
running: fingerprintScanning && !authenticating
loops: Animation.Infinite
NumberAnimation {
target: userIcon
property: "opacity"
to: 0.35
duration: 1170
easing.type: Easing.InOutSine
}
NumberAnimation {
target: userIcon
property: "opacity"
to: 1.0
duration: 1170
easing.type: Easing.InOutSine
}
}

onTextChanged: {
if (userIcon.text === Icons.user) {
if (userIcon.text !== Icons.circleNotch) {
userIcon.rotation = 0;
}
if (userIcon.text !== Icons.fingerprint) {
userIcon.opacity = 1.0;
}
}
}

Expand Down Expand Up @@ -525,6 +551,12 @@ WlSessionLockSurface {
if (passwordInput.text.trim() === "")
return;

// Stop fingerprint scan while password auth runs
if (fprintdVerify.running) {
fprintdVerify.running = false;
fingerprintScanning = false;
}

// Guardar contraseña y limpiar campo inmediatamente
authPasswordHolder.password = passwordInput.text;
passwordInput.text = "";
Expand Down Expand Up @@ -578,6 +610,11 @@ WlSessionLockSurface {
passwordInput.text = "";
authenticating = false;
passwordInputBox.showError = false;
// Resume fingerprint scanning after failed password attempt
if (fingerprintAvailable) {
fprintdVerify.running = true;
fingerprintScanning = true;
}
}
}
}
Expand Down Expand Up @@ -662,6 +699,58 @@ WlSessionLockSurface {
}
}

// Fingerprint authentication via fprintd
// Step 1: check if user has any enrolled fingers
Process {
id: fprintdCheckProc
command: ["bash", "-c", `fprintd-list '${usernameCollector.text.trim()}' 2>/dev/null`]
running: false

stdout: StdioCollector {
onStreamFinished: {
// fprintd-list output contains "finger" for each enrolled finger
if (text.indexOf("-finger") >= 0) {
fingerprintAvailable = true;
fprintdVerify.running = true;
fingerprintScanning = true;
}
}
}
}

// Step 2: run fprintd-verify and watch for match
Process {
id: fprintdVerify
command: ["fprintd-verify", usernameCollector.text.trim()]
running: false

stdout: StdioCollector {
id: fprintdVerifyOut
onStreamFinished: {
fingerprintScanning = false;
if (text.indexOf("verify-match") >= 0) {
// Fingerprint matched — trigger the same unlock flow as password success
startAnim = false;
unlockTimer.start();
errorMessage = "";
authenticating = false;
}
// On failure or timeout, do nothing — user can still type password
}
}
}

// Start fingerprint check once the username is known
Connections {
target: usernameCollector
function onTextChanged() {
const user = usernameCollector.text.trim();
if (user !== "" && startAnim && !fprintdCheckProc.running && !fingerprintAvailable) {
fprintdCheckProc.running = true;
}
}
}

// PAM authentication process
PamContext {
id: pamAuth
Expand Down Expand Up @@ -746,5 +835,11 @@ WlSessionLockSurface {
// Start animations
startAnim = true;
passwordInput.forceActiveFocus();

// If username is already available (whoami finished), start fingerprint check now.
// Otherwise the Connections on usernameCollector will trigger it.
if (usernameCollector.text.trim() !== "") {
fprintdCheckProc.running = true;
}
}
}
1 change: 1 addition & 0 deletions modules/theme/Icons.qml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ QtObject {
readonly property string sunDim: ""
readonly property string moon: ""
readonly property string user: ""
readonly property string fingerprint: ""
readonly property string spinnerGap: ""
readonly property string circleNotch: ""
readonly property string file: ""
Expand Down