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