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-08-09 21:33:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/otpclient (Old)
 and      /work/SRC/openSUSE:Factory/.otpclient.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "otpclient"

Sun Aug  9 21:33:39 2026 rev:54 rq:1369985 version:5.1.7

Changes:
--------
--- /work/SRC/openSUSE:Factory/otpclient/otpclient.changes      2026-08-02 
23:15:16.174017004 +0200
+++ /work/SRC/openSUSE:Factory/.otpclient.new.16738/otpclient.changes   
2026-08-09 21:35:23.774297496 +0200
@@ -1,0 +2,13 @@
+Wed Aug  5 06:42:46 UTC 2026 - Paolo Stivanin <[email protected]>
+
+- Update to 5.1.7:
+  * FIX: with minimize-to-tray enabled on a desktop that has no
+    system tray (stock GNOME without the AppIndicator extension,
+    i3bar, polybar), closing the window hid it with no icon to
+    restore it, leaving an invisible process holding a decrypted
+    database that only killall could stop. The window is now hidden
+    only once a tray host has accepted the icon, and if the tray
+    disappears while the window is hidden the window comes back
+    (#405)
+
+-------------------------------------------------------------------

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

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

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

Other differences:
------------------
++++++ otpclient.spec ++++++
--- /var/tmp/diff_new_pack.4lVqfX/_old  2026-08-09 21:35:24.286314961 +0200
+++ /var/tmp/diff_new_pack.4lVqfX/_new  2026-08-09 21:35:24.286314961 +0200
@@ -18,7 +18,7 @@
 
 %define uclname OTPClient
 Name:           otpclient
-Version:        5.1.6
+Version:        5.1.7
 Release:        0
 Summary:        Simple GTK+ client for managing TOTP and HOTP
 License:        GPL-3.0-or-later

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.4lVqfX/_old  2026-08-09 21:35:24.338316735 +0200
+++ /var/tmp/diff_new_pack.4lVqfX/_new  2026-08-09 21:35:24.342316872 +0200
@@ -1,6 +1,6 @@
-mtime: 1784788483
-commit: 500e6c52f63643c24e6f0b17e6fd96219ec1b31de29f7af9ae7132ddb510fb17
+mtime: 1785912198
+commit: f5e85cbf85fbc5370791666edeaed64554619ee2b7814a17142e78e9c325e62b
 url: https://src.opensuse.org/GNOME/otpclient
-revision: 500e6c52f63643c24e6f0b17e6fd96219ec1b31de29f7af9ae7132ddb510fb17
+revision: f5e85cbf85fbc5370791666edeaed64554619ee2b7814a17142e78e9c325e62b
 projectscmsync: https://src.opensuse.org/GNOME/_ObsPrj
 

++++++ 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-08-05 08:43:18.000000000 +0200
@@ -0,0 +1,4 @@
+*.obscpio
+*.osc
+_build.*
+.pbuild


++++++ v5.1.6.tar.gz -> v5.1.7.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.1.6/CMakeLists.txt 
new/OTPClient-5.1.7/CMakeLists.txt
--- old/OTPClient-5.1.6/CMakeLists.txt  2026-07-22 15:25:23.000000000 +0200
+++ new/OTPClient-5.1.7/CMakeLists.txt  2026-08-04 10:31:45.000000000 +0200
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.25)
-project(OTPClient VERSION "5.1.6" LANGUAGES "C")
+project(OTPClient VERSION "5.1.7" LANGUAGES "C")
 include(GNUInstallDirs)
 include(CTest)
 
@@ -12,7 +12,7 @@
 set(CMAKE_C_EXTENSIONS OFF)
 
 option(IS_FLATPAK "Use flatpak app's config folder to store the database" OFF)
-option(ENABLE_MINIMIZE_TO_TRAY "Enable minimize to tray feature" OFF)
+option(ENABLE_MINIMIZE_TO_TRAY "Enable minimize to tray feature" ON)
 option(BUILD_GUI "Build the GUI" ON)
 option(BUILD_CLI "Build the CLI" ON)
 option(BUILD_SEARCH_PROVIDER "Build the D-Bus search provider" ON)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.1.6/README.md 
