diff --git a/lib/wp/proxy-link.c b/lib/wp/proxy-link.c
index c7b5adee1eb6d23ec36dfd9d29f9d5ab8e035925..ff99fbc9b060e71852a246c37031a8914c83f898 100644
--- a/lib/wp/proxy-link.c
+++ b/lib/wp/proxy-link.c
@@ -6,46 +6,29 @@
  * SPDX-License-Identifier: MIT
  */
 
-#include "error.h"
 #include "proxy-link.h"
+
 #include <pipewire/pipewire.h>
 
 struct _WpProxyLink
 {
   WpProxy parent;
 
-  /* The task to signal the proxy is initialized */
-  GTask *init_task;
-
   /* The link proxy listener */
   struct spa_hook listener;
-
-  /* The link info */
-  struct pw_link_info *info;
 };
 
-static void wp_proxy_link_async_initable_init (gpointer iface,
-    gpointer iface_data);
-
-G_DEFINE_TYPE_WITH_CODE (WpProxyLink, wp_proxy_link, WP_TYPE_PROXY,
-    G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
-                           wp_proxy_link_async_initable_init))
+G_DEFINE_TYPE (WpProxyLink, wp_proxy_link, WP_TYPE_PROXY)
 
 static void
 link_event_info(void *data, const struct pw_link_info *info)
 {
-  WpProxyLink *self = data;
-
-  /* Make sure the task is valid */
-  if (!self->init_task)
-    return;
+  WpProxy *proxy = WP_PROXY (data);
 
-  /* Update the link info */
-  self->info = pw_link_info_update(self->info, info);
-
-  /* Finish the creation of the proxy */
-  g_task_return_boolean (self->init_task, TRUE);
-  g_clear_object (&self->init_task);
+  wp_proxy_update_native_info (proxy, info,
+      (WpProxyNativeInfoUpdate) pw_link_info_update,
+      (GDestroyNotify) pw_link_info_free);
+  wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_INFO);
 }
 
 static const struct pw_link_proxy_events link_events = {
@@ -54,101 +37,22 @@ static const struct pw_link_proxy_events link_events = {
 };
 
 static void
-wp_proxy_link_finalize (GObject * object)
-{
-  WpProxyLink *self = WP_PROXY_LINK(object);
-
-  /* Destroy the init task */
-  g_clear_object (&self->init_task);
-
-  /* Clear the info */
-  if (self->info) {
-    pw_link_info_free(self->info);
-    self->info = NULL;
-  }
-
-  G_OBJECT_CLASS (wp_proxy_link_parent_class)->finalize (object);
-}
-
-static void
-wp_proxy_link_destroy (WpProxy * proxy)
-{
-  WpProxyLink *self = WP_PROXY_LINK(proxy);
-  GError *error = NULL;
-
-  /* Return error if the pipewire destruction happened while the async creation
-   * of this proxy link object has not finished */
-  if (self->init_task) {
-    g_set_error (&error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
-        "pipewire link proxy destroyed before finishing");
-    g_task_return_error (self->init_task, error);
-    g_clear_object (&self->init_task);
-  }
-}
-
-static void
-wp_proxy_link_init_async (GAsyncInitable *initable, int io_priority,
-    GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
-{
-  WpProxyLink *self = WP_PROXY_LINK(initable);
-  WpProxy *wp_proxy = WP_PROXY(initable);
-  struct pw_link_proxy *proxy = NULL;
-
-  /* Create the async task */
-  self->init_task = g_task_new (initable, cancellable, callback, data);
-
-  /* Get the proxy from the base class */
-  proxy = wp_proxy_get_pw_proxy(wp_proxy);
-
-  /* Add the link proxy listener */
-  pw_link_proxy_add_listener(proxy, &self->listener, &link_events, self);
-}
-
-static void
-wp_proxy_link_async_initable_init (gpointer iface, gpointer iface_data)
+wp_proxy_link_init (WpProxyLink * self)
 {
-  GAsyncInitableIface *ai_iface = iface;
-
-  /* Only set the init_async */
-  ai_iface->init_async = wp_proxy_link_init_async;
 }
 
 static void
-wp_proxy_link_init (WpProxyLink * self)
+wp_proxy_link_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
 {
+  WpProxyLink *self = WP_PROXY_LINK (proxy);
+  pw_link_proxy_add_listener ((struct pw_link_proxy *) pw_proxy,
+      &self->listener, &link_events, self);
 }
 
 static void
 wp_proxy_link_class_init (WpProxyLinkClass * klass)
 {
-  GObjectClass *object_class = (GObjectClass *) klass;
   WpProxyClass *proxy_class = (WpProxyClass *) klass;
 
-  object_class->finalize = wp_proxy_link_finalize;
-
-  proxy_class->destroy = wp_proxy_link_destroy;
-}
-
-void
-wp_proxy_link_new (guint global_id, gpointer proxy,
-    GAsyncReadyCallback callback, gpointer user_data)
-{
-  g_async_initable_new_async (
-      WP_TYPE_PROXY_LINK, G_PRIORITY_DEFAULT, NULL, callback, user_data,
-      "global-id", global_id,
-      "pw-proxy", proxy,
-      NULL);
-}
-
-WpProxyLink *
-wp_proxy_link_new_finish(GObject *initable, GAsyncResult *res, GError **error)
-{
-  GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
-  return WP_PROXY_LINK(g_async_initable_new_finish(ai, res, error));
-}
-
-const struct pw_link_info *
-wp_proxy_link_get_info (WpProxyLink * self)
-{
-  return self->info;
+  proxy_class->pw_proxy_created = wp_proxy_link_pw_proxy_created;
 }
diff --git a/lib/wp/proxy-link.h b/lib/wp/proxy-link.h
index cd965f50bf5266d1096898398ba0023a764c34f8..1a7521badb1f038d0ddad330f18ca112c6d9dc4c 100644
--- a/lib/wp/proxy-link.h
+++ b/lib/wp/proxy-link.h
@@ -9,7 +9,6 @@
 #ifndef __WIREPLUMBER_PROXY_LINK_H__
 #define __WIREPLUMBER_PROXY_LINK_H__
 
-#include "core.h"
 #include "proxy.h"
 
 G_BEGIN_DECLS
@@ -17,12 +16,12 @@ G_BEGIN_DECLS
 #define WP_TYPE_PROXY_LINK (wp_proxy_link_get_type ())
 G_DECLARE_FINAL_TYPE (WpProxyLink, wp_proxy_link, WP, PROXY_LINK, WpProxy)
 
-void wp_proxy_link_new (guint global_id, gpointer proxy,
-    GAsyncReadyCallback callback, gpointer user_data);
-WpProxyLink *wp_proxy_link_new_finish(GObject *initable, GAsyncResult *res,
-    GError **error);
-
-const struct pw_link_info *wp_proxy_link_get_info (WpProxyLink * self);
+static inline const struct pw_link_info *
+wp_proxy_link_get_info (WpProxyLink * self)
+{
+  return (const struct pw_link_info *)
+      wp_proxy_get_native_info (WP_PROXY (self));
+}
 
 G_END_DECLS
 
diff --git a/lib/wp/proxy-node.c b/lib/wp/proxy-node.c
index 46569e31b9ff586b2abb246a8fe70bf165b69849..86443327ddea8e04cb7418381d3dba55083960eb 100644
--- a/lib/wp/proxy-node.c
+++ b/lib/wp/proxy-node.c
@@ -6,46 +6,29 @@
  * SPDX-License-Identifier: MIT
  */
 
-#include "error.h"
 #include "proxy-node.h"
+
 #include <pipewire/pipewire.h>
 
 struct _WpProxyNode
 {
   WpProxy parent;
 
-  /* The task to signal the proxy is initialized */
-  GTask *init_task;
-
   /* The node proxy listener */
   struct spa_hook listener;
-
-  /* The node info */
-  struct pw_node_info *info;
 };
 
-static void wp_proxy_node_async_initable_init (gpointer iface,
-    gpointer iface_data);
-
-G_DEFINE_TYPE_WITH_CODE (WpProxyNode, wp_proxy_node, WP_TYPE_PROXY,
-    G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
-                           wp_proxy_node_async_initable_init))
+G_DEFINE_TYPE (WpProxyNode, wp_proxy_node, WP_TYPE_PROXY)
 
 static void
 node_event_info(void *data, const struct pw_node_info *info)
 {
-  WpProxyNode *self = data;
-
-  /* Make sure the task is valid */
-  if (!self->init_task)
-    return;
+  WpProxy *proxy = WP_PROXY (data);
 
-  /* Update the node info */
-  self->info = pw_node_info_update(self->info, info);
-
-  /* Finish the creation of the proxy */
-  g_task_return_boolean (self->init_task, TRUE);
-  g_clear_object (&self->init_task);
+  wp_proxy_update_native_info (proxy, info,
+      (WpProxyNativeInfoUpdate) pw_node_info_update,
+      (GDestroyNotify) pw_node_info_free);
+  wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_INFO);
 }
 
 static const struct pw_node_proxy_events node_events = {
@@ -54,101 +37,22 @@ static const struct pw_node_proxy_events node_events = {
 };
 
 static void
-wp_proxy_node_finalize (GObject * object)
-{
-  WpProxyNode *self = WP_PROXY_NODE(object);
-
-  /* Destroy the init task */
-  g_clear_object (&self->init_task);
-
-  /* Clear the info */
-  if (self->info) {
-    pw_node_info_free(self->info);
-    self->info = NULL;
-  }
-
-  G_OBJECT_CLASS (wp_proxy_node_parent_class)->finalize (object);
-}
-
-static void
-wp_proxy_node_destroy (WpProxy * proxy)
-{
-  WpProxyNode *self = WP_PROXY_NODE(proxy);
-  GError *error = NULL;
-
-  /* Return error if the pipewire destruction happened while the async creation
-   * of this proxy node object has not finished */
-  if (self->init_task) {
-    g_set_error (&error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
-        "pipewire node proxy destroyed before finishing");
-    g_task_return_error (self->init_task, error);
-    g_clear_object (&self->init_task);
-  }
-}
-
-static void
-wp_proxy_node_init_async (GAsyncInitable *initable, int io_priority,
-    GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
-{
-  WpProxyNode *self = WP_PROXY_NODE(initable);
-  WpProxy *wp_proxy = WP_PROXY(initable);
-  struct pw_node_proxy *proxy = NULL;
-
-  /* Create the async task */
-  self->init_task = g_task_new (initable, cancellable, callback, data);
-
-  /* Get the proxy from the base class */
-  proxy = wp_proxy_get_pw_proxy(wp_proxy);
-
-  /* Add the node proxy listener */
-  pw_node_proxy_add_listener(proxy, &self->listener, &node_events, self);
-}
-
-static void
-wp_proxy_node_async_initable_init (gpointer iface, gpointer iface_data)
+wp_proxy_node_init (WpProxyNode * self)
 {
-  GAsyncInitableIface *ai_iface = iface;
-
-  /* Only set the init_async */
-  ai_iface->init_async = wp_proxy_node_init_async;
 }
 
 static void
-wp_proxy_node_init (WpProxyNode * self)
+wp_proxy_node_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
 {
+  WpProxyNode *self = WP_PROXY_NODE (proxy);
+  pw_node_proxy_add_listener ((struct pw_node_proxy *) pw_proxy,
+      &self->listener, &node_events, self);
 }
 
 static void
 wp_proxy_node_class_init (WpProxyNodeClass * klass)
 {
-  GObjectClass *object_class = (GObjectClass *) klass;
   WpProxyClass *proxy_class = (WpProxyClass *) klass;
 
-  object_class->finalize = wp_proxy_node_finalize;
-
-  proxy_class->destroy = wp_proxy_node_destroy;
-}
-
-void
-wp_proxy_node_new (guint global_id, gpointer proxy,
-    GAsyncReadyCallback callback, gpointer user_data)
-{
-  g_async_initable_new_async (
-      WP_TYPE_PROXY_NODE, G_PRIORITY_DEFAULT, NULL, callback, user_data,
-      "global-id", global_id,
-      "pw-proxy", proxy,
-      NULL);
-}
-
-WpProxyNode *
-wp_proxy_node_new_finish(GObject *initable, GAsyncResult *res, GError **error)
-{
-  GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
-  return WP_PROXY_NODE(g_async_initable_new_finish(ai, res, error));
-}
-
-const struct pw_node_info *
-wp_proxy_node_get_info (WpProxyNode * self)
-{
-  return self->info;
+  proxy_class->pw_proxy_created = wp_proxy_node_pw_proxy_created;
 }
diff --git a/lib/wp/proxy-node.h b/lib/wp/proxy-node.h
index 12feba0b79213a95706b32a6fda8e4edab3dae66..7d6050dcb8f0202f3c8d6adb9284b2089d78d927 100644
--- a/lib/wp/proxy-node.h
+++ b/lib/wp/proxy-node.h
@@ -9,7 +9,6 @@
 #ifndef __WIREPLUMBER_PROXY_NODE_H__
 #define __WIREPLUMBER_PROXY_NODE_H__
 
-#include "core.h"
 #include "proxy.h"
 
 G_BEGIN_DECLS
@@ -17,12 +16,12 @@ G_BEGIN_DECLS
 #define WP_TYPE_PROXY_NODE (wp_proxy_node_get_type ())
 G_DECLARE_FINAL_TYPE (WpProxyNode, wp_proxy_node, WP, PROXY_NODE, WpProxy)
 
-void wp_proxy_node_new (guint global_id, gpointer proxy,
-    GAsyncReadyCallback callback, gpointer user_data);
-WpProxyNode *wp_proxy_node_new_finish(GObject *initable, GAsyncResult *res,
-    GError **error);
-
-const struct pw_node_info *wp_proxy_node_get_info (WpProxyNode * self);
+static inline const struct pw_node_info *
+wp_proxy_node_get_info (WpProxyNode * self)
+{
+  return (const struct pw_node_info *)
+      wp_proxy_get_native_info (WP_PROXY (self));
+}
 
 G_END_DECLS
 
diff --git a/lib/wp/proxy-port.c b/lib/wp/proxy-port.c
index c796e5a24784475e91d0bdcd0316d6bbbd26c2a6..5cc68ba2355fb82ca3d69e5a38f6aa529aa6776d 100644
--- a/lib/wp/proxy-port.c
+++ b/lib/wp/proxy-port.c
@@ -6,8 +6,8 @@
  * SPDX-License-Identifier: MIT
  */
 
-#include "error.h"
 #include "proxy-port.h"
+
 #include <pipewire/pipewire.h>
 #include <spa/param/audio/format-utils.h>
 
@@ -15,46 +15,33 @@ struct _WpProxyPort
 {
   WpProxy parent;
 
-  /* The task to signal the proxy is initialized */
-  GTask *init_task;
-
   /* The port proxy listener */
   struct spa_hook listener;
 
-  /* The port info */
-  struct pw_port_info *info;
-
   /* The port format */
   uint32_t media_type;
   uint32_t media_subtype;
   struct spa_audio_info_raw format;
 };
 
-static void wp_proxy_port_async_initable_init (gpointer iface,
-    gpointer iface_data);
-
-G_DEFINE_TYPE_WITH_CODE (WpProxyPort, wp_proxy_port, WP_TYPE_PROXY,
-    G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
-                           wp_proxy_port_async_initable_init))
+G_DEFINE_TYPE (WpProxyPort, wp_proxy_port, WP_TYPE_PROXY)
 
 static void
 port_event_info(void *data, const struct pw_port_info *info)
 {
-  WpProxyPort *self = data;
+  WpProxy *proxy = WP_PROXY (data);
 
-  /* Update the port info */
-  self->info = pw_port_info_update(self->info, info);
+  wp_proxy_update_native_info (proxy, info,
+      (WpProxyNativeInfoUpdate) pw_port_info_update,
+      (GDestroyNotify) pw_port_info_free);
+  wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_INFO);
 }
 
 static void
 port_event_param(void *data, int seq, uint32_t id, uint32_t index,
     uint32_t next, const struct spa_pod *param)
 {
-  WpProxyPort *self = data;
-
-  /* Make sure the task is valid */
-  if (!self->init_task)
-    return;
+  WpProxyPort *self = WP_PROXY_PORT (data);
 
   /* Only handle EnumFormat */
   if (id != SPA_PARAM_EnumFormat)
@@ -64,17 +51,14 @@ port_event_param(void *data, int seq, uint32_t id, uint32_t index,
   spa_format_parse(param, &self->media_type, &self->media_subtype);
 
   /* Only handle raw audio formats for now */
-  if (self->media_type != SPA_MEDIA_TYPE_audio ||
-      self->media_subtype != SPA_MEDIA_SUBTYPE_raw)
-    return;
-
-  /* Parse the raw audio format */
-  spa_pod_fixate((struct spa_pod*)param);
-  spa_format_audio_raw_parse(param, &self->format);
+  if (self->media_type == SPA_MEDIA_TYPE_audio &&
+      self->media_subtype == SPA_MEDIA_SUBTYPE_raw) {
+    /* Parse the raw audio format */
+    spa_pod_fixate ((struct spa_pod *) param);
+    spa_format_audio_raw_parse (param, &self->format);
+  }
 
-  /* Finish the creation of the proxy */
-  g_task_return_boolean (self->init_task, TRUE);
-  g_clear_object (&self->init_task);
+  wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_PORT_FEATURE_FORMAT);
 }
 
 static const struct pw_port_proxy_events port_events = {
@@ -84,107 +68,40 @@ static const struct pw_port_proxy_events port_events = {
 };
 
 static void
-wp_proxy_port_finalize (GObject * object)
-{
-  WpProxyPort *self = WP_PROXY_PORT(object);
-
-  /* Destroy the init task */
-  g_clear_object (&self->init_task);
-
-  /* Clear the indo */
-  if (self->info) {
-    pw_port_info_free(self->info);
-    self->info = NULL;
-  }
-
-  G_OBJECT_CLASS (wp_proxy_port_parent_class)->finalize (object);
-}
-
-static void
-wp_proxy_port_destroy (WpProxy * proxy)
+wp_proxy_port_init (WpProxyPort * self)
 {
-  WpProxyPort *self = WP_PROXY_PORT(proxy);
-  GError *error = NULL;
-
-  /* Return error if the pipewire destruction happened while the async creation
-   * of this proxy port object has not finished */
-  if (self->init_task) {
-    g_set_error (&error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
-        "pipewire port proxy destroyed before finishing");
-    g_task_return_error (self->init_task, error);
-    g_clear_object (&self->init_task);
-  }
 }
 
 static void
-wp_proxy_port_init_async (GAsyncInitable *initable, int io_priority,
-    GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
+wp_proxy_port_augment (WpProxy * proxy, WpProxyFeatures features)
 {
-  WpProxyPort *self = WP_PROXY_PORT(initable);
-  WpProxy *wp_proxy = WP_PROXY(initable);
-  struct pw_port_proxy *proxy = NULL;
+  /* call the default implementation to ensure we have a proxy, if necessary */
+  WP_PROXY_CLASS (wp_proxy_port_parent_class)->augment (proxy, features);
 
-  /* Create the async task */
-  self->init_task = g_task_new (initable, cancellable, callback, data);
+  if (features & WP_PROXY_PORT_FEATURE_FORMAT) {
+    struct pw_proxy *pwp = wp_proxy_get_pw_proxy (proxy);
+    g_return_if_fail (pwp != NULL);
 
-  /* Get the proxy from the base class */
-  proxy = wp_proxy_get_pw_proxy(wp_proxy);
-
-  /* Add the port proxy listener */
-  pw_port_proxy_add_listener(proxy, &self->listener, &port_events, self);
-
-  /* Emit the EnumFormat param */
-  pw_port_proxy_enum_params((struct pw_port_proxy*)proxy, 0,
-          SPA_PARAM_EnumFormat, 0, -1, NULL);
-}
-
-static void
-wp_proxy_port_async_initable_init (gpointer iface, gpointer iface_data)
-{
-  GAsyncInitableIface *ai_iface = iface;
-
-  /* Only set the init_async */
-  ai_iface->init_async = wp_proxy_port_init_async;
+    pw_port_proxy_enum_params ((struct pw_port_proxy *) pwp, 0,
+        SPA_PARAM_EnumFormat, 0, -1, NULL);
+  }
 }
 
 static void
-wp_proxy_port_init (WpProxyPort * self)
+wp_proxy_port_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
 {
+  WpProxyPort *self = WP_PROXY_PORT (proxy);
+  pw_port_proxy_add_listener ((struct pw_port_proxy *) pw_proxy,
+      &self->listener, &port_events, self);
 }
 
 static void
 wp_proxy_port_class_init (WpProxyPortClass * klass)
 {
-  GObjectClass *object_class = (GObjectClass *) klass;
   WpProxyClass *proxy_class = (WpProxyClass *) klass;
 
-  object_class->finalize = wp_proxy_port_finalize;
-
-  proxy_class->destroy = wp_proxy_port_destroy;
-}
-
-void
-wp_proxy_port_new (guint global_id, gpointer proxy,
-    GAsyncReadyCallback callback, gpointer user_data)
-{
-  g_async_initable_new_async (
-      WP_TYPE_PROXY_PORT, G_PRIORITY_DEFAULT, NULL, callback, user_data,
-      "global-id", global_id,
-      "pw-proxy", proxy,
-      NULL);
-}
-
-WpProxyPort *
-wp_proxy_port_new_finish(GObject *initable, GAsyncResult *res, GError **error)
-{
-  GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
-  return WP_PROXY_PORT(g_async_initable_new_finish(ai, res, error));
-}
-
-const struct pw_port_info *
-wp_proxy_port_get_info (WpProxyPort * self)
-{
-  return self->info;
+  proxy_class->augment = wp_proxy_port_augment;
+  proxy_class->pw_proxy_created = wp_proxy_port_pw_proxy_created;
 }
 
 const struct spa_audio_info_raw *
diff --git a/lib/wp/proxy-port.h b/lib/wp/proxy-port.h
index 718c8da67e9c91291af79258aed72bd231259033..79261899155c9d54737b9fb9d3e3e967771397fc 100644
--- a/lib/wp/proxy-port.h
+++ b/lib/wp/proxy-port.h
@@ -9,20 +9,24 @@
 #ifndef __WIREPLUMBER_PROXY_PORT_H__
 #define __WIREPLUMBER_PROXY_PORT_H__
 
-#include "core.h"
 #include "proxy.h"
 
 G_BEGIN_DECLS
 
+typedef enum { /*< flags >*/
+  WP_PROXY_PORT_FEATURE_FORMAT = (WP_PROXY_FEATURE_LAST << 0),
+} WpProxyPortFeatures;
+
 #define WP_TYPE_PROXY_PORT (wp_proxy_port_get_type ())
 G_DECLARE_FINAL_TYPE (WpProxyPort, wp_proxy_port, WP, PROXY_PORT, WpProxy)
 
-void wp_proxy_port_new (guint global_id, gpointer proxy,
-    GAsyncReadyCallback callback, gpointer user_data);
-WpProxyPort *wp_proxy_port_new_finish(GObject *initable, GAsyncResult *res,
-    GError **error);
+static inline const struct pw_port_info *
+wp_proxy_port_get_info (WpProxyPort * self)
+{
+  return (const struct pw_port_info *)
+      wp_proxy_get_native_info (WP_PROXY (self));
+}
 
-const struct pw_port_info *wp_proxy_port_get_info (WpProxyPort * self);
 const struct spa_audio_info_raw *wp_proxy_port_get_format (WpProxyPort * self);
 
 G_END_DECLS
diff --git a/lib/wp/proxy.c b/lib/wp/proxy.c
index e3d612a40d1cec43dc163d9326e5aaba7d7096c6..79515792823ab760de1c81b403fc315dc1760376 100644
--- a/lib/wp/proxy.c
+++ b/lib/wp/proxy.c
@@ -2,46 +2,130 @@
  *
  * Copyright © 2019 Collabora Ltd.
  *    @author Julian Bouzas <julian.bouzas@collabora.com>
+ *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
  *
  * SPDX-License-Identifier: MIT
  */
 
-#include <pipewire/pipewire.h>
-
 #include "proxy.h"
+#include "error.h"
+#include "remote-pipewire.h"
+#include "wpenums.h"
+
+#include "proxy-link.h"
+#include "proxy-node.h"
+#include "proxy-port.h"
+
+#include <pipewire/pipewire.h>
+#include <spa/debug/types.h>
 
 typedef struct _WpProxyPrivate WpProxyPrivate;
 struct _WpProxyPrivate
 {
-  /* The global id */
-  guint global_id;
+  /* properties */
+  GWeakRef remote;
+
+  guint32 global_id;
+  guint32 global_perm;
+  WpProperties *global_props;
+
+  guint32 iface_type;
+  guint32 iface_version;
 
-  /* The proxy  */
-  struct pw_proxy *proxy;
+  struct pw_proxy *pw_proxy;
+  gpointer native_info;
+  GDestroyNotify native_info_destroy;
 
   /* The proxy listener */
   struct spa_hook listener;
+
+  /* augment state */
+  WpProxyFeatures ft_ready;
+  WpProxyFeatures ft_wanted;
+  GTask *task;
 };
 
 enum {
   PROP_0,
+  PROP_REMOTE,
   PROP_GLOBAL_ID,
-  PROP_PROXY,
+  PROP_GLOBAL_PERMISSIONS,
+  PROP_GLOBAL_PROPERTIES,
+  PROP_INTERFACE_TYPE,
+  PROP_INTERFACE_NAME,
+  PROP_INTERFACE_QUARK,
+  PROP_INTERFACE_VERSION,
+  PROP_PW_PROXY,
+  PROP_NATIVE_INFO,
+  PROP_FEATURES,
 };
 
 enum
 {
-  SIGNAL_DONE,
+  SIGNAL_PW_PROXY_CREATED,
+  SIGNAL_PW_PROXY_DESTROYED,
   LAST_SIGNAL,
 };
 
 static guint wp_proxy_signals[LAST_SIGNAL] = { 0 };
 
-static void wp_proxy_async_initable_init (gpointer iface, gpointer iface_data);
+G_DEFINE_TYPE_WITH_PRIVATE (WpProxy, wp_proxy, G_TYPE_OBJECT)
+
+G_DEFINE_QUARK (core, wp_proxy_core)
+G_DEFINE_QUARK (registry, wp_proxy_registry)
+G_DEFINE_QUARK (node, wp_proxy_node)
+G_DEFINE_QUARK (port, wp_proxy_port)
+G_DEFINE_QUARK (factory, wp_proxy_factory)
+G_DEFINE_QUARK (link, wp_proxy_link)
+G_DEFINE_QUARK (client, wp_proxy_client)
+G_DEFINE_QUARK (module, wp_proxy_module)
+G_DEFINE_QUARK (device, wp_proxy_device)
+G_DEFINE_QUARK (client-node, wp_proxy_client_node)
+
+static struct {
+  /* the pipewire interface type */
+  guint32 pw_type;
+  /* the minimum interface version that the remote object must support */
+  guint32 req_version;
+  /* the _get_type() function of the subclass */
+  GType (*get_type) (void);
+  /* a function returning a quark that identifies the interface */
+  GQuark (*get_quark) (void);
+} types_assoc[] = {
+  { PW_TYPE_INTERFACE_Core, 0, wp_proxy_get_type, wp_proxy_core_quark },
+  { PW_TYPE_INTERFACE_Registry, 0, wp_proxy_get_type, wp_proxy_registry_quark },
+  { PW_TYPE_INTERFACE_Node, 0, wp_proxy_node_get_type, wp_proxy_node_quark },
+  { PW_TYPE_INTERFACE_Port, 0, wp_proxy_port_get_type, wp_proxy_port_quark },
+  { PW_TYPE_INTERFACE_Factory, 0, wp_proxy_get_type, wp_proxy_factory_quark },
+  { PW_TYPE_INTERFACE_Link, 0, wp_proxy_link_get_type, wp_proxy_link_quark },
+  { PW_TYPE_INTERFACE_Client, 0, wp_proxy_get_type, wp_proxy_client_quark },
+  { PW_TYPE_INTERFACE_Module, 0, wp_proxy_get_type, wp_proxy_module_quark },
+  { PW_TYPE_INTERFACE_Device, 0, wp_proxy_get_type, wp_proxy_device_quark },
+  { PW_TYPE_INTERFACE_ClientNode, 0, wp_proxy_get_type, wp_proxy_client_node_quark },
+};
+
+static inline GType
+wp_proxy_find_instance_type (guint32 type, guint32 version)
+{
+  for (gint i = 0; i < SPA_N_ELEMENTS (types_assoc); i++) {
+    if (types_assoc[i].pw_type == type &&
+        types_assoc[i].req_version <= version)
+      return types_assoc[i].get_type ();
+  }
 
-G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpProxy, wp_proxy, G_TYPE_OBJECT,
-    G_ADD_PRIVATE (WpProxy)
-    G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, wp_proxy_async_initable_init))
+  return WP_TYPE_PROXY;
+}
+
+static inline GQuark
+wp_proxy_find_quark_for_type (guint32 type)
+{
+  for (gint i = 0; i < SPA_N_ELEMENTS (types_assoc); i++) {
+    if (types_assoc[i].pw_type == type)
+      return types_assoc[i].get_quark ();
+  }
+
+  return 0;
+}
 
 static void
 proxy_event_destroy (void *data)
