mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-01-06 18:30:10 +01:00
There are 3 kinds of WpProxy objects: * the ones that are created as a result of binding a global from the registry * the ones that are created as a result of calling into a remote factory (wp_node_new_from_factory, etc...) * the ones that are a local implementation of an object (WpImplNode, etc...) and are exported Previously the object manager was only able to track the first kind. With these changes we can now also have globals associated with WpProxies that were created earlier (and caused the creation of the global). This saves some resources and reduces round-trips (in case client code wants to change properties of an object that is locally implemented, it shouldn't need to do a round-trip through the server)
404 lines
9.8 KiB
C
404 lines
9.8 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2019 Collabora Ltd.
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include "core.h"
|
|
#include "wp.h"
|
|
#include "private.h"
|
|
|
|
#include <pipewire/pipewire.h>
|
|
|
|
#include <spa/utils/result.h>
|
|
#include <spa/debug/types.h>
|
|
|
|
/*
|
|
* Integration between the PipeWire main loop and GMainLoop
|
|
*/
|
|
|
|
#define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)
|
|
|
|
typedef struct _WpLoopSource WpLoopSource;
|
|
struct _WpLoopSource
|
|
{
|
|
GSource parent;
|
|
struct pw_loop *loop;
|
|
};
|
|
|
|
static gboolean
|
|
wp_loop_source_dispatch (GSource * s, GSourceFunc callback, gpointer user_data)
|
|
{
|
|
int result;
|
|
|
|
pw_loop_enter (WP_LOOP_SOURCE(s)->loop);
|
|
result = pw_loop_iterate (WP_LOOP_SOURCE(s)->loop, 0);
|
|
pw_loop_leave (WP_LOOP_SOURCE(s)->loop);
|
|
|
|
if (G_UNLIKELY (result < 0))
|
|
g_warning ("pw_loop_iterate failed: %s", spa_strerror (result));
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
wp_loop_source_finalize (GSource * s)
|
|
{
|
|
pw_loop_destroy (WP_LOOP_SOURCE(s)->loop);
|
|
}
|
|
|
|
static GSourceFuncs source_funcs = {
|
|
NULL,
|
|
NULL,
|
|
wp_loop_source_dispatch,
|
|
wp_loop_source_finalize
|
|
};
|
|
|
|
static GSource *
|
|
wp_loop_source_new (void)
|
|
{
|
|
GSource *s = g_source_new (&source_funcs, sizeof (WpLoopSource));
|
|
WP_LOOP_SOURCE(s)->loop = pw_loop_new (NULL);
|
|
|
|
g_source_add_unix_fd (s,
|
|
pw_loop_get_fd (WP_LOOP_SOURCE(s)->loop),
|
|
G_IO_IN | G_IO_ERR | G_IO_HUP);
|
|
|
|
return (GSource *) s;
|
|
}
|
|
|
|
/**
|
|
* WpCore
|
|
*/
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_CONTEXT,
|
|
PROP_PROPERTIES,
|
|
PROP_PW_CONTEXT,
|
|
PROP_PW_CORE,
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_CONNECTED,
|
|
SIGNAL_DISCONNECTED,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
static guint32 signals[NUM_SIGNALS];
|
|
|
|
|
|
G_DEFINE_TYPE (WpCore, wp_core, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
core_done (void *data, uint32_t id, int seq)
|
|
{
|
|
WpCore *self = WP_CORE (data);
|
|
g_autoptr (GTask) task = NULL;
|
|
|
|
g_hash_table_steal_extended (self->async_tasks, GINT_TO_POINTER (seq), NULL,
|
|
(gpointer *) &task);
|
|
if (task)
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
static const struct pw_core_events core_events = {
|
|
PW_VERSION_CORE_EVENTS,
|
|
.done = core_done,
|
|
};
|
|
|
|
static void
|
|
proxy_core_destroy (void *data)
|
|
{
|
|
WpCore *self = WP_CORE (data);
|
|
self->pw_core = NULL;
|
|
|
|
/* Emit the disconnected signal */
|
|
g_signal_emit (self, signals[SIGNAL_DISCONNECTED], 0);
|
|
}
|
|
|
|
static const struct pw_proxy_events proxy_core_events = {
|
|
PW_VERSION_PROXY_EVENTS,
|
|
.destroy = proxy_core_destroy,
|
|
};
|
|
|
|
static void
|
|
wp_core_init (WpCore * self)
|
|
{
|
|
wp_registry_init (&self->registry);
|
|
self->async_tasks = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
|
NULL, g_object_unref);
|
|
}
|
|
|
|
static void
|
|
wp_core_constructed (GObject *object)
|
|
{
|
|
WpCore *self = WP_CORE (object);
|
|
g_autoptr (GSource) source = NULL;
|
|
struct pw_properties *p = NULL;
|
|
|
|
/* loop */
|
|
source = wp_loop_source_new ();
|
|
g_source_attach (source, self->context);
|
|
|
|
/* context */
|
|
p = self->properties ? wp_properties_to_pw_properties (self->properties) : NULL;
|
|
self->pw_context = pw_context_new (WP_LOOP_SOURCE(source)->loop, p, 0);
|
|
|
|
G_OBJECT_CLASS (wp_core_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
wp_core_dispose (GObject * obj)
|
|
{
|
|
WpCore *self = WP_CORE (obj);
|
|
|
|
wp_registry_clear (&self->registry);
|
|
|
|
G_OBJECT_CLASS (wp_core_parent_class)->dispose (obj);
|
|
}
|
|
|
|
static void
|
|
wp_core_finalize (GObject * obj)
|
|
{
|
|
WpCore *self = WP_CORE (obj);
|
|
|
|
wp_core_disconnect (self);
|
|
g_clear_pointer (&self->pw_context, pw_context_destroy);
|
|
|
|
g_clear_pointer (&self->properties, wp_properties_unref);
|
|
g_clear_pointer (&self->context, g_main_context_unref);
|
|
g_clear_pointer (&self->async_tasks, g_hash_table_unref);
|
|
|
|
g_debug ("WpCore destroyed");
|
|
|
|
G_OBJECT_CLASS (wp_core_parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
wp_core_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpCore *self = WP_CORE (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_CONTEXT:
|
|
g_value_set_boxed (value, self->context);
|
|
break;
|
|
case PROP_PROPERTIES:
|
|
g_value_set_boxed (value, self->properties);
|
|
break;
|
|
case PROP_PW_CONTEXT:
|
|
g_value_set_pointer (value, self->pw_context);
|
|
break;
|
|
case PROP_PW_CORE:
|
|
g_value_set_pointer (value, self->pw_core);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_core_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpCore *self = WP_CORE (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_CONTEXT:
|
|
self->context = g_value_dup_boxed (value);
|
|
break;
|
|
case PROP_PROPERTIES:
|
|
self->properties = g_value_dup_boxed (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_core_class_init (WpCoreClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
|
|
pw_init (NULL, NULL);
|
|
|
|
object_class->constructed = wp_core_constructed;
|
|
object_class->dispose = wp_core_dispose;
|
|
object_class->finalize = wp_core_finalize;
|
|
object_class->get_property = wp_core_get_property;
|
|
object_class->set_property = wp_core_set_property;
|
|
|
|
g_object_class_install_property (object_class, PROP_CONTEXT,
|
|
g_param_spec_boxed ("context", "context", "A GMainContext to attach to",
|
|
G_TYPE_MAIN_CONTEXT,
|
|
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", "Extra properties",
|
|
WP_TYPE_PROPERTIES,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_PW_CONTEXT,
|
|
g_param_spec_pointer ("pw-context", "pw-context", "The pipewire context",
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class, PROP_PW_CORE,
|
|
g_param_spec_pointer ("pw-core", "pw-core", "The pipewire core",
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/* Signals */
|
|
signals[SIGNAL_CONNECTED] = g_signal_new ("connected",
|
|
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
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_ENDPOINT);
|
|
g_type_ensure (WP_TYPE_LINK);
|
|
g_type_ensure (WP_TYPE_NODE);
|
|
g_type_ensure (WP_TYPE_PORT);
|
|
g_type_ensure (WP_TYPE_SESSION);
|
|
}
|
|
|
|
WpCore *
|
|
wp_core_new (GMainContext *context, WpProperties * properties)
|
|
{
|
|
return g_object_new (WP_TYPE_CORE,
|
|
"context", context,
|
|
"properties", properties,
|
|
NULL);
|
|
}
|
|
|
|
GMainContext *
|
|
wp_core_get_context (WpCore * self)
|
|
{
|
|
g_return_val_if_fail (WP_IS_CORE (self), NULL);
|
|
return self->context;
|
|
}
|
|
|
|
struct pw_context *
|
|
wp_core_get_pw_context (WpCore * self)
|
|
{
|
|
g_return_val_if_fail (WP_IS_CORE (self), NULL);
|
|
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;
|
|
}
|
|
|
|
gboolean
|
|
wp_core_connect (WpCore *self)
|
|
{
|
|
struct pw_properties *p = NULL;
|
|
|
|
g_return_val_if_fail (WP_IS_CORE (self), FALSE);
|
|
|
|
/* Don't do anything if core is already connected */
|
|
if (self->pw_core)
|
|
return TRUE;
|
|
|
|
/* Connect */
|
|
p = self->properties ? wp_properties_to_pw_properties (self->properties) : NULL;
|
|
self->pw_core = pw_context_connect (self->pw_context, p, 0);
|
|
if (!self->pw_core)
|
|
return FALSE;
|
|
|
|
/* Add the core listeners */
|
|
pw_core_add_listener (self->pw_core, &self->core_listener, &core_events, self);
|
|
pw_proxy_add_listener((struct pw_proxy*)self->pw_core,
|
|
&self->proxy_core_listener, &proxy_core_events, self);
|
|
|
|
/* Add the registry listener */
|
|
wp_registry_attach (&self->registry, self->pw_core);
|
|
|
|
/* Emit the connected signal */
|
|
g_signal_emit (self, signals[SIGNAL_CONNECTED], 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
wp_core_disconnect (WpCore *self)
|
|
{
|
|
wp_registry_detach (&self->registry);
|
|
g_clear_pointer (&self->pw_core, pw_core_disconnect);
|
|
|
|
/* Emit the disconnected signal */
|
|
g_signal_emit (self, signals[SIGNAL_DISCONNECTED], 0);
|
|
}
|
|
|
|
gboolean
|
|
wp_core_is_connected (WpCore * self)
|
|
{
|
|
g_return_val_if_fail (WP_IS_CORE (self), FALSE);
|
|
return self->pw_core != NULL;
|
|
}
|
|
|
|
guint
|
|
wp_core_idle_add (WpCore * self, GSourceFunc function, gpointer data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
g_autoptr (GSource) source = NULL;
|
|
|
|
g_return_val_if_fail (WP_IS_CORE (self), 0);
|
|
|
|
source = g_idle_source_new ();
|
|
g_source_set_callback (source, function, data, destroy);
|
|
g_source_attach (source, self->context);
|
|
return g_source_get_id (source);
|
|
}
|
|
|
|
gboolean
|
|
wp_core_sync (WpCore * self, GCancellable * cancellable,
|
|
GAsyncReadyCallback callback, gpointer user_data)
|
|
{
|
|
g_autoptr (GTask) task = NULL;
|
|
int seq;
|
|
|
|
g_return_val_if_fail (WP_IS_CORE (self), FALSE);
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
if (G_UNLIKELY (!self->pw_core)) {
|
|
g_warn_if_reached ();
|
|
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
|
|
WP_LIBRARY_ERROR_INVARIANT, "No pipewire core");
|
|
return FALSE;
|
|
}
|
|
|
|
seq = pw_core_sync (self->pw_core, 0, 0);
|
|
if (G_UNLIKELY (seq < 0)) {
|
|
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED, "pw_core_sync failed: %s",
|
|
g_strerror (-seq));
|
|
return FALSE;
|
|
}
|
|
|
|
g_hash_table_insert (self->async_tasks, GINT_TO_POINTER (seq),
|
|
g_steal_pointer (&task));
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
wp_core_sync_finish (WpCore * self, GAsyncResult * res, GError ** error)
|
|
{
|
|
g_return_val_if_fail (WP_IS_CORE (self), FALSE);
|
|
g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|