new/OTPClient-5.1.7/README.md
--- old/OTPClient-5.1.6/README.md       2026-07-22 15:25:23.000000000 +0200
+++ new/OTPClient-5.1.7/README.md       2026-08-04 10:31:45.000000000 +0200
@@ -146,7 +146,7 @@
 | `BUILD_CLI`                | `ON`    | Build the command-line interface 
(`otpclient-cli`) and completions   |
 | `BUILD_SEARCH_PROVIDER`    | `ON`    | Build the GNOME Shell / KRunner D-Bus 
daemon                         |
 | `IS_FLATPAK`               | `OFF`   | Use the flatpak app's config folder 
for the database                 |
-| `ENABLE_MINIMIZE_TO_TRAY`  | `OFF`   | Enable minimize-to-tray support in 
the GUI                           |
+| `ENABLE_MINIMIZE_TO_TRAY`  | `ON`    | Offer minimize-to-tray in the GUI 
(needs a StatusNotifierWatcher)    |
 
 `Release` builds enable LTO and additional hardening flags
 (`-fcf-protection=full`, `-fzero-call-used-regs`, `-fstrict-flex-arrays=2`,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/OTPClient-5.1.6/data/com.github.paolostivanin.OTPClient.appdata.xml 
new/OTPClient-5.1.7/data/com.github.paolostivanin.OTPClient.appdata.xml
--- old/OTPClient-5.1.6/data/com.github.paolostivanin.OTPClient.appdata.xml     
2026-07-22 15:25:23.000000000 +0200
+++ new/OTPClient-5.1.7/data/com.github.paolostivanin.OTPClient.appdata.xml     
2026-08-04 10:31:45.000000000 +0200
@@ -90,6 +90,15 @@
   </content_rating>
 
   <releases>
+    <release version="5.1.7" date="2026-08-04">
+      <description>
+        <p>Minimize-to-tray is now offered in default builds. It had been an 
opt-in build flag because turning it on could strand the application: on a 
desktop with no system tray, closing the window hid it anyway and left 
OTPClient running invisibly, holding a decrypted database, with no icon to 
bring it back. The app now confirms a tray host is really there before it will 
hide to one, and restores the window if the tray goes away. The feature itself 
is still off by default in Settings.</p>
+        <ul>
+          <li>FIX: with minimize-to-tray enabled on a desktop that has no 
system tray (stock GNOME without the AppIndicator extension, i3bar, polybar), 
closing the window hid it with no icon to restore it, leaving an invisible 
process holding a decrypted database that only killall could stop. The window 
is now hidden only once a tray host has accepted the icon, and if the tray 
disappears while the window is hidden the window comes back (#405)</li>
+          <li>BUILD: ENABLE_MINIMIZE_TO_TRAY now defaults to ON, so 
distribution packages offer the feature without a custom build. The tray has 
not needed libayatana-appindicator since it moved to a direct 
StatusNotifierItem implementation over GDBus, so this adds no dependency. The 
minimize-to-tray setting itself is unchanged and still defaults to off, and its 
switch is greyed out where no tray was detected (#405)</li>
+        </ul>
+      </description>
+    </release>
     <release version="5.1.6" date="2026-07-22">
       <description>
         <p>Bug-fix release. Auto-Lock was effectively unusable: on any profile 
that enabled it without changing the timeout, the database re-locked about five 
seconds after every unlock, and the unlock prompt could not be dismissed and 
quit the whole application when closed, leaving Settings unreachable. The 
timeout now defaults to five minutes, and the unlock prompt is dismissable and 
returns to a locked screen instead of quitting.</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.1.6/src/gui/dialogs/settings-dialog.c 
new/OTPClient-5.1.7/src/gui/dialogs/settings-dialog.c
--- old/OTPClient-5.1.6/src/gui/dialogs/settings-dialog.c       2026-07-22 
15:25:23.000000000 +0200
+++ new/OTPClient-5.1.7/src/gui/dialogs/settings-dialog.c       2026-08-04 
10:31:45.000000000 +0200
@@ -7,6 +7,9 @@
 #include "secret-schema.h"
 #include "settings-import-export.h"
 #include "otp-button-row.h"
+#ifdef ENABLE_MINIMIZE_TO_TRAY
+#include "tray.h"
+#endif
 
 struct _SettingsDialog
 {
@@ -661,6 +664,14 @@
                                otpclient_application_get_minimize_to_tray 
(app));
     g_signal_connect (self->minimize_to_tray_switch, "notify::active",
                       G_CALLBACK (on_minimize_to_tray_toggled), self);
+    /* Disable rather than clear the preference: someone who normally runs a
+     * desktop with a tray shouldn't lose the setting after one session 
without. */
+    if (!otpclient_tray_is_available ())
+    {
+        gtk_widget_set_sensitive (self->minimize_to_tray_switch, FALSE);
+        adw_action_row_set_subtitle (ADW_ACTION_ROW 
(self->minimize_to_tray_switch),
+                                     _("No system tray was detected on this 
desktop"));
+    }
     adw_preferences_group_add (integration_group, 
self->minimize_to_tray_switch);
 #endif
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.1.6/src/gui/tray.c 
new/OTPClient-5.1.7/src/gui/tray.c
--- old/OTPClient-5.1.6/src/gui/tray.c  2026-07-22 15:25:23.000000000 +0200
+++ new/OTPClient-5.1.7/src/gui/tray.c  2026-08-04 10:31:45.000000000 +0200
@@ -1,5 +1,6 @@
 #ifdef ENABLE_MINIMIZE_TO_TRAY
 
+#include <unistd.h>
 #include <gio/gio.h>
 #include <gtk/gtk.h>
 #include "tray.h"
@@ -8,6 +9,9 @@
 #define SNI_OBJECT_PATH    "/StatusNotifierItem"
 #define DBUSMENU_OBJECT_PATH "/StatusNotifierMenu"
 
+#define WATCHER_BUS_NAME     "org.kde.StatusNotifierWatcher"
+#define WATCHER_OBJECT_PATH  "/StatusNotifierWatcher"
+
 #define MENU_ID_SHOW  1
 #define MENU_ID_QUIT  2
 
@@ -65,24 +69,48 @@
     "  </interface>"
     "</node>";
 
+/* Whether a StatusNotifierWatcher (and, where it says so, a host behind it) is
+ * on the session bus. UNKNOWN covers the startup window before the name 
watcher
+ * has reported in: the UI treats it as "maybe", while the close-request 
handler
+ * keys off `published` and so stays fail-safe either way. */
+typedef enum
+{
+    TRAY_HOST_UNKNOWN = 0,
+    TRAY_HOST_AVAILABLE,
+    TRAY_HOST_UNAVAILABLE
+} TrayHostState;
+
 typedef struct
 {
     OTPClientApplication *app;
-    GDBusConnection *connection;
+    GDBusConnection *connection;        /* bus we published the item on */
+    GDBusConnection *watch_connection;  /* bus the watcher was spotted on */
     guint sni_registration_id;
     guint menu_registration_id;
     guint bus_name_id;
+    guint watcher_watch_id;
+    guint host_signal_id;
     gulong close_handler_id;
     gchar *bus_name;
-    gboolean active;
+    TrayHostState host;
+    gboolean desired;       /* the user's minimize-to-tray preference */
+    gboolean publishing;    /* bus name request in flight, not yet confirmed */
+    gboolean published;     /* the watcher accepted our item: a tray icon 
exists */
+    gboolean holding;       /* a g_application_hold of ours is outstanding */
+    gboolean window_hidden; /* we tucked the window away on close */
 } TrayData;
 
 static TrayData *tray_data = NULL;
 
+static void tray_publish   (TrayData *td);
+static void tray_unpublish (TrayData *td);
+
 static void
-show_window (OTPClientApplication *app)
+show_window (TrayData *td)
 {
-    GtkWindow *window = gtk_application_get_active_window (GTK_APPLICATION 
(app));
+    td->window_hidden = FALSE;
+
+    GtkWindow *window = gtk_application_get_active_window (GTK_APPLICATION 
(td->app));
     if (window != NULL)
     {
         gtk_widget_set_visible (GTK_WIDGET (window), TRUE);
@@ -90,6 +118,24 @@
     }
 }
 
+/* The hold is what lets the app outlive its only window while it sits in the
+ * tray. It must track `published` exactly: holding without a visible icon
+ * leaves an unreachable process running with a decrypted database in memory. 
*/
+static void
+tray_sync_hold (TrayData *td)
+{
+    if (td->published && !td->holding)
+    {
+        g_application_hold (G_APPLICATION (td->app));
+        td->holding = TRUE;
+    }
+    else if (!td->published && td->holding)
+    {
+        td->holding = FALSE;
+        g_application_release (G_APPLICATION (td->app));
+    }
+}
+
 /* --- StatusNotifierItem D-Bus interface --- */
 
 static void
@@ -113,7 +159,7 @@
     if (g_strcmp0 (method_name, "Activate") == 0 ||
         g_strcmp0 (method_name, "SecondaryActivate") == 0)
     {
-        show_window (td->app);
+        show_window (td);
         g_dbus_method_invocation_return_value (invocation, NULL);
     }
     else
@@ -148,7 +194,7 @@
     if (g_strcmp0 (property_name, "Title") == 0)
         return g_variant_new_string ("OTPClient");
     if (g_strcmp0 (property_name, "Status") == 0)
-        return g_variant_new_string (td->active ? "Active" : "Passive");
+        return g_variant_new_string (td->desired ? "Active" : "Passive");
     if (g_strcmp0 (property_name, "IconName") == 0)
         return g_variant_new_string ("com.github.paolostivanin.OTPClient");
     if (g_strcmp0 (property_name, "Menu") == 0)
@@ -237,7 +283,7 @@
         if (g_strcmp0 (event_id, "clicked") == 0)
         {
             if (id == MENU_ID_SHOW)
-                show_window (td->app);
+                show_window (td);
             else if (id == MENU_ID_QUIT)
                 g_application_quit (G_APPLICATION (td->app));
         }
@@ -297,16 +343,20 @@
 {
     TrayData *td = user_data;
 
-    if (td->active && otpclient_application_get_minimize_to_tray (td->app))
+    /* Only swallow the close when there is an icon to restore the window from.
+     * Without a live tray item the user would be left with an invisible,
+     * unquittable process, so fall through to the normal close instead. */
+    if (td->published && otpclient_application_get_minimize_to_tray (td->app))
     {
         gtk_widget_set_visible (GTK_WIDGET (window), FALSE);
+        td->window_hidden = TRUE;
         return TRUE;
     }
 
     return FALSE;
 }
 
-/* --- Bus name acquired / registration --- */
+/* --- Publishing the StatusNotifierItem --- */
 
 static void
 on_bus_acquired (GDBusConnection *connection,
@@ -364,6 +414,43 @@
     }
 }
 
+/* Every async callback below re-reads the `tray_data` singleton instead of
+ * trusting user_data: cleanup NULLs it, so this doubles as a liveness check on
+ * the pointer the call was issued with. */
+static void
+on_item_registered (GObject      *source,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+    (void) user_data;
+
+    GError *err = NULL;
+    g_autoptr (GVariant) reply =
+        g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &err);
+
+    /* A disable or an unpublish while the call was in flight clears
+     * `publishing`, which makes this reply stale: acting on it would resurrect
+     * an item we already tore down, hold included. */
+    if (tray_data == NULL || !tray_data->publishing)
+    {
+        g_clear_error (&err);
+        return;
+    }
+
+    if (reply == NULL)
+    {
+        g_warning ("StatusNotifierWatcher refused our tray item: %s", 
err->message);
+        g_clear_error (&err);
+        tray_data->host = TRAY_HOST_UNAVAILABLE;
+        tray_unpublish (tray_data);
+        return;
+    }
+
+    tray_data->publishing = FALSE;
+    tray_data->published = TRUE;
+    tray_sync_hold (tray_data);
+}
+
 static void
 on_name_acquired (GDBusConnection *connection,
                   const gchar     *name,
@@ -371,16 +458,18 @@
 {
     (void) user_data;
 
-    /* Register with the StatusNotifierWatcher */
+    /* Ask the watcher to adopt the item, and this time listen to the answer:
+     * whether an icon actually exists decides whether closing the window is
+     * allowed to hide it. */
     g_dbus_connection_call (connection,
-                            "org.kde.StatusNotifierWatcher",
-                            "/StatusNotifierWatcher",
+                            WATCHER_BUS_NAME,
+                            WATCHER_OBJECT_PATH,
                             "org.kde.StatusNotifierWatcher",
                             "RegisterStatusNotifierItem",
                             g_variant_new ("(s)", name),
                             NULL,
                             G_DBUS_CALL_FLAGS_NONE,
-                            -1, NULL, NULL, NULL);
+                            -1, NULL, on_item_registered, NULL);
 }
 
 static void
@@ -392,7 +481,199 @@
     (void) name;
     (void) user_data;
 
+    if (tray_data == NULL)
+        return;
+
     g_info ("Lost bus name for StatusNotifierItem");
+    tray_unpublish (tray_data);
+}
+
+static void
+tray_publish (TrayData *td)
+{
+    if (td->publishing || td->published)
+        return;
+    if (!td->desired || td->host != TRAY_HOST_AVAILABLE)
+        return;
+
+    td->publishing = TRUE;
+    td->bus_name_id =
+        g_bus_own_name (G_BUS_TYPE_SESSION,
+                        td->bus_name,
+                        G_BUS_NAME_OWNER_FLAGS_NONE,
+                        on_bus_acquired,
+                        on_name_acquired,
+                        on_name_lost,
+                        td,
+                        NULL);
+}
+
+static void
+tray_unpublish (TrayData *td)
+{
+    if (td->connection != NULL)
+    {
+        if (td->sni_registration_id != 0)
+        {
+            g_dbus_connection_unregister_object (td->connection, 
td->sni_registration_id);
+            td->sni_registration_id = 0;
+        }
+        if (td->menu_registration_id != 0)
+        {
+            g_dbus_connection_unregister_object (td->connection, 
td->menu_registration_id);
+            td->menu_registration_id = 0;
+        }
+        td->connection = NULL;
+    }
+
+    if (td->bus_name_id != 0)
+    {
+        g_bus_unown_name (td->bus_name_id);
+        td->bus_name_id = 0;
+    }
+
+    td->publishing = FALSE;
+    td->published = FALSE;
+    tray_sync_hold (td);
+
+    /* The panel can go away (extension toggled off, shell restarted) while the
+     * window is tucked into the tray. Bring it back rather than stranding it. 
*/
+    if (td->window_hidden)
+        show_window (td);
+}
+
+/* --- StatusNotifierWatcher detection --- */
+
+static void
+tray_set_host_available (TrayData *td,
+                         gboolean  available)
+{
+    TrayHostState state = available ? TRAY_HOST_AVAILABLE : 
TRAY_HOST_UNAVAILABLE;
+
+    if (td->host == state)
+        return;
+
+    td->host = state;
+
+    if (available)
+        tray_publish (td);
+    else
+        tray_unpublish (td);
+}
+
+static void
+on_host_registered (GDBusConnection *connection,
+                    const gchar     *sender_name,
+                    const gchar     *object_path,
+                    const gchar     *interface_name,
+                    const gchar     *signal_name,
+                    GVariant        *parameters,
+                    gpointer         user_data)
+{
+    (void) connection;
+    (void) sender_name;
+    (void) object_path;
+    (void) interface_name;
+    (void) signal_name;
+    (void) parameters;
+
+    tray_set_host_available (user_data, TRUE);
+}
+
+static void
+on_host_property_read (GObject      *source,
+                       GAsyncResult *res,
+                       gpointer      user_data)
+{
+    (void) user_data;
+
+    GError *err = NULL;
+    g_autoptr (GVariant) reply =
+        g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &err);
+
+    /* watch_connection is cleared when the watcher goes away, so a NULL here
+     * means the watcher we were asking about is already gone. */
+    if (tray_data == NULL || tray_data->watch_connection == NULL)
+    {
+        g_clear_error (&err);
+        return;
+    }
+
+    gboolean host_registered = TRUE;
+
+    if (reply == NULL)
+    {
+        /* Not every watcher implements the property. Its presence on the bus 
is
+         * a good enough signal on its own, so don't refuse the feature over 
it. */
+        g_debug ("Could not read IsStatusNotifierHostRegistered: %s", 
err->message);
+        g_clear_error (&err);
+    }
+    else
+    {
+        g_autoptr (GVariant) value = NULL;
+        g_variant_get (reply, "(v)", &value);
+        if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
+            host_registered = g_variant_get_boolean (value);
+    }
+
+    tray_set_host_available (tray_data, host_registered);
+}
+
+static void
+on_watcher_appeared (GDBusConnection *connection,
+                     const gchar     *name,
+                     const gchar     *name_owner,
+                     gpointer         user_data)
+{
+    (void) name;
+
+    TrayData *td = user_data;
+    g_set_object (&td->watch_connection, connection);
+
+    /* A watcher can be up before any host has registered with it, so keep
+     * listening after the initial property read. */
+    td->host_signal_id =
+        g_dbus_connection_signal_subscribe (connection,
+                                            name_owner,
+                                            "org.kde.StatusNotifierWatcher",
+                                            "StatusNotifierHostRegistered",
+                                            WATCHER_OBJECT_PATH,
+                                            NULL,
+                                            G_DBUS_SIGNAL_FLAGS_NONE,
+                                            on_host_registered,
+                                            td,
+                                            NULL);
+
+    g_dbus_connection_call (connection,
+                            WATCHER_BUS_NAME,
+                            WATCHER_OBJECT_PATH,
+                            "org.freedesktop.DBus.Properties",
+                            "Get",
+                            g_variant_new ("(ss)", 
"org.kde.StatusNotifierWatcher",
+                                                   
"IsStatusNotifierHostRegistered"),
+                            G_VARIANT_TYPE ("(v)"),
+                            G_DBUS_CALL_FLAGS_NONE,
+                            -1, NULL, on_host_property_read, NULL);
+}
+
+static void
+on_watcher_vanished (GDBusConnection *connection,
+                     const gchar     *name,
+                     gpointer         user_data)
+{
+    (void) connection;
+    (void) name;
+
+    TrayData *td = user_data;
+
+    if (td->host_signal_id != 0 && td->watch_connection != NULL)
+    {
+        g_dbus_connection_signal_unsubscribe (td->watch_connection, 
td->host_signal_id);
+        td->host_signal_id = 0;
+    }
+    g_clear_object (&td->watch_connection);
+
+    tray_set_host_available (td, FALSE);
 }
 
 /* --- Public API --- */
@@ -405,21 +686,12 @@
 
     tray_data = g_new0 (TrayData, 1);
     tray_data->app = app;
-    tray_data->active = otpclient_application_get_minimize_to_tray (app);
+    tray_data->host = TRAY_HOST_UNKNOWN;
+    tray_data->desired = otpclient_application_get_minimize_to_tray (app);
 
     tray_data->bus_name = g_strdup_printf ("org.kde.StatusNotifierItem-%d-1",
                                             getpid ());
 
-    tray_data->bus_name_id =
-        g_bus_own_name (G_BUS_TYPE_SESSION,
-                        tray_data->bus_name,
-                        G_BUS_NAME_OWNER_FLAGS_NONE,
-                        on_bus_acquired,
-                        on_name_acquired,
-                        on_name_lost,
-                        tray_data,
-                        NULL);
-
     GtkWindow *window = gtk_application_get_active_window (GTK_APPLICATION 
(app));
     if (window != NULL)
     {
@@ -428,8 +700,18 @@
                               G_CALLBACK (on_close_request), tray_data);
     }
 
