diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 3aa7f75b7..5397e185a 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 f6d0f3d45..31007fbf1 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 f3ead0ff9..664c7a1c6 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 94d5a7987..352b7d600 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 b2f9d8151..41d031da9 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 @@ -23,13 +24,19 @@ 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 @@ -37,6 +44,25 @@ enum { 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) { - return shell_app_info_launch (self->info, error); + 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) +{ + /* 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; } /** @@ -399,6 +491,18 @@ _shell_app_new (ShellAppInfo *info) return app; } +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 0e2516162..42e071eac 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 9f91917e3..bfc89d3b3 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); } @@ -769,6 +797,24 @@ shell_window_tracker_get_running_apps (ShellWindowTracker *monitor, return ret; } +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, @@ -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); } /**