audio-groups, main-volume-policy, volume-api: Various fixes 49/26449/1
authorTanu Kaskinen <tanu.kaskinen@linux.intel.com>
Tue, 17 Jun 2014 16:45:45 +0000 (19:45 +0300)
committerTanu Kaskinen <tanu.kaskinen@linux.intel.com>
Fri, 22 Aug 2014 10:43:43 +0000 (13:43 +0300)
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

34 files changed:
src/Makefile.am
src/map-file
src/modules/audio-groups/audio-groups.conf.example
src/modules/audio-groups/module-audio-groups.c
src/modules/main-volume-policy/main-volume-context.c
src/modules/main-volume-policy/main-volume-context.h
src/modules/main-volume-policy/main-volume-policy.c
src/modules/main-volume-policy/main-volume-policy.conf.example
src/modules/main-volume-policy/main-volume-policy.h
src/modules/main-volume-policy/module-main-volume-policy.c
src/modules/volume-api/audio-group.c
src/modules/volume-api/audio-group.h
src/modules/volume-api/binding.c [deleted file]
src/modules/volume-api/binding.h [deleted file]
src/modules/volume-api/bvolume.h
src/modules/volume-api/device-creator.c
src/modules/volume-api/device.c
src/modules/volume-api/device.h
src/modules/volume-api/inidb.c [new file with mode: 0644]
src/modules/volume-api/inidb.h [new file with mode: 0644]
src/modules/volume-api/module-volume-api.c
src/modules/volume-api/mute-control.c
src/modules/volume-api/mute-control.h
src/modules/volume-api/sstream.c
src/modules/volume-api/sstream.h
src/modules/volume-api/stream-creator.c
src/modules/volume-api/volume-api.c
src/modules/volume-api/volume-api.h
src/modules/volume-api/volume-control.c
src/modules/volume-api/volume-control.h
src/pulse/ext-volume-api.c
src/pulse/ext-volume-api.h
src/tizen-ivi/audio-groups.conf
src/tizen-ivi/main-volume-policy.conf

index 9d17336..b5cf2a8 100644 (file)
@@ -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 \
index 28ea54e..cb31833 100644 (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;
index 8acdb76..6aa6989 100644 (file)
@@ -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
index 2b3a570..e37a24e 100644 (file)
 
 #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 userdatau;
+    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);
 }
index 7ac35c6..9b9f9fd 100644 (file)
 #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;
-}
index 4a0a6f7..3770168 100644 (file)
 
 #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
index b0b4ede..3c0fccf 100644 (file)
@@ -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);
index a4a35d3..3fcd267 100644 (file)
@@ -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
index 5cd669e..d5f6e02 100644 (file)
@@ -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);
index 0a89aa7..1b7693e 100644 (file)
@@ -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);
 }
index 76bfa69..66e0f8a 100644 (file)
 
 #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;
 }
index 41591ba..02db3eb 100644 (file)
@@ -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>
 
 
 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 (file)
index 6e73119..0000000
+++ /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 (file)
index ba4dea8..0000000
+++ /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
index 0317fb6..75545dd 100644 (file)
@@ -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
index f35fab0..fc486f8 100644 (file)
@@ -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) {
index ea496ba..c1a580c 100644 (file)
 
 #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)
index 9eac7e9..8bd5158 100644 (file)
@@ -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 (file)
index 0000000..8116e72
--- /dev/null
@@ -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 (file)
index 0000000..ded73ba
--- /dev/null
@@ -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
index 845ac09..7b112f6 100644 (file)
@@ -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);
 
index adc008e..1b2f276 100644 (file)
 
 #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));
-}
index 1f70a43..40f8a9c 100644 (file)
   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
index e3531a8..1738d15 100644 (file)
@@ -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>
 
 
 #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)");
 }
index a65b34c..715bf2c 100644 (file)
@@ -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);
 
index f6ca7b3..0d9ea24 100644 (file)
@@ -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);
index 9abea7e..4a8a2e6 100644 (file)
 #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);
 }
index 73a1410..f99182a 100644 (file)
 
 #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
index c7f5dbb..bf4db71 100644 (file)
 
 #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));
-}
index aaba758..a47ab20 100644 (file)
 ***/
 
 #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
index 032e108..b81909a 100644 (file)
@@ -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;
index 720ff39..6402f4b 100644 (file)
@@ -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,
index 4839307..182df0d 100644 (file)
@@ -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
index 0a83968..f2b3513 100644 (file)
@@ -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