diff --git a/lib/wp/client.c b/lib/wp/client.c index 10b08739f845b5190128738af84e5984d3e254b3..5622e3adc0895b3db570f60a618924f4de18f188 100644 --- a/lib/wp/client.c +++ b/lib/wp/client.c @@ -19,11 +19,10 @@ struct _WpClient { WpGlobalProxy parent; - struct pw_client_info *info; - struct spa_hook listener; }; -static void wp_client_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_client_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpClient: @@ -34,67 +33,46 @@ static void wp_client_pipewire_object_interface_init (WpPipewireObjectInterface * #WpObjectManager API. */ G_DEFINE_TYPE_WITH_CODE (WpClient, wp_client, WP_TYPE_GLOBAL_PROXY, - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_client_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_client_pw_object_mixin_priv_interface_init)) static void wp_client_init (WpClient * self) { } -static WpObjectFeatures -wp_client_get_supported_features (WpObject * object) -{ - return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO; -} - static void wp_client_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - /* just wait, info will be emitted anyway after binding */ - break; - default: + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ WP_OBJECT_CLASS (wp_client_parent_class)-> activate_execute_step (object, transition, step, missing); break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + default: + g_assert_not_reached (); } } -static void -client_event_info(void *data, const struct pw_client_info *info) -{ - WpClient *self = WP_CLIENT (data); - - self->info = pw_client_info_update (self->info, info); - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_CLIENT_CHANGE_MASK_PROPS, 0); -} - static const struct pw_client_events client_events = { PW_VERSION_CLIENT_EVENTS, - .info = client_event_info, + .info = (HandleEventInfoFunc(client)) wp_pw_object_mixin_handle_event_info, }; static void wp_client_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpClient *self = WP_CLIENT (proxy); - pw_client_add_listener ((struct pw_port *) pw_proxy, - &self->listener, &client_events, self); -} - -static void -wp_client_pw_proxy_destroyed (WpProxy * proxy) -{ - g_clear_pointer (&WP_CLIENT (proxy)->info, pw_client_info_free); - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + client, &client_events); } static void @@ -104,49 +82,28 @@ wp_client_class_init (WpClientClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->get_property = wp_pipewire_object_mixin_get_property; + object_class->get_property = wp_pw_object_mixin_get_property; - wpobject_class->get_supported_features = wp_client_get_supported_features; + wpobject_class->get_supported_features = + wp_pw_object_mixin_get_supported_features; wpobject_class->activate_get_next_step = - wp_pipewire_object_mixin_activate_get_next_step; + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_client_activate_execute_step; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Client; proxy_class->pw_iface_version = PW_VERSION_CLIENT; proxy_class->pw_proxy_created = wp_client_pw_proxy_created; - proxy_class->pw_proxy_destroyed = wp_client_pw_proxy_destroyed; - - wp_pipewire_object_mixin_class_override_properties (object_class); -} + proxy_class->pw_proxy_destroyed = + wp_pw_object_mixin_handle_pw_proxy_destroyed; -static gconstpointer -wp_client_get_native_info (WpPipewireObject * obj) -{ - return WP_CLIENT (obj)->info; -} - -static WpProperties * -wp_client_get_properties (WpPipewireObject * obj) -{ - return wp_properties_new_wrap_dict (WP_CLIENT (obj)->info->props); -} - -static GVariant * -wp_client_get_param_info (WpPipewireObject * obj) -{ - return NULL; + wp_pw_object_mixin_class_override_properties (object_class); } static void -wp_client_pipewire_object_interface_init (WpPipewireObjectInterface * iface) +wp_client_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_client_get_native_info; - iface->get_properties = wp_client_get_properties; - iface->get_param_info = wp_client_get_param_info; - iface->enum_params = wp_pipewire_object_mixin_enum_params_unimplemented; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; - iface->set_param = wp_pipewire_object_mixin_set_param_unimplemented; + wp_pw_object_mixin_priv_interface_info_init_no_params (iface, client, CLIENT); } /** diff --git a/lib/wp/device.c b/lib/wp/device.c index 2a5ab8de15fb34cad5efefd9de909e4c679e5134..be96d1d94f742008f81a6d6ff8e3dca91b7b246b 100644 --- a/lib/wp/device.c +++ b/lib/wp/device.c @@ -16,19 +16,20 @@ #include "device.h" #include "node.h" #include "core.h" +#include "debug.h" #include "private/pipewire-object-mixin.h" #include <pipewire/impl.h> #include <spa/monitor/device.h> +#include <spa/utils/result.h> struct _WpDevice { WpGlobalProxy parent; - struct pw_device_info *info; - struct spa_hook listener; }; -static void wp_device_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_device_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpDevice: @@ -43,84 +44,57 @@ static void wp_device_pipewire_object_interface_init (WpPipewireObjectInterface * on the remote PipeWire server by calling into a factory. */ G_DEFINE_TYPE_WITH_CODE (WpDevice, wp_device, WP_TYPE_GLOBAL_PROXY, - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_device_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_device_pw_object_mixin_priv_interface_init)); static void wp_device_init (WpDevice * self) { } -static WpObjectFeatures -wp_device_get_supported_features (WpObject * object) -{ - WpDevice *self = WP_DEVICE (object); - - return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO | - wp_pipewire_object_mixin_param_info_to_features ( - self->info ? self->info->params : NULL, - self->info ? self->info->n_params : 0); -} - static void wp_device_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - wp_pipewire_object_mixin_cache_info (object, transition); - break; - default: + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ WP_OBJECT_CLASS (wp_device_parent_class)-> activate_execute_step (object, transition, step, missing); break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS: + wp_pw_object_mixin_cache_params (object, missing); + break; + default: + g_assert_not_reached (); } } static void wp_device_deactivate (WpObject * object, WpObjectFeatures features) { - wp_pipewire_object_mixin_deactivate (object, features); - + wp_pw_object_mixin_deactivate (object, features); WP_OBJECT_CLASS (wp_device_parent_class)->deactivate (object, features); } -static void -device_event_info(void *data, const struct pw_device_info *info) -{ - WpDevice *self = WP_DEVICE (data); - - self->info = pw_device_info_update (self->info, info); - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_DEVICE_CHANGE_MASK_PROPS, PW_DEVICE_CHANGE_MASK_PARAMS); -} - static const struct pw_device_events device_events = { PW_VERSION_DEVICE_EVENTS, - .info = device_event_info, - .param = wp_pipewire_object_mixin_handle_event_param, + .info = (HandleEventInfoFunc(device)) wp_pw_object_mixin_handle_event_info, + .param = wp_pw_object_mixin_handle_event_param, }; static void wp_device_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpDevice *self = WP_DEVICE (proxy); - pw_device_add_listener ((struct pw_port *) pw_proxy, - &self->listener, &device_events, self); -} - -static void -wp_device_pw_proxy_destroyed (WpProxy * proxy) -{ - g_clear_pointer (&WP_DEVICE (proxy)->info, pw_device_info_free); - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO); - - wp_pipewire_object_mixin_deactivate (WP_OBJECT (proxy), - WP_OBJECT_FEATURES_ALL); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + device, &device_events); } static void @@ -130,66 +104,48 @@ wp_device_class_init (WpDeviceClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->get_property = wp_pipewire_object_mixin_get_property; + object_class->get_property = wp_pw_object_mixin_get_property; - wpobject_class->get_supported_features = wp_device_get_supported_features; + wpobject_class->get_supported_features = + wp_pw_object_mixin_get_supported_features; wpobject_class->activate_get_next_step = - wp_pipewire_object_mixin_activate_get_next_step; + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_device_activate_execute_step; wpobject_class->deactivate = wp_device_deactivate; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Device; proxy_class->pw_iface_version = PW_VERSION_DEVICE; proxy_class->pw_proxy_created = wp_device_pw_proxy_created; - proxy_class->pw_proxy_destroyed = wp_device_pw_proxy_destroyed; + proxy_class->pw_proxy_destroyed = + wp_pw_object_mixin_handle_pw_proxy_destroyed; - wp_pipewire_object_mixin_class_override_properties (object_class); + wp_pw_object_mixin_class_override_properties (object_class); } -static gconstpointer -wp_device_get_native_info (WpPipewireObject * obj) +static gint +wp_device_enum_params (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) { - return WP_DEVICE (obj)->info; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_device_enum_params (d->iface, 0, id, start, num, + filter ? wp_spa_pod_get_spa_pod (filter) : NULL); } -static WpProperties * -wp_device_get_properties (WpPipewireObject * obj) -{ - return wp_properties_new_wrap_dict (WP_DEVICE (obj)->info->props); -} - -static GVariant * -wp_device_get_param_info (WpPipewireObject * obj) -{ - WpDevice *self = WP_DEVICE (obj); - return wp_pipewire_object_mixin_param_info_to_gvariant (self->info->params, - self->info->n_params); -} - -static void -wp_device_enum_params (WpPipewireObject * obj, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) -{ - wp_pipewire_object_mixin_enum_params (pw_device, obj, id, filter, cancellable, - callback, user_data); -} - -static void -wp_device_set_param (WpPipewireObject * obj, const gchar * id, WpSpaPod * param) +static gint +wp_device_set_param (gpointer instance, guint32 id, guint32 flags, + WpSpaPod * param) { - wp_pipewire_object_mixin_set_param (pw_device, obj, id, param); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_device_set_param (d->iface, id, flags, + wp_spa_pod_get_spa_pod (param)); } static void -wp_device_pipewire_object_interface_init (WpPipewireObjectInterface * iface) +wp_device_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_device_get_native_info; - iface->get_properties = wp_device_get_properties; - iface->get_param_info = wp_device_get_param_info; + wp_pw_object_mixin_priv_interface_info_init (iface, device, DEVICE); iface->enum_params = wp_device_enum_params; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; iface->set_param = wp_device_set_param; } diff --git a/lib/wp/endpoint-link.c b/lib/wp/endpoint-link.c index 209c6eb37a80f03a0c750be08ea0fd69d375a44e..4262cd4e4a102d91fc282ca54699f0f64af3a680 100644 --- a/lib/wp/endpoint-link.c +++ b/lib/wp/endpoint-link.c @@ -14,6 +14,7 @@ #define G_LOG_DOMAIN "wp-endpoint-link" #include "endpoint-link.h" +#include "debug.h" #include "error.h" #include "wpenums.h" #include "private/impl-endpoint.h" @@ -29,16 +30,8 @@ enum { static guint32 signals[N_SIGNALS] = {0}; -typedef struct _WpEndpointLinkPrivate WpEndpointLinkPrivate; -struct _WpEndpointLinkPrivate -{ - WpProperties *properties; - struct pw_endpoint_link_info *info; - struct pw_endpoint_link *iface; - struct spa_hook listener; -}; - -static void wp_endpoint_link_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_endpoint_link_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpEndpointLink: @@ -52,107 +45,57 @@ static void wp_endpoint_link_pipewire_object_interface_init (WpPipewireObjectInt * #WpObjectManager API. */ G_DEFINE_TYPE_WITH_CODE (WpEndpointLink, wp_endpoint_link, WP_TYPE_GLOBAL_PROXY, - G_ADD_PRIVATE (WpEndpointLink) - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_endpoint_link_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_endpoint_link_pw_object_mixin_priv_interface_init)); static void wp_endpoint_link_init (WpEndpointLink * self) { } -static WpObjectFeatures -wp_endpoint_link_get_supported_features (WpObject * object) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (object); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO | - wp_pipewire_object_mixin_param_info_to_features ( - priv->info ? priv->info->params : NULL, - priv->info ? priv->info->n_params : 0); -} - static void wp_endpoint_link_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - wp_pipewire_object_mixin_cache_info (object, transition); - break; - default: + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ WP_OBJECT_CLASS (wp_endpoint_link_parent_class)-> activate_execute_step (object, transition, step, missing); break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS: + wp_pw_object_mixin_cache_params (object, missing); + break; + default: + g_assert_not_reached (); } } static void wp_endpoint_link_deactivate (WpObject * object, WpObjectFeatures features) { - wp_pipewire_object_mixin_deactivate (object, features); - + wp_pw_object_mixin_deactivate (object, features); WP_OBJECT_CLASS (wp_endpoint_link_parent_class)->deactivate (object, features); } -static void -endpoint_link_event_info (void *data, const struct pw_endpoint_link_info *info) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (data); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - WpEndpointLinkState old_state = priv->info ? - (WpEndpointLinkState) priv->info->state : WP_ENDPOINT_LINK_STATE_ERROR; - - priv->info = pw_endpoint_link_info_update (priv->info, info); - - if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PROPS) { - g_clear_pointer (&priv->properties, wp_properties_unref); - priv->properties = wp_properties_new_wrap_dict (priv->info->props); - } - - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_STATE) { - g_signal_emit (self, signals[SIGNAL_STATE_CHANGED], 0, - old_state, info->state, info->error); - } - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_ENDPOINT_LINK_CHANGE_MASK_PROPS, PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS); -} - static const struct pw_endpoint_link_events endpoint_link_events = { PW_VERSION_ENDPOINT_LINK_EVENTS, - .info = endpoint_link_event_info, - .param = wp_pipewire_object_mixin_handle_event_param, + .info = (HandleEventInfoFunc(endpoint_link)) wp_pw_object_mixin_handle_event_info, + .param = wp_pw_object_mixin_handle_event_param, }; static void wp_endpoint_link_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpEndpointLink *self = WP_ENDPOINT_LINK (proxy); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - priv->iface = (struct pw_endpoint_link *) pw_proxy; - pw_endpoint_link_add_listener (priv->iface, &priv->listener, - &endpoint_link_events, self); -} - -static void -wp_endpoint_link_pw_proxy_destroyed (WpProxy * proxy) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (proxy); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&priv->info, pw_endpoint_link_info_free); - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO); - - wp_pipewire_object_mixin_deactivate (WP_OBJECT (proxy), - WP_OBJECT_FEATURES_ALL); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + endpoint_link, &endpoint_link_events); } static void @@ -162,12 +105,12 @@ wp_endpoint_link_class_init (WpEndpointLinkClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->get_property = wp_pipewire_object_mixin_get_property; + object_class->get_property = wp_pw_object_mixin_get_property; wpobject_class->get_supported_features = - wp_endpoint_link_get_supported_features; + wp_pw_object_mixin_get_supported_features; wpobject_class->activate_get_next_step = - wp_pipewire_object_mixin_activate_get_next_step; + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_endpoint_link_activate_execute_step; wpobject_class->deactivate = wp_endpoint_link_deactivate; @@ -175,9 +118,10 @@ wp_endpoint_link_class_init (WpEndpointLinkClass * klass) proxy_class->pw_iface_type = PW_TYPE_INTERFACE_EndpointLink; proxy_class->pw_iface_version = PW_VERSION_ENDPOINT_LINK; proxy_class->pw_proxy_created = wp_endpoint_link_pw_proxy_created; - proxy_class->pw_proxy_destroyed = wp_endpoint_link_pw_proxy_destroyed; + proxy_class->pw_proxy_destroyed = + wp_pw_object_mixin_handle_pw_proxy_destroyed; - wp_pipewire_object_mixin_class_override_properties (object_class); + wp_pw_object_mixin_class_override_properties (object_class); /** * WpEndpointLink::state-changed: @@ -195,60 +139,46 @@ wp_endpoint_link_class_init (WpEndpointLinkClass * klass) WP_TYPE_ENDPOINT_LINK_STATE, WP_TYPE_ENDPOINT_LINK_STATE, G_TYPE_STRING); } -static gconstpointer -wp_endpoint_link_get_native_info (WpPipewireObject * obj) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (obj); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - return priv->info; -} - -static WpProperties * -wp_endpoint_link_get_properties (WpPipewireObject * obj) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (obj); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - return wp_properties_ref (priv->properties); -} - -static GVariant * -wp_endpoint_link_get_param_info (WpPipewireObject * obj) +static void +wp_endpoint_link_process_info (gpointer instance, gpointer old_info, gpointer i) { - WpEndpointLink *self = WP_ENDPOINT_LINK (obj); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); + const struct pw_endpoint_link_info *info = i; - return wp_pipewire_object_mixin_param_info_to_gvariant (priv->info->params, - priv->info->n_params); + if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_STATE) { + WpEndpointLinkState old_state = old_info ? + (WpEndpointLinkState) ((struct pw_endpoint_link_info *) old_info)->state + : WP_ENDPOINT_LINK_STATE_ERROR; + g_signal_emit (instance, signals[SIGNAL_STATE_CHANGED], 0, + old_state, info->state, info->error); + } } -static void -wp_endpoint_link_enum_params (WpPipewireObject * obj, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) +static gint +wp_endpoint_link_enum_params (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) { - wp_pipewire_object_mixin_enum_params (pw_endpoint_link, obj, id, filter, - cancellable, callback, user_data); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_endpoint_link_enum_params (d->iface, 0, id, start, num, + filter ? wp_spa_pod_get_spa_pod (filter) : NULL); } -static void -wp_endpoint_link_set_param (WpPipewireObject * obj, const gchar * id, +static gint +wp_endpoint_link_set_param (gpointer instance, guint32 id, guint32 flags, WpSpaPod * param) { - wp_pipewire_object_mixin_set_param (pw_endpoint_link, obj, id, param); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_endpoint_link_set_param (d->iface, id, flags, + wp_spa_pod_get_spa_pod (param)); } static void -wp_endpoint_link_pipewire_object_interface_init ( - WpPipewireObjectInterface * iface) +wp_endpoint_link_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_endpoint_link_get_native_info; - iface->get_properties = wp_endpoint_link_get_properties; - iface->get_param_info = wp_endpoint_link_get_param_info; + wp_pw_object_mixin_priv_interface_info_init (iface, + endpoint_link, ENDPOINT_LINK); + iface->process_info = wp_endpoint_link_process_info; iface->enum_params = wp_endpoint_link_enum_params; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; iface->set_param = wp_endpoint_link_set_param; } @@ -275,17 +205,18 @@ wp_endpoint_link_get_linked_object_ids (WpEndpointLink * self, { g_return_if_fail (WP_IS_ENDPOINT_LINK (self)); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - g_return_if_fail (priv->info); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + struct pw_endpoint_link_info *info = d->info; + g_return_if_fail (info); if (output_endpoint) - *output_endpoint = priv->info->output_endpoint_id; + *output_endpoint = info->output_endpoint_id; if (output_stream) - *output_stream = priv->info->output_stream_id; + *output_stream = info->output_stream_id; if (input_endpoint) - *input_endpoint = priv->info->input_endpoint_id; + *input_endpoint = info->input_endpoint_id; if (input_stream) - *input_stream = priv->info->input_stream_id; + *input_stream = info->input_stream_id; } /** @@ -304,12 +235,13 @@ wp_endpoint_link_get_state (WpEndpointLink * self, const gchar ** error) { g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), WP_ENDPOINT_LINK_STATE_ERROR); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - g_return_val_if_fail (priv->info, WP_ENDPOINT_LINK_STATE_ERROR); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + struct pw_endpoint_link_info *info = d->info; + g_return_val_if_fail (info, WP_ENDPOINT_LINK_STATE_ERROR); if (error) - *error = priv->info->error; - return (WpEndpointLinkState) priv->info->state; + *error = info->error; + return (WpEndpointLinkState) info->state; } /** @@ -327,10 +259,10 @@ wp_endpoint_link_request_state (WpEndpointLink * self, { g_return_if_fail (WP_IS_ENDPOINT_LINK (self)); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - g_return_if_fail (priv->iface); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + g_return_if_fail (d->iface); - pw_endpoint_link_request_state (priv->iface, + pw_endpoint_link_request_state (d->iface, (enum pw_endpoint_link_state) target); } @@ -346,62 +278,18 @@ struct _WpImplEndpointLink WpEndpointLink parent; struct spa_interface iface; - struct spa_hook_list hooks; struct pw_endpoint_link_info info; + WpProperties *immutable_props; WpSiLink *item; }; -G_DEFINE_TYPE (WpImplEndpointLink, wp_impl_endpoint_link, WP_TYPE_ENDPOINT_LINK) +static void wp_endpoint_link_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); -#define pw_endpoint_link_emit(hooks,method,version,...) \ - spa_hook_list_call_simple(hooks, struct pw_endpoint_link_events, \ - method, version, ##__VA_ARGS__) - -#define pw_endpoint_link_emit_info(hooks,...) \ - pw_endpoint_link_emit(hooks, info, 0, ##__VA_ARGS__) -#define pw_endpoint_link_emit_param(hooks,...) \ - pw_endpoint_link_emit(hooks, param, 0, ##__VA_ARGS__) - -static int -impl_add_listener(void *object, - struct spa_hook *listener, - const struct pw_endpoint_link_events *events, - void *data) -{ - WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (object); - struct spa_hook_list save; - - spa_hook_list_isolate (&self->hooks, &save, listener, events, data); - - self->info.change_mask = PW_ENDPOINT_LINK_CHANGE_MASK_ALL; - pw_endpoint_link_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; - - spa_hook_list_join (&self->hooks, &save); - return 0; -} - -static int -impl_enum_params (void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - return -ENOENT; -} - -static int -impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) -{ - return 0; -} - -static int -impl_set_param (void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - return -ENOENT; -} +G_DEFINE_TYPE_WITH_CODE (WpImplEndpointLink, wp_impl_endpoint_link, WP_TYPE_ENDPOINT_LINK, + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_endpoint_link_impl_pw_object_mixin_priv_interface_init)) static void on_item_activated (WpSessionItem * item, GAsyncResult * res, gpointer data) @@ -444,40 +332,48 @@ impl_request_state (void *object, enum pw_endpoint_link_state state) static const struct pw_endpoint_link_methods impl_endpoint_link = { PW_VERSION_ENDPOINT_LINK_METHODS, - .add_listener = impl_add_listener, - .subscribe_params = impl_subscribe_params, - .enum_params = impl_enum_params, - .set_param = impl_set_param, + .add_listener = + (ImplAddListenerFunc(endpoint_link)) wp_pw_object_mixin_impl_add_listener, + .subscribe_params = wp_pw_object_mixin_impl_subscribe_params, + .enum_params = wp_pw_object_mixin_impl_enum_params, + .set_param = wp_pw_object_mixin_impl_set_param, .request_state = impl_request_state, }; static void -populate_properties (WpImplEndpointLink * self, WpProperties *global_props) +wp_impl_endpoint_link_init (WpImplEndpointLink * self) { - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); - - g_clear_pointer (&priv->properties, wp_properties_unref); - priv->properties = wp_si_link_get_properties (self->item); - if (!priv->properties) - priv->properties = wp_properties_new_empty (); - priv->properties = wp_properties_ensure_unique_owner (priv->properties); - wp_properties_update (priv->properties, global_props); - - self->info.props = priv->properties ? - (struct spa_dict *) wp_properties_peek_dict (priv->properties) : NULL; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + + self->iface = SPA_INTERFACE_INIT ( + PW_TYPE_INTERFACE_EndpointLink, + PW_VERSION_ENDPOINT_LINK, + &impl_endpoint_link, self); + + d->info = &self->info; + d->iface = &self->iface; } static void -on_si_link_properties_changed (WpSiLink * item, WpImplEndpointLink * self) +populate_properties (WpImplEndpointLink * self) { - populate_properties (self, - wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (self))); - g_object_notify (G_OBJECT (self), "properties"); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + + g_clear_pointer (&d->properties, wp_properties_unref); + d->properties = wp_si_link_get_properties (self->item); + if (!d->properties) + d->properties = wp_properties_new_empty (); + d->properties = wp_properties_ensure_unique_owner (d->properties); + wp_properties_update (d->properties, self->immutable_props); - self->info.change_mask = PW_ENDPOINT_LINK_CHANGE_MASK_PROPS; - pw_endpoint_link_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; + self->info.props = (struct spa_dict *) wp_properties_peek_dict (d->properties); +} + +static void +on_si_link_properties_changed (WpSiLink * item, WpImplEndpointLink * self) +{ + populate_properties (self); + wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_LINK_CHANGE_MASK_PROPS); } static void @@ -499,30 +395,98 @@ on_si_link_flags_changed (WpSiLink * item, WpSiFlags flags, g_clear_pointer (&self->info.error, g_free); if (old_state != self->info.state) { - self->info.change_mask = PW_ENDPOINT_LINK_CHANGE_MASK_STATE; - pw_endpoint_link_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; - + wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_LINK_CHANGE_MASK_STATE); g_signal_emit (self, signals[SIGNAL_STATE_CHANGED], 0, old_state, self->info.state, self->info.error); } } static void -wp_impl_endpoint_link_init (WpImplEndpointLink * self) +wp_impl_endpoint_link_constructed (GObject * object) { - /* 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 */ - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); + WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (object); + g_autoptr (GVariant) info = NULL; + g_autoptr (GVariantIter) immutable_props = NULL; + const gchar *key, *value; + WpSiStream *stream; + + self->info.version = PW_VERSION_ENDPOINT_LINK_INFO; + self->info.error = NULL; + self->info.params = NULL; + self->info.n_params = 0; + + /* get info from the interface */ + info = wp_si_link_get_registration_info (self->item); + g_variant_get (info, "a{ss}", &immutable_props); + + /* get the current state */ + self->info.state = + (wp_session_item_get_flags (WP_SESSION_ITEM (self->item)) + & WP_SI_FLAG_ACTIVE) + ? PW_ENDPOINT_LINK_STATE_ACTIVE + : PW_ENDPOINT_LINK_STATE_INACTIVE; + + /* associate with the session, the endpoints and the streams */ + self->info.session_id = wp_session_item_get_associated_proxy_id ( + WP_SESSION_ITEM (self->item), WP_TYPE_SESSION); + + stream = wp_si_link_get_out_stream (self->item); + self->info.output_endpoint_id = wp_session_item_get_associated_proxy_id ( + WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT); + self->info.output_stream_id = wp_session_item_get_associated_proxy_id ( + WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT_STREAM); + + stream = wp_si_link_get_in_stream (self->item); + self->info.input_endpoint_id = wp_session_item_get_associated_proxy_id ( + WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT); + self->info.input_stream_id = wp_session_item_get_associated_proxy_id ( + WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT_STREAM); + + /* construct export properties (these will come back through + the registry and appear in wp_proxy_get_global_properties) */ + self->immutable_props = wp_properties_new_empty (); + wp_properties_setf (self->immutable_props, + PW_KEY_SESSION_ID, "%d", self->info.session_id); + wp_properties_setf (self->immutable_props, + PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT, "%d", self->info.output_endpoint_id); + wp_properties_setf (self->immutable_props, + PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM, "%d", self->info.output_stream_id); + wp_properties_setf (self->immutable_props, + PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT, "%d", self->info.input_endpoint_id); + wp_properties_setf (self->immutable_props, + PW_KEY_ENDPOINT_LINK_INPUT_STREAM, "%d", self->info.input_stream_id); + + /* populate immutable (global) properties */ + while (g_variant_iter_next (immutable_props, "{&s&s}", &key, &value)) + wp_properties_set (self->immutable_props, key, value); + + /* populate standard properties */ + populate_properties (self); + + /* subscribe to changes */ + g_signal_connect_object (self->item, "link-properties-changed", + G_CALLBACK (on_si_link_properties_changed), self, 0); + g_signal_connect_object (self->item, "flags-changed", + G_CALLBACK (on_si_link_flags_changed), self, 0); - self->iface = SPA_INTERFACE_INIT ( - PW_TYPE_INTERFACE_EndpointLink, - PW_VERSION_ENDPOINT_LINK, - &impl_endpoint_link, self); - spa_hook_list_init (&self->hooks); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + + G_OBJECT_CLASS (wp_impl_endpoint_link_parent_class)->constructed (object); +} - priv->iface = (struct pw_endpoint_link *) &self->iface; +static void +wp_impl_endpoint_link_dispose (GObject * object) +{ + WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (object); + + g_clear_pointer (&self->immutable_props, wp_properties_unref); + g_clear_pointer (&self->info.error, g_free); + + wp_object_update_features (WP_OBJECT (self), 0, + WP_PIPEWIRE_OBJECT_FEATURE_INFO); + + G_OBJECT_CLASS (wp_impl_endpoint_link_parent_class)->dispose (object); } static void @@ -563,16 +527,9 @@ wp_impl_endpoint_link_activate_execute_step (WpObject * object, WpObjectFeatures missing) { WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (object); - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND: { - g_autoptr (GVariant) info = NULL; - g_autoptr (GVariantIter) immutable_props = NULL; - g_autoptr (WpProperties) props = NULL; - const gchar *key, *value; - WpSiStream *stream; + case WP_PW_OBJECT_MIXIN_STEP_BIND: { g_autoptr (WpCore) core = wp_object_get_core (object); struct pw_core *pw_core = wp_core_get_pw_core (core); @@ -585,77 +542,11 @@ wp_impl_endpoint_link_activate_execute_step (WpObject * object, return; } - /* get info from the interface */ - info = wp_si_link_get_registration_info (self->item); - g_variant_get (info, "a{ss}", &immutable_props); - - /* get the current state */ - self->info.state = - (wp_session_item_get_flags (WP_SESSION_ITEM (self->item)) - & WP_SI_FLAG_ACTIVE) - ? PW_ENDPOINT_LINK_STATE_ACTIVE - : PW_ENDPOINT_LINK_STATE_INACTIVE; - - /* associate with the session, the endpoints and the streams */ - self->info.session_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (self->item), WP_TYPE_SESSION); - - stream = wp_si_link_get_out_stream (self->item); - self->info.output_endpoint_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT); - self->info.output_stream_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT_STREAM); - - stream = wp_si_link_get_in_stream (self->item); - self->info.input_endpoint_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT); - self->info.input_stream_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (stream), WP_TYPE_ENDPOINT_STREAM); - - /* construct export properties (these will come back through - the registry and appear in wp_proxy_get_global_properties) */ - props = wp_properties_new_empty (); - wp_properties_setf (props, PW_KEY_SESSION_ID, "%d", self->info.session_id); - wp_properties_setf (props, PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT, "%d", - self->info.output_endpoint_id); - wp_properties_setf (props, PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM, "%d", - self->info.output_stream_id); - wp_properties_setf (props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT, "%d", - self->info.input_endpoint_id); - wp_properties_setf (props, PW_KEY_ENDPOINT_LINK_INPUT_STREAM, "%d", - self->info.input_stream_id); - - /* populate immutable (global) properties */ - while (g_variant_iter_next (immutable_props, "{&s&s}", &key, &value)) - wp_properties_set (props, key, value); - - /* populate standard properties */ - populate_properties (self, props); - - /* subscribe to changes */ - g_signal_connect_object (self->item, "link-properties-changed", - G_CALLBACK (on_si_link_properties_changed), self, 0); - g_signal_connect_object (self->item, "flags-changed", - G_CALLBACK (on_si_link_flags_changed), self, 0); - - /* finalize info struct */ - self->info.version = PW_VERSION_ENDPOINT_LINK_INFO; - self->info.error = NULL; - self->info.params = NULL; - self->info.n_params = 0; - priv->info = &self->info; - /* bind */ wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core, PW_TYPE_INTERFACE_EndpointLink, - wp_properties_peek_dict (props), - priv->iface, 0)); - - /* notify */ - wp_object_update_features (object, WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - g_object_notify (G_OBJECT (self), "properties"); - g_object_notify (G_OBJECT (self), "param-info"); - + wp_properties_peek_dict (self->immutable_props), + &self->iface, 0)); break; } default: @@ -665,21 +556,6 @@ wp_impl_endpoint_link_activate_execute_step (WpObject * object, } } -static void -wp_impl_endpoint_link_pw_proxy_destroyed (WpProxy * proxy) -{ - WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (proxy); - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); - - g_signal_handlers_disconnect_by_data (self->item, self); - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&self->info.error, g_free); - priv->info = NULL; - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO); -} - static void wp_impl_endpoint_link_class_init (WpImplEndpointLinkClass * klass) { @@ -687,6 +563,8 @@ wp_impl_endpoint_link_class_init (WpImplEndpointLinkClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; + object_class->constructed = wp_impl_endpoint_link_constructed; + object_class->dispose = wp_impl_endpoint_link_dispose; object_class->set_property = wp_impl_endpoint_link_set_property; object_class->get_property = wp_impl_endpoint_link_get_property; @@ -694,13 +572,41 @@ wp_impl_endpoint_link_class_init (WpImplEndpointLinkClass * klass) wp_impl_endpoint_link_activate_execute_step; proxy_class->pw_proxy_created = NULL; - proxy_class->pw_proxy_destroyed = wp_impl_endpoint_link_pw_proxy_destroyed; + proxy_class->pw_proxy_destroyed = NULL; g_object_class_install_property (object_class, IMPL_PROP_ITEM, g_param_spec_object ("item", "item", "item", WP_TYPE_SI_LINK, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } +#define pw_endpoint_link_emit(hooks,method,version,...) \ + spa_hook_list_call_simple(hooks, struct pw_endpoint_link_events, \ + method, version, ##__VA_ARGS__) + +static void +wp_impl_endpoint_link_emit_info (struct spa_hook_list * hooks, gconstpointer info) +{ + pw_endpoint_link_emit (hooks, info, 0, info); +} + +static void +wp_impl_endpoint_link_emit_param (struct spa_hook_list * hooks, int seq, + guint32 id, guint32 index, guint32 next, const struct spa_pod *param) +{ + pw_endpoint_link_emit (hooks, param, 0, seq, id, index, next, param); +} + +static void +wp_endpoint_link_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) +{ + iface->flags |= WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE; + iface->enum_params = NULL; + iface->set_param = NULL; + iface->emit_info = wp_impl_endpoint_link_emit_info; + iface->emit_param = wp_impl_endpoint_link_emit_param; +} + WpImplEndpointLink * wp_impl_endpoint_link_new (WpCore * core, WpSiLink * item) { diff --git a/lib/wp/endpoint-stream.c b/lib/wp/endpoint-stream.c index 269557f521cf239b3b78a2ea3a25762075876fed..88bcf1facac4803f7ab79bc41dd5a6f51011d1c6 100644 --- a/lib/wp/endpoint-stream.c +++ b/lib/wp/endpoint-stream.c @@ -16,6 +16,8 @@ #include "endpoint-stream.h" #include "node.h" #include "error.h" +#include "debug.h" +#include "spa-type.h" #include "private/impl-endpoint.h" #include "private/pipewire-object-mixin.h" @@ -23,19 +25,11 @@ #include <pipewire/extensions/session-manager/introspect-funcs.h> enum { - PROP_NAME = WP_PIPEWIRE_OBJECT_MIXIN_PROP_CUSTOM_START, + PROP_NAME = WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START, }; -typedef struct _WpEndpointStreamPrivate WpEndpointStreamPrivate; -struct _WpEndpointStreamPrivate -{ - WpProperties *properties; - struct pw_endpoint_stream_info *info; - struct pw_endpoint_stream *iface; - struct spa_hook listener; -}; - -static void wp_endpoint_stream_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_endpoint_stream_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpEndpointStream: @@ -49,8 +43,10 @@ static void wp_endpoint_stream_pipewire_object_interface_init (WpPipewireObjectI * API. */ G_DEFINE_TYPE_WITH_CODE (WpEndpointStream, wp_endpoint_stream, WP_TYPE_GLOBAL_PROXY, - G_ADD_PRIVATE (WpEndpointStream) - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_endpoint_stream_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_endpoint_stream_pw_object_mixin_priv_interface_init)) static void wp_endpoint_stream_init (WpEndpointStream * self) @@ -61,106 +57,60 @@ static void wp_endpoint_stream_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { - WpEndpointStream *self = WP_ENDPOINT_STREAM (object); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); switch (property_id) { case PROP_NAME: - g_value_set_string (value, priv->info ? priv->info->name : NULL); + g_value_set_string (value, d->info ? + ((struct pw_endpoint_stream_info *) d->info)->name : NULL); break; default: - wp_pipewire_object_mixin_get_property (object, property_id, value, pspec); + wp_pw_object_mixin_get_property (object, property_id, value, pspec); break; } } -static WpObjectFeatures -wp_endpoint_stream_get_supported_features (WpObject * object) -{ - WpEndpointStream *self = WP_ENDPOINT_STREAM (object); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO | - wp_pipewire_object_mixin_param_info_to_features ( - priv->info ? priv->info->params : NULL, - priv->info ? priv->info->n_params : 0); -} - static void wp_endpoint_stream_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - wp_pipewire_object_mixin_cache_info (object, transition); - break; - default: + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ WP_OBJECT_CLASS (wp_endpoint_stream_parent_class)-> activate_execute_step (object, transition, step, missing); break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS: + wp_pw_object_mixin_cache_params (object, missing); + break; + default: + g_assert_not_reached (); } } static void wp_endpoint_stream_deactivate (WpObject * object, WpObjectFeatures features) { - wp_pipewire_object_mixin_deactivate (object, features); - + wp_pw_object_mixin_deactivate (object, features); WP_OBJECT_CLASS (wp_endpoint_stream_parent_class)->deactivate (object, features); } -static void -endpoint_stream_event_info (void *data, const struct pw_endpoint_stream_info *info) -{ - WpEndpointStream *self = WP_ENDPOINT_STREAM (data); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - priv->info = pw_endpoint_stream_info_update (priv->info, info); - - if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS) { - g_clear_pointer (&priv->properties, wp_properties_unref); - priv->properties = wp_properties_new_wrap_dict (priv->info->props); - } - - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS, - PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS); -} - static const struct pw_endpoint_stream_events endpoint_stream_events = { PW_VERSION_ENDPOINT_STREAM_EVENTS, - .info = endpoint_stream_event_info, - .param = wp_pipewire_object_mixin_handle_event_param, + .info = (HandleEventInfoFunc(endpoint_stream)) wp_pw_object_mixin_handle_event_info, + .param = wp_pw_object_mixin_handle_event_param, }; static void wp_endpoint_stream_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - priv->iface = (struct pw_endpoint_stream *) pw_proxy; - pw_endpoint_stream_add_listener (priv->iface, &priv->listener, - &endpoint_stream_events, self); -} - -static void -wp_endpoint_stream_pw_proxy_destroyed (WpProxy * proxy) -{ - WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&priv->info, pw_endpoint_stream_info_free); - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO); - - wp_pipewire_object_mixin_deactivate (WP_OBJECT (proxy), - WP_OBJECT_FEATURES_ALL); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + endpoint_stream, &endpoint_stream_events); } static void @@ -173,9 +123,9 @@ wp_endpoint_stream_class_init (WpEndpointStreamClass * klass) object_class->get_property = wp_endpoint_stream_get_property; wpobject_class->get_supported_features = - wp_endpoint_stream_get_supported_features; + wp_pw_object_mixin_get_supported_features; wpobject_class->activate_get_next_step = - wp_pipewire_object_mixin_activate_get_next_step; + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_endpoint_stream_activate_execute_step; wpobject_class->deactivate = wp_endpoint_stream_deactivate; @@ -183,9 +133,10 @@ wp_endpoint_stream_class_init (WpEndpointStreamClass * klass) proxy_class->pw_iface_type = PW_TYPE_INTERFACE_EndpointStream; proxy_class->pw_iface_version = PW_VERSION_ENDPOINT_STREAM; proxy_class->pw_proxy_created = wp_endpoint_stream_pw_proxy_created; - proxy_class->pw_proxy_destroyed = wp_endpoint_stream_pw_proxy_destroyed; + proxy_class->pw_proxy_destroyed = + wp_pw_object_mixin_handle_pw_proxy_destroyed; - wp_pipewire_object_mixin_class_override_properties (object_class); + wp_pw_object_mixin_class_override_properties (object_class); /** * WpEndpointStream:name: @@ -197,60 +148,31 @@ wp_endpoint_stream_class_init (WpEndpointStreamClass * klass) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } -static gconstpointer -wp_endpoint_stream_get_native_info (WpPipewireObject * obj) +static gint +wp_endpoint_stream_enum_params (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) { - WpEndpointStream *self = WP_ENDPOINT_STREAM (obj); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - return priv->info; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_endpoint_stream_enum_params (d->iface, 0, id, start, num, + filter ? wp_spa_pod_get_spa_pod (filter) : NULL); } -static WpProperties * -wp_endpoint_stream_get_properties (WpPipewireObject * obj) -{ - WpEndpointStream *self = WP_ENDPOINT_STREAM (obj); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - return wp_properties_ref (priv->properties); -} - -static GVariant * -wp_endpoint_stream_get_param_info (WpPipewireObject * obj) -{ - WpEndpointStream *self = WP_ENDPOINT_STREAM (obj); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - return wp_pipewire_object_mixin_param_info_to_gvariant (priv->info->params, - priv->info->n_params); -} - -static void -wp_endpoint_stream_enum_params (WpPipewireObject * obj, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) -{ - wp_pipewire_object_mixin_enum_params (pw_endpoint_stream, obj, id, filter, - cancellable, callback, user_data); -} - -static void -wp_endpoint_stream_set_param (WpPipewireObject * obj, const gchar * id, +static gint +wp_endpoint_stream_set_param (gpointer instance, guint32 id, guint32 flags, WpSpaPod * param) { - wp_pipewire_object_mixin_set_param (pw_endpoint_stream, obj, id, param); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_endpoint_stream_set_param (d->iface, id, flags, + wp_spa_pod_get_spa_pod (param)); } static void -wp_endpoint_stream_pipewire_object_interface_init ( - WpPipewireObjectInterface * iface) +wp_endpoint_stream_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_endpoint_stream_get_native_info; - iface->get_properties = wp_endpoint_stream_get_properties; - iface->get_param_info = wp_endpoint_stream_get_param_info; + wp_pw_object_mixin_priv_interface_info_init (iface, + endpoint_stream, ENDPOINT_STREAM); iface->enum_params = wp_endpoint_stream_enum_params; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; iface->set_param = wp_endpoint_stream_set_param; } @@ -267,8 +189,8 @@ wp_endpoint_stream_get_name (WpEndpointStream * self) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - return priv->info->name; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + return ((struct pw_endpoint_stream_info *) d->info)->name; } @@ -284,121 +206,151 @@ struct _WpImplEndpointStream WpEndpointStream parent; struct spa_interface iface; - struct spa_hook_list hooks; struct pw_endpoint_stream_info info; + WpProperties *immutable_props; WpSiStream *item; }; -G_DEFINE_TYPE (WpImplEndpointStream, wp_impl_endpoint_stream, WP_TYPE_ENDPOINT_STREAM) +static void wp_endpoint_stream_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); -#define pw_endpoint_stream_emit(hooks,method,version,...) \ - spa_hook_list_call_simple(hooks, struct pw_endpoint_stream_events, \ - method, version, ##__VA_ARGS__) +G_DEFINE_TYPE_WITH_CODE (WpImplEndpointStream, wp_impl_endpoint_stream, WP_TYPE_ENDPOINT_STREAM, + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_endpoint_stream_impl_pw_object_mixin_priv_interface_init)) -#define pw_endpoint_stream_emit_info(hooks,...) \ - pw_endpoint_stream_emit(hooks, info, 0, ##__VA_ARGS__) -#define pw_endpoint_stream_emit_param(hooks,...) \ - pw_endpoint_stream_emit(hooks, param, 0, ##__VA_ARGS__) - -// static struct spa_param_info impl_param_info[] = { -// SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE), -// SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ) -// }; - -static int -impl_add_listener(void *object, - struct spa_hook *listener, - const struct pw_endpoint_stream_events *events, - void *data) -{ - WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - struct spa_hook_list save; +static struct spa_param_info impl_param_info[] = { + SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE), + SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ) +}; - spa_hook_list_isolate (&self->hooks, &save, listener, events, data); +static const struct pw_endpoint_stream_methods impl_endpoint_stream = { + PW_VERSION_ENDPOINT_STREAM_METHODS, + .add_listener = + (ImplAddListenerFunc(endpoint_stream)) wp_pw_object_mixin_impl_add_listener, + .subscribe_params = wp_pw_object_mixin_impl_subscribe_params, + .enum_params = wp_pw_object_mixin_impl_enum_params, + .set_param = wp_pw_object_mixin_impl_set_param, +}; - self->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_ALL - & ~PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS; - pw_endpoint_stream_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; +static void +wp_impl_endpoint_stream_init (WpImplEndpointStream * self) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); - spa_hook_list_join (&self->hooks, &save); - return 0; -} + self->iface = SPA_INTERFACE_INIT ( + PW_TYPE_INTERFACE_EndpointStream, + PW_VERSION_ENDPOINT_STREAM, + &impl_endpoint_stream, self); -static int -impl_enum_params (void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - return -ENOENT; + d->info = &self->info; + d->iface = &self->iface; } -static int -impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) +static void +populate_properties (WpImplEndpointStream * self) { - return 0; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + + g_clear_pointer (&d->properties, wp_properties_unref); + d->properties = wp_si_stream_get_properties (self->item); + if (!d->properties) + d->properties = wp_properties_new_empty (); + d->properties = wp_properties_ensure_unique_owner (d->properties); + wp_properties_update (d->properties, self->immutable_props); + + self->info.props = (struct spa_dict *) wp_properties_peek_dict (d->properties); } -static int -impl_set_param (void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) +static void +on_si_stream_properties_changed (WpSiStream * item, WpImplEndpointStream * self) { - return -ENOENT; + populate_properties (self); + wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS); } -static const struct pw_endpoint_stream_methods impl_endpoint_stream = { - PW_VERSION_ENDPOINT_STREAM_METHODS, - .add_listener = impl_add_listener, - .subscribe_params = impl_subscribe_params, - .enum_params = impl_enum_params, - .set_param = impl_set_param, -}; - static void -populate_properties (WpImplEndpointStream * self, WpProperties *global_props) +on_node_params_changed (WpNode * node, guint32 param_id, WpImplEndpoint * self) { - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); - - g_clear_pointer (&priv->properties, wp_properties_unref); - priv->properties = wp_si_stream_get_properties (self->item); - if (!priv->properties) - priv->properties = wp_properties_new_empty (); - priv->properties = wp_properties_ensure_unique_owner (priv->properties); - wp_properties_update (priv->properties, global_props); - - self->info.props = priv->properties ? - (struct spa_dict *) wp_properties_peek_dict (priv->properties) : NULL; + if (param_id == SPA_PARAM_PropInfo || param_id == SPA_PARAM_Props) + wp_pw_object_mixin_notify_params_changed (self, param_id); } static void -on_si_stream_properties_changed (WpSiStream * item, WpImplEndpointStream * self) +wp_impl_endpoint_stream_constructed (GObject * object) { - populate_properties (self, - wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (self))); - g_object_notify (G_OBJECT (self), "properties"); + WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); + g_autoptr (GVariant) info = NULL; + g_autoptr (GVariantIter) immutable_props = NULL; + g_autoptr (WpObject) node = NULL; + const gchar *key, *value; + + self->info.version = PW_VERSION_ENDPOINT_STREAM_INFO; - self->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; - pw_endpoint_stream_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; + /* get info from the interface */ + info = wp_si_stream_get_registration_info (self->item); + g_variant_get (info, "(sa{ss})", &self->info.name, &immutable_props); + + /* associate with the endpoint */ + self->info.endpoint_id = wp_session_item_get_associated_proxy_id ( + WP_SESSION_ITEM (self->item), WP_TYPE_ENDPOINT); + + /* construct export properties (these will come back through + the registry and appear in wp_proxy_get_global_properties) */ + self->immutable_props = wp_properties_new ( + PW_KEY_ENDPOINT_STREAM_NAME, self->info.name, + NULL); + wp_properties_setf (self->immutable_props, PW_KEY_ENDPOINT_ID, + "%d", self->info.endpoint_id); + + /* populate immutable (global) properties */ + while (g_variant_iter_next (immutable_props, "{&s&s}", &key, &value)) + wp_properties_set (self->immutable_props, key, value); + + /* populate standard properties */ + populate_properties (self); + + /* subscribe to changes */ + g_signal_connect_object (self->item, "stream-properties-changed", + G_CALLBACK (on_si_stream_properties_changed), self, 0); + + /* if the item has a node, proxy its ParamProps */ + node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); + if (node && (wp_object_get_active_features (node) & + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS)) { + self->info.params = impl_param_info; + self->info.n_params = G_N_ELEMENTS (impl_param_info); + + g_signal_connect_object (node, "params-changed", + G_CALLBACK (on_node_params_changed), self, 0); + + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, 0); + } else { + self->info.params = NULL; + self->info.n_params = 0; + } + + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + + G_OBJECT_CLASS (wp_impl_endpoint_stream_parent_class)->constructed (object); } static void -wp_impl_endpoint_stream_init (WpImplEndpointStream * self) +wp_impl_endpoint_stream_dispose (GObject * object) { - /* 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 */ - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); + WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - self->iface = SPA_INTERFACE_INIT ( - PW_TYPE_INTERFACE_EndpointStream, - PW_VERSION_ENDPOINT_STREAM, - &impl_endpoint_stream, self); - spa_hook_list_init (&self->hooks); + g_clear_pointer (&self->immutable_props, wp_properties_unref); + g_clear_pointer (&self->info.name, g_free); - priv->iface = (struct pw_endpoint_stream *) &self->iface; + wp_object_update_features (WP_OBJECT (self), 0, + WP_PIPEWIRE_OBJECT_FEATURE_INFO | + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS); + + G_OBJECT_CLASS (wp_impl_endpoint_stream_parent_class)->dispose (object); } static void @@ -434,7 +386,7 @@ wp_impl_endpoint_stream_get_property (GObject * object, guint property_id, } enum { - STEP_ACTIVATE_NODE = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, + STEP_ACTIVATE_NODE = WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START, }; static guint @@ -444,27 +396,28 @@ wp_impl_endpoint_stream_activate_get_next_step (WpObject * object, { WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - /* bind if not already bound */ - if (missing & WP_PROXY_FEATURE_BOUND) { + /* before anything else, if the item has a node, + cache its props so that enum_params works */ + if (missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) { g_autoptr (WpObject) node = wp_session_item_get_associated_proxy ( WP_SESSION_ITEM (self->item), WP_TYPE_NODE); - /* if the item has a node, cache its props so that enum_params works */ - // if (node && !(wp_object_get_active_features (node) & - // WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS)) - // return STEP_ACTIVATE_NODE; - // else - return WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND; + if (node && (wp_object_get_supported_features (node) & + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS) && + !(wp_object_get_active_features (node) & + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS)) + return STEP_ACTIVATE_NODE; } - /* cache info if supported */ - else - return WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO; + + return WP_OBJECT_CLASS (wp_impl_endpoint_stream_parent_class)-> + activate_get_next_step (object, transition, step, missing); } static void wp_impl_endpoint_stream_node_activated (WpObject * node, GAsyncResult * res, WpTransition * transition) { + WpImplEndpointStream *self = wp_transition_get_source_object (transition); g_autoptr (GError) error = NULL; if (!wp_object_activate_finish (node, res, &error)) { @@ -472,7 +425,15 @@ wp_impl_endpoint_stream_node_activated (WpObject * node, return; } - wp_transition_advance (transition); + self->info.params = impl_param_info; + self->info.n_params = G_N_ELEMENTS (impl_param_info); + + g_signal_connect_object (node, "params-changed", + G_CALLBACK (on_node_params_changed), self, 0); + + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, 0); + wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS); } static void @@ -481,8 +442,6 @@ wp_impl_endpoint_stream_activate_execute_step (WpObject * object, WpObjectFeatures missing) { WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); switch (step) { case STEP_ACTIVATE_NODE: { @@ -490,15 +449,12 @@ wp_impl_endpoint_stream_activate_execute_step (WpObject * object, WP_SESSION_ITEM (self->item), WP_TYPE_NODE); wp_object_activate (node, - WP_PROXY_FEATURE_BOUND /*| WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS */, + WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, NULL, (GAsyncReadyCallback) wp_impl_endpoint_stream_node_activated, transition); break; } - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND: { - g_autoptr (GVariantIter) immutable_properties = NULL; - g_autoptr (WpProperties) properties = NULL; - const gchar *key, *value; + case WP_PW_OBJECT_MIXIN_STEP_BIND: { g_autoptr (WpCore) core = wp_object_get_core (object); struct pw_core *pw_core = wp_core_get_pw_core (core); @@ -511,57 +467,11 @@ wp_impl_endpoint_stream_activate_execute_step (WpObject * object, return; } - wp_debug_object (self, "exporting"); - - /* get info from the interface */ - { - g_autoptr (GVariant) info = NULL; - info = wp_si_stream_get_registration_info (self->item); - g_variant_get (info, "(sa{ss})", &self->info.name, &immutable_properties); - - /* associate with the endpoint */ - self->info.endpoint_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (self->item), WP_TYPE_ENDPOINT); - } - - /* construct export properties (these will come back through - the registry and appear in wp_proxy_get_global_properties) */ - properties = wp_properties_new ( - PW_KEY_ENDPOINT_STREAM_NAME, self->info.name, - NULL); - wp_properties_setf (properties, PW_KEY_ENDPOINT_ID, - "%d", self->info.endpoint_id); - - /* populate immutable (global) properties */ - while (g_variant_iter_next (immutable_properties, "{&s&s}", &key, &value)) - wp_properties_set (properties, key, value); - - /* populate standard properties */ - populate_properties (self, properties); - - /* subscribe to changes */ - g_signal_connect_object (self->item, "stream-properties-changed", - G_CALLBACK (on_si_stream_properties_changed), self, 0); - - /* finalize info struct */ - self->info.version = PW_VERSION_ENDPOINT_STREAM_INFO; - self->info.params = NULL; - self->info.n_params = 0; - priv->info = &self->info; - /* bind */ wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core, PW_TYPE_INTERFACE_EndpointStream, - wp_properties_peek_dict (properties), - priv->iface, 0)); - - /* notify */ - wp_object_update_features (object, - WP_PIPEWIRE_OBJECT_FEATURE_INFO - /*| WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS */, 0); - g_object_notify (G_OBJECT (self), "properties"); - g_object_notify (G_OBJECT (self), "param-info"); - + wp_properties_peek_dict (self->immutable_props), + &self->iface, 0)); break; } default: @@ -571,22 +481,6 @@ wp_impl_endpoint_stream_activate_execute_step (WpObject * object, } } -static void -wp_impl_endpoint_stream_pw_proxy_destroyed (WpProxy * proxy) -{ - WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (proxy); - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); - - g_signal_handlers_disconnect_by_data (self->item, self); - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&self->info.name, g_free); - priv->info = NULL; - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO - /*| WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS */); -} - static void wp_impl_endpoint_stream_class_init (WpImplEndpointStreamClass * klass) { @@ -594,6 +488,8 @@ wp_impl_endpoint_stream_class_init (WpImplEndpointStreamClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; + object_class->constructed = wp_impl_endpoint_stream_constructed; + object_class->dispose = wp_impl_endpoint_stream_dispose; object_class->set_property = wp_impl_endpoint_stream_set_property; object_class->get_property = wp_impl_endpoint_stream_get_property; @@ -603,13 +499,85 @@ wp_impl_endpoint_stream_class_init (WpImplEndpointStreamClass * klass) wp_impl_endpoint_stream_activate_execute_step; proxy_class->pw_proxy_created = NULL; - proxy_class->pw_proxy_destroyed = wp_impl_endpoint_stream_pw_proxy_destroyed; + proxy_class->pw_proxy_destroyed = NULL; g_object_class_install_property (object_class, IMPL_PROP_ITEM, g_param_spec_object ("item", "item", "item", WP_TYPE_SI_STREAM, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } +static GPtrArray * +wp_impl_endpoint_stream_enum_params_sync (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) +{ + WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (instance); + g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); + + if (!node) { + wp_warning_object (self, "associated node is no longer available"); + return NULL; + } + + /* bypass a few things, knowing that the node + caches params in the mixin param store */ + WpPwObjectMixinData *data = wp_pw_object_mixin_get_data (node); + GPtrArray *params = wp_pw_object_mixin_get_stored_params (data, id); + /* TODO filter */ + + return params; +} + +static gint +wp_impl_endpoint_stream_set_param (gpointer instance, guint32 id, guint32 flags, + WpSpaPod * param) +{ + WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (instance); + g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); + const gchar *idstr = NULL; + + if (!node) { + wp_warning_object (self, "associated node is no longer available"); + return -EPIPE; + } + + if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, NULL, &idstr, NULL)) { + wp_critical_object (self, "invalid param id: %u", id); + return -EINVAL; + } + + return wp_pipewire_object_set_param (node, idstr, flags, param) ? 0 : -EIO; +} + +#define pw_endpoint_stream_emit(hooks,method,version,...) \ + spa_hook_list_call_simple(hooks, struct pw_endpoint_stream_events, \ + method, version, ##__VA_ARGS__) + +static void +wp_impl_endpoint_stream_emit_info (struct spa_hook_list * hooks, gconstpointer info) +{ + pw_endpoint_stream_emit (hooks, info, 0, info); +} + +static void +wp_impl_endpoint_stream_emit_param (struct spa_hook_list * hooks, int seq, + guint32 id, guint32 index, guint32 next, const struct spa_pod *param) +{ + pw_endpoint_stream_emit (hooks, param, 0, seq, id, index, next, param); +} + +static void +wp_endpoint_stream_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) +{ + iface->flags |= WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE; + iface->enum_params_sync = wp_impl_endpoint_stream_enum_params_sync; + iface->set_param = wp_impl_endpoint_stream_set_param; + iface->emit_info = wp_impl_endpoint_stream_emit_info; + iface->emit_param = wp_impl_endpoint_stream_emit_param; +} + WpImplEndpointStream * wp_impl_endpoint_stream_new (WpCore * core, WpSiStream * item) { diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c index bc3e87e2320887f7c81458722974dfd783d26646..853f74a938b14e81a8555d42481a1d8824036b0d 100644 --- a/lib/wp/endpoint.c +++ b/lib/wp/endpoint.c @@ -11,6 +11,7 @@ * @title: PIpeWire Endpoint */ +#include "spa/param/param.h" #define G_LOG_DOMAIN "wp-endpoint" #include "endpoint.h" @@ -18,16 +19,19 @@ #include "session.h" #include "object-manager.h" #include "error.h" +#include "debug.h" #include "wpenums.h" +#include "spa-type.h" #include "si-factory.h" #include "private/impl-endpoint.h" #include "private/pipewire-object-mixin.h" #include <pipewire/extensions/session-manager.h> #include <pipewire/extensions/session-manager/introspect-funcs.h> +#include <spa/utils/result.h> enum { - PROP_NAME = WP_PIPEWIRE_OBJECT_MIXIN_PROP_CUSTOM_START, + PROP_NAME = WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START, PROP_MEDIA_CLASS, PROP_DIRECTION, }; @@ -42,14 +46,11 @@ static guint32 signals[N_SIGNALS] = {0}; typedef struct _WpEndpointPrivate WpEndpointPrivate; struct _WpEndpointPrivate { - WpProperties *properties; - struct pw_endpoint_info *info; - struct pw_endpoint *iface; - struct spa_hook listener; WpObjectManager *streams_om; }; -static void wp_endpoint_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_endpoint_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpEndpoint: @@ -63,7 +64,10 @@ static void wp_endpoint_pipewire_object_interface_init (WpPipewireObjectInterfac */ G_DEFINE_TYPE_WITH_CODE (WpEndpoint, wp_endpoint, WP_TYPE_GLOBAL_PROXY, G_ADD_PRIVATE (WpEndpoint) - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_endpoint_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_endpoint_pw_object_mixin_priv_interface_init)) static void wp_endpoint_init (WpEndpoint * self) @@ -74,21 +78,23 @@ static void wp_endpoint_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { - WpEndpoint *self = WP_ENDPOINT (object); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); switch (property_id) { case PROP_NAME: - g_value_set_string (value, priv->info ? priv->info->name : NULL); + g_value_set_string (value, d->info ? + ((struct pw_endpoint_info *) d->info)->name : NULL); break; case PROP_MEDIA_CLASS: - g_value_set_string (value, priv->info ? priv->info->media_class : NULL); + g_value_set_string (value, d->info ? + ((struct pw_endpoint_info *) d->info)->media_class : NULL); break; case PROP_DIRECTION: - g_value_set_enum (value, priv->info ? priv->info->direction : 0); + g_value_set_enum (value, d->info ? + ((struct pw_endpoint_info *) d->info)->direction : 0); break; default: - wp_pipewire_object_mixin_get_property (object, property_id, value, pspec); + wp_pw_object_mixin_get_property (object, property_id, value, pspec); break; } } @@ -111,12 +117,14 @@ wp_endpoint_emit_streams_changed (WpObjectManager *streams_om, static void wp_endpoint_enable_feature_streams (WpEndpoint * self) { + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); guint32 bound_id = wp_proxy_get_bound_id (WP_PROXY (self)); + guint32 n_streams = ((struct pw_endpoint_info *) d->info)->n_streams; wp_debug_object (self, "enabling WP_ENDPOINT_FEATURE_STREAMS, bound_id:%u, " - "n_streams:%u", bound_id, priv->info->n_streams); + "n_streams:%u", bound_id, n_streams); priv->streams_om = wp_object_manager_new (); /* proxy endpoint stream -> check for endpoint.id in global properties */ @@ -140,7 +148,7 @@ wp_endpoint_enable_feature_streams (WpEndpoint * self) and we get an endpoint with 0 streams in the WpSession's endpoints object manager... so, unless the endpoint really has no streams, wait for them to be prepared by waiting for the "objects-changed" only */ - if (G_UNLIKELY (priv->info->n_streams == 0)) { + if (G_UNLIKELY (n_streams == 0)) { g_signal_connect_object (priv->streams_om, "installed", G_CALLBACK (wp_endpoint_on_streams_om_installed), self, 0); } @@ -153,67 +161,48 @@ wp_endpoint_enable_feature_streams (WpEndpoint * self) static WpObjectFeatures wp_endpoint_get_supported_features (WpObject * object) { - WpEndpoint *self = WP_ENDPOINT (object); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - - return - WP_PROXY_FEATURE_BOUND | - WP_ENDPOINT_FEATURE_STREAMS | - WP_PIPEWIRE_OBJECT_FEATURE_INFO | - wp_pipewire_object_mixin_param_info_to_features ( - priv->info ? priv->info->params : NULL, - priv->info ? priv->info->n_params : 0); + return wp_pw_object_mixin_get_supported_features(object) + | WP_ENDPOINT_FEATURE_STREAMS; } enum { - STEP_STREAMS = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, + STEP_STREAMS = WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START, }; -static guint -wp_endpoint_activate_get_next_step (WpObject * object, - WpFeatureActivationTransition * transition, guint step, - WpObjectFeatures missing) -{ - step = wp_pipewire_object_mixin_activate_get_next_step (object, transition, - step, missing); - - /* extend the mixin's state machine; when the only remaining feature to - enable is FEATURE_STREAMS, advance to STEP_STREAMS */ - if (step == WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO && - missing == WP_ENDPOINT_FEATURE_STREAMS) - return STEP_STREAMS; - - return step; -} - static void wp_endpoint_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - wp_pipewire_object_mixin_cache_info (object, transition); + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ + WP_OBJECT_CLASS (wp_endpoint_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS: + wp_pw_object_mixin_cache_params (object, missing); break; case STEP_STREAMS: wp_endpoint_enable_feature_streams (WP_ENDPOINT (object)); break; default: - WP_OBJECT_CLASS (wp_endpoint_parent_class)-> - activate_execute_step (object, transition, step, missing); - break; + g_assert_not_reached (); } } static void wp_endpoint_deactivate (WpObject * object, WpObjectFeatures features) { - WpEndpoint *self = WP_ENDPOINT (object); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - - wp_pipewire_object_mixin_deactivate (object, features); + wp_pw_object_mixin_deactivate (object, features); if (features & WP_ENDPOINT_FEATURE_STREAMS) { + WpEndpoint *self = WP_ENDPOINT (object); + WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); g_clear_object (&priv->streams_om); wp_object_update_features (object, 0, WP_ENDPOINT_FEATURE_STREAMS); } @@ -221,41 +210,17 @@ wp_endpoint_deactivate (WpObject * object, WpObjectFeatures features) WP_OBJECT_CLASS (wp_endpoint_parent_class)->deactivate (object, features); } -static void -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 = 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_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_ENDPOINT_CHANGE_MASK_PROPS, PW_ENDPOINT_CHANGE_MASK_PARAMS); -} - static const struct pw_endpoint_events endpoint_events = { PW_VERSION_ENDPOINT_EVENTS, - .info = endpoint_event_info, - .param = wp_pipewire_object_mixin_handle_event_param, + .info = (HandleEventInfoFunc(endpoint)) wp_pw_object_mixin_handle_event_info, + .param = wp_pw_object_mixin_handle_event_param, }; static void 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); - - priv->iface = (struct pw_endpoint *) pw_proxy; - pw_endpoint_add_listener (priv->iface, &priv->listener, &endpoint_events, - self); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + endpoint, &endpoint_events); } static void @@ -264,14 +229,11 @@ wp_endpoint_pw_proxy_destroyed (WpProxy * proxy) WpEndpoint *self = WP_ENDPOINT (proxy); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&priv->info, pw_endpoint_info_free); + wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy); + g_clear_object (&priv->streams_om); wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO | WP_ENDPOINT_FEATURE_STREAMS); - - wp_pipewire_object_mixin_deactivate (WP_OBJECT (proxy), - WP_OBJECT_FEATURES_ALL); + WP_ENDPOINT_FEATURE_STREAMS); } static void @@ -284,7 +246,8 @@ wp_endpoint_class_init (WpEndpointClass * klass) object_class->get_property = wp_endpoint_get_property; wpobject_class->get_supported_features = wp_endpoint_get_supported_features; - wpobject_class->activate_get_next_step = wp_endpoint_activate_get_next_step; + wpobject_class->activate_get_next_step = + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_endpoint_activate_execute_step; wpobject_class->deactivate = wp_endpoint_deactivate; @@ -293,7 +256,7 @@ wp_endpoint_class_init (WpEndpointClass * klass) proxy_class->pw_proxy_created = wp_endpoint_pw_proxy_created; proxy_class->pw_proxy_destroyed = wp_endpoint_pw_proxy_destroyed; - wp_pipewire_object_mixin_class_override_properties (object_class); + wp_pw_object_mixin_class_override_properties (object_class); /** * WpEndpoint::streams-changed: @@ -334,60 +297,30 @@ wp_endpoint_class_init (WpEndpointClass * klass) WP_TYPE_DIRECTION, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } -static gconstpointer -wp_endpoint_get_native_info (WpPipewireObject * obj) -{ - WpEndpoint *self = WP_ENDPOINT (obj); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - - return priv->info; -} - -static WpProperties * -wp_endpoint_get_properties (WpPipewireObject * obj) -{ - WpEndpoint *self = WP_ENDPOINT (obj); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - - return wp_properties_ref (priv->properties); -} - -static GVariant * -wp_endpoint_get_param_info (WpPipewireObject * obj) +static gint +wp_endpoint_enum_params (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) { - WpEndpoint *self = WP_ENDPOINT (obj); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - - return wp_pipewire_object_mixin_param_info_to_gvariant (priv->info->params, - priv->info->n_params); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_endpoint_enum_params (d->iface, 0, id, start, num, + filter ? wp_spa_pod_get_spa_pod (filter) : NULL); } -static void -wp_endpoint_enum_params (WpPipewireObject * obj, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) -{ - wp_pipewire_object_mixin_enum_params (pw_endpoint, obj, id, filter, - cancellable, callback, user_data); -} - -static void -wp_endpoint_set_param (WpPipewireObject * obj, const gchar * id, +static gint +wp_endpoint_set_param (gpointer instance, guint32 id, guint32 flags, WpSpaPod * param) { - wp_pipewire_object_mixin_set_param (pw_endpoint, obj, id, param); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_endpoint_set_param (d->iface, id, flags, + wp_spa_pod_get_spa_pod (param)); } static void -wp_endpoint_pipewire_object_interface_init ( - WpPipewireObjectInterface * iface) +wp_endpoint_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_endpoint_get_native_info; - iface->get_properties = wp_endpoint_get_properties; - iface->get_param_info = wp_endpoint_get_param_info; + wp_pw_object_mixin_priv_interface_info_init (iface, endpoint, ENDPOINT); iface->enum_params = wp_endpoint_enum_params; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; iface->set_param = wp_endpoint_set_param; } @@ -406,8 +339,8 @@ wp_endpoint_get_name (WpEndpoint * self) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - return priv->info->name; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + return ((struct pw_endpoint_info *) d->info)->name; } /** @@ -425,8 +358,8 @@ wp_endpoint_get_media_class (WpEndpoint * self) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - return priv->info->media_class; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + return ((struct pw_endpoint_info *) d->info)->media_class; } /** @@ -444,8 +377,8 @@ wp_endpoint_get_direction (WpEndpoint * self) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - return (WpDirection) priv->info->direction; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + return (WpDirection) ((struct pw_endpoint_info *) d->info)->direction; } /** @@ -606,11 +539,10 @@ wp_endpoint_lookup_stream_full (WpEndpoint * self, WpObjectInterest * interest) void wp_endpoint_create_link (WpEndpoint * self, WpProperties * props) { - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); int res; - res = pw_endpoint_create_link (priv->iface, wp_properties_peek_dict (props)); + res = pw_endpoint_create_link (d->iface, wp_properties_peek_dict (props)); if (res < 0) { wp_warning_object (self, "pw_endpoint_create_link: %d: %s", res, spa_strerror (res)); @@ -629,65 +561,23 @@ struct _WpImplEndpoint WpEndpoint parent; struct spa_interface iface; - struct spa_hook_list hooks; struct pw_endpoint_info info; + WpProperties *immutable_props; WpSiEndpoint *item; }; -G_DEFINE_TYPE (WpImplEndpoint, wp_impl_endpoint, WP_TYPE_ENDPOINT) +static void wp_endpoint_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); -#define pw_endpoint_emit(hooks,method,version,...) \ - spa_hook_list_call_simple(hooks, struct pw_endpoint_events, \ - method, version, ##__VA_ARGS__) - -#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__) - -// static struct spa_param_info impl_param_info[] = { -// SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE), -// SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ) -// }; - -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; - - spa_hook_list_isolate (&self->hooks, &save, listener, events, data); - - self->info.change_mask = PW_ENDPOINT_CHANGE_MASK_ALL; - pw_endpoint_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; - - spa_hook_list_join (&self->hooks, &save); - return 0; -} +G_DEFINE_TYPE_WITH_CODE (WpImplEndpoint, wp_impl_endpoint, WP_TYPE_ENDPOINT, + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_endpoint_impl_pw_object_mixin_priv_interface_init)) -static int -impl_enum_params (void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - return -ENOENT; -} - -static int -impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) -{ - return 0; -} - -static int -impl_set_param (void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - return -ENOENT; -} +static struct spa_param_info impl_param_info[] = { + SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE), + SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ) +}; static void on_si_link_exported (WpSessionItem * link, GAsyncResult * res, gpointer data) @@ -865,57 +755,137 @@ impl_create_link (void *object, const struct spa_dict *props) 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, + .add_listener = + (ImplAddListenerFunc(endpoint)) wp_pw_object_mixin_impl_add_listener, + .subscribe_params = wp_pw_object_mixin_impl_subscribe_params, + .enum_params = wp_pw_object_mixin_impl_enum_params, + .set_param = wp_pw_object_mixin_impl_set_param, .create_link = impl_create_link, }; static void -populate_properties (WpImplEndpoint * self, WpProperties *global_props) +wp_impl_endpoint_init (WpImplEndpoint * self) { - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); - g_clear_pointer (&priv->properties, wp_properties_unref); - priv->properties = wp_si_endpoint_get_properties (self->item); - if (!priv->properties) - priv->properties = wp_properties_new_empty (); - priv->properties = wp_properties_ensure_unique_owner (priv->properties); - wp_properties_update (priv->properties, global_props); + self->iface = SPA_INTERFACE_INIT ( + PW_TYPE_INTERFACE_Endpoint, + PW_VERSION_ENDPOINT, + &impl_endpoint, self); - self->info.props = priv->properties ? - (struct spa_dict *) wp_properties_peek_dict (priv->properties) : NULL; + d->info = &self->info; + d->iface = &self->iface; +} + +static void +populate_properties (WpImplEndpoint * self) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + + g_clear_pointer (&d->properties, wp_properties_unref); + d->properties = wp_si_endpoint_get_properties (self->item); + if (!d->properties) + d->properties = wp_properties_new_empty (); + d->properties = wp_properties_ensure_unique_owner (d->properties); + wp_properties_update (d->properties, self->immutable_props); + + self->info.props = (struct spa_dict *) wp_properties_peek_dict (d->properties); } static void on_si_endpoint_properties_changed (WpSiEndpoint * item, WpImplEndpoint * self) { - populate_properties (self, - wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (self))); - g_object_notify (G_OBJECT (self), "properties"); + populate_properties (self); + wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_CHANGE_MASK_PROPS); +} - self->info.change_mask = PW_ENDPOINT_CHANGE_MASK_PROPS; - pw_endpoint_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; +static void +on_node_params_changed (WpNode * node, guint32 param_id, WpImplEndpoint * self) +{ + if (param_id == SPA_PARAM_PropInfo || param_id == SPA_PARAM_Props) + wp_pw_object_mixin_notify_params_changed (self, param_id); } static void -wp_impl_endpoint_init (WpImplEndpoint * self) +wp_impl_endpoint_constructed (GObject * object) { - /* 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)); + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); + g_autoptr (GVariant) info = NULL; + g_autoptr (GVariantIter) immutable_props = NULL; + g_autoptr (WpObject) node = NULL; + const gchar *key, *value; + guchar direction; - self->iface = SPA_INTERFACE_INIT ( - PW_TYPE_INTERFACE_Endpoint, - PW_VERSION_ENDPOINT, - &impl_endpoint, self); - spa_hook_list_init (&self->hooks); + 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; + self->info.n_streams = wp_si_endpoint_get_n_streams (self->item); - priv->iface = (struct pw_endpoint *) &self->iface; + /* associate with the session */ + self->info.session_id = wp_session_item_get_associated_proxy_id ( + WP_SESSION_ITEM (self->item), WP_TYPE_SESSION); + + /* construct export properties (these will come back through + the registry and appear in wp_proxy_get_global_properties) */ + self->immutable_props = wp_properties_new ( + PW_KEY_ENDPOINT_NAME, self->info.name, + PW_KEY_MEDIA_CLASS, self->info.media_class, + NULL); + wp_properties_setf (self->immutable_props, PW_KEY_SESSION_ID, + "%d", self->info.session_id); + + /* populate immutable (global) properties */ + while (g_variant_iter_next (immutable_props, "{&s&s}", &key, &value)) + wp_properties_set (self->immutable_props, key, value); + + /* populate standard properties */ + populate_properties (self); + + /* subscribe to changes */ + g_signal_connect_object (self->item, "endpoint-properties-changed", + G_CALLBACK (on_si_endpoint_properties_changed), self, 0); + + /* if the item has a node, proxy its ParamProps */ + node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); + if (node && (wp_object_get_active_features (node) & + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS)) { + self->info.params = impl_param_info; + self->info.n_params = G_N_ELEMENTS (impl_param_info); + + g_signal_connect_object (node, "params-changed", + G_CALLBACK (on_node_params_changed), self, 0); + + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, 0); + } else { + self->info.params = NULL; + self->info.n_params = 0; + } + + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + + G_OBJECT_CLASS (wp_impl_endpoint_parent_class)->constructed (object); +} + +static void +wp_impl_endpoint_dispose (GObject * object) +{ + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); + + g_clear_pointer (&self->immutable_props, wp_properties_unref); + g_clear_pointer (&self->info.name, g_free); + + wp_object_update_features (WP_OBJECT (self), 0, + WP_PIPEWIRE_OBJECT_FEATURE_INFO | + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS); + + G_OBJECT_CLASS (wp_impl_endpoint_parent_class)->dispose (object); } static void @@ -961,29 +931,28 @@ wp_impl_endpoint_activate_get_next_step (WpObject * object, { WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - /* bind if not already bound */ - if (missing & WP_PROXY_FEATURE_BOUND) { + /* before anything else, if the item has a node, + cache its props so that enum_params works */ + if (missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) { g_autoptr (WpObject) node = wp_session_item_get_associated_proxy ( WP_SESSION_ITEM (self->item), WP_TYPE_NODE); - /* if the item has a node, cache its props so that enum_params works */ - // if (node && !(wp_object_get_active_features (node) & - // WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS)) - // return STEP_ACTIVATE_NODE; - // else - return WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND; + if (node && (wp_object_get_supported_features (node) & + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS) && + !(wp_object_get_active_features (node) & + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS)) + return STEP_ACTIVATE_NODE; } - /* enable FEATURE_STREAMS when there is nothing else left to activate */ - else if (missing == WP_ENDPOINT_FEATURE_STREAMS) - return STEP_STREAMS; - else - return WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO; + + return WP_OBJECT_CLASS (wp_impl_endpoint_parent_class)-> + activate_get_next_step (object, transition, step, missing); } static void wp_impl_endpoint_node_activated (WpObject * node, GAsyncResult * res, WpTransition * transition) { + WpImplEndpoint *self = wp_transition_get_source_object (transition); g_autoptr (GError) error = NULL; if (!wp_object_activate_finish (node, res, &error)) { @@ -991,7 +960,15 @@ wp_impl_endpoint_node_activated (WpObject * node, return; } - wp_transition_advance (transition); + self->info.params = impl_param_info; + self->info.n_params = G_N_ELEMENTS (impl_param_info); + + g_signal_connect_object (node, "params-changed", + G_CALLBACK (on_node_params_changed), self, 0); + + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, 0); + wp_pw_object_mixin_notify_info (self, PW_ENDPOINT_CHANGE_MASK_PARAMS); } static void @@ -1000,8 +977,6 @@ wp_impl_endpoint_activate_execute_step (WpObject * object, WpObjectFeatures missing) { WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); switch (step) { case STEP_ACTIVATE_NODE: { @@ -1009,14 +984,12 @@ wp_impl_endpoint_activate_execute_step (WpObject * object, WP_SESSION_ITEM (self->item), WP_TYPE_NODE); wp_object_activate (node, - WP_PROXY_FEATURE_BOUND /*| WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS */, + WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, NULL, (GAsyncReadyCallback) wp_impl_endpoint_node_activated, transition); break; } - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND: { - g_autoptr (GVariantIter) immutable_properties = NULL; - g_autoptr (WpProperties) properties = NULL; + case WP_PW_OBJECT_MIXIN_STEP_BIND: { g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); struct pw_core *pw_core = wp_core_get_pw_core (core); @@ -1029,67 +1002,11 @@ wp_impl_endpoint_activate_execute_step (WpObject * object, return; } - wp_debug_object (self, "exporting"); - - /* get info from the interface */ - { - g_autoptr (GVariant) info = NULL; - guchar direction; - - info = wp_si_endpoint_get_registration_info (self->item); - g_variant_get (info, "(ssya{ss})", &self->info.name, - &self->info.media_class, &direction, &immutable_properties); - - self->info.direction = (enum pw_direction) direction; - self->info.n_streams = wp_si_endpoint_get_n_streams (self->item); - - /* associate with the session */ - self->info.session_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (self->item), WP_TYPE_SESSION); - } - - /* construct export properties (these will come back through - the registry and appear in wp_proxy_get_global_properties) */ - properties = wp_properties_new ( - PW_KEY_ENDPOINT_NAME, self->info.name, - PW_KEY_MEDIA_CLASS, self->info.media_class, - NULL); - wp_properties_setf (properties, PW_KEY_SESSION_ID, - "%d", self->info.session_id); - - /* populate immutable (global) properties */ - { - const gchar *key, *value; - while (g_variant_iter_next (immutable_properties, "{&s&s}", &key, &value)) - wp_properties_set (properties, key, value); - } - - /* populate standard properties */ - populate_properties (self, properties); - - /* subscribe to changes */ - g_signal_connect_object (self->item, "endpoint-properties-changed", - G_CALLBACK (on_si_endpoint_properties_changed), self, 0); - - /* finalize info struct */ - self->info.version = PW_VERSION_ENDPOINT_INFO; - self->info.params = NULL; - self->info.n_params = 0; - priv->info = &self->info; - /* bind */ wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core, PW_TYPE_INTERFACE_Endpoint, - wp_properties_peek_dict (properties), - priv->iface, 0)); - - /* notify */ - wp_object_update_features (object, - WP_PIPEWIRE_OBJECT_FEATURE_INFO - /*| WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS */, 0); - g_object_notify (G_OBJECT (self), "properties"); - g_object_notify (G_OBJECT (self), "param-info"); - + wp_properties_peek_dict (self->immutable_props), + &self->iface, 0)); break; } default: @@ -1102,18 +1019,12 @@ wp_impl_endpoint_activate_execute_step (WpObject * object, static void wp_impl_endpoint_pw_proxy_destroyed (WpProxy * proxy) { - WpImplEndpoint *self = WP_IMPL_ENDPOINT (proxy); WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); + wp_endpoint_get_instance_private (WP_ENDPOINT (proxy)); - g_signal_handlers_disconnect_by_data (self->item, self); - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&self->info.name, g_free); - g_clear_pointer (&self->info.media_class, g_free); - priv->info = NULL; + g_clear_object (&priv->streams_om); wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO - /*| WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS */); + WP_ENDPOINT_FEATURE_STREAMS); } static void @@ -1123,6 +1034,8 @@ wp_impl_endpoint_class_init (WpImplEndpointClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; + object_class->constructed = wp_impl_endpoint_constructed; + object_class->dispose = wp_impl_endpoint_dispose; object_class->set_property = wp_impl_endpoint_set_property; object_class->get_property = wp_impl_endpoint_get_property; @@ -1139,6 +1052,78 @@ wp_impl_endpoint_class_init (WpImplEndpointClass * klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } +static GPtrArray * +wp_impl_endpoint_enum_params_sync (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) +{ + WpImplEndpoint *self = WP_IMPL_ENDPOINT (instance); + g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); + + if (!node) { + wp_warning_object (self, "associated node is no longer available"); + return NULL; + } + + /* bypass a few things, knowing that the node + caches params in the mixin param store */ + WpPwObjectMixinData *data = wp_pw_object_mixin_get_data (node); + GPtrArray *params = wp_pw_object_mixin_get_stored_params (data, id); + /* TODO filter */ + + return params; +} + +static gint +wp_impl_endpoint_set_param (gpointer instance, guint32 id, guint32 flags, + WpSpaPod * param) +{ + WpImplEndpoint *self = WP_IMPL_ENDPOINT (instance); + g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); + const gchar *idstr = NULL; + + if (!node) { + wp_warning_object (self, "associated node is no longer available"); + return -EPIPE; + } + + if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, NULL, &idstr, NULL)) { + wp_critical_object (self, "invalid param id: %u", id); + return -EINVAL; + } + + return wp_pipewire_object_set_param (node, idstr, flags, param) ? 0 : -EIO; +} + +#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_emit_info (struct spa_hook_list * hooks, gconstpointer info) +{ + pw_endpoint_emit (hooks, info, 0, info); +} + +static void +wp_impl_endpoint_emit_param (struct spa_hook_list * hooks, int seq, + guint32 id, guint32 index, guint32 next, const struct spa_pod *param) +{ + pw_endpoint_emit (hooks, param, 0, seq, id, index, next, param); +} + +static void +wp_endpoint_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) +{ + iface->flags |= WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE; + iface->enum_params_sync = wp_impl_endpoint_enum_params_sync; + iface->set_param = wp_impl_endpoint_set_param; + iface->emit_info = wp_impl_endpoint_emit_info; + iface->emit_param = wp_impl_endpoint_emit_param; +} + WpImplEndpoint * wp_impl_endpoint_new (WpCore * core, WpSiEndpoint * item) { diff --git a/lib/wp/global-proxy.c b/lib/wp/global-proxy.c index 7de94a45bf1dc5541bbccd7319427c5212b29893..1f751ecbf18c3aebf26a70b21836bbb7f79238d5 100644 --- a/lib/wp/global-proxy.c +++ b/lib/wp/global-proxy.c @@ -121,6 +121,8 @@ wp_global_proxy_activate_get_next_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { + /* we only support BOUND, so this is the only + feature that can be in @missing */ g_return_val_if_fail (missing == WP_PROXY_FEATURE_BOUND, WP_TRANSITION_STEP_ERROR); diff --git a/lib/wp/link.c b/lib/wp/link.c index 0a030b1125364df33bfa89d0db9c27958f644c8b..136663177354a8a43e0eaa702b184a7c1b2984af 100644 --- a/lib/wp/link.c +++ b/lib/wp/link.c @@ -19,11 +19,10 @@ struct _WpLink { WpGlobalProxy parent; - struct pw_link_info *info; - struct spa_hook listener; }; -static void wp_link_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_link_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpLink: @@ -38,67 +37,46 @@ static void wp_link_pipewire_object_interface_init (WpPipewireObjectInterface * * on the remote PipeWire server by calling into a factory. */ G_DEFINE_TYPE_WITH_CODE (WpLink, wp_link, WP_TYPE_GLOBAL_PROXY, - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_link_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_link_pw_object_mixin_priv_interface_init)) static void wp_link_init (WpLink * self) { } -static WpObjectFeatures -wp_link_get_supported_features (WpObject * object) -{ - return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO; -} - static void wp_link_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - /* just wait, info will be emitted anyway after binding */ - break; - default: + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ WP_OBJECT_CLASS (wp_link_parent_class)-> activate_execute_step (object, transition, step, missing); break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + default: + g_assert_not_reached (); } } -static void -link_event_info(void *data, const struct pw_link_info *info) -{ - WpLink *self = WP_LINK (data); - - self->info = pw_link_info_update (self->info, info); - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_LINK_CHANGE_MASK_PROPS, 0); -} - static const struct pw_link_events link_events = { PW_VERSION_LINK_EVENTS, - .info = link_event_info, + .info = (HandleEventInfoFunc(link)) wp_pw_object_mixin_handle_event_info, }; static void wp_link_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpLink *self = WP_LINK (proxy); - pw_link_add_listener ((struct pw_link *) pw_proxy, - &self->listener, &link_events, self); -} - -static void -wp_link_pw_proxy_destroyed (WpProxy * proxy) -{ - g_clear_pointer (&WP_LINK (proxy)->info, pw_link_info_free); - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + link, &link_events); } static void @@ -108,49 +86,28 @@ wp_link_class_init (WpLinkClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->get_property = wp_pipewire_object_mixin_get_property; + object_class->get_property = wp_pw_object_mixin_get_property; - wpobject_class->get_supported_features = wp_link_get_supported_features; + wpobject_class->get_supported_features = + wp_pw_object_mixin_get_supported_features; wpobject_class->activate_get_next_step = - wp_pipewire_object_mixin_activate_get_next_step; + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_link_activate_execute_step; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Link; proxy_class->pw_iface_version = PW_VERSION_LINK; proxy_class->pw_proxy_created = wp_link_pw_proxy_created; - proxy_class->pw_proxy_destroyed = wp_link_pw_proxy_destroyed; - - wp_pipewire_object_mixin_class_override_properties (object_class); -} - -static gconstpointer -wp_link_get_native_info (WpPipewireObject * obj) -{ - return WP_LINK (obj)->info; -} + proxy_class->pw_proxy_destroyed = + wp_pw_object_mixin_handle_pw_proxy_destroyed; -static WpProperties * -wp_link_get_properties (WpPipewireObject * obj) -{ - return wp_properties_new_wrap_dict (WP_LINK (obj)->info->props); -} - -static GVariant * -wp_link_get_param_info (WpPipewireObject * obj) -{ - return NULL; + wp_pw_object_mixin_class_override_properties (object_class); } static void -wp_link_pipewire_object_interface_init (WpPipewireObjectInterface * iface) +wp_link_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_link_get_native_info; - iface->get_properties = wp_link_get_properties; - iface->get_param_info = wp_link_get_param_info; - iface->enum_params = wp_pipewire_object_mixin_enum_params_unimplemented; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; - iface->set_param = wp_pipewire_object_mixin_set_param_unimplemented; + wp_pw_object_mixin_priv_interface_info_init_no_params (iface, link, LINK); } /** @@ -202,7 +159,7 @@ wp_link_new_from_factory (WpCore * core, * * Retrieves the ids of the objects that are linked by this link * - * Note: Using this method requires %WP_PROXY_FEATURE_INFO + * Note: Using this method requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO */ void wp_link_get_linked_object_ids (WpLink * self, @@ -211,12 +168,16 @@ wp_link_get_linked_object_ids (WpLink * self, { g_return_if_fail (WP_IS_LINK (self)); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + struct pw_link_info *info = d->info; + g_return_if_fail (info); + if (output_node) - *output_node = self->info->output_node_id; + *output_node = info->output_node_id; if (output_port) - *output_port = self->info->output_port_id; + *output_port = info->output_port_id; if (input_node) - *input_node = self->info->input_node_id; + *input_node = info->input_node_id; if (input_port) - *input_port = self->info->input_port_id; + *input_port = info->input_port_id; } diff --git a/lib/wp/node.c b/lib/wp/node.c index fbd850537c67fd13759ac8aa6731e97290e22213..309643e3c127558133bec5480ff8597e8e295a56 100644 --- a/lib/wp/node.c +++ b/lib/wp/node.c @@ -16,6 +16,7 @@ #include "node.h" #include "core.h" #include "object-manager.h" +#include "debug.h" #include "wpenums.h" #include "private/pipewire-object-mixin.h" @@ -32,12 +33,11 @@ static guint32 signals[N_SIGNALS] = {0}; struct _WpNode { WpGlobalProxy parent; - struct pw_node_info *info; - struct spa_hook listener; WpObjectManager *ports_om; }; -static void wp_node_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_node_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpNode: @@ -52,7 +52,10 @@ static void wp_node_pipewire_object_interface_init (WpPipewireObjectInterface * * on the remote PipeWire server by calling into a factory. */ G_DEFINE_TYPE_WITH_CODE (WpNode, wp_node, WP_TYPE_GLOBAL_PROXY, - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_node_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_node_pw_object_mixin_priv_interface_init)) static void wp_node_init (WpNode * self) @@ -99,64 +102,47 @@ wp_node_enable_feature_ports (WpNode * self) static WpObjectFeatures wp_node_get_supported_features (WpObject * object) { - WpNode *self = WP_NODE (object); - return - WP_PROXY_FEATURE_BOUND | - WP_NODE_FEATURE_PORTS | - WP_PIPEWIRE_OBJECT_FEATURE_INFO | - wp_pipewire_object_mixin_param_info_to_features ( - self->info ? self->info->params : NULL, - self->info ? self->info->n_params : 0); + return wp_pw_object_mixin_get_supported_features (object) + | WP_NODE_FEATURE_PORTS; } enum { - STEP_PORTS = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, + STEP_PORTS = WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START, }; -static guint -wp_node_activate_get_next_step (WpObject * object, - WpFeatureActivationTransition * transition, guint step, - WpObjectFeatures missing) -{ - step = wp_pipewire_object_mixin_activate_get_next_step (object, transition, - step, missing); - - /* extend the mixin's state machine; when the only remaining feature to - enable is FEATURE_PORTS, advance to STEP_PORTS */ - if (step == WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO && - missing == WP_NODE_FEATURE_PORTS) - return STEP_PORTS; - - return step; -} - static void wp_node_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - wp_pipewire_object_mixin_cache_info (object, transition); + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ + WP_OBJECT_CLASS (wp_node_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS: + wp_pw_object_mixin_cache_params (object, missing); break; case STEP_PORTS: wp_node_enable_feature_ports (WP_NODE (object)); break; default: - WP_OBJECT_CLASS (wp_node_parent_class)-> - activate_execute_step (object, transition, step, missing); - break; + g_assert_not_reached (); } } static void wp_node_deactivate (WpObject * object, WpObjectFeatures features) { - WpNode *self = WP_NODE (object); - - wp_pipewire_object_mixin_deactivate (object, features); + wp_pw_object_mixin_deactivate (object, features); if (features & WP_NODE_FEATURE_PORTS) { + WpNode *self = WP_NODE (object); g_clear_object (&self->ports_om); wp_object_update_features (object, 0, WP_NODE_FEATURE_PORTS); } @@ -164,37 +150,17 @@ wp_node_deactivate (WpObject * object, WpObjectFeatures features) WP_OBJECT_CLASS (wp_node_parent_class)->deactivate (object, features); } -static void -node_event_info(void *data, const struct pw_node_info *info) -{ - WpNode *self = WP_NODE (data); - enum pw_node_state old_state = self->info ? - self->info->state : PW_NODE_STATE_CREATING; - - self->info = pw_node_info_update (self->info, info); - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) - g_signal_emit (self, signals[SIGNAL_STATE_CHANGED], 0, old_state, - self->info->state); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_NODE_CHANGE_MASK_PROPS, PW_NODE_CHANGE_MASK_PARAMS); -} - static const struct pw_node_events node_events = { PW_VERSION_NODE_EVENTS, - .info = node_event_info, - .param = wp_pipewire_object_mixin_handle_event_param, + .info = (HandleEventInfoFunc(node)) wp_pw_object_mixin_handle_event_info, + .param = wp_pw_object_mixin_handle_event_param, }; static void wp_node_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpNode *self = WP_NODE (proxy); - pw_node_add_listener ((struct pw_port *) pw_proxy, - &self->listener, &node_events, self); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + node, &node_events); } static void @@ -202,13 +168,10 @@ wp_node_pw_proxy_destroyed (WpProxy * proxy) { WpNode *self = WP_NODE (proxy); - g_clear_pointer (&self->info, pw_node_info_free); - g_clear_object (&self->ports_om); - wp_object_update_features (WP_OBJECT (self), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO | WP_NODE_FEATURE_PORTS); + wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy); - wp_pipewire_object_mixin_deactivate (WP_OBJECT (self), - WP_OBJECT_FEATURES_ALL); + g_clear_object (&self->ports_om); + wp_object_update_features (WP_OBJECT (self), 0, WP_NODE_FEATURE_PORTS); } static void @@ -218,10 +181,11 @@ wp_node_class_init (WpNodeClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->get_property = wp_pipewire_object_mixin_get_property; + object_class->get_property = wp_pw_object_mixin_get_property; wpobject_class->get_supported_features = wp_node_get_supported_features; - wpobject_class->activate_get_next_step = wp_node_activate_get_next_step; + wpobject_class->activate_get_next_step = + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_node_activate_execute_step; wpobject_class->deactivate = wp_node_deactivate; @@ -230,7 +194,7 @@ wp_node_class_init (WpNodeClass * klass) proxy_class->pw_proxy_created = wp_node_pw_proxy_created; proxy_class->pw_proxy_destroyed = wp_node_pw_proxy_destroyed; - wp_pipewire_object_mixin_class_override_properties (object_class); + wp_pw_object_mixin_class_override_properties (object_class); /** * WpNode::state-changed: @@ -258,50 +222,44 @@ wp_node_class_init (WpNodeClass * klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } -static gconstpointer -wp_node_get_native_info (WpPipewireObject * obj) -{ - return WP_NODE (obj)->info; -} - -static WpProperties * -wp_node_get_properties (WpPipewireObject * obj) +static void +wp_node_process_info (gpointer instance, gpointer old_info, gpointer i) { - return wp_properties_new_wrap_dict (WP_NODE (obj)->info->props); -} + const struct pw_node_info *info = i; -static GVariant * -wp_node_get_param_info (WpPipewireObject * obj) -{ - WpNode *self = WP_NODE (obj); - return wp_pipewire_object_mixin_param_info_to_gvariant (self->info->params, - self->info->n_params); + if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) { + enum pw_node_state old_state = old_info ? + ((struct pw_node_info *) old_info)->state : PW_NODE_STATE_CREATING; + g_signal_emit (instance, signals[SIGNAL_STATE_CHANGED], 0, + old_state, info->state); + } } -static void -wp_node_enum_params (WpPipewireObject * obj, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) +static gint +wp_node_enum_params (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) { - wp_pipewire_object_mixin_enum_params (pw_node, obj, id, filter, cancellable, - callback, user_data); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_node_enum_params (d->iface, 0, id, start, num, + filter ? wp_spa_pod_get_spa_pod (filter) : NULL); } -static void -wp_node_set_param (WpPipewireObject * obj, const gchar * id, WpSpaPod * param) +static gint +wp_node_set_param (gpointer instance, guint32 id, guint32 flags, + WpSpaPod * param) { - wp_pipewire_object_mixin_set_param (pw_node, obj, id, param); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_node_set_param (d->iface, id, flags, + wp_spa_pod_get_spa_pod (param)); } static void -wp_node_pipewire_object_interface_init (WpPipewireObjectInterface * iface) +wp_node_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_node_get_native_info; - iface->get_properties = wp_node_get_properties; - iface->get_param_info = wp_node_get_param_info; + wp_pw_object_mixin_priv_interface_info_init (iface, node, NODE); + iface->process_info = wp_node_process_info; iface->enum_params = wp_node_enum_params; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; iface->set_param = wp_node_set_param; } @@ -351,9 +309,12 @@ wp_node_get_state (WpNode * self, const gchar ** error) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, WP_NODE_STATE_ERROR); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + const struct pw_node_info *info = d->info; + if (error) - *error = self->info->error; - return (WpNodeState) self->info->state; + *error = info->error; + return (WpNodeState) info->state; } /** @@ -372,9 +333,12 @@ wp_node_get_n_input_ports (WpNode * self, guint * max) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + const struct pw_node_info *info = d->info; + if (max) - *max = self->info->max_input_ports; - return self->info->n_input_ports; + *max = info->max_input_ports; + return info->n_input_ports; } /** @@ -393,9 +357,12 @@ wp_node_get_n_output_ports (WpNode * self, guint * max) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + const struct pw_node_info *info = d->info; + if (max) - *max = self->info->max_output_ports; - return self->info->n_output_ports; + *max = info->max_output_ports; + return info->n_output_ports; } /** diff --git a/lib/wp/port.c b/lib/wp/port.c index 5d501140588e4f31a862dfd9d915b42f8aab0783..c29c176d7329424d37110ce06e4f17f545a7d60a 100644 --- a/lib/wp/port.c +++ b/lib/wp/port.c @@ -19,11 +19,10 @@ struct _WpPort { WpGlobalProxy parent; - struct pw_port_info *info; - struct spa_hook listener; }; -static void wp_port_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_port_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpPort: @@ -35,84 +34,57 @@ static void wp_port_pipewire_object_interface_init (WpPipewireObjectInterface * * PipeWire registry and it is made available through the #WpObjectManager API. */ G_DEFINE_TYPE_WITH_CODE (WpPort, wp_port, WP_TYPE_GLOBAL_PROXY, - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_port_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_port_pw_object_mixin_priv_interface_init)) static void wp_port_init (WpPort * self) { } -static WpObjectFeatures -wp_port_get_supported_features (WpObject * object) -{ - WpPort *self = WP_PORT (object); - - return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO | - wp_pipewire_object_mixin_param_info_to_features ( - self->info ? self->info->params : NULL, - self->info ? self->info->n_params : 0); -} - static void wp_port_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - wp_pipewire_object_mixin_cache_info (object, transition); - break; - default: + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ WP_OBJECT_CLASS (wp_port_parent_class)-> activate_execute_step (object, transition, step, missing); break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS: + wp_pw_object_mixin_cache_params (object, missing); + break; + default: + g_assert_not_reached (); } } static void wp_port_deactivate (WpObject * object, WpObjectFeatures features) { - wp_pipewire_object_mixin_deactivate (object, features); - + wp_pw_object_mixin_deactivate (object, features); WP_OBJECT_CLASS (wp_port_parent_class)->deactivate (object, features); } -static void -port_event_info(void *data, const struct pw_port_info *info) -{ - WpPort *self = WP_PORT (data); - - self->info = pw_port_info_update (self->info, info); - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_PORT_CHANGE_MASK_PROPS, PW_PORT_CHANGE_MASK_PARAMS); -} - static const struct pw_port_events port_events = { PW_VERSION_PORT_EVENTS, - .info = port_event_info, - .param = wp_pipewire_object_mixin_handle_event_param, + .info = (HandleEventInfoFunc(port)) wp_pw_object_mixin_handle_event_info, + .param = wp_pw_object_mixin_handle_event_param, }; static void wp_port_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpPort *self = WP_PORT (proxy); - pw_port_add_listener ((struct pw_port *) pw_proxy, - &self->listener, &port_events, self); -} - -static void -wp_port_pw_proxy_destroyed (WpProxy * proxy) -{ - g_clear_pointer (&WP_PORT (proxy)->info, pw_port_info_free); - wp_object_update_features (WP_OBJECT (proxy), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO); - - wp_pipewire_object_mixin_deactivate (WP_OBJECT (proxy), - WP_OBJECT_FEATURES_ALL); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + port, &port_events); } static void @@ -122,61 +94,39 @@ wp_port_class_init (WpPortClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->get_property = wp_pipewire_object_mixin_get_property; + object_class->get_property = wp_pw_object_mixin_get_property; - wpobject_class->get_supported_features = wp_port_get_supported_features; + wpobject_class->get_supported_features = + wp_pw_object_mixin_get_supported_features; wpobject_class->activate_get_next_step = - wp_pipewire_object_mixin_activate_get_next_step; + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_port_activate_execute_step; wpobject_class->deactivate = wp_port_deactivate; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Port; proxy_class->pw_iface_version = PW_VERSION_PORT; proxy_class->pw_proxy_created = wp_port_pw_proxy_created; - proxy_class->pw_proxy_destroyed = wp_port_pw_proxy_destroyed; - - wp_pipewire_object_mixin_class_override_properties (object_class); -} - -static gconstpointer -wp_port_get_native_info (WpPipewireObject * obj) -{ - return WP_PORT (obj)->info; -} - -static WpProperties * -wp_port_get_properties (WpPipewireObject * obj) -{ - return wp_properties_new_wrap_dict (WP_PORT (obj)->info->props); -} + proxy_class->pw_proxy_destroyed = + wp_pw_object_mixin_handle_pw_proxy_destroyed; -static GVariant * -wp_port_get_param_info (WpPipewireObject * obj) -{ - WpPort *self = WP_PORT (obj); - return wp_pipewire_object_mixin_param_info_to_gvariant (self->info->params, - self->info->n_params); + wp_pw_object_mixin_class_override_properties (object_class); } -static void -wp_port_enum_params (WpPipewireObject * obj, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) +static gint +wp_port_enum_params (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) { - wp_pipewire_object_mixin_enum_params (pw_port, obj, id, filter, cancellable, - callback, user_data); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_port_enum_params (d->iface, 0, id, start, num, + filter ? wp_spa_pod_get_spa_pod (filter) : NULL); } static void -wp_port_pipewire_object_interface_init (WpPipewireObjectInterface * iface) +wp_port_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_port_get_native_info; - iface->get_properties = wp_port_get_properties; - iface->get_param_info = wp_port_get_param_info; + wp_pw_object_mixin_priv_interface_info_init (iface, port, PORT); iface->enum_params = wp_port_enum_params; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; - iface->set_param = wp_pipewire_object_mixin_set_param_unimplemented; } WpDirection @@ -186,5 +136,8 @@ wp_port_get_direction (WpPort * self) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - return (WpDirection) self->info->direction; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + const struct pw_port_info *info = d->info; + + return (WpDirection) info->direction; } diff --git a/lib/wp/private/pipewire-object-mixin.c b/lib/wp/private/pipewire-object-mixin.c index 49b316d6f2f12f4ee0c38b41b6361cfe6301be67..e88ff9988535bc96dcceb8fa256f9f33187b2287 100644 --- a/lib/wp/private/pipewire-object-mixin.c +++ b/lib/wp/private/pipewire-object-mixin.c @@ -10,24 +10,445 @@ #include "private/pipewire-object-mixin.h" #include "core.h" +#include "spa-type.h" +#include "spa-pod.h" +#include "debug.h" #include "error.h" -G_DEFINE_QUARK (WpPipewireObjectMixinEnumParamsTasks, enum_params_tasks) +#include <spa/utils/result.h> + +G_DEFINE_INTERFACE (WpPwObjectMixinPriv, wp_pw_object_mixin_priv, WP_TYPE_PROXY) + +static void +wp_pw_object_mixin_priv_default_init (WpPwObjectMixinPrivInterface * iface) +{ +} + +static struct spa_param_info * +find_param_info (gpointer instance, guint32 id) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); + + /* offsets are 0 on objects that don't support params */ + if (d->info && iface->n_params_offset && iface->param_info_offset) { + struct spa_param_info * param_info = + G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); + guint32 n_params = + G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); + + for (guint i = 0; i < n_params; i++) { + if (param_info[i].id == id) + return ¶m_info[i]; + } + } + return NULL; +} + +/*************/ +/* INTERFACE */ + +static gconstpointer +wp_pw_object_mixin_get_native_info (WpPipewireObject * obj) +{ + return wp_pw_object_mixin_get_data (obj)->info; +} + +static WpProperties * +wp_pw_object_mixin_get_properties (WpPipewireObject * obj) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj); + return d->properties ? wp_properties_ref (d->properties) : NULL; +} + +static GVariant * +wp_pw_object_mixin_get_param_info (WpPipewireObject * obj) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj); + WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); + struct spa_param_info *info; + guint32 n_params; + g_auto (GVariantBuilder) b = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_DICTIONARY); + + if (!d->info || + iface->param_info_offset == 0 || + iface->n_params_offset == 0) + return NULL; + + info = G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); + n_params = G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); + + g_variant_builder_init (&b, G_VARIANT_TYPE ("a{ss}")); + + for (guint i = 0; i < n_params; i++) { + const gchar *nick = NULL; + gchar flags[3]; + guint flags_idx = 0; + + wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, info[i].id, NULL, &nick, + NULL); + g_return_val_if_fail (nick != NULL, NULL); + + if (info[i].flags & SPA_PARAM_INFO_READ) + flags[flags_idx++] = 'r'; + if (info[i].flags & SPA_PARAM_INFO_WRITE) + flags[flags_idx++] = 'w'; + flags[flags_idx] = '\0'; + + g_variant_builder_add (&b, "{ss}", nick, flags); + } + + return g_variant_builder_end (&b); +} + +static void +enum_params_done (WpCore * core, GAsyncResult * res, gpointer data) +{ + g_autoptr (GTask) task = G_TASK (data); + g_autoptr (GError) error = NULL; + gpointer instance = g_task_get_source_object (G_TASK (data)); + GPtrArray *params = g_task_get_task_data (task); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + + /* finish the sync task */ + wp_core_sync_finish (core, res, &error); + + /* return if the task was cancelled */ + if (g_task_get_completed (task)) + return; + + /* remove the task from the stored list; ref is held by the g_autoptr */ + d->enum_params_tasks = g_list_remove (d->enum_params_tasks, task); + + wp_debug_object (instance, "got %u params, %s, task " WP_OBJECT_FORMAT, + params->len, error ? "with error" : "ok", WP_OBJECT_ARGS (task)); + + if (error) + g_task_return_error (task, g_steal_pointer (&error)); + else { + g_task_return_pointer (task, g_ptr_array_ref (params), + (GDestroyNotify) g_ptr_array_unref); + } +} + +static void +wp_pw_object_mixin_enum_params_unchecked (gpointer obj, + guint32 id, WpSpaPod *filter, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj); + WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); + g_autoptr (GTask) task = NULL; + gint seq = 0; + GPtrArray *params = NULL; + + g_return_if_fail (iface->enum_params_sync || iface->enum_params); + + if (iface->enum_params_sync) { + params = iface->enum_params_sync (obj, id, 0, -1, filter); + } else { + seq = iface->enum_params (obj, id, 0, -1, filter); + + /* return early if seq contains an error */ + if (G_UNLIKELY (SPA_RESULT_IS_ERROR (seq))) { + wp_message_object (obj, "enum_params failed: %s", spa_strerror (seq)); + g_task_report_new_error (obj, callback, user_data, NULL, + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "enum_params failed: %s", spa_strerror (seq)); + return; + } + } + + if (!params) + params = g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref); + + /* create task */ + task = g_task_new (obj, cancellable, callback, user_data); + + /* debug */ + if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) { + const gchar *name = NULL; + wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, &name, NULL, NULL); + + wp_debug_object (obj, "enum id %u (%s), seq 0x%x (%u), task " + WP_OBJECT_FORMAT "%s", id, name, seq, seq, WP_OBJECT_ARGS (task), + iface->enum_params_sync ? ", sync" : ""); + } + + if (iface->enum_params_sync) { + g_task_return_pointer (task, params, (GDestroyNotify) g_ptr_array_unref); + } else { + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (obj)); + + /* store */ + g_task_set_task_data (task, params, (GDestroyNotify) g_ptr_array_unref); + g_task_set_source_tag (task, GINT_TO_POINTER (seq)); + d->enum_params_tasks = g_list_append (d->enum_params_tasks, task); + + /* call sync */ + wp_core_sync (core, cancellable, (GAsyncReadyCallback) enum_params_done, + g_object_ref (task)); + } +} + +static void +wp_pw_object_mixin_enum_params (WpPipewireObject * obj, const gchar * id, + WpSpaPod *filter, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); + guint32 param_id = 0; + + if (!(iface->enum_params || iface->enum_params_sync)) { + g_task_report_new_error (obj, callback, user_data, NULL, + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "enum_params is not supported on this object"); + return; + } + + /* translate the id */ + if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, ¶m_id, + NULL, NULL)) { + wp_critical_object (obj, "invalid param id: %s", id); + return; + } + + wp_pw_object_mixin_enum_params_unchecked (obj, param_id, filter, + cancellable, callback, user_data); +} + +static WpIterator * +wp_pw_object_mixin_enum_params_finish (WpPipewireObject * obj, + GAsyncResult * res, GError ** error) +{ + g_return_val_if_fail (g_task_is_valid (res, obj), NULL); + + GPtrArray *array = g_task_propagate_pointer (G_TASK (res), error); + if (!array) + return NULL; + + return wp_iterator_new_ptr_array (array, WP_TYPE_SPA_POD); +} + +static WpIterator * +wp_pw_object_mixin_enum_params_sync (WpPipewireObject * obj, const gchar * id, + WpSpaPod * filter) +{ + WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); + GPtrArray *params = NULL; + guint32 param_id = 0; + + /* translate the id */ + if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, ¶m_id, + NULL, NULL)) { + wp_critical_object (obj, "invalid param id: %s", id); + return NULL; + } + + if (iface->enum_params_sync) { + /* use enum_params_sync if supported */ + params = iface->enum_params_sync (obj, param_id, 0, -1, filter); + } else { + /* otherwise, find and return the cached params */ + WpPwObjectMixinData *data = wp_pw_object_mixin_get_data (obj); + params = wp_pw_object_mixin_get_stored_params (data, param_id); + /* TODO filter */ + } + + return params ? wp_iterator_new_ptr_array (params, WP_TYPE_SPA_POD) : NULL; +} + +static gboolean +wp_pw_object_mixin_set_param (WpPipewireObject * obj, const gchar * id, + guint32 flags, WpSpaPod * param) +{ + WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); + guint32 param_id = 0; + gint ret; + + if (!iface->set_param) { + wp_warning_object (obj, "set_param is not supported on this object"); + return FALSE; + } + + if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, ¶m_id, + NULL, NULL)) { + wp_critical_object (obj, "invalid param id: %s", id); + wp_spa_pod_unref (param); + return FALSE; + } + + ret = iface->set_param (obj, param_id, flags, param); + + if (G_UNLIKELY (SPA_RESULT_IS_ERROR (ret))) { + wp_message_object (obj, "set_param failed: %s", spa_strerror (ret)); + return FALSE; + } + return TRUE; +} void -wp_pipewire_object_mixin_get_property (GObject * object, guint property_id, +wp_pw_object_mixin_object_interface_init (WpPipewireObjectInterface * iface) +{ + iface->get_native_info = wp_pw_object_mixin_get_native_info; + iface->get_properties = wp_pw_object_mixin_get_properties; + iface->get_param_info = wp_pw_object_mixin_get_param_info; + iface->enum_params = wp_pw_object_mixin_enum_params; + iface->enum_params_finish = wp_pw_object_mixin_enum_params_finish; + iface->enum_params_sync = wp_pw_object_mixin_enum_params_sync; + iface->set_param = wp_pw_object_mixin_set_param; +} + +/********/ +/* DATA */ + +G_DEFINE_QUARK (WpPwObjectMixinData, wp_pw_object_mixin_data) + +static void wp_pw_object_mixin_param_store_free (gpointer data); + +static WpPwObjectMixinData * +wp_pw_object_mixin_data_new (void) +{ + WpPwObjectMixinData *d = g_slice_new0 (WpPwObjectMixinData); + spa_hook_list_init (&d->hooks); + return d; +} + +static void +wp_pw_object_mixin_data_free (gpointer data) +{ + WpPwObjectMixinData *d = data; + g_clear_pointer (&d->properties, wp_properties_unref); + g_list_free_full (d->params, wp_pw_object_mixin_param_store_free); + g_clear_pointer (&d->subscribed_ids, g_array_unref); + g_warn_if_fail (d->enum_params_tasks == NULL); + g_slice_free (WpPwObjectMixinData, d); +} + +WpPwObjectMixinData * +wp_pw_object_mixin_get_data (gpointer instance) +{ + WpPwObjectMixinData *d = g_object_get_qdata (G_OBJECT (instance), + wp_pw_object_mixin_data_quark ()); + if (G_UNLIKELY (!d)) { + d = wp_pw_object_mixin_data_new (); + g_object_set_qdata_full (G_OBJECT (instance), + wp_pw_object_mixin_data_quark (), d, wp_pw_object_mixin_data_free); + } + return d; +} + +/****************/ +/* PARAMS STORE */ + +typedef struct _WpPwObjectMixinParamStore WpPwObjectMixinParamStore; +struct _WpPwObjectMixinParamStore +{ + guint32 param_id; + GPtrArray *params; +}; + +static WpPwObjectMixinParamStore * +wp_pw_object_mixin_param_store_new (void) +{ + WpPwObjectMixinParamStore *d = g_slice_new0 (WpPwObjectMixinParamStore); + return d; +} + +static void +wp_pw_object_mixin_param_store_free (gpointer data) +{ + WpPwObjectMixinParamStore * p = data; + g_clear_pointer (&p->params, g_ptr_array_unref); + g_slice_free (WpPwObjectMixinParamStore, p); +} + +static gint +param_store_has_id (gconstpointer param, gconstpointer id) +{ + guint32 param_id = ((const WpPwObjectMixinParamStore *) param)->param_id; + return (param_id == GPOINTER_TO_UINT (id)) ? 0 : 1; +} + +GPtrArray * +wp_pw_object_mixin_get_stored_params (WpPwObjectMixinData * data, guint32 id) +{ + GList *link = g_list_find_custom (data->params, GUINT_TO_POINTER (id), + param_store_has_id); + WpPwObjectMixinParamStore *s = link ? link->data : NULL; + return (s && s->params) ? g_ptr_array_ref (s->params) : NULL; +} + +void +wp_pw_object_mixin_store_param (WpPwObjectMixinData * data, guint32 id, + guint32 flags, gpointer param) +{ + GList *link = g_list_find_custom (data->params, GUINT_TO_POINTER (id), + param_store_has_id); + WpPwObjectMixinParamStore *s = link ? link->data : NULL; + gint16 index = (gint16) (flags & 0xffff); + + /* if the link exists, data must also exist */ + g_warn_if_fail (!link || link->data); + + if (!s) { + if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE) + return; + s = wp_pw_object_mixin_param_store_new (); + s->param_id = id; + data->params = g_list_append (data->params, s); + } + else if (s && (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE)) { + wp_pw_object_mixin_param_store_free (s); + data->params = g_list_remove_link (data->params, link); + return; + } + + if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR) + g_clear_pointer (&s->params, g_ptr_array_unref); + + if (!param) + return; + + if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY) { + if (!s->params) + s->params = (GPtrArray *) param; + else + g_ptr_array_extend_and_steal (s->params, (GPtrArray *) param); + } + else { + WpSpaPod *param_pod = param; + + if (!s->params) + s->params = + g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref); + + /* copy if necessary to make sure we don't reference + `const struct spa_pod *` data allocated on the stack */ + param_pod = wp_spa_pod_ensure_unique_owner (param_pod); + g_ptr_array_insert (s->params, index, param_pod); + } +} + +/******************/ +/* PROPERTIES API */ + +void +wp_pw_object_mixin_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { switch (property_id) { - case WP_PIPEWIRE_OBJECT_MIXIN_PROP_NATIVE_INFO: + case WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO: g_value_set_pointer (value, (gpointer) wp_pipewire_object_get_native_info (WP_PIPEWIRE_OBJECT (object))); break; - case WP_PIPEWIRE_OBJECT_MIXIN_PROP_PROPERTIES: + case WP_PW_OBJECT_MIXIN_PROP_PROPERTIES: g_value_set_boxed (value, wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (object))); break; - case WP_PIPEWIRE_OBJECT_MIXIN_PROP_PARAM_INFO: + case WP_PW_OBJECT_MIXIN_PROP_PARAM_INFO: g_value_set_variant (value, wp_pipewire_object_get_param_info (WP_PIPEWIRE_OBJECT (object))); break; @@ -38,70 +459,300 @@ wp_pipewire_object_mixin_get_property (GObject * object, guint property_id, } void -wp_pipewire_object_mixin_class_override_properties (GObjectClass * klass) +wp_pw_object_mixin_class_override_properties (GObjectClass * klass) { g_object_class_override_property (klass, - WP_PIPEWIRE_OBJECT_MIXIN_PROP_NATIVE_INFO, "native-info"); + WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO, "native-info"); g_object_class_override_property (klass, - WP_PIPEWIRE_OBJECT_MIXIN_PROP_PROPERTIES, "properties"); + WP_PW_OBJECT_MIXIN_PROP_PROPERTIES, "properties"); g_object_class_override_property (klass, - WP_PIPEWIRE_OBJECT_MIXIN_PROP_PARAM_INFO, "param-info"); + WP_PW_OBJECT_MIXIN_PROP_PARAM_INFO, "param-info"); } +/****************/ +/* FEATURES API */ + static const struct { - gint param_id; WpObjectFeatures feature; -} feature_mappings[] = { - { SPA_PARAM_Props, WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS }, - { SPA_PARAM_Format, WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT }, - { SPA_PARAM_Profile, WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE }, - { SPA_PARAM_PortConfig, WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG }, - { SPA_PARAM_Route, WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE }, + guint32 param_ids[2]; +} params_features[] = { + { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, { SPA_PARAM_PropInfo, SPA_PARAM_Props } }, + { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT, { SPA_PARAM_EnumFormat, SPA_PARAM_Format } }, + { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE, { SPA_PARAM_EnumProfile, SPA_PARAM_Profile } }, + { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG, { SPA_PARAM_EnumPortConfig, SPA_PARAM_PortConfig } }, + { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE, { SPA_PARAM_EnumRoute, SPA_PARAM_Route } }, }; -WpObjectFeatures -wp_pipewire_object_mixin_param_info_to_features (struct spa_param_info * info, - guint n_params) +static WpObjectFeatures +get_feature_for_param_id (guint32 param_id) { - WpObjectFeatures ft = 0; + for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) { + if (params_features[i].param_ids[0] == param_id || + params_features[i].param_ids[1] == param_id) + return params_features[i].feature; + } + return 0; +} - for (gint i = 0; i < n_params; i++) { - for (gint j = 0; j < G_N_ELEMENTS (feature_mappings); j++) { - if (info[i].id == feature_mappings[j].param_id && - info[i].flags & SPA_PARAM_INFO_READ) - ft |= feature_mappings[j].feature; - } +WpObjectFeatures +wp_pw_object_mixin_get_supported_features (WpObject * object) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object); + WpObjectFeatures ft = + WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO; + + if (d->info && iface->n_params_offset && iface->param_info_offset) { + struct spa_param_info * param_info = + G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); + guint32 n_params = + G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); + + for (guint i = 0; i < n_params; i++) + ft |= get_feature_for_param_id (param_info[i].id); } return ft; } guint -wp_pipewire_object_mixin_activate_get_next_step (WpObject * object, +wp_pw_object_mixin_activate_get_next_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); + /* bind if not already bound */ - if (missing & WP_PROXY_FEATURE_BOUND) - return WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND; - /* then cache info */ + if (missing & WP_PROXY_FEATURE_BOUND || !d->iface) + return WP_PW_OBJECT_MIXIN_STEP_BIND; + /* wait for info before proceeding, if necessary */ + else if ((missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) && !d->info) + return WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO; + /* then cache params */ + else if (missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) + return WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS; else - return WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO; + return WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START; /* returning to STEP_NONE is handled by WpFeatureActivationTransition */ } +static void +enum_params_for_cache_done (GObject * object, GAsyncResult * res, gpointer data) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); + guint32 param_id = GPOINTER_TO_UINT (data); + g_autoptr (GError) error = NULL; + g_autoptr (GPtrArray) params = NULL; + + params = g_task_propagate_pointer (G_TASK (res), &error); + if (error) { + wp_warning_object (object, "enum params failed: %s", error->message); + return; + } + + wp_debug_object (object, "cached params id:%u, n_params:%u", param_id, + params->len); + + wp_pw_object_mixin_store_param (d, param_id, + WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY | + WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR | + WP_PW_OBJECT_MIXIN_STORE_PARAM_APPEND, + g_steal_pointer (¶ms)); + + g_signal_emit_by_name (object, "params-changed", param_id); +} + +G_DEFINE_QUARK (WpPwObjectMixinParamCacheActivatedFeatures, activated_features) + +static void +param_cache_features_enabled (WpCore * core, GAsyncResult * res, gpointer data) +{ + WpObject *object = WP_OBJECT (data); + WpObjectFeatures activated = GPOINTER_TO_UINT ( + g_object_get_qdata (G_OBJECT (object), activated_features_quark ())); + wp_object_update_features (object, activated, 0); +} + +void +wp_pw_object_mixin_cache_params (WpObject * object, WpObjectFeatures missing) +{ + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object); + g_autoptr (WpCore) core = wp_object_get_core (object); + struct spa_param_info * param_info; + WpObjectFeatures activated = 0; + + g_return_if_fail (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)); + + for (gint i = 0; i < G_N_ELEMENTS (params_features); i++) { + if (missing & params_features[i].feature) { + param_info = find_param_info (object, params_features[i].param_ids[0]); + if (param_info && param_info->flags & SPA_PARAM_INFO_READ) { + wp_pw_object_mixin_enum_params_unchecked (object, + param_info->id, NULL, NULL, enum_params_for_cache_done, + GUINT_TO_POINTER (param_info->id)); + } + + param_info = find_param_info (object, params_features[i].param_ids[1]); + if (param_info && param_info->flags & SPA_PARAM_INFO_READ) { + wp_pw_object_mixin_enum_params_unchecked (object, + param_info->id, NULL, NULL, enum_params_for_cache_done, + GUINT_TO_POINTER (param_info->id)); + } + + activated |= params_features[i].feature; + } + } + + g_object_set_qdata (G_OBJECT (object), + activated_features_quark (), GUINT_TO_POINTER (activated)); + wp_core_sync (core, NULL, + (GAsyncReadyCallback) param_cache_features_enabled, object); +} + void -wp_pipewire_object_mixin_cache_info (WpObject * object, - WpFeatureActivationTransition * transition) +wp_pw_object_mixin_deactivate (WpObject * object, WpObjectFeatures features) { - /* TODO */ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object); + + /* deactivate param caching */ + if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)) { + for (gint i = 0; i < G_N_ELEMENTS (params_features); i++) { + if (features & params_features[i].feature) { + wp_pw_object_mixin_store_param (d, params_features[i].param_ids[0], + WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); + wp_pw_object_mixin_store_param (d, params_features[i].param_ids[1], + WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); + wp_object_update_features (object, 0, params_features[i].feature); + } + } + } } +/************************/ +/* PROXY EVENT HANDLERS */ + void -wp_pipewire_object_mixin_deactivate (WpObject * object, - WpObjectFeatures features) +wp_pw_object_mixin_handle_pw_proxy_destroyed (WpProxy * proxy) { - /* TODO */ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (proxy); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (proxy); + + g_clear_pointer (&d->properties, wp_properties_unref); + g_clear_pointer (&d->info, iface->free_info); + d->iface = NULL; + + /* deactivate param caching */ + if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)) { + for (gint i = 0; i < G_N_ELEMENTS (params_features); i++) { + wp_pw_object_mixin_store_param (d, params_features[i].param_ids[0], + WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); + wp_pw_object_mixin_store_param (d, params_features[i].param_ids[1], + WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); + } + } + + /* cancel enum_params tasks */ + { + GList *link; + for (link = g_list_first (d->enum_params_tasks); + link; link = g_list_first (d->enum_params_tasks)) { + g_task_return_new_error (G_TASK (link->data), + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "pipewire proxy destroyed before finishing"); + d->enum_params_tasks = g_list_remove_link (d->enum_params_tasks, link); + } + } + + wp_object_update_features (WP_OBJECT (proxy), 0, + WP_PIPEWIRE_OBJECT_FEATURES_ALL); +} + +/***************************/ +/* PIPEWIRE EVENT HANDLERS */ + +void +wp_pw_object_mixin_handle_event_info (gpointer instance, gconstpointer update) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); + guint32 change_mask = + G_STRUCT_MEMBER (guint32, update, iface->change_mask_offset); + guint32 process_info_change_mask = + change_mask & ~(iface->CHANGE_MASK_PROPS | iface->CHANGE_MASK_PARAMS); + gpointer old_info = NULL; + + wp_debug_object (instance, "info, change_mask:0x%x [%s%s]", change_mask, + (change_mask & iface->CHANGE_MASK_PROPS) ? "props," : "", + (change_mask & iface->CHANGE_MASK_PARAMS) ? "params," : ""); + + /* make a copy of d->info for process_info() */ + if (iface->process_info && d->info && process_info_change_mask) { + /* copy everything that changed except props and params, for efficiency; + process_info() is only interested in variables that are not PROPS & PARAMS */ + G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = + process_info_change_mask; + old_info = iface->update_info (NULL, d->info); + } + + /* update params */ + if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE) && + (change_mask & iface->CHANGE_MASK_PARAMS) && d->info) { + struct spa_param_info * old_param_info = + G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); + struct spa_param_info * param_info = + G_STRUCT_MEMBER (struct spa_param_info *, update, iface->param_info_offset); + guint32 old_n_params = + G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); + guint32 n_params = + G_STRUCT_MEMBER (guint32, update, iface->n_params_offset); + WpObjectFeatures active_ft = + wp_object_get_active_features (WP_OBJECT (instance)); + + for (guint i = 0; i < n_params; i++) { + /* param changes when flags change */ + if (i >= old_n_params || old_param_info[i].flags != param_info[i].flags) { + /* update cached params if the relevant feature is active */ + if (active_ft & get_feature_for_param_id (param_info[i].id) && + param_info[i].flags & SPA_PARAM_INFO_READ) + { + wp_pw_object_mixin_enum_params_unchecked (instance, + param_info[i].id, NULL, NULL, enum_params_for_cache_done, + GUINT_TO_POINTER (param_info[i].id)); + } + } + } + } + + /* update our info struct */ + d->info = iface->update_info (d->info, update); + wp_object_update_features (WP_OBJECT (instance), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + + /* update properties */ + if (change_mask & iface->CHANGE_MASK_PROPS) { + const struct spa_dict * props = + G_STRUCT_MEMBER (const struct spa_dict *, d->info, iface->props_offset); + + g_clear_pointer (&d->properties, wp_properties_unref); + d->properties = wp_properties_new_wrap_dict (props); + + g_object_notify (G_OBJECT (instance), "properties"); + } + + if (change_mask & iface->CHANGE_MASK_PARAMS) + g_object_notify (G_OBJECT (instance), "param-info"); + + /* custom handling, if required */ + if (iface->process_info && process_info_change_mask) { + iface->process_info (instance, old_info, d->info); + g_clear_pointer (&old_info, iface->free_info); + } } static gint @@ -112,147 +763,196 @@ task_has_seq (gconstpointer task, gconstpointer seq) } void -wp_pipewire_object_mixin_handle_event_param (gpointer instance, int seq, +wp_pw_object_mixin_handle_event_param (gpointer instance, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); g_autoptr (WpSpaPod) w_param = wp_spa_pod_new_wrap_const (param); GList *list; GTask *task; - list = g_object_get_qdata (G_OBJECT (instance), enum_params_tasks_quark ()); - list = g_list_find_custom (list, GINT_TO_POINTER (seq), task_has_seq); + list = g_list_find_custom (d->enum_params_tasks, GINT_TO_POINTER (seq), + task_has_seq); task = list ? G_TASK (list->data) : NULL; + wp_trace_boxed (WP_TYPE_SPA_POD, w_param, + WP_OBJECT_FORMAT " param id:%u, index:%u", + WP_OBJECT_ARGS (instance), id, index); + if (task) { GPtrArray *array = g_task_get_task_data (task); g_ptr_array_add (array, wp_spa_pod_copy (w_param)); + } else { + /* this should never happen */ + wp_warning_object (instance, + "param event was received without calling enum_params"); } } -GVariant * -wp_pipewire_object_mixin_param_info_to_gvariant (struct spa_param_info * info, - guint n_params) +/***********************************/ +/* PIPEWIRE METHOD IMPLEMENTATIONS */ + +int +wp_pw_object_mixin_impl_add_listener (gpointer instance, + struct spa_hook *listener, gconstpointer events, gpointer data) { - g_auto (GVariantBuilder) b = - G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_DICTIONARY); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); + struct spa_hook_list save; - if (!info || n_params == 0) - return NULL; + spa_hook_list_isolate (&d->hooks, &save, listener, events, data); - for (guint i = 0; i < n_params; i++) { - const gchar *nick = NULL; - gchar flags[3]; - guint flags_idx = 0; + G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = iface->CHANGE_MASK_ALL; + iface->emit_info (&d->hooks, d->info); + G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = 0; - wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, info[i].id, NULL, &nick, - NULL); - g_return_val_if_fail (nick != NULL, NULL); + spa_hook_list_join (&d->hooks, &save); + return 0; +} - if (info[i].flags & SPA_PARAM_INFO_READ) - flags[flags_idx++] = 'r'; - if (info[i].flags & SPA_PARAM_INFO_WRITE) - flags[flags_idx++] = 'w'; - flags[flags_idx] = '\0'; +int +wp_pw_object_mixin_impl_enum_params (gpointer instance, int seq, + guint32 id, guint32 start, guint32 num, const struct spa_pod *filter) +{ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); + g_autoptr (GPtrArray) params = NULL; + g_autoptr (WpSpaPod) filter_pod = NULL; - g_variant_builder_add (&b, "{ss}", nick, flags); - } + if (!iface->enum_params_sync) + return -ENOTSUP; - return g_variant_builder_end (&b); -} + struct spa_param_info * info = find_param_info (instance, id); + if (!info || !(info->flags & SPA_PARAM_INFO_READ)) + return -EINVAL; -WpIterator * -wp_pipewire_object_mixin_enum_params_finish (WpPipewireObject * obj, - GAsyncResult * res, GError ** error) -{ - g_return_val_if_fail (g_task_is_valid (res, obj), NULL); + filter_pod = filter ? wp_spa_pod_new_wrap_const (filter) : NULL; + params = iface->enum_params_sync (instance, id, start, num, filter_pod); - GPtrArray *array = g_task_propagate_pointer (G_TASK (res), error); - if (!array) - return NULL; + if (params) { + for (guint i = 0; i < params->len; i++) { + WpSpaPod *pod = g_ptr_array_index (params, i); - return wp_iterator_new_ptr_array (array, WP_TYPE_SPA_POD); + wp_trace_boxed (WP_TYPE_SPA_POD, pod, + WP_OBJECT_FORMAT " emit param id:%u, index:%u", + WP_OBJECT_ARGS (instance), id, start+i); + + iface->emit_param (&d->hooks, seq, id, start+i, start+i+1, + wp_spa_pod_get_spa_pod (pod)); + } + } + return 0; } -WpIterator * -wp_pipewire_object_mixin_enum_cached_params (WpPipewireObject * obj, - const gchar * id) +int +wp_pw_object_mixin_impl_subscribe_params (gpointer instance, + guint32 *ids, guint32 n_ids) { - return NULL; //TODO + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); + + if (!iface->enum_params_sync) + return -ENOTSUP; + + for (guint i = 0; i < n_ids; i++) + wp_pw_object_mixin_impl_enum_params (instance, 1, ids[i], 0, -1, NULL); + + if (!d->subscribed_ids) + d->subscribed_ids = g_array_new (FALSE, FALSE, sizeof (guint32)); + + /* FIXME: deduplicate stored ids */ + g_array_append_vals (d->subscribed_ids, ids, n_ids); + return 0; } -void -wp_pipewire_object_mixin_enum_params_unimplemented (WpPipewireObject * obj, - const gchar * id, WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) +int +wp_pw_object_mixin_impl_set_param (gpointer instance, guint32 id, + guint32 flags, const struct spa_pod *param) { - wp_pipewire_object_mixin_create_enum_params_task (obj, 0, cancellable, - callback, user_data); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); + + if (!iface->set_param) + return -ENOTSUP; + + struct spa_param_info * info = find_param_info (instance, id); + if (!info || !(info->flags & SPA_PARAM_INFO_WRITE)) + return -EINVAL; + + WpSpaPod *param_pod = wp_spa_pod_new_wrap_const (param); + + wp_trace_boxed (WP_TYPE_SPA_POD, param_pod, + WP_OBJECT_FORMAT " set_param id:%u flags:0x%x", + WP_OBJECT_ARGS (instance), id, flags); + + return iface->set_param (instance, id, flags, param_pod); } +/**********************/ +/* NOTIFIERS */ + void -wp_pipewire_object_mixin_set_param_unimplemented (WpPipewireObject * obj, - const gchar * id, WpSpaPod * param) +wp_pw_object_mixin_notify_info (gpointer instance, guint32 change_mask) { - wp_warning_object (obj, - "setting params is not implemented on this object type"); -} + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); -static void -enum_params_done (WpCore * core, GAsyncResult * res, gpointer data) -{ - g_autoptr (GTask) task = G_TASK (data); - g_autoptr (GError) error = NULL; - gpointer instance = g_task_get_source_object (G_TASK (data)); - GList *list; + wp_debug_object (instance, "notify info, change_mask:0x%x [%s%s]", + change_mask, + (change_mask & iface->CHANGE_MASK_PROPS) ? "props," : "", + (change_mask & iface->CHANGE_MASK_PARAMS) ? "params," : ""); - /* finish the sync task */ - wp_core_sync_finish (core, res, &error); + G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = + (change_mask & iface->CHANGE_MASK_ALL); + iface->emit_info (&d->hooks, d->info); + G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = 0; - /* remove the task from the stored list; ref is held by the g_autoptr */ - list = g_object_get_qdata (G_OBJECT (instance), enum_params_tasks_quark ()); - list = g_list_remove (list, instance); - g_object_set_qdata (G_OBJECT (instance), enum_params_tasks_quark (), list); + if (change_mask & iface->CHANGE_MASK_PROPS) + g_object_notify (G_OBJECT (instance), "properties"); - if (error) - g_task_return_error (task, g_steal_pointer (&error)); - else { - GPtrArray *params = g_task_get_task_data (task); - g_task_return_pointer (task, g_ptr_array_ref (params), - (GDestroyNotify) g_ptr_array_unref); - } + if (change_mask & iface->CHANGE_MASK_PARAMS) + g_object_notify (G_OBJECT (instance), "param-info"); } void -wp_pipewire_object_mixin_create_enum_params_task (gpointer instance, - gint seq, GCancellable * cancellable, GAsyncReadyCallback callback, - gpointer user_data) +wp_pw_object_mixin_notify_params_changed (gpointer instance, guint32 id) { - g_autoptr (GTask) task = NULL; - GPtrArray *params; - GList *list; + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + WpPwObjectMixinPrivInterface *iface = + WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); + gboolean subscribed = FALSE; + + struct spa_param_info * info = find_param_info (instance, id); + g_return_if_fail (info); + + if (d->subscribed_ids) { + for (guint i = 0; i < d->subscribed_ids->len; i++) { + if (g_array_index (d->subscribed_ids, guint32, i) == id) { + subscribed = TRUE; + break; + } + } + } - /* create task */ - task = g_task_new (instance, cancellable, callback, user_data); - params = g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref); - g_task_set_task_data (task, params, (GDestroyNotify) g_ptr_array_unref); - g_task_set_source_tag (task, GINT_TO_POINTER (seq)); - - /* return early if seq contains an error */ - if (G_UNLIKELY (SPA_RESULT_IS_ERROR (seq))) { - wp_message_object (instance, "enum_params failed: %s", spa_strerror (seq)); - g_task_return_new_error (task, WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, "enum_params failed: %s", - spa_strerror (seq)); - return; + if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) { + const gchar *name = NULL; + wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, &name, NULL, NULL); + wp_debug_object (instance, "notify param id:%u (%s)", id, name); } - /* store */ - list = g_object_get_qdata (G_OBJECT (instance), enum_params_tasks_quark ()); - list = g_list_append (list, g_object_ref (task)); - g_object_set_qdata (G_OBJECT (instance), enum_params_tasks_quark (), list); + /* toggle the serial flag; this notifies that there is a data change */ + info->flags ^= SPA_PARAM_INFO_SERIAL; + + G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = + iface->CHANGE_MASK_PARAMS; + iface->emit_info (&d->hooks, d->info); + G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = 0; - /* call sync */ - g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (instance)); - wp_core_sync (core, cancellable, (GAsyncReadyCallback) enum_params_done, - task); + if (subscribed) + wp_pw_object_mixin_impl_enum_params (instance, 1, id, 0, -1, NULL); + g_signal_emit_by_name (instance, "params-changed", id); } diff --git a/lib/wp/private/pipewire-object-mixin.h b/lib/wp/private/pipewire-object-mixin.h index fc86101aa76816161c5f3c3502d5ea3610ab357e..1a8a688db9b7be0448ba5262801fa1f850940b52 100644 --- a/lib/wp/private/pipewire-object-mixin.h +++ b/lib/wp/private/pipewire-object-mixin.h @@ -10,151 +10,237 @@ #define __WIREPLUMBER_PIPEWIRE_OBJECT_MIXIN_H__ #include "proxy-interfaces.h" -#include "spa-type.h" -#include "spa-pod.h" -#include "debug.h" #include <pipewire/pipewire.h> -#include <spa/utils/result.h> G_BEGIN_DECLS enum { /* this is the same STEP_BIND as in WpGlobalProxy */ - WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START, - WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO, + WP_PW_OBJECT_MIXIN_STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START, + WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO, + WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS, - WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, + WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START, }; enum { - WP_PIPEWIRE_OBJECT_MIXIN_PROP_0, + WP_PW_OBJECT_MIXIN_PROP_0, // WpPipewireObject - WP_PIPEWIRE_OBJECT_MIXIN_PROP_NATIVE_INFO, - WP_PIPEWIRE_OBJECT_MIXIN_PROP_PROPERTIES, - WP_PIPEWIRE_OBJECT_MIXIN_PROP_PARAM_INFO, + WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO, + WP_PW_OBJECT_MIXIN_PROP_PROPERTIES, + WP_PW_OBJECT_MIXIN_PROP_PARAM_INFO, - WP_PIPEWIRE_OBJECT_MIXIN_PROP_CUSTOM_START, + WP_PW_OBJECT_MIXIN_PROP_CUSTOM_START, }; +#define WP_TYPE_PW_OBJECT_MIXIN_PRIV (wp_pw_object_mixin_priv_get_type ()) +G_DECLARE_INTERFACE (WpPwObjectMixinPriv, wp_pw_object_mixin_priv, + WP, PW_OBJECT_MIXIN_PRIV, WpProxy) + +struct _WpPwObjectMixinPrivInterface +{ + GTypeInterface parent; + + /* WpPwObjectMixinPriv-specific flags */ +#define WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE (1 << 0) + guint32 flags; + + /* pipewire info struct abstraction layer */ + gsize info_size; + gsize change_mask_offset; + gsize props_offset; + gsize param_info_offset; + gsize n_params_offset; + + guint32 CHANGE_MASK_ALL; + guint32 CHANGE_MASK_PROPS; + guint32 CHANGE_MASK_PARAMS; + + gpointer (*update_info) (gpointer info, gconstpointer update); + void (*free_info) (gpointer info); + + /* to further process info struct updates - for proxy objects only */ + void (*process_info) (gpointer instance, gpointer old_info, gpointer info); + + /* pipewire interface methods - proxy & impl */ + gint (*enum_params) (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter); + GPtrArray * (*enum_params_sync) (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter); + gint (*set_param) (gpointer instance, guint32 id, guint32 flags, + WpSpaPod *param /* transfer full */); + + /* pipewire interface events - for impl objects only */ + void (*emit_info) (struct spa_hook_list * hooks, gconstpointer info); + void (*emit_param) (struct spa_hook_list * hooks, int seq, + guint32 id, guint32 index, guint32 next, const struct spa_pod *param); +}; + +/* fills in info struct abstraction layer in WpPwObjectMixinPrivInterface */ +#define wp_pw_object_mixin_priv_interface_info_init(iface, type, TYPE) \ +({ \ + iface->info_size = sizeof (struct pw_ ## type ## _info); \ + iface->change_mask_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, change_mask); \ + iface->props_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, props); \ + iface->param_info_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, params); \ + iface->n_params_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, n_params); \ + iface->CHANGE_MASK_ALL = PW_ ## TYPE ## _CHANGE_MASK_ALL; \ + iface->CHANGE_MASK_PROPS = PW_ ## TYPE ## _CHANGE_MASK_PROPS; \ + iface->CHANGE_MASK_PARAMS = PW_ ## TYPE ## _CHANGE_MASK_PARAMS; \ + iface->update_info = (gpointer (*)(gpointer, gconstpointer)) pw_ ## type ## _info_update; \ + iface->free_info = (void (*)(gpointer)) pw_ ## type ## _info_free; \ +}) + +/* same as above, for types that don't have params */ +#define wp_pw_object_mixin_priv_interface_info_init_no_params(iface, type, TYPE) \ +({ \ + iface->flags = WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE; \ + iface->info_size = sizeof (struct pw_ ## type ## _info); \ + iface->change_mask_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, change_mask); \ + iface->props_offset = G_STRUCT_OFFSET (struct pw_ ## type ## _info, props); \ + iface->param_info_offset = 0; \ + iface->n_params_offset = 0; \ + iface->CHANGE_MASK_ALL = PW_ ## TYPE ## _CHANGE_MASK_ALL; \ + iface->CHANGE_MASK_PROPS = PW_ ## TYPE ## _CHANGE_MASK_PROPS; \ + iface->CHANGE_MASK_PARAMS = 0; \ + iface->update_info = (gpointer (*)(gpointer, gconstpointer)) pw_ ## type ## _info_update; \ + iface->free_info = (void (*)(gpointer)) pw_ ## type ## _info_free; \ +}) + +/*************/ +/* INTERFACE */ + +/* implements WpPipewireObject for an object that implements WpPwObjectMixinPriv */ +void wp_pw_object_mixin_object_interface_init (WpPipewireObjectInterface * iface); + +/********/ +/* DATA */ + +typedef struct _WpPwObjectMixinData WpPwObjectMixinData; +struct _WpPwObjectMixinData +{ + gpointer info; /* pointer to the info struct */ + gpointer iface; /* pointer to the interface (ex. pw_endpoint) */ + struct spa_hook listener; + struct spa_hook_list hooks; + WpProperties *properties; + GList *enum_params_tasks; /* element-type: GTask* */ + GList *params; /* element-type: WpPwObjectMixinParamStore* */ + GArray *subscribed_ids; /* element-type: guint32 */ +}; + +/* get mixin data (stored as qdata on the @instance) */ +WpPwObjectMixinData * wp_pw_object_mixin_get_data (gpointer instance); + +/****************/ +/* PARAMS STORE */ + +/* param store access; (transfer container) */ +GPtrArray * wp_pw_object_mixin_get_stored_params (WpPwObjectMixinData * data, + guint32 id); + +/* param store manipulation + * @flags: see below + * @param: (transfer full): WpSpaPod* or GPtrArray* */ +void wp_pw_object_mixin_store_param (WpPwObjectMixinData * data, guint32 id, + guint32 flags, gpointer param); + +/* set the index at which to store the new param */ +#define WP_PW_OBJECT_MIXIN_STORE_PARAM_SET(x) ((x) & 0x7fff) +#define WP_PW_OBJECT_MIXIN_STORE_PARAM_APPEND (0xffff) +#define WP_PW_OBJECT_MIXIN_STORE_PARAM_PREPEND (0) +/* @param is a GPtrArray* */ +#define WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY (1 << 16) +/* clear the existing array of params before storing */ +#define WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR (1 << 17) +/* completely remove stored params for @id */ +#define WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE (1 << 18) + /******************/ /* PROPERTIES API */ /* assign to get_property or chain it from there */ -void wp_pipewire_object_mixin_get_property (GObject * object, guint property_id, +void wp_pw_object_mixin_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec); /* call from class_init */ -void wp_pipewire_object_mixin_class_override_properties (GObjectClass * klass); +void wp_pw_object_mixin_class_override_properties (GObjectClass * klass); /****************/ /* FEATURES API */ /* call from get_supported_features */ -WpObjectFeatures wp_pipewire_object_mixin_param_info_to_features ( - struct spa_param_info * info, guint n_params); +WpObjectFeatures wp_pw_object_mixin_get_supported_features (WpObject * object); /* assign directly to activate_get_next_step */ -guint wp_pipewire_object_mixin_activate_get_next_step (WpObject * object, +guint wp_pw_object_mixin_activate_get_next_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing); /* call from activate_execute_step when - step == WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO */ -void wp_pipewire_object_mixin_cache_info (WpObject * object, - WpFeatureActivationTransition * transition); + step == WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS */ +void wp_pw_object_mixin_cache_params (WpObject * object, + WpObjectFeatures missing); /* handle deactivation of PARAM_* caching features */ -void wp_pipewire_object_mixin_deactivate (WpObject * object, +void wp_pw_object_mixin_deactivate (WpObject * object, WpObjectFeatures features); -/***************************/ -/* PIPEWIRE EVENT HANDLERS */ +/************************/ +/* PROXY EVENT HANDLERS */ +/* (for proxy objects) */ -/* call at the end of the info event callback */ -#define wp_pipewire_object_mixin_handle_event_info(instance, info, CM_PROPS, CM_PARAMS) \ +#define wp_pw_object_mixin_handle_pw_proxy_created(instance, pw_proxy, type, events) \ ({ \ - if (info->change_mask & CM_PROPS) \ - g_object_notify (G_OBJECT (instance), "properties"); \ - \ - if (info->change_mask & CM_PARAMS) \ - g_object_notify (G_OBJECT (instance), "param-info"); \ + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); \ + d->iface = pw_proxy; \ + pw_ ## type ## _add_listener ((struct pw_ ## type *) pw_proxy, \ + &d->listener, events, instance); \ }) -/* assign as the param event callback */ -void wp_pipewire_object_mixin_handle_event_param (gpointer instance, int seq, - uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param); +void wp_pw_object_mixin_handle_pw_proxy_destroyed (WpProxy * proxy); -/***********************/ -/* PIPEWIRE OBJECT API */ +/***************************/ +/* PIPEWIRE EVENT HANDLERS */ +/* (for proxy objects) */ -/* call from get_param_info */ -GVariant * wp_pipewire_object_mixin_param_info_to_gvariant ( - struct spa_param_info * info, guint n_params); +#define HandleEventInfoFunc(type) \ + void (*)(void *, const struct pw_ ## type ## _info *) -/* assign directly to enum_params_finish */ -WpIterator * wp_pipewire_object_mixin_enum_params_finish (WpPipewireObject * obj, - GAsyncResult * res, GError ** error); +void wp_pw_object_mixin_handle_event_info (gpointer instance, gconstpointer info); -/* assign directly to enum_cached_params */ -WpIterator * wp_pipewire_object_mixin_enum_cached_params (WpPipewireObject * obj, - const gchar * id); +/* assign as the param event callback */ +void wp_pw_object_mixin_handle_event_param (gpointer instance, int seq, + guint32 id, guint32 index, guint32 next, const struct spa_pod *param); -/* assign to enum_params on objects that don't support params (like pw_link) */ -void wp_pipewire_object_mixin_enum_params_unimplemented (WpPipewireObject * obj, - const gchar * id, WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data); +/***********************************/ +/* PIPEWIRE METHOD IMPLEMENTATIONS */ +/* (for impl objects) */ -/* assign to set_param on objects that don't support params (like pw_link) */ -void wp_pipewire_object_mixin_set_param_unimplemented (WpPipewireObject * obj, - const gchar * id, WpSpaPod * param); +#define ImplAddListenerFunc(type) \ + int (*)(void *, struct spa_hook *, const struct pw_ ## type ## _events *, void *) -/* call from enum_params */ -#define wp_pipewire_object_mixin_enum_params(type, instance, id, filter, cancellable, callback, user_data) \ -({ \ - struct type *pwp; \ - gint seq; \ - guint32 id_num = 0; \ - \ - if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &id_num, \ - NULL, NULL)) { \ - wp_critical_object (instance, "invalid param id: %s", id); \ - } else { \ - pwp = (struct type *) wp_proxy_get_pw_proxy (WP_PROXY (instance)); \ - seq = type ## _enum_params (pwp, 0, id_num, 0, -1, \ - filter ? wp_spa_pod_get_spa_pod (filter) : NULL); \ - \ - wp_pipewire_object_mixin_create_enum_params_task (instance, seq, cancellable, \ - callback, user_data); \ - } \ -}) +int wp_pw_object_mixin_impl_add_listener (gpointer instance, + struct spa_hook *listener, gconstpointer events, gpointer data); -/* call from set_param */ -#define wp_pipewire_object_mixin_set_param(type, instance, id, param) \ -({ \ - struct type *pwp; \ - gint ret; \ - guint32 id_num = 0; \ - \ - if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &id_num, \ - NULL, NULL)) { \ - wp_critical_object (instance, "invalid param id: %s", id); \ - } else { \ - pwp = (struct type *) wp_proxy_get_pw_proxy (WP_PROXY (instance)); \ - ret = type ## _set_param (pwp, id_num, 0, wp_spa_pod_get_spa_pod (param)); \ - if (G_UNLIKELY (SPA_RESULT_IS_ERROR (ret))) { \ - wp_message_object (instance, "set_param failed: %s", spa_strerror (ret)); \ - } \ - } \ -}) +int wp_pw_object_mixin_impl_enum_params (gpointer instance, int seq, + guint32 id, guint32 start, guint32 num, const struct spa_pod *filter); + +int wp_pw_object_mixin_impl_subscribe_params (gpointer instance, + guint32 *ids, guint32 n_ids); + +int wp_pw_object_mixin_impl_set_param (gpointer instance, guint32 id, + guint32 flags, const struct spa_pod *param); + +/**********************/ +/* NOTIFIERS */ +/* (for impl objects) */ -/************/ -/* INTERNAL */ +void wp_pw_object_mixin_notify_info (gpointer instance, guint32 change_mask); -void wp_pipewire_object_mixin_create_enum_params_task (gpointer instance, - gint seq, GCancellable * cancellable, GAsyncReadyCallback callback, - gpointer user_data); +void wp_pw_object_mixin_notify_params_changed (gpointer instance, guint32 id); G_END_DECLS diff --git a/lib/wp/proxy-interfaces.c b/lib/wp/proxy-interfaces.c index 8524da7c9e4669fc0aa549e0d3205f9107ccd711..b228e653ce45cd0494c25f8cac3b6006ac5328cb 100644 --- a/lib/wp/proxy-interfaces.c +++ b/lib/wp/proxy-interfaces.c @@ -45,8 +45,20 @@ wp_pipewire_object_default_init (WpPipewireObjectInterface * iface) "The param info of the object", G_VARIANT_TYPE ("a{ss}"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + * WpPipewireObject::params-changed: + * @self: the pipewire object + * @id: the parameter id + * + * Emitted when the params for @id have changed. On proxies that cache + * params from a remote object, this is emitted after the cached values + * have changed. + * + * You can expect this to be emitted only when the relevant + * WP_PIPEWIRE_OBJECT_FEATURE_PARAM_* has been activated. + */ g_signal_new ("params-changed", G_TYPE_FROM_INTERFACE (iface), - G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT); } /** @@ -216,6 +228,7 @@ wp_pipewire_object_enum_params_finish (WpPipewireObject * self, * wp_pipewire_object_enum_cached_params * @self: the pipewire object * @id: the parameter id to enumerate + * @filter: (nullable): a param filter or %NULL * * This method can be used to retrieve object parameters in a synchronous way * (in contrast with wp_pipewire_object_enum_params(), which is async), @@ -234,30 +247,35 @@ wp_pipewire_object_enum_params_finish (WpPipewireObject * self, * the items in the iterator are #WpSpaPod */ WpIterator * -wp_pipewire_object_enum_cached_params (WpPipewireObject * self, - const gchar * id) +wp_pipewire_object_enum_params_sync (WpPipewireObject * self, + const gchar * id, WpSpaPod * filter) { g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), NULL); - g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_cached_params, + g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params_sync, NULL); - return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_cached_params (self, id); + return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->enum_params_sync (self, id, + filter); } /** * wp_pipewire_object_set_param: * @self: the pipewire object * @id: the parameter id to set - * @param: the parameter to set + * @flags: optional flags or 0 + * @param: (transfer full): the parameter to set * * Sets a parameter on the object. + * + * Returns: %TRUE on success, %FALSE if setting the param failed */ -void +gboolean wp_pipewire_object_set_param (WpPipewireObject * self, const gchar * id, - WpSpaPod * param) + guint32 flags, WpSpaPod * param) { - g_return_if_fail (WP_IS_PIPEWIRE_OBJECT (self)); - g_return_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->set_param); + g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECT (self), FALSE); + g_return_val_if_fail (WP_PIPEWIRE_OBJECT_GET_IFACE (self)->set_param, FALSE); - WP_PIPEWIRE_OBJECT_GET_IFACE (self)->set_param (self, id, param); + return WP_PIPEWIRE_OBJECT_GET_IFACE (self)->set_param (self, id, flags, + param); } diff --git a/lib/wp/proxy-interfaces.h b/lib/wp/proxy-interfaces.h index 7b0d3ba85f55163f0437a464efa92a130d057762..62b8328074f4a6340edd835b76a59d71f1d3bb7f 100644 --- a/lib/wp/proxy-interfaces.h +++ b/lib/wp/proxy-interfaces.h @@ -36,17 +36,17 @@ struct _WpPipewireObjectInterface GVariant * (*get_param_info) (WpPipewireObject * self); void (*enum_params) (WpPipewireObject * self, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, + WpSpaPod * filter, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data); WpIterator * (*enum_params_finish) (WpPipewireObject * self, GAsyncResult * res, GError ** error); - WpIterator * (*enum_cached_params) (WpPipewireObject * self, - const gchar * id); + WpIterator * (*enum_params_sync) (WpPipewireObject * self, + const gchar * id, WpSpaPod * filter); - void (*set_param) (WpPipewireObject * self, const gchar * id, - WpSpaPod * param); + gboolean (*set_param) (WpPipewireObject * self, const gchar * id, + guint32 flags, WpSpaPod * param); }; WP_API @@ -75,12 +75,12 @@ WpIterator * wp_pipewire_object_enum_params_finish (WpPipewireObject * self, GAsyncResult * res, GError ** error); WP_API -WpIterator * wp_pipewire_object_enum_cached_params (WpPipewireObject * self, - const gchar * id); +WpIterator * wp_pipewire_object_enum_params_sync (WpPipewireObject * self, + const gchar * id, WpSpaPod * filter); WP_API -void wp_pipewire_object_set_param (WpPipewireObject * self, const gchar * id, - WpSpaPod * param); +gboolean wp_pipewire_object_set_param (WpPipewireObject * self, + const gchar * id, guint32 flags, WpSpaPod * param); G_END_DECLS diff --git a/lib/wp/session.c b/lib/wp/session.c index 59220ebc36411ae9a7dc33eb1de2e5ae75e9241b..e289b18204111f258e62746770d6f74b8259eeb5 100644 --- a/lib/wp/session.c +++ b/lib/wp/session.c @@ -16,6 +16,7 @@ #include "session.h" #include "object-manager.h" #include "error.h" +#include "debug.h" #include "wpenums.h" #include "private/impl-endpoint.h" #include "private/pipewire-object-mixin.h" @@ -35,15 +36,12 @@ static guint32 signals[N_SIGNALS] = {0}; typedef struct _WpSessionPrivate WpSessionPrivate; struct _WpSessionPrivate { - WpProperties *properties; - struct pw_session_info *info; - struct pw_session *iface; - struct spa_hook listener; WpObjectManager *endpoints_om; WpObjectManager *links_om; }; -static void wp_session_pipewire_object_interface_init (WpPipewireObjectInterface * iface); +static void wp_session_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); /** * WpSession: @@ -57,7 +55,10 @@ static void wp_session_pipewire_object_interface_init (WpPipewireObjectInterface */ G_DEFINE_TYPE_WITH_CODE (WpSession, wp_session, WP_TYPE_GLOBAL_PROXY, G_ADD_PRIVATE (WpSession) - G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, wp_session_pipewire_object_interface_init)); + G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECT, + wp_pw_object_mixin_object_interface_init) + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_session_pw_object_mixin_priv_interface_init)) static void wp_session_init (WpSession * self) @@ -156,57 +157,39 @@ wp_session_enable_features_endpoints_links (WpSession * self, static WpObjectFeatures wp_session_get_supported_features (WpObject * object) { - WpSession *self = WP_SESSION (object); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - return - WP_PROXY_FEATURE_BOUND | - WP_SESSION_FEATURE_ENDPOINTS | - WP_SESSION_FEATURE_LINKS | - WP_PIPEWIRE_OBJECT_FEATURE_INFO | - wp_pipewire_object_mixin_param_info_to_features ( - priv->info ? priv->info->params : NULL, - priv->info ? priv->info->n_params : 0); + return wp_pw_object_mixin_get_supported_features(object) + | WP_SESSION_FEATURE_ENDPOINTS + | WP_SESSION_FEATURE_LINKS; } enum { - STEP_CHILDREN = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, + STEP_CHILDREN = WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START, }; -static guint -wp_session_activate_get_next_step (WpObject * object, - WpFeatureActivationTransition * transition, guint step, - WpObjectFeatures missing) -{ - step = wp_pipewire_object_mixin_activate_get_next_step (object, transition, - step, missing); - - /* extend the mixin's state machine; when the only remaining feature(s) to - enable are FEATURE_ENDPOINTS or FEATURE_LINKS, advance to STEP_CHILDREN */ - if (step == WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO && - (missing & ~(WP_SESSION_FEATURE_ENDPOINTS | WP_SESSION_FEATURE_LINKS)) == 0) - return STEP_CHILDREN; - - return step; -} - static void wp_session_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: - wp_pipewire_object_mixin_cache_info (object, transition); + case WP_PW_OBJECT_MIXIN_STEP_BIND: + case WP_TRANSITION_STEP_ERROR: + /* base class can handle BIND and ERROR */ + WP_OBJECT_CLASS (wp_session_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS: + wp_pw_object_mixin_cache_params (object, missing); break; case STEP_CHILDREN: wp_session_enable_features_endpoints_links (WP_SESSION (object), missing); break; default: - WP_OBJECT_CLASS (wp_session_parent_class)-> - activate_execute_step (object, transition, step, missing); - break; + g_assert_not_reached (); } } @@ -216,7 +199,7 @@ wp_session_deactivate (WpObject * object, WpObjectFeatures features) WpSession *self = WP_SESSION (object); WpSessionPrivate *priv = wp_session_get_instance_private (self); - wp_pipewire_object_mixin_deactivate (object, features); + wp_pw_object_mixin_deactivate (object, features); if (features & WP_SESSION_FEATURE_ENDPOINTS) { g_clear_object (&priv->endpoints_om); @@ -230,40 +213,17 @@ wp_session_deactivate (WpObject * object, WpObjectFeatures features) WP_OBJECT_CLASS (wp_session_parent_class)->deactivate (object, features); } -static void -session_event_info (void *data, const struct pw_session_info *info) -{ - WpSession *self = WP_SESSION (data); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - priv->info = pw_session_info_update (priv->info, info); - - if (info->change_mask & PW_SESSION_CHANGE_MASK_PROPS) { - g_clear_pointer (&priv->properties, wp_properties_unref); - priv->properties = wp_properties_new_wrap_dict (priv->info->props); - } - - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - - wp_pipewire_object_mixin_handle_event_info (self, info, - PW_SESSION_CHANGE_MASK_PROPS, PW_SESSION_CHANGE_MASK_PARAMS); -} - static const struct pw_session_events session_events = { PW_VERSION_SESSION_EVENTS, - .info = session_event_info, - .param = wp_pipewire_object_mixin_handle_event_param, + .info = (HandleEventInfoFunc(session)) wp_pw_object_mixin_handle_event_info, + .param = wp_pw_object_mixin_handle_event_param, }; static void wp_session_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { - WpSession *self = WP_SESSION (proxy); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - priv->iface = (struct pw_session *) pw_proxy; - pw_session_add_listener (priv->iface, &priv->listener, &session_events, self); + wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy, + session, &session_events); } static void @@ -272,17 +232,13 @@ wp_session_pw_proxy_destroyed (WpProxy * proxy) WpSession *self = WP_SESSION (proxy); WpSessionPrivate *priv = wp_session_get_instance_private (self); - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&priv->info, pw_session_info_free); + wp_pw_object_mixin_handle_pw_proxy_destroyed (proxy); + g_clear_object (&priv->endpoints_om); g_clear_object (&priv->links_om); wp_object_update_features (WP_OBJECT (self), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO | WP_SESSION_FEATURE_ENDPOINTS | WP_SESSION_FEATURE_LINKS); - - wp_pipewire_object_mixin_deactivate (WP_OBJECT (proxy), - WP_OBJECT_FEATURES_ALL); } static void @@ -292,10 +248,11 @@ wp_session_class_init (WpSessionClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->get_property = wp_pipewire_object_mixin_get_property; + object_class->get_property = wp_pw_object_mixin_get_property; wpobject_class->get_supported_features = wp_session_get_supported_features; - wpobject_class->activate_get_next_step = wp_session_activate_get_next_step; + wpobject_class->activate_get_next_step = + wp_pw_object_mixin_activate_get_next_step; wpobject_class->activate_execute_step = wp_session_activate_execute_step; wpobject_class->deactivate = wp_session_deactivate; @@ -304,7 +261,7 @@ wp_session_class_init (WpSessionClass * klass) proxy_class->pw_proxy_created = wp_session_pw_proxy_created; proxy_class->pw_proxy_destroyed = wp_session_pw_proxy_destroyed; - wp_pipewire_object_mixin_class_override_properties (object_class); + wp_pw_object_mixin_class_override_properties (object_class); /** * WpSession::default-endpoint-changed: @@ -344,60 +301,30 @@ wp_session_class_init (WpSessionClass * klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } -static gconstpointer -wp_session_get_native_info (WpPipewireObject * obj) +static gint +wp_session_enum_params (gpointer instance, guint32 id, + guint32 start, guint32 num, WpSpaPod *filter) { - WpSession *self = WP_SESSION (obj); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - return priv->info; -} - -static WpProperties * -wp_session_get_properties (WpPipewireObject * obj) -{ - WpSession *self = WP_SESSION (obj); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - return wp_properties_ref (priv->properties); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_session_enum_params (d->iface, 0, id, start, num, + filter ? wp_spa_pod_get_spa_pod (filter) : NULL); } -static GVariant * -wp_session_get_param_info (WpPipewireObject * obj) -{ - WpSession *self = WP_SESSION (obj); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - return wp_pipewire_object_mixin_param_info_to_gvariant (priv->info->params, - priv->info->n_params); -} - -static void -wp_session_enum_params (WpPipewireObject * obj, const gchar * id, - WpSpaPod *filter, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) -{ - wp_pipewire_object_mixin_enum_params (pw_session, obj, id, filter, - cancellable, callback, user_data); -} - -static void -wp_session_set_param (WpPipewireObject * obj, const gchar * id, +static gint +wp_session_set_param (gpointer instance, guint32 id, guint32 flags, WpSpaPod * param) { - wp_pipewire_object_mixin_set_param (pw_session, obj, id, param); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); + return pw_session_set_param (d->iface, id, flags, + wp_spa_pod_get_spa_pod (param)); } static void -wp_session_pipewire_object_interface_init ( - WpPipewireObjectInterface * iface) +wp_session_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) { - iface->get_native_info = wp_session_get_native_info; - iface->get_properties = wp_session_get_properties; - iface->get_param_info = wp_session_get_param_info; + wp_pw_object_mixin_priv_interface_info_init (iface, session, SESSION); iface->enum_params = wp_session_enum_params; - iface->enum_params_finish = wp_pipewire_object_mixin_enum_params_finish; - iface->enum_cached_params = wp_pipewire_object_mixin_enum_cached_params; iface->set_param = wp_session_set_param; } @@ -416,8 +343,8 @@ wp_session_get_name (WpSession * self) g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - return wp_properties_get (priv->properties, "session.name"); + return wp_pipewire_object_get_property (WP_PIPEWIRE_OBJECT (self), + "session.name"); } /** @@ -690,106 +617,63 @@ struct _WpImplSession WpSession parent; struct spa_interface iface; - struct spa_hook_list hooks; struct pw_session_info info; }; + +static void wp_session_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface); + /** * WpImplSession: * * A #WpImplSession allows implementing a session and exporting it to PipeWire. * To export a #WpImplSession, activate %WP_PROXY_FEATURE_BOUND. */ -G_DEFINE_TYPE (WpImplSession, wp_impl_session, WP_TYPE_SESSION) - -#define pw_session_emit(hooks,method,version,...) \ - spa_hook_list_call_simple(hooks, struct pw_session_events, \ - method, version, ##__VA_ARGS__) - -#define pw_session_emit_info(hooks,...) pw_session_emit(hooks, info, 0, ##__VA_ARGS__) -#define pw_session_emit_param(hooks,...) pw_session_emit(hooks, param, 0, ##__VA_ARGS__) - -static int -impl_add_listener(void *object, - struct spa_hook *listener, - const struct pw_session_events *events, - void *data) -{ - WpImplSession *self = WP_IMPL_SESSION (object); - struct spa_hook_list save; - - spa_hook_list_isolate (&self->hooks, &save, listener, events, data); - - self->info.change_mask = PW_SESSION_CHANGE_MASK_ALL; - pw_session_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; - - spa_hook_list_join (&self->hooks, &save); - return 0; -} - -static int -impl_enum_params (void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - return -ENOENT; -} - -static int -impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) -{ - return 0; -} - -static int -impl_set_param (void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - return -ENOENT; -} +G_DEFINE_TYPE_WITH_CODE (WpImplSession, wp_impl_session, WP_TYPE_SESSION, + G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV, + wp_session_impl_pw_object_mixin_priv_interface_init)) static const struct pw_session_methods impl_session = { PW_VERSION_SESSION_METHODS, - .add_listener = impl_add_listener, - .subscribe_params = impl_subscribe_params, - .enum_params = impl_enum_params, - .set_param = impl_set_param, + .add_listener = + (ImplAddListenerFunc(session)) wp_pw_object_mixin_impl_add_listener, + .subscribe_params = wp_pw_object_mixin_impl_subscribe_params, + .enum_params = wp_pw_object_mixin_impl_enum_params, + .set_param = wp_pw_object_mixin_impl_set_param, }; static void 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)); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); self->iface = SPA_INTERFACE_INIT ( PW_TYPE_INTERFACE_Session, PW_VERSION_SESSION, &impl_session, self); - spa_hook_list_init (&self->hooks); - priv->iface = (struct pw_session *) &self->iface; + d->info = &self->info; + d->iface = &self->iface; /* prepare INFO */ - priv->properties = wp_properties_new_empty (); + d->properties = wp_properties_new_empty (); self->info.version = PW_VERSION_SESSION_INFO; - self->info.props = - (struct spa_dict *) wp_properties_peek_dict (priv->properties); + self->info.props = (struct spa_dict *) wp_properties_peek_dict (d->properties); self->info.params = NULL; self->info.n_params = 0; - priv->info = &self->info; + + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); } static void -wp_impl_session_finalize (GObject * object) +wp_impl_session_dispose (GObject * object) { - WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (object)); - - g_clear_pointer (&priv->properties, wp_properties_unref); + wp_object_update_features (WP_OBJECT (object), 0, + WP_PIPEWIRE_OBJECT_FEATURE_INFO); - G_OBJECT_CLASS (wp_impl_session_parent_class)->finalize (object); + G_OBJECT_CLASS (wp_impl_session_parent_class)->dispose (object); } static void @@ -798,10 +682,10 @@ wp_impl_session_activate_execute_step (WpObject * object, WpObjectFeatures missing) { WpImplSession *self = WP_IMPL_SESSION (object); - WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); switch (step) { - case WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND: { + case WP_PW_OBJECT_MIXIN_STEP_BIND: { g_autoptr (WpCore) core = wp_object_get_core (object); struct pw_core *pw_core = wp_core_get_pw_core (core); @@ -815,17 +699,14 @@ wp_impl_session_activate_execute_step (WpObject * object, } /* make sure these props are not present; they are added by the server */ - wp_properties_set (priv->properties, PW_KEY_OBJECT_ID, NULL); - wp_properties_set (priv->properties, PW_KEY_CLIENT_ID, NULL); - wp_properties_set (priv->properties, PW_KEY_FACTORY_ID, NULL); + wp_properties_set (d->properties, PW_KEY_OBJECT_ID, NULL); + wp_properties_set (d->properties, PW_KEY_CLIENT_ID, NULL); + wp_properties_set (d->properties, PW_KEY_FACTORY_ID, NULL); wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core, PW_TYPE_INTERFACE_Session, - wp_properties_peek_dict (priv->properties), - priv->iface, 0)); - - wp_object_update_features (WP_OBJECT (self), - WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + wp_properties_peek_dict (d->properties), + &self->iface, 0)); break; } default: @@ -844,7 +725,6 @@ wp_impl_session_pw_proxy_destroyed (WpProxy * proxy) g_clear_object (&priv->endpoints_om); g_clear_object (&priv->links_om); wp_object_update_features (WP_OBJECT (self), 0, - WP_PIPEWIRE_OBJECT_FEATURE_INFO | WP_SESSION_FEATURE_ENDPOINTS | WP_SESSION_FEATURE_LINKS); } @@ -856,7 +736,7 @@ wp_impl_session_class_init (WpImplSessionClass * klass) WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_impl_session_finalize; + object_class->dispose = wp_impl_session_dispose; wpobject_class->activate_execute_step = wp_impl_session_activate_execute_step; @@ -864,6 +744,32 @@ wp_impl_session_class_init (WpImplSessionClass * klass) proxy_class->pw_proxy_destroyed = wp_impl_session_pw_proxy_destroyed; } +#define pw_session_emit(hooks,method,version,...) \ + spa_hook_list_call_simple(hooks, struct pw_session_events, \ + method, version, ##__VA_ARGS__) + +static void +wp_impl_session_emit_info (struct spa_hook_list * hooks, gconstpointer info) +{ + pw_session_emit (hooks, info, 0, info); +} + +static void +wp_impl_session_emit_param (struct spa_hook_list * hooks, int seq, + guint32 id, guint32 index, guint32 next, const struct spa_pod *param) +{ + pw_session_emit (hooks, param, 0, seq, id, index, next, param); +} + +static void +wp_session_impl_pw_object_mixin_priv_interface_init ( + WpPwObjectMixinPrivInterface * iface) +{ + iface->flags |= WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE; + iface->emit_info = wp_impl_session_emit_info; + iface->emit_param = wp_impl_session_emit_param; +} + /** * wp_impl_session_new: * @core: the #WpCore @@ -896,21 +802,11 @@ void wp_impl_session_set_property (WpImplSession * self, const gchar * key, const gchar * value) { - WpSessionPrivate *priv; - g_return_if_fail (WP_IS_IMPL_SESSION (self)); - priv = wp_session_get_instance_private (WP_SESSION (self)); - - wp_properties_set (priv->properties, key, value); - g_object_notify (G_OBJECT (self), "properties"); - - /* update only after the session has been exported */ - if (wp_object_get_active_features (WP_OBJECT (self)) & WP_PROXY_FEATURE_BOUND) { - self->info.change_mask = PW_SESSION_CHANGE_MASK_PROPS; - pw_session_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; - } + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + wp_properties_set (d->properties, key, value); + wp_pw_object_mixin_notify_info (self, PW_SESSION_CHANGE_MASK_PROPS); } /** @@ -929,19 +825,9 @@ void wp_impl_session_update_properties (WpImplSession * self, WpProperties * updates) { - WpSessionPrivate *priv; - g_return_if_fail (WP_IS_IMPL_SESSION (self)); - priv = wp_session_get_instance_private (WP_SESSION (self)); - wp_properties_update (priv->properties, updates); - - g_object_notify (G_OBJECT (self), "properties"); - - /* update only after the session has been exported */ - if (wp_object_get_active_features (WP_OBJECT (self)) & WP_PROXY_FEATURE_BOUND) { - self->info.change_mask = PW_SESSION_CHANGE_MASK_PROPS; - pw_session_emit_info (&self->hooks, &self->info); - self->info.change_mask = 0; - } + WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (self); + wp_properties_update (d->properties, updates); + wp_pw_object_mixin_notify_info (self, PW_SESSION_CHANGE_MASK_PROPS); } diff --git a/modules/module-dbus-reservation/reserve-device.c b/modules/module-dbus-reservation/reserve-device.c index 042afd3b3f0fa35e814a3f5bc62502171f5a98af..6b872e40b3329a285b54ed620b92d4327527d5b3 100644 --- a/modules/module-dbus-reservation/reserve-device.c +++ b/modules/module-dbus-reservation/reserve-device.c @@ -46,7 +46,7 @@ set_device_profile (WpPipewireObject *device, gint index) "index", "i", index, NULL); wp_debug_object (device, "set profile %d", index); - wp_pipewire_object_set_param (device, "Profile", profile); + wp_pipewire_object_set_param (device, "Profile", 0, profile); } static gint diff --git a/modules/module-device-activation.c b/modules/module-device-activation.c index bb2b938d2e5d1dabec4abc42f8d0b7015385ac41..812c4733321202a1c0eaafc30a2c63c14e7d3dd9 100644 --- a/modules/module-device-activation.c +++ b/modules/module-device-activation.c @@ -32,7 +32,7 @@ set_device_profile (WpPipewireObject *device, gint index) "index", "i", index, NULL); wp_debug_object (device, "set profile %d", index); - wp_pipewire_object_set_param (device, "Profile", profile); + wp_pipewire_object_set_param (device, "Profile", 0, profile); } static void diff --git a/modules/module-si-adapter.c b/modules/module-si-adapter.c index d9b143bbb6f7403139d295de3b83a2c193c91084..33ac017ac3c64357e3bddae496a7117c34894d34 100644 --- a/modules/module-si-adapter.c +++ b/modules/module-si-adapter.c @@ -312,7 +312,7 @@ si_adapter_activate_execute_step (WpSessionItem * item, /* set the chosen device/client format on the node */ format = format_audio_raw_build (&self->format); wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node), - "Format", format); + "Format", 0, format); /* now choose the DSP format: keep the chanels but use F32 plannar @ 48K */ self->format.format = SPA_AUDIO_FORMAT_F32P; @@ -335,7 +335,7 @@ si_adapter_activate_execute_step (WpSessionItem * item, "format", "P", port_format, NULL); wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node), - "PortConfig", pod); + "PortConfig", 0, pod); g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self->node)); wp_core_sync (core, NULL, diff --git a/modules/module-si-bluez5-endpoint.c b/modules/module-si-bluez5-endpoint.c index 3369d04307292d80fdf0b10e34e69e0d018db76b..86bdcf88ca55ee0efb2edc3763271e947fe516cd 100644 --- a/modules/module-si-bluez5-endpoint.c +++ b/modules/module-si-bluez5-endpoint.c @@ -456,7 +456,8 @@ set_device_profile (WpDevice *device, gint index) "Profile", "Profile", "index", "i", index, NULL); - wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (device), "Profile", profile); + wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (device), + "Profile", 0, profile); } static void diff --git a/modules/module-si-convert.c b/modules/module-si-convert.c index fbab4f43a72a0b066793ebacbd7283793d99096d..a6da805c5f65475f49d80166e1472390ad0cfda8 100644 --- a/modules/module-si-convert.c +++ b/modules/module-si-convert.c @@ -248,7 +248,6 @@ si_convert_activate_execute_step (WpSessionItem * item, g_autoptr (WpProperties) props = NULL; g_autoptr (GVariant) target_config = NULL; g_autoptr (WpSpaPod) format = NULL; - g_autoptr (WpSpaPod) pod = NULL; guint32 channels = 2; guint32 rate; @@ -302,23 +301,22 @@ si_convert_activate_execute_step (WpSessionItem * item, the same format, but with altered volume. In the future we need to consider writing a simpler volume node for this, as doing merge + split is heavy for our needs */ - pod = wp_spa_pod_new_object ("PortConfig", "PortConfig", - "direction", "I", pw_direction_reverse (self->direction), - "mode", "I", SPA_PARAM_PORT_CONFIG_MODE_dsp, - "format", "P", format, - NULL); wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node), - "PortConfig", pod); - g_clear_pointer (&pod, wp_spa_pod_unref); - - pod = wp_spa_pod_new_object ("PortConfig", "PortConfig", - "direction", "I", self->direction, - "mode", "I", SPA_PARAM_PORT_CONFIG_MODE_dsp, - "control", "b", self->control_port, - "format", "P", format, - NULL); + "PortConfig", 0, + wp_spa_pod_new_object ("PortConfig", "PortConfig", + "direction", "I", pw_direction_reverse (self->direction), + "mode", "I", SPA_PARAM_PORT_CONFIG_MODE_dsp, + "format", "P", format, + NULL)); + wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node), - "PortConfig", pod); + "PortConfig", 0, + wp_spa_pod_new_object ("PortConfig", "PortConfig", + "direction", "I", self->direction, + "mode", "I", SPA_PARAM_PORT_CONFIG_MODE_dsp, + "control", "b", self->control_port, + "format", "P", format, + NULL)); wp_object_activate (WP_OBJECT (self->node), WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_NODE_FEATURE_PORTS, NULL,