[glib/gdbus] Handle properties and signals in GDBusProxy



commit dbdf7a8cd2ae9df58eda578cb771da608aadc6c9
Author: David Zeuthen <davidz redhat com>
Date:   Tue Apr 28 23:32:59 2009 -0400

    Handle properties and signals in GDBusProxy
    
    Also add generic signal subscription code to GDBusConnection.
    
    Note that we abort if AddMatch() fails since it can only fail with the
    bus being in an OOM condition. We might want to change that but that
    would involve making g_dbus_connection_dbus_1_signal_subscribe()
    asynchronous and having the call sites handle that.
    
    And there's really no sensible way of handling this short of retrying
    to add the match rule... and then there's the little thing that, hey,
    maybe there's a reason the bus in an OOM condition.
    
    Doable, but not really sure it's worth it...
---
 docs/reference/gdbus/gdbus-sections.txt |   13 +-
 gdbus/gbusnameowner.c                   |  158 ++-----
 gdbus/gbusnamewatcher.c                 |  175 +------
 gdbus/gdbus-marshal.list                |    2 +-
 gdbus/gdbus.symbols                     |   16 +-
 gdbus/gdbusconnection.c                 |  510 +++++++++++++++++++-
 gdbus/gdbusconnection.h                 |   22 +
 gdbus/gdbusenums.h                      |   15 +
 gdbus/gdbusproxy.c                      |  810 +++++++++++++++++++++++--------
 gdbus/gdbusproxy.h                      |  103 +++--
 gdbus/tests/proxy.c                     |  267 ++++++++++-
 gdbus/tests/testserver.py               |   33 ++
 12 files changed, 1596 insertions(+), 528 deletions(-)

diff --git a/docs/reference/gdbus/gdbus-sections.txt b/docs/reference/gdbus/gdbus-sections.txt
index ac0ff7f..0e60e0b 100644
--- a/docs/reference/gdbus/gdbus-sections.txt
+++ b/docs/reference/gdbus/gdbus-sections.txt
@@ -43,6 +43,9 @@ g_dbus_connection_send_dbus_1_message_with_reply
 g_dbus_connection_send_dbus_1_message_block
 g_dbus_connection_send_dbus_1_message_cancel
 g_dbus_connection_send_dbus_1_message_with_reply_finish
+GDBusSignalCallback1
+g_dbus_connection_dbus_1_signal_subscribe
+g_dbus_connection_dbus_1_signal_unsubscribe
 <SUBSECTION Standard>
 G_DBUS_CONNECTION
 G_IS_DBUS_CONNECTION
@@ -114,21 +117,23 @@ g_dbus_error_new_for_gerror
 <SECTION>
 <FILE>gdbusproxy</FILE>
 <TITLE>GDBusProxy</TITLE>
+GDBusProxyFlags
 GDBusProxy
 GDBusProxyClass
 g_dbus_proxy_new
-g_dbus_proxy_new_finish
+g_dbus_proxy_get_flags
 g_dbus_proxy_get_connection
 g_dbus_proxy_get_interface_name
 g_dbus_proxy_get_name
+g_dbus_proxy_get_name_owner
 g_dbus_proxy_get_object_path
+g_dbus_proxy_get_properties_available
 g_dbus_proxy_get_cached_property
-g_dbus_proxy_set_cached_property
 g_dbus_proxy_invoke_method
+g_dbus_proxy_invoke_method_block
+g_dbus_proxy_invoke_method_cancel
 g_dbus_proxy_invoke_method_finish
 g_dbus_proxy_invoke_method_sync
-g_dbus_proxy_get_properties_loaded
-g_dbus_proxy_block_for_properties
 <SUBSECTION Standard>
 G_DBUS_PROXY
 G_IS_DBUS_PROXY
diff --git a/gdbus/gbusnameowner.c b/gdbus/gbusnameowner.c
index d76df78..50a42c5 100644
--- a/gdbus/gbusnameowner.c
+++ b/gdbus/gbusnameowner.c
@@ -59,9 +59,8 @@ struct _GBusNameOwnerPrivate
 
   gboolean is_initialized;
 
-  /* stack of messages to process in idle */
-  GPtrArray *idle_processing_messages;
-  guint idle_processing_event_source_id;
+  guint name_lost_subscription_id;
+  guint name_acquired_subscription_id;
 };
 
 enum
@@ -94,11 +93,6 @@ static GBusNameOwner *singletons_get_owner (GDBusConnection *connection, const g
 static void singletons_add_owner (GBusNameOwner *owner, GDBusConnection *connection, const gchar *name);
 static void singletons_remove_owner (GBusNameOwner *owner);
 
-static DBusHandlerResult
-filter_function (DBusConnection *dbus_1_connection,
-                 DBusMessage    *message,
-                 void           *user_data);
-
 static guint signals[LAST_SIGNAL] = { 0 };
 
 static void on_connection_opened (GDBusConnection *connection,
@@ -155,25 +149,17 @@ g_bus_name_owner_finalize (GObject *object)
       g_signal_handlers_disconnect_by_func (owner->priv->connection, on_connection_closed, owner);
       g_signal_handlers_disconnect_by_func (owner->priv->connection, on_connection_initialized, owner);
 
-      if (g_dbus_connection_get_is_open (owner->priv->connection))
-        {
-          dbus_connection_remove_filter (g_dbus_connection_get_dbus_1_connection (owner->priv->connection),
-                                         filter_function,
-                                         owner);
-        }
+      if (owner->priv->name_lost_subscription_id > 0)
+        g_dbus_connection_dbus_1_signal_unsubscribe (owner->priv->connection,
+                                                     owner->priv->name_lost_subscription_id);
+      if (owner->priv->name_acquired_subscription_id > 0)
+        g_dbus_connection_dbus_1_signal_unsubscribe (owner->priv->connection,
+                                                     owner->priv->name_acquired_subscription_id);
 
       g_object_unref (owner->priv->connection);
     }
   g_free (owner->priv->name);
 
-  if (owner->priv->idle_processing_event_source_id > 0)
-    g_source_remove (owner->priv->idle_processing_event_source_id);
-  if (owner->priv->idle_processing_messages != NULL)
-    {
-      g_ptr_array_foreach (owner->priv->idle_processing_messages, (GFunc) dbus_message_unref, NULL);
-      g_ptr_array_free (owner->priv->idle_processing_messages, TRUE);
-    }
-
   if (G_OBJECT_CLASS (g_bus_name_owner_parent_class)->finalize != NULL)
     G_OBJECT_CLASS (g_bus_name_owner_parent_class)->finalize (object);
 }
@@ -250,56 +236,16 @@ g_bus_name_owner_set_property (GObject      *object,
     }
 }
 
-#define PRINT_MESSAGE(message)                          \
-  do {                                                  \
-    const gchar *message_type;                          \
-    switch (dbus_message_get_type (message))            \
-      {                                                 \
-      case DBUS_MESSAGE_TYPE_METHOD_CALL:               \
-        message_type = "method_call";                   \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_METHOD_RETURN:             \
-        message_type = "method_return";                 \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_ERROR:                     \
-        message_type = "error";                         \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_SIGNAL:                    \
-        message_type = "signal";                        \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_INVALID:                   \
-        message_type = "invalid";                       \
-        break;                                          \
-      default:                                          \
-        message_type = "unknown";                       \
-        break;                                          \
-      }                                                 \
-    g_print ("new message:\n"                           \
-             " type:         %s\n"                      \
-             " sender:       %s\n"                      \
-             " destination:  %s\n"                      \
-             " path:         %s\n"                      \
-             " interface:    %s\n"                      \
-             " member:       %s\n",                     \
-             message_type,                              \
-             dbus_message_get_sender (message),         \
-             dbus_message_get_destination (message),    \
-             dbus_message_get_path (message),           \
-             dbus_message_get_interface (message),      \
-             dbus_message_get_member (message));        \
-  } while (FALSE)
-
 static void
-process_message (GBusNameOwner *owner,
-                 DBusMessage   *message)
+on_name_lost_or_acquired (GDBusConnection *connection,
+                          DBusMessage     *message,
+                          gpointer         user_data)
 {
+  GBusNameOwner *owner = G_BUS_NAME_OWNER (user_data);
   DBusError dbus_error;
   const gchar *name;
   gboolean old_owns_name;
 
-  //g_debug ("in bus-name-owner's filter_function for dbus_1_connection %p", dbus_1_connection);
-  //PRINT_MESSAGE (message);
-
   dbus_error_init (&dbus_error);
 
   old_owns_name = owner->priv->owns_name;
@@ -369,42 +315,6 @@ process_message (GBusNameOwner *owner,
     }
 }
 
-static gboolean
-process_messages_in_idle (gpointer user_data)
-{
-  GBusNameOwner *owner = G_BUS_NAME_OWNER (user_data);
-  guint n;
-
-  for (n = 0; n < owner->priv->idle_processing_messages->len; n++)
-    {
-      DBusMessage *message = owner->priv->idle_processing_messages->pdata[n];
-      process_message (owner, message);
-      dbus_message_unref (message);
-    }
-
-  g_ptr_array_remove_range (owner->priv->idle_processing_messages,
-                            0,
-                            owner->priv->idle_processing_messages->len);
-  owner->priv->idle_processing_event_source_id = 0;
-  return FALSE;
-}
-
-static DBusHandlerResult
-filter_function (DBusConnection *dbus_1_connection,
-                 DBusMessage    *message,
-                 void           *user_data)
-{
-  GBusNameOwner *owner = G_BUS_NAME_OWNER (user_data);
-
-  if (owner->priv->idle_processing_messages == NULL)
-    owner->priv->idle_processing_messages = g_ptr_array_new ();
-  g_ptr_array_add (owner->priv->idle_processing_messages, dbus_message_ref (message));
-  if (owner->priv->idle_processing_event_source_id == 0)
-    owner->priv->idle_processing_event_source_id = g_idle_add (process_messages_in_idle, owner);
-
-  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-}
-
 static void
 request_name_cb (GDBusConnection *connection,
                  GAsyncResult    *res,
@@ -497,16 +407,31 @@ on_connection_opened (GDBusConnection *connection,
   DBusMessage *message;
   dbus_uint32_t flags;
 
+  /* invariants */
   g_assert (g_dbus_connection_get_is_open (owner->priv->connection));
-
-  /* set up a filter function for listening on NameLost and NameAcquired messages on the DBusConnection */
-  if (!dbus_connection_add_filter (g_dbus_connection_get_dbus_1_connection (owner->priv->connection),
-                                   filter_function,
-                                   owner,
-                                   NULL))
-    _g_dbus_oom ();
-
   g_assert (!owner->priv->owns_name);
+  g_assert (owner->priv->name_lost_subscription_id == 0);
+  g_assert (owner->priv->name_acquired_subscription_id == 0);
+
+  /* listen to NameLost and NameAcquired messages */
+  owner->priv->name_lost_subscription_id =
+    g_dbus_connection_dbus_1_signal_subscribe (owner->priv->connection,
+                                               DBUS_SERVICE_DBUS,
+                                               DBUS_INTERFACE_DBUS,
+                                               "NameLost",
+                                               DBUS_PATH_DBUS,
+                                               owner->priv->name,
+                                               on_name_lost_or_acquired,
+                                               owner);
+  owner->priv->name_acquired_subscription_id =
+    g_dbus_connection_dbus_1_signal_subscribe (owner->priv->connection,
+                                               DBUS_SERVICE_DBUS,
+                                               DBUS_INTERFACE_DBUS,
+                                               "NameAcquired",
+                                               DBUS_PATH_DBUS,
+                                               owner->priv->name,
+                                               on_name_lost_or_acquired,
+                                               owner);
 
   flags = get_request_name_flags (owner->priv->flags);
   if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
@@ -534,7 +459,18 @@ on_connection_closed (GDBusConnection *connection,
 {
   GBusNameOwner *owner = G_BUS_NAME_OWNER (user_data);
 
-  /* no need to remove filter; it is removed when the DBusConnection is destroyed */
+  if (owner->priv->name_lost_subscription_id != 0)
+    {
+      g_dbus_connection_dbus_1_signal_unsubscribe (owner->priv->connection,
+                                                   owner->priv->name_lost_subscription_id);
+      owner->priv->name_lost_subscription_id = 0;
+    }
+  if (owner->priv->name_acquired_subscription_id != 0)
+    {
+      g_dbus_connection_dbus_1_signal_unsubscribe (owner->priv->connection,
+                                                   owner->priv->name_acquired_subscription_id);
+      owner->priv->name_acquired_subscription_id = 0;
+    }
 
   /* if we currently own the name, well too bad, not anymore, it's up for grabs */
   if (owner->priv->owns_name)
diff --git a/gdbus/gbusnamewatcher.c b/gdbus/gbusnamewatcher.c
index 2e8da33..b65f1a3 100644
--- a/gdbus/gbusnamewatcher.c
+++ b/gdbus/gbusnamewatcher.c
@@ -56,13 +56,9 @@ struct _GBusNameWatcherPrivate
 
   gchar *name_owner;
 
-  gchar *match_rule;
-
   gboolean is_initialized;
 
-  /* stack of messages to process in idle */
-  GPtrArray *idle_processing_messages;
-  guint idle_processing_event_source_id;
+  guint name_owner_changed_subscription_id;
 };
 
 enum
@@ -95,11 +91,6 @@ static GBusNameWatcher *singletons_get_watcher (GDBusConnection *connection, con
 static void singletons_add_watcher (GBusNameWatcher *watcher, GDBusConnection *connection, const gchar *name);
 static void singletons_remove_watcher (GBusNameWatcher *watcher);
 
-static DBusHandlerResult
-filter_function (DBusConnection *dbus_1_connection,
-                 DBusMessage    *message,
-                 void           *user_data);
-
 static guint signals[LAST_SIGNAL] = { 0 };
 
 static void on_connection_opened (GDBusConnection *connection,
@@ -137,41 +128,15 @@ g_bus_name_watcher_finalize (GObject *object)
       g_signal_handlers_disconnect_by_func (watcher->priv->connection, on_connection_closed, watcher);
       g_signal_handlers_disconnect_by_func (watcher->priv->connection, on_connection_initialized, watcher);
 
-      if (g_dbus_connection_get_is_open (watcher->priv->connection))
-        {
-          DBusMessage *message;
-
-          if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
-                                                       DBUS_PATH_DBUS,
-                                                       DBUS_INTERFACE_DBUS,
-                                                       "RemoveMatch")) == NULL)
-            _g_dbus_oom ();
-          if (!dbus_message_append_args (message,
-                                         DBUS_TYPE_STRING, &watcher->priv->match_rule,
-                                         DBUS_TYPE_INVALID))
-            _g_dbus_oom ();
-          g_dbus_connection_send_dbus_1_message (watcher->priv->connection, message);
-          dbus_message_unref (message);
-
-          dbus_connection_remove_filter (g_dbus_connection_get_dbus_1_connection (watcher->priv->connection),
-                                         filter_function,
-                                         watcher);
-        }
-      g_free (watcher->priv->match_rule);
+      if (watcher->priv->name_owner_changed_subscription_id > 0)
+        g_dbus_connection_dbus_1_signal_unsubscribe (watcher->priv->connection,
+                                                     watcher->priv->name_owner_changed_subscription_id);
 
       g_object_unref (watcher->priv->connection);
     }
   g_free (watcher->priv->name);
   g_free (watcher->priv->name_owner);
 
-  if (watcher->priv->idle_processing_event_source_id > 0)
-    g_source_remove (watcher->priv->idle_processing_event_source_id);
-  if (watcher->priv->idle_processing_messages != NULL)
-    {
-      g_ptr_array_foreach (watcher->priv->idle_processing_messages, (GFunc) dbus_message_unref, NULL);
-      g_ptr_array_free (watcher->priv->idle_processing_messages, TRUE);
-    }
-
   if (G_OBJECT_CLASS (g_bus_name_watcher_parent_class)->finalize != NULL)
     G_OBJECT_CLASS (g_bus_name_watcher_parent_class)->finalize (object);
 }
