proxy/core: refactor object creation

* core no longer exposes create_remote/local_object
* node, device & link have constructor methods
  to enable the create_remote_object functionality
* added WpImplNode to wrap pw_impl_node and allow creating
  "local" node instances
* added WpSpaDevice to wrap spa_device and allow creating
  "local" device instances
* exporting objects in all cases now happens by requesting
  FEATURE_BOUND from the proxy, eliminating the need for WpExported
* replaced WpMonitor by new, simpler code directly in module-monitor
* the proxy type lookup table in WpProxy is gone, we now
  use a field on the class structure of every WpProxy subclass
  and iterate through all the class structures instead; this is
  more flexible and extensible
This commit is contained in:
George Kiagiadakis 2020-01-30 17:41:25 +02:00
parent d8ae151aba
commit 9330208ada
26 changed files with 1040 additions and 992 deletions

View file

@ -55,12 +55,12 @@ 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);
g_object_notify (G_OBJECT (self), "info");
if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
}
static const struct pw_client_events client_events = {
@ -84,6 +84,9 @@ wp_client_class_init (WpClientClass * klass)
object_class->finalize = wp_client_finalize;
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;

View file

@ -7,10 +7,7 @@
*/
#include "core.h"
#include "error.h"
#include "object-manager.h"
#include "proxy.h"
#include "wpenums.h"
#include "wp.h"
#include "private.h"
#include <pipewire/pipewire.h>
@ -411,6 +408,16 @@ wp_core_class_init (WpCoreClass * klass)
signals[SIGNAL_DISCONNECTED] = g_signal_new ("disconnected",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
/* ensure WpProxy subclasses are loaded, which is needed to be able
to autodetect the GType of proxies created through wp_proxy_new_global() */
g_type_ensure (WP_TYPE_CLIENT);
g_type_ensure (WP_TYPE_DEVICE);
g_type_ensure (WP_TYPE_PROXY_ENDPOINT);
g_type_ensure (WP_TYPE_LINK);
g_type_ensure (WP_TYPE_NODE);
g_type_ensure (WP_TYPE_PORT);
g_type_ensure (WP_TYPE_PROXY_SESSION);
}
WpCore *
@ -436,6 +443,13 @@ wp_core_get_pw_context (WpCore * self)
return self->pw_context;
}
struct pw_core *
wp_core_get_pw_core (WpCore * self)
{
g_return_val_if_fail (WP_IS_CORE (self), NULL);
return self->pw_core;
}
struct pw_registry *
wp_core_get_pw_registry (WpCore * self)
{
@ -554,83 +568,6 @@ wp_core_sync_finish (WpCore * self, GAsyncResult * res, GError ** error)
return g_task_propagate_boolean (G_TASK (res), error);
}
WpProxy *
wp_core_export_object (WpCore * self, const gchar * interface_type,
gpointer local_object, WpProperties * properties)
{
struct pw_proxy *proxy = NULL;
const char *type;
guint32 version;
g_return_val_if_fail (WP_IS_CORE (self), NULL);
g_return_val_if_fail (self->pw_core, NULL);
proxy = pw_core_export (self->pw_core, interface_type,
properties ? wp_properties_peek_dict (properties) : NULL,
local_object, 0);
if (!proxy)
return NULL;
type = pw_proxy_get_type (proxy, &version);
return wp_proxy_new_wrap (self, proxy, type, version, NULL);
}
WpProxy *
wp_core_create_local_object (WpCore * self, const gchar * factory_name,
const gchar *interface_type, guint32 interface_version,
WpProperties * properties)
{
struct pw_proxy *pw_proxy = NULL;
struct pw_impl_factory *factory = NULL;
gpointer local_object = NULL;
g_return_val_if_fail (WP_IS_CORE (self), NULL);
g_return_val_if_fail (self->pw_core, NULL);
factory = pw_context_find_factory (self->pw_context, factory_name);
if (!factory)
return NULL;
local_object = pw_impl_factory_create_object (factory,
NULL,
interface_type,
interface_version,
properties ? wp_properties_to_pw_properties (properties) : NULL,
0);
if (!local_object)
return NULL;
pw_proxy = pw_core_export (self->pw_core,
interface_type,
properties ? wp_properties_peek_dict (properties) : NULL,
local_object,
0);
if (!pw_proxy) {
wp_proxy_local_object_destroy_for_type (interface_type, local_object);
return NULL;
}
return wp_proxy_new_wrap (self, pw_proxy, interface_type, interface_version,
local_object);
}
WpProxy *
wp_core_create_remote_object (WpCore *self,
const gchar *factory_name, const gchar * interface_type,
guint32 interface_version, WpProperties * properties)
{
struct pw_proxy *pw_proxy;
g_return_val_if_fail (WP_IS_CORE (self), NULL);
g_return_val_if_fail (self->pw_core, NULL);
pw_proxy = pw_core_create_object (self->pw_core, factory_name,
interface_type, interface_version,
properties ? wp_properties_peek_dict (properties) : NULL, 0);
return wp_proxy_new_wrap (self, pw_proxy, interface_type, interface_version,
NULL);
}
/**
* wp_core_find_object: (skip)
* @self: the core

View file

@ -16,6 +16,7 @@
G_BEGIN_DECLS
struct pw_context;
struct pw_core;
#define WP_TYPE_CORE (wp_core_get_type ())
WP_API
@ -32,6 +33,9 @@ GMainContext * wp_core_get_context (WpCore * self);
WP_API
struct pw_context * wp_core_get_pw_context (WpCore * self);
WP_API
struct pw_core * wp_core_get_pw_core (WpCore * self);
/* Connection */
WP_API
@ -57,22 +61,6 @@ WP_API
gboolean wp_core_sync_finish (WpCore * self, GAsyncResult * res,
GError ** error);
/* Object */
WP_API
WpProxy * wp_core_export_object (WpCore * self, const gchar * interface_type,
gpointer local_object, WpProperties * properties);
WP_API
WpProxy * wp_core_create_local_object (WpCore * self,
const gchar *factory_name, const gchar * interface_type,
guint32 interface_version, WpProperties * properties);
WP_API
WpProxy * wp_core_create_remote_object (WpCore * self,
const gchar * factory_name, const gchar * interface_type,
guint32 interface_version, WpProperties * properties);
/* Object Manager */
WP_API

View file

