[gnome-shell] Bug 588343 - Major rework of window monitoring to be application-based



commit 2726fdb831345c51a632e72e56c0ac20b03d172e
Author: Colin Walters <walters verbum org>
Date:   Thu Jul 30 15:40:26 2009 -0400

    Bug 588343 - Major rework of window monitoring to be application-based
    
    The previous application monitoring code was originally designed
    to be based on WM_CLASS, which was then resolved on a server.
    We have that resolution code locally now, so instead
    of saving WM_CLASS data, save application IDs.
    
    Also, inside the WM we have a much better
    infrastructure for tracking windows.  In particular, rather
    than polling, we can just watch for focus notification on
    the display, and window add/remove.
    
    Instead of polling XScreensaver, use DBus to watch org.gnome.Session
    which already has an idle time watch.
    
    Now there is no polling at all inside the monitor.

 js/ui/appDisplay.js     |    9 +-
 src/shell-app-monitor.c | 1275 ++++++++++++++++++++++++++---------------------
 src/shell-app-monitor.h |    9 +-
 3 files changed, 708 insertions(+), 585 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 7025e4e..9a4c2a7 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -277,7 +277,8 @@ AppDisplay.prototype = {
     },
 
     _getMostUsed: function() {
-        return this._appMonitor.get_most_used_apps(0, 30).map(Lang.bind(this, function (id) {
+        let context = "";
+        return this._appMonitor.get_most_used_apps(context, 30).map(Lang.bind(this, function (id) {
             return this._appSystem.lookup_app(id + '.desktop');
         })).filter(function (e) { return e != null });
     },
@@ -723,6 +724,9 @@ AppWell.prototype = {
     },
 
     _redisplay: function() {
+        /* hardcode here pending some design about how exactly activities behave */
+        let contextId = "";
+
         let arrayToObject = function(a) {
             let o = {};
             for (let i = 0; i < a.length; i++)
@@ -731,7 +735,8 @@ AppWell.prototype = {
         };
         let favoriteIds = this._appSystem.get_favorites();
         let favoriteIdsObject = arrayToObject(favoriteIds);
-        let runningIds = this._appMonitor.get_running_app_ids().filter(function (e) {
+
+        let runningIds = this._appMonitor.get_running_app_ids(contextId).filter(function (e) {
             return !(e in favoriteIdsObject);
         });
         let favorites = this._lookupApps(favoriteIds);
diff --git a/src/shell-app-monitor.c b/src/shell-app-monitor.c
index 379f1b4..5710704 100644
--- a/src/shell-app-monitor.c
+++ b/src/shell-app-monitor.c
@@ -10,7 +10,7 @@
 #include <gio/gio.h>
 #include <gconf/gconf.h>
 #include <gconf/gconf-client.h>
-
+#include <dbus/dbus-glib.h>
 
 #include "shell-app-monitor.h"
 #include "shell-app-system.h"
@@ -26,20 +26,39 @@
  * Copyright Red Hat, Inc. 2006-2008
  */
 
+/**
+ * SECTION:shell-app-monitor
+ * @short_description: Associate windows with application data and track usage/state data
+ *
+ * The application monitor has two primary purposes.  First, it
+ * 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.
+ *
+ * Second, the monitor also maintains some usage and state statistics for
+ * windows by keeping track of the approximate time an application's
+ * windows are focus, as well as the last workspace it was seen on.
+ * This time tracking is implemented by watching for restack notifications,
+ * and computing a time delta between them.  Also we monitor 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"
 
-/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */
-#define DATA_FILENAME "applications_usage"
+#define FOCUS_TIME_MIN_SECONDS 7 /* Need 7 continuous seconds of focus */
 
-/* How often we save internally app data, in seconds */
-#define SAVE_APPS_TIMEOUT 3600    /* One hour */
+#define USAGE_CLEAN_DAYS 7 /* If after 7 days we haven't seen an app, purge it */
+
+#define SNAPSHOTS_PER_CLEAN_SECONDS ((USAGE_CLEAN_DAYS * 24 * 60 * 60) / MIN_SNAPSHOT_APP_SECONDS) /* One week */
 
-/* How often we save internally app data in burst mode */
-#define SAVE_APPS_BURST_TIMEOUT 120 /* Two minutes */
+/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */
+#define DATA_FILENAME "application_state"
 
-/* Length of the initial app burst, for each new activity */
-#define SAVE_APPS_BURST_LENGTH 3600       /* One hour */
+#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
@@ -48,14 +67,17 @@
  * 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.
  */
- 
-/* With this value, an app goes from bottom to top of the 
- * popularity list in 50 hours of use */
-#define SCORE_MAX (3600*50/SAVE_APPS_TIMEOUT)
+
+/* 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 5
+#define SCORE_MIN (SCORE_MAX >> 3)
 
 /* Title patterns to detect apps that don't set WM class as needed.
  * Format: pseudo/wanted WM class, title regex pattern, NULL (for GRegex) */
@@ -76,52 +98,47 @@ static struct
 };
 
 
-typedef struct AppPopularity AppPopularity;
+typedef struct AppUsage AppUsage;
 typedef struct ActiveAppsData ActiveAppsData;
 
 struct _ShellAppMonitor
 {
   GObject parent;
 
-
   GFile *configfile;
-  XScreenSaverInfo *info;
+  DBusGProxy *session_proxy;
   GdkDisplay *display;
   GConfClient *gconf_client;
-  glong activity_time;
   gulong last_idle;
-  guint poll_id;
-  guint save_apps_id;
+  guint idle_focus_change_id;
+  guint save_id;
   guint gconf_notify;
   gboolean currently_idle;
   gboolean enable_monitoring;
 
-  /* <char * appid, guint window_count> */
-  GHashTable *running_appids;
+  GSList *previously_running;
+
+  long watch_start_time;
+  MetaWindow *watched_window;
 
   /* <MetaWindow * window, ShellAppInfo *app> */
   GHashTable *window_to_app;
 
-  GHashTable *apps_by_wm_class; /* Seen apps by wm_class */
-  GHashTable *popularities; /* One AppPopularity struct list per activity */
-  int upload_apps_burst_count;
+  /* <char *context, GHashTable<char *appid, AppUsage *usage>> */
+  GHashTable *app_usages_for_context;
 };
 
 G_DEFINE_TYPE (ShellAppMonitor, shell_app_monitor, G_TYPE_OBJECT);
 
-/* Represents an application record for a given activity */
-struct AppPopularity
+/* Represents an application record for a given context */
+struct AppUsage
 {
-  gchar *wm_class;
   gdouble score; /* Based on the number of times we'e seen the app and normalized */
-  guint32 last_seen; /* Used to clear old apps we've only seen a few times */
-};
+  long last_seen; /* Used to clear old apps we've only seen a few times */
 
-struct ActiveAppsData
-{
-  int activity;
-  GSList *result;
-  GTime start_time;
+  /* how many windows are currently open; in terms of persistence we only save
+   * whether the app had any windows or not. */
+  guint window_count;
 };
 
 enum {
@@ -134,21 +151,11 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 static void shell_app_monitor_finalize (GObject *object);
 
-static void get_active_apps (ShellAppMonitor *monitor,
-                             int              in_last_seconds,
-                             GSList         **wm_classes,
-                             int             *activity);
-
-static void save_active_apps (ShellAppMonitor *monitor,
-                              int              collection_period,
-                              GSList          *wm_classes,
-                              int              activity);
+static void on_session_status_changed (DBusGProxy *proxy, guint status, ShellAppMonitor *monitor);
+static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellAppMonitor *monitor);
+static void ensure_queued_save (ShellAppMonitor *monitor);
 
-static gboolean poll_for_idleness (void *data);
-
-static gboolean on_save_apps_timeout (gpointer data);
-
-static void save_to_file (ShellAppMonitor *monitor);
+static gboolean idle_save_application_usage (gpointer data);
 
 static void restore_from_file (ShellAppMonitor *monitor);
 
@@ -159,7 +166,7 @@ static void on_enable_monitoring_key_changed (GConfClient *client,
                                               GConfEntry  *entry,
                                               gpointer     monitor);
 
-static glong
+static long
 get_time (void)
 {
   GTimeVal tv;
@@ -172,7 +179,7 @@ static void shell_app_monitor_class_init(ShellAppMonitorClass *klass)
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
   gobject_class->finalize = shell_app_monitor_finalize;
-  
+
   signals[CHANGED] = g_signal_new ("changed",
                                    SHELL_TYPE_APP_MONITOR,
                                    G_SIGNAL_RUN_LAST,
@@ -182,22 +189,10 @@ static void shell_app_monitor_class_init(ShellAppMonitorClass *klass)
                                    G_TYPE_NONE, 0);
 }
 
-/* Little callback to destroy lists inside hash table */
 static void
-destroy_popularity (gpointer key,
-                    gpointer value,
-                    gpointer user_data)
+destroy_usage (AppUsage *usage)
 {
-  GSList *list = value;
-  GSList *l;
-  AppPopularity *app_popularity;
-  for (l = list; l; l = l->next)
-    {
-      app_popularity = (AppPopularity *) l->data;
-      g_free (app_popularity->wm_class);
-      g_free (app_popularity);
-    }
-  g_slist_free (list);
+  g_free (usage);
 }
 
 static char *
@@ -261,7 +256,8 @@ get_cleaned_wmclass_for_window (MetaWindow  *window)
 
   cleaned_wmclass = g_utf8_strdown (wmclass, -1);
   g_free (wmclass);
-  /* This handles "Fedora Eclipse", probably others */
+  /* This handles "Fedora Eclipse", probably others.
+   * Note g_strdelimit is modify-in-place. */
   g_strdelimit (cleaned_wmclass, " ", '-');
   wmclass = g_strdup (cleaned_wmclass);
   g_free (cleaned_wmclass);
@@ -297,28 +293,213 @@ get_app_for_window (MetaWindow     *window)
   return result;
 }
 
+static const char *
+get_window_context (MetaWindow *window)
+{
+  return "";
+}
+
+static GHashTable *
+get_usages_for_context (ShellAppMonitor *monitor,
+                        const char      *context)
+{
+  GHashTable *context_usages;
+
+  context_usages = g_hash_table_lookup (monitor->app_usages_for_context, context);
+  if (context_usages == NULL)
+    {
+      context_usages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)destroy_usage);
+      g_hash_table_insert (monitor->app_usages_for_context, g_strdup (context),
+                           context_usages);
+    }
+  return context_usages;
+}
+
+static AppUsage *
+get_app_usage_for_context_and_id (ShellAppMonitor *monitor,
+                                   const char      *context,
+                                   const char      *appid)
+{
+  AppUsage *usage;
+  GHashTable *context_usages;
+
+  context_usages = get_usages_for_context (monitor, context);
+
+  usage = g_hash_table_lookup (context_usages, appid);
+  if (usage)
+    return usage;
+
+  usage = g_new0 (AppUsage, 1);
+  g_hash_table_insert (context_usages, g_strdup (appid), usage);
+
+  return usage;
+}
+
+static AppUsage *
+get_app_usage_from_window (ShellAppMonitor *monitor,
+                           MetaWindow      *window)
+{
+  ShellAppInfo *app;
+  const char *context;
+
+  app = g_hash_table_lookup (monitor->window_to_app, window);
+  if (!app)
+    return NULL;
+
+  context = get_window_context (window);
+
+  return get_app_usage_for_context_and_id (monitor, context, shell_app_info_get_id (app));
+}
+
+static MetaWindow *
+get_active_window (ShellAppMonitor *monitor)
+{
+  MetaScreen *screen;
+  MetaDisplay *display;
+  screen = shell_global_get_screen (shell_global_get ());
+  display = meta_screen_get_display (screen);
+  return meta_display_get_focus_window (display);
+}
+
+typedef struct {
+  gboolean in_context;
+  GHashTableIter context_iter;
+  const char *context_id;
+  GHashTableIter usage_iter;
+} UsageIterator;
+
+static void
+usage_iterator_init (ShellAppMonitor *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 (ShellAppMonitor *self,
+                     UsageIterator   *iter,
+                     const char     **context,
+                     const char     **id,
+                     AppUsage       **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 (ShellAppMonitor *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 (ShellAppMonitor *self)
+{
+  UsageIterator iter;
+  const char *context;
+  const char *id;
+  AppUsage *usage;
+
+  usage_iterator_init (self, &iter);
+
+  while (usage_iterator_next (self, &iter, &context, &id, &usage))
+    {
+      usage->score /= 2;
+    }
+}
+
+static void
+increment_usage_for_window_at_time (ShellAppMonitor *self,
+                                    MetaWindow      *window,
+                                    long             time)
+{
+  AppUsage *usage;
+  guint elapsed;
+  guint usage_count;
+
+  usage = get_app_usage_from_window (self, window);
+  if (usage == NULL)
+    {
+      /* This could in theory happen if we lost the app tracking, i.e.
+       * the window changed WM_CLASS.  In that case, time for a punt.
+       */
+      return;
+    }
+
+  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_window (ShellAppMonitor *self,
+                            MetaWindow      *window)
+{
+  long curtime = get_time ();
+  increment_usage_for_window_at_time (self, window, curtime);
+}
+
 static void
 track_window (ShellAppMonitor *self,
               MetaWindow      *window)
 {
   ShellAppInfo *app;
-  guint window_count;
-  const char *appid;
+  AppUsage *usage;
 
   app = get_app_for_window (window);
   if (!app)
     return;
 
-  /* Steal the ref */
   g_hash_table_insert (self->window_to_app, window, app);
 
-  appid = shell_app_info_get_id (app);
-
-  window_count = GPOINTER_TO_UINT (g_hash_table_lookup (self->running_appids, appid));
+  usage = get_app_usage_from_window (self, window);
 
-  window_count += 1;
-  g_hash_table_insert (self->running_appids, g_strdup (appid), GUINT_TO_POINTER (window_count));
-  if (window_count == 1)
+  /* Ephemerally keep track of the number of windows open for this app,
+   * when it switches between 0 and 1 we emit a changed signal.
+   */
+  usage->window_count++;
+  usage->last_seen = get_time ();
+  if (usage->window_count == 1)
     g_signal_emit (self, signals[CHANGED], 0);
 }
 
@@ -339,27 +520,21 @@ shell_app_monitor_on_window_removed (MetaWorkspace   *workspace,
 {
   ShellAppMonitor *self = SHELL_APP_MONITOR (user_data);
   ShellAppInfo *app;
-  const char *appid;
-  guint window_count;
+  AppUsage *usage;
 
   app = g_hash_table_lookup (self->window_to_app, window);
   if (!app)
     return;
 
-  appid = shell_app_info_get_id (app);
-  window_count = GPOINTER_TO_UINT (g_hash_table_lookup (self->running_appids, appid));
+  usage = get_app_usage_from_window (self, window);
+
+  if (window == self->watched_window)
+    self->watched_window = NULL;
+
+  usage->window_count--;
+  if (usage->window_count == 0)
+    g_signal_emit (self, signals[CHANGED], 0);
 
-  window_count -= 1;
-  if (window_count == 0)
-    {
-      g_hash_table_remove (self->running_appids, appid);
-      g_signal_emit (self, signals[CHANGED], 0);
-    }
-  else
-    {
-      g_hash_table_insert (self->running_appids, g_strdup (appid),
-                           GUINT_TO_POINTER (window_count));
-    }
   g_hash_table_remove (self->window_to_app, window);
 }
 
@@ -377,12 +552,52 @@ load_initial_windows (ShellAppMonitor *monitor)
       GList *window_iter;
 
       for (window_iter = windows; window_iter; window_iter = window_iter->next)
-        track_window (monitor, (MetaWindow*)window_iter->data);
+        {
+          MetaWindow *window = window_iter->data;
+          track_window (monitor, window);
+        }
 
       g_list_free (windows);
     }
 }
 
+static void
+on_session_status_changed (DBusGProxy      *proxy,
+                           guint            status,
+                           ShellAppMonitor *monitor)
+{
+  gboolean idle;
+
+  idle = (status >= 3);
+  if (monitor->currently_idle == idle)
+    return;
+
+  monitor->currently_idle = idle;
+  if (idle)
+    {
+      long end_time;
+
+      /* Resync the active window, it may have changed while
+       * we were idle and ignoring focus changes.
+       */
+      monitor->watched_window = get_active_window (monitor);
+
+      /* 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 (monitor->watched_window)
+        {
+          end_time = monitor->watch_start_time + IDLE_TIME_TRANSITION_SECONDS;
+          increment_usage_for_window_at_time (monitor, monitor->watched_window, end_time);
+        }
+    }
+  else
+    {
+      /* Transitioning to !idle, reset the start time */
+      monitor->watch_start_time = get_time ();
+    }
+}
+
 /**
  * shell_app_monitor_get_windows_for_app:
  * @self:
@@ -449,10 +664,14 @@ shell_app_monitor_on_n_workspaces_changed (MetaScreen    *screen,
 static void
 init_window_monitoring (ShellAppMonitor *self)
 {
+  MetaDisplay *display;
   MetaScreen *screen = shell_global_get_screen (shell_global_get ());
 
   g_signal_connect (screen, "notify::n-workspaces",
                     G_CALLBACK (shell_app_monitor_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_app_monitor_on_n_workspaces_changed (screen, NULL, self);
 }
@@ -460,50 +679,44 @@ init_window_monitoring (ShellAppMonitor *self)
 static void
 shell_app_monitor_init (ShellAppMonitor *self)
 {
-  int event_base, error_base;
   GdkDisplay *display;
-  Display *xdisplay;
   char *path;
   char *shell_config_dir;
+  DBusGConnection *session_bus;
 
   /* FIXME: should we create as many monitors as there are GdkScreens? */
-  display = gdk_display_get_default();
-  xdisplay = GDK_DISPLAY_XDISPLAY (display);
-  if (!XScreenSaverQueryExtension (xdisplay, &event_base, &error_base))
-    {
-      g_warning ("Screensaver extension not found on X display, can't detect user idleness");
-    }
-  
+  display = gdk_display_get_default ();
+
+  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->display = g_object_ref (display);
-  self->info = XScreenSaverAllocInfo ();
 
-  self->activity_time = get_time ();
   self->last_idle = 0;
   self->currently_idle = FALSE;
   self->enable_monitoring = FALSE;
 
-  /* No need for free functions: value is an int stored as a pointer, and keys are
-   * freed manually in finalize () since we replace elements and reuse app names */
-  self->popularities = g_hash_table_new (g_direct_hash, g_direct_equal);
-  self->apps_by_wm_class = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                  (GDestroyNotify) g_free,
-                                                  (GDestroyNotify) g_free);
-
-  self->running_appids = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                g_free, NULL);
+  self->app_usages_for_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+                                                         (GDestroyNotify) g_hash_table_destroy);
 
   self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
                                                NULL, (GDestroyNotify) shell_app_info_unref);
 
-  load_initial_windows (self);
-  init_window_monitoring (self);
-
   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);
   restore_from_file (self);
 
+  load_initial_windows (self);
+  init_window_monitoring (self);
+
   self->gconf_client = gconf_client_get_default ();
   gconf_client_add_dir (self->gconf_client, APP_MONITOR_GCONF_DIR,
                         GCONF_CLIENT_PRELOAD_NONE, NULL);
@@ -519,15 +732,12 @@ shell_app_monitor_finalize (GObject *object)
   ShellAppMonitor *self = SHELL_APP_MONITOR (object);
   int i;
 
-  XFree (self->info);
-  g_source_remove (self->poll_id);
-  g_source_remove (self->save_apps_id);
+  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->display);
-  g_hash_table_destroy (self->apps_by_wm_class);
-  g_hash_table_foreach (self->popularities, destroy_popularity, NULL);
-  g_hash_table_destroy (self->popularities);
+  g_hash_table_destroy (self->app_usages_for_context);
   for (i = 0; title_patterns[i].app_id; i++)
     g_regex_unref (title_patterns[i].regex);
   g_object_unref (self->configfile);