@@ -240,66 +205,19 @@ g_bus_name_watcher_set_property (GObject      *object,
     }
 }
 
-#define PRINT_MESSAGE(message)                          \
-  do {                                                  \
-    const gchar *message_type;                          \
-    switch (dbus_message_get_type (message))            \
-      {                                                 \
-      case DBUS_MESSAGE_TYPE_METHOD_CALL:               \
-        message_type = "method_call";                   \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_METHOD_RETURN:             \
-        message_type = "method_return";                 \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_ERROR:                     \
-        message_type = "error";                         \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_SIGNAL:                    \
-        message_type = "signal";                        \
-        break;                                          \
-      case DBUS_MESSAGE_TYPE_INVALID:                   \
-        message_type = "invalid";                       \
-        break;                                          \
-      default:                                          \
-        message_type = "unknown";                       \
-        break;                                          \
-      }                                                 \
-    g_print ("new message:\n"                           \
-             " type:         %s\n"                      \
-             " sender:       %s\n"                      \
-             " destination:  %s\n"                      \
-             " path:         %s\n"                      \
-             " interface:    %s\n"                      \
-             " member:       %s\n",                     \
-             message_type,                              \
-             dbus_message_get_sender (message),         \
-             dbus_message_get_destination (message),    \
-             dbus_message_get_path (message),           \
-             dbus_message_get_interface (message),      \
-             dbus_message_get_member (message));        \
-  } while (FALSE)
-
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-process_message (GBusNameWatcher *watcher,
-                 DBusMessage   *message)
+on_name_owner_changed (GDBusConnection *connection,
+                       DBusMessage     *message,
+                       gpointer         user_data)
 {
+  GBusNameWatcher *watcher = G_BUS_NAME_WATCHER (user_data);
   const gchar *name;
   const gchar *old_owner;
   const gchar *new_owner;
   DBusError dbus_error;
 
-  //PRINT_MESSAGE (message);
-
-  /* we only care about NameOwnerChanged */
-  if (!(dbus_message_is_signal (message,
-                                DBUS_INTERFACE_DBUS,
-                                "NameOwnerChanged") &&
-        g_strcmp0 (dbus_message_get_sender (message), DBUS_SERVICE_DBUS) == 0 &&
-        g_strcmp0 (dbus_message_get_path (message), DBUS_PATH_DBUS) == 0))
-    goto out;
-
   /* extract parameters */
   dbus_error_init (&dbus_error);
   if (!dbus_message_get_args (message,
@@ -340,42 +258,6 @@ process_message (GBusNameWatcher *watcher,
   ;
 }
 
-static gboolean
-process_messages_in_idle (gpointer user_data)
-{
-  GBusNameWatcher *watcher = G_BUS_NAME_WATCHER (user_data);
-  guint n;
-
-  for (n = 0; n < watcher->priv->idle_processing_messages->len; n++)
-    {
-      DBusMessage *message = watcher->priv->idle_processing_messages->pdata[n];
-      process_message (watcher, message);
-      dbus_message_unref (message);
-    }
-
-  g_ptr_array_remove_range (watcher->priv->idle_processing_messages,
-                            0,
-                            watcher->priv->idle_processing_messages->len);
-  watcher->priv->idle_processing_event_source_id = 0;
-  return FALSE;
-}
-
-static DBusHandlerResult
-filter_function (DBusConnection *dbus_1_connection,
-                 DBusMessage    *message,
-                 void           *user_data)
-{
-  GBusNameWatcher *watcher = G_BUS_NAME_WATCHER (user_data);
-
-  if (watcher->priv->idle_processing_messages == NULL)
-    watcher->priv->idle_processing_messages = g_ptr_array_new ();
-  g_ptr_array_add (watcher->priv->idle_processing_messages, dbus_message_ref (message));
-  if (watcher->priv->idle_processing_event_source_id == 0)
-    watcher->priv->idle_processing_event_source_id = g_idle_add (process_messages_in_idle, watcher);
-
-  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-}
-
 static void
 get_name_owner_cb (GDBusConnection *connection,
                    GAsyncResult    *res,
@@ -466,28 +348,20 @@ on_connection_opened (GDBusConnection *connection,
   GBusNameWatcher *watcher = G_BUS_NAME_WATCHER (user_data);
   DBusMessage *message;
 
+  /* invariants */
   g_assert (g_dbus_connection_get_is_open (watcher->priv->connection));
   g_assert (watcher->priv->name_owner == NULL);
+  g_assert (watcher->priv->name_owner_changed_subscription_id == 0);
 
-  /* set up a filter function for listening on NameOwnerChanged messages on the DBusConnection */
-  if (!dbus_connection_add_filter (g_dbus_connection_get_dbus_1_connection (watcher->priv->connection),
-                                   filter_function,
-                                   watcher,
-                                   NULL))
-    _g_dbus_oom ();
-
-  /* set up match rule */
-  if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
-                                               DBUS_PATH_DBUS,
+  watcher->priv->name_owner_changed_subscription_id =
+    g_dbus_connection_dbus_1_signal_subscribe (watcher->priv->connection,
+                                               DBUS_SERVICE_DBUS,
                                                DBUS_INTERFACE_DBUS,
-                                               "AddMatch")) == NULL)
-    _g_dbus_oom ();
-  if (!dbus_message_append_args (message,
-                                 DBUS_TYPE_STRING, &watcher->priv->match_rule,
-                                 DBUS_TYPE_INVALID))
-    _g_dbus_oom ();
-  g_dbus_connection_send_dbus_1_message (watcher->priv->connection, message);
-  dbus_message_unref (message);
+                                               "NameOwnerChanged",
+                                               DBUS_PATH_DBUS,
+                                               watcher->priv->name,
+                                               on_name_owner_changed,
+                                               watcher);
 
   if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
                                                DBUS_PATH_DBUS,
@@ -513,7 +387,12 @@ on_connection_closed (GDBusConnection *connection,
 {
   GBusNameWatcher *watcher = G_BUS_NAME_WATCHER (user_data);
 
-  /* no need to remove filter; it is removed when the DBusConnection is destroyed */
+  if (watcher->priv->name_owner_changed_subscription_id != 0)
+    {
+      g_dbus_connection_dbus_1_signal_unsubscribe (watcher->priv->connection,
+                                                   watcher->priv->name_owner_changed_subscription_id);
+      watcher->priv->name_owner_changed_subscription_id = 0;
+    }
 
   /* if we currently have a name owner then nuke it */
   if (watcher->priv->name_owner != NULL)
@@ -803,12 +682,6 @@ g_bus_name_watcher_constructed (GObject *object)
       watcher->priv->bus_type = g_dbus_connection_get_bus_type (watcher->priv->connection);
     }
 
-  watcher->priv->match_rule = g_strdup_printf ("type='signal',"
-                                               "sender='org.freedesktop.DBus',"
-                                               "member='NameOwnerChanged',"
-                                               "arg0='%s'",
-                                               watcher->priv->name);
-
   g_signal_connect (watcher->priv->connection, "opened", G_CALLBACK (on_connection_opened), watcher);
   g_signal_connect (watcher->priv->connection, "closed", G_CALLBACK (on_connection_closed), watcher);
   g_signal_connect (watcher->priv->connection, "initialized", G_CALLBACK (on_connection_initialized), watcher);
diff --git a/gdbus/gdbus-marshal.list b/gdbus/gdbus-marshal.list
index 02fd6ad..e42df79 100644
--- a/gdbus/gdbus-marshal.list
+++ b/gdbus/gdbus-marshal.list
@@ -1 +1 @@
-VOID:OBJECT,OBJECT,ENUM
+VOID:STRING,STRING,BOXED
diff --git a/gdbus/gdbus.symbols b/gdbus/gdbus.symbols
index 9ba1840..31bdb0e 100644
--- a/gdbus/gdbus.symbols
+++ b/gdbus/gdbus.symbols
@@ -31,6 +31,8 @@ g_dbus_connection_send_dbus_1_message_with_reply
 g_dbus_connection_send_dbus_1_message_with_reply_finish
 g_dbus_connection_send_dbus_1_message_block
 g_dbus_connection_send_dbus_1_message_cancel
+g_dbus_connection_dbus_1_signal_subscribe
+g_dbus_connection_dbus_1_signal_unsubscribe
 #endif
 #endif
 
@@ -75,6 +77,7 @@ g_bus_name_watcher_get_connection
 g_dbus_error_get_type G_GNUC_CONST
 g_bus_type_get_type G_GNUC_CONST
 g_bus_name_owner_flags_get_type G_GNUC_CONST
+g_dbus_proxy_flags_get_type G_GNUC_CONST
 #endif
 #endif
 
@@ -108,18 +111,19 @@ g_bus_unown_name
 #if IN_FILE(__G_DBUS_PROXY_C__)
 g_dbus_proxy_get_type G_GNUC_CONST
 g_dbus_proxy_new
-g_dbus_proxy_get_connection
-g_dbus_proxy_get_interface_name
+g_dbus_proxy_get_flags
 g_dbus_proxy_get_name
 g_dbus_proxy_get_object_path
+g_dbus_proxy_get_interface_name
+g_dbus_proxy_get_connection
+g_dbus_proxy_get_name_owner
 g_dbus_proxy_invoke_method
+g_dbus_proxy_invoke_method_block
+g_dbus_proxy_invoke_method_cancel
 g_dbus_proxy_invoke_method_finish
 g_dbus_proxy_invoke_method_sync
-g_dbus_proxy_block_for_properties
+g_dbus_proxy_get_properties_available
 g_dbus_proxy_get_cached_property
-g_dbus_proxy_get_properties_loaded
-g_dbus_proxy_new_finish
-g_dbus_proxy_set_cached_property
 #endif
 #endif
 
diff --git a/gdbus/gdbusconnection.c b/gdbus/gdbusconnection.c
index 4850b26..f077a91 100644
--- a/gdbus/gdbusconnection.c
+++ b/gdbus/gdbusconnection.c
@@ -72,6 +72,11 @@ struct _GDBusConnectionPrivate
   /* stack of messages to process in idle */
   GPtrArray *idle_processing_messages;
   guint idle_processing_event_source_id;
+
+  /* Maps used for signal subscription */
+  GHashTable *map_rule_to_signal_data;
+  GHashTable *map_id_to_signal_data;
+  GHashTable *map_sender_to_signal_data_array;
 };
 
 enum
@@ -94,6 +99,11 @@ enum
   PROP_DBUS_1_CONNECTION,
 };
 
+static void distribute_signals (GDBusConnection *connection,
+                                DBusMessage     *message);
+
+static void purge_all_signal_subscriptions (GDBusConnection *connection);
+
 G_LOCK_DEFINE_STATIC (connection_lock);
 static GDBusConnection *the_session_bus = NULL;
 static GDBusConnection *the_system_bus = NULL;
@@ -147,6 +157,11 @@ g_dbus_connection_finalize (GObject *object)
 
   g_hash_table_destroy (connection->priv->pending_call_id_to_simple);
 
+  purge_all_signal_subscriptions (connection);
+  g_hash_table_unref (connection->priv->map_rule_to_signal_data);
+  g_hash_table_unref (connection->priv->map_id_to_signal_data);
+  g_hash_table_unref (connection->priv->map_sender_to_signal_data_array);
+
   if (connection->priv->idle_processing_event_source_id > 0)
     g_source_remove (connection->priv->idle_processing_event_source_id);
   if (connection->priv->idle_processing_messages != NULL)
@@ -446,6 +461,16 @@ g_dbus_connection_init (GDBusConnection *connection)
 
   connection->priv->global_pending_call_id = 1;
   connection->priv->pending_call_id_to_simple = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+
+  connection->priv->map_rule_to_signal_data = g_hash_table_new (g_str_hash,
+                                                                g_str_equal);
+  connection->priv->map_id_to_signal_data = g_hash_table_new (g_direct_hash,
+                                                              g_direct_equal);
+  connection->priv->map_sender_to_signal_data_array = g_hash_table_new_full (g_str_hash,
+                                                                             g_str_equal,
+                                                                             g_free,
+                                                                             NULL);
 }
 
 /**
@@ -570,7 +595,9 @@ attempt_connect (GDBusConnection  *connection,
     }
   else
     {
-      /* for now, get a private bus even if it's shared */
+      /* For now, get a private bus even if it's shared... This can only
+       * be changed once the bug referenced in filter_function() is resolved.
+       */
       dbus_1_connection = dbus_bus_get_private (connection->priv->bus_type,
                                                 &dbus_error);
     }
