From 0045fe03c9b53c5919bfc744e64ce6287750073e Mon Sep 17 00:00:00 2001
From: Julian Bouzas <julian.bouzas@collabora.com>
Date: Thu, 16 Apr 2020 15:18:53 -0400
Subject: [PATCH] proxy: add set and get control vmethods, and remove them from
 sub-classes

---
 lib/wp/endpoint-stream.c | 130 +++------------------------------------
 lib/wp/endpoint-stream.h |  25 --------
 lib/wp/endpoint.c        | 127 +++-----------------------------------
 lib/wp/endpoint.h        |  19 +-----
 lib/wp/policy.c          |   2 +-
 lib/wp/private.h         |   2 +
 lib/wp/proxy.c           | 103 +++++++++++++++++++++++++++++++
 lib/wp/proxy.h           |  12 ++++
 lib/wp/session.c         | 112 +++++++++++----------------------
 lib/wp/session.h         |  16 ++---
 tests/wp/endpoint.c      |   6 +-
 tests/wp/session.c       |   3 +-
 tools/wireplumber-cli.c  |  14 ++---
 13 files changed, 193 insertions(+), 378 deletions(-)

diff --git a/lib/wp/endpoint-stream.c b/lib/wp/endpoint-stream.c
index 72c0abea..9fb729df 100644
--- a/lib/wp/endpoint-stream.c
+++ b/lib/wp/endpoint-stream.c
@@ -32,12 +32,6 @@
 #include <spa/pod/parser.h>
 #include <spa/pod/filter.h>
 
-enum {
-  SIGNAL_CONTROL_CHANGED,
-  N_SIGNALS,
-};
-
-static guint32 signals[N_SIGNALS] = {0};
 
 /* WpEndpointStream */
 
@@ -45,7 +39,6 @@ typedef struct _WpEndpointStreamPrivate WpEndpointStreamPrivate;
 struct _WpEndpointStreamPrivate
 {
   WpProperties *properties;
-  WpSpaProps spa_props;
   struct pw_endpoint_stream_info *info;
   struct pw_endpoint_stream *iface;
   struct spa_hook listener;
@@ -66,7 +59,6 @@ wp_endpoint_stream_finalize (GObject * object)
 
   g_clear_pointer (&priv->properties, wp_properties_unref);
   g_clear_pointer (&priv->info, pw_endpoint_stream_info_free);
-  wp_spa_props_clear (&priv->spa_props);
 
   G_OBJECT_CLASS (wp_endpoint_stream_parent_class)->finalize (object);
 }
@@ -77,7 +69,7 @@ wp_endpoint_stream_augment (WpProxy * proxy, WpProxyFeatures features)
   /* call the parent impl first to ensure we have a pw proxy if necessary */
   WP_PROXY_CLASS (wp_endpoint_stream_parent_class)->augment (proxy, features);
 
-  if (features & WP_ENDPOINT_STREAM_FEATURE_CONTROLS) {
+  if (features & WP_PROXY_FEATURE_CONTROLS) {
     struct pw_endpoint_stream *pw_proxy = NULL;
     uint32_t ids[] = { SPA_PARAM_Props };
 
@@ -191,33 +183,6 @@ wp_endpoint_stream_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy
       &endpoint_stream_events, self);
 }
 
-static void
-wp_endpoint_stream_param (WpProxy * proxy, gint seq, const gchar * id_name,
-    guint32 index, guint32 next, const WpSpaPod *param)
-{
-  WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy);
-  WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self);
-  g_autoptr (GPtrArray) changed_ids = NULL;
-  const gchar * prop_id;
-
-  if (g_strcmp0 ("PropInfo", id_name) == 0) {
-    wp_spa_props_register_from_prop_info (&priv->spa_props, param);
-  }
-
-  else if (g_strcmp0 ("Props", id_name) == 0) {
-    changed_ids = g_ptr_array_new_with_free_func (g_free);
-    wp_spa_props_store_from_props (&priv->spa_props, param, changed_ids);
-
-    for (guint i = 0; i < changed_ids->len; i++) {
-      prop_id = g_ptr_array_index (changed_ids, i);
-      g_signal_emit (self, signals[SIGNAL_CONTROL_CHANGED], 0, prop_id);
-    }
-
-    wp_proxy_set_feature_ready (WP_PROXY (self),
-        WP_ENDPOINT_STREAM_FEATURE_CONTROLS);
-  }
-}
-
 static const gchar *
 get_name (WpEndpointStream * self)
 {
@@ -225,30 +190,6 @@ get_name (WpEndpointStream * self)
   return priv->info->name;
 }
 
