[gnome-shell] Split ShellAppMonitor into ShellWindowTracker, ShellAppUsage



commit e941e8088b0e6edb475fc7b264dabec16beb588f
Author: Colin Walters <walters verbum org>
Date:   Thu Oct 15 19:28:29 2009 -0400

    Split ShellAppMonitor into ShellWindowTracker, ShellAppUsage
    
    The two parts were mapping windows to applications, and
    recording application usage statistics.  The latter part
    (now called ShellAppUsage) is much more naturally built on top of
    the former (now called ShellWindowTracker).
    
    ShellWindowTracker retains the startup-notification handling.
    
    ShellWindowTracker also gains a focus-app property, which is
    what most things in the shell UI are interested in (instead of
    window focus).
    
    ShellAppSystem moves to exporting ShellApp from more of its
    public API, rather than ShellAppInfo.  ShellAppSystem also
    ensures that ShellApp instances are unique by holding
    a hash on the ids.
    
    ShellApp's private API is split off into a shell-app-private.h,
    so shell-app.h can be included in shell-app-system.h.
    
    Favorites handling is removed from ShellAppSystem, now inside
    appFavorites.js.
    
    Port all of the JavaScript for these changes.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=598646

 js/ui/altTab.js            |    4 +-
 js/ui/appDisplay.js        |   70 +-
 js/ui/appFavorites.js      |   90 +++
 js/ui/appIcon.js           |   24 +-
 js/ui/main.js              |    7 +-
 js/ui/panel.js             |   31 +-
 js/ui/widget.js            |    8 +-
 js/ui/workspaces.js        |   15 +-
 src/Makefile.am            |    7 +-
 src/shell-app-monitor.c    | 1814 --------------------------------------------
 src/shell-app-monitor.h    |   70 --
 src/shell-app-private.h    |   22 +
 src/shell-app-system.c     |  217 ++----
 src/shell-app-system.h     |   13 +-
 src/shell-app-usage.c      |  970 +++++++++++++++++++++++
 src/shell-app-usage.h      |   36 +
 src/shell-app.c            |   21 +-
 src/shell-app.h            |   15 +-
 src/shell-window-tracker.c |  826 ++++++++++++++++++++
 src/shell-window-tracker.h |   57 ++
 20 files changed, 2157 insertions(+), 2160 deletions(-)
---
diff --git a/js/ui/altTab.js b/js/ui/altTab.js
index d063cab..62b95e3 100644
--- a/js/ui/altTab.js
+++ b/js/ui/altTab.js
@@ -59,8 +59,8 @@ AltTabPopup.prototype = {
     },
 
     show : function(backward) {
-        let appMonitor = Shell.AppMonitor.get_default();
-        let apps = appMonitor.get_running_apps ("");
+        let tracker = Shell.WindowTracker.get_default();
+        let apps = tracker.get_running_apps ("");
 
         if (!apps.length)
             return false;
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index e3da485..1ded1bc 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -13,6 +13,7 @@ const Mainloop = imports.mainloop;
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
 
+const AppFavorites = imports.ui.appFavorites;
 const AppIcon = imports.ui.appIcon;
 const DND = imports.ui.dnd;
 const GenericDisplay = imports.ui.genericDisplay;
@@ -57,7 +58,9 @@ AppDisplayItem.prototype = {
 
     // Opens an application represented by this display item.
     launch : function() {
-        let windows = Shell.AppMonitor.get_default().get_windows_for_app(this._appInfo.get_id());
+        let appSys = Shell.AppSystem.get_default();
+        let app = appSys.get_app(this._appInfo.get_id());
+        let windows = app.get_windows();
         if (windows.length > 0) {
             let mostRecentWindow = windows[0];
             Main.overview.activateWindow(mostRecentWindow, Main.currentTime());
@@ -181,17 +184,12 @@ AppDisplay.prototype = {
         // We use a map of appIds instead of an array to ensure that we don't have duplicates and for easier lookup.
         this._menuSearchAppMatches = {};
 
-        this._appMonitor = Shell.AppMonitor.get_default();
         this._appSystem = Shell.AppSystem.get_default();
         this._appsStale = true;
         this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
             this._appsStale = true;
             this._redisplay(GenericDisplay.RedisplayFlags.NONE);
         }));
-        this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
-            this._appsStale = true;
-            this._redisplay(GenericDisplay.RedisplayFlags.NONE);
-        }));
 
         this._focusInMenus = true;
         this._activeMenuIndex = -1;
@@ -301,9 +299,8 @@ AppDisplay.prototype = {
 
     _getMostUsed: function() {
         let context = "";
-        return this._appMonitor.get_most_used_apps(context, 30).map(Lang.bind(this, function (id) {
-            return this._appSystem.lookup_cached_app(id);
-        })).filter(function (e) { return e != null });
+        let usage = Shell.AppUsage.get_default();
+        return usage.get_most_used(context, 30);
     },
 
     _addMenuItem: function(name, id, index) {
@@ -523,7 +520,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.app.get_info().launch();
+        this.app.launch();
     },
 
     getDragActor: function() {
@@ -557,7 +554,7 @@ RunningWellItem.prototype = {
         let modifiers = Shell.get_event_state(event);
 
         if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
-            this.app.get_info().launch();
+            this.app.launch();
         } else {
             this.activateMostRecentWindow();
         }
@@ -611,7 +608,7 @@ InactiveWellItem.prototype = {
     },
 
     _onActivate: function() {
-        this.app.get_info().launch();
+        this.app.launch();
         Main.overview.hide();
         return true;
     },
@@ -791,6 +788,8 @@ AppWell.prototype = {
         this._menus = [];
         this._menuDisplays = [];
 
+        this._favorites = [];
+
         this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
                                    x_align: Big.BoxAlignment.CENTER });
         this.actor._delegate = this;
@@ -801,21 +800,13 @@ AppWell.prototype = {
         this._grid = new WellGrid();
         this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
 
+        this._tracker = Shell.WindowTracker.get_default();
         this._appSystem = Shell.AppSystem.get_default();
-        this._appMonitor = Shell.AppMonitor.get_default();
 
-        this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
-            this._redisplay();
-        }));
-        this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
-            this._redisplay();
-        }));
-        this._appMonitor.connect('window-added', Lang.bind(this, function(monitor) {
-            this._redisplay();
-        }));
-        this._appMonitor.connect('window-removed', Lang.bind(this, function(monitor) {
-            this._redisplay();
-        }));
+        this._appSystem.connect('installed-changed', Lang.bind(this, this._redisplay));
+
+        AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay));
+        this._tracker.connect('app-running-changed', Lang.bind(this, this._redisplay));
 
         this._redisplay();
     },
