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


git-svn-id: svn://svn.code.sf.net/p/rdesktop/code/trunk/rdesktop@1458 423420c4-83ab-492f-b58f-81f9feb106b5
This commit is contained in:
Peter Åstrand 2008-03-26 16:44:55 +00:00
parent d7f206df6c
commit 3a949d3127
2 changed files with 313 additions and 15 deletions

View File

@ -417,9 +417,21 @@ Blackbox, IceWM etc). You can also test without a window manager.
this using both the mouse and the keyboard.
* Test restacking of a middle window. For this test, you'll need to
* Test restacking of a bottom window. For this test, you'll need to
the tool "notepadbehindwordpad.exe". Run Task Manager
seamlessly. Start Notepad and Wordpad from it. Manually stack the
windows as (from top to bottom) Task Manager - Notepad - xterm -
Wordpad. Make sure the windows partially overlaps. From Task
Manager, execute notepadbehindwordpad.exe.
Manager, execute notepadbehindwordpad.exe. This
test is expected to fail when rdesktop emits the warning about
broken WM at startup.
* Test restacking of a middle window. For this test, you'll need to
the tool "notepadbehindwordpad.exe". Run Task Manager
seamlessly. Start Paint, Notepad, and Wordpad from it. Manually
stack the windows as (from top to bottom) Task Manager - Notepad -
xterm - Wordpad - Paint. Make sure the windows partially
overlaps. From Task Manager, execute notepadbehindwordpad.exe. This
test is expected to fail when rdesktop emits the warning about
broken WM at startup.

310
xwin.c
View File

@ -86,6 +86,7 @@ static unsigned long g_seamless_focused = 0;
static RD_BOOL g_seamless_started = False; /* Server end is up and running */
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;
extern uint32 g_embed_wnd;
@ -529,6 +530,243 @@ mwm_hide_decorations(Window wnd)
}
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) \
{ \
rv.red = ((colour >> 7) & 0xf8) | ((colour >> 12) & 0x7); \
@ -1536,17 +1774,37 @@ select_visual(int screen_num)
}
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 ((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");
if (g_error_expected)
return 0;
}
return g_old_error_handler(dpy, eev);
}
@ -1656,7 +1914,10 @@ ui_init(void)
xclip_init();
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));
@ -2138,8 +2399,23 @@ xwin_process_events(void)
if (!sw)
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;
}
@ -3683,6 +3959,8 @@ void
ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long flags)
{
seamless_window *sw;
XWindowChanges values;
unsigned long restack_serial;
if (!g_seamless_active)
return;
@ -3697,7 +3975,6 @@ ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long
if (behind)
{
seamless_window *sw_behind;
Window wnds[2];
sw_behind = sw_get_window_by_id(behind);
if (!sw_behind)
@ -3706,14 +3983,23 @@ ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long
return;
}
wnds[1] = sw->wnd;
wnds[0] = sw_behind->wnd;
XRestackWindows(g_display, wnds, 2);
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
{
XRaiseWindow(g_display, sw->wnd);
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);
}
sw_restack_window(sw, behind);