From ad743a21431824d3af0b49bf3648d6764a4ab263 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 12 Mar 2024 11:52:56 +0200 Subject: [PATCH] registry: move to a separate file and decouple it from the object manager So that it can have its own log topic... It also makes the code a bit easier to navigate. --- lib/wp/meson.build | 1 + lib/wp/object-manager.c | 510 +------------------------------------ lib/wp/object-manager.h | 16 ++ lib/wp/private/registry.c | 515 ++++++++++++++++++++++++++++++++++++++ lib/wp/private/registry.h | 3 + 5 files changed, 541 insertions(+), 504 deletions(-) create mode 100644 lib/wp/private/registry.c diff --git a/lib/wp/meson.build b/lib/wp/meson.build index ad6ee14e..498120db 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -41,6 +41,7 @@ wp_lib_sources = files( wp_lib_priv_sources = files( 'private/pipewire-object-mixin.c', 'private/internal-comp-loader.c', + 'private/registry.c', ) wp_lib_headers = files( diff --git a/lib/wp/object-manager.c b/lib/wp/object-manager.c index a86de348..aa87694d 100644 --- a/lib/wp/object-manager.c +++ b/lib/wp/object-manager.c @@ -672,7 +672,7 @@ idle_emit_objects_changed (WpObjectManager * self) return G_SOURCE_REMOVE; } -static void +void wp_object_manager_maybe_objects_changed (WpObjectManager * self) { wp_trace_object (self, "pending:%u changed:%d idle_source:%p installed:%d", @@ -725,7 +725,7 @@ wp_object_manager_maybe_objects_changed (WpObjectManager * self) } /* caller must also call wp_object_manager_maybe_objects_changed() after */ -static void +void wp_object_manager_add_object (WpObjectManager * self, gpointer object) { if (wp_object_manager_is_interested_in_object (self, object)) { @@ -737,7 +737,7 @@ wp_object_manager_add_object (WpObjectManager * self, gpointer object) } /* caller must also call wp_object_manager_maybe_objects_changed() after */ -static void +void wp_object_manager_rm_object (WpObjectManager * self, gpointer object) { guint index; @@ -766,7 +766,7 @@ on_proxy_ready (GObject * proxy, GAsyncResult * res, gpointer data) } /* caller must also call wp_object_manager_maybe_objects_changed() after */ -static void +void wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global) { WpProxyFeatures features = 0; @@ -795,405 +795,6 @@ wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global) } } -/* - * WpRegistry: - * - * The registry keeps track of registered objects on the wireplumber core. - * There are 3 kinds of registered objects: - * - * 1) PipeWire global objects, which live in another process. - * - * These objects are represented by a WpGlobal with the - * WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag set. They appear when - * the registry_global() event is fired and are removed by - * registry_global_remove(). These objects do not have an associated - * WpProxy, unless there is at least one WpObjectManager that is interested - * in them. In this case, a WpProxy is constructed and it is owned by the - * WpGlobal until the global is removed by the registry_global_remove() event. - * - * 2) PipeWire global objects, which were constructed by this process, either - * by calling into a remove factory (see wp_node_new_from_factory()) or - * by exporting a local object (WpImplSession etc...). - * - * These objects are also represented by a WpGlobal, which may however be - * constructed before they appear on the registry. The associated WpProxy - * calls into wp_registry_prepare_new_global() at the time it receives - * the 'bound' event and creates a global that has the - * WP_GLOBAL_FLAG_OWNED_BY_PROXY flag enabled. As the flag name suggests, - * these globals are "owned" by the WpProxy and the WpGlobal has no ref - * on the WpProxy itself. This allows destroying the proxy in client code - * by dropping its last reference. - * - * Normally, these global objects also appear on the pipewire registry. When - * this happens, the WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag is also added - * and that keeps an additional reference on the global (both flags must - * be dropped before the WpGlobal is destroyed). - * - * In some cases, such an object might appear first on the registry and - * then receive the 'bound' event. In order to handle this situation, globals - * are not advertised immediately when they appear on the registry, but - * they are added on a tmp_globals list instead, which is emptied on the - * next core sync. In all cases, the proxy 'bound' and the registry 'global' - * events will be fired in the same sync cycle, so we can catch a late - * 'bound' event and still associate the proxy with the WpGlobal before - * object managers are notified about the existence of this global. - * - * 3) WirePlumber global objects (WpModule, WpPlugin, WpSiFactory). - * - * These are local objects that have nothing to do with PipeWire. They do not - * have a global id and they are also not subclasses of WpProxy. The registry - * always owns a reference on them, so that they are kept alive for as long - * as the WpCore is alive. - */ - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "wp-registry" - -void -wp_registry_notify_add_object (WpRegistry *self, gpointer object) -{ - for (guint i = 0; i < self->object_managers->len; i++) { - WpObjectManager *om = g_ptr_array_index (self->object_managers, i); - wp_object_manager_add_object (om, object); - wp_object_manager_maybe_objects_changed (om); - } -} - -void -wp_registry_notify_rm_object (WpRegistry *self, gpointer object) -{ - for (guint 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); - wp_object_manager_maybe_objects_changed (om); - } -} - -static void -object_manager_destroyed (gpointer data, GObject * om) -{ - WpRegistry *self = data; - g_ptr_array_remove_fast (self->object_managers, om); -} - -/* find the subclass of WpPipewireGloabl that can handle - the given pipewire interface type of the given version */ -static inline GType -find_proxy_instance_type (const char * type, guint32 version) -{ - g_autofree GType *children; - guint n_children; - - children = g_type_children (WP_TYPE_GLOBAL_PROXY, &n_children); - - for (guint i = 0; i < n_children; i++) { - WpProxyClass *klass = (WpProxyClass *) g_type_class_ref (children[i]); - if (g_strcmp0 (klass->pw_iface_type, type) == 0 && - klass->pw_iface_version == version) { - g_type_class_unref (klass); - return children[i]; - } - - g_type_class_unref (klass); - } - - return WP_TYPE_GLOBAL_PROXY; -} - -/* called by the registry when a global appears */ -static void -registry_global (void *data, uint32_t id, uint32_t permissions, - const char *type, uint32_t version, const struct spa_dict *props) -{ - WpRegistry *self = data; - GType gtype = find_proxy_instance_type (type, version); - - wp_debug_object (wp_registry_get_core (self), - "global:%u perm:0x%x type:%s/%u -> %s", - id, permissions, type, version, g_type_name (gtype)); - - wp_registry_prepare_new_global (self, id, permissions, - WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY, gtype, NULL, props, NULL); -} - -/* called by the registry when a global is removed */ -static void -registry_global_remove (void *data, uint32_t id) -{ - WpRegistry *self = data; - WpGlobal *global = NULL; - - if (id < self->globals->len) - global = g_ptr_array_index (self->globals, id); - - /* if not found, look in the tmp_globals, as it may still not be exposed */ - if (!global) { - for (guint i = 0; i < self->tmp_globals->len; i++) { - WpGlobal *g = g_ptr_array_index (self->tmp_globals, i); - if (g->id == id) { - global = g; - break; - } - } - } - - g_return_if_fail (global && - global->flags & WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); - - wp_debug_object (wp_registry_get_core (self), - "global removed:%u type:%s", id, g_type_name (global->type)); - - wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); -} - -static const struct pw_registry_events registry_events = { - PW_VERSION_REGISTRY_EVENTS, - .global = registry_global, - .global_remove = registry_global_remove, -}; - -void -wp_registry_init (WpRegistry *self) -{ - self->globals = - g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref); - self->tmp_globals = - g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref); - self->objects = g_ptr_array_new_with_free_func (g_object_unref); - self->object_managers = g_ptr_array_new (); - self->features = g_ptr_array_new_with_free_func (g_free); -} - -void -wp_registry_clear (WpRegistry *self) -{ - wp_registry_detach (self); - g_clear_pointer (&self->globals, g_ptr_array_unref); - g_clear_pointer (&self->tmp_globals, g_ptr_array_unref); - g_clear_pointer (&self->features, g_ptr_array_unref); - - /* 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) { - g_autoptr (GObject) object = g_ptr_array_steal_index_fast (objlist, - objlist->len - 1); - wp_registry_notify_rm_object (self, 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); - } - } -} - -void -wp_registry_attach (WpRegistry *self, struct pw_core *pw_core) -{ - self->pw_registry = pw_core_get_registry (pw_core, - PW_VERSION_REGISTRY, 0); - pw_registry_add_listener (self->pw_registry, &self->listener, - ®istry_events, self); -} - -void -wp_registry_detach (WpRegistry *self) -{ - if (self->pw_registry) { - spa_hook_remove (&self->listener); - pw_proxy_destroy ((struct pw_proxy *) self->pw_registry); - self->pw_registry = NULL; - } - - /* remove pipewire globals */ - GPtrArray *objlist = self->globals; - while (objlist && objlist->len > 0) { - g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist, - objlist->len - 1); - - if (!global) - continue; - - if (global->proxy) - wp_registry_notify_rm_object (self, global->proxy); - - /* remove the APPEARS_ON_REGISTRY flag to unref the proxy if it is owned - by the registry; set registry to NULL to avoid further interference */ - global->registry = NULL; - wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); - - /* the registry's ref on global is dropped here; it may still live if - there is a proxy that owns a ref on it, but global->registry is set - to NULL, so there is no further interference */ - } - - /* drop tmp globals as well */ - objlist = self->tmp_globals; - while (objlist && objlist->len > 0) { - g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist, - objlist->len - 1); - wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); - } -} - -static gboolean -expose_tmp_globals (WpCore *core) -{ - WpRegistry *self = wp_core_get_registry (core); - g_autoptr (GPtrArray) tmp_globals = NULL; - g_autoptr (GPtrArray) object_managers = NULL; - - /* in case the registry was cleared in the meantime... */ - if (G_UNLIKELY (!self->tmp_globals)) - return G_SOURCE_REMOVE; - - /* steal the tmp_globals list and replace it with an empty one */ - tmp_globals = self->tmp_globals; - self->tmp_globals = - g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref); - - wp_debug_object (core, "exposing %u new globals", tmp_globals->len); - - /* traverse in the order that the globals appeared on the registry */ - for (guint i = 0; i < tmp_globals->len; i++) { - WpGlobal *g = g_ptr_array_index (tmp_globals, i); - - /* if global was already removed, drop it */ - if (g->flags == 0 || g->id == SPA_ID_INVALID) - continue; - - /* if old global is owned by proxy, remove it */ - if (self->globals->len > g->id) { - WpGlobal *old_g = g_ptr_array_index (self->globals, g->id); - if (old_g && (old_g->flags & WP_GLOBAL_FLAG_OWNED_BY_PROXY)) - wp_global_rm_flag (old_g, WP_GLOBAL_FLAG_OWNED_BY_PROXY); - } - - g_return_val_if_fail (self->globals->len <= g->id || - g_ptr_array_index (self->globals, g->id) == NULL, G_SOURCE_REMOVE); - - /* set the registry, so that wp_global_rm_flag() can work full-scale */ - g->registry = self; - - /* store it in the globals list */ - if (self->globals->len <= g->id) - g_ptr_array_set_size (self->globals, g->id + 1); - g_ptr_array_index (self->globals, g->id) = wp_global_ref (g); - } - - object_managers = g_ptr_array_copy (self->object_managers, - (GCopyFunc) g_object_ref, NULL); - g_ptr_array_set_free_func (object_managers, g_object_unref); - - /* notify object managers */ - for (guint i = 0; i < object_managers->len; i++) { - WpObjectManager *om = g_ptr_array_index (object_managers, i); - - for (guint i = 0; i < tmp_globals->len; i++) { - WpGlobal *g = g_ptr_array_index (tmp_globals, i); - - /* if global was already removed, drop it */ - if (g->flags == 0 || g->id == SPA_ID_INVALID) - continue; - - wp_object_manager_add_global (om, g); - } - wp_object_manager_maybe_objects_changed (om); - } - - return G_SOURCE_REMOVE; -} - -/* - * \param new_global (out) (transfer full) (optional): the new global - * - * This is normally called up to 2 times in the same sync cycle: - * one from registry_global(), another from the proxy bound event - * Unfortunately the order in which those 2 events happen is specific - * to the implementation of the object, which is why this is implemented - * with a temporary globals list that get exposed later to the object managers - */ -void -wp_registry_prepare_new_global (WpRegistry * self, guint32 id, - guint32 permissions, guint32 flag, GType type, - WpGlobalProxy *proxy, const struct spa_dict *props, - WpGlobal ** new_global) -{ - g_autoptr (WpGlobal) global = NULL; - WpCore *core = wp_registry_get_core (self); - - g_return_if_fail (flag != 0); - - for (guint i = 0; i < self->tmp_globals->len; i++) { - WpGlobal *g = g_ptr_array_index (self->tmp_globals, i); - if (g->id == id) { - global = wp_global_ref (g); - break; - } - } - - wp_debug_object (core, "%s WpGlobal:%u type:%s proxy:%p", - global ? "reuse" : "new", id, g_type_name (type), - (global && global->proxy) ? global->proxy : proxy); - - if (!global) { - global = g_rc_box_new0 (WpGlobal); - global->flags = flag; - global->id = id; - global->type = type; - global->permissions = permissions; - global->properties = props ? - wp_properties_new_copy_dict (props) : wp_properties_new_empty (); - global->proxy = proxy; - g_ptr_array_add (self->tmp_globals, wp_global_ref (global)); - - /* ensure we have 'object.id' so that we can filter by id on object managers */ - wp_properties_setf (global->properties, PW_KEY_OBJECT_ID, "%u", global->id); - - /* schedule exposing when adding the first global */ - if (self->tmp_globals->len == 1) { - wp_core_idle_add_closure (core, NULL, - g_cclosure_new_object (G_CALLBACK (expose_tmp_globals), G_OBJECT (core))); - } - } else { - /* store the most permissive permissions */ - if (permissions > global->permissions) - global->permissions = permissions; - - global->flags |= flag; - - /* store the most deep type (i.e. WpImplNode instead of WpNode), - so that object-manager interests can work more accurately - if the interest is on a specific subclass */ - if (g_type_depth (type) > g_type_depth (global->type)) - global->type = type; - - if (proxy) { - g_return_if_fail (global->proxy == NULL); - global->proxy = proxy; - } - - if (props) - wp_properties_update_from_dict (global->properties, props); - } - - if (new_global) - *new_global = g_steal_pointer (&global); -} - /*! * \brief Installs the object manager on this core, activating its internal * management engine. @@ -1209,111 +810,12 @@ void wp_core_install_object_manager (WpCore * self, WpObjectManager * om) { WpRegistry *reg; - guint i; g_return_if_fail (WP_IS_CORE (self)); g_return_if_fail (WP_IS_OBJECT_MANAGER (om)); - reg = wp_core_get_registry (self); - - g_object_weak_ref (G_OBJECT (om), object_manager_destroyed, reg); - g_ptr_array_add (reg->object_managers, om); g_weak_ref_set (&om->core, self); - /* add pre-existing objects to the object manager, - in case it's interested in them */ - for (i = 0; i < reg->globals->len; i++) { - WpGlobal *g = g_ptr_array_index (reg->globals, i); - /* check if null because the globals array can have gaps */ - if (g) - wp_object_manager_add_global (om, g); - } - for (i = 0; i < reg->objects->len; i++) { - GObject *o = g_ptr_array_index (reg->objects, i); - wp_object_manager_add_object (om, o); - } - - wp_object_manager_maybe_objects_changed (om); -} - -/* WpGlobal */ - -G_DEFINE_BOXED_TYPE (WpGlobal, wp_global, wp_global_ref, wp_global_unref) - -void -wp_global_rm_flag (WpGlobal *global, guint rm_flag) -{ - WpRegistry *reg = global->registry; - guint32 id = global->id; - - /* no flag to remove */ - if (!(global->flags & rm_flag)) - return; - - wp_trace_boxed (WP_TYPE_GLOBAL, global, - "remove global %u flag 0x%x [flags:0x%x, reg:%p]", - id, rm_flag, global->flags, reg); - - /* global was owned by the proxy; by removing the flag, we clear out - also the proxy pointer, which is presumably no longer valid and we - notify all listeners that the proxy is gone */ - if (rm_flag == WP_GLOBAL_FLAG_OWNED_BY_PROXY) { - global->flags &= ~WP_GLOBAL_FLAG_OWNED_BY_PROXY; - if (reg && global->proxy) { - wp_registry_notify_rm_object (reg, global->proxy); - } - global->proxy = NULL; - } - /* registry removed the global */ - else if (rm_flag == WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY) { - global->flags &= ~WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY; - - /* destroy the proxy if it exists */ - if (global->proxy) { - /* steal the proxy to avoid calling wp_registry_notify_rm_object() - again while removing OWNED_BY_PROXY; - keep a temporary ref so that _deactivate() doesn't crash in case the - pw-proxy-destroyed signal causes external references to be dropped */ - g_autoptr (WpGlobalProxy) proxy = - g_object_ref (g_steal_pointer (&global->proxy)); - - /* notify all listeners that the proxy is gone */ - if (reg) - wp_registry_notify_rm_object (reg, proxy); - - /* remove FEATURE_BOUND to destroy the underlying pw_proxy */ - wp_object_deactivate (WP_OBJECT (proxy), WP_PROXY_FEATURE_BOUND); - - /* stop all in-progress activations */ - wp_object_abort_activation (WP_OBJECT (proxy), "PipeWire proxy removed"); - - /* if the proxy is not owning the global, unref it */ - if (global->flags == 0) - g_object_unref (proxy); - } - - /* It's possible to receive consecutive {add, remove, add} events for the - * same id. Since the WpGlobal might not be destroyed immediately below, - * (e.g. it's in tmp_globals list), we must invalidate the id now, so that - * this WpGlobal is not used in reference to objects added later. - */ - global->id = SPA_ID_INVALID; - wp_properties_setf (global->properties, PW_KEY_OBJECT_ID, NULL); - } - - /* drop the registry's ref on global when it does not appear on the registry anymore */ - if (!(global->flags & WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY) && reg) { - g_clear_pointer (&g_ptr_array_index (reg->globals, id), wp_global_unref); - } -} - -struct pw_proxy * -wp_global_bind (WpGlobal * global) -{ - g_return_val_if_fail (global->proxy, NULL); - g_return_val_if_fail (global->registry, NULL); - - WpProxyClass *klass = WP_PROXY_GET_CLASS (global->proxy); - return pw_registry_bind (global->registry->pw_registry, global->id, - klass->pw_iface_type, klass->pw_iface_version, 0); + reg = wp_core_get_registry (self); + wp_registry_install_object_manager (reg, om); } diff --git a/lib/wp/object-manager.h b/lib/wp/object-manager.h index 65c874cc..86b4933b 100644 --- a/lib/wp/object-manager.h +++ b/lib/wp/object-manager.h @@ -72,6 +72,22 @@ WP_API gpointer wp_object_manager_lookup_full (WpObjectManager * self, WpObjectInterest * interest); +/* private */ + +typedef struct _WpGlobal WpGlobal; + +WP_PRIVATE_API +void wp_object_manager_maybe_objects_changed (WpObjectManager * self); + +WP_PRIVATE_API +void wp_object_manager_add_object (WpObjectManager * self, gpointer object); + +WP_PRIVATE_API +void wp_object_manager_rm_object (WpObjectManager * self, gpointer object); + +WP_PRIVATE_API +void wp_object_manager_add_global (WpObjectManager * self, WpGlobal * global); + G_END_DECLS #endif diff --git a/lib/wp/private/registry.c b/lib/wp/private/registry.c new file mode 100644 index 00000000..9ad99f41 --- /dev/null +++ b/lib/wp/private/registry.c @@ -0,0 +1,515 @@ +/* WirePlumber + * + * Copyright © 2019-2024 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include "registry.h" +#include "object-manager.h" +#include "log.h" + +WP_DEFINE_LOCAL_LOG_TOPIC ("wp-registry") + +/* + * WpRegistry: + * + * The registry keeps track of registered objects on the wireplumber core. + * There are 3 kinds of registered objects: + * + * 1) PipeWire global objects, which live in another process. + * + * These objects are represented by a WpGlobal with the + * WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag set. They appear when + * the registry_global() event is fired and are removed by + * registry_global_remove(). These objects do not have an associated + * WpProxy, unless there is at least one WpObjectManager that is interested + * in them. In this case, a WpProxy is constructed and it is owned by the + * WpGlobal until the global is removed by the registry_global_remove() event. + * + * 2) PipeWire global objects, which were constructed by this process, either + * by calling into a remove factory (see wp_node_new_from_factory()) or + * by exporting a local object (WpImplSession etc...). + * + * These objects are also represented by a WpGlobal, which may however be + * constructed before they appear on the registry. The associated WpProxy + * calls into wp_registry_prepare_new_global() at the time it receives + * the 'bound' event and creates a global that has the + * WP_GLOBAL_FLAG_OWNED_BY_PROXY flag enabled. As the flag name suggests, + * these globals are "owned" by the WpProxy and the WpGlobal has no ref + * on the WpProxy itself. This allows destroying the proxy in client code + * by dropping its last reference. + * + * Normally, these global objects also appear on the pipewire registry. When + * this happens, the WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY flag is also added + * and that keeps an additional reference on the global (both flags must + * be dropped before the WpGlobal is destroyed). + * + * In some cases, such an object might appear first on the registry and + * then receive the 'bound' event. In order to handle this situation, globals + * are not advertised immediately when they appear on the registry, but + * they are added on a tmp_globals list instead, which is emptied on the + * next core sync. In all cases, the proxy 'bound' and the registry 'global' + * events will be fired in the same sync cycle, so we can catch a late + * 'bound' event and still associate the proxy with the WpGlobal before + * object managers are notified about the existence of this global. + * + * 3) WirePlumber global objects (WpModule, WpPlugin, WpSiFactory). + * + * These are local objects that have nothing to do with PipeWire. They do not + * have a global id and they are also not subclasses of WpProxy. The registry + * always owns a reference on them, so that they are kept alive for as long + * as the WpCore is alive. + */ + +static void +object_manager_destroyed (gpointer data, GObject * om) +{ + WpRegistry *self = data; + g_ptr_array_remove_fast (self->object_managers, om); +} + +/* find the subclass of WpPipewireGloabl that can handle + the given pipewire interface type of the given version */ +static inline GType +find_proxy_instance_type (const char * type, guint32 version) +{ + g_autofree GType *children; + guint n_children; + + children = g_type_children (WP_TYPE_GLOBAL_PROXY, &n_children); + + for (guint i = 0; i < n_children; i++) { + WpProxyClass *klass = (WpProxyClass *) g_type_class_ref (children[i]); + if (g_strcmp0 (klass->pw_iface_type, type) == 0 && + klass->pw_iface_version == version) { + g_type_class_unref (klass); + return children[i]; + } + + g_type_class_unref (klass); + } + + return WP_TYPE_GLOBAL_PROXY; +} + +/* called by the registry when a global appears */ +static void +registry_global (void *data, uint32_t id, uint32_t permissions, + const char *type, uint32_t version, const struct spa_dict *props) +{ + WpRegistry *self = data; + GType gtype = find_proxy_instance_type (type, version); + + wp_debug_object (wp_registry_get_core (self), + "global:%u perm:0x%x type:%s/%u -> %s", + id, permissions, type, version, g_type_name (gtype)); + + wp_registry_prepare_new_global (self, id, permissions, + WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY, gtype, NULL, props, NULL); +} + +/* called by the registry when a global is removed */ +static void +registry_global_remove (void *data, uint32_t id) +{ + WpRegistry *self = data; + WpGlobal *global = NULL; + + if (id < self->globals->len) + global = g_ptr_array_index (self->globals, id); + + /* if not found, look in the tmp_globals, as it may still not be exposed */ + if (!global) { + for (guint i = 0; i < self->tmp_globals->len; i++) { + WpGlobal *g = g_ptr_array_index (self->tmp_globals, i); + if (g->id == id) { + global = g; + break; + } + } + } + + g_return_if_fail (global && + global->flags & WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); + + wp_debug_object (wp_registry_get_core (self), + "global removed:%u type:%s", id, g_type_name (global->type)); + + wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_global, + .global_remove = registry_global_remove, +}; + +void +wp_registry_init (WpRegistry *self) +{ + self->globals = + g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref); + self->tmp_globals = + g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref); + self->objects = g_ptr_array_new_with_free_func (g_object_unref); + self->object_managers = g_ptr_array_new (); + self->features = g_ptr_array_new_with_free_func (g_free); +} + +void +wp_registry_clear (WpRegistry *self) +{ + wp_registry_detach (self); + g_clear_pointer (&self->globals, g_ptr_array_unref); + g_clear_pointer (&self->tmp_globals, g_ptr_array_unref); + g_clear_pointer (&self->features, g_ptr_array_unref); + + /* 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) { + g_autoptr (GObject) object = g_ptr_array_steal_index_fast (objlist, + objlist->len - 1); + wp_registry_notify_rm_object (self, 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); + } + } +} + +void +wp_registry_attach (WpRegistry *self, struct pw_core *pw_core) +{ + self->pw_registry = pw_core_get_registry (pw_core, + PW_VERSION_REGISTRY, 0); + pw_registry_add_listener (self->pw_registry, &self->listener, + ®istry_events, self); +} + +void +wp_registry_detach (WpRegistry *self) +{ + if (self->pw_registry) { + spa_hook_remove (&self->listener); + pw_proxy_destroy ((struct pw_proxy *) self->pw_registry); + self->pw_registry = NULL; + } + + /* remove pipewire globals */ + GPtrArray *objlist = self->globals; + while (objlist && objlist->len > 0) { + g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist, + objlist->len - 1); + + if (!global) + continue; + + if (global->proxy) + wp_registry_notify_rm_object (self, global->proxy); + + /* remove the APPEARS_ON_REGISTRY flag to unref the proxy if it is owned + by the registry; set registry to NULL to avoid further interference */ + global->registry = NULL; + wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); + + /* the registry's ref on global is dropped here; it may still live if + there is a proxy that owns a ref on it, but global->registry is set + to NULL, so there is no further interference */ + } + + /* drop tmp globals as well */ + objlist = self->tmp_globals; + while (objlist && objlist->len > 0) { + g_autoptr (WpGlobal) global = g_ptr_array_steal_index_fast (objlist, + objlist->len - 1); + wp_global_rm_flag (global, WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY); + } +} + +static gboolean +expose_tmp_globals (WpCore *core) +{ + WpRegistry *self = wp_core_get_registry (core); + g_autoptr (GPtrArray) tmp_globals = NULL; + g_autoptr (GPtrArray) object_managers = NULL; + + /* in case the registry was cleared in the meantime... */ + if (G_UNLIKELY (!self->tmp_globals)) + return G_SOURCE_REMOVE; + + /* steal the tmp_globals list and replace it with an empty one */ + tmp_globals = self->tmp_globals; + self->tmp_globals = + g_ptr_array_new_with_free_func ((GDestroyNotify) wp_global_unref); + + wp_debug_object (core, "exposing %u new globals", tmp_globals->len); + + /* traverse in the order that the globals appeared on the registry */ + for (guint i = 0; i < tmp_globals->len; i++) { + WpGlobal *g = g_ptr_array_index (tmp_globals, i); + + /* if global was already removed, drop it */ + if (g->flags == 0 || g->id == SPA_ID_INVALID) + continue; + + /* if old global is owned by proxy, remove it */ + if (self->globals->len > g->id) { + WpGlobal *old_g = g_ptr_array_index (self->globals, g->id); + if (old_g && (old_g->flags & WP_GLOBAL_FLAG_OWNED_BY_PROXY)) + wp_global_rm_flag (old_g, WP_GLOBAL_FLAG_OWNED_BY_PROXY); + } + + g_return_val_if_fail (self->globals->len <= g->id || + g_ptr_array_index (self->globals, g->id) == NULL, G_SOURCE_REMOVE); + + /* set the registry, so that wp_global_rm_flag() can work full-scale */ + g->registry = self; + + /* store it in the globals list */ + if (self->globals->len <= g->id) + g_ptr_array_set_size (self->globals, g->id + 1); + g_ptr_array_index (self->globals, g->id) = wp_global_ref (g); + } + + object_managers = g_ptr_array_copy (self->object_managers, + (GCopyFunc) g_object_ref, NULL); + g_ptr_array_set_free_func (object_managers, g_object_unref); + + /* notify object managers */ + for (guint i = 0; i < object_managers->len; i++) { + WpObjectManager *om = g_ptr_array_index (object_managers, i); + + for (guint i = 0; i < tmp_globals->len; i++) { + WpGlobal *g = g_ptr_array_index (tmp_globals, i); + + /* if global was already removed, drop it */ + if (g->flags == 0 || g->id == SPA_ID_INVALID) + continue; + + wp_object_manager_add_global (om, g); + } + wp_object_manager_maybe_objects_changed (om); + } + + return G_SOURCE_REMOVE; +} + +/* + * \param new_global (out) (transfer full) (optional): the new global + * + * This is normally called up to 2 times in the same sync cycle: + * one from registry_global(), another from the proxy bound event + * Unfortunately the order in which those 2 events happen is specific + * to the implementation of the object, which is why this is implemented + * with a temporary globals list that get exposed later to the object managers + */ +void +wp_registry_prepare_new_global (WpRegistry * self, guint32 id, + guint32 permissions, guint32 flag, GType type, + WpGlobalProxy *proxy, const struct spa_dict *props, + WpGlobal ** new_global) +{ + g_autoptr (WpGlobal) global = NULL; + WpCore *core = wp_registry_get_core (self); + + g_return_if_fail (flag != 0); + + for (guint i = 0; i < self->tmp_globals->len; i++) { + WpGlobal *g = g_ptr_array_index (self->tmp_globals, i); + if (g->id == id) { + global = wp_global_ref (g); + break; + } + } + + wp_debug_object (core, "%s WpGlobal:%u type:%s proxy:%p", + global ? "reuse" : "new", id, g_type_name (type), + (global && global->proxy) ? global->proxy : proxy); + + if (!global) { + global = g_rc_box_new0 (WpGlobal); + global->flags = flag; + global->id = id; + global->type = type; + global->permissions = permissions; + global->properties = props ? + wp_properties_new_copy_dict (props) : wp_properties_new_empty (); + global->proxy = proxy; + g_ptr_array_add (self->tmp_globals, wp_global_ref (global)); + + /* ensure we have 'object.id' so that we can filter by id on object managers */ + wp_properties_setf (global->properties, PW_KEY_OBJECT_ID, "%u", global->id); + + /* schedule exposing when adding the first global */ + if (self->tmp_globals->len == 1) { + wp_core_idle_add_closure (core, NULL, + g_cclosure_new_object (G_CALLBACK (expose_tmp_globals), G_OBJECT (core))); + } + } else { + /* store the most permissive permissions */ + if (permissions > global->permissions) + global->permissions = permissions; + + global->flags |= flag; + + /* store the most deep type (i.e. WpImplNode instead of WpNode), + so that object-manager interests can work more accurately + if the interest is on a specific subclass */ + if (g_type_depth (type) > g_type_depth (global->type)) + global->type = type; + + if (proxy) { + g_return_if_fail (global->proxy == NULL); + global->proxy = proxy; + } + + if (props) + wp_properties_update_from_dict (global->properties, props); + } + + if (new_global) + *new_global = g_steal_pointer (&global); +} + +void +wp_registry_notify_add_object (WpRegistry *self, gpointer object) +{ + for (guint i = 0; i < self->object_managers->len; i++) { + WpObjectManager *om = g_ptr_array_index (self->object_managers, i); + wp_object_manager_add_object (om, object); + wp_object_manager_maybe_objects_changed (om); + } +} + +void +wp_registry_notify_rm_object (WpRegistry *self, gpointer object) +{ + for (guint 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); + wp_object_manager_maybe_objects_changed (om); + } +} + +void +wp_registry_install_object_manager (WpRegistry * self, WpObjectManager * om) +{ + guint i; + + g_object_weak_ref (G_OBJECT (om), object_manager_destroyed, self); + g_ptr_array_add (self->object_managers, om); + + /* 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); + } + for (i = 0; i < self->objects->len; i++) { + GObject *o = g_ptr_array_index (self->objects, i); + wp_object_manager_add_object (om, o); + } + + wp_object_manager_maybe_objects_changed (om); +} + +/* WpGlobal */ + +G_DEFINE_BOXED_TYPE (WpGlobal, wp_global, wp_global_ref, wp_global_unref) + +void +wp_global_rm_flag (WpGlobal *global, guint rm_flag) +{ + WpRegistry *reg = global->registry; + guint32 id = global->id; + + /* no flag to remove */ + if (!(global->flags & rm_flag)) + return; + + wp_trace_boxed (WP_TYPE_GLOBAL, global, + "remove global %u flag 0x%x [flags:0x%x, reg:%p]", + id, rm_flag, global->flags, reg); + + /* global was owned by the proxy; by removing the flag, we clear out + also the proxy pointer, which is presumably no longer valid and we + notify all listeners that the proxy is gone */ + if (rm_flag == WP_GLOBAL_FLAG_OWNED_BY_PROXY) { + global->flags &= ~WP_GLOBAL_FLAG_OWNED_BY_PROXY; + if (reg && global->proxy) { + wp_registry_notify_rm_object (reg, global->proxy); + } + global->proxy = NULL; + } + /* registry removed the global */ + else if (rm_flag == WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY) { + global->flags &= ~WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY; + + /* destroy the proxy if it exists */ + if (global->proxy) { + /* steal the proxy to avoid calling wp_registry_notify_rm_object() + again while removing OWNED_BY_PROXY; + keep a temporary ref so that _deactivate() doesn't crash in case the + pw-proxy-destroyed signal causes external references to be dropped */ + g_autoptr (WpGlobalProxy) proxy = + g_object_ref (g_steal_pointer (&global->proxy)); + + /* notify all listeners that the proxy is gone */ + if (reg) + wp_registry_notify_rm_object (reg, proxy); + + /* remove FEATURE_BOUND to destroy the underlying pw_proxy */ + wp_object_deactivate (WP_OBJECT (proxy), WP_PROXY_FEATURE_BOUND); + + /* stop all in-progress activations */ + wp_object_abort_activation (WP_OBJECT (proxy), "PipeWire proxy removed"); + + /* if the proxy is not owning the global, unref it */ + if (global->flags == 0) + g_object_unref (proxy); + } + + /* It's possible to receive consecutive {add, remove, add} events for the + * same id. Since the WpGlobal might not be destroyed immediately below, + * (e.g. it's in tmp_globals list), we must invalidate the id now, so that + * this WpGlobal is not used in reference to objects added later. + */ + global->id = SPA_ID_INVALID; + wp_properties_setf (global->properties, PW_KEY_OBJECT_ID, NULL); + } + + /* drop the registry's ref on global when it does not appear on the registry anymore */ + if (!(global->flags & WP_GLOBAL_FLAG_APPEARS_ON_REGISTRY) && reg) { + g_clear_pointer (&g_ptr_array_index (reg->globals, id), wp_global_unref); + } +} + +struct pw_proxy * +wp_global_bind (WpGlobal * global) +{ + g_return_val_if_fail (global->proxy, NULL); + g_return_val_if_fail (global->registry, NULL); + + WpProxyClass *klass = WP_PROXY_GET_CLASS (global->proxy); + return pw_registry_bind (global->registry->pw_registry, global->id, + klass->pw_iface_type, klass->pw_iface_version, 0); +} diff --git a/lib/wp/private/registry.h b/lib/wp/private/registry.h index df5b91d0..e7a8f3b3 100644 --- a/lib/wp/private/registry.h +++ b/lib/wp/private/registry.h @@ -46,6 +46,9 @@ void wp_registry_prepare_new_global (WpRegistry * self, guint32 id, void wp_registry_notify_add_object (WpRegistry * self, gpointer object); void wp_registry_notify_rm_object (WpRegistry * self, gpointer object); +void wp_registry_install_object_manager (WpRegistry * self, + WpObjectManager * om); + static inline void wp_registry_mark_feature_provided (WpRegistry * reg, const gchar * feature) {