lib: refactor WpProxy

This is an attempt to unclutter the API of WpProxy and
split functionality into smaller pieces, making it easier
to work with.

In this new class layout, we have the following classes:

- WpObject: base class for everything; handles activating
|           and deactivating "features"
|- WpProxy: base class for anything that wraps a pw_proxy;
 |          handles events from pw_proxy and nothing more
 |- WpGlobalProxy: handles integration with the registry

All the other classes derive from WpGlobalProxy. The reason
for separating WpGlobalProxy from WpProxy, though, is that
classes such as WpImplNode / WpSpaDevice can also derive from
WpProxy now, without interfacing with the registry.

All objects that come with an "info" structure and have properties
and/or params also implement the WpPipewireObject interface. This
provides the API to query properties and get/set params. Essentially,
this is implemented by all classes except WpMetadata (pw_metadata
does not have info)

This interface is implemented on each object separately, using
a private "mixin", which is a set of vfunc implementations and helper
functions (and macros) to facilitate the implementation of this interface.

A notable difference to the old WpProxy is that now features can be
deactivated, so it is possible to enable something and later disable
it again.

This commit disables modules, tests, tools, etc, to avoid growing the
patch more, while ensuring that the project compiles.
This commit is contained in:
George Kiagiadakis 2020-11-10 19:17:02 +02:00
parent 4dec10396a
commit 2f3f5f8e66
38 changed files with 2876 additions and 2859 deletions

View file

