From 9be8763c998a43048cbde4ccbc6a211cb5bf71d1 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 26 Apr 2012 17:42:45 +0100 Subject: [PATCH 08/11] Add GVariant-based signal subscriptions --- dbus/dbus-glib.h | 21 +++ dbus/dbus-gproxy.c | 247 +++++++++++++++++++++++++++++++++++ doc/reference/dbus-glib-sections.txt | 5 + 3 files changed, 273 insertions(+) diff --git a/dbus/dbus-glib.h b/dbus/dbus-glib.h index 80a6099..dbdff5a 100644 --- a/dbus/dbus-glib.h +++ b/dbus/dbus-glib.h @@ -263,6 +263,16 @@ void dbus_g_proxy_add_signal (DBusGProxy *proxy, GType first_type, ...); +typedef enum /*< flags >*/ { + DBUS_G_SIGNAL_FLAGS_NONE = 0 +} DBusGSignalFlags; + +typedef void (*DBusGProxySignalCallback) (DBusGProxy *proxy, + const gchar *sender, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data); + void dbus_g_proxy_connect_signal (DBusGProxy *proxy, const char *signal_name, GCallback handler, @@ -273,6 +283,17 @@ void dbus_g_proxy_disconnect_signal (DBusGProxy *proxy, GCallback handler, void *data); +guint dbus_g_proxy_signal_subscribe (DBusGProxy *proxy, + const gchar *signal_name, + const gchar *arg0, + const GVariantType *signature, + DBusGSignalFlags flags, + DBusGProxySignalCallback callback, + gpointer user_data, + GDestroyNotify destroy_user_data); +void dbus_g_proxy_signal_unsubscribe (DBusGProxy *proxy, + guint id); + gboolean dbus_g_proxy_call (DBusGProxy *proxy, const char *method, GError **error, diff --git a/dbus/dbus-gproxy.c b/dbus/dbus-gproxy.c index c3ae9ec..c0542e3 100644 --- a/dbus/dbus-gproxy.c +++ b/dbus/dbus-gproxy.c @@ -52,6 +52,17 @@ typedef struct _DBusGProxyManager DBusGProxyManager; typedef struct _DBusGProxyPrivate DBusGProxyPrivate; +typedef struct { + GVariantType *signature; + DBusGProxySignalCallback callback; + gpointer user_data; + GDestroyNotify destroy_user_data; + gchar *arg0; + + guint id; + GQuark signal_name; +} SignalSubscriber; + struct _DBusGProxyPrivate { DBusGProxyManager *manager; /**< Proxy manager */ @@ -71,6 +82,10 @@ struct _DBusGProxyPrivate GHashTable *pending_calls; /**< Calls made on this proxy which have not yet returned */ int default_timeout; /**< Default timeout to use, see dbus_g_proxy_set_default_timeout */ + + /* GUINT_TO_POINTER(id) => SignalSubscriber */ + GTree *variant_signals; + guint next_subscriber_id; }; static void dbus_g_proxy_init (DBusGProxy *proxy); @@ -1369,7 +1384,20 @@ dbus_g_proxy_manager_filter (DBusConnection *connection, return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } +static void +signal_subscriber_free (gpointer data) +{ + SignalSubscriber *s = data; + + if (s->signature != NULL) + g_variant_type_free (s->signature); + if (s->destroy_user_data != NULL) + s->destroy_user_data (s->user_data); + + g_free (s->arg0); + g_slice_free (SignalSubscriber, s); +} /* ---------- DBusGProxy -------------- */ #define DBUS_G_PROXY_DESTROYED(proxy) (DBUS_G_PROXY_GET_PRIVATE(proxy)->manager == NULL) @@ -1400,6 +1428,20 @@ enum static void *parent_class; static guint signals[LAST_SIGNAL] = { 0 }; +static gint +direct_guint_cmp (gconstpointer a_, + gconstpointer b_, + gpointer user_data) +{ + guint a = GPOINTER_TO_UINT (a_); + guint b = GPOINTER_TO_UINT (b_); + + if (a < b) + return -1; + + return (a > b); +} + static void dbus_g_proxy_init (DBusGProxy *proxy) { @@ -1411,6 +1453,9 @@ dbus_g_proxy_init (DBusGProxy *proxy) priv->name_call = 0; priv->associated = FALSE; priv->default_timeout = -1; + priv->variant_signals = g_tree_new_full (direct_guint_cmp, NULL, + NULL, signal_subscriber_free); + priv->next_subscriber_id = 1; } static GObject * @@ -1541,6 +1586,9 @@ dbus_g_proxy_dispose (GObject *object) g_hash_table_destroy (priv->pending_calls); priv->pending_calls = NULL; + g_tree_destroy (priv->variant_signals); + priv->variant_signals = NULL; + if (priv->manager && proxy != priv->manager->bus_proxy) { dbus_g_proxy_manager_unregister (priv->manager, proxy); @@ -1739,6 +1787,18 @@ marshal_dbus_message_to_g_marshaller (GClosure *closure, g_value_array_free (value_array); } +static gboolean +collect_subscriber (gpointer key, + gpointer value, + gpointer data) +{ + GSList **l = data; + + *l = g_slist_prepend (*l, key); + /* don't stop traversal */ + return FALSE; +} + static void dbus_g_proxy_emit_remote_signal (DBusGProxy *proxy, DBusMessage *message) @@ -1749,6 +1809,7 @@ dbus_g_proxy_emit_remote_signal (DBusGProxy *proxy, GQuark q; DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy); GArray *msg_gsignature = NULL; + GSList *subscribers = NULL; g_return_if_fail (!DBUS_G_PROXY_DESTROYED (proxy)); @@ -1758,6 +1819,66 @@ dbus_g_proxy_emit_remote_signal (DBusGProxy *proxy, g_assert (interface != NULL); g_assert (signal != NULL); + /* Do the vaguely GDBus-style version first. This collects the subscribers + * backwards; we reverse them if there are any subscribers and we can + * understand the message. + * + * We just collect the keys, so that we can check before calling each + * subscriber that it hasn't already been removed. */ + g_tree_foreach (priv->variant_signals, collect_subscriber, &subscribers); + + if (subscribers != NULL) + { + GVariant *parameters; + + /* no point in going any further if we can't decode the message */ + parameters = dbus_g_message_read_variants (message, NULL); + + if (parameters != NULL) + { + GSList *iter; + const gchar *signature; + const gchar *arg0; + const gchar *sender; + + subscribers = g_slist_reverse (subscribers); + sender = dbus_message_get_sender (message); + q = g_quark_try_string (signal); + + signature = g_variant_get_type_string (parameters); + + g_assert (signature[0] == '('); + + if (signature[1] == 's') + arg0 = g_variant_get_string ( + g_variant_get_child_value (parameters, 0), NULL); + else + arg0 = NULL; + + for (iter = subscribers; iter != NULL; iter = iter->next) + { + SignalSubscriber *s = g_tree_lookup (priv->variant_signals, + iter->data); + + if (s != NULL) + { + if (s->signal_name != 0 && s->signal_name != q) + continue; + + if (s->arg0 != NULL && g_strcmp0 (s->arg0, arg0) != 0) + continue; + + if (s->signature != NULL && + !g_variant_is_of_type (parameters, s->signature)) + continue; + + s->callback (proxy, sender, signal, parameters, + s->user_data); + } + } + } + } + name = create_signal_name (interface, signal); /* If the quark isn't preexisting, there's no way there @@ -3202,3 +3323,129 @@ dbus_g_proxy_set_default_timeout (DBusGProxy *proxy, priv = DBUS_G_PROXY_GET_PRIVATE(proxy); priv->default_timeout = timeout; } + +/** + * DBusGProxySignalCallback: + * @proxy: the proxy on which the signal was emitted + * @sender: (allow-none): the unique name of the sender of the message, + * or %NULL if the message came from the peer with an unspecified sender + * name. + * @signal_name: the name of the signal + * @parameters: a %GVariant tuple with the parameters. For instance, if the + * signal has one 32-bit integer parameter, this variant will be a tuple with + * type-string "(i)". + * @user_data: the user data given to dbus_g_proxy_signal_subscribe(). + * + * Called when a signal matches a subscription added by + * dbus_g_proxy_signal_subscribe(). + */ + +/** + * DBusGSignalFlags: + * @DBUS_G_SIGNAL_FLAGS_NONE: No unusual behaviour. + * + * Flags affecting a signal subscription via + * dbus_g_proxy_signal_subscribe(). + */ + +/** + * dbus_g_proxy_signal_subscribe: + * @proxy: a proxy + * @signal_name: the signal name to match, or %NULL to match any signal name + * @arg0: the string first argument to match, or %NULL to match any first + * argument, including non-strings + * @signature: (allow-none): the expected signature, or %NULL to accept + * anything + * @flags: flags affecting the subscription, normally %DBUS_G_SIGNAL_FLAGS_NONE + * @callback: (scope notified) (closure user_data): callback to call whenever + * the signal arrives + * @user_data: (closure) (allow-none): user data for the callback + * @destroy_user_data: (allow-none): called to destroy @user_data when the + * subscription is cancelled or when @proxy emits #DBusGProxy::destroy + * + * Subscribe to a D-Bus signal. This function is a mixture + * of g_dbus_connection_signal_subscribe() and GDBusProxy::g-signal in GDBus. + * + * It is an error to call this method on a proxy that has emitted + * the #DBusGProxy::destroy signal. + * + * Returns: an identifier which can be passed + * to dbus_g_proxy_signal_unsubscribe() + */ +guint +dbus_g_proxy_signal_subscribe (DBusGProxy *proxy, + const gchar *signal_name, + const gchar *arg0, + const GVariantType *signature, + DBusGSignalFlags flags, + DBusGProxySignalCallback callback, + gpointer user_data, + GDestroyNotify destroy_user_data) +{ + DBusGProxyPrivate *priv; + SignalSubscriber *s; + + g_return_val_if_fail (DBUS_IS_G_PROXY (proxy), 0); + g_return_val_if_fail (!DBUS_G_PROXY_DESTROYED (proxy), 0); + g_return_val_if_fail (signal_name == NULL || + g_dbus_is_member_name (signal_name), 0); + g_return_val_if_fail (signature == NULL || + g_variant_type_is_tuple (signature), 0); + g_return_val_if_fail (flags == DBUS_G_SIGNAL_FLAGS_NONE, 0); + g_return_val_if_fail (callback != NULL, 0); + + priv = DBUS_G_PROXY_GET_PRIVATE (proxy); + + s = g_slice_new0 (SignalSubscriber); + + s->id = (priv->next_subscriber_id++); + s->signal_name = g_quark_from_string (signal_name); + s->callback = callback; + s->user_data = user_data; + s->destroy_user_data = destroy_user_data; + + if (signature != NULL) + s->signature = g_variant_type_copy (signature); + + if (arg0 != NULL) + s->arg0 = g_strdup (arg0); + + g_tree_insert (priv->variant_signals, GUINT_TO_POINTER (s->id), s); + + return s->id; +} + +/** + * dbus_g_proxy_signal_unsubscribe: + * @proxy: a proxy + * @id: the ID that was returned by dbus_g_proxy_signal_subscribe() + * + * Unsubscribe from a D-Bus signal. The @callback given to + * dbus_g_proxy_signal_subscribe() will no longer be called, + * and the corresponding @user_data will be freed with @destroy_user_data. + * + * It is an error to call this method on a proxy that has already emitted + * the #DBusGProxy::destroy signal. (It is also unnecessary, since all + * signal subscriptions are automatically discarded at that time.) + */ +void +dbus_g_proxy_signal_unsubscribe (DBusGProxy *proxy, + guint id) +{ + SignalSubscriber *s = NULL; + DBusGProxyPrivate *priv; + + g_return_if_fail (DBUS_IS_G_PROXY (proxy)); + g_return_if_fail (!DBUS_G_PROXY_DESTROYED (proxy)); + g_return_if_fail (id != 0); + + priv = DBUS_G_PROXY_GET_PRIVATE (proxy); + + s = g_tree_lookup (priv->variant_signals, GUINT_TO_POINTER (id)); + g_tree_steal (priv->variant_signals, GUINT_TO_POINTER (id)); + + if (s == NULL) + g_warning ("signal subscription %u not found", id); + else + signal_subscriber_free (s); +} diff --git a/doc/reference/dbus-glib-sections.txt b/doc/reference/dbus-glib-sections.txt index 0b57641..4bff443 100644 --- a/doc/reference/dbus-glib-sections.txt +++ b/doc/reference/dbus-glib-sections.txt @@ -101,6 +101,11 @@ dbus_g_proxy_begin_call_with_timeout dbus_g_proxy_end_call dbus_g_proxy_cancel_call dbus_g_proxy_set_default_timeout + +DBusGProxySignalCallback +DBusGSignalFlags +dbus_g_proxy_signal_subscribe +dbus_g_proxy_signal_unsubscribe DBUS_G_PROXY DBUS_IS_G_PROXY -- 1.8.4.rc3