CVS: rdesktop xwin.c,1.229,1.230

Peter Åstrand <[email protected]> Wed, 26 Mar 2008 09:44:57 -0700
Newsgroups gmane.network.rdesktop.cvs
Message-ID <[email protected]>
Update of /cvsroot/rdesktop/rdesktop
In directory sc8-pr-cvs9.sourceforge.net:/tmp/cvs-serv20641

Modified Files:
	xwin.c 
Log Message:
Re-worked the support for SeamlessRDP window stacking:

* Since many window managers cannot properly restack a window between
two other windows, we need to check for this at startup.

* A new utility function, sw_wait_configurenotify, is used to wait for
the WM to process our restacking request. 

* We are using XReconfigureWMWindow instead of XRestackWindows and
XRaiseWindow, to easier meet the demands of ICCCM. Restacking between
is only done if the WM is not broken, though. 

* The error handler does not ignore BadMatch from ConfigureWindow
requests any longer. I haven't found any WM that gives BadMatch for
XReconfigureWMWindow.

* The test cases has been updated to test more stacking cases. 


A somewhat related bug fix wrt focus handling is also included, which
prevents FOCUS messages when reverting focus from a destroyed window




Index: xwin.c
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/xwin.c,v
retrieving revision 1.229
retrieving revision 1.230
diff -C2 -d -r1.229 -r1.230
*** xwin.c	14 Mar 2008 07:39:38 -0000	1.229
--- xwin.c	26 Mar 2008 16:44:55 -0000	1.230
***************
*** 87,90 ****
--- 87,91 ----
  static RD_BOOL g_seamless_active = False;	/* We are currently in seamless mode */
  static RD_BOOL g_seamless_hidden = False;	/* Desktop is hidden on server */
+ static RD_BOOL g_seamless_broken_restack = False;	/* WM does not properly restack */
  extern RD_BOOL g_seamless_rdp;
  
***************
*** 530,533 ****
--- 531,771 ----
  }
  