@ -7,55 +7,60 @@
*/
/**
* SECTION: WpClient
* SECTION: client
* @title: PipeWire Client
*/
#define G_LOG_DOMAIN "wp-client"
#include "client.h"
#include "private/pipewire-object-mixin.h"
struct _WpClient
{
WpGlobalProxy parent;
struct pw_client_info *info;
struct spa_hook listener;
};
static void wp_client_pipewire_object_interface_init (WpPipewireObjectInterface * iface);
/**
* 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.
*/
#include "client.h"
#include "private.h"
#include <pipewire/pipewire.h>
struct _WpClient
{
WpProxy parent;
struct pw_client_info *info;
/* The client proxy listener */
struct spa_hook listener;
};
G_DEFINE_TYPE (WpClient, wp_client, WP_TYPE_PROXY)
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_init (WpClient * self)
{
}
static WpObjectFeatures
wp_client_get_supported_features (WpObject * object)
{
return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO;
}
static void
wp_client_finalize (GObject * object)
wp_client_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
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)
{
return WP_CLIENT (self)->info;
}
static WpProperties *
wp_client_get_properties (WpProxy * self)
{
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);
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_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->get_info = wp_client_get_info;
proxy_class->get_properties = wp_client_get_properties;
proxy_class->pw_proxy_created = wp_client_pw_proxy_created;
proxy_class->pw_proxy_destroyed = wp_client_pw_proxy_destroyed;
wp_pipewire_object_mixin_class_override_properties (object_class);
}
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;
}
/**

View file

@ -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, ...);

View file

@ -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));
}

View file

@ -7,7 +7,31 @@
*/
/**
* SECTION: WpDevice
* SECTION: device
* @title: PipeWire Device
*/
#define G_LOG_DOMAIN "wp-device"
#include "device.h"
#include "node.h"
#include "core.h"
#include "private/pipewire-object-mixin.h"
#include <pipewire/impl.h>
#include <spa/monitor/device.h>
struct _WpDevice
{
WpGlobalProxy parent;
struct pw_device_info *info;
struct spa_hook listener;
};
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`).
@ -17,154 +41,156 @@
* 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.
*/
#define G_LOG_DOMAIN "wp-device"
#include "device.h"
#include "debug.h"
#include "node.h"
#include "error.h"
#include "private.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
{
struct pw_device_info *info;
struct spa_hook listener;
};
G_DEFINE_TYPE_WITH_PRIVATE (WpDevice, wp_device, WP_TYPE_PROXY)
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);
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 gconstpointer
wp_device_get_info (WpProxy * self)
static void
wp_device_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpDevicePrivate *priv = wp_device_get_instance_private (WP_DEVICE (self));
return priv->info;
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 WpProperties *
wp_device_get_properties (WpProxy * self)
static void
wp_device_deactivate (WpObject * object, WpObjectFeatures features)
{
WpDevicePrivate *priv = wp_device_get_instance_private (WP_DEVICE (self));
return wp_properties_new_wrap_dict (priv->info->props);
}
wp_pipewire_object_mixin_deactivate (object, features);
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)
{
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);
}
static gint
wp_device_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids)
{
struct pw_device *pwp = (struct pw_device *) wp_proxy_get_pw_proxy (self);
return pw_device_subscribe_params (pwp, ids, n_ids);
}
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);
self->info = pw_device_info_update (self->info, 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_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->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;
proxy_class->pw_proxy_created = wp_device_pw_proxy_created;
proxy_class->pw_proxy_destroyed = wp_device_pw_proxy_destroyed;
wp_pipewire_object_mixin_class_override_properties (object_class);
}
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

View file

@ -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,

View file

@ -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);
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 gconstpointer
wp_endpoint_link_get_info (WpProxy * proxy)
static void
wp_endpoint_link_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpEndpointLink *self = WP_ENDPOINT_LINK (proxy);
WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self);
return priv->info;
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 WpProperties *
wp_endpoint_link_get_properties (WpProxy * proxy)
static void
wp_endpoint_link_deactivate (WpObject * object, WpObjectFeatures features)
{
WpEndpointLink *self = WP_ENDPOINT_LINK (proxy);
WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self);
wp_pipewire_object_mixin_deactivate (object, features);
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)
{
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);
}
static gint
wp_endpoint_link_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids)
{
WpEndpointLinkPrivate *priv =
wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (self));
return pw_endpoint_link_subscribe_params (priv->iface, ids, n_ids);
}
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,

View file

@ -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

View file

@ -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,7 +35,22 @@ 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);
/**
* 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_init (WpEndpointStream * self)
@ -59,19 +58,7 @@ wp_endpoint_stream_init (WpEndpointStream * self)
}
static void
wp_endpoint_stream_finalize (GObject * object)
{
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)
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);
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_stream_get_properties (WpProxy * proxy)
static void
wp_endpoint_stream_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy);
WpEndpointStreamPrivate *priv = wp_endpoint_stream_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;
default:
WP_OBJECT_CLASS (wp_endpoint_stream_parent_class)->
activate_execute_step (object, transition, step, missing);
break;
}
}
static struct spa_param_info *
wp_endpoint_stream_get_param_info (WpProxy * proxy, guint * n_params)
static void
wp_endpoint_stream_deactivate (WpObject * object, WpObjectFeatures features)
{
WpEndpointStream *self = WP_ENDPOINT_STREAM (proxy);
WpEndpointStreamPrivate *priv = wp_endpoint_stream_get_instance_private (self);
wp_pipewire_object_mixin_deactivate (object, features);
*n_params = priv->info->n_params;
return priv->info->params;
}
static gint
wp_endpoint_stream_enum_params (WpProxy * self, guint32 id, guint32 start,
guint32 num, WpSpaPod * filter)
{
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);
}
static gint
wp_endpoint_stream_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids)
{
WpEndpointStreamPrivate *priv =
wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (self));
return pw_endpoint_stream_subscribe_params (priv->iface, ids, n_ids);
}
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)
enum {
STEP_ACTIVATE_NODE = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START,
};
static guint
wp_impl_endpoint_stream_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
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);
WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object);
g_autoptr (GVariantIter) immutable_properties = NULL;
g_autoptr (WpProperties) properties = NULL;
const gchar *key, *value;
/* 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);
/* 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;
/* 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;
}
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 = 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;
WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (object);
WpEndpointStreamPrivate *priv =
wp_endpoint_stream_get_instance_private (WP_ENDPOINT_STREAM (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_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,

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

293
lib/wp/global-proxy.c Normal file
View file

@ -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;
}

47
lib/wp/global-proxy.h Normal file
View file

@ -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

View file

@ -1,13 +1,32 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* Copyright © 2019-2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* SECTION: WpLink
* SECTION: link
* @title: PipeWire Link
*/
#define G_LOG_DOMAIN "wp-link"
#include "link.h"
#include "private/pipewire-object-mixin.h"
struct _WpLink
{
WpGlobalProxy parent;
struct pw_link_info *info;
struct spa_hook listener;
};
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`).
@ -18,50 +37,34 @@
* wp_link_new_from_factory(), which creates a new link object
* on the remote PipeWire server by calling into a factory.
*/
#define G_LOG_DOMAIN "wp-link"
#include "link.h"
#include "private.h"
#include <pipewire/pipewire.h>
struct _WpLink
{
WpProxy parent;
struct pw_link_info *info;
/* The link proxy listener */
struct spa_hook listener;
};
G_DEFINE_TYPE (WpLink, wp_link, WP_TYPE_PROXY)
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 WpObjectFeatures
wp_link_get_supported_features (WpObject * object)
{
return WP_PROXY_FEATURE_BOUND | WP_PIPEWIRE_OBJECT_FEATURE_INFO;
}
static void
wp_link_finalize (GObject * object)
wp_link_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpLink *self = WP_LINK (object);
g_clear_pointer (&self->info, pw_link_info_free);
G_OBJECT_CLASS (wp_link_parent_class)->finalize (object);
}
static gconstpointer
wp_link_get_info (WpProxy * self)
{
return WP_LINK (self)->info;
}
static WpProperties *
wp_link_get_properties (WpProxy * self)
{
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);
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_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->get_info = wp_link_get_info;
proxy_class->get_properties = wp_link_get_properties;
proxy_class->pw_proxy_created = wp_link_pw_proxy_created;
proxy_class->pw_proxy_destroyed = wp_link_pw_proxy_destroyed;
wp_pipewire_object_mixin_class_override_properties (object_class);
}
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

