pragma Singleton // The login keyring's lock state. // // The keyring is unlocked at sign-in by pam_gnome_keyring, exactly as it is // under GNOME. What a bare Hyprland session lacks is anywhere to see when that // has stopped being true. // // It stops being true rarely but expensively: gnome-keyring-daemon can crash, // D-Bus activates a replacement, and the replacement never received the login // password -- so the keyring is locked in the middle of a session that unlocked // it correctly. Nothing announces this. What the user sees instead is a mail // account that will not authenticate, a git push that cannot find its key, or // an integration reporting "not configured", none of which mention keyrings. // // Checked on demand and after an unlock, not polled: the state changes only // when a daemon dies or a password is entered. import Quickshell import Quickshell.Io import QtQuick Singleton { id: root readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-keyring" property bool available: false property bool locked: false property bool scanned: false property bool unlocking: false property string lastError: "" // "pam" when the daemon that holds the keyring is the one PAM started at // login, "dbus" when it is a D-Bus-activated replacement -- which is the // signature of the crash case, and worth showing, because a dbus daemon // that is currently unlocked was unlocked by hand and will not survive. property string daemon: "" readonly property bool replacementDaemon: root.daemon === "dbus" function refresh(): void { if (!query.running) query.running = true; } // Raises the standard password dialog. The password never passes through // Panama -- the Secret Service prompts, the same way it does under GNOME. function unlock(): void { if (root.unlocking) return; root.unlocking = true; unlockProcess.running = true; } function absorb(text: string): void { try { const parsed = JSON.parse(text); root.available = parsed.available === true; root.locked = parsed.locked === true; root.daemon = String(parsed.daemon ?? ""); root.lastError = String(parsed.error ?? ""); } catch (error) { root.available = false; root.lastError = "Could not read the keyring helper's output."; console.warn("Keyring: could not parse helper output:", error); } root.scanned = true; } Process { id: query command: [root.helperPath, "status"] stdout: StdioCollector { onStreamFinished: root.absorb(this.text) } } Process { id: unlockProcess command: [root.helperPath, "unlock"] stdout: StdioCollector { onStreamFinished: root.absorb(this.text) } onExited: root.unlocking = false } }