@@ -679,6 +706,7 @@ process_message (GDBusConnection *connection,
   //PRINT_MESSAGE (message);
 
   dbus_error_init (&dbus_error);
+
   /* check if we are disconnected from the bus */
   if (dbus_message_is_signal (message,
                               DBUS_INTERFACE_LOCAL,
@@ -692,6 +720,9 @@ process_message (GDBusConnection *connection,
           g_dbus_connection_set_dbus_1_connection (connection, NULL);
 
           //g_object_notify (G_OBJECT (bus), "unique-name");
+
+          purge_all_signal_subscriptions (connection);
+
           g_object_notify (G_OBJECT (connection), "is-open");
           g_signal_emit (connection, signals[CLOSED_SIGNAL], 0);
 
@@ -699,6 +730,11 @@ process_message (GDBusConnection *connection,
           setup_reconnect_timer (connection);
         }
     }
+
+  /* distribute to signal subscribers */
+  distribute_signals (connection, message);
+
+
 }
 
 static gboolean
@@ -728,6 +764,15 @@ filter_function (DBusConnection *dbus_1_connection,
 {
   GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
 
+  /* TODO: yikes, this is pretty bad style.. but hard to fix.. see
+   *
+   *       http://bugzilla.gnome.org/show_bug.cgi?id=579571#c54
+   *
+   * for pointers on how to fix it. Once it is fixed we can change
+   * attempt_connect() to not get a private bus (can't use a shared
+   * bus when we always return NOT_YET_HANDLED).
+   */
+
   if (connection->priv->idle_processing_messages == NULL)
     connection->priv->idle_processing_messages = g_ptr_array_new ();
   g_ptr_array_add (connection->priv->idle_processing_messages, dbus_message_ref (message));
@@ -1349,6 +1394,469 @@ g_dbus_connection_send_dbus_1_message_with_reply_finish (GDBusConnection   *conn
   return reply;
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  gchar *rule;
+  gchar *sender;
+  gchar *interface_name;
+  gchar *member;
+  gchar *object_path;
+  gchar *arg0;
+  GArray *subscribers;
+} SignalData;
+
+typedef struct
+{
+  GDBusSignalCallback1 callback;
+  gpointer user_data;
+  guint id;
+} SignalSubscriber;
+
+static void
+signal_data_free (SignalData *data)
+{
+  g_free (data->rule);
+  g_free (data->sender);
+  g_free (data->interface_name);
+  g_free (data->member);
+  g_free (data->object_path);
+  g_free (data->arg0);
+  g_array_free (data->subscribers, TRUE);
+  g_free (data);
+}
+
+static gchar *
+args_to_rule (const gchar         *sender,
+              const gchar         *interface_name,
+              const gchar         *member,
+              const gchar         *object_path,
+              const gchar         *arg0)
+{
+  GString *rule;
+
+  rule = g_string_new ("type='signal'");
+  if (sender != NULL)
+    g_string_append_printf (rule, ",sender='%s'", sender);
+  if (interface_name != NULL)
+    g_string_append_printf (rule, ",interface='%s'", interface_name);
+  if (member != NULL)
+    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);
+
+  return g_string_free (rule, FALSE);
+}
+
+static guint _global_subscriber_id = 1;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_match_cb (DBusPendingCall *pending_call,
+              void            *user_data)
+{
+  DBusMessage *reply;
+  DBusError dbus_error;
+
+  reply = dbus_pending_call_steal_reply (pending_call);
+  g_assert (reply != NULL);
+
+  dbus_error_init (&dbus_error);
+  if (dbus_set_error_from_message (&dbus_error, reply))
+    {
+      if (g_strcmp0 (dbus_error.name, "org.freedesktop.DBus.Error.OOM") == 0)
+        {
+          g_critical ("Message bus reported OOM when trying to add match rule: %s: %s",
+                      dbus_error.name,
+                      dbus_error.message);
+          _g_dbus_oom ();
+        }
+
+      /* Don't report other errors; the bus might have gone away while sending the message
+       * so @dbus_error might be a locally generated error.
+       */
+
+      dbus_error_free (&dbus_error);
+    }
+}
+
+static void
+add_match_rule (GDBusConnection *connection,
+                const gchar     *match_rule)
+{
+  DBusMessage *message;
+  DBusPendingCall *pending_call;
+
+  if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+                                               DBUS_PATH_DBUS,
+                                               DBUS_INTERFACE_DBUS,
+                                               "AddMatch")) == NULL)
+    _g_dbus_oom ();
+  if (!dbus_message_append_args (message,
+                                 DBUS_TYPE_STRING, &match_rule,
+                                 DBUS_TYPE_INVALID))
+    _g_dbus_oom ();
+
+  /* don't use g_dbus_connection_send_dbus_1_message_with_reply() since we don't want to ref @connection */
+  if (!dbus_connection_send_with_reply (connection->priv->dbus_1_connection,
+                                        message,
+                                        &pending_call,
+                                        -1))
+    _g_dbus_oom ();
+
+  dbus_pending_call_set_notify (pending_call,
+                                add_match_cb,
+                                NULL,
+                                NULL);
+
+  dbus_message_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+remove_match_cb (DBusPendingCall *pending_call,
+                 void            *user_data)
+{
+  DBusMessage *reply;
+  DBusError dbus_error;
+
+  reply = dbus_pending_call_steal_reply (pending_call);
+  g_assert (reply != NULL);
+
+  dbus_error_init (&dbus_error);
+  if (dbus_set_error_from_message (&dbus_error, reply))
+    {
+      if (g_strcmp0 (dbus_error.name, "org.freedesktop.DBus.Error.MatchRuleNotFound") == 0)
+        {
+          g_warning ("Message bus reported error removing match rule: %s: %s\n"
+                     "This is a bug in GDBus.",
+                     dbus_error.name,
+                     dbus_error.message);
+        }
+      dbus_error_free (&dbus_error);
+    }
+}
+
+static void
+remove_match_rule (GDBusConnection *connection,
+                   const gchar     *match_rule)
+{
+  DBusMessage *message;
+  DBusPendingCall *pending_call;
+
+  if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+                                               DBUS_PATH_DBUS,
+                                               DBUS_INTERFACE_DBUS,
+                                               "RemoveMatch")) == NULL)
+    _g_dbus_oom ();
+  if (!dbus_message_append_args (message,
+                                 DBUS_TYPE_STRING, &match_rule,
+                                 DBUS_TYPE_INVALID))
+    _g_dbus_oom ();
+
+  /* don't use g_dbus_connection_send_dbus_1_message_with_reply() since we don't want to ref @connection */
+  if (!dbus_connection_send_with_reply (connection->priv->dbus_1_connection,
+                                        message,
+                                        &pending_call,
+                                        -1))
+    _g_dbus_oom ();
+
+  dbus_pending_call_set_notify (pending_call,
+                                remove_match_cb,
+                                NULL,
+                                NULL);
+
+  dbus_message_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_signal_data_for_name_lost_or_acquired (SignalData *signal_data)
+{
+  return g_strcmp0 (signal_data->sender, DBUS_SERVICE_DBUS) == 0 &&
+    g_strcmp0 (signal_data->interface_name, DBUS_INTERFACE_DBUS) == 0 &&
+    g_strcmp0 (signal_data->object_path, DBUS_PATH_DBUS) == 0 &&
+    (g_strcmp0 (signal_data->member, "NameLost") == 0 ||
+     g_strcmp0 (signal_data->member, "NameAcquired") == 0);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_dbus_1_signal_subscribe:
+ * @connection: A #GDBusConnection.
+ * @sender: Sender name to match on. Must be either <literal>org.freedesktop.DBus</literal> (for listening to signals from the message bus daemon) or a unique name.
+ * @interface_name: D-Bus interface name to match on or %NULL or to match on all interfaces.
+ * @member: D-Bus signal name to match on or %NULL to match on all signals.
+ * @object_path: Object path to match on or %NULL to match on all object paths.
+ * @arg0: Contents of first string argument to match on or %NULL to match on all kinds of arguments.
+ * @callback: Callback to invoke when there is a signal matching the requested data.
+ * @user_data: User data to pass to @callback.
+ *
+ * Subscribes to signals on @connection and invokes @callback with a #DBusMessage whenever the signal
+ * is fired. This function handles setting up match rules on the bus.
+ *
+ * It is considered a programming error to use this function if @connection is not open.
+ *
+ * Note that if @sender is not <literal>org.freedesktop.DBus</literal> (for listening to signals from the
+ * message bus daemon), then it needs to be a unique bus name - you cannot pass a name like
+ * <literal>com.example.MyApp</literal>.
+ * Use #GBusNameWatcher to find the unique name for the owner of the name you are interested in. Also note
+ * that this function does not remove a subscription if @sender vanishes from the bus. You have to manually
+ * call g_dbus_connection_dbus_1_signal_unsubscribe().
+ *
+ * Returns: A subscription identifier that can be used with g_dbus_connection_dbus_1_signal_unsubscribe().
+ **/
+guint
+g_dbus_connection_dbus_1_signal_subscribe (GDBusConnection     *connection,
+                                           const gchar         *sender,
+                                           const gchar         *interface_name,
+                                           const gchar         *member,
+                                           const gchar         *object_path,
+                                           const gchar         *arg0,
+                                           GDBusSignalCallback1 callback,
+                                           gpointer             user_data)
+{
+  gchar *rule;
+  SignalData *signal_data;
+  SignalSubscriber subscriber;
+  GPtrArray *signal_data_array;
+
+  /* Right now we abort if AddMatch() fails since it can only fail with the bus being in
+   * an OOM condition. We might want to change that but that would involve making
+   * g_dbus_connection_dbus_1_signal_subscribe() asynchronous and having the call sites
+   * handle that. And there's really no sensible way of handling this short of retrying
+   * to add the match rule... and then there's the little thing that, hey, maybe there's
+   * a reason the bus in an OOM condition.
+   *
+   * Doable, but not really sure it's worth it...
+   *
+   * TODO XXX MERGE_BLOCKER
+   */
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (g_dbus_connection_get_is_open (connection), 0);
+  g_return_val_if_fail (sender != NULL && (strcmp (sender, DBUS_SERVICE_DBUS) == 0 || sender[0] == ':'), 0);
+  g_return_val_if_fail (callback != NULL, 0);
+  /* TODO: check that passed in data is well-formed */
+
+  rule = args_to_rule (sender, interface_name, member, object_path, arg0);
+
+  subscriber.callback = callback;
+  subscriber.user_data = user_data;
+  subscriber.id = _global_subscriber_id++; /* TODO: overflow etc. */
+
+  /* see if we've already have this rule */
+  signal_data = g_hash_table_lookup (connection->priv->map_rule_to_signal_data, rule);
+  if (signal_data != NULL)
+    {
+      g_array_append_val (signal_data->subscribers, subscriber);
+      g_free (rule);
+      goto out;
+    }
+
+  signal_data = g_new0 (SignalData, 1);
+  signal_data->rule           = rule;
+  signal_data->sender         = g_strdup (sender);
+  signal_data->interface_name = g_strdup (interface_name);
+  signal_data->member         = g_strdup (member);
+  signal_data->object_path    = g_strdup (object_path);
+  signal_data->arg0           = g_strdup (arg0);
+  signal_data->subscribers    = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+  g_array_append_val (signal_data->subscribers, subscriber);
+
+  g_hash_table_insert (connection->priv->map_rule_to_signal_data,
+                       signal_data->rule,
+                       signal_data);
+
+  /* Add the match rule to the bus...
+   *
+   * Avoid adding match rules for NameLost and NameAcquired messages - the bus will
+   * always send such messages to to us.
+   */
+  if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+    add_match_rule (connection, signal_data->rule);
+
+ out:
+  g_hash_table_insert (connection->priv->map_id_to_signal_data,
+                       GUINT_TO_POINTER (subscriber.id),
+                       signal_data);
+
+  signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+                                           signal_data->sender);
+  if (signal_data_array == NULL)
+    {
+      signal_data_array = g_ptr_array_new ();
+      g_hash_table_insert (connection->priv->map_sender_to_signal_data_array,
+                           g_strdup (signal_data->sender),
+                           signal_data_array);
+    }
+  g_ptr_array_add (signal_data_array, signal_data);
+
+  return subscriber.id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_dbus_1_signal_unsubscribe:
+ * @connection: A #GDBusConnection.
+ * @subscription_id: A subscription id obtained from g_dbus_connection_dbus_1_signal_subscribe().
+ *
+ * Unsubscribes from signals.
+ **/
+void
+g_dbus_connection_dbus_1_signal_unsubscribe (GDBusConnection    *connection,
+                                             guint               subscription_id)
+{
+  SignalData *signal_data;
+  GPtrArray *signal_data_array;
+  guint n;
+
+  signal_data = g_hash_table_lookup (connection->priv->map_id_to_signal_data,
+                                     GUINT_TO_POINTER (subscription_id));
+  if (signal_data == NULL)
+    {
+      /* Don't warn here, we may have thrown all subscriptions out when the connection was closed */
+      goto out;
+    }
+
+  for (n = 0; n < signal_data->subscribers->len; n++)
+    {
+      SignalSubscriber *subscriber;
+
+      subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, n));
+      if (subscriber->id != subscription_id)
+        continue;
+
+      g_assert (g_hash_table_remove (connection->priv->map_id_to_signal_data,
+                                     GUINT_TO_POINTER (subscription_id)));
+      g_array_remove_index (signal_data->subscribers, n);
+
+      if (signal_data->subscribers->len == 0)
+        g_assert (g_hash_table_remove (connection->priv->map_rule_to_signal_data, signal_data->rule));
+
+      signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+                                               signal_data->sender);
+      g_assert (signal_data_array != NULL);
+      g_assert (g_ptr_array_remove (signal_data_array, signal_data));
+
+      if (signal_data_array->len == 0)
+        g_assert (g_hash_table_remove (connection->priv->map_sender_to_signal_data_array, signal_data->sender));
+
+      /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */
+      if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+        remove_match_rule (connection, signal_data->rule);
+
+      signal_data_free (signal_data);
+
+      goto out;
+    }
+
+  g_assert_not_reached ();
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* do not call with any locks held */
+static void
+distribute_signals (GDBusConnection *connection,
+                    DBusMessage     *message)
+{
+  const gchar *sender;
+  GPtrArray *signal_data_array;
+  guint n, m;
+
+  sender = dbus_message_get_sender (message);
+  if (sender == NULL)
+    goto out;
+
+  signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, sender);
+  if (signal_data_array == NULL)
+    goto out;
+
+  /* TODO: if this is slow, then we can change signal_data_array into
+   *       map_object_path_to_signal_data_array or something.
+   */
+  for (n = 0; n < signal_data_array->len; n++)
+    {
+      SignalData *signal_data = signal_data_array->pdata[n];
+      const gchar *arg0;
+
+      if (signal_data->interface_name != NULL &&
+          g_strcmp0 (signal_data->interface_name, dbus_message_get_interface (message)) != 0)
+        continue;
+
+      if (signal_data->member != NULL &&
+          g_strcmp0 (signal_data->member, dbus_message_get_member (message)) != 0)
+        continue;
+
+      if (signal_data->object_path != NULL &&
+          g_strcmp0 (signal_data->object_path, dbus_message_get_path (message)) != 0)
+        continue;
+
+      if (signal_data->arg0 != NULL)
+        {
+          if (!dbus_message_get_args (message,
+                                      NULL,
+                                      DBUS_TYPE_STRING, &arg0,
+                                      DBUS_TYPE_INVALID))
+            continue;
+
+          if (g_strcmp0 (signal_data->arg0, arg0) != 0)
+            continue;
+        }
+
+      for (m = 0; m < signal_data->subscribers->len; m++)
+        {
+          SignalSubscriber *subscriber;
+
+          subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, m));
+
+          subscriber->callback (connection,
+                                message,
+                                subscriber->user_data);
+        }
+    }
+
+out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called when disconnected from the bus and in finalize() */
+static void
+purge_all_signal_subscriptions (GDBusConnection *connection)
+{
+  GHashTableIter iter;
+  gpointer key;
+  gpointer value;
+
+  g_hash_table_iter_init (&iter, connection->priv->map_rule_to_signal_data);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+      signal_data_free (value);
+  g_hash_table_remove_all (connection->priv->map_rule_to_signal_data);
+
+  g_hash_table_remove_all (connection->priv->map_id_to_signal_data);
+
+  g_hash_table_iter_init (&iter, connection->priv->map_sender_to_signal_data_array);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      g_ptr_array_free (value, TRUE);
+    }
+  g_hash_table_remove_all (connection->priv->map_sender_to_signal_data_array);
+}
+
 
 #define __G_DBUS_CONNECTION_C__
 #include "gdbusaliasdef.c"
