diff --git a/modules/meson.build b/modules/meson.build index 606e510ef9c7c4f2b6659dc450c7e54bf48a46f5..6cccd833a88f740053f3944ff67b0a085811436e 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -41,11 +41,11 @@ shared_library( ) shared_library( - 'wireplumber-module-pw-simple-policy', + 'wireplumber-module-simple-policy', [ - 'module-pw-simple-policy.c' + 'module-simple-policy.c' ], - c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-simple-policy"'], + c_args : [common_c_args, '-DG_LOG_DOMAIN="m-simple-policy"'], install : true, install_dir : wireplumber_module_dir, dependencies : [wp_dep, pipewire_dep], diff --git a/modules/module-pw-simple-policy.c b/modules/module-pw-simple-policy.c deleted file mode 100644 index 949824e0cefa25e297affbcdc900d1ead98e8182..0000000000000000000000000000000000000000 --- a/modules/module-pw-simple-policy.c +++ /dev/null @@ -1,224 +0,0 @@ -/* WirePlumber - * - * Copyright © 2019 Collabora Ltd. - * @author Julian Bouzas <julian.bouzas@collabora.com> - * - * SPDX-License-Identifier: MIT - */ - -/** - * module-pw-simple-policy connects the first audio output client endpoint with - * the first audio sink remote endpoint - */ - -#include <wp/wp.h> -#include <pipewire/pipewire.h> - -typedef void (*WpDoneCallback)(gpointer); - -struct impl { - WpCore *wp_core; - - /* Remote */ - struct pw_remote *remote; - struct spa_hook remote_listener; - - /* Core */ - struct pw_core_proxy *core_proxy; - struct spa_hook core_listener; - int core_seq; - WpDoneCallback done_cb; - gpointer done_cb_data; - - /* Endpoints */ - WpEndpoint *ep_client; - WpEndpoint *ep_remote; -}; - -static void -sync_core_with_callabck(struct impl* impl, WpDoneCallback callback, - gpointer data) -{ - /* Set the callback and data */ - impl->done_cb = callback; - impl->done_cb_data = data; - - /* Sync the core */ - impl->core_seq = pw_core_proxy_sync(impl->core_proxy, 0, impl->core_seq); -} - -static WpEndpoint * -endpoint_get_selected (WpCore *core, const char *media_class) -{ - g_autoptr (GPtrArray) ptr_array = NULL; - int i; - - /* Get all the endpoints with the specific media lcass*/ - ptr_array = wp_endpoint_find (core, media_class); - if (!ptr_array) - return NULL; - - /* Find and return the "selected" endpoint */ - /* FIXME: fix the endpoint API, this is terrible */ - for (i = 0; i < ptr_array->len; i++) { - WpEndpoint *ep = g_ptr_array_index (ptr_array, i); - GVariantIter iter; - g_autoptr (GVariant) controls = NULL; - g_autoptr (GVariant) value = NULL; - const gchar *name; - guint id; - - controls = wp_endpoint_list_controls (ep); - g_variant_iter_init (&iter, controls); - while ((value = g_variant_iter_next_value (&iter))) { - if (!g_variant_lookup (value, "name", "&s", &name) - || !g_str_equal (name, "selected")) { - g_variant_unref (value); - continue; - } - g_variant_lookup (value, "id", "u", &id); - g_variant_unref (value); - } - - value = wp_endpoint_get_control_value (ep, id); - if (value && g_variant_get_boolean (value)) - return ep; - } - - /* If not found, return the first endpoint */ - return (ptr_array->len > 1) ? g_ptr_array_index (ptr_array, 0) : NULL; -} - -static void -link_endpoints(gpointer data) -{ - struct impl *impl = data; - WpEndpointLink *ep_link = NULL; - - /* Make sure the endpoints are valid */ - if (!impl->ep_client || !impl->ep_remote) { - g_warning ("Endpoints not valid to link. Skipping...\n"); - return; - } - - /* Link the client with the remote */ - ep_link = wp_endpoint_link_new(impl->wp_core, impl->ep_client, 0, - impl->ep_remote, 0, NULL); - if (!ep_link) { - g_warning ("Could not link endpoints. Skipping...\n"); - return; - } - - g_info ("Endpoints linked successfully\n"); -} - -static void -endpoint_added (WpCore *core, GQuark key, WpEndpoint *ep, struct impl * impl) -{ - const char *media_class = NULL; - - /* Reset endpoints */ - impl->ep_remote = NULL; - impl->ep_client = NULL; - - /* Make sure an endpoint has been added */ - g_return_if_fail (key == WP_GLOBAL_ENDPOINT); - - /* Get the media class */ - media_class = wp_endpoint_get_media_class(ep); - - /* Only process client endpoints */ - if (!g_str_has_prefix(media_class, "Stream")) - return; - - /* TODO: For now we only accept audio output clients */ - if (!g_str_has_prefix(media_class, "Stream/Output/Audio")) - return; - impl->ep_client = ep; - - /* Get the first endpoint with media class Audio/Sink */ - impl->ep_remote = endpoint_get_selected (core, "Audio/Sink"); - if (!impl->ep_remote) { - g_warning ("Could not get an Audio/Sink remote endpoint\n"); - return; - } - - /* Do the linking when core is done */ - sync_core_with_callabck (impl, link_endpoints, impl); -} - -static void core_done(void *data, uint32_t id, int seq) -{ - struct impl * impl = data; - - /* Call the done callback if it exists */ - if (impl->done_cb) - impl->done_cb(impl->done_cb_data); - - impl->done_cb = NULL; - impl->done_cb_data = NULL; -} - -static const struct pw_core_proxy_events core_events = { - PW_VERSION_CORE_EVENTS, - .done = core_done -}; - -static void on_state_changed(void *_data, enum pw_remote_state old, - enum pw_remote_state state, const char *error) -{ - struct impl *impl = _data; - - switch (state) { - case PW_REMOTE_STATE_ERROR: - break; - - case PW_REMOTE_STATE_CONNECTED: - /* Register the core event callbacks */ - impl->core_proxy = pw_remote_get_core_proxy(impl->remote); - pw_core_proxy_add_listener(impl->core_proxy, &impl->core_listener, - &core_events, impl); - break; - - case PW_REMOTE_STATE_UNCONNECTED: - break; - - default: - break; - } -} - -static const struct pw_remote_events remote_events = { - PW_VERSION_REMOTE_EVENTS, - .state_changed = on_state_changed, -}; - -static void -module_destroy (gpointer data) -{ - struct impl *impl = data; - g_slice_free (struct impl, impl); -} - -void -wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) -{ - struct impl *impl = g_new0(struct impl, 1); - - /* Set destroy callback for impl */ - wp_module_set_destroy_callback (module, module_destroy, impl); - - /* Set the core */ - impl->wp_core = core; - - /* Set the core remote */ - impl->remote = wp_core_get_global(core, WP_GLOBAL_PW_REMOTE); - - /* Add a state changed listener */ - pw_remote_add_listener(impl->remote, &impl->remote_listener, &remote_events, - impl); - - /* Register the endpoint added and removed callbacks */ - g_signal_connect (core, "global-added::endpoint", - (GCallback) endpoint_added, impl); -} diff --git a/modules/module-simple-policy.c b/modules/module-simple-policy.c new file mode 100644 index 0000000000000000000000000000000000000000..44032923f5cdfa92d5c695f0cac129b72ca94036 --- /dev/null +++ b/modules/module-simple-policy.c @@ -0,0 +1,136 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wp/wp.h> + +struct _WpSimplePolicy +{ + WpPolicy parent; +}; + +G_DECLARE_FINAL_TYPE (WpSimplePolicy, simple_policy, WP, SIMPLE_POLICY, WpPolicy) +G_DEFINE_TYPE (WpSimplePolicy, simple_policy, WP_TYPE_POLICY) + +static void +simple_policy_init (WpSimplePolicy *self) +{ +} + +static gboolean +simple_policy_handle_endpoint (WpPolicy *policy, WpEndpoint *ep) +{ + const char *media_class = NULL; + GVariantDict d; + g_autoptr (GVariant) props = NULL; + g_autoptr (WpCore) core = NULL; + g_autoptr (WpEndpoint) target = NULL; + g_autoptr (GError) error = NULL; + guint32 stream_id; + + /* TODO: For now we only accept audio output clients */ + media_class = wp_endpoint_get_media_class(ep); + if (!g_str_equal (media_class, "Stream/Output/Audio")) + return FALSE; + + /* Locate the target endpoint */ + g_variant_dict_init (&d, NULL); + g_variant_dict_insert (&d, "action", "s", "link"); + g_variant_dict_insert (&d, "media.class", "s", "Audio/Sink"); + /* TODO: more properties are needed here */ + props = g_variant_dict_end (&d); + + core = wp_policy_get_core (policy); + target = wp_policy_find_endpoint (core, props, &stream_id); + if (!target) { + g_warning ("Could not find an Audio/Sink target endpoint\n"); + /* TODO: we should kill the client, otherwise it's going to hang waiting */ + return FALSE; + } + + /* Link the client with the target */ + if (!wp_endpoint_link_new (core, ep, 0, target, stream_id, &error)) { + g_warning ("Could not link endpoints: %s\n", error->message); + } else { + g_info ("Sucessfully linked '%s' to '%s'\n", wp_endpoint_get_name (ep), + wp_endpoint_get_name (target)); + } + + return TRUE; +} + +static WpEndpoint * +simple_policy_find_endpoint (WpPolicy *policy, GVariant *props, + guint32 *stream_id) +{ + g_autoptr (WpCore) core = NULL; + g_autoptr (GPtrArray) ptr_array = NULL; + const char *media_class = NULL; + WpEndpoint *ep; + int i; + + core = wp_policy_get_core (policy); + + /* 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); + if (!ptr_array) + return NULL; + + /* TODO: for now we statically return the first stream + * we should be looking into the media.role eventually */ + *stream_id = 0; + + /* Find and return the "selected" endpoint */ + /* FIXME: fix the endpoint API, this is terrible */ + for (i = 0; i < ptr_array->len; i++) { + ep = g_ptr_array_index (ptr_array, i); + GVariantIter iter; + g_autoptr (GVariant) controls = NULL; + g_autoptr (GVariant) value = NULL; + const gchar *name; + guint id; + + controls = wp_endpoint_list_controls (ep); + g_variant_iter_init (&iter, controls); + while ((value = g_variant_iter_next_value (&iter))) { + if (!g_variant_lookup (value, "name", "&s", &name) + || !g_str_equal (name, "selected")) { + g_variant_unref (value); + continue; + } + g_variant_lookup (value, "id", "u", &id); + g_variant_unref (value); + } + + value = wp_endpoint_get_control_value (ep, id); + if (value && g_variant_get_boolean (value)) + return g_object_ref (ep); + } + + /* If not found, return the first endpoint */ + ep = (ptr_array->len > 1) ? g_ptr_array_index (ptr_array, 0) : NULL; + return g_object_ref (ep); +} + +static void +simple_policy_class_init (WpSimplePolicyClass *klass) +{ + WpPolicyClass *policy_class = (WpPolicyClass *) klass; + + policy_class->handle_endpoint = simple_policy_handle_endpoint; + policy_class->find_endpoint = simple_policy_find_endpoint; +} + +void +wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) +{ + WpPolicy *p = g_object_new (simple_policy_get_type (), + "rank", WP_POLICY_RANK_UPSTREAM, + NULL); + wp_policy_register (p, core); +} diff --git a/src/wireplumber.conf b/src/wireplumber.conf index 8a35ac0f16c07e40c463930fbf949890321e7b75..9748aa755f9c396643bb2831db3feb46458def47 100644 --- a/src/wireplumber.conf +++ b/src/wireplumber.conf @@ -1,4 +1,4 @@ load-module C libwireplumber-module-pipewire load-module C libwireplumber-module-pw-audio-softdsp-endpoint load-module C libwireplumber-module-pw-alsa-udev -load-module C libwireplumber-module-pw-simple-policy +load-module C libwireplumber-module-simple-policy