Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package otpclient for openSUSE:Factory 
checked in at 2026-05-21 18:33:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/otpclient (Old)
 and      /work/SRC/openSUSE:Factory/.otpclient.new.2084 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "otpclient"

Thu May 21 18:33:03 2026 rev:48 rq:1354462 version:5.0.5

Changes:
--------
--- /work/SRC/openSUSE:Factory/otpclient/otpclient.changes      2026-05-20 
15:27:13.892839818 +0200
+++ /work/SRC/openSUSE:Factory/.otpclient.new.2084/otpclient.changes    
2026-05-21 18:34:14.743614606 +0200
@@ -1,0 +2,50 @@
+Thu May 21 07:55:45 UTC 2026 - Paolo Stivanin <[email protected]>
+
+- Update to 5.0.5:
+  * Welcome dialog Back/Next/page-indicator row got pushed below the
+    visible area when an AdwStatusPage's natural height exceeded the
+    dialog's content area (#441). The nav row lived inside the same
+    GtkBox as the stack; now pinned via AdwToolbarView's bottom bar
+    so it stays visible while the status page's internal scroll
+    handles long descriptions. Dialog also bumped from 500×420 to
+    560×560.
+  * Keyboard focus did not land on the token list after unlock
+    (#445). 5.x was leaving focus on whatever widget the stack last
+    rendered, so Up/Down/Enter required a mouse click into the list
+    first. Restored the 4.x default of focusing the token list after
+    unlock, and extended the same idea to the empty and no-database
+    pages so the obvious next action (add the first token / create
+    the first database) is one keystroke away. Focus only moves on
+    page transitions, so search-bar typing and in-page item changes
+    don't get stolen.
+  * Per-launch loop when the registered Secret Service is unavailable
+    (#446). On Kubuntu/KDE Plasma with KWallet disabled (but still
+    owning the org.freedesktop.secrets D-Bus name), libsecret has no
+    way to fall back to gnome-keyring — that has to be configured at
+    the session layer. The previous behavior looped: every launch did
+    a failing lookup, prompted for the password, then a failing store
+    fired a notification. Now the "Use Secret Service" toggle pre-
+    flights the keyring with a sync store/lookup/verify/clear round-
+    trip when you enable it; if the round-trip fails the switch
+    reverts and you get a dialog with the libsecret error. If the
+    keyring breaks after the setting was already enabled, the first
+    failed lookup or store flips the setting OFF, surfaces one
+    notification, and falls through to the password dialog so you can
+    still unlock. CLI and search-provider also fall through
+    gracefully without mutating GSettings (avoids races with the GUI
+    session).
+  * Lock bypass via unlock dialog dismissal (#447). The token list
+    was still rendered and on_otp_selection_changed was unguarded, so
+    a click would copy the OTP; the right-click "Show QR" action also
+    remained enabled and would render the secret. Defense in depth:
+    password dialog set non-dismissable for DECRYPT, new locked page
+    in content_stack hides the token list while locked,
+    lock_app_lock() wipes cached OTP values via
+    otpclient_window_clear_displayed_otps(), set_db_actions_enabled()
+    expanded from 5 to 18 actions gating every token-touching path
+    including show-qr. Lock guards added in on_otp_selection_changed,
+    on_drag_prepare, and on_token_right_click. A closed-signal
+    handler re-presents the dialog if it ever slips closed while
+    still locked.
+
+-------------------------------------------------------------------

Old:
----
  v5.0.3.tar.gz
  v5.0.3.tar.gz.asc

New:
----
  v5.0.5.tar.gz
  v5.0.5.tar.gz.asc

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ otpclient.spec ++++++
--- /var/tmp/diff_new_pack.cy0ORe/_old  2026-05-21 18:34:16.407683756 +0200
+++ /var/tmp/diff_new_pack.cy0ORe/_new  2026-05-21 18:34:16.411683923 +0200
@@ -18,7 +18,7 @@
 
 %define uclname OTPClient
 Name:           otpclient
-Version:        5.0.3
+Version:        5.0.5
 Release:        0
 Summary:        Simple GTK+ client for managing TOTP and HOTP
 License:        GPL-3.0-or-later

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.cy0ORe/_old  2026-05-21 18:34:16.451685585 +0200
+++ /var/tmp/diff_new_pack.cy0ORe/_new  2026-05-21 18:34:16.455685752 +0200
@@ -1,5 +1,5 @@
-mtime: 1778849880
-commit: 39467a0dac9cb4485ba8a91e93de7c06c0b8799cc0255b16b4dcca753a1061a1
+mtime: 1779350206
+commit: 52453677a8c3595aa71645ac0d6f370b78a4f924f98cb8315acf09b4da301ea6
 url: https://src.opensuse.org/GNOME/otpclient
 revision: factory
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-05-21 09:56:46.000000000 +0200
@@ -0,0 +1,4 @@
+*.obscpio
+*.osc
+_build.*
+.pbuild


++++++ v5.0.3.tar.gz -> v5.0.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/CMakeLists.txt 
new/OTPClient-5.0.5/CMakeLists.txt
--- old/OTPClient-5.0.3/CMakeLists.txt  2026-05-15 14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/CMakeLists.txt  2026-05-21 09:41:51.000000000 +0200
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.25)
-project(OTPClient VERSION "5.0.3" LANGUAGES "C")
+project(OTPClient VERSION "5.0.5" LANGUAGES "C")
 include(GNUInstallDirs)
 
 configure_file("src/common/version.h.in" "version.h")
@@ -214,7 +214,5 @@
 install(FILES data/com.github.paolostivanin.OTPClient.appdata.xml DESTINATION 
${CMAKE_INSTALL_DATADIR}/metainfo)
 install(FILES data/com.github.paolostivanin.OTPClient.gschema.xml DESTINATION 
${CMAKE_INSTALL_DATADIR}/glib-2.0/schemas)
 
-if(NOT IS_FLATPAK)
-    install(CODE "execute_process(COMMAND glib-compile-schemas 
\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/glib-2.0/schemas)")
-    install(CODE "execute_process(COMMAND gtk4-update-icon-cache -t -f 
\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons/hicolor)")
-endif()
+install(CODE "execute_process(COMMAND glib-compile-schemas 
\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/glib-2.0/schemas)")
+install(CODE "execute_process(COMMAND gtk4-update-icon-cache -t -f 
\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons/hicolor)")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/OTPClient-5.0.3/data/com.github.paolostivanin.OTPClient.appdata.xml 
new/OTPClient-5.0.5/data/com.github.paolostivanin.OTPClient.appdata.xml
--- old/OTPClient-5.0.3/data/com.github.paolostivanin.OTPClient.appdata.xml     
2026-05-15 14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/data/com.github.paolostivanin.OTPClient.appdata.xml     
2026-05-21 09:41:51.000000000 +0200
@@ -90,6 +90,25 @@
   </content_rating>
 
   <releases>
+    <release version="5.0.5" date="2026-05-21">
+      <description>
+        <p>Bug-fix release in the 5.0.x line. Headline fix is a hardening of 
the auto-lock flow: dismissing the unlock dialog (Esc, X, or click-outside) 
previously left the token list visible and interactive, so a single click would 
still copy an OTP — and "Show QR" would render the secret as a scannable image. 
The unlock dialog is now non-dismissable and a dedicated locked page hides the 
token list across every code path. Also recovers gracefully on systems where 
the registered Secret Service provider is broken (e.g. KDE Plasma with KWallet 
disabled): the setting is pre-flighted before being enabled and auto-disabled 
on runtime failure, instead of looping every launch.</p>
+        <ul>
+          <li>FIX: lock bypass via unlock dialog dismissal — the token list 
was still clickable, allowing OTP copy and QR render while the app was "locked" 
(#447)</li>
+          <li>FIX: per-launch loop when the system Secret Service is 
unavailable; the toggle now pre-flights the keyring before being enabled, and 
auto-disables on runtime failure rather than nagging on every start (#446)</li>
+          <li>FIX: keyboard focus did not land on the token list after unlock, 
so Up/Down/Enter required a mouse click first; restored the 4.x default and 
extended it to the empty and no-database pages (#445)</li>
+        </ul>
+      </description>
+    </release>
+    <release version="5.0.4" date="2026-05-19">
+      <description>
+        <p>Small bug-fix release in the 5.0.x line. The headline fix is a 
Flatpak-only data-loss-prevention hotfix: the GSettings schema XML was 
installed but never compiled into gschemas.compiled, so the schema lookup 
failed at runtime and every GSettings write was a silent no-op. As a result, 
the sidebar appeared empty on each launch (db-list never persisted) and 
preference changes did not stick. Existing databases were never lost — the .enc 
files remained on disk and the last-used path was still being written to 
otpclient.cfg via the GKeyFile fallback. On first launch after upgrading to 
5.0.4, the v4 migration path rebuilds the sidebar from that fallback 
automatically. Non-Flatpak builds are unaffected by the schema fix.</p>
+        <ul>
+          <li>FIX: Flatpak GSettings schema not compiled at install time, 
causing the sidebar to appear empty and preferences to reset on every restart 
(#442)</li>
+          <li>FIX: Welcome dialog Back/Next/page-indicator row got pushed 
below the visible area when the page's status description was tall enough to 
overflow; now pinned via AdwToolbarView's bottom bar (#441)</li>
+        </ul>
+      </description>
+    </release>
     <release version="5.0.3" date="2026-05-15">
       <description>
         <p>Small bug-fix release in the 5.0.x line. Fixes a startup crash on 
systems where the XDG portal cannot auto-report a color scheme (typically XFCE) 
and the user has the dark-theme GSetting enabled. The dark-theme preference was 
being applied before AdwApplication's startup chain ran, so 
adw_style_manager_get_default() reached into an uninitialized GDK display and 
aborted.</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/cli/exec-action.c 
new/OTPClient-5.0.5/src/cli/exec-action.c
--- old/OTPClient-5.0.3/src/cli/exec-action.c   2026-05-15 14:48:27.000000000 
+0200
+++ new/OTPClient-5.0.5/src/cli/exec-action.c   2026-05-21 09:41:51.000000000 
+0200
@@ -129,8 +129,24 @@
             return FALSE;
         }
     }
+    /* Issue #446: if the registered Secret Service provider is broken, the
+     * sync lookup fails with err != NULL. Treat that as "fall back to the
+     * interactive prompt" instead of looping. Don't persist secret-service=
+     * false in GSettings here — a scripted CLI run through a transiently
+     * broken D-Bus session shouldn't flip the user's GUI setting. */
+    gboolean secret_service_runtime_ok = TRUE;
     if (use_secret_service == TRUE && g_file_test (db_data->db_path, 
G_FILE_TEST_EXISTS)) {
-        gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, 
NULL, "string", db_data->db_path, NULL);
+        GError *ss_err = NULL;
+        gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, 
&ss_err,
+                                                  "string", db_data->db_path, 
NULL);
+        if (ss_err != NULL) {
+            g_printerr (_("Warning: secret service lookup failed: %s\n"
+                          "Falling back to interactive password prompt.\n"),
+                        ss_err->message);
+            g_clear_error (&ss_err);
+            secret_service_runtime_ok = FALSE;
+            goto get_pwd;
+        }
         if (pwd == NULL) {
             goto get_pwd;
         }
@@ -179,7 +195,7 @@
         }
     }
 