diff --git a/gdbus/gdbusconnection.h b/gdbus/gdbusconnection.h
index 33bbc77..26e2ca1 100644
--- a/gdbus/gdbusconnection.h
+++ b/gdbus/gdbusconnection.h
@@ -101,6 +101,18 @@ void             g_dbus_connection_set_exit_on_close          (GDBusConnection
 
 /* the following functions are intended only for language bindings and object mappings */
 
+/**
+ * GDBusSignalCallback1:
+ * @connection: A #GDBusConnection.
+ * @message: A #DBusMessage.
+ * @user_data: User data passed when subscribing to the signal.
+ *
+ * Signature for callback function used in g_dbus_connection_dbus_1_signal_subscribe().
+ */
+typedef void (*GDBusSignalCallback1) (GDBusConnection  *connection,
+                                      DBusMessage      *message,
+                                      gpointer          user_data);
+
 DBusConnection  *g_dbus_connection_get_dbus_1_connection                 (GDBusConnection    *connection);
 void             g_dbus_connection_send_dbus_1_message                   (GDBusConnection    *connection,
                                                                           DBusMessage        *message);
@@ -117,6 +129,16 @@ void             g_dbus_connection_send_dbus_1_message_cancel            (GDBusC
 DBusMessage     *g_dbus_connection_send_dbus_1_message_with_reply_finish (GDBusConnection    *connection,
                                                                           GAsyncResult       *res,
                                                                           GError            **error);
+guint            g_dbus_connection_dbus_1_signal_subscribe               (GDBusConnection    *connection,
+                                                                          const gchar        *sender,
+                                                                          const gchar        *interface_name,
+                                                                          const gchar        *member,
+                                                                          const gchar        *object_path,
+                                                                          const gchar        *arg0,
+                                                                          GDBusSignalCallback1 callback,
+                                                                          gpointer            user_data);
+void             g_dbus_connection_dbus_1_signal_unsubscribe             (GDBusConnection    *connection,
+                                                                          guint               subscription_id);
 
 G_END_DECLS
 
diff --git a/gdbus/gdbusenums.h b/gdbus/gdbusenums.h
index 38a1b4c..8417de8 100644
--- a/gdbus/gdbusenums.h
+++ b/gdbus/gdbusenums.h
@@ -68,6 +68,21 @@ typedef enum
 } GBusNameOwnerFlags;
 
 /**
+ * GDBusProxyFlags:
+ * @G_DBUS_PROXY_FLAGS_NONE: No flags set.
+ * @G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES: Don't load properties.
+ * @G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS: Don't connect to signals on the remote object.
+ *
+ * Flags used when constructing an instance of a #GDBusProxy derived class.
+ */
+typedef enum
+{
+  G_DBUS_PROXY_FLAGS_NONE = 0,                        /*< nick=none >*/
+  G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = (1<<0), /*< nick=do-not-load-properties >*/
+  G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1), /*< nick=do-not-connect-signals >*/
+} GDBusProxyFlags;
+
+/**
  * GDBusError:
  * @G_DBUS_ERROR_FAILED: The operation failed.
  * @G_DBUS_ERROR_CANCELLED: The operation was cancelled.
diff --git a/gdbus/gdbusproxy.c b/gdbus/gdbusproxy.c
index 4f043c9..7e80885 100644
--- a/gdbus/gdbusproxy.c
+++ b/gdbus/gdbusproxy.c
@@ -32,6 +32,8 @@
 #include "gdbuserror.h"
 #include "gdbusstructure.h"
 #include "gdbusvariant.h"
+#include "gbusnamewatcher.h"
+#include "gdbus-marshal.h"
 #include "gdbusprivate.h"
 
 #include "gdbusalias.h"
@@ -48,12 +50,22 @@
 struct _GDBusProxyPrivate
 {
   GDBusConnection *connection;
+  GDBusProxyFlags flags;
   gchar *name;
   gchar *object_path;
   gchar *interface_name;
-  gboolean properties_loaded;
+  gboolean properties_available;
   guint property_loading_pending_call_id;
   GHashTable *properties;
+
+  GBusNameWatcher *watcher;
+
+  gulong watcher_initialized_signal_handler_id;
+  gulong watcher_name_appeared_signal_handler_id;
+  gulong watcher_name_vanished_signal_handler_id;
+
+  guint properties_changed_subscriber_id;
+  guint signals_subscriber_id;
 };
 
 enum
@@ -61,11 +73,28 @@ enum
   PROP_0,
   PROP_G_DBUS_PROXY_CONNECTION,
   PROP_G_DBUS_PROXY_NAME,
+  PROP_G_DBUS_PROXY_NAME_OWNER,
+  PROP_G_DBUS_PROXY_FLAGS,
   PROP_G_DBUS_PROXY_OBJECT_PATH,
   PROP_G_DBUS_PROXY_INTERFACE_NAME,
-  PROP_G_DBUS_PROXY_PROPERTIES_LOADED,
+  PROP_G_DBUS_PROXY_PROPERTIES_AVAILABLE,
 };
 
+enum
+{
+  PROPERTIES_AVAILABLE_CHANGED_SIGNAL,
+  PROPERTIES_CHANGED_SIGNAL,
+  SIGNAL_SIGNAL, /* (!) */
+  LAST_SIGNAL,
+};
+
+static void g_dbus_proxy_constructed (GObject *object);
+static gboolean get_value_from_iter (DBusMessageIter *iter,
+                                     GValue          *out_value,
+                                     GError         **error);
+
+guint signals[LAST_SIGNAL] = {0};
+
 G_DEFINE_TYPE (GDBusProxy, g_dbus_proxy, G_TYPE_OBJECT);
 
 static void
