lib: introduce WpObjectManager

* rework how global objects are stored in the core
* rework how users get notified about global objects
  and proxies of remote global objects

The purpose of this change is to have a class that can manage
objects that are registered in the core or signalled through the
registry. This object can declare interest on certain types
of global objects and only keep & signal those objects that it is
interested in. Additionally, it can prepare proxy features and
asynchronously deliver an 'objects-changed' signal, which is
basically telling us that the list of objects has changed.

This is useful to simplify port proxies management in WpAudioStream.
Now the stream object can declare that it is interested in ports
that have "node.id" == X and the object manager will only maintain
a list of those. Additionally, it will emit the 'objects-changed'
signal when the list of ports is complete, so there is no reason to
do complex operations and core syncs in the WpAudioStream class
in order to figure out when the list of ports is ready.

As a side effect, this also reduces resource management. Now we
don't construct a WpProxy for every global that pipewire reports;
we only construct proxies when there is interest in them!

Another interesting side effect is that we can now register an
object manager at any point in time and get immediately notified
about remote globals that already exist. i.e. when you register
an object manager that is interested in nodes, it will be immediately
notified about all the existing nodes in the graph. This is useful
to avoid race conditions between connecting the signal and objects
beting created in pipewire
This commit is contained in:
George Kiagiadakis 2019-11-13 15:44:23 +02:00
parent a93cbdf8f0
commit e7e5c66853
19 changed files with 520 additions and 488 deletions

View file

