-From: Tanu Kaskinen <tanu.kaskinen@linux.intel.com>
-Date: Tue, 17 Jun 2014 19:45:45 +0300
-Subject: audio-groups, main-volume-policy, volume-api: Various fixes
-
-Sorry, this is a huge unreviewable commit. Contained improvements
-include at least:
-
- * Flat volumes are now handled properly. Previously, audio groups
- controlled the absolute volume of streams if flat volume was in
- effect, which made no sense.
- * Audio group volumes are now persistent.
- * Audio group volumes are applied to new streams before the streams
- start to play, instead of after, which could cause audible
- glitches.
- * When a stream volume is changed by the user, the volume is
- propagated to the stream's audio group.
- * Fixed the handling of the "NEG" keyword in the match syntax in
- module-audio-groups. Previously the "NEG" keyword was parsed, but
- it had no effect.
-
-Change-Id: I02bad3d23b3e562c71dbc6af6f3e308089893751
----
- src/Makefile.am | 2 +-
- src/map-file | 3 +
- src/modules/audio-groups/audio-groups.conf.example | 19 +-
- src/modules/audio-groups/module-audio-groups.c | 1710 ++++++++++++++------
- .../main-volume-policy/main-volume-context.c | 184 +--
- .../main-volume-policy/main-volume-context.h | 35 +-
- .../main-volume-policy/main-volume-policy.c | 65 +-
- .../main-volume-policy.conf.example | 1 -
- .../main-volume-policy/main-volume-policy.h | 6 +-
- .../main-volume-policy/module-main-volume-policy.c | 651 +++++---
- src/modules/volume-api/audio-group.c | 288 +---
- src/modules/volume-api/audio-group.h | 29 +-
- src/modules/volume-api/binding.c | 386 -----
- src/modules/volume-api/binding.h | 128 --
- src/modules/volume-api/bvolume.h | 3 +
- src/modules/volume-api/device-creator.c | 431 +++--
- src/modules/volume-api/device.c | 54 +-
- src/modules/volume-api/device.h | 4 +-
- src/modules/volume-api/inidb.c | 553 +++++++
- src/modules/volume-api/inidb.h | 54 +
- src/modules/volume-api/module-volume-api.c | 27 +
- src/modules/volume-api/mute-control.c | 241 +--
- src/modules/volume-api/mute-control.h | 75 +-
- src/modules/volume-api/sstream.c | 313 ++--
- src/modules/volume-api/sstream.h | 56 +-
- src/modules/volume-api/stream-creator.c | 691 ++++----
- src/modules/volume-api/volume-api.c | 430 +++--
- src/modules/volume-api/volume-api.h | 79 +-
- src/modules/volume-api/volume-control.c | 308 ++--
- src/modules/volume-api/volume-control.h | 93 +-
- src/pulse/ext-volume-api.c | 100 ++
- src/pulse/ext-volume-api.h | 3 +
- src/tizen-ivi/audio-groups.conf | 23 +-
- src/tizen-ivi/main-volume-policy.conf | 1 -
- 34 files changed, 4012 insertions(+), 3034 deletions(-)
- delete mode 100644 src/modules/volume-api/binding.c
- delete mode 100644 src/modules/volume-api/binding.h
- create mode 100644 src/modules/volume-api/inidb.c
- create mode 100644 src/modules/volume-api/inidb.h
-
-diff --git a/src/Makefile.am b/src/Makefile.am
-index 9d17336..b5cf2a8 100644
---- a/src/Makefile.am
-+++ b/src/Makefile.am
-@@ -1098,10 +1098,10 @@ 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/inidb.c modules/volume-api/inidb.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 \
-diff --git a/src/map-file b/src/map-file
-index 28ea54e..cb31833 100644
---- a/src/map-file
-+++ b/src/map-file
-@@ -190,13 +190,16 @@ pa_ext_node_manager_set_subscribe_cb;
- pa_ext_echo_cancel_set_volume;
- pa_ext_echo_cancel_set_device;
- pa_ext_volume_api_balance_valid;
-+pa_ext_volume_api_bvolume_balance_to_string;
- 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;
- pa_ext_volume_api_bvolume_init_invalid;
- pa_ext_volume_api_bvolume_init_mono;
-+pa_ext_volume_api_bvolume_parse_balance;
- pa_ext_volume_api_bvolume_remap;
- pa_ext_volume_api_bvolume_reset_balance;
- pa_ext_volume_api_bvolume_set_left_right_balance;
-diff --git a/src/modules/audio-groups/audio-groups.conf.example b/src/modules/audio-groups/audio-groups.conf.example
-index 8acdb76..6aa6989 100644
---- a/src/modules/audio-groups/audio-groups.conf.example
-+++ b/src/modules/audio-groups/audio-groups.conf.example
-@@ -1,28 +1,29 @@
- [General]
--audio-groups = x-example-call-downlink-audio-group x-example-default-output-audio-group x-example-music-output-audio-group
--streams = phone-output music-output default-output
-+stream-rules = phone-output music-output default-output
-
- [AudioGroup x-example-call-downlink-audio-group]
--volume-control = create
--mute-control = none
-+volume-control = create:call-downlink-volume-control
-+mute-control = create:call-downlink-mute-control
-
- [AudioGroup x-example-default-output-audio-group]
--volume-control = create
--mute-control = none
-+volume-control = create:default-output-volume-control
-+mute-control = create:call-downlink-mute-control
-
- [AudioGroup x-example-music-output-audio-group]
- volume-control = bind:AudioGroup:x-example-default-output-audio-group
-+mute-control = bind:AudioGroup:x-example-default-output-audio-group
-
--[Stream phone-output]
-+[StreamRule phone-output]
- match = (direction output AND property media.role=phone)
- audio-group-for-volume = x-example-call-downlink-audio-group
- audio-group-for-mute = x-example-call-downlink-audio-group
-
--[Stream music-output]
-+[StreamRule music-output]
- match = (direction output AND property media.role=music)
- audio-group-for-volume = x-example-music-output-audio-group
- audio-group-for-mute = x-example-music-output-audio-group
-
--[Stream default-output]
-+[StreamRule default-output]
-+match = (direction output)
- audio-group-for-volume = x-example-default-output-audio-group
- audio-group-for-mute = x-example-default-output-audio-group
-diff --git a/src/modules/audio-groups/module-audio-groups.c b/src/modules/audio-groups/module-audio-groups.c
-index 2b3a570..e37a24e 100644
---- a/src/modules/audio-groups/module-audio-groups.c
-+++ b/src/modules/audio-groups/module-audio-groups.c
-@@ -40,15 +40,18 @@
-
- #include "module-audio-groups-symdef.h"
-
-+#define AUDIOGROUP_START "AudioGroup "
-+#define STREAM_RULE_START "StreamRule "
-+#define NONE_KEYWORD "none"
-+#define CREATE_PREFIX "create:"
-+#define BIND_PREFIX "bind:"
-+#define BIND_AUDIO_GROUP_PREFIX BIND_PREFIX "AudioGroup:"
-+
- PA_MODULE_AUTHOR("Ismo Puustinen");
- PA_MODULE_DESCRIPTION("Create audio groups and classify streams to them");
- PA_MODULE_VERSION(PACKAGE_VERSION);
- PA_MODULE_LOAD_ONCE(true);
-
--#ifndef AUDIO_GROUP_CONFIG
--#define AUDIO_GROUP_CONFIG "audio-groups.conf"
--#endif
--
- enum match_direction {
- match_direction_unknown = 0,
- match_direction_input,
-@@ -80,60 +83,103 @@ struct expression {
- PA_LLIST_HEAD(struct conjunction, conjunctions);
- };
-
--/* data gathered from settings */
-+struct group {
-+ struct userdata *userdata;
-+ pa_audio_group *audio_group;
-+ struct control *volume_control;
-+ struct control *mute_control;
-+ char *own_volume_control_name;
-+ char *own_mute_control_name;
-+ struct group *volume_master;
-+ struct group *mute_master;
-+ char *volume_master_name;
-+ char *mute_master_name;
-+
-+ pa_hashmap *volume_slaves; /* struct group -> struct group (hashmap-as-a-set) */
-+ pa_hashmap *mute_slaves; /* struct group -> struct group (hashmap-as-a-set) */
-+ pa_hashmap *volume_stream_rules; /* struct stream_rule -> struct stream_rule (hashmap-as-a-set) */
-+ pa_hashmap *mute_stream_rules; /* struct stream_rule -> struct stream_rule (hashmap-as-a-set) */
-+
-+ bool unlinked;
-+};
-
--enum control_action {
-- CONTROL_ACTION_NONE,
-- CONTROL_ACTION_CREATE,
-- CONTROL_ACTION_BIND,
-+enum control_type {
-+ CONTROL_TYPE_VOLUME,
-+ CONTROL_TYPE_MUTE,
- };
-
--struct audio_group {
-+struct control {
- struct userdata *userdata;
-- char *id;
-- char *description;
-- enum control_action volume_control_action;
-- enum control_action mute_control_action;
-- pa_binding_target_info *volume_control_target_info;
-- pa_binding_target_info *mute_control_target_info;
-+ enum control_type type;
-+
-+ union {
-+ pa_volume_control *volume_control;
-+ pa_mute_control *mute_control;
-+ };
-+
-+ /* Controls that are created for streams don't own their pa_volume_control
-+ * and pa_mute_control objects, because they're owned by the streams. */
-+ bool own_control;
-
-- /* official audio group */
-- pa_audio_group *group;
-+ /* If non-NULL, then this control mirrors the state of the master
-+ * control. If someone changes the master state, the state of this control
-+ * is also updated, and also if someone changes this control's state, the
-+ * change is applied also to the master. */
-+ struct control *master;
-
-- struct audio_group_control *volume_control;
-- struct audio_group_control *mute_control;
-+ /* struct control -> struct control (hashmap-as-a-set)
-+ * Contains the controls that have this control as their master. */
-+ pa_hashmap *slaves;
-
-+ /* Set to true when the master control's state has been copied to this
-+ * control. */
-+ bool synced_with_master;
-+
-+ bool acquired;
- bool unlinked;
- };
-
--struct stream {
-+struct stream_rule {
- struct userdata *userdata;
-- char *id;
-+ char *name;
- enum match_direction direction;
- char *audio_group_name_for_volume;
- char *audio_group_name_for_mute;
-- pa_audio_group *audio_group_for_volume;
-- pa_audio_group *audio_group_for_mute;
-- pa_binding_target_info *volume_control_target_info;
-- pa_binding_target_info *mute_control_target_info;
-- struct expression *rule;
--
-- bool unlinked;
-+ struct group *group_for_volume;
-+ struct group *group_for_mute;
-+ struct expression *match_expression;
- };
-
- struct userdata {
-- pa_hashmap *audio_groups; /* name -> struct audio_group */
-- pa_dynarray *streams; /* struct stream */
-- pa_hook_slot *new_stream_volume;
-- pa_hook_slot *new_stream_mute;
--
-- pa_volume_api *api;
--
-- /* The following fields are only used during initialization. */
-- pa_hashmap *audio_group_names; /* name -> name (hashmap-as-a-set) */
-- pa_hashmap *unused_audio_groups; /* name -> struct audio_group */
-- pa_dynarray *stream_names;
-- pa_hashmap *unused_streams; /* name -> struct stream */
-+ pa_volume_api *volume_api;
-+ pa_hashmap *groups; /* name -> struct group */
-+ pa_hashmap *stream_rules; /* name -> struct stream_rule */
-+ pa_dynarray *stream_rules_list; /* struct stream_rule */
-+
-+ /* pas_stream -> struct stream_rule
-+ * When a stream matches with a rule, it's added here. */
-+ pa_hashmap *rules_by_stream;
-+
-+ /* pas_stream -> struct control
-+ * Contains proxy controls for all relative volume controls of streams. */
-+ pa_hashmap *stream_volume_controls;
-+
-+ /* pas_stream -> struct control
-+ * Contains proxy controls for all mute controls of streams. */
-+ pa_hashmap *stream_mute_controls;
-+
-+ pa_hook_slot *stream_put_slot;
-+ pa_hook_slot *stream_unlink_slot;
-+ pa_hook_slot *volume_control_implementation_initialized_slot;
-+ pa_hook_slot *mute_control_implementation_initialized_slot;
-+ pa_hook_slot *volume_control_set_initial_volume_slot;
-+ pa_hook_slot *mute_control_set_initial_mute_slot;
-+ pa_hook_slot *volume_control_volume_changed_slot;
-+ pa_hook_slot *mute_control_mute_changed_slot;
-+ pa_hook_slot *volume_control_unlink_slot;
-+ pa_hook_slot *mute_control_unlink_slot;
-+
-+ pa_dynarray *stream_rule_names; /* Only used during initialization. */
- };
-
- static const char* const valid_modargs[] = {
-@@ -141,77 +187,408 @@ static const char* const valid_modargs[] = {
- NULL
- };
-
--static void audio_group_unlink(struct audio_group *group);
-+static void control_free(struct control *control);
-+static void control_set_master(struct control *control, struct control *master);
-+static void control_add_slave(struct control *control, struct control *slave);
-+static void control_remove_slave(struct control *control, struct control *slave);
-
--static void print_literal(struct literal *l);
--static void print_conjunction(struct conjunction *c);
--static void print_expression(struct expression *e);
--static void delete_expression(struct expression *e);
-+static void group_free(struct group *group);
-+static void group_set_master(struct group *group, enum control_type type, struct group *master);
-+static int group_set_master_name(struct group *group, enum control_type type, const char *name);
-+static void group_disable_control(struct group *group, enum control_type type);
-+static void group_add_slave(struct group *group, enum control_type type, struct group *slave);
-+static void group_remove_slave(struct group *group, enum control_type type, struct group *slave);
-
--static struct audio_group *audio_group_new(struct userdata *u, const char *name) {
-- struct audio_group *group;
-+static void stream_rule_set_group(struct stream_rule *rule, enum control_type type, struct group *group);
-+static void stream_rule_set_group_name(struct stream_rule *rule, enum control_type type, const char *name);
-
-- pa_assert(u);
-- pa_assert(name);
-+static bool literal_match(struct literal *literal, pas_stream *stream);
-
-- group = pa_xnew0(struct audio_group, 1);
-- group->userdata = u;
-- group->id = pa_xstrdup(name);
-- group->description = pa_xstrdup(name);
-- group->volume_control_action = CONTROL_ACTION_NONE;
-- group->mute_control_action = CONTROL_ACTION_NONE;
-+static struct expression *expression_new(void);
-+static void expression_free(struct expression *expression);
-+
-+static int volume_control_set_volume_cb(pa_volume_control *volume_control, const pa_bvolume *original_volume,
-+ const pa_bvolume *remapped_volume, bool set_volume, bool set_balance) {
-+ struct control *control;
-+ struct control *slave;
-+ void *state;
-+
-+ pa_assert(volume_control);
-+ pa_assert(original_volume);
-+ pa_assert(remapped_volume);
-+
-+ control = volume_control->userdata;
-+
-+ /* There are four cases that need to be considered:
-+ *
-+ * 1) The master control is propagating the volume to this control. We need
-+ * to propagate the volume downstream.
-+ *
-+ * 2) This control was just assigned a master control and the volume hasn't
-+ * yet been synchronized. In this case the volume that is now being set for
-+ * this control is the master control's volume. We need to propagate the
-+ * volume downstream.
-+ *
-+ * 3) Someone set the volume directly for this control, and this control
-+ * has a master control. We need to propagate the volume upstream, and wait
-+ * for another call that will fall under the case 1.
-+ *
-+ * 4) Someone set the volume directly for this control, and this control
-+ * doesn't have a master control. We need to propagate the volume
-+ * downstream.
-+ *
-+ * As we can see, the action is the same in cases 1, 2 and 4. */
-+
-+ /* Case 3. */
-+ if (control->synced_with_master && !control->master->volume_control->set_volume_in_progress) {
-+ pa_volume_control_set_volume(control->master->volume_control, original_volume, set_volume, set_balance);
-+ return 0;
-+ }
-+
-+ /* Cases 1, 2 and 4. */
-+ PA_HASHMAP_FOREACH(slave, control->slaves, state)
-+ pa_volume_control_set_volume(slave->volume_control, original_volume, set_volume, set_balance);
-
-- return group;
-+ return 0;
- }
-
--static int audio_group_put(struct audio_group *group) {
-- int r;
-+static int mute_control_set_mute_cb(pa_mute_control *mute_control, bool mute) {
-+ struct control *control;
-+ struct control *slave;
-+ void *state;
-+
-+ pa_assert(mute_control);
-+
-+ control = mute_control->userdata;
-+
-+ /* There are four cases that need to be considered:
-+ *
-+ * 1) The master control is propagating the mute to this control. We need
-+ * to propagate the mute downstream.
-+ *
-+ * 2) This control was just assigned a master control and the mute hasn't
-+ * yet been synchronized. In this case the mute that is now being set for
-+ * this control is the master control's mute. We need to propagate the mute
-+ * downstream.
-+ *
-+ * 3) Someone set the mute directly for this control, and this control has
-+ * a master control. We need to propagate the mute upstream, and wait for
-+ * another call that will fall under the case 1.
-+ *
-+ * 4) Someone set the mute directly for this control, and this control
-+ * doesn't have a master control. We need to propagate the mute downstream.
-+ *
-+ * As we can see, the action is the same in cases 1, 2 and 4. */
-+
-+ /* Case 3. */
-+ if (control->synced_with_master && !control->master->mute_control->set_mute_in_progress) {
-+ pa_mute_control_set_mute(control->master->mute_control, mute);
-+ return 0;
-+ }
-+
-+ /* Cases 1, 2 and 4. */
-+ PA_HASHMAP_FOREACH(slave, control->slaves, state)
-+ pa_mute_control_set_mute(slave->mute_control, mute);
-+
-+ return 0;
-+}
-+
-+static int control_new_for_group(struct group *group, enum control_type type, const char *name, bool persistent, struct control **_r) {
-+ struct control *control = NULL;
-+ int r = 0;
-
- pa_assert(group);
-+ pa_assert(name);
-+ pa_assert(_r);
-+
-+ control = pa_xnew0(struct control, 1);
-+ control->userdata = group->userdata;
-+ control->type = type;
-+ control->slaves = pa_hashmap_new(NULL, NULL);
-+
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (persistent)
-+ control->volume_control = pa_hashmap_get(control->userdata->volume_api->volume_controls, name);
-+
-+ if (!control->volume_control) {
-+ r = pa_volume_control_new(control->userdata->volume_api, name, persistent, &control->volume_control);
-+ if (r < 0)
-+ goto fail;
-+ }
-
-- r = pa_audio_group_new(group->userdata->api, group->id, group->description, &group->group);
-- if (r < 0)
-- goto fail;
-+ pa_volume_control_set_convertible_to_dB(control->volume_control, true);
-+
-+ if (persistent) {
-+ r = pa_volume_control_acquire_for_audio_group(control->volume_control, group->audio_group,
-+ volume_control_set_volume_cb, control);
-+ if (r < 0)
-+ goto fail;
-+
-+ control->acquired = true;
-+ } else {
-+ control->volume_control->set_volume = volume_control_set_volume_cb;
-+ control->volume_control->userdata = control;
-+ }
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ if (persistent)
-+ control->mute_control = pa_hashmap_get(control->userdata->volume_api->mute_controls, name);
-+
-+ if (!control->mute_control) {
-+ r = pa_mute_control_new(control->userdata->volume_api, name, persistent, &control->mute_control);
-+ if (r < 0)
-+ goto fail;
-+ }
-+
-+ if (persistent) {
-+ r = pa_mute_control_acquire_for_audio_group(control->mute_control, group->audio_group,
-+ mute_control_set_mute_cb, control);
-+ if (r < 0)
-+ goto fail;
-+
-+ control->acquired = true;
-+ } else {
-+ control->mute_control->set_mute = mute_control_set_mute_cb;
-+ control->mute_control->userdata = control;
-+ }
-+ break;
-+ }
-+
-+ control->own_control = true;
-+
-+ *_r = control;
-+ return 0;
-+
-+fail:
-+ if (control)
-+ control_free(control);
-+
-+ return r;
-+}
-+
-+static struct control *control_new_for_stream(struct userdata *u, enum control_type type, pas_stream *stream) {
-+ struct control *control;
-+
-+ pa_assert(u);
-+ pa_assert(stream);
-
-- switch (group->volume_control_action) {
-- case CONTROL_ACTION_NONE:
-+ control = pa_xnew0(struct control, 1);
-+ control->userdata = u;
-+ control->type = type;
-+
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ control->volume_control = stream->relative_volume_control;
-+ pa_assert(control->volume_control);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ control->mute_control = stream->mute_control;
-+ pa_assert(control->mute_control);
- break;
-+ }
-
-- case CONTROL_ACTION_CREATE:
-- pa_audio_group_set_have_own_volume_control(group->group, true);
-- pa_audio_group_set_volume_control(group->group, group->group->own_volume_control);
-+ return control;
-+}
-+
-+static void control_put(struct control *control) {
-+ pa_assert(control);
-+
-+ switch (control->type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (control->own_control && !control->volume_control->linked)
-+ pa_volume_control_put(control->volume_control);
- break;
-
-- case CONTROL_ACTION_BIND:
-- pa_audio_group_bind_volume_control(group->group, group->volume_control_target_info);
-+ case CONTROL_TYPE_MUTE:
-+ if (control->own_control && !control->mute_control->linked)
-+ pa_mute_control_put(control->mute_control);
- break;
- }
-+}
-+
-+static void control_unlink(struct control *control) {
-+ pa_assert(control);
-+
-+ if (control->unlinked)
-+ return;
-+
-+ control->unlinked = true;
-+
-+ if (control->slaves) {
-+ struct control *slave;
-+
-+ while ((slave = pa_hashmap_first(control->slaves)))
-+ control_set_master(slave, NULL);
-+ }
-+
-+ control_set_master(control, NULL);
-
-- switch (group->mute_control_action) {
-- case CONTROL_ACTION_NONE:
-+ switch (control->type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (control->own_control && control->volume_control && !control->volume_control->persistent)
-+ pa_volume_control_unlink(control->volume_control);
- break;
-
-- case CONTROL_ACTION_CREATE:
-- pa_audio_group_set_have_own_mute_control(group->group, true);
-- pa_audio_group_set_mute_control(group->group, group->group->own_mute_control);
-+ case CONTROL_TYPE_MUTE:
-+ if (control->own_control && control->mute_control && !control->mute_control->persistent)
-+ pa_mute_control_unlink(control->mute_control);
- break;
-+ }
-+}
-+
-+static void control_free(struct control *control) {
-+ pa_assert(control);
-+
-+ if (!control->unlinked)
-+ control_unlink(control);
-+
-+ if (control->slaves) {
-+ pa_assert(pa_hashmap_isempty(control->slaves));
-+ pa_hashmap_free(control->slaves);
-+ }
-+
-+ switch (control->type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (control->acquired)
-+ pa_volume_control_release(control->volume_control);
-+
-+ if (control->own_control && control->volume_control && !control->volume_control->persistent)
-+ pa_volume_control_free(control->volume_control);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ if (control->acquired)
-+ pa_mute_control_release(control->mute_control);
-
-- case CONTROL_ACTION_BIND:
-- pa_audio_group_bind_mute_control(group->group, group->mute_control_target_info);
-+ if (control->own_control && control->mute_control && !control->mute_control->persistent)
-+ pa_mute_control_free(control->mute_control);
- break;
- }
-
-- pa_audio_group_put(group->group);
-+ pa_xfree(control);
-+}
-+
-+static void control_set_master(struct control *control, struct control *master) {
-+ struct control *old_master;
-+
-+ pa_assert(control);
-+ pa_assert(!master || master->type == control->type);
-+
-+ old_master = control->master;
-+
-+ if (master == old_master)
-+ return;
-+
-+ if (old_master) {
-+ control_remove_slave(old_master, control);
-+ control->synced_with_master = false;
-+ }
-+
-+ control->master = master;
-+
-+ if (master) {
-+ control_add_slave(master, control);
-+
-+ switch (control->type) {
-+ case CONTROL_TYPE_VOLUME:
-+ pa_volume_control_set_volume(control->volume_control, &master->volume_control->volume, true, true);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ pa_mute_control_set_mute(control->mute_control, master->mute_control->mute);
-+ break;
-+ }
-+
-+ control->synced_with_master = true;
-+ }
-+}
-+
-+static void control_add_slave(struct control *control, struct control *slave) {
-+ pa_assert(control);
-+ pa_assert(slave);
-+
-+ pa_assert_se(pa_hashmap_put(control->slaves, slave, slave) >= 0);
-+}
-+
-+static void control_remove_slave(struct control *control, struct control *slave) {
-+ pa_assert(control);
-+ pa_assert(slave);
-+
-+ pa_assert_se(pa_hashmap_remove(control->slaves, slave));
-+}
-+
-+static int group_new(struct userdata *u, const char *name, struct group **_r) {
-+ struct group *group = NULL;
-+ int r;
-+ struct group *slave;
-+ struct stream_rule *rule;
-+ void *state;
-+
-+ pa_assert(u);
-+ pa_assert(name);
-+ pa_assert(_r);
-+
-+ group = pa_xnew0(struct group, 1);
-+ group->userdata = u;
-+
-+ r = pa_audio_group_new(u->volume_api, name, &group->audio_group);
-+ if (r < 0)
-+ goto fail;
-+
-+ group->volume_slaves = pa_hashmap_new(NULL, NULL);
-+ group->mute_slaves = pa_hashmap_new(NULL, NULL);
-+ group->volume_stream_rules = pa_hashmap_new(NULL, NULL);
-+ group->mute_stream_rules = pa_hashmap_new(NULL, NULL);
-+
-+ PA_HASHMAP_FOREACH(slave, u->groups, state) {
-+ if (slave == group)
-+ continue;
-+
-+ if (pa_safe_streq(slave->volume_master_name, group->audio_group->name))
-+ group_set_master(slave, CONTROL_TYPE_VOLUME, group);
-+
-+ if (pa_safe_streq(slave->mute_master_name, group->audio_group->name))
-+ group_set_master(slave, CONTROL_TYPE_MUTE, group);
-+ }
-+
-+ PA_HASHMAP_FOREACH(rule, u->stream_rules, state) {
-+ if (pa_safe_streq(rule->audio_group_name_for_volume, group->audio_group->name))
-+ stream_rule_set_group(rule, CONTROL_TYPE_VOLUME, group);
-
-+ if (pa_safe_streq(rule->audio_group_name_for_mute, group->audio_group->name))
-+ stream_rule_set_group(rule, CONTROL_TYPE_MUTE, group);
-+ }
-+
-+ *_r = group;
- return 0;
-
- fail:
-- audio_group_unlink(group);
-+ if (group)
-+ group_free(group);
-
- return r;
- }
-
--static void audio_group_unlink(struct audio_group *group) {
-+static void group_put(struct group *group) {
-+ pa_assert(group);
-+
-+ pa_audio_group_put(group->audio_group);
-+
-+ if (group->volume_control)
-+ control_put(group->volume_control);
-+
-+ if (group->mute_control)
-+ control_put(group->mute_control);
-+}
-+
-+static void group_unlink(struct group *group) {
-+ struct stream_rule *rule;
-+ struct group *slave;
-+ void *state;
-+
- pa_assert(group);
-
- if (group->unlinked)
-@@ -219,192 +596,406 @@ static void audio_group_unlink(struct audio_group *group) {
-
- group->unlinked = true;
-
-- if (group->group) {
-- pa_audio_group_free(group->group);
-- group->group = NULL;
-- }
-+ PA_HASHMAP_FOREACH(rule, group->volume_stream_rules, state)
-+ stream_rule_set_group(rule, CONTROL_TYPE_VOLUME, NULL);
-+
-+ PA_HASHMAP_FOREACH(rule, group->mute_stream_rules, state)
-+ stream_rule_set_group(rule, CONTROL_TYPE_MUTE, NULL);
-+
-+ PA_HASHMAP_FOREACH(slave, group->volume_slaves, state)
-+ group_set_master(slave, CONTROL_TYPE_VOLUME, NULL);
-+
-+ PA_HASHMAP_FOREACH(slave, group->mute_slaves, state)
-+ group_set_master(slave, CONTROL_TYPE_MUTE, NULL);
-+
-+ group_disable_control(group, CONTROL_TYPE_MUTE);
-+ group_disable_control(group, CONTROL_TYPE_VOLUME);
-+
-+ if (group->audio_group)
-+ pa_audio_group_unlink(group->audio_group);
- }
-
--static void audio_group_free(struct audio_group *group) {
-+static void group_free(struct group *group) {
- pa_assert(group);
-
-- if (!group->unlinked)
-- audio_group_unlink(group);
-+ group_unlink(group);
-+
-+ if (group->mute_stream_rules) {
-+ pa_assert(pa_hashmap_isempty(group->mute_stream_rules));
-+ pa_hashmap_free(group->mute_stream_rules);
-+ }
-+
-+ if (group->volume_stream_rules) {
-+ pa_assert(pa_hashmap_isempty(group->volume_stream_rules));
-+ pa_hashmap_free(group->volume_stream_rules);
-+ }
-+
-+ if (group->mute_slaves) {
-+ pa_assert(pa_hashmap_isempty(group->mute_slaves));
-+ pa_hashmap_free(group->mute_slaves);
-+ }
-+
-+ if (group->volume_slaves) {
-+ pa_assert(pa_hashmap_isempty(group->volume_slaves));
-+ pa_hashmap_free(group->volume_slaves);
-+ }
-
-- if (group->mute_control_target_info)
-- pa_binding_target_info_free(group->mute_control_target_info);
-+ pa_assert(!group->mute_master_name);
-+ pa_assert(!group->volume_master_name);
-+ pa_assert(!group->mute_master);
-+ pa_assert(!group->volume_master);
-+ pa_assert(!group->mute_control);
-+ pa_assert(!group->volume_control);
-
-- if (group->volume_control_target_info)
-- pa_binding_target_info_free(group->volume_control_target_info);
-+ if (group->audio_group)
-+ pa_audio_group_free(group->audio_group);
-
-- pa_xfree(group->description);
-- pa_xfree(group->id);
- pa_xfree(group);
- }
-
--static void audio_group_set_description(struct audio_group *group, const char *description) {
-+static void group_set_own_control_name(struct group *group, enum control_type type, const char *name) {
-+ struct group *slave;
-+ void *state;
-+
- pa_assert(group);
-- pa_assert(description);
-
-- pa_xfree(group->description);
-- group->description = pa_xstrdup(description);
-+ if (name)
-+ group_set_master_name(group, type, NULL);
-+
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (pa_safe_streq(name, group->own_volume_control_name))
-+ return;
-+
-+ if (group->volume_control) {
-+ control_free(group->volume_control);
-+ group->volume_control = NULL;
-+ }
-+
-+ pa_xfree(group->own_volume_control_name);
-+ group->own_volume_control_name = pa_xstrdup(name);
-+
-+ if (name) {
-+ control_new_for_group(group, CONTROL_TYPE_VOLUME, name, true, &group->volume_control);
-+
-+ PA_HASHMAP_FOREACH(slave, group->volume_slaves, state) {
-+ if (slave->volume_control)
-+ control_set_master(slave->volume_control, group->volume_control);
-+ }
-+ }
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ if (pa_safe_streq(name, group->own_mute_control_name))
-+ return;
-+
-+ if (group->mute_control) {
-+ control_free(group->mute_control);
-+ group->mute_control = NULL;
-+ }
-+
-+ pa_xfree(group->own_mute_control_name);
-+ group->own_mute_control_name = pa_xstrdup(name);
-+
-+ if (name) {
-+ control_new_for_group(group, CONTROL_TYPE_MUTE, name, true, &group->mute_control);
-+
-+ PA_HASHMAP_FOREACH(slave, group->mute_slaves, state) {
-+ if (slave->mute_control)
-+ control_set_master(slave->mute_control, group->mute_control);
-+ }
-+ }
-+ break;
-+ }
- }
-
--static void audio_group_set_volume_control_action(struct audio_group *group, enum control_action action,
-- pa_binding_target_info *target_info) {
-+static void group_set_master(struct group *group, enum control_type type, struct group *master) {
-+ struct group *old_master;
-+ struct control *master_control = NULL;
-+
- pa_assert(group);
-- pa_assert((action == CONTROL_ACTION_BIND) ^ !target_info);
-+ pa_assert(master != group);
-+
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ old_master = group->volume_master;
-+
-+ if (master == old_master)
-+ return;
-+
-+ if (old_master)
-+ group_remove_slave(old_master, CONTROL_TYPE_VOLUME, group);
-+
-+ group->volume_master = master;
-+
-+ if (master)
-+ group_add_slave(master, CONTROL_TYPE_VOLUME, group);
-+
-+ if (group->volume_control) {
-+ if (master)
-+ master_control = master->volume_control;
-
-- group->volume_control_action = action;
-+ control_set_master(group->volume_control, master_control);
-+ }
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ old_master = group->mute_master;
-+
-+ if (master == old_master)
-+ return;
-
-- if (group->volume_control_target_info)
-- pa_binding_target_info_free(group->volume_control_target_info);
-+ if (old_master)
-+ group_remove_slave(old_master, CONTROL_TYPE_MUTE, group);
-
-- if (action == CONTROL_ACTION_BIND)
-- group->volume_control_target_info = pa_binding_target_info_copy(target_info);
-- else
-- group->volume_control_target_info = NULL;
-+ group->mute_master = master;
-+
-+ if (master)
-+ group_add_slave(master, CONTROL_TYPE_MUTE, group);
-+
-+ if (group->mute_control) {
-+ if (master)
-+ master_control = master->volume_control;
-+
-+ control_set_master(group->volume_control, master_control);
-+ }
-+ break;
-+ }
- }
-
--static void audio_group_set_mute_control_action(struct audio_group *group, enum control_action action,
-- pa_binding_target_info *target_info) {
-+static int group_set_master_name(struct group *group, enum control_type type, const char *name) {
-+ struct group *slave;
-+ void *state;
-+ struct group *master = NULL;
-+
- pa_assert(group);
-- pa_assert((action == CONTROL_ACTION_BIND) ^ !target_info);
-
-- group->mute_control_action = action;
-+ if (pa_safe_streq(name, group->audio_group->name)) {
-+ pa_log("Can't bind audio group control to itself.");
-+ return -PA_ERR_INVALID;
-+ }
-
-- if (group->mute_control_target_info)
-- pa_binding_target_info_free(group->mute_control_target_info);
-+ if (name)
-+ group_set_own_control_name(group, type, NULL);
-
-- if (action == CONTROL_ACTION_BIND)
-- group->mute_control_target_info = pa_binding_target_info_copy(target_info);
-- else
-- group->mute_control_target_info = NULL;
--}
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (pa_safe_streq(name, group->volume_master_name))
-+ return 0;
-
--static struct stream *stream_new(struct userdata *u, const char *name) {
-- struct stream *stream;
-+ pa_xfree(group->volume_master_name);
-+ group->volume_master_name = pa_xstrdup(name);
-
-- pa_assert(u);
-- pa_assert(name);
-+ if (name && !group->volume_control) {
-+ control_new_for_group(group, CONTROL_TYPE_VOLUME, "audio-group-volume-control", false, &group->volume_control);
-
-- stream = pa_xnew0(struct stream, 1);
-- stream->userdata = u;
-- stream->id = pa_xstrdup(name);
-- stream->direction = match_direction_unknown;
-+ PA_HASHMAP_FOREACH(slave, group->volume_slaves, state) {
-+ if (slave->volume_control)
-+ control_set_master(slave->volume_control, group->volume_control);
-+ }
-
-- return stream;
--}
-+ } else if (!name && group->volume_control) {
-+ control_free(group->volume_control);
-+ group->volume_control = NULL;
-+ }
-+ break;
-
--static void stream_put(struct stream *stream) {
-- pa_assert(stream);
-+ case CONTROL_TYPE_MUTE:
-+ if (pa_safe_streq(name, group->mute_master_name))
-+ return 0;
-
-- if (stream->audio_group_name_for_volume) {
-- stream->audio_group_for_volume = pa_hashmap_get(stream->userdata->audio_groups, stream->audio_group_name_for_volume);
-- if (stream->audio_group_for_volume)
-- stream->volume_control_target_info =
-- pa_binding_target_info_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, stream->audio_group_for_volume->name,
-- PA_AUDIO_GROUP_BINDING_TARGET_FIELD_VOLUME_CONTROL);
-- else
-- pa_log("Stream %s refers to undefined audio group %s.", stream->id, stream->audio_group_name_for_volume);
-+ pa_xfree(group->mute_master_name);
-+ group->mute_master_name = pa_xstrdup(name);
-+
-+ if (name && !group->mute_control) {
-+ control_new_for_group(group, CONTROL_TYPE_MUTE, "audio-group-mute-control", false, &group->mute_control);
-+
-+ PA_HASHMAP_FOREACH(slave, group->mute_slaves, state) {
-+ if (slave->mute_control)
-+ control_set_master(slave->mute_control, group->mute_control);
-+ }
-+
-+ } else if (!name && group->mute_control) {
-+ control_free(group->mute_control);
-+ group->mute_control = NULL;
-+ }
-+ break;
- }
-
-- if (stream->audio_group_name_for_mute) {
-- stream->audio_group_for_mute = pa_hashmap_get(stream->userdata->audio_groups, stream->audio_group_name_for_mute);
-- if (stream->audio_group_for_mute)
-- stream->mute_control_target_info =
-- pa_binding_target_info_new(PA_AUDIO_GROUP_BINDING_TARGET_TYPE, stream->audio_group_for_mute->name,
-- PA_AUDIO_GROUP_BINDING_TARGET_FIELD_MUTE_CONTROL);
-- else
-- pa_log("Stream %s refers to undefined audio group %s.", stream->id, stream->audio_group_name_for_volume);
-+ if (name)
-+ master = pa_hashmap_get(group->userdata->groups, name);
-+
-+ group_set_master(group, type, master);
-+
-+ return 0;
-+}
-+
-+static void group_disable_control(struct group *group, enum control_type type) {
-+ pa_assert(group);
-+
-+ group_set_own_control_name(group, type, NULL);
-+ group_set_master_name(group, type, NULL);
-+}
-+
-+static void group_add_slave(struct group *group, enum control_type type, struct group *slave) {
-+ pa_assert(group);
-+ pa_assert(slave);
-+
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ pa_assert_se(pa_hashmap_put(group->volume_slaves, slave, slave) >= 0);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ pa_assert_se(pa_hashmap_put(group->mute_slaves, slave, slave) >= 0);
-+ break;
- }
- }
-
--static void stream_unlink(struct stream *stream) {
-- pa_assert(stream);
-+static void group_remove_slave(struct group *group, enum control_type type, struct group *slave) {
-+ pa_assert(group);
-+ pa_assert(slave);
-
-- if (stream->unlinked)
-- return;
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ pa_assert_se(pa_hashmap_remove(group->volume_slaves, slave));
-+ break;
-
-- if (stream->mute_control_target_info) {
-- pa_binding_target_info_free(stream->mute_control_target_info);
-- stream->mute_control_target_info = NULL;
-+ case CONTROL_TYPE_MUTE:
-+ pa_assert_se(pa_hashmap_remove(group->mute_slaves, slave));
- }
-+}
-
-- if (stream->volume_control_target_info) {
-- pa_binding_target_info_free(stream->volume_control_target_info);
-- stream->volume_control_target_info = NULL;
-+static void group_add_stream_rule(struct group *group, enum control_type type, struct stream_rule *rule) {
-+ pa_assert(group);
-+ pa_assert(rule);
-+
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ pa_assert_se(pa_hashmap_put(group->volume_stream_rules, rule, rule) >= 0);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ pa_assert_se(pa_hashmap_put(group->mute_stream_rules, rule, rule) >= 0);
-+ break;
- }
-+}
-+
-+static void group_remove_stream_rule(struct group *group, enum control_type type, struct stream_rule *rule) {
-+ pa_assert(group);
-+ pa_assert(rule);
-
-- stream->unlinked = true;
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ pa_assert_se(pa_hashmap_remove(group->volume_stream_rules, rule));
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ pa_assert_se(pa_hashmap_remove(group->mute_stream_rules, rule));
-+ break;
-+ }
- }
-
--static void stream_free(struct stream *stream) {
-- pa_assert(stream);
-+static struct stream_rule *stream_rule_new(struct userdata *u, const char *name) {
-+ struct stream_rule *rule;
-
-- if (!stream->unlinked)
-- stream_unlink(stream);
-+ pa_assert(u);
-+ pa_assert(name);
-
-- if (stream->rule)
-- delete_expression(stream->rule);
-+ rule = pa_xnew0(struct stream_rule, 1);
-+ rule->userdata = u;
-+ rule->name = pa_xstrdup(name);
-+ rule->direction = match_direction_unknown;
-+ rule->match_expression = expression_new();
-
-- pa_xfree(stream->audio_group_name_for_mute);
-- pa_xfree(stream->audio_group_name_for_volume);
-- pa_xfree(stream->id);
-- pa_xfree(stream);
-+ return rule;
- }
-
--static void stream_set_audio_group_name_for_volume(struct stream *stream, const char *name) {
-- pa_assert(stream);
-+static void stream_rule_free(struct stream_rule *rule) {
-+ pa_assert(rule);
-
-- pa_xfree(stream->audio_group_name_for_volume);
-- stream->audio_group_name_for_volume = pa_xstrdup(name);
-+ if (rule->match_expression)
-+ expression_free(rule->match_expression);
-+
-+ stream_rule_set_group_name(rule, CONTROL_TYPE_MUTE, NULL);
-+ stream_rule_set_group_name(rule, CONTROL_TYPE_VOLUME, NULL);
-+ pa_xfree(rule->name);
-+ pa_xfree(rule);
- }
-
--static void stream_set_audio_group_name_for_mute(struct stream *stream, const char *name) {
-- pa_assert(stream);
-+static void stream_rule_set_match_expression(struct stream_rule *rule, struct expression *expression) {
-+ pa_assert(rule);
-+ pa_assert(expression);
-
-- pa_xfree(stream->audio_group_name_for_mute);
-- stream->audio_group_name_for_mute = pa_xstrdup(name);
-+ if (rule->match_expression)
-+ expression_free(rule->match_expression);
-+
-+ rule->match_expression = expression;
- }
-
--/* stream classification */
-+static void stream_rule_set_group(struct stream_rule *rule, enum control_type type, struct group *group) {
-+ pa_assert(rule);
-
--static bool match_predicate(struct literal *l, pas_stream *d) {
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (group == rule->group_for_volume)
-+ return;
-
-- if (l->stream_direction != match_direction_unknown) {
-- /* check the stream direction; _sink inputs_ are always _outputs_ */
-+ if (rule->group_for_volume)
-+ group_remove_stream_rule(rule->group_for_volume, CONTROL_TYPE_VOLUME, rule);
-
-- if ((d->direction == PA_DIRECTION_OUTPUT && l->stream_direction == match_direction_output) ||
-- ((d->direction == PA_DIRECTION_INPUT && l->stream_direction == match_direction_input))) {
-- return true;
-- }
-+ rule->group_for_volume = group;
-+
-+ if (group)
-+ group_add_stream_rule(group, CONTROL_TYPE_VOLUME, rule);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ if (group == rule->group_for_mute)
-+ return;
-+
-+ if (rule->group_for_mute)
-+ group_remove_stream_rule(rule->group_for_mute, CONTROL_TYPE_MUTE, rule);
-+
-+ rule->group_for_mute = group;
-+
-+ if (group)
-+ group_add_stream_rule(group, CONTROL_TYPE_MUTE, rule);
-+ break;
- }
-- else if (l->property_name && l->property_value) {
-- /* check the property from the property list */
-+}
-
-- if (pa_proplist_contains(d->proplist, l->property_name)) {
-- const char *prop = pa_proplist_gets(d->proplist, l->property_name);
-+static void stream_rule_set_group_name(struct stream_rule *rule, enum control_type type, const char *name) {
-+ struct group *group = NULL;
-
-- if (prop && strcmp(prop, l->property_value) == 0) {
-- return true;
-- }
-- }
-+ pa_assert(rule);
-+
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ pa_xfree(rule->audio_group_name_for_volume);
-+ rule->audio_group_name_for_volume = pa_xstrdup(name);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ pa_xfree(rule->audio_group_name_for_mute);
-+ rule->audio_group_name_for_mute = pa_xstrdup(name);
-+ break;
- }
-
-- /* no match */
-- return false;
--}
-+ if (name)
-+ group = pa_hashmap_get(rule->userdata->groups, name);
-
--static bool match_rule(struct expression *e, pas_stream *d) {
-+ stream_rule_set_group(rule, type, group);
-+}
-
-+static bool stream_rule_match(struct stream_rule *rule, pas_stream *stream) {
- struct conjunction *c;
-
-- PA_LLIST_FOREACH(c, e->conjunctions) {
-+ PA_LLIST_FOREACH(c, rule->match_expression->conjunctions) {
- struct literal *l;
- bool and_success = true;
- PA_LLIST_FOREACH(l, c->literals) {
-- if (!match_predicate(l, d)) {
-+ if (!literal_match(l, stream)) {
- /* at least one fail for conjunction */
- and_success = false;
- break;
-@@ -421,56 +1012,246 @@ static bool match_rule(struct expression *e, pas_stream *d) {
- return false;
- }
-
--static void classify_stream(struct userdata *u, pas_stream *new_data, bool mute) {
-- /* do the classification here */
-+/* stream classification */
-
-- struct stream *stream = NULL;
-- unsigned idx;
-+static bool literal_match(struct literal *literal, pas_stream *stream) {
-+
-+ if (literal->stream_direction != match_direction_unknown) {
-+ /* check the stream direction; _sink inputs_ are always _outputs_ */
-
-- /* go through the stream match definitions in given order */
-+ if ((stream->direction == PA_DIRECTION_OUTPUT && literal->stream_direction == match_direction_output) ||
-+ (stream->direction == PA_DIRECTION_INPUT && literal->stream_direction == match_direction_input)) {
-+ return literal->negation ? false : true;
-+ }
-+ }
-+ else if (literal->property_name && literal->property_value) {
-+ /* check the property from the property list */
-
-- PA_DYNARRAY_FOREACH(stream, u->streams, idx) {
-- if (stream->rule && match_rule(stream->rule, new_data)) {
-- pa_log_info("stream %s (%s) match with rule %s:", new_data->name, new_data->description, stream->id);
-- print_expression(stream->rule);
-+ if (pa_proplist_contains(stream->proplist, literal->property_name)) {
-+ const char *prop = pa_proplist_gets(stream->proplist, literal->property_name);
-
-- if (mute) {
-- if (new_data->use_default_mute_control && stream->audio_group_for_mute)
-- pas_stream_bind_mute_control(new_data, stream->mute_control_target_info);
-- } else {
-- if (new_data->use_default_volume_control && stream->audio_group_for_volume)
-- pas_stream_bind_volume_control(new_data, stream->volume_control_target_info);
-- }
-+ if (prop && strcmp(prop, literal->property_value) == 0)
-+ return literal->negation ? false : true;
-+ }
-+ }
-+
-+ /* no match */
-+ return literal->negation ? true : false;
-+}
-+
-+static pa_hook_result_t stream_put_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pas_stream *stream = call_data;
-+ struct stream_rule *rule;
-+ unsigned idx;
-
-- return;
-+ pa_assert(u);
-+ pa_assert(stream);
-+
-+ PA_DYNARRAY_FOREACH(rule, u->stream_rules_list, idx) {
-+ if (stream_rule_match(rule, stream)) {
-+ pa_hashmap_put(u->rules_by_stream, stream, rule);
-+ break;
- }
- }
-
-- /* no matches, don't touch the volumes */
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t stream_unlink_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pas_stream *stream = call_data;
-+
-+ pa_assert(u);
-+ pa_assert(stream);
-+
-+ pa_hashmap_remove(u->rules_by_stream, stream);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t volume_control_implementation_initialized_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_volume_control *volume_control = call_data;
-+ struct control *control;
-+
-+ pa_assert(u);
-+ pa_assert(volume_control);
-+
-+ if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
-+ return PA_HOOK_OK;
-+
-+ control = control_new_for_stream(u, CONTROL_TYPE_VOLUME, volume_control->owner_stream);
-+ control_put(control);
-+ pa_assert_se(pa_hashmap_put(u->stream_volume_controls, volume_control->owner_stream, control) >= 0);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t mute_control_implementation_initialized_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_mute_control *mute_control = call_data;
-+ struct control *control;
-+
-+ pa_assert(u);
-+ pa_assert(mute_control);
-+
-+ if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
-+ return PA_HOOK_OK;
-+
-+ control = control_new_for_stream(u, CONTROL_TYPE_MUTE, mute_control->owner_stream);
-+ control_put(control);
-+ pa_assert_se(pa_hashmap_put(u->stream_mute_controls, mute_control->owner_stream, control) >= 0);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t volume_control_set_initial_volume_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_volume_control *volume_control = call_data;
-+ struct stream_rule *rule;
-+ struct control *control;
-+
-+ pa_assert(u);
-+ pa_assert(volume_control);
-+
-+ if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
-+ return PA_HOOK_OK;
-+
-+ rule = pa_hashmap_get(u->rules_by_stream, volume_control->owner_stream);
-+ if (!rule)
-+ return PA_HOOK_OK;
-+
-+ if (!rule->group_for_volume)
-+ return PA_HOOK_OK;
-+
-+ if (!rule->group_for_volume->volume_control)
-+ return PA_HOOK_OK;
-+
-+ control = pa_hashmap_get(u->stream_volume_controls, volume_control->owner_stream);
-+ pa_assert(control);
-+ pa_assert(control->volume_control == volume_control);
-+
-+ /* This will set the volume for volume_control. */
-+ control_set_master(control, rule->group_for_volume->volume_control);
-+
-+ return PA_HOOK_STOP;
-+}
-+
-+static pa_hook_result_t mute_control_set_initial_mute_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_mute_control *mute_control = call_data;
-+ struct stream_rule *rule;
-+ struct control *control;
-+
-+ pa_assert(u);
-+ pa_assert(mute_control);
-+
-+ if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
-+ return PA_HOOK_OK;
-+
-+ rule = pa_hashmap_get(u->rules_by_stream, mute_control->owner_stream);
-+ if (!rule)
-+ return PA_HOOK_OK;
-+
-+ if (!rule->group_for_mute)
-+ return PA_HOOK_OK;
-+
-+ if (!rule->group_for_mute->mute_control)
-+ return PA_HOOK_OK;
-+
-+ control = pa_hashmap_get(u->stream_mute_controls, mute_control->owner_stream);
-+ pa_assert(control);
-+ pa_assert(control->mute_control == mute_control);
-+
-+ /* This will set the mute for mute_control. */
-+ control_set_master(control, rule->group_for_mute->mute_control);
-+
-+ return PA_HOOK_STOP;
-+}
-+
-+static pa_hook_result_t volume_control_volume_changed_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_volume_control *volume_control = call_data;
-+ struct control *control;
-+
-+ pa_assert(u);
-+ pa_assert(volume_control);
-+
-+ if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
-+ return PA_HOOK_OK;
-+
-+ control = pa_hashmap_get(u->stream_volume_controls, volume_control->owner_stream);
-+ if (!control)
-+ return PA_HOOK_OK;
-+
-+ if (!control->master)
-+ return PA_HOOK_OK;
-+
-+ pa_volume_control_set_volume(control->master->volume_control, &volume_control->volume, true, true);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t mute_control_mute_changed_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_mute_control *mute_control = call_data;
-+ struct control *control;
-+
-+ pa_assert(u);
-+ pa_assert(mute_control);
-+
-+ if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
-+ return PA_HOOK_OK;
-+
-+ control = pa_hashmap_get(u->stream_mute_controls, mute_control->owner_stream);
-+ if (!control)
-+ return PA_HOOK_OK;
-+
-+ if (!control->master)
-+ return PA_HOOK_OK;
-+
-+ pa_mute_control_set_mute(control->master->mute_control, mute_control->mute);
-+
-+ return PA_HOOK_OK;
- }
-
--static pa_hook_result_t set_volume_control_cb(
-- void *hook_data,
-- pas_stream *new_data,
-- struct userdata *u) {
-+static pa_hook_result_t volume_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_volume_control *volume_control = call_data;
-+ struct control *control;
-
-- pa_assert(new_data);
- pa_assert(u);
-+ pa_assert(volume_control);
-+
-+ if (volume_control->purpose != PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME)
-+ return PA_HOOK_OK;
-
-- classify_stream(u, new_data, false);
-+ control = pa_hashmap_remove(u->stream_volume_controls, volume_control->owner_stream);
-+ if (!control)
-+ return PA_HOOK_OK;
-+
-+ control_free(control);
-
- return PA_HOOK_OK;
- }
-
--static pa_hook_result_t set_mute_control_cb(
-- void *hook_data,
-- pas_stream *new_data,
-- struct userdata *u) {
-+static pa_hook_result_t mute_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_mute_control *mute_control = call_data;
-+ struct control *control;
-
-- pa_assert(new_data);
- pa_assert(u);
-+ pa_assert(mute_control);
-+
-+ if (mute_control->purpose != PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE)
-+ return PA_HOOK_OK;
-
-- classify_stream(u, new_data, true);
-+ control = pa_hashmap_remove(u->stream_mute_controls, mute_control->owner_stream);
-+ if (!control)
-+ return PA_HOOK_OK;
-+
-+ control_free(control);
-
- return PA_HOOK_OK;
- }
-@@ -520,6 +1301,7 @@ static pa_hook_result_t set_mute_control_cb(
- (property application.process.binary=paplay OR (direction input OR direction output))
- */
-
-+#if 0
- static void print_literal(struct literal *l) {
- if (l->stream_direction != match_direction_unknown) {
- pa_log_info(" %sstream direction %s",
-@@ -549,6 +1331,7 @@ static void print_expression(struct expression *e) {
- print_conjunction(c);
- }
- }
-+#endif
-
- static void delete_literal(struct literal *l) {
-
-@@ -573,14 +1356,23 @@ static void delete_conjunction(struct conjunction *c) {
- pa_xfree(c);
- }
-
--static void delete_expression(struct expression *e) {
-+static struct expression *expression_new(void) {
-+ struct expression *expression;
-+
-+ expression = pa_xnew0(struct expression, 1);
-+
-+ return expression;
-+}
-+
-+static void expression_free(struct expression *expression) {
- struct conjunction *c;
-
-- PA_LLIST_FOREACH(c, e->conjunctions) {
-+ pa_assert(expression);
-+
-+ PA_LLIST_FOREACH(c, expression->conjunctions)
- delete_conjunction(c);
-- }
-
-- pa_xfree(e);
-+ pa_xfree(expression);
- }
-
- enum logic_operator {
-@@ -917,26 +1709,21 @@ static bool gather_expression(struct expression *e, struct expression_token *et)
- return true;
- }
-
--static struct expression *parse_rule(const char *rule_string) {
-- char *k, *l;
-+static int expression_from_string(const char *str, struct expression **_r) {
-+ const char *k;
-+ char *l;
- struct expression *e = NULL;
-- int len;
- char *buf = NULL;
- struct expression_token *et = NULL;
-
-- if (!rule_string)
-- goto error;
--
-- len = strlen(rule_string);
--
-- buf = (char *) pa_xmalloc0(len);
-+ pa_assert(str);
-+ pa_assert(_r);
-
-- if (!buf)
-- goto error;
-+ buf = pa_xmalloc0(strlen(str) + 1);
-
- /* remove whitespace */
-
-- k = (char *) rule_string;
-+ k = str;
- l = buf;
-
- while (*k) {
-@@ -958,9 +1745,6 @@ static struct expression *parse_rule(const char *rule_string) {
-
- e = pa_xnew0(struct expression, 1);
-
-- if (!e)
-- goto error;
--
- PA_LLIST_HEAD_INIT(struct conjunction, e->conjunctions);
-
- /* gather expressions to actual match format */
-@@ -978,30 +1762,15 @@ static struct expression *parse_rule(const char *rule_string) {
- delete_expression_token(et);
- pa_xfree(buf);
-
-- return e;
-+ *_r = e;
-+ return 0;
-
- error:
- delete_expression_token(et);
- pa_xfree(buf);
-- pa_xfree(e);
-- return NULL;
--}
--
--static int parse_audio_groups(pa_config_parser_state *state) {
-- struct userdata *u;
-- char *name;
-- const char *split_state = NULL;
--
-- pa_assert(state);
--
-- u = state->userdata;
--
-- pa_hashmap_remove_all(u->audio_group_names);
-+ expression_free(e);
-
-- while ((name = pa_split_spaces(state->rvalue, &split_state)))
-- pa_hashmap_put(u->audio_group_names, name, name);
--
-- return 0;
-+ return -PA_ERR_INVALID;
- }
-
- static int parse_streams(pa_config_parser_state *state) {
-@@ -1013,15 +1782,13 @@ static int parse_streams(pa_config_parser_state *state) {
-
- u = state->userdata;
-
-- pa_dynarray_remove_all(u->stream_names);
--
- while ((name = pa_split_spaces(state->rvalue, &split_state))) {
- const char *name2;
- unsigned idx;
- bool duplicate = false;
-
-- /* Avoid adding duplicates in u->stream_names. */
-- PA_DYNARRAY_FOREACH(name2, u->stream_names, idx) {
-+ /* Avoid adding duplicates in u->stream_rule_names. */
-+ PA_DYNARRAY_FOREACH(name2, u->stream_rule_names, idx) {
- if (pa_streq(name, name2)) {
- duplicate = true;
- break;
-@@ -1033,230 +1800,221 @@ static int parse_streams(pa_config_parser_state *state) {
- continue;
- }
-
-- pa_dynarray_append(u->stream_names, name);
-+ pa_dynarray_append(u->stream_rule_names, name);
- }
-
- return 0;
- }
-
--static int parse_common(pa_config_parser_state *state) {
--#define AUDIOGROUP_START "AudioGroup "
--#define STREAM_START "Stream "
--#define BIND_KEYWORD "bind:"
--#define NONE_KEYWORD "none"
--
-- char *section;
-- struct userdata *u = (struct userdata *) state->userdata;
-- int r;
-- pa_binding_target_info *target_info;
--
-+static int parse_group_control(pa_config_parser_state *state, struct group *group, enum control_type type) {
- pa_assert(state);
-+ pa_assert(group);
-
-- section = state->section;
-- if (!section)
-- goto error;
-+ if (pa_streq(state->rvalue, NONE_KEYWORD))
-+ group_disable_control(group, type);
-+
-+ else if (pa_startswith(state->rvalue, CREATE_PREFIX))
-+ group_set_own_control_name(group, type, state->rvalue + strlen(CREATE_PREFIX));
-
-- if (strncmp(section, AUDIOGROUP_START, strlen(AUDIOGROUP_START)) == 0) {
-- char *ag_name = section + strlen(AUDIOGROUP_START);
-- struct audio_group *ag = (struct audio_group *) pa_hashmap_get(u->unused_audio_groups, ag_name);
-+ else if (pa_startswith(state->rvalue, BIND_PREFIX)) {
-+ if (pa_startswith(state->rvalue, BIND_AUDIO_GROUP_PREFIX)) {
-+ int r;
-
-- if (!ag) {
-- /* first item for this audio group section, so create the struct */
-- ag = audio_group_new(u, ag_name);
-- pa_hashmap_put(u->unused_audio_groups, ag->id, ag);
-+ r = group_set_master_name(group, type, state->rvalue + strlen(BIND_AUDIO_GROUP_PREFIX));
-+ if (r < 0) {
-+ pa_log("[%s:%u] Failed to set binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
-+ return r;
-+ }
-+ } else {
-+ pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
-+ return -PA_ERR_INVALID;
- }
-+ } else {
-+ pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
-+ return -PA_ERR_INVALID;
-+ }
-
-- if (strcmp(state->lvalue, "description") == 0)
-- audio_group_set_description(ag, state->rvalue);
-+ return 0;
-+}
-+
-+static int parse_common(pa_config_parser_state *state) {
-+ char *section;
-+ struct userdata *u = state->userdata;
-+ const char *name;
-+ int r;
-
-- else if (strcmp(state->lvalue, "volume-control") == 0) {
-- if (pa_streq(state->rvalue, "create"))
-- audio_group_set_volume_control_action(ag, CONTROL_ACTION_CREATE, NULL);
-+ pa_assert(state);
-
-- else if (pa_streq(state->rvalue, NONE_KEYWORD))
-- audio_group_set_volume_control_action(ag, CONTROL_ACTION_NONE, NULL);
-+ section = state->section;
-+ if (!section) {
-+ pa_log("[%s:%u] Lvalue \"%s\" not expected in the General section.", state->filename, state->lineno, state->lvalue);
-+ return -PA_ERR_INVALID;
-+ }
-
-- else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
-- r = pa_binding_target_info_new_from_string(state->rvalue, "volume_control", &target_info);
-- if (r < 0) {
-- pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
-- goto error;
-- }
-+ if (pa_startswith(section, AUDIOGROUP_START)) {
-+ struct group *group;
-
-- audio_group_set_volume_control_action(ag, CONTROL_ACTION_BIND, target_info);
-- pa_binding_target_info_free(target_info);
-+ name = section + strlen(AUDIOGROUP_START);
-
-- } else {
-- pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
-- goto error;
-+ group = pa_hashmap_get(u->groups, name);
-+ if (!group) {
-+ r = group_new(u, name, &group);
-+ if (r < 0) {
-+ pa_log("[%s:%u] Failed to create an audio group with name \"%s\".", state->filename, state->lineno, name);
-+ return r;
- }
-+
-+ pa_hashmap_put(u->groups, (void *) group->audio_group->name, group);
- }
-- else if (strcmp(state->lvalue, "mute-control") == 0) {
-- if (pa_streq(state->rvalue, "create"))
-- audio_group_set_mute_control_action(ag, CONTROL_ACTION_CREATE, NULL);
--
-- else if (pa_streq(state->rvalue, NONE_KEYWORD))
-- audio_group_set_mute_control_action(ag, CONTROL_ACTION_NONE, NULL);
--
-- else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
-- r = pa_binding_target_info_new_from_string(state->rvalue, "mute_control", &target_info);
-- if (r < 0) {
-- pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
-- goto error;
-- }
-
-- audio_group_set_mute_control_action(ag, CONTROL_ACTION_BIND, target_info);
-- pa_binding_target_info_free(target_info);
-+ if (pa_streq(state->lvalue, "description"))
-+ pa_audio_group_set_description(group->audio_group, state->rvalue);
-
-- } else {
-- pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
-- goto error;
-- }
-+ else if (pa_streq(state->lvalue, "volume-control"))
-+ return parse_group_control(state, group, CONTROL_TYPE_VOLUME);
-+
-+ else if (pa_streq(state->lvalue, "mute-control"))
-+ return parse_group_control(state, group, CONTROL_TYPE_MUTE);
-+
-+ else {
-+ pa_log("[%s:%u] Lvalue \"%s\" not expected in the AudioGroup section.", state->filename, state->lineno, state->lvalue);
-+ return -PA_ERR_INVALID;
- }
- }
-- else if (strncmp(section, STREAM_START, strlen(STREAM_START)) == 0) {
-- char *stream_name = section + strlen(STREAM_START);
-+ else if (pa_startswith(section, STREAM_RULE_START)) {
-+ struct stream_rule *rule;
-
-- struct stream *stream = (struct stream *) pa_hashmap_get(u->unused_streams, stream_name);
-+ name = section + strlen(STREAM_RULE_START);
-
-- if (!stream) {
-- /* first item for this stream section, so create the struct */
-- stream = stream_new(u, stream_name);
-- pa_hashmap_put(u->unused_streams, stream->id, stream);
-+ rule = pa_hashmap_get(u->stream_rules, name);
-+ if (!rule) {
-+ rule = stream_rule_new(u, name);
-+ pa_hashmap_put(u->stream_rules, rule->name, rule);
- }
-
- if (pa_streq(state->lvalue, "audio-group-for-volume"))
-- stream_set_audio_group_name_for_volume(stream, *state->rvalue ? state->rvalue : NULL);
-+ stream_rule_set_group_name(rule, CONTROL_TYPE_VOLUME, state->rvalue);
-
- else if (pa_streq(state->lvalue, "audio-group-for-mute"))
-- stream_set_audio_group_name_for_mute(stream, *state->rvalue ? state->rvalue : NULL);
-+ stream_rule_set_group_name(rule, CONTROL_TYPE_MUTE, state->rvalue);
-
-- else if (strcmp(state->lvalue, "match") == 0) {
-- if (!state->rvalue)
-- goto error;
-+ else if (pa_streq(state->lvalue, "match")) {
-+ struct expression *expression;
-
-- stream->rule = parse_rule(state->rvalue);
--
-- if (!stream->rule) {
-- goto error;
-+ r = expression_from_string(state->rvalue, &expression);
-+ if (r < 0) {
-+ pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
-+ return r;
- }
-+
-+ stream_rule_set_match_expression(rule, expression);
- }
- }
-
- return 0;
--
--error:
--
-- pa_log_error("failed parsing audio group definition file");
-- return -1;
--
--#undef NONE_KEYWORD
--#undef AUDIO_GROUP_KEYWORD
--#undef BIND_KEYWORD
--#undef STREAM_START
--#undef AUDIOGROUP_START
- }
-
--static void finalize_config(struct userdata *u) {
-- const char *group_name;
-+int pa__init(pa_module *module) {
-+ pa_modargs *ma = NULL;
-+ struct userdata *u;
-+ FILE *f;
-+ char *fn = NULL;
-+ struct group *group;
- void *state;
-- struct audio_group *group;
-- const char *stream_name;
-+ const char *name;
- unsigned idx;
-- struct stream *stream;
-
-- pa_assert(u);
--
-- PA_HASHMAP_FOREACH(group_name, u->audio_group_names, state) {
-- int r;
-+ pa_assert(module);
-
-- group = pa_hashmap_remove(u->unused_audio_groups, group_name);
-- if (!group)
-- group = audio_group_new(u, group_name);
--
-- r = audio_group_put(group);
-- if (r < 0) {
-- pa_log("Failed to create audio group %s.", group_name);
-- audio_group_free(group);
-- continue;
-- }
--
-- pa_assert_se(pa_hashmap_put(u->audio_groups, group->id, group) >= 0);
-+ if (!(ma = pa_modargs_new(module->argument, valid_modargs))) {
-+ pa_log("Failed to parse module arguments");
-+ goto fail;
- }
-
-- PA_HASHMAP_FOREACH(group, u->unused_audio_groups, state)
-- pa_log_debug("Audio group %s is not used.", group->id);
--
-- pa_hashmap_free(u->unused_audio_groups);
-- u->unused_audio_groups = NULL;
--
-- pa_hashmap_free(u->audio_group_names);
-- u->audio_group_names = NULL;
--
-- PA_DYNARRAY_FOREACH(stream_name, u->stream_names, idx) {
-- stream = pa_hashmap_remove(u->unused_streams, stream_name);
-- if (!stream) {
-- pa_log("Reference to undefined stream %s, ignoring.", stream_name);
-- continue;
-- }
-+ u = module->userdata = pa_xnew0(struct userdata, 1);
-+ u->volume_api = pa_volume_api_get(module->core);
-+ u->groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-+ (pa_free_cb_t) group_free);
-+ u->stream_rules = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-+ (pa_free_cb_t) stream_rule_free);
-+ u->stream_rules_list = pa_dynarray_new(NULL);
-+ u->rules_by_stream = pa_hashmap_new(NULL, NULL);
-+ u->stream_volume_controls = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) control_free);
-+ u->stream_mute_controls = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) control_free);
-+ u->stream_put_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PUT], PA_HOOK_NORMAL, stream_put_cb,
-+ u);
-+ u->stream_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], PA_HOOK_NORMAL,
-+ stream_unlink_cb, u);
-+ u->volume_control_implementation_initialized_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_IMPLEMENTATION_INITIALIZED],
-+ PA_HOOK_NORMAL, volume_control_implementation_initialized_cb, u);
-+ u->mute_control_implementation_initialized_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_IMPLEMENTATION_INITIALIZED],
-+ PA_HOOK_NORMAL, mute_control_implementation_initialized_cb, u);
-+ u->volume_control_set_initial_volume_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_SET_INITIAL_VOLUME], PA_HOOK_NORMAL,
-+ volume_control_set_initial_volume_cb, u);
-+ u->mute_control_set_initial_mute_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_SET_INITIAL_MUTE], PA_HOOK_NORMAL,
-+ mute_control_set_initial_mute_cb, u);
-+ u->volume_control_volume_changed_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED], PA_HOOK_NORMAL,
-+ volume_control_volume_changed_cb, u);
-+ u->mute_control_mute_changed_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], PA_HOOK_NORMAL,
-+ mute_control_mute_changed_cb, u);
-+ u->volume_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK],
-+ PA_HOOK_NORMAL, volume_control_unlink_cb, u);
-+ u->mute_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK],
-+ PA_HOOK_NORMAL, mute_control_unlink_cb, u);
-+ u->stream_rule_names = pa_dynarray_new(pa_xfree);
-+
-+ f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "audio-groups.conf", "audio-groups.conf", NULL, &fn);
-+ if (f) {
-+ pa_config_item config_items[] = {
-+ { "stream-rules", parse_streams, NULL, "General" },
-+ { NULL, parse_common, NULL, NULL },
-+ { NULL, NULL, NULL, NULL },
-+ };
-
-- stream_put(stream);
-- pa_dynarray_append(u->streams, stream);
-+ pa_config_parse(fn, f, config_items, NULL, u);
-+ pa_xfree(fn);
-+ fn = NULL;
-+ fclose(f);
-+ f = NULL;
- }
-
-- PA_HASHMAP_FOREACH(stream, u->unused_streams, state)
-- pa_log_debug("Stream %s is not used.", stream->id);
--
-- pa_hashmap_free(u->unused_streams);
-- u->unused_streams = NULL;
--
-- pa_dynarray_free(u->stream_names);
-- u->stream_names = NULL;
--}
-+ PA_HASHMAP_FOREACH(group, u->groups, state)
-+ group_put(group);
-
--static bool parse_configuration(struct userdata *u, const char *filename) {
-- FILE *f;
-- char *fn = NULL;
-+ PA_DYNARRAY_FOREACH(name, u->stream_rule_names, idx) {
-+ struct stream_rule *rule;
-
-- pa_config_item table[] = {
-- { "audio-groups", parse_audio_groups, NULL, "General" },
-- { "streams", parse_streams, NULL, "General" },
-- { NULL, parse_common, NULL, NULL },
-- { NULL, NULL, NULL, NULL },
-- };
-+ rule = pa_hashmap_get(u->stream_rules, name);
-+ if (rule)
-+ pa_dynarray_append(u->stream_rules_list, rule);
-+ else
-+ pa_log("Non-existent stream rule \"%s\" referenced, ignoring.", name);
-+ }
-
-- u->audio_group_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
-- u->unused_audio_groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-- (pa_free_cb_t) audio_group_free);
-- u->stream_names = pa_dynarray_new(pa_xfree);
-- u->unused_streams = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-- (pa_free_cb_t) stream_free);
-+ pa_dynarray_free(u->stream_rule_names);
-+ u->stream_rule_names = NULL;
-
-- if (pa_is_path_absolute(filename))
-- f = pa_open_config_file(filename, NULL, NULL, &fn);
-- else {
-- char *sys_conf_file;
-+ pa_modargs_free(ma);
-
-- sys_conf_file = pa_sprintf_malloc(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "%s", filename);
-- f = pa_open_config_file(sys_conf_file, filename, NULL, &fn);
-- pa_xfree(sys_conf_file);
-- }
-+ return 0;
-
-- if (f) {
-- pa_config_parse(fn, f, table, NULL, u);
-- pa_xfree(fn);
-- fn = NULL;
-- fclose(f);
-- f = NULL;
-- }
-+fail:
-+ pa__done(module);
-
-- finalize_config(u);
-+ if (ma)
-+ pa_modargs_free(ma);
-
-- return true;
-+ return -1;
- }
-
- void pa__done(pa_module *m) {
-- struct userdata* u;
-+ struct userdata *u;
-
- pa_assert(m);
-
-@@ -1265,70 +2023,56 @@ void pa__done(pa_module *m) {
- if (!u)
- return;
-
-- if (u->new_stream_volume)
-- pa_hook_slot_free(u->new_stream_volume);
-+ if (u->mute_control_unlink_slot)
-+ pa_hook_slot_free(u->mute_control_unlink_slot);
-
-- if (u->new_stream_mute)
-- pa_hook_slot_free(u->new_stream_mute);
-+ if (u->volume_control_unlink_slot)
-+ pa_hook_slot_free(u->volume_control_unlink_slot);
-
-- if (u->streams)
-- pa_dynarray_free(u->streams);
-+ if (u->mute_control_mute_changed_slot)
-+ pa_hook_slot_free(u->mute_control_mute_changed_slot);
-
-- if (u->audio_groups)
-- pa_hashmap_free(u->audio_groups);
-+ if (u->volume_control_volume_changed_slot)
-+ pa_hook_slot_free(u->volume_control_volume_changed_slot);
-
-- if (u->api)
-- pa_volume_api_unref(u->api);
-+ if (u->mute_control_set_initial_mute_slot)
-+ pa_hook_slot_free(u->mute_control_set_initial_mute_slot);
-
-- pa_xfree(u);
--}
-+ if (u->volume_control_set_initial_volume_slot)
-+ pa_hook_slot_free(u->volume_control_set_initial_volume_slot);
-
--int pa__init(pa_module *m) {
-- pa_modargs *ma = NULL;
-- struct userdata *u;
-- const char *filename;
-+ if (u->mute_control_implementation_initialized_slot)
-+ pa_hook_slot_free(u->mute_control_implementation_initialized_slot);
-
-- pa_assert(m);
-+ if (u->volume_control_implementation_initialized_slot)
-+ pa_hook_slot_free(u->volume_control_implementation_initialized_slot);
-
-- if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
-- pa_log("Failed to parse module arguments");
-- goto error;
-- }
-+ if (u->stream_unlink_slot)
-+ pa_hook_slot_free(u->stream_unlink_slot);
-
-- u = m->userdata = pa_xnew0(struct userdata, 1);
-+ if (u->stream_put_slot)
-+ pa_hook_slot_free(u->stream_put_slot);
-
-- if (!u)
-- goto error;
-+ if (u->stream_mute_controls)
-+ pa_hashmap_free(u->stream_mute_controls);
-
-- u->api = pa_volume_api_get(m->core);
-+ if (u->stream_volume_controls)
-+ pa_hashmap_free(u->stream_volume_controls);
-
-- if (!u->api)
-- goto error;
--
-- u->audio_groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-- (pa_free_cb_t) audio_group_free);
-- u->streams = pa_dynarray_new((pa_free_cb_t) stream_free);
--
-- filename = pa_modargs_get_value(ma, "filename", AUDIO_GROUP_CONFIG);
--
-- if (!parse_configuration(u, filename))
-- goto error;
-+ if (u->rules_by_stream)
-+ pa_hashmap_free(u->rules_by_stream);
-
-- u->new_stream_volume = pa_hook_connect(&u->api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_VOLUME_CONTROL], PA_HOOK_EARLY, (pa_hook_cb_t) set_volume_control_cb, u);
-- u->new_stream_mute = pa_hook_connect(&u->api->hooks[PA_VOLUME_API_HOOK_STREAM_SET_INITIAL_MUTE_CONTROL], PA_HOOK_EARLY, (pa_hook_cb_t) set_mute_control_cb, u);
-+ if (u->stream_rules_list)
-+ pa_dynarray_free(u->stream_rules_list);
-
-- if (!u->new_stream_volume || !u->new_stream_mute)
-- goto error;
-+ if (u->stream_rules)
-+ pa_hashmap_free(u->stream_rules);
-
-- pa_modargs_free(ma);
-+ if (u->groups)
-+ pa_hashmap_free(u->groups);
-
-- return 0;
-+ if (u->volume_api)
-+ pa_volume_api_unref(u->volume_api);
-
--error:
-- pa__done(m);
--
-- if (ma)
-- pa_modargs_free(ma);
--
-- return -1;
-+ pa_xfree(u);
- }
-diff --git a/src/modules/main-volume-policy/main-volume-context.c b/src/modules/main-volume-policy/main-volume-context.c
-index 7ac35c6..9b9f9fd 100644
---- a/src/modules/main-volume-policy/main-volume-context.c
-+++ b/src/modules/main-volume-policy/main-volume-context.c
-@@ -28,32 +28,33 @@
- #include <modules/volume-api/mute-control.h>
- #include <modules/volume-api/volume-control.h>
-
--int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description,
-- pa_main_volume_context **context) {
-- pa_main_volume_context *context_local;
-+#include <pulsecore/core-util.h>
-+
-+int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, void *userdata, pa_main_volume_context **_r) {
-+ pa_main_volume_context *context;
- int r;
-
- pa_assert(policy);
- pa_assert(name);
-- pa_assert(description);
-- pa_assert(context);
-+ pa_assert(_r);
-
-- context_local = pa_xnew0(struct pa_main_volume_context, 1);
-- context_local->main_volume_policy = policy;
-- context_local->index = pa_main_volume_policy_allocate_main_volume_context_index(policy);
-+ context = pa_xnew0(struct pa_main_volume_context, 1);
-+ context->main_volume_policy = policy;
-+ context->index = pa_main_volume_policy_allocate_main_volume_context_index(policy);
-
-- r = pa_main_volume_policy_register_name(policy, name, true, &context_local->name);
-+ r = pa_main_volume_policy_register_name(policy, name, true, &context->name);
- if (r < 0)
- goto fail;
-
-- context_local->description = pa_xstrdup(description);
--
-- *context = context_local;
-+ context->description = pa_xstrdup(context->name);
-+ context->userdata = userdata;
-
-+ *_r = context;
- return 0;
-
- fail:
-- pa_main_volume_context_free(context_local);
-+ if (context)
-+ pa_main_volume_context_free(context);
-
- return r;
- }
-@@ -62,7 +63,6 @@ void pa_main_volume_context_put(pa_main_volume_context *context) {
- pa_assert(context);
-
- pa_main_volume_policy_add_main_volume_context(context->main_volume_policy, context);
--
- context->linked = true;
-
- pa_log_debug("Created main volume context #%u.", context->index);
-@@ -93,40 +93,21 @@ void pa_main_volume_context_unlink(pa_main_volume_context *context) {
- pa_log_debug("Unlinking main volume context %s.", context->name);
-
- if (context->linked)
-- pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK], context);
--
-- if (context->main_input_mute_control_binding) {
-- pa_binding_free(context->main_input_mute_control_binding);
-- context->main_input_mute_control_binding = NULL;
-- }
-+ pa_main_volume_policy_remove_main_volume_context(context->main_volume_policy, context);
-
-- if (context->main_output_mute_control_binding) {
-- pa_binding_free(context->main_output_mute_control_binding);
-- context->main_output_mute_control_binding = NULL;
-- }
--
-- if (context->main_input_volume_control_binding) {
-- pa_binding_free(context->main_input_volume_control_binding);
-- context->main_input_volume_control_binding = NULL;
-- }
--
-- if (context->main_output_volume_control_binding) {
-- pa_binding_free(context->main_output_volume_control_binding);
-- context->main_output_volume_control_binding = NULL;
-- }
-+ pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK], context);
-
- context->main_input_mute_control = NULL;
- context->main_output_mute_control = NULL;
- context->main_input_volume_control = NULL;
- context->main_output_volume_control = NULL;
--
-- pa_main_volume_policy_remove_main_volume_context(context->main_volume_policy, context);
- }
-
- void pa_main_volume_context_free(pa_main_volume_context *context) {
- pa_assert(context);
-
-- if (!context->unlinked)
-+ /* unlink() expects name to be set. */
-+ if (!context->unlinked && context->name)
- pa_main_volume_context_unlink(context);
-
- pa_xfree(context->description);
-@@ -137,13 +118,33 @@ void pa_main_volume_context_free(pa_main_volume_context *context) {
- pa_xfree(context);
- }
-
--const char *pa_main_volume_context_get_name(pa_main_volume_context *context) {
-+void pa_main_volume_context_set_description(pa_main_volume_context *context, const char *description) {
-+ char *old_description;
-+
- pa_assert(context);
-+ pa_assert(description);
-+
-+ old_description = context->description;
-+
-+ if (pa_streq(description, old_description))
-+ return;
-
-- return context->name;
-+ context->description = pa_xstrdup(description);
-+
-+ if (!context->linked || context->unlinked) {
-+ pa_xfree(old_description);
-+ return;
-+ }
-+
-+ pa_log_debug("Main volume context %s description changed from \"%s\" to \"%s\".", context->name, old_description,
-+ description);
-+ pa_xfree(old_description);
-+
-+ pa_hook_fire(&context->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_DESCRIPTION_CHANGED],
-+ context);
- }
-
--static void set_main_output_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) {
-+void pa_main_volume_context_set_main_output_volume_control(pa_main_volume_context *context, pa_volume_control *control) {
- pa_volume_control *old_control;
-
- pa_assert(context);
-@@ -158,7 +159,7 @@ static void set_main_output_volume_control_internal(pa_main_volume_context *cont
- if (!context->linked || context->unlinked)
- return;
-
-- pa_log_debug("The main output volume control of main volume context %s changed from %s to %s.", context->name,
-+ pa_log_debug("Main volume context %s main output volume control changed from %s to %s.", context->name,
- old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
-
- pa_hook_fire(&context->main_volume_policy->hooks
-@@ -166,24 +167,7 @@ static void set_main_output_volume_control_internal(pa_main_volume_context *cont
- context);
- }
-
--void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context,
-- pa_binding_target_info *target_info) {
-- pa_binding_owner_info owner_info = {
-- .userdata = context,
-- .set_value = (pa_binding_set_value_cb_t) set_main_output_volume_control_internal,
-- };
--
-- pa_assert(context);
-- pa_assert(target_info);
--
-- if (context->main_output_volume_control_binding)
-- pa_binding_free(context->main_output_volume_control_binding);
--
-- context->main_output_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
-- target_info);
--}
--
--static void set_main_input_volume_control_internal(pa_main_volume_context *context, pa_volume_control *control) {
-+void pa_main_volume_context_set_main_input_volume_control(pa_main_volume_context *context, pa_volume_control *control) {
- pa_volume_control *old_control;
-
- pa_assert(context);
-@@ -198,7 +182,7 @@ static void set_main_input_volume_control_internal(pa_main_volume_context *conte
- if (!context->linked || context->unlinked)
- return;
-
-- pa_log_debug("The main input volume control of main volume context %s changed from %s to %s.", context->name,
-+ pa_log_debug("Main volume context %s main input volume control changed from %s to %s.", context->name,
- old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
-
- pa_hook_fire(&context->main_volume_policy->hooks
-@@ -206,24 +190,7 @@ static void set_main_input_volume_control_internal(pa_main_volume_context *conte
- context);
- }
-
--void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context,
-- pa_binding_target_info *target_info) {
-- pa_binding_owner_info owner_info = {
-- .userdata = context,
-- .set_value = (pa_binding_set_value_cb_t) set_main_input_volume_control_internal,
-- };
--
-- pa_assert(context);
-- pa_assert(target_info);
--
-- if (context->main_input_volume_control_binding)
-- pa_binding_free(context->main_input_volume_control_binding);
--
-- context->main_input_volume_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
-- target_info);
--}
--
--static void set_main_output_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) {
-+void pa_main_volume_context_set_main_output_mute_control(pa_main_volume_context *context, pa_mute_control *control) {
- pa_mute_control *old_control;
-
- pa_assert(context);
-@@ -238,7 +205,7 @@ static void set_main_output_mute_control_internal(pa_main_volume_context *contex
- if (!context->linked || context->unlinked)
- return;
-
-- pa_log_debug("The main output mute control of main volume context %s changed from %s to %s.", context->name,
-+ pa_log_debug("Main volume context %s main output mute control changed from %s to %s.", context->name,
- old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
-
- pa_hook_fire(&context->main_volume_policy->hooks
-@@ -246,24 +213,7 @@ static void set_main_output_mute_control_internal(pa_main_volume_context *contex
- context);
- }
-
--void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context,
-- pa_binding_target_info *target_info) {
-- pa_binding_owner_info owner_info = {
-- .userdata = context,
-- .set_value = (pa_binding_set_value_cb_t) set_main_output_mute_control_internal,
-- };
--
-- pa_assert(context);
-- pa_assert(target_info);
--
-- if (context->main_output_mute_control_binding)
-- pa_binding_free(context->main_output_mute_control_binding);
--
-- context->main_output_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
-- target_info);
--}
--
--static void set_main_input_mute_control_internal(pa_main_volume_context *context, pa_mute_control *control) {
-+void pa_main_volume_context_set_main_input_mute_control(pa_main_volume_context *context, pa_mute_control *control) {
- pa_mute_control *old_control;
-
- pa_assert(context);
-@@ -278,48 +228,10 @@ static void set_main_input_mute_control_internal(pa_main_volume_context *context
- if (!context->linked || context->unlinked)
- return;
-
-- pa_log_debug("The main input mute control of main volume context %s changed from %s to %s.", context->name,
-+ pa_log_debug("Main volume context %s main input mute control changed from %s to %s.", context->name,
- old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
-
- pa_hook_fire(&context->main_volume_policy->hooks
- [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED],
- context);
- }
--
--void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context,
-- pa_binding_target_info *target_info) {
-- pa_binding_owner_info owner_info = {
-- .userdata = context,
-- .set_value = (pa_binding_set_value_cb_t) set_main_input_mute_control_internal,
-- };
--
-- pa_assert(context);
-- pa_assert(target_info);
--
-- if (context->main_input_mute_control_binding)
-- pa_binding_free(context->main_input_mute_control_binding);
--
-- context->main_input_mute_control_binding = pa_binding_new(context->main_volume_policy->volume_api, &owner_info,
-- target_info);
--}
--
--pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy) {
-- pa_binding_target_type *type;
--
-- pa_assert(policy);
--
-- type = pa_binding_target_type_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, policy->main_volume_contexts,
-- &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT],
-- &policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK],
-- (pa_binding_target_type_get_name_cb_t) pa_main_volume_context_get_name);
-- pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL,
-- PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_volume_control));
-- pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL,
-- PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_volume_control));
-- pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL,
-- PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_output_mute_control));
-- pa_binding_target_type_add_field(type, PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL,
-- PA_BINDING_CALCULATE_FIELD_OFFSET(pa_main_volume_context, main_input_mute_control));
--
-- return type;
--}
-diff --git a/src/modules/main-volume-policy/main-volume-context.h b/src/modules/main-volume-policy/main-volume-context.h
-index 4a0a6f7..3770168 100644
---- a/src/modules/main-volume-policy/main-volume-context.h
-+++ b/src/modules/main-volume-policy/main-volume-context.h
-@@ -24,16 +24,8 @@
-
- #include <modules/main-volume-policy/main-volume-policy.h>
-
--#include <modules/volume-api/binding.h>
--
- typedef struct pa_main_volume_context pa_main_volume_context;
-
--#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE "MainVolumeContext"
--#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL "main_output_volume_control"
--#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL "main_input_volume_control"
--#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL "main_output_mute_control"
--#define PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL "main_input_mute_control"
--
- struct pa_main_volume_context {
- pa_main_volume_policy *main_volume_policy;
- uint32_t index;
-@@ -44,32 +36,21 @@ struct pa_main_volume_context {
- pa_mute_control *main_output_mute_control;
- pa_mute_control *main_input_mute_control;
-
-- 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;
--
- bool linked;
- bool unlinked;
-+
-+ void *userdata;
- };
-
--int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, const char *description,
-- pa_main_volume_context **context);
-+int pa_main_volume_context_new(pa_main_volume_policy *policy, const char *name, void *userdata, pa_main_volume_context **_r);
- void pa_main_volume_context_put(pa_main_volume_context *context);
- void pa_main_volume_context_unlink(pa_main_volume_context *context);
- void pa_main_volume_context_free(pa_main_volume_context *context);
-
--const char *pa_main_volume_context_get_name(pa_main_volume_context *context);
--
--void pa_main_volume_context_bind_main_output_volume_control(pa_main_volume_context *context,
-- pa_binding_target_info *target_info);
--void pa_main_volume_context_bind_main_input_volume_control(pa_main_volume_context *context,
-- pa_binding_target_info *target_info);
--void pa_main_volume_context_bind_main_output_mute_control(pa_main_volume_context *context,
-- pa_binding_target_info *target_info);
--void pa_main_volume_context_bind_main_input_mute_control(pa_main_volume_context *context, pa_binding_target_info *target_info);
--
--/* Called from main-volume-policy.c only. */
--pa_binding_target_type *pa_main_volume_context_create_binding_target_type(pa_main_volume_policy *policy);
-+void pa_main_volume_context_set_description(pa_main_volume_context *context, const char *description);
-+void pa_main_volume_context_set_main_output_volume_control(pa_main_volume_context *context, pa_volume_control *control);
-+void pa_main_volume_context_set_main_input_volume_control(pa_main_volume_context *context, pa_volume_control *control);
-+void pa_main_volume_context_set_main_output_mute_control(pa_main_volume_context *context, pa_mute_control *control);
-+void pa_main_volume_context_set_main_input_mute_control(pa_main_volume_context *context, pa_mute_control *control);
-
- #endif
-diff --git a/src/modules/main-volume-policy/main-volume-policy.c b/src/modules/main-volume-policy/main-volume-policy.c
-index b0b4ede..3c0fccf 100644
---- a/src/modules/main-volume-policy/main-volume-policy.c
-+++ b/src/modules/main-volume-policy/main-volume-policy.c
-@@ -28,6 +28,7 @@
- #include <modules/main-volume-policy/main-volume-context.h>
-
- #include <pulsecore/core-util.h>
-+#include <pulsecore/namereg.h>
- #include <pulsecore/shared.h>
-
- static pa_main_volume_policy *main_volume_policy_new(pa_core *core);
-@@ -70,6 +71,46 @@ void pa_main_volume_policy_unref(pa_main_volume_policy *policy) {
- }
- }
-
-+static pa_hook_result_t volume_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
-+ pa_main_volume_policy *policy = userdata;
-+ pa_volume_control *control = call_data;
-+ pa_main_volume_context *context;
-+ void *state;
-+
-+ pa_assert(policy);
-+ pa_assert(control);
-+
-+ PA_HASHMAP_FOREACH(context, policy->main_volume_contexts, state) {
-+ if (context->main_output_volume_control == control)
-+ pa_main_volume_context_set_main_output_volume_control(context, NULL);
-+
-+ if (context->main_input_volume_control == control)
-+ pa_main_volume_context_set_main_input_volume_control(context, NULL);
-+ }
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t mute_control_unlink_cb(void *hook_data, void *call_data, void *userdata) {
-+ pa_main_volume_policy *policy = userdata;
-+ pa_mute_control *control = call_data;
-+ pa_main_volume_context *context;
-+ void *state;
-+
-+ pa_assert(policy);
-+ pa_assert(control);
-+
-+ PA_HASHMAP_FOREACH(context, policy->main_volume_contexts, state) {
-+ if (context->main_output_mute_control == control)
-+ pa_main_volume_context_set_main_output_mute_control(context, NULL);
-+
-+ if (context->main_input_mute_control == control)
-+ pa_main_volume_context_set_main_input_mute_control(context, NULL);
-+ }
-+
-+ return PA_HOOK_OK;
-+}
-+
- static pa_main_volume_policy *main_volume_policy_new(pa_core *core) {
- pa_main_volume_policy *policy;
- unsigned i;
-@@ -86,8 +127,10 @@ static pa_main_volume_policy *main_volume_policy_new(pa_core *core) {
- for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++)
- pa_hook_init(&policy->hooks[i], policy);
-
-- policy->main_volume_context_binding_target_type = pa_main_volume_context_create_binding_target_type(policy);
-- pa_volume_api_add_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type);
-+ policy->volume_control_unlink_slot = pa_hook_connect(&policy->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK],
-+ PA_HOOK_NORMAL, volume_control_unlink_cb, policy);
-+ policy->mute_control_unlink_slot = pa_hook_connect(&policy->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK],
-+ PA_HOOK_NORMAL, mute_control_unlink_cb, policy);
-
- pa_log_debug("Created a pa_main_volume_policy object.");
-
-@@ -102,10 +145,11 @@ static void main_volume_policy_free(pa_main_volume_policy *policy) {
-
- pa_log_debug("Freeing the pa_main_volume_policy object.");
-
-- if (policy->main_volume_context_binding_target_type) {
-- pa_volume_api_remove_binding_target_type(policy->volume_api, policy->main_volume_context_binding_target_type);
-- pa_binding_target_type_free(policy->main_volume_context_binding_target_type);
-- }
-+ if (policy->mute_control_unlink_slot)
-+ pa_hook_slot_free(policy->mute_control_unlink_slot);
-+
-+ if (policy->volume_control_unlink_slot)
-+ pa_hook_slot_free(policy->volume_control_unlink_slot);
-
- for (i = 0; i < PA_MAIN_VOLUME_POLICY_HOOK_MAX; i++)
- pa_hook_done(&policy->hooks[i]);
-@@ -134,19 +178,24 @@ int pa_main_volume_policy_register_name(pa_main_volume_policy *policy, const cha
- pa_assert(requested_name);
- pa_assert(registered_name);
-
-+ if (!pa_namereg_is_valid_name(requested_name)) {
-+ pa_log("Invalid name: \"%s\"", requested_name);
-+ return -PA_ERR_INVALID;
-+ }
-+
- n = pa_xstrdup(requested_name);
-
- if (pa_hashmap_put(policy->names, n, n) < 0) {
- unsigned i = 1;
-
-- pa_xfree(n);
--
- if (fail_if_already_registered) {
-+ pa_xfree(n);
- pa_log("Name %s already registered.", requested_name);
- return -PA_ERR_EXIST;
- }
-
- do {
-+ pa_xfree(n);
- i++;
- n = pa_sprintf_malloc("%s.%u", requested_name, i);
- } while (pa_hashmap_put(policy->names, n, n) < 0);
-diff --git a/src/modules/main-volume-policy/main-volume-policy.conf.example b/src/modules/main-volume-policy/main-volume-policy.conf.example
-index a4a35d3..3fcd267 100644
---- a/src/modules/main-volume-policy/main-volume-policy.conf.example
-+++ b/src/modules/main-volume-policy/main-volume-policy.conf.example
-@@ -3,7 +3,6 @@ output-volume-model = by-active-main-volume-context
- input-volume-model = by-active-main-volume-context
- output-mute-model = none
- input-mute-model = none
--main-volume-contexts = x-example-call-main-volume-context x-example-default-main-volume-context
-
- [MainVolumeContext x-example-call-main-volume-context]
- description = Call main volume context
-diff --git a/src/modules/main-volume-policy/main-volume-policy.h b/src/modules/main-volume-policy/main-volume-policy.h
-index 5cd669e..d5f6e02 100644
---- a/src/modules/main-volume-policy/main-volume-policy.h
-+++ b/src/modules/main-volume-policy/main-volume-policy.h
-@@ -22,7 +22,6 @@
- USA.
- ***/
-
--#include <modules/volume-api/binding.h>
- #include <modules/volume-api/volume-api.h>
-
- #include <pulsecore/core.h>
-@@ -35,6 +34,7 @@ typedef struct pa_main_volume_context pa_main_volume_context;
- enum {
- PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_PUT,
- PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_UNLINK,
-+ PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_DESCRIPTION_CHANGED,
- PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED,
- PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED,
- PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED,
-@@ -53,7 +53,9 @@ struct pa_main_volume_policy {
-
- uint32_t next_main_volume_context_index;
- pa_hook hooks[PA_MAIN_VOLUME_POLICY_HOOK_MAX];
-- pa_binding_target_type *main_volume_context_binding_target_type;
-+
-+ pa_hook_slot *volume_control_unlink_slot;
-+ pa_hook_slot *mute_control_unlink_slot;
- };
-
- pa_main_volume_policy *pa_main_volume_policy_get(pa_core *core);
-diff --git a/src/modules/main-volume-policy/module-main-volume-policy.c b/src/modules/main-volume-policy/module-main-volume-policy.c
-index 0a89aa7..1b7693e 100644
---- a/src/modules/main-volume-policy/module-main-volume-policy.c
-+++ b/src/modules/main-volume-policy/module-main-volume-policy.c
-@@ -27,7 +27,7 @@
-
- #include <modules/main-volume-policy/main-volume-context.h>
-
--#include <modules/volume-api/binding.h>
-+#include <modules/volume-api/audio-group.h>
- #include <modules/volume-api/volume-api.h>
-
- #include <pulse/direction.h>
-@@ -36,6 +36,9 @@
- #include <pulsecore/core-util.h>
- #include <pulsecore/i18n.h>
-
-+#define BIND_PREFIX "bind:"
-+#define BIND_AUDIO_GROUP_PREFIX BIND_PREFIX "AudioGroup:"
-+
- PA_MODULE_AUTHOR("Tanu Kaskinen");
- PA_MODULE_DESCRIPTION(_("Main volume and mute policy"));
- PA_MODULE_VERSION(PACKAGE_VERSION);
-@@ -52,6 +55,7 @@ enum model {
- };
-
- struct userdata {
-+ pa_volume_api *volume_api;
- pa_main_volume_policy *main_volume_policy;
- enum model output_volume_model;
- enum model input_volume_model;
-@@ -60,26 +64,67 @@ struct userdata {
- pa_hashmap *contexts; /* name -> struct context */
-
- pa_hook_slot *active_main_volume_context_changed_slot;
-+ pa_hook_slot *main_volume_context_main_output_volume_control_changed_slot;
-+ pa_hook_slot *main_volume_context_main_input_volume_control_changed_slot;
-+ pa_hook_slot *main_volume_context_main_output_mute_control_changed_slot;
-+ pa_hook_slot *main_volume_context_main_input_mute_control_changed_slot;
-+ pa_hook_slot *audio_group_put_slot;
-+ pa_hook_slot *audio_group_unlink_slot;
-+ pa_hook_slot *audio_group_volume_control_changed_slot;
-+ pa_hook_slot *audio_group_mute_control_changed_slot;
-+};
-
-- /* The following fields are only used during initialization. */
-- pa_hashmap *context_names; /* name -> name (hashmap-as-a-set) */
-- pa_hashmap *unused_contexts; /* name -> struct context */
-+struct control_info {
-+ /* As appropriate for this control, points to one of
-+ * - pa_main_volume_context.main_output_volume_control
-+ * - pa_main_volume_context.main_input_volume_control
-+ * - pa_main_volume_context.main_output_mute_control
-+ * - pa_main_volume_context.main_input_mute_control */
-+ void **control;
-+
-+ /* As appropriate for this control, points to one of
-+ * - userdata.output_volume_model
-+ * - userdata.input_volume_model
-+ * - userdata.output_mute_model
-+ * - userdata.input_mute_model */
-+ enum model *model;
-+
-+ /* Name of the audio group to which the context volume or mute control is
-+ * bound. If the context control is not bound to anything, this is NULL. */
-+ char *binding_target_name;
-+
-+ /* Points to the audio group to which the context volume or mute control is
-+ * bound. If the context control is not bound to anything, or it's bound
-+ * but the target doesn't currently exist, this is NULL. */
-+ pa_audio_group *binding_target;
-+
-+ /* As appropriate for this control, points to one of
-+ * - pa_main_volume_context_set_main_output_volume_control()
-+ * - pa_main_volume_context_set_main_input_volume_control()
-+ * - pa_main_volume_context_set_main_output_mute_control()
-+ * - pa_main_volume_context_set_main_input_mute_control() */
-+ void (*set_control)(pa_main_volume_context *context, void *control);
-+
-+ /* As appropriate for this control, points to one of
-+ * - pa_volume_api_set_main_output_volume_control()
-+ * - pa_volume_api_set_main_input_volume_control()
-+ * - pa_volume_api_set_main_output_mute_control()
-+ * - pa_volume_api_set_main_input_mute_control() */
-+ void (*set_volume_api_control)(pa_volume_api *api, void *control);
- };
-
- struct context {
- struct userdata *userdata;
-- char *name;
-- char *description;
-- pa_binding_target_info *main_output_volume_control_target_info;
-- pa_binding_target_info *main_input_volume_control_target_info;
-- pa_binding_target_info *main_output_mute_control_target_info;
-- pa_binding_target_info *main_input_mute_control_target_info;
- pa_main_volume_context *main_volume_context;
-+ struct control_info output_volume_info;
-+ struct control_info input_volume_info;
-+ struct control_info output_mute_info;
-+ struct control_info input_mute_info;
-
- bool unlinked;
- };
-
--static void context_unlink(struct context *context);
-+static void context_free(struct context *context);
-
- static const char *model_to_string(enum model model) {
- switch (model) {
-@@ -107,56 +152,57 @@ static int model_from_string(const char *str, enum model *model) {
- return 0;
- }
-
--static struct context *context_new(struct userdata *u, const char *name) {
-- struct context *context;
-+static int context_new(struct userdata *u, const char *name, struct context **_r) {
-+ struct context *context = NULL;
-+ int r;
-
- pa_assert(u);
- pa_assert(name);
-+ pa_assert(_r);
-
- context = pa_xnew0(struct context, 1);
- context->userdata = u;
-- context->name = pa_xstrdup(name);
-- context->description = pa_xstrdup(name);
-
-- return context;
--}
--
--static int context_put(struct context *context) {
-- int r;
--
-- pa_assert(context);
--
-- r = pa_main_volume_context_new(context->userdata->main_volume_policy, context->name, context->description,
-- &context->main_volume_context);
-+ r = pa_main_volume_context_new(u->main_volume_policy, name, u, &context->main_volume_context);
- if (r < 0)
- goto fail;
-
-- if (context->main_output_volume_control_target_info)
-- pa_main_volume_context_bind_main_output_volume_control(context->main_volume_context,
-- context->main_output_volume_control_target_info);
-+ context->output_volume_info.control = (void **) &context->main_volume_context->main_output_volume_control;
-+ context->input_volume_info.control = (void **) &context->main_volume_context->main_input_volume_control;
-+ context->output_mute_info.control = (void **) &context->main_volume_context->main_output_mute_control;
-+ context->input_mute_info.control = (void **) &context->main_volume_context->main_input_mute_control;
-
-- if (context->main_input_volume_control_target_info)
-- pa_main_volume_context_bind_main_input_volume_control(context->main_volume_context,
-- context->main_input_volume_control_target_info);
-+ context->output_volume_info.model = &u->output_volume_model;
-+ context->input_volume_info.model = &u->input_volume_model;
-+ context->output_mute_info.model = &u->output_mute_model;
-+ context->input_mute_info.model = &u->input_mute_model;
-
-- if (context->main_output_mute_control_target_info)
-- pa_main_volume_context_bind_main_output_mute_control(context->main_volume_context,
-- context->main_output_mute_control_target_info);
-+ context->output_volume_info.set_control = (void *) pa_main_volume_context_set_main_output_volume_control;
-+ context->input_volume_info.set_control = (void *) pa_main_volume_context_set_main_input_volume_control;
-+ context->output_mute_info.set_control = (void *) pa_main_volume_context_set_main_output_mute_control;
-+ context->input_mute_info.set_control = (void *) pa_main_volume_context_set_main_input_mute_control;
-
-- if (context->main_input_mute_control_target_info)
-- pa_main_volume_context_bind_main_input_mute_control(context->main_volume_context,
-- context->main_input_mute_control_target_info);
--
-- pa_main_volume_context_put(context->main_volume_context);
-+ context->output_volume_info.set_volume_api_control = (void *) pa_volume_api_set_main_output_volume_control;
-+ context->input_volume_info.set_volume_api_control = (void *) pa_volume_api_set_main_input_volume_control;
-+ context->output_mute_info.set_volume_api_control = (void *) pa_volume_api_set_main_output_mute_control;
-+ context->input_mute_info.set_volume_api_control = (void *) pa_volume_api_set_main_input_mute_control;
-
-+ *_r = context;
- return 0;
-
- fail:
-- context_unlink(context);
-+ if (context)
-+ context_free(context);
-
- return r;
- }
-
-+static void context_put(struct context *context) {
-+ pa_assert(context);
-+
-+ pa_main_volume_context_put(context->main_volume_context);
-+}
-+
- static void context_unlink(struct context *context) {
- pa_assert(context);
-
-@@ -165,10 +211,8 @@ static void context_unlink(struct context *context) {
-
- context->unlinked = true;
-
-- if (context->main_volume_context) {
-- pa_main_volume_context_free(context->main_volume_context);
-- context->main_volume_context = NULL;
-- }
-+ if (context->main_volume_context)
-+ pa_main_volume_context_unlink(context->main_volume_context);
- }
-
- static void context_free(struct context *context) {
-@@ -177,132 +221,290 @@ static void context_free(struct context *context) {
- if (!context->unlinked)
- context_unlink(context);
-
-- if (context->main_input_mute_control_target_info)
-- pa_binding_target_info_free(context->main_input_mute_control_target_info);
--
-- if (context->main_output_mute_control_target_info)
-- pa_binding_target_info_free(context->main_output_mute_control_target_info);
--
-- if (context->main_input_volume_control_target_info)
-- pa_binding_target_info_free(context->main_input_volume_control_target_info);
--
-- if (context->main_output_volume_control_target_info)
-- pa_binding_target_info_free(context->main_output_volume_control_target_info);
-+ if (context->main_volume_context)
-+ pa_main_volume_context_free(context->main_volume_context);
-
-- pa_xfree(context->description);
-- pa_xfree(context->name);
- pa_xfree(context);
- }
-
--static void context_set_description(struct context *context, const char *description) {
-- pa_assert(context);
-- pa_assert(description);
--
-- pa_xfree(context->description);
-- context->description = pa_xstrdup(description);
--}
--
--static void context_set_main_control_target_info(struct context *context, enum control_type type, pa_direction_t direction,
-- pa_binding_target_info *info) {
-+static struct control_info *context_get_control_info(struct context *context, enum control_type type,
-+ pa_direction_t direction) {
- pa_assert(context);
-
- switch (type) {
- case CONTROL_TYPE_VOLUME:
-- if (direction == PA_DIRECTION_OUTPUT) {
-- if (context->main_output_volume_control_target_info)
-- pa_binding_target_info_free(context->main_output_volume_control_target_info);
--
-- if (info)
-- context->main_output_volume_control_target_info = pa_binding_target_info_copy(info);
-- else
-- context->main_output_volume_control_target_info = NULL;
-- } else {
-- if (context->main_input_volume_control_target_info)
-- pa_binding_target_info_free(context->main_input_volume_control_target_info);
--
-- if (info)
-- context->main_input_volume_control_target_info = pa_binding_target_info_copy(info);
-- else
-- context->main_input_volume_control_target_info = NULL;
-+ switch (direction) {
-+ case PA_DIRECTION_OUTPUT:
-+ return &context->output_volume_info;
-+
-+ case PA_DIRECTION_INPUT:
-+ return &context->input_volume_info;
- }
- break;
-
- case CONTROL_TYPE_MUTE:
-- if (direction == PA_DIRECTION_OUTPUT) {
-- if (context->main_output_mute_control_target_info)
-- pa_binding_target_info_free(context->main_output_mute_control_target_info);
--
-- if (info)
-- context->main_output_mute_control_target_info = pa_binding_target_info_copy(info);
-- else
-- context->main_output_mute_control_target_info = NULL;
-- } else {
-- if (context->main_input_mute_control_target_info)
-- pa_binding_target_info_free(context->main_input_mute_control_target_info);
--
-- if (info)
-- context->main_input_mute_control_target_info = pa_binding_target_info_copy(info);
-- else
-- context->main_input_mute_control_target_info = NULL;
-+ switch (direction) {
-+ case PA_DIRECTION_OUTPUT:
-+ return &context->output_mute_info;
-+
-+ case PA_DIRECTION_INPUT:
-+ return &context->input_mute_info;
- }
- break;
- }
-+
-+ pa_assert_not_reached();
-+}
-+
-+static void context_set_binding_target(struct context *context, enum control_type type, pa_direction_t direction,
-+ pa_audio_group *group) {
-+ struct control_info *info;
-+ void *control = NULL;
-+
-+ pa_assert(context);
-+
-+ info = context_get_control_info(context, type, direction);
-+ info->binding_target = group;
-+
-+ if (group) {
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ control = group->volume_control;
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ control = group->mute_control;
-+ break;
-+ }
-+ }
-+
-+ info->set_control(context->main_volume_context, control);
-+}
-+
-+static void context_set_binding_target_name(struct context *context, enum control_type type, pa_direction_t direction,
-+ const char *name) {
-+ struct control_info *info;
-+ pa_audio_group *group = NULL;
-+
-+ pa_assert(context);
-+
-+ info = context_get_control_info(context, type, direction);
-+
-+ if (pa_safe_streq(name, info->binding_target_name))
-+ return;
-+
-+ pa_xfree(info->binding_target_name);
-+ info->binding_target_name = pa_xstrdup(name);
-+
-+ if (name)
-+ group = pa_hashmap_get(context->userdata->volume_api->audio_groups, name);
-+
-+ context_set_binding_target(context, type, direction, group);
- }
-
- static pa_hook_result_t active_main_volume_context_changed_cb(void *hook_data, void *call_data, void *userdata) {
- struct userdata *u = userdata;
- pa_main_volume_context *context;
-- pa_volume_api *api;
-- pa_binding_target_info *info;
-
- pa_assert(u);
-
- context = u->main_volume_policy->active_main_volume_context;
-- api = u->main_volume_policy->volume_api;
-
- if (u->output_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
-- if (context) {
-- info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
-- PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_VOLUME_CONTROL);
-- pa_volume_api_bind_main_output_volume_control(api, info);
-- pa_binding_target_info_free(info);
-- } else
-- pa_volume_api_set_main_output_volume_control(api, NULL);
-+ if (context)
-+ pa_volume_api_set_main_output_volume_control(u->volume_api, context->main_output_volume_control);
-+ else
-+ pa_volume_api_set_main_output_volume_control(u->volume_api, NULL);
- }
-
- if (u->input_volume_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
-- if (context) {
-- info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
-- PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_VOLUME_CONTROL);
-- pa_volume_api_bind_main_input_volume_control(api, info);
-- pa_binding_target_info_free(info);
-- } else
-- pa_volume_api_set_main_input_volume_control(api, NULL);
-+ if (context)
-+ pa_volume_api_set_main_input_volume_control(u->volume_api, context->main_input_volume_control);
-+ else
-+ pa_volume_api_set_main_input_volume_control(u->volume_api, NULL);
- }
-
- if (u->output_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
-- if (context) {
-- info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
-- PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_OUTPUT_MUTE_CONTROL);
-- pa_volume_api_bind_main_output_mute_control(api, info);
-- pa_binding_target_info_free(info);
-- } else
-- pa_volume_api_set_main_output_mute_control(api, NULL);
-+ if (context)
-+ pa_volume_api_set_main_output_mute_control(u->volume_api, context->main_output_mute_control);
-+ else
-+ pa_volume_api_set_main_output_mute_control(u->volume_api, NULL);
- }
-
- if (u->input_mute_model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT) {
-- if (context) {
-- info = pa_binding_target_info_new(PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_TYPE, context->name,
-- PA_MAIN_VOLUME_CONTEXT_BINDING_TARGET_FIELD_MAIN_INPUT_MUTE_CONTROL);
-- pa_volume_api_bind_main_input_mute_control(api, info);
-- pa_binding_target_info_free(info);
-- } else
-- pa_volume_api_set_main_input_mute_control(api, NULL);
-+ if (context)
-+ pa_volume_api_set_main_input_mute_control(u->volume_api, context->main_input_mute_control);
-+ else
-+ pa_volume_api_set_main_input_mute_control(u->volume_api, NULL);
-+ }
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static void handle_context_control_change(struct context *context, enum control_type type, pa_direction_t direction) {
-+ struct control_info *info;
-+
-+ pa_assert(context);
-+
-+ info = context_get_control_info(context, type, direction);
-+
-+ if (*info->model == MODEL_BY_ACTIVE_MAIN_VOLUME_CONTEXT
-+ && context->userdata->main_volume_policy->active_main_volume_context == context->main_volume_context)
-+ info->set_volume_api_control(context->userdata->volume_api, *info->control);
-+}
-+
-+static pa_hook_result_t main_volume_context_main_output_volume_control_changed_cb(void *hook_data, void *call_data,
-+ void *userdata) {
-+ pa_main_volume_context *context = call_data;
-+
-+ pa_assert(context);
-+
-+ handle_context_control_change(context->userdata, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t main_volume_context_main_input_volume_control_changed_cb(void *hook_data, void *call_data,
-+ void *userdata) {
-+ pa_main_volume_context *context = call_data;
-+
-+ pa_assert(context);
-+
-+ handle_context_control_change(context->userdata, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t main_volume_context_main_output_mute_control_changed_cb(void *hook_data, void *call_data,
-+ void *userdata) {
-+ pa_main_volume_context *context = call_data;
-+
-+ pa_assert(context);
-+
-+ handle_context_control_change(context->userdata, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t main_volume_context_main_input_mute_control_changed_cb(void *hook_data, void *call_data,
-+ void *userdata) {
-+ pa_main_volume_context *context = call_data;
-+
-+ pa_assert(context);
-+
-+ handle_context_control_change(context->userdata, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t audio_group_put_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_audio_group *group = call_data;
-+ struct context *context;
-+ void *state;
-+
-+ pa_assert(u);
-+ pa_assert(group);
-+
-+ PA_HASHMAP_FOREACH(context, u->contexts, state) {
-+ if (context->output_volume_info.binding_target_name
-+ && pa_streq(context->output_volume_info.binding_target_name, group->name))
-+ context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT, group);
-+
-+ if (context->input_volume_info.binding_target_name
-+ && pa_streq(context->input_volume_info.binding_target_name, group->name))
-+ context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT, group);
-+
-+ if (context->output_mute_info.binding_target_name
-+ && pa_streq(context->output_mute_info.binding_target_name, group->name))
-+ context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT, group);
-+
-+ if (context->input_mute_info.binding_target_name
-+ && pa_streq(context->input_mute_info.binding_target_name, group->name))
-+ context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT, group);
-+ }
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t audio_group_unlink_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_audio_group *group = call_data;
-+ struct context *context;
-+ void *state;
-+
-+ pa_assert(u);
-+ pa_assert(group);
-+
-+ PA_HASHMAP_FOREACH(context, u->contexts, state) {
-+ if (context->output_volume_info.binding_target == group)
-+ context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT, NULL);
-+
-+ if (context->input_volume_info.binding_target == group)
-+ context_set_binding_target(context, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT, NULL);
-+
-+ if (context->output_mute_info.binding_target == group)
-+ context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT, NULL);
-+
-+ if (context->input_mute_info.binding_target == group)
-+ context_set_binding_target(context, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT, NULL);
- }
-
- return PA_HOOK_OK;
- }
-
-+static void handle_audio_group_control_change(struct userdata *u, pa_audio_group *group, enum control_type type) {
-+ struct context *context;
-+ void *state;
-+
-+ pa_assert(u);
-+ pa_assert(group);
-+
-+ PA_HASHMAP_FOREACH(context, u->contexts, state) {
-+ switch (type) {
-+ case CONTROL_TYPE_VOLUME:
-+ if (context->output_volume_info.binding_target == group)
-+ pa_main_volume_context_set_main_output_volume_control(context->main_volume_context, group->volume_control);
-+
-+ if (context->input_volume_info.binding_target == group)
-+ pa_main_volume_context_set_main_input_volume_control(context->main_volume_context, group->volume_control);
-+ break;
-+
-+ case CONTROL_TYPE_MUTE:
-+ if (context->output_mute_info.binding_target == group)
-+ pa_main_volume_context_set_main_output_mute_control(context->main_volume_context, group->mute_control);
-+
-+ if (context->input_mute_info.binding_target == group)
-+ pa_main_volume_context_set_main_input_mute_control(context->main_volume_context, group->mute_control);
-+ break;
-+ }
-+ }
-+}
-+
-+static pa_hook_result_t audio_group_volume_control_changed_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_audio_group *group = call_data;
-+
-+ pa_assert(u);
-+ pa_assert(group);
-+
-+ handle_audio_group_control_change(u, group, CONTROL_TYPE_VOLUME);
-+
-+ return PA_HOOK_OK;
-+}
-+
-+static pa_hook_result_t audio_group_mute_control_changed_cb(void *hook_data, void *call_data, void *userdata) {
-+ struct userdata *u = userdata;
-+ pa_audio_group *group = call_data;
-+
-+ pa_assert(u);
-+ pa_assert(group);
-+
-+ handle_audio_group_control_change(u, group, CONTROL_TYPE_MUTE);
-+
-+ return PA_HOOK_OK;
-+}
-+
- static int parse_model(pa_config_parser_state *state) {
- int r;
-
-@@ -315,105 +517,81 @@ static int parse_model(pa_config_parser_state *state) {
- return r;
- }
-
--static int parse_main_volume_contexts(pa_config_parser_state *state) {
-- struct userdata *u;
-- char *name;
-- const char *split_state = NULL;
--
-- pa_assert(state);
--
-- u = state->userdata;
--
-- while ((name = pa_split_spaces(state->rvalue, &split_state)))
-- pa_hashmap_put(u->context_names, name, name);
--
-- return 0;
--}
--
--static struct context *get_context(struct userdata *u, const char *section) {
-+static int get_context(struct userdata *u, const char *section, struct context **_r) {
- const char *name;
- struct context *context;
-
- pa_assert(u);
-
- if (!section)
-- return NULL;
-+ return -PA_ERR_INVALID;
-
- if (!pa_startswith(section, "MainVolumeContext "))
-- return NULL;
-+ return -PA_ERR_INVALID;
-
- name = section + 18;
-
-- context = pa_hashmap_get(u->unused_contexts, name);
-+ context = pa_hashmap_get(u->contexts, name);
- if (!context) {
-- context = context_new(u, name);
-- pa_hashmap_put(u->unused_contexts, context->name, context);
-+ int r;
-+
-+ r = context_new(u, name, &context);
-+ if (r < 0)
-+ return r;
-+
-+ pa_hashmap_put(u->contexts, (void *) context->main_volume_context->name, context);
- }
-
-- return context;
-+ *_r = context;
-+ return 0;
- }
-
- static int parse_description(pa_config_parser_state *state) {
- struct userdata *u;
-+ int r;
- struct context *context;
-
- pa_assert(state);
-
- u = state->userdata;
-
-- context = get_context(u, state->section);
-- if (!context) {
-- pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue,
-+ r = get_context(u, state->section, &context);
-+ if (r < 0) {
-+ pa_log("[%s:%u] Couldn't get main volume context for section \"%s\".", state->filename, state->lineno,
- pa_strnull(state->section));
- return -PA_ERR_INVALID;
- }
-
-- context_set_description(context, state->rvalue);
-+ pa_main_volume_context_set_description(context->main_volume_context, state->rvalue);
-
- return 0;
- }
-
--static const char *get_target_field_name(enum control_type type) {
-- switch (type) {
-- case CONTROL_TYPE_VOLUME:
-- return "volume_control";
--
-- case CONTROL_TYPE_MUTE:
-- return "mute_control";
-- }
--
-- pa_assert_not_reached();
--}
--
--static int parse_main_control(pa_config_parser_state *state, enum control_type type, pa_direction_t direction) {
-+static int parse_control(pa_config_parser_state *state, enum control_type type, pa_direction_t direction) {
- struct userdata *u;
-+ int r;
- struct context *context;
-
- pa_assert(state);
-
- u = state->userdata;
-
-- context = get_context(u, state->section);
-- if (!context) {
-- pa_log("[%s:%u] Key \"%s\" not expected in section %s.", state->filename, state->lineno, state->lvalue,
-+ r = get_context(u, state->section, &context);
-+ if (r < 0) {
-+ pa_log("[%s:%u] Couldn't get main volume context for section \"%s\".", state->filename, state->lineno,
- pa_strnull(state->section));
- return -PA_ERR_INVALID;
- }
-
- if (pa_streq(state->rvalue, "none"))
-- context_set_main_control_target_info(context, type, direction, NULL);
-- else if (pa_startswith(state->rvalue, "bind:")) {
-- int r;
-- pa_binding_target_info *info;
--
-- r = pa_binding_target_info_new_from_string(state->rvalue, get_target_field_name(type), &info);
-- if (r < 0) {
-- pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
-- return r;
-+ context_set_binding_target_name(context, type, direction, NULL);
-+ else if (pa_startswith(state->rvalue, BIND_PREFIX)) {
-+ if (pa_startswith(state->rvalue, BIND_AUDIO_GROUP_PREFIX))
-+ context_set_binding_target_name(context, type, direction, state->rvalue + strlen(BIND_AUDIO_GROUP_PREFIX));
-+ else {
-+ pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
-+ return -PA_ERR_INVALID;
- }
--
-- context_set_main_control_target_info(context, type, direction, info);
-- pa_binding_target_info_free(info);
- } else {
- pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
- return -PA_ERR_INVALID;
-@@ -425,69 +603,38 @@ static int parse_main_control(pa_config_parser_state *state, enum control_type t
- static int parse_main_output_volume_control(pa_config_parser_state *state) {
- pa_assert(state);
-
-- return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT);
-+ return parse_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_OUTPUT);
- }
-
- static int parse_main_input_volume_control(pa_config_parser_state *state) {
- pa_assert(state);
-
-- return parse_main_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT);
-+ return parse_control(state, CONTROL_TYPE_VOLUME, PA_DIRECTION_INPUT);
- }
-
- static int parse_main_output_mute_control(pa_config_parser_state *state) {
- pa_assert(state);
-
-- return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT);
-+ return parse_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_OUTPUT);
- }
-
- static int parse_main_input_mute_control(pa_config_parser_state *state) {
- pa_assert(state);
-
-- return parse_main_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT);
--}
--
--static void finalize_config(struct userdata *u) {
-- const char *context_name;
-- void *state;
-- struct context *context;
--
-- pa_assert(u);
--
-- PA_HASHMAP_FOREACH(context_name, u->context_names, state) {
-- int r;
--
-- context = pa_hashmap_remove(u->unused_contexts, context_name);
-- if (!context)
-- context = context_new(u, context_name);
--
-- r = context_put(context);
-- if (r < 0) {
-- pa_log_warn("Failed to create main volume context %s.", context_name);
-- context_free(context);
-- continue;
-- }
--
-- pa_assert_se(pa_hashmap_put(u->contexts, context->name, context) >= 0);
-- }
--
-- PA_HASHMAP_FOREACH(context, u->unused_contexts, state)
-- pa_log_debug("Main volume context %s is not used.", context->name);
--
-- pa_hashmap_free(u->unused_contexts);
-- u->unused_contexts = NULL;
--
-- pa_hashmap_free(u->context_names);
-- u->context_names = NULL;
-+ return parse_control(state, CONTROL_TYPE_MUTE, PA_DIRECTION_INPUT);
- }
-
- int pa__init(pa_module *module) {
- struct userdata *u;
- FILE *f;
- char *fn = NULL;
-+ struct context *context;
-+ void *state;
-
- pa_assert(module);
-
- u = module->userdata = pa_xnew0(struct userdata, 1);
-+ u->volume_api = pa_volume_api_get(module->core);
- u->main_volume_policy = pa_main_volume_policy_get(module->core);
- u->output_volume_model = MODEL_NONE;
- u->input_volume_model = MODEL_NONE;
-@@ -498,9 +645,32 @@ int pa__init(pa_module *module) {
- u->active_main_volume_context_changed_slot =
- pa_hook_connect(&u->main_volume_policy->hooks[PA_MAIN_VOLUME_POLICY_HOOK_ACTIVE_MAIN_VOLUME_CONTEXT_CHANGED],
- PA_HOOK_NORMAL, active_main_volume_context_changed_cb, u);
-- u->context_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
-- u->unused_contexts = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-- (pa_free_cb_t) context_free);
-+ u->main_volume_context_main_output_volume_control_changed_slot =
-+ pa_hook_connect(&u->main_volume_policy->hooks
-+ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED],
-+ PA_HOOK_NORMAL, main_volume_context_main_output_volume_control_changed_cb, u);
-+ u->main_volume_context_main_input_volume_control_changed_slot =
-+ pa_hook_connect(&u->main_volume_policy->hooks
-+ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_VOLUME_CONTROL_CHANGED],
-+ PA_HOOK_NORMAL, main_volume_context_main_input_volume_control_changed_cb, u);
-+ u->main_volume_context_main_output_mute_control_changed_slot =
-+ pa_hook_connect(&u->main_volume_policy->hooks
-+ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_OUTPUT_MUTE_CONTROL_CHANGED],
-+ PA_HOOK_NORMAL, main_volume_context_main_output_mute_control_changed_cb, u);
-+ u->main_volume_context_main_input_mute_control_changed_slot =
-+ pa_hook_connect(&u->main_volume_policy->hooks
-+ [PA_MAIN_VOLUME_POLICY_HOOK_MAIN_VOLUME_CONTEXT_MAIN_INPUT_MUTE_CONTROL_CHANGED],
-+ PA_HOOK_NORMAL, main_volume_context_main_input_mute_control_changed_cb, u);
-+ u->audio_group_put_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_PUT], PA_HOOK_NORMAL,
-+ audio_group_put_cb, u);
-+ u->audio_group_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], PA_HOOK_NORMAL,
-+ audio_group_unlink_cb, u);
-+ u->audio_group_volume_control_changed_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED], PA_HOOK_NORMAL,
-+ audio_group_volume_control_changed_cb, u);
-+ u->audio_group_mute_control_changed_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED], PA_HOOK_NORMAL,
-+ audio_group_mute_control_changed_cb, u);
-
- f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "main-volume-policy.conf", "main-volume-policy.conf", NULL, &fn);
- if (f) {
-@@ -509,7 +679,6 @@ int pa__init(pa_module *module) {
- { "input-volume-model", parse_model, &u->input_volume_model, "General" },
- { "output-mute-model", parse_model, &u->output_mute_model, "General" },
- { "input-mute-model", parse_model, &u->input_mute_model, "General" },
-- { "main-volume-contexts", parse_main_volume_contexts, NULL, "General" },
- { "description", parse_description, NULL, NULL },
- { "main-output-volume-control", parse_main_output_volume_control, NULL, NULL },
- { "main-input-volume-control", parse_main_input_volume_control, NULL, NULL },
-@@ -525,7 +694,8 @@ int pa__init(pa_module *module) {
- f = NULL;
- }
-
-- finalize_config(u);
-+ PA_HASHMAP_FOREACH(context, u->contexts, state)
-+ context_put(context);
-
- pa_log_debug("Output volume model: %s", model_to_string(u->output_volume_model));
- pa_log_debug("Input volume model: %s", model_to_string(u->input_volume_model));
-@@ -544,6 +714,30 @@ void pa__done(pa_module *module) {
- if (!u)
- return;
-
-+ if (u->audio_group_mute_control_changed_slot)
-+ pa_hook_slot_free(u->audio_group_mute_control_changed_slot);
-+
-+ if (u->audio_group_volume_control_changed_slot)
-+ pa_hook_slot_free(u->audio_group_volume_control_changed_slot);
-+
-+ if (u->audio_group_unlink_slot)
-+ pa_hook_slot_free(u->audio_group_unlink_slot);
-+
-+ if (u->audio_group_put_slot)
-+ pa_hook_slot_free(u->audio_group_put_slot);
-+
-+ if (u->main_volume_context_main_input_mute_control_changed_slot)
-+ pa_hook_slot_free(u->main_volume_context_main_input_mute_control_changed_slot);
-+
-+ if (u->main_volume_context_main_output_mute_control_changed_slot)
-+ pa_hook_slot_free(u->main_volume_context_main_output_mute_control_changed_slot);
-+
-+ if (u->main_volume_context_main_input_volume_control_changed_slot)
-+ pa_hook_slot_free(u->main_volume_context_main_input_volume_control_changed_slot);
-+
-+ if (u->main_volume_context_main_output_volume_control_changed_slot)
-+ pa_hook_slot_free(u->main_volume_context_main_output_volume_control_changed_slot);
-+
- if (u->active_main_volume_context_changed_slot)
- pa_hook_slot_free(u->active_main_volume_context_changed_slot);
-
-@@ -553,5 +747,8 @@ void pa__done(pa_module *module) {
- if (u->main_volume_policy)
- pa_main_volume_policy_unref(u->main_volume_policy);
-
-+ if (u->volume_api)
-+ pa_volume_api_unref(u->volume_api);
-+
- pa_xfree(u);
- }
-diff --git a/src/modules/volume-api/audio-group.c b/src/modules/volume-api/audio-group.c
-index 76bfa69..66e0f8a 100644
---- a/src/modules/volume-api/audio-group.c
-+++ b/src/modules/volume-api/audio-group.c
-@@ -29,34 +29,33 @@
-
- #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 pa_audio_group_new(pa_volume_api *api, const char *name, pa_audio_group **_r) {
-+ pa_audio_group *group = NULL;
- int r;
-
- pa_assert(api);
- pa_assert(name);
-- pa_assert(description);
-- pa_assert(group);
-+ pa_assert(_r);
-
-- group_local = pa_xnew0(pa_audio_group, 1);
-- group_local->volume_api = api;
-- group_local->index = pa_volume_api_allocate_audio_group_index(api);
-+ group = pa_xnew0(pa_audio_group, 1);
-+ group->volume_api = api;
-+ group->index = pa_volume_api_allocate_audio_group_index(api);
-
-- r = pa_volume_api_register_name(api, name, true, &group_local->name);
-+ r = pa_volume_api_register_name(api, name, true, &group->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;
-+ group->description = pa_xstrdup(group->name);
-+ group->proplist = pa_proplist_new();
-+ group->volume_streams = pa_hashmap_new(NULL, NULL);
-+ group->mute_streams = pa_hashmap_new(NULL, NULL);
-
-+ *_r = group;
- return 0;
-
- fail:
-- pa_audio_group_free(group_local);
-+ if (group)
-+ pa_audio_group_free(group);
-
- return r;
- }
-@@ -68,7 +67,6 @@ void pa_audio_group_put(pa_audio_group *group) {
- 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);
-@@ -99,9 +97,9 @@ void pa_audio_group_unlink(pa_audio_group *group) {
- 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);
-
-- pa_volume_api_remove_audio_group(group->volume_api, group);
-+ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK], group);
-
- while ((stream = pa_hashmap_first(group->mute_streams)))
- pas_stream_set_audio_group_for_mute(stream, NULL);
-@@ -109,34 +107,15 @@ void pa_audio_group_unlink(pa_audio_group *group) {
- 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;
-- }
-+ pa_audio_group_set_mute_control(group, NULL);
-+ pa_audio_group_set_volume_control(group, NULL);
- }
-
- void pa_audio_group_free(pa_audio_group *group) {
- pa_assert(group);
-
-- if (!group->unlinked)
-+ /* unlink() expects name to be set. */
-+ if (!group->unlinked && group->name)
- pa_audio_group_unlink(group);
-
- if (group->mute_streams)
-@@ -156,133 +135,33 @@ void pa_audio_group_free(pa_audio_group *group) {
- pa_xfree(group);
- }
-
--const char *pa_audio_group_get_name(pa_audio_group *group) {
-- pa_assert(group);
-+void pa_audio_group_set_description(pa_audio_group *group, const char *description) {
-+ char *old_description;
-
-- 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);
-+ pa_assert(description);
-
-- 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;
-+ old_description = group->description;
-
-- pa_assert(control);
-+ if (pa_streq(description, old_description))
-+ return;
-
-- group = control->userdata;
-+ group->description = pa_xstrdup(description);
-
-- PA_HASHMAP_FOREACH(stream, group->mute_streams, state) {
-- if (stream->own_mute_control)
-- pa_mute_control_set_mute(stream->own_mute_control, control->mute);
-+ if (!group->linked || group->unlinked) {
-+ pa_xfree(old_description);
-+ return;
- }
--}
-
--void pa_audio_group_set_have_own_mute_control(pa_audio_group *group, bool have) {
-- pa_assert(group);
-+ pa_log_debug("The description of audio group %s changed from \"%s\" to \"%s\".", group->name, old_description,
-+ description);
-+ pa_xfree(old_description);
-
-- if (have == group->have_own_mute_control)
-- return;
-+ pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_DESCRIPTION_CHANGED], group);
-
-- 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) {
-+void pa_audio_group_set_volume_control(pa_audio_group *group, pa_volume_control *control) {
- pa_volume_control *old_control;
-
- pa_assert(group);
-@@ -292,14 +171,8 @@ static void set_volume_control_internal(pa_audio_group *group, pa_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;
-
-@@ -309,18 +182,7 @@ static void set_volume_control_internal(pa_audio_group *group, pa_volume_control
- 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) {
-+void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control) {
- pa_mute_control *old_control;
-
- pa_assert(group);
-@@ -330,14 +192,8 @@ static void set_mute_control_internal(pa_audio_group *group, pa_mute_control *co
- 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;
-
-@@ -347,57 +203,11 @@ static void set_mute_control_internal(pa_audio_group *group, pa_mute_control *co
- 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) {
-@@ -405,8 +215,6 @@ void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stre
- 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) {
-@@ -414,11 +222,6 @@ void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream) {
- 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) {
-@@ -426,23 +229,4 @@ void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream
- 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
-index 41591ba..02db3eb 100644
---- a/src/modules/volume-api/audio-group.h
-+++ b/src/modules/volume-api/audio-group.h
-@@ -22,7 +22,6 @@
- USA.
- ***/
-
--#include <modules/volume-api/binding.h>
- #include <modules/volume-api/mute-control.h>
- #include <modules/volume-api/volume-control.h>
-
-@@ -32,10 +31,6 @@
-
- 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;
-@@ -44,13 +39,7 @@ struct pa_audio_group {
- 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) */
-
-@@ -58,28 +47,22 @@ struct pa_audio_group {
- bool unlinked;
- };
-
--int pa_audio_group_new(pa_volume_api *api, const char *name, const char *description, pa_audio_group **group);
-+int pa_audio_group_new(pa_volume_api *api, const char *name, pa_audio_group **_r);
- 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);
-+/* Called by the audio group implementation. */
-+void pa_audio_group_set_description(pa_audio_group *group, const char *description);
- 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. */
-+/* Called by sstream.c only. If you want to assign a stream to an audio group, use
-+ * pas_stream_set_audio_group_for_volume() and
-+ * pas_stream_set_audio_group_for_mute(). */
- 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
-deleted file mode 100644
-index 6e73119..0000000
---- a/src/modules/volume-api/binding.c
-+++ /dev/null
-@@ -1,386 +0,0 @@
--/***
-- 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
-deleted file mode 100644
-index ba4dea8..0000000
---- a/src/modules/volume-api/binding.h
-+++ /dev/null
-@@ -1,128 +0,0 @@
--#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
-index 0317fb6..75545dd 100644
---- a/src/modules/volume-api/bvolume.h
-+++ b/src/modules/volume-api/bvolume.h
-@@ -29,13 +29,16 @@ 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 pa_ext_volume_api_bvolume_init
- #define pa_bvolume_init_mono pa_ext_volume_api_bvolume_init_mono
-+#define pa_bvolume_parse_balance pa_ext_volume_api_bvolume_parse_balance
- #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_balance_to_string pa_ext_volume_api_bvolume_balance_to_string
-
- #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
-diff --git a/src/modules/volume-api/device-creator.c b/src/modules/volume-api/device-creator.c
-index f35fab0..fc486f8 100644
---- a/src/modules/volume-api/device-creator.c
-+++ b/src/modules/volume-api/device-creator.c
-@@ -59,6 +59,8 @@ struct device_volume_control {
- pa_hook_slot *volume_changed_slot;
- };
-
-+static void device_volume_control_free(struct device_volume_control *control);
-+
- struct device_mute_control {
- struct device *device;
- pa_mute_control *mute_control;
-@@ -68,6 +70,8 @@ struct device_mute_control {
- pa_hook_slot *mute_changed_slot;
- };
-
-+static void device_mute_control_free(struct device_mute_control *control);
-+
- struct device {
- pa_device_creator *creator;
- enum device_type type;
-@@ -85,6 +89,8 @@ struct device {
- struct device *monitor;
- };
-
-+static void device_free(struct device *device);
-+
- static const char *device_type_from_icon_name(const char *icon_name) {
- if (!icon_name)
- return NULL;
-@@ -168,112 +174,6 @@ static const char *get_source_description(pa_source *source) {
- 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;
-@@ -309,24 +209,55 @@ static pa_hook_result_t sink_or_source_volume_changed_cb(void *hook_data, void *
-
- if (sink)
- pa_bvolume_from_cvolume(&bvolume, &sink->reference_volume, &sink->channel_map);
-- else
-+ else if (source)
- pa_bvolume_from_cvolume(&bvolume, &source->reference_volume, &source->channel_map);
-+ else
-+ pa_assert_not_reached();
-
-- pa_volume_control_volume_changed(control->volume_control, &bvolume, true, true);
-+ pa_volume_control_set_volume(control->volume_control, &bvolume, true, true);
-
- return PA_HOOK_OK;
- }
-
--static void volume_control_set_initial_volume_cb(pa_volume_control *c) {
-+static int volume_control_set_volume_cb(pa_volume_control *c, const pa_bvolume *original_volume,
-+ const pa_bvolume *remapped_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(original_volume);
-+ pa_assert(remapped_volume);
-
- 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_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 = remapped_volume->volume;
-+
-+ if (set_balance)
-+ pa_bvolume_copy_balance(&bvolume, remapped_volume);
-+
-+ pa_bvolume_to_cvolume(&bvolume, &cvolume);
-
- switch (device->type) {
- case DEVICE_TYPE_PORT:
-@@ -345,44 +276,91 @@ static void volume_control_set_initial_volume_cb(pa_volume_control *c) {
- pa_sink_set_volume(device->sink, &cvolume, true, true);
- break;
- }
-+
-+ return 0;
- }
-
--static void device_volume_control_put(struct device_volume_control *control) {
-- struct device *device;
-+static int device_volume_control_new(struct device *device, struct device_volume_control **_r) {
-+ struct device_volume_control *control = NULL;
-+ const char *name = NULL;
- pa_bvolume volume;
-+ bool convertible_to_dB = false;
-+ int r;
-
-- pa_assert(control);
-+ pa_assert(device);
-+ pa_assert(_r);
-
-- device = control->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) {
- 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);
-+ convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME;
- } 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);
-+ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME;
- }
-+
- break;
-
- case DEVICE_TYPE_PORT_MONITOR:
-- case DEVICE_TYPE_SOURCE:
-+ name = "port-monitor-volume-control";
- 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);
-+ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME;
- break;
-
- case DEVICE_TYPE_SINK:
-+ name = "sink-volume-control";
- 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);
-+ convertible_to_dB = device->sink->flags & PA_SINK_DECIBEL_VOLUME;
-+ break;
-+
-+ case DEVICE_TYPE_SOURCE:
-+ name = "source-volume-control";
-+ 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);
-+ convertible_to_dB = device->source->flags & PA_SOURCE_DECIBEL_VOLUME;
- break;
- }
-
-- pa_volume_control_put(control->volume_control, &volume, volume_control_set_initial_volume_cb);
-+ r = pa_volume_control_new(device->creator->volume_api, name, false, &control->volume_control);
-+ if (r < 0)
-+ goto fail;
-+
-+ pa_volume_control_set_description(control->volume_control, device->device->description);
-+ pa_volume_control_set_channel_map(control->volume_control, &volume.channel_map);
-+ pa_volume_control_set_volume(control->volume_control, &volume, true, true);
-+ pa_volume_control_set_convertible_to_dB(control->volume_control, convertible_to_dB);
-+ control->volume_control->set_volume = volume_control_set_volume_cb;
-+ control->volume_control->userdata = control;
-+
-+ *_r = control;
-+ return 0;
-+
-+fail:
-+ if (control)
-+ device_volume_control_free(control);
-+
-+ return r;
-+}
-+
-+static void device_volume_control_put(struct device_volume_control *control) {
-+ pa_assert(control);
-+
-+ pa_volume_control_put(control->volume_control);
- }
-
- static void device_volume_control_unlink(struct device_volume_control *control) {
-@@ -395,11 +373,6 @@ static void device_volume_control_unlink(struct device_volume_control *control)
-
- 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) {
-@@ -411,71 +384,10 @@ static void device_volume_control_free(struct device_volume_control *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;
-+ if (control->volume_changed_slot)
-+ pa_hook_slot_free(control->volume_changed_slot);
-
-- return control;
-+ pa_xfree(control);
- }
-
- static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *call_data, void *userdata) {
-@@ -518,13 +430,13 @@ static pa_hook_result_t sink_or_source_mute_changed_cb(void *hook_data, void *ca
- else
- pa_assert_not_reached();
-
-- pa_mute_control_mute_changed(control->mute_control, mute);
-+ pa_mute_control_set_mute(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;
-+static int mute_control_set_mute_cb(pa_mute_control *c, bool mute) {
-+ struct device_mute_control *control;
- struct device *device;
-
- pa_assert(c);
-@@ -535,32 +447,40 @@ static void mute_control_set_initial_mute_cb(pa_mute_control *c) {
- switch (device->type) {
- case DEVICE_TYPE_PORT:
- if (device->port->direction == PA_DIRECTION_OUTPUT)
-- pa_sink_set_mute(device->sink, c->mute, true);
-+ pa_sink_set_mute(device->sink, mute, true);
- else
-- pa_source_set_mute(device->source, c->mute, true);
-+ pa_source_set_mute(device->source, mute, true);
- break;
-
- case DEVICE_TYPE_PORT_MONITOR:
- case DEVICE_TYPE_SOURCE:
-- pa_source_set_mute(device->source, c->mute, true);
-+ pa_source_set_mute(device->source, mute, true);
- break;
-
- case DEVICE_TYPE_SINK:
-- pa_sink_set_mute(device->sink, c->mute, true);
-+ pa_sink_set_mute(device->sink, mute, true);
- break;
- }
-+
-+ return 0;
- }
-
--static void device_mute_control_put(struct device_mute_control *control) {
-- struct device *device;
-+static int device_mute_control_new(struct device *device, struct device_mute_control **_r) {
-+ struct device_mute_control *control = NULL;
-+ const char *name = NULL;
- bool mute = false;
-+ int r;
-
-- pa_assert(control);
-+ pa_assert(device);
-+ pa_assert(_r);
-
-- device = control->device;
-+ control = pa_xnew0(struct device_mute_control, 1);
-+ control->device = device;
-
- switch (device->type) {
- case DEVICE_TYPE_PORT:
-+ name = "port-mute-control";
-+
- 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);
-@@ -573,20 +493,50 @@ static void device_mute_control_put(struct device_mute_control *control) {
- 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],
-+ name = "port-monitor-mute-control";
-+ 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_SINK:
-+ name = "sink-mute-control";
- 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;
-+
-+ case DEVICE_TYPE_SOURCE:
-+ name = "source-mute-control";
-+ 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;
- }
-
-- pa_mute_control_put(control->mute_control, mute, true, mute_control_set_initial_mute_cb);
-+ r = pa_mute_control_new(device->creator->volume_api, name, false, &control->mute_control);
-+ if (r < 0)
-+ goto fail;
-+
-+ pa_mute_control_set_description(control->mute_control, device->device->description);
-+ pa_mute_control_set_mute(control->mute_control, mute);
-+ control->mute_control->set_mute = mute_control_set_mute_cb;
-+ control->mute_control->userdata = control;
-+
-+ *_r = control;
-+ return 0;
-+
-+fail:
-+ if (control)
-+ device_mute_control_free(control);
-+
-+ return r;
-+}
-+
-+static void device_mute_control_put(struct device_mute_control *control) {
-+ pa_assert(control);
-+
-+ pa_mute_control_put(control->mute_control);
- }
-
- static void device_mute_control_unlink(struct device_mute_control *control) {
-@@ -599,11 +549,6 @@ static void device_mute_control_unlink(struct device_mute_control *control) {
-
- 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) {
-@@ -615,6 +560,9 @@ static void device_mute_control_free(struct device_mute_control *control) {
- if (control->mute_control)
- pa_mute_control_free(control->mute_control);
-
-+ if (control->mute_changed_slot)
-+ pa_hook_slot_free(control->mute_changed_slot);
-+
- pa_xfree(control);
- }
-
-@@ -707,22 +655,24 @@ static pa_hook_result_t sink_or_source_proplist_changed_cb(void *hook_data, void
- }
-
- 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);
-+ pa_volume_control_set_description(device->volume_control->volume_control, description);
-+ pa_mute_control_set_description(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) {
-+static int device_new(pa_device_creator *creator, enum device_type type, void *core_device, struct device **_r) {
- 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;
-+ int r;
-
- pa_assert(creator);
- pa_assert(core_device);
-+ pa_assert(_r);
-
- device = pa_xnew0(struct device, 1);
- device->creator = creator;
-@@ -767,18 +717,20 @@ static struct device *device_new(pa_device_creator *creator, enum device_type ty
- break;
- }
-
-- device->device = pa_device_new(creator->volume_api, name, description, direction, &device_type, device_type ? 1 : 0);
-+ r = pa_device_new(creator->volume_api, name, description, direction, &device_type, device_type ? 1 : 0, &device->device);
- pa_xfree(description);
-+ if (r < 0)
-+ goto fail;
-
- if (create_volume_and_mute_controls) {
-- device->volume_control = device_volume_control_new(device);
-- device->mute_control = device_mute_control_new(device);
-+ device_volume_control_new(device, &device->volume_control);
-+ device_mute_control_new(device, &device->mute_control);
- }
-
- switch (type) {
- case DEVICE_TYPE_PORT:
- if (device->port->direction == PA_DIRECTION_OUTPUT)
-- device->monitor = device_new(creator, DEVICE_TYPE_PORT_MONITOR, device->port);
-+ device_new(creator, DEVICE_TYPE_PORT_MONITOR, device->port, &device->monitor);
- break;
-
- case DEVICE_TYPE_PORT_MONITOR:
-@@ -795,7 +747,14 @@ static struct device *device_new(pa_device_creator *creator, enum device_type ty
- break;
- }
-
-- return device;
-+ *_r = device;
-+ return 0;
-+
-+fail:
-+ if (device)
-+ device_free(device);
-+
-+ return r;
- }
-
- static pa_hook_result_t port_active_changed_cb(void *hook_data, void *call_data, void *userdata) {
-@@ -825,25 +784,36 @@ static pa_hook_result_t port_active_changed_cb(void *hook_data, void *call_data,
- pa_assert_not_reached();
- }
-
-- if (should_have_volume_and_mute_controls && !device->volume_control) {
-- pa_assert(!device->mute_control);
-+ if (should_have_volume_and_mute_controls) {
-+ int r;
-
-- 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);
-+ if (!device->volume_control) {
-+ r = device_volume_control_new(device, &device->volume_control);
-+ if (r >= 0) {
-+ 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 (!device->mute_control) {
-+ r = device_mute_control_new(device, &device->mute_control);
-+ if (r >= 0) {
-+ 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);
-+ if (!should_have_volume_and_mute_controls) {
-+ if (device->mute_control) {
-+ device_mute_control_free(device->mute_control);
-+ device->mute_control = NULL;
-+ }
-
-- device_mute_control_free(device->mute_control);
-- device->mute_control = NULL;
-- device_volume_control_free(device->volume_control);
-- device->volume_control = NULL;
-+ if (device->volume_control) {
-+ device_volume_control_free(device->volume_control);
-+ device->volume_control = NULL;
-+ }
- }
-
- return PA_HOOK_OK;
-@@ -928,6 +898,7 @@ static void device_free(struct device *device) {
-
- static void create_device(pa_device_creator *creator, enum device_type type, void *core_device) {
- struct device *device;
-+ int r;
-
- pa_assert(creator);
- pa_assert(core_device);
-@@ -956,9 +927,11 @@ static void create_device(pa_device_creator *creator, enum device_type type, voi
- }
- }
-
-- device = device_new(creator, type, core_device);
-- pa_hashmap_put(creator->devices, core_device, device);
-- device_put(device);
-+ r = device_new(creator, type, core_device, &device);
-+ if (r >= 0) {
-+ 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) {
-diff --git a/src/modules/volume-api/device.c b/src/modules/volume-api/device.c
-index ea496ba..c1a580c 100644
---- a/src/modules/volume-api/device.c
-+++ b/src/modules/volume-api/device.c
-@@ -32,20 +32,26 @@
-
- #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;
-+int 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 **_r) {
-+ pa_device *device = NULL;
-+ int r;
- unsigned i;
-
- pa_assert(api);
- pa_assert(name);
- pa_assert(description);
- pa_assert(device_types || n_device_types == 0);
-+ pa_assert(_r);
-
- 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);
-+
-+ r = pa_volume_api_register_name(api, name, false, &device->name);
-+ if (r < 0)
-+ goto fail;
-+
- device->description = pa_xstrdup(description);
- device->direction = direction;
- device->device_types = pa_dynarray_new(pa_xfree);
-@@ -57,7 +63,14 @@ pa_device *pa_device_new(pa_volume_api *api, const char *name, const char *descr
- device->use_default_volume_control = true;
- device->use_default_mute_control = true;
-
-- return device;
-+ *_r = device;
-+ return 0;
-+
-+fail:
-+ if (device)
-+ pa_device_free(device);
-+
-+ return r;
- }
-
- void pa_device_put(pa_device *device, pa_volume_control *default_volume_control, pa_mute_control *default_mute_control) {
-@@ -84,7 +97,6 @@ void pa_device_put(pa_device *device, pa_volume_control *default_volume_control,
- }
-
- 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),
-@@ -120,35 +132,21 @@ void pa_device_unlink(pa_device *device) {
- 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);
-+ 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;
-- }
-+ pa_hook_fire(&device->volume_api->hooks[PA_VOLUME_API_HOOK_DEVICE_UNLINK], device);
-
-- 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;
-- }
-+ pa_device_set_mute_control(device, NULL);
-+ pa_device_set_default_mute_control(device, NULL);
-+ pa_device_set_volume_control(device, NULL);
-+ pa_device_set_default_volume_control(device, NULL);
- }
-
- void pa_device_free(pa_device *device) {
- pa_assert(device);
-
-- if (!device->unlinked)
-+ /* unlink() expects name to be set. */
-+ if (!device->unlinked && device->name)
- pa_device_unlink(device);
-
- if (device->proplist)
-diff --git a/src/modules/volume-api/device.h b/src/modules/volume-api/device.h
-index 9eac7e9..8bd5158 100644
---- a/src/modules/volume-api/device.h
-+++ b/src/modules/volume-api/device.h
-@@ -51,8 +51,8 @@ struct pa_device {
- 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);
-+int 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 **_r);
- 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);
-diff --git a/src/modules/volume-api/inidb.c b/src/modules/volume-api/inidb.c
-new file mode 100644
-index 0000000..8116e72
---- /dev/null
-+++ b/src/modules/volume-api/inidb.c
-@@ -0,0 +1,553 @@
-+/***
-+ 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 "inidb.h"
-+
-+#include <pulse/mainloop-api.h>
-+#include <pulse/rtclock.h>
-+#include <pulse/timeval.h>
-+#include <pulse/xmalloc.h>
-+
-+#include <pulsecore/conf-parser.h>
-+#include <pulsecore/core-error.h>
-+#include <pulsecore/core-rtclock.h>
-+#include <pulsecore/core-util.h>
-+#include <pulsecore/hashmap.h>
-+#include <pulsecore/macro.h>
-+#include <pulsecore/namereg.h>
-+
-+#include <errno.h>
-+
-+#define SAVE_INTERVAL_USEC (10 * PA_USEC_PER_SEC)
-+
-+struct pa_inidb {
-+ pa_core *core;
-+ char *name;
-+ char *file_path;
-+ char *tmp_file_path;
-+ pa_hashmap *tables; /* table name -> pa_inidb_table */
-+ pa_time_event *time_event;
-+ bool failed;
-+ void *userdata;
-+};
-+
-+struct pa_inidb_table {
-+ pa_inidb *db;
-+ char *name;
-+ pa_hashmap *columns; /* column name -> column */
-+ pa_hashmap *rows; /* row id -> pa_inidb_row */
-+ pa_inidb_get_object_cb_t get_object;
-+};
-+
-+struct column {
-+ char *name;
-+ pa_inidb_parse_cb_t parse;
-+};
-+
-+struct pa_inidb_row {
-+ char *id;
-+ char *header;
-+ pa_hashmap *cells; /* column name -> cell */
-+};
-+
-+struct pa_inidb_cell {
-+ pa_inidb *db;
-+ struct column *column;
-+ char *value;
-+ char *assignment;
-+};
-+
-+static void save(pa_inidb *db);
-+
-+static pa_inidb_table *table_new(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb);
-+static void table_free(pa_inidb_table *table);
-+static pa_inidb_row *table_add_row_internal(pa_inidb_table *table, const char *row_id);
-+
-+static struct column *column_new(const char *name, pa_inidb_parse_cb_t parse_cb);
-+static void column_free(struct column *column);
-+
-+static pa_inidb_row *row_new(pa_inidb_table *table, const char *id);
-+static void row_free(pa_inidb_row *row);
-+
-+static pa_inidb_cell *cell_new(pa_inidb *db, struct column *column);
-+static void cell_free(pa_inidb_cell *cell);
-+static void cell_set_value_internal(pa_inidb_cell *cell, const char *value);
-+
-+static int parse_assignment(pa_config_parser_state *state) {
-+ pa_inidb *db;
-+ const char *end_of_table_name;
-+ size_t table_name_len;
-+ char *table_name;
-+ pa_inidb_table *table;
-+ const char *row_id;
-+ pa_inidb_row *row;
-+ int r;
-+ void *object;
-+ struct column *column;
-+ pa_inidb_cell *cell;
-+
-+ pa_assert(state);
-+
-+ db = state->userdata;
-+
-+ /* FIXME: pa_config_parser should be improved so that it could parse the
-+ * table name and row id for us in the section header. */
-+ end_of_table_name = strchr(state->section, ' ');
-+ if (!end_of_table_name) {
-+ pa_log("[%s:%u] Failed to parse table name and row id in section \"%s\"", state->filename, state->lineno,
-+ state->section);
-+ return -PA_ERR_INVALID;
-+ }
-+
-+ table_name_len = end_of_table_name - state->section;
-+ table_name = pa_xstrndup(state->section, table_name_len);
-+ table = pa_hashmap_get(db->tables, table_name);
-+ if (!table)
-+ pa_log("[%s:%u] Unknown table name: \"%s\"", state->filename, state->lineno, table_name);
-+ pa_xfree(table_name);
-+ if (!table)
-+ return -PA_ERR_INVALID;
-+
-+ row_id = end_of_table_name + 1;
-+ if (!pa_namereg_is_valid_name(row_id)) {
-+ pa_log("[%s:%u] Invalid row id: \"%s\"", state->filename, state->lineno, row_id);
-+ return -PA_ERR_INVALID;
-+ }
-+
-+ /* This is not strictly necessary, but we do this to avoid saving the
-+ * database when there is no actual change. Without this, the get_object()
-+ * callback would cause redundant saving whenever creating new objects. */
-+ if (!(row = pa_hashmap_get(table->rows, row_id)))
-+ row = table_add_row_internal(table, row_id);
-+
-+ r = table->get_object(db, row_id, &object);
-+ if (r < 0) {
-+ pa_log("[%s:%u] Failed to create object %s.", state->filename, state->lineno, row_id);
-+ return r;
-+ }
-+
-+ column = pa_hashmap_get(table->columns, state->lvalue);
-+ if (!column) {
-+ pa_log("[%s:%u] Unknown column name: \"%s\"", state->filename, state->lineno, state->lvalue);
-+ return -PA_ERR_INVALID;
-+ }
-+
-+ /* This is not strictly necessary, but we do this to avoid saving the
-+ * database when there is no actual change. Without this, the parse()
-+ * callback would cause redundant saving whenever setting the cell value
-+ * for the first time. */
-+ cell = pa_hashmap_get(row->cells, column->name);
-+ cell_set_value_internal(cell, state->rvalue);
-+
-+ r = column->parse(db, state->rvalue, object);
-+ if (r < 0) {
-+ pa_log("[%s:%u] Failed to parse %s value \"%s\".", state->filename, state->lineno, column->name, state->rvalue);
-+ return r;
-+ }
-+
-+ return 0;
-+}
-+
-+pa_inidb *pa_inidb_new(pa_core *core, const char *name, void *userdata) {
-+ pa_inidb *db;
-+ int r;
-+
-+ pa_assert(core);
-+ pa_assert(name);
-+
-+ db = pa_xnew0(pa_inidb, 1);
-+ db->core = core;
-+ db->name = pa_xstrdup(name);
-+
-+ r = pa_append_to_config_home_dir(name, true, &db->file_path);
-+ if (r < 0) {
-+ pa_log("Failed to find the file location for database \"%s\". The database will start empty, and updates will not be "
-+ "saved on disk.", name);
-+ db->failed = true;
-+ }
-+
-+ if (db->file_path)
-+ db->tmp_file_path = pa_sprintf_malloc("%s.tmp", db->file_path);
-+
-+ db->tables = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-+ (pa_free_cb_t) table_free);
-+ db->userdata = userdata;
-+
-+ return db;
-+}
-+
-+void pa_inidb_free(pa_inidb *db) {
-+ pa_assert(db);
-+
-+ if (db->time_event) {
-+ db->core->mainloop->time_free(db->time_event);
-+ save(db);
-+ }
-+
-+ if (db->tables)
-+ pa_hashmap_free(db->tables);
-+
-+ pa_xfree(db->tmp_file_path);
-+ pa_xfree(db->file_path);
-+ pa_xfree(db->name);
-+ pa_xfree(db);
-+}
-+
-+void *pa_inidb_get_userdata(pa_inidb *db) {
-+ pa_assert(db);
-+
-+ return db->userdata;
-+}
-+
-+pa_inidb_table *pa_inidb_add_table(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb) {
-+ pa_inidb_table *table;
-+
-+ pa_assert(db);
-+ pa_assert(name);
-+ pa_assert(get_object_cb);
-+
-+ table = table_new(db, name, get_object_cb);
-+ pa_assert_se(pa_hashmap_put(db->tables, table->name, table) >= 0);
-+
-+ return table;
-+}
-+
-+void pa_inidb_load(pa_inidb *db) {
-+ unsigned n_config_items;
-+ pa_inidb_table *table;
-+ void *state;
-+ pa_config_item *config_items;
-+ unsigned i;
-+
-+ pa_assert(db);
-+
-+ if (db->failed)
-+ return;
-+
-+ n_config_items = 0;
-+ PA_HASHMAP_FOREACH(table, db->tables, state)
-+ n_config_items += pa_hashmap_size(table->columns);
-+
-+ config_items = pa_xnew0(pa_config_item, n_config_items + 1);
-+
-+ i = 0;
-+ PA_HASHMAP_FOREACH(table, db->tables, state) {
-+ struct column *column;
-+ void *state2;
-+
-+ PA_HASHMAP_FOREACH(column, table->columns, state2) {
-+ config_items[i].lvalue = column->name;
-+ config_items[i].parse = parse_assignment;
-+ i++;
-+ }
-+ }
-+
-+ pa_config_parse(db->file_path, NULL, config_items, NULL, db);
-+ pa_xfree(config_items);
-+}
-+
-+static void save(pa_inidb *db) {
-+ FILE *f;
-+ pa_inidb_table *table;
-+ void *state;
-+ int r;
-+
-+ pa_assert(db);
-+
-+ if (db->failed)
-+ return;
-+
-+ f = pa_fopen_cloexec(db->tmp_file_path, "w");
-+ if (!f) {
-+ pa_log("pa_fopen_cloexec() failed: %s", pa_cstrerror(errno));
-+ goto fail;
-+ }
-+
-+ PA_HASHMAP_FOREACH(table, db->tables, state) {
-+ pa_inidb_row *row;
-+ void *state2;
-+
-+ PA_HASHMAP_FOREACH(row, table->rows, state2) {
-+ size_t len;
-+ size_t items_written;
-+ pa_inidb_cell *cell;
-+ void *state3;
-+
-+ len = strlen(row->header);
-+ items_written = fwrite(row->header, len, 1, f);
-+
-+ if (items_written != 1) {
-+ pa_log("fwrite() failed: %s", pa_cstrerror(errno));
-+ goto fail;
-+ }
-+
-+ PA_HASHMAP_FOREACH(cell, row->cells, state3) {
-+ if (!cell->assignment)
-+ continue;
-+
-+ len = strlen(cell->assignment);
-+ items_written = fwrite(cell->assignment, len, 1, f);
-+
-+ if (items_written != 1) {
-+ pa_log("fwrite() failed: %s", pa_cstrerror(errno));
-+ goto fail;
-+ }
-+ }
-+
-+ items_written = fwrite("\n", 1, 1, f);
-+
-+ if (items_written != 1) {
-+ pa_log("fwrite() failed: %s", pa_cstrerror(errno));
-+ goto fail;
-+ }
-+ }
-+ }
-+
-+ r = fclose(f);
-+ if (r < 0) {
-+ pa_log("fclose() failed: %s", pa_cstrerror(errno));
-+ goto fail;
-+ }
-+
-+ r = rename(db->tmp_file_path, db->file_path);
-+ if (r < 0) {
-+ pa_log("rename() failed: %s", pa_cstrerror(errno));
-+ goto fail;
-+ }
-+
-+ pa_log_debug("Database \"%s\" saved.", db->name);
-+
-+ return;
-+
-+fail:
-+ if (f)
-+ fclose(f);
-+
-+ db->failed = true;
-+ pa_log("Saving database \"%s\" failed, current and future database changes will not be written to the disk.", db->name);
-+}
-+
-+static pa_inidb_table *table_new(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb) {
-+ pa_inidb_table *table;
-+
-+ pa_assert(db);
-+ pa_assert(name);
-+ pa_assert(get_object_cb);
-+
-+ table = pa_xnew0(pa_inidb_table, 1);
-+ table->db = db;
-+ table->name = pa_xstrdup(name);
-+ table->columns = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-+ (pa_free_cb_t) column_free);
-+ table->rows = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-+ (pa_free_cb_t) row_free);
-+ table->get_object = get_object_cb;
-+
-+ return table;
-+}
-+
-+static void table_free(pa_inidb_table *table) {
-+ pa_assert(table);
-+
-+ if (table->rows)
-+ pa_hashmap_free(table->rows);
-+
-+ if (table->columns)
-+ pa_hashmap_free(table->columns);
-+
-+ pa_xfree(table->name);
-+ pa_xfree(table);
-+}
-+
-+void pa_inidb_table_add_column(pa_inidb_table *table, const char *name, pa_inidb_parse_cb_t parse_cb) {
-+ struct column *column;
-+
-+ pa_assert(table);
-+ pa_assert(name);
-+ pa_assert(parse_cb);
-+
-+ column = column_new(name, parse_cb);
-+ pa_assert_se(pa_hashmap_put(table->columns, column->name, column) >= 0);
-+}
-+
-+static pa_inidb_row *table_add_row_internal(pa_inidb_table *table, const char *row_id) {
-+ pa_inidb_row *row;
-+
-+ pa_assert(table);
-+ pa_assert(row_id);
-+
-+ row = row_new(table, row_id);
-+ pa_assert_se(pa_hashmap_put(table->rows, row->id, row) >= 0);
-+
-+ return row;
-+}
-+
-+static void time_cb(pa_mainloop_api *api, pa_time_event *event, const struct timeval *tv, void *userdata) {
-+ pa_inidb *db = userdata;
-+
-+ pa_assert(api);
-+ pa_assert(db);
-+
-+ api->time_free(event);
-+ db->time_event = NULL;
-+
-+ save(db);
-+}
-+
-+static void trigger_save(pa_inidb *db) {
-+ struct timeval tv;
-+
-+ pa_assert(db);
-+
-+ if (db->time_event)
-+ return;
-+
-+ pa_timeval_rtstore(&tv, pa_rtclock_now() + SAVE_INTERVAL_USEC, true);
-+ db->time_event = db->core->mainloop->time_new(db->core->mainloop, &tv, time_cb, db);
-+}
-+
-+pa_inidb_row *pa_inidb_table_add_row(pa_inidb_table *table, const char *row_id) {
-+ pa_inidb_row *row;
-+
-+ pa_assert(table);
-+ pa_assert(row_id);
-+
-+ row = pa_hashmap_get(table->rows, row_id);
-+ if (row)
-+ return row;
-+
-+ row = table_add_row_internal(table, row_id);
-+ trigger_save(table->db);
-+
-+ return row;
-+}
-+
-+static struct column *column_new(const char *name, pa_inidb_parse_cb_t parse_cb) {
-+ struct column *column;
-+
-+ pa_assert(name);
-+ pa_assert(parse_cb);
-+
-+ column = pa_xnew(struct column, 1);
-+ column->name = pa_xstrdup(name);
-+ column->parse = parse_cb;
-+
-+ return column;
-+}
-+
-+static void column_free(struct column *column) {
-+ pa_assert(column);
-+
-+ pa_xfree(column->name);
-+ pa_xfree(column);
-+}
-+
-+static pa_inidb_row *row_new(pa_inidb_table *table, const char *id) {
-+ pa_inidb_row *row;
-+ struct column *column;
-+ void *state;
-+
-+ pa_assert(table);
-+ pa_assert(id);
-+
-+ row = pa_xnew0(pa_inidb_row, 1);
-+ row->id = pa_xstrdup(id);
-+ row->header = pa_sprintf_malloc("[%s %s]\n", table->name, id);
-+ row->cells = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
-+ (pa_free_cb_t) cell_free);
-+
-+ PA_HASHMAP_FOREACH(column, table->columns, state) {
-+ pa_inidb_cell *cell;
-+
-+ cell = cell_new(table->db, column);
-+ pa_hashmap_put(row->cells, cell->column->name, cell);
-+ }
-+
-+ return row;
-+}
-+
-+static void row_free(pa_inidb_row *row) {
-+ pa_assert(row);
-+
-+ pa_xfree(row->header);
-+ pa_xfree(row->id);
-+ pa_xfree(row);
-+}
-+
-+pa_inidb_cell *pa_inidb_row_get_cell(pa_inidb_row *row, const char *column_name) {
-+ pa_inidb_cell *cell;
-+
-+ pa_assert(row);
-+ pa_assert(column_name);
-+
-+ pa_assert_se(cell = pa_hashmap_get(row->cells, column_name));
-+
-+ return cell;
-+}
-+
-+static pa_inidb_cell *cell_new(pa_inidb *db, struct column *column) {
-+ pa_inidb_cell *cell;
-+
-+ pa_assert(db);
-+ pa_assert(column);
-+
-+ cell = pa_xnew0(pa_inidb_cell, 1);
-+ cell->db = db;
-+ cell->column = column;
-+
-+ return cell;
-+}
-+
-+static void cell_free(pa_inidb_cell *cell) {
-+ pa_assert(cell);
-+
-+ pa_xfree(cell->assignment);
-+ pa_xfree(cell->value);
-+ pa_xfree(cell);
-+}
-+
-+static void cell_set_value_internal(pa_inidb_cell *cell, const char *value) {
-+ pa_assert(cell);
-+ pa_assert(value);
-+
-+ pa_xfree(cell->value);
-+ cell->value = pa_xstrdup(value);
-+
-+ pa_xfree(cell->assignment);
-+ if (value)
-+ cell->assignment = pa_sprintf_malloc("%s = %s\n", cell->column->name, value);
-+ else
-+ cell->assignment = NULL;
-+}
-+
-+void pa_inidb_cell_set_value(pa_inidb_cell *cell, const char *value) {
-+ pa_assert(cell);
-+
-+ if (pa_safe_streq(value, cell->value))
-+ return;
-+
-+ cell_set_value_internal(cell, value);
-+ trigger_save(cell->db);
-+}
-diff --git a/src/modules/volume-api/inidb.h b/src/modules/volume-api/inidb.h
-new file mode 100644
-index 0000000..ded73ba
---- /dev/null
-+++ b/src/modules/volume-api/inidb.h
-@@ -0,0 +1,54 @@
-+#ifndef fooinidbhfoo
-+#define fooinidbhfoo
-+
-+/***
-+ 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_inidb pa_inidb;
-+typedef struct pa_inidb_cell pa_inidb_cell;
-+typedef struct pa_inidb_row pa_inidb_row;
-+typedef struct pa_inidb_table pa_inidb_table;
-+
-+/* If there's no object with the given name, the implementation is expected to
-+ * create a new object (or at least try to). */
-+typedef int (*pa_inidb_get_object_cb_t)(pa_inidb *db, const char *name, void **_r);
-+
-+/* The implementation is expected to parse the value, and set the parsed value
-+ * on the object. */
-+typedef int (*pa_inidb_parse_cb_t)(pa_inidb *db, const char *value, void *object);
-+
-+pa_inidb *pa_inidb_new(pa_core *core, const char *name, void *userdata);
-+void pa_inidb_free(pa_inidb *db);
-+
-+void *pa_inidb_get_userdata(pa_inidb *db);
-+pa_inidb_table *pa_inidb_add_table(pa_inidb *db, const char *name, pa_inidb_get_object_cb_t get_object_cb);
-+void pa_inidb_load(pa_inidb *db);
-+
-+void pa_inidb_table_add_column(pa_inidb_table *table, const char *name, pa_inidb_parse_cb_t parse_cb);
-+pa_inidb_row *pa_inidb_table_add_row(pa_inidb_table *table, const char *row_id);
-+
-+pa_inidb_cell *pa_inidb_row_get_cell(pa_inidb_row *row, const char *column_name);
-+
-+void pa_inidb_cell_set_value(pa_inidb_cell *cell, const char *value);
-+
-+#endif
-diff --git a/src/modules/volume-api/module-volume-api.c b/src/modules/volume-api/module-volume-api.c
-index 845ac09..7b112f6 100644
---- a/src/modules/volume-api/module-volume-api.c
-+++ b/src/modules/volume-api/module-volume-api.c
-@@ -51,6 +51,7 @@ struct userdata {
- pa_hook_slot *volume_control_unlink_slot;
- pa_hook_slot *volume_control_description_changed_slot;
- pa_hook_slot *volume_control_volume_changed_slot;
-+ pa_hook_slot *volume_control_convertible_to_db_changed_slot;
- pa_hook_slot *mute_control_put_slot;
- pa_hook_slot *mute_control_unlink_slot;
- pa_hook_slot *mute_control_description_changed_slot;
-@@ -63,10 +64,13 @@ struct userdata {
- pa_hook_slot *stream_put_slot;
- pa_hook_slot *stream_unlink_slot;
- pa_hook_slot *stream_description_changed_slot;
-+ pa_hook_slot *stream_proplist_changed_slot;
- pa_hook_slot *stream_volume_control_changed_slot;
-+ pa_hook_slot *stream_relative_volume_control_changed_slot;
- pa_hook_slot *stream_mute_control_changed_slot;
- pa_hook_slot *audio_group_put_slot;
- pa_hook_slot *audio_group_unlink_slot;
-+ pa_hook_slot *audio_group_description_changed_slot;
- pa_hook_slot *audio_group_volume_control_changed_slot;
- pa_hook_slot *audio_group_mute_control_changed_slot;
- pa_hook_slot *main_output_volume_control_changed_slot;
-@@ -1247,6 +1251,9 @@ int pa__init(pa_module *module) {
- u->volume_control_volume_changed_slot =
- pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED],
- PA_HOOK_NORMAL, volume_control_event_cb, u);
-+ u->volume_control_convertible_to_db_changed_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_CONVERTIBLE_TO_DB_CHANGED], PA_HOOK_NORMAL,
-+ volume_control_event_cb, u);
- u->mute_control_put_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_PUT],
- PA_HOOK_NORMAL, mute_control_put_cb, u);
- u->mute_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK],
-@@ -1277,9 +1284,14 @@ int pa__init(pa_module *module) {
- u->stream_description_changed_slot =
- pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED], PA_HOOK_NORMAL,
- stream_event_cb, u);
-+ u->stream_proplist_changed_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PROPLIST_CHANGED],
-+ PA_HOOK_NORMAL, stream_event_cb, u);
- u->stream_volume_control_changed_slot =
- pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED],
- PA_HOOK_NORMAL, stream_event_cb, u);
-+ u->stream_relative_volume_control_changed_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_RELATIVE_VOLUME_CONTROL_CHANGED],
-+ PA_HOOK_NORMAL, stream_event_cb, u);
- u->stream_mute_control_changed_slot =
- pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED], PA_HOOK_NORMAL,
- stream_event_cb, u);
-@@ -1287,6 +1299,9 @@ int pa__init(pa_module *module) {
- PA_HOOK_NORMAL, audio_group_put_cb, u);
- u->audio_group_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_UNLINK],
- PA_HOOK_NORMAL, audio_group_unlink_cb, u);
-+ u->audio_group_description_changed_slot =
-+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_DESCRIPTION_CHANGED], PA_HOOK_NORMAL,
-+ audio_group_event_cb, u);
- u->audio_group_volume_control_changed_slot =
- pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_VOLUME_CONTROL_CHANGED],
- PA_HOOK_NORMAL, audio_group_event_cb, u);
-@@ -1352,6 +1367,9 @@ void pa__done(pa_module *module) {
- if (u->audio_group_volume_control_changed_slot)
- pa_hook_slot_free(u->audio_group_volume_control_changed_slot);
-
-+ if (u->audio_group_description_changed_slot)
-+ pa_hook_slot_free(u->audio_group_description_changed_slot);
-+
- if (u->audio_group_unlink_slot)
- pa_hook_slot_free(u->audio_group_unlink_slot);
-
-@@ -1361,9 +1379,15 @@ void pa__done(pa_module *module) {
- if (u->stream_mute_control_changed_slot)
- pa_hook_slot_free(u->stream_mute_control_changed_slot);
-
-+ if (u->stream_relative_volume_control_changed_slot)
-+ pa_hook_slot_free(u->stream_relative_volume_control_changed_slot);
-+
- if (u->stream_volume_control_changed_slot)
- pa_hook_slot_free(u->stream_volume_control_changed_slot);
-
-+ if (u->stream_proplist_changed_slot)
-+ pa_hook_slot_free(u->stream_proplist_changed_slot);
-+
- if (u->stream_description_changed_slot)
- pa_hook_slot_free(u->stream_description_changed_slot);
-
-@@ -1400,6 +1424,9 @@ void pa__done(pa_module *module) {
- if (u->mute_control_put_slot)
- pa_hook_slot_free(u->mute_control_put_slot);
-
-+ if (u->volume_control_convertible_to_db_changed_slot)
-+ pa_hook_slot_free(u->volume_control_convertible_to_db_changed_slot);
-+
- if (u->volume_control_volume_changed_slot)
- pa_hook_slot_free(u->volume_control_volume_changed_slot);
-
-diff --git a/src/modules/volume-api/mute-control.c b/src/modules/volume-api/mute-control.c
-index adc008e..1b2f276 100644
---- a/src/modules/volume-api/mute-control.c
-+++ b/src/modules/volume-api/mute-control.c
-@@ -31,52 +31,73 @@
-
- #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;
-+int pa_mute_control_new(pa_volume_api *api, const char *name, bool persistent, pa_mute_control **_r) {
-+ pa_mute_control *control = NULL;
-+ int r;
-
- pa_assert(api);
- pa_assert(name);
-- pa_assert(description);
-+ pa_assert(_r);
-
- 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);
-+
-+ r = pa_volume_api_register_name(api, name, false, &control->name);
-+ if (r < 0)
-+ goto fail;
-+
-+ control->description = pa_xstrdup(control->name);
- control->proplist = pa_proplist_new();
-+ control->present = !persistent;
-+ control->persistent = persistent;
-+ control->purpose = PA_MUTE_CONTROL_PURPOSE_OTHER;
- 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;
-+ if (persistent) {
-+ pa_inidb_row *row;
-+
-+ row = pa_inidb_table_add_row(api->control_db.mute_controls, control->name);
-+ control->db_cells.description = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION);
-+ control->db_cells.mute = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_MUTE);
-+ }
-+
-+ *_r = control;
-+ return 0;
-+
-+fail:
-+ if (control)
-+ pa_mute_control_free(control);
-+
-+ return r;
- }
-
--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_put(pa_mute_control *control) {
- 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);
-+ pa_assert(control->set_mute || !control->present);
-
-- if (initial_mute_is_set)
-- control->mute = initial_mute;
-- else
-- control->mute = false;
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_IMPLEMENTATION_INITIALIZED], control);
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_SET_INITIAL_MUTE], control);
-
-- if (set_initial_mute_cb)
-- set_initial_mute_cb(control);
-+ if (control->set_mute) {
-+ control->set_mute_in_progress = true;
-+ control->set_mute(control, control->mute);
-+ control->set_mute_in_progress = false;
-+ }
-
- 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(" Present: %s", pa_yes_no(control->present));
-+ pa_log_debug(" Persistent: %s", pa_yes_no(control->persistent));
- pa_log_debug(" Properties:");
-
- while ((prop_key = pa_proplist_iterate(control->proplist, &state)))
-@@ -86,9 +107,7 @@ void pa_mute_control_put(pa_mute_control *control, bool initial_mute, bool initi
- }
-
- void pa_mute_control_unlink(pa_mute_control *control) {
-- pa_audio_group *group;
- pa_device *device;
-- pas_stream *stream;
-
- pa_assert(control);
-
-@@ -102,15 +121,9 @@ void pa_mute_control_unlink(pa_mute_control *control) {
- 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);
-
-- 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);
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK], control);
-
- while ((device = pa_hashmap_first(control->default_for_devices)))
- pa_device_set_default_mute_control(device, NULL);
-@@ -133,19 +146,10 @@ void pa_mute_control_unlink(pa_mute_control *control) {
- void pa_mute_control_free(pa_mute_control *control) {
- pa_assert(control);
-
-- if (!control->unlinked)
-+ /* unlink() expects name to be set. */
-+ if (!control->unlinked && control->name)
- 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);
-@@ -167,86 +171,137 @@ void pa_mute_control_free(pa_mute_control *control) {
- pa_xfree(control);
- }
-
--void pa_mute_control_set_owner_audio_group(pa_mute_control *control, pa_audio_group *group) {
-+void pa_mute_control_set_purpose(pa_mute_control *control, pa_mute_control_purpose_t purpose, void *owner) {
- pa_assert(control);
-- pa_assert(group);
-+ pa_assert(!control->linked);
-
-- control->owner_audio_group = group;
-+ control->purpose = purpose;
-+ control->owner = owner;
- }
-
--static void set_mute_internal(pa_mute_control *control, bool mute) {
-- bool old_mute;
--
-+int pa_mute_control_acquire_for_audio_group(pa_mute_control *control, pa_audio_group *group,
-+ pa_mute_control_set_mute_cb_t set_mute_cb, void *userdata) {
- pa_assert(control);
-+ pa_assert(group);
-+ pa_assert(set_mute_cb);
-
-- old_mute = control->mute;
-+ if (control->present) {
-+ pa_log("Can't acquire mute control %s, it's already present.", control->name);
-+ return -PA_ERR_BUSY;
-+ }
-
-- if (mute == old_mute)
-- return;
-+ control->owner_audio_group = group;
-+ control->set_mute = set_mute_cb;
-+ control->userdata = userdata;
-
-- control->mute = mute;
-+ control->set_mute_in_progress = true;
-+ control->set_mute(control, control->mute);
-+ control->set_mute_in_progress = false;
-+
-+ control->present = true;
-
- if (!control->linked || control->unlinked)
-- return;
-+ return 0;
-
-- 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_log_debug("Mute control %s became present.", control->name);
-
-- pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], control);
-+ return 0;
- }
-
--int pa_mute_control_set_mute(pa_mute_control *control, bool mute) {
-- int r;
--
-+void pa_mute_control_release(pa_mute_control *control) {
- 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 (!control->present)
-+ return;
-
-- if (mute == control->mute)
-- return 0;
-+ control->present = false;
-
-- control->set_mute_in_progress = true;
-- r = control->set_mute(control, mute);
-- control->set_mute_in_progress = false;
-+ control->userdata = NULL;
-+ control->set_mute = NULL;
-+ control->owner_audio_group = NULL;
-
-- if (r >= 0)
-- set_mute_internal(control, mute);
-+ if (!control->linked || control->unlinked)
-+ return;
-
-- return r;
-+ pa_log_debug("Mute control %s became not present.", control->name);
- }
-
--void pa_mute_control_description_changed(pa_mute_control *control, const char *new_description) {
-+void pa_mute_control_set_description(pa_mute_control *control, const char *description) {
- char *old_description;
-
- pa_assert(control);
-- pa_assert(new_description);
-+ pa_assert(description);
-
- old_description = control->description;
-
-- if (pa_streq(new_description, old_description))
-+ if (pa_streq(description, old_description))
-+ return;
-+
-+ control->description = pa_xstrdup(description);
-+
-+ if (control->persistent)
-+ pa_inidb_cell_set_value(control->db_cells.description, description);
-+
-+ if (!control->linked || control->unlinked) {
-+ pa_xfree(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);
-+ 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) {
-+static void set_mute_internal(pa_mute_control *control, bool mute) {
-+ bool old_mute;
-+
- pa_assert(control);
-
-- if (!control->linked)
-+ old_mute = control->mute;
-+
-+ if (mute == old_mute)
- return;
-
-- if (control->set_mute_in_progress)
-+ control->mute = mute;
-+
-+ if (control->persistent)
-+ pa_inidb_cell_set_value(control->db_cells.mute, pa_boolean_to_string(mute));
-+
-+ if (!control->linked || control->unlinked)
- return;
-
-- set_mute_internal(control, new_mute);
-+ pa_log_debug("The mute of mute control %s changed from %s to %s.", control->name, pa_boolean_to_string(old_mute),
-+ pa_boolean_to_string(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_in_progress)
-+ return 0;
-+
-+ if (mute == control->mute)
-+ return 0;
-+
-+ if (control->linked && control->present) {
-+ control->set_mute_in_progress = true;
-+ r = control->set_mute(control, mute);
-+ control->set_mute_in_progress = false;
-+
-+ if (r < 0) {
-+ pa_log("Setting the mute of mute control %s failed.", control->name);
-+ return r;
-+ }
-+ }
-+
-+ set_mute_internal(control, mute);
-+
-+ return 0;
- }
-
- void pa_mute_control_add_device(pa_mute_control *control, pa_device *device) {
-@@ -276,31 +331,3 @@ void pa_mute_control_remove_default_for_device(pa_mute_control *control, pa_devi
-
- 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
-index 1f70a43..40f8a9c 100644
---- a/src/modules/volume-api/mute-control.h
-+++ b/src/modules/volume-api/mute-control.h
-@@ -22,10 +22,18 @@
- USA.
- ***/
-
-+#include <modules/volume-api/inidb.h>
- #include <modules/volume-api/volume-api.h>
-
- typedef struct pa_mute_control pa_mute_control;
-
-+typedef enum {
-+ PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE,
-+ PA_MUTE_CONTROL_PURPOSE_OTHER,
-+} pa_mute_control_purpose_t;
-+
-+typedef int (*pa_mute_control_set_mute_cb_t)(pa_mute_control *control, bool mute);
-+
- struct pa_mute_control {
- pa_volume_api *volume_api;
- uint32_t index;
-@@ -33,6 +41,14 @@ struct pa_mute_control {
- char *description;
- pa_proplist *proplist;
- bool mute;
-+ bool present;
-+ bool persistent;
-+
-+ pa_mute_control_purpose_t purpose;
-+ union {
-+ pas_stream *owner_stream;
-+ void *owner;
-+ };
-
- /* 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. */
-@@ -40,50 +56,43 @@ struct pa_mute_control {
-
- 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) */
-+
-+ struct {
-+ pa_inidb_cell *description;
-+ pa_inidb_cell *mute;
-+ } db_cells;
-
- 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);
-+ * to return a negative error code on failure. */
-+ pa_mute_control_set_mute_cb_t set_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);
--
-+int pa_mute_control_new(pa_volume_api *api, const char *name, bool persistent, pa_mute_control **_r);
-+void pa_mute_control_put(pa_mute_control *control);
- 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, before pa_mute_control_put(). */
-+void pa_mute_control_set_purpose(pa_mute_control *control, pa_mute_control_purpose_t purpose, void *owner);
-
- /* 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);
-+int pa_mute_control_acquire_for_audio_group(pa_mute_control *control, pa_audio_group *group,
-+ pa_mute_control_set_mute_cb_t set_mute_cb, void *userdata);
-+
-+/* Called by the mute control implementation. This must only be called for
-+ * persistent controls; use pa_mute_control_free() for non-persistent
-+ * controls. */
-+void pa_mute_control_release(pa_mute_control *control);
-+
-+/* Called by anyone. */
-+void pa_mute_control_set_description(pa_mute_control *control, const char *description);
-+int pa_mute_control_set_mute(pa_mute_control *control, bool mute);
-
- /* Called from device.c only. */
- void pa_mute_control_add_device(pa_mute_control *control, pa_device *device);
-@@ -91,12 +100,4 @@ 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
-index e3531a8..1738d15 100644
---- a/src/modules/volume-api/sstream.c
-+++ b/src/modules/volume-api/sstream.c
-@@ -26,7 +26,6 @@
- #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>
-
-@@ -34,121 +33,43 @@
-
- #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;
-+int pas_stream_new(pa_volume_api *api, const char *name, pas_stream **_r) {
-+ pas_stream *stream = NULL;
-+ int r;
-
- pa_assert(api);
- pa_assert(name);
-- pa_assert(description);
-+ pa_assert(_r);
-
- 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;
-+ r = pa_volume_api_register_name(api, name, false, &stream->name);
-+ if (r < 0)
-+ goto fail;
-
-- 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);
-- }
-+ stream->description = pa_xstrdup(stream->name);
-+ stream->direction = PA_DIRECTION_OUTPUT;
-+ stream->proplist = pa_proplist_new();
-
-- if (!stream->linked || stream->unlinked)
-- return;
-+ *_r = stream;
-+ return 0;
-
-- 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)");
-+fail:
-+ if (stream)
-+ pas_stream_free(stream);
-
-- pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_MUTE_CONTROL_CHANGED], stream);
-+ return r;
- }
-
--void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties) {
-+void pas_stream_put(pas_stream *stream) {
- 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);
-@@ -157,6 +78,10 @@ void pas_stream_put(pas_stream *stream, pa_proplist *initial_properties) {
- 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(" Audio group for volume: %s",
-+ stream->audio_group_for_volume ? stream->audio_group_for_volume->name : "(unset)");
-+ pa_log_debug(" Audio group for mute: %s",
-+ stream->audio_group_for_mute ? stream->audio_group_for_mute->name : "(unset)");
- pa_log_debug(" Properties:");
-
- while ((prop_key = pa_proplist_iterate(stream->proplist, &state)))
-@@ -178,22 +103,22 @@ void pas_stream_unlink(pas_stream *stream) {
- 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);
-
-- pa_volume_api_remove_stream(stream->volume_api, stream);
-+ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], 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_relative_volume_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)
-+ /* unlink() expects name to be set. */
-+ if (!stream->unlinked && stream->name)
- pas_stream_unlink(stream);
-
- if (stream->proplist)
-@@ -207,160 +132,172 @@ void pas_stream_free(pas_stream *stream) {
- pa_xfree(stream);
- }
-
--int pas_stream_set_have_own_volume_control(pas_stream *stream, bool have) {
-+void pas_stream_set_direction(pas_stream *stream, pa_direction_t direction) {
- pa_assert(stream);
-+ pa_assert(!stream->linked);
-
-- if (have == stream->have_own_volume_control)
-- return 0;
-+ stream->direction = direction;
-+}
-
-- if (have) {
-- pa_assert(!stream->own_volume_control);
-+void pas_stream_set_description(pas_stream *stream, const char *description) {
-+ char *old_description;
-+
-+ pa_assert(stream);
-+ pa_assert(description);
-
-- if (!stream->create_own_volume_control) {
-- pa_log_debug("Stream %s doesn't support own volume control.", stream->name);
-- return -PA_ERR_NOTSUPPORTED;
-- }
-+ old_description = stream->description;
-+
-+ if (pa_streq(description, old_description))
-+ return;
-
-- stream->own_volume_control = stream->create_own_volume_control(stream);
-- } else {
-- stream->delete_own_volume_control(stream);
-- stream->own_volume_control = NULL;
-+ stream->description = pa_xstrdup(description);
-+
-+ if (!stream->linked || stream->unlinked) {
-+ pa_xfree(old_description);
-+ return;
- }
-
-- stream->have_own_volume_control = have;
-+ pa_log_debug("Stream %s description changed from \"%s\" to \"%s\".", stream->name, old_description,
-+ description);
-+ pa_xfree(old_description);
-
-- return 0;
-+ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_DESCRIPTION_CHANGED], stream);
- }
-
--int pas_stream_set_have_own_mute_control(pas_stream *stream, bool have) {
-+void pas_stream_set_property(pas_stream *stream, const char *key, const char *value) {
-+ const char *old_value;
-+
- pa_assert(stream);
-+ pa_assert(key);
-
-- if (have == stream->have_own_mute_control)
-- return 0;
-+ old_value = pa_proplist_gets(stream->proplist, key);
-
-- if (have) {
-- pa_assert(!stream->own_mute_control);
-+ if (pa_safe_streq(value, old_value))
-+ return;
-
-- if (!stream->create_own_mute_control) {
-- pa_log_debug("Stream %s doesn't support own mute control.", stream->name);
-- return -PA_ERR_NOTSUPPORTED;
-- }
-+ if (value)
-+ pa_proplist_sets(stream->proplist, key, value);
-+ else
-+ pa_proplist_unset(stream->proplist, key);
-
-- stream->own_mute_control = stream->create_own_mute_control(stream);
-- } else {
-- stream->delete_own_mute_control(stream);
-- stream->own_mute_control = NULL;
-- }
-+ if (!stream->linked || stream->unlinked)
-+ return;
-
-- stream->have_own_mute_control = have;
-+ pa_log_debug("Stream %s property \"%s\" changed from \"%s\" to \"%s\".", stream->name, key,
-+ old_value ? old_value : "(unset)", value ? value : "(unset)");
-
-- return 0;
-+ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PROPLIST_CHANGED], stream);
- }
-
- void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control) {
-- pa_assert(stream);
-+ pa_volume_control *old_control;
-
-- stream->use_default_volume_control = false;
-+ pa_assert(stream);
-
-- if (stream->volume_control_binding) {
-- pa_binding_free(stream->volume_control_binding);
-- stream->volume_control_binding = NULL;
-- }
-+ old_control = stream->volume_control;
-
-- set_volume_control_internal(stream, control);
--}
-+ if (control == old_control)
-+ return;
-
--void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control) {
-- pa_assert(stream);
-+ stream->volume_control = control;
-
-- stream->use_default_mute_control = false;
-+ if (!stream->linked || stream->unlinked)
-+ return;
-
-- if (stream->mute_control_binding) {
-- pa_binding_free(stream->mute_control_binding);
-- stream->mute_control_binding = NULL;
-- }
-+ 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)");
-
-- set_mute_control_internal(stream, control);
-+ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED], stream);
- }
-
--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,
-- };
-+void pas_stream_set_relative_volume_control(pas_stream *stream, pa_volume_control *control) {
-+ pa_volume_control *old_control;
-
- 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);
-+ old_control = stream->relative_volume_control;
-
-- stream->volume_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info);
--}
-+ if (control == old_control)
-+ return;
-
--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,
-- };
-+ stream->relative_volume_control = control;
-
-- pa_assert(stream);
-- pa_assert(target_info);
--
-- stream->use_default_mute_control = false;
-+ if (!stream->linked || stream->unlinked)
-+ return;
-
-- if (stream->mute_control_binding)
-- pa_binding_free(stream->mute_control_binding);
-+ pa_log_debug("The relative volume control of stream %s changed from %s to %s.", stream->name,
-+ old_control ? old_control->name : "(unset)", control ? control->name : "(unset)");
-
-- stream->mute_control_binding = pa_binding_new(stream->volume_api, &owner_info, target_info);
-+ pa_hook_fire(&stream->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_RELATIVE_VOLUME_CONTROL_CHANGED], stream);
- }
-
--void pas_stream_description_changed(pas_stream *stream, const char *new_description) {
-- char *old_description;
-+void pas_stream_set_mute_control(pas_stream *stream, pa_mute_control *control) {
-+ pa_mute_control *old_control;
-
- pa_assert(stream);
-- pa_assert(new_description);
-
-- old_description = stream->description;
-+ old_control = stream->mute_control;
-
-- if (pa_streq(new_description, old_description))
-+ if (control == old_control)
- 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);
-+ stream->mute_control = control;
-+
-+ 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_set_audio_group_for_volume(pas_stream *stream, pa_audio_group *group) {
-+ pa_audio_group *old_group;
-+
- pa_assert(stream);
-
-- if (group == stream->audio_group_for_volume)
-+ old_group = stream->audio_group_for_volume;
-+
-+ if (group == old_group)
- return;
-
-- if (stream->audio_group_for_volume)
-- pa_audio_group_remove_volume_stream(stream->audio_group_for_volume, stream);
-+ if (old_group)
-+ pa_audio_group_remove_volume_stream(old_group, stream);
-
- stream->audio_group_for_volume = group;
-
- if (group)
- pa_audio_group_add_volume_stream(group, stream);
-+
-+ if (!stream->linked || stream->unlinked)
-+ return;
-+
-+ pa_log_debug("Stream %s audio group for volume changed from %s to %s.", stream->name,
-+ old_group ? old_group->name : "(unset)", group ? group->name : "(unset)");
- }
-
- void pas_stream_set_audio_group_for_mute(pas_stream *stream, pa_audio_group *group) {
-+ pa_audio_group *old_group;
-+
- pa_assert(stream);
-
-- if (group == stream->audio_group_for_mute)
-+ old_group = stream->audio_group_for_mute;
-+
-+ if (group == old_group)
- return;
-
-- if (stream->audio_group_for_mute)
-- pa_audio_group_remove_mute_stream(stream->audio_group_for_mute, stream);
-+ if (old_group)
-+ pa_audio_group_remove_mute_stream(old_group, stream);
-
- stream->audio_group_for_mute = group;
-
- if (group)
- pa_audio_group_add_mute_stream(group, stream);
-+
-+ if (!stream->linked || stream->unlinked)
-+ return;
-+
-+ pa_log_debug("Stream %s audio group for mute changed from %s to %s.", stream->name,
-+ old_group ? old_group->name : "(unset)", group ? group->name : "(unset)");
- }
-diff --git a/src/modules/volume-api/sstream.h b/src/modules/volume-api/sstream.h
-index a65b34c..715bf2c 100644
---- a/src/modules/volume-api/sstream.h
-+++ b/src/modules/volume-api/sstream.h
-@@ -39,69 +39,33 @@ struct pas_stream {
- pa_direction_t direction;
- pa_proplist *proplist;
- pa_volume_control *volume_control;
-+ pa_volume_control *relative_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);
-+int pas_stream_new(pa_volume_api *api, const char *name, pas_stream **_r);
-+void pas_stream_put(pas_stream *stream);
- 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 the stream implementation, only during initialization. */
-+void pas_stream_set_direction(pas_stream *stream, pa_direction_t direction);
-
--/* Called by policy modules. */
-+/* Called by the stream implementation. */
-+void pas_stream_set_description(pas_stream *stream, const char *description);
-+void pas_stream_set_property(pas_stream *stream, const char *key, const char *value);
- void pas_stream_set_volume_control(pas_stream *stream, pa_volume_control *control);
-+void pas_stream_set_relative_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. */
-+/* Called by anyone. */
- 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);
-
-diff --git a/src/modules/volume-api/stream-creator.c b/src/modules/volume-api/stream-creator.c
-index f6ca7b3..0d9ea24 100644
---- a/src/modules/volume-api/stream-creator.c
-+++ b/src/modules/volume-api/stream-creator.c
-@@ -35,9 +35,9 @@
- 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_fixate_slot;
- pa_hook_slot *sink_input_unlink_slot;
-- pa_hook_slot *source_output_put_slot;
-+ pa_hook_slot *source_output_fixate_slot;
- pa_hook_slot *source_output_unlink_slot;
- };
-
-@@ -47,73 +47,61 @@ enum stream_type {
- };
-
- struct stream {
-+ pa_core *core;
- pa_stream_creator *creator;
- enum stream_type type;
-+ pa_sink_input_new_data *sink_input_new_data;
- pa_sink_input *sink_input;
-+ pa_source_output_new_data *source_output_new_data;
- pa_source_output *source_output;
- pa_client *client;
-+ pa_volume_control *volume_control;
-+ pa_volume_control *relative_volume_control;
-+ pa_mute_control *mute_control;
- 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 *reference_ratio_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 void stream_free(struct stream *stream);
-
--static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *volume, bool set_volume, bool set_balance) {
-+static int volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *original_volume,
-+ const pa_bvolume *remapped_volume, bool set_volume, bool set_balance) {
- struct stream *stream;
- pa_bvolume bvolume;
- pa_cvolume cvolume;
-
- pa_assert(control);
-- pa_assert(volume);
-+ pa_assert(original_volume);
-+ pa_assert(remapped_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;
-- }
-+ bvolume = control->volume;
-
- if (set_volume)
-- bvolume.volume = volume->volume;
-+ bvolume.volume = remapped_volume->volume;
-
- if (set_balance)
-- pa_bvolume_copy_balance(&bvolume, volume);
-+ pa_bvolume_copy_balance(&bvolume, remapped_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);
-+ if (stream->sink_input->state == PA_SINK_INPUT_INIT)
-+ pa_sink_input_new_data_set_volume(stream->sink_input_new_data, &cvolume, false);
-+ else
-+ 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);
-+ if (stream->source_output->state == PA_SOURCE_OUTPUT_INIT)
-+ pa_source_output_new_data_set_volume(stream->source_output_new_data, &cvolume, false);
-+ else
-+ pa_source_output_set_volume(stream->source_output, &cvolume, true, true);
- break;
- }
-
-@@ -129,6 +117,9 @@ static pa_hook_result_t sink_input_or_source_output_volume_changed_cb(void *hook
- pa_assert(stream);
- pa_assert(call_data);
-
-+ if (!stream->volume_control)
-+ return PA_HOOK_OK;
-+
- switch (stream->type) {
- case STREAM_TYPE_SINK_INPUT:
- input = call_data;
-@@ -144,63 +135,95 @@ static pa_hook_result_t sink_input_or_source_output_volume_changed_cb(void *hook
-
- if (input)
- pa_bvolume_from_cvolume(&bvolume, &input->volume, &input->channel_map);
-- else
-+ else if (output)
- pa_bvolume_from_cvolume(&bvolume, &output->volume, &output->channel_map);
-+ else
-+ pa_assert_not_reached();
-
-- pa_volume_control_volume_changed(stream->stream->own_volume_control, &bvolume, true, true);
-+ pa_volume_control_set_volume(stream->volume_control, &bvolume, true, true);
-
- return PA_HOOK_OK;
- }
-
--static void volume_control_set_initial_volume_cb(pa_volume_control *control) {
-+static int relative_volume_control_set_volume_cb(pa_volume_control *control, const pa_bvolume *original_volume,
-+ const pa_bvolume *remapped_volume, bool set_volume, bool set_balance) {
- struct stream *stream;
-+ pa_bvolume bvolume;
- pa_cvolume cvolume;
-
- pa_assert(control);
-+ pa_assert(original_volume);
-+ pa_assert(remapped_volume);
-
- 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;
-+ bvolume = control->volume;
-
-- 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;
-+ if (set_volume)
-+ bvolume.volume = remapped_volume->volume;
-
-- pa_assert(control);
-+ if (set_balance)
-+ pa_bvolume_copy_balance(&bvolume, remapped_volume);
-
-- stream = control->userdata;
-+ pa_bvolume_to_cvolume(&bvolume, &cvolume);
-
- switch (stream->type) {
- case STREAM_TYPE_SINK_INPUT:
-- pa_sink_input_set_mute(stream->sink_input, mute, true);
-+ if (stream->sink_input->state == PA_SINK_INPUT_INIT) {
-+ pa_sink_input_new_data_set_volume(stream->sink_input_new_data, &cvolume, true);
-+
-+ /* XXX: This is a bit ugly. This is needed, because when we
-+ * call pa_sink_input_new_data_set_volume(), there's no
-+ * automatic notification to the primary volume control object
-+ * about the changed volume. This problem should go away once
-+ * stream volume controls are moved into the core. */
-+ if (stream->volume_control) {
-+ pa_bvolume absolute_volume;
-+
-+ pa_bvolume_from_cvolume(&absolute_volume, &stream->sink_input_new_data->volume,
-+ &stream->sink_input_new_data->channel_map);
-+ pa_volume_control_set_volume(stream->volume_control, &absolute_volume, true, true);
-+ }
-+ } else
-+ pa_sink_input_set_volume(stream->sink_input, &cvolume, true, false);
- break;
-
- case STREAM_TYPE_SOURCE_OUTPUT:
-- pa_source_output_set_mute(stream->source_output, mute, true);
-+ if (stream->source_output->state == PA_SOURCE_OUTPUT_INIT) {
-+ pa_source_output_new_data_set_volume(stream->source_output_new_data, &cvolume, true);
-+
-+ /* XXX: This is a bit ugly. This is needed, because when we
-+ * call pa_source_output_new_data_set_volume(), there's no
-+ * automatic notification to the primary volume control object
-+ * about the changed volume. This problem should go away once
-+ * stream volume controls are moved into the core. */
-+ if (stream->volume_control) {
-+ pa_bvolume absolute_volume;
-+
-+ pa_bvolume_from_cvolume(&absolute_volume, &stream->source_output_new_data->volume,
-+ &stream->source_output_new_data->channel_map);
-+ pa_volume_control_set_volume(stream->volume_control, &absolute_volume, true, true);
-+ }
-+ } else
-+ pa_source_output_set_volume(stream->source_output, &cvolume, true, false);
- break;
- }
-
- return 0;
- }
-
--static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_data, void *call_data, void *userdata) {
-+static pa_hook_result_t sink_input_or_source_output_reference_ratio_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_bvolume bvolume;
-
- pa_assert(stream);
- pa_assert(call_data);
-
-+ if (!stream->relative_volume_control)
-+ return PA_HOOK_OK;
-+
- switch (stream->type) {
- case STREAM_TYPE_SINK_INPUT:
- input = call_data;
-@@ -215,18 +238,18 @@ static pa_hook_result_t sink_input_or_source_output_mute_changed_cb(void *hook_d
- return PA_HOOK_OK;
-
- if (input)
-- mute = input->muted;
-+ pa_bvolume_from_cvolume(&bvolume, &input->reference_ratio, &input->channel_map);
- else if (output)
-- mute = output->muted;
-+ pa_bvolume_from_cvolume(&bvolume, &output->reference_ratio, &output->channel_map);
- else
- pa_assert_not_reached();
-
-- pa_mute_control_mute_changed(stream->stream->own_mute_control, mute);
-+ pa_volume_control_set_volume(stream->relative_volume_control, &bvolume, true, true);
-
- return PA_HOOK_OK;
- }
-
--static void mute_control_set_initial_mute_cb(pa_mute_control *control) {
-+static int mute_control_set_mute_cb(pa_mute_control *control, bool mute) {
- struct stream *stream;
-
- pa_assert(control);
-@@ -235,167 +258,66 @@ static void mute_control_set_initial_mute_cb(pa_mute_control *control) {
-
- 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);
-+ if (stream->sink_input->state == PA_SINK_INPUT_INIT)
-+ pa_sink_input_new_data_set_muted(stream->sink_input_new_data, mute);
-+ else
-+ pa_sink_input_set_mute(stream->sink_input, mute, true);
- 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);
-+ if (stream->source_output->state == PA_SOURCE_OUTPUT_INIT)
-+ pa_source_output_new_data_set_muted(stream->source_output_new_data, mute);
-+ else
-+ pa_source_output_set_mute(stream->source_output, mute, true);
- 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);
-+ return 0;
- }
-
--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;
-- }
-+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;
-
-- 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);
-+ pa_assert(call_data);
-
-- pa_assert(!stream->mute_changed_slot);
-+ if (!stream->mute_control)
-+ return PA_HOOK_OK;
-
- 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;
-+ input = call_data;
- 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;
-+ output = call_data;
- break;
- }
-
-- pa_mute_control_put(control, mute, true, mute_control_set_initial_mute_cb);
--
-- return control;
--}
-+ if ((input && input != stream->sink_input) || (output && output != stream->source_output))
-+ return PA_HOOK_OK;
-
--static void stream_delete_own_mute_control_cb(pas_stream *s) {
-- struct stream *stream;
-+ if (input)
-+ mute = input->muted;
-+ else if (output)
-+ mute = output->muted;
-+ else
-+ pa_assert_not_reached();
-
-- pa_assert(s);
-+ pa_mute_control_set_mute(stream->mute_control, mute);
-
-- stream = s->userdata;
-- pa_hook_slot_free(stream->mute_changed_slot);
-- stream->mute_changed_slot = NULL;
-- pa_mute_control_free(s->own_mute_control);
-+ return PA_HOOK_OK;
- }
-
- 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_proplist *proplist = NULL;
-+ const char *description = NULL;
-
- pa_assert(stream);
- pa_assert(call_data);
-@@ -407,9 +329,7 @@ static pa_hook_result_t sink_input_or_source_output_proplist_changed_cb(void *ho
- 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;
-+ proplist = stream->sink_input->proplist;
- break;
-
- case STREAM_TYPE_SOURCE_OUTPUT:
-@@ -418,187 +338,290 @@ static pa_hook_result_t sink_input_or_source_output_proplist_changed_cb(void *ho
- 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;
-+ proplist = stream->source_output->proplist;
- break;
- }
-
-- pas_stream_description_changed(stream->stream, new_stream_description);
--
-- new_control_description = get_stream_volume_and_mute_control_description_malloc(stream);
-+ description = pa_proplist_gets(proplist, PA_PROP_MEDIA_NAME);
-+ if (!description)
-+ description = stream->stream->name;
-
-- 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);
-+ pas_stream_set_description(stream->stream, 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;
-+static int stream_new(pa_stream_creator *creator, enum stream_type type, void *new_data, void *core_stream,
-+ struct stream **_r) {
-+ struct stream *stream = NULL;
-+ pa_proplist *proplist = NULL;
-+ pa_channel_map *channel_map = NULL;
-+ bool volume_available = false;
-+ pa_bvolume volume;
-+ pa_bvolume relative_volume;
-+ bool mute = false;
-+ const char *stream_name = NULL;
- const char *description = NULL;
-+ const char *volume_control_name = NULL;
-+ const char *relative_volume_control_name = NULL;
-+ const char *mute_control_name = NULL;
- pa_direction_t direction = PA_DIRECTION_OUTPUT;
-+ int r;
-+ const char *prop_key;
-+ void *state = NULL;
-
- pa_assert(creator);
- pa_assert(core_stream);
-+ pa_assert(_r);
-+
-+ pa_bvolume_init_invalid(&volume);
-+ pa_bvolume_init_invalid(&relative_volume);
-
- stream = pa_xnew0(struct stream, 1);
-+ stream->core = creator->volume_api->core;
- stream->creator = creator;
- stream->type = type;
-
- switch (type) {
- case STREAM_TYPE_SINK_INPUT:
-+ stream->sink_input_new_data = new_data;
- 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;
-+ if (new_data) {
-+ stream->client = stream->sink_input_new_data->client;
-+ proplist = stream->sink_input_new_data->proplist;
-+ channel_map = &stream->sink_input_new_data->channel_map;
-+ volume_available = stream->sink_input_new_data->volume_writable;
-+
-+ if (volume_available) {
-+ if (!stream->sink_input_new_data->volume_is_set) {
-+ pa_cvolume cvolume;
-+
-+ pa_cvolume_reset(&cvolume, channel_map->channels);
-+ pa_sink_input_new_data_set_volume(stream->sink_input_new_data, &cvolume, true);
-+ }
-+
-+ pa_bvolume_from_cvolume(&volume, &stream->sink_input_new_data->volume, channel_map);
-+ pa_bvolume_from_cvolume(&relative_volume, &stream->sink_input_new_data->reference_ratio, channel_map);
-+ }
-+
-+ if (!stream->sink_input_new_data->muted_is_set)
-+ pa_sink_input_new_data_set_muted(stream->sink_input_new_data, false);
-+
-+ mute = stream->sink_input_new_data->muted;
-+ } else {
-+ stream->client = stream->sink_input->client;
-+ proplist = stream->sink_input->proplist;
-+ channel_map = &stream->sink_input->channel_map;
-+ pa_bvolume_from_cvolume(&volume, &stream->sink_input->volume, channel_map);
-+ pa_bvolume_from_cvolume(&relative_volume, &stream->sink_input->reference_ratio, channel_map);
-+ mute = stream->sink_input->muted;
-+ }
-+
-+ stream_name = "sink-input-stream";
-+ volume_control_name = "sink-input-volume-control";
-+ relative_volume_control_name = "sink-input-relative-volume-control";
-+ mute_control_name = "sink-input-mute-control";
-
- direction = PA_DIRECTION_OUTPUT;
-+
-+ stream->proplist_changed_slot =
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_NORMAL,
-+ sink_input_or_source_output_proplist_changed_cb, stream);
-+ stream->volume_changed_slot =
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], PA_HOOK_NORMAL,
-+ sink_input_or_source_output_volume_changed_cb, stream);
-+ stream->reference_ratio_changed_slot =
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_REFERENCE_RATIO_CHANGED], PA_HOOK_NORMAL,
-+ sink_input_or_source_output_reference_ratio_changed_cb, stream);
-+ stream->mute_changed_slot =
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], PA_HOOK_NORMAL,
-+ sink_input_or_source_output_mute_changed_cb, stream);
- break;
-
- case STREAM_TYPE_SOURCE_OUTPUT:
-+ stream->source_output_new_data = new_data;
- 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;
-+ if (new_data) {
-+ stream->client = stream->source_output_new_data->client;
-+ proplist = stream->source_output_new_data->proplist;
-+ channel_map = &stream->source_output_new_data->channel_map;
-+ volume_available = stream->source_output_new_data->volume_writable;
-+
-+ if (volume_available) {
-+ if (!stream->source_output_new_data->volume_is_set) {
-+ pa_cvolume cvolume;
-+
-+ pa_cvolume_reset(&cvolume, channel_map->channels);
-+ pa_source_output_new_data_set_volume(stream->source_output_new_data, &cvolume, true);
-+ }
-+
-+ pa_bvolume_from_cvolume(&volume, &stream->source_output_new_data->volume, channel_map);
-+ pa_bvolume_from_cvolume(&relative_volume, &stream->source_output_new_data->reference_ratio, channel_map);
-+ }
-+
-+ if (!stream->source_output_new_data->muted_is_set)
-+ pa_source_output_new_data_set_muted(stream->source_output_new_data, false);
-+
-+ mute = stream->source_output_new_data->muted;
-+ } else {
-+ stream->client = stream->source_output->client;
-+ proplist = stream->source_output->proplist;
-+ channel_map = &stream->source_output->channel_map;
-+ pa_bvolume_from_cvolume(&volume, &stream->source_output->volume, channel_map);
-+ pa_bvolume_from_cvolume(&relative_volume, &stream->source_output->reference_ratio, channel_map);
-+ mute = stream->source_output->muted;
-+ }
-+
-+ stream_name = "source-output-stream";
-+ volume_control_name = "source-output-volume-control";
-+ relative_volume_control_name = "source-output-relative-volume-control";
-+ mute_control_name = "source-output-mute-control";
-
- 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,
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_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);
-+ if (volume_available) {
-+ stream->volume_changed_slot =
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], PA_HOOK_NORMAL,
-+ sink_input_or_source_output_volume_changed_cb, stream);
-+ stream->reference_ratio_changed_slot =
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_REFERENCE_RATIO_CHANGED],
-+ PA_HOOK_NORMAL, sink_input_or_source_output_reference_ratio_changed_cb, stream);
-+ }
-+
-+ stream->mute_changed_slot =
-+ pa_hook_connect(&stream->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], PA_HOOK_NORMAL,
-+ sink_input_or_source_output_mute_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);
-+ r = pas_stream_new(creator->volume_api, stream_name, &stream->stream);
-+ if (r < 0)
-+ goto fail;
-
-- return stream;
--}
-+ description = pa_proplist_gets(proplist, PA_PROP_MEDIA_NAME);
-+ if (!description)
-+ description = stream->stream->name;
-
--static void stream_put(struct stream *stream) {
-- pa_proplist *proplist = NULL;
-+ pas_stream_set_description(stream->stream, description);
-
-- pa_assert(stream);
-+ while ((prop_key = pa_proplist_iterate(proplist, &state)))
-+ pas_stream_set_property(stream->stream, prop_key, pa_proplist_gets(proplist, prop_key));
-
-- switch (stream->type) {
-- case STREAM_TYPE_SINK_INPUT:
-- proplist = stream->sink_input->proplist;
-- break;
-+ pas_stream_set_direction(stream->stream, direction);
-+ stream->stream->userdata = stream;
-
-- case STREAM_TYPE_SOURCE_OUTPUT:
-- proplist = stream->source_output->proplist;
-- break;
-+ if (volume_available) {
-+ r = pa_volume_control_new(stream->creator->volume_api, volume_control_name, false,
-+ &stream->volume_control);
-+ if (r >= 0) {
-+ pa_volume_control_set_description(stream->volume_control, _("Volume"));
-+ pa_volume_control_set_channel_map(stream->volume_control, channel_map);
-+ pa_volume_control_set_volume(stream->volume_control, &volume, true, true);
-+ pa_volume_control_set_convertible_to_dB(stream->volume_control, true);
-+ stream->volume_control->set_volume = volume_control_set_volume_cb;
-+ stream->volume_control->userdata = stream;
-+
-+ pas_stream_set_volume_control(stream->stream, stream->volume_control);
-+ }
-+
-+ r = pa_volume_control_new(stream->creator->volume_api, relative_volume_control_name, false,
-+ &stream->relative_volume_control);
-+ if (r >= 0) {
-+ pa_volume_control_set_description(stream->relative_volume_control, _("Relative volume"));
-+ pa_volume_control_set_channel_map(stream->relative_volume_control, channel_map);
-+ pa_volume_control_set_volume(stream->relative_volume_control, &relative_volume, true, true);
-+ pa_volume_control_set_convertible_to_dB(stream->relative_volume_control, true);
-+ pa_volume_control_set_purpose(stream->relative_volume_control, PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME,
-+ stream->stream);
-+ stream->relative_volume_control->set_volume = relative_volume_control_set_volume_cb;
-+ stream->relative_volume_control->userdata = stream;
-+
-+ pas_stream_set_relative_volume_control(stream->stream, stream->relative_volume_control);
-+ }
- }
-
-- pas_stream_put(stream->stream, proplist);
--}
-+ r = pa_mute_control_new(stream->creator->volume_api, mute_control_name, false, &stream->mute_control);
-+ if (r >= 0) {
-+ pa_mute_control_set_description(stream->mute_control, _("Mute"));
-+ pa_mute_control_set_mute(stream->mute_control, mute);
-+ pa_mute_control_set_purpose(stream->mute_control, PA_MUTE_CONTROL_PURPOSE_STREAM_MUTE, stream->stream);
-+ stream->mute_control->set_mute = mute_control_set_mute_cb;
-+ stream->mute_control->userdata = stream;
-
--static void stream_unlink(struct stream *stream) {
-- pa_assert(stream);
-+ pas_stream_set_mute_control(stream->stream, stream->mute_control);
-+ }
-
-- if (stream->unlinked)
-- return;
-+ pas_stream_put(stream->stream);
-
-- stream->unlinked = true;
-+ if (stream->volume_control)
-+ pa_volume_control_put(stream->volume_control);
-
-- if (stream->stream)
-- pas_stream_unlink(stream->stream);
-+ if (stream->relative_volume_control)
-+ pa_volume_control_put(stream->relative_volume_control);
-+
-+ if (stream->mute_control)
-+ pa_mute_control_put(stream->mute_control);
-+
-+ *_r = stream;
-+ return 0;
-+
-+fail:
-+ if (stream)
-+ stream_free(stream);
-+
-+ return r;
- }
-
- static void stream_free(struct stream *stream) {
- pa_assert(stream);
-
-- if (!stream->unlinked)
-- stream_unlink(stream);
-+ if (stream->mute_changed_slot)
-+ pa_hook_slot_free(stream->mute_changed_slot);
-+
-+ if (stream->reference_ratio_changed_slot)
-+ pa_hook_slot_free(stream->reference_ratio_changed_slot);
-
-- if (stream->client_proplist_changed_slot)
-- pa_hook_slot_free(stream->client_proplist_changed_slot);
-+ if (stream->volume_changed_slot)
-+ pa_hook_slot_free(stream->volume_changed_slot);
-
- if (stream->proplist_changed_slot)
- pa_hook_slot_free(stream->proplist_changed_slot);
-
-+ if (stream->mute_control)
-+ pa_mute_control_free(stream->mute_control);
-+
-+ if (stream->relative_volume_control)
-+ pa_volume_control_free(stream->relative_volume_control);
-+
-+ if (stream->volume_control)
-+ pa_volume_control_free(stream->volume_control);
-+
- 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) {
-+static pa_hook_result_t sink_input_fixate_cb(void *hook_data, void *call_data, void *userdata) {
-+ pa_stream_creator *creator = userdata;
-+ pa_sink_input_new_data *data = call_data;
-+ int r;
- struct stream *stream;
-
- pa_assert(creator);
-- pa_assert(core_stream);
-+ pa_assert(data);
-
-- 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);
-+ r = stream_new(creator, STREAM_TYPE_SINK_INPUT, data, data->sink_input, &stream);
-+ if (r < 0)
-+ return PA_HOOK_OK;
-
-- create_stream(creator, STREAM_TYPE_SINK_INPUT, input);
-+ pa_hashmap_put(creator->streams, stream->sink_input, stream);
-
- return PA_HOOK_OK;
- }
-@@ -615,14 +638,20 @@ static pa_hook_result_t sink_input_unlink_cb(void *hook_data, void *call_data, v
- return PA_HOOK_OK;
- }
-
--static pa_hook_result_t source_output_put_cb(void *hook_data, void *call_data, void *userdata) {
-+static pa_hook_result_t source_output_fixate_cb(void *hook_data, void *call_data, void *userdata) {
- pa_stream_creator *creator = userdata;
-- pa_source_output *output = call_data;
-+ pa_source_output_new_data *data = call_data;
-+ int r;
-+ struct stream *stream;
-
- pa_assert(creator);
-- pa_assert(output);
-+ pa_assert(data);
-+
-+ r = stream_new(creator, STREAM_TYPE_SOURCE_OUTPUT, data, data->source_output, &stream);
-+ if (r < 0)
-+ return PA_HOOK_OK;
-
-- create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output);
-+ pa_hashmap_put(creator->streams, stream->source_output, stream);
-
- return PA_HOOK_OK;
- }
-@@ -644,26 +673,34 @@ pa_stream_creator *pa_stream_creator_new(pa_volume_api *api) {
- uint32_t idx;
- pa_sink_input *input;
- pa_source_output *output;
-+ int r;
-+ struct stream *stream;
-
- 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_fixate_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_NORMAL,
-+ sink_input_fixate_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_fixate_slot = pa_hook_connect(&api->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], PA_HOOK_NORMAL,
-+ source_output_fixate_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(input, api->core->sink_inputs, idx) {
-+ r = stream_new(creator, STREAM_TYPE_SINK_INPUT, NULL, input, &stream);
-+ if (r >= 0)
-+ pa_hashmap_put(creator->streams, stream->sink_input, stream);
-+ }
-
-- PA_IDXSET_FOREACH(output, api->core->source_outputs, idx)
-- create_stream(creator, STREAM_TYPE_SOURCE_OUTPUT, output);
-+ PA_IDXSET_FOREACH(output, api->core->source_outputs, idx) {
-+ r = stream_new(creator, STREAM_TYPE_SOURCE_OUTPUT, NULL, output, &stream);
-+ if (r >= 0)
-+ pa_hashmap_put(creator->streams, stream->source_output, stream);
-+ }
-
- return creator;
- }
-@@ -677,14 +714,14 @@ void pa_stream_creator_free(pa_stream_creator *creator) {
- 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->source_output_fixate_slot)
-+ pa_hook_slot_free(creator->source_output_fixate_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->sink_input_fixate_slot)
-+ pa_hook_slot_free(creator->sink_input_fixate_slot);
-
- if (creator->streams)
- pa_hashmap_free(creator->streams);
-diff --git a/src/modules/volume-api/volume-api.c b/src/modules/volume-api/volume-api.c
-index 9abea7e..4a8a2e6 100644
---- a/src/modules/volume-api/volume-api.c
-+++ b/src/modules/volume-api/volume-api.c
-@@ -26,16 +26,20 @@
- #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/inidb.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/namereg.h>
- #include <pulsecore/shared.h>
-
-+#define CONTROL_DB_TABLE_NAME_VOLUME_CONTROL "VolumeControl"
-+#define CONTROL_DB_TABLE_NAME_MUTE_CONTROL "MuteControl"
-+
- static pa_volume_api *volume_api_new(pa_core *core);
- static void volume_api_free(pa_volume_api *api);
-
-@@ -76,44 +80,209 @@ 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) {
-- pa_assert(api);
-- pa_assert(type);
-+static int control_db_get_volume_control_cb(pa_inidb *db, const char *name, void **_r) {
-+ pa_volume_api *api;
-+ pa_volume_control *control;
-+
-+ pa_assert(db);
-+ pa_assert(name);
-+ pa_assert(_r);
-+
-+ api = pa_inidb_get_userdata(db);
-
-- pa_assert_se(pa_hashmap_put(api->binding_target_types, type->name, type) >= 0);
-+ control = pa_hashmap_get(api->volume_controls_from_db, name);
-+ if (!control) {
-+ int r;
-
-- pa_log_debug("Added binding target type %s.", type->name);
-+ r = pa_volume_control_new(api, name, true, &control);
-+ if (r < 0)
-+ return r;
-
-- pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_ADDED], type);
-+ pa_hashmap_put(api->volume_controls_from_db, (void *) control->name, control);
-+ }
-+
-+ *_r = control;
-+ return 0;
- }
-
--void pa_volume_api_remove_binding_target_type(pa_volume_api *api, pa_binding_target_type *type) {
-- pa_assert(api);
-- pa_assert(type);
-+static int control_db_parse_volume_control_description_cb(pa_inidb *db, const char *value, void *object) {
-+ pa_volume_control *control = object;
-+
-+ pa_assert(db);
-+ pa_assert(value);
-+ pa_assert(control);
-
-- pa_log_debug("Removing binding target type %s.", type->name);
-+ pa_volume_control_set_description(control, value);
-
-- pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_BINDING_TARGET_TYPE_REMOVED], type);
-+ return 0;
-+}
-+
-+static int control_db_parse_volume_control_volume_cb(pa_inidb *db, const char *value, void *object) {
-+ pa_volume_control *control = object;
-+ int r;
-+ pa_bvolume bvolume;
-+
-+ pa_assert(db);
-+ pa_assert(value);
-+ pa_assert(control);
-+
-+ r = pa_atou(value, &bvolume.volume);
-+ if (r < 0)
-+ return -PA_ERR_INVALID;
-+
-+ if (!PA_VOLUME_IS_VALID(bvolume.volume))
-+ return -PA_ERR_INVALID;
-
-- pa_assert_se(pa_hashmap_remove(api->binding_target_types, type->name));
-+ pa_volume_control_set_volume(control, &bvolume, true, false);
-+
-+ return 0;
- }
-
--static void create_builtin_binding_target_types(pa_volume_api *api) {
-- pa_binding_target_type *type;
-+static int control_db_parse_volume_control_balance_cb(pa_inidb *db, const char *value, void *object) {
-+ pa_volume_control *control = object;
-+ int r;
-+ pa_bvolume bvolume;
-
-- pa_assert(api);
-+ pa_assert(db);
-+ pa_assert(value);
-+ pa_assert(control);
-+
-+ r = pa_bvolume_parse_balance(value, &bvolume);
-+ if (r < 0)
-+ return -PA_ERR_INVALID;
-+
-+ pa_volume_control_set_channel_map(control, &bvolume.channel_map);
-+ pa_volume_control_set_volume(control, &bvolume, false, true);
-+
-+ return 0;
-+}
-+
-+static int control_db_parse_volume_control_convertible_to_dB_cb(pa_inidb *db, const char *value, void *object) {
-+ pa_volume_control *control = object;
-+ int r;
-+
-+ pa_assert(db);
-+ pa_assert(value);
-+ pa_assert(control);
-+
-+ r = pa_parse_boolean(value);
-+ if (r < 0)
-+ return -PA_ERR_INVALID;
-+
-+ pa_volume_control_set_convertible_to_dB(control, r);
-+
-+ return 0;
-+}
-+
-+static int control_db_get_mute_control_cb(pa_inidb *db, const char *name, void **_r) {
-+ pa_volume_api *api;
-+ pa_mute_control *control;
-+
-+ pa_assert(db);
-+ pa_assert(name);
-+ pa_assert(_r);
-+
-+ api = pa_inidb_get_userdata(db);
-+
-+ control = pa_hashmap_get(api->mute_controls_from_db, name);
-+ if (!control) {
-+ int r;
-+
-+ r = pa_mute_control_new(api, name, true, &control);
-+ if (r < 0)
-+ return r;
-+
-+ pa_hashmap_put(api->mute_controls_from_db, (void *) control->name, control);
-+ }
-+
-+ *_r = control;
-+ return 0;
-+}
-+
-+static int control_db_parse_mute_control_description_cb(pa_inidb *db, const char *value, void *object) {
-+ pa_mute_control *control = object;
-
-- type = pa_audio_group_create_binding_target_type(api);
-- pa_volume_api_add_binding_target_type(api, type);
-+ pa_assert(db);
-+ pa_assert(value);
-+ pa_assert(control);
-+
-+ pa_mute_control_set_description(control, value);
-+
-+ return 0;
- }
-
--static void delete_builtin_binding_target_types(pa_volume_api *api) {
-- pa_binding_target_type *type;
-+static int control_db_parse_mute_control_mute_cb(pa_inidb *db, const char *value, void *object) {
-+ pa_mute_control *control = object;
-+ int mute;
-+
-+ pa_assert(db);
-+ pa_assert(value);
-+ pa_assert(control);
-+
-+ mute = pa_parse_boolean(value);
-+ if (mute < 0)
-+ return -PA_ERR_INVALID;
-+
-+ pa_mute_control_set_mute(control, mute);
-+
-+ return 0;
-+}
-+
-+static void create_control_db(pa_volume_api *api) {
-+ pa_volume_control *volume_control;
-+ pa_mute_control *mute_control;
-+
-+ pa_assert(api);
-+ pa_assert(!api->control_db.db);
-+
-+ api->control_db.db = pa_inidb_new(api->core, "controls", api);
-+
-+ api->control_db.volume_controls = pa_inidb_add_table(api->control_db.db, CONTROL_DB_TABLE_NAME_VOLUME_CONTROL,
-+ control_db_get_volume_control_cb);
-+ pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION,
-+ control_db_parse_volume_control_description_cb);
-+ pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_VOLUME,
-+ control_db_parse_volume_control_volume_cb);
-+ pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_BALANCE,
-+ control_db_parse_volume_control_balance_cb);
-+ pa_inidb_table_add_column(api->control_db.volume_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_CONVERTIBLE_TO_DB,
-+ control_db_parse_volume_control_convertible_to_dB_cb);
-+
-+ api->control_db.mute_controls = pa_inidb_add_table(api->control_db.db, CONTROL_DB_TABLE_NAME_MUTE_CONTROL,
-+ control_db_get_mute_control_cb);
-+ pa_inidb_table_add_column(api->control_db.mute_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION,
-+ control_db_parse_mute_control_description_cb);
-+ pa_inidb_table_add_column(api->control_db.mute_controls, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_MUTE,
-+ control_db_parse_mute_control_mute_cb);
-+
-+ api->volume_controls_from_db = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-+ api->mute_controls_from_db = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-+
-+ pa_inidb_load(api->control_db.db);
-+
-+ while ((volume_control = pa_hashmap_steal_first(api->volume_controls_from_db)))
-+ pa_volume_control_put(volume_control);
-
-+ pa_hashmap_free(api->volume_controls_from_db);
-+ api->volume_controls_from_db = NULL;
-+
-+ while ((mute_control = pa_hashmap_steal_first(api->mute_controls_from_db)))
-+ pa_mute_control_put(mute_control);
-+
-+ pa_hashmap_free(api->mute_controls_from_db);
-+ api->mute_controls_from_db = NULL;
-+}
-+
-+static void delete_control_db(pa_volume_api *api) {
- 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);
-+ if (!api->control_db.db)
-+ return;
-+
-+ pa_inidb_free(api->control_db.db);
-+ api->control_db.mute_controls = NULL;
-+ api->control_db.volume_controls = NULL;
-+ api->control_db.db = NULL;
- }
-
- static void create_objects_defer_event_cb(pa_mainloop_api *mainloop_api, pa_defer_event *event, void *userdata) {
-@@ -138,7 +307,6 @@ static pa_volume_api *volume_api_new(pa_core *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);
-@@ -149,7 +317,7 @@ static pa_volume_api *volume_api_new(pa_core *core) {
- for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++)
- pa_hook_init(&api->hooks[i], api);
-
-- create_builtin_binding_target_types(api);
-+ create_control_db(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
-@@ -170,6 +338,9 @@ static void volume_api_free(pa_volume_api *api) {
-
- pa_log_debug("Freeing the pa_volume_api object.");
-
-+ pa_assert(!api->mute_controls_from_db);
-+ pa_assert(!api->volume_controls_from_db);
-+
- if (api->stream_creator)
- pa_stream_creator_free(api->stream_creator);
-
-@@ -179,8 +350,7 @@ static void volume_api_free(pa_volume_api *api) {
- 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);
-+ delete_control_db(api);
-
- for (i = 0; i < PA_VOLUME_API_HOOK_MAX; i++)
- pa_hook_done(&api->hooks[i]);
-@@ -201,12 +371,24 @@ static void volume_api_free(pa_volume_api *api) {
- }
-
- if (api->mute_controls) {
-- pa_assert(pa_hashmap_isempty(api->mute_controls));
-+ pa_mute_control *control;
-+
-+ while ((control = pa_hashmap_first(api->mute_controls))) {
-+ pa_assert(!control->present);
-+ pa_mute_control_free(control);
-+ }
-+
- pa_hashmap_free(api->mute_controls);
- }
-
- if (api->volume_controls) {
-- pa_assert(pa_hashmap_isempty(api->volume_controls));
-+ pa_volume_control *control;
-+
-+ while ((control = pa_hashmap_first(api->volume_controls))) {
-+ pa_assert(!control->present);
-+ pa_volume_control_free(control);
-+ }
-+
- pa_hashmap_free(api->volume_controls);
- }
-
-@@ -215,11 +397,6 @@ static void volume_api_free(pa_volume_api *api) {
- 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);
- }
-
-@@ -231,19 +408,24 @@ int pa_volume_api_register_name(pa_volume_api *api, const char *requested_name,
- pa_assert(requested_name);
- pa_assert(registered_name);
-
-+ if (!pa_namereg_is_valid_name(requested_name)) {
-+ pa_log("Invalid name: \"%s\"", requested_name);
-+ return -PA_ERR_INVALID;
-+ }
-+
- 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_xfree(n);
- pa_log("Name %s already registered.", requested_name);
- return -PA_ERR_EXIST;
- }
-
- do {
-+ pa_xfree(n);
- i++;
- n = pa_sprintf_malloc("%s.%u", requested_name, i);
- } while (pa_hashmap_put(api->names, n, n) < 0);
-@@ -271,38 +453,6 @@ uint32_t pa_volume_api_allocate_volume_control_index(pa_volume_api *api) {
- 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);
-@@ -318,10 +468,10 @@ int pa_volume_api_remove_volume_control(pa_volume_api *api, pa_volume_control *c
- return -1;
-
- if (control == api->main_output_volume_control)
-- set_main_output_volume_control_internal(api, NULL);
-+ pa_volume_api_set_main_output_volume_control(api, NULL);
-
- if (control == api->main_input_volume_control)
-- set_main_input_volume_control_internal(api, NULL);
-+ pa_volume_api_set_main_input_volume_control(api, NULL);
-
- return 0;
- }
-@@ -350,38 +500,6 @@ uint32_t pa_volume_api_allocate_mute_control_index(pa_volume_api *api) {
- 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);
-@@ -397,10 +515,10 @@ int pa_volume_api_remove_mute_control(pa_volume_api *api, pa_mute_control *contr
- return -1;
-
- if (control == api->main_output_mute_control)
-- set_main_output_mute_control_internal(api, NULL);
-+ pa_volume_api_set_main_output_mute_control(api, NULL);
-
- if (control == api->main_input_mute_control)
-- set_main_input_mute_control_internal(api, NULL);
-+ pa_volume_api_set_main_input_mute_control(api, NULL);
-
- return 0;
- }
-@@ -543,105 +661,73 @@ pa_audio_group *pa_volume_api_get_audio_group_by_index(pa_volume_api *api, uint3
- }
-
- void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_control *control) {
-+ pa_volume_control *old_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;
-- }
-+ old_control = api->main_output_volume_control;
-
-- set_main_output_volume_control_internal(api, control);
--}
-+ if (control == old_control)
-+ return;
-
--void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control) {
-- pa_assert(api);
-+ api->main_output_volume_control = control;
-
-- if (api->main_input_volume_control_binding) {
-- pa_binding_free(api->main_input_volume_control_binding);
-- api->main_input_volume_control_binding = NULL;
-- }
-+ pa_log_debug("Main output volume control changed from %s to %s.", old_control ? old_control->name : "(unset)",
-+ control ? control->name : "(unset)");
-
-- set_main_input_volume_control_internal(api, control);
-+ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_VOLUME_CONTROL_CHANGED], api);
- }
-
--void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control) {
-+void pa_volume_api_set_main_input_volume_control(pa_volume_api *api, pa_volume_control *control) {
-+ pa_volume_control *old_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;
-- }
-+ old_control = api->main_input_volume_control;
-
-- set_main_output_mute_control_internal(api, control);
--}
-+ if (control == old_control)
-+ return;
-
--void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control) {
-- pa_assert(api);
-+ api->main_input_volume_control = control;
-
-- if (api->main_input_mute_control_binding) {
-- pa_binding_free(api->main_input_mute_control_binding);
-- api->main_input_mute_control_binding = NULL;
-- }
-+ pa_log_debug("Main input volume control changed from %s to %s.", old_control ? old_control->name : "(unset)",
-+ control ? control->name : "(unset)");
-
-- set_main_input_mute_control_internal(api, control);
-+ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_VOLUME_CONTROL_CHANGED], api);
- }
-
--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,
-- };
-+void pa_volume_api_set_main_output_mute_control(pa_volume_api *api, pa_mute_control *control) {
-+ pa_mute_control *old_control;
-
- 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);
--}
-+ old_control = api->main_output_mute_control;
-
--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,
-- };
-+ if (control == old_control)
-+ return;
-
-- pa_assert(api);
-- pa_assert(target_info);
-+ api->main_output_mute_control = control;
-
-- if (api->main_input_volume_control_binding)
-- pa_binding_free(api->main_input_volume_control_binding);
-+ pa_log_debug("Main output mute control changed from %s to %s.", old_control ? old_control->name : "(unset)",
-+ control ? control->name : "(unset)");
-
-- api->main_input_volume_control_binding = pa_binding_new(api, &owner_info, target_info);
-+ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_OUTPUT_MUTE_CONTROL_CHANGED], api);
- }
-
--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,
-- };
-+void pa_volume_api_set_main_input_mute_control(pa_volume_api *api, pa_mute_control *control) {
-+ pa_mute_control *old_control;
-
- 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);
--}
-+ old_control = api->main_input_mute_control;
-
--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,
-- };
-+ if (control == old_control)
-+ return;
-
-- pa_assert(api);
-- pa_assert(target_info);
-+ api->main_input_mute_control = control;
-
-- if (api->main_input_mute_control_binding)
-- pa_binding_free(api->main_input_mute_control_binding);
-+ pa_log_debug("Main input mute control changed from %s to %s.", old_control ? old_control->name : "(unset)",
-+ control ? control->name : "(unset)");
-
-- api->main_input_mute_control_binding = pa_binding_new(api, &owner_info, target_info);
-+ pa_hook_fire(&api->hooks[PA_VOLUME_API_HOOK_MAIN_INPUT_MUTE_CONTROL_CHANGED], api);
- }
-diff --git a/src/modules/volume-api/volume-api.h b/src/modules/volume-api/volume-api.h
-index 73a1410..f99182a 100644
---- a/src/modules/volume-api/volume-api.h
-+++ b/src/modules/volume-api/volume-api.h
-@@ -24,27 +24,56 @@
-
- #include <pulsecore/core.h>
-
-+#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION "description"
-+#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_VOLUME "volume"
-+#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_BALANCE "balance"
-+#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_CONVERTIBLE_TO_DB "convertible-to-dB"
-+#define PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_MUTE "mute"
-+
- 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_inidb pa_inidb;
-+typedef struct pa_inidb_table pa_inidb_table;
- 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,
-+ /* This is fired after the volume control implementation has done its part
-+ * of the volume control initialization, but before policy modules have
-+ * done their part of the initialization. Hook users are expected to not
-+ * modify the volume control state in this hook. */
-+ PA_VOLUME_API_HOOK_VOLUME_CONTROL_IMPLEMENTATION_INITIALIZED,
-+
-+ /* Policy modules can use this hook to initialize the volume control
-+ * volume. This is fired before PUT. If a policy module sets the volume, it
-+ * should return PA_HOOK_STOP to prevent lower-priority policy modules from
-+ * modifying the volume. */
-+ PA_VOLUME_API_HOOK_VOLUME_CONTROL_SET_INITIAL_VOLUME,
-+
- 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_VOLUME_CONTROL_CONVERTIBLE_TO_DB_CHANGED,
-+
-+ /* This is fired after the mute control implementation has done its part of
-+ * the mute control initialization, but before policy modules have done
-+ * their part of the initialization. Hook users are expected to not modify
-+ * the mute control state in this hook. */
-+ PA_VOLUME_API_HOOK_MUTE_CONTROL_IMPLEMENTATION_INITIALIZED,
-+
-+ /* Policy modules can use this hook to initialize the mute control mute.
-+ * This is fired before PUT. If a policy module sets the mute, it should
-+ * return PA_HOOK_STOP to prevent lower-priority policy modules from
-+ * modifying the mute. */
-+ PA_VOLUME_API_HOOK_MUTE_CONTROL_SET_INITIAL_MUTE,
-+
- PA_VOLUME_API_HOOK_MUTE_CONTROL_PUT,
- PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK,
- PA_VOLUME_API_HOOK_MUTE_CONTROL_DESCRIPTION_CHANGED,
-@@ -54,26 +83,16 @@ enum {
- 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_PROPLIST_CHANGED,
- PA_VOLUME_API_HOOK_STREAM_VOLUME_CONTROL_CHANGED,
-+ PA_VOLUME_API_HOOK_STREAM_RELATIVE_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_DESCRIPTION_CHANGED,
- 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,
-@@ -86,7 +105,6 @@ enum {
- 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 */
-@@ -103,27 +121,28 @@ struct pa_volume_api {
- 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];
-+
-+ struct {
-+ pa_inidb *db;
-+ pa_inidb_table *volume_controls;
-+ pa_inidb_table *mute_controls;
-+ } control_db;
-+
- pa_defer_event *create_objects_defer_event;
- pa_device_creator *device_creator;
- pa_stream_creator *stream_creator;
-+
-+ pa_hashmap *volume_controls_from_db; /* control name -> pa_volume_control, only used during initialization. */
-+ pa_hashmap *mute_controls_from_db; /* control name -> pa_mute_control, only used during initialization. */
- };
-
- 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);
-@@ -155,9 +174,5 @@ void pa_volume_api_set_main_output_volume_control(pa_volume_api *api, pa_volume_
- 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
-index c7f5dbb..bf4db71 100644
---- a/src/modules/volume-api/volume-control.c
-+++ b/src/modules/volume-api/volume-control.c
-@@ -27,88 +27,96 @@
-
- #include <modules/volume-api/audio-group.h>
- #include <modules/volume-api/device.h>
-+#include <modules/volume-api/inidb.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;
-+int pa_volume_control_new(pa_volume_api *api, const char *name, bool persistent, pa_volume_control **_r) {
-+ pa_volume_control *control = NULL;
-+ int r;
-
- pa_assert(api);
- pa_assert(name);
-- pa_assert(description);
-+ pa_assert(_r);
-
- 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);
-+
-+ r = pa_volume_api_register_name(api, name, persistent, &control->name);
-+ if (r < 0)
-+ goto fail;
-+
-+ control->description = pa_xstrdup(control->name);
- 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;
-+ pa_bvolume_init_mono(&control->volume, PA_VOLUME_NORM);
-+ control->present = !persistent;
-+ control->persistent = persistent;
-+ control->purpose = PA_VOLUME_CONTROL_PURPOSE_OTHER;
- 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;
-+ if (persistent) {
-+ pa_inidb_row *row;
-+
-+ row = pa_inidb_table_add_row(api->control_db.volume_controls, control->name);
-+ control->db_cells.description = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_DESCRIPTION);
-+ control->db_cells.volume = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_VOLUME);
-+ control->db_cells.balance = pa_inidb_row_get_cell(row, PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_BALANCE);
-+ control->db_cells.convertible_to_dB = pa_inidb_row_get_cell(row,
-+ PA_VOLUME_API_CONTROL_DB_COLUMN_NAME_CONVERTIBLE_TO_DB);
-+ }
-+
-+ *_r = control;
-+ return 0;
-+
-+fail:
-+ if (control)
-+ pa_volume_control_free(control);
-+
-+ return r;
- }
-
--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_put(pa_volume_control *control) {
- 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);
-- }
-+ pa_assert(control->set_volume || !control->present);
-
-- if (set_initial_volume_cb)
-- set_initial_volume_cb(control);
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_IMPLEMENTATION_INITIALIZED], control);
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_SET_INITIAL_VOLUME], control);
-
-- pa_volume_api_add_volume_control(control->volume_api, control);
-+ if (control->set_volume) {
-+ control->set_volume_in_progress = true;
-+ control->set_volume(control, &control->volume, &control->volume, true, true);
-+ control->set_volume_in_progress = false;
-+ }
-
-+ 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(" 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(" Present: %s", pa_yes_no(control->present));
-+ pa_log_debug(" Persistent: %s", pa_yes_no(control->persistent));
- 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);
-
-@@ -122,15 +130,9 @@ void pa_volume_control_unlink(pa_volume_control *control) {
- 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);
-+ pa_volume_api_remove_volume_control(control->volume_api, control);
-
-- while ((stream = pa_hashmap_first(control->streams)))
-- pas_stream_set_volume_control(stream, NULL);
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK], control);
-
- while ((device = pa_hashmap_first(control->default_for_devices)))
- pa_device_set_default_volume_control(device, NULL);
-@@ -153,19 +155,10 @@ void pa_volume_control_unlink(pa_volume_control *control) {
- void pa_volume_control_free(pa_volume_control *control) {
- pa_assert(control);
-
-- if (!control->unlinked)
-+ /* unlink() expects name to be set. */
-+ if (!control->unlinked && control->name)
- 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);
-@@ -187,17 +180,91 @@ void pa_volume_control_free(pa_volume_control *control) {
- pa_xfree(control);
- }
-
--void pa_volume_control_set_owner_audio_group(pa_volume_control *control, pa_audio_group *group) {
-+void pa_volume_control_set_purpose(pa_volume_control *control, pa_volume_control_purpose_t purpose, void *owner) {
-+ pa_assert(control);
-+ pa_assert(!control->linked);
-+
-+ control->purpose = purpose;
-+ control->owner = owner;
-+}
-+
-+int pa_volume_control_acquire_for_audio_group(pa_volume_control *control, pa_audio_group *group,
-+ pa_volume_control_set_volume_cb_t set_volume_cb, void *userdata) {
- pa_assert(control);
- pa_assert(group);
-+ pa_assert(set_volume_cb);
-+
-+ if (control->present) {
-+ pa_log("Can't acquire volume control %s, it's already present.", control->name);
-+ return -PA_ERR_BUSY;
-+ }
-+
-+ control->set_volume = set_volume_cb;
-+ control->userdata = userdata;
-+
-+ control->set_volume_in_progress = true;
-+ control->set_volume(control, &control->volume, &control->volume, true, true);
-+ control->set_volume_in_progress = false;
-+
-+ control->present = true;
-+
-+ if (!control->linked || control->unlinked)
-+ return 0;
-+
-+ pa_log_debug("Volume control %s became present.", control->name);
-+
-+ return 0;
-+}
-+
-+void pa_volume_control_release(pa_volume_control *control) {
-+ pa_assert(control);
-+
-+ if (!control->present)
-+ return;
-+
-+ control->present = false;
-+
-+ control->userdata = NULL;
-+ control->set_volume = NULL;
-+
-+ if (!control->linked || control->unlinked)
-+ return;
-+
-+ pa_log_debug("Volume control %s became not present.", control->name);
-+}
-+
-+void pa_volume_control_set_description(pa_volume_control *control, const char *description) {
-+ char *old_description;
-+
-+ pa_assert(control);
-+ pa_assert(description);
-
-- control->owner_audio_group = group;
-+ old_description = control->description;
-+
-+ if (pa_streq(description, old_description))
-+ return;
-+
-+ control->description = pa_xstrdup(description);
-+
-+ if (control->persistent)
-+ pa_inidb_cell_set_value(control->db_cells.description, description);
-+
-+ if (!control->linked || control->unlinked) {
-+ pa_xfree(old_description);
-+ return;
-+ }
-+
-+ pa_log_debug("The description of volume control %s changed from \"%s\" to \"%s\".", control->name, old_description,
-+ description);
-+ pa_xfree(old_description);
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_DESCRIPTION_CHANGED], control);
- }
-
- 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;
-+ char *str;
-
- pa_assert(control);
- pa_assert(volume);
-@@ -209,12 +276,26 @@ static void set_volume_internal(pa_volume_control *control, const pa_bvolume *vo
- if (!volume_changed && !balance_changed)
- return;
-
-- if (volume_changed)
-+ if (volume_changed) {
- control->volume.volume = volume->volume;
-
-- if (balance_changed)
-+ if (control->persistent) {
-+ str = pa_sprintf_malloc("%u", control->volume.volume);
-+ pa_inidb_cell_set_value(control->db_cells.volume, str);
-+ pa_xfree(str);
-+ }
-+ }
-+
-+ if (balance_changed) {
- pa_bvolume_copy_balance(&control->volume, volume);
-
-+ if (control->persistent) {
-+ pa_assert_se(pa_bvolume_balance_to_string(&control->volume, &str) >= 0);
-+ pa_inidb_cell_set_value(control->db_cells.balance, str);
-+ pa_xfree(str);
-+ }
-+ }
-+
- if (!control->linked || control->unlinked)
- return;
-
-@@ -248,62 +329,69 @@ int pa_volume_control_set_volume(pa_volume_control *control, const pa_bvolume *v
- pa_assert(control);
- pa_assert(volume);
-
-- volume_local = *volume;
-+ if (control->set_volume_in_progress)
-+ return 0;
-
-- 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;
-- }
-+ volume_local = *volume;
-
-- if (set_balance
-- && !control->channel_map_is_writable
-- && !pa_channel_map_equal(&volume_local.channel_map, &control->volume.channel_map))
-+ if (set_balance && !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 (control->linked && control->present) {
-+ control->set_volume_in_progress = true;
-+ r = control->set_volume(control, volume, &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);
-+ if (r < 0) {
-+ pa_log("Setting the volume of volume control %s failed.", control->name);
-+ return r;
-+ }
-+ }
-
-- return r;
-+ set_volume_internal(control, &volume_local, set_volume, set_balance);
-+
-+ return 0;
- }
-
--void pa_volume_control_description_changed(pa_volume_control *control, const char *new_description) {
-- char *old_description;
-+void pa_volume_control_set_channel_map(pa_volume_control *control, const pa_channel_map *map) {
-+ pa_bvolume bvolume;
-
- pa_assert(control);
-- pa_assert(new_description);
-+ pa_assert(map);
-
-- old_description = control->description;
--
-- if (pa_streq(new_description, old_description))
-+ if (pa_channel_map_equal(map, &control->volume.channel_map))
- 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);
-+ pa_bvolume_copy_balance(&bvolume, &control->volume);
-+ pa_bvolume_remap(&bvolume, map);
-+
-+ set_volume_internal(control, &bvolume, false, true);
- }
-
--void pa_volume_control_volume_changed(pa_volume_control *control, const pa_bvolume *new_volume, bool volume_changed,
-- bool balance_changed) {
-+void pa_volume_control_set_convertible_to_dB(pa_volume_control *control, bool convertible) {
-+ bool old_convertible;
-+
- pa_assert(control);
-- pa_assert(new_volume);
-
-- if (!control->linked)
-+ old_convertible = control->convertible_to_dB;
-+
-+ if (convertible == old_convertible)
- return;
-
-- if (control->set_volume_in_progress)
-+ control->convertible_to_dB = convertible;
-+
-+ if (control->persistent)
-+ pa_inidb_cell_set_value(control->db_cells.convertible_to_dB, pa_boolean_to_string(convertible));
-+
-+ if (!control->linked || control->unlinked)
- return;
-
-- set_volume_internal(control, new_volume, volume_changed, balance_changed);
-+ pa_log_debug("The volume of volume control %s became %sconvertible to dB.", control->name, convertible ? "" : "not ");
-+
-+ pa_hook_fire(&control->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_CONVERTIBLE_TO_DB_CHANGED], control);
- }
-
- void pa_volume_control_add_device(pa_volume_control *control, pa_device *device) {
-@@ -333,31 +421,3 @@ void pa_volume_control_remove_default_for_device(pa_volume_control *control, pa_
-
- 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
-index aaba758..a47ab20 100644
---- a/src/modules/volume-api/volume-control.h
-+++ b/src/modules/volume-api/volume-control.h
-@@ -23,10 +23,23 @@
- ***/
-
- #include <modules/volume-api/bvolume.h>
-+#include <modules/volume-api/inidb.h>
- #include <modules/volume-api/volume-api.h>
-
- typedef struct pa_volume_control pa_volume_control;
-
-+typedef enum {
-+ PA_VOLUME_CONTROL_PURPOSE_STREAM_RELATIVE_VOLUME,
-+ PA_VOLUME_CONTROL_PURPOSE_OTHER,
-+} pa_volume_control_purpose_t;
-+
-+/* Usually remapped_volume is the volume to use, because it has a matching
-+ * channel map with the control, but in case the volume needs to be propagated
-+ * to another control, original_volume can be used to avoid loss of precision
-+ * that can result from remapping. */
-+typedef int (*pa_volume_control_set_volume_cb_t)(pa_volume_control *control, const pa_bvolume *original_volume,
-+ const pa_bvolume *remapped_volume, bool set_volume, bool set_balance);
-+
- struct pa_volume_control {
- pa_volume_api *volume_api;
- uint32_t index;
-@@ -35,65 +48,61 @@ struct pa_volume_control {
- pa_proplist *proplist;
- pa_bvolume volume;
- bool convertible_to_dB;
-- bool channel_map_is_writable;
-+ bool present;
-+ bool persistent;
-
-- /* 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_volume_control_purpose_t purpose;
-+ union {
-+ pas_stream *owner_stream;
-+ void *owner;
-+ };
-
- 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) */
-+
-+ struct {
-+ pa_inidb_cell *description;
-+ pa_inidb_cell *volume;
-+ pa_inidb_cell *balance;
-+ pa_inidb_cell *convertible_to_dB;
-+ } db_cells;
-
- 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);
-+ * expected to return a negative error code on failure. */
-+ pa_volume_control_set_volume_cb_t set_volume;
-
- 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);
--
-+int pa_volume_control_new(pa_volume_api *api, const char *name, bool persistent, pa_volume_control **_r);
-+void pa_volume_control_put(pa_volume_control *control);
- 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 the volume control implementation, before
-+ * pa_volume_control_put(). */
-+void pa_volume_control_set_purpose(pa_volume_control *control, pa_volume_control_purpose_t purpose, void *owner);
-
--/* Called by clients and policy modules. */
-+/* Called by the volume control implementation. */
-+int pa_volume_control_acquire_for_audio_group(pa_volume_control *control, pa_audio_group *group,
-+ pa_volume_control_set_volume_cb_t set_volume_cb, void *userdata);
-+
-+/* Called by the volume control implementation. This must only be called for
-+ * persistent controls; use pa_volume_control_free() for non-persistent
-+ * controls. */
-+void pa_volume_control_release(pa_volume_control *control);
-+
-+/* Called by anyone. */
-+void pa_volume_control_set_description(pa_volume_control *control, const char *description);
- 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);
-+void pa_volume_control_set_channel_map(pa_volume_control *control, const pa_channel_map *map);
-+void pa_volume_control_set_convertible_to_dB(pa_volume_control *control, bool convertible);
-
- /* Called from device.c only. */
- void pa_volume_control_add_device(pa_volume_control *control, pa_device *device);
-@@ -101,12 +110,4 @@ void pa_volume_control_remove_device(pa_volume_control *control, pa_device *devi
- 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
-index 032e108..b81909a 100644
---- a/src/pulse/ext-volume-api.c
-+++ b/src/pulse/ext-volume-api.c
-@@ -36,6 +36,7 @@
- #include <pulsecore/i18n.h>
- #include <pulsecore/macro.h>
- #include <pulsecore/pstream-util.h>
-+#include <pulsecore/strbuf.h>
-
- #include <math.h>
-
-@@ -94,6 +95,21 @@ void pa_ext_volume_api_bvolume_init_invalid(pa_ext_volume_api_bvolume *volume) {
- pa_channel_map_init(&volume->channel_map);
- }
-
-+void pa_ext_volume_api_bvolume_init(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume, pa_channel_map *map) {
-+ unsigned i;
-+
-+ pa_assert(bvolume);
-+ pa_assert(PA_VOLUME_IS_VALID(volume));
-+ pa_assert(map);
-+ pa_assert(pa_channel_map_valid(map));
-+
-+ bvolume->volume = volume;
-+ bvolume->channel_map = *map;
-+
-+ for (i = 0; i < map->channels; i++)
-+ bvolume->balance[i] = 1.0;
-+}
-+
- 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));
-@@ -103,6 +119,67 @@ void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_
- pa_channel_map_init_mono(&bvolume->channel_map);
- }
-
-+int pa_ext_volume_api_bvolume_parse_balance(const char *str, pa_ext_volume_api_bvolume *_r) {
-+ pa_ext_volume_api_bvolume bvolume;
-+
-+ pa_assert(str);
-+ pa_assert(_r);
-+
-+ bvolume.channel_map.channels = 0;
-+
-+ for (;;) {
-+ const char *colon;
-+ size_t channel_name_len;
-+ char *channel_name;
-+ pa_channel_position_t position;
-+ const char *space;
-+ size_t balance_str_len;
-+ char *balance_str;
-+ int r;
-+ double balance;
-+
-+ colon = strchr(str, ':');
-+ if (!colon)
-+ return -PA_ERR_INVALID;
-+
-+ channel_name_len = colon - str;
-+ channel_name = pa_xstrndup(str, channel_name_len);
-+
-+ position = pa_channel_position_from_string(channel_name);
-+ pa_xfree(channel_name);
-+ if (position == PA_CHANNEL_POSITION_INVALID)
-+ return -PA_ERR_INVALID;
-+
-+ bvolume.channel_map.map[bvolume.channel_map.channels] = position;
-+ str = colon + 1;
-+
-+ space = strchr(str, ' ');
-+ if (space)
-+ balance_str_len = space - str;
-+ else
-+ balance_str_len = strlen(str);
-+
-+ balance_str = pa_xstrndup(str, balance_str_len);
-+
-+ r = pa_atod(balance_str, &balance);
-+ if (r < 0)
-+ return -PA_ERR_INVALID;
-+
-+ if (!pa_ext_volume_api_balance_valid(balance))
-+ return -PA_ERR_INVALID;
-+
-+ bvolume.balance[bvolume.channel_map.channels++] = balance;
-+
-+ if (space)
-+ str = space + 1;
-+ else
-+ break;
-+ }
-+
-+ pa_ext_volume_api_bvolume_copy_balance(_r, &bvolume);
-+ return 0;
-+}
-+
- 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;
-@@ -266,6 +343,29 @@ void pa_ext_volume_api_bvolume_set_rear_front_balance(pa_ext_volume_api_bvolume
- volume->volume = old_volume;
- }
-
-+int pa_ext_volume_api_bvolume_balance_to_string(const pa_ext_volume_api_bvolume *volume, char **_r) {
-+ pa_strbuf *buf;
-+ unsigned i;
-+
-+ pa_assert(volume);
-+ pa_assert(_r);
-+
-+ if (!pa_ext_volume_api_bvolume_valid(volume, false, true))
-+ return -PA_ERR_INVALID;
-+
-+ buf = pa_strbuf_new();
-+
-+ for (i = 0; i < volume->channel_map.channels; i++) {
-+ if (i != 0)
-+ pa_strbuf_putc(buf, ' ');
-+
-+ pa_strbuf_printf(buf, "%s:%.2f", pa_channel_position_to_string(volume->channel_map.map[i]), volume->balance[i]);
-+ }
-+
-+ *_r = pa_strbuf_tostring_free(buf);
-+ return 0;
-+}
-+
- char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_len,
- const pa_ext_volume_api_bvolume *volume) {
- char *e;
-diff --git a/src/pulse/ext-volume-api.h b/src/pulse/ext-volume-api.h
-index 720ff39..6402f4b 100644
---- a/src/pulse/ext-volume-api.h
-+++ b/src/pulse/ext-volume-api.h
-@@ -50,7 +50,9 @@ 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(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume, pa_channel_map *map);
- void pa_ext_volume_api_bvolume_init_mono(pa_ext_volume_api_bvolume *bvolume, pa_volume_t volume);
-+int pa_ext_volume_api_bvolume_parse_balance(const char *str, pa_ext_volume_api_bvolume *bvolume);
- 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,
-@@ -64,6 +66,7 @@ double pa_ext_volume_api_bvolume_get_left_right_balance(const pa_ext_volume_api_
- 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);
-+int pa_ext_volume_api_bvolume_balance_to_string(const pa_ext_volume_api_bvolume *volume, char **_r);
-
- #define PA_EXT_VOLUME_API_BVOLUME_SNPRINT_BALANCE_MAX 500
- char *pa_ext_volume_api_bvolume_snprint_balance(char *buf, size_t buf_size,
-diff --git a/src/tizen-ivi/audio-groups.conf b/src/tizen-ivi/audio-groups.conf
-index 4839307..182df0d 100644
---- a/src/tizen-ivi/audio-groups.conf
-+++ b/src/tizen-ivi/audio-groups.conf
-@@ -1,33 +1,32 @@
- [General]
--audio-groups = x-tizen-ivi-call-downlink-audio-group x-tizen-ivi-navigator-output-audio-group x-tizen-ivi-default-output-audio-group
--streams = call-downlink navigator-output default-output
-+stream-rules = call-downlink navigator-output default-output
-
- [AudioGroup x-tizen-ivi-call-downlink-audio-group]
- description = Call downlink
--volume-control = create
--mute-control = create
-+volume-control = create:call-downlink-volume-control
-+mute-control = create:call-downlink-mute-control
-
- [AudioGroup x-tizen-ivi-navigator-output-audio-group]
- description = Navigator
--volume-control = create
--mute-control = create
-+volume-control = bind:AudioGroup:x-tizen-ivi-default-output-audio-group
-+mute-control = bind:AudioGroup:x-tizen-ivi-default-output-audio-group
-
- [AudioGroup x-tizen-ivi-default-output-audio-group]
- description = Default
--volume-control = create
--mute-control = create
-+volume-control = create:default-output-volume-control
-+mute-control = create:default-output-mute-control
-
--[Stream call-downlink]
-+[StreamRule call-downlink]
- match = (direction output AND property media.role=phone)
- audio-group-for-volume = x-tizen-ivi-call-downlink-audio-group
- audio-group-for-mute = x-tizen-ivi-call-downlink-audio-group
-
--[Stream navigator-output]
-+[StreamRule navigator-output]
- match = (direction output AND property media.role=navigator)
- audio-group-for-volume = x-tizen-ivi-navigator-output-audio-group
- audio-group-for-mute = x-tizen-ivi-navigator-output-audio-group
-
--[Stream default-output]
--match = (direction output)
-+[StreamRule default-output]
-+match = (direction output AND NEG property media.role=filter)
- audio-group-for-volume = x-tizen-ivi-default-output-audio-group
- audio-group-for-mute = x-tizen-ivi-default-output-audio-group
-diff --git a/src/tizen-ivi/main-volume-policy.conf b/src/tizen-ivi/main-volume-policy.conf
-index 0a83968..f2b3513 100644
---- a/src/tizen-ivi/main-volume-policy.conf
-+++ b/src/tizen-ivi/main-volume-policy.conf
-@@ -3,7 +3,6 @@ output-volume-model = by-active-main-volume-context
- input-volume-model = none
- output-mute-model = by-active-main-volume-context
- input-mute-model = none
--main-volume-contexts = x-tizen-ivi-call default
-
- [MainVolumeContext x-tizen-ivi-call]
- description = Call main volume context