-    if (use_secret_service == TRUE && db_data->key_stored == FALSE) {
+    if (use_secret_service == TRUE && db_data->key_stored == FALSE && 
secret_service_runtime_ok) {
         secret_password_store (OTPCLIENT_SCHEMA, SECRET_COLLECTION_DEFAULT, 
"OTPClient database password", db_data->key, NULL, on_password_stored, NULL, 
"string", db_data->db_path, NULL);
     }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/common/secret-schema.c 
new/OTPClient-5.0.5/src/common/secret-schema.c
--- old/OTPClient-5.0.3/src/common/secret-schema.c      2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/common/secret-schema.c      2026-05-21 
09:41:51.000000000 +0200
@@ -1,5 +1,11 @@
 #include <libsecret/secret.h>
 #include <gio/gio.h>
+#include <string.h>
+#include "secret-schema.h"
+
+/* Sentinel value used as the "string" attribute for the keyring probe.
+ * Real db_paths are absolute filesystem paths, so they cannot match this. */
+#define OTPCLIENT_SECRET_PROBE_ATTR "__otpclient_probe__"
 
 const SecretSchema *
 otpclient_get_schema (void)
@@ -76,4 +82,61 @@
     if (removed == TRUE) {
         g_message ("Password successfully removed from the secret service.");
     }
+}
+
+
+gboolean
+otpclient_secret_service_probe (GError **error)
+{
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    /* Fresh random payload each call: a stale probe value left behind by a
+     * previously crashed run cannot produce a false positive — the verify
+     * step compares against this run's uuid. */
+    g_autofree gchar *expected = g_uuid_string_random ();
+
+    GError *probe_err = NULL;
+    gchar  *retrieved  = NULL;
+    gboolean ok = FALSE;
+
+    if (!secret_password_store_sync (OTPCLIENT_SCHEMA,
+                                     SECRET_COLLECTION_DEFAULT,
+                                     "OTPClient keyring probe",
+                                     expected,
+                                     NULL,
+                                     &probe_err,
+                                     "string", OTPCLIENT_SECRET_PROBE_ATTR,
+                                     NULL))
+    {
+        goto cleanup;
+    }
+
+    retrieved = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, 
&probe_err,
+                                             "string", 
OTPCLIENT_SECRET_PROBE_ATTR,
+                                             NULL);
+    if (probe_err != NULL)
+        goto cleanup;
+
+    if (retrieved == NULL || strcmp (retrieved, expected) != 0)
+    {
+        g_set_error_literal (&probe_err, G_IO_ERROR, G_IO_ERROR_FAILED,
+                             "keyring round-trip returned different data than 
was written");
+        goto cleanup;
+    }
+
+    ok = TRUE;
+
+cleanup:
+    /* Always attempt cleanup; ignore errors so we don't mask the original
+     * failure (or, on success, leave a stray probe value behind). */
+    secret_password_clear_sync (OTPCLIENT_SCHEMA, NULL, NULL,
+                                "string", OTPCLIENT_SECRET_PROBE_ATTR,
+                                NULL);
+    if (retrieved != NULL)
+        secret_password_free (retrieved);
+
+    if (probe_err != NULL)
+        g_propagate_error (error, probe_err);
+
+    return ok;
 }
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/common/secret-schema.h 
new/OTPClient-5.0.5/src/common/secret-schema.h
--- old/OTPClient-5.0.3/src/common/secret-schema.h      2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/common/secret-schema.h      2026-05-21 
09:41:51.000000000 +0200
@@ -12,4 +12,11 @@
 
 void on_password_cleared (GObject      *source,
                           GAsyncResult *result,
-                          gpointer      unused);
\ No newline at end of file
+                          gpointer      unused);
+
+/* Synchronous round-trip test of the registered Secret Service provider:
+ * store a probe value, look it up, verify it, then clear it. Uses a sentinel
+ * attribute value so the probe cannot collide with a real db_path. Returns
+ * TRUE iff every step succeeds. On failure, *error is set to the underlying
+ * libsecret error (or a synthetic one for the verify-mismatch case). */
+gboolean otpclient_secret_service_probe (GError **error);
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/dialogs/password-dialog.c 
new/OTPClient-5.0.5/src/gui/dialogs/password-dialog.c
--- old/OTPClient-5.0.3/src/gui/dialogs/password-dialog.c       2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/gui/dialogs/password-dialog.c       2026-05-21 
09:41:51.000000000 +0200
@@ -88,11 +88,17 @@
     if (self->current_password_row != NULL)
         gtk_editable_set_text (GTK_EDITABLE (self->current_password_row), "");
 
