
- Use similar code for consistency - Add changed signals everywhere - Port to the new object-manager API
626 lines
18 KiB
C
626 lines
18 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2019-2020 Collabora Ltd.
|
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "wp-node"
|
|
|
|
#include "node.h"
|
|
#include "debug.h"
|
|
#include "error.h"
|
|
#include "private.h"
|
|
#include "wpenums.h"
|
|
|
|
#include <pipewire/pipewire.h>
|
|
#include <pipewire/impl.h>
|
|
|
|
enum {
|
|
SIGNAL_STATE_CHANGED,
|
|
SIGNAL_PORTS_CHANGED,
|
|
N_SIGNALS,
|
|
};
|
|
|
|
static guint32 signals[N_SIGNALS] = {0};
|
|
|
|
typedef struct _WpNodePrivate WpNodePrivate;
|
|
struct _WpNodePrivate
|
|
{
|
|
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_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);
|
|
}
|
|
|
|
static void
|
|
wp_node_emit_ports_changed (WpObjectManager *ports_om, WpNode * self)
|
|
{
|
|
g_signal_emit (self, signals[SIGNAL_PORTS_CHANGED], 0);
|
|
}
|
|
|
|
static void
|
|
wp_node_ensure_feature_ports (WpNode * self, guint32 bound_id)
|
|
{
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
WpProxyFeatures ft = wp_proxy_get_features (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));
|
|
|
|
if (!bound_id)
|
|
bound_id = wp_proxy_get_bound_id (WP_PROXY (self));
|
|
|
|
wp_debug_object (self, "enabling WP_NODE_FEATURE_PORTS, bound_id:%u",
|
|
bound_id);
|
|
|
|
priv->ports_om = wp_object_manager_new ();
|
|
wp_object_manager_add_interest_1 (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);
|
|
|
|
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);
|
|
|
|
wp_core_install_object_manager (core, priv->ports_om);
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_node_augment (WpProxy * proxy, WpProxyFeatures features)
|
|
{
|
|
WpNode *self = WP_NODE (proxy);
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
|
|
/* call the parent impl first to ensure we have a pw proxy if necessary */
|
|
WP_PROXY_CLASS (wp_node_parent_class)->augment (proxy, features);
|
|
|
|
if (features & WP_NODE_FEATURE_PORTS) {
|
|
priv->ft_ports_requested = TRUE;
|
|
wp_node_ensure_feature_ports (self, 0);
|
|
}
|
|
}
|
|
|
|
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 gint
|
|
wp_node_enum_params (WpProxy * self, guint32 id, guint32 start,
|
|
guint32 num, const WpSpaPod * filter)
|
|
{
|
|
struct pw_node *pwp;
|
|
int node_enum_params_result;
|
|
|
|
pwp = (struct pw_node *) wp_proxy_get_pw_proxy (self);
|
|
node_enum_params_result = pw_node_enum_params (pwp, 0, id, start, num,
|
|
filter ? wp_spa_pod_get_spa_pod (filter) : NULL);
|
|
g_warn_if_fail (node_enum_params_result >= 0);
|
|
|
|
return node_enum_params_result;
|
|
}
|
|
|
|
static gint
|
|
wp_node_subscribe_params (WpProxy * self, guint32 n_ids, guint32 *ids)
|
|
{
|
|
struct pw_node *pwp;
|
|
int node_subscribe_params_result;
|
|
|
|
pwp = (struct pw_node *) wp_proxy_get_pw_proxy (self);
|
|
node_subscribe_params_result = pw_node_subscribe_params (pwp, ids, n_ids);
|
|
g_warn_if_fail (node_subscribe_params_result >= 0);
|
|
|
|
return node_subscribe_params_result;
|
|
}
|
|
|
|
static gint
|
|
wp_node_set_param (WpProxy * self, guint32 id, guint32 flags,
|
|
const WpSpaPod *param)
|
|
{
|
|
struct pw_node *pwp;
|
|
int node_set_param_result;
|
|
|
|
pwp = (struct pw_node *) wp_proxy_get_pw_proxy (self);
|
|
node_set_param_result = pw_node_set_param (pwp, id, flags,
|
|
wp_spa_pod_get_spa_pod (param));
|
|
g_warn_if_fail (node_set_param_result >= 0);
|
|
|
|
return node_set_param_result;
|
|
}
|
|
|
|
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;
|
|
|
|
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_STATE)
|
|
g_signal_emit (self, signals[SIGNAL_STATE_CHANGED], 0, old_state,
|
|
priv->info->state);
|
|
|
|
wp_node_ensure_feature_ports (self, 0);
|
|
}
|
|
|
|
static const struct pw_node_events node_events = {
|
|
PW_VERSION_NODE_EVENTS,
|
|
.info = node_event_info,
|
|
.param = wp_proxy_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);
|
|
}
|
|
|
|
static void
|
|
wp_node_bound (WpProxy * proxy, guint32 id)
|
|
{
|
|
WpNode *self = WP_NODE (proxy);
|
|
wp_node_ensure_feature_ports (self, id);
|
|
}
|
|
|
|
static void
|
|
wp_node_class_init (WpNodeClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
WpProxyClass *proxy_class = (WpProxyClass *) 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->augment = wp_node_augment;
|
|
proxy_class->get_info = wp_node_get_info;
|
|
proxy_class->get_properties = wp_node_get_properties;
|
|
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;
|
|
|
|
/**
|
|
* WpNode::state-changed:
|
|
* @self: the node
|
|
* @old_state: the old state
|
|
* @new_state: the new state
|
|
*
|
|
* Emitted when the node changes state. This is only emitted
|
|
* when %WP_PROXY_FEATURE_INFO is enabled.
|
|
*/
|
|
signals[SIGNAL_STATE_CHANGED] = g_signal_new (
|
|
"state-changed", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
|
|
WP_TYPE_NODE_STATE, WP_TYPE_NODE_STATE);
|
|
|
|
/**
|
|
* WpNode::ports-changed:
|
|
* @self: the node
|
|
*
|
|
* Emitted when the node's ports change. This is only emitted
|
|
* when %WP_NODE_FEATURE_PORTS is enabled.
|
|
*/
|
|
signals[SIGNAL_PORTS_CHANGED] = g_signal_new (
|
|
"ports-changed", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
|
}
|
|
|
|
/**
|
|
* 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 (G_UNLIKELY (!pw_core)) {
|
|
g_critical ("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;
|
|
}
|
|
|
|
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);
|
|
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
if (error)
|
|
*error = priv->info->error;
|
|
return (WpNodeState) priv->info->state;
|
|
}
|
|
|
|
/**
|
|
* wp_node_get_n_input_ports:
|
|
* @self: the node
|
|
* @max: (out) (optional): the maximum supported number of input ports
|
|
*
|
|
* Requires %WP_PROXY_FEATURE_INFO
|
|
*
|
|
* Returns: the number of input ports of this node, as reported by the node info
|
|
*/
|
|
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);
|
|
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
if (max)
|
|
*max = priv->info->max_input_ports;
|
|
return priv->info->n_input_ports;
|
|
}
|
|
|
|
/**
|
|
* wp_node_get_n_output_ports:
|
|
* @self: the node
|
|
* @max: (out) (optional): the maximum supported number of output ports
|
|
*
|
|
* Requires %WP_PROXY_FEATURE_INFO
|
|
*
|
|
* Returns: the number of output ports of this node, as reported by the node info
|
|
*/
|
|
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);
|
|
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
if (max)
|
|
*max = priv->info->max_output_ports;
|
|
return priv->info->n_output_ports;
|
|
}
|
|
|
|
/**
|
|
* wp_node_get_n_ports:
|
|
* @self: the node
|
|
*
|
|
* Requires %WP_NODE_FEATURE_PORTS
|
|
*
|
|
* Returns: the number of ports of this node. Note that this number may not
|
|
* add up to wp_node_get_n_input_ports() + wp_node_get_n_output_ports()
|
|
* because it is discovered by looking at the number of available ports
|
|
* in the registry, however ports may appear there with a delay or may
|
|
* not appear at all if this client does not have permission to read them
|
|
*/
|
|
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)) &
|
|
WP_NODE_FEATURE_PORTS, 0);
|
|
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
return wp_object_manager_get_n_objects (priv->ports_om);
|
|
}
|
|
|
|
/**
|
|
* wp_node_find_port:
|
|
* @self: the node
|
|
* @bound_id: the bound id of the port object to find
|
|
*
|
|
* Requires %WP_NODE_FEATURE_PORTS
|
|
*
|
|
* Returns: (transfer full) (nullable): the port that has the given
|
|
* @bound_id, or %NULL if there is no such port
|
|
*/
|
|
WpPort *
|
|
wp_node_find_port (WpNode * self, guint32 bound_id)
|
|
{
|
|
g_return_val_if_fail (WP_IS_NODE (self), NULL);
|
|
g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) &
|
|
WP_NODE_FEATURE_PORTS, NULL);
|
|
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
return (WpPort *) wp_object_manager_lookup (priv->ports_om,
|
|
WP_TYPE_PORT,
|
|
WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", bound_id,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* wp_node_iterate_ports:
|
|
* @self: the node
|
|
*
|
|
* Requires %WP_NODE_FEATURE_PORTS
|
|
*
|
|
* Returns: (transfer full): a #WpIterator that iterates over all
|
|
* the ports that belong to this node
|
|
*/
|
|
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)) &
|
|
WP_NODE_FEATURE_PORTS, NULL);
|
|
|
|
WpNodePrivate *priv = wp_node_get_instance_private (self);
|
|
return wp_object_manager_iterate (priv->ports_om);
|
|
}
|
|
|
|
|
|
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));
|
|
}
|
|
|
|
if (features & WP_NODE_FEATURE_PORTS) {
|
|
WpNodePrivate *priv = wp_node_get_instance_private (WP_NODE (self));
|
|
priv->ft_ports_requested = TRUE;
|
|
wp_node_ensure_feature_ports (WP_NODE (self), 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) {
|
|
wp_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) {
|
|
wp_warning ("failed to create node from factory '%s'", factory_name);
|
|
return NULL;
|
|
}
|
|
|
|
return wp_impl_node_new_wrap (core, node);
|
|
}
|