diff options
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/gactionmuxer.c | 456 | ||||
-rw-r--r-- | src/gactionmuxer.h | 44 | ||||
-rw-r--r-- | src/messages-service.c | 7 | ||||
-rw-r--r-- | test/Makefile.am | 6 |
5 files changed, 513 insertions, 5 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1dbb514..183d729 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -45,9 +45,10 @@ indicator_messages_service_SOURCES = \ app-section.c \ app-section.h \ dbus-data.h \ - \ status-items.c \ - status-items.h + status-items.h \ + gactionmuxer.c \ + gactionmuxer.h indicator_messages_service_CFLAGS = \ $(APPLET_CFLAGS) \ diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c new file mode 100644 index 0000000..cccfeda --- /dev/null +++ b/src/gactionmuxer.c @@ -0,0 +1,456 @@ +/* + * Copyright 2012 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> + * Ryan Lortie <desrt@desrt.ca> + */ + +#include "gactionmuxer.h" + +#include <string.h> + +/* + * SECTION:gactionmuxer + * @short_description: Aggregate several action groups + * + * #GActionMuxer is a #GActionGroup that is capable of containing other + * #GActionGroup instances. + * + * The typical use is aggregating all of the actions applicable to a + * particular context into a single action group, with namespacing. + * + * Consider the case of two action groups -- one containing actions + * applicable to an entire application (such as 'quit') and one + * containing actions applicable to a particular window in the + * application (such as 'fullscreen'). + * + * In this case, each of these action groups could be added to a + * #GActionMuxer with the prefixes "app" and "win", respectively. This + * would expose the actions as "app.quit" and "win.fullscreen" on the + * #GActionGroup interface presented by the #GActionMuxer. + * + * Activations and state change requests on the #GActionMuxer are wired + * through to the underlying action group in the expected way. + */ + +typedef GObjectClass GActionMuxerClass; + +struct _GActionMuxer +{ + GObject parent; + GActionGroup *global_actions; + GHashTable *groups; /* prefix -> subgroup */ + GHashTable *reverse; /* subgroup -> prefix */ +}; + + +static void g_action_muxer_group_init (GActionGroupInterface *iface); +static void g_action_muxer_dispose (GObject *object); +static void g_action_muxer_finalize (GObject *object); +static gchar ** g_action_muxer_list_actions (GActionGroup *group); +static void g_action_muxer_activate_action (GActionGroup *group, + const gchar *action_name, + GVariant *parameter); +static void g_action_muxer_change_action_state (GActionGroup *group, + const gchar *action_name, + GVariant *value); +static gboolean g_action_muxer_query_action (GActionGroup *group, + const gchar *action_name, + gboolean *enabled, + const GVariantType **parameter_type, + const GVariantType **state_type, + GVariant **state_hint, + GVariant **state); +static void g_action_muxer_action_added (GActionGroup *group, + gchar *action_name, + gpointer user_data); +static void g_action_muxer_action_removed (GActionGroup *group, + gchar *action_name, + gpointer user_data); +static void g_action_muxer_action_state_changed (GActionGroup *group, + gchar *action_name, + GVariant *value, + gpointer user_data); +static void g_action_muxer_action_enabled_changed (GActionGroup *group, + gchar *action_name, + gboolean enabled, + gpointer user_data); + +G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_init)); + + +static void +g_action_muxer_class_init (GObjectClass *klass) +{ + klass->dispose = g_action_muxer_dispose; + klass->finalize = g_action_muxer_finalize; +} + +static void +g_action_muxer_init (GActionMuxer *muxer) +{ + muxer->global_actions = NULL; + muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + muxer->reverse = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void +g_action_muxer_group_init (GActionGroupInterface *iface) +{ + iface->list_actions = g_action_muxer_list_actions; + iface->activate_action = g_action_muxer_activate_action; + iface->change_action_state = g_action_muxer_change_action_state; + iface->query_action = g_action_muxer_query_action; +} + +static void +g_action_muxer_dispose (GObject *object) +{ + GActionMuxer *muxer = G_ACTION_MUXER (object); + GHashTableIter it; + gchar *prefix; + + g_clear_object (&muxer->global_actions); + + g_hash_table_iter_init (&it, muxer->groups); + while (g_hash_table_iter_next (&it, (gpointer *) &prefix, NULL)) + g_action_muxer_remove (muxer, prefix); +} + +static void +g_action_muxer_finalize (GObject *object) +{ + GActionMuxer *muxer = G_ACTION_MUXER (object); + + g_hash_table_unref (muxer->groups); + g_hash_table_unref (muxer->reverse); + + G_OBJECT_CLASS (g_action_muxer_parent_class)->finalize (object); +} + +static GActionGroup * +g_action_muxer_lookup_group (GActionMuxer *muxer, + const gchar *full_name, + const gchar **action_name) +{ + const gchar *sep; + GActionGroup *group; + + sep = strchr (full_name, '.'); + + if (sep) + { + gchar *prefix; + prefix = g_strndup (full_name, sep - full_name); + group = g_hash_table_lookup (muxer->groups, prefix); + g_free (prefix); + if (action_name) + *action_name = sep + 1; + } + else + { + group = muxer->global_actions; + if (action_name) + *action_name = full_name; + } + + return group; +} + +static gchar * +g_action_muxer_lookup_full_name (GActionMuxer *muxer, + GActionGroup *subgroup, + const gchar *action_name) +{ + gpointer prefix; + + if (subgroup == muxer->global_actions) + return g_strdup (action_name); + + if (g_hash_table_lookup_extended (muxer->reverse, subgroup, NULL, &prefix)) + return g_strdup_printf ("%s.%s", (gchar *) prefix, action_name); + + return NULL; +} + +static gchar ** +g_action_muxer_list_actions (GActionGroup *group) +{ + GActionMuxer *muxer = G_ACTION_MUXER (group); + GHashTableIter it; + GArray *all_actions; + gchar *prefix; + GActionGroup *subgroup; + gchar **actions; + gchar **a; + + all_actions = g_array_sized_new (TRUE, FALSE, sizeof (gchar *), 8); + + actions = g_action_group_list_actions (muxer->global_actions); + for (a = actions; *a; a++) + { + gchar *name = g_strdup (*a); + g_array_append_val (all_actions, name); + } + g_strfreev (actions); + + g_hash_table_iter_init (&it, muxer->groups); + while (g_hash_table_iter_next (&it, (gpointer *) &prefix, (gpointer *) &subgroup)) + { + actions = g_action_group_list_actions (subgroup); + for (a = actions; *a; a++) + { + gchar *full_name = g_strdup_printf ("%s.%s", prefix, *a); + g_array_append_val (all_actions, full_name); + } + g_strfreev (actions); + } + + return (gchar **) g_array_free (all_actions, FALSE); +} + +static void +g_action_muxer_activate_action (GActionGroup *group, + const gchar *action_name, + GVariant *parameter) +{ + GActionMuxer *muxer = G_ACTION_MUXER (group); + GActionGroup *subgroup; + const gchar *action; + + subgroup = g_action_muxer_lookup_group (muxer, action_name, &action); + + if (subgroup) + g_action_group_activate_action (subgroup, action, parameter); +} + +static void +g_action_muxer_change_action_state (GActionGroup *group, + const gchar *action_name, + GVariant *value) +{ + GActionMuxer *muxer = G_ACTION_MUXER (group); + GActionGroup *subgroup; + const gchar *action; + + subgroup = g_action_muxer_lookup_group (muxer, action_name, &action); + + if (subgroup) + g_action_group_change_action_state (subgroup, action, value); +} + +static gboolean +g_action_muxer_query_action (GActionGroup *group, + const gchar *action_name, + gboolean *enabled, + const GVariantType **parameter_type, + const GVariantType **state_type, + GVariant **state_hint, + GVariant **state) +{ + GActionMuxer *muxer = G_ACTION_MUXER (group); + GActionGroup *subgroup; + const gchar *action; + + subgroup = g_action_muxer_lookup_group (muxer, action_name, &action); + + if (!subgroup) + return FALSE; + + return g_action_group_query_action (subgroup, action, enabled, parameter_type, + state_type, state_hint, state); +} + +static void +g_action_muxer_action_added (GActionGroup *group, + gchar *action_name, + gpointer user_data) +{ + GActionMuxer *muxer = user_data; + gchar *full_name; + + full_name = g_action_muxer_lookup_full_name (muxer, group, action_name); + + if (full_name) + { + g_action_group_action_added (G_ACTION_GROUP (muxer), full_name); + g_free (full_name); + } +} + +static void +g_action_muxer_action_removed (GActionGroup *group, + gchar *action_name, + gpointer user_data) +{ + GActionMuxer *muxer = user_data; + gchar *full_name; + + full_name = g_action_muxer_lookup_full_name (muxer, group, action_name); + + if (full_name) + { + g_action_group_action_removed (G_ACTION_GROUP (muxer), full_name); + g_free (full_name); + } +} + +static void +g_action_muxer_action_state_changed (GActionGroup *group, + gchar *action_name, + GVariant *value, + gpointer user_data) +{ + GActionMuxer *muxer = user_data; + gchar *full_name; + + full_name = g_action_muxer_lookup_full_name (muxer, group, action_name); + + if (full_name) + { + g_action_group_action_state_changed (G_ACTION_GROUP (muxer), full_name, value); + g_free (full_name); + } +} + +static void +g_action_muxer_action_enabled_changed (GActionGroup *group, + gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + GActionMuxer *muxer = user_data; + gchar *full_name; + + full_name = g_action_muxer_lookup_full_name (muxer, group, action_name); + + if (full_name) + { + g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), full_name, enabled); + g_free (full_name); + } +} + +/* + * g_action_muxer_new: + * + * Creates a new #GActionMuxer. + */ +GActionMuxer * +g_action_muxer_new (void) +{ + return g_object_new (G_TYPE_ACTION_MUXER, NULL); +} + +/* + * g_action_muxer_insert: + * @muxer: a #GActionMuxer + * @prefix: (allow-none): the prefix string for the action group, or NULL + * @group: a #GActionGroup + * + * Adds the actions in @group to the list of actions provided by @muxer. + * @prefix is prefixed to each action name, such that for each action + * <varname>x</varname> in @group, there is an equivalent action + * @prefix<literal>.</literal><varname>x</varname> in @muxer. + * + * For example, if @prefix is "<literal>app</literal>" and @group contains an + * action called "<literal>quit</literal>", then @muxer will now contain an + * action called "<literal>app.quit</literal>". + * + * If @prefix is <literal>NULL</literal>, the actions in @group will be added + * to @muxer without prefix. + * + * There may only be one group per prefix (including the + * <literal>NULL</literal>-prefix). If a group has been added with @prefix in + * a previous call to this function, it will be removed. + * + * @prefix must not contain a dot ('.'). + */ +void +g_action_muxer_insert (GActionMuxer *muxer, + const gchar *prefix, + GActionGroup *group) +{ + gchar *prefix_copy; + gchar **actions; + gchar **action; + + g_return_if_fail (G_IS_ACTION_MUXER (muxer)); + g_return_if_fail (G_IS_ACTION_GROUP (group)); + + g_action_muxer_remove (muxer, prefix); + + if (prefix) + { + prefix_copy = g_strdup (prefix); + g_hash_table_insert (muxer->groups, prefix_copy, g_object_ref (group)); + g_hash_table_insert (muxer->reverse, group, prefix_copy); + } + else + muxer->global_actions = g_object_ref (group); + + actions = g_action_group_list_actions (group); + for (action = actions; *action; action++) + g_action_muxer_action_added (group, *action, muxer); + g_strfreev (actions); + + g_signal_connect (group, "action-added", G_CALLBACK (g_action_muxer_action_added), muxer); + g_signal_connect (group, "action-removed", G_CALLBACK (g_action_muxer_action_removed), muxer); + g_signal_connect (group, "action-enabled-changed", G_CALLBACK (g_action_muxer_action_enabled_changed), muxer); + g_signal_connect (group, "action-state-changed", G_CALLBACK (g_action_muxer_action_state_changed), muxer); +} + +/* + * g_action_muxer_remove: + * @muxer: a #GActionMuxer + * @prefix: (allow-none): the prefix of the action group to remove, or NULL + * + * Removes a #GActionGroup from the #GActionMuxer. + */ +void +g_action_muxer_remove (GActionMuxer *muxer, + const gchar *prefix) +{ + GActionGroup *subgroup; + gchar **actions; + gchar **action; + + g_return_if_fail (G_IS_ACTION_MUXER (muxer)); + + subgroup = prefix ? g_hash_table_lookup (muxer->groups, prefix) : muxer->global_actions; + if (!subgroup) + return; + + actions = g_action_group_list_actions (subgroup); + for (action = actions; *action; action++) + g_action_muxer_action_removed (subgroup, *action, muxer); + g_strfreev (actions); + + g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_added, muxer); + g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_removed, muxer); + g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_enabled_changed, muxer); + g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_state_changed, muxer); + + if (prefix) + { + g_hash_table_remove (muxer->groups, prefix); + g_hash_table_remove (muxer->reverse, subgroup); + } + else + g_clear_object (&muxer->global_actions); +} + diff --git a/src/gactionmuxer.h b/src/gactionmuxer.h new file mode 100644 index 0000000..5c5e839 --- /dev/null +++ b/src/gactionmuxer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2012 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> + * Ryan Lortie <desrt@desrt.ca> + */ + +#ifndef __G_ACTION_MUXER_H__ +#define __G_ACTION_MUXER_H__ + +#include <gio/gio.h> + +#define G_TYPE_ACTION_MUXER (g_action_muxer_get_type ()) +#define G_ACTION_MUXER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_ACTION_MUXER, GActionMuxer)) +#define G_IS_ACTION_MUXER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_ACTION_MUXER)) + +typedef struct _GActionMuxer GActionMuxer; + +GType g_action_muxer_get_type (void) G_GNUC_CONST; + +GActionMuxer * g_action_muxer_new (void); + +void g_action_muxer_insert (GActionMuxer *muxer, + const gchar *prefix, + GActionGroup *group); + +void g_action_muxer_remove (GActionMuxer *muxer, + const gchar *prefix); + +#endif + diff --git a/src/messages-service.c b/src/messages-service.c index 28a64a0..c1069b8 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -34,12 +34,14 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include "dbus-data.h" #include "messages-service-dbus.h" #include "status-items.h" +#include "gactionmuxer.h" static IndicatorService * service = NULL; static GHashTable *applications; static GDBusConnection *bus; static GSimpleActionGroup *actions; +static GActionMuxer *action_muxer; static GMenu *menu; static GSettings *settings; static GMainLoop * mainloop = NULL; @@ -296,8 +298,11 @@ main (int argc, char ** argv) actions = g_simple_action_group_new (); g_simple_action_group_add_entries (actions, entries, G_N_ELEMENTS (entries), NULL); + + action_muxer = g_action_muxer_new (); + g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions)); g_dbus_connection_export_action_group (bus, INDICATOR_MESSAGES_DBUS_OBJECT, - G_ACTION_GROUP (actions), &error); + G_ACTION_GROUP (action_muxer), &error); if (error) { g_warning ("unable to export action group on dbus: %s", error->message); g_error_free (error); diff --git a/test/Makefile.am b/test/Makefile.am index 2b0f67e..977c1ed 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -42,14 +42,16 @@ libindicator_messages_service_la_HEADERS = \ $(top_srcdir)/src/gen-messages-service.xml.h \ $(top_srcdir)/src/app-section.h \ $(top_srcdir)/src/dbus-data.h \ - $(top_srcdir)/src/status-items.h + $(top_srcdir)/src/status-items.h \ + $(top_srcdir)/src/gactionmuxer.h libindicator_messages_service_la_SOURCES = \ $(top_srcdir)/src/messages-service.c \ $(top_srcdir)/src/messages-service-dbus.c \ $(top_srcdir)/src/gen-messages-service.xml.c \ $(top_srcdir)/src/app-section.c \ - $(top_srcdir)/src/status-items.c + $(top_srcdir)/src/status-items.c \ + $(top_srcdir)/src/gactionmuxer.c libindicator_messages_service_ladir = \ $(includedir)/libindicator-messages-service/ |