@ -1,26 +1,34 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* Copyright © 2019-2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "device.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;
struct pw_device_info *info;
};
/* The device proxy listener */
typedef struct _WpDevicePrivate WpDevicePrivate;
struct _WpDevicePrivate
{
struct pw_device_info *info;
struct spa_hook listener;
};
G_DEFINE_TYPE (WpDevice, wp_device, WP_TYPE_PROXY)
G_DEFINE_TYPE_WITH_PRIVATE (WpDevice, wp_device, WP_TYPE_PROXY)
static void
wp_device_init (WpDevice * self)
@ -31,8 +39,9 @@ static void
wp_device_finalize (GObject * object)
{
WpDevice *self = WP_DEVICE (object);
WpDevicePrivate *priv = wp_device_get_instance_private (self);
g_clear_pointer (&self->info, pw_device_info_free);
g_clear_pointer (&priv->info, pw_device_info_free);
G_OBJECT_CLASS (wp_device_parent_class)->finalize (object);
}
@ -40,13 +49,15 @@ wp_device_finalize (GObject * object)
static gconstpointer
wp_device_get_info (WpProxy * self)
{
return WP_DEVICE (self)->info;
WpDevicePrivate *priv = wp_device_get_instance_private (WP_DEVICE (self));
return priv->info;
}
static WpProperties *
wp_device_get_properties (WpProxy * self)
{
return wp_properties_new_wrap_dict (WP_DEVICE (self)->info->props);
WpDevicePrivate *priv = wp_device_get_instance_private (WP_DEVICE (self));
return wp_properties_new_wrap_dict (priv->info->props);
}
static gint
@ -82,14 +93,15 @@ 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);
g_object_notify (G_OBJECT (self), "info");
if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
}
static const struct pw_device_events device_events = {
@ -102,8 +114,9 @@ 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,
&self->listener, &device_events, self);
&priv->listener, &device_events, self);
}
static void
@ -114,6 +127,9 @@ wp_device_class_init (WpDeviceClass * klass)
object_class->finalize = wp_device_finalize;
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->enum_params = wp_device_enum_params;
@ -121,3 +137,320 @@ wp_device_class_init (WpDeviceClass * klass)
proxy_class->pw_proxy_created = wp_device_pw_proxy_created;
}
/**
* wp_device_new_from_factory:
* @core: the wireplumber core
* @factory_name: the pipewire factory name to construct the device
* @properties: (nullable) (transfer full): the properties to pass to the factory
*
* Constructs a device on the PipeWire server by asking the remote factory
* @factory_name to create it.
*
* Because of the nature of the PipeWire protocol, this operation completes
* asynchronously at some point in the future. In order to find out when
* this is done, you should call wp_proxy_augment(), 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
* will fail.
*
* Returns: (nullable) (transfer full): the new device or %NULL if the core
* is not connected and therefore the device cannot be created
*/
WpDevice *
wp_device_new_from_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties)
{
g_autoptr (WpProperties) props = properties;
WpDevice *self = NULL;
struct pw_core *pw_core = wp_core_get_pw_core (core);
if (!pw_core) {
g_warning ("The WirePlumber core is not connected; device cannot be created");
return NULL;
}
self = g_object_new (WP_TYPE_DEVICE, "core", core, NULL);
wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_create_object (pw_core,
factory_name, PW_TYPE_INTERFACE_Device, PW_VERSION_DEVICE,
props ? wp_properties_peek_dict (props) : NULL, 0));
return self;
}
struct _WpSpaDevice
{
WpDevice parent;
struct spa_handle *handle;
struct spa_device *interface;
struct spa_hook listener;
WpProperties *properties;
};
enum
{
SIGNAL_OBJECT_INFO,
SPA_DEVICE_LAST_SIGNAL,
};
static guint spa_device_signals[SPA_DEVICE_LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (WpSpaDevice, wp_spa_device, WP_TYPE_PROXY)
static void
wp_spa_device_init (WpSpaDevice * self)
{
}
static void
wp_spa_device_finalize (GObject * object)
{
WpSpaDevice *self = WP_SPA_DEVICE (object);
g_clear_pointer (&self->handle, pw_unload_spa_handle);
g_clear_pointer (&self->properties, wp_properties_unref);
G_OBJECT_CLASS (wp_spa_device_parent_class)->finalize (object);
}
static void
spa_device_event_info (void *data, const struct spa_device_info *info)
{
WpSpaDevice *self = WP_SPA_DEVICE (data);
/*
* This is emited syncrhonously at the time we add the listener and
* before object_info is emited. It gives us additional properties
* about the device, like the "api.alsa.card.*" ones that are not
* set by the monitor
*/
if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PROPS)
wp_properties_update_from_dict (self->properties, info->props);
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
wp_proxy_set_feature_ready (WP_PROXY (self), WP_SPA_DEVICE_FEATURE_ACTIVE);
}
static void
spa_device_event_result (void *data, int seq, int res, uint32_t type,
const void *result)
{
if (type != SPA_RESULT_TYPE_DEVICE_PARAMS)
return;
const struct spa_result_device_params *srdp = result;
wp_proxy_handle_event_param (WP_PROXY (data), seq, srdp->id, srdp->index,
srdp->next, srdp->param);
}
static void
spa_device_event_object_info (void *data, uint32_t id,
const struct spa_device_object_info *info)
{
WpSpaDevice *self = WP_SPA_DEVICE (data);
GType type = G_TYPE_NONE;
g_autoptr (WpProperties) props = NULL;
if (info) {
if (!g_strcmp0 (info->type, SPA_TYPE_INTERFACE_Device))
type = WP_TYPE_DEVICE;
else if (!g_strcmp0 (info->type, SPA_TYPE_INTERFACE_Node))
type = WP_TYPE_NODE;
props = wp_properties_new_wrap_dict (info->props);
}
g_signal_emit (self, spa_device_signals[SIGNAL_OBJECT_INFO], 0, id, type,
info ? info->factory_name : NULL, props, self->properties);
}
static const struct spa_device_events spa_device_events = {
SPA_VERSION_DEVICE_EVENTS,
.info = spa_device_event_info,
.result = spa_device_event_result,
.object_info = spa_device_event_object_info
};
static void
wp_spa_device_augment (WpProxy * proxy, WpProxyFeatures features)
{
WpSpaDevice *self = WP_SPA_DEVICE (proxy);
/* if any of the standard features is requested, make sure BOUND
is also requested, as they all depend on binding the pw_spa_device */
if (features & WP_PROXY_FEATURES_STANDARD)
features |= WP_PROXY_FEATURE_BOUND;
if (features & WP_PROXY_FEATURE_BOUND) {
g_autoptr (WpCore) core = wp_proxy_get_core (proxy);
struct pw_core *pw_core = wp_core_get_pw_core (core);
/* 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;
}
/* export to get a proxy; feature will complete
when the pw_proxy.bound event will be called. */
wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core,
SPA_TYPE_INTERFACE_Device,
wp_properties_peek_dict (self->properties),
self->interface, 0));
}
if (features & WP_SPA_DEVICE_FEATURE_ACTIVE) {
gint res = spa_device_add_listener (self->interface, &self->listener,
&spa_device_events, self);
if (res < 0) {
wp_proxy_augment_error (proxy, g_error_new (WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_OPERATION_FAILED,
"failed to initialize device: %s", spa_strerror (res)));
}
}
}
static gconstpointer
wp_spa_device_get_info (WpProxy * proxy)
{
return NULL;
}
static WpProperties *
wp_spa_device_get_properties (WpProxy * proxy)
{
WpSpaDevice *self = WP_SPA_DEVICE (proxy);
return wp_properties_ref (self->properties);
}
static gint
wp_spa_device_enum_params (WpProxy * proxy, guint32 id, guint32 start,
guint32 num, const struct spa_pod *filter)
{
WpSpaDevice *self = WP_SPA_DEVICE (proxy);
int device_enum_params_result;
device_enum_params_result = spa_device_enum_params (self->interface,
0, id, start, num, filter);
g_warn_if_fail (device_enum_params_result >= 0);
return device_enum_params_result;
}
static gint
wp_spa_device_set_param (WpProxy * proxy, guint32 id, guint32 flags,
const struct spa_pod *param)
{
WpSpaDevice *self = WP_SPA_DEVICE (proxy);
int device_set_param_result;
device_set_param_result = spa_device_set_param (self->interface,
id, flags, param);
g_warn_if_fail (device_set_param_result >= 0);
return device_set_param_result;
}
static void
wp_spa_device_class_init (WpSpaDeviceClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpProxyClass *proxy_class = (WpProxyClass *) klass;
object_class->finalize = wp_spa_device_finalize;
proxy_class->augment = wp_spa_device_augment;
proxy_class->get_info = wp_spa_device_get_info;
proxy_class->get_properties = wp_spa_device_get_properties;
proxy_class->enum_params = wp_spa_device_enum_params;
proxy_class->set_param = wp_spa_device_set_param;
/**
* WpSpaDevice::object-info:
* @self: the #WpSpaDevice
* @id: the id of the managed object
* @type: the #WpProxy subclass type that the managed object should have,
* or %G_TYPE_NONE if the object is being destroyed
* @factory: (nullable): the name of the SPA factory to use to construct
* the managed object, or %NULL if the object is being destroyed
* @properties: (nullable): additional properties that the managed object
* should have, or %NULL if the object is being destroyed
* @parent_props: the properties of the device itself
*
* This signal is emitted when the device is creating or destroying a managed
* object. The handler is expected to actually construct or destroy the
* object using the requested SPA @factory and with the given @properties.
*
* The handler may also use @parent_props to enrich the properties set
* that will be assigned on the object. @parent_props contains all the
* properties that this device object has.
*
* When the object is being created, @type can either be %WP_TYPE_DEVICE
* or %WP_TYPE_NODE. The handler is free to create a substitute of those,
* like %WP_TYPE_SPA_DEVICE instead of %WP_TYPE_DEVICE, depending on the
* use case.
*/
spa_device_signals[SIGNAL_OBJECT_INFO] = g_signal_new (
"object-info", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL, G_TYPE_NONE, 5, G_TYPE_UINT, G_TYPE_GTYPE,
G_TYPE_STRING, WP_TYPE_PROPERTIES, WP_TYPE_PROPERTIES);
}
/**
* wp_spa_device_new_from_spa_factory:
* @core: the wireplumber core
* @factory_name: the name of the SPA factory
* @properties: (nullable) (transfer full): properties to be passed to device
* constructor
*
* Constructs a `SPA_TYPE_INTERFACE_Device` by loading the given SPA
* @factory_name.
*
* To export this device to the PipeWire server, you need to call
* wp_proxy_augment() requesting %WP_PROXY_FEATURE_BOUND and
* wait for the operation to complete.
*
* Returns: (nullable) (transfer full): A new #WpSpaDevice wrapping the
* device that was constructed by the factory, or %NULL if the factory
* does not exist or was unable to construct the device
*/
WpSpaDevice *
wp_spa_device_new_from_spa_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties)
{
g_autoptr (WpProperties) props = properties;
struct pw_context *pw_context = wp_core_get_pw_context (core);
g_autoptr (WpSpaDevice) self = NULL;
gint res;
g_return_val_if_fail (pw_context != NULL, NULL);
self = g_object_new (WP_TYPE_SPA_DEVICE, "core", core, NULL);
/* Load the monitor handle */
self->handle = pw_context_load_spa_handle (pw_context,
factory_name, props ? wp_properties_peek_dict (props) : NULL);
if (!self->handle) {
g_warning ("SPA handle '%s' could not be loaded; is it installed?",
factory_name);
return NULL;
}
/* Get the handle interface */
res = spa_handle_get_interface (self->handle, SPA_TYPE_INTERFACE_Device,
(gpointer *) &self->interface);
if (res < 0) {
g_warning ("Could not get device interface from SPA handle: %s",
spa_strerror (res));
return NULL;
}
self->properties = props ?
g_steal_pointer (&props) : wp_properties_new_empty ();
return g_steal_pointer (&self);
}

View file

@ -13,10 +13,30 @@
G_BEGIN_DECLS
/* WpDevice */
#define WP_TYPE_DEVICE (wp_device_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpDevice, wp_device, WP, DEVICE, WpProxy)
WP_API
WpDevice * wp_device_new_from_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties);
/* WpSpaDevice */
typedef enum { /*< flags >*/
WP_SPA_DEVICE_FEATURE_ACTIVE = WP_PROXY_FEATURE_LAST,
} WpSpaDeviceFeatures;
#define WP_TYPE_SPA_DEVICE (wp_spa_device_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpSpaDevice, wp_spa_device, WP, SPA_DEVICE, WpProxy)
WP_API
WpSpaDevice * wp_spa_device_new_from_spa_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties);
G_END_DECLS
#endif

View file