@@ -49,34 +133,46 @@ proxy_event_destroy (void *data)
   WpProxy *self = WP_PROXY (data);
   WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
 
-  /* Set the proxy to NULL */
-  priv->proxy = NULL;
+  priv->pw_proxy = NULL;
 
-  /* Call the destroy method */
-  if (WP_PROXY_GET_CLASS (self)->destroy)
-    WP_PROXY_GET_CLASS (self)->destroy (self);
-}
+  g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED], 0);
 
-static void
-proxy_event_done (void *data, int seq)
-{
-  /* Emit the done signal */
-  g_signal_emit (data, wp_proxy_signals[SIGNAL_DONE], 0);
+  /* Return error if the pw_proxy destruction happened while the async
+   * init or augment of this proxy object was in progress */
+  if (priv->task) {
+    g_task_return_new_error (priv->task, WP_DOMAIN_LIBRARY,
+        WP_LIBRARY_ERROR_OPERATION_FAILED,
+        "pipewire node proxy destroyed before finishing");
+    g_clear_object (&priv->task);
+  }
 }
 
 static const struct pw_proxy_events proxy_events = {
   PW_VERSION_PROXY_EVENTS,
   .destroy = proxy_event_destroy,
-  .done = proxy_event_done,
 };
 
+static void
+wp_proxy_init (WpProxy * self)
+{
+  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
+  g_weak_ref_init (&priv->remote, NULL);
+}
+
 static void
 wp_proxy_constructed (GObject * object)
 {
-  WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
+  WpProxy *self = WP_PROXY (object);
+  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
+
+  /* native proxy was passed in the constructor, declare it as ready */
+  if (priv->pw_proxy) {
+    priv->ft_ready |= WP_PROXY_FEATURE_PW_PROXY;
 
-  /* Add the event listener */
-  pw_proxy_add_listener (priv->proxy, &priv->listener, &proxy_events, object);
+    /* and inform the subclasses */
+    g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_CREATED], 0,
+        priv->pw_proxy);
+  }
 }
 
 static void
