From 4cdc0053c01f83b4b1c55042e594e1951a084c4f Mon Sep 17 00:00:00 2001 From: Georg Chini Date: Tue, 14 Jan 2020 10:15:36 +0100 Subject: [PATCH] protocol-native: add message sending capability This patch adds the PA_COMMAND_SEND_OBJECT_MESSAGE command to protocol-native so that clients can use the messaging feature introduced in the previous patch. Sending messages can in effect replace the extension system for modules. The approach is more flexible than the extension interface because a generic string format is used to exchange information. Furthermore the messaging system can be used for any object, not only for modules, and is easier to implement than extensions. Part-of: --- PROTOCOL | 17 +++++++++ configure.ac | 2 +- meson.build | 2 +- src/map-file | 1 + src/pulse/introspect.c | 64 +++++++++++++++++++++++++++++++++ src/pulse/introspect.h | 16 +++++++++ src/pulsecore/native-common.h | 3 ++ src/pulsecore/pdispatch.c | 3 ++ src/pulsecore/protocol-native.c | 52 +++++++++++++++++++++++++++ 9 files changed, 158 insertions(+), 2 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 4307898c2..72d3af3c0 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -435,6 +435,23 @@ sink, source and card ports): string availability_group uint32 type +## v35, implemented by >= 15.0 + +Added new command for communication with objects. + +PA_COMMAND_SEND_OBJECT_MESSAGE: +sends a message to an object identified by an object path + +parameters: + string object_path - unique path identifying the object + string message - message name + string message_parameters - additional parameters if required (may be + NULL, which should be treated the same as an + empty string) + +The command returns a string, which may be empty or NULL (NULL should be +treated the same as an empty string). + #### If you just changed the protocol, read this ## module-tunnel depends on the sink/source/sink-input/source-input protocol ## internals, so if you changed these, you might have broken module-tunnel. diff --git a/configure.ac b/configure.ac index 6e6049fec..fb1b518e9 100644 --- a/configure.ac +++ b/configure.ac @@ -42,7 +42,7 @@ AC_SUBST(PA_MINOR, pa_minor) AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor) AC_SUBST(PA_API_VERSION, 12) -AC_SUBST(PA_PROTOCOL_VERSION, 34) +AC_SUBST(PA_PROTOCOL_VERSION, 35) # The stable ABI for client applications, for the version info x:y:z # always will hold x=z diff --git a/meson.build b/meson.build index 683f40e1f..06c44d748 100644 --- a/meson.build +++ b/meson.build @@ -19,7 +19,7 @@ endif pa_version_major_minor = pa_version_major + '.' + pa_version_minor pa_api_version = 12 -pa_protocol_version = 34 +pa_protocol_version = 35 # The stable ABI for client applications, for the version info x:y:z # always will hold x=z diff --git a/src/map-file b/src/map-file index b0cd1bfc4..ea3f70268 100644 --- a/src/map-file +++ b/src/map-file @@ -87,6 +87,7 @@ pa_context_remove_autoload_by_name; pa_context_remove_sample; pa_context_rttime_new; pa_context_rttime_restart; +pa_context_send_message_to_object; pa_context_set_card_profile_by_index; pa_context_set_card_profile_by_name; pa_context_set_default_sink; diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index 3027f38ed..7fefb9ce0 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -2205,3 +2205,67 @@ pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, in return o; } + +/** Object response string processing **/ + +static void context_string_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + const char *response; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + success = 0; + response = ""; + } else if (pa_tagstruct_gets(t, &response) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (!response) + response = ""; + + if (o->callback) { + pa_context_string_cb_t cb = (pa_context_string_cb_t) o->callback; + cb(o->context, success, response, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_send_message_to_object(pa_context *c, const char *object_path, const char *message, const char *message_parameters, pa_context_string_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SEND_OBJECT_MESSAGE, &tag); + + pa_tagstruct_puts(t, object_path); + pa_tagstruct_puts(t, message); + pa_tagstruct_puts(t, message_parameters); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_string_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index c547bde09..fae44a7d1 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -204,6 +204,12 @@ * Server modules can be remotely loaded and unloaded using * pa_context_load_module() and pa_context_unload_module(). * + * \subsection message_subsec Messages + * + * Server objects like sinks, sink inputs or modules can register a message + * handler to communicate with clients. A message can be sent to a named + * message handler using pa_context_send_message_to_object(). + * * \subsection client_subsec Clients * * The only operation supported on clients is the possibility of kicking @@ -489,6 +495,16 @@ pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_s /** @} */ +/** @{ \name Messages */ + +/** Callback prototype for pa_context_send_message_to_object() \since 15.0 */ +typedef void (*pa_context_string_cb_t)(pa_context *c, int success, const char *response, void *userdata); + +/** Send a message to an object that registered a message handler. \since 15.0 */ +pa_operation* pa_context_send_message_to_object(pa_context *c, const char *recipient_name, const char *message, const char *message_parameters, pa_context_string_cb_t cb, void *userdata); + +/** @} */ + /** @{ \name Clients */ /** Stores information about clients. Please note that this structure diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index 70338b9f3..3de960def 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -187,6 +187,9 @@ enum { * BOTH DIRECTIONS */ PA_COMMAND_REGISTER_MEMFD_SHMID, + /* Supported since protocol v34 (14.0) */ + PA_COMMAND_SEND_OBJECT_MESSAGE, + PA_COMMAND_MAX }; diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index ab632a5ab..c2d26e63e 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -199,6 +199,9 @@ static const char *command_names[PA_COMMAND_MAX] = { /* Supported since protocol v31 (9.0) */ /* BOTH DIRECTIONS */ [PA_COMMAND_REGISTER_MEMFD_SHMID] = "REGISTER_MEMFD_SHMID", + + /* Supported since protocol v35 (15.0) */ + [PA_COMMAND_SEND_OBJECT_MESSAGE] = "SEND_OBJECT_MESSAGE", }; #endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index e8559b239..f8dad57d6 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -4721,6 +4722,55 @@ static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, protocol_error(c); } +/* Send message to an object which registered a handler. Result must be returned as string. */ +static void command_send_object_message(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const char *object_path = NULL; + const char *message = NULL; + const char *message_parameters = NULL; + const char *client_name; + char *response = NULL; + int ret; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &object_path) < 0 || + pa_tagstruct_gets(t, &message) < 0 || + pa_tagstruct_gets(t, &message_parameters) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, object_path != NULL, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_utf8_valid(object_path), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, message != NULL, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_utf8_valid(message), tag, PA_ERR_INVALID); + if (message_parameters) + CHECK_VALIDITY(c->pstream, pa_utf8_valid(message_parameters), tag, PA_ERR_INVALID); + + client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); + pa_log_debug("Client %s sent message %s to path %s", client_name, message, object_path); + if (message_parameters) + pa_log_debug("Message parameters: %s", message_parameters); + + ret = pa_message_handler_send_message(c->protocol->core, object_path, message, message_parameters, &response); + + if (ret < 0) { + pa_pstream_send_error(c->pstream, tag, -ret); + return; + } + + reply = reply_new(tag); + pa_tagstruct_puts(reply, response); + pa_xfree(response); + + pa_pstream_send_tagstruct(c->pstream, reply); +} + static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); uint32_t idx = PA_INVALID_INDEX; @@ -4972,6 +5022,8 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid, + [PA_COMMAND_SEND_OBJECT_MESSAGE] = command_send_object_message, + [PA_COMMAND_EXTENSION] = command_extension };