@@ -1013,6 +1013,18 @@ static int ucm_create_profile(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps,
return 0;
}
+static pa_alsa_ucm_device *find_ucm_dev(pa_alsa_ucm_verb *verb, const char *dev_name) {
+ pa_alsa_ucm_device *dev;
+
+ PA_LLIST_FOREACH(dev, verb->devices) {
+ const char *name = pa_proplist_gets(dev->proplist, PA_PROP_UCM_NAME);
+ if (pa_streq(name, dev_name))
+ return dev;
+ }
+
+ return NULL;
+}
+
pa_alsa_profile_set* pa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
pa_alsa_ucm_verb *verb;
pa_alsa_profile_set *ps;
@@ -1081,3 +1093,86 @@ void pa_ucm_free(pa_alsa_ucm_config *ucm) {
ucm->ucm_mgr = NULL;
}
}
+
+static pa_bool_t stream_routed_to_mod_intent (pa_alsa_ucm_verb *verb,
+ pa_alsa_ucm_modifier *mod, const char *mapping_name) {
+ int i;
+ const char *dev_name;
+ pa_alsa_ucm_device *dev;
+ pa_alsa_mapping *mapping;
+
+ /* check if mapping_name is same as one of the modifier's supported device */
+ for (i=0; i<mod->n_suppdev; i++) {
+ dev_name = mod->supported_devices[i];
+ /* first find the supported device */
+ dev = find_ucm_dev(verb, dev_name);
+ if (dev) {
+ /* then match the mapping name */
+ mapping = mod->action_direct == PA_ALSA_UCM_DIRECT_SINK ? dev->playback_mapping : dev->capture_mapping;
+ if (mapping && pa_streq(mapping->name, mapping_name))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* enable the modifier when both of the conditions are met
+ * 1. the first stream with matched role starts
+ * 2. the stream is routed to the device of the modifier specifies.
+ */
+void pa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm,
+ const char *role, const char *mapping_name, int is_sink) {
+ pa_alsa_ucm_modifier *mod;
+
+ if (!ucm->active_verb)
+ return;
+
+ PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
+ if (((mod->action_direct == PA_ALSA_UCM_DIRECT_SINK && is_sink) ||
+ (mod->action_direct == PA_ALSA_UCM_DIRECT_SOURCE && !is_sink)) &&
+ (!strcasecmp(mod->media_role, role))) {
+ if (stream_routed_to_mod_intent(ucm->active_verb, mod, mapping_name)) {
+ if (mod->enabled_counter == 0) {
+ const char *mod_name = pa_proplist_gets(mod->proplist, PA_PROP_UCM_NAME);
+ pa_log_info("Enable ucm modifiers %s", mod_name);
+ if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0)
+ pa_log("failed to enable ucm modifier %s", mod_name);
+ }
+ mod->enabled_counter++;
+ /* TODO: set port of the sink/source to modifier's intent device? */
+ }
+ break;
+ }
+ }
+}
+
+/* disable the modifier when both of the conditions are met
+ * 1. the last stream with matched role ends
+ * 2. the stream is routed to the device of the modifier specifies.
+ */
+void pa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm,
+ const char *role, const char *mapping_name, int is_sink) {
+ pa_alsa_ucm_modifier *mod;
+
+ if (!ucm->active_verb)
+ return;
+
+ PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
+ if (((mod->action_direct == PA_ALSA_UCM_DIRECT_SINK && is_sink) ||
+ (mod->action_direct == PA_ALSA_UCM_DIRECT_SOURCE && !is_sink)) &&
+ (!strcasecmp(mod->media_role, role))) {
+ if (stream_routed_to_mod_intent(ucm->active_verb, mod, mapping_name)) {
+ mod->enabled_counter--;
+ if (mod->enabled_counter == 0) {
+ const char *mod_name = pa_proplist_gets(mod->proplist, PA_PROP_UCM_NAME);
+ pa_log_info("Disable ucm modifiers %s", mod_name);
+ if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0)
+ pa_log("failed to disable ucm modifier %s", mod_name);
+ }
+ /* TODO: set port of the sink/source to modifier's intent device? */
+ }
+ break;
+ }
+ }
+}
@@ -49,6 +49,9 @@ int pa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port,
void pa_ucm_free(pa_alsa_ucm_config *ucm);
+void pa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, const char *mapping_name, int is_sink);
+void pa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, const char *mapping_name, int is_sink);
+
/* UCM modifier action direction */
enum {
PA_ALSA_UCM_DIRECT_NONE = 0,
@@ -82,6 +85,9 @@ struct pa_alsa_ucm_modifier {
const char **supported_devices;
int action_direct;
char *media_role;
+
+ /* runtime variable */
+ int enabled_counter;
};
struct pa_alsa_ucm_verb {
@@ -124,6 +124,18 @@ struct userdata {
/* ucm stuffs */
pa_bool_t use_ucm;
pa_alsa_ucm_config ucm;
+
+ /* hooks for modifier action */
+ pa_hook_slot
+ *sink_input_put_hook_slot,
+ *source_output_put_hook_slot,
+ *sink_input_unlink_hook_slot,
+ *source_output_unlink_hook_slot,
+
+ *sink_input_move_start_hook_slot,
+ *source_output_move_start_hook_slot,
+ *sink_input_move_finish_hook_slot,
+ *source_output_move_finish_hook_slot;
};
struct profile_data {
@@ -286,7 +298,7 @@ static void init_profile(struct userdata *u) {
uint32_t idx;
pa_alsa_mapping *am;
struct profile_data *d;
- struct pa_alsa_ucm_config *ucm = &u->ucm;
+ pa_alsa_ucm_config *ucm = &u->ucm;
pa_assert(u);
@@ -476,7 +488,7 @@ static int card_query_ucm_profiles(struct userdata *u, int card_index)
/* get the properties of each UCM verb */
for (i = 0; i < num_verbs; i += 2) {
- struct pa_alsa_ucm_verb *verb;
+ pa_alsa_ucm_verb *verb;
/* Get devices and modifiers for each verb */
err = pa_ucm_get_verb(u->ucm.ucm_mgr, verb_list[i], verb_list[i+1], &verb);
@@ -512,6 +524,102 @@ name_fail:
return err;
}
+static pa_hook_result_t sink_input_put_hook_callback(
+ pa_core *c, pa_sink_input *sink_input, struct userdata *u) {
+ const char *role;
+ const char *mapping_name;
+ pa_sink *sink = sink_input->sink;
+
+ pa_assert(sink);
+
+ role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE);
+ mapping_name = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_PROFILE_NAME);
+
+ /* new sink input linked to sink of this card */
+ if (role && sink->card == u->card)
+ pa_ucm_roled_stream_begin(&u->ucm, role, mapping_name, 1);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_put_hook_callback(
+ pa_core *c, pa_source_output *source_output, struct userdata *u) {
+ const char *role;
+ const char *mapping_name;
+ pa_source *source = source_output->source;
+
+ pa_assert(source);
+
+ role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE);
+ mapping_name = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_PROFILE_NAME);
+
+ /* new source output linked to source of this card */
+ if (role && source->card == u->card)
+ pa_ucm_roled_stream_begin(&u->ucm, role, mapping_name, 0);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_unlink_hook_callback(
+ pa_core *c, pa_sink_input *sink_input, struct userdata *u) {
+ const char *role;
+ const char *mapping_name;
+ pa_sink *sink = sink_input->sink;
+
+ pa_assert(sink);
+
+ role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE);
+ mapping_name = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_PROFILE_NAME);
+
+ /* new sink input unlinked from sink of this card */
+ if (role && sink->card == u->card)
+ pa_ucm_roled_stream_end(&u->ucm, role, mapping_name, 1);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_unlink_hook_callback(
+ pa_core *c, pa_source_output *source_output, struct userdata *u) {
+ const char *role;
+ const char *mapping_name;
+ pa_source *source = source_output->source;
+
+ pa_assert(source);
+
+ role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE);
+ mapping_name = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_PROFILE_NAME);
+
+ /* new source output unlinked from source of this card */
+ if (role && source->card == u->card)
+ pa_ucm_roled_stream_end(&u->ucm, role, mapping_name, 0);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_move_start_hook_callback(
+ pa_core *c, pa_sink_input *sink_input, struct userdata *u) {
+ /* same as sink input unlink */
+ return sink_input_unlink_hook_callback(c, sink_input, u);
+}
+
+static pa_hook_result_t source_output_move_start_hook_callback(
+ pa_core *c, pa_source_output *source_output, struct userdata *u) {
+ /* same as source output unlink */
+ return source_output_unlink_hook_callback(c, source_output, u);
+}
+
+static pa_hook_result_t sink_input_move_finish_hook_callback(
+ pa_core *c, pa_sink_input *sink_input, struct userdata *u) {
+ /* same as sink input put */
+ return sink_input_put_hook_callback(c, sink_input, u);
+}
+
+static pa_hook_result_t source_output_move_finish_hook_callback(
+ pa_core *c, pa_source_output *source_output, struct userdata *u) {
+ /* same as source output put */
+ return source_output_put_hook_callback(c, source_output, u);
+}
+
int pa__init(pa_module *m) {
pa_card_new_data data;
pa_modargs *ma;
@@ -563,6 +671,38 @@ int pa__init(pa_module *m) {
pa_modargs_get_value_boolean(ma, "use_ucm", &u->use_ucm);
if (u->use_ucm && !card_query_ucm_profiles(u, u->alsa_card_index)) {
pa_log_info("Found UCM profiles");
+
+ /* hook start of sink input/source output to enable modifiers */
+ /* A little bit later than module-role-cork */
+ u->sink_input_put_hook_slot = pa_hook_connect(
+ &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE+10,
+ (pa_hook_cb_t) sink_input_put_hook_callback, u);
+ u->source_output_put_hook_slot = pa_hook_connect(
+ &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE+10,
+ (pa_hook_cb_t) source_output_put_hook_callback, u);
+
+ /* hook end of sink input/source output to disable modifiers */
+ /* A little bit later than module-role-cork */
+ u->sink_input_unlink_hook_slot = pa_hook_connect(
+ &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK],
+ PA_HOOK_LATE+10, (pa_hook_cb_t) sink_input_unlink_hook_callback, u);
+ u->source_output_unlink_hook_slot = pa_hook_connect(
+ &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK],
+ PA_HOOK_LATE+10, (pa_hook_cb_t) source_output_unlink_hook_callback, u);
+
+ /* hook move start of sink input/source output to disable modifiers */
+ /* A little bit later than module-role-cork */
+ u->sink_input_move_start_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START],
+ PA_HOOK_LATE+10, (pa_hook_cb_t) sink_input_move_start_hook_callback, u);
+ u->source_output_move_start_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START],
+ PA_HOOK_LATE+10, (pa_hook_cb_t) source_output_move_start_hook_callback, u);
+
+ /* hook move finish of sink input/source output to enable modifiers */
+ /* A little bit later than module-role-cork */
+ u->sink_input_move_finish_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH],
+ PA_HOOK_LATE+10, (pa_hook_cb_t) sink_input_move_finish_hook_callback, u);
+ u->source_output_move_finish_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH],
+ PA_HOOK_LATE+10, (pa_hook_cb_t) source_output_move_finish_hook_callback, u);
}
else {
u->use_ucm = FALSE;
@@ -695,6 +835,30 @@ void pa__done(pa_module*m) {
if (!(u = m->userdata))
goto finish;
+ if (u->sink_input_put_hook_slot)
+ pa_hook_slot_free(u->sink_input_put_hook_slot);
+
+ if (u->sink_input_unlink_hook_slot)
+ pa_hook_slot_free(u->sink_input_unlink_hook_slot);
+
+ if (u->source_output_put_hook_slot)
+ pa_hook_slot_free(u->source_output_put_hook_slot);
+
+ if (u->source_output_unlink_hook_slot)
+ pa_hook_slot_free(u->source_output_unlink_hook_slot);
+
+ if (u->sink_input_move_start_hook_slot)
+ pa_hook_slot_free(u->sink_input_move_start_hook_slot);
+
+ if (u->source_output_move_start_hook_slot)
+ pa_hook_slot_free(u->source_output_move_start_hook_slot);
+
+ if (u->sink_input_move_finish_hook_slot)
+ pa_hook_slot_free(u->sink_input_move_finish_hook_slot);
+
+ if (u->source_output_move_finish_hook_slot)
+ pa_hook_slot_free(u->source_output_move_finish_hook_slot);
+
if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl);
if (u->mixer_handle)