pulse-server: implement send_object_message

Use it for providing Bluez codec listing/switching interface.  It
currently works by just switching device profiles.
This commit is contained in:
Pauli Virtanen 2021-03-20 00:11:12 +02:00
parent eb9b787db1
commit 132786c202
6 changed files with 292 additions and 12 deletions

View file

@ -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
};

View file

@ -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) {

View file

@ -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;

View file

@ -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;
}
}

View file

@ -41,6 +41,7 @@
#include <sys/time.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <regex.h>
#if HAVE_SYS_VFS_H
#include <sys/vfs.h>
#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, &params,
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 : "<null>");
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 : "<null>");
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)