View file

@ -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,

View file

@ -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',

View file

@ -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 *

View file

@ -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

View file

@ -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);
/**
* 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_init (WpNode * self)
{
}
static void
wp_node_finalize (GObject * object)
{
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));
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);
wp_debug_object (self, "enabling WP_NODE_FEATURE_PORTS, bound_id:%u",
bound_id);
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);
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);
wp_core_install_object_manager (core, self->ports_om);
}
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);
static WpObjectFeatures
wp_node_get_supported_features (WpObject * object)
{
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);
}
wp_core_install_object_manager (core, priv->ports_om);
enum {
STEP_PORTS = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START,
};
static guint
wp_node_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
step = wp_pipewire_object_mixin_activate_get_next_step (object, transition,
step, missing);
/* 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;
return step;
}
static void
wp_node_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_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 void
wp_node_augment (WpProxy * proxy, WpProxyFeatures features)
wp_node_deactivate (WpObject * object, WpObjectFeatures features)
{
WpNode *self = WP_NODE (proxy);
WpNodePrivate *priv = wp_node_get_instance_private (self);
WpNode *self = WP_NODE (object);
/* call the parent impl first to ensure we have a pw proxy if necessary */
WP_PROXY_CLASS (wp_node_parent_class)->augment (proxy, features);
wp_pipewire_object_mixin_deactivate (object, features);
if (features & WP_NODE_FEATURE_PORTS) {
priv->ft_ports_requested = TRUE;
wp_node_ensure_feature_ports (self, 0);
g_clear_object (&self->ports_om);
wp_object_update_features (object, 0, WP_NODE_FEATURE_PORTS);
}
}
static gconstpointer
wp_node_get_info (WpProxy * self)
{
WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (self));
return priv->info;
}
static WpProperties *
wp_node_get_properties (WpProxy * self)
{
WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (self));
return wp_properties_new_wrap_dict (priv->info->props);
}
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));
*n_params = priv->info->n_params;
return priv->info->params;
}
static gint
wp_node_enum_params (WpProxy * self, guint32 id, guint32 start, guint32 num,
WpSpaPod * filter)
{
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);
}
static gint
wp_node_subscribe_params (WpProxy * self, guint32 *ids, guint32 n_ids)
{
struct pw_node *pwp = (struct pw_node *) wp_proxy_get_pw_proxy (self);
return pw_node_subscribe_params (pwp, ids, n_ids);
}
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_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;
enum pw_node_state old_state = self->info ?
self->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);
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

View file

@ -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,

View file

@ -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 */

View file

@ -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
*/

View file

@ -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)

View file

@ -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 */

View file