@@ -84,14 +180,14 @@ wp_proxy_finalize (GObject * object)
 {
   WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
 
-  g_debug ("%s:%p destroyed (pw proxy %p)", G_OBJECT_TYPE_NAME (object),
-      object, priv->proxy);
+  g_debug ("%s:%p destroyed (global %u; pw_proxy %p)", G_OBJECT_TYPE_NAME (object),
+      object, priv->global_id, priv->pw_proxy);
 
-  /* Destroy the proxy */
-  if (priv->proxy) {
-    pw_proxy_destroy (priv->proxy);
-    priv->proxy = NULL;
-  }
+  g_clear_object (&priv->task);
+  g_clear_pointer (&priv->global_props, wp_properties_unref);
+  g_clear_pointer (&priv->native_info, priv->native_info_destroy);
+  g_clear_pointer (&priv->pw_proxy, pw_proxy_destroy);
+  g_weak_ref_clear (&priv->remote);
 
   G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object);
 }
@@ -103,11 +199,29 @@ wp_proxy_set_property (GObject * object, guint property_id,
   WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
 
   switch (property_id) {
+  case PROP_REMOTE:
+    g_weak_ref_set (&priv->remote, g_value_get_object (value));
+    break;
   case PROP_GLOBAL_ID:
     priv->global_id = g_value_get_uint (value);
     break;
-  case PROP_PROXY:
-    priv->proxy = g_value_get_pointer (value);
+  case PROP_GLOBAL_PERMISSIONS:
+    priv->global_perm = g_value_get_uint (value);
+    break;
+  case PROP_GLOBAL_PROPERTIES:
+    priv->global_props = g_value_dup_boxed (value);
+    break;
+  case PROP_INTERFACE_TYPE:
+    priv->iface_type = g_value_get_uint (value);
+    break;
+  case PROP_INTERFACE_VERSION:
+    priv->iface_version = g_value_get_uint (value);
+    break;
+  case PROP_PW_PROXY:
+    priv->pw_proxy = g_value_get_pointer (value);
+    break;
+  case PROP_NATIVE_INFO:
+    priv->native_info = g_value_get_pointer (value);
     break;
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -122,11 +236,39 @@ wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
   WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
 
   switch (property_id) {
+  case PROP_REMOTE:
+    g_value_take_object (value, g_weak_ref_get (&priv->remote));
+    break;
   case PROP_GLOBAL_ID:
     g_value_set_uint (value, priv->global_id);
     break;
-  case PROP_PROXY:
-    g_value_set_pointer (value, priv->proxy);
+  case PROP_GLOBAL_PERMISSIONS:
+    g_value_set_uint (value, priv->global_perm);
+    break;
+  case PROP_GLOBAL_PROPERTIES:
+    g_value_set_boxed (value, priv->global_props);
+    break;
+  case PROP_INTERFACE_TYPE:
+    g_value_set_uint (value, priv->iface_type);
+    break;
+  case PROP_INTERFACE_NAME:
+    g_value_set_static_string (value,
+        spa_debug_type_find_name (pw_type_info(), priv->iface_type));
+    break;
+  case PROP_INTERFACE_QUARK:
+    g_value_set_uint (value, wp_proxy_find_quark_for_type (priv->iface_type));
+    break;
+  case PROP_INTERFACE_VERSION:
+    g_value_set_uint (value, priv->iface_version);
+    break;
+  case PROP_PW_PROXY:
+    g_value_set_pointer (value, priv->pw_proxy);
+    break;
+  case PROP_NATIVE_INFO:
+    g_value_set_pointer (value, priv->native_info);
+    break;
+  case PROP_FEATURES:
+    g_value_set_flags (value, priv->ft_ready);
     break;
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -134,27 +276,29 @@ wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
   }
 }
 
-static gboolean
-wp_proxy_init_finish (GAsyncInitable *initable, GAsyncResult *result,
-    GError **error)
-{
-  g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
-
-  return g_task_propagate_boolean (G_TASK (result), error);
-}
-
 static void
-wp_proxy_async_initable_init (gpointer iface, gpointer iface_data)
+wp_proxy_default_augment (WpProxy * self, WpProxyFeatures features)
 {
-  GAsyncInitableIface *ai_iface = iface;
-
-  /* The init_async must be implemented in the derived classes */
-  ai_iface->init_finish = wp_proxy_init_finish;
-}
+  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
 
-static void
-wp_proxy_init (WpProxy * self)
-{
+  /* ensure we have a pw_proxy, as we can't have
+   * any other feature without first having that */
+  if (!priv->pw_proxy && features != 0)
+    features |= WP_PROXY_FEATURE_PW_PROXY;
+
+  /* if we don't have a pw_proxy, we have to assume that this WpProxy
+   * represents a global object from the registry; we have no other way
+   * to get a pw_proxy */
+  if (features & WP_PROXY_FEATURE_PW_PROXY) {
+    if (!wp_proxy_is_global (self)) {
+      wp_proxy_augment_error (self, g_error_new (WP_DOMAIN_LIBRARY,
+            WP_LIBRARY_ERROR_INVALID_ARGUMENT,
+            "No global id specified; cannot bind pw_proxy"));
+      return;
+    }
+
+    wp_proxy_bind_global (self);
+  }
 }
 
 static void
@@ -167,22 +311,263 @@ wp_proxy_class_init (WpProxyClass * klass)
   object_class->get_property = wp_proxy_get_property;
   object_class->set_property = wp_proxy_set_property;
 
+  klass->augment = wp_proxy_default_augment;
+
   /* Install the properties */
+
+  g_object_class_install_property (object_class, PROP_REMOTE,
+      g_param_spec_object ("remote", "remote",
+          "The pipewire connection WpRemote", WP_TYPE_REMOTE,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_property (object_class, PROP_GLOBAL_ID,
-      g_param_spec_uint ("global-id", "global-id", "The pipewire global id",
-      0, G_MAXUINT, 0,
-      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-  g_object_class_install_property (object_class, PROP_PROXY,
-      g_param_spec_pointer ("pw-proxy", "pw-proxy", "The pipewire proxy",
-      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+      g_param_spec_uint ("global-id", "global-id",
+          "The pipewire global id", 0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_GLOBAL_PERMISSIONS,
+      g_param_spec_uint ("global-permissions", "global-permissions",
+          "The pipewire global permissions", 0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES,
+      g_param_spec_boxed ("global-properties", "global-properties",
+          "The pipewire global properties", WP_TYPE_PROPERTIES,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_INTERFACE_TYPE,
+      g_param_spec_uint ("interface-type", "interface-type",
+          "The pipewire interface type", 0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_INTERFACE_NAME,
+      g_param_spec_string ("interface-name", "interface-name",
+          "The name of the pipewire interface", NULL,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_INTERFACE_QUARK,
+      g_param_spec_uint ("interface-quark", "interface-quark",
+          "A quark identifying the pipewire interface", 0, G_MAXUINT, 0,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_INTERFACE_VERSION,
+      g_param_spec_uint ("interface-version", "interface-version",
+          "The pipewire interface version", 0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_PW_PROXY,
+      g_param_spec_pointer ("pw-proxy", "pw-proxy", "The struct pw_proxy *",
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_NATIVE_INFO,
+      g_param_spec_pointer ("native-info", "native-info",
+          "The struct pw_XXXX_info *",
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class, PROP_FEATURES,
+      g_param_spec_flags ("features", "features",
+          "The ready WpProxyFeatures on this proxy", WP_TYPE_PROXY_FEATURES, 0,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   /* Signals */
-  wp_proxy_signals[SIGNAL_DONE] =
-    g_signal_new ("done", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
-    G_STRUCT_OFFSET (WpProxyClass, done), NULL, NULL, NULL, G_TYPE_NONE, 0);
+  wp_proxy_signals[SIGNAL_PW_PROXY_CREATED] = g_signal_new (
+      "pw-proxy-created", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
+      G_STRUCT_OFFSET (WpProxyClass, pw_proxy_created), NULL, NULL, NULL,
+      G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+  wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED] = g_signal_new (
+      "pw-proxy-destroyed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
+      G_STRUCT_OFFSET (WpProxyClass, pw_proxy_destroyed), NULL, NULL, NULL,
+      G_TYPE_NONE, 0);
 }
 
-guint
+WpProxy *
+wp_proxy_new_global (WpRemote * remote,
+    guint32 id, guint32 permissions, WpProperties * properties,
+    guint32 type, guint32 version)
+{
+  GType gtype = wp_proxy_find_instance_type (type, version);
+  return g_object_new (gtype,
+      "remote", remote,
+      "global-id", id,
+      "global-permissions", permissions,
+      "global-properties", properties,
+      "interface-type", type,
+      "interface-version", version,
+      NULL);
+}
+
+WpProxy *
+wp_proxy_new_wrap (WpRemote * remote,
+    struct pw_proxy * proxy, guint32 type, guint32 version)
+{
+  GType gtype = wp_proxy_find_instance_type (type, version);
+  return g_object_new (gtype,
+      "remote", remote,
+      "pw-proxy", proxy,
+      "interface-type", type,
+      "interface-version", version,
+      NULL);
+}
+
+void
+wp_proxy_augment (WpProxy * self,
+    WpProxyFeatures ft_wanted, GCancellable * cancellable,
+    GAsyncReadyCallback callback, gpointer user_data)
+{
+  WpProxyPrivate *priv;
+  WpProxyFeatures missing = 0;
+
+  g_return_if_fail (WP_IS_PROXY (self));
+  g_return_if_fail (WP_PROXY_GET_CLASS (self)->augment);
+
+  priv = wp_proxy_get_instance_private (self);
+  priv->task = g_task_new (self, cancellable, callback, user_data);
+
+  /* we don't simply assign here, we keep all the previous wanted features;
+   * it is not allowed to remove features */
+  priv->ft_wanted |= ft_wanted;
+
+  /* find which features are wanted but missing from the "ready" set */
+  missing = (priv->ft_ready ^ priv->ft_wanted) & priv->ft_wanted;
+
+  /* if the features are not ready, call augment(),
+   * otherwise signal the callback directly */
+  if (missing != 0) {
+    WP_PROXY_GET_CLASS (self)->augment (self, missing);
+  } else {
+    g_task_return_boolean (priv->task, TRUE);
+    g_clear_object (&priv->task);
+  }
+}
+
+gboolean
+wp_proxy_augment_finish (WpProxy * self, GAsyncResult * res,
+    GError ** error)
+{
+  g_return_val_if_fail (WP_IS_PROXY (self), FALSE);
+  g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+void
+wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature)
+{
+  WpProxyPrivate *priv;
+
+  g_return_if_fail (WP_IS_PROXY (self));
+
+  priv = wp_proxy_get_instance_private (self);
+
+  /* feature already marked as ready */
+  if (priv->ft_ready & feature)
+    return;
+
+  priv->ft_ready |= feature;
+
+  g_object_notify (G_OBJECT (self), "features");
+
+  /* return from the task if all the wanted features are now ready */
+  if (priv->task &&
+      (priv->ft_ready & priv->ft_wanted) == priv->ft_wanted) {
+    g_task_return_boolean (priv->task, TRUE);
+    g_clear_object (&priv->task);
+  }
+}
+
+/**
+ * wp_proxy_augment_error:
+ * @self: the proxy
+ * @error: (transfer full): the error
+ *
+ * Reports an error that occured during the augment process
+ */
+void
+wp_proxy_augment_error (WpProxy * self, GError * error)
+{
+  WpProxyPrivate *priv;
+
+  g_return_if_fail (WP_IS_PROXY (self));
+
+  priv = wp_proxy_get_instance_private (self);
+
+  if (priv->task)
+    g_task_return_error (priv->task, error);
+  else
+    g_error_free (error);
+
+  g_clear_object (&priv->task);
+}
+
+gboolean
+wp_proxy_bind_global (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+  g_autoptr (WpRemote) remote = NULL;
+
+  g_return_val_if_fail (wp_proxy_is_global (self), FALSE);
+
+  priv = wp_proxy_get_instance_private (self);
+
+  if (priv->pw_proxy)
+    return FALSE;
+
+  remote = g_weak_ref_get (&priv->remote);
+  g_return_val_if_fail (remote != NULL, FALSE);
+
+  /* bind */
+  priv->pw_proxy  = wp_remote_pipewire_proxy_bind (
+      WP_REMOTE_PIPEWIRE (remote),
+      priv->global_id, priv->iface_type);
+  pw_proxy_add_listener (priv->pw_proxy, &priv->listener, &proxy_events,
+      self);
+
+  /* inform subclasses and listeners */
+  g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_CREATED], 0,
+      priv->pw_proxy);
+
+  /* declare the feature as ready */
+  wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_PW_PROXY);
+
+  return TRUE;
+}
+
+WpProxyFeatures
+wp_proxy_get_features (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), 0);
+
+  priv = wp_proxy_get_instance_private (self);
+  return priv->ft_ready;
+}
+
+/**
+ * wp_proxy_get_remote:
+ * @self: the proxy
+ *
+ * Returns: (transfer full): the remote that created this proxy
+ */
+WpRemote *
+wp_proxy_get_remote (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), NULL);
+
+  priv = wp_proxy_get_instance_private (self);
+  return g_weak_ref_get (&priv->remote);
+}
+
+gboolean
+wp_proxy_is_global (WpProxy * self)
+{
+  return wp_proxy_get_global_id (self) != 0;
+}
+
+guint32
 wp_proxy_get_global_id (WpProxy * self)
 {
   WpProxyPrivate *priv;
@@ -193,7 +578,78 @@ wp_proxy_get_global_id (WpProxy * self)
   return priv->global_id;
 }
 
-gpointer
+guint32
+wp_proxy_get_global_permissions (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), 0);
+
+  priv = wp_proxy_get_instance_private (self);
+  return priv->global_perm;
+}
+
+/**
+ * wp_proxy_get_global_properties:
+ *
+ * Returns: (transfer full): the global properties of the proxy
+ */
+WpProperties *
+wp_proxy_get_global_properties (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), NULL);
+
+  priv = wp_proxy_get_instance_private (self);
+  return priv->global_props ? wp_properties_ref (priv->global_props) : NULL;
+}
+
+guint32
+wp_proxy_get_interface_type (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), 0);
+
+  priv = wp_proxy_get_instance_private (self);
+  return priv->iface_type;
+}
+
+const gchar *
+wp_proxy_get_interface_name (WpProxy * self)
+{
+  const gchar *name = NULL;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), NULL);
+
+  g_object_get (self, "interface-name", &name, NULL);
+  return name;
+}
+
+GQuark
+wp_proxy_get_interface_quark (WpProxy * self)
+{
+  GQuark q = 0;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), 0);
+
+  g_object_get (self, "interface-quark", &q, NULL);
+  return q;
+}
+
+guint32
+wp_proxy_get_interface_version (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), 0);
+
+  priv = wp_proxy_get_instance_private (self);
+  return priv->iface_version;
+}
+
+struct pw_proxy *
 wp_proxy_get_pw_proxy (WpProxy * self)
 {
   WpProxyPrivate *priv;
@@ -201,17 +657,39 @@ wp_proxy_get_pw_proxy (WpProxy * self)
   g_return_val_if_fail (WP_IS_PROXY (self), NULL);
 
   priv = wp_proxy_get_instance_private (self);
-  return priv->proxy;
+  return priv->pw_proxy;
+}
+
+gconstpointer
+wp_proxy_get_native_info (WpProxy * self)
+{
+  WpProxyPrivate *priv;
+
+  g_return_val_if_fail (WP_IS_PROXY (self), NULL);
+
+  priv = wp_proxy_get_instance_private (self);
+  return priv->native_info;
 }
 
