diff options
Diffstat (limited to 'meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch')
-rw-r--r-- | meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch | 6118 |
1 files changed, 6118 insertions, 0 deletions
diff --git a/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch new file mode 100644 index 000000000..b904ac315 --- /dev/null +++ b/meta-agl-profile-core/recipes-multimedia/pulseaudio/pulseaudio/0030-volume-api-Add-libvolume-api.patch @@ -0,0 +1,6118 @@ +From 0223ac3e092249c792bedcab2bf5dbaec443f960 Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com> +Date: Wed, 21 May 2014 11:51:27 +0300 +Subject: [PATCH 1/1] volume-api: Add libvolume-api.so + +This library implements the "core" of the new volume system, which +will be used by several modules. + +Change-Id: Ib25ada1392e83237a3908e6064ee0ad6dff7afaf +Signed-off-by: Jaska Uimonen <jaska.uimonen@intel.com> +--- + Makefile.am | 3 + + src/Makefile.am | 19 +- + src/map-file | 15 + + src/modules/volume-api/audio-group.c | 448 +++++++++++++ + src/modules/volume-api/audio-group.h | 85 +++ + src/modules/volume-api/binding.c | 386 +++++++++++ + src/modules/volume-api/binding.h | 128 ++++ + src/modules/volume-api/bvolume.h | 43 ++ + src/modules/volume-api/device-creator.c | 1108 +++++++++++++++++++++++++++++++ + src/modules/volume-api/device-creator.h | 32 + + src/modules/volume-api/device.c | 293 ++++++++ + src/modules/volume-api/device.h | 76 +++ + src/modules/volume-api/mute-control.c | 306 +++++++++ + src/modules/volume-api/mute-control.h | 102 +++ + src/modules/volume-api/sstream.c | 366 ++++++++++ + src/modules/volume-api/sstream.h | 108 +++ + src/modules/volume-api/stream-creator.c | 691 +++++++++++++++++++ + src/modules/volume-api/stream-creator.h | 32 + + src/modules/volume-api/volume-api.c | 647 ++++++++++++++++++ + src/modules/volume-api/volume-api.h | 163 +++++ + src/modules/volume-api/volume-control.c | 363 ++++++++++ + src/modules/volume-api/volume-control.h | 112 ++++ + src/pulse/ext-volume-api.c | 275 ++++++++ + src/pulse/ext-volume-api.h | 68 ++ + 24 files changed, 5868 insertions(+), 1 deletion(-) + create mode 100644 src/modules/volume-api/audio-group.c + create mode 100644 src/modules/volume-api/audio-group.h + create mode 100644 src/modules/volume-api/binding.c + create mode 100644 src/modules/volume-api/binding.h + create mode 100644 src/modules/volume-api/bvolume.h + create mode 100644 src/modules/volume-api/device-creator.c + create mode 100644 src/modules/volume-api/device-creator.h + create mode 100644 src/modules/volume-api/device.c + create mode 100644 src/modules/volume-api/device.h + create mode 100644 src/modules/volume-api/mute-control.c + create mode 100644 src/modules/volume-api/mute-control.h + create mode 100644 src/modules/volume-api/sstream.c + create mode 100644 src/modules/volume-api/sstream.h + create mode 100644 src/modules/volume-api/stream-creator.c + create mode 100644 src/modules/volume-api/stream-creator.h + create mode 100644 src/modules/volume-api/volume-api.c + create mode 100644 src/modules/volume-api/volume-api.h + create mode 100644 src/modules/volume-api/volume-control.c + create mode 100644 src/modules/volume-api/volume-control.h + create mode 100644 src/pulse/ext-volume-api.c + create mode 100644 src/pulse/ext-volume-api.h + +diff --git a/Makefile.am b/Makefile.am +index 9431d4a..cf4a648 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -57,6 +57,9 @@ + moduledevinternal_DATA = src/pulse/internal.h src/pulse/client-conf.h src/pulse/fork-detect.h + moduledevinternaldir = $(includedir)/pulsemodule/pulse + ++moduledevvolumeapi_DATA = $(top_srcdir)/src/modules/volume-api/*.h ++moduledevvolumeapidir = $(includedir)/pulsemodule/modules/volume-api ++ + if HAVE_GLIB20 + pkgconfig_DATA += \ + libpulse-mainloop-glib.pc +diff --git a/src/Makefile.am b/src/Makefile.am +index 22b9b81..bc41dca 100644 +--- a/src/Makefile.am 2016-04-13 15:51:34.439019531 +0200 ++++ b/src/Makefile.am 2016-04-13 15:53:03.721019382 +0200 +@@ -792,6 +792,7 @@ + pulse/ext-device-manager.h \ + pulse/ext-device-restore.h \ + pulse/ext-stream-restore.h \ ++ pulse/ext-volume-api.h \ + pulse/format.h \ + pulse/gccmacro.h \ + pulse/introspect.h \ +@@ -838,6 +839,7 @@ + pulse/ext-device-manager.c pulse/ext-device-manager.h \ + pulse/ext-device-restore.c pulse/ext-device-restore.h \ + pulse/ext-stream-restore.c pulse/ext-stream-restore.h \ ++ pulse/ext-volume-api.c pulse/ext-volume-api.h \ + pulse/format.c pulse/format.h \ + pulse/gccmacro.h \ + pulse/internal.h \ +@@ -1018,7 +1020,8 @@ modlibexec_LTLIBRARIES = \ + libprotocol-cli.la \ + libprotocol-simple.la \ + libprotocol-http.la \ +- libprotocol-native.la ++ libprotocol-native.la \ ++ libvolume-api.la + + if HAVE_WEBRTC + modlibexec_LTLIBRARIES += libwebrtc-util.la +@@ -1065,6 +1068,20 @@ libprotocol_native_la_CFLAGS += $(DBUS_CFLAGS) + libprotocol_native_la_LIBADD += $(DBUS_LIBS) + endif + ++libvolume_api_la_SOURCES = \ ++ modules/volume-api/audio-group.c modules/volume-api/audio-group.h \ ++ modules/volume-api/binding.c modules/volume-api/binding.h \ ++ modules/volume-api/bvolume.h \ ++ modules/volume-api/device.c modules/volume-api/device.h \ ++ modules/volume-api/device-creator.c modules/volume-api/device-creator.h \ ++ modules/volume-api/mute-control.c modules/volume-api/mute-control.h \ ++ modules/volume-api/sstream.c modules/volume-api/sstream.h \ ++ modules/volume-api/stream-creator.c modules/volume-api/stream-creator.h \ ++ modules/volume-api/volume-api.c modules/volume-api/volume-api.h \ ++ modules/volume-api/volume-control.c modules/volume-api/volume-control.h ++libvolume_api_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version ++libvolume_api_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la ++ + if HAVE_ESOUND + libprotocol_esound_la_SOURCES = pulsecore/protocol-esound.c pulsecore/protocol-esound.h pulsecore/esound.h + libprotocol_esound_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version +diff --git a/src/map-file b/src/map-file +index fbf3f22..1f64a2f 100644 +--- a/src/map-file ++++ b/src/map-file +@@ -172,6 +172,21 @@ + pa_ext_stream_restore_subscribe; + pa_ext_stream_restore_test; + pa_ext_stream_restore_write; ++pa_ext_volume_api_balance_valid; ++pa_ext_volume_api_bvolume_copy_balance; ++pa_ext_volume_api_bvolume_get_left_right_balance; ++pa_ext_volume_api_bvolume_get_rear_front_balance; ++pa_ext_volume_api_bvolume_equal; ++pa_ext_volume_api_bvolume_from_cvolume; ++pa_ext_volume_api_bvolume_init_invalid; ++pa_ext_volume_api_bvolume_init_mono; ++pa_ext_volume_api_bvolume_remap; ++pa_ext_volume_api_bvolume_reset_balance; ++pa_ext_volume_api_bvolume_set_left_right_balance; ++pa_ext_volume_api_bvolume_set_rear_front_balance; ++pa_ext_volume_api_bvolume_snprint_balance; ++pa_ext_volume_api_bvolume_to_cvolume; ++pa_ext_volume_api_bvolume_valid; + pa_format_info_copy; + pa_format_info_free; + pa_format_info_from_string; +diff --git a/src/modules/volume-api/audio-group.c b/src/modules/volume-api/audio-group.c +new file mode 100644 +index 0000000..76bfa69 +--- /dev/null ++++ b/src/modules/volume-api/audio-group.c +@@ -0,0 +1,448 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "audio-group.h" ++ ++#include <modules/volume-api/sstream.h> ++ ++#include <pulsecore/core-util.h> ++ ++int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group) { ++ pa_audio_group *group_local; ++ int r; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ pa_assert(group); ++ ++ group_local = pa_xnew0(pa_audio_group, 1); ++ group_local->volume_api = api; ++ group_local->index = pa_volume_api_allocate_audio_group_index(api); ++ ++ r = pa_volume_api_register_name(api, name, true, &group_local->name); ++ if (r < 0) ++ goto fail; ++ ++ group_local->description = pa_xstrdup(description); ++ group_local->proplist = pa_proplist_new(); ++ group_local->volume_streams = pa_hashmap_new(NULL, NULL); ++ group_local->mute_streams = pa_hashmap_new(NULL, NULL); ++ ++ *group = group_local; ++ ++ return 0; ++ ++fail: ++ pa_audio_group_free(group_local); ++ ++ return r; ++} ++ ++void pa_audio_group_put(pa_audio_group *group) { ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(group); ++ ++ pa_volume_api_add_audio_group(group->volume_api, group); ++ ++ group->linked = true; ++ ++ pa_log_debug("Created audio group #%u.", group->index); ++ pa_log_debug(" Name: %s", group->name); ++ pa_log_debug(" Description: %s", group->description); ++ pa_log_debug(" Volume control: %s", group->volume_control ? group->volume_control->name : "(unset)"); ++ pa_log_debug(" Mute control: %s", group->mute_control ? group->mute_control->name : "(unset)"); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(group->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(group->proplist, prop_key))); ++ ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], group); ++} ++ ++void pa_audio_group_unlink(pa_audio_group *group) { ++ pas_stream *stream; ++ ++ pa_assert(group); ++ ++ if (group->unlinked) { ++ pa_log_debug("Unlinking audio group %s (already unlinked, this is a no-op).", group->name); ++ return; ++ } ++ ++ group->unlinked = true; ++ ++ pa_log_debug("Unlinking audio group %s.", group->name); ++ ++ if (group->linked) ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], group); ++ ++ pa_volume_api_remove_audio_group(group->volume_api, group); ++ ++ while ((stream = pa_hashmap_first(group->mute_streams))) ++ pas_stream_set_audio_group_for_mute(stream, NULL); ++ ++ while ((stream = pa_hashmap_first(group->volume_streams))) ++ pas_stream_set_audio_group_for_volume(stream, NULL); ++ ++ if (group->mute_control_binding) { ++ pa_binding_free(group->mute_control_binding); ++ group->mute_control_binding = NULL; ++ } ++ ++ if (group->volume_control_binding) { ++ pa_binding_free(group->volume_control_binding); ++ group->volume_control_binding = NULL; ++ } ++ ++ pa_audio_group_set_have_own_mute_control(group, false); ++ pa_audio_group_set_have_own_volume_control(group, false); ++ ++ if (group->mute_control) { ++ pa_mute_control_remove_audio_group(group->mute_control, group); ++ group->mute_control = NULL; ++ } ++ ++ if (group->volume_control) { ++ pa_volume_control_remove_audio_group(group->volume_control, group); ++ group->volume_control = NULL; ++ } ++} ++ ++void pa_audio_group_free(pa_audio_group *group) { ++ pa_assert(group); ++ ++ if (!group->unlinked) ++ pa_audio_group_unlink(group); ++ ++ if (group->mute_streams) ++ pa_hashmap_free(group->mute_streams); ++ ++ if (group->volume_streams) ++ pa_hashmap_free(group->volume_streams); ++ ++ if (group->proplist) ++ pa_proplist_free(group->proplist); ++ ++ pa_xfree(group->description); ++ ++ if (group->name) ++ pa_volume_api_unregister_name(group->volume_api, group->name); ++ ++ pa_xfree(group); ++} ++ ++const char *pa_audio_group_get_name(pa_audio_group *group) { ++ pa_assert(group); ++ ++ return group->name; ++} ++ ++static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, ++ bool set_balance) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->volume_streams, state) { ++ if (stream->own_volume_control) ++ pa_volume_control_set_volume(stream->own_volume_control, volume, set_volume, set_balance); ++ } ++ ++ return 0; ++} ++ ++static void volume_control_set_initial_volume_cb(pa_volume_control *control) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->volume_streams, state) { ++ if (stream->own_volume_control) ++ pa_volume_control_set_volume(stream->own_volume_control, &control->volume, true, true); ++ } ++} ++ ++void pa_audio_group_set_have_own_volume_control(pa_audio_group *group, bool have) { ++ pa_assert(group); ++ ++ if (have == group->have_own_volume_control) ++ return; ++ ++ if (have) { ++ pa_bvolume initial_volume; ++ ++ if (group->volume_api->core->flat_volumes) ++ /* Usually the initial volume should get overridden by some module ++ * that manages audio group volume levels, but if there's no such ++ * module, let's try to avoid too high volume in flat volume ++ * mode. */ ++ pa_bvolume_init_mono(&initial_volume, 0.3 * PA_VOLUME_NORM); ++ else ++ pa_bvolume_init_mono(&initial_volume, PA_VOLUME_NORM); ++ ++ pa_assert(!group->own_volume_control); ++ group->own_volume_control = pa_volume_control_new(group->volume_api, "audio-group-volume-control", ++ group->description, false, false); ++ pa_volume_control_set_owner_audio_group(group->own_volume_control, group); ++ group->own_volume_control->set_volume = volume_control_set_volume_cb; ++ group->own_volume_control->userdata = group; ++ pa_volume_control_put(group->own_volume_control, &initial_volume, volume_control_set_initial_volume_cb); ++ } else { ++ pa_volume_control_free(group->own_volume_control); ++ group->own_volume_control = NULL; ++ } ++ ++ group->have_own_volume_control = have; ++} ++ ++static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->mute_streams, state) { ++ if (stream->own_mute_control) ++ pa_mute_control_set_mute(stream->own_mute_control, mute); ++ } ++ ++ return 0; ++} ++ ++static void mute_control_set_initial_mute_cb(pa_mute_control *control) { ++ pa_audio_group *group; ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(control); ++ ++ group = control->userdata; ++ ++ PA_HASHMAP_FOREACH(stream, group->mute_streams, state) { ++ if (stream->own_mute_control) ++ pa_mute_control_set_mute(stream->own_mute_control, control->mute); ++ } ++} ++ ++void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have) { ++ pa_assert(group); ++ ++ if (have == group->have_own_mute_control) ++ return; ++ ++ group->have_own_mute_control = have; ++ ++ if (have) { ++ pa_assert(!group->own_mute_control); ++ group->own_mute_control = pa_mute_control_new(group->volume_api, "audio-group-mute-control", group->description); ++ pa_mute_control_set_owner_audio_group(group->own_mute_control, group); ++ group->own_mute_control->set_mute = mute_control_set_mute_cb; ++ group->own_mute_control->userdata = group; ++ pa_mute_control_put(group->own_mute_control, false, true, mute_control_set_initial_mute_cb); ++ } else { ++ pa_mute_control_free(group->own_mute_control); ++ group->own_mute_control = NULL; ++ } ++} ++ ++static void set_volume_control_internal(pa_audio_group *group, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(group); ++ ++ old_control = group->volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_volume_control_remove_audio_group(old_control, group); ++ ++ group->volume_control = control; ++ ++ if (control) ++ pa_volume_control_add_audio_group(control, group); ++ ++ if (!group->linked || group->unlinked) ++ return; ++ ++ pa_log_debug("The volume control of audio group %s changed from %s to %s.", group->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED], group); ++} ++ ++void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control) { ++ pa_assert(group); ++ ++ if (group->volume_control_binding) { ++ pa_binding_free(group->volume_control_binding); ++ group->volume_control_binding = NULL; ++ } ++ ++ set_volume_control_internal(group, control); ++} ++ ++static void set_mute_control_internal(pa_audio_group *group, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(group); ++ ++ old_control = group->mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_mute_control_remove_audio_group(old_control, group); ++ ++ group->mute_control = control; ++ ++ if (control) ++ pa_mute_control_add_audio_group(control, group); ++ ++ if (!group->linked || group->unlinked) ++ return; ++ ++ pa_log_debug("The mute control of audio group %s changed from %s to %s.", group->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED], group); ++} ++ ++void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control) { ++ pa_assert(group); ++ ++ if (group->mute_control_binding) { ++ pa_binding_free(group->mute_control_binding); ++ group->mute_control_binding = NULL; ++ } ++ ++ set_mute_control_internal(group, control); ++} ++ ++void pa_audio_group_bind_volume_control(pa_audio_group *group, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = group, ++ .set_value = (pa_binding_set_value_cb_t) set_volume_control_internal, ++ }; ++ ++ pa_assert(group); ++ pa_assert(target_info); ++ ++ if (group->volume_control_binding) ++ pa_binding_free(group->volume_control_binding); ++ ++ group->volume_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info); ++} ++ ++void pa_audio_group_bind_mute_control(pa_audio_group *group, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = group, ++ .set_value = (pa_binding_set_value_cb_t) set_mute_control_internal, ++ }; ++ ++ pa_assert(group); ++ pa_assert(target_info); ++ ++ if (group->mute_control_binding) ++ pa_binding_free(group->mute_control_binding); ++ ++ group->mute_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info); ++} ++ ++void pa_audio_group_add_volume_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(group->volume_streams, stream, stream) >= 0); ++ ++ if (stream->own_volume_control && group->own_volume_control) ++ pa_volume_control_set_volume(stream->own_volume_control, &group->own_volume_control->volume, true, true); ++ ++ pa_log_debug("Stream %s added to audio group %s (volume).", stream->name, group->name); ++} ++ ++void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(group->volume_streams, stream)); ++ ++ pa_log_debug("Stream %s removed from audio group %s (volume).", stream->name, group->name); ++} ++ ++void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(group->mute_streams, stream, stream) >= 0); ++ ++ if (stream->own_mute_control && group->own_mute_control) ++ pa_mute_control_set_mute(stream->own_mute_control, group->own_mute_control->mute); ++ ++ pa_log_debug("Stream %s added to audio group %s (mute).", stream->name, group->name); ++} ++ ++void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream) { ++ pa_assert(group); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(group->mute_streams, stream)); ++ ++ pa_log_debug("Stream %s removed from audio group %s (mute).", stream->name, group->name); ++} ++ ++pa_binding_target_type *pa_audio_group_create_binding_target_type(pa_volume_api *api) { ++ pa_binding_target_type *type; ++ ++ pa_assert(api); ++ ++ type = pa_binding_target_type_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, api->audio_groups, ++ &api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], ++ &api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], ++ (pa_binding_target_type_get_name_cb_t) pa_audio_group_get_name); ++ pa_binding_target_type_add_field(type, PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_audio_group, volume_control)); ++ pa_binding_target_type_add_field(type, PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL, ++ PA_BINDING_CALCULATE_FIELD_OFFSET(pa_audio_group, mute_control)); ++ ++ return type; ++} +diff --git a/src/modules/volume-api/audio-group.h b/src/modules/volume-api/audio-group.h +new file mode 100644 +index 0000000..41591ba +--- /dev/null ++++ b/src/modules/volume-api/audio-group.h +@@ -0,0 +1,85 @@ ++#ifndef fooaudiogrouphfoo ++#define fooaudiogrouphfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/binding.h> ++#include <modules/volume-api/mute-control.h> ++#include <modules/volume-api/volume-control.h> ++ ++#include <pulse/proplist.h> ++ ++#include <inttypes.h> ++ ++typedef struct pa_audio_group pa_audio_group; ++ ++#define PA_AUDIO_GROUP_BINDING_TARGET_TYPE "AudioGroup" ++#define PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL "volume_control" ++#define PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL "mute_control" ++ ++struct pa_audio_group { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_proplist *proplist; ++ pa_volume_control *volume_control; ++ pa_mute_control *mute_control; ++ bool have_own_volume_control; ++ bool have_own_mute_control; ++ pa_volume_control *own_volume_control; ++ pa_mute_control *own_mute_control; ++ ++ pa_binding *volume_control_binding; ++ pa_binding *mute_control_binding; ++ pa_hashmap *volume_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ pa_hashmap *mute_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ ++ bool linked; ++ bool unlinked; ++}; ++ ++int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group); ++void pa_audio_group_put(pa_audio_group *group); ++void pa_audio_group_unlink(pa_audio_group *group); ++void pa_audio_group_free(pa_audio_group *group); ++ ++const char *pa_audio_group_get_name(pa_audio_group *group); ++ ++/* Called by policy modules. */ ++void pa_audio_group_set_have_own_volume_control(pa_audio_group *group, bool have); ++void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have); ++void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control); ++void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control); ++void pa_audio_group_bind_volume_control(pa_audio_group *group, pa_binding_target_info *target_info); ++void pa_audio_group_bind_mute_control(pa_audio_group *group, pa_binding_target_info *target_info); ++ ++/* Called from sstream.c only. */ ++void pa_audio_group_add_volume_stream(pa_audio_group *group, pas_stream *stream); ++void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stream); ++void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream); ++void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream); ++ ++/* Called from volume-api.c only. */ ++pa_binding_target_type *pa_audio_group_create_binding_target_type(pa_volume_api *api); ++ ++#endif +diff --git a/src/modules/volume-api/binding.c b/src/modules/volume-api/binding.c +new file mode 100644 +index 0000000..6e73119 +--- /dev/null ++++ b/src/modules/volume-api/binding.c +@@ -0,0 +1,386 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "binding.h" ++ ++#include <pulse/def.h> ++#include <pulse/xmalloc.h> ++ ++#include <pulsecore/core-util.h> ++#include <pulsecore/macro.h> ++ ++struct field_entry { ++ char *name; ++ size_t offset; ++}; ++ ++static void set_target_type(pa_binding *binding, pa_binding_target_type *type); ++static void set_target_object(pa_binding *binding, void *object); ++ ++pa_binding_owner_info *pa_binding_owner_info_new(pa_binding_set_value_cb_t set_value, void *userdata) { ++ pa_binding_owner_info *info; ++ ++ pa_assert(set_value); ++ ++ info = pa_xnew0(pa_binding_owner_info, 1); ++ info->set_value = set_value; ++ info->userdata = userdata; ++ ++ return info; ++} ++ ++pa_binding_owner_info *pa_binding_owner_info_copy(const pa_binding_owner_info *info) { ++ pa_assert(info); ++ ++ return pa_binding_owner_info_new(info->set_value, info->userdata); ++} ++ ++void pa_binding_owner_info_free(pa_binding_owner_info *info) { ++ pa_assert(info); ++ ++ pa_xfree(info); ++} ++ ++pa_binding_target_info *pa_binding_target_info_new(const char *type, const char *name, const char *field) { ++ pa_binding_target_info *info; ++ ++ pa_assert(type); ++ pa_assert(name); ++ pa_assert(field); ++ ++ info = pa_xnew0(pa_binding_target_info, 1); ++ info->type = pa_xstrdup(type); ++ info->name = pa_xstrdup(name); ++ info->field = pa_xstrdup(field); ++ ++ return info; ++} ++ ++int pa_binding_target_info_new_from_string(const char *str, const char *field, pa_binding_target_info **info) { ++ const char *colon; ++ char *type = NULL; ++ char *name = NULL; ++ ++ pa_assert(str); ++ pa_assert(field); ++ pa_assert(info); ++ ++ if (!pa_startswith(str, "bind:")) ++ goto fail; ++ ++ colon = strchr(str + 5, ':'); ++ if (!colon) ++ goto fail; ++ ++ type = pa_xstrndup(str + 5, colon - (str + 5)); ++ ++ if (!*type) ++ goto fail; ++ ++ name = pa_xstrdup(colon + 1); ++ ++ if (!*name) ++ goto fail; ++ ++ *info = pa_binding_target_info_new(type, name, field); ++ pa_xfree(name); ++ pa_xfree(type); ++ ++ return 0; ++ ++fail: ++ pa_log("Invalid binding target: %s", str); ++ pa_xfree(name); ++ pa_xfree(type); ++ ++ return -PA_ERR_INVALID; ++} ++ ++pa_binding_target_info *pa_binding_target_info_copy(const pa_binding_target_info *info) { ++ pa_assert(info); ++ ++ return pa_binding_target_info_new(info->type, info->name, info->field); ++} ++ ++void pa_binding_target_info_free(pa_binding_target_info *info) { ++ pa_assert(info); ++ ++ pa_xfree(info->field); ++ pa_xfree(info->name); ++ pa_xfree(info->type); ++ pa_xfree(info); ++} ++ ++static void field_entry_free(struct field_entry *entry) { ++ pa_assert(entry); ++ ++ pa_xfree(entry->name); ++ pa_xfree(entry); ++} ++ ++pa_binding_target_type *pa_binding_target_type_new(const char *name, pa_hashmap *objects, pa_hook *put_hook, ++ pa_hook *unlink_hook, pa_binding_target_type_get_name_cb_t get_name) { ++ pa_binding_target_type *type; ++ ++ pa_assert(name); ++ pa_assert(objects); ++ pa_assert(put_hook); ++ pa_assert(unlink_hook); ++ pa_assert(get_name); ++ ++ type = pa_xnew0(pa_binding_target_type, 1); ++ type->name = pa_xstrdup(name); ++ type->objects = objects; ++ type->put_hook = put_hook; ++ type->unlink_hook = unlink_hook; ++ type->get_name = get_name; ++ type->fields = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) field_entry_free); ++ ++ return type; ++} ++ ++void pa_binding_target_type_free(pa_binding_target_type *type) { ++ pa_assert(type); ++ ++ if (type->fields) ++ pa_hashmap_free(type->fields); ++ ++ pa_xfree(type->name); ++ pa_xfree(type); ++} ++ ++void pa_binding_target_type_add_field(pa_binding_target_type *type, const char *name, size_t offset) { ++ struct field_entry *entry; ++ ++ pa_assert(type); ++ pa_assert(name); ++ ++ entry = pa_xnew0(struct field_entry, 1); ++ entry->name = pa_xstrdup(name); ++ entry->offset = offset; ++ ++ pa_assert_se(pa_hashmap_put(type->fields, entry->name, entry) >= 0); ++} ++ ++int pa_binding_target_type_get_field_offset(pa_binding_target_type *type, const char *field, size_t *offset) { ++ struct field_entry *entry; ++ ++ pa_assert(type); ++ pa_assert(field); ++ pa_assert(offset); ++ ++ entry = pa_hashmap_get(type->fields, field); ++ if (!entry) ++ return -PA_ERR_NOENTITY; ++ ++ *offset = entry->offset; ++ ++ return 0; ++} ++ ++static pa_hook_result_t target_type_added_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding_target_type *type = call_data; ++ pa_binding *binding = userdata; ++ ++ pa_assert(type); ++ pa_assert(binding); ++ ++ if (!pa_streq(type->name, binding->target_info->type)) ++ return PA_HOOK_OK; ++ ++ set_target_type(binding, type); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t target_type_removed_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding_target_type *type = call_data; ++ pa_binding *binding = userdata; ++ ++ pa_assert(type); ++ pa_assert(binding); ++ ++ if (type != binding->target_type) ++ return PA_HOOK_OK; ++ ++ set_target_type(binding, NULL); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t target_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding *binding = userdata; ++ ++ pa_assert(call_data); ++ pa_assert(binding); ++ ++ if (!pa_streq(binding->target_type->get_name(call_data), binding->target_info->name)) ++ return PA_HOOK_OK; ++ ++ set_target_object(binding, call_data); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t target_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_binding *binding = userdata; ++ ++ pa_assert(call_data); ++ pa_assert(binding); ++ ++ if (call_data != binding->target_object) ++ return PA_HOOK_OK; ++ ++ set_target_object(binding, NULL); ++ ++ return PA_HOOK_OK; ++} ++ ++static void set_target_object(pa_binding *binding, void *object) { ++ pa_assert(binding); ++ ++ binding->target_object = object; ++ ++ if (object) { ++ if (binding->target_put_slot) { ++ pa_hook_slot_free(binding->target_put_slot); ++ binding->target_put_slot = NULL; ++ } ++ ++ if (!binding->target_unlink_slot) ++ binding->target_unlink_slot = pa_hook_connect(binding->target_type->unlink_hook, PA_HOOK_NORMAL, target_unlink_cb, ++ binding); ++ ++ if (binding->target_field_offset_valid) ++ binding->owner_info->set_value(binding->owner_info->userdata, ++ *((void **) (((uint8_t *) object) + binding->target_field_offset))); ++ else ++ binding->owner_info->set_value(binding->owner_info->userdata, NULL); ++ } else { ++ if (binding->target_unlink_slot) { ++ pa_hook_slot_free(binding->target_unlink_slot); ++ binding->target_unlink_slot = NULL; ++ } ++ ++ if (binding->target_type) { ++ if (!binding->target_put_slot) ++ binding->target_put_slot = pa_hook_connect(binding->target_type->put_hook, PA_HOOK_NORMAL, target_put_cb, binding); ++ } else { ++ if (binding->target_put_slot) { ++ pa_hook_slot_free(binding->target_put_slot); ++ binding->target_put_slot = NULL; ++ } ++ } ++ ++ binding->owner_info->set_value(binding->owner_info->userdata, NULL); ++ } ++} ++ ++static void set_target_type(pa_binding *binding, pa_binding_target_type *type) { ++ pa_assert(binding); ++ ++ binding->target_type = type; ++ ++ if (type) { ++ int r; ++ ++ if (binding->target_type_added_slot) { ++ pa_hook_slot_free(binding->target_type_added_slot); ++ binding->target_type_added_slot = NULL; ++ } ++ ++ if (!binding->target_type_removed_slot) ++ binding->target_type_removed_slot = ++ pa_hook_connect(&binding->volume_api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], ++ PA_HOOK_NORMAL, target_type_removed_cb, binding); ++ ++ r = pa_binding_target_type_get_field_offset(type, binding->target_info->field, &binding->target_field_offset); ++ if (r >= 0) ++ binding->target_field_offset_valid = true; ++ else { ++ pa_log_warn("Reference to non-existing field \"%s\" in binding target type \"%s\".", binding->target_info->field, ++ type->name); ++ binding->target_field_offset_valid = false; ++ } ++ ++ set_target_object(binding, pa_hashmap_get(type->objects, binding->target_info->name)); ++ } else { ++ if (binding->target_type_removed_slot) { ++ pa_hook_slot_free(binding->target_type_removed_slot); ++ binding->target_type_removed_slot = NULL; ++ } ++ ++ if (!binding->target_type_added_slot) ++ binding->target_type_added_slot = ++ pa_hook_connect(&binding->volume_api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], ++ PA_HOOK_NORMAL, target_type_added_cb, binding); ++ ++ binding->target_field_offset_valid = false; ++ ++ set_target_object(binding, NULL); ++ } ++} ++ ++pa_binding *pa_binding_new(pa_volume_api *api, const pa_binding_owner_info *owner_info, ++ const pa_binding_target_info *target_info) { ++ pa_binding *binding; ++ ++ pa_assert(api); ++ pa_assert(owner_info); ++ pa_assert(target_info); ++ ++ binding = pa_xnew0(pa_binding, 1); ++ binding->volume_api = api; ++ binding->owner_info = pa_binding_owner_info_copy(owner_info); ++ binding->target_info = pa_binding_target_info_copy(target_info); ++ ++ set_target_type(binding, pa_hashmap_get(api->binding_target_types, target_info->type)); ++ ++ return binding; ++} ++ ++void pa_binding_free(pa_binding *binding) { ++ pa_assert(binding); ++ ++ if (binding->target_unlink_slot) ++ pa_hook_slot_free(binding->target_unlink_slot); ++ ++ if (binding->target_put_slot) ++ pa_hook_slot_free(binding->target_put_slot); ++ ++ if (binding->target_type_removed_slot) ++ pa_hook_slot_free(binding->target_type_removed_slot); ++ ++ if (binding->target_type_added_slot) ++ pa_hook_slot_free(binding->target_type_added_slot); ++ ++ if (binding->target_info) ++ pa_binding_target_info_free(binding->target_info); ++ ++ if (binding->owner_info) ++ pa_binding_owner_info_free(binding->owner_info); ++ ++ pa_xfree(binding); ++} +diff --git a/src/modules/volume-api/binding.h b/src/modules/volume-api/binding.h +new file mode 100644 +index 0000000..ba4dea8 +--- /dev/null ++++ b/src/modules/volume-api/binding.h +@@ -0,0 +1,128 @@ ++#ifndef foobindinghfoo ++#define foobindinghfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/volume-api.h> ++ ++typedef struct pa_binding pa_binding; ++typedef struct pa_binding_owner_info pa_binding_owner_info; ++typedef struct pa_binding_target_info pa_binding_target_info; ++typedef struct pa_binding_target_type pa_binding_target_type; ++ ++typedef void (*pa_binding_set_value_cb_t)(void *userdata, void *value); ++ ++struct pa_binding_owner_info { ++ /* This is the object that has the variable that the binding is created ++ * for. */ ++ void *userdata; ++ ++ /* Called when the owner object's value needs to be updated. The userdata ++ * parameter of the callback is the same as the userdata field in this ++ * struct, and the value parameter is the new value for whatever variable ++ * the binding was created for. */ ++ pa_binding_set_value_cb_t set_value; ++}; ++ ++pa_binding_owner_info *pa_binding_owner_info_new(pa_binding_set_value_cb_t set_value, void *userdata); ++pa_binding_owner_info *pa_binding_owner_info_copy(const pa_binding_owner_info *info); ++void pa_binding_owner_info_free(pa_binding_owner_info *info); ++ ++struct pa_binding_target_info { ++ /* The target type name as registered with ++ * pa_binding_target_type_register(). */ ++ char *type; ++ ++ /* The target object name as returned by the get_name callback of ++ * pa_binding_target_type. */ ++ char *name; ++ ++ /* The target field of the target object. */ ++ char *field; ++}; ++ ++pa_binding_target_info *pa_binding_target_info_new(const char *type, const char *name, const char *field); ++ ++/* The string format is "bind:TYPE:NAME". */ ++int pa_binding_target_info_new_from_string(const char *str, const char *field, pa_binding_target_info **info); ++ ++pa_binding_target_info *pa_binding_target_info_copy(const pa_binding_target_info *info); ++void pa_binding_target_info_free(pa_binding_target_info *info); ++ ++typedef const char *(*pa_binding_target_type_get_name_cb_t)(void *object); ++ ++struct pa_binding_target_type { ++ /* Identifier for this target type. */ ++ char *name; ++ ++ /* name -> object. Points directly to some "master" object hashmap, so the ++ * hashmap is not owned by pa_binding_target_type. */ ++ pa_hashmap *objects; ++ ++ /* The hook that notifies of new objects if this target type. The call data ++ * of the hook must be a pointer to the new object (this should be true for ++ * all PUT hooks, so don't worry too much). */ ++ pa_hook *put_hook; ++ ++ /* The hook that notifies of unlinked objects of this target type. The call ++ * data of the hook must be a pointer to the removed object (this should be ++ * true for all UNLINK hooks, so don't worry too much). */ ++ pa_hook *unlink_hook; ++ ++ /* Function for getting the name of an object of this target type. */ ++ pa_binding_target_type_get_name_cb_t get_name; ++ ++ pa_hashmap *fields; ++}; ++ ++pa_binding_target_type *pa_binding_target_type_new(const char *name, pa_hashmap *objects, pa_hook *put_hook, ++ pa_hook *unlink_hook, pa_binding_target_type_get_name_cb_t get_name); ++void pa_binding_target_type_free(pa_binding_target_type *type); ++ ++/* Useful when calling pa_binding_target_type_add_field(). */ ++#define PA_BINDING_CALCULATE_FIELD_OFFSET(type, field) ((size_t) &(((type *) 0)->field)) ++ ++/* Called during the type initialization (right after ++ * pa_binding_target_type_new()). */ ++void pa_binding_target_type_add_field(pa_binding_target_type *type, const char *name, size_t offset); ++ ++int pa_binding_target_type_get_field_offset(pa_binding_target_type *type, const char *field, size_t *offset); ++ ++struct pa_binding { ++ pa_volume_api *volume_api; ++ pa_binding_owner_info *owner_info; ++ pa_binding_target_info *target_info; ++ pa_binding_target_type *target_type; ++ void *target_object; ++ size_t target_field_offset; ++ bool target_field_offset_valid; ++ pa_hook_slot *target_type_added_slot; ++ pa_hook_slot *target_type_removed_slot; ++ pa_hook_slot *target_put_slot; ++ pa_hook_slot *target_unlink_slot; ++}; ++ ++pa_binding *pa_binding_new(pa_volume_api *api, const pa_binding_owner_info *owner_info, ++ const pa_binding_target_info *target_info); ++void pa_binding_free(pa_binding *binding); ++ ++#endif +diff --git a/src/modules/volume-api/bvolume.h b/src/modules/volume-api/bvolume.h +new file mode 100644 +index 0000000..0317fb6 +--- /dev/null ++++ b/src/modules/volume-api/bvolume.h +@@ -0,0 +1,43 @@ ++#ifndef foobvolumehfoo ++#define foobvolumehfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <pulse/ext-volume-api.h> ++ ++typedef pa_ext_volume_api_bvolume pa_bvolume; ++ ++#define pa_balance_valid pa_ext_volume_api_balance_valid ++#define pa_bvolume_valid pa_ext_volume_api_bvolume_valid ++#define pa_bvolume_init_invalid pa_ext_volume_api_bvolume_init_invalid ++#define pa_bvolume_init_mono pa_ext_volume_api_bvolume_init_mono ++#define pa_bvolume_equal pa_ext_volume_api_bvolume_equal ++#define pa_bvolume_from_cvolume pa_ext_volume_api_bvolume_from_cvolume ++#define pa_bvolume_to_cvolume pa_ext_volume_api_bvolume_to_cvolume ++#define pa_bvolume_copy_balance pa_ext_volume_api_bvolume_copy_balance ++#define pa_bvolume_reset_balance pa_ext_volume_api_bvolume_reset_balance ++#define pa_bvolume_remap pa_ext_volume_api_bvolume_remap ++ ++#define PA_BVOLUME_SNPRINT_BALANCE_MAX PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX ++#define pa_bvolume_snprint_balance pa_ext_volume_api_bvolume_snprint_balance ++ ++#endif +diff --git a/src/modules/volume-api/device-creator.c b/src/modules/volume-api/device-creator.c +new file mode 100644 +index 0000000..1d912ba +--- /dev/null ++++ b/src/modules/volume-api/device-creator.c +@@ -0,0 +1,1108 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "device-creator.h" ++ ++#include <modules/volume-api/device.h> ++#include <modules/volume-api/mute-control.h> ++#include <modules/volume-api/volume-control.h> ++ ++#include <pulsecore/core-util.h> ++#include <pulsecore/i18n.h> ++ ++struct pa_device_creator { ++ pa_volume_api *volume_api; ++ pa_hashmap *devices; /* pa_device_port/pa_sink/pa_source -> struct device */ ++ pa_hook_slot *card_put_slot; ++ pa_hook_slot *card_unlink_slot; ++ pa_hook_slot *sink_put_slot; ++ pa_hook_slot *sink_unlink_slot; ++ pa_hook_slot *source_put_slot; ++ pa_hook_slot *source_unlink_slot; ++}; ++ ++enum device_type { ++ DEVICE_TYPE_PORT, ++ DEVICE_TYPE_PORT_MONITOR, ++ DEVICE_TYPE_SINK, ++ DEVICE_TYPE_SOURCE, ++}; ++ ++struct device_volume_control { ++ struct device *device; ++ pa_volume_control *volume_control; ++ ++ bool unlinked; ++ ++ pa_hook_slot *volume_changed_slot; ++}; ++ ++struct device_mute_control { ++ struct device *device; ++ pa_mute_control *mute_control; ++ ++ bool unlinked; ++ ++ pa_hook_slot *mute_changed_slot; ++}; ++ ++struct device { ++ pa_device_creator *creator; ++ enum device_type type; ++ pa_device_port *port; ++ pa_sink *sink; ++ pa_source *source; ++ pa_device *device; ++ struct device_volume_control *volume_control; ++ struct device_mute_control *mute_control; ++ ++ bool unlinked; ++ ++ pa_hook_slot *proplist_changed_slot; ++ pa_hook_slot *port_active_changed_slot; ++ struct device *monitor; ++}; ++ ++static const char *device_type_from_icon_name(const char *icon_name) { ++ if (!icon_name) ++ return NULL; ++ ++ if (pa_streq(icon_name, "audio-input-microphone")) ++ return "microphone"; ++ ++ if (pa_streq(icon_name, "audio-speakers")) ++ return "speakers"; ++ ++ if (pa_streq(icon_name, "audio-headphones")) ++ return "headphones"; ++ ++ return NULL; ++} ++ ++static const char *device_type_from_port_name(pa_device_port *port) { ++ pa_assert(port); ++ ++ if (strstr(port->name, "analog")) { ++ if (port->direction == PA_DIRECTION_INPUT) ++ return "analog-input"; ++ else ++ return "analog-output"; ++ } ++ ++ if (strstr(port->name, "hdmi")) { ++ if (port->direction == PA_DIRECTION_INPUT) ++ return "hdmi-input"; ++ else ++ return "hdmi-output"; ++ } ++ ++ if (strstr(port->name, "iec958")) { ++ if (port->direction == PA_DIRECTION_INPUT) ++ return "spdif-input"; ++ else ++ return "spdif-output"; ++ } ++ ++ return NULL; ++} ++ ++static const char *device_type_from_port(pa_device_port *port) { ++ const char *device_type; ++ ++ pa_assert(port); ++ ++ device_type = device_type_from_icon_name(pa_proplist_gets(port->proplist, PA_PROP_DEVICE_ICON_NAME)); ++ if (device_type) ++ return device_type; ++ ++ device_type = device_type_from_port_name(port); ++ if (device_type) ++ return device_type; ++ ++ return NULL; ++} ++ ++static const char *get_sink_description(pa_sink *sink) { ++ const char *description; ++ ++ pa_assert(sink); ++ ++ description = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION); ++ if (description) ++ return description; ++ ++ return sink->name; ++} ++ ++static const char *get_source_description(pa_source *source) { ++ const char *description; ++ ++ pa_assert(source); ++ ++ description = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION); ++ if (description) ++ return description; ++ ++ return source->name; ++} ++ ++static int volume_control_set_volume_cb(pa_volume_control *c, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ struct device_volume_control *control; ++ struct device *device; ++ pa_bvolume bvolume; ++ pa_cvolume cvolume; ++ ++ pa_assert(c); ++ pa_assert(volume); ++ ++ control = c->userdata; ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_bvolume_from_cvolume(&bvolume, &device->sink->reference_volume, &device->sink->channel_map); ++ else ++ pa_bvolume_from_cvolume(&bvolume, &device->source->reference_volume, &device->source->channel_map); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_bvolume_from_cvolume(&bvolume, &device->sink->reference_volume, &device->sink->channel_map); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_bvolume_from_cvolume(&bvolume, &device->source->reference_volume, &device->source->channel_map); ++ break; ++ } ++ ++ if (set_volume) ++ bvolume.volume = volume->volume; ++ ++ if (set_balance) ++ pa_bvolume_copy_balance(&bvolume, volume); ++ ++ pa_bvolume_to_cvolume(&bvolume, &cvolume); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ else ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static struct device_volume_control *device_volume_control_new(struct device *device) { ++ struct device_volume_control *control; ++ const char *name = NULL; ++ bool convertible_to_dB = false; ++ bool channel_map_is_writable; ++ ++ pa_assert(device); ++ ++ control = pa_xnew0(struct device_volume_control, 1); ++ control->device = device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ name = "port-volume-control"; ++ ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME; ++ else ++ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; ++ ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ name = "port-monitor-volume-control"; ++ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ name = "sink-volume-control"; ++ convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ name = "source-volume-control"; ++ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME; ++ break; ++ } ++ ++ channel_map_is_writable = false; ++ control->volume_control = pa_volume_control_new(device->creator->volume_api, name, device->device->description, ++ convertible_to_dB, channel_map_is_writable); ++ control->volume_control->set_volume = volume_control_set_volume_cb; ++ control->volume_control->userdata = control; ++ ++ return control; ++} ++ ++static pa_hook_result_t sink_or_source_volume_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device_volume_control *control = userdata; ++ struct device *device; ++ pa_sink *sink = NULL; ++ pa_source *source = NULL; ++ pa_bvolume bvolume; ++ ++ pa_assert(control); ++ pa_assert(call_data); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ sink = call_data; ++ else ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ sink = call_data; ++ break; ++ } ++ ++ if ((sink && sink != device->sink) || (source && source != device->source)) ++ return PA_HOOK_OK; ++ ++ if (sink) ++ pa_bvolume_from_cvolume(&bvolume, &sink->reference_volume, &sink->channel_map); ++ else ++ pa_bvolume_from_cvolume(&bvolume, &source->reference_volume, &source->channel_map); ++ ++ pa_volume_control_volume_changed(control->volume_control, &bvolume, true, true); ++ ++ return PA_HOOK_OK; ++} ++ ++static void volume_control_set_initial_volume_cb(pa_volume_control *c) { ++ struct device_volume_control *control; ++ struct device *device; ++ pa_cvolume cvolume; ++ ++ pa_assert(c); ++ ++ control = c->userdata; ++ device = control->device; ++ pa_bvolume_to_cvolume(&control->volume_control->volume, &cvolume); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ else ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_volume(device->source, &cvolume, true, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_volume(device->sink, &cvolume, true, true); ++ break; ++ } ++} ++ ++static void device_volume_control_put(struct device_volume_control *control) { ++ struct device *device; ++ pa_bvolume volume; ++ ++ pa_assert(control); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) { ++ control->volume_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->sink->reference_volume, &device->sink->channel_map); ++ } else { ++ control->volume_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->source->reference_volume, &device->source->channel_map); ++ } ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ control->volume_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->source->reference_volume, &device->source->channel_map); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ control->volume_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_volume_changed_cb, control); ++ pa_bvolume_from_cvolume(&volume, &device->sink->reference_volume, &device->sink->channel_map); ++ break; ++ } ++ ++ pa_volume_control_put(control->volume_control, &volume, volume_control_set_initial_volume_cb); ++} ++ ++static void device_volume_control_unlink(struct device_volume_control *control) { ++ pa_assert(control); ++ ++ if (control->unlinked) ++ return; ++ ++ control->unlinked = true; ++ ++ if (control->volume_control) ++ pa_volume_control_unlink(control->volume_control); ++ ++ if (control->volume_changed_slot) { ++ pa_hook_slot_free(control->volume_changed_slot); ++ control->volume_changed_slot = NULL; ++ } ++} ++ ++static void device_volume_control_free(struct device_volume_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ device_volume_control_unlink(control); ++ ++ if (control->volume_control) ++ pa_volume_control_free(control->volume_control); ++ ++ pa_xfree(control); ++} ++ ++static int mute_control_set_mute_cb(pa_mute_control *c, bool mute) { ++ struct device_mute_control *control; ++ struct device *device; ++ ++ pa_assert(c); ++ ++ control = c->userdata; ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_mute(device->sink, mute, true); ++ else ++ pa_source_set_mute(device->source, mute, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_mute(device->source, mute, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_mute(device->sink, mute, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static struct device_mute_control *device_mute_control_new(struct device *device) { ++ struct device_mute_control *control; ++ const char *name = NULL; ++ ++ pa_assert(device); ++ ++ control = pa_xnew0(struct device_mute_control, 1); ++ control->device = device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ name = "port-mute-control"; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ name = "port-monitor-mute-control"; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ name = "sink-mute-control"; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ name = "source-mute-control"; ++ break; ++ } ++ ++ control->mute_control = pa_mute_control_new(device->creator->volume_api, name, device->device->description); ++ control->mute_control->set_mute = mute_control_set_mute_cb; ++ control->mute_control->userdata = control; ++ ++ return control; ++} ++ ++static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device_mute_control *control = userdata; ++ struct device *device; ++ pa_sink *sink = NULL; ++ pa_source *source = NULL; ++ bool mute; ++ ++ pa_assert(control); ++ pa_assert(call_data); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ sink = call_data; ++ else ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ source = call_data; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ sink = call_data; ++ break; ++ } ++ ++ if ((sink && sink != device->sink) || (source && source != device->source)) ++ return PA_HOOK_OK; ++ ++ if (sink) ++ mute = sink->muted; ++ else ++ mute = source->muted; ++ ++ pa_mute_control_mute_changed(control->mute_control, mute); ++ ++ return PA_HOOK_OK; ++} ++ ++static void mute_control_set_initial_mute_cb(pa_mute_control *c) { ++ struct device_volume_control *control; ++ struct device *device; ++ ++ pa_assert(c); ++ ++ control = c->userdata; ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ pa_sink_set_mute(device->sink, c->mute, true); ++ else ++ pa_source_set_mute(device->source, c->mute, true); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ pa_source_set_mute(device->source, c->mute, true); ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ pa_sink_set_mute(device->sink, c->mute, true); ++ break; ++ } ++} ++ ++static void device_mute_control_put(struct device_mute_control *control) { ++ struct device *device; ++ bool mute = false; ++ ++ pa_assert(control); ++ ++ device = control->device; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) { ++ control->mute_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->sink->muted; ++ } else { ++ control->mute_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->source->muted; ++ } ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ case DEVICE_TYPE_SOURCE: ++ control->mute_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->source->muted; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ control->mute_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_mute_changed_cb, control); ++ mute = device->sink->muted; ++ break; ++ } ++ ++ pa_mute_control_put(control->mute_control, mute, true, mute_control_set_initial_mute_cb); ++} ++ ++static void device_mute_control_unlink(struct device_mute_control *control) { ++ pa_assert(control); ++ ++ if (control->unlinked) ++ return; ++ ++ control->unlinked = true; ++ ++ if (control->mute_control) ++ pa_mute_control_unlink(control->mute_control); ++ ++ if (control->mute_changed_slot) { ++ pa_hook_slot_free(control->mute_changed_slot); ++ control->mute_changed_slot = NULL; ++ } ++} ++ ++static void device_mute_control_free(struct device_mute_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ device_mute_control_unlink(control); ++ ++ if (control->mute_control) ++ pa_mute_control_free(control->mute_control); ++ ++ pa_xfree(control); ++} ++ ++static void device_set_sink_and_source_from_port(struct device *device) { ++ pa_sink *sink; ++ pa_source *source; ++ uint32_t idx; ++ ++ pa_assert(device); ++ ++ device->sink = NULL; ++ device->source = NULL; ++ ++ if (!device->port->active) ++ return; ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) { ++ PA_IDXSET_FOREACH(sink, device->port->card->sinks, idx) { ++ if (sink->active_port == device->port) { ++ device->sink = sink; ++ break; ++ } ++ } ++ ++ pa_assert(device->sink); ++ } else { ++ PA_IDXSET_FOREACH(source, device->port->card->sources, idx) { ++ if (source->active_port == device->port) { ++ device->source = source; ++ break; ++ } ++ } ++ ++ pa_assert(device->source); ++ } ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: { ++ PA_IDXSET_FOREACH(sink, device->port->card->sinks, idx) { ++ if (sink->active_port == device->port) { ++ device->sink = sink; ++ device->source = sink->monitor_source; ++ break; ++ } ++ } ++ ++ pa_assert(device->sink); ++ break; ++ } ++ ++ case DEVICE_TYPE_SINK: ++ case DEVICE_TYPE_SOURCE: ++ pa_assert_not_reached(); ++ } ++} ++ ++static pa_hook_result_t sink_or_source_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device *device = userdata; ++ pa_sink *sink = NULL; ++ pa_source *source = NULL; ++ const char *description = NULL; ++ ++ pa_assert(device); ++ pa_assert(call_data); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ case DEVICE_TYPE_PORT_MONITOR: ++ pa_assert_not_reached(); ++ ++ case DEVICE_TYPE_SINK: ++ sink = call_data; ++ ++ if (sink != device->sink) ++ return PA_HOOK_OK; ++ ++ description = get_sink_description(sink); ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ source = call_data; ++ ++ if (source != device->source) ++ return PA_HOOK_OK; ++ ++ description = get_source_description(source); ++ break; ++ } ++ ++ pa_device_description_changed(device->device, description); ++ pa_volume_control_description_changed(device->volume_control->volume_control, description); ++ pa_mute_control_description_changed(device->mute_control->mute_control, description); ++ ++ return PA_HOOK_OK; ++} ++ ++static struct device *device_new(pa_device_creator *creator, enum device_type type, void *core_device) { ++ struct device *device = NULL; ++ const char *name = NULL; ++ char *description = NULL; ++ pa_direction_t direction = PA_DIRECTION_OUTPUT; ++ const char *device_type = NULL; ++ bool create_volume_and_mute_controls = true; ++ ++ pa_assert(creator); ++ pa_assert(core_device); ++ ++ device = pa_xnew0(struct device, 1); ++ device->creator = creator; ++ device->type = type; ++ ++ switch (type) { ++ case DEVICE_TYPE_PORT: ++ device->port = core_device; ++ device_set_sink_and_source_from_port(device); ++ name = "port-device"; ++ description = pa_xstrdup(device->port->description); ++ direction = device->port->direction; ++ device_type = device_type_from_port(device->port); ++ ++ if (!device->sink && !device->source) ++ create_volume_and_mute_controls = false; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ device->port = core_device; ++ device_set_sink_and_source_from_port(device); ++ name = "port-monitor-device"; ++ description = pa_sprintf_malloc(_("Monitor of %s"), device->port->description); ++ direction = PA_DIRECTION_INPUT; ++ ++ if (!device->source) ++ create_volume_and_mute_controls = false; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ device->sink = core_device; ++ name = "sink-device"; ++ description = pa_xstrdup(get_sink_description(device->sink)); ++ direction = PA_DIRECTION_OUTPUT; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ device->source = core_device; ++ name = "source-device"; ++ description = pa_xstrdup(get_source_description(device->source)); ++ direction = PA_DIRECTION_INPUT; ++ break; ++ } ++ ++ device->device = pa_device_new(creator->volume_api, name, description, direction, &device_type, device_type ? 1 : 0); ++ pa_xfree(description); ++ ++ if (create_volume_and_mute_controls) { ++ device->volume_control = device_volume_control_new(device); ++ device->mute_control = device_mute_control_new(device); ++ } ++ ++ switch (type) { ++ case DEVICE_TYPE_PORT: ++ if (device->port->direction == PA_DIRECTION_OUTPUT) ++ device->monitor = device_new(creator, DEVICE_TYPE_PORT_MONITOR, device->port); ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ device->proplist_changed_slot = pa_hook_connect(&device->sink->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_proplist_changed_cb, device); ++ break; ++ ++ case DEVICE_TYPE_SOURCE: ++ device->proplist_changed_slot = pa_hook_connect(&device->source->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, sink_or_source_proplist_changed_cb, device); ++ break; ++ } ++ ++ return device; ++} ++ ++static pa_hook_result_t port_active_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct device *device = userdata; ++ pa_device_port *port = call_data; ++ bool should_have_volume_and_mute_controls = false; ++ ++ pa_assert(device); ++ pa_assert(port); ++ ++ if (port != device->port) ++ return PA_HOOK_OK; ++ ++ device_set_sink_and_source_from_port(device); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ should_have_volume_and_mute_controls = device->sink || device->source; ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ should_have_volume_and_mute_controls = !!device->source; ++ break; ++ ++ case DEVICE_TYPE_SINK: ++ case DEVICE_TYPE_SOURCE: ++ pa_assert_not_reached(); ++ } ++ ++ if (should_have_volume_and_mute_controls && !device->volume_control) { ++ pa_assert(!device->mute_control); ++ ++ device->volume_control = device_volume_control_new(device); ++ device_volume_control_put(device->volume_control); ++ pa_device_set_default_volume_control(device->device, device->volume_control->volume_control); ++ ++ device->mute_control = device_mute_control_new(device); ++ device_mute_control_put(device->mute_control); ++ pa_device_set_default_mute_control(device->device, device->mute_control->mute_control); ++ } ++ ++ if (!should_have_volume_and_mute_controls && device->volume_control) { ++ pa_assert(device->mute_control); ++ ++ device_mute_control_free(device->mute_control); ++ device->mute_control = NULL; ++ device_volume_control_free(device->volume_control); ++ device->volume_control = NULL; ++ } ++ ++ return PA_HOOK_OK; ++} ++ ++static void device_put(struct device *device) { ++ pa_assert(device); ++ ++ switch (device->type) { ++ case DEVICE_TYPE_PORT: ++ case DEVICE_TYPE_PORT_MONITOR: ++ device->port_active_changed_slot = pa_hook_connect(&device->port->core->hooks[PA_CORE_HOOK_PORT_ACTIVE_CHANGED], ++ PA_HOOK_NORMAL, port_active_changed_cb, device); ++ ++ case DEVICE_TYPE_SINK: ++ case DEVICE_TYPE_SOURCE: ++ break; ++ } ++ ++ if (device->volume_control) ++ device_volume_control_put(device->volume_control); ++ ++ if (device->mute_control) ++ device_mute_control_put(device->mute_control); ++ ++ pa_device_put(device->device, device->volume_control ? device->volume_control->volume_control : NULL, ++ device->mute_control ? device->mute_control->mute_control : NULL); ++ ++ if (device->monitor) ++ device_put(device->monitor); ++} ++ ++static void device_unlink(struct device *device) { ++ pa_assert(device); ++ ++ if (device->unlinked) ++ return; ++ ++ device->unlinked = true; ++ ++ if (device->monitor) ++ device_unlink(device->monitor); ++ ++ if (device->device) ++ pa_device_unlink(device->device); ++ ++ if (device->mute_control) ++ device_mute_control_unlink(device->mute_control); ++ ++ if (device->volume_control) ++ device_volume_control_unlink(device->volume_control); ++ ++ if (device->port_active_changed_slot) { ++ pa_hook_slot_free(device->port_active_changed_slot); ++ device->port_active_changed_slot = NULL; ++ } ++} ++ ++static void device_free(struct device *device) { ++ pa_assert(device); ++ ++ if (!device->unlinked) ++ device_unlink(device); ++ ++ if (device->monitor) ++ device_free(device->monitor); ++ ++ if (device->proplist_changed_slot) ++ pa_hook_slot_free(device->proplist_changed_slot); ++ ++ if (device->mute_control) ++ device_mute_control_free(device->mute_control); ++ ++ if (device->volume_control) ++ device_volume_control_free(device->volume_control); ++ ++ if (device->device) ++ pa_device_free(device->device); ++ ++ pa_xfree(device); ++} ++ ++static void create_device(pa_device_creator *creator, enum device_type type, void *core_device) { ++ struct device *device; ++ ++ pa_assert(creator); ++ pa_assert(core_device); ++ ++ switch (type) { ++ case DEVICE_TYPE_PORT: ++ break; ++ ++ case DEVICE_TYPE_PORT_MONITOR: ++ pa_assert_not_reached(); ++ ++ case DEVICE_TYPE_SINK: ++ if (!pa_hashmap_isempty(((pa_sink *) core_device)->ports)) ++ return; ++ break; ++ ++ case DEVICE_TYPE_SOURCE: { ++ pa_source *source = core_device; ++ ++ if (source->monitor_of && !pa_hashmap_isempty(source->monitor_of->ports)) ++ return; ++ ++ if (!pa_hashmap_isempty(((pa_source *) core_device)->ports)) ++ return; ++ break; ++ } ++ } ++ ++ device = device_new(creator, type, core_device); ++ pa_hashmap_put(creator->devices, core_device, device); ++ device_put(device); ++} ++ ++static pa_hook_result_t card_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_card *card = call_data; ++ pa_device_port *port; ++ void *state; ++ ++ pa_assert(creator); ++ pa_assert(card); ++ ++ PA_HASHMAP_FOREACH(port, card->ports, state) ++ create_device(creator, DEVICE_TYPE_PORT, port); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t card_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_card *card = call_data; ++ pa_device_port *port; ++ void *state; ++ ++ pa_assert(creator); ++ pa_assert(card); ++ ++ PA_HASHMAP_FOREACH(port, card->ports, state) ++ pa_hashmap_remove_and_free(creator->devices, port); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t sink_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_sink *sink = call_data; ++ ++ pa_assert(creator); ++ pa_assert(sink); ++ ++ create_device(creator, DEVICE_TYPE_SINK, sink); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t sink_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_sink *sink = call_data; ++ ++ pa_assert(creator); ++ pa_assert(sink); ++ ++ pa_hashmap_remove_and_free(creator->devices, sink); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_source *source = call_data; ++ ++ pa_assert(creator); ++ pa_assert(source); ++ ++ create_device(creator, DEVICE_TYPE_SOURCE, source); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_device_creator *creator = userdata; ++ pa_source *source = call_data; ++ ++ pa_assert(creator); ++ pa_assert(source); ++ ++ pa_hashmap_remove_and_free(creator->devices, source); ++ ++ return PA_HOOK_OK; ++} ++ ++pa_device_creator *pa_device_creator_new(pa_volume_api *api) { ++ pa_device_creator *creator; ++ pa_card *card; ++ uint32_t idx; ++ pa_sink *sink; ++ pa_source *source; ++ ++ pa_assert(api); ++ ++ creator = pa_xnew0(pa_device_creator, 1); ++ creator->volume_api = api; ++ creator->devices = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) device_free); ++ creator->card_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_NORMAL, card_put_cb, creator); ++ creator->card_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_CARD_UNLINK], PA_HOOK_NORMAL, card_unlink_cb, ++ creator); ++ creator->sink_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, sink_put_cb, creator); ++ creator->sink_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_NORMAL, sink_unlink_cb, ++ creator); ++ creator->source_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, source_put_cb, ++ creator); ++ creator->source_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_NORMAL, ++ source_unlink_cb, creator); ++ ++ PA_IDXSET_FOREACH(card, api->core->cards, idx) { ++ pa_device_port *port; ++ void *state; ++ ++ PA_HASHMAP_FOREACH(port, card->ports, state) ++ create_device(creator, DEVICE_TYPE_PORT, port); ++ } ++ ++ PA_IDXSET_FOREACH(sink, api->core->sinks, idx) ++ create_device(creator, DEVICE_TYPE_SINK, sink); ++ ++ PA_IDXSET_FOREACH(source, api->core->sources, idx) ++ create_device(creator, DEVICE_TYPE_SOURCE, source); ++ ++ return creator; ++} ++ ++void pa_device_creator_free(pa_device_creator *creator) { ++ pa_assert(creator); ++ ++ if (creator->devices) ++ pa_hashmap_remove_all(creator->devices); ++ ++ if (creator->source_unlink_slot) ++ pa_hook_slot_free(creator->source_unlink_slot); ++ ++ if (creator->source_put_slot) ++ pa_hook_slot_free(creator->source_put_slot); ++ ++ if (creator->sink_unlink_slot) ++ pa_hook_slot_free(creator->sink_unlink_slot); ++ ++ if (creator->sink_put_slot) ++ pa_hook_slot_free(creator->sink_put_slot); ++ ++ if (creator->card_unlink_slot) ++ pa_hook_slot_free(creator->card_unlink_slot); ++ ++ if (creator->card_put_slot) ++ pa_hook_slot_free(creator->card_put_slot); ++ ++ if (creator->devices) ++ pa_hashmap_free(creator->devices); ++ ++ pa_xfree(creator); ++} +diff --git a/src/modules/volume-api/device-creator.h b/src/modules/volume-api/device-creator.h +new file mode 100644 +index 0000000..bcec8d9 +--- /dev/null ++++ b/src/modules/volume-api/device-creator.h +@@ -0,0 +1,32 @@ ++#ifndef foodevicecreatorhfoo ++#define foodevicecreatorhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/volume-api.h> ++ ++typedef struct pa_device_creator pa_device_creator; ++ ++pa_device_creator *pa_device_creator_new(pa_volume_api *api); ++void pa_device_creator_free(pa_device_creator *creator); ++ ++#endif +diff --git a/src/modules/volume-api/device.c b/src/modules/volume-api/device.c +new file mode 100644 +index 0000000..ea496ba +--- /dev/null ++++ b/src/modules/volume-api/device.c +@@ -0,0 +1,293 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "device.h" ++ ++#include <modules/volume-api/mute-control.h> ++#include <modules/volume-api/volume-control.h> ++ ++#include <pulse/direction.h> ++ ++#include <pulsecore/core-util.h> ++ ++pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction, ++ const char * const *device_types, unsigned n_device_types) { ++ pa_device *device; ++ unsigned i; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ pa_assert(device_types || n_device_types == 0); ++ ++ device = pa_xnew0(pa_device, 1); ++ device->volume_api = api; ++ device->index = pa_volume_api_allocate_device_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &device->name) >= 0); ++ device->description = pa_xstrdup(description); ++ device->direction = direction; ++ device->device_types = pa_dynarray_new(pa_xfree); ++ ++ for (i = 0; i < n_device_types; i++) ++ pa_dynarray_append(device->device_types, pa_xstrdup(device_types[i])); ++ ++ device->proplist = pa_proplist_new(); ++ device->use_default_volume_control = true; ++ device->use_default_mute_control = true; ++ ++ return device; ++} ++ ++void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control) { ++ char *device_types_str; ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(device); ++ ++ if (default_volume_control) { ++ device->default_volume_control = default_volume_control; ++ pa_volume_control_add_default_for_device(default_volume_control, device); ++ ++ device->volume_control = default_volume_control; ++ pa_volume_control_add_device(default_volume_control, device); ++ } ++ ++ if (default_mute_control) { ++ device->default_mute_control = default_mute_control; ++ pa_mute_control_add_default_for_device(default_mute_control, device); ++ ++ device->mute_control = default_mute_control; ++ pa_mute_control_add_device(default_mute_control, device); ++ } ++ ++ pa_volume_api_add_device(device->volume_api, device); ++ ++ device->linked = true; ++ ++ device_types_str = pa_join((const char * const *) pa_dynarray_get_raw_array(device->device_types), ++ pa_dynarray_size(device->device_types), ", "); ++ ++ pa_log_debug("Created device #%u.", device->index); ++ pa_log_debug(" Name: %s", device->name); ++ pa_log_debug(" Description: %s", device->description); ++ pa_log_debug(" Direction: %s", pa_direction_to_string(device->direction)); ++ pa_log_debug(" Device Types: %s", *device_types_str ? device_types_str : "(none)"); ++ pa_log_debug(" Volume control: %s", device->volume_control ? device->volume_control->name : "(unset)"); ++ pa_log_debug(" Mute control: %s", device->mute_control ? device->mute_control->name : "(unset)"); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(device->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(device->proplist, prop_key))); ++ ++ pa_xfree(device_types_str); ++ ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_PUT], device); ++} ++ ++void pa_device_unlink(pa_device *device) { ++ pa_assert(device); ++ ++ if (device->unlinked) { ++ pa_log_debug("Unlinking device %s (already unlinked, this is a no-op).", device->name); ++ return; ++ } ++ ++ device->unlinked = true; ++ ++ pa_log_debug("Unlinking device %s.", device->name); ++ ++ if (device->linked) ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_UNLINK], device); ++ ++ pa_volume_api_remove_device(device->volume_api, device); ++ ++ if (device->mute_control) { ++ pa_mute_control_remove_device(device->mute_control, device); ++ device->mute_control = NULL; ++ } ++ ++ if (device->default_mute_control) { ++ pa_mute_control_remove_default_for_device(device->default_mute_control, device); ++ device->default_mute_control = NULL; ++ } ++ ++ if (device->volume_control) { ++ pa_volume_control_remove_device(device->volume_control, device); ++ device->volume_control = NULL; ++ } ++ ++ if (device->default_volume_control) { ++ pa_volume_control_remove_default_for_device(device->default_volume_control, device); ++ device->default_volume_control = NULL; ++ } ++} ++ ++void pa_device_free(pa_device *device) { ++ pa_assert(device); ++ ++ if (!device->unlinked) ++ pa_device_unlink(device); ++ ++ if (device->proplist) ++ pa_proplist_free(device->proplist); ++ ++ if (device->device_types) ++ pa_dynarray_free(device->device_types); ++ ++ pa_xfree(device->description); ++ ++ if (device->name) ++ pa_volume_api_unregister_name(device->volume_api, device->name); ++ ++ pa_xfree(device); ++} ++ ++static void set_volume_control_internal(pa_device *device, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_volume_control_remove_device(old_control, device); ++ ++ device->volume_control = control; ++ ++ if (control) ++ pa_volume_control_add_device(control, device); ++ ++ if (!device->linked || device->unlinked) ++ return; ++ ++ pa_log_debug("The volume control of device %s changed from %s to %s.", device->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_VOLUME_CONTROL_CHANGED], device); ++} ++ ++void pa_device_set_volume_control(pa_device *device, pa_volume_control *control) { ++ pa_assert(device); ++ ++ device->use_default_volume_control = false; ++ set_volume_control_internal(device, control); ++} ++ ++static void set_mute_control_internal(pa_device *device, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_mute_control_remove_device(old_control, device); ++ ++ device->mute_control = control; ++ ++ if (control) ++ pa_mute_control_add_device(control, device); ++ ++ pa_log_debug("The mute control of device %s changed from %s to %s.", device->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_MUTE_CONTROL_CHANGED], device); ++} ++ ++void pa_device_set_mute_control(pa_device *device, pa_mute_control *control) { ++ pa_assert(device); ++ ++ device->use_default_mute_control = false; ++ set_mute_control_internal(device, control); ++} ++ ++void pa_device_description_changed(pa_device *device, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(device); ++ pa_assert(new_description); ++ ++ old_description = device->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ device->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of device %s changed from \"%s\" to \"%s\".", device->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_DESCRIPTION_CHANGED], device); ++} ++ ++void pa_device_set_default_volume_control(pa_device *device, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->default_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_volume_control_remove_default_for_device(old_control, device); ++ ++ device->default_volume_control = control; ++ ++ if (control) ++ pa_volume_control_add_default_for_device(control, device); ++ ++ if (device->use_default_volume_control) ++ set_volume_control_internal(device, control); ++} ++ ++void pa_device_set_default_mute_control(pa_device *device, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(device); ++ ++ old_control = device->default_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) ++ pa_mute_control_remove_default_for_device(old_control, device); ++ ++ device->default_mute_control = control; ++ ++ if (control) ++ pa_mute_control_add_default_for_device(control, device); ++ ++ if (device->use_default_mute_control) ++ set_mute_control_internal(device, control); ++} +diff --git a/src/modules/volume-api/device.h b/src/modules/volume-api/device.h +new file mode 100644 +index 0000000..9eac7e9 +--- /dev/null ++++ b/src/modules/volume-api/device.h +@@ -0,0 +1,76 @@ ++#ifndef foodevicehfoo ++#define foodevicehfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/volume-api.h> ++ ++#include <pulsecore/dynarray.h> ++ ++typedef struct pa_device pa_device; ++ ++struct pa_device { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_direction_t direction; ++ pa_dynarray *device_types; ++ pa_proplist *proplist; ++ pa_volume_control *volume_control; ++ pa_mute_control *mute_control; ++ ++ /* The device implementation can provide default volume and mute controls, ++ * which are used in case there's no policy module that wants to override ++ * the defaults. */ ++ pa_volume_control *default_volume_control; ++ bool use_default_volume_control; ++ pa_mute_control *default_mute_control; ++ bool use_default_mute_control; ++ ++ bool linked; ++ bool unlinked; ++}; ++ ++pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction, ++ const char * const *device_types, unsigned n_device_types); ++void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control); ++void pa_device_unlink(pa_device *device); ++void pa_device_free(pa_device *device); ++ ++/* Called by policy modules. */ ++void pa_device_set_volume_control(pa_device *device, pa_volume_control *control); ++void pa_device_set_mute_control(pa_device *device, pa_mute_control *control); ++ ++/* Called by policy modules. Note that pa_device_set_volume_control() and ++ * pa_device_set_mute_control() automatically disable the corresponding ++ * use_default flags, so these functions are mainly useful for re-enabling the ++ * flags. */ ++void pa_device_set_use_default_volume_control(pa_device *device, bool use); ++void pa_device_set_use_default_mute_control(pa_device *device, bool use); ++ ++/* Called by the device implementation. */ ++void pa_device_description_changed(pa_device *device, const char *new_description); ++void pa_device_set_default_volume_control(pa_device *device, pa_volume_control *control); ++void pa_device_set_default_mute_control(pa_device *device, pa_mute_control *control); ++ ++#endif +diff --git a/src/modules/volume-api/mute-control.c b/src/modules/volume-api/mute-control.c +new file mode 100644 +index 0000000..adc008e +--- /dev/null ++++ b/src/modules/volume-api/mute-control.c +@@ -0,0 +1,306 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "mute-control.h" ++ ++#include <modules/volume-api/audio-group.h> ++#include <modules/volume-api/device.h> ++#include <modules/volume-api/sstream.h> ++ ++#include <pulsecore/core-util.h> ++ ++pa_mute_control *pa_mute_control_new(pa_volume_api *api, const char *name, const char *description) { ++ pa_mute_control *control; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ ++ control = pa_xnew0(pa_mute_control, 1); ++ control->volume_api = api; ++ control->index = pa_volume_api_allocate_mute_control_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &control->name) >= 0); ++ control->description = pa_xstrdup(description); ++ control->proplist = pa_proplist_new(); ++ control->devices = pa_hashmap_new(NULL, NULL); ++ control->default_for_devices = pa_hashmap_new(NULL, NULL); ++ control->streams = pa_hashmap_new(NULL, NULL); ++ control->audio_groups = pa_hashmap_new(NULL, NULL); ++ ++ return control; ++} ++ ++void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initial_mute_is_set, ++ pa_mute_control_set_initial_mute_cb_t set_initial_mute_cb) { ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(control); ++ pa_assert(initial_mute_is_set || control->set_mute); ++ pa_assert(set_initial_mute_cb || !control->set_mute); ++ ++ if (initial_mute_is_set) ++ control->mute = initial_mute; ++ else ++ control->mute = false; ++ ++ if (set_initial_mute_cb) ++ set_initial_mute_cb(control); ++ ++ pa_volume_api_add_mute_control(control->volume_api, control); ++ ++ control->linked = true; ++ ++ pa_log_debug("Created mute control #%u.", control->index); ++ pa_log_debug(" Name: %s", control->name); ++ pa_log_debug(" Description: %s", control->description); ++ pa_log_debug(" Mute: %s", pa_yes_no(control->mute)); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(control->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(control->proplist, prop_key))); ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT], control); ++} ++ ++void pa_mute_control_unlink(pa_mute_control *control) { ++ pa_audio_group *group; ++ pa_device *device; ++ pas_stream *stream; ++ ++ pa_assert(control); ++ ++ if (control->unlinked) { ++ pa_log_debug("Unlinking mute control %s (already unlinked, this is a no-op).", control->name); ++ return; ++ } ++ ++ control->unlinked = true; ++ ++ pa_log_debug("Unlinking mute control %s.", control->name); ++ ++ if (control->linked) ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK], control); ++ ++ pa_volume_api_remove_mute_control(control->volume_api, control); ++ ++ while ((group = pa_hashmap_first(control->audio_groups))) ++ pa_audio_group_set_mute_control(group, NULL); ++ ++ while ((stream = pa_hashmap_first(control->streams))) ++ pas_stream_set_mute_control(stream, NULL); ++ ++ while ((device = pa_hashmap_first(control->default_for_devices))) ++ pa_device_set_default_mute_control(device, NULL); ++ ++ while ((device = pa_hashmap_first(control->devices))) { ++ /* Why do we have this assertion here? The concern is that if we call ++ * pa_device_set_mute_control() for some device that has the ++ * use_default_mute_control flag set, then that flag will be unset as ++ * a side effect, and we don't want that side effect. This assertion ++ * should be safe, because we just called ++ * pa_device_set_default_mute_control(NULL) for each device that this ++ * control was the default for, and that should ensure that we don't ++ * any more hold any references to devices that used to use this ++ * control as the default. */ ++ pa_assert(!device->use_default_mute_control); ++ pa_device_set_mute_control(device, NULL); ++ } ++} ++ ++void pa_mute_control_free(pa_mute_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ pa_mute_control_unlink(control); ++ ++ if (control->audio_groups) { ++ pa_assert(pa_hashmap_isempty(control->audio_groups)); ++ pa_hashmap_free(control->audio_groups); ++ } ++ ++ if (control->streams) { ++ pa_assert(pa_hashmap_isempty(control->streams)); ++ pa_hashmap_free(control->streams); ++ } ++ ++ if (control->default_for_devices) { ++ pa_assert(pa_hashmap_isempty(control->default_for_devices)); ++ pa_hashmap_free(control->default_for_devices); ++ } ++ ++ if (control->devices) { ++ pa_assert(pa_hashmap_isempty(control->devices)); ++ pa_hashmap_free(control->devices); ++ } ++ ++ if (control->proplist) ++ pa_proplist_free(control->proplist); ++ ++ pa_xfree(control->description); ++ ++ if (control->name) ++ pa_volume_api_unregister_name(control->volume_api, control->name); ++ ++ pa_xfree(control); ++} ++ ++void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ control->owner_audio_group = group; ++} ++ ++static void set_mute_internal(pa_mute_control *control, bool mute) { ++ bool old_mute; ++ ++ pa_assert(control); ++ ++ old_mute = control->mute; ++ ++ if (mute == old_mute) ++ return; ++ ++ control->mute = mute; ++ ++ if (!control->linked || control->unlinked) ++ return; ++ ++ pa_log_debug("The mute of mute control %s changed from %s to %s.", control->name, pa_yes_no(old_mute), ++ pa_yes_no(control->mute)); ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], control); ++} ++ ++int pa_mute_control_set_mute(pa_mute_control *control, bool mute) { ++ int r; ++ ++ pa_assert(control); ++ ++ if (!control->set_mute) { ++ pa_log_info("Tried to set the mute of mute control %s, but the mute control doesn't support the operation.", ++ control->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ if (mute == control->mute) ++ return 0; ++ ++ control->set_mute_in_progress = true; ++ r = control->set_mute(control, mute); ++ control->set_mute_in_progress = false; ++ ++ if (r >= 0) ++ set_mute_internal(control, mute); ++ ++ return r; ++} ++ ++void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(control); ++ pa_assert(new_description); ++ ++ old_description = control->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ control->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of mute control %s changed from \"%s\" to \"%s\".", control->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED], control); ++} ++ ++void pa_mute_control_mute_changed(pa_mute_control *control, bool new_mute) { ++ pa_assert(control); ++ ++ if (!control->linked) ++ return; ++ ++ if (control->set_mute_in_progress) ++ return; ++ ++ set_mute_internal(control, new_mute); ++} ++ ++void pa_mute_control_add_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->devices, device, device) >= 0); ++} ++ ++void pa_mute_control_remove_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->devices, device)); ++} ++ ++void pa_mute_control_add_default_for_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->default_for_devices, device, device) >= 0); ++} ++ ++void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->default_for_devices, device)); ++} ++ ++void pa_mute_control_add_stream(pa_mute_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(control->streams, stream, stream) >= 0); ++} ++ ++void pa_mute_control_remove_stream(pa_mute_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(control->streams, stream)); ++} ++ ++void pa_mute_control_add_audio_group(pa_mute_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_put(control->audio_groups, group, group) >= 0); ++} ++ ++void pa_mute_control_remove_audio_group(pa_mute_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_remove(control->audio_groups, group)); ++} +diff --git a/src/modules/volume-api/mute-control.h b/src/modules/volume-api/mute-control.h +new file mode 100644 +index 0000000..1f70a43 +--- /dev/null ++++ b/src/modules/volume-api/mute-control.h +@@ -0,0 +1,102 @@ ++#ifndef foomutecontrolhfoo ++#define foomutecontrolhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/volume-api.h> ++ ++typedef struct pa_mute_control pa_mute_control; ++ ++struct pa_mute_control { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_proplist *proplist; ++ bool mute; ++ ++ /* If this mute control is the "own mute control" of an audio group, this ++ * is set to point to that group, otherwise this is NULL. */ ++ pa_audio_group *owner_audio_group; ++ ++ pa_hashmap *devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *default_for_devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ pa_hashmap *audio_groups; /* pa_audio_group -> pa_audio_group (hashmap-as-a-set) */ ++ ++ bool linked; ++ bool unlinked; ++ bool set_mute_in_progress; ++ ++ /* Called from pa_mute_control_set_mute(). The implementation is expected ++ * to return a negative error code on failure. May be NULL, if the mute ++ * control is read-only. */ ++ int (*set_mute)(pa_mute_control *control, bool mute); ++ ++ void *userdata; ++}; ++ ++pa_mute_control *pa_mute_control_new(pa_volume_api *api, const char *name, const char *description); ++ ++typedef void (*pa_mute_control_set_initial_mute_cb_t)(pa_mute_control *control); ++ ++/* initial_mute is the preferred initial mute of the mute control ++ * implementation. It may be unset, if the implementation doesn't care about ++ * the initial state of the mute control. Read-only mute controls, however, ++ * must always set initial_mute. ++ * ++ * The implementation's initial mute preference may be overridden by policy, if ++ * the mute control isn't read-only. When the final initial mute is known, the ++ * the implementation is notified via set_initial_mute_cb (the mute can be read ++ * from control->mute). set_initial_mute_cb may be NULL, if the mute control is ++ * read-only. */ ++void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initial_mute_is_set, ++ pa_mute_control_set_initial_mute_cb_t set_initial_mute_cb); ++ ++void pa_mute_control_unlink(pa_mute_control *control); ++void pa_mute_control_free(pa_mute_control *control); ++ ++/* Called by audio-group.c only. */ ++void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group); ++ ++/* Called by clients and policy modules. */ ++int pa_mute_control_set_mute(pa_mute_control *control, bool mute); ++ ++/* Called by the mute control implementation. */ ++void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description); ++void pa_mute_control_mute_changed(pa_mute_control *control, bool new_mute); ++ ++/* Called from device.c only. */ ++void pa_mute_control_add_device(pa_mute_control *control, pa_device *device); ++void pa_mute_control_remove_device(pa_mute_control *control, pa_device *device); ++void pa_mute_control_add_default_for_device(pa_mute_control *control, pa_device *device); ++void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_device *device); ++ ++/* Called from sstream.c only. */ ++void pa_mute_control_add_stream(pa_mute_control *control, pas_stream *stream); ++void pa_mute_control_remove_stream(pa_mute_control *control, pas_stream *stream); ++ ++/* Called from audio-group.c only. */ ++void pa_mute_control_add_audio_group(pa_mute_control *control, pa_audio_group *group); ++void pa_mute_control_remove_audio_group(pa_mute_control *control, pa_audio_group *group); ++ ++#endif +diff --git a/src/modules/volume-api/sstream.c b/src/modules/volume-api/sstream.c +new file mode 100644 +index 0000000..e3531a8 +--- /dev/null ++++ b/src/modules/volume-api/sstream.c +@@ -0,0 +1,366 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "sstream.h" ++ ++#include <modules/volume-api/audio-group.h> ++#include <modules/volume-api/binding.h> ++#include <modules/volume-api/mute-control.h> ++#include <modules/volume-api/volume-control.h> ++ ++#include <pulse/direction.h> ++ ++#include <pulsecore/core-util.h> ++ ++pas_stream *pas_stream_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction) { ++ pas_stream *stream; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ ++ stream = pa_xnew0(pas_stream, 1); ++ stream->volume_api = api; ++ stream->index = pa_volume_api_allocate_stream_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &stream->name) >= 0); ++ stream->description = pa_xstrdup(description); ++ stream->direction = direction; ++ stream->proplist = pa_proplist_new(); ++ stream->use_default_volume_control = true; ++ stream->use_default_mute_control = true; ++ ++ return stream; ++} ++ ++static void set_volume_control_internal(pas_stream *stream, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(stream); ++ ++ old_control = stream->volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) { ++ /* If the old control pointed to the own volume control of an audio ++ * group, then the stream's audio group for volume needs to be ++ * updated. We set it to NULL here, and if it should be non-NULL, that ++ * will be fixed very soon (a few lines down). */ ++ pas_stream_set_audio_group_for_volume(stream, NULL); ++ ++ pa_volume_control_remove_stream(old_control, stream); ++ } ++ ++ stream->volume_control = control; ++ ++ if (control) { ++ pa_volume_control_add_stream(control, stream); ++ pas_stream_set_audio_group_for_volume(stream, control->owner_audio_group); ++ } ++ ++ if (!stream->linked || stream->unlinked) ++ return; ++ ++ pa_log_debug("The volume control of stream %s changed from %s to %s.", stream->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED], stream); ++} ++ ++static void set_mute_control_internal(pas_stream *stream, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(stream); ++ ++ old_control = stream->mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ if (old_control) { ++ /* If the old control pointed to the own mute control of an audio ++ * group, then the stream's audio group for mute needs to be updated. ++ * We set it to NULL here, and if it should be non-NULL, that will be ++ * fixed very soon (a few lines down). */ ++ pas_stream_set_audio_group_for_mute(stream, NULL); ++ ++ pa_mute_control_remove_stream(old_control, stream); ++ } ++ ++ stream->mute_control = control; ++ ++ if (control) { ++ pa_mute_control_add_stream(control, stream); ++ pas_stream_set_audio_group_for_mute(stream, control->owner_audio_group); ++ } ++ ++ if (!stream->linked || stream->unlinked) ++ return; ++ ++ pa_log_debug("The mute control of stream %s changed from %s to %s.", stream->name, ++ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)"); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED], stream); ++} ++ ++void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties) { ++ const char *prop_key; ++ void *state = NULL; ++ ++ pa_assert(stream); ++ pa_assert(!stream->create_own_volume_control || stream->delete_own_volume_control); ++ pa_assert(!stream->create_own_mute_control || stream->delete_own_mute_control); ++ ++ if (initial_properties) ++ pa_proplist_update(stream->proplist, PA_UPDATE_REPLACE, initial_properties); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL], stream); ++ ++ if (stream->use_default_volume_control) ++ set_volume_control_internal(stream, stream->own_volume_control); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL], stream); ++ ++ if (stream->use_default_mute_control) ++ set_mute_control_internal(stream, stream->own_mute_control); ++ ++ pa_volume_api_add_stream(stream->volume_api, stream); ++ ++ stream->linked = true; ++ ++ pa_log_debug("Created stream #%u.", stream->index); ++ pa_log_debug(" Name: %s", stream->name); ++ pa_log_debug(" Description: %s", stream->description); ++ pa_log_debug(" Direction: %s", pa_direction_to_string(stream->direction)); ++ pa_log_debug(" Volume control: %s", stream->volume_control ? stream->volume_control->name : "(unset)"); ++ pa_log_debug(" Mute control: %s", stream->mute_control ? stream->mute_control->name : "(unset)"); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(stream->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(stream->proplist, prop_key))); ++ ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PUT], stream); ++} ++ ++void pas_stream_unlink(pas_stream *stream) { ++ pa_assert(stream); ++ ++ if (stream->unlinked) { ++ pa_log_debug("Unlinking stream %s (already unlinked, this is a no-op).", stream->name); ++ return; ++ } ++ ++ stream->unlinked = true; ++ ++ pa_log_debug("Unlinking stream %s.", stream->name); ++ ++ if (stream->linked) ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], stream); ++ ++ pa_volume_api_remove_stream(stream->volume_api, stream); ++ ++ pas_stream_set_audio_group_for_mute(stream, NULL); ++ pas_stream_set_audio_group_for_volume(stream, NULL); ++ pas_stream_set_mute_control(stream, NULL); ++ pas_stream_set_volume_control(stream, NULL); ++ pas_stream_set_have_own_mute_control(stream, false); ++ pas_stream_set_have_own_volume_control(stream, false); ++} ++ ++void pas_stream_free(pas_stream *stream) { ++ pa_assert(stream); ++ ++ if (!stream->unlinked) ++ pas_stream_unlink(stream); ++ ++ if (stream->proplist) ++ pa_proplist_free(stream->proplist); ++ ++ pa_xfree(stream->description); ++ ++ if (stream->name) ++ pa_volume_api_unregister_name(stream->volume_api, stream->name); ++ ++ pa_xfree(stream); ++} ++ ++int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have) { ++ pa_assert(stream); ++ ++ if (have == stream->have_own_volume_control) ++ return 0; ++ ++ if (have) { ++ pa_assert(!stream->own_volume_control); ++ ++ if (!stream->create_own_volume_control) { ++ pa_log_debug("Stream %s doesn't support own volume control.", stream->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ stream->own_volume_control = stream->create_own_volume_control(stream); ++ } else { ++ stream->delete_own_volume_control(stream); ++ stream->own_volume_control = NULL; ++ } ++ ++ stream->have_own_volume_control = have; ++ ++ return 0; ++} ++ ++int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have) { ++ pa_assert(stream); ++ ++ if (have == stream->have_own_mute_control) ++ return 0; ++ ++ if (have) { ++ pa_assert(!stream->own_mute_control); ++ ++ if (!stream->create_own_mute_control) { ++ pa_log_debug("Stream %s doesn't support own mute control.", stream->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ stream->own_mute_control = stream->create_own_mute_control(stream); ++ } else { ++ stream->delete_own_mute_control(stream); ++ stream->own_mute_control = NULL; ++ } ++ ++ stream->have_own_mute_control = have; ++ ++ return 0; ++} ++ ++void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control) { ++ pa_assert(stream); ++ ++ stream->use_default_volume_control = false; ++ ++ if (stream->volume_control_binding) { ++ pa_binding_free(stream->volume_control_binding); ++ stream->volume_control_binding = NULL; ++ } ++ ++ set_volume_control_internal(stream, control); ++} ++ ++void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control) { ++ pa_assert(stream); ++ ++ stream->use_default_mute_control = false; ++ ++ if (stream->mute_control_binding) { ++ pa_binding_free(stream->mute_control_binding); ++ stream->mute_control_binding = NULL; ++ } ++ ++ set_mute_control_internal(stream, control); ++} ++ ++void pas_stream_bind_volume_control(pas_stream *stream, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = stream, ++ .set_value = (pa_binding_set_value_cb_t) set_volume_control_internal, ++ }; ++ ++ pa_assert(stream); ++ pa_assert(target_info); ++ ++ stream->use_default_volume_control = false; ++ ++ if (stream->volume_control_binding) ++ pa_binding_free(stream->volume_control_binding); ++ ++ stream->volume_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info); ++} ++ ++void pas_stream_bind_mute_control(pas_stream *stream, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = stream, ++ .set_value = (pa_binding_set_value_cb_t) set_mute_control_internal, ++ }; ++ ++ pa_assert(stream); ++ pa_assert(target_info); ++ ++ stream->use_default_mute_control = false; ++ ++ if (stream->mute_control_binding) ++ pa_binding_free(stream->mute_control_binding); ++ ++ stream->mute_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info); ++} ++ ++void pas_stream_description_changed(pas_stream *stream, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(stream); ++ pa_assert(new_description); ++ ++ old_description = stream->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ stream->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of stream %s changed from \"%s\" to \"%s\".", stream->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED], stream); ++} ++ ++void pas_stream_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group) { ++ pa_assert(stream); ++ ++ if (group == stream->audio_group_for_volume) ++ return; ++ ++ if (stream->audio_group_for_volume) ++ pa_audio_group_remove_volume_stream(stream->audio_group_for_volume, stream); ++ ++ stream->audio_group_for_volume = group; ++ ++ if (group) ++ pa_audio_group_add_volume_stream(group, stream); ++} ++ ++void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group) { ++ pa_assert(stream); ++ ++ if (group == stream->audio_group_for_mute) ++ return; ++ ++ if (stream->audio_group_for_mute) ++ pa_audio_group_remove_mute_stream(stream->audio_group_for_mute, stream); ++ ++ stream->audio_group_for_mute = group; ++ ++ if (group) ++ pa_audio_group_add_mute_stream(group, stream); ++} +diff --git a/src/modules/volume-api/sstream.h b/src/modules/volume-api/sstream.h +new file mode 100644 +index 0000000..a65b34c +--- /dev/null ++++ b/src/modules/volume-api/sstream.h +@@ -0,0 +1,108 @@ ++#ifndef foosstreamhfoo ++#define foosstreamhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/volume-api.h> ++ ++/* We use the "pas_" prefix in pas_stream, because there's already pa_stream in ++ * the client API, and there's no good alternative term for streams. The 's' in ++ * "pas" means "server", i.e. the point is that this stuff is for servers, ++ * while pa_stream is for clients. */ ++ ++typedef struct pas_stream pas_stream; ++ ++struct pas_stream { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_direction_t direction; ++ pa_proplist *proplist; ++ pa_volume_control *volume_control; ++ pa_mute_control *mute_control; ++ bool use_default_volume_control; ++ bool use_default_mute_control; ++ bool have_own_volume_control; ++ bool have_own_mute_control; ++ pa_volume_control *own_volume_control; ++ pa_mute_control *own_mute_control; ++ ++ pa_binding *volume_control_binding; ++ pa_binding *mute_control_binding; ++ pa_audio_group *audio_group_for_volume; ++ pa_audio_group *audio_group_for_mute; ++ ++ bool linked; ++ bool unlinked; ++ ++ /* Called when the own volume control is enabled. The callback ++ * implementation should return a new linked volume control object. The ++ * callback may be NULL, in which case the own volume control can't be ++ * enabled. */ ++ pa_volume_control *(*create_own_volume_control)(pas_stream *stream); ++ ++ /* Called when the own volume control is disabled. The implementation ++ * should free stream->own_volume_control. The callback may be NULL only if ++ * create_own_volume_control is NULL also. */ ++ void (*delete_own_volume_control)(pas_stream *stream); ++ ++ /* Called when the own mute control is enabled. The callback implementation ++ * should return a new linked mute control object. The callback may be ++ * NULL, in which case the own mute control can't be enabled. */ ++ pa_mute_control *(*create_own_mute_control)(pas_stream *stream); ++ ++ /* Called when the own mute control is disabled. The implementation should ++ * free stream->own_mute_control. The callback may be NULL only if ++ * create_own_mute_control is NULL also. */ ++ void (*delete_own_mute_control)(pas_stream *stream); ++ ++ void *userdata; ++}; ++ ++pas_stream *pas_stream_new(pa_volume_api *api, const char *name, const char *description, pa_direction_t direction); ++void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties); ++void pas_stream_unlink(pas_stream *stream); ++void pas_stream_free(pas_stream *stream); ++ ++/* Called by the stream implementation and possibly by policy modules. ++ * Enabling own controls may fail (the stream may not support own controls), ++ * disabling will never fail. */ ++int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have); ++int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have); ++ ++/* Called by policy modules. */ ++void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control); ++void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control); ++void pas_stream_bind_volume_control(pas_stream *stream, pa_binding_target_info *target_info); ++void pas_stream_bind_mute_control(pas_stream *stream, pa_binding_target_info *target_info); ++ ++/* Called by the stream implementation. */ ++void pas_stream_description_changed(pas_stream *stream, const char *new_description); ++ ++/* Called by audio-group.c only. Adding a stream to an audio group happens ++ * implicitly when the volume or mute control of a stream is set to point to ++ * the own control of an audio group. */ ++void pas_stream_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group); ++void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group); ++ ++#endif +diff --git a/src/modules/volume-api/stream-creator.c b/src/modules/volume-api/stream-creator.c +new file mode 100644 +index 0000000..2bd0053 +--- /dev/null ++++ b/src/modules/volume-api/stream-creator.c +@@ -0,0 +1,691 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "stream-creator.h" ++ ++#include <modules/volume-api/sstream.h> ++#include <modules/volume-api/mute-control.h> ++#include <modules/volume-api/volume-control.h> ++ ++#include <pulsecore/core-util.h> ++#include <pulsecore/i18n.h> ++ ++struct pa_stream_creator { ++ pa_volume_api *volume_api; ++ pa_hashmap *streams; /* pa_sink_input/pa_source_output -> struct stream */ ++ pa_hook_slot *sink_input_put_slot; ++ pa_hook_slot *sink_input_unlink_slot; ++ pa_hook_slot *source_output_put_slot; ++ pa_hook_slot *source_output_unlink_slot; ++}; ++ ++enum stream_type { ++ STREAM_TYPE_SINK_INPUT, ++ STREAM_TYPE_SOURCE_OUTPUT, ++}; ++ ++struct stream { ++ pa_stream_creator *creator; ++ enum stream_type type; ++ pa_sink_input *sink_input; ++ pa_source_output *source_output; ++ pa_client *client; ++ pas_stream *stream; ++ ++ bool unlinked; ++ ++ pa_hook_slot *proplist_changed_slot; ++ pa_hook_slot *client_proplist_changed_slot; ++ pa_hook_slot *volume_changed_slot; ++ pa_hook_slot *mute_changed_slot; ++}; ++ ++static char *get_stream_volume_and_mute_control_description_malloc(struct stream *stream) { ++ const char *application_name = NULL; ++ char *description; ++ ++ pa_assert(stream); ++ ++ if (stream->client) ++ application_name = pa_proplist_gets(stream->client->proplist, PA_PROP_APPLICATION_NAME); ++ ++ if (application_name) ++ description = pa_sprintf_malloc("%s: %s", application_name, stream->stream->description); ++ else ++ description = pa_xstrdup(stream->stream->description); ++ ++ return description; ++} ++ ++static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ struct stream *stream; ++ pa_bvolume bvolume; ++ pa_cvolume cvolume; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ stream = control->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_bvolume_from_cvolume(&bvolume, &stream->sink_input->volume, &stream->sink_input->channel_map); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_bvolume_from_cvolume(&bvolume, &stream->source_output->volume, &stream->source_output->channel_map); ++ break; ++ } ++ ++ if (set_volume) ++ bvolume.volume = volume->volume; ++ ++ if (set_balance) ++ pa_bvolume_copy_balance(&bvolume, volume); ++ ++ pa_bvolume_to_cvolume(&bvolume, &cvolume); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_volume(stream->sink_input, &cvolume, true, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_volume(stream->source_output, &cvolume, true, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static pa_hook_result_t sink_input_or_source_output_volume_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_sink_input *input = NULL; ++ pa_source_output *output = NULL; ++ pa_bvolume bvolume; ++ ++ pa_assert(stream); ++ pa_assert(call_data); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ input = call_data; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ output = call_data; ++ break; ++ } ++ ++ if ((input && input != stream->sink_input) || (output && output != stream->source_output)) ++ return PA_HOOK_OK; ++ ++ if (input) ++ pa_bvolume_from_cvolume(&bvolume, &input->volume, &input->channel_map); ++ else ++ pa_bvolume_from_cvolume(&bvolume, &output->volume, &output->channel_map); ++ ++ pa_volume_control_volume_changed(stream->stream->own_volume_control, &bvolume, true, true); ++ ++ return PA_HOOK_OK; ++} ++ ++static void volume_control_set_initial_volume_cb(pa_volume_control *control) { ++ struct stream *stream; ++ pa_cvolume cvolume; ++ ++ pa_assert(control); ++ ++ stream = control->userdata; ++ pa_bvolume_to_cvolume(&control->volume, &cvolume); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_volume(stream->sink_input, &cvolume, true, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_volume(stream->source_output, &cvolume, true, true); ++ break; ++ } ++} ++ ++static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) { ++ struct stream *stream; ++ ++ pa_assert(control); ++ ++ stream = control->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_mute(stream->sink_input, mute, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_mute(stream->source_output, mute, true); ++ break; ++ } ++ ++ return 0; ++} ++ ++static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_sink_input *input = NULL; ++ pa_source_output *output = NULL; ++ bool mute; ++ ++ pa_assert(stream); ++ pa_assert(call_data); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ input = call_data; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ output = call_data; ++ break; ++ } ++ ++ if ((input && input != stream->sink_input) || (output && output != stream->source_output)) ++ return PA_HOOK_OK; ++ ++ if (input) ++ mute = input->muted; ++ else ++ mute = output->muted; ++ ++ pa_mute_control_mute_changed(stream->stream->own_mute_control, mute); ++ ++ return PA_HOOK_OK; ++} ++ ++static void mute_control_set_initial_mute_cb(pa_mute_control *control) { ++ struct stream *stream; ++ ++ pa_assert(control); ++ ++ stream = control->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ pa_sink_input_set_mute(stream->sink_input, control->mute, true); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ pa_source_output_set_mute(stream->source_output, control->mute, true); ++ break; ++ } ++} ++ ++static const char *get_sink_input_description(pa_sink_input *input) { ++ const char *description; ++ ++ pa_assert(input); ++ ++ description = pa_proplist_gets(input->proplist, PA_PROP_MEDIA_NAME); ++ if (description) ++ return description; ++ ++ return NULL; ++} ++ ++static const char *get_source_output_description(pa_source_output *output) { ++ const char *description; ++ ++ pa_assert(output); ++ ++ description = pa_proplist_gets(output->proplist, PA_PROP_MEDIA_NAME); ++ if (description) ++ return description; ++ ++ return NULL; ++} ++ ++static pa_volume_control *stream_create_own_volume_control_cb(pas_stream *s) { ++ struct stream *stream; ++ const char *name = NULL; ++ char *description; ++ pa_volume_control *control; ++ pa_bvolume volume; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ name = "sink-input-volume-control"; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ name = "source-output-volume-control"; ++ break; ++ } ++ ++ description = get_stream_volume_and_mute_control_description_malloc(stream); ++ control = pa_volume_control_new(stream->creator->volume_api, name, description, true, false); ++ pa_xfree(description); ++ control->set_volume = volume_control_set_volume_cb; ++ control->userdata = stream; ++ ++ pa_assert(!stream->volume_changed_slot); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->volume_changed_slot = ++ pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], PA_HOOK_NORMAL, ++ sink_input_or_source_output_volume_changed_cb, stream); ++ pa_bvolume_from_cvolume(&volume, &stream->sink_input->volume, &stream->sink_input->channel_map); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->volume_changed_slot = ++ pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], ++ PA_HOOK_NORMAL, sink_input_or_source_output_volume_changed_cb, stream); ++ pa_bvolume_from_cvolume(&volume, &stream->source_output->volume, &stream->source_output->channel_map); ++ break; ++ } ++ ++ pa_volume_control_put(control, &volume, volume_control_set_initial_volume_cb); ++ ++ return control; ++} ++ ++static void stream_delete_own_volume_control_cb(pas_stream *s) { ++ struct stream *stream; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ pa_hook_slot_free(stream->volume_changed_slot); ++ stream->volume_changed_slot = NULL; ++ pa_volume_control_free(s->own_volume_control); ++} ++ ++static pa_mute_control *stream_create_own_mute_control_cb(pas_stream *s) { ++ struct stream *stream; ++ const char *name = NULL; ++ char *description; ++ pa_mute_control *control; ++ bool mute = false; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ name = "sink-input-mute-control"; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ name = "source-output-mute-control"; ++ break; ++ } ++ ++ description = get_stream_volume_and_mute_control_description_malloc(stream); ++ control = pa_mute_control_new(stream->creator->volume_api, name, description); ++ pa_xfree(description); ++ control->set_mute = mute_control_set_mute_cb; ++ control->userdata = stream; ++ ++ pa_assert(!stream->mute_changed_slot); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->mute_changed_slot = ++ pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], PA_HOOK_NORMAL, ++ sink_input_or_source_output_mute_changed_cb, stream); ++ mute = stream->sink_input->muted; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->mute_changed_slot = ++ pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], ++ PA_HOOK_NORMAL, sink_input_or_source_output_mute_changed_cb, stream); ++ mute = stream->source_output->muted; ++ break; ++ } ++ ++ pa_mute_control_put(control, mute, true, mute_control_set_initial_mute_cb); ++ ++ return control; ++} ++ ++static void stream_delete_own_mute_control_cb(pas_stream *s) { ++ struct stream *stream; ++ ++ pa_assert(s); ++ ++ stream = s->userdata; ++ pa_hook_slot_free(stream->mute_changed_slot); ++ stream->mute_changed_slot = NULL; ++ pa_mute_control_free(s->own_mute_control); ++} ++ ++static pa_hook_result_t sink_input_or_source_output_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_sink_input *input = NULL; ++ pa_source_output *output = NULL; ++ const char *new_stream_description = NULL; ++ char *new_control_description; ++ ++ pa_assert(stream); ++ pa_assert(call_data); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ input = call_data; ++ ++ if (input != stream->sink_input) ++ return PA_HOOK_OK; ++ ++ new_stream_description = get_sink_input_description(input); ++ if (!new_stream_description) ++ new_stream_description = stream->stream->name; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ output = call_data; ++ ++ if (output != stream->source_output) ++ return PA_HOOK_OK; ++ ++ new_stream_description = get_source_output_description(output); ++ if (!new_stream_description) ++ new_stream_description = stream->stream->name; ++ break; ++ } ++ ++ pas_stream_description_changed(stream->stream, new_stream_description); ++ ++ new_control_description = get_stream_volume_and_mute_control_description_malloc(stream); ++ ++ if (stream->stream->own_volume_control) ++ pa_volume_control_description_changed(stream->stream->own_volume_control, new_control_description); ++ ++ if (stream->stream->own_mute_control) ++ pa_mute_control_description_changed(stream->stream->own_mute_control, new_control_description); ++ ++ pa_xfree(new_control_description); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t client_proplist_changed_cb(void *hook_data, void *call_data, void *userdata) { ++ struct stream *stream = userdata; ++ pa_client *client = call_data; ++ char *description; ++ ++ pa_assert(stream); ++ pa_assert(client); ++ ++ if (client != stream->client) ++ return PA_HOOK_OK; ++ ++ description = get_stream_volume_and_mute_control_description_malloc(stream); ++ ++ if (stream->stream->own_volume_control) ++ pa_volume_control_description_changed(stream->stream->own_volume_control, description); ++ ++ if (stream->stream->own_mute_control) ++ pa_mute_control_description_changed(stream->stream->own_mute_control, description); ++ ++ pa_xfree(description); ++ ++ return PA_HOOK_OK; ++} ++ ++static struct stream *stream_new(pa_stream_creator *creator, enum stream_type type, void *core_stream) { ++ struct stream *stream; ++ const char *name = NULL; ++ const char *description = NULL; ++ pa_direction_t direction = PA_DIRECTION_OUTPUT; ++ ++ pa_assert(creator); ++ pa_assert(core_stream); ++ ++ stream = pa_xnew0(struct stream, 1); ++ stream->creator = creator; ++ stream->type = type; ++ ++ switch (type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->sink_input = core_stream; ++ stream->client = stream->sink_input->client; ++ name = "sink-input-stream"; ++ ++ description = get_sink_input_description(stream->sink_input); ++ if (!description) ++ description = name; ++ ++ direction = PA_DIRECTION_OUTPUT; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->source_output = core_stream; ++ stream->client = stream->source_output->client; ++ name = "source-output-stream"; ++ ++ description = get_source_output_description(stream->source_output); ++ if (!description) ++ description = name; ++ ++ direction = PA_DIRECTION_INPUT; ++ break; ++ } ++ ++ stream->stream = pas_stream_new(creator->volume_api, name, description, direction); ++ stream->stream->create_own_volume_control = stream_create_own_volume_control_cb; ++ stream->stream->delete_own_volume_control = stream_delete_own_volume_control_cb; ++ stream->stream->create_own_mute_control = stream_create_own_mute_control_cb; ++ stream->stream->delete_own_mute_control = stream_delete_own_mute_control_cb; ++ stream->stream->userdata = stream; ++ pas_stream_set_have_own_volume_control(stream->stream, true); ++ pas_stream_set_have_own_mute_control(stream->stream, true); ++ ++ switch (type) { ++ case STREAM_TYPE_SINK_INPUT: ++ stream->proplist_changed_slot = ++ pa_hook_connect(&stream->sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_NORMAL, ++ sink_input_or_source_output_proplist_changed_cb, stream); ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ stream->proplist_changed_slot = ++ pa_hook_connect(&stream->source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, sink_input_or_source_output_proplist_changed_cb, stream); ++ break; ++ } ++ ++ stream->client_proplist_changed_slot = ++ pa_hook_connect(&stream->creator->volume_api->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], ++ PA_HOOK_NORMAL, client_proplist_changed_cb, stream); ++ ++ return stream; ++} ++ ++static void stream_put(struct stream *stream) { ++ pa_proplist *proplist = NULL; ++ ++ pa_assert(stream); ++ ++ switch (stream->type) { ++ case STREAM_TYPE_SINK_INPUT: ++ proplist = stream->sink_input->proplist; ++ break; ++ ++ case STREAM_TYPE_SOURCE_OUTPUT: ++ proplist = stream->source_output->proplist; ++ break; ++ } ++ ++ pas_stream_put(stream->stream, proplist); ++} ++ ++static void stream_unlink(struct stream *stream) { ++ pa_assert(stream); ++ ++ if (stream->unlinked) ++ return; ++ ++ stream->unlinked = true; ++ ++ if (stream->stream) ++ pas_stream_unlink(stream->stream); ++} ++ ++static void stream_free(struct stream *stream) { ++ pa_assert(stream); ++ ++ if (!stream->unlinked) ++ stream_unlink(stream); ++ ++ if (stream->client_proplist_changed_slot) ++ pa_hook_slot_free(stream->client_proplist_changed_slot); ++ ++ if (stream->proplist_changed_slot) ++ pa_hook_slot_free(stream->proplist_changed_slot); ++ ++ if (stream->stream) ++ pas_stream_free(stream->stream); ++ ++ pa_xfree(stream); ++} ++ ++static void create_stream(pa_stream_creator *creator, enum stream_type type, void *core_stream) { ++ struct stream *stream; ++ ++ pa_assert(creator); ++ pa_assert(core_stream); ++ ++ stream = stream_new(creator, type, core_stream); ++ pa_hashmap_put(creator->streams, core_stream, stream); ++ stream_put(stream); ++} ++ ++static pa_hook_result_t sink_input_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_sink_input *input = call_data; ++ ++ pa_assert(creator); ++ pa_assert(input); ++ ++ create_stream(creator, STREAM_TYPE_SINK_INPUT, input); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t sink_input_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_sink_input *input = call_data; ++ ++ pa_assert(creator); ++ pa_assert(input); ++ ++ pa_hashmap_remove_and_free(creator->streams, input); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_output_put_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_source_output *output = call_data; ++ ++ pa_assert(creator); ++ pa_assert(output); ++ ++ create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output); ++ ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t source_output_unlink_cb(void *hook_data, void *call_data, void *userdata) { ++ pa_stream_creator *creator = userdata; ++ pa_source_output *output = call_data; ++ ++ pa_assert(creator); ++ pa_assert(output); ++ ++ pa_hashmap_remove_and_free(creator->streams, output); ++ ++ return PA_HOOK_OK; ++} ++ ++pa_stream_creator *pa_stream_creator_new(pa_volume_api *api) { ++ pa_stream_creator *creator; ++ uint32_t idx; ++ pa_sink_input *input; ++ pa_source_output *output; ++ ++ pa_assert(api); ++ ++ creator = pa_xnew0(pa_stream_creator, 1); ++ creator->volume_api = api; ++ creator->streams = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) stream_free); ++ creator->sink_input_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_NORMAL, ++ sink_input_put_cb, creator); ++ creator->sink_input_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_NORMAL, ++ sink_input_unlink_cb, creator); ++ creator->source_output_put_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, ++ source_output_put_cb, creator); ++ creator->source_output_unlink_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_NORMAL, ++ source_output_unlink_cb, creator); ++ ++ PA_IDXSET_FOREACH(input, api->core->sink_inputs, idx) ++ create_stream(creator, STREAM_TYPE_SINK_INPUT, input); ++ ++ PA_IDXSET_FOREACH(output, api->core->source_outputs, idx) ++ create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output); ++ ++ return creator; ++} ++ ++void pa_stream_creator_free(pa_stream_creator *creator) { ++ pa_assert(creator); ++ ++ if (creator->streams) ++ pa_hashmap_remove_all(creator->streams); ++ ++ if (creator->source_output_unlink_slot) ++ pa_hook_slot_free(creator->source_output_unlink_slot); ++ ++ if (creator->source_output_put_slot) ++ pa_hook_slot_free(creator->source_output_put_slot); ++ ++ if (creator->sink_input_unlink_slot) ++ pa_hook_slot_free(creator->sink_input_unlink_slot); ++ ++ if (creator->sink_input_put_slot) ++ pa_hook_slot_free(creator->sink_input_put_slot); ++ ++ if (creator->streams) ++ pa_hashmap_free(creator->streams); ++ ++ pa_xfree(creator); ++} +diff --git a/src/modules/volume-api/stream-creator.h b/src/modules/volume-api/stream-creator.h +new file mode 100644 +index 0000000..97a03a4 +--- /dev/null ++++ b/src/modules/volume-api/stream-creator.h +@@ -0,0 +1,32 @@ ++#ifndef foostreamcreatorhfoo ++#define foostreamcreatorhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/volume-api.h> ++ ++typedef struct pa_stream_creator pa_stream_creator; ++ ++pa_stream_creator *pa_stream_creator_new(pa_volume_api *api); ++void pa_stream_creator_free(pa_stream_creator *creator); ++ ++#endif +diff --git a/src/modules/volume-api/volume-api.c b/src/modules/volume-api/volume-api.c +new file mode 100644 +index 0000000..9abea7e +--- /dev/null ++++ b/src/modules/volume-api/volume-api.c +@@ -0,0 +1,647 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "volume-api.h" ++ ++#include <modules/volume-api/audio-group.h> ++#include <modules/volume-api/binding.h> ++#include <modules/volume-api/device.h> ++#include <modules/volume-api/device-creator.h> ++#include <modules/volume-api/sstream.h> ++#include <modules/volume-api/stream-creator.h> ++#include <modules/volume-api/volume-control.h> ++ ++#include <pulsecore/core-util.h> ++#include <pulsecore/shared.h> ++ ++static pa_volume_api *volume_api_new(pa_core *core); ++static void volume_api_free(pa_volume_api *api); ++ ++pa_volume_api *pa_volume_api_get(pa_core *core) { ++ pa_volume_api *api; ++ ++ pa_assert(core); ++ ++ api = pa_shared_get(core, "volume-api"); ++ ++ if (api) ++ pa_volume_api_ref(api); ++ else { ++ api = volume_api_new(core); ++ pa_assert_se(pa_shared_set(core, "volume-api", api) >= 0); ++ } ++ ++ return api; ++} ++ ++pa_volume_api *pa_volume_api_ref(pa_volume_api *api) { ++ pa_assert(api); ++ ++ api->refcnt++; ++ ++ return api; ++} ++ ++void pa_volume_api_unref(pa_volume_api *api) { ++ pa_assert(api); ++ pa_assert(api->refcnt > 0); ++ ++ api->refcnt--; ++ ++ if (api->refcnt == 0) { ++ pa_assert_se(pa_shared_remove(api->core, "volume-api") >= 0); ++ volume_api_free(api); ++ } ++} ++ ++void pa_volume_api_add_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) { ++ pa_assert(api); ++ pa_assert(type); ++ ++ pa_assert_se(pa_hashmap_put(api->binding_target_types, type->name, type) >= 0); ++ ++ pa_log_debug("Added binding target type %s.", type->name); ++ ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], type); ++} ++ ++void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) { ++ pa_assert(api); ++ pa_assert(type); ++ ++ pa_log_debug("Removing binding target type %s.", type->name); ++ ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], type); ++ ++ pa_assert_se(pa_hashmap_remove(api->binding_target_types, type->name)); ++} ++ ++static void create_builtin_binding_target_types(pa_volume_api *api) { ++ pa_binding_target_type *type; ++ ++ pa_assert(api); ++ ++ type = pa_audio_group_create_binding_target_type(api); ++ pa_volume_api_add_binding_target_type(api, type); ++} ++ ++static void delete_builtin_binding_target_types(pa_volume_api *api) { ++ pa_binding_target_type *type; ++ ++ pa_assert(api); ++ ++ type = pa_hashmap_get(api->binding_target_types, PA_AUDIO_GROUP_BINDING_TARGET_TYPE); ++ pa_volume_api_remove_binding_target_type(api, type); ++} ++ ++static void create_objects_defer_event_cb(pa_mainloop_api *mainloop_api, pa_defer_event *event, void *userdata) { ++ pa_volume_api *volume_api = userdata; ++ ++ pa_assert(volume_api); ++ pa_assert(event == volume_api->create_objects_defer_event); ++ ++ mainloop_api->defer_free(event); ++ volume_api->create_objects_defer_event = NULL; ++ ++ volume_api->device_creator = pa_device_creator_new(volume_api); ++ volume_api->stream_creator = pa_stream_creator_new(volume_api); ++} ++ ++static pa_volume_api *volume_api_new(pa_core *core) { ++ pa_volume_api *api; ++ unsigned i; ++ ++ pa_assert(core); ++ ++ api = pa_xnew0(pa_volume_api, 1); ++ api->core = core; ++ api->refcnt = 1; ++ api->binding_target_types = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); ++ api->volume_controls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->mute_controls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->streams = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ api->audio_groups = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); ++ ++ for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++) ++ pa_hook_init(&api->hooks[i], api); ++ ++ create_builtin_binding_target_types(api); ++ ++ /* We delay the object creation to ensure that policy modules have a chance ++ * to affect the initialization of the objects. If we created the objects ++ * immediately, policy modules wouldn't have a chance of connecting to the ++ * object creation hooks before the objects are created. */ ++ api->create_objects_defer_event = core->mainloop->defer_new(core->mainloop, create_objects_defer_event_cb, api); ++ ++ pa_log_debug("Created a pa_volume_api object."); ++ ++ return api; ++} ++ ++static void volume_api_free(pa_volume_api *api) { ++ unsigned i; ++ ++ pa_assert(api); ++ pa_assert(api->refcnt == 0); ++ ++ pa_log_debug("Freeing the pa_volume_api object."); ++ ++ if (api->stream_creator) ++ pa_stream_creator_free(api->stream_creator); ++ ++ if (api->device_creator) ++ pa_device_creator_free(api->device_creator); ++ ++ if (api->create_objects_defer_event) ++ api->core->mainloop->defer_free(api->create_objects_defer_event); ++ ++ if (api->binding_target_types) ++ delete_builtin_binding_target_types(api); ++ ++ for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++) ++ pa_hook_done(&api->hooks[i]); ++ ++ if (api->audio_groups) { ++ pa_assert(pa_hashmap_isempty(api->audio_groups)); ++ pa_hashmap_free(api->audio_groups); ++ } ++ ++ if (api->streams) { ++ pa_assert(pa_hashmap_isempty(api->streams)); ++ pa_hashmap_free(api->streams); ++ } ++ ++ if (api->devices) { ++ pa_assert(pa_hashmap_isempty(api->devices)); ++ pa_hashmap_free(api->devices); ++ } ++ ++ if (api->mute_controls) { ++ pa_assert(pa_hashmap_isempty(api->mute_controls)); ++ pa_hashmap_free(api->mute_controls); ++ } ++ ++ if (api->volume_controls) { ++ pa_assert(pa_hashmap_isempty(api->volume_controls)); ++ pa_hashmap_free(api->volume_controls); ++ } ++ ++ if (api->names) { ++ pa_assert(pa_hashmap_isempty(api->names)); ++ pa_hashmap_free(api->names); ++ } ++ ++ if (api->binding_target_types) { ++ pa_assert(pa_hashmap_isempty(api->binding_target_types)); ++ pa_hashmap_free(api->binding_target_types); ++ } ++ ++ pa_xfree(api); ++} ++ ++int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name, bool fail_if_already_registered, ++ const char **registered_name) { ++ char *n; ++ ++ pa_assert(api); ++ pa_assert(requested_name); ++ pa_assert(registered_name); ++ ++ n = pa_xstrdup(requested_name); ++ ++ if (pa_hashmap_put(api->names, n, n) < 0) { ++ unsigned i = 1; ++ ++ pa_xfree(n); ++ ++ if (fail_if_already_registered) { ++ pa_log("Name %s already registered.", requested_name); ++ return -PA_ERR_EXIST; ++ } ++ ++ do { ++ i++; ++ n = pa_sprintf_malloc("%s.%u", requested_name, i); ++ } while (pa_hashmap_put(api->names, n, n) < 0); ++ } ++ ++ *registered_name = n; ++ ++ return 0; ++} ++ ++void pa_volume_api_unregister_name(pa_volume_api *api, const char *name) { ++ pa_assert(api); ++ pa_assert(name); ++ ++ pa_assert_se(pa_hashmap_remove_and_free(api->names, name) >= 0); ++} ++ ++uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_volume_control_index++; ++ ++ return idx; ++} ++ ++static void set_main_output_volume_control_internal(pa_volume_api *api, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_output_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_output_volume_control = control; ++ pa_log_debug("Main output volume control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED], api); ++} ++ ++static void set_main_input_volume_control_internal(pa_volume_api *api, pa_volume_control *control) { ++ pa_volume_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_input_volume_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_input_volume_control = control; ++ pa_log_debug("Main input volume control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED], api); ++} ++ ++void pa_volume_api_add_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ pa_assert_se(pa_hashmap_put(api->volume_controls, (void *) control->name, control) >= 0); ++} ++ ++int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ if (!pa_hashmap_remove(api->volume_controls, control->name)) ++ return -1; ++ ++ if (control == api->main_output_volume_control) ++ set_main_output_volume_control_internal(api, NULL); ++ ++ if (control == api->main_input_volume_control) ++ set_main_input_volume_control_internal(api, NULL); ++ ++ return 0; ++} ++ ++pa_volume_control *pa_volume_api_get_volume_control_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_volume_control *control; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(control, api->volume_controls, state) { ++ if (control->index == idx) ++ return control; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_mute_control_index++; ++ ++ return idx; ++} ++ ++static void set_main_output_mute_control_internal(pa_volume_api *api, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_output_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_output_mute_control = control; ++ pa_log_debug("Main output mute control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED], api); ++} ++ ++static void set_main_input_mute_control_internal(pa_volume_api *api, pa_mute_control *control) { ++ pa_mute_control *old_control; ++ ++ pa_assert(api); ++ ++ old_control = api->main_input_mute_control; ++ ++ if (control == old_control) ++ return; ++ ++ api->main_input_mute_control = control; ++ pa_log_debug("Main input mute control changed from %s to %s.", old_control ? old_control->name : "(unset)", ++ control ? control->name : "(unset)"); ++ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED], api); ++} ++ ++void pa_volume_api_add_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ pa_assert_se(pa_hashmap_put(api->mute_controls, (void *) control->name, control) >= 0); ++} ++ ++int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ pa_assert(control); ++ ++ if (!pa_hashmap_remove(api->mute_controls, control->name)) ++ return -1; ++ ++ if (control == api->main_output_mute_control) ++ set_main_output_mute_control_internal(api, NULL); ++ ++ if (control == api->main_input_mute_control) ++ set_main_input_mute_control_internal(api, NULL); ++ ++ return 0; ++} ++ ++pa_mute_control *pa_volume_api_get_mute_control_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_mute_control *control; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(control, api->mute_controls, state) { ++ if (control->index == idx) ++ return control; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_device_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_device_index++; ++ ++ return idx; ++} ++ ++void pa_volume_api_add_device(pa_volume_api *api, pa_device *device) { ++ pa_assert(api); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(api->devices, (void *) device->name, device) >= 0); ++} ++ ++int pa_volume_api_remove_device(pa_volume_api *api, pa_device *device) { ++ pa_assert(api); ++ pa_assert(device); ++ ++ if (!pa_hashmap_remove(api->devices, device->name)) ++ return -1; ++ ++ return 0; ++} ++ ++pa_device *pa_volume_api_get_device_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_device *device; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(device, api->devices, state) { ++ if (device->index == idx) ++ return device; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_stream_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_stream_index++; ++ ++ return idx; ++} ++ ++void pa_volume_api_add_stream(pa_volume_api *api, pas_stream *stream) { ++ pa_assert(api); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(api->streams, (void *) stream->name, stream) >= 0); ++} ++ ++int pa_volume_api_remove_stream(pa_volume_api *api, pas_stream *stream) { ++ pa_assert(api); ++ pa_assert(stream); ++ ++ if (!pa_hashmap_remove(api->streams, stream->name)) ++ return -1; ++ ++ return 0; ++} ++ ++pas_stream *pa_volume_api_get_stream_by_index(pa_volume_api *api, uint32_t idx) { ++ pas_stream *stream; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(stream, api->streams, state) { ++ if (stream->index == idx) ++ return stream; ++ } ++ ++ return NULL; ++} ++ ++uint32_t pa_volume_api_allocate_audio_group_index(pa_volume_api *api) { ++ uint32_t idx; ++ ++ pa_assert(api); ++ ++ idx = api->next_audio_group_index++; ++ ++ return idx; ++} ++ ++void pa_volume_api_add_audio_group(pa_volume_api *api, pa_audio_group *group) { ++ pa_assert(api); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_put(api->audio_groups, (void *) group->name, group) >= 0); ++} ++ ++int pa_volume_api_remove_audio_group(pa_volume_api *api, pa_audio_group *group) { ++ pa_assert(api); ++ pa_assert(group); ++ ++ if (!pa_hashmap_remove(api->audio_groups, group->name)) ++ return -1; ++ ++ return 0; ++} ++ ++pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint32_t idx) { ++ pa_audio_group *group; ++ void *state; ++ ++ pa_assert(api); ++ ++ PA_HASHMAP_FOREACH(group, api->audio_groups, state) { ++ if (group->index == idx) ++ return group; ++ } ++ ++ return NULL; ++} ++ ++void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ ++ if (api->main_output_volume_control_binding) { ++ pa_binding_free(api->main_output_volume_control_binding); ++ api->main_output_volume_control_binding = NULL; ++ } ++ ++ set_main_output_volume_control_internal(api, control); ++} ++ ++void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control) { ++ pa_assert(api); ++ ++ if (api->main_input_volume_control_binding) { ++ pa_binding_free(api->main_input_volume_control_binding); ++ api->main_input_volume_control_binding = NULL; ++ } ++ ++ set_main_input_volume_control_internal(api, control); ++} ++ ++void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ ++ if (api->main_output_mute_control_binding) { ++ pa_binding_free(api->main_output_mute_control_binding); ++ api->main_output_mute_control_binding = NULL; ++ } ++ ++ set_main_output_mute_control_internal(api, control); ++} ++ ++void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control) { ++ pa_assert(api); ++ ++ if (api->main_input_mute_control_binding) { ++ pa_binding_free(api->main_input_mute_control_binding); ++ api->main_input_mute_control_binding = NULL; ++ } ++ ++ set_main_input_mute_control_internal(api, control); ++} ++ ++void pa_volume_api_bind_main_output_volume_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_output_volume_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_output_volume_control_binding) ++ pa_binding_free(api->main_output_volume_control_binding); ++ ++ api->main_output_volume_control_binding = pa_binding_new(api, &owner_info, target_info); ++} ++ ++void pa_volume_api_bind_main_input_volume_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_input_volume_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_input_volume_control_binding) ++ pa_binding_free(api->main_input_volume_control_binding); ++ ++ api->main_input_volume_control_binding = pa_binding_new(api, &owner_info, target_info); ++} ++ ++void pa_volume_api_bind_main_output_mute_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_output_mute_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_output_mute_control_binding) ++ pa_binding_free(api->main_output_mute_control_binding); ++ ++ api->main_output_mute_control_binding = pa_binding_new(api, &owner_info, target_info); ++} ++ ++void pa_volume_api_bind_main_input_mute_control(pa_volume_api *api, pa_binding_target_info *target_info) { ++ pa_binding_owner_info owner_info = { ++ .userdata = api, ++ .set_value = (pa_binding_set_value_cb_t) set_main_input_mute_control_internal, ++ }; ++ ++ pa_assert(api); ++ pa_assert(target_info); ++ ++ if (api->main_input_mute_control_binding) ++ pa_binding_free(api->main_input_mute_control_binding); ++ ++ api->main_input_mute_control_binding = pa_binding_new(api, &owner_info, target_info); ++} +diff --git a/src/modules/volume-api/volume-api.h b/src/modules/volume-api/volume-api.h +new file mode 100644 +index 0000000..73a1410 +--- /dev/null ++++ b/src/modules/volume-api/volume-api.h +@@ -0,0 +1,163 @@ ++#ifndef foovolumeapihfoo ++#define foovolumeapihfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <pulsecore/core.h> ++ ++typedef struct pa_volume_api pa_volume_api; ++ ++/* Avoid circular dependencies... */ ++typedef struct pa_audio_group pa_audio_group; ++typedef struct pa_binding pa_binding; ++typedef struct pa_binding_target_info pa_binding_target_info; ++typedef struct pa_binding_target_type pa_binding_target_type; ++typedef struct pa_device pa_device; ++typedef struct pa_device_creator pa_device_creator; ++typedef struct pa_mute_control pa_mute_control; ++typedef struct pas_stream pas_stream; ++typedef struct pa_stream_creator pa_stream_creator; ++typedef struct pa_volume_control pa_volume_control; ++ ++enum { ++ PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED, ++ PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED, ++ PA_VOLUME_API_HOOK_DEVICE_PUT, ++ PA_VOLUME_API_HOOK_DEVICE_UNLINK, ++ PA_VOLUME_API_HOOK_DEVICE_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_DEVICE_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_DEVICE_MUTE_CONTROL_CHANGED, ++ ++ /* Policy modules can use this to set the initial volume control for a ++ * stream. The hook callback should use pas_stream_set_volume_control() to ++ * set the volume control. The hook callback should not do anything if ++ * stream->volume_control is already non-NULL. */ ++ PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL, ++ ++ /* Policy modules can use this to set the initial mute control for a ++ * stream. The hook callback should use pas_stream_set_mute_control() to ++ * set the mute control. The hook callback should not do anything if ++ * stream->mute_control is already non-NULL. */ ++ PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL, ++ ++ PA_VOLUME_API_HOOK_STREAM_PUT, ++ PA_VOLUME_API_HOOK_STREAM_UNLINK, ++ PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED, ++ PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED, ++ PA_VOLUME_API_HOOK_MAX ++}; ++ ++struct pa_volume_api { ++ pa_core *core; ++ unsigned refcnt; ++ pa_hashmap *binding_target_types; /* name -> pa_binding_target_type */ ++ pa_hashmap *names; /* object name -> object name (hashmap-as-a-set) */ ++ pa_hashmap *volume_controls; /* name -> pa_volume_control */ ++ pa_hashmap *mute_controls; /* name -> pa_mute_control */ ++ pa_hashmap *devices; /* name -> pa_device */ ++ pa_hashmap *streams; /* name -> pas_stream */ ++ pa_hashmap *audio_groups; /* name -> pa_audio_group */ ++ pa_volume_control *main_output_volume_control; ++ pa_volume_control *main_input_volume_control; ++ pa_mute_control *main_output_mute_control; ++ pa_mute_control *main_input_mute_control; ++ ++ uint32_t next_volume_control_index; ++ uint32_t next_mute_control_index; ++ uint32_t next_device_index; ++ uint32_t next_stream_index; ++ uint32_t next_audio_group_index; ++ pa_binding *main_output_volume_control_binding; ++ pa_binding *main_input_volume_control_binding; ++ pa_binding *main_output_mute_control_binding; ++ pa_binding *main_input_mute_control_binding; ++ pa_hook hooks[PA_VOLUME_API_HOOK_MAX]; ++ pa_defer_event *create_objects_defer_event; ++ pa_device_creator *device_creator; ++ pa_stream_creator *stream_creator; ++}; ++ ++pa_volume_api *pa_volume_api_get(pa_core *core); ++pa_volume_api *pa_volume_api_ref(pa_volume_api *api); ++void pa_volume_api_unref(pa_volume_api *api); ++ ++void pa_volume_api_add_binding_target_type(pa_volume_api *api, pa_binding_target_type *type); ++void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type); ++ ++/* If fail_if_already_registered is false, this function never fails. */ ++int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name, bool fail_if_already_registered, ++ const char **registered_name); ++ ++void pa_volume_api_unregister_name(pa_volume_api *api, const char *name); ++ ++uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api); ++void pa_volume_api_add_volume_control(pa_volume_api *api, pa_volume_control *control); ++int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *control); ++pa_volume_control *pa_volume_api_get_volume_control_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api); ++void pa_volume_api_add_mute_control(pa_volume_api *api, pa_mute_control *control); ++int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *control); ++pa_mute_control *pa_volume_api_get_mute_control_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_device_index(pa_volume_api *api); ++void pa_volume_api_add_device(pa_volume_api *api, pa_device *device); ++int pa_volume_api_remove_device(pa_volume_api *api, pa_device *device); ++pa_device *pa_volume_api_get_device_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_stream_index(pa_volume_api *api); ++void pa_volume_api_add_stream(pa_volume_api *api, pas_stream *stream); ++int pa_volume_api_remove_stream(pa_volume_api *api, pas_stream *stream); ++pas_stream *pa_volume_api_get_stream_by_index(pa_volume_api *api, uint32_t idx); ++ ++uint32_t pa_volume_api_allocate_audio_group_index(pa_volume_api *api); ++void pa_volume_api_add_audio_group(pa_volume_api *api, pa_audio_group *group); ++int pa_volume_api_remove_audio_group(pa_volume_api *api, pa_audio_group *group); ++pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint32_t idx); ++ ++void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control); ++void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control); ++void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control); ++void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control); ++void pa_volume_api_bind_main_output_volume_control(pa_volume_api *api, pa_binding_target_info *target_info); ++void pa_volume_api_bind_main_input_volume_control(pa_volume_api *api, pa_binding_target_info *target_info); ++void pa_volume_api_bind_main_output_mute_control(pa_volume_api *api, pa_binding_target_info *target_info); ++void pa_volume_api_bind_main_input_mute_control(pa_volume_api *api, pa_binding_target_info *target_info); ++ ++#endif +diff --git a/src/modules/volume-api/volume-control.c b/src/modules/volume-api/volume-control.c +new file mode 100644 +index 0000000..c7f5dbb +--- /dev/null ++++ b/src/modules/volume-api/volume-control.c +@@ -0,0 +1,363 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "volume-control.h" ++ ++#include <modules/volume-api/audio-group.h> ++#include <modules/volume-api/device.h> ++#include <modules/volume-api/sstream.h> ++ ++#include <pulsecore/core-util.h> ++ ++pa_volume_control *pa_volume_control_new(pa_volume_api *api, const char *name, const char *description, bool convertible_to_dB, ++ bool channel_map_is_writable) { ++ pa_volume_control *control; ++ ++ pa_assert(api); ++ pa_assert(name); ++ pa_assert(description); ++ ++ control = pa_xnew0(pa_volume_control, 1); ++ control->volume_api = api; ++ control->index = pa_volume_api_allocate_volume_control_index(api); ++ pa_assert_se(pa_volume_api_register_name(api, name, false, &control->name) >= 0); ++ control->description = pa_xstrdup(description); ++ control->proplist = pa_proplist_new(); ++ pa_bvolume_init_invalid(&control->volume); ++ control->convertible_to_dB = convertible_to_dB; ++ control->channel_map_is_writable = channel_map_is_writable; ++ control->devices = pa_hashmap_new(NULL, NULL); ++ control->default_for_devices = pa_hashmap_new(NULL, NULL); ++ control->streams = pa_hashmap_new(NULL, NULL); ++ control->audio_groups = pa_hashmap_new(NULL, NULL); ++ ++ return control; ++} ++ ++void pa_volume_control_put(pa_volume_control *control, const pa_bvolume *initial_volume, ++ pa_volume_control_set_initial_volume_cb_t set_initial_volume_cb) { ++ const char *prop_key; ++ void *state = NULL; ++ char volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; ++ char balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; ++ ++ pa_assert(control); ++ pa_assert((initial_volume && pa_bvolume_valid(initial_volume, true, true)) || control->set_volume); ++ pa_assert((initial_volume && pa_channel_map_valid(&initial_volume->channel_map)) || control->channel_map_is_writable); ++ pa_assert(set_initial_volume_cb || !control->set_volume); ++ ++ if (initial_volume && pa_bvolume_valid(initial_volume, true, false)) ++ control->volume.volume = initial_volume->volume; ++ else ++ control->volume.volume = PA_VOLUME_NORM / 3; ++ ++ if (initial_volume && pa_bvolume_valid(initial_volume, false, true)) ++ pa_bvolume_copy_balance(&control->volume, initial_volume); ++ else if (initial_volume && pa_channel_map_valid(&initial_volume->channel_map)) ++ pa_bvolume_reset_balance(&control->volume, &initial_volume->channel_map); ++ else { ++ pa_channel_map_init_mono(&control->volume.channel_map); ++ pa_bvolume_reset_balance(&control->volume, &control->volume.channel_map); ++ } ++ ++ if (set_initial_volume_cb) ++ set_initial_volume_cb(control); ++ ++ pa_volume_api_add_volume_control(control->volume_api, control); ++ ++ control->linked = true; ++ ++ pa_log_debug("Created volume control #%u.", control->index); ++ pa_log_debug(" Name: %s", control->name); ++ pa_log_debug(" Description: %s", control->description); ++ pa_log_debug(" Properties:"); ++ ++ while ((prop_key = pa_proplist_iterate(control->proplist, &state))) ++ pa_log_debug(" %s = %s", prop_key, pa_strnull(pa_proplist_gets(control->proplist, prop_key))); ++ ++ pa_log_debug(" Volume: %s", pa_volume_snprint_verbose(volume_str, sizeof(volume_str), control->volume.volume, ++ control->convertible_to_dB)); ++ pa_log_debug(" Balance: %s", pa_bvolume_snprint_balance(balance_str, sizeof(balance_str), &control->volume)); ++ pa_log_debug(" Channel map is writable: %s", pa_yes_no(control->channel_map_is_writable)); ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT], control); ++} ++ ++void pa_volume_control_unlink(pa_volume_control *control) { ++ pa_audio_group *group; ++ pa_device *device; ++ pas_stream *stream; ++ ++ pa_assert(control); ++ ++ if (control->unlinked) { ++ pa_log_debug("Unlinking volume control %s (already unlinked, this is a no-op).", control->name); ++ return; ++ } ++ ++ control->unlinked = true; ++ ++ pa_log_debug("Unlinking volume control %s.", control->name); ++ ++ if (control->linked) ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK], control); ++ ++ pa_volume_api_remove_volume_control(control->volume_api, control); ++ ++ while ((group = pa_hashmap_first(control->audio_groups))) ++ pa_audio_group_set_volume_control(group, NULL); ++ ++ while ((stream = pa_hashmap_first(control->streams))) ++ pas_stream_set_volume_control(stream, NULL); ++ ++ while ((device = pa_hashmap_first(control->default_for_devices))) ++ pa_device_set_default_volume_control(device, NULL); ++ ++ while ((device = pa_hashmap_first(control->devices))) { ++ /* Why do we have this assertion here? The concern is that if we call ++ * pa_device_set_volume_control() for some device that has the ++ * use_default_volume_control flag set, then that flag will be unset as ++ * a side effect, and we don't want that side effect. This assertion ++ * should be safe, because we just called ++ * pa_device_set_default_volume_control(NULL) for each device that this ++ * control was the default for, and that should ensure that we don't ++ * any more hold any references to devices that used to use this ++ * control as the default. */ ++ pa_assert(!device->use_default_volume_control); ++ pa_device_set_volume_control(device, NULL); ++ } ++} ++ ++void pa_volume_control_free(pa_volume_control *control) { ++ pa_assert(control); ++ ++ if (!control->unlinked) ++ pa_volume_control_unlink(control); ++ ++ if (control->audio_groups) { ++ pa_assert(pa_hashmap_isempty(control->audio_groups)); ++ pa_hashmap_free(control->audio_groups); ++ } ++ ++ if (control->streams) { ++ pa_assert(pa_hashmap_isempty(control->streams)); ++ pa_hashmap_free(control->streams); ++ } ++ ++ if (control->default_for_devices) { ++ pa_assert(pa_hashmap_isempty(control->default_for_devices)); ++ pa_hashmap_free(control->default_for_devices); ++ } ++ ++ if (control->devices) { ++ pa_assert(pa_hashmap_isempty(control->devices)); ++ pa_hashmap_free(control->devices); ++ } ++ ++ if (control->proplist) ++ pa_proplist_free(control->proplist); ++ ++ pa_xfree(control->description); ++ ++ if (control->name) ++ pa_volume_api_unregister_name(control->volume_api, control->name); ++ ++ pa_xfree(control); ++} ++ ++void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ control->owner_audio_group = group; ++} ++ ++static void set_volume_internal(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ pa_bvolume old_volume; ++ bool volume_changed; ++ bool balance_changed; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ old_volume = control->volume; ++ volume_changed = !pa_bvolume_equal(volume, &old_volume, set_volume, false); ++ balance_changed = !pa_bvolume_equal(volume, &old_volume, false, set_balance); ++ ++ if (!volume_changed && !balance_changed) ++ return; ++ ++ if (volume_changed) ++ control->volume.volume = volume->volume; ++ ++ if (balance_changed) ++ pa_bvolume_copy_balance(&control->volume, volume); ++ ++ if (!control->linked || control->unlinked) ++ return; ++ ++ if (volume_changed) { ++ char old_volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; ++ char new_volume_str[PA_VOLUME_SNPRINT_VERBOSE_MAX]; ++ ++ pa_log_debug("The volume of volume control %s changed from %s to %s.", control->name, ++ pa_volume_snprint_verbose(old_volume_str, sizeof(old_volume_str), old_volume.volume, ++ control->convertible_to_dB), ++ pa_volume_snprint_verbose(new_volume_str, sizeof(new_volume_str), control->volume.volume, ++ control->convertible_to_dB)); ++ } ++ ++ if (balance_changed) { ++ char old_balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; ++ char new_balance_str[PA_BVOLUME_SNPRINT_BALANCE_MAX]; ++ ++ pa_log_debug("The balance of volume control %s changed from %s to %s.", control->name, ++ pa_bvolume_snprint_balance(old_balance_str, sizeof(old_balance_str), &control->volume), ++ pa_bvolume_snprint_balance(new_balance_str, sizeof(new_balance_str), &control->volume)); ++ } ++ ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED], control); ++} ++ ++int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) { ++ pa_bvolume volume_local; ++ int r; ++ ++ pa_assert(control); ++ pa_assert(volume); ++ ++ volume_local = *volume; ++ ++ if (!control->set_volume) { ++ pa_log_info("Tried to set the volume of volume control %s, but the volume control doesn't support the operation.", ++ control->name); ++ return -PA_ERR_NOTSUPPORTED; ++ } ++ ++ if (set_balance ++ && !control->channel_map_is_writable ++ && !pa_channel_map_equal(&volume_local.channel_map, &control->volume.channel_map)) ++ pa_bvolume_remap(&volume_local, &control->volume.channel_map); ++ ++ if (pa_bvolume_equal(&volume_local, &control->volume, set_volume, set_balance)) ++ return 0; ++ ++ control->set_volume_in_progress = true; ++ r = control->set_volume(control, &volume_local, set_volume, set_balance); ++ control->set_volume_in_progress = false; ++ ++ if (r >= 0) ++ set_volume_internal(control, &volume_local, set_volume, set_balance); ++ ++ return r; ++} ++ ++void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description) { ++ char *old_description; ++ ++ pa_assert(control); ++ pa_assert(new_description); ++ ++ old_description = control->description; ++ ++ if (pa_streq(new_description, old_description)) ++ return; ++ ++ control->description = pa_xstrdup(new_description); ++ pa_log_debug("The description of volume control %s changed from \"%s\" to \"%s\".", control->name, old_description, ++ new_description); ++ pa_xfree(old_description); ++ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED], control); ++} ++ ++void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed, ++ bool balance_changed) { ++ pa_assert(control); ++ pa_assert(new_volume); ++ ++ if (!control->linked) ++ return; ++ ++ if (control->set_volume_in_progress) ++ return; ++ ++ set_volume_internal(control, new_volume, volume_changed, balance_changed); ++} ++ ++void pa_volume_control_add_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->devices, device, device) >= 0); ++} ++ ++void pa_volume_control_remove_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->devices, device)); ++} ++ ++void pa_volume_control_add_default_for_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_put(control->default_for_devices, device, device) >= 0); ++} ++ ++void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_device *device) { ++ pa_assert(control); ++ pa_assert(device); ++ ++ pa_assert_se(pa_hashmap_remove(control->default_for_devices, device)); ++} ++ ++void pa_volume_control_add_stream(pa_volume_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_put(control->streams, stream, stream) >= 0); ++} ++ ++void pa_volume_control_remove_stream(pa_volume_control *control, pas_stream *stream) { ++ pa_assert(control); ++ pa_assert(stream); ++ ++ pa_assert_se(pa_hashmap_remove(control->streams, stream)); ++} ++ ++void pa_volume_control_add_audio_group(pa_volume_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_put(control->audio_groups, group, group) >= 0); ++} ++ ++void pa_volume_control_remove_audio_group(pa_volume_control *control, pa_audio_group *group) { ++ pa_assert(control); ++ pa_assert(group); ++ ++ pa_assert_se(pa_hashmap_remove(control->audio_groups, group)); ++} +diff --git a/src/modules/volume-api/volume-control.h b/src/modules/volume-api/volume-control.h +new file mode 100644 +index 0000000..aaba758 +--- /dev/null ++++ b/src/modules/volume-api/volume-control.h +@@ -0,0 +1,112 @@ ++#ifndef foovolumecontrolhfoo ++#define foovolumecontrolhfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <modules/volume-api/bvolume.h> ++#include <modules/volume-api/volume-api.h> ++ ++typedef struct pa_volume_control pa_volume_control; ++ ++struct pa_volume_control { ++ pa_volume_api *volume_api; ++ uint32_t index; ++ const char *name; ++ char *description; ++ pa_proplist *proplist; ++ pa_bvolume volume; ++ bool convertible_to_dB; ++ bool channel_map_is_writable; ++ ++ /* If this volume control is the "own volume control" of an audio group, ++ * this is set to point to that group, otherwise this is NULL. */ ++ pa_audio_group *owner_audio_group; ++ ++ pa_hashmap *devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *default_for_devices; /* pa_device -> pa_device (hashmap-as-a-set) */ ++ pa_hashmap *streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */ ++ pa_hashmap *audio_groups; /* pa_audio_group -> pa_audio_group (hashmap-as-a-set) */ ++ ++ bool linked; ++ bool unlinked; ++ bool set_volume_in_progress; ++ ++ /* Called from pa_volume_control_set_volume(). The implementation is ++ * expected to return a negative error code on failure. May be NULL, if the ++ * volume control is read-only. */ ++ int (*set_volume)(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance); ++ ++ void *userdata; ++}; ++ ++pa_volume_control *pa_volume_control_new(pa_volume_api *api, const char *name, const char *description, bool convertible_to_dB, ++ bool channel_map_is_writable); ++ ++typedef void (*pa_volume_control_set_initial_volume_cb_t)(pa_volume_control *control); ++ ++/* initial_volume is the preferred initial volume of the volume control ++ * implementation. It may be NULL or partially invalid, if the implementation ++ * doesn't care about the initial state of the volume control, as long as these ++ * two rules are followed: ++ * ++ * 1) Read-only volume controls must always specify fully valid initial ++ * volume. ++ * 2) Volume controls with read-only channel map must always specify a valid ++ * channel map in initial_volume. ++ * ++ * The implementation's initial volume preference may be overridden by policy, ++ * if the volume control isn't read-only. When the final initial volume is ++ * known, the implementation is notified via set_initial_volume_cb (the volume ++ * can be read from control->volume). set_initial_volume_cb may be NULL, if the ++ * volume control is read-only. */ ++void pa_volume_control_put(pa_volume_control *control, const pa_bvolume *initial_volume, ++ pa_volume_control_set_initial_volume_cb_t set_initial_volume_cb); ++ ++void pa_volume_control_unlink(pa_volume_control *control); ++void pa_volume_control_free(pa_volume_control *control); ++ ++/* Called by audio-group.c only. */ ++void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group); ++ ++/* Called by clients and policy modules. */ ++int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance); ++ ++/* Called by the volume control implementation. */ ++void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description); ++void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed, ++ bool balance_changed); ++ ++/* Called from device.c only. */ ++void pa_volume_control_add_device(pa_volume_control *control, pa_device *device); ++void pa_volume_control_remove_device(pa_volume_control *control, pa_device *device); ++void pa_volume_control_add_default_for_device(pa_volume_control *control, pa_device *device); ++void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_device *device); ++ ++/* Called from sstream.c only. */ ++void pa_volume_control_add_stream(pa_volume_control *control, pas_stream *stream); ++void pa_volume_control_remove_stream(pa_volume_control *control, pas_stream *stream); ++ ++/* Called from audio-group.c only. */ ++void pa_volume_control_add_audio_group(pa_volume_control *control, pa_audio_group *group); ++void pa_volume_control_remove_audio_group(pa_volume_control *control, pa_audio_group *group); ++ ++#endif +diff --git a/src/pulse/ext-volume-api.c b/src/pulse/ext-volume-api.c +new file mode 100644 +index 0000000..8e93bce +--- /dev/null ++++ b/src/pulse/ext-volume-api.c +@@ -0,0 +1,275 @@ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#ifdef HAVE_CONFIG_H ++#include <config.h> ++#endif ++ ++#include "ext-volume-api.h" ++ ++#include <pulsecore/core-util.h> ++#include <pulsecore/i18n.h> ++#include <pulsecore/macro.h> ++ ++#include <math.h> ++ ++int pa_ext_volume_api_balance_valid(double balance) { ++ return balance >= 0.0 && balance <= 1.0; ++} ++ ++int pa_ext_volume_api_bvolume_valid(const pa_ext_volume_api_bvolume *volume, int check_volume, int check_balance) { ++ unsigned channel; ++ ++ pa_assert(volume); ++ ++ if (check_volume && !PA_VOLUME_IS_VALID(volume->volume)) ++ return 0; ++ ++ if (!check_balance) ++ return 1; ++ ++ if (!pa_channel_map_valid(&volume->channel_map)) ++ return 0; ++ ++ for (channel = 0; channel < volume->channel_map.channels; channel++) { ++ if (!pa_ext_volume_api_balance_valid(volume->balance[channel])) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume) { ++ unsigned i; ++ ++ pa_assert(volume); ++ ++ volume->volume = PA_VOLUME_INVALID; ++ ++ for (i = 0; i < PA_CHANNELS_MAX; i++) ++ volume->balance[i] = -1.0; ++ ++ pa_channel_map_init(&volume->channel_map); ++} ++ ++void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume) { ++ pa_assert(bvolume); ++ pa_assert(PA_VOLUME_IS_VALID(volume)); ++ ++ bvolume->volume = volume; ++ bvolume->balance[0] = 1.0; ++ pa_channel_map_init_mono(&bvolume->channel_map); ++} ++ ++int pa_ext_volume_api_bvolume_equal(const pa_ext_volume_api_bvolume *a, const pa_ext_volume_api_bvolume *b, ++ int check_volume, int check_balance) { ++ unsigned i; ++ ++ pa_assert(a); ++ pa_assert(b); ++ ++ if (check_volume && a->volume != b->volume) ++ return 0; ++ ++ if (!check_balance) ++ return 1; ++ ++ if (!pa_channel_map_equal(&a->channel_map, &b->channel_map)) ++ return 0; ++ ++ for (i = 0; i < a->channel_map.channels; i++) { ++ if (fabs(a->balance[i] - b->balance[i]) > 0.00001) ++ return 0; ++ } ++ ++ return 1; ++} ++ ++void pa_ext_volume_api_bvolume_from_cvolume(pa_ext_volume_api_bvolume *bvolume, const pa_cvolume *cvolume, ++ const pa_channel_map *map) { ++ unsigned i; ++ ++ pa_assert(bvolume); ++ pa_assert(cvolume); ++ pa_assert(map); ++ pa_assert(cvolume->channels == map->channels); ++ ++ bvolume->volume = pa_cvolume_max(cvolume); ++ bvolume->channel_map = *map; ++ ++ for (i = 0; i < map->channels; i++) { ++ if (bvolume->volume != PA_VOLUME_MUTED) ++ bvolume->balance[i] = ((double) cvolume->values[i]) / ((double) bvolume->volume); ++ else ++ bvolume->balance[i] = 1.0; ++ } ++} ++ ++void pa_ext_volume_api_bvolume_to_cvolume(const pa_ext_volume_api_bvolume *bvolume, pa_cvolume *cvolume) { ++ unsigned i; ++ ++ pa_assert(bvolume); ++ pa_assert(cvolume); ++ pa_assert(pa_ext_volume_api_bvolume_valid(bvolume, true, true)); ++ ++ cvolume->channels = bvolume->channel_map.channels; ++ ++ for (i = 0; i < bvolume->channel_map.channels; i++) ++ cvolume->values[i] = bvolume->volume * bvolume->balance[i]; ++} ++ ++void pa_ext_volume_api_bvolume_copy_balance(pa_ext_volume_api_bvolume *to, ++ const pa_ext_volume_api_bvolume *from) { ++ pa_assert(to); ++ pa_assert(from); ++ ++ memcpy(to->balance, from->balance, sizeof(from->balance)); ++ to->channel_map = from->channel_map; ++} ++ ++void pa_ext_volume_api_bvolume_reset_balance(pa_ext_volume_api_bvolume *volume, const pa_channel_map *map) { ++ unsigned i; ++ ++ pa_assert(volume); ++ pa_assert(map); ++ pa_assert(pa_channel_map_valid(map)); ++ ++ for (i = 0; i < map->channels; i++) ++ volume->balance[i] = 1.0; ++ ++ volume->channel_map = *map; ++} ++ ++void pa_ext_volume_api_bvolume_remap(pa_ext_volume_api_bvolume *volume, const pa_channel_map *to) { ++ unsigned i; ++ pa_cvolume cvolume; ++ ++ pa_assert(volume); ++ pa_assert(to); ++ pa_assert(pa_ext_volume_api_bvolume_valid(volume, false, true)); ++ pa_assert(pa_channel_map_valid(to)); ++ ++ cvolume.channels = volume->channel_map.channels; ++ ++ for (i = 0; i < cvolume.channels; i++) ++ cvolume.values[i] = volume->balance[i] * (double) PA_VOLUME_NORM; ++ ++ pa_cvolume_remap(&cvolume, &volume->channel_map, to); ++ ++ for (i = 0; i < to->channels; i++) ++ volume->balance[i] = (double) cvolume.values[i] / (double) PA_VOLUME_NORM; ++ ++ volume->channel_map = *to; ++} ++ ++double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_bvolume *volume) { ++ pa_ext_volume_api_bvolume bvolume; ++ pa_cvolume cvolume; ++ double ret; ++ ++ pa_assert(volume); ++ ++ bvolume.volume = PA_VOLUME_NORM; ++ pa_ext_volume_api_bvolume_copy_balance(&bvolume, volume); ++ pa_ext_volume_api_bvolume_to_cvolume(&bvolume, &cvolume); ++ ret = pa_cvolume_get_balance(&cvolume, &volume->channel_map); ++ ++ return ret; ++} ++ ++void pa_ext_volume_api_bvolume_set_left_right_balance(pa_ext_volume_api_bvolume *volume, double balance) { ++ pa_cvolume cvolume; ++ pa_volume_t old_volume; ++ ++ pa_assert(volume); ++ ++ if (!pa_channel_map_can_balance(&volume->channel_map)) ++ return; ++ ++ pa_cvolume_reset(&cvolume, volume->channel_map.channels); ++ pa_cvolume_set_balance(&cvolume, &volume->channel_map, balance); ++ old_volume = volume->volume; ++ pa_ext_volume_api_bvolume_from_cvolume(volume, &cvolume, &volume->channel_map); ++ volume->volume = old_volume; ++} ++ ++double pa_ext_volume_api_bvolume_get_rear_front_balance(const pa_ext_volume_api_bvolume *volume) { ++ pa_ext_volume_api_bvolume bvolume; ++ pa_cvolume cvolume; ++ double ret; ++ ++ pa_assert(volume); ++ ++ bvolume.volume = PA_VOLUME_NORM; ++ pa_ext_volume_api_bvolume_copy_balance(&bvolume, volume); ++ pa_ext_volume_api_bvolume_to_cvolume(&bvolume, &cvolume); ++ ret = pa_cvolume_get_fade(&cvolume, &volume->channel_map); ++ ++ return ret; ++} ++ ++void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume *volume, double balance) { ++ pa_cvolume cvolume; ++ pa_volume_t old_volume; ++ ++ pa_assert(volume); ++ ++ if (!pa_channel_map_can_fade(&volume->channel_map)) ++ return; ++ ++ pa_cvolume_reset(&cvolume, volume->channel_map.channels); ++ pa_cvolume_set_fade(&cvolume, &volume->channel_map, balance); ++ old_volume = volume->volume; ++ pa_ext_volume_api_bvolume_from_cvolume(volume, &cvolume, &volume->channel_map); ++ volume->volume = old_volume; ++} ++ ++char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_len, ++ const pa_ext_volume_api_bvolume *volume) { ++ char *e; ++ unsigned channel; ++ bool first = true; ++ ++ pa_assert(buf); ++ pa_assert(buf_len > 0); ++ pa_assert(volume); ++ ++ pa_init_i18n(); ++ ++ if (!pa_ext_volume_api_bvolume_valid(volume, true, true)) { ++ pa_snprintf(buf, buf_len, _("(invalid)")); ++ return buf; ++ } ++ ++ *(e = buf) = 0; ++ ++ for (channel = 0; channel < volume->channel_map.channels && buf_len > 1; channel++) { ++ buf_len -= pa_snprintf(e, buf_len, "%s%s: %u%%", ++ first ? "" : ", ", ++ pa_channel_position_to_string(volume->channel_map.map[channel]), ++ (unsigned) (volume->balance[channel] * 100 + 0.5)); ++ ++ e = strchr(e, 0); ++ first = false; ++ } ++ ++ return buf; ++} +diff --git a/src/pulse/ext-volume-api.h b/src/pulse/ext-volume-api.h +new file mode 100644 +index 0000000..36b7748 +--- /dev/null ++++ b/src/pulse/ext-volume-api.h +@@ -0,0 +1,68 @@ ++#ifndef fooextvolumeapihfoo ++#define fooextvolumeapihfoo ++ ++/*** ++ This file is part of PulseAudio. ++ ++ Copyright 2014 Intel Corporation ++ ++ PulseAudio is free software; you can redistribute it and/or modify ++ it under the terms of the GNU Lesser General Public License as published ++ by the Free Software Foundation; either version 2.1 of the License, ++ or (at your option) any later version. ++ ++ PulseAudio is distributed in the hope that it will be useful, but ++ WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public License ++ along with PulseAudio; if not, write to the Free Software ++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ++ USA. ++***/ ++ ++#include <pulse/cdecl.h> ++#include <pulse/context.h> ++#include <pulse/volume.h> ++ ++/* This API is temporary, and has no stability guarantees whatsoever. Think ++ * twice before making anything that relies on this API. This is undocumented ++ * for a reason. */ ++ ++PA_C_DECL_BEGIN ++ ++typedef struct pa_ext_volume_api_bvolume pa_ext_volume_api_bvolume; ++ ++struct pa_ext_volume_api_bvolume { ++ pa_volume_t volume; ++ double balance[PA_CHANNELS_MAX]; ++ pa_channel_map channel_map; ++}; ++ ++int pa_ext_volume_api_balance_valid(double balance) PA_GCC_CONST; ++int pa_ext_volume_api_bvolume_valid(const pa_ext_volume_api_bvolume *volume, int check_volume, int check_balance) ++ PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume); ++void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume); ++int pa_ext_volume_api_bvolume_equal(const pa_ext_volume_api_bvolume *a, const pa_ext_volume_api_bvolume *b, ++ int check_volume, int check_balance) PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_from_cvolume(pa_ext_volume_api_bvolume *bvolume, const pa_cvolume *cvolume, ++ const pa_channel_map *map); ++void pa_ext_volume_api_bvolume_to_cvolume(const pa_ext_volume_api_bvolume *bvolume, pa_cvolume *cvolume); ++void pa_ext_volume_api_bvolume_copy_balance(pa_ext_volume_api_bvolume *to, ++ const pa_ext_volume_api_bvolume *from); ++void pa_ext_volume_api_bvolume_reset_balance(pa_ext_volume_api_bvolume *volume, const pa_channel_map *map); ++void pa_ext_volume_api_bvolume_remap(pa_ext_volume_api_bvolume *volume, const pa_channel_map *to); ++double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_bvolume *volume) PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_set_left_right_balance(pa_ext_volume_api_bvolume *volume, double balance); ++double pa_ext_volume_api_bvolume_get_rear_front_balance(const pa_ext_volume_api_bvolume *volume) PA_GCC_PURE; ++void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume *volume, double balance); ++ ++#define PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX 500 ++char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_size, ++ const pa_ext_volume_api_bvolume *volume); ++ ++PA_C_DECL_END ++ ++#endif +-- +2.1.4 + +--- a/po/POTFILES.in 2016-04-13 17:40:00.818008672 +0200 ++++ b/po/POTFILES.in 2016-04-13 17:40:30.885008622 +0200 +@@ -198,3 +198,5 @@ + src/utils/padsp.c + src/utils/pasuspender.c + src/utils/pax11publish.c ++src/modules/volume-api/device-creator.c ++src/pulse/ext-volume-api.c |