mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-13 15:56:23 +01:00
fcc9902e98
We provide a mechanism by which a 'platform' (eg: Gtk) can register some hook functions to be called to collect platform-data at the point of sending an outgoing action activation request and also to inform the platform of this data on incoming requests (before and after dispatching the actual request). This can be used for forwarding timestamp and startup-notification information (as is presently done in GApplication) but the before/after hook could also be used for acquiring/releasing the Gdk lock or other similar things. https://bugzilla.gnome.org/show_bug.cgi?id=665737
538 lines
16 KiB
C
538 lines
16 KiB
C
/*
|
|
* Copyright © 2010 Codethink Limited
|
|
* 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 <desrt@desrt.ca>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gdbusactiongroup.h"
|
|
|
|
#include "gdbusconnection.h"
|
|
#include "gactiongroup.h"
|
|
|
|
/**
|
|
* SECTION:gdbusactiongroup
|
|
* @title: GDBusActionGroup
|
|
* @short_description: A D-Bus GActionGroup implementation
|
|
* @see_also: <link linkend="gio-GActionGroup-exporter">GActionGroup exporter</link>
|
|
*
|
|
* #GDBusActionGroup is an implementation of the #GActionGroup
|
|
* interface that can be used as a proxy for an action group
|
|
* that is exported over D-Bus with g_dbus_connection_export_action_group().
|
|
*/
|
|
|
|
G_GNUC_INTERNAL GDBusActionGroupSendHookFunc g_dbus_action_group_send_hook;
|
|
G_GNUC_INTERNAL GDBusActionGroupEmitHookFunc g_dbus_action_group_before_emit_hook;
|
|
G_GNUC_INTERNAL GDBusActionGroupEmitHookFunc g_dbus_action_group_after_emit_hook;
|
|
|
|
struct _GDBusActionGroup
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GDBusConnection *connection;
|
|
gchar *bus_name;
|
|
gchar *object_path;
|
|
guint subscription_id;
|
|
GHashTable *actions;
|
|
|
|
/* The 'strict' flag indicates that the non-existence of at least one
|
|
* action has potentially been observed through the API. This means
|
|
* that we should always emit 'action-added' signals for all new
|
|
* actions.
|
|
*
|
|
* The user can observe the non-existence of an action by listing the
|
|
* actions or by performing a query (such as parameter type) on a
|
|
* non-existent action.
|
|
*
|
|
* If the user has no way of knowing that a given action didn't
|
|
* already exist then we can skip emitting 'action-added' signals
|
|
* since they have no way of knowing that it wasn't there from the
|
|
* start.
|
|
*/
|
|
gboolean strict;
|
|
};
|
|
|
|
typedef GObjectClass GDBusActionGroupClass;
|
|
|
|
typedef struct
|
|
{
|
|
gchar *name;
|
|
GVariantType *parameter_type;
|
|
gboolean enabled;
|
|
GVariant *state;
|
|
} ActionInfo;
|
|
|
|
static void
|
|
action_info_free (gpointer user_data)
|
|
{
|
|
ActionInfo *info = user_data;
|
|
|
|
g_free (info->name);
|
|
|
|
if (info->state)
|
|
g_variant_unref (info->state);
|
|
|
|
if (info->parameter_type)
|
|
g_variant_type_free (info->parameter_type);
|
|
|
|
g_slice_free (ActionInfo, info);
|
|
}
|
|
|
|
ActionInfo *
|
|
action_info_new_from_iter (GVariantIter *iter)
|
|
{
|
|
const gchar *param_str;
|
|
ActionInfo *info;
|
|
gboolean enabled;
|
|
GVariant *state;
|
|
gchar *name;
|
|
|
|
if (!g_variant_iter_next (iter, "{s(b&g@av)}", &name,
|
|
&enabled, ¶m_str, &state))
|
|
return NULL;
|
|
|
|
info = g_slice_new (ActionInfo);
|
|
info->name = name;
|
|
info->enabled = enabled;
|
|
|
|
if (g_variant_n_children (state))
|
|
g_variant_get_child (state, 0, "v", &info->state);
|
|
else
|
|
info->state = NULL;
|
|
g_variant_unref (state);
|
|
|
|
if (param_str[0])
|
|
info->parameter_type = g_variant_type_copy ((GVariantType *) param_str);
|
|
else
|
|
info->parameter_type = NULL;
|
|
|
|
return info;
|
|
}
|
|
|
|
static void g_dbus_action_group_iface_init (GActionGroupInterface *);
|
|
G_DEFINE_TYPE_WITH_CODE (GDBusActionGroup, g_dbus_action_group, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_dbus_action_group_iface_init))
|
|
|
|
static void
|
|
g_dbus_action_group_changed (GDBusConnection *connection,
|
|
const gchar *sender,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *signal_name,
|
|
GVariant *parameters,
|
|
gpointer user_data)
|
|
{
|
|
GDBusActionGroup *group = user_data;
|
|
GActionGroup *g_group = user_data;
|
|
|
|
/* make sure that we've been fully initialised */
|
|
if (group->actions == NULL)
|
|
return;
|
|
|
|
if (g_str_equal (signal_name, "Changed") &&
|
|
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(asa{sb}a{sv}a{s(bgav)})")))
|
|
{
|
|
/* Removes */
|
|
{
|
|
GVariantIter *iter;
|
|
const gchar *name;
|
|
|
|
g_variant_get_child (parameters, 0, "as", &iter);
|
|
while (g_variant_iter_next (iter, "&s", &name))
|
|
{
|
|
if (g_hash_table_lookup (group->actions, name))
|
|
{
|
|
g_hash_table_remove (group->actions, name);
|
|
g_action_group_action_removed (g_group, name);
|
|
}
|
|
}
|
|
g_variant_iter_free (iter);
|
|
}
|
|
|
|
/* Enable changes */
|
|
{
|
|
GVariantIter *iter;
|
|
const gchar *name;
|
|
gboolean enabled;
|
|
|
|
g_variant_get_child (parameters, 1, "a{sb}", &iter);
|
|
while (g_variant_iter_next (iter, "{&sb}", &name, &enabled))
|
|
{
|
|
ActionInfo *info;
|
|
|
|
info = g_hash_table_lookup (group->actions, name);
|
|
|
|
if (info && info->enabled != enabled)
|
|
{
|
|
info->enabled = enabled;
|
|
g_action_group_action_enabled_changed (g_group, name, enabled);
|
|
}
|
|
}
|
|
g_variant_iter_free (iter);
|
|
}
|
|
|
|
/* State changes */
|
|
{
|
|
GVariantIter *iter;
|
|
const gchar *name;
|
|
GVariant *state;
|
|
|
|
g_variant_get_child (parameters, 2, "a{sv}", &iter);
|
|
while (g_variant_iter_next (iter, "{&sv}", &name, &state))
|
|
{
|
|
ActionInfo *info;
|
|
|
|
info = g_hash_table_lookup (group->actions, name);
|
|
|
|
if (info && info->state && !g_variant_equal (state, info->state) &&
|
|
g_variant_is_of_type (state, g_variant_get_type (info->state)))
|
|
{
|
|
g_variant_unref (info->state);
|
|
info->state = g_variant_ref (state);
|
|
|
|
g_action_group_action_state_changed (g_group, name, state);
|
|
}
|
|
|
|
g_variant_unref (state);
|
|
}
|
|
g_variant_iter_free (iter);
|
|
}
|
|
|
|
/* Additions */
|
|
{
|
|
GVariantIter *iter;
|
|
ActionInfo *info;
|
|
|
|
g_variant_get_child (parameters, 3, "a{s(bgav)}", &iter);
|
|
while ((info = action_info_new_from_iter (iter)))
|
|
{
|
|
if (!g_hash_table_lookup (group->actions, info->name))
|
|
{
|
|
g_hash_table_insert (group->actions, info->name, info);
|
|
|
|
if (group->strict)
|
|
g_action_group_action_added (g_group, info->name);
|
|
}
|
|
else
|
|
action_info_free (info);
|
|
}
|
|
g_variant_iter_free (iter);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
g_dbus_action_group_describe_all_done (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GDBusActionGroup *group= user_data;
|
|
GVariant *reply;
|
|
|
|
g_assert (group->actions == NULL);
|
|
group->actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, action_info_free);
|
|
|
|
g_assert (group->connection == (gpointer) source);
|
|
reply = g_dbus_connection_call_finish (group->connection, result, NULL);
|
|
|
|
if (reply != NULL)
|
|
{
|
|
GVariantIter *iter;
|
|
ActionInfo *action;
|
|
|
|
g_variant_get (reply, "(a{s(bgav)})", &iter);
|
|
while ((action = action_info_new_from_iter (iter)))
|
|
{
|
|
g_hash_table_insert (group->actions, action->name, action);
|
|
|
|
if (group->strict)
|
|
g_action_group_action_added (G_ACTION_GROUP (group), action->name);
|
|
}
|
|
g_variant_iter_free (iter);
|
|
g_variant_unref (reply);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
g_dbus_action_group_async_init (GDBusActionGroup *group)
|
|
{
|
|
if (group->subscription_id != 0)
|
|
return;
|
|
|
|
group->subscription_id =
|
|
g_dbus_connection_signal_subscribe (group->connection, group->bus_name, "org.gtk.Actions", "Changed", group->object_path,
|
|
NULL, G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
|
|
|
|
g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "DescribeAll", NULL,
|
|
G_VARIANT_TYPE ("(a{s(bgav)})"), G_DBUS_CALL_FLAGS_NONE, -1, NULL,
|
|
g_dbus_action_group_describe_all_done, group);
|
|
}
|
|
|
|
static gchar **
|
|
g_dbus_action_group_list_actions (GActionGroup *g_group)
|
|
{
|
|
GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
|
|
gchar **keys;
|
|
|
|
if (group->actions != NULL)
|
|
{
|
|
GHashTableIter iter;
|
|
gint n, i = 0;
|
|
gpointer key;
|
|
|
|
n = g_hash_table_size (group->actions);
|
|
keys = g_new (gchar *, n + 1);
|
|
|
|
g_hash_table_iter_init (&iter, group->actions);
|
|
while (g_hash_table_iter_next (&iter, &key, NULL))
|
|
keys[i++] = g_strdup (key);
|
|
g_assert_cmpint (i, ==, n);
|
|
keys[n] = NULL;
|
|
}
|
|
else
|
|
{
|
|
g_dbus_action_group_async_init (group);
|
|
keys = g_new0 (gchar *, 1);
|
|
}
|
|
|
|
group->strict = TRUE;
|
|
|
|
return keys;
|
|
}
|
|
|
|
static gboolean
|
|
g_dbus_action_group_query_action (GActionGroup *g_group,
|
|
const gchar *action_name,
|
|
gboolean *enabled,
|
|
const GVariantType **parameter_type,
|
|
const GVariantType **state_type,
|
|
GVariant **state_hint,
|
|
GVariant **state)
|
|
{
|
|
GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
|
|
ActionInfo *info;
|
|
|
|
if (group->actions != NULL)
|
|
{
|
|
info = g_hash_table_lookup (group->actions, action_name);
|
|
|
|
if (info == NULL)
|
|
{
|
|
group->strict = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
if (enabled)
|
|
*enabled = info->enabled;
|
|
|
|
if (parameter_type)
|
|
*parameter_type = info->parameter_type;
|
|
|
|
if (state_type)
|
|
*state_type = info->state ? g_variant_get_type (info->state) : NULL;
|
|
|
|
if (state_hint)
|
|
*state_hint = NULL;
|
|
|
|
if (state)
|
|
*state = info->state ? g_variant_ref (info->state) : NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
g_dbus_action_group_async_init (group);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_dbus_action_group_change_state (GActionGroup *g_group,
|
|
const gchar *action_name,
|
|
GVariant *value)
|
|
{
|
|
GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
|
|
GVariantBuilder platform_data_builder;
|
|
|
|
g_variant_builder_init (&platform_data_builder, G_VARIANT_TYPE_VARDICT);
|
|
if (g_dbus_action_group_send_hook != NULL)
|
|
(* g_dbus_action_group_send_hook) (group, &platform_data_builder);
|
|
|
|
/* Don't bother with the checks. The other side will do it again. */
|
|
g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "SetState",
|
|
g_variant_new ("(sva{sv})", action_name, value, &platform_data_builder),
|
|
NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
g_dbus_action_group_activate (GActionGroup *g_group,
|
|
const gchar *action_name,
|
|
GVariant *parameter)
|
|
{
|
|
GDBusActionGroup *group = G_DBUS_ACTION_GROUP (g_group);
|
|
GVariantBuilder platform_data_builder;
|
|
GVariantBuilder builder;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
|
|
|
|
if (parameter)
|
|
g_variant_builder_add (&builder, "v", parameter);
|
|
|
|
g_variant_builder_init (&platform_data_builder, G_VARIANT_TYPE_VARDICT);
|
|
if (g_dbus_action_group_send_hook != NULL)
|
|
(* g_dbus_action_group_send_hook) (group, &platform_data_builder);
|
|
|
|
g_dbus_connection_call (group->connection, group->bus_name, group->object_path, "org.gtk.Actions", "Activate",
|
|
g_variant_new ("(sava{sv})", action_name, &builder, &platform_data_builder),
|
|
NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
g_dbus_action_group_finalize (GObject *object)
|
|
{
|
|
GDBusActionGroup *group = G_DBUS_ACTION_GROUP (object);
|
|
|
|
if (group->subscription_id)
|
|
g_dbus_connection_signal_unsubscribe (group->connection, group->subscription_id);
|
|
|
|
if (group->actions)
|
|
g_hash_table_unref (group->actions);
|
|
|
|
g_object_unref (group->connection);
|
|
g_free (group->object_path);
|
|
g_free (group->bus_name);
|
|
|
|
G_OBJECT_CLASS (g_dbus_action_group_parent_class)
|
|
->finalize (object);
|
|
}
|
|
|
|
static void
|
|
g_dbus_action_group_init (GDBusActionGroup *group)
|
|
{
|
|
}
|
|
|
|
static void
|
|
g_dbus_action_group_class_init (GDBusActionGroupClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
object_class->finalize = g_dbus_action_group_finalize;
|
|
}
|
|
|
|
static void
|
|
g_dbus_action_group_iface_init (GActionGroupInterface *iface)
|
|
{
|
|
iface->list_actions = g_dbus_action_group_list_actions;
|
|
iface->query_action = g_dbus_action_group_query_action;
|
|
iface->change_action_state = g_dbus_action_group_change_state;
|
|
iface->activate_action = g_dbus_action_group_activate;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_action_group_get:
|
|
* @connection: A #GDBusConnection
|
|
* @bus_name: the bus name which exports the action group
|
|
* @object_path: the object path at which the action group is exported
|
|
*
|
|
* Obtains a #GDBusActionGroup for the action group which is exported at
|
|
* the given @bus_name and @object_path.
|
|
*
|
|
* The thread default main context is taken at the time of this call.
|
|
* All signals on the menu model (and any linked models) are reported
|
|
* with respect to this context. All calls on the returned menu model
|
|
* (and linked models) must also originate from this same context, with
|
|
* the thread default main context unchanged.
|
|
*
|
|
* This call is non-blocking. The returned action group may or may not
|
|
* already be filled in. The correct thing to do is connect the signals
|
|
* for the action group to monitor for changes and then to call
|
|
* g_action_group_list_actions() to get the initial list.
|
|
*
|
|
* Returns: (transfer full): a #GDBusActionGroup
|
|
*
|
|
* Since: 2.32
|
|
*/
|
|
GDBusActionGroup *
|
|
g_dbus_action_group_get (GDBusConnection *connection,
|
|
const gchar *bus_name,
|
|
const gchar *object_path)
|
|
{
|
|
GDBusActionGroup *group;
|
|
|
|
group = g_object_new (G_TYPE_DBUS_ACTION_GROUP, NULL);
|
|
group->connection = g_object_ref (connection);
|
|
group->bus_name = g_strdup (bus_name);
|
|
group->object_path = g_strdup (object_path);
|
|
|
|
return group;
|
|
}
|
|
|
|
G_GNUC_INTERNAL gboolean
|
|
g_dbus_action_group_sync (GDBusActionGroup *group,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GVariant *reply;
|
|
|
|
g_assert (group->subscription_id == 0);
|
|
|
|
group->subscription_id =
|
|
g_dbus_connection_signal_subscribe (group->connection, group->bus_name, "org.gtk.Actions", "Changed", group->object_path,
|
|
NULL, G_DBUS_SIGNAL_FLAGS_NONE, g_dbus_action_group_changed, group, NULL);
|
|
|
|
reply = g_dbus_connection_call_sync (group->connection, group->bus_name, group->object_path, "org.gtk.Actions",
|
|
"DescribeAll", NULL, G_VARIANT_TYPE ("(a{s(bgav)})"),
|
|
G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error);
|
|
|
|
if (reply != NULL)
|
|
{
|
|
GVariantIter *iter;
|
|
ActionInfo *action;
|
|
|
|
g_assert (group->actions == NULL);
|
|
group->actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, action_info_free);
|
|
|
|
g_variant_get (reply, "(a{s(bgav)})", &iter);
|
|
while ((action = action_info_new_from_iter (iter)))
|
|
g_hash_table_insert (group->actions, action->name, action);
|
|
g_variant_iter_free (iter);
|
|
g_variant_unref (reply);
|
|
}
|
|
|
|
return reply != NULL;
|
|
}
|
|
|
|
void
|
|
g_dbus_action_group_register_platform (GDBusActionGroupSendHookFunc send,
|
|
GDBusActionGroupEmitHookFunc before,
|
|
GDBusActionGroupEmitHookFunc after)
|
|
{
|
|
g_return_if_fail (g_dbus_action_group_send_hook == NULL &&
|
|
g_dbus_action_group_before_emit_hook == NULL &&
|
|
g_dbus_action_group_after_emit_hook == NULL);
|
|
|
|
g_dbus_action_group_send_hook = send;
|
|
g_dbus_action_group_before_emit_hook = before;
|
|
g_dbus_action_group_after_emit_hook = after;
|
|
}
|