@@ -537,41 +747,27 @@ shell_app_monitor_finalize (GObject *object)
 
 /**
  * shell_app_monitor_get_most_used_apps:
- *
- * Get a list of desktop identifiers representing the most popular applications
- * for a given activity.
- *
  * @monitor: the app monitor instance to request
- * @activity: the activity for which stats are considered
+ * @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.
  *
- * Returns: (element-type utf8) (transfer full): List of application desktop
- *     identifiers, in low case
+ * Get a list of desktop identifiers representing the most popular applications
+ * for a given context.
+ *
+ * Returns: (element-type utf8) (transfer container): List of application desktop
+ *     identifiers
  */
-GSList *
+GList *
 shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor,
-                                      gint             activity,
+                                      const char      *context,
                                       gint             max_count)
 {
-  GSList *list = NULL;
-  GSList *popularity;
-  AppPopularity *app_popularity;
-  int i;
-
-  popularity = g_hash_table_lookup (monitor->popularities,
-                                    GINT_TO_POINTER (activity));
-
-  for (i = 0; i < max_count; i++)
-    {
-      if (!popularity)
-        break;
-      app_popularity = (AppPopularity *) (popularity->data);
-      list = g_slist_prepend (list, g_utf8_strdown (app_popularity->wm_class, -1));
-      popularity = popularity->next;
-    }
-  list = g_slist_reverse (list);
-  return list;
+  GHashTable *usages;
+  usages = g_hash_table_lookup (monitor->app_usages_for_context, context);
+  if (usages == NULL)
+    return NULL;
+  return g_hash_table_get_keys (usages);
 }
 
 /**
@@ -582,7 +778,8 @@ shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor,
  * Returns: Desktop file id associated with window
  */
 ShellAppInfo *
-shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin)
+shell_app_monitor_get_window_app (ShellAppMonitor *monitor,
+                                  MetaWindow      *metawin)
 {
   ShellAppInfo *info = g_hash_table_lookup (monitor->window_to_app, metawin);
   if (info)
@@ -592,450 +789,424 @@ shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin)
 
 /**
  * shell_app_monitor_get_running_app_ids:
- *
  * @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.
  *
  * Returns: (element-type utf8) (transfer container): List of application desktop
  *     identifiers
  */
-GList *
-shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor)
+GSList *
+shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor,
+                                       const char      *context)
 {
-  return g_hash_table_get_keys (monitor->running_appids);
-}
+  UsageIterator iter;
+  const char *cur_context;
+  const char *id;
+  AppUsage *usage;
+  GSList *ret;
 
-void
-update_app_info (ShellAppMonitor *monitor)
-{
-  char *wm_class;
-  ShellGlobal *global;
-  GHashTable *app_active_times = NULL; /* last active time for an application */
-  MetaScreen *screen;
-  MetaDisplay *display;
-  MetaWindow *active;
-  int activity;
-  guint32 timestamp;
+  usage_iterator_init (monitor, &iter);
 
-  global = shell_global_get ();
-  g_object_get (global, "screen", &screen, NULL);
-  display = meta_screen_get_display (screen);
-  g_object_unref (screen);
-
-  active = meta_display_get_focus_window (display);
-
-  if (active == NULL)
-    return;
-
-  wm_class = get_cleaned_wmclass_for_window (active);
-
-  if (!wm_class)
-    return;
-
-  app_active_times = g_hash_table_lookup (monitor->apps_by_wm_class, wm_class);
-  if (!app_active_times)
+  ret = NULL;
+  while (usage_iterator_next (monitor, &iter, &cur_context, &id, &usage))
     {
-      /* Create a hash table to save times per activity
-       * Value and key are int stored as pointers, no need to free them */
-      app_active_times = g_hash_table_new (g_direct_hash, g_direct_equal);
-      g_hash_table_replace (monitor->apps_by_wm_class, g_strdup (wm_class),
-                            app_active_times);
-    }
+      if (strcmp (cur_context, context) != 0)
+        continue;
 
-  timestamp = get_time ();
-  activity = 0;
-  g_hash_table_replace (app_active_times, GINT_TO_POINTER (activity),
-                        GINT_TO_POINTER (timestamp));
+      if (usage->window_count > 0)
+        ret = g_slist_prepend (ret, (char*)id);
+    }
 