-    adw_dialog_close (ADW_DIALOG (self));
-
+    /* Order matters: lock-state callers attach a "closed" handler that
+     * re-presents this dialog if the app is still locked when it fires. Run
+     * the callback first so it can flip app_locked to FALSE before the close
+     * triggers that handler — otherwise a successful unlock would race with a
+     * stale re-present. force_close bypasses can-close=FALSE (set by callers
+     * that want to block user-initiated dismissal). */
     if (self->callback != NULL)
         self->callback (secure_pwd, self->callback_data);
 
+    adw_dialog_force_close (ADW_DIALOG (self));
+
     gcry_free (secure_pwd);
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/dialogs/settings-dialog.c 
new/OTPClient-5.0.5/src/gui/dialogs/settings-dialog.c
--- old/OTPClient-5.0.3/src/gui/dialogs/settings-dialog.c       2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/gui/dialogs/settings-dialog.c       2026-05-21 
09:41:51.000000000 +0200
@@ -3,6 +3,7 @@
 #include "gui-misc.h"
 #include "common.h"
 #include "gsettings-common.h"
+#include "secret-schema.h"
 #include "settings-import-export.h"
 #include "otp-button-row.h"
 
@@ -116,13 +117,54 @@
 }
 
 static void
+present_secret_service_unavailable_dialog (SettingsDialog *self,
+                                            const gchar    *err_msg)
+{
+    g_autofree gchar *body = g_strdup_printf (
+        _("OTPClient could not access your system keyring (KWallet, GNOME 
Keyring, etc.). "
+          "The setting has been left disabled. Enable your keyring in the 
system settings, "
+          "then try again.\n\nError: %s"),
+        err_msg != NULL ? err_msg : _("unknown error"));
+
+    AdwAlertDialog *alert = ADW_ALERT_DIALOG (
+        adw_alert_dialog_new (_("Couldn't enable Secret Service"), body));
+    adw_alert_dialog_add_response (alert, "ok", _("OK"));
+    adw_alert_dialog_set_default_response (alert, "ok");
+    adw_alert_dialog_set_close_response (alert, "ok");
+    adw_dialog_present (ADW_DIALOG (alert), GTK_WIDGET (self));
+}
+
+static void
 on_secret_service_toggled (GObject        *obj,
                            GParamSpec     *pspec,
                            SettingsDialog *self)
 {
     (void) pspec;
     gboolean active = adw_switch_row_get_active (ADW_SWITCH_ROW (obj));
-    otpclient_application_set_use_secret_service (self->app, active);
+
+    if (!active) {
+        otpclient_application_set_use_secret_service (self->app, FALSE);
+        return;
+    }
+
+    /* Pre-flight: refuse to enable on a broken keyring rather than letting
+     * the user discover later via failed lookups and stored-password loops
+     * (issue #446). Sync calls are fine here — the user just clicked the
+     * toggle and expects immediate feedback. */
+    GError *err = NULL;
+    if (otpclient_secret_service_probe (&err)) {
+        otpclient_application_set_use_secret_service (self->app, TRUE);
+        return;
+    }
+
+    /* Revert the switch without re-entering this handler. */
+    g_signal_handlers_block_by_func (obj, on_secret_service_toggled, self);
+    adw_switch_row_set_active (ADW_SWITCH_ROW (obj), FALSE);
+    g_signal_handlers_unblock_by_func (obj, on_secret_service_toggled, self);
+
+    otpclient_application_set_use_secret_service (self->app, FALSE);
+    present_secret_service_unavailable_dialog (self, err != NULL ? 
err->message : NULL);
+    g_clear_error (&err);
 }
 
 static void
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/dialogs/whats-new-dialog.c 
new/OTPClient-5.0.5/src/gui/dialogs/whats-new-dialog.c
--- old/OTPClient-5.0.3/src/gui/dialogs/whats-new-dialog.c      2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/gui/dialogs/whats-new-dialog.c      2026-05-21 
09:41:51.000000000 +0200
@@ -234,8 +234,8 @@
 {
     WhatsNewDialog *self = g_object_new (WHATS_NEW_TYPE_DIALOG,
                                          "title", "",
-                                         "content-width", 500,
-                                         "content-height", 420,
+                                         "content-width", 560,
+                                         "content-height", 560,
                                          NULL);
 
     if (is_welcome) {
@@ -252,8 +252,6 @@
     GtkWidget *header = adw_header_bar_new ();
     adw_toolbar_view_add_top_bar (ADW_TOOLBAR_VIEW (toolbar_view), header);
 
-    GtkWidget *outer_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
-
     /* Stack with pages */
     self->stack = gtk_stack_new ();
     gtk_stack_set_transition_type (GTK_STACK (self->stack),
@@ -274,8 +272,6 @@
         gtk_stack_add_named (GTK_STACK (self->stack), status_page, page_name);
     }
 
-    gtk_box_append (GTK_BOX (outer_box), self->stack);
-
     /* Bottom navigation bar */
     GtkWidget *nav_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
     gtk_widget_set_halign (nav_bar, GTK_ALIGN_CENTER);
@@ -307,9 +303,8 @@
     gtk_box_append (GTK_BOX (nav_bar), self->dot_box);
     gtk_box_append (GTK_BOX (nav_bar), self->next_button);
 
-    gtk_box_append (GTK_BOX (outer_box), nav_bar);
-
-    adw_toolbar_view_set_content (ADW_TOOLBAR_VIEW (toolbar_view), outer_box);
+    adw_toolbar_view_set_content (ADW_TOOLBAR_VIEW (toolbar_view), 
self->stack);
+    adw_toolbar_view_add_bottom_bar (ADW_TOOLBAR_VIEW (toolbar_view), nav_bar);
     adw_dialog_set_child (ADW_DIALOG (self), toolbar_view);
 
     return self;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/lock-app.c 
new/OTPClient-5.0.5/src/gui/lock-app.c
--- old/OTPClient-5.0.3/src/gui/lock-app.c      2026-05-15 14:48:27.000000000 
+0200
+++ new/OTPClient-5.0.5/src/gui/lock-app.c      2026-05-21 09:41:51.000000000 
+0200
@@ -17,6 +17,45 @@
 
 static LockData *lock_data = NULL;
 
+static void on_unlock_password (const gchar *password, gpointer user_data);
+
+static void
+on_unlock_dialog_closed (AdwDialog *dialog,
+                         gpointer   user_data)
+{
+    (void) dialog;
+    OTPClientApplication *app = OTPCLIENT_APPLICATION (user_data);
+
+    /* set_can_close(FALSE) blocks user-initiated dismissal, but the dialog can
+     * still close on parent-window teardown or programmatic close. If the app
+     * is still locked when that happens, re-present a fresh dialog so the user
+     * can't end up with the locked indicator on and no way to unlock. */
+    if (otpclient_application_get_app_locked (app))
+        lock_app_present_unlock_dialog (app);
+}
+
+static void
+present_unlock_dialog (OTPClientApplication *app)
+{
+    GtkWindow *win = gtk_application_get_active_window (GTK_APPLICATION (app));
+    if (win == NULL)
+        return;
+
+    PasswordDialog *dlg = password_dialog_new (PASSWORD_MODE_DECRYPT,
+                                               on_unlock_password,
+                                               app);
+    adw_dialog_set_can_close (ADW_DIALOG (dlg), FALSE);
+    g_signal_connect (dlg, "closed", G_CALLBACK (on_unlock_dialog_closed), 
app);
+    adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET (win));
+}
+
+void
+lock_app_present_unlock_dialog (OTPClientApplication *app)
+{
+    g_return_if_fail (OTPCLIENT_IS_APPLICATION (app));
+    present_unlock_dialog (app);
+}
+
 static void
 on_unlock_password (const gchar *password,
                     gpointer     user_data)
@@ -34,19 +73,10 @@
     for (gsize i = 0; i < cmp_len; i++)
         result |= ((const volatile guchar *)password)[i] ^ ((const volatile 
guchar *)db_data->key)[i];
     if (result == 0)
-    {
         lock_app_unlock (app);
-    }
-    else
-    {
-        /* Wrong password, show dialog again */
-        PasswordDialog *dlg = password_dialog_new (PASSWORD_MODE_DECRYPT,
-                                                   on_unlock_password,
-                                                   app);
-        GtkWindow *active_win = gtk_application_get_active_window 
(GTK_APPLICATION (app));
-        if (active_win != NULL)
-            adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET (active_win));
-    }
+    /* On wrong password, the "closed" signal handler re-presents the dialog
+     * once the old one finishes closing. Calling present_unlock_dialog() here
+     * would race with that and pop two dialogs. */
 }
 
 void
