Files
wireplumber/lib/wp/core.c
George Kiagiadakis 8a8cd97ca8 core: make the object registration functions public
This allows registering arbitrary objects on the core's registry and
finding them later, without having to add API for each and every object.

I think this is useful enough to have it public, even though it's
probably not going to be used that much... The rationale here is to
allow registering custom component loaders without having to make them
subclass WpPlugin or to create custom API for registering component
loaders specifically.

Also, remove the wp_plugin_register() and wp_si_factory_register()
functions, since they are not going to be used much in the future.
The idea is to let the component loader do the registration under the
scenes, as the component is getting loaded.
2023-06-20 12:39:29 +03:00

1172 lines
32 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/registry.h"
#include "private/internal-comp-loader.h"
#include <pipewire/pipewire.h>
#include <spa/utils/result.h>
#include <spa/debug/types.h>
#include <spa/support/cpu.h>
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-core")
/*
* 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;
wp_trace_boxed (G_TYPE_SOURCE, s, "entering pw main loop");
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);
wp_trace_boxed (G_TYPE_SOURCE, s, "leaving pw main loop");
if (G_UNLIKELY (result < 0))
wp_warning_boxed (G_TYPE_SOURCE, s,
"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;
}
/*! \defgroup wpcore WpCore */
/*!
* \struct WpCore
*
* The core is the central object around which everything operates. It is
* essential to create a WpCore before using any other WirePlumber API.
*
* The core object has the following responsibilities:
* * it initializes the PipeWire library
* * it creates a `pw_context` and allows connecting to the PipeWire server,
* creating a local `pw_core`
* * it glues the PipeWire library's event loop system with GMainLoop
* * it maintains a list of registered objects, which other classes use
* to keep objects loaded permanently into memory
* * it watches the PipeWire registry and keeps track of remote and local
* objects that appear in the registry, making them accessible through
* the WpObjectManager API.
*
* \gproperties
*
* \gproperty{g-main-context, GMainContext *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
* A GMainContext to attach to}
*
* \gproperty{properties, WpProperties *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
* The pipewire properties of the pw_core}
*
* \gproperty{pw-context, gpointer (struct pw_context *), G_PARAM_READABLE,
* The pipewire context}
*
* \gproperty{pw-core, gpointer (struct pw_core *), G_PARAM_READABLE,
* The pipewire core}
*
* \gsignals
*
* \par connected
* \parblock
* \code
* void
* connected_callback (WpCore * self,
* gpointer user_data)
* \endcode
* Emitted when the core is successfully connected to the PipeWire server
* \endparblock
*
* \par disconnected
* \parblock
* \code
* void
* disconnected_callback (WpCore * self,
* gpointer user_data)
* \endcode
* Emitted when the core is disconnected from the PipeWire server
* \endparblock
*/
struct _WpCore
{
GObject parent;
/* main loop integration */
GMainContext *g_main_context;
/* extra properties */
WpProperties *properties;
/* pipewire main objects */
struct pw_context *pw_context;
struct pw_core *pw_core;
struct pw_core_info *info;
/* pipewire main listeners */
struct spa_hook core_listener;
struct spa_hook proxy_core_listener;
WpRegistry registry;
GHashTable *async_tasks; // <int seq, GTask*>
};
enum {
PROP_0,
PROP_G_MAIN_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_info (void *data, const struct pw_core_info * info)
{
WpCore *self = WP_CORE (data);
gboolean new_connection = (self->info == NULL);
self->info = pw_core_info_update (self->info, info);
wp_info_object (self, "connected to server: %s, cookie: %u",
self->info->name, self->info->cookie);
if (new_connection)
g_signal_emit (self, signals[SIGNAL_CONNECTED], 0);
}
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);
wp_debug_object (self, "done, seq 0x%x, task " WP_OBJECT_FORMAT,
seq, WP_OBJECT_ARGS (task));
if (task)
g_task_return_boolean (task, TRUE);
}
static gboolean
core_disconnect_async (WpCore * self)
{
wp_core_disconnect (self);
return G_SOURCE_REMOVE;
}
static void
core_error (void *data, uint32_t id, int seq, int res, const char *message)
{
WpCore *self = WP_CORE (data);
/* protocol socket disconnected; schedule disconnecting our core */
if (id == 0 && res == -EPIPE) {
wp_core_idle_add_closure (self, NULL, g_cclosure_new_object (
G_CALLBACK (core_disconnect_async), G_OBJECT (self)));
}
}
static const struct pw_core_events core_events = {
PW_VERSION_CORE_EVENTS,
.info = core_info,
.done = core_done,
.error = core_error,
};
static gboolean
async_tasks_finish (gpointer key, gpointer value, gpointer user_data)
{
GTask *task = G_TASK (value);
g_return_val_if_fail (task, FALSE);
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_INVARIANT, "core disconnected");
return TRUE;
}
static void
proxy_core_destroy (void *data)
{
WpCore *self = WP_CORE (data);
g_hash_table_foreach_remove (self->async_tasks, async_tasks_finish, NULL);
g_clear_pointer (&self->info, pw_core_info_free);
spa_hook_remove(&self->core_listener);
spa_hook_remove(&self->proxy_core_listener);
self->pw_core = NULL;
wp_debug_object (self, "emit disconnected");
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);
wp_core_register_object (self,
g_object_new (WP_TYPE_INTERNAL_COMP_LOADER, NULL));
}
static void
wp_core_constructed (GObject *object)
{
WpCore *self = WP_CORE (object);
g_autoptr (GSource) source = NULL;
/* loop */
source = wp_loop_source_new ();
g_source_attach (source, self->g_main_context);
/* context */
if (!self->pw_context) {
struct pw_properties *p = NULL;
const gchar *str = NULL;
/* properties are fully stored in the pw_context, no need to keep a copy */
p = self->properties ?
wp_properties_unref_and_take_pw_properties (self->properties) : NULL;
self->properties = NULL;
self->pw_context = pw_context_new (WP_LOOP_SOURCE(source)->loop, p,
sizeof (grefcount));
g_return_if_fail (self->pw_context);
/* use the same config option as pipewire to set the log level */
p = (struct pw_properties *) pw_context_get_properties (self->pw_context);
if (!g_getenv("WIREPLUMBER_DEBUG") &&
(str = pw_properties_get(p, "log.level")) != NULL)
wp_log_set_global_level (str);
/* Init refcount */
grefcount *rc = pw_context_get_user_data (self->pw_context);
g_return_if_fail (rc);
g_ref_count_init (rc);
} else {
/* Increase refcount */
grefcount *rc = pw_context_get_user_data (self->pw_context);
g_return_if_fail (rc);
g_ref_count_inc (rc);
}
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);
grefcount *rc = pw_context_get_user_data (self->pw_context);
g_return_if_fail (rc);
wp_core_disconnect (self);
/* Clear pw-context if refcount reaches 0 */
if (g_ref_count_dec (rc))
g_clear_pointer (&self->pw_context, pw_context_destroy);
g_clear_pointer (&self->properties, wp_properties_unref);
g_clear_pointer (&self->g_main_context, g_main_context_unref);
g_clear_pointer (&self->async_tasks, g_hash_table_unref);
wp_debug_object (self, "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_G_MAIN_CONTEXT:
g_value_set_boxed (value, self->g_main_context);
break;
case PROP_PROPERTIES:
g_value_take_boxed (value, wp_core_get_properties (self));
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_G_MAIN_CONTEXT:
self->g_main_context = g_value_dup_boxed (value);
break;
case PROP_PROPERTIES:
self->properties = g_value_dup_boxed (value);
break;
case PROP_PW_CONTEXT:
self->pw_context = g_value_get_pointer (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;
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_G_MAIN_CONTEXT,
g_param_spec_boxed ("g-main-context", "g-main-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_READWRITE | G_PARAM_CONSTRUCT_ONLY | 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[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);
}
/*!
* \brief Creates a new core object
*
* \ingroup wpcore
* \param context (transfer none) (nullable): the GMainContext to use for events
* \param properties (transfer full) (nullable): additional properties, which are
* passed to pw_context_new() and pw_context_connect()
* \returns (transfer full): a new WpCore
*/
WpCore *
wp_core_new (GMainContext *context, WpProperties * properties)
{
g_autoptr (WpProperties) props = properties;
return g_object_new (WP_TYPE_CORE,
"g-main-context", context,
"properties", properties,
"pw-context", NULL,
NULL);
}
/*!
* \brief Clones a core with the same context as \a self
*
* \ingroup wpcore
* \param self the core
* \returns (transfer full): the clone WpCore
*/
WpCore *
wp_core_clone (WpCore * self)
{
return g_object_new (WP_TYPE_CORE,
"g-main-context", self->g_main_context,
"properties", self->properties,
"pw-context", self->pw_context,
NULL);
}
/*!
* \brief Gets the GMainContext of the core
*
* \ingroup wpcore
* \param self the core
* \returns (transfer none) (nullable): the GMainContext that is in
* use by this core for events
*/
GMainContext *
wp_core_get_g_main_context (WpCore * self)
{
g_return_val_if_fail (WP_IS_CORE (self), NULL);
return self->g_main_context;
}
/*!
* \brief Gets the internal PipeWire context of the core
*
* \ingroup wpcore
* \param self the core
* \returns (transfer none): the internal pw_context object
*/
struct pw_context *
wp_core_get_pw_context (WpCore * self)
{
g_return_val_if_fail (WP_IS_CORE (self), NULL);
return self->pw_context;
}
/*!
* \brief Gets the internal PipeWire core of the core
*
* \ingroup wpcore
* \param self the core
* \returns (transfer none) (nullable): the internal pw_core object,
* or NULL if the core is not connected to PipeWire
*/
struct pw_core *
wp_core_get_pw_core (WpCore * self)
{
g_return_val_if_fail (WP_IS_CORE (self), NULL);
return self->pw_core;
}
/*!
* \brief Gets the virtual machine type of the core
*
* \ingroup wpcore
* \param self the core
* \returns (transfer full) (nullable): a comma separated string with all the
* virtual machine types that this core matches, or NULL if the core is not
* running in a virtual machine.
*/
gchar *
wp_core_get_vm_type (WpCore *self)
{
const struct spa_support *support;
uint32_t n_support;
struct spa_cpu *spa_cpu;
uint32_t vm_type;
gchar *res;
gboolean first = TRUE;
struct vm_type_info {
uint32_t type;
const gchar *name;
};
static struct vm_type_info vm_types[] = {
{SPA_CPU_VM_OTHER, "other"},
{SPA_CPU_VM_KVM, "kvm"},
{SPA_CPU_VM_QEMU, "qemu"},
{SPA_CPU_VM_BOCHS, "bochs"},
{SPA_CPU_VM_XEN, "zen"},
{SPA_CPU_VM_UML, "uml"},
{SPA_CPU_VM_VMWARE, "vmware"},
{SPA_CPU_VM_ORACLE, "oracle"},
{SPA_CPU_VM_MICROSOFT, "microsoft"},
{SPA_CPU_VM_ZVM, "zvm"},
{SPA_CPU_VM_PARALLELS, "parallels"},
{SPA_CPU_VM_BHYVE, "bhyve"},
{SPA_CPU_VM_QNX, "qnx"},
{SPA_CPU_VM_ACRN, "acrn"},
{SPA_CPU_VM_POWERVM, "powervm"},
{0, NULL},
};
g_return_val_if_fail (WP_IS_CORE (self), NULL);
g_return_val_if_fail (self->pw_context, NULL);
/* Get spa_cpu */
support = pw_context_get_support (self->pw_context, &n_support);
spa_cpu = spa_support_find (support, n_support, SPA_TYPE_INTERFACE_CPU);
g_return_val_if_fail (spa_cpu, NULL);
/* Just return NULL if CPU is not a VM */
vm_type = spa_cpu_get_vm_type (spa_cpu);
if (vm_type == SPA_CPU_VM_NONE)
return NULL;
/* Otherwise return a string with all matching VM types */
res = g_strdup ("");
for (guint i = 0; vm_types[i].name; i++) {
if (vm_type & vm_types[i].type) {
gchar *tmp = g_strdup_printf ("%s%s%s", res, first ? "": ",",
vm_types[i].name);
g_free (res);
res = tmp;
first = FALSE;
}
}
return res;
}
/*!
* \brief Connects this core to the PipeWire server.
*
* When connection succeeds, the WpCore \c "connected" signal is emitted.
*
* \ingroup wpcore
* \param self the core
* \returns TRUE if the core is effectively connected or FALSE if
* connection failed
*/
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);
return TRUE;
}
/*!
* \brief Disconnects this core from the PipeWire server.
*
* This also effectively destroys all WpCore objects that were created through
* the registry, destroys the pw_core and finally emits the WpCore
* \c "disconnected" signal.
*
* \ingroup wpcore
* \param self the core
*/
void
wp_core_disconnect (WpCore *self)
{
wp_registry_detach (&self->registry);
/* pw_core_disconnect destroys the core proxy
and we continue in proxy_core_destroy() */
if (self->pw_core)
pw_core_disconnect (self->pw_core);
}
/*!
* \brief Checks if the core is connected to PipeWire
*
* \ingroup wpcore
* \param self the core
* \returns TRUE if the core is connected to PipeWire, FALSE otherwise
*/
gboolean
wp_core_is_connected (WpCore * self)
{
g_return_val_if_fail (WP_IS_CORE (self), FALSE);
return self->pw_core && self->info;
}
/*!
* \brief Gets the cookie of the core's connected PipeWire instance
*
* \ingroup wpcore
* \param self the core
* \returns The cookie of the PipeWire instance that \a self is connected to.
* The cookie is a unique random number for identifying an instance of
* PipeWire
*/
guint32
wp_core_get_remote_cookie (WpCore * self)
{
g_return_val_if_fail (wp_core_is_connected (self), 0);
return self->info->cookie;
}
/*!
* \brief Gets the name of the core's connected PipeWire instance
* \ingroup wpcore
* \param self the core
* \returns The name of the PipeWire instance that \a self is connected to
*/
const gchar *
wp_core_get_remote_name (WpCore * self)
{
g_return_val_if_fail (wp_core_is_connected (self), NULL);
return self->info->name;
}
/*!
* \brief Gets the user name of the core's connected PipeWire instance
* \ingroup wpcore
* \param self the core
* \returns The name of the user that started the PipeWire instance that
* \a self is connected to
*/
const gchar *
wp_core_get_remote_user_name (WpCore * self)
{
g_return_val_if_fail (wp_core_is_connected (self), NULL);
return self->info->user_name;
}
/*!
* \brief Gets the host name of the core's connected PipeWire instance
* \ingroup wpcore
* \param self the core
* \returns The name of the host where the PipeWire instance that
* \a self is connected to is running on
*/
const gchar *
wp_core_get_remote_host_name (WpCore * self)
{
g_return_val_if_fail (wp_core_is_connected (self), NULL);
return self->info->host_name;
}
/*!
* \brief Gets the version of the core's connected PipeWire instance
* \ingroup wpcore
* \param self the core
* \returns The version of the PipeWire instance that \a self is connected to
*/
const gchar *
wp_core_get_remote_version (WpCore * self)
{
g_return_val_if_fail (wp_core_is_connected (self), NULL);
return self->info->version;
}
/*!
* \brief Gets the properties of the core's connected PipeWire instance
* \ingroup wpcore
* \param self the core
* \returns (transfer full): the properties of the PipeWire instance that
* \a self is connected to
*/
WpProperties *
wp_core_get_remote_properties (WpCore * self)
{
g_return_val_if_fail (wp_core_is_connected (self), NULL);
return wp_properties_new_wrap_dict (self->info->props);
}
/*!
* \brief Gets the properties of the core
* \ingroup wpcore
* \param self the core
* \returns (transfer full): the properties of \a self
*/
WpProperties *
wp_core_get_properties (WpCore * self)
{
g_return_val_if_fail (WP_IS_CORE (self), NULL);
/* pw_core has all the properties of the pw_context,
plus our updates, passed in pw_context_connect() */
if (self->pw_core)
return wp_properties_new_wrap (pw_core_get_properties (self->pw_core));
/* if there is no connection yet, return the properties of the context */
else if (!self->properties)
return wp_properties_new_wrap (pw_context_get_properties (self->pw_context));
/* ... plus any further updates that we got from wp_core_update_properties() */
else {
/* we need to copy here in order to augment with the updates */
WpProperties *ret =
wp_properties_new_copy (pw_context_get_properties (self->pw_context));
wp_properties_update (ret, self->properties);
return ret;
}
}
/*!
* \brief Updates the properties of \a self on the connection, making them
* appear on the client object that represents this connection.
*
* If \a self is not connected yet, these properties are stored and passed to
* pw_context_connect() when connecting.
*
* \ingroup wpcore
* \param self the core
* \param updates (transfer full): updates to apply to the properties of
* \a self; this does not need to include properties that have not changed
*/
void
wp_core_update_properties (WpCore * self, WpProperties * updates)
{
g_autoptr (WpProperties) upd = updates;
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (updates != NULL);
/* store updates locally so that
- they persist after disconnection
- we can pass them to pw_context_connect */
if (!self->properties)
self->properties = wp_properties_new_empty ();
wp_properties_update (self->properties, upd);
if (self->pw_core)
pw_core_update_properties (self->pw_core, wp_properties_peek_dict (upd));
}
/*!
* \brief Adds an idle callback to be called in the same GMainContext as the
* one used by this core.
*
* This is essentially the same as g_idle_add_full(), but it adds the created
* GSource on the GMainContext used by this core instead of the default context.
*
* \ingroup wpcore
* \param self the core
* \param source (out) (optional): the source
* \param function (scope notified): the function to call
* \param data (closure): data to pass to \a function
* \param destroy (nullable): a function to destroy \a data
*/
void
wp_core_idle_add (WpCore * self, GSource **source, GSourceFunc function,
gpointer data, GDestroyNotify destroy)
{
g_autoptr (GSource) s = NULL;
g_return_if_fail (WP_IS_CORE (self));
s = g_idle_source_new ();
g_source_set_callback (s, function, data, destroy);
g_source_attach (s, self->g_main_context);
if (source)
*source = g_source_ref (s);
}
/*!
* \brief Adds an idle callback to be called in the same GMainContext as
* the one used by this core.
*
* This is the same as wp_core_idle_add(), but it allows you to specify a
* GClosure instead of a C callback.
*
* \ingroup wpcore
* \param self the core
* \param source (out) (optional): the source
* \param closure the closure to invoke
*/
void
wp_core_idle_add_closure (WpCore * self, GSource **source, GClosure * closure)
{
g_autoptr (GSource) s = NULL;
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (closure != NULL);
s = g_idle_source_new ();
g_source_set_closure (s, closure);
g_source_attach (s, self->g_main_context);
if (source)
*source = g_source_ref (s);
}
/*!
* \brief Adds a timeout callback to be called at regular intervals in the same
* GMainContext as the one used by this core.
*
* The function is called repeatedly until it returns FALSE, at which point
* the timeout is automatically destroyed and the function will not be called
* again. The first call to the function will be at the end of the first
* interval.
* This is essentially the same as g_timeout_add_full(), but it adds
* the created GSource on the GMainContext used by this core instead of the
* default context.
*
* \ingroup wpcore
* \param self the core
* \param source (out) (optional): the source
* \param timeout_ms the timeout in milliseconds
* \param function (scope notified): the function to call
* \param data (closure): data to pass to \a function
* \param destroy (nullable): a function to destroy \a data
*/
void
wp_core_timeout_add (WpCore * self, GSource **source, guint timeout_ms,
GSourceFunc function, gpointer data, GDestroyNotify destroy)
{
g_autoptr (GSource) s = NULL;
g_return_if_fail (WP_IS_CORE (self));
s = g_timeout_source_new (timeout_ms);
g_source_set_callback (s, function, data, destroy);
g_source_attach (s, self->g_main_context);
if (source)
*source = g_source_ref (s);
}
/*!
* \brief Adds a timeout callback to be called at regular intervals in the same
* GMainContext as the one used by this core.
*
* This is the same as wp_core_timeout_add(), but it allows you to specify a
* GClosure instead of a C callback.
*
* \ingroup wpcore
* \param self the core
* \param source (out) (optional): the source
* \param timeout_ms the timeout in milliseconds
* \param closure the closure to invoke
*/
void
wp_core_timeout_add_closure (WpCore * self, GSource **source, guint timeout_ms,
GClosure * closure)
{
g_autoptr (GSource) s = NULL;
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (closure != NULL);
s = g_timeout_source_new (timeout_ms);
g_source_set_closure (s, closure);
g_source_attach (s, self->g_main_context);
if (source)
*source = g_source_ref (s);
}
/*!
* \brief Asks the PipeWire server to call the \a callback via an event.
*
* Since methods are handled in-order and events are delivered
* in-order, this can be used as a barrier to ensure all previous
* methods and the resulting events have been handled.
*
* In both success and error cases, \a callback is always called.
* Use wp_core_sync_finish() from within the \a callback to determine whether
* the operation completed successfully or if an error occurred.
*
* \ingroup wpcore
* \param self the core
* \param cancellable (nullable): a GCancellable to cancel the operation
* \param callback (scope async): a function to call when the operation is done
* \param user_data (closure): data to pass to \a callback
* \returns TRUE if the sync operation was started, FALSE if an error
* occurred before returning from this function
*/
gboolean
wp_core_sync (WpCore * self, GCancellable * cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
return wp_core_sync_closure (self, cancellable,
g_cclosure_new (G_CALLBACK (callback), user_data, NULL));
}
static void
invoke_closure (GObject * obj, GAsyncResult * res, gpointer data)
{
GClosure *closure = data;
GValue values[2] = { G_VALUE_INIT, G_VALUE_INIT };
g_value_init (&values[0], G_TYPE_OBJECT);
g_value_init (&values[1], G_TYPE_OBJECT);
g_value_set_object (&values[0], obj);
g_value_set_object (&values[1], res);
g_closure_invoke (closure, NULL, 2, values, NULL);
g_value_unset (&values[0]);
g_value_unset (&values[1]);
g_closure_unref (closure);
}
/*!
* \brief Asks the PipeWire server to invoke the \a closure via an event.
*
* Since methods are handled in-order and events are delivered
* in-order, this can be used as a barrier to ensure all previous
* methods and the resulting events have been handled.
*
* In both success and error cases, \a closure is always invoked.
* Use wp_core_sync_finish() from within the \a closure to determine whether
* the operation completed successfully or if an error occurred.
*
* \ingroup wpcore
* \since 0.4.6
* \param self the core
* \param cancellable (nullable): a GCancellable to cancel the operation
* \param closure (transfer floating): a closure to invoke when the operation
* is done
* \returns TRUE if the sync operation was started, FALSE if an error
* occurred before returning from this function
*/
gboolean
wp_core_sync_closure (WpCore * self, GCancellable * cancellable,
GClosure * closure)
{
g_autoptr (GTask) task = NULL;
int seq;
g_return_val_if_fail (WP_IS_CORE (self), FALSE);
g_return_val_if_fail (closure, FALSE);
closure = g_closure_ref (closure);
g_closure_sink (closure);
if (G_CLOSURE_NEEDS_MARSHAL (closure))
g_closure_set_marshal (closure, g_cclosure_marshal_VOID__OBJECT);
task = g_task_new (self, cancellable, invoke_closure, closure);
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;
}
wp_debug_object (self, "sync, seq 0x%x, task " WP_OBJECT_FORMAT,
seq, WP_OBJECT_ARGS (task));
g_hash_table_insert (self->async_tasks, GINT_TO_POINTER (seq),
g_steal_pointer (&task));
return TRUE;
}
/*!
* \brief This function is meant to be called from within the callback of
* wp_core_sync() in order to determine the success or failure of the operation.
*
* \ingroup wpcore
* \param self the core
* \param res a GAsyncResult
* \param error (out) (optional): the error that occurred, if any
* \returns TRUE if the operation succeeded, FALSE otherwise
*/
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);
}
/*!
* \brief Finds a registered object
*
* \param self the core
* \param func (scope call): a function that takes the object being searched
* as the first argument and \a data as the second. it should return TRUE if
* the object is found or FALSE otherwise
* \param data the second argument to \a func
* \returns (transfer full) (type GObject*) (nullable): the registered object
* or NULL if not found
*/
gpointer
wp_core_find_object (WpCore * self, GEqualFunc func, gconstpointer data)
{
GObject *object;
guint i;
g_return_val_if_fail (WP_IS_CORE (self), NULL);
/* prevent bad things when called from within wp_registry_clear() */
if (G_UNLIKELY (!self->registry.objects))
return NULL;
for (i = 0; i < self->registry.objects->len; i++) {
object = g_ptr_array_index (self->registry.objects, i);
if (func (object, data))
return g_object_ref (object);
}
return NULL;
}
/*!
* \brief Registers \a obj with the core, making it appear on WpObjectManager
* instances as well.
*
* The core will also maintain a ref to that object until it
* is removed.
*
* \ingroup wpcore
* \param self the core
* \param obj (transfer full) (type GObject*): the object to register
*/
void
wp_core_register_object (WpCore * self, gpointer obj)
{
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (G_IS_OBJECT (obj));
/* prevent bad things when called from within wp_registry_clear() */
if (G_UNLIKELY (!self->registry.objects)) {
g_object_unref (obj);
return;
}
/* ensure the registered object is associated with this core */
if (WP_IS_OBJECT (obj)) {
g_autoptr (WpCore) obj_core = wp_object_get_core (WP_OBJECT (obj));
g_return_if_fail (obj_core == self);
}
g_ptr_array_add (self->registry.objects, obj);
/* notify object managers */
wp_registry_notify_add_object (&self->registry, obj);
}
/*!
* \brief Detaches and unrefs the specified object from this core.
*
* \ingroup wpcore
* \param self the core
* \param obj (transfer none) (type GObject*): a pointer to the object to remove
*/
void
wp_core_remove_object (WpCore * self, gpointer obj)
{
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (G_IS_OBJECT (obj));
/* prevent bad things when called from within wp_registry_clear() */
if (G_UNLIKELY (!self->registry.objects))
return;
/* notify object managers */
wp_registry_notify_rm_object (&self->registry, obj);
g_ptr_array_remove_fast (self->registry.objects, obj);
}
/*!
* \brief Test if a global feature is provided
*
* \ingroup wpcore
* \param self the core
* \param feature the feature name
* \returns TRUE if the feature is provided, FALSE otherwise
*/
gboolean
wp_core_test_feature (WpCore * self, const gchar * feature)
{
return g_ptr_array_find_with_equal_func (self->registry.features, feature,
g_str_equal, NULL);
}
WpRegistry *
wp_core_get_registry (WpCore * self)
{
return &self->registry;
}
WpCore *
wp_registry_get_core (WpRegistry * self)
{
return SPA_CONTAINER_OF (self, WpCore, registry);
}