From 1ee08fca518ce151ab194ba5d8e01105f358cbef Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:20:01 +0100 Subject: [PATCH] keyringstorage: properly handle keyring fetching (#2108) - if auto lock enabled, don't do anything and wait for lock password - not try to overwrite and don't consider loaded when unlocking fails - retry unlock and re fetch on demand (ai request) --- .../quickshell/ii/modules/lock/Lock.qml | 30 ++++++++++++------- .../ii/scripts/keyring/is_unlocked.sh | 11 +++++++ .../ii/scripts/keyring/try_lookup.sh | 15 ++++++++++ .../quickshell/ii/scripts/keyring/unlock.sh | 8 ++--- dots/.config/quickshell/ii/services/Ai.qml | 6 ++-- .../quickshell/ii/services/KeyringStorage.qml | 21 ++++++++----- 6 files changed, 66 insertions(+), 25 deletions(-) create mode 100755 dots/.config/quickshell/ii/scripts/keyring/is_unlocked.sh create mode 100755 dots/.config/quickshell/ii/scripts/keyring/try_lookup.sh diff --git a/dots/.config/quickshell/ii/modules/lock/Lock.qml b/dots/.config/quickshell/ii/modules/lock/Lock.qml index ead478bdc..e5557425b 100644 --- a/dots/.config/quickshell/ii/modules/lock/Lock.qml +++ b/dots/.config/quickshell/ii/modules/lock/Lock.qml @@ -13,10 +13,16 @@ import Quickshell.Hyprland Scope { id: root + Process { + id: unlockKeyringProc + onExited: (exitCode, exitStatus) => { + KeyringStorage.fetchKeyringData(); + } + } function unlockKeyring() { - Quickshell.execDetached({ + unlockKeyringProc.exec({ environment: ({ - UNLOCK_PASSWORD: root.currentText + "UNLOCK_PASSWORD": lockContext.currentText }), command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")] }) @@ -24,7 +30,7 @@ Scope { property var windowData: [] function saveWindowPositionAndTile() { - Hyprland.dispatch(`keyword dwindle:pseudotile true`) + Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"]) root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id)) root.windowData.forEach(w => { Hyprland.dispatch(`pseudo address:${w.address}`) @@ -38,7 +44,7 @@ Scope { Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`) Hyprland.dispatch(`pseudo address:${w.address}`) }) - Hyprland.dispatch(`keyword dwindle:pseudotile false`) + Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"]) } // This stores all the information shared between the lock surfaces on each screen. @@ -156,20 +162,24 @@ Scope { } } + function initIfReady() { + if (!Config.ready || !Persistent.ready) return; + if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) { + Hyprland.dispatch("global quickshell:lock") + } else { + KeyringStorage.fetchKeyringData(); + } + } Connections { target: Config function onReadyChanged() { - if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) { - Hyprland.dispatch("global quickshell:lock") - } + root.initIfReady(); } } Connections { target: Persistent function onReadyChanged() { - if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) { - Hyprland.dispatch("global quickshell:lock") - } + root.initIfReady(); } } } diff --git a/dots/.config/quickshell/ii/scripts/keyring/is_unlocked.sh b/dots/.config/quickshell/ii/scripts/keyring/is_unlocked.sh new file mode 100755 index 000000000..d4063d816 --- /dev/null +++ b/dots/.config/quickshell/ii/scripts/keyring/is_unlocked.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +locked_state=$(busctl --user get-property org.freedesktop.secrets \ + /org/freedesktop/secrets/collection/login \ + org.freedesktop.Secret.Collection Locked) +if [[ "${locked_state}" == "b false" ]]; then + echo 'Keyring is unlocked' >&2 + exit 0 +else + echo 'Keyring is locked' >&2 + exit 1 +fi diff --git a/dots/.config/quickshell/ii/scripts/keyring/try_lookup.sh b/dots/.config/quickshell/ii/scripts/keyring/try_lookup.sh new file mode 100755 index 000000000..a076aac91 --- /dev/null +++ b/dots/.config/quickshell/ii/scripts/keyring/try_lookup.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +data=$(secret-tool lookup 'application' 'illogical-impulse') +if [[ -z "$data" ]]; then + if "${SCRIPT_DIR}/is_unlocked.sh"; then + echo 'not found' + exit 1 + else + echo 'locked' + exit 2 + fi +fi +echo "$data" diff --git a/dots/.config/quickshell/ii/scripts/keyring/unlock.sh b/dots/.config/quickshell/ii/scripts/keyring/unlock.sh index b255f8e0f..30509aa37 100755 --- a/dots/.config/quickshell/ii/scripts/keyring/unlock.sh +++ b/dots/.config/quickshell/ii/scripts/keyring/unlock.sh @@ -1,12 +1,10 @@ #!/usr/bin/env bash # Based on https://unix.stackexchange.com/a/602935 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + # Skip if already unlocked -locked_state=$(busctl --user get-property org.freedesktop.secrets \ - /org/freedesktop/secrets/collection/login \ - org.freedesktop.Secret.Collection Locked) -if [[ "${locked_state}" == "b false" ]]; then - echo 'Keyring is already unlocked.' >&2 +if "${SCRIPT_DIR}/is_unlocked.sh"; then exit 1 fi diff --git a/dots/.config/quickshell/ii/services/Ai.qml b/dots/.config/quickshell/ii/services/Ai.qml index 47b1d151f..ccd237de8 100644 --- a/dots/.config/quickshell/ii/services/Ai.qml +++ b/dots/.config/quickshell/ii/services/Ai.qml @@ -532,8 +532,6 @@ Singleton { modelId = modelId.toLowerCase() if (modelList.indexOf(modelId) !== -1) { const model = models[modelId] - // Fetch API keys if needed - if (model?.requires_key) KeyringStorage.fetchKeyringData(); // See if policy prevents online models if (Config.options.policies.ai === 2 && !model.endpoint.includes("localhost")) { root.addMessage( @@ -641,6 +639,10 @@ Singleton { function makeRequest() { const model = models[currentModelId]; + + // Fetch API keys if needed + if (model?.requires_key && !KeyringStorage.loaded) KeyringStorage.fetchKeyringData(); + requester.currentStrategy = root.currentApiStrategy; requester.currentStrategy.reset(); // Reset strategy state diff --git a/dots/.config/quickshell/ii/services/KeyringStorage.qml b/dots/.config/quickshell/ii/services/KeyringStorage.qml index 508247966..1e8562cac 100644 --- a/dots/.config/quickshell/ii/services/KeyringStorage.qml +++ b/dots/.config/quickshell/ii/services/KeyringStorage.qml @@ -1,6 +1,7 @@ pragma Singleton pragma ComponentBehavior: Bound +import qs import qs.modules.common import qs.modules.common.functions import Quickshell; @@ -89,11 +90,13 @@ Singleton { Process { id: getData command: [ // We need to use echo for a newline so splitparser does parse - "bash", "-c", `echo $(secret-tool lookup 'application' 'illogical-impulse')`, + "bash", "-c", `${Directories.scriptPath}/keyring/try_lookup.sh 2> /dev/null`, ] - stdout: SplitParser { - onRead: data => { - if(data.length === 0) return; + stdout: StdioCollector { + id: keyringDataOutputCollector + onStreamFinished: { + const data = keyringDataOutputCollector.text; + if (data.length === 0 || !data.startsWith("{")) return; try { root.keyringData = JSON.parse(data); // console.log("[KeyringStorage] Keyring data fetched:", JSON.stringify(root.keyringData)); @@ -105,13 +108,15 @@ Singleton { } } onExited: (exitCode, exitStatus) => { - // console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode); - if (exitCode !== 0) { - console.error("[KeyringStorage] Failed to get keyring data, reinitializing."); + console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode); + if (exitCode === 1) { + console.error("[KeyringStorage] Entry not found, initializing."); root.keyringData = {}; saveKeyringData() } - root.loaded = true; + if (exitCode !== 2) { + root.loaded = true; + } } }