1010#include < memory>
1111#include < mutex>
1212#include < string>
13+ #include < thread>
1314#include < utility>
1415
1516// lib includes
2122
2223namespace tray_linux {
2324 /* *
24- * Currently shown notification object
25+ * Notification element struct
2526 */
26- NotifyNotification *notification_current = nullptr ; // NOSONAR(cpp:S5421) - mutable state, not const
27+ struct notification_data {
28+ /* *
29+ * @brief Notification object
30+ */
31+ NotifyNotification *obj = nullptr ;
32+ /* *
33+ * @brief Notification callback
34+ */
35+ void (*cb)() = nullptr ;
36+ /* *
37+ * @brief Notification shown indicator
38+ */
39+ bool shown = false ;
40+ /* *
41+ * @brief Notification mutex for async thread synchronization
42+ */
43+ std::mutex mutex;
44+ };
45+
2746 /* *
28- * Currently shown notification callback
47+ * Currently shown notifications
2948 */
30- void (*notification_current_callback)() = nullptr ; // NOSONAR(cpp:S5421) - mutable state, not const
49+ std::vector<std::shared_ptr<notification_data>> notifications ; // NOSONAR(cpp:S5421) - mutable state, not const
3150 /* *
32- * Lock for currently shown notification/callback
51+ * Lock for currently shown notifications vector
3352 */
34- std::mutex notification_mutex; // NOSONAR(cpp:S5421) - mutable state, not const
53+ std::mutex notifications_mutex; // NOSONAR(cpp:S5421) - mutable state, not const
54+
3555 /* *
3656 * QtTrayMenu instance
3757 */
@@ -42,39 +62,77 @@ namespace tray_linux {
4262 void (*log_callback)(int , const char *) = nullptr ; // NOSONAR(cpp:S5421) - mutable state, not const
4363
4464 /* *
45- * @brief Initialize notifications
46- * @param app_name application name for notifications
47- * @return true if successful
65+ * @brief Acknowledge notification asynchronously with timeout to avoid Dbus lockups
66+ * @param notification - Tray notification to close
67+ * @param timeout - optional timeout for async run in ms (default: 1000)
4868 */
49- bool init_notify (const char *app_name) {
50- if (!notify_is_initted ()) {
51- std::scoped_lock lock (notification_mutex);
52- return notify_init (app_name);
69+ void async_tray_notification_acknowledge_ (const std::shared_ptr<notification_data> ¬ification, int timeout = 1000 ) {
70+ std::thread t ([notification]() { // NOSONAR(cpp:S6168) - jthread is only available on C++20 onwards
71+ std::scoped_lock lock (notification->mutex );
72+ if (notification->shown && notification->obj != nullptr && NOTIFY_IS_NOTIFICATION (notification->obj ) && notify_notification_close (notification->obj , nullptr )) {
73+ notification->shown = false ;
74+ g_object_unref (G_OBJECT (notification->obj ));
75+ notification->obj = nullptr ;
76+ notification->cb = nullptr ;
77+ }
78+ });
79+ while (notification->obj != nullptr && timeout > 0 ) {
80+ std::this_thread::sleep_for (std::chrono::milliseconds (10 ));
81+ timeout -= 10 ;
82+ }
83+ if (timeout > 0 ) {
84+ // Finished. Join thread.
85+ t.join ();
86+ } else {
87+ // Timed out. Detach thread and continue.
88+ t.detach (); // NOSONAR(cpp:S5962) - Keep this running until it times out by itself (usually after 25 seconds due to DBus)
5389 }
54- return true ; // Already initialized, so init was successful
5590 }
5691
5792 /* *
58- * @brief Acknowledge/click current notification
93+ * @brief Acknowledge/click current notifications
5994 * @param run_callback - Run notification callback when acknowledging
6095 */
61- void acknowledge_notification ( const bool run_callback = false ) {
96+ void acknowledge_notifications ( bool run_callback = false ) {
6297 if (notify_is_initted ()) {
63- std::scoped_lock lock (notification_mutex );
64- if (notification_current != nullptr && NOTIFY_IS_NOTIFICATION (notification_current) ) {
65- if (run_callback && notification_current_callback != nullptr ) {
66- notification_current_callback ();
98+ std::scoped_lock lock (notifications_mutex );
99+ for ( auto notification : notifications ) {
100+ if (run_callback && notification-> cb != nullptr ) {
101+ notification-> cb ();
67102 }
68- notify_notification_close (notification_current, nullptr );
69- g_object_unref (G_OBJECT (notification_current));
70- notification_current = nullptr ;
71- notification_current_callback = nullptr ;
103+ async_tray_notification_acknowledge_ (notification);
72104 }
105+ notifications.clear ();
73106 } else if (qt_tray_menu != nullptr && QtTrayMenu::supportsMessages ()) {
74107 qt_tray_menu->clickMessage ();
75108 }
76109 }
77110
111+ /* *
112+ * @brief Show notification asynchronously with timeout to avoid Dbus lockups
113+ * @param notification - Tray notification to show
114+ * @param timeout - optional timeout for async run in ms (default: 1000)
115+ */
116+ void async_tray_notification_show_ (const std::shared_ptr<notification_data> ¬ification, int timeout = 1000 ) {
117+ std::thread t ([notification]() { // NOSONAR(cpp:S6168) - jthread is only available on C++20 onwards
118+ std::scoped_lock lock (notification->mutex );
119+ if (notification->obj != nullptr && NOTIFY_IS_NOTIFICATION (notification->obj ) && notify_notification_show (notification->obj , nullptr )) {
120+ notification->shown = true ;
121+ }
122+ });
123+ while (!notification->shown && timeout > 0 ) {
124+ std::this_thread::sleep_for (std::chrono::milliseconds (10 ));
125+ timeout -= 10 ;
126+ }
127+ if (timeout > 0 ) {
128+ // Finished. Join thread.
129+ t.join ();
130+ } else {
131+ // Timed out. Detach thread and continue.
132+ t.detach (); // NOSONAR(cpp:S5962) - Keep this running until it times out by itself (usually after 25 seconds due to DBus)
133+ }
134+ }
135+
78136 /* *
79137 * @brief Show tray notification via desktop-independent interface
80138 * @param tray Tray structure containing notification information
@@ -85,39 +143,52 @@ namespace tray_linux {
85143 }
86144 // Try to notify using libnotify
87145 if (notify_is_initted ()) {
88- if (notification_current != nullptr ) {
89- acknowledge_notification ();
146+ if (!notifications. empty () ) {
147+ acknowledge_notifications ();
90148 }
91- std::scoped_lock lock (notification_mutex );
149+ std::scoped_lock lock (notifications_mutex );
92150 std::filesystem::path notification_icon = tray->notification_icon != nullptr ? tray->notification_icon : tray->icon ;
93151 if (std::filesystem::exists (notification_icon)) {
94152 // Use absolute path for filesystem icon files, not a relative one
95153 notification_icon = std::filesystem::absolute (notification_icon);
96154 }
97- notification_current = notify_notification_new (tray->notification_title , tray->notification_text , notification_icon.c_str ());
98- if (notification_current != nullptr && NOTIFY_IS_NOTIFICATION (notification_current)) {
155+ auto notification = std::make_shared<struct notification_data >();
156+ notification->obj = notify_notification_new (tray->notification_title , tray->notification_text , notification_icon.c_str ());
157+ if (notification->obj != nullptr && NOTIFY_IS_NOTIFICATION (notification->obj )) {
99158 if (tray->notification_cb != nullptr ) {
100- notification_current_callback = tray->notification_cb ;
101- notify_notification_add_action (notification_current, " default" , " Default" , NOTIFY_ACTION_CALLBACK (tray->notification_cb ), nullptr , nullptr );
102- }
103- if (notify_notification_show (notification_current, nullptr )) {
104- return ;
159+ notification->cb = tray->notification_cb ;
160+ notify_notification_add_action (notification->obj , " default" , " Default" , NOTIFY_ACTION_CALLBACK (tray->notification_cb ), nullptr , nullptr );
105161 }
162+ notifications.emplace_back (notification);
163+ async_tray_notification_show_ (notification);
106164 }
107- }
108- // Fallback to QtTrayMenu notification
109- if (qt_tray_menu != nullptr && QtTrayMenu::supportsMessages ()) {
165+ } else if (qt_tray_menu != nullptr && QtTrayMenu::supportsMessages ()) {
166+ // Fallback to QtTrayMenu notification
110167 qt_tray_menu->showMessage (tray->notification_title , tray->notification_text , tray->notification_icon , tray->notification_cb );
111168 }
112169 }
113170
171+ /* *
172+ * @brief Initialize notifications
173+ * @param app_name application name for notifications
174+ * @return true if successful
175+ */
176+ bool init_notify (const char *app_name) {
177+ if (!notify_is_initted ()) {
178+ if (!notifications.empty ()) {
179+ acknowledge_notifications ();
180+ }
181+ return notify_init (app_name);
182+ }
183+ return true ; // Already initialized, so init was successful
184+ }
185+
114186 /* *
115187 * @brief Uninitialize notifications
116188 */
117189 void uninit_notify () {
118190 if (notify_is_initted ()) {
119- acknowledge_notification ();
120- std::scoped_lock lock (notification_mutex);
191+ acknowledge_notifications ();
121192 notify_uninit ();
122193 }
123194 }
@@ -251,6 +322,6 @@ extern "C" {
251322 }
252323
253324 void tray_simulate_notification_click (void ) {
254- tray_linux::acknowledge_notification (true );
325+ tray_linux::acknowledge_notifications (true );
255326 }
256327} // extern "C"
0 commit comments