diff --git a/js/ui/altTab.js b/js/ui/altTab.js index 600e1cc33..d063cab21 100644 --- a/js/ui/altTab.js +++ b/js/ui/altTab.js @@ -93,7 +93,7 @@ AltTabPopup.prototype = { // Make the initial selection if (this._appIcons.length == 1) { - if (!backward && this._appIcons[0].windows.length > 1) { + if (!backward && this._appIcons[0].cachedWindows.length > 1) { // For compatibility with the multi-app case below this._select(0, 1); } else @@ -101,9 +101,10 @@ AltTabPopup.prototype = { } else if (backward) { this._select(this._appIcons.length - 1); } else { - if (this._appIcons[0].windows.length > 1) { - let curAppNextWindow = this._appIcons[0].windows[1]; - let nextAppWindow = this._appIcons[1].windows[0]; + let firstWindows = this._appIcons[0].cachedWindows; + if (firstWindows.length > 1) { + let curAppNextWindow = firstWindows[1]; + let nextAppWindow = this._appIcons[1].cachedWindows[0]; // If the next window of the current app is more-recently-used // than the first window of the next app, then select it. @@ -139,11 +140,11 @@ AltTabPopup.prototype = { _nextWindow : function() { return mod(this._currentWindows[this._currentApp] + 1, - this._appIcons[this._currentApp].windows.length); + this._appIcons[this._currentApp].cachedWindows.length); }, _previousWindow : function() { return mod(this._currentWindows[this._currentApp] - 1, - this._appIcons[this._currentApp].windows.length); + this._appIcons[this._currentApp].cachedWindows.length); }, _keyPressEvent : function(actor, event) { @@ -188,7 +189,7 @@ AltTabPopup.prototype = { }, _appActivated : function(appSwitcher, n) { - Main.activateWindow(this._appIcons[n].windows[this._currentWindows[n]]); + Main.activateWindow(this._appIcons[n].cachedWindows[this._currentWindows[n]]); this.destroy(); }, @@ -200,7 +201,7 @@ AltTabPopup.prototype = { }, _windowActivated : function(thumbnailList, n) { - Main.activateWindow(this._appIcons[this._currentApp].windows[n]); + Main.activateWindow(this._appIcons[this._currentApp].cachedWindows[n]); this.destroy(); }, @@ -225,7 +226,7 @@ AltTabPopup.prototype = { _finish : function() { let app = this._appIcons[this._currentApp]; - let window = app.windows[this._currentWindows[this._currentApp]]; + let window = app.cachedWindows[this._currentWindows[this._currentApp]]; Main.activateWindow(window); this.destroy(); }, @@ -267,7 +268,7 @@ AltTabPopup.prototype = { this._appSwitcher.showArrow(app); } else { this._appSwitcher.highlight(app); - if (this._appIcons[this._currentApp].windows.length > 1) + if (this._appIcons[this._currentApp].cachedWindows.length > 1) this._appSwitcher.showArrow(app); } @@ -276,7 +277,7 @@ AltTabPopup.prototype = { this._createThumbnails(); this._currentWindows[this._currentApp] = window; this._thumbnails.highlight(window); - } else if (this._appIcons[this._currentApp].windows.length > 1 && + } else if (this._appIcons[this._currentApp].cachedWindows.length > 1 && !noTimeout) { this._thumbnailTimeoutId = Mainloop.timeout_add ( THUMBNAIL_POPUP_TIME, @@ -289,7 +290,7 @@ AltTabPopup.prototype = { }, _createThumbnails : function() { - this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].windows); + this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].cachedWindows); this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated)); this._thumbnails.connect('item-hovered', Lang.bind(this, this._windowHovered)); @@ -552,8 +553,11 @@ AppSwitcher.prototype = { let workspaceIcons = []; let otherIcons = []; for (let i = 0; i < apps.length; i++) { - let appIcon = new AppIcon.AppIcon({ appInfo: apps[i], + let appIcon = new AppIcon.AppIcon({ app: apps[i], size: POPUP_APPICON_SIZE }); + // Cache the window list now; we don't handle dynamic changes here, + // and we don't want to be continually retrieving it + appIcon.cachedWindows = appIcon.app.get_windows(); if (this._hasWindowsOnWorkspace(appIcon, activeWorkspace)) workspaceIcons.push(appIcon); else @@ -621,35 +625,16 @@ AppSwitcher.prototype = { }, _hasWindowsOnWorkspace: function(appIcon, workspace) { - for (let i = 0; i < appIcon.windows.length; i++) { - if (appIcon.windows[i].get_workspace() == workspace) - return true; - } - return false; - }, - - _hasVisibleWindows : function(appIcon) { - for (let i = 0; i < appIcon.windows.length; i++) { - if (appIcon.windows[i].showing_on_its_workspace()) + let windows = appIcon.cachedWindows; + for (let i = 0; i < windows.length; i++) { + if (windows[i].get_workspace() == workspace) return true; } return false; }, _sortAppIcon : function(appIcon1, appIcon2) { - let vis1 = this._hasVisibleWindows(appIcon1); - let vis2 = this._hasVisibleWindows(appIcon2); - - if (vis1 && !vis2) { - return -1; - } else if (vis2 && !vis1) { - return 1; - } else { - // The app's most-recently-used window is first - // in its list - return (appIcon2.windows[0].get_user_time() - - appIcon1.windows[0].get_user_time()); - } + return appIcon1.app.compare(appIcon2.app); } }; diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index e24ffc2ba..14f246b86 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -476,15 +476,15 @@ AppDisplay.prototype = { Signals.addSignalMethods(AppDisplay.prototype); -function BaseWellItem(appInfo, isFavorite, hasMenu) { - this._init(appInfo, isFavorite, hasMenu); +function BaseWellItem(app, isFavorite, hasMenu) { + this._init(app, isFavorite, hasMenu); } BaseWellItem.prototype = { __proto__: AppIcon.AppIcon.prototype, - _init: function(appInfo, isFavorite) { - AppIcon.AppIcon.prototype._init.call(this, { appInfo: appInfo, + _init: function(app, isFavorite) { + AppIcon.AppIcon.prototype._init.call(this, { app: app, menuType: AppIcon.MenuType.ON_RIGHT, glow: true }); @@ -523,7 +523,7 @@ BaseWellItem.prototype = { // 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.appInfo.launch(); + this.app.get_info().launch(); }, getDragActor: function() { @@ -537,15 +537,15 @@ BaseWellItem.prototype = { } } -function RunningWellItem(appInfo, isFavorite) { - this._init(appInfo, isFavorite); +function RunningWellItem(app, isFavorite) { + this._init(app, isFavorite); } RunningWellItem.prototype = { __proto__: BaseWellItem.prototype, - _init: function(appInfo, isFavorite) { - BaseWellItem.prototype._init.call(this, appInfo, isFavorite); + _init: function(app, isFavorite) { + BaseWellItem.prototype._init.call(this, app, isFavorite); this._dragStartX = 0; this._dragStartY = 0; @@ -557,15 +557,14 @@ RunningWellItem.prototype = { let modifiers = Shell.get_event_state(event); if (modifiers & Clutter.ModifierType.CONTROL_MASK) { - this.appInfo.launch(); + this.app.get_info().launch(); } else { this.activateMostRecentWindow(); } }, activateMostRecentWindow: function () { - // The _get_windows_for_app sorts them for us - let mostRecentWindow = this.windows[0]; + let mostRecentWindow = this.app.get_windows()[0]; Main.overview.activateWindow(mostRecentWindow, Main.currentTime()); }, @@ -582,7 +581,7 @@ RunningWellItem.prototype = { }, menuPoppedUp: function() { - Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.appInfo.get_id()); + Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id()); }, menuPoppedDown: function() { @@ -593,15 +592,15 @@ RunningWellItem.prototype = { } }; -function InactiveWellItem(appInfo, isFavorite) { - this._init(appInfo, isFavorite); +function InactiveWellItem(app, isFavorite) { + this._init(app, isFavorite); } InactiveWellItem.prototype = { __proto__: BaseWellItem.prototype, - _init : function(appInfo, isFavorite) { - BaseWellItem.prototype._init.call(this, appInfo, isFavorite); + _init : function(app, isFavorite) { + BaseWellItem.prototype._init.call(this, app, isFavorite); this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged)); this.actor.connect('activate', Lang.bind(this, this._onActivate)); @@ -612,12 +611,9 @@ InactiveWellItem.prototype = { }, _onActivate: function() { - if (this.windows.length == 0) { - this.appInfo.launch(); - Main.overview.hide(); - return true; - } - return false; + this.app.get_info().launch(); + Main.overview.hide(); + return true; }, menuPoppedUp: function() { @@ -824,21 +820,11 @@ AppWell.prototype = { this._redisplay(); }, - _lookupApps: function(appIds) { - let result = []; - for (let i = 0; i < appIds.length; i++) { - let id = appIds[i]; - let app = this._appSystem.lookup_cached_app(id); - if (!app) - continue; - result.push(app); - } - return result; - }, - - _arrayValues: function(array) { - return array.reduce(function (values, id, index) { - values[id] = index; return values; }, {}); + _appIdListToHash: function(apps) { + let ids = {}; + for (let i = 0; i < apps.length; i++) + ids[apps[i].get_id()] = apps[i]; + return ids; }, _onMappedNotify: function() { @@ -857,32 +843,29 @@ AppWell.prototype = { this._grid.removeAll(); - let favoriteIds = this._appSystem.get_favorites(); - let favoriteIdsHash = this._arrayValues(favoriteIds); + let favorites = this._appMonitor.get_favorites(); + let favoriteIds = this._appIdListToHash(favorites); /* hardcode here pending some design about how exactly desktop contexts behave */ let contextId = ""; - let running = this._appMonitor.get_running_apps(contextId).filter(function (e) { - return !(e.get_id() in favoriteIdsHash); - }); - let favorites = this._lookupApps(favoriteIds); + let running = this._appMonitor.get_running_apps(contextId); + let runningIds = this._appIdListToHash(running) - let displays = [] - this._addApps(favorites, true); - this._addApps(running, false); - this._displays = displays; - }, - - _addApps: function(apps, isFavorite) { - for (let i = 0; i < apps.length; i++) { - let app = apps[i]; - let windows = this._appMonitor.get_windows_for_app(app.get_id()); + for (let i = 0; i < favorites.length; i++) { + let app = favorites[i]; let display; - if (windows.length > 0) - display = new RunningWellItem(app, isFavorite); - else - display = new InactiveWellItem(app, isFavorite); + if (app.get_windows().length > 0) { + display = new RunningWellItem(app, true); + } else { + display = new InactiveWellItem(app, true); + } + this._grid.actor.add_actor(display.actor); + } + + for (let i = 0; i < running.length; i++) { + let app = running[i]; + let display = new RunningWellItem(app, false); this._grid.actor.add_actor(display.actor); } }, diff --git a/js/ui/appIcon.js b/js/ui/appIcon.js index 9467f59b8..cc7bfc104 100644 --- a/js/ui/appIcon.js +++ b/js/ui/appIcon.js @@ -55,9 +55,9 @@ function AppIcon(params) { AppIcon.prototype = { _init : function(params) { - this.appInfo = params.appInfo; - if (!this.appInfo) - throw new Error('AppIcon constructor requires "appInfo" param'); + this.app = params.app; + if (!this.app) + throw new Error('AppIcon constructor requires "app" param'); this._menuType = ('menuType' in params) ? params.menuType : MenuType.NONE; this._iconSize = ('size' in params) ? params.size : APPICON_DEFAULT_ICON_SIZE; @@ -70,20 +70,6 @@ AppIcon.prototype = { reactive: true }); this.actor._delegate = this; this.highlight_border_color = APPICON_DEFAULT_BORDER_COLOR; - this._signalIds = []; - - // Note, we don't presently update the window list dynamically here; this actor - // gets destroyed and recreated by AppWell, and in alt-tab the whole thing is - // created each time - this.windows = Shell.AppMonitor.get_default().get_windows_for_app(this.appInfo.get_id()); - for (let i = 0; i < this.windows.length; i++) { - let sigId = this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows)); - this._signalIds.push([this.windows[i], sigId]); - } - this._resortWindows(); - - this.actor.connect('destroy', Lang.bind(this, this._destroy)); - this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedChanged)); if (this._menuType != MenuType.NONE) { this.actor.connect('button-press-event', Lang.bind(this, this._updateMenuOnButtonPress)); @@ -99,7 +85,7 @@ AppIcon.prototype = { y_align: Big.BoxAlignment.CENTER, width: this._iconSize, height: this._iconSize }); - this.icon = this.appInfo.create_icon_texture(this._iconSize); + this.icon = this.app.create_icon_texture(this._iconSize); iconBox.append(this.icon, Big.BoxPackFlags.NONE); this.actor.append(iconBox, Big.BoxPackFlags.EXPAND); @@ -114,12 +100,13 @@ AppIcon.prototype = { font_name: "Sans 12px", line_alignment: Pango.Alignment.CENTER, ellipsize: Pango.EllipsizeMode.END, - text: this.appInfo.get_name() }); + text: this.app.get_name() }); nameBox.add_actor(this._name); if (showGlow) { this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL }); let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', ''); - for (let i = 0; i < this.windows.length && i < 3; i++) { + let windows = this.app.get_windows(); + for (let i = 0; i < windows.length && i < 3; i++) { let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER, glowPath, -1, -1); glow.keep_aspect_ratio = false; @@ -175,53 +162,11 @@ AppIcon.prototype = { } }, - _destroy: function() { - for (let i = 0; i < this._signalIds.length; i++) { - let [obj, sigId] = this._signalIds[i]; - obj.disconnect(sigId); - } - }, - - _onMappedChanged: function() { - let mapped = this.actor.mapped; - if (mapped && this._windowSortStale) - this._resortWindows(); - }, - - _resortWindows: function() { - let mapped = this.actor.mapped; - if (!mapped) { - this._windowSortStale = true; - return; - } - this._windowSortStale = false; - this.windows.sort(function (a, b) { - let activeWorkspace = global.screen.get_active_workspace(); - let wsA = a.get_workspace() == activeWorkspace; - let wsB = b.get_workspace() == activeWorkspace; - - if (wsA && !wsB) - return -1; - else if (wsB && !wsA) - return 1; - - let visA = a.showing_on_its_workspace(); - let visB = b.showing_on_its_workspace(); - - if (visA && !visB) - return -1; - else if (visB && !visA) - return 1; - else - return b.get_user_time() - a.get_user_time(); - }); - }, - // AppIcon itself is not a draggable, but if you want to make // a subclass of it draggable, you can use this method to create // a drag actor createDragActor: function() { - return this.appInfo.create_icon_texture(this._iconSize); + return this.app.create_icon_texture(this._iconSize); }, setHighlight: function(highlight) { @@ -421,7 +366,7 @@ AppIconMenu.prototype = { _redisplay: function() { this._windowContainer.remove_all(); - let windows = this._source.windows; + let windows = this._source.app.get_windows(); this._windowContainer.show(); @@ -462,7 +407,7 @@ AppIconMenu.prototype = { this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null; let favorites = Shell.AppSystem.get_default().get_favorites(); - let id = this._source.appInfo.get_id(); + let id = this._source.app.get_id(); this._isFavorite = false; for (let i = 0; i < favorites.length; i++) { if (id == favorites[i]) { @@ -615,14 +560,14 @@ AppIconMenu.prototype = { let metaWindow = child._window; this.emit('activate-window', metaWindow); } else if (child == this._newWindowMenuItem) { - this._source.appInfo.launch(); + this._source.app.get_info().launch(); this.emit('activate-window', null); } else if (child == this._toggleFavoriteMenuItem) { let appSys = Shell.AppSystem.get_default(); if (this._isFavorite) - appSys.remove_favorite(this._source.appInfo.get_id()); + appSys.remove_favorite(this._source.app.get_id()); else - appSys.add_favorite(this._source.appInfo.get_id()); + appSys.add_favorite(this._source.app.get_id()); } this.popdown(); }, diff --git a/js/ui/panel.js b/js/ui/panel.js index c0f7431fa..c7966354b 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -148,10 +148,11 @@ AppPanelMenu.prototype = { this._iconBox.hide(); this._label.text = ''; if (this._focusedApp != null) { - let icon = focusedApp.create_icon_texture(PANEL_ICON_SIZE); + let info = focusedApp.get_info(); + let icon = info.create_icon_texture(PANEL_ICON_SIZE); this._iconBox.append(icon, Big.BoxPackFlags.NONE); this._iconBox.show(); - this._label.text = focusedApp.get_name(); + this._label.text = info.get_name(); } else if (this._activeSequence != null) { let icon = this._activeSequence.create_icon(PANEL_ICON_SIZE); this._iconBox.append(icon, Big.BoxPackFlags.NONE); diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 3f461d8bb..217adcf6e 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -1227,7 +1227,7 @@ Workspace.prototype = { _createWindowIcon: function(window) { let appSys = Shell.AppSystem.get_default(); let appMon = Shell.AppMonitor.get_default() - let appInfo = appMon.get_window_app(window.metaWindow); + let appInfo = appMon.get_window_app(window.metaWindow).get_info(); let iconTexture = null; // The design is application based, so prefer the application // icon here if we have it. FIXME - should move this fallback code diff --git a/src/Makefile.am b/src/Makefile.am index d29ce27d9..faa625673 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -54,6 +54,8 @@ CLEANFILES += $(SHELL_STAMP_FILES) libgnome_shell_la_SOURCES = \ $(shell_built_sources) \ gnome-shell-plugin.c \ + shell-app.c \ + shell-app.h \ shell-app-monitor.c \ shell-app-monitor.h \ shell-app-system.c \ diff --git a/src/shell-app-monitor.c b/src/shell-app-monitor.c index 691a27b47..248b87ca2 100644 --- a/src/shell-app-monitor.c +++ b/src/shell-app-monitor.c @@ -129,9 +129,12 @@ struct _ShellAppMonitor long watch_start_time; MetaWindow *watched_window; - /* */ + /* */ GHashTable *window_to_app; + /* */ + GHashTable *running_apps; + /* > */ GHashTable *app_usages_for_context; }; @@ -214,35 +217,35 @@ static void shell_app_monitor_class_init(ShellAppMonitorClass *klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - _shell_marshal_VOID__BOXED, + g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - SHELL_TYPE_APP_INFO); + SHELL_TYPE_APP); signals[APP_REMOVED] = g_signal_new ("app-removed", SHELL_TYPE_APP_MONITOR, G_SIGNAL_RUN_LAST, 0, NULL, NULL, - _shell_marshal_VOID__BOXED, + g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - SHELL_TYPE_APP_INFO); + SHELL_TYPE_APP); signals[WINDOW_ADDED] = g_signal_new ("window-added", SHELL_TYPE_APP_MONITOR, G_SIGNAL_RUN_LAST, 0, NULL, NULL, - _shell_marshal_VOID__BOXED_OBJECT, + _shell_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, - SHELL_TYPE_APP_INFO, + SHELL_TYPE_APP, META_TYPE_WINDOW); signals[WINDOW_REMOVED] = g_signal_new ("window-removed", SHELL_TYPE_APP_MONITOR, G_SIGNAL_RUN_LAST, 0, NULL, NULL, - _shell_marshal_VOID__BOXED_OBJECT, + _shell_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, - SHELL_TYPE_APP_INFO, + SHELL_TYPE_APP, META_TYPE_WINDOW); signals[STARTUP_SEQUENCE_CHANGED] = g_signal_new ("startup-sequence-changed", @@ -396,12 +399,6 @@ shell_app_monitor_is_window_usage_tracked (MetaWindow *window) return TRUE; } -static ShellAppInfo * -create_transient_app_for_window (MetaWindow *window) -{ - return shell_app_system_create_from_window (shell_app_system_get_default (), window); -} - /** * get_app_for_window_direct: * @@ -409,12 +406,13 @@ create_transient_app_for_window (MetaWindow *window) * an application based on WM_CLASS. If that fails, then * a "transient" application is created. * - * Return value: (transfer full): A newly-referenced #ShellAppInfo + * Return value: (transfer full): A newly-referenced #ShellApp */ -static ShellAppInfo * +static ShellApp * get_app_for_window_direct (MetaWindow *window) { - ShellAppInfo *result; + ShellApp *app; + ShellAppInfo *appinfo; ShellAppSystem *appsys; char *wmclass; char *with_desktop; @@ -422,26 +420,28 @@ get_app_for_window_direct (MetaWindow *window) wmclass = get_cleaned_wmclass_for_window (window); if (!wmclass) - return create_transient_app_for_window (window); + return _shell_app_new_for_window (window); with_desktop = g_strjoin (NULL, wmclass, ".desktop", NULL); g_free (wmclass); appsys = shell_app_system_get_default (); - result = shell_app_system_lookup_heuristic_basename (appsys, with_desktop); + appinfo = shell_app_system_lookup_heuristic_basename (appsys, with_desktop); g_free (with_desktop); - if (result == NULL) + if (appinfo == NULL) { const char *id = get_app_id_from_title (window); if (id != NULL) - result = shell_app_system_load_from_desktop_file (appsys, id, NULL); + appinfo = shell_app_system_load_from_desktop_file (appsys, id, NULL); } - if (result == NULL) - result = create_transient_app_for_window (window); + if (appinfo == NULL) + return _shell_app_new_for_window (window); - return result; + app = _shell_app_new (appinfo); + shell_app_info_unref (appinfo); + return app; } /** @@ -451,11 +451,11 @@ get_app_for_window_direct (MetaWindow *window) * all available information such as the window's MetaGroup, * and what we know about other windows. */ -static ShellAppInfo * +static ShellApp * get_app_for_window (ShellAppMonitor *monitor, MetaWindow *window) { - ShellAppInfo *result; + ShellApp *result; MetaWindow *source_window; GSList *group_windows; MetaGroup *group; @@ -489,7 +489,7 @@ get_app_for_window (ShellAppMonitor *monitor, if (result != NULL) { - shell_app_info_ref (result); + g_object_ref (result); return result; } @@ -502,6 +502,12 @@ get_window_context (MetaWindow *window) return ""; } +static const char * +get_app_context (ShellApp *app) +{ + return ""; +} + static GHashTable * get_usages_for_context (ShellAppMonitor *monitor, const char *context) @@ -543,7 +549,7 @@ static AppUsage * get_app_usage_from_window (ShellAppMonitor *monitor, MetaWindow *window) { - ShellAppInfo *app; + ShellApp *app; const char *context; app = g_hash_table_lookup (monitor->window_to_app, window); @@ -552,7 +558,7 @@ get_app_usage_from_window (ShellAppMonitor *monitor, context = get_window_context (window); - return get_app_usage_for_context_and_id (monitor, context, shell_app_info_get_id (app)); + return get_app_usage_for_context_and_id (monitor, context, shell_app_get_id (app)); } static MetaWindow * @@ -714,14 +720,9 @@ on_transient_window_title_changed (MetaWindow *window, ShellAppMonitor *self) { ShellAppSystem *appsys; - ShellAppInfo *current_app; ShellAppInfo *new_app; const char *id; - current_app = g_hash_table_lookup (self->window_to_app, window); - /* Can't have lost the app */ - g_assert (current_app != NULL); - /* Check if we now have a mapping using the window title */ id = get_app_id_from_title (window); if (id == NULL) @@ -742,7 +743,7 @@ static void track_window (ShellAppMonitor *self, MetaWindow *window) { - ShellAppInfo *app; + ShellApp *app; AppUsage *usage; if (!window_is_tracked (window)) @@ -764,7 +765,7 @@ track_window (ShellAppMonitor *self, return; usage = get_app_usage_from_window (self, window); - usage->transient = shell_app_info_is_transient (app); + usage->transient = shell_app_info_is_transient (shell_app_get_info (app)); if (usage->transient) { @@ -776,6 +777,8 @@ track_window (ShellAppMonitor *self, g_signal_connect (window, "notify::title", G_CALLBACK (on_transient_window_title_changed), self); } + _shell_app_add_window (app, window); + /* Keep track of the number of windows open for this app, when it * switches between 0 and 1 we emit an app-added signal. */ @@ -784,7 +787,12 @@ track_window (ShellAppMonitor *self, usage->initially_seen_sequence = ++self->initially_seen_sequence; usage->last_seen = get_time (); if (usage->window_count == 1) - g_signal_emit (self, signals[APP_ADDED], 0, app); + { + /* key is owned by the app */ + g_hash_table_insert (self->running_apps, (char*)shell_app_get_id (app), + app); + g_signal_emit (self, signals[APP_ADDED], 0, app); + } /* Emit window-added after app-added */ g_signal_emit (self, signals[WINDOW_ADDED], 0, app, window); @@ -800,18 +808,17 @@ shell_app_monitor_on_window_added (MetaWorkspace *workspace, track_window (self, window); } - static void disassociate_window (ShellAppMonitor *self, MetaWindow *window) { - ShellAppInfo *app; + ShellApp *app; app = g_hash_table_lookup (self->window_to_app, window); if (!app) return; - shell_app_info_ref (app); + g_object_ref (app); if (window == self->watched_window) self->watched_window = NULL; @@ -827,18 +834,22 @@ disassociate_window (ShellAppMonitor *self, g_hash_table_remove (self->window_to_app, window); + _shell_app_remove_window (app, window); + g_signal_emit (self, signals[WINDOW_REMOVED], 0, app, window); if (usage->window_count == 0) { + const char *id = shell_app_get_id (app); + g_hash_table_remove (self->running_apps, id); g_signal_emit (self, signals[APP_REMOVED], 0, app); - reset_usage (self, context, shell_app_info_get_id (app), usage); + reset_usage (self, context, id, usage); } } else g_hash_table_remove (self->window_to_app, window); - shell_app_info_unref (app); + g_object_unref (app); } static void @@ -931,8 +942,8 @@ shell_app_monitor_get_windows_for_app (ShellAppMonitor *self, while (g_hash_table_iter_next (&iter, &key, &value)) { MetaWindow *window = key; - ShellAppInfo *app = value; - const char *id = shell_app_info_get_id (app); + ShellApp *app = value; + const char *id = shell_app_get_id (app); if (!shell_app_monitor_is_window_usage_tracked (window)) continue; @@ -1033,7 +1044,9 @@ shell_app_monitor_init (ShellAppMonitor *self) (GDestroyNotify) g_hash_table_destroy); self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal, - NULL, (GDestroyNotify) shell_app_info_unref); + NULL, (GDestroyNotify) g_object_unref); + + self->running_apps = g_hash_table_new (g_str_hash, g_str_equal); g_object_get (shell_global_get(), "configdir", &shell_config_dir, NULL), path = g_build_filename (shell_config_dir, DATA_FILENAME, NULL); @@ -1068,6 +1081,7 @@ shell_app_monitor_finalize (GObject *object) gconf_client_notify_remove (self->gconf_client, self->gconf_notify); g_object_unref (self->gconf_client); g_object_unref (self->display); + g_hash_table_destroy (self->running_apps); g_hash_table_destroy (self->app_usages_for_context); for (i = 0; title_patterns[i].app_id; i++) g_regex_unref (title_patterns[i].regex); @@ -1108,48 +1122,22 @@ shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor, * * Returns: Application associated with window */ -ShellAppInfo * +ShellApp * shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin) { MetaWindow *transient_for; - ShellAppInfo *info; + ShellApp *app; transient_for = meta_window_get_transient_for (metawin); if (transient_for != NULL) metawin = transient_for; - info = g_hash_table_lookup (monitor->window_to_app, metawin); - if (info) - shell_app_info_ref (info); - return info; -} + app = g_hash_table_lookup (monitor->window_to_app, metawin); + if (app) + g_object_ref (app); -typedef struct { - ShellAppMonitor *self; - const char *context_id; -} AppOpenSequenceSortData; - -static int -sort_apps_by_open_sequence (gconstpointer a, - gconstpointer b, - gpointer datap) -{ - AppOpenSequenceSortData *data = datap; - ShellAppInfo *app_a = (ShellAppInfo*)a; - ShellAppInfo *app_b = (ShellAppInfo*)b; - const char *id_a = shell_app_info_get_id (app_a); - const char *id_b = shell_app_info_get_id (app_b); - AppUsage *usage_a; - AppUsage *usage_b; - - usage_a = get_app_usage_for_context_and_id (data->self, data->context_id, id_a); - usage_b = get_app_usage_for_context_and_id (data->self, data->context_id, id_b); - if (usage_a->initially_seen_sequence == usage_b->initially_seen_sequence) - return 0; - if (usage_a->initially_seen_sequence < usage_b->initially_seen_sequence) - return -1; - return 1; + return app; } /** @@ -1158,50 +1146,94 @@ sort_apps_by_open_sequence (gconstpointer a, * @context: Activity identifier * * Returns the set of applications which currently have at least one open - * window in the given context. + * window in the given context. The returned list will be sorted + * by shell_app_compare(). * - * Returns: (element-type ShellAppInfo) (transfer container): Active applications + * Returns: (element-type ShellApp) (transfer container): Active applications */ GSList * shell_app_monitor_get_running_apps (ShellAppMonitor *monitor, const char *context) { - GHashTableIter iter; gpointer key, value; GSList *ret; - AppOpenSequenceSortData data; - GHashTable *unique_apps; + GHashTableIter iter; - unique_apps = g_hash_table_new (g_str_hash, g_str_equal); - - g_hash_table_iter_init (&iter, monitor->window_to_app); + g_hash_table_iter_init (&iter, monitor->running_apps); ret = NULL; while (g_hash_table_iter_next (&iter, &key, &value)) { - MetaWindow *window = key; - ShellAppInfo *app = value; - const char *id; + ShellApp *app = value; - if (strcmp (get_window_context (window), context) != 0) + if (strcmp (context, get_app_context (app)) != 0) continue; - if (!shell_app_monitor_is_window_usage_tracked (window)) - continue; - - id = shell_app_info_get_id (app); - - if (g_hash_table_lookup (unique_apps, id)) - continue; - g_hash_table_insert (unique_apps, (gpointer)id, (gpointer)id); - ret = g_slist_prepend (ret, app); } - g_hash_table_destroy (unique_apps); - data.self = monitor; - data.context_id = context; - return g_slist_sort_with_data (ret, sort_apps_by_open_sequence, &data); + ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare); + + return ret; +} + +/** + * shell_app_monitor_get_app: + * @montior: + * @id: Application identifier + * + * For running applications, returns the existing instance + * of the running application model object. Otherwise, + * returns a new object. + * + * Returns: (transfer full): Application associated with id + */ +ShellApp * +shell_app_monitor_get_app (ShellAppMonitor *monitor, + const char *id) +{ + ShellApp *app; + ShellAppInfo *info; + + app = g_hash_table_lookup (monitor->running_apps, id); + if (app) + return g_object_ref (app); + + info = shell_app_system_lookup_cached_app (shell_app_system_get_default(), id); + if (!info) + return NULL; + + app = _shell_app_new (info); + shell_app_info_unref (info); + + return app; +} + +/** + * shell_app_monitor_get_favorites: + * @monitor: + * + * Returns: (transfer full) (element-type ShellApp): List of favorite applications + */ +GSList * +shell_app_monitor_get_favorites (ShellAppMonitor *monitor) +{ + GSList *apps = NULL; + GList *favorite_ids, *iter; + + favorite_ids = shell_app_system_get_favorites (shell_app_system_get_default ()); + for (iter = favorite_ids; iter; iter = iter->next) + { + const char *id = iter->data; + ShellApp *app; + + app = shell_app_monitor_get_app (monitor, id); + if (app) + apps = g_slist_prepend (apps, g_object_ref (app)); + } + apps = g_slist_reverse (apps); + + return apps; } static gboolean diff --git a/src/shell-app-monitor.h b/src/shell-app-monitor.h index efc1e9ac6..75b463e0d 100644 --- a/src/shell-app-monitor.h +++ b/src/shell-app-monitor.h @@ -6,9 +6,10 @@ #include #include "window.h" +#include "shell-app.h" #include "shell-app-system.h" -/* +/* * This object provides monitoring of system application directories (.desktop files) * and activity-based statistics about applications usage */ @@ -29,14 +30,17 @@ typedef struct _ShellAppMonitorPrivate ShellAppMonitorPrivate; struct _ShellAppMonitorClass { GObjectClass parent_class; - }; GType shell_app_monitor_get_type (void) G_GNUC_CONST; ShellAppMonitor* shell_app_monitor_get_default(void); -ShellAppInfo *shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin); +ShellApp *shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin); + +ShellApp *shell_app_monitor_get_app (ShellAppMonitor *monitor, const char *id); + +GSList *shell_app_monitor_get_favorites (ShellAppMonitor *monitor); GList *shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor, const char *context, diff --git a/src/shell-app.c b/src/shell-app.c new file mode 100644 index 000000000..8effd1bd4 --- /dev/null +++ b/src/shell-app.c @@ -0,0 +1,331 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include "shell-app.h" +#include "shell-global.h" + +/** + * SECTION:shell-app + * @short_description: Object representing an application + * + * This object wraps a #ShellAppInfo, providing methods and signals + * primarily useful for running applications. + */ +struct _ShellApp +{ + GObject parent; + + ShellAppInfo *info; + + guint workspace_switch_id; + + gboolean window_sort_stale; + GSList *windows; +}; + +G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT); + +enum { + WINDOWS_CHANGED, + LAST_SIGNAL +}; + +static guint shell_app_signals[LAST_SIGNAL] = { 0 }; + +const char * +shell_app_get_id (ShellApp *app) +{ + return shell_app_info_get_id (app->info); +} + +/** + * shell_app_create_icon_texture: + * + * Look up the icon for this application, and create a #ClutterTexture + * for it at the given size. + * + * Return value: (transfer none): A floating #ClutterActor + */ +ClutterActor * +shell_app_create_icon_texture (ShellApp *app, + float size) +{ + return shell_app_info_create_icon_texture (app->info, size); +} + +char * +shell_app_get_name (ShellApp *app) +{ + return shell_app_info_get_name (app->info); +} + +char * +shell_app_get_description (ShellApp *app) +{ + return shell_app_info_get_description (app->info); +} + +/** + * shell_app_get_info: + * + * Returns: (transfer none): Associated app info + */ +ShellAppInfo * +shell_app_get_info (ShellApp *app) +{ + return app->info; +} + +typedef struct { + ShellApp *app; + MetaWorkspace *active_workspace; +} CompareWindowsData; + +static int +shell_app_compare_windows (gconstpointer a, + gconstpointer b, + gpointer datap) +{ + MetaWindow *win_a = (gpointer)a; + MetaWindow *win_b = (gpointer)b; + CompareWindowsData *data = datap; + gboolean ws_a, ws_b; + gboolean vis_a, vis_b; + + ws_a = meta_window_get_workspace (win_a) == data->active_workspace; + ws_b = meta_window_get_workspace (win_b) == data->active_workspace; + + if (ws_a && !ws_b) + return -1; + else if (!ws_a && ws_b) + return 1; + + vis_a = meta_window_showing_on_its_workspace (win_a); + vis_b = meta_window_showing_on_its_workspace (win_b); + + if (vis_a && !vis_b) + return -1; + else if (!vis_a && vis_b) + return 1; + + return meta_window_get_user_time (win_b) - meta_window_get_user_time (win_a); +} + +/** + * shell_app_get_windows: + * @app: + * + * Get the toplevel, interesting windows which are associated with this + * application. The returned list will be sorted first by whether + * they're on the active workspace, then by whether they're visible, + * and finally by the time the user last interacted with them. + * + * Returns: (transfer none) (element-type MetaWindow): List of windows + */ +GSList * +shell_app_get_windows (ShellApp *app) +{ + if (app->window_sort_stale) + { + CompareWindowsData data; + data.app = app; + data.active_workspace = meta_screen_get_active_workspace (shell_global_get_screen (shell_global_get ())); + app->windows = g_slist_sort_with_data (app->windows, shell_app_compare_windows, &data); + app->window_sort_stale = FALSE; + } + + return app->windows; +} + +static gboolean +shell_app_has_visible_windows (ShellApp *app) +{ + GSList *iter; + + for (iter = app->windows; iter; iter = iter->next) + { + MetaWindow *window = iter->data; + + if (!meta_window_showing_on_its_workspace (window)) + return FALSE; + } + + return TRUE; +} + +gboolean +shell_app_is_on_workspace (ShellApp *app, + MetaWorkspace *workspace) +{ + GSList *iter; + + for (iter = app->windows; iter; iter = iter->next) + { + if (meta_window_get_workspace (iter->data) == workspace) + return TRUE; + } + + return FALSE; +} + +/** + * shell_app_compare: + * @app: + * @other: A #ShellApp + * + * Compare one #ShellApp instance to another, in the following way: + * - If one of them has visible windows and the other does not, the one + * with visible windows is first. + * - If one has no windows at all (i.e. it's not running) and the other + * does, the one with windows is first. + * - Finally, the application which the user interacted with most recently + * compares earlier. + */ +int +shell_app_compare (ShellApp *app, + ShellApp *other) +{ + gboolean vis_app, vis_other; + GSList *windows_app, *windows_other; + + vis_app = shell_app_has_visible_windows (app); + vis_other = shell_app_has_visible_windows (other); + + if (vis_app && !vis_other) + return -1; + else if (!vis_app && vis_other) + return 1; + + if (app->windows && !other->windows) + return -1; + else if (!app->windows && other->windows) + return 1; + + windows_app = shell_app_get_windows (app); + windows_other = shell_app_get_windows (other); + + return meta_window_get_user_time (windows_other->data) - meta_window_get_user_time (windows_app->data); +} + +ShellApp * +_shell_app_new_for_window (MetaWindow *window) +{ + ShellApp *app; + + app = g_object_new (SHELL_TYPE_APP, NULL); + app->info = shell_app_system_create_from_window (shell_app_system_get_default (), window); + _shell_app_add_window (app, window); + + return app; +} + +ShellApp * +_shell_app_new (ShellAppInfo *info) +{ + ShellApp *app; + + app = g_object_new (SHELL_TYPE_APP, NULL); + app->info = shell_app_info_ref (info); + + return app; +} + +static void +shell_app_on_unmanaged (MetaWindow *window, + ShellApp *app) +{ + _shell_app_remove_window (app, window); +} + +static void +shell_app_on_ws_switch (ShellApp *self) +{ + self->window_sort_stale = TRUE; + g_signal_emit (self, shell_app_signals[WINDOWS_CHANGED], 0); +} + +void +_shell_app_add_window (ShellApp *app, + MetaWindow *window) +{ + if (g_slist_find (app->windows, window)) + return; + + app->windows = g_slist_prepend (app->windows, g_object_ref (window)); + g_signal_connect (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app); + app->window_sort_stale = TRUE; + + g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0); + + if (app->workspace_switch_id == 0) + { + MetaScreen *screen = shell_global_get_screen (shell_global_get ()); + + app->workspace_switch_id = + g_signal_connect (screen, "workspace-switched", G_CALLBACK(shell_app_on_ws_switch), app); + } +} + +static void +disconnect_workspace_switch (ShellApp *app) +{ + MetaScreen *screen; + + if (app->workspace_switch_id == 0) + return; + + screen = shell_global_get_screen (shell_global_get ()); + g_signal_handler_disconnect (screen, app->workspace_switch_id); + app->workspace_switch_id = 0; +} + +void +_shell_app_remove_window (ShellApp *app, + MetaWindow *window) +{ + g_object_unref (window); + app->windows = g_slist_remove (app->windows, window); + if (app->windows == NULL) + disconnect_workspace_switch (app); +} + +static void +shell_app_init (ShellApp *self) +{ +} + +static void +shell_app_dispose (GObject *object) +{ + ShellApp *app = SHELL_APP (object); + + if (app->info) + { + shell_app_info_unref (app->info); + app->info = NULL; + } + + if (app->windows) + { + g_slist_foreach (app->windows, (GFunc) g_object_unref, NULL); + g_slist_free (app->windows); + app->windows = NULL; + } + + disconnect_workspace_switch (app); +} + +static void +shell_app_class_init(ShellAppClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = shell_app_dispose; + + shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed", + SHELL_TYPE_APP, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} diff --git a/src/shell-app.h b/src/shell-app.h new file mode 100644 index 000000000..9af07c6ad --- /dev/null +++ b/src/shell-app.h @@ -0,0 +1,56 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __SHELL_APP_H__ +#define __SHELL_APP_H__ + +#include +#include + +#include "window.h" +#include "shell-app-system.h" + +G_BEGIN_DECLS + +typedef struct _ShellApp ShellApp; +typedef struct _ShellAppClass ShellAppClass; +typedef struct _ShellAppPrivate ShellAppPrivate; + +#define SHELL_TYPE_APP (shell_app_get_type ()) +#define SHELL_APP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP, ShellApp)) +#define SHELL_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_APP, ShellAppClass)) +#define SHELL_IS_APP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_APP)) +#define SHELL_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_APP)) +#define SHELL_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_APP, ShellAppClass)) + +struct _ShellAppClass +{ + GObjectClass parent_class; + +}; + +GType shell_app_get_type (void) G_GNUC_CONST; + +const char *shell_app_get_id (ShellApp *app); + +ClutterActor *shell_app_create_icon_texture (ShellApp *app, float size); +char *shell_app_get_name (ShellApp *app); +char *shell_app_get_description (ShellApp *app); + +ShellAppInfo *shell_app_get_info (ShellApp *app); + +GSList *shell_app_get_windows (ShellApp *app); + +gboolean shell_app_is_on_workspace (ShellApp *app, MetaWorkspace *workspace); + +int shell_app_compare (ShellApp *app, ShellApp *other); + +ShellApp* _shell_app_new_for_window (MetaWindow *window); + +ShellApp* _shell_app_new (ShellAppInfo *appinfo); + +void _shell_app_add_window (ShellApp *app, MetaWindow *window); + +void _shell_app_remove_window (ShellApp *app, MetaWindow *window); + +G_END_DECLS + +#endif /* __SHELL_APP_H__ */ diff --git a/src/shell-marshal.list b/src/shell-marshal.list index 6a69de7e0..588693dc1 100644 --- a/src/shell-marshal.list +++ b/src/shell-marshal.list @@ -2,4 +2,5 @@ VOID:INT,INT,INT VOID:OBJECT,INT,INT,INT,INT VOID:BOXED VOID:BOXED,OBJECT +VOID:OBJECT,OBJECT VOID:STRING,OBJECT,BOOLEAN