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 \
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;
[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
#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,
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[] = {
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)
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;
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;
}
(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",
print_conjunction(c);
}
}
+#endif
static void delete_literal(struct literal *l) {
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 {
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) {
e = pa_xnew0(struct expression, 1);
- if (!e)
- goto error;
-
PA_LLIST_HEAD_INIT(struct conjunction, e->conjunctions);
/* gather expressions to actual match format */
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) {
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;
continue;
}
- pa_dynarray_append(u->stream_names, name);
+ pa_dynarray_append(u->stream_rule_names, name);
}
return 0;
}
-static int parse_common(pa_config_parser_state *state) {
-#define AUDIOGROUP_START "AudioGroup "
-#define STREAM_START "Stream "
-#define BIND_KEYWORD "bind:"
-#define NONE_KEYWORD "none"
-
- char *section;
- struct userdata *u = (struct userdata *) state->userdata;
- int r;
- pa_binding_target_info *target_info;
-
+static int parse_group_control(pa_config_parser_state *state, struct group *group, enum control_type type) {
pa_assert(state);
+ pa_assert(group);
- section = state->section;
- if (!section)
- goto error;
+ if (pa_streq(state->rvalue, NONE_KEYWORD))
+ group_disable_control(group, type);
+
+ else if (pa_startswith(state->rvalue, CREATE_PREFIX))
+ group_set_own_control_name(group, type, state->rvalue + strlen(CREATE_PREFIX));
- if (strncmp(section, AUDIOGROUP_START, strlen(AUDIOGROUP_START)) == 0) {
- char *ag_name = section + strlen(AUDIOGROUP_START);
- struct audio_group *ag = (struct audio_group *) pa_hashmap_get(u->unused_audio_groups, ag_name);
+ else if (pa_startswith(state->rvalue, BIND_PREFIX)) {
+ if (pa_startswith(state->rvalue, BIND_AUDIO_GROUP_PREFIX)) {
+ int r;
- if (!ag) {
- /* first item for this audio group section, so create the struct */
- ag = audio_group_new(u, ag_name);
- pa_hashmap_put(u->unused_audio_groups, ag->id, ag);
+ r = group_set_master_name(group, type, state->rvalue + strlen(BIND_AUDIO_GROUP_PREFIX));
+ if (r < 0) {
+ pa_log("[%s:%u] Failed to set binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
+ return r;
+ }
+ } else {
+ pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue + strlen(BIND_PREFIX));
+ return -PA_ERR_INVALID;
}
+ } else {
+ pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
+ return -PA_ERR_INVALID;
+ }
- if (strcmp(state->lvalue, "description") == 0)
- audio_group_set_description(ag, state->rvalue);
+ return 0;
+}
+
+static int parse_common(pa_config_parser_state *state) {
+ char *section;
+ struct userdata *u = state->userdata;
+ const char *name;
+ int r;
- else if (strcmp(state->lvalue, "volume-control") == 0) {
- if (pa_streq(state->rvalue, "create"))
- audio_group_set_volume_control_action(ag, CONTROL_ACTION_CREATE, NULL);
+ pa_assert(state);
- else if (pa_streq(state->rvalue, NONE_KEYWORD))
- audio_group_set_volume_control_action(ag, CONTROL_ACTION_NONE, NULL);
+ section = state->section;
+ if (!section) {
+ pa_log("[%s:%u] Lvalue \"%s\" not expected in the General section.", state->filename, state->lineno, state->lvalue);
+ return -PA_ERR_INVALID;
+ }
- else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
- r = pa_binding_target_info_new_from_string(state->rvalue, "volume_control", &target_info);
- if (r < 0) {
- pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
- goto error;
- }
+ if (pa_startswith(section, AUDIOGROUP_START)) {
+ struct group *group;
- audio_group_set_volume_control_action(ag, CONTROL_ACTION_BIND, target_info);
- pa_binding_target_info_free(target_info);
+ name = section + strlen(AUDIOGROUP_START);
- } else {
- pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
- goto error;
+ group = pa_hashmap_get(u->groups, name);
+ if (!group) {
+ r = group_new(u, name, &group);
+ if (r < 0) {
+ pa_log("[%s:%u] Failed to create an audio group with name \"%s\".", state->filename, state->lineno, name);
+ return r;
}
+
+ pa_hashmap_put(u->groups, (void *) group->audio_group->name, group);
}
- else if (strcmp(state->lvalue, "mute-control") == 0) {
- if (pa_streq(state->rvalue, "create"))
- audio_group_set_mute_control_action(ag, CONTROL_ACTION_CREATE, NULL);
-
- else if (pa_streq(state->rvalue, NONE_KEYWORD))
- audio_group_set_mute_control_action(ag, CONTROL_ACTION_NONE, NULL);
-
- else if (pa_startswith(state->rvalue, BIND_KEYWORD)) {
- r = pa_binding_target_info_new_from_string(state->rvalue, "mute_control", &target_info);
- if (r < 0) {
- pa_log("[%s:%u] Failed to parse binding target \"%s\".", state->filename, state->lineno, state->rvalue);
- goto error;
- }
- audio_group_set_mute_control_action(ag, CONTROL_ACTION_BIND, target_info);
- pa_binding_target_info_free(target_info);
+ if (pa_streq(state->lvalue, "description"))
+ pa_audio_group_set_description(group->audio_group, state->rvalue);
- } else {
- pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
- goto error;
- }
+ else if (pa_streq(state->lvalue, "volume-control"))
+ return parse_group_control(state, group, CONTROL_TYPE_VOLUME);
+
+ else if (pa_streq(state->lvalue, "mute-control"))
+ return parse_group_control(state, group, CONTROL_TYPE_MUTE);
+
+ else {
+ pa_log("[%s:%u] Lvalue \"%s\" not expected in the AudioGroup section.", state->filename, state->lineno, state->lvalue);
+ return -PA_ERR_INVALID;
}
}
- else if (strncmp(section, STREAM_START, strlen(STREAM_START)) == 0) {
- char *stream_name = section + strlen(STREAM_START);
+ else if (pa_startswith(section, STREAM_RULE_START)) {
+ struct stream_rule *rule;
- struct stream *stream = (struct stream *) pa_hashmap_get(u->unused_streams, stream_name);
+ name = section + strlen(STREAM_RULE_START);
- if (!stream) {
- /* first item for this stream section, so create the struct */
- stream = stream_new(u, stream_name);
- pa_hashmap_put(u->unused_streams, stream->id, stream);
+ rule = pa_hashmap_get(u->stream_rules, name);
+ if (!rule) {
+ rule = stream_rule_new(u, name);
+ pa_hashmap_put(u->stream_rules, rule->name, rule);
}
if (pa_streq(state->lvalue, "audio-group-for-volume"))
- stream_set_audio_group_name_for_volume(stream, *state->rvalue ? state->rvalue : NULL);
+ stream_rule_set_group_name(rule, CONTROL_TYPE_VOLUME, state->rvalue);
else if (pa_streq(state->lvalue, "audio-group-for-mute"))
- stream_set_audio_group_name_for_mute(stream, *state->rvalue ? state->rvalue : NULL);
+ stream_rule_set_group_name(rule, CONTROL_TYPE_MUTE, state->rvalue);
- else if (strcmp(state->lvalue, "match") == 0) {
- if (!state->rvalue)
- goto error;
+ else if (pa_streq(state->lvalue, "match")) {
+ struct expression *expression;
- stream->rule = parse_rule(state->rvalue);
-
- if (!stream->rule) {
- goto error;
+ r = expression_from_string(state->rvalue, &expression);
+ if (r < 0) {
+ pa_log("[%s:%u] Failed to parse value \"%s\".", state->filename, state->lineno, state->rvalue);
+ return r;
}
+
+ stream_rule_set_match_expression(rule, expression);
}
}
return 0;
-
-error:
-
- pa_log_error("failed parsing audio group definition file");
- return -1;
-
-#undef NONE_KEYWORD
-#undef AUDIO_GROUP_KEYWORD
-#undef BIND_KEYWORD
-#undef STREAM_START
-#undef AUDIOGROUP_START
}
-static void finalize_config(struct userdata *u) {
- const char *group_name;
+int pa__init(pa_module *module) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ FILE *f;
+ char *fn = NULL;
+ struct group *group;
void *state;
- struct audio_group *group;
- const char *stream_name;
+ const char *name;
unsigned idx;
- struct stream *stream;
- pa_assert(u);
-
- PA_HASHMAP_FOREACH(group_name, u->audio_group_names, state) {
- int r;
+ pa_assert(module);
- group = pa_hashmap_remove(u->unused_audio_groups, group_name);
- if (!group)
- group = audio_group_new(u, group_name);
-
- r = audio_group_put(group);
- if (r < 0) {
- pa_log("Failed to create audio group %s.", group_name);
- audio_group_free(group);
- continue;
- }
-
- pa_assert_se(pa_hashmap_put(u->audio_groups, group->id, group) >= 0);
+ if (!(ma = pa_modargs_new(module->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
}
- PA_HASHMAP_FOREACH(group, u->unused_audio_groups, state)
- pa_log_debug("Audio group %s is not used.", group->id);
-
- pa_hashmap_free(u->unused_audio_groups);
- u->unused_audio_groups = NULL;
-
- pa_hashmap_free(u->audio_group_names);
- u->audio_group_names = NULL;
-
- PA_DYNARRAY_FOREACH(stream_name, u->stream_names, idx) {
- stream = pa_hashmap_remove(u->unused_streams, stream_name);
- if (!stream) {
- pa_log("Reference to undefined stream %s, ignoring.", stream_name);
- continue;
- }
+ u = module->userdata = pa_xnew0(struct userdata, 1);
+ u->volume_api = pa_volume_api_get(module->core);
+ u->groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+ (pa_free_cb_t) group_free);
+ u->stream_rules = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+ (pa_free_cb_t) stream_rule_free);
+ u->stream_rules_list = pa_dynarray_new(NULL);
+ u->rules_by_stream = pa_hashmap_new(NULL, NULL);
+ u->stream_volume_controls = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) control_free);
+ u->stream_mute_controls = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) control_free);
+ u->stream_put_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_PUT], PA_HOOK_NORMAL, stream_put_cb,
+ u);
+ u->stream_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_STREAM_UNLINK], PA_HOOK_NORMAL,
+ stream_unlink_cb, u);
+ u->volume_control_implementation_initialized_slot =
+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_IMPLEMENTATION_INITIALIZED],
+ PA_HOOK_NORMAL, volume_control_implementation_initialized_cb, u);
+ u->mute_control_implementation_initialized_slot =
+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_IMPLEMENTATION_INITIALIZED],
+ PA_HOOK_NORMAL, mute_control_implementation_initialized_cb, u);
+ u->volume_control_set_initial_volume_slot =
+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_SET_INITIAL_VOLUME], PA_HOOK_NORMAL,
+ volume_control_set_initial_volume_cb, u);
+ u->mute_control_set_initial_mute_slot =
+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_SET_INITIAL_MUTE], PA_HOOK_NORMAL,
+ mute_control_set_initial_mute_cb, u);
+ u->volume_control_volume_changed_slot =
+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_VOLUME_CHANGED], PA_HOOK_NORMAL,
+ volume_control_volume_changed_cb, u);
+ u->mute_control_mute_changed_slot =
+ pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_MUTE_CHANGED], PA_HOOK_NORMAL,
+ mute_control_mute_changed_cb, u);
+ u->volume_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_VOLUME_CONTROL_UNLINK],
+ PA_HOOK_NORMAL, volume_control_unlink_cb, u);
+ u->mute_control_unlink_slot = pa_hook_connect(&u->volume_api->hooks[PA_VOLUME_API_HOOK_MUTE_CONTROL_UNLINK],
+ PA_HOOK_NORMAL, mute_control_unlink_cb, u);
+ u->stream_rule_names = pa_dynarray_new(pa_xfree);
+
+ f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "audio-groups.conf", "audio-groups.conf", NULL, &fn);
+ if (f) {
+ pa_config_item config_items[] = {
+ { "stream-rules", parse_streams, NULL, "General" },
+ { NULL, parse_common, NULL, NULL },
+ { NULL, NULL, NULL, NULL },
+ };
- stream_put(stream);
- pa_dynarray_append(u->streams, stream);
+ pa_config_parse(fn, f, config_items, NULL, u);
+ pa_xfree(fn);
+ fn = NULL;
+ fclose(f);
+ f = NULL;
}
- PA_HASHMAP_FOREACH(stream, u->unused_streams, state)
- pa_log_debug("Stream %s is not used.", stream->id);
-
- pa_hashmap_free(u->unused_streams);
- u->unused_streams = NULL;
-
- pa_dynarray_free(u->stream_names);
- u->stream_names = NULL;
-}
+ PA_HASHMAP_FOREACH(group, u->groups, state)
+ group_put(group);
-static bool parse_configuration(struct userdata *u, const char *filename) {
- FILE *f;
- char *fn = NULL;
+ PA_DYNARRAY_FOREACH(name, u->stream_rule_names, idx) {
+ struct stream_rule *rule;
- pa_config_item table[] = {
- { "audio-groups", parse_audio_groups, NULL, "General" },
- { "streams", parse_streams, NULL, "General" },
- { NULL, parse_common, NULL, NULL },
- { NULL, NULL, NULL, NULL },
- };
+ rule = pa_hashmap_get(u->stream_rules, name);
+ if (rule)
+ pa_dynarray_append(u->stream_rules_list, rule);
+ else
+ pa_log("Non-existent stream rule \"%s\" referenced, ignoring.", name);
+ }
- u->audio_group_names = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
- u->unused_audio_groups = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
- (pa_free_cb_t) audio_group_free);
- u->stream_names = pa_dynarray_new(pa_xfree);
- u->unused_streams = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
- (pa_free_cb_t) stream_free);
+ pa_dynarray_free(u->stream_rule_names);
+ u->stream_rule_names = NULL;
- if (pa_is_path_absolute(filename))
- f = pa_open_config_file(filename, NULL, NULL, &fn);
- else {
- char *sys_conf_file;
+ pa_modargs_free(ma);
- sys_conf_file = pa_sprintf_malloc(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "%s", filename);
- f = pa_open_config_file(sys_conf_file, filename, NULL, &fn);
- pa_xfree(sys_conf_file);
- }
+ return 0;
- if (f) {
- pa_config_parse(fn, f, table, NULL, u);
- pa_xfree(fn);
- fn = NULL;
- fclose(f);
- f = NULL;
- }
+fail:
+ pa__done(module);
- finalize_config(u);
+ if (ma)
+ pa_modargs_free(ma);
- return true;
+ return -1;
}
void pa__done(pa_module *m) {
- struct userdata* u;
+ struct userdata *u;
pa_assert(m);
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);
}
#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;
}
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);
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);
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);
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
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);
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
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);
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
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);
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;
-}
#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;
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
#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);
}
}
+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;
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.");
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]);
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);
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
USA.
***/
-#include <modules/volume-api/binding.h>
#include <modules/volume-api/volume-api.h>
#include <pulsecore/core.h>
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,
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);
#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>
#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);
};
struct userdata {
+ pa_volume_api *volume_api;
pa_main_volume_policy *main_volume_policy;
enum model output_volume_model;
enum model input_volume_model;
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) {
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);
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) {
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;
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;
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;
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) {
{ "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 },
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));
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);
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);
}
#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;
}
pa_assert(group);
pa_volume_api_add_audio_group(group->volume_api, group);
-
group->linked = true;
pa_log_debug("Created audio group #%u.", group->index);
pa_log_debug("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);
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)
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);
if (control == old_control)
return;
- if (old_control)
- pa_volume_control_remove_audio_group(old_control, group);
-
group->volume_control = control;
- if (control)
- pa_volume_control_add_audio_group(control, group);
-
if (!group->linked || group->unlinked)
return;
pa_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);
if (control == old_control)
return;
- if (old_control)
- pa_mute_control_remove_audio_group(old_control, group);
-
group->mute_control = control;
- if (control)
- pa_mute_control_add_audio_group(control, group);
-
if (!group->linked || group->unlinked)
return;
pa_hook_fire(&group->volume_api->hooks[PA_VOLUME_API_HOOK_AUDIO_GROUP_MUTE_CONTROL_CHANGED], group);
}
-void pa_audio_group_set_mute_control(pa_audio_group *group, pa_mute_control *control) {
- pa_assert(group);
-
- if (group->mute_control_binding) {
- pa_binding_free(group->mute_control_binding);
- group->mute_control_binding = NULL;
- }
-
- set_mute_control_internal(group, control);
-}
-
-void pa_audio_group_bind_volume_control(pa_audio_group *group, pa_binding_target_info *target_info) {
- pa_binding_owner_info owner_info = {
- .userdata = group,
- .set_value = (pa_binding_set_value_cb_t) set_volume_control_internal,
- };
-
- pa_assert(group);
- pa_assert(target_info);
-
- if (group->volume_control_binding)
- pa_binding_free(group->volume_control_binding);
-
- group->volume_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info);
-}
-
-void pa_audio_group_bind_mute_control(pa_audio_group *group, pa_binding_target_info *target_info) {
- pa_binding_owner_info owner_info = {
- .userdata = group,
- .set_value = (pa_binding_set_value_cb_t) set_mute_control_internal,
- };
-
- pa_assert(group);
- pa_assert(target_info);
-
- if (group->mute_control_binding)
- pa_binding_free(group->mute_control_binding);
-
- group->mute_control_binding = pa_binding_new(group->volume_api, &owner_info, target_info);
-}
-
void pa_audio_group_add_volume_stream(pa_audio_group *group, pas_stream *stream) {
pa_assert(group);
pa_assert(stream);
pa_assert_se(pa_hashmap_put(group->volume_streams, stream, stream) >= 0);
-
- if (stream->own_volume_control && group->own_volume_control)
- pa_volume_control_set_volume(stream->own_volume_control, &group->own_volume_control->volume, true, true);
-
- pa_log_debug("Stream %s added to audio group %s (volume).", stream->name, group->name);
}
void pa_audio_group_remove_volume_stream(pa_audio_group *group, pas_stream *stream) {
pa_assert(stream);
pa_assert_se(pa_hashmap_remove(group->volume_streams, stream));
-
- pa_log_debug("Stream %s removed from audio group %s (volume).", stream->name, group->name);
}
void pa_audio_group_add_mute_stream(pa_audio_group *group, pas_stream *stream) {
pa_assert(stream);
pa_assert_se(pa_hashmap_put(group->mute_streams, stream, stream) >= 0);
-
- if (stream->own_mute_control && group->own_mute_control)
- pa_mute_control_set_mute(stream->own_mute_control, group->own_mute_control->mute);
-
- pa_log_debug("Stream %s added to audio group %s (mute).", stream->name, group->name);
}
void pa_audio_group_remove_mute_stream(pa_audio_group *group, pas_stream *stream) {
pa_assert(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;
}
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;
pa_proplist *proplist;
pa_volume_control *volume_control;
pa_mute_control *mute_control;
- bool have_own_volume_control;
- bool have_own_mute_control;
- pa_volume_control *own_volume_control;
- pa_mute_control *own_mute_control;
- pa_binding *volume_control_binding;
- pa_binding *mute_control_binding;
pa_hashmap *volume_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */
pa_hashmap *mute_streams; /* pas_stream -> pas_stream (hashmap-as-a-set) */
bool 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
+++ /dev/null
-/***
- 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);
-}
+++ /dev/null
-#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
#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
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;
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;
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;
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;
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:
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) {
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) {
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) {
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);
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);
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) {
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) {
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);
}
}
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;
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:
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) {
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;
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);
}
}
- 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) {
#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);
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) {
}
pa_volume_api_add_device(device->volume_api, device);
-
device->linked = true;
device_types_str = pa_join((const char * const *) pa_dynarray_get_raw_array(device->device_types),
pa_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)
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);
--- /dev/null
+/***
+ 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);
+}
--- /dev/null
+#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
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;
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;
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],
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);
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);
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);
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);
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);
#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)))
}
void pa_mute_control_unlink(pa_mute_control *control) {
- pa_audio_group *group;
pa_device *device;
- pas_stream *stream;
pa_assert(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);
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);
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) {
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));
-}
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;
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. */
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);
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
#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);
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)))
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)
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)");
}
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);
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;
};
};
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;
}
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;
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;
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);
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);
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:
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;
}
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;
}
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;
}
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);
#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);
}
}
-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) {
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);
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
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);
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]);
}
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);
}
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);
}
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);
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);
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;
}
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);
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;
}
}
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);
}
#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,
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,
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 */
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);
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
#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);
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);
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);
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);
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;
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) {
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));
-}
***/
#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;
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);
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
#include <pulsecore/i18n.h>
#include <pulsecore/macro.h>
#include <pulsecore/pstream-util.h>
+#include <pulsecore/strbuf.h>
#include <math.h>
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));
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;
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;
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,
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,
[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
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