@@ -843,17 +834,16 @@ AppWell.prototype = {
 
         this._grid.removeAll();
 
-        let favorites = this._appMonitor.get_favorites();
-        let favoriteIds = this._appIdListToHash(favorites);
+        let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
 
         /* hardcode here pending some design about how exactly desktop contexts behave */
         let contextId = "";
 
-        let running = this._appMonitor.get_running_apps(contextId);
+        let running = this._tracker.get_running_apps(contextId);
         let runningIds = this._appIdListToHash(running);
 
-        for (let i = 0; i < favorites.length; i++) {
-            let app = favorites[i];
+        for (let id in favorites) {
+            let app = favorites[id];
             let display;
             if (app.get_windows().length > 0) {
                 display = new RunningWellItem(app, true);
@@ -865,7 +855,7 @@ AppWell.prototype = {
 
         for (let i = 0; i < running.length; i++) {
             let app = running[i];
-            if (app.get_id() in favoriteIds)
+            if (app.get_id() in favorites)
                 continue;
             let display = new RunningWellItem(app, false);
             this._grid.actor.add_actor(display.actor);
@@ -874,14 +864,11 @@ AppWell.prototype = {
 
     // Draggable target interface
     acceptDrop : function(source, actor, x, y, time) {
-        let appSystem = Shell.AppSystem.get_default();
-
         let app = null;
         if (source instanceof AppDisplayItem) {
-            app = appSystem.lookup_cached_app(source.getId());
+            app = this._appSystem.get_app(source.getId());
         } else if (source instanceof Workspaces.WindowClone) {
-            let appMonitor = Shell.AppMonitor.get_default();
-            app = appMonitor.get_window_app(source.metaWindow);
+            app = this._tracker.get_window_app(source.metaWindow);
         }
 
         // Don't allow favoriting of transient apps
@@ -891,18 +878,17 @@ AppWell.prototype = {
 
         let id = app.get_id();
 
-        let favorites = this._appMonitor.get_favorites();
-        let favoriteIds = this._appIdListToHash(favorites);
+        let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
 
-        let srcIsFavorite = (id in favoriteIds);
+        let srcIsFavorite = (id in favorites);
 
         if (srcIsFavorite) {
             return false;
         } else {
-            Mainloop.idle_add(function () {
-                appSystem.add_favorite(id);
+            Mainloop.idle_add(Lang.bind(this, function () {
+                AppFavorites.getAppFavorites().addFavorite(id);
                 return false;
-            });
+            }));
         }
 
         return true;
diff --git a/js/ui/appFavorites.js b/js/ui/appFavorites.js
new file mode 100644
index 0000000..3f32c0b
--- /dev/null
+++ b/js/ui/appFavorites.js
@@ -0,0 +1,90 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Shell = imports.gi.Shell;
+const Lang = imports.lang;
+const Signals = imports.signals;
+
+
+function AppFavorites() {
+    this._init();
+}
+
+AppFavorites.prototype = {
+    FAVORITE_APPS_KEY: 'favorite_apps',
+
+    _init: function() {
+        this._favorites = {};
+        this._gconf = Shell.GConf.get_default();
+        this._gconf.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
+        this._reload();
+    },
+
+    _onFavsChanged: function() {
+        this._reload();
+        this.emit('changed');
+    },
+
+    _reload: function() {
+        let ids = Shell.GConf.get_default().get_string_list('favorite_apps');
+        let appSys = Shell.AppSystem.get_default();
+        let apps = ids.map(function (id) {
+                return appSys.get_app(id);
+            }).filter(function (app) {
+                return app != null;
+            });
+        this._favorites = {};
+        for (let i = 0; i < apps.length; i++) {
+            let app = apps[i];
+            this._favorites[app.get_id()] = app;
+        }
+    },
+
+    _getIds: function() {
+        let ret = [];
+        for (let id in this._favorites)
+            ret.push(id);
+        return ret;
+    },
+
+    getFavoriteMap: function() {
+        return this._favorites;
+    },
+
+    getFavorites: function() {
+        let ret = [];
+        for (let id in this._favorites)
+            ret.push(this._favorites[id]);
+        return ret;
+    },
+
+    isFavorite: function(appId) {
+        return appId in this._favorites;
+    },
+
+    addFavorite: function(appId) {
+        if (appId in this._favorites)
+            return;
+        let app = Shell.AppSystem.get_default().get_app(appId);
+        if (!app)
+            return;
+        let ids = this._getIds();
+        ids.push(appId);
+        this._gconf.set_string_list(this.FAVORITE_APPS_KEY, ids);
+        this._favorites[appId] = app;
+    },
+
+    removeFavorite: function(appId) {
+        if (!appId in this._favorites)
+            return;
+        let ids = this._getIds().filter(function (id) { return id != appId; });
+        this._gconf.set_string_list(this.FAVORITE_APPS_KEY, ids);
+    }
+};
+Signals.addSignalMethods(AppFavorites.prototype);
+
+var appFavoritesInstance = null;
+function getAppFavorites() {
+    if (appFavoritesInstance == null)
+        appFavoritesInstance = new AppFavorites();
+    return appFavoritesInstance;
+}
diff --git a/js/ui/appIcon.js b/js/ui/appIcon.js
index cc7bfc1..ab8ede1 100644
--- a/js/ui/appIcon.js
+++ b/js/ui/appIcon.js
@@ -12,6 +12,7 @@ const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
 
 const GenericDisplay = imports.ui.genericDisplay;
+const AppFavorites = imports.ui.appFavorites;
 const Main = imports.ui.main;
 const Workspaces = imports.ui.workspaces;
 
@@ -404,20 +405,13 @@ AppIconMenu.prototype = {
         if (windows.length > 0)
             this._appendSeparator();
 
+        let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
+
         this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
 
-        let favorites = Shell.AppSystem.get_default().get_favorites();
-        let id = this._source.app.get_id();
-        this._isFavorite = false;
-        for (let i = 0; i < favorites.length; i++) {
-            if (id == favorites[i]) {
-                this._isFavorite = true;
-                break;
-            }
-        }
         if (windows.length > 0)
             this._appendSeparator();
-        this._toggleFavoriteMenuItem = this._appendMenuItem(null, this._isFavorite ? _("Remove from Favorites")
+        this._toggleFavoriteMenuItem = this._appendMenuItem(null, isFavorite ? _("Remove from Favorites")
                                                                     : _("Add to Favorites"));
 
         this._highlightedItem = null;
@@ -560,14 +554,14 @@ AppIconMenu.prototype = {
             let metaWindow = child._window;
             this.emit('activate-window', metaWindow);
         } else if (child == this._newWindowMenuItem) {
-            this._source.app.get_info().launch();
+            this._source.app.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.app.get_id());
+            let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
+            if (isFavorite)
+                favs.removeFavorite(this._source.app.get_id());
             else
-                appSys.add_favorite(this._source.app.get_id());
+                favs.addFavorite(this._source.app.get_id());
         }
         this.popdown();
     },
diff --git a/js/ui/main.js b/js/ui/main.js
index 6235846..aac2672 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -55,15 +55,16 @@ function start() {
 
     Environment.init();
 
-    // Ensure ShellAppMonitor is initialized; this will
+    // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
     // also initialize ShellAppSystem first.  ShellAppSystem
-    // needs to load all the .desktop files, and ShellAppMonitor
+    // needs to load all the .desktop files, and ShellWindowTracker
     // will use those to associate with windows.  Right now
     // the Monitor doesn't listen for installed app changes
     // and recalculate application associations, so to avoid
     // races for now we initialize it here.  It's better to
     // be predictable anyways.
-    Shell.AppMonitor.get_default();
+    Shell.WindowTracker.get_default();
+    Shell.AppUsage.get_default();
 
     // The background color really only matters if there is no desktop
     // window (say, nautilus) running. We set it mostly so things look good
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 9b48123..1365b66 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -109,34 +109,26 @@ AppPanelMenu.prototype = {
             this.actor.opacity = 192;
         }));
 
-        this._metaDisplay.connect('notify::focus-window', Lang.bind(this, this._sync));
-
-        let appMonitor = Shell.AppMonitor.get_default();
-        appMonitor.connect('startup-sequence-changed', Lang.bind(this, this._sync));
-        // For now just resync on application add/remove; this is mainly to handle
+        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
         // title which is a dynamic property.
-        appMonitor.connect('app-added', Lang.bind(this, this._sync));
-        appMonitor.connect('app-removed', Lang.bind(this, this._sync));
+        tracker.connect('app-running-changed', Lang.bind(this, this._sync));
 
         this._sync();
     },
 
     _sync: function() {
-        let appMonitor = Shell.AppMonitor.get_default();
+        let tracker = Shell.WindowTracker.get_default();
 
-        let focusWindow = this._metaDisplay.get_focus_window();
-        let focusedApp;
-        if (focusWindow == null) {
-           focusedApp = null;
-        } else {
-           focusedApp = appMonitor.get_window_app(focusWindow);
-        }
+        let focusedApp = tracker.focus_app;
 
         let lastSequence = null;
         if (focusedApp == null) {
-            let sequences = appMonitor.get_startup_sequences();
+            let sequences = tracker.get_startup_sequences();
             if (sequences.length > 0)
                 lastSequence = sequences[sequences.length - 1];
         }
@@ -156,11 +148,10 @@ AppPanelMenu.prototype = {
         this._iconBox.hide();
         this._label.text = '';
         if (this._focusedApp != null) {
-            let info = focusedApp.get_info();
-            let icon = info.create_icon_texture(PANEL_ICON_SIZE);
+            let icon = this._focusedApp.create_icon_texture(PANEL_ICON_SIZE);
             this._iconBox.append(icon, Big.BoxPackFlags.NONE);
             this._iconBox.show();
-            this._label.text = info.get_name();
+            this._label.text = this._focusedApp.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/widget.js b/js/ui/widget.js
index cd8fd42..341833d 100644
--- a/js/ui/widget.js
+++ b/js/ui/widget.js
@@ -12,6 +12,7 @@ const Signals = imports.signals;
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
 
+const AppFavorites = imports.ui.appFavorites;
 const DocInfo = imports.misc.docInfo;
 
 const COLLAPSED_WIDTH = 24;
@@ -318,12 +319,9 @@ AppsWidget.prototype = {
         this.collapsedActor = new Big.Box({ spacing: 2});
 
         let appSystem = Shell.AppSystem.get_default();
-        let apps = appSystem.get_favorites();
+        let apps = AppFavorites.getAppFavorites().getFavorites();
         for (let i = 0; i < apps.length; i++) {
-            let app = appSystem.lookup_cached_app(apps[i]);
-            if (!app)
-                continue;
-            this.addItem(new AppsWidgetInfo(app));
+            this.addItem(new AppsWidgetInfo(apps[i]));
         }
     }
 };
diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js
index 3be0ab3..1cde8ac 100644
--- a/js/ui/workspaces.js
+++ b/js/ui/workspaces.js
@@ -825,7 +825,6 @@ Workspace.prototype = {
      * fashion by the order in which the windows were created.
      */
     _orderWindowsByMotionAndStartup: function(windows, slots) {
-        let appMonitor = Shell.AppMonitor.get_default();
         windows.sort(function(w1, w2) {
             return w2.get_stable_sequence() - w1.get_stable_sequence();
         });
@@ -1220,14 +1219,13 @@ Workspace.prototype = {
 
     // Tests if @win should be shown in the Overview
     _isOverviewWindow : function (win) {
-        let appMon = Shell.AppMonitor.get_default()
-        return appMon.is_window_usage_tracked(win.get_meta_window());
+        let tracker = Shell.WindowTracker.get_default()
+        return tracker.is_window_interesting(win.get_meta_window());
     },
 
     _createWindowIcon: function(window) {
-        let appSys = Shell.AppSystem.get_default();
-        let appMon = Shell.AppMonitor.get_default()
-        let app = appMon.get_window_app(window.metaWindow);
+        let tracker = Shell.WindowTracker.get_default()
+        let app = tracker.get_window_app(window.metaWindow);
         let iconTexture = null;
         // The design is application based, so prefer the application
         // icon here if we have it.  FIXME - should move this fallback code
@@ -1458,10 +1456,11 @@ Workspaces.prototype = {
 
         this._windowSelectionAppId = appId;
 
-        let appSys = Shell.AppMonitor.get_default();
+        let appSys = Shell.AppSystem.get_default();
 
         let showOnlyWindows = {};
-        let windows = appSys.get_windows_for_app(appId);
+        let app = appSys.get_app(appId);
+        let windows = app.get_windows();
         for (let i = 0; i < windows.length; i++) {
             showOnlyWindows[windows[i]] = 1;
         }
diff --git a/src/Makefile.am b/src/Makefile.am
index faa6256..5290748 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -56,10 +56,11 @@ libgnome_shell_la_SOURCES =			\
 	gnome-shell-plugin.c			\
 	shell-app.c         \
 	shell-app.h         \
-	shell-app-monitor.c			\
-	shell-app-monitor.h			\
+	shell-app-private.h \
 	shell-app-system.c			\
 	shell-app-system.h			\
+	shell-app-usage.c			\
+	shell-app-usage.h			\
 	shell-arrow.c			\
 	shell-arrow.h			\
 	shell-button-box.c           \
@@ -95,6 +96,8 @@ libgnome_shell_la_SOURCES =			\
 	shell-texture-cache.h			\
 	shell-uri-util.c           \
 	shell-uri-util.h           \
+	shell-window-tracker.c		\
+	shell-window-tracker.h  \
 	shell-wm.c				\
 	shell-wm.h
 
diff --git a/src/shell-app-private.h b/src/shell-app-private.h
new file mode 100644
index 0000000..f3ead0f
--- /dev/null
+++ b/src/shell-app-private.h
@@ -0,0 +1,22 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_PRIVATE_H__
+#define __SHELL_APP_PRIVATE_H__
+
+#include "shell-app.h"
+#include "shell-app-system.h"
+
+G_BEGIN_DECLS
+
+ShellAppInfo *_shell_app_get_info (ShellApp *app);
+
+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_PRIVATE_H__ */
diff --git a/src/shell-app-system.c b/src/shell-app-system.c
index 610b24a..898fd0f 100644
--- a/src/shell-app-system.c
+++ b/src/shell-app-system.c
@@ -10,6 +10,7 @@
 #include <gconf/gconf-client.h>
 #include <clutter/clutter.h>
 
+#include "shell-app-private.h"
 #include "shell-global.h"
 #include "shell-texture-cache.h"
 #include "display.h"
@@ -33,7 +34,6 @@ enum {
 
 enum {
   INSTALLED_CHANGED,
-  FAVORITES_CHANGED,
   LAST_SIGNAL
 };
 
@@ -43,6 +43,7 @@ struct _ShellAppSystemPrivate {
   GMenuTree *apps_tree;
   GMenuTree *settings_tree;
 
+  GHashTable *app_id_to_info;
   GHashTable *app_id_to_app;
 
   GHashTable *cached_menu_contents;  /* <char *id, GSList<ShellAppInfo*>> */
@@ -50,8 +51,6 @@ struct _ShellAppSystemPrivate {
 
   GSList *cached_settings; /* ShellAppInfo */
 
-  GList *cached_favorites; /* utf8 */
-
   gint app_monitor_id;
 
   guint app_change_timeout_id;
@@ -62,8 +61,6 @@ static void shell_app_system_finalize (GObject *object);
 static gboolean on_tree_changed (gpointer user_data);
 static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data);
 static void reread_menus (ShellAppSystem *self);
-static void on_favorite_apps_changed (GConfClient *client, guint id, GConfEntry *entry, gpointer user_data);
-static void reread_favorite_apps (ShellAppSystem *system);
 
 G_DEFINE_TYPE(ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
 
@@ -205,14 +202,6 @@ static void shell_app_system_class_init(ShellAppSystemClass *klass)
 		  NULL, NULL,
 		  g_cclosure_marshal_VOID__VOID,
 		  G_TYPE_NONE, 0);
-  signals[FAVORITES_CHANGED] =
-    g_signal_new ("favorites-changed",
-                  SHELL_TYPE_APP_SYSTEM,
-                  G_SIGNAL_RUN_LAST,
-                  G_STRUCT_OFFSET (ShellAppSystemClass, favorites_changed),
-                  NULL, NULL,
-                  g_cclosure_marshal_VOID__VOID,
-                  G_TYPE_NONE, 0);
 
   g_type_class_add_private (gobject_class, sizeof (ShellAppSystemPrivate));
 }
@@ -228,8 +217,11 @@ shell_app_system_init (ShellAppSystem *self)
                                                    ShellAppSystemPrivate);
 
   /* The key is owned by the value */
-  priv->app_id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                               NULL, (GDestroyNotify) shell_app_info_unref);
+  priv->app_id_to_info = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                NULL, (GDestroyNotify) shell_app_info_unref);
+
+  /* Key is owned by info */
+  priv->app_id_to_app = g_hash_table_new (g_str_hash, g_str_equal);
 
   priv->cached_menu_contents = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                       g_free, free_appinfo_gslist);
@@ -249,10 +241,6 @@ shell_app_system_init (ShellAppSystem *self)
   reread_menus (self);
 
   client = gconf_client_get_default ();
-
-  self->priv->app_monitor_id = gconf_client_notify_add (client, SHELL_APP_FAVORITES_KEY,
-                                                        on_favorite_apps_changed, self, NULL, NULL);
-  reread_favorite_apps (self);
 }
 
 static void
@@ -269,6 +257,7 @@ shell_app_system_finalize (GObject *object)
 
   g_hash_table_destroy (priv->cached_menu_contents);
 
+  g_hash_table_destroy (priv->app_id_to_info);
   g_hash_table_destroy (priv->app_id_to_app);
 
   g_slist_foreach (priv->cached_app_menus, (GFunc)shell_app_menu_entry_free, NULL);
@@ -279,10 +268,6 @@ shell_app_system_finalize (GObject *object)
   g_slist_free (priv->cached_settings);
   priv->cached_settings = NULL;
 
-  g_list_free (priv->cached_favorites);
-
-  gconf_client_notify_remove (gconf_client_get_default (), priv->app_monitor_id);
-
   G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(object);
 }
 
@@ -404,7 +389,7 @@ cache_by_id (ShellAppSystem *self, GSList *apps, gboolean ref)
       if (ref)
         shell_app_info_ref (info);
       /* the name is owned by the info itself */
-      g_hash_table_insert (self->priv->app_id_to_app, (char*)shell_app_info_get_id (info),
+      g_hash_table_insert (self->priv->app_id_to_info, (char*)shell_app_info_get_id (info),
                            info);
     }
 }
@@ -421,7 +406,7 @@ reread_menus (ShellAppSystem *self)
 
   /* Now loop over applications.menu and settings.menu, inserting each by desktop file
    * ID into a hash */
-  g_hash_table_remove_all (self->priv->app_id_to_app);
+  g_hash_table_remove_all (self->priv->app_id_to_info);
   trunk = gmenu_tree_get_root_directory (self->priv->apps_tree);
   apps = gather_entries_recurse (self, NULL, trunk);
   gmenu_tree_item_unref (trunk);
@@ -469,63 +454,6 @@ on_tree_changed_cb (GMenuTree *monitor, gpointer user_data)
                                                           self, NULL);
 }
 
-static GList *
-convert_gconf_value_string_list_to_list_uniquify (GConfValue *value )
-{
-  GSList *list;
-  GSList *tmp;
-  GList *result = NULL;
-  GHashTable *tmp_table = g_hash_table_new (g_str_hash, g_str_equal);
-
-  list = gconf_value_get_list (value);
-
-  for (tmp = list ; tmp; tmp = tmp->next)
-    {
-      GConfValue *value = tmp->data;
-      char *str = g_strdup (gconf_value_get_string (value));
-      if (!str)
-        continue;
-      if (g_hash_table_lookup (tmp_table, str))
-        {
-          g_free (str);
-          continue;
-        }
-      g_hash_table_insert (tmp_table, str, GUINT_TO_POINTER(1));
-      result = g_list_prepend (result, str);
-    }
-  g_hash_table_destroy (tmp_table);
-  return g_list_reverse (result);
-}
-
-static void
-reread_favorite_apps (ShellAppSystem *system)
-{
-  GConfClient *client = gconf_client_get_default ();
-  GConfValue *val;
-
-  val = gconf_client_get (client, SHELL_APP_FAVORITES_KEY, NULL);
-
-  if (!(val && val->type == GCONF_VALUE_LIST && gconf_value_get_list_type (val) == GCONF_VALUE_STRING))
-    return;
-
-  g_list_foreach (system->priv->cached_favorites, (GFunc) g_free, NULL);
-  g_list_free (system->priv->cached_favorites);
-  system->priv->cached_favorites = convert_gconf_value_string_list_to_list_uniquify (val);
-
-  gconf_value_free (val);
-}
-
-void
-on_favorite_apps_changed (GConfClient *client,
-                          guint        id,
-                          GConfEntry  *entry,
-                          gpointer     user_data)
-{
-  ShellAppSystem *system = SHELL_APP_SYSTEM (user_data);
-  reread_favorite_apps (system);
-  g_signal_emit (G_OBJECT (system), signals[FAVORITES_CHANGED], 0);
-}
-
 GType
 shell_app_info_get_type (void)
 {
@@ -628,93 +556,70 @@ shell_app_system_get_default ()
   return instance;
 }
 
-/**
- * shell_app_system_get_favorites:
- *
- * Return the list of applications which have been explicitly added to the
- * favorites.
- *
- * Return value: (transfer none) (element-type utf8): List of favorite application ids
- */
-GList *
-shell_app_system_get_favorites (ShellAppSystem *system)
-{
-  return system->priv->cached_favorites;
-}
+typedef struct {
+  ShellAppSystem *appsys;
+  ShellAppInfo *info;
+} ShellAppRef;
 
 static void
-set_gconf_value_string_list (GConfValue *val, GList *items)
+shell_app_system_on_app_weakref (gpointer  data,
+                                 GObject  *location)
 {
-  GList *iter;
-  GSList *tmp = NULL;
-
-  for (iter = items; iter; iter = iter->next)
-    {
-      const char *str = iter->data;
-      GConfValue *strval = gconf_value_new (GCONF_VALUE_STRING);
-      gconf_value_set_string (strval, str);
-      tmp = g_slist_prepend (tmp, strval);
-    }
-  tmp = g_slist_reverse (tmp);
+  ShellAppRef *ref = data;
 
-  gconf_value_set_list (val, tmp);
-  g_slist_free (tmp);
+  g_hash_table_remove (ref->appsys->priv->app_id_to_app, shell_app_info_get_id (ref->info));
+  shell_app_info_unref (ref->info);
+  g_free (ref);
 }
 
-void
-shell_app_system_add_favorite (ShellAppSystem *system, const char *id)
+/**
+ * shell_app_system_get_app:
+ *
+ * Find or create a #ShellApp corresponding to an id; if already cached
+ * elsewhere in memory, return that instance.  Otherwise, create a new
+ * one.
+ *
+ * Return value: (transfer full): The #ShellApp for id, or %NULL if none
+ */
+ShellApp *
+shell_app_system_get_app (ShellAppSystem   *self,
+                          const char       *id)
 {
-  GConfClient *client = gconf_client_get_default ();
-  GConfValue *val;
-  GList *iter;
+  ShellAppInfo *info;
+  ShellApp *app;
 
-  iter = g_list_find_custom (system->priv->cached_favorites, id, (GCompareFunc)strcmp);
-  if (iter)
-    return;
+  app = g_hash_table_lookup (self->priv->app_id_to_app, id);
+  if (app)
+    return g_object_ref (app);
 
-  val = gconf_value_new (GCONF_VALUE_LIST);
-  gconf_value_set_list_type (val, GCONF_VALUE_STRING);
+  info = g_hash_table_lookup (self->priv->app_id_to_info, id);
+  if (!info)
+    return NULL;
 
-  system->priv->cached_favorites = g_list_append (system->priv->cached_favorites, g_strdup (id));
+  app = _shell_app_new (info);
 
-  set_gconf_value_string_list (val, system->priv->cached_favorites);
-  gconf_client_set (client, SHELL_APP_FAVORITES_KEY, val, NULL);
+  return app;
 }
 
+/* ShellAppSystem ensures we have a unique instance of
+ * apps per id.
+ */
 void
-shell_app_system_remove_favorite (ShellAppSystem *system, const char *id)
+_shell_app_system_register_app (ShellAppSystem   *self,
+                                ShellApp         *app)
 {
-  GConfClient *client = gconf_client_get_default ();
-  GConfValue *val;
-  GList *iter;
-
-  iter = g_list_find_custom (system->priv->cached_favorites, id, (GCompareFunc)strcmp);
-  if (!iter)
-    return;
-  g_free (iter->data);
-  system->priv->cached_favorites = g_list_delete_link (system->priv->cached_favorites, iter);
+  const char *id;
+  ShellAppRef *ref;
 
-  val = gconf_value_new (GCONF_VALUE_LIST);
-  gconf_value_set_list_type (val, GCONF_VALUE_STRING);
+  id = shell_app_get_id (app);
 
-  set_gconf_value_string_list (val, system->priv->cached_favorites);
-  gconf_client_set (client, SHELL_APP_FAVORITES_KEY, val, NULL);
-}
-
-/**
- * shell_app_system_lookup_app:
- *
- * Return value: (transfer full): The #ShellAppInfo for id, or %NULL if none
- */
-ShellAppInfo *
-shell_app_system_lookup_cached_app (ShellAppSystem *self, const char *id)
-{
-  ShellAppInfo *info;
+  g_return_if_fail (g_hash_table_lookup (self->priv->app_id_to_app, id) == NULL);
 
-  info = g_hash_table_lookup (self->priv->app_id_to_app, id);
-  if (info)
-    shell_app_info_ref (info);
-  return info;
+  ref = g_new0 (ShellAppRef, 1);
+  ref->appsys = self;
+  ref->info = shell_app_info_ref (_shell_app_get_info (app));
+  g_hash_table_insert (self->priv->app_id_to_app, (char*)shell_app_info_get_id (ref->info), app);
+  g_object_weak_ref (G_OBJECT (app), shell_app_system_on_app_weakref, ref);
 }
 
 ShellAppInfo *
@@ -778,16 +683,16 @@ shell_app_system_create_from_window (ShellAppSystem *system, MetaWindow *window)
  * heuristically determined application identifier
  * string, or %NULL if none.
  *
- * Returns: (transfer full): A #ShellAppInfo for name
+ * Returns: (transfer full): A #ShellApp for name
  */
-ShellAppInfo *
+ShellApp *
 shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
                                             const char *name)
 {
-  ShellAppInfo *result;
+  ShellApp *result;
   char **vendor_prefixes;
 
-  result = shell_app_system_lookup_cached_app (system, name);
+  result = shell_app_system_get_app (system, name);
   if (result != NULL)
     return result;
 
@@ -795,7 +700,7 @@ shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
        *vendor_prefixes; vendor_prefixes++)
     {
       char *tmpid = g_strjoin (NULL, *vendor_prefixes, "-", name, NULL);
-      result = shell_app_system_lookup_cached_app (system, tmpid);
+      result = shell_app_system_get_app (system, tmpid);
       g_free (tmpid);
       if (result != NULL)
         return result;
diff --git a/src/shell-app-system.h b/src/shell-app-system.h
index b8d20f9..7b8ec48 100644
--- a/src/shell-app-system.h
+++ b/src/shell-app-system.h
@@ -4,6 +4,7 @@
 #include <gio/gio.h>
 #include <clutter/clutter.h>
 
+#include "shell-app.h"
 #include "window.h"
 
 #define SHELL_TYPE_APP_SYSTEM                 (shell_app_system_get_type ())
@@ -75,9 +76,11 @@ gboolean shell_app_info_launch (ShellAppInfo *info,
 
 ShellAppInfo *shell_app_system_load_from_desktop_file (ShellAppSystem *system, const char *filename, GError **error);
 
-ShellAppInfo *shell_app_system_lookup_cached_app (ShellAppSystem *system, const char *id);
+ShellApp *shell_app_system_get_app (ShellAppSystem *system, const char *id);
 
-ShellAppInfo *shell_app_system_lookup_heuristic_basename (ShellAppSystem *system, const char *id);
+void _shell_app_system_register_app (ShellAppSystem *self, ShellApp *app);
+
+ShellApp *shell_app_system_lookup_heuristic_basename (ShellAppSystem *system, const char *id);
 
 ShellAppInfo *shell_app_system_create_from_window (ShellAppSystem *system, MetaWindow *window);
 
@@ -85,10 +88,4 @@ GSList *shell_app_system_get_menus (ShellAppSystem *system);
 
 GSList *shell_app_system_get_all_settings (ShellAppSystem *system);
 
-GList *shell_app_system_get_favorites (ShellAppSystem *system);
-
-void shell_app_system_add_favorite (ShellAppSystem *system, const char *id);
-
-void shell_app_system_remove_favorite (ShellAppSystem *system, const char *id);
-
 #endif /* __SHELL_APP_SYSTEM_H__ */
diff --git a/src/shell-app-usage.c b/src/shell-app-usage.c
new file mode 100644
index 0000000..f02dd65
--- /dev/null
+++ b/src/shell-app-usage.c
@@ -0,0 +1,970 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#include <string.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gio/gio.h>
+#include <gconf/gconf.h>
+#include <gconf/gconf-client.h>
+#include <dbus/dbus-glib.h>
+
+#include "shell-app-usage.h"
+#include "shell-window-tracker.h"
+#include "shell-global.h"
+#include "shell-marshal.h"
+
+#include "display.h"
+#include "window.h"
+#include "group.h"
+
+/* This file includes modified code from
+ * desktop-data-engine/engine-dbus/hippo-application-monitor.c
+ * in the functions collecting application usage data.
+ * Written by Owen Taylor, originally licensed under LGPL 2.1.
+ * Copyright Red Hat, Inc. 2006-2008
+ */
+
+/**
+ * SECTION:shell-app-usage
+ * @short_description: Track application usage/state data
+ *
+ * This class maintains some usage and state statistics for
+ * applications by keeping track of the approximate time an application's
+ * windows are focused, as well as the last workspace it was seen on.
+ * This time tracking is implemented by watching for focus notifications,
+ * and computing a time delta between them.  Also we watch the
+ * GNOME Session "StatusChanged" signal which by default is emitted after 5
+ * minutes to signify idle.
+ */
+
+#define APP_MONITOR_GCONF_DIR SHELL_GCONF_DIR"/app_monitor"
+#define ENABLE_MONITORING_KEY APP_MONITOR_GCONF_DIR"/enable_monitoring"
+
+#define FOCUS_TIME_MIN_SECONDS 7 /* Need 7 continuous seconds of focus */
+
+#define USAGE_CLEAN_DAYS 7 /* If after 7 days we haven't seen an app, purge it */
+
+/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */
+#define DATA_FILENAME "application_state"
+
+#define IDLE_TIME_TRANSITION_SECONDS 30 /* If we transition to idle, only count
+                                         * this many seconds of usage */
+
+/* The ranking algorithm we use is: every time an app score reaches SCORE_MAX,
+ * divide all scores by 2. Scores are raised by 1 unit every SAVE_APPS_TIMEOUT
+ * seconds. This mechanism allows the list to update relatively fast when
+ * a new app is used intensively.
+ * To keep the list clean, and avoid being Big Brother, apps that have not been
+ * seen for a week and whose score is below SCORE_MIN are removed.
+ */
+
+/* How often we save internally app data, in seconds */
+#define SAVE_APPS_TIMEOUT_SECONDS 5    /* leave this low for testing, we can bump later if need be */
+
+/* With this value, an app goes from bottom to top of the
+ * usage list in 50 hours of use */
+#define SCORE_MAX (3600 * 50 / FOCUS_TIME_MIN_SECONDS)
+
+/* If an app's score in lower than this and the app has not been used in a week,
+ * remove it */
+#define SCORE_MIN (SCORE_MAX >> 3)
+
+/* http://www.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Presence */
+#define GNOME_SESSION_STATUS_IDLE 3
+
+typedef struct UsageData UsageData;
+
+struct _ShellAppUsage
+{
+  GObject parent;
+
+  GFile *configfile;
+  DBusGProxy *session_proxy;
+  GdkDisplay *display;
+  GConfClient *gconf_client;
+  gulong last_idle;
+  guint idle_focus_change_id;
+  guint save_id;
+  guint gconf_notify;
+  gboolean currently_idle;
+  gboolean enable_monitoring;
+
+  GSList *previously_running;
+
+  long watch_start_time;
+  ShellApp *watched_app;
+
+  /* <char *context, GHashTable<char *appid, UsageData *usage>> */
+  GHashTable *app_usages_for_context;
+};
+
+G_DEFINE_TYPE (ShellAppUsage, shell_app_usage, G_TYPE_OBJECT);
+
+/* Represents an application record for a given context */
+struct UsageData
+{
+  /* Whether the application we're tracking is "transient", see
+   * shell_app_info_is_transient.
+   */
+  gboolean transient;
+
+  gdouble score; /* Based on the number of times we'e seen the app and normalized */
+  long last_seen; /* Used to clear old apps we've only seen a few times */
+};
+
+static void shell_app_usage_finalize (GObject *object);
+
+static void on_session_status_changed (DBusGProxy *proxy, guint status, ShellAppUsage *self);
+static void on_focus_app_changed (ShellWindowTracker *tracker, GParamSpec *spec, ShellAppUsage *self);
+static void ensure_queued_save (ShellAppUsage *self);
+static UsageData * get_app_usage_for_context_and_id (ShellAppUsage  *self,
+                                                    const char     *context,
+                                                    const char     *appid);
+
+static gboolean idle_save_application_usage (gpointer data);
+
+static void restore_from_file (ShellAppUsage *self);
+
+static void update_enable_monitoring (ShellAppUsage *self);
+
+static void on_enable_monitoring_key_changed (GConfClient *client,
+                                              guint        connexion_id,
+                                              GConfEntry  *entry,
+                                              gpointer     self);
+
+static long
+get_time (void)
+{
+  GTimeVal tv;
+  g_get_current_time (&tv);
+  return tv.tv_sec;
+}
+
+static void
+shell_app_usage_class_init (ShellAppUsageClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = shell_app_usage_finalize;
+}
+
+static GHashTable *
+get_usages_for_context (ShellAppUsage *self,
+                        const char    *context)
+{
+  GHashTable *context_usages;
+
+  context_usages = g_hash_table_lookup (self->app_usages_for_context, context);
+  if (context_usages == NULL)
+    {
+      context_usages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+      g_hash_table_insert (self->app_usages_for_context, g_strdup (context),
+                           context_usages);
+    }
+  return context_usages;
+}
+
+static UsageData *
+get_app_usage_for_context_and_id (ShellAppUsage *self,
+                                  const char    *context,
+                                  const char    *appid)
+{
+  UsageData *usage;
+  GHashTable *context_usages;
+
+  context_usages = get_usages_for_context (self, context);
+
+  usage = g_hash_table_lookup (context_usages, appid);
+  if (usage)
+    return usage;
+
+  usage = g_new0 (UsageData, 1);
+  g_hash_table_insert (context_usages, g_strdup (appid), usage);
+
+  return usage;
+}
+
+static UsageData *
+get_usage_for_app (ShellAppUsage *self,
+                   ShellApp      *app)
+{
+  const char *context;
+
+  context = _shell_window_tracker_get_app_context (shell_window_tracker_get_default (), app);
+
+  return get_app_usage_for_context_and_id (self, context, shell_app_get_id (app));
+}
+
+typedef struct {
+  gboolean in_context;
+  GHashTableIter context_iter;
+  const char *context_id;
+  GHashTableIter usage_iter;
+} UsageIterator;
+
+static void
+usage_iterator_init (ShellAppUsage *self,
+                     UsageIterator *iter)
+{
+  iter->in_context = FALSE;
+  g_hash_table_iter_init (&(iter->context_iter), self->app_usages_for_context);
+}
+
+static gboolean
+usage_iterator_next (ShellAppUsage   *self,
+                     UsageIterator   *iter,
+                     const char     **context,
+                     const char     **id,
+                     UsageData       **usage)
+{
+  gpointer key, value;
+  gboolean next_context;
+
+  if (!iter->in_context)
+    next_context = TRUE;
+  else if (!g_hash_table_iter_next (&(iter->usage_iter), &key, &value))
+    next_context = TRUE;
+  else
+    next_context = FALSE;
+
+  while (next_context)
+    {
+      GHashTable *app_usages;
+
+      if (!g_hash_table_iter_next (&(iter->context_iter), &key, &value))
+        return FALSE;
+      iter->in_context = TRUE;
+      iter->context_id = key;
+      app_usages = value;
+      g_hash_table_iter_init (&(iter->usage_iter), app_usages);
+
+      next_context = !g_hash_table_iter_next (&(iter->usage_iter), &key, &value);
+    }
+
+  *context = iter->context_id;
+  *id = key;
+  *usage = value;
+
+  return TRUE;
+}
+
+static void
+usage_iterator_remove (ShellAppUsage *self,
+                       UsageIterator *iter)
+{
+  g_assert (iter->in_context);
+
+  g_hash_table_iter_remove (&(iter->usage_iter));
+}
+
+/* Limit the score to a certain level so that most used apps can change */
+static void
+normalize_usage (ShellAppUsage *self)
+{
+  UsageIterator iter;
+  const char *context;
+  const char *id;
+  UsageData *usage;
+
+  usage_iterator_init (self, &iter);
+
+  while (usage_iterator_next (self, &iter, &context, &id, &usage))
+    {
+      usage->score /= 2;
+    }
+}
+
+static void
+increment_usage_for_app_at_time (ShellAppUsage *self,
+                                 ShellApp      *app,
+                                 long           time)
+{
+  UsageData *usage;
+  guint elapsed;
+  guint usage_count;
+
+  usage = get_usage_for_app (self, app);
+
+  usage->last_seen = time;
+
+  elapsed = time - self->watch_start_time;
+  usage_count = elapsed / FOCUS_TIME_MIN_SECONDS;
+  if (usage_count > 0)
+    {
+      usage->score += usage_count;
+      if (usage->score > SCORE_MAX)
+        normalize_usage (self);
+      ensure_queued_save (self);
+    }
+}
+
+static void
+increment_usage_for_app (ShellAppUsage *self,
+                         ShellApp      *app)
+{
+  long curtime = get_time ();
+  increment_usage_for_app_at_time (self, app, curtime);
+}
+
+static void
+on_app_running_changed (ShellWindowTracker *tracker,
+                        ShellApp           *app,
+                        gpointer            user_data)
+{
+  ShellAppUsage *self = SHELL_APP_USAGE (user_data);
+  UsageData *usage;
+  gboolean running;
+
+  if (shell_app_is_transient (app))
+    return;
+
+  usage = get_usage_for_app (self, app);
+
+  running = shell_app_get_n_windows (app) > 0;
+
+  usage->last_seen = get_time ();
+}
+
+static void
+on_focus_app_changed (ShellWindowTracker *tracker,
+                      GParamSpec         *spec,
+                      ShellAppUsage      *self)
+{
+  if (self->watched_app != NULL)
+    increment_usage_for_app (self, self->watched_app);
+
+  if (self->watched_app)
+    g_object_unref (self->watched_app);
+
+  g_object_get (tracker, "focus-app", &(self->watched_app), NULL);
+  self->watch_start_time = get_time ();
+}
+
+static void
+on_session_status_changed (DBusGProxy      *proxy,
+                           guint            status,
+                           ShellAppUsage *self)
+{
+  gboolean idle;
+
+  idle = (status >= GNOME_SESSION_STATUS_IDLE);
+  if (self->currently_idle == idle)
+    return;
+
+  self->currently_idle = idle;
+  if (idle)
+    {
+      long end_time;
+
+      /* The GNOME Session signal we watch is 5 minutes, but that's a long
+       * time for this purpose.  Instead, just add a base 30 seconds.
+       */
+      if (self->watched_app)
+        {
+          end_time = self->watch_start_time + IDLE_TIME_TRANSITION_SECONDS;
+          increment_usage_for_app_at_time (self, self->watched_app, end_time);
+        }
+    }
+  else
+    {
+      /* Transitioning to !idle, reset the start time */
+      self->watch_start_time = get_time ();
+    }
+}
+
+static void
+shell_app_usage_init (ShellAppUsage *self)
+{
+  char *shell_config_dir, *path;
+  DBusGConnection *session_bus;
+  ShellWindowTracker *tracker;
+
+  self->app_usages_for_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy);
+
+  tracker = shell_window_tracker_get_default ();
+  g_signal_connect (tracker, "notify::focus-app", G_CALLBACK (on_focus_app_changed), self);
+  g_signal_connect (tracker, "app-running-changed", G_CALLBACK (on_app_running_changed), self);
+
+  session_bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+  self->session_proxy = dbus_g_proxy_new_for_name (session_bus, "org.gnome.SessionManager",
+                                                   "/org/gnome/SessionManager/Presence",
+                                                   "org.gnome.SessionManager");
+  dbus_g_proxy_add_signal (self->session_proxy, "StatusChanged",
+                           G_TYPE_UINT, G_TYPE_INVALID, G_TYPE_INVALID);
+  dbus_g_proxy_connect_signal (self->session_proxy, "StatusChanged",
+                               G_CALLBACK (on_session_status_changed), self, NULL);
+
+  self->last_idle = 0;
+  self->currently_idle = FALSE;
+  self->enable_monitoring = FALSE;
+
+  g_object_get (shell_global_get(), "configdir", &shell_config_dir, NULL),
+  path = g_build_filename (shell_config_dir, DATA_FILENAME, NULL);
+  g_free (shell_config_dir);
+  self->configfile = g_file_new_for_path (path);
+  g_free (path);
+  restore_from_file (self);
+
+  self->gconf_client = gconf_client_get_default ();
+  gconf_client_add_dir (self->gconf_client, APP_MONITOR_GCONF_DIR,
+                        GCONF_CLIENT_PRELOAD_NONE, NULL);
+  self->gconf_notify =
+    gconf_client_notify_add (self->gconf_client, ENABLE_MONITORING_KEY,
+                             on_enable_monitoring_key_changed, self, NULL, NULL);
+  update_enable_monitoring (self);
+}
+
+static void
+shell_app_usage_finalize (GObject *object)
+{
+  ShellAppUsage *self = SHELL_APP_USAGE (object);
+
+  if (self->save_id > 0)
+    g_source_remove (self->save_id);
+  gconf_client_notify_remove (self->gconf_client, self->gconf_notify);
+  g_object_unref (self->gconf_client);
+
+  g_object_unref (self->configfile);
+
+  G_OBJECT_CLASS (shell_app_usage_parent_class)->finalize(object);
+}
+
+typedef struct {
+  ShellAppUsage *usage;
+  GHashTable *context_usages;
+} SortAppsByUsageData;
+
+static int
+sort_apps_by_usage (gconstpointer a,
+                    gconstpointer b,
+                    gpointer      datap)
+{
+  SortAppsByUsageData *data = datap;
+  ShellApp *app_a, *app_b;
+  UsageData *usage_a, *usage_b;
+
+  app_a = (ShellApp*)a;
+  app_b = (ShellApp*)b;
+
+  usage_a = g_hash_table_lookup (data->context_usages, shell_app_get_id (app_a));
+  usage_b = g_hash_table_lookup (data->context_usages, shell_app_get_id (app_b));
+
+  return usage_b->score - usage_a->score;
+}
+
+/**
+ * shell_app_usage_get_most_used:
+ * @usage: the usage instance to request
+ * @context: Activity identifier
+ * @max_count: how many applications are requested. Note that the actual
+ *     list size may be less, or NULL if not enough applications are registered.
+ *
+ * Get a list of most popular applications for a given context.
+ *
+ * Returns: (element-type ShellApp) (transfer full): List of applications
+ */
+GSList *
+shell_app_usage_get_most_used (ShellAppUsage   *self,
+                               const char      *context,
+                               gint             max_count)
+{
+  GSList *apps;
+  GList *appids, *iter;
+  GHashTable *usages;
+  ShellAppSystem *appsys;
+  SortAppsByUsageData data;
+
+  usages = g_hash_table_lookup (self->app_usages_for_context, context);
+  if (usages == NULL)
+    return NULL;
+
+  appsys = shell_app_system_get_default ();
+
+  appids = g_hash_table_get_keys (usages);
+  apps = NULL;
+  for (iter = appids; iter; iter = iter->next)
+    {
+      const char *appid = iter->data;
+      ShellApp *app;
+
+      app = shell_app_system_get_app (appsys, appid);
+      if (!app)
+        continue;
+
+      apps = g_slist_prepend (apps, g_object_ref (app));
+    }
+
+  g_list_free (appids);
+
+  data.usage = self;
+  data.context_usages = usages;
+  apps = g_slist_sort_with_data (apps, sort_apps_by_usage, &data);
+
+  return apps;
+}
+
+static void
+ensure_queued_save (ShellAppUsage *self)
+{
+  if (self->save_id != 0)
+    return;
+  self->save_id = g_timeout_add_seconds (SAVE_APPS_TIMEOUT_SECONDS, idle_save_application_usage, self);
+}
+
+/* Used to sort highest scores at the top */
+static gint
+usage_sort_apps (gconstpointer data1,
+                 gconstpointer data2)
+{
+  const UsageData *u1 = data1;
+  const UsageData *u2 = data2;
+
+  if (u1->score > u2->score)
+    return -1;
+  else if (u1->score == u2->score)
+    return 0;
+  else
+    return 1;
+}
+
+/* Clean up apps we see rarely.
+ * The logic behind this is that if an app was seen less than SCORE_MIN times
+ * and not seen for a week, it can probably be forgotten about.
+ * This should much reduce the size of the list and avoid 'pollution'. */
+static gboolean
+idle_clean_usage (ShellAppUsage *self)
+{
+  UsageIterator iter;
+  const char *context;
+  const char *id;
+  UsageData *usage;
+  long current_time;
+  long week_ago;
+
+  current_time = get_time ();
+  week_ago = current_time - (7 * 24 * 60 * 60);
+
+  usage_iterator_init (self, &iter);
+
+  while (usage_iterator_next (self, &iter, &context, &id, &usage))
+    {
+      if ((usage->score < SCORE_MIN) &&
+          (usage->last_seen < week_ago))
+        usage_iterator_remove (self, &iter);
+    }
+
+  return FALSE;
+}
+
+static gboolean
+write_escaped (GDataOutputStream   *stream,
+               const char          *str,
+               GError             **error)
+{
+  gboolean ret;
+  char *quoted = g_markup_escape_text (str, -1);
+  ret = g_data_output_stream_put_string (stream, quoted, NULL, error);
+  g_free (quoted);
+  return ret;
+}
+
+static gboolean
+write_attribute_string (GDataOutputStream *stream,
+                        const char        *elt_name,
+                        const char        *str,
+                        GError           **error)
+{
+  gboolean ret = FALSE;
+  char *elt;
+
+  elt = g_strdup_printf (" %s=\"", elt_name);
+  ret = g_data_output_stream_put_string (stream, elt, NULL, error);
+  g_free (elt);
+  if (!ret)
+    goto out;
+
+  ret = write_escaped (stream, str, error);
+  if (!ret)
+    goto out;
+
+  ret = g_data_output_stream_put_string (stream, "\"", NULL, error);
+
+out:
+  return ret;
+}
+
+static gboolean
+write_attribute_uint (GDataOutputStream *stream,
+                      const char        *elt_name,
+                      guint              value,
+                      GError           **error)
+{
+  gboolean ret;
+  char *buf;
+
+  buf = g_strdup_printf ("%u", value);
+  ret = write_attribute_string (stream, elt_name, buf, error);
+  g_free (buf);
+
+  return ret;
+}
+
+static gboolean
+write_attribute_double (GDataOutputStream *stream,
+                        const char        *elt_name,
+                        double             value,
+                        GError           **error)
+{
+  gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
+  gboolean ret;
+
+  g_ascii_dtostr (buf, sizeof (buf), value);
+  ret = write_attribute_string (stream, elt_name, buf, error);
+
+  return ret;
+}
+
+/* Save app data lists to file */
+static gboolean
+idle_save_application_usage (gpointer data)
+{
+  ShellAppUsage *self = SHELL_APP_USAGE (data);
+  UsageIterator iter;
+  const char *current_context;
+  const char *context;
+  const char *id;
+  UsageData *usage;
+  GFileOutputStream *output;
+  GOutputStream *buffered_output;
+  GDataOutputStream *data_output;
+  GError *error = NULL;
+
+  self->save_id = 0;
+
+  /* Parent directory is already created by shell-global */
+  output = g_file_replace (self->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
+  if (!output)
+    {
+      g_debug ("Could not save applications usage data: %s", error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+  buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM (output));
+  g_object_unref (output);
+  data_output = g_data_output_stream_new (G_OUTPUT_STREAM (buffered_output));
+  g_object_unref (buffered_output);
+
+  if (!g_data_output_stream_put_string (data_output, "<?xml version=\"1.0\"?>\n<application-state>\n", NULL, &error))
+    goto out;
+
+  usage_iterator_init (self, &iter);
+
+  current_context = NULL;
+  while (usage_iterator_next (self, &iter, &context, &id, &usage))
+    {
+      ShellApp *app;
+
+      app = shell_app_system_get_app (shell_app_system_get_default(), id);
+
+      if (!app)
+        continue;
+
+      if (context != current_context)
+        {
+          if (current_context != NULL)
+            {
+              if (!g_data_output_stream_put_string (data_output, "  </context>", NULL, &error))
+                goto out;
+            }
+          current_context = context;
+          if (!g_data_output_stream_put_string (data_output, "  <context", NULL, &error))
+            goto out;
+          if (!write_attribute_string (data_output, "id", context, &error))
+            goto out;
+          if (!g_data_output_stream_put_string (data_output, ">\n", NULL, &error))
+            goto out;
+        }
+      if (!g_data_output_stream_put_string (data_output, "    <application", NULL, &error))
+        goto out;
+      if (!write_attribute_string (data_output, "id", id, &error))
+        goto out;
+      if (!write_attribute_uint (data_output, "open-window-count", shell_app_get_n_windows (app), &error))
+        goto out;
+
+      if (!write_attribute_double (data_output, "score", usage->score, &error))
+        goto out;
+      if (!write_attribute_uint (data_output, "last-seen", usage->last_seen, &error))
+        goto out;
+      if (!g_data_output_stream_put_string (data_output, "/>\n", NULL, &error))
+        goto out;
+    }
+  if (current_context != NULL)
+    {
+      if (!g_data_output_stream_put_string (data_output, "  </context>\n", NULL, &error))
+        goto out;
+    }
+  if (!g_data_output_stream_put_string (data_output, "</application-state>\n", NULL, &error))
+    goto out;
+
+out:
+  if (!error)
+    g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error);
+  g_object_unref (data_output);
+  if (error)
+    {
+      g_debug ("Could not save applications usage data: %s", error->message);
+      g_error_free (error);
+    }
+  return FALSE;
+}
+
+typedef struct {
+  ShellAppUsage *self;
+  char *context;
+} ParseData;
+
+static void
+shell_app_usage_start_element_handler  (GMarkupParseContext *context,
+                                          const gchar         *element_name,
+                                          const gchar        **attribute_names,
+                                          const gchar        **attribute_values,
+                                          gpointer             user_data,
+                                          GError             **error)
+{
+  ParseData *data = user_data;
+
+  if (strcmp (element_name, "application-state") == 0)
+    {
+    }
+  else if (strcmp (element_name, "context") == 0)
+    {
+      char *context = NULL;
+      const char **attribute;
+      const char **value;
+
+      for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+        {
+          if (strcmp (*attribute, "id") == 0)
+            context = g_strdup (*value);
+        }
+      if (context < 0)
+        {
+          g_set_error (error,
+                       G_MARKUP_ERROR,
+                       G_MARKUP_ERROR_PARSE,
+                       "Missing attribute id on <%s> element",
+                       element_name);
+          return;
+        }
+      data->context = context;
+    }
+  else if (strcmp (element_name, "application") == 0)
+    {
+      const char **attribute;
+      const char **value;
+      UsageData *usage;
+      char *appid = NULL;
+      GHashTable *usage_table;
+
+      for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+        {
+          if (strcmp (*attribute, "id") == 0)
+            appid = g_strdup (*value);
+        }
+
+      if (!appid)
+        {
+          g_set_error (error,
+                       G_MARKUP_ERROR,
+                       G_MARKUP_ERROR_PARSE,
+                       "Missing attribute id on <%s> element",
+                       element_name);
+          return;
+        }
+
+      usage_table = get_usages_for_context (data->self, data->context);
+
+      usage = g_new0 (UsageData, 1);
+      g_hash_table_insert (usage_table, appid, usage);
+
+      for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
+        {
+          if (strcmp (*attribute, "open-window-count") == 0)
+            {
+              guint count = strtoul (*value, NULL, 10);
+              if (count > 0)
+                 data->self->previously_running = g_slist_prepend (data->self->previously_running,
+                                                                      usage);
+            }
+          else if (strcmp (*attribute, "score") == 0)
+            {
+              usage->score = g_ascii_strtod (*value, NULL);
+            }
+          else if (strcmp (*attribute, "last-seen") == 0)
+            {
+              usage->last_seen = (guint) g_ascii_strtoull (*value, NULL, 10);
+            }
+        }
+    }
+  else
+    {
+      g_set_error (error,
+                   G_MARKUP_ERROR,
+                   G_MARKUP_ERROR_PARSE,
+                   "Unknown element <%s>",
+                   element_name);
+    }
+}
+
+static void
+shell_app_usage_end_element_handler (GMarkupParseContext *context,
+                                       const gchar         *element_name,
+                                       gpointer             user_data,
+                                       GError             **error)
+{
+  ParseData *data = user_data;
+
+  if (strcmp (element_name, "context") == 0)
+    {
+      g_free (data->context);
+      data->context = NULL;
+    }
+}
+
+static void
+shell_app_usage_text_handler (GMarkupParseContext *context,
+                                const gchar         *text,
+                                gsize                text_len,
+                                gpointer             user_data,
+                                GError             **error)
+{
+  /* do nothing, very very fast */
+}
+
+static GMarkupParser app_state_parse_funcs =
+{
+  shell_app_usage_start_element_handler,
+  shell_app_usage_end_element_handler,
+  shell_app_usage_text_handler,
+  NULL,
+  NULL
+};
+
+/* Load data about apps usage from file */
+static void
+restore_from_file (ShellAppUsage *self)
+{
+  GFileInputStream *input;
+  ParseData parse_data;
+  GMarkupParseContext *parse_context;
+  GError *error = NULL;
+  char buf[1024];
+
+  input = g_file_read (self->configfile, NULL, &error);
+  if (error)
+    {
+      if (error->code != G_IO_ERROR_NOT_FOUND)
+        g_warning ("Could not load applications usage data: %s", error->message);
+
+      g_error_free (error);
+      return;
+    }
+
+  memset (&parse_data, 0, sizeof (ParseData));
+  parse_data.self = self;
+  parse_data.context = NULL;
+  parse_context = g_markup_parse_context_new (&app_state_parse_funcs, 0, &parse_data, NULL);
+
+  while (TRUE)
+    {
+      gssize count = g_input_stream_read ((GInputStream*) input, buf, sizeof(buf), NULL, &error);
+      if (count <= 0)
+        goto out;
+      if (!g_markup_parse_context_parse (parse_context, buf, count, &error))
+        goto out;
+     }
+
+out:
+  g_free (parse_data.context);
+  g_markup_parse_context_free (parse_context);
+  g_input_stream_close ((GInputStream*)input, NULL, NULL);
+  g_object_unref (input);
+
+  idle_clean_usage (self);
+  self->previously_running = g_slist_sort (self->previously_running, usage_sort_apps);
+
+  if (error)
+    {
+      g_warning ("Could not load applications usage data: %s", error->message);
+      g_error_free (error);
+    }
+}
+
+/* Enable or disable the timers, depending on the value of ENABLE_MONITORING_KEY
+ * and taking care of the previous state.  If selfing is disabled, we still
+ * report apps usage based on (possibly) saved data, but don't collect data.
+ */
+static void
+update_enable_monitoring (ShellAppUsage *self)
+{
+  GConfValue *value;
+  gboolean enable;
+
+  value = gconf_client_get (self->gconf_client, ENABLE_MONITORING_KEY, NULL);
+  if (value)
+    {
+      enable = gconf_value_get_bool (value);
+      gconf_value_free (value);
+    }
+  else /* Schema is not present, set default value by hand to avoid getting FALSE */
+    enable = TRUE;
+
+  /* Be sure not to start the timers if they were already set */
+  if (enable && !self->enable_monitoring)
+    {
+      on_focus_app_changed (shell_window_tracker_get_default (), NULL, self);
+    }
+  /* ...and don't try to stop them if they were not running */
+  else if (!enable && self->enable_monitoring)
+    {
+      if (self->watched_app)
+        g_object_unref (self->watched_app);
+      self->watched_app = NULL;
+      if (self->save_id)
+        {
+          g_source_remove (self->save_id);
+          self->save_id = 0;
+        }
+    }
+
+  self->enable_monitoring = enable;
+}
+
+/* Called when the ENABLE_MONITORING_KEY boolean has changed */
+static void
+on_enable_monitoring_key_changed (GConfClient *client,
+                                  guint        connexion_id,
+                                  GConfEntry  *entry,
+                                  gpointer     self)
+{
+  update_enable_monitoring ((ShellAppUsage *) self);
+}
+
+/**
+ * shell_app_usage_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellAppUsage instance
+ */
+ShellAppUsage *
+shell_app_usage_get_default ()
+{
+  static ShellAppUsage *instance;
+
+  if (instance == NULL)
+    instance = g_object_new (SHELL_TYPE_APP_USAGE, NULL);
+
+  return instance;
+}
diff --git a/src/shell-app-usage.h b/src/shell-app-usage.h
new file mode 100644
index 0000000..f136bde
--- /dev/null
+++ b/src/shell-app-usage.h
@@ -0,0 +1,36 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_USAGE_H__
+#define __SHELL_APP_USAGE_H__
+
+#include "shell-app.h"
+#include "shell-window-tracker.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellAppUsage ShellAppUsage;
+typedef struct _ShellAppUsageClass ShellAppUsageClass;
+typedef struct _ShellAppUsagePrivate ShellAppUsagePrivate;
+
+#define SHELL_TYPE_APP_USAGE              (shell_app_usage_get_type ())
+#define SHELL_APP_USAGE(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP_USAGE, ShellAppUsage))
+#define SHELL_APP_USAGE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_APP_USAGE, ShellAppUsageClass))
+#define SHELL_IS_APP_USAGE(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_APP_USAGE))
+#define SHELL_IS_APP_USAGE_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_APP_USAGE))
+#define SHELL_APP_USAGE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_APP_USAGE, ShellAppUsageClass))
+
+struct _ShellAppUsageClass
+{
+  GObjectClass parent_class;
+};
+
+GType shell_app_usage_get_type (void) G_GNUC_CONST;
+
+ShellAppUsage* shell_app_usage_get_default(void);
+
+GSList *shell_app_usage_get_most_used (ShellAppUsage *monitor,
+                                       const char      *context,
+                                       gint             number);
+
+G_END_DECLS
+
+#endif /* __SHELL_APP_USAGE_H__ */
diff --git a/src/shell-app.c b/src/shell-app.c
index b89fafb..ac0cf56 100644
--- a/src/shell-app.c
+++ b/src/shell-app.c
@@ -1,6 +1,6 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 
-#include "shell-app.h"
+#include "shell-app-private.h"
 #include "shell-global.h"
 
 /**
@@ -70,13 +70,20 @@ shell_app_is_transient (ShellApp *app)
   return shell_app_info_is_transient (app->info);
 }
 
+gboolean
+shell_app_launch (ShellApp  *self,
+                  GError   **error)
+{
+  return shell_app_info_launch (self->info, error);
+}
+
 /**
- * shell_app_get_info:
+ * _shell_app_get_info:
  *
  * Returns: (transfer none): Associated app info
  */
 ShellAppInfo *
-shell_app_get_info (ShellApp *app)
+_shell_app_get_info (ShellApp *app)
 {
   return app->info;
 }
@@ -142,6 +149,12 @@ shell_app_get_windows (ShellApp *app)
   return app->windows;
 }
 
+guint
+shell_app_get_n_windows (ShellApp *app)
+{
+  return g_slist_length (app->windows);
+}
+
 static gboolean
 shell_app_has_visible_windows (ShellApp   *app)
 {
@@ -219,6 +232,7 @@ _shell_app_new_for_window (MetaWindow      *window)
 
   app = g_object_new (SHELL_TYPE_APP, NULL);
   app->info = shell_app_system_create_from_window (shell_app_system_get_default (), window);
+  _shell_app_system_register_app (shell_app_system_get_default (), app);
   _shell_app_add_window (app, window);
 
   return app;
@@ -231,6 +245,7 @@ _shell_app_new (ShellAppInfo    *info)
 
   app = g_object_new (SHELL_TYPE_APP, NULL);
   app->info = shell_app_info_ref (info);
+  _shell_app_system_register_app (shell_app_system_get_default (), app);
 
   return app;
 }
diff --git a/src/shell-app.h b/src/shell-app.h
index 232b07a..f6e2f71 100644
--- a/src/shell-app.h
+++ b/src/shell-app.h
@@ -2,11 +2,9 @@
 #ifndef __SHELL_APP_H__
 #define __SHELL_APP_H__
 
-#include <glib-object.h>
-#include <glib.h>
+#include <clutter/clutter.h>
 
 #include "window.h"
-#include "shell-app-system.h"
 
 G_BEGIN_DECLS
 
@@ -35,8 +33,9 @@ ClutterActor *shell_app_create_icon_texture (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);
 
-ShellAppInfo *shell_app_get_info (ShellApp *app);
+guint shell_app_get_n_windows (ShellApp *app);
 
 GSList *shell_app_get_windows (ShellApp *app);
 
@@ -44,14 +43,6 @@ 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-window-tracker.c b/src/shell-window-tracker.c
new file mode 100644
index 0000000..a311a0f
--- /dev/null
+++ b/src/shell-window-tracker.c
@@ -0,0 +1,826 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#include <string.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#define SN_API_NOT_YET_FROZEN 1
+#include <libsn/sn.h>
+
+#include "shell-window-tracker.h"
+#include "shell-app-system.h"
+#include "shell-app-private.h"
+#include "shell-texture-cache.h"
+#include "shell-global.h"
+#include "shell-marshal.h"
+
+#include "display.h"
+#include "window.h"
+#include "group.h"
+#include "util.h"
+
+/* This file includes modified code from
+ * desktop-data-engine/engine-dbus/hippo-application-monitor.c
+ * in the functions collecting application usage data.
+ * Written by Owen Taylor, originally licensed under LGPL 2.1.
+ * Copyright Red Hat, Inc. 2006-2008
+ */
+
+/**
+ * SECTION:shell-window-tracker
+ * @short_description: Associate windows with applications
+ *
+ * Maintains a mapping from windows to applications (.desktop file ids).
+ * It currently implements this with some heuristics on the WM_CLASS X11
+ * property (and some static override regexps); in the future, we want to
+ * have it also track through startup-notification.
+ */
+
+/* Title patterns to detect apps that don't set WM class as needed.
+ * Format: application ID, title regex pattern, NULL (for GRegex) */
+static struct
+{
+  const char *app_id;
+  const char *pattern;
+  GRegex *regex;
+} title_patterns[] =  {
+    {"mozilla-firefox.desktop", ".* - Mozilla Firefox", NULL}, \
+    {"openoffice.org-writer.desktop", ".* - OpenOffice.org Writer$", NULL}, \
+    {"openoffice.org-calc.desktop", ".* - OpenOffice.org Calc$", NULL}, \
+    {"openoffice.org-impress.desktop", ".* - OpenOffice.org Impress$", NULL}, \
+    {"openoffice.org-draw.desktop", ".* - OpenOffice.org Draw$", NULL}, \
+    {"openoffice.org-base.desktop", ".* - OpenOffice.org Base$", NULL}, \
+    {"openoffice.org-math.desktop", ".* - OpenOffice.org Math$", NULL}, \
+    {NULL, NULL, NULL}
+};
+
+struct _ShellWindowTracker
+{
+  GObject parent;
+
+  guint idle_focus_change_id;
+  ShellApp *focus_app;
+
+  /* <MetaWindow * window, ShellApp *app> */
+  GHashTable *window_to_app;
+
+  /* <const char *id, ShellApp *app> */
+  GHashTable *running_apps;
+};
+
+G_DEFINE_TYPE (ShellWindowTracker, shell_window_tracker, G_TYPE_OBJECT);
+
+enum {
+  PROP_0,
+  PROP_FOCUS_APP
+};
+
+enum {
+  APP_RUNNING_CHANGED,
+  STARTUP_SEQUENCE_CHANGED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void shell_window_tracker_finalize (GObject *object);
+
+static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellWindowTracker *tracker);
+
+static void track_window (ShellWindowTracker *monitor, MetaWindow *window);
+static void disassociate_window (ShellWindowTracker *monitor, MetaWindow *window);
+
+
+static void
+shell_window_tracker_get_property (GObject    *gobject,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  ShellWindowTracker *tracker = SHELL_WINDOW_TRACKER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_FOCUS_APP:
+      g_value_set_object (value, tracker->focus_app);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+shell_window_tracker_class_init (ShellWindowTrackerClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->get_property = shell_window_tracker_get_property;
+  gobject_class->finalize = shell_window_tracker_finalize;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_FOCUS_APP,
+                                   g_param_spec_object ("focus-app",
+                                                        "Focus App",
+                                                        "Focused application",
+                                                        SHELL_TYPE_APP,
+                                                        G_PARAM_READABLE));
+
+  signals[APP_RUNNING_CHANGED] = g_signal_new ("app-running-changed",
+                                     SHELL_TYPE_WINDOW_TRACKER,
+                                     G_SIGNAL_RUN_LAST,
+                                     0,
+                                     NULL, NULL,
+                                     g_cclosure_marshal_VOID__OBJECT,
+                                     G_TYPE_NONE, 1,
+                                     SHELL_TYPE_APP);
+  signals[STARTUP_SEQUENCE_CHANGED] = g_signal_new ("startup-sequence-changed",
+                                   SHELL_TYPE_WINDOW_TRACKER,
+                                   G_SIGNAL_RUN_LAST,
+                                   0,
+                                   NULL, NULL,
+                                   g_cclosure_marshal_VOID__BOXED,
+                                   G_TYPE_NONE, 1, SHELL_TYPE_STARTUP_SEQUENCE);
+}
+
+/**
+ * get_app_id_from_title:
+ *
+ * Use a window's "title" property to determine an application ID.
+ * This is a temporary crutch for a few applications until we get
+ * them correctly setting their WM_CLASS.
+ */
+static const char *
+get_app_id_from_title (MetaWindow   *window)
+{
+  static gboolean patterns_initialized = FALSE;
+  const char *title;
+  int i;
+
+  title = meta_window_get_title (window);
+
+  if (!patterns_initialized) /* Generate match patterns once for all */
+    {
+      patterns_initialized = TRUE;
+      for (i = 0; title_patterns[i].app_id; i++)
+        {
+          title_patterns[i].regex = g_regex_new (title_patterns[i].pattern,
+                                                 0, 0, NULL);
+        }
+    }
+
+  /* Match window title patterns to identifiers for non-standard apps */
+  if (title)
+    {
+      for (i = 0; title_patterns[i].app_id; i++)
+        {
+          if (g_regex_match (title_patterns[i].regex, title, 0, NULL))
+            {
+              /* Matched, return the app id we want */
+              return title_patterns[i].app_id;
+            }
+        }
+    }
+  return NULL;
+}
+
+/**
+ * get_appid_from_window:
+ *
+ * Turn the WM_CLASS property into our best guess at a .desktop file id.
+ */
+static char *
+get_appid_from_window (MetaWindow  *window)
+{
+  const char *wmclass;
+  char *appid_guess;
+
+  wmclass = meta_window_get_wm_class (window);
+  if (!wmclass)
+    return NULL;
+
+  appid_guess = g_ascii_strdown (wmclass, -1);
+
+  /* This handles "Fedora Eclipse", probably others.
+   * Note g_strdelimit is modify-in-place. */
+  g_strdelimit (appid_guess, " ", '-');
+
+  return appid_guess;
+}
+
+/**
+ * window_is_tracked:
+ *
+ * We don't attempt to associate override-redirect windows with applications
+ * at all, since there's no reason to do so yet.
+ *
+ * Returns: %TRUE iff we want to scan this window for application association
+ */
+static gboolean
+window_is_tracked (MetaWindow *window)
+{
+  if (meta_window_is_override_redirect (window))
+    return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * shell_window_tracker_is_window_interesting:
+ *
+ * The ShellWindowTracker associates certain kinds of windows with
+ * applications; however, others we don't want to
+ * appear in places where we want to give a list of windows
+ * for an application, such as the alt-tab dialog.
+ *
+ * An example of a window we don't want to show is the root
+ * desktop window.  We skip all override-redirect types, and also
+ * exclude other window types like tooltip explicitly, though generally
+ * most of these should be override-redirect.
+ *
+ * Returns: %TRUE iff a window is "interesting"
+ */
+gboolean
+shell_window_tracker_is_window_interesting (MetaWindow *window)
+{
+  if (!window_is_tracked (window))
+    return FALSE;
+
+  if (meta_window_is_skip_taskbar (window))
+    return FALSE;
+
+  switch (meta_window_get_window_type (window))
+    {
+      /* Definitely ignore these. */
+      case META_WINDOW_DESKTOP:
+      case META_WINDOW_DOCK:
+      case META_WINDOW_SPLASHSCREEN:
+      /* Should have already been handled by override_redirect above,
+       * but explicitly list here so we get the "unhandled enum"
+       * warning if in the future anything is added.*/
+      case META_WINDOW_DROPDOWN_MENU:
+      case META_WINDOW_POPUP_MENU:
+      case META_WINDOW_TOOLTIP:
+      case META_WINDOW_NOTIFICATION:
+      case META_WINDOW_COMBO:
+      case META_WINDOW_DND:
+      case META_WINDOW_OVERRIDE_OTHER:
+        return FALSE;
+      case META_WINDOW_NORMAL:
+      case META_WINDOW_DIALOG:
+      case META_WINDOW_MODAL_DIALOG:
+      case META_WINDOW_MENU:
+      case META_WINDOW_TOOLBAR:
+      case META_WINDOW_UTILITY:
+        break;
+    }
+
+  return TRUE;
+}
+
+/**
+ * get_app_for_window_direct:
+ *
+ * Looks only at the given window, and attempts to determine
+ * an application based on WM_CLASS.  If that fails, then
+ * a "transient" application is created.
+ *
+ * Return value: (transfer full): A newly-referenced #ShellApp
+ */
+static ShellApp *
+get_app_for_window_direct (MetaWindow  *window)
+{
+  ShellApp *app;
+  ShellAppSystem *appsys;
+  char *wmclass;
+  char *with_desktop;
+
+  wmclass = get_appid_from_window (window);
+
+  if (!wmclass)
+    return _shell_app_new_for_window (window);
+
+  with_desktop = g_strjoin (NULL, wmclass, ".desktop", NULL);
+  g_free (wmclass);
+
+  appsys = shell_app_system_get_default ();
+  app = shell_app_system_lookup_heuristic_basename (appsys, with_desktop);
+  g_free (with_desktop);
+
+  if (app == NULL)
+    {
+      const char *id = get_app_id_from_title (window);
+
+      if (id != NULL)
+        app = shell_app_system_get_app (appsys, id);
+    }
+
+  return app;
+}
+
+/**
+ * get_app_for_window:
+ *
+ * Determines the application associated with a window, using
+ * all available information such as the window's MetaGroup,
+ * and what we know about other windows.
+ */
+static ShellApp *
+get_app_for_window (ShellWindowTracker    *monitor,
+                    MetaWindow         *window)
+{
+  ShellApp *result;
+  MetaWindow *source_window;
+  GSList *group_windows;
+  MetaGroup *group;
+  GSList *iter;
+
+  group = meta_window_get_group (window);
+  if (group == NULL)
+    group_windows = g_slist_prepend (NULL, window);
+  else
+    group_windows = meta_group_list_windows (group);
+
+  source_window = window;
+
+  result = NULL;
+  /* Try finding a window in the group of type NORMAL; if we
+   * succeed, use that as our source. */
+  for (iter = group_windows; iter; iter = iter->next)
+    {
+      MetaWindow *group_window = iter->data;
+
+      if (meta_window_get_window_type (group_window) != META_WINDOW_NORMAL)
+        continue;
+
+       source_window = group_window;
+       result = g_hash_table_lookup (monitor->window_to_app, group_window);
+       if (result)
+         break;
+    }
+
+  g_slist_free (group_windows);
+
+  if (result != NULL)
+    {
+      g_object_ref (result);
+      return result;
+    }
+
+  return get_app_for_window_direct (source_window);
+}
+
+const char *
+_shell_window_tracker_get_app_context (ShellWindowTracker *tracker, ShellApp *app)
+{
+  return "";
+}
+
+static MetaWindow *
+get_active_window (ShellWindowTracker *monitor)
+{
+  MetaScreen *screen;
+  MetaDisplay *display;
+  MetaWindow *window;
+
+  screen = shell_global_get_screen (shell_global_get ());
+  display = meta_screen_get_display (screen);
+  window = meta_display_get_focus_window (display);
+
+  if (window != NULL && shell_window_tracker_is_window_interesting (window))
+    return window;
+  return NULL;
+}
+
+static void
+on_transient_window_title_changed (MetaWindow      *window,
+                                   GParamSpec      *spec,
+                                   ShellWindowTracker *self)
+{
+  ShellAppSystem *appsys;
+  ShellApp *app;
+  const char *id;
+
+  /* Check if we now have a mapping using the window title */
+  id = get_app_id_from_title (window);
+  if (id == NULL)
+    return;
+
+  appsys = shell_app_system_get_default ();
+  app = shell_app_system_get_app (appsys, id);
+  if (app == NULL)
+    return;
+  g_object_unref (app);
+
+  /* We found an app, don't listen for further title changes */
+  g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_transient_window_title_changed),
+                                        self);
+
+  /* It's simplest to just treat this as a remove + add. */
+  disassociate_window (self, window);
+  track_window (self, window);
+}
+
+static void
+track_window (ShellWindowTracker *self,
+              MetaWindow      *window)
+{
+  ShellApp *app;
+
+  if (!window_is_tracked (window))
+    return;
+
+  app = get_app_for_window (self, window);
+  if (!app)
+    return;
+
+  /* At this point we've stored the association from window -> application */
+  g_hash_table_insert (self->window_to_app, window, app);
+
+  /* However, only put interesting windows in the window list for an app. */
+  if (!shell_window_tracker_is_window_interesting (window))
+    return;
+
+  if (shell_app_is_transient (app))
+    {
+      /* For a transient application, it's possible one of our title regexps
+       * will match at a later time, i.e. the application may not have set
+       * its title fully at the time it initially maps a window.  Watch
+       * for title changes and recompute the app.
+       */
+      g_signal_connect (window, "notify::title", G_CALLBACK (on_transient_window_title_changed), self);
+    }
+
+  _shell_app_add_window (app, window);
+
+  if (shell_app_get_n_windows (app) == 1)
+    {
+      /* 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_RUNNING_CHANGED], 0, app);
+    }
+}
+
+static void
+shell_window_tracker_on_window_added (MetaWorkspace   *workspace,
+                                   MetaWindow      *window,
+                                   gpointer         user_data)
+{
+  ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
+
+  track_window (self, window);
+}
+
+static void
+disassociate_window (ShellWindowTracker   *self,
+                     MetaWindow        *window)
+{
+  ShellApp *app;
+
+  app = g_hash_table_lookup (self->window_to_app, window);
+  if (!app)
+    return;
+
+  g_object_ref (app);
+
+  g_hash_table_remove (self->window_to_app, window);
+
+  _shell_app_remove_window (app, window);
+
+  if (shell_app_get_n_windows (app) == 0)
+    {
+       const char *id = shell_app_get_id (app);
+       g_hash_table_remove (self->running_apps, id);
+       g_signal_emit (self, signals[APP_RUNNING_CHANGED], 0, app);
+    }
+
+  g_object_unref (app);
+}
+
+static void
+shell_window_tracker_on_window_removed (MetaWorkspace   *workspace,
+                                     MetaWindow      *window,
+                                     gpointer         user_data)
+{
+  disassociate_window (SHELL_WINDOW_TRACKER (user_data), window);
+}
+
+static void
+load_initial_windows (ShellWindowTracker *monitor)
+{
+  GList *workspaces, *iter;
+  MetaScreen *screen = shell_global_get_screen (shell_global_get ());
+  workspaces = meta_screen_get_workspaces (screen);
+
+  for (iter = workspaces; iter; iter = iter->next)
+    {
+      MetaWorkspace *workspace = iter->data;
+      GList *windows = meta_workspace_list_windows (workspace);
+      GList *window_iter;
+
+      for (window_iter = windows; window_iter; window_iter = window_iter->next)
+        {
+          MetaWindow *window = window_iter->data;
+          track_window (monitor, window);
+        }
+
+      g_list_free (windows);
+    }
+}
+
+static void
+shell_window_tracker_on_n_workspaces_changed (MetaScreen    *screen,
+                                           GParamSpec    *pspec,
+                                           gpointer       user_data)
+{
+  ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
+  GList *workspaces, *iter;
+
+  workspaces = meta_screen_get_workspaces (screen);
+
+  for (iter = workspaces; iter; iter = iter->next)
+    {
+      MetaWorkspace *workspace = iter->data;
+
+      /* This pair of disconnect/connect is idempotent if we were
+       * already connected, while ensuring we get connected for
+       * new workspaces.
+       */
+      g_signal_handlers_disconnect_by_func (workspace,
+                                            shell_window_tracker_on_window_added,
+                                            self);
+      g_signal_handlers_disconnect_by_func (workspace,
+                                            shell_window_tracker_on_window_removed,
+                                            self);
+
+      g_signal_connect (workspace, "window-added",
+                        G_CALLBACK (shell_window_tracker_on_window_added), self);
+      g_signal_connect (workspace, "window-removed",
+                        G_CALLBACK (shell_window_tracker_on_window_removed), self);
+    }
+}
+
+static void
+init_window_tracking (ShellWindowTracker *self)
+{
+  MetaDisplay *display;
+  MetaScreen *screen = shell_global_get_screen (shell_global_get ());
+
+  g_signal_connect (screen, "notify::n-workspaces",
+                    G_CALLBACK (shell_window_tracker_on_n_workspaces_changed), self);
+  display = meta_screen_get_display (screen);
+  g_signal_connect (display, "notify::focus-window",
+                    G_CALLBACK (on_focus_window_changed), self);
+
+  shell_window_tracker_on_n_workspaces_changed (screen, NULL, self);
+}
+
+static void
+on_startup_sequence_changed (MetaScreen            *screen,
+                             SnStartupSequence     *sequence,
+                             ShellWindowTracker    *self)
+{
+  /* Just proxy the signal */
+  g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
+}
+
+static void
+shell_window_tracker_init (ShellWindowTracker *self)
+{
+  MetaScreen *screen;
+
+  self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                               NULL, (GDestroyNotify) g_object_unref);
+
+  self->running_apps = g_hash_table_new (g_str_hash, g_str_equal);
+
+  screen = shell_global_get_screen (shell_global_get ());
+
+  g_signal_connect (G_OBJECT (screen), "startup-sequence-changed",
+                    G_CALLBACK (on_startup_sequence_changed), self);
+
+  load_initial_windows (self);
+  init_window_tracking (self);
+}
+
+static void
+shell_window_tracker_finalize (GObject *object)
+{
+  ShellWindowTracker *self = SHELL_WINDOW_TRACKER (object);
+  int i;
+
+  g_hash_table_destroy (self->running_apps);
+  g_hash_table_destroy (self->window_to_app);
+  for (i = 0; title_patterns[i].app_id; i++)
+    g_regex_unref (title_patterns[i].regex);
+
+  G_OBJECT_CLASS (shell_window_tracker_parent_class)->finalize(object);
+}
+
+/**
+ * shell_window_tracker_get_window_app
+ * @monitor: An app monitor instance
+ * @metawin: A #MetaWindow
+ *
+ * Returns: (transfer full): Application associated with window
+ */
+ShellApp *
+shell_window_tracker_get_window_app (ShellWindowTracker *monitor,
+                                  MetaWindow      *metawin)
+{
+  MetaWindow *transient_for;
+  ShellApp *app;
+
+  transient_for = meta_window_get_transient_for (metawin);
+  if (transient_for != NULL)
+    metawin = transient_for;
+
+  app = g_hash_table_lookup (monitor->window_to_app, metawin);
+  if (app)
+    g_object_ref (app);
+
+  return app;
+}
+
+/**
+ * shell_window_tracker_get_running_apps:
+ * @monitor: An app monitor instance
+ * @context: Activity identifier
+ *
+ * Returns the set of applications which currently have at least one open
+ * window in the given context.  The returned list will be sorted
+ * by shell_app_compare().
+ *
+ * Returns: (element-type ShellApp) (transfer full): Active applications
+ */
+GSList *
+shell_window_tracker_get_running_apps (ShellWindowTracker *monitor,
+                                    const char      *context)
+{
+  gpointer key, value;
+  GSList *ret;
+  GHashTableIter iter;
+
+  g_hash_table_iter_init (&iter, monitor->running_apps);
+
+  ret = NULL;
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      ShellApp *app = value;
+
+      if (strcmp (context, _shell_window_tracker_get_app_context (monitor, app)) != 0)
+        continue;
+
+      ret = g_slist_prepend (ret, g_object_ref (app));
+    }
+
+  ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
+
+  return ret;
+}
+
+static gboolean
+idle_handle_focus_change (gpointer data)
+{
+  ShellWindowTracker *tracker = data;
+  MetaWindow *new_focus_win;
+  ShellApp *new_focus_app;
+
+  tracker->idle_focus_change_id = 0;
+
+  new_focus_win = get_active_window (tracker);
+  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 FALSE;
+
+  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");
+
+  return FALSE;
+}
+
+static void
+on_focus_window_changed (MetaDisplay     *display,
+                         GParamSpec      *spec,
+                         ShellWindowTracker *self)
+{
+  if (self->idle_focus_change_id != 0)
+    return;
+
+  self->idle_focus_change_id = meta_later_add (META_LATER_BEFORE_REDRAW, idle_handle_focus_change, self, NULL);
+}
+
+
+/**
+ * shell_window_tracker_get_startup_sequences:
+ * @self:
+ *
+ * Returns: (transfer none) (element-type ShellStartupSequence): Currently active startup sequences
+ */
+GSList *
+shell_window_tracker_get_startup_sequences (ShellWindowTracker *self)
+{
+  ShellGlobal *global = shell_global_get ();
+  MetaScreen *screen = shell_global_get_screen (global);
+  return meta_screen_get_startup_sequences (screen);
+}
+
+/* sn_startup_sequence_ref returns void, so make a
+ * wrapper which returns self */
+static SnStartupSequence *
+sequence_ref (SnStartupSequence *sequence)
+{
+  sn_startup_sequence_ref (sequence);
+  return sequence;
+}
+
+GType
+shell_startup_sequence_get_type (void)
+{
+  static GType gtype = G_TYPE_INVALID;
+  if (gtype == G_TYPE_INVALID)
+    {
+      gtype = g_boxed_type_register_static ("ShellStartupSequence",
+          (GBoxedCopyFunc)sequence_ref,
+          (GBoxedFreeFunc)sn_startup_sequence_unref);
+    }
+  return gtype;
+}
+
+const char *
+shell_startup_sequence_get_id (ShellStartupSequence *sequence)
+{
+  return sn_startup_sequence_get_id ((SnStartupSequence*)sequence);
+}
+
+const char *
+shell_startup_sequence_get_name (ShellStartupSequence *sequence)
+{
+  return sn_startup_sequence_get_name ((SnStartupSequence*)sequence);
+}
+
+gboolean
+shell_startup_sequence_get_completed (ShellStartupSequence *sequence)
+{
+  return sn_startup_sequence_get_completed ((SnStartupSequence*)sequence);
+}
+
+/**
+ * shell_startup_sequence_create_icon:
+ * @sequence:
+ * @size: Size in pixels of icon
+ *
+ * Returns: (transfer none): A new #ClutterTexture containing an icon for the sequence
+ */
+ClutterActor *
+shell_startup_sequence_create_icon (ShellStartupSequence *sequence, guint size)
+{
+  GIcon *themed;
+  const char *icon_name;
+  ClutterActor *texture;
+
+  icon_name = sn_startup_sequence_get_icon_name ((SnStartupSequence*)sequence);
+  if (!icon_name)
+    {
+      texture = clutter_texture_new ();
+      clutter_actor_set_size (texture, size, size);
+      return texture;
+    }
+
+  themed = g_themed_icon_new (icon_name);
+  texture = shell_texture_cache_load_gicon (shell_texture_cache_get_default (),
+                                            themed, size);
+  g_object_unref (G_OBJECT (themed));
+  return texture;
+}
+
+
+/**
+ * shell_window_tracker_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellWindowTracker instance
+ */
+ShellWindowTracker *
+shell_window_tracker_get_default ()
+{
+  static ShellWindowTracker *instance;
+
+  if (instance == NULL)
+    instance = g_object_new (SHELL_TYPE_WINDOW_TRACKER, NULL);
+
+  return instance;
+}
diff --git a/src/shell-window-tracker.h b/src/shell-window-tracker.h
new file mode 100644
index 0000000..a61439e
--- /dev/null
+++ b/src/shell-window-tracker.h
@@ -0,0 +1,57 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_WINDOW_TRACKER_H__
+#define __SHELL_WINDOW_TRACKER_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include "window.h"
+#include "shell-app.h"
+#include "shell-app-system.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellWindowTracker ShellWindowTracker;
+typedef struct _ShellWindowTrackerClass ShellWindowTrackerClass;
+typedef struct _ShellWindowTrackerPrivate ShellWindowTrackerPrivate;
+
+#define SHELL_TYPE_WINDOW_TRACKER              (shell_window_tracker_get_type ())
+#define SHELL_WINDOW_TRACKER(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_WINDOW_TRACKER, ShellWindowTracker))
+#define SHELL_WINDOW_TRACKER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_WINDOW_TRACKER, ShellWindowTrackerClass))
+#define SHELL_IS_WINDOW_TRACKER(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_WINDOW_TRACKER))
+#define SHELL_IS_WINDOW_TRACKER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_WINDOW_TRACKER))
+#define SHELL_WINDOW_TRACKER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_WINDOW_TRACKER, ShellWindowTrackerClass))
+
+struct _ShellWindowTrackerClass
+{
+  GObjectClass parent_class;
+};
+
+GType shell_window_tracker_get_type (void) G_GNUC_CONST;
+
+ShellWindowTracker* shell_window_tracker_get_default(void);
+
+GSList * shell_window_tracker_get_running_apps (ShellWindowTracker *monitor,
+                                                const char         *context);
+
+ShellApp *shell_window_tracker_get_window_app (ShellWindowTracker *monitor, MetaWindow *metawin);
+
+gboolean shell_window_tracker_is_window_interesting (MetaWindow *window);
+
+const char *_shell_window_tracker_get_app_context (ShellWindowTracker *tracker, ShellApp *app);
+
+GSList *shell_window_tracker_get_startup_sequences (ShellWindowTracker *tracker);
+
+/* Hidden typedef for SnStartupSequence */
+typedef struct _ShellStartupSequence ShellStartupSequence;
+#define SHELL_TYPE_STARTUP_SEQUENCE (shell_startup_sequence_get_type ())
+GType shell_startup_sequence_get_type (void);
+
+const char *shell_startup_sequence_get_id (ShellStartupSequence *sequence);
+const char *shell_startup_sequence_get_name (ShellStartupSequence *sequence);
+gboolean shell_startup_sequence_get_completed (ShellStartupSequence *sequence);
+ClutterActor *shell_startup_sequence_create_icon (ShellStartupSequence *sequence, guint size);
+
+G_END_DECLS
+
+#endif /* __SHELL_WINDOW_TRACKER_H__ */



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