[gnome-shell] Major ShellApp API cleanup, startup notification, window focus handling



commit 6aaf4b87d5310e83c424212b339c2ebf05e65e17
Author: Colin Walters <walters verbum org>
Date:   Sat Apr 3 14:07:44 2010 -0400

    Major ShellApp API cleanup, startup notification, window focus handling
    
    This patch combines several high level changes which are conceptually
    independent but in practice rather intertwined.
    
    * Add a "state" property to ShellApp which reflects whether it's
      stopped, starting, or started.  This will allow us to later clean
      up all the callers that are using ".get_windows().length > 0" as
      a proxy for this property
    * Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
      A lot of code was calling .launch, but it's signficantly clearer
      if we call this ".open_new_window()", and later if we gain the ability
      to call into an application's menu, we can implement this correctly rather
      than trying to update all .launch callers.
    * Because ShellApp now has a "starting" state, rebase panel.js on top of
      this so that when we get a startup-notification sequence for an app
      and transition it to starting, it becomes the focus app, and panel.js
      cleanly just tracks the focus app, rather than bouncing between SN
      sequences.  This removes display of non-app startup sequences, which
      I consider an acceptable action in light of the committed changes
      to startup-notification and GTK+.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=614755

 js/ui/appDisplay.js        |   84 ++++++++----------------
 js/ui/panel.js             |   34 +---------
 src/shell-app-private.h    |    2 +
 src/shell-app-system.c     |    5 --
 src/shell-app.c            |  152 ++++++++++++++++++++++++++++++++++++++++++--
 src/shell-app.h            |   13 ++++-
 src/shell-window-tracker.c |   62 ++++++++++++++----
 7 files changed, 239 insertions(+), 113 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 3aa7f75..5397e18 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -286,17 +286,12 @@ BaseAppSearchProvider.prototype = {
 
     activateResult: function(id) {
         let app = this._appSys.get_app(id);
-        let windows = app.get_windows();
-
-        if (windows.length > 0)
-            Main.activateWindow(windows[0]);
-        else
-            app.launch();
+        app.activate();
     },
 
     dragActivateResult: function(id) {
         let app = this._appSys.get_app(id);
-        app.launch();
+        app.open_new_window();
     }
 };
 
