window: Eliminate a potential race condition with _NET_WM_MOVERESIZE

Clients using _NET_WM_MOVERESIZE to start a drag operation may encounter
a race condition if the user presses and releases a mouse button very
fast, getting "stuck" in a grab state. While this is easily fixed with
the user pressing the button or hitting Escape as the EWMH spec suggests,
its's still a bit of annoyance for users.

After starting a grab operation, check that the button is actually pressed
by the client, and if not, cancel the grab operation. This prevents the
stuck grab in a race-free way, although it requires an extra round-trip
to the server.

With client-side decorations becoming more popular, the use of
_NET_WM_MOVERESIZE is on the rise, thus this bug is seen more frequently
than before.

https://bugzilla.gnome.org/show_bug.cgi?id=699777
This commit is contained in:
Jasper St. Pierre 2013-05-06 15:45:24 -04:00 committed by Ray Strode
parent 74fb5a83dd
commit ca4e1fd4c9

View File

@ -6634,6 +6634,41 @@ meta_window_change_workspace_by_index (MetaWindow *window,
#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 #define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10
#define _NET_WM_MOVERESIZE_CANCEL 11 #define _NET_WM_MOVERESIZE_CANCEL 11
static int
query_pressed_buttons (MetaWindow *window)
{
double x, y, query_root_x, query_root_y;
Window root, child;
XIButtonState buttons;
XIModifierState mods;
XIGroupState group;
int button = 0;
meta_error_trap_push (window->display);
XIQueryPointer (window->display->xdisplay,
META_VIRTUAL_CORE_POINTER_ID,
window->xwindow,
&root, &child,
&query_root_x, &query_root_y,
&x, &y,
&buttons, &mods, &group);
if (meta_error_trap_pop_with_return (window->display) != Success)
goto out;
if (XIMaskIsSet (buttons.mask, Button1))
button |= 1 << 1;
if (XIMaskIsSet (buttons.mask, Button2))
button |= 1 << 2;
if (XIMaskIsSet (buttons.mask, Button3))
button |= 1 << 3;
free (buttons.mask);
out:
return button;
}
gboolean gboolean
meta_window_client_message (MetaWindow *window, meta_window_client_message (MetaWindow *window,
XEvent *event) XEvent *event)
@ -6992,56 +7027,58 @@ meta_window_client_message (MetaWindow *window,
(op != META_GRAB_OP_MOVING && (op != META_GRAB_OP_MOVING &&
op != META_GRAB_OP_KEYBOARD_MOVING)))) op != META_GRAB_OP_KEYBOARD_MOVING))))
{ {
/* int button_mask;
* the button SHOULD already be included in the message
*/ meta_topic (META_DEBUG_WINDOW_OPS,
"Beginning move/resize with button = %d\n", button);
meta_display_begin_grab_op (window->display,
window->screen,
window,
op,
FALSE,
frame_action,
button, 0,
timestamp,
x_root,
y_root);
button_mask = query_pressed_buttons (window);
if (button == 0) if (button == 0)
{ {
double x, y, query_root_x, query_root_y; /*
Window root, child; * the button SHOULD already be included in the message
XIButtonState buttons;
XIModifierState mods;
XIGroupState group;
/* The race conditions in this _NET_WM_MOVERESIZE thing
* are mind-boggling
*/ */
meta_error_trap_push (window->display); if ((button_mask & (1 << 1)) != 0)
XIQueryPointer (window->display->xdisplay,
META_VIRTUAL_CORE_POINTER_ID,
window->xwindow,
&root, &child,
&query_root_x, &query_root_y,
&x, &y,
&buttons, &mods, &group);
meta_error_trap_pop (window->display);
if (XIMaskIsSet (buttons.mask, Button1))
button = 1; button = 1;
else if (XIMaskIsSet (buttons.mask, Button2)) else if ((button_mask & (1 << 2)) != 0)
button = 2; button = 2;
else if (XIMaskIsSet (buttons.mask, Button3)) else if ((button_mask & (1 << 3)) != 0)
button = 3; button = 3;
if (button != 0)
window->display->grab_button = button;
else else
button = 0; meta_display_end_grab_op (window->display,
timestamp);
free (buttons.mask);
} }
else
if (button != 0)
{ {
meta_topic (META_DEBUG_WINDOW_OPS, /* There is a potential race here. If the user presses and
"Beginning move/resize with button = %d\n", button); * releases their mouse button very fast, it's possible for
meta_display_begin_grab_op (window->display, * both the ButtonPress and ButtonRelease to be sent to the
window->screen, * client before it can get a chance to send _NET_WM_MOVERESIZE
window, * to us. When that happens, we'll become stuck in a grab
op, * state, as we haven't received a ButtonRelease to cancel the
FALSE, * grab.
frame_action, *
button, 0, * We can solve this by querying after we take the explicit
timestamp, * pointer grab -- if the button isn't pressed, we cancel the
x_root, * drag immediately.
y_root); */
if ((button_mask & (1 << button)) == 0)
meta_display_end_grab_op (window->display, timestamp);
} }
} }