Merge lp:~indicator-applet-developers/indicator-application/karmic into lp:~ubuntu-desktop/indicator-application/ubuntu

Proposed by Ted Gould
Status: Merged
Merged at revision: not available
Proposed branch: lp:~indicator-applet-developers/indicator-application/karmic
Merge into: lp:~ubuntu-desktop/indicator-application/ubuntu
Diff against target: 1185 lines (+838/-31)
12 files modified
configure.ac (+6/-3)
debian/changelog (+13/-0)
debian/control (+2/-1)
src/Makefile.am (+2/-0)
src/application-service-appstore.c (+123/-20)
src/application-service-appstore.h (+2/-0)
src/application-service-lru-file.c (+473/-0)
src/application-service-lru-file.h (+59/-0)
src/application-service.c (+8/-1)
src/application-service.xml (+1/-1)
src/indicator-application.c (+145/-5)
src/libappindicator/app-indicator.c (+4/-0)
To merge this branch: bzr merge lp:~indicator-applet-developers/indicator-application/karmic
Reviewer Review Type Date Requested Status
Indicator Applet Developers Pending
Review via email: mp+17846@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ted Gould (ted) wrote :

0.0.10

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2010-01-14 16:36:02 +0000
3+++ configure.ac 2010-01-21 20:27:14 +0000
4@@ -1,11 +1,11 @@
5
6-AC_INIT(indicator-application, 0.0.9, ted@canonical.com)
7+AC_INIT(indicator-application, 0.0.10, ted@canonical.com)
8 AC_COPYRIGHT([Copyright 2009, 2010 Canonical])
9
10 AC_PREREQ(2.53)
11
12 AM_CONFIG_HEADER(config.h)
13-AM_INIT_AUTOMAKE(indicator-application, 0.0.9)
14+AM_INIT_AUTOMAKE(indicator-application, 0.0.10)
15
16 AM_MAINTAINER_MODE
17
18@@ -38,11 +38,13 @@
19 ###########################
20
21 GTK_REQUIRED_VERSION=2.12
22-INDICATOR_REQUIRED_VERSION=0.3.0
23+INDICATOR_REQUIRED_VERSION=0.3.1
24 DBUSMENUGTK_REQUIRED_VERSION=0.1.1
25+JSON_GLIB_REQUIRED_VERSION=0.7.6
26
27 PKG_CHECK_MODULES(INDICATOR, gtk+-2.0 >= $GTK_REQUIRED_VERSION
28 indicator >= $INDICATOR_REQUIRED_VERSION
29+ json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION
30 dbusmenu-gtk >= $DBUSMENUGTK_REQUIRED_VERSION)
31
32 AC_SUBST(INDICATOR_CFLAGS)
33@@ -51,6 +53,7 @@
34 ###########################
35 # Check for Mono support
36 ###########################
37+
38 MONO_REQUIRED_VERSION=1.0
39 PKG_CHECK_MODULES(MONO_DEPENDENCY, mono >= $MONO_REQUIRED_VERSION, has_mono=true, has_mono=false)
40
41
42=== modified file 'debian/changelog'
43--- debian/changelog 2010-01-19 21:57:32 +0000
44+++ debian/changelog 2010-01-21 20:27:14 +0000
45@@ -1,3 +1,16 @@
46+indicator-application (0.0.10-0ubuntu1~ppa1) karmic; urgency=low
47+
48+ * Upstream release 0.0.10
49+ * Adding in a file to position the indicators
50+ * Ref counting theme directories as they get added and removed
51+ * Use the right callback function for fallback (LP: #507975)
52+ * Support getting the app list from a running service.
53+ * debian/control: Adding dependency for json-glib to build.
54+ * debian/control: Adjusted back changes in 0.0.9-0ubuntu3 as this
55+ branch is still on Karmic. Changes are in Lucid.
56+
57+ -- Ted Gould <ted@ubuntu.com> Thu, 21 Jan 2010 14:21:00 -0600
58+
59 indicator-application (0.0.9-0ubuntu3) lucid; urgency=low
60
61 * debian/control:
62
63=== modified file 'debian/control'
64--- debian/control 2010-01-19 21:57:32 +0000
65+++ debian/control 2010-01-21 20:27:14 +0000
66@@ -8,6 +8,7 @@
67 python-central (>= 0.6),
68 libgtk2.0-dev (>= 2.12.0),
69 libdbus-glib-1-dev,
70+ libjson-glib-dev,
71 gnome-doc-utils,
72 gtk-doc-tools,
73 intltool,
74@@ -23,7 +24,7 @@
75 gtk-sharp2-gapi,
76 libmono-dev,
77 libnunit2.4-cil,
78- libgtk2.0-cil-dev
79+ libgtk2.0-cil
80 Standards-Version: 3.8.3
81 Homepage: https://launchpad.net/indicator-application
82 Vcs-Bzr: http://bazaar.launchpad.net/~ubuntu-desktop/indicator-application/ubuntu
83
84=== modified file 'src/Makefile.am'
85--- src/Makefile.am 2010-01-08 02:53:46 +0000
86+++ src/Makefile.am 2010-01-21 20:27:14 +0000
87@@ -35,6 +35,8 @@
88 application-service.c \
89 application-service-appstore.h \
90 application-service-appstore.c \
91+ application-service-lru-file.h \
92+ application-service-lru-file.c \
93 application-service-marshal.h \
94 application-service-marshal.c \
95 application-service-server.h \
96
97=== modified file 'src/application-service-appstore.c'
98--- src/application-service-appstore.c 2010-01-08 20:10:45 +0000
99+++ src/application-service-appstore.c 2010-01-21 20:27:14 +0000
100@@ -31,7 +31,7 @@
101 #include "dbus-shared.h"
102
103 /* DBus Prototypes */
104-static gboolean _application_service_server_get_applications (ApplicationServiceAppstore * appstore, GArray ** apps);
105+static gboolean _application_service_server_get_applications (ApplicationServiceAppstore * appstore, GPtrArray ** apps, GError ** error);
106
107 #include "application-service-server.h"
108
109@@ -52,6 +52,7 @@
110 struct _ApplicationServiceAppstorePrivate {
111 DBusGConnection * bus;
112 GList * applications;
113+ AppLruFile * lrufile;
114 };
115
116 #define APP_STATUS_PASSIVE_STR "passive"
117@@ -67,6 +68,8 @@
118
119 typedef struct _Application Application;
120 struct _Application {
121+ gchar * id;
122+ gchar * category;
123 gchar * dbus_name;
124 gchar * dbus_object;
125 ApplicationServiceAppstore * appstore; /* not ref'd */
126@@ -148,6 +151,7 @@
127 ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(self);
128
129 priv->applications = NULL;
130+ priv->lrufile = NULL;
131
132 GError * error = NULL;
133 priv->bus = dbus_g_bus_get(DBUS_BUS_STARTER, &error);
134@@ -201,6 +205,8 @@
135 Application * app = (Application *)data;
136
137 if (g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_MENU) == NULL ||
138+ g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ID) == NULL ||
139+ g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_CATEGORY) == NULL ||
140 g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_STATUS) == NULL ||
141 g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME) == NULL) {
142 g_warning("Notification Item on object %s of %s doesn't have enough properties.", app->dbus_object, app->dbus_name);
143@@ -210,6 +216,11 @@
144
145 app->validated = TRUE;
146
147+ app->id = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ID));
148+ app->category = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_CATEGORY));
149+ ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(app->appstore);
150+ app_lru_file_touch(priv->lrufile, app->id, app->category);
151+
152 app->icon = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME));
153 app->menu = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_MENU));
154 if (g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_AICON_NAME) != NULL) {
155@@ -273,6 +284,12 @@
156 g_object_unref(app->prop_proxy);
157 }
158
159+ if (app->id != NULL) {
160+ g_free(app->id);
161+ }
162+ if (app->category != NULL) {
163+ g_free(app->category);
164+ }
165 if (app->dbus_name != NULL) {
166 g_free(app->dbus_name);
167 }
168@@ -311,6 +328,40 @@
169 return;
170 }
171
172+static gboolean
173+can_add_application (GList *applications, Application *app)
174+{
175+ if (applications)
176+ {
177+ GList *l = NULL;
178+
179+ for (l = applications; l != NULL; l = g_list_next (l))
180+ {
181+ Application *tmp_app = (Application *)l->data;
182+
183+ if (g_strcmp0 (tmp_app->dbus_name, app->dbus_name) == 0 &&
184+ g_strcmp0 (tmp_app->dbus_object, app->dbus_object) == 0)
185+ {
186+ return FALSE;
187+ }
188+ }
189+ }
190+
191+ return TRUE;
192+}
193+
194+/* This function takes two Application structure
195+ pointers and uses the lrufile to compare them. */
196+static gint
197+app_sort_func (gconstpointer a, gconstpointer b, gpointer userdata)
198+{
199+ Application * appa = (Application *)a;
200+ Application * appb = (Application *)b;
201+ AppLruFile * lrufile = (AppLruFile *)userdata;
202+
203+ return app_lru_file_sort(lrufile, appa->id, appb->id);
204+}
205+
206 /* Change the status of the application. If we're going passive
207 it removes it from the panel. If we're coming online, then
208 it add it to the panel. Otherwise it changes the icon. */
209@@ -333,8 +384,7 @@
210 g_signal_emit(G_OBJECT(appstore),
211 signals[APPLICATION_REMOVED], 0,
212 position, TRUE);
213-
214- priv->applications = g_list_remove(priv->applications, app);
215+ priv->applications = g_list_remove(priv->applications, app);
216 } else {
217 /* Figure out which icon we should be using */
218 gchar * newicon = app->icon;
219@@ -344,21 +394,19 @@
220
221 /* Determine whether we're already shown or not */
222 if (app->status == APP_STATUS_PASSIVE) {
223- /* Put on panel */
224- priv->applications = g_list_prepend(priv->applications, app);
225-
226- /* TODO: We need to have the position determined better. This
227- would involve looking at the name and category and sorting
228- it with the other entries. */
229+ if (can_add_application (priv->applications, app)) {
230+ /* Put on panel */
231+ priv->applications = g_list_insert_sorted_with_data (priv->applications, app, app_sort_func, priv->lrufile);
232
233- g_signal_emit(G_OBJECT(app->appstore),
234- signals[APPLICATION_ADDED], 0,
235- newicon,
236- 0, /* Position */
237- app->dbus_name,
238- app->menu,
239- app->icon_path,
240- TRUE);
241+ g_signal_emit(G_OBJECT(app->appstore),
242+ signals[APPLICATION_ADDED], 0,
243+ newicon,
244+ g_list_index(priv->applications, app), /* Position */
245+ app->dbus_name,
246+ app->menu,
247+ app->icon_path,
248+ TRUE);
249+ }
250 } else {
251 /* Icon update */
252 gint position = get_position(app);
253@@ -622,11 +670,66 @@
254 return;
255 }
256
257+/* Creates a basic appstore object and attaches the
258+ LRU file object to it. */
259+ApplicationServiceAppstore *
260+application_service_appstore_new (AppLruFile * lrufile)
261+{
262+ g_return_val_if_fail(IS_APP_LRU_FILE(lrufile), NULL);
263+ ApplicationServiceAppstore * appstore = APPLICATION_SERVICE_APPSTORE(g_object_new(APPLICATION_SERVICE_APPSTORE_TYPE, NULL));
264+ ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore);
265+ priv->lrufile = lrufile;
266+ return appstore;
267+}
268+
269 /* DBus Interface */
270 static gboolean
271-_application_service_server_get_applications (ApplicationServiceAppstore * appstore, GArray ** apps)
272+_application_service_server_get_applications (ApplicationServiceAppstore * appstore, GPtrArray ** apps, GError ** error)
273 {
274-
275- return FALSE;
276+ ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore);
277+
278+ *apps = g_ptr_array_new();
279+ GList * listpntr;
280+ gint position = 0;
281+
282+ for (listpntr = priv->applications; listpntr != NULL; listpntr = g_list_next(listpntr)) {
283+ GValueArray * values = g_value_array_new(5);
284+
285+ GValue value = {0};
286+
287+ /* Icon name */
288+ g_value_init(&value, G_TYPE_STRING);
289+ g_value_set_string(&value, ((Application *)listpntr->data)->icon);
290+ g_value_array_append(values, &value);
291+ g_value_unset(&value);
292+
293+ /* Position */
294+ g_value_init(&value, G_TYPE_INT);
295+ g_value_set_int(&value, position++);
296+ g_value_array_append(values, &value);
297+ g_value_unset(&value);
298+
299+ /* DBus Address */
300+ g_value_init(&value, G_TYPE_STRING);
301+ g_value_set_string(&value, ((Application *)listpntr->data)->dbus_name);
302+ g_value_array_append(values, &value);
303+ g_value_unset(&value);
304+
305+ /* DBus Object */
306+ g_value_init(&value, DBUS_TYPE_G_OBJECT_PATH);
307+ g_value_set_static_boxed(&value, ((Application *)listpntr->data)->menu);
308+ g_value_array_append(values, &value);
309+ g_value_unset(&value);
310+
311+ /* Icon path */
312+ g_value_init(&value, G_TYPE_STRING);
313+ g_value_set_string(&value, ((Application *)listpntr->data)->icon_path);
314+ g_value_array_append(values, &value);
315+ g_value_unset(&value);
316+
317+ g_ptr_array_add(*apps, values);
318+ }
319+
320+ return TRUE;
321 }
322
323
324=== modified file 'src/application-service-appstore.h'
325--- src/application-service-appstore.h 2010-01-08 20:10:45 +0000
326+++ src/application-service-appstore.h 2010-01-21 20:27:14 +0000
327@@ -25,6 +25,7 @@
328
329 #include <glib.h>
330 #include <glib-object.h>
331+#include "application-service-lru-file.h"
332
333 G_BEGIN_DECLS
334
335@@ -50,6 +51,7 @@
336 GObject parent;
337 };
338
339+ApplicationServiceAppstore * application_service_appstore_new (AppLruFile * lrufile);
340 GType application_service_appstore_get_type (void);
341 void application_service_appstore_application_add (ApplicationServiceAppstore * appstore,
342 const gchar * dbus_name,
343
344=== added file 'src/application-service-lru-file.c'
345--- src/application-service-lru-file.c 1970-01-01 00:00:00 +0000
346+++ src/application-service-lru-file.c 2010-01-21 20:27:14 +0000
347@@ -0,0 +1,473 @@
348+/*
349+This object manages the database of when the entires were touched
350+and loved. And writes that out to disk occationally as well.
351+
352+Copyright 2010 Canonical Ltd.
353+
354+Authors:
355+ Ted Gould <ted@canonical.com>
356+
357+This program is free software: you can redistribute it and/or modify it
358+under the terms of the GNU General Public License version 3, as published
359+by the Free Software Foundation.
360+
361+This program is distributed in the hope that it will be useful, but
362+WITHOUT ANY WARRANTY; without even the implied warranties of
363+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
364+PURPOSE. See the GNU General Public License for more details.
365+
366+You should have received a copy of the GNU General Public License along
367+with this program. If not, see <http://www.gnu.org/licenses/>.
368+*/
369+
370+#ifdef HAVE_CONFIG_H
371+#include "config.h"
372+#endif
373+
374+#include <string.h>
375+#include <gio/gio.h>
376+#include <json-glib/json-glib.h>
377+#include "application-service-lru-file.h"
378+
379+#define ENTRY_CATEGORY "category"
380+#define ENTRY_FIRST_TIME "first-time"
381+#define ENTRY_LAST_TIME "last-time"
382+#define ENTRY_VERSION "version"
383+
384+#define CONFIG_DIR ("indicators" G_DIR_SEPARATOR_S "application")
385+#define CONFIG_FILE "lru-file.json"
386+
387+typedef struct _AppLruFilePrivate AppLruFilePrivate;
388+struct _AppLruFilePrivate {
389+ GHashTable * apps;
390+ gboolean dirty;
391+ guint timer;
392+ gchar * filename;
393+};
394+
395+typedef struct _AppData AppData;
396+struct _AppData {
397+ gchar * category;
398+ GTimeVal last_touched;
399+ GTimeVal first_touched;
400+};
401+
402+#define APP_LRU_FILE_GET_PRIVATE(o) \
403+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), APP_LRU_FILE_TYPE, AppLruFilePrivate))
404+
405+static void app_lru_file_class_init (AppLruFileClass *klass);
406+static void app_lru_file_init (AppLruFile *self);
407+static void app_lru_file_dispose (GObject *object);
408+static void app_lru_file_finalize (GObject *object);
409+static void app_data_free (gpointer data);
410+static void get_dirty (AppLruFile * lrufile);
411+static gboolean load_from_file (gpointer data);
412+static void load_file_object_cb (JsonObject * obj, const gchar * key, JsonNode * value, gpointer data);
413+static void clean_off_hash_cb (gpointer key, gpointer value, gpointer data);
414+static void clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data);
415+
416+G_DEFINE_TYPE (AppLruFile, app_lru_file, G_TYPE_OBJECT);
417+
418+/* Set up the class variable stuff */
419+static void
420+app_lru_file_class_init (AppLruFileClass *klass)
421+{
422+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
423+
424+ g_type_class_add_private (klass, sizeof (AppLruFilePrivate));
425+
426+ object_class->dispose = app_lru_file_dispose;
427+ object_class->finalize = app_lru_file_finalize;
428+
429+ return;
430+}
431+
432+/* Set all the data of the per-instance variables */
433+static void
434+app_lru_file_init (AppLruFile *self)
435+{
436+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(self);
437+
438+ /* Default values */
439+ priv->apps = NULL;
440+ priv->dirty = FALSE;
441+ priv->timer = 0;
442+ priv->filename = NULL;
443+
444+ /* Now let's build some stuff */
445+ priv->apps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, app_data_free);
446+ priv->filename = g_build_filename(g_get_user_config_dir(), CONFIG_DIR, CONFIG_FILE, NULL);
447+
448+ /* No reason to delay other stuff for this, we'll
449+ merge any values that get touched. */
450+ g_idle_add(load_from_file, self);
451+
452+ return;
453+}
454+
455+/* Get rid of objects and other big things */
456+static void
457+app_lru_file_dispose (GObject *object)
458+{
459+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object);
460+
461+ if (priv->timer != 0) {
462+ g_source_remove(priv->timer);
463+ priv->timer = 0;
464+ }
465+
466+ if (priv->apps != NULL) {
467+ /* Cleans up the keys and entries itself */
468+ g_hash_table_destroy(priv->apps);
469+ priv->apps = NULL;
470+ }
471+
472+ G_OBJECT_CLASS (app_lru_file_parent_class)->dispose (object);
473+ return;
474+}
475+
476+/* Deallocate memory */
477+static void
478+app_lru_file_finalize (GObject *object)
479+{
480+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object);
481+
482+ if (priv->filename != NULL) {
483+ g_free(priv->filename);
484+ priv->filename = NULL;
485+ }
486+
487+ G_OBJECT_CLASS (app_lru_file_parent_class)->finalize (object);
488+ return;
489+}
490+
491+/* Takes an AppData structure and free's the
492+ memory from it. */
493+static void
494+app_data_free (gpointer data)
495+{
496+ AppData * appdata = (AppData *)data;
497+
498+ if (appdata->category != NULL) {
499+ g_free(appdata->category);
500+ }
501+
502+ g_free(appdata);
503+
504+ return;
505+}
506+
507+/* Loads all of the data out of a json file */
508+static gboolean
509+load_from_file (gpointer data)
510+{
511+ AppLruFile * lrufile = (AppLruFile *)data;
512+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
513+
514+ if (!g_file_test(priv->filename, G_FILE_TEST_EXISTS)) {
515+ return FALSE;
516+ }
517+
518+ JsonParser * parser = json_parser_new();
519+
520+ if (!json_parser_load_from_file(parser, priv->filename, NULL)) {
521+ g_warning("Unable to parse JSON file '%s'", priv->filename);
522+ g_object_unref(parser);
523+ return FALSE;
524+ }
525+
526+ JsonNode * root = json_parser_get_root(parser);
527+ JsonObject * rootobj = json_node_get_object(root);
528+ if (rootobj == NULL) {
529+ g_warning("Malformed LRU file. The root node is not an object.");
530+ g_object_unref(parser);
531+ return FALSE;
532+ }
533+
534+ json_object_foreach_member(rootobj, load_file_object_cb, lrufile);
535+
536+ g_object_unref(parser);
537+ return FALSE;
538+}
539+
540+/* Looks at the various things that we need on a node, makes
541+ sure that we have them, and then copies them into the
542+ application hash table of love. */
543+static void
544+load_file_object_cb (JsonObject * rootobj, const gchar * key, JsonNode * value, gpointer data)
545+{
546+ AppLruFile * lrufile = (AppLruFile *)data;
547+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
548+
549+ /* We're not looking at this today. */
550+ if (!g_strcmp0(key, ENTRY_VERSION)) {
551+ return;
552+ }
553+
554+ JsonObject * obj = json_node_get_object(value);
555+ if (obj == NULL) {
556+ g_warning("Data for node '%s' is not an object.", key);
557+ return;
558+ }
559+
560+ const gchar * obj_category = json_object_get_string_member(obj, ENTRY_CATEGORY);
561+ const gchar * obj_first = json_object_get_string_member(obj, ENTRY_FIRST_TIME);
562+ const gchar * obj_last = json_object_get_string_member(obj, ENTRY_LAST_TIME);
563+
564+ if (obj_category == NULL || obj_first == NULL || obj_last == NULL) {
565+ g_warning("Node '%s' is missing data. Got: ('%s', '%s', '%s')", key, obj_category, obj_first, obj_last);
566+ get_dirty(lrufile);
567+ return;
568+ }
569+
570+ /* Check to see how old this entry is. If it hasn't been
571+ used in the last year, we remove the cruft. */
572+ GTimeVal currenttime;
573+ g_get_current_time(&currenttime);
574+ GDate * currentdate = g_date_new();
575+ g_date_set_time_val(currentdate, &currenttime);
576+
577+ GTimeVal lasttouch;
578+ g_time_val_from_iso8601(obj_last, &lasttouch);
579+ GDate * lastdate = g_date_new();
580+ g_date_set_time_val(lastdate, &lasttouch);
581+
582+ gint spread = g_date_days_between(lastdate, currentdate);
583+
584+ g_date_free(currentdate);
585+ g_date_free(lastdate);
586+
587+ if (spread > 365) {
588+ g_debug("Removing node '%s' as it's %d days old.", key, spread);
589+ get_dirty(lrufile);
590+ return;
591+ }
592+
593+ /* See if we already have one of these. It's a little bit
594+ unlikely, but since we're async, we need to check */
595+ gpointer datapntr = g_hash_table_lookup(priv->apps, key);
596+ if (datapntr == NULL) {
597+ /* Build a new node */
598+ AppData * appdata = g_new0(AppData, 1);
599+ appdata->category = g_strdup(obj_category);
600+ g_time_val_from_iso8601(obj_first, &appdata->first_touched);
601+ g_time_val_from_iso8601(obj_last, &appdata->last_touched);
602+
603+ g_hash_table_insert(priv->apps, g_strdup(key), appdata);
604+ } else {
605+ /* Merge nodes */
606+ AppData * appdata = (AppData *)datapntr;
607+ GTimeVal temptime;
608+ g_time_val_from_iso8601(obj_first, &temptime);
609+
610+ if (temptime.tv_sec < appdata->first_touched.tv_sec) {
611+ g_time_val_from_iso8601(obj_first, &appdata->first_touched);
612+ }
613+ }
614+
615+ return;
616+}
617+
618+/* Write out our cache to a file so that we can unmark the dirty
619+ bit and be happy. */
620+static gboolean
621+clean_off (gpointer data)
622+{
623+ AppLruFile * lrufile = (AppLruFile *)data;
624+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
625+ priv->timer = 0;
626+
627+ GError * error = NULL;
628+
629+ /* Check to see if our directory exists. Build it if not. */
630+ gchar * dirname = g_build_filename(g_get_user_config_dir(), CONFIG_DIR, NULL);
631+ if (!g_file_test(dirname, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
632+ GFile * dirfile = g_file_new_for_path(dirname);
633+ g_file_make_directory_with_parents(dirfile, NULL, NULL);
634+ g_object_unref(dirfile);
635+ }
636+ g_free(dirname);
637+
638+ GFile * file = g_file_new_for_path(priv->filename);
639+ GFileOutputStream * ostream = g_file_replace(file,
640+ NULL, /* etag */
641+ TRUE, /* backup */
642+ G_FILE_CREATE_NONE, /* flags */
643+ NULL, /* cancelable */
644+ &error);
645+ if (error != NULL) {
646+ g_warning("Unable to open a file to store LRU file: %s", error->message);
647+ return FALSE;
648+ }
649+
650+ /* This is how the file will start */
651+ GString * filestring = g_string_new("{\n \"" ENTRY_VERSION "\": 1");
652+
653+ /* Put the middle in. */
654+ g_hash_table_foreach (priv->apps, clean_off_hash_cb, filestring);
655+
656+ /* And then tack on the end. */
657+ g_string_append(filestring, "\n}\n");
658+ gchar * filedata = g_string_free(filestring, FALSE);
659+ g_output_stream_write_async(G_OUTPUT_STREAM(ostream),
660+ filedata,
661+ strlen(filedata),
662+ G_PRIORITY_DEFAULT_IDLE,
663+ NULL,
664+ clean_off_write_end_cb,
665+ filedata);
666+
667+ return FALSE; /* drop the timer */
668+}
669+
670+/* Looks at every value in the applications hash table and
671+ turns it into a string for writing out. */
672+static void
673+clean_off_hash_cb (gpointer key, gpointer value, gpointer data)
674+{
675+ /* Mega-cast */
676+ gchar * id = (gchar *)key;
677+ AppData * appdata = (AppData *)value;
678+ GString * string = (GString *)data;
679+
680+ gchar * firsttime = g_time_val_to_iso8601(&appdata->first_touched);
681+ gchar * lasttime = g_time_val_to_iso8601(&appdata->last_touched);
682+
683+ g_string_append_printf(string, ",\n \"%s\": { \"" ENTRY_FIRST_TIME "\": \"%s\", \"" ENTRY_LAST_TIME "\": \"%s\", \"" ENTRY_CATEGORY "\": \"%s\"}", id, firsttime, lasttime, appdata->category);
684+
685+ g_free(lasttime);
686+ g_free(firsttime);
687+
688+ return;
689+}
690+
691+/* Very much like clean_off_write_cb except that it is the
692+ last actor on this Output Stream so it closes it. */
693+static void
694+clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data)
695+{
696+ g_free(data);
697+
698+ GError * error = NULL;
699+ g_output_stream_close(G_OUTPUT_STREAM(obj), NULL, &error);
700+
701+ if (error != NULL) {
702+ g_warning("Unable to close LRU File: %s", error->message);
703+ g_error_free(error);
704+ }
705+
706+ return;
707+}
708+
709+/* Sets the dirty bit if not already set and makes sure that
710+ we have a timer to fix that at some point. */
711+static void
712+get_dirty (AppLruFile * lrufile)
713+{
714+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
715+
716+ priv->dirty = TRUE;
717+
718+ if (priv->timer == 0) {
719+ priv->timer = g_timeout_add_seconds(60, clean_off, lrufile);
720+ }
721+
722+ return;
723+}
724+
725+/* API */
726+
727+/* Simple helper to create a new object */
728+AppLruFile *
729+app_lru_file_new (void)
730+{
731+ return APP_LRU_FILE(g_object_new(APP_LRU_FILE_TYPE, NULL));
732+}
733+
734+/* This updates the timestamp for a particular
735+ entry in the database. It also queues up a
736+ write out of the database. But that'll happen
737+ later. */
738+void
739+app_lru_file_touch (AppLruFile * lrufile, const gchar * id, const gchar * category)
740+{
741+ g_return_if_fail(IS_APP_LRU_FILE(lrufile));
742+ g_return_if_fail(id != NULL && id[0] != '\0');
743+ g_return_if_fail(category != NULL && category[0] != '\0');
744+
745+ AppData * appdata = NULL;
746+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
747+ gpointer data = g_hash_table_lookup(priv->apps, id);
748+
749+ if (data == NULL) {
750+ /* Oh, we don't have one, let's build it and put it
751+ into the hash table ourselves */
752+ appdata = g_new0(AppData, 1);
753+
754+ appdata->category = g_strdup(category);
755+ g_get_current_time(&(appdata->first_touched));
756+ /* NOTE: last touched set below */
757+
758+ g_hash_table_insert(priv->apps, g_strdup(id), appdata);
759+ } else {
760+ /* Boring, we've got this one already */
761+ appdata = (AppData *)data;
762+ }
763+
764+ /* Touch it and mark the DB as dirty */
765+ g_get_current_time(&(appdata->last_touched));
766+ get_dirty(lrufile);
767+ return;
768+}
769+
770+/* Used to sort or compare different applications. */
771+gint
772+app_lru_file_sort (AppLruFile * lrufile, const gchar * id_a, const gchar * id_b)
773+{
774+ g_return_val_if_fail(IS_APP_LRU_FILE(lrufile), -1);
775+
776+ /* Let's first look to see if the IDs are the same, this
777+ really shouldn't happen, but it'll be confusing if we
778+ don't get it out of the way to start. */
779+ if (g_strcmp0(id_a, id_b) == 0) {
780+ return 0;
781+ }
782+
783+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
784+
785+ /* Now make sure we have app data for both of these. If
786+ not we'll assume that the one without is newer? */
787+ gpointer data_a = g_hash_table_lookup(priv->apps, id_a);
788+ if (data_a == NULL) {
789+ return -1;
790+ }
791+
792+ gpointer data_b = g_hash_table_lookup(priv->apps, id_b);
793+ if (data_b == NULL) {
794+ return 1;
795+ }
796+
797+ /* Ugly casting */
798+ AppData * appdata_a = (AppData *)data_a;
799+ AppData * appdata_b = (AppData *)data_b;
800+
801+ /* Look at categories, we'll put the categories in alpha
802+ order if they're not the same. */
803+ gint catcompare = g_strcmp0(appdata_a->category, appdata_b->category);
804+ if (catcompare != 0) {
805+ return catcompare;
806+ }
807+
808+ /* Now we're looking at the first time that these two were
809+ seen in this account. Only using seconds. I mean, seriously. */
810+ if (appdata_a->first_touched.tv_sec < appdata_b->first_touched.tv_sec) {
811+ return -1;
812+ }
813+ if (appdata_b->first_touched.tv_sec < appdata_a->first_touched.tv_sec) {
814+ return 1;
815+ }
816+
817+ /* Eh, this seems roughly impossible. But if we have to choose,
818+ I like A better. */
819+ return 1;
820+}
821
822=== added file 'src/application-service-lru-file.h'
823--- src/application-service-lru-file.h 1970-01-01 00:00:00 +0000
824+++ src/application-service-lru-file.h 2010-01-21 20:27:14 +0000
825@@ -0,0 +1,59 @@
826+/*
827+This object manages the database of when the entires were touched
828+and loved. And writes that out to disk occationally as well.
829+
830+Copyright 2010 Canonical Ltd.
831+
832+Authors:
833+ Ted Gould <ted@canonical.com>
834+
835+This program is free software: you can redistribute it and/or modify it
836+under the terms of the GNU General Public License version 3, as published
837+by the Free Software Foundation.
838+
839+This program is distributed in the hope that it will be useful, but
840+WITHOUT ANY WARRANTY; without even the implied warranties of
841+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
842+PURPOSE. See the GNU General Public License for more details.
843+
844+You should have received a copy of the GNU General Public License along
845+with this program. If not, see <http://www.gnu.org/licenses/>.
846+*/
847+
848+#ifndef __APP_LRU_FILE_H__
849+#define __APP_LRU_FILE_H__
850+
851+#include <glib.h>
852+#include <glib-object.h>
853+
854+G_BEGIN_DECLS
855+
856+#define APP_LRU_FILE_TYPE (app_lru_file_get_type ())
857+#define APP_LRU_FILE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), APP_LRU_FILE_TYPE, AppLruFile))
858+#define APP_LRU_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), APP_LRU_FILE_TYPE, AppLruFileClass))
859+#define IS_APP_LRU_FILE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), APP_LRU_FILE_TYPE))
860+#define IS_APP_LRU_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), APP_LRU_FILE_TYPE))
861+#define APP_LRU_FILE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), APP_LRU_FILE_TYPE, AppLruFileClass))
862+
863+typedef struct _AppLruFile AppLruFile;
864+typedef struct _AppLruFileClass AppLruFileClass;
865+
866+struct _AppLruFileClass {
867+ GObjectClass parent_class;
868+
869+};
870+
871+struct _AppLruFile {
872+ GObject parent;
873+
874+};
875+
876+GType app_lru_file_get_type (void);
877+
878+AppLruFile * app_lru_file_new (void);
879+void app_lru_file_touch (AppLruFile * lrufile, const gchar * id, const gchar * category);
880+gint app_lru_file_sort (AppLruFile * lrufile, const gchar * id_a, const gchar * id_b);
881+
882+G_END_DECLS
883+
884+#endif
885
886=== modified file 'src/application-service.c'
887--- src/application-service.c 2010-01-08 21:02:29 +0000
888+++ src/application-service.c 2010-01-21 20:27:14 +0000
889@@ -25,6 +25,7 @@
890 #include "notification-item-client.h"
891 #include "application-service-appstore.h"
892 #include "application-service-watcher.h"
893+#include "application-service-lru-file.h"
894 #include "dbus-shared.h"
895
896 /* The base main loop */
897@@ -35,6 +36,8 @@
898 static ApplicationServiceWatcher * watcher = NULL;
899 /* The service management interface */
900 static IndicatorService * service = NULL;
901+/* The LRU file interface */
902+static AppLruFile * lrufile = NULL;
903
904 /* Recieves the disonnection signal from the service
905 object and closes the mainloop. */
906@@ -59,8 +62,11 @@
907 service = indicator_service_new(INDICATOR_APPLICATION_DBUS_ADDR);
908 g_signal_connect(G_OBJECT(service), INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_disconnected), NULL);
909
910+ /* Start up the LRU file reading */
911+ lrufile = app_lru_file_new();
912+
913 /* Building our app store */
914- appstore = APPLICATION_SERVICE_APPSTORE(g_object_new(APPLICATION_SERVICE_APPSTORE_TYPE, NULL));
915+ appstore = application_service_appstore_new(lrufile);
916
917 /* Adding a watcher for the Apps coming up */
918 watcher = application_service_watcher_new(appstore);
919@@ -73,6 +79,7 @@
920 g_object_unref(G_OBJECT(watcher));
921 g_object_unref(G_OBJECT(appstore));
922 g_object_unref(G_OBJECT(service));
923+ g_object_unref(G_OBJECT(lrufile));
924
925 return 0;
926 }
927
928=== modified file 'src/application-service.xml'
929--- src/application-service.xml 2010-01-07 23:06:38 +0000
930+++ src/application-service.xml 2010-01-21 20:27:14 +0000
931@@ -26,7 +26,7 @@
932
933 <!-- Methods -->
934 <method name="GetApplications">
935- <arg type="a(siso)" name="applications" direction="out" />
936+ <arg type="a(sisos)" name="applications" direction="out" />
937 </method>
938
939 <!-- Signals -->
940
941=== modified file 'src/indicator-application.c'
942--- src/indicator-application.c 2010-01-07 23:06:38 +0000
943+++ src/indicator-application.c 2010-01-21 20:27:14 +0000
944@@ -75,6 +75,7 @@
945 DBusGConnection * bus;
946 DBusGProxy * service_proxy;
947 GList * applications;
948+ GHashTable * theme_dirs;
949 };
950
951 typedef struct _ApplicationEntry ApplicationEntry;
952@@ -91,11 +92,15 @@
953 static void indicator_application_dispose (GObject *object);
954 static void indicator_application_finalize (GObject *object);
955 static GList * get_entries (IndicatorObject * io);
956+static guint get_location (IndicatorObject * io, IndicatorObjectEntry * entry);
957 static void connected (IndicatorServiceManager * sm, gboolean connected, IndicatorApplication * application);
958 static void application_added (DBusGProxy * proxy, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_path, IndicatorApplication * application);
959 static void application_removed (DBusGProxy * proxy, gint position , IndicatorApplication * application);
960 static void application_icon_changed (DBusGProxy * proxy, gint position, const gchar * iconname, IndicatorApplication * application);
961 static void get_applications (DBusGProxy *proxy, GPtrArray *OUT_applications, GError *error, gpointer userdata);
962+static void get_applications_helper (gpointer data, gpointer user_data);
963+static void theme_dir_unref(IndicatorApplication * ia, const gchar * dir);
964+static void theme_dir_ref(IndicatorApplication * ia, const gchar * dir);
965
966 G_DEFINE_TYPE (IndicatorApplication, indicator_application, INDICATOR_OBJECT_TYPE);
967
968@@ -112,6 +117,7 @@
969 IndicatorObjectClass * io_class = INDICATOR_OBJECT_CLASS(klass);
970
971 io_class->get_entries = get_entries;
972+ io_class->get_location = get_location;
973
974 dbus_g_object_register_marshaller(_application_service_marshal_VOID__STRING_INT_STRING_STRING_STRING,
975 G_TYPE_NONE,
976@@ -138,12 +144,15 @@
977 /* These are built in the connection phase */
978 priv->bus = NULL;
979 priv->service_proxy = NULL;
980+ priv->theme_dirs = NULL;
981
982 priv->sm = indicator_service_manager_new(INDICATOR_APPLICATION_DBUS_ADDR);
983 g_signal_connect(G_OBJECT(priv->sm), INDICATOR_SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE, G_CALLBACK(connected), self);
984
985 priv->applications = NULL;
986
987+ priv->theme_dirs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
988+
989 return;
990 }
991
992@@ -173,6 +182,15 @@
993 priv->service_proxy = NULL;
994 }
995
996+ if (priv->theme_dirs != NULL) {
997+ while (g_hash_table_size(priv->theme_dirs)) {
998+ GList * keys = g_hash_table_get_keys(priv->theme_dirs);
999+ theme_dir_unref(INDICATOR_APPLICATION(object), (gchar *)keys->data);
1000+ }
1001+ g_hash_table_destroy(priv->theme_dirs);
1002+ priv->theme_dirs = NULL;
1003+ }
1004+
1005 G_OBJECT_CLASS (indicator_application_parent_class)->dispose (object);
1006 return;
1007 }
1008@@ -282,6 +300,15 @@
1009 return retval;
1010 }
1011
1012+/* Finds the location of a specific entry */
1013+static guint
1014+get_location (IndicatorObject * io, IndicatorObjectEntry * entry)
1015+{
1016+ g_return_val_if_fail(IS_INDICATOR_APPLICATION(io), 0);
1017+ IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(io);
1018+ return g_list_index(priv->applications, entry);
1019+}
1020+
1021 /* Here we respond to new applications by building up the
1022 ApplicationEntry and signaling the indicator host that
1023 we've got a new indicator. */
1024@@ -295,14 +322,17 @@
1025 app->icon_path = NULL;
1026 if (icon_path != NULL && icon_path[0] != '\0') {
1027 app->icon_path = g_strdup(icon_path);
1028- g_debug("\tAppending search path: %s", app->icon_path);
1029- gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), app->icon_path);
1030+ theme_dir_ref(application, icon_path);
1031 }
1032
1033 app->entry.image = GTK_IMAGE(gtk_image_new_from_icon_name(iconname, GTK_ICON_SIZE_MENU));
1034 app->entry.label = NULL;
1035 app->entry.menu = GTK_MENU(dbusmenu_gtkmenu_new((gchar *)dbusaddress, (gchar *)dbusobject));
1036
1037+ /* Keep copies of these for ourself, just in case. */
1038+ g_object_ref(app->entry.image);
1039+ g_object_ref(app->entry.menu);
1040+
1041 gtk_widget_show(GTK_WIDGET(app->entry.image));
1042
1043 priv->applications = g_list_insert(priv->applications, app, position);
1044@@ -329,6 +359,7 @@
1045 g_signal_emit(G_OBJECT(application), INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED_ID, 0, &(app->entry), TRUE);
1046
1047 if (app->icon_path != NULL) {
1048+ theme_dir_unref(application, app->icon_path);
1049 g_free(app->icon_path);
1050 }
1051 if (app->entry.image != NULL) {
1052@@ -368,6 +399,115 @@
1053 static void
1054 get_applications (DBusGProxy *proxy, GPtrArray *OUT_applications, GError *error, gpointer userdata)
1055 {
1056-
1057- return;
1058-}
1059+ if (error != NULL) {
1060+ g_warning("Unable to get application list: %s", error->message);
1061+ return;
1062+ }
1063+ g_ptr_array_foreach(OUT_applications, get_applications_helper, userdata);
1064+
1065+ return;
1066+}
1067+
1068+/* A little helper that takes apart the DBus structure and calls
1069+ application_added on every entry in the list. */
1070+static void
1071+get_applications_helper (gpointer data, gpointer user_data)
1072+{
1073+ GValueArray * array = (GValueArray *)data;
1074+
1075+ g_return_if_fail(array->n_values == 5);
1076+
1077+ const gchar * icon_name = g_value_get_string(g_value_array_get_nth(array, 0));
1078+ gint position = g_value_get_int(g_value_array_get_nth(array, 1));
1079+ const gchar * dbus_address = g_value_get_string(g_value_array_get_nth(array, 2));
1080+ const gchar * dbus_object = g_value_get_boxed(g_value_array_get_nth(array, 3));
1081+ const gchar * icon_path = g_value_get_string(g_value_array_get_nth(array, 4));
1082+
1083+ return application_added(NULL, icon_name, position, dbus_address, dbus_object, icon_path, user_data);
1084+}
1085+
1086+/* Refs a theme directory, and it may add it to the search
1087+ path */
1088+static void
1089+theme_dir_unref(IndicatorApplication * ia, const gchar * dir)
1090+{
1091+ IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(ia);
1092+
1093+ /* Grab the count for this dir */
1094+ int count = GPOINTER_TO_INT(g_hash_table_lookup(priv->theme_dirs, dir));
1095+
1096+ /* Is this a simple deprecation, if so, we can just lower the
1097+ number and move on. */
1098+ if (count > 1) {
1099+ count--;
1100+ g_hash_table_insert(priv->theme_dirs, g_strdup(dir), GINT_TO_POINTER(count));
1101+ return;
1102+ }
1103+
1104+ /* Try to remove it from the hash table, this makes sure
1105+ that it existed */
1106+ if (!g_hash_table_remove(priv->theme_dirs, dir)) {
1107+ g_warning("Unref'd a directory that wasn't in the theme dir hash table.");
1108+ return;
1109+ }
1110+
1111+ GtkIconTheme * icon_theme = gtk_icon_theme_get_default();
1112+ gchar ** paths;
1113+ gint path_count;
1114+
1115+ gtk_icon_theme_get_search_path(icon_theme, &paths, &path_count);
1116+
1117+ gint i;
1118+ gboolean found = FALSE;
1119+ for (i = 0; i < path_count; i++) {
1120+ if (found) {
1121+ /* If we've already found the right entry */
1122+ paths[i - 1] = paths[i];
1123+ } else {
1124+ /* We're still looking, is this the one? */
1125+ if (!g_strcmp0(paths[i], dir)) {
1126+ found = TRUE;
1127+ /* We're freeing this here as it won't be captured by the
1128+ g_strfreev() below as it's out of the array. */
1129+ g_free(paths[i]);
1130+ }
1131+ }
1132+ }
1133+
1134+ /* If we found one we need to reset the path to
1135+ accomidate the changes */
1136+ if (found) {
1137+ paths[path_count - 1] = NULL; /* Clear the last one */
1138+ gtk_icon_theme_set_search_path(icon_theme, (const gchar **)paths, path_count - 1);
1139+ }
1140+
1141+ g_strfreev(paths);
1142+
1143+ return;
1144+}
1145+
1146+/* Unrefs a theme directory. This may involve removing it from
1147+ the search path. */
1148+static void
1149+theme_dir_ref(IndicatorApplication * ia, const gchar * dir)
1150+{
1151+ IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(ia);
1152+
1153+ int count = 0;
1154+ if ((count = GPOINTER_TO_INT(g_hash_table_lookup(priv->theme_dirs, dir))) != 0) {
1155+ /* It exists so what we need to do is increase the ref
1156+ count of this dir. */
1157+ count++;
1158+ } else {
1159+ /* It doesn't exist, so we need to add it to the table
1160+ and to the search path. */
1161+ gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), dir);
1162+ g_debug("\tAppending search path: %s", dir);
1163+ count = 1;
1164+ }
1165+
1166+ g_hash_table_insert(priv->theme_dirs, g_strdup(dir), GINT_TO_POINTER(count));
1167+
1168+ return;
1169+}
1170+
1171
1172=== modified file 'src/libappindicator/app-indicator.c'
1173--- src/libappindicator/app-indicator.c 2010-01-19 19:53:16 +0000
1174+++ src/libappindicator/app-indicator.c 2010-01-21 20:27:14 +0000
1175@@ -370,6 +370,10 @@
1176 priv->menu = NULL;
1177 }
1178
1179+ if (priv->menuservice != NULL) {
1180+ g_object_unref (priv->menuservice);
1181+ }
1182+
1183 if (priv->dbus_proxy != NULL) {
1184 g_object_unref(G_OBJECT(priv->dbus_proxy));
1185 priv->dbus_proxy = NULL;

Subscribers

People subscribed via source and target branches

to all changes: