[gnome-flashback] notifications: copy code from notification-daemon
- From: Alberts Muktupāvels <muktupavels src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-flashback] notifications: copy code from notification-daemon
- Date: Thu, 18 Feb 2016 12:22:00 +0000 (UTC)
commit 19052b08bddf993dae9de4c075aecced00091545
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date: Thu Feb 18 14:20:18 2016 +0200
notifications: copy code from notification-daemon
gnome-flashback/libnotifications/Makefile.am | 12 +
.../libnotifications/gf-notifications.c | 23 +-
gnome-flashback/libnotifications/nd-bubble.c | 915 ++++++++++++++++++
gnome-flashback/libnotifications/nd-bubble.h | 56 ++
gnome-flashback/libnotifications/nd-daemon.c | 341 +++++++
gnome-flashback/libnotifications/nd-daemon.h | 32 +
.../libnotifications/nd-notification-box.c | 411 ++++++++
.../libnotifications/nd-notification-box.h | 54 +
gnome-flashback/libnotifications/nd-notification.c | 570 +++++++++++
gnome-flashback/libnotifications/nd-notification.h | 90 ++
gnome-flashback/libnotifications/nd-queue.c | 1020 ++++++++++++++++++++
gnome-flashback/libnotifications/nd-queue.h | 65 ++
gnome-flashback/libnotifications/nd-stack.c | 480 +++++++++
gnome-flashback/libnotifications/nd-stack.h | 74 ++
po/POTFILES.in | 4 +
15 files changed, 4146 insertions(+), 1 deletions(-)
---
diff --git a/gnome-flashback/libnotifications/Makefile.am b/gnome-flashback/libnotifications/Makefile.am
index 15948d0..cb7ff5f 100644
--- a/gnome-flashback/libnotifications/Makefile.am
+++ b/gnome-flashback/libnotifications/Makefile.am
@@ -17,6 +17,18 @@ libnotifications_la_CFLAGS = \
libnotifications_la_SOURCES = \
gf-notifications.c \
gf-notifications.h \
+ nd-bubble.c \
+ nd-bubble.h \
+ nd-daemon.c \
+ nd-daemon.h \
+ nd-notification.c \
+ nd-notification.h \
+ nd-notification-box.c \
+ nd-notification-box.h \
+ nd-queue.c \
+ nd-queue.h \
+ nd-stack.c \
+ nd-stack.h \
$(BUILT_SOURCES) \
$(NULL)
diff --git a/gnome-flashback/libnotifications/gf-notifications.c
b/gnome-flashback/libnotifications/gf-notifications.c
index ac09757..f84d55f 100644
--- a/gnome-flashback/libnotifications/gf-notifications.c
+++ b/gnome-flashback/libnotifications/gf-notifications.c
@@ -17,23 +17,44 @@
#include "config.h"
+#include "nd-daemon.h"
#include "gf-notifications.h"
struct _GfNotifications
{
- GObject parent;
+ GObject parent;
+
+ NdDaemon *daemon;
};
G_DEFINE_TYPE (GfNotifications, gf_notifications, G_TYPE_OBJECT)
static void
+gf_notifications_dispose (GObject *object)
+{
+ GfNotifications *notifications;
+
+ notifications = GF_NOTIFICATIONS (object);
+
+ g_clear_object (¬ifications->daemon);
+
+ G_OBJECT_CLASS (gf_notifications_parent_class)->dispose (object);
+}
+
+static void
gf_notifications_class_init (GfNotificationsClass *notifications_class)
{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (notifications_class);
+
+ object_class->dispose = gf_notifications_dispose;
}
static void
gf_notifications_init (GfNotifications *notifications)
{
+ notifications->daemon = nd_daemon_new ();
}
GfNotifications *
diff --git a/gnome-flashback/libnotifications/nd-bubble.c b/gnome-flashback/libnotifications/nd-bubble.c
new file mode 100644
index 0000000..ff24d27
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-bubble.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <strings.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "nd-notification.h"
+#include "nd-bubble.h"
+
+#define ND_BUBBLE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ND_TYPE_BUBBLE, NdBubblePrivate))
+
+#define EXPIRATION_TIME_DEFAULT -1
+#define EXPIRATION_TIME_NEVER_EXPIRES 0
+#define TIMEOUT_SEC 5
+
+#define WIDTH 400
+#define DEFAULT_X0 0
+#define DEFAULT_Y0 0
+#define DEFAULT_RADIUS 16
+#define IMAGE_SIZE 48
+#define BODY_X_OFFSET (IMAGE_SIZE + 8)
+#define BACKGROUND_ALPHA 0.90
+
+#define MAX_ICON_SIZE IMAGE_SIZE
+
+struct NdBubblePrivate
+{
+ NdNotification *notification;
+
+ GtkWidget *main_hbox;
+ GtkWidget *icon;
+ GtkWidget *content_hbox;
+ GtkWidget *summary_label;
+ GtkWidget *close_button;
+ GtkWidget *body_label;
+ GtkWidget *actions_box;
+ GtkWidget *last_sep;
+
+ int width;
+ int height;
+ int last_width;
+ int last_height;
+
+ gboolean have_icon;
+ gboolean have_body;
+ gboolean have_actions;
+
+ gboolean url_clicked_lock;
+
+ gboolean composited;
+ glong remaining;
+ guint timeout_id;
+};
+
+static void nd_bubble_finalize (GObject *object);
+static void on_notification_changed (NdNotification *notification,
+ NdBubble *bubble);
+
+G_DEFINE_TYPE (NdBubble, nd_bubble, GTK_TYPE_WINDOW)
+
+NdNotification *
+nd_bubble_get_notification (NdBubble *bubble)
+{
+ g_return_val_if_fail (ND_IS_BUBBLE (bubble), NULL);
+
+ return bubble->priv->notification;
+}
+
+static gboolean
+nd_bubble_configure_event (GtkWidget *widget,
+ GdkEventConfigure *event)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+
+ bubble->priv->width = event->width;
+ bubble->priv->height = event->height;
+
+ gtk_widget_queue_draw (widget);
+
+ return FALSE;
+}
+
+static void
+nd_bubble_composited_changed (GtkWidget *widget)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+ GdkScreen *screen;
+ GdkVisual *visual;
+
+ bubble->priv->composited = gdk_screen_is_composited (gtk_widget_get_screen (widget));
+
+ screen = gtk_window_get_screen (GTK_WINDOW (bubble));
+ visual = gdk_screen_get_rgba_visual (screen);
+ if (visual == NULL) {
+ visual = gdk_screen_get_system_visual (screen);
+ }
+
+ gtk_widget_set_visual (GTK_WIDGET (bubble), visual);
+
+ gtk_widget_queue_draw (widget);
+}
+
+static void
+draw_round_rect (cairo_t *cr,
+ gdouble aspect,
+ gdouble x,
+ gdouble y,
+ gdouble corner_radius,
+ gdouble width,
+ gdouble height)
+{
+ gdouble radius = corner_radius / aspect;
+
+ cairo_move_to (cr, x + radius, y);
+
+ // top-right, left of the corner
+ cairo_line_to (cr,
+ x + width - radius,
+ y);
+
+ // top-right, below the corner
+ cairo_arc (cr,
+ x + width - radius,
+ y + radius,
+ radius,
+ -90.0f * G_PI / 180.0f,
+ 0.0f * G_PI / 180.0f);
+
+ // bottom-right, above the corner
+ cairo_line_to (cr,
+ x + width,
+ y + height - radius);
+
+ // bottom-right, left of the corner
+ cairo_arc (cr,
+ x + width - radius,
+ y + height - radius,
+ radius,
+ 0.0f * G_PI / 180.0f,
+ 90.0f * G_PI / 180.0f);
+
+ // bottom-left, right of the corner
+ cairo_line_to (cr,
+ x + radius,
+ y + height);
+
+ // bottom-left, above the corner
+ cairo_arc (cr,
+ x + radius,
+ y + height - radius,
+ radius,
+ 90.0f * G_PI / 180.0f,
+ 180.0f * G_PI / 180.0f);
+
+ // top-left, below the corner
+ cairo_line_to (cr,
+ x,
+ y + radius);
+
+ // top-left, right of the corner
+ cairo_arc (cr,
+ x + radius,
+ y + radius,
+ radius,
+ 180.0f * G_PI / 180.0f,
+ 270.0f * G_PI / 180.0f);
+}
+
+static void
+get_background_color (GtkStyleContext *context,
+ GtkStateFlags state,
+ GdkRGBA *color)
+{
+ GdkRGBA *c;
+
+ g_return_if_fail (color != NULL);
+ g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
+
+ gtk_style_context_get (context, state,
+ "background-color", &c,
+ NULL);
+
+ *color = *c;
+ gdk_rgba_free (c);
+}
+
+static void
+paint_bubble (NdBubble *bubble,
+ cairo_t *cr)
+{
+ GtkStyleContext *context;
+ GdkRGBA bg;
+ GdkRGBA fg;
+ cairo_t *cr2;
+ cairo_surface_t *surface;
+ cairo_region_t *region;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (bubble), &allocation);
+ if (bubble->priv->width == 0 || bubble->priv->height == 0) {
+ bubble->priv->width = MAX (allocation.width, 1);
+ bubble->priv->height = MAX (allocation.height, 1);
+ }
+
+ surface = cairo_surface_create_similar (cairo_get_target (cr),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ bubble->priv->width,
+ bubble->priv->height);
+ cr2 = cairo_create (surface);
+
+ /* transparent background */
+ cairo_rectangle (cr2, 0, 0, bubble->priv->width, bubble->priv->height);
+ cairo_set_source_rgba (cr2, 0.0, 0.0, 0.0, 0.0);
+ cairo_fill (cr2);
+
+ draw_round_rect (cr2,
+ 1.0f,
+ DEFAULT_X0 + 1,
+ DEFAULT_Y0 + 1,
+ DEFAULT_RADIUS,
+ allocation.width - 2,
+ allocation.height - 2);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (bubble));
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
+
+ get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
+ gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg);
+
+ gtk_style_context_restore (context);
+
+ cairo_set_source_rgba (cr2, bg.red, bg.green, bg.blue,
+ BACKGROUND_ALPHA);
+ cairo_fill_preserve (cr2);
+
+ cairo_set_source_rgba (cr2, fg.red, fg.green, fg.blue,
+ BACKGROUND_ALPHA / 2);
+ cairo_set_line_width (cr2, 2);
+ cairo_stroke (cr2);
+
+ cairo_destroy (cr2);
+
+ cairo_save (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface (cr, surface, 0, 0);
+ cairo_paint (cr);
+ cairo_restore (cr);
+
+ if (bubble->priv->width == bubble->priv->last_width
+ && bubble->priv->height == bubble->priv->last_height) {
+ goto done;
+ }
+
+ /* Don't shape when composited */
+ if (bubble->priv->composited) {
+ gtk_widget_shape_combine_region (GTK_WIDGET (bubble), NULL);
+ goto done;
+ }
+
+ bubble->priv->last_width = bubble->priv->width;
+ bubble->priv->last_height = bubble->priv->height;
+
+ region = gdk_cairo_region_create_from_surface (surface);
+ gtk_widget_shape_combine_region (GTK_WIDGET (bubble), region);
+ cairo_region_destroy (region);
+
+ done:
+ cairo_surface_destroy (surface);
+
+}
+
+static gboolean
+nd_bubble_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+
+ paint_bubble (bubble, cr);
+
+ GTK_WIDGET_CLASS (nd_bubble_parent_class)->draw (widget, cr);
+
+ return FALSE;
+}
+
+static gboolean
+nd_bubble_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+
+ if (bubble->priv->url_clicked_lock) {
+ bubble->priv->url_clicked_lock = FALSE;
+ return FALSE;
+ }
+
+ nd_notification_action_invoked (bubble->priv->notification, "default");
+ gtk_widget_destroy (GTK_WIDGET (bubble));
+
+ return FALSE;
+}
+
+static gboolean
+timeout_bubble (NdBubble *bubble)
+{
+ bubble->priv->timeout_id = 0;
+
+ /* FIXME: if transient also close it */
+
+ gtk_widget_destroy (GTK_WIDGET (bubble));
+
+ return FALSE;
+}
+
+static void
+add_timeout (NdBubble *bubble)
+{
+ int timeout = nd_notification_get_timeout(bubble->priv->notification);
+
+ if (bubble->priv->timeout_id != 0) {
+ g_source_remove (bubble->priv->timeout_id);
+ bubble->priv->timeout_id = 0;
+ }
+
+ if (timeout == EXPIRATION_TIME_NEVER_EXPIRES)
+ return;
+
+ if (timeout == EXPIRATION_TIME_DEFAULT)
+ timeout = TIMEOUT_SEC * 1000;
+
+ bubble->priv->timeout_id = g_timeout_add (timeout,
+ (GSourceFunc) timeout_bubble,
+ bubble);
+}
+
+static void
+nd_bubble_realize (GtkWidget *widget)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+
+ add_timeout (bubble);
+
+ GTK_WIDGET_CLASS (nd_bubble_parent_class)->realize (widget);
+}
+
+static void
+nd_bubble_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *nat_width)
+{
+ if (nat_width != NULL) {
+ *nat_width = WIDTH;
+ }
+}
+
+static gboolean
+nd_bubble_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+
+ add_timeout (bubble);
+
+ return FALSE;
+}
+
+static void
+nd_bubble_class_init (NdBubbleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nd_bubble_finalize;
+
+ widget_class->draw = nd_bubble_draw;
+ widget_class->configure_event = nd_bubble_configure_event;
+ widget_class->composited_changed = nd_bubble_composited_changed;
+ widget_class->button_release_event = nd_bubble_button_release_event;
+ widget_class->motion_notify_event = nd_bubble_motion_notify_event;
+ widget_class->realize = nd_bubble_realize;
+ widget_class->get_preferred_width = nd_bubble_get_preferred_width;
+
+ g_type_class_add_private (klass, sizeof (NdBubblePrivate));
+}
+
+static gboolean
+on_activate_link (GtkLabel *label,
+ char *uri,
+ NdBubble *bubble)
+{
+ char *escaped_uri;
+ char *cmd = NULL;
+ char *found = NULL;
+
+ /* Somewhat of a hack.. */
+ bubble->priv->url_clicked_lock = TRUE;
+
+ escaped_uri = g_shell_quote (uri);
+
+ if ((found = g_find_program_in_path ("gvfs-open")) != NULL) {
+ cmd = g_strdup_printf ("gvfs-open %s", escaped_uri);
+ } else if ((found = g_find_program_in_path ("xdg-open")) != NULL) {
+ cmd = g_strdup_printf ("xdg-open %s", escaped_uri);
+ } else if ((found = g_find_program_in_path ("firefox")) != NULL) {
+ cmd = g_strdup_printf ("firefox %s", escaped_uri);
+ } else {
+ g_warning ("Unable to find a browser.");
+ }
+
+ g_free (escaped_uri);
+ g_free (found);
+
+ if (cmd != NULL) {
+ g_spawn_command_line_async (cmd, NULL);
+ g_free (cmd);
+ }
+
+ return TRUE;
+}
+
+static void
+on_close_button_clicked (GtkButton *button,
+ NdBubble *bubble)
+{
+ nd_notification_close (bubble->priv->notification, ND_NOTIFICATION_CLOSED_USER);
+ gtk_widget_destroy (GTK_WIDGET (bubble));
+}
+
+static void
+nd_bubble_init (NdBubble *bubble)
+{
+ GtkWidget *main_vbox;
+ GtkWidget *vbox;
+ GtkWidget *close_button;
+ GtkWidget *image;
+ AtkObject *atkobj;
+ GdkScreen *screen;
+ GdkVisual *visual;
+
+ bubble->priv = ND_BUBBLE_GET_PRIVATE (bubble);
+
+ gtk_widget_add_events (GTK_WIDGET (bubble), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK);
+ atk_object_set_role (gtk_widget_get_accessible (GTK_WIDGET (bubble)), ATK_ROLE_ALERT);
+
+ screen = gtk_window_get_screen (GTK_WINDOW (bubble));
+ visual = gdk_screen_get_rgba_visual (screen);
+ if (visual == NULL) {
+ visual = gdk_screen_get_system_visual (screen);
+ }
+
+ gtk_widget_set_visual (GTK_WIDGET (bubble), visual);
+
+ if (gdk_screen_is_composited (screen)) {
+ bubble->priv->composited = TRUE;
+ }
+
+ main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_show (main_vbox);
+ gtk_container_add (GTK_CONTAINER (bubble), main_vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
+
+ bubble->priv->main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_show (bubble->priv->main_hbox);
+ gtk_box_pack_start (GTK_BOX (main_vbox),
+ bubble->priv->main_hbox,
+ FALSE, FALSE, 0);
+
+ /* Add icon */
+
+ bubble->priv->icon = gtk_image_new ();
+ gtk_widget_set_valign (bubble->priv->icon, GTK_ALIGN_START);
+ gtk_widget_set_margin_top (bubble->priv->icon, 5);
+ gtk_widget_set_size_request (bubble->priv->icon, BODY_X_OFFSET, -1);
+ gtk_widget_show (bubble->priv->icon);
+
+ gtk_box_pack_start (GTK_BOX (bubble->priv->main_hbox),
+ bubble->priv->icon,
+ FALSE, FALSE, 0);
+
+ /* Add vbox */
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (bubble->priv->main_hbox), vbox, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
+
+ /* Add the close button */
+
+ close_button = gtk_button_new ();
+ gtk_widget_set_valign (close_button, GTK_ALIGN_START);
+ gtk_widget_show (close_button);
+
+ bubble->priv->close_button = close_button;
+ gtk_box_pack_start (GTK_BOX (bubble->priv->main_hbox),
+ bubble->priv->close_button,
+ FALSE, FALSE, 0);
+
+ gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
+ gtk_container_set_border_width (GTK_CONTAINER (close_button), 0);
+ g_signal_connect (G_OBJECT (close_button),
+ "clicked",
+ G_CALLBACK (on_close_button_clicked),
+ bubble);
+
+ atkobj = gtk_widget_get_accessible (close_button);
+ atk_action_set_description (ATK_ACTION (atkobj), 0,
+ _("Closes the notification."));
+ atk_object_set_name (atkobj, "");
+ atk_object_set_description (atkobj, _("Closes the notification."));
+
+ image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+ gtk_container_add (GTK_CONTAINER (close_button), image);
+
+ /* center vbox */
+ bubble->priv->summary_label = gtk_label_new (NULL);
+ gtk_widget_show (bubble->priv->summary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), bubble->priv->summary_label, TRUE, TRUE, 0);
+ gtk_label_set_xalign (GTK_LABEL (bubble->priv->summary_label), 0.0);
+ gtk_label_set_yalign (GTK_LABEL (bubble->priv->summary_label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (bubble->priv->summary_label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (bubble->priv->summary_label), PANGO_WRAP_WORD_CHAR);
+
+ atkobj = gtk_widget_get_accessible (bubble->priv->summary_label);
+ atk_object_set_description (atkobj, _("Notification summary text."));
+
+ bubble->priv->content_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_show (bubble->priv->content_hbox);
+ gtk_box_pack_start (GTK_BOX (vbox), bubble->priv->content_hbox, FALSE, FALSE, 0);
+
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (bubble->priv->content_hbox), vbox, TRUE, TRUE, 0);
+
+ bubble->priv->body_label = gtk_label_new (NULL);
+ gtk_widget_show (bubble->priv->body_label);
+ gtk_box_pack_start (GTK_BOX (vbox), bubble->priv->body_label, TRUE, TRUE, 0);
+ gtk_label_set_xalign (GTK_LABEL (bubble->priv->body_label), 0.0);
+ gtk_label_set_yalign (GTK_LABEL (bubble->priv->body_label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (bubble->priv->body_label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (bubble->priv->body_label), PANGO_WRAP_WORD_CHAR);
+ g_signal_connect (bubble->priv->body_label,
+ "activate-link",
+ G_CALLBACK (on_activate_link),
+ bubble);
+
+ atkobj = gtk_widget_get_accessible (bubble->priv->body_label);
+ atk_object_set_description (atkobj, _("Notification summary text."));
+
+ bubble->priv->actions_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_set_halign (bubble->priv->actions_box, GTK_ALIGN_END);
+ gtk_widget_show (bubble->priv->actions_box);
+
+ gtk_box_pack_start (GTK_BOX (vbox), bubble->priv->actions_box, FALSE, TRUE, 0);
+}
+
+static void
+nd_bubble_finalize (GObject *object)
+{
+ NdBubble *bubble;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (ND_IS_BUBBLE (object));
+
+ bubble = ND_BUBBLE (object);
+
+ g_return_if_fail (bubble->priv != NULL);
+
+ if (bubble->priv->timeout_id != 0) {
+ g_source_remove (bubble->priv->timeout_id);
+ }
+
+ g_signal_handlers_disconnect_by_func (bubble->priv->notification, G_CALLBACK
(on_notification_changed), bubble);
+
+ g_object_unref (bubble->priv->notification);
+
+ G_OBJECT_CLASS (nd_bubble_parent_class)->finalize (object);
+}
+
+static void
+update_content_hbox_visibility (NdBubble *bubble)
+{
+ if (bubble->priv->have_icon
+ || bubble->priv->have_body
+ || bubble->priv->have_actions) {
+ gtk_widget_show (bubble->priv->content_hbox);
+ } else {
+ gtk_widget_hide (bubble->priv->content_hbox);
+ }
+}
+
+static void
+set_notification_text (NdBubble *bubble,
+ const char *summary,
+ const char *body)
+{
+ char *str;
+ char *quoted;
+ GtkRequisition req;
+ int summary_width;
+
+ quoted = g_markup_escape_text (summary, -1);
+ str = g_strdup_printf ("<b><big>%s</big></b>", quoted);
+ g_free (quoted);
+
+ gtk_label_set_markup (GTK_LABEL (bubble->priv->summary_label), str);
+
+ g_free (str);
+ gtk_widget_show_all (GTK_WIDGET (bubble));
+
+ if (pango_parse_markup (body, -1, 0, NULL, NULL, NULL, NULL))
+ gtk_label_set_markup (GTK_LABEL (bubble->priv->body_label), body);
+ else {
+ gchar *tmp;
+
+ tmp = g_markup_escape_text (body, -1);
+ gtk_label_set_text (GTK_LABEL (bubble->priv->body_label), body);
+ g_free (tmp);
+ }
+
+ if (body == NULL || *body == '\0') {
+ bubble->priv->have_body = FALSE;
+ gtk_widget_hide (bubble->priv->body_label);
+ } else {
+ bubble->priv->have_body = TRUE;
+ gtk_widget_show (bubble->priv->body_label);
+ }
+ update_content_hbox_visibility (bubble);
+
+ gtk_widget_get_preferred_size (bubble->priv->close_button, NULL, &req);
+ /* -1: main_vbox border width
+ -10: vbox border width
+ -6: spacing for hbox */
+ summary_width = WIDTH - (1*2) - (10*2) - BODY_X_OFFSET - req.width - (6*2);
+
+ if (body != NULL && *body != '\0') {
+ gtk_widget_set_size_request (bubble->priv->body_label,
+ summary_width,
+ -1);
+ }
+
+ gtk_widget_set_size_request (bubble->priv->summary_label,
+ summary_width,
+ -1);
+}
+
+static GdkPixbuf *
+scale_pixbuf (GdkPixbuf *pixbuf,
+ int max_width,
+ int max_height,
+ gboolean no_stretch_hint)
+{
+ int pw;
+ int ph;
+ float scale_factor_x = 1.0;
+ float scale_factor_y = 1.0;
+ float scale_factor = 1.0;
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ /* Determine which dimension requires the smallest scale. */
+ scale_factor_x = (float) max_width / (float) pw;
+ scale_factor_y = (float) max_height / (float) ph;
+
+ if (scale_factor_x > scale_factor_y) {
+ scale_factor = scale_factor_y;
+ } else {
+ scale_factor = scale_factor_x;
+ }
+
+ /* always scale down, allow to disable scaling up */
+ if (scale_factor < 1.0 || !no_stretch_hint) {
+ int scale_x;
+ int scale_y;
+
+ scale_x = (int) (pw * scale_factor);
+ scale_y = (int) (ph * scale_factor);
+ return gdk_pixbuf_scale_simple (pixbuf,
+ scale_x,
+ scale_y,
+ GDK_INTERP_BILINEAR);
+ } else {
+ return g_object_ref (pixbuf);
+ }
+}
+
+static void
+set_notification_icon (NdBubble *bubble,
+ GdkPixbuf *pixbuf)
+{
+ GdkPixbuf *scaled;
+
+ scaled = NULL;
+ if (pixbuf != NULL) {
+ scaled = scale_pixbuf (pixbuf,
+ MAX_ICON_SIZE,
+ MAX_ICON_SIZE,
+ TRUE);
+ }
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (bubble->priv->icon), scaled);
+
+ if (scaled != NULL) {
+ int pixbuf_width = gdk_pixbuf_get_width (scaled);
+
+ gtk_widget_show (bubble->priv->icon);
+ gtk_widget_set_size_request (bubble->priv->icon,
+ MAX (BODY_X_OFFSET, pixbuf_width), -1);
+ g_object_unref (scaled);
+ bubble->priv->have_icon = TRUE;
+ } else {
+ gtk_widget_hide (bubble->priv->icon);
+ gtk_widget_set_size_request (bubble->priv->icon,
+ BODY_X_OFFSET,
+ -1);
+ bubble->priv->have_icon = FALSE;
+ }
+
+ update_content_hbox_visibility (bubble);
+}
+
+static void
+on_action_clicked (GtkButton *button,
+ GdkEventButton *event,
+ NdBubble *bubble)
+{
+ const char *key = g_object_get_data (G_OBJECT (button), "_action_key");
+ gboolean resident = nd_notification_get_is_resident (bubble->priv->notification);
+ gboolean transient = nd_notification_get_is_transient (bubble->priv->notification);
+
+ nd_notification_action_invoked (bubble->priv->notification,
+ key);
+
+ if (transient || !resident)
+ gtk_widget_destroy (GTK_WIDGET (bubble));
+}
+
+static void
+add_notification_action (NdBubble *bubble,
+ const char *text,
+ const char *key)
+{
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GdkPixbuf *pixbuf;
+ char *buf;
+
+ if (!gtk_widget_get_visible (bubble->priv->actions_box)) {
+ gtk_widget_show (bubble->priv->actions_box);
+ update_content_hbox_visibility (bubble);
+ }
+
+ button = gtk_button_new ();
+ gtk_widget_show (button);
+ gtk_box_pack_start (GTK_BOX (bubble->priv->actions_box), button, FALSE, FALSE, 0);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_container_set_border_width (GTK_CONTAINER (button), 0);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (button), hbox);
+
+ pixbuf = NULL;
+ /* try to load an icon if requested */
+ if (nd_notification_get_action_icons (bubble->priv->notification)) {
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_for_screen (gtk_widget_get_screen
(GTK_WIDGET (bubble))),
+ key,
+ 20,
+ GTK_ICON_LOOKUP_USE_BUILTIN,
+ NULL);
+ }
+
+ if (pixbuf != NULL) {
+ GtkWidget *image;
+
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ g_object_unref (pixbuf);
+ atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (button)),
+ text);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+ gtk_widget_set_halign (image, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (image, GTK_ALIGN_CENTER);
+ } else {
+ GtkWidget *label;
+
+ label = gtk_label_new (NULL);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ buf = g_strdup_printf ("<small>%s</small>", text);
+ gtk_label_set_markup (GTK_LABEL (label), buf);
+ g_free (buf);
+ }
+
+ g_object_set_data_full (G_OBJECT (button),
+ "_action_key", g_strdup (key), g_free);
+ g_signal_connect (G_OBJECT (button),
+ "button-release-event",
+ G_CALLBACK (on_action_clicked),
+ bubble);
+}
+
+static void
+clear_actions (NdBubble *bubble)
+{
+ gtk_widget_hide (bubble->priv->actions_box);
+ gtk_container_foreach (GTK_CONTAINER (bubble->priv->actions_box),
+ (GtkCallback)gtk_widget_destroy,
+ NULL);
+ bubble->priv->have_actions = FALSE;
+}
+
+static void
+add_actions (NdBubble *bubble)
+{
+ char **actions;
+ int i;
+
+ actions = nd_notification_get_actions (bubble->priv->notification);
+
+ for (i = 0; actions[i] != NULL; i += 2) {
+ char *l = actions[i + 1];
+
+ if (l == NULL) {
+ g_warning ("Label not found for action %s. "
+ "The protocol specifies that a label must "
+ "follow an action in the actions array",
+ actions[i]);
+
+ break;
+ }
+
+ if (strcasecmp (actions[i], "default") != 0) {
+ add_notification_action (bubble,
+ l,
+ actions[i]);
+ bubble->priv->have_actions = TRUE;
+ }
+ }
+}
+
+static void
+update_image (NdBubble *bubble)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = nd_notification_load_image (bubble->priv->notification, IMAGE_SIZE);
+ if (pixbuf != NULL) {
+ set_notification_icon (bubble, pixbuf);
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+}
+
+static void
+update_bubble (NdBubble *bubble)
+{
+ set_notification_text (bubble,
+ nd_notification_get_summary (bubble->priv->notification),
+ nd_notification_get_body (bubble->priv->notification));
+ clear_actions (bubble);
+ add_actions (bubble);
+ update_image (bubble);
+ update_content_hbox_visibility (bubble);
+
+ add_timeout (bubble);
+}
+
+static void
+on_notification_changed (NdNotification *notification,
+ NdBubble *bubble)
+{
+ update_bubble (bubble);
+}
+
+NdBubble *
+nd_bubble_new_for_notification (NdNotification *notification)
+{
+ NdBubble *bubble;
+
+ bubble = g_object_new (ND_TYPE_BUBBLE,
+ "app-paintable", TRUE,
+ "type", GTK_WINDOW_POPUP,
+ "title", "Notification",
+ "resizable", FALSE,
+ "type-hint", GDK_WINDOW_TYPE_HINT_NOTIFICATION,
+ NULL);
+
+ bubble->priv->notification = g_object_ref (notification);
+ g_signal_connect (notification, "changed", G_CALLBACK (on_notification_changed), bubble);
+ update_bubble (bubble);
+
+ return bubble;
+}
diff --git a/gnome-flashback/libnotifications/nd-bubble.h b/gnome-flashback/libnotifications/nd-bubble.h
new file mode 100644
index 0000000..d496698
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-bubble.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ND_BUBBLE_H
+#define __ND_BUBBLE_H
+
+#include <gtk/gtk.h>
+#include "nd-notification.h"
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_BUBBLE (nd_bubble_get_type ())
+#define ND_BUBBLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ND_TYPE_BUBBLE, NdBubble))
+#define ND_BUBBLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ND_TYPE_BUBBLE, NdBubbleClass))
+#define ND_IS_BUBBLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ND_TYPE_BUBBLE))
+#define ND_IS_BUBBLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ND_TYPE_BUBBLE))
+#define ND_BUBBLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ND_TYPE_BUBBLE, NdBubbleClass))
+
+typedef struct NdBubblePrivate NdBubblePrivate;
+
+typedef struct
+{
+ GtkWindow parent;
+ NdBubblePrivate *priv;
+} NdBubble;
+
+typedef struct
+{
+ GtkWindowClass parent_class;
+
+ void (* changed) (NdBubble *bubble);
+} NdBubbleClass;
+
+GType nd_bubble_get_type (void);
+
+NdBubble * nd_bubble_new_for_notification (NdNotification *notification);
+
+NdNotification * nd_bubble_get_notification (NdBubble *bubble);
+
+G_END_DECLS
+
+#endif /* __ND_BUBBLE_H */
diff --git a/gnome-flashback/libnotifications/nd-daemon.c b/gnome-flashback/libnotifications/nd-daemon.c
new file mode 100644
index 0000000..7ff56d5
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-daemon.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2006 Christian Hammond <chipx86 chipx86 com>
+ * Copyright (C) 2005 John (J5) Palmieri <johnp redhat com>
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2015 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "nd-daemon.h"
+#include "gf-fd-notifications-gen.h"
+#include "nd-notification.h"
+#include "nd-queue.h"
+
+#define NOTIFICATIONS_DBUS_NAME "org.freedesktop.Notifications"
+#define NOTIFICATIONS_DBUS_PATH "/org/freedesktop/Notifications"
+
+#define INFO_NAME "Notification Daemon"
+#define INFO_VENDOR "GNOME"
+#define INFO_VERSION PACKAGE_VERSION
+#define INFO_SPEC_VERSION "1.2"
+
+#define MAX_NOTIFICATIONS 20
+
+struct _NdDaemon
+{
+ GObject parent;
+
+ GfFdNotificationsGen *notifications;
+ guint bus_name_id;
+
+ NdQueue *queue;
+};
+
+G_DEFINE_TYPE (NdDaemon, nd_daemon, G_TYPE_OBJECT)
+
+static void
+closed_cb (NdNotification *notification,
+ gint reason,
+ gpointer user_data)
+{
+ NdDaemon *daemon;
+ gint id;
+
+ daemon = ND_DAEMON (user_data);
+ id = nd_notification_get_id (notification);
+
+ gf_fd_notifications_gen_emit_notification_closed (daemon->notifications,
+ id, reason);
+}
+
+static void
+action_invoked_cb (NdNotification *notification,
+ const gchar *action,
+ gpointer user_data)
+{
+ NdDaemon *daemon;
+ gint id;
+
+ daemon = ND_DAEMON (user_data);
+ id = nd_notification_get_id (notification);
+
+ gf_fd_notifications_gen_emit_action_invoked (daemon->notifications,
+ id, action);
+
+ /* Resident notifications does not close when actions are invoked. */
+ if (!nd_notification_get_is_resident (notification))
+ nd_notification_close (notification, ND_NOTIFICATION_CLOSED_USER);
+}
+
+static gboolean
+handle_close_notification_cb (GfFdNotificationsGen *object,
+ GDBusMethodInvocation *invocation,
+ guint id,
+ gpointer user_data)
+{
+ NdDaemon *daemon;
+ const gchar *error_name;
+ const gchar *error_message;
+ NdNotification *notification;
+
+ daemon = ND_DAEMON (user_data);
+ error_name = "org.freedesktop.Notifications.InvalidId";
+ error_message = _("Invalid notification identifier");
+
+ if (id == 0)
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation, error_name,
+ error_message);
+
+ return TRUE;
+ }
+
+ notification = nd_queue_lookup (daemon->queue, id);
+
+ if (notification == NULL)
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation, error_name,
+ error_message);
+
+ return TRUE;
+ }
+
+ nd_notification_close (notification, ND_NOTIFICATION_CLOSED_API);
+ gf_fd_notifications_gen_complete_close_notification (object, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+handle_get_capabilities_cb (GfFdNotificationsGen *object,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ const gchar *const capabilities[] =
+ {
+ "actions", "body", "body-hyperlinks", "body-markup", "icon-static",
+ "sound", "persistence", "action-icons", NULL
+ };
+
+ gf_fd_notifications_gen_complete_get_capabilities (object, invocation,
+ capabilities);
+
+ return TRUE;
+}
+
+static gboolean
+handle_get_server_information_cb (GfFdNotificationsGen *object,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ gf_fd_notifications_gen_complete_get_server_information (object, invocation,
+ INFO_NAME,
+ INFO_VENDOR,
+ INFO_VERSION,
+ INFO_SPEC_VERSION);
+
+ return TRUE;
+}
+
+static gboolean
+handle_notify_cb (GfFdNotificationsGen *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *app_name,
+ guint replaces_id,
+ const gchar *app_icon,
+ const gchar *summary,
+ const gchar *body,
+ const gchar *const *actions,
+ GVariant *hints,
+ gint expire_timeout,
+ gpointer user_data)
+{
+ NdDaemon *daemon;
+ const gchar *error_name;
+ const gchar *error_message;
+ NdNotification *notification;
+ gint new_id;
+
+ daemon = ND_DAEMON (user_data);
+
+ if (nd_queue_length (daemon->queue) > MAX_NOTIFICATIONS)
+ {
+ error_name = "org.freedesktop.Notifications.MaxNotificationsExceeded";
+ error_message = _("Exceeded maximum number of notifications");
+
+ g_dbus_method_invocation_return_dbus_error (invocation, error_name,
+ error_message);
+
+ return TRUE;
+ }
+
+ if (replaces_id > 0)
+ {
+ notification = nd_queue_lookup (daemon->queue, replaces_id);
+
+ if (notification == NULL)
+ replaces_id = 0;
+ else
+ g_object_ref (notification);
+ }
+
+ if (replaces_id == 0)
+ {
+ const gchar *sender;
+
+ sender = g_dbus_method_invocation_get_sender (invocation);
+ notification = nd_notification_new (sender);
+
+ g_signal_connect (notification, "closed",
+ G_CALLBACK (closed_cb), daemon);
+ g_signal_connect (notification, "action-invoked",
+ G_CALLBACK (action_invoked_cb), daemon);
+ }
+
+ nd_notification_update (notification, app_name, app_icon, summary, body,
+ actions, hints, expire_timeout);
+
+ if (replaces_id == 0 || !nd_notification_get_is_queued (notification))
+ {
+ nd_queue_add (daemon->queue, notification);
+ nd_notification_set_is_queued (notification, TRUE);
+ }
+
+ new_id = nd_notification_get_id (notification);
+ gf_fd_notifications_gen_complete_notify (object, invocation, new_id);
+
+ g_object_unref (notification);
+
+ return TRUE;
+}
+
+static void
+bus_acquired_handler_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ NdDaemon *daemon;
+ GDBusInterfaceSkeleton *skeleton;
+ GError *error;
+ gboolean exported;
+
+ daemon = ND_DAEMON (user_data);
+ skeleton = G_DBUS_INTERFACE_SKELETON (daemon->notifications);
+
+ g_signal_connect (daemon->notifications, "handle-close-notification",
+ G_CALLBACK (handle_close_notification_cb), daemon);
+ g_signal_connect (daemon->notifications, "handle-get-capabilities",
+ G_CALLBACK (handle_get_capabilities_cb), daemon);
+ g_signal_connect (daemon->notifications, "handle-get-server-information",
+ G_CALLBACK (handle_get_server_information_cb), daemon);
+ g_signal_connect (daemon->notifications, "handle-notify",
+ G_CALLBACK (handle_notify_cb), daemon);
+
+ error = NULL;
+ exported = g_dbus_interface_skeleton_export (skeleton, connection,
+ NOTIFICATIONS_DBUS_PATH, &error);
+
+ if (!exported)
+ {
+ g_warning ("Failed to export interface: %s", error->message);
+ g_error_free (error);
+
+ gtk_main_quit ();
+ }
+}
+
+static void
+name_lost_handler_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ gtk_main_quit ();
+}
+
+static void
+nd_daemon_constructed (GObject *object)
+{
+ NdDaemon *daemon;
+ GBusNameOwnerFlags flags;
+
+ daemon = ND_DAEMON (object);
+
+ G_OBJECT_CLASS (nd_daemon_parent_class)->constructed (object);
+
+ flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+ G_BUS_NAME_OWNER_FLAGS_REPLACE;
+
+ daemon->bus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ NOTIFICATIONS_DBUS_NAME, flags,
+ bus_acquired_handler_cb, NULL,
+ name_lost_handler_cb, daemon, NULL);
+}
+
+static void
+nd_daemon_dispose (GObject *object)
+{
+ NdDaemon *daemon;
+
+ daemon = ND_DAEMON (object);
+
+ if (daemon->notifications != NULL)
+ {
+ GDBusInterfaceSkeleton *skeleton;
+
+ skeleton = G_DBUS_INTERFACE_SKELETON (daemon->notifications);
+ g_dbus_interface_skeleton_unexport (skeleton);
+
+ g_clear_object (&daemon->notifications);
+ }
+
+ if (daemon->bus_name_id > 0)
+ {
+ g_bus_unown_name (daemon->bus_name_id);
+ daemon->bus_name_id = 0;
+ }
+
+ g_clear_object (&daemon->queue);
+
+ G_OBJECT_CLASS (nd_daemon_parent_class)->dispose (object);
+}
+
+static void
+nd_daemon_class_init (NdDaemonClass *daemon_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (daemon_class);
+
+ object_class->constructed = nd_daemon_constructed;
+ object_class->dispose = nd_daemon_dispose;
+}
+
+static void
+nd_daemon_init (NdDaemon *daemon)
+{
+ daemon->notifications = gf_fd_notifications_gen_skeleton_new ();
+ daemon->queue = nd_queue_new ();
+}
+
+NdDaemon *
+nd_daemon_new (void)
+{
+ return g_object_new (ND_TYPE_DAEMON, NULL);
+}
diff --git a/gnome-flashback/libnotifications/nd-daemon.h b/gnome-flashback/libnotifications/nd-daemon.h
new file mode 100644
index 0000000..5e6ac9d
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-daemon.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ND_DAEMON_H
+#define ND_DAEMON_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_DAEMON nd_daemon_get_type ()
+G_DECLARE_FINAL_TYPE (NdDaemon, nd_daemon, ND, DAEMON, GObject)
+
+NdDaemon *nd_daemon_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/gnome-flashback/libnotifications/nd-notification-box.c
b/gnome-flashback/libnotifications/nd-notification-box.c
new file mode 100644
index 0000000..537ac58
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-notification-box.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <strings.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "nd-notification.h"
+#include "nd-notification-box.h"
+
+#define ND_NOTIFICATION_BOX_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ND_TYPE_NOTIFICATION_BOX,
NdNotificationBoxPrivate))
+
+#define IMAGE_SIZE 48
+#define BODY_X_OFFSET (IMAGE_SIZE + 8)
+#define WIDTH 400
+
+struct NdNotificationBoxPrivate
+{
+ NdNotification *notification;
+
+ GtkWidget *icon;
+ GtkWidget *close_button;
+ GtkWidget *summary_label;
+ GtkWidget *body_label;
+
+ GtkWidget *main_hbox;
+ GtkWidget *content_hbox;
+ GtkWidget *actions_box;
+ GtkWidget *last_sep;
+};
+
+static void nd_notification_box_finalize (GObject *object);
+
+G_DEFINE_TYPE (NdNotificationBox, nd_notification_box, GTK_TYPE_EVENT_BOX)
+
+NdNotification *
+nd_notification_box_get_notification (NdNotificationBox *notification_box)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION_BOX (notification_box), NULL);
+
+ return notification_box->priv->notification;
+}
+
+static gboolean
+nd_notification_box_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ NdNotificationBox *notification_box = ND_NOTIFICATION_BOX (widget);
+
+ nd_notification_action_invoked (notification_box->priv->notification, "default");
+
+ return FALSE;
+}
+
+static void
+nd_notification_box_class_init (NdNotificationBoxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = nd_notification_box_finalize;
+ widget_class->button_release_event = nd_notification_box_button_release_event;
+
+ g_type_class_add_private (klass, sizeof (NdNotificationBoxPrivate));
+}
+
+static void
+on_close_button_clicked (GtkButton *button,
+ NdNotificationBox *notification_box)
+{
+ nd_notification_close (notification_box->priv->notification, ND_NOTIFICATION_CLOSED_USER);
+}
+
+static void
+on_action_clicked (GtkButton *button,
+ GdkEventButton *event,
+ NdNotificationBox *notification_box)
+{
+ const char *key = g_object_get_data (G_OBJECT (button), "_action_key");
+
+ nd_notification_action_invoked (notification_box->priv->notification,
+ key);
+}
+
+static GtkWidget *
+create_notification_action (NdNotificationBox *box,
+ NdNotification *notification,
+ const char *text,
+ const char *key)
+{
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GdkPixbuf *pixbuf;
+ char *buf;
+
+ button = gtk_button_new ();
+ gtk_widget_show (button);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_container_set_border_width (GTK_CONTAINER (button), 0);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (button), hbox);
+
+ pixbuf = NULL;
+ /* try to load an icon if requested */
+ if (nd_notification_get_action_icons (box->priv->notification)) {
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_for_screen (gtk_widget_get_screen
(GTK_WIDGET (box))),
+ key,
+ 20,
+ GTK_ICON_LOOKUP_USE_BUILTIN,
+ NULL);
+ }
+
+ if (pixbuf != NULL) {
+ GtkWidget *image;
+
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ g_object_unref (pixbuf);
+ atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (button)),
+ text);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+ gtk_widget_set_halign (image, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (image, GTK_ALIGN_CENTER);
+ } else {
+ GtkWidget *label;
+
+ label = gtk_label_new (NULL);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ buf = g_strdup_printf ("<small>%s</small>", text);
+ gtk_label_set_markup (GTK_LABEL (label), buf);
+ g_free (buf);
+ }
+
+ g_object_set_data_full (G_OBJECT (button),
+ "_action_key", g_strdup (key), g_free);
+ g_signal_connect (G_OBJECT (button),
+ "button-release-event",
+ G_CALLBACK (on_action_clicked),
+ box);
+ return button;
+}
+
+static void
+remove_item (GtkWidget *item,
+ gpointer data)
+{
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (item)),
+ item);
+}
+
+static void
+update_notification_box (NdNotificationBox *notification_box)
+{
+ gboolean have_icon;
+ gboolean have_body;
+ const char *body;
+ gboolean have_actions;
+ GdkPixbuf *pixbuf;
+ char **actions;
+ int i;
+ char *str;
+ char *quoted;
+ GtkRequisition req;
+ int summary_width;
+
+ /* Add content */
+
+ have_icon = FALSE;
+ have_body = FALSE;
+ have_actions = FALSE;
+
+ /* image */
+ pixbuf = nd_notification_load_image (notification_box->priv->notification, IMAGE_SIZE);
+ if (pixbuf != NULL) {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (notification_box->priv->icon), pixbuf);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ have_icon = TRUE;
+ }
+
+ /* summary */
+ quoted = g_markup_escape_text (nd_notification_get_summary (notification_box->priv->notification),
-1);
+ str = g_strdup_printf ("<b><big>%s</big></b>", quoted);
+ g_free (quoted);
+
+ gtk_label_set_markup (GTK_LABEL (notification_box->priv->summary_label), str);
+ g_free (str);
+
+ gtk_widget_get_preferred_size (notification_box->priv->close_button, NULL, &req);
+ /* -1: main_vbox border width
+ -10: vbox border width
+ -6: spacing for hbox */
+ summary_width = WIDTH - (1*2) - (10*2) - BODY_X_OFFSET - req.width - (6*2);
+
+ gtk_widget_set_size_request (notification_box->priv->summary_label,
+ summary_width,
+ -1);
+
+ /* body */
+ body = nd_notification_get_body (notification_box->priv->notification);
+ if (pango_parse_markup (body, -1, 0, NULL, NULL, NULL, NULL))
+ gtk_label_set_markup (GTK_LABEL (notification_box->priv->body_label), body);
+ else {
+ gchar *tmp;
+
+ tmp = g_markup_escape_text (body, -1);
+ gtk_label_set_text (GTK_LABEL (notification_box->priv->body_label), body);
+ g_free (tmp);
+ }
+
+ if (body != NULL && *body != '\0') {
+ gtk_widget_set_size_request (notification_box->priv->body_label,
+ summary_width,
+ -1);
+ have_body = TRUE;
+ }
+
+ /* actions */
+ gtk_container_foreach (GTK_CONTAINER (notification_box->priv->actions_box), remove_item, NULL);
+ actions = nd_notification_get_actions (notification_box->priv->notification);
+ for (i = 0; actions[i] != NULL; i += 2) {
+ char *l = actions[i + 1];
+
+ if (l == NULL) {
+ g_warning ("Label not found for action %s. "
+ "The protocol specifies that a label must "
+ "follow an action in the actions array",
+ actions[i]);
+
+ break;
+ }
+
+ if (strcasecmp (actions[i], "default") != 0) {
+ GtkWidget *button;
+
+ button = create_notification_action (notification_box,
+ notification_box->priv->notification,
+ l,
+ actions[i]);
+ gtk_box_pack_start (GTK_BOX (notification_box->priv->actions_box), button, FALSE,
FALSE, 0);
+
+ have_actions = TRUE;
+ }
+ }
+
+ if (have_icon || have_body || have_actions) {
+ gtk_widget_show (notification_box->priv->content_hbox);
+ } else {
+ gtk_widget_hide (notification_box->priv->content_hbox);
+ }
+}
+
+static void
+nd_notification_box_init (NdNotificationBox *notification_box)
+{
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ AtkObject *atkobj;
+
+ notification_box->priv = ND_NOTIFICATION_BOX_GET_PRIVATE (notification_box);
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_container_add (GTK_CONTAINER (notification_box), box);
+ gtk_widget_show (box);
+
+ /* Add icon */
+
+ notification_box->priv->icon = gtk_image_new ();
+ gtk_widget_set_valign (notification_box->priv->icon, GTK_ALIGN_START);
+ gtk_widget_set_margin_top (notification_box->priv->icon, 5);
+ gtk_widget_set_size_request (notification_box->priv->icon,
+ BODY_X_OFFSET, -1);
+ gtk_widget_show (notification_box->priv->icon);
+
+ gtk_box_pack_start (GTK_BOX (box), notification_box->priv->icon,
+ FALSE, FALSE, 0);
+
+ /* Add vbox */
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (box), vbox, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
+
+ /* Add the close button */
+
+ notification_box->priv->close_button = gtk_button_new ();
+ gtk_widget_set_valign (notification_box->priv->close_button,
+ GTK_ALIGN_START);
+ gtk_widget_show (notification_box->priv->close_button);
+
+ gtk_box_pack_start (GTK_BOX (box), notification_box->priv->close_button,
+ FALSE, FALSE, 0);
+
+ gtk_button_set_relief (GTK_BUTTON (notification_box->priv->close_button), GTK_RELIEF_NONE);
+ gtk_container_set_border_width (GTK_CONTAINER (notification_box->priv->close_button), 0);
+ g_signal_connect (G_OBJECT (notification_box->priv->close_button),
+ "clicked",
+ G_CALLBACK (on_close_button_clicked),
+ notification_box);
+
+ atkobj = gtk_widget_get_accessible (notification_box->priv->close_button);
+ atk_action_set_description (ATK_ACTION (atkobj), 0,
+ _("Closes the notification."));
+ atk_object_set_name (atkobj, "");
+ atk_object_set_description (atkobj, _("Closes the notification."));
+
+ image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+ gtk_container_add (GTK_CONTAINER (notification_box->priv->close_button), image);
+
+ /* center vbox */
+ notification_box->priv->summary_label = gtk_label_new (NULL);
+ gtk_widget_show (notification_box->priv->summary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), notification_box->priv->summary_label, TRUE, TRUE, 0);
+ gtk_label_set_xalign (GTK_LABEL (notification_box->priv->summary_label), 0.0);
+ gtk_label_set_yalign (GTK_LABEL (notification_box->priv->summary_label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (notification_box->priv->summary_label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (notification_box->priv->summary_label),
PANGO_WRAP_WORD_CHAR);
+
+ atkobj = gtk_widget_get_accessible (notification_box->priv->summary_label);
+ atk_object_set_description (atkobj, _("Notification summary text."));
+
+ notification_box->priv->content_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_show (notification_box->priv->content_hbox);
+ gtk_box_pack_start (GTK_BOX (vbox), notification_box->priv->content_hbox, FALSE, FALSE, 0);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (notification_box->priv->content_hbox), vbox, TRUE, TRUE, 0);
+
+ notification_box->priv->body_label = gtk_label_new (NULL);
+ gtk_widget_show (notification_box->priv->body_label);
+ gtk_box_pack_start (GTK_BOX (vbox), notification_box->priv->body_label, TRUE, TRUE, 0);
+ gtk_label_set_xalign (GTK_LABEL (notification_box->priv->body_label), 0.0);
+ gtk_label_set_yalign (GTK_LABEL (notification_box->priv->body_label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (notification_box->priv->body_label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (notification_box->priv->body_label), PANGO_WRAP_WORD_CHAR);
+
+ atkobj = gtk_widget_get_accessible (notification_box->priv->body_label);
+ atk_object_set_description (atkobj, _("Notification body text."));
+
+ notification_box->priv->actions_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_set_halign (notification_box->priv->actions_box, GTK_ALIGN_END);
+ gtk_widget_show (notification_box->priv->actions_box);
+
+ gtk_box_pack_start (GTK_BOX (vbox), notification_box->priv->actions_box, FALSE, TRUE, 0);
+}
+
+static void
+on_notification_changed (NdNotification *notification,
+ NdNotificationBox *notification_box)
+{
+ update_notification_box (notification_box);
+}
+
+static void
+nd_notification_box_finalize (GObject *object)
+{
+ NdNotificationBox *notification_box;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (ND_IS_NOTIFICATION_BOX (object));
+
+ notification_box = ND_NOTIFICATION_BOX (object);
+
+ g_return_if_fail (notification_box->priv != NULL);
+
+ g_signal_handlers_disconnect_by_func (notification_box->priv->notification, G_CALLBACK
(on_notification_changed), notification_box);
+
+ g_object_unref (notification_box->priv->notification);
+
+ G_OBJECT_CLASS (nd_notification_box_parent_class)->finalize (object);
+}
+
+NdNotificationBox *
+nd_notification_box_new_for_notification (NdNotification *notification)
+{
+ NdNotificationBox *notification_box;
+
+ notification_box = g_object_new (ND_TYPE_NOTIFICATION_BOX,
+ "visible-window", FALSE,
+ NULL);
+ notification_box->priv->notification = g_object_ref (notification);
+ g_signal_connect (notification, "changed", G_CALLBACK (on_notification_changed), notification_box);
+ update_notification_box (notification_box);
+
+ return notification_box;
+}
diff --git a/gnome-flashback/libnotifications/nd-notification-box.h
b/gnome-flashback/libnotifications/nd-notification-box.h
new file mode 100644
index 0000000..dffd7a9
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-notification-box.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ND_NOTIFICATION_BOX_H
+#define __ND_NOTIFICATION_BOX_H
+
+#include <gtk/gtk.h>
+#include "nd-notification.h"
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_NOTIFICATION_BOX (nd_notification_box_get_type ())
+#define ND_NOTIFICATION_BOX(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ND_TYPE_NOTIFICATION_BOX,
NdNotificationBox))
+#define ND_NOTIFICATION_BOX_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ND_TYPE_NOTIFICATION_BOX,
NdNotificationBoxClass))
+#define ND_IS_NOTIFICATION_BOX(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ND_TYPE_NOTIFICATION_BOX))
+#define ND_IS_NOTIFICATION_BOX_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ND_TYPE_NOTIFICATION_BOX))
+#define ND_NOTIFICATION_BOX_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ND_TYPE_NOTIFICATION_BOX,
NdNotificationBoxClass))
+
+typedef struct NdNotificationBoxPrivate NdNotificationBoxPrivate;
+
+typedef struct
+{
+ GtkEventBox parent;
+ NdNotificationBoxPrivate *priv;
+} NdNotificationBox;
+
+typedef struct
+{
+ GtkEventBoxClass parent_class;
+} NdNotificationBoxClass;
+
+GType nd_notification_box_get_type (void);
+
+NdNotificationBox * nd_notification_box_new_for_notification (NdNotification *notification);
+
+NdNotification * nd_notification_box_get_notification (NdNotificationBox *notification_box);
+
+G_END_DECLS
+
+#endif /* __ND_NOTIFICATION_BOX_H */
diff --git a/gnome-flashback/libnotifications/nd-notification.c
b/gnome-flashback/libnotifications/nd-notification.c
new file mode 100644
index 0000000..36f3480
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-notification.c
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <strings.h>
+#include <gtk/gtk.h>
+
+#include "nd-notification.h"
+
+#define ND_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ND_TYPE_NOTIFICATION,
NdNotificationClass))
+#define ND_IS_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ND_TYPE_NOTIFICATION))
+#define ND_NOTIFICATION_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), ND_TYPE_NOTIFICATION,
NdNotificationClass))
+
+enum {
+ CHANGED,
+ CLOSED,
+ ACTION_INVOKED,
+ LAST_SIGNAL
+};
+
+struct _NdNotification {
+ GObject parent;
+
+ gboolean is_queued;
+ gboolean is_closed;
+
+ GTimeVal update_time;
+
+ char *sender;
+ guint32 id;
+ char *app_name;
+ char *icon;
+ char *summary;
+ char *body;
+ char **actions;
+ GHashTable *hints;
+ int timeout;
+};
+
+static void nd_notification_finalize (GObject *object);
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (NdNotification, nd_notification, G_TYPE_OBJECT)
+
+static guint32 notification_serial = 1;
+
+static guint32
+get_next_notification_serial (void)
+{
+ guint32 serial;
+
+ serial = notification_serial++;
+
+ if ((gint32)notification_serial < 0) {
+ notification_serial = 1;
+ }
+
+ return serial;
+}
+
+static void
+nd_notification_class_init (NdNotificationClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = nd_notification_finalize;
+
+ signals [CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals [CLOSED] =
+ g_signal_new ("closed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1, G_TYPE_INT);
+ signals [ACTION_INVOKED] =
+ g_signal_new ("action-invoked",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+nd_notification_init (NdNotification *notification)
+{
+ notification->id = get_next_notification_serial ();
+
+ notification->app_name = NULL;
+ notification->icon = NULL;
+ notification->summary = NULL;
+ notification->body = NULL;
+ notification->actions = NULL;
+ notification->hints = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) g_variant_unref);
+}
+
+static void
+nd_notification_finalize (GObject *object)
+{
+ NdNotification *notification;
+
+ notification = ND_NOTIFICATION (object);
+
+ g_free (notification->sender);
+ g_free (notification->app_name);
+ g_free (notification->icon);
+ g_free (notification->summary);
+ g_free (notification->body);
+ g_strfreev (notification->actions);
+
+ if (notification->hints != NULL) {
+ g_hash_table_destroy (notification->hints);
+ }
+
+ if (G_OBJECT_CLASS (nd_notification_parent_class)->finalize)
+ (*G_OBJECT_CLASS (nd_notification_parent_class)->finalize) (object);
+}
+
+gboolean
+nd_notification_update (NdNotification *notification,
+ const gchar *app_name,
+ const gchar *icon,
+ const gchar *summary,
+ const gchar *body,
+ const gchar *const *actions,
+ GVariant *hints,
+ gint timeout)
+{
+ GVariant *item;
+ GVariantIter iter;
+
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), FALSE);
+
+ g_free (notification->app_name);
+ notification->app_name = g_strdup (app_name);
+
+ g_free (notification->icon);
+ notification->icon = g_strdup (icon);
+
+ g_free (notification->summary);
+ notification->summary = g_strdup (summary);
+
+ g_free (notification->body);
+ notification->body = g_strdup (body);
+
+ g_strfreev (notification->actions);
+ notification->actions = g_strdupv ((char **)actions);
+
+ g_hash_table_remove_all (notification->hints);
+
+ g_variant_iter_init (&iter, hints);
+ while ((item = g_variant_iter_next_value (&iter))) {
+ const char *key;
+ GVariant *value;
+
+ g_variant_get (item,
+ "{sv}",
+ &key,
+ &value);
+
+ g_hash_table_insert (notification->hints,
+ g_strdup (key),
+ value); /* steals value */
+ }
+
+ notification->timeout = timeout;
+
+ g_signal_emit (notification, signals[CHANGED], 0);
+
+ g_get_current_time (¬ification->update_time);
+
+ return TRUE;
+}
+
+void
+nd_notification_get_update_time (NdNotification *notification,
+ GTimeVal *tvp)
+{
+ g_return_if_fail (ND_IS_NOTIFICATION (notification));
+
+ if (tvp == NULL) {
+ return;
+ }
+
+ tvp->tv_usec = notification->update_time.tv_usec;
+ tvp->tv_sec = notification->update_time.tv_sec;
+}
+
+void
+nd_notification_set_is_queued (NdNotification *notification,
+ gboolean is_queued)
+{
+ g_return_if_fail (ND_IS_NOTIFICATION (notification));
+
+ notification->is_queued = is_queued;
+}
+
+gboolean
+nd_notification_get_is_queued (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), FALSE);
+
+ return notification->is_queued;
+}
+
+gboolean
+nd_notification_get_is_closed (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), FALSE);
+
+ return notification->is_closed;
+}
+
+static gboolean
+hint_to_boolean (NdNotification *notification,
+ const gchar *hint_name)
+{
+ GVariant *value;
+
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), FALSE);
+
+ value = g_hash_table_lookup (notification->hints, hint_name);
+ if (value == NULL)
+ return FALSE;
+
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32)) {
+ return (g_variant_get_int32 (value) != 0);
+ } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_DOUBLE)) {
+ return (g_variant_get_double (value) != 0);
+ } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
+ return TRUE;
+ } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_BYTE)) {
+ return (g_variant_get_byte (value) != 0);
+ } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) {
+ return g_variant_get_boolean (value);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return FALSE;
+}
+
+gboolean
+nd_notification_get_is_transient (NdNotification *notification)
+{
+ return hint_to_boolean (notification, "transient");
+}
+
+gboolean
+nd_notification_get_is_resident (NdNotification *notification)
+{
+ return hint_to_boolean (notification, "resident");
+}
+
+gboolean
+nd_notification_get_action_icons (NdNotification *notification)
+{
+ return hint_to_boolean (notification, "action-icons");
+}
+
+guint32
+nd_notification_get_id (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), -1);
+
+ return notification->id;
+}
+
+GHashTable *
+nd_notification_get_hints (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), NULL);
+
+ return notification->hints;
+}
+
+char **
+nd_notification_get_actions (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), NULL);
+
+ return notification->actions;
+}
+
+const char *
+nd_notification_get_sender (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), NULL);
+
+ return notification->sender;
+}
+
+const char *
+nd_notification_get_summary (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), NULL);
+
+ return notification->summary;
+}
+
+const char *
+nd_notification_get_body (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), NULL);
+
+ return notification->body;
+}
+
+const char *
+nd_notification_get_icon (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), NULL);
+
+ return notification->icon;
+}
+
+int
+nd_notification_get_timeout (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), -1);
+
+ return notification->timeout;
+}
+
+static GdkPixbuf *
+scale_pixbuf (GdkPixbuf *pixbuf,
+ int max_width,
+ int max_height,
+ gboolean no_stretch_hint)
+{
+ int pw;
+ int ph;
+ float scale_factor_x = 1.0;
+ float scale_factor_y = 1.0;
+ float scale_factor = 1.0;
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ /* Determine which dimension requires the smallest scale. */
+ scale_factor_x = (float) max_width / (float) pw;
+ scale_factor_y = (float) max_height / (float) ph;
+
+ if (scale_factor_x > scale_factor_y) {
+ scale_factor = scale_factor_y;
+ } else {
+ scale_factor = scale_factor_x;
+ }
+
+ /* always scale down, allow to disable scaling up */
+ if (scale_factor < 1.0 || !no_stretch_hint) {
+ int scale_x;
+ int scale_y;
+
+ scale_x = (int) (pw * scale_factor);
+ scale_y = (int) (ph * scale_factor);
+ return gdk_pixbuf_scale_simple (pixbuf,
+ scale_x,
+ scale_y,
+ GDK_INTERP_BILINEAR);
+ } else {
+ return g_object_ref (pixbuf);
+ }
+}
+
+static GdkPixbuf *
+_notify_daemon_pixbuf_from_data_hint (GVariant *icon_data,
+ int size)
+{
+ gboolean has_alpha;
+ int bits_per_sample;
+ int width;
+ int height;
+ int rowstride;
+ int n_channels;
+ GVariant *data_variant;
+ gsize expected_len;
+ guchar *data;
+ GdkPixbuf *pixbuf;
+
+ g_variant_get (icon_data,
+ "(iiibii ay)",
+ &width,
+ &height,
+ &rowstride,
+ &has_alpha,
+ &bits_per_sample,
+ &n_channels,
+ &data_variant);
+
+ expected_len = (height - 1) * rowstride + width
+ * ((n_channels * bits_per_sample + 7) / 8);
+
+ if (expected_len != g_variant_get_size (data_variant)) {
+ g_warning ("Expected image data to be of length %" G_GSIZE_FORMAT
+ " but got a " "length of %" G_GSIZE_FORMAT,
+ expected_len,
+ g_variant_get_size (data_variant));
+ return NULL;
+ }
+
+ data = (guchar *) g_memdup (g_variant_get_data (data_variant),
+ g_variant_get_size (data_variant));
+
+ pixbuf = gdk_pixbuf_new_from_data (data,
+ GDK_COLORSPACE_RGB,
+ has_alpha,
+ bits_per_sample,
+ width,
+ height,
+ rowstride,
+ (GdkPixbufDestroyNotify) g_free,
+ NULL);
+ if (pixbuf != NULL && size > 0) {
+ GdkPixbuf *scaled;
+ scaled = scale_pixbuf (pixbuf, size, size, TRUE);
+ g_object_unref (pixbuf);
+ pixbuf = scaled;
+ }
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+_notify_daemon_pixbuf_from_path (const char *path,
+ int size)
+{
+ GFile *file;
+ GdkPixbuf *pixbuf = NULL;
+
+ file = g_file_new_for_commandline_arg (path);
+ if (g_file_is_native (file)) {
+ char *realpath;
+
+ realpath = g_file_get_path (file);
+ pixbuf = gdk_pixbuf_new_from_file_at_size (realpath, size, size, NULL);
+ g_free (realpath);
+ }
+ g_object_unref (file);
+
+ if (pixbuf == NULL) {
+ /* Load icon theme icon */
+ GtkIconTheme *theme;
+ GtkIconInfo *icon_info;
+
+ theme = gtk_icon_theme_get_default ();
+ icon_info = gtk_icon_theme_lookup_icon (theme,
+ path,
+ size,
+ GTK_ICON_LOOKUP_USE_BUILTIN);
+
+ if (icon_info != NULL) {
+ gint icon_size;
+
+ icon_size = MIN (size,
+ gtk_icon_info_get_base_size (icon_info));
+
+ if (icon_size == 0)
+ icon_size = size;
+
+ pixbuf = gtk_icon_theme_load_icon (theme,
+ path,
+ icon_size,
+ GTK_ICON_LOOKUP_USE_BUILTIN,
+ NULL);
+
+ g_object_unref (icon_info);
+ }
+ }
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+nd_notification_load_image (NdNotification *notification,
+ int size)
+{
+ GVariant *data;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = NULL;
+
+ if ((data = (GVariant *) g_hash_table_lookup (notification->hints, "image-data"))
+ || (data = (GVariant *) g_hash_table_lookup (notification->hints, "image_data"))) {
+ pixbuf = _notify_daemon_pixbuf_from_data_hint (data, size);
+ } else if ((data = (GVariant *) g_hash_table_lookup (notification->hints, "image-path"))
+ || (data = (GVariant *) g_hash_table_lookup (notification->hints, "image_path"))) {
+ if (g_variant_is_of_type (data, G_VARIANT_TYPE_STRING)) {
+ const char *path;
+ path = g_variant_get_string (data, NULL);
+ pixbuf = _notify_daemon_pixbuf_from_path (path, size);
+ } else {
+ g_warning ("Expected image_path hint to be of type string");
+ }
+ } else if (*notification->icon != '\0') {
+ pixbuf = _notify_daemon_pixbuf_from_path (notification->icon, size);
+ } else if ((data = (GVariant *) g_hash_table_lookup (notification->hints, "icon_data"))) {
+ g_warning("\"icon_data\" hint is deprecated, please use \"image_data\" instead");
+ pixbuf = _notify_daemon_pixbuf_from_data_hint (data, size);
+ }
+
+ return pixbuf;
+}
+
+void
+nd_notification_close (NdNotification *notification,
+ NdNotificationClosedReason reason)
+{
+ g_return_if_fail (ND_IS_NOTIFICATION (notification));
+
+ g_object_ref (notification);
+ g_signal_emit (notification, signals[CLOSED], 0, reason);
+ g_object_unref (notification);
+
+ notification->is_closed = TRUE;
+}
+
+void
+nd_notification_action_invoked (NdNotification *notification,
+ const char *action)
+{
+ g_return_if_fail (ND_IS_NOTIFICATION (notification));
+
+ g_object_ref (notification);
+ g_signal_emit (notification, signals[ACTION_INVOKED], 0, action);
+ g_object_unref (notification);
+}
+
+NdNotification *
+nd_notification_new (const char *sender)
+{
+ NdNotification *notification;
+
+ notification = (NdNotification *) g_object_new (ND_TYPE_NOTIFICATION, NULL);
+ notification->sender = g_strdup (sender);
+
+ return notification;
+}
diff --git a/gnome-flashback/libnotifications/nd-notification.h
b/gnome-flashback/libnotifications/nd-notification.h
new file mode 100644
index 0000000..e320f24
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-notification.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ND_NOTIFICATION__
+#define __ND_NOTIFICATION__ 1
+
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_NOTIFICATION (nd_notification_get_type ())
+#define ND_NOTIFICATION(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), ND_TYPE_NOTIFICATION, NdNotification))
+#define ND_IS_NOTIFICATION(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), ND_TYPE_NOTIFICATION))
+
+typedef struct _NdNotification NdNotification;
+
+typedef struct _NdNotificationClass
+{
+ GObjectClass parent_class;
+} NdNotificationClass;
+
+typedef enum
+{
+ ND_NOTIFICATION_CLOSED_EXPIRED = 1,
+ ND_NOTIFICATION_CLOSED_USER = 2,
+ ND_NOTIFICATION_CLOSED_API = 3,
+ ND_NOTIFICATION_CLOSED_RESERVED = 4
+} NdNotificationClosedReason;
+
+GType nd_notification_get_type (void) G_GNUC_CONST;
+
+NdNotification * nd_notification_new (const char *sender);
+gboolean nd_notification_update (NdNotification *notification,
+ const gchar *app_name,
+ const gchar *icon,
+ const gchar *summary,
+ const gchar *body,
+ const gchar *const *actions,
+ GVariant *hints,
+ gint timeout);
+
+void nd_notification_set_is_queued (NdNotification *notification,
+ gboolean is_queued);
+gboolean nd_notification_get_is_queued (NdNotification *notification);
+
+gboolean nd_notification_get_is_closed (NdNotification *notification);
+void nd_notification_get_update_time (NdNotification *notification,
+ GTimeVal *timeval);
+
+guint nd_notification_get_id (NdNotification *notification);
+int nd_notification_get_timeout (NdNotification *notification);
+const char * nd_notification_get_sender (NdNotification *notification);
+const char * nd_notification_get_app_name (NdNotification *notification);
+const char * nd_notification_get_icon (NdNotification *notification);
+const char * nd_notification_get_summary (NdNotification *notification);
+const char * nd_notification_get_body (NdNotification *notification);
+char ** nd_notification_get_actions (NdNotification *notification);
+GHashTable * nd_notification_get_hints (NdNotification *notification);
+
+GdkPixbuf * nd_notification_load_image (NdNotification *notification,
+ int size);
+gboolean nd_notification_get_is_resident (NdNotification *notification);
+gboolean nd_notification_get_is_transient (NdNotification *notification);
+gboolean nd_notification_get_action_icons (NdNotification *notification);
+
+void nd_notification_close (NdNotification *notification,
+ NdNotificationClosedReason reason);
+void nd_notification_action_invoked (NdNotification *notification,
+ const char *action);
+void nd_notification_url_clicked (NdNotification *notification,
+ const char *url);
+
+G_END_DECLS
+
+#endif
diff --git a/gnome-flashback/libnotifications/nd-queue.c b/gnome-flashback/libnotifications/nd-queue.c
new file mode 100644
index 0000000..8e3e3ee
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-queue.c
@@ -0,0 +1,1020 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <X11/Xproto.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+
+#include "nd-queue.h"
+
+#include "nd-notification.h"
+#include "nd-notification-box.h"
+#include "nd-stack.h"
+
+#define ND_QUEUE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ND_TYPE_QUEUE, NdQueuePrivate))
+
+#define WIDTH 400
+
+typedef struct
+{
+ NdStack **stacks;
+ int n_stacks;
+ Atom workarea_atom;
+} NotifyScreen;
+
+struct NdQueuePrivate
+{
+ GHashTable *notifications;
+ GHashTable *bubbles;
+ GQueue *queue;
+
+ GtkStatusIcon *status_icon;
+ GIcon *numerable_icon;
+ GtkWidget *dock;
+ GtkWidget *dock_scrolled_window;
+
+ NotifyScreen *screen;
+
+ guint update_id;
+};
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void nd_queue_finalize (GObject *object);
+static void queue_update (NdQueue *queue);
+static void on_notification_close (NdNotification *notification,
+ int reason,
+ NdQueue *queue);
+
+static gpointer queue_object = NULL;
+
+G_DEFINE_TYPE (NdQueue, nd_queue, G_TYPE_OBJECT)
+
+static void
+create_stack_for_monitor (NdQueue *queue,
+ GdkScreen *screen,
+ int monitor_num)
+{
+ NotifyScreen *nscreen;
+
+ nscreen = queue->priv->screen;
+
+ nscreen->stacks[monitor_num] = nd_stack_new (screen,
+ monitor_num);
+}
+
+static void
+on_screen_monitors_changed (GdkScreen *screen,
+ NdQueue *queue)
+{
+ NotifyScreen *nscreen;
+ int n_monitors;
+ int i;
+
+ nscreen = queue->priv->screen;
+
+ n_monitors = gdk_screen_get_n_monitors (screen);
+
+ if (n_monitors > nscreen->n_stacks) {
+ /* grow */
+ nscreen->stacks = g_renew (NdStack *,
+ nscreen->stacks,
+ n_monitors);
+
+ /* add more stacks */
+ for (i = nscreen->n_stacks; i < n_monitors; i++) {
+ create_stack_for_monitor (queue, screen, i);
+ }
+
+ nscreen->n_stacks = n_monitors;
+ } else if (n_monitors < nscreen->n_stacks) {
+ NdStack *last_stack;
+
+ last_stack = nscreen->stacks[n_monitors - 1];
+
+ /* transfer items before removing stacks */
+ for (i = n_monitors; i < nscreen->n_stacks; i++) {
+ NdStack *stack;
+ GList *bubbles;
+ GList *l;
+
+ stack = nscreen->stacks[i];
+ bubbles = g_list_copy (nd_stack_get_bubbles (stack));
+ for (l = bubbles; l != NULL; l = l->next) {
+ /* skip removing the bubble from the
+ old stack since it will try to
+ unrealize the window. And the
+ stack is going away anyhow. */
+ nd_stack_add_bubble (last_stack, l->data, TRUE);
+ }
+ g_list_free (bubbles);
+ g_object_unref (stack);
+ nscreen->stacks[i] = NULL;
+ }
+
+ /* remove the extra stacks */
+ nscreen->stacks = g_renew (NdStack *,
+ nscreen->stacks,
+ n_monitors);
+ nscreen->n_stacks = n_monitors;
+ }
+}
+
+static void
+create_stacks_for_screen (NdQueue *queue,
+ GdkScreen *screen)
+{
+ NotifyScreen *nscreen;
+ int i;
+
+ nscreen = queue->priv->screen;
+
+ nscreen->n_stacks = gdk_screen_get_n_monitors (screen);
+
+ nscreen->stacks = g_renew (NdStack *,
+ nscreen->stacks,
+ nscreen->n_stacks);
+
+ for (i = 0; i < nscreen->n_stacks; i++) {
+ create_stack_for_monitor (queue, screen, i);
+ }
+}
+
+static GdkFilterReturn
+screen_xevent_filter (GdkXEvent *xevent,
+ GdkEvent *event,
+ NotifyScreen *nscreen)
+{
+ XEvent *xev;
+
+ xev = (XEvent *) xevent;
+
+ if (xev->type == PropertyNotify &&
+ xev->xproperty.atom == nscreen->workarea_atom) {
+ int i;
+
+ for (i = 0; i < nscreen->n_stacks; i++) {
+ nd_stack_queue_update_position (nscreen->stacks[i]);
+ }
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static void
+create_screen (NdQueue *queue)
+{
+ GdkDisplay *display;
+ GdkScreen *screen;
+ GdkWindow *gdkwindow;
+
+ g_assert (queue->priv->screen == NULL);
+
+ display = gdk_display_get_default ();
+ screen = gdk_display_get_default_screen (display);
+
+ g_signal_connect (screen,
+ "monitors-changed",
+ G_CALLBACK (on_screen_monitors_changed),
+ queue);
+
+ queue->priv->screen = g_new0 (NotifyScreen, 1);
+ queue->priv->screen->workarea_atom = XInternAtom (GDK_DISPLAY_XDISPLAY (display), "_NET_WORKAREA",
True);
+
+ gdkwindow = gdk_screen_get_root_window (screen);
+ gdk_window_add_filter (gdkwindow, (GdkFilterFunc) screen_xevent_filter, queue->priv->screen);
+ gdk_window_set_events (gdkwindow, gdk_window_get_events (gdkwindow) | GDK_PROPERTY_CHANGE_MASK);
+
+ create_stacks_for_screen (queue, screen);
+}
+
+static void
+nd_queue_class_init (NdQueueClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nd_queue_finalize;
+
+ signals [CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (NdQueueClass, changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (klass, sizeof (NdQueuePrivate));
+}
+
+static void
+ungrab (NdQueue *queue,
+ guint time)
+{
+ GdkDisplay *display;
+ GdkSeat *seat;
+
+ display = gtk_widget_get_display (queue->priv->dock);
+ seat = gdk_display_get_default_seat (display);
+
+ gdk_seat_ungrab (seat);
+
+ gtk_grab_remove (queue->priv->dock);
+
+ /* hide again */
+ gtk_widget_hide (queue->priv->dock);
+}
+
+static void
+popdown_dock (NdQueue *queue)
+{
+ ungrab (queue, GDK_CURRENT_TIME);
+ queue_update (queue);
+}
+
+static void
+release_grab (NdQueue *queue,
+ GdkEventButton *event)
+{
+ ungrab (queue, event->time);
+}
+
+/* This is called when the grab is broken for
+ * either the dock, or the scale itself */
+static void
+grab_notify (NdQueue *queue,
+ gboolean was_grabbed)
+{
+ GtkWidget *current;
+
+ if (was_grabbed) {
+ return;
+ }
+
+ if (!gtk_widget_has_grab (queue->priv->dock)) {
+ return;
+ }
+
+ current = gtk_grab_get_current ();
+ if (current == queue->priv->dock
+ || gtk_widget_is_ancestor (current, queue->priv->dock)) {
+ return;
+ }
+
+ popdown_dock (queue);
+}
+
+static void
+on_dock_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed,
+ NdQueue *queue)
+{
+ grab_notify (queue, was_grabbed);
+}
+
+static gboolean
+on_dock_grab_broken_event (GtkWidget *widget,
+ gboolean was_grabbed,
+ NdQueue *queue)
+{
+ grab_notify (queue, FALSE);
+
+ return FALSE;
+}
+
+static gboolean
+on_dock_key_release (GtkWidget *widget,
+ GdkEventKey *event,
+ NdQueue *queue)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ popdown_dock (queue);
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+static void
+clear_stacks (NdQueue *queue)
+{
+ NotifyScreen *nscreen;
+ gint i;
+
+ nscreen = queue->priv->screen;
+ for (i = 0; i < nscreen->n_stacks; i++) {
+ NdStack *stack;
+ stack = nscreen->stacks[i];
+ nd_stack_remove_all (stack);
+ }
+}
+
+static void
+_nd_queue_remove_all (NdQueue *queue)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ gboolean changed;
+
+ changed = FALSE;
+
+ clear_stacks (queue);
+
+ g_queue_clear (queue->priv->queue);
+ g_hash_table_iter_init (&iter, queue->priv->notifications);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ NdNotification *n = ND_NOTIFICATION (value);
+
+ g_signal_handlers_disconnect_by_func (n, G_CALLBACK (on_notification_close), queue);
+ nd_notification_close (n, ND_NOTIFICATION_CLOSED_USER);
+ g_hash_table_iter_remove (&iter);
+ changed = TRUE;
+ }
+ popdown_dock (queue);
+ queue_update (queue);
+
+ if (changed) {
+ g_signal_emit (queue, signals[CHANGED], 0);
+ }
+}
+
+static void
+on_clear_all_clicked (GtkButton *button,
+ NdQueue *queue)
+{
+ _nd_queue_remove_all (queue);
+}
+
+static gboolean
+on_dock_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ NdQueue *queue)
+{
+ GtkWidget *event_widget;
+
+ if (event->type != GDK_BUTTON_PRESS) {
+ return FALSE;
+ }
+ event_widget = gtk_get_event_widget ((GdkEvent *)event);
+ g_debug ("Button press: %p dock=%p", event_widget, widget);
+ if (event_widget == widget) {
+ release_grab (queue, event);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+create_dock (NdQueue *queue)
+{
+ GtkWidget *frame;
+ GtkWidget *box;
+ GtkWidget *button;
+
+ queue->priv->dock = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_widget_add_events (queue->priv->dock,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+
+ gtk_widget_set_name (queue->priv->dock, "notification-popup-window");
+ g_signal_connect (queue->priv->dock,
+ "grab-notify",
+ G_CALLBACK (on_dock_grab_notify),
+ queue);
+ g_signal_connect (queue->priv->dock,
+ "grab-broken-event",
+ G_CALLBACK (on_dock_grab_broken_event),
+ queue);
+ g_signal_connect (queue->priv->dock,
+ "key-release-event",
+ G_CALLBACK (on_dock_key_release),
+ queue);
+ g_signal_connect (queue->priv->dock,
+ "button-press-event",
+ G_CALLBACK (on_dock_button_press),
+ queue);
+#if 0
+ g_signal_connect (queue->priv->dock,
+ "scroll-event",
+ G_CALLBACK (on_dock_scroll_event),
+ queue);
+#endif
+ gtk_window_set_decorated (GTK_WINDOW (queue->priv->dock), FALSE);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (queue->priv->dock), frame);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 2);
+ gtk_container_add (GTK_CONTAINER (frame), box);
+
+ queue->priv->dock_scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (queue->priv->dock_scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_size_request (queue->priv->dock_scrolled_window,
+ WIDTH,
+ -1);
+ gtk_box_pack_start (GTK_BOX (box), queue->priv->dock_scrolled_window, TRUE, TRUE, 0);
+
+ button = gtk_button_new_with_label (_("Clear all notifications"));
+ g_signal_connect (button, "clicked", G_CALLBACK (on_clear_all_clicked), queue);
+ gtk_box_pack_end (GTK_BOX (box), button, FALSE, FALSE, 0);
+}
+
+static void
+nd_queue_init (NdQueue *queue)
+{
+ queue->priv = ND_QUEUE_GET_PRIVATE (queue);
+ queue->priv->notifications = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+ queue->priv->bubbles = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+ queue->priv->queue = g_queue_new ();
+ queue->priv->status_icon = NULL;
+
+ create_dock (queue);
+ create_screen (queue);
+}
+
+static void
+destroy_screen (NdQueue *queue)
+{
+ GdkDisplay *display;
+ GdkScreen *screen;
+ GdkWindow *gdkwindow;
+ gint i;
+
+ display = gdk_display_get_default ();
+ screen = gdk_display_get_default_screen (display);
+
+ g_signal_handlers_disconnect_by_func (screen,
+ G_CALLBACK (on_screen_monitors_changed),
+ queue);
+
+ gdkwindow = gdk_screen_get_root_window (screen);
+ gdk_window_remove_filter (gdkwindow, (GdkFilterFunc) screen_xevent_filter, queue->priv->screen);
+ for (i = 0; i < queue->priv->screen->n_stacks; i++) {
+ g_clear_object (&queue->priv->screen->stacks[i]);
+ }
+
+ g_free (queue->priv->screen->stacks);
+ queue->priv->screen->stacks = NULL;
+
+ g_free (queue->priv->screen);
+ queue->priv->screen = NULL;
+}
+
+static void
+nd_queue_finalize (GObject *object)
+{
+ NdQueue *queue;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (ND_IS_QUEUE (object));
+
+ queue = ND_QUEUE (object);
+
+ g_return_if_fail (queue->priv != NULL);
+
+ if (queue->priv->update_id != 0) {
+ g_source_remove (queue->priv->update_id);
+ }
+
+ g_hash_table_destroy (queue->priv->notifications);
+ g_queue_free (queue->priv->queue);
+
+ destroy_screen (queue);
+
+ if (queue->priv->numerable_icon != NULL) {
+ g_object_unref (queue->priv->numerable_icon);
+ }
+
+ G_OBJECT_CLASS (nd_queue_parent_class)->finalize (object);
+}
+
+NdNotification *
+nd_queue_lookup (NdQueue *queue,
+ guint id)
+{
+ NdNotification *notification;
+
+ g_return_val_if_fail (ND_IS_QUEUE (queue), NULL);
+
+ notification = g_hash_table_lookup (queue->priv->notifications, GUINT_TO_POINTER (id));
+
+ return notification;
+}
+
+guint
+nd_queue_length (NdQueue *queue)
+{
+ g_return_val_if_fail (ND_IS_QUEUE (queue), 0);
+
+ return g_hash_table_size (queue->priv->notifications);
+}
+
+static NdStack *
+get_stack_with_pointer (NdQueue *queue)
+{
+ GdkDisplay *display;
+ GdkSeat *seat;
+ GdkDevice *pointer;
+ GdkScreen *screen;
+ int x, y;
+ int monitor_num;
+
+ display = gdk_display_get_default ();
+ seat = gdk_display_get_default_seat (display);
+ pointer = gdk_seat_get_pointer (seat);
+
+ gdk_device_get_position (pointer, &screen, &x, &y);
+ monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
+
+ if (monitor_num >= queue->priv->screen->n_stacks) {
+ /* screw it - dump it on the last one we'll get
+ a monitors-changed signal soon enough*/
+ monitor_num = queue->priv->screen->n_stacks - 1;
+ }
+
+ return queue->priv->screen->stacks[monitor_num];
+}
+
+static void
+on_bubble_destroyed (NdBubble *bubble,
+ NdQueue *queue)
+{
+ NdNotification *notification;
+
+ g_debug ("Bubble destroyed");
+ notification = nd_bubble_get_notification (bubble);
+
+ nd_notification_set_is_queued (notification, FALSE);
+
+ if (nd_notification_get_is_transient (notification)) {
+ g_debug ("Bubble is transient");
+ nd_notification_close (notification, ND_NOTIFICATION_CLOSED_EXPIRED);
+ }
+
+ queue_update (queue);
+}
+
+static void
+maybe_show_notification (NdQueue *queue)
+{
+ gpointer id;
+ NdNotification *notification;
+ NdBubble *bubble;
+ NdStack *stack;
+ GList *list;
+
+ /* FIXME: show one at a time if not busy or away */
+
+ /* don't show bubbles when dock is showing */
+ if (gtk_widget_get_visible (queue->priv->dock)) {
+ g_debug ("Dock is showing");
+ return;
+ }
+
+ stack = get_stack_with_pointer (queue);
+ list = nd_stack_get_bubbles (stack);
+ if (g_list_length (list) > 0) {
+ /* already showing bubbles */
+ g_debug ("Already showing bubbles");
+ return;
+ }
+
+ id = g_queue_pop_tail (queue->priv->queue);
+ if (id == NULL) {
+ /* Nothing to do */
+ g_debug ("No queued notifications");
+ return;
+ }
+
+ notification = g_hash_table_lookup (queue->priv->notifications, id);
+ g_assert (notification != NULL);
+
+ bubble = nd_bubble_new_for_notification (notification);
+ g_signal_connect (bubble, "destroy", G_CALLBACK (on_bubble_destroyed), queue);
+
+ nd_stack_add_bubble (stack, bubble, TRUE);
+}
+
+static int
+collate_notifications (NdNotification *a,
+ NdNotification *b)
+{
+ GTimeVal tva;
+ GTimeVal tvb;
+
+ nd_notification_get_update_time (a, &tva);
+ nd_notification_get_update_time (b, &tvb);
+ if (tva.tv_sec > tvb.tv_sec) {
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+static void
+update_dock (NdQueue *queue)
+{
+ GtkWidget *child;
+ GList *list;
+ GList *l;
+ int min_height;
+ int height;
+ int monitor_num;
+ GdkScreen *screen;
+ GdkRectangle area;
+ GtkStatusIcon *status_icon;
+ gboolean visible;
+
+ g_return_if_fail (queue);
+
+ child = gtk_bin_get_child (GTK_BIN (queue->priv->dock_scrolled_window));
+ if (child != NULL)
+ gtk_container_remove (GTK_CONTAINER (queue->priv->dock_scrolled_window), child);
+
+ child = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_add (GTK_CONTAINER (queue->priv->dock_scrolled_window),
+ child);
+
+ gtk_container_set_focus_hadjustment (GTK_CONTAINER (child),
+ gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW
(queue->priv->dock_scrolled_window)));
+ gtk_container_set_focus_vadjustment (GTK_CONTAINER (child),
+ gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW
(queue->priv->dock_scrolled_window)));
+
+ list = g_hash_table_get_values (queue->priv->notifications);
+ list = g_list_sort (list, (GCompareFunc)collate_notifications);
+
+ for (l = list; l != NULL; l = l->next) {
+ NdNotification *n = l->data;
+ NdNotificationBox *box;
+ GtkWidget *sep;
+
+ box = nd_notification_box_new_for_notification (n);
+ gtk_widget_show (GTK_WIDGET (box));
+ gtk_box_pack_start (GTK_BOX (child), GTK_WIDGET (box), FALSE, FALSE, 0);
+
+ sep = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show (sep);
+ gtk_box_pack_start (GTK_BOX (child), sep, FALSE, FALSE, 0);
+ }
+ gtk_widget_show (child);
+
+ status_icon = queue->priv->status_icon;
+ visible = FALSE;
+
+ if (status_icon != NULL) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ visible = gtk_status_icon_get_visible (status_icon);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+
+ if (visible) {
+ gtk_widget_get_preferred_height (child, &min_height, &height);
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_status_icon_get_geometry (status_icon, &screen, &area, NULL);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ monitor_num = gdk_screen_get_monitor_at_point (screen, area.x, area.y);
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &area);
+ height = MIN (height, (area.height / 2));
+ gtk_widget_set_size_request (queue->priv->dock_scrolled_window,
+ WIDTH,
+ height);
+ }
+
+ g_list_free (list);
+}
+
+static gboolean
+popup_dock (NdQueue *queue,
+ guint time)
+{
+ GdkRectangle area;
+ GtkOrientation orientation;
+ GdkDisplay *display;
+ GdkScreen *screen;
+ gboolean res;
+ int x;
+ int y;
+ int monitor_num;
+ GdkRectangle monitor;
+ GtkRequisition dock_req;
+ GtkStatusIcon *status_icon;
+ GdkWindow *window;
+ GdkSeat *seat;
+ GdkSeatCapabilities capabilities;
+ GdkGrabStatus status;
+
+ update_dock (queue);
+
+ status_icon = queue->priv->status_icon;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ res = gtk_status_icon_get_geometry (status_icon, &screen, &area, &orientation);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ if (! res) {
+ g_warning ("Unable to determine geometry of status icon");
+ return FALSE;
+ }
+
+ /* position roughly */
+ gtk_window_set_screen (GTK_WINDOW (queue->priv->dock), screen);
+
+ monitor_num = gdk_screen_get_monitor_at_point (screen, area.x, area.y);
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gtk_container_foreach (GTK_CONTAINER (queue->priv->dock),
+ (GtkCallback) gtk_widget_show_all, NULL);
+ gtk_widget_get_preferred_size (queue->priv->dock, &dock_req, NULL);
+
+ if (orientation == GTK_ORIENTATION_VERTICAL) {
+ if (area.x + area.width + dock_req.width <= monitor.x + monitor.width) {
+ x = area.x + area.width;
+ } else {
+ x = area.x - dock_req.width;
+ }
+ if (area.y + dock_req.height <= monitor.y + monitor.height) {
+ y = area.y;
+ } else {
+ y = monitor.y + monitor.height - dock_req.height;
+ }
+ } else {
+ if (area.y + area.height + dock_req.height <= monitor.y + monitor.height) {
+ y = area.y + area.height;
+ } else {
+ y = area.y - dock_req.height;
+ }
+ if (area.x + dock_req.width <= monitor.x + monitor.width) {
+ x = area.x;
+ } else {
+ x = monitor.x + monitor.width - dock_req.width;
+ }
+ }
+
+ gtk_window_move (GTK_WINDOW (queue->priv->dock), x, y);
+
+ /* FIXME: without this, the popup window appears as a square
+ * after changing the orientation
+ */
+ gtk_window_resize (GTK_WINDOW (queue->priv->dock), 1, 1);
+
+ gtk_widget_show_all (queue->priv->dock);
+
+ /* grab focus */
+ gtk_grab_add (queue->priv->dock);
+
+ display = gtk_widget_get_display (queue->priv->dock);
+ window = gtk_widget_get_window (queue->priv->dock);
+ seat = gdk_display_get_default_seat (display);
+
+ capabilities = GDK_SEAT_CAPABILITY_POINTER |
+ GDK_SEAT_CAPABILITY_KEYBOARD;
+
+ status = gdk_seat_grab (seat, window, capabilities, TRUE, NULL,
+ NULL, NULL, NULL);
+
+ if (status != GDK_GRAB_SUCCESS) {
+ ungrab (queue, time);
+ return FALSE;
+ }
+
+ gtk_widget_grab_focus (queue->priv->dock);
+
+ return TRUE;
+}
+
+static void
+show_dock (NdQueue *queue)
+{
+ /* clear the bubble queue since the user will be looking at a
+ full list now */
+ clear_stacks (queue);
+ g_queue_clear (queue->priv->queue);
+
+ popup_dock (queue, GDK_CURRENT_TIME);
+}
+
+static void
+on_status_icon_popup_menu (GtkStatusIcon *status_icon,
+ guint button,
+ guint activate_time,
+ NdQueue *queue)
+{
+ show_dock (queue);
+}
+
+static void
+on_status_icon_activate (GtkStatusIcon *status_icon,
+ NdQueue *queue)
+{
+ show_dock (queue);
+}
+
+static void
+on_status_icon_visible_notify (GtkStatusIcon *icon,
+ GParamSpec *pspec,
+ NdQueue *queue)
+{
+ gboolean visible;
+
+ g_object_get (icon, "visible", &visible, NULL);
+ if (! visible) {
+ if (queue->priv->dock != NULL) {
+ gtk_widget_hide (queue->priv->dock);
+ }
+ }
+}
+
+static gboolean
+update_idle (NdQueue *queue)
+{
+ int num;
+
+ num = g_hash_table_size (queue->priv->notifications);
+
+ /* Show the status icon when their are stored notifications */
+ if (num > 0) {
+ if (gtk_widget_get_visible (queue->priv->dock)) {
+ update_dock (queue);
+ }
+
+ if (queue->priv->status_icon == NULL) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ queue->priv->status_icon = gtk_status_icon_new ();
+ gtk_status_icon_set_title (queue->priv->status_icon,
+ _("Notifications"));
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ g_signal_connect (queue->priv->status_icon,
+ "activate",
+ G_CALLBACK (on_status_icon_activate),
+ queue);
+ g_signal_connect (queue->priv->status_icon,
+ "popup-menu",
+ G_CALLBACK (on_status_icon_popup_menu),
+ queue);
+ g_signal_connect (queue->priv->status_icon,
+ "notify::visible",
+ G_CALLBACK (on_status_icon_visible_notify),
+ queue);
+ }
+
+ if (queue->priv->numerable_icon == NULL) {
+ GIcon *icon;
+ /* FIXME: use a more appropriate icon here */
+ icon = g_themed_icon_new ("mail-message-new");
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ queue->priv->numerable_icon = gtk_numerable_icon_new (icon);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ g_object_unref (icon);
+ }
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_numerable_icon_set_count (GTK_NUMERABLE_ICON (queue->priv->numerable_icon), num);
+ gtk_status_icon_set_from_gicon (queue->priv->status_icon,
+ queue->priv->numerable_icon);
+ gtk_status_icon_set_visible (queue->priv->status_icon, TRUE);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ maybe_show_notification (queue);
+ } else {
+ if (gtk_widget_get_visible (queue->priv->dock)) {
+ popdown_dock (queue);
+ }
+
+ if (queue->priv->status_icon != NULL) {
+ g_object_unref (queue->priv->status_icon);
+ queue->priv->status_icon = NULL;
+ }
+ }
+
+ queue->priv->update_id = 0;
+ return FALSE;
+}
+
+static void
+queue_update (NdQueue *queue)
+{
+ if (queue->priv->update_id > 0) {
+ g_source_remove (queue->priv->update_id);
+ }
+
+ queue->priv->update_id = g_idle_add ((GSourceFunc)update_idle, queue);
+}
+
+static void
+_nd_queue_remove (NdQueue *queue,
+ NdNotification *notification)
+{
+ guint id;
+
+ id = nd_notification_get_id (notification);
+ g_debug ("Removing id %u", id);
+
+ /* FIXME: withdraw currently showing bubbles */
+
+ g_signal_handlers_disconnect_by_func (notification, G_CALLBACK (on_notification_close), queue);
+
+ if (queue->priv->queue != NULL) {
+ g_queue_remove (queue->priv->queue, GUINT_TO_POINTER (id));
+ }
+ g_hash_table_remove (queue->priv->notifications, GUINT_TO_POINTER (id));
+
+ /* FIXME: should probably only emit this when it really removes something */
+ g_signal_emit (queue, signals[CHANGED], 0);
+
+ queue_update (queue);
+}
+
+static void
+on_notification_close (NdNotification *notification,
+ int reason,
+ NdQueue *queue)
+{
+ g_debug ("Notification closed - removing from queue");
+ _nd_queue_remove (queue, notification);
+}
+
+void
+nd_queue_remove_for_id (NdQueue *queue,
+ guint id)
+{
+ NdNotification *notification;
+
+ g_return_if_fail (ND_IS_QUEUE (queue));
+
+ notification = g_hash_table_lookup (queue->priv->notifications, GUINT_TO_POINTER (id));
+ if (notification != NULL) {
+ _nd_queue_remove (queue, notification);
+ }
+}
+
+void
+nd_queue_add (NdQueue *queue,
+ NdNotification *notification)
+{
+ guint id;
+
+ g_return_if_fail (ND_IS_QUEUE (queue));
+
+ id = nd_notification_get_id (notification);
+ g_debug ("Adding id %u", id);
+ g_hash_table_insert (queue->priv->notifications, GUINT_TO_POINTER (id), g_object_ref (notification));
+ g_queue_push_head (queue->priv->queue, GUINT_TO_POINTER (id));
+
+ g_signal_connect (notification, "closed", G_CALLBACK (on_notification_close), queue);
+
+ /* FIXME: should probably only emit this when it really adds something */
+ g_signal_emit (queue, signals[CHANGED], 0);
+
+ queue_update (queue);
+}
+
+NdQueue *
+nd_queue_new (void)
+{
+ if (queue_object != NULL) {
+ g_object_ref (queue_object);
+ } else {
+ queue_object = g_object_new (ND_TYPE_QUEUE, NULL);
+ g_object_add_weak_pointer (queue_object,
+ (gpointer *) &queue_object);
+ }
+
+ return ND_QUEUE (queue_object);
+}
diff --git a/gnome-flashback/libnotifications/nd-queue.h b/gnome-flashback/libnotifications/nd-queue.h
new file mode 100644
index 0000000..0d49324
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-queue.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ND_QUEUE_H
+#define __ND_QUEUE_H
+
+#include <glib-object.h>
+
+#include "nd-notification.h"
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_QUEUE (nd_queue_get_type ())
+#define ND_QUEUE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ND_TYPE_QUEUE, NdQueue))
+#define ND_QUEUE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ND_TYPE_QUEUE, NdQueueClass))
+#define ND_IS_QUEUE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ND_TYPE_QUEUE))
+#define ND_IS_QUEUE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ND_TYPE_QUEUE))
+#define ND_QUEUE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ND_TYPE_QUEUE, NdQueueClass))
+
+typedef struct NdQueuePrivate NdQueuePrivate;
+
+typedef struct
+{
+ GObject parent;
+ NdQueuePrivate *priv;
+} NdQueue;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ void (* changed) (NdQueue *queue);
+} NdQueueClass;
+
+GType nd_queue_get_type (void);
+
+NdQueue * nd_queue_new (void);
+
+guint nd_queue_length (NdQueue *queue);
+
+NdNotification * nd_queue_lookup (NdQueue *queue,
+ guint id);
+
+void nd_queue_add (NdQueue *queue,
+ NdNotification *notification);
+void nd_queue_remove_for_id (NdQueue *queue,
+ guint id);
+
+G_END_DECLS
+
+#endif /* __ND_QUEUE_H */
diff --git a/gnome-flashback/libnotifications/nd-stack.c b/gnome-flashback/libnotifications/nd-stack.c
new file mode 100644
index 0000000..335eca7
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-stack.c
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <strings.h>
+#include <glib.h>
+
+#include <X11/Xproto.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+
+#include "nd-stack.h"
+
+#define ND_STACK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ND_TYPE_STACK, NdStackPrivate))
+
+#define NOTIFY_STACK_SPACING 2
+#define WORKAREA_PADDING 6
+
+struct NdStackPrivate
+{
+ GdkScreen *screen;
+ guint monitor;
+ NdStackLocation location;
+ GList *bubbles;
+ guint update_id;
+};
+
+static void nd_stack_finalize (GObject *object);
+
+G_DEFINE_TYPE (NdStack, nd_stack, G_TYPE_OBJECT)
+
+GList *
+nd_stack_get_bubbles (NdStack *stack)
+{
+ return stack->priv->bubbles;
+}
+
+static int
+get_current_desktop (GdkScreen *screen)
+{
+ Display *display;
+ Window win;
+ Atom current_desktop, type;
+ int format;
+ unsigned long n_items, bytes_after;
+ unsigned char *data_return = NULL;
+ int workspace = 0;
+
+ display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+ win = XRootWindow (display, GDK_SCREEN_XNUMBER (screen));
+
+ current_desktop = XInternAtom (display, "_NET_CURRENT_DESKTOP", True);
+
+ XGetWindowProperty (display,
+ win,
+ current_desktop,
+ 0, G_MAXLONG,
+ False, XA_CARDINAL,
+ &type, &format, &n_items, &bytes_after,
+ &data_return);
+
+ if (type == XA_CARDINAL && format == 32 && n_items > 0)
+ workspace = (int) data_return[0];
+ if (data_return)
+ XFree (data_return);
+
+ return workspace;
+}
+
+static gboolean
+get_work_area (NdStack *stack,
+ GdkRectangle *rect)
+{
+ Atom workarea;
+ Atom type;
+ Window win;
+ int format;
+ gulong num;
+ gulong leftovers;
+ gulong max_len = 4 * 32;
+ guchar *ret_workarea;
+ long *workareas;
+ int result;
+ int disp_screen;
+ int desktop;
+ Display *display;
+
+ display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (stack->priv->screen));
+ workarea = XInternAtom (display, "_NET_WORKAREA", True);
+
+ disp_screen = GDK_SCREEN_XNUMBER (stack->priv->screen);
+
+ /* Defaults in case of error */
+ rect->x = 0;
+ rect->y = 0;
+ rect->width = gdk_screen_get_width (stack->priv->screen);
+ rect->height = gdk_screen_get_height (stack->priv->screen);
+
+ if (workarea == None)
+ return FALSE;
+
+ win = XRootWindow (display, disp_screen);
+ result = XGetWindowProperty (display,
+ win,
+ workarea,
+ 0,
+ max_len,
+ False,
+ AnyPropertyType,
+ &type,
+ &format,
+ &num,
+ &leftovers,
+ &ret_workarea);
+
+ if (result != Success
+ || type == None
+ || format == 0
+ || leftovers
+ || num % 4) {
+ return FALSE;
+ }
+
+ desktop = get_current_desktop (stack->priv->screen);
+
+ workareas = (long *) ret_workarea;
+ rect->x = workareas[desktop * 4];
+ rect->y = workareas[desktop * 4 + 1];
+ rect->width = workareas[desktop * 4 + 2];
+ rect->height = workareas[desktop * 4 + 3];
+
+ XFree (ret_workarea);
+
+ return TRUE;
+}
+
+static void
+get_origin_coordinates (NdStackLocation stack_location,
+ GdkRectangle *workarea,
+ gint *x,
+ gint *y,
+ gint *shiftx,
+ gint *shifty,
+ gint width,
+ gint height)
+{
+ switch (stack_location) {
+ case ND_STACK_LOCATION_TOP_LEFT:
+ *x = workarea->x;
+ *y = workarea->y;
+ *shifty = height;
+ break;
+
+ case ND_STACK_LOCATION_TOP_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y = workarea->y;
+ *shifty = height;
+ break;
+
+ case ND_STACK_LOCATION_BOTTOM_LEFT:
+ *x = workarea->x;
+ *y = workarea->y + workarea->height - height;
+ break;
+
+ case ND_STACK_LOCATION_BOTTOM_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y = workarea->y + workarea->height - height;
+ break;
+
+ case ND_STACK_LOCATION_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+translate_coordinates (NdStackLocation stack_location,
+ GdkRectangle *workarea,
+ gint *x,
+ gint *y,
+ gint *shiftx,
+ gint *shifty,
+ gint width,
+ gint height)
+{
+ switch (stack_location) {
+ case ND_STACK_LOCATION_TOP_LEFT:
+ *x = workarea->x;
+ *y += *shifty;
+ *shifty = height;
+ break;
+
+ case ND_STACK_LOCATION_TOP_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y += *shifty;
+ *shifty = height;
+ break;
+
+ case ND_STACK_LOCATION_BOTTOM_LEFT:
+ *x = workarea->x;
+ *y -= height;
+ break;
+
+ case ND_STACK_LOCATION_BOTTOM_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y -= height;
+ break;
+
+ case ND_STACK_LOCATION_UNKNOWN:
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+nd_stack_class_init (NdStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = nd_stack_finalize;
+
+ g_type_class_add_private (klass, sizeof (NdStackPrivate));
+}
+
+static void
+nd_stack_init (NdStack *stack)
+{
+ stack->priv = ND_STACK_GET_PRIVATE (stack);
+ stack->priv->location = ND_STACK_LOCATION_DEFAULT;
+}
+
+static void
+nd_stack_finalize (GObject *object)
+{
+ NdStack *stack;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (ND_IS_STACK (object));
+
+ stack = ND_STACK (object);
+
+ g_return_if_fail (stack->priv != NULL);
+
+ if (stack->priv->update_id != 0) {
+ g_source_remove (stack->priv->update_id);
+ }
+
+ g_list_free (stack->priv->bubbles);
+
+ G_OBJECT_CLASS (nd_stack_parent_class)->finalize (object);
+}
+
+void
+nd_stack_set_location (NdStack *stack,
+ NdStackLocation location)
+{
+ g_return_if_fail (ND_IS_STACK (stack));
+
+ stack->priv->location = location;
+}
+
+NdStack *
+nd_stack_new (GdkScreen *screen,
+ guint monitor)
+{
+ NdStack *stack;
+
+ g_assert (screen != NULL && GDK_IS_SCREEN (screen));
+ g_assert (monitor < (guint)gdk_screen_get_n_monitors (screen));
+
+ stack = g_object_new (ND_TYPE_STACK, NULL);
+ stack->priv->screen = screen;
+ stack->priv->monitor = monitor;
+
+ return stack;
+}
+
+
+static void
+add_padding_to_rect (GdkRectangle *rect)
+{
+ rect->x += WORKAREA_PADDING;
+ rect->y += WORKAREA_PADDING;
+ rect->width -= WORKAREA_PADDING * 2;
+ rect->height -= WORKAREA_PADDING * 2;
+
+ if (rect->width < 0)
+ rect->width = 0;
+ if (rect->height < 0)
+ rect->height = 0;
+}
+
+static void
+nd_stack_shift_notifications (NdStack *stack,
+ NdBubble *bubble,
+ GList **nw_l,
+ gint init_width,
+ gint init_height,
+ gint *nw_x,
+ gint *nw_y)
+{
+ GdkRectangle workarea;
+ GdkRectangle monitor;
+ GdkRectangle *positions;
+ GList *l;
+ gint x, y;
+ gint shiftx = 0;
+ gint shifty = 0;
+ int i;
+ int n_wins;
+
+ get_work_area (stack, &workarea);
+ gdk_screen_get_monitor_geometry (stack->priv->screen,
+ stack->priv->monitor,
+ &monitor);
+ gdk_rectangle_intersect (&monitor, &workarea, &workarea);
+
+ add_padding_to_rect (&workarea);
+
+ n_wins = g_list_length (stack->priv->bubbles);
+ positions = g_new0 (GdkRectangle, n_wins);
+
+ get_origin_coordinates (stack->priv->location,
+ &workarea,
+ &x, &y,
+ &shiftx,
+ &shifty,
+ init_width,
+ init_height);
+
+ if (nw_x != NULL)
+ *nw_x = x;
+
+ if (nw_y != NULL)
+ *nw_y = y;
+
+ for (i = 0, l = stack->priv->bubbles; l != NULL; i++, l = l->next) {
+ NdBubble *nw2 = ND_BUBBLE (l->data);
+ GtkRequisition req;
+
+ if (bubble == NULL || nw2 != bubble) {
+ gtk_widget_get_preferred_size (GTK_WIDGET (nw2), NULL, &req);
+
+ translate_coordinates (stack->priv->location,
+ &workarea,
+ &x,
+ &y,
+ &shiftx,
+ &shifty,
+ req.width,
+ req.height + NOTIFY_STACK_SPACING);
+ positions[i].x = x;
+ positions[i].y = y;
+ } else if (nw_l != NULL) {
+ *nw_l = l;
+ positions[i].x = -1;
+ positions[i].y = -1;
+ }
+ }
+
+ /* move bubbles at the bottom of the stack first
+ to avoid overlapping */
+ for (i = n_wins - 1, l = g_list_last (stack->priv->bubbles); l != NULL; i--, l = l->prev) {
+ NdBubble *nw2 = ND_BUBBLE (l->data);
+
+ if (bubble == NULL || nw2 != bubble) {
+ gtk_window_move (GTK_WINDOW (nw2), positions[i].x, positions[i].y);
+ }
+ }
+
+ g_free (positions);
+}
+
+static void
+update_position (NdStack *stack)
+{
+ nd_stack_shift_notifications (stack,
+ NULL, /* window */
+ NULL, /* list pointer */
+ 0, /* init width */
+ 0, /* init height */
+ NULL, /* out window x */
+ NULL); /* out window y */
+}
+
+static gboolean
+update_position_idle (NdStack *stack)
+{
+ update_position (stack);
+
+ stack->priv->update_id = 0;
+ return FALSE;
+}
+
+void
+nd_stack_queue_update_position (NdStack *stack)
+{
+ if (stack->priv->update_id != 0) {
+ return;
+ }
+
+ stack->priv->update_id = g_idle_add ((GSourceFunc) update_position_idle, stack);
+}
+
+void
+nd_stack_add_bubble (NdStack *stack,
+ NdBubble *bubble,
+ gboolean new_notification)
+{
+ GtkRequisition req;
+ int x, y;
+
+ gtk_widget_get_preferred_size (GTK_WIDGET (bubble), NULL, &req);
+ nd_stack_shift_notifications (stack,
+ bubble,
+ NULL,
+ req.width,
+ req.height + NOTIFY_STACK_SPACING,
+ &x,
+ &y);
+ gtk_widget_show (GTK_WIDGET (bubble));
+ gtk_window_move (GTK_WINDOW (bubble), x, y);
+
+ if (new_notification) {
+ g_signal_connect_swapped (G_OBJECT (bubble),
+ "destroy",
+ G_CALLBACK (nd_stack_remove_bubble),
+ stack);
+ stack->priv->bubbles = g_list_prepend (stack->priv->bubbles, bubble);
+ }
+}
+
+void
+nd_stack_remove_bubble (NdStack *stack,
+ NdBubble *bubble)
+{
+ GList *remove_l = NULL;
+
+ nd_stack_shift_notifications (stack,
+ bubble,
+ &remove_l,
+ 0,
+ 0,
+ NULL,
+ NULL);
+
+ if (remove_l != NULL)
+ stack->priv->bubbles = g_list_delete_link (stack->priv->bubbles, remove_l);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (bubble)))
+ gtk_widget_unrealize (GTK_WIDGET (bubble));
+}
+
+void
+nd_stack_remove_all (NdStack *stack)
+{
+ GList *bubbles;
+
+ bubbles = g_list_copy (stack->priv->bubbles);
+ g_list_foreach (bubbles, (GFunc)gtk_widget_destroy, NULL);
+ g_list_free (bubbles);
+}
diff --git a/gnome-flashback/libnotifications/nd-stack.h b/gnome-flashback/libnotifications/nd-stack.h
new file mode 100644
index 0000000..8007009
--- /dev/null
+++ b/gnome-flashback/libnotifications/nd-stack.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ND_STACK_H
+#define __ND_STACK_H
+
+#include <gtk/gtk.h>
+#include "nd-bubble.h"
+
+G_BEGIN_DECLS
+
+#define ND_TYPE_STACK (nd_stack_get_type ())
+#define ND_STACK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ND_TYPE_STACK, NdStack))
+#define ND_STACK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ND_TYPE_STACK, NdStackClass))
+#define ND_IS_STACK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ND_TYPE_STACK))
+#define ND_IS_STACK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ND_TYPE_STACK))
+#define ND_STACK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ND_TYPE_STACK, NdStackClass))
+
+typedef struct NdStackPrivate NdStackPrivate;
+
+typedef struct
+{
+ GObject parent;
+ NdStackPrivate *priv;
+} NdStack;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} NdStackClass;
+
+typedef enum
+{
+ ND_STACK_LOCATION_UNKNOWN = -1,
+ ND_STACK_LOCATION_TOP_LEFT,
+ ND_STACK_LOCATION_TOP_RIGHT,
+ ND_STACK_LOCATION_BOTTOM_LEFT,
+ ND_STACK_LOCATION_BOTTOM_RIGHT,
+ ND_STACK_LOCATION_DEFAULT = ND_STACK_LOCATION_TOP_RIGHT
+} NdStackLocation;
+
+GType nd_stack_get_type (void);
+
+NdStack * nd_stack_new (GdkScreen *screen,
+ guint monitor);
+
+void nd_stack_set_location (NdStack *stack,
+ NdStackLocation location);
+void nd_stack_add_bubble (NdStack *stack,
+ NdBubble *bubble,
+ gboolean new_notification);
+void nd_stack_remove_bubble (NdStack *stack,
+ NdBubble *bubble);
+void nd_stack_remove_all (NdStack *stack);
+GList * nd_stack_get_bubbles (NdStack *stack);
+void nd_stack_queue_update_position (NdStack *stack);
+
+G_END_DECLS
+
+#endif /* __ND_STACK_H */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index acf4004..d856c67 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -18,6 +18,10 @@ gnome-flashback/libdisplay-config/flashback-display-config.c
gnome-flashback/libend-session-dialog/gf-inhibit-dialog.c
gnome-flashback/libend-session-dialog/gf-inhibit-dialog.ui
gnome-flashback/libinput-sources/gf-input-sources.c
+gnome-flashback/libnotifications/nd-bubble.c
+gnome-flashback/libnotifications/nd-daemon.c
+gnome-flashback/libnotifications/nd-notification-box.c
+gnome-flashback/libnotifications/nd-queue.c
gnome-flashback/libpolkit/flashback-authenticator.c
gnome-flashback/libpolkit/flashback-listener.c
gnome-flashback/libpolkit/flashback-polkit-dialog.c
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]