@@ -73,6 +102,26 @@ g_dbus_proxy_finalize (GObject *object)
 {
   GDBusProxy *proxy = G_DBUS_PROXY (object);
 
+  if (proxy->priv->watcher_initialized_signal_handler_id > 0)
+    g_signal_handler_disconnect (proxy->priv->watcher, proxy->priv->watcher_initialized_signal_handler_id);
+  if (proxy->priv->watcher_name_appeared_signal_handler_id > 0)
+    g_signal_handler_disconnect (proxy->priv->watcher, proxy->priv->watcher_name_appeared_signal_handler_id);
+  if (proxy->priv->watcher_name_vanished_signal_handler_id > 0)
+    g_signal_handler_disconnect (proxy->priv->watcher, proxy->priv->watcher_name_vanished_signal_handler_id);
+  g_object_unref (proxy->priv->watcher);
+
+  if (proxy->priv->properties_changed_subscriber_id > 0)
+    {
+      g_dbus_connection_dbus_1_signal_unsubscribe (proxy->priv->connection,
+                                                   proxy->priv->properties_changed_subscriber_id);
+    }
+
+  if (proxy->priv->signals_subscriber_id > 0)
+    {
+      g_dbus_connection_dbus_1_signal_unsubscribe (proxy->priv->connection,
+                                                   proxy->priv->signals_subscriber_id);
+    }
+
   g_object_unref (proxy->priv->connection);
   g_free (proxy->priv->name);
   g_free (proxy->priv->object_path);
@@ -98,10 +147,18 @@ g_dbus_proxy_get_property (GObject    *object,
       g_value_set_object (value, proxy->priv->connection);
       break;
 
+    case PROP_G_DBUS_PROXY_FLAGS:
+      g_value_set_flags (value, proxy->priv->flags);
+      break;
+
     case PROP_G_DBUS_PROXY_NAME:
       g_value_set_string (value, proxy->priv->name);
       break;
 
+    case PROP_G_DBUS_PROXY_NAME_OWNER:
+      g_value_set_string (value, g_dbus_proxy_get_name_owner (proxy));
+      break;
+
     case PROP_G_DBUS_PROXY_OBJECT_PATH:
       g_value_set_string (value, proxy->priv->object_path);
       break;
@@ -110,8 +167,8 @@ g_dbus_proxy_get_property (GObject    *object,
       g_value_set_string (value, proxy->priv->interface_name);
       break;
 
-    case PROP_G_DBUS_PROXY_PROPERTIES_LOADED:
-      g_value_set_boolean (value, proxy->priv->properties_loaded);
+    case PROP_G_DBUS_PROXY_PROPERTIES_AVAILABLE:
+      g_value_set_boolean (value, proxy->priv->properties_available);
       break;
 
     default:
@@ -134,6 +191,10 @@ g_dbus_proxy_set_property (GObject      *object,
       proxy->priv->connection = g_value_dup_object (value);
       break;
 
+    case PROP_G_DBUS_PROXY_FLAGS:
+      proxy->priv->flags = g_value_get_flags (value);
+      break;
+
     case PROP_G_DBUS_PROXY_NAME:
       proxy->priv->name = g_value_dup_string (value);
       break;
@@ -153,17 +214,6 @@ g_dbus_proxy_set_property (GObject      *object,
 }
 
 static void
-g_dbus_proxy_constructed (GObject *object)
-{
-  //GDBusProxy *proxy = G_DBUS_PROXY (object);
-
-  /* TODO: Start loading all properties */
-
-  if (G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed != NULL)
-    G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed (object);
-}
-
-static void
 g_dbus_proxy_class_init (GDBusProxyClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
@@ -194,15 +244,34 @@ g_dbus_proxy_class_init (GDBusProxyClass *klass)
                                                         G_PARAM_STATIC_NICK));
 
   /**
+   * GDBusProxy:g-dbus-proxy-flags:
+   *
+   * Flags from the #GDBusProxyFlags enumeration.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_DBUS_PROXY_FLAGS,
+                                   g_param_spec_flags ("g-dbus-proxy-flags",
+                                                       _("g-dbus-proxy-flags"),
+                                                       _("Flags for the proxy"),
+                                                       G_TYPE_DBUS_PROXY_FLAGS,
+                                                       G_DBUS_PROXY_FLAGS_NONE,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
    * GDBusProxy:g-dbus-proxy-name:
    *
-   * The bus name the proxy is for.
+   * The name the proxy is for.
    */
   g_object_class_install_property (gobject_class,
                                    PROP_G_DBUS_PROXY_NAME,
                                    g_param_spec_string ("g-dbus-proxy-name",
                                                         _("g-dbus-proxy-name"),
-                                                        _("The bus name the proxy is for"),
+                                                        _("The name the proxy is for"),
                                                         NULL,
                                                         G_PARAM_READABLE |
                                                         G_PARAM_WRITABLE |
@@ -212,6 +281,23 @@ g_dbus_proxy_class_init (GDBusProxyClass *klass)
                                                         G_PARAM_STATIC_NICK));
 
   /**
+   * GDBusProxy:g-dbus-proxy-name-owner:
+   *
+   * The unique name of the owner owning the name the proxy is for or
+   * %NULL if there is no such owner.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_DBUS_PROXY_NAME_OWNER,
+                                   g_param_spec_string ("g-dbus-proxy-name-owner",
+                                                        _("g-dbus-proxy-name-owner"),
+                                                        _("The owner of the name the proxy is for"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
    * GDBusProxy:g-dbus-proxy-object-path:
    *
    * The object path the proxy is for.
@@ -248,14 +334,14 @@ g_dbus_proxy_class_init (GDBusProxyClass *klass)
                                                         G_PARAM_STATIC_NICK));
 
   /**
-   * GDBusProxy:g-dbus-proxy-properties-loaded:
+   * GDBusProxy:g-dbus-proxy-properties-available:
    *
    * Whether D-Bus properties has been acquired.
    */
   g_object_class_install_property (gobject_class,
-                                   PROP_G_DBUS_PROXY_PROPERTIES_LOADED,
-                                   g_param_spec_boolean ("g-dbus-proxy-properties-loaded",
-                                                         _("g-dbus-proxy-properties-loaded"),
+                                   PROP_G_DBUS_PROXY_PROPERTIES_AVAILABLE,
+                                   g_param_spec_boolean ("g-dbus-proxy-properties-available",
+                                                         _("g-dbus-proxy-properties-available"),
                                                          _("Whether D-Bus properties has been acquired"),
                                                          FALSE,
                                                          G_PARAM_READABLE |
@@ -263,6 +349,66 @@ g_dbus_proxy_class_init (GDBusProxyClass *klass)
                                                          G_PARAM_STATIC_BLURB |
                                                          G_PARAM_STATIC_NICK));
 
+  /**
+   * GDBusProxy::g-dbus-proxy-properties-available-changed:
+   * @proxy: The #GDBusProxy emitting the signal.
+   * @properties_available: Whether properties are available.
+   *
+   * Emitted when properties are either available or unavailable on @proxy.
+   **/
+  signals[PROPERTIES_AVAILABLE_CHANGED_SIGNAL] = g_signal_new ("g-dbus-proxy-properties-available-changed",
+                                                               G_TYPE_DBUS_PROXY,
+                                                               G_SIGNAL_RUN_LAST,
+                                                               G_STRUCT_OFFSET (GDBusProxyClass, properties_available_changed),
+                                                               NULL,
+                                                               NULL,
+                                                               g_cclosure_marshal_VOID__BOOLEAN,
+                                                               G_TYPE_NONE,
+                                                               1,
+                                                               G_TYPE_BOOLEAN);
+
+  /**
+   * GDBusProxy::g-dbus-proxy-properties-changed:
+   * @proxy: The #GDBusProxy emitting the signal.
+   * @changed_properties: A #GHashTable containing the properties that changed.
+   *
+   * Emitted when one or more D-Bus properties on @proxy changes. The cached properties
+   * are already replaced when this signal fires.
+   **/
+  signals[PROPERTIES_CHANGED_SIGNAL] = g_signal_new ("g-dbus-proxy-properties-changed",
+                                                     G_TYPE_DBUS_PROXY,
+                                                     G_SIGNAL_RUN_LAST,
+                                                     G_STRUCT_OFFSET (GDBusProxyClass, properties_changed),
+                                                     NULL,
+                                                     NULL,
+                                                     g_cclosure_marshal_VOID__BOXED,
+                                                     G_TYPE_NONE,
+                                                     1,
+                                                     G_TYPE_HASH_TABLE);
+
+  /**
+   * GDBusProxy::g-dbus-proxy-signal:
+   * @proxy: The #GDBusProxy emitting the signal.
+   * @name: The name of the signal.
+   * @signature: The signature of the signal.
+   * @args: A #GValueArray containing the arguments for the signal.
+   *
+   * Emitted when a signal from the remote object and interface that @proxy is for, has been received.
+   **/
+  signals[SIGNAL_SIGNAL] = g_signal_new ("g-dbus-proxy-signal",
+                                         G_TYPE_DBUS_PROXY,
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (GDBusProxyClass, signal),
+                                         NULL,
+                                         NULL,
+                                         _gdbus_marshal_VOID__STRING_STRING_BOXED,
+                                         G_TYPE_NONE,
+                                         3,
+                                         G_TYPE_STRING,
+                                         G_TYPE_STRING,
+                                         G_TYPE_VALUE_ARRAY);
+
+
   g_type_class_add_private (klass, sizeof (GDBusProxyPrivate));
 }
 
@@ -280,11 +426,19 @@ g_dbus_proxy_init (GDBusProxy *proxy)
  * @property_name: Property name.
  * @error: Return location for error.
  *
- * Looks up the value for a property from the cache. This call does no
- * blocking IO.
+ * Looks up the value for a property from the cache. This call does no blocking IO.
  *
- * Returns: A #GDBusVariant that holds the value for @property_name or
- * %NULL if @error is set. Free with g_object_unref().
+ * Normally you will not need to modify the returned variant since it is updated automatically
+ * in response to <literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
+ * D-Bus signals (which also causes #GDBusProxy::g-dbus-proxy-properties-changed to be emitted).
+ * However, for properties for which said D-Bus signal is not emitted, you
+ * can catch other signals and modify the returned variant accordingly (remember to emit
+ * #GDBusProxy::g-dbus-proxy-properties-changed yourself). This should be done in a subclass
+ * of #GDBusProxy since multiple different call sites may share the same underlying
+ * #GDBusProxy object.
+ *
+ * Returns: A reference to the #GDBusVariant object that holds the value for @property_name or
+ * %NULL if @error is set. Free the reference with g_object_unref().
  **/
 GDBusVariant *
 g_dbus_proxy_get_cached_property (GDBusProxy          *proxy,
@@ -298,21 +452,21 @@ g_dbus_proxy_get_cached_property (GDBusProxy          *proxy,
 
   variant = NULL;
 
-  if (!proxy->priv->properties_loaded)
+  if (proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)
     {
       g_set_error (error,
                    G_DBUS_ERROR,
                    G_DBUS_ERROR_FAILED,
-                   _("Properties are not loaded"));
+                   _("Properties are not available (proxy created with G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)"));
       goto out;
     }
 
-  if (proxy->priv->properties == NULL)
+  if (!proxy->priv->properties_available)
     {
       g_set_error (error,
                    G_DBUS_ERROR,
                    G_DBUS_ERROR_FAILED,
-                   _("Failed loading properties"));
+                   _("Properties are not available"));
       goto out;
     }
 
@@ -334,148 +488,400 @@ g_dbus_proxy_get_cached_property (GDBusProxy          *proxy,
   return variant;
 }
 
-/**
- * g_dbus_proxy_set_cached_property:
- * @proxy: A #GDBusProxy.
- * @property_name: Property name.
- * @variant: The value to set.
- * @error: Return location for error.
- *
- * Updates the local cache with the value for @property_name. This
- * call does no blocking IO.
- *
- * Returns: %TRUE if the property was updated, %FALSE if @error is set.
- **/
-gboolean
-g_dbus_proxy_set_cached_property (GDBusProxy          *proxy,
-                                  const gchar         *property_name,
-                                  GDBusVariant        *variant,
-                                  GError             **error)
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_all_cb (GDBusProxy   *proxy,
+            GAsyncResult *res,
+            gpointer      user_data)
 {
-  GDBusVariant *existing_variant;
-  gboolean ret;
+  GError *error;
 
-  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), FALSE);
-  g_return_val_if_fail (property_name != NULL, FALSE);
-  g_return_val_if_fail (variant != NULL, FALSE);
+  proxy->priv->property_loading_pending_call_id = 0;
 
-  ret = FALSE;
+  /* check for invariants */
+  g_warn_if_fail (proxy->priv->properties == NULL);
+  g_warn_if_fail (!proxy->priv->properties_available);
+
+  error = NULL;
+  if (!g_dbus_proxy_invoke_method_finish (proxy,
+                                          "a{sv}",
+                                          res,
+                                          &error,
+                                          G_TYPE_HASH_TABLE, &proxy->priv->properties,
+                                          G_TYPE_INVALID))
+    {
+      /* this can happen if the remote object does not have any properties */
+      g_error_free (error);
+      proxy->priv->properties = NULL;
+    }
+  else
+    {
+      proxy->priv->properties_available = TRUE;
+      g_object_notify (G_OBJECT (proxy), "g-dbus-proxy-properties-available");
+      g_signal_emit (proxy, signals[PROPERTIES_AVAILABLE_CHANGED_SIGNAL], 0, proxy->priv->properties_available);
+    }
+}
+
+static void
+on_signal_received (GDBusConnection  *connection,
+                    DBusMessage      *message,
+                    gpointer          user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+  GValueArray *value_array;
+  DBusMessageIter iter;
+  guint n;
+
+  value_array = g_value_array_new (0);
+
+  dbus_message_iter_init (message, &iter);
 
-  if (!proxy->priv->properties_loaded)
+  n = 0;
+  while (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID)
     {
-      g_set_error (error,
-                   G_DBUS_ERROR,
-                   G_DBUS_ERROR_FAILED,
-                   _("Cannot set property %s (properties are not loaded)"),
-                   property_name);
-      goto out;
+      GValue value = {0};
+      GError *error;
+
+      error = NULL;
+      if (!get_value_from_iter (&iter,
+                                &value,
+                                &error))
+        {
+          g_warning ("Error getting argument %d from %s signal: %s",
+                     n,
+                     dbus_message_get_member (message),
+                     error->message);
+          g_error_free (error);
+          goto out;
+        }
+
+      g_value_array_append (value_array, &value);
+      g_value_unset (&value);
+
+      dbus_message_iter_next (&iter);
+      n++;
     }
 
-  if (proxy->priv->properties == NULL)
+  g_signal_emit (proxy,
+                 signals[SIGNAL_SIGNAL],
+                 0,
+                 dbus_message_get_member (message),
+                 dbus_message_get_signature (message),
+                 value_array);
+
+ out:
+  g_value_array_free (value_array);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_properties_changed (GDBusConnection  *connection,
+                       DBusMessage      *message,
+                       gpointer          user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+  DBusMessageIter iter;
+  GError *error;
+  GValue value;
+  gchar *interface_name;
+  GHashTable *changed_properties;
+  GHashTableIter hash_iter;
+  gchar *property_name;
+  GDBusVariant *property_value;
+
+  error = NULL;
+  interface_name = NULL;
+  changed_properties = NULL;
+
+  /* Ignore this signal if properties are not yet available
+   *
+   * (can happen in the window between subscribing to PropertiesChanged() and until
+   *  org.freedesktop.DBus.Properties.GetAll() returns)
+   */
+  if (!proxy->priv->properties_available)
+    goto out;
+
+  dbus_message_iter_init (message, &iter);
+  memset (&value, '\0', sizeof (GValue));
+  if (!get_value_from_iter (&iter,
+                            &value,
+                            &error))
     {
-      g_set_error (error,
-                   G_DBUS_ERROR,
-                   G_DBUS_ERROR_FAILED,
-                   _("Cannot set property %s (properties are not loaded and the attempt to load them failed)"),
-                   property_name);
+      g_warning ("Error getting argument 1 of PropertiesChanged() signal: %s",
+                 error->message);
+      g_error_free (error);
       goto out;
     }
-
-  existing_variant = g_hash_table_lookup (proxy->priv->properties, property_name);
-  if (existing_variant == NULL)
+  if (G_VALUE_TYPE (&value) != G_TYPE_STRING)
     {
-      g_set_error (error,
-                   G_DBUS_ERROR,
-                   G_DBUS_ERROR_FAILED,
-                   _("There is no existing property with name %s"),
-                   property_name);
+      g_warning ("Expected a string for argument 1 of PropertiesChanged() but got %s",
+                 g_type_name (G_VALUE_TYPE (&value)));
+      g_value_unset (&value);
       goto out;
     }
+  interface_name = g_value_dup_string (&value);
+  g_value_unset (&value);
 
-  if (g_strcmp0 (g_dbus_variant_get_variant_signature (existing_variant),
-                 g_dbus_variant_get_variant_signature (variant)) != 0)
+  if (g_strcmp0 (interface_name, proxy->priv->interface_name) != 0)
     {
-      g_set_error (error,
-                   G_DBUS_ERROR,
-                   G_DBUS_ERROR_FAILED,
-                   _("Existing value for property %s has signature %s and value to set has signature %s"),
-                   property_name,
-                   g_dbus_variant_get_variant_signature (existing_variant),
-                   g_dbus_variant_get_variant_signature (variant));
+      g_warning ("Expected PropertiesChanged() for interface %s but got the signal for interface %s",
+                 proxy->priv->interface_name,
+                 interface_name);
       goto out;
     }
 
-  g_hash_table_insert (proxy->priv->properties,
-                       g_strdup (property_name),
-                       g_object_ref (variant));
+  dbus_message_iter_next (&iter);
 
-  ret = TRUE;
+  memset (&value, '\0', sizeof (GValue));
+  if (!get_value_from_iter (&iter,
+                            &value,
+                            &error))
+    {
+      g_warning ("Error getting argument 2 of PropertiesChanged() signal: %s",
+                 error->message);
+      g_error_free (error);
+      goto out;
+    }
+  if (G_VALUE_TYPE (&value) != G_TYPE_HASH_TABLE)
+    {
+      g_warning ("Expected a hash table for argument 2 of PropertiesChanged() but got %s",
+                 g_type_name (G_VALUE_TYPE (&value)));
+      g_value_unset (&value);
+      goto out;
+    }
+  changed_properties = g_value_dup_boxed (&value);
+  g_value_unset (&value);
+
+  /* update local cache */
+  g_hash_table_iter_init (&hash_iter, changed_properties);
+  while (g_hash_table_iter_next (&hash_iter,
+                                 (gpointer) &property_name,
+                                 (gpointer) &property_value))
+    {
+      g_hash_table_insert (proxy->priv->properties,
+                           g_strdup (property_name),
+                           g_object_ref (property_value));
+    }
+
+  /* emit signal */
+  g_signal_emit (proxy, signals[PROPERTIES_CHANGED_SIGNAL], 0, changed_properties);
 
  out:
+  g_free (interface_name);
+  if (changed_properties != NULL)
+    g_hash_table_unref (changed_properties);
+}
 
-  return ret;
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_watcher_name_appeared (GBusNameWatcher *watcher,
+                          gpointer         user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+    {
+      /* check for invariants */
+      g_warn_if_fail (proxy->priv->properties == NULL);
+      g_warn_if_fail (!proxy->priv->properties_available);
+      g_warn_if_fail (proxy->priv->properties_changed_subscriber_id == 0);
+
+      /* subscribe to PropertiesChanged() */
+      proxy->priv->properties_changed_subscriber_id =
+        g_dbus_connection_dbus_1_signal_subscribe (proxy->priv->connection,
+                                                   g_bus_name_watcher_get_name_owner (proxy->priv->watcher),
+                                                   "org.freedesktop.DBus.Properties",
+                                                   "PropertiesChanged",
+                                                   proxy->priv->object_path,
+                                                   proxy->priv->interface_name,
+                                                   on_properties_changed,
+                                                   proxy);
+
+      /* start loading properties */
+      proxy->priv->property_loading_pending_call_id =
+        g_dbus_proxy_invoke_method (proxy,
+                                    "org.freedesktop.DBus.Properties.GetAll",
+                                    "s",
+                                    -1,
+                                    NULL,
+                                    (GAsyncReadyCallback) get_all_cb,
+                                    NULL,
+                                    G_TYPE_STRING,
+                                    proxy->priv->interface_name,
+                                    G_TYPE_INVALID);
+    }
+
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS))
+    {
+      /* check for invariants */
+      g_warn_if_fail (proxy->priv->signals_subscriber_id == 0);
+
+      /* subscribe to all signals for the object */
+      proxy->priv->signals_subscriber_id =
+        g_dbus_connection_dbus_1_signal_subscribe (proxy->priv->connection,
+                                                   g_bus_name_watcher_get_name_owner (proxy->priv->watcher),
+                                                   proxy->priv->interface_name,
+                                                   NULL,                        /* member */
+                                                   proxy->priv->object_path,
+                                                   NULL,                        /* arg0 */
+                                                   on_signal_received,
+                                                   proxy);
+    }
+
+  g_object_notify (G_OBJECT (proxy), "g-dbus-proxy-name-owner");
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-get_all_cb (GDBusProxy   *proxy,
-            GAsyncResult *res,
-            gpointer      user_data)
+on_watcher_name_vanished (GBusNameWatcher *watcher,
+                          gpointer         user_data)
 {
-  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
-  GError *error;
+  GDBusProxy *proxy = G_DBUS_PROXY (user_data);
 
-  error = NULL;
-  if (!g_dbus_proxy_invoke_method_finish (proxy,
-                                          "a{sv}",
-                                          res,
-                                          &error,
-                                          G_TYPE_HASH_TABLE, &proxy->priv->properties,
-                                          G_TYPE_INVALID))
+  g_object_notify (G_OBJECT (proxy), "g-dbus-proxy-name-owner");
+
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
     {
-      g_simple_async_result_set_from_error (simple, error);
-      g_error_free (error);
-      proxy->priv->properties = NULL;
+      gboolean old_properties_available;
+
+      if (proxy->priv->properties_changed_subscriber_id > 0)
+        {
+          g_dbus_connection_dbus_1_signal_unsubscribe (proxy->priv->connection,
+                                                       proxy->priv->properties_changed_subscriber_id);
+          proxy->priv->properties_changed_subscriber_id = 0;
+        }
+
+      old_properties_available = proxy->priv->properties_available;
+      proxy->priv->properties_available = FALSE;
+      if (proxy->priv->properties != NULL)
+        {
+          g_hash_table_unref (proxy->priv->properties);
+          proxy->priv->properties = NULL;
+        }
+      if (old_properties_available != proxy->priv->properties_available)
+        {
+          g_object_notify (G_OBJECT (proxy), "g-dbus-proxy-properties-available");
+          g_signal_emit (proxy, signals[PROPERTIES_AVAILABLE_CHANGED_SIGNAL], 0, proxy->priv->properties_available);
+
+          /* cancel loading properties if applicable */
+          if (proxy->priv->property_loading_pending_call_id > 0)
+            {
+              g_dbus_connection_send_dbus_1_message_cancel (proxy->priv->connection,
+                                                            proxy->priv->property_loading_pending_call_id);
+              proxy->priv->property_loading_pending_call_id = 0;
+            }
+        }
+
     }
 
-  proxy->priv->properties_loaded = TRUE;
-  proxy->priv->property_loading_pending_call_id = 0;
-  g_object_notify (G_OBJECT (proxy), "g-dbus-proxy-properties-loaded");
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS))
+    {
+      if (proxy->priv->signals_subscriber_id > 0)
+        {
+          g_dbus_connection_dbus_1_signal_unsubscribe (proxy->priv->connection,
+                                                       proxy->priv->signals_subscriber_id);
+          proxy->priv->signals_subscriber_id = 0;
+        }
+    }
+}
 
-  g_simple_async_result_complete (simple);
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_watcher_initialized (GBusNameWatcher *watcher,
+                        gpointer         user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+  const gchar *name_owner;
+
+  proxy->priv->watcher_name_appeared_signal_handler_id = g_signal_connect (proxy->priv->watcher,
+                                                                           "name-appeared",
+                                                                           G_CALLBACK (on_watcher_name_appeared),
+                                                                           proxy);
+
+  proxy->priv->watcher_name_vanished_signal_handler_id = g_signal_connect (proxy->priv->watcher,
+                                                                           "name-vanished",
+                                                                           G_CALLBACK (on_watcher_name_vanished),
+                                                                           proxy);
+
+  name_owner = g_bus_name_watcher_get_name_owner (proxy->priv->watcher);
+  if (name_owner != NULL)
+    {
+      on_watcher_name_appeared (proxy->priv->watcher, proxy);
+    }
+  else
+    {
+      /* No name owner.. just tool around until "name-appeared" fires */
+    }
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_proxy_constructed (GObject *object)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (object);
+
+  proxy->priv->watcher = g_bus_name_watcher_new_for_connection (proxy->priv->connection, proxy->priv->name);
+  if (g_bus_name_watcher_get_is_initialized (proxy->priv->watcher))
+    on_watcher_initialized (proxy->priv->watcher, proxy);
+  else
+    proxy->priv->watcher_initialized_signal_handler_id = g_signal_connect (proxy->priv->watcher,
+                                                                           "initialized",
+                                                                           G_CALLBACK (on_watcher_initialized),
+                                                                           proxy);
+
+  if (G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed (object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 /**
  * g_dbus_proxy_new:
  * @connection: A #GDBusConnection.
+ * @flags: Flags used when constructing the proxy.
  * @name: A bus name.
  * @object_path: An object path.
  * @interface_name: A D-Bus interface name.
- * @cancellable: A #GCancellable or %NULL.
- * @callback: The callback function to invoke when finished attempting to load properties or %NULL.
- * @user_data: User data to pass to @callback.
  *
- * Creates a proxy for accessing @interface_name on the remote at
- * @object_path owned by @name at @connection and starts loading
- * the D-Bus properties for the remote object.
+ * Creates a proxy for accessing @interface_name on the remote object at @object_path
+ * owned by @name at @connection and starts loading D-Bus properties unless the
+ * #G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used.
+ *
+ * Use g_dbus_proxy_get_properties_available() to check whether properties
+ * are available and connect to the #GDBusProxy::g-dbus-proxy-properties-available-changed
+ * signal or listen for notifications on the #GDBusProxy:g-dbus-proxy-properties-available
+ * property to get informed when properties are available. Properties are reloaded whenever
+ * a new owner acquires @name.
  *
- * Once the D-Bus properties are loaded, @callback will be invoked
- * and you can call g_dbus_proxy_new_finish() to check the result.
+ * If the #G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up
+ * match rules for signals. Connect to the #GDBusProxy::g-dbus-proxy-signal signal
+ * to handle signals from the remote object.
+ *
+ * Use g_dbus_proxy_get_name_owner() to check the current owner of the proxy and
+ * watch for notifications on #GDBusProxy:g-dbus-proxy-name-owner for changes. If
+ * there is no owner, method calls may activate the name depending on whether
+ * the name supports activation.
  *
  * Returns: A #GDBusProxy. Free with g_object_unref().
  **/
 GDBusProxy *
 g_dbus_proxy_new (GDBusConnection     *connection,
+                  GDBusProxyFlags      flags,
                   const gchar         *name,
                   const gchar         *object_path,
-                  const gchar         *interface_name,
-                  GCancellable        *cancellable,
-                  GAsyncReadyCallback  callback,
-                  gpointer             user_data)
+                  const gchar         *interface_name)
 {
   GDBusProxy *proxy;
-  GSimpleAsyncResult *simple;
+
+  /* TODO: maybe do singleton stuff like we do for watchers and owners. Tricky though,
+   *       since the normal use case is to instantiate a subclass.
+   */
 
   g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
   g_return_val_if_fail (name != NULL, NULL);
@@ -483,62 +889,22 @@ g_dbus_proxy_new (GDBusConnection     *connection,
   g_return_val_if_fail (interface_name, NULL);
 
   proxy = G_DBUS_PROXY (g_object_new (G_TYPE_DBUS_PROXY,
+                                      "g-dbus-proxy-flags", flags,
                                       "g-dbus-proxy-name", name,
                                       "g-dbus-proxy-connection", connection,
                                       "g-dbus-proxy-object-path", object_path,
                                       "g-dbus-proxy-interface-name", interface_name,
                                       NULL));
-
-  simple = g_simple_async_result_new (G_OBJECT (connection),
-                                      callback,
-                                      user_data,
-                                      g_dbus_proxy_new_finish);
-
-  proxy->priv->property_loading_pending_call_id = g_dbus_proxy_invoke_method (proxy,
-                                                                              "org.freedesktop.DBus.Properties.GetAll",
-                                                                              "s",
-                                                                              -1,
-                                                                              cancellable,
-                                                                              (GAsyncReadyCallback) get_all_cb,
-                                                                              simple,
-                                                                              G_TYPE_STRING,
-                                                                              proxy->priv->interface_name,
-                                                                              G_TYPE_INVALID);
-
   return proxy;
 }
 
-/**
- * g_dbus_proxy_new_finish:
- * @proxy: A #GDBusProxy.
- * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback function passed to g_dbus_proxy_new().
- * @error: Return location for error or %NULL.
- *
- * Finishes loading properties for @proxy.
- *
- * Returns: %TRUE if properties was successfully loaded, otherwise %FALSE with @error set.
- **/
-gboolean
-g_dbus_proxy_new_finish (GDBusProxy         *proxy,
-                         GAsyncResult       *res,
-                         GError            **error)
-{
-  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
-  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), FALSE);
-  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_proxy_new_finish);
-  if (g_simple_async_result_propagate_error (simple, error))
-    return FALSE;
-  else
-    return proxy->priv->properties_loaded;
-}
-
 /* ---------------------------------------------------------------------------------------------------- */
 
 /**
  * g_dbus_proxy_get_connection:
  * @proxy: A #GDBusProxy.
  *
- * Gets the connection the proxy is for.
+ * Gets the connection @proxy is for.
  *
  * Returns: A #GDBusConnection owned by @proxy. Do not free.
  **/
@@ -550,10 +916,25 @@ g_dbus_proxy_get_connection (GDBusProxy *proxy)
 }
 
 /**
+ * g_dbus_proxy_get_flags:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the flags that @proxy was constructed with.
+ *
+ * Returns: Flags from the #GDBusProxyFlags enumeration.
+ **/
+GDBusProxyFlags
+g_dbus_proxy_get_flags (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), 0);
+  return proxy->priv->flags;
+}
+
+/**
  * g_dbus_proxy_get_name:
  * @proxy: A #GDBusProxy.
  *
- * Gets the bus name the proxy is for.
+ * Gets the bus name @proxy is for.
  *
  * Returns: A string owned by @proxy. Do not free.
  **/
