diff --git a/pipewire-alsa/alsa-plugins/ctl_pipewire.c b/pipewire-alsa/alsa-plugins/ctl_pipewire.c index 603dac2f2..8cf898f75 100644 --- a/pipewire-alsa/alsa-plugins/ctl_pipewire.c +++ b/pipewire-alsa/alsa-plugins/ctl_pipewire.c @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -62,11 +63,11 @@ typedef struct { int pending_seq; int error; - uint32_t sink; + char default_sink[1024]; int sink_muted; struct volume sink_volume; - uint32_t source; + char default_source[1024]; int source_muted; struct volume source_volume; @@ -153,16 +154,25 @@ static int wait_resync(snd_ctl_pipewire_t *ctl) 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; + uint32_t name_id = name ? (uint32_t)atoi(name) : SPA_ID_INVALID; + const char *str; + 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)) 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; } + static struct global *find_best_node(snd_ctl_pipewire_t *ctl, uint32_t flags) { struct global *g, *best = NULL; @@ -202,13 +212,13 @@ static int pipewire_update_volume(snd_ctl_pipewire_t * ctl) bool changed = false; struct global *g; - if (ctl->sink == 0) + if (ctl->default_sink[0] == '\0') g = find_best_node(ctl, NODE_FLAG_SINK); 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) { - ctl->sink = g->id; if (!!ctl->sink_muted != !!g->node.mute) { ctl->sink_muted = g->node.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); 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) { - ctl->source = g->id; if (!!ctl->source_muted != !!g->node.mute) { ctl->source_muted = g->node.mute; ctl->updated |= UPDATE_SOURCE_MUTE; @@ -270,9 +280,9 @@ static int pipewire_elem_count(snd_ctl_ext_t * ext) goto finish; } - if (ctl->source) + if (ctl->default_source[0] != '\0') count += 2; - if (ctl->sink) + if (ctl->default_sink[0] != '\0') count += 2; finish: @@ -300,7 +310,7 @@ static int pipewire_elem_list(snd_ctl_ext_t * ext, unsigned int offset, if (err < 0) goto finish; - if (ctl->source) { + if (ctl->default_source[0] != '\0') { if (offset == 0) snd_ctl_elem_id_set_name(id, SOURCE_VOL_NAME); 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]); } -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; 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 *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) return -EINVAL; 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) id = dg->device.active_route_output; 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]; if (key == 0) - set_volume_mute(ctl, ctl->source, vol, NULL); + set_volume_mute(ctl, ctl->default_source, vol, NULL); else - set_volume_mute(ctl, ctl->sink, vol, NULL); + set_volume_mute(ctl, ctl->default_sink, vol, NULL); } else { 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 - set_volume_mute(ctl, ctl->sink, NULL, &ctl->sink_muted); + set_volume_mute(ctl, ctl->default_sink, NULL, &ctl->sink_muted); } 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; } - if (ctl->source) + if (ctl->default_source[0] != '\0') offset = 2; else offset = 0; @@ -968,6 +978,29 @@ struct global_info node_info = { }; /** 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, uint32_t subject, const char *key, @@ -978,11 +1011,20 @@ static int metadata_property(void *object, snd_ctl_pipewire_t *ctl = g->ctl; if (subject == PW_ID_CORE) { - uint32_t val = (key && value) ? (uint32_t)atoi(value) : 0; - if (key == NULL || strcmp(key, "default.audio.sink") == 0) - ctl->sink = val; - if (key == NULL || strcmp(key, "default.audio.source") == 0) - ctl->source = val; + if (key == NULL || strcmp(key, "default.audio.sink") == 0) { + if (value == NULL || + json_object_find(value, "name", + ctl->default_sink, sizeof(ctl->default_sink)) < 0) + 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; } @@ -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) { snd_ctl_pipewire_t *ctl = data; - if (ctl->sink == id) - ctl->sink = 0; - if (ctl->source == id) - ctl->source = 0; + struct global *g; + const char *name; + + 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 = { @@ -1229,25 +1279,16 @@ SND_CTL_PLUGIN_DEFINE_FUNC(pipewire) spa_list_init(&ctl->globals); - if (source) - ctl->source = atoi(source); - else if (device) - ctl->source = atoi(device); - - if ((source || device) && !ctl->source) { - err = -EINVAL; - goto error; - } - - if (sink) - ctl->sink = atoi(sink); - else if (device) - ctl->sink = atoi(device); - - if ((sink || device) && !ctl->sink) { - err = -EINVAL; - goto error; - } + if (source == NULL) + source = device; + if (source != NULL) + snprintf(ctl->default_source, sizeof(ctl->default_source), + "%s", source); + if (sink == NULL) + sink = device; + if (sink != NULL) + snprintf(ctl->default_sink, sizeof(ctl->default_sink), + "%s", sink); ctl->mainloop = pw_thread_loop_new("alsa-pipewire", NULL); if (ctl->mainloop == NULL) { diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index f8930c876..e5834eaf4 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -110,6 +111,7 @@ struct object { union { struct { char name[JACK_CLIENT_NAME_SIZE+1]; + char node_name[512]; int32_t priority; uint32_t client_id; } node; @@ -132,6 +134,7 @@ struct object { int32_t priority; struct port *port; bool is_monitor; + struct object *node; } port; }; }; @@ -241,8 +244,8 @@ struct metadata { struct pw_metadata *proxy; struct spa_hook listener; - uint32_t default_audio_sink; - uint32_t default_audio_source; + char default_audio_sink[1024]; + char default_audio_source[1024]; }; struct client { @@ -548,6 +551,21 @@ static struct object *find_node(struct client *c, const char *name) 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) { 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.alias2, name) == 0) return o; - if (c->metadata && - (o->port.node_id == c->metadata->default_audio_source || - o->port.node_id == c->metadata->default_audio_sink) && - strcmp(o->port.system, name) == 0) + if (is_port_default(c, o) && strcmp(o->port.system, name) == 0) return o; } return NULL; @@ -2037,6 +2052,29 @@ static jack_uuid_t client_make_uuid(uint32_t id) 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, 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); 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) - c->metadata->default_audio_sink = val; - if (key == NULL || strcmp(key, "default.audio.source") == 0) - c->metadata->default_audio_source = val; + if (key == NULL || strcmp(key, "default.audio.sink") == 0) { + if (value != NULL) { + if (json_object_find(value, "name", + c->metadata->default_audio_sink, + 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 { pthread_mutex_lock(&c->context.lock); 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) 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); @@ -2245,6 +2300,7 @@ static void registry_event_global(void *data, uint32_t id, o->port.port_id = SPA_ID_INVALID; o->port.priority = ot->node.priority; + o->port.node = ot; } 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->proxy = (struct pw_metadata*)proxy; - c->metadata->default_audio_sink = SPA_ID_INVALID; - c->metadata->default_audio_source = SPA_ID_INVALID; + c->metadata->default_audio_sink[0] = '\0'; + c->metadata->default_audio_source[0] = '\0'; pw_metadata_add_listener(proxy, &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); - 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); o = pw_map_lookup(&c->context.globals, id); pthread_mutex_unlock(&c->context.lock); @@ -2376,6 +2425,12 @@ static void registry_event_global_remove(void *object, uint32_t id) switch (o->type) { 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) c->registration_callback(o->node.name, 0, c->registration_arg); break; @@ -4255,15 +4310,24 @@ static int port_compare_func(const void *v1, const void *v2) !(*o2)->port.is_monitor; if (c->metadata) { + struct object *ot1, *ot2; + + ot1 = (*o1)->port.node; + 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) - 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) - 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) - 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) res = (*o1)->port.type_id - (*o2)->port.type_id; diff --git a/src/examples/media-session/default-nodes.c b/src/examples/media-session/default-nodes.c index 87f9f40b8..fea8ef6dd 100644 --- a/src/examples/media-session/default-nodes.c +++ b/src/examples/media-session/default-nodes.c @@ -71,8 +71,20 @@ struct impl { struct default_node defaults[4]; 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 impl *impl; const char *name; @@ -94,32 +106,34 @@ static int find_name(void *data, struct sm_object *object) return 0; } -#if 0 static uint32_t find_id_for_name(struct impl *impl, const char *name) { struct find_data d = { impl, name, SPA_ID_INVALID }; sm_media_session_for_each_object(impl->session, find_name, &d); 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; - const char *str; + struct spa_json it[2]; + const char *v; + char k[128]; - if (id == SPA_ID_INVALID) - return NULL; + spa_json_init(&it[0], obj, strlen(obj)); + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + return -EINVAL; - obj = sm_media_session_find_object(impl->session, id); - if (obj == NULL) - return NULL; - - if (strcmp(obj->type, PW_TYPE_INTERFACE_Node) == 0 && - obj->props && - (str = pw_properties_get(obj->props, PW_KEY_NODE_NAME)) != NULL) - return str; - return NULL; + 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 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) { struct impl *impl = object; - uint32_t val; - bool changed = false; + int changed = 0; + + if (impl->sync) + return 0; if (subject == PW_ID_CORE) { - struct default_node *def; - val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID; - for (def = impl->defaults; def->key != NULL; ++def) { - if (key == NULL || strcmp(key, def->key) == 0) { - changed = (def->value != val); - def->value = val; + if (key == NULL) { + pw_properties_clear(impl->properties); + changed++; + } else { + uint32_t id; + 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) { - 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); + if (changed) add_idle_timeout(impl); - } + return 0; } @@ -198,42 +226,22 @@ static void session_create(void *data, struct sm_object *object) return; spa_dict_for_each(item, &impl->properties->dict) { - struct spa_json it[2]; - const char *value; - char name [1024] = "\0", key[128]; + char name [1024] = "\0"; struct find_data d; - spa_json_init(&it[0], item->value, strlen(item->value)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) + if (find_default(impl, item->key) == NULL) + continue; + + if (json_object_find(item->value, "name", name, sizeof(name)) < 0) 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 }; 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) { - char val[16]; - snprintf(val, sizeof(val), "%u", d.id); - pw_log_info("found %s with id:%s restore as %s", - name, val, item->key); + pw_log_info("found %s with id:%u restore as %s", + name, d.id, item->key); 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->context = session->context; - impl->defaults[0] = (struct default_node){ DEFAULT_CONFIG_AUDIO_SINK_KEY, SPA_ID_INVALID }; - impl->defaults[1] = (struct default_node){ DEFAULT_CONFIG_AUDIO_SOURCE_KEY, SPA_ID_INVALID }; - impl->defaults[2] = (struct default_node){ DEFAULT_CONFIG_VIDEO_SOURCE_KEY, SPA_ID_INVALID }; - impl->defaults[3] = (struct default_node){ NULL, 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, }; + impl->defaults[2] = (struct default_node){ DEFAULT_CONFIG_VIDEO_SOURCE_KEY, }; + impl->defaults[3] = (struct default_node){ NULL, }; impl->properties = pw_properties_new(NULL, NULL); if (impl->properties == NULL) { diff --git a/src/examples/media-session/policy-node.c b/src/examples/media-session/policy-node.c index 3bcab7df4..94098330f 100644 --- a/src/examples/media-session/policy-node.c +++ b/src/examples/media-session/policy-node.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "pipewire/pipewire.h" #include "extensions/metadata.h" @@ -60,8 +61,8 @@ struct default_node { char *key; char *key_config; - uint32_t value; - uint32_t config; + char *value; + char *config; }; 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); } -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; + uint32_t name_id = name ? atoi(name) : SPA_ID_INVALID; + 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 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) { - struct default_node *def; struct impl *impl = data; 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) 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); } @@ -491,14 +531,18 @@ static int find_node(void *data, struct node *node) if (node->media) { bool is_default = false; + if (strcmp(node->media, "Audio") == 0) { 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) - 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) { 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) priority += 10000; @@ -813,19 +857,30 @@ static void refresh_auto_default_nodes(struct impl *impl) if (impl->session->metadata == NULL) return; + pw_log_debug(NAME" %p: refresh", impl); + /* Auto set default nodes */ for (def = impl->defaults; def->key != NULL; ++def) { struct node *node; node = find_auto_default_node(impl, def); - if (node == NULL && def->value != SPA_ID_INVALID) { - def->value = SPA_ID_INVALID; - pw_metadata_set_property(impl->session->metadata, PW_ID_CORE, def->key, NULL, NULL); - } else if (node != NULL && def->value != node->id) { - char buf[64]; - def->value = node->id; - snprintf(buf, sizeof(buf), "%d", node->id); - pw_metadata_set_property(impl->session->metadata, PW_ID_CORE, def->key, - SPA_TYPE_INFO_BASE"Id", buf); + if (node == NULL && def->value != NULL) { + def->value = NULL; + pw_metadata_set_property(impl->session->metadata, + PW_ID_CORE, def->key, NULL, NULL); + } else if (node != NULL) { + const char *name = pw_properties_get(node->obj->obj.props, PW_KEY_NODE_NAME); + char buf[1024]; + + 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) { struct impl *impl = object; - uint32_t val = (key && value) ? (uint32_t)atoi(value) : SPA_ID_INVALID; if (subject == PW_ID_CORE) { struct default_node *def; 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) { if (key == NULL || strcmp(key, def->key_config) == 0) { - if (def->config != val) + if (strzcmp(def->config, val) != 0) changed = true; - def->config = val; + free(def->config); + def->config = val ? strdup(val) : NULL; } if (key == NULL || strcmp(key, def->key) == 0) { - bool eff_changed = (def->value != val); - def->value = val; + bool eff_changed = strzcmp(def->value, val) != 0; + free(def->value); + def->value = val ? strdup(val) : NULL; /* The effective value was changed. In case it was changed by * 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); } } - if (changed) sm_media_session_schedule_rescan(impl->session); - } else { - if (val != SPA_ID_INVALID && strcmp(key, "target.node") == 0) { + } else if (key != NULL && strcmp(key, "target.node") == 0) { + if (value != NULL) { struct node *src_node, *dst_node; - dst_node = find_node_by_id(impl, val); - src_node = dst_node ? find_node_by_id(impl, subject) : NULL; + dst_node = find_node_by_id_name(impl, SPA_ID_INVALID, value); + src_node = dst_node ? find_node_by_id_name(impl, subject, NULL) : NULL; if (dst_node && src_node) handle_move(impl, src_node, dst_node); - } else if (val == SPA_ID_INVALID && key != NULL && - strcmp(key, "target.node") == 0) { + } else { /* Unset target node. Schedule rescan to re-link, if needed. */ 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) { free(src_node->obj->target_node); 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->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){ - 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){ - 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"); impl->streams_follow_default = (flag != NULL && pw_properties_parse_bool(flag)); diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index c146d3b20..ae2bcb413 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -139,8 +139,8 @@ struct client { uint32_t subscribed; struct pw_manager_object *metadata_default; - uint32_t default_sink; - uint32_t default_source; + char *default_sink; + char *default_source; struct pw_manager_object *metadata_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)); } +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, uint32_t subject, const char *key, const char *type, const char *value) { struct client *client = data; - uint32_t val; bool changed = false; pw_log_debug("meta id:%d subject:%d key:%s type:%s value:%s", o->id, subject, key, type, value); 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) { - changed = client->default_sink != val; - client->default_sink = val; + if (value != NULL) { + 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) { - changed = client->default_source != val; - client->default_source = val; + if (value != NULL) { + 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) 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); if (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; } else { 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; } 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, PW_ID_CORE, 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 reply_simple_ack(client, tag); @@ -5280,6 +5332,8 @@ static void client_free(struct client *client) client->disconnecting = true; pw_core_disconnect(client->core); } + free(client->default_sink); + free(client->default_source); if (client->props) pw_properties_free(client->props); if (client->routes) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 821e9c5fd..29b51a044 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -105,8 +106,8 @@ struct data { struct spa_hook registry_listener; struct pw_metadata *metadata; struct spa_hook metadata_listener; - uint32_t default_sink; - uint32_t default_source; + char default_sink[1024]; + char default_source[1024]; struct pw_stream *stream; struct spa_hook stream_listener; @@ -647,17 +648,47 @@ static const struct pw_core_events core_events = { .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, uint32_t subject, const char *key, const char *type, const char *value) { struct data *data = object; 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) - data->default_sink = val; - if (key == NULL || strcmp(key, "default.audio.source") == 0) - data->default_source = val; + if (key == NULL || strcmp(key, "default.audio.sink") == 0) { + if (value == NULL || + json_object_find(value, "name", + data->default_sink, sizeof(data->default_sink)) < 0) + 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; } @@ -1326,7 +1357,6 @@ int main(int argc, char *argv[]) struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const char *prog; int exit_code = EXIT_FAILURE, c, ret; - struct target *target, *target_default; enum pw_stream_flags flags = 0; pw_init(&argc, &argv); @@ -1339,9 +1369,6 @@ int main(int argc, char *argv[]) else prog = argv[0]; - data.default_source = SPA_ID_INVALID; - data.default_sink = SPA_ID_INVALID; - /* prime the mode from the program name */ if (!strcmp(prog, "pw-play")) data.mode = mode_playback; @@ -1676,9 +1703,10 @@ int main(int argc, char *argv[]) exit_code = EXIT_SUCCESS; } else { 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; exit_code = EXIT_SUCCESS; @@ -1687,12 +1715,12 @@ int main(int argc, char *argv[]) target_default = NULL; spa_list_for_each(target, &data.targets, link) { if (target_default == NULL || - default_id == target->id || - (default_id == SPA_ID_INVALID && + strcmp(default_name, target->name) == 0 || + (default_name[0] == '\0' && target->prio > target_default->prio)) 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) { printf("%s\t%"PRIu32": description=\"%s\" prio=%d\n", target == target_default ? "*" : "", @@ -1703,6 +1731,7 @@ int main(int argc, char *argv[]) /* destroy targets */ while (!spa_list_is_empty(&data.targets)) { + struct target *target; target = spa_list_last(&data.targets, struct target, link); spa_list_remove(&target->link); target_destroy(target);