@ -7,12 +7,14 @@
*/
#include "core.h"
#include "object-manager.h"
#include "proxy.h"
#include "wpenums.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
@ -94,19 +96,9 @@ struct _WpCore
struct pw_registry_proxy *registry_proxy;
struct spa_hook registry_listener;
/* local proxies */
GHashTable *proxies;
GHashTable *default_features;
/* local global objects */
GPtrArray *global_objects;
};
struct global_object
{
GQuark key;
gpointer object;
GDestroyNotify destroy;
GPtrArray *globals; // elementy-type: WpGlobal*
GPtrArray *objects; // element-type: GObject*
GPtrArray *object_managers; // element-type: WpObjectManager*
};
enum {
@ -120,10 +112,6 @@ enum {
enum {
SIGNAL_REMOTE_STATE_CHANGED,
SIGNAL_GLOBAL_ADDED,
SIGNAL_GLOBAL_REMOVED,
SIGNAL_REMOTE_GLOBAL_ADDED,
SIGNAL_REMOTE_GLOBAL_REMOVED,
NUM_SIGNALS
};
@ -131,61 +119,59 @@ static guint32 signals[NUM_SIGNALS];
G_DEFINE_TYPE (WpCore, wp_core, G_TYPE_OBJECT)
static void
on_proxy_ready (GObject * obj, GAsyncResult * res, gpointer data)
{
WpCore *self = WP_CORE (data);
WpProxy *proxy = WP_PROXY (obj);
g_autoptr (GError) error = NULL;
if (!wp_proxy_augment_finish (proxy, res, &error)) {
g_warning ("Failed to augment WpProxy (%p): %s", obj, error->message);
return;
}
g_signal_emit (self, signals[SIGNAL_REMOTE_GLOBAL_ADDED],
wp_proxy_get_interface_quark (proxy), proxy);
}
static void
registry_global (void *data, uint32_t id, uint32_t permissions,
uint32_t type, uint32_t version, const struct spa_dict *props)
{
WpCore *self = WP_CORE (data);
WpProxy *proxy;
WpProxyFeatures features;
g_autoptr (WpProperties) properties = wp_properties_new_copy_dict (props);
WpGlobal *global = NULL;
guint i;
g_return_if_fail (!g_hash_table_contains (self->proxies, GUINT_TO_POINTER (id)));
g_return_if_fail (self->globals->len <= id ||
g_ptr_array_index (self->globals, id) == NULL);
/* construct & store WpProxy */
proxy = wp_proxy_new_global (self, id, permissions, properties,
type, version);
g_hash_table_insert (self->proxies, GUINT_TO_POINTER (id), proxy);
g_debug ("registry global:%u perm:0x%x type:%u/%u (%s)",
id, permissions, type, version,
spa_debug_type_find_name (pw_type_info (), type));
g_debug ("registry global:%u perm:0x%x type:%u/%u -> %s:%p",
id, permissions, type, version, G_OBJECT_TYPE_NAME (proxy), proxy);
/* construct & store the global */
global = wp_global_new ();
global->id = id;
global->type = type;
global->version = version;
global->permissions = permissions;
global->properties = wp_properties_new_copy_dict (props);
/* augment */
features = GPOINTER_TO_UINT (g_hash_table_lookup (self->default_features,
GUINT_TO_POINTER (G_TYPE_FROM_INSTANCE (proxy))));
wp_proxy_augment (proxy, features, NULL, on_proxy_ready, self);
if (self->globals->len <= id)
g_ptr_array_set_size (self->globals, id + 1);
g_ptr_array_index (self->globals, id) = global;
/* notify object managers */
for (i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_add_global (om, global);
}
}
static void
registry_global_remove (void *data, uint32_t id)
{
WpCore *self = WP_CORE (data);
g_autoptr (WpProxy) proxy = NULL;
g_autoptr (WpGlobal) global = NULL;
guint i;
g_hash_table_steal_extended (self->proxies, GUINT_TO_POINTER (id), NULL,
(gpointer *) &proxy);
global = g_steal_pointer (&g_ptr_array_index (self->globals, id));
g_debug ("registry global removed: %u (%p)", id, proxy);
g_debug ("registry global removed:%u type:%u/%u (%s)", id,
global->type, global->version,
spa_debug_type_find_name (pw_type_info (), global->type));
if (proxy)
g_signal_emit (data, signals[SIGNAL_REMOTE_GLOBAL_REMOVED],
wp_proxy_get_interface_quark (proxy), proxy);
/* notify object managers */
for (i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_rm_global (om, id);
}
}
static const struct pw_registry_proxy_events registry_proxy_events = {
@ -232,25 +218,21 @@ static const struct pw_remote_events remote_events = {
.state_changed = on_remote_state_changed,
};
static void
free_global_object (gpointer p)
/* wrapper around wp_global_unref because
the WpGlobal pointers in self->globals can be NULL */
static inline void
free_global (WpGlobal * g)
{
struct global_object *g = p;
/* Destroy the object */
if (g->destroy)
g->destroy(g->object);
g_slice_free (struct global_object, p);
if (g)
wp_global_unref (g);
}
static void
wp_core_init (WpCore * self)
{
self->proxies = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
g_object_unref);
self->default_features = g_hash_table_new (g_direct_hash, g_direct_equal);
self->global_objects = g_ptr_array_new_with_free_func (free_global_object);
self->globals = g_ptr_array_new_with_free_func ((GDestroyNotify) free_global);
self->objects = g_ptr_array_new_with_free_func (g_object_unref);
self->object_managers = g_ptr_array_new ();
}
static void
@ -274,22 +256,62 @@ wp_core_constructed (GObject *object)
G_OBJECT_CLASS (wp_core_parent_class)->constructed (object);
}
static void object_manager_destroyed (gpointer data, GObject * om);
static void
wp_core_dispose (GObject * obj)
{
WpCore *self = WP_CORE (obj);
g_autoptr (GPtrArray) global_objects;
struct global_object *global;
global_objects = g_steal_pointer (&self->global_objects);
/* remove pipewire globals */
{
g_autoptr (GPtrArray) objlist = g_steal_pointer (&self->globals);
/* Remove and emit the removed signal for all globals */
while (global_objects->len > 0) {
global = g_ptr_array_steal_index_fast (global_objects,
global_objects->len - 1);
g_signal_emit (self, signals[SIGNAL_GLOBAL_REMOVED], global->key,
global->key, global->object);
free_global_object (global);
while (objlist->len > 0) {
guint i;
g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist,
objlist->len - 1);
if (!global)
continue;
for (i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_rm_global (om, global->id);
}
}
}
/* remove all the registered objects
this will normally also destroy the object managers, eventually, since
they are normally ref'ed by modules, which are registered objects */
{
g_autoptr (GPtrArray) objlist = g_steal_pointer (&self->objects);
while (objlist->len > 0) {
guint i;
g_autoptr (GObject) object = g_ptr_array_steal_index_fast (objlist,
objlist->len - 1);
for (i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_rm_object (om, object);
}
}
}
/* in case there are any object managers left,
remove the weak ref on them and let them be... */
{
g_autoptr (GPtrArray) object_mgrs;
GObject *om;
object_mgrs = g_steal_pointer (&self->object_managers);
while (object_mgrs->len > 0) {
om = g_ptr_array_steal_index_fast (object_mgrs, object_mgrs->len - 1);
g_object_weak_unref (om, object_manager_destroyed, self);
}
}
G_OBJECT_CLASS (wp_core_parent_class)->dispose (obj);
@ -300,8 +322,6 @@ wp_core_finalize (GObject * obj)
{
WpCore *self = WP_CORE (obj);
g_clear_pointer (&self->proxies, g_hash_table_unref);
g_clear_pointer (&self->default_features, g_hash_table_unref);
g_clear_pointer (&self->pw_remote, pw_remote_destroy);
self->core_proxy= NULL;
self->registry_proxy = NULL;
@ -402,22 +422,6 @@ wp_core_class_init (WpCoreClass * klass)
signals[SIGNAL_REMOTE_STATE_CHANGED] = g_signal_new ("remote-state-changed",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_REMOTE_STATE);
signals[SIGNAL_GLOBAL_ADDED] = g_signal_new ("global-added",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST, 0, NULL,
NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER);
signals[SIGNAL_GLOBAL_REMOVED] = g_signal_new ("global-removed",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST, 0, NULL,
NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER);
signals[SIGNAL_REMOTE_GLOBAL_ADDED] = g_signal_new ("remote-global-added",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_PROXY);
signals[SIGNAL_REMOTE_GLOBAL_REMOVED] = g_signal_new ("remote-global-removed",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_PROXY);
}
WpCore *
@ -485,16 +489,6 @@ wp_core_get_remote_state (WpCore * self, const gchar ** error)
return (WpRemoteState) pw_remote_get_state (self->pw_remote, error);
}
void
wp_core_set_default_proxy_features (WpCore * self,
GType proxy_type, WpProxyFeatures features)
{
g_return_if_fail (WP_IS_CORE (self));
g_hash_table_insert (self->default_features, GUINT_TO_POINTER (proxy_type),
GUINT_TO_POINTER (features));
}
WpProxy *
wp_core_create_remote_object (WpCore *self,
const gchar *factory_name, guint32 interface_type,
@ -527,130 +521,137 @@ wp_core_get_pw_registry_proxy (WpCore * self)
}
/**
* wp_core_get_global: (method)
* wp_core_find_object: (skip)
* @self: the core
* @key: the key of the global
* @func: (scope call): a function that takes the object being searched
* as the first argument and @data as the second. it should return TRUE if
* the object is found or FALSE otherwise
* @data: the second argument to @func
*
* Returns: (type GObject*) (nullable) (transfer none): the global object
* associated with @key; if multiple globals with the same key exist, the
* first one found is returned
* Finds a registered object
*
* Returns: (transfer full) (type GObject *) (nullable): the registered object
* or NULL if not found
*/
gpointer
wp_core_get_global (WpCore * self, GQuark key)
wp_core_find_object (WpCore * self, GEqualFunc func, gconstpointer data)
{
gint i;
struct global_object *global;
GObject *object;
guint i;
g_return_val_if_fail (WP_IS_CORE (self), NULL);
if (G_UNLIKELY (!self->global_objects))
/* prevent bad things when called from within _dispose() */
if (G_UNLIKELY (!self->objects))
return NULL;
for (i = 0; i < self->global_objects->len; i++) {
global = g_ptr_array_index (self->global_objects, i);
if (global->key == key)
return global->object;
for (i = 0; i < self->objects->len; i++) {
object = g_ptr_array_index (self->objects, i);
if (func (object, data))
return g_object_ref (object);
}
return NULL;
}
/**
* wp_core_foreach_global: (method)
* wp_core_register_object: (skip)
* @self: the core
* @callback: (scope call): the function to call for each global object
* @user_data: data to passs to @callback
* @obj: (transfer full) (type GObject*): the object to register
*
* Calls @callback for every global object registered
* Registers @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.
*/
void
wp_core_foreach_global (WpCore * self, WpCoreForeachGlobalFunc callback,
gpointer user_data)
wp_core_register_object (WpCore * self, gpointer obj)
{
gint i;
struct global_object *global;
guint i;
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (G_IS_OBJECT (obj));
if (G_UNLIKELY (!self->global_objects))
/* prevent bad things when called from within _dispose() */
if (G_UNLIKELY (!self->objects)) {
g_object_unref (obj);
return;
}
for (i = 0; i < self->global_objects->len; i++) {
global = g_ptr_array_index (self->global_objects, i);
if (!callback (global->key, global->object, user_data))
break;
g_ptr_array_add (self->objects, obj);
/* notify object managers */
for (i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_add_object (om, obj);
}
}
/**
* wp_core_register_global: (method)
* wp_core_remove_object: (skip)
* @self: the core
* @key: the key for this global
* @obj: (transfer full): the global object to attach
* @destroy_obj: the destroy function for @obj
* @obj: (transfer none) (type GObject*): a pointer to the object to remove
*
* Registers @obj as a global object associated with @key
* Detaches and unrefs the specified object from this core
*/
void
wp_core_register_global (WpCore * self, GQuark key, gpointer obj,
GDestroyNotify destroy_obj)
wp_core_remove_object (WpCore * self, gpointer obj)
{
struct global_object *global;
guint i;
g_return_if_fail (WP_IS_CORE(self));
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (G_IS_OBJECT (obj));
if (G_UNLIKELY (!self->global_objects)) {
if (destroy_obj)
destroy_obj (obj);
/* prevent bad things when called from within _dispose() */
if (G_UNLIKELY (!self->objects))
return;
/* notify object managers */
for (i = 0; i < self->object_managers->len; i++) {
WpObjectManager *om = g_ptr_array_index (self->object_managers, i);
wp_object_manager_rm_object (om, obj);
}
global = g_slice_new0 (struct global_object);
global->key = key;
global->object = obj;
global->destroy = destroy_obj;
g_ptr_array_add (self->global_objects, global);
g_ptr_array_remove_fast (self->objects, obj);
}
g_signal_emit (self, signals[SIGNAL_GLOBAL_ADDED], key, key, obj);
static void
object_manager_destroyed (gpointer data, GObject * om)
{
WpCore *self = WP_CORE (data);
g_ptr_array_remove_fast (self->object_managers, om);
}
/**
* wp_core_remove_global: (method)
* wp_core_install_object_manager: (method)
* @self: the core
* @key: the key for this global
* @obj: (nullable): a pointer to the global object to match, if there are
* multiple ones with the same key
* @om: (transfer none): a #WpObjectManager
*
* Detaches and unrefs the specified global from this core
* Installs the object manager on this core, activating its internal management
* engine. This will immediately emit signals about objects added on @om
* if objects that the @om is interested in were in existence already.
*/
void
wp_core_remove_global (WpCore * self, GQuark key, gpointer obj)
wp_core_install_object_manager (WpCore * self, WpObjectManager * om)
{
gint i;
struct global_object *global;
guint i;
g_return_if_fail (WP_IS_CORE (self));
g_return_if_fail (WP_IS_OBJECT_MANAGER (om));
if (G_UNLIKELY (!self->global_objects))
return;
g_object_weak_ref (G_OBJECT (om), object_manager_destroyed, self);
g_ptr_array_add (self->object_managers, om);
g_object_set (om, "core", self, NULL);
for (i = 0; i < self->global_objects->len; i++) {
global = g_ptr_array_index (self->global_objects, i);
if (global->key == key && (!obj || global->object == obj))
break;
/* add pre-existing objects to the object manager,
in case it's interested in them */
for (i = 0; i < self->globals->len; i++) {
WpGlobal *g = g_ptr_array_index (self->globals, i);
/* check if null because the globals array can have gaps */
if (g)
wp_object_manager_add_global (om, g);
}
if (i < self->global_objects->len) {
global = g_ptr_array_steal_index_fast (self->global_objects, i);
g_signal_emit (self, signals[SIGNAL_GLOBAL_REMOVED], key,
key, global->object);
free_global_object (global);
for (i = 0; i < self->objects->len; i++) {
GObject *o = g_ptr_array_index (self->objects, i);
wp_object_manager_add_object (om, o);
}
}
G_DEFINE_QUARK (endpoint, wp_global_endpoint)
G_DEFINE_QUARK (factory, wp_global_factory)
G_DEFINE_QUARK (module, wp_global_module)
G_DEFINE_QUARK (policy-manager, wp_global_policy_manager)

View file

@ -10,6 +10,7 @@
#define __WIREPLUMBER_CORE_H__
#include <glib-object.h>
#include "object-manager.h"
#include "proxy.h"
G_BEGIN_DECLS
@ -45,13 +46,12 @@ struct pw_remote * wp_core_get_pw_remote (WpCore * self);
gboolean wp_core_connect (WpCore * self);
WpRemoteState wp_core_get_remote_state (WpCore * self, const gchar ** error);
void wp_core_set_default_proxy_features (
WpCore * self, GType proxy_type, WpProxyFeatures features);
WpProxy * wp_core_create_remote_object (WpCore * self,
const gchar * factory_name, guint32 interface_type,
guint32 interface_version, WpProperties * properties);
void wp_core_install_object_manager (WpCore * self, WpObjectManager * om);
G_END_DECLS
#endif

View file

@ -356,8 +356,7 @@ wp_endpoint_register (WpEndpoint * self)
g_info ("WpEndpoint:%p registering '%s' (%s)", self, priv->name,
priv->media_class);
wp_core_register_global (core, WP_GLOBAL_ENDPOINT, g_object_ref (self),
g_object_unref);
wp_core_register_object (core, g_object_ref (self));
}
/**
@ -387,82 +386,10 @@ wp_endpoint_unregister (WpEndpoint * self)
g_info ("WpEndpoint:%p unregistering '%s' (%s)", self, priv->name,
priv->media_class);
wp_core_remove_global (core, WP_GLOBAL_ENDPOINT, self);
wp_core_remove_object (core, self);
}
}
struct endpoints_foreach_data
{
GPtrArray *result;
const gchar *lookup;
};
static inline gboolean
media_class_matches (const gchar * media_class, const gchar * lookup)
{
const gchar *c1 = media_class, *c2 = lookup;
/* empty lookup matches all classes */
if (!lookup)
return TRUE;
/* compare until we reach the end of the lookup string */
for (; *c2 != '\0'; c1++, c2++) {
if (*c1 != *c2)
return FALSE;
}
/* the lookup may not end in a slash, however it must match up
* to the end of a submedia_class. i.e.:
* match: media_class: Audio/Source/Virtual
* lookup: Audio/Source
*
* NO match: media_class: Audio/Source/Virtual
* lookup: Audio/Sou
*
* if *c1 is not /, also check the previous char, because the lookup
* may actually end in a slash:
*
* match: media_class: Audio/Source/Virtual
* lookup: Audio/Source/
*/
if (!(*c1 == '/' || *c1 == '\0' || *(c1 - 1) == '/'))
return FALSE;
return TRUE;
}
static gboolean
find_endpoints (GQuark key, gpointer global, gpointer user_data)
{
struct endpoints_foreach_data * data = user_data;
if (key == WP_GLOBAL_ENDPOINT &&
media_class_matches (wp_endpoint_get_media_class (WP_ENDPOINT (global)),
data->lookup))
g_ptr_array_add (data->result, g_object_ref (global));
return WP_CORE_FOREACH_GLOBAL_CONTINUE;
}
/**
* wp_endpoint_find:
* @core: the core
* @media_class_lookup: the media class lookup string
*
* Returns: (element-type WpEndpoint) (transfer full): an array with all the
* endpoints matching the media class lookup string
*/
GPtrArray *
wp_endpoint_find (WpCore * core, const gchar * media_class_lookup)
{
struct endpoints_foreach_data data;
data.result = g_ptr_array_new_with_free_func (g_object_unref);
data.lookup = media_class_lookup;
wp_core_foreach_global (core, find_endpoints, &data);
return data.result;
}
/**
* wp_endpoint_get_core:
* @self: the endpoint

View file

@ -43,7 +43,6 @@ WpEndpoint * wp_endpoint_new_finish (GObject *initable, GAsyncResult *res,
GError **error);
void wp_endpoint_register (WpEndpoint * self);
void wp_endpoint_unregister (WpEndpoint * self);
GPtrArray * wp_endpoint_find (WpCore * core, const gchar * media_class_lookup);
WpCore *wp_endpoint_get_core (WpEndpoint * self);
const gchar * wp_endpoint_get_name (WpEndpoint * self);

View file

@ -52,7 +52,9 @@ wp_factory_class_init (WpFactoryClass * klass)
* @name: the name of the factory
* @func: the create object callback
*
* Returns: (transfer full): the newly created factory
* Returns: (transfer none): the newly created factory. No reference
* is passed to the caller, since the reference is held by the core.
* The caller is free to ignore the return value
*/
WpFactory *
wp_factory_new (WpCore * core, const gchar * name, WpFactoryFunc func)
@ -70,7 +72,7 @@ wp_factory_new (WpCore * core, const gchar * name, WpFactoryFunc func)
g_info ("WpFactory:%p new factory: %s", f, name);
wp_core_register_global (core, WP_GLOBAL_FACTORY, f, g_object_unref);
wp_core_register_object (core, f);
return f;
}
@ -103,23 +105,11 @@ wp_factory_create_object (WpFactory * self, GType type,
self->create_object (self, type, properties, ready, user_data);
}
struct find_factory_data
{
GQuark name_quark;
WpFactory *ret;
};
static gboolean
find_factory_func (GQuark key, gpointer global, gpointer user_data)
find_factory_func (gpointer factory, gpointer name_quark)
{
struct find_factory_data *d = user_data;
if (key != WP_GLOBAL_FACTORY ||
WP_FACTORY (global)->name_quark != d->name_quark)
return WP_CORE_FOREACH_GLOBAL_CONTINUE;
d->ret = WP_FACTORY (global);
return WP_CORE_FOREACH_GLOBAL_DONE;
return WP_IS_FACTORY (factory) &&
WP_FACTORY (factory)->name_quark == GPOINTER_TO_UINT (name_quark);
}
/**
@ -127,21 +117,23 @@ find_factory_func (GQuark key, gpointer global, gpointer user_data)
* @core: the core
* @name: the lookup name
*
* Returns: (transfer none): the factory matching the lookup name
* Returns: (transfer full): the factory matching the lookup name
*/
WpFactory *
wp_factory_find (WpCore * core, const gchar * name)
{
struct find_factory_data d = { g_quark_from_string (name), NULL };
wp_core_foreach_global (core, find_factory_func, &d);
return d.ret;
GObject *f;
GQuark q = g_quark_from_string (name);
f = wp_core_find_object (core, (GEqualFunc) find_factory_func,
GUINT_TO_POINTER (q));
return f ? WP_FACTORY (f) : NULL;
}
void
wp_factory_make (WpCore * core, const gchar * name, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
{
WpFactory *f = wp_factory_find (core, name);
g_autoptr (WpFactory) f = wp_factory_find (core, name);
if (!f) return;
wp_factory_create_object (f, type, properties, ready, user_data);
}

View file

@ -5,6 +5,7 @@ wp_lib_sources = [
'factory.c',
'module.c',
'monitor.c',
'object-manager.c',
'policy.c',
'properties.c',
'proxy.c',
@ -21,6 +22,7 @@ wp_lib_headers = [
'factory.h',
'module.h',
'monitor.h',
'object-manager.h',
'policy.h',
'properties.h',
'proxy.h',

View file

@ -117,7 +117,7 @@ wp_module_load_c (WpModule * self, WpCore * core,
* @args: the args passed to the module
* @error: return location for errors, or NULL to ignore
*
* Returns: (transfer full): the loaded module
* Returns: (transfer none): the loaded module
*/
WpModule *
wp_module_load (WpCore * core, const gchar * abi, const gchar * module_name,
@ -139,8 +139,7 @@ wp_module_load (WpCore * core, const gchar * abi, const gchar * module_name,
return NULL;
}
wp_core_register_global (core, WP_GLOBAL_MODULE, g_object_ref (module),
g_object_unref);
wp_core_register_object (core, g_object_ref (module));
return module;
}

View file

@ -15,6 +15,7 @@ struct _WpPolicyManager
{
GObject parent;
GList *policies;
WpObjectManager *endpoints_om;
};
enum {
@ -29,6 +30,7 @@ G_DEFINE_TYPE (WpPolicyManager, wp_policy_manager, G_TYPE_OBJECT)
static void
wp_policy_manager_init (WpPolicyManager *self)
{
self->endpoints_om = wp_object_manager_new ();
}
static void
@ -38,6 +40,7 @@ wp_policy_manager_finalize (GObject *object)
g_debug ("WpPolicyManager destroyed");
g_clear_object (&self->endpoints_om);
g_list_free_full (self->policies, g_object_unref);
G_OBJECT_CLASS (wp_policy_manager_parent_class)->finalize (object);
@ -56,7 +59,7 @@ wp_policy_manager_class_init (WpPolicyManagerClass *klass)
}
static void
policy_mgr_endpoint_added (WpCore *core, GQuark key, WpEndpoint *ep,
policy_mgr_endpoint_added (WpObjectManager *om, WpEndpoint *ep,
WpPolicyManager *self)
{
GList *l;
@ -75,7 +78,7 @@ policy_mgr_endpoint_added (WpCore *core, GQuark key, WpEndpoint *ep,
}
static void
policy_mgr_endpoint_removed (WpCore *core, GQuark key, WpEndpoint *ep,
policy_mgr_endpoint_removed (WpObjectManager *om, WpEndpoint *ep,
WpPolicyManager *self)
{
GList *l;
@ -103,20 +106,85 @@ wp_policy_manager_get_instance (WpCore *core)
g_return_val_if_fail (WP_IS_CORE (core), NULL);
mgr = wp_core_get_global (core, WP_GLOBAL_POLICY_MANAGER);
mgr = wp_core_find_object (core, (GEqualFunc) WP_IS_POLICY_MANAGER,
NULL);
if (G_UNLIKELY (!mgr)) {
mgr = g_object_new (WP_TYPE_POLICY_MANAGER, NULL);
g_signal_connect_object (core, "global-added::endpoint",
/* install the object manager to listen to added/removed endpoints */
wp_object_manager_add_object_interest (mgr->endpoints_om,
WP_TYPE_ENDPOINT, NULL);
g_signal_connect_object (mgr->endpoints_om, "object-added",
(GCallback) policy_mgr_endpoint_added, mgr, 0);
g_signal_connect_object (core, "global-removed::endpoint",
g_signal_connect_object (mgr->endpoints_om, "object-removed",
(GCallback) policy_mgr_endpoint_removed, mgr, 0);
wp_core_install_object_manager (core, mgr->endpoints_om);
wp_core_register_global (core, WP_GLOBAL_POLICY_MANAGER, mgr,
g_object_unref);
wp_core_register_object (core, g_object_ref (mgr));
}
return g_object_ref (mgr);
return mgr;
}
static inline gboolean
media_class_matches (const gchar * media_class, const gchar * lookup)
{
const gchar *c1 = media_class, *c2 = lookup;
/* empty lookup matches all classes */
if (!lookup)
return TRUE;
/* compare until we reach the end of the lookup string */
for (; *c2 != '\0'; c1++, c2++) {
if (*c1 != *c2)
return FALSE;
}
/* the lookup may not end in a slash, however it must match up
* to the end of a submedia_class. i.e.:
* match: media_class: Audio/Source/Virtual
* lookup: Audio/Source
*
* NO match: media_class: Audio/Source/Virtual
* lookup: Audio/Sou
*
* if *c1 is not /, also check the previous char, because the lookup
* may actually end in a slash:
*
* match: media_class: Audio/Source/Virtual
* lookup: Audio/Source/
*/
if (!(*c1 == '/' || *c1 == '\0' || *(c1 - 1) == '/'))
return FALSE;
return TRUE;
}
/**
* wp_policy_manager_list_endpoints:
* @self: the policy manager
* @media_class: the media class lookup string
*
* Returns: (transfer full) (element-type WpEndpoint*): an array with all the
* endpoints matching the media class lookup string
*/
GPtrArray *
wp_policy_manager_list_endpoints (WpPolicyManager * self,
const gchar * media_class)
{
GPtrArray * ret;
guint i;
g_return_val_if_fail (WP_IS_POLICY_MANAGER (self), NULL);
ret = wp_object_manager_get_objects (self->endpoints_om, 0);
for (i = ret->len; i > 0; i--) {
WpEndpoint *ep = g_ptr_array_index (ret, i-1);
if (!media_class_matches (wp_endpoint_get_media_class (ep), media_class))
g_ptr_array_remove_index_fast (ret, i-1);
}
return ret;
}
/* WpPolicy */
@ -309,7 +377,7 @@ wp_policy_register (WpPolicy *self, WpCore *core)
void
wp_policy_unregister (WpPolicy *self)
{
WpPolicyManager *mgr;
g_autoptr (WpPolicyManager) mgr = NULL;
WpPolicyPrivate *priv;
g_return_if_fail (WP_IS_POLICY (self));
@ -317,7 +385,8 @@ wp_policy_unregister (WpPolicy *self)
priv = wp_policy_get_instance_private (self);
if (priv->core) {
mgr = wp_core_get_global (priv->core, WP_GLOBAL_POLICY_MANAGER);
mgr = wp_core_find_object (priv->core, (GEqualFunc) WP_IS_POLICY_MANAGER,
NULL);
if (G_UNLIKELY (!mgr)) {
g_critical ("WpPolicy:%p seems registered, but the policy manager "
"is absent", self);
@ -333,14 +402,15 @@ wp_policy_unregister (WpPolicy *self)
void
wp_policy_notify_changed (WpPolicy *self)
{
WpPolicyManager *mgr;
g_autoptr (WpPolicyManager) mgr = NULL;
WpPolicyPrivate *priv;
g_return_if_fail (WP_IS_POLICY (self));
priv = wp_policy_get_instance_private (self);
if (priv->core) {
mgr = wp_core_get_global (priv->core, WP_GLOBAL_POLICY_MANAGER);
mgr = wp_core_find_object (priv->core, (GEqualFunc) WP_IS_POLICY_MANAGER,
NULL);
if (G_UNLIKELY (!mgr)) {
g_critical ("WpPolicy:%p seems registered, but the policy manager "
"is absent", self);
@ -366,7 +436,7 @@ WpEndpoint *
wp_policy_find_endpoint (WpCore *core, GVariant *props,
guint32 *stream_id)
{
WpPolicyManager *mgr;
g_autoptr (WpPolicyManager) mgr = NULL;
GList *l;
WpPolicy *p;
WpEndpoint * ret;
@ -375,7 +445,8 @@ wp_policy_find_endpoint (WpCore *core, GVariant *props,
g_return_val_if_fail (g_variant_is_of_type (props, G_VARIANT_TYPE_VARDICT), NULL);
g_return_val_if_fail (stream_id != NULL, NULL);
mgr = wp_core_get_global (core, WP_GLOBAL_POLICY_MANAGER);
mgr = wp_core_find_object (core,
(GEqualFunc) WP_IS_POLICY_MANAGER, NULL);
if (mgr) {
for (l = g_list_first (mgr->policies); l; l = g_list_next (l)) {
p = WP_POLICY (l->data);

View file

@ -53,6 +53,8 @@ struct _WpPolicyClass
};
WpPolicyManager * wp_policy_manager_get_instance (WpCore *core);
GPtrArray * wp_policy_manager_list_endpoints (WpPolicyManager * self,
const gchar * media_class);
guint32 wp_policy_get_rank (WpPolicy *self);
WpCore *wp_policy_get_core (WpPolicy *self);

View file

@ -9,6 +9,10 @@
#ifndef __WIREPLUMBER_PRIVATE_H__
#define __WIREPLUMBER_PRIVATE_H__
#include "core.h"
#include "object-manager.h"
#include "proxy.h"
G_BEGIN_DECLS
/* core */
@ -19,36 +23,65 @@ struct pw_registry_proxy;
struct pw_core_proxy * wp_core_get_pw_core_proxy (WpCore * self);
struct pw_registry_proxy * wp_core_get_pw_registry_proxy (WpCore * self);
enum {
WP_CORE_FOREACH_GLOBAL_DONE = FALSE,
WP_CORE_FOREACH_GLOBAL_CONTINUE = TRUE,
gpointer wp_core_find_object (WpCore * self, GEqualFunc func,
gconstpointer data);
void wp_core_register_object (WpCore * self, gpointer obj);
void wp_core_remove_object (WpCore * self, gpointer obj);
/* global */
typedef struct _WpGlobal WpGlobal;
struct _WpGlobal
{
guint32 id;
guint32 type;
guint32 version;
guint32 permissions;
WpProperties *properties;
GWeakRef proxy;
};
typedef gboolean (*WpCoreForeachGlobalFunc) (GQuark key, gpointer global,
gpointer user_data);
static inline WpGlobal *
wp_global_new (void)
{
WpGlobal *self = g_rc_box_new0 (WpGlobal);
g_weak_ref_init (&self->proxy, NULL);
return self;
}
gpointer wp_core_get_global (WpCore * self, GQuark key);
void wp_core_foreach_global (WpCore * self, WpCoreForeachGlobalFunc callback,
gpointer user_data);
static inline void
wp_global_clear (WpGlobal * self)
{
g_clear_pointer (&self->properties, wp_properties_unref);
g_weak_ref_clear (&self->proxy);
}
void wp_core_register_global (WpCore * self, GQuark key, gpointer obj,
GDestroyNotify destroy_obj);
void wp_core_remove_global (WpCore * self, GQuark key, gpointer obj);
static inline WpGlobal *
wp_global_ref (WpGlobal * self)
{
return g_rc_box_acquire (self);
}
#define WP_GLOBAL_ENDPOINT (wp_global_endpoint_quark ())
GQuark wp_global_endpoint_quark (void);
static inline void
wp_global_unref (WpGlobal * self)
{
g_rc_box_release_full (self, (GDestroyNotify) wp_global_clear);
}
#define WP_GLOBAL_FACTORY (wp_global_factory_quark ())
GQuark wp_global_factory_quark (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpGlobal, wp_global_unref)
#define WP_GLOBAL_MODULE (wp_global_module_quark ())
GQuark wp_global_module_quark (void);
/* object manager */
#define WP_GLOBAL_POLICY_MANAGER (wp_global_policy_manager_quark ())
GQuark wp_global_policy_manager_quark (void);
void wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global);
void wp_object_manager_rm_global (WpObjectManager * self, guint32 id);
void wp_object_manager_add_object (WpObjectManager * self, GObject * object);
void wp_object_manager_rm_object (WpObjectManager * self, GObject * object);
/* proxy */
WpProxy * wp_proxy_new_global (WpCore * core, WpGlobal * global);
void wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature);
void wp_proxy_augment_error (WpProxy * self, GError * error);

View file

@ -27,9 +27,7 @@ struct _WpProxyPrivate
/* properties */
GWeakRef core;
guint32 global_id;
guint32 global_perm;
WpProperties *global_props;
WpGlobal *global;
guint32 iface_type;
guint32 iface_version;
@ -49,6 +47,7 @@ struct _WpProxyPrivate
enum {
PROP_0,
PROP_CORE,
PROP_GLOBAL,
PROP_GLOBAL_ID,
PROP_GLOBAL_PERMISSIONS,
PROP_GLOBAL_PROPERTIES,
@ -69,6 +68,7 @@ enum
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)
G_DEFINE_QUARK (core, wp_proxy_core)
@ -137,7 +137,11 @@ proxy_event_destroy (void *data)
GHashTableIter iter;
GTask *task;
g_debug ("destroyed pw proxy %p for global %u", priv->pw_proxy, priv->global_id);
g_debug ("%s:%p destroyed pw_proxy %p (%s; %s; %u)",
G_OBJECT_TYPE_NAME (self), self, priv->pw_proxy,
spa_debug_type_find_name (pw_type_info(), priv->iface_type),
priv->global ? "global" : "not global",
priv->global ? priv->global->id : 0);
priv->pw_proxy = NULL;
g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED], 0);
@ -232,10 +236,12 @@ wp_proxy_finalize (GObject * object)
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
g_debug ("%s:%p destroyed (global %u; pw_proxy %p)",
G_OBJECT_TYPE_NAME (object), object, priv->global_id, priv->pw_proxy);
G_OBJECT_TYPE_NAME (object), object,
priv->global ? priv->global->id : 0,
priv->pw_proxy);
g_clear_pointer (&priv->augment_tasks, g_ptr_array_unref);
g_clear_pointer (&priv->global_props, wp_properties_unref);
g_clear_pointer (&priv->global, wp_global_unref);
g_weak_ref_clear (&priv->core);
g_clear_pointer (&priv->async_tasks, g_hash_table_unref);
@ -252,14 +258,8 @@ wp_proxy_set_property (GObject * object, guint property_id,
case PROP_CORE:
g_weak_ref_set (&priv->core, g_value_get_object (value));
break;
case PROP_GLOBAL_ID:
priv->global_id = g_value_get_uint (value);
break;
case PROP_GLOBAL_PERMISSIONS:
priv->global_perm = g_value_get_uint (value);
break;
case PROP_GLOBAL_PROPERTIES:
priv->global_props = g_value_dup_boxed (value);
case PROP_GLOBAL:
priv->global = g_value_dup_boxed (value);
break;
case PROP_INTERFACE_TYPE:
priv->iface_type = g_value_get_uint (value);
@ -287,13 +287,13 @@ wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
g_value_take_object (value, g_weak_ref_get (&priv->core));
break;
case PROP_GLOBAL_ID:
g_value_set_uint (value, priv->global_id);
g_value_set_uint (value, priv->global ? priv->global->id : 0);
break;
case PROP_GLOBAL_PERMISSIONS:
g_value_set_uint (value, priv->global_perm);
g_value_set_uint (value, priv->global ? priv->global->permissions : 0);
break;
case PROP_GLOBAL_PROPERTIES:
g_value_set_boxed (value, priv->global_props);
g_value_set_boxed (value, priv->global ? priv->global->properties : NULL);
break;
case PROP_INTERFACE_TYPE:
g_value_set_uint (value, priv->iface_type);
@ -347,7 +347,7 @@ wp_proxy_default_augment (WpProxy * self, WpProxyFeatures features)
/* bind */
priv->pw_proxy = pw_registry_proxy_bind (
wp_core_get_pw_registry_proxy (core), priv->global_id,
wp_core_get_pw_registry_proxy (core), priv->global->id,
priv->iface_type, priv->iface_version, 0);
wp_proxy_got_pw_proxy (self);
}
@ -372,20 +372,25 @@ wp_proxy_class_init (WpProxyClass * klass)
g_param_spec_object ("core", "core", "The WpCore", WP_TYPE_CORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_GLOBAL,
g_param_spec_boxed ("global", "global", "Internal WpGlobal object",
wp_global_get_type (),
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_GLOBAL_ID,
g_param_spec_uint ("global-id", "global-id",
"The pipewire global id", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_GLOBAL_PERMISSIONS,
g_param_spec_uint ("global-permissions", "global-permissions",
"The pipewire global permissions", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES,
g_param_spec_boxed ("global-properties", "global-properties",
"The pipewire global properties", WP_TYPE_PROPERTIES,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_INTERFACE_TYPE,
g_param_spec_uint ("interface-type", "interface-type",
@ -429,18 +434,14 @@ wp_proxy_class_init (WpProxyClass * klass)
}
WpProxy *
wp_proxy_new_global (WpCore * core,
guint32 id, guint32 permissions, WpProperties * properties,
guint32 type, guint32 version)
wp_proxy_new_global (WpCore * core, WpGlobal * global)
{
GType gtype = wp_proxy_find_instance_type (type, version);
GType gtype = wp_proxy_find_instance_type (global->type, global->version);
return g_object_new (gtype,
"core", core,
"global-id", id,
"global-permissions", permissions,
"global-properties", properties,
"interface-type", type,
"interface-version", version,
"global", global,
"interface-type", global->type,
"interface-version", global->version,
NULL);
}
@ -585,7 +586,12 @@ wp_proxy_get_core (WpProxy * self)
gboolean
wp_proxy_is_global (WpProxy * self)
{
return wp_proxy_get_global_id (self) != 0;
WpProxyPrivate *priv;
g_return_val_if_fail (WP_IS_PROXY (self), FALSE);
priv = wp_proxy_get_instance_private (self);
return priv->global != NULL;
}
guint32
@ -596,7 +602,7 @@ wp_proxy_get_global_id (WpProxy * self)
g_return_val_if_fail (WP_IS_PROXY (self), 0);
priv = wp_proxy_get_instance_private (self);
return priv->global_id;
return priv->global ? priv->global->id : 0;
}
guint32
@ -607,7 +613,7 @@ wp_proxy_get_global_permissions (WpProxy * self)
g_return_val_if_fail (WP_IS_PROXY (self), 0);
priv = wp_proxy_get_instance_private (self);
return priv->global_perm;
return priv->global ? priv->global->permissions : 0;
}
/**
@ -623,7 +629,9 @@ wp_proxy_get_global_properties (WpProxy * self)
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
priv = wp_proxy_get_instance_private (self);
return priv->global_props ? wp_properties_ref (priv->global_props) : NULL;
if (!priv->global || !priv->global->properties)
return NULL;
return wp_properties_ref (priv->global->properties);
}
guint32

View file

@ -40,9 +40,6 @@ struct _WpProxyClass
void (*pw_proxy_destroyed) (WpProxy * self);
};
WpProxy * wp_proxy_new_global (WpCore * core,
guint32 id, guint32 permissions, WpProperties * properties,
guint32 type, guint32 version);
WpProxy * wp_proxy_new_wrap (WpCore * core,
struct pw_proxy * proxy, guint32 type, guint32 version);

View file

@ -12,6 +12,7 @@
#include "factory.h"
#include "module.h"
#include "monitor.h"
#include "object-manager.h"
#include "policy.h"
#include "properties.h"
#include "proxy.h"

View file

@ -10,7 +10,7 @@
#include <pipewire/pipewire.h>
static void
client_added (WpCore * core, WpProxyClient *client, gpointer data)
client_added (WpObjectManager * om, WpProxyClient *client, gpointer data)
{
g_autoptr (WpProperties) properties = NULL;
const char *access;
@ -30,9 +30,14 @@ client_added (WpCore * core, WpProxyClient *client, gpointer data)
void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
wp_core_set_default_proxy_features (core, WP_TYPE_PROXY_CLIENT,
WpObjectManager *om;
om = wp_object_manager_new ();
wp_object_manager_add_proxy_interest (om, PW_TYPE_INTERFACE_Client, NULL,
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO);
g_signal_connect(core, "remote-global-added::client",
(GCallback) client_added, NULL);
g_signal_connect (om, "object-added", (GCallback) client_added, NULL);
wp_core_install_object_manager (core, om);
wp_module_set_destroy_callback (module, g_object_unref, om);
}

View file

@ -31,9 +31,9 @@ struct _WpAudioStreamPrivate
/* Stream Proxy */
WpProxyNode *proxy;
/* Stream Port Proxies */
GPtrArray *port_proxies;
WpObjectManager *ports_om;
GVariantBuilder port_vb;
gboolean port_config_done;
/* Stream Controls */
gfloat volume;
@ -94,98 +94,6 @@ wp_audio_stream_id_decode (guint id, guint *stream_id, guint *control_id)
*control_id = c_id;
}
/* called once after all the ports are augmented with INFO */
static void
on_all_ports_augmented (WpProxy *proxy, GAsyncResult *res, WpAudioStream *self)
{
g_autoptr (GError) error = NULL;
wp_proxy_sync_finish (proxy, res, &error);
if (error) {
g_warning ("WpAudioStream:%p second sync failed: %s", self,
error->message);
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
return;
}
g_debug ("%s:%p second sync done", G_OBJECT_TYPE_NAME (self), self);
wp_audio_stream_init_task_finish (self, NULL);
}
/* called multiple times after on_port_config_done */
static void
on_audio_stream_port_augmented (WpProxy *port_proxy, GAsyncResult *res,
WpAudioStream *self)
{
g_autoptr (GError) error = NULL;
wp_proxy_augment_finish (port_proxy, res, &error);
if (error) {
g_warning ("WpAudioStream:%p Stream port failed to augment: %s", self,
error->message);
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
return;
}
}
/* called once after we have all the ports added */
static void
on_port_config_done (WpProxy *proxy, GAsyncResult *res, WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (GError) error = NULL;
wp_proxy_sync_finish (proxy, res, &error);
if (error) {
g_warning ("WpAudioStream:%p port config sync failed: %s", self,
error->message);
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
return;
}
g_debug ("%s:%p port config done", G_OBJECT_TYPE_NAME (self), self);
wp_proxy_sync (WP_PROXY (priv->proxy), NULL,
(GAsyncReadyCallback) on_all_ports_augmented, self);
}
/* called multiple times after we set the PortConfig */
static void
on_audio_stream_port_added (WpCore *core, WpProxy *proxy, WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpProperties) props = wp_proxy_get_global_properties (proxy);
const struct pw_node_info *info = NULL;
const char *s;
guint node_id = 0;
/* Get the node id */
info = wp_proxy_node_get_info (priv->proxy);
if (!info)
return;
if ((s = wp_properties_get (props, PW_KEY_NODE_ID)))
node_id = atoi(s);
/* Skip ports that are not owned by this stream */
if (info->id != node_id)
return;
/* Add the proxy port to the array */
g_ptr_array_add(priv->port_proxies, g_object_ref (proxy));
wp_proxy_augment (proxy, WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO,
NULL, (GAsyncReadyCallback) on_audio_stream_port_augmented, self);
}
static void
on_audio_stream_port_removed (WpCore *core, WpProxy *proxy, WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_ptr_array_remove (priv->port_proxies, proxy);
}
static void
audio_stream_event_param (WpProxy *proxy, int seq, uint32_t id,
uint32_t index, uint32_t next, const struct spa_pod *param,
@ -232,11 +140,29 @@ audio_stream_event_param (WpProxy *proxy, int seq, uint32_t id,
}
}
static void
on_ports_changed (WpObjectManager *om, WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
if (priv->port_config_done) {
g_debug ("%s:%p port config done", G_OBJECT_TYPE_NAME (self), self);
wp_audio_stream_init_task_finish (self, NULL);
g_signal_handlers_disconnect_by_func (priv->ports_om, on_ports_changed,
self);
}
}
static void
on_node_proxy_augmented (WpProxy * proxy, GAsyncResult * res,
WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (GError) error = NULL;
g_autoptr (WpCore) core = NULL;
GVariantBuilder b;
const struct pw_node_info *info = NULL;
g_autofree gchar *node_id = NULL;
wp_proxy_augment_finish (proxy, res, &error);
if (error) {
@ -249,6 +175,36 @@ on_node_proxy_augmented (WpProxy * proxy, GAsyncResult * res,
g_signal_connect_object (proxy, "param",
(GCallback) audio_stream_event_param, self, 0);
wp_proxy_node_subscribe_params (WP_PROXY_NODE (proxy), 1, SPA_PARAM_Props);
priv->ports_om = wp_object_manager_new ();
/* Get the node id */
info = wp_proxy_node_get_info (WP_PROXY_NODE (proxy));
node_id = g_strdup_printf ("%u", info->id);
/* set a constraint: the port's "node.id" must match
the stream's underlying node id */
g_variant_builder_init (&b, G_VARIANT_TYPE ("aa{sv}"));
g_variant_builder_open (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "type",
g_variant_new_int32 (WP_OBJECT_MANAGER_CONSTRAINT_PW_GLOBAL_PROPERTY));
g_variant_builder_add (&b, "{sv}", "name",
g_variant_new_string (PW_KEY_NODE_ID));
g_variant_builder_add (&b, "{sv}", "value",
g_variant_new_string (node_id));
g_variant_builder_close (&b);
/* declare interest on ports with this constraint */
wp_object_manager_add_proxy_interest (priv->ports_om, PW_TYPE_INTERFACE_Port,
g_variant_builder_end (&b),
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO);
g_signal_connect (priv->ports_om, "objects-changed",
(GCallback) on_ports_changed, self);
/* install the object manager */
g_object_get (proxy, "core", &core, NULL);
wp_core_install_object_manager (core, priv->ports_om);
}
static void
@ -257,15 +213,14 @@ wp_audio_stream_finalize (GObject * object)
WpAudioStreamPrivate *priv =
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
g_clear_object (&priv->ports_om);
/* Clear the endpoint weak reference */
g_weak_ref_clear (&priv->endpoint);
/* Clear the name */
g_clear_pointer (&priv->name, g_free);
/* Clear the port proxies */
g_clear_pointer (&priv->port_proxies, g_ptr_array_unref);
g_clear_object (&priv->init_task);
G_OBJECT_CLASS (wp_audio_stream_parent_class)->finalize (object);
@ -371,12 +326,6 @@ wp_audio_stream_init_async (GAsyncInitable *initable, int io_priority,
wp_proxy_augment (WP_PROXY (priv->proxy),
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO, NULL,
(GAsyncReadyCallback) on_node_proxy_augmented, self);
/* Register a port_added & removed callback */
g_signal_connect_object(core, "remote-global-added::port",
(GCallback) on_audio_stream_port_added, self, 0);
g_signal_connect_object(core, "remote-global-removed::port",
(GCallback) on_audio_stream_port_removed, self, 0);
}
static gboolean
@ -405,7 +354,6 @@ wp_audio_stream_init (WpAudioStream * self)
/* Controls */
priv->volume = 1.0;
priv->mute = FALSE;
priv->port_proxies = g_ptr_array_new_full(4, (GDestroyNotify)g_object_unref);
}
static void
@ -524,10 +472,12 @@ wp_audio_stream_prepare_link (WpAudioStream * self, GVariant ** properties,
GError ** error)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (GPtrArray) port_proxies =
wp_object_manager_get_objects (priv->ports_om, 0);
/* Create a variant array with all the ports */
g_variant_builder_init (&priv->port_vb, G_VARIANT_TYPE ("a(uuuy)"));
g_ptr_array_foreach(priv->port_proxies, port_proxies_foreach_func, self);
g_ptr_array_foreach (port_proxies, port_proxies_foreach_func, self);
*properties = g_variant_builder_end (&priv->port_vb);
return TRUE;
@ -635,7 +585,5 @@ void
wp_audio_stream_finish_port_config (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
wp_proxy_sync (WP_PROXY (priv->proxy), NULL,
(GAsyncReadyCallback) on_port_config_done, self);
priv->port_config_done = TRUE;
}

View file

@ -19,6 +19,7 @@
struct impl
{
WpModule *module;
WpObjectManager *om;
GHashTable *registered_endpoints;
GVariant *streams;
};
@ -84,13 +85,14 @@ is_alsa_node (WpProperties * props)
}
static void
on_node_added(WpCore *core, WpProxy *proxy, struct impl *impl)
on_node_added (WpObjectManager *om, WpProxy *proxy, struct impl *impl)
{
const gchar *media_class, *name;
enum pw_direction direction;
GVariantBuilder b;
g_autoptr (WpProperties) props = NULL;
g_autoptr (GVariant) endpoint_props = NULL;
g_autoptr (WpCore) core = NULL;
props = wp_proxy_get_global_properties (proxy);
g_return_if_fail(props);
@ -121,12 +123,13 @@ on_node_added(WpCore *core, WpProxy *proxy, struct impl *impl)
endpoint_props = g_variant_builder_end (&b);
/* Create the endpoint async */
g_object_get (om, "core", &core, NULL);
wp_factory_make (core, "pw-audio-softdsp-endpoint", WP_TYPE_ENDPOINT,
endpoint_props, on_endpoint_created, impl);
}
static void
on_node_removed (WpCore *core, WpProxy *proxy, struct impl *impl)
on_node_removed (WpObjectManager *om, WpProxy *proxy, struct impl *impl)
{
WpEndpoint *endpoint = NULL;
guint32 id = wp_proxy_get_global_id (proxy);
@ -150,6 +153,8 @@ module_destroy (gpointer data)
/* Set to NULL as we don't own the reference */
impl->module = NULL;
g_clear_object (&impl->om);
/* Destroy the registered endpoints table */
g_hash_table_unref(impl->registered_endpoints);
impl->registered_endpoints = NULL;
@ -168,6 +173,7 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
/* Create the module data */
impl = g_slice_new0(struct impl);
impl->module = module;
impl->om = wp_object_manager_new ();
impl->registered_endpoints = g_hash_table_new_full (g_direct_hash,
g_direct_equal, NULL, (GDestroyNotify)g_object_unref);
impl->streams = g_variant_lookup_value (args, "streams",
@ -177,8 +183,13 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_module_set_destroy_callback (module, module_destroy, impl);
/* Register the global addded/removed callbacks */
g_signal_connect(core, "remote-global-added::node",
g_signal_connect(impl->om, "object-added",
(GCallback) on_node_added, impl);
g_signal_connect(core, "remote-global-removed::node",
g_signal_connect(impl->om, "object-removed",
(GCallback) on_node_removed, impl);
//TODO add constraints & features
wp_object_manager_add_proxy_interest (impl->om, PW_TYPE_INTERFACE_Node, NULL,
0);
wp_core_install_object_manager (core, impl->om);
}

View file

@ -16,6 +16,7 @@
struct module_data
{
WpObjectManager *om;
GHashTable *registered_endpoints;
};
@ -48,7 +49,7 @@ on_endpoint_created(GObject *initable, GAsyncResult *res, gpointer d)
}
static void
on_node_added (WpCore *core, WpProxy *proxy, gpointer d)
on_node_added (WpObjectManager *om, WpProxy *proxy, gpointer d)
{
struct module_data *data = d;
const gchar *name, *media_class;
@ -57,6 +58,7 @@ on_node_added (WpCore *core, WpProxy *proxy, gpointer d)
g_autoptr (GVariant) endpoint_props = NULL;
guint32 id = wp_proxy_get_global_id (proxy);
g_autoptr (WpProperties) props = wp_proxy_get_global_properties (proxy);
g_autoptr (WpCore) core = NULL;
/* Get the media_class */
media_class = wp_properties_get (props, PW_KEY_MEDIA_CLASS);
@ -95,12 +97,13 @@ on_node_added (WpCore *core, WpProxy *proxy, gpointer d)
endpoint_props = g_variant_builder_end (&b);
/* Create the endpoint async */
g_object_get (om, "core", &core, NULL);
wp_factory_make (core, "pw-audio-softdsp-endpoint", WP_TYPE_ENDPOINT,
endpoint_props, on_endpoint_created, data);
}
static void
on_node_removed (WpCore *core, WpProxy *proxy, gpointer d)
on_node_removed (WpObjectManager *om, WpProxy *proxy, gpointer d)
{
struct module_data *data = d;
WpEndpoint *endpoint = NULL;
@ -122,6 +125,8 @@ module_destroy (gpointer d)
{
struct module_data *data = d;
g_clear_object (&data->om);
/* Destroy the registered endpoints table */
g_clear_pointer (&data->registered_endpoints, g_hash_table_unref);
@ -136,15 +141,21 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
/* Create the module data */
data = g_slice_new0 (struct module_data);
data->om = wp_object_manager_new ();
data->registered_endpoints = g_hash_table_new_full (g_direct_hash,
g_direct_equal, NULL, (GDestroyNotify)g_object_unref);
g_direct_equal, NULL, (GDestroyNotify) g_object_unref);
/* Set the module destroy callback */
wp_module_set_destroy_callback (module, module_destroy, data);
/* Register the global added/removed callbacks */
g_signal_connect(core, "remote-global-added::node",
g_signal_connect(data->om, "object-added",
(GCallback) on_node_added, data);
g_signal_connect(core, "remote-global-removed::node",
g_signal_connect(data->om, "object-removed",
(GCallback) on_node_removed, data);
//TODO add constraints & features
wp_object_manager_add_proxy_interest (data->om, PW_TYPE_INTERFACE_Node, NULL,
0);
wp_core_install_object_manager (core, data->om);
}

View file

@ -128,6 +128,7 @@ static gboolean
select_new_endpoint (WpSimplePolicy *self)
{
g_autoptr (WpCore) core = NULL;
g_autoptr (WpPolicyManager) pmgr = NULL;
g_autoptr (GPtrArray) ptr_array = NULL;
const gchar *media_class = NULL;
WpEndpoint *other;
@ -144,9 +145,10 @@ select_new_endpoint (WpSimplePolicy *self)
return G_SOURCE_REMOVE;
core = wp_policy_get_core (WP_POLICY (self));
pmgr = wp_policy_manager_get_instance (core);
/* Get all the endpoints with the same media class */
ptr_array = wp_endpoint_find (core, media_class);
ptr_array = wp_policy_manager_list_endpoints (pmgr, media_class);
/* select the first available that has the "selected" control */
for (i = 0; i < (ptr_array ? ptr_array->len : 0); i++) {
@ -392,13 +394,15 @@ static gboolean
simple_policy_rescan_in_idle (WpSimplePolicy *self)
{
g_autoptr (WpCore) core = wp_policy_get_core (WP_POLICY (self));
g_autoptr (WpPolicyManager) pmgr = wp_policy_manager_get_instance (core);
g_autoptr (GPtrArray) endpoints = NULL;
WpEndpoint *ep;
gint i;
g_debug ("rescanning for clients that need linking");
endpoints = wp_endpoint_find (core, "Stream/Input/Audio");
endpoints = wp_policy_manager_list_endpoints (pmgr,
"Stream/Input/Audio");
if (endpoints) {
/* link all capture clients */
for (i = 0; i < endpoints->len; i++) {
@ -408,7 +412,8 @@ simple_policy_rescan_in_idle (WpSimplePolicy *self)
}
g_clear_pointer (&endpoints, g_ptr_array_unref);
endpoints = wp_endpoint_find (core, "Persistent/Stream/Input/Audio");
endpoints = wp_policy_manager_list_endpoints (pmgr,
"Persistent/Stream/Input/Audio");
if (endpoints) {
/* link all persistent capture clients */
for (i = 0; i < endpoints->len; i++) {
@ -418,7 +423,8 @@ simple_policy_rescan_in_idle (WpSimplePolicy *self)
}
g_clear_pointer (&endpoints, g_ptr_array_unref);
endpoints = wp_endpoint_find (core, "Stream/Output/Audio");
endpoints = wp_policy_manager_list_endpoints (pmgr,
"Stream/Output/Audio");
if (endpoints && endpoints->len > 0) {
/* sort based on role priorities */
g_ptr_array_sort_with_data (endpoints, compare_client_priority,
@ -430,7 +436,8 @@ simple_policy_rescan_in_idle (WpSimplePolicy *self)
}
g_clear_pointer (&endpoints, g_ptr_array_unref);
endpoints = wp_endpoint_find (core, "Persistent/Stream/Output/Audio");
endpoints = wp_policy_manager_list_endpoints (pmgr,
"Persistent/Stream/Output/Audio");
if (endpoints) {
/* link all persistent output clients */
for (i = 0; i < endpoints->len; i++) {
@ -474,6 +481,7 @@ simple_policy_find_endpoint (WpPolicy *policy, GVariant *props,
guint32 *stream_id)
{
g_autoptr (WpCore) core = NULL;
g_autoptr (WpPolicyManager) pmgr = NULL;
g_autoptr (GPtrArray) ptr_array = NULL;
const char *action = NULL;
const char *media_class = NULL;
@ -482,12 +490,13 @@ simple_policy_find_endpoint (WpPolicy *policy, GVariant *props,
int i;
core = wp_policy_get_core (policy);
pmgr = wp_policy_manager_get_instance (core);
g_variant_lookup (props, "action", "&s", &action);
/* Get all the endpoints with the specific media class*/
g_variant_lookup (props, "media.class", "&s", &media_class);
ptr_array = wp_endpoint_find (core, media_class);
ptr_array = wp_policy_manager_list_endpoints (pmgr, media_class);
if (!ptr_array)
return NULL;

View file

@ -24,6 +24,9 @@ typedef struct {
/* the client wireplumber core */
WpCore *core;
/* the object manager that listens for proxies */
WpObjectManager *om;
} TestProxyFixture;
static gboolean
@ -65,6 +68,7 @@ test_proxy_setup (TestProxyFixture *self, gconstpointer user_data)
self->context = g_main_context_new ();
self->loop = g_main_loop_new (self->context, FALSE);
self->core = wp_core_new (self->context, props);
self->om = wp_object_manager_new ();
g_main_context_push_thread_default (self->context);
@ -83,6 +87,7 @@ test_proxy_teardown (TestProxyFixture *self, gconstpointer user_data)
{
g_main_context_pop_thread_default (self->context);
g_clear_object (&self->om);
g_clear_object (&self->core);
g_clear_pointer (&self->timeout_source, g_source_unref);
g_clear_pointer (&self->loop, g_main_loop_unref);
@ -117,14 +122,18 @@ test_proxy_basic_augmented (WpProxy *proxy, GAsyncResult *res,
}
static void
test_proxy_basic_remote_global_added (WpCore *core, WpProxy *proxy,
test_proxy_basic_object_added (WpObjectManager *om, WpProxy *proxy,
TestProxyFixture *fixture)
{
g_assert_nonnull (proxy);
{
g_autoptr (WpCore) pcore = wp_proxy_get_core (proxy);
g_autoptr (WpCore) pcore = NULL;
g_autoptr (WpCore) omcore = NULL;
g_object_get (proxy, "core", &pcore, NULL);
g_object_get (om, "core", &omcore, NULL);
g_assert_nonnull (pcore);
g_assert_true (pcore == core);
g_assert_nonnull (omcore);
g_assert_true (pcore == omcore);
}
g_assert_cmpuint (wp_proxy_get_global_id (proxy), !=, 0);
g_assert_true (wp_proxy_is_global (proxy));
@ -156,8 +165,12 @@ test_proxy_basic (TestProxyFixture *fixture, gconstpointer data)
{
/* our test server should advertise exactly one
* client: our WpRemote; use this to test WpProxy */
g_signal_connect (fixture->core, "remote-global-added::client",
(GCallback) test_proxy_basic_remote_global_added, fixture);
g_signal_connect (fixture->om, "object-added",
(GCallback) test_proxy_basic_object_added, fixture);
wp_object_manager_add_proxy_interest (fixture->om,
PW_TYPE_INTERFACE_Client, NULL, 0);
wp_core_install_object_manager (fixture->core, fixture->om);
g_assert_true (wp_core_connect (fixture->core));
g_main_loop_run (fixture->loop);
@ -200,7 +213,7 @@ test_proxy_node_enum_params_done (WpProxyNode *node, GAsyncResult *res,
}
static void
test_proxy_node_remote_global_added (WpCore *core, WpProxy *proxy,
test_proxy_node_object_added (WpObjectManager *om, WpProxy *proxy,
TestProxyFixture *fixture)
{
const struct pw_node_info *info;
@ -257,12 +270,15 @@ test_proxy_node (TestProxyFixture *fixture, gconstpointer data)
pw_thread_loop_unlock (fixture->server.thread_loop);
/* we should be able to see this exported audiotestsrc node on the client */
g_signal_connect (fixture->core, "remote-global-added::node",
(GCallback) test_proxy_node_remote_global_added, fixture);
g_signal_connect (fixture->om, "object-added",
(GCallback) test_proxy_node_object_added, fixture);
/* tell the remote to call global-added only when these features are ready */
wp_core_set_default_proxy_features (fixture->core,
WP_TYPE_PROXY_NODE, WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO);
/* 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_core_install_object_manager (fixture->core, fixture->om);
g_assert_true (wp_core_connect (fixture->core));
g_main_loop_run (fixture->loop);