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