-WpSpaPod *
-get_control (WpEndpointStream * self, const gchar * id_name)
-{
-  WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self);
-  return wp_spa_props_get_stored (&priv->spa_props, id_name);
-}
-
-static gboolean
-set_control (WpEndpointStream * self, const gchar * id_name,
-    const WpSpaPod * pod)
-{
-  g_autoptr (WpSpaPod) param = wp_spa_pod_new_object (
-      "Props", "Props",
-      id_name, "P", pod,
-      NULL);
-
-  /* our spa_props will be updated by the param event */
-
-  WP_PROXY_GET_CLASS (self)->set_param (WP_PROXY (self), SPA_PARAM_Props, 0,
-      param);
-
-  return TRUE;
-}
-
 static void
 wp_endpoint_stream_class_init (WpEndpointStreamClass * klass)
 {
@@ -268,22 +209,8 @@ wp_endpoint_stream_class_init (WpEndpointStreamClass * klass)
   proxy_class->set_param = wp_endpoint_stream_set_param;
 
   proxy_class->pw_proxy_created = wp_endpoint_stream_pw_proxy_created;
-  proxy_class->param = wp_endpoint_stream_param;
 
   klass->get_name = get_name;
-  klass->get_control = get_control;
-  klass->set_control = set_control;
-
-  /**
-   * WpEndpointStream::control-changed:
-   * @self: the endpoint stream
-   * @control: the control that changed (a #WpEndpointControl)
-   *
-   * Emitted when an endpoint stream control changes value
-   */
-  signals[SIGNAL_CONTROL_CHANGED] = g_signal_new (
-      "control-changed", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
 }
 
 /**
@@ -301,43 +228,6 @@ wp_endpoint_stream_get_name (WpEndpointStream * self)
   return WP_ENDPOINT_STREAM_GET_CLASS (self)->get_name (self);
 }
 
-/**
- * wp_endpoint_stream_get_control:
- * @self: the endpoint stream
- * @id_name: the control id (a #WpEndpointControl)
- *
- * Returns: (transfer full) (nullable): the `spa_pod` containing the value
- *   of this control, or %NULL if @control_id does not exist on this endpoint
- *   stream
- */
-WpSpaPod *
-wp_endpoint_stream_get_control (WpEndpointStream * self, const gchar * id_name)
-{
-  g_return_val_if_fail (WP_IS_ENDPOINT_STREAM (self), NULL);
-  g_return_val_if_fail (WP_ENDPOINT_STREAM_GET_CLASS (self)->get_control, NULL);
-
-  return WP_ENDPOINT_STREAM_GET_CLASS (self)->get_control (self, id_name);
-}
-
-/**
- * wp_endpoint_stream_set_control:
- * @self: the endpoint stream
- * @id_name: the control id (a #WpEndpointControl)
- * @value: the new value for this control, as a `spa_pod`
- *
- * Returns: %TRUE on success, %FALSE if an error occurred
- */
-gboolean
-wp_endpoint_stream_set_control (WpEndpointStream * self, const gchar * id_name,
-    const WpSpaPod * value)
-{
-  g_return_val_if_fail (WP_IS_ENDPOINT_STREAM (self), FALSE);
-  g_return_val_if_fail (WP_ENDPOINT_STREAM_GET_CLASS (self)->set_control, FALSE);
-
-  return WP_ENDPOINT_STREAM_GET_CLASS (self)->set_control (self, id_name,
-      value);
-}
-
 
 /* WpImplEndpointStream */
 
@@ -400,17 +290,15 @@ impl_enum_params (void *object, int seq,
     const struct spa_pod *filter)
 {
   WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object);
-  WpEndpointStreamPrivate *priv =
-      wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self));
   char buf[1024];
   struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf));
   struct spa_pod *result;
   guint count = 0;
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
 
   switch (id) {
     case SPA_PARAM_PropInfo: {
-      g_autoptr (GPtrArray) params =
-          wp_spa_props_build_propinfo (&priv->spa_props);
+      g_autoptr (GPtrArray) params = wp_spa_props_build_propinfo (controls);
 
       for (guint i = start; i < params->len; i++) {
         WpSpaPod *pod = g_ptr_array_index (params, i);
@@ -427,7 +315,7 @@ impl_enum_params (void *object, int seq,
     }
     case SPA_PARAM_Props: {
       if (start == 0) {
-        g_autoptr (WpSpaPod) pod = wp_spa_props_build_props (&priv->spa_props);
+        g_autoptr (WpSpaPod) pod = wp_spa_props_build_props (controls);
         const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod);
         if (spa_pod_filter (&b, &result, param, filter) == 0) {
           pw_endpoint_stream_emit_param (&self->hooks, seq, id, 0, 1, result);
@@ -461,16 +349,15 @@ impl_set_param (void *object, uint32_t id, uint32_t flags,
     const struct spa_pod *param)
 {
   WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object);
-  WpEndpointStreamPrivate *priv =
-      wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self));
   g_autoptr (GPtrArray) changed_ids = NULL;
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
 
   if (id != SPA_PARAM_Props)
     return -ENOENT;
 
   changed_ids = g_ptr_array_new_with_free_func (g_free);
   g_autoptr (WpSpaPod) pod = wp_spa_pod_new_regular_wrap_copy (param);
-  wp_spa_props_store_from_props (&priv->spa_props, pod, changed_ids);
+  wp_spa_props_store_from_props (controls, pod, changed_ids);
 
   /* notify subscribers */
   if (self->subscribed)
@@ -479,7 +366,8 @@ impl_set_param (void *object, uint32_t id, uint32_t flags,
   /* notify controls locally */
   for (guint i = 0; i < changed_ids->len; i++) {
     const gchar * prop_id = g_ptr_array_index (changed_ids, i);
-    g_signal_emit (self, signals[SIGNAL_CONTROL_CHANGED], 0, prop_id);
+    WP_PROXY_GET_CLASS (WP_PROXY (self))->control_changed (WP_PROXY (self),
+        prop_id);
   }
 
   return 0;
@@ -536,7 +424,7 @@ wp_impl_endpoint_stream_init (WpImplEndpointStream * self)
 
   priv->iface = (struct pw_endpoint_stream *) &self->iface;
 
-  wp_proxy_set_feature_ready (WP_PROXY (self), WP_ENDPOINT_STREAM_FEATURE_CONTROLS);
+  wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_CONTROLS);
 }
 
 static void
diff --git a/lib/wp/endpoint-stream.h b/lib/wp/endpoint-stream.h
index 51e575e2..7fc22c03 100644
--- a/lib/wp/endpoint-stream.h
+++ b/lib/wp/endpoint-stream.h
@@ -14,19 +14,6 @@
 
 G_BEGIN_DECLS
 
-/**
- * WpEndpointStreamFeatures:
- * @WP_ENDPOINT_STREAM_FEATURE_CONTROLS: enables the use of the
- *   wp_endpoint_stream_get_control() and wp_endpoint_stream_set_control()
- *   families of functions to be able to work with endpoint-stream-specific
- *   controls
- *
- * An extension of #WpProxyFeatures
- */
-typedef enum { /*< flags >*/
-  WP_ENDPOINT_STREAM_FEATURE_CONTROLS = WP_PROXY_FEATURE_LAST,
-} WpEndpointStreamFeatures;
-
 /**
  * WP_TYPE_ENDPOINT_STREAM:
  *
@@ -42,23 +29,11 @@ struct _WpEndpointStreamClass
   WpProxyClass parent_class;
 
   const gchar * (*get_name) (WpEndpointStream * self);
-
-  WpSpaPod * (*get_control) (WpEndpointStream * self, const gchar * id_name);
-  gboolean (*set_control) (WpEndpointStream * self, const gchar * id_name,
-      const WpSpaPod * value);
 };
 
 WP_API
 const gchar * wp_endpoint_stream_get_name (WpEndpointStream * self);
 
-WP_API
-WpSpaPod * wp_endpoint_stream_get_control (WpEndpointStream * self,
-    const gchar *id_name);
-
-WP_API
-gboolean wp_endpoint_stream_set_control (WpEndpointStream * self,
-    const gchar *id_name, const WpSpaPod * value);
-
 G_END_DECLS
 
 #endif
diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c
index c6dc2342..2e558cd4 100644
--- a/lib/wp/endpoint.c
+++ b/lib/wp/endpoint.c
@@ -35,12 +35,6 @@
 #include <spa/pod/parser.h>
 #include <spa/pod/filter.h>
 
-enum {
-  SIGNAL_CONTROL_CHANGED,
-  N_SIGNALS,
-};
-
-static guint32 signals[N_SIGNALS] = {0};
 
 /* WpEndpoint */
 
@@ -48,7 +42,6 @@ typedef struct _WpEndpointPrivate WpEndpointPrivate;
 struct _WpEndpointPrivate
 {
   WpProperties *properties;
-  WpSpaProps spa_props;
   struct pw_endpoint_info *info;
   struct pw_endpoint *iface;
   struct spa_hook listener;
@@ -71,7 +64,6 @@ wp_endpoint_finalize (GObject * object)
   g_clear_object (&priv->streams_om);
   g_clear_pointer (&priv->properties, wp_properties_unref);
   g_clear_pointer (&priv->info, pw_endpoint_info_free);
-  wp_spa_props_clear (&priv->spa_props);
 
   G_OBJECT_CLASS (wp_endpoint_parent_class)->finalize (object);
 }
@@ -125,7 +117,7 @@ wp_endpoint_augment (WpProxy * proxy, WpProxyFeatures features)
   /* call the parent impl first to ensure we have a pw proxy if necessary */
   WP_PROXY_CLASS (wp_endpoint_parent_class)->augment (proxy, features);
 
-  if (features & WP_ENDPOINT_FEATURE_CONTROLS) {
+  if (features & WP_PROXY_FEATURE_CONTROLS) {
     struct pw_endpoint *pw_proxy = NULL;
     uint32_t ids[] = { SPA_PARAM_Props };
 
@@ -261,33 +253,6 @@ wp_endpoint_bound (WpProxy * proxy, guint32 id)
     wp_endpoint_enable_feature_streams (self, id);
 }
 
-static void
-wp_endpoint_param (WpProxy * proxy, gint seq, const gchar * id_name,
-    guint32 index, guint32 next, const WpSpaPod *param)
-{
-  WpEndpoint *self = WP_ENDPOINT (proxy);
-  WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self);
-  g_autoptr (GPtrArray) changed_ids = NULL;
-  const gchar * prop_id;
-
-  if (g_strcmp0 ("PropInfo", id_name) == 0) {
-    wp_spa_props_register_from_prop_info (&priv->spa_props, param);
-  }
-
-  else if (g_strcmp0 ("Props", id_name) == 0) {
-    changed_ids = g_ptr_array_new_with_free_func (g_free);
-    wp_spa_props_store_from_props (&priv->spa_props, param, changed_ids);
-
-    for (guint i = 0; i < changed_ids->len; i++) {
-      prop_id = g_ptr_array_index (changed_ids, i);
-      g_signal_emit (self, signals[SIGNAL_CONTROL_CHANGED], 0, prop_id);
-    }
-
-    wp_proxy_set_feature_ready (WP_PROXY (self),
-        WP_ENDPOINT_FEATURE_CONTROLS);
-  }
-}
-
 static const gchar *
 get_name (WpEndpoint * self)
 {
@@ -309,29 +274,6 @@ get_direction (WpEndpoint * self)
   return priv->info->direction;
 }
 
-static WpSpaPod *
-get_control (WpEndpoint * self, const gchar * id_name)
-{
-  WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self);
-  return wp_spa_props_get_stored (&priv->spa_props, id_name);
-}
-
-static gboolean
-set_control (WpEndpoint * self, const gchar * id_name, const WpSpaPod * pod)
-{
-  g_autoptr (WpSpaPod) param = wp_spa_pod_new_object (
-      "Props", "Props",
-      id_name, "P", pod,
-      NULL);
-
-  /* our spa_props will be updated by the param event */
-
-  WP_PROXY_GET_CLASS (self)->set_param (WP_PROXY (self), SPA_PARAM_Props, 0,
-      param);
-
-  return TRUE;
-}
-
 static void
 wp_endpoint_class_init (WpEndpointClass * klass)
 {
@@ -352,24 +294,10 @@ wp_endpoint_class_init (WpEndpointClass * klass)
 
   proxy_class->pw_proxy_created = wp_endpoint_pw_proxy_created;
   proxy_class->bound = wp_endpoint_bound;
-  proxy_class->param = wp_endpoint_param;
 
   klass->get_name = get_name;
   klass->get_media_class = get_media_class;
   klass->get_direction = get_direction;
-  klass->get_control = get_control;
-  klass->set_control = set_control;
-
-  /**
-   * WpEndpoint::control-changed:
-   * @self: the endpoint
-   * @control: the control that changed (a #WpEndpointControl)
-   *
-   * Emitted when an endpoint control changes value
-   */
-  signals[SIGNAL_CONTROL_CHANGED] = g_signal_new (
-      "control-changed", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
 }
 
 /**
@@ -417,41 +345,6 @@ wp_endpoint_get_direction (WpEndpoint * self)
   return WP_ENDPOINT_GET_CLASS (self)->get_direction (self);
 }
 
-/**
- * wp_endpoint_get_control:
- * @self: the endpoint
- * @id_name: the control id name
- *
- * Returns: (transfer full) (nullable): the spa pod containing the value
- *   of this control, or %NULL if @control_id does not exist on this endpoint
- */
-WpSpaPod *
-wp_endpoint_get_control (WpEndpoint * self, const gchar * id_name)
-{
-  g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
-  g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (self)->get_control, NULL);
-
-  return WP_ENDPOINT_GET_CLASS (self)->get_control (self, id_name);
-}
-
-/**
- * wp_endpoint_set_control:
- * @self: the endpoint
- * @id_name: the control id name
- * @value: the new value for this control, as a `spa_pod`
- *
- * Returns: %TRUE on success, %FALSE if an error occurred
- */
-gboolean
-wp_endpoint_set_control (WpEndpoint * self, const gchar * id_name,
-    const WpSpaPod * value)
-{
-  g_return_val_if_fail (WP_IS_ENDPOINT (self), FALSE);
-  g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (self)->set_control, FALSE);
-
-  return WP_ENDPOINT_GET_CLASS (self)->set_control (self, id_name, value);
-}
-
 /**
  * wp_endpoint_get_n_streams:
  * @self: the endpoint
@@ -565,17 +458,15 @@ impl_enum_params (void *object, int seq,
     const struct spa_pod *filter)
 {
   WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);
-  WpEndpointPrivate *priv =
-      wp_endpoint_get_instance_private (WP_ENDPOINT (self));
   char buf[1024];
   struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf));
   struct spa_pod *result;
   guint count = 0;
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
 
   switch (id) {
     case SPA_PARAM_PropInfo: {
-      g_autoptr (GPtrArray) params =
-          wp_spa_props_build_propinfo (&priv->spa_props);
+      g_autoptr (GPtrArray) params = wp_spa_props_build_propinfo (controls);
 
       for (guint i = start; i < params->len; i++) {
         WpSpaPod *pod = g_ptr_array_index (params, i);
@@ -592,7 +483,7 @@ impl_enum_params (void *object, int seq,
     }
     case SPA_PARAM_Props: {
       if (start == 0) {
-        g_autoptr (WpSpaPod) pod = wp_spa_props_build_props (&priv->spa_props);
+        g_autoptr (WpSpaPod) pod = wp_spa_props_build_props (controls);
         const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod);
         if (spa_pod_filter (&b, &result, param, filter) == 0) {
           pw_endpoint_emit_param (&self->hooks, seq, id, 0, 1, result);
@@ -626,16 +517,15 @@ impl_set_param (void *object, uint32_t id, uint32_t flags,
     const struct spa_pod *param)
 {
   WpImplEndpoint *self = WP_IMPL_ENDPOINT (object);
-  WpEndpointPrivate *priv =
-      wp_endpoint_get_instance_private (WP_ENDPOINT (self));
   g_autoptr (GPtrArray) changed_ids = NULL;
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
 
   if (id != SPA_PARAM_Props)
     return -ENOENT;
 
   changed_ids = g_ptr_array_new_with_free_func (g_free);
   g_autoptr (WpSpaPod) pod = wp_spa_pod_new_regular_wrap_copy (param);
-  wp_spa_props_store_from_props (&priv->spa_props, pod, changed_ids);
+  wp_spa_props_store_from_props (controls, pod, changed_ids);
 
   /* notify subscribers */
   if (self->subscribed)