-void wp_proxy_sync (WpProxy * self)
+void
+wp_proxy_update_native_info (WpProxy * self, gconstpointer info,
+    WpProxyNativeInfoUpdate update, GDestroyNotify destroy)
 {
   WpProxyPrivate *priv;
 
   g_return_if_fail (WP_IS_PROXY (self));
+  g_return_if_fail (destroy != NULL);
 
   priv = wp_proxy_get_instance_private (self);
 
-  /* Trigger the done callback */
-  pw_proxy_sync(priv->proxy, 0);
+  if (update) {
+    priv->native_info = update (priv->native_info, info);
+  } else {
+    g_clear_pointer (&priv->native_info, priv->native_info_destroy);
+    priv->native_info = (gpointer) info;
+  }
+
+  priv->native_info_destroy = destroy;
+
+  g_object_notify (G_OBJECT (self), "native-info");
 }
diff --git a/lib/wp/proxy.h b/lib/wp/proxy.h
index 528898746cd919e78a9c6daf915431c9ff64cbc3..9114858b999b4bebb71b6db3f7d1cfab376efda0 100644
--- a/lib/wp/proxy.h
+++ b/lib/wp/proxy.h
@@ -2,6 +2,7 @@
  *
  * Copyright © 2019 Collabora Ltd.
  *    @author Julian Bouzas <julian.bouzas@collabora.com>
