aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMike Gabriel <mike.gabriel@das-netzwerkteam.de>2021-05-12 13:07:56 +0200
committerMike Gabriel <mike.gabriel@das-netzwerkteam.de>2021-05-12 13:07:56 +0200
commit025c376068f63d8c2aa5899a3c0b1b827370da28 (patch)
tree99d5ce170fa25fd17f5d4569e8b3b3656c9fe44b /src
parentaa6723b9cc3dcac5b9bdd87944a8c99b74f16ba2 (diff)
parentc277aa68455b232c37a4c52b39e948a6ab0b0e11 (diff)
downloadlibayatana-indicator-025c376068f63d8c2aa5899a3c0b1b827370da28.tar.gz
libayatana-indicator-025c376068f63d8c2aa5899a3c0b1b827370da28.tar.bz2
libayatana-indicator-025c376068f63d8c2aa5899a3c0b1b827370da28.zip
Merge branch 'tari01-pr/move-to-cmake'
Attributes GH PR #38: https://github.com/AyatanaIndicators/libayatana-indicator/pull/38
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt127
-rw-r--r--src/dbus-shared.h28
-rw-r--r--src/indicator-desktop-shortcuts.c682
-rw-r--r--src/indicator-desktop-shortcuts.h80
-rw-r--r--src/indicator-image-helper.c218
-rw-r--r--src/indicator-image-helper.h35
-rw-r--r--src/indicator-ng.c1031
-rw-r--r--src/indicator-ng.h48
-rw-r--r--src/indicator-object-enum-types.c.template30
-rw-r--r--src/indicator-object-enum-types.h.template27
-rw-r--r--src/indicator-object-marshal.list4
-rw-r--r--src/indicator-object.c967
-rw-r--r--src/indicator-object.h216
-rw-r--r--src/indicator-service-manager.c704
-rw-r--r--src/indicator-service-manager.h88
-rw-r--r--src/indicator-service.c649
-rw-r--r--src/indicator-service.h85
-rw-r--r--src/indicator-service.xml22
-rw-r--r--src/indicator.h41
19 files changed, 5082 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..d6c6647
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,127 @@
+# indicator-desktop-shortcuts.h
+# indicator-image-helper.h
+# indicator-ng.h
+# indicator-object.h
+# indicator-service-manager.h
+# indicator-service.h
+# indicator.h
+
+install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/indicator-desktop-shortcuts.h" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/libayatana-indicator3-0.4/libayatana-indicator")
+install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/indicator-image-helper.h" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/libayatana-indicator3-0.4/libayatana-indicator")
+install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/indicator-ng.h" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/libayatana-indicator3-0.4/libayatana-indicator")
+install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/indicator-object.h" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/libayatana-indicator3-0.4/libayatana-indicator")
+install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/indicator-service-manager.h" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/libayatana-indicator3-0.4/libayatana-indicator")
+install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/indicator-service.h" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/libayatana-indicator3-0.4/libayatana-indicator")
+install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/indicator.h" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/libayatana-indicator3-0.4/libayatana-indicator")
+
+set(HEADERS
+ indicator.h
+ indicator-desktop-shortcuts.h
+ indicator-image-helper.h
+ indicator-object.h
+ indicator-service.h
+ indicator-service-manager.h
+ indicator-ng.h
+)
+
+set(SOURCES
+ gen-indicator-service.xml.c
+ indicator-desktop-shortcuts.c
+ indicator-image-helper.c
+ indicator-ng.c
+ indicator-object.c
+ indicator-object-enum-types.c
+ indicator-object-marshal.c
+ indicator-service.c
+ indicator-service-manager.c
+
+)
+
+# indicator-object-enum-types.h
+
+find_program(GLIB_MKENUMS glib-mkenums)
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/indicator-object-enum-types.h"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND
+ ${GLIB_MKENUMS}
+ --template indicator-object-enum-types.h.template
+ ${HEADERS}
+ --output="${CMAKE_CURRENT_BINARY_DIR}/indicator-object-enum-types.h"
+)
+
+# indicator-object-enum-types.c
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/indicator-object-enum-types.c"
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/indicator-object-enum-types.h"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND
+ ${GLIB_MKENUMS}
+ --template indicator-object-enum-types.c.template
+ ${HEADERS}
+ --output="${CMAKE_CURRENT_BINARY_DIR}/indicator-object-enum-types.c"
+)
+
+find_program(GLIB_GENMARSHAL glib-genmarshal)
+
+# indicator-object-marshal.h
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/indicator-object-marshal.h"
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/indicator-object-enum-types.c"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND
+ ${GLIB_GENMARSHAL}
+ --prefix=_indicator_object_marshal
+ --header indicator-object-marshal.list
+ --quiet
+ --output="${CMAKE_CURRENT_BINARY_DIR}/indicator-object-marshal.h"
+)
+
+# indicator-object-marshal.c
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/indicator-object-marshal.c"
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/indicator-object-marshal.h"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND
+ ${GLIB_GENMARSHAL}
+ --prefix=_indicator_object_marshal
+ --body indicator-object-marshal.list
+ --include-header=indicator-object-marshal.h
+ --quiet
+ --output="${CMAKE_CURRENT_BINARY_DIR}/indicator-object-marshal.c"
+)
+
+add_custom_target("src-generated" DEPENDS "indicator-object-marshal.c")
+
+# gen-indicator-service.xml.h
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/gen-indicator-service.xml.h" "extern const char * _indicator_service;")
+
+# gen-indicator-service.xml.c
+
+file(READ "${CMAKE_CURRENT_SOURCE_DIR}/indicator-service.xml" GEN_INDICATOR_SERVICE_XML_C)
+string(REPLACE "\"" "\\\"" GEN_INDICATOR_SERVICE_XML_C ${GEN_INDICATOR_SERVICE_XML_C})
+string(REPLACE "\n" "\\n\"\n\"" GEN_INDICATOR_SERVICE_XML_C ${GEN_INDICATOR_SERVICE_XML_C})
+string(REGEX REPLACE "\n\"$" "\n" GEN_INDICATOR_SERVICE_XML_C ${GEN_INDICATOR_SERVICE_XML_C})
+string(PREPEND GEN_INDICATOR_SERVICE_XML_C "const char * _indicator_service = \n\"")
+string(APPEND GEN_INDICATOR_SERVICE_XML_C "\;")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/gen-indicator-service.xml.c" ${GEN_INDICATOR_SERVICE_XML_C})
+
+# libayatana-indicator3.so
+
+set_source_files_properties(${SOURCES} PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS})
+add_library("ayatana-indicator3" SHARED ${SOURCES})
+set_target_properties("ayatana-indicator3" PROPERTIES VERSION 7.0.0 SOVERSION 7)
+target_compile_definitions("ayatana-indicator3" PUBLIC DG_LOG_DOMAIN="libayatana-indicator")
+target_include_directories("ayatana-indicator3" PUBLIC ${PROJECT_DEPS_INCLUDE_DIRS})
+target_include_directories("ayatana-indicator3" PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+target_include_directories("ayatana-indicator3" PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
+target_link_libraries("ayatana-indicator3" ${PROJECT_DEPS_LIBRARIES})
+add_dependencies("ayatana-indicator3" "src-generated")
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libayatana-indicator3.so" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libayatana-indicator3.so.7" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libayatana-indicator3.so.7.0.0" DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
diff --git a/src/dbus-shared.h b/src/dbus-shared.h
new file mode 100644
index 0000000..b55acef
--- /dev/null
+++ b/src/dbus-shared.h
@@ -0,0 +1,28 @@
+/*
+Shared defines for DBus interfaces and API versions to
+make sure the server and client agree.
+
+Copyright 2009 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/>.
+*/
+
+#define INDICATOR_SERVICE_INTERFACE "org.ayatana.indicator.service"
+#define INDICATOR_SERVICE_OBJECT "/org/ayatana/indicator/service"
+
+#define INDICATOR_SERVICE_VERSION 1
+
diff --git a/src/indicator-desktop-shortcuts.c b/src/indicator-desktop-shortcuts.c
new file mode 100644
index 0000000..be1d632
--- /dev/null
+++ b/src/indicator-desktop-shortcuts.c
@@ -0,0 +1,682 @@
+/*
+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 ACTIONS_KEY "Actions"
+#define ACTION_GROUP_PREFIX "Desktop Action"
+
+#define OLD_GROUP_SUFFIX "Shortcut Group"
+#define OLD_SHORTCUTS_KEY "X-Ayatana-Desktop-Shortcuts"
+#define OLD_ENVIRON_KEY "TargetEnvironment"
+
+#define PROP_DESKTOP_FILE_S "desktop-file"
+#define PROP_IDENTITY_S "identity"
+
+typedef enum _actions_t actions_t;
+enum _actions_t {
+ ACTIONS_NONE,
+ ACTIONS_XAYATANA,
+ ACTIONS_DESKTOP_SPEC
+};
+
+typedef struct {
+ actions_t actions;
+ GKeyFile * keyfile;
+ gchar * identity;
+ GArray * nicks;
+ gchar * domain;
+} IndicatorDesktopShortcutsPrivate;
+
+enum {
+ PROP_0,
+ PROP_DESKTOP_FILE,
+ PROP_IDENTITY
+};
+
+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, gboolean should_have_target);
+
+G_DEFINE_TYPE_WITH_PRIVATE (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);
+
+ 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_instance_private(self);
+
+ priv->keyfile = NULL;
+ priv->identity = NULL;
+ priv->domain = NULL;
+ priv->nicks = g_array_new(TRUE, TRUE, sizeof(gchar *));
+ priv->actions = ACTIONS_NONE;
+
+ return;
+}
+
+/* Clear object references */
+static void
+indicator_desktop_shortcuts_dispose (GObject *object)
+{
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(object);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+
+ 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)
+{
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(object);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+
+ if (priv->identity != NULL) {
+ g_free(priv->identity);
+ priv->identity = NULL;
+ }
+
+ if (priv->domain != NULL) {
+ g_free(priv->domain);
+ priv->domain = NULL;
+ }
+
+ if (priv->nicks != NULL) {
+ guint 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));
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(object);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+
+ switch(prop_id) {
+ case PROP_DESKTOP_FILE: {
+ if (priv->keyfile != NULL) {
+ g_key_file_free(priv->keyfile);
+ priv->keyfile = NULL;
+ priv->actions = ACTIONS_NONE;
+ }
+
+ 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;
+ }
+
+ /* Always prefer the desktop spec if we can get it */
+ if (priv->actions == ACTIONS_NONE && g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, ACTIONS_KEY, NULL)) {
+ priv->actions = ACTIONS_DESKTOP_SPEC;
+ }
+
+ /* But fallback if we can't */
+ if (priv->actions == ACTIONS_NONE && g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, OLD_SHORTCUTS_KEY, NULL)) {
+ priv->actions = ACTIONS_XAYATANA;
+ g_warning("Desktop file '%s' is using a deprecated format for its actions that will be dropped soon.", g_value_get_string(value));
+ }
+
+ if (priv->actions == ACTIONS_NONE) {
+ 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));
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(object);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+
+ 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)
+{
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(ids);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+
+ if (priv->keyfile == NULL) {
+ return;
+ }
+
+ if (priv->identity == NULL) {
+ return;
+ }
+
+ /* Remove a previous translation domain if we had one
+ from a previously parsed file. */
+ if (priv->domain != NULL) {
+ g_free(priv->domain);
+ priv->domain = NULL;
+ }
+
+ /* Check to see if there is a custom translation domain that
+ we should take into account. */
+ if (priv->domain == NULL &&
+ g_key_file_has_key(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL)) {
+ priv->domain = g_key_file_get_string(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL);
+ }
+
+ if (priv->domain == NULL &&
+ g_key_file_has_key(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL)) {
+ priv->domain = g_key_file_get_string(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL);
+ }
+
+ /* We need to figure out what we're looking for and what we want to
+ look for in the rest of the file */
+ const gchar * list_name = NULL;
+ const gchar * group_format = NULL;
+ gboolean should_have_target = FALSE;
+
+ switch (priv->actions) {
+ case ACTIONS_NONE:
+ /* None, let's just get outta here */
+ return;
+ case ACTIONS_XAYATANA:
+ list_name = OLD_SHORTCUTS_KEY;
+ group_format = "%s " OLD_GROUP_SUFFIX;
+ should_have_target = TRUE;
+ break;
+ case ACTIONS_DESKTOP_SPEC:
+ list_name = ACTIONS_KEY;
+ group_format = ACTION_GROUP_PREFIX " %s";
+ should_have_target = FALSE;
+ break;
+ default:
+ g_assert_not_reached();
+ return;
+ }
+
+ /* Okay, we've got everything we need. Let's get it on! */
+ gsize i;
+ gsize num_nicks = 0;
+ gchar ** nicks = g_key_file_get_string_list(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, list_name, &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(group_format, 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, FALSE)) {
+ g_free(groupname);
+ continue;
+ }
+
+ if (!should_show(priv->keyfile, groupname, priv->identity, should_have_target)) {
+ g_free(groupname);
+ continue;
+ }
+
+ gchar * nickalloc = g_strdup(nicks[i]);
+ g_array_append_val(priv->nicks, nickalloc);
+ g_free(groupname);
+ }
+
+ 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, gboolean should_have_target)
+{
+ if (should_have_target && g_key_file_has_key(keyfile, group, OLD_ENVIRON_KEY, NULL)) {
+ /* If we've got this key, we're going to return here and not
+ process the deprecated keys. */
+ gsize j;
+ gsize num_env = 0;
+ gchar ** envs = g_key_file_get_string_list(keyfile, group, OLD_ENVIRON_KEY, &num_env, NULL);
+
+ for (j = 0; j < num_env; j++) {
+ if (g_strcmp0(envs[j], identity) == 0) {
+ break;
+ }
+ }
+
+ if (envs != NULL) {
+ g_strfreev(envs);
+ }
+
+ if (j == num_env) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /* 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)) {
+ gsize 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)) {
+ gsize 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 to 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);
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(ids);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+ 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);
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(ids);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+
+ g_return_val_if_fail(priv->actions != ACTIONS_NONE, NULL);
+ g_return_val_if_fail(priv->keyfile != NULL, NULL);
+ g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), NULL);
+
+ const gchar * group_format = NULL;
+
+ switch (priv->actions) {
+ case ACTIONS_XAYATANA:
+ group_format = "%s " OLD_GROUP_SUFFIX;
+ break;
+ case ACTIONS_DESKTOP_SPEC:
+ group_format = ACTION_GROUP_PREFIX " %s";
+ break;
+ default:
+ g_assert_not_reached();
+ return NULL;
+ }
+
+ gchar * groupheader = g_strdup_printf(group_format, 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 = NULL;
+ gchar * keyvalue = g_key_file_get_string(priv->keyfile,
+ groupheader,
+ G_KEY_FILE_DESKTOP_KEY_NAME,
+ NULL);
+ gchar * localeval = g_key_file_get_locale_string(priv->keyfile,
+ groupheader,
+ G_KEY_FILE_DESKTOP_KEY_NAME,
+ NULL,
+ NULL);
+ g_free(groupheader);
+
+ if (priv->domain != NULL && g_strcmp0(keyvalue, localeval) == 0) {
+ name = g_strdup(g_dgettext(priv->domain, keyvalue));
+ g_free(localeval);
+ } else {
+ name = localeval;
+ }
+
+ g_free(keyvalue);
+
+ return name;
+}
+
+/**
+ indicator_desktop_shortcuts_nick_exec_with_context:
+ @ids: The #IndicatorDesktopShortcuts object to look in
+ @nick: Which command that we're referencing.
+ @launch_context: The #GAppLaunchContext to use for launching the shortcut
+
+ 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_with_context (IndicatorDesktopShortcuts * ids, const gchar * nick, GAppLaunchContext * launch_context)
+{
+ GError * error = NULL;
+
+ g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), FALSE);
+ IndicatorDesktopShortcuts * self = INDICATOR_DESKTOP_SHORTCUTS(ids);
+ IndicatorDesktopShortcutsPrivate * priv = indicator_desktop_shortcuts_get_instance_private(self);
+
+ g_return_val_if_fail(priv->actions != ACTIONS_NONE, FALSE);
+ g_return_val_if_fail(priv->keyfile != NULL, FALSE);
+ g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), FALSE);
+
+ const gchar * group_format = NULL;
+
+ switch (priv->actions) {
+ case ACTIONS_XAYATANA:
+ group_format = "%s " OLD_GROUP_SUFFIX;
+ break;
+ case ACTIONS_DESKTOP_SPEC:
+ group_format = ACTION_GROUP_PREFIX " %s";
+ break;
+ default:
+ g_assert_not_reached();
+ return FALSE;
+ }
+
+ gchar * groupheader = g_strdup_printf(group_format, 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);
+
+ g_free(groupheader);
+
+ GAppInfoCreateFlags flags = G_APP_INFO_CREATE_NONE;
+
+ if (launch_context) {
+ flags |= G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION;
+ }
+
+ GAppInfo * appinfo = g_app_info_create_from_commandline(exec, name, flags, &error);
+ g_free(name); g_free(exec);
+
+ if (error != NULL) {
+ g_warning("Unable to build Command line App info: %s", error->message);
+ g_error_free(error);
+ return FALSE;
+ }
+
+ if (appinfo == NULL) {
+ g_warning("Unable to build Command line App info (unknown)");
+ return FALSE;
+ }
+
+ gboolean launched = g_app_info_launch(appinfo, NULL, launch_context, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to launch file from nick '%s': %s", nick, error->message);
+ g_clear_error(&error);
+ }
+
+ g_object_unref(appinfo);
+
+ return launched;
+}
+
+/**
+ 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.
+ This function is deprecated and shouldn't be used in newly
+ written code.
+
+ Return value: #TRUE on success or #FALSE on error.
+*/
+gboolean
+indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids, const gchar * nick)
+{
+ return indicator_desktop_shortcuts_nick_exec_with_context (ids, nick, NULL);
+}
diff --git a/src/indicator-desktop-shortcuts.h b/src/indicator-desktop-shortcuts.h
new file mode 100644
index 0000000..fb997ea
--- /dev/null
+++ b/src/indicator-desktop-shortcuts.h
@@ -0,0 +1,80 @@
+/*
+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 <gio/gio.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_with_context (IndicatorDesktopShortcuts * ids,
+ const gchar * nick,
+ GAppLaunchContext * launch_context);
+
+GLIB_DEPRECATED_FOR(indicator_desktop_shortcuts_nick_exec_with_context)
+gboolean indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids,
+ const gchar * nick);
+
+G_END_DECLS
+
+#endif
diff --git a/src/indicator-image-helper.c b/src/indicator-image-helper.c
new file mode 100644
index 0000000..2c0e244
--- /dev/null
+++ b/src/indicator-image-helper.c
@@ -0,0 +1,218 @@
+/*
+A little helper to make a themed image with fallbacks that
+is only constrained in the vertical dimention.
+
+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/>.
+*/
+
+#include <math.h>
+#include "indicator-image-helper.h"
+
+const gchar * INDICATOR_NAMES_DATA = "indicator-names-data";
+const gint ICON_SIZE = 22;
+
+static void
+refresh_image (GtkImage * image)
+{
+ g_return_if_fail(GTK_IS_IMAGE(image));
+ const gchar * icon_filename = NULL;
+ GtkIconInfo * icon_info = NULL;
+
+ GIcon * icon_names = (GIcon *)g_object_get_data(G_OBJECT(image), INDICATOR_NAMES_DATA);
+ g_return_if_fail(G_IS_ICON (icon_names));
+
+ /* Get the default theme */
+ GtkIconTheme * default_theme = gtk_icon_theme_get_default();
+ g_return_if_fail(default_theme != NULL);
+
+ /* Look through the themes for that icon */
+ icon_info = gtk_icon_theme_lookup_by_gicon(default_theme, icon_names, ICON_SIZE, 0);
+ if (icon_info == NULL) {
+ /* Maybe the icon was just added to the theme, see if a rescan helps */
+ gtk_icon_theme_rescan_if_needed(default_theme);
+ icon_info = gtk_icon_theme_lookup_by_gicon(default_theme, icon_names, ICON_SIZE, 0);
+ }
+ if (icon_info == NULL) {
+ /* Try using the second item in the names, which should be the original filename supplied */
+ const gchar * const * names = g_themed_icon_get_names(G_THEMED_ICON( icon_names ));
+ if (names) {
+ icon_filename = names[1];
+ } else {
+ g_warning("Unable to find icon\n");
+ gtk_image_clear(image);
+ return;
+ }
+ } else {
+ /* Grab the filename */
+ icon_filename = gtk_icon_info_get_filename(icon_info);
+ }
+
+ if (icon_filename == NULL && !G_IS_BYTES_ICON(icon_names)) {
+ /* show a broken image if we don't have a filename or image data */
+ gtk_image_set_from_icon_name(image, "image-missing", GTK_ICON_SIZE_LARGE_TOOLBAR);
+ return;
+ }
+
+ if (icon_info != NULL && !G_IS_BYTES_ICON(icon_names)) {
+ GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon_info, NULL);
+
+ if (gdk_pixbuf_get_height(pixbuf) < ICON_SIZE) {
+ gtk_image_set_from_file(image, icon_filename);
+ } else {
+ gtk_image_set_from_gicon(image, icon_names, GTK_ICON_SIZE_LARGE_TOOLBAR);
+ }
+ g_object_unref (pixbuf);
+ } else if (icon_filename != NULL) {
+ gtk_image_set_from_file(image, icon_filename);
+
+ gint height;
+ gdk_pixbuf_get_file_info(icon_filename, NULL, &height);
+
+ if (height > ICON_SIZE) {
+ gtk_image_set_pixel_size(image, ICON_SIZE);
+ }
+ } else if (G_IS_LOADABLE_ICON(icon_names)) {
+ /* Build a pixbuf if needed */
+ GdkPixbuf * pixbuf = NULL;
+ GError * error = NULL;
+ GInputStream * stream = g_loadable_icon_load(G_LOADABLE_ICON(icon_names), ICON_SIZE, NULL, NULL, &error);
+
+ if (stream != NULL) {
+ pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, &error);
+ g_input_stream_close (stream, NULL, NULL);
+ g_object_unref (stream);
+
+ if (pixbuf != NULL) {
+ /* Scale icon if all we get is something too big. */
+ if (gdk_pixbuf_get_height(pixbuf) > ICON_SIZE) {
+ gfloat scale = (gfloat)ICON_SIZE / (gfloat)gdk_pixbuf_get_height(pixbuf);
+ gint width = round(gdk_pixbuf_get_width(pixbuf) * scale);
+
+ GdkPixbuf * scaled = gdk_pixbuf_scale_simple(pixbuf, width, ICON_SIZE, GDK_INTERP_BILINEAR);
+ g_object_unref(G_OBJECT(pixbuf));
+ pixbuf = scaled;
+ }
+
+ /* Put the pixbuf on the image */
+ gtk_image_set_from_pixbuf(image, pixbuf);
+ g_object_unref(G_OBJECT(pixbuf));
+ } else {
+ g_warning ("Unable to load icon from data: %s", error->message);
+ g_error_free (error);
+ }
+ } else {
+ g_warning ("Unable to load icon from data: %s", error->message);
+ g_error_free (error);
+ }
+ }
+
+ if (icon_info != NULL) {
+#if GTK_CHECK_VERSION(3, 8, 0)
+ g_object_unref(icon_info);
+#else
+ /* NOTE: Leaving this in for lower version as it seems
+ the object_unref() doesn't work on earlier versions. */
+ gtk_icon_info_free (icon_info);
+#endif
+ }
+}
+
+/* Handles the theme changed signal to refresh the icon to make
+ sure that it changes appropriately */
+static void
+theme_changed_cb (__attribute__((unused)) GtkIconTheme * theme, gpointer user_data)
+{
+ GtkImage * image = GTK_IMAGE(user_data);
+ refresh_image(image);
+ return;
+}
+
+/* Removes the signal on the theme that was calling update on this
+ image. */
+static void
+image_destroyed_cb (GtkImage * image, __attribute__((unused)) gpointer user_data)
+{
+ g_signal_handlers_disconnect_by_func(gtk_icon_theme_get_default(), theme_changed_cb, image);
+ return;
+}
+
+/* Catch the style changing on the image to make sure
+ we've got the latest. */
+static void
+image_style_change_cb (GtkImage * image, __attribute__((unused)) GtkStyle * previous_style, __attribute__((unused)) gpointer user_data)
+{
+ refresh_image(image);
+ return;
+}
+
+/* Builds an image with the name and fallbacks and all kinds of fun
+ stuff . */
+GtkImage *
+indicator_image_helper (const gchar * name)
+{
+ /* Build us an image */
+ GtkImage * image = GTK_IMAGE(gtk_image_new());
+
+ if (name)
+ indicator_image_helper_update(image, name);
+
+ return image;
+}
+
+/* Updates and image with all the fun stuff */
+void
+indicator_image_helper_update (GtkImage * image, const gchar * name)
+{
+ g_return_if_fail(name != NULL);
+ g_return_if_fail(name[0] != '\0');
+ g_return_if_fail(GTK_IS_IMAGE(image));
+
+ /* Build us a GIcon */
+ GIcon * icon_names = g_themed_icon_new_with_default_fallbacks(name);
+ g_warn_if_fail(icon_names != NULL);
+ g_return_if_fail(icon_names != NULL);
+
+ indicator_image_helper_update_from_gicon (image, icon_names);
+
+ g_object_unref (icon_names);
+ return;
+}
+
+void
+indicator_image_helper_update_from_gicon (GtkImage *image, GIcon *icon)
+{
+ gboolean seen_previously = FALSE;
+
+ seen_previously = (g_object_get_data(G_OBJECT(image), INDICATOR_NAMES_DATA) != NULL);
+
+ /* Attach our names to the image */
+ g_object_set_data_full(G_OBJECT(image), INDICATOR_NAMES_DATA, g_object_ref (icon), g_object_unref);
+
+ /* Put the pixbuf in */
+ refresh_image(image);
+
+ /* Connect to all changes */
+ if (!seen_previously) {
+ g_signal_connect(G_OBJECT(gtk_icon_theme_get_default()), "changed", G_CALLBACK(theme_changed_cb), image);
+ g_signal_connect(G_OBJECT(image), "destroy", G_CALLBACK(image_destroyed_cb), NULL);
+ g_signal_connect(G_OBJECT(image), "style-set", G_CALLBACK(image_style_change_cb), NULL);
+ }
+
+ return;
+}
diff --git a/src/indicator-image-helper.h b/src/indicator-image-helper.h
new file mode 100644
index 0000000..290f4e2
--- /dev/null
+++ b/src/indicator-image-helper.h
@@ -0,0 +1,35 @@
+/*
+A little helper to make a themed image with fallbacks that
+is only constrained in the vertical dimention.
+
+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_IMAGE_HELPER_H__
+#define __INDICATOR_IMAGE_HELPER_H__
+
+#include <gtk/gtk.h>
+
+GtkImage * indicator_image_helper (const gchar * name);
+void indicator_image_helper_update (GtkImage * image,
+ const gchar * name);
+void indicator_image_helper_update_from_gicon (GtkImage * image,
+ GIcon * icon);
+
+#endif /* __INDICATOR_IMAGE_HELPER_H__ */
diff --git a/src/indicator-ng.c b/src/indicator-ng.c
new file mode 100644
index 0000000..f057600
--- /dev/null
+++ b/src/indicator-ng.c
@@ -0,0 +1,1031 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * 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/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include "indicator-ng.h"
+#include "indicator-image-helper.h"
+#include <libayatana-ido/ayatanamenuitemfactory.h>
+#include <string.h>
+
+#define MENU_SECTIONS 20
+
+struct _IndicatorNg
+{
+ IndicatorObject parent;
+
+ gchar *service_file;
+ gchar *name;
+ gchar *object_path;
+ gchar *menu_object_path;
+ gchar *bus_name;
+ gchar *profile;
+ gchar *header_action;
+ gchar *scroll_action;
+ gchar *secondary_action;
+ gchar *submenu_action;
+ gint position;
+
+ guint name_watch_id;
+
+ GDBusConnection *session_bus;
+ GActionGroup *actions;
+ GMenuModel *menu;
+
+ IndicatorObjectEntry entry;
+ gchar *accessible_desc;
+
+ gint64 last_service_restart;
+ GMenuModel *lMenuSections[MENU_SECTIONS];
+};
+
+static void indicator_ng_initable_iface_init (GInitableIface *initable);
+G_DEFINE_TYPE_WITH_CODE (IndicatorNg, indicator_ng, INDICATOR_OBJECT_TYPE,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, indicator_ng_initable_iface_init))
+
+enum
+{
+ PROP_0,
+ PROP_SERVICE_FILE,
+ PROP_PROFILE,
+ N_PROPERTIES
+};
+
+static GQuark m_pActionMuxer = 0;
+static GParamSpec *properties[N_PROPERTIES];
+
+static void
+indicator_ng_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IndicatorNg *self = INDICATOR_NG (object);
+
+ switch (property_id)
+ {
+ case PROP_SERVICE_FILE:
+ g_value_set_string (value, self->service_file);
+ break;
+
+ case PROP_PROFILE:
+ g_value_set_string (value, self->profile);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+indicator_ng_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IndicatorNg *self = INDICATOR_NG (object);
+
+ switch (property_id)
+ {
+ case PROP_SERVICE_FILE: /* construct-only */
+ self->service_file = g_strdup (g_value_get_string (value));
+ break;
+
+ case PROP_PROFILE: /* construct-only */
+ self->profile = g_strdup (g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+indicator_ng_free_actions_and_menu (IndicatorNg *self)
+{
+ if (self->actions)
+ {
+ gtk_widget_insert_action_group (GTK_WIDGET (self->entry.menu), "indicator", NULL);
+ g_signal_handlers_disconnect_by_data (self->actions, self);
+ g_clear_object (&self->actions);
+ }
+
+ if (self->menu)
+ {
+ for (guint nMenuSection = 0; nMenuSection < MENU_SECTIONS; nMenuSection++)
+ {
+ if (self->lMenuSections[nMenuSection])
+ {
+ g_object_unref(self->lMenuSections[nMenuSection]);
+ self->lMenuSections[nMenuSection] = NULL;
+ }
+ }
+
+ g_signal_handlers_disconnect_by_data (self->menu, self);
+ g_clear_object (&self->menu);
+ }
+}
+
+static void
+indicator_ng_dispose (GObject *object)
+{
+ IndicatorNg *self = INDICATOR_NG (object);
+
+ if (self->name_watch_id)
+ {
+ g_bus_unwatch_name (self->name_watch_id);
+ self->name_watch_id = 0;
+ }
+
+ g_clear_object (&self->session_bus);
+
+ indicator_ng_free_actions_and_menu (self);
+
+ g_clear_object (&self->entry.label);
+ g_clear_object (&self->entry.image);
+ g_clear_object (&self->entry.menu);
+
+ G_OBJECT_CLASS (indicator_ng_parent_class)->dispose (object);
+}
+
+static void
+indicator_ng_finalize (GObject *object)
+{
+ IndicatorNg *self = INDICATOR_NG (object);
+
+ g_free (self->service_file);
+ g_free (self->name);
+ g_free (self->object_path);
+ g_free (self->menu_object_path);
+ g_free (self->bus_name);
+ g_free (self->accessible_desc);
+ g_free (self->header_action);
+ g_free (self->scroll_action);
+ g_free (self->secondary_action);
+ g_free (self->submenu_action);
+
+ G_OBJECT_CLASS (indicator_ng_parent_class)->finalize (object);
+}
+
+static GList *
+indicator_ng_get_entries (IndicatorObject *io)
+{
+ IndicatorNg *self = INDICATOR_NG (io);
+
+ return g_list_append (NULL, &self->entry);
+}
+
+static gint
+indicator_ng_get_position (IndicatorObject *io)
+{
+ IndicatorNg *self = INDICATOR_NG (io);
+
+ return self->position;
+}
+
+static void
+indicator_ng_entry_scrolled (IndicatorObject *io,
+ __attribute__((unused)) IndicatorObjectEntry *entry,
+ gint delta,
+ IndicatorScrollDirection direction)
+{
+ IndicatorNg *self = INDICATOR_NG (io);
+
+ if (self->actions && self->scroll_action)
+ {
+ if (direction == INDICATOR_OBJECT_SCROLL_DOWN ||
+ direction == INDICATOR_OBJECT_SCROLL_LEFT)
+ {
+ delta *= -1;
+ }
+
+ g_action_group_activate_action (self->actions, self->scroll_action,
+ g_variant_new_int32 (delta));
+ }
+}
+
+void
+indicator_ng_secondary_activate (IndicatorObject *io,
+ __attribute__((unused)) IndicatorObjectEntry *entry,
+ __attribute__((unused)) guint timestamp,
+ __attribute__((unused)) gpointer user_data)
+{
+ IndicatorNg *self = INDICATOR_NG (io);
+
+ if (self->actions && self->secondary_action)
+ {
+ g_action_group_activate_action (self->actions, self->secondary_action, NULL);
+ }
+}
+
+static gboolean indicator_ng_menu_insert_idos(IndicatorNg *self, GMenuModel *pSection, guint nModelItem, guint nMenuItem, gboolean bNamespace, gchar *sNamespace)
+{
+ gboolean bChanged = FALSE;
+ gchar *sType;
+ gboolean bHasType = g_menu_model_get_item_attribute(pSection, nModelItem, "x-ayatana-type", "s", &sType);
+
+ if (bHasType)
+ {
+ GList *lMenuItems = gtk_container_get_children(GTK_CONTAINER(self->entry.menu));
+ GtkWidget *pMenuItemOld = GTK_WIDGET(g_list_nth_data(lMenuItems, nMenuItem));
+ const gchar *sName = gtk_widget_get_name(pMenuItemOld);
+
+ if (!g_str_equal(sName, sType))
+ {
+ GActionGroup *pActionGroup = (GActionGroup*)g_object_get_qdata(G_OBJECT(self->entry.menu), m_pActionMuxer);
+ GMenuItem *pMenuModelItem = g_menu_item_new_from_model(pSection, nModelItem);
+ GtkMenuItem* pMenuItemNew = NULL;
+ gchar *sAction;
+
+ if (bNamespace && g_menu_item_get_attribute(pMenuModelItem, G_MENU_ATTRIBUTE_ACTION, "s", &sAction))
+ {
+ gchar *sNamespacedAction = g_strconcat(sNamespace, ".", sAction, NULL);
+ g_menu_item_set_attribute(pMenuModelItem, G_MENU_ATTRIBUTE_ACTION, "s", sNamespacedAction);
+ g_free (sNamespacedAction);
+ g_free (sAction);
+ }
+
+ for (GList *pFactory = ayatana_menu_item_factory_get_all(); pFactory != NULL && pMenuItemNew == NULL; pFactory = pFactory->next)
+ {
+ pMenuItemNew = ayatana_menu_item_factory_create_menu_item(pFactory->data, sType, pMenuModelItem, pActionGroup);
+ bChanged = TRUE;
+ }
+
+ if (pMenuItemNew == NULL)
+ {
+ pMenuItemNew = GTK_MENU_ITEM(gtk_menu_item_new_with_label("Failed to create IDO object"));
+ }
+
+ gtk_widget_set_name(GTK_WIDGET(pMenuItemNew), sType);
+ gtk_widget_show(GTK_WIDGET(pMenuItemNew));
+ gtk_container_remove(GTK_CONTAINER(self->entry.menu), pMenuItemOld);
+ gtk_menu_shell_insert(GTK_MENU_SHELL(self->entry.menu), GTK_WIDGET(pMenuItemNew), nMenuItem);
+ g_object_unref(pMenuModelItem);
+ }
+
+ g_list_free(lMenuItems);
+ g_free(sType);
+ }
+
+ return bChanged;
+}
+
+static void indicator_ng_menu_size_allocate(__attribute__((unused)) GtkWidget *pWidget, __attribute__((unused)) GtkAllocation *pAllocation, gpointer pUserData)
+{
+ IndicatorNg *self = pUserData;
+ GList *pMenuItem = gtk_container_get_children(GTK_CONTAINER(self->entry.menu));
+ guint nWidth = 0;
+ guint nHeight = 0;
+ GdkWindow *pWindowBin = NULL;
+
+ while (pMenuItem)
+ {
+ if (!pWindowBin)
+ {
+ pWindowBin = gtk_widget_get_parent_window(pMenuItem->data);
+ }
+
+ gint nWidthNat;
+ gint nHeightNat;
+ gtk_widget_get_preferred_width(pMenuItem->data, NULL, &nWidthNat);
+ gtk_widget_get_preferred_height(pMenuItem->data, NULL, &nHeightNat);
+ nWidth = MAX((gint)nWidth, nWidthNat);
+ nHeight += nHeightNat;
+ GtkBorder cPadding;
+ GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(pMenuItem->data));
+ gtk_style_context_get_padding(pContext, gtk_style_context_get_state(pContext), &cPadding);
+ nWidth += cPadding.left + cPadding.right;
+ pMenuItem = g_list_next(pMenuItem);
+ }
+
+ g_list_free(pMenuItem);
+ GtkBorder cPadding;
+ GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(self->entry.menu));
+ gtk_style_context_get_padding(pContext, gtk_style_context_get_state(pContext), &cPadding);
+ gint nBorderWidth = gtk_container_get_border_width(GTK_CONTAINER(self->entry.menu));
+ gint nIconWidth;
+ gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &nIconWidth, NULL);
+ nWidth += (2 * nBorderWidth) + cPadding.left + cPadding.right + (nIconWidth * 3) / 2;
+ nHeight += (2 * nBorderWidth) + cPadding.top + cPadding.bottom + (nIconWidth * 3) / 4;
+
+ GdkRectangle cRectangle = {0};
+ GdkDisplay *pDisplay = gdk_display_get_default();
+ GdkMonitor *pMonitor = gdk_display_get_primary_monitor(pDisplay);
+ gdk_monitor_get_workarea(pMonitor, &cRectangle);
+
+ if ((gint)nHeight <= cRectangle.height)
+ {
+ gdk_window_move_resize(pWindowBin, 0, 0, nWidth, nHeight);
+ }
+
+ nHeight = MIN((gint)nHeight, cRectangle.height);
+
+ GdkWindow *pWindow = gtk_widget_get_parent_window(GTK_WIDGET(self->entry.menu));
+ gdk_window_resize(pWindow, nWidth, nHeight);
+
+ gtk_menu_reposition(self->entry.menu);
+}
+
+static void indicator_ng_menu_section_changed(__attribute__((unused)) GMenuModel *pMenuSection, __attribute__((unused)) gint nPosition, __attribute__((unused)) gint nRemoved, __attribute__((unused)) gint nAdded, gpointer pUserData)
+{
+ IndicatorNg *self = pUserData;
+ GMenuModel *pModel = g_menu_model_get_item_link(self->menu, 0, G_MENU_LINK_SUBMENU);
+ guint nMenuItem = 0;
+ gboolean bChanged = FALSE;
+
+ if (pModel)
+ {
+ guint nSections = g_menu_model_get_n_items(pModel);
+
+ for (guint nSection = 0; nSection < nSections; nSection++)
+ {
+ GMenuModel *pSection = g_menu_model_get_item_link(pModel, nSection, G_MENU_LINK_SECTION);
+ guint nSubsections = 0;
+
+ if (pSection)
+ {
+ gchar *sNamespace;
+ gboolean bNamespace = g_menu_model_get_item_attribute(pModel, nSection, G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &sNamespace);
+ nSubsections = g_menu_model_get_n_items(pSection);
+
+ for (guint nSubsection = 0; nSubsection < nSubsections; nSubsection++)
+ {
+ GMenuModel *pSubsection = g_menu_model_get_item_link(pSection, nSubsection, G_MENU_LINK_SECTION);
+
+ if (pSubsection)
+ {
+ guint nItems = g_menu_model_get_n_items(pSubsection);
+
+ // Skip the subsection separator (if there is one)
+ GList *lMenuItems = gtk_container_get_children(GTK_CONTAINER(self->entry.menu));
+ GtkWidget *pMenuItem = GTK_WIDGET(g_list_nth_data(lMenuItems, nMenuItem));
+
+ if (GTK_IS_SEPARATOR_MENU_ITEM(pMenuItem))
+ {
+ nMenuItem++;
+ }
+
+ g_list_free(lMenuItems);
+
+ for (guint nItem = 0; nItem < nItems; nItem++)
+ {
+ bChanged = indicator_ng_menu_insert_idos(self, pSubsection, nItem, nMenuItem, bNamespace, sNamespace) || bChanged;
+ nMenuItem++;
+ }
+ }
+
+ g_object_unref(pSubsection);
+
+ bChanged = indicator_ng_menu_insert_idos(self, pSection, nSubsection, nMenuItem, bNamespace, sNamespace) || bChanged;
+
+ if (!g_str_equal(self->name, "ayatana-indicator-messages"))
+ {
+ nMenuItem++;
+ }
+ }
+
+ if (bNamespace)
+ {
+ g_free(sNamespace);
+ }
+
+ g_object_unref(pSection);
+ }
+
+ if (pSection && nSubsections)
+ {
+ nMenuItem++;
+ }
+ }
+
+ g_object_unref(pModel);
+ }
+
+ if (bChanged)
+ {
+ indicator_ng_menu_size_allocate(NULL, NULL, self);
+ }
+}
+
+static void indicator_ng_menu_shown(__attribute__((unused)) GtkWidget *pWidget, gpointer pUserData)
+{
+ IndicatorNg *self = pUserData;
+ guint nSectionCount = 0;
+
+ if (!self->lMenuSections[0])
+ {
+ self->lMenuSections[0] = g_menu_model_get_item_link(self->menu, 0, G_MENU_LINK_SUBMENU);
+
+ if (self->lMenuSections[0])
+ {
+ guint nSections = g_menu_model_get_n_items(self->lMenuSections[0]);
+
+ for (guint nSection = 0; nSection < nSections; nSection++)
+ {
+ self->lMenuSections[++nSectionCount] = g_menu_model_get_item_link(self->lMenuSections[0], nSection, G_MENU_LINK_SECTION);
+
+ if (self->lMenuSections[nSectionCount])
+ {
+ g_signal_connect(self->lMenuSections[nSectionCount], "items-changed", G_CALLBACK(indicator_ng_menu_section_changed), self);
+ guint nSubsections = g_menu_model_get_n_items(self->lMenuSections[nSectionCount]);
+ guint nParent = nSectionCount;
+
+ for (guint nSubsection = 0; nSubsection < nSubsections; nSubsection++)
+ {
+ self->lMenuSections[++nSectionCount] = g_menu_model_get_item_link(self->lMenuSections[nParent], nSubsection, G_MENU_LINK_SECTION);
+
+ if (self->lMenuSections[nSectionCount])
+ {
+ g_signal_connect(self->lMenuSections[nSectionCount], "items-changed", G_CALLBACK(indicator_ng_menu_section_changed), self);
+ }
+ }
+ }
+ }
+
+ g_signal_connect(self->lMenuSections[0], "items-changed", G_CALLBACK(indicator_ng_menu_section_changed), self);
+ indicator_ng_menu_section_changed(self->lMenuSections[0], 0, 0, 1, self);
+ }
+ }
+
+ if (self->submenu_action)
+ {
+ g_action_group_change_action_state(self->actions, self->submenu_action, g_variant_new_boolean(TRUE));
+ }
+}
+
+static void
+indicator_ng_menu_hidden (__attribute__((unused)) GtkWidget *widget,
+ gpointer user_data)
+{
+ IndicatorNg *self = user_data;
+
+ if (self->submenu_action)
+ g_action_group_change_action_state (self->actions, self->submenu_action,
+ g_variant_new_boolean (FALSE));
+}
+
+static void
+indicator_ng_set_accessible_desc (IndicatorNg *self,
+ const gchar *accessible_desc)
+{
+ g_free (self->accessible_desc);
+ self->accessible_desc = g_strdup (accessible_desc);
+
+ self->entry.accessible_desc = self->accessible_desc;
+ g_signal_emit_by_name (self, INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE, &self->entry);
+}
+
+static void
+indicator_ng_set_icon_from_variant (IndicatorNg *self,
+ GVariant *variant)
+{
+ GIcon *icon;
+
+ if (variant == NULL)
+ {
+ if (self->entry.image)
+ {
+ gtk_image_clear (self->entry.image);
+ gtk_widget_hide (GTK_WIDGET (self->entry.image));
+ }
+ return;
+ }
+
+ gtk_widget_show (GTK_WIDGET (self->entry.image));
+
+ icon = g_icon_deserialize (variant);
+ if (icon)
+ {
+ indicator_image_helper_update_from_gicon (self->entry.image, icon);
+ g_object_unref (icon);
+ }
+ else
+ {
+ gchar *text = g_variant_print (variant, TRUE);
+ g_warning ("invalid icon variant '%s'", text);
+ gtk_image_set_from_icon_name (self->entry.image, "image-missing", GTK_ICON_SIZE_LARGE_TOOLBAR);
+ g_free (text);
+ }
+}
+
+static void indicator_ng_set_label(IndicatorNg *self, const gchar *label)
+{
+ if (!self->entry.label)
+ {
+ return;
+ }
+
+ const gchar *sLabel = label;
+ guint nSpacing = 3;
+ guint nPadding = 6;
+
+ if (label == NULL || *label == '\0' || !self->entry.image || !gtk_widget_get_visible(GTK_WIDGET(self->entry.image)))
+ {
+ nSpacing = 0;
+ nPadding = 0;
+ }
+
+ GtkWidget *pParent = gtk_widget_get_parent(GTK_WIDGET(self->entry.label));
+ GtkCssProvider *pCssProvider = gtk_css_provider_new();
+ GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(self->entry.label));
+ gtk_style_context_add_provider(pStyleContext, GTK_STYLE_PROVIDER(pCssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gchar *sCss = g_strdup_printf("label{padding-left: %ipx;}", nPadding);
+ gtk_css_provider_load_from_data(pCssProvider, sCss, -1, NULL);
+ g_free(sCss);
+ g_object_unref(pCssProvider);
+ if (GTK_IS_BOX(pParent))
+ {
+ gtk_box_set_spacing(GTK_BOX(pParent), nSpacing);
+ }
+ gtk_label_set_label(GTK_LABEL (self->entry.label), sLabel);
+
+ if (label)
+ {
+ gtk_widget_show(GTK_WIDGET (self->entry.label));
+ }
+}
+
+static void
+indicator_ng_update_entry (IndicatorNg *self)
+{
+ GVariant *state;
+ const gchar *label = NULL;
+ GVariant *icon = NULL;
+ const gchar *accessible_desc = NULL;
+ gboolean visible = TRUE;
+
+ g_return_if_fail (self->menu != NULL);
+ g_return_if_fail (self->actions != NULL);
+
+ if (!self->header_action ||
+ !g_action_group_has_action (self->actions, self->header_action))
+ {
+ indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE);
+ return;
+ }
+
+ state = g_action_group_get_action_state (self->actions, self->header_action);
+ if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("(sssb)")))
+ {
+ const gchar *iconstr = NULL;
+
+ g_variant_get (state, "(&s&s&sb)", &label, &iconstr, &accessible_desc, &visible);
+
+ if (iconstr)
+ icon = g_variant_ref_sink (g_variant_new_string (iconstr));
+ }
+ else if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("a{sv}")))
+ {
+ g_variant_lookup (state, "label", "&s", &label);
+ g_variant_lookup (state, "icon", "*", &icon);
+ g_variant_lookup (state, "accessible-desc", "&s", &accessible_desc);
+ g_variant_lookup (state, "visible", "b", &visible);
+ }
+ else
+ g_warning ("the action of the indicator menu item must have state with type (sssb) or a{sv}");
+
+ indicator_ng_set_label (self, label);
+ indicator_ng_set_icon_from_variant (self, icon);
+ indicator_ng_set_accessible_desc (self, accessible_desc);
+ indicator_object_set_visible (INDICATOR_OBJECT (self), visible);
+
+ if (icon)
+ g_variant_unref (icon);
+ if (state)
+ g_variant_unref (state);
+}
+
+static gboolean
+indicator_ng_menu_item_is_of_type (GMenuModel *menu,
+ gint index,
+ const gchar *expected_type)
+{
+ gchar *type;
+ gboolean has_type = FALSE;
+
+ if (g_menu_model_get_item_attribute (menu, index, "x-ayatana-type", "s", &type))
+ {
+ has_type = g_str_equal (type, expected_type);
+ g_free (type);
+ }
+
+ return has_type;
+}
+
+static void
+indicator_ng_menu_changed (__attribute__((unused)) GMenuModel *menu,
+ gint position,
+ gint removed,
+ gint added,
+ gpointer user_data)
+{
+ IndicatorNg *self = user_data;
+
+ /* The menu may only contain one item (the indicator title menu).
+ * Thus, the position is always 0, and there is either exactly one
+ * item added or exactly one item removed.
+ */
+ g_return_if_fail (position == 0);
+ g_return_if_fail (added < 2 && removed < 2 && added ^ removed);
+
+ if (removed)
+ indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE);
+
+ if (added)
+ {
+ g_clear_pointer (&self->header_action, g_free);
+ g_clear_pointer (&self->scroll_action, g_free);
+ g_clear_pointer (&self->secondary_action, g_free);
+
+ if (indicator_ng_menu_item_is_of_type (self->menu, 0, "org.ayatana.indicator.root"))
+ {
+ GMenuModel *popup;
+ gchar *action;
+
+ if (g_menu_model_get_item_attribute (self->menu, 0, G_MENU_ATTRIBUTE_ACTION, "s", &action))
+ {
+ if (g_str_has_prefix (action, "indicator."))
+ self->header_action = g_strdup (action + strlen ("indicator."));
+ g_free (action);
+ }
+
+ if (g_menu_model_get_item_attribute (self->menu, 0, "x-ayatana-scroll-action", "s", &action))
+ {
+ if (g_str_has_prefix (action, "indicator."))
+ self->scroll_action = g_strdup (action + strlen ("indicator."));
+ g_free (action);
+ }
+
+ if (g_menu_model_get_item_attribute (self->menu, 0, "x-ayatana-secondary-action", "s", &action))
+ {
+ if (g_str_has_prefix (action, "indicator."))
+ self->secondary_action = g_strdup (action + strlen ("indicator."));
+ g_free (action);
+ }
+
+ if (g_menu_model_get_item_attribute (self->menu, 0, "submenu-action", "s", &action))
+ {
+ if (g_str_has_prefix (action, "indicator."))
+ self->submenu_action = g_strdup (action + strlen ("indicator."));
+ g_free (action);
+ }
+
+ for (guint nMenuSection = 0; nMenuSection < MENU_SECTIONS; nMenuSection++)
+ {
+ if (self->lMenuSections[nMenuSection])
+ {
+ g_object_unref(self->lMenuSections[nMenuSection]);
+ }
+ }
+
+ popup = g_menu_model_get_item_link (self->menu, 0, G_MENU_LINK_SUBMENU);
+ if (popup)
+ {
+ gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->entry.menu), popup, NULL, TRUE);
+ g_object_unref (popup);
+ }
+
+ indicator_ng_update_entry (self);
+ }
+ else
+ g_warning ("indicator menu item must be of type 'org.ayatana.indicator.root'");
+ }
+}
+
+static void
+indicator_ng_service_appeared (GDBusConnection *connection,
+ __attribute__((unused)) const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ IndicatorNg *self = user_data;
+
+ g_assert (!self->actions);
+ g_assert (!self->menu);
+
+ /* watch is not established when menu_object_path == NULL */
+ g_assert (self->menu_object_path);
+
+ self->session_bus = g_object_ref (connection);
+
+ self->actions = G_ACTION_GROUP (g_dbus_action_group_get (connection, name_owner, self->object_path));
+ gtk_widget_insert_action_group (GTK_WIDGET (self->entry.menu), "indicator", self->actions);
+ g_signal_connect_swapped (self->actions, "action-added", G_CALLBACK (indicator_ng_update_entry), self);
+ g_signal_connect_swapped (self->actions, "action-removed", G_CALLBACK (indicator_ng_update_entry), self);
+ g_signal_connect_swapped (self->actions, "action-state-changed", G_CALLBACK (indicator_ng_update_entry), self);
+
+ self->menu = G_MENU_MODEL (g_dbus_menu_model_get (connection, name_owner, self->menu_object_path));
+ g_signal_connect (self->menu, "items-changed", G_CALLBACK (indicator_ng_menu_changed), self);
+ if (g_menu_model_get_n_items (self->menu))
+ indicator_ng_menu_changed (self->menu, 0, 0, 1, self);
+
+ indicator_ng_update_entry (self);
+}
+
+static void
+indicator_ng_service_started (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ IndicatorNg *self = user_data;
+ GError *error = NULL;
+ GVariant *result;
+ guint32 start_service_reply;
+
+ result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+ if (!result)
+ {
+ g_warning ("Could not activate service '%s': %s", self->name, error->message);
+ indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE);
+ g_error_free (error);
+ return;
+ }
+
+ start_service_reply = 0;
+ g_variant_get (result, "(u)", &start_service_reply);
+
+ switch (start_service_reply)
+ {
+ case 1: /* DBUS_START_REPLY_SUCCESS */
+ break;
+
+ case 2: /* DBUS_START_REPLY_ALREADY_RUNNING */
+ g_warning ("could not start service '%s': it is already running", self->name);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_variant_unref (result);
+}
+
+static void
+indicator_ng_service_vanished (__attribute__((unused)) GDBusConnection *connection,
+ __attribute__((unused)) const gchar *name,
+ gpointer user_data)
+{
+ IndicatorNg *self = user_data;
+
+ indicator_ng_free_actions_and_menu (self);
+
+ /* Names may vanish because the service decided it doesn't need to
+ * show its indicator anymore, or because it crashed. Let's assume it
+ * crashes and restart it unless it explicitly hid its indicator. */
+
+ if (indicator_object_entry_is_visible (INDICATOR_OBJECT (self), &self->entry))
+ {
+ gint64 now;
+
+ /* take care not to start it if it repeatedly crashes */
+ now = g_get_monotonic_time ();
+ if (now - self->last_service_restart < 1 * G_USEC_PER_SEC)
+ {
+ g_warning ("The indicator '%s' vanished too quickly after appearing. It won't "
+ "be respawned anymore, as it could be crashing repeatedly.", self->name);
+ return;
+ }
+
+ self->last_service_restart = now;
+
+ g_dbus_connection_call (self->session_bus,
+ "org.freedesktop.DBus",
+ "/",
+ "org.freedesktop.DBus",
+ "StartServiceByName",
+ g_variant_new ("(su)", self->bus_name, 0),
+ G_VARIANT_TYPE ("(u)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ indicator_ng_service_started,
+ self);
+ }
+}
+
+/* Get an integer from a keyfile. Returns @default_value if the key
+ * doesn't exist exists or is not an integer */
+static gint
+g_key_file_maybe_get_integer (GKeyFile *keyfile,
+ const gchar *group,
+ const gchar *key,
+ gint default_value)
+{
+ GError *error = NULL;
+ gint i;
+
+ i = g_key_file_get_integer (keyfile, group, key, &error);
+ if (error)
+ {
+ g_error_free (error);
+ return default_value;
+ }
+
+ return i;
+}
+
+static gboolean
+indicator_ng_load_from_keyfile (IndicatorNg *self,
+ GKeyFile *keyfile,
+ GError **error)
+{
+ g_assert (self->name == NULL);
+ g_assert (self->object_path == NULL);
+ g_assert (self->menu_object_path == NULL);
+
+ self->name = g_key_file_get_string (keyfile, "Indicator Service", "Name", error);
+ if (self->name == NULL)
+ return FALSE;
+
+ self->object_path = g_key_file_get_string (keyfile, "Indicator Service", "ObjectPath", error);
+ if (self->object_path == NULL)
+ return FALSE;
+
+ self->position = g_key_file_maybe_get_integer (keyfile, "Indicator Service", "Position", -1);
+
+ /*
+ * Don't throw an error when the profile doesn't exist. Non-existant
+ * profiles are silently ignored by not showing an indicator at all.
+ */
+ if (g_key_file_has_group (keyfile, self->profile))
+ {
+ /* however, if the profile exists, it must have "ObjectPath" */
+ self->menu_object_path = g_key_file_get_string (keyfile, self->profile, "ObjectPath", error);
+ if (self->menu_object_path == NULL)
+ return FALSE;
+
+ /* a position in the profile overrides the global one */
+ self->position = g_key_file_maybe_get_integer (keyfile, self->profile, "Position", self->position);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+indicator_ng_initable_init (GInitable *initable,
+ __attribute__((unused)) GCancellable *cancellable,
+ GError **error)
+{
+ IndicatorNg *self = INDICATOR_NG (initable);
+ GKeyFile *keyfile;
+ gboolean success = FALSE;
+
+ self->bus_name = g_path_get_basename (self->service_file);
+
+ keyfile = g_key_file_new ();
+ if (g_key_file_load_from_file (keyfile, self->service_file, G_KEY_FILE_NONE, error) &&
+ indicator_ng_load_from_keyfile (self, keyfile, error))
+ {
+ self->entry.name_hint = self->name;
+
+ /* only watch the service when it supports the proile we're interested in */
+ if (self->menu_object_path)
+ {
+ self->name_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ self->bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+ indicator_ng_service_appeared,
+ indicator_ng_service_vanished,
+ self, NULL);
+ }
+
+ success = TRUE;
+ }
+
+ g_key_file_free (keyfile);
+ return success;
+}
+
+static void
+indicator_ng_class_init (IndicatorNgClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ IndicatorObjectClass *io_class = INDICATOR_OBJECT_CLASS (class);
+
+ object_class->get_property = indicator_ng_get_property;
+ object_class->set_property = indicator_ng_set_property;
+ object_class->dispose = indicator_ng_dispose;
+ object_class->finalize = indicator_ng_finalize;
+
+ io_class->get_entries = indicator_ng_get_entries;
+ io_class->get_position = indicator_ng_get_position;
+ io_class->entry_scrolled = indicator_ng_entry_scrolled;
+ io_class->secondary_activate = indicator_ng_secondary_activate;
+
+ properties[PROP_SERVICE_FILE] = g_param_spec_string ("service-file",
+ "Service file",
+ "Path of the service file",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_PROFILE] = g_param_spec_string ("profile",
+ "Profile",
+ "Indicator profile",
+ "desktop",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(object_class, N_PROPERTIES, properties);
+}
+
+static void
+indicator_ng_initable_iface_init (GInitableIface *initable)
+{
+ initable->init = indicator_ng_initable_init;
+}
+
+static void
+indicator_ng_init (IndicatorNg *self)
+{
+ m_pActionMuxer = g_quark_from_static_string ("gtk-widget-action-muxer");
+
+ for (guint nMenuSection = 0; nMenuSection < MENU_SECTIONS; nMenuSection++)
+ {
+ self->lMenuSections[nMenuSection] = NULL;
+ }
+
+ self->entry.label = (GtkLabel*)g_object_ref_sink (gtk_label_new (NULL));
+ self->entry.image = (GtkImage*)g_object_ref_sink (gtk_image_new ());
+
+ self->entry.menu = (GtkMenu*)g_object_ref_sink (gtk_menu_new ());
+
+ g_signal_connect (self->entry.menu, "show", G_CALLBACK (indicator_ng_menu_shown), self);
+ g_signal_connect (self->entry.menu, "hide", G_CALLBACK (indicator_ng_menu_hidden), self);
+ g_signal_connect (self->entry.menu, "size-allocate", G_CALLBACK (indicator_ng_menu_size_allocate), self);
+
+ GtkCssProvider *pCssProvider = gtk_css_provider_new();
+ GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(self->entry.menu));
+ gtk_style_context_add_provider(pStyleContext, GTK_STYLE_PROVIDER(pCssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_css_provider_load_from_data(pCssProvider, "menu > arrow{min-height: 0; padding: 0; margin: 0;}", -1, NULL);
+
+ GtkWidget *pWindow = gtk_widget_get_parent(GTK_WIDGET(self->entry.menu));
+ pStyleContext = gtk_widget_get_style_context(pWindow);
+ gtk_style_context_add_provider(pStyleContext, GTK_STYLE_PROVIDER(pCssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_css_provider_load_from_data(pCssProvider, "window > decoration {box-shadow: 0 1px 2px rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.13);}", -1, NULL);
+
+ g_object_unref(pCssProvider);
+
+ /* work around IndicatorObject's warning that the accessible
+ * description is missing. We never set it on construction, but when
+ * the menu model has arrived on the bus.
+ */
+ self->accessible_desc = g_strdup ("");
+ self->entry.accessible_desc = self->accessible_desc;
+
+ self->position = -1;
+
+ indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE);
+}
+
+IndicatorNg *
+indicator_ng_new (const gchar *service_file,
+ GError **error)
+{
+ return g_initable_new (INDICATOR_TYPE_NG, NULL, error,
+ "service-file", service_file,
+ NULL);
+}
+
+IndicatorNg *
+indicator_ng_new_for_profile (const gchar *service_file,
+ const gchar *profile,
+ GError **error)
+{
+ return g_initable_new (INDICATOR_TYPE_NG, NULL, error,
+ "service-file", service_file,
+ "profile", profile,
+ NULL);
+}
+
+const gchar *
+indicator_ng_get_service_file (IndicatorNg *self)
+{
+ g_return_val_if_fail (INDICATOR_IS_NG (self), NULL);
+
+ return self->service_file;
+}
+
+const gchar *
+indicator_ng_get_profile (IndicatorNg *self)
+{
+ g_return_val_if_fail (INDICATOR_IS_NG (self), NULL);
+
+ return self->profile;
+}
diff --git a/src/indicator-ng.h b/src/indicator-ng.h
new file mode 100644
index 0000000..f074a47
--- /dev/null
+++ b/src/indicator-ng.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * 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/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __INDICATOR_NG_H__
+#define __INDICATOR_NG_H__
+
+#include "indicator-object.h"
+
+#define INDICATOR_TYPE_NG (indicator_ng_get_type ())
+#define INDICATOR_NG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_TYPE_NG, IndicatorNg))
+#define INDICATOR_NG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_TYPE_NG, IndicatorNgClass))
+#define INDICATOR_IS_NG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_TYPE_NG))
+#define INDICATOR_IS_NG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_TYPE_NG))
+#define INDICATOR_NG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_TYPE_NG, IndicatorNgClass))
+
+typedef struct _IndicatorNg IndicatorNg;
+typedef IndicatorObjectClass IndicatorNgClass;
+
+GType indicator_ng_get_type (void);
+
+IndicatorNg * indicator_ng_new (const gchar *service_file,
+ GError **error);
+
+IndicatorNg * indicator_ng_new_for_profile (const gchar *service_file,
+ const gchar *profile,
+ GError **error);
+
+const gchar * indicator_ng_get_service_file (IndicatorNg *indicator);
+
+const gchar * indicator_ng_get_profile (IndicatorNg *indicator);
+
+#endif
diff --git a/src/indicator-object-enum-types.c.template b/src/indicator-object-enum-types.c.template
new file mode 100644
index 0000000..e5b3352
--- /dev/null
+++ b/src/indicator-object-enum-types.c.template
@@ -0,0 +1,30 @@
+/*** BEGIN file-header ***/
+#include "indicator-object-enum-types.h"
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@basename@" */
+#include "@basename@"
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void) {
+ static GType enum_type_id = 0;
+ if (G_UNLIKELY (!enum_type_id))
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ enum_type_id = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+ }
+ return enum_type_id;
+}
+/*** END value-tail ***/
diff --git a/src/indicator-object-enum-types.h.template b/src/indicator-object-enum-types.h.template
new file mode 100644
index 0000000..2ac8ef8
--- /dev/null
+++ b/src/indicator-object-enum-types.h.template
@@ -0,0 +1,27 @@
+/*** BEGIN file-header ***/
+
+#ifndef __INDICATOR_OBJECT_ENUM_TYPES_H__
+#define __INDICATOR_OBJECT_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@basename@" */
+/*** END file-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* !__INDICATOR_OBJECT_ENUM_TYPES_H__ */
+/*** END file-tail ***/
+
+/*** BEGIN value-header ***/
+
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define INDICATOR_OBJECT_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
+
+/*** END value-header ***/
diff --git a/src/indicator-object-marshal.list b/src/indicator-object-marshal.list
new file mode 100644
index 0000000..4896ac4
--- /dev/null
+++ b/src/indicator-object-marshal.list
@@ -0,0 +1,4 @@
+VOID: POINTER, UINT, UINT
+VOID: POINTER, UINT, ENUM
+VOID: POINTER, UINT
+VOID: POINTER, BOOLEAN
diff --git a/src/indicator-object.c b/src/indicator-object.c
new file mode 100644
index 0000000..50b3078
--- /dev/null
+++ b/src/indicator-object.c
@@ -0,0 +1,967 @@
+/*
+An object to represent loadable indicator modules to make loading
+them easy and objectified.
+
+Copyright 2009 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 "indicator.h"
+#include "indicator-object.h"
+#include "indicator-object-marshal.h"
+#include "indicator-object-enum-types.h"
+
+/**
+ @ENTRY_INIT: The entry hasn't been initialized yet, so its
+ visibility will depend upon the inital-visibility property.
+ @ENTRY_VISIBLE: The entry is visible
+ @ENTRY_INVISIBLE: The entry is invisible
+*/
+typedef enum {
+ ENTRY_INIT,
+ ENTRY_VISIBLE,
+ ENTRY_INVISIBLE
+}
+EntryVisibility;
+
+typedef struct _IndicatorObjectEntryPrivate {
+ EntryVisibility visibility;
+}
+IndicatorObjectEntryPrivate;
+
+/**
+ IndicatorObjectPrivate:
+ @module: The loaded module representing the object. Note to
+ subclasses: This will not be set when you're initalized.
+ @entry: A default entry for objects that don't need all the
+ fancy stuff. This works with #get_entries_default.
+ @gotten_entries: A check to see if the @entry has been
+ populated intelligently yet.
+
+ Structure to define the memory for the private area
+ of the object instance.
+*/
+struct _IndicatorObjectPrivate {
+ GModule * module;
+
+ /* For get_entries_default */
+ IndicatorObjectEntry entry;
+ gboolean gotten_entries;
+
+ /* Whether or not entries are visible by default */
+ gboolean default_visibility;
+ GHashTable * entry_privates;
+
+ GStrv environments;
+};
+
+/* Signals Stuff */
+enum {
+ ENTRY_ADDED,
+ ENTRY_REMOVED,
+ ENTRY_MOVED,
+ ENTRY_SCROLLED,
+ MENU_SHOW,
+ SHOW_NOW_CHANGED,
+ ACCESSIBLE_DESC_UPDATE,
+ SECONDARY_ACTIVATE,
+ LAST_SIGNAL
+};
+
+/* Properties */
+/* Enum for the properties so that they can be quickly
+ found and looked up. */
+enum {
+ PROP_0,
+ PROP_GSETTINGS_SCHEMA_ID,
+ PROP_DEFAULT_VISIBILITY,
+};
+
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* GObject stuff */
+static void indicator_object_class_init (IndicatorObjectClass *klass);
+static void indicator_object_init (IndicatorObject *self);
+static void indicator_object_dispose (GObject *object);
+static void indicator_object_finalize (GObject *object);
+static void set_property (GObject*, guint prop_id, const GValue*, GParamSpec* );
+static void get_property (GObject*, guint prop_id, GValue*, GParamSpec* );
+
+/* entries' visibility */
+static GList * get_entries_default (IndicatorObject*);
+static GList * get_all_entries (IndicatorObject*);
+static void indicator_object_entry_being_removed (IndicatorObject*, IndicatorObjectEntry*);
+static void indicator_object_entry_was_added (IndicatorObject*, IndicatorObjectEntry*);
+static gint indicator_object_real_get_position (IndicatorObject*);
+static IndicatorObjectEntryPrivate * entry_get_private (IndicatorObject*, IndicatorObjectEntry*);
+
+G_DEFINE_TYPE_WITH_PRIVATE (IndicatorObject, indicator_object, G_TYPE_OBJECT);
+
+/* Setup the class and put the functions into the
+ class structure */
+static void
+indicator_object_class_init (IndicatorObjectClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = indicator_object_dispose;
+ object_class->finalize = indicator_object_finalize;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ klass->get_label = NULL;
+ klass->get_menu = NULL;
+ klass->get_image = NULL;
+ klass->get_accessible_desc = NULL;
+ klass->get_entries = get_entries_default;
+ klass->get_location = NULL;
+ klass->entry_being_removed = NULL;
+ klass->entry_was_added = NULL;
+ klass->get_position = indicator_object_real_get_position;
+
+ klass->entry_activate = NULL;
+ klass->entry_activate_window = NULL;
+ klass->entry_close = NULL;
+
+ /**
+ IndicatorObject::entry-added:
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry that
+ is being added.
+
+ Signaled when a new entry is added and should
+ be shown by the person using this object.
+ */
+ signals[ENTRY_ADDED] = g_signal_new (INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, entry_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER, G_TYPE_NONE);
+
+ /**
+ IndicatorObject::entry-removed:
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry that
+ is being removed.
+
+ Signaled when an entry is removed and should
+ be removed by the person using this object.
+ */
+ signals[ENTRY_REMOVED] = g_signal_new (INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, entry_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER, G_TYPE_NONE);
+ /**
+ IndicatorObject::entry-moved:
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry that
+ is being moved.
+ @arg2: The old location of the entry
+ @arg3: The new location of the entry
+
+ When the order of the entries change, then this signal
+ is sent to tell the new location.
+ */
+ signals[ENTRY_MOVED] = g_signal_new (INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, entry_moved),
+ NULL, NULL,
+ _indicator_object_marshal_VOID__POINTER_UINT_UINT,
+ G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_NONE);
+ /**
+ IndicatorObject::entry-scrolled:
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry that
+ receives the scroll event.
+ @arg2: The delta of the scroll event
+ @arg3: The orientation of the scroll event.
+
+ When the indicator receives a mouse scroll wheel event
+ from the user, this signal is emitted.
+ */
+ signals[ENTRY_SCROLLED] = g_signal_new (INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, entry_scrolled),
+ NULL, NULL,
+ _indicator_object_marshal_VOID__POINTER_UINT_ENUM,
+ G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_UINT,
+ INDICATOR_OBJECT_TYPE_SCROLL_DIRECTION);
+ /**
+ IndicatorObject::secondary-activate:
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry that
+ receives the secondary activate event.
+ @arg2: The timestamp of the event
+
+ When the indicator receives a secondary activation event
+ from the user, this signal is emitted.
+ */
+ signals[SECONDARY_ACTIVATE] = g_signal_new (INDICATOR_OBJECT_SIGNAL_SECONDARY_ACTIVATE,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, secondary_activate),
+ NULL, NULL,
+ _indicator_object_marshal_VOID__POINTER_UINT,
+ G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+ /**
+ IndicatorObject::menu-show:
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry that
+ is being shown.
+ @arg2: The timestamp of the event
+
+ Used when the indicator wants to signal up the stack
+ that the menu should be shown.
+ */
+ signals[MENU_SHOW] = g_signal_new (INDICATOR_OBJECT_SIGNAL_MENU_SHOW,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, menu_show),
+ NULL, NULL,
+ _indicator_object_marshal_VOID__POINTER_UINT,
+ G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+ /**
+ IndicatorObject::show-now-changed:
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry that
+ is changing it's state
+ @arg2: The state of whether the entry should be shown
+
+ Whether the entry should be shown or not has changed so we need
+ to tell whoever is displaying it.
+ */
+ signals[SHOW_NOW_CHANGED] = g_signal_new (INDICATOR_OBJECT_SIGNAL_SHOW_NOW_CHANGED,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, show_now_changed),
+ NULL, NULL,
+ _indicator_object_marshal_VOID__POINTER_BOOLEAN,
+ G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
+
+ /**
+ IndicatorObject::accessible-desc-update::
+ @arg0: The #IndicatorObject object
+ @arg1: A pointer to the #IndicatorObjectEntry whos
+ accessible description has been updated.
+
+ Signaled when an indicator's accessible description
+ has been updated, so that the displayer of the
+ indicator can fetch the new description.
+ */
+ signals[ACCESSIBLE_DESC_UPDATE] = g_signal_new (INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorObjectClass, accessible_desc_update),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER, G_TYPE_NONE);
+
+ /* Properties */
+
+ GParamSpec * pspec = g_param_spec_boolean (INDICATOR_OBJECT_DEFAULT_VISIBILITY,
+ "default visibility",
+ "Whether or not entries should initially be visible.",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DEFAULT_VISIBILITY, pspec);
+}
+
+/* Initialize an instance */
+static void
+indicator_object_init (IndicatorObject *self)
+{
+ IndicatorObjectPrivate * priv = indicator_object_get_instance_private(self);
+
+ priv->module = NULL;
+
+ priv->entry.parent_object = self;
+ priv->entry.menu = NULL;
+ priv->entry.label = NULL;
+ priv->entry.image = NULL;
+ priv->entry.accessible_desc = NULL;
+ priv->entry.name_hint = NULL;
+ priv->entry.parent_window = 0;
+
+ priv->gotten_entries = FALSE;
+ priv->default_visibility = TRUE;
+ priv->entry_privates = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+
+ priv->environments = NULL;
+
+ self->priv = priv;
+
+ GObject * o = G_OBJECT(self);
+ /* Invoke the entry-being-removed virtual function first */
+ g_signal_connect (o, INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED,
+ G_CALLBACK(indicator_object_entry_being_removed), NULL);
+ /* Invoke the entry-was-added virtual function last */
+ g_signal_connect_after (o, INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED,
+ G_CALLBACK(indicator_object_entry_was_added), NULL);
+}
+
+/* Unref the objects that we're holding on to. */
+static void
+indicator_object_dispose (GObject *object)
+{
+ /* Ensure that hidden entries are re-added so their widgetry will
+ be cleaned up properly by the client */
+ indicator_object_set_visible (INDICATOR_OBJECT (object), TRUE);
+
+ G_OBJECT_CLASS (indicator_object_parent_class)->dispose (object);
+}
+
+/* A small helper function that closes a module but
+ in the function prototype of a GSourceFunc. */
+static gboolean
+module_unref (gpointer data)
+{
+ if (!g_module_close((GModule *)data)) {
+ /* All we can do is warn. */
+ g_warning("Unable to close module!");
+ }
+ return FALSE;
+}
+
+/* Free memory */
+static void
+indicator_object_finalize (GObject *object)
+{
+
+ IndicatorObject * obj = INDICATOR_OBJECT (object);
+ IndicatorObjectPrivate * priv = indicator_object_get_instance_private(obj);
+
+ if (priv->entry_privates != NULL) {
+ g_hash_table_destroy (priv->entry_privates);
+ priv->entry_privates = NULL;
+ }
+
+ if (priv->environments != NULL) {
+ g_strfreev(priv->environments);
+ priv->environments = NULL;
+ }
+
+ if (priv->module != NULL) {
+ /* Wow, this is convoluted. So basically we want to unref
+ the module which will cause the code it included to be
+ removed. But, since its finalize function is the function
+ that called this one, we can't really remove it before
+ it finishes being executed. So we're putting the job into
+ the main loop to remove it the next time it gets a chance.
+ Slightly non-deterministic, but should work. */
+ g_idle_add(module_unref, priv->module);
+ priv->module = NULL;
+ }
+
+ G_OBJECT_CLASS (indicator_object_parent_class)->finalize (object);
+ return;
+}
+
+/**
+ indicator_object_new_from_file:
+ @file: Filename containing a loadable module
+
+ This function builds an #IndicatorObject using the symbols
+ that are found in @file. The module is loaded and the
+ references are all kept by the object. To unload the
+ module the object must be destroyed.
+
+ Return value: A valid #IndicatorObject or #NULL if error.
+*/
+IndicatorObject *
+indicator_object_new_from_file (const gchar * file)
+{
+ GObject * object = NULL;
+ GModule * module = NULL;
+
+ /* Check to make sure the name exists and that the
+ file itself exists */
+ if (file == NULL) {
+ g_warning("Invalid filename.");
+ return NULL;
+ }
+
+ if (!g_file_test(file, G_FILE_TEST_EXISTS)) {
+ g_warning("File '%s' does not exist.", file);
+ return NULL;
+ }
+
+ /* Grab the g_module reference, pull it in but let's
+ keep the symbols local to avoid conflicts. */
+ module = g_module_open(file,
+ G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+ if (module == NULL) {
+ g_warning("Unable to load module: %s", file);
+ return NULL;
+ }
+
+ /* Look for the version function, error if not found. */
+ get_version_t lget_version = NULL;
+ if (!g_module_symbol(module, INDICATOR_GET_VERSION_S, (gpointer *)(&lget_version))) {
+ g_warning("Unable to get the symbol for getting the version.");
+ return NULL;
+ }
+
+ /* Check the version with the macro and make sure we're
+ all talking the same language. */
+ if (!INDICATOR_VERSION_CHECK(lget_version())) {
+ g_warning("Indicator using API version '%s' we're expecting '%s'", lget_version(), INDICATOR_VERSION);
+ return NULL;
+ }
+
+ /* The function for grabbing a label from the module
+ execute it, and make sure everything is a-okay */
+ get_type_t lget_type = NULL;
+ if (!g_module_symbol(module, INDICATOR_GET_TYPE_S, (gpointer *)(&lget_type))) {
+ g_warning("Unable to get '" INDICATOR_GET_TYPE_S "' symbol from module: %s", file);
+ goto unrefandout;
+ }
+ if (lget_type == NULL) {
+ g_warning("Symbol '" INDICATOR_GET_TYPE_S "' is (null) in module: %s", file);
+ goto unrefandout;
+ }
+
+ /* A this point we allocate the object, any code beyond
+ here needs to deallocate it if we're returning in an
+ error'd state. */
+ object = g_object_new(lget_type(), NULL);
+ if (object == NULL) {
+ g_warning("Unable to build an object if type '%d' in module: %s", (gint)lget_type(), file);
+ goto unrefandout;
+ }
+ if (!INDICATOR_IS_OBJECT(object)) {
+ g_warning("Type '%d' in file %s is not a subclass of IndicatorObject.", (gint)lget_type(), file);
+ goto unrefandout;
+ }
+
+ /* Now we can track the module */
+ IndicatorObject * obj = INDICATOR_OBJECT(object);
+ IndicatorObjectPrivate * priv = indicator_object_get_instance_private(obj);
+ priv->module = module;
+
+ return obj;
+
+ /* Error, let's drop the object and return NULL. Sad when
+ this happens. */
+unrefandout:
+ g_clear_object (&object);
+ g_clear_object (&module);
+ g_warning("Error building IndicatorObject from file: %s", file);
+ return NULL;
+}
+
+/* The default get entries function uses the other single
+ entries in the class to create an entry structure and
+ put it into a list. This makes it simple for simple objects
+ to create the list. Small changes from the way they
+ previously were. */
+static GList *
+get_entries_default (IndicatorObject * io)
+{
+ IndicatorObjectPrivate * priv = indicator_object_get_instance_private(io);
+
+ if (!priv->gotten_entries) {
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ priv->entry.parent_object = io;
+
+ if (class->get_label) {
+ priv->entry.label = class->get_label(io);
+ }
+
+ if (class->get_image) {
+ priv->entry.image = class->get_image(io);
+ }
+
+ if (priv->entry.image == NULL && priv->entry.label == NULL) {
+ g_warning("IndicatorObject class does not create an image or a label. We need one of those.");
+ return NULL;
+ }
+
+ if (class->get_menu) {
+ priv->entry.menu = class->get_menu(io);
+ }
+
+ if (priv->entry.menu == NULL) {
+ g_warning("IndicatorObject class does not create a menu. We need one of those.");
+ return NULL;
+ }
+
+ if (class->get_accessible_desc) {
+ priv->entry.accessible_desc = class->get_accessible_desc(io);
+ }
+
+ if (priv->entry.accessible_desc == NULL) {
+ g_warning("IndicatorObject class does not have an accessible description.");
+ }
+
+ if (class->get_name_hint) {
+ priv->entry.name_hint = class->get_name_hint(io);
+ }
+
+ if (class->get_parent_window) {
+ priv->entry.parent_window = class->get_parent_window(io);
+ }
+
+ priv->gotten_entries = TRUE;
+ }
+
+ return g_list_append(NULL, &(priv->entry));
+}
+
+/* returns a list of all IndicatorObjectEntries, visible or not */
+static GList*
+get_all_entries (IndicatorObject * io)
+{
+ GList * all_entries = NULL, *l;
+
+ g_return_val_if_fail(INDICATOR_IS_OBJECT(io), NULL);
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ if (class->get_entries == NULL)
+ g_error("No get_entries function on object. It must have been deleted?!?!");
+ else
+ {
+ all_entries = class->get_entries(io);
+
+ for (l = all_entries; l; l = l->next)
+ {
+ IndicatorObjectEntry *entry = l->data;
+
+ if (entry)
+ entry->parent_object = io;
+ }
+ }
+
+ return all_entries;
+}
+
+/* get the private structure that corresponds to a caller-specified entry */
+static IndicatorObjectEntryPrivate *
+entry_get_private (IndicatorObject * io, IndicatorObjectEntry * entry)
+{
+ g_return_val_if_fail (INDICATOR_IS_OBJECT(io), NULL);
+ g_return_val_if_fail (io->priv != NULL, NULL);
+
+ GHashTable * h = io->priv->entry_privates;
+ IndicatorObjectEntryPrivate * priv = g_hash_table_lookup (h, entry);
+ if (priv == NULL)
+ {
+ priv = g_new0 (IndicatorObjectEntryPrivate, 1);
+ priv->visibility = ENTRY_INIT;
+ g_hash_table_insert (h, entry, priv);
+ }
+
+ return priv;
+}
+
+/**
+ indicator_object_get_entries:
+ @io: #IndicatorObject to query
+
+ This function returns a list of visible entries. The list is built
+ by calling the object's #IndicatorObjectClass::get_entries
+ virtual function and testing each of the results for visibility.
+ Callers should free the GList with g_list_free(), but the entries
+ are owned by the IndicatorObject and should not be freed.
+
+ Return value: (element-type IndicatorObjectEntry) (transfer container):
+ A list if #IndicatorObjectEntry structures or NULL on error.
+*/
+GList *
+indicator_object_get_entries (IndicatorObject * io)
+{
+ GList * l;
+ GList * ret = NULL;
+ GList * all_entries = get_all_entries (io);
+ IndicatorObjectPrivate * priv = indicator_object_get_instance_private(io);
+ const gboolean default_visibility = priv->default_visibility;
+
+ for (l=all_entries; l!=NULL; l=l->next)
+ {
+ gboolean show_me;
+ IndicatorObjectEntry * entry = l->data;
+
+ switch (entry_get_private(io,entry)->visibility) {
+ case ENTRY_VISIBLE: show_me = TRUE; break;
+ case ENTRY_INVISIBLE: show_me = FALSE; break;
+ case ENTRY_INIT: show_me = default_visibility; break;
+ default: show_me = TRUE; g_warn_if_reached(); break;
+ }
+
+ if (show_me)
+ ret = g_list_prepend (ret, entry);
+ }
+
+ g_list_free (all_entries);
+ return g_list_reverse (ret);
+}
+
+/**
+ indicator_object_get_location:
+ @io: #IndicatorObject to query
+ @entry: The #IndicatorObjectEntry to look for.
+
+ This function looks on the class for the object and calls
+ it's #IndicatorObjectClass::get_location function. If the
+ function doesn't exist it returns zero.
+
+ Return value: Location of the @entry in the display or
+ zero if no location is specified.
+*/
+guint
+indicator_object_get_location (IndicatorObject * io, IndicatorObjectEntry * entry)
+{
+ g_return_val_if_fail(INDICATOR_IS_OBJECT(io), 0);
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ if (class->get_location) {
+ return class->get_location(io, entry);
+ }
+
+ return 0;
+}
+
+/**
+ indicator_object_get_show_now:
+ @io: #IndicatorObject to query
+ @entry: The #IndicatorObjectEntry to look for.
+
+ This function returns whether the entry should be shown with
+ priority on the panel. If the object does not support checking
+ it assumes that its entries should never have priority.
+
+ Return value: Whether the entry should be shown with priority.
+*/
+guint
+indicator_object_get_show_now (IndicatorObject * io, IndicatorObjectEntry * entry)
+{
+ g_return_val_if_fail(INDICATOR_IS_OBJECT(io), 0);
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ if (class->get_show_now) {
+ return class->get_show_now(io, entry);
+ }
+
+ return FALSE;
+}
+
+/**
+ indicator_object_entry_activate_window:
+ @io: #IndicatorObject to query
+ @entry: The #IndicatorObjectEntry whose entry was shown
+ @windowid: ID of the window that is currently focused (or will
+ be very shortly)
+ @timestamp: The X11 timestamp of the event
+
+ Used to signal to the indicator that the menu on an entry has
+ been clicked on. This can either be an activate or a showing
+ of the menu. Also includes a window ID so that we can know what
+ application is going to be getting focused soon. If there is
+ no override of this function, it is the same as calling
+ indicator_object_entry_activate and in general is preferable
+ if you have that information available.
+*/
+void
+indicator_object_entry_activate_window (IndicatorObject * io, IndicatorObjectEntry * entry, guint windowid, guint timestamp)
+{
+ g_return_if_fail(INDICATOR_IS_OBJECT(io));
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ if (class->entry_activate_window != NULL) {
+ return class->entry_activate_window(io, entry, windowid, timestamp);
+ } else {
+ indicator_object_entry_activate(io, entry, timestamp);
+ }
+
+ return;
+}
+
+/**
+ indicator_object_entry_activate:
+ @io: #IndicatorObject to query
+ @entry: The #IndicatorObjectEntry whose entry was shown
+ @timestamp: The X11 timestamp of the event
+
+ Used to signal to the indicator that the menu on an entry has
+ been clicked on. This can either be an activate or a showing
+ of the menu. Note, this does not actually show the menu that's
+ left up to the reader.
+*/
+void
+indicator_object_entry_activate (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp)
+{
+ g_return_if_fail(INDICATOR_IS_OBJECT(io));
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ if (class->entry_activate != NULL) {
+ return class->entry_activate(io, entry, timestamp);
+ }
+
+ return;
+}
+
+/**
+ indicator_object_entry_close:
+ @io: #IndicatorObject to query
+ @entry: The #IndicatorObjectEntry whose menu was closed
+ @timestamp: The X11 timestamp of the event
+
+ Used to tell the indicator that a menu has been closed for the
+ entry that is specified.
+*/
+void
+indicator_object_entry_close (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp)
+{
+ g_return_if_fail(INDICATOR_IS_OBJECT(io));
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ if (class->entry_close != NULL) {
+ return class->entry_close(io, entry, timestamp);
+ }
+
+ return;
+}
+
+static void
+indicator_object_entry_being_removed (IndicatorObject * io, IndicatorObjectEntry * entry)
+{
+ g_return_if_fail(INDICATOR_IS_OBJECT(io));
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ entry_get_private (io, entry)->visibility = ENTRY_INVISIBLE;
+
+ if (entry)
+ entry->parent_object = NULL;
+
+ if (class->entry_being_removed != NULL)
+ {
+ class->entry_being_removed (io, entry);
+ }
+}
+
+static void
+indicator_object_entry_was_added (IndicatorObject * io, IndicatorObjectEntry * entry)
+{
+ g_return_if_fail(INDICATOR_IS_OBJECT(io));
+ IndicatorObjectClass * class = INDICATOR_OBJECT_GET_CLASS(io);
+
+ entry_get_private (io, entry)->visibility = ENTRY_VISIBLE;
+
+ if (entry)
+ entry->parent_object = io;
+
+ if (class->entry_was_added != NULL)
+ {
+ class->entry_was_added (io, entry);
+ }
+}
+
+static gint
+indicator_object_real_get_position (IndicatorObject *io)
+{
+ g_return_val_if_fail (INDICATOR_IS_OBJECT (io), -1);
+
+ return -1;
+}
+
+/**
+ indicator_object_set_environment:
+ @io: #IndicatorObject to set on
+ @env: List of enviroment names to use
+
+ Sets the names of the environment that the indicator is being
+ loaded into. This allows for indicators to behave differently
+ in different hosts if need be.
+*/
+void
+indicator_object_set_environment (IndicatorObject * io, GStrv env)
+{
+ /* FIXME: should this be a property? */
+ g_return_if_fail(INDICATOR_IS_OBJECT(io));
+
+ if (io->priv->environments != NULL) {
+ g_strfreev(io->priv->environments);
+ io->priv->environments = NULL;
+ }
+
+ io->priv->environments = g_strdupv(env);
+
+ return;
+}
+
+/**
+ indicator_object_get_environment:
+ @io: #IndicatorObject to get the environment from
+
+ Gets the list of environment strings that this object is
+ placed into.
+
+ Return value: (transfer none): Gets the list of strings that
+ represent the environment or NULL if none were given.
+*/
+GStrv
+indicator_object_get_environment (IndicatorObject * io)
+{
+ g_return_val_if_fail(INDICATOR_IS_OBJECT(io), NULL);
+ return io->priv->environments;
+}
+
+/**
+ indicator_object_check_environment:
+ @io: #IndicatorObject to check on
+ @env: Environment that we're looking for
+
+ Convience function to check to see if the specified environment
+ @env is in our list of environments.
+
+ Return Value: Whether we're in environment @env
+*/
+gboolean
+indicator_object_check_environment (IndicatorObject * io, const gchar * env)
+{
+ g_return_val_if_fail(INDICATOR_IS_OBJECT(io), FALSE);
+ g_return_val_if_fail(env != NULL, FALSE);
+
+ if (io->priv->environments == NULL) {
+ return FALSE;
+ }
+
+ int i;
+ for (i = 0; io->priv->environments[i] != NULL; i++) {
+ if (g_strcmp0(env, io->priv->environments[i]) == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ indicator_object_set_visible:
+ @io: #IndicatorObject to check on
+ @visible: whether or not the entries should be visible
+
+ Used to set all of an indicator's entries to be visible or hidden.
+*/
+void
+indicator_object_set_visible (IndicatorObject * io, gboolean visible)
+{
+ g_return_if_fail(INDICATOR_IS_OBJECT(io));
+
+ GList * l;
+ GList * entries = get_all_entries (io);
+ const guint signal_id = signals[visible ? ENTRY_ADDED : ENTRY_REMOVED];
+ EntryVisibility visibility = visible ? ENTRY_VISIBLE : ENTRY_INVISIBLE;
+ const GQuark detail = (GQuark)0;
+
+ for (l=entries; l!=NULL; l=l->next) {
+ IndicatorObjectEntry *entry = l->data;
+ if (entry_get_private (io, entry)->visibility != visibility)
+ g_signal_emit(io, signal_id, detail, entry);
+ }
+ g_list_free (entries);
+}
+
+static void
+get_property (GObject * object,
+ guint prop_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorObject * self = INDICATOR_OBJECT(object);
+ g_return_if_fail(self != NULL);
+
+ IndicatorObjectPrivate * priv = indicator_object_get_instance_private(self);
+ g_return_if_fail(priv != NULL);
+
+ switch (prop_id) {
+ /* *********************** */
+ case PROP_DEFAULT_VISIBILITY:
+ if (G_VALUE_HOLDS_BOOLEAN(value)) {
+ g_value_set_boolean(value, priv->default_visibility);
+ } else {
+ g_warning("default-visibility property requires a boolean value.");
+ }
+ break;
+ /* *********************** */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorObject * self = INDICATOR_OBJECT(object);
+ g_return_if_fail (self != NULL);
+
+ IndicatorObjectPrivate * priv = indicator_object_get_instance_private(self);
+ g_return_if_fail (priv != NULL);
+
+
+ switch (prop_id) {
+
+ /* *********************** */
+ case PROP_DEFAULT_VISIBILITY:
+ if (G_VALUE_HOLDS_BOOLEAN(value)) {
+ priv->default_visibility = g_value_get_boolean (value);
+ } else {
+ g_warning("default-visibility property requires a boolean value.");
+ }
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+gboolean
+indicator_object_entry_is_visible (IndicatorObject * io, IndicatorObjectEntry * entry)
+{
+ g_return_val_if_fail (INDICATOR_IS_OBJECT (io), FALSE);
+
+ return entry_get_private (io, entry)->visibility == ENTRY_VISIBLE;
+}
+
+gint
+indicator_object_get_position (IndicatorObject *io)
+{
+ g_return_val_if_fail (INDICATOR_IS_OBJECT (io), FALSE);
+
+ return INDICATOR_OBJECT_GET_CLASS (io)->get_position (io);
+}
diff --git a/src/indicator-object.h b/src/indicator-object.h
new file mode 100644
index 0000000..e542c0d
--- /dev/null
+++ b/src/indicator-object.h
@@ -0,0 +1,216 @@
+/*
+An object to represent loadable indicator modules to make loading
+them easy and objectified.
+
+Copyright 2009 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_OBJECT_H__
+#define __INDICATOR_OBJECT_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ INDICATOR_OBJECT_SCROLL_UP,
+ INDICATOR_OBJECT_SCROLL_DOWN,
+ INDICATOR_OBJECT_SCROLL_LEFT,
+ INDICATOR_OBJECT_SCROLL_RIGHT
+} IndicatorScrollDirection;
+
+#define INDICATOR_OBJECT_TYPE (indicator_object_get_type ())
+#define INDICATOR_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_OBJECT_TYPE, IndicatorObject))
+#define INDICATOR_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_OBJECT_TYPE, IndicatorObjectClass))
+#define INDICATOR_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_OBJECT_TYPE))
+#define INDICATOR_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_OBJECT_TYPE, IndicatorObjectClass))
+
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED "entry-added"
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED, INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED "entry-removed"
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_ENTRY_REMOVED, INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED "entry-moved"
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_ENTRY_MOVED, INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED "entry-scrolled"
+#define INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_ENTRY_SCROLLED, INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_SIGNAL_MENU_SHOW "menu-show"
+#define INDICATOR_OBJECT_SIGNAL_MENU_SHOW_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_MENU_SHOW, INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_SIGNAL_SHOW_NOW_CHANGED "show-now-changed"
+#define INDICATOR_OBJECT_SIGNAL_SHOW_NOW_CHANGED_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_SHOW_NOW_CHANGED, INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE "accessible-desc-update"
+#define INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE, INDICATOR_OBJECT_TYPE))
+#define INDICATOR_OBJECT_SIGNAL_SECONDARY_ACTIVATE "secondary-activate"
+#define INDICATOR_OBJECT_SIGNAL_SECONDARY_ACTIVATE_ID (g_signal_lookup(INDICATOR_OBJECT_SIGNAL_SECONDARY_ACTIVATE, INDICATOR_OBJECT_TYPE))
+
+/* the name of the property to decide whether or not entries are visible by default */
+#define INDICATOR_OBJECT_DEFAULT_VISIBILITY "indicator-object-default-visibility"
+
+typedef struct _IndicatorObject IndicatorObject;
+typedef struct _IndicatorObjectClass IndicatorObjectClass;
+typedef struct _IndicatorObjectPrivate IndicatorObjectPrivate;
+typedef struct _IndicatorObjectEntry IndicatorObjectEntry;
+
+/**
+ IndicatorObjectClass:
+ @parent_class: #GObjectClass
+ @get_label: Gets the label for this object. Should be set
+ to #NULL if @get_entries is set. Should NOT ref the
+ object.
+ @get_image: Gets the image for this object. Should be set
+ to #NULL if @get_entries is set. Should NOT ref the
+ object.
+ @get_menu: Gets the image for this object. Should be set
+ to #NULL if @get_entries is set. Should NOT ref the
+ object.
+ @get_accessible_desc: Gets the accessible descriptionfor this
+ object.
+ @get_name_hint: Gets the hint of the type of indicator that this
+ is for the caller.
+ @get_entries: Gets all of the entires for this object returning
+ a #GList of #IndicatorObjectEntries. The list should be
+ under the ownership of the caller but the entires will
+ not be.
+ @get_location: Returns the location that a particular entry
+ should be placed in. This is really only relevant for
+ indicators that have more than one entry.
+ @get_show_now: Returns whether the entry is requesting to
+ be shown "right now" in that it has something important
+ to tell the user.
+ @entry_being_removed: Called before an entry is removed.
+ The default implementation is to ref and unparent the
+ entry's widgets so that they can be re-added later.
+ @entry_was_added: Called after an entry is added.
+ The default implementation is to unref the entry's widgets if
+ previously reffed by entry_being_removed's default impementation
+ @entry_activate: Should be called when the menus for a given
+ entry are shown to the user.
+ @entry_close: Called when the menu is closed.
+ @entry_added: Slot for #IndicatorObject::entry-added
+ @entry_removed: Slot for #IndicatorObject::entry-removed
+ @entry_moved: Slot for #IndicatorObject::entry-moved
+ @menu_show: Slot for #IndicatorObject::menu-show
+ @entry_scrolled: Slot for #IndicatorObject::entry-scrolled
+ @show_now_changed: Slot for #IndicatorObject::show-now-changed
+ @accessible_desc_update: Slot for #IndicatorObject::accessible-desc-update
+ @secondary_activate: Slot for #IndicatorObject::secondary-activate
+ @get_position: returns the desired position on the panel (0 is right-most), or -1
+*/
+struct _IndicatorObjectClass {
+ GObjectClass parent_class;
+
+ /* Virtual Functions */
+ GtkLabel * (*get_label) (IndicatorObject * io);
+ GtkImage * (*get_image) (IndicatorObject * io);
+ GtkMenu * (*get_menu) (IndicatorObject * io);
+ const gchar * (*get_accessible_desc) (IndicatorObject * io);
+ const gchar * (*get_name_hint) (IndicatorObject * io);
+
+ GList * (*get_entries) (IndicatorObject * io);
+ guint (*get_location) (IndicatorObject * io, IndicatorObjectEntry * entry);
+ gboolean (*get_show_now) (IndicatorObject * io, IndicatorObjectEntry * entry);
+
+ void (*entry_being_removed) (IndicatorObject * io, IndicatorObjectEntry * entry);
+ void (*entry_was_added) (IndicatorObject * io, IndicatorObjectEntry * entry);
+
+ void (*entry_activate) (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp);
+ void (*entry_activate_window) (IndicatorObject * io, IndicatorObjectEntry * entry, guint windowid, guint timestamp);
+ void (*entry_close) (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp);
+
+ /* Signals */
+ void (*entry_added) (IndicatorObject * io, IndicatorObjectEntry * entry, gpointer user_data);
+ void (*entry_removed) (IndicatorObject * io, IndicatorObjectEntry * entry, gpointer user_data);
+ void (*entry_moved) (IndicatorObject * io, IndicatorObjectEntry * entry, guint old_pos, guint new_pos, gpointer user_data);
+ void (*entry_scrolled) (IndicatorObject * io, IndicatorObjectEntry * entry, gint delta, IndicatorScrollDirection direction);
+ void (*menu_show) (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp, gpointer user_data);
+ void (*show_now_changed) (IndicatorObject * io, IndicatorObjectEntry * entry, gboolean show_now_state, gpointer user_data);
+ void (*accessible_desc_update) (IndicatorObject * io, IndicatorObjectEntry * entry, gpointer user_data);
+ void (*secondary_activate) (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp, gpointer user_data);
+
+ gint (*get_position) (IndicatorObject *io);
+ guint (*get_parent_window) (IndicatorObject *io);
+
+ /* Reserved */
+ void (*reserved1) (void);
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+};
+
+/**
+ IndicatorObject:
+ @parent: #GObject
+ @priv: A cached reference to the private data for the
+ instance.
+*/
+struct _IndicatorObject {
+ GObject parent;
+ IndicatorObjectPrivate * priv;
+};
+
+/**
+ IndicatorObjectEntry:
+ @parent_object: The #IndicatorObject that created this entry
+ @label: The label to be shown on the panel
+ @image: The image to be shown on the panel
+ @menu: The menu to be added to the menubar
+ @accessible_desc: The accessible description
+ of the indicator
+ @name_hint: A name to describe the indicator being placed to allow
+ the caller to be more aware of the individual entries.
+ @parent_window: the id of the parent window of the indicator entry (if any).
+
+ @reserved1: Reserved for future use
+ @reserved2: Reserved for future use
+ @reserved3: Reserved for future use
+*/
+struct _IndicatorObjectEntry {
+ IndicatorObject * parent_object;
+ GtkLabel * label;
+ GtkImage * image;
+ GtkMenu * menu;
+ const gchar * accessible_desc;
+ const gchar * name_hint;
+ guint parent_window;
+
+ void (*reserved1) (void);
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+};
+
+GType indicator_object_get_type (void);
+IndicatorObject * indicator_object_new_from_file (const gchar * file);
+
+GList * indicator_object_get_entries (IndicatorObject * io);
+guint indicator_object_get_location (IndicatorObject * io, IndicatorObjectEntry * entry);
+guint indicator_object_get_show_now (IndicatorObject * io, IndicatorObjectEntry * entry);
+void indicator_object_set_visible (IndicatorObject * io, gboolean visible);
+gboolean indicator_object_entry_is_visible (IndicatorObject * io, IndicatorObjectEntry * entry);
+void indicator_object_entry_activate (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp);
+void indicator_object_entry_activate_window (IndicatorObject * io, IndicatorObjectEntry * entry, guint windowid, guint timestamp);
+void indicator_object_entry_close (IndicatorObject * io, IndicatorObjectEntry * entry, guint timestamp);
+gint indicator_object_get_position (IndicatorObject *io);
+
+void indicator_object_set_environment (IndicatorObject * io, GStrv env);
+GStrv indicator_object_get_environment (IndicatorObject * io);
+gboolean indicator_object_check_environment (IndicatorObject * io, const gchar * env);
+
+G_END_DECLS
+
+#endif
diff --git a/src/indicator-service-manager.c b/src/indicator-service-manager.c
new file mode 100644
index 0000000..33bcba9
--- /dev/null
+++ b/src/indicator-service-manager.c
@@ -0,0 +1,704 @@
+/*
+An object used to manage services. Either start them or
+just connect to them.
+
+Copyright 2009 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 <stdlib.h>
+#include <gio/gio.h>
+
+#include "indicator-service-manager.h"
+#include "gen-indicator-service.xml.h"
+#include "dbus-shared.h"
+
+/* Private Stuff */
+/**
+ IndicatorServiceManagerPrivate:
+ @name: The well known dbus name the service should be on.
+ @service_proxy: The proxy to the service itself.
+ @connected: Whether we're connected to the service or not.
+ @this_service_version: The version of the service that we're looking for.
+ @restart_count: The number of times we've restarted this service.
+*/
+typedef struct _IndicatorServiceManagerPrivate IndicatorServiceManagerPrivate;
+struct _IndicatorServiceManagerPrivate {
+ gchar * name;
+ GDBusProxy * service_proxy;
+ GCancellable * service_proxy_cancel;
+ guint name_watcher;
+ gboolean connected;
+ guint this_service_version;
+ guint restart_count;
+ gint restart_source;
+ GCancellable * watch_cancel;
+};
+
+/* Signals Stuff */
+enum {
+ CONNECTION_CHANGE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* If this env variable is set, we don't restart */
+#define TIMEOUT_ENV_NAME "INDICATOR_SERVICE_RESTART_DISABLE"
+#define TIMEOUT_MULTIPLIER 100 /* In ms */
+/* What to reset the restart_count to if we know that we're
+ in a recoverable error condition, but waiting a little bit
+ will probably make things better. 5 ~= 3 sec. */
+#define TIMEOUT_A_LITTLE_WHILE 5
+
+/* Properties */
+/* Enum for the properties so that they can be quickly
+ found and looked up. */
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_VERSION
+};
+
+/* The strings so that they can be slowly looked up. */
+#define PROP_NAME_S "name"
+#define PROP_VERSION_S "version"
+
+/* GDBus Stuff */
+static GDBusNodeInfo * node_info = NULL;
+static GDBusInterfaceInfo * interface_info = NULL;
+
+static void indicator_service_manager_class_init (IndicatorServiceManagerClass *klass);
+static void indicator_service_manager_init (IndicatorServiceManager *self);
+static void indicator_service_manager_dispose (GObject *object);
+static void indicator_service_manager_finalize (GObject *object);
+
+/* Prototypes */
+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 start_service (IndicatorServiceManager * service);
+static void start_service_again (IndicatorServiceManager * manager);
+static void unwatch (GDBusProxy * proxy);
+static void service_proxy_cb (GObject * object, GAsyncResult * res, gpointer user_data);
+static void service_proxy_name_changed (GDBusConnection * connection, const gchar * sender_name, const gchar * object_path, const gchar * interface_name, const gchar * signal_name, GVariant * parameters, gpointer user_data);
+
+G_DEFINE_TYPE_WITH_PRIVATE (IndicatorServiceManager, indicator_service_manager, G_TYPE_OBJECT);
+
+/* Build all of our signals and proxies and tie everything
+ all together. Lovely. */
+static void
+indicator_service_manager_class_init (IndicatorServiceManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = indicator_service_manager_dispose;
+ object_class->finalize = indicator_service_manager_finalize;
+
+ /* Property funcs */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ /**
+ IndicatorServiceManager::connecton-change:
+ @arg0: The #IndicatorServiceManager object
+ @arg1: The state of the connection, TRUE is connected.
+
+ Signaled when the service is connected or disconnected
+ depending on it's previous state.
+ */
+ signals[CONNECTION_CHANGE] = g_signal_new (INDICATOR_SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorServiceManagerClass, connection_change),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN, G_TYPE_NONE);
+
+ /* Properties */
+ g_object_class_install_property(object_class, PROP_NAME,
+ g_param_spec_string(PROP_NAME_S,
+ "The DBus name for the service to monitor",
+ "This is the name that should be used to start a service.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property(object_class, PROP_VERSION,
+ g_param_spec_uint(PROP_VERSION_S,
+ "The version of the service that we're expecting.",
+ "A number to check and reject a service if it gives us the wrong number. This should match across the manager and the service",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* Setting up the DBus interfaces */
+ if (node_info == NULL) {
+ GError * error = NULL;
+
+ node_info = g_dbus_node_info_new_for_xml(_indicator_service, &error);
+ if (error != NULL) {
+ g_error("Unable to parse Indicator Service Interface description: %s", error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (interface_info == NULL) {
+ interface_info = g_dbus_node_info_lookup_interface(node_info, INDICATOR_SERVICE_INTERFACE);
+
+ if (interface_info == NULL) {
+ g_error("Unable to find interface '" INDICATOR_SERVICE_INTERFACE "'");
+ }
+ }
+
+ return;
+}
+
+/* This inits all the variable and sets up the proxy
+ to dbus. It doesn't look for the service as at this
+ point we don't know it's name. */
+static void
+indicator_service_manager_init (IndicatorServiceManager *self)
+{
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(self);
+
+ /* Get the private variables in a decent state */
+ priv->name = NULL;
+ priv->service_proxy = NULL;
+ priv->service_proxy_cancel = NULL;
+ priv->name_watcher = 0;
+ priv->connected = FALSE;
+ priv->this_service_version = 0;
+ priv->restart_count = 0;
+ priv->restart_source = 0;
+ priv->watch_cancel = NULL;
+
+ return;
+}
+
+/* If we're connected this provides all the signals to say
+ that we're about to not be. Then it takes down the proxies
+ and tells the service that we're not interested in being
+ its friend anymore either. */
+static void
+indicator_service_manager_dispose (GObject *object)
+{
+ IndicatorServiceManager * sm = INDICATOR_SERVICE_MANAGER(object);
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(sm);
+
+ /* Removing the idle task to restart if it exists. */
+ if (priv->restart_source != 0) {
+ g_source_remove(priv->restart_source);
+ }
+ /* Block any restart calls */
+ priv->restart_source = -1;
+
+ /* If we were connected we need to make sure to
+ tell people that it's no longer the case. */
+ if (priv->connected) {
+ priv->connected = FALSE;
+ g_signal_emit(object, signals[CONNECTION_CHANGE], 0, FALSE, TRUE);
+ }
+
+ if (priv->name_watcher != 0) {
+ g_dbus_connection_signal_unsubscribe(g_dbus_proxy_get_connection(priv->service_proxy),
+ priv->name_watcher);
+ priv->name_watcher = 0;
+ }
+
+ /* If we're still getting the proxy, stop looking so we
+ can then clean up some more. */
+ if (priv->service_proxy_cancel != NULL) {
+ g_cancellable_cancel(priv->service_proxy_cancel);
+ g_object_unref(priv->service_proxy_cancel);
+ priv->service_proxy_cancel = NULL;
+ }
+
+ /* If we've sent a watch, cancel looking for the reply before
+ sending the unwatch */
+ if (priv->watch_cancel != NULL) {
+ g_cancellable_cancel(priv->watch_cancel);
+ g_object_unref(priv->watch_cancel);
+ priv->watch_cancel = NULL;
+ }
+
+ /* If we have a proxy, tell it we're shutting down. Just
+ to be polite about it. */
+ if (priv->service_proxy != NULL) {
+ unwatch(priv->service_proxy);
+ }
+
+ /* Destory our service proxy, we won't need it. */
+ if (priv->service_proxy != NULL) {
+ g_object_unref(G_OBJECT(priv->service_proxy));
+ priv->service_proxy = NULL;
+ }
+
+ /* Let's see if our parents want to do anything. */
+ G_OBJECT_CLASS (indicator_service_manager_parent_class)->dispose (object);
+ return;
+}
+
+/* Ironically, we don't allocate a lot of memory ourselves. */
+static void
+indicator_service_manager_finalize (GObject *object)
+{
+ IndicatorServiceManager * sm = INDICATOR_SERVICE_MANAGER(object);
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(sm);
+
+ if (priv->name != NULL) {
+ g_free(priv->name);
+ priv->name = NULL;
+ }
+
+ G_OBJECT_CLASS (indicator_service_manager_parent_class)->finalize (object);
+ return;
+}
+
+/* Either copies the name into the private variable or
+ sets the version. Do it wrong and it'll get upset. */
+static void
+set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ IndicatorServiceManager * self = INDICATOR_SERVICE_MANAGER(object);
+ g_return_if_fail(self != NULL);
+
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(self);
+ g_return_if_fail(priv != NULL);
+
+ switch (prop_id) {
+ /* *********************** */
+ case PROP_NAME:
+ if (priv->name != NULL) {
+ g_error("Name can not be set twice!");
+ }
+ priv->name = g_value_dup_string(value);
+ start_service(self);
+ break;
+ /* *********************** */
+ case PROP_VERSION:
+ priv->this_service_version = g_value_get_uint(value);
+ break;
+ /* *********************** */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Grabs the values from the private variables and
+ puts them into the value. */
+static void
+get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ IndicatorServiceManager * self = INDICATOR_SERVICE_MANAGER(object);
+ g_return_if_fail(self != NULL);
+
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(self);
+ g_return_if_fail(priv != NULL);
+
+ switch (prop_id) {
+ /* *********************** */
+ case PROP_NAME:
+ g_value_set_string(value, priv->name);
+ break;
+ /* *********************** */
+ case PROP_VERSION:
+ g_value_set_uint(value, priv->this_service_version);
+ break;
+ /* *********************** */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Small little function to make a long function call a little
+ bit cleaner. */
+static void
+unwatch (GDBusProxy * proxy)
+{
+ g_dbus_proxy_call(proxy,
+ "UnWatch",
+ NULL, /* parameters */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ NULL, /* cancelable */
+ NULL, /* callback */
+ NULL); /* user data */
+ return;
+}
+
+/* A callback from telling a service that we want to watch
+ it. It gives us the service API version and the version
+ of the other APIs it supports. We check both of those.
+ If they don't match then we unwatch it. Otherwise, we
+ signal a connection change to tell the rest of the world
+ that we have a service now. */
+static void
+watch_cb (GObject * object, GAsyncResult * res, gpointer user_data)
+{
+ GError * error = NULL;
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(user_data);
+
+ GVariant * params = g_dbus_proxy_call_finish(G_DBUS_PROXY(object), res, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to set watch on '%s': '%s'", priv->name, error->message);
+ g_error_free(error);
+ start_service_again(INDICATOR_SERVICE_MANAGER(user_data));
+ return;
+ }
+
+ guint service_api_version;
+ guint this_service_version;
+
+ g_variant_get(params, "(uu)", &service_api_version, &this_service_version);
+ g_variant_unref(params);
+
+ /* We've done it, now let's stop counting. */
+ /* Note: we're not checking versions. Because, the hope is that
+ the guy holding the name we want with the wrong version will
+ drop and we can start another service quickly. */
+ priv->restart_count = 0;
+
+ if (service_api_version != INDICATOR_SERVICE_VERSION) {
+ g_warning("Service is using a different version of the service interface. Expecting %d and got %d.", INDICATOR_SERVICE_VERSION, service_api_version);
+ unwatch(priv->service_proxy);
+
+ /* Let's make us wait a little while, then try again */
+ priv->restart_count = TIMEOUT_A_LITTLE_WHILE;
+ start_service_again(INDICATOR_SERVICE_MANAGER(user_data));
+ return;
+ }
+
+ if (this_service_version != priv->this_service_version) {
+ g_warning("Service is using a different API version than the manager. Expecting %d and got %d.", priv->this_service_version, this_service_version);
+ unwatch(priv->service_proxy);
+
+ /* Let's make us wait a little while, then try again */
+ priv->restart_count = TIMEOUT_A_LITTLE_WHILE;
+ start_service_again(INDICATOR_SERVICE_MANAGER(user_data));
+ return;
+ }
+
+ if (!priv->connected) {
+ priv->connected = TRUE;
+ g_signal_emit(G_OBJECT(user_data), signals[CONNECTION_CHANGE], 0, TRUE, TRUE);
+ }
+
+ return;
+}
+
+/* The function that handles getting us connected to the service.
+ In many cases it will start the service, but if the service
+ is already there it just allocates the service proxy and acts
+ like it was no big deal. */
+static void
+start_service (IndicatorServiceManager * service)
+{
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(service);
+
+ g_return_if_fail(priv->name != NULL);
+
+ if (priv->service_proxy_cancel != NULL) {
+ /* A service proxy is being gotten currently */
+ return;
+ }
+
+ if (priv->service_proxy != NULL) {
+ g_object_unref(priv->service_proxy);
+ priv->service_proxy = NULL;
+ }
+
+ priv->service_proxy_cancel = g_cancellable_new();
+
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ interface_info,
+ priv->name,
+ INDICATOR_SERVICE_OBJECT,
+ INDICATOR_SERVICE_INTERFACE,
+ priv->service_proxy_cancel,
+ service_proxy_cb,
+ service);
+
+ return;
+}
+
+/* Callback from trying to create the proxy for the service, this
+ could include starting the service. Sometime it'll fail and
+ we'll try to start that dang service again! */
+static void
+service_proxy_cb (__attribute__((unused)) GObject * object, GAsyncResult * res, gpointer user_data)
+{
+ GError * error = NULL;
+
+ IndicatorServiceManager * service = INDICATOR_SERVICE_MANAGER(user_data);
+ g_return_if_fail(service != NULL);
+
+ GDBusProxy * proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
+
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(user_data);
+
+ if (priv->service_proxy_cancel != NULL) {
+ g_object_unref(priv->service_proxy_cancel);
+ priv->service_proxy_cancel = NULL;
+ }
+
+ if (error != NULL) {
+ /* Unable to create the proxy, eh, let's try again
+ in a bit */
+ g_error_free(error);
+ start_service_again(service);
+ return;
+ }
+
+ gchar * name = g_dbus_proxy_get_name_owner(proxy);
+ if (name == NULL) {
+ /* Hmm, since creating the proxy should start it, it seems very
+ odd that it wouldn't have an owner at this point. But, all
+ we can do is try again. */
+ g_object_unref(proxy);
+ start_service_again(service);
+ return;
+ }
+ g_free(name);
+
+ /* Okay, we're good to grab the proxy at this point, we're
+ sure that it's ours. */
+ priv->service_proxy = proxy;
+
+ /* Signal for drop */
+ priv->name_watcher = g_dbus_connection_signal_subscribe(
+ g_dbus_proxy_get_connection(proxy),
+ "org.freedesktop.DBus",
+ "org.freedesktop.DBus",
+ "NameOwnerChanged",
+ "/org/freedesktop/DBus",
+ g_dbus_proxy_get_name(proxy),
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ service_proxy_name_changed,
+ user_data,
+ NULL);
+
+ /* Build cancelable if we need it */
+ if (priv->watch_cancel == NULL) {
+ priv->watch_cancel = g_cancellable_new();
+ }
+
+ /* Send watch */
+ g_dbus_proxy_call(priv->service_proxy,
+ "Watch",
+ NULL, /* params */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ priv->watch_cancel,
+ watch_cb,
+ user_data);
+
+ return;
+}
+
+/* Responds to the name owner changing of the proxy, this
+ usually means the service died. We're dropping the proxy
+ and recreating it so that it'll restart the service. */
+static void
+service_proxy_name_changed (__attribute__((unused)) GDBusConnection * connection,
+ __attribute__((unused)) const gchar * sender_name,
+ __attribute__((unused)) const gchar * object_path,
+ __attribute__((unused)) const gchar * interface_name,
+ __attribute__((unused)) const gchar * signal_name,
+ GVariant * parameters,
+ gpointer user_data)
+
+{
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(user_data);
+
+ const gchar * new_name = NULL;
+ const gchar * prev_name = NULL;
+ g_variant_get(parameters, "(&s&s&s)", NULL, &prev_name, &new_name);
+
+ if (new_name == NULL || new_name[0] == 0) {
+ if (priv->connected) {
+ priv->connected = FALSE;
+ g_signal_emit(G_OBJECT(user_data), signals[CONNECTION_CHANGE], 0, FALSE, TRUE);
+ }
+
+ start_service_again(INDICATOR_SERVICE_MANAGER(user_data));
+ } else {
+ /* If we weren't connected before, we are now. Let's tell the
+ world! */
+ if (!priv->connected) {
+ priv->connected = TRUE;
+ g_signal_emit(G_OBJECT(user_data), signals[CONNECTION_CHANGE], 0, TRUE, TRUE);
+ }
+
+ /* If the names are both valid, and they're not the same, it means that
+ we've actually changed. So we need to tell the new guy that we're
+ watching them */
+ if (new_name != NULL && prev_name != NULL && new_name[0] != 0 && prev_name != 0 && g_strcmp0(prev_name, new_name) != 0) {
+ /* Send watch */
+ g_dbus_proxy_call(priv->service_proxy,
+ "Watch",
+ NULL, /* params */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ priv->watch_cancel,
+ watch_cb,
+ user_data);
+ }
+ }
+
+ return;
+}
+
+/* The callback that starts the service for real after
+ the timeout as determined in 'start_service_again'.
+ This could be in the idle or a timer. */
+static gboolean
+start_service_again_cb (gpointer data)
+{
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(data);
+ priv->restart_count++;
+ g_debug("Restarting service '%s' count %d", priv->name, priv->restart_count);
+ start_service(INDICATOR_SERVICE_MANAGER(data));
+ priv->restart_source = 0;
+ return FALSE;
+}
+
+/* This function tries to start a new service, perhaps
+ after a timeout that it determines. The real issue
+ here is that it throttles restarting if we're not
+ being successful. */
+static void
+start_service_again (IndicatorServiceManager * manager)
+{
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(manager);
+
+ /* If we've already got a restart source running then
+ let's not do this again. */
+ if (priv->restart_source != 0) {
+ return;
+ }
+
+ /* Allow the restarting to be disabled */
+ if (g_getenv(TIMEOUT_ENV_NAME)) {
+ return;
+ }
+
+ if (priv->restart_count == 0) {
+ /* First time, do it in idle */
+ g_idle_add(start_service_again_cb, manager);
+ } else {
+ /* Not our first time 'round the block. Let's slow this down. */
+ if (priv->restart_count > 16)
+ priv->restart_count = 16; /* Not more than 1024x */
+ priv->restart_source = g_timeout_add((1 << priv->restart_count) * TIMEOUT_MULTIPLIER, start_service_again_cb, manager);
+ }
+
+ return;
+}
+
+/* API */
+
+/**
+ indicator_service_manager_new:
+ @dbus_name: The well known name of the service on DBus
+
+ This creates a new service manager object. If the service
+ is not running it will start it. No matter what, it will
+ give a IndicatorServiceManager::connection-changed event
+ signal when it gets connected.
+
+ Return value: A brand new lovely #IndicatorServiceManager
+ object.
+*/
+IndicatorServiceManager *
+indicator_service_manager_new (const gchar * dbus_name)
+{
+ GObject * obj = g_object_new(INDICATOR_SERVICE_MANAGER_TYPE,
+ PROP_NAME_S, dbus_name,
+ NULL);
+
+ return INDICATOR_SERVICE_MANAGER(obj);
+}
+
+/**
+ inicator_service_manager_new_version:
+ @dbus_name: The well known name of the service on DBus
+ @version: Version of the service we expect
+
+ This creates a new service manager object. It also sets
+ the version of the service that we're expecting to see.
+ In general, it behaves similarly to #indicator_service_manager_new()
+ except that it checks @version against the version returned
+ by the service.
+
+ Return value: A brand new lovely #IndicatorServiceManager
+ object.
+*/
+IndicatorServiceManager *
+indicator_service_manager_new_version (const gchar * dbus_name, guint version)
+{
+ GObject * obj = g_object_new(INDICATOR_SERVICE_MANAGER_TYPE,
+ PROP_NAME_S, dbus_name,
+ PROP_VERSION_S, version,
+ NULL);
+
+ return INDICATOR_SERVICE_MANAGER(obj);
+}
+
+/**
+ indicator_service_manager_connected:
+ @sm: #IndicatorServiceManager object to check
+
+ Checks to see if the service manager is connected to a
+ service.
+
+ Return value: #TRUE if there is a service connceted.
+*/
+gboolean
+indicator_service_manager_connected (IndicatorServiceManager * sm)
+{
+ g_return_val_if_fail(INDICATOR_IS_SERVICE_MANAGER(sm), FALSE);
+ IndicatorServiceManagerPrivate * priv = indicator_service_manager_get_instance_private(sm);
+ return priv->connected;
+}
+
+/**
+ indicator_service_manager_set_refresh:
+ @sm: #IndicatorServiceManager object to configure
+ @time_in_ms: The refresh time in milliseconds
+
+ Use this function to set the amount of time between restarting
+ services that may crash or shutdown. This is mostly useful
+ for testing and development.
+
+ NOTE: Not yet implemented.
+*/
+void
+indicator_service_manager_set_refresh (__attribute__((unused)) IndicatorServiceManager * sm, __attribute__((unused)) guint time_in_ms)
+{
+
+ return;
+}
diff --git a/src/indicator-service-manager.h b/src/indicator-service-manager.h
new file mode 100644
index 0000000..7d444c6
--- /dev/null
+++ b/src/indicator-service-manager.h
@@ -0,0 +1,88 @@
+/*
+An object used to manage services. Either start them or
+just connect to them.
+
+Copyright 2009 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_SERVICE_MANAGER_H__
+#define __INDICATOR_SERVICE_MANAGER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define INDICATOR_SERVICE_MANAGER_TYPE (indicator_service_manager_get_type ())
+#define INDICATOR_SERVICE_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_SERVICE_MANAGER_TYPE, IndicatorServiceManager))
+#define INDICATOR_SERVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_SERVICE_MANAGER_TYPE, IndicatorServiceManagerClass))
+#define INDICATOR_IS_SERVICE_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_SERVICE_MANAGER_TYPE))
+#define INDICATOR_IS_SERVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_SERVICE_MANAGER_TYPE))
+#define INDICATOR_SERVICE_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_SERVICE_MANAGER_TYPE, IndicatorServiceManagerClass))
+
+#define INDICATOR_SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE "connection-change"
+
+typedef struct _IndicatorServiceManager IndicatorServiceManager;
+typedef struct _IndicatorServiceManagerClass IndicatorServiceManagerClass;
+
+/**
+ IndicatorServiceManagerClass:
+ @parent: #GObjectClass
+ @connection_changed: Slot for #IndicatorServiceManager::connection-changed.
+ @indicator_service_manager_reserved1: Reserved for future use.
+ @indicator_service_manager_reserved2: Reserved for future use.
+ @indicator_service_manager_reserved3: Reserved for future use.
+ @indicator_service_manager_reserved4: Reserved for future use.
+
+*/
+struct _IndicatorServiceManagerClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*connection_change) (IndicatorServiceManager * sm, gboolean connected, gpointer user_data);
+
+ /* Buffer */
+ void (*indicator_service_manager_reserved1) (void);
+ void (*indicator_service_manager_reserved2) (void);
+ void (*indicator_service_manager_reserved3) (void);
+ void (*indicator_service_manager_reserved4) (void);
+};
+
+/**
+ IndicatorServiceManager:
+ @parent: #GObject
+
+*/
+struct _IndicatorServiceManager {
+ GObject parent;
+
+};
+
+GType indicator_service_manager_get_type (void);
+
+IndicatorServiceManager * indicator_service_manager_new (const gchar * dbus_name);
+IndicatorServiceManager * indicator_service_manager_new_version (const gchar * dbus_name,
+ guint version);
+gboolean indicator_service_manager_connected (IndicatorServiceManager * sm);
+void indicator_service_manager_set_refresh (IndicatorServiceManager * sm,
+ guint time_in_ms);
+
+G_END_DECLS
+
+#endif
diff --git a/src/indicator-service.c b/src/indicator-service.c
new file mode 100644
index 0000000..de3a0cf
--- /dev/null
+++ b/src/indicator-service.c
@@ -0,0 +1,649 @@
+/*
+An object used to provide a simple interface for a service
+to query version and manage whether it's running.
+
+Copyright 2009 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 <stdlib.h> /* exit() */
+
+#include <gio/gio.h>
+
+#include "indicator-service.h"
+#include "gen-indicator-service.xml.h"
+#include "dbus-shared.h"
+
+static void unwatch_core (IndicatorService * service, const gchar * name);
+static void watchers_remove (gpointer value);
+static void bus_get_cb (GObject * object, GAsyncResult * res, gpointer user_data);
+static GVariant * bus_watch (IndicatorService * service, const gchar * sender);
+
+/* Private Stuff */
+/**
+ IndicatorSevicePrivate:
+ @name: The DBus well known name for the service.
+ @timeout: The source ID for the timeout event.
+ @watcher: A list of processes on dbus that are watching us.
+ @this_service_version: The version to hand out that we're
+ implementing. May not be set, so we'll send zero (default).
+ @dbus_registration: The handle for this object being registered
+ on dbus.
+*/
+typedef struct _IndicatorServicePrivate IndicatorServicePrivate;
+struct _IndicatorServicePrivate {
+ gchar * name;
+ GDBusConnection * bus;
+ GCancellable * bus_cancel;
+ guint timeout;
+ guint timeout_length;
+ GHashTable * watchers;
+ guint this_service_version;
+ guint dbus_registration;
+ gboolean replace_mode;
+};
+
+/* Signals Stuff */
+enum {
+ SHUTDOWN,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/* Properties */
+/* Enum for the properties so that they can be quickly
+ found and looked up. */
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_VERSION
+};
+
+/* The strings so that they can be slowly looked up. */
+#define PROP_NAME_S "name"
+#define PROP_VERSION_S "version"
+
+/* GObject Stuff */
+#define INDICATOR_SERVICE_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), INDICATOR_SERVICE_TYPE, IndicatorServicePrivate))
+
+static void indicator_service_class_init (IndicatorServiceClass *klass);
+static void indicator_service_init (IndicatorService *self);
+static void indicator_service_dispose (GObject *object);
+static void indicator_service_finalize (GObject *object);
+
+/* Other prototypes */
+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 try_and_get_name (IndicatorService * service);
+static void bus_method_call (GDBusConnection * connection, const gchar * sender, const gchar * path, const gchar * interface, const gchar * method, GVariant * params, GDBusMethodInvocation * invocation, gpointer user_data);
+
+/* GDBus Stuff */
+static GDBusNodeInfo * node_info = NULL;
+static GDBusInterfaceInfo * interface_info = NULL;
+static GDBusInterfaceVTable interface_table = {
+ .method_call = bus_method_call,
+ .get_property = NULL, /* No properties */
+ .set_property = NULL /* No properties */
+};
+
+/* THE define */
+G_DEFINE_TYPE_WITH_PRIVATE (IndicatorService, indicator_service, G_TYPE_OBJECT);
+
+static void
+indicator_service_class_init (IndicatorServiceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = indicator_service_dispose;
+ object_class->finalize = indicator_service_finalize;
+
+ /* Property funcs */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ /* Properties */
+ g_object_class_install_property(object_class, PROP_NAME,
+ g_param_spec_string(PROP_NAME_S,
+ "The DBus name for this service",
+ "This is the name that should be used on DBus for this service.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property(object_class, PROP_VERSION,
+ g_param_spec_uint(PROP_VERSION_S,
+ "The version of the service that we're implementing.",
+ "A number to represent the version of the other APIs the service provides. This should match across the manager and the service",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* Signals */
+
+ /**
+ IndicatorService::shutdown:
+ @arg0: The #IndicatorService object
+
+ Signaled when the service should shutdown as no one
+ is listening anymore.
+ */
+ signals[SHUTDOWN] = g_signal_new (INDICATOR_SERVICE_SIGNAL_SHUTDOWN,
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IndicatorServiceClass, shutdown),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ /* Setting up the DBus interfaces */
+ if (node_info == NULL) {
+ GError * error = NULL;
+
+ node_info = g_dbus_node_info_new_for_xml(_indicator_service, &error);
+ if (error != NULL) {
+ g_error("Unable to parse Indicator Service Interface description: %s", error->message);
+ g_error_free(error);
+ }
+ }
+
+ if (interface_info == NULL) {
+ interface_info = g_dbus_node_info_lookup_interface(node_info, INDICATOR_SERVICE_INTERFACE);
+
+ if (interface_info == NULL) {
+ g_error("Unable to find interface '" INDICATOR_SERVICE_INTERFACE "'");
+ }
+ }
+
+ return;
+}
+
+/* This function builds the variables, sets up the dbus
+ proxy and registers the object on dbus. Importantly,
+ it does not request a name as we don't know what name
+ we have yet. */
+static void
+indicator_service_init (IndicatorService *self)
+{
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(self);
+
+ /* Get the private variables in a decent state */
+ priv->name = NULL;
+ priv->timeout = 0;
+ priv->watchers = NULL;
+ priv->bus = NULL;
+ priv->bus_cancel = NULL;
+ priv->this_service_version = 0;
+ priv->timeout_length = 500;
+ priv->dbus_registration = 0;
+ priv->replace_mode = FALSE;
+
+ const gchar * timeoutenv = g_getenv("INDICATOR_SERVICE_SHUTDOWN_TIMEOUT");
+ if (timeoutenv != NULL) {
+ gdouble newtimeout = g_strtod(timeoutenv, NULL);
+ if (newtimeout >= 1.0f) {
+ priv->timeout_length = newtimeout;
+ g_debug("Setting shutdown timeout to: %u", priv->timeout_length);
+ }
+ }
+
+ const gchar * replaceenv = g_getenv("INDICATOR_SERVICE_REPLACE_MODE");
+ if (replaceenv != NULL) {
+ priv->replace_mode = TRUE;
+ g_debug("Putting into replace mode");
+ }
+
+ /* NOTE: We're using g_free here because that's what needs to
+ happen and we're watchers_remove as well to clean up the dbus
+ watches we've setup. */
+ priv->watchers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, watchers_remove);
+
+ priv->bus_cancel = g_cancellable_new();
+ g_bus_get(G_BUS_TYPE_SESSION,
+ priv->bus_cancel,
+ bus_get_cb,
+ self);
+
+ return;
+}
+
+/* Unrefcounting the proxies and making sure that our
+ timeout doesn't come to haunt us. */
+static void
+indicator_service_dispose (GObject *object)
+{
+ IndicatorService * service = INDICATOR_SERVICE(object);
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(service);
+
+ g_clear_pointer (&priv->watchers, g_hash_table_destroy);
+
+ if (priv->timeout != 0) {
+ g_source_remove(priv->timeout);
+ priv->timeout = 0;
+ }
+
+ if (priv->dbus_registration != 0) {
+ g_dbus_connection_unregister_object(priv->bus, priv->dbus_registration);
+ /* Don't care if it fails, there's nothing we can do */
+ priv->dbus_registration = 0;
+ }
+
+ g_clear_object (&priv->bus);
+
+ if (priv->bus_cancel != NULL) {
+ g_cancellable_cancel(priv->bus_cancel);
+ g_object_unref(priv->bus_cancel);
+ priv->bus_cancel = NULL;
+ }
+
+ G_OBJECT_CLASS (indicator_service_parent_class)->dispose (object);
+ return;
+}
+
+/* Freeing the name we're looking for and all of the
+ information on the watchers we're tracking. */
+static void
+indicator_service_finalize (GObject *object)
+{
+ IndicatorService * service = INDICATOR_SERVICE(object);
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(service);
+
+ g_free (priv->name);
+ g_clear_pointer (&priv->watchers, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (indicator_service_parent_class)->finalize (object);
+ return;
+}
+
+/* Either copies a string for the name or it just grabs
+ the value of the version. */
+static void
+set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ IndicatorService * self = INDICATOR_SERVICE(object);
+ g_return_if_fail(self != NULL);
+
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(self);
+ g_return_if_fail(priv != NULL);
+
+ switch (prop_id) {
+ /* *********************** */
+ case PROP_NAME:
+ if (G_VALUE_HOLDS_STRING(value)) {
+ if (priv->name != NULL) {
+ g_error("Name can not be set twice!");
+ }
+ priv->name = g_value_dup_string(value);
+ try_and_get_name(self);
+ } else {
+ g_warning("Name property requires a string value.");
+ }
+ break;
+ /* *********************** */
+ case PROP_VERSION:
+ priv->this_service_version = g_value_get_uint(value);
+ break;
+ /* *********************** */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Copies out the name into a value or the version number.
+ Probably this is the least useful code in this file. */
+static void
+get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ IndicatorService * self = INDICATOR_SERVICE(object);
+ g_return_if_fail(self != NULL);
+
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(self);
+ g_return_if_fail(priv != NULL);
+
+ switch (prop_id) {
+ /* *********************** */
+ case PROP_NAME:
+ if (G_VALUE_HOLDS_STRING(value)) {
+ g_value_set_string(value, priv->name);
+ } else {
+ g_warning("Name property requires a string value.");
+ }
+ break;
+ /* *********************** */
+ case PROP_VERSION:
+ g_value_set_uint(value, priv->this_service_version);
+ break;
+ /* *********************** */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ return;
+}
+
+/* Callback for getting our connection to DBus */
+static void
+bus_get_cb (__attribute__((unused)) GObject * object, GAsyncResult * res, gpointer user_data)
+{
+ GError * error = NULL;
+ GDBusConnection * connection = g_bus_get_finish(res, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to get a connection to the session DBus: %s", error->message);
+ g_error_free(error);
+ exit (0);
+ }
+
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(user_data);
+
+ g_warn_if_fail(priv->bus == NULL);
+ priv->bus = connection;
+
+ if (priv->bus_cancel != NULL) {
+ g_object_unref(priv->bus_cancel);
+ priv->bus_cancel = NULL;
+ }
+
+ /* Now register our object on our new connection */
+ priv->dbus_registration = g_dbus_connection_register_object(priv->bus,
+ INDICATOR_SERVICE_OBJECT,
+ interface_info,
+ &interface_table,
+ user_data,
+ NULL,
+ &error);
+ if (error != NULL) {
+ g_error("Unable to register the object to DBus: %s", error->message);
+ }
+
+ return;
+}
+
+/* A method has been called from our dbus inteface. Figure out what it
+ is and dispatch it. */
+static void
+bus_method_call (__attribute__((unused)) GDBusConnection * connection, const gchar * sender, __attribute__((unused)) const gchar * path, __attribute__((unused)) const gchar * interface, const gchar * method, __attribute__((unused)) GVariant * params, GDBusMethodInvocation * invocation, gpointer user_data)
+{
+ IndicatorService * service = INDICATOR_SERVICE(user_data);
+ GVariant * retval = NULL;
+
+ if (g_strcmp0(method, "Watch") == 0) {
+ retval = bus_watch(service, sender);
+ } else if (g_strcmp0(method, "UnWatch") == 0) {
+ unwatch_core(service, sender);
+ } else if (g_strcmp0(method, "Shutdown") == 0) {
+ g_signal_emit(G_OBJECT(service), signals[SHUTDOWN], 0, TRUE);
+ } else {
+ g_warning("Calling method '%s' on the indicator service and it's unknown", method);
+ }
+
+ g_dbus_method_invocation_return_value(invocation, retval);
+ return;
+}
+
+/* A function to remove the signals on a proxy before we destroy
+ it because in this case we've stopped caring. */
+static void
+watchers_remove (gpointer value)
+{
+ g_bus_unwatch_name(GPOINTER_TO_UINT(value));
+ return;
+}
+
+/* This is the function that gets executed if we timeout
+ because there are no watchers. We sent the shutdown
+ signal and hope someone does something sane with it. */
+static gboolean
+timeout_no_watchers (gpointer data)
+{
+ g_warning("No watchers, service timing out.");
+ if (g_getenv("INDICATOR_ALLOW_NO_WATCHERS") == NULL) {
+ g_signal_emit(G_OBJECT(data), signals[SHUTDOWN], 0, TRUE);
+ } else {
+ g_warning("\tblocked by environment variable.");
+ }
+ return FALSE;
+}
+
+/* Callback saying that the name we were looking for has been
+ found and we've got it. Now start the timer to see if anyone
+ cares about us. */
+static void
+try_and_get_name_acquired_cb (GDBusConnection * connection, __attribute__((unused)) const gchar * name, gpointer user_data)
+{
+ g_return_if_fail(connection != NULL);
+ g_return_if_fail(INDICATOR_IS_SERVICE(user_data));
+
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(user_data);
+
+ /* Check to see if we already had a timer, if so we want to
+ extend it a bit. */
+ if (priv->timeout != 0) {
+ g_source_remove(priv->timeout);
+ priv->timeout = 0;
+ }
+
+ /* Allow some extra time at start up as things can be in high
+ contention then. */
+ priv->timeout = g_timeout_add(priv->timeout_length * 2, timeout_no_watchers, user_data);
+
+ return;
+}
+
+/* Callback saying that we didn't get the name, so we need to
+ shutdown this service. */
+static void
+try_and_get_name_lost_cb (GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_return_if_fail(connection != NULL);
+ g_return_if_fail(INDICATOR_IS_SERVICE(user_data));
+
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(user_data);
+
+ if (!priv->replace_mode) {
+ g_warning("Name request failed.");
+ g_signal_emit(G_OBJECT(user_data), signals[SHUTDOWN], 0, TRUE);
+ } else {
+ /* If we're in replace mode we can be a little more trickey
+ here. We're going to tell the other guy to shutdown and hope
+ that we get the name. */
+ GDBusMessage * message = NULL;
+ message = g_dbus_message_new_method_call(name,
+ INDICATOR_SERVICE_OBJECT,
+ INDICATOR_SERVICE_INTERFACE,
+ "Shutdown");
+
+ g_dbus_connection_send_message(connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL);
+ g_object_unref(message);
+
+ /* Check to see if we need to clean up a timeout */
+ if (priv->timeout != 0) {
+ g_source_remove(priv->timeout);
+ priv->timeout = 0;
+ }
+
+ /* Set a timeout for no watchers if we can't get the name */
+ priv->timeout = g_timeout_add(priv->timeout_length * 4, timeout_no_watchers, user_data);
+ }
+
+ return;
+}
+
+/* This function sets up the request for the name on dbus. */
+static void
+try_and_get_name (IndicatorService * service)
+{
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(service);
+ g_return_if_fail(priv->name != NULL);
+
+ g_bus_own_name(G_BUS_TYPE_SESSION,
+ priv->name,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL, /* bus acquired */
+ try_and_get_name_acquired_cb, /* name acquired */
+ try_and_get_name_lost_cb, /* name lost */
+ service,
+ NULL); /* user data destroy */
+
+ return;
+}
+
+/* When the watcher vanishes we don't really care about it
+ anymore. */
+static void
+watcher_vanished_cb (__attribute__((unused)) GDBusConnection * connection, const gchar * name, gpointer user_data)
+{
+ g_return_if_fail(INDICATOR_IS_SERVICE(user_data));
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(user_data);
+
+ gpointer finddata = g_hash_table_lookup(priv->watchers, name);
+ if (finddata != NULL) {
+ unwatch_core(INDICATOR_SERVICE(user_data), name);
+ } else {
+ g_warning("Odd, we were watching for '%s' and it disappeard, but then it wasn't in the hashtable.", name);
+ }
+
+ return;
+}
+
+/* Here is the function that gets called by the dbus
+ interface "Watch" function. It is an async function so
+ that we can get the sender and store that information. We
+ put them in a list and reset the timeout. */
+static GVariant *
+bus_watch (IndicatorService * service, const gchar * sender)
+{
+ g_return_val_if_fail(INDICATOR_IS_SERVICE(service), NULL);
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(service);
+
+ if (GPOINTER_TO_UINT(g_hash_table_lookup(priv->watchers, sender)) == 0) {
+ guint watch = g_bus_watch_name_on_connection(priv->bus,
+ sender,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL, /* appeared, we dont' care, should have already happened. */
+ watcher_vanished_cb,
+ service,
+ NULL);
+
+ if (watch != 0) {
+ g_hash_table_insert(priv->watchers, g_strdup(sender), GUINT_TO_POINTER(watch));
+ } else {
+ g_warning("Unable watch for '%s'", sender);
+ }
+ }
+
+ if (priv->timeout != 0) {
+ g_source_remove(priv->timeout);
+ priv->timeout = 0;
+ }
+
+ return g_variant_new("(uu)", INDICATOR_SERVICE_VERSION, priv->this_service_version);
+}
+
+/* Performs the core of loosing a watcher; it removes them
+ from the list of watchers. If there are none left, it then
+ starts the timer for the shutdown signal. */
+static void
+unwatch_core (IndicatorService * service, const gchar * name)
+{
+ g_return_if_fail(name != NULL);
+ g_return_if_fail(INDICATOR_IS_SERVICE(service));
+
+ IndicatorServicePrivate * priv = indicator_service_get_instance_private(service);
+
+ /* Remove us from the watcher list here */
+ gpointer watcher_item = g_hash_table_lookup(priv->watchers, name);
+ if (watcher_item != NULL) {
+ gchar * safe_name = g_strdup(name);
+ g_hash_table_remove(priv->watchers, safe_name);
+ g_free(safe_name);
+ } else {
+ /* Odd that we couldn't find the person, but, eh */
+ g_warning("Unable to find watcher who is unwatching: %s", name);
+ }
+
+ /* If we're out of watchers set the timeout for shutdown */
+ if (g_hash_table_size(priv->watchers) == 0) {
+ if (priv->timeout != 0) {
+ /* This should never really happen, but let's ensure that
+ bad things don't happen if it does. */
+ g_warning("No watchers timeout set twice. Resolving, but odd.");
+ g_source_remove(priv->timeout);
+ priv->timeout = 0;
+ }
+ /* If we don't get a new watcher quickly, we'll shutdown. */
+ priv->timeout = g_timeout_add(priv->timeout_length, timeout_no_watchers, service);
+ }
+
+ return;
+}
+
+/* API */
+
+/**
+ indicator_service_new:
+ @name: The name for the service on dbus
+
+ This function creates the service on DBus and tries to
+ get a well-known name specified in @name. If the name
+ can't be estabilished then the #IndicatorService::shutdown
+ signal will be sent.
+
+ Return value: A brand new #IndicatorService object or #NULL
+ if there is an error.
+*/
+IndicatorService *
+indicator_service_new (gchar * name)
+{
+ GObject * obj = g_object_new(INDICATOR_SERVICE_TYPE,
+ PROP_NAME_S, name,
+ NULL);
+
+ return INDICATOR_SERVICE(obj);
+}
+
+/**
+ indicator_service_new_version:
+ @name: The name for the service on dbus
+ @version: The version of the other interfaces provide
+ by the service.
+
+ This function creates the service on DBus and tries to
+ get a well-known name specified in @name. If the name
+ can't be estabilished then the #IndicatorService::shutdown
+ signal will be sent.
+
+ Return value: A brand new #IndicatorService object or #NULL
+ if there is an error.
+*/
+IndicatorService *
+indicator_service_new_version (gchar * name, guint version)
+{
+ GObject * obj = g_object_new(INDICATOR_SERVICE_TYPE,
+ PROP_NAME_S, name,
+ PROP_VERSION_S, version,
+ NULL);
+
+ return INDICATOR_SERVICE(obj);
+}
diff --git a/src/indicator-service.h b/src/indicator-service.h
new file mode 100644
index 0000000..bda9cb7
--- /dev/null
+++ b/src/indicator-service.h
@@ -0,0 +1,85 @@
+/*
+An object used to provide a simple interface for a service
+to query version and manage whether it's running.
+
+Copyright 2009 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_SERVICE_H__
+#define __INDICATOR_SERVICE_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define INDICATOR_SERVICE_TYPE (indicator_service_get_type ())
+#define INDICATOR_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_SERVICE_TYPE, IndicatorService))
+#define INDICATOR_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_SERVICE_TYPE, IndicatorServiceClass))
+#define INDICATOR_IS_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_SERVICE_TYPE))
+#define INDICATOR_IS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_SERVICE_TYPE))
+#define INDICATOR_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_SERVICE_TYPE, IndicatorServiceClass))
+
+#define INDICATOR_SERVICE_SIGNAL_SHUTDOWN "shutdown"
+
+typedef struct _IndicatorService IndicatorService;
+typedef struct _IndicatorServiceClass IndicatorServiceClass;
+
+/**
+ IndicatorServiceClass:
+ @parent_class: #GObjectClass
+ @shutdown: Slot for IndicatorServiceClass::shutdown
+ @indicator_service_reserved1: Reserved for future use
+ @indicator_service_reserved2: Reserved for future use
+ @indicator_service_reserved3: Reserved for future use
+ @indicator_service_reserved4: Reserved for future use
+
+*/
+struct _IndicatorServiceClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*shutdown) (IndicatorService * service, gpointer user_data);
+
+ /* Reserved */
+ void (*indicator_service_reserved1) (void);
+ void (*indicator_service_reserved2) (void);
+ void (*indicator_service_reserved3) (void);
+ void (*indicator_service_reserved4) (void);
+};
+
+/**
+ IndicatorService:
+ @parent: #GObject
+
+*/
+struct _IndicatorService {
+ GObject parent;
+
+};
+
+GType indicator_service_get_type (void);
+
+IndicatorService * indicator_service_new (gchar * name);
+IndicatorService * indicator_service_new_version (gchar * name,
+ guint version);
+
+G_END_DECLS
+
+#endif
diff --git a/src/indicator-service.xml b/src/indicator-service.xml
new file mode 100644
index 0000000..71ef4df
--- /dev/null
+++ b/src/indicator-service.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/">
+ <interface name="org.ayatana.indicator.service">
+<!-- Properties -->
+ <!-- None currently -->
+
+<!-- Methods -->
+ <method name="Watch">
+ <annotation name="org.freedesktop.DBus.GLib.Async" value="true" />
+ <arg type="u" name="version" direction="out" />
+ <arg type="u" name="service_version" direction="out" />
+ </method>
+ <method name="UnWatch">
+ <annotation name="org.freedesktop.DBus.GLib.Async" value="true" />
+ </method>
+ <method name="Shutdown" />
+
+<!-- Signals -->
+ <!-- None currently -->
+
+ </interface>
+</node>
diff --git a/src/indicator.h b/src/indicator.h
new file mode 100644
index 0000000..8bff270
--- /dev/null
+++ b/src/indicator.h
@@ -0,0 +1,41 @@
+/*
+An interface for indicators to link to for creation.
+
+Copyright 2009 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 __LIBINDICATOR_INDICATOR_H_SEEN__
+#define __LIBINDICATOR_INDICATOR_H_SEEN__ 1
+
+#include <gtk/gtk.h>
+
+#define INDICATOR_GET_VERSION_S "get_version"
+typedef gchar * (*get_version_t) (void);
+gchar * get_version (void);
+
+#define INDICATOR_VERSION "0.3.0"
+#define INDICATOR_SET_VERSION gchar * get_version(void) { return INDICATOR_VERSION; }
+#define INDICATOR_VERSION_CHECK(x) (!g_strcmp0(x, INDICATOR_VERSION))
+
+#define INDICATOR_GET_TYPE_S "get_type"
+typedef GType (*get_type_t) (void);
+#define INDICATOR_SET_TYPE(x) GType get_type (void) { return x; }
+
+#endif /* __LIBINDICATOR_INDICATOR_H_SEEN__ */
+