@@ -391,7 +386,6 @@ function AppWellIcon(app) {
 AppWellIcon.prototype = {
     _init : function(app) {
         this.app = app;
-        this._running = false;
         this.actor = new St.Clickable({ style_class: 'app-well-app',
                                          reactive: true,
                                          x_fill: true,
@@ -415,20 +409,20 @@ AppWellIcon.prototype = {
         this.actor.connect('hide', Lang.bind(this, this._onHideDestroy));
         this.actor.connect('destroy', Lang.bind(this, this._onHideDestroy));
 
-        this._appWindowChangedId = 0;
+        this._stateChangedId = 0;
         this._menuTimeoutId = 0;
     },
 
     _onShow: function() {
-        this._appWindowChangedId = this.app.connect('windows-changed',
-                                                    Lang.bind(this,
-                                                              this._updateStyleClass));
-        this._updateStyleClass();
+        this._stateChangedId = this.app.connect('notify::state',
+                                                Lang.bind(this,
+                                                          this._onStateChanged));
+        this._onStateChanged();
     },
 
     _onHideDestroy: function() {
-        if (this._appWindowChangedId > 0)
-            this.app.disconnect(this._appWindowChangedId);
+        if (this._stateChangedId > 0)
+            this.app.disconnect(this._stateChangedId);
         this._removeMenuTimeout();
     },
 
@@ -439,16 +433,11 @@ AppWellIcon.prototype = {
         }
     },
 
-    _updateStyleClass: function() {
-        let windows = this.app.get_windows();
-        let running = windows.length > 0;
-        this._running = running;
-        let style = "app-well-app";
-        if (this._running)
-            style += " running";
-        if (this._selected)
-            style += " selected";
-        this.actor.style_class = style;
+    _onStateChanged: function() {
+        if (this.app.state != Shell.AppState.STOPPED)
+            this.actor.add_style_class_name('running');
+        else
+            this.actor.remove_style_class_name('running');
     },
 
     _onButtonPress: function(actor, event) {
@@ -506,11 +495,6 @@ AppWellIcon.prototype = {
         return false;
     },
 
-    activateMostRecentWindow: function () {
-        let mostRecentWindow = this.app.get_windows()[0];
-        Main.activateWindow(mostRecentWindow);
-    },
-
     highlightWindow: function(metaWindow) {
         if (this._didActivateWindow)
             return;
@@ -523,13 +507,17 @@ AppWellIcon.prototype = {
         if (metaWindow) {
             this._didActivateWindow = true;
             Main.activateWindow(metaWindow);
-        } else
+        } else {
             Main.overview.hide();
+        }
     },
 
     setSelected: function (isSelected) {
         this._selected = isSelected;
-        this._updateStyleClass();
+        if (this._selected)
+            this.actor.add_style_class_name('selected');
+        else
+            this.actor.remove_style_class_name('selected');
     },
 
     _onMenuPoppedUp: function() {
@@ -553,38 +541,24 @@ AppWellIcon.prototype = {
     },
 
     _getRunning: function() {
-        return this.app.get_windows().length > 0;
+        return this.app.state != Shell.AppState.STOPPED;
     },
 
     _onActivate: function (event) {
-        let running = this._getRunning();
         this.emit('launching');
+        let modifiers = Shell.get_event_state(event);
 
-        if (!running) {
-            this.app.launch();
-            Main.overview.hide();
+        if (modifiers & Clutter.ModifierType.CONTROL_MASK
+            && this.app.state == Shell.AppState.RUNNING) {
+            this.app.open_new_window();
         } else {
-            let modifiers = Shell.get_event_state(event);
-
-            if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
-                this.app.launch();
-                Main.overview.hide();
-            } else {
-                this.activateMostRecentWindow();
-            }
+            this.app.activate();
         }
+        Main.overview.hide();
     },
 
     shellWorkspaceLaunch : function() {
-        // Here we just always launch the application again, even if we know
-        // it was already running.  For most applications this
-        // should have the effect of creating a new window, whether that's
-        // a second process (in the case of Calculator) or IPC to existing
-        // instance (Firefox).  There are a few less-sensical cases such
-        // as say Pidgin, but ideally what we do there is have the app
-        // express to us that it doesn't do relaunch=new-window in the
-        // .desktop file.
-        this.app.launch();
+        this.app.open_new_window();
     },
 
     getDragActor: function() {
@@ -842,7 +816,7 @@ AppIconMenu.prototype = {
             let metaWindow = child._window;
             this.emit('activate-window', metaWindow);
         } else if (child == this._newWindowMenuItem) {
-            this._source.app.launch();
+            this._source.app.open_new_window();
             this.emit('activate-window', null);
         } else if (child == this._toggleFavoriteMenuItem) {
             let favs = AppFavorites.getAppFavorites();
diff --git a/js/ui/panel.js b/js/ui/panel.js
index f6d0f3d..31007fb 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -135,8 +135,6 @@ AppPanelMenu.prototype = {
         this._metaDisplay = global.screen.get_display();
 
         this._focusedApp = null;
-        this._activeSequence = null;
-        this._startupSequences = {};
 
         this.actor = new St.Bin({ name: 'appMenu' });
         this._container = new Shell.GenericContainer();
@@ -159,7 +157,6 @@ AppPanelMenu.prototype = {
 
         let tracker = Shell.WindowTracker.get_default();
         tracker.connect('notify::focus-app', Lang.bind(this, this._sync));
-        tracker.connect('startup-sequence-changed', Lang.bind(this, this._sync));
         // For now just resync on all running state changes; this is mainly to handle
         // cases where the focused window's application changes without the focus
         // changing.  An example case is how we map Firefox based on the window
@@ -232,44 +229,19 @@ AppPanelMenu.prototype = {
         let tracker = Shell.WindowTracker.get_default();
 
         let focusedApp = tracker.focus_app;
-
-        let lastSequence = null;
-        let sequences = tracker.get_startup_sequences();
-        if (sequences.length > 0) {
-            lastSequence = sequences[sequences.length - 1];
-        }
-
-        // If the currently focused app hasn't changed and the current
-        // startup sequence hasn't changed, we have nothing to do
-        if (focusedApp == this._focusedApp
-            && ((lastSequence == null && this._activeSequence == null)
-                || (lastSequence != null && this._activeSequence != null
-                    && lastSequence.get_id() == this._activeSequence.get_id())))
-            return;
+        if (focusedApp == this._focusedApp)
+          return;
 
         if (this._iconBox.child != null)
             this._iconBox.child.destroy();
         this._iconBox.hide();
         this._label.setText('');
 
-        if (focusedApp == null && lastSequence != null)
-            focusedApp = lastSequence.get_app();
-
         this._focusedApp = focusedApp;
-        this._activeSequence = lastSequence;
 
-        let icon;
         if (this._focusedApp != null) {
-            icon = this._focusedApp.get_faded_icon(AppDisplay.APPICON_SIZE);
+            let icon = this._focusedApp.get_faded_icon(AppDisplay.APPICON_SIZE);
             this._label.setText(this._focusedApp.get_name());
-        } else if (this._activeSequence != null) {
-            icon = this._activeSequence.create_icon(AppDisplay.APPICON_SIZE);
-            this._label.setText(this._activeSequence.get_name());
-        } else {
-            icon = null;
-        }
-
-        if (icon != null) {
             this._iconBox.set_child(icon);
             this._iconBox.show();
         }
diff --git a/src/shell-app-private.h b/src/shell-app-private.h
index f3ead0f..664c7a1 100644
--- a/src/shell-app-private.h
+++ b/src/shell-app-private.h
@@ -13,6 +13,8 @@ ShellApp* _shell_app_new_for_window (MetaWindow *window);
 
 ShellApp* _shell_app_new (ShellAppInfo *appinfo);
 
+void _shell_app_set_starting (ShellApp *app, gboolean starting);
+
 void _shell_app_add_window (ShellApp *app, MetaWindow *window);
 
 void _shell_app_remove_window (ShellApp *app, MetaWindow *window);
diff --git a/src/shell-app-system.c b/src/shell-app-system.c
index 94d5a79..352b7d6 100644
--- a/src/shell-app-system.c
+++ b/src/shell-app-system.c
@@ -1248,11 +1248,6 @@ shell_app_info_launch_full (ShellAppInfo *info,
   if (timestamp == 0)
     timestamp = clutter_get_current_event_time ();
 
-  /* Shell design calls for on application launch, no window is focused,
-   * and we have startup notification displayed.
-   */
-  meta_display_focus_the_no_focus_window (display, screen, timestamp);
-
   if (workspace < 0)
     workspace = meta_screen_get_active_workspace_index (screen);
 
diff --git a/src/shell-app.c b/src/shell-app.c
index b2f9d81..41d031d 100644
--- a/src/shell-app.c
+++ b/src/shell-app.c
@@ -2,9 +2,10 @@
 
 #include "config.h"
 
+#include "st.h"
 #include "shell-app-private.h"
 #include "shell-global.h"
-#include "st.h"
+#include "shell-enum-types.h"
 
 #include <string.h>
 
@@ -23,20 +24,45 @@ struct _ShellApp
 
   guint workspace_switch_id;
 
-  gboolean window_sort_stale;
   GSList *windows;
 
+  ShellAppState state;
+  gboolean window_sort_stale : 1;
 };
 
 G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
 
 enum {
+  PROP_0,
+  PROP_STATE
+};
+
+enum {
   WINDOWS_CHANGED,
   LAST_SIGNAL
 };
 
 static guint shell_app_signals[LAST_SIGNAL] = { 0 };
 
+static void
+shell_app_get_property (GObject    *gobject,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  ShellApp *app = SHELL_APP (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_STATE:
+      g_value_set_enum (value, app->state);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
 const char *
 shell_app_get_id (ShellApp *app)
 {
@@ -219,11 +245,77 @@ shell_app_is_transient (ShellApp *app)
   return shell_app_info_is_transient (app->info);
 }
 
-gboolean
-shell_app_launch (ShellApp  *self,
-                  GError   **error)
+/**
+ * shell_app_activate:
+ * @app: a #ShellApp
+ *
+ * Perform an appropriate default action for operating on this application,
+ * dependent on its current state.  For example, if the application is not
+ * currently running, launch it.  If it is running, activate the most recently
+ * used window.
+ */
+void
+shell_app_activate (ShellApp  *app)
+{
+  switch (app->state)
+    {
+      case SHELL_APP_STATE_STOPPED:
+        /* TODO sensibly handle this error */
+        shell_app_info_launch (app->info, NULL);
+        break;
+      case SHELL_APP_STATE_STARTING:
+        break;
+      case SHELL_APP_STATE_RUNNING:
+        {
+          GSList *windows = shell_app_get_windows (app);
+          if (windows)
+            {
+              ShellGlobal *global = shell_global_get ();
+              MetaScreen *screen = shell_global_get_screen (global);
+              MetaWorkspace *active = meta_screen_get_active_workspace (screen);
+              MetaWindow *window = windows->data;
+              MetaWorkspace *workspace = meta_window_get_workspace (window);
+
+              if (active != workspace)
+                meta_workspace_activate_with_focus (workspace, window, shell_global_get_current_time (global));
+              else
+                meta_window_activate (window, shell_global_get_current_time (global));
+            }
+        }
+        break;
+    }
+}
+
+/**
+ * shell_app_open_new_window:
+ * @app: a #ShellApp
+ *
+ * Request that the application create a new window.
+ */
+void
+shell_app_open_new_window (ShellApp *app)
 {
-  return shell_app_info_launch (self->info, error);
+  /* Here we just always launch the application again, even if we know
+   * it was already running.  For most applications this
+   * should have the effect of creating a new window, whether that's
+   * a second process (in the case of Calculator) or IPC to existing
+   * instance (Firefox).  There are a few less-sensical cases such
+   * as say Pidgin.  Ideally, we have the application express to us
+   * that it supports an explicit new-window action.
+   */
+  shell_app_info_launch (app->info, NULL);
+}
+
+/**
+ * shell_app_get_state:
+ * @app: a #ShellApp
+ *
+ * Returns: State of the application
+ */
+ShellAppState
+shell_app_get_state (ShellApp *app)
+{
+  return app->state;
 }
 
 /**
@@ -400,6 +492,18 @@ _shell_app_new (ShellAppInfo    *info)
 }
 
 static void
+shell_app_state_transition (ShellApp      *app,
+                            ShellAppState  state)
+{
+  if (app->state == state)
+    return;
+  g_return_if_fail (!(app->state == SHELL_APP_STATE_RUNNING &&
+                      state == SHELL_APP_STATE_STARTING));
+  app->state = state;
+  g_object_notify (G_OBJECT (app), "state");
+}
+
+static void
 shell_app_on_unmanaged (MetaWindow      *window,
                         ShellApp *app)
 {
@@ -445,6 +549,9 @@ _shell_app_add_window (ShellApp        *app,
   g_signal_connect (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app);
   app->window_sort_stale = TRUE;
 
+  if (app->state != SHELL_APP_STATE_RUNNING)
+    shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
+
   g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
 
   if (app->workspace_switch_id == 0)
@@ -484,12 +591,27 @@ _shell_app_remove_window (ShellApp   *app,
   g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
 
   if (app->windows == NULL)
-    disconnect_workspace_switch (app);
+    {
+      disconnect_workspace_switch (app);
+
+      shell_app_state_transition (app, SHELL_APP_STATE_STOPPED);
+    }
+}
+
+void
+_shell_app_set_starting (ShellApp        *app,
+                         gboolean         starting)
+{
+  if (starting && app->state == SHELL_APP_STATE_STOPPED)
+    shell_app_state_transition (app, SHELL_APP_STATE_STARTING);
+  else if (!starting && app->state == SHELL_APP_STATE_STARTING)
+    shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
 }
 
 static void
 shell_app_init (ShellApp *self)
 {
+  self->state = SHELL_APP_STATE_STOPPED;
 }
 
 static void
@@ -516,6 +638,7 @@ shell_app_class_init(ShellAppClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
+  gobject_class->get_property = shell_app_get_property;
   gobject_class->dispose = shell_app_dispose;
 
   shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed",
@@ -525,4 +648,19 @@ shell_app_class_init(ShellAppClass *klass)
                                      NULL, NULL,
                                      g_cclosure_marshal_VOID__VOID,
                                      G_TYPE_NONE, 0);
+
+  /**
+   * ShellApp:state:
+   *
+   * The high-level state of the application, effectively whether it's
+   * running or not, or transitioning between those states.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_STATE,
+                                   g_param_spec_enum ("state",
+                                                      "State",
+                                                      "Application state",
+                                                      SHELL_TYPE_APP_STATE,
+                                                      SHELL_APP_STATE_STOPPED,
+                                                      G_PARAM_READABLE));
 }
diff --git a/src/shell-app.h b/src/shell-app.h
index 0e25161..42e071e 100644
--- a/src/shell-app.h
+++ b/src/shell-app.h
@@ -25,6 +25,12 @@ struct _ShellAppClass
 
 };
 
+typedef enum {
+  SHELL_APP_STATE_STOPPED,
+  SHELL_APP_STATE_STARTING,
+  SHELL_APP_STATE_RUNNING
+} ShellAppState;
+
 GType shell_app_get_type (void) G_GNUC_CONST;
 
 const char *shell_app_get_id (ShellApp *app);
@@ -34,7 +40,12 @@ ClutterActor *shell_app_get_faded_icon (ShellApp *app, float size);
 char *shell_app_get_name (ShellApp *app);
 char *shell_app_get_description (ShellApp *app);
 gboolean shell_app_is_transient (ShellApp *app);
-gboolean shell_app_launch (ShellApp *info, GError   **error);
+
+void shell_app_activate (ShellApp *app);
+
+void shell_app_open_new_window (ShellApp *app);
+
+ShellAppState shell_app_get_state (ShellApp *app);
 
 guint shell_app_get_n_windows (ShellApp *app);
 
diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c
index 9f91917..bfc89d3 100644
--- a/src/shell-window-tracker.c
+++ b/src/shell-window-tracker.c
@@ -91,7 +91,8 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 static void shell_window_tracker_finalize (GObject *object);
-
+static void set_focus_app (ShellWindowTracker  *tracker,
+                           ShellApp            *new_focus_app);
 static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellWindowTracker *tracker);
 
 static void track_window (ShellWindowTracker *monitor, MetaWindow *window);
@@ -634,6 +635,33 @@ on_startup_sequence_changed (MetaScreen            *screen,
                              SnStartupSequence     *sequence,
                              ShellWindowTracker    *self)
 {
+  ShellApp *app;
+
+  app = shell_startup_sequence_get_app ((ShellStartupSequence*)sequence);
+  if (app)
+    {
+      gboolean starting = !sn_startup_sequence_get_completed (sequence);
+
+      /* The Shell design calls for on application launch, the app title
+       * appears at top, and no X window is focused.  So when we get
+       * a startup-notification for this app, transition it to STARTING
+       * if it's currently stopped, set it as our application focus,
+       * but focus the no_focus window.
+       */
+      if (starting && shell_app_get_state (app) == SHELL_APP_STATE_STOPPED)
+        {
+          MetaScreen *screen = shell_global_get_screen (shell_global_get ());
+          MetaDisplay *display = meta_screen_get_display (screen);
+          long tv_sec, tv_usec;
+
+          sn_startup_sequence_get_initiated_time (sequence, &tv_sec, &tv_usec);
+
+          _shell_app_set_starting (app, starting);
+          set_focus_app (self, app);
+          meta_display_focus_the_no_focus_window (display, screen, tv_sec);
+        }
+    }
+
   g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
 }
 
@@ -770,6 +798,24 @@ shell_window_tracker_get_running_apps (ShellWindowTracker *monitor,
 }
 
 static void
+set_focus_app (ShellWindowTracker  *tracker,
+               ShellApp            *new_focus_app)
+{
+  if (new_focus_app == tracker->focus_app)
+    return;
+
+  if (tracker->focus_app != NULL)
+    g_object_unref (tracker->focus_app);
+
+  tracker->focus_app = new_focus_app;
+
+  if (tracker->focus_app != NULL)
+    g_object_ref (tracker->focus_app);
+
+  g_object_notify (G_OBJECT (tracker), "focus-app");
+}
+
+static void
 on_focus_window_changed (MetaDisplay        *display,
                          GParamSpec         *spec,
                          ShellWindowTracker *tracker)
@@ -783,19 +829,7 @@ on_focus_window_changed (MetaDisplay        *display,
   new_focus_win = meta_display_get_focus_window (display);
   new_focus_app = new_focus_win ? g_hash_table_lookup (tracker->window_to_app, new_focus_win) : NULL;
 
-  if (new_focus_app == tracker->focus_app)
-    return;
-
-  if (tracker->focus_app != NULL)
-    g_object_unref (tracker->focus_app);
-
-  if (tracker->focus_app != NULL
-      && (!new_focus_win || !new_focus_app))
-    tracker->focus_app = NULL;
-  else
-    tracker->focus_app = g_object_ref (new_focus_app);
-
-  g_object_notify (G_OBJECT (tracker), "focus-app");
+  set_focus_app (tracker, new_focus_app);
 }
 
 /**



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