aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTed Gould <ted@gould.cx>2010-02-16 22:51:13 -0600
committerTed Gould <ted@gould.cx>2010-02-16 22:51:13 -0600
commita9e097f3f87fa3aac7a06ddbc137c6122c65c9dc (patch)
tree42360c9b2d858b2c97ac8dea8cef91f2afcaa1bd
parent72ef75fa51c63e533ff2883bffc75bfc86a2c521 (diff)
parent6665fd01b1b85646f5c21aa393a3c3e6a8e4ab97 (diff)
downloadlibayatana-indicator-a9e097f3f87fa3aac7a06ddbc137c6122c65c9dc.tar.gz
libayatana-indicator-a9e097f3f87fa3aac7a06ddbc137c6122c65c9dc.tar.bz2
libayatana-indicator-a9e097f3f87fa3aac7a06ddbc137c6122c65c9dc.zip
* Upstream merge
* Adding new object for parsing desktop files for Shortcuts
-rw-r--r--.bzrignore4
-rw-r--r--configure.ac2
-rw-r--r--debian/changelog7
-rw-r--r--libindicator/Makefile.am2
-rw-r--r--libindicator/indicator-desktop-shortcuts.c523
-rw-r--r--libindicator/indicator-desktop-shortcuts.h74
-rw-r--r--tests/Makefile.am36
-rw-r--r--tests/test-desktop-shortcuts.c137
-rw-r--r--tests/test-well-formed.desktop24
9 files changed, 809 insertions, 0 deletions
diff --git a/.bzrignore b/.bzrignore
index 22df703..5d1c40e 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -147,3 +147,7 @@ libindicator/indicator-object-marshal.c
libindicator/indicator-object-marshal.h
libindicator/libindicator_la-indicator-object-marshal.lo
libindicator/stamp-marshal
+libindicator/libindicator_la-indicator-desktop-shortcuts.lo
+tests/test-desktop-shortcuts
+tests/test-desktop-shortcuts-tester
+tests/test-desktop-shortcuts-touch-test
diff --git a/configure.ac b/configure.ac
index a816206..698f6c7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -28,8 +28,10 @@ m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
GTK_REQUIRED_VERSION=2.18
DBUS_REQUIRED_VERSION=0.76
+GIO_UNIX_REQUIRED_VERSION=2.23
PKG_CHECK_MODULES(LIBINDICATOR, gtk+-2.0 >= $GTK_REQUIRED_VERSION
+ gio-unix-2.0 >= $GIO_UNIX_REQUIRED_VERSION
dbus-glib-1 >= $DBUS_REQUIRED_VERSION)
AC_SUBST(LIBINDICATOR_CFLAGS)
diff --git a/debian/changelog b/debian/changelog
index 70a75d0..f7b15c8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+libindicator (0.3.2-0ubuntu1~ppa2~shortcuts1) UNRELEASED; urgency=low
+
+ * Upstream merge
+ * Adding new object for parsing desktop files for Shortcuts
+
+ -- Ted Gould <ted@ubuntu.com> Tue, 16 Feb 2010 22:50:39 -0600
+
libindicator (0.3.2-0ubuntu1~ppa1) lucid; urgency=low
* Upstream release 0.3.2
diff --git a/libindicator/Makefile.am b/libindicator/Makefile.am
index 19247ce..467e09a 100644
--- a/libindicator/Makefile.am
+++ b/libindicator/Makefile.am
@@ -10,6 +10,7 @@ libindicatorincludedir=$(includedir)/libindicator-0.3/libindicator
indicator_headers = \
indicator.h \
+ indicator-desktop-shortcuts.h \
indicator-object.h \
indicator-service.h \
indicator-service-manager.h
@@ -24,6 +25,7 @@ libindicator_la_SOURCES = \
$(indicator_headers) \
dbus-shared.h \
indicator-object.c \
+ indicator-desktop-shortcuts.c \
indicator-object-marshal.h \
indicator-object-marshal.c \
indicator-service.c \
diff --git a/libindicator/indicator-desktop-shortcuts.c b/libindicator/indicator-desktop-shortcuts.c
new file mode 100644
index 0000000..e86a6ab
--- /dev/null
+++ b/libindicator/indicator-desktop-shortcuts.c
@@ -0,0 +1,523 @@
+/*
+A small file to parse through the actions that are available
+in the desktop file and making those easily usable.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 3.0 as published by the Free Software Foundation.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 3.0 for more details.
+
+You should have received a copy of the GNU General Public
+License along with this library. If not, see
+<http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gio/gdesktopappinfo.h>
+#include "indicator-desktop-shortcuts.h"
+
+#define GROUP_SUFFIX "Shortcut Group"
+#define SHORTCUTS_KEY "X-Ayatana-Desktop-Shortcuts"
+
+#define PROP_DESKTOP_FILE_S "desktop-file"
+#define PROP_IDENTITY_S "identity"
+
+typedef struct _IndicatorDesktopShortcutsPrivate IndicatorDesktopShortcutsPrivate;
+struct _IndicatorDesktopShortcutsPrivate {
+ GKeyFile * keyfile;
+ gchar * identity;
+ GArray * nicks;
+};
+
+enum {
+ PROP_0,
+ PROP_DESKTOP_FILE,
+ PROP_IDENTITY
+};
+
+#define INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsPrivate))
+
+static void indicator_desktop_shortcuts_class_init (IndicatorDesktopShortcutsClass *klass);
+static void indicator_desktop_shortcuts_init (IndicatorDesktopShortcuts *self);
+static void indicator_desktop_shortcuts_dispose (GObject *object);
+static void indicator_desktop_shortcuts_finalize (GObject *object);
+static void set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
+static void get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
+static void parse_keyfile (IndicatorDesktopShortcuts * ids);
+static gboolean should_show (GKeyFile * keyfile, const gchar * group, const gchar * identity);
+
+G_DEFINE_TYPE (IndicatorDesktopShortcuts, indicator_desktop_shortcuts, G_TYPE_OBJECT);
+
+/* Build up the class */
+static void
+indicator_desktop_shortcuts_class_init (IndicatorDesktopShortcutsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (IndicatorDesktopShortcutsPrivate));
+
+ object_class->dispose = indicator_desktop_shortcuts_dispose;
+ object_class->finalize = indicator_desktop_shortcuts_finalize;
+
+ /* Property funcs */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ g_object_class_install_property(object_class, PROP_DESKTOP_FILE,
+ g_param_spec_string(PROP_DESKTOP_FILE_S,
+ "The path of the desktop file to read",
+ "A path to a desktop file that we'll look for shortcuts in.",
+ NULL,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property(object_class, PROP_IDENTITY,
+ g_param_spec_string(PROP_IDENTITY_S,
+ "The string that represents the identity that we're acting as.",
+ "Used to process ShowIn and NotShownIn fields of the desktop shortcust to get the proper list.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+
+ return;
+}
+
+/* Initialize instance data */
+static void
+indicator_desktop_shortcuts_init (IndicatorDesktopShortcuts *self)
+{
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(self);
+
+ priv->keyfile = NULL;
+ priv->identity = NULL;
+ priv->nicks = g_array_new(TRUE, TRUE, sizeof(gchar *));
+
+ return;
+}
+
+/* Clear object references */
+static void
+indicator_desktop_shortcuts_dispose (GObject *object)
+{
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object);
+
+ if (priv->keyfile) {
+ g_key_file_free(priv->keyfile);
+ priv->keyfile = NULL;
+ }
+
+ G_OBJECT_CLASS (indicator_desktop_shortcuts_parent_class)->dispose (object);
+ return;
+}
+
+/* Free all memory */
+static void
+indicator_desktop_shortcuts_finalize (GObject *object)
+{
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object);
+
+ if (priv->identity != NULL) {
+ g_free(priv->identity);
+ priv->identity = NULL;
+ }
+
+ if (priv->nicks != NULL) {
+ gint i;
+ for (i = 0; i < priv->nicks->len; i++) {
+ gchar * nick = g_array_index(priv->nicks, gchar *, i);
+ g_free(nick);
+ }
+ g_array_free(priv->nicks, TRUE);
+ priv->nicks = NULL;
+ }
+
+ G_OBJECT_CLASS (indicator_desktop_shortcuts_parent_class)->finalize (object);
+ return;
+}
+
+/* Sets one of the two properties we have, only at construction though */
+static void
+set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ g_return_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(object));
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object);
+
+ switch(prop_id) {
+ case PROP_DESKTOP_FILE: {
+ GError * error = NULL;
+ GKeyFile * keyfile = g_key_file_new();
+ g_key_file_load_from_file(keyfile, g_value_get_string(value), G_KEY_FILE_NONE, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to load keyfile from file '%s': %s", g_value_get_string(value), error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ break;
+ }
+
+ if (!g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, SHORTCUTS_KEY, NULL)) {
+ g_warning("Keyfile from file '%s' does not have '" SHORTCUTS_KEY "' key", g_value_get_string(value));
+ g_key_file_free(keyfile);
+ break;
+ }
+
+ priv->keyfile = keyfile;
+ parse_keyfile(INDICATOR_DESKTOP_SHORTCUTS(object));
+ break;
+ }
+ case PROP_IDENTITY:
+ if (priv->identity != NULL) {
+ g_warning("Identity already set to '%s' and trying to set it to '%s'.", priv->identity, g_value_get_string(value));
+ return;
+ }
+ priv->identity = g_value_dup_string(value);
+ parse_keyfile(INDICATOR_DESKTOP_SHORTCUTS(object));
+ break;
+ /* *********************** */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Gets either the desktop file our the identity. */
+static void
+get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ g_return_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(object));
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object);
+
+ switch(prop_id) {
+ case PROP_IDENTITY:
+ g_value_set_string(value, priv->identity);
+ break;
+ /* *********************** */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Checks to see if we can, and if we can it goes through
+ and parses the keyfile entries. */
+static void
+parse_keyfile (IndicatorDesktopShortcuts * ids)
+{
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids);
+
+ if (priv->keyfile == NULL) {
+ return;
+ }
+
+ if (priv->identity == NULL) {
+ return;
+ }
+
+ /* Okay, we've got everything we need. Let's get it on! */
+ gint i;
+ gsize num_nicks = 0;
+ gchar ** nicks = g_key_file_get_string_list(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, SHORTCUTS_KEY, &num_nicks, NULL);
+
+ /* If there is an error from get_string_list num_nicks should still
+ be zero, so this loop will drop out. */
+ for (i = 0; i < num_nicks; i++) {
+ /* g_debug("Looking at group nick %s", nicks[i]); */
+ gchar * groupname = g_strdup_printf("%s " GROUP_SUFFIX, nicks[i]);
+ if (!g_key_file_has_group(priv->keyfile, groupname)) {
+ g_warning("Unable to find group '%s'", groupname);
+ g_free(groupname);
+ continue;
+ }
+
+ if (!should_show(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, priv->identity)) {
+ g_free(groupname);
+ continue;
+ }
+
+ if (!should_show(priv->keyfile, groupname, priv->identity)) {
+ g_free(groupname);
+ continue;
+ }
+
+ gchar * nickalloc = g_strdup(nicks[i]);
+ g_array_append_val(priv->nicks, nickalloc);
+ }
+
+ if (nicks != NULL) {
+ g_strfreev(nicks);
+ }
+
+ return;
+}
+
+/* Checks the ONLY_SHOW_IN and NOT_SHOW_IN keys for a group to
+ see if we should be showing ourselves. */
+static gboolean
+should_show (GKeyFile * keyfile, const gchar * group, const gchar * identity)
+{
+ /* If there is a list of OnlyShowIn entries we need to check
+ to see if we're in that list. If not, we drop this nick */
+ if (g_key_file_has_key(keyfile, group, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL)) {
+ gint j;
+ gsize num_only = 0;
+ gchar ** onlies = g_key_file_get_string_list(keyfile, group, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, &num_only, NULL);
+
+ for (j = 0; j < num_only; j++) {
+ if (g_strcmp0(onlies[j], identity) == 0) {
+ break;
+ }
+ }
+
+ if (onlies != NULL) {
+ g_strfreev(onlies);
+ }
+
+ if (j == num_only) {
+ return FALSE;
+ }
+ }
+
+ /* If there is a NotShowIn entry we need to make sure that we're
+ not in that list. If we are, we need to drop out. */
+ if (g_key_file_has_key(keyfile, group, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, NULL)) {
+ gint j;
+ gsize num_not = 0;
+ gchar ** nots = g_key_file_get_string_list(keyfile, group, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, &num_not, NULL);
+
+ for (j = 0; j < num_not; j++) {
+ if (g_strcmp0(nots[j], identity) == 0) {
+ break;
+ }
+ }
+
+ if (nots != NULL) {
+ g_strfreev(nots);
+ }
+
+ if (j != num_not) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* Looks through the nicks ot see if this one is in the list,
+ and thus valid to use. */
+static gboolean
+is_valid_nick (gchar ** list, const gchar * nick)
+{
+ if (*list == NULL)
+ return FALSE;
+ /* g_debug("Checking Nick: %s", list[0]); */
+ if (g_strcmp0(list[0], nick) == 0)
+ return TRUE;
+ return is_valid_nick(&list[1], nick);
+}
+
+/* API */
+
+/**
+ indicator_desktop_shortcuts_new:
+ @file: The desktop file that would be opened to
+ find the actions.
+ @identity: This is a string that represents the identity
+ that should be used in searching those actions. It
+ relates to the ShowIn and NotShownIn properties.
+
+ This function creates the basic object. It involves opening
+ the file and parsing it. It could potentially block on IO. At
+ the end of the day you'll have a fully functional object.
+
+ Return value: A new #IndicatorDesktopShortcuts object.
+*/
+IndicatorDesktopShortcuts *
+indicator_desktop_shortcuts_new (const gchar * file, const gchar * identity)
+{
+ GObject * obj = g_object_new(INDICATOR_TYPE_DESKTOP_SHORTCUTS,
+ PROP_DESKTOP_FILE_S, file,
+ PROP_IDENTITY_S, identity,
+ NULL);
+ return INDICATOR_DESKTOP_SHORTCUTS(obj);
+}
+
+/**
+ indicator_desktop_shortcuts_get_nicks:
+ @ids: The #IndicatorDesktopShortcuts object to look in
+
+ Give you the list of commands that are available for this desktop
+ file given the identity that was passed in at creation. This will
+ filter out the various items in the desktop file. These nicks can
+ then be used as keys for working with the desktop file.
+
+ Return value: A #NULL terminated list of strings. This memory
+ is managed by the @ids object.
+*/
+const gchar **
+indicator_desktop_shortcuts_get_nicks (IndicatorDesktopShortcuts * ids)
+{
+ g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), NULL);
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids);
+ return (const gchar **)priv->nicks->data;
+}
+
+/**
+ indicator_desktop_shortcuts_nick_get_name:
+ @ids: The #IndicatorDesktopShortcuts object to look in
+ @nick: Which command that we're referencing.
+
+ This function looks in a desktop file for a nick to find the
+ user visible name for that shortcut. The @nick parameter
+ should be gotten from #indicator_desktop_shortcuts_get_nicks
+ though it's not required that the exact memory location
+ be the same.
+
+ Return value: A user visible string for the shortcut or
+ #NULL on error.
+*/
+gchar *
+indicator_desktop_shortcuts_nick_get_name (IndicatorDesktopShortcuts * ids, const gchar * nick)
+{
+ g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), NULL);
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids);
+
+ g_return_val_if_fail(priv->keyfile != NULL, NULL);
+ g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), NULL);
+
+ gchar * groupheader = g_strdup_printf("%s " GROUP_SUFFIX, nick);
+ if (!g_key_file_has_group(priv->keyfile, groupheader)) {
+ g_warning("The group for nick '%s' doesn't exist anymore.", nick);
+ g_free(groupheader);
+ return NULL;
+ }
+
+ if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_NAME, NULL)) {
+ g_warning("No name available for nick '%s'", nick);
+ g_free(groupheader);
+ return NULL;
+ }
+
+ gchar * name = g_key_file_get_locale_string(priv->keyfile,
+ groupheader,
+ G_KEY_FILE_DESKTOP_KEY_NAME,
+ NULL,
+ NULL);
+
+ g_free(groupheader);
+
+ return name;
+}
+
+/**
+ indicator_desktop_shortcuts_nick_exec:
+ @ids: The #IndicatorDesktopShortcuts object to look in
+ @nick: Which command that we're referencing.
+
+ Here we take a @nick and try and execute the action that is
+ associated with it. The @nick parameter should be gotten
+ from #indicator_desktop_shortcuts_get_nicks though it's not
+ required that the exact memory location be the same.
+
+ Return value: #TRUE on success or #FALSE on error.
+*/
+gboolean
+indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids, const gchar * nick)
+{
+ GError * error = NULL;
+
+ g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), FALSE);
+ IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids);
+
+ g_return_val_if_fail(priv->keyfile != NULL, FALSE);
+ g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), FALSE);
+
+ gchar * groupheader = g_strdup_printf("%s " GROUP_SUFFIX, nick);
+ if (!g_key_file_has_group(priv->keyfile, groupheader)) {
+ g_warning("The group for nick '%s' doesn't exist anymore.", nick);
+ g_free(groupheader);
+ return FALSE;
+ }
+
+ if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_NAME, NULL)) {
+ g_warning("No name available for nick '%s'", nick);
+ g_free(groupheader);
+ return FALSE;
+ }
+
+ if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL)) {
+ g_warning("No exec available for nick '%s'", nick);
+ g_free(groupheader);
+ return FALSE;
+ }
+
+ /* Grab the name and the exec entries out of our current group */
+ gchar * name = g_key_file_get_locale_string(priv->keyfile,
+ groupheader,
+ G_KEY_FILE_DESKTOP_KEY_NAME,
+ NULL,
+ NULL);
+
+ gchar * exec = g_key_file_get_locale_string(priv->keyfile,
+ groupheader,
+ G_KEY_FILE_DESKTOP_KEY_EXEC,
+ NULL,
+ NULL);
+
+ /* Build a new desktop file with the name and exec in the desktop
+ group. We have to do this with data as apparently there isn't
+ and add_group function in g_key_file. Go figure. */
+ gchar * desktopdata = g_strdup_printf("[" G_KEY_FILE_DESKTOP_GROUP "]\n"
+ G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_APPLICATION "\n"
+ G_KEY_FILE_DESKTOP_KEY_NAME "=%s\n"
+ G_KEY_FILE_DESKTOP_KEY_EXEC "=%s\n",
+ name, exec);
+
+
+ g_free(name); g_free(exec);
+ /* g_debug("Desktop file: \n%s", desktopdata); */
+
+ GKeyFile * launcher = g_key_file_new();
+ g_key_file_load_from_data(launcher, desktopdata, -1, G_KEY_FILE_NONE, &error);
+ g_free(desktopdata);
+
+ if (error != NULL) {
+ g_warning("Unable to build desktop keyfile for executing shortcut '%s': %s", nick, error->message);
+ g_error_free(error);
+ return FALSE;
+ }
+
+ GDesktopAppInfo * appinfo = g_desktop_app_info_new_from_keyfile(launcher);
+ if (appinfo == NULL) {
+ g_warning("Unable to build Desktop App info (unknown)");
+ g_key_file_free(launcher);
+ return FALSE;
+ }
+
+ gboolean launched = g_app_info_launch(G_APP_INFO(appinfo), NULL, NULL, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to launch file from nick '%s': %s", nick, error->message);
+ g_error_free(error);
+ g_key_file_free(launcher);
+ return FALSE;
+ }
+
+ g_object_unref(appinfo);
+ g_key_file_free(launcher);
+
+ return launched;
+}
diff --git a/libindicator/indicator-desktop-shortcuts.h b/libindicator/indicator-desktop-shortcuts.h
new file mode 100644
index 0000000..5ed490e
--- /dev/null
+++ b/libindicator/indicator-desktop-shortcuts.h
@@ -0,0 +1,74 @@
+/*
+A small file to parse through the actions that are available
+in the desktop file and making those easily usable.
+
+Copyright 2010 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 3.0 as published by the Free Software Foundation.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License version 3.0 for more details.
+
+You should have received a copy of the GNU General Public
+License along with this library. If not, see
+<http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __INDICATOR_DESKTOP_SHORTCUTS_H__
+#define __INDICATOR_DESKTOP_SHORTCUTS_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define INDICATOR_TYPE_DESKTOP_SHORTCUTS (indicator_desktop_shortcuts_get_type ())
+#define INDICATOR_DESKTOP_SHORTCUTS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcuts))
+#define INDICATOR_DESKTOP_SHORTCUTS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsClass))
+#define INDICATOR_IS_DESKTOP_SHORTCUTS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS))
+#define INDICATOR_IS_DESKTOP_SHORTCUTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_TYPE_DESKTOP_SHORTCUTS))
+#define INDICATOR_DESKTOP_SHORTCUTS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsClass))
+
+typedef struct _IndicatorDesktopShortcuts IndicatorDesktopShortcuts;
+typedef struct _IndicatorDesktopShortcutsClass IndicatorDesktopShortcutsClass;
+
+/**
+ IndicatorDesktopShortcutsClass:
+ @parent_class: Space for #GObjectClass
+
+ The vtable for our precious #IndicatorDesktopShortcutsClass.
+*/
+struct _IndicatorDesktopShortcutsClass {
+ GObjectClass parent_class;
+};
+
+/**
+ IndicatorDesktopShortcuts:
+ @parent: The parent data from #GObject
+
+ The public data for an instance of the class
+ #IndicatorDesktopShortcuts.
+*/
+struct _IndicatorDesktopShortcuts {
+ GObject parent;
+};
+
+GType indicator_desktop_shortcuts_get_type (void);
+IndicatorDesktopShortcuts * indicator_desktop_shortcuts_new (const gchar * file,
+ const gchar * identity);
+const gchar ** indicator_desktop_shortcuts_get_nicks (IndicatorDesktopShortcuts * ids);
+gchar * indicator_desktop_shortcuts_nick_get_name (IndicatorDesktopShortcuts * ids,
+ const gchar * nick);
+gboolean indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids,
+ const gchar * nick);
+
+G_END_DECLS
+
+#endif
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b111655..cd1a958 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -34,6 +34,42 @@ test_loader_LDADD = \
-lindicator
#############################
+# Test Desktop Shortcuts
+#############################
+
+check_PROGRAMS += test-desktop-shortcuts
+
+test_desktop_shortcuts_SOURCES = \
+ test-desktop-shortcuts.c
+
+test_desktop_shortcuts_CFLAGS = \
+ -Wall -Werror \
+ -DSRCDIR="\"$(srcdir)\"" \
+ $(LIBINDICATOR_CFLAGS) -I$(top_srcdir) \
+ -DBUILD_DIR="\"$(builddir)\""
+
+test_desktop_shortcuts_LDADD = \
+ $(LIBINDICATOR_LIBS) \
+ -L$(top_builddir)/libindicator/.libs \
+ -lindicator
+
+DS_XML_REPORT = desktop-shortcuts-check-results.xml
+DS_HTML_REPORT = desktop-shortcuts-check-results.html
+
+test-desktop-shortcuts-tester: test-desktop-shortcuts Makefile.am
+ @echo "#!/bin/bash" > $@
+ @echo $(XVFB_RUN) >> $@
+ @echo gtester -k --verbose -o=$(XML_REPORT) ./test-desktop-shortcuts >> $@
+ @chmod +x $@
+
+TESTS += test-desktop-shortcuts-tester
+DISTCLEANFILES += test-desktop-shortcuts-tester \
+ test-desktop-shortcuts-touch-test \
+ $(DS_XML_REPORT) \
+ $(DS_HTML_REPORT)
+EXTRA_DIST += test-well-formed.desktop
+
+#############################
# Dummy Indicator Blank
#############################
diff --git a/tests/test-desktop-shortcuts.c b/tests/test-desktop-shortcuts.c
new file mode 100644
index 0000000..1a655f7
--- /dev/null
+++ b/tests/test-desktop-shortcuts.c
@@ -0,0 +1,137 @@
+#include <gtk/gtk.h>
+#include "libindicator/indicator-desktop-shortcuts.h"
+
+/* Basic object creation and destruction. Stop big
+ f*** ups here. */
+void
+test_desktop_shortcuts_creation (void)
+{
+
+ IndicatorDesktopShortcuts * ids = indicator_desktop_shortcuts_new(SRCDIR "/test-well-formed.desktop", "France");
+ g_assert(ids != NULL);
+
+ g_object_add_weak_pointer(G_OBJECT(ids), (gpointer *)&ids);
+ g_object_unref(G_OBJECT(ids));
+
+ g_assert(ids == NULL);
+ return;
+}
+
+/* Tests that the NotShowIn the desktop group is watched
+ for */
+void
+test_desktop_shortcuts_globalnoshow (void)
+{
+
+ IndicatorDesktopShortcuts * ids = indicator_desktop_shortcuts_new(SRCDIR "/test-well-formed.desktop", "Germany");
+ g_assert(ids != NULL);
+
+ const gchar ** nicks = indicator_desktop_shortcuts_get_nicks(ids);
+ g_assert(nicks[0] == NULL);
+
+ g_object_unref(ids);
+
+ return;
+}
+
+gboolean
+nicks_contains (const gchar ** nicks, const gchar * search)
+{
+ if (nicks[0] == NULL)
+ return FALSE;
+ if (g_strcmp0(nicks[0], search) == 0)
+ return TRUE;
+ return nicks_contains(&nicks[1], search);
+}
+
+/* Checking that the local show OnlyIn works. */
+void
+test_desktop_shortcuts_localfilter (void)
+{
+ IndicatorDesktopShortcuts * ids = indicator_desktop_shortcuts_new(SRCDIR "/test-well-formed.desktop", "France");
+ g_assert(ids != NULL);
+
+ const gchar ** nicks = indicator_desktop_shortcuts_get_nicks(ids);
+
+ g_assert(nicks_contains(nicks, "bob"));
+ g_assert(nicks_contains(nicks, "alvin"));
+ g_assert(!nicks_contains(nicks, "jim"));
+
+ g_object_unref(ids);
+
+ return;
+}
+
+/* Nick names -- checks to see they all have names */
+void
+test_desktop_shortcuts_nicknames (void)
+{
+ IndicatorDesktopShortcuts * ids = indicator_desktop_shortcuts_new(SRCDIR "/test-well-formed.desktop", "France");
+ g_assert(ids != NULL);
+
+ const gchar ** nicks = indicator_desktop_shortcuts_get_nicks(ids);
+ gint i = 0;
+ while (nicks[i] != NULL) {
+ gchar * expectedstr = g_strdup_printf("%s's shortcut", nicks[i]);
+ gchar * name = indicator_desktop_shortcuts_nick_get_name(ids, nicks[i]);
+ g_assert(name != NULL);
+
+ gboolean same = (g_strcmp0(expectedstr, name) == 0);
+
+ g_free(name);
+ g_free(expectedstr);
+
+ g_assert(same);
+
+ i++;
+ }
+
+
+ g_object_unref(ids);
+
+ return;
+}
+
+/* Try executing a shortcut which will touch a file */
+void
+test_desktop_shortcuts_launch (void)
+{
+ IndicatorDesktopShortcuts * ids = indicator_desktop_shortcuts_new(SRCDIR "/test-well-formed.desktop", "TouchTest");
+ g_assert(ids != NULL);
+
+ const gchar ** nicks = indicator_desktop_shortcuts_get_nicks(ids);
+ g_assert(nicks_contains(nicks, "touch"));
+
+ g_assert(indicator_desktop_shortcuts_nick_exec(ids, "touch"));
+ g_assert(g_file_test(BUILD_DIR "/test-desktop-shortcuts-touch-test", G_FILE_TEST_EXISTS));
+
+ g_object_unref(ids);
+
+ return;
+}
+
+/* Build our test suite */
+void
+test_desktop_shortcuts_suite (void)
+{
+ g_test_add_func ("/libindicator/desktopshortcuts/creation", test_desktop_shortcuts_creation);
+ g_test_add_func ("/libindicator/desktopshortcuts/globalnosho", test_desktop_shortcuts_globalnoshow);
+ g_test_add_func ("/libindicator/desktopshortcuts/nicknames", test_desktop_shortcuts_nicknames);
+ g_test_add_func ("/libindicator/desktopshortcuts/launch", test_desktop_shortcuts_launch);
+
+ return;
+}
+
+int
+main (int argc, char ** argv)
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+ gtk_init(&argc, &argv);
+
+ test_desktop_shortcuts_suite();
+
+ g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL);
+
+ return g_test_run();
+}
diff --git a/tests/test-well-formed.desktop b/tests/test-well-formed.desktop
new file mode 100644
index 0000000..84e2e02
--- /dev/null
+++ b/tests/test-well-formed.desktop
@@ -0,0 +1,24 @@
+[Desktop Entry]
+Name=My Application
+Exec=ls
+NotShowIn=Germany
+X-Ayatana-Desktop-Shortcuts=bob;alvin;jim;touch
+
+[bob Shortcut Group]
+Name=bob's shortcut
+Exec=ls bob
+
+[alvin Shortcut Group]
+Name=alvin's shortcut
+Exec=ls alvin
+OnlyShowIn=France
+
+[jim Shortcut Group]
+Name=Jim's shortcut
+Exec=ls jim
+NotShowIn=France
+
+[touch Shortcut Group]
+Name=Touch Test
+Exec=touch test-desktop-shortcuts-touch-test
+OnlyShowIn=TouchTest