gchar *member;
gchar *object_path;
gchar *arg0;
+ GDBusSignalFlags flags;
GArray *subscribers;
} SignalData;
}
static gchar *
-args_to_rule (const gchar *sender,
- const gchar *interface_name,
- const gchar *member,
- const gchar *object_path,
- const gchar *arg0,
- gboolean negate)
+args_to_rule (const gchar *sender,
+ const gchar *interface_name,
+ const gchar *member,
+ const gchar *object_path,
+ const gchar *arg0,
+ GDBusSignalFlags flags)
{
GString *rule;
rule = g_string_new ("type='signal'");
- if (negate)
+ if (flags & G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE)
g_string_prepend_c (rule, '-');
if (sender != NULL)
g_string_append_printf (rule, ",sender='%s'", sender);
g_string_append_printf (rule, ",member='%s'", member);
if (object_path != NULL)
g_string_append_printf (rule, ",path='%s'", object_path);
+
if (arg0 != NULL)
- g_string_append_printf (rule, ",arg0='%s'", arg0);
+ {
+ if (flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH)
+ g_string_append_printf (rule, ",arg0path='%s'", arg0);
+ else if (flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE)
+ g_string_append_printf (rule, ",arg0namespace='%s'", arg0);
+ else
+ g_string_append_printf (rule, ",arg0='%s'", arg0);
+ }
return g_string_free (rule, FALSE);
}
* tracking the name owner of the well-known name and use that when
* processing the received signal.
*
+ * If one of %G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE or
+ * %G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH are given, @arg0 is
+ * interpreted as part of a namespace or path. The first argument
+ * of a signal is matched against that part as specified by D-Bus.
+ *
* Returns: A subscription identifier that can be used with g_dbus_connection_signal_unsubscribe().
*
* Since: 2.26
g_return_val_if_fail (object_path == NULL || g_variant_is_object_path (object_path), 0);
g_return_val_if_fail (callback != NULL, 0);
g_return_val_if_fail (check_initialized (connection), 0);
+ g_return_val_if_fail (!((flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH) && (flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE)), 0);
+ g_return_val_if_fail (!(arg0 == NULL && (flags & (G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH | G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE))), 0);
CONNECTION_LOCK (connection);
* the usual way, but the '-' prevents the match rule from ever
* actually being send to the bus (either for add or remove).
*/
- rule = args_to_rule (sender, interface_name, member, object_path, arg0,
- (flags & G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE) != 0);
+ rule = args_to_rule (sender, interface_name, member, object_path, arg0, flags);
if (sender != NULL && (g_dbus_is_unique_name (sender) || g_strcmp0 (sender, "org.freedesktop.DBus") == 0))
sender_unique_name = sender;
signal_data->member = g_strdup (member);
signal_data->object_path = g_strdup (object_path);
signal_data->arg0 = g_strdup (arg0);
+ signal_data->flags = flags;
signal_data->subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
g_array_append_val (signal_data->subscribers, subscriber);
g_free (signal_instance);
}
+static gboolean
+namespace_rule_matches (const gchar *namespace,
+ const gchar *name)
+{
+ gint len_namespace;
+ gint len_name;
+
+ len_namespace = strlen (namespace);
+ len_name = strlen (name);
+
+ if (len_name < len_namespace)
+ return FALSE;
+
+ if (memcmp (namespace, name, len_namespace) != 0)
+ return FALSE;
+
+ return len_namespace == len_name || name[len_namespace] == '.';
+}
+
+static gboolean
+path_rule_matches (const gchar *path_a,
+ const gchar *path_b)
+{
+ gint len_a, len_b;
+
+ len_a = strlen (path_a);
+ len_b = strlen (path_b);
+
+ if (len_a < len_b && path_a[len_a - 1] != '/')
+ return FALSE;
+
+ if (len_b < len_a && path_b[len_b - 1] != '/')
+ return FALSE;
+
+ return memcmp (path_a, path_b, MIN (len_a, len_b)) == 0;
+}
+
/* called in GDBusWorker thread WITH lock held */
static void
schedule_callbacks (GDBusConnection *connection,
if (signal_data->object_path != NULL && g_strcmp0 (signal_data->object_path, path) != 0)
continue;
- if (signal_data->arg0 != NULL && g_strcmp0 (signal_data->arg0, arg0) != 0)
- continue;
+ if (signal_data->arg0 != NULL)
+ {
+ if (arg0 == NULL)
+ continue;
+
+ if (signal_data->flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE)
+ {
+ if (!namespace_rule_matches (signal_data->arg0, arg0))
+ continue;
+ }
+ else if (signal_data->flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH)
+ {
+ if (!path_rule_matches (signal_data->arg0, arg0))
+ continue;
+ }
+ else if (!g_str_equal (signal_data->arg0, arg0))
+ continue;
+ }
for (m = 0; m < signal_data->subscribers->len; m++)
{
* @G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE: Don't actually send the AddMatch
* D-Bus call for this signal subscription. This gives you more control
* over which match rules you add (but you must add them manually).
+ * @G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE: Match first arguments that
+ * contain a bus or interface name with the given namespace.
+ * @G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH: Match first arguments that
+ * contain an object path that is either equivalent to the given path,
+ * or one of the paths is a subpath of the other.
*
* Flags used when subscribing to signals via g_dbus_connection_signal_subscribe().
*
typedef enum /*< flags >*/
{
G_DBUS_SIGNAL_FLAGS_NONE = 0,
- G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE = (1<<0)
+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE = (1<<0),
+ G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE = (1<<1),
+ G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH = (1<<2)
} GDBusSignalFlags;
/**
session_bus_down ();
}
+static void
+test_match_rule (GDBusConnection *connection,
+ GDBusSignalFlags flags,
+ gchar *arg0_rule,
+ gchar *arg0,
+ gboolean should_match)
+{
+ guint subscription_ids[2];
+ gint emissions = 0;
+ gint matches = 0;
+ GError *error = NULL;
+
+ subscription_ids[0] = g_dbus_connection_signal_subscribe (connection,
+ NULL, "org.gtk.ExampleInterface", "Foo", "/",
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ test_connection_signal_handler,
+ &emissions, NULL);
+ subscription_ids[1] = g_dbus_connection_signal_subscribe (connection,
+ NULL, "org.gtk.ExampleInterface", "Foo", "/",
+ arg0_rule,
+ flags,
+ test_connection_signal_handler,
+ &matches, NULL);
+ g_assert_cmpint (subscription_ids[0], !=, 0);
+ g_assert_cmpint (subscription_ids[1], !=, 0);
+
+ g_dbus_connection_emit_signal (connection,
+ NULL, "/", "org.gtk.ExampleInterface",
+ "Foo", g_variant_new ("(s)", arg0),
+ &error);
+ g_assert_no_error (error);
+
+ /* synchronously ping a non-existent method to make sure the signals are dispatched */
+ g_dbus_connection_call_sync (connection, "org.gtk.ExampleInterface", "/", "org.gtk.ExampleInterface",
+ "Bar", g_variant_new ("()"), G_VARIANT_TYPE_UNIT, G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL);
+
+ while (g_main_context_iteration (NULL, FALSE))
+ ;
+
+ g_assert_cmpint (emissions, ==, 1);
+ g_assert_cmpint (matches, ==, should_match ? 1 : 0);
+
+ g_dbus_connection_signal_unsubscribe (connection, subscription_ids[0]);
+ g_dbus_connection_signal_unsubscribe (connection, subscription_ids[1]);
+}
+
+static void
+test_connection_signal_match_rules (void)
+{
+ GDBusConnection *con;
+
+ session_bus_up ();
+ con = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_NONE, "foo", "foo", TRUE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_NONE, "foo", "bar", FALSE);
+
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "", FALSE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org", FALSE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org.gtk", TRUE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org.gtk.Example", TRUE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org.gtk+", FALSE);
+
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/", "/", TRUE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/", "", FALSE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/Example", "/org/gtk/Example", TRUE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/", "/org/gtk/Example", TRUE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/Example", "/org/gtk/", TRUE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/Example", "/org/gtk", FALSE);
+ test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk+", "/org/gtk", FALSE);
+
+ g_object_unref (con);
+ session_bus_down ();
+}
+
/* ---------------------------------------------------------------------------------------------------- */
typedef struct
g_test_add_func ("/gdbus/connection/life-cycle", test_connection_life_cycle);
g_test_add_func ("/gdbus/connection/send", test_connection_send);
g_test_add_func ("/gdbus/connection/signals", test_connection_signals);
+ g_test_add_func ("/gdbus/connection/signal-match-rules", test_connection_signal_match_rules);
g_test_add_func ("/gdbus/connection/filter", test_connection_filter);
g_test_add_func ("/gdbus/connection/serials", test_connection_serials);
return g_test_run();