+ *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
  *
  * SPDX-License-Identifier: MIT
  */
@@ -11,10 +12,20 @@
 
 #include <gio/gio.h>
 
-#include "core.h"
+#include "remote.h"
+#include "properties.h"
 
 G_BEGIN_DECLS
 
+struct pw_proxy;
+
+typedef enum { /*< flags >*/
+  WP_PROXY_FEATURE_PW_PROXY = (1 << 0),
+  WP_PROXY_FEATURE_INFO         = (1 << 1),
+
+  WP_PROXY_FEATURE_LAST         = (1 << 5), /*< skip >*/
+} WpProxyFeatures;
+
 #define WP_TYPE_PROXY (wp_proxy_get_type ())
 G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, GObject)
 
@@ -23,16 +34,53 @@ struct _WpProxyClass
 {
   GObjectClass parent_class;
 
-  /* Methods */
-  void (*destroy) (WpProxy * self);
+  void (*augment) (WpProxy *self, WpProxyFeatures features);
 
-  /* Signals */
-  void (*done)(WpProxy *wp_proxy, gpointer data);
+  void (*pw_proxy_created) (WpProxy * self, struct pw_proxy * proxy);
+  void (*pw_proxy_destroyed) (WpProxy * self);
 };
 
-guint wp_proxy_get_global_id (WpProxy * self);
-gpointer wp_proxy_get_pw_proxy (WpProxy * self);
-void wp_proxy_sync (WpProxy * self);
+WpProxy * wp_proxy_new_global (WpRemote * remote,
+    guint32 id, guint32 permissions, WpProperties * properties,
+    guint32 type, guint32 version);
+WpProxy * wp_proxy_new_wrap (WpRemote * remote,
+    struct pw_proxy * proxy, guint32 type, guint32 version);
+
+void wp_proxy_augment (WpProxy *self,
+    WpProxyFeatures wanted_features, GCancellable * cancellable,
+    GAsyncReadyCallback callback, gpointer user_data);
+gboolean wp_proxy_augment_finish (WpProxy * self, GAsyncResult * res,
+    GError ** error);
+
+WpProxyFeatures wp_proxy_get_features (WpProxy * self);
+
+WpRemote * wp_proxy_get_remote (WpProxy * self);
+
+gboolean wp_proxy_is_global (WpProxy * self);
+guint32 wp_proxy_get_global_id (WpProxy * self);
+guint32 wp_proxy_get_global_permissions (WpProxy * self);
+WpProperties * wp_proxy_get_global_properties (WpProxy * self);
+
+guint32 wp_proxy_get_interface_type (WpProxy * self);
+const gchar * wp_proxy_get_interface_name (WpProxy * self);
+GQuark wp_proxy_get_interface_quark (WpProxy * self);
+guint32 wp_proxy_get_interface_version (WpProxy * self);
+
+struct pw_proxy * wp_proxy_get_pw_proxy (WpProxy * self);
+gconstpointer wp_proxy_get_native_info (WpProxy * self);
+
+/* for subclasses only */
+
+typedef gpointer (*WpProxyNativeInfoUpdate) (gpointer old_info,
+    gconstpointer new_info);
+
+void wp_proxy_update_native_info (WpProxy * self, gconstpointer info,
+    WpProxyNativeInfoUpdate update, GDestroyNotify destroy);
+
+void wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature);
+void wp_proxy_augment_error (WpProxy * self, GError * error);
+
+gboolean wp_proxy_bind_global (WpProxy * self);
 
 G_END_DECLS
 
