1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2011 Colin Guthrie
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <pulse/timeval.h>
25 #include <pulse/rtclock.h>
26 #include <pulse/xmalloc.h>
27
28 #include <pulsecore/core.h>
29 #include <pulsecore/core-util.h>
30 #include <pulsecore/i18n.h>
31 #include <pulsecore/macro.h>
32 #include <pulsecore/hashmap.h>
33 #include <pulsecore/hook-list.h>
34 #include <pulsecore/sink-input.h>
35 #include <pulsecore/modargs.h>
36 #include <pulsecore/proplist-util.h>
37
38 #define PA_PROP_FILTER_APPLY_PARAMETERS PA_PROP_FILTER_APPLY".%s.parameters"
39 #define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving"
40 #define PA_PROP_FILTER_APPLY_SET_BY_MFA "filter.apply.set_by_mfa"
41 #define PA_PROP_MDM_AUTO_FILTERED "module-device-manager.auto_filtered"
42
43 PA_MODULE_AUTHOR("Colin Guthrie");
44 PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed");
45 PA_MODULE_VERSION(PACKAGE_VERSION);
46 PA_MODULE_LOAD_ONCE(true);
47 PA_MODULE_USAGE(_("autoclean=<automatically unload unused filters?>"));
48
49 static const char* const valid_modargs[] = {
50 "autoclean",
51 NULL
52 };
53
54 #define DEFAULT_AUTOCLEAN true
55 #define HOUSEKEEPING_INTERVAL (10 * PA_USEC_PER_SEC)
56
57 struct filter {
58 char *name;
59 char *parameters;
60 uint32_t module_index;
61 pa_sink *sink;
62 pa_sink *sink_master;
63 pa_source *source;
64 pa_source *source_master;
65 };
66
67 struct userdata {
68 pa_core *core;
69 pa_hashmap *filters;
70 /* Keep track of streams we're managing PA_PROP_MDM_AUTO_FILTERED on, we're
71 * only maintaining membership, so key and value are just the
72 * pa_sink_input/pa_source_output. */
73 pa_hashmap *mdm_ignored_inputs, *mdm_ignored_outputs;
74 bool autoclean;
75 pa_time_event *housekeeping_time_event;
76 };
77
filter_hash(const void *p)78 static unsigned filter_hash(const void *p) {
79 const struct filter *f = p;
80
81 if (f->sink_master && !f->source_master)
82 return (unsigned) (f->sink_master->index + pa_idxset_string_hash_func(f->name));
83 else if (!f->sink_master && f->source_master)
84 return (unsigned) ((f->source_master->index << 16) + pa_idxset_string_hash_func(f->name));
85 else
86 return (unsigned) (f->sink_master->index + (f->source_master->index << 16) + pa_idxset_string_hash_func(f->name));
87 }
88
filter_compare(const void *a, const void *b)89 static int filter_compare(const void *a, const void *b) {
90 const struct filter *fa = a, *fb = b;
91 int r;
92
93 if (fa->sink_master != fb->sink_master || fa->source_master != fb->source_master)
94 return 1;
95 if ((r = strcmp(fa->name, fb->name)))
96 return r;
97
98 return 0;
99 }
100
filter_new(const char *name, const char *parameters, pa_sink *sink, pa_source *source)101 static struct filter *filter_new(const char *name, const char *parameters, pa_sink *sink, pa_source *source) {
102 struct filter *f;
103
104 pa_assert(sink || source);
105
106 f = pa_xnew(struct filter, 1);
107 f->name = pa_xstrdup(name);
108 f->parameters = pa_xstrdup(parameters);
109 f->sink_master = sink;
110 f->source_master = source;
111 f->module_index = PA_INVALID_INDEX;
112 f->sink = NULL;
113 f->source = NULL;
114
115 return f;
116 }
117
filter_free(struct filter *f)118 static void filter_free(struct filter *f) {
119 if (f) {
120 pa_xfree(f->name);
121 pa_xfree(f->parameters);
122 pa_xfree(f);
123 }
124 }
125
get_filter_name(pa_object *o, bool is_sink_input)126 static const char* get_filter_name(pa_object *o, bool is_sink_input) {
127 const char *apply;
128 pa_proplist *pl;
129
130 if (is_sink_input)
131 pl = PA_SINK_INPUT(o)->proplist;
132 else
133 pl = PA_SOURCE_OUTPUT(o)->proplist;
134
135 /* If the stream doesn't want any filter, then let it be. */
136 if ((apply = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY)) && !pa_streq(apply, "")) {
137 const char* suppress = pa_proplist_gets(pl, PA_PROP_FILTER_SUPPRESS);
138
139 if (!suppress || !pa_streq(suppress, apply))
140 return apply;
141 }
142
143 return NULL;
144 }
145
get_filter_parameters(pa_object *o, const char *want, bool is_sink_input)146 static const char* get_filter_parameters(pa_object *o, const char *want, bool is_sink_input) {
147 const char *parameters;
148 char *prop_parameters;
149 pa_proplist *pl, *device_pl;
150
151 if (is_sink_input) {
152 pl = PA_SINK_INPUT(o)->proplist;
153 device_pl = PA_SINK_INPUT(o)->sink->proplist;
154 } else {
155 pl = PA_SOURCE_OUTPUT(o)->proplist;
156 device_pl = PA_SOURCE_OUTPUT(o)->source->proplist;
157 }
158
159 prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, want);
160 parameters = pa_proplist_gets(pl, prop_parameters);
161 if (!parameters)
162 parameters = pa_proplist_gets(device_pl, prop_parameters);
163 pa_xfree(prop_parameters);
164
165 return parameters;
166 }
167
168 /* This function is used to set or unset the filter related stream properties. This is necessary
169 * if a stream does not have filter.apply set and is manually moved to a filter sink or source.
170 * In this case, the properties must be temporarily set and removed when the stream is moved away
171 * from the filter. */
set_filter_properties(pa_proplist *pl, struct filter *filter, bool set_properties)172 static void set_filter_properties(pa_proplist *pl, struct filter *filter, bool set_properties) {
173 char *prop_parameters;
174
175 if (set_properties) {
176 pa_assert(filter);
177
178 pa_proplist_sets(pl, PA_PROP_FILTER_APPLY, filter->name);
179
180 if (filter->parameters) {
181 prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, filter->name);
182 pa_proplist_sets(pl, prop_parameters, filter->parameters);
183 pa_xfree(prop_parameters);
184 }
185
186 pa_proplist_sets(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA, "1");
187
188 } else {
189 const char *old_filter_name = NULL;
190
191 if (filter)
192 old_filter_name = filter->name;
193 else
194 old_filter_name = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY);
195
196 /* If the filter name cannot be determined, properties cannot be removed. */
197 if (!old_filter_name)
198 return;
199
200 prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, old_filter_name);
201 pa_proplist_unset(pl, prop_parameters);
202 pa_xfree(prop_parameters);
203
204 pa_proplist_unset(pl, PA_PROP_FILTER_APPLY);
205 pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA);
206 }
207 }
208
get_filter_for_object(struct userdata *u, pa_object *o, bool is_sink_input)209 static struct filter* get_filter_for_object(struct userdata *u, pa_object *o, bool is_sink_input) {
210 pa_sink *sink = NULL;
211 pa_source *source = NULL;
212 struct filter *filter = NULL;
213 void *state;
214
215 if (is_sink_input)
216 sink = PA_SINK_INPUT(o)->sink;
217 else
218 source = PA_SOURCE_OUTPUT(o)->source;
219
220 PA_HASHMAP_FOREACH(filter, u->filters, state) {
221 if ((is_sink_input && sink == filter->sink) || (!is_sink_input && source == filter->source)) {
222 return filter;
223 }
224 }
225
226 return NULL;
227 }
228
should_group_filter(struct filter *filter)229 static bool should_group_filter(struct filter *filter) {
230 return pa_streq(filter->name, "echo-cancel");
231 }
232
get_group(pa_object *o, bool is_sink_input)233 static char* get_group(pa_object *o, bool is_sink_input) {
234 pa_proplist *pl;
235
236 if (is_sink_input)
237 pl = PA_SINK_INPUT(o)->proplist;
238 else
239 pl = PA_SOURCE_OUTPUT(o)->proplist;
240
241 /* There's a bit of cleverness here -- the second argument ensures that we
242 * only group streams that require the same filter */
243 return pa_proplist_get_stream_group(pl, pa_proplist_gets(pl, PA_PROP_FILTER_APPLY), NULL);
244 }
245
246 /* For filters that apply on a source-output/sink-input pair, this finds the
247 * master sink if we know the master source, or vice versa. It does this by
248 * looking up streams that belong to the same stream group as the original
249 * object. The idea is that streams from the sam group are always routed
250 * together. */
find_paired_master(struct userdata *u, struct filter *filter, pa_object *o, bool is_sink_input)251 static bool find_paired_master(struct userdata *u, struct filter *filter, pa_object *o, bool is_sink_input) {
252 char *group;
253
254 if ((group = get_group(o, is_sink_input))) {
255 uint32_t idx;
256 char *g;
257 char *module_name = pa_sprintf_malloc("module-%s", filter->name);
258
259 if (is_sink_input) {
260 pa_source_output *so;
261
262 PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) {
263 g = get_group(PA_OBJECT(so), false);
264
265 if (pa_streq(g, group)) {
266 if (pa_streq(module_name, so->source->module->name)) {
267 /* Make sure we are not routing to the monitor source
268 * of the same filter */
269 if (so->source->monitor_of) {
270 pa_xfree(g);
271 continue;
272 }
273 /* Make sure we're not routing to another instance of
274 * the same filter. */
275 filter->source_master = so->source->output_from_master->source;
276 } else {
277 filter->source_master = so->source;
278 }
279
280 pa_xfree(g);
281 break;
282 }
283
284 pa_xfree (g);
285 }
286 } else {
287 pa_sink_input *si;
288
289 PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) {
290 g = get_group(PA_OBJECT(si), true);
291
292 if (pa_streq(g, group)) {
293 if (pa_streq(module_name, si->sink->module->name)) {
294 /* Make sure we're not routing to another instance of
295 * the same filter. */
296 filter->sink_master = si->sink->input_to_master->sink;
297 } else {
298 filter->sink_master = si->sink;
299 }
300
301 pa_xfree(g);
302 break;
303 }
304
305 pa_xfree(g);
306 }
307 }
308
309 pa_xfree(group);
310 pa_xfree(module_name);
311
312 if (!filter->sink_master || !filter->source_master)
313 return false;
314 }
315
316 return true;
317 }
318
nothing_attached(struct filter *f)319 static bool nothing_attached(struct filter *f) {
320 bool no_si = true, no_so = true;
321
322 if (f->sink)
323 no_si = pa_idxset_isempty(f->sink->inputs);
324 if (f->source)
325 no_so = pa_idxset_isempty(f->source->outputs);
326
327 return no_si && no_so;
328 }
329
housekeeping_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata)330 static void housekeeping_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
331 struct userdata *u = userdata;
332 struct filter *filter;
333 void *state;
334
335 pa_assert(a);
336 pa_assert(e);
337 pa_assert(u);
338
339 pa_assert(e == u->housekeeping_time_event);
340 u->core->mainloop->time_free(u->housekeeping_time_event);
341 u->housekeeping_time_event = NULL;
342
343 PA_HASHMAP_FOREACH(filter, u->filters, state) {
344 if (nothing_attached(filter)) {
345 uint32_t idx;
346
347 pa_log_debug("Detected filter %s as no longer used. Unloading.", filter->name);
348 idx = filter->module_index;
349 pa_hashmap_remove(u->filters, filter);
350 filter_free(filter);
351 pa_module_unload_request_by_index(u->core, idx, true);
352 }
353 }
354
355 pa_log_info("Housekeeping Done.");
356 }
357
trigger_housekeeping(struct userdata *u)358 static void trigger_housekeeping(struct userdata *u) {
359 pa_assert(u);
360
361 if (!u->autoclean)
362 return;
363
364 if (u->housekeeping_time_event)
365 return;
366
367 u->housekeeping_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + HOUSEKEEPING_INTERVAL, housekeeping_time_callback, u);
368 }
369
do_move(struct userdata *u, pa_object *obj, pa_object *parent, bool is_input)370 static int do_move(struct userdata *u, pa_object *obj, pa_object *parent, bool is_input) {
371 /* Keep track of objects that we've marked for module-device-manager to ignore */
372 pa_hashmap_put(is_input ? u->mdm_ignored_inputs : u->mdm_ignored_outputs, obj, obj);
373
374 if (is_input) {
375 pa_sink_input_set_property(PA_SINK_INPUT(obj), PA_PROP_MDM_AUTO_FILTERED, "1");
376 return pa_sink_input_move_to(PA_SINK_INPUT(obj), PA_SINK(parent), false);
377 } else {
378 pa_source_output_set_property(PA_SOURCE_OUTPUT(obj), PA_PROP_MDM_AUTO_FILTERED, "1");
379 return pa_source_output_move_to(PA_SOURCE_OUTPUT(obj), PA_SOURCE(parent), false);
380 }
381 }
382
move_object_for_filter(struct userdata *u, pa_object *o, struct filter *filter, bool restore, bool is_sink_input)383 static void move_object_for_filter(struct userdata *u, pa_object *o, struct filter *filter, bool restore, bool is_sink_input) {
384 pa_object *parent;
385 pa_proplist *pl;
386 const char *name;
387
388 pa_assert(o);
389 pa_assert(filter);
390
391 if (is_sink_input) {
392 pl = PA_SINK_INPUT(o)->proplist;
393 parent = PA_OBJECT(restore ? filter->sink_master : filter->sink);
394 if (!parent)
395 return;
396 name = PA_SINK(parent)->name;
397 } else {
398 pl = PA_SOURCE_OUTPUT(o)->proplist;
399 parent = PA_OBJECT(restore ? filter->source_master : filter->source);
400 if (!parent)
401 return;
402 name = PA_SOURCE(parent)->name;
403 }
404
405 pa_proplist_sets(pl, PA_PROP_FILTER_APPLY_MOVING, "1");
406
407 if (do_move(u, o, parent, is_sink_input) < 0)
408 pa_log_info("Failed to move %s for \"%s\" to <%s>.", is_sink_input ? "sink-input" : "source-output",
409 pa_strnull(pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)), name);
410 else
411 pa_log_info("Successfully moved %s for \"%s\" to <%s>.", is_sink_input ? "sink-input" : "source-output",
412 pa_strnull(pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)), name);
413
414 pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_MOVING);
415 }
416
move_objects_for_filter(struct userdata *u, pa_object *o, struct filter *filter, bool restore, bool is_sink_input)417 static void move_objects_for_filter(struct userdata *u, pa_object *o, struct filter *filter, bool restore,
418 bool is_sink_input) {
419
420 if (!should_group_filter(filter))
421 move_object_for_filter(u, o, filter, restore, is_sink_input);
422 else {
423 pa_source_output *so;
424 pa_sink_input *si;
425 char *g, *group;
426 uint32_t idx;
427
428 group = get_group(o, is_sink_input);
429
430 PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) {
431 g = get_group(PA_OBJECT(so), false);
432
433 if (pa_streq(g, group))
434 move_object_for_filter(u, PA_OBJECT(so), filter, restore, false);
435
436 pa_xfree(g);
437 }
438
439 PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) {
440 g = get_group(PA_OBJECT(si), true);
441
442 if (pa_streq(g, group))
443 move_object_for_filter(u, PA_OBJECT(si), filter, restore, true);
444
445 pa_xfree(g);
446 }
447
448 pa_xfree(group);
449 }
450 }
451
452 /* Note that we assume a filter will provide at most one sink and at most one
453 * source (and at least one of either). */
find_filters_for_module(struct userdata *u, pa_module *m, const char *name, const char *parameters)454 static void find_filters_for_module(struct userdata *u, pa_module *m, const char *name, const char *parameters) {
455 uint32_t idx;
456 pa_sink *sink;
457 pa_source *source;
458 struct filter *fltr = NULL;
459
460 PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
461 if (sink->module == m) {
462 pa_assert(pa_sink_is_filter(sink));
463
464 fltr = filter_new(name, parameters, sink->input_to_master->sink, NULL);
465 fltr->module_index = m->index;
466 fltr->sink = sink;
467
468 break;
469 }
470 }
471
472 PA_IDXSET_FOREACH(source, u->core->sources, idx) {
473 if (source->module == m && !source->monitor_of) {
474 pa_assert(pa_source_is_filter(source));
475
476 if (!fltr) {
477 fltr = filter_new(name, parameters, NULL, source->output_from_master->source);
478 fltr->module_index = m->index;
479 fltr->source = source;
480 } else {
481 fltr->source = source;
482 fltr->source_master = source->output_from_master->source;
483 }
484
485 break;
486 }
487 }
488
489 pa_hashmap_put(u->filters, fltr, fltr);
490 }
491
can_unload_module(struct userdata *u, uint32_t idx)492 static bool can_unload_module(struct userdata *u, uint32_t idx) {
493 void *state;
494 struct filter *filter;
495
496 /* Check if any other struct filters point to the same module */
497 PA_HASHMAP_FOREACH(filter, u->filters, state) {
498 if (filter->module_index == idx && !nothing_attached(filter))
499 return false;
500 }
501
502 return true;
503 }
504
process(struct userdata *u, pa_object *o, bool is_sink_input, bool is_property_change)505 static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_input, bool is_property_change) {
506 const char *want;
507 const char *parameters;
508 bool done_something = false;
509 pa_sink *sink = NULL;
510 pa_source *source = NULL;
511 pa_module *module = NULL;
512 char *module_name = NULL;
513 struct filter *fltr = NULL, *filter = NULL;
514 pa_proplist *pl;
515
516 if (is_sink_input) {
517 if ((sink = PA_SINK_INPUT(o)->sink))
518 module = sink->module;
519 pl = PA_SINK_INPUT(o)->proplist;
520 } else {
521 if ((source = PA_SOURCE_OUTPUT(o)->source))
522 module = source->module;
523 pl = PA_SOURCE_OUTPUT(o)->proplist;
524 }
525
526 /* If there is no sink/source yet, we can't do much */
527 if ((is_sink_input && !sink) || (!is_sink_input && !source))
528 goto done;
529
530 /* If the stream doesn't want any filter, then let it be. */
531 if ((want = get_filter_name(o, is_sink_input))) {
532 /* We need to ensure the SI is playing on a sink of this type
533 * attached to the sink it's "officially" playing on */
534
535 if (!module)
536 goto done;
537
538 module_name = pa_sprintf_malloc("module-%s", want);
539 if (pa_streq(module->name, module_name)) {
540 pa_log_debug("Stream appears to be playing on an appropriate sink already. Ignoring.");
541 goto done;
542 }
543
544 /* If the stream originally did not have the filter.apply property set and is
545 * manually moved away from the filter, remove the filter properties from the
546 * stream */
547 if (pa_proplist_gets(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA)) {
548
549 set_filter_properties(pl, NULL, false);
550
551 /* If the new sink/source is also a filter, the stream has been moved from
552 * one filter to another, so add the properties for the new filter. */
553 if ((filter = get_filter_for_object(u, o, is_sink_input)))
554 set_filter_properties(pl, filter, true);
555
556 done_something = true;
557 goto done;
558 }
559
560 /* The stream needs be moved to a filter. */
561
562 /* Some filter modules might require parameters by default.
563 * (e.g 'plugin', 'label', 'control' of module-ladspa-sink) */
564 parameters = get_filter_parameters(o, want, is_sink_input);
565
566 fltr = filter_new(want, parameters, sink, source);
567
568 if (should_group_filter(fltr) && !find_paired_master(u, fltr, o, is_sink_input)) {
569 pa_log_debug("Want group filtering but don't have enough streams.");
570 goto done;
571 }
572
573 if (!(filter = pa_hashmap_get(u->filters, fltr))) {
574 char *args;
575 pa_module *m;
576
577 args = pa_sprintf_malloc("autoloaded=1 %s%s %s%s %s",
578 fltr->sink_master ? "sink_master=" : "",
579 fltr->sink_master ? fltr->sink_master->name : "",
580 fltr->source_master ? "source_master=" : "",
581 fltr->source_master ? fltr->source_master->name : "",
582 fltr->parameters ? fltr->parameters : "");
583
584 pa_log_debug("Loading %s with arguments '%s'", module_name, args);
585
586 if (pa_module_load(&m, u->core, module_name, args) >= 0) {
587 find_filters_for_module(u, m, want, parameters);
588 filter = pa_hashmap_get(u->filters, fltr);
589 done_something = true;
590 }
591 pa_xfree(args);
592 }
593
594 if (!filter) {
595 pa_log("Unable to load %s", module_name);
596 goto done;
597 }
598
599 /* We can move the stream now as we know the destination. If this
600 * isn't true, we will do it later when the sink appears. */
601 if ((is_sink_input && filter->sink) || (!is_sink_input && filter->source)) {
602 move_objects_for_filter(u, o, filter, false, is_sink_input);
603 done_something = true;
604 }
605 } else {
606 /* The filter.apply property is not set. If the stream is nevertheless using a
607 * filter sink/source, it either has been moved to the filter manually or the
608 * user just removed the filter.apply property. */
609
610 if ((filter = get_filter_for_object(u, o, is_sink_input))) {
611 if (is_property_change) {
612 /* 'filter.apply' has been manually unset. Do restore. */
613 move_objects_for_filter(u, o, filter, true, is_sink_input);
614 set_filter_properties(pl, filter, false);
615 done_something = true;
616 } else {
617 /* Stream has been manually moved to a filter sink/source
618 * without 'filter.apply' set. Leave sink as it is. */
619 set_filter_properties(pl, filter, true);
620 }
621 }
622 }
623
624 done:
625 if (done_something)
626 trigger_housekeeping(u);
627
628 pa_xfree(module_name);
629 filter_free(fltr);
630
631 return PA_HOOK_OK;
632 }
633
sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u)634 static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
635 pa_core_assert_ref(core);
636 pa_sink_input_assert_ref(i);
637
638 return process(u, PA_OBJECT(i), true, false);
639 }
640
sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u)641 static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
642 pa_core_assert_ref(core);
643 pa_sink_input_assert_ref(i);
644
645 if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
646 return PA_HOOK_OK;
647
648 /* If we're managing m-d-m.auto_filtered on this, remove and re-add if we're continuing to manage it */
649 pa_hashmap_remove(u->mdm_ignored_inputs, i);
650
651 return process(u, PA_OBJECT(i), true, false);
652 }
653
sink_input_proplist_cb(pa_core *core, pa_sink_input *i, struct userdata *u)654 static pa_hook_result_t sink_input_proplist_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
655 pa_core_assert_ref(core);
656 pa_sink_input_assert_ref(i);
657
658 /* Eliminate nested and redundant hook event that is triggered by
659 pa_sink_input_set_property() in do_move(). */
660 if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
661 return PA_HOOK_OK;
662
663 return process(u, PA_OBJECT(i), true, true);
664 }
665
sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u)666 static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
667 pa_core_assert_ref(core);
668 pa_sink_input_assert_ref(i);
669
670 pa_assert(u);
671
672 if (pa_hashmap_size(u->filters) > 0)
673 trigger_housekeeping(u);
674
675 pa_hashmap_remove(u->mdm_ignored_inputs, i);
676
677 return PA_HOOK_OK;
678 }
679
sink_unlink_cb(pa_core *core, pa_sink *sink, struct userdata *u)680 static pa_hook_result_t sink_unlink_cb(pa_core *core, pa_sink *sink, struct userdata *u) {
681 void *state;
682 struct filter *filter = NULL;
683
684 pa_core_assert_ref(core);
685 pa_sink_assert_ref(sink);
686 pa_assert(u);
687
688 /* If either the parent or the sink we've loaded disappears,
689 * we should remove it from our hashmap */
690 PA_HASHMAP_FOREACH(filter, u->filters, state) {
691 if (filter->sink_master == sink || filter->sink == sink) {
692 uint32_t idx;
693
694 /* Attempt to rescue any streams to the parent sink as this is likely
695 * the best course of action */
696 if (filter->sink == sink) {
697 pa_sink_input *i;
698
699 PA_IDXSET_FOREACH(i, sink->inputs, idx)
700 move_objects_for_filter(u, PA_OBJECT(i), filter, true, true);
701 }
702
703 idx = filter->module_index;
704 pa_hashmap_remove(u->filters, filter);
705 filter_free(filter);
706
707 if (can_unload_module(u, idx))
708 pa_module_unload_request_by_index(u->core, idx, true);
709 }
710 }
711
712 return PA_HOOK_OK;
713 }
714
source_output_put_cb(pa_core *core, pa_source_output *o, struct userdata *u)715 static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
716 pa_core_assert_ref(core);
717 pa_source_output_assert_ref(o);
718
719 return process(u, PA_OBJECT(o), false, false);
720 }
721
source_output_move_finish_cb(pa_core *core, pa_source_output *o, struct userdata *u)722 static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
723 pa_core_assert_ref(core);
724 pa_source_output_assert_ref(o);
725
726 if (pa_proplist_gets(o->proplist, PA_PROP_FILTER_APPLY_MOVING))
727 return PA_HOOK_OK;
728
729 /* If we're managing m-d-m.auto_filtered on this, remove and re-add if we're continuing to manage it */
730 pa_hashmap_remove(u->mdm_ignored_outputs, o);
731
732 return process(u, PA_OBJECT(o), false, false);
733 }
734
source_output_proplist_cb(pa_core *core, pa_source_output *o, struct userdata *u)735 static pa_hook_result_t source_output_proplist_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
736 pa_core_assert_ref(core);
737 pa_source_output_assert_ref(o);
738
739 /* Eliminate nested and redundant hook event that is triggered by
740 pa_source_output_set_property() in do_move(). */
741 if (pa_proplist_gets(o->proplist, PA_PROP_FILTER_APPLY_MOVING))
742 return PA_HOOK_OK;
743
744 return process(u, PA_OBJECT(o), false, true);
745 }
746
source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u)747 static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
748 pa_core_assert_ref(core);
749 pa_source_output_assert_ref(o);
750
751 pa_assert(u);
752
753 if (pa_hashmap_size(u->filters) > 0)
754 trigger_housekeeping(u);
755
756 pa_hashmap_remove(u->mdm_ignored_outputs, o);
757
758 return PA_HOOK_OK;
759 }
760
source_unlink_cb(pa_core *core, pa_source *source, struct userdata *u)761 static pa_hook_result_t source_unlink_cb(pa_core *core, pa_source *source, struct userdata *u) {
762 void *state;
763 struct filter *filter = NULL;
764
765 pa_core_assert_ref(core);
766 pa_source_assert_ref(source);
767 pa_assert(u);
768
769 /* If either the parent or the source we've loaded disappears,
770 * we should remove it from our hashmap */
771 PA_HASHMAP_FOREACH(filter, u->filters, state) {
772 if (filter->source_master == source || filter->source == source) {
773 uint32_t idx;
774
775 /* Attempt to rescue any streams to the parent source as this is likely
776 * the best course of action */
777 if (filter->source == source) {
778 pa_source_output *o;
779
780 PA_IDXSET_FOREACH(o, source->outputs, idx)
781 move_objects_for_filter(u, PA_OBJECT(o), filter, true, false);
782 }
783
784 idx = filter->module_index;
785 pa_hashmap_remove(u->filters, filter);
786 filter_free(filter);
787
788 if (can_unload_module(u, idx))
789 pa_module_unload_request_by_index(u->core, idx, true);
790 }
791 }
792
793 return PA_HOOK_OK;
794 }
795
unset_mdm_ignore_input(pa_sink_input *i)796 static void unset_mdm_ignore_input(pa_sink_input *i)
797 {
798 pa_sink_input_set_property(i, PA_PROP_MDM_AUTO_FILTERED, NULL);
799 }
800
unset_mdm_ignore_output(pa_source_output *o)801 static void unset_mdm_ignore_output(pa_source_output *o)
802 {
803 pa_source_output_set_property(o, PA_PROP_MDM_AUTO_FILTERED, NULL);
804 }
805
pa__init(pa_module *m)806 int pa__init(pa_module *m) {
807 pa_modargs *ma = NULL;
808 struct userdata *u;
809
810 pa_assert(m);
811
812 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
813 pa_log("Failed to parse module arguments");
814 goto fail;
815 }
816
817 m->userdata = u = pa_xnew0(struct userdata, 1);
818
819 u->core = m->core;
820
821 u->autoclean = DEFAULT_AUTOCLEAN;
822 if (pa_modargs_get_value_boolean(ma, "autoclean", &u->autoclean) < 0) {
823 pa_log("Failed to parse autoclean value");
824 goto fail;
825 }
826
827 u->filters = pa_hashmap_new(filter_hash, filter_compare);
828 u->mdm_ignored_inputs = pa_hashmap_new_full(NULL, NULL, (pa_free_cb_t) unset_mdm_ignore_input, NULL);
829 u->mdm_ignored_outputs = pa_hashmap_new_full(NULL, NULL, (pa_free_cb_t) unset_mdm_ignore_output, NULL);
830
831 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u);
832 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u);
833 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_proplist_cb, u);
834 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u);
835 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_unlink_cb, u);
836 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_output_put_cb, u);
837 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) source_output_move_finish_cb, u);
838 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) source_output_proplist_cb, u);
839 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_output_unlink_cb, u);
840 pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE-1, (pa_hook_cb_t) source_unlink_cb, u);
841
842 pa_modargs_free(ma);
843
844 return 0;
845
846 fail:
847 pa__done(m);
848
849 if (ma)
850 pa_modargs_free(ma);
851
852 return -1;
853 }
854
pa__done(pa_module *m)855 void pa__done(pa_module *m) {
856 struct userdata* u;
857
858 pa_assert(m);
859
860 if (!(u = m->userdata))
861 return;
862
863 if (u->housekeeping_time_event)
864 u->core->mainloop->time_free(u->housekeeping_time_event);
865
866 if (u->filters) {
867 struct filter *f;
868
869 while ((f = pa_hashmap_steal_first(u->filters))) {
870 pa_module_unload_request_by_index(u->core, f->module_index, true);
871 filter_free(f);
872 }
873
874 pa_hashmap_free(u->filters);
875 }
876
877 if (u->mdm_ignored_inputs)
878 pa_hashmap_free(u->mdm_ignored_inputs);
879
880 if (u->mdm_ignored_outputs)
881 pa_hashmap_free(u->mdm_ignored_outputs);
882
883 pa_xfree(u);
884 }
885