diff --git a/lib/wp/core.c b/lib/wp/core.c index b6b2d546..98b9bcc6 100644 --- a/lib/wp/core.c +++ b/lib/wp/core.c @@ -220,3 +220,4 @@ 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) +G_DEFINE_QUARK (proxy, wp_global_proxy) diff --git a/lib/wp/core.h b/lib/wp/core.h index e9b331f6..23aac5b8 100644 --- a/lib/wp/core.h +++ b/lib/wp/core.h @@ -60,6 +60,9 @@ GQuark wp_global_module_quark (void); #define WP_GLOBAL_POLICY_MANAGER (wp_global_policy_manager_quark ()) GQuark wp_global_policy_manager_quark (void); +#define WP_GLOBAL_PROXY (wp_global_proxy_quark ()) +GQuark wp_global_proxy_quark (void); + G_END_DECLS #endif diff --git a/lib/wp/meson.build b/lib/wp/meson.build index beadafac..21ef08f7 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -5,6 +5,9 @@ wp_lib_sources = [ 'factory.c', 'module.c', 'policy.c', + 'proxy.c', + 'proxy-node.c', + 'proxy-port.c', ] wp_lib_headers = [ @@ -14,6 +17,9 @@ wp_lib_headers = [ 'factory.h', 'module.h', 'policy.h', + 'proxy.h', + 'proxy-node.h', + 'proxy-port.h', 'wp.h', ] @@ -33,7 +39,7 @@ wp_lib = library('wireplumber-' + wireplumber_api_version, ], install: true, include_directories: wp_lib_include_dir, - dependencies : [gobject_dep, gmodule_dep], + dependencies : [gobject_dep, gmodule_dep, gio_dep, pipewire_dep], soversion: wireplumber_so_version, version: meson.project_version(), ) diff --git a/lib/wp/proxy-node.c b/lib/wp/proxy-node.c new file mode 100644 index 00000000..766e5c2b --- /dev/null +++ b/lib/wp/proxy-node.c @@ -0,0 +1,128 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include "proxy-node.h" +#include + +struct _WpProxyNode +{ + WpProxy parent; + + /* The node proxy listener */ + struct spa_hook listener; + + /* The node info */ + struct pw_node_info *info; +}; + +static GAsyncInitableIface *proxy_node_parent_interface = NULL; +static void wp_proxy_node_async_initable_init (gpointer iface, + gpointer iface_data); + +G_DEFINE_TYPE_WITH_CODE (WpProxyNode, wp_proxy_node, WP_TYPE_PROXY, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + wp_proxy_node_async_initable_init)) + +static void +node_event_info(void *data, const struct pw_node_info *info) +{ + WpProxyNode *self = data; + + /* Update the node info */ + self->info = pw_node_info_update(self->info, info); +} + +static const struct pw_node_proxy_events node_events = { + PW_VERSION_NODE_PROXY_EVENTS, + .info = node_event_info, +}; + +static void +wp_proxy_node_finalize (GObject * object) +{ + WpProxyNode *self = WP_PROXY_NODE(object); + + /* Remove the listener */ + spa_hook_remove (&self->listener); + + /* Clear the info */ + if (self->info) { + pw_node_info_free(self->info); + self->info = NULL; + } + + G_OBJECT_CLASS (wp_proxy_node_parent_class)->finalize (object); +} + +static void +wp_proxy_node_init_async (GAsyncInitable *initable, int io_priority, + GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) +{ + WpProxyNode *self = WP_PROXY_NODE(initable); + WpProxy *wp_proxy = WP_PROXY(initable); + struct pw_node_proxy *proxy = NULL; + + /* Get the proxy from the base class */ + proxy = wp_proxy_get_pw_proxy(wp_proxy); + + /* Add the node proxy listener */ + pw_node_proxy_add_listener(proxy, &self->listener, &node_events, self); + + /* Call the parent interface */ + proxy_node_parent_interface->init_async (initable, io_priority, cancellable, + callback, data); +} + +static void +wp_proxy_node_async_initable_init (gpointer iface, gpointer iface_data) +{ + GAsyncInitableIface *ai_iface = iface; + + /* Set the parent interface */ + proxy_node_parent_interface = g_type_interface_peek_parent (iface); + + /* Only set the init_async */ + ai_iface->init_async = wp_proxy_node_init_async; +} + +static void +wp_proxy_node_init (WpProxyNode * self) +{ +} + +static void +wp_proxy_node_class_init (WpProxyNodeClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->finalize = wp_proxy_node_finalize; +} + +void +wp_proxy_node_new (WpCore *core, gpointer proxy, + GAsyncReadyCallback callback, gpointer user_data) +{ + g_async_initable_new_async ( + WP_TYPE_PROXY_NODE, G_PRIORITY_DEFAULT, NULL, callback, user_data, + "core", (gpointer) core, + "pw-proxy", proxy, + NULL); +} + +WpProxyNode * +wp_proxy_node_new_finish(GObject *initable, GAsyncResult *res, GError **error) +{ + GAsyncInitable *ai = G_ASYNC_INITABLE(initable); + return WP_PROXY_NODE(g_async_initable_new_finish(ai, res, error)); +} + +const struct pw_node_info * +wp_proxy_node_get_info (WpProxyNode * self) +{ + return self->info; +} diff --git a/lib/wp/proxy-node.h b/lib/wp/proxy-node.h new file mode 100644 index 00000000..8ba4e69b --- /dev/null +++ b/lib/wp/proxy-node.h @@ -0,0 +1,29 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_PROXY_NODE_H__ +#define __WIREPLUMBER_PROXY_NODE_H__ + +#include "core.h" +#include "proxy.h" + +G_BEGIN_DECLS + +#define WP_TYPE_PROXY_NODE (wp_proxy_node_get_type ()) +G_DECLARE_FINAL_TYPE (WpProxyNode, wp_proxy_node, WP, PROXY_NODE, WpProxy) + +void wp_proxy_node_new (WpCore *core, gpointer proxy, + GAsyncReadyCallback callback, gpointer user_data); +WpProxyNode *wp_proxy_node_new_finish(GObject *initable, GAsyncResult *res, + GError **error); + +const struct pw_node_info *wp_proxy_node_get_info (WpProxyNode * self); + +G_END_DECLS + +#endif diff --git a/lib/wp/proxy-port.c b/lib/wp/proxy-port.c new file mode 100644 index 00000000..ec495646 --- /dev/null +++ b/lib/wp/proxy-port.c @@ -0,0 +1,168 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include "proxy-port.h" +#include +#include + +struct _WpProxyPort +{ + WpProxy parent; + + /* The port proxy listener */ + struct spa_hook listener; + + /* The port info */ + struct pw_port_info *info; + + /* The port format */ + uint32_t media_type; + uint32_t media_subtype; + struct spa_audio_info_raw format; +}; + +static GAsyncInitableIface *proxy_port_parent_interface = NULL; +static void wp_proxy_port_async_initable_init (gpointer iface, + gpointer iface_data); + +G_DEFINE_TYPE_WITH_CODE (WpProxyPort, wp_proxy_port, WP_TYPE_PROXY, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + wp_proxy_port_async_initable_init)) + +static void +port_event_info(void *data, const struct pw_port_info *info) +{ + WpProxyPort *self = data; + + /* Update the port info */ + self->info = pw_port_info_update(self->info, info); +} + +static void +port_event_param(void *data, int seq, uint32_t id, uint32_t index, + uint32_t next, const struct spa_pod *param) +{ + WpProxyPort *self = data; + + /* Only handle EnumFormat */ + if (id != SPA_PARAM_EnumFormat) + return; + + /* Parse the format */ + spa_format_parse(param, &self->media_type, &self->media_subtype); + + /* Only handle raw audio formats for now */ + if (self->media_type != SPA_MEDIA_TYPE_audio || + self->media_subtype != SPA_MEDIA_SUBTYPE_raw) + return; + + /* Parse the raw audio format */ + spa_pod_fixate((struct spa_pod*)param); + spa_format_audio_raw_parse(param, &self->format); +} + +static const struct pw_port_proxy_events port_events = { + PW_VERSION_PORT_PROXY_EVENTS, + .info = port_event_info, + .param = port_event_param, +}; + +static void +wp_proxy_port_finalize (GObject * object) +{ + WpProxyPort *self = WP_PROXY_PORT(object); + + /* Remove the listener */ + spa_hook_remove (&self->listener); + + /* Clear the indo */ + if (self->info) { + pw_port_info_free(self->info); + self->info = NULL; + } + + G_OBJECT_CLASS (wp_proxy_port_parent_class)->finalize (object); +} + +static void +wp_proxy_port_init_async (GAsyncInitable *initable, int io_priority, + GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) +{ + WpProxyPort *self = WP_PROXY_PORT(initable); + WpProxy *wp_proxy = WP_PROXY(initable); + struct pw_port_proxy *proxy = NULL; + + /* Get the proxy from the base class */ + proxy = wp_proxy_get_pw_proxy(wp_proxy); + + /* Add the port proxy listener */ + pw_port_proxy_add_listener(proxy, &self->listener, &port_events, self); + + /* Emit the EnumFormat param */ + pw_port_proxy_enum_params((struct pw_port_proxy*)proxy, 0, + SPA_PARAM_EnumFormat, 0, -1, NULL); + + /* Call the parent interface */ + proxy_port_parent_interface->init_async (initable, io_priority, cancellable, + callback, data); +} + +static void +wp_proxy_port_async_initable_init (gpointer iface, gpointer iface_data) +{ + GAsyncInitableIface *ai_iface = iface; + + /* Set the parent interface */ + proxy_port_parent_interface = g_type_interface_peek_parent (iface); + + /* Only set the init_async */ + ai_iface->init_async = wp_proxy_port_init_async; +} + +static void +wp_proxy_port_init (WpProxyPort * self) +{ +} + +static void +wp_proxy_port_class_init (WpProxyPortClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->finalize = wp_proxy_port_finalize; +} + +void +wp_proxy_port_new (WpCore *core, gpointer proxy, + GAsyncReadyCallback callback, gpointer user_data) +{ + g_async_initable_new_async ( + WP_TYPE_PROXY_PORT, G_PRIORITY_DEFAULT, NULL, callback, user_data, + "core", (gpointer) core, + "pw-proxy", proxy, + NULL); +} + +WpProxyPort * +wp_proxy_port_new_finish(GObject *initable, GAsyncResult *res, GError **error) +{ + GAsyncInitable *ai = G_ASYNC_INITABLE(initable); + return WP_PROXY_PORT(g_async_initable_new_finish(ai, res, error)); +} + +const struct pw_port_info * +wp_proxy_port_get_info (WpProxyPort * self) +{ + return self->info; +} + +const struct spa_audio_info_raw * +wp_proxy_port_get_format (WpProxyPort * self) +{ + return &self->format; +} diff --git a/lib/wp/proxy-port.h b/lib/wp/proxy-port.h new file mode 100644 index 00000000..0c38ecbb --- /dev/null +++ b/lib/wp/proxy-port.h @@ -0,0 +1,30 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_PROXY_PORT_H__ +#define __WIREPLUMBER_PROXY_PORT_H__ + +#include "core.h" +#include "proxy.h" + +G_BEGIN_DECLS + +#define WP_TYPE_PROXY_PORT (wp_proxy_port_get_type ()) +G_DECLARE_FINAL_TYPE (WpProxyPort, wp_proxy_port, WP, PROXY_PORT, WpProxy) + +void wp_proxy_port_new (WpCore *core, gpointer proxy, + GAsyncReadyCallback callback, gpointer user_data); +WpProxyPort *wp_proxy_port_new_finish(GObject *initable, GAsyncResult *res, + GError **error); + +const struct pw_port_info *wp_proxy_port_get_info (WpProxyPort * self); +const struct spa_audio_info_raw *wp_proxy_port_get_format (WpProxyPort * self); + +G_END_DECLS + +#endif diff --git a/lib/wp/proxy.c b/lib/wp/proxy.c new file mode 100644 index 00000000..feb26127 --- /dev/null +++ b/lib/wp/proxy.c @@ -0,0 +1,220 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include "proxy.h" + +typedef struct _WpProxyPrivate WpProxyPrivate; +struct _WpProxyPrivate +{ + /* The core */ + WpCore *core; + + /* The proxy */ + struct pw_proxy *proxy; + + /* The proxy listener */ + struct spa_hook listener; + + /* The done info */ + GTask *done_task; +}; + +enum { + PROP_0, + PROP_CORE, + PROP_PROXY, +}; + +static void wp_proxy_async_initable_init (gpointer iface, gpointer iface_data); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpProxy, wp_proxy, G_TYPE_OBJECT, + G_ADD_PRIVATE (WpProxy) + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, wp_proxy_async_initable_init)) + +static void +proxy_event_destroy (void *data) +{ + WpProxyPrivate *self = wp_proxy_get_instance_private (WP_PROXY(data)); + + /* Set the proxy to NULL */ + self->proxy = NULL; + + /* Remove the proxy from core */ + wp_core_remove_global (self->core, WP_GLOBAL_PROXY, data); +} + +static void +proxy_event_done (void *data, int seq) +{ + WpProxyPrivate *self = wp_proxy_get_instance_private (WP_PROXY(data)); + + /* Make sure the task is valid */ + if (!self->done_task) + return; + + /* Execute the task */ + g_task_return_boolean (self->done_task, TRUE); + + /* Clean up */ + g_object_unref (self->done_task); + self->done_task = NULL; +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = proxy_event_destroy, + .done = proxy_event_done, +}; + +static void +wp_proxy_finalize (GObject * object) +{ + WpProxyPrivate *self = wp_proxy_get_instance_private (WP_PROXY(object)); + + /* Remove the listener */ + spa_hook_remove (&self->listener); + + /* Destroy the proxy */ + if (self->proxy) { + pw_proxy_destroy (self->proxy); + self->proxy = NULL; + } + + G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object); +} + +static void +wp_proxy_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + WpProxyPrivate *self = wp_proxy_get_instance_private (WP_PROXY(object)); + + switch (property_id) { + case PROP_CORE: + self->core = g_value_get_pointer (value); + break; + case PROP_PROXY: + self->proxy = g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +wp_proxy_get_property (GObject * object, guint property_id, GValue * value, + GParamSpec * pspec) +{ + WpProxyPrivate *self = wp_proxy_get_instance_private (WP_PROXY(object)); + + switch (property_id) { + case PROP_CORE: + g_value_set_pointer (value, self->core); + break; + case PROP_PROXY: + g_value_set_pointer (value, self->proxy); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +wp_proxy_init_async (GAsyncInitable *initable, int io_priority, + GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) +{ + WpProxyPrivate *self = wp_proxy_get_instance_private (WP_PROXY(initable)); + + /* Create the async task */ + self->done_task = g_task_new (initable, cancellable, callback, data); + + /* Add the event listener */ + pw_proxy_add_listener (self->proxy, &self->listener, &proxy_events, initable); + + /* Trigger the done callback */ + pw_proxy_sync(self->proxy, 0); +} + +static gboolean +wp_proxy_init_finish (GAsyncInitable *initable, GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, initable), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +wp_proxy_async_initable_init (gpointer iface, gpointer iface_data) +{ + GAsyncInitableIface *ai_iface = iface; + + ai_iface->init_async = wp_proxy_init_async; + ai_iface->init_finish = wp_proxy_init_finish; +} + +static void +wp_proxy_init (WpProxy * self) +{ +} + +static void +wp_proxy_class_init (WpProxyClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->finalize = wp_proxy_finalize; + object_class->get_property = wp_proxy_get_property; + object_class->set_property = wp_proxy_set_property; + + /* Install the properties */ + g_object_class_install_property (object_class, PROP_CORE, + g_param_spec_pointer ("core", "core", "The wireplumber core", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_PROXY, + g_param_spec_pointer ("pw-proxy", "pw-proxy", "The pipewire proxy", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +void +wp_proxy_register(WpProxy * self) +{ + WpProxyPrivate *priv; + + g_return_if_fail (WP_IS_PROXY (self)); + + priv = wp_proxy_get_instance_private (self); + wp_core_register_global (priv->core, WP_GLOBAL_PROXY, g_object_ref (self), + g_object_unref); +} + +WpCore * +wp_proxy_get_core (WpProxy * self) +{ + WpProxyPrivate *priv; + + g_return_val_if_fail (WP_IS_PROXY (self), NULL); + + priv = wp_proxy_get_instance_private (self); + return priv->core; +} + +gpointer +wp_proxy_get_pw_proxy (WpProxy * self) +{ + WpProxyPrivate *priv; + + g_return_val_if_fail (WP_IS_PROXY (self), NULL); + + priv = wp_proxy_get_instance_private (self); + return priv->proxy; +} diff --git a/lib/wp/proxy.h b/lib/wp/proxy.h new file mode 100644 index 00000000..24bc0dda --- /dev/null +++ b/lib/wp/proxy.h @@ -0,0 +1,33 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_PROXY_H__ +#define __WIREPLUMBER_PROXY_H__ + +#include + +#include "core.h" + +G_BEGIN_DECLS + +#define WP_TYPE_PROXY (wp_proxy_get_type ()) +G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, GObject) + +/* The proxy base class */ +struct _WpProxyClass +{ + GObjectClass parent_class; +}; + +void wp_proxy_register (WpProxy * self); +WpCore *wp_proxy_get_core (WpProxy * self); +gpointer wp_proxy_get_pw_proxy (WpProxy * self); + +G_END_DECLS + +#endif diff --git a/lib/wp/wp.h b/lib/wp/wp.h index aa8906df..9d9d1d13 100644 --- a/lib/wp/wp.h +++ b/lib/wp/wp.h @@ -12,3 +12,6 @@ #include "factory.h" #include "module.h" #include "policy.h" +#include "proxy.h" +#include "proxy-node.h" +#include "proxy-port.h"