@@ -644,7 +534,8 @@ impl_set_param (void *object, uint32_t id, uint32_t flags,
   /* notify controls locally */
   for (guint i = 0; i < changed_ids->len; i++) {
     const gchar * prop_id = g_ptr_array_index (changed_ids, i);
-    g_signal_emit (self, signals[SIGNAL_CONTROL_CHANGED], 0, prop_id);
+    WP_PROXY_GET_CLASS (WP_PROXY (self))->control_changed (WP_PROXY (self),
+        prop_id);
   }
 
   return 0;
@@ -874,7 +765,7 @@ wp_impl_endpoint_init (WpImplEndpoint * self)
 
   priv->iface = (struct pw_endpoint *) &self->iface;
 
-  wp_proxy_set_feature_ready (WP_PROXY (self), WP_ENDPOINT_FEATURE_CONTROLS);
+  wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_CONTROLS);
 }
 
 static void
diff --git a/lib/wp/endpoint.h b/lib/wp/endpoint.h
index 7bd15a18..91464ebe 100644
--- a/lib/wp/endpoint.h
+++ b/lib/wp/endpoint.h
@@ -30,9 +30,6 @@ typedef enum {
 
 /**
  * WpEndpointFeatures:
- * @WP_ENDPOINT_FEATURE_CONTROLS: enables the use of the
- *   wp_endpoint_get_control() and wp_endpoint_set_control() families of
- *   functions to be able to work with endpoint-specific controls
  * @WP_ENDPOINT_FEATURE_STREAMS: caches information about streams, enabling
  *   the use of wp_endpoint_get_n_streams(), wp_endpoint_find_stream() and
  *   wp_endpoint_iterate_streams()
@@ -40,8 +37,7 @@ typedef enum {
  * An extension of #WpProxyFeatures
  */
 typedef enum { /*< flags >*/
-  WP_ENDPOINT_FEATURE_CONTROLS = WP_PROXY_FEATURE_LAST,
-  WP_ENDPOINT_FEATURE_STREAMS,
+  WP_ENDPOINT_FEATURE_STREAMS = WP_PROXY_FEATURE_LAST,
 } WpEndpointFeatures;
 
 /**
@@ -52,7 +48,7 @@ typedef enum { /*< flags >*/
  */
 #define WP_ENDPOINT_FEATURES_STANDARD \
     (WP_PROXY_FEATURES_STANDARD | \
-     WP_ENDPOINT_FEATURE_CONTROLS | \
+     WP_PROXY_FEATURE_CONTROLS | \
      WP_ENDPOINT_FEATURE_STREAMS)
 
 /**
@@ -71,10 +67,6 @@ struct _WpEndpointClass
   const gchar * (*get_name) (WpEndpoint * self);
   const gchar * (*get_media_class) (WpEndpoint * self);
   WpDirection (*get_direction) (WpEndpoint * self);
-
-  WpSpaPod * (*get_control) (WpEndpoint * self, const gchar * id_name);
-  gboolean (*set_control) (WpEndpoint * self, const gchar * id_name,
-      const WpSpaPod * value);
 };
 
 WP_API
@@ -86,13 +78,6 @@ const gchar * wp_endpoint_get_media_class (WpEndpoint * self);
 WP_API
 WpDirection wp_endpoint_get_direction (WpEndpoint * self);
 
-WP_API
-WpSpaPod * wp_endpoint_get_control (WpEndpoint * self, const gchar * id_name);
-
-WP_API
-gboolean wp_endpoint_set_control (WpEndpoint * self, const gchar * id_name,
-    const WpSpaPod * value);
-
 WP_API
 guint wp_endpoint_get_n_streams (WpEndpoint * self);
 
diff --git a/lib/wp/policy.c b/lib/wp/policy.c
index 5639fccc..7149c1a7 100644
--- a/lib/wp/policy.c
+++ b/lib/wp/policy.c
@@ -131,7 +131,7 @@ wp_policy_manager_get_instance (WpCore *core)
     /* install the object manager to listen to changed sessions */
     wp_object_manager_add_interest (mgr->sessions_om,
         WP_TYPE_IMPL_SESSION, NULL,
-        WP_PROXY_FEATURES_STANDARD | WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
+        WP_PROXY_FEATURES_STANDARD | WP_PROXY_FEATURE_CONTROLS);
     wp_core_install_object_manager (core, mgr->sessions_om);
 
     wp_registry_register_object (wp_core_get_registry (core),
diff --git a/lib/wp/private.h b/lib/wp/private.h
index 3934b468..dfbfc29c 100644
--- a/lib/wp/private.h
+++ b/lib/wp/private.h
@@ -119,6 +119,8 @@ void wp_proxy_augment_error (WpProxy * self, GError * error);
 void wp_proxy_handle_event_param (void * proxy, int seq, uint32_t id,
     uint32_t index, uint32_t next, const struct spa_pod *param);
 
+WpSpaProps *wp_proxy_get_spa_props (WpProxy * self);
+
 /* iterator */
 
 struct _WpIteratorMethods {
diff --git a/lib/wp/proxy.c b/lib/wp/proxy.c
index 820d8a7c..8468fcfd 100644
--- a/lib/wp/proxy.c
+++ b/lib/wp/proxy.c
@@ -47,6 +47,9 @@ struct _WpProxyPrivate
   GPtrArray *augment_tasks; // element-type: GTask*
 
   GHashTable *async_tasks; // <int seq, GTask*>
+
+  /* controls */
+  WpSpaProps controls;
 };
 
 enum {
@@ -68,6 +71,7 @@ enum
   SIGNAL_PW_PROXY_DESTROYED,
   SIGNAL_BOUND,
   SIGNAL_PARAM,
+  SIGNAL_CONTROL_CHANGED,
   LAST_SIGNAL,
 };
 
@@ -202,6 +206,7 @@ wp_proxy_finalize (GObject * object)
   g_clear_pointer (&priv->global, wp_global_unref);
   g_weak_ref_clear (&priv->core);
   g_clear_pointer (&priv->async_tasks, g_hash_table_unref);
+  wp_spa_props_clear (&priv->controls);
 
   G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object);
 }
