root/trunk/libnotify/libnotify/notification.c

Revision 3015 (checked in by chipx86, 3 months ago)

And fix the registration of the signal. It's been way too long since I worked with gtk in C. Or not long enough.

Line 
1 /**
2  * @file libnotify/notification.c Notification object
3  *
4  * @Copyright (C) 2006 Christian Hammond
5  * @Copyright (C) 2006 John Palmieri
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA  02111-1307, USA.
21  */
22 #include "config.h"
23 #include <dbus/dbus.h>
24 #include <dbus/dbus-glib.h>
25
26 #include <libnotify/notify.h>
27 #include <libnotify/internal.h>
28
29 #include <gtk/gtkversion.h>
30 #if GTK_CHECK_VERSION(2, 9, 2)
31 # define HAVE_STATUS_ICON
32 # include <gtk/gtkstatusicon.h>
33 #endif
34 #include <gdk/gdkx.h>
35
36 #define CHECK_DBUS_VERSION(major, minor) \
37     (DBUS_MAJOR_VER > (major) || \
38      (DBUS_MAJOR_VER == (major) && DBUS_MINOR_VER >= (minor)))
39
40 #if !defined(G_PARAM_STATIC_NAME) && !defined(G_PARAM_STATIC_NICK) && \
41     !defined(G_PARAM_STATIC_BLURB)
42 # define G_PARAM_STATIC_NAME 0
43 # define G_PARAM_STATIC_NICK 0
44 # define G_PARAM_STATIC_BLURB 0
45 #endif
46
47 static void notify_notification_class_init(NotifyNotificationClass *klass);
48 static void notify_notification_init(NotifyNotification *sp);
49 static void notify_notification_finalize(GObject *object);
50 static void _close_signal_handler(DBusGProxy *proxy, guint32 id, guint32 reason,
51                                   NotifyNotification *notification);
52
53 static void _action_signal_handler(DBusGProxy *proxy, guint32 id,
54                                    gchar *action,
55                                    NotifyNotification *notification);
56
57 typedef struct
58 {
59     NotifyActionCallback cb;
60     GFreeFunc free_func;
61     gpointer user_data;
62
63 } CallbackPair;
64
65 struct _NotifyNotificationPrivate
66 {
67     guint32 id;
68     gchar *summary;
69     gchar *body;
70
71     /* NULL to use icon data. Anything else to have server lookup icon */
72     gchar *icon_name;
73
74     /*
75      * -1   = use server default
76      *  0   = never timeout
77      *  > 0 = Number of milliseconds before we timeout
78     */
79     gint timeout;
80
81     GSList *actions;
82     GHashTable *action_map;
83     GHashTable *hints;
84
85     GtkWidget *attached_widget;
86 #ifdef HAVE_STATUS_ICON
87     GtkStatusIcon *status_icon;
88 #endif
89
90     gboolean has_nondefault_actions;
91     gboolean updates_pending;
92     gboolean signals_registered;
93
94     gint closed_reason;
95 };
96
97 enum
98 {
99     SIGNAL_CLOSED,
100     LAST_SIGNAL
101 };
102
103 enum
104 {
105     PROP_0,
106     PROP_ID,
107     PROP_SUMMARY,
108     PROP_BODY,
109     PROP_ICON_NAME,
110     PROP_ATTACH_WIDGET,
111     PROP_STATUS_ICON,
112     PROP_CLOSED_REASON
113 };
114
115 static void notify_notification_set_property(GObject *object, guint prop_id,
116                                              const GValue *value,
117                                              GParamSpec *pspec);
118 static void notify_notification_get_property(GObject *object, guint prop_id,
119                                              GValue *value, GParamSpec *pspec);
120 static guint signals[LAST_SIGNAL] = { 0 };
121 static GObjectClass *parent_class = NULL;
122
123 G_DEFINE_TYPE(NotifyNotification, notify_notification, G_TYPE_OBJECT)
124
125 static GObject *
126 notify_notification_constructor(GType type,
127                                 guint n_construct_properties,
128                                 GObjectConstructParam *construct_params)
129 {
130     GObject *object = parent_class->constructor(type, n_construct_properties,
131                                                 construct_params);
132
133     _notify_cache_add_notification(NOTIFY_NOTIFICATION(object));
134
135     return object;
136 }
137
138 static void
139 notify_notification_class_init(NotifyNotificationClass *klass)
140 {
141     GObjectClass *object_class = G_OBJECT_CLASS(klass);
142
143     parent_class = g_type_class_peek_parent(klass);
144
145     object_class->constructor = notify_notification_constructor;
146     object_class->get_property = notify_notification_get_property;
147     object_class->set_property = notify_notification_set_property;
148     object_class->finalize = notify_notification_finalize;
149
150     /**
151      * NotifyNotification::closed:
152      * @notification: The object which received the signal.
153      *
154      * Emitted when the notification is closed.
155      */
156     signals[SIGNAL_CLOSED] =
157         g_signal_new("closed",
158                      G_TYPE_FROM_CLASS(object_class),
159                      G_SIGNAL_RUN_FIRST,
160                      G_STRUCT_OFFSET(NotifyNotificationClass, closed),
161                      NULL, NULL,
162                      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
163
164     g_object_class_install_property(object_class, PROP_ID,
165         g_param_spec_int("id", "ID",
166                          "The notification ID",
167                          0,
168                          G_MAXINT32,
169                          0,
170                          G_PARAM_READWRITE |
171                          G_PARAM_CONSTRUCT |
172                          G_PARAM_STATIC_NAME |
173                          G_PARAM_STATIC_NICK |
174                          G_PARAM_STATIC_BLURB));
175
176     g_object_class_install_property(object_class, PROP_SUMMARY,
177         g_param_spec_string("summary", "Summary",
178                             "The summary text",
179                             NULL,
180                             G_PARAM_READWRITE |
181                             G_PARAM_CONSTRUCT |
182                             G_PARAM_STATIC_NAME |
183                             G_PARAM_STATIC_NICK |
184                             G_PARAM_STATIC_BLURB));
185
186     g_object_class_install_property(object_class, PROP_BODY,
187         g_param_spec_string("body", "Message Body",
188                             "The message body text",
189                             NULL,
190                             G_PARAM_READWRITE |
191                             G_PARAM_CONSTRUCT |
192                             G_PARAM_STATIC_NAME |
193                             G_PARAM_STATIC_NICK |
194                             G_PARAM_STATIC_BLURB));
195
196     g_object_class_install_property(object_class, PROP_ICON_NAME,
197         g_param_spec_string("icon-name",
198                             "Icon Name",
199                             "The icon filename or icon theme-compliant name",
200                             NULL,
201                             G_PARAM_READWRITE |
202                             G_PARAM_CONSTRUCT |
203                             G_PARAM_STATIC_NAME |
204                             G_PARAM_STATIC_NICK |
205                             G_PARAM_STATIC_BLURB));
206
207     g_object_class_install_property(object_class, PROP_ATTACH_WIDGET,
208         g_param_spec_object("attach-widget",
209                             "Attach Widget",
210                             "The widget to attach the notification to",
211                             GTK_TYPE_WIDGET,
212                             G_PARAM_READWRITE |
213                             G_PARAM_CONSTRUCT |
214                             G_PARAM_STATIC_NAME |
215                             G_PARAM_STATIC_NICK |
216                             G_PARAM_STATIC_BLURB));
217
218 #ifdef HAVE_STATUS_ICON
219     g_object_class_install_property(object_class, PROP_STATUS_ICON,
220         g_param_spec_object("status-icon",
221                             "Status Icon",
222                             "The status icon to attach the notification to",
223                             GTK_TYPE_STATUS_ICON,
224                             G_PARAM_READWRITE |
225                             G_PARAM_CONSTRUCT |
226                             G_PARAM_STATIC_NAME |
227                             G_PARAM_STATIC_NICK |
228                             G_PARAM_STATIC_BLURB));
229 #endif /* HAVE_STATUS_ICON */
230
231     g_object_class_install_property(object_class, PROP_CLOSED_REASON,
232         g_param_spec_int("closed-reason", "Closed Reason",
233                          "The reason code for why the notification was closed",
234                          -1,
235                          G_MAXINT32,
236                          -1,
237                          G_PARAM_READABLE |
238                          G_PARAM_STATIC_NAME |
239                          G_PARAM_STATIC_NICK |
240                          G_PARAM_STATIC_BLURB));
241 }
242
243 static void
244 notify_notification_set_property(GObject *object,
245                                  guint prop_id,
246                                  const GValue *value,
247                                  GParamSpec *pspec)
248 {
249     NotifyNotification *notification = NOTIFY_NOTIFICATION(object);
250     NotifyNotificationPrivate *priv = notification->priv;
251
252     switch (prop_id)
253     {
254         case PROP_ID:
255             priv->id = g_value_get_int(value);
256             break;
257
258         case PROP_SUMMARY:
259             notify_notification_update(notification, g_value_get_string(value),
260                                        priv->body, priv->icon_name);
261             break;
262
263         case PROP_BODY:
264             notify_notification_update(notification, priv->summary,
265                                        g_value_get_string(value),
266                                        priv->icon_name);
267             break;
268
269         case PROP_ICON_NAME:
270             notify_notification_update(notification, priv->summary,
271                                        priv->body, g_value_get_string(value));
272             break;
273
274         case PROP_ATTACH_WIDGET:
275             notify_notification_attach_to_widget(notification,
276                 g_value_get_object(value));
277             break;
278
279 #ifdef HAVE_STATUS_ICON
280         case PROP_STATUS_ICON:
281             notify_notification_attach_to_status_icon(notification,
282                 g_value_get_object(value));
283             break;
284 #endif
285
286         default:
287             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
288             break;
289     }
290 }
291
292 static void
293 notify_notification_get_property(GObject *object,
294                                  guint prop_id,
295                                  GValue *value,
296                                  GParamSpec *pspec)
297 {
298     NotifyNotification *notification = NOTIFY_NOTIFICATION(object);
299     NotifyNotificationPrivate *priv = notification->priv;
300
301     switch (prop_id)
302     {
303         case PROP_ID:
304             g_value_set_int(value, priv->id);
305             break;
306
307         case PROP_SUMMARY:
308             g_value_set_string(value, priv->summary);
309             break;
310
311         case PROP_BODY:
312             g_value_set_string(value, priv->body);
313             break;
314
315         case PROP_ICON_NAME:
316             g_value_set_string(value, priv->icon_name);
317             break;
318
319         case PROP_ATTACH_WIDGET:
320             g_value_set_object(value, priv->attached_widget);
321             break;
322
323 #ifdef HAVE_STATUS_ICON
324         case PROP_STATUS_ICON:
325             g_value_set_object(value, priv->status_icon);
326             break;
327 #endif
328
329         case PROP_CLOSED_REASON:
330             g_value_set_int(value, priv->closed_reason);
331             break;
332
333         default:
334             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
335             break;
336     }
337 }
338
339 static void
340 _g_value_free(GValue *value)
341 {
342     g_value_unset(value);
343     g_free(value);
344 }
345
346 static void
347 destroy_pair(CallbackPair *pair)
348 {
349     if (pair->user_data != NULL && pair->free_func != NULL)
350         pair->free_func(pair->user_data);
351
352     g_free(pair);
353 }
354
355 static void
356 notify_notification_init(NotifyNotification *obj)
357 {
358     obj->priv = g_new0(NotifyNotificationPrivate, 1);
359     obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT;
360     obj->priv->closed_reason = -1;
361     obj->priv->hints = g_hash_table_new_full(g_str_hash, g_str_equal,
362                                              g_free,
363                                              (GFreeFunc)_g_value_free);
364
365     obj->priv->action_map = g_hash_table_new_full(g_str_hash, g_str_equal,
366                                                   g_free,
367                                                   (GFreeFunc)destroy_pair);
368 }
369
370 static void
371 notify_notification_finalize(GObject *object)
372 {
373     NotifyNotification *obj = NOTIFY_NOTIFICATION(object);
374     NotifyNotificationPrivate *priv = obj->priv;
375     DBusGProxy *proxy = _notify_get_g_proxy();
376
377     _notify_cache_remove_notification(obj);
378
379     g_free(priv->summary);
380     g_free(priv->body);
381     g_free(priv->icon_name);
382
383     if (priv->actions != NULL)
384     {
385         g_slist_foreach(priv->actions, (GFunc)g_free, NULL);
386         g_slist_free(priv->actions);
387     }
388
389     if (priv->action_map != NULL)
390         g_hash_table_destroy(priv->action_map);
391
392     if (priv->hints != NULL)
393         g_hash_table_destroy(priv->hints);
394
395     if (priv->attached_widget != NULL)
396         g_object_unref(G_OBJECT(priv->attached_widget));
397
398 #ifdef HAVE_STATUS_ICON
399     if (priv->status_icon != NULL)
400         g_object_remove_weak_pointer(G_OBJECT(priv->status_icon),
401                                      (gpointer)&priv->status_icon);
402 #endif
403
404     if (priv->signals_registered)
405     {
406         dbus_g_proxy_disconnect_signal(proxy, "NotificationClosed",
407                                        G_CALLBACK(_close_signal_handler),
408                                        object);
409         dbus_g_proxy_disconnect_signal(proxy, "ActionInvoked",
410                                        G_CALLBACK(_action_signal_handler),
411                                        object);
412     }
413
414     g_free(obj->priv);
415
416     G_OBJECT_CLASS(parent_class)->finalize(object);
417 }
418
419 static GtkWidget *
420 get_internal_tray_icon (GtkStatusIcon *status)
421 {
422     /* This function is a temporary hack */
423     return GTK_WIDGET (*((GtkWidget**)(status->priv)));
424 }
425
426 static void
427 _notify_notification_update_applet_hints(NotifyNotification *n)
428 {
429     NotifyNotificationPrivate *priv = n->priv;
430     GdkScreen *screen = NULL;
431     gint x, y;
432
433 #ifdef HAVE_STATUS_ICON
434     if (priv->status_icon != NULL)
435     {
436         GdkRectangle rect;
437         GtkWidget *internal_tray = get_internal_tray_icon (priv->status_icon);
438         GdkWindow *window;
439
440         // TODO: this is sort of a hack, but we need a window ID to send along
441         gtk_widget_realize (internal_tray);
442         window = internal_tray->window;
443
444         if (window != NULL)
445         {
446             guint32 xid = GDK_WINDOW_XID (window);
447             notify_notification_set_hint_uint32(n, "window-xid", xid);
448         }
449
450         if (!gtk_status_icon_get_geometry(priv->status_icon, &screen,
451                                           &rect, NULL))
452         {
453             return;
454         }
455
456         x = rect.x + rect.width / 2;
457         y = rect.y + rect.height / 2;
458     }
459     else
460 #endif /* HAVE_STATUS_ICON */
461     if (priv->attached_widget != NULL)
462     {
463         GtkWidget *widget = priv->attached_widget;
464
465         screen = gtk_widget_get_screen(widget);
466
467         gdk_window_get_origin(widget->window, &x, &y);
468
469         if (GTK_WIDGET_NO_WINDOW(widget))
470         {
471             x += widget->allocation.x;
472             y += widget->allocation.y;
473         }
474
475         x += widget->allocation.width / 2;
476         y += widget->allocation.height / 2;
477     }
478     else
479         return;
480
481     notify_notification_set_geometry_hints(n, screen, x, y);
482 }
483
484 #if 0
485 /*
486  * This is left here just incase we revisit autoupdating
487  * One thought would be to check for updates every time the icon
488  * is redrawn but we still have to deal with the race conditions
489  * that could occure between the server and the client so we will
490  * leave this alone for now.
491  */
492 static gboolean
493 _idle_check_updates(void *user_data)
494 {
495     NotifyNotification *n = NOTIFY_NOTIFICATION(user_data);
496     NotifyNotificationPrivate *priv = n->priv;
497
498     if (priv->is_visible)
499     {
500         priv->updates_pending = _notify_notification_update_applet_hints(n);
501
502         if (priv->updates_pending)
503         {
504             /* Try again if we fail on next idle */
505             priv->updates_pending = !notify_notification_show(n, NULL);
506         }
507     }
508     else
509     {
510         priv->updates_pending = FALSE;
511     }
512
513     return TRUE;
514 }
515 #endif
516
517 /**
518  * notify_notification_new:
519  * @summary: The required summary text.
520  * @body: The optional body text.
521  * @icon: The optional icon theme icon name or filename.
522  * @attach: The optional widget to attach to.
523  *
524  * Creates a new #NotifyNotification. The summary text is required, but
525  * all other parameters are optional.
526  *
527  * Returns: The new #NotifyNotification.
528  */
529 NotifyNotification *
530 notify_notification_new(const gchar *summary, const gchar *body,
531                         const gchar *icon, GtkWidget *attach)
532 {
533     g_return_val_if_fail(attach == NULL || GTK_IS_WIDGET(attach), NULL);
534
535     return g_object_new(NOTIFY_TYPE_NOTIFICATION,
536                         "summary", summary,
537                         "body", body,
538                         "icon-name",  icon,
539                         "attach-widget", attach,
540                         NULL);
541 }
542
543 #ifdef HAVE_STATUS_ICON
544 /**
545  * notify_notification_new_with_status_icon:
546  * @summary: The required summary text.
547  * @body: The optional body text.
548  * @icon: The optional icon theme icon name or filename.
549  * @status_icon: The required #GtkStatusIcon.
550  *
551  * Creates a new #NotifyNotification and attaches to a #GtkStatusIcon.
552  * The summary text and @status_icon is required, but all other parameters
553  * are optional.
554  *
555  * Returns: The new #NotifyNotification.
556  *
557  * Since: 0.4.1
558  */
559 NotifyNotification *
560 notify_notification_new_with_status_icon(const gchar *summary,
561                                          const gchar *message,
562                                          const gchar *icon,
563                                          GtkStatusIcon *status_icon)
564 {
565     g_return_val_if_fail(status_icon != NULL, NULL);
566     g_return_val_if_fail(GTK_IS_STATUS_ICON(status_icon), NULL);
567
568     return g_object_new(NOTIFY_TYPE_NOTIFICATION,
569                         "summary", summary,
570                         "body", message,
571                         "icon-name",  icon,
572                         "status-icon", status_icon,
573                         NULL);
574 }
575 #endif /* HAVE_STATUS_ICON */
576
577 /**
578  * notify_notification_update:
579  * @notification: The notification to update.
580  * @summary: The new required summary text.
581  * @body: The optional body text.
582  * @icon: The optional icon theme icon name or filename.
583  *
584  * Updates the notification text and icon. This won't send the update out
585  * and display it on the screen. For that, you will need to call
586  * notify_notification_show().
587  *
588  * Returns: %TRUE, unless an invalid parameter was passed.
589  */
590 gboolean
591 notify_notification_update(NotifyNotification *notification,
592                            const gchar *summary, const gchar *body,
593                            const gchar *icon)
594 {
595     g_return_val_if_fail(notification != NULL,                 FALSE);
596     g_return_val_if_fail(NOTIFY_IS_NOTIFICATION(notification), FALSE);
597     g_return_val_if_fail(summary != NULL && *summary != '\0',  FALSE);
598
599     if (notification->priv->summary != summary)
600     {
601         g_free(notification->priv->summary);
602         notification->priv->summary = g_strdup(summary);
603         g_object_notify(G_OBJECT(notification), "summary");
604     }
605
606     if (notification->priv->body != body)
607     {
608         g_free(notification->priv->body);
609         notification->priv->body =
610             (body != NULL && *body != '\0' ? g_strdup(body) : NULL);
611         g_object_notify(G_OBJECT(notification), "body");
612     }
613
614     if (notification->priv->icon_name != icon)
615     {
616         g_free(notification->priv->icon_name);
617         notification->priv->icon_name =
618             (icon != NULL && *icon != '\0' ? g_strdup(icon) : NULL);
619         g_object_notify(G_OBJECT(notification), "icon-name");
620     }
621
622     notification->priv->updates_pending = TRUE;
623
624     return TRUE;
625 }
626
627 /**
628  * notify_notification_attach_to_widget:
629  * @notification: The notification.
630  * @attach: The widget to attach to, or %NULL.
631  *
632  * Attaches the notification to a widget. This will set hints on the
633  * notification requesting that the notification point to the widget's
634  * location. If @attach is %NULL, the widget will be unset.
635  */
636 void
637 notify_notification_attach_to_widget(NotifyNotification *notification,
638                                      GtkWidget *attach)
639 {
640     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
641
642     if (notification->priv->attached_widget == attach)
643         return;
644
645     if (notification->priv->attached_widget != NULL)
646         g_object_unref(notification->priv->attached_widget);
647
648     notification->priv->attached_widget =
649         (attach != NULL ? g_object_ref(attach) : NULL);
650
651     g_object_notify(G_OBJECT(notification), "attach-widget");
652 }
653
654 #ifdef HAVE_STATUS_ICON
655 /**
656  * notify_notification_attach_to_status_icon:
657  * @notification: The notification.
658  * @status_icon: The #GtkStatusIcon to attach to, or %NULL.
659  *
660  * Attaches the notification to a #GtkStatusIcon. This will set hints on the
661  * notification requesting that the notification point to the status icon's
662  * location. If @status_icon is %NULL, the status icon will be unset.
663  *
664  * Since: 0.4.1
665  */
666 void
667 notify_notification_attach_to_status_icon(NotifyNotification *notification,
668                                           GtkStatusIcon *status_icon)
669 {
670     NotifyNotificationPrivate *priv;
671
672     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
673     g_return_if_fail(status_icon == NULL || GTK_IS_STATUS_ICON(status_icon));
674
675     priv = notification->priv;
676
677     if (priv->status_icon == status_icon)
678         return;
679
680     if (priv->status_icon != NULL)
681     {
682         g_object_remove_weak_pointer(G_OBJECT(priv->status_icon),
683                                      (gpointer)&priv->status_icon);
684     }
685
686     priv->status_icon = status_icon;
687
688     if (priv->status_icon != NULL)
689     {
690         g_object_add_weak_pointer(G_OBJECT(priv->status_icon),
691                                   (gpointer)&priv->status_icon);
692     }
693
694     g_object_notify(G_OBJECT(notification), "status-icon");
695 }
696 #endif /* HAVE_STATUS_ICON */
697
698 /**
699  * notify_notification_set_geometry_hints:
700  * @notification: The notification.
701  * @screen: The #GdkScreen the notification should appear on.
702  * @x: The X coordinate to point to.
703  * @y: The Y coordinate to point to.
704  *
705  * Sets the geometry hints on the notification. This sets the screen
706  * the notification should appear on and the X, Y coordinates it should
707  * point to, if the particular notification supports X, Y hints.
708  *
709  * Since: 0.4.1
710  */
711 void
712 notify_notification_set_geometry_hints(NotifyNotification *notification,
713                                        GdkScreen *screen,
714                                        gint x,
715                                        gint y)
716 {
717     char *display_name;
718
719     g_return_if_fail(notification != NULL);
720     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
721     g_return_if_fail(screen != NULL);
722     g_return_if_fail(GDK_IS_SCREEN(screen));
723
724     notify_notification_set_hint_int32(notification, "x", x);
725     notify_notification_set_hint_int32(notification, "y", y);
726
727     display_name = gdk_screen_make_display_name(screen);
728     notify_notification_set_hint_string(notification, "xdisplay", display_name);
729     g_free(display_name);
730 }
731
732 static void
733 _close_signal_handler(DBusGProxy *proxy, guint32 id, guint32 reason,
734                       NotifyNotification *notification)
735 {
736     if (id == notification->priv->id)
737     {
738         g_object_ref(G_OBJECT(notification));
739         notification->priv->closed_reason = reason;
740         g_signal_emit(notification, signals[SIGNAL_CLOSED], 0);
741         notification->priv->id = 0;
742         g_object_unref(G_OBJECT(notification));
743     }
744 }
745
746 static void
747 _action_signal_handler(DBusGProxy *proxy, guint32 id, gchar *action,
748                        NotifyNotification *notification)
749 {
750     CallbackPair *pair;
751
752     g_return_if_fail(notification != NULL);
753     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
754
755     if (id != notification->priv->id)
756         return;
757
758     pair = (CallbackPair *)g_hash_table_lookup(
759         notification->priv->action_map, action);
760
761     if (pair == NULL)
762     {
763         if (g_ascii_strcasecmp(action, "default"))
764             g_warning("Received unknown action %s", action);
765     }
766     else
767     {
768         pair->cb(notification, action, pair->user_data);
769     }
770 }
771
772 static gchar **
773 _gslist_to_string_array(GSList *list)
774 {
775     GSList *l;
776     GArray *a = g_array_sized_new(TRUE, FALSE, sizeof(gchar *),
777                                   g_slist_length(list));
778
779     for (l = list; l != NULL; l = l->next)
780         g_array_append_val(a, l->data);
781
782     return (gchar **)g_array_free(a, FALSE);
783 }
784
785 /**
786  * notify_notification_show:
787  * @notification: The notification.
788  * @error: The returned error information.
789  *
790  * Tells the notification server to display the notification on the screen.
791  *
792  * Returns: %TRUE if successful. On error, this will return %FALSE and set
793  *          @error.
794  */
795 gboolean
796 notify_notification_show(NotifyNotification *notification, GError **error)
797 {
798     NotifyNotificationPrivate *priv;
799     GError *tmp_error = NULL;
800     gchar **action_array;
801     DBusGProxy *proxy;
802
803     g_return_val_if_fail(notification != NULL, FALSE);
804     g_return_val_if_fail(NOTIFY_IS_NOTIFICATION(notification), FALSE);
805     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
806
807     priv = notification->priv;
808     proxy = _notify_get_g_proxy();
809
810     if (!priv->signals_registered)
811     {
812         dbus_g_proxy_connect_signal(proxy, "NotificationClosed",
813                                     G_CALLBACK(_close_signal_handler),
814                                     notification, NULL);
815
816         dbus_g_proxy_connect_signal(proxy, "ActionInvoked",
817                                     G_CALLBACK(_action_signal_handler),
818                                     notification, NULL);
819
820         priv->signals_registered = TRUE;
821     }
822
823     /* If attached to a widget or status icon, modify x and y in hints */
824     _notify_notification_update_applet_hints(notification);
825
826     action_array = _gslist_to_string_array(priv->actions);
827
828     /* TODO: make this nonblocking */
829     dbus_g_proxy_call(proxy, "Notify", &tmp_error,
830                       G_TYPE_STRING, notify_get_app_name(),
831                       G_TYPE_UINT, priv->id,
832                       G_TYPE_STRING, priv->icon_name,
833                       G_TYPE_STRING, priv->summary,
834                       G_TYPE_STRING, priv->body,
835                       G_TYPE_STRV, action_array,
836                       dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
837                                           G_TYPE_VALUE), priv->hints,
838                       G_TYPE_INT, priv->timeout,
839                       G_TYPE_INVALID,
840                       G_TYPE_UINT, &priv->id,
841                       G_TYPE_INVALID);
842
843     /* Don't free the elements because they are owned by priv->actions */
844     g_free(action_array);
845
846     if (tmp_error != NULL)
847     {
848         g_propagate_error(error, tmp_error);
849         return FALSE;
850     }
851
852     return TRUE;
853 }
854
855 /**
856  * notify_notification_set_timeout:
857  * @notification: The notification.
858  * @timeout: The timeout in milliseconds.
859  *
860  * Sets the timeout of the notification. To set the default time, pass
861  * %NOTIFY_EXPIRES_DEFAULT as @timeout. To set the notification to never
862  * expire, pass %NOTIFY_EXPIRES_NEVER.
863  */
864 void
865 notify_notification_set_timeout(NotifyNotification *notification,
866                                 gint timeout)
867 {
868     g_return_if_fail(notification != NULL);
869     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
870
871     notification->priv->timeout = timeout;
872 }
873
874 gint
875 _notify_notification_get_timeout(const NotifyNotification *notification)
876 {
877     g_return_val_if_fail(notification != NULL, -1);
878     g_return_val_if_fail(NOTIFY_IS_NOTIFICATION(notification), -1);
879
880     return notification->priv->timeout;
881 }
882
883 /**
884  * notify_notification_set_category:
885  * @notification: The notification.
886  * @category: The category.
887  *
888  * Sets the category of this notification. This can be used by the
889  * notification server to filter or display the data in a certain way.
890  */
891 void
892 notify_notification_set_category(NotifyNotification *notification,
893                                  const char *category)
894 {
895     g_return_if_fail(notification != NULL);
896     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
897
898     notify_notification_set_hint_string(notification, "category", category);
899 }
900
901 /**
902  * notify_notification_set_urgency:
903  * @notification: The notification.
904  * @urgency: The urgency level.
905  *
906  * Sets the urgency level of this notification.
907  *
908  * See: #NotifyUrgency
909  */
910 void
911 notify_notification_set_urgency(NotifyNotification *notification,
912                                 NotifyUrgency urgency)
913 {
914     g_return_if_fail(notification != NULL);
915     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
916
917     notify_notification_set_hint_byte(notification, "urgency", (guchar)urgency);
918 }
919
920 #if CHECK_DBUS_VERSION(0, 60)
921 static void
922 _gvalue_array_append_int(GValueArray *array, gint i)
923 {
924     GValue value = {0};
925
926     g_value_init(&value, G_TYPE_INT);
927     g_value_set_int(&value, i);
928     g_value_array_append(array, &value);
929     g_value_unset(&value);
930 }
931
932 static void
933 _gvalue_array_append_bool(GValueArray *array, gboolean b)
934 {
935     GValue value = {0};
936
937     g_value_init(&value, G_TYPE_BOOLEAN);
938     g_value_set_boolean(&value, b);
939     g_value_array_append(array, &value);
940     g_value_unset(&value);
941 }
942
943 static void
944 _gvalue_array_append_byte_array(GValueArray *array, guchar *bytes, gsize len)
945 {
946     GArray *byte_array;
947     GValue value = {0};
948
949     byte_array = g_array_sized_new(FALSE, FALSE, sizeof(guchar), len);
950     g_assert(byte_array != NULL);
951     byte_array = g_array_append_vals(byte_array, bytes, len);
952
953     g_value_init(&value, DBUS_TYPE_G_UCHAR_ARRAY);
954     g_value_set_boxed_take_ownership(&value, byte_array);
955     g_value_array_append(array, &value);
956     g_value_unset(&value);
957 }
958 #endif /* D-BUS >= 0.60 */
959
960 /**
961  * notify_notification_set_icon_from_pixbuf:
962  * @notification: The notification.
963  * @icon: The icon.
964  *
965  * Sets the icon in the notification from a #GdkPixbuf.
966  *
967  * This will only work when libnotify is compiled against D-BUS 0.60 or
968  * higher.
969  */
970 void
971 notify_notification_set_icon_from_pixbuf(NotifyNotification *notification,
972                                          GdkPixbuf *icon)
973 {
974 #if CHECK_DBUS_VERSION(0, 60)
975     gint width;
976     gint height;
977     gint rowstride;
978     gint bits_per_sample;
979     gint n_channels;
980     guchar *image;
981     gsize image_len;
982     GValueArray *image_struct;
983     GValue *value;
984 #endif
985
986     g_return_if_fail(notification != NULL);
987     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
988
989 #if CHECK_DBUS_VERSION(0, 60)
990     width           = gdk_pixbuf_get_width(icon);
991     height          = gdk_pixbuf_get_height(icon);
992     rowstride       = gdk_pixbuf_get_rowstride(icon);
993     n_channels      = gdk_pixbuf_get_n_channels(icon);
994     bits_per_sample = gdk_pixbuf_get_bits_per_sample(icon);
995     image_len       = (height - 1) * rowstride + width *
996                       ((n_channels * bits_per_sample + 7) / 8);
997
998     image = gdk_pixbuf_get_pixels(icon);
999
1000     image_struct = g_value_array_new(1);
1001
1002     _gvalue_array_append_int(image_struct, width);
1003     _gvalue_array_append_int(image_struct, height);
1004     _gvalue_array_append_int(image_struct, rowstride);
1005     _gvalue_array_append_bool(image_struct, gdk_pixbuf_get_has_alpha(icon));
1006     _gvalue_array_append_int(image_struct, bits_per_sample);
1007     _gvalue_array_append_int(image_struct, n_channels);
1008     _gvalue_array_append_byte_array(image_struct, image, image_len);
1009
1010     value = g_new0(GValue, 1);
1011     g_value_init(value, G_TYPE_VALUE_ARRAY);
1012     g_value_set_boxed_take_ownership(value, image_struct);
1013
1014     g_hash_table_insert(notification->priv->hints,
1015                         g_strdup("icon_data"), value);
1016 #else /* D-BUS < 0.60 */
1017     g_warning("Raw images and pixbufs require D-BUS >= 0.60");
1018 #endif
1019 }
1020
1021 /**
1022  * notify_notification_set_hint_int32:
1023  * @notification: The notification.
1024  * @key: The hint.
1025  * @value: The hint's value.
1026  *
1027  * Sets a hint with a 32-bit integer value.
1028  */
1029 void
1030 notify_notification_set_hint_int32(NotifyNotification *notification,
1031                                    const gchar *key, gint value)
1032 {
1033     GValue *hint_value;
1034
1035     g_return_if_fail(notification != NULL);
1036     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
1037     g_return_if_fail(key != NULL && *key != '\0');
1038
1039     hint_value = g_new0(GValue, 1);
1040     g_value_init(hint_value, G_TYPE_INT);
1041     g_value_set_int(hint_value, value);
1042     g_hash_table_insert(notification->priv->hints,
1043                         g_strdup(key), hint_value);
1044 }
1045
1046
1047 /**
1048  * notify_notification_set_hint_uint32:
1049  * @notification: The notification.
1050  * @key: The hint.
1051  * @value: The hint's value.
1052  *
1053  * Sets a hint with an unsigned 32-bit integer value.
1054  */
1055 void
1056 notify_notification_set_hint_uint32(NotifyNotification *notification,
1057                                     const gchar *key, guint value)
1058 {
1059     GValue *hint_value;
1060
1061     g_return_if_fail(notification != NULL);
1062     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
1063     g_return_if_fail(key != NULL && *key != '\0');
1064
1065     hint_value = g_new0(GValue, 1);
1066     g_value_init(hint_value, G_TYPE_UINT);
1067     g_value_set_uint(hint_value, value);
1068     g_hash_table_insert(notification->priv->hints,
1069                         g_strdup(key), hint_value);
1070 }
1071
1072 /**
1073  * notify_notification_set_hint_double:
1074  * @notification: The notification.
1075  * @key: The hint.
1076  * @value: The hint's value.
1077  *
1078  * Sets a hint with a double value.
1079  */
1080 void
1081 notify_notification_set_hint_double(NotifyNotification *notification,
1082                                     const gchar *key, gdouble value)
1083 {
1084     GValue *hint_value;
1085
1086     g_return_if_fail(notification != NULL);
1087     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
1088     g_return_if_fail(key != NULL && *key != '\0');
1089
1090     hint_value = g_new0(GValue, 1);
1091     g_value_init(hint_value, G_TYPE_FLOAT);
1092     g_value_set_float(hint_value, value);
1093     g_hash_table_insert(notification->priv->hints,
1094                         g_strdup(key), hint_value);
1095 }
1096
1097 /**
1098  * notify_notification_set_hint_byte:
1099  * @notification: The notification.
1100  * @key: The hint.
1101  * @value: The hint's value.
1102  *
1103  * Sets a hint with a byte value.
1104  */
1105 void
1106 notify_notification_set_hint_byte(NotifyNotification *notification,
1107                                   const gchar *key, guchar value)
1108 {
1109     GValue *hint_value;
1110
1111     g_return_if_fail(notification != NULL);
1112     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
1113     g_return_if_fail(key != NULL && *key != '\0');
1114
1115     hint_value = g_new0(GValue, 1);
1116     g_value_init(hint_value, G_TYPE_UCHAR);
1117     g_value_set_uchar(hint_value, value);
1118
1119     g_hash_table_insert(notification->priv->hints, g_strdup(key), hint_value);
1120 }
1121
1122 /**
1123  * notify_notification_set_hint_byte_array:
1124  * @notification: The notification.
1125  * @key: The hint.
1126  * @value: The hint's value.
1127  * @len: The length of the byte array.
1128  *
1129  * Sets a hint with a byte array value. The length of @value must be passed
1130  * as @len.
1131  */
1132 void
1133 notify_notification_set_hint_byte_array(NotifyNotification *notification,
1134                                         const gchar *key,
1135                                         const guchar *value, gsize len)
1136 {
1137     GValue *hint_value;
1138     GArray *byte_array;
1139
1140     g_return_if_fail(notification != NULL);
1141     g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification));
1142     g_return_if_fail(key != NULL && *key != '\0');
1143     g_return_if_fail(value != NULL);
1144     g_return_if_fail(len > 0);
1145
1146     byte_array = g_array_sized_new(FALSE, FALSE, sizeof(guchar), len);
1147     byte_array = g_array_append_vals(byte_array, value, len);
1148
1149     hint_value = g_new0(GValue, 1);
1150     g_value_init(hint_value, dbus_g_type_get_collection("GArray",
1151                                                         G_TYPE_UCHAR));
1152     g_value_set_boxed_take_ownership(hint_value, byte_array);
1153
1154     g_hash_table_insert(notification->priv->hints,