@@ -66,15 +96,13 @@
         /* Persist any deferred HOTP counter advances while we still hold the 
key. */
         otpclient_window_flush_pending_writes (OTPCLIENT_WINDOW (win));
         otpclient_window_clear_clipboard_now (OTPCLIENT_WINDOW (win));
+        otpclient_window_clear_displayed_otps (OTPCLIENT_WINDOW (win));
         otpclient_window_set_locked_indicator (OTPCLIENT_WINDOW (win), TRUE);
         otpclient_window_set_db_actions_enabled (OTPCLIENT_WINDOW (win), 
FALSE);
+        otpclient_window_refresh_content_page (OTPCLIENT_WINDOW (win));
     }
 
-    /* Show password dialog to unlock */
-    PasswordDialog *dlg = password_dialog_new (PASSWORD_MODE_DECRYPT,
-                                               on_unlock_password,
-                                               app);
-    adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET (win));
+    present_unlock_dialog (app);
 }
 
 void
@@ -87,6 +115,7 @@
     {
         otpclient_window_set_locked_indicator (OTPCLIENT_WINDOW (win), FALSE);
         otpclient_window_set_db_actions_enabled (OTPCLIENT_WINDOW (win), TRUE);
+        otpclient_window_refresh_content_page (OTPCLIENT_WINDOW (win));
     }
 
     if (lock_data != NULL)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/lock-app.h 