@ -315,12 +315,12 @@ endpoint_event_info (void *data, const struct pw_endpoint_info *info)
WpProxyEndpoint *self = WP_PROXY_ENDPOINT (data);
self->info = endpoint_info_update (self->info, &self->properties, 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_ENDPOINT_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
}
static const struct pw_endpoint_events endpoint_events = {
@ -425,6 +425,9 @@ wp_proxy_endpoint_class_init (WpProxyEndpointClass * klass)
object_class->finalize = wp_proxy_endpoint_finalize;
proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Endpoint;
proxy_class->pw_iface_version = PW_VERSION_ENDPOINT;
proxy_class->augment = wp_proxy_endpoint_augment;
proxy_class->get_info = wp_proxy_endpoint_get_info;
proxy_class->get_properties = wp_proxy_endpoint_get_properties;
@ -604,6 +607,7 @@ wp_exported_endpoint_export (WpExported * self)
wp_exported_endpoint_get_instance_private (WP_EXPORTED_ENDPOINT (self));
g_autoptr (WpCore) core = wp_exported_get_core (self);
struct pw_client_endpoint *pw_proxy = NULL;
struct pw_core *pw_core = wp_core_get_pw_core (core);
/* make sure these props are not present; they are added by the server */
wp_properties_set (priv->properties, PW_KEY_OBJECT_ID, NULL);
@ -614,12 +618,11 @@ wp_exported_endpoint_export (WpExported * self)
wp_properties_set (priv->properties, PW_KEY_ENDPOINT_NAME, priv->info.name);
wp_properties_set (priv->properties, PW_KEY_MEDIA_CLASS, priv->info.media_class);
priv->client_ep = wp_core_create_remote_object (core, "client-endpoint",
pw_proxy = pw_core_create_object (pw_core, "client-endpoint",
PW_TYPE_INTERFACE_ClientEndpoint, PW_VERSION_CLIENT_ENDPOINT,
priv->properties);
pw_proxy = (struct pw_client_endpoint *) wp_proxy_get_pw_proxy (
priv->client_ep);
wp_properties_peek_dict (priv->properties), 0);
priv->client_ep = g_object_new (WP_TYPE_PROXY, "core", core,
"pw-proxy", pw_proxy, NULL);
pw_client_endpoint_add_listener (pw_proxy, &priv->listener,
&client_endpoint_events, self);

View file

@ -55,12 +55,12 @@ 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);
g_object_notify (G_OBJECT (self), "info");
if (info->change_mask & PW_LINK_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
}
static const struct pw_link_events link_events = {
@ -84,8 +84,50 @@ wp_link_class_init (WpLinkClass * klass)
object_class->finalize = wp_link_finalize;
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;
}
/**
* wp_link_new_from_factory:
* @core: the wireplumber core
* @factory_name: the pipewire factory name to construct the link
* @properties: (nullable) (transfer full): the properties to pass to the factory
*
* Constructs a link on the PipeWire server by asking the remote factory
* @factory_name to create it.
*
* Because of the nature of the PipeWire protocol, this operation completes
* asynchronously at some point in the future. In order to find out when
* this is done, you should call wp_proxy_augment(), 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
* will fail.
*
* Returns: (nullable) (transfer full): the new link or %NULL if the core
* is not connected and therefore the link cannot be created
*/
WpLink *
wp_link_new_from_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties)
{
g_autoptr (WpProperties) props = properties;
WpLink *self = NULL;
struct pw_core *pw_core = wp_core_get_pw_core (core);
if (!pw_core) {
g_warning ("The WirePlumber core is not connected; link cannot be created");
return NULL;
}
self = g_object_new (WP_TYPE_LINK, "core", core, NULL);
wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_create_object (pw_core,
factory_name, PW_TYPE_INTERFACE_Link, PW_VERSION_LINK,
props ? wp_properties_peek_dict (props) : NULL, 0));
return self;
}

View file

@ -17,6 +17,10 @@ G_BEGIN_DECLS
WP_API
G_DECLARE_FINAL_TYPE (WpLink, wp_link, WP, LINK, WpProxy)
WP_API
WpLink * wp_link_new_from_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties);
G_END_DECLS
#endif

View file

