Merge branch '976-disable-assert-checks' into 'master'
[glib.git] / gio / gfdonotificationbackend.c
blobc4fa0dc1dbff90f55cfe17722b50bf8b8790ef64
1 /*
2 * Copyright © 2013 Lars Uebernickel
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General
15 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 * Authors: Lars Uebernickel <lars@uebernic.de>
20 #include "config.h"
22 #include "gnotificationbackend.h"
24 #include "gapplication.h"
25 #include "giomodule-priv.h"
26 #include "gnotification-private.h"
27 #include "gdbusconnection.h"
28 #include "gactiongroup.h"
29 #include "gaction.h"
30 #include "gthemedicon.h"
31 #include "gfileicon.h"
32 #include "gfile.h"
33 #include "gdbusutils.h"
35 #define G_TYPE_FDO_NOTIFICATION_BACKEND (g_fdo_notification_backend_get_type ())
36 #define G_FDO_NOTIFICATION_BACKEND(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FDO_NOTIFICATION_BACKEND, GFdoNotificationBackend))
38 typedef struct _GFdoNotificationBackend GFdoNotificationBackend;
39 typedef GNotificationBackendClass GFdoNotificationBackendClass;
41 struct _GFdoNotificationBackend
43 GNotificationBackend parent;
45 guint notify_subscription;
46 GSList *notifications;
49 GType g_fdo_notification_backend_get_type (void);
51 G_DEFINE_TYPE_WITH_CODE (GFdoNotificationBackend, g_fdo_notification_backend, G_TYPE_NOTIFICATION_BACKEND,
52 _g_io_modules_ensure_extension_points_registered ();
53 g_io_extension_point_implement (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME,
54 g_define_type_id, "freedesktop", 0))
56 typedef struct
58 GFdoNotificationBackend *backend;
59 gchar *id;
60 guint32 notify_id;
61 gchar *default_action;
62 GVariant *default_action_target;
63 } FreedesktopNotification;
65 static void
66 freedesktop_notification_free (gpointer data)
68 FreedesktopNotification *n = data;
70 g_object_unref (n->backend);
71 g_free (n->id);
72 g_free (n->default_action);
73 if (n->default_action_target)
74 g_variant_unref (n->default_action_target);
76 g_slice_free (FreedesktopNotification, n);
79 static FreedesktopNotification *
80 freedesktop_notification_new (GFdoNotificationBackend *backend,
81 const gchar *id,
82 GNotification *notification)
84 FreedesktopNotification *n;
86 n = g_slice_new0 (FreedesktopNotification);
87 n->backend = g_object_ref (backend);
88 n->id = g_strdup (id);
89 n->notify_id = 0;
90 g_notification_get_default_action (notification,
91 &n->default_action,
92 &n->default_action_target);
94 return n;
97 static FreedesktopNotification *
98 g_fdo_notification_backend_find_notification (GFdoNotificationBackend *backend,
99 const gchar *id)
101 GSList *it;
103 for (it = backend->notifications; it != NULL; it = it->next)
105 FreedesktopNotification *n = it->data;
106 if (g_str_equal (n->id, id))
107 return n;
110 return NULL;
113 static FreedesktopNotification *
114 g_fdo_notification_backend_find_notification_by_notify_id (GFdoNotificationBackend *backend,
115 guint32 id)
117 GSList *it;
119 for (it = backend->notifications; it != NULL; it = it->next)
121 FreedesktopNotification *n = it->data;
122 if (n->notify_id == id)
123 return n;
126 return NULL;
129 static void
130 activate_action (GFdoNotificationBackend *backend,
131 const gchar *name,
132 GVariant *parameter)
134 GNotificationBackend *g_backend = G_NOTIFICATION_BACKEND (backend);
136 if (name)
138 if (g_str_has_prefix (name, "app."))
139 g_action_group_activate_action (G_ACTION_GROUP (g_backend->application), name + 4, parameter);
141 else
143 g_application_activate (g_backend->application);
147 static void
148 notify_signal (GDBusConnection *connection,
149 const gchar *sender_name,
150 const gchar *object_path,
151 const gchar *interface_name,
152 const gchar *signal_name,
153 GVariant *parameters,
154 gpointer user_data)
156 GFdoNotificationBackend *backend = user_data;
157 guint32 id = 0;
158 const gchar *action = NULL;
159 FreedesktopNotification *n;
161 if (g_str_equal (signal_name, "NotificationClosed") &&
162 g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)")))
164 g_variant_get (parameters, "(uu)", &id, NULL);
166 else if (g_str_equal (signal_name, "ActionInvoked") &&
167 g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)")))
169 g_variant_get (parameters, "(u&s)", &id, &action);
171 else
172 return;
174 n = g_fdo_notification_backend_find_notification_by_notify_id (backend, id);
175 if (n == NULL)
176 return;
178 if (action)
180 if (g_str_equal (action, "default"))
182 activate_action (backend, n->default_action, n->default_action_target);
184 else
186 gchar *name;
187 GVariant *target;
189 if (g_action_parse_detailed_name (action, &name, &target, NULL))
191 activate_action (backend, name, target);
192 g_free (name);
193 if (target)
194 g_variant_unref (target);
199 /* Get the notification again in case the action redrew it */
200 n = g_fdo_notification_backend_find_notification_by_notify_id (backend, id);
201 if (n != NULL)
203 backend->notifications = g_slist_remove (backend->notifications, n);
204 freedesktop_notification_free (n);
208 /* Converts a GNotificationPriority to an urgency level as defined by
209 * the freedesktop spec (0: low, 1: normal, 2: critical).
211 static guchar
212 urgency_from_priority (GNotificationPriority priority)
214 switch (priority)
216 case G_NOTIFICATION_PRIORITY_LOW:
217 return 0;
219 default:
220 case G_NOTIFICATION_PRIORITY_NORMAL:
221 case G_NOTIFICATION_PRIORITY_HIGH:
222 return 1;
224 case G_NOTIFICATION_PRIORITY_URGENT:
225 return 2;
229 static void
230 call_notify (GDBusConnection *con,
231 GApplication *app,
232 guint32 replace_id,
233 GNotification *notification,
234 GAsyncReadyCallback callback,
235 gpointer user_data)
237 GVariantBuilder action_builder;
238 guint n_buttons;
239 guint i;
240 GVariantBuilder hints_builder;
241 GIcon *icon;
242 GVariant *parameters;
243 const gchar *body;
244 guchar urgency;
246 g_variant_builder_init (&action_builder, G_VARIANT_TYPE_STRING_ARRAY);
247 if (g_notification_get_default_action (notification, NULL, NULL))
249 g_variant_builder_add (&action_builder, "s", "default");
250 g_variant_builder_add (&action_builder, "s", "");
253 n_buttons = g_notification_get_n_buttons (notification);
254 for (i = 0; i < n_buttons; i++)
256 gchar *label;
257 gchar *action;
258 GVariant *target;
259 gchar *detailed_name;
261 g_notification_get_button (notification, i, &label, &action, &target);
262 detailed_name = g_action_print_detailed_name (action, target);
264 /* Actions named 'default' collide with libnotify's naming of the
265 * default action. Rewriting them to something unique is enough,
266 * because those actions can never be activated (they aren't
267 * prefixed with 'app.').
269 if (g_str_equal (detailed_name, "default"))
271 g_free (detailed_name);
272 detailed_name = g_dbus_generate_guid ();
275 g_variant_builder_add_value (&action_builder, g_variant_new_take_string (detailed_name));
276 g_variant_builder_add_value (&action_builder, g_variant_new_take_string (label));
278 g_free (action);
279 if (target)
280 g_variant_unref (target);
283 g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}"));
284 g_variant_builder_add (&hints_builder, "{sv}", "desktop-entry",
285 g_variant_new_string (g_application_get_application_id (app)));
286 urgency = urgency_from_priority (g_notification_get_priority (notification));
287 g_variant_builder_add (&hints_builder, "{sv}", "urgency", g_variant_new_byte (urgency));
288 icon = g_notification_get_icon (notification);
289 if (icon != NULL)
291 if (G_IS_FILE_ICON (icon))
293 GFile *file;
295 file = g_file_icon_get_file (G_FILE_ICON (icon));
296 g_variant_builder_add (&hints_builder, "{sv}", "image-path",
297 g_variant_new_take_string (g_file_get_path (file)));
299 else if (G_IS_THEMED_ICON (icon))
301 const gchar* const* icon_names = g_themed_icon_get_names(G_THEMED_ICON (icon));
302 /* Take first name from GThemedIcon */
303 g_variant_builder_add (&hints_builder, "{sv}", "image-path",
304 g_variant_new_string (icon_names[0]));
308 body = g_notification_get_body (notification);
310 parameters = g_variant_new ("(susssasa{sv}i)",
311 "", /* app name */
312 replace_id,
313 "", /* app icon */
314 g_notification_get_title (notification),
315 body ? body : "",
316 &action_builder,
317 &hints_builder,
318 -1); /* expire_timeout */
320 g_dbus_connection_call (con, "org.freedesktop.Notifications", "/org/freedesktop/Notifications",
321 "org.freedesktop.Notifications", "Notify",
322 parameters, G_VARIANT_TYPE ("(u)"),
323 G_DBUS_CALL_FLAGS_NONE, -1, NULL,
324 callback, user_data);
327 static void
328 notification_sent (GObject *source_object,
329 GAsyncResult *result,
330 gpointer user_data)
332 FreedesktopNotification *n = user_data;
333 GVariant *val;
334 GError *error = NULL;
335 static gboolean warning_printed = FALSE;
337 val = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), result, &error);
338 if (val)
340 GFdoNotificationBackend *backend = n->backend;
341 FreedesktopNotification *match;
343 g_variant_get (val, "(u)", &n->notify_id);
344 g_variant_unref (val);
346 match = g_fdo_notification_backend_find_notification_by_notify_id (backend, n->notify_id);
347 if (match != NULL)
349 backend->notifications = g_slist_remove (backend->notifications, match);
350 freedesktop_notification_free (match);
352 backend->notifications = g_slist_prepend (backend->notifications, n);
354 else
356 if (!warning_printed)
358 g_warning ("unable to send notifications through org.freedesktop.Notifications: %s",
359 error->message);
360 warning_printed = TRUE;
363 freedesktop_notification_free (n);
364 g_error_free (error);
368 static void
369 g_fdo_notification_backend_dispose (GObject *object)
371 GFdoNotificationBackend *backend = G_FDO_NOTIFICATION_BACKEND (object);
373 if (backend->notify_subscription)
375 GDBusConnection *session_bus;
377 session_bus = G_NOTIFICATION_BACKEND (backend)->dbus_connection;
378 g_dbus_connection_signal_unsubscribe (session_bus, backend->notify_subscription);
379 backend->notify_subscription = 0;
382 if (backend->notifications)
384 g_slist_free_full (backend->notifications, freedesktop_notification_free);
385 backend->notifications = NULL;
388 G_OBJECT_CLASS (g_fdo_notification_backend_parent_class)->dispose (object);
391 static gboolean
392 g_fdo_notification_backend_is_supported (void)
394 /* This is the fallback backend with the lowest priority. To avoid an
395 * unnecessary synchronous dbus call to check for
396 * org.freedesktop.Notifications, this function always succeeds. A
397 * warning will be printed when sending the first notification fails.
399 return TRUE;
402 static void
403 g_fdo_notification_backend_send_notification (GNotificationBackend *backend,
404 const gchar *id,
405 GNotification *notification)
407 GFdoNotificationBackend *self = G_FDO_NOTIFICATION_BACKEND (backend);
408 FreedesktopNotification *n, *tmp;
410 if (self->notify_subscription == 0)
412 self->notify_subscription =
413 g_dbus_connection_signal_subscribe (backend->dbus_connection,
414 "org.freedesktop.Notifications",
415 "org.freedesktop.Notifications", NULL,
416 "/org/freedesktop/Notifications", NULL,
417 G_DBUS_SIGNAL_FLAGS_NONE,
418 notify_signal, backend, NULL);
421 n = freedesktop_notification_new (self, id, notification);
423 tmp = g_fdo_notification_backend_find_notification (self, id);
424 if (tmp)
425 n->notify_id = tmp->notify_id;
427 call_notify (backend->dbus_connection, backend->application, n->notify_id, notification, notification_sent, n);
430 static void
431 g_fdo_notification_backend_withdraw_notification (GNotificationBackend *backend,
432 const gchar *id)
434 GFdoNotificationBackend *self = G_FDO_NOTIFICATION_BACKEND (backend);
435 FreedesktopNotification *n;
437 n = g_fdo_notification_backend_find_notification (self, id);
438 if (n)
440 if (n->notify_id > 0)
442 g_dbus_connection_call (backend->dbus_connection,
443 "org.freedesktop.Notifications",
444 "/org/freedesktop/Notifications",
445 "org.freedesktop.Notifications", "CloseNotification",
446 g_variant_new ("(u)", n->notify_id), NULL,
447 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
450 self->notifications = g_slist_remove (self->notifications, n);
451 freedesktop_notification_free (n);
455 static void
456 g_fdo_notification_backend_init (GFdoNotificationBackend *backend)
460 static void
461 g_fdo_notification_backend_class_init (GFdoNotificationBackendClass *class)
463 GObjectClass *object_class = G_OBJECT_CLASS (class);
464 GNotificationBackendClass *backend_class = G_NOTIFICATION_BACKEND_CLASS (class);
466 object_class->dispose = g_fdo_notification_backend_dispose;
468 backend_class->is_supported = g_fdo_notification_backend_is_supported;
469 backend_class->send_notification = g_fdo_notification_backend_send_notification;
470 backend_class->withdraw_notification = g_fdo_notification_backend_withdraw_notification;