/* WirePlumber * * Copyright © 2019 Collabora Ltd. * @author George Kiagiadakis * * SPDX-License-Identifier: MIT */ #include "core.h" #include "wp.h" #include "private.h" #include #include #include /* * Integration between the PipeWire main loop and GMainLoop */ #define WP_LOOP_SOURCE(x) ((WpLoopSource *) x) typedef struct _WpLoopSource WpLoopSource; struct _WpLoopSource { GSource parent; struct pw_loop *loop; }; static gboolean wp_loop_source_dispatch (GSource * s, GSourceFunc callback, gpointer user_data) { int result; pw_loop_enter (WP_LOOP_SOURCE(s)->loop); result = pw_loop_iterate (WP_LOOP_SOURCE(s)->loop, 0); pw_loop_leave (WP_LOOP_SOURCE(s)->loop); if (G_UNLIKELY (result < 0)) g_warning ("pw_loop_iterate failed: %s", spa_strerror (result)); return G_SOURCE_CONTINUE; } static void wp_loop_source_finalize (GSource * s) { pw_loop_destroy (WP_LOOP_SOURCE(s)->loop); } static GSourceFuncs source_funcs = { NULL, NULL, wp_loop_source_dispatch, wp_loop_source_finalize }; static GSource * wp_loop_source_new (void) { GSource *s = g_source_new (&source_funcs, sizeof (WpLoopSource)); WP_LOOP_SOURCE(s)->loop = pw_loop_new (NULL); g_source_add_unix_fd (s, pw_loop_get_fd (WP_LOOP_SOURCE(s)->loop), G_IO_IN | G_IO_ERR | G_IO_HUP); return (GSource *) s; } /** * WpCore */ struct _WpCore { GObject parent; /* main loop integration */ GMainContext *context; /* extra properties */ WpProperties *properties; /* pipewire main objects */ struct pw_context *pw_context; struct pw_core *pw_core; /* pipewire main listeners */ struct spa_hook core_listener; struct spa_hook proxy_core_listener; WpRegistry registry; GHashTable *async_tasks; // }; enum { PROP_0, PROP_CONTEXT, PROP_PROPERTIES, PROP_PW_CONTEXT, PROP_PW_CORE, }; enum { SIGNAL_CONNECTED, SIGNAL_DISCONNECTED, NUM_SIGNALS }; static guint32 signals[NUM_SIGNALS]; G_DEFINE_TYPE (WpCore, wp_core, G_TYPE_OBJECT) static void core_done (void *data, uint32_t id, int seq) { WpCore *self = WP_CORE (data); g_autoptr (GTask) task = NULL; g_hash_table_steal_extended (self->async_tasks, GINT_TO_POINTER (seq), NULL, (gpointer *) &task); if (task) g_task_return_boolean (task, TRUE); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = core_done, }; static void proxy_core_destroy (void *data) { WpCore *self = WP_CORE (data); self->pw_core = NULL; /* Emit the disconnected signal */ g_signal_emit (self, signals[SIGNAL_DISCONNECTED], 0); } static const struct pw_proxy_events proxy_core_events = { PW_VERSION_PROXY_EVENTS, .destroy = proxy_core_destroy, }; static void wp_core_init (WpCore * self) { wp_registry_init (&self->registry); self->async_tasks = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); } static void wp_core_constructed (GObject *object) { WpCore *self = WP_CORE (object); g_autoptr (GSource) source = NULL; struct pw_properties *p = NULL; /* loop */ source = wp_loop_source_new (); g_source_attach (source, self->context); /* context */ p = self->properties ? wp_properties_to_pw_properties (self->properties) : NULL; self->pw_context = pw_context_new (WP_LOOP_SOURCE(source)->loop, p, 0); G_OBJECT_CLASS (wp_core_parent_class)->constructed (object); } static void wp_core_dispose (GObject * obj) { WpCore *self = WP_CORE (obj); wp_registry_clear (&self->registry); G_OBJECT_CLASS (wp_core_parent_class)->dispose (obj); } static void wp_core_finalize (GObject * obj) { WpCore *self = WP_CORE (obj); wp_core_disconnect (self); g_clear_pointer (&self->pw_context, pw_context_destroy); g_clear_pointer (&self->properties, wp_properties_unref); g_clear_pointer (&self->context, g_main_context_unref); g_clear_pointer (&self->async_tasks, g_hash_table_unref); g_debug ("WpCore destroyed"); G_OBJECT_CLASS (wp_core_parent_class)->finalize (obj); } static void wp_core_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { WpCore *self = WP_CORE (object); switch (property_id) { case PROP_CONTEXT: g_value_set_boxed (value, self->context); break; case PROP_PROPERTIES: g_value_set_boxed (value, self->properties); break; case PROP_PW_CONTEXT: g_value_set_pointer (value, self->pw_context); break; case PROP_PW_CORE: g_value_set_pointer (value, self->pw_core); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void wp_core_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { WpCore *self = WP_CORE (object); switch (property_id) { case PROP_CONTEXT: self->context = g_value_dup_boxed (value); break; case PROP_PROPERTIES: self->properties = g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void wp_core_class_init (WpCoreClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; pw_init (NULL, NULL); object_class->constructed = wp_core_constructed; object_class->dispose = wp_core_dispose; object_class->finalize = wp_core_finalize; object_class->get_property = wp_core_get_property; object_class->set_property = wp_core_set_property; g_object_class_install_property (object_class, PROP_CONTEXT, g_param_spec_boxed ("context", "context", "A GMainContext to attach to", G_TYPE_MAIN_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PROPERTIES, g_param_spec_boxed ("properties", "properties", "Extra properties", WP_TYPE_PROPERTIES, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PW_CONTEXT, g_param_spec_pointer ("pw-context", "pw-context", "The pipewire context", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PW_CORE, g_param_spec_pointer ("pw-core", "pw-core", "The pipewire core", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /* Signals */ signals[SIGNAL_CONNECTED] = g_signal_new ("connected", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[SIGNAL_DISCONNECTED] = g_signal_new ("disconnected", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /* ensure WpProxy subclasses are loaded, which is needed to be able to autodetect the GType of proxies created through wp_proxy_new_global() */ g_type_ensure (WP_TYPE_CLIENT); g_type_ensure (WP_TYPE_DEVICE); g_type_ensure (WP_TYPE_ENDPOINT); g_type_ensure (WP_TYPE_LINK); g_type_ensure (WP_TYPE_NODE); g_type_ensure (WP_TYPE_PORT); g_type_ensure (WP_TYPE_SESSION); } WpCore * wp_core_new (GMainContext *context, WpProperties * properties) { return g_object_new (WP_TYPE_CORE, "context", context, "properties", properties, NULL); } GMainContext * wp_core_get_context (WpCore * self) { g_return_val_if_fail (WP_IS_CORE (self), NULL); return self->context; } struct pw_context * wp_core_get_pw_context (WpCore * self) { g_return_val_if_fail (WP_IS_CORE (self), NULL); return self->pw_context; } struct pw_core * wp_core_get_pw_core (WpCore * self) { g_return_val_if_fail (WP_IS_CORE (self), NULL); return self->pw_core; } gboolean wp_core_connect (WpCore *self) { struct pw_properties *p = NULL; g_return_val_if_fail (WP_IS_CORE (self), FALSE); /* Don't do anything if core is already connected */ if (self->pw_core) return TRUE; /* Connect */ p = self->properties ? wp_properties_to_pw_properties (self->properties) : NULL; self->pw_core = pw_context_connect (self->pw_context, p, 0); if (!self->pw_core) return FALSE; /* Add the core listeners */ pw_core_add_listener (self->pw_core, &self->core_listener, &core_events, self); pw_proxy_add_listener((struct pw_proxy*)self->pw_core, &self->proxy_core_listener, &proxy_core_events, self); /* Add the registry listener */ wp_registry_attach (&self->registry, self->pw_core); /* Emit the connected signal */ g_signal_emit (self, signals[SIGNAL_CONNECTED], 0); return TRUE; } void wp_core_disconnect (WpCore *self) { wp_registry_detach (&self->registry); g_clear_pointer (&self->pw_core, pw_core_disconnect); /* Emit the disconnected signal */ g_signal_emit (self, signals[SIGNAL_DISCONNECTED], 0); } gboolean wp_core_is_connected (WpCore * self) { g_return_val_if_fail (WP_IS_CORE (self), FALSE); return self->pw_core != NULL; } guint wp_core_idle_add (WpCore * self, GSourceFunc function, gpointer data, GDestroyNotify destroy) { g_autoptr (GSource) source = NULL; g_return_val_if_fail (WP_IS_CORE (self), 0); source = g_idle_source_new (); g_source_set_callback (source, function, data, destroy); g_source_attach (source, self->context); return g_source_get_id (source); } gboolean wp_core_sync (WpCore * self, GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr (GTask) task = NULL; int seq; g_return_val_if_fail (WP_IS_CORE (self), FALSE); task = g_task_new (self, cancellable, callback, user_data); if (G_UNLIKELY (!self->pw_core)) { g_warn_if_reached (); g_task_return_new_error (task, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "No pipewire core"); return FALSE; } seq = pw_core_sync (self->pw_core, 0, 0); if (G_UNLIKELY (seq < 0)) { g_task_return_new_error (task, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED, "pw_core_sync failed: %s", g_strerror (-seq)); return FALSE; } g_hash_table_insert (self->async_tasks, GINT_TO_POINTER (seq), g_steal_pointer (&task)); return TRUE; } gboolean wp_core_sync_finish (WpCore * self, GAsyncResult * res, GError ** error) { g_return_val_if_fail (WP_IS_CORE (self), FALSE); g_return_val_if_fail (g_task_is_valid (res, self), FALSE); return g_task_propagate_boolean (G_TASK (res), error); } WpRegistry * wp_core_get_registry (WpCore * self) { return &self->registry; } WpCore * wp_registry_get_core (WpRegistry * self) { return SPA_CONTAINER_OF (self, WpCore, registry); }