diff options
author | acheronfail <acheronfail@gmail.com> | 2021-06-06 18:25:08 +1000 |
---|---|---|
committer | PulseAudio Marge Bot <pulseaudio-maintainers@lists.freedesktop.org> | 2021-08-11 15:31:05 +0000 |
commit | 19adddee31ca34bf4e0db95df01b4ec595f2d267 (patch) | |
tree | 057595b4dcc9cdff4f8848bc7dce8005369ac3dd | |
parent | 1b96b49f65744930050b927a548a3d2e771c7310 (diff) |
pactl: add format flag for JSON output
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/497>
-rw-r--r-- | man/pactl.1.xml.in | 8 | ||||
-rw-r--r-- | src/pulsecore/json.c | 10 | ||||
-rw-r--r-- | src/pulsecore/json.h | 2 | ||||
-rw-r--r-- | src/utils/pactl.c | 1245 |
4 files changed, 1045 insertions, 220 deletions
diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in index 6da020fe5..d4eb03458 100644 --- a/man/pactl.1.xml.in +++ b/man/pactl.1.xml.in @@ -54,6 +54,12 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. </option> <option> + <p><opt>-f | --format</opt><arg>=FORMAT</arg></p> + + <optdesc><p>Choose output format, available options are "text" or "json".</p></optdesc> + </option> + + <option> <p><opt>-n | --client-name</opt><arg>=NAME</arg></p> <optdesc><p>Specify the client name <file>pactl</file> shall pass to the server when connecting.</p></optdesc> @@ -63,7 +69,7 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. <section name="Commands"> <p> - When supplied as arguments to the commands below, the special names \@DEFAULT_SINK@, \@DEFAULT_SOURCE@ and \@DEFAULT_MONITOR@ + When supplied as arguments to the commands below, the special names \@DEFAULT_SINK@, \@DEFAULT_SOURCE@ and \@DEFAULT_MONITOR@ can be used to specify the default sink, source and monitor respectively. </p> diff --git a/src/pulsecore/json.c b/src/pulsecore/json.c index 0b7b4c3e0..e2010e9fc 100644 --- a/src/pulsecore/json.c +++ b/src/pulsecore/json.c @@ -650,6 +650,16 @@ static pa_json_context_type_t json_encoder_context_pop(pa_json_encoder *encoder) return type; } +bool pa_json_encoder_is_empty(pa_json_encoder *encoder) { + pa_json_context_type_t type; + + pa_assert(encoder); + pa_assert(encoder->context); + + type = encoder->context->type; + return type == PA_JSON_CONTEXT_EMPTY; +} + pa_json_encoder *pa_json_encoder_new(void) { pa_json_encoder *encoder; diff --git a/src/pulsecore/json.h b/src/pulsecore/json.h index 0c5024288..8f187d838 100644 --- a/src/pulsecore/json.h +++ b/src/pulsecore/json.h @@ -71,6 +71,8 @@ void pa_json_encoder_free(pa_json_encoder *encoder); /** Convert pa_json_encoder to string, free pa_json_encoder structure. * The returned string needs to be freed with pa_xree(). \since 15.0 */ char *pa_json_encoder_to_string_free(pa_json_encoder *encoder); +/** Check if a pa_json_encoder is empty (nothing has been added). \since 16.0 */ +bool pa_json_encoder_is_empty(pa_json_encoder *encoder); /** Start appending JSON object element by writing an opening brace. \since 15.0 */ void pa_json_encoder_begin_element_object(pa_json_encoder *encoder); diff --git a/src/utils/pactl.c b/src/utils/pactl.c index 0f68861d7..cb6db58b7 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -36,6 +36,7 @@ #include <pulse/pulseaudio.h> #include <pulse/ext-device-restore.h> +#include <pulse/xmalloc.h> #include <pulsecore/i18n.h> #include <pulsecore/json.h> @@ -103,6 +104,8 @@ static size_t sample_length = 0; static int actions = 0; static bool nl = false; +static pa_json_encoder *list_encoder = NULL; +static pa_json_encoder *json_encoder = NULL; static enum { NONE, @@ -144,6 +147,11 @@ static enum { SUBSCRIBE } action = NONE; +static enum { + TEXT, + JSON +} format = TEXT; + static void quit(int ret) { pa_assert(mainloop_api); mainloop_api->quit(mainloop_api, ret); @@ -177,20 +185,31 @@ static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) return; } - pa_bytes_snprint(s, sizeof(s), i->memblock_total_size); - printf(ngettext("Currently in use: %u block containing %s bytes total.\n", - "Currently in use: %u blocks containing %s bytes total.\n", - i->memblock_total), - i->memblock_total, s); - - pa_bytes_snprint(s, sizeof(s), i->memblock_allocated_size); - printf(ngettext("Allocated during whole lifetime: %u block containing %s bytes total.\n", - "Allocated during whole lifetime: %u blocks containing %s bytes total.\n", - i->memblock_allocated), - i->memblock_allocated, s); - - pa_bytes_snprint(s, sizeof(s), i->scache_size); - printf(_("Sample cache size: %s\n"), s); + if (format == JSON) { + printf("{\"current\":{\"blocks\":%u,\"size\":%u}," + "\"lifetime\":{\"blocks\":%u,\"size\":%u}," + "\"sample_cache_size\":%u}", + i->memblock_total, + i->memblock_total_size, + i->memblock_allocated, + i->memblock_allocated_size, + i->scache_size); + } else { + pa_bytes_snprint(s, sizeof(s), i->memblock_total_size); + printf(ngettext("Currently in use: %u block containing %s bytes total.\n", + "Currently in use: %u blocks containing %s bytes total.\n", + i->memblock_total), + i->memblock_total, s); + + pa_bytes_snprint(s, sizeof(s), i->memblock_allocated_size); + printf(ngettext("Allocated during whole lifetime: %u block containing %s bytes total.\n", + "Allocated during whole lifetime: %u blocks containing %s bytes total.\n", + i->memblock_allocated), + i->memblock_allocated, s); + + pa_bytes_snprint(s, sizeof(s), i->scache_size); + printf(_("Sample cache size: %s\n"), s); + } complete_action(); } @@ -228,7 +247,38 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi return; } - printf(_("Server String: %s\n" + pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec); + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map); + + if (format == JSON) { + char* tile_size = pa_sprintf_malloc("%zu", pa_context_get_tile_size(c, NULL)); + char* cookie = pa_sprintf_malloc("%04x:%04x", i->cookie >> 16, i->cookie & 0xFFFFU); + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "server_string", pa_context_get_server(c)); + pa_json_encoder_add_member_int(encoder, "library_protocol_version", pa_context_get_protocol_version(c)); + pa_json_encoder_add_member_int(encoder, "server_protocol_version", pa_context_get_server_protocol_version(c)); + pa_json_encoder_add_member_string(encoder, "is_local", pa_yes_no_localised(pa_context_is_local(c))); + pa_json_encoder_add_member_int(encoder, "client_index", pa_context_get_index(c)); + pa_json_encoder_add_member_string(encoder, "tile_size", tile_size); + pa_json_encoder_add_member_string(encoder, "user_name", i->user_name); + pa_json_encoder_add_member_string(encoder, "host_name", i->host_name); + pa_json_encoder_add_member_string(encoder, "server_name", i->server_name); + pa_json_encoder_add_member_string(encoder, "server_version", i->server_version); + pa_json_encoder_add_member_string(encoder, "default_sample_specification", ss); + pa_json_encoder_add_member_string(encoder, "default_channel_map", cm); + pa_json_encoder_add_member_string(encoder, "default_sink_name", i->default_sink_name); + pa_json_encoder_add_member_string(encoder, "default_source_name", i->default_source_name); + pa_json_encoder_add_member_string(encoder, "cookie", cookie); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + printf("%s", json_str); + pa_xfree(json_str); + pa_xfree(tile_size); + pa_xfree(cookie); + } else { + printf(_("Server String: %s\n" "Library Protocol Version: %u\n" "Server Protocol Version: %u\n" "Is Local: %s\n" @@ -241,28 +291,26 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi pa_context_get_index(c), pa_context_get_tile_size(c, NULL)); - pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec); - pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map); - - printf(_("User Name: %s\n" - "Host Name: %s\n" - "Server Name: %s\n" - "Server Version: %s\n" - "Default Sample Specification: %s\n" - "Default Channel Map: %s\n" - "Default Sink: %s\n" - "Default Source: %s\n" - "Cookie: %04x:%04x\n"), - i->user_name, - i->host_name, - i->server_name, - i->server_version, - ss, - cm, - i->default_sink_name, - i->default_source_name, - i->cookie >> 16, - i->cookie & 0xFFFFU); + printf(_("User Name: %s\n" + "Host Name: %s\n" + "Server Name: %s\n" + "Server Version: %s\n" + "Default Sample Specification: %s\n" + "Default Channel Map: %s\n" + "Default Sink: %s\n" + "Default Source: %s\n" + "Cookie: %04x:%04x\n"), + i->user_name, + i->host_name, + i->server_name, + i->server_version, + ss, + cm, + i->default_sink_name, + i->default_source_name, + i->cookie >> 16, + i->cookie & 0xFFFFU); + } complete_action(); } @@ -308,6 +356,191 @@ static const char* get_device_port_type(unsigned int type) { return buf; } +char* pa_proplist_to_json_object(const pa_proplist *p) { + const char *key; + void *state = NULL; + pa_json_encoder *encoder; + + pa_assert(p); + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + while (true) { + key = pa_proplist_iterate(p, &state); + if (!key) break; + + const char *v; + + if ((v = pa_proplist_gets(p, key))) { + pa_json_encoder_add_member_string(encoder, key, v); + } else { + const void *value; + size_t nbytes; + char *c; + char* hex_str; + + pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0); + c = pa_xmalloc(nbytes*2+1); + pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1); + + hex_str = pa_sprintf_malloc("hex:%s", c); + pa_json_encoder_add_member_string(encoder, key, hex_str); + pa_xfree(c); + pa_xfree(hex_str); + } + } + pa_json_encoder_end_object(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + +static const char* pa_sink_ports_to_json_array(pa_sink_port_info **ports) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!ports) { + pa_json_encoder_begin_element_array(encoder); + pa_json_encoder_end_array(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + pa_sink_port_info **p; + + pa_json_encoder_begin_element_array(encoder); + for (p = ports; *p; p++) { + pa_json_encoder *sink_port_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(sink_port_encoder); + pa_json_encoder_add_member_string(sink_port_encoder, "name", (*p)->name); + pa_json_encoder_add_member_string(sink_port_encoder, "description", (*p)->description); + pa_json_encoder_add_member_string(sink_port_encoder, "type", get_device_port_type((*p)->type)); + pa_json_encoder_add_member_int(sink_port_encoder, "priority", (*p)->priority); + pa_json_encoder_add_member_string(sink_port_encoder, "availability_group", (*p)->availability_group); + pa_json_encoder_add_member_string(sink_port_encoder, "availability", get_available_str((*p)->available)); + pa_json_encoder_end_object(sink_port_encoder); + + char* sink_port_str = pa_json_encoder_to_string_free(sink_port_encoder); + pa_json_encoder_add_element_raw_json(encoder, sink_port_str); + pa_xfree(sink_port_str); + } + pa_json_encoder_end_array(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + +static const char* pa_source_ports_to_json_array(pa_source_port_info **ports) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!ports) { + pa_json_encoder_begin_element_array(encoder); + pa_json_encoder_end_array(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + pa_source_port_info **p; + + pa_json_encoder_begin_element_array(encoder); + for (p = ports; *p; p++) { + pa_json_encoder *source_port_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(source_port_encoder); + pa_json_encoder_add_member_string(source_port_encoder, "name", (*p)->name); + pa_json_encoder_add_member_string(source_port_encoder, "description", (*p)->description); + pa_json_encoder_add_member_string(source_port_encoder, "type", get_device_port_type((*p)->type)); + pa_json_encoder_add_member_int(source_port_encoder, "priority", (*p)->priority); + pa_json_encoder_add_member_string(source_port_encoder, "availability_group", (*p)->availability_group); + pa_json_encoder_add_member_string(source_port_encoder, "availability", get_available_str((*p)->available)); + pa_json_encoder_end_object(source_port_encoder); + + char* source_port_str = pa_json_encoder_to_string_free(source_port_encoder); + pa_json_encoder_add_element_raw_json(encoder, source_port_str); + pa_xfree(source_port_str); + } + pa_json_encoder_end_array(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + +static const char* pa_format_infos_to_json_array(pa_format_info **formats, uint8_t n_formats) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!formats) { + pa_json_encoder_begin_element_array(encoder); + pa_json_encoder_end_array(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + char f[PA_FORMAT_INFO_SNPRINT_MAX]; + uint8_t i; + + pa_json_encoder_begin_element_array(encoder); + for (i = 0; i < n_formats; i++) { + pa_json_encoder_add_element_string(encoder, pa_format_info_snprint(f, sizeof(f), formats[i])); + } + pa_json_encoder_end_array(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + +const char* pa_volume_to_json_object(pa_volume_t v, int print_dB) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!PA_VOLUME_IS_VALID(v)) { + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "error", _("(invalid)")); + pa_json_encoder_end_object(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + char dB[PA_SW_VOLUME_SNPRINT_DB_MAX]; + char* value_percent = pa_sprintf_malloc("%u%%", (unsigned)(((uint64_t)v * 100 + (uint64_t)PA_VOLUME_NORM / 2) / (uint64_t)PA_VOLUME_NORM)); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "value", v); + pa_json_encoder_add_member_string(encoder, "value_percent", value_percent); + pa_json_encoder_add_member_string(encoder, "db", print_dB ? pa_sw_volume_snprint_dB(dB, sizeof(dB), v) : NULL); + pa_json_encoder_end_object(encoder); + pa_xfree(value_percent); + + return pa_json_encoder_to_string_free(encoder); +} + +const char* pa_cvolume_to_json_object(const pa_cvolume *c, const pa_channel_map *map, int print_dB) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!pa_cvolume_valid(c)) { + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "error", _("(invalid)")); + pa_json_encoder_end_object(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + pa_assert(!map || (map->channels == c->channels)); + pa_assert(!map || pa_channel_map_valid(map)); + + pa_json_encoder_begin_element_object(encoder); + for (unsigned channel = 0; channel < c->channels; channel++) { + char channel_position[32]; + if (map) + pa_snprintf(channel_position, sizeof(channel_position), "%s", pa_channel_position_to_string(map->map[channel])); + else + pa_snprintf(channel_position, sizeof(channel_position), "%u", channel); + + pa_json_encoder_add_member_raw_json(encoder, + channel_position, + pa_volume_to_json_object(c->values[channel], print_dB)); + } + pa_json_encoder_end_object(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + +static void pa_json_encoder_end_array_handler(const char *name) { + pa_assert(json_encoder != NULL); + + pa_json_encoder_end_array(json_encoder); + char* json_str = pa_json_encoder_to_string_free(json_encoder); + if (list_encoder != NULL) { + pa_json_encoder_add_member_raw_json(list_encoder, name, json_str); + } else { + printf("%s", json_str); + } + pa_xfree(json_str); + + json_encoder = NULL; +} + static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) { static const char *state_table[] = { @@ -325,6 +558,11 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ f[PA_FORMAT_INFO_SNPRINT_MAX]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get sink information: %s"), pa_strerror(pa_context_errno(c))); quit(1); @@ -332,27 +570,98 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("sinks"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; + char *sample_spec = pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec); if (short_list_format) { - printf("%u\t%s\t%s\t%s\t%s\n", + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_string(encoder, "state", state_table[1+i->state]); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%s\t%s\t%s\t%s\n", i->index, i->name, pa_strnull(i->driver), - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + sample_spec, state_table[1+i->state]); + } return; } - printf(_("Sink #%u\n" + char *channel_map = pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map); + float volume_balance = pa_cvolume_get_balance(&i->volume, &i->channel_map); + + if (format == JSON) { + pa_json_encoder *latency_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(latency_encoder); + pa_json_encoder_add_member_double(latency_encoder, "actual", (double) i->latency, 2); + pa_json_encoder_add_member_double(latency_encoder, "configured", (double) i->configured_latency, 2); + pa_json_encoder_end_object(latency_encoder); + char* latency_json_str = pa_json_encoder_to_string_free(latency_encoder); + + pa_json_encoder *flags_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(flags_encoder); + if (i->flags & PA_SINK_HARDWARE) pa_json_encoder_add_element_string(flags_encoder, "HARDWARE"); + if (i->flags & PA_SINK_NETWORK) pa_json_encoder_add_element_string(flags_encoder, "NETWORK"); + if (i->flags & PA_SINK_HW_MUTE_CTRL) pa_json_encoder_add_element_string(flags_encoder, "HW_MUTE_CTRL"); + if (i->flags & PA_SINK_HW_VOLUME_CTRL) pa_json_encoder_add_element_string(flags_encoder, "HW_VOLUME_CTRL"); + if (i->flags & PA_SINK_DECIBEL_VOLUME) pa_json_encoder_add_element_string(flags_encoder, "DECIBEL_VOLUME"); + if (i->flags & PA_SINK_LATENCY) pa_json_encoder_add_element_string(flags_encoder, "LATENCY"); + if (i->flags & PA_SINK_SET_FORMATS) pa_json_encoder_add_element_string(flags_encoder, "SET_FORMATS"); + pa_json_encoder_end_array(flags_encoder); + char* flags_json_str = pa_json_encoder_to_string_free(flags_encoder); + + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "state", state_table[1+i->state]); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "description", i->description); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_string(encoder, "channel_map", channel_map); + pa_json_encoder_add_member_int(encoder, "owner_module", i->owner_module); + pa_json_encoder_add_member_bool(encoder, "mute", i->mute); + pa_json_encoder_add_member_raw_json(encoder, "volume", pa_cvolume_to_json_object(&i->volume, &i->channel_map, i->flags & PA_SINK_DECIBEL_VOLUME)); + pa_json_encoder_add_member_double(encoder, "balance", volume_balance, 2); + pa_json_encoder_add_member_raw_json(encoder, "base_volume", pa_volume_to_json_object(i->base_volume, i->flags & PA_SINK_DECIBEL_VOLUME)); + pa_json_encoder_add_member_string(encoder, "monitor_source", i->monitor_source_name); + pa_json_encoder_add_member_raw_json(encoder, "latency", latency_json_str); + pa_json_encoder_add_member_raw_json(encoder, "flags", flags_json_str); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_add_member_raw_json(encoder, "ports", pa_sink_ports_to_json_array(i->ports)); + i->active_port ? pa_json_encoder_add_member_string(encoder, "active_port", i->active_port->name): pa_json_encoder_add_member_null(encoder, "active_port"); + pa_json_encoder_add_member_raw_json(encoder, "formats", pa_format_infos_to_json_array(i->formats, i->n_formats)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + pa_xfree(latency_json_str); + pa_xfree(flags_json_str); + } else { + printf(_("Sink #%u\n" "\tState: %s\n" "\tName: %s\n" "\tDescription: %s\n" @@ -373,12 +682,12 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ i->name, pa_strnull(i->description), pa_strnull(i->driver), - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + sample_spec, + channel_map, i->owner_module, pa_yes_no_localised(i->mute), pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, i->flags & PA_SINK_DECIBEL_VOLUME), - pa_cvolume_get_balance(&i->volume, &i->channel_map), + volume_balance, pa_volume_snprint_verbose(v, sizeof(v), i->base_volume, i->flags & PA_SINK_DECIBEL_VOLUME), pa_strnull(i->monitor_source_name), (double) i->latency, (double) i->configured_latency, @@ -391,30 +700,31 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ i->flags & PA_SINK_SET_FORMATS ? "SET_FORMATS " : "", pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); - pa_xfree(pl); - - if (i->ports) { - pa_sink_port_info **p; + if (i->ports) { + pa_sink_port_info **p; - printf(_("\tPorts:\n")); - for (p = i->ports; *p; p++) - printf(_("\t\t%s: %s (type: %s, priority: %u%s%s, %s)\n"), - (*p)->name, (*p)->description, get_device_port_type((*p)->type), - (*p)->priority, (*p)->availability_group ? _(", availability group: ") : "", - (*p)->availability_group ?: "", get_available_str((*p)->available)); - } + printf(_("\tPorts:\n")); + for (p = i->ports; *p; p++) + printf(_("\t\t%s: %s (type: %s, priority: %u%s%s, %s)\n"), + (*p)->name, (*p)->description, get_device_port_type((*p)->type), + (*p)->priority, (*p)->availability_group ? _(", availability group: ") : "", + (*p)->availability_group ?: "", get_available_str((*p)->available)); + } - if (i->active_port) - printf(_("\tActive Port: %s\n"), - i->active_port->name); + if (i->active_port) + printf(_("\tActive Port: %s\n"), + i->active_port->name); - if (i->formats) { - uint8_t j; + if (i->formats) { + uint8_t j; - printf(_("\tFormats:\n")); - for (j = 0; j < i->n_formats; j++) - printf("\t\t%s\n", pa_format_info_snprint(f, sizeof(f), i->formats[j])); + printf(_("\tFormats:\n")); + for (j = 0; j < i->n_formats; j++) + printf("\t\t%s\n", pa_format_info_snprint(f, sizeof(f), i->formats[j])); + } } + + pa_xfree(pl); } static void get_source_info_callback(pa_context *c, const pa_source_info *i, int is_last, void *userdata) { @@ -434,6 +744,11 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int f[PA_FORMAT_INFO_SNPRINT_MAX]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get source information: %s"), pa_strerror(pa_context_errno(c))); quit(1); @@ -441,94 +756,170 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("sources"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; + char *sample_spec = pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec); if (short_list_format) { - printf("%u\t%s\t%s\t%s\t%s\n", + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_string(encoder, "state", state_table[1+i->state]); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%s\t%s\t%s\t%s\n", i->index, i->name, pa_strnull(i->driver), - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + sample_spec, state_table[1+i->state]); + } return; } - printf(_("Source #%u\n" - "\tState: %s\n" - "\tName: %s\n" - "\tDescription: %s\n" - "\tDriver: %s\n" - "\tSample Specification: %s\n" - "\tChannel Map: %s\n" - "\tOwner Module: %u\n" - "\tMute: %s\n" - "\tVolume: %s\n" - "\t balance %0.2f\n" - "\tBase Volume: %s\n" - "\tMonitor of Sink: %s\n" - "\tLatency: %0.0f usec, configured %0.0f usec\n" - "\tFlags: %s%s%s%s%s%s\n" - "\tProperties:\n\t\t%s\n"), - i->index, - state_table[1+i->state], - i->name, - pa_strnull(i->description), - pa_strnull(i->driver), - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), - i->owner_module, - pa_yes_no_localised(i->mute), - pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, i->flags & PA_SOURCE_DECIBEL_VOLUME), - pa_cvolume_get_balance(&i->volume, &i->channel_map), - pa_volume_snprint_verbose(v, sizeof(v), i->base_volume, i->flags & PA_SOURCE_DECIBEL_VOLUME), - i->monitor_of_sink_name ? i->monitor_of_sink_name : _("n/a"), - (double) i->latency, (double) i->configured_latency, - i->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "", - i->flags & PA_SOURCE_NETWORK ? "NETWORK " : "", - i->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", - i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", - i->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", - i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", - pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + char *channel_map = pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map); + float volume_balance = pa_cvolume_get_balance(&i->volume, &i->channel_map); + + if (format == JSON) { + pa_json_encoder *latency_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(latency_encoder); + pa_json_encoder_add_member_double(latency_encoder, "actual", (double) i->latency, 2); + pa_json_encoder_add_member_double(latency_encoder, "configured", (double) i->configured_latency, 2); + pa_json_encoder_end_object(latency_encoder); + char* latency_json_str = pa_json_encoder_to_string_free(latency_encoder); + + pa_json_encoder *flags_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(flags_encoder); + if (i->flags & PA_SOURCE_HARDWARE) pa_json_encoder_add_element_string(flags_encoder, "HARDWARE"); + if (i->flags & PA_SOURCE_NETWORK) pa_json_encoder_add_element_string(flags_encoder, "NETWORK"); + if (i->flags & PA_SOURCE_HW_MUTE_CTRL) pa_json_encoder_add_element_string(flags_encoder, "HW_MUTE_CTRL"); + if (i->flags & PA_SOURCE_HW_VOLUME_CTRL) pa_json_encoder_add_element_string(flags_encoder, "HW_VOLUME_CTRL"); + if (i->flags & PA_SOURCE_DECIBEL_VOLUME) pa_json_encoder_add_element_string(flags_encoder, "DECIBEL_VOLUME"); + if (i->flags & PA_SOURCE_LATENCY) pa_json_encoder_add_element_string(flags_encoder, "LATENCY"); + pa_json_encoder_end_array(flags_encoder); + char* flags_json_str = pa_json_encoder_to_string_free(flags_encoder); + + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "state", state_table[1+i->state]); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "description", i->description); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_string(encoder, "channel_map", channel_map); + pa_json_encoder_add_member_int(encoder, "owner_module", i->owner_module); + pa_json_encoder_add_member_bool(encoder, "mute", i->mute); + pa_json_encoder_add_member_raw_json(encoder, "volume", pa_cvolume_to_json_object(&i->volume, &i->channel_map, i->flags & PA_SINK_DECIBEL_VOLUME)); + pa_json_encoder_add_member_double(encoder, "balance", volume_balance, 2); + pa_json_encoder_add_member_raw_json(encoder, "base_volume", pa_volume_to_json_object(i->base_volume, i->flags & PA_SINK_DECIBEL_VOLUME)); + pa_json_encoder_add_member_string(encoder, "monitor_source", i->monitor_of_sink_name); + pa_json_encoder_add_member_raw_json(encoder, "latency", latency_json_str); + pa_json_encoder_add_member_raw_json(encoder, "flags", flags_json_str); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_add_member_raw_json(encoder, "ports", pa_source_ports_to_json_array(i->ports)); + i->active_port ? pa_json_encoder_add_member_string(encoder, "active_port", i->active_port->name) : pa_json_encoder_add_member_null(encoder, "active_port"); + pa_json_encoder_add_member_raw_json(encoder, "formats", pa_format_infos_to_json_array(i->formats, i->n_formats)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + pa_xfree(latency_json_str); + pa_xfree(flags_json_str); + } else { + printf(_("Source #%u\n" + "\tState: %s\n" + "\tName: %s\n" + "\tDescription: %s\n" + "\tDriver: %s\n" + "\tSample Specification: %s\n" + "\tChannel Map: %s\n" + "\tOwner Module: %u\n" + "\tMute: %s\n" + "\tVolume: %s\n" + "\t balance %0.2f\n" + "\tBase Volume: %s\n" + "\tMonitor of Sink: %s\n" + "\tLatency: %0.0f usec, configured %0.0f usec\n" + "\tFlags: %s%s%s%s%s%s\n" + "\tProperties:\n\t\t%s\n"), + i->index, + state_table[1+i->state], + i->name, + pa_strnull(i->description), + pa_strnull(i->driver), + sample_spec, + channel_map, + i->owner_module, + pa_yes_no_localised(i->mute), + pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, i->flags & PA_SOURCE_DECIBEL_VOLUME), + volume_balance, + pa_volume_snprint_verbose(v, sizeof(v), i->base_volume, i->flags & PA_SOURCE_DECIBEL_VOLUME), + i->monitor_of_sink_name ? i->monitor_of_sink_name : _("n/a"), + (double) i->latency, (double) i->configured_latency, + i->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "", + i->flags & PA_SOURCE_NETWORK ? "NETWORK " : "", + i->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", + i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + i->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", + i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", + pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + + if (i->ports) { + pa_source_port_info **p; + + printf(_("\tPorts:\n")); + for (p = i->ports; *p; p++) + printf(_("\t\t%s: %s (type: %s, priority: %u%s%s, %s)\n"), + (*p)->name, (*p)->description, get_device_port_type((*p)->type), + (*p)->priority, (*p)->availability_group ? _(", availability group: ") : "", + (*p)->availability_group ?: "", get_available_str((*p)->available)); + } - pa_xfree(pl); + if (i->active_port) + printf(_("\tActive Port: %s\n"), + i->active_port->name); - if (i->ports) { - pa_source_port_info **p; + if (i->formats) { + uint8_t j; - printf(_("\tPorts:\n")); - for (p = i->ports; *p; p++) - printf(_("\t\t%s: %s (type: %s, priority: %u%s%s, %s)\n"), - (*p)->name, (*p)->description, get_device_port_type((*p)->type), - (*p)->priority, (*p)->availability_group ? _(", availability group: ") : "", - (*p)->availability_group ?: "", get_available_str((*p)->available)); + printf(_("\tFormats:\n")); + for (j = 0; j < i->n_formats; j++) + printf("\t\t%s\n", pa_format_info_snprint(f, sizeof(f), i->formats[j])); + } } - if (i->active_port) - printf(_("\tActive Port: %s\n"), - i->active_port->name); - - if (i->formats) { - uint8_t j; - - printf(_("\tFormats:\n")); - for (j = 0; j < i->n_formats; j++) - printf("\t\t%s\n", pa_format_info_snprint(f, sizeof(f), i->formats[j])); - } + pa_xfree(pl); } static void get_module_info_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) { char t[32]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get module information: %s"), pa_strerror(pa_context_errno(c))); quit(1); @@ -536,33 +927,64 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("modules"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; pa_snprintf(t, sizeof(t), "%u", i->n_used); if (short_list_format) { - printf("%u\t%s\t%s\t\n", i->index, i->name, i->argument ? i->argument : ""); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "argument", i->argument); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%s\t%s\t\n", i->index, i->name, i->argument ? i->argument : ""); + } return; } - printf(_("Module #%u\n" - "\tName: %s\n" - "\tArgument: %s\n" - "\tUsage counter: %s\n" - "\tProperties:\n\t\t%s\n"), - i->index, - i->name, - i->argument ? i->argument : "", - i->n_used != PA_INVALID_INDEX ? t : _("n/a"), - pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + char *n_used = i->n_used != PA_INVALID_INDEX ? t : _("n/a"); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "argument", i->argument); + pa_json_encoder_add_member_string(encoder, "usage_counter", n_used); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf(_("Module #%u\n" + "\tName: %s\n" + "\tArgument: %s\n" + "\tUsage counter: %s\n" + "\tProperties:\n\t\t%s\n"), + i->index, + i->name, + i->argument ? i->argument : "", + n_used, + pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + + } pa_xfree(pl); } @@ -571,6 +993,11 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int char t[32]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get client information: %s"), pa_strerror(pa_context_errno(c))); quit(1); @@ -578,42 +1005,165 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("clients"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; pa_snprintf(t, sizeof(t), "%u", i->owner_module); if (short_list_format) { - printf("%u\t%s\t%s\n", + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_add_member_string(encoder, PA_PROP_APPLICATION_PROCESS_BINARY, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%s\t%s\n", i->index, pa_strnull(i->driver), pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_PROCESS_BINARY))); + } return; + } else { + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + i->driver ? pa_json_encoder_add_member_string(encoder, "driver", i->driver) : pa_json_encoder_add_member_null(encoder, "driver"); + i->owner_module != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "owner_module", t) : pa_json_encoder_add_member_null(encoder, "owner_module"); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf(_("Client #%u\n" + "\tDriver: %s\n" + "\tOwner Module: %s\n" + "\tProperties:\n\t\t%s\n"), + i->index, + pa_strnull(i->driver), + i->owner_module != PA_INVALID_INDEX ? t : _("n/a"), + pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + } } - printf(_("Client #%u\n" - "\tDriver: %s\n" - "\tOwner Module: %s\n" - "\tProperties:\n\t\t%s\n"), - i->index, - pa_strnull(i->driver), - i->owner_module != PA_INVALID_INDEX ? t : _("n/a"), - pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); - pa_xfree(pl); } +const char* pa_card_profile_info_2_to_json_object(pa_card_profile_info2 **profiles2) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!profiles2) { + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_end_object(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + pa_card_profile_info2 **p; + + pa_json_encoder_begin_element_object(encoder); + for (p = profiles2; *p; p++) { + pa_json_encoder *info_json_2_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(info_json_2_encoder); + pa_json_encoder_add_member_string(info_json_2_encoder, "description", (*p)->description); + pa_json_encoder_add_member_int(info_json_2_encoder, "sinks", (*p)->n_sinks); + pa_json_encoder_add_member_int(info_json_2_encoder, "sources", (*p)->n_sources); + pa_json_encoder_add_member_int(info_json_2_encoder, "priority", (*p)->priority); + pa_json_encoder_add_member_bool(info_json_2_encoder, "available", (*p)->available); + pa_json_encoder_end_object(info_json_2_encoder); + + char *info_json_2_str = pa_json_encoder_to_string_free(info_json_2_encoder); + pa_json_encoder_add_member_raw_json(encoder, (*p)->name, info_json_2_str); + pa_xfree(info_json_2_str); + } + pa_json_encoder_end_object(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + +const char* pa_card_profile_info_to_json_array(pa_card_profile_info **info) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!info) { + pa_json_encoder_begin_element_array(encoder); + pa_json_encoder_end_array(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + pa_card_profile_info **p; + + pa_json_encoder_begin_element_array(encoder); + for (p = info; *p; p++) { + pa_json_encoder_add_element_string(encoder, (*p)->name); + } + pa_json_encoder_end_array(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + +const char* pa_card_port_info_to_json_object(pa_card_port_info **info) { + pa_json_encoder *encoder = pa_json_encoder_new(); + if (!info) { + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_end_object(encoder); + return pa_json_encoder_to_string_free(encoder); + } + + pa_card_port_info **p; + char *pl; + + pa_json_encoder_begin_element_object(encoder); + for (p = info; *p; p++) { + pa_card_profile_info **pr = (*p)->profiles; + + char* latency_offset_str = pa_sprintf_malloc("%"PRId64" usec", (*p)->latency_offset); + pa_json_encoder *port_info_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(port_info_encoder); + pa_json_encoder_add_member_string(port_info_encoder, "description", (*p)->description); + pa_json_encoder_add_member_string(port_info_encoder, "type", get_device_port_type((*p)->type)); + pa_json_encoder_add_member_int(port_info_encoder, "priority", (*p)->priority); + pa_json_encoder_add_member_string(port_info_encoder, "latency_offset", latency_offset_str); + pa_json_encoder_add_member_string(port_info_encoder, "availability_group", (*p)->availability_group); + pa_json_encoder_add_member_string(port_info_encoder, "availability", get_available_str((*p)->available)); + pa_json_encoder_add_member_raw_json(port_info_encoder, "properties", pl = pa_proplist_to_json_object((*p)->proplist)); + pa_json_encoder_add_member_raw_json(port_info_encoder, "profiles", pa_card_profile_info_to_json_array(pr)); + pa_json_encoder_end_object(port_info_encoder); + + char *port_info_str = pa_json_encoder_to_string_free(port_info_encoder); + pa_json_encoder_add_member_raw_json(encoder, (*p)->name, port_info_str); + pa_xfree(port_info_str); + pa_xfree(latency_offset_str); + pa_xfree(pl); + } + pa_json_encoder_end_object(encoder); + + return pa_json_encoder_to_string_free(encoder); +} + static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_last, void *userdata) { char t[32]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get card information: %s"), pa_strerror(pa_context_errno(c))); complete_action(); @@ -621,82 +1171,121 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_ } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("cards"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; pa_snprintf(t, sizeof(t), "%u", i->owner_module); if (short_list_format) { - printf("%u\t%s\t%s\n", i->index, i->name, pa_strnull(i->driver)); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%s\t%s\n", i->index, i->name, pa_strnull(i->driver)); + } return; } - printf(_("Card #%u\n" + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + i->owner_module != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "owner_module", t) : pa_json_encoder_add_member_null(encoder, "owner_module"); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_add_member_raw_json(encoder, "profiles", i->n_profiles > 0 ? pa_card_profile_info_2_to_json_object(i->profiles2) : "{}"); + i->active_profile ? pa_json_encoder_add_member_string(encoder, "active_profile", i->active_profile->name) : pa_json_encoder_add_member_null(encoder, "active_profile"); + pa_json_encoder_add_member_raw_json(encoder, "ports", pa_card_port_info_to_json_object(i->ports)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf(_("Card #%u\n" "\tName: %s\n" "\tDriver: %s\n" "\tOwner Module: %s\n" "\tProperties:\n\t\t%s\n"), - i->index, - i->name, - pa_strnull(i->driver), - i->owner_module != PA_INVALID_INDEX ? t : _("n/a"), - pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); - - pa_xfree(pl); - - if (i->n_profiles > 0) { - pa_card_profile_info2 **p; - - printf(_("\tProfiles:\n")); - for (p = i->profiles2; *p; p++) - printf(_("\t\t%s: %s (sinks: %u, sources: %u, priority: %u, available: %s)\n"), (*p)->name, - (*p)->description, (*p)->n_sinks, (*p)->n_sources, (*p)->priority, pa_yes_no_localised((*p)->available)); - } + i->index, + i->name, + pa_strnull(i->driver), + i->owner_module != PA_INVALID_INDEX ? t : _("n/a"), + pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + + if (i->n_profiles > 0) { + pa_card_profile_info2 **p; + + printf(_("\tProfiles:\n")); + for (p = i->profiles2; *p; p++) + printf(_("\t\t%s: %s (sinks: %u, sources: %u, priority: %u, available: %s)\n"), (*p)->name, + (*p)->description, (*p)->n_sinks, (*p)->n_sources, (*p)->priority, pa_yes_no_localised((*p)->available)); + } - if (i->active_profile) - printf(_("\tActive Profile: %s\n"), - i->active_profile->name); + if (i->active_profile) + printf(_("\tActive Profile: %s\n"), + i->active_profile->name); - if (i->ports) { - pa_card_port_info **p; + if (i->ports) { + pa_card_port_info **p; - printf(_("\tPorts:\n")); - for (p = i->ports; *p; p++) { - pa_card_profile_info **pr = (*p)->profiles; - printf(_("\t\t%s: %s (type: %s, priority: %u, latency offset: %" PRId64 " usec%s%s, %s)\n"), (*p)->name, - (*p)->description, get_device_port_type((*p)->type), (*p)->priority, (*p)->latency_offset, - (*p)->availability_group ? _(", availability group: ") : "", (*p)->availability_group ?: "", - get_available_str((*p)->available)); + printf(_("\tPorts:\n")); + for (p = i->ports; *p; p++) { + pa_card_profile_info **pr = (*p)->profiles; + printf(_("\t\t%s: %s (type: %s, priority: %u, latency offset: %" PRId64 " usec%s%s, %s)\n"), (*p)->name, + (*p)->description, get_device_port_type((*p)->type), (*p)->priority, (*p)->latency_offset, + (*p)->availability_group ? _(", availability group: ") : "", (*p)->availability_group ?: "", + get_available_str((*p)->available)); - if (!pa_proplist_isempty((*p)->proplist)) { - printf(_("\t\t\tProperties:\n\t\t\t\t%s\n"), pl = pa_proplist_to_string_sep((*p)->proplist, "\n\t\t\t\t")); - pa_xfree(pl); - } + if (!pa_proplist_isempty((*p)->proplist)) { + pa_xfree(pl); + printf(_("\t\t\tProperties:\n\t\t\t\t%s\n"), pl = pa_proplist_to_string_sep((*p)->proplist, "\n\t\t\t\t")); + } - if (pr) { - printf(_("\t\t\tPart of profile(s): %s"), pa_strnull((*pr)->name)); - pr++; - while (*pr) { - printf(", %s", pa_strnull((*pr)->name)); + if (pr) { + printf(_("\t\t\tPart of profile(s): %s"), pa_strnull((*pr)->name)); pr++; + while (*pr) { + printf(", %s", pa_strnull((*pr)->name)); + pr++; + } + printf("\n"); } - printf("\n"); } } } + + pa_xfree(pl); } static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) { char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], f[PA_FORMAT_INFO_SNPRINT_MAX]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get sink input information: %s"), pa_strerror(pa_context_errno(c))); quit(1); @@ -704,30 +1293,77 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("sink_inputs"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; pa_snprintf(t, sizeof(t), "%u", i->owner_module); pa_snprintf(k, sizeof(k), "%u", i->client); + char *sample_spec = pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec); if (short_list_format) { - printf("%u\t%u\t%s\t%s\t%s\n", + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_int(encoder, "sink", i->sink); + i->client != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "client", k) : pa_json_encoder_add_member_null(encoder, "client"); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%u\t%s\t%s\t%s\n", i->index, i->sink, i->client != PA_INVALID_INDEX ? k : "-", pa_strnull(i->driver), - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec)); + sample_spec); + } return; } - printf(_("Sink Input #%u\n" + char *channel_map = pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map); + char *format_info = pa_format_info_snprint(f, sizeof(f), i->format); + float balance = pa_cvolume_get_balance(&i->volume, &i->channel_map); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + i->owner_module != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "owner_module", t) : pa_json_encoder_add_member_null(encoder, "owner_module"); + i->client != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "client", k) : pa_json_encoder_add_member_null(encoder, "client"); + pa_json_encoder_add_member_int(encoder, "sink", i->sink); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_string(encoder, "channel_map", channel_map); + pa_json_encoder_add_member_string(encoder, "format", format_info); + pa_json_encoder_add_member_bool(encoder, "corked", i->corked); + pa_json_encoder_add_member_bool(encoder, "mute", i->mute); + pa_json_encoder_add_member_raw_json(encoder, "volume", pa_cvolume_to_json_object(&i->volume, &i->channel_map, true)); + pa_json_encoder_add_member_double(encoder, "balance", balance, 2); + pa_json_encoder_add_member_double(encoder, "buffer_latency_usec", (double) i->buffer_usec, 2); + pa_json_encoder_add_member_double(encoder, "sink_latency_usec", (double) i->sink_usec, 2); + pa_json_encoder_add_member_string(encoder, "resample_method", i->resample_method); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf(_("Sink Input #%u\n" "\tDriver: %s\n" "\tOwner Module: %s\n" "\tClient: %s\n" @@ -748,17 +1384,18 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info i->owner_module != PA_INVALID_INDEX ? t : _("n/a"), i->client != PA_INVALID_INDEX ? k : _("n/a"), i->sink, - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), - pa_format_info_snprint(f, sizeof(f), i->format), + sample_spec, + channel_map, + format_info, pa_yes_no_localised(i->corked), pa_yes_no_localised(i->mute), pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, true), - pa_cvolume_get_balance(&i->volume, &i->channel_map), + balance, (double) i->buffer_usec, (double) i->sink_usec, i->resample_method ? i->resample_method : _("n/a"), pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + } pa_xfree(pl); } @@ -767,6 +1404,11 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], f[PA_FORMAT_INFO_SNPRINT_MAX]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get source output information: %s"), pa_strerror(pa_context_errno(c))); quit(1); @@ -774,30 +1416,77 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("source_outputs"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; pa_snprintf(t, sizeof(t), "%u", i->owner_module); pa_snprintf(k, sizeof(k), "%u", i->client); + char *sample_spec = pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec); if (short_list_format) { - printf("%u\t%u\t%s\t%s\t%s\n", + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_int(encoder, "source", i->source); + i->client != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "client", k) : pa_json_encoder_add_member_null(encoder, "client"); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%u\t%s\t%s\t%s\n", i->index, i->source, i->client != PA_INVALID_INDEX ? k : "-", pa_strnull(i->driver), - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec)); + sample_spec); + } return; } - printf(_("Source Output #%u\n" + char *channel_map = pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map); + char *format_info = pa_format_info_snprint(f, sizeof(f), i->format); + float balance = pa_cvolume_get_balance(&i->volume, &i->channel_map); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "driver", i->driver); + i->owner_module != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "owner_module", t) : pa_json_encoder_add_member_null(encoder, "owner_module"); + i->client != PA_INVALID_INDEX ? pa_json_encoder_add_member_string(encoder, "client", k) : pa_json_encoder_add_member_null(encoder, "client"); + pa_json_encoder_add_member_int(encoder, "source", i->source); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_string(encoder, "channel_map", channel_map); + pa_json_encoder_add_member_string(encoder, "format", format_info); + pa_json_encoder_add_member_bool(encoder, "corked", i->corked); + pa_json_encoder_add_member_bool(encoder, "mute", i->mute); + pa_json_encoder_add_member_raw_json(encoder, "volume", pa_cvolume_to_json_object(&i->volume, &i->channel_map, true)); + pa_json_encoder_add_member_double(encoder, "balance", balance, 2); + pa_json_encoder_add_member_double(encoder, "buffer_latency_usec", (double) i->buffer_usec, 2); + pa_json_encoder_add_member_double(encoder, "source_latency_usec", (double) i->source_usec, 2); + pa_json_encoder_add_member_string(encoder, "resample_method", i->resample_method); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf(_("Source Output #%u\n" "\tDriver: %s\n" "\tOwner Module: %s\n" "\tClient: %s\n" @@ -818,17 +1507,18 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu i->owner_module != PA_INVALID_INDEX ? t : _("n/a"), i->client != PA_INVALID_INDEX ? k : _("n/a"), i->source, - pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), - pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), - pa_format_info_snprint(f, sizeof(f), i->format), + sample_spec, + channel_map, + format_info, pa_yes_no_localised(i->corked), pa_yes_no_localised(i->mute), pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, true), - pa_cvolume_get_balance(&i->volume, &i->channel_map), + balance, (double) i->buffer_usec, (double) i->source_usec, i->resample_method ? i->resample_method : _("n/a"), pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + } pa_xfree(pl); } @@ -837,6 +1527,11 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int char t[PA_BYTES_SNPRINT_MAX], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; char *pl; + if (format == JSON && json_encoder == NULL) { + json_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(json_encoder); + } + if (is_last < 0) { pa_log(_("Failed to get sample information: %s"), pa_strerror(pa_context_errno(c))); quit(1); @@ -844,28 +1539,69 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int } if (is_last) { + if (format == JSON) { + pa_json_encoder_end_array_handler("samples"); + } complete_action(); return; } pa_assert(i); - if (nl && !short_list_format) + if (nl && !short_list_format && format == TEXT) printf("\n"); nl = true; pa_bytes_snprint(t, sizeof(t), i->bytes); + char *sample_spec = pa_sample_spec_valid(&i->sample_spec) ? pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec) : short_list_format ? "-" : _("n/a"); + double duration = (double) i->duration/1000000.0; if (short_list_format) { - printf("%u\t%s\t%s\t%0.3f\n", + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_double(encoder, "duration", duration, 3); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf("%u\t%s\t%s\t%0.3f\n", i->index, i->name, - pa_sample_spec_valid(&i->sample_spec) ? pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec) : "-", - (double) i->duration/1000000.0); + sample_spec, + duration); + } return; } - printf(_("Sample #%u\n" + char *channel_map = pa_sample_spec_valid(&i->sample_spec) ? pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map) : _("n/a"); + float balance = pa_cvolume_get_balance(&i->volume, &i->channel_map); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", i->index); + pa_json_encoder_add_member_string(encoder, "name", i->name); + pa_json_encoder_add_member_string(encoder, "sample_specification", sample_spec); + pa_json_encoder_add_member_string(encoder, "channel_map", channel_map); + pa_json_encoder_add_member_raw_json(encoder, "volume", pa_cvolume_to_json_object(&i->volume, &i->channel_map, true)); + pa_json_encoder_add_member_double(encoder, "balance", balance, 2); + pa_json_encoder_add_member_double(encoder, "duration", duration, 3); + pa_json_encoder_add_member_string(encoder, "size", t); + pa_json_encoder_add_member_bool(encoder, "lazy", i->lazy); + pa_json_encoder_add_member_string(encoder, "filename", i->filename); + pa_json_encoder_add_member_raw_json(encoder, "properties", pl = pa_proplist_to_json_object(i->proplist)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + pa_json_encoder_add_element_raw_json(json_encoder, json_str); + pa_xfree(json_str); + } else { + printf(_("Sample #%u\n" "\tName: %s\n" "\tSample Specification: %s\n" "\tChannel Map: %s\n" @@ -878,15 +1614,16 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int "\tProperties:\n\t\t%s\n"), i->index, i->name, - pa_sample_spec_valid(&i->sample_spec) ? pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec) : _("n/a"), - pa_sample_spec_valid(&i->sample_spec) ? pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map) : _("n/a"), + sample_spec, + channel_map, pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, true), - pa_cvolume_get_balance(&i->volume, &i->channel_map), - (double) i->duration/1000000.0, + balance, + duration, t, pa_yes_no_localised(i->lazy), i->filename ? i->filename : _("n/a"), pl = pa_proplist_to_string_sep(i->proplist, "\n\t\t")); + } pa_xfree(pl); } @@ -908,7 +1645,18 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) { return; } - printf("%u\n", idx); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", idx); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + printf("%s", json_str); + pa_xfree(json_str); + } else { + printf("%u\n", idx); + } complete_action(); } @@ -921,7 +1669,18 @@ static void send_message_callback(pa_context *c, int success, char *response, vo return; } - printf("%s\n", response); + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "response", response); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + printf("%s", json_str); + pa_xfree(json_str); + } else { + printf("%s\n", response); + } complete_action(); } @@ -938,6 +1697,14 @@ static void list_handlers_callback(pa_context *c, int success, char *response, v return; } + // The response is already JSON encoded + if (format == JSON) { + printf("%s\n", response); + fflush(stdout); + complete_action(); + return; + } + o = pa_json_parse(response); if (!o) { @@ -975,9 +1742,9 @@ static void list_handlers_callback(pa_context *c, int success, char *response, v break; } - if (short_list_format) + if (short_list_format) { printf("%s\n", pa_json_object_get_string(path)); - else { + } else { if (nl) printf("\n"); nl = true; @@ -1424,10 +2191,23 @@ static const char *subscription_event_facility_to_string(pa_subscription_event_t static void context_subscribe_callback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { pa_assert(c); - printf(_("Event '%s' on %s #%u\n"), + if (format == JSON) { + pa_json_encoder *encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_int(encoder, "index", idx); + pa_json_encoder_add_member_string(encoder, "event", subscription_event_type_to_string(t)); + pa_json_encoder_add_member_string(encoder, "on", subscription_event_facility_to_string(t)); + pa_json_encoder_end_object(encoder); + + char* json_str = pa_json_encoder_to_string_free(encoder); + printf("%s", json_str); + pa_xfree(json_str); + } else { + printf(_("Event '%s' on %s #%u\n"), subscription_event_type_to_string(t), subscription_event_facility_to_string(t), idx); + } fflush(stdout); } @@ -1497,6 +2277,11 @@ static void context_state_callback(pa_context *c, void *userdata) { else pa_assert_not_reached(); } else { + if (format == JSON) { + list_encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(list_encoder); + } + o = pa_context_get_module_info_list(c, get_module_info_callback, NULL); if (o) { pa_operation_unref(o); @@ -1868,6 +2653,7 @@ static void help(const char *argv0) { printf(_("\n" " -h, --help Show this help\n" " --version Show version\n\n" + " -f, --format=FORMAT The format of the output. Either \"normal\" or \"json\"\n" " -s, --server=SERVER The name of the server to connect to\n" " -n, --client-name=NAME How to call this client on the server\n")); } @@ -1879,11 +2665,12 @@ enum { int main(int argc, char *argv[]) { pa_mainloop *m = NULL; int ret = 1, c; - char *server = NULL, *bn; + char *server = NULL, *opt_format = NULL, *bn; static const struct option long_options[] = { {"server", 1, NULL, 's'}, {"client-name", 1, NULL, 'n'}, + {"format", 1, NULL, 'f'}, {"version", 0, NULL, ARG_VERSION}, {"help", 0, NULL, 'h'}, {NULL, 0, NULL, 0} @@ -1898,7 +2685,7 @@ int main(int argc, char *argv[]) { proplist = pa_proplist_new(); - while ((c = getopt_long(argc, argv, "+s:n:h", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "+s:n:f:h", long_options, NULL)) != -1) { switch (c) { case 'h' : help(bn); @@ -1920,6 +2707,10 @@ int main(int argc, char *argv[]) { server = pa_xstrdup(optarg); break; + case 'f': + opt_format = pa_xstrdup(optarg); + break; + case 'n': { char *t; @@ -1940,6 +2731,15 @@ int main(int argc, char *argv[]) { } } + if (!opt_format || pa_streq(opt_format, "text")) { + format = TEXT; + } else if (pa_streq(opt_format, "json")) { + format = JSON; + } else { + pa_log(_("Invalid format value '%s'"), opt_format); + goto quit; + } + if (optind < argc) { if (pa_streq(argv[optind], "stat")) { action = STAT; @@ -2430,6 +3230,13 @@ int main(int argc, char *argv[]) { goto quit; } + if (format == JSON && list_encoder && !pa_json_encoder_is_empty(list_encoder)) { + pa_json_encoder_end_object(list_encoder); + char* list_json_str = pa_json_encoder_to_string_free(list_encoder); + printf("%s", list_json_str); + pa_xfree(list_json_str); + } + quit: if (sample_stream) pa_stream_unref(sample_stream); |