new/OTPClient-5.0.5/src/gui/lock-app.h
--- old/OTPClient-5.0.3/src/gui/lock-app.h      2026-05-15 14:48:27.000000000 
+0200
+++ new/OTPClient-5.0.5/src/gui/lock-app.h      2026-05-21 09:41:51.000000000 
+0200
@@ -11,6 +11,8 @@
 void lock_app_lock                 (OTPClientApplication *app);
 void lock_app_unlock               (OTPClientApplication *app);
 
+void lock_app_present_unlock_dialog (OTPClientApplication *app);
+
 void lock_app_reset_inactivity     (OTPClientApplication *app);
 
 G_END_DECLS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/otpclient-application.c 
new/OTPClient-5.0.5/src/gui/otpclient-application.c
--- old/OTPClient-5.0.3/src/gui/otpclient-application.c 2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/gui/otpclient-application.c 2026-05-21 
09:41:51.000000000 +0200
@@ -193,6 +193,46 @@
     g_object_unref (builder);
 }
 
+/* Issue #446: when a libsecret call fails at runtime with a real error
+ * (i.e. not "no password stored"), the registered Secret Service provider
+ * is broken in some way we can't recover from in-process — libsecret has
+ * no fallback to a different keyring. Flip the setting OFF so we don't
+ * loop on the failure every launch, and surface one notification so the
+ * user knows what happened and how to re-enable it later. */
+static void
+application_disable_secret_service_runtime (OTPClientApplication *self,
+                                             const gchar          
*libsecret_err_msg)
+{
+    if (!self->use_secret_service)
+        return;
+
+    otpclient_application_set_use_secret_service (self, FALSE);
+
+    g_autofree gchar *body = g_strdup_printf (
+        _("OTPClient's system-keyring integration was disabled because the 
keyring "
+          "rejected the request. Re-enable it in Settings once your keyring is 
available."
+          "\n\nError: %s"),
+        libsecret_err_msg != NULL ? libsecret_err_msg : _("unknown error"));
+
+    gui_misc_send_notification (G_APPLICATION (self),
+                                _("Secret Service disabled"),
+                                body);
+}
+
+static void
+on_password_stored_gui (GObject      *source         __attribute__((unused)),
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+    OTPClientApplication *self = OTPCLIENT_APPLICATION (user_data);
+    GError *err = NULL;
+    secret_password_store_finish (result, &err);
+    if (err != NULL) {
+        application_disable_secret_service_runtime (self, err->message);
+        g_error_free (err);
+    }
+}
+
 static void
 on_change_password_received (const gchar *password,
                               gpointer     user_data)