-  g_free (wm_class);
+  return ret;
 }
 
 static gboolean
-poll_for_idleness (gpointer data)
+idle_handle_focus_change (gpointer data)
 {
   ShellAppMonitor *monitor = data;
-  int i;
-  int n_screens;
-  unsigned long idle_time;
-  gboolean was_idle;
-
-  idle_time = G_MAXINT;
-  n_screens = gdk_display_get_n_screens (monitor->display);
-  for (i = 0; i < n_screens; ++i)
-    {
-      int result = 0;
-      GdkScreen *screen;
-
-      screen = gdk_display_get_screen (monitor->display, i);
-      result = XScreenSaverQueryInfo (GDK_DISPLAY_XDISPLAY (monitor->display),
-                                      GDK_SCREEN_XSCREEN (screen)->root,
-                                      monitor->info);
-      if (result == 0)
-        {
-          g_warning ("Failed to get idle time from screensaver extension");
-          break;
-        }
-
-      /* monitor->info->idle is time in milliseconds since last user interaction event */
-      idle_time = MIN (monitor->info->idle, idle_time);
-    }
-
-  was_idle = monitor->currently_idle;
-
-  /* If the idle time has gone down, there must have been activity since we last checked */
-  if (idle_time < monitor->last_idle)
-    {
-      monitor->activity_time = get_time ();
-      monitor->currently_idle = FALSE;
-    }
-  else
-    {
-      /* If no activity, see how long ago it was and count ourselves idle 
-       * if it's been a short while. We keep this idle really short,
-       * so it can be "more aggressive" about idle detection than
-       * a screensaver would be.
-       */
-      GTime now = get_time ();
-      if (now < monitor->activity_time)
-        {
-          /* clock went backward... just "catch up" 
-           * then wait until the idle timeout expires again
-           */
-          monitor->activity_time = now;
-        }
-      else if ((now - monitor->activity_time) > 120)
-        {                       /* 120 = 2 minutes */
-          monitor->currently_idle = TRUE;
-        }
-    }
+  long curtime = get_time ();
 
-  monitor->last_idle = idle_time;
+  increment_usage_for_window (monitor, monitor->watched_window);
 
-  if (!monitor->currently_idle)
-    {
-      update_app_info (monitor);
-    }
+  monitor->watched_window = get_active_window (monitor);
+  monitor->watch_start_time = curtime;
 
-  return TRUE;
+  monitor->idle_focus_change_id = 0;
+  return FALSE;
 }
 