-    if (tray_data->active)
-        g_application_hold (G_APPLICATION (app));
+    /* The item is published lazily, once a watcher is known to be there and 
the
+     * user has actually asked for minimize-to-tray. Registering 
unconditionally
+     * would park a Passive item in the tray overflow of every desktop that
+     * shows them, for a feature the user never enabled. */
+    tray_data->watcher_watch_id =
+        g_bus_watch_name (G_BUS_TYPE_SESSION,
+                          WATCHER_BUS_NAME,
+                          G_BUS_NAME_WATCHER_FLAGS_NONE,
+                          on_watcher_appeared,
+                          on_watcher_vanished,
+                          tray_data,
+                          NULL);
 }
 
 void
@@ -441,34 +723,40 @@
         return;
     }
 
-    if (!tray_data->active)
-    {
-        tray_data->active = TRUE;
-        g_application_hold (G_APPLICATION (app));
+    if (tray_data->desired)
+        return;
 
-        if (tray_data->connection != NULL)
-        {
-            g_dbus_connection_emit_signal (tray_data->connection,
-                                           NULL,
-                                           SNI_OBJECT_PATH,
-                                           "org.kde.StatusNotifierItem",
-                                           "NewStatus",
-                                           g_variant_new ("(s)", "Active"),
-                                           NULL);
-        }
+    tray_data->desired = TRUE;
+
+    if (tray_data->published && tray_data->connection != NULL)
+    {
+        g_dbus_connection_emit_signal (tray_data->connection,
+                                       NULL,
+                                       SNI_OBJECT_PATH,
+                                       "org.kde.StatusNotifierItem",
+                                       "NewStatus",
+                                       g_variant_new ("(s)", "Active"),
+                                       NULL);
+    }
+    else
+    {
+        tray_publish (tray_data);
     }
 }
 
 void
 otpclient_tray_disable (OTPClientApplication *app)
 {
-    if (tray_data == NULL || !tray_data->active)
+    (void) app;
+
+    if (tray_data == NULL || !tray_data->desired)
         return;
 
-    tray_data->active = FALSE;
-    g_application_release (G_APPLICATION (app));
+    tray_data->desired = FALSE;
 
-    if (tray_data->connection != NULL)
+    /* Tell any host that cached the item before tearing it down, so it doesn't
+     * hold on to a stale Active entry. */
+    if (tray_data->published && tray_data->connection != NULL)
     {
         g_dbus_connection_emit_signal (tray_data->connection,
                                        NULL,
@@ -478,6 +766,14 @@
                                        g_variant_new ("(s)", "Passive"),
                                        NULL);
     }
+
+    tray_unpublish (tray_data);
+}
+
+gboolean
+otpclient_tray_is_available (void)
+{
+    return tray_data != NULL && tray_data->host != TRAY_HOST_UNAVAILABLE;
 }
 
 void
