diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index a51753a8a..3aa73a454 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -146,7 +146,7 @@ static void init_node(struct impl *this, struct node *node, uint32_t id) node->volumes[i] = 1.0; } -static const char *get_hfp_codec_name(unsigned int codec) +static const char *get_hfp_codec_description(unsigned int codec) { switch (codec) { case HFP_AUDIO_CODEC_MSBC: @@ -157,7 +157,7 @@ static const char *get_hfp_codec_name(unsigned int codec) return "unknown"; } -static const char *get_hfp_codec_key(unsigned int codec) +static const char *get_hfp_codec_name(unsigned int codec) { switch (codec) { case HFP_AUDIO_CODEC_MSBC: @@ -172,13 +172,7 @@ static const char *get_codec_name(struct spa_bt_transport *t) { if (t->a2dp_codec != NULL) return t->a2dp_codec->name; - switch (t->codec) { - case HFP_AUDIO_CODEC_MSBC: - return "mSBC"; - case HFP_AUDIO_CODEC_CVSD: - return "CVSD"; - } - return "unknown"; + return get_hfp_codec_name(t->codec); } static void emit_node(struct impl *this, struct spa_bt_transport *t, @@ -864,8 +858,8 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * errno = -EINVAL; return NULL; } - name_and_codec = spa_aprintf("%s-%s", name, get_hfp_codec_key(hfp_codec)); - desc_and_codec = spa_aprintf(desc, ", codec ", get_hfp_codec_name(hfp_codec)); + name_and_codec = spa_aprintf("%s-%s", name, get_hfp_codec_name(hfp_codec)); + desc_and_codec = spa_aprintf(desc, ", codec ", get_hfp_codec_description(hfp_codec)); name = name_and_codec; desc = desc_and_codec; } else { diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index 1c6615867..7210d28ee 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -39,7 +39,7 @@ #define PROTOCOL_FLAG_MASK 0xffff0000u #define PROTOCOL_VERSION_MASK 0x0000ffffu -#define PROTOCOL_VERSION 34 +#define PROTOCOL_VERSION 35 #define NATIVE_COOKIE_LENGTH 256 #define MAX_TAG_SIZE (64*1024) @@ -287,6 +287,9 @@ enum { * BOTH DIRECTIONS */ COMMAND_REGISTER_MEMFD_SHMID, + /* Supported since protocol v35 (15.0) */ + COMMAND_SEND_OBJECT_MESSAGE, + COMMAND_MAX }; diff --git a/src/modules/module-protocol-pulse/manager.c b/src/modules/module-protocol-pulse/manager.c index 184b08035..f03bf78b5 100644 --- a/src/modules/module-protocol-pulse/manager.c +++ b/src/modules/module-protocol-pulse/manager.c @@ -176,6 +176,8 @@ static void object_destroy(struct object *o) pw_proxy_destroy(o->this.proxy); if (o->this.props) pw_properties_free(o->this.props); + if (o->this.message_object_path) + free(o->this.message_object_path); clear_params(&o->this.param_list, SPA_ID_INVALID); clear_params(&o->pending_list, SPA_ID_INVALID); spa_list_consume(d, &o->data_list, link) { diff --git a/src/modules/module-protocol-pulse/manager.h b/src/modules/module-protocol-pulse/manager.h index 5d79c293a..8560b6da1 100644 --- a/src/modules/module-protocol-pulse/manager.h +++ b/src/modules/module-protocol-pulse/manager.h @@ -79,6 +79,9 @@ struct pw_manager_object { uint32_t version; struct pw_properties *props; struct pw_proxy *proxy; + char *message_object_path; + int (*message_handler)(struct pw_manager *m, struct pw_manager_object *o, + const char *message, const char *params, char **response); int changed; void *info; diff --git a/src/modules/module-protocol-pulse/message-handler.c b/src/modules/module-protocol-pulse/message-handler.c new file mode 100644 index 000000000..dd2cfa74f --- /dev/null +++ b/src/modules/module-protocol-pulse/message-handler.c @@ -0,0 +1,212 @@ +static int bluez_card_object_message_handler(struct pw_manager *m, struct pw_manager_object *o, const char *message, const char *params, char **response) +{ + struct card_info card_info = CARD_INFO_INIT; + uint32_t n_profiles; + struct profile_info *profile_info; + const char *prefix; + uint32_t prefix_len; + + /* + * XXX: We just list/switch profiles here, until codec is a separate + * XXX: device param. + */ + + pw_log_debug(NAME "bluez-card %p object message:'%s' params:'%s'", o, message, params); + + collect_card_info(o, &card_info); + profile_info = alloca(card_info.n_profiles * sizeof(*profile_info)); + n_profiles = collect_profile_info(o, &card_info, profile_info); + + if (card_info.active_profile_name && strstr(card_info.active_profile_name, "headset-head-unit") != NULL) + prefix = "headset-head-unit-"; + else + prefix = "a2dp-sink-"; + prefix_len = strlen(prefix); + + if (strcmp(message, "switch-codec") == 0) { + uint32_t i; + uint32_t profile_id = SPA_ID_INVALID; + regex_t re; + regmatch_t matches[2]; + const char *str; + uint32_t str_len; + + /* Parse args */ + if (params == NULL) + return -EINVAL; + if (regcomp(&re, "[:space:]*{\\(.*\\)}[:space:]*", 0) != 0) + return -EIO; + if (regexec(&re, params, SPA_N_ELEMENTS(matches), matches, 0) != 0) { + regfree(&re); + return -EINVAL; + } + regfree(&re); + + str = params + matches[1].rm_so; + str_len = matches[1].rm_eo - matches[1].rm_so; + + /* Find profile corresponding to selected codec */ + for (i = 0; i < n_profiles; ++i) { + if (strncmp(profile_info[i].name, prefix, prefix_len) != 0) + continue; + if (strncmp(profile_info[i].name + prefix_len, str, str_len) == 0 && + strlen(profile_info[i].name) == prefix_len + str_len) { + profile_id = profile_info[i].id; + goto found; + } + } + + found: + if (profile_id != SPA_ID_INVALID) { + char buf[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + + /* Switch profile */ + pw_device_set_param((struct pw_device*)o->proxy, + SPA_PARAM_Profile, 0, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_index, SPA_POD_Int(profile_id), + SPA_PARAM_PROFILE_save, SPA_POD_Bool(true))); + return 0; + } else { + return -EINVAL; + } + } else if (strcmp(message, "list-codecs") == 0) { + uint32_t i; + const char *p; + FILE *r; + size_t size; + regex_t re; + regmatch_t matches[2]; + bool found = false; + + r = open_memstream(response, &size); + if (r == NULL) + return -ENOMEM; + + if (regcomp(&re, "codec \\(.*\\))", 0) != 0) + return -ENOMEM; + + fputc('{', r); + + for (i = 0; i < n_profiles; ++i) { + if (strncmp(profile_info[i].name, prefix, prefix_len) != 0) + continue; + + found = true; + fputc('{', r); + fprintf(r, "{%s}", profile_info[i].name + prefix_len); + + /* Parse codec name from description */ + p = profile_info[i].description; + if (regexec(&re, p, SPA_N_ELEMENTS(matches), matches, 0) == 0) { + fputc('{', r); + fwrite(p + matches[1].rm_so, 1, matches[1].rm_eo - matches[1].rm_so, r); + fputc('}', r); + } else { + fprintf(r, "{%s}", p); + } + + fputc('}', r); + } + + fputc('}', r); + + regfree(&re); + + if (!found) { + fclose(r); + free(*response); + *response = NULL; + return -ENOSYS; + } + + return fclose(r) ? -errno : 0; + } else if (strcmp(message, "get-codec") == 0) { + const char *name = "none"; + FILE *r; + size_t size; + struct pw_manager_object *obj; + + r = open_memstream(response, &size); + if (r == NULL) + return -ENOMEM; + + /* Look up from nodes */ + spa_list_for_each(obj, &m->object_list, link) { + const char *str; + uint32_t card_id = SPA_ID_INVALID; + struct pw_node_info *info; + + if (obj->creating || obj->removing) + continue; + if (!object_is_sink(obj) && !object_is_source_or_monitor(obj)) + continue; + if ((info = obj->info) == NULL || info->props == NULL) + continue; + if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL) + card_id = (uint32_t)atoi(str); + if (card_id != o->id) + continue; + str = spa_dict_lookup(info->props, "api.bluez5.codec"); + if (str) { + name = str; + break; + } + } + + fprintf(r, "{%s}", name); + return fclose(r) ? -errno : 0; + } + + return -ENOSYS; +} + +static int core_object_message_handler(struct pw_manager *m, struct pw_manager_object *o, const char *message, const char *params, char **response) +{ + pw_log_debug(NAME "core %p object message:'%s' params:'%s'", o, message, params); + + if (strcmp(message, "list-handlers") == 0) { + FILE *r; + size_t size; + + r = open_memstream(response, &size); + if (r == NULL) + return -ENOMEM; + + fputc('{', r); + spa_list_for_each(o, &m->object_list, link) { + if (o->message_object_path) + fprintf(r, "{{%s}{%s}}", o->message_object_path, o->type); + } + fputc('}', r); + return fclose(r) ? -errno : 0; + } + + return -ENOSYS; +} + +static void register_object_message_handlers(struct pw_manager_object *o) +{ + const char *str; + + if (o->id == 0) { + free(o->message_object_path); + o->message_object_path = strdup("/core"); + o->message_handler = core_object_message_handler; + return; + } + + if (object_is_card(o) && o->props != NULL && + (str = pw_properties_get(o->props, PW_KEY_DEVICE_API)) != NULL && + strcmp(str, "bluez5") == 0) { + str = pw_properties_get(o->props, PW_KEY_DEVICE_NAME); + if (str) { + free(o->message_object_path); + o->message_object_path = spa_aprintf("/card/%s/bluez", str); + o->message_handler = bluez_card_object_message_handler; + } + return; + } +} diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index dc2c83449..5334bb7be 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -41,6 +41,7 @@ #include #include #include +#include #if HAVE_SYS_VFS_H #include #endif @@ -282,6 +283,7 @@ struct impl { #include "collect.c" #include "module.c" +#include "message-handler.c" static void sample_free(struct sample *sample) @@ -900,6 +902,8 @@ static void manager_added(void *data, struct pw_manager_object *o) uint32_t event, id; const char *str; + register_object_message_handlers(o); + if (strcmp(o->type, PW_TYPE_INTERFACE_Metadata) == 0) { if (o->props != NULL && (str = pw_properties_get(o->props, PW_KEY_METADATA_NAME)) != NULL) @@ -5196,6 +5200,65 @@ static int do_unload_module(struct client *client, uint32_t command, uint32_t ta return reply_simple_ack(client, tag); } +static int do_send_object_message(struct client *client, uint32_t command, uint32_t tag, struct message *m) +{ + struct impl *impl = client->impl; + struct pw_manager *manager = client->manager; + const char *object_path = NULL; + const char *message = NULL; + const char *params = NULL; + char *response = NULL; + char *path = NULL; + struct message *reply; + struct pw_manager_object *o; + int len = 0; + int res; + + if ((res = message_get(m, + TAG_STRING, &object_path, + TAG_STRING, &message, + TAG_STRING, ¶ms, + TAG_INVALID)) < 0) + return -EPROTO; + + pw_log_info(NAME" %p: [%s] %s tag:%u object_path:'%s' message:'%s' params:'%s'", impl, + client->name, commands[command].name, tag, object_path, + message, params ? params : ""); + + if (object_path == NULL || message == NULL) + return -EINVAL; + + len = strlen(object_path); + if (len > 0 && object_path[len - 1] == '/') + --len; + path = strndup(object_path, len); + if (path == NULL) + return -ENOMEM; + + res = -ENOENT; + + spa_list_for_each(o, &manager->object_list, link) { + if (o->message_object_path && strcmp(o->message_object_path, path) == 0) { + if (o->message_handler) + res = o->message_handler(manager, o, message, params, &response); + else + res = -ENOSYS; + break; + } + } + + free(path); + if (res < 0) + return res; + + pw_log_debug(NAME" %p: object message response:'%s'", impl, response ? response : ""); + + reply = reply_new(client, tag); + message_put(reply, TAG_STRING, response, TAG_INVALID); + free(response); + return send_message(client, reply); +} + static int do_error_access(struct client *client, uint32_t command, uint32_t tag, struct message *m) { return -EACCES; @@ -5364,6 +5427,9 @@ static const struct command commands[COMMAND_MAX] = /* Supported since protocol v31 (9.0) * BOTH DIRECTIONS */ [COMMAND_REGISTER_MEMFD_SHMID] = { "REGISTER_MEMFD_SHMID", do_error_access, }, + + /* Supported since protocol v35 (15.0) */ + [COMMAND_SEND_OBJECT_MESSAGE] = { "SEND_OBJECT_MESSAGE", do_send_object_message, }, }; static int client_free_stream(void *item, void *data)