-/* Used to iterate over apps to create a list of those that have been active
- * since the activity specified by app_data has been started */
 static void
-active_apps_foreach (const gpointer key,
-                     const gpointer value,
-                     gpointer       data)
+on_focus_window_changed (MetaDisplay     *display,
+                         GParamSpec      *spec,
+                         ShellAppMonitor *self)
 {
-  char *name = key;
-  GHashTable *app_active_times = value; /* GTime spent per activity */
-  ActiveAppsData *app_data = data;
-  GTime active_time;
-  
-  /* Only return apps that have been used in the current activity */
-  active_time = GPOINTER_TO_INT (g_hash_table_lookup
-                                 (app_active_times, GINT_TO_POINTER (app_data->activity)));
-  if (active_time > app_data->start_time)
-    app_data->result = g_slist_prepend (app_data->result, g_strdup (name));
-}
-
-/*
- * Returns list of application names we've seen the user interacting with
- * within the last 'in_last_seconds' seconds and in the specified activity.
- * Free the names in the GSList with g_free(), the lists themselves with
- * g_slist_free().
- */
-static void
-get_active_apps (ShellAppMonitor *monitor,
-                 int              in_last_seconds,
-                 GSList         **wm_classes,
-                 int             *activity)
-{
-  ActiveAppsData app_data;
-  guint32 now;
+  if (!self->enable_monitoring || self->currently_idle)
+    return;
 
-  now = get_time ();
-  app_data.activity = 0;
-  *activity = app_data.activity; /* Be sure we use the exact same timestamp everywhere */
-  app_data.start_time = now - in_last_seconds;
+  if (self->idle_focus_change_id != 0)
+    return;
 
-  if (wm_classes && g_hash_table_size (monitor->apps_by_wm_class))
-    {
-      app_data.result = NULL;
-      g_hash_table_foreach (monitor->apps_by_wm_class, active_apps_foreach,
-                            &app_data);
-      *wm_classes = app_data.result;
-    }
+  /* Defensively compress notifications here in case something is going berserk,
+   * we'll at least use a bit less system resources. */
+  self->idle_focus_change_id = g_timeout_add (250, idle_handle_focus_change, self);
 }
 
