Rework window / actor focus handling
The duality of the Clutter's key focus and mutter's window focus has long been a problem for us in lots of case, and caused us to create large and complicated hacks to get around the issue, including GrabHelper's focus grab model. Instead of doing this, tie basic focus management into the core of gnome-shell, instead of requiring complex "application-level" management to get it done right. Do this by making sure that only one of an actor or window can be focused at the same time, and apply the appropriate logic to drop one or the other, reactively. Modals are considered a special case, as we grab all keyboard events, but at the X level, the client window still has focus. Make sure to not do any input synchronization when we have a modal. At the same time, remove the FOCUSED input mode, as it's no longer necessary. https://bugzilla.gnome.org/show_bug.cgi?id=700735
This commit is contained in:
parent
393577ee78
commit
93dc7a51c0
@ -58,14 +58,10 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
focusGroup: function(item, timestamp) {
|
focusGroup: function(item, timestamp) {
|
||||||
if (item.focusCallback) {
|
if (item.focusCallback)
|
||||||
item.focusCallback(timestamp);
|
item.focusCallback(timestamp);
|
||||||
} else {
|
else
|
||||||
if (global.stage_input_mode == Shell.StageInputMode.NORMAL)
|
|
||||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
|
||||||
|
|
||||||
item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sort the items into a consistent order; panel first, tray last,
|
// Sort the items into a consistent order; panel first, tray last,
|
||||||
@ -136,8 +132,6 @@ const CtrlAltTabManager = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_focusWindows: function(timestamp) {
|
_focusWindows: function(timestamp) {
|
||||||
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
|
||||||
global.stage.key_focus = null;
|
|
||||||
global.screen.focus_default_window(timestamp);
|
global.screen.focus_default_window(timestamp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -528,17 +528,10 @@ const LayoutManager = new Lang.Class({
|
|||||||
get focusIndex() {
|
get focusIndex() {
|
||||||
let i = Main.layoutManager.primaryIndex;
|
let i = Main.layoutManager.primaryIndex;
|
||||||
|
|
||||||
if (global.stage_input_mode == Shell.StageInputMode.FOCUSED ||
|
if (global.stage.key_focus != null)
|
||||||
global.stage_input_mode == Shell.StageInputMode.FULLSCREEN) {
|
i = this.findIndexForActor(global.stage.key_focus);
|
||||||
let focusActor = global.stage.key_focus;
|
else if (global.display.focus_window != null)
|
||||||
if (focusActor)
|
i = global.display.focus_window.get_monitor();
|
||||||
i = this.findIndexForActor(focusActor);
|
|
||||||
} else {
|
|
||||||
let focusWindow = global.display.focus_window;
|
|
||||||
if (focusWindow)
|
|
||||||
i = focusWindow.get_monitor();
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ const AppMenuButton = new Lang.Class({
|
|||||||
// If the app has just lost focus to the panel, pretend
|
// If the app has just lost focus to the panel, pretend
|
||||||
// nothing happened; otherwise you can't keynav to the
|
// nothing happened; otherwise you can't keynav to the
|
||||||
// app menu.
|
// app menu.
|
||||||
if (global.stage_input_mode == Shell.StageInputMode.FOCUSED)
|
if (global.stage.key_focus != null)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._sync();
|
this._sync();
|
||||||
|
@ -94,6 +94,8 @@ struct _ShellGlobal {
|
|||||||
guint32 xdnd_timestamp;
|
guint32 xdnd_timestamp;
|
||||||
|
|
||||||
gint64 last_gc_end_time;
|
gint64 last_gc_end_time;
|
||||||
|
|
||||||
|
gboolean has_modal;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -529,6 +531,18 @@ shell_global_get (void)
|
|||||||
return the_object;
|
return the_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static guint32
|
||||||
|
get_current_time_maybe_roundtrip (ShellGlobal *global)
|
||||||
|
{
|
||||||
|
guint32 time;
|
||||||
|
|
||||||
|
time = shell_global_get_current_time (global);
|
||||||
|
if (time != CurrentTime)
|
||||||
|
return time;
|
||||||
|
|
||||||
|
return meta_display_get_current_time_roundtrip (global->meta_display);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
focus_window_changed (MetaDisplay *display,
|
focus_window_changed (MetaDisplay *display,
|
||||||
GParamSpec *param,
|
GParamSpec *param,
|
||||||
@ -536,9 +550,58 @@ focus_window_changed (MetaDisplay *display,
|
|||||||
{
|
{
|
||||||
ShellGlobal *global = user_data;
|
ShellGlobal *global = user_data;
|
||||||
|
|
||||||
if (global->input_mode == SHELL_STAGE_INPUT_MODE_FOCUSED &&
|
if (global->has_modal)
|
||||||
meta_display_get_focus_window (display) != NULL)
|
return;
|
||||||
shell_global_set_stage_input_mode (global, SHELL_STAGE_INPUT_MODE_NORMAL);
|
|
||||||
|
/* If the stage window became unfocused, drop the key focus
|
||||||
|
* on Clutter's side. */
|
||||||
|
if (!meta_stage_is_focused (global->meta_screen))
|
||||||
|
clutter_stage_set_key_focus (global->stage, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClutterActor *
|
||||||
|
get_key_focused_actor (ShellGlobal *global)
|
||||||
|
{
|
||||||
|
ClutterActor *actor;
|
||||||
|
|
||||||
|
actor = clutter_stage_get_key_focus (global->stage);
|
||||||
|
|
||||||
|
/* If there's no explicit key focus, clutter_stage_get_key_focus()
|
||||||
|
* returns the stage. This is a terrible API. */
|
||||||
|
if (actor == CLUTTER_ACTOR (global->stage))
|
||||||
|
actor = NULL;
|
||||||
|
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sync_stage_window_focus (ShellGlobal *global)
|
||||||
|
{
|
||||||
|
ClutterActor *actor;
|
||||||
|
|
||||||
|
if (global->has_modal)
|
||||||
|
return;
|
||||||
|
|
||||||
|
actor = get_key_focused_actor (global);
|
||||||
|
|
||||||
|
/* An actor got key focus and the stage needs to be focused. */
|
||||||
|
if (actor != NULL && !meta_stage_is_focused (global->meta_screen))
|
||||||
|
meta_focus_stage_window (global->meta_screen,
|
||||||
|
get_current_time_maybe_roundtrip (global));
|
||||||
|
|
||||||
|
/* An actor dropped key focus. Focus the default window. */
|
||||||
|
else if (actor == NULL && meta_stage_is_focused (global->meta_screen))
|
||||||
|
meta_screen_focus_default_window (global->meta_screen,
|
||||||
|
get_current_time_maybe_roundtrip (global));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
focus_actor_changed (ClutterStage *stage,
|
||||||
|
GParamSpec *param,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ShellGlobal *global = user_data;
|
||||||
|
sync_stage_window_focus (global);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -552,12 +615,6 @@ focus_window_changed (MetaDisplay *display,
|
|||||||
* passes through clicks outside that region. When it is
|
* passes through clicks outside that region. When it is
|
||||||
* %SHELL_STAGE_INPUT_MODE_FULLSCREEN, the stage absorbs all input.
|
* %SHELL_STAGE_INPUT_MODE_FULLSCREEN, the stage absorbs all input.
|
||||||
*
|
*
|
||||||
* When the input mode is %SHELL_STAGE_INPUT_MODE_FOCUSED, the pointer
|
|
||||||
* is handled as with %SHELL_STAGE_INPUT_MODE_NORMAL, but additionally
|
|
||||||
* the stage window has the keyboard focus. If the stage loses the
|
|
||||||
* focus (eg, because the user clicked into a window) the input mode
|
|
||||||
* will revert to %SHELL_STAGE_INPUT_MODE_NORMAL.
|
|
||||||
*
|
|
||||||
* Note that whenever a mutter-internal Gtk widget has a pointer grab,
|
* Note that whenever a mutter-internal Gtk widget has a pointer grab,
|
||||||
* the shell goes unresponsive and passes things to the underlying GTK+
|
* the shell goes unresponsive and passes things to the underlying GTK+
|
||||||
* widget to ensure that the widget gets any clicks it is expecting.
|
* widget to ensure that the widget gets any clicks it is expecting.
|
||||||
@ -579,10 +636,6 @@ shell_global_set_stage_input_mode (ShellGlobal *global,
|
|||||||
else
|
else
|
||||||
meta_set_stage_input_region (screen, global->input_region);
|
meta_set_stage_input_region (screen, global->input_region);
|
||||||
|
|
||||||
if (mode == SHELL_STAGE_INPUT_MODE_FOCUSED)
|
|
||||||
meta_focus_stage_window (global->meta_screen,
|
|
||||||
shell_global_get_current_time (global));
|
|
||||||
|
|
||||||
if (mode != global->input_mode)
|
if (mode != global->input_mode)
|
||||||
{
|
{
|
||||||
global->input_mode = mode;
|
global->input_mode = mode;
|
||||||
@ -956,6 +1009,8 @@ _shell_global_set_plugin (ShellGlobal *global,
|
|||||||
"End of stage page repaint",
|
"End of stage page repaint",
|
||||||
"");
|
"");
|
||||||
|
|
||||||
|
g_signal_connect (global->stage, "notify::key-focus",
|
||||||
|
G_CALLBACK (focus_actor_changed), global);
|
||||||
g_signal_connect (global->meta_display, "notify::focus-window",
|
g_signal_connect (global->meta_display, "notify::focus-window",
|
||||||
G_CALLBACK (focus_window_changed), global);
|
G_CALLBACK (focus_window_changed), global);
|
||||||
|
|
||||||
@ -991,7 +1046,13 @@ shell_global_begin_modal (ShellGlobal *global,
|
|||||||
guint32 timestamp,
|
guint32 timestamp,
|
||||||
MetaModalOptions options)
|
MetaModalOptions options)
|
||||||
{
|
{
|
||||||
return meta_plugin_begin_modal (global->plugin, global->stage_xwindow, None, options, timestamp);
|
/* Make it an error to call begin_modal while we already
|
||||||
|
* have a modal active. */
|
||||||
|
if (global->has_modal)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
global->has_modal = meta_plugin_begin_modal (global->plugin, global->stage_xwindow, None, options, timestamp);
|
||||||
|
return global->has_modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1004,7 +1065,23 @@ void
|
|||||||
shell_global_end_modal (ShellGlobal *global,
|
shell_global_end_modal (ShellGlobal *global,
|
||||||
guint32 timestamp)
|
guint32 timestamp)
|
||||||
{
|
{
|
||||||
|
ClutterActor *actor;
|
||||||
|
|
||||||
|
if (!global->has_modal)
|
||||||
|
return;
|
||||||
|
|
||||||
meta_plugin_end_modal (global->plugin, timestamp);
|
meta_plugin_end_modal (global->plugin, timestamp);
|
||||||
|
global->has_modal = FALSE;
|
||||||
|
|
||||||
|
/* If the stage window is unfocused, ensure that there's no
|
||||||
|
* actor focused on Clutter's side. */
|
||||||
|
if (!meta_stage_is_focused (global->meta_screen))
|
||||||
|
clutter_stage_set_key_focus (global->stage, NULL);
|
||||||
|
|
||||||
|
/* An actor dropped key focus. Focus the default window. */
|
||||||
|
else if (get_key_focused_actor (global) && meta_stage_is_focused (global->meta_screen))
|
||||||
|
meta_screen_focus_default_window (global->meta_screen,
|
||||||
|
get_current_time_maybe_roundtrip (global));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -49,7 +49,6 @@ void shell_global_freeze_keyboard (ShellGlobal *global,
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SHELL_STAGE_INPUT_MODE_NORMAL,
|
SHELL_STAGE_INPUT_MODE_NORMAL,
|
||||||
SHELL_STAGE_INPUT_MODE_FOCUSED,
|
|
||||||
SHELL_STAGE_INPUT_MODE_FULLSCREEN
|
SHELL_STAGE_INPUT_MODE_FULLSCREEN
|
||||||
} ShellStageInputMode;
|
} ShellStageInputMode;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user