pragma Singleton // The fingerprint reader, for the Users page. // // Two facts, owned by two different systems: fprintd holds the enrolled // prints (GNOME's Users panel owns the enrollment dialog and Panama hands off // to it), and authselect decides whether PAM asks the reader at unlock. Both // come through scripts/panama-fingerprint, and the one privileged change -- // flipping authselect's with-fingerprint feature -- prompts through polkit // with a stated reason, like everything else on that page. // // Read when the page opens rather than at shell startup: probing fprintd // bus-activates the daemon, and most sessions never open this page. import Quickshell import Quickshell.Io import QtQuick Singleton { id: root readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-fingerprint" property bool readerPresent: false property string readerName: "" property var enrolled: [] property bool pamEnabled: false property bool scanned: false property bool busy: false property string lastError: "" // "right-index-finger" -> "Right index finger". Presentation lives here // rather than in the page, the way PowerProfiles.label does, so nothing // can disagree about what a finger is called. function fingerLabel(finger: string): string { const words = String(finger).split("-").join(" "); return words.slice(0, 1).toUpperCase() + words.slice(1); } function refresh(): void { if (!query.running) query.running = true; } function setUnlockEnabled(on: bool): void { if (root.busy) return; root.busy = true; root.lastError = ""; apply.command = [root.helperPath, "set-unlock", on ? "on" : "off"]; apply.running = true; } Process { id: query command: [root.helperPath, "status"] stdout: StdioCollector { onStreamFinished: { try { const parsed = JSON.parse(this.text); root.readerPresent = parsed.reader === true; root.readerName = String(parsed.readerName ?? ""); root.enrolled = Array.isArray(parsed.enrolled) ? parsed.enrolled : []; root.pamEnabled = parsed.pamEnabled === true; if (String(parsed.error ?? "") !== "") root.lastError = String(parsed.error); } catch (error) { root.readerPresent = false; root.lastError = "Could not read the fingerprint helper's output."; console.warn("Fingerprint: could not parse helper output:", error); } root.scanned = true; } } } Process { id: apply stderr: StdioCollector { // A dismissed polkit prompt is a normal outcome on this page, not // a failure to report. onStreamFinished: { const text = this.text.trim(); if (text !== "" && !/dismissed|not authorized/i.test(text)) root.lastError = text; } } // Re-read rather than assuming: authselect may refuse, and the // prompt may have been dismissed. onExited: { root.busy = false; root.refresh(); } } }