diff --git a/lib/wp/session.c b/lib/wp/session.c index 8a7c09a8a689fd44b6ebe05be7a0f2b75b6e2339..7896c39b6c3ba30d81b4d62e79cea3aaf1d58fb6 100644 --- a/lib/wp/session.c +++ b/lib/wp/session.c @@ -27,8 +27,11 @@ #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, @@ -37,52 +40,6 @@ enum { static guint32 signals[N_SIGNALS] = {0}; -/* helpers */ - -static struct pw_session_info * -session_info_update (struct pw_session_info *info, - WpProperties ** props_storage, - const struct pw_session_info *update) -{ - if (update == NULL) - return info; - - if (info == NULL) { - info = calloc(1, sizeof(struct pw_session_info)); - if (info == NULL) - return NULL; - - info->id = update->id; - } - info->change_mask = update->change_mask; - - if (update->change_mask & PW_SESSION_CHANGE_MASK_PROPS) { - if (*props_storage) - wp_properties_unref (*props_storage); - *props_storage = wp_properties_new_copy_dict (update->props); - info->props = (struct spa_dict *) wp_properties_peek_dict (*props_storage); - } - if (update->change_mask & PW_SESSION_CHANGE_MASK_PARAMS) { - info->n_params = update->n_params; - free((void *) info->params); - if (update->params) { - size_t size = info->n_params * sizeof(struct spa_param_info); - info->params = malloc(size); - memcpy(info->params, update->params, size); - } - else - info->params = NULL; - } - return info; -} - -static void -session_info_free (struct pw_session_info *info) -{ - free((void *) info->params); - free(info); -} - /* WpSession */ typedef struct _WpSessionPrivate WpSessionPrivate; @@ -91,6 +48,7 @@ struct _WpSessionPrivate WpProperties *properties; WpSpaProps spa_props; struct pw_session_info *info; + struct pw_session *iface; struct spa_hook listener; }; @@ -107,7 +65,7 @@ wp_session_finalize (GObject * object) WpSession *self = WP_SESSION (object); WpSessionPrivate *priv = wp_session_get_instance_private (self); - g_clear_pointer (&priv->info, session_info_free); + g_clear_pointer (&priv->info, pw_session_info_free); g_clear_pointer (&priv->properties, wp_properties_unref); wp_spa_props_clear (&priv->spa_props); @@ -136,12 +94,11 @@ static gint wp_session_enum_params (WpProxy * self, guint32 id, guint32 start, guint32 num, const struct spa_pod *filter) { - struct pw_session *pwp; + WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); int session_enum_params_result; - pwp = (struct pw_session *) wp_proxy_get_pw_proxy (self); - session_enum_params_result = pw_session_enum_params (pwp, 0, id, start, num, - filter); + session_enum_params_result = pw_session_enum_params (priv->iface, 0, id, + start, num, filter); g_warn_if_fail (session_enum_params_result >= 0); return session_enum_params_result; @@ -150,12 +107,11 @@ wp_session_enum_params (WpProxy * self, guint32 id, guint32 start, static gint wp_session_subscribe_params (WpProxy * self, guint32 n_ids, guint32 *ids) { - struct pw_session *pwp; + WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); int session_subscribe_params_result; - pwp = (struct pw_session *) wp_proxy_get_pw_proxy (self); - session_subscribe_params_result = pw_session_subscribe_params (pwp, ids, - n_ids); + session_subscribe_params_result = pw_session_subscribe_params (priv->iface, + ids, n_ids); g_warn_if_fail (session_subscribe_params_result >= 0); return session_subscribe_params_result; @@ -165,11 +121,11 @@ static gint wp_session_set_param (WpProxy * self, guint32 id, guint32 flags, const struct spa_pod *param) { - struct pw_session *pwp; + WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); int session_set_param_result; - pwp = (struct pw_session *) wp_proxy_get_pw_proxy (self); - session_set_param_result = pw_session_set_param (pwp, id, flags, param); + session_set_param_result = pw_session_set_param (priv->iface, id, flags, + param); g_warn_if_fail (session_set_param_result >= 0); return session_set_param_result; @@ -181,9 +137,14 @@ session_event_info (void *data, const struct pw_session_info *info) WpSession *self = WP_SESSION (data); WpSessionPrivate *priv = wp_session_get_instance_private (self); - priv->info = session_info_update (priv->info, &priv->properties, info); - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + priv->info = pw_session_info_update (priv->info, info); + + if (info->change_mask & PW_SESSION_CHANGE_MASK_PROPS) { + g_clear_pointer (&priv->properties, wp_properties_unref); + priv->properties = wp_properties_new_wrap_dict (priv->info->props); + } + wp_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) @@ -201,8 +162,9 @@ wp_session_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy) { WpSession *self = WP_SESSION (proxy); WpSessionPrivate *priv = wp_session_get_instance_private (self); - pw_session_add_listener ((struct pw_session *) pw_proxy, - &priv->listener, &session_events, self); + + priv->iface = (struct pw_session *) pw_proxy; + pw_session_add_listener (priv->iface, &priv->listener, &session_events, self); } static void @@ -275,14 +237,11 @@ set_default_endpoint (WpSession * self, WpDefaultEndpointType type, guint32 id) { char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf)); - struct pw_session *pw_proxy = NULL; /* set the default endpoint id as a property param on the session; our spa_props cache will be updated by the param event */ - pw_proxy = (struct pw_session *) wp_proxy_get_pw_proxy (WP_PROXY (self)); - pw_session_set_param (pw_proxy, - SPA_PARAM_Props, 0, + WP_PROXY_GET_CLASS (self)->set_param (WP_PROXY (self), SPA_PARAM_Props, 0, spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, type, SPA_POD_Int (id))); @@ -366,147 +325,217 @@ wp_session_set_default_endpoint (WpSession * self, /* WpImplSession */ -typedef struct _WpImplSessionPrivate WpImplSessionPrivate; -struct _WpImplSessionPrivate +typedef struct _WpImplSession WpImplSession; +struct _WpImplSession { - WpSessionPrivate *pp; + WpSession parent; + + struct spa_interface iface; + struct spa_hook_list hooks; struct pw_session_info info; - struct spa_param_info param_info[2]; + gboolean subscribed; }; -G_DEFINE_TYPE_WITH_PRIVATE (WpImplSession, wp_impl_session, WP_TYPE_SESSION) +G_DEFINE_TYPE (WpImplSession, wp_impl_session, WP_TYPE_SESSION) -static void -wp_impl_session_init (WpImplSession * self) -{ - WpImplSessionPrivate *priv = wp_impl_session_get_instance_private (self); - - /* store a pointer to the parent's private; we use that structure - as well to optimize memory usage and to be able to re-use some of the - parent's methods without reimplementing them */ - priv->pp = wp_session_get_instance_private (WP_SESSION (self)); - - priv->pp->properties = wp_properties_new_empty (); +#define pw_session_emit(hooks,method,version,...) \ + spa_hook_list_call_simple(hooks, struct pw_session_events, \ + method, version, ##__VA_ARGS__) - priv->param_info[0] = SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - priv->param_info[1] = SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); +#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__) - priv->info.version = PW_VERSION_SESSION_INFO; - priv->info.props = - (struct spa_dict *) wp_properties_peek_dict (priv->pp->properties); - priv->info.params = priv->param_info; - priv->info.n_params = SPA_N_ELEMENTS (priv->param_info); - priv->pp->info = &priv->info; - wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); - - wp_spa_props_register (&priv->pp->spa_props, - WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SOURCE, - "Default Audio Source", SPA_POD_Int (0)); - wp_spa_props_register (&priv->pp->spa_props, - WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SINK, - "Default Audio Sink", SPA_POD_Int (0)); - wp_spa_props_register (&priv->pp->spa_props, - WP_DEFAULT_ENDPOINT_TYPE_VIDEO_SOURCE, - "Default Video Source", SPA_POD_Int (0)); - wp_proxy_set_feature_ready (WP_PROXY (self), - WP_SESSION_FEATURE_DEFAULT_ENDPOINT); -} +static struct spa_param_info impl_param_info[] = { + SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE), + SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ) +}; -static void -wp_impl_session_finalize (GObject * object) +static int +impl_add_listener(void *object, + struct spa_hook *listener, + const struct pw_session_events *events, + void *data) { - WpImplSessionPrivate *priv = - wp_impl_session_get_instance_private (WP_IMPL_SESSION (object)); + WpImplSession *self = WP_IMPL_SESSION (object); + struct spa_hook_list save; - /* set to NULL to prevent parent's finalize from calling free() on it */ - priv->pp->info = NULL; + spa_hook_list_isolate (&self->hooks, &save, listener, events, data); - G_OBJECT_CLASS (wp_impl_session_parent_class)->finalize (object); + self->info.change_mask = PW_SESSION_CHANGE_MASK_ALL; + pw_session_emit_info (&self->hooks, &self->info); + self->info.change_mask = 0; + + spa_hook_list_join (&self->hooks, &save); + return 0; } -static void -client_session_update (WpImplSession * self, guint32 change_mask, - guint32 info_change_mask) +static int +impl_enum_params (void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) { - WpImplSessionPrivate *priv = - wp_impl_session_get_instance_private (self); + WpImplSession *self = WP_IMPL_SESSION (object); + WpSessionPrivate *priv = + wp_session_get_instance_private (WP_SESSION (self)); char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buf, sizeof (buf)); - struct pw_client_session *pw_proxy = NULL; - struct pw_session_info *info = NULL; - g_autoptr (GPtrArray) params = NULL; - - pw_proxy = (struct pw_client_session *) wp_proxy_get_pw_proxy (WP_PROXY (self)); + struct spa_pod *result; + guint count = 0; - if (change_mask & PW_CLIENT_SESSION_UPDATE_PARAMS) { - params = wp_spa_props_build_all_pods (&priv->pp->spa_props, &b); - } - if (change_mask & PW_CLIENT_SESSION_UPDATE_INFO) { - info = &priv->info; - info->change_mask = info_change_mask; + switch (id) { + case SPA_PARAM_PropInfo: { + g_autoptr (GPtrArray) params = + wp_spa_props_build_propinfo (&priv->spa_props, &b); + + for (guint i = start; i < params->len; i++) { + struct spa_pod *param = g_ptr_array_index (params, i); + pw_session_emit_param (&self->hooks, seq, id, i, i+1, param); + wp_proxy_handle_event_param (self, seq, id, i, i+1, param); + if (++count == num) + break; + } + break; + } + case SPA_PARAM_Props: { + if (start == 0) { + struct spa_pod *param = wp_spa_props_build_props (&priv->spa_props, &b); + if (spa_pod_filter (&b, &result, param, filter) == 0) { + pw_session_emit_param (&self->hooks, seq, id, 0, 1, result); + wp_proxy_handle_event_param (self, seq, id, 0, 1, result); + } + } + break; + } + default: + return -ENOENT; } - pw_client_session_update (pw_proxy, - change_mask, - params ? params->len : 0, - (const struct spa_pod **) (params ? params->pdata : NULL), - info); + return 0; +} - if (info) - info->change_mask = 0; +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; } static int -client_session_set_param (void *object, - uint32_t id, uint32_t flags, const struct spa_pod *param) +impl_set_param (void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) { WpImplSession *self = WP_IMPL_SESSION (object); - WpImplSessionPrivate *priv = - wp_impl_session_get_instance_private (self); + WpSessionPrivate *priv = + wp_session_get_instance_private (WP_SESSION (self)); g_autoptr (GArray) changed_ids = NULL; - guint32 prop_id; - gint32 value; if (id != SPA_PARAM_Props) return -ENOENT; changed_ids = g_array_new (FALSE, FALSE, sizeof (guint32)); - wp_spa_props_store_from_props (&priv->pp->spa_props, param, changed_ids); + wp_spa_props_store_from_props (&priv->spa_props, param, changed_ids); + /* notify subscribers */ + if (self->subscribed) + impl_enum_params (self, 1, SPA_PARAM_Props, 0, UINT32_MAX, NULL); + + /* notify controls locally */ for (guint i = 0; i < changed_ids->len; i++) { - prop_id = g_array_index (changed_ids, guint32, i); - param = wp_spa_props_get_stored (&priv->pp->spa_props, prop_id); + guint32 prop_id = g_array_index (changed_ids, guint32, i); + const struct spa_pod *param = + wp_spa_props_get_stored (&priv->spa_props, prop_id); + gint value; + if (spa_pod_get_int (param, &value) == 0) { g_signal_emit (self, signals[SIGNAL_DEFAULT_ENDPOINT_CHANGED], 0, prop_id, value); } } - client_session_update (self, PW_CLIENT_SESSION_UPDATE_PARAMS, 0); - return 0; } -static struct pw_client_session_events client_session_events = { - PW_VERSION_CLIENT_SESSION_EVENTS, - .set_param = client_session_set_param, +static const struct pw_session_methods impl_session = { + PW_VERSION_SESSION_METHODS, + .add_listener = impl_add_listener, + .subscribe_params = impl_subscribe_params, + .enum_params = impl_enum_params, + .set_param = impl_set_param, }; +static void +wp_impl_session_init (WpImplSession * self) +{ + /* reuse the parent's private to optimize memory usage and to be able + to re-use some of the parent's methods without reimplementing them */ + WpSessionPrivate *priv = + wp_session_get_instance_private (WP_SESSION (self)); + + self->iface = SPA_INTERFACE_INIT ( + PW_TYPE_INTERFACE_Session, + PW_VERSION_SESSION, + &impl_session, self); + spa_hook_list_init (&self->hooks); + + priv->iface = (struct pw_session *) &self->iface; + + /* prepare INFO */ + priv->properties = wp_properties_new_empty (); + self->info.version = PW_VERSION_SESSION_INFO; + self->info.props = + (struct spa_dict *) wp_properties_peek_dict (priv->properties); + self->info.params = impl_param_info; + self->info.n_params = SPA_N_ELEMENTS (impl_param_info); + priv->info = &self->info; + g_object_notify (G_OBJECT (self), "info"); + + wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO); + + /* prepare DEFAULT_ENDPOINT */ + wp_spa_props_register (&priv->spa_props, + WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SOURCE, + "Default Audio Source", SPA_POD_Int (0)); + wp_spa_props_register (&priv->spa_props, + WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SINK, + "Default Audio Sink", SPA_POD_Int (0)); + wp_spa_props_register (&priv->spa_props, + WP_DEFAULT_ENDPOINT_TYPE_VIDEO_SOURCE, + "Default Video Source", SPA_POD_Int (0)); + + wp_proxy_set_feature_ready (WP_PROXY (self), + WP_SESSION_FEATURE_DEFAULT_ENDPOINT); +} + +static void +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_OBJECT_CLASS (wp_impl_session_parent_class)->finalize (object); +} + static void wp_impl_session_augment (WpProxy * proxy, WpProxyFeatures features) { WpImplSession *self = WP_IMPL_SESSION (proxy); - WpImplSessionPrivate *priv = wp_impl_session_get_instance_private (self); + WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (self)); - /* if any of the default features is requested, make sure BOUND - is also requested, as they all depend on binding the session */ - if (features & WP_PROXY_FEATURES_STANDARD) + /* 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); struct pw_core *pw_core = wp_core_get_pw_core (core); - struct pw_proxy *pw_proxy = NULL; /* no pw_core -> we are not connected */ if (!pw_core) { @@ -518,46 +547,14 @@ wp_impl_session_augment (WpProxy * proxy, WpProxyFeatures features) } /* make sure these props are not present; they are added by the server */ - wp_properties_set (priv->pp->properties, PW_KEY_OBJECT_ID, NULL); - wp_properties_set (priv->pp->properties, PW_KEY_CLIENT_ID, NULL); - wp_properties_set (priv->pp->properties, PW_KEY_FACTORY_ID, NULL); - - pw_proxy = pw_core_create_object (pw_core, "client-session", - PW_TYPE_INTERFACE_ClientSession, PW_VERSION_CLIENT_SESSION, - wp_properties_peek_dict (priv->pp->properties), 0); - wp_proxy_set_pw_proxy (proxy, pw_proxy); - - pw_client_session_add_listener (pw_proxy, &priv->pp->listener, - &client_session_events, self); - - client_session_update (WP_IMPL_SESSION (self), - PW_CLIENT_SESSION_UPDATE_PARAMS | PW_CLIENT_SESSION_UPDATE_INFO, - PW_SESSION_CHANGE_MASK_ALL); - } -} - -static gint -wp_impl_session_set_param (WpProxy * self, guint32 id, guint32 flags, - const struct spa_pod *param) -{ - return client_session_set_param (self, id, flags, param); -} - -static void -wp_impl_session_set_default_endpoint (WpSession * session, - WpDefaultEndpointType type, guint32 id) -{ - WpImplSession *self = WP_IMPL_SESSION (session); - WpImplSessionPrivate *priv = wp_impl_session_get_instance_private (self); - - wp_spa_props_store (&priv->pp->spa_props, type, SPA_POD_Int (id)); - - g_signal_emit (session, signals[SIGNAL_DEFAULT_ENDPOINT_CHANGED], 0, type, id); - - /* update only after the session has been exported */ - if (wp_proxy_get_features (WP_PROXY (self)) & WP_PROXY_FEATURE_BOUND) { - client_session_update (WP_IMPL_SESSION (session), - PW_CLIENT_SESSION_UPDATE_PARAMS, 0); + wp_properties_set (priv->properties, PW_KEY_OBJECT_ID, NULL); + wp_properties_set (priv->properties, PW_KEY_CLIENT_ID, NULL); + wp_properties_set (priv->properties, PW_KEY_FACTORY_ID, NULL); + + wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core, + PW_TYPE_INTERFACE_Session, + wp_properties_peek_dict (priv->properties), + priv->iface, 0)); } } @@ -566,19 +563,12 @@ wp_impl_session_class_init (WpImplSessionClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; WpProxyClass *proxy_class = (WpProxyClass *) klass; - WpSessionClass *session_class = (WpSessionClass *) 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; - proxy_class->set_param = wp_impl_session_set_param; - proxy_class->pw_proxy_created = NULL; proxy_class->param = NULL; - - session_class->set_default_endpoint = wp_impl_session_set_default_endpoint; } /** @@ -613,19 +603,20 @@ void wp_impl_session_set_property (WpImplSession * self, const gchar * key, const gchar * value) { - WpImplSessionPrivate *priv; + WpSessionPrivate *priv; g_return_if_fail (WP_IS_IMPL_SESSION (self)); - priv = wp_impl_session_get_instance_private (self); + priv = wp_session_get_instance_private (WP_SESSION (self)); - wp_properties_set (priv->pp->properties, key, value); + wp_properties_set (priv->properties, key, value); 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) { - client_session_update (self, PW_CLIENT_SESSION_UPDATE_INFO, - PW_SESSION_CHANGE_MASK_PROPS); + self->info.change_mask = PW_SESSION_CHANGE_MASK_PROPS; + pw_session_emit_info (&self->hooks, &self->info); + self->info.change_mask = 0; } } @@ -645,19 +636,19 @@ void wp_impl_session_update_properties (WpImplSession * self, WpProperties * updates) { - WpImplSessionPrivate *priv; + WpSessionPrivate *priv; g_return_if_fail (WP_IS_IMPL_SESSION (self)); - priv = wp_impl_session_get_instance_private (self); + priv = wp_session_get_instance_private (WP_SESSION (self)); - wp_properties_update_from_dict (priv->pp->properties, - wp_properties_peek_dict (updates)); + wp_properties_update (priv->properties, updates); 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) { - client_session_update (self, PW_CLIENT_SESSION_UPDATE_INFO, - PW_SESSION_CHANGE_MASK_PROPS); + 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 148e5a7793688a0e2649589bf0d3ff87fea86809..83fe3665a169f88706e00dde1d6a9005b3557fee 100644 --- a/lib/wp/session.h +++ b/lib/wp/session.h @@ -73,12 +73,7 @@ void wp_session_set_default_endpoint (WpSession * self, */ #define WP_TYPE_IMPL_SESSION (wp_impl_session_get_type ()) WP_API -G_DECLARE_DERIVABLE_TYPE (WpImplSession, wp_impl_session, WP, IMPL_SESSION, WpSession) - -struct _WpImplSessionClass -{ - WpSessionClass parent_class; -}; +G_DECLARE_FINAL_TYPE (WpImplSession, wp_impl_session, WP, IMPL_SESSION, WpSession) WP_API WpImplSession * wp_impl_session_new (WpCore * core);