@@ -493,21 +789,17 @@
             g_signal_handler_disconnect (window, tray_data->close_handler_id);
     }
 
-    if (tray_data->connection != NULL)
-    {
-        if (tray_data->sni_registration_id != 0)
-            g_dbus_connection_unregister_object (tray_data->connection,
-                                                  
tray_data->sni_registration_id);
-        if (tray_data->menu_registration_id != 0)
-            g_dbus_connection_unregister_object (tray_data->connection,
-                                                  
tray_data->menu_registration_id);
-    }
-
-    if (tray_data->bus_name_id != 0)
-        g_bus_unown_name (tray_data->bus_name_id);
-
-    if (tray_data->active)
-        g_application_release (G_APPLICATION (app));
+    if (tray_data->host_signal_id != 0 && tray_data->watch_connection != NULL)
+        g_dbus_connection_signal_unsubscribe (tray_data->watch_connection,
+                                              tray_data->host_signal_id);
+    g_clear_object (&tray_data->watch_connection);
+
+    if (tray_data->watcher_watch_id != 0)
+        g_bus_unwatch_name (tray_data->watcher_watch_id);
+
+    /* Clears the hold too, so the teardown doesn't leave the app held. */
+    tray_data->window_hidden = FALSE;
+    tray_unpublish (tray_data);
 
     g_free (tray_data->bus_name);
     g_free (tray_data);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/OTPClient-5.1.6/src/gui/tray.h 
new/OTPClient-5.1.7/src/gui/tray.h
--- old/OTPClient-5.1.6/src/gui/tray.h  2026-07-22 15:25:23.000000000 +0200
+++ new/OTPClient-5.1.7/src/gui/tray.h  2026-08-04 10:31:45.000000000 +0200
@@ -11,6 +11,11 @@
 void otpclient_tray_disable (OTPClientApplication *app);
 void otpclient_tray_cleanup (OTPClientApplication *app);
 
+/* FALSE only once we know there is no StatusNotifierWatcher on the session 
bus,
+ * so the UI can tell the user the feature won't work here. Callers that need a
+ * hard guarantee an icon exists must not rely on this. */
+gboolean otpclient_tray_is_available (void);
+
 G_END_DECLS
 
 #endif

Reply via email to