@@ -565,10 +946,28 @@ g_dbus_proxy_get_name (GDBusProxy *proxy)
 }
 
 /**
+ * g_dbus_proxy_get_name_owner:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the unique name of the owner owning the name @proxy is for. If there
+ * is no owner of the name, this function returns %NULL. You can watch the
+ * property #GDBusProxy:g-dbus-proxy-name-owner for notifications to track
+ * changes.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+g_dbus_proxy_get_name_owner (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return g_bus_name_watcher_get_name_owner (proxy->priv->watcher);
+}
+
+/**
  * g_dbus_proxy_get_object_path:
  * @proxy: A #GDBusProxy.
  *
- * Gets the object path the proxy is for.
+ * Gets the object path @proxy is for.
  *
  * Returns: A string owned by @proxy. Do not free.
  **/
@@ -583,7 +982,7 @@ g_dbus_proxy_get_object_path (GDBusProxy *proxy)
  * g_dbus_proxy_get_interface_name:
  * @proxy: A #GDBusProxy.
  *
- * Gets the D-Bus interface name the proxy is for.
+ * Gets the D-Bus interface name @proxy is for.
  *
  * Returns: A string owned by @proxy. Do not free.
  **/
@@ -595,43 +994,21 @@ g_dbus_proxy_get_interface_name (GDBusProxy *proxy)
 }
 
 /**
- * g_dbus_proxy_get_properties_loaded:
+ * g_dbus_proxy_get_properties_available:
  * @proxy: A #GDBusProxy.
  *
- * Gets whether properties has been loaded.
+ * Gets whether properties has been available.
  *
  * To track changes, listen for notifications on the
- * #GDBusProxy:g-dbus-proxy-properties-loaded property.
+ * #GDBusProxy:g-dbus-proxy-properties-available property.
  *
  * Returns: %TRUE if properties are available, %FALSE otherwise.
  **/
 gboolean
