[Galago-commits] r3012 - in trunk/notification-daemon: . src/daemon

galago-commits at freedesktop.org galago-commits at freedesktop.org
Thu Sep 25 18:25:58 PDT 2008


Author: chipx86
Date: 2008-09-25 18:25:57 -0700 (Thu, 25 Sep 2008)
New Revision: 3012

Modified:
   trunk/notification-daemon/ChangeLog
   trunk/notification-daemon/NEWS
   trunk/notification-daemon/src/daemon/daemon.c
Log:
Added better support for attaching context notifications to an icon on the system tray, even when it moves. Patch by Colin Walters.


Modified: trunk/notification-daemon/ChangeLog
===================================================================
--- trunk/notification-daemon/ChangeLog	2008-09-26 01:12:47 UTC (rev 3011)
+++ trunk/notification-daemon/ChangeLog	2008-09-26 01:25:57 UTC (rev 3012)
@@ -1,3 +1,10 @@
+Thu Sep 25 18:25:36 PDT 2008  Christian Hammond <chipx86 at chipx86.com>
+
+	* src/daemon/daemon.c:
+	* NEWS:
+	  - Added better support for attaching context notifications to an icon on
+	    the system tray, even when it moves. Patch by Colin Walters.
+
 Tue Mar 18 21:39:53 PDT 2008  Christian Hammond <chipx86 at chipx86.com>
 
 	* src/daemon/daemon.c:

Modified: trunk/notification-daemon/NEWS
===================================================================
--- trunk/notification-daemon/NEWS	2008-09-26 01:12:47 UTC (rev 3011)
+++ trunk/notification-daemon/NEWS	2008-09-26 01:25:57 UTC (rev 3012)
@@ -15,6 +15,8 @@
 	* Added a control panel applet for controlling such things as the
 	  notification theme and popup positions. Patch by John Wendell.
 	  (Ticket #126)
+	* Added better support for attaching context notifications to an icon on
+	  the system tray, even when it moves. Patch by Colin Walters.
 	* Added an Arabic translation. Patch by Djihed Afifi. (Ticket #131)
 	* Added an Italian translation. Patch by Luca Ferretti.
 	* Fixed a bug where notifications weren't displayed if a fullscreen

Modified: trunk/notification-daemon/src/daemon/daemon.c
===================================================================
--- trunk/notification-daemon/src/daemon/daemon.c	2008-09-26 01:12:47 UTC (rev 3011)
+++ trunk/notification-daemon/src/daemon/daemon.c	2008-09-26 01:25:57 UTC (rev 3012)
@@ -83,13 +83,15 @@
 	gboolean paused;
 	guint id;
 	GtkWindow *nw;
-
+	Window src_window_xid;
 } NotifyTimeout;
 
 struct _NotifyDaemonPrivate
 {
 	guint next_id;
 	guint timeout_source;
+	GHashTable *idle_reposition_notify_ids;
+	GHashTable *monitored_window_hash;
 	GHashTable *notification_hash;
 	gboolean url_clicked_lock;
 	NotifyStack **stacks;
@@ -118,9 +120,17 @@
 static void _close_notification(NotifyDaemon *daemon, guint id,
 								gboolean hide_notification,
 								NotifydClosedReason reason);
+static GdkFilterReturn _notify_x11_filter(GdkXEvent *xevent,
+										  GdkEvent *event,
+										  gpointer user_data);
 static void _emit_closed_signal(GtkWindow *nw, NotifydClosedReason reason);
 static void _action_invoked_cb(GtkWindow *nw, const char *key);
 static NotifyStackLocation get_stack_location_from_string(const char *slocation);
+static void sync_notification_position(NotifyDaemon *daemon, GtkWindow *nw,
+									   Window source);
+static void monitor_notification_source_windows(NotifyDaemon *daemon,
+												NotifyTimeout *nt,
+												Window source);
 
 G_DEFINE_TYPE(NotifyDaemon, notify_daemon, G_TYPE_OBJECT);
 
@@ -167,6 +177,10 @@
 	daemon->priv->stacks_size = gdk_screen_get_n_monitors(screen);
 	daemon->priv->stacks = g_new0(NotifyStack *, daemon->priv->stacks_size);
 
+	daemon->priv->idle_reposition_notify_ids = g_hash_table_new(NULL, NULL);
+	daemon->priv->monitored_window_hash = g_hash_table_new(NULL, NULL);
+	gdk_window_add_filter(NULL, _notify_x11_filter, daemon);
+
 	for (i = 0; i < daemon->priv->stacks_size; i++)
 	{
 		daemon->priv->stacks[i] = notify_stack_new(daemon, screen,
@@ -184,6 +198,8 @@
 	NotifyDaemon *daemon       = NOTIFY_DAEMON(object);
 	GObjectClass *parent_class = G_OBJECT_CLASS(notify_daemon_parent_class);
 
+	g_hash_table_destroy(daemon->priv->monitored_window_hash);
+	g_hash_table_destroy(daemon->priv->idle_reposition_notify_ids);
 	g_hash_table_destroy(daemon->priv->notification_hash);
 	g_free(daemon->priv);
 
@@ -295,7 +311,110 @@
 						NOTIFYD_CLOSED_EXPIRED);
 }
 
+typedef struct
+{
+	NotifyDaemon *daemon;
+	gint id;
+} IdleRepositionData;
+
+static gboolean
+idle_reposition_notification(gpointer datap)
+{
+	IdleRepositionData *data = (IdleRepositionData *)datap;
+	NotifyDaemon *daemon = data->daemon;
+	NotifyTimeout *nt;
+	gint notify_id;
+
+	notify_id = data->id;
+
+	/* Look up the timeout, if it's completed we don't need to do anything */
+	nt = (NotifyTimeout *)g_hash_table_lookup(daemon->priv->notification_hash,
+											  &notify_id);
+	if (nt != NULL) {
+		sync_notification_position(daemon, nt->nw, nt->src_window_xid);
+	}
+
+	g_hash_table_remove(daemon->priv->idle_reposition_notify_ids,
+						GINT_TO_POINTER(notify_id));
+	g_object_unref(daemon);
+	g_free(data);
+
+	return FALSE;
+}
+
 static void
+_queue_idle_reposition_notification(NotifyDaemon *daemon, gint notify_id)
+{
+	IdleRepositionData *data;
+	gpointer orig_key;
+	gpointer value;
+	guint idle_id;
+
+	/* Do we already have an idle update pending? */
+	if (g_hash_table_lookup_extended(daemon->priv->idle_reposition_notify_ids,
+					 GINT_TO_POINTER(notify_id), &orig_key, &value))
+		return;
+
+	data = g_new0(IdleRepositionData, 1);
+	data->daemon = g_object_ref(daemon);
+	data->id = notify_id;
+
+	/* We do this as a short timeout to avoid repositioning spam */
+	idle_id = g_timeout_add_full(G_PRIORITY_LOW, 50,
+								 idle_reposition_notification, data, NULL);
+	g_hash_table_insert(daemon->priv->idle_reposition_notify_ids,
+						GINT_TO_POINTER(notify_id), GUINT_TO_POINTER(idle_id));
+}
+
+static GdkFilterReturn
+_notify_x11_filter(GdkXEvent *xevent,
+				   GdkEvent *event,
+				   gpointer user_data)
+{
+	NotifyDaemon *daemon = NOTIFY_DAEMON(user_data);
+	XEvent *xev = (XEvent *)xevent;
+	gpointer orig_key;
+	gpointer value;
+	gint notify_id;
+	NotifyTimeout *nt;
+
+	if (xev->xany.type == DestroyNotify)
+	{
+		g_hash_table_remove(daemon->priv->monitored_window_hash,
+							GUINT_TO_POINTER(xev->xany.window));
+		return GDK_FILTER_CONTINUE;
+	}
+
+	if (!g_hash_table_lookup_extended(daemon->priv->monitored_window_hash,
+					 GUINT_TO_POINTER(xev->xany.window), &orig_key, &value))
+		return GDK_FILTER_CONTINUE;
+
+	notify_id = GPOINTER_TO_INT(value);
+
+	if (xev->xany.type == ConfigureNotify || xev->xany.type == MapNotify)
+	{
+		_queue_idle_reposition_notification(daemon, notify_id);
+	}
+	else if (xev->xany.type == ReparentNotify)
+	{
+		nt = (NotifyTimeout *)g_hash_table_lookup(
+			daemon->priv->notification_hash, &notify_id);
+
+		if (nt == NULL)
+			return GDK_FILTER_CONTINUE;
+
+		/*
+		 * If the window got reparented, we need to start monitoring the
+		 * new parents.
+		 */
+		monitor_notification_source_windows(daemon, nt, nt->src_window_xid);
+		sync_notification_position(daemon, nt->nw, nt->src_window_xid);
+	}
+
+	return GDK_FILTER_CONTINUE;
+}
+
+static void
 _mouse_entered_cb(GtkWindow *nw, GdkEventCrossing *event, NotifyDaemon *daemon)
 {
 	NotifyTimeout *nt;
@@ -436,7 +555,7 @@
 	}
 }
 
-static guint
+static NotifyTimeout *
 _store_notification(NotifyDaemon *daemon, GtkWindow *nw, int timeout)
 {
 	NotifyDaemonPrivate *priv = daemon->priv;
@@ -466,7 +585,7 @@
 	g_hash_table_insert(priv->notification_hash,
 						g_memdup(&id, sizeof(guint)), nt);
 
-	return id;
+	return nt;
 }
 
 static gboolean
@@ -634,7 +753,8 @@
 	if (expected_len != tmp_array->len)
 	{
 		g_warning("_notify_daemon_process_icon_data expected image "
-				  "data to be of length %i but got a length of %i",
+				  "data to be of length %" G_GSIZE_FORMAT " but got a "
+				  "length of %u",
 				  expected_len, tmp_array->len);
 		return FALSE;
 	}
@@ -822,6 +942,102 @@
 	return FALSE;
 }
 