diff --git a/lib/wp/remote-pipewire.c b/lib/wp/remote-pipewire.c
index 64fcd3e52276bcd736cc97d282a5d1c1be983662..45f0759fb29aed1d60dcde218f0a5d3fed965deb 100644
--- a/lib/wp/remote-pipewire.c
+++ b/lib/wp/remote-pipewire.c
@@ -7,6 +7,7 @@
  */
 
 #include "remote-pipewire.h"
+
 #include <pipewire/pipewire.h>
 
 /*
@@ -15,15 +16,6 @@
 
 #define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)
 
-G_DEFINE_QUARK (node, signal_detail_node)
-G_DEFINE_QUARK (port, signal_detail_port)
-G_DEFINE_QUARK (factory, signal_detail_factory)
-G_DEFINE_QUARK (link, signal_detail_link)
-G_DEFINE_QUARK (client, signal_detail_client)
-G_DEFINE_QUARK (module, signal_detail_module)
-G_DEFINE_QUARK (device, signal_detail_device)
-G_DEFINE_QUARK (endpoint, signal_detail_endpoint)
-
 typedef struct _WpLoopSource WpLoopSource;
 struct _WpLoopSource
 {
@@ -90,6 +82,10 @@ struct _WpRemotePipewire
   struct spa_hook registry_listener;
 
   GMainContext *context;
+
+  GHashTable *proxies;
+  GHashTable *default_features;
+  GQueue created_obj_proxies;
 };
 
 enum {
@@ -113,45 +109,53 @@ static guint signals[LAST_SIGNAL] = { 0 };
 G_DEFINE_TYPE (WpRemotePipewire, wp_remote_pipewire, WP_TYPE_REMOTE)
 
 static void
-registry_global(void *data, uint32_t id,
-    uint32_t permissions, uint32_t type, uint32_t version,
-    const struct spa_dict *props)
+on_proxy_ready (GObject * obj, GAsyncResult * res, gpointer data)
 {
-  GQuark detail = 0;
+  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (data);
+  WpProxy *proxy = WP_PROXY (obj);
+  g_autoptr (GError) error = NULL;
 
-  switch (type) {
-  case PW_TYPE_INTERFACE_Node:
-    detail = signal_detail_node_quark ();
-    break;
-  case PW_TYPE_INTERFACE_Port:
-    detail = signal_detail_port_quark ();
-    break;
-  case PW_TYPE_INTERFACE_Factory:
-    detail = signal_detail_factory_quark ();
-    break;
-  case PW_TYPE_INTERFACE_Link:
-    detail = signal_detail_link_quark ();
-    break;
-  case PW_TYPE_INTERFACE_Client:
-    detail = signal_detail_client_quark ();
-    break;
-  case PW_TYPE_INTERFACE_Module:
-    detail = signal_detail_module_quark ();
-    break;
-  case PW_TYPE_INTERFACE_Device:
-    detail = signal_detail_device_quark ();
-    break;
-  default:
-    break;
+  if (!wp_proxy_augment_finish (proxy, res, &error)) {
+    g_warning ("Failed to augment WpProxy (%p): %s", obj, error->message);
   }
 
-  g_signal_emit (data, signals[SIGNAL_GLOBAL_ADDED], detail, id, props);
+  g_signal_emit (self, signals[SIGNAL_GLOBAL_ADDED],
+      wp_proxy_get_interface_quark (proxy), proxy);
+}
+
+static void
+registry_global(void *data, uint32_t id, uint32_t permissions,
+    uint32_t type, uint32_t version, const struct spa_dict *props)
+{
+  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (data);
+  WpProxy *proxy;
+  WpProxyFeatures features;
+  g_autoptr (WpProperties) properties = wp_properties_new_copy_dict (props);
+
+  /* construct & store WpProxy */
+  proxy = wp_proxy_new_global (WP_REMOTE (self), id, permissions, properties,
+      type, version);
+  g_hash_table_insert (self->proxies, GUINT_TO_POINTER (id), proxy);
+
+  g_debug ("registry global:%u perm:0x%x type:%u/%u -> WpProxy:%p",
+      id, permissions, type, version, proxy);
+
+  /* augment */
+  features = GPOINTER_TO_UINT (g_hash_table_lookup (self->default_features,
+          GUINT_TO_POINTER (G_TYPE_FROM_INSTANCE (proxy))));
+  wp_proxy_augment (proxy, features, NULL, on_proxy_ready, self);
 }
 
 static void
 registry_global_remove (void *data, uint32_t id)
 {
-  g_signal_emit (data, signals[SIGNAL_GLOBAL_REMOVED], 0, id);
+  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (data);
+  g_autoptr (WpProxy) proxy = NULL;
+
+  if (g_hash_table_steal_extended (self->proxies, GUINT_TO_POINTER (id), NULL,
+          (gpointer *) &proxy))
+    g_signal_emit (data, signals[SIGNAL_GLOBAL_REMOVED],
+        wp_proxy_get_interface_quark (proxy), proxy);
 }
 
 static const struct pw_registry_proxy_events registry_proxy_events = {
@@ -198,6 +202,9 @@ static const struct pw_remote_events remote_events = {
 static void
 wp_remote_pipewire_init (WpRemotePipewire *self)
 {
+  self->proxies = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+      g_object_unref);
+  self->default_features = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 static void
@@ -223,6 +230,8 @@ wp_remote_pipewire_finalize (GObject *object)
 {
   WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (object);
 
+  g_clear_pointer (&self->proxies, g_hash_table_unref);
+  g_clear_pointer (&self->default_features, g_hash_table_unref);
   pw_remote_destroy (self->remote);
   pw_core_destroy (self->core);
   g_clear_pointer (&self->context, g_main_context_unref);
@@ -336,10 +345,10 @@ wp_remote_pipewire_class_init (WpRemotePipewireClass *klass)
   /* Signals */
   signals[SIGNAL_GLOBAL_ADDED] = g_signal_new ("global-added",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
-      0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER);
+      0, NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_PROXY);
   signals[SIGNAL_GLOBAL_REMOVED] = g_signal_new ("global-removed",
-      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
-      0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
+      G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
+      0, NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_PROXY);
 }
 
 WpRemote *
@@ -359,6 +368,33 @@ wp_remote_pipewire_new (WpCore *core, GMainContext *context)
   return remote;
 }
 
