From 5b41d699faeec402fc78f89879604ab215b306b2 Mon Sep 17 00:00:00 2001 From: purian23 Date: Tue, 14 Jul 2026 19:41:36 -0400 Subject: [PATCH] fix(greeter): reimplement regression to hide debug logs during login - Provide more status feedback - Allow 2 fprint tries within 10 seconds for DMS-managed PAM - Updated dms greeter docs Note: distro-managed PAM takes precedence and may use different limits. DMS auth changes remain conditional on greetd being installed. Fixes #2853 Port 1.5 --- core/internal/pam/pam.go | 2 +- core/internal/pam/pam_test.go | 4 +- quickshell/Common/settings/Processes.qml | 2 +- quickshell/Modules/Greetd/GreetdMemory.qml | 6 +-- quickshell/Modules/Greetd/GreetdSettings.qml | 5 +-- quickshell/Modules/Greetd/GreeterContent.qml | 47 ++++++++++++-------- quickshell/Modules/Greetd/README.md | 36 ++++++++++++++- quickshell/Modules/Greetd/assets/dms-greeter | 20 ++++++++- 8 files changed, 89 insertions(+), 33 deletions(-) diff --git a/core/internal/pam/pam.go b/core/internal/pam/pam.go index e468647fb..38de5cf60 100644 --- a/core/internal/pam/pam.go +++ b/core/internal/pam/pam.go @@ -849,7 +849,7 @@ func syncGreeterPamConfigWithDeps(logFunc func(string), sudoPassword string, set if wantFprint || wantU2f { blockLines := []string{GreeterPamManagedBlockStart} if wantFprint { - blockLines = append(blockLines, "auth sufficient pam_fprintd.so max-tries=1 timeout=5") + blockLines = append(blockLines, "auth sufficient pam_fprintd.so max-tries=2 timeout=10") } if wantU2f { blockLines = append(blockLines, "auth sufficient pam_u2f.so cue nouserok timeout=10") diff --git a/core/internal/pam/pam_test.go b/core/internal/pam/pam_test.go index 065123e86..3adb1114b 100644 --- a/core/internal/pam/pam_test.go +++ b/core/internal/pam/pam_test.go @@ -723,7 +723,7 @@ func TestSyncGreeterPamConfigWithDeps(t *testing.T) { got := readFileString(t, env.greetdPath) for _, want := range []string{ GreeterPamManagedBlockStart, - "auth sufficient pam_fprintd.so max-tries=1 timeout=5", + "auth sufficient pam_fprintd.so max-tries=2 timeout=10", "auth sufficient pam_u2f.so cue nouserok timeout=10", GreeterPamManagedBlockEnd, } { @@ -840,7 +840,7 @@ func TestSyncAuthConfigWithDeps(t *testing.T) { } greetd := readFileString(t, env.greetdPath) - if !strings.Contains(greetd, "auth sufficient pam_fprintd.so max-tries=1 timeout=5") { + if !strings.Contains(greetd, "auth sufficient pam_fprintd.so max-tries=2 timeout=10") { t.Fatalf("expected greetd PAM to receive fingerprint auth block:\n%s", greetd) } if strings.Contains(greetd, "auth sufficient pam_u2f.so cue nouserok timeout=10") { diff --git a/quickshell/Common/settings/Processes.qml b/quickshell/Common/settings/Processes.qml index f7e895859..2458e5eae 100644 --- a/quickshell/Common/settings/Processes.qml +++ b/quickshell/Common/settings/Processes.qml @@ -476,7 +476,7 @@ Singleton { if (exitCode === 0) return "missing_enrollment"; if (exitCode === 127 || (output || "").includes("__missing_command__")) - return "probe_failed"; + return "missing_pam_support"; return pamFprintDetected ? "probe_failed" : "missing_pam_support"; } diff --git a/quickshell/Modules/Greetd/GreetdMemory.qml b/quickshell/Modules/Greetd/GreetdMemory.qml index 93158fd90..54de73bc7 100644 --- a/quickshell/Modules/Greetd/GreetdMemory.qml +++ b/quickshell/Modules/Greetd/GreetdMemory.qml @@ -153,12 +153,10 @@ Singleton { blockWrites: true atomicWrites: false watchChanges: false - printErrors: true + printErrors: false onLoaded: { parseSessionConfig(sessionConfigFileView.text()); } - onLoadFailed: error => { - log.warn("Could not load greeter session config from", root.sessionConfigPath, "error:", error); - } + onLoadFailed: {} } } diff --git a/quickshell/Modules/Greetd/GreetdSettings.qml b/quickshell/Modules/Greetd/GreetdSettings.qml index d536d367e..9b83fc413 100644 --- a/quickshell/Modules/Greetd/GreetdSettings.qml +++ b/quickshell/Modules/Greetd/GreetdSettings.qml @@ -219,12 +219,11 @@ Singleton { blockWrites: true atomicWrites: false watchChanges: false - printErrors: true + printErrors: false onLoaded: { parseSettings(settingsFile.text()); } - onLoadFailed: error => { - log.warn("Failed to load greetd settings:", error); + onLoadFailed: { root.parseSettings(""); } } diff --git a/quickshell/Modules/Greetd/GreeterContent.qml b/quickshell/Modules/Greetd/GreeterContent.qml index 849796246..f991dbc69 100644 --- a/quickshell/Modules/Greetd/GreeterContent.qml +++ b/quickshell/Modules/Greetd/GreeterContent.qml @@ -51,6 +51,7 @@ Item { property int passwordFailureCount: 0 property int passwordAttemptLimitHint: 0 property string authFeedbackMessage: "" + property string authSuccessMessage: "" property string greetdPamText: "" property string systemAuthPamText: "" property string commonAuthPamText: "" @@ -67,11 +68,23 @@ Item { property bool fprintdProbeComplete: false property bool fprintdHasDevice: false property bool autoLoginOnSuccess: false + readonly property bool greeterPamStackHasFprint: greeterPamStackHasModule("pam_fprintd") // 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 greeterPamHasFprint: greeterPamStackHasFprint && (!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 + readonly property bool greeterPamHasExternalAuth: greeterPamStackHasFprint || greeterPamHasU2f + readonly property bool externalAuthInProgress: awaitingExternalAuth || (Greetd.state !== GreetdState.Inactive && passwordSubmitRequested && greeterPamHasExternalAuth && !pendingPasswordResponse) + readonly property string externalAuthStatusMessage: { + if (!externalAuthInProgress) + return ""; + if (greeterPamStackHasFprint && greeterPamHasU2f) + return I18n.tr("Awaiting fingerprint or security key authentication"); + if (greeterPamStackHasFprint) + return I18n.tr("Awaiting fingerprint authentication"); + return I18n.tr("Awaiting security key authentication"); + } + readonly property string authDisplayMessage: authFeedbackMessage || authSuccessMessage || externalAuthStatusMessage readonly property bool autoLoginAvailable: GreetdSettings.rememberLastUser && GreetdSettings.rememberLastSession readonly property bool multipleUsersAvailable: GreeterUsersService.loaded && GreeterUsersService.users.length > 1 // Single-user systems get the picker too when auto-login is available, so the @@ -238,7 +251,7 @@ Item { 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"); + return lower.includes("account is locked") || lower.includes("too many") || lower.includes("maximum number of"); } function currentAuthMessage() { @@ -251,11 +264,11 @@ Item { const attempt = Math.max(1, Math.min(passwordFailureCount, passwordAttemptLimitHint)); const remaining = Math.max(passwordAttemptLimitHint - attempt, 0); if (remaining > 0) { - return I18n.tr("Incorrect password - attempt %1 of %2 (lockout may follow)").arg(attempt).arg(passwordAttemptLimitHint); + return I18n.tr("Authentication failed - attempt %1 of %2").arg(attempt).arg(passwordAttemptLimitHint); } - return I18n.tr("Incorrect password - next failures may trigger account lockout"); + return I18n.tr("Authentication failed - lockout can occur"); } - return I18n.tr("Incorrect password"); + return I18n.tr("Authentication failed - try again"); } return ""; } @@ -263,6 +276,7 @@ Item { function clearAuthFeedback() { GreeterState.pamState = ""; authFeedbackMessage = ""; + authSuccessMessage = ""; } function resetPasswordSessionTransition(clearSubmitRequest) { @@ -619,8 +633,8 @@ Item { 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; + // Let the effective PAM stack finish external authentication. + const waitingOnPamExternalBeforePassword = submitPassword && root.greeterPamHasExternalAuth; authTimeout.interval = (awaitingExternalAuth || waitingOnPamExternalBeforePassword) ? externalAuthTimeoutMs : defaultAuthTimeoutMs; authTimeout.restart(); Greetd.createSession(GreeterState.username); @@ -1406,16 +1420,16 @@ Item { StyledText { Layout.fillWidth: true - Layout.preferredHeight: root.authFeedbackMessage !== "" ? 38 : 0 + Layout.preferredHeight: root.authDisplayMessage !== "" ? 38 : 0 Layout.topMargin: -Theme.spacingS Layout.bottomMargin: -Theme.spacingS - text: root.authFeedbackMessage - color: Theme.error + text: root.authDisplayMessage + color: root.authFeedbackMessage !== "" ? Theme.error : (root.authSuccessMessage !== "" ? Theme.success : Theme.surfaceVariantText) font.pixelSize: Theme.fontSizeSmall horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap maximumLineCount: 2 - opacity: root.authFeedbackMessage !== "" ? 1 : 0 + opacity: root.authDisplayMessage !== "" ? 1 : 0 Behavior on opacity { NumberAnimation { @@ -1975,6 +1989,7 @@ Item { authTimeout.stop(); passwordFailureCount = 0; clearAuthFeedback(); + authSuccessMessage = I18n.tr("Authenticated - starting session"); const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]; const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex]; const sessionDesktopId = GreeterState.selectedSessionDesktopId || GreeterState.sessionDesktopIds[GreeterState.currentSessionIndex] || desktopIdFromPath(sessionPath); @@ -2055,12 +2070,8 @@ Item { pendingLaunchCommand = ""; pendingLaunchEnv = []; const sessionArgs = sessionCommand.trim().split(/\s+/); - const needsVoidDbusSession = Quickshell.env("DMS_VOID") === "1" - && !Quickshell.env("DBUS_SESSION_BUS_ADDRESS") - && sessionArgs[0] !== "dbus-run-session"; - const launchArgs = needsVoidDbusSession - ? ["dbus-run-session"].concat(sessionArgs) - : sessionArgs; + const needsVoidDbusSession = Quickshell.env("DMS_VOID") === "1" && !Quickshell.env("DBUS_SESSION_BUS_ADDRESS") && sessionArgs[0] !== "dbus-run-session"; + const launchArgs = needsVoidDbusSession ? ["dbus-run-session"].concat(sessionArgs) : sessionArgs; Greetd.launch(launchArgs, launchEnv); } } diff --git a/quickshell/Modules/Greetd/README.md b/quickshell/Modules/Greetd/README.md index 1752ff778..f23768849 100644 --- a/quickshell/Modules/Greetd/README.md +++ b/quickshell/Modules/Greetd/README.md @@ -51,12 +51,44 @@ dms greeter enable dms greeter sync ``` +Fingerprint authentication is optional. Install your distribution's +fingerprint service/client and PAM integration before enabling it. Package +names vary by distribution; on Fedora the native stack is: + +```bash +sudo dnf install fprintd fprintd-pam +fprintd-enroll +``` + +The distro `fprintd` package normally pulls in `libfprint` automatically. After +enrollment, enable fingerprint login in **Settings → Greeter**. The toggle +applies the shared PAM configuration automatically, so no additional sync is +needed after initial greeter setup. Use `dms auth sync` only to repair or apply +authentication manually without syncing the rest of the greeter. + +greetd tries fingerprint and password sequentially. DMS-managed PAM allows two +scans for up to ten seconds before password fallback. Existing distro PAM +policy takes precedence, so fallback timing can differ. + +Verify enrollment with `fprintd-list "$USER"`; pass a token such as +`fprintd-enroll -f right-thumb` to enroll a specific finger. Security keys +similarly require `pam_u2f` setup and key registration before enabling the +login toggle. + +If `fprintd-enroll` reports **No devices available**, check the [libfprint +supported devices list](https://fprint.freedesktop.org/supported-devices.html). +Some Validity/Synaptics readers are not supported by the native stack, +regardless of distribution; the +[open-fprintd](https://github.com/uunicorn/open-fprintd) service with the +[python-validity](https://github.com/uunicorn/python-validity) driver may work +instead. Follow the driver's installation and firmware instructions, and do +not run the stock `fprintd` daemon alongside its replacement. + #### Syncing themes (Optional) To sync your wallpaper and theme with the greeter login screen, follow the manual setup below: -
-Manual theme syncing +##### Manual theme syncing ```bash # Add yourself to greeter group diff --git a/quickshell/Modules/Greetd/assets/dms-greeter b/quickshell/Modules/Greetd/assets/dms-greeter index ad98f04e5..6a462a1f7 100755 --- a/quickshell/Modules/Greetd/assets/dms-greeter +++ b/quickshell/Modules/Greetd/assets/dms-greeter @@ -88,6 +88,19 @@ is_hyprland_lua_config() { return 1 } +clear_vt() { + local clear_sequence=$'\033[2J\033[H\033[3J\033[?25l' + + # Clear retained console text on the controlling VT. + if printf '%s' "$clear_sequence" >/dev/tty 2>/dev/null; then + return + fi + + if [[ -t 1 ]]; then + printf '%s' "$clear_sequence" + fi +} + exec_compositor() { local log_tag="$1" shift @@ -96,11 +109,14 @@ exec_compositor() { exec "$@" fi + clear_vt + if command -v systemd-cat >/dev/null 2>&1; then - exec "$@" > >(systemd-cat -t "dms-greeter/$log_tag" -p info) 2>&1 + exec "$@" > >(systemd-cat -t "dms-greeter/$log_tag" -p info 2>/dev/null) 2>&1 fi - exec "$@" + # Preserve diagnostics on systems without journald. + exec "$@" >>"$CACHE_DIR/$log_tag.log" 2>&1 } while [[ $# -gt 0 ]]; do