+static Window
+get_window_parent(Display *display,
+				  Window window,
+				  Window *root)
+{
+	Window parent;
+	Window *children = NULL;
+	guint nchildren;
+	gboolean result;
+
+	gdk_error_trap_push();
+	result = XQueryTree(display, window, root, &parent, &children, &nchildren);
+	if (gdk_error_trap_pop() || !result)
+		return None;
+
+	if (children)
+		XFree(children);
+
+	return parent;
+}
+
+/*
+ * Recurse over X Window and parents, up to root, and start watching them
+ * for position changes.
+ */
+static void
+monitor_notification_source_windows(NotifyDaemon *daemon,
+									NotifyTimeout *nt,
+									Window source)
+{
+	Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+	Window root = None;
+	Window parent;
+
+	/* Store the window in the timeout */
+	g_assert(nt != NULL);
+	nt->src_window_xid = source;
+
+	for (parent = get_window_parent(display, source, &root);
+		 parent != None && root != parent;
+		 parent = get_window_parent(display, parent, &root)) {
+
+		XSelectInput(display, parent, StructureNotifyMask);
+		g_hash_table_insert(daemon->priv->monitored_window_hash,
+							GUINT_TO_POINTER(parent), GINT_TO_POINTER(nt->id));
+	}
+}
+
+/* Use a source X Window ID to reposition a notification. */
+static void
+sync_notification_position(NotifyDaemon *daemon,
+						   GtkWindow *nw,
+						   Window source)
+{
+	Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+	Status result;
+	Window root;
+	Window child;
+	int x, y;
+	unsigned int width, height;
+	unsigned int border_width, depth;
+
+	gdk_error_trap_push();
+
+	/* Get the root for this window */
+	result = XGetGeometry(display, source, &root, &x, &y,
+						  &width, &height, &border_width, &depth);
+	if (gdk_error_trap_pop() || !result)
+		return;
+
+	/*
+	 * Now calculate the offset coordinates for the source window from
+	 * the root.
+	 */
+	gdk_error_trap_push ();
+	result = XTranslateCoordinates(display, source, root, 0, 0,
+								   &x, &y, &child);
+	if (gdk_error_trap_pop() || !result)
+		return;
+
+	x += width  / 2;
+	y += height / 2;
+
+	theme_set_notification_arrow(nw, TRUE, x, y);
+	theme_move_notification(nw, x, y);
+	theme_show_notification(nw);
+
+	/*
+	 * We need to manually queue a draw here as the default theme recalculates
+	 * its position in the draw handler and moves the window (which seems
+	 * fairly broken), so just calling move/show above isn't enough to cause
+	 * its position to be calculated.
+	 */
+	gtk_widget_queue_draw(GTK_WIDGET(nw));
+}
+
 GQuark
 notify_daemon_error_quark(void)
 {
@@ -852,6 +1068,7 @@
 	gboolean new_notification = FALSE;
 	gint x = 0;
 	gint y = 0;
+	Window window_xid = None;
 	guint return_id;
 	gchar *sender;
 	gchar *sound_file = NULL;
@@ -897,8 +1114,13 @@
 	 *XXX This needs to handle file URIs and all that.
 	 */
 
+
+	if ((data = (GValue *)g_hash_table_lookup(hints, "window-xid")) != NULL)
+	{
+		window_xid = (Window)g_value_get_uint(data);
+	}
 	/* deal with x, and y hints */
-	if ((data = (GValue *)g_hash_table_lookup(hints, "x")) != NULL)
+	else if ((data = (GValue *)g_hash_table_lookup(hints, "x")) != NULL)
 	{
 		x = g_value_get_int(data);
 
@@ -1044,9 +1266,17 @@
 		}
 	}
 
-	if (use_pos_data)
+
+	if (window_xid != None)
 	{
 		/*
+		 * Do nothing here if we were passed an XID; we'll call
+		 * sync_notification_position later.
+		 */
+	}
+	else if (use_pos_data)
+	{
+		/*
 		 * Typically, the theme engine will set its own position based on
 		 * the arrow X, Y hints. However, in case, move the notification to
 		 * that position.
@@ -1070,6 +1300,25 @@
 		notify_stack_add_window(priv->stacks[monitor], nw, new_notification);
 	}
 
+	if (id == 0)
+	{
+		nt = _store_notification(daemon, nw, timeout);
+		return_id = nt->id;
+	}
+	else
+		return_id = id;
+
+	/*
+	 * If we have a source Window XID, start monitoring the tree
+	 * for changes, and reposition the window based on the source
+	 * window.  We need to do this after return_id is calculated.
+	 */
+	if (window_xid != None)
+	{
+		monitor_notification_source_windows(daemon, nt, window_xid);
+		sync_notification_position(daemon, nw, window_xid);
+	}
+
 	if (!screensaver_active(GTK_WIDGET(nw)) &&
 		!fullscreen_window_exists(GTK_WIDGET(nw)))
 	{
@@ -1080,8 +1329,6 @@
 
 	g_free(sound_file);
 
-	return_id = (id == 0 ? _store_notification(daemon, nw, timeout) : id);
-
 #if CHECK_DBUS_VERSION(0, 60)
 	sender = dbus_g_method_get_sender(context);
 #else



More information about the galago-commits mailing list