diff options
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | debian/changelog | 23 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/application-service-appstore.c | 143 | ||||
-rw-r--r-- | src/application-service-appstore.h | 2 | ||||
-rw-r--r-- | src/application-service-lru-file.c | 473 | ||||
-rw-r--r-- | src/application-service-lru-file.h | 59 | ||||
-rw-r--r-- | src/application-service.c | 9 | ||||
-rw-r--r-- | src/application-service.xml | 2 | ||||
-rw-r--r-- | src/indicator-application.c | 144 | ||||
-rw-r--r-- | src/libappindicator/app-indicator.c | 4 |
12 files changed, 841 insertions, 24 deletions
diff --git a/configure.ac b/configure.ac index ebd6b20..5855168 100644 --- a/configure.ac +++ b/configure.ac @@ -40,9 +40,11 @@ AC_CONFIG_MACRO_DIR(m4) GTK_REQUIRED_VERSION=2.12 INDICATOR_REQUIRED_VERSION=0.3.0 DBUSMENUGTK_REQUIRED_VERSION=0.1.1 +JSON_GLIB_REQUIRED_VERSION=0.7.6 PKG_CHECK_MODULES(INDICATOR, gtk+-2.0 >= $GTK_REQUIRED_VERSION indicator >= $INDICATOR_REQUIRED_VERSION + json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION dbusmenu-gtk >= $DBUSMENUGTK_REQUIRED_VERSION) AC_SUBST(INDICATOR_CFLAGS) @@ -51,6 +53,7 @@ AC_SUBST(INDICATOR_LIBS) ########################### # Check for Mono support ########################### + MONO_REQUIRED_VERSION=1.0 PKG_CHECK_MODULES(MONO_DEPENDENCY, mono >= $MONO_REQUIRED_VERSION, has_mono=true, has_mono=false) diff --git a/debian/changelog b/debian/changelog index 2fcbc65..ba22fde 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,26 @@ +indicator-application (0.0.9-0ubuntu1~ppa4) karmic; urgency=low + + * Upstream merge + * Adding in a file to position the indicators + * debian/control: Adding dependency for json-glib to build. + + -- Ted Gould <ted@ubuntu.com> Thu, 21 Jan 2010 14:12:37 -0600 + +indicator-application (0.0.9-0ubuntu1~ppa3) karmic; urgency=low + + * Upstream update + * Ref counting theme directories as they get added and removed + * Use the right callback function for fallback (LP: #507975) + + -- Ted Gould <ted@ubuntu.com> Wed, 20 Jan 2010 14:59:48 -0600 + +indicator-application (0.0.9-0ubuntu1~ppa2) karmic; urgency=low + + * Upstream update + * Support getting the app list from a running service. + + -- Ted Gould <ted@ubuntu.com> Wed, 20 Jan 2010 14:47:47 -0600 + indicator-application (0.0.9-0ubuntu3) lucid; urgency=low * debian/control: diff --git a/debian/control b/debian/control index f115a59..02eed84 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,7 @@ Build-Depends: debhelper (>= 5.0), python-central (>= 0.6), libgtk2.0-dev (>= 2.12.0), libdbus-glib-1-dev, + libjson-glib-dev, gnome-doc-utils, gtk-doc-tools, intltool, diff --git a/src/Makefile.am b/src/Makefile.am index d9b7963..38e6dd9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,6 +35,8 @@ indicator_application_service_SOURCES = \ application-service.c \ application-service-appstore.h \ application-service-appstore.c \ + application-service-lru-file.h \ + application-service-lru-file.c \ application-service-marshal.h \ application-service-marshal.c \ application-service-server.h \ diff --git a/src/application-service-appstore.c b/src/application-service-appstore.c index fa1b9d2..5b0cc15 100644 --- a/src/application-service-appstore.c +++ b/src/application-service-appstore.c @@ -31,7 +31,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "dbus-shared.h" /* DBus Prototypes */ -static gboolean _application_service_server_get_applications (ApplicationServiceAppstore * appstore, GArray ** apps); +static gboolean _application_service_server_get_applications (ApplicationServiceAppstore * appstore, GPtrArray ** apps, GError ** error); #include "application-service-server.h" @@ -52,6 +52,7 @@ typedef struct _ApplicationServiceAppstorePrivate ApplicationServiceAppstorePriv struct _ApplicationServiceAppstorePrivate { DBusGConnection * bus; GList * applications; + AppLruFile * lrufile; }; #define APP_STATUS_PASSIVE_STR "passive" @@ -67,6 +68,8 @@ enum _ApplicationStatus { typedef struct _Application Application; struct _Application { + gchar * id; + gchar * category; gchar * dbus_name; gchar * dbus_object; ApplicationServiceAppstore * appstore; /* not ref'd */ @@ -148,6 +151,7 @@ application_service_appstore_init (ApplicationServiceAppstore *self) ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(self); priv->applications = NULL; + priv->lrufile = NULL; GError * error = NULL; priv->bus = dbus_g_bus_get(DBUS_BUS_STARTER, &error); @@ -201,6 +205,8 @@ get_all_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * err Application * app = (Application *)data; if (g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_MENU) == NULL || + g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ID) == NULL || + g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_CATEGORY) == NULL || g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_STATUS) == NULL || g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME) == NULL) { g_warning("Notification Item on object %s of %s doesn't have enough properties.", app->dbus_object, app->dbus_name); @@ -210,6 +216,11 @@ get_all_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError * err app->validated = TRUE; + app->id = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ID)); + app->category = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_CATEGORY)); + ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(app->appstore); + app_lru_file_touch(priv->lrufile, app->id, app->category); + app->icon = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_ICON_NAME)); app->menu = g_value_dup_string(g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_MENU)); if (g_hash_table_lookup(properties, NOTIFICATION_ITEM_PROP_AICON_NAME) != NULL) { @@ -273,6 +284,12 @@ application_free (Application * app) g_object_unref(app->prop_proxy); } + if (app->id != NULL) { + g_free(app->id); + } + if (app->category != NULL) { + g_free(app->category); + } if (app->dbus_name != NULL) { g_free(app->dbus_name); } @@ -311,6 +328,40 @@ application_removed_cb (DBusGProxy * proxy, gpointer userdata) return; } +static gboolean +can_add_application (GList *applications, Application *app) +{ + if (applications) + { + GList *l = NULL; + + for (l = applications; l != NULL; l = g_list_next (l)) + { + Application *tmp_app = (Application *)l->data; + + if (g_strcmp0 (tmp_app->dbus_name, app->dbus_name) == 0 && + g_strcmp0 (tmp_app->dbus_object, app->dbus_object) == 0) + { + return FALSE; + } + } + } + + return TRUE; +} + +/* This function takes two Application structure + pointers and uses the lrufile to compare them. */ +static gint +app_sort_func (gconstpointer a, gconstpointer b, gpointer userdata) +{ + Application * appa = (Application *)a; + Application * appb = (Application *)b; + AppLruFile * lrufile = (AppLruFile *)userdata; + + return app_lru_file_sort(lrufile, appa->id, appb->id); +} + /* Change the status of the application. If we're going passive it removes it from the panel. If we're coming online, then it add it to the panel. Otherwise it changes the icon. */ @@ -333,8 +384,7 @@ apply_status (Application * app, ApplicationStatus status) g_signal_emit(G_OBJECT(appstore), signals[APPLICATION_REMOVED], 0, position, TRUE); - - priv->applications = g_list_remove(priv->applications, app); + priv->applications = g_list_remove(priv->applications, app); } else { /* Figure out which icon we should be using */ gchar * newicon = app->icon; @@ -344,21 +394,19 @@ apply_status (Application * app, ApplicationStatus status) /* Determine whether we're already shown or not */ if (app->status == APP_STATUS_PASSIVE) { - /* Put on panel */ - priv->applications = g_list_prepend(priv->applications, app); - - /* TODO: We need to have the position determined better. This - would involve looking at the name and category and sorting - it with the other entries. */ - - g_signal_emit(G_OBJECT(app->appstore), - signals[APPLICATION_ADDED], 0, - newicon, - 0, /* Position */ - app->dbus_name, - app->menu, - app->icon_path, - TRUE); + if (can_add_application (priv->applications, app)) { + /* Put on panel */ + priv->applications = g_list_insert_sorted_with_data (priv->applications, app, app_sort_func, priv->lrufile); + + g_signal_emit(G_OBJECT(app->appstore), + signals[APPLICATION_ADDED], 0, + newicon, + g_list_index(priv->applications, app), /* Position */ + app->dbus_name, + app->menu, + app->icon_path, + TRUE); + } } else { /* Icon update */ gint position = get_position(app); @@ -622,11 +670,66 @@ application_service_appstore_application_remove (ApplicationServiceAppstore * ap return; } +/* Creates a basic appstore object and attaches the + LRU file object to it. */ +ApplicationServiceAppstore * +application_service_appstore_new (AppLruFile * lrufile) +{ + g_return_val_if_fail(IS_APP_LRU_FILE(lrufile), NULL); + ApplicationServiceAppstore * appstore = APPLICATION_SERVICE_APPSTORE(g_object_new(APPLICATION_SERVICE_APPSTORE_TYPE, NULL)); + ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore); + priv->lrufile = lrufile; + return appstore; +} + /* DBus Interface */ static gboolean -_application_service_server_get_applications (ApplicationServiceAppstore * appstore, GArray ** apps) +_application_service_server_get_applications (ApplicationServiceAppstore * appstore, GPtrArray ** apps, GError ** error) { + ApplicationServiceAppstorePrivate * priv = APPLICATION_SERVICE_APPSTORE_GET_PRIVATE(appstore); + + *apps = g_ptr_array_new(); + GList * listpntr; + gint position = 0; + + for (listpntr = priv->applications; listpntr != NULL; listpntr = g_list_next(listpntr)) { + GValueArray * values = g_value_array_new(5); + + GValue value = {0}; + + /* Icon name */ + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, ((Application *)listpntr->data)->icon); + g_value_array_append(values, &value); + g_value_unset(&value); + + /* Position */ + g_value_init(&value, G_TYPE_INT); + g_value_set_int(&value, position++); + g_value_array_append(values, &value); + g_value_unset(&value); + + /* DBus Address */ + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, ((Application *)listpntr->data)->dbus_name); + g_value_array_append(values, &value); + g_value_unset(&value); + + /* DBus Object */ + g_value_init(&value, DBUS_TYPE_G_OBJECT_PATH); + g_value_set_static_boxed(&value, ((Application *)listpntr->data)->menu); + g_value_array_append(values, &value); + g_value_unset(&value); + + /* Icon path */ + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, ((Application *)listpntr->data)->icon_path); + g_value_array_append(values, &value); + g_value_unset(&value); + + g_ptr_array_add(*apps, values); + } - return FALSE; + return TRUE; } diff --git a/src/application-service-appstore.h b/src/application-service-appstore.h index 6bd7a01..d2e0013 100644 --- a/src/application-service-appstore.h +++ b/src/application-service-appstore.h @@ -25,6 +25,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <glib.h> #include <glib-object.h> +#include "application-service-lru-file.h" G_BEGIN_DECLS @@ -50,6 +51,7 @@ struct _ApplicationServiceAppstore { GObject parent; }; +ApplicationServiceAppstore * application_service_appstore_new (AppLruFile * lrufile); GType application_service_appstore_get_type (void); void application_service_appstore_application_add (ApplicationServiceAppstore * appstore, const gchar * dbus_name, diff --git a/src/application-service-lru-file.c b/src/application-service-lru-file.c new file mode 100644 index 0000000..c69a20f --- /dev/null +++ b/src/application-service-lru-file.c @@ -0,0 +1,473 @@ +/* +This object manages the database of when the entires were touched +and loved. And writes that out to disk occationally as well. + +Copyright 2010 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <gio/gio.h> +#include <json-glib/json-glib.h> +#include "application-service-lru-file.h" + +#define ENTRY_CATEGORY "category" +#define ENTRY_FIRST_TIME "first-time" +#define ENTRY_LAST_TIME "last-time" +#define ENTRY_VERSION "version" + +#define CONFIG_DIR ("indicators" G_DIR_SEPARATOR_S "application") +#define CONFIG_FILE "lru-file.json" + +typedef struct _AppLruFilePrivate AppLruFilePrivate; +struct _AppLruFilePrivate { + GHashTable * apps; + gboolean dirty; + guint timer; + gchar * filename; +}; + +typedef struct _AppData AppData; +struct _AppData { + gchar * category; + GTimeVal last_touched; + GTimeVal first_touched; +}; + +#define APP_LRU_FILE_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), APP_LRU_FILE_TYPE, AppLruFilePrivate)) + +static void app_lru_file_class_init (AppLruFileClass *klass); +static void app_lru_file_init (AppLruFile *self); +static void app_lru_file_dispose (GObject *object); +static void app_lru_file_finalize (GObject *object); +static void app_data_free (gpointer data); +static void get_dirty (AppLruFile * lrufile); +static gboolean load_from_file (gpointer data); +static void load_file_object_cb (JsonObject * obj, const gchar * key, JsonNode * value, gpointer data); +static void clean_off_hash_cb (gpointer key, gpointer value, gpointer data); +static void clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data); + +G_DEFINE_TYPE (AppLruFile, app_lru_file, G_TYPE_OBJECT); + +/* Set up the class variable stuff */ +static void +app_lru_file_class_init (AppLruFileClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (AppLruFilePrivate)); + + object_class->dispose = app_lru_file_dispose; + object_class->finalize = app_lru_file_finalize; + + return; +} + +/* Set all the data of the per-instance variables */ +static void +app_lru_file_init (AppLruFile *self) +{ + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(self); + + /* Default values */ + priv->apps = NULL; + priv->dirty = FALSE; + priv->timer = 0; + priv->filename = NULL; + + /* Now let's build some stuff */ + priv->apps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, app_data_free); + priv->filename = g_build_filename(g_get_user_config_dir(), CONFIG_DIR, CONFIG_FILE, NULL); + + /* No reason to delay other stuff for this, we'll + merge any values that get touched. */ + g_idle_add(load_from_file, self); + + return; +} + +/* Get rid of objects and other big things */ +static void +app_lru_file_dispose (GObject *object) +{ + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object); + + if (priv->timer != 0) { + g_source_remove(priv->timer); + priv->timer = 0; + } + + if (priv->apps != NULL) { + /* Cleans up the keys and entries itself */ + g_hash_table_destroy(priv->apps); + priv->apps = NULL; + } + + G_OBJECT_CLASS (app_lru_file_parent_class)->dispose (object); + return; +} + +/* Deallocate memory */ +static void +app_lru_file_finalize (GObject *object) +{ + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object); + + if (priv->filename != NULL) { + g_free(priv->filename); + priv->filename = NULL; + } + + G_OBJECT_CLASS (app_lru_file_parent_class)->finalize (object); + return; +} + +/* Takes an AppData structure and free's the + memory from it. */ +static void +app_data_free (gpointer data) +{ + AppData * appdata = (AppData *)data; + + if (appdata->category != NULL) { + g_free(appdata->category); + } + + g_free(appdata); + + return; +} + +/* Loads all of the data out of a json file */ +static gboolean +load_from_file (gpointer data) +{ + AppLruFile * lrufile = (AppLruFile *)data; + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile); + + if (!g_file_test(priv->filename, G_FILE_TEST_EXISTS)) { + return FALSE; + } + + JsonParser * parser = json_parser_new(); + + if (!json_parser_load_from_file(parser, priv->filename, NULL)) { + g_warning("Unable to parse JSON file '%s'", priv->filename); + g_object_unref(parser); + return FALSE; + } + + JsonNode * root = json_parser_get_root(parser); + JsonObject * rootobj = json_node_get_object(root); + if (rootobj == NULL) { + g_warning("Malformed LRU file. The root node is not an object."); + g_object_unref(parser); + return FALSE; + } + + json_object_foreach_member(rootobj, load_file_object_cb, lrufile); + + g_object_unref(parser); + return FALSE; +} + +/* Looks at the various things that we need on a node, makes + sure that we have them, and then copies them into the + application hash table of love. */ +static void +load_file_object_cb (JsonObject * rootobj, const gchar * key, JsonNode * value, gpointer data) +{ + AppLruFile * lrufile = (AppLruFile *)data; + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile); + + /* We're not looking at this today. */ + if (!g_strcmp0(key, ENTRY_VERSION)) { + return; + } + + JsonObject * obj = json_node_get_object(value); + if (obj == NULL) { + g_warning("Data for node '%s' is not an object.", key); + return; + } + + const gchar * obj_category = json_object_get_string_member(obj, ENTRY_CATEGORY); + const gchar * obj_first = json_object_get_string_member(obj, ENTRY_FIRST_TIME); + const gchar * obj_last = json_object_get_string_member(obj, ENTRY_LAST_TIME); + + if (obj_category == NULL || obj_first == NULL || obj_last == NULL) { + g_warning("Node '%s' is missing data. Got: ('%s', '%s', '%s')", key, obj_category, obj_first, obj_last); + get_dirty(lrufile); + return; + } + + /* Check to see how old this entry is. If it hasn't been + used in the last year, we remove the cruft. */ + GTimeVal currenttime; + g_get_current_time(¤ttime); + GDate * currentdate = g_date_new(); + g_date_set_time_val(currentdate, ¤ttime); + + GTimeVal lasttouch; + g_time_val_from_iso8601(obj_last, &lasttouch); + GDate * lastdate = g_date_new(); + g_date_set_time_val(lastdate, &lasttouch); + + gint spread = g_date_days_between(lastdate, currentdate); + + g_date_free(currentdate); + g_date_free(lastdate); + + if (spread > 365) { + g_debug("Removing node '%s' as it's %d days old.", key, spread); + get_dirty(lrufile); + return; + } + + /* See if we already have one of these. It's a little bit + unlikely, but since we're async, we need to check */ + gpointer datapntr = g_hash_table_lookup(priv->apps, key); + if (datapntr == NULL) { + /* Build a new node */ + AppData * appdata = g_new0(AppData, 1); + appdata->category = g_strdup(obj_category); + g_time_val_from_iso8601(obj_first, &appdata->first_touched); + g_time_val_from_iso8601(obj_last, &appdata->last_touched); + + g_hash_table_insert(priv->apps, g_strdup(key), appdata); + } else { + /* Merge nodes */ + AppData * appdata = (AppData *)datapntr; + GTimeVal temptime; + g_time_val_from_iso8601(obj_first, &temptime); + + if (temptime.tv_sec < appdata->first_touched.tv_sec) { + g_time_val_from_iso8601(obj_first, &appdata->first_touched); + } + } + + return; +} + +/* Write out our cache to a file so that we can unmark the dirty + bit and be happy. */ +static gboolean +clean_off (gpointer data) +{ + AppLruFile * lrufile = (AppLruFile *)data; + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile); + priv->timer = 0; + + GError * error = NULL; + + /* Check to see if our directory exists. Build it if not. */ + gchar * dirname = g_build_filename(g_get_user_config_dir(), CONFIG_DIR, NULL); + if (!g_file_test(dirname, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { + GFile * dirfile = g_file_new_for_path(dirname); + g_file_make_directory_with_parents(dirfile, NULL, NULL); + g_object_unref(dirfile); + } + g_free(dirname); + + GFile * file = g_file_new_for_path(priv->filename); + GFileOutputStream * ostream = g_file_replace(file, + NULL, /* etag */ + TRUE, /* backup */ + G_FILE_CREATE_NONE, /* flags */ + NULL, /* cancelable */ + &error); + if (error != NULL) { + g_warning("Unable to open a file to store LRU file: %s", error->message); + return FALSE; + } + + /* This is how the file will start */ + GString * filestring = g_string_new("{\n \"" ENTRY_VERSION "\": 1"); + + /* Put the middle in. */ + g_hash_table_foreach (priv->apps, clean_off_hash_cb, filestring); + + /* And then tack on the end. */ + g_string_append(filestring, "\n}\n"); + gchar * filedata = g_string_free(filestring, FALSE); + g_output_stream_write_async(G_OUTPUT_STREAM(ostream), + filedata, + strlen(filedata), + G_PRIORITY_DEFAULT_IDLE, + NULL, + clean_off_write_end_cb, + filedata); + + return FALSE; /* drop the timer */ +} + +/* Looks at every value in the applications hash table and + turns it into a string for writing out. */ +static void +clean_off_hash_cb (gpointer key, gpointer value, gpointer data) +{ + /* Mega-cast */ + gchar * id = (gchar *)key; + AppData * appdata = (AppData *)value; + GString * string = (GString *)data; + + gchar * firsttime = g_time_val_to_iso8601(&appdata->first_touched); + gchar * lasttime = g_time_val_to_iso8601(&appdata->last_touched); + + g_string_append_printf(string, ",\n \"%s\": { \"" ENTRY_FIRST_TIME "\": \"%s\", \"" ENTRY_LAST_TIME "\": \"%s\", \"" ENTRY_CATEGORY "\": \"%s\"}", id, firsttime, lasttime, appdata->category); + + g_free(lasttime); + g_free(firsttime); + + return; +} + +/* Very much like clean_off_write_cb except that it is the + last actor on this Output Stream so it closes it. */ +static void +clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data) +{ + g_free(data); + + GError * error = NULL; + g_output_stream_close(G_OUTPUT_STREAM(obj), NULL, &error); + + if (error != NULL) { + g_warning("Unable to close LRU File: %s", error->message); + g_error_free(error); + } + + return; +} + +/* Sets the dirty bit if not already set and makes sure that + we have a timer to fix that at some point. */ +static void +get_dirty (AppLruFile * lrufile) +{ + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile); + + priv->dirty = TRUE; + + if (priv->timer == 0) { + priv->timer = g_timeout_add_seconds(60, clean_off, lrufile); + } + + return; +} + +/* API */ + +/* Simple helper to create a new object */ +AppLruFile * +app_lru_file_new (void) +{ + return APP_LRU_FILE(g_object_new(APP_LRU_FILE_TYPE, NULL)); +} + +/* This updates the timestamp for a particular + entry in the database. It also queues up a + write out of the database. But that'll happen + later. */ +void +app_lru_file_touch (AppLruFile * lrufile, const gchar * id, const gchar * category) +{ + g_return_if_fail(IS_APP_LRU_FILE(lrufile)); + g_return_if_fail(id != NULL && id[0] != '\0'); + g_return_if_fail(category != NULL && category[0] != '\0'); + + AppData * appdata = NULL; + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile); + gpointer data = g_hash_table_lookup(priv->apps, id); + + if (data == NULL) { + /* Oh, we don't have one, let's build it and put it + into the hash table ourselves */ + appdata = g_new0(AppData, 1); + + appdata->category = g_strdup(category); + g_get_current_time(&(appdata->first_touched)); + /* NOTE: last touched set below */ + + g_hash_table_insert(priv->apps, g_strdup(id), appdata); + } else { + /* Boring, we've got this one already */ + appdata = (AppData *)data; + } + + /* Touch it and mark the DB as dirty */ + g_get_current_time(&(appdata->last_touched)); + get_dirty(lrufile); + return; +} + +/* Used to sort or compare different applications. */ +gint +app_lru_file_sort (AppLruFile * lrufile, const gchar * id_a, const gchar * id_b) +{ + g_return_val_if_fail(IS_APP_LRU_FILE(lrufile), -1); + + /* Let's first look to see if the IDs are the same, this + really shouldn't happen, but it'll be confusing if we + don't get it out of the way to start. */ + if (g_strcmp0(id_a, id_b) == 0) { + return 0; + } + + AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile); + + /* Now make sure we have app data for both of these. If + not we'll assume that the one without is newer? */ + gpointer data_a = g_hash_table_lookup(priv->apps, id_a); + if (data_a == NULL) { + return -1; + } + + gpointer data_b = g_hash_table_lookup(priv->apps, id_b); + if (data_b == NULL) { + return 1; + } + + /* Ugly casting */ + AppData * appdata_a = (AppData *)data_a; + AppData * appdata_b = (AppData *)data_b; + + /* Look at categories, we'll put the categories in alpha + order if they're not the same. */ + gint catcompare = g_strcmp0(appdata_a->category, appdata_b->category); + if (catcompare != 0) { + return catcompare; + } + + /* Now we're looking at the first time that these two were + seen in this account. Only using seconds. I mean, seriously. */ + if (appdata_a->first_touched.tv_sec < appdata_b->first_touched.tv_sec) { + return -1; + } + if (appdata_b->first_touched.tv_sec < appdata_a->first_touched.tv_sec) { + return 1; + } + + /* Eh, this seems roughly impossible. But if we have to choose, + I like A better. */ + return 1; +} diff --git a/src/application-service-lru-file.h b/src/application-service-lru-file.h new file mode 100644 index 0000000..7c92c82 --- /dev/null +++ b/src/application-service-lru-file.h @@ -0,0 +1,59 @@ +/* +This object manages the database of when the entires were touched +and loved. And writes that out to disk occationally as well. + +Copyright 2010 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __APP_LRU_FILE_H__ +#define __APP_LRU_FILE_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define APP_LRU_FILE_TYPE (app_lru_file_get_type ()) +#define APP_LRU_FILE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), APP_LRU_FILE_TYPE, AppLruFile)) +#define APP_LRU_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), APP_LRU_FILE_TYPE, AppLruFileClass)) +#define IS_APP_LRU_FILE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), APP_LRU_FILE_TYPE)) +#define IS_APP_LRU_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), APP_LRU_FILE_TYPE)) +#define APP_LRU_FILE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), APP_LRU_FILE_TYPE, AppLruFileClass)) + +typedef struct _AppLruFile AppLruFile; +typedef struct _AppLruFileClass AppLruFileClass; + +struct _AppLruFileClass { + GObjectClass parent_class; + +}; + +struct _AppLruFile { + GObject parent; + +}; + +GType app_lru_file_get_type (void); + +AppLruFile * app_lru_file_new (void); +void app_lru_file_touch (AppLruFile * lrufile, const gchar * id, const gchar * category); +gint app_lru_file_sort (AppLruFile * lrufile, const gchar * id_a, const gchar * id_b); + +G_END_DECLS + +#endif diff --git a/src/application-service.c b/src/application-service.c index 351e9aa..ffd042e 100644 --- a/src/application-service.c +++ b/src/application-service.c @@ -25,6 +25,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "notification-item-client.h" #include "application-service-appstore.h" #include "application-service-watcher.h" +#include "application-service-lru-file.h" #include "dbus-shared.h" /* The base main loop */ @@ -35,6 +36,8 @@ static ApplicationServiceAppstore * appstore = NULL; static ApplicationServiceWatcher * watcher = NULL; /* The service management interface */ static IndicatorService * service = NULL; +/* The LRU file interface */ +static AppLruFile * lrufile = NULL; /* Recieves the disonnection signal from the service object and closes the mainloop. */ @@ -59,8 +62,11 @@ main (int argc, char ** argv) service = indicator_service_new(INDICATOR_APPLICATION_DBUS_ADDR); g_signal_connect(G_OBJECT(service), INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_disconnected), NULL); + /* Start up the LRU file reading */ + lrufile = app_lru_file_new(); + /* Building our app store */ - appstore = APPLICATION_SERVICE_APPSTORE(g_object_new(APPLICATION_SERVICE_APPSTORE_TYPE, NULL)); + appstore = application_service_appstore_new(lrufile); /* Adding a watcher for the Apps coming up */ watcher = application_service_watcher_new(appstore); @@ -73,6 +79,7 @@ main (int argc, char ** argv) g_object_unref(G_OBJECT(watcher)); g_object_unref(G_OBJECT(appstore)); g_object_unref(G_OBJECT(service)); + g_object_unref(G_OBJECT(lrufile)); return 0; } diff --git a/src/application-service.xml b/src/application-service.xml index d74aaa9..0b2e959 100644 --- a/src/application-service.xml +++ b/src/application-service.xml @@ -26,7 +26,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. <!-- Methods --> <method name="GetApplications"> - <arg type="a(siso)" name="applications" direction="out" /> + <arg type="a(sisos)" name="applications" direction="out" /> </method> <!-- Signals --> diff --git a/src/indicator-application.c b/src/indicator-application.c index c330645..3ef5688 100644 --- a/src/indicator-application.c +++ b/src/indicator-application.c @@ -75,6 +75,7 @@ struct _IndicatorApplicationPrivate { DBusGConnection * bus; DBusGProxy * service_proxy; GList * applications; + GHashTable * theme_dirs; }; typedef struct _ApplicationEntry ApplicationEntry; @@ -91,11 +92,15 @@ static void indicator_application_init (IndicatorApplication *self); static void indicator_application_dispose (GObject *object); static void indicator_application_finalize (GObject *object); static GList * get_entries (IndicatorObject * io); +static guint get_location (IndicatorObject * io, IndicatorObjectEntry * entry); static void connected (IndicatorServiceManager * sm, gboolean connected, IndicatorApplication * application); static void application_added (DBusGProxy * proxy, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_path, IndicatorApplication * application); static void application_removed (DBusGProxy * proxy, gint position , IndicatorApplication * application); static void application_icon_changed (DBusGProxy * proxy, gint position, const gchar * iconname, IndicatorApplication * application); static void get_applications (DBusGProxy *proxy, GPtrArray *OUT_applications, GError *error, gpointer userdata); +static void get_applications_helper (gpointer data, gpointer user_data); +static void theme_dir_unref(IndicatorApplication * ia, const gchar * dir); +static void theme_dir_ref(IndicatorApplication * ia, const gchar * dir); G_DEFINE_TYPE (IndicatorApplication, indicator_application, INDICATOR_OBJECT_TYPE); @@ -112,6 +117,7 @@ indicator_application_class_init (IndicatorApplicationClass *klass) IndicatorObjectClass * io_class = INDICATOR_OBJECT_CLASS(klass); io_class->get_entries = get_entries; + io_class->get_location = get_location; dbus_g_object_register_marshaller(_application_service_marshal_VOID__STRING_INT_STRING_STRING_STRING, G_TYPE_NONE, @@ -138,12 +144,15 @@ indicator_application_init (IndicatorApplication *self) /* These are built in the connection phase */ priv->bus = NULL; priv->service_proxy = NULL; + priv->theme_dirs = NULL; priv->sm = indicator_service_manager_new(INDICATOR_APPLICATION_DBUS_ADDR); g_signal_connect(G_OBJECT(priv->sm), INDICATOR_SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE, G_CALLBACK(connected), self); priv->applications = NULL; + priv->theme_dirs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + return; } @@ -173,6 +182,15 @@ indicator_application_dispose (GObject *object) priv->service_proxy = NULL; } + if (priv->theme_dirs != NULL) { + while (g_hash_table_size(priv->theme_dirs)) { + GList * keys = g_hash_table_get_keys(priv->theme_dirs); + theme_dir_unref(INDICATOR_APPLICATION(object), (gchar *)keys->data); + } + g_hash_table_destroy(priv->theme_dirs); + priv->theme_dirs = NULL; + } + G_OBJECT_CLASS (indicator_application_parent_class)->dispose (object); return; } @@ -282,6 +300,15 @@ get_entries (IndicatorObject * io) return retval; } +/* Finds the location of a specific entry */ +static guint +get_location (IndicatorObject * io, IndicatorObjectEntry * entry) +{ + g_return_val_if_fail(IS_INDICATOR_APPLICATION(io), 0); + IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(io); + return g_list_index(priv->applications, entry); +} + /* Here we respond to new applications by building up the ApplicationEntry and signaling the indicator host that we've got a new indicator. */ @@ -295,14 +322,17 @@ application_added (DBusGProxy * proxy, const gchar * iconname, gint position, co app->icon_path = NULL; if (icon_path != NULL && icon_path[0] != '\0') { app->icon_path = g_strdup(icon_path); - g_debug("\tAppending search path: %s", app->icon_path); - gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), app->icon_path); + theme_dir_ref(application, icon_path); } app->entry.image = GTK_IMAGE(gtk_image_new_from_icon_name(iconname, GTK_ICON_SIZE_MENU)); app->entry.label = NULL; app->entry.menu = GTK_MENU(dbusmenu_gtkmenu_new((gchar *)dbusaddress, (gchar *)dbusobject)); + /* Keep copies of these for ourself, just in case. */ + g_object_ref(app->entry.image); + g_object_ref(app->entry.menu); + gtk_widget_show(GTK_WIDGET(app->entry.image)); priv->applications = g_list_insert(priv->applications, app, position); @@ -329,6 +359,7 @@ application_removed (DBusGProxy * proxy, gint position, IndicatorApplication * a g_signal_emit(G_OBJECT(application), INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED_ID, 0, &(app->entry), TRUE); if (app->icon_path != NULL) { + theme_dir_unref(application, app->icon_path); g_free(app->icon_path); } if (app->entry.image != NULL) { @@ -368,6 +399,115 @@ application_icon_changed (DBusGProxy * proxy, gint position, const gchar * iconn static void get_applications (DBusGProxy *proxy, GPtrArray *OUT_applications, GError *error, gpointer userdata) { + if (error != NULL) { + g_warning("Unable to get application list: %s", error->message); + return; + } + g_ptr_array_foreach(OUT_applications, get_applications_helper, userdata); return; } + +/* A little helper that takes apart the DBus structure and calls + application_added on every entry in the list. */ +static void +get_applications_helper (gpointer data, gpointer user_data) +{ + GValueArray * array = (GValueArray *)data; + + g_return_if_fail(array->n_values == 5); + + const gchar * icon_name = g_value_get_string(g_value_array_get_nth(array, 0)); + gint position = g_value_get_int(g_value_array_get_nth(array, 1)); + const gchar * dbus_address = g_value_get_string(g_value_array_get_nth(array, 2)); + const gchar * dbus_object = g_value_get_boxed(g_value_array_get_nth(array, 3)); + const gchar * icon_path = g_value_get_string(g_value_array_get_nth(array, 4)); + + return application_added(NULL, icon_name, position, dbus_address, dbus_object, icon_path, user_data); +} + +/* Refs a theme directory, and it may add it to the search + path */ +static void +theme_dir_unref(IndicatorApplication * ia, const gchar * dir) +{ + IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(ia); + + /* Grab the count for this dir */ + int count = GPOINTER_TO_INT(g_hash_table_lookup(priv->theme_dirs, dir)); + + /* Is this a simple deprecation, if so, we can just lower the + number and move on. */ + if (count > 1) { + count--; + g_hash_table_insert(priv->theme_dirs, g_strdup(dir), GINT_TO_POINTER(count)); + return; + } + + /* Try to remove it from the hash table, this makes sure + that it existed */ + if (!g_hash_table_remove(priv->theme_dirs, dir)) { + g_warning("Unref'd a directory that wasn't in the theme dir hash table."); + return; + } + + GtkIconTheme * icon_theme = gtk_icon_theme_get_default(); + gchar ** paths; + gint path_count; + + gtk_icon_theme_get_search_path(icon_theme, &paths, &path_count); + + gint i; + gboolean found = FALSE; + for (i = 0; i < path_count; i++) { + if (found) { + /* If we've already found the right entry */ + paths[i - 1] = paths[i]; + } else { + /* We're still looking, is this the one? */ + if (!g_strcmp0(paths[i], dir)) { + found = TRUE; + /* We're freeing this here as it won't be captured by the + g_strfreev() below as it's out of the array. */ + g_free(paths[i]); + } + } + } + + /* If we found one we need to reset the path to + accomidate the changes */ + if (found) { + paths[path_count - 1] = NULL; /* Clear the last one */ + gtk_icon_theme_set_search_path(icon_theme, (const gchar **)paths, path_count - 1); + } + + g_strfreev(paths); + + return; +} + +/* Unrefs a theme directory. This may involve removing it from + the search path. */ +static void +theme_dir_ref(IndicatorApplication * ia, const gchar * dir) +{ + IndicatorApplicationPrivate * priv = INDICATOR_APPLICATION_GET_PRIVATE(ia); + + int count = 0; + if ((count = GPOINTER_TO_INT(g_hash_table_lookup(priv->theme_dirs, dir))) != 0) { + /* It exists so what we need to do is increase the ref + count of this dir. */ + count++; + } else { + /* It doesn't exist, so we need to add it to the table + and to the search path. */ + gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), dir); + g_debug("\tAppending search path: %s", dir); + count = 1; + } + + g_hash_table_insert(priv->theme_dirs, g_strdup(dir), GINT_TO_POINTER(count)); + + return; +} + diff --git a/src/libappindicator/app-indicator.c b/src/libappindicator/app-indicator.c index 5e7fee4..b0f721e 100644 --- a/src/libappindicator/app-indicator.c +++ b/src/libappindicator/app-indicator.c @@ -370,6 +370,10 @@ app_indicator_dispose (GObject *object) priv->menu = NULL; } + if (priv->menuservice != NULL) { + g_object_unref (priv->menuservice); + } + if (priv->dbus_proxy != NULL) { g_object_unref(G_OBJECT(priv->dbus_proxy)); priv->dbus_proxy = NULL; |