[gnome-shell] Rework window / actor focus handling



commit 93dc7a51c0e70a6ff5a03c3fd8ebba39d5a13978
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Sat May 18 00:18:13 2013 -0400

    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 edfd6d3..e522909 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 2444a2a..e8dcf1d 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 729a574..795775f 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 4a82b95..5629b62 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 c93cb1b..7d61165 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;
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]