message-params: use JSON instead of custom format

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/525>
This commit is contained in:
Igor V. Kovalenko 2021-03-14 09:49:05 +03:00 committed by PulseAudio Marge Bot
parent 0ba768b2e9
commit 1dd05f4a9b
6 changed files with 93 additions and 69 deletions

View file

@ -4,26 +4,18 @@ The message API allows any object within pulseaudio to register a message
handler. A message handler is a function that can be called by clients using handler. A message handler is a function that can be called by clients using
PA_COMMAND_SEND_OBJECT_MESSAGE. A message consists at least of an object path PA_COMMAND_SEND_OBJECT_MESSAGE. A message consists at least of an object path
and a message command, both specified as strings. Additional parameters can and a message command, both specified as strings. Additional parameters can
be specified using a single string, but are not mandatory. The message handler be specified using a single string in JSON format, but are not mandatory.
returns an error number as defined in def.h and also returns a string in
the "response" variable. If the string is not empty it consists of elements.
Curly braces are used to separate elements. Each element can itself contain
further elements. For example consider a message that returns multiple elements
which each contain an integer and an array of float. A response string would
look like that:
{{Integer} {{1st float} {2nd float} ...}}{...}
Any characters that are not enclosed in curly braces are ignored (all characters
between { and {, between } and } and between } and {). The same syntax is used
to specify message parameters. The reference further down lists available messages,
their parameters and return values. If a return value is enclosed in {}, this
means that multiple elements of the same type may be returned.
For string parameters that contain curly braces or backslashes, those characters The message handler returns an error number as defined in def.h and also returns
must be escaped by adding a "\" before them. a string in the "response" variable. Non-empty response will be in JSON format.
The reference further down lists available messages, their parameters
and return values.
Reference: Reference:
Object path: /core Object path: /core
Message: list-handlers Message: list-handlers
Parameters: None Parameters: None
Return value: {{{Handler name} {Description}} ...} Return value: JSON array of handler description objects
[{"name":"Handler name","description":"Description"} ...]

View file