+void
+wp_remote_pipewire_set_default_features (WpRemotePipewire * self,
+    GType proxy_type, WpProxyFeatures features)
+{
+  g_return_if_fail (WP_IS_REMOTE_PIPEWIRE (self));
+
+  g_hash_table_insert (self->default_features, GUINT_TO_POINTER (proxy_type),
+      GUINT_TO_POINTER (features));
+}
+
+WpProxy *
+wp_remote_pipewire_create_object (WpRemotePipewire *self,
+    const gchar *factory_name, guint32 interface_type,
+    guint32 interface_version, WpProperties * properties)
+{
+  struct pw_proxy *pw_proxy;
+
+  g_return_val_if_fail (WP_IS_REMOTE_PIPEWIRE (self), NULL);
+  g_return_val_if_fail (self->core_proxy, NULL);
+
+  pw_proxy = pw_core_proxy_create_object (self->core_proxy, factory_name,
+      interface_type, interface_version, wp_properties_peek_dict (properties),
+      0);
+  return wp_proxy_new_wrap (WP_REMOTE (self), pw_proxy, interface_type,
+      interface_version);
+}
+
 gpointer
 wp_remote_pipewire_proxy_bind (WpRemotePipewire *self, guint global_id,
     guint global_type)
@@ -380,17 +416,6 @@ wp_remote_pipewire_find_factory (WpRemotePipewire *self,
   return pw_core_find_factory(self->core, factory_name);
 }
 
