Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-gtk-notifications"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <time.h>
9 : :
10 : : #include <gio/gdesktopappinfo.h>
11 : : #include <gio/gio.h>
12 : : #include <valent.h>
13 : :
14 : : #include "valent-gtk-notifications.h"
15 : :
16 : :
17 : : struct _ValentGtkNotifications
18 : : {
19 : : ValentNotificationsAdapter parent_instance;
20 : :
21 : : GHashTable *active;
22 : : GDBusInterfaceVTable vtable;
23 : : GDBusNodeInfo *node_info;
24 : : GDBusInterfaceInfo *iface_info;
25 : : GDBusConnection *monitor;
26 : : unsigned int monitor_id;
27 : : char *name_owner;
28 : : unsigned int name_owner_id;
29 : : };
30 : :
31 : : static void g_async_initable_iface_init (GAsyncInitableIface *iface);
32 : :
33 [ + + + - ]: 10 : G_DEFINE_FINAL_TYPE_WITH_CODE (ValentGtkNotifications, valent_gtk_notifications, VALENT_TYPE_NOTIFICATIONS_ADAPTER,
34 : : G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, g_async_initable_iface_init))
35 : :
36 : :
37 : : /*
38 : : * GDBusInterfaceSkeleton
39 : : */
40 : : static const char interface_xml[] =
41 : : "<node>"
42 : : " <interface name='org.gtk.Notifications'>"
43 : : " <method name='AddNotification'>"
44 : : " <arg name='applicationId' type='s' direction='in'/>"
45 : : " <arg name='notificationId' type='s' direction='in'/>"
46 : : " <arg name='parameters' type='a{sv}' direction='in'/>"
47 : : " </method>"
48 : : " <method name='RemoveNotification'>"
49 : : " <arg name='applicationId' type='s' direction='in'/>"
50 : : " <arg name='notificationId' type='s' direction='in'/>"
51 : : " </method>"
52 : : " </interface>"
53 : : "</node>";
54 : :
55 : : static const char *interface_matches[] = {
56 : : "interface='org.gtk.Notifications',member='AddNotification',type='method_call'",
57 : : "interface='org.gtk.Notifications',member='RemoveNotification',type='method_call'",
58 : : NULL
59 : : };
60 : :
61 : :
62 : : static void
63 : 1 : _add_notification (ValentNotificationsAdapter *adapter,
64 : : GVariant *parameters)
65 : : {
66 : 1 : ValentGtkNotifications *self = VALENT_GTK_NOTIFICATIONS (adapter);
67 : 1 : g_autoptr (ValentNotification) notification = NULL;
68 [ + - ]: 1 : g_autofree char *desktop_id = NULL;
69 : 1 : g_autoptr (GDesktopAppInfo) desktop_info = NULL;
70 [ - + ]: 1 : g_autoptr (GVariant) props = NULL;
71 : 1 : const char *app_id;
72 : 1 : const char *notif_id;
73 : :
74 : : /* Extract what we need from the parameters */
75 : 1 : g_variant_get (parameters, "(&s&s@a{sv})", &app_id, ¬if_id, &props);
76 : :
77 : : /* Ignore our own notifications */
78 [ - + ]: 1 : if (g_str_equal (app_id, APPLICATION_ID))
79 [ # # ]: 0 : return;
80 : :
81 : : /* Deserialize GNotification into ValentNotification */
82 : 1 : notification = valent_notification_deserialize (props);
83 : 1 : valent_notification_set_id (notification, notif_id);
84 : :
85 : : /* Set a timestamp */
86 : 1 : valent_notification_set_time (notification, valent_timestamp_ms ());
87 : :
88 : : /* Try and get an application name */
89 : 1 : desktop_id = g_strdup_printf ("%s.desktop", app_id);
90 : :
91 [ - + ]: 1 : if ((desktop_info = g_desktop_app_info_new (desktop_id)))
92 : : {
93 : 0 : const char *app_name;
94 : :
95 : 0 : app_name = g_app_info_get_display_name (G_APP_INFO (desktop_info));
96 : 0 : valent_notification_set_application (notification, app_name);
97 : : }
98 : :
99 : 2 : g_hash_table_replace (self->active,
100 : 1 : (char *)valent_notification_get_id (notification),
101 : : g_object_ref (notification));
102 [ + - ]: 1 : valent_notifications_adapter_notification_added (adapter, notification);
103 : : }
104 : :
105 : : static void
106 : 1 : _remove_notification (ValentNotificationsAdapter *adapter,
107 : : GVariant *parameters)
108 : : {
109 : 1 : ValentGtkNotifications *self = VALENT_GTK_NOTIFICATIONS (adapter);
110 : 1 : g_autoptr (ValentNotification) notification = NULL;
111 : 1 : const char *app_id;
112 : 1 : const char *notif_id;
113 : :
114 : 1 : g_variant_get (parameters, "(&s&s)", &app_id, ¬if_id);
115 : :
116 : : /* Ignore our own notifications */
117 [ - + ]: 1 : if (g_str_equal (app_id, APPLICATION_ID))
118 [ # # ]: 0 : return;
119 : :
120 [ + - ]: 1 : if (g_hash_table_steal_extended (self->active,
121 : : notif_id,
122 : : NULL,
123 : : (void **)¬ification))
124 : : {
125 : 1 : valent_notifications_adapter_notification_removed (adapter, notification);
126 : : }
127 : : }
128 : :
129 : : static void
130 : 2 : valent_gtk_notifications_method_call (GDBusConnection *connection,
131 : : const char *sender,
132 : : const char *object_path,
133 : : const char *interface_name,
134 : : const char *method_name,
135 : : GVariant *parameters,
136 : : GDBusMethodInvocation *invocation,
137 : : gpointer user_data)
138 : : {
139 : 2 : ValentNotificationsAdapter *adapter = VALENT_NOTIFICATIONS_ADAPTER (user_data);
140 : 2 : ValentGtkNotifications *self = VALENT_GTK_NOTIFICATIONS (user_data);
141 : 2 : GDBusMessage *message;
142 : 2 : const char *destination;
143 : :
144 [ - + ]: 2 : g_assert (VALENT_IS_GTK_NOTIFICATIONS (adapter));
145 : :
146 : 2 : message = g_dbus_method_invocation_get_message (invocation);
147 : 2 : destination = g_dbus_message_get_destination (message);
148 : :
149 [ - + - - ]: 2 : if (g_strcmp0 ("org.gtk.Notifications", destination) != 0 &&
150 : 0 : g_strcmp0 (self->name_owner, destination) != 0)
151 : 0 : goto out;
152 : :
153 [ + + ]: 2 : if (g_strcmp0 (method_name, "AddNotification") == 0)
154 : 1 : _add_notification (adapter, parameters);
155 : :
156 [ - + ]: 1 : else if (g_strcmp0 (method_name, "RemoveNotification") == 0)
157 : 1 : _remove_notification (adapter, parameters);
158 : :
159 : 0 : out:
160 : 2 : g_object_unref (invocation);
161 : 2 : }
162 : :
163 : : /*
164 : : * Setup
165 : : */
166 : : static void
167 : 1 : on_name_appeared (GDBusConnection *connection,
168 : : const char *name,
169 : : const char *name_owner,
170 : : gpointer user_data)
171 : : {
172 : 1 : ValentGtkNotifications *self = VALENT_GTK_NOTIFICATIONS (user_data);
173 : :
174 [ - + ]: 1 : g_assert (VALENT_IS_GTK_NOTIFICATIONS (self));
175 : :
176 [ - + ]: 1 : self->name_owner = g_strdup (name_owner);
177 : 1 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
178 : : VALENT_PLUGIN_STATE_ACTIVE,
179 : : NULL);
180 : 1 : }
181 : :
182 : : static void
183 : 0 : on_name_vanished (GDBusConnection *connection,
184 : : const char *name,
185 : : gpointer user_data)
186 : : {
187 : 0 : ValentGtkNotifications *self = VALENT_GTK_NOTIFICATIONS (user_data);
188 : :
189 [ # # ]: 0 : g_assert (VALENT_IS_GTK_NOTIFICATIONS (self));
190 : :
191 [ # # ]: 0 : g_clear_pointer (&self->name_owner, g_free);
192 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
193 : : VALENT_PLUGIN_STATE_INACTIVE,
194 : : NULL);
195 : 0 : }
196 : :
197 : : static void
198 : 1 : become_monitor_cb (GDBusConnection *connection,
199 : : GAsyncResult *result,
200 : : gpointer user_data)
201 : : {
202 : 1 : g_autoptr (GTask) task = G_TASK (user_data);
203 : 1 : ValentGtkNotifications *self = g_task_get_source_object (task);
204 [ - - + - ]: 1 : g_autoptr (GVariant) reply = NULL;
205 [ - - ]: 1 : g_autoptr (GError) error = NULL;
206 : :
207 : 1 : reply = g_dbus_connection_call_finish (connection, result, &error);
208 [ - + ]: 1 : if (reply == NULL)
209 : : {
210 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
211 : : VALENT_PLUGIN_STATE_ERROR,
212 : : error);
213 [ # # ]: 0 : g_clear_object (&self->monitor);
214 : 0 : g_dbus_error_strip_remote_error (error);
215 : 0 : g_task_return_error (task, g_steal_pointer (&error));
216 [ # # ]: 0 : return;
217 : : }
218 : :
219 : : /* Watch the true name owner*/
220 : 1 : self->name_owner_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
221 : : "org.gtk.Notifications",
222 : : G_BUS_NAME_WATCHER_FLAGS_NONE,
223 : : on_name_appeared,
224 : : on_name_vanished,
225 : : self, NULL);
226 : :
227 [ - + ]: 1 : g_task_return_boolean (task, TRUE);
228 : : }
229 : :
230 : : static void
231 : 1 : new_for_address_cb (GObject *object,
232 : : GAsyncResult *result,
233 : : gpointer user_data)
234 : : {
235 : 1 : g_autoptr (GTask) task = G_TASK (user_data);
236 : 1 : ValentGtkNotifications *self = g_task_get_source_object (task);
237 : 1 : GCancellable *cancellable = g_task_get_cancellable (task);
238 [ - - ]: 1 : g_autoptr (GError) error = NULL;
239 : :
240 : 1 : self->monitor = g_dbus_connection_new_for_address_finish (result, &error);
241 : :
242 [ - + ]: 1 : if (self->monitor == NULL)
243 : : {
244 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
245 : : VALENT_PLUGIN_STATE_ERROR,
246 : : error);
247 : 0 : g_dbus_error_strip_remote_error (error);
248 : 0 : g_task_return_error (task, g_steal_pointer (&error));
249 : 0 : return;
250 : : }
251 : :
252 : : /* Export the interface and register as a monitor
253 : : */
254 : 2 : self->monitor_id =
255 : 1 : g_dbus_connection_register_object (self->monitor,
256 : : "/org/gtk/Notifications",
257 : : self->iface_info,
258 : 1 : &self->vtable,
259 : : self, NULL,
260 : : &error);
261 : :
262 [ - + ]: 1 : if (self->monitor_id == 0)
263 : : {
264 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
265 : : VALENT_PLUGIN_STATE_ERROR,
266 : : error);
267 [ # # ]: 0 : g_clear_object (&self->monitor);
268 : 0 : g_dbus_error_strip_remote_error (error);
269 : 0 : g_task_return_error (task, g_steal_pointer (&error));
270 : 0 : return;
271 : : }
272 : :
273 [ - + ]: 1 : g_dbus_connection_call (self->monitor,
274 : : "org.freedesktop.DBus",
275 : : "/org/freedesktop/DBus",
276 : : "org.freedesktop.DBus.Monitoring",
277 : : "BecomeMonitor",
278 : : g_variant_new ("(^asu)", interface_matches, 0),
279 : : NULL,
280 : : G_DBUS_CALL_FLAGS_NONE,
281 : : -1,
282 : : cancellable,
283 : : (GAsyncReadyCallback)become_monitor_cb,
284 : : g_steal_pointer (&task));
285 : : }
286 : :
287 : :
288 : : /*
289 : : * GAsyncInitable
290 : : */
291 : : static void
292 : 1 : valent_gtk_notifications_init_async (GAsyncInitable *initable,
293 : : int io_priority,
294 : : GCancellable *cancellable,
295 : : GAsyncReadyCallback callback,
296 : : gpointer user_data)
297 : : {
298 : 1 : g_autoptr (GTask) task = NULL;
299 [ - - ]: 1 : g_autofree char *address = NULL;
300 : 1 : g_autoptr (GError) error = NULL;
301 : :
302 [ - + ]: 1 : g_assert (VALENT_IS_GTK_NOTIFICATIONS (initable));
303 : :
304 : 1 : task = g_task_new (initable, cancellable, callback, user_data);
305 : 1 : g_task_set_priority (task, io_priority);
306 [ + - ]: 1 : g_task_set_source_tag (task, valent_gtk_notifications_init_async);
307 : :
308 : : /* Get a bus address and dedicated monitoring connection
309 : : */
310 : 1 : address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION,
311 : : cancellable,
312 : : &error);
313 [ - + ]: 1 : if (address == NULL)
314 : : {
315 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (initable),
316 : : VALENT_PLUGIN_STATE_ERROR,
317 : : error);
318 : 0 : g_dbus_error_strip_remote_error (error);
319 : 0 : g_task_return_error (task, g_steal_pointer (&error));
320 [ # # ]: 0 : return;
321 : : }
322 : :
323 [ - + ]: 1 : g_dbus_connection_new_for_address (address,
324 : : G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
325 : : G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
326 : : NULL,
327 : : cancellable,
328 : : (GAsyncReadyCallback)new_for_address_cb,
329 : : g_steal_pointer (&task));
330 : : }
331 : :
332 : : static void
333 : 2 : g_async_initable_iface_init (GAsyncInitableIface *iface)
334 : : {
335 : 2 : iface->init_async = valent_gtk_notifications_init_async;
336 : 2 : }
337 : :
338 : : /*
339 : : * ValentObject
340 : : */
341 : : static void
342 : 1 : valent_gtk_notifications_destroy (ValentObject *object)
343 : : {
344 : 1 : ValentGtkNotifications *self = VALENT_GTK_NOTIFICATIONS (object);
345 : :
346 [ + - ]: 1 : if (self->name_owner_id > 0)
347 : : {
348 : 1 : g_clear_handle_id (&self->name_owner_id, g_bus_unwatch_name);
349 [ + - ]: 1 : g_clear_pointer (&self->name_owner, g_free);
350 : : }
351 : :
352 [ + - ]: 1 : if (self->monitor_id != 0)
353 : : {
354 : 1 : g_dbus_connection_unregister_object (self->monitor, self->monitor_id);
355 : 1 : self->monitor_id = 0;
356 : : }
357 : :
358 [ + - ]: 1 : g_clear_object (&self->monitor);
359 : :
360 : 1 : VALENT_OBJECT_CLASS (valent_gtk_notifications_parent_class)->destroy (object);
361 : 1 : }
362 : :
363 : : /*
364 : : * GObject
365 : : */
366 : : static void
367 : 1 : valent_gtk_notifications_finalize (GObject *object)
368 : : {
369 : 1 : ValentGtkNotifications *self = VALENT_GTK_NOTIFICATIONS (object);
370 : :
371 [ + - ]: 1 : g_clear_pointer (&self->node_info, g_dbus_node_info_unref);
372 [ + - ]: 1 : g_clear_pointer (&self->active, g_hash_table_unref);
373 : :
374 : 1 : G_OBJECT_CLASS (valent_gtk_notifications_parent_class)->finalize(object);
375 : 1 : }
376 : :
377 : : static void
378 : 2 : valent_gtk_notifications_class_init (ValentGtkNotificationsClass *klass)
379 : : {
380 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
381 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
382 : :
383 : 2 : object_class->finalize = valent_gtk_notifications_finalize;
384 : :
385 : 2 : vobject_class->destroy = valent_gtk_notifications_destroy;
386 : : }
387 : :
388 : : static void
389 : 1 : valent_gtk_notifications_init (ValentGtkNotifications *self)
390 : : {
391 : 1 : self->node_info = g_dbus_node_info_new_for_xml (interface_xml, NULL);
392 : 1 : self->iface_info = self->node_info->interfaces[0];
393 : :
394 : 1 : self->vtable.method_call = valent_gtk_notifications_method_call;
395 : 1 : self->vtable.get_property = NULL;
396 : 1 : self->vtable.set_property = NULL;
397 : :
398 : 1 : self->active = g_hash_table_new_full (g_str_hash,
399 : : g_str_equal,
400 : : NULL,
401 : : g_object_unref);
402 : 1 : }
403 : :
|