@@ -293,6 +298,54 @@ wp_proxy_default_augment (WpProxy * self, WpProxyFeatures features)
   }
 }
 
+static void
+wp_proxy_default_param (WpProxy * self, gint seq, const gchar * id_name,
+    guint32 index, guint32 next, const WpSpaPod *param)
+{
+  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
+  g_autoptr (GPtrArray) changed_ids = NULL;
+  const gchar * prop_id;
+
+  if (g_strcmp0 ("PropInfo", id_name) == 0) {
+    wp_spa_props_register_from_prop_info (&priv->controls, param);
+  }
+
+  else if (g_strcmp0 ("Props", id_name) == 0) {
+    changed_ids = g_ptr_array_new_with_free_func (g_free);
+    wp_spa_props_store_from_props (&priv->controls, param, changed_ids);
+
+    for (guint i = 0; i < changed_ids->len; i++) {
+      prop_id = g_ptr_array_index (changed_ids, i);
+      g_signal_emit (self, wp_proxy_signals[SIGNAL_CONTROL_CHANGED], 0, prop_id);
+    }
+
+    wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_CONTROLS);
+  }
+}
+
+static WpSpaPod *
+wp_proxy_default_get_control (WpProxy * self, const gchar * id_name)
+{
+  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
+  return wp_spa_props_get_stored (&priv->controls, id_name);
+}
+
+static gboolean
+wp_proxy_default_set_control (WpProxy * self, const gchar * id_name,
+    const WpSpaPod * value)
+{
+  g_return_val_if_fail (WP_PROXY_GET_CLASS (self)->set_param, FALSE);
+
+  g_autoptr (WpSpaPod) param = wp_spa_pod_new_object (
+      "Props", "Props",
+      id_name, "P", value,
+      NULL);
+
+  /* our spa_props will be updated by the param event */
+  return WP_PROXY_GET_CLASS (self)->set_param (self, SPA_PARAM_Props, 0,
+      param) >= 0;
+}
+
 static void
 wp_proxy_class_init (WpProxyClass * klass)
 {
@@ -304,6 +357,9 @@ wp_proxy_class_init (WpProxyClass * klass)
   object_class->set_property = wp_proxy_set_property;
 
   klass->augment = wp_proxy_default_augment;
+  klass->param = wp_proxy_default_param;
+  klass->get_control = wp_proxy_default_get_control;
+  klass->set_control = wp_proxy_default_set_control;
 
   /* Install the properties */
 
@@ -369,6 +425,11 @@ wp_proxy_class_init (WpProxyClass * klass)
       "param", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
       G_STRUCT_OFFSET (WpProxyClass, param), NULL, NULL, NULL, G_TYPE_NONE, 5,
       G_TYPE_INT, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, WP_TYPE_SPA_POD);