-static gboolean
-on_save_apps_timeout (gpointer data)
+static void
+ensure_queued_save (ShellAppMonitor *monitor)
 {
-  ShellAppMonitor *monitor = (ShellAppMonitor *) data;
-  static guint32 period = SAVE_APPS_TIMEOUT;
-
-  if (monitor->upload_apps_burst_count >= 0)
-    {
-      period = SAVE_APPS_BURST_TIMEOUT;
-      monitor->upload_apps_burst_count--;
-
-      if (monitor->upload_apps_burst_count == 0)
-        {
-          g_source_remove (monitor->save_apps_id);
-          g_timeout_add_seconds (SAVE_APPS_TIMEOUT, on_save_apps_timeout, monitor);
-        }
-    }
-
-  GSList *wm_classes = NULL;
-  int activity;
-
-  get_active_apps (monitor, period, &wm_classes, &activity);
-
-  if (wm_classes)
-    {
-      save_active_apps (monitor, period, wm_classes, activity);
-      save_to_file (monitor);
-
-      if (wm_classes)
-        {
-          g_slist_foreach (wm_classes, (GFunc) g_free, NULL);
-          g_slist_free (wm_classes);
-        }
-    }
-
-  return TRUE;
-}
-
-/* Used to find an app item from its wm_class, when non empty */
-static gint
-popularity_find_app (gconstpointer list_data,
-                     gconstpointer user_data)
-{ 
-  AppPopularity *list_pop = (AppPopularity *) list_data;
-  AppPopularity *user_pop = (AppPopularity *) user_data;
-  return strcmp (list_pop->wm_class, user_pop->wm_class);
+  if (monitor->save_id != 0)
+    return;
+  monitor->save_id = g_timeout_add_seconds (SAVE_APPS_TIMEOUT_SECONDS, idle_save_application_usage, monitor);
 }
 
 /* Used to sort highest scores at the top */
-static gint popularity_sort_apps (gconstpointer data1,
-                                  gconstpointer data2)
+static gint
+usage_sort_apps (gconstpointer data1,
+                 gconstpointer data2)
 {
-  const AppPopularity *pop1 = data1;
-  const AppPopularity *pop2 = data2;
-  
-  if (pop1->score > pop2->score)
+  const AppUsage *u1 = data1;
+  const AppUsage *u2 = data2;
+
+  if (u1->score > u2->score)
     return -1;
-  else if (pop1->score == pop2->score)
+  else if (u1->score == u2->score)
     return 0;
   else
     return 1;
 }
 
-/* Limit the score to a certain level so that most popular apps can change */
-static void
-normalize_popularity (GSList *list)
-{
-  if (!list)
-    return;
-  
-  AppPopularity *app_popularity;
-
-  /* Highest score since list is sorted */
-  app_popularity = (AppPopularity *) (list->data);
-  /* Limiting score allows new apps to catch up (see SCORE_MAX definition) */
-  if (app_popularity->score > SCORE_MAX)
-    while (list)
-      {
-        app_popularity = (AppPopularity *) (list->data);
-        app_popularity->score /= 2;
-        list = list->next;
-      }
-}
-  
 /* 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 GSList *
-clean_popularity (GSList *list)
+static gboolean
+idle_clean_usage (ShellAppMonitor *monitor)
 {
-  AppPopularity *app_popularity;
+  UsageIterator iter;
+  const char *context;
+  const char *id;
+  AppUsage *usage;
   GDate *date;
   guint32 date_days;
-  GSList *next, *head;
 
+  /* Subtract a week */
   date = g_date_new ();
   g_date_set_time_t (date, time (NULL));
-  g_date_subtract_days (date, 7);
+  g_date_subtract_days (date, USAGE_CLEAN_DAYS);
   date_days = g_date_get_julian (date);
-  head = list;
-  while (list)
-  {
-    next = list->next;
-    app_popularity = (AppPopularity *) (list->data);
-    if ((app_popularity->score < SCORE_MIN) &&
-          (app_popularity->last_seen < date_days))
-      head = g_slist_remove (head, list);
-    list = next;
-  }
+
+  usage_iterator_init (monitor, &iter);
+
+  while (usage_iterator_next (monitor, &iter, &context, &id, &usage))
+    {
+      if ((usage->score < SCORE_MIN) &&
+          (usage->last_seen < date_days))
+        usage_iterator_remove (monitor, &iter);
+    }
   g_date_free (date);
-  return head;
+
+  return FALSE;
 }
 
