#define PA_PROP_FILTER_APPLY_GROUP PA_PROP_FILTER_APPLY".%s.group"
#define PA_PROP_FILTER_APPLY_PARAMETERS PA_PROP_FILTER_APPLY".%s.parameters"
#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving"
+#define PA_PROP_FILTER_APPLY_SET_BY_MFA "filter.apply.set_by_mfa"
PA_MODULE_AUTHOR("Colin Guthrie");
PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed");
return group;
}
+/* This function is used to set or unset the filter related stream properties. This is necessary
+ * if a stream does not have filter.apply set and is manually moved to a filter sink or source.
+ * In this case, the properies must be temporarily set and removed when the stream is moved away
+ * from the filter. */
+static void set_filter_properties(pa_proplist *pl, struct filter *filter, bool set_properties) {
+ char *prop_parameters;
+
+ if (set_properties) {
+ pa_assert(filter);
+
+ pa_proplist_sets(pl, PA_PROP_FILTER_APPLY, filter->name);
+
+ if (filter->parameters) {
+ prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, filter->name);
+ pa_proplist_sets(pl, prop_parameters, filter->parameters);
+ pa_xfree(prop_parameters);
+ }
+
+ pa_proplist_sets(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA, "1");
+
+ } else {
+ const char *old_filter_name = NULL;
+
+ if (filter)
+ old_filter_name = filter->name;
+ else
+ old_filter_name = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY);
+
+ /* If the filter name cannot be determined, properties cannot be removed. */
+ if (!old_filter_name)
+ return;
+
+ prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, old_filter_name);
+ pa_proplist_unset(pl, prop_parameters);
+ pa_xfree(prop_parameters);
+
+ pa_proplist_unset(pl, PA_PROP_FILTER_APPLY);
+ pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA);
+ }
+}
+
+static struct filter* get_filter_for_object(struct userdata *u, pa_object *o, bool is_sink_input) {
+ pa_sink *sink = NULL;
+ pa_source *source = NULL;
+ struct filter *filter = NULL;
+ void *state;
+
+ if (is_sink_input)
+ sink = PA_SINK_INPUT(o)->sink;
+ else
+ source = PA_SOURCE_OUTPUT(o)->source;
+
+ PA_HASHMAP_FOREACH(filter, u->filters, state) {
+ if ((is_sink_input && sink == filter->sink) || (!is_sink_input && source == filter->source)) {
+ return filter;
+ }
+ }
+
+ return NULL;
+}
+
static bool should_group_filter(struct filter *filter) {
return pa_streq(filter->name, "echo-cancel");
}
return pa_source_output_move_to(PA_SOURCE_OUTPUT(obj), PA_SOURCE(parent), restore);
}
-static void move_object_for_filter(pa_object *o, struct filter* filter, bool restore, bool is_sink_input) {
+static void move_object_for_filter(pa_object *o, struct filter *filter, bool restore, bool is_sink_input) {
pa_object *parent;
pa_proplist *pl;
const char *name;
pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_MOVING);
}
-static void move_objects_for_filter(struct userdata *u, pa_object *o, struct filter* filter, bool restore,
+static void move_objects_for_filter(struct userdata *u, pa_object *o, struct filter *filter, bool restore,
bool is_sink_input) {
if (!should_group_filter(filter))
return true;
}
-static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_input) {
+static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_input, bool is_property_change) {
const char *want;
const char *group;
const char *parameters;
pa_sink *sink = NULL;
pa_source *source = NULL;
pa_module *module = NULL;
+ pa_proplist *pl;
if (is_sink_input) {
- sink = PA_SINK_INPUT(o)->sink;
-
- if (sink)
+ if ((sink = PA_SINK_INPUT(o)->sink))
module = sink->module;
+ pl = PA_SINK_INPUT(o)->proplist;
} else {
- source = PA_SOURCE_OUTPUT(o)->source;
-
- if (source)
+ if ((source = PA_SOURCE_OUTPUT(o)->source))
module = source->module;
+ pl = PA_SOURCE_OUTPUT(o)->proplist;
}
/* If there is no sink/source yet, we can't do much */
if ((is_sink_input && !sink) || (!is_sink_input && !source))
return PA_HOOK_OK;
- /* If the stream doesn't what any filter, then let it be. */
+ /* If the stream doesn't want any filter, then let it be. */
if ((want = get_filter_name(o, is_sink_input))) {
char *module_name;
struct filter *fltr, *filter;
return PA_HOOK_OK;
}
+ /* If the stream originally did not have the filter.apply property set and is
+ * manually moved away from the filter, remove the filter properties from the
+ * stream */
+ if (pa_proplist_gets(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA)) {
+
+ set_filter_properties(pl, NULL, false);
+
+ /* If the new sink/source is also a filter, the stream has been moved from
+ * one filter to another, so add the properties for the new filter. */
+ if ((filter = get_filter_for_object(u, o, is_sink_input)))
+ set_filter_properties(pl, filter, true);
+
+ done_something = true;
+ goto done;
+ }
+
+ /* The stream needs be moved to a filter. */
+
/* Some applications may want group of filters. (optional) */
group = get_filter_group(o, want, is_sink_input);
done_something = true;
}
} else {
- void *state;
struct filter *filter = NULL;
- /* We do not want to filter... but are we already filtered?
- * This can happen if an input's proplist changes */
- PA_HASHMAP_FOREACH(filter, u->filters, state) {
- if ((is_sink_input && sink == filter->sink) || (!is_sink_input && source == filter->source)) {
+ /* The filter.apply property is not set. If the stream is nevertheless using a
+ * filter sink/source, it either has been moved to the filter manually or the
+ * user just removed the filter.apply property. */
+
+ if ((filter = get_filter_for_object(u, o, is_sink_input))) {
+ if (is_property_change) {
+ /* 'filter.apply' has been manually unset. Do restore. */
move_objects_for_filter(u, o, filter, true, is_sink_input);
+ set_filter_properties(pl, filter, false);
done_something = true;
- break;
+ } else {
+ /* Stream has been manually moved to a filter sink/source
+ * without 'filter.apply' set. Leave sink as it is. */
+ set_filter_properties(pl, filter, true);
}
}
}
+done:
if (done_something)
trigger_housekeeping(u);
pa_core_assert_ref(core);
pa_sink_input_assert_ref(i);
- return process(u, PA_OBJECT(i), true);
+ return process(u, PA_OBJECT(i), true, false);
}
static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
return PA_HOOK_OK;
- return process(u, PA_OBJECT(i), true);
+ return process(u, PA_OBJECT(i), true, false);
}
static pa_hook_result_t sink_input_proplist_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
pa_core_assert_ref(core);
pa_sink_input_assert_ref(i);
- return process(u, PA_OBJECT(i), true);
+ return process(u, PA_OBJECT(i), true, true);
}
static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
pa_core_assert_ref(core);
pa_source_output_assert_ref(o);
- return process(u, PA_OBJECT(o), false);
+ return process(u, PA_OBJECT(o), false, false);
}
static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
if (pa_proplist_gets(o->proplist, PA_PROP_FILTER_APPLY_MOVING))
return PA_HOOK_OK;
- return process(u, PA_OBJECT(o), false);
+ return process(u, PA_OBJECT(o), false, false);
}
static pa_hook_result_t source_output_proplist_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
pa_core_assert_ref(core);
pa_source_output_assert_ref(o);
- return process(u, PA_OBJECT(o), false);
+ return process(u, PA_OBJECT(o), false, true);
}
static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u) {