import QtCore import QtQuick import QtQuick.Effects import QtQuick.Layouts import Qt.labs.folderlistmodel import Quickshell import Quickshell.Hyprland import Quickshell.Io import Quickshell.Services.Greetd import qs.Common import qs.Services import qs.Widgets import qs.Modules.Lock Item { id: root function encodeFileUrl(path) { if (!path) return ""; return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); } readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS") property string screenName: "" property string hyprlandCurrentLayout: "" property string hyprlandKeyboard: "" property int hyprlandLayoutCount: 0 property bool isPrimaryScreen: !Quickshell.screens?.length || screenName === Quickshell.screens[0]?.name signal launchRequested property bool weatherInitialized: false property bool awaitingExternalAuth: false property bool pendingPasswordResponse: false property bool passwordSubmitRequested: false property bool cancelingExternalAuthForPassword: false property int defaultAuthTimeoutMs: 10000 property int externalAuthTimeoutMs: 30000 property int memoryFlushDelayMs: 120 property string pendingLaunchCommand: "" property var pendingLaunchEnv: [] property int passwordFailureCount: 0 property int passwordAttemptLimitHint: 0 property string authFeedbackMessage: "" property string greetdPamText: "" property string systemAuthPamText: "" property string commonAuthPamText: "" property string passwordAuthPamText: "" property string systemLoginPamText: "" property string systemLocalLoginPamText: "" property string commonAuthPcPamText: "" property string loginPamText: "" property string faillockConfigText: "" property bool greeterWallpaperOverrideExists: false property string externalAuthAutoStartedForUser: "" property int passwordSessionTransitionRetryCount: 0 property int maxPasswordSessionTransitionRetries: 2 property bool fprintdProbeComplete: false property bool fprintdHasDevice: false // Falls back to PAM-only detection until the fprintd D-Bus probe completes. readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd") && (!fprintdProbeComplete || fprintdHasDevice) readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f") readonly property bool greeterExternalAuthAvailable: (greeterPamHasFprint && GreetdSettings.greeterEnableFprint) || (greeterPamHasU2f && GreetdSettings.greeterEnableU2f) readonly property bool greeterPamHasExternalAuth: greeterPamHasFprint || greeterPamHasU2f function initWeatherService() { if (weatherInitialized) return; if (!GreetdSettings.settingsLoaded) return; if (!GreetdSettings.weatherEnabled) return; weatherInitialized = true; WeatherService.addRef(); WeatherService.forceRefresh(); } function stripPamComment(line) { if (!line) return ""; const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) return ""; const hashIdx = trimmed.indexOf("#"); if (hashIdx >= 0) return trimmed.substring(0, hashIdx).trim(); return trimmed; } function pamModuleEnabled(pamText, moduleName) { if (!pamText || !moduleName) return false; const lines = pamText.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const line = stripPamComment(lines[i]); if (!line) continue; if (line.includes(moduleName)) return true; } return false; } function pamTextIncludesFile(pamText, filename) { if (!pamText || !filename) return false; const lines = pamText.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const line = stripPamComment(lines[i]); if (!line) continue; if (line.includes(filename) && (line.includes("include") || line.includes("substack") || line.startsWith("@include"))) return true; } return false; } function greeterPamStackHasModule(moduleName) { if (pamModuleEnabled(greetdPamText, moduleName)) return true; const includedPamStacks = [ ["system-auth", systemAuthPamText], ["common-auth", commonAuthPamText], ["password-auth", passwordAuthPamText], ["system-login", systemLoginPamText], ["system-local-login", systemLocalLoginPamText], ["common-auth-pc", commonAuthPcPamText], ["login", loginPamText] ]; for (let i = 0; i < includedPamStacks.length; i++) { const stack = includedPamStacks[i]; if (pamTextIncludesFile(greetdPamText, stack[0]) && pamModuleEnabled(stack[1], moduleName)) return true; } return false; } function usesPamLockoutPolicy(pamText) { if (!pamText) return false; const lines = pamText.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const line = stripPamComment(lines[i]); if (!line) continue; if (line.includes("pam_faillock.so") || line.includes("pam_tally2.so") || line.includes("pam_tally.so")) return true; } return false; } function parsePamLineDenyValue(pamText) { if (!pamText) return -1; const lines = pamText.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const line = stripPamComment(lines[i]); if (!line) continue; if (!line.includes("pam_faillock.so") && !line.includes("pam_tally2.so") && !line.includes("pam_tally.so")) continue; const denyMatch = line.match(/\bdeny\s*=\s*(\d+)\b/i); if (!denyMatch) continue; const parsed = parseInt(denyMatch[1], 10); if (!isNaN(parsed)) return parsed; } return -1; } function parseFaillockDenyValue(configText) { if (!configText) return -1; const lines = configText.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const line = stripPamComment(lines[i]); if (!line) continue; const denyMatch = line.match(/^deny\s*=\s*(\d+)\s*$/i); if (!denyMatch) continue; const parsed = parseInt(denyMatch[1], 10); if (!isNaN(parsed)) return parsed; } return -1; } function refreshPasswordAttemptPolicyHint() { const pamSources = [greetdPamText, systemAuthPamText, commonAuthPamText, passwordAuthPamText, systemLoginPamText, systemLocalLoginPamText, commonAuthPcPamText, loginPamText]; let lockoutConfigured = false; let denyFromPam = -1; for (let i = 0; i < pamSources.length; i++) { const source = pamSources[i]; if (!source) continue; if (usesPamLockoutPolicy(source)) lockoutConfigured = true; const denyValue = parsePamLineDenyValue(source); if (denyValue >= 0 && (denyFromPam < 0 || denyValue < denyFromPam)) denyFromPam = denyValue; } if (!lockoutConfigured) { passwordAttemptLimitHint = 0; return; } const denyFromConfig = parseFaillockDenyValue(faillockConfigText); if (denyFromConfig >= 0) { passwordAttemptLimitHint = denyFromConfig; return; } if (denyFromPam >= 0) { passwordAttemptLimitHint = denyFromPam; return; } // pam_faillock default deny value when no explicit config is set. passwordAttemptLimitHint = 3; } function isLikelyLockoutMessage(message) { const lower = (message || "").toLowerCase(); return lower.includes("account is locked") || lower.includes("too many") || lower.includes("maximum number of") || lower.includes("auth_err"); } function currentAuthMessage() { if (GreeterState.pamState === "error") return "Authentication error - try again"; if (GreeterState.pamState === "max") return "Too many failed attempts - account may be locked"; if (GreeterState.pamState === "fail") { if (passwordAttemptLimitHint > 0) { const attempt = Math.max(1, Math.min(passwordFailureCount, passwordAttemptLimitHint)); const remaining = Math.max(passwordAttemptLimitHint - attempt, 0); if (remaining > 0) { return "Incorrect password - attempt " + attempt + " of " + passwordAttemptLimitHint + " (lockout may follow)"; } return "Incorrect password - next failures may trigger account lockout"; } return "Incorrect password"; } return ""; } function clearAuthFeedback() { GreeterState.pamState = ""; authFeedbackMessage = ""; } function resetPasswordSessionTransition(clearSubmitRequest) { cancelingExternalAuthForPassword = false; passwordSessionTransitionRetryCount = 0; if (clearSubmitRequest) passwordSubmitRequested = false; } Connections { target: GreetdSettings function onSettingsLoadedChanged() { if (GreetdSettings.settingsLoaded) { initWeatherService(); if (isPrimaryScreen) { applyLastSuccessfulUser(); finalizeSessionSelection(); } } } function onRememberLastUserChanged() { if (!isPrimaryScreen) return; if (!GreetdSettings.rememberLastUser && GreetdMemory.lastSuccessfulUser) { GreetdMemory.setLastSuccessfulUser(""); } applyLastSuccessfulUser(); } function onRememberLastSessionChanged() { if (!isPrimaryScreen) return; if (!GreetdSettings.rememberLastSession && GreetdMemory.lastSessionId) { GreetdMemory.setLastSessionId(""); } finalizeSessionSelection(); } } FileView { id: greetdPamWatcher path: "/etc/pam.d/greetd" printErrors: false onLoaded: { root.greetdPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.greetdPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: systemAuthPamWatcher path: "/etc/pam.d/system-auth" printErrors: false onLoaded: { root.systemAuthPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.systemAuthPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: commonAuthPamWatcher path: "/etc/pam.d/common-auth" printErrors: false onLoaded: { root.commonAuthPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.commonAuthPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: passwordAuthPamWatcher path: "/etc/pam.d/password-auth" printErrors: false onLoaded: { root.passwordAuthPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.passwordAuthPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: systemLoginPamWatcher path: "/etc/pam.d/system-login" printErrors: false onLoaded: { root.systemLoginPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.systemLoginPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: systemLocalLoginPamWatcher path: "/etc/pam.d/system-local-login" printErrors: false onLoaded: { root.systemLocalLoginPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.systemLocalLoginPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: commonAuthPcPamWatcher path: "/etc/pam.d/common-auth-pc" printErrors: false onLoaded: { root.commonAuthPcPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.commonAuthPcPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: loginPamWatcher path: "/etc/pam.d/login" printErrors: false onLoaded: { root.loginPamText = text(); root.refreshPasswordAttemptPolicyHint(); root.maybeAutoStartExternalAuth(); } onLoadFailed: { root.loginPamText = ""; root.refreshPasswordAttemptPolicyHint(); } } FileView { id: faillockConfigWatcher path: "/etc/security/faillock.conf" printErrors: false onLoaded: { root.faillockConfigText = text(); root.refreshPasswordAttemptPolicyHint(); } onLoadFailed: { root.faillockConfigText = ""; root.refreshPasswordAttemptPolicyHint(); } } Component.onCompleted: { initWeatherService(); refreshPasswordAttemptPolicyHint(); if (isPrimaryScreen) applyLastSuccessfulUser(); if (CompositorService.isHyprland) updateHyprlandLayout(); fprintdDeviceProbe.running = true; } function applyLastSuccessfulUser() { if (!GreetdSettings.settingsLoaded || !GreetdSettings.rememberLastUser) return; const lastUser = GreetdMemory.lastSuccessfulUser; if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) { GreeterState.username = lastUser; GreeterState.usernameInput = lastUser; GreeterState.showPasswordInput = true; PortalService.getGreeterUserProfileImage(lastUser); maybeAutoStartExternalAuth(); } } function submitUsername(rawValue) { const user = (rawValue || "").trim(); if (!user) return; if (GreeterState.username !== user) { passwordFailureCount = 0; clearAuthFeedback(); externalAuthAutoStartedForUser = ""; } GreeterState.username = user; GreeterState.showPasswordInput = true; PortalService.getGreeterUserProfileImage(user); GreeterState.passwordBuffer = ""; pendingPasswordResponse = false; resetPasswordSessionTransition(true); maybeAutoStartExternalAuth(); } function submitBufferedPassword() { pendingPasswordResponse = false; resetPasswordSessionTransition(true); awaitingExternalAuth = false; authTimeout.interval = defaultAuthTimeoutMs; authTimeout.restart(); // Some PAM stacks expect an explicit empty response to advance U2F/fprint or fail normally. Greetd.respond(GreeterState.passwordBuffer || ""); GreeterState.passwordBuffer = ""; inputField.text = ""; return true; } function requestPasswordSessionTransition() { const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0; if (!passwordSubmitRequested && !hasPasswordBuffer) return; if (cancelingExternalAuthForPassword) return; if (passwordSessionTransitionRetryCount >= maxPasswordSessionTransitionRetries) { pendingPasswordResponse = false; awaitingExternalAuth = false; authTimeout.interval = defaultAuthTimeoutMs; authTimeout.stop(); resetPasswordSessionTransition(true); GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); placeholderDelay.restart(); Greetd.cancelSession(); return; } cancelingExternalAuthForPassword = true; passwordSessionTransitionRetryCount = passwordSessionTransitionRetryCount + 1; awaitingExternalAuth = false; pendingPasswordResponse = false; authTimeout.interval = defaultAuthTimeoutMs; authTimeout.stop(); Greetd.cancelSession(); } function startAuthSession(submitPassword) { submitPassword = submitPassword === true; if (!GreeterState.showPasswordInput || !GreeterState.username) return; if (GreeterState.unlocking) return; const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0; if (Greetd.state !== GreetdState.Inactive) { if (pendingPasswordResponse && submitPassword) submitBufferedPassword(); else if (submitPassword) passwordSubmitRequested = true; return; } if (cancelingExternalAuthForPassword) { if (submitPassword) passwordSubmitRequested = true; return; } if (!submitPassword && !hasPasswordBuffer && !root.greeterExternalAuthAvailable) return; pendingPasswordResponse = false; passwordSubmitRequested = submitPassword; awaitingExternalAuth = !submitPassword && !hasPasswordBuffer && root.greeterExternalAuthAvailable; // Use greeterExternalAuthAvailable so systems with pam_fprintd but no hardware don't incur the 30 s wait. const waitingOnPamExternalBeforePassword = submitPassword && root.greeterExternalAuthAvailable; authTimeout.interval = (awaitingExternalAuth || waitingOnPamExternalBeforePassword) ? externalAuthTimeoutMs : defaultAuthTimeoutMs; authTimeout.restart(); Greetd.createSession(GreeterState.username); } function maybeAutoStartExternalAuth() { if (!GreeterState.showPasswordInput || !GreeterState.username) return; if (!root.greeterExternalAuthAvailable) return; if (GreeterState.unlocking || Greetd.state !== GreetdState.Inactive) return; if (passwordSubmitRequested || cancelingExternalAuthForPassword) return; if (GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0) return; if (externalAuthAutoStartedForUser === GreeterState.username) return; externalAuthAutoStartedForUser = GreeterState.username; startAuthSession(false); } function isExternalAuthPrompt(message, responseRequired) { // Non-response PAM messages commonly represent waiting states (fprint/U2F/token touch). return !responseRequired; } Component.onDestruction: { if (weatherInitialized) WeatherService.removeRef(); } function updateHyprlandLayout() { if (CompositorService.isHyprland) { hyprlandLayoutProcess.running = true; } } Process { id: hyprlandLayoutProcess running: false command: ["hyprctl", "-j", "devices"] stdout: StdioCollector { onStreamFinished: { try { const data = JSON.parse(text); const mainKeyboard = data.keyboards.find(kb => kb.main === true); if (!mainKeyboard) { hyprlandCurrentLayout = ""; hyprlandLayoutCount = 0; return; } hyprlandKeyboard = mainKeyboard.name; if (mainKeyboard.active_keymap) { const parts = mainKeyboard.active_keymap.split(" "); hyprlandCurrentLayout = parts[0].substring(0, 2).toUpperCase(); } else { hyprlandCurrentLayout = ""; } hyprlandLayoutCount = mainKeyboard.layout ? mainKeyboard.layout.split(",").length : 0; } catch (e) { hyprlandCurrentLayout = ""; hyprlandLayoutCount = 0; } } } } // Probe fprintd D-Bus for physically enrolled scanners to eliminate PAM stack false-positives. Process { id: fprintdDeviceProbe running: false // sh wrapper: emits PROBE_UNAVAILABLE if gdbus is absent or fprintd unreachable, // keeping the PAM-only fallback active in those cases. command: ["sh", "-c", "command -v gdbus >/dev/null 2>&1 || { echo PROBE_UNAVAILABLE; exit 0; }; " + "gdbus call --system " + "--dest net.reactivated.Fprint " + "--object-path /net/reactivated/Fprint/Manager " + "--method net.reactivated.Fprint.Manager.GetDevices 2>/dev/null " + "|| echo PROBE_UNAVAILABLE"] stdout: StdioCollector { onStreamFinished: { if (text.includes("PROBE_UNAVAILABLE")) return; // PAM-only fallback stays active root.fprintdHasDevice = text.includes("objectpath"); root.fprintdProbeComplete = true; root.maybeAutoStartExternalAuth(); } } onExited: function(exitCode, exitStatus) { if (!root.fprintdProbeComplete) root.maybeAutoStartExternalAuth(); // PAM-only fallback stays active } } Connections { target: CompositorService.isHyprland ? Hyprland : null enabled: CompositorService.isHyprland function onRawEvent(event) { if (event.name === "activelayout") updateHyprlandLayout(); } } Connections { target: GreetdMemory enabled: isPrimaryScreen function onLastSuccessfulUserChanged() { applyLastSuccessfulUser(); } function onMemoryReadyChanged() { finalizeSessionSelection(); } } Connections { target: GreeterState function onUsernameChanged() { if (GreeterState.username) { PortalService.getGreeterUserProfileImage(GreeterState.username); } } } FileView { id: greeterWallpaperOverrideFile path: GreetdSettings.greeterWallpaperOverridePath printErrors: false watchChanges: true onLoaded: root.greeterWallpaperOverrideExists = true onLoadFailed: root.greeterWallpaperOverrideExists = false } Connections { target: GreetdSettings function onGreeterWallpaperOverridePathChanged() { if (!GreetdSettings.greeterWallpaperOverridePath) { root.greeterWallpaperOverrideExists = false; return; } greeterWallpaperOverrideFile.reload(); } function onGreeterWallpaperPathChanged() { if (!GreetdSettings.greeterWallpaperPath) { root.greeterWallpaperOverrideExists = false; return; } greeterWallpaperOverrideFile.reload(); } } DankBackdrop { anchors.fill: parent screenName: root.screenName visible: { if (GreetdSettings.greeterWallpaperPath !== "" && root.greeterWallpaperOverrideExists) return false; var _ = SessionData.perMonitorWallpaper; var __ = SessionData.monitorWallpapers; var currentWallpaper = SessionData.getMonitorWallpaper(screenName); return !currentWallpaper || currentWallpaper === "" || (currentWallpaper && currentWallpaper.startsWith("#")); } } Image { id: wallpaperBackground anchors.fill: parent source: { if (GreetdSettings.greeterWallpaperPath !== "" && root.greeterWallpaperOverrideExists) return encodeFileUrl(GreetdSettings.greeterWallpaperOverridePath); var _ = SessionData.perMonitorWallpaper; var __ = SessionData.monitorWallpapers; var currentWallpaper = SessionData.getMonitorWallpaper(screenName); return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : ""; } fillMode: Theme.getFillMode(GreetdSettings.getEffectiveWallpaperFillMode()) smooth: true asynchronous: false cache: true visible: source !== "" layer.enabled: true layer.effect: MultiEffect { autoPaddingEnabled: false blurEnabled: true blur: 0.8 blurMax: 32 blurMultiplier: 1 } Behavior on opacity { NumberAnimation { duration: Theme.mediumDuration easing.type: Theme.standardEasing } } } Rectangle { anchors.fill: parent color: "black" opacity: 0.4 } SystemClock { id: systemClock precision: SystemClock.Seconds } Rectangle { anchors.fill: parent color: "transparent" Item { id: clockContainer anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.verticalCenter anchors.bottomMargin: 60 width: parent.width height: clockText.implicitHeight Row { id: clockText anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top spacing: 0 property string fullTimeStr: { const format = GreetdSettings.getEffectiveTimeFormat(); return systemClock.date.toLocaleTimeString(I18n.locale(), format); } property var timeParts: fullTimeStr.split(':') property string hours: timeParts[0] || "" property string minutes: timeParts[1] || "" property string secondsWithAmPm: timeParts.length > 2 ? timeParts[2] : "" property string seconds: secondsWithAmPm.replace(/\s*(AM|PM|am|pm)$/i, '') property string ampm: { const match = fullTimeStr.match(/\s*(AM|PM|am|pm)$/i); return match ? match[0].trim() : ""; } property bool hasSeconds: timeParts.length > 2 StyledText { width: 75 text: clockText.hours.length > 1 ? clockText.hours[0] : "" font.pixelSize: 120 font.weight: Font.Light color: "white" horizontalAlignment: Text.AlignHCenter } StyledText { width: 75 text: clockText.hours.length > 1 ? clockText.hours[1] : clockText.hours.length > 0 ? clockText.hours[0] : "" font.pixelSize: 120 font.weight: Font.Light color: "white" horizontalAlignment: Text.AlignHCenter } StyledText { text: ":" font.pixelSize: 120 font.weight: Font.Light color: "white" } StyledText { width: 75 text: clockText.minutes.length > 0 ? clockText.minutes[0] : "" font.pixelSize: 120 font.weight: Font.Light color: "white" horizontalAlignment: Text.AlignHCenter } StyledText { width: 75 text: clockText.minutes.length > 1 ? clockText.minutes[1] : "" font.pixelSize: 120 font.weight: Font.Light color: "white" horizontalAlignment: Text.AlignHCenter } StyledText { text: clockText.hasSeconds ? ":" : "" font.pixelSize: 120 font.weight: Font.Light color: "white" visible: clockText.hasSeconds } StyledText { width: 75 text: clockText.hasSeconds && clockText.seconds.length > 0 ? clockText.seconds[0] : "" font.pixelSize: 120 font.weight: Font.Light color: "white" horizontalAlignment: Text.AlignHCenter visible: clockText.hasSeconds } StyledText { width: 75 text: clockText.hasSeconds && clockText.seconds.length > 1 ? clockText.seconds[1] : "" font.pixelSize: 120 font.weight: Font.Light color: "white" horizontalAlignment: Text.AlignHCenter visible: clockText.hasSeconds } StyledText { width: 20 text: " " font.pixelSize: 120 font.weight: Font.Light color: "white" visible: clockText.ampm !== "" } StyledText { text: clockText.ampm font.pixelSize: 120 font.weight: Font.Light color: "white" visible: clockText.ampm !== "" } } } StyledText { id: dateText anchors.horizontalCenter: parent.horizontalCenter anchors.top: clockContainer.bottom anchors.topMargin: 4 text: { return systemClock.date.toLocaleDateString(I18n.locale(), GreetdSettings.getEffectiveLockDateFormat()); } font.pixelSize: Theme.fontSizeXLarge color: "white" opacity: 0.9 } Item { anchors.horizontalCenter: parent.horizontalCenter anchors.top: dateText.bottom anchors.topMargin: Theme.spacingL width: 380 height: 140 ColumnLayout { anchors.fill: parent spacing: Theme.spacingM RowLayout { spacing: Theme.spacingL Layout.fillWidth: true DankCircularImage { Layout.preferredWidth: 60 Layout.preferredHeight: 60 imageSource: { if (PortalService.profileImage === "") return ""; if (PortalService.profileImage.startsWith("/")) return encodeFileUrl(PortalService.profileImage); return PortalService.profileImage; } fallbackIcon: "person" visible: GreetdSettings.lockScreenShowProfileImage } Rectangle { property bool showPassword: false Layout.fillWidth: true Layout.preferredHeight: 60 radius: Theme.cornerRadius color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9) border.color: inputField.activeFocus ? Theme.primary : Qt.rgba(1, 1, 1, 0.3) border.width: inputField.activeFocus ? 2 : 1 DankIcon { id: lockIcon anchors.left: parent.left anchors.leftMargin: Theme.spacingM anchors.verticalCenter: parent.verticalCenter name: GreeterState.showPasswordInput ? "lock" : "person" size: 20 color: inputField.activeFocus ? Theme.primary : Theme.surfaceVariantText } TextInput { id: inputField property bool syncingFromState: false anchors.fill: parent anchors.leftMargin: lockIcon.width + Theme.spacingM * 2 anchors.rightMargin: { let margin = Theme.spacingM; if (GreeterState.showPasswordInput && revealButton.visible) { margin += revealButton.width; } if (externalAuthButton.visible) { margin += externalAuthButton.width; } if (virtualKeyboardButton.visible) { margin += virtualKeyboardButton.width; } if (enterButton.visible) { margin += enterButton.width + 2; } return margin; } opacity: 0 focus: true echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal onTextChanged: { if (syncingFromState) return; if (GreeterState.showPasswordInput) { GreeterState.passwordBuffer = text; if (!text || text.length === 0) root.passwordSubmitRequested = false; } else { GreeterState.usernameInput = text; } } onAccepted: { if (GreeterState.showPasswordInput) { root.startAuthSession(true); } else { if (text.trim()) { root.submitUsername(text); syncingFromState = true; text = ""; syncingFromState = false; } } } Component.onCompleted: { syncingFromState = true; text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput; syncingFromState = false; if (isPrimaryScreen && !powerMenu.isVisible) forceActiveFocus(); } onVisibleChanged: { if (visible && isPrimaryScreen && !powerMenu.isVisible) forceActiveFocus(); } } KeyboardController { id: keyboard_controller target: inputField rootObject: root } StyledText { id: placeholder anchors.left: lockIcon.right anchors.leftMargin: Theme.spacingM anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))) anchors.rightMargin: 2 anchors.verticalCenter: parent.verticalCenter text: { if (GreeterState.unlocking) { return "Logging in..."; } if (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse) { return "Authenticating..."; } if (GreeterState.showPasswordInput) { return "Password..."; } return "Username..."; } color: (GreeterState.unlocking || (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse)) ? Theme.primary : Theme.outline font.pixelSize: Theme.fontSizeMedium opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length === 0 : GreeterState.usernameInput.length === 0) ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Theme.mediumDuration easing.type: Theme.standardEasing } } Behavior on color { ColorAnimation { duration: Theme.shortDuration easing.type: Theme.standardEasing } } } StyledText { anchors.left: lockIcon.right anchors.leftMargin: Theme.spacingM anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))) anchors.rightMargin: 2 anchors.verticalCenter: parent.verticalCenter text: { if (GreeterState.showPasswordInput) { if (parent.showPassword) { return GreeterState.passwordBuffer; } return "•".repeat(GreeterState.passwordBuffer.length); } return GreeterState.usernameInput; } color: Theme.surfaceText font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length > 0 : GreeterState.usernameInput.length > 0) ? 1 : 0 clip: true elide: Text.ElideNone horizontalAlignment: implicitWidth > width ? Text.AlignRight : Text.AlignLeft Behavior on opacity { NumberAnimation { duration: Theme.mediumDuration easing.type: Theme.standardEasing } } } DankActionButton { id: revealButton anchors.right: externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)) anchors.rightMargin: 0 anchors.verticalCenter: parent.verticalCenter iconName: parent.showPassword ? "visibility_off" : "visibility" buttonSize: 32 visible: GreeterState.showPasswordInput && GreeterState.passwordBuffer.length > 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking enabled: visible onClicked: parent.showPassword = !parent.showPassword } DankActionButton { id: externalAuthButton anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right) anchors.rightMargin: 0 anchors.verticalCenter: parent.verticalCenter iconName: root.greeterPamHasFprint ? "fingerprint" : "key" buttonSize: 32 visible: GreeterState.showPasswordInput && root.greeterExternalAuthAvailable && GreeterState.passwordBuffer.length === 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking enabled: visible onClicked: root.startAuthSession(false) } DankActionButton { id: virtualKeyboardButton anchors.right: enterButton.visible ? enterButton.left : parent.right anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS anchors.verticalCenter: parent.verticalCenter iconName: "keyboard" buttonSize: 32 visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking enabled: visible onClicked: { if (keyboard_controller.isKeyboardActive) { keyboard_controller.hide(); } else { keyboard_controller.show(); } } } DankActionButton { id: enterButton anchors.right: parent.right anchors.rightMargin: 2 anchors.verticalCenter: parent.verticalCenter iconName: "keyboard_return" buttonSize: 36 visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking enabled: true onClicked: { if (GreeterState.showPasswordInput) { root.startAuthSession(true); } else { if (inputField.text.trim()) { root.submitUsername(inputField.text); inputField.text = ""; } } } Behavior on opacity { NumberAnimation { duration: Theme.shortDuration easing.type: Theme.standardEasing } } } Behavior on border.color { ColorAnimation { duration: Theme.shortDuration easing.type: Theme.standardEasing } } } } StyledText { Layout.fillWidth: true Layout.preferredHeight: 38 Layout.topMargin: -Theme.spacingS Layout.bottomMargin: -Theme.spacingS text: root.authFeedbackMessage color: Theme.error font.pixelSize: Theme.fontSizeSmall horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap maximumLineCount: 2 opacity: root.authFeedbackMessage !== "" ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Theme.shortDuration easing.type: Theme.standardEasing } } } Rectangle { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 0 Layout.preferredWidth: switchUserRow.width + Theme.spacingL * 2 Layout.preferredHeight: 40 radius: Theme.cornerRadius color: Theme.surfaceContainer opacity: GreeterState.showPasswordInput ? 1 : 0 enabled: GreeterState.showPasswordInput Behavior on opacity { NumberAnimation { duration: Theme.mediumDuration easing.type: Theme.standardEasing } } Row { id: switchUserRow anchors.centerIn: parent spacing: Theme.spacingS DankIcon { name: "people" size: Theme.iconSize - 4 color: Theme.surfaceText anchors.verticalCenter: parent.verticalCenter } StyledText { text: I18n.tr("Switch User") font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceText anchors.verticalCenter: parent.verticalCenter } } StateLayer { stateColor: Theme.primary cornerRadius: parent.radius enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput onClicked: { GreeterState.reset(); root.externalAuthAutoStartedForUser = ""; inputField.text = ""; PortalService.profileImage = ""; } } } } } Row { anchors.top: parent.top anchors.right: parent.right anchors.margins: Theme.spacingXL spacing: Theme.spacingL Item { width: keyboardLayoutRow.width height: keyboardLayoutRow.height anchors.verticalCenter: parent.verticalCenter visible: { if (CompositorService.isNiri) { return NiriService.keyboardLayoutNames.length > 1; } else if (CompositorService.isHyprland) { return hyprlandLayoutCount > 1; } return false; } Row { id: keyboardLayoutRow spacing: 4 Item { width: Theme.iconSize height: Theme.iconSize DankIcon { name: "keyboard" size: Theme.iconSize color: "white" anchors.centerIn: parent } } Item { width: childrenRect.width height: Theme.iconSize StyledText { text: { if (CompositorService.isNiri) { const layout = NiriService.getCurrentKeyboardLayoutName(); if (!layout) return ""; const parts = layout.split(" "); if (parts.length > 0) { return parts[0].substring(0, 2).toUpperCase(); } return layout.substring(0, 2).toUpperCase(); } else if (CompositorService.isHyprland) { return hyprlandCurrentLayout; } return ""; } font.pixelSize: Theme.fontSizeMedium font.weight: Font.Light color: "white" anchors.verticalCenter: parent.verticalCenter } } } MouseArea { id: keyboardLayoutArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { if (CompositorService.isNiri) { NiriService.cycleKeyboardLayout(); } else if (CompositorService.isHyprland) { Quickshell.execDetached(["hyprctl", "switchxkblayout", hyprlandKeyboard, "next"]); updateHyprlandLayout(); } } } } Rectangle { width: 1 height: 24 color: Qt.rgba(255, 255, 255, 0.2) anchors.verticalCenter: parent.verticalCenter visible: { const keyboardVisible = (CompositorService.isNiri && NiriService.keyboardLayoutNames.length > 1) || (CompositorService.isHyprland && hyprlandLayoutCount > 1); return keyboardVisible && GreetdSettings.weatherEnabled && WeatherService.weather.available; } } Row { spacing: 6 visible: GreetdSettings.weatherEnabled && WeatherService.weather.available anchors.verticalCenter: parent.verticalCenter DankIcon { name: WeatherService.getWeatherIcon(WeatherService.weather.wCode) size: Theme.iconSize color: "white" anchors.verticalCenter: parent.verticalCenter } StyledText { text: (GreetdSettings.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" font.pixelSize: Theme.fontSizeLarge font.weight: Font.Light color: "white" anchors.verticalCenter: parent.verticalCenter } } Rectangle { width: 1 height: 24 color: Qt.rgba(255, 255, 255, 0.2) anchors.verticalCenter: parent.verticalCenter visible: GreetdSettings.weatherEnabled && WeatherService.weather.available && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio) || BatteryService.batteryAvailable) } Row { spacing: Theme.spacingM anchors.verticalCenter: parent.verticalCenter visible: NetworkService.networkStatus !== "disconnected" || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio) DankIcon { name: NetworkService.networkStatus === "ethernet" ? "lan" : NetworkService.wifiSignalIcon size: Theme.iconSize - 2 color: NetworkService.networkStatus !== "disconnected" ? "white" : Qt.rgba(255, 255, 255, 0.5) anchors.verticalCenter: parent.verticalCenter visible: NetworkService.networkStatus !== "disconnected" } DankIcon { name: "bluetooth" size: Theme.iconSize - 2 color: "white" anchors.verticalCenter: parent.verticalCenter visible: BluetoothService.available && BluetoothService.enabled } DankIcon { name: { if (!AudioService.sink?.audio) { return "volume_up"; } if (AudioService.sink.audio.muted) return "volume_off"; if (AudioService.sink.audio.volume === 0) return "volume_mute"; if (AudioService.sink.audio.volume * 100 < 33) { return "volume_down"; } return "volume_up"; } size: Theme.iconSize - 2 color: (AudioService.sink && AudioService.sink.audio && (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)) ? Qt.rgba(255, 255, 255, 0.5) : "white" anchors.verticalCenter: parent.verticalCenter visible: AudioService.sink && AudioService.sink.audio } } Rectangle { width: 1 height: 24 color: Qt.rgba(255, 255, 255, 0.2) anchors.verticalCenter: parent.verticalCenter visible: BatteryService.batteryAvailable && (NetworkService.networkStatus !== "disconnected" || BluetoothService.enabled || (AudioService.sink && AudioService.sink.audio)) } Row { spacing: 4 visible: BatteryService.batteryAvailable anchors.verticalCenter: parent.verticalCenter DankIcon { name: { if (BatteryService.isCharging) { if (BatteryService.batteryLevel >= 90) { return "battery_charging_full"; } if (BatteryService.batteryLevel >= 80) { return "battery_charging_90"; } if (BatteryService.batteryLevel >= 60) { return "battery_charging_80"; } if (BatteryService.batteryLevel >= 50) { return "battery_charging_60"; } if (BatteryService.batteryLevel >= 30) { return "battery_charging_50"; } if (BatteryService.batteryLevel >= 20) { return "battery_charging_30"; } return "battery_charging_20"; } if (BatteryService.isPluggedIn) { if (BatteryService.batteryLevel >= 90) { return "battery_charging_full"; } if (BatteryService.batteryLevel >= 80) { return "battery_charging_90"; } if (BatteryService.batteryLevel >= 60) { return "battery_charging_80"; } if (BatteryService.batteryLevel >= 50) { return "battery_charging_60"; } if (BatteryService.batteryLevel >= 30) { return "battery_charging_50"; } if (BatteryService.batteryLevel >= 20) { return "battery_charging_30"; } return "battery_charging_20"; } if (BatteryService.batteryLevel >= 95) { return "battery_full"; } if (BatteryService.batteryLevel >= 85) { return "battery_6_bar"; } if (BatteryService.batteryLevel >= 70) { return "battery_5_bar"; } if (BatteryService.batteryLevel >= 55) { return "battery_4_bar"; } if (BatteryService.batteryLevel >= 40) { return "battery_3_bar"; } if (BatteryService.batteryLevel >= 25) { return "battery_2_bar"; } return "battery_1_bar"; } size: Theme.iconSize color: { if (BatteryService.isLowBattery && !BatteryService.isCharging) { return Theme.error; } if (BatteryService.isCharging || BatteryService.isPluggedIn) { return Theme.primary; } return "white"; } anchors.verticalCenter: parent.verticalCenter } StyledText { text: BatteryService.batteryLevel + "%" font.pixelSize: Theme.fontSizeLarge font.weight: Font.Light color: "white" anchors.verticalCenter: parent.verticalCenter } } } DankActionButton { anchors.bottom: parent.bottom anchors.left: parent.left anchors.margins: Theme.spacingXL visible: GreetdSettings.lockScreenShowPowerActions iconName: "power_settings_new" iconColor: Theme.error buttonSize: 40 onClicked: powerMenu.show() } Item { anchors.bottom: parent.bottom anchors.right: parent.right anchors.margins: Theme.spacingXL width: Math.max(200, currentSessionMetrics.width + 80) height: 60 StyledTextMetrics { id: currentSessionMetrics text: root.currentSessionName } property real longestSessionWidth: { let maxWidth = 0; for (var i = 0; i < sessionMetricsRepeater.count; i++) { const item = sessionMetricsRepeater.itemAt(i); if (item && item.width > maxWidth) { maxWidth = item.width; } } return maxWidth; } Repeater { id: sessionMetricsRepeater model: GreeterState.sessionList delegate: StyledTextMetrics { text: modelData } } DankDropdown { id: sessionDropdown anchors.fill: parent text: "" description: "" currentValue: root.currentSessionName options: GreeterState.sessionList enableFuzzySearch: GreeterState.sessionList.length > 5 popupWidthOffset: 0 popupWidth: Math.max(250, parent.longestSessionWidth + 100) openUpwards: true alignPopupRight: true onValueChanged: value => { const idx = GreeterState.sessionList.indexOf(value); if (idx < 0) return; GreeterState.currentSessionIndex = idx; GreeterState.selectedSession = GreeterState.sessionExecs[idx]; GreeterState.selectedSessionPath = GreeterState.sessionPaths[idx]; } } } } property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || "" function finalizeSessionSelection() { if (GreeterState.sessionList.length === 0) return; if (!GreetdMemory.memoryReady) return; if (!GreetdSettings.settingsLoaded) return; const savedSession = GreetdSettings.rememberLastSession ? GreetdMemory.lastSessionId : ""; if (savedSession && GreetdSettings.rememberLastSession) { for (var i = 0; i < GreeterState.sessionPaths.length; i++) { if (GreeterState.sessionPaths[i] === savedSession) { GreeterState.currentSessionIndex = i; GreeterState.selectedSession = GreeterState.sessionExecs[i] || ""; GreeterState.selectedSessionPath = GreeterState.sessionPaths[i]; return; } } } GreeterState.currentSessionIndex = 0; GreeterState.selectedSession = GreeterState.sessionExecs[0] || ""; GreeterState.selectedSessionPath = GreeterState.sessionPaths[0] || ""; } property var sessionDirs: { const homeDir = Quickshell.env("HOME") || ""; const dirs = ["/usr/share/wayland-sessions", "/usr/share/xsessions", "/usr/local/share/wayland-sessions", "/usr/local/share/xsessions"]; if (homeDir) { dirs.push(homeDir + "/.local/share/wayland-sessions"); dirs.push(homeDir + "/.local/share/xsessions"); } if (xdgDataDirs) { xdgDataDirs.split(":").forEach(dir => { if (dir) { dirs.push(dir + "/wayland-sessions"); dirs.push(dir + "/xsessions"); } }); } return dirs; } property var _pendingFiles: ({}) property int _pendingCount: 0 function _addSession(path, name, exec) { if (!name || !exec || GreeterState.sessionList.includes(name)) return; GreeterState.sessionList = GreeterState.sessionList.concat([name]); GreeterState.sessionExecs = GreeterState.sessionExecs.concat([exec]); GreeterState.sessionPaths = GreeterState.sessionPaths.concat([path]); } function _parseDesktopFile(content, path) { let name = ""; let exec = ""; const lines = content.split("\n"); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (!name && line.startsWith("Name=")) name = line.substring(5).trim(); else if (!exec && line.startsWith("Exec=")) exec = line.substring(5).trim(); if (name && exec) break; } _addSession(path, name, exec); } function _loadDesktopFile(filePath) { if (_pendingFiles[filePath]) return; _pendingFiles[filePath] = true; _pendingCount++; const loader = desktopFileLoader.createObject(root, { "filePath": filePath }); } function _onFileLoaded(filePath) { _pendingCount--; if (_pendingCount === 0) Qt.callLater(finalizeSessionSelection); } Component { id: desktopFileLoader FileView { id: fv property string filePath: "" path: filePath onLoaded: { root._parseDesktopFile(text(), filePath); root._onFileLoaded(filePath); fv.destroy(); } onLoadFailed: { root._onFileLoaded(filePath); fv.destroy(); } } } Repeater { model: isPrimaryScreen ? sessionDirs : [] Item { required property string modelData FolderListModel { folder: encodeFileUrl(modelData) nameFilters: ["*.desktop"] showDirs: false showDotAndDotDot: false onStatusChanged: { if (status !== FolderListModel.Ready) return; for (let i = 0; i < count; i++) { let fp = get(i, "filePath"); if (fp.startsWith("file://")) fp = fp.substring(7); root._loadDesktopFile(fp); } } } } } Connections { target: Greetd enabled: isPrimaryScreen function onAuthMessage(message, error, responseRequired, echoResponse) { if (responseRequired) { cancelingExternalAuthForPassword = false; passwordSessionTransitionRetryCount = 0; awaitingExternalAuth = false; pendingPasswordResponse = true; const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0; if (!passwordSubmitRequested && hasPasswordBuffer) passwordSubmitRequested = true; if (passwordSubmitRequested && !root.submitBufferedPassword()) passwordSubmitRequested = false; if (passwordSubmitRequested || hasPasswordBuffer) { authTimeout.interval = defaultAuthTimeoutMs; authTimeout.restart(); } else { authTimeout.stop(); } return; } pendingPasswordResponse = false; const externalPrompt = root.isExternalAuthPrompt(message, responseRequired); if (!passwordSubmitRequested) awaitingExternalAuth = root.greeterExternalAuthAvailable && externalPrompt; if (awaitingExternalAuth || (passwordSubmitRequested && externalPrompt && root.greeterPamHasExternalAuth)) authTimeout.interval = externalAuthTimeoutMs; else authTimeout.interval = defaultAuthTimeoutMs; authTimeout.restart(); Greetd.respond(""); } function onStateChanged() { if (Greetd.state === GreetdState.Inactive) { const resumePasswordSubmit = cancelingExternalAuthForPassword && passwordSubmitRequested; awaitingExternalAuth = false; pendingPasswordResponse = false; cancelingExternalAuthForPassword = false; authTimeout.interval = defaultAuthTimeoutMs; authTimeout.stop(); if (resumePasswordSubmit) { Qt.callLater(function() { root.startAuthSession(true); }); return; } resetPasswordSessionTransition(true); } } function onReadyToLaunch() { awaitingExternalAuth = false; pendingPasswordResponse = false; resetPasswordSessionTransition(true); authTimeout.interval = defaultAuthTimeoutMs; authTimeout.stop(); passwordFailureCount = 0; clearAuthFeedback(); const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]; const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex]; if (!sessionCmd) { GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); placeholderDelay.restart(); return; } GreeterState.unlocking = true; launchTimeout.restart(); if (GreetdSettings.rememberLastSession) { GreetdMemory.setLastSessionId(sessionPath); } else if (GreetdMemory.lastSessionId) { GreetdMemory.setLastSessionId(""); } if (GreetdSettings.rememberLastUser) { GreetdMemory.setLastSuccessfulUser(GreeterState.username); } else if (GreetdMemory.lastSuccessfulUser) { GreetdMemory.setLastSuccessfulUser(""); } pendingLaunchCommand = sessionCmd; pendingLaunchEnv = ["XDG_SESSION_TYPE=wayland"]; memoryFlushTimer.restart(); } function onAuthFailure(message) { awaitingExternalAuth = false; pendingPasswordResponse = false; resetPasswordSessionTransition(true); authTimeout.interval = defaultAuthTimeoutMs; authTimeout.stop(); launchTimeout.stop(); GreeterState.unlocking = false; if (isLikelyLockoutMessage(message)) { GreeterState.pamState = "max"; } else { GreeterState.pamState = "fail"; passwordFailureCount = passwordFailureCount + 1; } authFeedbackMessage = currentAuthMessage(); GreeterState.passwordBuffer = ""; inputField.text = ""; placeholderDelay.restart(); Greetd.cancelSession(); } function onError(error) { awaitingExternalAuth = false; pendingPasswordResponse = false; resetPasswordSessionTransition(true); authTimeout.interval = defaultAuthTimeoutMs; authTimeout.stop(); launchTimeout.stop(); GreeterState.unlocking = false; GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); GreeterState.passwordBuffer = ""; inputField.text = ""; placeholderDelay.restart(); Greetd.cancelSession(); } } Timer { id: memoryFlushTimer interval: memoryFlushDelayMs onTriggered: { if (!pendingLaunchCommand) return; const sessionCommand = pendingLaunchCommand; const launchEnv = pendingLaunchEnv; pendingLaunchCommand = ""; pendingLaunchEnv = []; Greetd.launch(sessionCommand.split(" "), launchEnv); } } Timer { id: authTimeout interval: defaultAuthTimeoutMs onTriggered: { if (GreeterState.unlocking || Greetd.state === GreetdState.Inactive) return; awaitingExternalAuth = false; pendingPasswordResponse = false; resetPasswordSessionTransition(true); authTimeout.interval = defaultAuthTimeoutMs; GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); GreeterState.passwordBuffer = ""; inputField.text = ""; placeholderDelay.restart(); Greetd.cancelSession(); } } Timer { id: launchTimeout interval: 8000 onTriggered: { if (!GreeterState.unlocking) return; pendingPasswordResponse = false; resetPasswordSessionTransition(true); GreeterState.unlocking = false; GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); placeholderDelay.restart(); Greetd.cancelSession(); } } Timer { id: placeholderDelay interval: 4000 onTriggered: clearAuthFeedback() } LockPowerMenu { id: powerMenu showLogout: false powerActionConfirmOverride: GreetdSettings.powerActionConfirm powerActionHoldDurationOverride: GreetdSettings.powerActionHoldDuration powerMenuActionsOverride: GreetdSettings.powerMenuActions powerMenuDefaultActionOverride: GreetdSettings.powerMenuDefaultAction powerMenuGridLayoutOverride: GreetdSettings.powerMenuGridLayout requiredActions: ["poweroff"] onClosed: { if (isPrimaryScreen && inputField && inputField.forceActiveFocus) { Qt.callLater(() => inputField.forceActiveFocus()); } } } }