From ab4a7c52371c8e8d87e76f0c68a56569d2ee6e6e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 15 Dec 2011 00:29:31 -0500 Subject: [PATCH] Another update for GLib API changes GDBusActionGroup api has changed again, adapt to that. Also, use a GActionMuxer to add the 'app.' prefix to actions, instead of manually stripping it out of the action names. In the future, the muxer will also contain per-window actions with a 'win.' prefix. --- js/ui/popupMenu.js | 1 - src/Makefile.am | 9 +- src/gactionmuxer.c | 495 ++++++++++++++++++++++++++++++++++++++++ src/gactionmuxer.h | 53 +++++ src/gactionobservable.c | 80 +++++++ src/gactionobservable.h | 64 ++++++ src/gactionobserver.c | 161 +++++++++++++ src/gactionobserver.h | 90 ++++++++ src/shell-app.c | 100 +++----- 9 files changed, 982 insertions(+), 71 deletions(-) create mode 100644 src/gactionmuxer.c create mode 100644 src/gactionmuxer.h create mode 100644 src/gactionobservable.c create mode 100644 src/gactionobservable.h create mode 100644 src/gactionobserver.c create mode 100644 src/gactionobserver.h diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 4e9d1579e..c6dc3df31 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -1752,7 +1752,6 @@ const RemoteMenu = new Lang.Class({ } let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack(); - action_id = action_id.replace('app.', ''); if (!this.actionGroup.has_action(action_id)) { // the action may not be there yet, wait for action-added return [null, false, 'action-added']; diff --git a/src/Makefile.am b/src/Makefile.am index 375ff9147..3000a09fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -157,7 +157,14 @@ libgnome_shell_la_SOURCES = \ shell-util.c \ shell-window-tracker.c \ shell-wm.c \ - shell-xfixes-cursor.c + shell-xfixes-cursor.c \ + gactionmuxer.h \ + gactionmuxer.c \ + gactionobservable.h \ + gactionobservable.c \ + gactionobserver.h \ + gactionobserver.c + libgnome_shell_la_gir_sources = \ $(filter-out %-private.h $(shell_recorder_non_gir_sources), $(shell_public_headers_h) $(libgnome_shell_la_SOURCES)) diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c new file mode 100644 index 000000000..bdc34b73a --- /dev/null +++ b/src/gactionmuxer.c @@ -0,0 +1,495 @@ +/* + * Copyright © 2011 Canonical Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#include "config.h" + +#include "gactionmuxer.h" + +#include "gactionobservable.h" +#include "gactionobserver.h" + +#include + +/* + * SECTION:gactionmuxer + * @short_description: Aggregate and monitor several action groups + * + * #GActionMuxer is a #GActionGroup and #GActionObservable 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. + * + * This class is typically only used at the site of "consumption" of + * actions (eg: when displaying a menu that contains many actions on + * different objects). + */ + +static void g_action_muxer_group_iface_init (GActionGroupInterface *iface); +static void g_action_muxer_observable_iface_init (GActionObservableInterface *iface); + +typedef GObjectClass GActionMuxerClass; + +struct _GActionMuxer +{ + GObject parent_instance; + + GHashTable *actions; + GHashTable *groups; +}; + +G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init)) + +typedef struct +{ + GActionMuxer *muxer; + GSList *watchers; + gchar *fullname; +} Action; + +typedef struct +{ + GActionMuxer *muxer; + GActionGroup *group; + gchar *prefix; + gulong handler_ids[4]; +} Group; + +static gchar ** +g_action_muxer_list_actions (GActionGroup *action_group) +{ + GActionMuxer *muxer = G_ACTION_MUXER (action_group); + + return (gchar **) muxer->groups; +} + +static Group * +g_action_muxer_find_group (GActionMuxer *muxer, + const gchar **name) +{ + const gchar *dot; + gchar *prefix; + Group *group; + + dot = strchr (*name, '.'); + + if (!dot) + return NULL; + + prefix = g_strndup (*name, dot - *name); + group = g_hash_table_lookup (muxer->groups, prefix); + g_free (prefix); + + *name = dot + 1; + + return group; +} + +static Action * +g_action_muxer_lookup_action (GActionMuxer *muxer, + const gchar *prefix, + const gchar *action_name, + gchar **fullname) +{ + Action *action; + + *fullname = g_strconcat (prefix, ".", action_name, NULL); + action = g_hash_table_lookup (muxer->actions, *fullname); + + return action; +} + +static void +g_action_muxer_action_enabled_changed (GActionGroup *action_group, + const gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + Group *group = user_data; + gchar *fullname; + Action *action; + GSList *node; + + action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname); + for (node = action ? action->watchers : NULL; node; node = node->next) + g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled); + g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled); + g_free (fullname); +} + +static void +g_action_muxer_action_state_changed (GActionGroup *action_group, + const gchar *action_name, + GVariant *state, + gpointer user_data) +{ + Group *group = user_data; + gchar *fullname; + Action *action; + GSList *node; + + action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname); + for (node = action ? action->watchers : NULL; node; node = node->next) + g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state); + g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state); + g_free (fullname); +} + +static void +g_action_muxer_action_added (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + const GVariantType *parameter_type; + Group *group = user_data; + gboolean enabled; + GVariant *state; + + if (g_action_group_query_action (group->group, action_name, &enabled, ¶meter_type, NULL, NULL, &state)) + { + gchar *fullname; + Action *action; + GSList *node; + + action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname); + + for (node = action ? action->watchers : NULL; node; node = node->next) + g_action_observer_action_added (node->data, + G_ACTION_OBSERVABLE (group->muxer), + fullname, parameter_type, enabled, state); + + g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname); + + if (state) + g_variant_unref (state); + + g_free (fullname); + } +} + +static void +g_action_muxer_action_removed (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + Group *group = user_data; + gchar *fullname; + Action *action; + GSList *node; + + action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname); + for (node = action ? action->watchers : NULL; node; node = node->next) + g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname); + g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname); + g_free (fullname); +} + +static gboolean +g_action_muxer_query_action (GActionGroup *action_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 (action_group); + Group *group; + + group = g_action_muxer_find_group (muxer, &action_name); + + if (!group) + return FALSE; + + return g_action_group_query_action (group->group, action_name, enabled, + parameter_type, state_type, state_hint, state); +} + +static void +g_action_muxer_activate_action (GActionGroup *action_group, + const gchar *action_name, + GVariant *parameter) +{ + GActionMuxer *muxer = G_ACTION_MUXER (action_group); + Group *group; + + group = g_action_muxer_find_group (muxer, &action_name); + + if (group) + g_action_group_activate_action (group->group, action_name, parameter); +} + +static void +g_action_muxer_change_action_state (GActionGroup *action_group, + const gchar *action_name, + GVariant *state) +{ + GActionMuxer *muxer = G_ACTION_MUXER (action_group); + Group *group; + + group = g_action_muxer_find_group (muxer, &action_name); + + if (group) + g_action_group_change_action_state (group->group, action_name, state); +} + +static void +g_action_muxer_unregister_internal (Action *action, + gpointer observer) +{ + GActionMuxer *muxer = action->muxer; + GSList **ptr; + + for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next) + if ((*ptr)->data == observer) + { + *ptr = g_slist_remove (*ptr, observer); + + if (action->watchers == NULL) + { + g_hash_table_remove (muxer->actions, action->fullname); + g_free (action->fullname); + + g_slice_free (Action, action); + + g_object_unref (muxer); + } + + break; + } +} + +static void +g_action_muxer_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + Action *action = data; + + g_action_muxer_unregister_internal (action, where_the_object_was); +} + +static void +g_action_muxer_register_observer (GActionObservable *observable, + const gchar *name, + GActionObserver *observer) +{ + GActionMuxer *muxer = G_ACTION_MUXER (observable); + Action *action; + + action = g_hash_table_lookup (muxer->actions, name); + + if (action == NULL) + { + action = g_slice_new (Action); + action->muxer = g_object_ref (muxer); + action->fullname = g_strdup (name); + action->watchers = NULL; + + g_hash_table_insert (muxer->actions, action->fullname, action); + } + + action->watchers = g_slist_prepend (action->watchers, observer); + g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action); +} + +static void +g_action_muxer_unregister_observer (GActionObservable *observable, + const gchar *name, + GActionObserver *observer) +{ + GActionMuxer *muxer = G_ACTION_MUXER (observable); + Action *action; + + action = g_hash_table_lookup (muxer->actions, name); + g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action); + g_action_muxer_unregister_internal (action, observer); +} + +static void +g_action_muxer_free_group (gpointer data) +{ + Group *group = data; + + g_object_unref (group->group); + g_free (group->prefix); + + g_slice_free (Group, group); +} + +static void +g_action_muxer_finalize (GObject *object) +{ + GActionMuxer *muxer = G_ACTION_MUXER (object); + + g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0); + g_hash_table_unref (muxer->actions); + g_hash_table_unref (muxer->groups); + + G_OBJECT_CLASS (g_action_muxer_parent_class) + ->finalize (object); +} + +static void +g_action_muxer_init (GActionMuxer *muxer) +{ + muxer->actions = g_hash_table_new (g_str_hash, g_str_equal); + muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group); +} + +static void +g_action_muxer_observable_iface_init (GActionObservableInterface *iface) +{ + iface->register_observer = g_action_muxer_register_observer; + iface->unregister_observer = g_action_muxer_unregister_observer; +} + +static void +g_action_muxer_group_iface_init (GActionGroupInterface *iface) +{ + iface->list_actions = g_action_muxer_list_actions; + iface->query_action = g_action_muxer_query_action; + iface->activate_action = g_action_muxer_activate_action; + iface->change_action_state = g_action_muxer_change_action_state; +} + +static void +g_action_muxer_class_init (GObjectClass *class) +{ + class->finalize = g_action_muxer_finalize; +} + +/* + * g_action_muxer_insert: + * @muxer: a #GActionMuxer + * @prefix: the prefix string for the action group + * @action_group: a #GActionGroup + * + * Adds the actions in @action_group to the list of actions provided by + * @muxer. @prefix is prefixed to each action name, such that for each + * action x in @action_group, there is an equivalent + * action @prefix.x in @muxer. + * + * For example, if @prefix is "app" and @action_group + * contains an action called "quit", then @muxer will + * now contain an action called "app.quit". + * + * If any #GActionObservers are registered for actions in the group, + * "action_added" notifications will be emitted, as appropriate. + * + * @prefix must not contain a dot ('.'). + */ +void +g_action_muxer_insert (GActionMuxer *muxer, + const gchar *prefix, + GActionGroup *action_group) +{ + gchar **actions; + Group *group; + gint i; + + /* TODO: diff instead of ripout and replace */ + g_action_muxer_remove (muxer, prefix); + + group = g_slice_new (Group); + group->muxer = muxer; + group->group = g_object_ref (action_group); + group->prefix = g_strdup (prefix); + + g_hash_table_insert (muxer->groups, group->prefix, group); + + actions = g_action_group_list_actions (group->group); + for (i = 0; actions[i]; i++) + g_action_muxer_action_added (group->group, actions[i], group); + g_strfreev (actions); + + group->handler_ids[0] = g_signal_connect (group->group, "action-added", + G_CALLBACK (g_action_muxer_action_added), group); + group->handler_ids[1] = g_signal_connect (group->group, "action-removed", + G_CALLBACK (g_action_muxer_action_removed), group); + group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed", + G_CALLBACK (g_action_muxer_action_enabled_changed), group); + group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed", + G_CALLBACK (g_action_muxer_action_state_changed), group); +} + +/* + * g_action_muxer_remove: + * @muxer: a #GActionMuxer + * @prefix: the prefix of the action group to remove + * + * Removes a #GActionGroup from the #GActionMuxer. + * + * If any #GActionObservers are registered for actions in the group, + * "action_removed" notifications will be emitted, as appropriate. + */ +void +g_action_muxer_remove (GActionMuxer *muxer, + const gchar *prefix) +{ + Group *group; + + group = g_hash_table_lookup (muxer->groups, prefix); + + if (group != NULL) + { + gchar **actions; + gint i; + + g_hash_table_steal (muxer->groups, prefix); + + actions = g_action_group_list_actions (group->group); + for (i = 0; actions[i]; i++) + g_action_muxer_action_removed (group->group, actions[i], group); + g_strfreev (actions); + + /* 'for loop' or 'four loop'? */ + for (i = 0; i < 4; i++) + g_signal_handler_disconnect (group->group, group->handler_ids[i]); + + g_action_muxer_free_group (group); + } +} + +/* + * g_action_muxer_new: + * + * Creates a new #GActionMuxer. + */ +GActionMuxer * +g_action_muxer_new (void) +{ + return g_object_new (G_TYPE_ACTION_MUXER, NULL); +} diff --git a/src/gactionmuxer.h b/src/gactionmuxer.h new file mode 100644 index 000000000..adafd03b2 --- /dev/null +++ b/src/gactionmuxer.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2011 Canonical Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#ifndef __G_ACTION_MUXER_H__ +#define __G_ACTION_MUXER_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_ACTION_MUXER (g_action_muxer_get_type ()) +#define G_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + G_TYPE_ACTION_MUXER, GActionMuxer)) +#define G_IS_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + G_TYPE_ACTION_MUXER)) + +typedef struct _GActionMuxer GActionMuxer; + +G_GNUC_INTERNAL +GType g_action_muxer_get_type (void); +G_GNUC_INTERNAL +GActionMuxer * g_action_muxer_new (void); + +G_GNUC_INTERNAL +void g_action_muxer_insert (GActionMuxer *muxer, + const gchar *prefix, + GActionGroup *group); + +G_GNUC_INTERNAL +void g_action_muxer_remove (GActionMuxer *muxer, + const gchar *prefix); + +G_END_DECLS + +#endif /* __G_ACTION_MUXER_H__ */ diff --git a/src/gactionobservable.c b/src/gactionobservable.c new file mode 100644 index 000000000..5d1c6526c --- /dev/null +++ b/src/gactionobservable.c @@ -0,0 +1,80 @@ +/* + * Copyright © 2011 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#include "config.h" + +#include "gactionobservable.h" + +G_DEFINE_INTERFACE (GActionObservable, g_action_observable, G_TYPE_OBJECT) + +/* + * SECTION:gactionobserable + * @short_description: an interface implemented by objects that report + * changes to actions + */ + +void +g_action_observable_default_init (GActionObservableInterface *iface) +{ +} + +/* + * g_action_observable_register_observer: + * @observable: a #GActionObservable + * @action_name: the name of the action + * @observer: the #GActionObserver to which the events will be reported + * + * Registers @observer as being interested in changes to @action_name on + * @observable. + */ +void +g_action_observable_register_observer (GActionObservable *observable, + const gchar *action_name, + GActionObserver *observer) +{ + g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable)); + + G_ACTION_OBSERVABLE_GET_IFACE (observable) + ->register_observer (observable, action_name, observer); +} + +/* + * g_action_observable_unregister_observer: + * @observable: a #GActionObservable + * @action_name: the name of the action + * @observer: the #GActionObserver to which the events will be reported + * + * Removes the registration of @observer as being interested in changes + * to @action_name on @observable. + * + * If the observer was registered multiple times, it must be + * unregistered an equal number of times. + */ +void +g_action_observable_unregister_observer (GActionObservable *observable, + const gchar *action_name, + GActionObserver *observer) +{ + g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable)); + + G_ACTION_OBSERVABLE_GET_IFACE (observable) + ->unregister_observer (observable, action_name, observer); +} diff --git a/src/gactionobservable.h b/src/gactionobservable.h new file mode 100644 index 000000000..1d5d1aa2d --- /dev/null +++ b/src/gactionobservable.h @@ -0,0 +1,64 @@ +/* + * Copyright © 2011 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#ifndef __G_ACTION_OBSERVABLE_H__ +#define __G_ACTION_OBSERVABLE_H__ + +#include "gactionobserver.h" + +G_BEGIN_DECLS + +#define G_TYPE_ACTION_OBSERVABLE (g_action_observable_get_type ()) +#define G_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + G_TYPE_ACTION_OBSERVABLE, GActionObservable)) +#define G_IS_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + G_TYPE_ACTION_OBSERVABLE)) +#define G_ACTION_OBSERVABLE_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \ + G_TYPE_ACTION_OBSERVABLE, GActionObservableInterface)) + +typedef struct _GActionObservableInterface GActionObservableInterface; + +struct _GActionObservableInterface +{ + GTypeInterface g_iface; + + void (* register_observer) (GActionObservable *observable, + const gchar *action_name, + GActionObserver *observer); + void (* unregister_observer) (GActionObservable *observable, + const gchar *action_name, + GActionObserver *observer); +}; + +G_GNUC_INTERNAL +GType g_action_observable_get_type (void); +G_GNUC_INTERNAL +void g_action_observable_register_observer (GActionObservable *observable, + const gchar *action_name, + GActionObserver *observer); +G_GNUC_INTERNAL +void g_action_observable_unregister_observer (GActionObservable *observable, + const gchar *action_name, + GActionObserver *observer); + +G_END_DECLS + +#endif /* __G_ACTION_OBSERVABLE_H__ */ diff --git a/src/gactionobserver.c b/src/gactionobserver.c new file mode 100644 index 000000000..0586f0773 --- /dev/null +++ b/src/gactionobserver.c @@ -0,0 +1,161 @@ +/* + * Copyright © 2011 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#include "config.h" + +#include "gactionobserver.h" + +G_DEFINE_INTERFACE (GActionObserver, g_action_observer, G_TYPE_OBJECT) + +/** + * SECTION:gactionobserver + * @short_description: an interface implemented by objects that are + * interested in monitoring actions for changes + * + * GActionObserver is a simple interface allowing objects that wish to + * be notified of changes to actions to be notified of those changes. + * + * It is also possible to monitor changes to action groups using + * #GObject signals, but there are a number of reasons that this + * approach could become problematic: + * + * - there are four separate signals that must be manually connected + * and disconnected + * + * - when a large number of different observers wish to monitor a + * (usually disjoint) set of actions within the same action group, + * there is only one way to avoid having all notifications delivered + * to all observers: signal detail. In order to use signal detail, + * each action name must be quarked, which is not always practical. + * + * - even if quarking is acceptable, #GObject signal details are + * implemented by scanning a linked list, so there is no real + * decrease in complexity + */ + +void +g_action_observer_default_init (GActionObserverInterface *class) +{ +} + +/* + * g_action_observer_action_added: + * @observer: a #GActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * @enabled: %TRUE if the action is now enabled + * @parameter_type: the parameter type for action invocations, or %NULL + * if no parameter is required + * @state: the current state of the action, or %NULL if the action is + * stateless + * + * This function is called when an action that the observer is + * registered to receive events for is added. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +g_action_observer_action_added (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state) +{ + g_return_if_fail (G_IS_ACTION_OBSERVER (observer)); + + G_ACTION_OBSERVER_GET_IFACE (observer) + ->action_added (observer, observable, action_name, parameter_type, enabled, state); +} + +/* + * g_action_observer_action_enabled_changed: + * @observer: a #GActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * @enabled: %TRUE if the action is now enabled + * + * This function is called when an action that the observer is + * registered to receive events for becomes enabled or disabled. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +g_action_observer_action_enabled_changed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + gboolean enabled) +{ + g_return_if_fail (G_IS_ACTION_OBSERVER (observer)); + + G_ACTION_OBSERVER_GET_IFACE (observer) + ->action_enabled_changed (observer, observable, action_name, enabled); +} + +/* + * g_action_observer_action_state_changed: + * @observer: a #GActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * @state: the new state of the action + * + * This function is called when an action that the observer is + * registered to receive events for changes its state. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +g_action_observer_action_state_changed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + GVariant *state) +{ + g_return_if_fail (G_IS_ACTION_OBSERVER (observer)); + + G_ACTION_OBSERVER_GET_IFACE (observer) + ->action_state_changed (observer, observable, action_name, state); +} + +/* + * g_action_observer_action_removed: + * @observer: a #GActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * + * This function is called when an action that the observer is + * registered to receive events for is removed. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +g_action_observer_action_removed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name) +{ + g_return_if_fail (G_IS_ACTION_OBSERVER (observer)); + + G_ACTION_OBSERVER_GET_IFACE (observer) + ->action_removed (observer, observable, action_name); +} diff --git a/src/gactionobserver.h b/src/gactionobserver.h new file mode 100644 index 000000000..eb15c3ab4 --- /dev/null +++ b/src/gactionobserver.h @@ -0,0 +1,90 @@ +/* + * Copyright © 2011 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#ifndef __G_ACTION_OBSERVER_H__ +#define __G_ACTION_OBSERVER_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_ACTION_OBSERVER (g_action_observer_get_type ()) +#define G_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + G_TYPE_ACTION_OBSERVER, GActionObserver)) +#define G_IS_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + G_TYPE_ACTION_OBSERVER)) +#define G_ACTION_OBSERVER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \ + G_TYPE_ACTION_OBSERVER, GActionObserverInterface)) + +typedef struct _GActionObserverInterface GActionObserverInterface; +typedef struct _GActionObservable GActionObservable; +typedef struct _GActionObserver GActionObserver; + +struct _GActionObserverInterface +{ + GTypeInterface g_iface; + + void (* action_added) (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state); + void (* action_enabled_changed) (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + gboolean enabled); + void (* action_state_changed) (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + GVariant *state); + void (* action_removed) (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name); +}; + +G_GNUC_INTERNAL +GType g_action_observer_get_type (void); +G_GNUC_INTERNAL +void g_action_observer_action_added (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state); +G_GNUC_INTERNAL +void g_action_observer_action_enabled_changed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + gboolean enabled); +G_GNUC_INTERNAL +void g_action_observer_action_state_changed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + GVariant *state); +G_GNUC_INTERNAL +void g_action_observer_action_removed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name); + +G_END_DECLS + +#endif /* __G_ACTION_OBSERVER_H__ */ diff --git a/src/shell-app.c b/src/shell-app.c index 42d11654e..7d64e2a01 100644 --- a/src/shell-app.c +++ b/src/shell-app.c @@ -15,6 +15,7 @@ #include "shell-app-system-private.h" #include "shell-window-tracker-private.h" #include "st.h" +#include "gactionmuxer.h" typedef enum { MATCH_NONE, @@ -42,8 +43,9 @@ typedef struct { gint name_watcher_id; gchar *dbus_name; GDBusProxy *app_proxy; - GDBusActionGroup *remote_actions; + GActionGroup *remote_actions; GMenuModel *remote_menu; + GActionMuxer *muxer; GCancellable *dbus_cancellable; } ShellAppRunningState; @@ -129,7 +131,7 @@ shell_app_get_property (GObject *gobject, break; case PROP_ACTION_GROUP: if (app->running_state) - g_value_set_object (value, app->running_state->remote_actions); + g_value_set_object (value, app->running_state->muxer); break; case PROP_MENU: if (app->running_state) @@ -1022,66 +1024,6 @@ _shell_app_remove_window (ShellApp *app, g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0); } -static void -on_action_group_acquired (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - ShellApp *self = SHELL_APP (user_data); - ShellAppRunningState *state = self->running_state; - GError *error = NULL; - GVariant *menu_property; - - state->remote_actions = g_dbus_action_group_new_finish (result, - &error); - - if (error) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && - !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) - { - g_warning ("Unexpected error while reading application actions: %s", error->message); - } - - g_clear_error (&error); - g_clear_object (&state->dbus_cancellable); - g_clear_object (&state->app_proxy); - - if (state->name_watcher_id) - { - g_bus_unwatch_name (state->name_watcher_id); - state->name_watcher_id = 0; - } - - g_free (state->dbus_name); - state->dbus_name = NULL; - - g_object_unref (self); - return; - } - - g_object_notify (G_OBJECT (self), "action-group"); - - /* third step: the application menu */ - - menu_property = g_dbus_proxy_get_cached_property (state->app_proxy, "AppMenu"); - - if (menu_property && g_variant_n_children (menu_property) > 0) - { - const gchar *object_path; - - g_variant_get_child (menu_property, 0, "&o", &object_path); - - state->remote_menu = G_MENU_MODEL (g_dbus_menu_model_get (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL), - state->dbus_name, - object_path)); - - g_object_notify (G_OBJECT (self), "menu"); - } - - g_object_unref (self); -} - static void on_dbus_proxy_gotten (GObject *initable, GAsyncResult *result, @@ -1090,6 +1032,7 @@ on_dbus_proxy_gotten (GObject *initable, ShellApp *self = SHELL_APP (user_data); ShellAppRunningState *state = self->running_state; GError *error = NULL; + GVariant *menu_property; state->app_proxy = g_dbus_proxy_new_finish (result, &error); @@ -1120,13 +1063,30 @@ on_dbus_proxy_gotten (GObject *initable, /* on to the second step, the primary action group */ - g_dbus_action_group_new (g_dbus_proxy_get_connection (state->app_proxy), + state->remote_actions = (GActionGroup*)g_dbus_action_group_get ( + g_dbus_proxy_get_connection (state->app_proxy), g_dbus_proxy_get_name (state->app_proxy), - g_dbus_proxy_get_object_path (state->app_proxy), - G_DBUS_ACTION_GROUP_FLAGS_NONE, - state->dbus_cancellable, - on_action_group_acquired, - self); + g_dbus_proxy_get_object_path (state->app_proxy)); + state->muxer = g_action_muxer_new (); + g_action_muxer_insert (state->muxer, "app", state->remote_actions); + g_strfreev (g_action_group_list_actions (state->remote_actions)); + + g_object_notify (G_OBJECT (self), "action-group"); + + menu_property = g_dbus_proxy_get_cached_property (state->app_proxy, "AppMenu"); + + if (menu_property && g_variant_n_children (menu_property) > 0) + { + const gchar *object_path; + + g_variant_get_child (menu_property, 0, "&o", &object_path); + + state->remote_menu = G_MENU_MODEL (g_dbus_menu_model_get (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL), + state->dbus_name, + object_path)); + + g_object_notify (G_OBJECT (self), "menu"); + } } static void @@ -1183,6 +1143,7 @@ on_dbus_name_disappeared (GDBusConnection *bus, g_clear_object (&state->app_proxy); g_clear_object (&state->remote_actions); g_clear_object (&state->remote_menu); + g_clear_object (&state->muxer); g_free (state->dbus_name); state->dbus_name = NULL; @@ -1428,6 +1389,7 @@ unref_running_state (ShellAppRunningState *state) g_clear_object (&state->app_proxy); g_clear_object (&state->remote_actions); g_clear_object (&state->remote_menu); + g_clear_object (&state->muxer); g_free (state->dbus_name); if (state->name_watcher_id) @@ -1701,7 +1663,7 @@ shell_app_class_init(ShellAppClass *klass) g_param_spec_object ("action-group", "Application Action Group", "The action group exported by the remote application", - G_TYPE_DBUS_ACTION_GROUP, + G_TYPE_ACTION_GROUP, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * ShellApp:menu: