modules: re-implement the simple policy module using WpPolicy
This commit is contained in:
@@ -41,11 +41,11 @@ shared_library(
|
|||||||
)
|
)
|
||||||
|
|
||||||
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 : true,
|
||||||
install_dir : wireplumber_module_dir,
|
install_dir : wireplumber_module_dir,
|
||||||
dependencies : [wp_dep, pipewire_dep],
|
dependencies : [wp_dep, pipewire_dep],
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
136
modules/module-simple-policy.c
Normal file
136
modules/module-simple-policy.c
Normal file
@@ -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);
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
load-module C libwireplumber-module-pipewire
|
load-module C libwireplumber-module-pipewire
|
||||||
load-module C libwireplumber-module-pw-audio-softdsp-endpoint
|
load-module C libwireplumber-module-pw-audio-softdsp-endpoint
|
||||||
load-module C libwireplumber-module-pw-alsa-udev
|
load-module C libwireplumber-module-pw-alsa-udev
|
||||||
load-module C libwireplumber-module-pw-simple-policy
|
load-module C libwireplumber-module-simple-policy
|
||||||
|
Reference in New Issue
Block a user