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