From 93dc7a51c0e70a6ff5a03c3fd8ebba39d5a13978 Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Sat, 18 May 2013 00:18:13 -0400 Subject: [PATCH] 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 --- js/ui/ctrlAltTab.js | 10 +---- js/ui/layout.js | 15 ++----- js/ui/panel.js | 2 +- src/shell-global.c | 105 ++++++++++++++++++++++++++++++++++++++------ src/shell-global.h | 1 - 5 files changed, 98 insertions(+), 35 deletions(-) diff --git a/js/ui/ctrlAltTab.js b/js/ui/ctrlAltTab.js index edfd6d3cd..e52290905 100644 --- a/js/ui/ctrlAltTab.js +++ b/js/ui/ctrlAltTab.js @@ -58,14 +58,10 @@ const CtrlAltTabManager = new Lang.Class({ }, focusGroup: function(item, timestamp) { - if (item.focusCallback) { + if (item.focusCallback) item.focusCallback(timestamp); - } else { - if (global.stage_input_mode == Shell.StageInputMode.NORMAL) - global.set_stage_input_mode(Shell.StageInputMode.FOCUSED); - + else item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); - } }, // Sort the items into a consistent order; panel first, tray last, @@ -136,8 +132,6 @@ const CtrlAltTabManager = new Lang.Class({ }, _focusWindows: function(timestamp) { - global.set_stage_input_mode(Shell.StageInputMode.NORMAL); - global.stage.key_focus = null; global.screen.focus_default_window(timestamp); } }); diff --git a/js/ui/layout.js b/js/ui/layout.js index 2444a2ab4..e8dcf1d11 100644 --- a/js/ui/layout.js +++ b/js/ui/layout.js @@ -528,17 +528,10 @@ const LayoutManager = new Lang.Class({ get focusIndex() { let i = Main.layoutManager.primaryIndex; - if (global.stage_input_mode == Shell.StageInputMode.FOCUSED || - global.stage_input_mode == Shell.StageInputMode.FULLSCREEN) { - let focusActor = global.stage.key_focus; - if (focusActor) - i = this.findIndexForActor(focusActor); - } else { - let focusWindow = global.display.focus_window; - if (focusWindow) - i = focusWindow.get_monitor(); - } - + if (global.stage.key_focus != null) + i = this.findIndexForActor(global.stage.key_focus); + else if (global.display.focus_window != null) + i = global.display.focus_window.get_monitor(); return i; }, diff --git a/js/ui/panel.js b/js/ui/panel.js index 729a57409..795775f3d 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -449,7 +449,7 @@ const AppMenuButton = new Lang.Class({ // If the app has just lost focus to the panel, pretend // nothing happened; otherwise you can't keynav to the // app menu. - if (global.stage_input_mode == Shell.StageInputMode.FOCUSED) + if (global.stage.key_focus != null) return; } this._sync(); diff --git a/src/shell-global.c b/src/shell-global.c index 4a82b9533..5629b622f 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -94,6 +94,8 @@ struct _ShellGlobal { guint32 xdnd_timestamp; gint64 last_gc_end_time; + + gboolean has_modal; }; enum { @@ -529,6 +531,18 @@ shell_global_get (void) 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 focus_window_changed (MetaDisplay *display, GParamSpec *param, @@ -536,9 +550,58 @@ focus_window_changed (MetaDisplay *display, { ShellGlobal *global = user_data; - if (global->input_mode == SHELL_STAGE_INPUT_MODE_FOCUSED && - meta_display_get_focus_window (display) != NULL) - shell_global_set_stage_input_mode (global, SHELL_STAGE_INPUT_MODE_NORMAL); + if (global->has_modal) + return; + + /* 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 * %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, * the shell goes unresponsive and passes things to the underlying GTK+ * 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 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) { global->input_mode = mode; @@ -956,6 +1009,8 @@ _shell_global_set_plugin (ShellGlobal *global, "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_CALLBACK (focus_window_changed), global); @@ -991,7 +1046,13 @@ shell_global_begin_modal (ShellGlobal *global, guint32 timestamp, 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, guint32 timestamp) { + ClutterActor *actor; + + if (!global->has_modal) + return; + 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 diff --git a/src/shell-global.h b/src/shell-global.h index c93cb1bdf..7d61165c2 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -49,7 +49,6 @@ void shell_global_freeze_keyboard (ShellGlobal *global, typedef enum { SHELL_STAGE_INPUT_MODE_NORMAL, - SHELL_STAGE_INPUT_MODE_FOCUSED, SHELL_STAGE_INPUT_MODE_FULLSCREEN } ShellStageInputMode;