-/* Save apps data internally to lists, merging if necessary */
-static void
-save_active_apps (ShellAppMonitor *monitor,
-                  int              collection_period,
-                  GSList          *wm_classes,
-                  int              activity)
+static gboolean
+write_escaped (GDataOutputStream   *stream,
+               const char          *str,
+               GError             **error)
 {
-  AppPopularity *app_popularity;
-  AppPopularity temp; /* We only set/use two fields here */
-  GDate *date;
-  guint32 date_days;
-  GSList *popularity;
-  GSList *item;
-  GSList *l;
-  
-  popularity = g_hash_table_lookup (monitor->popularities,
-                                    GINT_TO_POINTER (activity));
-  date = g_date_new ();
-  g_date_set_time_t (date, time (NULL));
-  date_days = g_date_get_julian (date);
-  if (!popularity) /* Just create the list using provided information */
-    {
-      for (l = wm_classes; l; l = l->next)
-        {
-          app_popularity = g_new (AppPopularity, 1);
-          app_popularity->last_seen = date_days;
-          app_popularity->score = 1;
-          /* Copy data from the old list */
-          app_popularity->wm_class = g_strdup ((gchar *) l->data);
-          popularity = g_slist_prepend (popularity, app_popularity);
-        }
-    }
-  else /* Merge with old data */
-    {
-      for (l = wm_classes; l; l = l->next)
-        {
-          temp.wm_class = (gchar *) l->data;
-          
-          item = g_slist_find_custom (popularity, &temp, popularity_find_app);
-          if (!item)
-            {
-              app_popularity = g_new (AppPopularity, 1);
-              app_popularity->score = 1;
-              /* Copy data from other lists */
-              app_popularity->wm_class = g_strdup ((gchar *) l->data);
-              popularity = g_slist_prepend (popularity, app_popularity);
-            }
-          else
-            {
-              app_popularity = (AppPopularity *) item->data;
-              app_popularity->score++;
-            }
-          app_popularity->last_seen = date_days;
-        }
-    }
+  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;
+}
 
-    /* Clean once in a while, doing so at start may no be enough if uptime is high */
-    popularity = clean_popularity (popularity);
-    /* Need to do this often since SCORE_MAX should be relatively low */
-    normalize_popularity (popularity);
-    popularity = g_slist_sort (popularity, popularity_sort_apps);
-    g_hash_table_replace (monitor->popularities, GINT_TO_POINTER (activity),
-                          popularity);
-    g_date_free (date);
+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);
 
-    g_signal_emit (monitor, signals[CHANGED], 0);
+  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 void
-save_to_file (ShellAppMonitor *monitor)
+static gboolean
+idle_save_application_usage (gpointer data)
 {
-  GHashTableIter iter;
-  gpointer key;
-  gpointer value;
-  int activity;
-  GSList *popularity;
-  AppPopularity *app_popularity;
+  ShellAppMonitor *monitor = SHELL_APP_MONITOR (data);
+  UsageIterator iter;
+  const char *current_context;
+  const char *context;
+  const char *id;
+  AppUsage *usage;
   GFileOutputStream *output;
+  GOutputStream *buffered_output;
   GDataOutputStream *data_output;
   GError *error = NULL;
-  gchar *line;
-  gchar score_buf[G_ASCII_DTOSTR_BUF_SIZE];
-  static int last_error_code = 0;
+
+
+  monitor->save_id = 0;
 
   /* Parent directory is already created by shell-global */
   output = g_file_replace (monitor->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
   if (!output)
     {
-      if (last_error_code == error->code)
-        {
-          g_error_free (error);
-          return;
-        }
-      last_error_code = error->code;
-      g_warning ("Could not save applications usage data: %s. This warning will be printed only once.", error->message);
+      g_debug ("Could not save applications usage data: %s", error->message);
       g_error_free (error);
-      return;
+      return FALSE;
     }
-  data_output = g_data_output_stream_new (G_OUTPUT_STREAM(output));
+  buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM(output));
   g_object_unref (output);
-    
-  g_hash_table_iter_init (&iter, monitor->popularities);
-  while (g_hash_table_iter_next (&iter, &key, &value) && value)
-    { 
-      activity = GPOINTER_TO_INT (key);
-      popularity = value;
-
-      line = g_strdup_printf ("%i\n", activity);
-      g_data_output_stream_put_string (data_output, "--\n", NULL, NULL);
-      g_data_output_stream_put_string (data_output, line, NULL, NULL);
-      g_free (line);
-
-      do
+  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 (monitor, &iter);
+
+  current_context = NULL;
+  while (usage_iterator_next (monitor, &iter, &context, &id, &usage))
+    {
+      if (context != current_context)
         {
-          app_popularity = (AppPopularity *) popularity->data;
-          g_ascii_dtostr (score_buf, sizeof (score_buf), app_popularity->score);
-          line = g_strdup_printf ("%s,%s,%u\n", app_popularity->wm_class,
-                                  score_buf, app_popularity->last_seen);
-          g_data_output_stream_put_string (data_output, line, NULL, &error);
-          g_free (line);
-          if (error)
+          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;
         }
-      while ( (popularity = popularity->next) );
+      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", usage->window_count > 0, &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:
-  g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error);
+out:
+  if (!error)
+    g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error);
   g_object_unref (data_output);
   if (error)
     {
-      if (last_error_code == error->code)
+      g_debug ("Could not save applications usage data: %s", error->message);
+      g_error_free (error);
+    }
+  return FALSE;
+}
+
+typedef struct {
+  ShellAppMonitor *monitor;
+  char *context;
+} ParseData;
+
+static void
+shell_app_monitor_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_error_free (error);
+          g_set_error (error,
+                       G_MARKUP_ERROR,
+                       G_MARKUP_ERROR_PARSE,
+                       "Missing attribute id on <%s> element",
+                       element_name);
           return;
         }