-g_dbus_proxy_get_properties_loaded (GDBusProxy *proxy)
+g_dbus_proxy_get_properties_available (GDBusProxy *proxy)
 {
   g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), FALSE);
-  return proxy->priv->properties_loaded;
-}
-
-/**
- * g_dbus_proxy_block_for_properties:
- * @proxy: A #GDBusProxy.
- *
- * If the attempt to load properties is done, does nothing. Otherwise blocks (not
- * in the mainloop), until the attempt to load properties is done.
- **/
-void
-g_dbus_proxy_block_for_properties (GDBusProxy *proxy)
-{
-  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
-
-  if (G_LIKELY (proxy->priv->properties_loaded))
-    return;
-
-  if (proxy->priv->property_loading_pending_call_id != 0)
-    {
-      g_dbus_connection_send_dbus_1_message_block (proxy->priv->connection,
-                                                   proxy->priv->property_loading_pending_call_id);
-    }
+  return proxy->priv->properties_available;
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -2217,8 +2594,8 @@ g_dbus_proxy_invoke_method_finish_valist (GDBusProxy          *proxy,
  * Note that @signature and and the supplied (type, value) pairs must match as described in
  * chapter TODO_SECTION_EXPLAINING_DBUS_TO_GTYPE_OBJECT_MAPPING.
  *
- * Returns: An pending call id (never 0) for the message that can be used in
- * g_dbus_connection_send_dbus_1_message_cancel() or g_dbus_connection_send_dbus_1_message_block().
+ * Returns: An pending call id (never 0) for the message that can be used with
+ * g_dbus_proxy_invoke_method_cancel() or g_dbus_proxy_invoke_method_block().
  **/
 guint
 g_dbus_proxy_invoke_method (GDBusProxy          *proxy,
@@ -2250,6 +2627,44 @@ g_dbus_proxy_invoke_method (GDBusProxy          *proxy,
 }
 
 /**
+ * g_dbus_proxy_invoke_method_block:
+ * @proxy: A #GDBusProxy.
+ * @pending_call_id: A pending call id obtained from g_dbus_proxy_invoke_method().
+ *
+ * Blocks until the pending call specified by @pending_call_id is done. This will
+ * not block in the main loop. You are guaranteed that the #GAsyncReadyCallback passed to
+ * g_dbus_proxy_invoke_method() is invoked before this function returns.
+ **/
+void
+g_dbus_proxy_invoke_method_block (GDBusProxy *proxy,
+                                  guint       pending_call_id)
+{
+  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+  g_dbus_connection_send_dbus_1_message_block (proxy->priv->connection,
+                                               pending_call_id);
+}
+
+/**
+ * g_dbus_proxy_invoke_method_cancel:
+ * @proxy: A #GDBusProxy.
+ * @pending_call_id: A pending call id obtained from g_dbus_proxy_invoke_method().
+ *
+ * Cancels the pending call specified by @pending_call_id.
+ *
+ * The #GAsyncReadyCallback passed to g_dbus_proxy_invoke_method() will
+ * be invoked in idle (e.g. after this function returns).
+ **/
+void
+g_dbus_proxy_invoke_method_cancel (GDBusProxy *proxy,
+                                   guint       pending_call_id)
+{
+  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+  g_dbus_connection_send_dbus_1_message_cancel (proxy->priv->connection,
+                                                pending_call_id);
+}
+
+
+/**
  * g_dbus_proxy_invoke_method_finish:
  * @proxy: A #GDBusProxy.
  * @signature: The D-Bus signature for all return values.
@@ -2277,6 +2692,9 @@ g_dbus_proxy_invoke_method_finish (GDBusProxy          *proxy,
   gboolean ret;
   va_list va_args;
 
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), FALSE);
+  /* TODO: other checks */
+
   va_start (va_args, first_out_arg_type);
   ret = g_dbus_proxy_invoke_method_finish_valist (proxy,
                                                   signature,
@@ -2339,6 +2757,9 @@ g_dbus_proxy_invoke_method_sync (GDBusProxy          *proxy,
   GType first_out_arg_type;
   gboolean ret;
 
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), FALSE);
+  /* TODO: other checks */
+
   ret = FALSE;
 
   va_start (va_args, first_in_arg_type);
@@ -2365,8 +2786,7 @@ g_dbus_proxy_invoke_method_sync (GDBusProxy          *proxy,
   first_out_arg_type = va_arg (va_args, GType);
 
   res = NULL;
-  g_dbus_connection_send_dbus_1_message_block (proxy->priv->connection,
-                                               pending_call_id);
+  g_dbus_proxy_invoke_method_block (proxy, pending_call_id);
 
   g_assert (res != NULL);
 
diff --git a/gdbus/gdbusproxy.h b/gdbus/gdbusproxy.h
index ebe2630..6b63efd 100644
--- a/gdbus/gdbusproxy.h
+++ b/gdbus/gdbusproxy.h
@@ -57,6 +57,9 @@ struct _GDBusProxy
 
 /**
  * GDBusProxyClass:
+ * @properties_available_changed: Signal class handler for the #GDBusProxy::g-dbus-proxy-properties-available-changed signal.
+ * @properties_changed: Signal class handler for the #GDBusProxy::g-dbus-proxy-properties-changed signal.
+ * @signal: Signal class handler for the #GDBusProxy::g-dbus-proxy-signal signal.
  *
  * Class structure for #GDBusProxy.
  */
@@ -66,6 +69,14 @@ struct _GDBusProxyClass
   GObjectClass parent_class;
 
   /*< public >*/
+  void (*properties_available_changed) (GDBusProxy   *proxy,
+                                        gboolean      properties_available);
+  void (*properties_changed)           (GDBusProxy   *proxy,
+                                        GHashTable   *changed_properties);
+  void (*signal)                       (GDBusProxy   *proxy,
+                                        const gchar  *name,
+                                        const gchar  *signature,
+                                        GValueArray  *args);
 
   /*< private >*/
   /* Padding for future expansion */
@@ -79,54 +90,50 @@ struct _GDBusProxyClass
   void (*_g_reserved8) (void);
 };
 
-GType            g_dbus_proxy_get_type              (void) G_GNUC_CONST;
-GDBusProxy      *g_dbus_proxy_new                   (GDBusConnection     *connection,
-                                                     const gchar         *name,
-                                                     const gchar         *object_path,
-                                                     const gchar         *interface_name,
-                                                     GCancellable        *cancellable,
-                                                     GAsyncReadyCallback  callback,
-                                                     gpointer             user_data);
-gboolean         g_dbus_proxy_new_finish             (GDBusProxy         *proxy,
-                                                      GAsyncResult       *res,
-                                                      GError            **error);
-GDBusConnection *g_dbus_proxy_get_connection        (GDBusProxy          *proxy);
-const gchar     *g_dbus_proxy_get_name              (GDBusProxy          *proxy);
-const gchar     *g_dbus_proxy_get_object_path       (GDBusProxy          *proxy);
-const gchar     *g_dbus_proxy_get_interface_name    (GDBusProxy          *proxy);
-gboolean         g_dbus_proxy_get_properties_loaded (GDBusProxy          *proxy);
-void             g_dbus_proxy_block_for_properties  (GDBusProxy          *proxy);
-guint            g_dbus_proxy_invoke_method         (GDBusProxy          *proxy,
-                                                     const gchar         *name,
-                                                     const gchar         *signature,
-                                                     guint                timeout_msec,
-                                                     GCancellable        *cancellable,
-                                                     GAsyncReadyCallback  callback,
-                                                     gpointer             user_data,
-                                                     GType                first_in_arg_type,
-                                                     ...);
-gboolean         g_dbus_proxy_invoke_method_finish  (GDBusProxy          *proxy,
-                                                     const gchar         *signature,
-                                                     GAsyncResult        *res,
-                                                     GError             **error,
-                                                     GType                first_out_arg_type,
-                                                     ...);
-gboolean         g_dbus_proxy_invoke_method_sync    (GDBusProxy          *proxy,
-                                                     const gchar         *name,
-                                                     const gchar         *in_signature,
-                                                     const gchar         *out_signature,
-                                                     guint                timeout_msec,
-                                                     GCancellable        *cancellable,
-                                                     GError             **error,
-                                                     GType                first_in_arg_type,
-                                                     ...);
-GDBusVariant    *g_dbus_proxy_get_cached_property   (GDBusProxy          *proxy,
-                                                     const gchar         *property_name,
-                                                     GError             **error);
-gboolean         g_dbus_proxy_set_cached_property   (GDBusProxy          *proxy,
-                                                     const gchar         *property_name,
-                                                     GDBusVariant        *variant,
-                                                     GError             **error);
+GType            g_dbus_proxy_get_type                    (void) G_GNUC_CONST;
+GDBusProxy      *g_dbus_proxy_new                         (GDBusConnection     *connection,
+                                                           GDBusProxyFlags      flags,
+                                                           const gchar         *name,
+                                                           const gchar         *object_path,
+                                                           const gchar         *interface_name);
+GDBusConnection *g_dbus_proxy_get_connection              (GDBusProxy          *proxy);
+GDBusProxyFlags  g_dbus_proxy_get_flags                   (GDBusProxy          *proxy);
+const gchar     *g_dbus_proxy_get_name                    (GDBusProxy          *proxy);
+const gchar     *g_dbus_proxy_get_name_owner              (GDBusProxy          *proxy);
+const gchar     *g_dbus_proxy_get_object_path             (GDBusProxy          *proxy);
+const gchar     *g_dbus_proxy_get_interface_name          (GDBusProxy          *proxy);
+guint            g_dbus_proxy_invoke_method               (GDBusProxy          *proxy,
+                                                           const gchar         *name,
+                                                           const gchar         *signature,
+                                                           guint                timeout_msec,
+                                                           GCancellable        *cancellable,
+                                                           GAsyncReadyCallback  callback,
+                                                           gpointer             user_data,
+                                                           GType                first_in_arg_type,
+                                                           ...);
+gboolean         g_dbus_proxy_invoke_method_finish        (GDBusProxy          *proxy,
+                                                           const gchar         *signature,
+                                                           GAsyncResult        *res,
+                                                           GError             **error,
+                                                           GType                first_out_arg_type,
+                                                           ...);
+void             g_dbus_proxy_invoke_method_block         (GDBusProxy          *proxy,
+                                                           guint                pending_call_id);
+void             g_dbus_proxy_invoke_method_cancel        (GDBusProxy          *proxy,
+                                                           guint                pending_call_id);
+gboolean         g_dbus_proxy_invoke_method_sync          (GDBusProxy          *proxy,
+                                                           const gchar         *name,
+                                                           const gchar         *in_signature,
+                                                           const gchar         *out_signature,
+                                                           guint                timeout_msec,
+                                                           GCancellable        *cancellable,
+                                                           GError             **error,
+                                                           GType                first_in_arg_type,
+                                                           ...);
+gboolean         g_dbus_proxy_get_properties_available    (GDBusProxy          *proxy);
+GDBusVariant    *g_dbus_proxy_get_cached_property         (GDBusProxy          *proxy,
+                                                           const gchar         *property_name,
+                                                           GError             **error);
 
 G_END_DECLS
 
diff --git a/gdbus/tests/proxy.c b/gdbus/tests/proxy.c
index 2618824..7b12f8b 100644
--- a/gdbus/tests/proxy.c
+++ b/gdbus/tests/proxy.c
@@ -29,10 +29,9 @@
 static GMainLoop *loop = NULL;
 
 /* ---------------------------------------------------------------------------------------------------- */
-/* Test GDBusProxy */
+/* Test marshalling on GDBusProxy (e.g. D-Bus <-> GType mapping) */
 /* ---------------------------------------------------------------------------------------------------- */
 
-
 static void
 proxy_on_name_appeared (GDBusConnection *connection,
                         const gchar     *name,
@@ -131,13 +130,11 @@ proxy_on_name_appeared (GDBusConnection *connection,
   error = NULL;
 
   frob = g_dbus_proxy_new (connection,
+                           G_DBUS_PROXY_FLAGS_NONE,
                            name,
                            "/com/example/TestObject",
-                           "com.example.Frob",
-                           NULL,
-                           NULL,
-                           NULL);
-  g_dbus_proxy_block_for_properties (frob);
+                           "com.example.Frob");
+  _g_assert_signal_received (frob, "g-dbus-proxy-properties-available-changed");
 
   ret = g_dbus_proxy_invoke_method_sync (frob,
                                          "HelloWorld",
@@ -911,7 +908,7 @@ proxy_on_name_vanished (GDBusConnection *connection,
 }
 
 static void
-test_proxy (void)
+test_proxy_marshalling (void)
 {
   guint watcher_id;
 
@@ -926,8 +923,7 @@ test_proxy (void)
   /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
    * until one can connect to the bus but that's not how things work right now
    */
-  usleep (200 * 1000);
-
+  usleep (500 * 1000);
   /* this is safe; testserver will exit once the bus goes away */
   g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
 
@@ -940,6 +936,253 @@ test_proxy (void)
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
+/* Test that the property aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_proxy_properties (void)
+{
+  GDBusConnection *c;
+  GDBusProxy *frob;
+  gboolean ret;
+  GError *error;
+  GDBusVariant *variant;
+  GDBusVariant *variant2;
+
+  error = NULL;
+
+  session_bus_up ();
+
+  c = g_dbus_connection_bus_get (G_BUS_TYPE_SESSION);
+  g_dbus_connection_set_exit_on_close (c, FALSE);
+  frob = g_dbus_proxy_new (c,
+                           G_DBUS_PROXY_FLAGS_NONE,
+                           "com.example.TestService",
+                           "/com/example/TestObject",
+                           "com.example.Frob");
+  g_assert (!g_dbus_proxy_get_properties_available (frob));
+
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+
+  /**
+   * Once com.example.TestService appears on the bus we should start loading properties.
+   */
+  _g_assert_signal_received (frob, "g-dbus-proxy-properties-available-changed");
+  g_assert (g_dbus_proxy_get_properties_available (frob));
+
+  /**
+   * If the service disappears and reappears, check
+   *  1. that we receive #GDBusProxy::g-dbus-proxy-properties-available-changed
+   *  2. notification on #GDBusProxy:g-dbus-proxy-properties-available
+   */
+  /* case 1 */
+  ret = g_dbus_proxy_invoke_method_sync (frob, "Quit", "", "", -1, NULL, &error, G_TYPE_INVALID, G_TYPE_INVALID);
+  g_assert_no_error (error);
+  g_assert (ret);
+  _g_assert_signal_received (frob, "g-dbus-proxy-properties-available-changed");
+  g_assert (!g_dbus_proxy_get_properties_available (frob));
+  /* case 2 */
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+  _g_assert_property_notify (frob, "g-dbus-proxy-properties-available");
+  g_assert (g_dbus_proxy_get_properties_available (frob));
+  ret = g_dbus_proxy_invoke_method_sync (frob, "Quit", "", "", -1, NULL, &error, G_TYPE_INVALID, G_TYPE_INVALID);
+  g_assert_no_error (error);
+  g_assert (ret);
+  _g_assert_property_notify (frob, "g-dbus-proxy-properties-available");
+  g_assert (!g_dbus_proxy_get_properties_available (frob));
+
+  /**
+   *  Also check that we get notified about the name owner, e.g.#GDBusProxy:g-dbus-proxy-name-owner
+   */
+  g_assert_cmpstr (g_dbus_proxy_get_name_owner (frob), ==, NULL);
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+  _g_assert_property_notify (frob, "g-dbus-proxy-name-owner");
+  g_assert_cmpstr (g_dbus_proxy_get_name_owner (frob), !=, NULL);
+  ret = g_dbus_proxy_invoke_method_sync (frob, "Quit", "", "", -1, NULL, &error, G_TYPE_INVALID, G_TYPE_INVALID);
+  g_assert_no_error (error);
+  g_assert (ret);
+  _g_assert_property_notify (frob, "g-dbus-proxy-name-owner");
+  g_assert_cmpstr (g_dbus_proxy_get_name_owner (frob), ==, NULL);
+
+  /**
+   * Check that trying to access properties when they're not there will return an error
+   */
+  variant = g_dbus_proxy_get_cached_property (frob, "y", &error);
+  g_assert (variant == NULL);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED);
+  g_error_free (error);
+  error = NULL;
+
+  /**
+   * Bring up the service, wait for properties and check we can access them
+   */
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+  _g_assert_property_notify (frob, "g-dbus-proxy-properties-available");
+  /* don't want to test all properties, we already have tests for GDBusVariant */
+  variant = g_dbus_proxy_get_cached_property (frob, "y", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpint (g_dbus_variant_get_byte (variant), ==, 1);
+  g_object_unref (variant);
+  variant = g_dbus_proxy_get_cached_property (frob, "o", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpstr (g_dbus_variant_get_object_path (variant), ==, "/some/path");
+  g_object_unref (variant);
+
+  /**
+   * Now ask the service to change a property and check that #GDBusProxy::g-dbus-proxy-property-changed
+   * is received. Also check that the cache is updated.
+   */
+  variant2 = g_dbus_variant_new_for_byte (42);
+  ret = g_dbus_proxy_invoke_method_sync (frob, "FrobSetProperty", "sv", "",
+                                         -1, NULL, &error,
+                                         G_TYPE_STRING, "y",
+                                         G_TYPE_DBUS_VARIANT, variant2,
+                                         G_TYPE_INVALID,
+                                         G_TYPE_INVALID);
+  g_assert_no_error (error);
+  g_assert (ret);
+  g_object_unref (variant2);
+  _g_assert_signal_received (frob, "g-dbus-proxy-properties-changed");
+  variant = g_dbus_proxy_get_cached_property (frob, "y", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpint (g_dbus_variant_get_byte (variant), ==, 42);
+  g_object_unref (variant);
+
+  g_object_unref (frob);
+  g_object_unref (c);
+
+  session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the signal aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_proxy_signals_on_signal (GDBusProxy  *proxy,
+                              const gchar *signal_name,
+                              const gchar *signature,
+                              GValueArray *args,
+                              gpointer     user_data)
+{
+  GString *s = user_data;
+
+  g_assert_cmpstr (signature, ==, "so");
+  g_assert_cmpint (args->n_values, ==, 2);
+  g_assert (G_VALUE_TYPE (&args->values[0]) == G_TYPE_STRING);
+  g_assert (G_VALUE_TYPE (&args->values[1]) == G_TYPE_STRING);
+
+  g_string_append (s, signal_name);
+  g_string_append_c (s, ':');
+  g_string_append (s, g_value_get_string (&args->values[0]));
+  g_string_append_c (s, ',');
+  g_string_append (s, g_value_get_string (&args->values[1]));
+}
+
+static void
+test_proxy_signals (void)
+{
+  GDBusConnection *c;
+  GDBusProxy *frob;
+  gboolean ret;
+  GError *error;
+  GString *s;
+
+  error = NULL;
+
+  session_bus_up ();
+
+  c = g_dbus_connection_bus_get (G_BUS_TYPE_SESSION);
+  g_dbus_connection_set_exit_on_close (c, FALSE);
+  frob = g_dbus_proxy_new (c,
+                           G_DBUS_PROXY_FLAGS_NONE,
+                           "com.example.TestService",
+                           "/com/example/TestObject",
+                           "com.example.Frob");
+  g_assert (!g_dbus_proxy_get_properties_available (frob));
+
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+  _g_assert_property_notify (frob, "g-dbus-proxy-properties-available");
+  g_assert (g_dbus_proxy_get_properties_available (frob));
+
+  /**
+   * Ask the service to emit some signals and check that we receive them.
+   *
+   * Note that blocking calls don't block in the mainloop so wait for the signal (which
+   * is dispatched before the method reply)
+   */
+  s = g_string_new (NULL);
+  g_signal_connect (frob,
+                    "g-dbus-proxy-signal",
+                    G_CALLBACK (test_proxy_signals_on_signal),
+                    s);
+  ret = g_dbus_proxy_invoke_method_sync (frob, "EmitSignal", "sso", "",
+                                         -1, NULL, &error,
+                                         G_TYPE_STRING, "SomeSignal",
+                                         G_TYPE_STRING, "Accept the next proposition you hear",
+                                         G_TYPE_STRING, "/some/path",
+                                         G_TYPE_INVALID,
+                                         G_TYPE_INVALID);
+  g_assert_no_error (error);
+  g_assert (ret);
+  _g_assert_signal_received (frob, "g-dbus-proxy-signal");
+  g_assert_cmpstr (s->str, ==, "SomeSignal:Accept the next proposition you hear .. in bed!,/some/path/in/bed");
+
+  /**
+   * Make the service quit, bring it back up and then try again (with the same proxy).
+   *
+   * This checks that D-Bus signals are properly reconnected to the unique name of the
+   * instance who currently owns the name.
+   */
+  /* nuke and bring up */
+  ret = g_dbus_proxy_invoke_method_sync (frob, "Quit", "", "", -1, NULL, &error, G_TYPE_INVALID, G_TYPE_INVALID);
+  g_assert_no_error (error);
+  g_assert (ret);
+  _g_assert_property_notify (frob, "g-dbus-proxy-name-owner");
+  g_assert_cmpstr (g_dbus_proxy_get_name_owner (frob), ==, NULL);
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+  _g_assert_property_notify (frob, "g-dbus-proxy-name-owner");
+  g_assert_cmpstr (g_dbus_proxy_get_name_owner (frob), !=, NULL);
+  /* again */
+  g_string_set_size (s, 0);
+  ret = g_dbus_proxy_invoke_method_sync (frob, "EmitSignal", "sso", "",
+                                         -1, NULL, &error,
+                                         G_TYPE_STRING, "OtherSignal",
+                                         G_TYPE_STRING, "Your present plans are going to succeed",
+                                         G_TYPE_STRING, "/another/path",
+                                         G_TYPE_INVALID,
+                                         G_TYPE_INVALID);
+  g_assert_no_error (error);
+  g_assert (ret);
+  _g_assert_signal_received (frob, "g-dbus-proxy-signal");
+  g_assert_cmpstr (s->str, ==, "OtherSignal:Your present plans are going to succeed .. in bed!,/another/path/in/bed");
+  g_signal_handlers_disconnect_by_func (frob, test_proxy_signals_on_signal, s);
+  g_string_free (s, TRUE);
+
+  g_object_unref (frob);
+  g_object_unref (c);
+
+  session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
 
 int
 main (int   argc,
@@ -957,6 +1200,8 @@ main (int   argc,
   g_unsetenv ("DISPLAY");
   g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
 
-  g_test_add_func ("/gdbus/proxy", test_proxy);
+  g_test_add_func ("/gdbus/proxy-marshalling", test_proxy_marshalling);
+  g_test_add_func ("/gdbus/proxy-properties", test_proxy_properties);
+  g_test_add_func ("/gdbus/proxy-signals", test_proxy_signals);
   return g_test_run();
 }
diff --git a/gdbus/tests/testserver.py b/gdbus/tests/testserver.py
index 5ce3dfd..78b66b4 100755
--- a/gdbus/tests/testserver.py
+++ b/gdbus/tests/testserver.py
@@ -158,6 +158,39 @@ class TestService(dbus.service.Object):
 
         return ret_h_str_to_pair, ret_h_str_to_variant, h_str_to_av, h_str_to_aav, h_str_to_array_of_pairs, hash_of_hashes
 
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                          in_signature='', out_signature='')
+    def Quit(self):
+        mainloop.quit()
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                          in_signature='sv', out_signature='')
+    def FrobSetProperty(self, prop_name, prop_value):
+        self.frob_props[prop_name] = prop_value
+        message = dbus.lowlevel.SignalMessage("/com/example/TestObject",
+                                              "org.freedesktop.DBus.Properties",
+                                              "PropertiesChanged")
+        message.append("com.example.Frob")
+        message.append({prop_name : prop_value})
+        session_bus.send_message(message)
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                          in_signature='sso', out_signature='')
+    def EmitSignal(self, signal_name, str1, objpath1):
+        message = dbus.lowlevel.SignalMessage("/com/example/TestObject",
+                                              "com.example.Frob",
+                                              signal_name)
+        message.append(str1 + " .. in bed!")
+        message.append(dbus.ObjectPath (objpath1 + "/in/bed"))
+        session_bus.send_message(message)
+
+    # ----------------------------------------------------------------------------------------------------
+
     @dbus.service.method("org.freedesktop.DBus.Properties",
                          in_signature  = 'ss',
                          out_signature = 'v')



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]