[pulseaudio-discuss] [PATCH 1/2] protocol-native: Add commands for communication with modules
Georg Chini
georg at chini.tk
Sun May 21 12:56:34 UTC 2017
This patch adds two new commands to the native protocol that enable direct
communication with modules during run time. SEND_MODULE_COMMAND is used to
send a command string to a module if no reply is needed. This can for example
be employed to change parameters on the fly. SEND_MODULE_QUERY is similar to
SEND_MODULE_COMMAND but expects a reply string from the module. This command
can for example be used to query the current value of parameters.
Within the module, the functions set_command_callback() and get_command_callback()
need to be implemented to handle the two types of requests.
This is a rather generic interface because it only passes strings between the
module and a client without making any assumptions about the content of the
strings.
---
PROTOCOL | 10 ++++
configure.ac | 2 +-
src/map-file | 4 ++
src/pulse/introspect.c | 112 ++++++++++++++++++++++++++++++++++++++++
src/pulse/introspect.h | 15 ++++++
src/pulsecore/module.c | 29 +++++++++++
src/pulsecore/module.h | 8 +++
src/pulsecore/native-common.h | 3 ++
src/pulsecore/pdispatch.c | 3 ++
src/pulsecore/protocol-native.c | 91 ++++++++++++++++++++++++++++++++
10 files changed, 276 insertions(+), 1 deletion(-)
diff --git a/PROTOCOL b/PROTOCOL
index 546998b7..1abd7c32 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -420,6 +420,16 @@ memfd support only to 10.0+ clients.
Check commit 451d1d676237c81 for further details.
+## v33, implemented by > 11.0
+
+Added new commands for communication with modules.
+
+PA_COMMAND_SEND_MODULE_COMMAND:
+sends a string to a module without reply
+
+PA_COMMAND_SEND_MODULE_QUERY:
+sends a string to a module and receives a reply string from the module
+
#### 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 da0bfcd5..520549bd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,7 +40,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, 32)
+AC_SUBST(PA_PROTOCOL_VERSION, 33)
# The stable ABI for client applications, for the version info x:y:z
# always will hold y=z
diff --git a/src/map-file b/src/map-file
index 93a62b86..69df5a46 100644
--- a/src/map-file
+++ b/src/map-file
@@ -118,6 +118,10 @@ pa_context_suspend_sink_by_name;
pa_context_suspend_source_by_index;
pa_context_suspend_source_by_name;
pa_context_unload_module;
+pa_context_send_module_command_by_name;
+pa_context_send_module_command_by_index;
+pa_context_send_module_query_by_name;
+pa_context_send_module_query_by_index;
pa_context_unref;
pa_cvolume_avg;
pa_cvolume_avg_mask;
diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
index 510d784a..6b14bed3 100644
--- a/src/pulse/introspect.c
+++ b/src/pulse/introspect.c
@@ -2184,3 +2184,115 @@ pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, in
return o;
}
+
+static pa_operation* send_module_command_by_index_or_name(pa_context *c, uint32_t idx, const char *module_name, const char *command, pa_context_success_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_MODULE_COMMAND, &tag);
+
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, module_name);
+ pa_tagstruct_puts(t, command);
+
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_send_module_command_by_name(pa_context *c, const char *module_name, const char *command, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ o = send_module_command_by_index_or_name(c, PA_INVALID_INDEX, module_name, command, cb, userdata);
+ return o;
+}
+
+pa_operation* pa_context_send_module_command_by_index(pa_context *c, uint32_t idx, const char *command, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ o = send_module_command_by_index_or_name(c, idx, NULL, command, cb, userdata);
+ return o;
+}
+
+/** Module response string **/
+
+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;
+
+ 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;
+
+ response = NULL;
+ } else if (pa_tagstruct_gets(t, &response) ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_context_string_cb_t cb = (pa_context_string_cb_t) o->callback;
+ cb(o->context, response, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+static pa_operation* send_module_query_by_index_or_name(pa_context *c, uint32_t idx, const char *module_name, const char *query, 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_MODULE_QUERY, &tag);
+
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, module_name);
+ pa_tagstruct_puts(t, query);
+
+ 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;
+}
+
+pa_operation* pa_context_send_module_query_by_name(pa_context *c, const char *module_name, const char *query, pa_context_string_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ o = send_module_query_by_index_or_name(c, PA_INVALID_INDEX, module_name, query, cb, userdata);
+ return o;
+}
+
+pa_operation* pa_context_send_module_query_by_index(pa_context *c, uint32_t idx, const char *query, pa_context_string_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ o = send_module_query_by_index_or_name(c, idx, NULL, query, cb, userdata);
+ return o;
+}
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
index 43389b73..9514b65d 100644
--- a/src/pulse/introspect.h
+++ b/src/pulse/introspect.h
@@ -432,12 +432,27 @@ pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t
/** Callback prototype for pa_context_load_module() */
typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata);
+/** Callback prototype for pa_context_send_module_query_by_{index,name}() */
+typedef void (*pa_context_string_cb_t)(pa_context *c, const char *response, void *userdata);
+
/** Load a module. */
pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata);
/** Unload a module. */
pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+/** Send command string to module by its index. */
+pa_operation* pa_context_send_module_command_by_index(pa_context *c, uint32_t idx, const char *command, pa_context_success_cb_t cb, void *userdata);
+
+/** Send command string to module by its name. */
+pa_operation* pa_context_send_module_command_by_name(pa_context *c, const char *module_name, const char *command, pa_context_success_cb_t cb, void *userdata);
+
+/** Send query string to module by its index. */
+pa_operation* pa_context_send_module_query_by_index(pa_context *c, uint32_t idx, const char *query, pa_context_string_cb_t cb, void *userdata);
+
+/** Send query string to module by its name. */
+pa_operation* pa_context_send_module_query_by_name(pa_context *c, const char *module_name, const char *query, pa_context_string_cb_t cb, void *userdata);
+
/** @} */
/** @{ \name Clients */
diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c
index ac158159..9e89a028 100644
--- a/src/pulsecore/module.c
+++ b/src/pulsecore/module.c
@@ -171,6 +171,8 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE);
m->get_n_used = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_N_USED);
m->userdata = NULL;
+ m->set_command_callback = NULL;
+ m->get_command_callback = NULL;
m->core = c;
m->unload_requested = false;
@@ -393,3 +395,30 @@ void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist
pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index);
pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_PROPLIST_CHANGED], m);
}
+
+/* Find a unique instance of a module. If more than one instance
+ * is loaded, it is considered an error. */
+int pa_module_find_unique_instance(pa_core *c, pa_module **m, uint32_t idx, const char *name) {
+ pa_module *mod_search;
+
+ *m = NULL;
+
+ if (idx != PA_INVALID_INDEX)
+ *m = pa_idxset_get_by_index(c->modules, idx);
+ else {
+ pa_module *mod = NULL;
+
+ PA_IDXSET_FOREACH(mod_search, c->modules, idx) {
+ if (pa_streq(name, mod_search->name)) {
+ if (!mod)
+ mod = mod_search;
+ else {
+ *m = NULL;
+ return -1;
+ }
+ }
+ }
+ *m = mod;
+ }
+ return 0;
+}
diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h
index 41e2189c..1cf9d13e 100644
--- a/src/pulsecore/module.h
+++ b/src/pulsecore/module.h
@@ -41,6 +41,12 @@ struct pa_module {
void (*done)(pa_module*m);
int (*get_n_used)(pa_module *m);
+ /* Functions used for sending commands to the module. set_command_callback()
+ * will only return success/failure while get_command_callback() returns a
+ * string to the caller. May be NULL if the functions are not implemented. */
+ int (*set_command_callback)(pa_module *m, const char *command);
+ int (*get_command_callback)(pa_module *m, char **response, const char *query);
+
void *userdata;
bool load_once:1;
@@ -68,6 +74,8 @@ void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist
void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data);
+int pa_module_find_unique_instance(pa_core *c, pa_module **m, uint32_t idx, const char *name);
+
#define PA_MODULE_AUTHOR(s) \
const char *pa__get_author(void) { return s; } \
struct __stupid_useless_struct_to_allow_trailing_semicolon
diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h
index 70338b9f..7a0716af 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,
+ PA_COMMAND_SEND_MODULE_COMMAND,
+ PA_COMMAND_SEND_MODULE_QUERY,
+
PA_COMMAND_MAX
};
diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c
index ab632a5a..ee29d0c2 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",
+
+ [PA_COMMAND_SEND_MODULE_COMMAND] = "SEND_MODULE_COMMAND",
+ [PA_COMMAND_SEND_MODULE_QUERY] = "SEND_MODULE_QUERY",
};
#endif
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 266b676d..fa1b1472 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -4648,6 +4648,94 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
pa_pstream_send_simple_ack(c->pstream, tag);
}
+/* Send command string to module. */
+static void command_send_module_command(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;
+ const char *name = NULL;
+ const char *command_string = NULL;
+ pa_module *m;
+ int ret;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &command_string) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_utf8_valid(name), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+
+ if (pa_module_find_unique_instance(c->protocol->core, &m, idx, name) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, m->set_command_callback, tag, PA_ERR_NOTSUPPORTED);
+
+
+ if ((ret = m->set_command_callback(m, command_string)) < 0) {
+ pa_pstream_send_error(c->pstream, tag, -ret);
+ return;
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+/* Send query command to module. Result must be returned as string. */
+static void command_send_module_query(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;
+ const char *name = NULL;
+ const char *query_string = NULL;
+ char *response = NULL;
+ pa_module *m;
+ pa_tagstruct *reply;
+ int ret;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &query_string) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_utf8_valid(name), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+
+ if (pa_module_find_unique_instance(c->protocol->core, &m, idx, name) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, m->get_command_callback, tag, PA_ERR_NOTSUPPORTED);
+
+ if ((ret = m->get_command_callback(m, &response, query_string)) < 0) {
+ pa_pstream_send_error(c->pstream, tag, -ret);
+ pa_xfree(response);
+ return;
+ }
+
+ reply = reply_new(tag);
+ pa_tagstruct_puts(reply, (const char *)response);
+ pa_xfree(response);
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
static void command_extension(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;
@@ -4936,6 +5024,9 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
[PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid,
+ [PA_COMMAND_SEND_MODULE_COMMAND] = command_send_module_command,
+ [PA_COMMAND_SEND_MODULE_QUERY] = command_send_module_query,
+
[PA_COMMAND_EXTENSION] = command_extension
};
--
2.11.0
More information about the pulseaudio-discuss
mailing list