-      last_error_code = error->code;
-      g_warning ("Could not save applications usage data: %s. This warning will be printed only once.", error->message);
-      g_error_free (error);
+      data->context = context;
+    }
+  else if (strcmp (element_name, "application") == 0)
+    {
+      const char **attribute;
+      const char **value;
+      AppUsage *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->monitor, data->context);
+
+      usage = g_new0 (AppUsage, 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->monitor->previously_running = g_slist_prepend (data->monitor->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_monitor_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_monitor_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_monitor_start_element_handler,
+  shell_app_monitor_end_element_handler,
+  shell_app_monitor_text_handler,
+  NULL,
+  NULL
+};
+
 /* Load data about apps usage from file */
 static void
 restore_from_file (ShellAppMonitor *monitor)
 {
-  int activity = -1; /* Means invalid ID */
-  GSList *popularity = NULL;
-  AppPopularity *app_popularity;
   GFileInputStream *input;
-  GDataInputStream *data_input;
+  ParseData parse_data;
+  GMarkupParseContext *parse_context;
   GError *error = NULL;
-  gchar *line;
-  gchar **info;
-  
+  char buf[1024];
+
   input = g_file_read (monitor->configfile, NULL, &error);
   if (error)
     {
@@ -1046,75 +1217,34 @@ restore_from_file (ShellAppMonitor *monitor)
       return;
     }
 
-  data_input = g_data_input_stream_new (G_INPUT_STREAM(input));
-  g_object_unref (input);
+  memset (&parse_data, 0, sizeof (ParseData));
+  parse_data.monitor = monitor;
+  parse_data.context = NULL;
+  parse_context = g_markup_parse_context_new (&app_state_parse_funcs, 0, &parse_data, NULL);
 
   while (TRUE)
     {
-      line = g_data_input_stream_read_line (data_input, NULL, NULL, &error);
-      if (!line)
+      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;
-      if (strcmp (line, "--") == 0) /* Line starts a new activity */
-        {
-          g_free (line);
-          line = g_data_input_stream_read_line (data_input, NULL, NULL, &error);
-          if (line && (strcmp (line, "") != 0))
-            {
-              if (activity != -1) /* Save previous activity, cleaning and sorting it */
-                {
-                  popularity = clean_popularity (popularity);
-                  popularity = g_slist_sort (popularity, popularity_sort_apps);
-                  g_hash_table_replace (monitor->popularities,
-                                        GINT_TO_POINTER (activity), popularity);
-                  popularity = NULL;
-                }
-              activity = atoi (line);
-              /* FIXME: do something if conversion fails! */
-              /* like: errno = NULL; ... if (errno) { g_free (line); goto out; } */
-            }
-          else
-            {
-              g_free (line);
-              goto out;
-            }
-        }
-      /* Line is about an app.
-       * If no activity was provided yet, just skip */
-      else if ((activity != -1) && (strcmp (line, "") != 0))
-        {
-          info = g_strsplit (line, ",", 0);
-          if (info[0] && info [1] && info[2]) /* Skip on wrong syntax */
-            {
-              app_popularity = g_new (AppPopularity, 1);
-              app_popularity->wm_class = g_strdup(info[0]);
-              app_popularity->score = g_ascii_strtod (info[1], NULL);
-              app_popularity->last_seen = (guint32) strtoul (info[2], NULL, 10);
-              popularity = g_slist_prepend (popularity, app_popularity);
-            }
-
-          g_strfreev (info);
-          g_free (line);
-        }
-      else
-        g_free (line); /* Just skip to next app */
      }
 
 out:
-  if (activity != -1) /* Save last activity, cleaning and sorting it */
+  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 (monitor);
+  monitor->previously_running = g_slist_sort (monitor->previously_running, usage_sort_apps);
+
+  if (error)
     {
-      popularity = clean_popularity (popularity);
-      popularity = g_slist_sort  (popularity, popularity_sort_apps);
-      g_hash_table_replace (monitor->popularities, GINT_TO_POINTER (activity),
-                            popularity);
+      g_warning ("Could not load applications usage data: %s", error->message);
+      g_error_free (error);
     }
-
-    g_input_stream_close (G_INPUT_STREAM (data_input), NULL, NULL);
-    g_object_unref (data_input);
-    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
@@ -1135,29 +1265,18 @@ update_enable_monitoring (ShellAppMonitor *monitor)
     }
   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 && !monitor->enable_monitoring)
     {
-      /* If no stats are available so far, set burst mode on */
-      if (g_hash_table_size (monitor->popularities))
-        monitor->upload_apps_burst_count = 0;
-      else
-        monitor->upload_apps_burst_count = SAVE_APPS_BURST_LENGTH / SAVE_APPS_BURST_TIMEOUT;
-
-      monitor->poll_id = g_timeout_add_seconds (5, poll_for_idleness, monitor);
-      if (monitor->upload_apps_burst_count > 0)
-        monitor->save_apps_id =
-          g_timeout_add_seconds (SAVE_APPS_BURST_TIMEOUT, on_save_apps_timeout, monitor);
-      else
-        monitor->save_apps_id =
-          g_timeout_add_seconds (SAVE_APPS_TIMEOUT, on_save_apps_timeout, monitor);
+      on_focus_window_changed (NULL, NULL, monitor);
     }
   /* ...and don't try to stop them if they were not running */
   else if (!enable && monitor->enable_monitoring)
     {
-      g_source_remove (monitor->poll_id);
-      g_source_remove (monitor->save_apps_id);
+      monitor->watched_window = NULL;
+      if (monitor->save_id)
+        g_source_remove (monitor->save_id);
     }
 
   monitor->enable_monitoring = enable;
diff --git a/src/shell-app-monitor.h b/src/shell-app-monitor.h
index 1470455..41f7129 100644
--- a/src/shell-app-monitor.h
+++ b/src/shell-app-monitor.h
@@ -40,15 +40,14 @@ ShellAppMonitor* shell_app_monitor_get_default(void);
 
 ShellAppInfo *shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin);
 
-/* Get the most popular applications for a given activity */
-GSList *shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor,
-                                              int              activity,
-                                              gint             number);
+GList *shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor,
+                                             const char      *context,
+                                             gint             number);
 
 GSList *shell_app_monitor_get_windows_for_app (ShellAppMonitor *monitor, const char *appid);
 
 /* Get whatever's running right now */
-GList *shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor);
+GSList *shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor, const char *context);
 
 G_END_DECLS
 



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