-gpointer
-wp_remote_pipewire_create_object (WpRemotePipewire *self,
-    const char *factory_name, guint global_type, gconstpointer props)
-{
-  g_return_val_if_fail (WP_IS_REMOTE_PIPEWIRE(self), NULL);
-  g_return_val_if_fail (self->core_proxy, NULL);
-
-  return pw_core_proxy_create_object (self->core_proxy, factory_name,
-      global_type, 0, props, 0);
-}
-
 void
 wp_remote_pipewire_add_spa_lib (WpRemotePipewire *self,
     const char *factory_regexp, const char *lib)
diff --git a/lib/wp/remote-pipewire.h b/lib/wp/remote-pipewire.h
index 42d430aeeb30034f4961e5e1913fbae3d5655be4..31fe45915ee61876623e1a554c51220ac1714087 100644
--- a/lib/wp/remote-pipewire.h
+++ b/lib/wp/remote-pipewire.h
@@ -10,6 +10,7 @@
 #define __WIREPLUMBER_REMOTE_PIPEWIRE_H__
 
 #include "remote.h"
+#include "proxy.h"
 
 G_BEGIN_DECLS
 
@@ -19,12 +20,17 @@ G_DECLARE_FINAL_TYPE (WpRemotePipewire, wp_remote_pipewire,
 
 WpRemote *wp_remote_pipewire_new (WpCore *core, GMainContext *context);
 
+void wp_remote_pipewire_set_default_features (
+    WpRemotePipewire * self, GType proxy_type, WpProxyFeatures features);
+
+WpProxy * wp_remote_pipewire_create_object (WpRemotePipewire *self,
+    const gchar *factory_name, guint32 interface_type,
+    guint32 interface_version, WpProperties * properties);
+
 gpointer wp_remote_pipewire_proxy_bind (WpRemotePipewire *self, guint global_id,
     guint global_type);
 gpointer wp_remote_pipewire_find_factory (WpRemotePipewire *self,
     const char *factory_name);
-gpointer wp_remote_pipewire_create_object (WpRemotePipewire *self,
-    const char *factory_name, guint global_type, gconstpointer props);
 void wp_remote_pipewire_add_spa_lib (WpRemotePipewire *self,
     const char *factory_regexp, const char *lib);
 gpointer wp_remote_pipewire_load_spa_handle(WpRemotePipewire *self,