@@ -230,7 +270,7 @@
                                "OTPClient database password",
                                self->db_data->key,
                                NULL,
-                               on_password_stored,
+                               on_password_stored_gui,
                                self,
                                "string", self->db_data->db_path,
                                NULL);
@@ -493,6 +533,7 @@
                 PasswordDialog *dlg = password_dialog_new 
(PASSWORD_MODE_DECRYPT,
                                                            
on_password_received,
                                                            self);
+                adw_dialog_set_can_close (ADW_DIALOG (dlg), FALSE);
                 adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET 
(self->window));
             }
             return;
@@ -522,7 +563,7 @@
                                "OTPClient database password",
                                self->db_data->key,
                                NULL,
-                               on_password_stored,
+                               on_password_stored_gui,
                                self,
                                "string", self->db_data->db_path,
                                NULL);
@@ -581,17 +622,29 @@
     {
         on_password_received (password, self);
         secret_password_free (password);
+        g_clear_error (&err);
+        return;
     }
-    else if (self->window != NULL)
+
+    /* password == NULL: distinguish "not stored" (err == NULL, normal) from
+     * "keyring is broken" (err != NULL). For the broken case, disable the
+     * setting so we don't keep hitting the same failure on every launch
+     * (issue #446). In both cases, fall through to the password dialog so
+     * the user can still unlock. */
+    if (err != NULL)
+    {
+        application_disable_secret_service_runtime (self, err->message);
+        g_clear_error (&err);
+    }
+
+    if (self->window != NULL)
     {
-        /* No stored password — show the password dialog */
         PasswordDialog *dlg = password_dialog_new (PASSWORD_MODE_DECRYPT,
                                                    on_password_received,
                                                    self);
+        adw_dialog_set_can_close (ADW_DIALOG (dlg), FALSE);
         adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET (self->window));
     }
-
-    g_clear_error (&err);
 }
 
 static void
@@ -682,6 +735,7 @@
         PasswordDialog *dlg = password_dialog_new (PASSWORD_MODE_DECRYPT,
                                                    on_password_received,
                                                    self);
+        adw_dialog_set_can_close (ADW_DIALOG (dlg), FALSE);
         adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET (self->window));
     }
 }
@@ -972,6 +1026,7 @@
         PasswordDialog *dlg = password_dialog_new (PASSWORD_MODE_DECRYPT,
                                                    on_password_received,
                                                    self);
+        adw_dialog_set_can_close (ADW_DIALOG (dlg), FALSE);
         adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET (self->window));
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/otpclient-window.c 
new/OTPClient-5.0.5/src/gui/otpclient-window.c
--- old/OTPClient-5.0.3/src/gui/otpclient-window.c      2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/gui/otpclient-window.c      2026-05-21 
09:41:51.000000000 +0200
@@ -40,9 +40,12 @@
     GtkWidget *open_db_button;
     GtkWidget *no_db_create_button;
     GtkWidget *no_db_open_button;
+    GtkWidget *empty_state_add_button;
     GtkWidget *otp_list;
     GtkWidget *content_stack;
     GtkWidget *loading_status_page;
+    GtkWidget *locked_status_page;
+    GtkWidget *locked_unlock_button;
     GListStore *otp_store;
     GtkFilterListModel *filter_model;
     GtkCustomFilter *search_filter;
@@ -111,6 +114,14 @@
 
 static void schedule_hotp_flush (OTPClientWindow *self);
 
+static inline gboolean
+window_is_locked (OTPClientWindow *self)
+{
+    OTPClientApplication *app = OTPCLIENT_APPLICATION (
+        gtk_window_get_application (GTK_WINDOW (self)));
+    return app != NULL && otpclient_application_get_app_locked (app);
+}
+
 static void
 validity_widgets_free (ValidityWidgets *widgets)
 {
@@ -1058,26 +1069,51 @@
     if (self->content_stack == NULL || self->otp_store == NULL)
         return;
 
-    /* Three pre-list states: no DB at all (fresh install) → "no-db" CTA;
-     * an existing DB is being decrypted → "Unlocking…"; the loaded list is
-     * either populated or empty-but-unlocked, handled below. */
+    /* Pre-list states: locked supersedes everything (don't show the token
+     * list, or even the "empty"/"loading" pages, while locked). No DB at all
+     * (fresh install) → "no-db" CTA; an existing DB is being decrypted →
+     * "Unlocking…"; the loaded list is either populated or empty-but-unlocked,
+     * handled below. */
     OTPClientApplication *app = OTPCLIENT_APPLICATION (
         gtk_window_get_application (GTK_WINDOW (self)));
     DatabaseData *db_data = app != NULL ? otpclient_application_get_db_data 
(app) : NULL;
-    if (db_data == NULL)
-    {
-        gtk_stack_set_visible_child_name (GTK_STACK (self->content_stack), 
"no-db");
-        return;
-    }
-    if (db_data->in_memory_json_data == NULL)
-    {
-        gtk_stack_set_visible_child_name (GTK_STACK (self->content_stack), 
"loading");
-        return;
+    const gchar *target_page;
+    if (app != NULL && otpclient_application_get_app_locked (app))
+        target_page = "locked";
+    else if (db_data == NULL)
+        target_page = "no-db";
+    else if (db_data->in_memory_json_data == NULL)
+        target_page = "loading";
+    else
+        target_page = g_list_model_get_n_items (G_LIST_MODEL 
(self->otp_store)) > 0
+                          ? "list" : "empty";
+
+    const gchar *current_page = gtk_stack_get_visible_child_name (GTK_STACK 
(self->content_stack));
+    gtk_stack_set_visible_child_name (GTK_STACK (self->content_stack), 
target_page);
+
+    /* Move focus onto the page's primary widget when it first becomes
+     * visible, so the obvious next action (consume a token, add the first
+     * token, create the first DB) is one keystroke away rather than buried
+     * past the toolbar (issue #445). "loading" is transient — leave focus
+     * alone so it doesn't flicker. */
+    if (g_strcmp0 (target_page, current_page) != 0)
+    {
+        GtkWidget *focus_target = NULL;
+        if (g_strcmp0 (target_page, "list") == 0)
+            focus_target = self->otp_list;
+        else if (g_strcmp0 (target_page, "empty") == 0)
+            focus_target = self->empty_state_add_button;
+        else if (g_strcmp0 (target_page, "no-db") == 0)
+            focus_target = self->no_db_create_button;
+        else if (g_strcmp0 (target_page, "locked") == 0)
+            focus_target = self->locked_unlock_button;
+
+        if (focus_target != NULL)
+            gtk_widget_grab_focus (focus_target);
     }
 
-    guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self->otp_store));
-    gtk_stack_set_visible_child_name (GTK_STACK (self->content_stack),
-                                      n_items > 0 ? "list" : "empty");
+    if (g_strcmp0 (target_page, "list") != 0 && g_strcmp0 (target_page, 
"empty") != 0)
+        return;
 
     refresh_backup_age_banner (self);
 }
