[notification-daemon] Redesign for GNOME 3.0
- From: William Jon McCann <mccann src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [notification-daemon] Redesign for GNOME 3.0
- Date: Thu, 7 Oct 2010 12:11:01 +0000 (UTC)
commit 48feafcd4a73ac23df4669552d689f7d287cb053
Author: William Jon McCann <jmccann redhat com>
Date: Wed Oct 6 21:50:29 2010 -0400
Redesign for GNOME 3.0
Not quite finished...
INSTALL | 236 ----
configure.ac | 33 +-
data/Makefile.am | 17 -
src/Makefile.am | 33 +-
src/daemon.c | 417 +++++++
src/{daemon => }/daemon.h | 17 -
src/daemon/Makefile.am | 30 -
src/daemon/daemon.c | 1804 -------------------------------
src/daemon/engines.c | 398 -------
src/daemon/engines.h | 59 -
src/daemon/stack.h | 57 -
src/nd-bubble.c | 984 +++++++++++++++++
src/nd-bubble.h | 59 +
src/nd-notification.c | 600 ++++++++++
src/nd-notification.h | 85 ++
src/nd-queue.c | 1088 +++++++++++++++++++
src/nd-queue.h | 68 ++
src/{daemon/stack.c => nd-stack.c} | 275 +++--
src/nd-stack.h | 77 ++
src/{daemon => }/notificationdaemon.xml | 0
src/{daemon => }/sound.c | 0
src/{daemon => }/sound.h | 0
22 files changed, 3569 insertions(+), 2768 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d81f6b9..c6eaa07 100644
--- a/configure.ac
+++ b/configure.ac
@@ -79,46 +79,22 @@ AC_DEFINE([GETTEXT_PACKAGE], [PACKAGE_TARNAME], [Define to the gettext package n
dnl ---------------------------------------------------------------------------
dnl Requirements for the daemon
dnl ---------------------------------------------------------------------------
-REQ_GTK_VERSION=2.18.0
-REQ_GLIB_VERSION=$REQ_GTK_VERSION
+REQ_GTK_VERSION=2.91.0
+REQ_GLIB_VERSION=2.27.0
REQ_DBUS_VERSION=0.78
REQ_LIBCANBERRA_GTK_VERSION=0.4
pkg_modules="
- gtk+-2.0 >= $REQ_GTK_VERSION, \
+ gtk+-3.0 >= $REQ_GTK_VERSION, \
glib-2.0 >= $REQ_GLIB_VERSION, \
dbus-1 >= $REQ_DBUS_VERSION, \
dbus-glib-1 >= $REQ_DBUS_VERSION, \
- libcanberra-gtk >= $REQ_LIBCANBERRA_GTK_VERSION, \
- gconf-2.0, \
- libwnck-1.0 \
+ libcanberra-gtk3 >= $REQ_LIBCANBERRA_GTK_VERSION, \
x11 \
"
PKG_CHECK_MODULES(NOTIFICATION_DAEMON, $pkg_modules)
AC_SUBST(NOTIFICATION_DAEMON_CFLAGS)
AC_SUBST(NOTIFICATION_DAEMON_LIBS)
-gdk_modules="
- gdk-2.0 >= $REQ_GTK_VERSION, \
- gdk-pixbuf-2.0 >= $REQ_GTK_VERSION \
-"
-PKG_CHECK_MODULES(GDK, $gdk_modules, have_gdk=yes,
-[
- have_gdk=no
- AC_MSG_WARN("Some test apps will not be built")
-])
-
-AM_CONDITIONAL(HAVE_GDK, test "x$have_gdk" = "xyes")
-
-AC_SUBST(GDK_CFLAGS)
-AC_SUBST(GDK_LIBS)
-
-AM_GCONF_SOURCE_2
-AC_PATH_PROG(GCONFTOOL, gconftool-2, no)
-
-if test "x$GCONFTOOL" = "xno"; then
- AC_MSG_ERROR([gconftool-2 executable not found in your path - should be installed with Gconf])
-fi
-
dnl
dnl Check the D-BUS version.
dnl
@@ -217,7 +193,6 @@ data/Makefile
data/org.freedesktop.Notifications.service
po/Makefile.in
src/Makefile
-src/daemon/Makefile
])
AC_OUTPUT
diff --git a/data/Makefile.am b/data/Makefile.am
index 896f983..7145130 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,24 +1,7 @@
servicedir = $(DBUS_SERVICES_DIR)
service_DATA = org.freedesktop.Notifications.service
-schemasdir = $(GCONF_SCHEMA_FILE_DIR)
-schemas_in_files = notification-daemon.schemas.in
-schemas_DATA = $(schemas_in_files:.schemas.in=.schemas)
-
- INTLTOOL_SCHEMAS_RULE@
-
-if GCONF_SCHEMAS_INSTALL
-install-data-local:
- GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(schemas_DATA)
-else
-install-data-local:
-endif
-
EXTRA_DIST = \
- $(schemas_in_files) \
$(service_DATA)
-CLEANFILES = $(schemas_DATA)
-
-
-include $(top_srcdir)/git.mk
diff --git a/src/Makefile.am b/src/Makefile.am
index 4b42b29..ad3cb8a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,34 @@
-SUBDIRS = daemon
+libexec_PROGRAMS = notification-daemon
+
+notification_daemon_SOURCES = \
+ nd-notification.c \
+ nd-notification.h \
+ nd-bubble.c \
+ nd-bubble.h \
+ nd-stack.c \
+ nd-stack.h \
+ nd-queue.c \
+ nd-queue.h \
+ daemon.c \
+ daemon.h \
+ sound.c \
+ sound.h
+
+notification_daemon_LDADD = $(NOTIFICATION_DAEMON_LIBS)
+
+BUILT_SOURCES = notificationdaemon-dbus-glue.h
+
+notificationdaemon-dbus-glue.h: notificationdaemon.xml
+ dbus-binding-tool --mode=glib-server --prefix=notification_daemon \
+ $(srcdir)/notificationdaemon.xml > notificationdaemon-dbus-glue.h
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(NOTIFICATION_DAEMON_CFLAGS) \
+ -DENGINES_DIR=\"$(libdir)/notification-daemon-1.0/engines\"
+
+EXTRA_DIST = notificationdaemon.xml
+DISTCLEANFILES = \
+ notificationdaemon-dbus-glue.h
-include $(top_srcdir)/git.mk
diff --git a/src/daemon.c b/src/daemon.c
new file mode 100644
index 0000000..34325c2
--- /dev/null
+++ b/src/daemon.c
@@ -0,0 +1,417 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006 Christian Hammond <chipx86 chipx86 com>
+ * Copyright (C) 2005 John (J5) Palmieri <johnp redhat com>
+ * 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 2, 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <glib-object.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 "daemon.h"
+#include "nd-notification.h"
+#include "nd-queue.h"
+#include "notificationdaemon-dbus-glue.h"
+
+#define MAX_NOTIFICATIONS 20
+
+#define IDLE_SECONDS 30
+#define NOTIFICATION_BUS_NAME "org.freedesktop.Notifications"
+#define NOTIFICATION_BUS_PATH "/org/freedesktop/Notifications"
+
+#define NW_GET_DAEMON(nw) \
+ (g_object_get_data(G_OBJECT(nw), "_notify_daemon"))
+
+struct _NotifyDaemonPrivate
+{
+ guint exit_timeout_source;
+ NdQueue *queue;
+};
+
+static DBusConnection *dbus_conn = NULL;
+
+static void notify_daemon_finalize (GObject *object);
+
+G_DEFINE_TYPE (NotifyDaemon, notify_daemon, G_TYPE_OBJECT);
+
+static void
+notify_daemon_class_init (NotifyDaemonClass *daemon_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (daemon_class);
+
+ object_class->finalize = notify_daemon_finalize;
+
+ g_type_class_add_private (daemon_class, sizeof (NotifyDaemonPrivate));
+}
+
+static gboolean
+do_exit (gpointer user_data)
+{
+ g_debug ("Exiting due to inactivity");
+ exit (1);
+ return FALSE;
+}
+
+static void
+add_exit_timeout (NotifyDaemon *daemon)
+{
+ if (daemon->priv->exit_timeout_source > 0)
+ return;
+
+ daemon->priv->exit_timeout_source = g_timeout_add_seconds (IDLE_SECONDS, do_exit, NULL);
+}
+
+static void
+remove_exit_timeout (NotifyDaemon *daemon)
+{
+ if (daemon->priv->exit_timeout_source == 0)
+ return;
+
+ g_source_remove (daemon->priv->exit_timeout_source);
+ daemon->priv->exit_timeout_source = 0;
+}
+
+static void
+on_queue_changed (NdQueue *queue,
+ NotifyDaemon *daemon)
+{
+ if (nd_queue_length (queue) > 0) {
+ remove_exit_timeout (daemon);
+ } else {
+ add_exit_timeout (daemon);
+ }
+}
+
+static void
+notify_daemon_init (NotifyDaemon *daemon)
+{
+ daemon->priv = G_TYPE_INSTANCE_GET_PRIVATE (daemon,
+ NOTIFY_TYPE_DAEMON,
+ NotifyDaemonPrivate);
+
+ daemon->priv->queue = nd_queue_new ();
+ add_exit_timeout (daemon);
+ g_signal_connect (daemon->priv->queue, "changed", G_CALLBACK (on_queue_changed), daemon);
+}
+
+static void
+notify_daemon_finalize (GObject *object)
+{
+ NotifyDaemon *daemon;
+
+ daemon = NOTIFY_DAEMON (object);
+
+ remove_exit_timeout (daemon);
+
+ g_object_unref (daemon->priv->queue);
+
+ g_free (daemon->priv);
+
+ G_OBJECT_CLASS (notify_daemon_parent_class)->finalize (object);
+}
+
+static DBusMessage *
+create_signal_for_notification (NdNotification *notification,
+ const char *signal_name)
+{
+ guint id;
+ const char *dest;
+ DBusMessage *message;
+
+ id = nd_notification_get_id (notification);
+ dest = nd_notification_get_sender (notification);
+ g_assert (dest != NULL);
+
+ message = dbus_message_new_signal (NOTIFICATION_BUS_PATH,
+ NOTIFICATION_BUS_NAME,
+ signal_name);
+
+ dbus_message_set_destination (message, dest);
+ dbus_message_append_args (message,
+ DBUS_TYPE_UINT32,
+ &id,
+ DBUS_TYPE_INVALID);
+
+ return message;
+}
+
+GQuark
+notify_daemon_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0)
+ q = g_quark_from_static_string ("notification-daemon-error-quark");
+
+ return q;
+}
+
+static void
+on_notification_close (NdNotification *notification,
+ int reason,
+ NotifyDaemon *daemon)
+{
+ DBusMessage *message;
+
+ message = create_signal_for_notification (notification, "NotificationClosed");
+ dbus_message_append_args (message,
+ DBUS_TYPE_UINT32,
+ &reason,
+ DBUS_TYPE_INVALID);
+ dbus_connection_send (dbus_conn, message, NULL);
+ dbus_message_unref (message);
+
+ nd_queue_remove (daemon->priv->queue,
+ nd_notification_get_id (notification));
+}
+
+static void
+on_notification_action_invoked (NdNotification *notification,
+ const char *action,
+ NotifyDaemon *daemon)
+{
+ guint id;
+ DBusMessage *message;
+
+ id = nd_notification_get_id (notification);
+ message = create_signal_for_notification (notification, "ActionInvoked");
+ dbus_message_append_args (message,
+ DBUS_TYPE_STRING,
+ &action,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send (dbus_conn, message, NULL);
+ dbus_message_unref (message);
+
+ nd_notification_close (notification, ND_NOTIFICATION_CLOSED_USER);
+}
+
+gboolean
+notify_daemon_notify_handler (NotifyDaemon *daemon,
+ const char *app_name,
+ guint id,
+ const char *icon,
+ const char *summary,
+ const char *body,
+ char **actions,
+ GHashTable *hints,
+ int timeout,
+ DBusGMethodInvocation *context)
+{
+ NotifyDaemonPrivate *priv = daemon->priv;
+ NdNotification *notification;
+
+ if (nd_queue_length (priv->queue) > MAX_NOTIFICATIONS) {
+ GError *error;
+
+ error = g_error_new (notify_daemon_error_quark (),
+ 1,
+ _("Exceeded maximum number of notifications"));
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+
+ return TRUE;
+ }
+
+ if (id > 0) {
+ notification = nd_queue_lookup (priv->queue, id);
+ if (notification == NULL) {
+ id = 0;
+ } else {
+ g_object_ref (notification);
+ }
+ }
+
+ if (id == 0) {
+ char *sender;
+
+ sender = dbus_g_method_get_sender (context);
+
+ notification = nd_notification_new (sender);
+
+ g_free (sender);
+ }
+
+ nd_notification_update (notification,
+ app_name,
+ icon,
+ summary,
+ body,
+ (const char **)actions,
+ hints,
+ timeout);
+ g_signal_connect (notification, "closed", G_CALLBACK (on_notification_close), daemon);
+ g_signal_connect (notification, "action-invoked", G_CALLBACK (on_notification_action_invoked), daemon);
+
+ if (id == 0) {
+ nd_queue_add (priv->queue, notification);
+ }
+
+ dbus_g_method_return (context, nd_notification_get_id (notification));
+
+ g_object_unref (notification);
+
+ return TRUE;
+}
+
+gboolean
+notify_daemon_close_notification_handler (NotifyDaemon *daemon,
+ guint id,
+ GError **error)
+{
+ if (id == 0) {
+ g_set_error (error,
+ notify_daemon_error_quark (),
+ 100,
+ _("%u is not a valid notification ID"),
+ id);
+ return FALSE;
+ } else {
+ NdNotification *notification;
+
+ notification = nd_queue_lookup (daemon->priv->queue, id);
+ if (notification != NULL) {
+ nd_notification_close (notification, ND_NOTIFICATION_CLOSED_API);
+ }
+
+ return TRUE;
+ }
+}
+
+gboolean
+notify_daemon_get_capabilities (NotifyDaemon *daemon,
+ char ***caps)
+{
+ GPtrArray *a;
+ char **_caps;
+
+ a = g_ptr_array_new ();
+ g_ptr_array_add (a, g_strdup ("actions"));
+ g_ptr_array_add (a, g_strdup ("body"));
+ g_ptr_array_add (a, g_strdup ("body-hyperlinks"));
+ g_ptr_array_add (a, g_strdup ("body-markup"));
+ g_ptr_array_add (a, g_strdup ("icon-static"));
+ g_ptr_array_add (a, g_strdup ("sound"));
+ g_ptr_array_add (a, NULL);
+ _caps = (char **) g_ptr_array_free (a, FALSE);
+
+ *caps = _caps;
+
+ return TRUE;
+}
+
+gboolean
+notify_daemon_get_server_information (NotifyDaemon *daemon,
+ char **out_name,
+ char **out_vendor,
+ char **out_version,
+ char **out_spec_ver)
+{
+ *out_name = g_strdup ("Notification Daemon");
+ *out_vendor = g_strdup ("GNOME");
+ *out_version = g_strdup (PACKAGE_VERSION);
+ *out_spec_ver = g_strdup ("1.1");
+
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ NotifyDaemon *daemon;
+ DBusGConnection *connection;
+ DBusGProxy *bus_proxy;
+ GError *error;
+ gboolean res;
+ guint request_name_result;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
+
+ gtk_init (&argc, &argv);
+
+ error = NULL;
+ connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
+ if (connection == NULL) {
+ g_printerr ("Failed to open connection to bus: %s\n",
+ error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ dbus_conn = dbus_g_connection_get_connection (connection);
+
+ dbus_g_object_type_install_info (NOTIFY_TYPE_DAEMON,
+ &dbus_glib_notification_daemon_object_info);
+
+ bus_proxy = dbus_g_proxy_new_for_name (connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus");
+
+ res = dbus_g_proxy_call (bus_proxy,
+ "RequestName",
+ &error,
+ G_TYPE_STRING, NOTIFICATION_BUS_NAME,
+ G_TYPE_UINT, 0,
+ G_TYPE_INVALID,
+ G_TYPE_UINT, &request_name_result,
+ G_TYPE_INVALID);
+ if (! res
+ || request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ if (error != NULL) {
+ g_warning ("Failed to acquire name %s: %s",
+ NOTIFICATION_BUS_NAME,
+ error->message);
+ g_error_free (error);
+ } else {
+ g_warning ("Failed to acquire name %s", NOTIFICATION_BUS_NAME);
+ }
+ goto out;
+ }
+
+ daemon = g_object_new (NOTIFY_TYPE_DAEMON, NULL);
+
+ dbus_g_connection_register_g_object (connection,
+ "/org/freedesktop/Notifications",
+ G_OBJECT (daemon));
+
+ gtk_main ();
+
+ g_object_unref (daemon);
+ out:
+
+ return 0;
+}
diff --git a/src/daemon/daemon.h b/src/daemon.h
similarity index 87%
rename from src/daemon/daemon.h
rename to src/daemon.h
index 00e2fe1..7a55dd1 100644
--- a/src/daemon/daemon.h
+++ b/src/daemon.h
@@ -22,19 +22,12 @@
#ifndef NOTIFY_DAEMON_H
#define NOTIFY_DAEMON_H
-#include <gconf/gconf-client.h>
#include <glib.h>
#include <glib-object.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
-#define GCONF_KEY_DAEMON "/apps/notification-daemon"
-#define GCONF_KEY_THEME GCONF_KEY_DAEMON "/theme"
-#define GCONF_KEY_POPUP_LOCATION GCONF_KEY_DAEMON "/popup_location"
-#define GCONF_KEY_SOUND_ENABLED GCONF_KEY_DAEMON "/sound_enabled"
-#define GCONF_KEY_DEFAULT_SOUND GCONF_KEY_DAEMON "/default_sound"
-
#define NOTIFY_TYPE_DAEMON (notify_daemon_get_type())
#define NOTIFY_DAEMON(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), NOTIFY_TYPE_DAEMON, NotifyDaemon))
@@ -56,14 +49,6 @@ enum
URGENCY_CRITICAL
};
-typedef enum
-{
- NOTIFYD_CLOSED_EXPIRED = 1,
- NOTIFYD_CLOSED_USER = 2,
- NOTIFYD_CLOSED_API = 3,
- NOTIFYD_CLOSED_RESERVED = 4
-} NotifydClosedReason;
-
typedef struct _NotifyDaemon NotifyDaemon;
typedef struct _NotifyDaemonClass NotifyDaemonClass;
typedef struct _NotifyDaemonPrivate NotifyDaemonPrivate;
@@ -109,7 +94,5 @@ gboolean notify_daemon_get_server_information (NotifyDaemon *daemon,
char **out_version,
char **out_spec_ver);
-GConfClient *get_gconf_client (void);
-
G_END_DECLS
#endif /* NOTIFY_DAEMON_H */
diff --git a/src/nd-bubble.c b/src/nd-bubble.c
new file mode 100644
index 0000000..9c7656e
--- /dev/null
+++ b/src/nd-bubble.c
@@ -0,0 +1,984 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <strings.h>
+#include <glib.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 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 *iconbox;
+ 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_class_init (NdBubbleClass *klass);
+static void nd_bubble_init (NdBubble *bubble);
+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)
+
+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
+color_reverse (const GdkColor *a,
+ GdkColor *b)
+{
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+ gdouble h;
+ gdouble s;
+ gdouble v;
+
+ red = (gdouble) a->red / 65535.0;
+ green = (gdouble) a->green / 65535.0;
+ blue = (gdouble) a->blue / 65535.0;
+
+ gtk_rgb_to_hsv (red, green, blue, &h, &s, &v);
+
+ /* pivot brightness around the center */
+ v = 0.5 + (0.5 - v);
+ if (v > 1.0)
+ v = 1.0;
+ else if (v < 0.0)
+ v = 0.0;
+
+ /* reduce saturation by 50% */
+ s *= 0.5;
+
+ gtk_hsv_to_rgb (h, s, v, &red, &green, &blue);
+
+ b->red = red * 65535.0;
+ b->green = green * 65535.0;
+ b->blue = blue * 65535.0;
+}
+
+static void
+override_style (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ GtkStateType state;
+ GtkStyle *style;
+ GtkStyle *old_style;
+ GdkColor fg;
+ GdkColor bg;
+
+ /* FIXME! causes a segfault */
+ return;
+
+ gtk_widget_ensure_style (widget);
+ old_style = gtk_widget_get_style (widget);
+ style = gtk_style_copy (old_style);
+ if (previous_style == NULL
+ || (previous_style != NULL
+ && (previous_style->bg[GTK_STATE_NORMAL].red != style->bg[GTK_STATE_NORMAL].red
+ || previous_style->bg[GTK_STATE_NORMAL].green != style->bg[GTK_STATE_NORMAL].green
+ || previous_style->bg[GTK_STATE_NORMAL].blue != style->bg[GTK_STATE_NORMAL].blue))) {
+
+ state = (GtkStateType) 0;
+ while (state < (GtkStateType) G_N_ELEMENTS (old_style->bg)) {
+ color_reverse (&style->bg[state], &bg);
+ gtk_widget_modify_bg (widget, state, &bg);
+ state++;
+ }
+
+ }
+
+ if (previous_style == NULL
+ || (previous_style != NULL
+ && (previous_style->fg[GTK_STATE_NORMAL].red != style->fg[GTK_STATE_NORMAL].red
+ || previous_style->fg[GTK_STATE_NORMAL].green != style->fg[GTK_STATE_NORMAL].green
+ || previous_style->fg[GTK_STATE_NORMAL].blue != style->fg[GTK_STATE_NORMAL].blue))) {
+
+ state = (GtkStateType) 0;
+ while (state < (GtkStateType) G_N_ELEMENTS (old_style->fg)) {
+ color_reverse (&style->fg[state], &fg);
+ gtk_widget_modify_fg (widget, state, &fg);
+ state++;
+ }
+ }
+
+ g_object_unref (style);
+}
+
+static void
+nd_bubble_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ override_style (widget, previous_style);
+ gtk_widget_queue_draw (widget);
+
+ GTK_WIDGET_CLASS (nd_bubble_parent_class)->style_set (widget, previous_style);
+}
+
+static void
+nd_bubble_composited_changed (GtkWidget *widget)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+
+ bubble->priv->composited = gdk_screen_is_composited (gtk_widget_get_screen (widget));
+ 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
+paint_bubble (NdBubble *bubble,
+ cairo_t *cr)
+{
+ GdkColor color;
+ double r, g, b;
+ cairo_t *cr2;
+ cairo_surface_t *surface;
+ cairo_region_t *region;
+ GtkStyle *style;
+ 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);
+
+ style = gtk_widget_get_style (GTK_WIDGET (bubble));
+ color = style->bg [GTK_STATE_NORMAL];
+ r = (float)color.red / 65535.0;
+ g = (float)color.green / 65535.0;
+ b = (float)color.blue / 65535.0;
+ cairo_set_source_rgba (cr2, r, g, b, BACKGROUND_ALPHA);
+ cairo_fill_preserve (cr2);
+
+ color = style->text_aa [GTK_STATE_NORMAL];
+ r = (float) color.red / 65535.0;
+ g = (float) color.green / 65535.0;
+ b = (float) color.blue / 65535.0;
+ cairo_set_source_rgba (cr2, r, g, b, BACKGROUND_ALPHA / 2);
+ cairo_set_line_width (cr2, 2);
+ cairo_stroke (cr2);
+
+ cairo_destroy (cr2);
+ cairo_set_source_surface (cr, surface, 0, 0);
+ cairo_paint (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)
+{
+ if (bubble->priv->timeout_id != 0) {
+ g_source_remove (bubble->priv->timeout_id);
+ }
+ bubble->priv->timeout_id = g_timeout_add_seconds (TIMEOUT_SEC,
+ (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 gboolean
+nd_bubble_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ NdBubble *bubble = ND_BUBBLE (widget);
+ if (bubble->priv->timeout_id != 0) {
+ g_source_remove (bubble->priv->timeout_id);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+nd_bubble_leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *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->style_set = nd_bubble_style_set;
+ 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->enter_notify_event = nd_bubble_enter_notify_event;
+ widget_class->leave_notify_event = nd_bubble_leave_notify_event;
+ widget_class->realize = nd_bubble_realize;
+
+ 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;
+
+ /* Somewhat of a hack.. */
+ bubble->priv->url_clicked_lock = TRUE;
+
+ escaped_uri = g_shell_quote (uri);
+
+ if (g_find_program_in_path ("gvfs-open") != NULL) {
+ cmd = g_strdup_printf ("gvfs-open %s", escaped_uri);
+ } else if (g_find_program_in_path ("xdg-open") != NULL) {
+ cmd = g_strdup_printf ("xdg-open %s", escaped_uri);
+ } else if (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);
+
+ 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
+on_style_set (GtkWidget *widget,
+ GtkStyle *previous_style,
+ NdBubble *bubble)
+{
+ g_signal_handlers_block_by_func (G_OBJECT (widget), on_style_set, bubble);
+ override_style (widget, previous_style);
+
+ gtk_widget_queue_draw (widget);
+
+ g_signal_handlers_unblock_by_func (G_OBJECT (widget), on_style_set, bubble);
+}
+
+static void
+nd_bubble_init (NdBubble *bubble)
+{
+ GtkWidget *main_vbox;
+ GtkWidget *vbox;
+ GtkWidget *close_button;
+ GtkWidget *image;
+ GtkWidget *alignment;
+ AtkObject *atkobj;
+ GtkRcStyle *rcstyle;
+ 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);
+ 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_vbox_new (FALSE, 0);
+ g_signal_connect (G_OBJECT (main_vbox),
+ "style-set",
+ G_CALLBACK (on_style_set),
+ bubble);
+ 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_hbox_new (FALSE, 0);
+ gtk_widget_show (bubble->priv->main_hbox);
+ gtk_box_pack_start (GTK_BOX (main_vbox),
+ bubble->priv->main_hbox,
+ FALSE, FALSE, 0);
+
+ /* First row (icon, vbox, close) */
+ bubble->priv->iconbox = gtk_alignment_new (0.5, 0, 0, 0);
+ gtk_widget_show (bubble->priv->iconbox);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (bubble->priv->iconbox),
+ 5, 0, 0, 0);
+ gtk_box_pack_start (GTK_BOX (bubble->priv->main_hbox),
+ bubble->priv->iconbox,
+ FALSE, FALSE, 0);
+ gtk_widget_set_size_request (bubble->priv->iconbox, BODY_X_OFFSET, -1);
+
+ bubble->priv->icon = gtk_image_new ();
+ gtk_widget_show (bubble->priv->icon);
+ gtk_container_add (GTK_CONTAINER (bubble->priv->iconbox), bubble->priv->icon);
+
+ vbox = gtk_vbox_new (FALSE, 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 */
+ alignment = gtk_alignment_new (0.5, 0, 0, 0);
+ gtk_widget_show (alignment);
+ gtk_box_pack_start (GTK_BOX (bubble->priv->main_hbox), alignment, FALSE, FALSE, 0);
+
+ close_button = gtk_button_new ();
+ g_signal_connect (G_OBJECT (close_button),
+ "style-set",
+ G_CALLBACK (on_style_set),
+ bubble);
+ gtk_widget_show (close_button);
+ bubble->priv->close_button = close_button;
+ gtk_container_add (GTK_CONTAINER (alignment), close_button);
+ 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);
+
+ rcstyle = gtk_rc_style_new ();
+ rcstyle->xthickness = rcstyle->ythickness = 0;
+ gtk_widget_modify_style (close_button, rcstyle);
+ g_object_unref (rcstyle);
+
+ 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_stock (GTK_STOCK_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);
+ g_signal_connect (G_OBJECT (bubble->priv->summary_label),
+ "style-set",
+ G_CALLBACK (on_style_set),
+ bubble);
+ gtk_widget_show (bubble->priv->summary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), bubble->priv->summary_label, TRUE, TRUE, 0);
+ gtk_misc_set_alignment (GTK_MISC (bubble->priv->summary_label), 0, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (bubble->priv->summary_label), TRUE);
+
+ atkobj = gtk_widget_get_accessible (bubble->priv->summary_label);
+ atk_object_set_description (atkobj, "Notification summary text.");
+
+ bubble->priv->content_hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (bubble->priv->content_hbox);
+ gtk_box_pack_start (GTK_BOX (vbox), bubble->priv->content_hbox, FALSE, FALSE, 0);
+
+
+ vbox = gtk_vbox_new (FALSE, 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);
+ g_signal_connect (G_OBJECT (bubble->priv->body_label),
+ "style-set",
+ G_CALLBACK (on_style_set),
+ bubble);
+ gtk_widget_show (bubble->priv->body_label);
+ gtk_box_pack_start (GTK_BOX (vbox), bubble->priv->body_label, TRUE, TRUE, 0);
+ gtk_misc_set_alignment (GTK_MISC (bubble->priv->body_label), 0, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (bubble->priv->body_label), TRUE);
+ 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 body text.");
+
+ alignment = gtk_alignment_new (1, 0.5, 0, 0);
+ gtk_widget_show (alignment);
+ gtk_box_pack_start (GTK_BOX (vbox), alignment, FALSE, TRUE, 0);
+
+ bubble->priv->actions_box = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (bubble->priv->actions_box);
+ gtk_container_add (GTK_CONTAINER (alignment), bubble->priv->actions_box);
+}
+
+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));
+ gtk_label_set_markup (GTK_LABEL (bubble->priv->body_label), body);
+
+ 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_size_request (bubble->priv->close_button, &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->iconbox,
+ 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->iconbox,
+ 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");
+
+ nd_notification_action_invoked (bubble->priv->notification,
+ key);
+ gtk_widget_destroy (GTK_WIDGET (bubble));
+}
+
+static void
+add_notification_action (NdBubble *bubble,
+ const char *text,
+ const char *key)
+{
+ GtkWidget *label;
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GdkPixbuf *pixbuf;
+ char *buf;
+
+ if (!gtk_widget_get_visible (bubble->priv->actions_box)) {
+ GtkWidget *alignment;
+
+ gtk_widget_show (bubble->priv->actions_box);
+ update_content_hbox_visibility (bubble);
+
+ alignment = gtk_alignment_new (1, 0.5, 0, 0);
+ gtk_widget_show (alignment);
+ gtk_box_pack_end (GTK_BOX (bubble->priv->actions_box),
+ alignment,
+ FALSE, TRUE, 0);
+ }
+
+ button = gtk_button_new ();
+ g_signal_connect (G_OBJECT (button),
+ "style-set",
+ G_CALLBACK (on_style_set),
+ bubble);
+ 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_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (button), hbox);
+
+ /* Try to be smart and find a suitable icon. */
+ buf = g_strdup_printf ("stock_%s", key);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (bubble))),
+ buf,
+ 16,
+ GTK_ICON_LOOKUP_USE_BUILTIN,
+ NULL);
+ g_free(buf);
+
+ if (pixbuf != NULL) {
+ GtkWidget *image = gtk_image_new_from_pixbuf (pixbuf);
+ g_signal_connect (G_OBJECT (image),
+ "style-set",
+ G_CALLBACK (on_style_set),
+ bubble);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.5);
+ }
+
+ label = gtk_label_new (NULL);
+ g_signal_connect (G_OBJECT (label),
+ "style-set",
+ G_CALLBACK (on_style_set),
+ bubble);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+ 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);
+}
+
+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/src/nd-bubble.h b/src/nd-bubble.h
new file mode 100644
index 0000000..617c653
--- /dev/null
+++ b/src/nd-bubble.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#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);
+
+
+G_END_DECLS
+
+#endif /* __ND_BUBBLE_H */
diff --git a/src/nd-notification.c b/src/nd-notification.c
new file mode 100644
index 0000000..255e64b
--- /dev/null
+++ b/src/nd-notification.c
@@ -0,0 +1,600 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <strings.h>
+#include <gtk/gtk.h>
+#include <dbus/dbus-glib.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_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 (g_str_hash, g_str_equal);
+}
+
+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 char *app_name,
+ const char *icon,
+ const char *summary,
+ const char *body,
+ const char **actions,
+ GHashTable *hints,
+ int timeout)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ 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_hash_table_iter_init (&iter, hints);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_hash_table_insert (notification->hints, key, value);
+ }
+
+ 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;
+}
+
+gboolean
+nd_notification_get_is_closed (NdNotification *notification)
+{
+ g_return_val_if_fail (ND_IS_NOTIFICATION (notification), FALSE);
+
+ return notification->is_closed;
+}
+
+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;
+}
+
+
+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 (GValue *icon_data,
+ int size)
+{
+ const guchar *data = NULL;
+ gboolean has_alpha;
+ int bits_per_sample;
+ int width;
+ int height;
+ int rowstride;
+ int n_channels;
+ gsize expected_len;
+ GdkPixbuf *pixbuf;
+ GValueArray *image_struct;
+ GValue *value;
+ GArray *tmp_array;
+ GType struct_type;
+
+ struct_type = dbus_g_type_get_struct ("GValueArray",
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_BOOLEAN,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ dbus_g_type_get_collection ("GArray", G_TYPE_UCHAR),
+ G_TYPE_INVALID);
+
+ if (!G_VALUE_HOLDS (icon_data, struct_type)) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected a "
+ "GValue of type GValueArray");
+ return NULL;
+ }
+
+ image_struct = (GValueArray *) g_value_get_boxed (icon_data);
+ value = g_value_array_get_nth (image_struct, 0);
+
+ if (value == NULL) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected position "
+ "0 of the GValueArray to exist");
+ return NULL;
+ }
+
+ if (!G_VALUE_HOLDS (value, G_TYPE_INT)) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 0 of the GValueArray to be of type int");
+ return NULL;
+ }
+
+ width = g_value_get_int (value);
+ value = g_value_array_get_nth (image_struct, 1);
+
+ if (value == NULL) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 1 of the GValueArray to exist");
+ return NULL;
+ }
+
+ if (!G_VALUE_HOLDS (value, G_TYPE_INT)) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 1 of the GValueArray to be of type int");
+ return NULL;
+ }
+
+ height = g_value_get_int (value);
+ value = g_value_array_get_nth (image_struct, 2);
+
+ if (value == NULL) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 2 of the GValueArray to exist");
+ return NULL;
+ }
+
+ if (!G_VALUE_HOLDS (value, G_TYPE_INT)) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 2 of the GValueArray to be of type int");
+ return NULL;
+ }
+
+ rowstride = g_value_get_int (value);
+ value = g_value_array_get_nth (image_struct, 3);
+
+ if (value == NULL) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 3 of the GValueArray to exist");
+ return NULL;
+ }
+
+ if (!G_VALUE_HOLDS (value, G_TYPE_BOOLEAN)) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 3 of the GValueArray to be of type gboolean");
+ return NULL;
+ }
+
+ has_alpha = g_value_get_boolean (value);
+ value = g_value_array_get_nth (image_struct, 4);
+
+ if (value == NULL) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 4 of the GValueArray to exist");
+ return NULL;
+ }
+
+ if (!G_VALUE_HOLDS (value, G_TYPE_INT)) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 4 of the GValueArray to be of type int");
+ return NULL;
+ }
+
+ bits_per_sample = g_value_get_int (value);
+ value = g_value_array_get_nth (image_struct, 5);
+
+ if (value == NULL) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 5 of the GValueArray to exist");
+ return NULL;
+ }
+
+ if (!G_VALUE_HOLDS (value, G_TYPE_INT)) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 5 of the GValueArray to be of type int");
+ return NULL;
+ }
+
+ n_channels = g_value_get_int (value);
+ value = g_value_array_get_nth (image_struct, 6);
+
+ if (value == NULL) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 6 of the GValueArray to exist");
+ return NULL;
+ }
+
+ if (!G_VALUE_HOLDS (value,
+ dbus_g_type_get_collection ("GArray",
+ G_TYPE_UCHAR))) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected "
+ "position 6 of the GValueArray to be of type GArray");
+ return NULL;
+ }
+
+ tmp_array = (GArray *) g_value_get_boxed (value);
+ expected_len = (height - 1) * rowstride + width
+ * ((n_channels * bits_per_sample + 7) / 8);
+
+ if (expected_len != tmp_array->len) {
+ g_warning ("_notify_daemon_pixbuf_from_data_hint expected image "
+ "data to be of length %" G_GSIZE_FORMAT
+ " but got a " "length of %u", expected_len,
+ tmp_array->len);
+ return NULL;
+ }
+
+ data = (guchar *) g_memdup (tmp_array->data, tmp_array->len);
+ 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)
+{
+ GdkPixbuf *pixbuf = NULL;
+
+ if (!strncmp (path, "file://", 7) || *path == '/') {
+ if (!strncmp (path, "file://", 7)) {
+ path += 7;
+ }
+
+ /* Load file */
+ pixbuf = gdk_pixbuf_new_from_file_at_size (path, size, size, NULL);
+ } else {
+ /* 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);
+
+ gtk_icon_info_free (icon_info);
+ }
+
+ if (pixbuf == NULL) {
+ /* Well... maybe this is a file afterall. */
+ pixbuf = gdk_pixbuf_new_from_file_at_size (path, size, size, NULL);
+ }
+ }
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+nd_notification_load_image (NdNotification *notification,
+ int size)
+{
+ GValue *data;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = NULL;
+
+ if ((data = (GValue *) g_hash_table_lookup (notification->hints, "image_data"))) {
+ pixbuf = _notify_daemon_pixbuf_from_data_hint (data, size);
+ } else if ((data = (GValue *) g_hash_table_lookup (notification->hints, "image_path"))) {
+ if (G_VALUE_HOLDS_STRING (data)) {
+ const char *path = g_value_get_string (data);
+ pixbuf = _notify_daemon_pixbuf_from_path (path, size);
+ } else {
+ g_warning ("notify_daemon_notify_handler 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 = (GValue *) 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/src/nd-notification.h b/src/nd-notification.h
new file mode 100644
index 0000000..b2c4438
--- /dev/null
+++ b/src/nd-notification.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#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 char *app_name,
+ const char *icon,
+ const char *summary,
+ const char *body,
+ const char **actions,
+ GHashTable *hints,
+ int timeout);
+
+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);
+
+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/src/nd-queue.c b/src/nd-queue.c
new file mode 100644
index 0000000..2f2979b
--- /dev/null
+++ b/src/nd-queue.c
@@ -0,0 +1,1088 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#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-stack.h"
+
+#define ND_QUEUE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ND_TYPE_QUEUE, NdQueuePrivate))
+
+#define IMAGE_SIZE 48
+#define BODY_X_OFFSET (IMAGE_SIZE + 8)
+#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;
+ GtkWidget *dock;
+ GtkWidget *dock_scrolled_window;
+
+ NotifyScreen **screens;
+ int n_screens;
+
+ guint update_id;
+};
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void nd_queue_class_init (NdQueueClass *klass);
+static void nd_queue_init (NdQueue *queue);
+static void nd_queue_finalize (GObject *object);
+static void queue_update (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;
+ int screen_num;
+
+ screen_num = gdk_screen_get_number (screen);
+ nscreen = queue->priv->screens[screen_num];
+
+ nscreen->stacks[monitor_num] = nd_stack_new (screen,
+ monitor_num);
+}
+
+static void
+on_screen_monitors_changed (GdkScreen *screen,
+ NdQueue *queue)
+{
+ NotifyScreen *nscreen;
+ int screen_num;
+ int n_monitors;
+ int i;
+
+ screen_num = gdk_screen_get_number (screen);
+ nscreen = queue->priv->screens[screen_num];
+
+ 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 screen_num;
+ int i;
+
+ screen_num = gdk_screen_get_number (screen);
+ nscreen = queue->priv->screens[screen_num];
+
+ 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_screens (NdQueue *queue)
+{
+ GdkDisplay *display;
+ int i;
+
+ g_assert (queue->priv->screens == NULL);
+
+ display = gdk_display_get_default ();
+ queue->priv->n_screens = gdk_display_get_n_screens (display);
+
+ queue->priv->screens = g_new0 (NotifyScreen *, queue->priv->n_screens);
+
+ for (i = 0; i < queue->priv->n_screens; i++) {
+ GdkScreen *screen;
+ GdkWindow *gdkwindow;
+
+ screen = gdk_display_get_screen (display, i);
+ g_signal_connect (screen,
+ "monitors-changed",
+ G_CALLBACK (on_screen_monitors_changed),
+ queue);
+
+ queue->priv->screens[i] = g_new0 (NotifyScreen, 1);
+
+ queue->priv->screens[i]->workarea_atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WORKAREA", True);
+ gdkwindow = gdk_screen_get_root_window (screen);
+ gdk_window_add_filter (gdkwindow, (GdkFilterFunc) screen_xevent_filter, queue->priv->screens[i]);
+ gdk_window_set_events (gdkwindow, gdk_window_get_events (gdkwindow) | GDK_PROPERTY_CHANGE_MASK);
+
+ create_stacks_for_screen (queue, gdk_display_get_screen (display, i));
+ }
+}
+
+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
+popdown_dock (NdQueue *queue)
+{
+ GdkDisplay *display;
+
+ /* ungrab focus */
+ display = gtk_widget_get_display (queue->priv->dock);
+ gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
+ gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
+ gtk_grab_remove (queue->priv->dock);
+
+ /* hide again */
+ gtk_widget_hide (queue->priv->dock);
+}
+
+/* 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)
+{
+ if (was_grabbed != FALSE) {
+ return;
+ }
+
+ if (!gtk_widget_has_grab (queue->priv->dock)) {
+ return;
+ }
+
+ if (gtk_widget_is_ancestor (gtk_grab_get_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
+create_dock (NdQueue *queue)
+{
+ GtkWidget *frame;
+ GtkWidget *box;
+ GtkWidget *button;
+
+ queue->priv->dock = gtk_window_new (GTK_WINDOW_POPUP);
+ 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);
+#if 0
+ g_signal_connect (queue->priv->dock,
+ "button-press-event",
+ G_CALLBACK (on_dock_button_press),
+ queue);
+ 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_vbox_new (FALSE, 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"));
+ 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_screens (queue);
+}
+
+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);
+
+ g_hash_table_destroy (queue->priv->notifications);
+ g_queue_free (queue->priv->queue);
+
+ 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)
+{
+ GdkScreen *screen;
+ int x, y;
+ int screen_num;
+ int monitor_num;
+
+ gdk_display_get_pointer (gdk_display_get_default (),
+ &screen,
+ &x,
+ &y,
+ NULL);
+ screen_num = gdk_screen_get_number (screen);
+ monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
+
+ if (monitor_num >= queue->priv->screens[screen_num]->n_stacks) {
+ /* screw it - dump it on the last one we'll get
+ a monitors-changed signal soon enough*/
+ monitor_num = queue->priv->screens[screen_num]->n_stacks - 1;
+ }
+
+ return queue->priv->screens[screen_num]->stacks[monitor_num];
+}
+
+static void
+on_bubble_destroyed (NdBubble *bubble,
+ NdQueue *queue)
+{
+ g_debug ("Bubble destroyed");
+ 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 */
+
+ 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 gboolean
+on_activate_link (GtkLabel *label,
+ char *uri,
+ NdQueue *queue)
+{
+ char *escaped_uri;
+ char *cmd = NULL;
+
+ /* Somewhat of a hack.. */
+ //bubble->priv->url_clicked_lock = TRUE;
+
+ escaped_uri = g_shell_quote (uri);
+
+ if (g_find_program_in_path ("gvfs-open") != NULL) {
+ cmd = g_strdup_printf ("gvfs-open %s", escaped_uri);
+ } else if (g_find_program_in_path ("xdg-open") != NULL) {
+ cmd = g_strdup_printf ("xdg-open %s", escaped_uri);
+ } else if (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);
+
+ if (cmd != NULL) {
+ g_spawn_command_line_async (cmd, NULL);
+ g_free (cmd);
+ }
+
+ return TRUE;
+}
+
+static void
+on_close_button_clicked (GtkButton *button,
+ NdNotification *notification)
+{
+ nd_notification_close (notification, ND_NOTIFICATION_CLOSED_USER);
+}
+
+static void
+on_action_clicked (GtkButton *button,
+ GdkEventButton *event,
+ NdNotification *notification)
+{
+ const char *key = g_object_get_data (G_OBJECT (button), "_action_key");
+
+ nd_notification_action_invoked (notification,
+ key);
+}
+
+static GtkWidget *
+create_notification_action (NdQueue *queue,
+ NdNotification *notification,
+ const char *text,
+ const char *key)
+{
+ GtkWidget *label;
+ 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_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (button), hbox);
+
+ /* Try to be smart and find a suitable icon. */
+ buf = g_strdup_printf ("stock_%s", key);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (queue->priv->dock))),
+ buf,
+ 16,
+ GTK_ICON_LOOKUP_USE_BUILTIN,
+ NULL);
+ g_free (buf);
+
+ if (pixbuf != NULL) {
+ GtkWidget *image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.5);
+ }
+
+ label = gtk_label_new (NULL);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+ 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),
+ notification);
+ return button;
+}
+
+static gboolean
+on_button_release (GtkWidget *widget,
+ GdkEventButton *event,
+ NdNotification *notification)
+{
+ g_debug ("CLICK");
+ nd_notification_action_invoked (notification, "default");
+
+ return FALSE;
+}
+
+static GtkWidget *
+create_notification_box (NdQueue *queue,
+ NdNotification *n)
+{
+ GtkWidget *box;
+ GtkWidget *iconbox;
+ GtkWidget *icon;
+ GtkWidget *image;
+ GtkWidget *content_hbox;
+ GtkWidget *actions_box;
+ GtkWidget *vbox;
+ GtkWidget *summary_label;
+ GtkWidget *body_label;
+ GtkWidget *alignment;
+ GtkWidget *close_button;
+ AtkObject *atkobj;
+ GtkRcStyle *rcstyle;
+ char *str;
+ char *quoted;
+ GtkRequisition req;
+ int summary_width;
+ gboolean have_icon;
+ gboolean have_body;
+ gboolean have_actions;
+ GdkPixbuf *pixbuf;
+ char **actions;
+ int i;
+
+ box = gtk_hbox_new (FALSE, 6);
+ gtk_widget_add_events (box, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+
+ /* First row (icon, vbox, close) */
+ iconbox = gtk_alignment_new (0.5, 0, 0, 0);
+ gtk_widget_show (iconbox);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (iconbox),
+ 5, 0, 0, 0);
+ gtk_box_pack_start (GTK_BOX (box),
+ iconbox,
+ FALSE, FALSE, 0);
+ gtk_widget_set_size_request (iconbox, BODY_X_OFFSET, -1);
+
+ icon = gtk_image_new ();
+ gtk_widget_show (icon);
+ gtk_container_add (GTK_CONTAINER (iconbox), icon);
+
+ vbox = gtk_vbox_new (FALSE, 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 */
+ alignment = gtk_alignment_new (0.5, 0, 0, 0);
+ gtk_widget_show (alignment);
+ gtk_box_pack_start (GTK_BOX (box), alignment, FALSE, FALSE, 0);
+
+ close_button = gtk_button_new ();
+ gtk_widget_show (close_button);
+ gtk_container_add (GTK_CONTAINER (alignment), close_button);
+ 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),
+ n);
+
+ rcstyle = gtk_rc_style_new ();
+ rcstyle->xthickness = rcstyle->ythickness = 0;
+ gtk_widget_modify_style (close_button, rcstyle);
+ g_object_unref (rcstyle);
+
+ 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_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+ gtk_container_add (GTK_CONTAINER (close_button), image);
+
+ /* center vbox */
+ summary_label = gtk_label_new (NULL);
+ gtk_widget_show (summary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), summary_label, TRUE, TRUE, 0);
+ gtk_misc_set_alignment (GTK_MISC (summary_label), 0, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (summary_label), TRUE);
+
+ atkobj = gtk_widget_get_accessible (summary_label);
+ atk_object_set_description (atkobj, "Notification summary text.");
+
+ content_hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (content_hbox);
+ gtk_box_pack_start (GTK_BOX (vbox), content_hbox, FALSE, FALSE, 0);
+
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (content_hbox), vbox, TRUE, TRUE, 0);
+
+ body_label = gtk_label_new (NULL);
+ gtk_widget_show (body_label);
+ gtk_box_pack_start (GTK_BOX (vbox), body_label, TRUE, TRUE, 0);
+ gtk_misc_set_alignment (GTK_MISC (body_label), 0, 0);
+ gtk_label_set_line_wrap (GTK_LABEL (body_label), TRUE);
+ g_signal_connect (body_label,
+ "activate-link",
+ G_CALLBACK (on_activate_link),
+ queue);
+
+ atkobj = gtk_widget_get_accessible (body_label);
+ atk_object_set_description (atkobj, "Notification body text.");
+
+ alignment = gtk_alignment_new (1, 0.5, 0, 0);
+ gtk_widget_show (alignment);
+ gtk_box_pack_start (GTK_BOX (vbox), alignment, FALSE, TRUE, 0);
+
+ actions_box = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (actions_box);
+ gtk_container_add (GTK_CONTAINER (alignment), actions_box);
+
+ /* Add content */
+
+ have_icon = FALSE;
+ have_body = FALSE;
+ have_actions = FALSE;
+
+ /* image */
+ pixbuf = nd_notification_load_image (n, IMAGE_SIZE);
+ if (pixbuf != NULL) {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (icon), pixbuf);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ have_icon = TRUE;
+ }
+
+ /* summary */
+ quoted = g_markup_escape_text (nd_notification_get_summary (n), -1);
+ str = g_strdup_printf ("<b><big>%s</big></b>", quoted);
+ g_free (quoted);
+
+ gtk_label_set_markup (GTK_LABEL (summary_label), str);
+ g_free (str);
+
+ gtk_widget_size_request (close_button, &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 (summary_label,
+ summary_width,
+ -1);
+
+ /* body */
+ gtk_label_set_markup (GTK_LABEL (body_label), nd_notification_get_body (n));
+
+ if (str != NULL && *str != '\0') {
+ gtk_widget_set_size_request (body_label,
+ summary_width,
+ -1);
+ have_body = TRUE;
+ }
+
+ /* actions */
+ actions = nd_notification_get_actions (n);
+ 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 (queue,
+ n,
+ l,
+ actions[i]);
+ gtk_box_pack_start (GTK_BOX (actions_box), button, FALSE, FALSE, 0);
+
+ have_actions = TRUE;
+ }
+ }
+
+ g_signal_connect (box, "button-release-event", G_CALLBACK (on_button_release), n);
+
+ if (have_icon || have_body || have_actions) {
+ gtk_widget_show (content_hbox);
+ } else {
+ gtk_widget_hide (content_hbox);
+ }
+
+ return box;
+}
+
+static void
+update_dock (NdQueue *queue)
+{
+ GtkWidget *child;
+ GList *list;
+ GList *l;
+ int min_height;
+ int height;
+ int monitor_num;
+ GdkScreen *screen;
+ GdkRectangle area;
+
+ 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_vbox_new (FALSE, 6);
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (queue->priv->dock_scrolled_window),
+ child);
+ 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;
+ GtkWidget *hbox;
+ GtkWidget *sep;
+
+ hbox = create_notification_box (queue, n);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (child), hbox, FALSE, FALSE, 0);
+
+ sep = gtk_hseparator_new ();
+ gtk_widget_show (sep);
+ gtk_box_pack_start (GTK_BOX (child), sep, FALSE, FALSE, 0);
+ }
+ gtk_widget_show (child);
+ gtk_widget_get_preferred_height (child,
+ &min_height,
+ &height);
+
+ gtk_status_icon_get_geometry (GTK_STATUS_ICON (queue->priv->status_icon),
+ &screen,
+ &area,
+ NULL);
+ 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;
+
+ update_dock (queue);
+
+ res = gtk_status_icon_get_geometry (GTK_STATUS_ICON (queue->priv->status_icon),
+ &screen,
+ &area,
+ &orientation);
+ 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);
+
+ if (gdk_pointer_grab (gtk_widget_get_window (queue->priv->dock), TRUE,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK, NULL, NULL,
+ time)
+ != GDK_GRAB_SUCCESS) {
+ gtk_grab_remove (queue->priv->dock);
+ gtk_widget_hide (queue->priv->dock);
+ return FALSE;
+ }
+
+ if (gdk_keyboard_grab (gtk_widget_get_window (queue->priv->dock), TRUE, time) != GDK_GRAB_SUCCESS) {
+ display = gtk_widget_get_display (queue->priv->dock);
+ gdk_display_pointer_ungrab (display, time);
+ gtk_grab_remove (queue->priv->dock);
+ gtk_widget_hide (queue->priv->dock);
+ return FALSE;
+ }
+
+ gtk_widget_grab_focus (queue->priv->dock);
+
+ return TRUE;
+}
+
+static void
+on_status_icon_activate (GtkStatusIcon *status_icon,
+ NdQueue *queue)
+{
+ popup_dock (queue, GDK_CURRENT_TIME);
+}
+
+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)
+{
+ /* Show the status icon when their are stored notifications */
+ if (g_hash_table_size (queue->priv->notifications) > 0) {
+ if (queue->priv->status_icon == NULL) {
+ queue->priv->status_icon = gtk_status_icon_new_from_icon_name ("mail-message-new");
+ gtk_status_icon_set_title (GTK_STATUS_ICON (queue->priv->status_icon),
+ _("Notifications"));
+ g_signal_connect (queue->priv->status_icon,
+ "activate",
+ G_CALLBACK (on_status_icon_activate),
+ queue);
+ g_signal_connect (queue->priv->status_icon,
+ "notify::visible",
+ G_CALLBACK (on_status_icon_visible_notify),
+ queue);
+ }
+ gtk_status_icon_set_visible (queue->priv->status_icon, TRUE);
+
+ maybe_show_notification (queue);
+ } else {
+ if (queue->priv->status_icon != NULL) {
+ g_object_unref (queue->priv->status_icon);
+ queue->priv->status_icon = NULL;
+ }
+ }
+
+ 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);
+}
+
+void
+nd_queue_remove (NdQueue *queue,
+ guint id)
+{
+ g_return_if_fail (ND_IS_QUEUE (queue));
+ g_debug ("Removing id %u", id);
+ 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);
+}
+
+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));
+
+ /* 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/src/nd-queue.h b/src/nd-queue.h
new file mode 100644
index 0000000..876a2d8
--- /dev/null
+++ b/src/nd-queue.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#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 (NdQueue *queue,
+ guint id);
+
+G_END_DECLS
+
+#endif /* __ND_QUEUE_H */
diff --git a/src/daemon/stack.c b/src/nd-stack.c
similarity index 52%
rename from src/daemon/stack.c
rename to src/nd-stack.c
index 936edbe..27fca03 100644
--- a/src/daemon/stack.c
+++ b/src/nd-stack.c
@@ -1,12 +1,11 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
- * Copyright (C) 2006 Christian Hammond <chipx86 chipx86 com>
* 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 2, or (at your option)
- * any later version.
+ * the Free Software Foundation; either version 2 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
@@ -15,13 +14,15 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
*/
#include "config.h"
-#include "engines.h"
-#include "stack.h"
+
+#include <string.h>
+#include <strings.h>
+#include <glib.h>
#include <X11/Xproto.h>
#include <X11/Xlib.h>
@@ -29,27 +30,36 @@
#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 _NotifyStack
+struct NdStackPrivate
{
- NotifyDaemon *daemon;
- GdkScreen *screen;
- guint monitor;
- NotifyStackLocation location;
- GList *windows;
- guint update_id;
+ GdkScreen *screen;
+ guint monitor;
+ NdStackLocation location;
+ GList *bubbles;
+ guint update_id;
};
+static void nd_stack_class_init (NdStackClass *klass);
+static void nd_stack_init (NdStack *stack);
+static void nd_stack_finalize (GObject *object);
+
+G_DEFINE_TYPE (NdStack, nd_stack, G_TYPE_OBJECT)
+
GList *
-notify_stack_get_windows (NotifyStack *stack)
+nd_stack_get_bubbles (NdStack *stack)
{
- return stack->windows;
+ return stack->priv->bubbles;
}
static gboolean
-get_work_area (NotifyStack *stack,
+get_work_area (NdStack *stack,
GdkRectangle *rect)
{
Atom workarea;
@@ -63,22 +73,24 @@ get_work_area (NotifyStack *stack,
long *workareas;
int result;
int disp_screen;
+ Display *display;
- workarea = XInternAtom (GDK_DISPLAY (), "_NET_WORKAREA", True);
+ display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (stack->priv->screen));
+ workarea = XInternAtom (display, "_NET_WORKAREA", True);
- disp_screen = GDK_SCREEN_XNUMBER (stack->screen);
+ 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->screen);
- rect->height = gdk_screen_get_height (stack->screen);
+ 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 (GDK_DISPLAY (), disp_screen);
- result = XGetWindowProperty (GDK_DISPLAY (),
+ win = XRootWindow (display, disp_screen);
+ result = XGetWindowProperty (display,
win,
workarea,
0,
@@ -111,7 +123,7 @@ get_work_area (NotifyStack *stack,
}
static void
-get_origin_coordinates (NotifyStackLocation stack_location,
+get_origin_coordinates (NdStackLocation stack_location,
GdkRectangle *workarea,
gint *x,
gint *y,
@@ -121,24 +133,24 @@ get_origin_coordinates (NotifyStackLocation stack_location,
gint height)
{
switch (stack_location) {
- case NOTIFY_STACK_LOCATION_TOP_LEFT:
+ case ND_STACK_LOCATION_TOP_LEFT:
*x = workarea->x;
*y = workarea->y;
*shifty = height;
break;
- case NOTIFY_STACK_LOCATION_TOP_RIGHT:
+ case ND_STACK_LOCATION_TOP_RIGHT:
*x = workarea->x + workarea->width - width;
*y = workarea->y;
*shifty = height;
break;
- case NOTIFY_STACK_LOCATION_BOTTOM_LEFT:
+ case ND_STACK_LOCATION_BOTTOM_LEFT:
*x = workarea->x;
*y = workarea->y + workarea->height - height;
break;
- case NOTIFY_STACK_LOCATION_BOTTOM_RIGHT:
+ case ND_STACK_LOCATION_BOTTOM_RIGHT:
*x = workarea->x + workarea->width - width;
*y = workarea->y + workarea->height - height;
break;
@@ -149,7 +161,7 @@ get_origin_coordinates (NotifyStackLocation stack_location,
}
static void
-translate_coordinates (NotifyStackLocation stack_location,
+translate_coordinates (NdStackLocation stack_location,
GdkRectangle *workarea,
gint *x,
gint *y,
@@ -159,24 +171,24 @@ translate_coordinates (NotifyStackLocation stack_location,
gint height)
{
switch (stack_location) {
- case NOTIFY_STACK_LOCATION_TOP_LEFT:
+ case ND_STACK_LOCATION_TOP_LEFT:
*x = workarea->x;
*y += *shifty;
*shifty = height;
break;
- case NOTIFY_STACK_LOCATION_TOP_RIGHT:
+ case ND_STACK_LOCATION_TOP_RIGHT:
*x = workarea->x + workarea->width - width;
*y += *shifty;
*shifty = height;
break;
- case NOTIFY_STACK_LOCATION_BOTTOM_LEFT:
+ case ND_STACK_LOCATION_BOTTOM_LEFT:
*x = workarea->x;
*y -= height;
break;
- case NOTIFY_STACK_LOCATION_BOTTOM_RIGHT:
+ case ND_STACK_LOCATION_BOTTOM_RIGHT:
*x = workarea->x + workarea->width - width;
*y -= height;
break;
@@ -186,48 +198,70 @@ translate_coordinates (NotifyStackLocation stack_location,
}
}
-NotifyStack *
-notify_stack_new (NotifyDaemon *daemon,
- GdkScreen *screen,
- guint monitor,
- NotifyStackLocation location)
+static void
+nd_stack_class_init (NdStackClass *klass)
{
- NotifyStack *stack;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
- g_assert (daemon != NULL);
- g_assert (screen != NULL && GDK_IS_SCREEN (screen));
- g_assert (monitor < (guint)gdk_screen_get_n_monitors (screen));
- g_assert (location != NOTIFY_STACK_LOCATION_UNKNOWN);
+ object_class->finalize = nd_stack_finalize;
- stack = g_new0 (NotifyStack, 1);
- stack->daemon = daemon;
- stack->screen = screen;
- stack->monitor = monitor;
- stack->location = location;
+ g_type_class_add_private (klass, sizeof (NdStackPrivate));
+}
- return stack;
+static void
+nd_stack_init (NdStack *stack)
+{
+ stack->priv = ND_STACK_GET_PRIVATE (stack);
+ stack->priv->location = ND_STACK_LOCATION_DEFAULT;
}
-void
-notify_stack_destroy (NotifyStack *stack)
+static void
+nd_stack_finalize (GObject *object)
{
- g_assert (stack != NULL);
+ NdStack *stack;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (ND_IS_STACK (object));
- if (stack->update_id != 0) {
- g_source_remove (stack->update_id);
+ 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->windows);
- g_free (stack);
+ g_list_free (stack->priv->bubbles);
+
+ G_OBJECT_CLASS (nd_stack_parent_class)->finalize (object);
}
void
-notify_stack_set_location (NotifyStack *stack,
- NotifyStackLocation location)
+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)
{
- stack->location = location;
+ 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)
{
@@ -243,13 +277,13 @@ add_padding_to_rect (GdkRectangle *rect)
}
static void
-notify_stack_shift_notifications (NotifyStack *stack,
- GtkWindow *nw,
- GList **nw_l,
- gint init_width,
- gint init_height,
- gint *nw_x,
- gint *nw_y)
+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;
@@ -262,17 +296,17 @@ notify_stack_shift_notifications (NotifyStack *stack,
int n_wins;
get_work_area (stack, &workarea);
- gdk_screen_get_monitor_geometry (stack->screen,
- stack->monitor,
+ 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->windows);
+ n_wins = g_list_length (stack->priv->bubbles);
positions = g_new0 (GdkRectangle, n_wins);
- get_origin_coordinates (stack->location,
+ get_origin_coordinates (stack->priv->location,
&workarea,
&x, &y,
&shiftx,
@@ -286,14 +320,14 @@ notify_stack_shift_notifications (NotifyStack *stack,
if (nw_y != NULL)
*nw_y = y;
- for (i = 0, l = stack->windows; l != NULL; i++, l = l->next) {
- GtkWindow *nw2 = GTK_WINDOW (l->data);
+ for (i = 0, l = stack->priv->bubbles; l != NULL; i++, l = l->next) {
+ NdBubble *nw2 = ND_BUBBLE (l->data);
GtkRequisition req;
- if (nw == NULL || nw2 != nw) {
+ if (bubble == NULL || nw2 != bubble) {
gtk_widget_size_request (GTK_WIDGET (nw2), &req);
- translate_coordinates (stack->location,
+ translate_coordinates (stack->priv->location,
&workarea,
&x,
&y,
@@ -312,11 +346,11 @@ notify_stack_shift_notifications (NotifyStack *stack,
/* move bubbles at the bottom of the stack first
to avoid overlapping */
- for (i = n_wins - 1, l = g_list_last (stack->windows); l != NULL; i--, l = l->prev) {
- GtkWindow *nw2 = GTK_WINDOW (l->data);
+ 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 (nw == NULL || nw2 != nw) {
- theme_move_notification (nw2, positions[i].x, positions[i].y);
+ if (bubble == NULL || nw2 != bubble) {
+ gtk_window_move (GTK_WINDOW (nw2), positions[i].x, positions[i].y);
}
}
@@ -324,80 +358,81 @@ notify_stack_shift_notifications (NotifyStack *stack,
}
static void
-update_position (NotifyStack *stack)
+update_position (NdStack *stack)
{
- notify_stack_shift_notifications (stack,
- NULL, /* window */
- NULL, /* list pointer */
- 0, /* init width */
- 0, /* init height */
- NULL, /* out window x */
- NULL); /* out window y */
+ 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 (NotifyStack *stack)
+update_position_idle (NdStack *stack)
{
update_position (stack);
- stack->update_id = 0;
+ stack->priv->update_id = 0;
return FALSE;
}
void
-notify_stack_queue_update_position (NotifyStack *stack)
+nd_stack_queue_update_position (NdStack *stack)
{
- if (stack->update_id != 0) {
+ if (stack->priv->update_id != 0) {
return;
}
- stack->update_id = g_idle_add ((GSourceFunc) update_position_idle, stack);
+ stack->priv->update_id = g_idle_add ((GSourceFunc) update_position_idle, stack);
}
void
-notify_stack_add_window (NotifyStack *stack,
- GtkWindow *nw,
- gboolean new_notification)
+nd_stack_add_bubble (NdStack *stack,
+ NdBubble *bubble,
+ gboolean new_notification)
{
GtkRequisition req;
- gint x, y;
-
- gtk_widget_size_request (GTK_WIDGET (nw), &req);
- notify_stack_shift_notifications (stack,
- nw,
- NULL,
- req.width,
- req.height + NOTIFY_STACK_SPACING,
- &x,
- &y);
- theme_move_notification (nw, x, y);
+ int x, y;
+
+ gtk_widget_size_request (GTK_WIDGET (bubble), &req);
+ nd_stack_shift_notifications (stack,
+ bubble,
+ NULL,
+ req.width,
+ req.height + NOTIFY_STACK_SPACING,
+ &x,
+ &y);
+ gtk_widget_show (bubble);
+ gtk_window_move (GTK_WINDOW (bubble), x, y);
if (new_notification) {
- g_signal_connect_swapped (G_OBJECT (nw),
+ g_signal_connect_swapped (G_OBJECT (bubble),
"destroy",
- G_CALLBACK (notify_stack_remove_window),
+ G_CALLBACK (nd_stack_remove_bubble),
stack);
- stack->windows = g_list_prepend (stack->windows, nw);
+ stack->priv->bubbles = g_list_prepend (stack->priv->bubbles, bubble);
}
}
void
-notify_stack_remove_window (NotifyStack *stack,
- GtkWindow *nw)
+nd_stack_remove_bubble (NdStack *stack,
+ NdBubble *bubble)
{
GList *remove_l = NULL;
- notify_stack_shift_notifications (stack,
- nw,
- &remove_l,
- 0,
- 0,
- NULL,
- NULL);
+ nd_stack_shift_notifications (stack,
+ bubble,
+ &remove_l,
+ 0,
+ 0,
+ NULL,
+ NULL);
if (remove_l != NULL)
- stack->windows = g_list_delete_link (stack->windows, remove_l);
+ stack->priv->bubbles = g_list_delete_link (stack->priv->bubbles, remove_l);
- if (GTK_WIDGET_REALIZED (GTK_WIDGET (nw)))
- gtk_widget_unrealize (GTK_WIDGET (nw));
+ if (gtk_widget_get_realized (GTK_WIDGET (bubble)))
+ gtk_widget_unrealize (GTK_WIDGET (bubble));
}
diff --git a/src/nd-stack.h b/src/nd-stack.h
new file mode 100644
index 0000000..4f1dedf
--- /dev/null
+++ b/src/nd-stack.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#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);
+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/src/daemon/notificationdaemon.xml b/src/notificationdaemon.xml
similarity index 100%
rename from src/daemon/notificationdaemon.xml
rename to src/notificationdaemon.xml
diff --git a/src/daemon/sound.c b/src/sound.c
similarity index 100%
rename from src/daemon/sound.c
rename to src/sound.c
diff --git a/src/daemon/sound.h b/src/sound.h
similarity index 100%
rename from src/daemon/sound.h
rename to src/sound.h
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]