mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-15 22:08:08 +02:00
Now the WpPipewireObject interface is directly implemented by the mixin and there is another interface that users of the mixin must implement in order for the mixin to work proprely. A lot of manual stuff that proxy classes had to do before are now in the mixin. Also most of the data that would normally reside in Private structures is now in the mixin data structure (stored as qdata on the object). This is achieving the best amount of code reuse so far. For impl objects (WpImpl*) there are also default implementations of the standard pipewire object methods and the INFO & PARAM_* features are more coherently enabled during the whole lifetime of these objects.
542 lines
16 KiB
C
542 lines
16 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2019-2020 Collabora Ltd.
|
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
/**
|
|
* SECTION: device
|
|
* @title: PipeWire Device
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "wp-device"
|
|
|
|
#include "device.h"
|
|
#include "node.h"
|
|
#include "core.h"
|
|
#include "debug.h"
|
|
#include "private/pipewire-object-mixin.h"
|
|
|
|
#include <pipewire/impl.h>
|
|
#include <spa/monitor/device.h>
|
|
#include <spa/utils/result.h>
|
|
|
|
struct _WpDevice
|
|
{
|
|
WpGlobalProxy parent;
|
|
};
|
|
|
|
static void wp_device_pw_object_mixin_priv_interface_init (
|
|
WpPwObjectMixinPrivInterface * 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_pw_object_mixin_object_interface_init)
|
|
G_IMPLEMENT_INTERFACE (WP_TYPE_PW_OBJECT_MIXIN_PRIV,
|
|
wp_device_pw_object_mixin_priv_interface_init));
|
|
|
|
static void
|
|
wp_device_init (WpDevice * self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
wp_device_activate_execute_step (WpObject * object,
|
|
WpFeatureActivationTransition * transition, guint step,
|
|
WpObjectFeatures missing)
|
|
{
|
|
switch (step) {
|
|
case WP_PW_OBJECT_MIXIN_STEP_BIND:
|
|
case WP_TRANSITION_STEP_ERROR:
|
|
/* base class can handle BIND and ERROR */
|
|
WP_OBJECT_CLASS (wp_device_parent_class)->
|
|
activate_execute_step (object, transition, step, missing);
|
|
break;
|
|
case WP_PW_OBJECT_MIXIN_STEP_WAIT_INFO:
|
|
/* just wait, info will be emitted anyway after binding */
|
|
break;
|
|
case WP_PW_OBJECT_MIXIN_STEP_CACHE_PARAMS:
|
|
wp_pw_object_mixin_cache_params (object, missing);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_device_deactivate (WpObject * object, WpObjectFeatures features)
|
|
{
|
|
wp_pw_object_mixin_deactivate (object, features);
|
|
WP_OBJECT_CLASS (wp_device_parent_class)->deactivate (object, features);
|
|
}
|
|
|
|
static const struct pw_device_events device_events = {
|
|
PW_VERSION_DEVICE_EVENTS,
|
|
.info = (HandleEventInfoFunc(device)) wp_pw_object_mixin_handle_event_info,
|
|
.param = wp_pw_object_mixin_handle_event_param,
|
|
};
|
|
|
|
static void
|
|
wp_device_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
|
|
{
|
|
wp_pw_object_mixin_handle_pw_proxy_created (proxy, pw_proxy,
|
|
device, &device_events);
|
|
}
|
|
|
|
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->get_property = wp_pw_object_mixin_get_property;
|
|
|
|
wpobject_class->get_supported_features =
|
|
wp_pw_object_mixin_get_supported_features;
|
|
wpobject_class->activate_get_next_step =
|
|
wp_pw_object_mixin_activate_get_next_step;
|
|
wpobject_class->activate_execute_step = wp_device_activate_execute_step;
|
|
wpobject_class->deactivate = wp_device_deactivate;
|
|
|
|
proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Device;
|
|
proxy_class->pw_iface_version = PW_VERSION_DEVICE;
|
|
proxy_class->pw_proxy_created = wp_device_pw_proxy_created;
|
|
proxy_class->pw_proxy_destroyed =
|
|
wp_pw_object_mixin_handle_pw_proxy_destroyed;
|
|
|
|
wp_pw_object_mixin_class_override_properties (object_class);
|
|
}
|
|
|
|
static gint
|
|
wp_device_enum_params (gpointer instance, guint32 id,
|
|
guint32 start, guint32 num, WpSpaPod *filter)
|
|
{
|
|
WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
|
|
return pw_device_enum_params (d->iface, 0, id, start, num,
|
|
filter ? wp_spa_pod_get_spa_pod (filter) : NULL);
|
|
}
|
|
|
|
static gint
|
|
wp_device_set_param (gpointer instance, guint32 id, guint32 flags,
|
|
WpSpaPod * param)
|
|
{
|
|
WpPwObjectMixinData *d = wp_pw_object_mixin_get_data (instance);
|
|
return pw_device_set_param (d->iface, id, flags,
|
|
wp_spa_pod_get_spa_pod (param));
|
|
}
|
|
|
|
static void
|
|
wp_device_pw_object_mixin_priv_interface_init (
|
|
WpPwObjectMixinPrivInterface * iface)
|
|
{
|
|
wp_pw_object_mixin_priv_interface_info_init (iface, device, DEVICE);
|
|
iface->enum_params = wp_device_enum_params;
|
|
iface->set_param = wp_device_set_param;
|
|
}
|
|
|
|
/**
|
|
* wp_device_new_from_factory:
|
|
* @core: the wireplumber core
|
|
* @factory_name: the pipewire factory name to construct the device
|
|
* @properties: (nullable) (transfer full): the properties to pass to the factory
|
|
*
|
|
* Constructs a device on the PipeWire server by asking the remote factory
|
|
* @factory_name to create it.
|
|
*
|
|
* 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_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 activation operation
|
|
* will fail.
|
|
*
|
|
* Returns: (nullable) (transfer full): the new device or %NULL if the core
|
|
* is not connected and therefore the device cannot be created
|
|
*/
|
|
WpDevice *
|
|
wp_device_new_from_factory (WpCore * core,
|
|
const gchar * factory_name, WpProperties * properties)
|
|
{
|
|
g_autoptr (WpProperties) props = properties;
|
|
WpDevice *self = NULL;
|
|
struct pw_core *pw_core = wp_core_get_pw_core (core);
|
|
|
|
if (G_UNLIKELY (!pw_core)) {
|
|
g_critical ("The WirePlumber core is not connected; "
|
|
"device cannot be created");
|
|
return NULL;
|
|
}
|
|
|
|
self = g_object_new (WP_TYPE_DEVICE, "core", core, NULL);
|
|
wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_create_object (pw_core,
|
|
factory_name, PW_TYPE_INTERFACE_Device, PW_VERSION_DEVICE,
|
|
props ? wp_properties_peek_dict (props) : NULL, 0));
|
|
return self;
|
|
}
|
|
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_CORE,
|
|
PROP_SPA_DEVICE_HANDLE,
|
|
PROP_PROPERTIES,
|
|
};
|
|
|
|
struct _WpSpaDevice
|
|
{
|
|
GObject parent;
|
|
GWeakRef core;
|
|
struct spa_handle *handle;
|
|
struct spa_device *device;
|
|
struct spa_hook listener;
|
|
WpProperties *properties;
|
|
struct pw_proxy *proxy;
|
|
struct spa_hook proxy_listener;
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIGNAL_OBJECT_INFO,
|
|
SPA_DEVICE_LAST_SIGNAL,
|
|
};
|
|
|
|
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
|
|
wp_spa_device_init (WpSpaDevice * self)
|
|
{
|
|
g_weak_ref_init (&self->core, NULL);
|
|
self->properties = wp_properties_new_empty ();
|
|
}
|
|
|
|
static void
|
|
wp_spa_device_constructed (GObject *object)
|
|
{
|
|
WpSpaDevice *self = WP_SPA_DEVICE (object);
|
|
gint res;
|
|
|
|
g_return_if_fail (self->handle);
|
|
|
|
/* Get the handle interface */
|
|
res = spa_handle_get_interface (self->handle, SPA_TYPE_INTERFACE_Device,
|
|
(gpointer *) &self->device);
|
|
if (res < 0) {
|
|
wp_warning_object (self,
|
|
"Could not get device interface from SPA handle: %s",
|
|
spa_strerror (res));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_CLASS (wp_spa_device_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
wp_spa_device_finalize (GObject * object)
|
|
{
|
|
WpSpaDevice *self = WP_SPA_DEVICE (object);
|
|
|
|
g_clear_pointer (&self->proxy, pw_proxy_destroy);
|
|
self->device = NULL;
|
|
g_clear_pointer (&self->handle, pw_unload_spa_handle);
|
|
g_clear_pointer (&self->properties, wp_properties_unref);
|
|
g_weak_ref_clear (&self->core);
|
|
|
|
G_OBJECT_CLASS (wp_spa_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
wp_spa_device_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpSpaDevice *self = WP_SPA_DEVICE (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_CORE:
|
|
g_weak_ref_set (&self->core, g_value_get_object (value));
|
|
break;
|
|
case PROP_SPA_DEVICE_HANDLE:
|
|
self->handle = g_value_get_pointer (value);
|
|
break;
|
|
case PROP_PROPERTIES: {
|
|
WpProperties *p = g_value_get_boxed (value);
|
|
if (p)
|
|
wp_properties_update (self->properties, p);
|
|
break;
|
|
}
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_spa_device_get_property (GObject * object, guint property_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
WpSpaDevice *self = WP_SPA_DEVICE (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_CORE:
|
|
g_value_take_object (value, g_weak_ref_get (&self->core));
|
|
break;
|
|
case PROP_SPA_DEVICE_HANDLE:
|
|
g_value_set_pointer (value, self->handle);
|
|
break;
|
|
case PROP_PROPERTIES:
|
|
g_value_take_boxed (value, wp_properties_ref (self->properties));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
spa_device_event_info (void *data, const struct spa_device_info *info)
|
|
{
|
|
WpSpaDevice *self = WP_SPA_DEVICE (data);
|
|
|
|
/*
|
|
* This is emited syncrhonously at the time we add the listener and
|
|
* before object_info is emited. It gives us additional properties
|
|
* about the device, like the "api.alsa.card.*" ones that are not
|
|
* set by the monitor
|
|
*/
|
|
if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PROPS)
|
|
wp_properties_update_from_dict (self->properties, info->props);
|
|
}
|
|
|
|
static void
|
|
spa_device_event_object_info (void *data, uint32_t id,
|
|
const struct spa_device_object_info *info)
|
|
{
|
|
WpSpaDevice *self = WP_SPA_DEVICE (data);
|
|
GType type = G_TYPE_NONE;
|
|
g_autoptr (WpProperties) props = NULL;
|
|
|
|
if (info) {
|
|
if (!g_strcmp0 (info->type, SPA_TYPE_INTERFACE_Device))
|
|
type = WP_TYPE_DEVICE;
|
|
else if (!g_strcmp0 (info->type, SPA_TYPE_INTERFACE_Node))
|
|
type = WP_TYPE_NODE;
|
|
|
|
props = wp_properties_new_wrap_dict (info->props);
|
|
}
|
|
|
|
g_signal_emit (self, spa_device_signals[SIGNAL_OBJECT_INFO], 0, id, type,
|
|
info ? info->factory_name : NULL, props, self->properties);
|
|
}
|
|
|
|
static const struct spa_device_events spa_device_events = {
|
|
SPA_VERSION_DEVICE_EVENTS,
|
|
.info = spa_device_event_info,
|
|
.object_info = spa_device_event_object_info
|
|
};
|
|
|
|
static void
|
|
wp_spa_device_class_init (WpSpaDeviceClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
|
|
object_class->constructed = wp_spa_device_constructed;
|
|
object_class->finalize = wp_spa_device_finalize;
|
|
object_class->set_property = wp_spa_device_set_property;
|
|
object_class->get_property = wp_spa_device_get_property;
|
|
|
|
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_SPA_DEVICE_HANDLE,
|
|
g_param_spec_pointer ("spa-device-handle", "spa-device-handle",
|
|
"The spa device handle",
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_PROPERTIES,
|
|
g_param_spec_boxed ("properties", "properties",
|
|
"Properties of the device", WP_TYPE_PROPERTIES,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* WpSpaDevice::object-info:
|
|
* @self: the #WpSpaDevice
|
|
* @id: the id of the managed object
|
|
* @type: the #WpProxy subclass type that the managed object should have,
|
|
* or %G_TYPE_NONE if the object is being destroyed
|
|
* @factory: (nullable): the name of the SPA factory to use to construct
|
|
* the managed object, or %NULL if the object is being destroyed
|
|
* @properties: (nullable): additional properties that the managed object
|
|
* should have, or %NULL if the object is being destroyed
|
|
* @parent_props: the properties of the device itself
|
|
*
|
|
* This signal is emitted when the device is creating or destroying a managed
|
|
* object. The handler is expected to actually construct or destroy the
|
|
* object using the requested SPA @factory and with the given @properties.
|
|
*
|
|
* The handler may also use @parent_props to enrich the properties set
|
|
* that will be assigned on the object. @parent_props contains all the
|
|
* properties that this device object has.
|
|
*
|
|
* When the object is being created, @type can either be %WP_TYPE_DEVICE
|
|
* or %WP_TYPE_NODE. The handler is free to create a substitute of those,
|
|
* like %WP_TYPE_SPA_DEVICE instead of %WP_TYPE_DEVICE, depending on the
|
|
* use case.
|
|
*/
|
|
spa_device_signals[SIGNAL_OBJECT_INFO] = g_signal_new (
|
|
"object-info", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL, G_TYPE_NONE, 5, G_TYPE_UINT, G_TYPE_GTYPE,
|
|
G_TYPE_STRING, WP_TYPE_PROPERTIES, WP_TYPE_PROPERTIES);
|
|
}
|
|
|
|
/**
|
|
* wp_spa_device_new_wrap:
|
|
* @core: the wireplumber core
|
|
* @spa_device_handle: the spa device handle
|
|
* @properties: (nullable) (transfer full): additional properties of the device
|
|
*
|
|
* Returns: (transfer full): A new #WpSpaDevice
|
|
*/
|
|
WpSpaDevice *
|
|
wp_spa_device_new_wrap (WpCore * core, gpointer spa_device_handle,
|
|
WpProperties * properties)
|
|
{
|
|
g_autoptr (WpProperties) props = properties;
|
|
return g_object_new (WP_TYPE_SPA_DEVICE,
|
|
"core", core,
|
|
"spa-device-handle", spa_device_handle,
|
|
"properties", props,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* wp_spa_device_new_from_spa_factory:
|
|
* @core: the wireplumber core
|
|
* @factory_name: the name of the SPA factory
|
|
* @properties: (nullable) (transfer full): properties to be passed to device
|
|
* constructor
|
|
*
|
|
* Constructs a `SPA_TYPE_INTERFACE_Device` by loading the given SPA
|
|
* @factory_name.
|
|
*
|
|
* To export this device to the PipeWire server, you need to call
|
|
* wp_proxy_augment() requesting %WP_PROXY_FEATURE_BOUND and
|
|
* wait for the operation to complete.
|
|
*
|
|
* Returns: (nullable) (transfer full): A new #WpSpaDevice wrapping the
|
|
* device that was constructed by the factory, or %NULL if the factory
|
|
* does not exist or was unable to construct the device
|
|
*/
|
|
WpSpaDevice *
|
|
wp_spa_device_new_from_spa_factory (WpCore * core,
|
|
const gchar * factory_name, WpProperties * properties)
|
|
{
|
|
g_autoptr (WpProperties) props = properties;
|
|
struct pw_context *pw_context = wp_core_get_pw_context (core);
|
|
struct spa_handle *handle = NULL;
|
|
|
|
g_return_val_if_fail (pw_context != NULL, NULL);
|
|
|
|
/* Load the monitor handle */
|
|
handle = pw_context_load_spa_handle (pw_context, factory_name,
|
|
props ? wp_properties_peek_dict (props) : NULL);
|
|
if (!handle) {
|
|
wp_warning ("SPA handle '%s' could not be loaded; is it installed?",
|
|
factory_name);
|
|
return NULL;
|
|
}
|
|
|
|
return wp_spa_device_new_wrap (core, handle, g_steal_pointer (&props));
|
|
}
|
|
|
|
guint32
|
|
wp_spa_device_get_bound_id (WpSpaDevice * self)
|
|
{
|
|
g_return_val_if_fail (WP_IS_SPA_DEVICE (self), SPA_ID_INVALID);
|
|
return self->proxy ? pw_proxy_get_bound_id (self->proxy) : SPA_ID_INVALID;
|
|
}
|
|
|
|
static void
|
|
proxy_event_bound (void *data, uint32_t global_id)
|
|
{
|
|
GTask *task = G_TASK (data);
|
|
WpSpaDevice *self = g_task_get_source_object (task);
|
|
|
|
spa_hook_remove (&self->proxy_listener);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static const struct pw_proxy_events proxy_events = {
|
|
PW_VERSION_PROXY_EVENTS,
|
|
.bound = proxy_event_bound,
|
|
};
|
|
|
|
void
|
|
wp_spa_device_export (WpSpaDevice * self, GCancellable * cancellable,
|
|
GAsyncReadyCallback callback, gpointer user_data)
|
|
{
|
|
g_autoptr (GTask) task = NULL;
|
|
|
|
g_return_if_fail (WP_IS_SPA_DEVICE (self));
|
|
g_return_if_fail (!self->proxy);
|
|
|
|
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
|
|
struct pw_core *pw_core = wp_core_get_pw_core (core);
|
|
|
|
g_return_if_fail (pw_core);
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
self->proxy = pw_core_export (pw_core,
|
|
SPA_TYPE_INTERFACE_Device,
|
|
wp_properties_peek_dict (self->properties),
|
|
self->device, 0);
|
|
pw_proxy_add_listener (self->proxy, &self->proxy_listener,
|
|
&proxy_events, g_steal_pointer (&task));
|
|
}
|
|
|
|
gboolean
|
|
wp_spa_device_export_finish (WpSpaDevice * self, GAsyncResult * res,
|
|
GError ** error)
|
|
{
|
|
g_return_val_if_fail (WP_IS_SPA_DEVICE (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_spa_device_activate (WpSpaDevice * self)
|
|
{
|
|
g_return_if_fail (WP_IS_SPA_DEVICE (self));
|
|
|
|
gint res = spa_device_add_listener (self->device, &self->listener,
|
|
&spa_device_events, self);
|
|
if (res < 0)
|
|
wp_warning_object (self, "failed to activate device: %s",
|
|
spa_strerror (res));
|
|
}
|