@ -27,6 +27,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <pulse/json.h>
#include <pulse/rtclock.h> #include <pulse/rtclock.h>
#include <pulse/timeval.h> #include <pulse/timeval.h>
#include <pulse/utf8.h> #include <pulse/utf8.h>
@ -2458,7 +2459,7 @@ static char *list_codecs(struct userdata *u) {
const pa_a2dp_codec_capabilities *a2dp_capabilities; const pa_a2dp_codec_capabilities *a2dp_capabilities;
const pa_a2dp_codec_id *key; const pa_a2dp_codec_id *key;
pa_hashmap *a2dp_endpoints; pa_hashmap *a2dp_endpoints;
pa_message_params *param; pa_json_encoder *encoder;
unsigned int i; unsigned int i;
bool is_a2dp_sink; bool is_a2dp_sink;
void *state; void *state;
@ -2467,9 +2468,9 @@ static char *list_codecs(struct userdata *u) {
a2dp_endpoints = is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints; a2dp_endpoints = is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints;
param = pa_message_params_new(); encoder = pa_json_encoder_new();
pa_message_params_begin_list(param); pa_json_encoder_begin_element_array(encoder);
PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, a2dp_endpoints, state) { PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, a2dp_endpoints, state) {
for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) { for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) {
@ -2479,23 +2480,23 @@ static char *list_codecs(struct userdata *u) {
if (memcmp(key, &a2dp_codec->id, sizeof(pa_a2dp_codec_id)) == 0) { if (memcmp(key, &a2dp_codec->id, sizeof(pa_a2dp_codec_id)) == 0) {
if (a2dp_codec->can_be_supported(is_a2dp_sink)) { if (a2dp_codec->can_be_supported(is_a2dp_sink)) {
pa_message_params_begin_list(param); pa_json_encoder_begin_element_object(encoder);
pa_message_params_write_string(param, a2dp_codec->name); pa_json_encoder_add_member_string(encoder, "name", a2dp_codec->name);
pa_message_params_write_string(param, a2dp_codec->description); pa_json_encoder_add_member_string(encoder, "description", a2dp_codec->description);
pa_message_params_end_list(param); pa_json_encoder_end_object(encoder);
} }
} }
} }
} }
pa_message_params_end_list(param); pa_json_encoder_end_array(encoder);
return pa_message_params_to_string_free(param); return pa_json_encoder_to_string_free(encoder);
} }
static int bluez5_device_message_handler(const char *object_path, const char *message, char *message_parameters, char **response, void *userdata) { static int bluez5_device_message_handler(const char *object_path, const char *message, const pa_json_object *parameters, char **response, void *userdata) {
char *message_handler_path; char *message_handler_path;
pa_hashmap *capabilities_hashmap; pa_hashmap *capabilities_hashmap;
pa_bluetooth_profile_t profile; pa_bluetooth_profile_t profile;
@ -2503,8 +2504,6 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
const char *codec_name; const char *codec_name;
struct userdata *u; struct userdata *u;
bool is_a2dp_sink; bool is_a2dp_sink;
void *state = NULL;
int err;
pa_assert(u = (struct userdata *)userdata); pa_assert(u = (struct userdata *)userdata);
pa_assert(message); pa_assert(message);
@ -2539,9 +2538,17 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
} }
if (pa_streq(message, "switch-codec")) { if (pa_streq(message, "switch-codec")) {
err = pa_message_params_read_string(message_parameters, &codec_name, &state); if (!parameters) {
if (err < 0) pa_log_info("Codec switching operation requires codec name string parameter");
return err; return -PA_ERR_INVALID;
}
if (pa_json_object_get_type(parameters) != PA_JSON_TYPE_STRING) {
pa_log_info("Codec name object parameter must be a string");
return -PA_ERR_INVALID;
}
codec_name = pa_json_object_get_string(parameters);
if (u->a2dp_codec && pa_streq(codec_name, u->a2dp_codec->name)) { if (u->a2dp_codec && pa_streq(codec_name, u->a2dp_codec->name)) {
pa_log_info("Requested codec is currently selected codec"); pa_log_info("Requested codec is currently selected codec");
@ -2599,15 +2606,13 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
*response = list_codecs(u); *response = list_codecs(u);
return PA_OK; return PA_OK;
} else if (pa_streq(message, "get-codec")) { } else if (pa_streq(message, "get-codec")) {
pa_message_params *param; pa_json_encoder *encoder;
param = pa_message_params_new(); encoder = pa_json_encoder_new();
if (u->a2dp_codec) if (u->a2dp_codec)
pa_message_params_write_string(param, u->a2dp_codec->name); pa_json_encoder_add_element_string(encoder, u->a2dp_codec->name);
else
pa_message_params_write_string(param, "none");
*response = pa_message_params_to_string_free(param); *response = pa_json_encoder_to_string_free(encoder);
return PA_OK; return PA_OK;
} }

View file

@ -66,29 +66,27 @@ static void core_free(pa_object *o);
/* Returns a list of handlers. */ /* Returns a list of handlers. */
static char *message_handler_list(pa_core *c) { static char *message_handler_list(pa_core *c) {
pa_message_params *param; pa_json_encoder *encoder;
void *state = NULL; void *state = NULL;
struct pa_message_handler *handler; struct pa_message_handler *handler;
param = pa_message_params_new(); encoder = pa_json_encoder_new();
pa_message_params_begin_list(param); pa_json_encoder_begin_element_array(encoder);
PA_HASHMAP_FOREACH(handler, c->message_handlers, state) { PA_HASHMAP_FOREACH(handler, c->message_handlers, state) {
pa_message_params_begin_list(param); pa_json_encoder_begin_element_object(encoder);
/* object_path cannot contain characters that need escaping, therefore pa_json_encoder_add_member_string(encoder, "name", handler->object_path);
* pa_message_params_write_raw() can safely be used here. */ pa_json_encoder_add_member_string(encoder, "description", handler->description);
pa_message_params_write_raw(param, handler->object_path, true);
pa_message_params_write_string(param, handler->description);
pa_message_params_end_list(param); pa_json_encoder_end_object(encoder);
} }
pa_message_params_end_list(param); pa_json_encoder_end_array(encoder);
return pa_message_params_to_string_free(param); return pa_json_encoder_to_string_free(encoder);
} }
static int core_message_handler(const char *object_path, const char *message, char *message_parameters, char **response, void *userdata) { static int core_message_handler(const char *object_path, const char *message, const pa_json_object *parameters, char **response, void *userdata) {
pa_core *c; pa_core *c;
pa_assert(c = (pa_core *) userdata); pa_assert(c = (pa_core *) userdata);

View file

@ -105,7 +105,8 @@ void pa_message_handler_unregister(pa_core *c, const char *object_path) {
int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response) { int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response) {
struct pa_message_handler *handler; struct pa_message_handler *handler;
int ret; int ret;
char *parameter_copy, *path_copy; char *path_copy;
pa_json_object *parameters = NULL;
pa_assert(c); pa_assert(c);
pa_assert(object_path); pa_assert(object_path);
@ -125,14 +126,21 @@ int pa_message_handler_send_message(pa_core *c, const char *object_path, const c
return -PA_ERR_NOENTITY; return -PA_ERR_NOENTITY;
} }
parameter_copy = pa_xstrdup(message_parameters); pa_xfree(path_copy);
if (message_parameters) {
parameters = pa_json_parse(message_parameters);
if (!parameters)
return -PA_ERR_INVALID;
}
/* The handler is expected to return an error code and may also /* The handler is expected to return an error code and may also
return an error string in response */ return an error string in response */
ret = handler->callback(handler->object_path, message, parameter_copy, response, handler->userdata); ret = handler->callback(handler->object_path, message, parameters, response, handler->userdata);
pa_xfree(parameter_copy); if (parameters)
pa_xfree(path_copy); pa_json_object_free(parameters);
return ret; return ret;
} }

View file

@ -19,6 +19,7 @@
***/ ***/
#include <pulsecore/core.h> #include <pulsecore/core.h>
#include <pulse/json.h>
/* Message handler types and functions */ /* Message handler types and functions */
@ -26,7 +27,7 @@
typedef int (*pa_message_handler_cb_t)( typedef int (*pa_message_handler_cb_t)(
const char *object_path, const char *object_path,
const char *message, const char *message,
char *message_parameters, const pa_json_object *parameters,
char **response, char **response,
void *userdata); void *userdata);

View file

@ -37,6 +37,7 @@
#include <pulse/pulseaudio.h> #include <pulse/pulseaudio.h>
#include <pulse/message-params.h> #include <pulse/message-params.h>
#include <pulse/ext-device-restore.h> #include <pulse/ext-device-restore.h>
#include <pulse/json.h>
#include <pulsecore/i18n.h> #include <pulsecore/i18n.h>
#include <pulsecore/macro.h> #include <pulsecore/macro.h>
@ -897,10 +898,10 @@ static void send_message_callback(pa_context *c, int success, char *response, vo
} }
static void list_handlers_callback(pa_context *c, int success, char *response, void *userdata) { static void list_handlers_callback(pa_context *c, int success, char *response, void *userdata) {
void *state = NULL;
char *handler_list;
char *handler_struct;
int err; int err;
pa_json_object *o;
int i;
const pa_json_object *v, *path, *description;
if (!success) { if (!success) {
pa_log(_("list-handlers message failed: %s"), pa_strerror(pa_context_errno(c))); pa_log(_("list-handlers message failed: %s"), pa_strerror(pa_context_errno(c)));
@ -908,29 +909,45 @@ static void list_handlers_callback(pa_context *c, int success, char *response, v
return; return;
} }
if (pa_message_params_read_raw(response, &handler_list, &state) <= 0) { o = pa_json_parse(response);
if (!o) {
pa_log(_("list-handlers message response could not be parsed correctly")); pa_log(_("list-handlers message response could not be parsed correctly"));
pa_json_object_free(o);
quit(1); quit(1);
return; return;
} }
state = NULL; if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY) {
while ((err = pa_message_params_read_raw(handler_list, &handler_struct, &state)) > 0) { pa_log(_("list-handlers message response is not a JSON array"));
void *state2 = NULL; pa_json_object_free(o);
const char *path; quit(1);
const char *description; return;
}
if (pa_message_params_read_string(handler_struct, &path, &state2) <= 0) { err = 0;
for (i = 0; i < pa_json_object_get_array_length(o); ++i) {
v = pa_json_object_get_array_member(o, i);
if (pa_json_object_get_type(v) != PA_JSON_TYPE_OBJECT) {
pa_log(_("list-handlers message response array element %d is not a JSON object"), i);
err = -1; err = -1;
break; break;
} }
if (pa_message_params_read_string(handler_struct, &description, &state2) <= 0) {
path = pa_json_object_get_object_member(v, "name");
if (!path || pa_json_object_get_type(path) != PA_JSON_TYPE_STRING) {
err = -1;
break;
}
description = pa_json_object_get_object_member(v, "description");
if (!description || pa_json_object_get_type(description) != PA_JSON_TYPE_STRING) {
err = -1; err = -1;
break; break;
} }
if (short_list_format) if (short_list_format)
printf("%s\n", path); printf("%s\n", pa_json_object_get_string(path));
else { else {
if (nl) if (nl)
printf("\n"); printf("\n");
@ -938,17 +955,20 @@ static void list_handlers_callback(pa_context *c, int success, char *response, v
printf("Message Handler %s\n" printf("Message Handler %s\n"
"\tDescription: %s\n", "\tDescription: %s\n",
path, pa_json_object_get_string(path),
description); pa_json_object_get_string(description));
} }
} }
if (err < 0) { if (err < 0) {
pa_log(_("list-handlers message response could not be parsed correctly")); pa_log(_("list-handlers message response could not be parsed correctly"));
pa_json_object_free(o);
quit(1); quit(1);
return; return;
} }
pa_json_object_free(o);
complete_action(); complete_action();
} }