@ -10,7 +10,6 @@ wp_lib_sources = files(
'factory.c',
'link.c',
'module.c',
'monitor.c',
'node.c',
'object-manager.c',
'policy.c',
@ -34,7 +33,6 @@ wp_lib_headers = files(
'factory.h',
'link.h',
'module.h',
'monitor.h',
'node.h',
'object-manager.h',
'policy.h',

View file

@ -1,556 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <spa/monitor/device.h>
#include <spa/pod/builder.h>
#include <spa/pod/iter.h>
#include <spa/utils/result.h>
#include <pipewire/pipewire.h>
#include "node.h"
#include "device.h"
#include "monitor.h"
#include "error.h"
#include "wpenums.h"
#include "private.h"
typedef struct {
struct spa_handle *handle;
struct spa_device *interface;
struct spa_hook listener;
} WpSpaObject;
struct _WpMonitor
{
GObject parent;
/* Props */
GWeakRef core;
gchar *factory_name;
WpProperties *properties;
WpMonitorFlags flags;
struct object *device;
};
struct object
{
guint32 id;
GType type;
WpProxy *proxy;
WpProperties *properties;
GList *children; /* element-type: struct object* */
WpMonitor *self;
WpSpaObject *spa_obj;
};
enum {
PROP_0,
PROP_CORE,
PROP_FACTORY_NAME,
PROP_PROPERTIES,
PROP_FLAGS,
};
enum {
SIG_SETUP_NODE_PROPS,
SIG_SETUP_DEVICE_PROPS,
N_SIGNALS
};
static guint32 signals[N_SIGNALS] = {0};
G_DEFINE_TYPE (WpMonitor, wp_monitor, G_TYPE_OBJECT)
static gpointer find_object (GList *list, guint32 id, GList **link);
static struct object * node_new (struct object *dev, uint32_t id,
const struct spa_device_object_info *info);
static struct object * device_new (WpMonitor *self, uint32_t id,
const gchar *factory_name, WpProperties *properties, GError **error);
static void object_free (struct object *obj);
/* device events */
static void
device_info (void *data, const struct spa_device_info *info)
{
struct object *obj = data;
/*
* This is emited syncrhonously at the time we add the listener and
* before object_info is emited. It gives us additional properties
* about the device, like the "api.alsa.card.*" ones that are not
* set by the monitor
*/
if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PROPS && obj->properties)
wp_properties_update_from_dict (obj->properties, info->props);
}
static void
device_object_info (void *data, uint32_t id,
const struct spa_device_object_info *info)
{
struct object *obj = data;
struct object *child = NULL;
WpMonitor *self = obj->self;
GList *link = NULL;
g_autoptr (GError) err = NULL;
/* Find the child */
child = find_object (obj->children, id, &link);
/* new object, construct... */
if (info && !child) {
/* Device */
if (g_strcmp0 (info->type, SPA_TYPE_INTERFACE_Device) == 0) {
if (!(child = device_new (self, id, info->factory_name,
wp_properties_new_wrap_dict (info->props), &err)))
g_debug ("WpMonitor:%p:%s %s", self, self->factory_name, err->message);
return;
}
/* Node */
else if (g_strcmp0 (info->type, SPA_TYPE_INTERFACE_Node) == 0) {
if (!(child = node_new (obj, id, info)))
return;
}
/* Default */
else {
g_debug ("WpMonitor:%p:%s got device_object_info for unknown object "
"type %s", self, self->factory_name, info->type);
return;
}
obj->children = g_list_append (obj->children, child);
}
/* object removed, delete... */
else if (!info && child) {
object_free (child);
obj->children = g_list_delete_link (obj->children, link);
}
}
static const struct spa_device_events device_events = {
SPA_VERSION_DEVICE_EVENTS,
.info = device_info,
.object_info = device_object_info
};
/* WpSpaObject */
static void
wp_spa_object_free (WpSpaObject *self)
{
spa_hook_remove (&self->listener);
pw_unload_spa_handle (self->handle);
}
static inline WpSpaObject *
wp_spa_object_ref (WpSpaObject *self)
{
return g_rc_box_acquire (self);
}
static inline void
wp_spa_object_unref (WpSpaObject *self)
{
g_rc_box_release_full (self, (GDestroyNotify) wp_spa_object_free);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpSpaObject, wp_spa_object_unref)
static WpSpaObject *
load_spa_object (WpCore *core, const gchar *factory, const char * iface_type,
WpProperties *props, GError **error)
{
g_autoptr (WpSpaObject) self = g_rc_box_new0 (WpSpaObject);
gint res;
/* Load the monitor handle */
self->handle = pw_context_load_spa_handle (wp_core_get_pw_context (core),
factory, props ? wp_properties_peek_dict (props) : NULL);
if (!self->handle) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"SPA handle '%s' could not be loaded; is it installed?",
factory);
return NULL;
}
/* Get the handle interface */
res = spa_handle_get_interface (self->handle, iface_type,
(gpointer *)&self->interface);
if (res < 0) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Could not get interface %s from SPA handle", iface_type);
return NULL;
}
return g_steal_pointer (&self);
}
/* struct object */
static gpointer
find_object (GList *list, guint32 id, GList **link)
{
/*
* The first element of struct object is the guint32 containing the id,
* so we can directly cast the list data to guint32, no matter what the
* actual structure is
*/
for (; list; list = g_list_next (list)) {
if (id == *((guint32 *) list->data)) {
*link = list;
return list->data;
}
}
return NULL;
}
static struct object *
node_new (struct object *dev, uint32_t id,
const struct spa_device_object_info *info)
{
WpMonitor *self = dev->self;
g_autoptr (WpCore) core = NULL;
g_autoptr (WpProperties) props = NULL;
g_autoptr (WpProxy) proxy = NULL;
struct object *node = NULL;
const gchar *pw_factory_name = "spa-node-factory";
g_return_val_if_fail (g_strcmp0 (info->type, SPA_TYPE_INTERFACE_Node) == 0, NULL);
g_debug ("WpMonitor:%p:%s new node %u", self, self->factory_name, id);
/* use the adapter instead of spa-node-factory if requested */
if (self->flags & WP_MONITOR_FLAG_USE_ADAPTER)
pw_factory_name = "adapter";
core = g_weak_ref_get (&self->core);
props = wp_properties_new_copy_dict (info->props);
/* pass down the id to the setup function */
wp_properties_setf (props, WP_MONITOR_KEY_OBJECT_ID, "%u", id);
/* the SPA factory name must be set as a property
for the spa-node-factory / adapter */
wp_properties_set (props, PW_KEY_FACTORY_NAME, info->factory_name);
/* the rest is up to the user */
g_signal_emit (self, signals[SIG_SETUP_NODE_PROPS], 0, dev->properties,
props);
/* and delete the id - it should not appear on the proxy */
wp_properties_set (props, WP_MONITOR_KEY_OBJECT_ID, NULL);
/* create the node locally or remotely */
proxy = (self->flags & WP_MONITOR_FLAG_LOCAL_NODES) ?
wp_core_create_local_object (core, pw_factory_name,
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, props) :
wp_core_create_remote_object (core, pw_factory_name,
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, props);
if (!proxy) {
g_warning ("WpMonitor:%p: failed to create node: %s", self,
g_strerror (errno));
return NULL;
}
node = g_slice_new0 (struct object);
node->self = self;
node->id = id;
node->type = WP_TYPE_NODE;
node->proxy = g_steal_pointer (&proxy);
return node;
}
static void
set_profile(struct spa_device * dev, int index)
{
char buf[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
spa_device_set_param (dev,
SPA_PARAM_Profile, 0,
spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamProfile, 0,
SPA_PARAM_PROFILE_index, SPA_POD_Int(index)));
}
static struct object *
device_new (WpMonitor *self, uint32_t id, const gchar *factory_name,
WpProperties *properties, GError **error)
{
g_autoptr (GError) err = NULL;
g_autoptr (WpCore) core = NULL;
g_autoptr (WpProperties) props = NULL;
g_autoptr (WpSpaObject) spa_dev = NULL;
g_autoptr (WpProxy) proxy = NULL;
struct object *dev = NULL;
gint ret = 0;
g_debug ("WpMonitor:%p:%s new device %d", self, self->factory_name, (gint) id);
core = g_weak_ref_get (&self->core);
props = properties ? wp_properties_copy (properties) : wp_properties_new_empty ();
/* pass down the id to the setup function */
wp_properties_setf (props, WP_MONITOR_KEY_OBJECT_ID, "%d", (gint) id);
/* let the handler setup the properties accordingly */
g_signal_emit (self, signals[SIG_SETUP_DEVICE_PROPS], 0, props);
/* and delete the id - it should not appear on the proxy */
wp_properties_set (props, WP_MONITOR_KEY_OBJECT_ID, NULL);
/* load the spa device */
spa_dev = load_spa_object (core, factory_name, SPA_TYPE_INTERFACE_Device,
props, &err);
if (!spa_dev) {
g_propagate_error (error, g_steal_pointer (&err));
return NULL;
}
/* check for id != -1 to avoid exporting the "monitor" device itself;
exporting it is buggy, but we should revise this in the future; FIXME */
if (id != -1 && !(proxy = wp_core_export_object (core,
SPA_TYPE_INTERFACE_Device, spa_dev->interface, props))) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"failed to export device: %s", g_strerror (errno));
return NULL;
}
/* Create the device */
dev = g_slice_new0 (struct object);
dev->self = self;
dev->id = id;
dev->type = WP_TYPE_DEVICE;
dev->spa_obj = g_steal_pointer (&spa_dev);
dev->properties = g_steal_pointer (&props);
dev->proxy = g_steal_pointer (&proxy);
/* Add device listener for events */
ret = spa_device_add_listener (dev->spa_obj->interface,
&dev->spa_obj->listener, &device_events, dev);
if (ret < 0) {
object_free (dev);
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"failed to initialize device: %s", spa_strerror (ret));
return NULL;
}
/* HACK this is very specific to the current alsa pcm profiles */
if (self->flags & WP_MONITOR_FLAG_ACTIVATE_DEVICES)
set_profile (dev->spa_obj->interface, 1);
return dev;
}
static void
object_free (struct object *obj)
{
g_debug ("WpMonitor:%p:%s free %s %u", obj->self, obj->self->factory_name,
g_type_name (obj->type), obj->id);
g_list_free_full (obj->children, (GDestroyNotify) object_free);
g_clear_object (&obj->proxy);
g_clear_pointer (&obj->spa_obj, wp_spa_object_unref);
g_clear_pointer (&obj->properties, wp_properties_unref);
g_slice_free (struct object, obj);
}
/* WpMonitor */
static void
wp_monitor_init (WpMonitor * self)
{
g_weak_ref_init (&self->core, NULL);
}
static void
wp_monitor_finalize (GObject * object)
{
WpMonitor * self = WP_MONITOR (object);
wp_monitor_stop (self);
g_clear_pointer (&self->properties, wp_properties_unref);
g_weak_ref_clear (&self->core);
g_free (self->factory_name);
G_OBJECT_CLASS (wp_monitor_parent_class)->finalize (object);
}
static void
wp_monitor_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpMonitor * self = WP_MONITOR (object);
switch (property_id) {
case PROP_CORE:
g_weak_ref_set (&self->core, g_value_get_object (value));
break;
case PROP_FACTORY_NAME:
self->factory_name = g_value_dup_string (value);
break;
case PROP_PROPERTIES:
self->properties = g_value_dup_boxed (value);
break;
case PROP_FLAGS:
self->flags = (WpMonitorFlags) g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_monitor_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpMonitor * self = WP_MONITOR (object);
switch (property_id) {
case PROP_CORE:
g_value_take_object (value, g_weak_ref_get (&self->core));
break;
case PROP_FACTORY_NAME:
g_value_set_string (value, self->factory_name);
break;
case PROP_PROPERTIES:
g_value_set_boxed (value, self->properties);
break;
case PROP_FLAGS:
g_value_set_flags (value, (guint) self->flags);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_monitor_class_init (WpMonitorClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_monitor_finalize;
object_class->set_property = wp_monitor_set_property;
object_class->get_property = wp_monitor_get_property;
/* Install the properties */
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "core", "The wireplumber core",
WP_TYPE_CORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_FACTORY_NAME,
g_param_spec_string ("factory-name", "factory-name",
"The factory name of the spa device", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PROPERTIES,
g_param_spec_boxed ("properties", "properties",
"Properties for the spa device", WP_TYPE_PROPERTIES,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_FLAGS,
g_param_spec_flags ("flags", "flags",
"Additional feature flags", WP_TYPE_MONITOR_FLAGS, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
/**
* WpMonitor::setup-device-props:
* @self: the #WpMonitor
* @device_props: the properties of the device to be created
*
* This signal allows the handler to modify the properties of a device
* object before it is created.
*/
signals[SIG_SETUP_DEVICE_PROPS] = g_signal_new (
"setup-device-props", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0,
NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_PROPERTIES);
/**
* WpMonitor::setup-node-props:
* @self: the #WpMonitor
* @device_props: the properties of the parent device
* @node_props: the properties of the node to be created
*
* This signal allows the handler to modify the properties of a node
* object before it is created.
*/
signals[SIG_SETUP_NODE_PROPS] = g_signal_new (
"setup-node-props", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0,
NULL, NULL, NULL, G_TYPE_NONE, 2, WP_TYPE_PROPERTIES, WP_TYPE_PROPERTIES);
}
/**
* wp_monitor_new:
* @core: the wireplumber core
* @factory_name: the factory name of the spa device
* @props: properties to pass to the spa device
* @flags: additional feature flags
*
* Returns: (transfer full): the newly created monitor
*/
WpMonitor *
wp_monitor_new (WpCore * core, const gchar * factory_name, WpProperties *props,
WpMonitorFlags flags)
{
g_return_val_if_fail (WP_IS_CORE (core), NULL);
g_return_val_if_fail (factory_name != NULL && *factory_name != '\0', NULL);
return g_object_new (WP_TYPE_MONITOR,
"core", core,
"factory-name", factory_name,
"properties", props,
"flags", flags,
NULL);
}
const gchar *
wp_monitor_get_factory_name (WpMonitor *self)
{
g_return_val_if_fail (WP_IS_MONITOR (self), NULL);
return self->factory_name;
}
gboolean
wp_monitor_start (WpMonitor *self, GError **error)
{
g_autoptr (WpCore) core = NULL;
g_autoptr (GError) err = NULL;
g_return_val_if_fail (WP_IS_MONITOR (self), FALSE);
core = g_weak_ref_get (&self->core);
g_debug ("WpMonitor:%p:%s starting monitor, flags 0x%x", self,
self->factory_name, self->flags);
self->device = device_new (self, -1, self->factory_name, self->properties,
&err);
if (!self->device) {
g_propagate_error (error, g_steal_pointer (&err));
return FALSE;
}
return TRUE;
}
void
wp_monitor_stop (WpMonitor *self)
{
g_return_if_fail (WP_IS_MONITOR (self));
g_debug ("WpMonitor:%p:%s stopping monitor", self, self->factory_name);
g_clear_pointer (&self->device, object_free);
}

View file

@ -1,46 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_MONITOR_H__
#define __WIREPLUMBER_MONITOR_H__
#include "core.h"
G_BEGIN_DECLS
typedef enum { /*< flags, prefix=WP_MONITOR_FLAG_ >*/
WP_MONITOR_FLAG_LOCAL_NODES = (1 << 0),
WP_MONITOR_FLAG_USE_ADAPTER = (1 << 1),
WP_MONITOR_FLAG_ACTIVATE_DEVICES = (1 << 2),
} WpMonitorFlags;
#define WP_MONITOR_KEY_OBJECT_ID "wp.monitor.object.id"
#define WP_TYPE_MONITOR (wp_monitor_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpMonitor, wp_monitor, WP, MONITOR, GObject)
WP_API
WpMonitor * wp_monitor_new (WpCore * core, const gchar * factory_name,
WpProperties *props, WpMonitorFlags flags);
WP_API
const gchar * wp_monitor_get_factory_name (WpMonitor *self);
WP_API
WpMonitorFlags wp_monitor_get_flags (WpMonitor *self);
WP_API
gboolean wp_monitor_start (WpMonitor *self, GError **error);
WP_API
void wp_monitor_stop (WpMonitor *self);
G_END_DECLS
#endif

View file

@ -1,26 +1,26 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* Copyright © 2019-2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "node.h"
#include "error.h"
#include "private.h"
#include <pipewire/pipewire.h>
#include <pipewire/impl.h>
struct _WpNode
typedef struct _WpNodePrivate WpNodePrivate;
struct _WpNodePrivate
{
WpProxy parent;
struct pw_node_info *info;
/* The node proxy listener */
struct spa_hook listener;
};
G_DEFINE_TYPE (WpNode, wp_node, WP_TYPE_PROXY)
G_DEFINE_TYPE_WITH_PRIVATE (WpNode, wp_node, WP_TYPE_PROXY)
static void
wp_node_init (WpNode * self)
@ -31,8 +31,9 @@ static void
wp_node_finalize (GObject * object)
{
WpNode *self = WP_NODE (object);
WpNodePrivate *priv = wp_node_get_instance_private (self);
g_clear_pointer (&self->info, pw_node_info_free);
g_clear_pointer (&priv->info, pw_node_info_free);
G_OBJECT_CLASS (wp_node_parent_class)->finalize (object);
}
@ -40,13 +41,15 @@ wp_node_finalize (GObject * object)
static gconstpointer
wp_node_get_info (WpProxy * self)
{
return WP_NODE (self)->info;
WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (self));
return priv->info;
}
static WpProperties *
wp_node_get_properties (WpProxy * self)
{
return wp_properties_new_wrap_dict (WP_NODE (self)->info->props);
WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (self));
return wp_properties_new_wrap_dict (priv->info->props);
}
static gint
@ -94,14 +97,15 @@ 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);
priv->info = pw_node_info_update (priv->info, info);
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
self->info = pw_node_info_update (self->info, info);
g_object_notify (G_OBJECT (self), "info");
if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
}
static const struct pw_node_events node_events = {
@ -114,8 +118,9 @@ 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,
&self->listener, &node_events, self);
&priv->listener, &node_events, self);
}
static void
@ -126,6 +131,9 @@ wp_node_class_init (WpNodeClass * klass)
object_class->finalize = wp_node_finalize;
proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Node;
proxy_class->pw_iface_version = PW_VERSION_NODE;
proxy_class->get_info = wp_node_get_info;
proxy_class->get_properties = wp_node_get_properties;
proxy_class->enum_params = wp_node_enum_params;
@ -134,3 +142,214 @@ wp_node_class_init (WpNodeClass * klass)
proxy_class->pw_proxy_created = wp_node_pw_proxy_created;
}
/**
* wp_node_new_from_factory:
* @core: the wireplumber core
* @factory_name: the pipewire factory name to construct the node
* @properties: (nullable) (transfer full): the properties to pass to the factory
*
* Constructs a node on the PipeWire server by asking the remote factory
* @factory_name to create it.
*
* Because of the nature of the PipeWire protocol, this operation completes
* asynchronously at some point in the future. In order to find out when
* this is done, you should call wp_proxy_augment(), 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
* will fail.
*
* Returns: (nullable) (transfer full): the new node or %NULL if the core
* is not connected and therefore the node cannot be created
*/
WpNode *
wp_node_new_from_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties)
{
g_autoptr (WpProperties) props = properties;
WpNode *self = NULL;
struct pw_core *pw_core = wp_core_get_pw_core (core);
if (!pw_core) {
g_warning ("The WirePlumber core is not connected; node cannot be created");
return NULL;
}
self = g_object_new (WP_TYPE_NODE, "core", core, NULL);
wp_proxy_set_pw_proxy (WP_PROXY (self), pw_core_create_object (pw_core,
factory_name, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE,
props ? wp_properties_peek_dict (props) : NULL, 0));
return self;
}
enum {
PROP_0,
PROP_PW_IMPL_NODE,
};
struct _WpImplNode
{
WpNode parent;
struct pw_impl_node *pw_impl_node;
};
G_DEFINE_TYPE (WpImplNode, wp_impl_node, WP_TYPE_NODE)
static void
wp_impl_node_init (WpImplNode * self)
{
}
static void
wp_impl_node_finalize (GObject * object)
{
WpImplNode *self = WP_IMPL_NODE (object);
g_clear_pointer (&self->pw_impl_node, pw_impl_node_destroy);
G_OBJECT_CLASS (wp_impl_node_parent_class)->finalize (object);
}
static void
wp_impl_node_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpImplNode *self = WP_IMPL_NODE (object);
switch (property_id) {
case PROP_PW_IMPL_NODE:
self->pw_impl_node = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_impl_node_get_property (GObject * object, guint property_id, GValue * value,
GParamSpec * pspec)
{
WpImplNode *self = WP_IMPL_NODE (object);
switch (property_id) {
case PROP_PW_IMPL_NODE:
g_value_set_pointer (value, self->pw_impl_node);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_impl_node_augment (WpProxy * proxy, WpProxyFeatures features)
{
WpImplNode *self = WP_IMPL_NODE (proxy);
/* if any of the default features is requested, make sure BOUND
is also requested, as they all depend on binding the pw_impl_node */
if (features & WP_PROXY_FEATURES_STANDARD)
features |= WP_PROXY_FEATURE_BOUND;
if (features & WP_PROXY_FEATURE_BOUND) {
g_autoptr (WpCore) core = wp_proxy_get_core (proxy);
struct pw_core *pw_core = wp_core_get_pw_core (core);
/* 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;
}
/* export to get a proxy; feature will complete
when the pw_proxy.bound event will be called.
properties are NULL because they are not needed;
remote-node uses the properties of the pw_impl_node */
wp_proxy_set_pw_proxy (proxy, pw_core_export (pw_core,
PW_TYPE_INTERFACE_Node, NULL, self->pw_impl_node, 0));
}
}
static void
wp_impl_node_class_init (WpImplNodeClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpProxyClass *proxy_class = (WpProxyClass *) klass;
object_class->finalize = wp_impl_node_finalize;
object_class->set_property = wp_impl_node_set_property;
object_class->get_property = wp_impl_node_get_property;
proxy_class->augment = wp_impl_node_augment;
g_object_class_install_property (object_class, PROP_PW_IMPL_NODE,
g_param_spec_pointer ("pw-impl-node", "pw-impl-node",
"The actual node implementation, struct pw_impl_node *",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
/**
* wp_impl_node_new_wrap:
* @core: the wireplumber core
* @node: an existing pw_impl_node to wrap
*
* Returns: (transfer full): A new #WpImplNode wrapping @node
*/
WpImplNode *
wp_impl_node_new_wrap (WpCore * core, struct pw_impl_node * node)
{
return g_object_new (WP_TYPE_IMPL_NODE,
"core", core,
"pw-impl-node", node,
NULL);
}
/**
* wp_impl_node_new_from_pw_factory:
* @core: the wireplumber core
* @factory_name: the name of the pipewire factory
* @properties: (nullable) (transfer full): properties to be passed to node
* constructor
*
* Constructs a new node, locally on this process, using the specified
* @factory_name.
*
* To export this node to the PipeWire server, you need to call
* wp_proxy_augment() requesting %WP_PROXY_FEATURE_BOUND and
* wait for the operation to complete.
*
* Returns: (nullable) (transfer full): A new #WpImplNode wrapping the
* node that was constructed by the factory, or %NULL if the factory
* does not exist or was unable to construct the node
*/
WpImplNode *
wp_impl_node_new_from_pw_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties)
{
g_autoptr (WpProperties) props = properties;
struct pw_context *pw_context = wp_core_get_pw_context (core);
struct pw_impl_factory *factory = NULL;
struct pw_impl_node *node = NULL;
g_return_val_if_fail (pw_context != NULL, NULL);
factory = pw_context_find_factory (pw_context, factory_name);
if (!factory) {
g_warning ("pipewire factory '%s' not found", factory_name);
return NULL;
}
node = pw_impl_factory_create_object (factory,
NULL, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE,
props ? wp_properties_to_pw_properties (props) : NULL, 0);
if (!node) {
g_warning ("failed to create node from factory '%s'", factory_name);
return NULL;
}
return wp_impl_node_new_wrap (core, node);
}

View file

@ -13,9 +13,32 @@
G_BEGIN_DECLS
struct pw_impl_node;
#define WP_TYPE_NODE (wp_node_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpNode, wp_node, WP, NODE, WpProxy)
G_DECLARE_DERIVABLE_TYPE (WpNode, wp_node, WP, NODE, WpProxy)
struct _WpNodeClass
{
WpProxyClass parent_class;
};
WP_API
WpNode * wp_node_new_from_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties);
#define WP_TYPE_IMPL_NODE (wp_impl_node_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpImplNode, wp_impl_node, WP, IMPL_NODE, WpNode)
WP_API
WpImplNode * wp_impl_node_new_wrap (WpCore * core, struct pw_impl_node * node);
WP_API
WpImplNode * wp_impl_node_new_from_pw_factory (WpCore * core,
const gchar * factory_name, WpProperties * properties);
G_END_DECLS

View file

@ -82,12 +82,12 @@ 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);
g_object_notify (G_OBJECT (self), "info");
if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
}
static const struct pw_port_events port_events = {
@ -112,6 +112,9 @@ wp_port_class_init (WpPortClass * klass)
object_class->finalize = wp_port_finalize;
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->enum_params = wp_port_enum_params;

View file

@ -86,11 +86,10 @@ void wp_object_manager_rm_object (WpObjectManager * self, GObject * object);
/* proxy */
void wp_proxy_local_object_destroy_for_type (const char * type,
gpointer local_object);
WpProxy * wp_proxy_new_global (WpCore * core, WpGlobal * global);
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);

View file

@ -36,13 +36,7 @@ struct _WpProxyPrivate
{
/* properties */
GWeakRef core;
WpGlobal *global;
char *iface_type;
guint32 iface_version;
gpointer local_object;
struct pw_proxy *pw_proxy;
/* The proxy listener */
@ -61,9 +55,6 @@ enum {
PROP_GLOBAL,
PROP_GLOBAL_PERMISSIONS,
PROP_GLOBAL_PROPERTIES,
PROP_INTERFACE_TYPE,
PROP_INTERFACE_VERSION,
PROP_LOCAL_OBJECT,
PROP_FEATURES,
PROP_PW_PROXY,
PROP_INFO,
@ -84,61 +75,30 @@ static guint wp_proxy_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_BOXED_TYPE (WpGlobal, wp_global, wp_global_ref, wp_global_unref)
G_DEFINE_TYPE_WITH_PRIVATE (WpProxy, wp_proxy, G_TYPE_OBJECT)
static struct {
/* the pipewire interface type */
const char * pw_type;
/* the minimum interface version that the remote object must support */
guint32 req_version;
/* the _get_type() function of the subclass */
GType (*get_type) (void);
/* the destroy function of the local object, if any */
void (*local_object_destroy) (gpointer);
} types_assoc[] = {
{ PW_TYPE_INTERFACE_Core, 0, wp_proxy_get_type, NULL },
{ PW_TYPE_INTERFACE_Registry, 0, wp_proxy_get_type, NULL },
{ PW_TYPE_INTERFACE_Node, 0, wp_node_get_type, (GDestroyNotify)pw_impl_node_destroy },
{ PW_TYPE_INTERFACE_Port, 0, wp_port_get_type, NULL },
{ PW_TYPE_INTERFACE_Factory, 0, wp_proxy_get_type, (GDestroyNotify)pw_impl_factory_destroy },
{ PW_TYPE_INTERFACE_Link, 0, wp_link_get_type, (GDestroyNotify)pw_impl_link_destroy },
{ PW_TYPE_INTERFACE_Client, 0, wp_client_get_type, (GDestroyNotify)pw_impl_client_destroy },
{ PW_TYPE_INTERFACE_Module, 0, wp_proxy_get_type, (GDestroyNotify)pw_impl_module_destroy },
{ PW_TYPE_INTERFACE_Device, 0, wp_device_get_type, (GDestroyNotify)pw_impl_device_destroy },
{ PW_TYPE_INTERFACE_Metadata, 0, wp_proxy_get_type, NULL },
{ PW_TYPE_INTERFACE_Session, 0, wp_proxy_session_get_type, NULL },
{ PW_TYPE_INTERFACE_Endpoint, 0, wp_proxy_endpoint_get_type, NULL },
{ PW_TYPE_INTERFACE_EndpointStream, 0, wp_proxy_get_type, NULL },
{ PW_TYPE_INTERFACE_EndpointLink, 0, wp_proxy_get_type, NULL },
{ PW_TYPE_INTERFACE_ClientNode, 0, wp_proxy_get_type, NULL },
{ PW_TYPE_INTERFACE_ClientSession, 0, wp_proxy_get_type, NULL },
{ PW_TYPE_INTERFACE_ClientEndpoint, 0, wp_proxy_get_type, NULL },
};
/* find the subclass of WpProxy that can handle
the given pipewire interface type of the given version */
static inline GType
wp_proxy_find_instance_type (const char * type, guint32 version)
{
for (gint i = 0; i < SPA_N_ELEMENTS (types_assoc); i++) {
if (g_strcmp0 (types_assoc[i].pw_type, type) == 0 &&
types_assoc[i].req_version <= version)
return types_assoc[i].get_type ();
g_autofree GType *children;
guint n_children;
children = g_type_children (WP_TYPE_PROXY, &n_children);
for (gint i = 0; i < n_children; i++) {
WpProxyClass *klass = (WpProxyClass *) g_type_class_ref (children[i]);
if (g_strcmp0 (klass->pw_iface_type, type) == 0 &&
klass->pw_iface_version == version) {
g_type_class_unref (klass);
return children[i];
}
g_type_class_unref (klass);
}
return WP_TYPE_PROXY;
}
void
wp_proxy_local_object_destroy_for_type (const char *type, gpointer local_object)
{
g_return_if_fail (local_object);
for (gint i = 0; i < SPA_N_ELEMENTS (types_assoc); i++) {
if (g_strcmp0 (types_assoc[i].pw_type, type) == 0) {
if (types_assoc[i].local_object_destroy)
types_assoc[i].local_object_destroy (local_object);
return;
}
}
}
static void
proxy_event_destroy (void *data)
{
@ -149,10 +109,10 @@ proxy_event_destroy (void *data)
GHashTableIter iter;
GTask *task;
g_debug ("%s:%p destroyed pw_proxy %p (%s; %s; %u)",
G_OBJECT_TYPE_NAME (self), self, priv->pw_proxy, priv->iface_type,
g_debug ("%s:%p destroyed pw_proxy %p (%s; %u)",
G_OBJECT_TYPE_NAME (self), self, priv->pw_proxy,
priv->global ? "global" : "not global",
priv->global ? priv->global->id : 0);
wp_proxy_get_bound_id (self));
priv->pw_proxy = NULL;
g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED], 0);
@ -196,11 +156,17 @@ static const struct pw_proxy_events proxy_events = {
.bound = proxy_event_bound,
};
static void
wp_proxy_got_pw_proxy (WpProxy * self)
void
wp_proxy_set_pw_proxy (WpProxy * self, struct pw_proxy * proxy)
{
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
if (!proxy)
return;
g_return_if_fail (priv->pw_proxy == NULL);
priv->pw_proxy = proxy;
pw_proxy_add_listener (priv->pw_proxy, &priv->listener, &proxy_events,
self);
@ -223,17 +189,6 @@ wp_proxy_init (WpProxy * self)
NULL, g_object_unref);
}
static void
wp_proxy_constructed (GObject * object)
{
WpProxy *self = WP_PROXY (object);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
/* native proxy was passed in the constructor, declare it as ready */
if (priv->pw_proxy)
wp_proxy_got_pw_proxy (self);
}
static void
wp_proxy_dispose (GObject * object)
{
@ -257,14 +212,6 @@ wp_proxy_finalize (GObject * object)
WpProxy *self = WP_PROXY (object);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
/* Clear the local object */
if (priv->local_object) {
wp_proxy_local_object_destroy_for_type (priv->iface_type,
priv->local_object);
priv->local_object = NULL;
}
g_clear_pointer (&priv->iface_type, g_free);
g_clear_pointer (&priv->augment_tasks, g_ptr_array_unref);
g_clear_pointer (&priv->global, wp_global_unref);
g_weak_ref_clear (&priv->core);
@ -277,7 +224,8 @@ static void
wp_proxy_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
WpProxy *self = WP_PROXY (object);
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
switch (property_id) {
case PROP_CORE:
@ -286,17 +234,8 @@ wp_proxy_set_property (GObject * object, guint property_id,
case PROP_GLOBAL:
priv->global = g_value_dup_boxed (value);
break;
case PROP_INTERFACE_TYPE:
priv->iface_type = g_value_dup_string (value);
break;
case PROP_INTERFACE_VERSION:
priv->iface_version = g_value_get_uint (value);
break;
case PROP_LOCAL_OBJECT:
priv->local_object = g_value_get_pointer (value);
break;
case PROP_PW_PROXY:
priv->pw_proxy = g_value_get_pointer (value);
wp_proxy_set_pw_proxy (self, g_value_get_pointer (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@ -321,15 +260,6 @@ wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
case PROP_GLOBAL_PROPERTIES:
g_value_set_boxed (value, priv->global ? priv->global->properties : NULL);
break;
case PROP_INTERFACE_TYPE:
g_value_set_string (value, priv->iface_type);
break;
case PROP_INTERFACE_VERSION:
g_value_set_uint (value, priv->iface_version);
break;
case PROP_LOCAL_OBJECT:
g_value_set_pointer (value, priv->local_object);
break;
case PROP_FEATURES:
g_value_set_flags (value, priv->ft_ready);
break;
@ -377,10 +307,9 @@ wp_proxy_default_augment (WpProxy * self, WpProxyFeatures features)
g_return_if_fail (core);
/* bind */
priv->pw_proxy = pw_registry_bind (
wp_core_get_pw_registry (core), priv->global->id,
priv->iface_type, priv->iface_version, 0);
wp_proxy_got_pw_proxy (self);
wp_proxy_set_pw_proxy (self, pw_registry_bind (
wp_core_get_pw_registry (core), priv->global->id,
priv->global->type, priv->global->version, 0));
}
}
@ -389,7 +318,6 @@ wp_proxy_class_init (WpProxyClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->constructed = wp_proxy_constructed;
object_class->dispose = wp_proxy_dispose;
object_class->finalize = wp_proxy_finalize;
object_class->get_property = wp_proxy_get_property;
@ -418,21 +346,6 @@ wp_proxy_class_init (WpProxyClass * klass)
"The pipewire global properties", WP_TYPE_PROPERTIES,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_INTERFACE_TYPE,
g_param_spec_string ("interface-type", "interface-type",
"The pipewire interface type", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_INTERFACE_VERSION,
g_param_spec_uint ("interface-version", "interface-version",
"The pipewire interface version", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_LOCAL_OBJECT,
g_param_spec_pointer ("local-object", "local-object",
"The local object this proxy refers to, if any",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_FEATURES,
g_param_spec_flags ("features", "features",
"The ready WpProxyFeatures on this proxy", WP_TYPE_PROXY_FEATURES, 0,
@ -440,7 +353,7 @@ wp_proxy_class_init (WpProxyClass * klass)
g_object_class_install_property (object_class, PROP_PW_PROXY,
g_param_spec_pointer ("pw-proxy", "pw-proxy", "The struct pw_proxy *",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_INFO,
g_param_spec_pointer ("info", "info", "The native info structure",
@ -480,22 +393,6 @@ wp_proxy_new_global (WpCore * core, WpGlobal * global)
return g_object_new (gtype,
"core", core,
"global", global,
"interface-type", global->type,
"interface-version", global->version,
NULL);
}
WpProxy *
wp_proxy_new_wrap (WpCore * core, struct pw_proxy * proxy, const char *type,
guint32 version, gpointer local_object)
{
GType gtype = wp_proxy_find_instance_type (type, version);
return g_object_new (gtype,
"core", core,
"pw-proxy", proxy,
"interface-type", type,
"interface-version", version,
"local-object", local_object,
NULL);
}
@ -653,28 +550,6 @@ wp_proxy_get_global_properties (WpProxy * self)
return wp_properties_ref (priv->global->properties);
}
const char *
wp_proxy_get_interface_type (WpProxy * self)
{
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), 0);
priv = wp_proxy_get_instance_private (self);
return priv->iface_type;
}
guint32
wp_proxy_get_interface_version (WpProxy * self)
{
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), 0);
priv = wp_proxy_get_instance_private (self);
return priv->iface_version;
}
struct pw_proxy *
wp_proxy_get_pw_proxy (WpProxy * self)
{

View file

@ -20,6 +20,16 @@ struct pw_proxy;
struct spa_pod;
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().
*/
typedef enum { /*< flags >*/
WP_PROXY_FEATURE_PW_PROXY = (1 << 0),
WP_PROXY_FEATURE_INFO = (1 << 1),
@ -28,6 +38,19 @@ typedef enum { /*< flags >*/
WP_PROXY_FEATURE_LAST = (1 << 5), /*< skip >*/
} WpProxyFeatures;
/**
* WP_PROXY_FEATURES_STANDARD:
*
* 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.
*/
#define WP_PROXY_FEATURES_STANDARD \
(WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND)
#define WP_TYPE_PROXY (wp_proxy_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, GObject)
@ -37,6 +60,9 @@ struct _WpProxyClass
{
GObjectClass parent_class;
const gchar * pw_iface_type;
guint32 pw_iface_version;
void (*augment) (WpProxy *self, WpProxyFeatures features);
gconstpointer (*get_info) (WpProxy * self);
@ -56,10 +82,6 @@ struct _WpProxyClass
guint32 next, const struct spa_pod *param);
};
WP_API
WpProxy * wp_proxy_new_wrap (WpCore * core, struct pw_proxy * proxy,
const char *type, guint32 version, gpointer local_object);
/* features API */
WP_API
@ -87,14 +109,6 @@ guint32 wp_proxy_get_global_permissions (WpProxy * self);
WP_API
WpProperties * wp_proxy_get_global_properties (WpProxy * self);
/* the pipewire type & version */
WP_API
const char * wp_proxy_get_interface_type (WpProxy * self);
WP_API
guint32 wp_proxy_get_interface_version (WpProxy * self);
/* native pw_proxy object getter (requires FEATURE_PW_PROXY) */
WP_API

View file

@ -203,12 +203,12 @@ session_event_info (void *data, const struct pw_session_info *info)
WpProxySession *self = WP_PROXY_SESSION (data);
self->info = session_info_update (self->info, &self->properties, 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_SESSION_CHANGE_MASK_PROPS)
g_object_notify (G_OBJECT (self), "properties");
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_FEATURE_INFO);
}
static const struct pw_session_events session_events = {
@ -318,6 +318,9 @@ wp_proxy_session_class_init (WpProxySessionClass * klass)
object_class->finalize = wp_proxy_session_finalize;
proxy_class->pw_iface_type = PW_TYPE_INTERFACE_Session;
proxy_class->pw_iface_version = PW_VERSION_SESSION;
proxy_class->augment = wp_proxy_session_augment;
proxy_class->get_info = wp_proxy_session_get_info;
proxy_class->get_properties = wp_proxy_session_get_properties;
@ -504,18 +507,18 @@ wp_exported_session_export (WpExported * self)
wp_exported_session_get_instance_private (WP_EXPORTED_SESSION (self));
g_autoptr (WpCore) core = wp_exported_get_core (self);
struct pw_client_session *pw_proxy = NULL;
struct pw_core *pw_core = wp_core_get_pw_core (core);
/* make sure these props are not present; they are added by the server */
wp_properties_set (priv->properties, PW_KEY_OBJECT_ID, NULL);
wp_properties_set (priv->properties, PW_KEY_CLIENT_ID, NULL);
wp_properties_set (priv->properties, PW_KEY_FACTORY_ID, NULL);
priv->client_sess = wp_core_create_remote_object (core, "client-session",
pw_proxy = pw_core_create_object (pw_core, "client-session",
PW_TYPE_INTERFACE_ClientSession, PW_VERSION_CLIENT_SESSION,
priv->properties);
pw_proxy = (struct pw_client_session *) wp_proxy_get_pw_proxy (
priv->client_sess);
wp_properties_peek_dict (priv->properties), 0);
priv->client_sess = g_object_new (WP_TYPE_PROXY, "core", core,
"pw-proxy", pw_proxy, NULL);
pw_client_session_add_listener (pw_proxy, &priv->listener,
&client_session_events, self);

View file

@ -17,7 +17,6 @@
#include "factory.h"
#include "link.h"
#include "module.h"
#include "monitor.h"
#include "node.h"
#include "object-manager.h"
#include "policy.h"

View file

@ -39,33 +39,45 @@ static guint signals[N_SIGNALS];
G_DEFINE_TYPE (WpConfigStaticNodesContext, wp_config_static_nodes_context,
G_TYPE_OBJECT)
static void
on_node_created (GObject * proxy, GAsyncResult * res, gpointer user_data)
{
WpConfigStaticNodesContext *self = user_data;
g_autoptr (GError) error = NULL;
if (!wp_proxy_augment_finish (WP_PROXY (proxy), res, &error)) {
g_warning ("WpConfigStaticNodesContext:%p: failed to export node: %s",
self, error->message);
return;
}
g_ptr_array_add (self->static_nodes, g_object_ref (proxy));
/* Emit the node-created signal */
g_signal_emit (self, signals[SIGNAL_NODE_CREATED], 0, proxy);
}
static void
wp_config_static_nodes_context_create_node (WpConfigStaticNodesContext *self,
const struct WpParserNodeData *node_data)
{
g_autoptr (WpProxy) node_proxy = NULL;
g_autoptr (WpProxy) node = NULL;
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
g_return_if_fail (core);
/* Create the node */
node_proxy = node_data->n.local ?
wp_core_create_local_object (core, node_data->n.factory,
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, node_data->n.props) :
wp_core_create_remote_object (core, node_data->n.factory,
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, node_data->n.props);
if (!node_proxy) {
g_warning ("WpConfigStaticNodesContext:%p: failed to create node: %s", self,
g_strerror (errno));
node = node_data->n.local ?
(WpProxy *) wp_impl_node_new_from_pw_factory (core, node_data->n.factory,
wp_properties_ref (node_data->n.props)) :
(WpProxy *) wp_node_new_from_factory (core, node_data->n.factory,
wp_properties_ref (node_data->n.props));
if (!node) {
g_warning ("WpConfigStaticNodesContext:%p: failed to create node", self);
return;
}
/* Add the node to the array */
g_ptr_array_add (self->static_nodes, g_object_ref (node_proxy));
g_debug ("WpConfigStaticNodesContext:%p: added static node: %s", self,
node_data->n.factory);
/* Emit the node-created signal */
g_signal_emit (self, signals[SIGNAL_NODE_CREATED], 0, node_proxy);
/* export to pipewire by requesting FEATURE_BOUND */
wp_proxy_augment (node, WP_PROXY_FEATURE_BOUND, NULL, on_node_created, self);
}
static void

View file

@ -12,9 +12,40 @@
#include <pipewire/pipewire.h>
#include <spa/utils/keys.h>
#include <spa/monitor/device.h>
#include <spa/pod/builder.h>
G_DEFINE_QUARK (wp-module-monitor-id, id);
G_DEFINE_QUARK (wp-module-monitor-children, children);
typedef enum {
FLAG_LOCAL_NODES = (1 << 0),
FLAG_USE_ADAPTER = (1 << 1),
FLAG_ACTIVATE_DEVICES = (1 << 2),
} MonitorFlags;
static const struct {
MonitorFlags flag;
const gchar *name;
} flag_names[] = {
{ FLAG_LOCAL_NODES, "local-nodes" },
{ FLAG_USE_ADAPTER, "use-adapter" },
{ FLAG_ACTIVATE_DEVICES, "activate-devices" }
};
struct module_data
{
WpSpaDevice *monitor;
gchar *factory;
MonitorFlags flags;
};
static void on_object_info (WpSpaDevice * device,
guint id, GType type, const gchar * spa_factory,
WpProperties * props, WpProperties * parent_props,
struct module_data * data);
static void
setup_device_props (WpMonitor *self, WpProperties *p, WpModule *module)
setup_device_props (WpProperties *p)
{
const gchar *s, *d, *api;
@ -24,7 +55,7 @@ setup_device_props (WpMonitor *self, WpProperties *p, WpModule *module)
if (!wp_properties_get (p, SPA_KEY_DEVICE_NAME)) {
if ((s = wp_properties_get (p, SPA_KEY_DEVICE_BUS_ID)) == NULL) {
if ((s = wp_properties_get (p, SPA_KEY_DEVICE_BUS_PATH)) == NULL) {
s = wp_properties_get (p, WP_MONITOR_KEY_OBJECT_ID);
s = "unknown";
}
}
@ -102,8 +133,7 @@ setup_device_props (WpMonitor *self, WpProperties *p, WpModule *module)
}
static void
setup_node_props (WpMonitor *self, WpProperties *dev_props,
WpProperties *node_props, WpModule *module)
setup_node_props (WpProperties *dev_props, WpProperties *node_props)
{
const gchar *api, *name, *description, *factory;
@ -135,7 +165,7 @@ setup_node_props (WpMonitor *self, WpProperties *dev_props,
/* get some strings that we are going to need below */
api = wp_properties_get (node_props, SPA_KEY_DEVICE_API);
factory = wp_properties_get (node_props, PW_KEY_FACTORY_NAME);
factory = wp_properties_get (node_props, SPA_KEY_FACTORY_NAME);
name = wp_properties_get (node_props, SPA_KEY_DEVICE_NAME);
if (G_UNLIKELY (!name))
@ -200,51 +230,201 @@ setup_node_props (WpMonitor *self, WpProperties *dev_props,
}
static void
start_monitor (WpMonitor *monitor)
augment_done (GObject * proxy, GAsyncResult * res, gpointer user_data)
{
g_autoptr (GError) error = NULL;
if (!wp_monitor_start (monitor, &error)) {
g_message ("Failed to start monitor: %s", error->message);
if (!wp_proxy_augment_finish (WP_PROXY (proxy), res, &error)) {
g_warning ("%s", error->message);
}
}
static void
free_children (GList * children)
{
g_list_free_full (children, g_object_unref);
}
static void
find_child (GObject * parent, guint32 id, GList ** children, GList ** link,
GObject ** child)
{
*children = g_object_steal_qdata (parent, children_quark ());
/* Find the child */
for (*link = *children; *link != NULL; *link = g_list_next (*link)) {
*child = G_OBJECT ((*link)->data);
guint32 child_id = GPOINTER_TO_UINT (g_object_get_qdata (*child, id_quark ()));
if (id == child_id)
break;
}
}
static void
create_node (struct module_data * data, WpProxy * parent, GList ** children,
guint id, const gchar * spa_factory, WpProperties * props,
WpProperties * parent_props)
{
g_autoptr (WpCore) core = wp_proxy_get_core (parent);
WpProxy *node = NULL;
const gchar *pw_factory_name;
g_debug ("module-monitor:%p:%s new node %u (%s)", data, data->factory, id,
spa_factory);
/* use the adapter instead of spa-node-factory if requested */
pw_factory_name =
(data->flags & FLAG_USE_ADAPTER) ? "adapter" : "spa-node-factory";
props = wp_properties_copy (props);
wp_properties_set (props, SPA_KEY_FACTORY_NAME, spa_factory);
setup_node_props (parent_props, props);
/* create the node */
node = (data->flags & FLAG_LOCAL_NODES) ?
(WpProxy *) wp_impl_node_new_from_pw_factory (core, pw_factory_name, props) :
(WpProxy *) wp_node_new_from_factory (core, pw_factory_name, props);
if (!node)
return;
/* export to pipewire by requesting FEATURE_BOUND */
wp_proxy_augment (node, WP_PROXY_FEATURE_BOUND, NULL, augment_done, NULL);
g_object_set_qdata (G_OBJECT (node), id_quark (), GUINT_TO_POINTER (id));
*children = g_list_prepend (*children, node);
}
static void
device_created (GObject * proxy, GAsyncResult * res, gpointer user_data)
{
struct module_data * data = user_data;
g_autoptr (GError) error = NULL;
if (!wp_proxy_augment_finish (WP_PROXY (proxy), res, &error)) {
g_warning ("%s", error->message);
return;
}
if (data->flags & FLAG_ACTIVATE_DEVICES) {
char buf[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
wp_proxy_set_param (WP_PROXY (proxy),
SPA_PARAM_Profile, 0,
spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_ParamProfile, 0,
SPA_PARAM_PROFILE_index, SPA_POD_Int (1)));
}
}
static void
create_device (struct module_data * data, WpProxy * parent, GList ** children,
guint id, const gchar * spa_factory, WpProperties * props)
{
g_autoptr (WpCore) core = wp_proxy_get_core (parent);
WpSpaDevice *device;
g_debug ("module-monitor:%p:%s new device %u", data, data->factory, id);
props = wp_properties_copy (props);
setup_device_props (props);
if (!(device = wp_spa_device_new_from_spa_factory (core, spa_factory, props)))
return;
g_signal_connect (device, "object-info", (GCallback) on_object_info, data);
wp_proxy_augment (WP_PROXY (device),
WP_PROXY_FEATURE_BOUND | WP_SPA_DEVICE_FEATURE_ACTIVE,
NULL, device_created, data);
g_object_set_qdata (G_OBJECT (device), id_quark (), GUINT_TO_POINTER (id));
*children = g_list_prepend (*children, device);
}
static void
on_object_info (WpSpaDevice * device,
guint id, GType type, const gchar * spa_factory,
WpProperties * props, WpProperties * parent_props,
struct module_data * data)
{
GList *children = NULL;
GList *link = NULL;
GObject *child = NULL;
/* Find the child */
find_child (G_OBJECT (device), id, &children, &link, &child);
/* new object, construct... */
if (type != G_TYPE_NONE && !link) {
if (type == WP_TYPE_DEVICE) {
create_device (data, WP_PROXY (device), &children, id, spa_factory, props);
} else if (type == WP_TYPE_NODE) {
create_node (data, WP_PROXY (device), &children, id, spa_factory, props,
parent_props);
} else {
g_debug ("module-monitor:%p:%s got device object-info for unknown object "
"type %s", data, data->factory, g_type_name (type));
}
}
/* object removed, delete... */
else if (type == G_TYPE_NONE && link) {
g_object_unref (child);
children = g_list_delete_link (children, link);
}
/* put back the children */
g_object_set_qdata_full (G_OBJECT (device), children_quark (), children,
(GDestroyNotify) free_children);
}
static void
start_monitor (WpSpaDevice * monitor)
{
/* no FEATURE_BOUND here; exporting the monitor device is buggy */
wp_proxy_augment (WP_PROXY (monitor),
WP_SPA_DEVICE_FEATURE_ACTIVE,
NULL, augment_done, NULL);
}
static void
module_destroy (gpointer d)
{
struct module_data *data = d;
g_clear_object (&data->monitor);
g_free (data->factory);
g_slice_free (struct module_data, data);
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
WpMonitor *monitor;
const gchar *factory = NULL;
WpMonitorFlags flags = 0;
GVariantIter *iter;
struct module_data *data = g_slice_new0 (struct module_data);
wp_module_set_destroy_callback (module, module_destroy, data);
if (!g_variant_lookup (args, "factory", "&s", &factory)) {
if (!g_variant_lookup (args, "factory", "s", &data->factory)) {
g_message ("Failed to load monitor: no 'factory' key specified");
return;
}
GVariantIter *iter;
if (g_variant_lookup (args, "flags", "as", &iter)) {
gchar *flag_str = NULL;
GFlagsValue *flag_val = NULL;
GFlagsClass *flag_class = g_type_class_ref (WP_TYPE_MONITOR_FLAGS);
while (g_variant_iter_loop (iter, "s", &flag_str)) {
flag_val = g_flags_get_value_by_nick (flag_class, flag_str);
if (flag_val)
flags |= flag_val->value;
for (gint i = 0; i < SPA_N_ELEMENTS (flag_names); i++) {
if (!g_strcmp0 (flag_str, flag_names[i].name))
data->flags |= flag_names[i].flag;
}
}
g_variant_iter_free (iter);
}
monitor = wp_monitor_new (core, factory, NULL, flags);
g_signal_connect (monitor, "setup-device-props",
(GCallback) setup_device_props, module);
g_signal_connect (monitor, "setup-node-props",
(GCallback) setup_node_props, module);
wp_module_set_destroy_callback (module, g_object_unref, monitor);
data->monitor = wp_spa_device_new_from_spa_factory (core, data->factory,
NULL);
g_signal_connect (data->monitor, "object-info", (GCallback) on_object_info,
data);
/* Start the monitor when the connected callback is triggered */
g_signal_connect_object (core, "connected",
(GCallback) start_monitor, monitor, G_CONNECT_SWAPPED);
g_signal_connect_object (core, "connected", (GCallback) start_monitor,
data->monitor, G_CONNECT_SWAPPED);
}

View file

@ -46,7 +46,7 @@ create_link_cb (WpProperties *props, gpointer user_data)
{
WpAudioConvert *self = WP_AUDIO_CONVERT (user_data);
g_autoptr (WpCore) core = NULL;
WpProxy *proxy;
WpLink *link;
core = wp_audio_stream_get_core (WP_AUDIO_STREAM (self));
g_return_if_fail (core);
@ -57,10 +57,10 @@ create_link_cb (WpProperties *props, gpointer user_data)
wp_properties_set (props, PW_KEY_LINK_PASSIVE, "1");
/* Create the link */
proxy = wp_core_create_remote_object(core, "link-factory",
PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, props);
g_return_if_fail (proxy);
g_ptr_array_add(self->link_proxies, proxy);
link = wp_link_new_from_factory (core, "link-factory",
wp_properties_ref (props));
g_return_if_fail (link);
g_ptr_array_add(self->link_proxies, link);
}
static void
@ -177,8 +177,8 @@ wp_audio_convert_init_async (GAsyncInitable *initable, int io_priority,
wp_properties_set (props, "factory.name", SPA_NAME_AUDIO_CONVERT);
/* Create the proxy */
proxy = wp_core_create_remote_object (core, "spa-node-factory",
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, props);
proxy = (WpProxy *) wp_node_new_from_factory (core, "spa-node-factory",
g_steal_pointer (&props));
g_return_if_fail (proxy);
g_object_set (self, "node", proxy, NULL);

View file

@ -265,7 +265,7 @@ wp_audio_stream_init_async (GAsyncInitable *initable, int io_priority,
g_return_if_fail (priv->proxy);
wp_proxy_augment (WP_PROXY (priv->proxy),
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO, NULL,
WP_PROXY_FEATURES_STANDARD, cancellable,
(GAsyncReadyCallback) on_node_proxy_augmented, self);
}

View file

@ -170,21 +170,20 @@ create_link_cb (WpProperties *props, gpointer user_data)
{
WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(user_data);
g_autoptr (WpCore) core = NULL;
WpProxy *proxy;
WpLink *link;
core = g_weak_ref_get (&self->core);
g_return_if_fail (core);
/* Create the link */
proxy = wp_core_create_remote_object(core, "link-factory",
PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, props);
g_return_if_fail (proxy);
g_ptr_array_add(self->link_proxies, proxy);
link = wp_link_new_from_factory (core, "link-factory",
wp_properties_ref (props));
g_return_if_fail (link);
g_ptr_array_add (self->link_proxies, link);
/* Wait for the link to be created on the server side
by waiting for the info event, which will be signaled anyway */
/* Wait for the link to be created on the server side */
self->link_count++;
wp_proxy_augment (proxy, WP_PROXY_FEATURE_INFO, NULL,
wp_proxy_augment (WP_PROXY (link), WP_PROXY_FEATURE_BOUND, NULL,
(GAsyncReadyCallback) on_link_augmented, self);
}

View file

@ -56,9 +56,11 @@ loop_thread_start (void *d)
/* Create the server */
wp_test_server_setup (&self->server);
/* Add the audioconvert SPA library */
/* Load needed plugins on the server side */
pw_context_add_spa_lib (self->server.context, "audio.convert*",
"audioconvert/libspa-audioconvert");
pw_context_load_module (self->server.context,
"libpipewire-module-spa-node-factory", NULL, NULL);
/* Create the core and connect to the server */
g_autoptr (WpProperties) props = NULL;

View file

@ -113,8 +113,6 @@ test_proxy_basic_object_added (WpObjectManager *om, WpProxy *proxy,
g_assert_nonnull (omcore);
g_assert_true (pcore == omcore);
}
g_assert_cmpstr (wp_proxy_get_interface_type (proxy), ==,
PW_TYPE_INTERFACE_Client);
g_assert_cmphex (wp_proxy_get_global_permissions (proxy), ==, PW_PERM_RWX);
g_assert_true (WP_IS_CLIENT (proxy));
@ -192,10 +190,7 @@ test_node_object_added (WpObjectManager *om, WpProxy *proxy,
TestNodeParamData *param_data;
g_assert_nonnull (proxy);
g_assert_cmpstr (wp_proxy_get_interface_type (proxy), ==,
PW_TYPE_INTERFACE_Node);
g_assert_cmphex (wp_proxy_get_features (proxy), ==,
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND);
g_assert_cmphex (wp_proxy_get_features (proxy), ==, WP_PROXY_FEATURES_STANDARD);
g_assert_nonnull (wp_proxy_get_pw_proxy (proxy));
g_assert_true (WP_IS_NODE (proxy));
@ -246,8 +241,7 @@ test_node (TestProxyFixture *fixture, gconstpointer data)
/* declare interest and set default features to be ready
when the signal is fired */
wp_object_manager_add_proxy_interest (fixture->om,
PW_TYPE_INTERFACE_Node, NULL,
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND);
PW_TYPE_INTERFACE_Node, NULL, WP_PROXY_FEATURES_STANDARD);
wp_core_install_object_manager (fixture->core, fixture->om);
g_assert_true (wp_core_connect (fixture->core));