diff --git a/dbus/dbus-gproxy.c b/dbus/dbus-gproxy.c index 572b7fb..9a6a4d7 100644 --- a/dbus/dbus-gproxy.c +++ b/dbus/dbus-gproxy.c @@ -125,6 +125,12 @@ typedef struct { GSList *proxies; /**< The list of proxies */ + /** + * Set of signals with the DBusGProxy which subscribes to the signals + * gchar *signal_name -> GHashTable* of (DBusGProxy *proxy -> guint count) + */ + GHashTable *signal_subscribed; + char name[4]; /**< name (empty string for none), nul byte, * path, nul byte, * interface, nul byte @@ -455,6 +461,7 @@ g_proxy_list_new (DBusGProxy *first_proxy) priv->path, priv->interface); list->proxies = NULL; + list->signal_subscribed = NULL; return list; } @@ -466,22 +473,41 @@ g_proxy_list_free (DBusGProxyList *list) * as they ref the GProxyManager */ g_slist_free (list->proxies); + if (list->signal_subscribed != NULL) + { + g_hash_table_destroy (list->signal_subscribed); + } g_free (list); } static char* -g_proxy_get_signal_match_rule (DBusGProxy *proxy) +g_proxy_get_signal_match_rule (DBusGProxy *proxy, const gchar *signal_name) { DBusGProxyPrivate *priv = DBUS_G_PROXY_GET_PRIVATE(proxy); /* FIXME Escaping is required here */ if (priv->name) - return g_strdup_printf ("type='signal',sender='%s',path='%s',interface='%s'", + { + if (signal_name == NULL) + return g_strdup_printf ("type='signal',sender='%s',path='%s',interface='%s'", priv->name, priv->path, priv->interface); + else + return g_strdup_printf ("type='signal',sender='%s',path='%s'," + "interface='%s',member='%s'", + priv->name, priv->path, priv->interface, + signal_name); + } else - return g_strdup_printf ("type='signal',path='%s',interface='%s'", - priv->path, priv->interface); + { + if (signal_name == NULL) + return g_strdup_printf ("type='signal',path='%s',interface='%s'", + priv->path, priv->interface); + else + return g_strdup_printf ("type='signal',path='%s',interface='%s'," + "member='%s'", + priv->path, priv->interface, signal_name); + } } static char * @@ -960,14 +986,9 @@ dbus_g_proxy_manager_register (DBusGProxyManager *manager, */ char *rule; - rule = g_proxy_get_signal_match_rule (proxy); - /* We don't check for errors; it's not like anyone would handle them, and - * we don't want a round trip here. - */ - dbus_bus_add_match (manager->connection, - rule, NULL); - g_free (rule); - + list->signal_subscribed = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); + rule = g_proxy_get_owner_match_rule (proxy); if (rule) dbus_bus_add_match (manager->connection, @@ -1068,17 +1089,34 @@ dbus_g_proxy_manager_unregister (DBusGProxyManager *manager, } } + if (list->signal_subscribed != NULL) + { + GHashTableIter iter; + gpointer proxy_set; + gpointer signal_name; + + g_hash_table_iter_init (&iter, list->signal_subscribed); + while (g_hash_table_iter_next (&iter, &signal_name, &proxy_set)) + { + g_hash_table_remove (proxy_set, proxy); + if (g_hash_table_size (proxy_set) == 0) + { + char *rule; + + rule = g_proxy_get_signal_match_rule (proxy, (gchar *) signal_name); + dbus_bus_remove_match (priv->manager->connection, + rule, NULL); + g_free (rule); + } + } + } + if (list->proxies == NULL) { char *rule; g_hash_table_remove (manager->proxy_lists, tri); list = NULL; - - rule = g_proxy_get_signal_match_rule (proxy); - dbus_bus_remove_match (manager->connection, - rule, NULL); - g_free (rule); rule = g_proxy_get_owner_match_rule (proxy); if (rule) dbus_bus_remove_match (manager->connection, @@ -2887,6 +2925,8 @@ dbus_g_proxy_connect_signal (DBusGProxy *proxy, GClosure *closure; GQuark q; DBusGProxyPrivate *priv; + char *tri; + DBusGProxyList *list; g_return_if_fail (DBUS_IS_G_PROXY (proxy)); g_return_if_fail (!DBUS_G_PROXY_DESTROYED (proxy)); @@ -2894,6 +2934,57 @@ dbus_g_proxy_connect_signal (DBusGProxy *proxy, g_return_if_fail (handler != NULL); priv = DBUS_G_PROXY_GET_PRIVATE(proxy); + + tri = tristring_from_proxy (proxy); + list = g_hash_table_lookup (priv->manager->proxy_lists, tri); + g_free (tri); + /* We have to add match rules to the server, + * but only if the server is a message bus, + * not if it's a peer. + */ + if (priv->name && list->signal_subscribed != NULL) + { + gchar *orig_key; + GHashTable *proxy_set; + gboolean found = g_hash_table_lookup_extended (list->signal_subscribed, + signal_name, (gpointer *) &orig_key, (gpointer *) &proxy_set); + gint handler_count; + gpointer value; + + if (!found || g_hash_table_size (proxy_set) == 0) + { + char *rule; + + rule = g_proxy_get_signal_match_rule (proxy, signal_name); + /* We don't check for errors; it's not like anyone would handle + * them, and we don't want a round trip here. + */ + dbus_bus_add_match (priv->manager->connection, + rule, NULL); + g_free (rule); + } + + if (!found) + { + proxy_set = g_hash_table_new (NULL, NULL); + orig_key = g_strdup (signal_name); + } + + if (g_hash_table_lookup_extended (proxy_set, proxy, NULL, + &value)) + { + handler_count = GPOINTER_TO_UINT (value) + 1; + } + else + { + handler_count = 1; + } + g_hash_table_insert (proxy_set, proxy, GINT_TO_POINTER (handler_count)); + + g_hash_table_steal (list->signal_subscribed, signal_name); + g_hash_table_insert (list->signal_subscribed, orig_key, proxy_set); + } + name = create_signal_name (priv->interface, signal_name); q = g_quark_try_string (name); @@ -2933,9 +3024,14 @@ dbus_g_proxy_disconnect_signal (DBusGProxy *proxy, GCallback handler, void *data) { + guint matched_count; + gint handler_count; char *name; GQuark q; DBusGProxyPrivate *priv; + char *tri; + DBusGProxyList *list; + gchar *_signal_name; g_return_if_fail (DBUS_IS_G_PROXY (proxy)); g_return_if_fail (!DBUS_G_PROXY_DESTROYED (proxy)); @@ -2946,25 +3042,72 @@ dbus_g_proxy_disconnect_signal (DBusGProxy *proxy, name = create_signal_name (priv->interface, signal_name); q = g_quark_try_string (name); + g_free (name); - if (q != 0) - { - g_signal_handlers_disconnect_matched (G_OBJECT (proxy), - G_SIGNAL_MATCH_DETAIL | - G_SIGNAL_MATCH_FUNC | - G_SIGNAL_MATCH_DATA, - signals[RECEIVED], - q, - NULL, - G_CALLBACK (handler), data); - } - else + if (q == 0) { g_warning ("Attempt to disconnect from signal '%s' which is not registered\n", - name); + signal_name); + return; } - g_free (name); + /* signal_name may be freed by g_signal_handlers_disconnect_matched() :'( + * Keep a copy while we need it + */ + _signal_name = g_strdup (signal_name); + matched_count = g_signal_handlers_disconnect_matched (G_OBJECT (proxy), + G_SIGNAL_MATCH_DETAIL | G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, + signals[RECEIVED], q, NULL, G_CALLBACK (handler), data); + + tri = tristring_from_proxy (proxy); + list = g_hash_table_lookup (priv->manager->proxy_lists, tri); + g_free (tri); + + /* Remove a match rule */ + if (list->signal_subscribed != NULL) + { + GHashTable *proxy_set = g_hash_table_lookup (list->signal_subscribed, + _signal_name); + gpointer value; + + if (proxy_set == NULL || !g_hash_table_lookup_extended (proxy_set, proxy, + NULL, &value)) + { + g_warning ("Attempt to disconnect from signal '%s' which is not" + " registered\n", _signal_name); + g_free (_signal_name); + return; + } + + handler_count = GPOINTER_TO_INT (value); + handler_count -= matched_count; + g_assert (handler_count >= 0); + if (handler_count == 0) + { + g_hash_table_remove (proxy_set, proxy); + } + else + { + g_hash_table_insert (proxy_set, proxy, + GINT_TO_POINTER (handler_count)); + } + + if (g_hash_table_size (proxy_set) == 0) + { + char *rule; + + rule = g_proxy_get_signal_match_rule (proxy, _signal_name); + /* We don't check for errors; it's not like anyone would handle them, + * and we don't want a round trip here. + */ + dbus_bus_remove_match (priv->manager->connection, + rule, NULL); + g_free (rule); + + g_hash_table_remove (list->signal_subscribed, _signal_name); + } + } + g_free (_signal_name); } /**