Gdk unexpected window destruction patch.

    This file contains a patch to Gdk which addresses some of the
problems with unexpected window destruction that can arise when a
Bonobo container dies.

    Bonobo uses the GtkPlug system for remote window embedding.  This
is the same system used by the Gnome Panel and by the Gnome Control
Center.  It has some robustness deficiencies.

    In Bonobo, it is extremely important that each individual
component continue to execute when its container dies.  A given
component server process may be displaying embedded views on any
number of containers, and the unexpected death of one of those
containers should not affect the others.

    One of the problems we encountered with using GtkPlug in Bonobo
was that if a container died, it took all its embedded components to
the grave with it.  The reasons for this were severalfold.

    The way GtkPlug works is that the container creates a window and
passes the XID of the window to the embedded component.  The component
then creates the GtkPlug widget using the supplied XID.  GtkPlug
reparents the foreign, container-created window into its window
hierarchy.  The component's embedded widgets are all created as
descendents of the GtkPlug, and the corresponding X Windows are
therefore descendents of the GtkPlug's foreign, reparented window in
the window hierarchy.

    When a container dies and its connection to the X server is
severed, the X server dutifully destroys all the container's X
windows, and all their descendents.  So what happens to the embedded
component is that all its windows begin to die unexpectedly.  And in
seemingly random order.

    You'd think that the X server would destroy the windows in
top-down or bottom-up order, but it doesn't seem to do either of
those.  For the purposes of this code, the order is random.

    So the component's widgets are randomly picked off by the maniacal
sadism of the X server, and the process dies a violent and grotesque
death.  X laughing.  X laughing.

    That is problem number one, and it is solved by the Gdk patch
attached at the end of this file.  What the patch does is fairly
simple: when an unexpected DestroyNotify arrives, Gdk walks up the
GdkWindow hierarchy, looking for a foreign window.  If it finds one,
it rewrites the event to occur on that foreign window.  The foreign
window corresponds to the GtkPlug, and so the GtkPlug receives a
"destroy_event" signal.  The Bonobo component can then gracefully deal
with the situation by killing off the GtkPlug and its children.  ANd
its children's children.  And so on, world without end.

    Problem number two (2) is that the component may have had some
drawing queued on one of those now-defunct windows.  And those draw
operations will result in BadDrawable X errors, terminating the
component.  The source-level disfigurement required to fix this has
been relegated to Bonobo, which basically just ignores all BadDrawable
X errors for now (see bonobo/gnome-main.c).  Suggestions welcome.

    It would be nice if this patch could make it into the Gtk 1.2
branch.  It doesn't affect Gtk's behavior under normal circumstances,
and makes it deal infinitely better with the pathological case I've
outlined above.  So consider this my obsequious appeal that this patch
be applied to Gtk 1.2.

Nat (

Index: gdk/gdkevents.c
RCS file: /cvs/gnome/gtk+/gdk/gdkevents.c,v
retrieving revision
diff -u -r1.20.2.5 gdkevents.c
--- gdk/gdkevents.c	1999/06/25 23:23:04
+++ gdk/gdkevents.c	1999/08/19 00:47:51
@@ -1622,7 +1622,8 @@
       return_val = window_private && !window_private->destroyed;
       if(window && window_private->xwindow != GDK_ROOT_WINDOW())
-	gdk_window_destroy_notify (window);
+	gdk_window_destroy_notify (window, event);
     case UnmapNotify:
Index: gdk/gdkprivate.h
RCS file: /cvs/gnome/gtk+/gdk/gdkprivate.h,v
retrieving revision 1.36
diff -u -r1.36 gdkprivate.h
--- gdk/gdkprivate.h	1999/02/24 07:33:09	1.36
+++ gdk/gdkprivate.h	1999/08/19 00:47:51
@@ -240,7 +240,7 @@
 GdkVisual*   gdk_visual_lookup	 (Visual   *xvisual);
 void gdk_window_add_colormap_windows (GdkWindow *window);
-void gdk_window_destroy_notify	     (GdkWindow *window);
+void gdk_window_destroy_notify	     (GdkWindow *window, GdkEvent *event);
 void	 gdk_xid_table_insert (XID	*xid,
			       gpointer	 data);
Index: gdk/gdkwindow.c
RCS file: /cvs/gnome/gtk+/gdk/gdkwindow.c,v
retrieving revision
diff -u -r1.80.2.4 gdkwindow.c
--- gdk/gdkwindow.c	1999/05/06 03:12:08
+++ gdk/gdkwindow.c	1999/08/19 00:47:57
@@ -689,7 +689,7 @@
 /* This function is called when the XWindow is really gone.  */
-gdk_window_destroy_notify (GdkWindow *window)
+gdk_window_destroy_notify (GdkWindow *window, GdkEvent *event)
   GdkWindowPrivate *private;
@@ -701,8 +701,33 @@
       if (private->window_type == GDK_WINDOW_FOREIGN)
	gdk_window_internal_destroy (window, FALSE, FALSE);
-      else
+      else {
+	GdkWindowPrivate *parent;
	g_warning ("GdkWindow %#lx unexpectedly destroyed", private->xwindow);
+	/*
+	 * A window has been unexpectedly destroyed by the X server.
+	 *
+	 * In the case where a foreign, reparented window (such as is
+	 * used by GtkPlug), the X server will recursively destroy
+	 * all of that window's subwindows.  In what is effectively
+	 * a random order.  So what we do here is to walk up the
+	 * window tree until we encounter the first foreign window,
+	 * and rewrite the event to occur on that window.  That way,
+	 * the first widget to be destroyed will be the GtkPlug,
+	 * and its subwidgets can be killed politely.
+	 *
+	 * If there is no foreign ancestral window, we don't rewrite
+	 * the event.
+	 */
+	parent = private;
+	while (parent != NULL && parent->window_type != GDK_WINDOW_FOREIGN)
+	  parent = parent->parent;
+	if (parent != NULL)
+	  event->any.window = (GdkWindow *) parent;
+      }
   gdk_xid_table_remove (private->xwindow);
cvs server: Diffing gtk
Index: gtk/gtkplug.c
RCS file: /cvs/gnome/gtk+/gtk/gtkplug.c,v
retrieving revision 1.6
diff -u -r1.6 gtkplug.c
--- gtk/gtkplug.c	1999/02/24 07:35:47	1.6
+++ gtk/gtkplug.c	1999/08/19 00:48:03
@@ -105,6 +105,7 @@
   if (plug->socket_window == NULL)
       plug->socket_window = gdk_window_foreign_new (socket_id);
+      gdk_window_set_user_data (plug->socket_window, plug);
       plug->same_app = FALSE;
Index: gtk/gtksocket.c
RCS file: /cvs/gnome/gtk+/gtk/gtksocket.c,v
retrieving revision
diff -u -r1.8.2.1 gtksocket.c
--- gtk/gtksocket.c	1999/07/23 00:36:33
+++ gtk/gtksocket.c	1999/08/19 00:48:06
@@ -670,7 +670,7 @@
	    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (socket));
	    if (toplevel && GTK_IS_WINDOW (toplevel))
	      gtk_window_remove_embedded_xid (GTK_WINDOW (toplevel), xdwe->window);
-	    gdk_window_destroy_notify (socket->plug_window);
+	    gdk_window_destroy_notify (socket->plug_window, event);
	    gtk_widget_destroy (widget);
	    socket->plug_window = NULL;

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]