Add missing includes
[platform/upstream/glib.git] / gio / gfdonotificationbackend.c
1 /*
2  * Copyright © 2013 Lars Uebernickel
3  *
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 of the License, or (at your option) any later version.
8  *
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.
13  *
14  * You should have received a copy of the GNU Lesser General
15  * Public License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Authors: Lars Uebernickel <lars@uebernic.de>
20  */
21
22 #include "config.h"
23
24 #include "gnotificationbackend.h"
25
26 #include "gapplication.h"
27 #include "giomodule-priv.h"
28 #include "gnotification-private.h"
29 #include "gdbusconnection.h"
30 #include "gactiongroup.h"
31 #include "gaction.h"
32 #include "gfileicon.h"
33 #include "gfile.h"
34 #include "gdbusutils.h"
35
36 #define G_TYPE_FDO_NOTIFICATION_BACKEND  (g_fdo_notification_backend_get_type ())
37 #define G_FDO_NOTIFICATION_BACKEND(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FDO_NOTIFICATION_BACKEND, GFdoNotificationBackend))
38
39 typedef struct _GFdoNotificationBackend GFdoNotificationBackend;
40 typedef GNotificationBackendClass       GFdoNotificationBackendClass;
41
42 struct _GFdoNotificationBackend
43 {
44   GNotificationBackend parent;
45
46   guint   notify_subscription;
47   GSList *notifications;
48 };
49
50 GType g_fdo_notification_backend_get_type (void);
51
52 G_DEFINE_TYPE_WITH_CODE (GFdoNotificationBackend, g_fdo_notification_backend, G_TYPE_NOTIFICATION_BACKEND,
53   _g_io_modules_ensure_extension_points_registered ();
54   g_io_extension_point_implement (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME,
55                                  g_define_type_id, "freedesktop", 0))
56
57 typedef struct
58 {
59   GFdoNotificationBackend *backend;
60   gchar *id;
61   guint32 notify_id;
62   gchar *default_action;
63   GVariant *default_action_target;
64 } FreedesktopNotification;
65
66
67 static void
68 freedesktop_notification_free (gpointer data)
69 {
70   FreedesktopNotification *n = data;
71
72   g_free (n->id);
73   g_free (n->default_action);
74   if (n->default_action_target)
75     g_variant_unref (n->default_action_target);
76
77   g_slice_free (FreedesktopNotification, n);
78 }
79
80 static FreedesktopNotification *
81 g_fdo_notification_backend_find_notification (GFdoNotificationBackend *backend,
82                                               const gchar             *id)
83 {
84   GSList *it;
85
86   for (it = backend->notifications; it != NULL; it = it->next)
87     {
88       FreedesktopNotification *n = it->data;
89       if (g_str_equal (n->id, id))
90         return n;
91     }
92
93   return NULL;
94 }
95
96 static FreedesktopNotification *
97 g_fdo_notification_backend_find_notification_by_notify_id (GFdoNotificationBackend *backend,
98                                                            guint32                  id)
99 {
100   GSList *it;
101
102   for (it = backend->notifications; it != NULL; it = it->next)
103     {
104       FreedesktopNotification *n = it->data;
105       if (n->notify_id == id)
106         return n;
107     }
108
109   return NULL;
110 }
111
112 static void
113 activate_action (GFdoNotificationBackend *backend,
114                  const gchar             *name,
115                  GVariant                *parameter)
116 {
117   GNotificationBackend *g_backend = G_NOTIFICATION_BACKEND (backend);
118
119   if (name)
120     {
121       if (g_str_has_prefix (name, "app."))
122         g_action_group_activate_action (G_ACTION_GROUP (g_backend->application), name + 4, parameter);
123     }
124   else
125     {
126       g_application_activate (g_backend->application);
127     }
128 }
129
130 static void
131 notify_signal (GDBusConnection *connection,
132                const gchar     *sender_name,
133                const gchar     *object_path,
134                const gchar     *interface_name,
135                const gchar     *signal_name,
136                GVariant        *parameters,
137                gpointer         user_data)
138 {
139   GFdoNotificationBackend *backend = user_data;
140   guint32 id = 0;
141   const gchar *action = NULL;
142   FreedesktopNotification *n;
143
144   if (g_str_equal (signal_name, "NotificationClosed") &&
145       g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)")))
146     {
147       g_variant_get (parameters, "(uu)", &id, NULL);
148     }
149   else if (g_str_equal (signal_name, "ActionInvoked") &&
150            g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)")))
151     {
152       g_variant_get (parameters, "(u&s)", &id, &action);
153     }
154   else
155     return;
156
157   n = g_fdo_notification_backend_find_notification_by_notify_id (backend, id);
158   if (n == NULL)
159     return;
160
161   if (action)
162     {
163       if (g_str_equal (action, "default"))
164         {
165           activate_action (backend, n->default_action, n->default_action_target);
166         }
167       else
168         {
169           gchar *name;
170           GVariant *target;
171
172           if (g_action_parse_detailed_name (action, &name, &target, NULL))
173             {
174               activate_action (backend, name, target);
175               g_free (name);
176               if (target)
177                 g_variant_unref (target);
178             }
179         }
180     }
181
182   backend->notifications = g_slist_remove (backend->notifications, n);
183   freedesktop_notification_free (n);
184 }
185
186 static void
187 call_notify (GDBusConnection     *con,
188              GApplication        *app,
189              guint32              replace_id,
190              GNotification       *notification,
191              GAsyncReadyCallback  callback,
192              gpointer             user_data)
193 {
194   GVariantBuilder action_builder;
195   guint n_buttons;
196   guint i;
197   GVariantBuilder hints_builder;
198   GIcon *icon;
199   GVariant *parameters;
200   const gchar *body;
201
202   g_variant_builder_init (&action_builder, G_VARIANT_TYPE_STRING_ARRAY);
203   if (g_notification_get_default_action (notification, NULL, NULL))
204     {
205       g_variant_builder_add (&action_builder, "s", "default");
206       g_variant_builder_add (&action_builder, "s", "");
207     }
208
209   n_buttons = g_notification_get_n_buttons (notification);
210   for (i = 0; i < n_buttons; i++)
211     {
212       gchar *label;
213       gchar *action;
214       GVariant *target;
215       gchar *detailed_name;
216
217       g_notification_get_button (notification, i, &label, &action, &target);
218       detailed_name = g_action_print_detailed_name (action, target);
219
220       /* Actions named 'default' collide with libnotify's naming of the
221        * default action. Rewriting them to something unique is enough,
222        * because those actions can never be activated (they aren't
223        * prefixed with 'app.').
224        */
225       if (g_str_equal (detailed_name, "default"))
226         {
227           g_free (detailed_name);
228           detailed_name = g_dbus_generate_guid ();
229         }
230
231       g_variant_builder_add_value (&action_builder, g_variant_new_take_string (detailed_name));
232       g_variant_builder_add_value (&action_builder, g_variant_new_take_string (label));
233
234       g_free (action);
235       if (target)
236         g_variant_unref (target);
237     }
238
239   g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}"));
240   g_variant_builder_add (&hints_builder, "{sv}", "desktop-entry",
241                          g_variant_new_string (g_application_get_application_id (app)));
242   if (g_notification_get_urgent (notification))
243     g_variant_builder_add (&hints_builder, "{sv}", "urgency", g_variant_new_byte (2));
244   icon = g_notification_get_icon (notification);
245   if (icon != NULL && G_IS_FILE_ICON (icon))
246     {
247       GFile *file;
248
249       file = g_file_icon_get_file (G_FILE_ICON (icon));
250       g_variant_builder_add (&hints_builder, "{sv}", "image-path",
251                              g_variant_new_take_string (g_file_get_path (file)));
252     }
253
254   body = g_notification_get_body (notification);
255
256   parameters = g_variant_new ("(susssasa{sv}i)",
257                               "",           /* app name */
258                               replace_id,
259                               "",           /* app icon */
260                               g_notification_get_title (notification),
261                               body ? body : "",
262                               &action_builder,
263                               &hints_builder,
264                               -1);          /* expire_timeout */
265
266   g_dbus_connection_call (con, "org.freedesktop.Notifications", "/org/freedesktop/Notifications",
267                           "org.freedesktop.Notifications", "Notify",
268                           parameters, G_VARIANT_TYPE ("(u)"),
269                           G_DBUS_CALL_FLAGS_NONE, -1, NULL,
270                           callback, user_data);
271 }
272
273 static void
274 notification_sent (GObject      *source_object,
275                    GAsyncResult *result,
276                    gpointer      user_data)
277 {
278   FreedesktopNotification *n = user_data;
279   GVariant *val;
280   GError *error = NULL;
281   static gboolean warning_printed = FALSE;
282
283   val = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), result, &error);
284   if (val)
285     {
286       g_variant_get (val, "(u)", &n->notify_id);
287       g_variant_unref (val);
288     }
289   else
290     {
291       if (!warning_printed)
292         {
293           g_warning ("unable to send notifications through org.freedesktop.Notifications: %s",
294                      error->message);
295           warning_printed = TRUE;
296         }
297
298       n->backend->notifications = g_slist_remove (n->backend->notifications, n);
299       freedesktop_notification_free (n);
300
301       g_error_free (error);
302     }
303 }
304
305 static void
306 g_fdo_notification_backend_dispose (GObject *object)
307 {
308   GFdoNotificationBackend *backend = G_FDO_NOTIFICATION_BACKEND (object);
309
310   if (backend->notify_subscription)
311     {
312       GDBusConnection *session_bus;
313
314       session_bus = G_NOTIFICATION_BACKEND (backend)->dbus_connection;
315       g_dbus_connection_signal_unsubscribe (session_bus, backend->notify_subscription);
316       backend->notify_subscription = 0;
317     }
318
319   if (backend->notifications)
320     {
321       g_slist_free_full (backend->notifications, freedesktop_notification_free);
322       backend->notifications = NULL;
323     }
324
325   G_OBJECT_CLASS (g_fdo_notification_backend_parent_class)->dispose (object);
326 }
327
328 static gboolean
329 g_fdo_notification_backend_is_supported (void)
330 {
331   /* This is the fallback backend with the lowest priority. To avoid an
332    * unnecessary synchronous dbus call to check for
333    * org.freedesktop.Notifications, this function always succeeds. A
334    * warning will be printed when sending the first notification fails.
335    */
336   return TRUE;
337 }
338
339 static void
340 g_fdo_notification_backend_send_notification (GNotificationBackend *backend,
341                                               const gchar          *id,
342                                               GNotification        *notification)
343 {
344   GFdoNotificationBackend *self = G_FDO_NOTIFICATION_BACKEND (backend);
345   FreedesktopNotification *n;
346
347   if (self->notify_subscription == 0)
348     {
349       self->notify_subscription =
350         g_dbus_connection_signal_subscribe (backend->dbus_connection,
351                                             "org.freedesktop.Notifications",
352                                             "org.freedesktop.Notifications", NULL,
353                                             "/org/freedesktop/Notifications", NULL,
354                                             G_DBUS_SIGNAL_FLAGS_NONE,
355                                             notify_signal, backend, NULL);
356     }
357
358   n = g_fdo_notification_backend_find_notification (self, id);
359   if (n == NULL)
360     {
361       n = g_slice_new0 (FreedesktopNotification);
362       n->backend = self;
363       n->id = g_strdup (id);
364       n->notify_id = 0;
365
366       n->backend->notifications = g_slist_prepend (n->backend->notifications, n);
367     }
368   else
369     {
370       /* Only clear default action. All other fields are still valid */
371       g_clear_pointer (&n->default_action, g_free);
372       g_clear_pointer (&n->default_action_target, g_variant_unref);
373     }
374
375   g_notification_get_default_action (notification, &n->default_action, &n->default_action_target);
376
377   call_notify (backend->dbus_connection, backend->application, n->notify_id, notification, notification_sent, n);
378 }
379
380 static void
381 g_fdo_notification_backend_withdraw_notification (GNotificationBackend *backend,
382                                                   const gchar          *id)
383 {
384   GFdoNotificationBackend *self = G_FDO_NOTIFICATION_BACKEND (backend);
385   FreedesktopNotification *n;
386
387   n = g_fdo_notification_backend_find_notification (self, id);
388   if (n)
389     {
390       if (n->notify_id > 0)
391         {
392           g_dbus_connection_call (backend->dbus_connection,
393                                   "org.freedesktop.Notifications",
394                                   "/org/freedesktop/Notifications",
395                                   "org.freedesktop.Notifications", "CloseNotification",
396                                   g_variant_new ("(u)", n->id), NULL,
397                                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
398         }
399
400       self->notifications = g_slist_remove (self->notifications, n);
401       freedesktop_notification_free (n);
402     }
403 }
404
405 static void
406 g_fdo_notification_backend_init (GFdoNotificationBackend *backend)
407 {
408 }
409
410 static void
411 g_fdo_notification_backend_class_init (GFdoNotificationBackendClass *class)
412 {
413   GObjectClass *object_class = G_OBJECT_CLASS (class);
414   GNotificationBackendClass *backend_class = G_NOTIFICATION_BACKEND_CLASS (class);
415
416   object_class->dispose = g_fdo_notification_backend_dispose;
417
418   backend_class->is_supported = g_fdo_notification_backend_is_supported;
419   backend_class->send_notification = g_fdo_notification_backend_send_notification;
420   backend_class->withdraw_notification = g_fdo_notification_backend_withdraw_notification;
421 }