import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Pam import Quickshell.Wayland import qs.Commons Item { id: root property var shell: null property string omarchyPath: "" readonly property string home: Quickshell.env("HOME") readonly property string userName: Quickshell.env("USER") || Quickshell.env("LOGNAME") readonly property string currentBackgroundLink: home + "/.config/omarchy/current/background" property bool lockRequested: false property bool authenticatingPassword: false property bool fingerprintAuthenticating: false property bool passwordPamConfigured: false property bool fingerprintConfigured: false property bool previewVisible: false property string enteredPassword: "" property string pendingPassword: "" property string failureMessage: "" property int failedAttempts: 0 property string backgroundPath: "" property int backgroundVersion: 0 property string lastEvent: "init" property string lastEventAt: "" readonly property bool locked: lockRequested || sessionLock.locked || sessionLock.secure readonly property bool authenticating: authenticatingPassword || fingerprintAuthenticating function refreshBackground() { if (!readlinkProc.running) readlinkProc.running = true } function refreshFingerprintStatus() { if (!fingerprintCheckProc.running) fingerprintCheckProc.running = true } function logEvent(event) { lastEvent = event lastEventAt = new Date().toISOString() console.log("omarchy lock " + lastEventAt + " " + event) } function resetAuthenticationState() { enteredPassword = "" pendingPassword = "" failureMessage = "" failedAttempts = 0 authenticatingPassword = false fingerprintAuthenticating = false fingerprintRetryTimer.stop() if (passwordPam.active) passwordPam.abort() if (fingerprintPam.active) fingerprintPam.abort() } function beginLock() { if (!passwordPamConfigured) { logEvent("lock-denied: missing-pam") return false } resetAuthenticationState() lockRequested = true sessionLock.locked = true idleBlankTimer.restart() logEvent("lock-requested") Qt.callLater(function() { root.refreshBackground() root.refreshFingerprintStatus() }) return true } function finishUnlock() { if (!root.locked && !lockRequested) return lockRequested = false resetAuthenticationState() idleBlankTimer.stop() sessionLock.locked = false logEvent("unlocked") runWake() } function runWake() { if (!wakeProcess.running) wakeProcess.running = true if (lockRequested) idleBlankTimer.restart() } function runBlank() { if (!blankProcess.running) blankProcess.running = true } function submitPassword(value) { var password = String(value || "") if (!lockRequested || authenticatingPassword || password.length === 0) return runWake() pendingPassword = password failureMessage = "" authenticatingPassword = true if (!passwordPam.start()) { handlePasswordFailure() return } Qt.callLater(respondToPasswordPrompt) } function respondToPasswordPrompt() { if (!authenticatingPassword || !passwordPam.active || !passwordPam.responseRequired) return passwordPam.respond(pendingPassword) } function handlePasswordFailure() { if (!lockRequested) return authenticatingPassword = false enteredPassword = "" pendingPassword = "" failedAttempts += 1 failureMessage = "Authentication failed (" + failedAttempts + ")" runWake() } function startFingerprint() { if (!lockRequested || !sessionLock.secure || !fingerprintConfigured) return if (fingerprintPam.active || fingerprintAuthenticating) return fingerprintAuthenticating = true if (!fingerprintPam.start()) { fingerprintAuthenticating = false } } function handleFingerprintFinished(result) { fingerprintAuthenticating = false if (!lockRequested) return if (result === PamResult.Success) { finishUnlock() } else if (fingerprintConfigured) { fingerprintRetryTimer.restart() } } WlSessionLock { id: sessionLock locked: false onSecureStateChanged: { root.logEvent("secure=" + secure) if (secure) root.startFingerprint() } onLockStateChanged: { root.logEvent("session-locked=" + locked) if (!locked && root.lockRequested) { root.lockRequested = false root.resetAuthenticationState() root.runWake() } } WlSessionLockSurface { id: lockSurface color: Color.background LockView { id: lockView anchors.fill: parent backgroundPath: root.backgroundPath backgroundVersion: root.backgroundVersion fingerprintConfigured: root.fingerprintConfigured authenticatingPassword: root.authenticatingPassword failureMessage: root.failureMessage failedAttempts: root.failedAttempts inputEnabled: root.lockRequested passwordText: root.enteredPassword scaleFactor: lockSurface.screen && lockSurface.screen.devicePixelRatio > 0 ? lockSurface.screen.devicePixelRatio : 1 onPasswordTextEdited: function(password) { root.enteredPassword = password } onSubmitPassword: function(password) { root.submitPassword(password) } onClearFailureRequested: root.failureMessage = "" onWakeRequested: root.runWake() } } } PanelWindow { id: previewWindow visible: root.previewVisible anchors { top: true; bottom: true; left: true; right: true } color: "transparent" WlrLayershell.namespace: "omarchy-lock-preview" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive exclusionMode: ExclusionMode.Ignore LockView { anchors.fill: parent backgroundPath: root.backgroundPath backgroundVersion: root.backgroundVersion fingerprintConfigured: root.fingerprintConfigured authenticatingPassword: false failureMessage: "" failedAttempts: 0 inputEnabled: false passwordText: "" scaleFactor: previewWindow.screen && previewWindow.screen.devicePixelRatio > 0 ? previewWindow.screen.devicePixelRatio : 1 } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: root.previewVisible = false } } PamContext { id: passwordPam config: "omarchy-lock-password" user: root.userName onResponseRequiredChanged: root.respondToPasswordPrompt() onPamMessage: root.respondToPasswordPrompt() onCompleted: function(result) { root.authenticatingPassword = false root.pendingPassword = "" if (!root.lockRequested) return if (result === PamResult.Success) root.finishUnlock() else root.handlePasswordFailure() } onError: function(error) { root.handlePasswordFailure() } } PamContext { id: fingerprintPam config: "omarchy-lock-fingerprint" user: root.userName onCompleted: function(result) { root.handleFingerprintFinished(result) } onError: function(error) { root.fingerprintAuthenticating = false if (root.lockRequested && root.fingerprintConfigured) fingerprintRetryTimer.restart() } } Timer { id: fingerprintRetryTimer interval: 250 repeat: false onTriggered: root.startFingerprint() } Process { id: readlinkProc command: ["readlink", "-f", root.currentBackgroundLink] stdout: StdioCollector { waitForEnd: true onStreamFinished: { var next = String(text || "").trim() if (next !== root.backgroundPath) { root.backgroundPath = next root.backgroundVersion += 1 } } } } Process { id: fingerprintCheckProc command: ["bash", "-lc", "if [[ -f /etc/pam.d/omarchy-lock-fingerprint ]] && command -v fprintd-list >/dev/null 2>&1 && fprintd-list \"$USER\" 2>/dev/null | grep -qi finger; then echo yes; else echo no; fi"] 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() } } Process { id: wakeProcess command: ["bash", "-lc", "omarchy-system-wake"] } Process { id: blankProcess command: ["bash", "-lc", "omarchy-brightness-keyboard off; omarchy-brightness-display off"] } Timer { id: idleBlankTimer interval: 30000 repeat: false onTriggered: if (root.lockRequested && !root.authenticating) root.runBlank() } onAuthenticatingChanged: { if (!lockRequested) return if (authenticating) idleBlankTimer.stop() else idleBlankTimer.restart() } FileView { path: "/etc/pam.d/omarchy-lock-password" watchChanges: true printErrors: false onLoaded: root.passwordPamConfigured = true onLoadFailed: root.passwordPamConfigured = false onFileChanged: reload() } Component.onCompleted: { refreshBackground() refreshFingerprintStatus() } IpcHandler { target: "lock" function lock(): string { if (!root.passwordPamConfigured) return "missing-pam" if (!root.locked && !root.beginLock()) return "failed" return "ok" } function isLocked(): string { return root.locked ? "true" : "false" } function status(): string { return JSON.stringify({ locked: root.locked, requested: root.lockRequested, sessionLocked: sessionLock.locked, secure: sessionLock.secure, passwordPam: root.passwordPamConfigured, fingerprint: root.fingerprintConfigured, authenticating: root.authenticating, lastEvent: root.lastEvent, lastEventAt: root.lastEventAt }) } function preview(): string { root.refreshBackground() root.refreshFingerprintStatus() root.previewVisible = true return "ok" } function hidePreview(): string { root.previewVisible = false return "ok" } } }