@@ -1143,18 +1179,59 @@
 {
     g_return_if_fail (OTPCLIENT_IS_WINDOW (self));
 
+    /* Every action that reads or mutates token data must be gated by the lock
+     * state. win.show-qr in particular renders the secret as a scannable QR. 
*/
     static const gchar * const db_actions[] = {
         "win.add-manual",
         "win.add-qr-file",
         "win.add-qr-webcam",
+        "win.add-qr-clipboard",
         "win.import",
         "win.export",
+        "win.edit-token",
+        "win.delete-token",
+        "win.show-qr",
+        "win.move-token",
+        "win.set-group",
+        "win.new-group",
+        "win.remove-from-group",
+        "win.rename-db",
+        "win.set-primary-db",
+        "win.remove-db",
+        "win.backup-tokens",
+        "win.restore-tokens",
     };
 
     for (gsize i = 0; i < G_N_ELEMENTS (db_actions); i++)
         gtk_widget_action_set_enabled (GTK_WIDGET (self), db_actions[i], 
enabled);
 }
 
+void
+otpclient_window_clear_displayed_otps (OTPClientWindow *self)
+{
+    g_return_if_fail (OTPCLIENT_IS_WINDOW (self));
+    if (self->otp_store == NULL)
+        return;
+
+    guint n = g_list_model_get_n_items (G_LIST_MODEL (self->otp_store));
+    for (guint i = 0; i < n; i++)
+    {
+        g_autoptr (OTPEntry) entry = g_list_model_get_item (G_LIST_MODEL 
(self->otp_store), i);
+        if (entry != NULL)
+            otp_entry_set_otp_value (entry, "");
+    }
+
+    if (self->otp_selection != NULL)
+        gtk_single_selection_set_selected (self->otp_selection, 
GTK_INVALID_LIST_POSITION);
+}
+
+void
+otpclient_window_refresh_content_page (OTPClientWindow *self)
+{
+    g_return_if_fail (OTPCLIENT_IS_WINDOW (self));
+    update_empty_state (self);
+}
+
 static void
 setup_otp_view (OTPClientWindow *self)
 {
@@ -1191,19 +1268,7 @@
     (void) action_name;
     (void) parameter;
 
-    OTPClientWindow *self = OTPCLIENT_WINDOW (widget);
-
-    /* Clear all displayed OTP values */
-    guint n = g_list_model_get_n_items (G_LIST_MODEL (self->otp_store));
-    for (guint i = 0; i < n; i++)
-    {
-        g_autoptr (OTPEntry) entry = g_list_model_get_item (G_LIST_MODEL 
(self->otp_store), i);
-        if (entry != NULL)
-            otp_entry_set_otp_value (entry, "");
-    }
-
-    /* Unselect all rows */
-    gtk_single_selection_set_selected (self->otp_selection, 
GTK_INVALID_LIST_POSITION);
+    otpclient_window_clear_displayed_otps (OTPCLIENT_WINDOW (widget));
 }
 
 static void
@@ -1638,6 +1703,13 @@
 {
     (void) pspec;
 
+    /* Refuse to reveal or copy any OTP while the database is locked. The
+     * locked-page swap normally hides the list, but a stray selection signal
+     * during the lock transition could still fire — bail before reading the
+     * cached OTP value. */
+    if (window_is_locked (self))
+        return;
+
     guint pos = gtk_single_selection_get_selected (selection);
     if (pos == GTK_INVALID_LIST_POSITION)
         return;
@@ -1835,6 +1907,9 @@
 
     OTPClientWindow *self = OTPCLIENT_WINDOW (user_data);
 
+    if (window_is_locked (self))
+        return NULL;
+
     /* Disable DnD while search filter is active */
     const gchar *search_text = gtk_editable_get_text (GTK_EDITABLE 
(self->search_entry));
     if (search_text != NULL && search_text[0] != '\0')
@@ -3155,6 +3230,18 @@
         g_action_group_activate_action (G_ACTION_GROUP (app), "lock", NULL);
 }
 
