gdbusproxy: Add G_DBUS_PROXY_FLAGS_NO_MATCH_RULE flag

D-Bus has an upper limit on number of Match rules and it's rather easy to hit
with a big number of proxies with signal subscriptions. This happens with
NetworkManager with hundreds of devices or connection settings. By passing
G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE to g_dbus_connection_signal_subscribe(), the
user can call AddMatch with a less granular match instead of a match per every
proxy.

Tests subsequently added by Philip Withnall.

Fixes: #1109
diff --git a/gio/gdbusproxy.c b/gio/gdbusproxy.c
index 7a2289b..9a9b352 100644
--- a/gio/gdbusproxy.c
+++ b/gio/gdbusproxy.c
@@ -1694,6 +1694,10 @@
 async_initable_init_first (GAsyncInitable *initable)
 {
   GDBusProxy *proxy = G_DBUS_PROXY (initable);
+  GDBusSignalFlags signal_flags = G_DBUS_SIGNAL_FLAGS_NONE;
+
+  if (proxy->priv->flags & G_DBUS_PROXY_FLAGS_NO_MATCH_RULE)
+    signal_flags |= G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE;
 
   if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
     {
@@ -1705,7 +1709,7 @@
                                             "PropertiesChanged",
                                             proxy->priv->object_path,
                                             proxy->priv->interface_name,
-                                            G_DBUS_SIGNAL_FLAGS_NONE,
+                                            signal_flags,
                                             on_properties_changed,
                                             weak_ref_new (G_OBJECT (proxy)),
                                             (GDestroyNotify) weak_ref_free);
@@ -1721,7 +1725,7 @@
                                             NULL,                        /* member */
                                             proxy->priv->object_path,
                                             NULL,                        /* arg0 */
-                                            G_DBUS_SIGNAL_FLAGS_NONE,
+                                            signal_flags,
                                             on_signal_received,
                                             weak_ref_new (G_OBJECT (proxy)),
                                             (GDestroyNotify) weak_ref_free);
@@ -1737,7 +1741,7 @@
                                             "NameOwnerChanged",      /* signal name */
                                             "/org/freedesktop/DBus", /* path */
                                             proxy->priv->name,       /* arg0 */
-                                            G_DBUS_SIGNAL_FLAGS_NONE,
+                                            signal_flags,
                                             on_name_owner_changed,
                                             weak_ref_new (G_OBJECT (proxy)),
                                             (GDestroyNotify) weak_ref_free);
diff --git a/gio/gioenums.h b/gio/gioenums.h
index d81ada4..756aed7 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -1021,6 +1021,9 @@
  * do not ask the bus to launch an owner during proxy initialization, but allow it to be
  * autostarted by a method call. This flag is only meaningful in proxies for well-known names,
  * and only if %G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START is not also specified.
+ * @G_DBUS_PROXY_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). (Since: 2.70)
  *
  * Flags used when constructing an instance of a #GDBusProxy derived class.
  *
@@ -1033,7 +1036,8 @@
   G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1),
   G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START = (1<<2),
   G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES = (1<<3),
-  G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION = (1<<4)
+  G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION = (1<<4),
+  G_DBUS_PROXY_FLAGS_NO_MATCH_RULE GLIB_AVAILABLE_ENUMERATOR_IN_2_70 = (1<<5)
 } GDBusProxyFlags;
 
 /**
diff --git a/gio/tests/gdbus-proxy.c b/gio/tests/gdbus-proxy.c
index 7e619c2..a16d70c 100644
--- a/gio/tests/gdbus-proxy.c
+++ b/gio/tests/gdbus-proxy.c
@@ -701,7 +701,6 @@
   connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
 
   g_assert_true (g_dbus_proxy_get_connection (proxy) == connection);
-  g_assert_cmpint (g_dbus_proxy_get_flags (proxy), ==, G_DBUS_PROXY_FLAGS_NONE);
   g_assert_null (g_dbus_proxy_get_interface_info (proxy));
   g_assert_cmpstr (g_dbus_proxy_get_name (proxy), ==, "com.example.TestService");
   g_assert_cmpstr (g_dbus_proxy_get_object_path (proxy), ==, "/com/example/TestObject");
@@ -720,7 +719,7 @@
 
   g_assert_true (conn == connection);
   g_assert_null (info);
-  g_assert_cmpint (flags, ==, G_DBUS_PROXY_FLAGS_NONE);
+  g_assert_cmpint (flags, ==, g_dbus_proxy_get_flags (proxy));
   g_assert_cmpstr (name, ==, "com.example.TestService");
   g_assert_cmpstr (path, ==, "/com/example/TestObject");
   g_assert_cmpstr (interface, ==, "com.example.Frob");
@@ -763,7 +762,7 @@
 }
 
 static void
-test_proxy (void)
+test_proxy_with_flags (GDBusProxyFlags flags)
 {
   GDBusProxy *proxy;
   GDBusConnection *connection;
@@ -777,7 +776,7 @@
   g_assert_no_error (error);
   error = NULL;
   proxy = g_dbus_proxy_new_sync (connection,
-                                 G_DBUS_PROXY_FLAGS_NONE,
+                                 flags,
                                  NULL,                      /* GDBusInterfaceInfo */
                                  "com.example.TestService", /* name */
                                  "/com/example/TestObject", /* object path */
@@ -809,6 +808,12 @@
   g_object_unref (connection);
 }
 
+static void
+test_proxy (void)
+{
+  test_proxy_with_flags (G_DBUS_PROXY_FLAGS_NONE);
+}
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
@@ -930,6 +935,58 @@
   g_source_remove (id);
 }
 
+typedef enum {
+  ADD_MATCH,
+  REMOVE_MATCH,
+} AddOrRemove;
+
+static void
+add_or_remove_match_rule (GDBusConnection *connection,
+                          AddOrRemove      add_or_remove,
+                          GVariant        *match_rule)
+{
+  GDBusMessage *message = NULL;
+  GError *error = NULL;
+
+  message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
+                                            "/org/freedesktop/DBus", /* path */
+                                            "org.freedesktop.DBus", /* interface */
+                                            (add_or_remove == ADD_MATCH) ? "AddMatch" : "RemoveMatch");
+  g_dbus_message_set_body (message, match_rule);
+  g_dbus_connection_send_message (connection,
+                                  message,
+                                  G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+                                  NULL,
+                                  &error);
+  g_assert_no_error (error);
+  g_clear_object (&message);
+}
+
+static void
+test_proxy_no_match_rule (void)
+{
+  GDBusConnection *connection = NULL;
+  GVariant *match_rule = NULL;
+
+  g_test_summary ("Test that G_DBUS_PROXY_FLAGS_NO_MATCH_RULE works");
+  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/1109");
+
+  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+
+  /* Add a custom match rule which matches everything. */
+  match_rule = g_variant_ref_sink (g_variant_new ("(s)", "type='signal'"));
+  add_or_remove_match_rule (connection, ADD_MATCH, match_rule);
+
+  /* Run the tests. */
+  test_proxy_with_flags (G_DBUS_PROXY_FLAGS_NO_MATCH_RULE);
+
+  /* Remove the match rule again. */
+  add_or_remove_match_rule (connection, REMOVE_MATCH, match_rule);
+
+  g_clear_pointer (&match_rule, g_variant_unref);
+  g_clear_object (&connection);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -950,6 +1007,7 @@
   g_test_add_func ("/gdbus/proxy/no-properties", test_no_properties);
   g_test_add_func ("/gdbus/proxy/wellknown-noauto", test_wellknown_noauto);
   g_test_add_func ("/gdbus/proxy/async", test_async);
+  g_test_add_func ("/gdbus/proxy/no-match-rule", test_proxy_no_match_rule);
 
   ret = session_bus_run();