/* 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 "spa-type.h" #include "spa-pod.h" #include "debug.h" #include "error.h" #include <spa/utils/result.h> G_DEFINE_INTERFACE (WpPwObjectMixinPriv, wp_pw_object_mixin_priv, WP_TYPE_PROXY) static void wp_pw_object_mixin_priv_default_init (WpPwObjectMixinPrivInterface * iface) { } static struct spa_param_info * find_param_info (gpointer instance, guint32 id) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); /* offsets are 0 on objects that don't support params */ if (d->info && iface->n_params_offset && iface->param_info_offset) { struct spa_param_info * param_info = G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); guint32 n_params = G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); for (guint i = 0; i < n_params; i++) { if (param_info[i].id == id) return ¶m_info[i]; } } return NULL; } /*************/ /* INTERFACE */ static gconstpointer wp_pw_object_mixin_get_native_info (WpPipewireObject * obj) { return wp_pw_object_mixin_get_data (obj)->info; } static WpProperties * wp_pw_object_mixin_get_properties (WpPipewireObject * obj) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj); return d->properties ? wp_properties_ref (d->properties) : NULL; } static GVariant * wp_pw_object_mixin_get_param_info (WpPipewireObject * obj) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); struct spa_param_info *info; guint32 n_params; g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_DICTIONARY); if (!d->info || iface->param_info_offset == 0 || iface->n_params_offset == 0) return NULL; info = G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); n_params = G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); g_variant_builder_init (&b, G_VARIANT_TYPE ("a{ss}")); for (guint i = 0; i < n_params; i++) { const gchar *nick = NULL; gchar flags[3]; guint flags_idx = 0; wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, info[i].id, NULL, &nick, NULL); g_return_val_if_fail (nick != NULL, NULL); if (info[i].flags & SPA_PARAM_INFO_READ) flags[flags_idx++] = 'r'; if (info[i].flags & SPA_PARAM_INFO_WRITE) flags[flags_idx++] = 'w'; flags[flags_idx] = '\0'; g_variant_builder_add (&b, "{ss}", nick, flags); } return g_variant_builder_end (&b); } static void enum_params_done (WpCore * core, GAsyncResult * res, gpointer data) { g_autoptr (GTask) task = G_TASK (data); g_autoptr (GError) error = NULL; gpointer instance = g_task_get_source_object (G_TASK (data)); GPtrArray *params = g_task_get_task_data (task); WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); /* finish the sync task */ wp_core_sync_finish (core, res, &error); /* return if the task was cancelled */ if (g_task_get_completed (task)) return; /* remove the task from the stored list; ref is held by the g_autoptr */ d->enum_params_tasks = g_list_remove (d->enum_params_tasks, task); wp_debug_object (instance, "got %u params, %s, task " WP_OBJECT_FORMAT, params->len, error ? "with error" : "ok", WP_OBJECT_ARGS (task)); if (error) g_task_return_error (task, g_steal_pointer (&error)); else { g_task_return_pointer (task, g_ptr_array_ref (params), (GDestroyNotify) g_ptr_array_unref); } } static void wp_pw_object_mixin_enum_params_unchecked (gpointer obj, guint32 id, WpSpaPod *filter, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (obj); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); g_autoptr (GTask) task = NULL; gint seq = 0; GPtrArray *params = NULL; g_return_if_fail (iface->enum_params_sync || iface->enum_params); if (iface->enum_params_sync) { params = iface->enum_params_sync (obj, id, 0, -1, filter); } else { seq = iface->enum_params (obj, id, 0, -1, filter); /* return early if seq contains an error */ if (G_UNLIKELY (SPA_RESULT_IS_ERROR (seq))) { wp_message_object (obj, "enum_params failed: %s", spa_strerror (seq)); g_task_report_new_error (obj, callback, user_data, NULL, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "enum_params failed: %s", spa_strerror (seq)); return; } } if (!params) params = g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref); /* create task */ task = g_task_new (obj, cancellable, callback, user_data); /* debug */ if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) { const gchar *name = NULL; wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, &name, NULL, NULL); wp_debug_object (obj, "enum id %u (%s), seq 0x%x (%u), task " WP_OBJECT_FORMAT "%s", id, name, seq, seq, WP_OBJECT_ARGS (task), iface->enum_params_sync ? ", sync" : ""); } if (iface->enum_params_sync) { g_task_return_pointer (task, params, (GDestroyNotify) g_ptr_array_unref); } else { g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (obj)); /* store */ g_task_set_task_data (task, params, (GDestroyNotify) g_ptr_array_unref); g_task_set_source_tag (task, GINT_TO_POINTER (seq)); d->enum_params_tasks = g_list_append (d->enum_params_tasks, task); /* call sync */ wp_core_sync (core, cancellable, (GAsyncReadyCallback) enum_params_done, g_object_ref (task)); } } static void wp_pw_object_mixin_enum_params (WpPipewireObject * obj, const gchar * id, WpSpaPod *filter, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); guint32 param_id = 0; if (!(iface->enum_params || iface->enum_params_sync)) { g_task_report_new_error (obj, callback, user_data, NULL, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "enum_params is not supported on this object"); return; } /* translate the id */ if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, ¶m_id, NULL, NULL)) { wp_critical_object (obj, "invalid param id: %s", id); return; } wp_pw_object_mixin_enum_params_unchecked (obj, param_id, filter, cancellable, callback, user_data); } static WpIterator * wp_pw_object_mixin_enum_params_finish (WpPipewireObject * obj, GAsyncResult * res, GError ** error) { g_return_val_if_fail (g_task_is_valid (res, obj), NULL); GPtrArray *array = g_task_propagate_pointer (G_TASK (res), error); if (!array) return NULL; return wp_iterator_new_ptr_array (array, WP_TYPE_SPA_POD); } static WpIterator * wp_pw_object_mixin_enum_params_sync (WpPipewireObject * obj, const gchar * id, WpSpaPod * filter) { WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); GPtrArray *params = NULL; guint32 param_id = 0; /* translate the id */ if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, ¶m_id, NULL, NULL)) { wp_critical_object (obj, "invalid param id: %s", id); return NULL; } if (iface->enum_params_sync) { /* use enum_params_sync if supported */ params = iface->enum_params_sync (obj, param_id, 0, -1, filter); } else { /* otherwise, find and return the cached params */ WpPwObjectMixinData *data = wp_pw_object_mixin_get_data (obj); params = wp_pw_object_mixin_get_stored_params (data, param_id); /* TODO filter */ } return params ? wp_iterator_new_ptr_array (params, WP_TYPE_SPA_POD) : NULL; } static gboolean wp_pw_object_mixin_set_param (WpPipewireObject * obj, const gchar * id, guint32 flags, WpSpaPod * param) { WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj); guint32 param_id = 0; gint ret; if (!iface->set_param) { wp_warning_object (obj, "set_param is not supported on this object"); return FALSE; } if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, ¶m_id, NULL, NULL)) { wp_critical_object (obj, "invalid param id: %s", id); wp_spa_pod_unref (param); return FALSE; } ret = iface->set_param (obj, param_id, flags, param); if (G_UNLIKELY (SPA_RESULT_IS_ERROR (ret))) { wp_message_object (obj, "set_param failed: %s", spa_strerror (ret)); return FALSE; } return TRUE; } void wp_pw_object_mixin_object_interface_init (WpPipewireObjectInterface * iface) { iface->get_native_info = wp_pw_object_mixin_get_native_info; iface->get_properties = wp_pw_object_mixin_get_properties; iface->get_param_info = wp_pw_object_mixin_get_param_info; iface->enum_params = wp_pw_object_mixin_enum_params; iface->enum_params_finish = wp_pw_object_mixin_enum_params_finish; iface->enum_params_sync = wp_pw_object_mixin_enum_params_sync; iface->set_param = wp_pw_object_mixin_set_param; } /********/ /* DATA */ G_DEFINE_QUARK (WpPwObjectMixinData, wp_pw_object_mixin_data) static void wp_pw_object_mixin_param_store_free (gpointer data); static WpPwObjectMixinData * wp_pw_object_mixin_data_new (void) { WpPwObjectMixinData *d = g_slice_new0 (WpPwObjectMixinData); spa_hook_list_init (&d->hooks); return d; } static void wp_pw_object_mixin_data_free (gpointer data) { WpPwObjectMixinData *d = data; g_clear_pointer (&d->properties, wp_properties_unref); g_list_free_full (d->params, wp_pw_object_mixin_param_store_free); g_clear_pointer (&d->subscribed_ids, g_array_unref); g_warn_if_fail (d->enum_params_tasks == NULL); g_slice_free (WpPwObjectMixinData, d); } WpPwObjectMixinData * wp_pw_object_mixin_get_data (gpointer instance) { WpPwObjectMixinData *d = g_object_get_qdata (G_OBJECT (instance), wp_pw_object_mixin_data_quark ()); if (G_UNLIKELY (!d)) { d = wp_pw_object_mixin_data_new (); g_object_set_qdata_full (G_OBJECT (instance), wp_pw_object_mixin_data_quark (), d, wp_pw_object_mixin_data_free); } return d; } /****************/ /* PARAMS STORE */ typedef struct _WpPwObjectMixinParamStore WpPwObjectMixinParamStore; struct _WpPwObjectMixinParamStore { guint32 param_id; GPtrArray *params; }; static WpPwObjectMixinParamStore * wp_pw_object_mixin_param_store_new (void) { WpPwObjectMixinParamStore *d = g_slice_new0 (WpPwObjectMixinParamStore); return d; } static void wp_pw_object_mixin_param_store_free (gpointer data) { WpPwObjectMixinParamStore * p = data; g_clear_pointer (&p->params, g_ptr_array_unref); g_slice_free (WpPwObjectMixinParamStore, p); } static gint param_store_has_id (gconstpointer param, gconstpointer id) { guint32 param_id = ((const WpPwObjectMixinParamStore *) param)->param_id; return (param_id == GPOINTER_TO_UINT (id)) ? 0 : 1; } GPtrArray * wp_pw_object_mixin_get_stored_params (WpPwObjectMixinData * data, guint32 id) { GList *link = g_list_find_custom (data->params, GUINT_TO_POINTER (id), param_store_has_id); WpPwObjectMixinParamStore *s = link ? link->data : NULL; return (s && s->params) ? g_ptr_array_ref (s->params) : NULL; } void wp_pw_object_mixin_store_param (WpPwObjectMixinData * data, guint32 id, guint32 flags, gpointer param) { GList *link = g_list_find_custom (data->params, GUINT_TO_POINTER (id), param_store_has_id); WpPwObjectMixinParamStore *s = link ? link->data : NULL; gint16 index = (gint16) (flags & 0xffff); /* if the link exists, data must also exist */ g_warn_if_fail (!link || link->data); if (!s) { if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE) return; s = wp_pw_object_mixin_param_store_new (); s->param_id = id; data->params = g_list_append (data->params, s); } else if (s && (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE)) { wp_pw_object_mixin_param_store_free (s); data->params = g_list_remove_link (data->params, link); return; } if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR) g_clear_pointer (&s->params, g_ptr_array_unref); if (!param) return; if (flags & WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY) { if (!s->params) s->params = (GPtrArray *) param; else g_ptr_array_extend_and_steal (s->params, (GPtrArray *) param); } else { WpSpaPod *param_pod = param; if (!s->params) s->params = g_ptr_array_new_with_free_func ((GDestroyNotify) wp_spa_pod_unref); /* copy if necessary to make sure we don't reference `const struct spa_pod *` data allocated on the stack */ param_pod = wp_spa_pod_ensure_unique_owner (param_pod); g_ptr_array_insert (s->params, index, param_pod); } } /******************/ /* PROPERTIES API */ void wp_pw_object_mixin_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { switch (property_id) { case WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO: g_value_set_pointer (value, (gpointer) wp_pipewire_object_get_native_info (WP_PIPEWIRE_OBJECT (object))); break; case WP_PW_OBJECT_MIXIN_PROP_PROPERTIES: g_value_set_boxed (value, wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (object))); break; case WP_PW_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_pw_object_mixin_class_override_properties (GObjectClass * klass) { g_object_class_override_property (klass, WP_PW_OBJECT_MIXIN_PROP_NATIVE_INFO, "native-info"); g_object_class_override_property (klass, WP_PW_OBJECT_MIXIN_PROP_PROPERTIES, "properties"); g_object_class_override_property (klass, WP_PW_OBJECT_MIXIN_PROP_PARAM_INFO, "param-info"); } /****************/ /* FEATURES API */ static const struct { WpObjectFeatures feature; guint32 param_ids[2]; } params_features[] = { { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROPS, { SPA_PARAM_PropInfo, SPA_PARAM_Props } }, { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_FORMAT, { SPA_PARAM_EnumFormat, SPA_PARAM_Format } }, { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PROFILE, { SPA_PARAM_EnumProfile, SPA_PARAM_Profile } }, { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_PORT_CONFIG, { SPA_PARAM_EnumPortConfig, SPA_PARAM_PortConfig } }, { WP_PIPEWIRE_OBJECT_FEATURE_PARAM_ROUTE, { SPA_PARAM_EnumRoute, SPA_PARAM_Route } }, }; static WpObjectFeatures get_feature_for_param_id (guint32 param_id) { for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) { if (params_features[i].param_ids[0] == param_id || params_features[i].param_ids[1] == param_id) return params_features[i].feature; } return 0; } WpObjectFeatures wp_pw_object_mixin_get_supported_features (WpObject * object) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object); WpObjectFeatures ft = WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO; if (d->info && iface->n_params_offset && iface->param_info_offset) { struct spa_param_info * param_info = G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); guint32 n_params = G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); for (guint i = 0; i < n_params; i++) ft |= get_feature_for_param_id (param_info[i].id); } return ft; } guint wp_pw_object_mixin_activate_get_next_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); /* bind if not already bound */ if (missing & WP_PROXY_FEATURE_BOUND || !d->iface) return WP_PW_OBJECT_MIXIN_STEP_BIND; /* wait for info before proceeding, if necessary */ else if ((missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) && !d->info) return WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO; /* then cache params */ else if (missing & WP_PIPEWIRE_OBJECT_FEATURES_ALL) return WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS; else return WP_PW_OBJECT_MIXIN_STEP_CUSTOM_START; /* returning to STEP_NONE is handled by WpFeatureActivationTransition */ } static void enum_params_for_cache_done (GObject * object, GAsyncResult * res, gpointer data) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); guint32 param_id = GPOINTER_TO_UINT (data); g_autoptr (GError) error = NULL; g_autoptr (GPtrArray) params = NULL; params = g_task_propagate_pointer (G_TASK (res), &error); if (error) { wp_warning_object (object, "enum params failed: %s", error->message); return; } wp_debug_object (object, "cached params id:%u, n_params:%u", param_id, params->len); wp_pw_object_mixin_store_param (d, param_id, WP_PW_OBJECT_MIXIN_STORE_PARAM_ARRAY | WP_PW_OBJECT_MIXIN_STORE_PARAM_CLEAR | WP_PW_OBJECT_MIXIN_STORE_PARAM_APPEND, g_steal_pointer (¶ms)); g_signal_emit_by_name (object, "params-changed", param_id); } G_DEFINE_QUARK (WpPwObjectMixinParamCacheActivatedFeatures, activated_features) static void param_cache_features_enabled (WpCore * core, GAsyncResult * res, gpointer data) { WpObject *object = WP_OBJECT (data); WpObjectFeatures activated = GPOINTER_TO_UINT ( g_object_get_qdata (G_OBJECT (object), activated_features_quark ())); wp_object_update_features (object, activated, 0); } void wp_pw_object_mixin_cache_params (WpObject * object, WpObjectFeatures missing) { WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object); g_autoptr (WpCore) core = wp_object_get_core (object); struct spa_param_info * param_info; WpObjectFeatures activated = 0; g_return_if_fail (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)); for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) { if (missing & params_features[i].feature) { param_info = find_param_info (object, params_features[i].param_ids[0]); if (param_info && param_info->flags & SPA_PARAM_INFO_READ) { wp_pw_object_mixin_enum_params_unchecked (object, param_info->id, NULL, NULL, enum_params_for_cache_done, GUINT_TO_POINTER (param_info->id)); } param_info = find_param_info (object, params_features[i].param_ids[1]); if (param_info && param_info->flags & SPA_PARAM_INFO_READ) { wp_pw_object_mixin_enum_params_unchecked (object, param_info->id, NULL, NULL, enum_params_for_cache_done, GUINT_TO_POINTER (param_info->id)); } activated |= params_features[i].feature; } } g_object_set_qdata (G_OBJECT (object), activated_features_quark (), GUINT_TO_POINTER (activated)); wp_core_sync (core, NULL, (GAsyncReadyCallback) param_cache_features_enabled, object); } void wp_pw_object_mixin_deactivate (WpObject * object, WpObjectFeatures features) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (object); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (object); /* deactivate param caching */ if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)) { for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) { if (features & params_features[i].feature) { wp_pw_object_mixin_store_param (d, params_features[i].param_ids[0], WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); wp_pw_object_mixin_store_param (d, params_features[i].param_ids[1], WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); wp_object_update_features (object, 0, params_features[i].feature); } } } } /************************/ /* PROXY EVENT HANDLERS */ void wp_pw_object_mixin_handle_pw_proxy_destroyed (WpProxy * proxy) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (proxy); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (proxy); g_clear_pointer (&d->properties, wp_properties_unref); g_clear_pointer (&d->info, iface->free_info); d->iface = NULL; /* deactivate param caching */ if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE)) { for (guint i = 0; i < G_N_ELEMENTS (params_features); i++) { wp_pw_object_mixin_store_param (d, params_features[i].param_ids[0], WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); wp_pw_object_mixin_store_param (d, params_features[i].param_ids[1], WP_PW_OBJECT_MIXIN_STORE_PARAM_REMOVE, NULL); } } /* cancel enum_params tasks */ { GList *link; for (link = g_list_first (d->enum_params_tasks); link; link = g_list_first (d->enum_params_tasks)) { g_task_return_new_error (G_TASK (link->data), WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "pipewire proxy destroyed before finishing"); d->enum_params_tasks = g_list_remove_link (d->enum_params_tasks, link); } } wp_object_update_features (WP_OBJECT (proxy), 0, WP_PIPEWIRE_OBJECT_FEATURES_ALL); } /***************************/ /* PIPEWIRE EVENT HANDLERS */ void wp_pw_object_mixin_handle_event_info (gpointer instance, gconstpointer update) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); guint32 change_mask = G_STRUCT_MEMBER (guint32, update, iface->change_mask_offset); guint32 process_info_change_mask = change_mask & ~(iface->CHANGE_MASK_PROPS | iface->CHANGE_MASK_PARAMS); gpointer old_info = NULL; wp_debug_object (instance, "info, change_mask:0x%x [%s%s]", change_mask, (change_mask & iface->CHANGE_MASK_PROPS) ? "props," : "", (change_mask & iface->CHANGE_MASK_PARAMS) ? "params," : ""); /* make a copy of d->info for process_info() */ if (iface->process_info && d->info && process_info_change_mask) { /* copy everything that changed except props and params, for efficiency; process_info() is only interested in variables that are not PROPS & PARAMS */ G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = process_info_change_mask; old_info = iface->update_info (NULL, d->info); } /* update params */ if (!(iface->flags & WP_PW_OBJECT_MIXIN_PRIV_NO_PARAM_CACHE) && (change_mask & iface->CHANGE_MASK_PARAMS) && d->info) { struct spa_param_info * old_param_info = G_STRUCT_MEMBER (struct spa_param_info *, d->info, iface->param_info_offset); struct spa_param_info * param_info = G_STRUCT_MEMBER (struct spa_param_info *, update, iface->param_info_offset); guint32 old_n_params = G_STRUCT_MEMBER (guint32, d->info, iface->n_params_offset); guint32 n_params = G_STRUCT_MEMBER (guint32, update, iface->n_params_offset); WpObjectFeatures active_ft = wp_object_get_active_features (WP_OBJECT (instance)); for (guint i = 0; i < n_params; i++) { /* param changes when flags change */ if (i >= old_n_params || old_param_info[i].flags != param_info[i].flags) { /* update cached params if the relevant feature is active */ if (active_ft & get_feature_for_param_id (param_info[i].id) && param_info[i].flags & SPA_PARAM_INFO_READ) { wp_pw_object_mixin_enum_params_unchecked (instance, param_info[i].id, NULL, NULL, enum_params_for_cache_done, GUINT_TO_POINTER (param_info[i].id)); } } } } /* update our info struct */ d->info = iface->update_info (d->info, update); wp_object_update_features (WP_OBJECT (instance), WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0); /* update properties */ if (change_mask & iface->CHANGE_MASK_PROPS) { const struct spa_dict * props = G_STRUCT_MEMBER (const struct spa_dict *, d->info, iface->props_offset); g_clear_pointer (&d->properties, wp_properties_unref); d->properties = wp_properties_new_wrap_dict (props); g_object_notify (G_OBJECT (instance), "properties"); } if (change_mask & iface->CHANGE_MASK_PARAMS) g_object_notify (G_OBJECT (instance), "param-info"); /* custom handling, if required */ if (iface->process_info && process_info_change_mask) { iface->process_info (instance, old_info, d->info); g_clear_pointer (&old_info, iface->free_info); } } static gint 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_pw_object_mixin_handle_event_param (gpointer instance, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); g_autoptr (WpSpaPod) w_param = wp_spa_pod_new_wrap_const (param); GList *list; GTask *task; list = g_list_find_custom (d->enum_params_tasks, GINT_TO_POINTER (seq), task_has_seq); task = list ? G_TASK (list->data) : NULL; wp_trace_boxed (WP_TYPE_SPA_POD, w_param, WP_OBJECT_FORMAT " param id:%u, index:%u", WP_OBJECT_ARGS (instance), id, index); if (task) { GPtrArray *array = g_task_get_task_data (task); g_ptr_array_add (array, wp_spa_pod_copy (w_param)); } else { /* this should never happen */ wp_warning_object (instance, "param event was received without calling enum_params"); } } /***********************************/ /* PIPEWIRE METHOD IMPLEMENTATIONS */ int wp_pw_object_mixin_impl_add_listener (gpointer instance, struct spa_hook *listener, gconstpointer events, gpointer data) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); struct spa_hook_list save; spa_hook_list_isolate (&d->hooks, &save, listener, events, data); G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = iface->CHANGE_MASK_ALL; iface->emit_info (&d->hooks, d->info); G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = 0; spa_hook_list_join (&d->hooks, &save); return 0; } int wp_pw_object_mixin_impl_enum_params (gpointer instance, int seq, guint32 id, guint32 start, guint32 num, const struct spa_pod *filter) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); g_autoptr (GPtrArray) params = NULL; g_autoptr (WpSpaPod) filter_pod = NULL; if (!iface->enum_params_sync) return -ENOTSUP; struct spa_param_info * info = find_param_info (instance, id); if (!info || !(info->flags & SPA_PARAM_INFO_READ)) return -EINVAL; filter_pod = filter ? wp_spa_pod_new_wrap_const (filter) : NULL; params = iface->enum_params_sync (instance, id, start, num, filter_pod); if (params) { for (guint i = 0; i < params->len; i++) { WpSpaPod *pod = g_ptr_array_index (params, i); wp_trace_boxed (WP_TYPE_SPA_POD, pod, WP_OBJECT_FORMAT " emit param id:%u, index:%u", WP_OBJECT_ARGS (instance), id, start+i); iface->emit_param (&d->hooks, seq, id, start+i, start+i+1, wp_spa_pod_get_spa_pod (pod)); } } return 0; } int wp_pw_object_mixin_impl_subscribe_params (gpointer instance, guint32 *ids, guint32 n_ids) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); if (!iface->enum_params_sync) return -ENOTSUP; for (guint i = 0; i < n_ids; i++) wp_pw_object_mixin_impl_enum_params (instance, 1, ids[i], 0, -1, NULL); if (!d->subscribed_ids) d->subscribed_ids = g_array_new (FALSE, FALSE, sizeof (guint32)); /* FIXME: deduplicate stored ids */ g_array_append_vals (d->subscribed_ids, ids, n_ids); return 0; } int wp_pw_object_mixin_impl_set_param (gpointer instance, guint32 id, guint32 flags, const struct spa_pod *param) { WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); if (!iface->set_param) return -ENOTSUP; struct spa_param_info * info = find_param_info (instance, id); if (!info || !(info->flags & SPA_PARAM_INFO_WRITE)) return -EINVAL; WpSpaPod *param_pod = wp_spa_pod_new_wrap_const (param); wp_trace_boxed (WP_TYPE_SPA_POD, param_pod, WP_OBJECT_FORMAT " set_param id:%u flags:0x%x", WP_OBJECT_ARGS (instance), id, flags); return iface->set_param (instance, id, flags, param_pod); } /**********************/ /* NOTIFIERS */ void wp_pw_object_mixin_notify_info (gpointer instance, guint32 change_mask) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); wp_debug_object (instance, "notify info, change_mask:0x%x [%s%s]", change_mask, (change_mask & iface->CHANGE_MASK_PROPS) ? "props," : "", (change_mask & iface->CHANGE_MASK_PARAMS) ? "params," : ""); G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = (change_mask & iface->CHANGE_MASK_ALL); iface->emit_info (&d->hooks, d->info); G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = 0; if (change_mask & iface->CHANGE_MASK_PROPS) g_object_notify (G_OBJECT (instance), "properties"); if (change_mask & iface->CHANGE_MASK_PARAMS) g_object_notify (G_OBJECT (instance), "param-info"); } void wp_pw_object_mixin_notify_params_changed (gpointer instance, guint32 id) { WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance); WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (instance); gboolean subscribed = FALSE; struct spa_param_info * info = find_param_info (instance, id); g_return_if_fail (info); if (d->subscribed_ids) { for (guint i = 0; i < d->subscribed_ids->len; i++) { if (g_array_index (d->subscribed_ids, guint32, i) == id) { subscribed = TRUE; break; } } } if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) { const gchar *name = NULL; wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, &name, NULL, NULL); wp_debug_object (instance, "notify param id:%u (%s)", id, name); } /* toggle the serial flag; this notifies that there is a data change */ info->flags ^= SPA_PARAM_INFO_SERIAL; G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = iface->CHANGE_MASK_PARAMS; iface->emit_info (&d->hooks, d->info); G_STRUCT_MEMBER (guint32, d->info, iface->change_mask_offset) = 0; if (subscribed) wp_pw_object_mixin_impl_enum_params (instance, 1, id, 0, -1, NULL); g_signal_emit_by_name (instance, "params-changed", id); }