diff --git a/lib/wp/client.c b/lib/wp/client.c index 0d8a52d23bc53e4d1f6071ca2cb16b4eb623f835..10b08739f845b5190128738af84e5984d3e254b3 100644 --- a/lib/wp/client.c +++ b/lib/wp/client.c @@ -7,55 +7,60 @@ */ /** - * SECTION: WpClient - * - * The #WpClient class allows accessing the properties and methods of a PipeWire - * client object (`struct pw_client`). A #WpClient is constructed internally - * when a new client connects to PipeWire and it is made available through the - * #WpObjectManager API. + * SECTION: client + * @title: PipeWire Client */ -#include "client.h" -#include "private.h" +#define G_LOG_DOMAIN "wp-client" -#include <pipewire/pipewire.h> +#include "client.h" +#include "private/pipewire-object-mixin.h" struct _WpClient { - WpProxy parent; + WpGlobalProxy parent; struct pw_client_info *info; - - /* The client proxy listener */ struct spa_hook listener; }; -G_DEFINE_TYPE (WpClient, wp_client, WP_TYPE_PROXY) +static void wp_client_pipewire_object_interface_init (WpPipewireObjectInterface * iface); -static void -wp_client_init (WpClient * self) -{ -} +/** + * WpClient: + * + * The #WpClient class allows accessing the properties and methods of a PipeWire + * client object (`struct pw_client`). A #WpClient is constructed internally + * when a new client connects to PipeWire and it is made available through the + * #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)); static void -wp_client_finalize (GObject * object) +wp_client_init (WpClient * self) { - WpClient *self = WP_CLIENT (object); - - g_clear_pointer (&self->info, pw_client_info_free); - - G_OBJECT_CLASS (wp_client_parent_class)->finalize (object); } -static gconstpointer -wp_client_get_info (WpProxy * self) +static WpObjectFeatures +wp_client_get_supported_features (WpObject * object) { - return WP_CLIENT (self)->info; + return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO; } -static WpProperties * -wp_client_get_properties (WpProxy * self) +static void +wp_client_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - return wp_properties_new_wrap_dict (WP_CLIENT (self)->info->props); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + default: + WP_OBJECT_CLASS (wp_client_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } } static void @@ -64,12 +69,11 @@ 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_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - - g_object_notify (G_OBJECT (self), "info"); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); + wp_pipewire_object_mixin_handle_event_info (self, info, + PW_CLIENT_CHANGE_MASK_PROPS, 0); } static const struct pw_client_events client_events = { @@ -81,25 +85,68 @@ 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_client *) pw_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); +} + static void wp_client_class_init (WpClientClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_client_finalize; + object_class->get_property = wp_pipewire_object_mixin_get_property; + + wpobject_class->get_supported_features = wp_client_get_supported_features; + wpobject_class->activate_get_next_step = + wp_pipewire_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; - proxy_class->get_info = wp_client_get_info; - proxy_class->get_properties = wp_client_get_properties; + wp_pipewire_object_mixin_class_override_properties (object_class); +} - proxy_class->pw_proxy_created = wp_client_pw_proxy_created; +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; +} + +static void +wp_client_pipewire_object_interface_init (WpPipewireObjectInterface * 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; } /** diff --git a/lib/wp/client.h b/lib/wp/client.h index 89eef4f0c9b4b235588960a67bb6a2c0a72fa789..4cd9a2bd98514cb331a342f36f7192a8253ab501 100644 --- a/lib/wp/client.h +++ b/lib/wp/client.h @@ -9,7 +9,7 @@ #ifndef __WIREPLUMBER_CLIENT_H__ #define __WIREPLUMBER_CLIENT_H__ -#include "proxy.h" +#include "global-proxy.h" G_BEGIN_DECLS @@ -22,7 +22,7 @@ struct pw_permission; */ #define WP_TYPE_CLIENT (wp_client_get_type ()) WP_API -G_DECLARE_FINAL_TYPE (WpClient, wp_client, WP, CLIENT, WpProxy) +G_DECLARE_FINAL_TYPE (WpClient, wp_client, WP, CLIENT, WpGlobalProxy) WP_API void wp_client_update_permissions (WpClient * self, guint n_perm, ...); diff --git a/lib/wp/debug.c b/lib/wp/debug.c index d58c03f2912727be5d9e2cc2028cf7d25c589de4..edc568b7381be2075825c688f018c00847053c9f 100644 --- a/lib/wp/debug.c +++ b/lib/wp/debug.c @@ -218,7 +218,7 @@ format_message (struct common_fields *cf) spa_dbg_str = NULL; } else if (cf->object && g_type_is_a (cf->object_type, WP_TYPE_PROXY) && - (wp_proxy_get_features ((WpProxy *) cf->object) & WP_PROXY_FEATURE_BOUND)) { + (wp_object_get_active_features ((WpObject *) cf->object) & WP_PROXY_FEATURE_BOUND)) { extra_object = g_strdup_printf (":%u:", wp_proxy_get_bound_id ((WpProxy *) cf->object)); } diff --git a/lib/wp/device.c b/lib/wp/device.c index b70013bbdedd212a28c31d884a7f7c01c1a8555f..2a5ab8de15fb34cad5efefd9de909e4c679e5134 100644 --- a/lib/wp/device.c +++ b/lib/wp/device.c @@ -7,164 +7,190 @@ */ /** - * SECTION: WpDevice - * - * The #WpDevice class allows accessing the properties and methods of a - * PipeWire device object (`struct pw_device`). - * - * A #WpDevice is constructed internally when a new device appears on the - * PipeWire registry and it is made available through the #WpObjectManager API. - * Alternatively, a #WpDevice can also be constructed using - * wp_device_new_from_factory(), which creates a new device object - * on the remote PipeWire server by calling into a factory. - * - * A #WpSpaDevice allows running a `spa_device` object locally, - * loading the implementation from a SPA factory. This is useful to run device - * monitors inside the session manager and have control over creating the - * actual nodes that the `spa_device` requests to create. + * SECTION: device + * @title: PipeWire Device */ #define G_LOG_DOMAIN "wp-device" #include "device.h" -#include "debug.h" #include "node.h" -#include "error.h" -#include "private.h" +#include "core.h" +#include "private/pipewire-object-mixin.h" -#include <pipewire/pipewire.h> #include <pipewire/impl.h> #include <spa/monitor/device.h> -#include <spa/utils/result.h> struct _WpDevice { - WpProxy parent; -}; - -typedef struct _WpDevicePrivate WpDevicePrivate; -struct _WpDevicePrivate -{ + WpGlobalProxy parent; struct pw_device_info *info; struct spa_hook listener; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpDevice, wp_device, WP_TYPE_PROXY) +static void wp_device_pipewire_object_interface_init (WpPipewireObjectInterface * iface); + +/** + * WpDevice: + * + * The #WpDevice class allows accessing the properties and methods of a + * PipeWire device object (`struct pw_device`). + * + * A #WpDevice is constructed internally when a new device appears on the + * PipeWire registry and it is made available through the #WpObjectManager API. + * Alternatively, a #WpDevice can also be constructed using + * wp_device_new_from_factory(), which creates a new device object + * 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)); static void wp_device_init (WpDevice * self) { } -static void -wp_device_finalize (GObject * object) +static WpObjectFeatures +wp_device_get_supported_features (WpObject * object) { WpDevice *self = WP_DEVICE (object); - WpDevicePrivate *priv = wp_device_get_instance_private (self); - - g_clear_pointer (&priv->info, pw_device_info_free); - - G_OBJECT_CLASS (wp_device_parent_class)->finalize (object); -} - -static gconstpointer -wp_device_get_info (WpProxy * self) -{ - WpDevicePrivate *priv = wp_device_get_instance_private (WP_DEVICE (self)); - return priv->info; -} -static WpProperties * -wp_device_get_properties (WpProxy * self) -{ - WpDevicePrivate *priv = wp_device_get_instance_private (WP_DEVICE (self)); - return wp_properties_new_wrap_dict (priv->info->props); + 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); } -struct spa_param_info * -wp_device_get_param_info (WpProxy * self, guint * n_params) -{ - WpDevicePrivate *priv = wp_device_get_instance_private (WP_DEVICE (self)); - *n_params = priv->info->n_params; - return priv->info->params; -} - -static gint -wp_device_enum_params (WpProxy * self, guint32 id, guint32 start, - guint32 num, WpSpaPod * filter) +static void +wp_device_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - struct pw_device *pwp = (struct pw_device *) wp_proxy_get_pw_proxy (self); - return pw_device_enum_params (pwp, 0, id, start, num, - filter ? wp_spa_pod_get_spa_pod (filter) : NULL); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + wp_pipewire_object_mixin_cache_info (object, transition); + break; + default: + WP_OBJECT_CLASS (wp_device_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } } -static gint -wp_device_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids) +static void +wp_device_deactivate (WpObject * object, WpObjectFeatures features) { - struct pw_device *pwp = (struct pw_device *) wp_proxy_get_pw_proxy (self); - return pw_device_subscribe_params (pwp, ids, n_ids); -} + wp_pipewire_object_mixin_deactivate (object, features); -static gint -wp_device_set_param (WpProxy * self, guint32 id, guint32 flags, WpSpaPod *param) -{ - struct pw_device *pwp = (struct pw_device *) wp_proxy_get_pw_proxy (self); - return pw_device_set_param (pwp, id, flags, - wp_spa_pod_get_spa_pod (param)); + 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); - WpDevicePrivate *priv = wp_device_get_instance_private (self); - - priv->info = pw_device_info_update (priv->info, info); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - g_object_notify (G_OBJECT (self), "info"); + self->info = pw_device_info_update (self->info, info); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); - - if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) - g_object_notify (G_OBJECT (self), "param-info"); + 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_proxy_handle_event_param, + .param = wp_pipewire_object_mixin_handle_event_param, }; static void wp_device_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { WpDevice *self = WP_DEVICE (proxy); - WpDevicePrivate *priv = wp_device_get_instance_private (self); - pw_device_add_listener ((struct pw_device *) pw_proxy, - &priv->listener, &device_events, self); + 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); } static void wp_device_class_init (WpDeviceClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_device_finalize; + object_class->get_property = wp_pipewire_object_mixin_get_property; + + wpobject_class->get_supported_features = wp_device_get_supported_features; + wpobject_class->activate_get_next_step = + wp_pipewire_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->get_info = wp_device_get_info; - proxy_class->get_properties = wp_device_get_properties; - proxy_class->get_param_info = wp_device_get_param_info; - proxy_class->enum_params = wp_device_enum_params; - proxy_class->subscribe_params = wp_device_subscribe_params; - proxy_class->set_param = wp_device_set_param; + wp_pipewire_object_mixin_class_override_properties (object_class); +} - proxy_class->pw_proxy_created = wp_device_pw_proxy_created; +static gconstpointer +wp_device_get_native_info (WpPipewireObject * obj) +{ + return WP_DEVICE (obj)->info; +} + +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) +{ + wp_pipewire_object_mixin_set_param (pw_device, obj, id, param); +} + +static void +wp_device_pipewire_object_interface_init (WpPipewireObjectInterface * 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; + 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; } /** @@ -178,9 +204,9 @@ wp_device_class_init (WpDeviceClass * klass) * * Because of the nature of the PipeWire protocol, this operation completes * asynchronously at some point in the future. In order to find out when - * this is done, you should call wp_proxy_augment(), requesting at least + * this is done, you should call wp_object_activate(), requesting at least * %WP_PROXY_FEATURE_BOUND. When this feature is ready, the device is ready for - * use on the server. If the device cannot be created, this augment operation + * use on the server. If the device cannot be created, this activation operation * will fail. * * Returns: (nullable) (transfer full): the new device or %NULL if the core @@ -235,6 +261,14 @@ enum static guint spa_device_signals[SPA_DEVICE_LAST_SIGNAL] = { 0 }; +/** + * WpSpaDevice: + * + * A #WpSpaDevice allows running a `spa_device` object locally, + * loading the implementation from a SPA factory. This is useful to run device + * monitors inside the session manager and have control over creating the + * actual nodes that the `spa_device` requests to create. + */ G_DEFINE_TYPE (WpSpaDevice, wp_spa_device, G_TYPE_OBJECT) static void diff --git a/lib/wp/device.h b/lib/wp/device.h index ebcc95ffe67a81078c3b843d24e2dda796cf6d23..10592eac8fe27c4b1f22b731fdcc62aafa8a09ad 100644 --- a/lib/wp/device.h +++ b/lib/wp/device.h @@ -9,7 +9,7 @@ #ifndef __WIREPLUMBER_DEVICE_H__ #define __WIREPLUMBER_DEVICE_H__ -#include "proxy.h" +#include "global-proxy.h" G_BEGIN_DECLS @@ -22,7 +22,7 @@ G_BEGIN_DECLS */ #define WP_TYPE_DEVICE (wp_device_get_type ()) WP_API -G_DECLARE_FINAL_TYPE (WpDevice, wp_device, WP, DEVICE, WpProxy) +G_DECLARE_FINAL_TYPE (WpDevice, wp_device, WP, DEVICE, WpGlobalProxy) WP_API WpDevice * wp_device_new_from_factory (WpCore * core, diff --git a/lib/wp/endpoint-link.c b/lib/wp/endpoint-link.c index 4e8be1fab0e2ae239907b67dac64b13408658084..5c872e7462ff04c6142e08eeab8df846cfe0a1c7 100644 --- a/lib/wp/endpoint-link.c +++ b/lib/wp/endpoint-link.c @@ -7,26 +7,18 @@ */ /** - * SECTION: WpEndpointLink - * - * The #WpEndpointLink class allows accessing the properties and methods of a - * PipeWire endpoint link object (`struct pw_endpoint_link` from the - * session-manager extension). - * - * A #WpEndpointLink is constructed internally when a new endpoint link appears - * on the PipeWire registry and it is made available through the - * #WpObjectManager API. + * SECTION: endpoint-link + * @title: PipeWire Endpoint Link */ #define G_LOG_DOMAIN "wp-endpoint-link" #include "endpoint-link.h" -#include "debug.h" -#include "private.h" #include "error.h" #include "wpenums.h" +#include "private.h" +#include "private/pipewire-object-mixin.h" -#include <pipewire/pipewire.h> #include <pipewire/extensions/session-manager.h> #include <pipewire/extensions/session-manager/introspect-funcs.h> @@ -37,8 +29,6 @@ enum { static guint32 signals[N_SIGNALS] = {0}; -/* WpEndpointLink */ - typedef struct _WpEndpointLinkPrivate WpEndpointLinkPrivate; struct _WpEndpointLinkPrivate { @@ -48,79 +38,62 @@ struct _WpEndpointLinkPrivate struct spa_hook listener; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpEndpointLink, wp_endpoint_link, WP_TYPE_PROXY) +static void wp_endpoint_link_pipewire_object_interface_init (WpPipewireObjectInterface * iface); + +/** + * WpEndpointLink: + * + * The #WpEndpointLink class allows accessing the properties and methods of a + * PipeWire endpoint link object (`struct pw_endpoint_link` from the + * session-manager extension). + * + * A #WpEndpointLink is constructed internally when a new endpoint link appears + * on the PipeWire registry and it is made available through the + * #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)); static void wp_endpoint_link_init (WpEndpointLink * self) { } -static void -wp_endpoint_link_finalize (GObject * object) +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); - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&priv->info, pw_endpoint_link_info_free); - - G_OBJECT_CLASS (wp_endpoint_link_parent_class)->finalize (object); -} - -static gconstpointer -wp_endpoint_link_get_info (WpProxy * proxy) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (proxy); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - return priv->info; + 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 WpProperties * -wp_endpoint_link_get_properties (WpProxy * proxy) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (proxy); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - return wp_properties_ref (priv->properties); -} - -static struct spa_param_info * -wp_endpoint_link_get_param_info (WpProxy * proxy, guint * n_params) -{ - WpEndpointLink *self = WP_ENDPOINT_LINK (proxy); - WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); - - *n_params = priv->info->n_params; - return priv->info->params; -} - -static gint -wp_endpoint_link_enum_params (WpProxy * self, guint32 id, guint32 start, - guint32 num, WpSpaPod * filter) +static void +wp_endpoint_link_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); - return pw_endpoint_link_enum_params (priv->iface, 0, id, start, num, - filter ? wp_spa_pod_get_spa_pod (filter) : NULL); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + wp_pipewire_object_mixin_cache_info (object, transition); + break; + default: + WP_OBJECT_CLASS (wp_endpoint_link_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } } -static gint -wp_endpoint_link_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids) +static void +wp_endpoint_link_deactivate (WpObject * object, WpObjectFeatures features) { - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); - return pw_endpoint_link_subscribe_params (priv->iface, ids, n_ids); -} + wp_pipewire_object_mixin_deactivate (object, features); -static gint -wp_endpoint_link_set_param (WpProxy * self, guint32 id, guint32 flags, - WpSpaPod *param) -{ - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); - return pw_endpoint_link_set_param (priv->iface, id, flags, - wp_spa_pod_get_spa_pod (param)); + WP_OBJECT_CLASS (wp_endpoint_link_parent_class)->deactivate (object, features); } static void @@ -128,8 +101,8 @@ 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 ? priv->info->state : PW_ENDPOINT_LINK_STATE_ERROR; + WpEndpointLinkState old_state = priv->info ? + (WpEndpointLinkState) priv->info->state : WP_ENDPOINT_LINK_STATE_ERROR; priv->info = pw_endpoint_link_info_update (priv->info, info); @@ -138,25 +111,22 @@ endpoint_link_event_info (void *data, const struct pw_endpoint_link_info *info) priv->properties = wp_properties_new_wrap_dict (priv->info->props); } - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - g_object_notify (G_OBJECT (self), "info"); - - if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); - - if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS) - g_object_notify (G_OBJECT (self), "param-info"); + 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_proxy_handle_event_param, + .param = wp_pipewire_object_mixin_handle_event_param, }; static void @@ -170,24 +140,44 @@ wp_endpoint_link_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) &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); +} + static void wp_endpoint_link_class_init (WpEndpointLinkClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_endpoint_link_finalize; + object_class->get_property = wp_pipewire_object_mixin_get_property; + + wpobject_class->get_supported_features = + wp_endpoint_link_get_supported_features; + wpobject_class->activate_get_next_step = + wp_pipewire_object_mixin_activate_get_next_step; + wpobject_class->activate_execute_step = + wp_endpoint_link_activate_execute_step; + wpobject_class->deactivate = wp_endpoint_link_deactivate; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_EndpointLink; proxy_class->pw_iface_version = PW_VERSION_ENDPOINT_LINK; - - proxy_class->get_info = wp_endpoint_link_get_info; - proxy_class->get_properties = wp_endpoint_link_get_properties; - proxy_class->get_param_info = wp_endpoint_link_get_param_info; - proxy_class->enum_params = wp_endpoint_link_enum_params; - proxy_class->subscribe_params = wp_endpoint_link_subscribe_params; - proxy_class->set_param = wp_endpoint_link_set_param; proxy_class->pw_proxy_created = wp_endpoint_link_pw_proxy_created; + proxy_class->pw_proxy_destroyed = wp_endpoint_link_pw_proxy_destroyed; + + wp_pipewire_object_mixin_class_override_properties (object_class); /** * WpEndpointLink::state-changed: @@ -205,6 +195,63 @@ 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) +{ + WpEndpointLink *self = WP_ENDPOINT_LINK (obj); + WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self); + + return wp_pipewire_object_mixin_param_info_to_gvariant (priv->info->params, + priv->info->n_params); +} + +static void +wp_endpoint_link_enum_params (WpPipewireObject * obj, const gchar * id, + WpSpaPod *filter, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + wp_pipewire_object_mixin_enum_params (pw_endpoint_link, obj, id, filter, + cancellable, callback, user_data); +} + +static void +wp_endpoint_link_set_param (WpPipewireObject * obj, const gchar * id, + WpSpaPod * param) +{ + wp_pipewire_object_mixin_set_param (pw_endpoint_link, obj, id, param); +} + +static void +wp_endpoint_link_pipewire_object_interface_init ( + WpPipewireObjectInterface * 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; + 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; +} + /** * wp_endpoint_link_get_linked_object_ids: * @self: the endpoint link @@ -219,7 +266,7 @@ wp_endpoint_link_class_init (WpEndpointLinkClass * klass) * * Retrieves the ids of the objects that are linked by this endpoint link * - * Note: Using this method requires %WP_PROXY_FEATURE_INFO + * Note: Using this method requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO */ void wp_endpoint_link_get_linked_object_ids (WpEndpointLink * self, @@ -249,7 +296,7 @@ wp_endpoint_link_get_linked_object_ids (WpEndpointLink * self, * * Retrieves the current state of the link * - * Note: Using this method requires %WP_PROXY_FEATURE_INFO + * Note: Using this method requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO * Returns: the current state of the link */ WpEndpointLinkState @@ -262,7 +309,7 @@ wp_endpoint_link_get_state (WpEndpointLink * self, const gchar ** error) if (error) *error = priv->info->error; - return priv->info->state; + return (WpEndpointLinkState) priv->info->state; } /** @@ -272,7 +319,7 @@ wp_endpoint_link_get_state (WpEndpointLink * self, const gchar ** error) * * Requests a state change on the link * - * Note: Using this method requires %WP_PROXY_FEATURE_PW_PROXY + * Note: Using this method requires %WP_PROXY_FEATURE_BOUND */ void wp_endpoint_link_request_state (WpEndpointLink * self, @@ -419,14 +466,14 @@ populate_properties (WpImplEndpointLink * self, WpProperties *global_props) self->info.props = priv->properties ? (struct spa_dict *) wp_properties_peek_dict (priv->properties) : NULL; - - g_object_notify (G_OBJECT (self), "properties"); } static void on_si_link_properties_changed (WpSiLink * item, WpImplEndpointLink * self) { - populate_properties (self, wp_proxy_get_global_properties (WP_PROXY (self))); + populate_properties (self, + wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (self))); + g_object_notify (G_OBJECT (self), "properties"); self->info.change_mask = PW_ENDPOINT_LINK_CHANGE_MASK_PROPS; pw_endpoint_link_emit_info (&self->hooks, &self->info); @@ -478,19 +525,6 @@ wp_impl_endpoint_link_init (WpImplEndpointLink * self) priv->iface = (struct pw_endpoint_link *) &self->iface; } -static void -wp_impl_endpoint_link_finalize (GObject * object) -{ - WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (object); - WpEndpointLinkPrivate *priv = - wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); - - g_free (self->info.error); - priv->info = NULL; - - G_OBJECT_CLASS (wp_impl_endpoint_link_parent_class)->finalize (object); -} - static void wp_impl_endpoint_link_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) @@ -524,26 +558,32 @@ wp_impl_endpoint_link_get_property (GObject * object, guint property_id, } static void -wp_impl_endpoint_link_augment (WpProxy * proxy, WpProxyFeatures features) +wp_impl_endpoint_link_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (proxy); + WpImplEndpointLink *self = WP_IMPL_ENDPOINT_LINK (object); WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self)); - g_autoptr (GVariant) info = NULL; - g_autoptr (GVariantIter) immutable_props = NULL; - g_autoptr (WpProperties) props = NULL; - - /* PW_PROXY depends on BOUND */ - if (features & WP_PROXY_FEATURE_PW_PROXY) - features |= WP_PROXY_FEATURE_BOUND; - - /* BOUND depends on INFO */ - if (features & WP_PROXY_FEATURE_BOUND) - features |= WP_PROXY_FEATURE_INFO; - if (features & WP_PROXY_FEATURE_INFO) { + 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; + g_autoptr (WpCore) core = wp_object_get_core (object); + struct pw_core *pw_core = wp_core_get_pw_core (core); + + /* no pw_core -> we are not connected */ + if (!pw_core) { + wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "The WirePlumber core is not connected; " + "object cannot be exported to PipeWire")); + return; + } /* get info from the interface */ info = wp_si_link_get_registration_info (self->item); @@ -553,8 +593,8 @@ wp_impl_endpoint_link_augment (WpProxy * proxy, WpProxyFeatures features) self->info.state = (wp_session_item_get_flags (WP_SESSION_ITEM (self->item)) & WP_SI_FLAG_ACTIVE) - ? WP_ENDPOINT_LINK_STATE_ACTIVE - : WP_ENDPOINT_LINK_STATE_INACTIVE; + ? 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 ( @@ -604,46 +644,57 @@ wp_impl_endpoint_link_augment (WpProxy * proxy, WpProxyFeatures features) self->info.params = NULL; self->info.n_params = 0; priv->info = &self->info; - g_object_notify (G_OBJECT (self), "info"); - g_object_notify (G_OBJECT (self), "param-info"); - - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - } - - if (features & WP_PROXY_FEATURE_BOUND) { - g_autoptr (WpCore) core = wp_proxy_get_core (proxy); - struct pw_core *pw_core = wp_core_get_pw_core (core); - - /* no pw_core -> we are not connected */ - if (!pw_core) { - wp_proxy_augment_error (proxy, g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, - "The WirePlumber core is not connected; " - "object cannot be exported to PipeWire")); - return; - } - wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core, + /* 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"); + + break; } + default: + WP_OBJECT_CLASS (wp_impl_endpoint_link_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } +} + +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) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_impl_endpoint_link_finalize; object_class->set_property = wp_impl_endpoint_link_set_property; object_class->get_property = wp_impl_endpoint_link_get_property; - proxy_class->augment = wp_impl_endpoint_link_augment; - proxy_class->enum_params = NULL; - proxy_class->subscribe_params = NULL; + wpobject_class->activate_execute_step = + 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; g_object_class_install_property (object_class, IMPL_PROP_ITEM, g_param_spec_object ("item", "item", "item", WP_TYPE_SI_LINK, diff --git a/lib/wp/endpoint-link.h b/lib/wp/endpoint-link.h index ca3f0ef1bb81a1b1d8561c59aaacd40845a03b23..1ee5d1c47851f505f19526458e2619dcdead2d8d 100644 --- a/lib/wp/endpoint-link.h +++ b/lib/wp/endpoint-link.h @@ -9,7 +9,7 @@ #ifndef __WIREPLUMBER_ENDPOINT_LINK_H__ #define __WIREPLUMBER_ENDPOINT_LINK_H__ -#include "proxy.h" +#include "global-proxy.h" G_BEGIN_DECLS @@ -35,11 +35,11 @@ typedef enum { #define WP_TYPE_ENDPOINT_LINK (wp_endpoint_link_get_type ()) WP_API G_DECLARE_DERIVABLE_TYPE (WpEndpointLink, wp_endpoint_link, - WP, ENDPOINT_LINK, WpProxy) + WP, ENDPOINT_LINK, WpGlobalProxy) struct _WpEndpointLinkClass { - WpProxyClass parent_class; + WpGlobalProxyClass parent_class; }; WP_API diff --git a/lib/wp/endpoint-stream.c b/lib/wp/endpoint-stream.c index a6cc5c3870c3f3400aa36b3f432375daa3dbcb6b..1fca995854315f2880d3366bd30832644d11a916 100644 --- a/lib/wp/endpoint-stream.c +++ b/lib/wp/endpoint-stream.c @@ -7,39 +7,23 @@ */ /** - * SECTION: WpEndpointStream - * - * The #WpEndpointStream class allows accessing the properties and methods of a - * PipeWire endpoint stream object (`struct pw_endpoint_stream` from the - * session-manager extension). - * - * A #WpEndpointStream is constructed internally when a new endpoint appears on - * the PipeWire registry and it is made available through the #WpObjectManager - * API. + * SECTION: endpoint-stream + * @title: PipeWire Endpoint Stream */ #define G_LOG_DOMAIN "wp-endpoint-stream" #include "endpoint-stream.h" -#include "debug.h" #include "node.h" -#include "private.h" #include "error.h" +#include "private.h" +#include "private/pipewire-object-mixin.h" -#include <pipewire/pipewire.h> #include <pipewire/extensions/session-manager.h> #include <pipewire/extensions/session-manager/introspect-funcs.h> -#include <spa/pod/builder.h> -#include <spa/pod/parser.h> -#include <spa/pod/filter.h> - - -/* WpEndpointStream */ - enum { - PROP_0, - PROP_NAME, + PROP_NAME = WP_PIPEWIRE_OBJECT_MIXIN_PROP_CUSTOM_START, }; typedef struct _WpEndpointStreamPrivate WpEndpointStreamPrivate; @@ -51,27 +35,30 @@ struct _WpEndpointStreamPrivate struct spa_hook listener; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpEndpointStream, wp_endpoint_stream, WP_TYPE_PROXY) +static void wp_endpoint_stream_pipewire_object_interface_init (WpPipewireObjectInterface * iface); -static void -wp_endpoint_stream_init (WpEndpointStream * self) -{ -} +/** + * WpEndpointStream: + * + * The #WpEndpointStream class allows accessing the properties and methods of a + * PipeWire endpoint stream object (`struct pw_endpoint_stream` from the + * session-manager extension). + * + * A #WpEndpointStream is constructed internally when a new endpoint appears on + * the PipeWire registry and it is made available through the #WpObjectManager + * 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)); static void -wp_endpoint_stream_finalize (GObject * object) +wp_endpoint_stream_init (WpEndpointStream * self) { - WpEndpointStream *self = WP_ENDPOINT_STREAM (object); - 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); - - G_OBJECT_CLASS (wp_endpoint_stream_parent_class)->finalize (object); } static void -wp_endpoint_stream_get_gobj_property (GObject * object, guint property_id, +wp_endpoint_stream_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { WpEndpointStream *self = WP_ENDPOINT_STREAM (object); @@ -82,65 +69,45 @@ wp_endpoint_stream_get_gobj_property (GObject * object, guint property_id, g_value_set_string (value, priv->info ? priv->info->name : NULL); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + wp_pipewire_object_mixin_get_property (object, property_id, value, pspec); break; } } -static gconstpointer -wp_endpoint_stream_get_info (WpProxy * proxy) -{ - WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - return priv->info; -} - -static WpProperties * -wp_endpoint_stream_get_properties (WpProxy * proxy) -{ - WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy); - WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - - return wp_properties_ref (priv->properties); -} - -static struct spa_param_info * -wp_endpoint_stream_get_param_info (WpProxy * proxy, guint * n_params) +static WpObjectFeatures +wp_endpoint_stream_get_supported_features (WpObject * object) { - WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy); + WpEndpointStream *self = WP_ENDPOINT_STREAM (object); WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); - *n_params = priv->info->n_params; - return priv->info->params; + 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 gint -wp_endpoint_stream_enum_params (WpProxy * self, guint32 id, guint32 start, - guint32 num, WpSpaPod * filter) +static void +wp_endpoint_stream_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); - return pw_endpoint_stream_enum_params (priv->iface, 0, id, start, num, - filter ? wp_spa_pod_get_spa_pod (filter) : NULL); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + wp_pipewire_object_mixin_cache_info (object, transition); + break; + default: + WP_OBJECT_CLASS (wp_endpoint_stream_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } } -static gint -wp_endpoint_stream_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids) +static void +wp_endpoint_stream_deactivate (WpObject * object, WpObjectFeatures features) { - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); - return pw_endpoint_stream_subscribe_params (priv->iface, ids, n_ids); -} + wp_pipewire_object_mixin_deactivate (object, features); -static gint -wp_endpoint_stream_set_param (WpProxy * self, guint32 id, guint32 flags, - WpSpaPod *param) -{ - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); - return pw_endpoint_stream_set_param (priv->iface, id, flags, - wp_spa_pod_get_spa_pod (param)); + WP_OBJECT_CLASS (wp_endpoint_stream_parent_class)->deactivate (object, features); } static void @@ -156,20 +123,18 @@ endpoint_stream_event_info (void *data, const struct pw_endpoint_stream_info *in priv->properties = wp_properties_new_wrap_dict (priv->info->props); } - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - g_object_notify (G_OBJECT (self), "info"); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); - - if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) - g_object_notify (G_OBJECT (self), "param-info"); + 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_proxy_handle_event_param, + .param = wp_pipewire_object_mixin_handle_event_param, }; static void @@ -183,26 +148,44 @@ wp_endpoint_stream_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy &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); +} + static void wp_endpoint_stream_class_init (WpEndpointStreamClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_endpoint_stream_finalize; - object_class->get_property = wp_endpoint_stream_get_gobj_property; + object_class->get_property = wp_endpoint_stream_get_property; + + wpobject_class->get_supported_features = + wp_endpoint_stream_get_supported_features; + wpobject_class->activate_get_next_step = + wp_pipewire_object_mixin_activate_get_next_step; + wpobject_class->activate_execute_step = + wp_endpoint_stream_activate_execute_step; + wpobject_class->deactivate = wp_endpoint_stream_deactivate; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_EndpointStream; proxy_class->pw_iface_version = PW_VERSION_ENDPOINT_STREAM; - - proxy_class->get_info = wp_endpoint_stream_get_info; - proxy_class->get_properties = wp_endpoint_stream_get_properties; - proxy_class->get_param_info = wp_endpoint_stream_get_param_info; - proxy_class->enum_params = wp_endpoint_stream_enum_params; - proxy_class->subscribe_params = wp_endpoint_stream_subscribe_params; - proxy_class->set_param = wp_endpoint_stream_set_param; - proxy_class->pw_proxy_created = wp_endpoint_stream_pw_proxy_created; + proxy_class->pw_proxy_destroyed = wp_endpoint_stream_pw_proxy_destroyed; + + wp_pipewire_object_mixin_class_override_properties (object_class); /** * WpEndpointStream:name: @@ -214,6 +197,63 @@ wp_endpoint_stream_class_init (WpEndpointStreamClass * klass) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } +static gconstpointer +wp_endpoint_stream_get_native_info (WpPipewireObject * obj) +{ + WpEndpointStream *self = WP_ENDPOINT_STREAM (obj); + WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self); + + return priv->info; +} + +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, + WpSpaPod * param) +{ + wp_pipewire_object_mixin_set_param (pw_endpoint_stream, obj, id, param); +} + +static void +wp_endpoint_stream_pipewire_object_interface_init ( + WpPipewireObjectInterface * 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; + 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; +} + /** * wp_endpoint_stream_get_name: * @self: the endpoint stream @@ -224,8 +264,8 @@ const gchar * wp_endpoint_stream_get_name (WpEndpointStream * self) { g_return_val_if_fail (WP_IS_ENDPOINT_STREAM (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, NULL); + 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; @@ -246,7 +286,6 @@ struct _WpImplEndpointStream struct spa_interface iface; struct spa_hook_list hooks; struct pw_endpoint_stream_info info; - gboolean subscribed; WpSiStream *item; }; @@ -262,10 +301,10 @@ G_DEFINE_TYPE (WpImplEndpointStream, wp_impl_endpoint_stream, WP_TYPE_ENDPOINT_S #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 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, @@ -292,57 +331,12 @@ impl_enum_params (void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { - WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf)); - struct spa_pod *result; - guint count = 0; - WpProps *props = wp_proxy_get_props (WP_PROXY (self)); - - switch (id) { - case SPA_PARAM_PropInfo: { - g_autoptr (WpIterator) params = wp_props_iterate_prop_info (props); - g_auto (GValue) item = G_VALUE_INIT; - guint i = 0; - - for (; wp_iterator_next (params, &item); g_value_unset (&item), i++) { - WpSpaPod *pod = g_value_get_boxed (&item); - const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod); - if (spa_pod_filter (&b, &result, param, filter) == 0) { - pw_endpoint_stream_emit_param (&self->hooks, seq, id, i, i+1, result); - if (++count == num) - break; - } - } - break; - } - case SPA_PARAM_Props: { - if (start == 0) { - g_autoptr (WpSpaPod) pod = wp_props_get_all (props); - const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod); - if (spa_pod_filter (&b, &result, param, filter) == 0) { - pw_endpoint_stream_emit_param (&self->hooks, seq, id, 0, 1, result); - } - } - break; - } - default: - return -ENOENT; - } - - return 0; + return -ENOENT; } static int impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) { - WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - - for (guint i = 0; i < n_ids; i++) { - if (ids[i] == SPA_PARAM_Props) - self->subscribed = TRUE; - impl_enum_params (self, 1, ids[i], 0, UINT32_MAX, NULL); - } return 0; } @@ -350,14 +344,7 @@ static int impl_set_param (void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { - WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - - if (id != SPA_PARAM_Props) - return -ENOENT; - - WpProps *props = wp_proxy_get_props (WP_PROXY (self)); - wp_props_set (props, NULL, wp_spa_pod_new_wrap_const (param)); - return 0; + return -ENOENT; } static const struct pw_endpoint_stream_methods impl_endpoint_stream = { @@ -383,14 +370,14 @@ populate_properties (WpImplEndpointStream * self, WpProperties *global_props) self->info.props = priv->properties ? (struct spa_dict *) wp_properties_peek_dict (priv->properties) : NULL; - - g_object_notify (G_OBJECT (self), "properties"); } static void on_si_stream_properties_changed (WpSiStream * item, WpImplEndpointStream * self) { - populate_properties (self, wp_proxy_get_global_properties (WP_PROXY (self))); + populate_properties (self, + wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (self))); + g_object_notify (G_OBJECT (self), "properties"); self->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; pw_endpoint_stream_emit_info (&self->hooks, &self->info); @@ -414,19 +401,6 @@ wp_impl_endpoint_stream_init (WpImplEndpointStream * self) priv->iface = (struct pw_endpoint_stream *) &self->iface; } -static void -wp_impl_endpoint_stream_finalize (GObject * object) -{ - WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); - - g_free (self->info.name); - priv->info = NULL; - - G_OBJECT_CLASS (wp_impl_endpoint_stream_parent_class)->finalize (object); -} - static void wp_impl_endpoint_stream_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) @@ -459,146 +433,177 @@ wp_impl_endpoint_stream_get_property (GObject * object, guint property_id, } } -static void -wp_impl_endpoint_stream_export (WpImplEndpointStream * self) -{ - WpEndpointStreamPrivate *priv = - wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self)); - g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (self)); - struct pw_core *pw_core = wp_core_get_pw_core (core); - - g_autoptr (GVariantIter) immutable_properties = NULL; - g_autoptr (WpProperties) properties = NULL; - const gchar *key, *value; - - /* no pw_core -> we are not connected */ - if (!pw_core) { - wp_proxy_augment_error (WP_PROXY (self), g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, - "The WirePlumber core is not connected; " - "object cannot be exported to PipeWire")); - return; - } +enum { + STEP_ACTIVATE_NODE = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, +}; - wp_debug_object (self, "exporting"); +static guint +wp_impl_endpoint_stream_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) +{ + WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object); - /* 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); + /* bind if not already bound */ + if (missing & WP_PROXY_FEATURE_BOUND) { + g_autoptr (WpObject) node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); - /* associate with the endpoint */ - self->info.endpoint_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (self->item), WP_TYPE_ENDPOINT); + /* 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; } - - /* 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 = impl_param_info; - self->info.n_params = SPA_N_ELEMENTS (impl_param_info); - priv->info = &self->info; - - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - g_object_notify (G_OBJECT (self), "info"); - g_object_notify (G_OBJECT (self), "param-info"); - - 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)); + /* cache info if supported */ + else + return WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO; } static void -wp_impl_endpoint_stream_continue_feature_props (WpProxy * node, - GAsyncResult * res, WpImplEndpointStream * self) +wp_impl_endpoint_stream_node_activated (WpObject * node, + GAsyncResult * res, WpTransition * transition) { g_autoptr (GError) error = NULL; - if (!wp_proxy_augment_finish (node, res, &error)) { - wp_proxy_augment_error (WP_PROXY (self), g_steal_pointer (&error)); + if (!wp_object_activate_finish (node, res, &error)) { + wp_transition_return_error (transition, g_steal_pointer (&error)); return; } - WpProps *props = wp_proxy_get_props (node); - wp_proxy_set_props (WP_PROXY (self), g_object_ref (props)); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_PROPS); - wp_impl_endpoint_stream_export (self); + wp_transition_advance (transition); } static void -wp_impl_endpoint_stream_augment (WpProxy * proxy, WpProxyFeatures features) +wp_impl_endpoint_stream_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (proxy); - g_autoptr (GVariant) info = NULL; - g_autoptr (GVariantIter) immutable_props = NULL; - g_autoptr (WpProperties) props = NULL; - - /* if any of these features are requested, export, - after ensuring that we have a WpProps so that enum_params works */ - if (features & (WP_PROXY_FEATURES_STANDARD | WP_PROXY_FEATURE_PROPS)) { - g_autoptr (WpProxy) node = wp_session_item_get_associated_proxy ( + 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: { + g_autoptr (WpObject) node = wp_session_item_get_associated_proxy ( WP_SESSION_ITEM (self->item), WP_TYPE_NODE); - if (node) { - /* if the item has a node, use the props of that node */ - wp_proxy_augment (node, WP_PROXY_FEATURE_PROPS, NULL, - (GAsyncReadyCallback) wp_impl_endpoint_stream_continue_feature_props, - self); - } else { - /* else install an empty WpProps */ - WpProps *props = wp_props_new (WP_PROPS_MODE_STORE, proxy); - wp_proxy_set_props (proxy, props); - wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_PROPS); - wp_impl_endpoint_stream_export (self); + wp_object_activate (node, + 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; + g_autoptr (WpCore) core = wp_object_get_core (object); + struct pw_core *pw_core = wp_core_get_pw_core (core); + + /* no pw_core -> we are not connected */ + if (!pw_core) { + wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "The WirePlumber core is not connected; " + "object cannot be exported to PipeWire")); + 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"); + + break; + } + default: + WP_OBJECT_CLASS (wp_impl_endpoint_stream_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; } } static void -wp_impl_endpoint_stream_prop_changed (WpProxy * proxy, const gchar * prop_name) +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)); - /* notify subscribers */ - if (self->subscribed) - impl_enum_params (self, 1, SPA_PARAM_Props, 0, UINT32_MAX, NULL); + 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) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_impl_endpoint_stream_finalize; object_class->set_property = wp_impl_endpoint_stream_set_property; object_class->get_property = wp_impl_endpoint_stream_get_property; - proxy_class->augment = wp_impl_endpoint_stream_augment; - proxy_class->enum_params = NULL; - proxy_class->subscribe_params = NULL; + wpobject_class->activate_get_next_step = + wp_impl_endpoint_stream_activate_get_next_step; + wpobject_class->activate_execute_step = + wp_impl_endpoint_stream_activate_execute_step; + proxy_class->pw_proxy_created = NULL; - proxy_class->prop_changed = wp_impl_endpoint_stream_prop_changed; + proxy_class->pw_proxy_destroyed = wp_impl_endpoint_stream_pw_proxy_destroyed; g_object_class_install_property (object_class, IMPL_PROP_ITEM, g_param_spec_object ("item", "item", "item", WP_TYPE_SI_STREAM, diff --git a/lib/wp/endpoint-stream.h b/lib/wp/endpoint-stream.h index c6b21bb04ecc801c5f6e2835be45500ac3c9988f..665e4b540de3cda332e0992f24c1229e26385834 100644 --- a/lib/wp/endpoint-stream.h +++ b/lib/wp/endpoint-stream.h @@ -9,8 +9,7 @@ #ifndef __WIREPLUMBER_ENDPOINT_STREAM_H__ #define __WIREPLUMBER_ENDPOINT_STREAM_H__ -#include "proxy.h" -#include "spa-pod.h" +#include "global-proxy.h" G_BEGIN_DECLS @@ -22,11 +21,11 @@ G_BEGIN_DECLS #define WP_TYPE_ENDPOINT_STREAM (wp_endpoint_stream_get_type ()) WP_API G_DECLARE_DERIVABLE_TYPE (WpEndpointStream, wp_endpoint_stream, - WP, ENDPOINT_STREAM, WpProxy) + WP, ENDPOINT_STREAM, WpGlobalProxy) struct _WpEndpointStreamClass { - WpProxyClass parent_class; + WpGlobalProxyClass parent_class; }; WP_API diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c index cdf5d593b4f89dc1eceb16ecf559768a9be57df6..667a42798747cddc965b02855c0c632c85c8176a 100644 --- a/lib/wp/endpoint.c +++ b/lib/wp/endpoint.c @@ -7,41 +7,26 @@ */ /** - * SECTION: WpEndpoint - * - * The #WpEndpoint class allows accessing the properties and methods of a - * PipeWire endpoint object (`struct pw_endpoint` from the session-manager - * extension). - * - * A #WpEndpoint is constructed internally when a new endpoint appears on the - * PipeWire registry and it is made available through the #WpObjectManager API. + * SECTION: endpoint + * @title: PIpeWire Endpoint */ #define G_LOG_DOMAIN "wp-endpoint" #include "endpoint.h" -#include "debug.h" #include "node.h" #include "session.h" -#include "private.h" #include "error.h" #include "wpenums.h" #include "si-factory.h" +#include "private.h" +#include "private/pipewire-object-mixin.h" -#include <pipewire/pipewire.h> #include <pipewire/extensions/session-manager.h> #include <pipewire/extensions/session-manager/introspect-funcs.h> -#include <spa/pod/builder.h> -#include <spa/pod/parser.h> -#include <spa/pod/filter.h> -#include <spa/utils/result.h> - -/* WpEndpoint */ - enum { - PROP_0, - PROP_NAME, + PROP_NAME = WP_PIPEWIRE_OBJECT_MIXIN_PROP_CUSTOM_START, PROP_MEDIA_CLASS, PROP_DIRECTION, }; @@ -61,31 +46,31 @@ struct _WpEndpointPrivate struct pw_endpoint *iface; struct spa_hook listener; WpObjectManager *streams_om; - gboolean ft_streams_requested; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpEndpoint, wp_endpoint, WP_TYPE_PROXY) +static void wp_endpoint_pipewire_object_interface_init (WpPipewireObjectInterface * iface); -static void -wp_endpoint_init (WpEndpoint * self) -{ -} +/** + * WpEndpoint: + * + * The #WpEndpoint class allows accessing the properties and methods of a + * PipeWire endpoint object (`struct pw_endpoint` from the session-manager + * extension). + * + * A #WpEndpoint is constructed internally when a new endpoint appears on the + * PipeWire registry and it is made available through the #WpObjectManager API. + */ +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)); static void -wp_endpoint_finalize (GObject * object) +wp_endpoint_init (WpEndpoint * self) { - WpEndpoint *self = WP_ENDPOINT (object); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - - g_clear_object (&priv->streams_om); - g_clear_pointer (&priv->properties, wp_properties_unref); - g_clear_pointer (&priv->info, pw_endpoint_info_free); - - G_OBJECT_CLASS (wp_endpoint_parent_class)->finalize (object); } static void -wp_endpoint_get_gobj_property (GObject * object, guint property_id, +wp_endpoint_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { WpEndpoint *self = WP_ENDPOINT (object); @@ -102,7 +87,7 @@ wp_endpoint_get_gobj_property (GObject * object, guint property_id, g_value_set_enum (value, priv->info ? priv->info->direction : 0); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + wp_pipewire_object_mixin_get_property (object, property_id, value, pspec); break; } } @@ -111,7 +96,7 @@ static void wp_endpoint_on_streams_om_installed (WpObjectManager *streams_om, WpEndpoint * self) { - wp_proxy_set_feature_ready (WP_PROXY (self), WP_ENDPOINT_FEATURE_STREAMS); + wp_object_update_features (WP_OBJECT (self), WP_ENDPOINT_FEATURE_STREAMS, 0); } static void @@ -119,129 +104,120 @@ wp_endpoint_emit_streams_changed (WpObjectManager *streams_om, WpEndpoint * self) { g_signal_emit (self, signals[SIGNAL_STREAMS_CHANGED], 0); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_ENDPOINT_FEATURE_STREAMS); + wp_object_update_features (WP_OBJECT (self), WP_ENDPOINT_FEATURE_STREAMS, 0); } static void -wp_endpoint_ensure_feature_streams (WpEndpoint * self, guint32 bound_id) +wp_endpoint_enable_feature_streams (WpEndpoint * self) { WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - WpProxyFeatures ft = wp_proxy_get_features (WP_PROXY (self)); - - if (priv->ft_streams_requested && !priv->streams_om && - (ft & WP_PROXY_FEATURES_STANDARD) == WP_PROXY_FEATURES_STANDARD) - { - g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (self)); - - if (!bound_id) - bound_id = wp_proxy_get_bound_id (WP_PROXY (self)); + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); + guint32 bound_id = wp_proxy_get_bound_id (WP_PROXY (self)); - wp_debug_object (self, "enabling WP_ENDPOINT_FEATURE_STREAMS, bound_id:%u, " - "n_streams:%u", bound_id, priv->info->n_streams); + wp_debug_object (self, "enabling WP_ENDPOINT_FEATURE_STREAMS, bound_id:%u, " + "n_streams:%u", bound_id, priv->info->n_streams); - priv->streams_om = wp_object_manager_new (); - /* proxy endpoint stream -> check for endpoint.id in global properties */ - wp_object_manager_add_interest (priv->streams_om, - WP_TYPE_ENDPOINT_STREAM, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, PW_KEY_ENDPOINT_ID, "=u", bound_id, - NULL); - /* impl endpoint stream -> check for endpoint.id in standard properties */ - wp_object_manager_add_interest (priv->streams_om, - WP_TYPE_IMPL_ENDPOINT_STREAM, - WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_ENDPOINT_ID, "=u", bound_id, - NULL); - wp_object_manager_request_proxy_features (priv->streams_om, - WP_TYPE_ENDPOINT_STREAM, - WP_PROXY_FEATURES_STANDARD | WP_PROXY_FEATURE_PROPS); - - /* endpoints, under normal circumstances, always have streams. - When we export (self is a WpImplEndpoint), we have to export first - the endpoint and afterwards the streams (so that the streams can be - associated with the endpoint's bound id), but then the issue is that - the "installed" signal gets fired here without any streams being ready - 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)) { - g_signal_connect_object (priv->streams_om, "installed", - G_CALLBACK (wp_endpoint_on_streams_om_installed), self, 0); - } - g_signal_connect_object (priv->streams_om, "objects-changed", - G_CALLBACK (wp_endpoint_emit_streams_changed), self, 0); - - wp_core_install_object_manager (core, priv->streams_om); + priv->streams_om = wp_object_manager_new (); + /* proxy endpoint stream -> check for endpoint.id in global properties */ + wp_object_manager_add_interest (priv->streams_om, + WP_TYPE_ENDPOINT_STREAM, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, PW_KEY_ENDPOINT_ID, "=u", bound_id, + NULL); + /* impl endpoint stream -> check for endpoint.id in standard properties */ + wp_object_manager_add_interest (priv->streams_om, + WP_TYPE_IMPL_ENDPOINT_STREAM, + WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_ENDPOINT_ID, "=u", bound_id, + NULL); + wp_object_manager_request_object_features (priv->streams_om, + WP_TYPE_ENDPOINT_STREAM, WP_OBJECT_FEATURES_ALL); + + /* endpoints, under normal circumstances, always have streams. + When we export (self is a WpImplEndpoint), we have to export first + the endpoint and afterwards the streams (so that the streams can be + associated with the endpoint's bound id), but then the issue is that + the "installed" signal gets fired here without any streams being ready + 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)) { + g_signal_connect_object (priv->streams_om, "installed", + G_CALLBACK (wp_endpoint_on_streams_om_installed), self, 0); } + g_signal_connect_object (priv->streams_om, "objects-changed", + G_CALLBACK (wp_endpoint_emit_streams_changed), self, 0); + + wp_core_install_object_manager (core, priv->streams_om); } -static void -wp_endpoint_augment (WpProxy * proxy, WpProxyFeatures features) +static WpObjectFeatures +wp_endpoint_get_supported_features (WpObject * object) { - WpEndpoint *self = WP_ENDPOINT (proxy); + WpEndpoint *self = WP_ENDPOINT (object); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - /* call the parent impl first to ensure we have a pw proxy if necessary */ - WP_PROXY_CLASS (wp_endpoint_parent_class)->augment (proxy, features); - - if (features & WP_ENDPOINT_FEATURE_STREAMS) { - priv->ft_streams_requested = TRUE; - wp_endpoint_ensure_feature_streams (self, 0); - } + 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); } -static gconstpointer -wp_endpoint_get_info (WpProxy * proxy) +enum { + STEP_STREAMS = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, +}; + +static guint +wp_endpoint_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpEndpoint *self = WP_ENDPOINT (proxy); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); + step = wp_pipewire_object_mixin_activate_get_next_step (object, transition, + step, missing); - return priv->info; + /* 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 WpProperties * -wp_endpoint_get_properties (WpProxy * proxy) +static void +wp_endpoint_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpEndpoint *self = WP_ENDPOINT (proxy); - WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - - return wp_properties_ref (priv->properties); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + wp_pipewire_object_mixin_cache_info (object, transition); + 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; + } } -static struct spa_param_info * -wp_endpoint_get_param_info (WpProxy * proxy, guint * n_params) +static void +wp_endpoint_deactivate (WpObject * object, WpObjectFeatures features) { - WpEndpoint *self = WP_ENDPOINT (proxy); + WpEndpoint *self = WP_ENDPOINT (object); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); - *n_params = priv->info->n_params; - return priv->info->params; -} + wp_pipewire_object_mixin_deactivate (object, features); -static gint -wp_endpoint_enum_params (WpProxy * self, guint32 id, guint32 start, guint32 num, - WpSpaPod * filter) -{ - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - return pw_endpoint_enum_params (priv->iface, 0, id, - start, num, filter ? wp_spa_pod_get_spa_pod (filter) : NULL); -} - -static gint -wp_endpoint_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids) -{ - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - return pw_endpoint_subscribe_params (priv->iface, ids, n_ids); -} + if (features & WP_ENDPOINT_FEATURE_STREAMS) { + g_clear_object (&priv->streams_om); + wp_object_update_features (object, 0, WP_ENDPOINT_FEATURE_STREAMS); + } -static gint -wp_endpoint_set_param (WpProxy * self, guint32 id, guint32 flags, - WpSpaPod *param) -{ - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - return pw_endpoint_set_param (priv->iface, id, flags, - wp_spa_pod_get_spa_pod (param)); + WP_OBJECT_CLASS (wp_endpoint_parent_class)->deactivate (object, features); } static void @@ -257,22 +233,17 @@ endpoint_event_info (void *data, const struct pw_endpoint_info *info) priv->properties = wp_properties_new_wrap_dict (priv->info->props); } - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - g_object_notify (G_OBJECT (self), "info"); - - if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); - - if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) - g_object_notify (G_OBJECT (self), "param-info"); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - wp_endpoint_ensure_feature_streams (self, 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_proxy_handle_event_param, + .param = wp_pipewire_object_mixin_handle_event_param, }; static void @@ -287,34 +258,41 @@ wp_endpoint_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) } static void -wp_endpoint_bound (WpProxy * proxy, guint32 id) +wp_endpoint_pw_proxy_destroyed (WpProxy * proxy) { WpEndpoint *self = WP_ENDPOINT (proxy); - wp_endpoint_ensure_feature_streams (self, id); + 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); + 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); } static void wp_endpoint_class_init (WpEndpointClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_endpoint_finalize; - object_class->get_property = wp_endpoint_get_gobj_property; + 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_execute_step = wp_endpoint_activate_execute_step; + wpobject_class->deactivate = wp_endpoint_deactivate; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Endpoint; proxy_class->pw_iface_version = PW_VERSION_ENDPOINT; - - proxy_class->augment = wp_endpoint_augment; - proxy_class->get_info = wp_endpoint_get_info; - proxy_class->get_properties = wp_endpoint_get_properties; - proxy_class->get_param_info = wp_endpoint_get_param_info; - proxy_class->enum_params = wp_endpoint_enum_params; - proxy_class->subscribe_params = wp_endpoint_subscribe_params; - proxy_class->set_param = wp_endpoint_set_param; - proxy_class->pw_proxy_created = wp_endpoint_pw_proxy_created; - proxy_class->bound = wp_endpoint_bound; + proxy_class->pw_proxy_destroyed = wp_endpoint_pw_proxy_destroyed; + + wp_pipewire_object_mixin_class_override_properties (object_class); /** * WpEndpoint::streams-changed: @@ -355,18 +333,77 @@ 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) +{ + 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); +} + +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, + WpSpaPod * param) +{ + wp_pipewire_object_mixin_set_param (pw_endpoint, obj, id, param); +} + +static void +wp_endpoint_pipewire_object_interface_init ( + WpPipewireObjectInterface * 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; + 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; +} + /** * wp_endpoint_get_name: * @self: the endpoint * + * Requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO + * * Returns: the name of the endpoint */ const gchar * wp_endpoint_get_name (WpEndpoint * self) { g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, NULL); + 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; @@ -376,14 +413,16 @@ wp_endpoint_get_name (WpEndpoint * self) * wp_endpoint_get_media_class: * @self: the endpoint * + * Requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO + * * Returns: the media class of the endpoint (ex. "Audio/Sink") */ const gchar * wp_endpoint_get_media_class (WpEndpoint * self) { g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, NULL); + 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; @@ -393,14 +432,16 @@ wp_endpoint_get_media_class (WpEndpoint * self) * wp_endpoint_get_direction: * @self: the endpoint * + * Requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO + * * Returns: the direction of this endpoint */ WpDirection wp_endpoint_get_direction (WpEndpoint * self) { g_return_val_if_fail (WP_IS_ENDPOINT (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, 0); + 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; @@ -418,7 +459,7 @@ guint wp_endpoint_get_n_streams (WpEndpoint * self) { g_return_val_if_fail (WP_IS_ENDPOINT (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_ENDPOINT_FEATURE_STREAMS, 0); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); @@ -438,7 +479,7 @@ WpIterator * wp_endpoint_iterate_streams (WpEndpoint * self) { g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_ENDPOINT_FEATURE_STREAMS, NULL); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); @@ -484,7 +525,7 @@ wp_endpoint_iterate_streams_filtered_full (WpEndpoint * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_ENDPOINT_FEATURE_STREAMS, NULL); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); @@ -529,7 +570,7 @@ WpEndpointStream * wp_endpoint_lookup_stream_full (WpEndpoint * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_ENDPOINT_FEATURE_STREAMS, NULL); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self); @@ -589,7 +630,6 @@ struct _WpImplEndpoint struct spa_interface iface; struct spa_hook_list hooks; struct pw_endpoint_info info; - gboolean subscribed; WpSiEndpoint *item; }; @@ -603,10 +643,10 @@ G_DEFINE_TYPE (WpImplEndpoint, wp_impl_endpoint, WP_TYPE_ENDPOINT) #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 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, @@ -632,57 +672,12 @@ impl_enum_params (void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { - WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf)); - struct spa_pod *result; - guint count = 0; - WpProps *props = wp_proxy_get_props (WP_PROXY (self)); - - switch (id) { - case SPA_PARAM_PropInfo: { - g_autoptr (WpIterator) params = wp_props_iterate_prop_info (props); - g_auto (GValue) item = G_VALUE_INIT; - guint i = 0; - - for (; wp_iterator_next (params, &item); g_value_unset (&item), i++) { - WpSpaPod *pod = g_value_get_boxed (&item); - const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod); - if (spa_pod_filter (&b, &result, param, filter) == 0) { - pw_endpoint_emit_param (&self->hooks, seq, id, i, i+1, result); - if (++count == num) - break; - } - } - break; - } - case SPA_PARAM_Props: { - if (start == 0) { - g_autoptr (WpSpaPod) pod = wp_props_get_all (props); - const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod); - if (spa_pod_filter (&b, &result, param, filter) == 0) { - pw_endpoint_emit_param (&self->hooks, seq, id, 0, 1, result); - } - } - break; - } - default: - return -ENOENT; - } - - return 0; + return -ENOENT; } static int impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) { - WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - - for (guint i = 0; i < n_ids; i++) { - if (ids[i] == SPA_PARAM_Props) - self->subscribed = TRUE; - impl_enum_params (self, 1, ids[i], 0, UINT32_MAX, NULL); - } return 0; } @@ -690,14 +685,7 @@ static int impl_set_param (void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { - WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - - if (id != SPA_PARAM_Props) - return -ENOENT; - - WpProps *props = wp_proxy_get_props (WP_PROXY (self)); - wp_props_set (props, NULL, wp_spa_pod_new_wrap_const (param)); - return 0; + return -ENOENT; } static void @@ -839,7 +827,7 @@ impl_create_link (void *object, const struct spa_dict *props) GVariantBuilder b; guint64 out_stream_i, in_stream_i; - core = wp_proxy_get_core (WP_PROXY (self)); + core = wp_object_get_core (WP_OBJECT (self)); link = wp_session_item_make (core, "si-standard-link"); if (!link) { wp_warning_object (self, "si-standard-link factory is not available"); @@ -898,14 +886,14 @@ populate_properties (WpImplEndpoint * self, WpProperties *global_props) self->info.props = priv->properties ? (struct spa_dict *) wp_properties_peek_dict (priv->properties) : NULL; - - g_object_notify (G_OBJECT (self), "properties"); } static void on_si_endpoint_properties_changed (WpSiEndpoint * item, WpImplEndpoint * self) { - populate_properties (self, wp_proxy_get_global_properties (WP_PROXY (self))); + populate_properties (self, + wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (self))); + g_object_notify (G_OBJECT (self), "properties"); self->info.change_mask = PW_ENDPOINT_CHANGE_MASK_PROPS; pw_endpoint_emit_info (&self->hooks, &self->info); @@ -929,20 +917,6 @@ wp_impl_endpoint_init (WpImplEndpoint * self) priv->iface = (struct pw_endpoint *) &self->iface; } -static void -wp_impl_endpoint_finalize (GObject * object) -{ - WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - - g_free (self->info.name); - g_free (self->info.media_class); - priv->info = NULL; - - G_OBJECT_CLASS (wp_impl_endpoint_parent_class)->finalize (object); -} - static void wp_impl_endpoint_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) @@ -975,157 +949,189 @@ wp_impl_endpoint_get_property (GObject * object, guint property_id, } } -static void -wp_impl_endpoint_export (WpImplEndpoint *self) -{ - WpEndpointPrivate *priv = - wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - g_autoptr (GVariantIter) immutable_properties = NULL; - g_autoptr (WpProperties) properties = NULL; - g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (self)); - struct pw_core *pw_core = wp_core_get_pw_core (core); - - /* no pw_core -> we are not connected */ - if (!pw_core) { - wp_proxy_augment_error (WP_PROXY (self), g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, - "The WirePlumber core is not connected; " - "object cannot be exported to PipeWire")); - 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); +enum { + STEP_ACTIVATE_NODE = STEP_STREAMS + 1, +}; - /* associate with the session */ - self->info.session_id = wp_session_item_get_associated_proxy_id ( - WP_SESSION_ITEM (self->item), WP_TYPE_SESSION); - } +static guint +wp_impl_endpoint_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) +{ + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); - /* 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); + /* bind if not already bound */ + if (missing & WP_PROXY_FEATURE_BOUND) { + g_autoptr (WpObject) node = wp_session_item_get_associated_proxy ( + WP_SESSION_ITEM (self->item), WP_TYPE_NODE); - /* 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); + /* 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; } - - /* 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 = impl_param_info; - self->info.n_params = SPA_N_ELEMENTS (impl_param_info); - priv->info = &self->info; - - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - g_object_notify (G_OBJECT (self), "info"); - g_object_notify (G_OBJECT (self), "param-info"); - - 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)); + /* 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; } static void -wp_impl_endpoint_continue_feature_props (WpProxy * node, GAsyncResult * res, - WpImplEndpoint * self) +wp_impl_endpoint_node_activated (WpObject * node, + GAsyncResult * res, WpTransition * transition) { g_autoptr (GError) error = NULL; - if (!wp_proxy_augment_finish (node, res, &error)) { - wp_proxy_augment_error (WP_PROXY (self), g_steal_pointer (&error)); + if (!wp_object_activate_finish (node, res, &error)) { + wp_transition_return_error (transition, g_steal_pointer (&error)); return; } - WpProps *props = wp_proxy_get_props (node); - wp_proxy_set_props (WP_PROXY (self), g_object_ref (props)); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_PROPS); - wp_impl_endpoint_export (self); + wp_transition_advance (transition); } static void -wp_impl_endpoint_augment (WpProxy * proxy, WpProxyFeatures features) +wp_impl_endpoint_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpImplEndpoint *self = WP_IMPL_ENDPOINT (proxy); + WpImplEndpoint *self = WP_IMPL_ENDPOINT (object); WpEndpointPrivate *priv = wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - /* if any of these features are requested, export, - after ensuring that we have a WpProps so that enum_params works */ - if (features & (WP_PROXY_FEATURES_STANDARD | WP_PROXY_FEATURE_PROPS)) { - g_autoptr (WpProxy) node = wp_session_item_get_associated_proxy ( + switch (step) { + case STEP_ACTIVATE_NODE: { + g_autoptr (WpObject) node = wp_session_item_get_associated_proxy ( WP_SESSION_ITEM (self->item), WP_TYPE_NODE); - if (node) { - /* if the item has a node, use the props of that node */ - wp_proxy_augment (node, WP_PROXY_FEATURE_PROPS, NULL, - (GAsyncReadyCallback) wp_impl_endpoint_continue_feature_props, self); - } else { - /* else install an empty WpProps */ - WpProps *props = wp_props_new (WP_PROPS_MODE_STORE, proxy); - wp_proxy_set_props (proxy, props); - wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_PROPS); - wp_impl_endpoint_export (self); - } + wp_object_activate (node, + 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; + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); + struct pw_core *pw_core = wp_core_get_pw_core (core); + + /* no pw_core -> we are not connected */ + if (!pw_core) { + wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "The WirePlumber core is not connected; " + "object cannot be exported to PipeWire")); + return; + } - if (features & WP_ENDPOINT_FEATURE_STREAMS) { - priv->ft_streams_requested = TRUE; - wp_endpoint_ensure_feature_streams (WP_ENDPOINT (self), 0); + 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"); + + break; + } + default: + WP_OBJECT_CLASS (wp_impl_endpoint_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; } } static void -wp_impl_endpoint_prop_changed (WpProxy * proxy, const gchar * prop_name) +wp_impl_endpoint_pw_proxy_destroyed (WpProxy * proxy) { WpImplEndpoint *self = WP_IMPL_ENDPOINT (proxy); + WpEndpointPrivate *priv = + wp_endpoint_get_instance_private (WP_ENDPOINT (self)); - /* notify subscribers */ - if (self->subscribed) - impl_enum_params (self, 1, SPA_PARAM_Props, 0, UINT32_MAX, NULL); + 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; + wp_object_update_features (WP_OBJECT (proxy), 0, + WP_PIPEWIRE_OBJECT_FEATURE_INFO + /*| WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS */); } static void wp_impl_endpoint_class_init (WpImplEndpointClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_impl_endpoint_finalize; object_class->set_property = wp_impl_endpoint_set_property; object_class->get_property = wp_impl_endpoint_get_property; - proxy_class->augment = wp_impl_endpoint_augment; - proxy_class->enum_params = NULL; - proxy_class->subscribe_params = NULL; + wpobject_class->activate_get_next_step = + wp_impl_endpoint_activate_get_next_step; + wpobject_class->activate_execute_step = + wp_impl_endpoint_activate_execute_step; + proxy_class->pw_proxy_created = NULL; - proxy_class->prop_changed = wp_impl_endpoint_prop_changed; + proxy_class->pw_proxy_destroyed = wp_impl_endpoint_pw_proxy_destroyed; g_object_class_install_property (object_class, IMPL_PROP_ITEM, g_param_spec_object ("item", "item", "item", WP_TYPE_SI_ENDPOINT, diff --git a/lib/wp/endpoint.h b/lib/wp/endpoint.h index 2aefdff819b86f5979d73f590a916c889868a842..d7bce89c5b644c618fe0f621e65cb224de2ca7b4 100644 --- a/lib/wp/endpoint.h +++ b/lib/wp/endpoint.h @@ -9,8 +9,7 @@ #ifndef __WIREPLUMBER_ENDPOINT_H__ #define __WIREPLUMBER_ENDPOINT_H__ -#include "spa-pod.h" -#include "proxy.h" +#include "global-proxy.h" #include "port.h" #include "endpoint-stream.h" #include "iterator.h" @@ -27,20 +26,9 @@ G_BEGIN_DECLS * An extension of #WpProxyFeatures */ typedef enum { /*< flags >*/ - WP_ENDPOINT_FEATURE_STREAMS = (WP_PROXY_FEATURE_LAST << 0), + WP_ENDPOINT_FEATURE_STREAMS = (WP_PROXY_FEATURE_CUSTOM_START << 0), } WpEndpointFeatures; -/** - * WP_ENDPOINT_FEATURES_STANDARD: - * - * A constant set of features that contains the standard features that are - * available in the #WpEndpoint class. - */ -#define WP_ENDPOINT_FEATURES_STANDARD \ - (WP_PROXY_FEATURES_STANDARD | \ - WP_PROXY_FEATURE_PROPS | \ - WP_ENDPOINT_FEATURE_STREAMS) - /** * WP_TYPE_ENDPOINT: * @@ -48,11 +36,11 @@ typedef enum { /*< flags >*/ */ #define WP_TYPE_ENDPOINT (wp_endpoint_get_type ()) WP_API -G_DECLARE_DERIVABLE_TYPE (WpEndpoint, wp_endpoint, WP, ENDPOINT, WpProxy) +G_DECLARE_DERIVABLE_TYPE (WpEndpoint, wp_endpoint, WP, ENDPOINT, WpGlobalProxy) struct _WpEndpointClass { - WpProxyClass parent_class; + WpGlobalProxyClass parent_class; }; WP_API diff --git a/lib/wp/global-proxy.c b/lib/wp/global-proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..7de94a45bf1dc5541bbccd7319427c5212b29893 --- /dev/null +++ b/lib/wp/global-proxy.c @@ -0,0 +1,293 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +/** + * SECTION: global-proxy + * @title: PipeWire Global Object Proxy + */ + +#define G_LOG_DOMAIN "wp-global-proxy" + +#include "global-proxy.h" +#include "private/registry.h" +#include "core.h" +#include "error.h" + +typedef struct _WpGlobalProxyPrivate WpGlobalProxyPrivate; +struct _WpGlobalProxyPrivate +{ + WpGlobal *global; +}; + +enum { + PROP_0, + PROP_GLOBAL, + PROP_PERMISSIONS, + PROP_GLOBAL_PROPERTIES, +}; + +/** + * WpGlobalProxy: + * + * A proxy that represents a PipeWire global object, i.e. an object that is + * made available through the PipeWire registry. + */ +G_DEFINE_TYPE_WITH_PRIVATE (WpGlobalProxy, wp_global_proxy, WP_TYPE_PROXY) + +static void +wp_global_proxy_init (WpGlobalProxy * self) +{ +} + +static void +wp_global_proxy_dispose (GObject * object) +{ + WpGlobalProxy *self = WP_GLOBAL_PROXY (object); + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + + if (priv->global) + wp_global_rm_flag (priv->global, WP_GLOBAL_FLAG_OWNED_BY_PROXY); + + G_OBJECT_CLASS (wp_global_proxy_parent_class)->dispose (object); +} + +static void +wp_global_proxy_finalize (GObject * object) +{ + WpGlobalProxy *self = WP_GLOBAL_PROXY (object); + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + + g_clear_pointer (&priv->global, wp_global_unref); + + G_OBJECT_CLASS (wp_global_proxy_parent_class)->finalize (object); +} + +static void +wp_global_proxy_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + WpGlobalProxy *self = WP_GLOBAL_PROXY (object); + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + + switch (property_id) { + case PROP_GLOBAL: + priv->global = g_value_dup_boxed (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +wp_global_proxy_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + WpGlobalProxy *self = WP_GLOBAL_PROXY (object); + + switch (property_id) { + case PROP_PERMISSIONS: + g_value_set_uint (value, wp_global_proxy_get_permissions (self)); + break; + case PROP_GLOBAL_PROPERTIES: + g_value_set_boxed (value, wp_global_proxy_get_global_properties (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static WpObjectFeatures +wp_global_proxy_get_supported_features (WpObject * object) +{ + return WP_PROXY_FEATURE_BOUND; +} + +enum { + STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START, +}; + +static guint +wp_global_proxy_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) +{ + g_return_val_if_fail (missing == WP_PROXY_FEATURE_BOUND, + WP_TRANSITION_STEP_ERROR); + + return STEP_BIND; +} + +static void +wp_global_proxy_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) +{ + WpGlobalProxy *self = WP_GLOBAL_PROXY (object); + + switch (step) { + case STEP_BIND: + if (wp_proxy_get_pw_proxy (WP_PROXY (self)) == NULL) { + if (!wp_global_proxy_bind (self)) { + wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT, + "No global specified; cannot bind proxy")); + } + } + break; + case WP_TRANSITION_STEP_ERROR: + break; + default: + g_assert_not_reached (); + } +} + +static void +wp_global_proxy_bound (WpProxy * proxy, guint32 global_id) +{ + WpGlobalProxy *self = WP_GLOBAL_PROXY (proxy); + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); + + if (!priv->global) { + wp_registry_prepare_new_global (wp_core_get_registry (core), + global_id, PW_PERM_ALL, WP_GLOBAL_FLAG_OWNED_BY_PROXY, + G_TYPE_FROM_INSTANCE (self), self, NULL, &priv->global); + } +} + +static void +wp_global_proxy_class_init (WpGlobalProxyClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; + WpProxyClass *proxy_class = (WpProxyClass *) klass; + + object_class->finalize = wp_global_proxy_finalize; + object_class->dispose = wp_global_proxy_dispose; + object_class->set_property = wp_global_proxy_set_property; + object_class->get_property = wp_global_proxy_get_property; + + wpobject_class->get_supported_features = + wp_global_proxy_get_supported_features; + wpobject_class->activate_get_next_step = + wp_global_proxy_activate_get_next_step; + wpobject_class->activate_execute_step = + wp_global_proxy_activate_execute_step; + + proxy_class->bound = wp_global_proxy_bound; + + g_object_class_install_property (object_class, PROP_GLOBAL, + g_param_spec_boxed ("global", "global", "Internal WpGlobal object", + wp_global_get_type (), + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_PERMISSIONS, + g_param_spec_uint ("permissions", "permissions", + "The pipewire global permissions", 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES, + g_param_spec_boxed ("global-properties", "global-properties", + "The pipewire global properties", WP_TYPE_PROPERTIES, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +/** + * wp_global_proxy_request_destroy: + * @self: the pipewire global + * + * Requests the PipeWire server to destroy the object represented by this proxy. + * If the server allows it, the object will be destroyed and the + * WpProxy::pw-proxy-destroyed signal will be emitted. If the server does + * not allow it, nothing will happen. + * + * This is mostly useful for destroying #WpLink and #WpEndpointLink objects. + */ +void +wp_global_proxy_request_destroy (WpGlobalProxy * self) +{ + g_return_if_fail (WP_IS_GLOBAL_PROXY (self)); + + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); + + if (priv->global && core) { + WpRegistry *reg = wp_core_get_registry (core); + pw_registry_destroy (reg->pw_registry, priv->global->id); + } +} + +/** + * wp_global_proxy_get_permissions: + * @self: the pipewire global + * + * Returns: the permissions that wireplumber has on this object + */ +guint32 +wp_global_proxy_get_permissions (WpGlobalProxy * self) +{ + g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), 0); + + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + + return priv->global ? priv->global->permissions : PW_PERM_ALL; +} + +/** + * wp_global_proxy_get_global_properties: + * @self: the pipewire global + * + * Returns: (transfer full): the global (immutable) properties of this + * pipewire object + */ +WpProperties * +wp_global_proxy_get_global_properties (WpGlobalProxy * self) +{ + g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), NULL); + + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + + if (priv->global && priv->global->properties) + return wp_properties_ref (priv->global->properties); + return NULL; +} + +/** + * wp_global_proxy_bind: + * @self: the pipewire global + * + * Binds to the global and creates the underlying `pw_proxy`. This may only + * be called if there is no `pw_proxy` associated with this object yet. + * + * This is mostly meant to be called internally. It will create the `pw_proxy` + * and will activate the %WP_PROXY_FEATURE_BOUND feature. + * + * Returns: %TRUE on success, %FALSE if there is no global to bind to + */ +gboolean +wp_global_proxy_bind (WpGlobalProxy * self) +{ + g_return_val_if_fail (WP_IS_GLOBAL_PROXY (self), FALSE); + g_return_val_if_fail (wp_proxy_get_pw_proxy (WP_PROXY (self)) == NULL, FALSE); + + WpGlobalProxyPrivate *priv = + wp_global_proxy_get_instance_private (self); + + if (priv->global) + wp_proxy_set_pw_proxy (WP_PROXY (self), wp_global_bind (priv->global)); + return !!priv->global; +} diff --git a/lib/wp/global-proxy.h b/lib/wp/global-proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..2a674353c8854e65f2b91b48040ae3f9d4ec47ec --- /dev/null +++ b/lib/wp/global-proxy.h @@ -0,0 +1,47 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_GLOBAL_PROXY_H__ +#define __WIREPLUMBER_GLOBAL_PROXY_H__ + +#include "proxy.h" +#include "properties.h" + +G_BEGIN_DECLS + +/** + * WP_TYPE_GLOBAL_PROXY: + * + * The #WpGlobalProxy #GType + */ +#define WP_TYPE_GLOBAL_PROXY (wp_global_proxy_get_type ()) +WP_API +G_DECLARE_DERIVABLE_TYPE (WpGlobalProxy, wp_global_proxy, + WP, GLOBAL_PROXY, WpObject) + +struct _WpGlobalProxyClass +{ + WpProxyClass parent_class; +}; + +WP_API +void wp_global_proxy_request_destroy (WpGlobalProxy * self); + +WP_API +guint32 wp_global_proxy_get_permissions (WpGlobalProxy * self); + +WP_API +WpProperties * wp_global_proxy_get_global_properties ( + WpGlobalProxy * self); + +WP_API +gboolean wp_global_proxy_bind (WpGlobalProxy * self); + +G_END_DECLS + +#endif diff --git a/lib/wp/link.c b/lib/wp/link.c index 157cb155cc985e0318e45d4f52d2f6e9b2fdb288..0a030b1125364df33bfa89d0db9c27958f644c8b 100644 --- a/lib/wp/link.c +++ b/lib/wp/link.c @@ -1,67 +1,70 @@ /* WirePlumber * - * Copyright © 2019 Collabora Ltd. + * Copyright © 2019-2020 Collabora Ltd. * @author Julian Bouzas <julian.bouzas@collabora.com> * * SPDX-License-Identifier: MIT */ /** - * SECTION: WpLink - * - * The #WpLink class allows accessing the properties and methods of a - * PipeWire link object (`struct pw_link`). - * - * A #WpLink is constructed internally when a new link appears on the - * PipeWire registry and it is made available through the #WpObjectManager API. - * Alternatively, a #WpLink can also be constructed using - * wp_link_new_from_factory(), which creates a new link object - * on the remote PipeWire server by calling into a factory. + * SECTION: link + * @title: PipeWire Link */ #define G_LOG_DOMAIN "wp-link" #include "link.h" -#include "private.h" - -#include <pipewire/pipewire.h> +#include "private/pipewire-object-mixin.h" struct _WpLink { - WpProxy parent; + WpGlobalProxy parent; struct pw_link_info *info; - - /* The link proxy listener */ struct spa_hook listener; }; -G_DEFINE_TYPE (WpLink, wp_link, WP_TYPE_PROXY) +static void wp_link_pipewire_object_interface_init (WpPipewireObjectInterface * iface); + +/** + * WpLink: + * + * The #WpLink class allows accessing the properties and methods of a + * PipeWire link object (`struct pw_link`). + * + * A #WpLink is constructed internally when a new link appears on the + * PipeWire registry and it is made available through the #WpObjectManager API. + * Alternatively, a #WpLink can also be constructed using + * wp_link_new_from_factory(), which creates a new link object + * 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)); static void wp_link_init (WpLink * self) { } -static void -wp_link_finalize (GObject * object) +static WpObjectFeatures +wp_link_get_supported_features (WpObject * object) { - WpLink *self = WP_LINK (object); - - g_clear_pointer (&self->info, pw_link_info_free); - - G_OBJECT_CLASS (wp_link_parent_class)->finalize (object); + return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO; } -static gconstpointer -wp_link_get_info (WpProxy * self) -{ - return WP_LINK (self)->info; -} - -static WpProperties * -wp_link_get_properties (WpProxy * self) +static void +wp_link_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - return wp_properties_new_wrap_dict (WP_LINK (self)->info->props); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + /* just wait, info will be emitted anyway after binding */ + break; + default: + WP_OBJECT_CLASS (wp_link_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } } static void @@ -70,12 +73,11 @@ 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_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - - g_object_notify (G_OBJECT (self), "info"); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - if (info->change_mask & PW_LINK_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); + wp_pipewire_object_mixin_handle_event_info (self, info, + PW_LINK_CHANGE_MASK_PROPS, 0); } static const struct pw_link_events link_events = { @@ -91,21 +93,64 @@ wp_link_pw_proxy_created (WpProxy * proxy, struct pw_proxy * 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); +} + static void wp_link_class_init (WpLinkClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_link_finalize; + object_class->get_property = wp_pipewire_object_mixin_get_property; + + wpobject_class->get_supported_features = wp_link_get_supported_features; + wpobject_class->activate_get_next_step = + wp_pipewire_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; - proxy_class->get_info = wp_link_get_info; - proxy_class->get_properties = wp_link_get_properties; + wp_pipewire_object_mixin_class_override_properties (object_class); +} - proxy_class->pw_proxy_created = wp_link_pw_proxy_created; +static gconstpointer +wp_link_get_native_info (WpPipewireObject * obj) +{ + return WP_LINK (obj)->info; +} + +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; +} + +static void +wp_link_pipewire_object_interface_init (WpPipewireObjectInterface * 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; } /** @@ -119,9 +164,9 @@ wp_link_class_init (WpLinkClass * klass) * * Because of the nature of the PipeWire protocol, this operation completes * asynchronously at some point in the future. In order to find out when - * this is done, you should call wp_proxy_augment(), requesting at least + * this is done, you should call wp_object_activate(), requesting at least * %WP_PROXY_FEATURE_BOUND. When this feature is ready, the link is ready for - * use on the server. If the link cannot be created, this augment operation + * use on the server. If the link cannot be created, this activation operation * will fail. * * Returns: (nullable) (transfer full): the new link or %NULL if the core diff --git a/lib/wp/link.h b/lib/wp/link.h index a9b294d09b490b0cf7062c681d47338e40a7a9ba..f0fba7cbcb2caf81251d3f0dbaf331631c17b1c1 100644 --- a/lib/wp/link.h +++ b/lib/wp/link.h @@ -9,7 +9,7 @@ #ifndef __WIREPLUMBER_LINK_H__ #define __WIREPLUMBER_LINK_H__ -#include "proxy.h" +#include "global-proxy.h" G_BEGIN_DECLS @@ -20,7 +20,7 @@ G_BEGIN_DECLS */ #define WP_TYPE_LINK (wp_link_get_type ()) WP_API -G_DECLARE_FINAL_TYPE (WpLink, wp_link, WP, LINK, WpProxy) +G_DECLARE_FINAL_TYPE (WpLink, wp_link, WP, LINK, WpGlobalProxy) WP_API WpLink * wp_link_new_from_factory (WpCore * core, diff --git a/lib/wp/meson.build b/lib/wp/meson.build index 3fa8be4c354febd3b17a3b9ea931b903fc025f9b..d8292a082077a4ec7af912ea81394edc2f894cba 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -1,4 +1,5 @@ wp_lib_sources = files( + 'private/pipewire-object-mixin.c', 'client.c', 'configuration.c', 'core.c', @@ -8,6 +9,7 @@ wp_lib_sources = files( 'endpoint-link.c', 'endpoint-stream.c', 'error.c', + 'global-proxy.c', 'iterator.c', 'link.c', 'metadata.c', @@ -19,7 +21,7 @@ wp_lib_sources = files( 'plugin.c', 'port.c', 'properties.c', - 'props.c', + #'props.c', 'proxy.c', 'proxy-interfaces.c', 'session.c', @@ -44,6 +46,7 @@ wp_lib_headers = files( 'endpoint-link.h', 'endpoint-stream.h', 'error.h', + 'global-proxy.h', 'iterator.h', 'link.h', 'metadata.h', @@ -55,7 +58,7 @@ wp_lib_headers = files( 'plugin.h', 'port.h', 'properties.h', - 'props.h', + #'props.h', 'proxy.h', 'proxy-interfaces.h', 'session.h', diff --git a/lib/wp/metadata.c b/lib/wp/metadata.c index 4729cbca3c3dc1c47dcd50584ff8235fb35a7593..c578c8c3489c9c397095097ed97739f16290dc04 100644 --- a/lib/wp/metadata.c +++ b/lib/wp/metadata.c @@ -7,20 +7,18 @@ */ /** - * SECTION: WpMetadata - * - * The #WpMetadata class allows accessing the properties and methods of - * Pipewire metadata object (`struct pw_metadata`). - * + * SECTION: metadata + * @title: PipeWire Metadata */ #define G_LOG_DOMAIN "wp-metadata" #include "metadata.h" +#include "core.h" #include "debug.h" -#include "private.h" #include "error.h" #include "wpenums.h" +#include "private.h" #include <pipewire/pipewire.h> #include <pipewire/extensions/metadata.h> @@ -114,7 +112,13 @@ struct _WpMetadataPrivate struct pw_array metadata; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpMetadata, wp_metadata, WP_TYPE_PROXY) +/** + * WpMetadata: + * + * The #WpMetadata class allows accessing the properties and methods of + * PipeWire metadata object (`struct pw_metadata`). + */ +G_DEFINE_TYPE_WITH_PRIVATE (WpMetadata, wp_metadata, WP_TYPE_GLOBAL_PROXY) static void wp_metadata_init (WpMetadata * self) @@ -129,12 +133,54 @@ wp_metadata_finalize (GObject * object) WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (object)); - clear_items (&priv->metadata); pw_array_clear (&priv->metadata); G_OBJECT_CLASS (wp_metadata_parent_class)->finalize (object); } +static WpObjectFeatures +wp_metadata_get_supported_features (WpObject * object) +{ + return WP_PROXY_FEATURE_BOUND | WP_METADATA_FEATURE_DATA; +} + +enum { + STEP_BIND = WP_TRANSITION_STEP_CUSTOM_START, + STEP_CACHE +}; + +static guint +wp_metadata_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) +{ + g_return_val_if_fail ( + missing & (WP_PROXY_FEATURE_BOUND | WP_METADATA_FEATURE_DATA), + WP_TRANSITION_STEP_ERROR); + + /* bind if not already bound */ + if (missing & WP_PROXY_FEATURE_BOUND) + return STEP_BIND; + else + return STEP_CACHE; +} + +static void +wp_metadata_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) +{ + switch (step) { + case STEP_CACHE: + /* just wait for initial_sync_done() */ + break; + default: + WP_OBJECT_CLASS (wp_metadata_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } +} + static int metadata_event_property (void *object, uint32_t subject, const char *key, const char *type, const char *value) @@ -194,7 +240,7 @@ initial_sync_done (WpCore * core, GAsyncResult * res, WpMetadata * self) return; } - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + wp_object_update_features (WP_OBJECT (self), WP_METADATA_FEATURE_DATA, 0); } static void @@ -202,7 +248,7 @@ wp_metadata_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { WpMetadata *self = WP_METADATA (proxy); WpMetadataPrivate *priv = wp_metadata_get_instance_private (self); - g_autoptr (WpCore) core = wp_proxy_get_core (proxy); + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); priv->iface = (struct pw_metadata *) pw_proxy; pw_metadata_add_listener (priv->iface, &priv->listener, @@ -210,18 +256,33 @@ wp_metadata_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) wp_core_sync (core, NULL, (GAsyncReadyCallback) initial_sync_done, self); } +static void +wp_metadata_pw_proxy_destroyed (WpProxy * proxy) +{ + WpMetadata *self = WP_METADATA (proxy); + WpMetadataPrivate *priv = wp_metadata_get_instance_private (self); + + clear_items (&priv->metadata); + wp_object_update_features (WP_OBJECT (self), 0, WP_METADATA_FEATURE_DATA); +} + static void wp_metadata_class_init (WpMetadataClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; object_class->finalize = wp_metadata_finalize; + wpobject_class->get_supported_features = wp_metadata_get_supported_features; + wpobject_class->activate_get_next_step = wp_metadata_activate_get_next_step; + wpobject_class->activate_execute_step = wp_metadata_activate_execute_step; + proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Metadata; proxy_class->pw_iface_version = PW_VERSION_METADATA; - proxy_class->pw_proxy_created = wp_metadata_pw_proxy_created; + proxy_class->pw_proxy_destroyed = wp_metadata_pw_proxy_destroyed; signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 4, @@ -421,6 +482,13 @@ struct _WpImplMetadata struct spa_hook_list hooks; }; +/** + * WpImplMetadata: + * + * The #WpImplMetadata class implements a PipeWire metadata object. It can + * be exported and made available by requesting the %WP_PROXY_FEATURE_BOUND + * feature. + */ G_DEFINE_TYPE (WpImplMetadata, wp_impl_metadata, WP_TYPE_METADATA) #define pw_metadata_emit(hooks,method,version,...) \ @@ -500,7 +568,19 @@ wp_impl_metadata_init (WpImplMetadata * self) spa_hook_list_init (&self->hooks); priv->iface = (struct pw_metadata *) &self->iface; - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + wp_object_update_features (WP_OBJECT (self), WP_METADATA_FEATURE_DATA, 0); +} + +static void +wp_impl_metadata_dispose (GObject * object) +{ + WpMetadataPrivate *priv = + wp_metadata_get_instance_private (WP_METADATA (object)); + + clear_items (&priv->metadata); + wp_object_update_features (WP_OBJECT (object), 0, WP_METADATA_FEATURE_DATA); + + G_OBJECT_CLASS (wp_impl_metadata_parent_class)->dispose (object); } static void @@ -513,44 +593,68 @@ wp_impl_metadata_on_changed (WpImplMetadata * self, guint32 subject, } static void -wp_impl_metadata_augment (WpProxy * proxy, WpProxyFeatures features) +wp_impl_metadata_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpImplMetadata *self = WP_IMPL_METADATA (proxy); + WpImplMetadata *self = WP_IMPL_METADATA (object); WpMetadataPrivate *priv = wp_metadata_get_instance_private (WP_METADATA (self)); - /* PW_PROXY depends on BOUND */ - if (features & WP_PROXY_FEATURE_PW_PROXY) - features |= WP_PROXY_FEATURE_BOUND; - - if (features & WP_PROXY_FEATURE_BOUND) { - g_autoptr (WpCore) core = wp_proxy_get_core (proxy); + switch (step) { + case STEP_BIND: { + g_autoptr (WpCore) core = wp_object_get_core (object); struct pw_core *pw_core = wp_core_get_pw_core (core); /* no pw_core -> we are not connected */ if (!pw_core) { - wp_proxy_augment_error (proxy, g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, + wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "The WirePlumber core is not connected; " "object cannot be exported to PipeWire")); return; } - wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core, + wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_export (pw_core, PW_TYPE_INTERFACE_Metadata, NULL, priv->iface, 0)); g_signal_connect (self, "changed", (GCallback) wp_impl_metadata_on_changed, NULL); + break; + } + case STEP_CACHE: + /* never reached because WP_METADATA_FEATURE_DATA is always enabled */ + g_assert_not_reached (); + break; + default: + WP_OBJECT_CLASS (wp_impl_metadata_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; } } +static void +wp_impl_metadata_pw_proxy_destroyed (WpProxy * proxy) +{ + g_signal_handlers_disconnect_by_func (proxy, + (GCallback) wp_impl_metadata_on_changed, NULL); +} + static void wp_impl_metadata_class_init (WpImplMetadataClass * klass) { + GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - proxy_class->augment = wp_impl_metadata_augment; + object_class->dispose = wp_impl_metadata_dispose; + + wpobject_class->activate_execute_step = + wp_impl_metadata_activate_execute_step; + + /* disable adding a listener for events */ proxy_class->pw_proxy_created = NULL; + proxy_class->pw_proxy_destroyed = wp_impl_metadata_pw_proxy_destroyed; } WpImplMetadata * diff --git a/lib/wp/metadata.h b/lib/wp/metadata.h index c501339d526e75a964660fb10561be79dfb8fefc..5008eebd6ed7c3c17e5a07575c153608310da522 100644 --- a/lib/wp/metadata.h +++ b/lib/wp/metadata.h @@ -9,10 +9,20 @@ #ifndef __WIREPLUMBER_METADATA_H__ #define __WIREPLUMBER_METADATA_H__ -#include "proxy.h" +#include "global-proxy.h" G_BEGIN_DECLS +/** + * WpMetadataFeatures: + * @WP_METADATA_FEATURE_DATA: caches metadata locally + * + * An extension of #WpProxyFeatures + */ +typedef enum { /*< flags >*/ + WP_METADATA_FEATURE_DATA = (WP_PROXY_FEATURE_CUSTOM_START << 0), +} WpMetadataFeatures; + /** * WP_TYPE_METADATA: * @@ -21,11 +31,11 @@ G_BEGIN_DECLS #define WP_TYPE_METADATA (wp_metadata_get_type ()) WP_API -G_DECLARE_DERIVABLE_TYPE (WpMetadata, wp_metadata, WP, METADATA, WpProxy) +G_DECLARE_DERIVABLE_TYPE (WpMetadata, wp_metadata, WP, METADATA, WpGlobalProxy) struct _WpMetadataClass { - WpProxyClass parent_class; + WpGlobalProxyClass parent_class; }; WP_API diff --git a/lib/wp/node.c b/lib/wp/node.c index 4196cbe3613bdb874983a0638109edf58019365f..fbd850537c67fd13759ac8aa6731e97290e22213 100644 --- a/lib/wp/node.c +++ b/lib/wp/node.c @@ -7,33 +7,18 @@ */ /** - * SECTION: WpNode - * - * The #WpNode class allows accessing the properties and methods of a - * PipeWire node object (`struct pw_node`). - * - * A #WpNode is constructed internally when a new node appears on the - * PipeWire registry and it is made available through the #WpObjectManager API. - * Alternatively, a #WpNode can also be constructed using - * wp_node_new_from_factory(), which creates a new node object - * on the remote PipeWire server by calling into a factory. - * - * A #WpImplNode allows running a node implementation (`struct pw_impl_node`) - * locally, loading the implementation from factory or wrapping a manually - * constructed `pw_impl_node`. This object can then be exported to PipeWire - * by requesting %WP_PROXY_FEATURE_BOUND and be used as if it was a #WpNode - * proxy to a remote object. + * SECTION: node + * @title: PipeWire Node */ #define G_LOG_DOMAIN "wp-node" #include "node.h" -#include "debug.h" -#include "error.h" -#include "private.h" +#include "core.h" +#include "object-manager.h" #include "wpenums.h" +#include "private/pipewire-object-mixin.h" -#include <pipewire/pipewire.h> #include <pipewire/impl.h> enum { @@ -44,38 +29,40 @@ enum { static guint32 signals[N_SIGNALS] = {0}; -typedef struct _WpNodePrivate WpNodePrivate; -struct _WpNodePrivate +struct _WpNode { + WpGlobalProxy parent; struct pw_node_info *info; struct spa_hook listener; WpObjectManager *ports_om; - gboolean ft_ports_requested; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpNode, wp_node, WP_TYPE_PROXY) +static void wp_node_pipewire_object_interface_init (WpPipewireObjectInterface * iface); -static void -wp_node_init (WpNode * self) -{ -} +/** + * WpNode: + * + * The #WpNode class allows accessing the properties and methods of a + * PipeWire node object (`struct pw_node`). + * + * A #WpNode is constructed internally when a new node appears on the + * PipeWire registry and it is made available through the #WpObjectManager API. + * Alternatively, a #WpNode can also be constructed using + * wp_node_new_from_factory(), which creates a new node object + * 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)); static void -wp_node_finalize (GObject * object) +wp_node_init (WpNode * self) { - WpNode *self = WP_NODE (object); - WpNodePrivate *priv = wp_node_get_instance_private (self); - - g_clear_pointer (&priv->info, pw_node_info_free); - g_clear_object (&priv->ports_om); - - G_OBJECT_CLASS (wp_node_parent_class)->finalize (object); } static void wp_node_on_ports_om_installed (WpObjectManager *ports_om, WpNode * self) { - wp_proxy_set_feature_ready (WP_PROXY (self), WP_NODE_FEATURE_PORTS); + wp_object_update_features (WP_OBJECT (self), WP_NODE_FEATURE_PORTS, 0); } static void @@ -85,169 +72,165 @@ wp_node_emit_ports_changed (WpObjectManager *ports_om, WpNode * self) } static void -wp_node_ensure_feature_ports (WpNode * self, guint32 bound_id) +wp_node_enable_feature_ports (WpNode * self) { - WpNodePrivate *priv = wp_node_get_instance_private (self); - WpProxyFeatures ft = wp_proxy_get_features (WP_PROXY (self)); + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); + guint32 bound_id = wp_proxy_get_bound_id (WP_PROXY (self)); - if (priv->ft_ports_requested && !priv->ports_om && - (ft & WP_PROXY_FEATURES_STANDARD) == WP_PROXY_FEATURES_STANDARD) - { - g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (self)); + wp_debug_object (self, "enabling WP_NODE_FEATURE_PORTS, bound_id:%u", + bound_id); - if (!bound_id) - bound_id = wp_proxy_get_bound_id (WP_PROXY (self)); - - wp_debug_object (self, "enabling WP_NODE_FEATURE_PORTS, bound_id:%u", - bound_id); - - priv->ports_om = wp_object_manager_new (); - wp_object_manager_add_interest (priv->ports_om, - WP_TYPE_PORT, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, PW_KEY_NODE_ID, "=u", bound_id, - NULL); - wp_object_manager_request_proxy_features (priv->ports_om, - WP_TYPE_PORT, WP_PROXY_FEATURES_STANDARD); + self->ports_om = wp_object_manager_new (); + wp_object_manager_add_interest (self->ports_om, + WP_TYPE_PORT, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, PW_KEY_NODE_ID, "=u", bound_id, + NULL); + wp_object_manager_request_object_features (self->ports_om, + WP_TYPE_PORT, WP_OBJECT_FEATURES_ALL); - g_signal_connect_object (priv->ports_om, "installed", - G_CALLBACK (wp_node_on_ports_om_installed), self, 0); - g_signal_connect_object (priv->ports_om, "objects-changed", - G_CALLBACK (wp_node_emit_ports_changed), self, 0); + g_signal_connect_object (self->ports_om, "installed", + G_CALLBACK (wp_node_on_ports_om_installed), self, 0); + g_signal_connect_object (self->ports_om, "objects-changed", + G_CALLBACK (wp_node_emit_ports_changed), self, 0); - wp_core_install_object_manager (core, priv->ports_om); - } + wp_core_install_object_manager (core, self->ports_om); } -static void -wp_node_augment (WpProxy * proxy, WpProxyFeatures features) +static WpObjectFeatures +wp_node_get_supported_features (WpObject * object) { - WpNode *self = WP_NODE (proxy); - WpNodePrivate *priv = wp_node_get_instance_private (self); - - /* call the parent impl first to ensure we have a pw proxy if necessary */ - WP_PROXY_CLASS (wp_node_parent_class)->augment (proxy, features); - - if (features & WP_NODE_FEATURE_PORTS) { - priv->ft_ports_requested = TRUE; - wp_node_ensure_feature_ports (self, 0); - } + 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); } -static gconstpointer -wp_node_get_info (WpProxy * self) -{ - WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (self)); - return priv->info; -} +enum { + STEP_PORTS = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, +}; -static WpProperties * -wp_node_get_properties (WpProxy * self) +static guint +wp_node_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (self)); - return wp_properties_new_wrap_dict (priv->info->props); -} + step = wp_pipewire_object_mixin_activate_get_next_step (object, transition, + step, missing); -static struct spa_param_info * -wp_node_get_param_info (WpProxy * proxy, guint * n_params) -{ - WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (proxy)); + /* 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; - *n_params = priv->info->n_params; - return priv->info->params; + return step; } -static gint -wp_node_enum_params (WpProxy * self, guint32 id, guint32 start, guint32 num, - WpSpaPod * filter) +static void +wp_node_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - struct pw_node *pwp = (struct pw_node *) wp_proxy_get_pw_proxy (self); - return pw_node_enum_params (pwp, 0, id, start, num, - filter ? wp_spa_pod_get_spa_pod (filter) : NULL); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + wp_pipewire_object_mixin_cache_info (object, transition); + 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; + } } -static gint -wp_node_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids) +static void +wp_node_deactivate (WpObject * object, WpObjectFeatures features) { - struct pw_node *pwp = (struct pw_node *) wp_proxy_get_pw_proxy (self); - return pw_node_subscribe_params (pwp, ids, n_ids); -} + WpNode *self = WP_NODE (object); -static gint -wp_node_set_param (WpProxy * self, guint32 id, guint32 flags, WpSpaPod *param) -{ - struct pw_node *pwp = (struct pw_node *) wp_proxy_get_pw_proxy (self); - return pw_node_set_param (pwp, id, flags, wp_spa_pod_get_spa_pod (param)); + wp_pipewire_object_mixin_deactivate (object, features); + + if (features & WP_NODE_FEATURE_PORTS) { + g_clear_object (&self->ports_om); + wp_object_update_features (object, 0, WP_NODE_FEATURE_PORTS); + } + + 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); - WpNodePrivate *priv = wp_node_get_instance_private (self); - enum pw_node_state old_state = priv->info ? - priv->info->state : PW_NODE_STATE_CREATING; - - priv->info = pw_node_info_update (priv->info, info); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + enum pw_node_state old_state = self->info ? + self->info->state : PW_NODE_STATE_CREATING; - g_object_notify (G_OBJECT (self), "info"); - - if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); - - if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) - g_object_notify (G_OBJECT (self), "param-info"); + 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, - priv->info->state); + self->info->state); - wp_node_ensure_feature_ports (self, 0); + 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_proxy_handle_event_param, + .param = wp_pipewire_object_mixin_handle_event_param, }; static void wp_node_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { WpNode *self = WP_NODE (proxy); - WpNodePrivate *priv = wp_node_get_instance_private (self); - pw_node_add_listener ((struct pw_node *) pw_proxy, - &priv->listener, &node_events, self); + pw_node_add_listener ((struct pw_port *) pw_proxy, + &self->listener, &node_events, self); } static void -wp_node_bound (WpProxy * proxy, guint32 id) +wp_node_pw_proxy_destroyed (WpProxy * proxy) { WpNode *self = WP_NODE (proxy); - wp_node_ensure_feature_ports (self, id); + + 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_pipewire_object_mixin_deactivate (WP_OBJECT (self), + WP_OBJECT_FEATURES_ALL); } static void wp_node_class_init (WpNodeClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_node_finalize; + object_class->get_property = wp_pipewire_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_execute_step = wp_node_activate_execute_step; + wpobject_class->deactivate = wp_node_deactivate; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Node; proxy_class->pw_iface_version = PW_VERSION_NODE; - - proxy_class->augment = wp_node_augment; - proxy_class->get_info = wp_node_get_info; - proxy_class->get_properties = wp_node_get_properties; - proxy_class->get_param_info = wp_node_get_param_info; - proxy_class->enum_params = wp_node_enum_params; - proxy_class->subscribe_params = wp_node_subscribe_params; - proxy_class->set_param = wp_node_set_param; - proxy_class->pw_proxy_created = wp_node_pw_proxy_created; - proxy_class->bound = wp_node_bound; + proxy_class->pw_proxy_destroyed = wp_node_pw_proxy_destroyed; + + wp_pipewire_object_mixin_class_override_properties (object_class); /** * WpNode::state-changed: @@ -275,6 +258,53 @@ 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) +{ + return wp_properties_new_wrap_dict (WP_NODE (obj)->info->props); +} + +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); +} + +static void +wp_node_enum_params (WpPipewireObject * obj, const gchar * id, + WpSpaPod *filter, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + wp_pipewire_object_mixin_enum_params (pw_node, obj, id, filter, cancellable, + callback, user_data); +} + +static void +wp_node_set_param (WpPipewireObject * obj, const gchar * id, WpSpaPod * param) +{ + wp_pipewire_object_mixin_set_param (pw_node, obj, id, param); +} + +static void +wp_node_pipewire_object_interface_init (WpPipewireObjectInterface * 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; + 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; +} + /** * wp_node_new_from_factory: * @core: the wireplumber core @@ -286,9 +316,9 @@ wp_node_class_init (WpNodeClass * klass) * * Because of the nature of the PipeWire protocol, this operation completes * asynchronously at some point in the future. In order to find out when - * this is done, you should call wp_proxy_augment(), requesting at least + * this is done, you should call wp_object_activate(), requesting at least * %WP_PROXY_FEATURE_BOUND. When this feature is ready, the node is ready for - * use on the server. If the node cannot be created, this augment operation + * use on the server. If the node cannot be created, this activation operation * will fail. * * Returns: (nullable) (transfer full): the new node or %NULL if the core @@ -318,13 +348,12 @@ WpNodeState wp_node_get_state (WpNode * self, const gchar ** error) { g_return_val_if_fail (WP_IS_NODE (self), WP_NODE_STATE_ERROR); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, WP_NODE_STATE_ERROR); + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & + WP_PIPEWIRE_OBJECT_FEATURE_INFO, WP_NODE_STATE_ERROR); - WpNodePrivate *priv = wp_node_get_instance_private (self); if (error) - *error = priv->info->error; - return (WpNodeState) priv->info->state; + *error = self->info->error; + return (WpNodeState) self->info->state; } /** @@ -332,7 +361,7 @@ wp_node_get_state (WpNode * self, const gchar ** error) * @self: the node * @max: (out) (optional): the maximum supported number of input ports * - * Requires %WP_PROXY_FEATURE_INFO + * Requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO * * Returns: the number of input ports of this node, as reported by the node info */ @@ -340,13 +369,12 @@ guint wp_node_get_n_input_ports (WpNode * self, guint * max) { g_return_val_if_fail (WP_IS_NODE (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, 0); + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - WpNodePrivate *priv = wp_node_get_instance_private (self); if (max) - *max = priv->info->max_input_ports; - return priv->info->n_input_ports; + *max = self->info->max_input_ports; + return self->info->n_input_ports; } /** @@ -354,7 +382,7 @@ wp_node_get_n_input_ports (WpNode * self, guint * max) * @self: the node * @max: (out) (optional): the maximum supported number of output ports * - * Requires %WP_PROXY_FEATURE_INFO + * Requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO * * Returns: the number of output ports of this node, as reported by the node info */ @@ -362,13 +390,12 @@ guint wp_node_get_n_output_ports (WpNode * self, guint * max) { g_return_val_if_fail (WP_IS_NODE (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, 0); + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - WpNodePrivate *priv = wp_node_get_instance_private (self); if (max) - *max = priv->info->max_output_ports; - return priv->info->n_output_ports; + *max = self->info->max_output_ports; + return self->info->n_output_ports; } /** @@ -387,11 +414,10 @@ guint wp_node_get_n_ports (WpNode * self) { g_return_val_if_fail (WP_IS_NODE (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_NODE_FEATURE_PORTS, 0); - WpNodePrivate *priv = wp_node_get_instance_private (self); - return wp_object_manager_get_n_objects (priv->ports_om); + return wp_object_manager_get_n_objects (self->ports_om); } /** @@ -407,11 +433,10 @@ WpIterator * wp_node_iterate_ports (WpNode * self) { g_return_val_if_fail (WP_IS_NODE (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_NODE_FEATURE_PORTS, NULL); - WpNodePrivate *priv = wp_node_get_instance_private (self); - return wp_object_manager_iterate (priv->ports_om); + return wp_object_manager_iterate (self->ports_om); } /** @@ -452,11 +477,10 @@ WpIterator * wp_node_iterate_ports_filtered_full (WpNode * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_NODE (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_NODE_FEATURE_PORTS, NULL); - WpNodePrivate *priv = wp_node_get_instance_private (self); - return wp_object_manager_iterate_filtered_full (priv->ports_om, interest); + return wp_object_manager_iterate_filtered_full (self->ports_om, interest); } /** @@ -497,12 +521,11 @@ WpPort * wp_node_lookup_port_full (WpNode * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_NODE (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_NODE_FEATURE_PORTS, NULL); - WpNodePrivate *priv = wp_node_get_instance_private (self); return (WpPort *) - wp_object_manager_lookup_full (priv->ports_om, interest); + wp_object_manager_lookup_full (self->ports_om, interest); } /** @@ -539,6 +562,15 @@ struct _WpImplNode struct pw_proxy *proxy; }; +/** + * WpImplNode: + * + * A #WpImplNode allows running a node implementation (`struct pw_impl_node`) + * locally, loading the implementation from factory or wrapping a manually + * constructed `pw_impl_node`. This object can then be exported to PipeWire + * by requesting %WP_PROXY_FEATURE_BOUND and be used as if it was a #WpNode + * proxy to a remote object. + */ G_DEFINE_TYPE (WpImplNode, wp_impl_node, G_TYPE_OBJECT) static void diff --git a/lib/wp/node.h b/lib/wp/node.h index 2b6ae2e22012f07d232c991606bfc6a8729b3584..55d2dcb54cb493d93b81e8e5d9a7169210805be3 100644 --- a/lib/wp/node.h +++ b/lib/wp/node.h @@ -9,7 +9,7 @@ #ifndef __WIREPLUMBER_NODE_H__ #define __WIREPLUMBER_NODE_H__ -#include "proxy.h" +#include "global-proxy.h" #include "port.h" #include "iterator.h" #include "object-interest.h" @@ -59,19 +59,9 @@ typedef enum { * An extension of #WpProxyFeatures */ typedef enum { /*< flags >*/ - WP_NODE_FEATURE_PORTS = (WP_PROXY_FEATURE_LAST << 0), + WP_NODE_FEATURE_PORTS = (WP_PROXY_FEATURE_CUSTOM_START << 0), } WpNodeFeatures; -/** - * WP_NODE_FEATURES_STANDARD: - * - * A constant set of features that contains the standard features that are - * available in the #WpNode class. - */ -#define WP_NODE_FEATURES_STANDARD \ - (WP_PROXY_FEATURES_STANDARD | \ - WP_NODE_FEATURE_PORTS) - /** * WP_TYPE_NODE: * @@ -79,12 +69,7 @@ typedef enum { /*< flags >*/ */ #define WP_TYPE_NODE (wp_node_get_type ()) WP_API -G_DECLARE_DERIVABLE_TYPE (WpNode, wp_node, WP, NODE, WpProxy) - -struct _WpNodeClass -{ - WpProxyClass parent_class; -}; +G_DECLARE_FINAL_TYPE (WpNode, wp_node, WP, NODE, WpGlobalProxy) WP_API WpNode * wp_node_new_from_factory (WpCore * core, diff --git a/lib/wp/object-interest.c b/lib/wp/object-interest.c index 72694ae39d66592f0d5729ffe3875cda40ed1296..b9f7a70ac8792b34d3fb8afa4586deeb4893e96c 100644 --- a/lib/wp/object-interest.c +++ b/lib/wp/object-interest.c @@ -14,6 +14,8 @@ #define G_LOG_DOMAIN "wp-object-interest" #include "object-interest.h" +#include "global-proxy.h" +#include "proxy-interfaces.h" #include "debug.h" #include "error.h" #include "private.h" @@ -308,7 +310,8 @@ gboolean wp_object_interest_validate (WpObjectInterest * self, GError ** error) { struct constraint *c; - gboolean is_proxy; + gboolean is_pwobj; + gboolean is_global; g_return_val_if_fail (self != NULL, FALSE); @@ -322,7 +325,8 @@ wp_object_interest_validate (WpObjectInterest * self, GError ** error) return FALSE; } - is_proxy = g_type_is_a (self->gtype, WP_TYPE_PROXY); + is_pwobj = g_type_is_a (self->gtype, WP_TYPE_PIPEWIRE_OBJECT); + is_global = g_type_is_a (self->gtype, WP_TYPE_GLOBAL_PROXY); pw_array_for_each (c, &self->constraints) { const GVariantType *value_type = NULL; @@ -334,10 +338,16 @@ wp_object_interest_validate (WpObjectInterest * self, GError ** error) return FALSE; } - if (!is_proxy && (c->type == WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY || - c->type == WP_CONSTRAINT_TYPE_PW_PROPERTY)) { + if (!is_pwobj && c->type == WP_CONSTRAINT_TYPE_PW_PROPERTY) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, - "constraint type %d cannot apply to non-WpProxy type '%s'", + "constraint type %d cannot apply to non-WpPipewireObject type '%s'", + c->type, g_type_name (self->gtype)); + return FALSE; + } + + if (!is_global && c->type == WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "constraint type %d cannot apply to non-WpGlobalProxy type '%s'", c->type, g_type_name (self->gtype)); return FALSE; } @@ -698,8 +708,8 @@ wp_object_interest_matches (WpObjectInterest * self, gpointer object) * are any constraints that require them, the match will fail. * As a special case, if @object is not %NULL and is a subclass of #WpProxy, * then @pw_props and @pw_global_props, if required, will be internally - * retrieved from @object by calling wp_proxy_get_properties() and - * wp_proxy_get_global_properties() respectively. + * retrieved from @object by calling wp_pipewire_object_get_properties() and + * wp_global_proxy_get_global_properties() respectively. * * Returns: %TRUE if the the type matches this interest and the properties * match the constraints, %FALSE otherwise @@ -727,14 +737,20 @@ wp_object_interest_matches_full (WpObjectInterest * self, return FALSE; /* prepare for constraint lookups on proxy properties */ - if (object && g_type_is_a (object_type, WP_TYPE_PROXY)) { - WpProxy *p = WP_PROXY (object); + if (object) { + if (!pw_global_props && WP_IS_GLOBAL_PROXY (object)) { + WpGlobalProxy *pwg = (WpGlobalProxy *) object; + pw_global_props = global_props = + wp_global_proxy_get_global_properties (pwg); + } - if (!pw_global_props) - pw_global_props = global_props = wp_proxy_get_global_properties (p); + if (!pw_props && WP_IS_PIPEWIRE_OBJECT (object)) { + WpObject *oo = (WpObject *) object; + WpPipewireObject *pwo = (WpPipewireObject *) object; - if (!pw_props && wp_proxy_get_features (p) & WP_PROXY_FEATURE_INFO) - pw_props = props = wp_proxy_get_properties (p); + if (wp_object_get_active_features (oo) & WP_PIPEWIRE_OBJECT_FEATURE_INFO) + pw_props = props = wp_pipewire_object_get_properties (pwo); + } } /* check all constraints; if any of them fails at any point, fail the match */ diff --git a/lib/wp/object-interest.h b/lib/wp/object-interest.h index b52853c71f2f14884280c66d6d952d020f2232dc..983eeea79b95217924460ca7623229de9a979df6 100644 --- a/lib/wp/object-interest.h +++ b/lib/wp/object-interest.h @@ -20,10 +20,10 @@ G_BEGIN_DECLS * @WP_CONSTRAINT_TYPE_NONE: invalid constraint type * @WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY: constraint applies * to a PipeWire global property of the object (the ones returned by - * wp_proxy_get_global_properties()) + * wp_global_proxy_get_global_properties()) * @WP_CONSTRAINT_TYPE_PW_PROPERTY: constraint applies * to a PipeWire property of the object (the ones returned by - * wp_proxy_get_properties()) + * wp_pipewire_object_get_properties()) * @WP_CONSTRAINT_TYPE_G_PROPERTY: constraint applies to a #GObject * property of the object */ diff --git a/lib/wp/object-manager.c b/lib/wp/object-manager.c index 60289b5493df9fa5425302fd4512d51dfaede53d..13a8cede242c3d10165163fe1049afcad6cb7e2e 100644 --- a/lib/wp/object-manager.c +++ b/lib/wp/object-manager.c @@ -15,14 +15,14 @@ * * There are 4 kinds of objects that can be managed by a #WpObjectManager: * * remote PipeWire global objects that are advertised on the registry; - * these are bound locally to subclasses of #WpProxy + * these are bound locally to subclasses of #WpGlobalProxy * * remote PipeWire global objects that are created by calling a remote * factory through the WirePlumber API; these are very similar to other - * global objects but it should be noted that the same #WpProxy instance - * that created them appears in the #WpObjectManager (as soon as its - * %WP_PROXY_FEATURE_BOUND is enabled) + * global objects but it should be noted that the same #WpGlobalProxy + * instance that created them appears in the #WpObjectManager (as soon as + * its %WP_PROXY_FEATURE_BOUND is enabled) * * local PipeWire objects that are being exported to PipeWire - * (#WpImplNode, WpImplEndpoint [private], etc); these appear in the + * (#WpImplSession, WpImplEndpoint [private], etc); these appear in the * #WpObjectManager as soon as they are exported (so, when their * %WP_PROXY_FEATURE_BOUND is enabled) * * WirePlumber-specific objects, such as WirePlumber factories @@ -278,29 +278,29 @@ wp_object_manager_add_interest_full (WpObjectManager *self, } /** - * wp_object_manager_request_proxy_features: + * wp_object_manager_request_object_features: * @self: the object manager - * @proxy_type: the #WpProxy descendant type - * @wanted_features: the features to enable on this kind of proxy + * @object_type: the #WpProxy descendant type + * @wanted_features: the features to enable on this kind of object * * Requests the object manager to automatically prepare the @wanted_features - * on any managed object that is of the specified @proxy_type. These features + * on any managed object that is of the specified @object_type. These features * will always be prepared before the object appears on the object manager. */ void -wp_object_manager_request_proxy_features (WpObjectManager *self, - GType proxy_type, WpProxyFeatures wanted_features) +wp_object_manager_request_object_features (WpObjectManager *self, + GType object_type, WpProxyFeatures wanted_features) { g_autofree GType *children = NULL; GType *child; g_return_if_fail (WP_IS_OBJECT_MANAGER (self)); - g_return_if_fail (g_type_is_a (proxy_type, WP_TYPE_PROXY)); + g_return_if_fail (g_type_is_a (object_type, WP_TYPE_OBJECT)); - g_hash_table_insert (self->features, GSIZE_TO_POINTER (proxy_type), + g_hash_table_insert (self->features, GSIZE_TO_POINTER (object_type), GUINT_TO_POINTER (wanted_features)); - child = children = g_type_children (proxy_type, NULL); + child = children = g_type_children (object_type, NULL); while (*child) { WpProxyFeatures existing_ft = (WpProxyFeatures) GPOINTER_TO_UINT ( g_hash_table_lookup (self->features, GSIZE_TO_POINTER (*child))); @@ -565,7 +565,7 @@ wp_object_manager_is_interested_in_object (WpObjectManager * self, static gboolean wp_object_manager_is_interested_in_global (WpObjectManager * self, - WpGlobal * global, WpProxyFeatures * wanted_features) + WpGlobal * global, WpObjectFeatures * wanted_features) { gint i; WpObjectInterest *interest = NULL; @@ -576,7 +576,7 @@ wp_object_manager_is_interested_in_global (WpObjectManager * self, global->proxy, NULL, global->properties)) { gpointer ft = g_hash_table_lookup (self->features, GSIZE_TO_POINTER (global->type)); - *wanted_features = (WpProxyFeatures) GPOINTER_TO_UINT (ft); + *wanted_features = (WpObjectFeatures) GPOINTER_TO_UINT (ft); return TRUE; } } @@ -658,8 +658,8 @@ on_proxy_ready (GObject * proxy, GAsyncResult * res, gpointer data) self->pending_objects--; - if (!wp_proxy_augment_finish (WP_PROXY (proxy), res, &error)) { - wp_message_object (self, "proxy augment failed: %s", error->message); + if (!wp_object_activate_finish (WP_OBJECT (proxy), res, &error)) { + wp_message_object (self, "proxy activation failed: %s", error->message); } else { wp_trace_object (self, "added: " WP_OBJECT_FORMAT, WP_OBJECT_ARGS (proxy)); g_ptr_array_add (self->objects, proxy); @@ -678,7 +678,7 @@ wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global) /* do not allow proxies that don't have a defined subclass; bind will fail because proxy_class->pw_iface_type is NULL */ - if (global->type == WP_TYPE_PROXY) + if (global->type == WP_TYPE_GLOBAL_PROXY) return; if (wp_object_manager_is_interested_in_global (self, global, &features)) { @@ -695,8 +695,8 @@ wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global) wp_trace_object (self, "adding global:%u -> " WP_OBJECT_FORMAT, global->id, WP_OBJECT_ARGS (global->proxy)); - wp_proxy_augment (global->proxy, features, NULL, on_proxy_ready, - g_object_ref (self)); + wp_object_activate (WP_OBJECT (global->proxy), features, NULL, + on_proxy_ready, g_object_ref (self)); } } @@ -742,7 +742,7 @@ wp_object_manager_rm_object (WpObjectManager * self, gpointer object) * * 2) PipeWire global objects, which were constructed by this process, either * by calling into a remove factory (see wp_node_new_from_factory()) or - * by exporting a local object (WpImplNode etc...). + * by exporting a local object (WpImplSession etc...). * * These objects are also represented by a WpGlobal, which may however be * constructed before they appear on the registry. The associated WpProxy @@ -805,7 +805,7 @@ object_manager_destroyed (gpointer data, GObject * om) g_ptr_array_remove_fast (self->object_managers, om); } -/* find the subclass of WpProxy that can handle +/* find the subclass of WpPipewireGloabl that can handle the given pipewire interface type of the given version */ static inline GType find_proxy_instance_type (const char * type, guint32 version) @@ -813,7 +813,7 @@ find_proxy_instance_type (const char * type, guint32 version) g_autofree GType *children; guint n_children; - children = g_type_children (WP_TYPE_PROXY, &n_children); + children = g_type_children (WP_TYPE_GLOBAL_PROXY, &n_children); for (gint i = 0; i < n_children; i++) { WpProxyClass *klass = (WpProxyClass *) g_type_class_ref (children[i]); @@ -826,7 +826,7 @@ find_proxy_instance_type (const char * type, guint32 version) g_type_class_unref (klass); } - return WP_TYPE_PROXY; + return WP_TYPE_GLOBAL_PROXY; } /* called by the registry when a global appears */ @@ -1043,7 +1043,7 @@ expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self) void wp_registry_prepare_new_global (WpRegistry * self, guint32 id, guint32 permissions, guint32 flag, GType type, - WpProxy *proxy, const struct spa_dict *props, + WpGlobalProxy *proxy, const struct spa_dict *props, WpGlobal ** new_global) { g_autoptr (WpGlobal) global = NULL; @@ -1260,7 +1260,7 @@ wp_global_rm_flag (WpGlobal *global, guint rm_flag) if (global->proxy) { if (reg) wp_registry_notify_rm_object (reg, global->proxy); - wp_proxy_destroy (global->proxy); + wp_object_deactivate (WP_OBJECT (global->proxy), WP_PROXY_FEATURE_BOUND); /* if the proxy is not owning the global, unref it */ if (global->flags == 0) diff --git a/lib/wp/object-manager.h b/lib/wp/object-manager.h index e36037d2894beb1f203e75f8b75e8c5ed4f55cb2..dd9dadcdedc360432b331170e018121e6833ede4 100644 --- a/lib/wp/object-manager.h +++ b/lib/wp/object-manager.h @@ -10,7 +10,7 @@ #define __WIREPLUMBER_OBJECT_MANAGER_H__ #include <glib-object.h> -#include "proxy.h" +#include "object.h" #include "iterator.h" #include "object-interest.h" @@ -43,11 +43,11 @@ WP_API void wp_object_manager_add_interest_full (WpObjectManager * self, WpObjectInterest * interest); -/* proxy features */ +/* object features */ WP_API -void wp_object_manager_request_proxy_features (WpObjectManager *self, - GType proxy_type, WpProxyFeatures wanted_features); +void wp_object_manager_request_object_features (WpObjectManager *self, + GType object_type, WpObjectFeatures wanted_features); /* object inspection */ diff --git a/lib/wp/port.c b/lib/wp/port.c index 490edd7f36ac959d56cd85bbed7f49c265a23ff8..5d501140588e4f31a862dfd9d915b42f8aab0783 100644 --- a/lib/wp/port.c +++ b/lib/wp/port.c @@ -1,91 +1,80 @@ /* WirePlumber * - * Copyright © 2019 Collabora Ltd. + * Copyright © 2019-2020 Collabora Ltd. * @author Julian Bouzas <julian.bouzas@collabora.com> * * SPDX-License-Identifier: MIT */ /** - * SECTION: WpPort - * - * The #WpPort class allows accessing the properties and methods of a - * PipeWire port object (`struct pw_port`). - * - * A #WpPort is constructed internally when a new port appears on the - * PipeWire registry and it is made available through the #WpObjectManager API. + * SECTION: port + * @title: PipeWire Port */ #define G_LOG_DOMAIN "wp-port" #include "port.h" -#include "private.h" +#include "private/pipewire-object-mixin.h" -#include <pipewire/pipewire.h> - -/** - * WpPort: - */ struct _WpPort { - WpProxy parent; + WpGlobalProxy parent; struct pw_port_info *info; - - /* The port proxy listener */ struct spa_hook listener; }; -G_DEFINE_TYPE (WpPort, wp_port, WP_TYPE_PROXY) +static void wp_port_pipewire_object_interface_init (WpPipewireObjectInterface * iface); + +/** + * WpPort: + * + * The #WpPort class allows accessing the properties and methods of a + * PipeWire port object (`struct pw_port`). + * + * A #WpPort is constructed internally when a new port appears on the + * 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)); static void wp_port_init (WpPort * self) { } -static void -wp_port_finalize (GObject * object) +static WpObjectFeatures +wp_port_get_supported_features (WpObject * object) { WpPort *self = WP_PORT (object); - g_clear_pointer (&self->info, pw_port_info_free); - - G_OBJECT_CLASS (wp_port_parent_class)->finalize (object); -} - -static gconstpointer -wp_port_get_info (WpProxy * self) -{ - return WP_PORT (self)->info; -} - -static WpProperties * -wp_port_get_properties (WpProxy * self) -{ - return wp_properties_new_wrap_dict (WP_PORT (self)->info->props); + 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 struct spa_param_info * -wp_port_get_param_info (WpProxy * proxy, guint * n_params) +static void +wp_port_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpPort *self = WP_PORT (proxy); - *n_params = self->info->n_params; - return self->info->params; + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO: + wp_pipewire_object_mixin_cache_info (object, transition); + break; + default: + WP_OBJECT_CLASS (wp_port_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; + } } -static gint -wp_port_enum_params (WpProxy * self, guint32 id, guint32 start, guint32 num, - WpSpaPod * filter) +static void +wp_port_deactivate (WpObject * object, WpObjectFeatures features) { - struct pw_port *pwp = (struct pw_port *) wp_proxy_get_pw_proxy (self); - return pw_port_enum_params (pwp, 0, id, start, num, - filter ? wp_spa_pod_get_spa_pod (filter) : NULL); -} + wp_pipewire_object_mixin_deactivate (object, features); -static gint -wp_port_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids) -{ - struct pw_port *pwp = (struct pw_port *) wp_proxy_get_pw_proxy (self); - return pw_port_subscribe_params (pwp, ids, n_ids); + WP_OBJECT_CLASS (wp_port_parent_class)->deactivate (object, features); } static void @@ -94,21 +83,17 @@ 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_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - g_object_notify (G_OBJECT (self), "info"); - - if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); - - if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) - g_object_notify (G_OBJECT (self), "param-info"); + 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_proxy_handle_event_param, + .param = wp_pipewire_object_mixin_handle_event_param, }; static void @@ -119,32 +104,87 @@ wp_port_pw_proxy_created (WpProxy * proxy, struct pw_proxy * 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); +} + static void wp_port_class_init (WpPortClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_port_finalize; + object_class->get_property = wp_pipewire_object_mixin_get_property; + + wpobject_class->get_supported_features = wp_port_get_supported_features; + wpobject_class->activate_get_next_step = + wp_pipewire_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; - proxy_class->get_info = wp_port_get_info; - proxy_class->get_properties = wp_port_get_properties; - proxy_class->get_param_info = wp_port_get_param_info; - proxy_class->enum_params = wp_port_enum_params; - proxy_class->subscribe_params = wp_port_subscribe_params; + wp_pipewire_object_mixin_class_override_properties (object_class); +} - proxy_class->pw_proxy_created = wp_port_pw_proxy_created; +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); +} + +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); +} + +static void +wp_port_enum_params (WpPipewireObject * obj, const gchar * id, + WpSpaPod *filter, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + wp_pipewire_object_mixin_enum_params (pw_port, obj, id, filter, cancellable, + callback, user_data); +} + +static void +wp_port_pipewire_object_interface_init (WpPipewireObjectInterface * 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; + 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 wp_port_get_direction (WpPort * self) { g_return_val_if_fail (WP_IS_PORT (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, 0); + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); return (WpDirection) self->info->direction; } diff --git a/lib/wp/port.h b/lib/wp/port.h index e9de692df6d237d769efeda70cde23dccf2314e6..bba7eecff3eb060ad3f1461d000c583fca463e5e 100644 --- a/lib/wp/port.h +++ b/lib/wp/port.h @@ -9,7 +9,7 @@ #ifndef __WIREPLUMBER_PORT_H__ #define __WIREPLUMBER_PORT_H__ -#include "proxy.h" +#include "global-proxy.h" G_BEGIN_DECLS @@ -32,7 +32,7 @@ typedef enum { */ #define WP_TYPE_PORT (wp_port_get_type ()) WP_API -G_DECLARE_FINAL_TYPE (WpPort, wp_port, WP, PORT, WpProxy) +G_DECLARE_FINAL_TYPE (WpPort, wp_port, WP, PORT, WpGlobalProxy) WP_API WpDirection wp_port_get_direction (WpPort * self); diff --git a/lib/wp/private.h b/lib/wp/private.h index b913ea90f273d5bff86b98d8f228fbcd81418c10..2734d0991739e58a5cf90f4189bdabaddcbbaf28 100644 --- a/lib/wp/private.h +++ b/lib/wp/private.h @@ -33,20 +33,6 @@ struct spa_pod_builder; void wp_props_handle_proxy_param_event (WpProps * self, guint32 id, WpSpaPod * pod); -/* proxy */ - -void wp_proxy_destroy (WpProxy *self); -void wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy); - -void wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature); -void wp_proxy_augment_error (WpProxy * self, GError * error); - -void wp_proxy_handle_event_param (void * proxy, int seq, uint32_t id, - uint32_t index, uint32_t next, const struct spa_pod *param); - -WpProps * wp_proxy_get_props (WpProxy * self); -void wp_proxy_set_props (WpProxy * self, WpProps * props); - /* iterator */ struct _WpIteratorMethods { diff --git a/lib/wp/private/pipewire-object-mixin.c b/lib/wp/private/pipewire-object-mixin.c new file mode 100644 index 0000000000000000000000000000000000000000..49b316d6f2f12f4ee0c38b41b6361cfe6301be67 --- /dev/null +++ b/lib/wp/private/pipewire-object-mixin.c @@ -0,0 +1,258 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#define G_LOG_DOMAIN "wp-pw-obj-mixin" + +#include "private/pipewire-object-mixin.h" +#include "core.h" +#include "error.h" + +G_DEFINE_QUARK (WpPipewireObjectMixinEnumParamsTasks, enum_params_tasks) + +void +wp_pipewire_object_mixin_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + case WP_PIPEWIRE_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: + g_value_set_boxed (value, + wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (object))); + break; + case WP_PIPEWIRE_OBJECT_MIXIN_PROP_PARAM_INFO: + g_value_set_variant (value, + wp_pipewire_object_get_param_info (WP_PIPEWIRE_OBJECT (object))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +wp_pipewire_object_mixin_class_override_properties (GObjectClass * klass) +{ + g_object_class_override_property (klass, + WP_PIPEWIRE_OBJECT_MIXIN_PROP_NATIVE_INFO, "native-info"); + g_object_class_override_property (klass, + WP_PIPEWIRE_OBJECT_MIXIN_PROP_PROPERTIES, "properties"); + g_object_class_override_property (klass, + WP_PIPEWIRE_OBJECT_MIXIN_PROP_PARAM_INFO, "param-info"); +} + +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 }, +}; + +WpObjectFeatures +wp_pipewire_object_mixin_param_info_to_features (struct spa_param_info * info, + guint n_params) +{ + WpObjectFeatures ft = 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; + } + } + return ft; +} + +guint +wp_pipewire_object_mixin_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) +{ + /* bind if not already bound */ + if (missing & WP_PROXY_FEATURE_BOUND) + return WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND; + /* then cache info */ + else + return WP_PIPEWIRE_OBJECT_MIXIN_STEP_CACHE_INFO; + + /* returning to STEP_NONE is handled by WpFeatureActivationTransition */ +} + +void +wp_pipewire_object_mixin_cache_info (WpObject * object, + WpFeatureActivationTransition * transition) +{ + /* TODO */ +} + +void +wp_pipewire_object_mixin_deactivate (WpObject * object, + WpObjectFeatures features) +{ + /* TODO */ +} + +static gint +task_has_seq (gconstpointer task, gconstpointer seq) +{ + gpointer t_seq = g_task_get_source_tag (G_TASK (task)); + return (GPOINTER_TO_INT (t_seq) == GPOINTER_TO_INT (seq)) ? 0 : 1; +} + +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) +{ + 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); + task = list ? G_TASK (list->data) : NULL; + + if (task) { + GPtrArray *array = g_task_get_task_data (task); + g_ptr_array_add (array, wp_spa_pod_copy (w_param)); + } +} + +GVariant * +wp_pipewire_object_mixin_param_info_to_gvariant (struct spa_param_info * info, + guint n_params) +{ + g_auto (GVariantBuilder) b = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_DICTIONARY); + + if (!info || n_params == 0) + return NULL; + + 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); +} + +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); + + 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); +} + +WpIterator * +wp_pipewire_object_mixin_enum_cached_params (WpPipewireObject * obj, + const gchar * id) +{ + return NULL; //TODO +} + +void +wp_pipewire_object_mixin_enum_params_unimplemented (WpPipewireObject * obj, + const gchar * id, WpSpaPod *filter, GCancellable * cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + wp_pipewire_object_mixin_create_enum_params_task (obj, 0, cancellable, + callback, user_data); +} + +void +wp_pipewire_object_mixin_set_param_unimplemented (WpPipewireObject * obj, + const gchar * id, WpSpaPod * param) +{ + wp_warning_object (obj, + "setting params is not implemented on this object type"); +} + +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; + + /* finish the sync task */ + wp_core_sync_finish (core, res, &error); + + /* 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 (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); + } +} + +void +wp_pipewire_object_mixin_create_enum_params_task (gpointer instance, + gint seq, GCancellable * cancellable, GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr (GTask) task = NULL; + GPtrArray *params; + GList *list; + + /* 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; + } + + /* 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); + + /* call sync */ + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (instance)); + wp_core_sync (core, cancellable, (GAsyncReadyCallback) enum_params_done, + task); +} diff --git a/lib/wp/private/pipewire-object-mixin.h b/lib/wp/private/pipewire-object-mixin.h new file mode 100644 index 0000000000000000000000000000000000000000..a329f8d87ef51ef60d5ebd86b712e5dc468b91aa --- /dev/null +++ b/lib/wp/private/pipewire-object-mixin.h @@ -0,0 +1,162 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_PIPEWIRE_OBJECT_MIXIN_H__ +#define __WIREPLUMBER_PIPEWIRE_OBJECT_MIXIN_H__ + +#include "proxy-interfaces.h" +#include "spa-type.h" +#include "spa-pod.h" +#include "debug.h" +#include "private.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_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, +}; + +enum { + WP_PIPEWIRE_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_PIPEWIRE_OBJECT_MIXIN_PROP_CUSTOM_START, +}; + +/******************/ +/* PROPERTIES API */ + +/* assign to get_property or chain it from there */ +void wp_pipewire_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); + +/****************/ +/* FEATURES API */ + +/* call from get_supported_features */ +WpObjectFeatures wp_pipewire_object_mixin_param_info_to_features ( + struct spa_param_info * info, guint n_params); + +/* assign directly to activate_get_next_step */ +guint wp_pipewire_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); + +/* handle deactivation of PARAM_* caching features */ +void wp_pipewire_object_mixin_deactivate (WpObject * object, + WpObjectFeatures features); + +/***************************/ +/* PIPEWIRE EVENT HANDLERS */ + +/* call at the end of the info event callback */ +#define wp_pipewire_object_mixin_handle_event_info(instance, info, CM_PROPS, CM_PARAMS) \ +({ \ + 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"); \ +}) + +/* 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); + +/***********************/ +/* PIPEWIRE OBJECT API */ + +/* call from get_param_info */ +GVariant * wp_pipewire_object_mixin_param_info_to_gvariant ( + struct spa_param_info * info, guint n_params); + +/* assign directly to enum_params_finish */ +WpIterator * wp_pipewire_object_mixin_enum_params_finish (WpPipewireObject * obj, + GAsyncResult * res, GError ** error); + +/* assign directly to enum_cached_params */ +WpIterator * wp_pipewire_object_mixin_enum_cached_params (WpPipewireObject * obj, + const gchar * id); + +/* 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); + +/* 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); + +/* 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); \ + } \ +}) + +/* 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)); \ + } \ + } \ +}) + +/************/ +/* INTERNAL */ + +void wp_pipewire_object_mixin_create_enum_params_task (gpointer instance, + gint seq, GCancellable * cancellable, GAsyncReadyCallback callback, + gpointer user_data); + +G_END_DECLS + +#endif diff --git a/lib/wp/private/registry.h b/lib/wp/private/registry.h index cb62664fc9f8bac1ca26412e2e1f292ffb1f3da2..9078e9aa853f4b3363e76d645cd44ce3c3de2498 100644 --- a/lib/wp/private/registry.h +++ b/lib/wp/private/registry.h @@ -10,6 +10,7 @@ #define __WIREPLUMBER_REGISTRY_H__ #include "core.h" +#include "global-proxy.h" #include <pipewire/pipewire.h> @@ -38,7 +39,7 @@ void wp_registry_detach (WpRegistry *self); void wp_registry_prepare_new_global (WpRegistry * self, guint32 id, guint32 permissions, guint32 flag, GType type, - WpProxy *proxy, const struct spa_dict *props, + WpGlobalProxy *proxy, const struct spa_dict *props, WpGlobal ** new_global); gpointer wp_registry_find_object (WpRegistry *reg, GEqualFunc func, @@ -66,7 +67,7 @@ struct _WpGlobal GType type; guint32 permissions; WpProperties *properties; - WpProxy *proxy; + WpGlobalProxy *proxy; WpRegistry *registry; }; diff --git a/lib/wp/proxy.c b/lib/wp/proxy.c index b4dd8d66f7c34018945d90bb67f2e4f57c954a04..bed50720ca3889b3f19f950df10631e14a952aa8 100644 --- a/lib/wp/proxy.c +++ b/lib/wp/proxy.c @@ -1,69 +1,35 @@ /* WirePlumber * - * Copyright © 2019 Collabora Ltd. - * @author Julian Bouzas <julian.bouzas@collabora.com> + * Copyright © 2020 Collabora Ltd. * @author George Kiagiadakis <george.kiagiadakis@collabora.com> * * SPDX-License-Identifier: MIT */ /** - * SECTION: WpProxy - * + * SECTION: proxy + * @title: PipeWire Object Proxy */ #define G_LOG_DOMAIN "wp-proxy" #include "proxy.h" #include "debug.h" -#include "core.h" -#include "error.h" -#include "wpenums.h" -#include "private.h" #include <pipewire/pipewire.h> -#include <pipewire/impl.h> -#include <pipewire/extensions/metadata.h> -#include <pipewire/extensions/client-node.h> -#include <pipewire/extensions/session-manager.h> - -#include <spa/debug/types.h> -#include <spa/pod/builder.h> -#include <spa/utils/result.h> +#include <spa/utils/hook.h> typedef struct _WpProxyPrivate WpProxyPrivate; struct _WpProxyPrivate { - /* properties */ - GWeakRef core; - WpGlobal *global; struct pw_proxy *pw_proxy; - - /* The proxy listener */ struct spa_hook listener; - - /* augment state */ - WpProxyFeatures ft_ready; - GPtrArray *augment_tasks; // element-type: GTask* - - GHashTable *async_tasks; // <int seq, GTask*> - - /* props cache */ - WpProps *props; }; enum { PROP_0, - PROP_CORE, - PROP_GLOBAL, - PROP_GLOBAL_PERMISSIONS, - PROP_GLOBAL_PROPERTIES, - PROP_FEATURES, - PROP_PW_PROXY, - PROP_INFO, - PROP_PROPERTIES, - PROP_PARAM_INFO, PROP_BOUND_ID, + PROP_PW_PROXY, }; enum @@ -71,74 +37,46 @@ enum SIGNAL_PW_PROXY_CREATED, SIGNAL_PW_PROXY_DESTROYED, SIGNAL_BOUND, - SIGNAL_PARAM, - SIGNAL_PROP_CHANGED, LAST_SIGNAL, }; -static guint wp_proxy_signals[LAST_SIGNAL] = { 0 }; +static guint signals[LAST_SIGNAL] = { 0 }; -G_DEFINE_TYPE_WITH_PRIVATE (WpProxy, wp_proxy, G_TYPE_OBJECT) +/** + * WpProxy: + * + * Base class for all objects that expose PipeWire objects using `pw_proxy` + * underneath. + * + * This base class cannot be instantiated. It provides handling of + * pw_proxy's events and exposes common functionality. + */ +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpProxy, wp_proxy, WP_TYPE_OBJECT) static void proxy_event_destroy (void *data) { - /* hold a reference to the proxy because unref-ing the tasks might - destroy the proxy, in case the registry is no longer holding a reference */ - g_autoptr (WpProxy) self = g_object_ref (WP_PROXY (data)); + WpProxy *self = WP_PROXY (data); WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - GHashTableIter iter; - GTask *task; wp_trace_object (self, "destroyed pw_proxy %p (%u)", priv->pw_proxy, - priv->global ? priv->global->id : pw_proxy_get_bound_id (priv->pw_proxy)); + pw_proxy_get_bound_id (priv->pw_proxy)); spa_hook_remove (&priv->listener); priv->pw_proxy = NULL; - g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED], 0); - - /* Return error if the pw_proxy destruction happened while the async - * init or augment of this proxy object was in progress */ - if (priv->augment_tasks->len > 0) { - GError *err = g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, - "pipewire proxy destroyed before finishing"); - wp_proxy_augment_error (self, err); - } - - g_hash_table_iter_init (&iter, priv->async_tasks); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &task)) { - g_task_return_new_error (task, WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, - "pipewire proxy destroyed before finishing"); - g_hash_table_iter_remove (&iter); - } + wp_object_update_features (WP_OBJECT (self), 0, WP_PROXY_FEATURE_BOUND); + g_signal_emit (self, signals[SIGNAL_PW_PROXY_DESTROYED], 0); } static void proxy_event_bound (void *data, uint32_t global_id) { WpProxy *self = WP_PROXY (data); - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - - /* we generally make the assumption here that the bound id is the - same as the global id, but while this **is** it's intended use, - the truth is that the bound id **can** be changed anytime with - pw_proxy_set_bound_id() and this can be very bad... */ - g_warn_if_fail (!priv->global || priv->global->id == global_id); - - wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_BOUND); - /* construct a WpGlobal if it was not already there */ - if (!priv->global) { - g_autoptr (WpCore) core = g_weak_ref_get (&priv->core); + wp_trace_object (self, "bound to %u", global_id); - wp_registry_prepare_new_global (wp_core_get_registry (core), - global_id, PW_PERM_RWX, WP_GLOBAL_FLAG_OWNED_BY_PROXY, - G_TYPE_FROM_INSTANCE (self), self, NULL, &priv->global); - } - - g_signal_emit (self, wp_proxy_signals[SIGNAL_BOUND], 0, global_id); + wp_object_update_features (WP_OBJECT (self), WP_PROXY_FEATURE_BOUND, 0); + g_signal_emit (self, signals[SIGNAL_BOUND], 0, global_id); } static void @@ -154,130 +92,24 @@ static const struct pw_proxy_events proxy_events = { .removed = proxy_event_removed, }; -void -wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy) -{ - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - - if (!proxy) - return; - - g_return_if_fail (priv->pw_proxy == NULL); - priv->pw_proxy = proxy; - - pw_proxy_add_listener (priv->pw_proxy, &priv->listener, &proxy_events, - self); - - /* inform subclasses and listeners */ - g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_CREATED], 0, - priv->pw_proxy); - - /* declare the feature as ready */ - wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_PW_PROXY); -} - static void wp_proxy_init (WpProxy * self) { - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - - g_weak_ref_init (&priv->core, NULL); - priv->augment_tasks = g_ptr_array_new_with_free_func (g_object_unref); - priv->async_tasks = g_hash_table_new_full (g_direct_hash, g_direct_equal, - NULL, g_object_unref); } static void -wp_proxy_dispose (GObject * object) -{ - WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object)); - - wp_trace_object (object, "dispose (global %u; pw_proxy %p)", - priv->global ? priv->global->id : 0, priv->pw_proxy); - - if (priv->global) - wp_global_rm_flag (priv->global, WP_GLOBAL_FLAG_OWNED_BY_PROXY); - - /* this will trigger proxy_event_destroy() if the pw_proxy exists */ - if (priv->pw_proxy) - pw_proxy_destroy (priv->pw_proxy); - - G_OBJECT_CLASS (wp_proxy_parent_class)->dispose (object); -} - -static void -wp_proxy_finalize (GObject * object) -{ - WpProxy *self = WP_PROXY (object); - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - - g_clear_object (&priv->props); - g_clear_pointer (&priv->augment_tasks, g_ptr_array_unref); - g_clear_pointer (&priv->global, wp_global_unref); - g_weak_ref_clear (&priv->core); - g_clear_pointer (&priv->async_tasks, g_hash_table_unref); - - G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object); -} - -static void -wp_proxy_set_gobj_property (GObject * object, guint property_id, - const GValue * value, GParamSpec * pspec) -{ - WpProxy *self = WP_PROXY (object); - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - - switch (property_id) { - case PROP_CORE: - g_weak_ref_set (&priv->core, g_value_get_object (value)); - break; - case PROP_GLOBAL: - priv->global = g_value_dup_boxed (value); - break; - case PROP_PW_PROXY: - wp_proxy_set_pw_proxy (self, g_value_get_pointer (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -wp_proxy_get_gobj_property (GObject * object, guint property_id, GValue * value, +wp_proxy_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { WpProxy *self = WP_PROXY (object); - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); switch (property_id) { - case PROP_CORE: - g_value_take_object (value, g_weak_ref_get (&priv->core)); - break; - case PROP_GLOBAL_PERMISSIONS: - g_value_set_uint (value, priv->global ? priv->global->permissions : 0); - break; - case PROP_GLOBAL_PROPERTIES: - g_value_set_boxed (value, priv->global ? priv->global->properties : NULL); - break; - case PROP_FEATURES: - g_value_set_flags (value, priv->ft_ready); - break; - case PROP_PW_PROXY: - g_value_set_pointer (value, priv->pw_proxy); - break; - case PROP_INFO: - g_value_set_pointer (value, (gpointer) wp_proxy_get_info (self)); - break; - case PROP_PROPERTIES: - g_value_take_boxed (value, wp_proxy_get_properties (self)); - break; - case PROP_PARAM_INFO: - g_value_take_variant (value, wp_proxy_get_param_info (self)); - break; case PROP_BOUND_ID: g_value_set_uint (value, wp_proxy_get_bound_id (self)); break; + case PROP_PW_PROXY: + g_value_set_pointer (value, wp_proxy_get_pw_proxy (self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -285,91 +117,13 @@ wp_proxy_get_gobj_property (GObject * object, guint property_id, GValue * value, } static void -wp_proxy_enum_props_done (WpCore * core, GAsyncResult * res, WpProxy * self) -{ - g_autoptr (GError) error = NULL; - if (!wp_core_sync_finish (core, res, &error)) { - wp_warning_object (self, "core sync failed: %s", error->message); - } - - wp_trace_object (self, "enum props done"); - wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_PROPS); -} - -static void -wp_proxy_enable_feature_props (WpProxy * self) -{ - WpProxyClass *klass = WP_PROXY_GET_CLASS (self); - struct spa_param_info *param_info; - guint n_params; - guint have_propinfo = FALSE, have_props = FALSE; - uint32_t ids[] = { SPA_PARAM_Props }; - - /* check if we actually have props */ - param_info = klass->get_param_info (self, &n_params); - for (guint i = 0; i < n_params; i++) { - if (param_info[i].id == SPA_PARAM_PropInfo) - have_propinfo = TRUE; - else if (param_info[i].id == SPA_PARAM_Props) - have_props = TRUE; - } - - if (have_propinfo && have_props) { - g_autoptr (WpCore) core = wp_proxy_get_core (self); - - if (!klass->enum_params || !klass->subscribe_params) { - wp_proxy_augment_error (self, g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_INVARIANT, - "Proxy does not support enum/subscribe params API")); - return; - } - - klass->enum_params (self, SPA_PARAM_PropInfo, 0, -1, NULL); - klass->subscribe_params (self, ids, SPA_N_ELEMENTS (ids)); - wp_core_sync (core, NULL, (GAsyncReadyCallback) wp_proxy_enum_props_done, - self); - } else { - /* declare as ready with no props */ - wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_PROPS); - } - - g_signal_handlers_disconnect_by_func (self, - wp_proxy_enable_feature_props, self); -} - -static void -wp_proxy_default_augment (WpProxy * self, WpProxyFeatures features) +wp_proxy_deactivate (WpObject * object, WpObjectFeatures features) { - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - - /* ensure we have a pw_proxy, as we can't have - * any other feature without first having that */ - if (!priv->pw_proxy && features != 0) - features |= WP_PROXY_FEATURE_PW_PROXY; - - /* if we don't have a pw_proxy, we have to assume that this WpProxy - * represents a global object from the registry; we have no other way - * to get a pw_proxy */ - if (features & WP_PROXY_FEATURE_PW_PROXY) { - if (priv->global == NULL) { - wp_proxy_augment_error (self, g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_INVALID_ARGUMENT, - "No global specified; cannot bind pw_proxy")); - return; - } - - /* bind */ - wp_proxy_set_pw_proxy (self, wp_global_bind (priv->global)); - } - - if (features & WP_PROXY_FEATURE_PROPS && !priv->props) { - wp_proxy_set_props (self, wp_props_new (WP_PROPS_MODE_CACHE, self)); - - if (priv->ft_ready & WP_PROXY_FEATURE_INFO) - wp_proxy_enable_feature_props (self); - else - g_signal_connect (self, "notify::param-info", - G_CALLBACK (wp_proxy_enable_feature_props), self); + if (features & WP_PROXY_FEATURE_BOUND) { + WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY (object)); + if (priv->pw_proxy) + pw_proxy_destroy (priv->pw_proxy); + wp_object_update_features (object, 0, WP_PROXY_FEATURE_BOUND); } } @@ -377,451 +131,38 @@ static void wp_proxy_class_init (WpProxyClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; - object_class->dispose = wp_proxy_dispose; - object_class->finalize = wp_proxy_finalize; - object_class->get_property = wp_proxy_get_gobj_property; - object_class->set_property = wp_proxy_set_gobj_property; + object_class->get_property = wp_proxy_get_property; - klass->augment = wp_proxy_default_augment; + wpobject_class->deactivate = wp_proxy_deactivate; /* Install the properties */ - g_object_class_install_property (object_class, PROP_CORE, - g_param_spec_object ("core", "core", "The WpCore", WP_TYPE_CORE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_GLOBAL, - g_param_spec_boxed ("global", "global", "Internal WpGlobal object", - wp_global_get_type (), - G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_GLOBAL_PERMISSIONS, - g_param_spec_uint ("global-permissions", "global-permissions", - "The pipewire global permissions", 0, G_MAXUINT, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES, - g_param_spec_boxed ("global-properties", "global-properties", - "The pipewire global properties", WP_TYPE_PROPERTIES, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_FEATURES, - g_param_spec_flags ("features", "features", - "The ready WpProxyFeatures on this proxy", WP_TYPE_PROXY_FEATURES, 0, + g_object_class_install_property (object_class, PROP_BOUND_ID, + g_param_spec_uint ("bound-id", "bound-id", + "The id that this object has on the registry", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PW_PROXY, g_param_spec_pointer ("pw-proxy", "pw-proxy", "The struct pw_proxy *", - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_INFO, - g_param_spec_pointer ("info", "info", "The native info structure", - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_PROPERTIES, - g_param_spec_boxed ("properties", "properties", - "The pipewire properties of the object", WP_TYPE_PROPERTIES, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_PARAM_INFO, - g_param_spec_variant ("param-info", "param-info", - "The param info of the object", G_VARIANT_TYPE ("a{ss}"), NULL, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (object_class, PROP_BOUND_ID, - g_param_spec_uint ("bound-id", "bound-id", - "The id that this object has on the registry", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /* Signals */ - wp_proxy_signals[SIGNAL_PW_PROXY_CREATED] = g_signal_new ( + signals[SIGNAL_PW_PROXY_CREATED] = g_signal_new ( "pw-proxy-created", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (WpProxyClass, pw_proxy_created), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_POINTER); - wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED] = g_signal_new ( + signals[SIGNAL_PW_PROXY_DESTROYED] = g_signal_new ( "pw-proxy-destroyed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (WpProxyClass, pw_proxy_destroyed), NULL, NULL, NULL, G_TYPE_NONE, 0); - wp_proxy_signals[SIGNAL_BOUND] = g_signal_new ( + signals[SIGNAL_BOUND] = g_signal_new ( "bound", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (WpProxyClass, bound), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT); - - wp_proxy_signals[SIGNAL_PROP_CHANGED] = g_signal_new ( - "prop-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (WpProxyClass, prop_changed), NULL, NULL, NULL, - G_TYPE_NONE, 1, G_TYPE_STRING); -} - -/* private */ -void -wp_proxy_destroy (WpProxy *self) -{ - WpProxyPrivate *priv; - - g_return_if_fail (WP_IS_PROXY (self)); - - priv = wp_proxy_get_instance_private (self); - if (priv->pw_proxy) - pw_proxy_destroy (priv->pw_proxy); -} - -/** - * wp_proxy_request_destroy: - * @self: the proxy - * - * Requests the PipeWire server to destroy the object represented by this proxy. - * If the server allows it, the object will be destroyed and the - * WpProxy::pw-proxy-destroyed signal will be emitted. If the server does - * not allow it, nothing will happen. - * - * This is mostly useful for destroying #WpLink and #WpEndpointLink objects. - */ -void -wp_proxy_request_destroy (WpProxy * self) -{ - WpProxyPrivate *priv; - g_autoptr (WpCore) core = NULL; - WpRegistry *reg; - - g_return_if_fail (WP_IS_PROXY (self)); - - priv = wp_proxy_get_instance_private (self); - core = wp_proxy_get_core (self); - - if (priv->pw_proxy && core) { - reg = wp_core_get_registry (core); - pw_registry_destroy (reg->pw_registry, - pw_proxy_get_bound_id (priv->pw_proxy)); - } -} - -void -wp_proxy_augment (WpProxy * self, - WpProxyFeatures ft_wanted, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) -{ - WpProxyPrivate *priv; - WpProxyFeatures missing = 0; - g_autoptr (GTask) task = NULL; - - g_return_if_fail (WP_IS_PROXY (self)); - g_return_if_fail (WP_PROXY_GET_CLASS (self)->augment); - - priv = wp_proxy_get_instance_private (self); - - task = g_task_new (self, cancellable, callback, user_data); - - /* find which features are wanted but missing from the "ready" set */ - missing = (priv->ft_ready ^ ft_wanted) & ft_wanted; - - /* if the features are not ready, call augment(), - * otherwise signal the callback directly */ - if (missing != 0) { - g_task_set_task_data (task, GUINT_TO_POINTER (missing), NULL); - g_ptr_array_add (priv->augment_tasks, g_steal_pointer (&task)); - WP_PROXY_GET_CLASS (self)->augment (self, missing); - } else { - g_task_return_boolean (task, TRUE); - } -} - -gboolean -wp_proxy_augment_finish (WpProxy * self, GAsyncResult * res, - GError ** error) -{ - g_return_val_if_fail (WP_IS_PROXY (self), FALSE); - g_return_val_if_fail (g_task_is_valid (res, self), FALSE); - - return g_task_propagate_boolean (G_TASK (res), error); -} - -void -wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature) -{ - WpProxyPrivate *priv; - g_autoptr (GPtrArray) ready_tasks = NULL; - guint i; - - g_return_if_fail (WP_IS_PROXY (self)); - - priv = wp_proxy_get_instance_private (self); - - /* feature already marked as ready */ - if (priv->ft_ready & feature) - return; - - priv->ft_ready |= feature; - - if (wp_log_level_is_enabled (WP_LOG_LEVEL_TRACE)) { - g_autofree gchar *str = g_flags_to_string (WP_TYPE_PROXY_FEATURES, - priv->ft_ready); - wp_trace_object (self, "features changed: %s", str); - } - - g_object_notify (G_OBJECT (self), "features"); - - /* hold a reference to the proxy because unref-ing the tasks might - destroy the proxy, in case the registry is no longer holding a reference */ - g_object_ref (self); - - /* move the ready tasks to another array to avoid recursion issues */ - ready_tasks = g_ptr_array_new_with_free_func (g_object_unref); - - /* return from the task if all the wanted features are now ready */ - for (i = priv->augment_tasks->len; i > 0; i--) { - GTask *task = g_ptr_array_index (priv->augment_tasks, i - 1); - WpProxyFeatures wanted = GPOINTER_TO_UINT (g_task_get_task_data (task)); - - if ((priv->ft_ready & wanted) == wanted) { - /* this is safe as long as we are traversing the array backwards */ - g_ptr_array_add (ready_tasks, - g_ptr_array_steal_index_fast (priv->augment_tasks, i - 1)); - } - } - - for (i = 0; i < ready_tasks->len; i++) { - GTask *task = g_ptr_array_index (ready_tasks, i); - g_task_return_boolean (task, TRUE); - } - - g_object_unref (self); -} - -/** - * wp_proxy_augment_error: - * @self: the proxy - * @error: (transfer full): the error - * - * Reports an error that occured during the augment process - */ -void -wp_proxy_augment_error (WpProxy * self, GError * error) -{ - WpProxyPrivate *priv; - - g_return_if_fail (WP_IS_PROXY (self)); - - priv = wp_proxy_get_instance_private (self); - - /* steal the array to avoid recursion here */ - if (priv->augment_tasks->len > 0) { - guint i; - g_autoptr (GPtrArray) augment_tasks = - g_steal_pointer (&priv->augment_tasks); - priv->augment_tasks = g_ptr_array_new_with_free_func (g_object_unref); - - for (i = 0; i < augment_tasks->len; i++) { - GTask *task = g_ptr_array_index (augment_tasks, i); - g_task_return_error (task, g_error_copy (error)); - } - } - - g_error_free (error); -} - -WpProxyFeatures -wp_proxy_get_features (WpProxy * self) -{ - WpProxyPrivate *priv; - - g_return_val_if_fail (WP_IS_PROXY (self), 0); - - priv = wp_proxy_get_instance_private (self); - return priv->ft_ready; -} - -/** - * wp_proxy_get_core: - * @self: the proxy - * - * Returns: (transfer full): the core that created this proxy - */ -WpCore * -wp_proxy_get_core (WpProxy * self) -{ - WpProxyPrivate *priv; - - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - priv = wp_proxy_get_instance_private (self); - return g_weak_ref_get (&priv->core); -} - -guint32 -wp_proxy_get_global_permissions (WpProxy * self) -{ - WpProxyPrivate *priv; - - g_return_val_if_fail (WP_IS_PROXY (self), 0); - - priv = wp_proxy_get_instance_private (self); - return priv->global ? priv->global->permissions : 0; -} - -/** - * wp_proxy_get_global_properties: - * - * Returns: (transfer full): the global properties of the proxy - */ -WpProperties * -wp_proxy_get_global_properties (WpProxy * self) -{ - WpProxyPrivate *priv; - - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - priv = wp_proxy_get_instance_private (self); - if (!priv->global || !priv->global->properties) - return NULL; - return wp_properties_ref (priv->global->properties); -} - -struct pw_proxy * -wp_proxy_get_pw_proxy (WpProxy * self) -{ - WpProxyPrivate *priv; - - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - priv = wp_proxy_get_instance_private (self); - return priv->pw_proxy; -} - -/** - * wp_proxy_get_info: - * @self: the proxy - * - * Requires %WP_PROXY_FEATURE_INFO - * - * Returns: the pipewire info structure of this object - * (pw_node_info, pw_port_info, etc...) - */ -gconstpointer -wp_proxy_get_info (WpProxy * self) -{ - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_INFO); - - return (WP_PROXY_GET_CLASS (self)->get_info) ? - WP_PROXY_GET_CLASS (self)->get_info (self) : NULL; -} - -/** - * wp_proxy_get_properties: - * @self: the proxy - * - * Requires %WP_PROXY_FEATURE_INFO - * - * Returns: (transfer full): the pipewire properties of this object; - * normally these are the properties that are part of the info structure - */ -WpProperties * -wp_proxy_get_properties (WpProxy * self) -{ - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_INFO); - - return (WP_PROXY_GET_CLASS (self)->get_properties) ? - WP_PROXY_GET_CLASS (self)->get_properties (self) : NULL; -} - -/** - * wp_proxy_get_property: - * @self: the proxy - * @key: the property name - * - * Returns the value of a single pipewire property. This is the same as getting - * the whole properties structure with wp_proxy_get_properties() and accessing - * a single property with wp_properties_get(), but saves one call - * and having to clean up the #WpProperties reference count afterwards. - * - * The value is owned by the proxy, but it is guaranteed to stay alive - * until execution returns back to the event loop. - * - * Requires %WP_PROXY_FEATURE_INFO - * - * Returns: (transfer none) (nullable): the value of the pipewire property @key - * or %NULL if the property doesn't exist - */ -const gchar * -wp_proxy_get_property (WpProxy * self, const gchar * key) -{ - /* the proxy always keeps a ref to the data, so it's safe - to discard the ref count of the WpProperties */ - g_autoptr (WpProperties) props = NULL; - props = wp_proxy_get_properties (self); - return props ? wp_properties_get (props, key) : NULL; -} - -/** - * wp_proxy_get_param_info: - * @self: the proxy - * - * Returns the available parameters of this proxy. The return value is - * a variant of type `a{ss}`, where the key of each map entry is a spa param - * type id (the same ids that you can pass in wp_proxy_enum_params()) - * and the value is a string that can contain the following letters, - * each of them representing a flag: - * - `r`: the param is readable (`SPA_PARAM_INFO_READ`) - * - `w`: the param is writable (`SPA_PARAM_INFO_WRITE`) - * - `s`: the param was updated (`SPA_PARAM_INFO_SERIAL`) - * - * For params that are readable, you can query them with wp_proxy_enum_params() - * - * Params that are writable can be set with wp_proxy_set_param() - * - * Requires %WP_PROXY_FEATURE_INFO - * - * Returns: (transfer full) (nullable): a variant of type `a{ss}` or %NULL - * if the proxy does not support params at all - */ -GVariant * -wp_proxy_get_param_info (WpProxy * self) -{ - g_auto (GVariantBuilder) b = - G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_DICTIONARY); - guint n_params = 0; - struct spa_param_info *info; - - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_INFO); - - info = (WP_PROXY_GET_CLASS (self)->get_param_info) ? - WP_PROXY_GET_CLASS (self)->get_param_info (self, &n_params) : NULL; - if (!info || n_params == 0) - return NULL; - - for (guint i = 0; i < n_params; i++) { - const gchar *nick = NULL; - gchar flags[4]; - 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'; - if (info[i].flags & SPA_PARAM_INFO_SERIAL) - flags[flags_idx++] = 's'; - flags[flags_idx] = '\0'; - - g_variant_builder_add (&b, "{ss}", nick, flags); - } - - return g_variant_builder_end (&b); } /** @@ -838,300 +179,50 @@ guint32 wp_proxy_get_bound_id (WpProxy * self) { g_return_val_if_fail (WP_IS_PROXY (self), 0); + g_warn_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & + WP_PROXY_FEATURE_BOUND); WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_warn_if_fail (priv->ft_ready & WP_PROXY_FEATURE_BOUND); - return priv->pw_proxy ? pw_proxy_get_bound_id (priv->pw_proxy) : SPA_ID_INVALID; } -static void -wp_proxy_register_async_task (WpProxy * self, int seq, GTask * task) -{ - WpProxyPrivate *priv; - - g_return_if_fail (WP_IS_PROXY (self)); - g_return_if_fail (g_task_is_valid (task, self)); - - priv = wp_proxy_get_instance_private (self); - g_hash_table_insert (priv->async_tasks, GINT_TO_POINTER (seq), task); -} - -static GTask * -wp_proxy_find_async_task (WpProxy * self, int seq, gboolean steal) -{ - WpProxyPrivate *priv; - GTask *task = NULL; - - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - priv = wp_proxy_get_instance_private (self); - if (steal) - g_hash_table_steal_extended (priv->async_tasks, GINT_TO_POINTER (seq), - NULL, (gpointer *) &task); - else - task = g_hash_table_lookup (priv->async_tasks, GINT_TO_POINTER (seq)); - - return task; -} - -static void -enum_params_done (WpCore * core, GAsyncResult * res, gpointer data) -{ - int seq = GPOINTER_TO_INT (g_task_get_source_tag (G_TASK (data))); - WpProxy *proxy = g_task_get_source_object (G_TASK (data)); - g_autoptr (GTask) task = NULL; - g_autoptr (GError) error = NULL; - - /* finish the sync task */ - wp_core_sync_finish (core, res, &error); - - /* find the enum params task in the hash table to steal the reference */ - task = wp_proxy_find_async_task (proxy, seq, TRUE); - g_return_if_fail (task != NULL); - - 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); - } -} - -/** - * wp_proxy_enum_params: - * @self: the proxy - * @id: (nullable): the parameter id to enumerate or %NULL for all parameters - * @filter: (nullable): a param filter or %NULL - * @cancellable: (nullable): a cancellable for the async operation - * @callback: (scope async): a callback to call with the result - * @user_data: (closure): data to pass to @callback - * - * Enumerate object parameters. This will asynchronously return the result, - * or an error, by calling the given @callback. The result is going to - * be a #WpIterator containing #WpSpaPod objects, which can be retrieved - * with wp_proxy_enum_params_finish(). - */ -void -wp_proxy_enum_params (WpProxy * self, const gchar * id, WpSpaPod *filter, - GCancellable * cancellable, GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr (GTask) task = NULL; - guint32 id_num = 0; - int seq; - GPtrArray *params; - - g_return_if_fail (WP_IS_PROXY (self)); - - /* create task for enum_params */ - task = g_task_new (self, 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); - - if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &id_num, - NULL, NULL)) { - wp_critical_object (self, "invalid param id: %s", id); - return; - } - - /* call enum_params */ - seq = (WP_PROXY_GET_CLASS (self)->enum_params) ? - WP_PROXY_GET_CLASS (self)->enum_params (self, id_num, 0, -1, filter) : - -ENOTSUP; - if (G_UNLIKELY (seq < 0)) { - wp_message_object (self, "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; - } - g_task_set_source_tag (task, GINT_TO_POINTER (seq)); - wp_proxy_register_async_task (self, seq, g_object_ref (task)); - - /* call sync */ - g_autoptr (WpCore) core = wp_proxy_get_core (self); - wp_core_sync (core, cancellable, (GAsyncReadyCallback) enum_params_done, - task); -} - -/** - * wp_proxy_enum_params_finish: - * @self: the proxy - * @res: the async result - * @error: (out) (optional): the reported error of the operation, if any - * - * Returns: (transfer full) (nullable): an iterator to iterate over the - * collected params, or %NULL if the operation resulted in error; - * the items in the iterator are #WpSpaPod - */ -WpIterator * -wp_proxy_enum_params_finish (WpProxy * self, GAsyncResult * res, - GError ** error) -{ - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - g_return_val_if_fail (g_task_is_valid (res, self), 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); -} - /** - * wp_proxy_set_param: - * @self: the proxy - * @id: the parameter id to set - * @param: the parameter to set + * wp_proxy_get_pw_proxy: * - * Sets a parameter on the object. + * Returns: a pointer to the underlying `pw_proxy` object */ -void -wp_proxy_set_param (WpProxy * self, const gchar * id, WpSpaPod *param) -{ - guint32 id_num = 0; - gint ret; - - g_return_if_fail (WP_IS_PROXY (self)); - - if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &id_num, - NULL, NULL)) { - wp_critical_object (self, "invalid param id: %s", id); - return; - } - - ret = (WP_PROXY_GET_CLASS (self)->set_param) ? - WP_PROXY_GET_CLASS (self)->set_param (self, id_num, 0, param) : - -ENOTSUP; - if (G_UNLIKELY (ret < 0)) { - wp_message_object (self, "set_param failed: %s", spa_strerror (ret)); - } -} - -/** - * wp_proxy_iterate_prop_info: - * @self: the proxy - * - * Requires %WP_PROXY_FEATURE_PROPS - * - * Returns: (transfer full) (nullable): an iterator to iterate over the - * `SPA_PARAM_PropInfo` params, or %NULL if the object has no props; - * the items in the iterator are #WpSpaPod - */ -WpIterator * -wp_proxy_iterate_prop_info (WpProxy * self) -{ - g_return_val_if_fail (WP_IS_PROXY (self), NULL); - - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_return_val_if_fail (priv->ft_ready & WP_PROXY_FEATURE_PROPS, NULL); - - return wp_props_iterate_prop_info (priv->props); -} - -/** - * wp_proxy_get_prop: - * @self: the proxy - * @prop_name: the prop name - * - * Requires %WP_PROXY_FEATURE_PROPS - * - * Returns: (transfer full) (nullable): the spa pod containing the value - * of this prop, or %NULL if @prop_name does not exist on this proxy - */ -WpSpaPod * -wp_proxy_get_prop (WpProxy * self, const gchar * prop_name) +struct pw_proxy * +wp_proxy_get_pw_proxy (WpProxy * self) { g_return_val_if_fail (WP_IS_PROXY (self), NULL); WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_return_val_if_fail (priv->ft_ready & WP_PROXY_FEATURE_PROPS, NULL); - - return wp_props_get (priv->props, prop_name); + return priv->pw_proxy; } /** - * wp_proxy_set_prop: - * @self: the proxy - * @prop_name: the prop name - * @value: (transfer full): the new value for this prop, as a spa pod + * wp_proxy_set_pw_proxy: * - * Sets a single property in the `SPA_PARAM_Props` param of this object. + * Private method to be used by subclasses to set the `pw_proxy` pointer + * when it is available. This can be called only if there is no `pw_proxy` + * already set. Takes ownership of @proxy. */ void -wp_proxy_set_prop (WpProxy * self, const gchar * prop_name, WpSpaPod * value) +wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy) { g_return_if_fail (WP_IS_PROXY (self)); - g_return_if_fail (value != NULL); WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_return_if_fail (priv->ft_ready & WP_PROXY_FEATURE_PROPS); - - wp_props_set (priv->props, prop_name, value); -} -void -wp_proxy_handle_event_param (void * proxy, int seq, uint32_t id, - uint32_t index, uint32_t next, const struct spa_pod *param) -{ - WpProxy *self = WP_PROXY (proxy); - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - g_autoptr (WpSpaPod) w_param = wp_spa_pod_new_wrap_const (param); - GTask *task; - - /* if this param event was emited because of enum_params(), - * copy the param in the result array of that API */ - task = wp_proxy_find_async_task (self, seq, FALSE); - if (task) { - GPtrArray *array = g_task_get_task_data (task); - g_ptr_array_add (array, wp_spa_pod_copy (w_param)); - } - /* else consider this to be a prop update, either triggered from augment() - * or because we are subscribed to props */ - else if (priv->props) { - switch (id) { - case SPA_PARAM_PropInfo: - wp_trace_boxed (WP_TYPE_SPA_POD, w_param, - "storing PropInfo on " WP_OBJECT_FORMAT, WP_OBJECT_ARGS (self)); - wp_props_register_from_info (priv->props, g_steal_pointer (&w_param)); - break; - case SPA_PARAM_Props: - wp_trace_boxed (WP_TYPE_SPA_POD, w_param, - "storing Props on " WP_OBJECT_FORMAT, WP_OBJECT_ARGS (self)); - wp_props_store (priv->props, NULL, g_steal_pointer (&w_param)); - break; - default: - break; - } - } -} - -WpProps * -wp_proxy_get_props (WpProxy * self) -{ - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); - return priv->props; -} - -static void -propagate_prop_changed (WpProps * props, const gchar * name, WpProxy * self) -{ - /* only emit if FEATURE_PROPS is enabled, because users might call - wp_proxy_get_prop() in the handler and it will assert */ - if (wp_proxy_get_features (self) & WP_PROXY_FEATURE_PROPS) - g_signal_emit (self, wp_proxy_signals[SIGNAL_PROP_CHANGED], 0, name); -} + if (!proxy) + return; -void -wp_proxy_set_props (WpProxy * self, WpProps * props) -{ - WpProxyPrivate *priv = wp_proxy_get_instance_private (self); + g_return_if_fail (priv->pw_proxy == NULL); + priv->pw_proxy = proxy; - g_return_if_fail (priv->props == NULL); - priv->props = props; + pw_proxy_add_listener (priv->pw_proxy, &priv->listener, &proxy_events, + self); - g_signal_connect_object (props, "prop-changed", - G_CALLBACK (propagate_prop_changed), self, 0); + /* inform subclasses and listeners */ + g_signal_emit (self, signals[SIGNAL_PW_PROXY_CREATED], 0, priv->pw_proxy); } diff --git a/lib/wp/proxy.h b/lib/wp/proxy.h index 2b5d51d479684f3734564f5c3ea607ed0ffa2f61..e9ed203b7718013851680c65c0a5b9b9cb5311f2 100644 --- a/lib/wp/proxy.h +++ b/lib/wp/proxy.h @@ -1,7 +1,6 @@ /* WirePlumber * - * Copyright © 2019 Collabora Ltd. - * @author Julian Bouzas <julian.bouzas@collabora.com> + * Copyright © 2020 Collabora Ltd. * @author George Kiagiadakis <george.kiagiadakis@collabora.com> * * SPDX-License-Identifier: MIT @@ -10,50 +9,54 @@ #ifndef __WIREPLUMBER_PROXY_H__ #define __WIREPLUMBER_PROXY_H__ -#include <gio/gio.h> - -#include "spa-pod.h" -#include "properties.h" +#include "object.h" G_BEGIN_DECLS struct pw_proxy; -struct spa_param_info; -typedef struct _WpCore WpCore; /** * WpProxyFeatures: * - * Flags that specify functionality that is available on this class. - * Use wp_proxy_augment() to enable more features and wp_proxy_get_features() - * to find out which features are already enabled. - * - * Subclasses may also specify additional features that can be ORed with these - * ones and they can also be enabled with wp_proxy_augment(). + * Flags to be used as #WpObjectFeatures for #WpProxy subclasses. */ typedef enum { /*< flags >*/ /* standard features */ - WP_PROXY_FEATURE_PW_PROXY = (1 << 0), - WP_PROXY_FEATURE_INFO = (1 << 1), - WP_PROXY_FEATURE_BOUND = (1 << 2), + WP_PROXY_FEATURE_BOUND = (1 << 0), - /* param caching features */ - WP_PROXY_FEATURE_PROPS = (1 << 3), + /* WpPipewireObjectInterface */ + WP_PIPEWIRE_OBJECT_FEATURE_INFO = (1 << 4), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS = (1 << 5), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT = (1 << 6), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE = (1 << 7), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG = (1 << 8), + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE = (1 << 9), - WP_PROXY_FEATURE_LAST = (1 << 16), /*< skip >*/ + WP_PROXY_FEATURE_CUSTOM_START = (1 << 16), /*< skip >*/ } WpProxyFeatures; /** - * WP_PROXY_FEATURES_STANDARD: + * WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL: * - * A constant set of features that contains the standard features that are - * available in the #WpProxy class. The standard features are usually all - * enabled at once, even if not requested explicitly. It is a good practice, - * though, to enable only the features that you actually need. This leaves - * room for optimizations in the #WpProxy class. + * The minimal feature set for proxies implementing #WpPipewireObject. + * This is a subset of #WP_PIPEWIRE_OBJECT_FEATURES_ALL */ -#define WP_PROXY_FEATURES_STANDARD \ - (WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND) +static const WpObjectFeatures WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL = + (WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO); + +/** + * WP_PIPEWIRE_OBJECT_FEATURES_ALL: + * + * The complete common feature set for proxies implementing #WpPipewireObject. + * This is a subset of #WP_OBJECT_FEATURES_ALL + */ +static const WpObjectFeatures WP_PIPEWIRE_OBJECT_FEATURES_ALL = + (WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS | + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT | + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE | + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG | + WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE); /** * WP_TYPE_PROXY: @@ -62,7 +65,7 @@ typedef enum { /*< flags >*/ */ #define WP_TYPE_PROXY (wp_proxy_get_type ()) WP_API -G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, GObject) +G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, WpObject) /** * WpProxyClass: @@ -73,110 +76,28 @@ G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, GObject) */ struct _WpProxyClass { - GObjectClass parent_class; + WpObjectClass parent_class; const gchar * pw_iface_type; guint32 pw_iface_version; - void (*augment) (WpProxy *self, WpProxyFeatures features); - - gconstpointer (*get_info) (WpProxy * self); - WpProperties * (*get_properties) (WpProxy * self); - struct spa_param_info * (*get_param_info) (WpProxy * self, guint * n_params); - - gint (*enum_params) (WpProxy * self, guint32 id, guint32 start, guint32 num, - WpSpaPod * filter); - gint (*subscribe_params) (WpProxy * self, guint32 *ids, guint32 n_ids); - gint (*set_param) (WpProxy * self, guint32 id, guint32 flags, - WpSpaPod * param); - /* signals */ void (*pw_proxy_created) (WpProxy * self, struct pw_proxy * proxy); void (*pw_proxy_destroyed) (WpProxy * self); void (*bound) (WpProxy * self, guint32 id); - void (*prop_changed) (WpProxy * self, const gchar * prop_name); }; -WP_API -void wp_proxy_request_destroy (WpProxy * self); - -/* features API */ - -WP_API -void wp_proxy_augment (WpProxy *self, - WpProxyFeatures wanted_features, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data); - -WP_API -gboolean wp_proxy_augment_finish (WpProxy * self, GAsyncResult * res, - GError ** error); - -WP_API -WpProxyFeatures wp_proxy_get_features (WpProxy * self); - -/* the owner core */ - -WP_API -WpCore * wp_proxy_get_core (WpProxy * self); - -/* global object API */ - -WP_API -guint32 wp_proxy_get_global_permissions (WpProxy * self); - -WP_API -WpProperties * wp_proxy_get_global_properties (WpProxy * self); - -/* native pw_proxy object getter (requires FEATURE_PW_PROXY) */ - -WP_API -struct pw_proxy * wp_proxy_get_pw_proxy (WpProxy * self); - -/* native info structure + wrappers (requires FEATURE_INFO) */ - -WP_API -gconstpointer wp_proxy_get_info (WpProxy * self); - -WP_API -WpProperties * wp_proxy_get_properties (WpProxy * self); - -WP_API -const gchar * wp_proxy_get_property (WpProxy * self, const gchar * key); - -WP_API -GVariant * wp_proxy_get_param_info (WpProxy * self); - -/* the bound id (aka global id, requires FEATURE_BOUND) */ - WP_API guint32 wp_proxy_get_bound_id (WpProxy * self); -/* params API */ - -WP_API -void wp_proxy_enum_params (WpProxy * self, const gchar * id, WpSpaPod *filter, - GCancellable * cancellable, GAsyncReadyCallback callback, - gpointer user_data); - -WP_API -WpIterator * wp_proxy_enum_params_finish (WpProxy * self, GAsyncResult * res, - GError ** error); - -WP_API -void wp_proxy_set_param (WpProxy * self, const gchar * id, WpSpaPod * param); - -/* PARAM_PropInfo - PARAM_Props */ - WP_API -WpIterator * wp_proxy_iterate_prop_info (WpProxy * self); +struct pw_proxy * wp_proxy_get_pw_proxy (WpProxy * self); -WP_API -WpSpaPod * wp_proxy_get_prop (WpProxy * self, const gchar * prop_name); +/* for subclasses only */ WP_API -void wp_proxy_set_prop (WpProxy * self, const gchar * prop_name, - WpSpaPod * value); +void wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy); G_END_DECLS diff --git a/lib/wp/session-item.c b/lib/wp/session-item.c index c8636b449e42053fa1436258d4e803e760c774f0..ca2154420ea3aa2bd36c4e0109283b98dafd1031 100644 --- a/lib/wp/session-item.c +++ b/lib/wp/session-item.c @@ -222,7 +222,7 @@ wp_session_item_default_export_get_next_step (WpSessionItem * self, g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), WP_TRANSITION_STEP_ERROR); g_return_val_if_fail (priv->impl_streams, WP_TRANSITION_STEP_ERROR); - /* go to next step only when all impl proxies are augmented */ + /* go to next step only when all impl proxies are activated */ if (g_hash_table_size (priv->impl_streams) == wp_si_endpoint_get_n_streams (WP_SI_ENDPOINT (self))) return EXPORT_STEP_ENDPOINT_FT_STREAMS; @@ -245,14 +245,14 @@ wp_session_item_default_export_get_next_step (WpSessionItem * self, } static void -on_export_proxy_augmented (WpProxy * proxy, GAsyncResult * res, gpointer data) +on_export_proxy_activated (WpObject * proxy, GAsyncResult * res, gpointer data) { WpTransition *transition = WP_TRANSITION (data); WpSessionItem *self = wp_transition_get_source_object (transition); WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); g_autoptr (GError) error = NULL; - if (!wp_proxy_augment_finish (proxy, res, &error)) { + if (!wp_object_activate_finish (proxy, res, &error)) { wp_transition_return_error (transition, g_steal_pointer (&error)); return; } @@ -266,7 +266,7 @@ on_export_proxy_augmented (WpProxy * proxy, GAsyncResult * res, gpointer data) g_hash_table_insert (priv->impl_streams, si_stream, g_object_ref (proxy)); } - wp_debug_object (self, "export proxy " WP_OBJECT_FORMAT " augmented", + wp_debug_object (self, "export proxy " WP_OBJECT_FORMAT " activated", WP_OBJECT_ARGS (proxy)); wp_transition_advance (transition); @@ -293,10 +293,10 @@ on_export_proxy_destroyed_deferred (WpSessionItem * self) } static void -on_export_proxy_destroyed (WpProxy * proxy, gpointer data) +on_export_proxy_destroyed (WpObject * proxy, gpointer data) { WpSessionItem *self = WP_SESSION_ITEM (data); - g_autoptr (WpCore) core = wp_proxy_get_core (proxy); + g_autoptr (WpCore) core = wp_object_get_core (proxy); if (core) wp_core_idle_add_closure (core, NULL, g_cclosure_new_object ( @@ -309,15 +309,15 @@ wp_session_item_default_export_execute_step (WpSessionItem * self, { WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); g_autoptr (WpSession) session = g_weak_ref_get (&priv->session); - g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (session)); + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (session)); switch (step) { case EXPORT_STEP_ENDPOINT: priv->impl_endpoint = wp_impl_endpoint_new (core, WP_SI_ENDPOINT (self)); - wp_proxy_augment (WP_PROXY (priv->impl_endpoint), - WP_PROXY_FEATURES_STANDARD, NULL, - (GAsyncReadyCallback) on_export_proxy_augmented, + wp_object_activate (WP_OBJECT (priv->impl_endpoint), + WP_PIPEWIRE_OBJECT_FEATURES_ALL, NULL, + (GAsyncReadyCallback) on_export_proxy_activated, transition); break; @@ -333,33 +333,33 @@ wp_session_item_default_export_execute_step (WpSessionItem * self, WpImplEndpointStream *impl_stream = wp_impl_endpoint_stream_new (core, stream); - wp_proxy_augment (WP_PROXY (impl_stream), - WP_PROXY_FEATURES_STANDARD, NULL, - (GAsyncReadyCallback) on_export_proxy_augmented, + wp_object_activate (WP_OBJECT (impl_stream), + WP_OBJECT_FEATURES_ALL, NULL, + (GAsyncReadyCallback) on_export_proxy_activated, transition); /* the augment task holds a ref; object will be added to - priv->impl_streams when augmented */ + priv->impl_streams when activated */ g_object_unref (impl_stream); } break; } case EXPORT_STEP_ENDPOINT_FT_STREAMS: /* add feature streams only after the streams are exported, otherwise - the endpoint will never be augmented in the first place (because it + the endpoint will never be activated in the first place (because it internally waits for the streams to be ready) */ - wp_proxy_augment (WP_PROXY (priv->impl_endpoint), + wp_object_activate (WP_OBJECT (priv->impl_endpoint), WP_ENDPOINT_FEATURE_STREAMS, NULL, - (GAsyncReadyCallback) on_export_proxy_augmented, + (GAsyncReadyCallback) on_export_proxy_activated, transition); break; case EXPORT_STEP_LINK: priv->impl_link = wp_impl_endpoint_link_new (core, WP_SI_LINK (self)); - wp_proxy_augment (WP_PROXY (priv->impl_link), - WP_PROXY_FEATURES_STANDARD, NULL, - (GAsyncReadyCallback) on_export_proxy_augmented, + wp_object_activate (WP_OBJECT (priv->impl_link), + WP_OBJECT_FEATURES_ALL, NULL, + (GAsyncReadyCallback) on_export_proxy_activated, transition); break; diff --git a/lib/wp/session.c b/lib/wp/session.c index a2d0f74cd1fadb4d687c740d6a5477d05bce00d4..a7dda9aabadb0df01ef2147d96cc3a19fd2d7527 100644 --- a/lib/wp/session.c +++ b/lib/wp/session.c @@ -7,37 +7,21 @@ */ /** - * SECTION: WpSession - * - * The #WpSession class allows accessing the properties and methods of a - * PipeWire session object (`struct pw_session` from the session-manager - * extension). - * - * A #WpSession is constructed internally when a new session appears on the - * PipeWire registry and it is made available through the #WpObjectManager API. - * - * A #WpImplSession allows implementing a session and exporting it to PipeWire, - * which is done by augmenting the #WpImplSession with %WP_PROXY_FEATURE_BOUND. + * SECTION: session + * @title: PipeWire Session */ #define G_LOG_DOMAIN "wp-session" #include "session.h" -#include "spa-type.h" -#include "spa-pod.h" -#include "debug.h" -#include "private.h" #include "error.h" #include "wpenums.h" +#include "private.h" +#include "private/pipewire-object-mixin.h" -#include <pipewire/pipewire.h> #include <pipewire/extensions/session-manager.h> #include <pipewire/extensions/session-manager/introspect-funcs.h> -#include <spa/pod/builder.h> -#include <spa/pod/parser.h> -#include <spa/pod/filter.h> - enum { SIGNAL_DEFAULT_ENDPOINT_CHANGED, SIGNAL_ENDPOINTS_CHANGED, @@ -47,8 +31,6 @@ enum { static guint32 signals[N_SIGNALS] = {0}; -/* WpSession */ - typedef struct _WpSessionPrivate WpSessionPrivate; struct _WpSessionPrivate { @@ -58,36 +40,34 @@ struct _WpSessionPrivate struct spa_hook listener; WpObjectManager *endpoints_om; WpObjectManager *links_om; - gboolean ft_endpoints_requested; - gboolean ft_links_requested; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpSession, wp_session, WP_TYPE_PROXY) +static void wp_session_pipewire_object_interface_init (WpPipewireObjectInterface * iface); -static void -wp_session_init (WpSession * self) -{ -} +/** + * WpSession: + * + * The #WpSession class allows accessing the properties and methods of a + * PipeWire session object (`struct pw_session` from the session-manager + * extension). + * + * A #WpSession is constructed internally when a new session appears on the + * PipeWire registry and it is made available through the #WpObjectManager API. + */ +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)); static void -wp_session_finalize (GObject * object) +wp_session_init (WpSession * self) { - WpSession *self = WP_SESSION (object); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - g_clear_object (&priv->endpoints_om); - g_clear_object (&priv->links_om); - g_clear_pointer (&priv->info, pw_session_info_free); - g_clear_pointer (&priv->properties, wp_properties_unref); - - G_OBJECT_CLASS (wp_session_parent_class)->finalize (object); } static void wp_session_on_endpoints_om_installed (WpObjectManager *endpoints_om, WpSession * self) { - wp_proxy_set_feature_ready (WP_PROXY (self), WP_SESSION_FEATURE_ENDPOINTS); + wp_object_update_features (WP_OBJECT (self), WP_SESSION_FEATURE_ENDPOINTS, 0); } static void @@ -100,7 +80,7 @@ wp_session_emit_endpoints_changed (WpObjectManager *endpoints_om, static void wp_session_on_links_om_installed (WpObjectManager *links_om, WpSession * self) { - wp_proxy_set_feature_ready (WP_PROXY (self), WP_SESSION_FEATURE_LINKS); + wp_object_update_features (WP_OBJECT (self), WP_SESSION_FEATURE_LINKS, 0); } static void @@ -110,20 +90,14 @@ wp_session_emit_links_changed (WpObjectManager *links_om, WpSession * self) } static void -wp_session_ensure_features_endpoints_links (WpSession * self, guint32 bound_id) +wp_session_enable_features_endpoints_links (WpSession * self, + WpObjectFeatures missing) { WpSessionPrivate *priv = wp_session_get_instance_private (self); - WpProxyFeatures ft = wp_proxy_get_features (WP_PROXY (self)); - g_autoptr (WpCore) core = NULL; - - if (!(ft & WP_PROXY_FEATURE_BOUND)) - return; + g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); + guint32 bound_id = wp_proxy_get_bound_id (WP_PROXY (self)); - core = wp_proxy_get_core (WP_PROXY (self)); - if (!bound_id) - bound_id = wp_proxy_get_bound_id (WP_PROXY (self)); - - if (priv->ft_endpoints_requested && !priv->endpoints_om) { + if (missing & WP_SESSION_FEATURE_ENDPOINTS) { wp_debug_object (self, "enabling WP_SESSION_FEATURE_ENDPOINTS, bound_id:%u", bound_id); @@ -139,8 +113,8 @@ wp_session_ensure_features_endpoints_links (WpSession * self, guint32 bound_id) WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_SESSION_ID, "=u", bound_id, NULL); - wp_object_manager_request_proxy_features (priv->endpoints_om, - WP_TYPE_ENDPOINT, WP_ENDPOINT_FEATURES_STANDARD); + wp_object_manager_request_object_features (priv->endpoints_om, + WP_TYPE_ENDPOINT, WP_OBJECT_FEATURES_ALL); g_signal_connect_object (priv->endpoints_om, "installed", G_CALLBACK (wp_session_on_endpoints_om_installed), self, 0); @@ -150,7 +124,7 @@ wp_session_ensure_features_endpoints_links (WpSession * self, guint32 bound_id) wp_core_install_object_manager (core, priv->endpoints_om); } - if (priv->ft_links_requested && !priv->links_om) { + if (missing & WP_SESSION_FEATURE_LINKS) { wp_debug_object (self, "enabling WP_SESSION_FEATURE_LINKS, bound_id:%u", bound_id); @@ -166,8 +140,8 @@ wp_session_ensure_features_endpoints_links (WpSession * self, guint32 bound_id) WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_SESSION_ID, "=u", bound_id, NULL); - wp_object_manager_request_proxy_features (priv->links_om, - WP_TYPE_ENDPOINT_LINK, WP_PROXY_FEATURES_STANDARD); + wp_object_manager_request_object_features (priv->links_om, + WP_TYPE_ENDPOINT_LINK, WP_OBJECT_FEATURES_ALL); g_signal_connect_object (priv->links_om, "installed", G_CALLBACK (wp_session_on_links_om_installed), self, 0); @@ -178,57 +152,81 @@ wp_session_ensure_features_endpoints_links (WpSession * self, guint32 bound_id) } } -static gconstpointer -wp_session_get_info (WpProxy * proxy) +static WpObjectFeatures +wp_session_get_supported_features (WpObject * object) { - WpSession *self = WP_SESSION (proxy); + WpSession *self = WP_SESSION (object); WpSessionPrivate *priv = wp_session_get_instance_private (self); - return priv->info; + 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); } -static WpProperties * -wp_session_get_properties (WpProxy * proxy) -{ - WpSession *self = WP_SESSION (proxy); - WpSessionPrivate *priv = wp_session_get_instance_private (self); - - return wp_properties_ref (priv->properties); -} +enum { + STEP_CHILDREN = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START, +}; -static struct spa_param_info * -wp_session_get_param_info (WpProxy * proxy, guint * n_params) +static guint +wp_session_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpSession *self = WP_SESSION (proxy); - WpSessionPrivate *priv = wp_session_get_instance_private (self); + step = wp_pipewire_object_mixin_activate_get_next_step (object, transition, + step, missing); - *n_params = priv->info->n_params; - return priv->info->params; -} + /* 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; -static gint -wp_session_enum_params (WpProxy * self, guint32 id, guint32 start, - guint32 num, WpSpaPod * filter) -{ - WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); - return pw_session_enum_params (priv->iface, 0, id, - start, num, filter ? wp_spa_pod_get_spa_pod (filter) : NULL); + return step; } -static gint -wp_session_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids) -{ - WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); - return pw_session_subscribe_params (priv->iface, ids, n_ids); +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); + 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; + } } -static gint -wp_session_set_param (WpProxy * self, guint32 id, guint32 flags, - WpSpaPod *param) +static void +wp_session_deactivate (WpObject * object, WpObjectFeatures features) { - WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); - return pw_session_set_param (priv->iface, id, flags, - wp_spa_pod_get_spa_pod (param)); + WpSession *self = WP_SESSION (object); + WpSessionPrivate *priv = wp_session_get_instance_private (self); + + wp_pipewire_object_mixin_deactivate (object, features); + + if (features & WP_SESSION_FEATURE_ENDPOINTS) { + g_clear_object (&priv->endpoints_om); + wp_object_update_features (object, 0, WP_SESSION_FEATURE_ENDPOINTS); + } + if (features & WP_SESSION_FEATURE_LINKS) { + g_clear_object (&priv->links_om); + wp_object_update_features (object, 0, WP_SESSION_FEATURE_LINKS); + } + + WP_OBJECT_CLASS (wp_session_parent_class)->deactivate (object, features); } static void @@ -244,20 +242,17 @@ session_event_info (void *data, const struct pw_session_info *info) priv->properties = wp_properties_new_wrap_dict (priv->info->props); } - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - g_object_notify (G_OBJECT (self), "info"); - - if (info->change_mask & PW_SESSION_CHANGE_MASK_PROPS) - g_object_notify (G_OBJECT (self), "properties"); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); - if (info->change_mask & PW_SESSION_CHANGE_MASK_PARAMS) - g_object_notify (G_OBJECT (self), "param-info"); + 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_proxy_handle_event_param, + .param = wp_pipewire_object_mixin_handle_event_param, }; static void @@ -271,67 +266,44 @@ wp_session_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) } static void -wp_session_bound (WpProxy * proxy, guint32 id) -{ - WpSession *self = WP_SESSION (proxy); - wp_session_ensure_features_endpoints_links (self, id); -} - -static void -wp_session_augment (WpProxy * proxy, WpProxyFeatures features) +wp_session_pw_proxy_destroyed (WpProxy * proxy) { WpSession *self = WP_SESSION (proxy); WpSessionPrivate *priv = wp_session_get_instance_private (self); - /* call the parent impl first to ensure we have a pw proxy if necessary */ - WP_PROXY_CLASS (wp_session_parent_class)->augment (proxy, features); - - if (features & (WP_SESSION_FEATURE_ENDPOINTS | WP_SESSION_FEATURE_LINKS)) { - priv->ft_endpoints_requested = (features & WP_SESSION_FEATURE_ENDPOINTS); - priv->ft_links_requested = (features & WP_SESSION_FEATURE_LINKS); - wp_session_ensure_features_endpoints_links (self, 0); - } -} - -static void -wp_session_prop_changed (WpProxy * proxy, const gchar * prop) -{ - WpSession *self = WP_SESSION (proxy); + g_clear_pointer (&priv->properties, wp_properties_unref); + g_clear_pointer (&priv->info, pw_session_info_free); + 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); - if (g_strcmp0 (prop, "Wp:defaultSink") == 0) { - g_signal_emit (self, signals[SIGNAL_DEFAULT_ENDPOINT_CHANGED], 0, - WP_DIRECTION_INPUT, - wp_session_get_default_endpoint (self, WP_DIRECTION_INPUT)); - } - else if (g_strcmp0 (prop, "Wp:defaultSource") == 0) { - g_signal_emit (self, signals[SIGNAL_DEFAULT_ENDPOINT_CHANGED], 0, - WP_DIRECTION_OUTPUT, - wp_session_get_default_endpoint (self, WP_DIRECTION_OUTPUT)); - } + wp_pipewire_object_mixin_deactivate (WP_OBJECT (proxy), + WP_OBJECT_FEATURES_ALL); } static void wp_session_class_init (WpSessionClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - object_class->finalize = wp_session_finalize; + object_class->get_property = wp_pipewire_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_execute_step = wp_session_activate_execute_step; + wpobject_class->deactivate = wp_session_deactivate; proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Session; proxy_class->pw_iface_version = PW_VERSION_SESSION; - - proxy_class->augment = wp_session_augment; - proxy_class->get_info = wp_session_get_info; - proxy_class->get_properties = wp_session_get_properties; - proxy_class->get_param_info = wp_session_get_param_info; - proxy_class->enum_params = wp_session_enum_params; - proxy_class->subscribe_params = wp_session_subscribe_params; - proxy_class->set_param = wp_session_set_param; - proxy_class->pw_proxy_created = wp_session_pw_proxy_created; - proxy_class->bound = wp_session_bound; - proxy_class->prop_changed = wp_session_prop_changed; + proxy_class->pw_proxy_destroyed = wp_session_pw_proxy_destroyed; + + wp_pipewire_object_mixin_class_override_properties (object_class); /** * WpSession::default-endpoint-changed: @@ -371,87 +343,80 @@ wp_session_class_init (WpSessionClass * klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } -/** - * wp_session_get_name: - * @self: the session - * - * Returns: (transfer none): the (unique) name of the session - */ -const gchar * -wp_session_get_name (WpSession * self) +static gconstpointer +wp_session_get_native_info (WpPipewireObject * obj) { - g_return_val_if_fail (WP_IS_SESSION (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & - WP_PROXY_FEATURE_INFO, NULL); + 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_get (priv->properties, "session.name"); + + return wp_properties_ref (priv->properties); } -/** - * wp_session_get_default_endpoint: - * @self: the session - * @direction: the endpoint direction - * - * Returns: the bound id of the default endpoint of this @direction - */ -guint32 -wp_session_get_default_endpoint (WpSession * self, WpDirection direction) +static GVariant * +wp_session_get_param_info (WpPipewireObject * obj) { - g_autoptr (WpSpaPod) pod = NULL; - const gchar *id_name = NULL; - gint32 value; - - g_return_val_if_fail (WP_IS_SESSION (self), SPA_ID_INVALID); - - switch (direction) { - case WP_DIRECTION_INPUT: - id_name = "Wp:defaultSink"; - break; - case WP_DIRECTION_OUTPUT: - id_name = "Wp:defaultSource"; - break; - default: - g_return_val_if_reached (SPA_ID_INVALID); - break; - } + WpSession *self = WP_SESSION (obj); + WpSessionPrivate *priv = wp_session_get_instance_private (self); - pod = wp_proxy_get_prop (WP_PROXY (self), id_name); - if (pod && wp_spa_pod_get_int (pod, &value)) - return (guint32) value; - return 0; + 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, + WpSpaPod * param) +{ + wp_pipewire_object_mixin_set_param (pw_session, obj, id, param); +} + +static void +wp_session_pipewire_object_interface_init ( + WpPipewireObjectInterface * 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; + 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; } /** - * wp_session_set_default_endpoint: + * wp_session_get_name: * @self: the session - * @direction: the endpoint direction - * @id: the bound id of the endpoint to set as the default for this @direction * - * Sets the default endpoint for this @direction to be the one identified - * with @id + * Requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO + * + * Returns: (transfer none): the (unique) name of the session */ -void -wp_session_set_default_endpoint (WpSession * self, WpDirection direction, - guint32 id) +const gchar * +wp_session_get_name (WpSession * self) { - const gchar *id_name = NULL; - - g_return_if_fail (WP_IS_SESSION (self)); - - switch (direction) { - case WP_DIRECTION_INPUT: - id_name = "Wp:defaultSink"; - break; - case WP_DIRECTION_OUTPUT: - id_name = "Wp:defaultSource"; - break; - default: - g_return_if_reached (); - break; - } + g_return_val_if_fail (WP_IS_SESSION (self), NULL); + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & + WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL); - wp_proxy_set_prop (WP_PROXY (self), id_name, wp_spa_pod_new_int (id)); + WpSessionPrivate *priv = wp_session_get_instance_private (self); + return wp_properties_get (priv->properties, "session.name"); } /** @@ -466,7 +431,7 @@ guint wp_session_get_n_endpoints (WpSession * self) { g_return_val_if_fail (WP_IS_SESSION (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_ENDPOINTS, 0); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -486,7 +451,7 @@ WpIterator * wp_session_iterate_endpoints (WpSession * self) { g_return_val_if_fail (WP_IS_SESSION (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_ENDPOINTS, NULL); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -532,7 +497,7 @@ wp_session_iterate_endpoints_filtered_full (WpSession * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_SESSION (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_ENDPOINTS, NULL); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -577,7 +542,7 @@ WpEndpoint * wp_session_lookup_endpoint_full (WpSession * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_SESSION (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_ENDPOINTS, NULL); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -597,7 +562,7 @@ guint wp_session_get_n_links (WpSession * self) { g_return_val_if_fail (WP_IS_SESSION (self), 0); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_LINKS, 0); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -617,7 +582,7 @@ WpIterator * wp_session_iterate_links (WpSession * self) { g_return_val_if_fail (WP_IS_SESSION (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_LINKS, NULL); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -663,7 +628,7 @@ wp_session_iterate_links_filtered_full (WpSession * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_SESSION (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_LINKS, NULL); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -708,7 +673,7 @@ WpEndpointLink * wp_session_lookup_link_full (WpSession * self, WpObjectInterest * interest) { g_return_val_if_fail (WP_IS_SESSION (self), NULL); - g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) & + g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) & WP_SESSION_FEATURE_LINKS, NULL); WpSessionPrivate *priv = wp_session_get_instance_private (self); @@ -726,9 +691,14 @@ struct _WpImplSession struct spa_interface iface; struct spa_hook_list hooks; struct pw_session_info info; - gboolean subscribed; }; +/** + * 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,...) \ @@ -738,11 +708,6 @@ G_DEFINE_TYPE (WpImplSession, wp_impl_session, WP_TYPE_SESSION) #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 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, @@ -767,56 +732,12 @@ impl_enum_params (void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { - WpImplSession *self = WP_IMPL_SESSION (object); - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf)); - struct spa_pod *result; - guint count = 0; - WpProps *props = wp_proxy_get_props (WP_PROXY (self)); - - switch (id) { - case SPA_PARAM_PropInfo: { - g_autoptr (WpIterator) params = wp_props_iterate_prop_info (props); - g_auto (GValue) item = G_VALUE_INIT; - guint i = 0; - - for (; wp_iterator_next (params, &item); g_value_unset (&item), i++) { - WpSpaPod *pod = g_value_get_boxed (&item); - const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod); - if (spa_pod_filter (&b, &result, param, filter) == 0) { - pw_session_emit_param (&self->hooks, seq, id, i, i+1, result); - if (++count == num) - break; - } - } - break; - } - case SPA_PARAM_Props: { - if (start == 0) { - g_autoptr (WpSpaPod) pod = wp_props_get_all (props); - const struct spa_pod *param = wp_spa_pod_get_spa_pod (pod); - if (spa_pod_filter (&b, &result, param, filter) == 0) { - pw_session_emit_param (&self->hooks, seq, id, 0, 1, result); - } - } - break; - } - default: - return -ENOENT; - } - - return 0; + return -ENOENT; } static int impl_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids) { - WpImplSession *self = WP_IMPL_SESSION (object); - for (guint i = 0; i < n_ids; i++) { - if (ids[i] == SPA_PARAM_Props) - self->subscribed = TRUE; - impl_enum_params (self, 1, ids[i], 0, UINT32_MAX, NULL); - } return 0; } @@ -824,14 +745,7 @@ static int impl_set_param (void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { - WpImplSession *self = WP_IMPL_SESSION (object); - - if (id != SPA_PARAM_Props) - return -ENOENT; - - WpProps *props = wp_proxy_get_props (WP_PROXY (self)); - wp_props_set (props, NULL, wp_spa_pod_new_wrap_const (param)); - return 0; + return -ENOENT; } static const struct pw_session_methods impl_session = { @@ -848,7 +762,6 @@ 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)); - WpProps *props; self->iface = SPA_INTERFACE_INIT ( PW_TYPE_INTERFACE_Session, @@ -863,21 +776,9 @@ wp_impl_session_init (WpImplSession * self) self->info.version = PW_VERSION_SESSION_INFO; self->info.props = (struct spa_dict *) wp_properties_peek_dict (priv->properties); - self->info.params = impl_param_info; - self->info.n_params = SPA_N_ELEMENTS (impl_param_info); + self->info.params = NULL; + self->info.n_params = 0; priv->info = &self->info; - g_object_notify (G_OBJECT (self), "info"); - - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - - /* prepare props */ - props = wp_props_new (WP_PROPS_MODE_STORE, WP_PROXY (self)); - wp_props_register (props, - "Wp:defaultSource", "Default Source", wp_spa_pod_new_int (0)); - wp_props_register (props, - "Wp:defaultSink", "Default Sink", wp_spa_pod_new_int (0)); - wp_proxy_set_props (WP_PROXY (self), props); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_PROPS); } static void @@ -885,32 +786,30 @@ wp_impl_session_finalize (GObject * object) { WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (object)); - /* set to NULL to prevent parent's finalize from calling free() on it */ - priv->info = NULL; + g_clear_pointer (&priv->properties, wp_properties_unref); G_OBJECT_CLASS (wp_impl_session_parent_class)->finalize (object); } static void -wp_impl_session_augment (WpProxy * proxy, WpProxyFeatures features) +wp_impl_session_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * transition, guint step, + WpObjectFeatures missing) { - WpImplSession *self = WP_IMPL_SESSION (proxy); + WpImplSession *self = WP_IMPL_SESSION (object); WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); - /* PW_PROXY depends on BOUND */ - if (features & WP_PROXY_FEATURE_PW_PROXY) - features |= WP_PROXY_FEATURE_BOUND; - - if (features & WP_PROXY_FEATURE_BOUND) { - g_autoptr (WpCore) core = wp_proxy_get_core (proxy); + switch (step) { + case WP_PIPEWIRE_OBJECT_MIXIN_STEP_BIND: { + g_autoptr (WpCore) core = wp_object_get_core (object); struct pw_core *pw_core = wp_core_get_pw_core (core); /* no pw_core -> we are not connected */ if (!pw_core) { - wp_proxy_augment_error (proxy, g_error_new (WP_DOMAIN_LIBRARY, - WP_LIBRARY_ERROR_OPERATION_FAILED, - "The WirePlumber core is not connected; " - "object cannot be exported to PipeWire")); + wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( + WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, + "The WirePlumber core is not connected; " + "object cannot be exported to PipeWire")); return; } @@ -919,44 +818,49 @@ wp_impl_session_augment (WpProxy * proxy, WpProxyFeatures features) wp_properties_set (priv->properties, PW_KEY_CLIENT_ID, NULL); wp_properties_set (priv->properties, PW_KEY_FACTORY_ID, NULL); - wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core, + 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)); - } - if (features & (WP_SESSION_FEATURE_ENDPOINTS | WP_SESSION_FEATURE_LINKS)) { - priv->ft_endpoints_requested = (features & WP_SESSION_FEATURE_ENDPOINTS); - priv->ft_links_requested = (features & WP_SESSION_FEATURE_LINKS); - wp_session_ensure_features_endpoints_links (WP_SESSION (self), 0); + wp_object_update_features (WP_OBJECT (self), + WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); + break; + } + default: + WP_OBJECT_CLASS (wp_impl_session_parent_class)-> + activate_execute_step (object, transition, step, missing); + break; } } static void -wp_impl_session_prop_changed (WpProxy * proxy, const gchar * prop_name) +wp_impl_session_pw_proxy_destroyed (WpProxy * proxy) { WpImplSession *self = WP_IMPL_SESSION (proxy); + WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); - /* notify subscribers */ - if (self->subscribed) - impl_enum_params (self, 1, SPA_PARAM_Props, 0, UINT32_MAX, NULL); - - WP_PROXY_CLASS (wp_impl_session_parent_class)->prop_changed (proxy, prop_name); + 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); } static void wp_impl_session_class_init (WpImplSessionClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; object_class->finalize = wp_impl_session_finalize; - proxy_class->augment = wp_impl_session_augment; - proxy_class->enum_params = NULL; - proxy_class->subscribe_params = NULL; + wpobject_class->activate_execute_step = wp_impl_session_activate_execute_step; + proxy_class->pw_proxy_created = NULL; - proxy_class->prop_changed = wp_impl_session_prop_changed; + proxy_class->pw_proxy_destroyed = wp_impl_session_pw_proxy_destroyed; } /** @@ -1001,7 +905,7 @@ wp_impl_session_set_property (WpImplSession * self, g_object_notify (G_OBJECT (self), "properties"); /* update only after the session has been exported */ - if (wp_proxy_get_features (WP_PROXY (self)) & WP_PROXY_FEATURE_BOUND) { + 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; @@ -1034,7 +938,7 @@ wp_impl_session_update_properties (WpImplSession * self, g_object_notify (G_OBJECT (self), "properties"); /* update only after the session has been exported */ - if (wp_proxy_get_features (WP_PROXY (self)) & WP_PROXY_FEATURE_BOUND) { + 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; diff --git a/lib/wp/session.h b/lib/wp/session.h index 00db81be286bedcd630b545ecdefbd1aa3edd81a..a5eb590ac6b1acdeeafa06f9dea085b428d31930 100644 --- a/lib/wp/session.h +++ b/lib/wp/session.h @@ -9,7 +9,7 @@ #ifndef __WIREPLUMBER_SESSION_H__ #define __WIREPLUMBER_SESSION_H__ -#include "proxy.h" +#include "global-proxy.h" #include "endpoint.h" #include "endpoint-link.h" @@ -27,22 +27,10 @@ G_BEGIN_DECLS * An extension of #WpProxyFeatures */ typedef enum { /*< flags >*/ - WP_SESSION_FEATURE_ENDPOINTS = (WP_PROXY_FEATURE_LAST << 0), - WP_SESSION_FEATURE_LINKS = (WP_PROXY_FEATURE_LAST << 1), + WP_SESSION_FEATURE_ENDPOINTS = (WP_PROXY_FEATURE_CUSTOM_START << 0), + WP_SESSION_FEATURE_LINKS = (WP_PROXY_FEATURE_CUSTOM_START << 1), } WpSessionFeatures; -/** - * WP_SESSION_FEATURES_STANDARD: - * - * A constant set of features that contains the standard features that are - * available in the #WpSession class. - */ -#define WP_SESSION_FEATURES_STANDARD \ - (WP_PROXY_FEATURES_STANDARD | \ - WP_PROXY_FEATURE_PROPS | \ - WP_SESSION_FEATURE_ENDPOINTS | \ - WP_SESSION_FEATURE_LINKS) - /** * WP_TYPE_SESSION: * @@ -50,24 +38,16 @@ typedef enum { /*< flags >*/ */ #define WP_TYPE_SESSION (wp_session_get_type ()) WP_API -G_DECLARE_DERIVABLE_TYPE (WpSession, wp_session, WP, SESSION, WpProxy) +G_DECLARE_DERIVABLE_TYPE (WpSession, wp_session, WP, SESSION, WpGlobalProxy) struct _WpSessionClass { - WpProxyClass parent_class; + WpGlobalProxyClass parent_class; }; WP_API const gchar * wp_session_get_name (WpSession * self); -WP_API -guint32 wp_session_get_default_endpoint (WpSession * self, - WpDirection direction); - -WP_API -void wp_session_set_default_endpoint (WpSession * self, - WpDirection direction, guint32 id); - /* endpoints */ WP_API diff --git a/lib/wp/transition.c b/lib/wp/transition.c index 0f69f51640f107f2a0e3af7fcc2edfb7acd17917..5ee1ea28ed6c9cfbf2c5f3dcfe7c084ed42b3444 100644 --- a/lib/wp/transition.c +++ b/lib/wp/transition.c @@ -433,6 +433,9 @@ wp_transition_advance (WpTransition * self) /* find the next step */ next_step = WP_TRANSITION_GET_CLASS (self)->get_next_step (self, priv->step); + wp_trace_object (priv->source_object, "transition: %d -> %d", priv->step, + next_step); + if (next_step == WP_TRANSITION_STEP_ERROR) { /* return error if the callback didn't do it already */ if (G_UNLIKELY (!priv->error)) { @@ -454,6 +457,8 @@ wp_transition_advance (WpTransition * self) if (next_step == priv->step) return; + wp_trace_object (priv->source_object, "transition: execute %d", next_step); + /* execute the next step */ priv->step = next_step; WP_TRANSITION_GET_CLASS (self)->execute_step (self, priv->step); @@ -516,5 +521,9 @@ wp_transition_finish (GAsyncResult * res, GError ** error) g_propagate_error (error, priv->error); priv->error = NULL; } + + wp_trace_object (priv->source_object, "transition: finished %s", + (priv->step == WP_TRANSITION_STEP_NONE) ? "ok" : "with error"); + return (priv->step == WP_TRANSITION_STEP_NONE); } diff --git a/lib/wp/wp.h b/lib/wp/wp.h index 167d4397a9a68da0c7bb56d98297f00f908c6e05..2c6625f58013062e4737f16f0962596c2960b23a 100644 --- a/lib/wp/wp.h +++ b/lib/wp/wp.h @@ -18,6 +18,7 @@ #include "endpoint-link.h" #include "endpoint-stream.h" #include "error.h" +#include "global-proxy.h" #include "iterator.h" #include "link.h" #include "metadata.h" @@ -29,7 +30,6 @@ #include "plugin.h" #include "port.h" #include "properties.h" -#include "props.h" #include "proxy.h" #include "proxy-interfaces.h" #include "session.h" diff --git a/meson.build b/meson.build index 9f71356f4ae50ef0e0a3235867b0e53273e1a6f3..4c87f82efcaa4be76ab447fd5f927c01b49eae71 100644 --- a/meson.build +++ b/meson.build @@ -72,7 +72,7 @@ endif subdir('lib') subdir('docs') -subdir('modules') -subdir('src') -subdir('tests') -subdir('tools') +#subdir('modules') +#subdir('src') +#subdir('tests') +#subdir('tools')