1 From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
2 Date: Wed, 21 May 2014 14:05:47 +0300
3 Subject: pactl: Add support for the new volume API
5 Change-Id: I2bb6625c1cd575366388ec8dc3dd4fd2097c9a4a
6 Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com>
8 man/pactl.1.xml.in | 50 +++-
9 src/utils/pactl.c | 727 +++++++++++++++++++++++++++++++++++++++++++++++++++--
10 2 files changed, 759 insertions(+), 18 deletions(-)
12 diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in
13 index 29071b3..cd54e4c 100644
14 --- a/man/pactl.1.xml.in
15 +++ b/man/pactl.1.xml.in
16 @@ -80,9 +80,12 @@ USA.
19 <p><opt>list</opt> [<arg>short</arg>] [<arg>TYPE</arg>]</p>
20 - <optdesc><p>Dump all currently loaded modules, available sinks, sources, streams, etc. <arg>TYPE</arg> must be one of:
21 - modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards. If not specified, all info is listed. If
22 - short is given, output is in a tabular format, for easy parsing by scripts.</p></optdesc>
23 + <optdesc><p>Dump all currently loaded modules, available sinks, sources,
24 + streams, etc. <arg>TYPE</arg> must be one of: modules, sinks, sources,
25 + sink-inputs, source-outputs, clients, samples, cards, volume-controls,
26 + mute-controls, devices, streams, audio-groups. If not specified, all info
27 + is listed. If short is given, output is in a tabular format, for easy
28 + parsing by scripts. </p></optdesc>
32 @@ -244,7 +247,46 @@ USA.
33 <arg>FORMATS</arg> is specified as a semi-colon (;) separated list of formats in the form
34 'encoding[, key1=value1, key2=value2, ...]' (for example, AC3 at 32000, 44100 and 48000 Hz would be specified as
35 'ac3-iec61937, format.rate = "[ 32000, 44100, 48000 ]"').
36 - </p></optdesc> </option>
41 + <p><opt>set-volume-control-volume</opt> <arg>CONTROL</arg>
42 + <arg>VOLUME</arg> <arg>[BALANCE ...]</arg>
44 + <optdesc><p>Set the overall volume of the specified volume control
45 + (identified by its name or index). <arg>VOLUME</arg> can be specified
46 + as an integer (e.g. 2000, 16384), a linear factor (e.g. 0.4, 1.100), a
47 + percentage (e.g. 10%, 100%) or a decibel value (e.g. 0dB, 20dB). If
48 + the volume specification start with a + or - the volume adjustment will
49 + be relative to the current source output volume. Optionally, you can
50 + also provide the channel balance (see also set-volume-control-balance).
55 + <p><opt>set-volume-control-balance</opt> <arg>CONTROL</arg>
56 + <arg>BALANCE ...</arg>
58 + <optdesc><p>Set the channel balance of the specified volume control
59 + (identified by its name or index). The balance is given as separate
60 + values for each channel. The balance values must be between 0.0 and
61 + 1.0. The number of values must match the volume control's channel map.
67 + <opt>set-mute-control-mute</opt> <arg>CONTROL</arg> <arg>1|0|toggle
71 + Set the mute state of the specified mute control (identified by its
72 + name or index). If the mute value is "toggle", then the mute control
73 + will be muted if it was previously unmuted, and unmuted if it was
79 <p><opt>subscribe</opt></p>
80 diff --git a/src/utils/pactl.c b/src/utils/pactl.c
81 index 958d700..f947681 100644
82 --- a/src/utils/pactl.c
83 +++ b/src/utils/pactl.c
85 #include <pulse/pulseaudio.h>
86 #include <pulse/ext-device-restore.h>
87 #include <pulse/ext-node-manager.h>
88 +#include <pulse/ext-volume-api.h>
90 #include <pulsecore/i18n.h>
91 #include <pulsecore/macro.h>
92 @@ -59,7 +60,9 @@ static char
98 + *volume_control_name = NULL,
99 + *mute_control_name = NULL;
102 sink_input_idx = PA_INVALID_INDEX,
103 @@ -101,6 +104,14 @@ static bool nl = false;
104 static uint32_t src_node_id;
105 static uint32_t dst_node_id;
106 static uint32_t conn_id;
107 +bool volume_api_connected = false;
108 +pa_ext_volume_api_bvolume bvolume;
109 +bool volume_valid = false;
110 +bool balance_valid = false;
111 +uint32_t main_output_volume_control = PA_INVALID_INDEX;
112 +uint32_t main_input_volume_control = PA_INVALID_INDEX;
113 +uint32_t main_output_mute_control = PA_INVALID_INDEX;
114 +uint32_t main_input_mute_control = PA_INVALID_INDEX;
118 @@ -132,6 +143,8 @@ static enum {
119 SET_SOURCE_OUTPUT_MUTE,
121 SET_PORT_LATENCY_OFFSET,
122 + SET_VOLUME_CONTROL_VOLUME,
123 + SET_MUTE_CONTROL_MUTE,
127 @@ -838,18 +851,18 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
131 -static void volume_relative_adjust(pa_cvolume *cv) {
132 +static void volume_relative_adjust(pa_cvolume *cv, pa_volume_t adjustment) {
133 pa_assert((volume_flags & VOL_RELATIVE) == VOL_RELATIVE);
135 /* Relative volume change is additive in case of UINT or PERCENT
136 * and multiplicative for LINEAR or DECIBEL */
137 if ((volume_flags & 0x0F) == VOL_UINT || (volume_flags & 0x0F) == VOL_PERCENT) {
138 pa_volume_t v = pa_cvolume_avg(cv);
139 - v = v + volume < PA_VOLUME_NORM ? PA_VOLUME_MUTED : v + volume - PA_VOLUME_NORM;
140 + v = v + adjustment < PA_VOLUME_NORM ? PA_VOLUME_MUTED : v + adjustment - PA_VOLUME_NORM;
141 pa_cvolume_set(cv, 1, v);
143 if ((volume_flags & 0x0F) == VOL_LINEAR || (volume_flags & 0x0F) == VOL_DECIBEL) {
144 - pa_sw_cvolume_multiply_scalar(cv, cv, volume);
145 + pa_sw_cvolume_multiply_scalar(cv, cv, adjustment);
149 @@ -893,7 +906,7 @@ static void get_sink_volume_callback(pa_context *c, const pa_sink_info *i, int i
153 - volume_relative_adjust(&cv);
154 + volume_relative_adjust(&cv, volume);
155 pa_operation_unref(pa_context_set_sink_volume_by_name(c, sink_name, &cv, simple_callback, NULL));
158 @@ -912,7 +925,7 @@ static void get_source_volume_callback(pa_context *c, const pa_source_info *i, i
162 - volume_relative_adjust(&cv);
163 + volume_relative_adjust(&cv, volume);
164 pa_operation_unref(pa_context_set_source_volume_by_name(c, source_name, &cv, simple_callback, NULL));
167 @@ -931,7 +944,7 @@ static void get_sink_input_volume_callback(pa_context *c, const pa_sink_input_in
171 - volume_relative_adjust(&cv);
172 + volume_relative_adjust(&cv, volume);
173 pa_operation_unref(pa_context_set_sink_input_volume(c, sink_input_idx, &cv, simple_callback, NULL));
176 @@ -950,7 +963,7 @@ static void get_source_output_volume_callback(pa_context *c, const pa_source_out
180 - volume_relative_adjust(&cv);
181 + volume_relative_adjust(&cv, volume);
182 pa_operation_unref(pa_context_set_source_output_volume(c, source_output_idx, &cv, simple_callback, NULL));
185 @@ -1189,6 +1202,575 @@ static void context_subscribe_callback(pa_context *c, pa_subscription_event_type
189 +static void get_volume_control_info_callback(pa_context *c, const pa_ext_volume_api_volume_control_info *info,
190 + int is_last, void *userdata) {
191 + char volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX];
192 + char balance_str[PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX];
193 + char *proplist_str;
198 + pa_log(_("Failed to get volume control information: %s"), pa_strerror(pa_context_errno(c)));
210 + if (action == INFO) {
211 + if (info->index == main_output_volume_control)
212 + printf(_("Main output volume control: %s\n"), info->name);
214 + if (info->index == main_input_volume_control)
215 + printf(_("Main input volume control: %s\n"), info->name);
220 + if (action == SET_VOLUME_CONTROL_VOLUME) {
221 + pa_ext_volume_api_bvolume bv;
223 + if (balance_valid && bvolume.channel_map.channels != info->volume.channel_map.channels) {
224 + pa_log(_("Incompatible number of channels, expected %u channels."), info->volume.channel_map.channels);
230 + if (volume_valid) {
231 + if (volume_flags & VOL_RELATIVE) {
234 + pa_cvolume_set(&cv, 1, info->volume.volume);
235 + volume_relative_adjust(&cv, bvolume.volume);
236 + bv.volume = cv.values[0];
238 + bv.volume = bvolume.volume;
242 + memcpy(bv.balance, bvolume.balance, sizeof(bv.balance));
244 + pa_operation_unref(pa_ext_volume_api_set_volume_control_volume_by_name(c, volume_control_name, &bv,
245 + volume_valid, balance_valid,
246 + simple_callback, NULL));
252 + pa_assert(action == LIST);
254 + if (nl && !short_list_format)
258 + if (short_list_format) {
259 + printf("%u\t%s\t%u\n", info->index, info->name, info->volume.volume);
263 + pa_volume_snprint_verbose(volume_str, sizeof(volume_str), info->volume.volume, info->convertible_to_dB);
264 + pa_ext_volume_api_bvolume_snprint_balance(balance_str, sizeof(balance_str), &info->volume);
265 + proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
267 + printf(_("Volume Control #%u\n"
269 + "\tDescription: %s\n"
272 + "\tProperties: %s%s\n"),
278 + *proplist_str ? "\n\t\t" : _("(none)"),
281 + pa_xfree(proplist_str);
284 +static void get_mute_control_info_callback(pa_context *c, const pa_ext_volume_api_mute_control_info *info, int is_last,
286 + char *proplist_str;
291 + pa_log(_("Failed to get mute control information: %s"), pa_strerror(pa_context_errno(c)));
303 + if (action == INFO) {
304 + if (info->index == main_output_mute_control)
305 + printf(_("Main output mute control: %s\n"), info->name);
307 + if (info->index == main_input_mute_control)
308 + printf(_("Main input mute control: %s\n"), info->name);
313 + if (action == SET_MUTE_CONTROL_MUTE) {
314 + pa_operation_unref(pa_ext_volume_api_set_mute_control_mute_by_index(c, info->index, info->mute ? false : true,
315 + simple_callback, NULL));
320 + pa_assert(action == LIST);
322 + if (nl && !short_list_format)
326 + if (short_list_format) {
327 + printf("%u\t%s\t%s\n", info->index, info->name, pa_yes_no(info->mute));
331 + proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
333 + printf(_("Mute Control #%u\n"
335 + "\tDescription: %s\n"
337 + "\tProperties: %s%s\n"),
341 + pa_yes_no(info->mute),
342 + *proplist_str ? "\n\t\t" : _("(none)"),
345 + pa_xfree(proplist_str);
348 +static void volume_api_get_server_info_callback(pa_context *c, const pa_ext_volume_api_server_info *info, void *userdata) {
352 + pa_log(_("Failed to get server information: %s"), pa_strerror(pa_context_errno(c)));
357 + main_output_volume_control = info->main_output_volume_control;
358 + main_input_volume_control = info->main_input_volume_control;
359 + main_output_mute_control = info->main_output_mute_control;
360 + main_input_mute_control = info->main_input_mute_control;
362 + if (main_output_volume_control == PA_INVALID_INDEX)
363 + printf(_("Main output volume control: (unset)\n"));
365 + if (main_input_volume_control == PA_INVALID_INDEX)
366 + printf(_("Main input volume control: (unset)\n"));
368 + if (main_output_mute_control == PA_INVALID_INDEX)
369 + printf(_("Main output mute control: (unset)\n"));
371 + if (main_input_mute_control == PA_INVALID_INDEX)
372 + printf(_("Main input mute control: (unset)\n"));
374 + if (main_output_volume_control != PA_INVALID_INDEX || main_input_volume_control != PA_INVALID_INDEX) {
375 + pa_operation_unref(pa_ext_volume_api_get_volume_control_info_list(c, get_volume_control_info_callback, NULL));
379 + if (main_output_mute_control != PA_INVALID_INDEX || main_input_mute_control != PA_INVALID_INDEX) {
380 + pa_operation_unref(pa_ext_volume_api_get_mute_control_info_list(c, get_mute_control_info_callback, NULL));
387 +static void get_device_info_callback(pa_context *c, const pa_ext_volume_api_device_info *info, int is_last,
389 + char *device_types_str = NULL;
390 + char *volume_control_str;
391 + char *mute_control_str;
392 + char *proplist_str;
397 + pa_log(_("Failed to get device information: %s"), pa_strerror(pa_context_errno(c)));
409 + if (nl && !short_list_format)
413 + if (info->n_device_types > 0)
414 + device_types_str = pa_join(info->device_types, info->n_device_types, ", ");
416 + device_types_str = pa_xstrdup(_("(none)"));
418 + if (info->volume_control != PA_INVALID_INDEX)
419 + volume_control_str = pa_sprintf_malloc("%u", info->volume_control);
421 + volume_control_str = pa_xstrdup(_("(unset)"));
423 + if (info->mute_control != PA_INVALID_INDEX)
424 + mute_control_str = pa_sprintf_malloc("%u", info->mute_control);
426 + mute_control_str = pa_xstrdup(_("(unset)"));
428 + if (short_list_format) {
429 + printf("%u\t%s\t%s\t%s\t%s\t%s\n", info->index, info->name, pa_direction_to_string(info->direction), device_types_str,
430 + volume_control_str, mute_control_str);
431 + pa_xfree(mute_control_str);
432 + pa_xfree(volume_control_str);
433 + pa_xfree(device_types_str);
437 + proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
439 + printf(_("Device #%u\n"
441 + "\tDescription: %s\n"
442 + "\tDirection: %s\n"
443 + "\tDevice Types: %s\n"
444 + "\tVolume Control: %s\n"
445 + "\tMute Control: %s\n"
446 + "\tProperties: %s%s\n"),
450 + pa_direction_to_string(info->direction),
452 + volume_control_str,
454 + *proplist_str ? "\n\t\t" : _("(none)"),
457 + pa_xfree(proplist_str);
458 + pa_xfree(mute_control_str);
459 + pa_xfree(volume_control_str);
460 + pa_xfree(device_types_str);
463 +static void get_stream_info_callback(pa_context *c, const pa_ext_volume_api_stream_info *info, int is_last,
465 + char *volume_control_str;
466 + char *mute_control_str;
467 + char *proplist_str;
472 + pa_log(_("Failed to get stream information: %s"), pa_strerror(pa_context_errno(c)));
484 + if (nl && !short_list_format)
488 + if (info->volume_control != PA_INVALID_INDEX)
489 + volume_control_str = pa_sprintf_malloc("%u", info->volume_control);
491 + volume_control_str = pa_xstrdup(_("(unset)"));
493 + if (info->mute_control != PA_INVALID_INDEX)
494 + mute_control_str = pa_sprintf_malloc("%u", info->mute_control);
496 + mute_control_str = pa_xstrdup(_("(unset)"));
498 + if (short_list_format) {
499 + printf("%u\t%s\t%s\t%s\t%s\n", info->index, info->name, pa_direction_to_string(info->direction), volume_control_str,
501 + pa_xfree(mute_control_str);
502 + pa_xfree(volume_control_str);
506 + proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
508 + printf(_("Stream #%u\n"
510 + "\tDescription: %s\n"
511 + "\tDirection: %s\n"
512 + "\tVolume Control: %s\n"
513 + "\tMute Control: %s\n"
514 + "\tProperties: %s%s\n"),
518 + pa_direction_to_string(info->direction),
519 + volume_control_str,
521 + *proplist_str ? "\n\t\t" : _("(none)"),
524 + pa_xfree(proplist_str);
525 + pa_xfree(mute_control_str);
526 + pa_xfree(volume_control_str);
529 +static void get_audio_group_info_callback(pa_context *c, const pa_ext_volume_api_audio_group_info *info, int is_last,
531 + char *volume_control_str;
532 + char *mute_control_str;
533 + char *proplist_str;
538 + pa_log(_("Failed to get audio group information: %s"), pa_strerror(pa_context_errno(c)));
550 + if (nl && !short_list_format)
554 + if (info->volume_control != PA_INVALID_INDEX)
555 + volume_control_str = pa_sprintf_malloc("%u", info->volume_control);
557 + volume_control_str = pa_xstrdup(_("(unset)"));
559 + if (info->mute_control != PA_INVALID_INDEX)
560 + mute_control_str = pa_sprintf_malloc("%u", info->mute_control);
562 + mute_control_str = pa_xstrdup(_("(unset)"));
564 + if (short_list_format) {
565 + printf("%u\t%s\t%s\t%s\n", info->index, info->name, volume_control_str, mute_control_str);
566 + pa_xfree(mute_control_str);
567 + pa_xfree(volume_control_str);
571 + proplist_str = pa_proplist_to_string_sep(info->proplist, "\n\t\t");
573 + printf(_("Audio Group #%u\n"
575 + "\tDescription: %s\n"
576 + "\tVolume Control: %s\n"
577 + "\tMute Control: %s\n"
578 + "\tProperties: %s%s\n"),
582 + volume_control_str,
584 + *proplist_str ? "\n\t\t" : _("(none)"),
587 + pa_xfree(proplist_str);
588 + pa_xfree(mute_control_str);
589 + pa_xfree(volume_control_str);
592 +static const char *volume_api_subscription_event_facility_to_string(pa_ext_volume_api_subscription_event_type_t type) {
594 + switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
595 + case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_SERVER:
596 + return _("server (volume API)");
598 + case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_VOLUME_CONTROL:
599 + return _("volume-control");
601 + case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_MUTE_CONTROL:
602 + return _("mute-control");
604 + case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_DEVICE:
605 + return _("device");
607 + case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_STREAM:
608 + return _("stream");
610 + case PA_EXT_VOLUME_API_SUBSCRIPTION_EVENT_AUDIO_GROUP:
611 + return _("audio-group");
614 + return _("unknown");
617 +static void volume_api_subscribe_cb(pa_context *c, pa_ext_volume_api_subscription_event_type_t event_type, uint32_t idx,
621 + printf(_("Event '%s' on %s #%u\n"),
622 + subscription_event_type_to_string(event_type),
623 + volume_api_subscription_event_facility_to_string(event_type),
628 +static void volume_api_state_cb(pa_context *c, void *userdata) {
629 + pa_ext_volume_api_state_t state;
633 + state = pa_ext_volume_api_get_state(c);
636 + case PA_EXT_VOLUME_API_STATE_READY: {
637 + pa_operation *o = NULL;
639 + volume_api_connected = true;
643 + o = pa_ext_volume_api_get_server_info(c, volume_api_get_server_info_callback, NULL);
649 + o = pa_ext_volume_api_get_volume_control_info_list(c, get_volume_control_info_callback, NULL);
650 + pa_operation_unref(o);
651 + o = pa_ext_volume_api_get_mute_control_info_list(c, get_mute_control_info_callback, NULL);
652 + pa_operation_unref(o);
653 + o = pa_ext_volume_api_get_device_info_list(c, get_device_info_callback, NULL);
654 + pa_operation_unref(o);
655 + o = pa_ext_volume_api_get_stream_info_list(c, get_stream_info_callback, NULL);
656 + pa_operation_unref(o);
657 + o = pa_ext_volume_api_get_audio_group_info_list(c, get_audio_group_info_callback, NULL);
658 + pa_operation_unref(o);
661 + } else if (pa_streq(list_type, "volume-controls")) {
662 + o = pa_ext_volume_api_get_volume_control_info_list(c, get_volume_control_info_callback, NULL);
664 + } else if (pa_streq(list_type, "mute-controls")) {
665 + o = pa_ext_volume_api_get_mute_control_info_list(c, get_mute_control_info_callback, NULL);
667 + } else if (pa_streq(list_type, "devices")) {
668 + o = pa_ext_volume_api_get_device_info_list(c, get_device_info_callback, NULL);
670 + } else if (pa_streq(list_type, "streams")) {
671 + o = pa_ext_volume_api_get_stream_info_list(c, get_stream_info_callback, NULL);
673 + } else if (pa_streq(list_type, "audio-groups")) {
674 + o = pa_ext_volume_api_get_audio_group_info_list(c, get_audio_group_info_callback, NULL);
679 + case SET_VOLUME_CONTROL_VOLUME:
680 + if (!balance_valid && !(volume_flags & VOL_RELATIVE)) {
681 + pa_assert(volume_valid);
682 + o = pa_ext_volume_api_set_volume_control_volume_by_name(c, volume_control_name, &bvolume, true,
683 + false, simple_callback, NULL);
685 + o = pa_ext_volume_api_get_volume_control_info_by_name(c, volume_control_name,
686 + get_volume_control_info_callback, NULL);
691 + case SET_MUTE_CONTROL_MUTE:
692 + if (mute == TOGGLE_MUTE)
693 + o = pa_ext_volume_api_get_mute_control_info_by_name(c, mute_control_name,
694 + get_mute_control_info_callback, NULL);
696 + o = pa_ext_volume_api_set_mute_control_mute_by_name(c, mute_control_name, mute, simple_callback,
703 + pa_ext_volume_api_set_subscribe_callback(c, volume_api_subscribe_cb, NULL);
704 + o = pa_ext_volume_api_subscribe(c, PA_EXT_VOLUME_API_SUBSCRIPTION_MASK_ALL, NULL, NULL);
712 + pa_operation_unref(o);
718 + case PA_EXT_VOLUME_API_STATE_FAILED:
719 + pa_log("Volume API context failed: %s", pa_strerror(pa_context_errno(c)));
721 + /* If the main context failed too, let's not do anything, because
722 + * calling complete_action() would reset the context error code to
723 + * PA_ERR_BADSTATE, meaning that the original error code would be
725 + if (pa_context_get_state(c) == PA_CONTEXT_FAILED)
728 + if (action == INFO || (action == LIST && !list_type) || action == SUBSCRIBE) {
729 + /* In these cases we shouldn't exit with an error if the volume
730 + * API happens to be or become unavailable. If we haven't yet
731 + * connected to the volume API, then we need to complete the
732 + * "connect to volume API" action. */
734 + if (!volume_api_connected)
746 +static void connect_to_volume_api(void) {
749 + pa_assert(context);
751 + pa_ext_volume_api_set_state_callback(context, volume_api_state_cb, NULL);
753 + r = pa_ext_volume_api_connect(context);
758 static void context_state_callback(pa_context *c, void *userdata) {
759 pa_operation *o = NULL;
761 @@ -1215,6 +1797,7 @@ static void context_state_callback(pa_context *c, void *userdata) {
764 o = pa_context_get_server_info(c, get_server_info_callback, NULL);
765 + connect_to_volume_api();
769 @@ -1259,7 +1842,14 @@ static void context_state_callback(pa_context *c, void *userdata) {
770 o = pa_context_get_card_info_list(c, get_card_info_callback, NULL);
771 else if (pa_streq(list_type, "nodes"))
772 o = pa_ext_node_manager_read_nodes(c, node_list_callback, NULL);
774 + else if (pa_streq(list_type, "volume-controls")
775 + || pa_streq(list_type, "mute-controls")
776 + || pa_streq(list_type, "devices")
777 + || pa_streq(list_type, "streams")
778 + || pa_streq(list_type, "audio-groups")) {
779 + connect_to_volume_api();
782 pa_assert_not_reached();
784 o = pa_context_get_module_info_list(c, get_module_info_callback, NULL);
785 @@ -1309,6 +1899,7 @@ static void context_state_callback(pa_context *c, void *userdata) {
789 + connect_to_volume_api();
793 @@ -1442,6 +2033,11 @@ static void context_state_callback(pa_context *c, void *userdata) {
794 o = pa_context_set_port_latency_offset(c, card_name, port_name, latency_offset, simple_callback, NULL);
797 + case SET_VOLUME_CONTROL_VOLUME:
798 + case SET_MUTE_CONTROL_MUTE:
799 + connect_to_volume_api();
803 pa_context_set_subscribe_callback(c, context_subscribe_callback, NULL);
805 @@ -1457,6 +2053,14 @@ static void context_state_callback(pa_context *c, void *userdata) {
806 PA_SUBSCRIPTION_MASK_CARD,
811 + pa_operation_unref(o);
816 + connect_to_volume_api();
819 pa_operation_unref(pa_ext_node_manager_connect_nodes(c,
820 @@ -1599,6 +2203,9 @@ static void help(const char *argv0) {
821 printf("%s %s %s %s\n", argv0, _("[options]"), "set-(sink-input|source-output)-mute", _("#N 1|0|toggle"));
822 printf("%s %s %s %s\n", argv0, _("[options]"), "set-sink-formats", _("#N FORMATS"));
823 printf("%s %s %s %s\n", argv0, _("[options]"), "set-port-latency-offset", _("CARD-NAME|CARD-#N PORT OFFSET"));
824 + printf("%s %s %s %s\n", argv0, _("[options]"), "set-volume-control-volume", _("NAME|#N VOLUME [BALANCE ...]"));
825 + printf("%s %s %s %s\n", argv0, _("[options]"), "set-volume-control-balance", _("NAME|#N BALANCE ..."));
826 + printf("%s %s %s %s\n", argv0, _("[options]"), "set-mute-control-mute", _("NAME|#N 1|0|toggle"));
827 printf("%s %s %s\n", argv0, _("[options]"), "subscribe");
828 printf(_("\nThe special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and @DEFAULT_MONITOR@\n"
829 "can be used to specify the default sink, source and monitor.\n"));
830 @@ -1613,6 +2220,40 @@ static void help(const char *argv0) {
831 " -n, --client-name=NAME How to call this client on the server\n"));
834 +static int parse_balance(char *argv[], unsigned first_arg, unsigned n_channels, pa_ext_volume_api_bvolume *bv) {
835 + pa_ext_volume_api_bvolume bv_local;
838 + pa_assert(n_channels > 0);
841 + if (n_channels > PA_CHANNELS_MAX) {
842 + pa_log("Too many channels, the maximum is %u.", PA_CHANNELS_MAX);
848 + for (i = 0; i < n_channels; i++) {
849 + const char *balance_str;
852 + balance_str = argv[first_arg + i];
854 + if (pa_atod(balance_str, &balance) < 0 || !pa_ext_volume_api_balance_valid(balance)) {
855 + pa_log(_("Invalid balance value: %s"), balance_str);
859 + bv_local.balance[i] = balance;
862 + bv_local.channel_map.channels = n_channels;
871 @@ -1698,15 +2339,19 @@ int main(int argc, char *argv[]) {
874 for (int i = optind+1; i < argc; i++) {
875 - if (pa_streq(argv[i], "modules") || pa_streq(argv[i], "clients") ||
876 - pa_streq(argv[i], "sinks") || pa_streq(argv[i], "sink-inputs") ||
877 - pa_streq(argv[i], "sources") || pa_streq(argv[i], "source-outputs") ||
878 - pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards") || pa_streq(argv[i], "nodes")) {
879 + if (pa_streq(argv[i], "modules") || pa_streq(argv[i], "clients") ||
880 + pa_streq(argv[i], "sinks") || pa_streq(argv[i], "sink-inputs") ||
881 + pa_streq(argv[i], "sources") || pa_streq(argv[i], "source-outputs") ||
882 + pa_streq(argv[i], "samples") || pa_streq(argv[i], "cards") || pa_streq(argv[i], "nodes") ||
883 + pa_streq(argv[i], "volume-controls") || pa_streq(argv[i], "mute-controls") ||
884 + pa_streq(argv[i], "devices") || pa_streq(argv[i], "streams") ||
885 + pa_streq(argv[i], "audio-groups")) {
886 list_type = pa_xstrdup(argv[i]);
887 } else if (pa_streq(argv[i], "short")) {
888 short_list_format = true;
890 - pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, clients, samples, cards");
891 + pa_log(_("Specify nothing, or one of: %s"), "modules, sinks, sources, sink-inputs, source-outputs, "
892 + "clients, samples, cards, volume-controls, mute-controls, devices, streams, audio-groups");
896 @@ -2092,6 +2737,58 @@ int main(int argc, char *argv[]) {
898 conn_id = (uint32_t) atoi(argv[optind+1]);
900 + } else if (pa_streq(argv[optind], "set-volume-control-volume")) {
901 + action = SET_VOLUME_CONTROL_VOLUME;
903 + if (argc < optind + 3) {
904 + pa_log(_("You have to specify a volume control name/index and a volume, and optionally balance parameters."));
908 + volume_control_name = pa_xstrdup(argv[optind + 1]);
910 + if (parse_volume(argv[optind + 2], &bvolume.volume, &volume_flags) < 0)
913 + volume_valid = true;
915 + if (argc > optind + 3) {
916 + if (parse_balance(argv, optind + 3, argc - (optind + 3), &bvolume) < 0)
919 + balance_valid = true;
922 + } else if (pa_streq(argv[optind], "set-volume-control-balance")) {
923 + action = SET_VOLUME_CONTROL_VOLUME;
925 + if (argc < optind + 3) {
926 + pa_log(_("You have to specify a volume control name/index and balance parameters."));
930 + volume_control_name = pa_xstrdup(argv[optind + 1]);
932 + if (parse_balance(argv, optind + 2, argc - (optind + 2), &bvolume) < 0)
935 + balance_valid = true;
937 + } else if (pa_streq(argv[optind], "set-mute-control-mute")) {
938 + action = SET_MUTE_CONTROL_MUTE;
940 + if (argc != optind + 3) {
941 + pa_log(_("You have to specify a mute control name/index and a mute value."));
945 + mute_control_name = pa_xstrdup(argv[optind + 1]);
947 + if ((mute = parse_mute(argv[optind + 2])) == INVALID_MUTE) {
948 + pa_log(_("Invalid mute specification"));
952 } else if (pa_streq(argv[optind], "help")) {
955 @@ -2154,6 +2851,8 @@ quit:
956 pa_xfree(profile_name);
959 + pa_xfree(mute_control_name);
960 + pa_xfree(volume_control_name);