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
This commit is contained in:
Florian Müllner 2012-08-29 04:38:54 +02:00
parent 99cbe762d7
commit 59bc5b7975
5 changed files with 186 additions and 38 deletions

View File

@ -172,6 +172,9 @@ struct _MetaDisplay
/* Pings which we're waiting for a reply from */ /* Pings which we're waiting for a reply from */
GSList *pending_pings; GSList *pending_pings;
/* Pending focus change */
guint focus_timeout_id;
/* Pending autoraise */ /* Pending autoraise */
guint autoraise_timeout_id; guint autoraise_timeout_id;
MetaWindow* autoraise_window; MetaWindow* autoraise_window;

View File

@ -50,6 +50,7 @@
#include "workspace-private.h" #include "workspace-private.h"
#include "bell.h" #include "bell.h"
#include <meta/compositor.h> #include <meta/compositor.h>
#include <meta/compositor-mutter.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/cursorfont.h> #include <X11/cursorfont.h>
#include "mutter-enum-types.h" #include "mutter-enum-types.h"
@ -122,6 +123,15 @@ typedef struct
Window xwindow; Window xwindow;
} MetaAutoRaiseData; } MetaAutoRaiseData;
typedef struct
{
MetaDisplay *display;
MetaWindow *window;
int pointer_x;
int pointer_y;
} MetaFocusData;
G_DEFINE_TYPE(MetaDisplay, meta_display, G_TYPE_OBJECT); G_DEFINE_TYPE(MetaDisplay, meta_display, G_TYPE_OBJECT);
/* Signals */ /* Signals */
@ -1039,6 +1049,10 @@ meta_display_close (MetaDisplay *display,
meta_display_remove_autoraise_callback (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) if (display->grab_old_window_stacking)
g_list_free (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; 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 void
meta_display_queue_autoraise_callback (MetaDisplay *display, meta_display_queue_autoraise_callback (MetaDisplay *display,
MetaWindow *window) MetaWindow *window)
@ -1596,6 +1707,37 @@ meta_display_queue_autoraise_callback (MetaDisplay *display,
display->autoraise_window = window; 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 #if 0
static void static void
handle_net_restack_window (MetaDisplay* display, 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_SLOPPY:
case G_DESKTOP_FOCUS_MODE_MOUSE: case G_DESKTOP_FOCUS_MODE_MOUSE:
display->mouse_mode = TRUE; display->mouse_mode = TRUE;
if (window->type != META_WINDOW_DOCK && if (window->type != META_WINDOW_DOCK)
window->type != META_WINDOW_DESKTOP)
{ {
meta_topic (META_DEBUG_FOCUS, meta_topic (META_DEBUG_FOCUS,
"Focusing %s due to enter notify with serial %lu " "Queuing a focus change for %s due to "
"at time %lu, and setting display->mouse_mode to " "enter notify with serial %lu at time %lu, "
"TRUE.\n", "and setting display->mouse_mode to TRUE.\n",
window->desc, window->desc,
event->xany.serial, event->xany.serial,
event->xcrossing.time); 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 */ /* stop ignoring stuff */
reset_ignored_crossing_serials (display); 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; break;
case G_DESKTOP_FOCUS_MODE_CLICK: case G_DESKTOP_FOCUS_MODE_CLICK:

View File

@ -87,6 +87,7 @@ static gboolean application_based = FALSE;
static gboolean disable_workarounds = FALSE; static gboolean disable_workarounds = FALSE;
static gboolean auto_raise = FALSE; static gboolean auto_raise = FALSE;
static gboolean auto_raise_delay = 500; static gboolean auto_raise_delay = 500;
static gboolean focus_change_on_pointer_rest = FALSE;
static gboolean bell_is_visible = FALSE; static gboolean bell_is_visible = FALSE;
static gboolean bell_is_audible = TRUE; static gboolean bell_is_audible = TRUE;
static gboolean gnome_accessibility = FALSE; static gboolean gnome_accessibility = FALSE;
@ -304,6 +305,13 @@ static MetaBoolPreference preferences_bool[] =
}, },
&auto_raise, &auto_raise,
}, },
{
{ "focus-change-on-pointer-rest",
SCHEMA_MUTTER,
META_PREF_FOCUS_CHANGE_ON_POINTER_REST,
},
&focus_change_on_pointer_rest
},
{ {
{ "visual-bell", { "visual-bell",
SCHEMA_GENERAL, SCHEMA_GENERAL,
@ -1608,6 +1616,9 @@ meta_preference_to_string (MetaPreference pref)
case META_PREF_AUTO_RAISE_DELAY: case META_PREF_AUTO_RAISE_DELAY:
return "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: case META_PREF_BUTTON_LAYOUT:
return "BUTTON_LAYOUT"; return "BUTTON_LAYOUT";
@ -2046,6 +2057,12 @@ meta_prefs_get_auto_raise_delay (void)
return auto_raise_delay; return auto_raise_delay;
} }
gboolean
meta_prefs_get_focus_change_on_pointer_rest ()
{
return focus_change_on_pointer_rest;
}
gboolean gboolean
meta_prefs_get_gnome_accessibility () meta_prefs_get_gnome_accessibility ()
{ {

View File

@ -45,6 +45,7 @@ typedef enum
META_PREF_ACTION_RIGHT_CLICK_TITLEBAR, META_PREF_ACTION_RIGHT_CLICK_TITLEBAR,
META_PREF_AUTO_RAISE, META_PREF_AUTO_RAISE,
META_PREF_AUTO_RAISE_DELAY, META_PREF_AUTO_RAISE_DELAY,
META_PREF_FOCUS_CHANGE_ON_POINTER_REST,
META_PREF_THEME, META_PREF_THEME,
META_PREF_TITLEBAR_FONT, META_PREF_TITLEBAR_FONT,
META_PREF_NUM_WORKSPACES, 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_disable_workarounds (void);
gboolean meta_prefs_get_auto_raise (void); gboolean meta_prefs_get_auto_raise (void);
int meta_prefs_get_auto_raise_delay (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_accessibility (void);
gboolean meta_prefs_get_gnome_animations (void); gboolean meta_prefs_get_gnome_animations (void);
gboolean meta_prefs_get_edge_tiling (void); gboolean meta_prefs_get_edge_tiling (void);

View File

@ -63,6 +63,16 @@
</_description> </_description>
</key> </key>
<key name="focus-change-on-pointer-rest" type="b">
<default>false</default>
<_summary>Delay focus changes until the pointer stops moving</_summary>
<_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.
</_description>
</key>
<key name="draggable-border-width" type="i"> <key name="draggable-border-width" type="i">
<default>10</default> <default>10</default>
<range min="0" max="64"/> <range min="0" max="64"/>