+
+  wp_proxy_signals[SIGNAL_CONTROL_CHANGED] = g_signal_new (
+      "control-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+      G_STRUCT_OFFSET (WpProxyClass, control_changed), NULL, NULL, NULL,
+      G_TYPE_NONE, 1, G_TYPE_STRING);
 }
 
 void
@@ -859,6 +920,41 @@ wp_proxy_set_param (WpProxy * self, guint32 id, guint32 flags,
       -ENOTSUP;
 }
 
+/**
+ * wp_proxy_get_control:
+ * @self: the proxy
+ * @id_name: the control id name
+ *
+ * Returns: (transfer full) (nullable): the spa pod containing the value
+ *   of this control, or %NULL if @control_id does not exist on this proxy
+ */
+WpSpaPod *
+wp_proxy_get_control (WpProxy * self, const gchar * id_name)
+{
+  g_return_val_if_fail (WP_IS_PROXY (self), NULL);
+  g_return_val_if_fail (WP_PROXY_GET_CLASS (self)->get_control, NULL);
+
+  return WP_PROXY_GET_CLASS (self)->get_control (self, id_name);
+}
+
+/**
+ * wp_proxy_set_control:
+ * @self: the proxy
+ * @id_name: the control id name
+ * @value: the new value for this control, as a spa pod
+ *
+ * Returns: %TRUE on success, %FALSE if an error occurred
+ */
+gboolean
+wp_proxy_set_control (WpProxy * self, const gchar * id_name,
+    const WpSpaPod * value)
+{
+  g_return_val_if_fail (WP_IS_PROXY (self), FALSE);
+  g_return_val_if_fail (WP_PROXY_GET_CLASS (self)->set_control, FALSE);
+
+  return WP_PROXY_GET_CLASS (self)->set_control (self, id_name, value);
+}
+
 void
 wp_proxy_handle_event_param (void * proxy, int seq, uint32_t id,
     uint32_t index, uint32_t next, const struct spa_pod *param)
@@ -882,3 +978,10 @@ wp_proxy_handle_event_param (void * proxy, int seq, uint32_t id,
     g_ptr_array_add (array, g_steal_pointer (&pod));
   }
 }
+
+WpSpaProps *
+wp_proxy_get_spa_props (WpProxy * self)
+{
+  WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
+  return &priv->controls;
+}
diff --git a/lib/wp/proxy.h b/lib/wp/proxy.h
index 4808a7f9..04498dba 100644
--- a/lib/wp/proxy.h
+++ b/lib/wp/proxy.h
@@ -34,6 +34,7 @@ typedef enum { /*< flags >*/
   WP_PROXY_FEATURE_PW_PROXY     = (1 << 0),
   WP_PROXY_FEATURE_INFO         = (1 << 1),
   WP_PROXY_FEATURE_BOUND        = (1 << 2),
+  WP_PROXY_FEATURE_CONTROLS     = (1 << 3),
 
   WP_PROXY_FEATURE_LAST         = (1 << 5), /*< skip >*/
 } WpProxyFeatures;
@@ -83,6 +84,9 @@ struct _WpProxyClass
   gint (*subscribe_params) (WpProxy * self, guint32 n_ids, guint32 *ids);
   gint (*set_param) (WpProxy * self, guint32 id, guint32 flags,
       const WpSpaPod * param);
+  WpSpaPod * (*get_control) (WpProxy * self, const gchar * id_name);
+  gboolean (*set_control) (WpProxy * self, const gchar * id_name,
+      const WpSpaPod * value);
 
   /* signals */
 
@@ -91,6 +95,7 @@ struct _WpProxyClass
   void (*bound) (WpProxy * self, guint32 id);
   void (*param) (WpProxy * self, gint seq, const gchar * id_name, guint32 index,
       guint32 next, const WpSpaPod *param);
+  void (*control_changed) (WpProxy * self, const char * id_name);
 };
 
 /* features API */
@@ -165,6 +170,13 @@ WP_API
 gint wp_proxy_set_param (WpProxy * self, guint32 id, guint32 flags,
     const WpSpaPod *param);
 
