diff --git a/src/core/display.c b/src/core/display.c index 5f9a53638..f809caa19 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -2229,9 +2229,11 @@ handle_input_xevent (MetaDisplay *display, XIEvent *input_event, gulong serial) { + XIDeviceEvent *device_event = (XIDeviceEvent *) input_event; XIEnterEvent *enter_event = (XIEnterEvent *) input_event; Window modified; MetaWindow *window; + gboolean frame_was_receiver; if (input_event == NULL) return FALSE; @@ -2239,8 +2241,242 @@ handle_input_xevent (MetaDisplay *display, modified = xievent_get_modified_window (display, input_event); window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL; + frame_was_receiver = FALSE; + if (window && + window->frame && + modified == window->frame->xwindow) + { + /* Note that if the frame and the client both have an + * XGrabButton (as is normal with our setup), the event + * goes to the frame. + */ + frame_was_receiver = TRUE; + meta_topic (META_DEBUG_EVENTS, "Frame was receiver of event for %s\n", + window->desc); + } + + if (window && !window->override_redirect && + (input_event->evtype == XI_KeyPress || input_event->evtype == XI_ButtonPress)) + { + if (CurrentTime == display->current_time) + { + /* We can't use missing (i.e. invalid) timestamps to set user time, + * nor do we want to use them to sanity check other timestamps. + * See bug 313490 for more details. + */ + meta_warning ("Event has no timestamp! You may be using a broken " + "program such as xse. Please ask the authors of that " + "program to fix it.\n"); + } + else + { + meta_window_set_user_time (window, display->current_time); + sanity_check_timestamps (display, display->current_time); + } + } + switch (input_event->evtype) { + case XI_ButtonPress: + if (display->grab_op == META_GRAB_OP_COMPOSITOR) + break; + + display->overlay_key_only_pressed = FALSE; + + if (device_event->detail == 4 || device_event->detail == 5) + /* Scrollwheel event, do nothing and deliver event to compositor below */ + break; + + if ((window && + meta_grab_op_is_mouse (display->grab_op) && + (device_event->mods.effective & display->window_grab_modifiers) && + display->grab_button != device_event->detail && + display->grab_window == window) || + grab_op_is_keyboard (display->grab_op)) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Ending grab op %u on window %s due to button press\n", + display->grab_op, + (display->grab_window ? + display->grab_window->desc : + "none")); + if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) + { + MetaScreen *screen; + meta_topic (META_DEBUG_WINDOW_OPS, + "Syncing to old stack positions.\n"); + screen = + meta_display_screen_for_root (display, device_event->event); + + if (screen!=NULL) + meta_stack_set_positions (screen->stack, + display->grab_old_window_stacking); + } + meta_display_end_grab_op (display, + device_event->time); + } + else if (window && display->grab_op == META_GRAB_OP_NONE) + { + gboolean begin_move = FALSE; + unsigned int grab_mask; + gboolean unmodified; + + grab_mask = display->window_grab_modifiers; + if (g_getenv ("MUTTER_DEBUG_BUTTON_GRABS")) + grab_mask |= ControlMask; + + /* Two possible sources of an unmodified event; one is a + * client that's letting button presses pass through to the + * frame, the other is our focus_window_grab on unmodified + * button 1. So for all such events we focus the window. + */ + unmodified = (device_event->mods.effective & grab_mask) == 0; + + if (unmodified || + device_event->detail == 1) + { + /* don't focus if frame received, will be lowered in + * frames.c or special-cased if the click was on a + * minimize/close button. + */ + if (!frame_was_receiver) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + else + meta_topic (META_DEBUG_FOCUS, + "Not raising window on click due to don't-raise-on-click option\n"); + + /* Don't focus panels--they must explicitly request focus. + * See bug 160470 + */ + if (window->type != META_WINDOW_DOCK) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing %s due to unmodified button %u press (display.c)\n", + window->desc, device_event->detail); + meta_window_focus (window, device_event->time); + } + else + /* However, do allow terminals to lose focus due to new + * window mappings after the user clicks on a panel. + */ + display->allow_terminal_deactivation = TRUE; + } + + /* you can move on alt-click but not on + * the click-to-focus + */ + if (!unmodified) + begin_move = TRUE; + } + else if (!unmodified && device_event->detail == meta_prefs_get_mouse_button_resize()) + { + if (window->has_resize_func) + { + gboolean north, south; + gboolean west, east; + MetaRectangle frame_rect; + MetaGrabOp op; + + meta_window_get_frame_rect (window, &frame_rect); + + west = device_event->root_x < (frame_rect.x + 1 * frame_rect.width / 3); + east = device_event->root_x > (frame_rect.x + 2 * frame_rect.width / 3); + north = device_event->root_y < (frame_rect.y + 1 * frame_rect.height / 3); + south = device_event->root_y > (frame_rect.y + 2 * frame_rect.height / 3); + + if (north && west) + op = META_GRAB_OP_RESIZING_NW; + else if (north && east) + op = META_GRAB_OP_RESIZING_NE; + else if (south && west) + op = META_GRAB_OP_RESIZING_SW; + else if (south && east) + op = META_GRAB_OP_RESIZING_SE; + else if (north) + op = META_GRAB_OP_RESIZING_N; + else if (west) + op = META_GRAB_OP_RESIZING_W; + else if (east) + op = META_GRAB_OP_RESIZING_E; + else if (south) + op = META_GRAB_OP_RESIZING_S; + else /* Middle region is no-op to avoid user triggering wrong action */ + op = META_GRAB_OP_NONE; + + if (op != META_GRAB_OP_NONE) + meta_display_begin_grab_op (display, + window->screen, + window, + op, + TRUE, + FALSE, + device_event->detail, + 0, + device_event->time, + device_event->root_x, + device_event->root_y); + } + } + else if (device_event->detail == meta_prefs_get_mouse_button_menu()) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_show_menu (window, + device_event->root_x, + device_event->root_y, + device_event->detail, + device_event->time); + } + + if (!frame_was_receiver && unmodified) + { + /* This is from our synchronous grab since + * it has no modifiers and was on the client window + */ + + meta_verbose ("Allowing events time %u\n", + (unsigned int)device_event->time); + + XIAllowEvents (display->xdisplay, device_event->deviceid, + XIReplayDevice, device_event->time); + } + + if (begin_move && window->has_move_func) + { + meta_display_begin_grab_op (display, + window->screen, + window, + META_GRAB_OP_MOVING, + TRUE, + FALSE, + device_event->detail, + 0, + device_event->time, + device_event->root_x, + device_event->root_y); + } + } + break; + case XI_ButtonRelease: + if (display->grab_op == META_GRAB_OP_COMPOSITOR) + break; + + display->overlay_key_only_pressed = FALSE; + + if (display->grab_window == window && + meta_grab_op_is_mouse (display->grab_op)) + meta_window_handle_mouse_grab_op_xevent (window, device_event); + break; + case XI_Motion: + if (display->grab_op == META_GRAB_OP_COMPOSITOR) + break; + + if (display->grab_window == window && + meta_grab_op_is_mouse (display->grab_op)) + meta_window_handle_mouse_grab_op_xevent (window, device_event); + break; case XI_Enter: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; diff --git a/src/core/window-private.h b/src/core/window-private.h index 0f1630adb..307129c9b 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -648,8 +648,10 @@ void meta_window_update_sync_request_counter (MetaWindow *window, gint64 new_counter_value); #endif /* HAVE_XSYNC */ -void meta_window_handle_mouse_grab_op_event (MetaWindow *window, - const ClutterEvent *event); +void meta_window_handle_mouse_grab_op_event (MetaWindow *window, + const ClutterEvent *event); +void meta_window_handle_mouse_grab_op_xevent (MetaWindow *window, + XIDeviceEvent *xevent); GList* meta_window_get_workspaces (MetaWindow *window); diff --git a/src/core/window.c b/src/core/window.c index bb334f39e..3a3fde008 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -10029,20 +10029,96 @@ update_resize (MetaWindow *window, g_get_current_time (&window->display->grab_last_moveresize_time); } -static gboolean -check_use_this_motion_notify (MetaWindow *window, - const ClutterEvent *event) +typedef struct { - /* XXX: Previously this code would walk through the X event queue - and filter out motion events that are followed by a later motion - event. There currently isn't any API to do the equivalent - procedure with the Clutter event queue so this function does - nothing. Clutter does its own motion event squashing so it may be - the case that this function isn't necessary. If it turns out that - we do need additional motion event squashing we could add some - extra API to the Clutter event queue and implement this function - properly. */ - return TRUE; + Window window; + int count; + guint32 last_time; +} EventScannerData; + +static Bool +find_last_time_predicate (Display *display, + XEvent *ev, + XPointer arg) +{ + EventScannerData *esd = (void*) arg; + XIEvent *xev; + + if (ev->type != GenericEvent) + return False; + + /* We are peeking into events not yet handled by GDK, + * Allocate cookie events here so we can handle XI2. + * + * GDK will handle later these events, and eventually + * free the cookie data itself. + */ + XGetEventData (display, &ev->xcookie); + xev = (XIEvent *) ev->xcookie.data; + + if (xev->evtype != XI_Motion) + return False; + + if (esd->window != ((XIDeviceEvent *) xev)->event) + return False; + + esd->count += 1; + esd->last_time = xev->time; + + return False; +} + +static gboolean +check_use_this_motion_notify (MetaWindow *window, + XIDeviceEvent *xev) +{ + EventScannerData esd; + XEvent useless; + + /* This code is copied from Owen's GDK code. */ + + if (window->display->grab_motion_notify_time != 0) + { + /* == is really the right test, but I'm all for paranoia */ + if (window->display->grab_motion_notify_time <= + xev->time) + { + meta_topic (META_DEBUG_RESIZING, + "Arrived at event with time %u (waiting for %u), using it\n", + (unsigned int)xev->time, + window->display->grab_motion_notify_time); + window->display->grab_motion_notify_time = 0; + return TRUE; + } + else + return FALSE; /* haven't reached the saved timestamp yet */ + } + + esd.window = xev->event; + esd.count = 0; + esd.last_time = 0; + + /* "useless" isn't filled in because the predicate never returns True */ + XCheckIfEvent (window->display->xdisplay, + &useless, + find_last_time_predicate, + (XPointer) &esd); + + if (esd.count > 0) + meta_topic (META_DEBUG_RESIZING, + "Will skip %d motion events and use the event with time %u\n", + esd.count, (unsigned int) esd.last_time); + + if (esd.last_time == 0) + return TRUE; + else + { + /* Save this timestamp, and ignore all motion notify + * until we get to the one with this stamp. + */ + window->display->grab_motion_notify_time = esd.last_time; + return FALSE; + } } static void @@ -10116,8 +10192,98 @@ meta_window_update_sync_request_counter (MetaWindow *window, #endif /* HAVE_XSYNC */ void -meta_window_handle_mouse_grab_op_event (MetaWindow *window, - const ClutterEvent *event) +meta_window_handle_mouse_grab_op_xevent (MetaWindow *window, + XIDeviceEvent *xevent) +{ + gboolean is_window_root = (xevent->root == window->screen->xroot); + + switch (xevent->evtype) + { + case XI_ButtonRelease: + if (xevent->detail == 1 || + xevent->detail == meta_prefs_get_mouse_button_resize ()) + { + meta_display_check_threshold_reached (window->display, + xevent->root_x, + xevent->root_y); + /* If the user was snap moving then ignore the button + * release because they may have let go of shift before + * releasing the mouse button and they almost certainly do + * not want a non-snapped movement to occur from the button + * release. + */ + if (!window->display->grab_last_user_action_was_snap) + { + if (meta_grab_op_is_moving (window->display->grab_op)) + { + if (window->tile_mode != META_TILE_NONE) + meta_window_tile (window); + else if (is_window_root) + update_move (window, + xevent->mods.effective & ShiftMask, + xevent->root_x, + xevent->root_y); + } + else if (meta_grab_op_is_resizing (window->display->grab_op)) + { + if (is_window_root) + update_resize (window, + xevent->mods.effective & ShiftMask, + xevent->root_x, + xevent->root_y, + TRUE); + + /* If a tiled window has been dragged free with a + * mouse resize without snapping back to the tiled + * state, it will end up with an inconsistent tile + * mode on mouse release; cleaning the mode earlier + * would break the ability to snap back to the tiled + * state, so we wait until mouse release. + */ + update_tile_mode (window); + } + meta_display_end_grab_op (window->display, xevent->time); + } + } + break; + + case XI_Motion: + meta_display_check_threshold_reached (window->display, + xevent->root_x, + xevent->root_y); + if (meta_grab_op_is_moving (window->display->grab_op)) + { + if (is_window_root) + { + if (check_use_this_motion_notify (window, xevent)) + update_move (window, + xevent->mods.effective & ShiftMask, + xevent->root_x, + xevent->root_y); + } + } + else if (meta_grab_op_is_resizing (window->display->grab_op)) + { + if (is_window_root) + { + if (check_use_this_motion_notify (window, xevent)) + update_resize (window, + xevent->mods.effective & ShiftMask, + xevent->root_x, + xevent->root_y, + FALSE); + } + } + break; + + default: + break; + } +} + +void +meta_window_handle_mouse_grab_op_event (MetaWindow *window, + const ClutterEvent *event) { gboolean is_window_root = (event->any.stage != NULL && window && @@ -10170,7 +10336,6 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, */ update_tile_mode (window); } - meta_display_end_grab_op (window->display, event->any.time); } } @@ -10184,23 +10349,21 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window, { if (is_window_root) { - if (check_use_this_motion_notify (window, event)) - update_move (window, - event->button.modifier_state & CLUTTER_SHIFT_MASK, - event->motion.x, - event->motion.y); + update_move (window, + event->button.modifier_state & CLUTTER_SHIFT_MASK, + event->motion.x, + event->motion.y); } } else if (meta_grab_op_is_resizing (window->display->grab_op)) { if (is_window_root) { - if (check_use_this_motion_notify (window, event)) - update_resize (window, - event->button.modifier_state & CLUTTER_SHIFT_MASK, - event->motion.x, - event->motion.y, - FALSE); + update_resize (window, + event->button.modifier_state & CLUTTER_SHIFT_MASK, + event->motion.x, + event->motion.y, + FALSE); } } break;