mirror of
https://github.com/brl/mutter.git
synced 2024-11-24 17:10:40 -05:00
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:
parent
74fb5a83dd
commit
ca4e1fd4c9
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user