+ typedef struct _sw_configurenotify_context
+ {
+ 	Window window;
+ 	unsigned long serial;
+ } sw_configurenotify_context;
+ 
+ /* Predicate procedure for sw_wait_configurenotify */
+ static Bool
+ sw_configurenotify_p(Display * display, XEvent * xevent, XPointer arg)
+ {
+ 	sw_configurenotify_context *context = (sw_configurenotify_context *) arg;
+ 	if (xevent->xany.type == ConfigureNotify
+ 	    && xevent->xconfigure.window == context->window
+ 	    && xevent->xany.serial >= context->serial)
+ 		return True;
+ 
+ 	return False;
+ }
+ 
+ /* Wait for a ConfigureNotify, with a equal or larger serial, on the
+    specified window. The event will be removed from the queue. We
+    could use XMaskEvent(StructureNotifyMask), but we would then risk
+    throwing away crucial events like DestroyNotify. 
+ 
+    After a ConfigureWindow, according to ICCCM section 4.1.5, we
+    should recieve a ConfigureNotify, either a real or synthetic
+    one. This indicates that the configure has been "completed".
+    However, some WMs such as several versions of Metacity fails to
+    send synthetic events. See bug
+    http://bugzilla.gnome.org/show_bug.cgi?id=322840. We need to use a
+    timeout to avoid a hang. Tk uses the same approach. */
+ static void
+ sw_wait_configurenotify(Window wnd, unsigned long serial)
+ {
+ 	XEvent xevent;
+ 	sw_configurenotify_context context;
+ 	time_t start;
+ 	RD_BOOL got = False;
+ 
+ 	context.window = wnd;
+ 	context.serial = serial;
+ 	start = time(NULL);
+ 
+ 	do
+ 	{
+ 		if (XCheckIfEvent(g_display, &xevent, sw_configurenotify_p, (XPointer) & context))
+ 		{
+ 			got = True;
+ 			break;
+ 		}
+ 		usleep(100000);
+ 	}
+ 	while (time(NULL) - start < 2);
+ 
+ 	if (!got)
+ 	{
+ 		warning("Broken Window Manager: Timeout while waiting for ConfigureNotify\n");
+ 	}
+ }
+ 
+ /* Get the toplevel window, in case of reparenting */
+ static Window
+ sw_get_toplevel(Window wnd)
+ {
+ 	Window root, parent;
+ 	Window *child_list;
+ 	unsigned int num_children;
+ 
+ 	while (1)
+ 	{
+ 		XQueryTree(g_display, wnd, &root, &parent, &child_list, &num_children);
+ 		if (root == parent)
+ 		{
+ 			break;
+ 		}
+ 		else if (!parent)
+ 		{
+ 			warning("Internal error: sw_get_toplevel called with root window\n");
+ 		}
+ 
+ 		wnd = parent;
+ 	}
+ 
+ 	return wnd;
+ }
+ 
+ 
+ /* Check if wnd is already behind a window wrt stacking order */
+ static RD_BOOL
+ sw_window_is_behind(Window wnd, Window behind)
+ {
+ 	Window dummy1, dummy2;
+ 	Window *child_list;
+ 	unsigned int num_children;
+ 	unsigned int i;
+ 	RD_BOOL found_behind = False;
+ 	RD_BOOL found_wnd = False;
+ 
+ 	wnd = sw_get_toplevel(wnd);
+ 	behind = sw_get_toplevel(behind);
+ 
+ 	XQueryTree(g_display, RootWindowOfScreen(g_screen), &dummy1, &dummy2, &child_list,
+ 		   &num_children);
+ 
+ 	for (i = num_children - 1; i >= 0; i--)
+ 	{
+ 		if (child_list[i] == behind)
+ 		{
+ 			found_behind = True;
+ 		}
+ 		else if (child_list[i] == wnd)
+ 		{
+ 			found_wnd = True;
+ 			break;
+ 		}
+ 	}
+ 
+ 	if (child_list)
+ 		XFree(child_list);
+ 
+ 	if (!found_wnd)
+ 	{
+ 		warning("sw_window_is_behind: Unable to find window 0x%lx\n", wnd);
+ 
+ 		if (!found_behind)
+ 		{
+ 			warning("sw_window_is_behind: Unable to find behind window 0x%lx\n",
+ 				behind);
+ 		}
+ 	}
+ 
+ 	return found_behind;
+ }
+ 
+ 
+ /* Test if the window manager correctly handles window restacking. In
+    particular, we are testing if it's possible to place a window
+    between two other windows. Many WMs such as Metacity can only stack
+    windows on the top or bottom. The window creation should mostly
+    match ui_seamless_create_window. */
+ static void
+ seamless_restack_test()
+ {
+ 	/* The goal is to have the middle window between top and
+ 	   bottom.  The middle window is initially at the top,
+ 	   though. */
+ 	Window wnds[3];		/* top, middle and bottom */
+ 	int i;
+ 	XEvent xevent;
+ 	XWindowChanges values;
+ 	unsigned long restack_serial;
+ 
+ 	for (i = 0; i < 3; i++)
+ 	{
+ 		char name[64];
+ 		wnds[i] =
+ 			XCreateSimpleWindow(g_display, RootWindowOfScreen(g_screen), 0, 0, 20, 20,
+ 					    0, 0, 0);
+ 		snprintf(name, sizeof(name), "SeamlessRDP restack test - window %d", i);
+ 		XStoreName(g_display, wnds[i], name);
+ 		ewmh_set_wm_name(wnds[i], name);
+ 
+ 		/* Hide decorations. Often this means that no
+ 		   reparenting will be done, which makes the restack
+ 		   easier. Besides, we want to mimic our other
+ 		   seamless windows as much as possible. We must still
+ 		   handle the case with reparenting, though. */
+ 		mwm_hide_decorations(wnds[i]);
+ 
+ 		/* Prevent windows from appearing in task bar */
+ 		XSetTransientForHint(g_display, wnds[i], RootWindowOfScreen(g_screen));
+ 		ewmh_set_window_popup(wnds[i]);
+ 
+ 		/* We need to catch MapNotify/ConfigureNotify */
+ 		XSelectInput(g_display, wnds[i], StructureNotifyMask);
+ 	}
+ 
+ 	/* Map Windows. Currently, we assume that XMapRaised places
+ 	   the window on the top of the stack. Should be fairly safe;
+ 	   the window is configured before it's mapped. */
+ 	XMapRaised(g_display, wnds[2]);	/* bottom */
+ 	do
+ 	{
+ 		XWindowEvent(g_display, wnds[2], StructureNotifyMask, &xevent);
+ 	}
+ 	while (xevent.type != MapNotify);
+ 	XMapRaised(g_display, wnds[0]);	/* top */
+ 	do
+ 	{
+ 		XWindowEvent(g_display, wnds[0], StructureNotifyMask, &xevent);
+ 	}
+ 	while (xevent.type != MapNotify);
+ 	XMapRaised(g_display, wnds[1]);	/* middle */
+ 	do
+ 	{
+ 		XWindowEvent(g_display, wnds[1], StructureNotifyMask, &xevent);
+ 	}
+ 	while (xevent.type != MapNotify);
+ 
+ 	/* The stacking order should now be 1 - 0 - 2 */
+ 	if (!sw_window_is_behind(wnds[0], wnds[1]) || !sw_window_is_behind(wnds[2], wnds[1]))
+ 	{
+ 		/* Ok, technically a WM is allowed to stack windows arbitrarily, but... */
+ 		warning("Broken Window Manager: Unable to test window restacking\n");
+ 		g_seamless_broken_restack = True;
+ 		for (i = 0; i < 3; i++)
+ 			XDestroyWindow(g_display, wnds[i]);
+ 		return;
+ 	}
+ 
+ 	/* Restack, using XReconfigureWMWindow, which should correctly
+ 	   handle reparented windows as well as nonreparenting WMs. */
+ 	values.stack_mode = Below;
+ 	values.sibling = wnds[0];
+ 	restack_serial = XNextRequest(g_display);
+ 	XReconfigureWMWindow(g_display, wnds[1], DefaultScreen(g_display), CWStackMode | CWSibling,
+ 			     &values);
+ 	sw_wait_configurenotify(wnds[1], restack_serial);
+ 
+ 	/* Now verify that middle is behind top but not behind
+ 	   bottom */
+ 	if (!sw_window_is_behind(wnds[1], wnds[0]))
+ 	{
+ 		warning("Broken Window Manager: doesn't handle restack (restack request was ignored)\n");
+ 		g_seamless_broken_restack = True;
+ 	}
+ 	else if (sw_window_is_behind(wnds[1], wnds[2]))
+ 	{
+ 		warning("Broken Window Manager: doesn't handle restack (window was moved to bottom)\n");
+ 		g_seamless_broken_restack = True;
+ 	}
+ 
+ 	/* Destroy windows */
+ 	for (i = 0; i < 3; i++)
+ 		XDestroyWindow(g_display, wnds[i]);
+ }
+ 
  #define SPLITCOLOUR15(colour, rv) \
  { \
***************
*** 1537,1551 ****
  
  static XErrorHandler g_old_error_handler;
  
  static int
  error_handler(Display * dpy, XErrorEvent * eev)
  {
! 	if ((eev->error_code == BadMatch) && (eev->request_code == X_ConfigureWindow))
! 	{
! 		fprintf(stderr, "Got \"BadMatch\" when trying to restack windows.\n");
! 		fprintf(stderr,
! 			"This is most likely caused by a broken window manager (commonly KWin).\n");
  		return 0;
- 	}
  
  	return g_old_error_handler(dpy, eev);
--- 1775,1809 ----
  
  static XErrorHandler g_old_error_handler;
+ static RD_BOOL g_error_expected = False;
+ 
+ /* Check if the X11 window corresponding to a seamless window with
+    specified id exists. */
+ RD_BOOL
+ sw_window_exists(unsigned long id)
+ {
+ 	seamless_window *sw;
+ 	char *name;
+ 	Status sts = 0;
+ 
+ 	sw = sw_get_window_by_id(id);
+ 	if (!sw)
+ 		return False;
+ 
+ 	g_error_expected = True;
+ 	sts = XFetchName(g_display, sw->wnd, &name);
+ 	g_error_expected = False;
+ 	if (sts)
+ 	{
+ 		XFree(name);
+ 	}
+ 
+ 	return sts;
+ }
  
  static int
  error_handler(Display * dpy, XErrorEvent * eev)
  {
! 	if (g_error_expected)
  		return 0;
  
  	return g_old_error_handler(dpy, eev);
***************
*** 1657,1661 ****
--- 1915,1922 ----
  	ewmh_init();
  	if (g_seamless_rdp)
+ 	{
+ 		seamless_restack_test();
  		seamless_init();
+ 	}
  
  	DEBUG_RDP5(("server bpp %d client bpp %d depth %d\n", g_server_depth, g_bpp, g_depth));
***************
*** 2139,2145 ****
  					break;
  
  				if (sw->id != g_seamless_focused)
  				{
! 					seamless_send_focus(sw->id, 0);
  					g_seamless_focused = sw->id;
  				}
--- 2400,2421 ----
  					break;
  
+ 				/* Menu windows are real X11 windows,
+ 				   with focus. When such a window is
+ 				   destroyed, focus is reverted to the
+ 				   main application window, which
+ 				   would cause us to send FOCUS. This
+ 				   breaks window switching in, say,
+ 				   Seamonkey. We shouldn't need to
+ 				   send FOCUS: Windows should also
+ 				   revert focus to some other window
+ 				   when the menu window is
+ 				   destroyed. So, we only send FOCUS
+ 				   if the previous focus window still
+ 				   exists. */
  				if (sw->id != g_seamless_focused)
  				{
! 
! 					if (sw_window_exists(g_seamless_focused))
! 						seamless_send_focus(sw->id, 0);
  					g_seamless_focused = sw->id;
  				}
***************
*** 3684,3687 ****
--- 3960,3965 ----
  {
  	seamless_window *sw;
+ 	XWindowChanges values;
+ 	unsigned long restack_serial;
  
  	if (!g_seamless_active)
***************
*** 3698,3702 ****
  	{
  		seamless_window *sw_behind;
- 		Window wnds[2];
  
  		sw_behind = sw_get_window_by_id(behind);
--- 3976,3979 ----
***************
*** 3707,3718 ****
  		}
  
! 		wnds[1] = sw->wnd;
! 		wnds[0] = sw_behind->wnd;
! 
! 		XRestackWindows(g_display, wnds, 2);
  	}
  	else
  	{
! 		XRaiseWindow(g_display, sw->wnd);
  	}
  
--- 3984,4004 ----
  		}
  
! 		if (!g_seamless_broken_restack)
! 		{
! 			values.stack_mode = Below;
! 			values.sibling = sw_behind->wnd;
! 			restack_serial = XNextRequest(g_display);
! 			XReconfigureWMWindow(g_display, sw->wnd, DefaultScreen(g_display),
! 					     CWStackMode | CWSibling, &values);
! 			sw_wait_configurenotify(sw->wnd, restack_serial);
! 		}
  	}
  	else
  	{
! 		values.stack_mode = Above;
! 		restack_serial = XNextRequest(g_display);
! 		XReconfigureWMWindow(g_display, sw->wnd, DefaultScreen(g_display), CWStackMode,
! 				     &values);
! 		sw_wait_configurenotify(sw->wnd, restack_serial);
  	}
  


-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://ad.doubleclick.net/clk;164216239;13503038;w?http://sf.net/marketplace