metadata: store default-nodes as JSON

Don't just store the id in the metadata but a JSON object with
the node name. This makes it possible to easily introspect the
metadata and also extend the metadata with more fields later.

Instead of matching the metadata id to the global ids we now
have to match it against the name.
This commit is contained in:
Wim Taymans 2021-03-05 17:37:12 +01:00
parent 8d5cc7013d
commit c8fd34a41d
6 changed files with 462 additions and 202 deletions

View file

@ -26,6 +26,7 @@
#include <alsa/control_external.h> #include <alsa/control_external.h>
#include <spa/utils/result.h> #include <spa/utils/result.h>
#include <spa/utils/json.h>
#include <spa/param/props.h> #include <spa/param/props.h>
#include <spa/param/audio/format-utils.h> #include <spa/param/audio/format-utils.h>
@ -62,11 +63,11 @@ typedef struct {
int pending_seq; int pending_seq;
int error; int error;
uint32_t sink; char default_sink[1024];
int sink_muted; int sink_muted;
struct volume sink_volume; struct volume sink_volume;
uint32_t source; char default_source[1024];
int source_muted; int source_muted;
struct volume source_volume; struct volume source_volume;
@ -153,16 +154,25 @@ static int wait_resync(snd_ctl_pipewire_t *ctl)
return 0; return 0;
} }
static struct global *find_global(snd_ctl_pipewire_t *ctl, uint32_t id, const char *type) static struct global *find_global(snd_ctl_pipewire_t *ctl, uint32_t id,
const char *name, const char *type)
{ {
struct global *g; struct global *g;
uint32_t name_id = name ? (uint32_t)atoi(name) : SPA_ID_INVALID;
const char *str;
spa_list_for_each(g, &ctl->globals, link) { spa_list_for_each(g, &ctl->globals, link) {
if (g->id == id && if ((g->id == id || g->id == name_id) &&
(type == NULL || strcmp(g->ginfo->type, type) == 0)) (type == NULL || strcmp(g->ginfo->type, type) == 0))
return g; return g;
if (name != NULL && name[0] != '\0' &&
(str = pw_properties_get(g->props, PW_KEY_NODE_NAME)) != NULL &&
strcmp(name, str) == 0)
return g;
} }
return NULL; return NULL;
} }
static struct global *find_best_node(snd_ctl_pipewire_t *ctl, uint32_t flags) static struct global *find_best_node(snd_ctl_pipewire_t *ctl, uint32_t flags)
{ {
struct global *g, *best = NULL; struct global *g, *best = NULL;
@ -202,13 +212,13 @@ static int pipewire_update_volume(snd_ctl_pipewire_t * ctl)
bool changed = false; bool changed = false;
struct global *g; struct global *g;
if (ctl->sink == 0) if (ctl->default_sink[0] == '\0')
g = find_best_node(ctl, NODE_FLAG_SINK); g = find_best_node(ctl, NODE_FLAG_SINK);
else else
g = find_global(ctl, ctl->sink, PW_TYPE_INTERFACE_Node); g = find_global(ctl, SPA_ID_INVALID, ctl->default_sink,
PW_TYPE_INTERFACE_Node);
if (g) { if (g) {
ctl->sink = g->id;
if (!!ctl->sink_muted != !!g->node.mute) { if (!!ctl->sink_muted != !!g->node.mute) {
ctl->sink_muted = g->node.mute; ctl->sink_muted = g->node.mute;
ctl->updated |= UPDATE_SINK_MUTE; ctl->updated |= UPDATE_SINK_MUTE;
@ -221,13 +231,13 @@ static int pipewire_update_volume(snd_ctl_pipewire_t * ctl)
} }
} }
if (ctl->source == 0) if (ctl->default_source[0] == '\0')
g = find_best_node(ctl, NODE_FLAG_SOURCE); g = find_best_node(ctl, NODE_FLAG_SOURCE);
else else
g = find_global(ctl, ctl->source, PW_TYPE_INTERFACE_Node); g = find_global(ctl, SPA_ID_INVALID, ctl->default_source,
PW_TYPE_INTERFACE_Node);
if (g) { if (g) {
ctl->source = g->id;
if (!!ctl->source_muted != !!g->node.mute) { if (!!ctl->source_muted != !!g->node.mute) {
ctl->source_muted = g->node.mute; ctl->source_muted = g->node.mute;
ctl->updated |= UPDATE_SOURCE_MUTE; ctl->updated |= UPDATE_SOURCE_MUTE;
@ -270,9 +280,9 @@ static int pipewire_elem_count(snd_ctl_ext_t * ext)
goto finish; goto finish;
} }
if (ctl->source) if (ctl->default_source[0] != '\0')
count += 2; count += 2;
if (ctl->sink) if (ctl->default_sink[0] != '\0')
count += 2; count += 2;
finish: finish:
@ -300,7 +310,7 @@ static int pipewire_elem_list(snd_ctl_ext_t * ext, unsigned int offset,
if (err < 0) if (err < 0)
goto finish; goto finish;
if (ctl->source) { if (ctl->default_source[0] != '\0') {
if (offset == 0) if (offset == 0)
snd_ctl_elem_id_set_name(id, SOURCE_VOL_NAME); snd_ctl_elem_id_set_name(id, SOURCE_VOL_NAME);
else if (offset == 1) else if (offset == 1)
@ -479,7 +489,7 @@ static struct spa_pod *build_volume_mute(struct spa_pod_builder *b, struct volum
return spa_pod_builder_pop(b, &f[0]); return spa_pod_builder_pop(b, &f[0]);
} }
static int set_volume_mute(snd_ctl_pipewire_t *ctl, uint32_t node, struct volume *volume, int *mute) static int set_volume_mute(snd_ctl_pipewire_t *ctl, const char *name, struct volume *volume, int *mute)
{ {
struct global *g, *dg; struct global *g, *dg;
uint32_t id = SPA_ID_INVALID, device_id = SPA_ID_INVALID; uint32_t id = SPA_ID_INVALID, device_id = SPA_ID_INVALID;
@ -488,12 +498,12 @@ static int set_volume_mute(snd_ctl_pipewire_t *ctl, uint32_t node, struct volume
struct spa_pod_frame f[2]; struct spa_pod_frame f[2];
struct spa_pod *param; struct spa_pod *param;
g = find_global(ctl, node, PW_TYPE_INTERFACE_Node); g = find_global(ctl, SPA_ID_INVALID, name, PW_TYPE_INTERFACE_Node);
if (g == NULL) if (g == NULL)
return -EINVAL; return -EINVAL;
if (SPA_FLAG_IS_SET(g->node.flags, NODE_FLAG_DEVICE_VOLUME) && if (SPA_FLAG_IS_SET(g->node.flags, NODE_FLAG_DEVICE_VOLUME) &&
(dg = find_global(ctl, g->node.device_id, PW_TYPE_INTERFACE_Device)) != NULL) { (dg = find_global(ctl, g->node.device_id, NULL, PW_TYPE_INTERFACE_Device)) != NULL) {
if (g->node.flags & NODE_FLAG_SINK) if (g->node.flags & NODE_FLAG_SINK)
id = dg->device.active_route_output; id = dg->device.active_route_output;
else if (g->node.flags & NODE_FLAG_SOURCE) else if (g->node.flags & NODE_FLAG_SOURCE)
@ -589,14 +599,14 @@ static int pipewire_write_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
vol->values[i] = value[i]; vol->values[i] = value[i];
if (key == 0) if (key == 0)
set_volume_mute(ctl, ctl->source, vol, NULL); set_volume_mute(ctl, ctl->default_source, vol, NULL);
else else
set_volume_mute(ctl, ctl->sink, vol, NULL); set_volume_mute(ctl, ctl->default_sink, vol, NULL);
} else { } else {
if (key == 1) if (key == 1)
set_volume_mute(ctl, ctl->source, NULL, &ctl->source_muted); set_volume_mute(ctl, ctl->default_source, NULL, &ctl->source_muted);
else else
set_volume_mute(ctl, ctl->sink, NULL, &ctl->sink_muted); set_volume_mute(ctl, ctl->default_sink, NULL, &ctl->sink_muted);
} }
wait_resync(ctl); wait_resync(ctl);
@ -651,7 +661,7 @@ static int pipewire_read_event(snd_ctl_ext_t * ext, snd_ctl_elem_id_t * id,
goto finish; goto finish;
} }
if (ctl->source) if (ctl->default_source[0] != '\0')
offset = 2; offset = 2;
else else
offset = 0; offset = 0;
@ -968,6 +978,29 @@ struct global_info node_info = {
}; };
/** metadata */ /** metadata */
static int json_object_find(const char *obj, const char *key, char *value, size_t len)
{
struct spa_json it[2];
const char *v;
char k[128];
spa_json_init(&it[0], obj, strlen(obj));
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
return -EINVAL;
while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) {
if (strcmp(k, key) == 0) {
if (spa_json_get_string(&it[1], value, len) <= 0)
continue;
return 0;
} else {
if (spa_json_next(&it[1], &v) <= 0)
break;
}
}
return -ENOENT;
}
static int metadata_property(void *object, static int metadata_property(void *object,
uint32_t subject, uint32_t subject,
const char *key, const char *key,
@ -978,11 +1011,20 @@ static int metadata_property(void *object,
snd_ctl_pipewire_t *ctl = g->ctl; snd_ctl_pipewire_t *ctl = g->ctl;
if (subject == PW_ID_CORE) { if (subject == PW_ID_CORE) {
uint32_t val = (key && value) ? (uint32_t)atoi(value) : 0; if (key == NULL || strcmp(key, "default.audio.sink") == 0) {
if (key == NULL || strcmp(key, "default.audio.sink") == 0) if (value == NULL ||
ctl->sink = val; json_object_find(value, "name",
if (key == NULL || strcmp(key, "default.audio.source") == 0) ctl->default_sink, sizeof(ctl->default_sink)) < 0)
ctl->source = val; ctl->default_sink[0] = '\0';
pw_log_debug("found default sink: %s", ctl->default_sink);
}
if (key == NULL || strcmp(key, "default.audio.source") == 0) {
if (value == NULL ||
json_object_find(value, "name",
ctl->default_source, sizeof(ctl->default_source)) < 0)
ctl->default_source[0] = '\0';
pw_log_debug("found default source: %s", ctl->default_source);
}
} }
return 0; return 0;
} }
@ -1094,10 +1136,18 @@ static void registry_event_global(void *data, uint32_t id,
static void registry_event_global_remove(void *data, uint32_t id) static void registry_event_global_remove(void *data, uint32_t id)
{ {
snd_ctl_pipewire_t *ctl = data; snd_ctl_pipewire_t *ctl = data;
if (ctl->sink == id) struct global *g;
ctl->sink = 0; const char *name;
if (ctl->source == id)
ctl->source = 0; if ((g = find_global(ctl, id, NULL, PW_TYPE_INTERFACE_Node)) == NULL)
return;
if ((name = pw_properties_get(g->props, PW_KEY_NODE_NAME)) == NULL)
return;
if (strcmp(name, ctl->default_sink) == 0)
ctl->default_sink[0] = '\0';
if (strcmp(name, ctl->default_source) == 0)
ctl->default_source[0] = '\0';
} }
static const struct pw_registry_events registry_events = { static const struct pw_registry_events registry_events = {
@ -1229,25 +1279,16 @@ SND_CTL_PLUGIN_DEFINE_FUNC(pipewire)
spa_list_init(&ctl->globals); spa_list_init(&ctl->globals);
if (source) if (source == NULL)
ctl->source = atoi(source); source = device;
else if (device) if (source != NULL)
ctl->source = atoi(device); snprintf(ctl->default_source, sizeof(ctl->default_source),
"%s", source);
if ((source || device) && !ctl->source) { if (sink == NULL)
err = -EINVAL; sink = device;
goto error; if (sink != NULL)
} snprintf(ctl->default_sink, sizeof(ctl->default_sink),
"%s", sink);
if (sink)
ctl->sink = atoi(sink);
else if (device)
ctl->sink = atoi(device);
if ((sink || device) && !ctl->sink) {
err = -EINVAL;
goto error;
}
ctl->mainloop = pw_thread_loop_new("alsa-pipewire", NULL); ctl->mainloop = pw_thread_loop_new("alsa-pipewire", NULL);
if (ctl->mainloop == NULL) { if (ctl->mainloop == NULL) {

View file

@ -44,6 +44,7 @@
#include <spa/param/video/format-utils.h> #include <spa/param/video/format-utils.h>
#include <spa/debug/types.h> #include <spa/debug/types.h>
#include <spa/debug/pod.h> #include <spa/debug/pod.h>
#include <spa/utils/json.h>
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <pipewire/private.h> #include <pipewire/private.h>
@ -110,6 +111,7 @@ struct object {
union { union {
struct { struct {
char name[JACK_CLIENT_NAME_SIZE+1]; char name[JACK_CLIENT_NAME_SIZE+1];
char node_name[512];
int32_t priority; int32_t priority;
uint32_t client_id; uint32_t client_id;
} node; } node;
@ -132,6 +134,7 @@ struct object {
int32_t priority; int32_t priority;
struct port *port; struct port *port;
bool is_monitor; bool is_monitor;
struct object *node;
} port; } port;
}; };
}; };
@ -241,8 +244,8 @@ struct metadata {
struct pw_metadata *proxy; struct pw_metadata *proxy;
struct spa_hook listener; struct spa_hook listener;
uint32_t default_audio_sink; char default_audio_sink[1024];
uint32_t default_audio_source; char default_audio_source[1024];
}; };
struct client { struct client {
@ -548,6 +551,21 @@ static struct object *find_node(struct client *c, const char *name)
return NULL; return NULL;
} }
static bool is_port_default(struct client *c, struct object *o)
{
struct object *ot;
if (c->metadata == NULL)
return false;
if ((ot = o->port.node) != NULL &&
(strcmp(ot->node.node_name, c->metadata->default_audio_source) == 0 ||
strcmp(ot->node.node_name, c->metadata->default_audio_sink) == 0))
return true;
return false;
}
static struct object *find_port(struct client *c, const char *name) static struct object *find_port(struct client *c, const char *name)
{ {
struct object *o; struct object *o;
@ -557,10 +575,7 @@ static struct object *find_port(struct client *c, const char *name)
strcmp(o->port.alias1, name) == 0 || strcmp(o->port.alias1, name) == 0 ||
strcmp(o->port.alias2, name) == 0) strcmp(o->port.alias2, name) == 0)
return o; return o;
if (c->metadata && if (is_port_default(c, o) && strcmp(o->port.system, name) == 0)
(o->port.node_id == c->metadata->default_audio_source ||
o->port.node_id == c->metadata->default_audio_sink) &&
strcmp(o->port.system, name) == 0)
return o; return o;
} }
return NULL; return NULL;
@ -2037,6 +2052,29 @@ static jack_uuid_t client_make_uuid(uint32_t id)
return uuid; return uuid;
} }
static int json_object_find(const char *obj, const char *key, char *value, size_t len)
{
struct spa_json it[2];
const char *v;
char k[128];
spa_json_init(&it[0], obj, strlen(obj));
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
return -EINVAL;
while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) {
if (strcmp(k, key) == 0) {
if (spa_json_get_string(&it[1], value, len) <= 0)
continue;
return 0;
} else {
if (spa_json_next(&it[1], &v) <= 0)
break;
}
}
return -ENOENT;
}
static int metadata_property(void *object, uint32_t id, static int metadata_property(void *object, uint32_t id,
const char *key, const char *type, const char *value) const char *key, const char *type, const char *value)
{ {
@ -2047,11 +2085,26 @@ static int metadata_property(void *object, uint32_t id,
pw_log_info("set id:%u key:'%s' value:'%s' type:'%s'", id, key, value, type); pw_log_info("set id:%u key:'%s' value:'%s' type:'%s'", id, key, value, type);
if (id == PW_ID_CORE) { if (id == PW_ID_CORE) {
uint32_t val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID; if (key == NULL || strcmp(key, "default.audio.sink") == 0) {
if (key == NULL || strcmp(key, "default.audio.sink") == 0) if (value != NULL) {
c->metadata->default_audio_sink = val; if (json_object_find(value, "name",
if (key == NULL || strcmp(key, "default.audio.source") == 0) c->metadata->default_audio_sink,
c->metadata->default_audio_source = val; sizeof(c->metadata->default_audio_sink)) < 0)
value = NULL;
}
if (value == NULL)
c->metadata->default_audio_sink[0] = '\0';
}
if (key == NULL || strcmp(key, "default.audio.source") == 0) {
if (value != NULL) {
if (json_object_find(value, "name",
c->metadata->default_audio_source,
sizeof(c->metadata->default_audio_source)) < 0)
value = NULL;
}
if (value == NULL)
c->metadata->default_audio_source[0] = '\0';
}
} else { } else {
pthread_mutex_lock(&c->context.lock); pthread_mutex_lock(&c->context.lock);
o = pw_map_lookup(&c->context.globals, id); o = pw_map_lookup(&c->context.globals, id);
@ -2122,6 +2175,8 @@ static void registry_event_global(void *data, uint32_t id,
if (node_name != NULL) if (node_name != NULL)
snprintf(c->name, sizeof(c->name), "%s", node_name); snprintf(c->name, sizeof(c->name), "%s", node_name);
} }
snprintf(o->node.node_name, sizeof(o->node.node_name),
"%s", node_name);
app = spa_dict_lookup(props, PW_KEY_APP_NAME); app = spa_dict_lookup(props, PW_KEY_APP_NAME);
@ -2245,6 +2300,7 @@ static void registry_event_global(void *data, uint32_t id,
o->port.port_id = SPA_ID_INVALID; o->port.port_id = SPA_ID_INVALID;
o->port.priority = ot->node.priority; o->port.priority = ot->node.priority;
o->port.node = ot;
} }
if ((str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH)) != NULL) if ((str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH)) != NULL)
@ -2303,8 +2359,8 @@ static void registry_event_global(void *data, uint32_t id,
c->metadata = pw_proxy_get_user_data(proxy); c->metadata = pw_proxy_get_user_data(proxy);
c->metadata->proxy = (struct pw_metadata*)proxy; c->metadata->proxy = (struct pw_metadata*)proxy;
c->metadata->default_audio_sink = SPA_ID_INVALID; c->metadata->default_audio_sink[0] = '\0';
c->metadata->default_audio_source = SPA_ID_INVALID; c->metadata->default_audio_source[0] = '\0';
pw_metadata_add_listener(proxy, pw_metadata_add_listener(proxy,
&c->metadata->listener, &c->metadata->listener,
@ -2359,13 +2415,6 @@ static void registry_event_global_remove(void *object, uint32_t id)
pw_log_debug(NAME" %p: removed: %u", c, id); pw_log_debug(NAME" %p: removed: %u", c, id);
if (c->metadata) {
if (id == c->metadata->default_audio_sink)
c->metadata->default_audio_sink = SPA_ID_INVALID;
if (id == c->metadata->default_audio_source)
c->metadata->default_audio_source = SPA_ID_INVALID;
}
pthread_mutex_lock(&c->context.lock); pthread_mutex_lock(&c->context.lock);
o = pw_map_lookup(&c->context.globals, id); o = pw_map_lookup(&c->context.globals, id);
pthread_mutex_unlock(&c->context.lock); pthread_mutex_unlock(&c->context.lock);
@ -2376,6 +2425,12 @@ static void registry_event_global_remove(void *object, uint32_t id)
switch (o->type) { switch (o->type) {
case INTERFACE_Node: case INTERFACE_Node:
if (c->metadata) {
if (strcmp(o->node.node_name, c->metadata->default_audio_sink) == 0)
c->metadata->default_audio_sink[0] = '\0';
if (strcmp(o->node.node_name, c->metadata->default_audio_source) == 0)
c->metadata->default_audio_source[0] = '\0';
}
if (c->registration_callback) if (c->registration_callback)
c->registration_callback(o->node.name, 0, c->registration_arg); c->registration_callback(o->node.name, 0, c->registration_arg);
break; break;
@ -4255,15 +4310,24 @@ static int port_compare_func(const void *v1, const void *v2)
!(*o2)->port.is_monitor; !(*o2)->port.is_monitor;
if (c->metadata) { if (c->metadata) {
struct object *ot1, *ot2;
ot1 = (*o1)->port.node;
if (is_cap1) if (is_cap1)
is_def1 = (*o1)->port.node_id == c->metadata->default_audio_source; is_def1 = ot1 != NULL && strcmp(ot1->node.node_name,
c->metadata->default_audio_source) == 0;
else if (!is_cap1) else if (!is_cap1)
is_def1 = (*o1)->port.node_id == c->metadata->default_audio_sink; is_def1 = ot1 != NULL && strcmp(ot1->node.node_name,
c->metadata->default_audio_sink) == 0;
ot2 = (*o2)->port.node;
if (is_cap2) if (is_cap2)
is_def2 = (*o2)->port.node_id == c->metadata->default_audio_source; is_def2 = ot2 != NULL && strcmp(ot2->node.node_name,
c->metadata->default_audio_source) == 0;
else if (!is_cap2) else if (!is_cap2)
is_def2 = (*o2)->port.node_id == c->metadata->default_audio_sink; is_def2 = ot2 != NULL && strcmp(ot2->node.node_name,
c->metadata->default_audio_sink) == 0;
} }
if ((*o1)->port.type_id != (*o2)->port.type_id) if ((*o1)->port.type_id != (*o2)->port.type_id)
res = (*o1)->port.type_id - (*o2)->port.type_id; res = (*o1)->port.type_id - (*o2)->port.type_id;

View file

@ -71,8 +71,20 @@ struct impl {
struct default_node defaults[4]; struct default_node defaults[4];
struct pw_properties *properties; struct pw_properties *properties;
unsigned int sync:1;
}; };
static struct default_node *find_default(struct impl *impl, const char *key)
{
struct default_node *def;
/* Check that the item key is a valid default key */
for (def = impl->defaults; def->key != NULL; ++def)
if (strcmp(key, def->key) == 0)
return def;
return NULL;
}
struct find_data { struct find_data {
struct impl *impl; struct impl *impl;
const char *name; const char *name;
@ -94,32 +106,34 @@ static int find_name(void *data, struct sm_object *object)
return 0; return 0;
} }
#if 0
static uint32_t find_id_for_name(struct impl *impl, const char *name) static uint32_t find_id_for_name(struct impl *impl, const char *name)
{ {
struct find_data d = { impl, name, SPA_ID_INVALID }; struct find_data d = { impl, name, SPA_ID_INVALID };
sm_media_session_for_each_object(impl->session, find_name, &d); sm_media_session_for_each_object(impl->session, find_name, &d);
return d.id; return d.id;
} }
#endif
static const char *find_name_for_id(struct impl *impl, uint32_t id) static int json_object_find(const char *obj, const char *key, char *value, size_t len)
{ {
struct sm_object *obj; struct spa_json it[2];
const char *str; const char *v;
char k[128];
if (id == SPA_ID_INVALID) spa_json_init(&it[0], obj, strlen(obj));
return NULL; if (spa_json_enter_object(&it[0], &it[1]) <= 0)
return -EINVAL;
obj = sm_media_session_find_object(impl->session, id); while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) {
if (obj == NULL) if (strcmp(k, key) == 0) {
return NULL; if (spa_json_get_string(&it[1], value, len) <= 0)
continue;
if (strcmp(obj->type, PW_TYPE_INTERFACE_Node) == 0 && return 0;
obj->props && } else {
(str = pw_properties_get(obj->props, PW_KEY_NODE_NAME)) != NULL) if (spa_json_next(&it[1], &v) <= 0)
return str; break;
return NULL; }
}
return -ENOENT;
} }
static void remove_idle_timeout(struct impl *impl) static void remove_idle_timeout(struct impl *impl)
@ -160,27 +174,41 @@ static int metadata_property(void *object, uint32_t subject,
const char *key, const char *type, const char *value) const char *key, const char *type, const char *value)
{ {
struct impl *impl = object; struct impl *impl = object;
uint32_t val; int changed = 0;
bool changed = false;
if (impl->sync)
return 0;
if (subject == PW_ID_CORE) { if (subject == PW_ID_CORE) {
struct default_node *def; if (key == NULL) {
val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID; pw_properties_clear(impl->properties);
for (def = impl->defaults; def->key != NULL; ++def) { changed++;
if (key == NULL || strcmp(key, def->key) == 0) { } else {
changed = (def->value != val); uint32_t id;
def->value = val; struct default_node *def;
char name[1024];
if ((def = find_default(impl, key)) == NULL)
return 0;
if (value == NULL) {
def->value = SPA_ID_INVALID;
} else {
if (json_object_find(value, "name", name, sizeof(name)) < 0)
return 0;
if ((id = find_id_for_name(impl, name)) == SPA_ID_INVALID)
return 0;
def->value = id;
changed += pw_properties_set(impl->properties,
key, value);
} }
} }
} }
if (changed) { if (changed)
const char *name = find_name_for_id(impl, val);
if (key == NULL)
pw_properties_clear(impl->properties);
else
pw_properties_setf(impl->properties, key, "{ \"name\": \"%s\" }", name);
add_idle_timeout(impl); add_idle_timeout(impl);
}
return 0; return 0;
} }
@ -198,42 +226,22 @@ static void session_create(void *data, struct sm_object *object)
return; return;
spa_dict_for_each(item, &impl->properties->dict) { spa_dict_for_each(item, &impl->properties->dict) {
struct spa_json it[2]; char name [1024] = "\0";
const char *value;
char name [1024] = "\0", key[128];
struct find_data d; struct find_data d;
spa_json_init(&it[0], item->value, strlen(item->value)); if (find_default(impl, item->key) == NULL)
if (spa_json_enter_object(&it[0], &it[1]) <= 0) continue;
if (json_object_find(item->value, "name", name, sizeof(name)) < 0)
continue; continue;
while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) {
if (strcmp(key, "name") == 0) {
if (spa_json_get_string(&it[1], name, sizeof(name)) <= 0)
continue;
} else {
if (spa_json_next(&it[1], &value) <= 0)
break;
}
}
d = (struct find_data){ impl, name, SPA_ID_INVALID }; d = (struct find_data){ impl, name, SPA_ID_INVALID };
if (find_name(&d, object)) { if (find_name(&d, object)) {
const struct default_node *def;
/* Check that the item key is a valid default key */
for (def = impl->defaults; def->key != NULL; ++def)
if (item->key != NULL && strcmp(item->key, def->key) == 0)
break;
if (def->key == NULL)
continue;
if (impl->session->metadata != NULL) { if (impl->session->metadata != NULL) {
char val[16]; pw_log_info("found %s with id:%u restore as %s",
snprintf(val, sizeof(val), "%u", d.id); name, d.id, item->key);
pw_log_info("found %s with id:%s restore as %s",
name, val, item->key);
pw_metadata_set_property(impl->session->metadata, pw_metadata_set_property(impl->session->metadata,
PW_ID_CORE, item->key, SPA_TYPE_INFO_BASE"Id", val); PW_ID_CORE, item->key, "Spa:String:JSON", item->value);
} }
} }
} }
@ -288,10 +296,10 @@ int sm_default_nodes_start(struct sm_media_session *session)
impl->session = session; impl->session = session;
impl->context = session->context; impl->context = session->context;
impl->defaults[0] = (struct default_node){ DEFAULT_CONFIG_AUDIO_SINK_KEY, SPA_ID_INVALID }; impl->defaults[0] = (struct default_node){ DEFAULT_CONFIG_AUDIO_SINK_KEY, };
impl->defaults[1] = (struct default_node){ DEFAULT_CONFIG_AUDIO_SOURCE_KEY, SPA_ID_INVALID }; impl->defaults[1] = (struct default_node){ DEFAULT_CONFIG_AUDIO_SOURCE_KEY, };
impl->defaults[2] = (struct default_node){ DEFAULT_CONFIG_VIDEO_SOURCE_KEY, SPA_ID_INVALID }; impl->defaults[2] = (struct default_node){ DEFAULT_CONFIG_VIDEO_SOURCE_KEY, };
impl->defaults[3] = (struct default_node){ NULL, SPA_ID_INVALID }; impl->defaults[3] = (struct default_node){ NULL, };
impl->properties = pw_properties_new(NULL, NULL); impl->properties = pw_properties_new(NULL, NULL);
if (impl->properties == NULL) { if (impl->properties == NULL) {

View file

@ -35,6 +35,7 @@
#include <spa/param/audio/format-utils.h> #include <spa/param/audio/format-utils.h>
#include <spa/param/props.h> #include <spa/param/props.h>
#include <spa/debug/pod.h> #include <spa/debug/pod.h>
#include <spa/utils/json.h>
#include "pipewire/pipewire.h" #include "pipewire/pipewire.h"
#include "extensions/metadata.h" #include "extensions/metadata.h"
@ -60,8 +61,8 @@
struct default_node { struct default_node {
char *key; char *key;
char *key_config; char *key_config;
uint32_t value; char *value;
uint32_t config; char *config;
}; };
struct impl { struct impl {
@ -362,11 +363,56 @@ static void destroy_node(struct impl *impl, struct node *node)
sm_object_remove_data((struct sm_object*)node->obj, SESSION_KEY); sm_object_remove_data((struct sm_object*)node->obj, SESSION_KEY);
} }
static struct node *find_node_by_id(struct impl *impl, uint32_t id) static inline int strzcmp(const char *s1, const char *s2)
{
if (s1 == s2)
return 0;
if (s1 == NULL || s2 == NULL)
return 1;
return strcmp(s1, s2);
}
static int json_object_find(const char *obj, const char *key, char *value, size_t len)
{
struct spa_json it[2];
const char *v;
char k[128];
spa_json_init(&it[0], obj, strlen(obj));
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
return -EINVAL;
while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) {
if (strcmp(k, key) == 0) {
if (spa_json_get_string(&it[1], value, len) <= 0)
continue;
return 0;
} else {
if (spa_json_next(&it[1], &v) <= 0)
break;
}
}
return -ENOENT;
}
static bool check_node_name(struct node *node, const char *name)
{
const char *str;
if ((str = pw_properties_get(node->obj->obj.props, PW_KEY_NODE_NAME)) != NULL &&
name != NULL && strcmp(str, name) == 0)
return true;
return false;
}
static struct node *find_node_by_id_name(struct impl *impl, uint32_t id, const char *name)
{ {
struct node *node; struct node *node;
uint32_t name_id = name ? atoi(name) : SPA_ID_INVALID;
spa_list_for_each(node, &impl->node_list, link) { spa_list_for_each(node, &impl->node_list, link) {
if (node->id == id) if (node->id == id || node->id == name_id)
return node;
if (check_node_name(node, name))
return node; return node;
} }
return NULL; return NULL;
@ -417,7 +463,6 @@ static void session_create(void *data, struct sm_object *object)
static void session_remove(void *data, struct sm_object *object) static void session_remove(void *data, struct sm_object *object)
{ {
struct default_node *def;
struct impl *impl = data; struct impl *impl = data;
pw_log_debug(NAME " %p: remove global '%d'", impl, object->id); pw_log_debug(NAME " %p: remove global '%d'", impl, object->id);
@ -431,12 +476,7 @@ static void session_remove(void *data, struct sm_object *object)
if (n->peer == node) if (n->peer == node)
n->peer = NULL; n->peer = NULL;
} }
for (def = impl->defaults; def->key != NULL; ++def)
if (def->config == object->id)
def->config = SPA_ID_INVALID;
} }
sm_media_session_schedule_rescan(impl->session); sm_media_session_schedule_rescan(impl->session);
} }
@ -491,14 +531,18 @@ static int find_node(void *data, struct node *node)
if (node->media) { if (node->media) {
bool is_default = false; bool is_default = false;
if (strcmp(node->media, "Audio") == 0) { if (strcmp(node->media, "Audio") == 0) {
if (node->direction == PW_DIRECTION_INPUT) if (node->direction == PW_DIRECTION_INPUT)
is_default = impl->defaults[DEFAULT_AUDIO_SINK].config == node->id; is_default = check_node_name(node,
impl->defaults[DEFAULT_AUDIO_SINK].config);
else if (node->direction == PW_DIRECTION_OUTPUT) else if (node->direction == PW_DIRECTION_OUTPUT)
is_default = impl->defaults[DEFAULT_AUDIO_SOURCE].config == node->id; is_default = check_node_name(node,
impl->defaults[DEFAULT_AUDIO_SOURCE].config);
} else if (strcmp(node->media, "Video") == 0) { } else if (strcmp(node->media, "Video") == 0) {
if (node->direction == PW_DIRECTION_OUTPUT) if (node->direction == PW_DIRECTION_OUTPUT)
is_default = impl->defaults[DEFAULT_VIDEO_SOURCE].config == node->id; is_default = check_node_name(node,
impl->defaults[DEFAULT_VIDEO_SOURCE].config);
} }
if (is_default) if (is_default)
priority += 10000; priority += 10000;
@ -813,19 +857,30 @@ static void refresh_auto_default_nodes(struct impl *impl)
if (impl->session->metadata == NULL) if (impl->session->metadata == NULL)
return; return;
pw_log_debug(NAME" %p: refresh", impl);
/* Auto set default nodes */ /* Auto set default nodes */
for (def = impl->defaults; def->key != NULL; ++def) { for (def = impl->defaults; def->key != NULL; ++def) {
struct node *node; struct node *node;
node = find_auto_default_node(impl, def); node = find_auto_default_node(impl, def);
if (node == NULL && def->value != SPA_ID_INVALID) { if (node == NULL && def->value != NULL) {
def->value = SPA_ID_INVALID; def->value = NULL;
pw_metadata_set_property(impl->session->metadata, PW_ID_CORE, def->key, NULL, NULL); pw_metadata_set_property(impl->session->metadata,
} else if (node != NULL && def->value != node->id) { PW_ID_CORE, def->key, NULL, NULL);
char buf[64]; } else if (node != NULL) {
def->value = node->id; const char *name = pw_properties_get(node->obj->obj.props, PW_KEY_NODE_NAME);
snprintf(buf, sizeof(buf), "%d", node->id); char buf[1024];
pw_metadata_set_property(impl->session->metadata, PW_ID_CORE, def->key,
SPA_TYPE_INFO_BASE"Id", buf); if (strzcmp(name, def->value) == 0)
continue;
free(def->value);
def->value = strdup(name);
snprintf(buf, sizeof(buf), "{ \"name\": \"%s\" }", name);
pw_metadata_set_property(impl->session->metadata,
PW_ID_CORE, def->key,
"Spa:String:JSON", buf);
} }
} }
} }
@ -905,21 +960,32 @@ static int metadata_property(void *object, uint32_t subject,
const char *key, const char *type, const char *value) const char *key, const char *type, const char *value)
{ {
struct impl *impl = object; struct impl *impl = object;
uint32_t val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID;
if (subject == PW_ID_CORE) { if (subject == PW_ID_CORE) {
struct default_node *def; struct default_node *def;
bool changed = false; bool changed = false;
char *val = NULL;
if (key != NULL && value != NULL) {
char name[1024];
pw_log_info("meta %s: %s", key, value);
if (json_object_find(value, "name", name, sizeof(name)) < 0)
return 0;
pw_log_info("meta name: %s", name);
val = name;
}
for (def = impl->defaults; def->key != NULL; ++def) { for (def = impl->defaults; def->key != NULL; ++def) {
if (key == NULL || strcmp(key, def->key_config) == 0) { if (key == NULL || strcmp(key, def->key_config) == 0) {
if (def->config != val) if (strzcmp(def->config, val) != 0)
changed = true; changed = true;
def->config = val; free(def->config);
def->config = val ? strdup(val) : NULL;
} }
if (key == NULL || strcmp(key, def->key) == 0) { if (key == NULL || strcmp(key, def->key) == 0) {
bool eff_changed = (def->value != val); bool eff_changed = strzcmp(def->value, val) != 0;
def->value = val; free(def->value);
def->value = val ? strdup(val) : NULL;
/* The effective value was changed. In case it was changed by /* The effective value was changed. In case it was changed by
* someone else than us, reset the value to avoid confusion. */ * someone else than us, reset the value to avoid confusion. */
@ -927,23 +993,21 @@ static int metadata_property(void *object, uint32_t subject,
refresh_auto_default_nodes(impl); refresh_auto_default_nodes(impl);
} }
} }
if (changed) if (changed)
sm_media_session_schedule_rescan(impl->session); sm_media_session_schedule_rescan(impl->session);
} else { } else if (key != NULL && strcmp(key, "target.node") == 0) {
if (val != SPA_ID_INVALID && strcmp(key, "target.node") == 0) { if (value != NULL) {
struct node *src_node, *dst_node; struct node *src_node, *dst_node;
dst_node = find_node_by_id(impl, val); dst_node = find_node_by_id_name(impl, SPA_ID_INVALID, value);
src_node = dst_node ? find_node_by_id(impl, subject) : NULL; src_node = dst_node ? find_node_by_id_name(impl, subject, NULL) : NULL;
if (dst_node && src_node) if (dst_node && src_node)
handle_move(impl, src_node, dst_node); handle_move(impl, src_node, dst_node);
} else if (val == SPA_ID_INVALID && key != NULL && } else {
strcmp(key, "target.node") == 0) {
/* Unset target node. Schedule rescan to re-link, if needed. */ /* Unset target node. Schedule rescan to re-link, if needed. */
struct node *src_node; struct node *src_node;
src_node = find_node_by_id(impl, subject); src_node = find_node_by_id_name(impl, subject, NULL);
if (src_node) { if (src_node) {
free(src_node->obj->target_node); free(src_node->obj->target_node);
src_node->obj->target_node = NULL; src_node->obj->target_node = NULL;
@ -974,15 +1038,15 @@ int sm_policy_node_start(struct sm_media_session *session)
impl->sample_rate = 48000; impl->sample_rate = 48000;
impl->defaults[DEFAULT_AUDIO_SINK] = (struct default_node){ impl->defaults[DEFAULT_AUDIO_SINK] = (struct default_node){
DEFAULT_AUDIO_SINK_KEY, DEFAULT_CONFIG_AUDIO_SINK_KEY, SPA_ID_INVALID, SPA_ID_INVALID DEFAULT_AUDIO_SINK_KEY, DEFAULT_CONFIG_AUDIO_SINK_KEY, NULL, NULL
}; };
impl->defaults[DEFAULT_AUDIO_SOURCE] = (struct default_node){ impl->defaults[DEFAULT_AUDIO_SOURCE] = (struct default_node){
DEFAULT_AUDIO_SOURCE_KEY, DEFAULT_CONFIG_AUDIO_SOURCE_KEY, SPA_ID_INVALID, SPA_ID_INVALID DEFAULT_AUDIO_SOURCE_KEY, DEFAULT_CONFIG_AUDIO_SOURCE_KEY, NULL, NULL
}; };
impl->defaults[DEFAULT_VIDEO_SOURCE] = (struct default_node){ impl->defaults[DEFAULT_VIDEO_SOURCE] = (struct default_node){
DEFAULT_VIDEO_SOURCE_KEY, DEFAULT_CONFIG_VIDEO_SOURCE_KEY, SPA_ID_INVALID, SPA_ID_INVALID DEFAULT_VIDEO_SOURCE_KEY, DEFAULT_CONFIG_VIDEO_SOURCE_KEY, NULL, NULL
}; };
impl->defaults[3] = (struct default_node){ NULL, NULL, SPA_ID_INVALID, SPA_ID_INVALID }; impl->defaults[3] = (struct default_node){ NULL, NULL, NULL, NULL };
flag = pw_properties_get(session->props, NAME ".streams-follow-default"); flag = pw_properties_get(session->props, NAME ".streams-follow-default");
impl->streams_follow_default = (flag != NULL && pw_properties_parse_bool(flag)); impl->streams_follow_default = (flag != NULL && pw_properties_parse_bool(flag));

View file

@ -139,8 +139,8 @@ struct client {
uint32_t subscribed; uint32_t subscribed;
struct pw_manager_object *metadata_default; struct pw_manager_object *metadata_default;
uint32_t default_sink; char *default_sink;
uint32_t default_source; char *default_source;
struct pw_manager_object *metadata_routes; struct pw_manager_object *metadata_routes;
struct pw_properties *routes; struct pw_properties *routes;
@ -899,25 +899,75 @@ static void manager_removed(void *data, struct pw_manager_object *o)
send_default_change_subscribe_event(client, object_is_sink(o), object_is_source_or_monitor(o)); send_default_change_subscribe_event(client, object_is_sink(o), object_is_source_or_monitor(o));
} }
static int json_object_find(const char *obj, const char *key, char *value, size_t len)
{
struct spa_json it[2];
const char *v;
char k[128];
spa_json_init(&it[0], obj, strlen(obj));
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
return -EINVAL;
while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) {
if (strcmp(k, key) == 0) {
if (spa_json_get_string(&it[1], value, len) <= 0)
continue;
return 0;
} else {
if (spa_json_next(&it[1], &v) <= 0)
break;
}
}
return -ENOENT;
}
static inline int strzcmp(const char *s1, const char *s2)
{
if (s1 == s2)
return 0;
if (s1 == NULL || s2 == NULL)
return 1;
return strcmp(s1, s2);
}
static void manager_metadata(void *data, struct pw_manager_object *o, static void manager_metadata(void *data, struct pw_manager_object *o,
uint32_t subject, const char *key, const char *type, const char *value) uint32_t subject, const char *key, const char *type, const char *value)
{ {
struct client *client = data; struct client *client = data;
uint32_t val;
bool changed = false; bool changed = false;
pw_log_debug("meta id:%d subject:%d key:%s type:%s value:%s", pw_log_debug("meta id:%d subject:%d key:%s type:%s value:%s",
o->id, subject, key, type, value); o->id, subject, key, type, value);
if (subject == PW_ID_CORE && o == client->metadata_default) { if (subject == PW_ID_CORE && o == client->metadata_default) {
val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID; char name[1024];
if (key == NULL || strcmp(key, "default.audio.sink") == 0) { if (key == NULL || strcmp(key, "default.audio.sink") == 0) {
changed = client->default_sink != val; if (value != NULL) {
client->default_sink = val; if (json_object_find(value,
"name", name, sizeof(name)) < 0)
value = NULL;
else
value = name;
}
if ((changed = strzcmp(client->default_sink, value))) {
free(client->default_sink);
client->default_sink = strdup(value);
}
} }
if (key == NULL || strcmp(key, "default.audio.source") == 0) { if (key == NULL || strcmp(key, "default.audio.source") == 0) {
changed = client->default_source != val; if (value != NULL) {
client->default_source = val; if (json_object_find(value,
"name", name, sizeof(name)) < 0)
value = NULL;
else
value = name;
}
if ((changed = strzcmp(client->default_source, value))) {
free(client->default_source);
client->default_source = strdup(value);
}
} }
if (changed) if (changed)
send_default_change_subscribe_event(client, true, true); send_default_change_subscribe_event(client, true, true);
@ -2594,11 +2644,13 @@ static const char *get_default(struct client *client, bool sink)
spa_zero(sel); spa_zero(sel);
if (sink) { if (sink) {
sel.type = object_is_sink; sel.type = object_is_sink;
sel.id = client->default_sink; sel.key = PW_KEY_NODE_NAME;
sel.value = client->default_sink;
def = DEFAULT_SINK; def = DEFAULT_SINK;
} else { } else {
sel.type = object_is_source_or_monitor; sel.type = object_is_source_or_monitor;
sel.id = client->default_source; sel.key = PW_KEY_NODE_NAME;
sel.value = client->default_source;
def = DEFAULT_SOURCE; def = DEFAULT_SOURCE;
} }
sel.accumulate = select_best; sel.accumulate = select_best;
@ -4801,7 +4853,7 @@ static int do_set_default(struct client *client, uint32_t command, uint32_t tag,
if ((res = pw_manager_set_metadata(manager, client->metadata_default, if ((res = pw_manager_set_metadata(manager, client->metadata_default,
PW_ID_CORE, PW_ID_CORE,
sink ? METADATA_CONFIG_DEFAULT_SINK : METADATA_CONFIG_DEFAULT_SOURCE, sink ? METADATA_CONFIG_DEFAULT_SINK : METADATA_CONFIG_DEFAULT_SOURCE,
SPA_TYPE_INFO_BASE"Id", "%d", o->id)) < 0) "Spa:String:JSON", "{ \"name\": \"%s\" }", name)) < 0)
return res; return res;
return reply_simple_ack(client, tag); return reply_simple_ack(client, tag);
@ -5280,6 +5332,8 @@ static void client_free(struct client *client)
client->disconnecting = true; client->disconnecting = true;
pw_core_disconnect(client->core); pw_core_disconnect(client->core);
} }
free(client->default_sink);
free(client->default_source);
if (client->props) if (client->props)
pw_properties_free(client->props); pw_properties_free(client->props);
if (client->routes) if (client->routes)

View file

@ -42,6 +42,7 @@
#include <spa/param/audio/type-info.h> #include <spa/param/audio/type-info.h>
#include <spa/param/props.h> #include <spa/param/props.h>
#include <spa/utils/result.h> #include <spa/utils/result.h>
#include <spa/utils/json.h>
#include <spa/debug/types.h> #include <spa/debug/types.h>
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
@ -105,8 +106,8 @@ struct data {
struct spa_hook registry_listener; struct spa_hook registry_listener;
struct pw_metadata *metadata; struct pw_metadata *metadata;
struct spa_hook metadata_listener; struct spa_hook metadata_listener;
uint32_t default_sink; char default_sink[1024];
uint32_t default_source; char default_source[1024];
struct pw_stream *stream; struct pw_stream *stream;
struct spa_hook stream_listener; struct spa_hook stream_listener;
@ -647,17 +648,47 @@ static const struct pw_core_events core_events = {
.error = on_core_error, .error = on_core_error,
}; };
static int json_object_find(const char *obj, const char *key, char *value, size_t len)
{
struct spa_json it[2];
const char *v;
char k[128];
spa_json_init(&it[0], obj, strlen(obj));
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
return -EINVAL;
while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) {
if (strcmp(k, key) == 0) {
if (spa_json_get_string(&it[1], value, len) <= 0)
continue;
return 0;
} else {
if (spa_json_next(&it[1], &v) <= 0)
break;
}
}
return -ENOENT;
}
static int metadata_property(void *object, static int metadata_property(void *object,
uint32_t subject, const char *key, const char *type, const char *value) uint32_t subject, const char *key, const char *type, const char *value)
{ {
struct data *data = object; struct data *data = object;
if (subject == PW_ID_CORE) { if (subject == PW_ID_CORE) {
uint32_t val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID; if (key == NULL || strcmp(key, "default.audio.sink") == 0) {
if (key == NULL || strcmp(key, "default.audio.sink") == 0) if (value == NULL ||
data->default_sink = val; json_object_find(value, "name",
if (key == NULL || strcmp(key, "default.audio.source") == 0) data->default_sink, sizeof(data->default_sink)) < 0)
data->default_source = val; data->default_sink[0] = '\0';
}
if (key == NULL || strcmp(key, "default.audio.source") == 0) {
if (value == NULL ||
json_object_find(value, "name",
data->default_source, sizeof(data->default_source)) < 0)
data->default_source[0] = '\0';
}
} }
return 0; return 0;
} }
@ -1326,7 +1357,6 @@ int main(int argc, char *argv[])
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
const char *prog; const char *prog;
int exit_code = EXIT_FAILURE, c, ret; int exit_code = EXIT_FAILURE, c, ret;
struct target *target, *target_default;
enum pw_stream_flags flags = 0; enum pw_stream_flags flags = 0;
pw_init(&argc, &argv); pw_init(&argc, &argv);
@ -1339,9 +1369,6 @@ int main(int argc, char *argv[])
else else
prog = argv[0]; prog = argv[0];
data.default_source = SPA_ID_INVALID;
data.default_sink = SPA_ID_INVALID;
/* prime the mode from the program name */ /* prime the mode from the program name */
if (!strcmp(prog, "pw-play")) if (!strcmp(prog, "pw-play"))
data.mode = mode_playback; data.mode = mode_playback;
@ -1676,9 +1703,10 @@ int main(int argc, char *argv[])
exit_code = EXIT_SUCCESS; exit_code = EXIT_SUCCESS;
} else { } else {
if (data.targets_listed) { if (data.targets_listed) {
uint32_t default_id; struct target *target, *target_default;
char *default_name;
default_id = (data.mode == mode_record) ? default_name = (data.mode == mode_record) ?
data.default_source : data.default_sink; data.default_source : data.default_sink;
exit_code = EXIT_SUCCESS; exit_code = EXIT_SUCCESS;
@ -1687,12 +1715,12 @@ int main(int argc, char *argv[])
target_default = NULL; target_default = NULL;
spa_list_for_each(target, &data.targets, link) { spa_list_for_each(target, &data.targets, link) {
if (target_default == NULL || if (target_default == NULL ||
default_id == target->id || strcmp(default_name, target->name) == 0 ||
(default_id == SPA_ID_INVALID && (default_name[0] == '\0' &&
target->prio > target_default->prio)) target->prio > target_default->prio))
target_default = target; target_default = target;
} }
printf("Available targets (\"*\" denotes default): %d\n", default_id); printf("Available targets (\"*\" denotes default): %s\n", default_name);
spa_list_for_each(target, &data.targets, link) { spa_list_for_each(target, &data.targets, link) {
printf("%s\t%"PRIu32": description=\"%s\" prio=%d\n", printf("%s\t%"PRIu32": description=\"%s\" prio=%d\n",
target == target_default ? "*" : "", target == target_default ? "*" : "",
@ -1703,6 +1731,7 @@ int main(int argc, char *argv[])
/* destroy targets */ /* destroy targets */
while (!spa_list_is_empty(&data.targets)) { while (!spa_list_is_empty(&data.targets)) {
struct target *target;
target = spa_list_last(&data.targets, struct target, link); target = spa_list_last(&data.targets, struct target, link);
spa_list_remove(&target->link); spa_list_remove(&target->link);
target_destroy(target); target_destroy(target);