gnome-shell-3.6.3.1/src/shell-app-usage.c

No issues found

   1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
   2 
   3 #include "config.h"
   4 
   5 #include <string.h>
   6 #include <stdlib.h>
   7 
   8 #include <X11/Xlib.h>
   9 #include <X11/Xatom.h>
  10 #include <gdk/gdk.h>
  11 #include <gdk/gdkx.h>
  12 #include <glib.h>
  13 #include <gio/gio.h>
  14 #include <meta/display.h>
  15 #include <meta/group.h>
  16 #include <meta/window.h>
  17 
  18 #include "shell-app-usage.h"
  19 #include "shell-window-tracker.h"
  20 #include "shell-global.h"
  21 
  22 /* This file includes modified code from
  23  * desktop-data-engine/engine-dbus/hippo-application-monitor.c
  24  * in the functions collecting application usage data.
  25  * Written by Owen Taylor, originally licensed under LGPL 2.1.
  26  * Copyright Red Hat, Inc. 2006-2008
  27  */
  28 
  29 /**
  30  * SECTION:shell-app-usage
  31  * @short_description: Track application usage/state data
  32  *
  33  * This class maintains some usage and state statistics for
  34  * applications by keeping track of the approximate time an application's
  35  * windows are focused, as well as the last workspace it was seen on.
  36  * This time tracking is implemented by watching for focus notifications,
  37  * and computing a time delta between them.  Also we watch the
  38  * GNOME Session "StatusChanged" signal which by default is emitted after 5
  39  * minutes to signify idle.
  40  */
  41 
  42 #define ENABLE_MONITORING_KEY "enable-app-monitoring"
  43 
  44 #define FOCUS_TIME_MIN_SECONDS 7 /* Need 7 continuous seconds of focus */
  45 
  46 #define USAGE_CLEAN_DAYS 7 /* If after 7 days we haven't seen an app, purge it */
  47 
  48 /* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */
  49 #define DATA_FILENAME "application_state"
  50 
  51 #define IDLE_TIME_TRANSITION_SECONDS 30 /* If we transition to idle, only count
  52                                          * this many seconds of usage */
  53 
  54 /* The ranking algorithm we use is: every time an app score reaches SCORE_MAX,
  55  * divide all scores by 2. Scores are raised by 1 unit every SAVE_APPS_TIMEOUT
  56  * seconds. This mechanism allows the list to update relatively fast when
  57  * a new app is used intensively.
  58  * To keep the list clean, and avoid being Big Brother, apps that have not been
  59  * seen for a week and whose score is below SCORE_MIN are removed.
  60  */
  61 
  62 /* How often we save internally app data, in seconds */
  63 #define SAVE_APPS_TIMEOUT_SECONDS (5 * 60)
  64 
  65 /* With this value, an app goes from bottom to top of the
  66  * usage list in 50 hours of use */
  67 #define SCORE_MAX (3600 * 50 / FOCUS_TIME_MIN_SECONDS)
  68 
  69 /* If an app's score in lower than this and the app has not been used in a week,
  70  * remove it */
  71 #define SCORE_MIN (SCORE_MAX >> 3)
  72 
  73 /* http://www.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Presence */
  74 #define GNOME_SESSION_STATUS_IDLE 3
  75 
  76 typedef struct UsageData UsageData;
  77 
  78 struct _ShellAppUsage
  79 {
  80   GObject parent;
  81 
  82   GFile *configfile;
  83   GDBusProxy *session_proxy;
  84   GdkDisplay *display;
  85   gulong last_idle;
  86   guint idle_focus_change_id;
  87   guint save_id;
  88   guint settings_notify;
  89   gboolean currently_idle;
  90   gboolean enable_monitoring;
  91 
  92   GSList *previously_running;
  93 
  94   long watch_start_time;
  95   ShellApp *watched_app;
  96 
  97   /* <char *context, GHashTable<char *appid, UsageData *usage>> */
  98   GHashTable *app_usages_for_context;
  99 };
 100 
 101 G_DEFINE_TYPE (ShellAppUsage, shell_app_usage, G_TYPE_OBJECT);
 102 
 103 /* Represents an application record for a given context */
 104 struct UsageData
 105 {
 106   /* Whether the application we're tracking is "transient", see
 107    * shell_app_is_window_backed.
 108    */
 109   gboolean transient;
 110 
 111   gdouble score; /* Based on the number of times we'e seen the app and normalized */
 112   long last_seen; /* Used to clear old apps we've only seen a few times */
 113 };
 114 
 115 static void shell_app_usage_finalize (GObject *object);
 116 
 117 static void on_session_status_changed (GDBusProxy *proxy, guint status, ShellAppUsage *self);
 118 static void on_focus_app_changed (ShellWindowTracker *tracker, GParamSpec *spec, ShellAppUsage *self);
 119 static void ensure_queued_save (ShellAppUsage *self);
 120 static UsageData * get_app_usage_for_context_and_id (ShellAppUsage  *self,
 121                                                     const char     *context,
 122                                                     const char     *appid);
 123 
 124 static gboolean idle_save_application_usage (gpointer data);
 125 
 126 static void restore_from_file (ShellAppUsage *self);
 127 
 128 static void update_enable_monitoring (ShellAppUsage *self);
 129 
 130 static void on_enable_monitoring_key_changed (GSettings     *settings,
 131                                               const gchar   *key,
 132                                               ShellAppUsage *self);
 133 
 134 static long
 135 get_time (void)
 136 {
 137   GTimeVal tv;
 138   g_get_current_time (&tv);
 139   return tv.tv_sec;
 140 }
 141 
 142 static void
 143 shell_app_usage_class_init (ShellAppUsageClass *klass)
 144 {
 145   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 146 
 147   gobject_class->finalize = shell_app_usage_finalize;
 148 }
 149 
 150 static GHashTable *
 151 get_usages_for_context (ShellAppUsage *self,
 152                         const char    *context)
 153 {
 154   GHashTable *context_usages;
 155 
 156   context_usages = g_hash_table_lookup (self->app_usages_for_context, context);
 157   if (context_usages == NULL)
 158     {
 159       context_usages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 160       g_hash_table_insert (self->app_usages_for_context, g_strdup (context),
 161                            context_usages);
 162     }
 163   return context_usages;
 164 }
 165 
 166 static UsageData *
 167 get_app_usage_for_context_and_id (ShellAppUsage *self,
 168                                   const char    *context,
 169                                   const char    *appid)
 170 {
 171   UsageData *usage;
 172   GHashTable *context_usages;
 173 
 174   context_usages = get_usages_for_context (self, context);
 175 
 176   usage = g_hash_table_lookup (context_usages, appid);
 177   if (usage)
 178     return usage;
 179 
 180   usage = g_new0 (UsageData, 1);
 181   g_hash_table_insert (context_usages, g_strdup (appid), usage);
 182 
 183   return usage;
 184 }
 185 
 186 static UsageData *
 187 get_usage_for_app (ShellAppUsage *self,
 188                    ShellApp      *app)
 189 {
 190   const char *context;
 191 
 192   context = _shell_window_tracker_get_app_context (shell_window_tracker_get_default (), app);
 193 
 194   return get_app_usage_for_context_and_id (self, context, shell_app_get_id (app));
 195 }
 196 
 197 typedef struct {
 198   gboolean in_context;
 199   GHashTableIter context_iter;
 200   const char *context_id;
 201   GHashTableIter usage_iter;
 202 } UsageIterator;
 203 
 204 static void
 205 usage_iterator_init (ShellAppUsage *self,
 206                      UsageIterator *iter)
 207 {
 208   iter->in_context = FALSE;
 209   g_hash_table_iter_init (&(iter->context_iter), self->app_usages_for_context);
 210 }
 211 
 212 static gboolean
 213 usage_iterator_next (ShellAppUsage   *self,
 214                      UsageIterator   *iter,
 215                      const char     **context,
 216                      const char     **id,
 217                      UsageData       **usage)
 218 {
 219   gpointer key, value;
 220   gboolean next_context;
 221 
 222   if (!iter->in_context)
 223     next_context = TRUE;
 224   else if (!g_hash_table_iter_next (&(iter->usage_iter), &key, &value))
 225     next_context = TRUE;
 226   else
 227     next_context = FALSE;
 228 
 229   while (next_context)
 230     {
 231       GHashTable *app_usages;
 232 
 233       if (!g_hash_table_iter_next (&(iter->context_iter), &key, &value))
 234         return FALSE;
 235       iter->in_context = TRUE;
 236       iter->context_id = key;
 237       app_usages = value;
 238       g_hash_table_iter_init (&(iter->usage_iter), app_usages);
 239 
 240       next_context = !g_hash_table_iter_next (&(iter->usage_iter), &key, &value);
 241     }
 242 
 243   *context = iter->context_id;
 244   *id = key;
 245   *usage = value;
 246 
 247   return TRUE;
 248 }
 249 
 250 static void
 251 usage_iterator_remove (ShellAppUsage *self,
 252                        UsageIterator *iter)
 253 {
 254   g_assert (iter->in_context);
 255 
 256   g_hash_table_iter_remove (&(iter->usage_iter));
 257 }
 258 
 259 /* Limit the score to a certain level so that most used apps can change */
 260 static void
 261 normalize_usage (ShellAppUsage *self)
 262 {
 263   UsageIterator iter;
 264   const char *context;
 265   const char *id;
 266   UsageData *usage;
 267 
 268   usage_iterator_init (self, &iter);
 269 
 270   while (usage_iterator_next (self, &iter, &context, &id, &usage))
 271     {
 272       usage->score /= 2;
 273     }
 274 }
 275 
 276 static void
 277 increment_usage_for_app_at_time (ShellAppUsage *self,
 278                                  ShellApp      *app,
 279                                  long           time)
 280 {
 281   UsageData *usage;
 282   guint elapsed;
 283   guint usage_count;
 284 
 285   usage = get_usage_for_app (self, app);
 286 
 287   usage->last_seen = time;
 288 
 289   elapsed = time - self->watch_start_time;
 290   usage_count = elapsed / FOCUS_TIME_MIN_SECONDS;
 291   if (usage_count > 0)
 292     {
 293       usage->score += usage_count;
 294       if (usage->score > SCORE_MAX)
 295         normalize_usage (self);
 296       ensure_queued_save (self);
 297     }
 298 }
 299 
 300 static void
 301 increment_usage_for_app (ShellAppUsage *self,
 302                          ShellApp      *app)
 303 {
 304   long curtime = get_time ();
 305   increment_usage_for_app_at_time (self, app, curtime);
 306 }
 307 
 308 static void
 309 on_app_state_changed (ShellAppSystem *app_system,
 310                       ShellApp       *app,
 311                       gpointer        user_data)
 312 {
 313   ShellAppUsage *self = SHELL_APP_USAGE (user_data);
 314   UsageData *usage;
 315   gboolean running;
 316 
 317   if (shell_app_is_window_backed (app))
 318     return;
 319 
 320   usage = get_usage_for_app (self, app);
 321 
 322   running = shell_app_get_state (app) == SHELL_APP_STATE_RUNNING;
 323 
 324   if (running)
 325     usage->last_seen = get_time ();
 326 }
 327 
 328 static void
 329 on_focus_app_changed (ShellWindowTracker *tracker,
 330                       GParamSpec         *spec,
 331                       ShellAppUsage      *self)
 332 {
 333   if (self->watched_app != NULL)
 334     increment_usage_for_app (self, self->watched_app);
 335 
 336   if (self->watched_app)
 337     g_object_unref (self->watched_app);
 338 
 339   g_object_get (tracker, "focus-app", &(self->watched_app), NULL);
 340   self->watch_start_time = get_time ();
 341 }
 342 
 343 static void
 344 on_session_status_changed (GDBusProxy      *proxy,
 345                            guint            status,
 346                            ShellAppUsage *self)
 347 {
 348   gboolean idle;
 349 
 350   idle = (status >= GNOME_SESSION_STATUS_IDLE);
 351   if (self->currently_idle == idle)
 352     return;
 353 
 354   self->currently_idle = idle;
 355   if (idle)
 356     {
 357       long end_time;
 358 
 359       /* The GNOME Session signal we watch is 5 minutes, but that's a long
 360        * time for this purpose.  Instead, just add a base 30 seconds.
 361        */
 362       if (self->watched_app)
 363         {
 364           end_time = self->watch_start_time + IDLE_TIME_TRANSITION_SECONDS;
 365           increment_usage_for_app_at_time (self, self->watched_app, end_time);
 366         }
 367     }
 368   else
 369     {
 370       /* Transitioning to !idle, reset the start time */
 371       self->watch_start_time = get_time ();
 372     }
 373 }
 374 
 375 static void
 376 session_proxy_signal (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data)
 377 {
 378   if (g_str_equal (signal_name, "StatusChanged"))
 379     {
 380       guint status;
 381       g_variant_get (parameters, "(u)", &status);
 382       on_session_status_changed (proxy, status, SHELL_APP_USAGE (user_data));
 383     }
 384 }
 385 
 386 static void
 387 shell_app_usage_init (ShellAppUsage *self)
 388 {
 389   ShellGlobal *global;
 390   char *shell_userdata_dir, *path;
 391   GDBusConnection *session_bus;
 392   ShellWindowTracker *tracker;
 393   ShellAppSystem *app_system;
 394 
 395   global = shell_global_get ();
 396 
 397   self->app_usages_for_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy);
 398 
 399   tracker = shell_window_tracker_get_default ();
 400   g_signal_connect (tracker, "notify::focus-app", G_CALLBACK (on_focus_app_changed), self);
 401 
 402   app_system = shell_app_system_get_default ();
 403   g_signal_connect (app_system, "app-state-changed", G_CALLBACK (on_app_state_changed), self);
 404 
 405   session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
 406   self->session_proxy = g_dbus_proxy_new_sync (session_bus,
 407                                                G_DBUS_PROXY_FLAGS_NONE,
 408                                                NULL, /* interface info */
 409                                                "org.gnome.SessionManager",
 410                                                "/org/gnome/SessionManager/Presence",
 411                                                "org.gnome.SessionManager",
 412                                                NULL, /* cancellable */
 413                                                NULL /* error */);
 414   g_signal_connect (self->session_proxy, "g-signal", G_CALLBACK (session_proxy_signal), self);
 415   g_object_unref (session_bus);
 416 
 417   self->last_idle = 0;
 418   self->currently_idle = FALSE;
 419   self->enable_monitoring = FALSE;
 420 
 421   g_object_get (shell_global_get(), "userdatadir", &shell_userdata_dir, NULL),
 422   path = g_build_filename (shell_userdata_dir, DATA_FILENAME, NULL);
 423   g_free (shell_userdata_dir);
 424   self->configfile = g_file_new_for_path (path);
 425   g_free (path);
 426   restore_from_file (self);
 427 
 428 
 429   self->settings_notify = g_signal_connect (shell_global_get_settings (global),
 430                                             "changed::" ENABLE_MONITORING_KEY,
 431                                             G_CALLBACK (on_enable_monitoring_key_changed),
 432                                             self);
 433   update_enable_monitoring (self);
 434 }
 435 
 436 static void
 437 shell_app_usage_finalize (GObject *object)
 438 {
 439   ShellGlobal *global;
 440   ShellAppUsage *self = SHELL_APP_USAGE (object);
 441 
 442   if (self->save_id > 0)
 443     g_source_remove (self->save_id);
 444 
 445   global = shell_global_get ();
 446   g_signal_handler_disconnect (shell_global_get_settings (global),
 447                                self->settings_notify);
 448 
 449   g_object_unref (self->configfile);
 450 
 451   g_object_unref (self->session_proxy);
 452 
 453   G_OBJECT_CLASS (shell_app_usage_parent_class)->finalize(object);
 454 }
 455 
 456 typedef struct {
 457   ShellAppUsage *usage;
 458   GHashTable *context_usages;
 459 } SortAppsByUsageData;
 460 
 461 static int
 462 sort_apps_by_usage (gconstpointer a,
 463                     gconstpointer b,
 464                     gpointer      datap)
 465 {
 466   SortAppsByUsageData *data = datap;
 467   ShellApp *app_a, *app_b;
 468   UsageData *usage_a, *usage_b;
 469 
 470   app_a = (ShellApp*)a;
 471   app_b = (ShellApp*)b;
 472 
 473   usage_a = g_hash_table_lookup (data->context_usages, shell_app_get_id (app_a));
 474   usage_b = g_hash_table_lookup (data->context_usages, shell_app_get_id (app_b));
 475 
 476   return usage_b->score - usage_a->score;
 477 }
 478 
 479 /**
 480  * shell_app_usage_get_most_used:
 481  * @usage: the usage instance to request
 482  * @context: Activity identifier
 483  * @max_count: how many applications are requested. Note that the actual
 484  *     list size may be less, or NULL if not enough applications are registered.
 485  *
 486  * Get a list of most popular applications for a given context.
 487  *
 488  * Returns: (element-type ShellApp) (transfer full): List of applications
 489  */
 490 GSList *
 491 shell_app_usage_get_most_used (ShellAppUsage   *self,
 492                                const char      *context,
 493                                gint             max_count)
 494 {
 495   GSList *apps;
 496   GList *appids, *iter;
 497   GHashTable *usages;
 498   ShellAppSystem *appsys;
 499   SortAppsByUsageData data;
 500 
 501   usages = g_hash_table_lookup (self->app_usages_for_context, context);
 502   if (usages == NULL)
 503     return NULL;
 504 
 505   appsys = shell_app_system_get_default ();
 506 
 507   appids = g_hash_table_get_keys (usages);
 508   apps = NULL;
 509   for (iter = appids; iter; iter = iter->next)
 510     {
 511       const char *appid = iter->data;
 512       ShellApp *app;
 513 
 514       app = shell_app_system_lookup_app (appsys, appid);
 515       if (!app)
 516         continue;
 517 
 518       apps = g_slist_prepend (apps, g_object_ref (app));
 519     }
 520 
 521   g_list_free (appids);
 522 
 523   data.usage = self;
 524   data.context_usages = usages;
 525   apps = g_slist_sort_with_data (apps, sort_apps_by_usage, &data);
 526 
 527   return apps;
 528 }
 529 
 530 
 531 /**
 532  * shell_app_usage_compare:
 533  * @self: the usage instance to request
 534  * @context: Activity identifier
 535  * @app_a: First app
 536  * @app_b: Second app
 537  *
 538  * Compare @app_a and @app_b based on frequency of use.
 539  *
 540  * Returns: -1 if @app_a ranks higher than @app_b, 1 if @app_b ranks higher
 541  *          than @app_a, and 0 if both rank equally.
 542  */
 543 int
 544 shell_app_usage_compare (ShellAppUsage *self,
 545                          const char    *context,
 546                          ShellApp      *app_a,
 547                          ShellApp      *app_b)
 548 {
 549   GHashTable *usages;
 550   UsageData *usage_a, *usage_b;
 551 
 552   usages = g_hash_table_lookup (self->app_usages_for_context, context);
 553   if (usages == NULL)
 554     return 0;
 555 
 556   usage_a = g_hash_table_lookup (usages, shell_app_get_id (app_a));
 557   usage_b = g_hash_table_lookup (usages, shell_app_get_id (app_b));
 558 
 559   if (usage_a == NULL && usage_b == NULL)
 560     return 0;
 561   else if (usage_a == NULL)
 562     return 1;
 563   else if (usage_b == NULL)
 564     return -1;
 565 
 566   return usage_b->score - usage_a->score;
 567 }
 568 
 569 static void
 570 ensure_queued_save (ShellAppUsage *self)
 571 {
 572   if (self->save_id != 0)
 573     return;
 574   self->save_id = g_timeout_add_seconds (SAVE_APPS_TIMEOUT_SECONDS, idle_save_application_usage, self);
 575 }
 576 
 577 /* Clean up apps we see rarely.
 578  * The logic behind this is that if an app was seen less than SCORE_MIN times
 579  * and not seen for a week, it can probably be forgotten about.
 580  * This should much reduce the size of the list and avoid 'pollution'. */
 581 static gboolean
 582 idle_clean_usage (ShellAppUsage *self)
 583 {
 584   UsageIterator iter;
 585   const char *context;
 586   const char *id;
 587   UsageData *usage;
 588   long current_time;
 589   long week_ago;
 590 
 591   current_time = get_time ();
 592   week_ago = current_time - (7 * 24 * 60 * 60);
 593 
 594   usage_iterator_init (self, &iter);
 595 
 596   while (usage_iterator_next (self, &iter, &context, &id, &usage))
 597     {
 598       if ((usage->score < SCORE_MIN) &&
 599           (usage->last_seen < week_ago))
 600         usage_iterator_remove (self, &iter);
 601     }
 602 
 603   return FALSE;
 604 }
 605 
 606 static gboolean
 607 write_escaped (GDataOutputStream   *stream,
 608                const char          *str,
 609                GError             **error)
 610 {
 611   gboolean ret;
 612   char *quoted = g_markup_escape_text (str, -1);
 613   ret = g_data_output_stream_put_string (stream, quoted, NULL, error);
 614   g_free (quoted);
 615   return ret;
 616 }
 617 
 618 static gboolean
 619 write_attribute_string (GDataOutputStream *stream,
 620                         const char        *elt_name,
 621                         const char        *str,
 622                         GError           **error)
 623 {
 624   gboolean ret = FALSE;
 625   char *elt;
 626 
 627   elt = g_strdup_printf (" %s=\"", elt_name);
 628   ret = g_data_output_stream_put_string (stream, elt, NULL, error);
 629   g_free (elt);
 630   if (!ret)
 631     goto out;
 632 
 633   ret = write_escaped (stream, str, error);
 634   if (!ret)
 635     goto out;
 636 
 637   ret = g_data_output_stream_put_string (stream, "\"", NULL, error);
 638 
 639 out:
 640   return ret;
 641 }
 642 
 643 static gboolean
 644 write_attribute_uint (GDataOutputStream *stream,
 645                       const char        *elt_name,
 646                       guint              value,
 647                       GError           **error)
 648 {
 649   gboolean ret;
 650   char *buf;
 651 
 652   buf = g_strdup_printf ("%u", value);
 653   ret = write_attribute_string (stream, elt_name, buf, error);
 654   g_free (buf);
 655 
 656   return ret;
 657 }
 658 
 659 static gboolean
 660 write_attribute_double (GDataOutputStream *stream,
 661                         const char        *elt_name,
 662                         double             value,
 663                         GError           **error)
 664 {
 665   gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
 666   gboolean ret;
 667 
 668   g_ascii_dtostr (buf, sizeof (buf), value);
 669   ret = write_attribute_string (stream, elt_name, buf, error);
 670 
 671   return ret;
 672 }
 673 
 674 /* Save app data lists to file */
 675 static gboolean
 676 idle_save_application_usage (gpointer data)
 677 {
 678   ShellAppUsage *self = SHELL_APP_USAGE (data);
 679   UsageIterator iter;
 680   const char *current_context;
 681   const char *context;
 682   const char *id;
 683   UsageData *usage;
 684   GFileOutputStream *output;
 685   GOutputStream *buffered_output;
 686   GDataOutputStream *data_output;
 687   GError *error = NULL;
 688 
 689   self->save_id = 0;
 690 
 691   /* Parent directory is already created by shell-global */
 692   output = g_file_replace (self->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
 693   if (!output)
 694     {
 695       g_debug ("Could not save applications usage data: %s", error->message);
 696       g_error_free (error);
 697       return FALSE;
 698     }
 699   buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM (output));
 700   g_object_unref (output);
 701   data_output = g_data_output_stream_new (G_OUTPUT_STREAM (buffered_output));
 702   g_object_unref (buffered_output);
 703 
 704   if (!g_data_output_stream_put_string (data_output, "<?xml version=\"1.0\"?>\n<application-state>\n", NULL, &error))
 705     goto out;
 706 
 707   usage_iterator_init (self, &iter);
 708 
 709   current_context = NULL;
 710   while (usage_iterator_next (self, &iter, &context, &id, &usage))
 711     {
 712       ShellApp *app;
 713 
 714       app = shell_app_system_lookup_app (shell_app_system_get_default(), id);
 715 
 716       if (!app)
 717         continue;
 718 
 719       if (context != current_context)
 720         {
 721           if (current_context != NULL)
 722             {
 723               if (!g_data_output_stream_put_string (data_output, "  </context>", NULL, &error))
 724                 goto out;
 725             }
 726           current_context = context;
 727           if (!g_data_output_stream_put_string (data_output, "  <context", NULL, &error))
 728             goto out;
 729           if (!write_attribute_string (data_output, "id", context, &error))
 730             goto out;
 731           if (!g_data_output_stream_put_string (data_output, ">\n", NULL, &error))
 732             goto out;
 733         }
 734       if (!g_data_output_stream_put_string (data_output, "    <application", NULL, &error))
 735         goto out;
 736       if (!write_attribute_string (data_output, "id", id, &error))
 737         goto out;
 738       if (!write_attribute_uint (data_output, "open-window-count", shell_app_get_n_windows (app), &error))
 739         goto out;
 740 
 741       if (!write_attribute_double (data_output, "score", usage->score, &error))
 742         goto out;
 743       if (!write_attribute_uint (data_output, "last-seen", usage->last_seen, &error))
 744         goto out;
 745       if (!g_data_output_stream_put_string (data_output, "/>\n", NULL, &error))
 746         goto out;
 747     }
 748   if (current_context != NULL)
 749     {
 750       if (!g_data_output_stream_put_string (data_output, "  </context>\n", NULL, &error))
 751         goto out;
 752     }
 753   if (!g_data_output_stream_put_string (data_output, "</application-state>\n", NULL, &error))
 754     goto out;
 755 
 756 out:
 757   if (!error)
 758     g_output_stream_close_async (G_OUTPUT_STREAM (data_output), 0, NULL, NULL, NULL);
 759   g_object_unref (data_output);
 760   if (error)
 761     {
 762       g_debug ("Could not save applications usage data: %s", error->message);
 763       g_error_free (error);
 764     }
 765   return FALSE;
 766 }
 767 
 768 typedef struct {
 769   ShellAppUsage *self;
 770   char *context;
 771 } ParseData;
 772 
 773 static void
 774 shell_app_usage_start_element_handler  (GMarkupParseContext *context,
 775                                           const gchar         *element_name,
 776                                           const gchar        **attribute_names,
 777                                           const gchar        **attribute_values,
 778                                           gpointer             user_data,
 779                                           GError             **error)
 780 {
 781   ParseData *data = user_data;
 782 
 783   if (strcmp (element_name, "application-state") == 0)
 784     {
 785     }
 786   else if (strcmp (element_name, "context") == 0)
 787     {
 788       char *context = NULL;
 789       const char **attribute;
 790       const char **value;
 791 
 792       for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
 793         {
 794           if (strcmp (*attribute, "id") == 0)
 795             context = g_strdup (*value);
 796         }
 797       if (context < 0)
 798         {
 799           g_set_error (error,
 800                        G_MARKUP_ERROR,
 801                        G_MARKUP_ERROR_PARSE,
 802                        "Missing attribute id on <%s> element",
 803                        element_name);
 804           return;
 805         }
 806       data->context = context;
 807     }
 808   else if (strcmp (element_name, "application") == 0)
 809     {
 810       const char **attribute;
 811       const char **value;
 812       UsageData *usage;
 813       char *appid = NULL;
 814       GHashTable *usage_table;
 815 
 816       for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
 817         {
 818           if (strcmp (*attribute, "id") == 0)
 819             appid = g_strdup (*value);
 820         }
 821 
 822       if (!appid)
 823         {
 824           g_set_error (error,
 825                        G_MARKUP_ERROR,
 826                        G_MARKUP_ERROR_PARSE,
 827                        "Missing attribute id on <%s> element",
 828                        element_name);
 829           return;
 830         }
 831 
 832       usage_table = get_usages_for_context (data->self, data->context);
 833 
 834       usage = g_new0 (UsageData, 1);
 835       g_hash_table_insert (usage_table, appid, usage);
 836 
 837       for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++)
 838         {
 839           if (strcmp (*attribute, "open-window-count") == 0)
 840             {
 841               guint count = strtoul (*value, NULL, 10);
 842               if (count > 0)
 843                  data->self->previously_running = g_slist_prepend (data->self->previously_running,
 844                                                                    g_strdup (appid));
 845             }
 846           else if (strcmp (*attribute, "score") == 0)
 847             {
 848               usage->score = g_ascii_strtod (*value, NULL);
 849             }
 850           else if (strcmp (*attribute, "last-seen") == 0)
 851             {
 852               usage->last_seen = (guint) g_ascii_strtoull (*value, NULL, 10);
 853             }
 854         }
 855     }
 856   else
 857     {
 858       g_set_error (error,
 859                    G_MARKUP_ERROR,
 860                    G_MARKUP_ERROR_PARSE,
 861                    "Unknown element <%s>",
 862                    element_name);
 863     }
 864 }
 865 
 866 static void
 867 shell_app_usage_end_element_handler (GMarkupParseContext *context,
 868                                        const gchar         *element_name,
 869                                        gpointer             user_data,
 870                                        GError             **error)
 871 {
 872   ParseData *data = user_data;
 873 
 874   if (strcmp (element_name, "context") == 0)
 875     {
 876       g_free (data->context);
 877       data->context = NULL;
 878     }
 879 }
 880 
 881 static void
 882 shell_app_usage_text_handler (GMarkupParseContext *context,
 883                                 const gchar         *text,
 884                                 gsize                text_len,
 885                                 gpointer             user_data,
 886                                 GError             **error)
 887 {
 888   /* do nothing, very very fast */
 889 }
 890 
 891 static GMarkupParser app_state_parse_funcs =
 892 {
 893   shell_app_usage_start_element_handler,
 894   shell_app_usage_end_element_handler,
 895   shell_app_usage_text_handler,
 896   NULL,
 897   NULL
 898 };
 899 
 900 /* Load data about apps usage from file */
 901 static void
 902 restore_from_file (ShellAppUsage *self)
 903 {
 904   GFileInputStream *input;
 905   ParseData parse_data;
 906   GMarkupParseContext *parse_context;
 907   GError *error = NULL;
 908   char buf[1024];
 909 
 910   input = g_file_read (self->configfile, NULL, &error);
 911   if (error)
 912     {
 913       if (error->code != G_IO_ERROR_NOT_FOUND)
 914         g_warning ("Could not load applications usage data: %s", error->message);
 915 
 916       g_error_free (error);
 917       return;
 918     }
 919 
 920   memset (&parse_data, 0, sizeof (ParseData));
 921   parse_data.self = self;
 922   parse_data.context = NULL;
 923   parse_context = g_markup_parse_context_new (&app_state_parse_funcs, 0, &parse_data, NULL);
 924 
 925   while (TRUE)
 926     {
 927       gssize count = g_input_stream_read ((GInputStream*) input, buf, sizeof(buf), NULL, &error);
 928       if (count <= 0)
 929         goto out;
 930       if (!g_markup_parse_context_parse (parse_context, buf, count, &error))
 931         goto out;
 932      }
 933 
 934 out:
 935   g_free (parse_data.context);
 936   g_markup_parse_context_free (parse_context);
 937   g_input_stream_close ((GInputStream*)input, NULL, NULL);
 938   g_object_unref (input);
 939 
 940   idle_clean_usage (self);
 941 
 942   if (error)
 943     {
 944       g_warning ("Could not load applications usage data: %s", error->message);
 945       g_error_free (error);
 946     }
 947 }
 948 
 949 /* Enable or disable the timers, depending on the value of ENABLE_MONITORING_KEY
 950  * and taking care of the previous state.  If selfing is disabled, we still
 951  * report apps usage based on (possibly) saved data, but don't collect data.
 952  */
 953 static void
 954 update_enable_monitoring (ShellAppUsage *self)
 955 {
 956   ShellGlobal *global;
 957   gboolean enable;
 958 
 959   global = shell_global_get ();
 960   enable = g_settings_get_boolean (shell_global_get_settings (global),
 961                                    ENABLE_MONITORING_KEY);
 962 
 963   /* Be sure not to start the timers if they were already set */
 964   if (enable && !self->enable_monitoring)
 965     {
 966       on_focus_app_changed (shell_window_tracker_get_default (), NULL, self);
 967     }
 968   /* ...and don't try to stop them if they were not running */
 969   else if (!enable && self->enable_monitoring)
 970     {
 971       if (self->watched_app)
 972         g_object_unref (self->watched_app);
 973       self->watched_app = NULL;
 974       if (self->save_id)
 975         {
 976           g_source_remove (self->save_id);
 977           self->save_id = 0;
 978         }
 979     }
 980 
 981   self->enable_monitoring = enable;
 982 }
 983 
 984 /* Called when the ENABLE_MONITORING_KEY boolean has changed */
 985 static void
 986 on_enable_monitoring_key_changed (GSettings     *settings,
 987                                   const gchar   *key,
 988                                   ShellAppUsage *self)
 989 {
 990   update_enable_monitoring (self);
 991 }
 992 
 993 /**
 994  * shell_app_usage_get_default:
 995  *
 996  * Return Value: (transfer none): The global #ShellAppUsage instance
 997  */
 998 ShellAppUsage *
 999 shell_app_usage_get_default ()
1000 {
1001   static ShellAppUsage *instance;
1002 
1003   if (instance == NULL)
1004     instance = g_object_new (SHELL_TYPE_APP_USAGE, NULL);
1005 
1006   return instance;
1007 }