
There are 3 kinds of WpProxy objects: * the ones that are created as a result of binding a global from the registry * the ones that are created as a result of calling into a remote factory (wp_node_new_from_factory, etc...) * the ones that are a local implementation of an object (WpImplNode, etc...) and are exported Previously the object manager was only able to track the first kind. With these changes we can now also have globals associated with WpProxies that were created earlier (and caused the creation of the global). This saves some resources and reduces round-trips (in case client code wants to change properties of an object that is locally implemented, it shouldn't need to do a round-trip through the server)
323 lines
9.3 KiB
C
323 lines
9.3 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2019 Collabora Ltd.
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <wp/wp.h>
|
|
#include <pipewire/pipewire.h>
|
|
|
|
static GOptionEntry entries[] =
|
|
{
|
|
{ NULL }
|
|
};
|
|
|
|
struct WpCliData
|
|
{
|
|
WpCore *core;
|
|
GMainLoop *loop;
|
|
|
|
union {
|
|
struct {
|
|
guint32 id;
|
|
} set_default;
|
|
struct {
|
|
guint32 id;
|
|
gfloat volume;
|
|
} set_volume;
|
|
} params;
|
|
};
|
|
|
|
static void
|
|
async_quit (WpCore *core, GAsyncResult *res, struct WpCliData * d)
|
|
{
|
|
g_print ("Success\n");
|
|
g_main_loop_quit (d->loop);
|
|
}
|
|
|
|
static void
|
|
print_dev_endpoint (WpEndpoint *ep, WpSession *session, WpDefaultEndpointType type)
|
|
{
|
|
guint32 id = wp_proxy_get_bound_id (WP_PROXY (ep));
|
|
gboolean is_default = (session && type != 0 &&
|
|
wp_session_get_default_endpoint (session, type) == id);
|
|
gfloat volume = 0.0;
|
|
gboolean mute = FALSE;
|
|
|
|
wp_endpoint_get_control_float (ep, WP_ENDPOINT_CONTROL_VOLUME, &volume);
|
|
wp_endpoint_get_control_boolean (ep, WP_ENDPOINT_CONTROL_MUTE, &mute);
|
|
|
|
g_print (" %c %4u. %60s\tvol: %.2f %s\n", is_default ? '*' : ' ', id,
|
|
wp_endpoint_get_name (ep), volume, mute ? "MUTE" : "");
|
|
}
|
|
|
|
static void
|
|
print_client_endpoint (WpEndpoint *ep)
|
|
{
|
|
guint32 id = wp_proxy_get_bound_id (WP_PROXY (ep));
|
|
g_print (" %4u. %s (%s)\n", id, wp_endpoint_get_name (ep),
|
|
wp_endpoint_get_media_class (ep));
|
|
}
|
|
|
|
static void
|
|
list_endpoints (WpObjectManager * om, struct WpCliData * d)
|
|
{
|
|
g_autoptr (GPtrArray) arr = NULL;
|
|
g_autoptr (WpSession) session = NULL;
|
|
guint i;
|
|
|
|
arr = wp_object_manager_get_objects (om, WP_TYPE_SESSION);
|
|
if (arr->len > 0)
|
|
session = WP_SESSION (g_object_ref (g_ptr_array_index (arr, 0)));
|
|
g_clear_pointer (&arr, g_ptr_array_unref);
|
|
|
|
arr = wp_object_manager_get_objects (om, WP_TYPE_ENDPOINT);
|
|
|
|
g_print ("Audio capture devices:\n");
|
|
for (i = 0; i < arr->len; i++) {
|
|
WpEndpoint *ep = g_ptr_array_index (arr, i);
|
|
if (g_strcmp0 (wp_endpoint_get_media_class (ep), "Audio/Source") == 0)
|
|
print_dev_endpoint (ep, session, WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SOURCE);
|
|
}
|
|
|
|
g_print ("\nAudio playback devices:\n");
|
|
for (i = 0; i < arr->len; i++) {
|
|
WpEndpoint *ep = g_ptr_array_index (arr, i);
|
|
if (g_strcmp0 (wp_endpoint_get_media_class (ep), "Audio/Sink") == 0)
|
|
print_dev_endpoint (ep, session, WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SINK);
|
|
}
|
|
|
|
g_print ("\nClient streams:\n");
|
|
for (i = 0; i < arr->len; i++) {
|
|
WpEndpoint *ep = g_ptr_array_index (arr, i);
|
|
if (g_str_has_suffix (wp_endpoint_get_media_class (ep), "/Audio"))
|
|
print_client_endpoint (ep);
|
|
}
|
|
|
|
g_main_loop_quit (d->loop);
|
|
}
|
|
|
|
static void
|
|
set_default (WpObjectManager * om, struct WpCliData * d)
|
|
{
|
|
g_autoptr (GPtrArray) arr = NULL;
|
|
g_autoptr (WpSession) session = NULL;
|
|
guint i;
|
|
|
|
arr = wp_object_manager_get_objects (om, WP_TYPE_SESSION);
|
|
if (arr->len > 0)
|
|
session = WP_SESSION (g_object_ref (g_ptr_array_index (arr, 0)));
|
|
g_clear_pointer (&arr, g_ptr_array_unref);
|
|
|
|
if (!session) {
|
|
g_print ("No Session object - changing the default endpoint is not supported\n");
|
|
g_main_loop_quit (d->loop);
|
|
return;
|
|
}
|
|
|
|
arr = wp_object_manager_get_objects (om, WP_TYPE_ENDPOINT);
|
|
|
|
for (i = 0; i < arr->len; i++) {
|
|
WpEndpoint *ep = g_ptr_array_index (arr, i);
|
|
guint32 id = wp_proxy_get_bound_id (WP_PROXY (ep));
|
|
|
|
if (id == d->params.set_default.id) {
|
|
WpDefaultEndpointType type;
|
|
if (g_strcmp0 (wp_endpoint_get_media_class (ep), "Audio/Sink") == 0)
|
|
type = WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SINK;
|
|
else if (g_strcmp0 (wp_endpoint_get_media_class (ep), "Audio/Source") == 0)
|
|
type = WP_DEFAULT_ENDPOINT_TYPE_AUDIO_SOURCE;
|
|
else {
|
|
g_print ("%u: not a device endpoint\n", id);
|
|
g_main_loop_quit (d->loop);
|
|
return;
|
|
}
|
|
|
|
wp_session_set_default_endpoint (session, type, id);
|
|
wp_core_sync (d->core, NULL, (GAsyncReadyCallback) async_quit, d);
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_print ("%u: not an endpoint\n", d->params.set_default.id);
|
|
g_main_loop_quit (d->loop);
|
|
}
|
|
|
|
static void
|
|
set_volume (WpObjectManager * om, struct WpCliData * d)
|
|
{
|
|
g_autoptr (GPtrArray) arr = NULL;
|
|
guint i;
|
|
|
|
arr = wp_object_manager_get_objects (om, WP_TYPE_ENDPOINT);
|
|
|
|
for (i = 0; i < arr->len; i++) {
|
|
WpEndpoint *ep = g_ptr_array_index (arr, i);
|
|
guint32 id = wp_proxy_get_bound_id (WP_PROXY (ep));
|
|
|
|
if (id == d->params.set_volume.id) {
|
|
wp_endpoint_set_control_float (ep, WP_ENDPOINT_CONTROL_VOLUME,
|
|
d->params.set_volume.volume);
|
|
wp_core_sync (d->core, NULL, (GAsyncReadyCallback) async_quit, d);
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_print ("%u: not an endpoint\n", d->params.set_default.id);
|
|
g_main_loop_quit (d->loop);
|
|
}
|
|
|
|
static void
|
|
device_node_props (WpObjectManager * om, struct WpCliData * d)
|
|
{
|
|
g_autoptr (GPtrArray) arr = NULL;
|
|
guint i;
|
|
const struct spa_dict * dict;
|
|
const struct spa_dict_item *item;
|
|
|
|
arr = wp_object_manager_get_objects (om, WP_TYPE_NODE);
|
|
|
|
g_print ("Capture device nodes:\n");
|
|
|
|
for (i = 0; i < arr->len; i++) {
|
|
WpProxy *node = g_ptr_array_index (arr, i);
|
|
g_autoptr (WpProperties) props = wp_proxy_get_properties (node);
|
|
|
|
if (g_strcmp0 (wp_properties_get (props, "media.class"), "Audio/Source") != 0)
|
|
continue;
|
|
|
|
g_print (" node id: %u\n", wp_proxy_get_bound_id (node));
|
|
|
|
dict = wp_properties_peek_dict (props);
|
|
spa_dict_for_each (item, dict) {
|
|
g_print (" %s = \"%s\"\n", item->key, item->value);
|
|
}
|
|
|
|
g_print ("\n");
|
|
}
|
|
|
|
g_print ("Playback device nodes:\n");
|
|
|
|
for (i = 0; i < arr->len; i++) {
|
|
WpProxy *node = g_ptr_array_index (arr, i);
|
|
g_autoptr (WpProperties) props = wp_proxy_get_properties (node);
|
|
|
|
if (g_strcmp0 (wp_properties_get (props, "media.class"), "Audio/Sink") != 0)
|
|
continue;
|
|
|
|
g_print (" node id: %u\n", wp_proxy_get_bound_id (node));
|
|
|
|
dict = wp_properties_peek_dict (props);
|
|
spa_dict_for_each (item, dict) {
|
|
g_print (" %s = \"%s\"\n", item->key, item->value);
|
|
}
|
|
|
|
g_print ("\n");
|
|
}
|
|
|
|
g_main_loop_quit (d->loop);
|
|
}
|
|
|
|
static void
|
|
on_disconnected (WpCore *core, struct WpCliData * d)
|
|
{
|
|
g_main_loop_quit (d->loop);
|
|
}
|
|
|
|
static const gchar * const usage_string =
|
|
"Operations:\n"
|
|
" ls-endpoints\t\tLists all endpoints\n"
|
|
" set-default [id]\tSets [id] to be the default device endpoint of its kind (capture/playback)\n"
|
|
" set-volume [id] [vol]\tSets the volume of [id] to [vol] (floating point, 1.0 is 100%%)\n"
|
|
" device-node-props\tShows device node properties\n"
|
|
"";
|
|
|
|
gint
|
|
main (gint argc, gchar **argv)
|
|
{
|
|
struct WpCliData data = {0};
|
|
g_autoptr (GOptionContext) context = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
g_autoptr (WpCore) core = NULL;
|
|
g_autoptr (WpObjectManager) om = NULL;
|
|
g_autoptr (GMainLoop) loop = NULL;
|
|
|
|
context = g_option_context_new ("- PipeWire Session/Policy Manager Helper CLI");
|
|
g_option_context_add_main_entries (context, entries, NULL);
|
|
g_option_context_set_description (context, usage_string);
|
|
if (!g_option_context_parse (context, &argc, &argv, &error)) {
|
|
return 1;
|
|
}
|
|
|
|
data.loop = loop = g_main_loop_new (NULL, FALSE);
|
|
data.core = core = wp_core_new (NULL, NULL);
|
|
g_signal_connect (core, "disconnected", (GCallback) on_disconnected, &data);
|
|
|
|
om = wp_object_manager_new ();
|
|
|
|
if (argc == 2 && !g_strcmp0 (argv[1], "ls-endpoints")) {
|
|
wp_object_manager_add_interest (om, WP_TYPE_ENDPOINT,
|
|
NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
|
|
WP_ENDPOINT_FEATURE_CONTROLS);
|
|
wp_object_manager_add_interest (om, WP_TYPE_SESSION,
|
|
NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
|
|
WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
|
|
g_signal_connect (om, "objects-changed", (GCallback) list_endpoints, &data);
|
|
}
|
|
|
|
else if (argc == 3 && !g_strcmp0 (argv[1], "set-default")) {
|
|
long id = strtol (argv[2], NULL, 10);
|
|
if (id == 0) {
|
|
g_print ("%s: not a valid id\n", argv[2]);
|
|
return 1;
|
|
}
|
|
|
|
wp_object_manager_add_interest (om, WP_TYPE_ENDPOINT,
|
|
NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND);
|
|
wp_object_manager_add_interest (om, WP_TYPE_SESSION,
|
|
NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
|
|
WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
|
|
|
|
data.params.set_default.id = id;
|
|
g_signal_connect (om, "objects-changed", (GCallback) set_default, &data);
|
|
}
|
|
|
|
else if (argc == 4 && !g_strcmp0 (argv[1], "set-volume")) {
|
|
long id = strtol (argv[2], NULL, 10);
|
|
float volume = strtof (argv[3], NULL);
|
|
if (id == 0) {
|
|
g_print ("%s: not a valid id\n", argv[2]);
|
|
return 1;
|
|
}
|
|
|
|
wp_object_manager_add_interest (om, WP_TYPE_ENDPOINT,
|
|
NULL, WP_PROXY_FEATURE_INFO | WP_PROXY_FEATURE_BOUND |
|
|
WP_ENDPOINT_FEATURE_CONTROLS);
|
|
|
|
data.params.set_volume.id = id;
|
|
data.params.set_volume.volume = volume;
|
|
g_signal_connect (om, "objects-changed", (GCallback) set_volume, &data);
|
|
}
|
|
|
|
else if (argc == 2 && !g_strcmp0 (argv[1], "device-node-props")) {
|
|
wp_object_manager_add_interest (om, WP_TYPE_NODE, NULL,
|
|
WP_PROXY_FEATURE_INFO);
|
|
g_signal_connect (om, "objects-changed", (GCallback) device_node_props,
|
|
&data);
|
|
}
|
|
|
|
else {
|
|
g_autofree gchar *help = g_option_context_get_help (context, TRUE, NULL);
|
|
g_print ("%s", help);
|
|
return 1;
|
|
}
|
|
|
|
wp_core_install_object_manager (core, om);
|
|
if (wp_core_connect (core))
|
|
g_main_loop_run (loop);
|
|
|
|
return 0;
|
|
}
|