/* WirePlumber * * Copyright © 2020 Collabora Ltd. * @author George Kiagiadakis * * SPDX-License-Identifier: MIT */ #include "global-proxy.h" #include "private/registry.h" #include "core.h" #include "error.h" #include "log.h" WP_DEFINE_LOCAL_LOG_TOPIC ("wp-global-proxy") /*! \defgroup wpglobalproxy WpGlobalProxy */ /*! * \struct WpGlobalProxy * * A proxy that represents a PipeWire global object, i.e. an * object that is made available through the PipeWire registry. * * \gproperties * * \gproperty{global, WpGlobal *, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY, * Internal WpGlobal object} * * \gproperty{factory-name, gchar *, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY, * The factory name} * * \gproperty{global-properties, WpProperties *, * G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY, * The pipewire global properties} * * \gproperty{permissions, guint, G_PARAM_READABLE, * The pipewire global permissions} */ typedef struct _WpGlobalProxyPrivate WpGlobalProxyPrivate; struct _WpGlobalProxyPrivate { WpGlobal *global; gchar factory_name[96]; WpProperties *properties; }; enum { PROP_0, PROP_GLOBAL, PROP_FACTORY_NAME, PROP_GLOBAL_PROPERTIES, PROP_PERMISSIONS, }; 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->properties, wp_properties_unref); 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; case PROP_FACTORY_NAME: priv->factory_name[0] = '\0'; strncpy (priv->factory_name, g_value_get_string (value), sizeof (priv->factory_name) - 1); break; case PROP_GLOBAL_PROPERTIES: priv->properties = 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_take_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) { /* we only support BOUND, so this is the only feature that can be in @em missing */ g_return_val_if_fail (missing == WP_PROXY_FEATURE_BOUND, WP_TRANSITION_STEP_ERROR); return STEP_BIND; } static void wp_global_proxy_step_bind (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { WpProxyClass *proxy_klass = WP_PROXY_GET_CLASS (WP_PROXY (object)); WpGlobalProxy *self = WP_GLOBAL_PROXY (object); WpGlobalProxyPrivate *priv = wp_global_proxy_get_instance_private (self); /* Create the pipewire object if global is NULL */ if (priv->global == NULL && priv->factory_name[0] != '\0') { g_autoptr (WpCore) core = NULL; struct pw_core *pw_core = NULL; struct pw_proxy * p = NULL; core = wp_object_get_core (WP_OBJECT (self)); if (G_UNLIKELY (!core)) { wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "The WirePlumber core is not valid; object cannot be created")); return; } pw_core = wp_core_get_pw_core (core); if (G_UNLIKELY (!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 created")); return; } p = pw_core_create_object (pw_core, priv->factory_name, proxy_klass->pw_iface_type, proxy_klass->pw_iface_version, priv->properties ? wp_properties_peek_dict (priv->properties) : NULL, 0); if (G_UNLIKELY (!p)) { wp_transition_return_error (WP_TRANSITION (transition), g_error_new ( WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "Failed to create object with given factory name and properties")); return; } wp_proxy_set_pw_proxy (WP_PROXY (self), p); } /* 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, "global not specified or destroyed; cannot bind proxy")); } } } static void wp_global_proxy_activate_execute_step (WpObject * object, WpFeatureActivationTransition * transition, guint step, WpObjectFeatures missing) { switch (step) { case STEP_BIND: wp_global_proxy_step_bind (object, transition, step, missing); 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, priv->properties ? wp_properties_peek_dict (priv->properties) : NULL, &priv->global); } } static void wp_global_proxy_destroyed (WpProxy * proxy) { WpGlobalProxy *self = WP_GLOBAL_PROXY (proxy); WpGlobalProxyPrivate *priv = wp_global_proxy_get_instance_private (self); g_clear_pointer (&priv->global, wp_global_unref); } 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; proxy_class->pw_proxy_destroyed = wp_global_proxy_destroyed; 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_FACTORY_NAME, g_param_spec_string ("factory-name", "factory-name", "The factory name", "", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | 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_READWRITE | 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)); } /*! * \brief 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's `pw-proxy-destroyed` signal will be emitted. If the server does * not allow it, nothing will happen. * * This is mostly useful for destroying WpLink objects. * * \ingroup wpglobalproxy * \param self the pipewire global */ 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); if (reg->pw_registry) pw_registry_destroy (reg->pw_registry, priv->global->id); } } /*! * \brief Gets the permissions of a pipewire global * \ingroup wpglobalproxy * \param 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; } /*! * \brief Gets the global properties of a pipewire global * \ingroup wpglobalproxy * \param 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; } /*! * \brief Binds to the global and creates the underlying `pw_proxy`. * * This is mostly meant to be called internally. It will create the `pw_proxy` * and will activate the WP_PROXY_FEATURE_BOUND feature. * * This may only be called if there is no `pw_proxy` associated with this * object yet. * * \ingroup wpglobalproxy * \param self the pipewire global * \returns TRUE on success, FALSE if there is no global to bind to */ gboolean wp_global_proxy_bind (WpGlobalProxy * self) { WpGlobalProxyPrivate *priv; struct pw_proxy *p; 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); priv = wp_global_proxy_get_instance_private (self); if (!priv->global || !priv->global->proxy) return FALSE; /* * We can bind only if the WpGlobal will unbind this proxy on removal, so * assert here that this is so. Why: pw_registry_bind can race with the global * id being replaced with a different object on server side, so we must rely * on the registry_global messages to know if the object was replaced. If the * race occurs, and a wrong object is going to be bound here, what will then * happen is that WpGlobal will dispose this proxy and no problem arises. */ g_return_val_if_fail (priv->global->proxy == self, FALSE); p = wp_global_bind (priv->global); if (!p) return FALSE; wp_proxy_set_pw_proxy (WP_PROXY (self), p); return TRUE; }