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 }