+static void
+locked_unlock_button_clicked (GtkButton       *button,
+                              OTPClientWindow *self)
+{
+    (void) button;
+
+    OTPClientApplication *app = OTPCLIENT_APPLICATION (
+        gtk_window_get_application (GTK_WINDOW (self)));
+    if (app != NULL)
+        lock_app_present_unlock_dialog (app);
+}
+
 static gboolean
 on_db_modified_idle (gpointer user_data)
 {
@@ -3357,6 +3444,9 @@
     gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
     gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
 
+    if (window_is_locked (self))
+        return;
+
     /* Select the row under the cursor so a right-click alone is enough. */
     OTPEntry *picked = pick_entry_at (self, x, y, NULL);
     if (picked != NULL)
@@ -4115,6 +4205,7 @@
     g_signal_connect (self->open_db_button, "clicked", G_CALLBACK 
(open_db_button_clicked), self);
     g_signal_connect (self->no_db_create_button, "clicked", G_CALLBACK 
(new_db_button_clicked), self);
     g_signal_connect (self->no_db_open_button,   "clicked", G_CALLBACK 
(open_db_button_clicked), self);
+    g_signal_connect (self->locked_unlock_button, "clicked", G_CALLBACK 
(locked_unlock_button_clicked), self);
 
     setup_dnd (self);
 
@@ -4163,9 +4254,12 @@
     gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
open_db_button);
     gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
no_db_create_button);
     gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
no_db_open_button);
+    gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
empty_state_add_button);
     gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
otp_list);
     gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
content_stack);
     gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
loading_status_page);
+    gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
locked_status_page);
+    gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
locked_unlock_button);
     gtk_widget_class_bind_template_child (widget_class, OTPClientWindow, 
group_dropdown);
 
     gtk_widget_class_add_binding_action (widget_class, GDK_KEY_q, 
GDK_CONTROL_MASK, "window.close", NULL);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/otpclient-window.h 
new/OTPClient-5.0.5/src/gui/otpclient-window.h
--- old/OTPClient-5.0.3/src/gui/otpclient-window.h      2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/gui/otpclient-window.h      2026-05-21 
09:41:51.000000000 +0200
@@ -42,6 +42,10 @@
 void                otpclient_window_set_db_actions_enabled (OTPClientWindow 
*self,
                                                              gboolean         
enabled);
 
+void                otpclient_window_clear_displayed_otps (OTPClientWindow 
*self);
+
+void                otpclient_window_refresh_content_page (OTPClientWindow 
*self);
+
 void                otpclient_window_flush_pending_writes (OTPClientWindow 
*self);
 
 void                otpclient_window_clear_clipboard_now (OTPClientWindow 
*self);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.0.3/src/gui/ui/window.ui 
new/OTPClient-5.0.5/src/gui/ui/window.ui
--- old/OTPClient-5.0.3/src/gui/ui/window.ui    2026-05-15 14:48:27.000000000 
+0200
+++ new/OTPClient-5.0.5/src/gui/ui/window.ui    2026-05-21 09:41:51.000000000 
+0200
@@ -245,6 +245,30 @@
                         </property>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkStackPage">
+                        <property name="name">locked</property>
+                        <property name="child">
+                          <object class="AdwStatusPage" 
id="locked_status_page">
+                            <property 
name="icon-name">changes-prevent-symbolic</property>
+                            <property name="title" translatable="yes">Database 
Locked</property>
+                            <property name="description" 
translatable="yes">Enter your password to unlock.</property>
+                            <property name="vexpand">True</property>
+                            <property name="hexpand">True</property>
+                            <child>
+                              <object class="GtkButton" 
id="locked_unlock_button">
+                                <property name="label" 
translatable="yes">Unlock</property>
+                                <property name="halign">center</property>
+                                <style>
+                                  <class name="suggested-action"/>
+                                  <class name="pill"/>
+                                </style>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
                   </object>
                 </property>
               </object>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/OTPClient-5.0.3/src/search-provider/search-provider.c 
new/OTPClient-5.0.5/src/search-provider/search-provider.c
--- old/OTPClient-5.0.3/src/search-provider/search-provider.c   2026-05-15 
14:48:27.000000000 +0200
+++ new/OTPClient-5.0.5/src/search-provider/search-provider.c   2026-05-21 
09:41:51.000000000 +0200
@@ -146,8 +146,18 @@
     if (!gsettings_common_get_use_secret_service ())
         return;
 
-    gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, NULL,
+    /* Issue #446: surface broken-keyring errors via a warning instead of
+     * silently returning. Don't mutate GSettings here — the search provider
+     * is a passive consumer; the GUI app owns the setting. */
+    GError *ss_err = NULL;
+    gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, &ss_err,
                                                "string", db_path, NULL);
+    if (ss_err != NULL) {
+        g_warning ("Search provider: secret service lookup failed for %s: %s",
+                   db_path, ss_err->message);
+        g_clear_error (&ss_err);
+        return;
+    }
     if (pwd == NULL)
         return;
 
@@ -449,8 +459,14 @@
     if (entry == NULL || entry->db_path == NULL) return NULL;
     if (!gsettings_common_get_use_secret_service ()) return NULL;
 
-    gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, NULL,
+    GError *ss_err = NULL;
+    gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, &ss_err,
                                                "string", entry->db_path, NULL);
+    if (ss_err != NULL) {
+        g_warning ("Search provider: secret service lookup failed: %s", 
ss_err->message);
+        g_clear_error (&ss_err);
+        return NULL;
+    }
     if (pwd == NULL) return NULL;
 
     DatabaseData *db_data = g_new0 (DatabaseData, 1);

Reply via email to