+WP_API
+WpSpaPod * wp_proxy_get_control (WpProxy * self, const gchar * id_name);
+
+WP_API
+gboolean wp_proxy_set_control (WpProxy * self, const gchar * id_name,
+    const WpSpaPod * value);
+
 G_END_DECLS
 
 #endif
diff --git a/lib/wp/session.c b/lib/wp/session.c
index baac77a4..40cc53f3 100644
--- a/lib/wp/session.c
+++ b/lib/wp/session.c
@@ -50,7 +50,6 @@ typedef struct _WpSessionPrivate WpSessionPrivate;
 struct _WpSessionPrivate
 {
   WpProperties *properties;
-  WpSpaProps spa_props;
   struct pw_session_info *info;
   struct pw_session *iface;
   struct spa_hook listener;
@@ -75,7 +74,6 @@ wp_session_finalize (GObject * object)
   g_clear_object (&priv->links_om);
   g_clear_pointer (&priv->info, pw_session_info_free);
   g_clear_pointer (&priv->properties, wp_properties_unref);
-  wp_spa_props_clear (&priv->spa_props);
 
   G_OBJECT_CLASS (wp_session_parent_class)->finalize (object);
 }
@@ -175,38 +173,6 @@ wp_session_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
   pw_session_add_listener (priv->iface, &priv->listener, &session_events, self);
 }
 
