• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***
2   This file is part of PulseAudio.
3 
4   Copyright 2009 Lennart Poettering
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/xmalloc.h>
25 #include <pulse/volume.h>
26 
27 #include <pulsecore/macro.h>
28 #include <pulsecore/hashmap.h>
29 #include <pulsecore/hook-list.h>
30 #include <pulsecore/core.h>
31 #include <pulsecore/core-util.h>
32 #include <pulsecore/sink-input.h>
33 #include <pulsecore/modargs.h>
34 
35 #include "stream-interaction.h"
36 
37 struct group {
38     char *name;
39     pa_idxset *trigger_roles;
40     pa_idxset *interaction_roles;
41     pa_hashmap *interaction_state;
42     pa_volume_t volume;
43 };
44 
45 struct userdata {
46     pa_core *core;
47     uint32_t n_groups;
48     struct group **groups;
49     bool global:1;
50     bool duck:1;
51     bool source_trigger:1;
52     pa_hook_slot
53         *sink_input_put_slot,
54         *sink_input_unlink_slot,
55         *sink_input_move_start_slot,
56         *sink_input_move_finish_slot,
57         *sink_input_state_changed_slot,
58         *sink_input_mute_changed_slot,
59         *sink_input_proplist_changed_slot,
60         *source_output_put_slot,
61         *source_output_unlink_slot,
62         *source_output_move_start_slot,
63         *source_output_move_finish_slot,
64         *source_output_state_changed_slot,
65         *source_output_mute_changed_slot,
66         *source_output_proplist_changed_slot;
67 };
68 
GET_DEVICE_FROM_STREAM(pa_object * stream)69 static inline pa_object* GET_DEVICE_FROM_STREAM(pa_object *stream) {
70     return pa_sink_input_isinstance(stream) ? PA_OBJECT(PA_SINK_INPUT(stream)->sink) : PA_OBJECT(PA_SOURCE_OUTPUT(stream)->source);
71 }
72 
GET_PROPLIST_FROM_STREAM(pa_object * stream)73 static inline pa_proplist* GET_PROPLIST_FROM_STREAM(pa_object *stream) {
74     return pa_sink_input_isinstance(stream) ? PA_SINK_INPUT(stream)->proplist : PA_SOURCE_OUTPUT(stream)->proplist;
75 }
76 
get_trigger_role(struct userdata * u,pa_object * stream,struct group * g)77 static const char *get_trigger_role(struct userdata *u, pa_object *stream, struct group *g) {
78     const char *role, *trigger_role;
79     uint32_t role_idx;
80 
81     if (!(role = pa_proplist_gets(GET_PROPLIST_FROM_STREAM(stream), PA_PROP_MEDIA_ROLE)))
82         role = "no_role";
83 
84     if (g == NULL) {
85         /* get it from all groups */
86         uint32_t j;
87         for (j = 0; j < u->n_groups; j++) {
88             PA_IDXSET_FOREACH(trigger_role, u->groups[j]->trigger_roles, role_idx) {
89                 if (pa_streq(role, trigger_role))
90                     return trigger_role;
91             }
92         }
93     } else {
94         PA_IDXSET_FOREACH(trigger_role, g->trigger_roles, role_idx) {
95             if (pa_streq(role, trigger_role))
96                 return trigger_role;
97         }
98     }
99 
100     return NULL;
101 }
102 
find_trigger_stream(struct userdata * u,pa_object * current_stream,pa_object * device,pa_object * ignore_stream,struct group * g)103 static const char *find_trigger_stream(struct userdata *u, pa_object *current_stream, pa_object *device, pa_object *ignore_stream, struct group *g) {
104     pa_object *j;
105     uint32_t idx;
106     const char *trigger_role;
107 
108     pa_assert(u);
109     pa_object_assert_ref(device);
110 
111     /* If the current stream is a trigger stream, return the role of this stream, otherwise
112      * return the role of the first trigger stream that is found on the device. */
113 
114     trigger_role = get_trigger_role(u, current_stream, g);
115     if (GET_DEVICE_FROM_STREAM(current_stream) == device && current_stream != ignore_stream && trigger_role) {
116 
117         if (pa_sink_isinstance(device)) {
118             if (!PA_SINK_INPUT(current_stream)->muted &&
119                 PA_SINK_INPUT(current_stream)->state != PA_SINK_INPUT_CORKED)
120                 return trigger_role;
121         } else {
122             if (!PA_SOURCE_OUTPUT(current_stream)->muted &&
123                 PA_SOURCE_OUTPUT(current_stream)->state != PA_SOURCE_OUTPUT_CORKED)
124                 return trigger_role;
125         }
126     }
127 
128     PA_IDXSET_FOREACH(j, pa_sink_isinstance(device) ? PA_SINK(device)->inputs : PA_SOURCE(device)->outputs, idx) {
129         if (j == ignore_stream)
130             continue;
131 
132         if (!(trigger_role = get_trigger_role(u, PA_OBJECT(j), g)))
133             continue;
134 
135         if (pa_sink_isinstance(device)) {
136             if (!PA_SINK_INPUT(j)->muted &&
137                 PA_SINK_INPUT(j)->state != PA_SINK_INPUT_CORKED)
138                 return trigger_role;
139         } else {
140             if (!PA_SOURCE_OUTPUT(j)->muted &&
141                 PA_SOURCE_OUTPUT(j)->state != PA_SOURCE_OUTPUT_CORKED)
142                 return trigger_role;
143         }
144     }
145 
146     return NULL;
147 }
148 
find_global_trigger_stream(struct userdata * u,pa_object * current_stream,pa_object * ignore_stream,struct group * g)149 static const char *find_global_trigger_stream(struct userdata *u, pa_object *current_stream, pa_object *ignore_stream, struct group *g) {
150     const char *trigger_role = NULL;
151     pa_sink *sink;
152     pa_source *source;
153     uint32_t idx;
154 
155     pa_assert(u);
156 
157     /* Check device of current stream first in case the current stream is a trigger stream. */
158     if ((trigger_role = find_trigger_stream(u, current_stream, GET_DEVICE_FROM_STREAM(current_stream), ignore_stream, g)))
159         return trigger_role;
160 
161     /* Find any trigger role among the sink-inputs and source-outputs. */
162     PA_IDXSET_FOREACH(sink, u->core->sinks, idx)
163         if ((trigger_role = find_trigger_stream(u, current_stream, PA_OBJECT(sink), ignore_stream, g)))
164             break;
165 
166     if (!u->source_trigger || trigger_role)
167         return trigger_role;
168 
169     PA_IDXSET_FOREACH(source, u->core->sources, idx)
170         if ((trigger_role = find_trigger_stream(u, current_stream, PA_OBJECT(source), ignore_stream, g)))
171             break;
172 
173     return trigger_role;
174 }
175 
cork_or_duck(struct userdata * u,pa_sink_input * i,const char * interaction_role,const char * trigger_role,bool interaction_applied,struct group * g)176 static void cork_or_duck(struct userdata *u, pa_sink_input *i, const char *interaction_role,  const char *trigger_role, bool interaction_applied, struct group *g) {
177 
178     if (u->duck && !interaction_applied) {
179         pa_cvolume vol;
180         vol.channels = 1;
181         vol.values[0] = g->volume;
182 
183         pa_log_debug("Found a '%s' stream of '%s' that ducks a '%s' stream.", trigger_role, g->name, interaction_role);
184         pa_sink_input_add_volume_factor(i, g->name, &vol);
185 
186     } else if (!u->duck) {
187         pa_log_debug("Found a '%s' stream that corks/mutes a '%s' stream.", trigger_role, interaction_role);
188         pa_sink_input_set_mute(i, true, false);
189         pa_sink_input_send_event(i, PA_STREAM_EVENT_REQUEST_CORK, NULL);
190     }
191 }
192 
uncork_or_unduck(struct userdata * u,pa_sink_input * i,const char * interaction_role,bool corked,struct group * g)193 static void uncork_or_unduck(struct userdata *u, pa_sink_input *i, const char *interaction_role, bool corked, struct group *g) {
194 
195     if (u->duck) {
196        pa_log_debug("In '%s', found a '%s' stream that should be unducked", g->name, interaction_role);
197        pa_sink_input_remove_volume_factor(i, g->name);
198     }
199     else if (corked || i->muted) {
200        pa_log_debug("Found a '%s' stream that should be uncorked/unmuted.", interaction_role);
201        if (i->muted)
202           pa_sink_input_set_mute(i, false, false);
203        if (corked)
204           pa_sink_input_send_event(i, PA_STREAM_EVENT_REQUEST_UNCORK, NULL);
205     }
206 }
207 
apply_interaction_to_sink(struct userdata * u,pa_sink * s,const char * new_trigger,pa_sink_input * ignore_stream,bool new_stream,struct group * g)208 static inline void apply_interaction_to_sink(struct userdata *u, pa_sink *s, const char *new_trigger, pa_sink_input *ignore_stream, bool new_stream, struct group *g) {
209     pa_sink_input *j;
210     uint32_t idx, role_idx;
211     const char *interaction_role;
212     bool trigger = false;
213 
214     pa_assert(u);
215     pa_sink_assert_ref(s);
216 
217     PA_IDXSET_FOREACH(j, s->inputs, idx) {
218         bool corked, interaction_applied;
219         const char *role;
220 
221         if (j == ignore_stream)
222             continue;
223 
224         if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE)))
225             role = "no_role";
226 
227         PA_IDXSET_FOREACH(interaction_role, g->interaction_roles, role_idx) {
228             if ((trigger = (pa_streq(interaction_role, role) && (!get_trigger_role(u, PA_OBJECT(j), g) || !pa_safe_streq(new_trigger, role)))))
229                 break;
230             if ((trigger = (pa_streq(interaction_role, "any_role") && !get_trigger_role(u, PA_OBJECT(j), g))))
231                 break;
232         }
233         if (!trigger)
234             continue;
235 
236         /* Some applications start their streams corked, so the stream is uncorked by */
237         /* the application only after sink_input_put() was called. If a new stream turns */
238         /* up, act as if it was not corked. In the case of module-role-cork this will */
239         /* only mute the stream because corking is reverted later by the application */
240         corked = (j->state == PA_SINK_INPUT_CORKED);
241         if (new_stream && corked)
242             corked = false;
243         interaction_applied = !!pa_hashmap_get(g->interaction_state, j);
244 
245         if (new_trigger && ((!corked && !j->muted) || u->duck)) {
246             if (!interaction_applied)
247                 pa_hashmap_put(g->interaction_state, j, PA_INT_TO_PTR(1));
248 
249             cork_or_duck(u, j, role, new_trigger, interaction_applied, g);
250 
251         } else if (!new_trigger && interaction_applied) {
252             pa_hashmap_remove(g->interaction_state, j);
253 
254             uncork_or_unduck(u, j, role, corked, g);
255         }
256     }
257 }
258 
apply_interaction_global(struct userdata * u,const char * trigger_role,pa_sink_input * ignore_stream,bool new_stream,struct group * g)259 static void apply_interaction_global(struct userdata *u, const char *trigger_role, pa_sink_input *ignore_stream, bool new_stream, struct group *g) {
260     uint32_t idx;
261     pa_sink *s;
262 
263     pa_assert(u);
264 
265     PA_IDXSET_FOREACH(s, u->core->sinks, idx)
266         apply_interaction_to_sink(u, s, trigger_role, ignore_stream, new_stream, g);
267 }
268 
remove_interactions(struct userdata * u,struct group * g)269 static void remove_interactions(struct userdata *u, struct group *g) {
270     uint32_t idx, idx_input;
271     pa_sink *s;
272     pa_sink_input *j;
273     bool corked;
274     const char *role;
275 
276     PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
277         PA_IDXSET_FOREACH(j, s->inputs, idx_input) {
278             if(!!pa_hashmap_get(g->interaction_state, j)) {
279                 corked = (j->state == PA_SINK_INPUT_CORKED);
280                 if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE)))
281                    role = "no_role";
282                 uncork_or_unduck(u, j, role, corked, g);
283             }
284         }
285     }
286 }
287 
process(struct userdata * u,pa_object * stream,bool create,bool new_stream)288 static pa_hook_result_t process(struct userdata *u, pa_object *stream, bool create, bool new_stream) {
289     const char *trigger_role;
290     uint32_t j;
291 
292     pa_assert(u);
293     pa_object_assert_ref(stream);
294 
295     if (!create)
296         for (j = 0; j < u->n_groups; j++)
297             pa_hashmap_remove(u->groups[j]->interaction_state, stream);
298 
299     if ((pa_sink_input_isinstance(stream) && !PA_SINK_INPUT(stream)->sink) ||
300         (pa_source_output_isinstance(stream) && !PA_SOURCE_OUTPUT(stream)->source))
301         return PA_HOOK_OK;
302 
303     if (pa_source_output_isinstance(stream)) {
304         if (!u->source_trigger)
305             return PA_HOOK_OK;
306         /* If it is triggered from source-output with false of global option, no need to apply interaction. */
307         if (!u->global)
308             return PA_HOOK_OK;
309     }
310 
311     for (j = 0; j < u->n_groups; j++) {
312         if (u->global) {
313             trigger_role = find_global_trigger_stream(u, stream, create ? NULL : stream, u->groups[j]);
314             apply_interaction_global(u, trigger_role, create ? NULL : (pa_sink_input_isinstance(stream) ? PA_SINK_INPUT(stream) : NULL), new_stream, u->groups[j]);
315         } else {
316             trigger_role = find_trigger_stream(u, stream, GET_DEVICE_FROM_STREAM(stream), create ? NULL : stream, u->groups[j]);
317             if (pa_sink_input_isinstance(stream))
318                 apply_interaction_to_sink(u, PA_SINK_INPUT(stream)->sink, trigger_role, create ? NULL : PA_SINK_INPUT(stream), new_stream, u->groups[j]);
319         }
320     }
321 
322     return PA_HOOK_OK;
323 }
324 
sink_input_put_cb(pa_core * core,pa_sink_input * i,struct userdata * u)325 static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
326     pa_core_assert_ref(core);
327     pa_sink_input_assert_ref(i);
328 
329     return process(u, PA_OBJECT(i), true, true);
330 }
331 
sink_input_unlink_cb(pa_core * core,pa_sink_input * i,struct userdata * u)332 static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
333     pa_sink_input_assert_ref(i);
334 
335     return process(u, PA_OBJECT(i), false, false);
336 }
337 
sink_input_move_start_cb(pa_core * core,pa_sink_input * i,struct userdata * u)338 static pa_hook_result_t sink_input_move_start_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
339     pa_core_assert_ref(core);
340     pa_sink_input_assert_ref(i);
341 
342     return process(u, PA_OBJECT(i), false, false);
343 }
344 
sink_input_move_finish_cb(pa_core * core,pa_sink_input * i,struct userdata * u)345 static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
346     pa_core_assert_ref(core);
347     pa_sink_input_assert_ref(i);
348 
349     return process(u, PA_OBJECT(i), true, false);
350 }
351 
sink_input_state_changed_cb(pa_core * core,pa_sink_input * i,struct userdata * u)352 static pa_hook_result_t sink_input_state_changed_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
353     pa_core_assert_ref(core);
354     pa_sink_input_assert_ref(i);
355 
356     if (PA_SINK_INPUT_IS_LINKED(i->state) && get_trigger_role(u, PA_OBJECT(i), NULL))
357         return process(u, PA_OBJECT(i), true, false);
358 
359     return PA_HOOK_OK;
360 }
361 
sink_input_mute_changed_cb(pa_core * core,pa_sink_input * i,struct userdata * u)362 static pa_hook_result_t sink_input_mute_changed_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
363     pa_core_assert_ref(core);
364     pa_sink_input_assert_ref(i);
365 
366     if (PA_SINK_INPUT_IS_LINKED(i->state) && get_trigger_role(u, PA_OBJECT(i), NULL))
367         return process(u, PA_OBJECT(i), true, false);
368 
369     return PA_HOOK_OK;
370 }
371 
sink_input_proplist_changed_cb(pa_core * core,pa_sink_input * i,struct userdata * u)372 static pa_hook_result_t sink_input_proplist_changed_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
373     pa_core_assert_ref(core);
374     pa_sink_input_assert_ref(i);
375 
376     if (PA_SINK_INPUT_IS_LINKED(i->state))
377         return process(u, PA_OBJECT(i), true, false);
378 
379     return PA_HOOK_OK;
380 }
381 
source_output_put_cb(pa_core * core,pa_source_output * o,struct userdata * u)382 static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
383     pa_core_assert_ref(core);
384     pa_source_output_assert_ref(o);
385 
386     return process(u, PA_OBJECT(o), true, true);
387 }
388 
source_output_unlink_cb(pa_core * core,pa_source_output * o,struct userdata * u)389 static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
390     pa_source_output_assert_ref(o);
391 
392     return process(u, PA_OBJECT(o), false, false);
393 }
394 
source_output_move_start_cb(pa_core * core,pa_source_output * o,struct userdata * u)395 static pa_hook_result_t source_output_move_start_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
396     pa_core_assert_ref(core);
397     pa_source_output_assert_ref(o);
398 
399     return process(u, PA_OBJECT(o), false, false);
400 }
401 
source_output_move_finish_cb(pa_core * core,pa_source_output * o,struct userdata * u)402 static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
403     pa_core_assert_ref(core);
404     pa_source_output_assert_ref(o);
405 
406     return process(u, PA_OBJECT(o), true, false);
407 }
408 
source_output_state_changed_cb(pa_core * core,pa_source_output * o,struct userdata * u)409 static pa_hook_result_t source_output_state_changed_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
410     pa_core_assert_ref(core);
411     pa_source_output_assert_ref(o);
412 
413     if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && get_trigger_role(u, PA_OBJECT(o), NULL))
414         return process(u, PA_OBJECT(o), true, false);
415 
416     return PA_HOOK_OK;
417 }
418 
source_output_mute_changed_cb(pa_core * core,pa_source_output * o,struct userdata * u)419 static pa_hook_result_t source_output_mute_changed_cb(pa_core *core, pa_source_output*o, struct userdata *u) {
420     pa_core_assert_ref(core);
421     pa_source_output_assert_ref(o);
422 
423     if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && get_trigger_role(u, PA_OBJECT(o), NULL))
424         return process(u, PA_OBJECT(o), true, false);
425 
426     return PA_HOOK_OK;
427 }
428 
source_output_proplist_changed_cb(pa_core * core,pa_source_output * o,struct userdata * u)429 static pa_hook_result_t source_output_proplist_changed_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
430     pa_core_assert_ref(core);
431     pa_source_output_assert_ref(o);
432 
433     if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
434         return process(u, PA_OBJECT(o), true, false);
435 
436     return PA_HOOK_OK;
437 }
438 
pa_stream_interaction_init(pa_module * m,const char * const v_modargs[])439 int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
440     pa_modargs *ma = NULL;
441     struct userdata *u;
442     const char *roles;
443     char *roles_in_group = NULL;
444     bool global = false;
445     bool source_trigger = false;
446     uint32_t i = 0;
447     uint32_t group_count_tr = 0;
448     uint32_t group_count_du = 0;
449     uint32_t group_count_vol = 0;
450 
451     pa_assert(m);
452 
453     if (!(ma = pa_modargs_new(m->argument, v_modargs))) {
454         pa_log("Failed to parse module arguments");
455         goto fail;
456     }
457 
458     m->userdata = u = pa_xnew0(struct userdata, 1);
459 
460     u->core = m->core;
461 
462     u->duck = false;
463     if (pa_streq(m->name, "module-role-ducking"))
464         u->duck = true;
465 
466     u->n_groups = 1;
467 
468     roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
469     if (roles) {
470         const char *split_state = NULL;
471         char *n = NULL;
472         while ((n = pa_split(roles, "/", &split_state))) {
473             group_count_tr++;
474             pa_xfree(n);
475         }
476     }
477     roles = pa_modargs_get_value(ma, u->duck ? "ducking_roles" : "cork_roles", NULL);
478     if (roles) {
479         const char *split_state = NULL;
480         char *n = NULL;
481         while ((n = pa_split(roles, "/", &split_state))) {
482             group_count_du++;
483             pa_xfree(n);
484         }
485     }
486 
487     if (u->duck) {
488         const char *volumes;
489 
490         volumes = pa_modargs_get_value(ma, "volume", NULL);
491         if (volumes) {
492             const char *split_state = NULL;
493             char *n = NULL;
494             while ((n = pa_split(volumes, "/", &split_state))) {
495                 group_count_vol++;
496                 pa_xfree(n);
497             }
498         }
499 
500         if ((group_count_tr > 1 || group_count_du > 1 || group_count_vol > 1) &&
501             ((group_count_tr != group_count_du) || (group_count_tr != group_count_vol))) {
502             pa_log("Invalid number of groups");
503             goto fail;
504         }
505     } else {
506         if ((group_count_tr > 1 || group_count_du > 1) && (group_count_tr != group_count_du)) {
507             pa_log("Invalid number of groups");
508             goto fail;
509         }
510     }
511 
512     if (group_count_tr > 0)
513         u->n_groups = group_count_tr;
514 
515     u->groups = pa_xnew0(struct group*, u->n_groups);
516     for (i = 0; i < u->n_groups; i++) {
517         u->groups[i] = pa_xnew0(struct group, 1);
518         u->groups[i]->trigger_roles = pa_idxset_new(NULL, NULL);
519         u->groups[i]->interaction_roles = pa_idxset_new(NULL, NULL);
520         u->groups[i]->interaction_state = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
521         if (u->duck)
522             u->groups[i]->name = pa_sprintf_malloc("ducking_group_%u", i);
523     }
524 
525     roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
526     if (roles) {
527         const char *group_split_state = NULL;
528         i = 0;
529 
530         while ((roles_in_group = pa_split(roles, "/", &group_split_state))) {
531             if (roles_in_group[0] != '\0') {
532                 const char *split_state = NULL;
533                 char *n = NULL;
534                 while ((n = pa_split(roles_in_group, ",", &split_state))) {
535                     if (n[0] != '\0')
536                         pa_idxset_put(u->groups[i]->trigger_roles, n, NULL);
537                     else {
538                         pa_log("empty trigger role");
539                         pa_xfree(n);
540                         goto fail;
541                     }
542                 }
543                 i++;
544             } else {
545                 pa_log("empty trigger roles");
546                 goto fail;
547             }
548 
549             pa_xfree(roles_in_group);
550         }
551     }
552     if (pa_idxset_isempty(u->groups[0]->trigger_roles)) {
553         pa_log_debug("Using role 'phone' as trigger role.");
554         pa_idxset_put(u->groups[0]->trigger_roles, pa_xstrdup("phone"), NULL);
555     }
556 
557     roles = pa_modargs_get_value(ma, u->duck ? "ducking_roles" : "cork_roles", NULL);
558     if (roles) {
559         const char *group_split_state = NULL;
560         i = 0;
561 
562         while ((roles_in_group = pa_split(roles, "/", &group_split_state))) {
563             if (roles_in_group[0] != '\0') {
564                 const char *split_state = NULL;
565                 char *n = NULL;
566                 while ((n = pa_split(roles_in_group, ",", &split_state))) {
567                     if (n[0] != '\0')
568                         pa_idxset_put(u->groups[i]->interaction_roles, n, NULL);
569                     else {
570                         pa_log("empty ducking role");
571                         pa_xfree(n);
572                         goto fail;
573                      }
574                 }
575                 i++;
576             } else {
577                 pa_log("empty ducking roles");
578                 goto fail;
579             }
580 
581             pa_xfree(roles_in_group);
582         }
583     }
584     if (pa_idxset_isempty(u->groups[0]->interaction_roles)) {
585         pa_log_debug("Using roles 'music' and 'video' as %s roles.", u->duck ? "ducking" : "cork");
586         pa_idxset_put(u->groups[0]->interaction_roles, pa_xstrdup("music"), NULL);
587         pa_idxset_put(u->groups[0]->interaction_roles, pa_xstrdup("video"), NULL);
588     }
589 
590     if (u->duck) {
591         const char *volumes;
592         u->groups[0]->volume = pa_sw_volume_from_dB(-20);
593         if ((volumes = pa_modargs_get_value(ma, "volume", NULL))) {
594             const char *group_split_state = NULL;
595             char *n = NULL;
596             i = 0;
597             while ((n = pa_split(volumes, "/", &group_split_state))) {
598                 if (n[0] != '\0') {
599                     if (pa_parse_volume(n, &(u->groups[i++]->volume)) < 0) {
600                         pa_log("Failed to parse volume");
601                         pa_xfree(n);
602                         goto fail;
603                     }
604                 } else {
605                     pa_log("empty volume");
606                     pa_xfree(n);
607                     goto fail;
608                 }
609                 pa_xfree(n);
610             }
611         }
612     }
613 
614     if (pa_modargs_get_value_boolean(ma, "global", &global) < 0) {
615         pa_log("Invalid boolean parameter: global");
616         goto fail;
617     }
618     u->global = global;
619 
620     if (pa_modargs_get_value_boolean(ma, "use_source_trigger", &source_trigger) < 0) {
621         pa_log("Invalid boolean parameter: use_source_trigger");
622         goto fail;
623     }
624     u->source_trigger = source_trigger;
625 
626     u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u);
627     u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u);
628     u->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_start_cb, u);
629     u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u);
630     u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_state_changed_cb, u);
631     u->sink_input_mute_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_mute_changed_cb, u);
632     u->sink_input_proplist_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_proplist_changed_cb, u);
633     u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_output_put_cb, u);
634     u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_output_unlink_cb, u);
635     u->source_output_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) source_output_move_start_cb, u);
636     u->source_output_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) source_output_move_finish_cb, u);
637     u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) source_output_state_changed_cb, u);
638     u->source_output_mute_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) source_output_mute_changed_cb, u);
639     u->source_output_proplist_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) source_output_proplist_changed_cb, u);
640 
641     pa_modargs_free(ma);
642 
643     return 0;
644 
645 fail:
646     pa_stream_interaction_done(m);
647 
648     if (ma)
649         pa_modargs_free(ma);
650     if (roles_in_group)
651         pa_xfree(roles_in_group);
652 
653     return -1;
654 
655 }
656 
pa_stream_interaction_done(pa_module * m)657 void pa_stream_interaction_done(pa_module *m) {
658     struct userdata* u;
659 
660     pa_assert(m);
661 
662     if (!(u = m->userdata))
663         return;
664 
665     if (u->groups) {
666         uint32_t j;
667         for (j = 0; j < u->n_groups; j++) {
668             remove_interactions(u, u->groups[j]);
669             pa_idxset_free(u->groups[j]->trigger_roles, pa_xfree);
670             pa_idxset_free(u->groups[j]->interaction_roles, pa_xfree);
671             pa_hashmap_free(u->groups[j]->interaction_state);
672             if (u->duck)
673                 pa_xfree(u->groups[j]->name);
674             pa_xfree(u->groups[j]);
675         }
676         pa_xfree(u->groups);
677     }
678 
679     if (u->sink_input_put_slot)
680         pa_hook_slot_free(u->sink_input_put_slot);
681     if (u->sink_input_unlink_slot)
682         pa_hook_slot_free(u->sink_input_unlink_slot);
683     if (u->sink_input_move_start_slot)
684         pa_hook_slot_free(u->sink_input_move_start_slot);
685     if (u->sink_input_move_finish_slot)
686         pa_hook_slot_free(u->sink_input_move_finish_slot);
687     if (u->sink_input_state_changed_slot)
688         pa_hook_slot_free(u->sink_input_state_changed_slot);
689     if (u->sink_input_mute_changed_slot)
690         pa_hook_slot_free(u->sink_input_mute_changed_slot);
691     if (u->sink_input_proplist_changed_slot)
692         pa_hook_slot_free(u->sink_input_proplist_changed_slot);
693     if (u->source_output_put_slot)
694         pa_hook_slot_free(u->source_output_put_slot);
695     if (u->source_output_unlink_slot)
696         pa_hook_slot_free(u->source_output_unlink_slot);
697     if (u->source_output_move_start_slot)
698         pa_hook_slot_free(u->source_output_move_start_slot);
699     if (u->source_output_move_finish_slot)
700         pa_hook_slot_free(u->source_output_move_finish_slot);
701     if (u->source_output_state_changed_slot)
702         pa_hook_slot_free(u->source_output_state_changed_slot);
703     if (u->source_output_mute_changed_slot)
704         pa_hook_slot_free(u->source_output_mute_changed_slot);
705     if (u->source_output_proplist_changed_slot)
706         pa_hook_slot_free(u->source_output_proplist_changed_slot);
707 
708     pa_xfree(u);
709 
710 }
711