@ -1,13 +1,32 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* Copyright © 2019-2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* SECTION: WpPort
* SECTION: port
* @title: PipeWire Port
*/
#define G_LOG_DOMAIN "wp-port"
#include "port.h"
#include "private/pipewire-object-mixin.h"
struct _WpPort
{
WpGlobalProxy parent;
struct pw_port_info *info;
struct spa_hook listener;
};
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`).
@ -15,77 +34,47 @@
* A #WpPort is constructed internally when a new port appears on the
* PipeWire registry and it is made available through the #WpObjectManager API.
*/
#define G_LOG_DOMAIN "wp-port"
#include "port.h"
#include "private.h"
#include <pipewire/pipewire.h>
/**
* WpPort:
*/
struct _WpPort
{
WpProxy parent;
struct pw_port_info *info;
/* The port proxy listener */
struct spa_hook listener;
};
G_DEFINE_TYPE (WpPort, wp_port, WP_TYPE_PROXY)
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);
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 gconstpointer
wp_port_get_info (WpProxy * self)
static void
wp_port_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
return WP_PORT (self)->info;
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 WpProperties *
wp_port_get_properties (WpProxy * self)
static void
wp_port_deactivate (WpObject * object, WpObjectFeatures features)
{
return wp_properties_new_wrap_dict (WP_PORT (self)->info->props);
}
wp_pipewire_object_mixin_deactivate (object, features);
static struct spa_param_info *
wp_port_get_param_info (WpProxy * proxy, guint * n_params)
{
WpPort *self = WP_PORT (proxy);
*n_params = self->info->n_params;
return self->info->params;
}
static gint
wp_port_enum_params (WpProxy * self, guint32 id, guint32 start, guint32 num,
WpSpaPod * filter)
{
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);
}
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->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;
proxy_class->pw_proxy_created = wp_port_pw_proxy_created;
proxy_class->pw_proxy_destroyed = wp_port_pw_proxy_destroyed;
wp_pipewire_object_mixin_class_override_properties (object_class);
}
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;
}

View file

@ -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);

View file

@ -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 {

View file

@ -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);
}

View file

@ -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

View file

@ -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;
};

File diff suppressed because it is too large Load diff

View file

@ -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
struct pw_proxy * wp_proxy_get_pw_proxy (WpProxy * self);
/* for subclasses only */
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);
WP_API
WpSpaPod * wp_proxy_get_prop (WpProxy * self, const gchar * prop_name);
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

View file

@ -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;

View file

@ -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);
/**
* 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_init (WpSession * self)
{
}
static void
wp_session_finalize (GObject * object)
{
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;
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
guint32 bound_id = wp_proxy_get_bound_id (WP_PROXY (self));
if (!(ft & WP_PROXY_FEATURE_BOUND))
return;
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)
enum {
STEP_CHILDREN = WP_PIPEWIRE_OBJECT_MIXIN_STEP_CUSTOM_START,
};
static guint
wp_session_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpSession *self = WP_SESSION (proxy);
step = wp_pipewire_object_mixin_activate_get_next_step (object, transition,
step, missing);
/* 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;
return step;
}
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 void
wp_session_deactivate (WpObject * object, WpObjectFeatures features)
{
WpSession *self = WP_SESSION (object);
WpSessionPrivate *priv = wp_session_get_instance_private (self);
return wp_properties_ref (priv->properties);
}
wp_pipewire_object_mixin_deactivate (object, features);
static struct spa_param_info *
wp_session_get_param_info (WpProxy * proxy, guint * n_params)
{
WpSession *self = WP_SESSION (proxy);
WpSessionPrivate *priv = wp_session_get_instance_private (self);
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);
}
*n_params = priv->info->n_params;
return priv->info->params;
}
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);
}
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 gint
wp_session_set_param (WpProxy * self, guint32 id, guint32 flags,
WpSpaPod *param)
{
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));
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");
wp_object_update_features (WP_OBJECT (self),
WP_PIPEWIRE_OBJECT_FEATURE_INFO, 0);
if (info->change_mask & PW_SESSION_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
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);
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 (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);
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,89 +343,82 @@ wp_session_class_init (WpSessionClass * klass)
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}
static gconstpointer
wp_session_get_native_info (WpPipewireObject * obj)
{
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_ref (priv->properties);
}
static GVariant *
wp_session_get_param_info (WpPipewireObject * obj)
{
WpSession *self = WP_SESSION (obj);
WpSessionPrivate *priv = wp_session_get_instance_private (self);
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_get_name:
* @self: the session
*
* Requires %WP_PIPEWIRE_OBJECT_FEATURE_INFO
*
* Returns: (transfer none): the (unique) name of the session
*/
const gchar *
wp_session_get_name (WpSession * self)
{
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);
g_return_val_if_fail (wp_object_get_active_features (WP_OBJECT (self)) &
WP_PIPEWIRE_OBJECT_FEATURE_INFO, NULL);
WpSessionPrivate *priv = wp_session_get_instance_private (self);
return wp_properties_get (priv->properties, "session.name");
}
/**
* 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)
{
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;
}
pod = wp_proxy_get_prop (WP_PROXY (self), id_name);
if (pod && wp_spa_pod_get_int (pod, &value))
return (guint32) value;
return 0;
}
/**
* wp_session_set_default_endpoint:
* @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
*/
void
wp_session_set_default_endpoint (WpSession * self, WpDirection direction,
guint32 id)
{
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;
}
wp_proxy_set_prop (WP_PROXY (self), id_name, wp_spa_pod_new_int (id));
}
/**
* wp_session_get_n_endpoints:
* @self: the session
@ -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;

View file

@ -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

View file

@ -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);
}

View file

@ -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"

View file

@ -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')