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
