From 677215f67ac11223194b23b00b83284b15133a26 Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Fri, 28 Jul 2023 01:43:05 -0300 Subject: [PATCH 1/6] use SPDX header Signed-off-by: Gustavo Marques --- src/Services/NotificationsMonitor.vala | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Services/NotificationsMonitor.vala b/src/Services/NotificationsMonitor.vala index b2775730..452c2038 100644 --- a/src/Services/NotificationsMonitor.vala +++ b/src/Services/NotificationsMonitor.vala @@ -1,25 +1,13 @@ -/*- - * Copyright (c) 2015 Wingpanel Developers (http://launchpad.net/wingpanel) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as published by - * the Free Software Foundation, either version 2.1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this program. If not, see . +/* + * Copyright 2015 Wingpanel Developers (http://launchpad.net/wingpanel) + * Copyright 2015-2023 elementary, Inc (https://elementary.io) + * SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Original code from: * http://bazaar.launchpad.net/~jconti/recent-notifications/gnome3/view/head:/src/recent-notifications.vala */ - public class Notifications.NotificationMonitor : Object { private const string NOTIFY_IFACE = "org.freedesktop.Notifications"; private const string NOTIFY_PATH = "/org/freedesktop/Notifications"; From 253d66dd68acf29d5660b2996dd2c82352d558cb Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Fri, 28 Jul 2023 01:33:05 -0300 Subject: [PATCH 2/6] remove unused Fdo.Notifications proxy GLib.DBusProxy cannot be used to emit signals in the bus. Signed-off-by: Gustavo Marques --- meson.build | 1 - src/Services/Interfaces.vala | 21 --------------------- src/Services/Notification.vala | 21 --------------------- src/Services/NotificationsMonitor.vala | 12 +----------- src/Widgets/NotificationsList.vala | 10 ++++++++-- 5 files changed, 9 insertions(+), 56 deletions(-) delete mode 100644 src/Services/Interfaces.vala diff --git a/meson.build b/meson.build index 475d8e70..6dd5eaaa 100644 --- a/meson.build +++ b/meson.build @@ -46,7 +46,6 @@ shared_module( 'src/Services/NotificationsMonitor.vala', 'src/Services/Notification.vala', 'src/Services/Session.vala', - 'src/Services/Interfaces.vala', gresource, config_vala, dependencies: [ diff --git a/src/Services/Interfaces.vala b/src/Services/Interfaces.vala deleted file mode 100644 index 022d1fd6..00000000 --- a/src/Services/Interfaces.vala +++ /dev/null @@ -1,21 +0,0 @@ -/*- - * Copyright (c) 2016 Wingpanel Developers (http://launchpad.net/wingpanel) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as published by - * the Free Software Foundation, either version 2.1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this program. If not, see . - */ - -[DBus (name = "org.freedesktop.Notifications")] -public interface Notifications.INotifications : Object { - public signal void action_invoked (uint32 id, string action); -} diff --git a/src/Services/Notification.vala b/src/Services/Notification.vala index 4a6b20e8..a9725335 100644 --- a/src/Services/Notification.vala +++ b/src/Services/Notification.vala @@ -144,27 +144,6 @@ public class Notifications.Notification : Object { is_transient = hints.lookup_value (X_CANONICAL_PRIVATE_KEY, null) != null || (transient_hint != null && transient_hint.get_boolean ()); } - public bool run_default_action () { - if (DEFAULT_ACTION in actions) { - app_info.launch_action (DEFAULT_ACTION, new GLib.AppLaunchContext ()); - - var notifications_iface = NotificationMonitor.get_instance ().notifications_iface; - if (notifications_iface != null) { - notifications_iface.action_invoked (id, DEFAULT_ACTION); - } - - return true; - } else { - try { - app_info.launch (null, null); - } catch (Error e) { - critical ("Unable to launch app: %s", e.message); - } - } - - return false; - } - private string get_string (Variant tuple, int column) { var child = tuple.get_child_value (column); return child.dup_string (); diff --git a/src/Services/NotificationsMonitor.vala b/src/Services/NotificationsMonitor.vala index 452c2038..14bdd1da 100644 --- a/src/Services/NotificationsMonitor.vala +++ b/src/Services/NotificationsMonitor.vala @@ -9,8 +9,6 @@ * http://bazaar.launchpad.net/~jconti/recent-notifications/gnome3/view/head:/src/recent-notifications.vala */ public class Notifications.NotificationMonitor : Object { - private const string NOTIFY_IFACE = "org.freedesktop.Notifications"; - private const string NOTIFY_PATH = "/org/freedesktop/Notifications"; private const string METHOD_CALL_MATCH_STRING = "type='method_call',interface='org.freedesktop.Notifications'"; private const string METHOD_RETURN_MATCH_STRING = "type='method_return'"; private const string ERROR_MATCH_STRING = "type='error'"; @@ -32,8 +30,6 @@ public class Notifications.NotificationMonitor : Object { return instance; } - public INotifications? notifications_iface = null; - construct { initialize.begin (); } @@ -74,16 +70,10 @@ public class Notifications.NotificationMonitor : Object { } catch (Error e) { critical ("Unable to monitor notifications bus: %s", e.message); } - - try { - notifications_iface = yield Bus.get_proxy (BusType.SESSION, NOTIFY_IFACE, NOTIFY_PATH, DBusProxyFlags.NONE, null); - } catch (Error e) { - warning ("Unable to connection to notifications bus: %s", e.message); - } } private DBusMessage? message_filter (DBusConnection con, owned DBusMessage message, bool incoming) { - if (incoming && message.get_interface () == NOTIFY_IFACE) { + if (incoming && message.get_interface () == "org.freedesktop.Notifications") { switch (message.get_message_type ()) { case DBusMessageType.METHOD_CALL: if (message.get_member () == "Notify") { diff --git a/src/Widgets/NotificationsList.vala b/src/Widgets/NotificationsList.vala index d0082273..5e589110 100644 --- a/src/Widgets/NotificationsList.vala +++ b/src/Widgets/NotificationsList.vala @@ -130,9 +130,15 @@ public class Notifications.NotificationsList : Gtk.ListBox { private void on_row_activated (Gtk.ListBoxRow row) { if (row is NotificationEntry) { unowned NotificationEntry notification_entry = (NotificationEntry) row; - notification_entry.notification.run_default_action (); - notification_entry.clear (); + var context = notification_entry.get_display ().get_app_launch_context (); + + if (notification_entry.notification.app_info != null) try { + notification_entry.notification.app_info.launch (null, context); + } catch (Error e) { + critical ("Unable to launch app: %s", e.message); + } + notification_entry.clear (); close_popover (); } } From ad16490ea524419bb19ef1eed431523a127a04eb Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Fri, 28 Jul 2023 01:28:13 -0300 Subject: [PATCH 3/6] use SingleInstance attribute, mark as sealed, fix class name the [SingleInstance] attribute deal with making the class a singleton and forcing a unique instance is created and referenced everytime the constructor method is called, sealed mark the class as non-derivable. while here fix the class name to match the file name. Signed-off-by: Gustavo Marques --- src/Indicator.vala | 5 ++++- src/Services/NotificationsMonitor.vala | 13 ++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Indicator.vala b/src/Indicator.vala index 8c493058..5c7f20b3 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -23,11 +23,14 @@ public class Notifications.Indicator : Wingpanel.Indicator { private Gee.HashMap app_settings_cache; private GLib.Settings notify_settings; + private Gtk.Grid? main_box = null; private Gtk.ModelButton clear_all_btn; private Gtk.Spinner? dynamic_icon = null; private NotificationsList nlist; + private List previous_session = null; + private NotificationsMonitor monitor; public Indicator () { Object ( @@ -60,7 +63,7 @@ public class Notifications.Indicator : Wingpanel.Indicator { nlist = new NotificationsList (); - var monitor = NotificationMonitor.get_instance (); + monitor = new NotificationsMonitor (); monitor.notification_received.connect (on_notification_received); monitor.notification_closed.connect (on_notification_closed); diff --git a/src/Services/NotificationsMonitor.vala b/src/Services/NotificationsMonitor.vala index 14bdd1da..79901906 100644 --- a/src/Services/NotificationsMonitor.vala +++ b/src/Services/NotificationsMonitor.vala @@ -8,28 +8,19 @@ * Original code from: * http://bazaar.launchpad.net/~jconti/recent-notifications/gnome3/view/head:/src/recent-notifications.vala */ -public class Notifications.NotificationMonitor : Object { +[SingleInstance] +public sealed class Notifications.NotificationsMonitor : Object { private const string METHOD_CALL_MATCH_STRING = "type='method_call',interface='org.freedesktop.Notifications'"; private const string METHOD_RETURN_MATCH_STRING = "type='method_return'"; private const string ERROR_MATCH_STRING = "type='error'"; private const string SIGNAL_MATCH_STRING = "type='signal'"; - private static NotificationMonitor? instance = null; - private DBusConnection connection; private DBusMessage? awaiting_reply = null; public signal void notification_received (DBusMessage message, uint32 id); public signal void notification_closed (uint32 id, uint32 reason); - public static NotificationMonitor get_instance () { - if (instance == null) { - instance = new NotificationMonitor (); - } - - return instance; - } - construct { initialize.begin (); } From 965c2d2ec0b66084d9fc26d961c70aad0d3ed49f Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Fri, 28 Jul 2023 01:41:04 -0300 Subject: [PATCH 4/6] =?UTF-8?q?private=20initialize()=20=E2=86=92=20=20pub?= =?UTF-8?q?lic=20init()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rather than calling initialize() in the construct block make it public and let the Indicator decides when to initalize the monitor. Signed-off-by: Gustavo Marques --- src/Indicator.vala | 10 ++++- src/Services/NotificationsMonitor.vala | 60 +++++++++++--------------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/Indicator.vala b/src/Indicator.vala index 5c7f20b3..4e594293 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -45,6 +45,8 @@ public class Notifications.Indicator : Wingpanel.Indicator { notify_settings = new GLib.Settings ("io.elementary.notifications"); app_settings_cache = new Gee.HashMap (); + + monitor = new NotificationsMonitor (); } public override Gtk.Widget get_display_widget () { @@ -63,9 +65,15 @@ public class Notifications.Indicator : Wingpanel.Indicator { nlist = new NotificationsList (); - monitor = new NotificationsMonitor (); monitor.notification_received.connect (on_notification_received); monitor.notification_closed.connect (on_notification_closed); + monitor.init.begin ((obj, res) => { + try { + ((NotificationsMonitor) obj).init.end (res); + } catch (Error e) { + critical ("Unable to monitor notifications bus: %s", e.message); + } + }); notify_settings.changed["do-not-disturb"].connect (() => { set_display_icon_name (); diff --git a/src/Services/NotificationsMonitor.vala b/src/Services/NotificationsMonitor.vala index 79901906..5922b2a0 100644 --- a/src/Services/NotificationsMonitor.vala +++ b/src/Services/NotificationsMonitor.vala @@ -21,46 +21,34 @@ public sealed class Notifications.NotificationsMonitor : Object { public signal void notification_received (DBusMessage message, uint32 id); public signal void notification_closed (uint32 id, uint32 reason); - construct { - initialize.begin (); - } - - private async void initialize () { - try { + public async void init () throws Error { #if VALA_0_54 - string address = BusType.SESSION.get_address_sync (); + string address = BusType.SESSION.get_address_sync (); #else - string address = BusType.get_address_sync (BusType.SESSION); + string address = BusType.get_address_sync (BusType.SESSION); #endif - connection = yield new DBusConnection.for_address ( - address, - DBusConnectionFlags.AUTHENTICATION_CLIENT | DBusConnectionFlags.MESSAGE_BUS_CONNECTION, - null, null - ); - connection.add_filter (message_filter); - - yield connection.call ( - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus.Monitoring", - "BecomeMonitor", - new Variant.tuple ({ - new Variant.array (VariantType.STRING, { - METHOD_CALL_MATCH_STRING, - METHOD_RETURN_MATCH_STRING, - ERROR_MATCH_STRING, - SIGNAL_MATCH_STRING - }), - (uint32)0 + connection = yield new DBusConnection.for_address (address, AUTHENTICATION_CLIENT | MESSAGE_BUS_CONNECTION); + + yield connection.call ( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus.Monitoring", + "BecomeMonitor", + new Variant.tuple ({ + new Variant.array (VariantType.STRING, { + METHOD_CALL_MATCH_STRING, + METHOD_RETURN_MATCH_STRING, + ERROR_MATCH_STRING, + SIGNAL_MATCH_STRING }), - null, - DBusCallFlags.NONE, - -1, - null - ); - } catch (Error e) { - critical ("Unable to monitor notifications bus: %s", e.message); - } + 0U + }), + null, + NONE, + -1 + ); + + connection.add_filter (message_filter); } private DBusMessage? message_filter (DBusConnection con, owned DBusMessage message, bool incoming) { From cbd960930c39cd40ef75ef6bf571ec14a65621f6 Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Fri, 28 Jul 2023 01:00:42 -0300 Subject: [PATCH 5/6] rewrite match strings use a generic match for method calls and signal emissions for the org.freedesktop.Notifications interface, restrict the method_return and error ones to the org.freedesktop.Notifications name. Signed-off-by: Gustavo Marques --- src/Services/NotificationsMonitor.vala | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Services/NotificationsMonitor.vala b/src/Services/NotificationsMonitor.vala index 5922b2a0..80f5b2b3 100644 --- a/src/Services/NotificationsMonitor.vala +++ b/src/Services/NotificationsMonitor.vala @@ -10,17 +10,19 @@ */ [SingleInstance] public sealed class Notifications.NotificationsMonitor : Object { - private const string METHOD_CALL_MATCH_STRING = "type='method_call',interface='org.freedesktop.Notifications'"; - private const string METHOD_RETURN_MATCH_STRING = "type='method_return'"; - private const string ERROR_MATCH_STRING = "type='error'"; - private const string SIGNAL_MATCH_STRING = "type='signal'"; + public signal void notification_received (DBusMessage message, uint32 id); + public signal void notification_closed (uint32 id, uint32 reason); + + // matches method calls and signal emissions in the org.freedesktop.Notifications interface at /org/freedesktop/Notifications. + private const string CALL_MATCH = "interface='org.freedesktop.Notifications',path='/org/freedesktop/Notifications'"; + // matches responses sent from the org.freedesktop.Notifications name. + private const string RESPONSE_MATCH = "type=method_return,sender='org.freedesktop.Notifications'"; + // matches errors sent from the org.freeedesktop.Notifications name. + private const string ERROR_MATCH = "type=error,sender='org.freedesktop.Notifications'"; private DBusConnection connection; private DBusMessage? awaiting_reply = null; - public signal void notification_received (DBusMessage message, uint32 id); - public signal void notification_closed (uint32 id, uint32 reason); - public async void init () throws Error { #if VALA_0_54 string address = BusType.SESSION.get_address_sync (); @@ -35,12 +37,7 @@ public sealed class Notifications.NotificationsMonitor : Object { "org.freedesktop.DBus.Monitoring", "BecomeMonitor", new Variant.tuple ({ - new Variant.array (VariantType.STRING, { - METHOD_CALL_MATCH_STRING, - METHOD_RETURN_MATCH_STRING, - ERROR_MATCH_STRING, - SIGNAL_MATCH_STRING - }), + new Variant.array (VariantType.STRING, { CALL_MATCH, RESPONSE_MATCH, ERROR_MATCH }), 0U }), null, @@ -52,7 +49,7 @@ public sealed class Notifications.NotificationsMonitor : Object { } private DBusMessage? message_filter (DBusConnection con, owned DBusMessage message, bool incoming) { - if (incoming && message.get_interface () == "org.freedesktop.Notifications") { + if (incoming) { switch (message.get_message_type ()) { case DBusMessageType.METHOD_CALL: if (message.get_member () == "Notify") { From 76943d16c12b38af5dad290d6750b370372c2150 Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Fri, 28 Jul 2023 01:23:14 -0300 Subject: [PATCH 6/6] rewrite filter function rewrite the switch statement to check member_name instead of message_type, this allow to mege the CloseNotification and NotificationClosed cases. the case of the notify call beeing successfull or not is also merged. the monitor signals are now emitted before the next Gtk redraw cycle, making sure that the tray got updated in the current cycle. Signed-off-by: Gustavo Marques --- src/Indicator.vala | 27 ++-- src/Services/Notification.vala | 4 - src/Services/NotificationsMonitor.vala | 169 ++++++++++++------------- 3 files changed, 90 insertions(+), 110 deletions(-) diff --git a/src/Indicator.vala b/src/Indicator.vala index 4e594293..1996432d 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -16,7 +16,6 @@ */ public class Notifications.Indicator : Wingpanel.Indicator { - private const string[] EXCEPTIONS = { "NetworkManager", "gnome-settings-daemon", "gnome-power-panel" }; private const string CHILD_SCHEMA_ID = "io.elementary.notifications.applications"; private const string CHILD_PATH = "/io/elementary/notifications/applications/%s/"; private const string REMEMBER_KEY = "remember"; @@ -173,12 +172,8 @@ public class Notifications.Indicator : Wingpanel.Indicator { private void on_notification_received (DBusMessage message, uint32 id) { var notification = new Notification.from_message (message, id); - if (notification.is_transient || notification.app_name in EXCEPTIONS) { - return; - } string app_id = notification.desktop_id.replace (Notification.DESKTOP_ID_EXT, ""); - Settings? app_settings = app_settings_cache.get (app_id); var schema = SettingsSchemaSource.get_default ().lookup (CHILD_SCHEMA_ID, true); @@ -198,18 +193,16 @@ public class Notifications.Indicator : Wingpanel.Indicator { clear_all_btn.sensitive = nlist.app_entries.size > 0; } - private void on_notification_closed (uint32 id, uint32 reason) { - // If notification bubble was dismissed by user do not keep in list - if (reason == Notification.CloseReason.DISMISSED || - reason == Notification.CloseReason.CLOSE_NOTIFICATION_CALL) { - - foreach (var app_entry in nlist.app_entries.values) { - foreach (var item in app_entry.app_notifications) { - if (item.notification.id == id) { - item.clear (); - return; - } - } + private void on_notification_closed (uint32 id, Notification.CloseReason reason) { + SearchFunc find_entry = (e, i) => { + return i == e.notification.id ? 0 : i > e.notification.id ? 1 : -1; + }; + + foreach (var app_entry in nlist.app_entries.values) { + unowned var node = app_entry.app_notifications.search (id, find_entry); + if (node != null) { + node.data.clear (); + return; } } } diff --git a/src/Services/Notification.vala b/src/Services/Notification.vala index a9725335..9f710814 100644 --- a/src/Services/Notification.vala +++ b/src/Services/Notification.vala @@ -25,7 +25,6 @@ public class Notifications.Notification : Object { public const string DESKTOP_ID_EXT = ".desktop"; - public bool is_transient = false; public string app_name; public string summary; public string message_body; @@ -139,9 +138,6 @@ public class Notifications.Notification : Object { desktop_id = FALLBACK_DESKTOP_ID; app_info = new DesktopAppInfo (desktop_id); } - - var transient_hint = hints.lookup_value ("transient", VariantType.BOOLEAN); - is_transient = hints.lookup_value (X_CANONICAL_PRIVATE_KEY, null) != null || (transient_hint != null && transient_hint.get_boolean ()); } private string get_string (Variant tuple, int column) { diff --git a/src/Services/NotificationsMonitor.vala b/src/Services/NotificationsMonitor.vala index 80f5b2b3..24e1e0da 100644 --- a/src/Services/NotificationsMonitor.vala +++ b/src/Services/NotificationsMonitor.vala @@ -11,7 +11,7 @@ [SingleInstance] public sealed class Notifications.NotificationsMonitor : Object { public signal void notification_received (DBusMessage message, uint32 id); - public signal void notification_closed (uint32 id, uint32 reason); + public signal void notification_closed (uint32 id, Notification.CloseReason reason); // matches method calls and signal emissions in the org.freedesktop.Notifications interface at /org/freedesktop/Notifications. private const string CALL_MATCH = "interface='org.freedesktop.Notifications',path='/org/freedesktop/Notifications'"; @@ -20,8 +20,8 @@ public sealed class Notifications.NotificationsMonitor : Object { // matches errors sent from the org.freeedesktop.Notifications name. private const string ERROR_MATCH = "type=error,sender='org.freedesktop.Notifications'"; + private Gee.Map awaiting = new Gee.HashMap (); private DBusConnection connection; - private DBusMessage? awaiting_reply = null; public async void init () throws Error { #if VALA_0_54 @@ -45,106 +45,97 @@ public sealed class Notifications.NotificationsMonitor : Object { -1 ); - connection.add_filter (message_filter); + connection.add_filter (filter); } - private DBusMessage? message_filter (DBusConnection con, owned DBusMessage message, bool incoming) { - if (incoming) { - switch (message.get_message_type ()) { - case DBusMessageType.METHOD_CALL: - if (message.get_member () == "Notify") { - try { - awaiting_reply = message.copy (); - } catch (Error e) { - warning (e.message); - } - } else if (message.get_member () == "CloseNotification") { - unowned GLib.Variant? body = message.get_body (); - if (body == null || body.n_children () != 1) { - return message; - } - - var child = body.get_child_value (0); - if (!child.is_of_type (VariantType.UINT32)) { - return message; - } - - uint32 id = child.get_uint32 (); - Idle.add (() => { - notification_closed (id, Notification.CloseReason.CLOSE_NOTIFICATION_CALL); - return Source.REMOVE; - }); - } + private DBusMessage? filter (DBusConnection connection, owned DBusMessage message, bool incoming) { + if (!incoming) { + return null; + } - break; - - case DBusMessageType.SIGNAL: - if (message.get_member () == "NotificationClosed") { - unowned GLib.Variant? body = message.get_body (); - if (body == null || body.n_children () != 2) { - return message; - } - - var id_val = body.get_child_value (0); - if (!id_val.is_of_type (VariantType.UINT32)) { - return message; - } - - var reason_val = body.get_child_value (1); - if (!reason_val.is_of_type (VariantType.UINT32)) { - return message; - } - - uint32 id = id_val.get_uint32 (); - uint32 reason = reason_val.get_uint32 ().clamp (1, 4); - Idle.add (() => { - notification_closed (id, reason); - return Source.REMOVE; - }); - } + var body = message.get_body (); + debug ( + "got message (" + + @"$(message.get_message_type ()), " + + @"$(message.get_interface () ?? "null"), " + + @"$(message.get_member () ?? "null"), " + + @"$(body != null ? body.print (true) : "null"))" + ); - break; + switch (message.get_member ()) { + case "Notify": + try { + awaiting[message.get_serial ()] = message.copy (); + } catch { + warning ("failed to make a copy of notify message, notification won't be included in the list"); + } + + break; + + case "CloseNotification": + case "NotificationClosed": + Notification.CloseReason reason; + uint32 id; + + if (message.get_member () == "NotificationClosed") { + body.get ("(uu)", out id, out reason); + } else { + id = body.get_child_value (0).get_uint32 (); + reason = CLOSE_NOTIFICATION_CALL; + } + + emit_closed (id, reason); + break; + + case null: // if null it's either a method_return or a error. + DBusMessage awaiting_message; + + if (awaiting.unset (message.get_reply_serial (), out awaiting_message)) { + if (message.get_message_type () != METHOD_RETURN) { + break; + } - default: - break; - } + var hints = new VariantDict (awaiting_message.get_body ().get_child_value (6)); + var id = body.get_child_value (0).get_uint32 (); - return null; - } else if (awaiting_reply != null && awaiting_reply.get_serial () == message.get_reply_serial ()) { - switch (message.get_message_type ()) { - case DBusMessageType.METHOD_RETURN: - unowned GLib.Variant? body = message.get_body (); - if (body == null || body.n_children () != 1) { - return message; - } + emit_closed (id, UNDEFINED); // make sure we remove a replaced notification. + bool transient; - var child = body.get_child_value (0); - if (!child.is_of_type (VariantType.UINT32)) { - return message; + if (hints.lookup ("transient", "b", out transient) && transient + || "x-canonical-private-synchronous" in hints + ) { + break; // don't append transient notifications. } - uint32 id = child.get_uint32 (); - try { - var copy = awaiting_reply.copy (); - Idle.add (() => { - notification_received (copy, id); - return Source.REMOVE; - }); - } catch (Error e) { - warning (e.message); + // XXX: are this list still needed? or even right? + const string[] EXCEPTIONS = { "NetworkManager", "gnome-settings-daemon", "gnome-power-panel" }; + var app_name = awaiting_message.get_body ().get_child_value (0).get_string (); + if (app_name in EXCEPTIONS) { + break; } - awaiting_reply = null; - break; + emit_received (awaiting_message, id); + } - case DBusMessageType.ERROR: - awaiting_reply = null; - break; - default: - break; - } + break; } - return message; + return null; + } + + private inline void emit_closed (uint32 id, Notification.CloseReason reason) { + // HIGH_IDLE so that they got executed before gtk's resize/redrawing sources. + GLib.MainContext.default ().invoke_full (GLib.Priority.HIGH_IDLE, () => { + notification_closed (id, reason); + return GLib.Source.REMOVE; + }); + } + + private inline void emit_received (DBusMessage message, uint32 id) { + // HIGH_IDLE + 1 so closed emissions run first. + GLib.MainContext.default ().invoke_full (GLib.Priority.HIGH_IDLE + 1, () => { + notification_received (message, id); + return GLib.Source.REMOVE; + }); } }