diff --git a/src/core/display-private.h b/src/core/display-private.h index 1f73fc3b2..3415c8414 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -103,19 +103,17 @@ struct _MetaDisplay #include #undef item - /* This is the actual window from focus events, - * not the one we last set + /* The window and serial of the most recent FocusIn event. */ + Window server_focus_window; + gulong server_focus_serial; + + /* Our best guess as to the "currently" focused window (that is, the + * window that we expect will be focused at the point when the X + * server processes our next request), and the serial of the request + * or event that caused this. */ MetaWindow *focus_window; - - /* window we are expecting a FocusIn event for or the current focus - * window if we are not expecting any FocusIn/FocusOut events; not - * perfect because applications can call XSetInputFocus directly. - * (It could also be messed up if a timestamp later than current - * time is sent to meta_display_set_input_focus_window, though that - * would be a programming error). See bug 154598 for more info. - */ - MetaWindow *expected_focus_window; + gulong focus_serial; /* last timestamp passed to XSetInputFocus */ guint32 last_focus_time; diff --git a/src/core/display.c b/src/core/display.c index f4f7e6325..7ee9c590f 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -519,7 +519,9 @@ meta_display_open (void) the_display->autoraise_timeout_id = 0; the_display->autoraise_window = NULL; the_display->focus_window = NULL; - the_display->expected_focus_window = NULL; + the_display->focus_serial = 0; + the_display->server_focus_window = None; + the_display->server_focus_serial = 0; the_display->grab_old_window_stacking = NULL; the_display->mouse_mode = TRUE; /* Only relevant for mouse or sloppy focus */ @@ -1632,12 +1634,12 @@ meta_display_mouse_mode_focus (MetaDisplay *display, * alternative mechanism works great. */ if (meta_prefs_get_focus_mode() == G_DESKTOP_FOCUS_MODE_MOUSE && - display->expected_focus_window != NULL) + display->focus_window != NULL) { meta_topic (META_DEBUG_FOCUS, "Unsetting focus from %s due to mouse entering " "the DESKTOP window\n", - display->expected_focus_window->desc); + display->focus_window->desc); meta_display_focus_the_no_focus_window (display, window->screen, timestamp); @@ -1851,6 +1853,163 @@ get_input_event (MetaDisplay *display, return NULL; } +static void +set_focus_window (MetaDisplay *display, + MetaWindow *window, + gulong serial) +{ + display->focus_serial = serial; + + if (window == display->focus_window) + return; + + if (display->focus_window) + { + MetaWindow *previous; + + meta_topic (META_DEBUG_FOCUS, + "%s is now the previous focus window due to being focused out or unmapped\n", + display->focus_window->desc); + + /* Make sure that signals handlers invoked by + * meta_window_set_focused_internal() don't see + * display->focus_window->has_focus == FALSE + */ + previous = display->focus_window; + display->focus_window = NULL; + + meta_window_set_focused_internal (previous, FALSE); + } + + display->focus_window = window; + + if (display->focus_window) + { + meta_topic (META_DEBUG_FOCUS, "* Focus --> %s with serial %lu\n", + display->focus_window->desc, serial); + meta_window_set_focused_internal (display->focus_window, TRUE); + } + else + meta_topic (META_DEBUG_FOCUS, "* Focus --> NULL with serial %lu\n", serial); + + g_object_notify (G_OBJECT (display), "focus-window"); + meta_display_update_active_window_hint (display); +} + +static void +handle_window_focus_event (MetaDisplay *display, + MetaWindow *window, + XIEnterEvent *event, + unsigned long serial) +{ + MetaWindow *focus_window; +#ifdef WITH_VERBOSE_MODE + const char *window_type; + + /* Note the event can be on either the window or the frame, + * we focus the frame for shaded windows + */ + if (window) + { + if (event->event == window->xwindow) + window_type = "client window"; + else if (window->frame && event->event == window->frame->xwindow) + window_type = "frame window"; + else + window_type = "unknown client window"; + } + else if (meta_display_xwindow_is_a_no_focus_window (display, event->event)) + window_type = "no_focus_window"; + else if (meta_display_screen_for_root (display, event->event)) + window_type = "root window"; + else + window_type = "unknown window"; + + meta_topic (META_DEBUG_FOCUS, + "Focus %s event received on %s 0x%lx (%s) " + "mode %s detail %s serial %lu\n", + event->evtype == XI_FocusIn ? "in" : + event->evtype == XI_FocusOut ? "out" : + "???", + window ? window->desc : "", + event->event, window_type, + meta_event_mode_to_string (event->mode), + meta_event_detail_to_string (event->mode), + event->serial); +#endif + + /* FIXME our pointer tracking is broken; see how + * gtk+/gdk/x11/gdkevents-x11.c or XFree86/xc/programs/xterm/misc.c + * for how to handle it the correct way. In brief you need to track + * pointer focus and regular focus, and handle EnterNotify in + * PointerRoot mode with no window manager. However as noted above, + * accurate focus tracking will break things because we want to keep + * windows "focused" when using keybindings on them, and also we + * sometimes "focus" a window by focusing its frame or + * no_focus_window; so this all needs rethinking massively. + * + * My suggestion is to change it so that we clearly separate + * actual keyboard focus tracking using the xterm algorithm, + * and mutter's "pretend" focus window, and go through all + * the code and decide which one should be used in each place; + * a hard bit is deciding on a policy for that. + * + * http://bugzilla.gnome.org/show_bug.cgi?id=90382 + */ + + /* We ignore grabs, though this is questionable. It may be better to + * increase the intelligence of the focus window tracking. + * + * The problem is that keybindings for windows are done with + * XGrabKey, which means focus_window disappears and the front of + * the MRU list gets confused from what the user expects once a + * keybinding is used. + */ + + if (event->mode == NotifyGrab || + event->mode == NotifyUngrab || + /* From WindowMaker, ignore all funky pointer root events */ + event->detail > NotifyNonlinearVirtual) + { + meta_topic (META_DEBUG_FOCUS, + "Ignoring focus event generated by a grab or other weirdness\n"); + return; + } + + if (event->evtype == XI_FocusIn) + { + display->server_focus_window = event->event; + display->server_focus_serial = serial; + + if (window && window->override_redirect) + focus_window = NULL; + else + focus_window = window; + } + else if (event->evtype == XI_FocusOut) + { + if (event->detail == NotifyInferior) + { + /* This event means the client moved focus to a subwindow */ + meta_topic (META_DEBUG_FOCUS, + "Ignoring focus out with NotifyInferior\n"); + return; + } + + display->server_focus_window = None; + display->server_focus_serial = serial; + focus_window = NULL; + } + else + g_return_if_reached (); + + if (display->server_focus_serial >= display->focus_serial) + { + set_focus_window (display, focus_window, + display->server_focus_serial); + } +} + /** * event_callback: * @event: The event that just happened @@ -1893,7 +2052,18 @@ event_callback (XEvent *event, filter_out_event = FALSE; display->current_time = event_get_time (display, event); display->monitor_cache_invalidated = TRUE; - + + if (event->xany.serial > display->focus_serial && + display->focus_window && + display->focus_window->xwindow != display->server_focus_window) + { + meta_topic (META_DEBUG_FOCUS, "Earlier attempt to focus %s failed\n", + display->focus_window->desc); + set_focus_window (display, + meta_display_lookup_x_window (display, display->server_focus_window), + display->server_focus_serial); + } + modified = event_get_modified_window (display, event); input_event = get_input_event (display, event); @@ -2350,40 +2520,19 @@ event_callback (XEvent *event, break; case XI_FocusIn: case XI_FocusOut: - if (window) - { - meta_window_notify_focus (window, enter_event); - } - else if (meta_display_xwindow_is_a_no_focus_window (display, - enter_event->event)) - { - meta_topic (META_DEBUG_FOCUS, - "Focus %s event received on no_focus_window 0x%lx " - "mode %s detail %s\n", - enter_event->evtype == XI_FocusIn ? "in" : - enter_event->evtype == XI_FocusOut ? "out" : - "???", - enter_event->event, - meta_event_mode_to_string (enter_event->mode), - meta_event_detail_to_string (enter_event->detail)); - } - else + /* libXi does not properly copy the serial to the XIEnterEvent, so pull it + * from the parent XAnyEvent. + * See: https://bugs.freedesktop.org/show_bug.cgi?id=64687 + */ + handle_window_focus_event (display, window, enter_event, event->xany.serial); + if (!window) { + /* Check if the window is a root window. */ MetaScreen *screen = meta_display_screen_for_root(display, enter_event->event); if (screen == NULL) break; - - meta_topic (META_DEBUG_FOCUS, - "Focus %s event received on root window 0x%lx " - "mode %s detail %s\n", - enter_event->evtype == XI_FocusIn ? "in" : - enter_event->evtype == XI_FocusOut ? "out" : - "???", - enter_event->event, - meta_event_mode_to_string (enter_event->mode), - meta_event_detail_to_string (enter_event->detail)); if (enter_event->evtype == XI_FocusIn && enter_event->mode == XINotifyDetailNone) @@ -2521,13 +2670,6 @@ event_callback (XEvent *event, window->unmaps_pending); } } - - /* Unfocus on UnmapNotify, do this after the possible - * window_free above so that window_free can see if window->has_focus - * and move focus to another window - */ - if (window) - meta_window_lost_focus (window); } break; case MapNotify: @@ -5622,18 +5764,57 @@ meta_display_set_input_focus_window (MetaDisplay *display, return; meta_error_trap_push (display); + display->focus_serial = XNextRequest (display->xdisplay); + meta_topic (META_DEBUG_FOCUS, "XSetInputFocus(%s, %u) with serial %lu\n", + window->desc, timestamp, display->focus_serial); XSetInputFocus (display->xdisplay, focus_frame ? window->frame->xwindow : window->xwindow, RevertToPointerRoot, timestamp); meta_error_trap_pop (display); - display->expected_focus_window = window; display->last_focus_time = timestamp; display->active_screen = window->screen; if (window != display->autoraise_window) meta_display_remove_autoraise_callback (window->display); + + set_focus_window (display, window, display->focus_serial); +} + +void +meta_display_request_take_focus (MetaDisplay *display, + MetaWindow *window, + guint32 timestamp) +{ + if (timestamp_too_old (display, window, ×tamp)) + return; + + meta_topic (META_DEBUG_FOCUS, "WM_TAKE_FOCUS(%s, %u)\n", + window->desc, timestamp); + + if (window != display->focus_window) + { + /* The "Globally Active Input" window case, where the window + * doesn't want us to call XSetInputFocus on it, but does + * want us to send a WM_TAKE_FOCUS. + * + * We can't just set display->focus_window to @window, since we + * we don't know when (or even if) the window will actually take + * focus, so we could end up being wrong for arbitrarily long. + * But we also can't leave it set to the current window, or else + * bug #597352 would come back. So we focus the no_focus_window + * now (and set display->focus_window to that), send the + * WM_TAKE_FOCUS, and then just forget about @window + * until/unless we get a FocusIn. + */ + meta_display_focus_the_no_focus_window (display, + window->screen, + timestamp); + } + meta_window_send_icccm_message (window, + display->atom_WM_TAKE_FOCUS, + timestamp); } void @@ -5644,15 +5825,19 @@ meta_display_focus_the_no_focus_window (MetaDisplay *display, if (timestamp_too_old (display, NULL, ×tamp)) return; + display->focus_serial = XNextRequest (display->xdisplay); + meta_topic (META_DEBUG_FOCUS, "Focusing no_focus_window at %u with serial %lu\n", + timestamp, display->focus_serial); XSetInputFocus (display->xdisplay, screen->no_focus_window, RevertToPointerRoot, timestamp); - display->expected_focus_window = NULL; display->last_focus_time = timestamp; display->active_screen = screen; meta_display_remove_autoraise_callback (display); + + set_focus_window (display, NULL, display->focus_serial); } void @@ -5762,11 +5947,9 @@ meta_display_has_shape (MetaDisplay *display) * meta_display_get_focus_window: * @display: a #MetaDisplay * - * Get the window that, according to events received from X server, - * currently has the input focus. We may have already sent a request - * to the X server to move the focus window elsewhere. (The - * expected_focus_window records where we've last set the input - * focus.) + * Get our best guess as to the "currently" focused window (that is, + * the window that we expect will be focused at the point when the X + * server processes our next request). * * Return Value: (transfer none): The current focus window */ diff --git a/src/core/stack.c b/src/core/stack.c index e91094d51..3b05cdbd3 100644 --- a/src/core/stack.c +++ b/src/core/stack.c @@ -278,7 +278,7 @@ static gboolean is_focused_foreach (MetaWindow *window, void *data) { - if (window == window->display->expected_focus_window) + if (window->has_focus) { *((gboolean*) data) = TRUE; return FALSE; @@ -335,11 +335,11 @@ get_standalone_layer (MetaWindow *window) layer = META_LAYER_BOTTOM; else if (window->fullscreen && (focused_transient || - window == window->display->expected_focus_window || - window->display->expected_focus_window == NULL || - (window->display->expected_focus_window != NULL && + window == window->display->focus_window || + window->display->focus_window == NULL || + (window->display->focus_window != NULL && windows_on_different_monitor (window, - window->display->expected_focus_window)))) + window->display->focus_window)))) layer = META_LAYER_FULLSCREEN; else if (window->wm_state_above && !META_WINDOW_MAXIMIZED (window)) layer = META_LAYER_TOP; diff --git a/src/core/window-private.h b/src/core/window-private.h index e9f935f4e..ec94cca07 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -277,7 +277,7 @@ struct _MetaWindow /* EWHH demands attention flag */ guint wm_state_demands_attention : 1; - /* this flag tracks receipt of focus_in focus_out */ + /* TRUE iff window == window->display->focus_window */ guint has_focus : 1; /* Have we placed this window? */ @@ -590,9 +590,8 @@ gboolean meta_window_property_notify (MetaWindow *window, XEvent *event); gboolean meta_window_client_message (MetaWindow *window, XEvent *event); -gboolean meta_window_notify_focus (MetaWindow *window, - XIEnterEvent *event); -void meta_window_lost_focus (MetaWindow *window); +void meta_window_set_focused_internal (MetaWindow *window, + gboolean focused); void meta_window_set_current_workspace_hint (MetaWindow *window); diff --git a/src/core/window.c b/src/core/window.c index e6dd691ba..9ed387e3a 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -1754,16 +1754,6 @@ meta_window_unmanage (MetaWindow *window, window, timestamp); } - else if (window->display->expected_focus_window == window) - { - meta_topic (META_DEBUG_FOCUS, - "Focusing default window since expected focus window freed %s\n", - window->desc); - window->display->expected_focus_window = NULL; - meta_workspace_focus_default_window (window->screen->active_workspace, - window, - timestamp); - } else { meta_topic (META_DEBUG_FOCUS, @@ -1771,6 +1761,8 @@ meta_window_unmanage (MetaWindow *window, window->desc); } + g_assert (window->display->focus_window != window); + if (window->struts) { meta_free_gslist_and_elements (window->struts); @@ -1793,12 +1785,6 @@ meta_window_unmanage (MetaWindow *window, g_assert (window->display->grab_window != window); - if (window->display->focus_window == window) - { - window->display->focus_window = NULL; - g_object_notify (G_OBJECT (window->display), "focus-window"); - } - if (window->maximized_horizontally || window->maximized_vertically) unmaximize_window_before_freeing (window); @@ -3332,14 +3318,7 @@ meta_window_hide (MetaWindow *window) invalidate_work_areas (window); } - /* The check on expected_focus_window is a temporary workaround for - * https://bugzilla.gnome.org/show_bug.cgi?id=597352 - * We may have already switched away from this window but not yet - * gotten FocusIn/FocusOut events. A more complete comprehensive - * fix for these type of issues is described in the bug. - */ - if (window->has_focus && - window == window->display->expected_focus_window) + if (window->has_focus) { MetaWindow *not_this_one = NULL; MetaWorkspace *my_workspace = meta_window_get_workspace (window); @@ -5975,10 +5954,10 @@ meta_window_focus (MetaWindow *window, meta_topic (META_DEBUG_FOCUS, "Sending WM_TAKE_FOCUS to %s since take_focus = true\n", window->desc); - meta_window_send_icccm_message (window, - window->display->atom_WM_TAKE_FOCUS, - timestamp); - window->display->expected_focus_window = window; + + meta_display_request_take_focus (window->display, + window, + timestamp); } } @@ -6518,7 +6497,7 @@ meta_window_configure_request (MetaWindow *window, if (event->xconfigurerequest.value_mask & CWStackMode) { MetaWindow *active_window; - active_window = window->display->expected_focus_window; + active_window = window->display->focus_window; if (meta_prefs_get_disable_workarounds ()) { meta_topic (META_DEBUG_STACK, @@ -7197,8 +7176,7 @@ meta_window_propagate_focus_appearance (MetaWindow *window, parent->attached_focus_window = NULL; } - if (child_focus_state_changed && !parent->has_focus && - parent != window->display->expected_focus_window) + if (child_focus_state_changed && !parent->has_focus) { meta_window_appears_focused_changed (parent); } @@ -7208,18 +7186,12 @@ meta_window_propagate_focus_appearance (MetaWindow *window, } } -static void +void meta_window_set_focused_internal (MetaWindow *window, gboolean focused) { if (focused) { - if (window == window->display->focus_window) - return; - - meta_topic (META_DEBUG_FOCUS, - "* Focus --> %s\n", window->desc); - window->display->focus_window = window; window->has_focus = TRUE; /* Move to the front of the focusing workspace's MRU list. @@ -7278,7 +7250,6 @@ meta_window_set_focused_internal (MetaWindow *window, meta_display_ungrab_focus_window_button (window->display, window); g_signal_emit (window, window_signals[FOCUS], 0); - g_object_notify (G_OBJECT (window->display), "focus-window"); if (!window->attached_focus_window) meta_window_appears_focused_changed (window); @@ -7287,12 +7258,7 @@ meta_window_set_focused_internal (MetaWindow *window, } else { - if (window != window->display->focus_window) - return; - meta_window_propagate_focus_appearance (window, FALSE); - window->display->focus_window = NULL; - g_object_notify (G_OBJECT (window->display), "focus-window"); window->has_focus = FALSE; if (!window->attached_focus_window) @@ -7313,115 +7279,6 @@ meta_window_set_focused_internal (MetaWindow *window, } } -void -meta_window_lost_focus (MetaWindow *window) -{ - meta_window_set_focused_internal (window, FALSE); -} - -gboolean -meta_window_notify_focus (MetaWindow *window, - XIEnterEvent *event) -{ - /* note the event can be on either the window or the frame, - * we focus the frame for shaded windows - */ - - /* The event can be FocusIn, FocusOut, or UnmapNotify. - * On UnmapNotify we have to pretend it's focus out, - * because we won't get a focus out if it occurs, apparently. - */ - - /* We ignore grabs, though this is questionable. - * It may be better to increase the intelligence of - * the focus window tracking. - * - * The problem is that keybindings for windows are done with - * XGrabKey, which means focus_window disappears and the front of - * the MRU list gets confused from what the user expects once a - * keybinding is used. - */ - meta_topic (META_DEBUG_FOCUS, - "Focus %s event received on %s 0x%lx (%s) " - "mode %s detail %s\n", - event->evtype == XI_FocusIn ? "in" : - event->evtype == XI_FocusOut ? "out" : - "???", - window->desc, event->event, - event->event == window->xwindow ? - "client window" : - (window->frame && event->event == window->frame->xwindow) ? - "frame window" : - "unknown window", - meta_event_mode_to_string (event->mode), - meta_event_detail_to_string (event->detail)); - - /* FIXME our pointer tracking is broken; see how - * gtk+/gdk/x11/gdkevents-x11.c or XFree86/xc/programs/xterm/misc.c - * handle it for the correct way. In brief you need to track - * pointer focus and regular focus, and handle EnterNotify in - * PointerRoot mode with no window manager. However as noted above, - * accurate focus tracking will break things because we want to keep - * windows "focused" when using keybindings on them, and also we - * sometimes "focus" a window by focusing its frame or - * no_focus_window; so this all needs rethinking massively. - * - * My suggestion is to change it so that we clearly separate - * actual keyboard focus tracking using the xterm algorithm, - * and mutter's "pretend" focus window, and go through all - * the code and decide which one should be used in each place; - * a hard bit is deciding on a policy for that. - * - * http://bugzilla.gnome.org/show_bug.cgi?id=90382 - */ - - if ((event->evtype == XI_FocusIn || - event->evtype == XI_FocusOut) && - (event->mode == NotifyGrab || - event->mode == NotifyUngrab || - /* From WindowMaker, ignore all funky pointer root events */ - event->detail > NotifyNonlinearVirtual)) - { - meta_topic (META_DEBUG_FOCUS, - "Ignoring focus event generated by a grab or other weirdness\n"); - return TRUE; - } - - if (event->evtype == XI_FocusIn) - { - if (window->override_redirect) - { - window->display->focus_window = NULL; - g_object_notify (G_OBJECT (window->display), "focus-window"); - return FALSE; - } - else - { - meta_window_set_focused_internal (window, TRUE); - } - } - else if (event->evtype == XI_FocusOut) - { - if (event->detail == NotifyInferior) - { - /* This event means the client moved focus to a subwindow */ - meta_topic (META_DEBUG_FOCUS, - "Ignoring focus out on %s with NotifyInferior\n", - window->desc); - return TRUE; - } - else - { - meta_window_set_focused_internal (window, FALSE); - } - } - - /* Now set _NET_ACTIVE_WINDOW hint */ - meta_display_update_active_window_hint (window->display); - - return FALSE; -} - static gboolean process_property_notify (MetaWindow *window, XPropertyEvent *event) diff --git a/src/meta/display.h b/src/meta/display.h index d4c3aaeb5..7ace85806 100644 --- a/src/meta/display.h +++ b/src/meta/display.h @@ -165,6 +165,10 @@ void meta_display_set_input_focus_window (MetaDisplay *display, gboolean focus_frame, guint32 timestamp); +void meta_display_request_take_focus (MetaDisplay *display, + MetaWindow *window, + guint32 timestamp); + /* meta_display_focus_the_no_focus_window is called when the * designated no_focus_window should be focused, but is otherwise the * same as meta_display_set_input_focus_window diff --git a/src/ui/tabpopup.c b/src/ui/tabpopup.c index ac1029439..c8dbeeaa4 100644 --- a/src/ui/tabpopup.c +++ b/src/ui/tabpopup.c @@ -857,10 +857,7 @@ meta_convert_meta_to_wnck (MetaWindow *window, MetaScreen *screen) WnckWindowDisplayInfo wnck_window; wnck_window.icon = window->icon; wnck_window.mini_icon = window->mini_icon; - - wnck_window.is_active = FALSE; - if (window == window->display->expected_focus_window) - wnck_window.is_active = TRUE; + wnck_window.is_active = window->has_focus; if (window->frame) {