[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 17 commits: protocol-native: add message sending capability
PulseAudio Marge Bot
gitlab at gitlab.freedesktop.org
Thu Dec 3 14:48:22 UTC 2020
PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio
Commits:
4cdc0053 by Georg Chini at 2020-12-03T14:41:39+00:00
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: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
68f2f139 by Georg Chini at 2020-12-03T14:41:39+00:00
pactl, pacmd, cli-command: Add send-message command
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
5c0ab521 by Georg Chini at 2020-12-03T14:41:39+00:00
core: add message handler
This patch adds a small message handler to the core which enables
clients to list available handlers via the list-handlers message.
Command: pacmd send-message /core list-handlers
pactl can be used with the same parameters.
The patch also introduces a convention for the return string.
It consists of a list of elements where 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} ...}}{...}
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
4a28b164 by Georg Chini at 2020-12-03T14:41:39+00:00
pactl: Implement list message-handlers
For better readability, "pactl list message-handlers" is introduced which
prints a formatted output of "pactl send-message /core list-handlers".
The patch also adds the functions pa_message_params_read_raw() and
pa_message_params_read_string() for easy parsing of the message response
string. Because the functions need to modify the parameter string,
the message handler and the pa_context_string_callback function now
receive a char* instead of a const char* as parameter argument.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
590fd1ca by Georg Chini at 2020-12-03T14:41:39+00:00
message-params: Allow parameter strings to contain escaped curly braces
The patch adds the possibility to escape curly braces within parameter strings
and introduces several new functions that can be used for writing parameters.
For writing, the structure pa_message_params, which is a wrapper for pa_strbuf
has been created. Following new write functions are available:
pa_message_params_new() - creates a new pa_message_params structure
pa_message_params_free() - frees a pa_message_params structure
pa_message_param_to_string_free() - converts a pa_message_param to string and
frees the structure
pa_message_params_begin_list() - starts a list
pa_message_params_end_list() - ends a list
pa_message_params_write_string() - writes a string to a pa_message_params structure
pa_message_params_write_raw() - writes a raw string to a pa_message_params structure
For string parameters that contain curly braces or backslashes, those characters
will be escaped when using pa_message_params_write_string(), while write_raw() will
put the string into the buffer without any changes.
For reading, pa_message_params_read_string() reverts the changes that
pa_message_params_write_string() might have introduced.
The patch also adds more restrictions on the object path name. Now only
alphanumeric characters and one of "_", ".", "-" and "/" are allowed.
The path name may not end with a / or contain a double slash. If the user
specifies a trailing / when sending a message, it will be silently removed.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
ca663886 by Georg Chini at 2020-12-03T14:41:39+00:00
core-util: Add pa_atoi64() and pa_atou64() functions
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
8140932a by Georg Chini at 2020-12-03T14:41:39+00:00
message-params: Add read/write functions for various simple data types
The following functions have been added:
pa_message_params_write_double() - writes a double to a pa_message_params structure
pa_message_params_write_int64() - writes an integer to a pa_message_params structure
pa_message_params_write_uint64() - writes an unsigned to a pa_message_params structure
pa_message_params_write_bool() - writes a boolean to a pa_message_params structure
pa_message_params_read_double() - read a double from a parameter list
pa_message_params_read_int64() - read an integer from a parameter list
pa_message_params_read_uint64() - read an unsigned from a parameter list
pa_message_params_read_bool() - read a boolean from a parameter list
The patch also improves the doxygen documentation im message-params.h
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
ff64defc by Georg Chini at 2020-12-03T14:41:39+00:00
message-params: Add read functions for arrays
The following new functions have been added:
pa_message_params_read_double_array() - read an array of double from list
pa_message_params_read_int64_array() - read an array of int64 from list
pa_message_params_read_uint64_array() - read an array of uint64 from list
pa_message_params_read_string_array() - read an array of strings from list
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
6722b2d8 by Tanu Kaskinen at 2020-12-03T14:41:39+00:00
core-util: Make range checks easier to read
It wasn't immediately obvious to me what these checks are supposed to
do. Explicitly checking against the min/max values should make the code
easier to understand.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
5d022a07 by Tanu Kaskinen at 2020-12-03T14:41:39+00:00
introspect: Add version check to pa_context_send_message_to_object()
If an application calls the function when the server doesn't support the
feature, the result should be just an error from the function. Without
the check the whole connection gets terminated due to protocol error.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
d50145a9 by Tanu Kaskinen at 2020-12-03T14:41:39+00:00
core-util: Reduce repetition in number parsing functions
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
6bc00720 by Tanu Kaskinen at 2020-12-03T14:41:39+00:00
core-util: Never parse integers as octal
I believe nobody needs to pass octal numbers to PulseAudio, and if we
encounter integer strings starting with zeros, the intention is to use
them in base 10. Hexadecimal numbers are more common, and they can't be
interpreted in base 10 anyway, so they are still supported.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
b76964e4 by Tanu Kaskinen at 2020-12-03T14:41:39+00:00
core-util-test: Test parsing integer strings with leading zeros
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
ef5a2f15 by Tanu Kaskinen at 2020-12-03T14:41:39+00:00
core-util-test: Drop "modargs" from test function names
These tests aren't directly related to modargs. I suppose these were
first written for the modargs.h API and renaming was forgotten.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
fe162e0b by Tanu Kaskinen at 2020-12-03T14:41:39+00:00
core-util-test: Test pa_atou64() and pa_atoi64()
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
818a87de by Igor V. Kovalenko at 2020-12-03T14:41:39+00:00
message-params: add read length param reference to array read methods
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
c1ac7601 by Igor V. Kovalenko at 2020-12-03T14:41:39+00:00
message-params: consume array enclosing {} by array read methods
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
- - - - -
29 changed files:
- PROTOCOL
- configure.ac
- + doc/messaging_api.txt
- doxygen/doxygen.conf.in
- man/pactl.1.xml.in
- man/pulse-cli-syntax.5.xml.in
- meson.build
- shell-completion/bash/pulseaudio
- shell-completion/zsh/_pulseaudio
- src/Makefile.am
- src/map-file
- src/meson.build
- src/pulse/introspect.c
- src/pulse/introspect.h
- src/pulse/meson.build
- + src/pulse/message-params.c
- + src/pulse/message-params.h
- src/pulsecore/cli-command.c
- src/pulsecore/core-util.c
- src/pulsecore/core-util.h
- src/pulsecore/core.c
- src/pulsecore/message-handler.c
- src/pulsecore/message-handler.h
- src/pulsecore/native-common.h
- src/pulsecore/pdispatch.c
- src/pulsecore/protocol-native.c
- src/tests/core-util-test.c
- src/utils/pacmd.c
- src/utils/pactl.c
Changes:
=====================================
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.
=====================================
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
=====================================
doc/messaging_api.txt
=====================================
@@ -0,0 +1,29 @@
+Message API reference
+
+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
+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
+be specified using a single string, but are not mandatory. The message handler
+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
+must be escaped by adding a "\" before them.
+
+Reference:
+
+Object path: /core
+Message: list-handlers
+Parameters: None
+Return value: {{{Handler name} {Description}} ...}
=====================================
doxygen/doxygen.conf.in
=====================================
@@ -683,6 +683,7 @@ INPUT = @top_srcdir@/src/pulse/channelmap.h \
@top_srcdir@/src/pulse/mainloop-api.h \
@top_srcdir@/src/pulse/mainloop-signal.h \
@top_srcdir@/src/pulse/mainloop.h \
+ @top_srcdir@/src/pulse/message-params.h \
@top_srcdir@/src/pulse/operation.h \
@top_srcdir@/src/pulse/proplist.h \
@top_srcdir@/src/pulse/pulseaudio.h \
=====================================
man/pactl.1.xml.in
=====================================
@@ -80,8 +80,8 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
<option>
<p><opt>list</opt> [<arg>short</arg>] [<arg>TYPE</arg>]</p>
<optdesc><p>Dump all currently loaded modules, available sinks, sources, streams, etc. <arg>TYPE</arg> must be one of:
- modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards. If not specified, all info is listed. If
- short is given, output is in a tabular format, for easy parsing by scripts.</p></optdesc>
+ modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards, message-handlers. If not specified, all info is listed
+ with the exception of the message-handlers. If short is given, output is in a tabular format, for easy parsing by scripts.</p></optdesc>
</option>
<option>
@@ -253,6 +253,13 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
for possible encodings. </p></optdesc>
</option>
+ <option>
+ <p><opt>send-message</opt> <arg>RECIPIENT</arg> <arg>MESSAGE</arg> <arg>MESSAGE_PARAMETERS</arg></p>
+ <optdesc><p>Send a message to the specified recipient object. If applicable an additional string containing
+ message parameters can be specified. A string is returned as a response to the message. For available messages
+ see https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt.</p></optdesc>
+ </option>
+
<option>
<p><opt>subscribe</opt></p>
<optdesc><p>Subscribe to events, pactl does not exit by itself, but keeps waiting for new events.</p></optdesc>
=====================================
man/pulse-cli-syntax.5.xml.in
=====================================
@@ -306,6 +306,13 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
<optdesc><p>Debug: Show shared properties.</p></optdesc>
</option>
+ <option>
+ <p><opt>send-message</opt> <arg>recipient</arg> <arg>message</arg> <arg>message_parameters</arg></p>
+ <optdesc><p>Send a message to the specified recipient object. If applicable an additional string containing
+ message parameters can be specified. A string is returned as a response to the message. For available messages
+ see https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt.</p></optdesc>
+ </option>
+
<option>
<p><opt>exit</opt></p>
<optdesc><p>Terminate the daemon. If you want to terminate a CLI
=====================================
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
=====================================
shell-completion/bash/pulseaudio
=====================================
@@ -113,7 +113,7 @@ _pactl() {
local comps
local flags='-h --help --version -s --server= --client-name='
local list_types='short sinks sources sink-inputs source-outputs cards
- modules samples clients'
+ modules samples clients message-handlers'
local commands=(stat info list exit upload-sample play-sample remove-sample
load-module unload-module move-sink-input move-source-output
suspend-sink suspend-source set-card-profile set-default-sink
@@ -121,7 +121,7 @@ _pactl() {
set-source-volume set-sink-input-volume set-source-output-volume
set-sink-mute set-source-mute set-sink-input-mute
set-source-output-mute set-sink-formats set-port-latency-offset
- subscribe help)
+ subscribe send-message help)
_init_completion -n = || return
preprev=${words[$cword-2]}
@@ -271,7 +271,7 @@ _pacmd() {
move-sink-input move-source-output suspend-sink suspend-source
suspend set-card-profile set-sink-port set-source-port
set-port-latency-offset set-log-target set-log-level set-log-meta
- set-log-time set-log-backtrace)
+ set-log-time set-log-backtrace send-message)
_init_completion -n = || return
preprev=${words[$cword-2]}
=====================================
shell-completion/zsh/_pulseaudio
=====================================
@@ -263,6 +263,7 @@ _pactl_completion() {
'set-sink-input-mute: mute a stream'
'set-source-output-mute: mute a recording stream'
'set-sink-formats: set supported formats of a sink'
+ 'send-message: send a message to a pulseaudio object'
'subscribe: subscribe to events'
)
@@ -284,6 +285,7 @@ _pactl_completion() {
'clients: list connected clients'
'samples: list samples'
'cards: list available cards'
+ 'message-handlers: list available message-handlers'
)
if ((CURRENT == 2)); then
@@ -561,6 +563,7 @@ _pacmd_completion() {
'dump: show daemon configuration'
'dump-volumes: show the state of all volumes'
'shared: show shared properties'
+ 'send-message: send a message to a pulseaudio object'
'exit: ask the PulseAudio daemon to exit'
)
_describe 'pacmd commands' _pacmd_commands
=====================================
src/Makefile.am
=====================================
@@ -711,6 +711,7 @@ libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES = \
pulse/timeval.c pulse/timeval.h \
pulse/rtclock.c pulse/rtclock.h \
pulse/volume.c pulse/volume.h \
+ pulse/message-params.c pulse/message-params.h \
pulsecore/atomic.h \
pulsecore/authkey.c pulsecore/authkey.h \
pulsecore/conf-parser.c pulsecore/conf-parser.h \
@@ -917,6 +918,7 @@ libpulse_la_SOURCES = \
pulse/mainloop-api.c pulse/mainloop-api.h \
pulse/mainloop-signal.c pulse/mainloop-signal.h \
pulse/mainloop.c pulse/mainloop.h \
+ pulse/message-params.c pulse/message-params.h \
pulse/operation.c pulse/operation.h \
pulse/proplist.c pulse/proplist.h \
pulse/pulseaudio.h \
=====================================
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;
@@ -228,6 +229,27 @@ pa_mainloop_quit;
pa_mainloop_run;
pa_mainloop_set_poll_func;
pa_mainloop_wakeup;
+pa_message_params_begin_list;
+pa_message_params_end_list;
+pa_message_params_free;
+pa_message_params_new;
+pa_message_params_read_bool;
+pa_message_params_read_double;
+pa_message_params_read_double_array;
+pa_message_params_read_int64;
+pa_message_params_read_int64_array;
+pa_message_params_read_raw;
+pa_message_params_read_string;
+pa_message_params_read_string_array;
+pa_message_params_read_uint64;
+pa_message_params_read_uint64_array;
+pa_message_params_to_string_free;
+pa_message_params_write_bool;
+pa_message_params_write_double;
+pa_message_params_write_int64;
+pa_message_params_write_raw;
+pa_message_params_write_string;
+pa_message_params_write_uint64;
pa_msleep;
pa_thread_make_realtime;
pa_operation_cancel;
=====================================
src/meson.build
=====================================
@@ -5,6 +5,7 @@ libpulsecommon_sources = [
'pulse/format.c',
'pulse/json.c',
'pulse/mainloop-api.c',
+ 'pulse/message-params.c',
'pulse/xmalloc.c',
'pulse/proplist.c',
'pulse/utf8.c',
@@ -78,6 +79,7 @@ libpulsecommon_headers = [
'pulse/format.h',
'pulse/json.h',
'pulse/mainloop-api.h',
+ 'pulse/message-params.h',
'pulse/xmalloc.h',
'pulse/proplist.h',
'pulse/utf8.h',
=====================================
src/pulse/introspect.c
=====================================
@@ -2205,3 +2205,75 @@ 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) {
+ char *response_copy;
+ pa_context_string_cb_t cb;
+
+ response_copy = pa_xstrdup(response);
+
+ cb = (pa_context_string_cb_t) o->callback;
+ cb(o->context, success, response_copy, o->userdata);
+
+ pa_xfree(response_copy);
+ }
+
+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);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 35, PA_ERR_NOTSUPPORTED);
+
+ 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;
+}
=====================================
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,17 @@ 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, char *response, void *userdata);
+
+/** Send a message to an object that registered a message handler. For more information
+ * see https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt. \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
=====================================
src/pulse/meson.build
=====================================
@@ -19,6 +19,7 @@ libpulse_sources = [
'mainloop-api.c',
'mainloop-signal.c',
'mainloop.c',
+ 'message-params.c',
'operation.c',
'proplist.c',
'rtclock.c',
@@ -50,6 +51,7 @@ libpulse_headers = [
'mainloop-api.h',
'mainloop-signal.h',
'mainloop.h',
+ 'message-params.h',
'operation.h',
'proplist.h',
'pulseaudio.h',
=====================================
src/pulse/message-params.c
=====================================
@@ -0,0 +1,645 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <sys/types.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/core-util.h>
+
+#include "message-params.h"
+
+/* Message parameter structure, a wrapper for pa_strbuf */
+struct pa_message_params {
+ pa_strbuf *buffer;
+};
+
+/* Helper functions */
+
+/* Count number of top level elements in parameter list */
+static int count_elements(const char *c) {
+ const char *s;
+ uint32_t element_count;
+ bool found_element, found_backslash;
+ int open_braces;
+
+ if (!c || *c == 0)
+ return PA_MESSAGE_PARAMS_LIST_END;
+
+ element_count = 0;
+ open_braces = 0;
+ found_element = false;
+ found_backslash = false;
+ s = c;
+
+ /* Count elements in list */
+ while (*s != 0) {
+
+ /* Skip escaped curly braces. */
+ if (*s == '\\' && !found_backslash) {
+ found_backslash = true;
+ s++;
+ continue;
+ }
+
+ if (*s == '{' && !found_backslash) {
+ found_element = true;
+ open_braces++;
+ }
+ if (*s == '}' && !found_backslash)
+ open_braces--;
+
+ /* unexpected closing brace, parse error */
+ if (open_braces < 0)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ if (open_braces == 0 && found_element) {
+ element_count++;
+ found_element = false;
+ }
+
+ found_backslash = false;
+ s++;
+ }
+
+ /* missing closing brace, parse error */
+ if (open_braces > 0)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ return element_count;
+}
+
+/* Split the specified string into elements. An element is defined as
+ * a sub-string between curly braces. The function is needed to parse
+ * the parameters of messages. Each time it is called it returns the
+ * position of the current element in result and the state pointer is
+ * advanced to the next list element. On return, the parameter
+ * *is_unpacked indicates if the string is plain text or contains a
+ * sub-list. is_unpacked may be NULL.
+ *
+ * The variable state points to, should be initialized to NULL before
+ * the first call. The function returns 1 on success, 0 if end of string
+ * is encountered and -1 on parse error.
+ *
+ * result is set to NULL on end of string or parse error. */
+static int split_list(char *c, char **result, bool *is_unpacked, void **state) {
+ char *current = *state ? *state : c;
+ uint32_t open_braces;
+ bool found_backslash = false;
+
+ pa_assert(result);
+
+ *result = NULL;
+
+ /* Empty or no string */
+ if (!current || *current == 0)
+ return PA_MESSAGE_PARAMS_LIST_END;
+
+ /* Find opening brace */
+ while (*current != 0) {
+
+ /* Skip escaped curly braces. */
+ if (*current == '\\' && !found_backslash) {
+ found_backslash = true;
+ current++;
+ continue;
+ }
+
+ if (*current == '{' && !found_backslash)
+ break;
+
+ /* unexpected closing brace, parse error */
+ if (*current == '}' && !found_backslash)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ found_backslash = false;
+ current++;
+ }
+
+ /* No opening brace found, end of string */
+ if (*current == 0)
+ return PA_MESSAGE_PARAMS_LIST_END;
+
+ if (is_unpacked)
+ *is_unpacked = true;
+ *result = current + 1;
+ found_backslash = false;
+ open_braces = 1;
+
+ while (open_braces != 0 && *current != 0) {
+ current++;
+
+ /* Skip escaped curly braces. */
+ if (*current == '\\' && !found_backslash) {
+ found_backslash = true;
+ continue;
+ }
+
+ if (*current == '{' && !found_backslash) {
+ open_braces++;
+ if (is_unpacked)
+ *is_unpacked = false;
+ }
+ if (*current == '}' && !found_backslash)
+ open_braces--;
+
+ found_backslash = false;
+ }
+
+ /* Parse error, closing brace missing */
+ if (open_braces != 0) {
+ *result = NULL;
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ }
+
+ /* Replace } with 0 */
+ *current = 0;
+
+ *state = current + 1;
+
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Read functions */
+
+/* Read a string from the parameter list. The state pointer is
+ * advanced to the next element of the list. Returns a pointer
+ * to a sub-string within c. Escape characters will be removed
+ * from the string. The result must not be freed. */
+int pa_message_params_read_string(char *c, const char **result, void **state) {
+ char *start_pos;
+ char *value = NULL;
+ int r;
+ bool is_unpacked = true;
+
+ pa_assert(result);
+
+ if ((r = split_list(c, &start_pos, &is_unpacked, state)) == PA_MESSAGE_PARAMS_OK)
+ value = start_pos;
+
+ /* Check if we got a plain string not containing further lists */
+ if (!is_unpacked) {
+ /* Parse error */
+ r = PA_MESSAGE_PARAMS_PARSE_ERROR;
+ value = NULL;
+ }
+
+ if (value)
+ *result = pa_unescape(value);
+
+ return r;
+}
+
+/* A wrapper for split_list() to distinguish between reading pure
+ * string data and raw data which may contain further lists. */
+int pa_message_params_read_raw(char *c, char **result, void **state) {
+ return split_list(c, result, NULL, state);
+}
+
+/* Read a double from the parameter list. The state pointer is
+ * advanced to the next element of the list. */
+int pa_message_params_read_double(char *c, double *result, void **state) {
+ char *start_pos, *end_pos, *s;
+ int err;
+ struct lconv *locale;
+ double value;
+ bool is_unpacked = true;
+
+ pa_assert(result);
+
+ if ((err = split_list(c, &start_pos, &is_unpacked, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ /* Empty element */
+ if (!*start_pos)
+ return PA_MESSAGE_PARAMS_IS_NULL;
+
+ /* Check if we got a plain string not containing further lists */
+ if (!is_unpacked)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ /* Get decimal separator for current locale */
+ locale = localeconv();
+
+ /* Replace decimal point with the correct character for the
+ * current locale. This assumes that no thousand separator
+ * is used. */
+ for (s = start_pos; *s; s++) {
+ if (*s == '.' || *s == ',')
+ *s = *locale->decimal_point;
+ }
+
+ /* Convert to double */
+ errno = 0;
+ value = strtod(start_pos, &end_pos);
+
+ /* Conversion error or string contains invalid characters. If the
+ * whole string was used for conversion, end_pos should point to
+ * the end of the string. */
+ if (errno != 0 || *end_pos != 0 || end_pos == start_pos)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ *result = value;
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Read an integer from the parameter list. The state pointer is
+ * advanced to the next element of the list. */
+int pa_message_params_read_int64(char *c, int64_t *result, void **state) {
+ char *start_pos;
+ int err;
+ int64_t value;
+ bool is_unpacked = true;
+
+ pa_assert(result);
+
+ if ((err = split_list(c, &start_pos, &is_unpacked, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ /* Empty element */
+ if (!*start_pos)
+ return PA_MESSAGE_PARAMS_IS_NULL;
+
+ /* Check if we got a plain string not containing further lists */
+ if (!is_unpacked)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ /* Convert to int64 */
+ if (pa_atoi64(start_pos, &value) < 0)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ *result = value;
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Read an unsigned integer from the parameter list. The state pointer is
+ * advanced to the next element of the list. */
+int pa_message_params_read_uint64(char *c, uint64_t *result, void **state) {
+ char *start_pos;
+ int err;
+ uint64_t value;
+ bool is_unpacked = true;
+
+ pa_assert(result);
+
+ if ((err = split_list(c, &start_pos, &is_unpacked, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ /* Empty element */
+ if (!*start_pos)
+ return PA_MESSAGE_PARAMS_IS_NULL;
+
+ /* Check if we got a plain string not containing further lists */
+ if (!is_unpacked)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ /* Convert to int64 */
+ if (pa_atou64(start_pos, &value) < 0)
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+
+ *result = value;
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Read a boolean from the parameter list. The state pointer is
+ * advanced to the next element of the list. */
+int pa_message_params_read_bool(char *c, bool *result, void **state) {
+ int err;
+ uint64_t value;
+
+ pa_assert(result);
+
+ if ((err = pa_message_params_read_uint64(c, &value, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ *result = false;
+ if (value)
+ *result = true;
+
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Converts a parameter list to a string array. */
+int pa_message_params_read_string_array(char *c, const char ***results, int *length) {
+ void *state = NULL;
+ uint32_t element_count, i;
+ int err;
+ const char **values;
+ char *start_pos;
+
+ pa_assert(results);
+ pa_assert(length);
+
+ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ /* Count elements, return if no element was found or parse error. */
+ element_count = count_elements(start_pos);
+ if (element_count < 0) {
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ } else if (element_count == 0) {
+ *length = 0;
+ return PA_MESSAGE_PARAMS_OK;
+ }
+
+ /* Allocate array */
+ values = pa_xmalloc0(element_count * sizeof(char *));
+
+ state = NULL;
+ for (i = 0; (err = pa_message_params_read_string(start_pos, &(values[i]), &state)) > 0; i++)
+ ;
+
+ if (err < 0) {
+ pa_xfree(values);
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ }
+
+ *results = values;
+ *length = element_count;
+
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Converts a parameter list to a double array. */
+int pa_message_params_read_double_array(char *c, double **results, int *length) {
+ double *values;
+ void *state = NULL;
+ uint32_t element_count, i;
+ int err;
+ char *start_pos;
+
+ pa_assert(results);
+ pa_assert(length);
+
+ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ /* Count elements, return if no element was found or parse error. */
+ element_count = count_elements(start_pos);
+ if (element_count < 0) {
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ } else if (element_count == 0) {
+ *length = 0;
+ return PA_MESSAGE_PARAMS_OK;
+ }
+
+ /* Allocate array */
+ values = pa_xmalloc0(element_count * sizeof(double));
+
+ state = NULL;
+ for (i = 0; (err = pa_message_params_read_double(start_pos, &(values[i]), &state)) > 0; i++)
+ ;
+
+ if (err < 0) {
+ pa_xfree(values);
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ }
+
+ *results = values;
+ *length = element_count;
+
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Converts a parameter list to an int64 array. */
+int pa_message_params_read_int64_array(char *c, int64_t **results, int *length) {
+ int64_t *values;
+ void *state = NULL;
+ uint32_t element_count, i;
+ int err;
+ char *start_pos;
+
+ pa_assert(results);
+ pa_assert(length);
+
+ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ /* Count elements, return if no element was found or parse error. */
+ element_count = count_elements(start_pos);
+ if (element_count < 0) {
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ } else if (element_count == 0) {
+ *length = 0;
+ return PA_MESSAGE_PARAMS_OK;
+ }
+
+ /* Allocate array */
+ values = pa_xmalloc0(element_count * sizeof(int64_t));
+
+ state = NULL;
+ for (i = 0; (err = pa_message_params_read_int64(start_pos, &(values[i]), &state)) > 0; i++)
+ ;
+
+ if (err < 0) {
+ pa_xfree(values);
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ }
+
+ *results = values;
+ *length = element_count;
+
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Converts a parameter list to an uint64 array. */
+int pa_message_params_read_uint64_array(char *c, uint64_t **results, int *length) {
+ uint64_t *values;
+ void *state = NULL;
+ uint32_t element_count, i;
+ int err;
+ char *start_pos;
+
+ pa_assert(results);
+ pa_assert(length);
+
+ if ((err = split_list(c, &start_pos, NULL, state)) != PA_MESSAGE_PARAMS_OK)
+ return err;
+
+ /* Count elements, return if no element was found or parse error. */
+ element_count = count_elements(start_pos);
+ if (element_count < 0) {
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ } else if (element_count == 0) {
+ *length = 0;
+ return PA_MESSAGE_PARAMS_OK;
+ }
+
+ /* Allocate array */
+ values = pa_xmalloc0(element_count * sizeof(uint64_t));
+
+ state = NULL;
+ for (i = 0; (err = pa_message_params_read_uint64(start_pos, &(values[i]), &state)) > 0; i++)
+ ;
+
+ if (err < 0) {
+ pa_xfree(values);
+ return PA_MESSAGE_PARAMS_PARSE_ERROR;
+ }
+
+ *results = values;
+ *length = element_count;
+
+ return PA_MESSAGE_PARAMS_OK;
+}
+
+/* Write functions. The functions are wrapper functions around pa_strbuf,
+ * so that the client does not need to use pa_strbuf directly. */
+
+/* Creates a new pa_message_param structure */
+pa_message_params *pa_message_params_new(void) {
+ pa_message_params *params;
+
+ params = pa_xnew(pa_message_params, 1);
+ params->buffer = pa_strbuf_new();
+
+ return params;
+}
+
+/* Frees a pa_message_params structure */
+void pa_message_params_free(pa_message_params *params) {
+ pa_assert(params);
+
+ pa_strbuf_free(params->buffer);
+ pa_xfree(params);
+}
+
+/* Converts a pa_message_param structure to string and frees the structure.
+ * The returned string needs to be freed with pa_xree(). */
+char *pa_message_params_to_string_free(pa_message_params *params) {
+ char *result;
+
+ pa_assert(params);
+
+ result = pa_strbuf_to_string_free(params->buffer);
+
+ pa_xfree(params);
+ return result;
+}
+
+/* Writes an opening curly brace */
+void pa_message_params_begin_list(pa_message_params *params) {
+
+ pa_assert(params);
+
+ pa_strbuf_putc(params->buffer, '{');
+}
+
+/* Writes a closing curly brace */
+void pa_message_params_end_list(pa_message_params *params) {
+
+ pa_assert(params);
+
+ pa_strbuf_putc(params->buffer, '}');
+}
+
+/* Writes a string to a message_params structure, adding curly braces
+ * around the string and escaping curly braces within the string. */
+void pa_message_params_write_string(pa_message_params *params, const char *value) {
+ char *output;
+
+ pa_assert(params);
+
+ /* Null value is written as empty element */
+ if (!value)
+ value = "";
+
+ output = pa_escape(value, "{}");
+ pa_strbuf_printf(params->buffer, "{%s}", output);
+
+ pa_xfree(output);
+}
+
+/* Writes a raw string to a message_params structure, adding curly braces
+ * around the string if add_braces is true. This function can be used to
+ * write parts of a string or whole parameter lists that have been prepared
+ * elsewhere (for example an array). */
+void pa_message_params_write_raw(pa_message_params *params, const char *value, bool add_braces) {
+ pa_assert(params);
+
+ /* Null value is written as empty element if add_braces is true.
+ * Otherwise nothing is written. */
+ if (!value)
+ value = "";
+
+ if (add_braces)
+ pa_strbuf_printf(params->buffer, "{%s}", value);
+ else
+ pa_strbuf_puts(params->buffer, value);
+}
+
+/* Writes a double to a message_params structure, adding curly braces.
+ * precision gives the number of significant digits, not digits after
+ * the decimal point. */
+void pa_message_params_write_double(pa_message_params *params, double value, int precision) {
+ char *buf, *s;
+
+ pa_assert(params);
+
+ /* We do not care about locale because we do not know which locale is
+ * used on the server side. If the decimal separator is a comma, we
+ * replace it with a dot to achieve consistent output on all locales. */
+ buf = pa_sprintf_malloc("{%.*g}", precision, value);
+ for (s = buf; *s; s++) {
+ if (*s == ',') {
+ *s = '.';
+ break;
+ }
+ }
+
+ pa_strbuf_puts(params->buffer, buf);
+
+ pa_xfree(buf);
+}
+
+/* Writes an integer to a message_param structure, adding curly braces. */
+void pa_message_params_write_int64(pa_message_params *params, int64_t value) {
+
+ pa_assert(params);
+
+ pa_strbuf_printf(params->buffer, "{%lli}", (long long)value);
+}
+
+/* Writes an unsigned integer to a message_params structure, adding curly braces. */
+void pa_message_params_write_uint64(pa_message_params *params, uint64_t value) {
+
+ pa_assert(params);
+
+ pa_strbuf_printf(params->buffer, "{%llu}", (unsigned long long)value);
+}
+
+/* Writes a boolean to a message_params structure, adding curly braces. */
+void pa_message_params_write_bool(pa_message_params *params, bool value) {
+
+ pa_assert(params);
+
+ if (value)
+ pa_strbuf_puts(params->buffer, "{1}");
+ else
+ pa_strbuf_puts(params->buffer, "{0}");
+}
=====================================
src/pulse/message-params.h
=====================================
@@ -0,0 +1,157 @@
+#ifndef foomessagehelperhfoo
+#define foomessagehelperhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include <pulse/cdecl.h>
+#include <pulse/version.h>
+
+/** \file
+ * Utility functions for reading and writing message parameters.
+ * All read functions return a value from pa_message_params_error_code
+ * and the read value in result (or *result for string functions).
+ * The string read functions read_string() and read_raw() return a pointer
+ * to a sub-string within the parameter list in *result, therefore the
+ * string in *result must not be freed and is only valid within the
+ * message handler callback function. If the string is needed outside
+ * the callback, it must be copied using pa_xstrdup().
+ * When a read function is called, the state pointer is advanced to the
+ * next list element. The variable state points to should be initialized
+ * to NULL before the first call.
+ * All read functions except read_raw() preserve a default value passed
+ * in result if the call fails. For the array functions, results must be
+ * initialized prior to the call either to NULL or to an array with default
+ * values. If the function succeeds, the default array will be freed and
+ * the number of elements in the result array is returned.\n\n
+ * Write functions operate on a pa_message_params structure which is a
+ * wrapper for pa_strbuf. A parameter list or sub-list is started by a
+ * call to begin_list() and ended by a call to end_list().
+ * A pa_message_params structure must be converted to a string using
+ * pa_message_params_to_string_free() before it can be passed to a
+ * message handler. */
+
+PA_C_DECL_BEGIN
+
+/** Structure which holds a parameter list. Wrapper for pa_strbuf \since 15.0 */
+typedef struct pa_message_params pa_message_params;
+
+/** Read function return values \since 15.0 */
+enum pa_message_params_error_code {
+ /** No value (empty element) found for numeric or boolean value */
+ PA_MESSAGE_PARAMS_IS_NULL = -2,
+ /** Error encountered while parsing a value */
+ PA_MESSAGE_PARAMS_PARSE_ERROR = -1,
+ /** End of parameter list reached */
+ PA_MESSAGE_PARAMS_LIST_END = 0,
+ /** Parsing successful */
+ PA_MESSAGE_PARAMS_OK = 1,
+};
+
+/** @{ \name Read functions */
+
+/** Read a boolean from parameter list in c. \since 15.0 */
+int pa_message_params_read_bool(char *c, bool *result, void **state);
+
+/** Read a double from parameter list in c. \since 15.0 */
+int pa_message_params_read_double(char *c, double *result, void **state);
+
+/** Converts a parameter list to a double array. Empty elements in the parameter
+ * list are treated as error. Returns allocated array in *results and array size in *length.
+ * The returned array must be freed with pa_xfree(). \since 15.0 */
+int pa_message_params_read_double_array(char *c, double **results, int *length);
+
+/** Read an integer from parameter list in c. \since 15.0 */
+int pa_message_params_read_int64(char *c, int64_t *result, void **state);
+
+/** Converts a parameter list to an int64 array. Empty elements in the parameter
+ * list are treated as error. Returns allocated array in *results and array size in *length.
+ * The returned array must be freed with pa_xfree(). \since 15.0 */
+int pa_message_params_read_int64_array(char *c, int64_t **results, int *length);
+
+/** Read raw data from parameter list in c. Used to split a message parameter
+ * string into list elements. The string returned in *result must not be freed. \since 15.0 */
+int pa_message_params_read_raw(char *c, char **result, void **state);
+
+/** Read a string from a parameter list in c. Escaped curly braces and backslashes
+ * will be unescaped. \since 15.0 */
+int pa_message_params_read_string(char *c, const char **result, void **state);
+
+/** Convert a parameter list to a string array. Escaping is removed from
+ * the strings. Returns allocated array of pointers to sub-strings within c in
+ * *results and stores array size in *length. The returned array must be
+ * freed with pa_xfree(), but not the strings within the array. \since 15.0 */
+int pa_message_params_read_string_array(char *c, const char ***results, int *length);
+
+/** Read an unsigned integer from parameter list in c. \since 15.0 */
+int pa_message_params_read_uint64(char *c, uint64_t *result, void **state);
+
+/** Converts a parameter list to an uint64 array. Empty elements in the parameter
+ * list are treated as error. Returns allocated array in *results and array size in *length.
+ * The returned array must be freed with pa_xfree(). \since 15.0 */
+int pa_message_params_read_uint64_array(char *c, uint64_t **results, int *length);
+
+/** @} */
+
+/** @{ \name Write functions */
+
+/** Create a new pa_message_params structure. \since 15.0 */
+pa_message_params *pa_message_params_new(void);
+
+/** Free a pa_message_params structure. \since 15.0 */
+void pa_message_params_free(pa_message_params *params);
+
+/** Convert pa_message_params to string, free pa_message_params structure. \since 15.0 */
+char *pa_message_params_to_string_free(pa_message_params *params);
+
+/** Start a list by writing an opening brace. \since 15.0 */
+void pa_message_params_begin_list(pa_message_params *params);
+
+/** End a list by writing a closing brace. \since 15.0 */
+void pa_message_params_end_list(pa_message_params *params);
+
+/** Append a boolean to parameter list. \since 15.0 */
+void pa_message_params_write_bool(pa_message_params *params, bool value);
+
+/** Append a double to parameter list. Precision gives the number of
+ * significant digits. The decimal separator will always be written as
+ * dot, regardless which locale is used. \since 15.0 */
+void pa_message_params_write_double(pa_message_params *params, double value, int precision);
+
+/** Append an integer to parameter list. \since 15.0 */
+void pa_message_params_write_int64(pa_message_params *params, int64_t value);
+
+/** Append string to parameter list. Curly braces and backslashes will be escaped. \since 15.0 */
+void pa_message_params_write_string(pa_message_params *params, const char *value);
+
+/** Append raw string to parameter list. Used to write incomplete strings
+ * or complete parameter lists (for example arrays). Adds curly braces around
+ * the string if add_braces is true. \since 15.0 */
+void pa_message_params_write_raw(pa_message_params *params, const char *value, bool add_braces);
+
+/** Append an unsigned integer to parameter list. \since 15.0 */
+void pa_message_params_write_uint64(pa_message_params *params, uint64_t value);
+
+/** @} */
+
+PA_C_DECL_END
+
+#endif
=====================================
src/pulsecore/cli-command.c
=====================================
@@ -53,6 +53,7 @@
#include <pulsecore/sound-file-stream.h>
#include <pulsecore/shared.h>
#include <pulsecore/core-util.h>
+#include <pulsecore/message-handler.h>
#include <pulsecore/core-error.h>
#include <pulsecore/modinfo.h>
#include <pulsecore/dynarray.h>
@@ -135,6 +136,7 @@ static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf,
static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_send_message_to_object(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
/* A method table for all available commands */
@@ -191,6 +193,7 @@ static const struct command commands[] = {
{ "set-log-meta", pa_cli_command_log_meta, "Show source code location in log messages (args: bool)", 2},
{ "set-log-time", pa_cli_command_log_time, "Show timestamps in log messages (args: bool)", 2},
{ "set-log-backtrace", pa_cli_command_log_backtrace, "Show backtrace in log messages (args: frames)", 2},
+ { "send-message", pa_cli_command_send_message_to_object, "Send a message to an object (args: recipient, message, message_parameters)", 4},
{ "play-file", pa_cli_command_play_file, "Play a sound file (args: filename, sink|index)", 3},
{ "dump", pa_cli_command_dump, "Dump daemon configuration", 1},
{ "dump-volumes", pa_cli_command_dump_volumes, "Debug: Show the state of all volumes", 1 },
@@ -1784,6 +1787,47 @@ static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
return 0;
}
+static int pa_cli_command_send_message_to_object(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *object_path, *message, *message_parameters;
+ char *response = NULL;
+ int ret;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+
+ if (!(object_path = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify an object path as recipient for the message.\n");
+ return -1;
+ }
+
+ if (!(message = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a message name.\n");
+ return -1;
+ }
+
+ /* parameters may be NULL */
+ message_parameters = pa_tokenizer_get(t, 3);
+
+ ret = pa_message_handler_send_message(c, object_path, message, message_parameters, &response);
+
+ if (ret < 0) {
+ pa_strbuf_printf(buf, "Send message failed: %s\n", pa_strerror(ret));
+ ret = -1;
+
+ } else {
+ if (response)
+ pa_strbuf_puts(buf, response);
+ pa_strbuf_puts(buf, "\n");
+ ret = 0;
+ }
+
+ pa_xfree(response);
+ return ret;
+}
+
static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
pa_module *m;
pa_sink *sink;
=====================================
src/pulsecore/core-util.c
=====================================
@@ -2189,7 +2189,7 @@ int pa_atoi(const char *s, int32_t *ret_i) {
if (pa_atol(s, &l) < 0)
return -1;
- if ((int32_t) l != l) {
+ if (l < INT32_MIN || l > INT32_MAX) {
errno = ERANGE;
return -1;
}
@@ -2199,6 +2199,90 @@ int pa_atoi(const char *s, int32_t *ret_i) {
return 0;
}
+enum numtype {
+ NUMTYPE_UINT,
+ NUMTYPE_INT,
+ NUMTYPE_DOUBLE,
+};
+
+/* A helper function for pa_atou() and friends. This does some common checks,
+ * because our number parsing is more strict than the strtoX functions.
+ *
+ * Leading zeros are stripped from integers so that they don't get parsed as
+ * octal (but "0x" is preserved for hexadecimal numbers). For NUMTYPE_INT the
+ * zero stripping may involve allocating a new string, in which case it's
+ * stored in tmp. Otherwise tmp is set to NULL. The caller needs to free tmp
+ * after they're done with ret. When parsing other types than NUMTYPE_INT the
+ * caller can pass NULL as tmp.
+ *
+ * The final string to parse is returned in ret. ret will point either inside
+ * s or to tmp. */
+static int prepare_number_string(const char *s, enum numtype type, char **tmp, const char **ret) {
+ const char *original = s;
+ bool negative = false;
+
+ pa_assert(s);
+ pa_assert(type != NUMTYPE_INT || tmp);
+ pa_assert(ret);
+
+ if (tmp)
+ *tmp = NULL;
+
+ /* The strtoX functions accept leading spaces, we don't. */
+ if (isspace((unsigned char) s[0]))
+ return -1;
+
+ /* The strtoX functions accept a plus sign, we don't. */
+ if (s[0] == '+')
+ return -1;
+
+ /* The strtoul and strtoull functions allow a minus sign even though they
+ * parse an unsigned number. In case of a minus sign the original negative
+ * number gets negated. We don't want that kind of behviour. */
+ if (type == NUMTYPE_UINT && s[0] == '-')
+ return -1;
+
+ /* The strtoX functions interpret the number as octal if it starts with
+ * a zero. We prefer to use base 10, so we strip all leading zeros (if the
+ * string starts with "0x", strtoul() interprets it as hexadecimal, which
+ * is fine, because it's unambiguous unlike octal).
+ *
+ * While stripping the leading zeros, we have to remember to also handle
+ * the case where the number is negative, which makes the zero skipping
+ * code somewhat complex. */
+
+ /* Doubles don't need zero stripping, we can finish now. */
+ if (type == NUMTYPE_DOUBLE)
+ goto finish;
+
+ if (s[0] == '-') {
+ negative = true;
+ s++; /* Skip the minus sign. */
+ }
+
+ /* Don't skip zeros if the string starts with "0x". */
+ if (s[0] == '0' && s[1] != 'x') {
+ while (s[0] == '0' && s[1])
+ s++; /* Skip zeros. */
+ }
+
+ if (negative) {
+ s--; /* Go back one step, we need the minus sign back. */
+
+ /* If s != original, then we have skipped some zeros and we need to replace
+ * the last skipped zero with a minus sign. */
+ if (s != original) {
+ *tmp = pa_xstrdup(s);
+ *tmp[0] = '-';
+ s = *tmp;
+ }
+ }
+
+finish:
+ *ret = s;
+ return 0;
+}
+
/* Convert the string s to an unsigned integer in *ret_u */
int pa_atou(const char *s, uint32_t *ret_u) {
char *x = NULL;
@@ -2207,23 +2291,47 @@ int pa_atou(const char *s, uint32_t *ret_u) {
pa_assert(s);
pa_assert(ret_u);
- /* strtoul() ignores leading spaces. We don't. */
- if (isspace((unsigned char)*s)) {
+ if (prepare_number_string(s, NUMTYPE_UINT, NULL, &s) < 0) {
errno = EINVAL;
return -1;
}
- /* strtoul() accepts strings that start with a minus sign. In that case the
- * original negative number gets negated, and strtoul() returns the negated
- * result. We don't want that kind of behaviour. strtoul() also allows a
- * leading plus sign, which is also a thing that we don't want. */
- if (*s == '-' || *s == '+') {
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ /* If x doesn't point to the end of s, there was some trailing garbage in
+ * the string. If x points to s, no conversion was done (empty string). */
+ if (!x || *x || x == s || errno) {
+ if (!errno)
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (l > UINT32_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *ret_u = (uint32_t) l;
+
+ return 0;
+}
+
+/* Convert the string s to an unsigned 64 bit integer in *ret_u */
+int pa_atou64(const char *s, uint64_t *ret_u) {
+ char *x = NULL;
+ unsigned long long l;
+
+ pa_assert(s);
+ pa_assert(ret_u);
+
+ if (prepare_number_string(s, NUMTYPE_UINT, NULL, &s) < 0) {
errno = EINVAL;
return -1;
}
errno = 0;
- l = strtoul(s, &x, 0);
+ l = strtoull(s, &x, 0);
/* If x doesn't point to the end of s, there was some trailing garbage in
* the string. If x points to s, no conversion was done (empty string). */
@@ -2233,39 +2341,66 @@ int pa_atou(const char *s, uint32_t *ret_u) {
return -1;
}
- if ((uint32_t) l != l) {
+ if (l > UINT64_MAX) {
errno = ERANGE;
return -1;
}
- *ret_u = (uint32_t) l;
+ *ret_u = (uint64_t) l;
return 0;
}
/* Convert the string s to a signed long integer in *ret_l. */
int pa_atol(const char *s, long *ret_l) {
+ char *tmp;
char *x = NULL;
long l;
pa_assert(s);
pa_assert(ret_l);
- /* strtol() ignores leading spaces. We don't. */
- if (isspace((unsigned char)*s)) {
+ if (prepare_number_string(s, NUMTYPE_INT, &tmp, &s) < 0) {
errno = EINVAL;
return -1;
}
- /* strtol() accepts leading plus signs, but that's ugly, so we don't allow
- * that. */
- if (*s == '+') {
+ errno = 0;
+ l = strtol(s, &x, 0);
+
+ /* If x doesn't point to the end of s, there was some trailing garbage in
+ * the string. If x points to s, no conversion was done (at least an empty
+ * string can trigger this). */
+ if (!x || *x || x == s || errno) {
+ if (!errno)
+ errno = EINVAL;
+ pa_xfree(tmp);
+ return -1;
+ }
+
+ pa_xfree(tmp);
+
+ *ret_l = l;
+
+ return 0;
+}
+
+/* Convert the string s to a signed 64 bit integer in *ret_l. */
+int pa_atoi64(const char *s, int64_t *ret_l) {
+ char *tmp;
+ char *x = NULL;
+ long long l;
+
+ pa_assert(s);
+ pa_assert(ret_l);
+
+ if (prepare_number_string(s, NUMTYPE_INT, &tmp, &s) < 0) {
errno = EINVAL;
return -1;
}
errno = 0;
- l = strtol(s, &x, 0);
+ l = strtoll(s, &x, 0);
/* If x doesn't point to the end of s, there was some trailing garbage in
* the string. If x points to s, no conversion was done (at least an empty
@@ -2273,11 +2408,19 @@ int pa_atol(const char *s, long *ret_l) {
if (!x || *x || x == s || errno) {
if (!errno)
errno = EINVAL;
+ pa_xfree(tmp);
return -1;
}
+ pa_xfree(tmp);
+
*ret_l = l;
+ if (l < INT64_MIN || l > INT64_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+
return 0;
}
@@ -2296,15 +2439,7 @@ int pa_atod(const char *s, double *ret_d) {
pa_assert(s);
pa_assert(ret_d);
- /* strtod() ignores leading spaces. We don't. */
- if (isspace((unsigned char)*s)) {
- errno = EINVAL;
- return -1;
- }
-
- /* strtod() accepts leading plus signs, but that's ugly, so we don't allow
- * that. */
- if (*s == '+') {
+ if (prepare_number_string(s, NUMTYPE_DOUBLE, NULL, &s) < 0) {
errno = EINVAL;
return -1;
}
=====================================
src/pulsecore/core-util.h
=====================================
@@ -151,6 +151,8 @@ int pa_atoi(const char *s, int32_t *ret_i);
int pa_atou(const char *s, uint32_t *ret_u);
int pa_atol(const char *s, long *ret_l);
int pa_atod(const char *s, double *ret_d);
+int pa_atoi64(const char *s, int64_t *ret_l);
+int pa_atou64(const char *s, uint64_t *ret_u);
size_t pa_snprintf(char *str, size_t size, const char *format, ...);
size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap);
=====================================
src/pulsecore/core.c
=====================================
@@ -29,15 +29,18 @@
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/xmalloc.h>
+#include <pulse/message-params.h>
#include <pulsecore/module.h>
#include <pulsecore/core-rtclock.h>
#include <pulsecore/core-util.h>
+#include <pulsecore/message-handler.h>
#include <pulsecore/core-scache.h>
#include <pulsecore/core-subscribe.h>
#include <pulsecore/random.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
+#include <pulsecore/strbuf.h>
#include "core.h"
@@ -61,6 +64,46 @@ static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t o
static void core_free(pa_object *o);
+/* Returns a list of handlers. */
+static char *message_handler_list(pa_core *c) {
+ pa_message_params *param;
+ void *state = NULL;
+ struct pa_message_handler *handler;
+
+ param = pa_message_params_new();
+
+ pa_message_params_begin_list(param);
+ PA_HASHMAP_FOREACH(handler, c->message_handlers, state) {
+ pa_message_params_begin_list(param);
+
+ /* object_path cannot contain characters that need escaping, therefore
+ * pa_message_params_write_raw() can safely be used here. */
+ 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_message_params_end_list(param);
+
+ return pa_message_params_to_string_free(param);
+}
+
+static int core_message_handler(const char *object_path, const char *message, char *message_parameters, char **response, void *userdata) {
+ pa_core *c;
+
+ pa_assert(c = (pa_core *) userdata);
+ pa_assert(message);
+ pa_assert(response);
+ pa_assert(pa_safe_streq(object_path, "/core"));
+
+ if (pa_streq(message, "list-handlers")) {
+ *response = message_handler_list(c);
+ return PA_OK;
+ }
+
+ return -PA_ERR_NOTIMPLEMENTED;
+}
+
pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) {
pa_core* c;
pa_mempool *pool;
@@ -105,6 +148,8 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t
c->shared = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ pa_message_handler_register(c, "/core", "Core message handler", core_message_handler, (void *) c);
+
c->default_source = NULL;
c->default_sink = NULL;
@@ -202,6 +247,8 @@ static void core_free(pa_object *o) {
pa_assert(pa_hashmap_isempty(c->shared));
pa_hashmap_free(c->shared);
+ pa_message_handler_unregister(c, "/core");
+
pa_assert(pa_hashmap_isempty(c->message_handlers));
pa_hashmap_free(c->message_handlers);
=====================================
src/pulsecore/message-handler.c
=====================================
@@ -31,6 +31,39 @@
#include "message-handler.h"
+/* Check if a path string starts with a / and only contains valid characters.
+ * Also reject double slashes. */
+static bool object_path_is_valid(const char *test_string) {
+ uint32_t i;
+
+ if (!test_string)
+ return false;
+
+ /* Make sure the string starts with a / */
+ if (test_string[0] != '/')
+ return false;
+
+ for (i = 0; test_string[i]; i++) {
+
+ if ((test_string[i] >= 'a' && test_string[i] <= 'z') ||
+ (test_string[i] >= 'A' && test_string[i] <= 'Z') ||
+ (test_string[i] >= '0' && test_string[i] <= '9') ||
+ test_string[i] == '.' ||
+ test_string[i] == '_' ||
+ test_string[i] == '-' ||
+ (test_string[i] == '/' && test_string[i + 1] != '/'))
+ continue;
+
+ return false;
+ }
+
+ /* Make sure the string does not end with a / */
+ if (test_string[i - 1] == '/')
+ return false;
+
+ return true;
+}
+
/* Message handler functions */
/* Register message handler for the specified object. object_path must be a unique name starting with "/". */
@@ -42,8 +75,8 @@ void pa_message_handler_register(pa_core *c, const char *object_path, const char
pa_assert(cb);
pa_assert(userdata);
- /* Ensure that the object path is not empty and starts with "/". */
- pa_assert(object_path[0] == '/');
+ /* Ensure that object path is valid */
+ pa_assert(object_path_is_valid(object_path));
handler = pa_xnew0(struct pa_message_handler, 1);
handler->userdata = userdata;
@@ -71,6 +104,8 @@ void pa_message_handler_unregister(pa_core *c, const char *object_path) {
/* Send a message to an object identified by object_path */
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;
+ int ret;
+ char *parameter_copy, *path_copy;
pa_assert(c);
pa_assert(object_path);
@@ -79,12 +114,26 @@ int pa_message_handler_send_message(pa_core *c, const char *object_path, const c
*response = NULL;
- if (!(handler = pa_hashmap_get(c->message_handlers, object_path)))
+ path_copy = pa_xstrdup(object_path);
+
+ /* Remove trailing / from path name if present */
+ if (path_copy[strlen(path_copy) - 1] == '/')
+ path_copy[strlen(path_copy) - 1] = 0;
+
+ if (!(handler = pa_hashmap_get(c->message_handlers, path_copy))) {
+ pa_xfree(path_copy);
return -PA_ERR_NOENTITY;
+ }
+
+ parameter_copy = pa_xstrdup(message_parameters);
/* The handler is expected to return an error code and may also
return an error string in response */
- return handler->callback(handler->object_path, message, message_parameters, response, handler->userdata);
+ ret = handler->callback(handler->object_path, message, parameter_copy, response, handler->userdata);
+
+ pa_xfree(parameter_copy);
+ pa_xfree(path_copy);
+ return ret;
}
/* Set handler description */
=====================================
src/pulsecore/message-handler.h
=====================================
@@ -26,7 +26,7 @@
typedef int (*pa_message_handler_cb_t)(
const char *object_path,
const char *message,
- const char *message_parameters,
+ char *message_parameters,
char **response,
void *userdata);
=====================================
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
};
=====================================
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
=====================================
src/pulsecore/protocol-native.c
=====================================
@@ -47,6 +47,7 @@
#include <pulsecore/namereg.h>
#include <pulsecore/core-scache.h>
#include <pulsecore/core-subscribe.h>
+#include <pulsecore/message-handler.h>
#include <pulsecore/log.h>
#include <pulsecore/mem.h>
#include <pulsecore/strlist.h>
@@ -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
};
=====================================
src/tests/core-util-test.c
=====================================
@@ -26,7 +26,7 @@
#include <pulse/xmalloc.h>
#include <pulsecore/core-util.h>
-START_TEST (modargs_test_parse_boolean) {
+START_TEST (test_parse_boolean) {
ck_assert_int_eq(pa_parse_boolean("true"), true);
ck_assert_int_eq(pa_parse_boolean("yes"), true);
ck_assert_int_eq(pa_parse_boolean("1"), true);
@@ -40,7 +40,7 @@ START_TEST (modargs_test_parse_boolean) {
}
END_TEST
-START_TEST (modargs_test_parse_volume) {
+START_TEST (test_parse_volume) {
pa_volume_t value;
// dB volumes
@@ -92,7 +92,7 @@ START_TEST (modargs_test_parse_volume) {
}
END_TEST
-START_TEST (modargs_test_atoi) {
+START_TEST (test_atoi) {
int32_t value;
// decimal
@@ -100,6 +100,10 @@ START_TEST (modargs_test_atoi) {
ck_assert_int_eq(value, 100000);
ck_assert_int_eq(pa_atoi("-100000", &value), 0);
ck_assert_int_eq(value, -100000);
+ ck_assert_int_eq(pa_atoi("010", &value), 0);
+ ck_assert_int_eq(value, 10);
+ ck_assert_int_eq(pa_atoi("-010", &value), 0);
+ ck_assert_int_eq(value, -10);
// hexadecimal
ck_assert_int_eq(pa_atoi("0x100000", &value), 0);
@@ -111,15 +115,18 @@ START_TEST (modargs_test_atoi) {
ck_assert_int_lt(pa_atoi("3.14", &value), 0);
ck_assert_int_lt(pa_atoi("7*8", &value), 0);
ck_assert_int_lt(pa_atoi("false", &value), 0);
+ ck_assert_int_lt(pa_atoi("10000000000", &value), 0);
}
END_TEST
-START_TEST (modargs_test_atou) {
+START_TEST (test_atou) {
uint32_t value;
// decimal
ck_assert_int_eq(pa_atou("100000", &value), 0);
ck_assert_int_eq(value, 100000);
+ ck_assert_int_eq(pa_atou("010", &value), 0);
+ ck_assert_int_eq(value, 10);
// hexadecimal
ck_assert_int_eq(pa_atou("0x100000", &value), 0);
@@ -131,10 +138,35 @@ START_TEST (modargs_test_atou) {
ck_assert_int_lt(pa_atou("3.14", &value), 0);
ck_assert_int_lt(pa_atou("7*8", &value), 0);
ck_assert_int_lt(pa_atou("false", &value), 0);
+ ck_assert_int_lt(pa_atou("10000000000", &value), 0);
}
END_TEST
-START_TEST (modargs_test_atol) {
+START_TEST (test_atou64) {
+ uint64_t value;
+
+ // decimal
+ ck_assert_int_eq(pa_atou64("100000", &value), 0);
+ ck_assert_int_eq(value, 100000);
+ ck_assert_int_eq(pa_atou64("010", &value), 0);
+ ck_assert_int_eq(value, 10);
+ ck_assert_int_eq(pa_atou64("10000000000", &value), 0);
+ ck_assert_int_eq(value, 10000000000);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atou64("0x100000", &value), 0);
+ ck_assert_int_eq(value, 0x100000);
+
+ // invalid values
+ ck_assert_int_lt(pa_atou64("-100000", &value), 0);
+ ck_assert_int_lt(pa_atou64("-0x100000", &value), 0);
+ ck_assert_int_lt(pa_atou64("3.14", &value), 0);
+ ck_assert_int_lt(pa_atou64("7*8", &value), 0);
+ ck_assert_int_lt(pa_atou64("false", &value), 0);
+}
+END_TEST
+
+START_TEST (test_atol) {
long value;
// decimal
@@ -142,6 +174,10 @@ START_TEST (modargs_test_atol) {
ck_assert_int_eq(value, 100000l);
ck_assert_int_eq(pa_atol("-100000", &value), 0);
ck_assert_int_eq(value, -100000l);
+ ck_assert_int_eq(pa_atol("010", &value), 0);
+ ck_assert_int_eq(value, 10);
+ ck_assert_int_eq(pa_atol("-010", &value), 0);
+ ck_assert_int_eq(value, -10);
// hexadecimal
ck_assert_int_eq(pa_atol("0x100000", &value), 0);
@@ -156,7 +192,35 @@ START_TEST (modargs_test_atol) {
}
END_TEST
-START_TEST (modargs_test_atod) {
+START_TEST (test_atoi64) {
+ int64_t value;
+
+ // decimal
+ ck_assert_int_eq(pa_atoi64("100000", &value), 0);
+ ck_assert_int_eq(value, 100000);
+ ck_assert_int_eq(pa_atoi64("-100000", &value), 0);
+ ck_assert_int_eq(value, -100000);
+ ck_assert_int_eq(pa_atoi64("010", &value), 0);
+ ck_assert_int_eq(value, 10);
+ ck_assert_int_eq(pa_atoi64("-010", &value), 0);
+ ck_assert_int_eq(value, -10);
+ ck_assert_int_eq(pa_atoi64("10000000000", &value), 0);
+ ck_assert_int_eq(value, 10000000000);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atoi64("0x100000", &value), 0);
+ ck_assert_int_eq(value, 0x100000);
+ ck_assert_int_eq(pa_atoi64("-0x100000", &value), 0);
+ ck_assert_int_eq(value, -0x100000);
+
+ // invalid values
+ ck_assert_int_lt(pa_atoi64("3.14", &value), 0);
+ ck_assert_int_lt(pa_atoi64("7*8", &value), 0);
+ ck_assert_int_lt(pa_atoi64("false", &value), 0);
+}
+END_TEST
+
+START_TEST (test_atod) {
double value;
double epsilon = 0.001;
@@ -177,7 +241,7 @@ START_TEST (modargs_test_atod) {
}
END_TEST
-START_TEST (modargs_test_replace) {
+START_TEST (test_replace) {
char* value;
value = pa_replace("abcde", "bcd", "XYZ");
@@ -198,22 +262,22 @@ START_TEST (modargs_test_replace) {
}
END_TEST
-START_TEST (modargs_test_replace_fail_1) {
+START_TEST (test_replace_fail_1) {
pa_replace(NULL, "b", "bab");
}
END_TEST
-START_TEST (modargs_test_replace_fail_2) {
+START_TEST (test_replace_fail_2) {
pa_replace("abe", NULL, "bab");
}
END_TEST
-START_TEST (modargs_test_replace_fail_3) {
+START_TEST (test_replace_fail_3) {
pa_replace("abcde", "b", NULL);
}
END_TEST
-START_TEST (modargs_test_escape) {
+START_TEST (test_escape) {
char* value;
value = pa_escape("abcde", "bcd");
@@ -230,12 +294,12 @@ START_TEST (modargs_test_escape) {
}
END_TEST
-START_TEST (modargs_test_replace_fail_4) {
+START_TEST (test_replace_fail_4) {
pa_replace("abe", "", "bab");
}
END_TEST
-START_TEST (modargs_test_unescape) {
+START_TEST (test_unescape) {
char* value;
value = pa_unescape(pa_xstrdup("a\\b\\c\\de"));
@@ -261,19 +325,21 @@ int main(int argc, char *argv[]) {
tc = tcase_create("core-util");
suite_add_tcase(s, tc);
- tcase_add_test(tc, modargs_test_parse_boolean);
- tcase_add_test(tc, modargs_test_parse_volume);
- tcase_add_test(tc, modargs_test_atoi);
- tcase_add_test(tc, modargs_test_atou);
- tcase_add_test(tc, modargs_test_atol);
- tcase_add_test(tc, modargs_test_atod);
- tcase_add_test(tc, modargs_test_replace);
- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_1, SIGABRT);
- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_2, SIGABRT);
- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_3, SIGABRT);
- tcase_add_test_raise_signal(tc, modargs_test_replace_fail_4, SIGABRT);
- tcase_add_test(tc, modargs_test_escape);
- tcase_add_test(tc, modargs_test_unescape);
+ tcase_add_test(tc, test_parse_boolean);
+ tcase_add_test(tc, test_parse_volume);
+ tcase_add_test(tc, test_atoi);
+ tcase_add_test(tc, test_atou);
+ tcase_add_test(tc, test_atou64);
+ tcase_add_test(tc, test_atol);
+ tcase_add_test(tc, test_atoi64);
+ tcase_add_test(tc, test_atod);
+ tcase_add_test(tc, test_replace);
+ tcase_add_test_raise_signal(tc, test_replace_fail_1, SIGABRT);
+ tcase_add_test_raise_signal(tc, test_replace_fail_2, SIGABRT);
+ tcase_add_test_raise_signal(tc, test_replace_fail_3, SIGABRT);
+ tcase_add_test_raise_signal(tc, test_replace_fail_4, SIGABRT);
+ tcase_add_test(tc, test_escape);
+ tcase_add_test(tc, test_unescape);
sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
=====================================
src/utils/pacmd.c
=====================================
@@ -77,6 +77,7 @@ static void help(const char *argv0) {
printf("%s %s %s\n", argv0, "set-log-meta", _("1|0"));
printf("%s %s %s\n", argv0, "set-log-time", _("1|0"));
printf("%s %s %s\n", argv0, "set-log-backtrace", _("FRAMES"));
+ printf("%s %s %s\n", argv0, "send-message", _("RECIPIENT MESSAGE [MESSAGE_PARAMETERS]"));
printf(_("\n"
" -h, --help Show this help\n"
=====================================
src/utils/pactl.c
=====================================
@@ -35,6 +35,7 @@
#include <sndfile.h>
#include <pulse/pulseaudio.h>
+#include <pulse/message-params.h>
#include <pulse/ext-device-restore.h>
#include <pulsecore/i18n.h>
@@ -56,7 +57,10 @@ static char
*card_name = NULL,
*profile_name = NULL,
*port_name = NULL,
- *formats = NULL;
+ *formats = NULL,
+ *object_path = NULL,
+ *message = NULL,
+ *message_args = NULL;
static uint32_t
sink_input_idx = PA_INVALID_INDEX,
@@ -130,6 +134,7 @@ static enum {
SET_SOURCE_OUTPUT_MUTE,
SET_SINK_FORMATS,
SET_PORT_LATENCY_OFFSET,
+ SEND_MESSAGE,
SUBSCRIBE
} action = NONE;
@@ -878,6 +883,75 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
complete_action();
}
+static void send_message_callback(pa_context *c, int success, char *response, void *userdata) {
+
+ if (!success) {
+ pa_log(_("Send message failed: %s"), pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ printf("%s\n", response);
+
+ complete_action();
+}
+
+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;
+
+ if (!success) {
+ pa_log(_("list-handlers message failed: %s"), pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (pa_message_params_read_raw(response, &handler_list, &state) <= 0) {
+ pa_log(_("list-handlers message response could not be parsed correctly"));
+ quit(1);
+ return;
+ }
+
+ state = NULL;
+ while ((err = pa_message_params_read_raw(handler_list, &handler_struct, &state)) > 0) {
+ void *state2 = NULL;
+ const char *path;
+ const char *description;
+
+ if (pa_message_params_read_string(handler_struct, &path, &state2) <= 0) {
+ err = -1;
+ break;
+ }
+ if (pa_message_params_read_string(handler_struct, &description, &state2) <= 0) {
+ err = -1;
+ break;
+ }
+
+ if (short_list_format)
+ printf("%s\n", path);
+ else {
+ if (nl)
+ printf("\n");
+ nl = true;
+
+ printf("Message Handler %s\n"
+ "\tDescription: %s\n",
+ path,
+ description);
+ }
+ }
+
+ if (err < 0) {
+ pa_log(_("list-handlers message response could not be parsed correctly"));
+ quit(1);
+ return;
+ }
+
+ complete_action();
+}
+
static void volume_relative_adjust(pa_cvolume *cv) {
pa_assert(volume_flags & VOL_RELATIVE);
@@ -1291,6 +1365,8 @@ static void context_state_callback(pa_context *c, void *userdata) {
o = pa_context_get_sample_info_list(c, get_sample_info_callback, NULL);
else if (pa_streq(list_type, "cards"))
o = pa_context_get_card_info_list(c, get_card_info_callback, NULL);
+ else if (pa_streq(list_type, "message-handlers"))
+ o = pa_context_send_message_to_object(c, "/core", "list-handlers", NULL, list_handlers_callback, NULL);
else
pa_assert_not_reached();
} else {
@@ -1450,6 +1526,10 @@ static void context_state_callback(pa_context *c, void *userdata) {
o = pa_context_set_port_latency_offset(c, card_name, port_name, latency_offset, simple_callback, NULL);
break;
+ case SEND_MESSAGE:
+ o = pa_context_send_message_to_object(c, object_path, message, message_args, send_message_callback, NULL);
+ break;
+
case SUBSCRIBE:
pa_context_set_subscribe_callback(c, context_subscribe_callback, NULL);
@@ -1626,6 +1706,7 @@ static void help(const char *argv0) {
printf("%s %s %s %s\n", argv0, _("[options]"), "set-(sink-input|source-output)-mute", _("#N 1|0|toggle"));
printf("%s %s %s %s\n", argv0, _("[options]"), "set-sink-formats", _("#N FORMATS"));
printf("%s %s %s %s\n", argv0, _("[options]"), "set-port-latency-offset", _("CARD-NAME|CARD-#N PORT OFFSET"));
+ printf("%s %s %s %s\n", argv0, _("[options]"), "send-message", _("RECIPIENT MESSAGE [MESSAGE_PARAMETERS]"));
printf("%s %s %s\n", argv0, _("[options]"), "subscribe");
printf(_("\nThe special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and @DEFAULT_MONITOR@\n"
"can be used to specify the default sink, source and monitor.\n"));
@@ -1722,12 +1803,13 @@ int main(int argc, char *argv[]) {
if (pa_streq(argv[i], "modules") || pa_streq(argv[i], "clients") ||
pa_streq(argv[i], "sinks") || pa_streq(argv[i], "sink-inputs") ||
pa_streq(argv[i], "sources") || pa_streq(argv[i], "source-outputs") ||
- pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards")) {
+ pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards") ||
+ pa_streq(argv[i], "message-handlers")) {
list_type = pa_xstrdup(argv[i]);
} else if (pa_streq(argv[i], "short")) {
short_list_format = true;
} else {
- pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards");
+ pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards, message-handlers");
goto quit;
}
}
@@ -2061,6 +2143,22 @@ int main(int argc, char *argv[]) {
goto quit;
}
+ } else if (pa_streq(argv[optind], "send-message")) {
+ action = SEND_MESSAGE;
+
+ if (argc < optind+3) {
+ pa_log(_("You have to specify at least an object path and a message name"));
+ goto quit;
+ }
+
+ object_path = pa_xstrdup(argv[optind + 1]);
+ message = pa_xstrdup(argv[optind + 2]);
+ if (argc >= optind+4)
+ message_args = pa_xstrdup(argv[optind + 3]);
+
+ if (argc > optind+4)
+ pa_log(_("Excess arguments given, they will be ignored. Note that all message parameters must be given as a single string."));
+
} else if (pa_streq(argv[optind], "subscribe"))
action = SUBSCRIBE;
@@ -2154,6 +2252,9 @@ quit:
pa_xfree(profile_name);
pa_xfree(port_name);
pa_xfree(formats);
+ pa_xfree(object_path);
+ pa_xfree(message);
+ pa_xfree(message_args);
if (sndfile)
sf_close(sndfile);
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/cb3d12377cb1131fb3627ece66b0b70c2c5e0479...c1ac76014e89e995f107a0f569a8cf7fe661b307
--
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/cb3d12377cb1131fb3627ece66b0b70c2c5e0479...c1ac76014e89e995f107a0f569a8cf7fe661b307
You're receiving this email because of your account on gitlab.freedesktop.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/pulseaudio-commits/attachments/20201203/f2bcf371/attachment-0001.htm>
More information about the pulseaudio-commits
mailing list