mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-02 20:28:19 +02:00
354 lines
9.5 KiB
QML
354 lines
9.5 KiB
QML
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
|
|
|
|
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 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) return false
|
|
|
|
resetAuthenticationState()
|
|
refreshBackground()
|
|
refreshFingerprintStatus()
|
|
lockRequested = true
|
|
sessionLock.locked = true
|
|
idleBlankTimer.restart()
|
|
return true
|
|
}
|
|
|
|
function finishUnlock() {
|
|
if (!root.locked && !lockRequested) return
|
|
|
|
lockRequested = false
|
|
resetAuthenticationState()
|
|
idleBlankTimer.stop()
|
|
sessionLock.locked = false
|
|
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: {
|
|
if (secure) root.startFingerprint()
|
|
}
|
|
|
|
onLockStateChanged: {
|
|
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,
|
|
secure: sessionLock.secure,
|
|
passwordPam: root.passwordPamConfigured,
|
|
fingerprint: root.fingerprintConfigured,
|
|
authenticating: root.authenticating
|
|
})
|
|
}
|
|
|
|
function preview(): string {
|
|
root.refreshBackground()
|
|
root.refreshFingerprintStatus()
|
|
root.previewVisible = true
|
|
return "ok"
|
|
}
|
|
|
|
function hidePreview(): string {
|
|
root.previewVisible = false
|
|
return "ok"
|
|
}
|
|
}
|
|
}
|