From 59bc5b7975f1f19ebacb520c1c2666c0828d1111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 29 Aug 2012 04:38:54 +0200 Subject: [PATCH] display: (Optionally) delay focus changes in focus-follows-mouse mode Moving focus immediately on crossing events as we currently do in focus-follows-mouse mode may trigger a lot of unwanted focus changes when moving over unrelated windows on the way to a target. Those accidental focus changes prevent features like GNOME Shell's application menu from working properly and are visually expensive since we now use a very distinct style for unfocused windows. Instead, delay the actual focus change until the pointer has stopped moving. https://bugzilla.gnome.org/show_bug.cgi?id=678169 --- src/core/display-private.h | 3 + src/core/display.c | 192 ++++++++++++++++++++++------ src/core/prefs.c | 17 +++ src/meta/prefs.h | 2 + src/org.gnome.mutter.gschema.xml.in | 10 ++ 5 files changed, 186 insertions(+), 38 deletions(-) diff --git a/src/core/display-private.h b/src/core/display-private.h index 6db1b6461..b03fffd69 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -172,6 +172,9 @@ struct _MetaDisplay /* Pings which we're waiting for a reply from */ GSList *pending_pings; + /* Pending focus change */ + guint focus_timeout_id; + /* Pending autoraise */ guint autoraise_timeout_id; MetaWindow* autoraise_window; diff --git a/src/core/display.c b/src/core/display.c index f62fad1d0..19c343700 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -50,6 +50,7 @@ #include "workspace-private.h" #include "bell.h" #include +#include #include #include #include "mutter-enum-types.h" @@ -122,6 +123,15 @@ typedef struct Window xwindow; } MetaAutoRaiseData; +typedef struct +{ + MetaDisplay *display; + MetaWindow *window; + int pointer_x; + int pointer_y; +} MetaFocusData; + + G_DEFINE_TYPE(MetaDisplay, meta_display, G_TYPE_OBJECT); /* Signals */ @@ -1039,6 +1049,10 @@ meta_display_close (MetaDisplay *display, meta_display_remove_autoraise_callback (display); + if (display->focus_timeout_id); + g_source_remove (display->focus_timeout_id); + display->focus_timeout_id = 0; + if (display->grab_old_window_stacking) g_list_free (display->grab_old_window_stacking); @@ -1569,6 +1583,103 @@ window_raise_with_delay_callback (void *data) return FALSE; } +static void +meta_display_mouse_mode_focus (MetaDisplay *display, + MetaWindow *window, + guint32 timestamp) { + if (window->type != META_WINDOW_DESKTOP) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing %s at time %u.\n", window->desc, timestamp); + + meta_window_focus (window, timestamp); + + if (meta_prefs_get_auto_raise ()) + meta_display_queue_autoraise_callback (display, window); + else + meta_topic (META_DEBUG_FOCUS, "Auto raise is disabled\n"); + } + else + { + /* In mouse focus mode, we defocus when the mouse *enters* + * the DESKTOP window, instead of defocusing on LeaveNotify. + * This is because having the mouse enter override-redirect + * child windows unfortunately causes LeaveNotify events that + * we can't distinguish from the mouse actually leaving the + * toplevel window as we expect. But, since we filter out + * EnterNotify events on override-redirect windows, this + * alternative mechanism works great. + */ + if (meta_prefs_get_focus_mode() == G_DESKTOP_FOCUS_MODE_MOUSE && + display->expected_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); + meta_display_focus_the_no_focus_window (display, + window->screen, + timestamp); + } + } +} + +static gboolean +window_focus_on_pointer_rest_callback (gpointer data) { + MetaFocusData *focus_data; + MetaDisplay *display; + MetaScreen *screen; + MetaWindow *window; + Window root, child; + int root_x, root_y, x, y; + guint32 timestamp; + guint mask; + + focus_data = data; + display = focus_data->display; + screen = focus_data->window->screen; + + if (meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_CLICK) + goto out; + + meta_error_trap_push (display); + XQueryPointer (display->xdisplay, + screen->xroot, + &root, &child, + &root_x, &root_y, &x, &y, &mask); + meta_error_trap_pop (display); + + if (root_x != focus_data->pointer_x || + root_y != focus_data->pointer_y) + { + focus_data->pointer_x = root_x; + focus_data->pointer_y = root_y; + return TRUE; + } + + /* Explicitly check for the overlay window, as get_focus_window_at_point() + * may return windows that extend underneath the chrome (like + * override-redirect or DESKTOP windows) + */ + if (child == meta_get_overlay_window (screen)) + goto out; + + window = + meta_stack_get_default_focus_window_at_point (screen->stack, + screen->active_workspace, + None, root_x, root_y); + + if (window == NULL) + goto out; + + timestamp = meta_display_get_current_time_roundtrip (display); + meta_display_mouse_mode_focus (display, window, timestamp); + +out: + display->focus_timeout_id = 0; + return FALSE; +} + void meta_display_queue_autoraise_callback (MetaDisplay *display, MetaWindow *window) @@ -1596,6 +1707,37 @@ meta_display_queue_autoraise_callback (MetaDisplay *display, display->autoraise_window = window; } +/* The interval, in milliseconds, we use in focus-follows-mouse + * mode to check whether the pointer has stopped moving after a + * crossing event. + */ +#define FOCUS_TIMEOUT_DELAY 25 + +static void +meta_display_queue_focus_callback (MetaDisplay *display, + MetaWindow *window, + int pointer_x, + int pointer_y) +{ + MetaFocusData *focus_data; + + focus_data = g_new (MetaFocusData, 1); + focus_data->display = display; + focus_data->window = window; + focus_data->pointer_x = pointer_x; + focus_data->pointer_y = pointer_y; + + if (display->focus_timeout_id != 0) + g_source_remove (display->focus_timeout_id); + + display->focus_timeout_id = + g_timeout_add_full (G_PRIORITY_DEFAULT, + FOCUS_TIMEOUT_DELAY, + window_focus_on_pointer_rest_callback, + focus_data, + g_free); +} + #if 0 static void handle_net_restack_window (MetaDisplay* display, @@ -2084,52 +2226,26 @@ event_callback (XEvent *event, case G_DESKTOP_FOCUS_MODE_SLOPPY: case G_DESKTOP_FOCUS_MODE_MOUSE: display->mouse_mode = TRUE; - if (window->type != META_WINDOW_DOCK && - window->type != META_WINDOW_DESKTOP) + if (window->type != META_WINDOW_DOCK) { meta_topic (META_DEBUG_FOCUS, - "Focusing %s due to enter notify with serial %lu " - "at time %lu, and setting display->mouse_mode to " - "TRUE.\n", - window->desc, + "Queuing a focus change for %s due to " + "enter notify with serial %lu at time %lu, " + "and setting display->mouse_mode to TRUE.\n", + window->desc, event->xany.serial, event->xcrossing.time); - meta_window_focus (window, event->xcrossing.time); + if (meta_prefs_get_focus_change_on_pointer_rest()) + meta_display_queue_focus_callback (display, window, + event->xcrossing.x_root, + event->xcrossing.y_root); + else + meta_display_mouse_mode_focus (display, window, + event->xcrossing.time); /* stop ignoring stuff */ reset_ignored_crossing_serials (display); - - if (meta_prefs_get_auto_raise ()) - { - meta_display_queue_autoraise_callback (display, window); - } - else - { - meta_topic (META_DEBUG_FOCUS, - "Auto raise is disabled\n"); - } - } - /* In mouse focus mode, we defocus when the mouse *enters* - * the DESKTOP window, instead of defocusing on LeaveNotify. - * This is because having the mouse enter override-redirect - * child windows unfortunately causes LeaveNotify events that - * we can't distinguish from the mouse actually leaving the - * toplevel window as we expect. But, since we filter out - * EnterNotify events on override-redirect windows, this - * alternative mechanism works great. - */ - if (window->type == META_WINDOW_DESKTOP && - meta_prefs_get_focus_mode() == G_DESKTOP_FOCUS_MODE_MOUSE && - display->expected_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); - meta_display_focus_the_no_focus_window (display, - window->screen, - event->xcrossing.time); } break; case G_DESKTOP_FOCUS_MODE_CLICK: diff --git a/src/core/prefs.c b/src/core/prefs.c index f67b2831b..802d620ea 100644 --- a/src/core/prefs.c +++ b/src/core/prefs.c @@ -87,6 +87,7 @@ static gboolean application_based = FALSE; static gboolean disable_workarounds = FALSE; static gboolean auto_raise = FALSE; static gboolean auto_raise_delay = 500; +static gboolean focus_change_on_pointer_rest = FALSE; static gboolean bell_is_visible = FALSE; static gboolean bell_is_audible = TRUE; static gboolean gnome_accessibility = FALSE; @@ -304,6 +305,13 @@ static MetaBoolPreference preferences_bool[] = }, &auto_raise, }, + { + { "focus-change-on-pointer-rest", + SCHEMA_MUTTER, + META_PREF_FOCUS_CHANGE_ON_POINTER_REST, + }, + &focus_change_on_pointer_rest + }, { { "visual-bell", SCHEMA_GENERAL, @@ -1608,6 +1616,9 @@ meta_preference_to_string (MetaPreference pref) case META_PREF_AUTO_RAISE_DELAY: return "AUTO_RAISE_DELAY"; + case META_PREF_FOCUS_CHANGE_ON_POINTER_REST: + return "FOCUS_CHANGE_ON_POINTER_REST"; + case META_PREF_BUTTON_LAYOUT: return "BUTTON_LAYOUT"; @@ -2046,6 +2057,12 @@ meta_prefs_get_auto_raise_delay (void) return auto_raise_delay; } +gboolean +meta_prefs_get_focus_change_on_pointer_rest () +{ + return focus_change_on_pointer_rest; +} + gboolean meta_prefs_get_gnome_accessibility () { diff --git a/src/meta/prefs.h b/src/meta/prefs.h index bbbe76868..ff6984cc2 100644 --- a/src/meta/prefs.h +++ b/src/meta/prefs.h @@ -45,6 +45,7 @@ typedef enum META_PREF_ACTION_RIGHT_CLICK_TITLEBAR, META_PREF_AUTO_RAISE, META_PREF_AUTO_RAISE_DELAY, + META_PREF_FOCUS_CHANGE_ON_POINTER_REST, META_PREF_THEME, META_PREF_TITLEBAR_FONT, META_PREF_NUM_WORKSPACES, @@ -100,6 +101,7 @@ gboolean meta_prefs_get_application_based (void); gboolean meta_prefs_get_disable_workarounds (void); gboolean meta_prefs_get_auto_raise (void); int meta_prefs_get_auto_raise_delay (void); +gboolean meta_prefs_get_focus_change_on_pointer_rest (void); gboolean meta_prefs_get_gnome_accessibility (void); gboolean meta_prefs_get_gnome_animations (void); gboolean meta_prefs_get_edge_tiling (void); diff --git a/src/org.gnome.mutter.gschema.xml.in b/src/org.gnome.mutter.gschema.xml.in index e23ad8175..a2b9eec0b 100644 --- a/src/org.gnome.mutter.gschema.xml.in +++ b/src/org.gnome.mutter.gschema.xml.in @@ -63,6 +63,16 @@ + + false + <_summary>Delay focus changes until the pointer stops moving + <_description> + If set to true, and the focus mode is either "sloppy" or "mouse" + then the focus will not be changed immediately when entering a + window, but only after the pointer stops moving. + + + 10