-static void
-wp_session_param (WpProxy * proxy, gint seq, const gchar * id_name,
-    guint32 index, guint32 next, const WpSpaPod *param)
-{
-  WpSession *self = WP_SESSION (proxy);
-  WpSessionPrivate *priv = wp_session_get_instance_private (self);
-  g_autoptr (GPtrArray) changed_ids = NULL;
-  g_autoptr (WpSpaPod) stored = NULL;
-  const gchar * prop_id;
-  gint32 value;
-
-  if (g_strcmp0 ("PropInfo", id_name) == 0) {
-    wp_spa_props_register_from_prop_info (&priv->spa_props, param);
-  }
-
-  else if (g_strcmp0 ("Props", id_name) == 0) {
-    changed_ids = g_ptr_array_new_with_free_func (g_free);
-    wp_spa_props_store_from_props (&priv->spa_props, param, changed_ids);
-
-    for (guint i = 0; i < changed_ids->len; i++) {
-      prop_id = g_ptr_array_index (changed_ids, i);
-      stored = wp_spa_props_get_stored (&priv->spa_props, prop_id);
-      wp_spa_pod_get_int (stored, &value);
-      g_signal_emit (self, signals[SIGNAL_DEFAULT_ENDPOINT_CHANGED], 0, prop_id,
-          value);
-    }
-
-    wp_proxy_set_feature_ready (WP_PROXY (self),
-        WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
-  }
-}
-
 static void
 wp_session_enable_feature_endpoints (WpSession * self, guint32 bound_id)
 {
@@ -305,6 +271,18 @@ wp_session_bound (WpProxy * proxy, guint32 id)
     wp_session_enable_feature_links (self, id);
 }
 
+static void
+wp_session_control_changed (WpProxy * proxy, const char * id_name)
+{
+  WpSession *self = WP_SESSION (proxy);
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
+  g_autoptr (WpSpaPod) pod = wp_spa_props_get_stored (controls, id_name);
+  gint value;
+  if (wp_spa_pod_get_int (pod, &value))
+    g_signal_emit (self, signals[SIGNAL_DEFAULT_ENDPOINT_CHANGED], 0, id_name,
+        value);
+}
+
 static void
 wp_session_augment (WpProxy * proxy, WpProxyFeatures features)
 {
@@ -313,7 +291,7 @@ wp_session_augment (WpProxy * proxy, WpProxyFeatures features)
   /* call the parent impl first to ensure we have a pw proxy if necessary */
   WP_PROXY_CLASS (wp_session_parent_class)->augment (proxy, features);
 
-  if (features & WP_SESSION_FEATURE_DEFAULT_ENDPOINT) {
+  if (features & WP_PROXY_FEATURE_CONTROLS) {
     struct pw_session *pw_proxy = NULL;
     uint32_t ids[] = { SPA_PARAM_Props };
 
@@ -349,31 +327,21 @@ wp_session_augment (WpProxy * proxy, WpProxyFeatures features)
 }
 
 static guint32
-get_default_endpoint (WpSession * self, const gchar * type_name)
+get_default_endpoint (WpSession * self, const gchar * id_name)
 {
-  WpSessionPrivate *priv = wp_session_get_instance_private (self);
-  g_autoptr (WpSpaPod) pod = NULL;
+  g_autoptr (WpSpaPod) pod = wp_proxy_get_control (WP_PROXY (self), id_name);
   gint32 value;
 
-  pod = wp_spa_props_get_stored (&priv->spa_props, type_name);
   if (pod && wp_spa_pod_get_int (pod, &value))
     return (guint32) value;
   return 0;
 }
 
 static void
-set_default_endpoint (WpSession * self, const gchar * type_name, guint32 id)
+set_default_endpoint (WpSession * self, const gchar * id_name, guint32 id)
 {
-  g_autoptr (WpSpaPod) param = wp_spa_pod_new_object (
-      "Props", "Props",
-      type_name, "i", id,
-      NULL);
-
-  /* set the default endpoint id as a property param on the session;
-     our spa_props cache will be updated by the param event */
-
-  WP_PROXY_GET_CLASS (self)->set_param (WP_PROXY (self), SPA_PARAM_Props, 0,
-      param);
+  g_autoptr (WpSpaPod) param = wp_spa_pod_new_int (id);
+  wp_proxy_set_control (WP_PROXY (self), id_name, param);
 }
 
 static void
@@ -406,8 +374,8 @@ wp_session_class_init (WpSessionClass * klass)
   proxy_class->set_param = wp_session_set_param;
 
   proxy_class->pw_proxy_created = wp_session_pw_proxy_created;
-  proxy_class->param = wp_session_param;
   proxy_class->bound = wp_session_bound;
+  proxy_class->control_changed = wp_session_control_changed;
 
   klass->get_default_endpoint = get_default_endpoint;
   klass->set_default_endpoint = set_default_endpoint;
@@ -431,37 +399,37 @@ wp_session_class_init (WpSessionClass * klass)
 /**
  * wp_session_get_default_endpoint:
  * @self: the session
- * @type_name: the endpoint type name
+ * @id_name: the endpoint id name
  *
  * Returns: the bound id of the default endpoint of this @type
  */
 guint32
 wp_session_get_default_endpoint (WpSession * self,
-    const gchar * type_name)
+    const gchar * id_name)
 {
   g_return_val_if_fail (WP_IS_SESSION (self), SPA_ID_INVALID);
   g_return_val_if_fail (WP_SESSION_GET_CLASS (self)->get_default_endpoint,
       SPA_ID_INVALID);
 
-  return WP_SESSION_GET_CLASS (self)->get_default_endpoint (self, type_name);
+  return WP_SESSION_GET_CLASS (self)->get_default_endpoint (self, id_name);
 }
 
 /**
  * wp_session_set_default_endpoint:
  * @self: the session
- * @type_name: the endpoint type name
+ * @id_name: the endpoint id name
  * @id: the bound id of the endpoint to set as the default for this @type
  *
  * Sets the default endpoint for this @type to be the one identified with @id
  */
 void
-wp_session_set_default_endpoint (WpSession * self, const char * type_name,
+wp_session_set_default_endpoint (WpSession * self, const char * id_name,
     guint32 id)
 {
   g_return_if_fail (WP_IS_SESSION (self));
   g_return_if_fail (WP_SESSION_GET_CLASS (self)->set_default_endpoint);
 
-  WP_SESSION_GET_CLASS (self)->set_default_endpoint (self, type_name, id);
+  WP_SESSION_GET_CLASS (self)->set_default_endpoint (self, id_name, id);
 }
 
 /**
@@ -626,17 +594,15 @@ impl_enum_params (void *object, int seq,
     const struct spa_pod *filter)
 {
   WpImplSession *self = WP_IMPL_SESSION (object);
-  WpSessionPrivate *priv =
-      wp_session_get_instance_private (WP_SESSION (self));
   char buf[1024];
   struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf));
   struct spa_pod *result;
   guint count = 0;
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
 
   switch (id) {
     case SPA_PARAM_PropInfo: {
-      g_autoptr (GPtrArray) params =
-          wp_spa_props_build_propinfo (&priv->spa_props);
+      g_autoptr (GPtrArray) params = wp_spa_props_build_propinfo (controls);
 
       for (guint i = start; i < params->len; i++) {
         WpSpaPod *pod = g_ptr_array_index (params, i);
@@ -650,7 +616,7 @@ impl_enum_params (void *object, int seq,
     }
     case SPA_PARAM_Props: {
       if (start == 0) {
-        g_autoptr (WpSpaPod) pod = wp_spa_props_build_props (&priv->spa_props);
+        g_autoptr (WpSpaPod) pod = wp_spa_props_build_props (controls);
         const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod);
         if (spa_pod_filter (&b, &result, param, filter) == 0) {
           pw_session_emit_param (&self->hooks, seq, id, 0, 1, result);
@@ -683,15 +649,15 @@ impl_set_param (void *object, uint32_t id, uint32_t flags,
     const struct spa_pod *param)
 {
   WpImplSession *self = WP_IMPL_SESSION (object);
-  WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self));
   g_autoptr (GPtrArray) changed_ids = NULL;
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
 
   if (id != SPA_PARAM_Props)
     return -ENOENT;
 
   changed_ids = g_ptr_array_new_with_free_func (g_free);
   g_autoptr (WpSpaPod) pod = wp_spa_pod_new_regular_wrap_copy (param);
-  wp_spa_props_store_from_props (&priv->spa_props, pod, changed_ids);
+  wp_spa_props_store_from_props (controls, pod, changed_ids);
 
   /* notify subscribers */
   if (self->subscribed)
@@ -700,12 +666,8 @@ impl_set_param (void *object, uint32_t id, uint32_t flags,
   /* notify controls locally */
   for (guint i = 0; i < changed_ids->len; i++) {
     const gchar * prop_id = g_ptr_array_index (changed_ids, i);
-    g_autoptr (WpSpaPod) pod = wp_spa_props_get_stored (&priv->spa_props,
+    WP_PROXY_GET_CLASS (WP_PROXY (self))->control_changed (WP_PROXY (self),
         prop_id);
-    gint value;
-    if (wp_spa_pod_get_int (pod, &value))
-      g_signal_emit (self, signals[SIGNAL_DEFAULT_ENDPOINT_CHANGED], 0, prop_id,
-        value);
   }
 
   return 0;
@@ -724,8 +686,8 @@ wp_impl_session_init (WpImplSession * self)
 {
   /* reuse the parent's private to optimize memory usage and to be able
      to re-use some of the parent's methods without reimplementing them */
-  WpSessionPrivate *priv =
-      wp_session_get_instance_private (WP_SESSION (self));
+  WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self));
+  WpSpaProps *controls = wp_proxy_get_spa_props (WP_PROXY (self));
 
   self->iface = SPA_INTERFACE_INIT (
       PW_TYPE_INTERFACE_Session,
@@ -748,18 +710,18 @@ wp_impl_session_init (WpImplSession * self)
   wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
 
   /* prepare default endpoint */
-  wp_spa_props_register (&priv->spa_props,
+  wp_spa_props_register (controls,
       "wp-session-default-endpoint-audio-source",
       "Default Audio Source", wp_spa_pod_new_int (0));
-  wp_spa_props_register (&priv->spa_props,
+  wp_spa_props_register (controls,
       "wp-session-default-endpoint-audio-sink",
       "Default Audio Sink", wp_spa_pod_new_int (0));
-  wp_spa_props_register (&priv->spa_props,
+  wp_spa_props_register (controls,
       "wp-session-default-endpoint-video-source",
       "Default Video Source", wp_spa_pod_new_int (0));
 
   wp_proxy_set_feature_ready (WP_PROXY (self),
-      WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
+      WP_PROXY_FEATURE_CONTROLS);
 }
 
 static void
diff --git a/lib/wp/session.h b/lib/wp/session.h
index d501105a..a66ea478 100644
--- a/lib/wp/session.h
+++ b/lib/wp/session.h
@@ -17,9 +17,6 @@ G_BEGIN_DECLS
 
 /**
  * WpSessionFeatures:
- * @WP_SESSION_FEATURE_DEFAULT_ENDPOINT: enables the use of
- *   wp_session_get_default_endpoint() and wp_session_set_default_endpoint()
- *   to store default endpoint preferences on the session
  * @WP_SESSION_FEATURE_ENDPOINTS: caches information about endpoints, enabling
  *   the use of wp_session_get_n_endpoints(), wp_session_find_endpoint() and
  *   wp_session_iterate_endpoints()
@@ -30,8 +27,7 @@ G_BEGIN_DECLS
  * An extension of #WpProxyFeatures
  */
 typedef enum { /*< flags >*/
-  WP_SESSION_FEATURE_DEFAULT_ENDPOINT = WP_PROXY_FEATURE_LAST,
-  WP_SESSION_FEATURE_ENDPOINTS,
+  WP_SESSION_FEATURE_ENDPOINTS = WP_PROXY_FEATURE_LAST,
   WP_SESSION_FEATURE_LINKS,
 } WpSessionFeatures;
 
@@ -43,7 +39,7 @@ typedef enum { /*< flags >*/
  */
 #define WP_SESSION_FEATURES_STANDARD \
     (WP_PROXY_FEATURES_STANDARD | \
-     WP_SESSION_FEATURE_DEFAULT_ENDPOINT | \
+     WP_PROXY_FEATURE_CONTROLS | \
      WP_SESSION_FEATURE_ENDPOINTS | \
      WP_SESSION_FEATURE_LINKS)
 
@@ -60,17 +56,17 @@ struct _WpSessionClass
 {
   WpProxyClass parent_class;
 
-  guint32 (*get_default_endpoint) (WpSession * self, const gchar * type_name);
-  void (*set_default_endpoint) (WpSession * self, const gchar * type_name,
+  guint32 (*get_default_endpoint) (WpSession * self, const gchar * id_name);
+  void (*set_default_endpoint) (WpSession * self, const gchar * id_name,
       guint32 id);
 };
 
 WP_API
 guint32 wp_session_get_default_endpoint (WpSession * self,
-    const gchar * type_name);
+    const gchar * id_name);
 
 WP_API
-void wp_session_set_default_endpoint (WpSession * self, const gchar * type_name,
+void wp_session_set_default_endpoint (WpSession * self, const gchar * id_name,
     guint32 id);
 
 WP_API
diff --git a/tests/wp/endpoint.c b/tests/wp/endpoint.c
index 06e2aabd..1d0b04a5 100644
--- a/tests/wp/endpoint.c
+++ b/tests/wp/endpoint.c
@@ -363,7 +363,7 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data)
       (GCallback) test_endpoint_basic_impl_object_removed, fixture);
   wp_object_manager_add_interest (fixture->export_om,
       WP_TYPE_ENDPOINT, NULL,
-      WP_PROXY_FEATURES_STANDARD | WP_ENDPOINT_FEATURE_CONTROLS);
+      WP_PROXY_FEATURES_STANDARD | WP_PROXY_FEATURE_CONTROLS);
   wp_core_install_object_manager (fixture->export_core, fixture->export_om);
 
   g_assert_true (wp_core_connect (fixture->export_core));
@@ -375,7 +375,7 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data)
       (GCallback) test_endpoint_basic_proxy_object_removed, fixture);
   wp_object_manager_add_interest (fixture->proxy_om,
       WP_TYPE_ENDPOINT, NULL,
-      WP_PROXY_FEATURES_STANDARD | WP_ENDPOINT_FEATURE_CONTROLS);
+      WP_PROXY_FEATURES_STANDARD | WP_PROXY_FEATURE_CONTROLS);
   wp_core_install_object_manager (fixture->proxy_core, fixture->proxy_om);
 
   g_assert_true (wp_core_connect (fixture->proxy_core));
@@ -416,7 +416,7 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data)
       WP_PROXY_FEATURE_PW_PROXY |
       WP_PROXY_FEATURE_INFO |
       WP_PROXY_FEATURE_BOUND |
-      WP_ENDPOINT_FEATURE_CONTROLS);
+      WP_PROXY_FEATURE_CONTROLS);
 
   g_assert_cmpuint (wp_proxy_get_bound_id (fixture->proxy_endpoint), ==,
       wp_proxy_get_bound_id (fixture->impl_endpoint));
