diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c index 8dfa99bf1741f1ccc76ef93d7c47dd34fce4a5fa..7722cc4f838528d3968528b7fcf4a00358ed33c7 100644 --- a/lib/wp/endpoint.c +++ b/lib/wp/endpoint.c @@ -22,14 +22,18 @@ */ #include "endpoint.h" +#include "session.h" #include "private.h" #include "error.h" #include "wpenums.h" #include <pipewire/pipewire.h> #include <pipewire/extensions/session-manager.h> +#include <pipewire/extensions/session-manager/introspect-funcs.h> + #include <spa/pod/builder.h> #include <spa/pod/parser.h> +#include <spa/pod/filter.h> enum { SIGNAL_CONTROL_CHANGED, @@ -38,64 +42,6 @@ enum { static guint32 signals[N_SIGNALS] = {0}; -/* helpers */ - -static struct pw_endpoint_info * -endpoint_info_update (struct pw_endpoint_info *info, - WpProperties ** props_storage, - const struct pw_endpoint_info *update) -{ - if (update == NULL) - return info; - - if (info == NULL) { - info = calloc(1, sizeof(struct pw_endpoint_info)); - if (info == NULL) - return NULL; - - info->id = update->id; - info->name = g_strdup(update->name); - info->media_class = g_strdup(update->media_class); - info->direction = update->direction; - info->flags = update->flags; - } - info->change_mask = update->change_mask; - - if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS) - info->n_streams = update->n_streams; - - if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION) - info->session_id = update->session_id; - - if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) { - if (*props_storage) - wp_properties_unref (*props_storage); - *props_storage = wp_properties_new_copy_dict (update->props); - info->props = (struct spa_dict *) wp_properties_peek_dict (*props_storage); - } - if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) { - info->n_params = update->n_params; - free((void *) info->params); - if (update->params) { - size_t size = info->n_params * sizeof(struct spa_param_info); - info->params = malloc(size); - memcpy(info->params, update->params, size); - } - else - info->params = NULL; - } - return info; -} - -static void -endpoint_info_free (struct pw_endpoint_info *info) -{ - g_free(info->name); - g_free(info->media_class); - free((void *) info->params); - free(info); -} - /* WpEndpoint */ typedef struct _WpEndpointPrivate WpEndpointPrivate; @@ -104,6 +50,7 @@ struct _WpEndpointPrivate WpProperties *properties; WpSpaProps spa_props; struct pw_endpoint_info *info; + struct pw_endpoint *iface; struct spa_hook listener; }; @@ -120,8 +67,8 @@ wp_endpoint_finalize (GObject * object) WpEndpoint *self = WP_ENDPOINT (object); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - g_clear_pointer (&priv->info, endpoint_info_free); 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); @@ -168,12 +115,12 @@ static gint wp_endpoint_enum_params (WpProxy * self, guint32 id, guint32 start, guint32 num, const struct spa_pod *filter) { - struct pw_endpoint *pwp; + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); int endpoint_enum_params_result; - pwp = (struct pw_endpoint *) wp_proxy_get_pw_proxy (self); - endpoint_enum_params_result = pw_endpoint_enum_params (pwp, 0, id, start, num, - filter); + endpoint_enum_params_result = pw_endpoint_enum_params (priv->iface, 0, id, + start, num, filter); g_warn_if_fail (endpoint_enum_params_result >= 0); return endpoint_enum_params_result; @@ -182,12 +129,12 @@ wp_endpoint_enum_params (WpProxy * self, guint32 id, guint32 start, static gint wp_endpoint_subscribe_params (WpProxy * self, guint32 n_ids, guint32 *ids) { - struct pw_endpoint *pwp; + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); int endpoint_subscribe_params_result; - pwp = (struct pw_endpoint *) wp_proxy_get_pw_proxy (self); - endpoint_subscribe_params_result = pw_endpoint_subscribe_params (pwp, ids, - n_ids); + endpoint_subscribe_params_result = pw_endpoint_subscribe_params (priv->iface, + ids, n_ids); g_warn_if_fail (endpoint_subscribe_params_result >= 0); return endpoint_subscribe_params_result; @@ -197,11 +144,12 @@ static gint wp_endpoint_set_param (WpProxy * self, guint32 id, guint32 flags, const struct spa_pod *param) { - struct pw_endpoint *pwp; + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); int endpoint_set_param_result; - pwp = (struct pw_endpoint *) wp_proxy_get_pw_proxy (self); - endpoint_set_param_result = pw_endpoint_set_param (pwp, id, flags, param); + endpoint_set_param_result = pw_endpoint_set_param (priv->iface, id, flags, + param); g_warn_if_fail (endpoint_set_param_result >= 0); return endpoint_set_param_result; @@ -213,9 +161,14 @@ endpoint_event_info (void *data, const struct pw_endpoint_info *info) WpEndpoint *self = WP_ENDPOINT (data); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - priv->info = endpoint_info_update (priv->info, &priv->properties, info); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + priv->info = pw_endpoint_info_update (priv->info, info); + + if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) { + g_clear_pointer (&priv->properties, wp_properties_unref); + priv->properties = wp_properties_new_wrap_dict (priv->info->props); + } + wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); g_object_notify (G_OBJECT (self), "info"); if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) @@ -234,8 +187,9 @@ wp_endpoint_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) WpEndpoint *self = WP_ENDPOINT (proxy); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - pw_endpoint_add_listener ((struct pw_endpoint *) pw_proxy, - &priv->listener, &endpoint_events, self); + priv->iface = (struct pw_endpoint *) pw_proxy; + pw_endpoint_add_listener (priv->iface, &priv->listener, &endpoint_events, + self); } static void @@ -300,17 +254,10 @@ set_control (WpEndpoint * self, guint32 control_id, { char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf)); - struct pw_endpoint *pw_proxy = NULL; - - /* set the default endpoint id as a property param on the endpoint; - our spa_props will be updated by the param event */ - pw_proxy = (struct pw_endpoint *) wp_proxy_get_pw_proxy (WP_PROXY (self)); - if (!pw_proxy) - return FALSE; + /* our spa_props will be updated by the param event */ - pw_endpoint_set_param (pw_proxy, - SPA_PARAM_Props, 0, + WP_PROXY_GET_CLASS (self)->set_param (WP_PROXY (self), SPA_PARAM_Props, 0, spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, control_id, SPA_POD_Pod (pod))); @@ -546,358 +493,372 @@ wp_endpoint_set_control_float (WpEndpoint * self, guint32 control_id, /* WpImplEndpoint */ -typedef struct _WpImplEndpointPrivate WpImplEndpointPrivate; -struct _WpImplEndpointPrivate -{ - WpEndpointPrivate *pp; - struct pw_endpoint_info info; - struct spa_param_info param_info[2]; +enum { + IMPL_PROP_0, + IMPL_PROP_ITEM, }; -G_DEFINE_TYPE_WITH_PRIVATE (WpImplEndpoint, wp_impl_endpoint, WP_TYPE_ENDPOINT) - -static void -wp_impl_endpoint_init (WpImplEndpoint * self) +struct _WpImplEndpoint { - WpImplEndpointPrivate *priv = wp_impl_endpoint_get_instance_private (self); + WpEndpoint parent; - /* store a pointer to the parent's private; we use that structure - as well to optimize memory usage and to be able to re-use some of the - parent's methods without reimplementing them */ - priv->pp = wp_endpoint_get_instance_private (WP_ENDPOINT (self)); + struct spa_interface iface; + struct spa_hook_list hooks; + struct pw_endpoint_info info; + gboolean subscribed; - priv->pp->properties = wp_properties_new_empty (); + WpSiEndpoint *item; +}; - priv->param_info[0] = SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - priv->param_info[1] = SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); +G_DEFINE_TYPE (WpImplEndpoint, wp_impl_endpoint, WP_TYPE_ENDPOINT) - priv->info.version = PW_VERSION_ENDPOINT_INFO; - priv->info.props = - (struct spa_dict *) wp_properties_peek_dict (priv->pp->properties); - priv->info.params = priv->param_info; - priv->info.n_params = SPA_N_ELEMENTS (priv->param_info); - priv->pp->info = &priv->info; - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_ENDPOINT_FEATURE_CONTROLS); -} +#define pw_endpoint_emit(hooks,method,version,...) \ + spa_hook_list_call_simple(hooks, struct pw_endpoint_events, \ + method, version, ##__VA_ARGS__) -static void -wp_impl_endpoint_finalize (GObject * object) -{ - WpImplEndpointPrivate *priv = - wp_impl_endpoint_get_instance_private (WP_IMPL_ENDPOINT (object)); +#define pw_endpoint_emit_info(hooks,...) pw_endpoint_emit(hooks, info, 0, ##__VA_ARGS__) +#define pw_endpoint_emit_param(hooks,...) pw_endpoint_emit(hooks, param, 0, ##__VA_ARGS__) - /* set to NULL to prevent parent's finalize from calling free() on it */ - priv->pp->info = NULL; - g_free (priv->info.name); - g_free (priv->info.media_class); +static int +impl_add_listener(void *object, + struct spa_hook *listener, + const struct pw_endpoint_events *events, + void *data) +{ + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); + struct spa_hook_list save; - G_OBJECT_CLASS (wp_impl_endpoint_parent_class)->finalize (object); + spa_hook_list_isolate (&self->hooks, &save, listener, events, data); + pw_endpoint_emit_info (&self->hooks, &self->info); + spa_hook_list_join (&self->hooks, &save); + return 0; } -static void -client_endpoint_update (WpImplEndpoint * self, guint32 change_mask, - guint32 info_change_mask) +static int +impl_enum_params (void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) { - WpImplEndpointPrivate *priv = wp_impl_endpoint_get_instance_private (self); + 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 pw_client_endpoint *pw_proxy = NULL; - struct pw_endpoint_info *info = NULL; - g_autoptr (GPtrArray) params = NULL; - - pw_proxy = (struct pw_client_endpoint *) wp_proxy_get_pw_proxy (WP_PROXY (self)); + struct spa_pod *result; + guint count = 0; - if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS) { - params = wp_spa_props_build_all_pods (&priv->pp->spa_props, &b); - } - if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO) { - info = &priv->info; - info->change_mask = info_change_mask; + switch (id) { + case SPA_PARAM_PropInfo: { + g_autoptr (GPtrArray) params = + wp_spa_props_build_propinfo (&priv->spa_props, &b); + + for (guint i = start; i < params->len; i++) { + struct spa_pod *param = g_ptr_array_index (params, i); + + if (spa_pod_filter (&b, &result, param, filter) == 0) { + pw_endpoint_emit_param (&self->hooks, seq, id, i, i+1, result); + wp_proxy_handle_event_param (self, seq, id, i, i+1, result); + if (++count == num) + break; + } + } + break; + } + case SPA_PARAM_Props: { + if (start == 0) { + struct spa_pod *param = wp_spa_props_build_props (&priv->spa_props, &b); + if (spa_pod_filter (&b, &result, param, filter) == 0) { + pw_endpoint_emit_param (&self->hooks, seq, id, 0, 1, result); + wp_proxy_handle_event_param (self, seq, id, 0, 1, result); + } + } + break; + } + default: + return -ENOENT; } - pw_client_endpoint_update (pw_proxy, - change_mask, - params ? params->len : 0, - (const struct spa_pod **) (params ? params->pdata : NULL), - info); + return 0; +} + +static int +impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) +{ + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - if (info) - info->change_mask = 0; + for (guint i = 0; i < n_ids; i++) { + if (ids[i] == SPA_PARAM_Props) + self->subscribed = TRUE; + impl_enum_params (self, 1, ids[i], 0, UINT32_MAX, NULL); + } + return 0; } static int -client_endpoint_set_param (void *object, - uint32_t id, uint32_t flags, const struct spa_pod *param) +impl_set_param (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) { WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - WpImplEndpointPrivate *priv = wp_impl_endpoint_get_instance_private (self); + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); g_autoptr (GArray) changed_ids = NULL; - guint32 prop_id; if (id != SPA_PARAM_Props) return -ENOENT; changed_ids = g_array_new (FALSE, FALSE, sizeof (guint32)); - wp_spa_props_store_from_props (&priv->pp->spa_props, param, changed_ids); + wp_spa_props_store_from_props (&priv->spa_props, param, changed_ids); + /* notify subscribers */ + if (self->subscribed) + impl_enum_params (self, 1, SPA_PARAM_Props, 0, UINT32_MAX, NULL); + + /* notify controls locally */ for (guint i = 0; i < changed_ids->len; i++) { - prop_id = g_array_index (changed_ids, guint32, i); + guint32 prop_id = g_array_index (changed_ids, guint32, i); g_signal_emit (self, signals[SIGNAL_CONTROL_CHANGED], 0, prop_id); } - client_endpoint_update (self, PW_CLIENT_ENDPOINT_UPDATE_PARAMS, 0); - return 0; } -static struct pw_client_endpoint_events client_endpoint_events = { - PW_VERSION_CLIENT_ENDPOINT_EVENTS, - .set_param = client_endpoint_set_param, +static int +impl_create_link (void *object, const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static const struct pw_endpoint_methods impl_endpoint = { + PW_VERSION_ENDPOINT_METHODS, + .add_listener = impl_add_listener, + .subscribe_params = impl_subscribe_params, + .enum_params = impl_enum_params, + .set_param = impl_set_param, + .create_link = impl_create_link, }; static void -wp_impl_endpoint_augment (WpProxy * proxy, WpProxyFeatures features) +populate_endpoint_info (WpImplEndpoint * self, guint32 change_mask) { - WpImplEndpoint *self = WP_IMPL_ENDPOINT (proxy); - WpImplEndpointPrivate *priv = wp_impl_endpoint_get_instance_private (self); + self->info.change_mask = change_mask & PW_ENDPOINT_CHANGE_MASK_ALL; - /* if any of the default features is requested, make sure BOUND - is also requested, as they all depend on binding the endpoint */ - if (features & WP_PROXY_FEATURES_STANDARD) - features |= WP_PROXY_FEATURE_BOUND; + if (change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS) { + self->info.n_streams = wp_si_endpoint_get_n_streams (self->item); + } - if (features & WP_PROXY_FEATURE_BOUND) { - g_autoptr (WpCore) core = wp_proxy_get_core (proxy); - struct pw_core *pw_core = wp_core_get_pw_core (core); - struct pw_proxy *pw_proxy = NULL; + if (change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION) { + g_autoptr (WpSession) session = + wp_session_item_get_session (WP_SESSION_ITEM (self->item)); + self->info.session_id = + session ? wp_proxy_get_bound_id (WP_PROXY (session)) : SPA_ID_INVALID; + } - /* no pw_core -> we are not connected */ - if (!pw_core) { - wp_proxy_augment_error (proxy, g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, - "The WirePlumber core is not connected; " - "object cannot be exported to PipeWire")); - return; - } + if (change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) { + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - /* make sure these props are not present; they are added by the server */ - wp_properties_set (priv->pp->properties, PW_KEY_OBJECT_ID, NULL); - wp_properties_set (priv->pp->properties, PW_KEY_CLIENT_ID, NULL); - wp_properties_set (priv->pp->properties, PW_KEY_FACTORY_ID, NULL); + g_clear_pointer (&priv->properties, wp_properties_unref); + priv->properties = wp_si_endpoint_get_properties (self->item); - /* add must-have global properties */ - wp_properties_set (priv->pp->properties, - PW_KEY_ENDPOINT_NAME, priv->info.name); - wp_properties_set (priv->pp->properties, - PW_KEY_MEDIA_CLASS, priv->info.media_class); + self->info.props = priv->properties ? + (struct spa_dict *) wp_properties_peek_dict (priv->properties) : NULL; - pw_proxy = pw_core_create_object (pw_core, "client-endpoint", - PW_TYPE_INTERFACE_ClientEndpoint, PW_VERSION_CLIENT_ENDPOINT, - wp_properties_peek_dict (priv->pp->properties), 0); - wp_proxy_set_pw_proxy (proxy, pw_proxy); + g_object_notify (G_OBJECT (self), "properties"); + } - pw_client_endpoint_add_listener (pw_proxy, &priv->pp->listener, - &client_endpoint_events, self); + if (change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) { + static struct spa_param_info param_info[] = { + SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE), + SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ) + }; - client_endpoint_update (WP_IMPL_ENDPOINT (self), - PW_CLIENT_ENDPOINT_UPDATE_PARAMS | PW_CLIENT_ENDPOINT_UPDATE_INFO, - PW_ENDPOINT_CHANGE_MASK_ALL); + self->info.params = param_info; + self->info.n_params = SPA_N_ELEMENTS (param_info); } + + g_object_notify (G_OBJECT (self), "info"); } -static gint -wp_impl_endpoint_set_param (WpProxy * self, guint32 id, guint32 flags, - const struct spa_pod *param) +static void +on_si_endpoint_properties_changed (WpSiEndpoint * item, WpImplEndpoint * self) { - return client_endpoint_set_param (self, id, flags, param); + populate_endpoint_info (self, PW_ENDPOINT_CHANGE_MASK_PROPS); } -static gboolean -wp_impl_endpoint_set_control (WpEndpoint * endpoint, guint32 control_id, - const struct spa_pod * pod) +static void +wp_impl_endpoint_init (WpImplEndpoint * self) { - WpImplEndpointPrivate *priv = - wp_impl_endpoint_get_instance_private (WP_IMPL_ENDPOINT (endpoint)); + /* 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 */ + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - if (wp_spa_props_store_pod (&priv->pp->spa_props, control_id, pod) < 0) - return FALSE; + self->iface = SPA_INTERFACE_INIT ( + PW_TYPE_INTERFACE_Endpoint, + PW_VERSION_ENDPOINT, + &impl_endpoint, self); + spa_hook_list_init (&self->hooks); - g_signal_emit (endpoint, signals[SIGNAL_CONTROL_CHANGED], 0, control_id); + priv->iface = (struct pw_endpoint *) &self->iface; - /* update only after the endpoint has been exported */ - if (wp_proxy_get_features (WP_PROXY (endpoint)) & WP_PROXY_FEATURE_BOUND) { - client_endpoint_update (WP_IMPL_ENDPOINT (endpoint), - PW_CLIENT_ENDPOINT_UPDATE_PARAMS, 0); - } - - return TRUE; + wp_proxy_set_feature_ready (WP_PROXY (self), WP_ENDPOINT_FEATURE_CONTROLS); } static void -wp_impl_endpoint_class_init (WpImplEndpointClass * klass) +wp_impl_endpoint_finalize (GObject * object) { - GObjectClass *object_class = (GObjectClass *) klass; - WpProxyClass *proxy_class = (WpProxyClass *) klass; - WpEndpointClass *endpoint_class = (WpEndpointClass *) klass; - - object_class->finalize = wp_impl_endpoint_finalize; - - proxy_class->augment = wp_impl_endpoint_augment; - proxy_class->enum_params = NULL; - proxy_class->subscribe_params = NULL; - proxy_class->set_param = wp_impl_endpoint_set_param; + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - proxy_class->pw_proxy_created = NULL; - proxy_class->param = NULL; + g_free (self->info.name); + g_free (self->info.media_class); + priv->info = NULL; - endpoint_class->set_control = wp_impl_endpoint_set_control; + G_OBJECT_CLASS (wp_impl_endpoint_parent_class)->finalize (object); } -/** - * wp_impl_endpoint_new: - * @core: the #WpCore - * - * Returns: (transfer full): the newly constructed endpoint implementation - */ -WpImplEndpoint * -wp_impl_endpoint_new (WpCore * core) +static void +wp_impl_endpoint_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) { - g_return_val_if_fail (WP_IS_CORE (core), NULL); + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - return g_object_new (WP_TYPE_IMPL_ENDPOINT, - "core", core, - NULL); + switch (property_id) { + case IMPL_PROP_ITEM: + self->item = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } } -/** - * wp_impl_endpoint_set_property: - * @self: the endpoint implementation - * @key: a property key - * @value: a property value - * - * Sets the specified property on the PipeWire properties of the endpoint. - * - * If this property is set before exporting the endpoint, then it is also used - * in the construction process of the endpoint object and appears as a global - * property. - */ -void -wp_impl_endpoint_set_property (WpImplEndpoint * self, - const gchar * key, const gchar * value) +static void +wp_impl_endpoint_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) { - WpImplEndpointPrivate *priv; - - g_return_if_fail (WP_IS_IMPL_ENDPOINT (self)); - priv = wp_impl_endpoint_get_instance_private (self); - - wp_properties_set (priv->pp->properties, key, value); - - g_object_notify (G_OBJECT (self), "properties"); + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - /* update only after the endpoint has been exported */ - if (wp_proxy_get_features (WP_PROXY (self)) & WP_PROXY_FEATURE_BOUND) { - client_endpoint_update (self, PW_CLIENT_ENDPOINT_UPDATE_INFO, - PW_ENDPOINT_CHANGE_MASK_PROPS); + switch (property_id) { + case IMPL_PROP_ITEM: + g_value_set_object (value, self->item); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; } } -/** - * wp_impl_endpoint_update_properties: - * @self: the endpoint implementation - * @updates: a set of properties to add or update in the endpoint's properties - * - * Adds or updates the values of the PipeWire properties of the endpoint - * using the properties in @updates as a source. - * - * If the properties are set before exporting the endpoint, then they are also - * used in the construction process of the endpoint object and appear as - * global properties. - */ -void -wp_impl_endpoint_update_properties (WpImplEndpoint * self, - WpProperties * updates) +static void +wp_impl_endpoint_augment (WpProxy * proxy, WpProxyFeatures features) { - WpImplEndpointPrivate *priv; + WpImplEndpoint *self = WP_IMPL_ENDPOINT (proxy); + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); + g_autoptr (GVariant) info = NULL; + g_autoptr (GVariantIter) immutable_props = NULL; + g_autoptr (WpProperties) props = NULL; + + /* PW_PROXY depends on BOUND */ + if (features & WP_PROXY_FEATURE_PW_PROXY) + features |= WP_PROXY_FEATURE_BOUND; - g_return_if_fail (WP_IS_IMPL_ENDPOINT (self)); - priv = wp_impl_endpoint_get_instance_private (self); + /* BOUND depends on INFO */ + if (features & WP_PROXY_FEATURE_BOUND) + features |= WP_PROXY_FEATURE_INFO; + + if (features & WP_PROXY_FEATURE_INFO) { + guchar direction; + const gchar *key, *value; + + /* initialize info struct */ + priv->info = &self->info; + self->info.version = PW_VERSION_ENDPOINT_INFO; + + info = wp_si_endpoint_get_registration_info (self->item); + g_variant_get (info, "(ssya{ss})", + &self->info.name, + &self->info.media_class, + &direction, + &immutable_props); + self->info.direction = (enum pw_direction) direction; + + populate_endpoint_info (self, PW_ENDPOINT_CHANGE_MASK_ALL); + + /* subscribe to changes */ + g_signal_connect_object (self->item, "endpoint-properties-changed", + G_CALLBACK (on_si_endpoint_properties_changed), self, 0); + + /* construct export properties (these will come back through + the registry and appear in wp_proxy_get_global_properties) */ + props = wp_properties_new ( + PW_KEY_ENDPOINT_NAME, self->info.name, + PW_KEY_MEDIA_CLASS, self->info.media_class, + NULL); + if (self->info.session_id != SPA_ID_INVALID) { + wp_properties_setf (props, PW_KEY_SESSION_ID, "%u", + self->info.session_id); + } + while (g_variant_iter_next (immutable_props, "{&s&s}", &key, &value)) { + wp_properties_set (props, key, value); + } + + wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + } - wp_properties_update_from_dict (priv->pp->properties, - wp_properties_peek_dict (updates)); + if (features & WP_PROXY_FEATURE_BOUND) { + g_autoptr (WpCore) core = wp_proxy_get_core (proxy); + struct pw_core *pw_core = wp_core_get_pw_core (core); - g_object_notify (G_OBJECT (self), "properties"); + /* no pw_core -> we are not connected */ + if (!pw_core) { + wp_proxy_augment_error (proxy, g_error_new (WP_DOMAIN_LIBRARY, + WP_LIBRARY_ERROR_OPERATION_FAILED, + "The WirePlumber core is not connected; " + "object cannot be exported to PipeWire")); + return; + } - /* update only after the endpoint has been exported */ - if (wp_proxy_get_features (WP_PROXY (self)) & WP_PROXY_FEATURE_BOUND) { - client_endpoint_update (self, PW_CLIENT_ENDPOINT_UPDATE_INFO, - PW_ENDPOINT_CHANGE_MASK_PROPS); + wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core, + PW_TYPE_INTERFACE_Endpoint, + wp_properties_peek_dict (props), + priv->iface, 0)); } } -/** - * wp_impl_endpoint_set_name: - * @self: the endpoint implementation - * @name: the name to set - * - * Sets the name of the endpoint to be @name. - * - * This only makes sense to set before exporting the endpoint. - */ -void -wp_impl_endpoint_set_name (WpImplEndpoint * self, const gchar * name) +static void +wp_impl_endpoint_class_init (WpImplEndpointClass * klass) { - WpImplEndpointPrivate *priv; - - g_return_if_fail (WP_IS_IMPL_ENDPOINT (self)); - priv = wp_impl_endpoint_get_instance_private (self); + GObjectClass *object_class = (GObjectClass *) klass; + WpProxyClass *proxy_class = (WpProxyClass *) klass; - g_free (priv->info.name); - priv->info.name = g_strdup (name); -} + object_class->finalize = wp_impl_endpoint_finalize; + object_class->set_property = wp_impl_endpoint_set_property; + object_class->get_property = wp_impl_endpoint_get_property; -/** - * wp_impl_endpoint_set_media_class: - * @self: the endpoint implementation - * @media_class: the media class to set - * - * Sets the media class of the endpoint to be @media_class. - * - * This only makes sense to set before exporting the endpoint. - */ -void -wp_impl_endpoint_set_media_class (WpImplEndpoint * self, - const gchar * media_class) -{ - WpImplEndpointPrivate *priv; + proxy_class->augment = wp_impl_endpoint_augment; - g_return_if_fail (WP_IS_IMPL_ENDPOINT (self)); - priv = wp_impl_endpoint_get_instance_private (self); + proxy_class->pw_proxy_created = NULL; + proxy_class->param = NULL; - g_free (priv->info.media_class); - priv->info.media_class = g_strdup (media_class); + g_object_class_install_property (object_class, IMPL_PROP_ITEM, + g_param_spec_object ("item", "item", "item", WP_TYPE_SI_ENDPOINT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } -/** - * wp_impl_endpoint_set_direction: - * @self: the endpoint implementation - * @dir: the direction to set - * - * Sets the direction of the endpoint to be @dir. - * - * This only makes sense to set before exporting the endpoint. - */ -void -wp_impl_endpoint_set_direction (WpImplEndpoint * self, WpDirection dir) +WpImplEndpoint * +wp_impl_endpoint_new (WpCore * core, WpSiEndpoint * item) { - WpImplEndpointPrivate *priv; - - g_return_if_fail (WP_IS_IMPL_ENDPOINT (self)); - priv = wp_impl_endpoint_get_instance_private (self); + g_return_val_if_fail (WP_IS_CORE (core), NULL); - priv->info.direction = (enum pw_direction) dir; + return g_object_new (WP_TYPE_IMPL_ENDPOINT, + "core", core, + "item", item, + NULL); } +#if 0 /** * wp_impl_endpoint_register_control: * @self: the endpoint implementation @@ -936,3 +897,4 @@ wp_impl_endpoint_register_control (WpImplEndpoint * self, break; } } +#endif diff --git a/lib/wp/endpoint.h b/lib/wp/endpoint.h index f3ca9330749b850eb143a38a3a7a32b3267bc497..ad500c7c564ab2b553cb47a1184260867b4f7b9f 100644 --- a/lib/wp/endpoint.h +++ b/lib/wp/endpoint.h @@ -112,48 +112,6 @@ WP_API gboolean wp_endpoint_set_control_float (WpEndpoint * self, guint32 control_id, gfloat value); -/** - * WP_TYPE_IMPL_ENDPOINT: - * - * The #WpImplEndpoint #GType - */ -#define WP_TYPE_IMPL_ENDPOINT (wp_impl_endpoint_get_type ()) -WP_API -G_DECLARE_DERIVABLE_TYPE (WpImplEndpoint, wp_impl_endpoint, - WP, IMPL_ENDPOINT, WpEndpoint) - -struct _WpImplEndpointClass -{ - WpEndpointClass parent_class; -}; - -WP_API -WpImplEndpoint * wp_impl_endpoint_new (WpCore * core); - -WP_API -void wp_impl_endpoint_set_property (WpImplEndpoint * self, - const gchar * key, const gchar * value); - -WP_API -void wp_impl_endpoint_update_properties (WpImplEndpoint * self, - WpProperties * updates); - -WP_API -void wp_impl_endpoint_set_name (WpImplEndpoint * self, - const gchar * name); - -WP_API -void wp_impl_endpoint_set_media_class (WpImplEndpoint * self, - const gchar * media_class); - -WP_API -void wp_impl_endpoint_set_direction (WpImplEndpoint * self, - WpDirection dir); - -WP_API -void wp_impl_endpoint_register_control (WpImplEndpoint * self, - WpEndpointControl control); - G_END_DECLS #endif diff --git a/lib/wp/private.h b/lib/wp/private.h index e48b2d6df96ba3ab23dfca991005733aac616cea..660512669b4a1de7e250f95a0b2824515697db0e 100644 --- a/lib/wp/private.h +++ b/lib/wp/private.h @@ -12,6 +12,8 @@ #include "core.h" #include "object-manager.h" #include "proxy.h" +#include "endpoint.h" +#include "si-interfaces.h" #include <stdint.h> #include <pipewire/pipewire.h> @@ -135,6 +137,10 @@ gint wp_spa_props_store_pod (WpSpaProps * self, guint32 id, gint wp_spa_props_store_from_props (WpSpaProps * self, const struct spa_pod * props, GArray * changed_ids); +struct spa_pod * wp_spa_props_build_props (WpSpaProps * self, + struct spa_pod_builder * b); +GPtrArray * wp_spa_props_build_propinfo (WpSpaProps * self, + struct spa_pod_builder * b); GPtrArray * wp_spa_props_build_all_pods (WpSpaProps * self, struct spa_pod_builder * b); struct spa_pod * wp_spa_props_build_update (WpSpaProps * self, guint32 id, @@ -168,6 +174,14 @@ wp_spa_props_build_pod (gchar * buffer, gsize size, ...) wp_spa_props_build_pod (b, sizeof (b), ##__VA_ARGS__, NULL)); \ }) +/* impl endpoint */ + +#define WP_TYPE_IMPL_ENDPOINT (wp_impl_endpoint_get_type ()) +G_DECLARE_FINAL_TYPE (WpImplEndpoint, wp_impl_endpoint, + WP, IMPL_ENDPOINT, WpEndpoint) + +WpImplEndpoint * wp_impl_endpoint_new (WpCore * core, WpSiEndpoint * item); + G_END_DECLS #endif diff --git a/lib/wp/session-item.c b/lib/wp/session-item.c index 0a7d9f9e16c7529944955be7d4740c23f6707761..5c88a0cf252e4d513982ff3315ba9f49998c8f2f 100644 --- a/lib/wp/session-item.c +++ b/lib/wp/session-item.c @@ -12,6 +12,8 @@ */ #include "session-item.h" +#include "private.h" +#include "error.h" #include "wpenums.h" typedef struct _WpSessionItemPrivate WpSessionItemPrivate; @@ -19,6 +21,8 @@ struct _WpSessionItemPrivate { GWeakRef session; guint32 flags; + + WpImplEndpoint *impl_endpoint; }; enum { @@ -56,8 +60,7 @@ static void wp_session_item_finalize (GObject * object) { WpSessionItem * self = WP_SESSION_ITEM (object); - WpSessionItemPrivate *priv = - wp_session_item_get_instance_private (self); + WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); g_weak_ref_clear (&priv->session); @@ -76,13 +79,92 @@ wp_session_item_default_get_next_step (WpSessionItem * self, static void wp_session_item_default_reset (WpSessionItem * self) { - WpSessionItemPrivate *priv = - wp_session_item_get_instance_private (self); + WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); + + wp_session_item_unexport (self); priv->flags &= ~(WP_SI_FLAG_ACTIVE | WP_SI_FLAG_IN_ERROR); g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); } +static void +on_export_proxy_augmented (WpProxy * proxy, GAsyncResult * res, gpointer data) +{ + g_autoptr (GTask) task = G_TASK (data); + g_autoptr (GError) error = NULL; + WpSessionItem *self = WP_SESSION_ITEM (g_task_get_source_object (task)); + WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); + + priv->flags &= ~WP_SI_FLAG_EXPORTING; + + if (!wp_proxy_augment_finish (proxy, res, &error)) { + g_weak_ref_set (&priv->session, NULL); + g_clear_object (&priv->impl_endpoint); + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + priv->flags |= WP_SI_FLAG_EXPORTED; + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); + g_task_return_boolean (task, TRUE); +} + +static void +wp_session_item_default_export (WpSessionItem * self, + WpSession * session, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer callback_data) +{ + WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); + g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (session)); + g_autoptr (GTask) task = NULL; + + g_return_if_fail (priv->flags & WP_SI_FLAG_ACTIVE); + g_return_if_fail (!(priv->flags & (WP_SI_FLAG_EXPORTING | WP_SI_FLAG_EXPORTED))); + + task = g_task_new (self, cancellable, callback, callback_data); + g_task_set_source_tag (task, wp_session_item_default_export); + + if (WP_IS_SI_ENDPOINT (self)) { + g_weak_ref_set (&priv->session, session); + priv->flags |= WP_SI_FLAG_EXPORTING; + priv->impl_endpoint = wp_impl_endpoint_new (core, WP_SI_ENDPOINT (self)); + + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); + + wp_proxy_augment (WP_PROXY (priv->impl_endpoint), + WP_PROXY_FEATURES_STANDARD, NULL, + (GAsyncReadyCallback) on_export_proxy_augmented, + g_steal_pointer (&task)); + } + else { + g_task_return_new_error (task, WP_DOMAIN_LIBRARY, + WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "Cannot export WpSessionItem of unknown type (%s:%p)", + G_OBJECT_TYPE_NAME (self), self); + } +} + +static gboolean +wp_session_item_default_export_finish (WpSessionItem * self, + GAsyncResult * res, GError ** error) +{ + g_return_val_if_fail ( + g_async_result_is_tagged (res, wp_session_item_default_export), FALSE); + + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +wp_session_item_default_unexport (WpSessionItem * self) +{ + WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); + + //TODO cancel job if EXPORTING + + g_clear_object (&priv->impl_endpoint); + priv->flags &= ~(WP_SI_FLAG_EXPORTING | WP_SI_FLAG_EXPORTED); +} + static void wp_session_item_class_init (WpSessionItemClass * klass) { @@ -94,6 +176,9 @@ wp_session_item_class_init (WpSessionItemClass * klass) klass->reset = wp_session_item_default_reset; klass->get_next_step = wp_session_item_default_get_next_step; + klass->export = wp_session_item_default_export; + klass->export_finish = wp_session_item_default_export_finish; + klass->unexport = wp_session_item_default_unexport; /** * WpSessionItem::flags-changed: @@ -358,3 +443,68 @@ wp_session_item_reset (WpSessionItem * self) WP_SESSION_ITEM_GET_CLASS (self)->reset (self); } + +/** + * wp_session_item_export: (virtual export) + * @self: the session item + * @session: the session on which to export this item + * @callback: (scope async): a callback to call when exporting is finished + * @callback_data: (closure): data passed to @callback + * + * Exports this item asynchronously on PipeWire, making it part of the + * specified @session. + * + * Exporting only makes sense for endpoints (items that implement #WpSiEndpoint) + * and endpoint links (items that implement #WpSiLink). On other items the + * default implementation will immediately call the @callback, reporting error. + */ +void +wp_session_item_export (WpSessionItem * self, WpSession * session, + GAsyncReadyCallback callback, gpointer callback_data) +{ + g_return_if_fail (WP_IS_SESSION_ITEM (self)); + g_return_if_fail (WP_IS_SESSION (session)); + g_return_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->export); + + WP_SESSION_ITEM_GET_CLASS (self)->export (self, session, NULL, + callback, callback_data); +} + +/** + * wp_session_item_export_finish: (virtual export_finish) + * @self: the session item + * @res: the async operation result + * @error: (out) (optional): the error of the operation, if any + * + * Returns: %TRUE if the item is now exported, %FALSE if there was an error + */ +gboolean +wp_session_item_export_finish (WpSessionItem * self, GAsyncResult * res, + GError ** error) +{ + g_return_val_if_fail (WP_IS_SESSION_ITEM (self), FALSE); + g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->export_finish, FALSE); + + return WP_SESSION_ITEM_GET_CLASS (self)->export_finish (self, res, error); +} + +/** + * wp_session_item_unexport: (virtual unexport) + * @self: the session item + * + * Reverses the effects of a previous call to wp_session_item_export(). + * This means that after this method is called: + * - The item is no longer exported on PipeWire + * - The item is no longer associated with a session + * - If an export operation was in progress, it is cancelled. + * + * If the item was not exported, this method does nothing. + */ +void +wp_session_item_unexport (WpSessionItem * self) +{ + g_return_if_fail (WP_IS_SESSION_ITEM (self)); + g_return_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->unexport); + + WP_SESSION_ITEM_GET_CLASS (self)->unexport (self); +} diff --git a/lib/wp/session-item.h b/lib/wp/session-item.h index 5446581e0f86af3d5266131beee56818280783a0..07407dc838a0cb1e62fa874f3fa4ede20f9af287 100644 --- a/lib/wp/session-item.h +++ b/lib/wp/session-item.h @@ -10,11 +10,10 @@ #define __WIREPLUMBER_SESSION_ITEM_H__ #include "transition.h" +#include "session.h" G_BEGIN_DECLS -typedef struct _WpSession WpSession; - /** * WP_TYPE_SESSION_ITEM: * @@ -29,23 +28,25 @@ G_DECLARE_DERIVABLE_TYPE (WpSessionItem, wp_session_item, * WpSiFlags: * @WP_SI_FLAG_ACTIVATING: set when an activation transition is in progress * @WP_SI_FLAG_ACTIVE: set when an activation transition completes successfully - * @WP_SI_FLAG_EXPORTED: set when the item has exported all necessary objects - * to PipeWire * @WP_SI_FLAG_IN_ERROR: set when there was an error in the activation process; * to recover, the handler must call wp_session_item_reset() before anything * else * @WP_SI_FLAG_CONFIGURED: must be set by subclasses when all the required * (%WP_SI_CONFIG_OPTION_REQUIRED) configuration options have been set + * @WP_SI_FLAG_EXPORTING: set when an export operation is in progress + * @WP_SI_FLAG_EXPORTED: set when the item has exported all necessary objects + * to PipeWire */ typedef enum { /* immutable flags, set internally */ WP_SI_FLAG_ACTIVATING = (1<<0), WP_SI_FLAG_ACTIVE = (1<<1), - WP_SI_FLAG_EXPORTED = (1<<2), - WP_SI_FLAG_IN_ERROR = (1<<3), + WP_SI_FLAG_IN_ERROR = (1<<4), /* flags that can be changed by subclasses */ WP_SI_FLAG_CONFIGURED = (1<<8), + WP_SI_FLAG_EXPORTING = (1<<9), + WP_SI_FLAG_EXPORTED = (1<<10), /* implementation-specific flags */ WP_SI_FLAG_CUSTOM_START = (1<<16), @@ -70,6 +71,9 @@ typedef enum { * @execute_step: Implements #WpTransitionClass.execute_step() for the * transition of wp_session_item_activate() * @reset: See wp_session_item_reset() + * @export: See wp_session_item_export() + * @export_finish: See wp_session_item_export_finish() + * @unexport: See wp_session_item_unexport() */ struct _WpSessionItemClass { @@ -84,6 +88,13 @@ struct _WpSessionItemClass guint step); void (*reset) (WpSessionItem * self); + + void (*export) (WpSessionItem * self, + WpSession * session, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer callback_data); + gboolean (*export_finish) (WpSessionItem * self, GAsyncResult * res, + GError ** error); + void (*unexport) (WpSessionItem * self); }; /* properties */ @@ -121,6 +132,19 @@ gboolean wp_session_item_activate_finish (WpSessionItem * self, WP_API void wp_session_item_reset (WpSessionItem * self); +/* exporting */ + +WP_API +void wp_session_item_export (WpSessionItem * self, WpSession * session, + GAsyncReadyCallback callback, gpointer callback_data); + +WP_API +gboolean wp_session_item_export_finish (WpSessionItem * self, + GAsyncResult * res, GError ** error); + +WP_API +void wp_session_item_unexport (WpSessionItem * self); + G_END_DECLS #endif diff --git a/lib/wp/si-interfaces.c b/lib/wp/si-interfaces.c index a4c2506a126b28a2d95dac7ffd70f6e99931836d..f583d26d928487acde2eca82931f20b1014dab27 100644 --- a/lib/wp/si-interfaces.c +++ b/lib/wp/si-interfaces.c @@ -23,66 +23,33 @@ G_DEFINE_INTERFACE (WpSiEndpoint, wp_si_endpoint, WP_TYPE_SESSION_ITEM) static void wp_si_endpoint_default_init (WpSiEndpointInterface * iface) { -} - -/** - * wp_si_endpoint_get_name: (virtual get_name) - * @self: the session item - * - * Returns: (transfer none): the name of the endpoint - */ -const gchar * -wp_si_endpoint_get_name (WpSiEndpoint * self) -{ - g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), NULL); - g_return_val_if_fail (WP_SI_ENDPOINT_GET_IFACE (self)->get_name, NULL); + g_signal_new ("endpoint-properties-changed", G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); - return WP_SI_ENDPOINT_GET_IFACE (self)->get_name (self); + g_signal_new ("endpoint-streams-changed", G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } /** - * wp_si_endpoint_get_media_class: (virtual get_media_class) + * wp_si_endpoint_get_registration_info: (virtual get_registration_info) * @self: the session item * - * Returns: (transfer none): the media class of the endpoint - */ -const gchar * -wp_si_endpoint_get_media_class (WpSiEndpoint * self) -{ - g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), NULL); - g_return_val_if_fail (WP_SI_ENDPOINT_GET_IFACE (self)->get_media_class, NULL); - - return WP_SI_ENDPOINT_GET_IFACE (self)->get_media_class (self); -} - -/** - * wp_si_endpoint_get_direction: (virtual get_direction) - * @self: the session item - * - * Returns: the direction of the endpoint - */ -WpDirection -wp_si_endpoint_get_direction (WpSiEndpoint * self) -{ - g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), 0); - g_return_val_if_fail (WP_SI_ENDPOINT_GET_IFACE (self)->get_direction, 0); - - return WP_SI_ENDPOINT_GET_IFACE (self)->get_direction (self); -} - -/** - * wp_si_endpoint_get_priority: (virtual get_priority) - * @self: the session item + * This should return information that is used for registering the endpoint, + * as a GVariant tuple of type (ssya{ss}) that contains, in order: + * - s: the endpoint's name + * - s: the media class + * - y: the direction + * - a{ss}: additional properties to be added to the list of global properties * - * Returns: the priority of the endpoint + * Returns: (transfer full): registration info for the endpoint */ -guint -wp_si_endpoint_get_priority (WpSiEndpoint * self) +GVariant * +wp_si_endpoint_get_registration_info (WpSiEndpoint * self) { - g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), 0); - g_return_val_if_fail (WP_SI_ENDPOINT_GET_IFACE (self)->get_priority, 0); + g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), NULL); + g_return_val_if_fail (WP_SI_ENDPOINT_GET_IFACE (self)->get_registration_info, NULL); - return WP_SI_ENDPOINT_GET_IFACE (self)->get_priority (self); + return WP_SI_ENDPOINT_GET_IFACE (self)->get_registration_info (self); } /** @@ -197,21 +164,28 @@ G_DEFINE_INTERFACE (WpSiStream, wp_si_stream, WP_TYPE_SESSION_ITEM) static void wp_si_stream_default_init (WpSiStreamInterface * iface) { + g_signal_new ("stream-properties-changed", G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } /** - * wp_si_stream_get_name: (virtual get_name) + * wp_si_stream_get_registration_info: (virtual get_registration_info) * @self: the session item * - * Returns: (transfer none): the name of the stream + * This should return information that is used for registering the stream, + * as a GVariant tuple of type (sa{ss}) that contains, in order: + * - s: the stream's name + * - a{ss}: additional properties to be added to the list of global properties + * + * Returns: (transfer full): registration info for the stream */ -const gchar * -wp_si_stream_get_name (WpSiStream * self) +GVariant * +wp_si_stream_get_registration_info (WpSiStream * self) { g_return_val_if_fail (WP_IS_SI_STREAM (self), NULL); - g_return_val_if_fail (WP_SI_STREAM_GET_IFACE (self)->get_name, NULL); + g_return_val_if_fail (WP_SI_STREAM_GET_IFACE (self)->get_registration_info, NULL); - return WP_SI_STREAM_GET_IFACE (self)->get_name (self); + return WP_SI_STREAM_GET_IFACE (self)->get_registration_info (self); } /** diff --git a/lib/wp/si-interfaces.h b/lib/wp/si-interfaces.h index cf6dc57f3e7f6301d635801d510df5c3eccfa1bb..f31cf8f4db2884093b2f6861d092dc9be34850d7 100644 --- a/lib/wp/si-interfaces.h +++ b/lib/wp/si-interfaces.h @@ -17,6 +17,8 @@ G_BEGIN_DECLS typedef struct _WpSiStream WpSiStream; + + /** * WP_TYPE_SI_ENDPOINT: * @@ -31,11 +33,7 @@ struct _WpSiEndpointInterface { GTypeInterface interface; - const gchar * (*get_name) (WpSiEndpoint * self); - const gchar * (*get_media_class) (WpSiEndpoint * self); - const gchar * (*get_role) (WpSiEndpoint * self); - WpDirection (*get_direction) (WpSiEndpoint * self); - guint (*get_priority) (WpSiEndpoint * self); + GVariant * (*get_registration_info) (WpSiEndpoint * self); WpProperties * (*get_properties) (WpSiEndpoint * self); guint (*get_n_streams) (WpSiEndpoint * self); @@ -43,16 +41,7 @@ struct _WpSiEndpointInterface }; WP_API -const gchar * wp_si_endpoint_get_name (WpSiEndpoint * self); - -WP_API -const gchar * wp_si_endpoint_get_media_class (WpSiEndpoint * self); - -WP_API -WpDirection wp_si_endpoint_get_direction (WpSiEndpoint * self); - -WP_API -guint wp_si_endpoint_get_priority (WpSiEndpoint * self); +GVariant * wp_si_endpoint_get_registration_info (WpSiEndpoint * self); WP_API WpProperties * wp_si_endpoint_get_properties (WpSiEndpoint * self); @@ -102,14 +91,14 @@ struct _WpSiStreamInterface { GTypeInterface interface; - const gchar * (*get_name) (WpSiStream * self); + GVariant * (*get_registration_info) (WpSiStream * self); WpProperties * (*get_properties) (WpSiStream * self); WpSiEndpoint * (*get_parent_endpoint) (WpSiStream * self); }; WP_API -const gchar * wp_si_stream_get_name (WpSiStream * self); +GVariant * wp_si_stream_get_registration_info (WpSiStream * self); WP_API WpProperties * wp_si_stream_get_properties (WpSiStream * self); diff --git a/lib/wp/spa-props.c b/lib/wp/spa-props.c index aa99248a3cabcc0fbe9831afa52c29f2380f3599..bbe3ab854b0413de27d0b1aaf61adfd516077f67 100644 --- a/lib/wp/spa-props.c +++ b/lib/wp/spa-props.c @@ -224,16 +224,12 @@ wp_spa_props_store_from_props (WpSpaProps * self, const struct spa_pod * props, return count; } -// for exported update / prop_info + props -GPtrArray * -wp_spa_props_build_all_pods (WpSpaProps * self, struct spa_pod_builder * b) +struct spa_pod * +wp_spa_props_build_props (WpSpaProps * self, struct spa_pod_builder * b) { - GPtrArray *res = g_ptr_array_new (); - GList *l; struct spa_pod_frame f; - struct spa_pod *pod; + GList *l; - /* Props */ spa_pod_builder_push_object (b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); for (l = self->entries; l != NULL; l = g_list_next (l)) { struct entry * e = (struct entry *) l->data; @@ -242,10 +238,16 @@ wp_spa_props_build_all_pods (WpSpaProps * self, struct spa_pod_builder * b) spa_pod_builder_primitive (b, e->value); } } - pod = spa_pod_builder_pop (b, &f); - g_ptr_array_add (res, pod); + return spa_pod_builder_pop (b, &f); +} + +GPtrArray * +wp_spa_props_build_propinfo (WpSpaProps * self, struct spa_pod_builder * b) +{ + GPtrArray *res = g_ptr_array_new (); + GList *l; + struct spa_pod *pod; - /* PropInfo */ for (l = self->entries; l != NULL; l = g_list_next (l)) { struct entry * e = (struct entry *) l->data; pod = spa_pod_builder_add_object (b, @@ -259,6 +261,20 @@ wp_spa_props_build_all_pods (WpSpaProps * self, struct spa_pod_builder * b) return res; } +// for exported update / prop_info + props +GPtrArray * +wp_spa_props_build_all_pods (WpSpaProps * self, struct spa_pod_builder * b) +{ + GPtrArray *res; + struct spa_pod *pod; + + pod = wp_spa_props_build_props (self, b); + res = wp_spa_props_build_propinfo (self, b); + g_ptr_array_insert (res, 0, pod); + + return res; +} + // proxy set --> value to props object -> push struct spa_pod * wp_spa_props_build_update (WpSpaProps * self, guint32 id, diff --git a/modules/module-si-adapter.c b/modules/module-si-adapter.c index e94f625abb34790ac39e0bb87276a5031f4fdbfd..221f26014a51ab90256e1dcb9668118da1fb77e3 100644 --- a/modules/module-si-adapter.c +++ b/modules/module-si-adapter.c @@ -380,39 +380,19 @@ si_adapter_multi_endpoint_init (WpSiMultiEndpointInterface * iface) iface->get_endpoint = si_adapter_get_endpoint; } -static const gchar * -si_adapter_get_name (WpSiEndpoint * item) -{ - WpSiAdapter *self = WP_SI_ADAPTER (item); - return self->name; -} - -static const gchar * -si_adapter_get_media_class (WpSiEndpoint * item) -{ - WpSiAdapter *self = WP_SI_ADAPTER (item); - return self->media_class; -} - -static const gchar * -si_adapter_get_role (WpSiEndpoint * item) +static GVariant * +si_adapter_get_registration_info (WpSiEndpoint * item) { WpSiAdapter *self = WP_SI_ADAPTER (item); - return self->role; -} + GVariantBuilder b; -static WpDirection -si_adapter_get_direction (WpSiEndpoint * item) -{ - WpSiAdapter *self = WP_SI_ADAPTER (item); - return self->direction; -} + g_variant_builder_init (&b, G_VARIANT_TYPE ("(ssya{ss})")); + g_variant_builder_add (&b, "s", self->name); + g_variant_builder_add (&b, "s", self->media_class); + g_variant_builder_add (&b, "y", (guchar) self->direction); + g_variant_builder_add (&b, "a{ss}", NULL); -static guint -si_adapter_get_priority (WpSiEndpoint * item) -{ - WpSiAdapter *self = WP_SI_ADAPTER (item); - return self->priority; + return g_variant_builder_end (&b); } static WpProperties * @@ -422,7 +402,10 @@ si_adapter_get_properties (WpSiEndpoint * item) g_autoptr (WpProperties) node_props = NULL; WpProperties *result; - result = wp_properties_new_empty (); + result = wp_properties_new ( + PW_KEY_MEDIA_ROLE, self->role, + "endpoint.priority", self->priority, + NULL); /* copy useful properties from the node */ node_props = wp_proxy_get_properties (WP_PROXY (self->node)); @@ -464,20 +447,22 @@ si_adapter_get_stream (WpSiEndpoint * item, guint index) static void si_adapter_endpoint_init (WpSiEndpointInterface * iface) { - iface->get_name = si_adapter_get_name; - iface->get_media_class = si_adapter_get_media_class; - iface->get_role = si_adapter_get_role; - iface->get_direction = si_adapter_get_direction; - iface->get_priority = si_adapter_get_priority; + iface->get_registration_info = si_adapter_get_registration_info; iface->get_properties = si_adapter_get_properties; iface->get_n_streams = si_adapter_get_n_streams; iface->get_stream = si_adapter_get_stream; } -static const gchar * -si_adapter_get_stream_name (WpSiStream * self) +static GVariant * +si_adapter_get_stream_registration_info (WpSiStream * self) { - return "default"; + GVariantBuilder b; + + g_variant_builder_init (&b, G_VARIANT_TYPE ("(sa{ss})")); + g_variant_builder_add (&b, "s", "default"); + g_variant_builder_add (&b, "a{ss}", NULL); + + return g_variant_builder_end (&b); } static WpProperties * @@ -495,7 +480,7 @@ si_adapter_get_stream_parent_endpoint (WpSiStream * self) static void si_adapter_stream_init (WpSiStreamInterface * iface) { - iface->get_name = si_adapter_get_stream_name; + iface->get_registration_info = si_adapter_get_stream_registration_info; iface->get_properties = si_adapter_get_stream_properties; iface->get_parent_endpoint = si_adapter_get_stream_parent_endpoint; } diff --git a/tests/wp/endpoint.c b/tests/wp/endpoint.c index c7fc68088629965b6e57724d7d3928af184719c3..b292a79fe41da3b82fb68d853ecf1645612bd34d 100644 --- a/tests/wp/endpoint.c +++ b/tests/wp/endpoint.c @@ -12,6 +12,108 @@ #include "test-server.h" +struct _TestSiEndpoint +{ + WpSessionItem parent; + const gchar *name; + const gchar *media_class; + WpDirection direction; +}; + +G_DECLARE_FINAL_TYPE (TestSiEndpoint, test_si_endpoint, + TEST, SI_ENDPOINT, WpSessionItem) + +static GVariant * +test_si_endpoint_get_registration_info (WpSiEndpoint * item) +{ + TestSiEndpoint *self = TEST_SI_ENDPOINT (item); + GVariantBuilder b; + + g_variant_builder_init (&b, G_VARIANT_TYPE ("(ssya{ss})")); + g_variant_builder_add (&b, "s", self->name); + g_variant_builder_add (&b, "s", self->media_class); + g_variant_builder_add (&b, "y", (guchar) self->direction); + g_variant_builder_add (&b, "a{ss}", NULL); + + return g_variant_builder_end (&b); +} + +static WpProperties * +test_si_endpoint_get_properties (WpSiEndpoint * item) +{ + return wp_properties_new ("test.property", "test-value", NULL); +} + +static guint +test_si_endpoint_get_n_streams (WpSiEndpoint * item) +{ + return 1; +} + +static WpSiStream * +test_si_endpoint_get_stream (WpSiEndpoint * item, guint index) +{ + g_return_val_if_fail (index == 0, NULL); + return WP_SI_STREAM (item); +} + +static void +test_si_endpoint_endpoint_init (WpSiEndpointInterface * iface) +{ + iface->get_registration_info = test_si_endpoint_get_registration_info; + iface->get_properties = test_si_endpoint_get_properties; + iface->get_n_streams = test_si_endpoint_get_n_streams; + iface->get_stream = test_si_endpoint_get_stream; +} + +static GVariant * +test_si_endpoint_get_stream_registration_info (WpSiStream * self) +{ + GVariantBuilder b; + + g_variant_builder_init (&b, G_VARIANT_TYPE ("(sa{ss})")); + g_variant_builder_add (&b, "s", "default"); + g_variant_builder_add (&b, "a{ss}", NULL); + + return g_variant_builder_end (&b); +} + +static WpProperties * +test_si_endpoint_get_stream_properties (WpSiStream * self) +{ + return wp_properties_new ("stream.property", "test-value-2", NULL); +} + +static WpSiEndpoint * +test_si_endpoint_get_stream_parent_endpoint (WpSiStream * self) +{ + return WP_SI_ENDPOINT (self); +} + +static void +test_si_endpoint_stream_init (WpSiStreamInterface * iface) +{ + iface->get_registration_info = test_si_endpoint_get_stream_registration_info; + iface->get_properties = test_si_endpoint_get_stream_properties; + iface->get_parent_endpoint = test_si_endpoint_get_stream_parent_endpoint; +} + +G_DEFINE_TYPE_WITH_CODE (TestSiEndpoint, test_si_endpoint, WP_TYPE_SESSION_ITEM, + G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ENDPOINT, test_si_endpoint_endpoint_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_SI_STREAM, test_si_endpoint_stream_init)) + +static void +test_si_endpoint_init (TestSiEndpoint * self) +{ +} + +static void +test_si_endpoint_class_init (TestSiEndpointClass * klass) +{ +} + +/*******************/ + typedef struct { /* the local pipewire server */ WpTestServer server; @@ -29,7 +131,7 @@ typedef struct { WpCore *proxy_core; WpObjectManager *proxy_om; - WpImplEndpoint *impl_endpoint; + WpProxy *impl_endpoint; WpProxy *proxy_endpoint; gint n_events; @@ -114,10 +216,11 @@ test_endpoint_basic_impl_object_added (WpObjectManager *om, { g_debug ("impl object added"); - g_assert_true (WP_IS_IMPL_ENDPOINT (endpoint)); + g_assert_true (WP_IS_ENDPOINT (endpoint)); + g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpImplEndpoint"); g_assert_null (fixture->impl_endpoint); - fixture->impl_endpoint = WP_IMPL_ENDPOINT (endpoint); + fixture->impl_endpoint = WP_PROXY (endpoint); if (++fixture->n_events == 3) g_main_loop_quit (fixture->loop); @@ -129,7 +232,8 @@ test_endpoint_basic_impl_object_removed (WpObjectManager *om, { g_debug ("impl object removed"); - g_assert_true (WP_IS_IMPL_ENDPOINT (endpoint)); + g_assert_true (WP_IS_ENDPOINT (endpoint)); + g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpImplEndpoint"); g_assert_nonnull (fixture->impl_endpoint); fixture->impl_endpoint = NULL; @@ -145,6 +249,7 @@ test_endpoint_basic_proxy_object_added (WpObjectManager *om, g_debug ("proxy object added"); g_assert_true (WP_IS_ENDPOINT (endpoint)); + g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpEndpoint"); g_assert_null (fixture->proxy_endpoint); fixture->proxy_endpoint = WP_PROXY (endpoint); @@ -160,6 +265,7 @@ test_endpoint_basic_proxy_object_removed (WpObjectManager *om, g_debug ("proxy object removed"); g_assert_true (WP_IS_ENDPOINT (endpoint)); + g_assert_cmpstr (G_OBJECT_TYPE_NAME (endpoint), ==, "WpEndpoint"); g_assert_nonnull (fixture->proxy_endpoint); fixture->proxy_endpoint = NULL; @@ -169,22 +275,49 @@ test_endpoint_basic_proxy_object_removed (WpObjectManager *om, } static void -test_endpoint_basic_export_done (WpProxy * endpoint, GAsyncResult * res, +test_endpoint_basic_activate_done (WpSessionItem * item, GAsyncResult * res, TestEndpointFixture *fixture) { g_autoptr (GError) error = NULL; - g_debug ("export done"); + g_debug ("activate done"); - g_assert_true (wp_proxy_augment_finish (endpoint, res, &error)); + g_assert_true (wp_session_item_activate_finish (item, res, &error)); g_assert_no_error (error); +} + +static void +test_endpoint_basic_export_done (WpSessionItem * item, GAsyncResult * res, + TestEndpointFixture *fixture) +{ + g_autoptr (GError) error = NULL; + + g_debug ("export done"); - g_assert_true (WP_IS_IMPL_ENDPOINT (endpoint)); + g_assert_true (wp_session_item_export_finish (item, res, &error)); + g_assert_no_error (error); if (++fixture->n_events == 3) g_main_loop_quit (fixture->loop); } +static void +test_endpoint_basic_session_bound (WpProxy * session, GAsyncResult * res, + TestEndpointFixture *fixture) +{ + g_autoptr (GError) error = NULL; + + g_debug ("session export done"); + + g_assert_true (wp_proxy_augment_finish (session, res, &error)); + g_assert_no_error (error); + + g_assert_true (WP_IS_IMPL_SESSION (session)); + + g_main_loop_quit (fixture->loop); +} + +#if 0 static void test_endpoint_basic_control_changed (WpEndpoint * endpoint, guint32 control_id, TestEndpointFixture *fixture) @@ -209,13 +342,15 @@ test_endpoint_basic_notify_properties (WpEndpoint * endpoint, GParamSpec * param if (++fixture->n_events == 2) g_main_loop_quit (fixture->loop); } +#endif static void test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data) { - g_autoptr (WpImplEndpoint) endpoint = NULL; - gfloat float_value; - gboolean boolean_value; + g_autoptr (TestSiEndpoint) endpoint = NULL; + g_autoptr (WpImplSession) session = NULL; + // gfloat float_value; + // gboolean boolean_value; /* set up the export side */ g_signal_connect (fixture->export_om, "object-added", @@ -223,7 +358,7 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data) g_signal_connect (fixture->export_om, "object-removed", (GCallback) test_endpoint_basic_impl_object_removed, fixture); wp_object_manager_add_interest (fixture->export_om, - WP_TYPE_IMPL_ENDPOINT, NULL, + WP_TYPE_ENDPOINT, NULL, WP_PROXY_FEATURES_STANDARD | WP_ENDPOINT_FEATURE_CONTROLS); wp_core_install_object_manager (fixture->export_core, fixture->export_om); @@ -241,32 +376,27 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data) g_assert_true (wp_core_connect (fixture->proxy_core)); - /* create endpoint */ - endpoint = wp_impl_endpoint_new (fixture->export_core); - wp_impl_endpoint_set_property (endpoint, "test.property", "test-value"); - wp_impl_endpoint_register_control (endpoint, WP_ENDPOINT_CONTROL_VOLUME); - wp_impl_endpoint_register_control (endpoint, WP_ENDPOINT_CONTROL_MUTE); - g_assert_true (wp_endpoint_set_control_float (WP_ENDPOINT (endpoint), - WP_ENDPOINT_CONTROL_VOLUME, 0.7f)); - g_assert_true (wp_endpoint_set_control_boolean (WP_ENDPOINT (endpoint), - WP_ENDPOINT_CONTROL_MUTE, TRUE)); + /* create session */ + session = wp_impl_session_new (fixture->export_core); + wp_proxy_augment (WP_PROXY (session), WP_PROXY_FEATURE_BOUND, NULL, + (GAsyncReadyCallback) test_endpoint_basic_session_bound, fixture); - /* verify properties are set before export */ - { - g_autoptr (WpProperties) props = - wp_proxy_get_properties (WP_PROXY (endpoint)); - g_assert_cmpstr (wp_properties_get (props, "test.property"), ==, - "test-value"); - } - g_assert_true (wp_endpoint_get_control_float (WP_ENDPOINT (endpoint), - WP_ENDPOINT_CONTROL_VOLUME, &float_value)); - g_assert_true (wp_endpoint_get_control_boolean (WP_ENDPOINT (endpoint), - WP_ENDPOINT_CONTROL_MUTE, &boolean_value)); - g_assert_cmpfloat_with_epsilon (float_value, 0.7f, 0.001); - g_assert_cmpint (boolean_value, ==, TRUE); + /* run until session is bound */ + g_main_loop_run (fixture->loop); + g_assert_cmpint (wp_proxy_get_features (WP_PROXY (session)), &, + WP_PROXY_FEATURE_BOUND); + g_assert_cmpint (wp_proxy_get_bound_id (WP_PROXY (session)), >, 0); - /* do export */ - wp_proxy_augment (WP_PROXY (endpoint), WP_PROXY_FEATURE_BOUND, NULL, + /* create endpoint */ + endpoint = g_object_new (test_si_endpoint_get_type (), NULL); + endpoint->name = "test-endpoint"; + endpoint->media_class = "Audio/Source"; + endpoint->direction = WP_DIRECTION_OUTPUT; + wp_session_item_activate (WP_SESSION_ITEM (endpoint), + (GAsyncReadyCallback) test_endpoint_basic_activate_done, fixture); + g_assert_cmpint (wp_session_item_get_flags (WP_SESSION_ITEM (endpoint)), + &, WP_SI_FLAG_ACTIVE); + wp_session_item_export (WP_SESSION_ITEM (endpoint), WP_SESSION (session), (GAsyncReadyCallback) test_endpoint_basic_export_done, fixture); /* run until objects are created and features are cached */ @@ -275,7 +405,6 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data) g_assert_cmpint (fixture->n_events, ==, 3); g_assert_nonnull (fixture->impl_endpoint); g_assert_nonnull (fixture->proxy_endpoint); - g_assert_true (fixture->impl_endpoint == endpoint); /* test round 1: verify the values on the proxy */ @@ -286,14 +415,38 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data) WP_ENDPOINT_FEATURE_CONTROLS); g_assert_cmpuint (wp_proxy_get_bound_id (fixture->proxy_endpoint), ==, - wp_proxy_get_bound_id (WP_PROXY (endpoint))); + wp_proxy_get_bound_id (fixture->impl_endpoint)); { g_autoptr (WpProperties) props = wp_proxy_get_properties (fixture->proxy_endpoint); + g_assert_cmpstr (wp_properties_get (props, "test.property"), ==, "test-value"); } + + { + g_autoptr (WpProperties) props = + wp_proxy_get_global_properties (fixture->proxy_endpoint); + g_autofree gchar * session_id = g_strdup_printf ("%u", + wp_proxy_get_bound_id (WP_PROXY (session))); + + g_assert_cmpstr (wp_properties_get (props, PW_KEY_ENDPOINT_NAME), ==, + "test-endpoint"); + g_assert_cmpstr (wp_properties_get (props, PW_KEY_MEDIA_CLASS), ==, + "Audio/Source"); + g_assert_cmpstr (wp_properties_get (props, PW_KEY_SESSION_ID), ==, + session_id); + } + + g_assert_cmpstr ("test-endpoint", ==, + wp_endpoint_get_name (WP_ENDPOINT (fixture->proxy_endpoint))); + g_assert_cmpstr ("Audio/Source", ==, + wp_endpoint_get_media_class (WP_ENDPOINT (fixture->proxy_endpoint))); + g_assert_cmpint (WP_DIRECTION_OUTPUT, ==, + wp_endpoint_get_direction (WP_ENDPOINT (fixture->proxy_endpoint))); + +#if 0 g_assert_true (wp_endpoint_get_control_float ( WP_ENDPOINT (fixture->proxy_endpoint), WP_ENDPOINT_CONTROL_VOLUME, &float_value)); @@ -390,6 +543,7 @@ test_endpoint_basic (TestEndpointFixture *fixture, gconstpointer data) g_assert_cmpstr (wp_properties_get (props, "test.property"), ==, "changed-value"); } +#endif /* destroy impl endpoint */ fixture->n_events = 0;