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:
parent
d7f206df6c
commit
3a949d3127
@ -417,9 +417,21 @@ Blackbox, IceWM etc). You can also test without a window manager.
|
|||||||
this using both the mouse and the keyboard.
|
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
|
the tool "notepadbehindwordpad.exe". Run Task Manager
|
||||||
seamlessly. Start Notepad and Wordpad from it. Manually stack the
|
seamlessly. Start Notepad and Wordpad from it. Manually stack the
|
||||||
windows as (from top to bottom) Task Manager - Notepad - xterm -
|
windows as (from top to bottom) Task Manager - Notepad - xterm -
|
||||||
Wordpad. Make sure the windows partially overlaps. From Task
|
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
310
xwin.c
@ -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_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_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_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 RD_BOOL g_seamless_rdp;
|
||||||
|
|
||||||
extern uint32 g_embed_wnd;
|
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) \
|
#define SPLITCOLOUR15(colour, rv) \
|
||||||
{ \
|
{ \
|
||||||
rv.red = ((colour >> 7) & 0xf8) | ((colour >> 12) & 0x7); \
|
rv.red = ((colour >> 7) & 0xf8) | ((colour >> 12) & 0x7); \
|
||||||
@ -1536,17 +1774,37 @@ select_visual(int screen_num)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static XErrorHandler g_old_error_handler;
|
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
|
static int
|
||||||
error_handler(Display * dpy, XErrorEvent * eev)
|
error_handler(Display * dpy, XErrorEvent * eev)
|
||||||
{
|
{
|
||||||
if ((eev->error_code == BadMatch) && (eev->request_code == X_ConfigureWindow))
|
if (g_error_expected)
|
||||||
{
|
|
||||||
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 0;
|
||||||
}
|
|
||||||
|
|
||||||
return g_old_error_handler(dpy, eev);
|
return g_old_error_handler(dpy, eev);
|
||||||
}
|
}
|
||||||
@ -1656,7 +1914,10 @@ ui_init(void)
|
|||||||
xclip_init();
|
xclip_init();
|
||||||
ewmh_init();
|
ewmh_init();
|
||||||
if (g_seamless_rdp)
|
if (g_seamless_rdp)
|
||||||
|
{
|
||||||
|
seamless_restack_test();
|
||||||
seamless_init();
|
seamless_init();
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_RDP5(("server bpp %d client bpp %d depth %d\n", g_server_depth, g_bpp, g_depth));
|
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)
|
if (!sw)
|
||||||
break;
|
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->id != g_seamless_focused)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (sw_window_exists(g_seamless_focused))
|
||||||
seamless_send_focus(sw->id, 0);
|
seamless_send_focus(sw->id, 0);
|
||||||
g_seamless_focused = sw->id;
|
g_seamless_focused = sw->id;
|
||||||
}
|
}
|
||||||
@ -3683,6 +3959,8 @@ void
|
|||||||
ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long flags)
|
ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long flags)
|
||||||
{
|
{
|
||||||
seamless_window *sw;
|
seamless_window *sw;
|
||||||
|
XWindowChanges values;
|
||||||
|
unsigned long restack_serial;
|
||||||
|
|
||||||
if (!g_seamless_active)
|
if (!g_seamless_active)
|
||||||
return;
|
return;
|
||||||
@ -3697,7 +3975,6 @@ ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long
|
|||||||
if (behind)
|
if (behind)
|
||||||
{
|
{
|
||||||
seamless_window *sw_behind;
|
seamless_window *sw_behind;
|
||||||
Window wnds[2];
|
|
||||||
|
|
||||||
sw_behind = sw_get_window_by_id(behind);
|
sw_behind = sw_get_window_by_id(behind);
|
||||||
if (!sw_behind)
|
if (!sw_behind)
|
||||||
@ -3706,14 +3983,23 @@ ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wnds[1] = sw->wnd;
|
if (!g_seamless_broken_restack)
|
||||||
wnds[0] = sw_behind->wnd;
|
{
|
||||||
|
values.stack_mode = Below;
|
||||||
XRestackWindows(g_display, wnds, 2);
|
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
|
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);
|
sw_restack_window(sw, behind);
|
||||||
|
Loading…
Reference in New Issue
Block a user