diff --git a/tests/wp/session.c b/tests/wp/session.c
index 5709c0fc..5233dac6 100644
--- a/tests/wp/session.c
+++ b/tests/wp/session.c
@@ -282,7 +282,8 @@ test_session_basic (TestSessionFixture *fixture, gconstpointer data)
       WP_PROXY_FEATURE_PW_PROXY |
       WP_PROXY_FEATURE_INFO |
       WP_PROXY_FEATURE_BOUND |
-      WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
+      WP_PROXY_FEATURE_CONTROLS |
+      WP_SESSION_FEATURE_ENDPOINTS);
 
   g_assert_cmpuint (wp_proxy_get_bound_id (fixture->proxy_session), ==,
       wp_proxy_get_bound_id (WP_PROXY (session)));
diff --git a/tools/wireplumber-cli.c b/tools/wireplumber-cli.c
index 3ade4549..66ca2da9 100644
--- a/tools/wireplumber-cli.c
+++ b/tools/wireplumber-cli.c
@@ -47,9 +47,9 @@ print_dev_endpoint (WpEndpoint *ep, WpSession *session, const gchar *type_name)
   gfloat volume = 0.0;
   gboolean mute = FALSE;
 
-  ctrl = wp_endpoint_get_control (ep, "volume");
+  ctrl = wp_proxy_get_control (WP_PROXY (ep), "volume");
   wp_spa_pod_get_float (ctrl, &volume);
-  ctrl = wp_endpoint_get_control (ep, "mute");
+  ctrl = wp_proxy_get_control (WP_PROXY (ep), "mute");
   wp_spa_pod_get_boolean (ctrl, &mute);
 
   g_print (" %c %4u. %60s\tvol: %.2f %s\n", is_default ? '*' : ' ', id,
@@ -189,7 +189,7 @@ set_volume (WpObjectManager * om, struct WpCliData * d)
 
     if (id == d->params.set_volume.id) {
       g_autoptr (WpSpaPod) vol = wp_spa_pod_new_float (d->params.set_volume.volume);
-      wp_endpoint_set_control (ep, "volume", vol);
+      wp_proxy_set_control (WP_PROXY (ep), "volume", vol);
       wp_core_sync (d->core, NULL, (GAsyncReadyCallback) async_quit, d);
       return;
     }
@@ -305,10 +305,10 @@ main (gint argc, gchar **argv)
   if (argc == 2 && !g_strcmp0 (argv[1], "ls-endpoints")) {
     wp_object_manager_add_interest (om, WP_TYPE_ENDPOINT,
         NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
-        WP_ENDPOINT_FEATURE_CONTROLS);
+        WP_PROXY_FEATURE_CONTROLS);
     wp_object_manager_add_interest (om, WP_TYPE_SESSION,
         NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
-        WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
+        WP_PROXY_FEATURE_CONTROLS);
     g_signal_connect (om, "objects-changed", (GCallback) list_endpoints, &data);
   }
 
@@ -323,7 +323,7 @@ main (gint argc, gchar **argv)
         NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND);
     wp_object_manager_add_interest (om, WP_TYPE_SESSION,
         NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
-        WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
+        WP_PROXY_FEATURE_CONTROLS);
 
     data.params.set_default.id = id;
     g_signal_connect (om, "objects-changed", (GCallback) set_default, &data);
@@ -339,7 +339,7 @@ main (gint argc, gchar **argv)
 
     wp_object_manager_add_interest (om, WP_TYPE_ENDPOINT,
         NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
-        WP_ENDPOINT_FEATURE_CONTROLS);
+        WP_PROXY_FEATURE_CONTROLS);
 
     data.params.set_volume.id = id;
     data.params.set_volume.volume = volume;
-- 
GitLab