1/***
2  This file is part of PulseAudio.
3
4  Copyright 2006 Lennart Poettering
5  Copyright 2009 Canonical Ltd
6  Copyright (C) 2012 Intel Corporation
7
8  PulseAudio is free software; you can redistribute it and/or modify
9  it under the terms of the GNU Lesser General Public License as published
10  by the Free Software Foundation; either version 2.1 of the License,
11  or (at your option) any later version.
12
13  PulseAudio is distributed in the hope that it will be useful, but
14  WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  General Public License for more details.
17
18  You should have received a copy of the GNU Lesser General Public License
19  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
20***/
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
26#include <pulse/xmalloc.h>
27
28#include <pulsecore/core.h>
29#include <pulsecore/modargs.h>
30#include <pulsecore/source-output.h>
31#include <pulsecore/source.h>
32#include <pulsecore/core-util.h>
33
34PA_MODULE_AUTHOR("Frédéric Dalleau, Pali Rohár");
35PA_MODULE_DESCRIPTION("Policy module to make using bluetooth devices out-of-the-box easier");
36PA_MODULE_VERSION(PACKAGE_VERSION);
37PA_MODULE_LOAD_ONCE(true);
38PA_MODULE_USAGE(
39        "auto_switch=<Switch between hsp and a2dp profile? (0 - never, 1 - media.role=phone, 2 - heuristic> "
40        "a2dp_source=<Handle a2dp_source card profile (sink role)?> "
41        "ag=<Handle headset_audio_gateway or handsfree_audio_gateway card profile (headset role)?> ");
42
43static const char* const valid_modargs[] = {
44    "auto_switch",
45    "a2dp_source",
46    "ag",
47    NULL
48};
49
50struct userdata {
51    uint32_t auto_switch;
52    bool enable_a2dp_source;
53    bool enable_ag;
54    pa_hook_slot *source_put_slot;
55    pa_hook_slot *sink_put_slot;
56    pa_hook_slot *source_output_put_slot;
57    pa_hook_slot *source_output_unlink_slot;
58    pa_hook_slot *card_init_profile_slot;
59    pa_hook_slot *card_unlink_slot;
60    pa_hook_slot *profile_available_changed_slot;
61    pa_hashmap *will_need_revert_card_map;
62};
63
64/* When a source is created, loopback it to default sink */
65static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void *userdata) {
66    struct userdata *u = userdata;
67    const char *s;
68    const char *role;
69    char *args;
70    pa_module *m = NULL;
71
72    pa_assert(c);
73    pa_assert(source);
74
75    /* Only consider bluetooth sinks and sources */
76    s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_BUS);
77    if (!s)
78        return PA_HOOK_OK;
79
80    if (!pa_streq(s, "bluetooth"))
81        return PA_HOOK_OK;
82
83    s = pa_proplist_gets(source->proplist, "bluetooth.protocol");
84    if (!s)
85        return PA_HOOK_OK;
86
87    if (u->enable_a2dp_source && pa_streq(s, "a2dp_source"))
88        role = "music";
89    else if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway")))
90        role = "phone";
91    else {
92        pa_log_debug("Profile %s cannot be selected for loopback", s);
93        return PA_HOOK_OK;
94    }
95
96    /* Load module-loopback */
97    args = pa_sprintf_malloc("source=\"%s\" source_dont_move=\"true\" sink_input_properties=\"media.role=%s\"", source->name,
98                             role);
99    (void) pa_module_load(&m, c, "module-loopback", args);
100    pa_xfree(args);
101
102    return PA_HOOK_OK;
103}
104
105/* When a sink is created, loopback it to default source */
106static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *userdata) {
107    struct userdata *u = userdata;
108    const char *s;
109    const char *role;
110    char *args;
111    pa_module *m = NULL;
112
113    pa_assert(c);
114    pa_assert(sink);
115
116    /* Only consider bluetooth sinks and sources */
117    s = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_BUS);
118    if (!s)
119        return PA_HOOK_OK;
120
121    if (!pa_streq(s, "bluetooth"))
122        return PA_HOOK_OK;
123
124    s = pa_proplist_gets(sink->proplist, "bluetooth.protocol");
125    if (!s)
126        return PA_HOOK_OK;
127
128    if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway")))
129        role = "phone";
130    else {
131        pa_log_debug("Profile %s cannot be selected for loopback", s);
132        return PA_HOOK_OK;
133    }
134
135    /* Load module-loopback */
136    args = pa_sprintf_malloc("sink=\"%s\" sink_dont_move=\"true\" source_output_properties=\"media.role=%s\"", sink->name,
137                             role);
138    (void) pa_module_load(&m, c, "module-loopback", args);
139    pa_xfree(args);
140
141    return PA_HOOK_OK;
142}
143
144static void card_set_profile(struct userdata *u, pa_card *card, bool revert_to_a2dp)
145{
146    pa_card_profile *profile;
147    void *state;
148
149    /* Find available profile and activate it */
150    PA_HASHMAP_FOREACH(profile, card->profiles, state) {
151        if (profile->available == PA_AVAILABLE_NO)
152            continue;
153
154        /* Check for correct profile based on revert_to_a2dp */
155        if (revert_to_a2dp) {
156            if (!pa_streq(profile->name, "a2dp_sink"))
157                continue;
158        } else {
159            if (!pa_streq(profile->name, "headset_head_unit") && !pa_streq(profile->name, "handsfree_head_unit"))
160                continue;
161        }
162
163        pa_log_debug("Setting card '%s' to profile '%s'", card->name, profile->name);
164
165        if (pa_card_set_profile(card, profile, false) != 0) {
166            pa_log_warn("Could not set profile '%s'", profile->name);
167            continue;
168        }
169
170        /* When we are not in revert_to_a2dp phase flag this card for will_need_revert */
171        if (!revert_to_a2dp)
172            pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1));
173
174        break;
175    }
176}
177
178/* Switch profile for one card */
179static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) {
180    struct userdata *u = userdata;
181    const char *s;
182
183    /* Only consider bluetooth cards */
184    s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS);
185    if (!s || !pa_streq(s, "bluetooth"))
186        return;
187
188    if (revert_to_a2dp) {
189        /* In revert_to_a2dp phase only consider cards with will_need_revert flag and remove it */
190        if (!pa_hashmap_remove(u->will_need_revert_card_map, card))
191            return;
192
193        /* Skip card if does not have active headset profile */
194        if (!pa_streq(card->active_profile->name, "headset_head_unit") && !pa_streq(card->active_profile->name, "handsfree_head_unit"))
195            return;
196
197        /* Skip card if already has active a2dp profile */
198        if (pa_streq(card->active_profile->name, "a2dp_sink"))
199            return;
200    } else {
201        /* Skip card if does not have active a2dp profile */
202        if (!pa_streq(card->active_profile->name, "a2dp_sink"))
203            return;
204
205        /* Skip card if already has active headset profile */
206        if (pa_streq(card->active_profile->name, "headset_head_unit") || pa_streq(card->active_profile->name, "handsfree_head_unit"))
207            return;
208    }
209
210    card_set_profile(u, card, revert_to_a2dp);
211}
212
213/* Return true if we should ignore this source output */
214static bool ignore_output(pa_source_output *source_output, void *userdata) {
215    struct userdata *u = userdata;
216    const char *s;
217
218    /* New applications could set media.role for identifying streams */
219    /* We are interested only in media.role=phone */
220    s = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE);
221    if (s)
222        return !pa_streq(s, "phone");
223
224    /* If media.role is not set use some heuristic (if enabled) */
225    if (u->auto_switch != 2)
226        return true;
227
228    /* Ignore if resample method is peaks (used by desktop volume programs) */
229    if (pa_source_output_get_resample_method(source_output) == PA_RESAMPLER_PEAKS)
230        return true;
231
232    /* Ignore if there is no client/application assigned (used by virtual stream) */
233    if (!source_output->client)
234        return true;
235
236    /* Ignore if recording from monitor of sink */
237    if (source_output->direct_on_input)
238        return true;
239
240    return false;
241}
242
243static unsigned source_output_count(pa_core *c, void *userdata) {
244    pa_source_output *source_output;
245    uint32_t idx;
246    unsigned count = 0;
247
248    PA_IDXSET_FOREACH(source_output, c->source_outputs, idx)
249        if (!ignore_output(source_output, userdata))
250            ++count;
251
252    return count;
253}
254
255/* Switch profile for all cards */
256static void switch_profile_all(pa_idxset *cards, bool revert_to_a2dp, void *userdata) {
257    pa_card *card;
258    uint32_t idx;
259
260    PA_IDXSET_FOREACH(card, cards, idx)
261        switch_profile(card, revert_to_a2dp, userdata);
262}
263
264/* When a source output is created, switch profile a2dp to profile hsp */
265static pa_hook_result_t source_output_put_hook_callback(pa_core *c, pa_source_output *source_output, void *userdata) {
266    pa_assert(c);
267    pa_assert(source_output);
268
269    if (ignore_output(source_output, userdata))
270        return PA_HOOK_OK;
271
272    switch_profile_all(c->cards, false, userdata);
273    return PA_HOOK_OK;
274}
275
276/* When all source outputs are unlinked, switch profile hsp back back to profile a2dp */
277static pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source_output *source_output, void *userdata) {
278    pa_assert(c);
279    pa_assert(source_output);
280
281    if (ignore_output(source_output, userdata))
282        return PA_HOOK_OK;
283
284    /* If there are still some source outputs do nothing. */
285    if (source_output_count(c, userdata) > 0)
286        return PA_HOOK_OK;
287
288    switch_profile_all(c->cards, true, userdata);
289    return PA_HOOK_OK;
290}
291
292static pa_hook_result_t card_init_profile_hook_callback(pa_core *c, pa_card *card, void *userdata) {
293    struct userdata *u = userdata;
294    const char *s;
295
296    pa_assert(c);
297    pa_assert(card);
298
299    if (source_output_count(c, userdata) == 0)
300        return PA_HOOK_OK;
301
302    /* Only consider bluetooth cards */
303    s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS);
304    if (!s || !pa_streq(s, "bluetooth"))
305        return PA_HOOK_OK;
306
307    /* Ignore card if has already set other initial profile than a2dp */
308    if (card->active_profile &&
309        !pa_streq(card->active_profile->name, "a2dp_sink"))
310        return PA_HOOK_OK;
311
312    /* Set initial profile to hsp */
313    card_set_profile(u, card, false);
314
315    /* Flag this card for will_need_revert */
316    pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1));
317    return PA_HOOK_OK;
318}
319
320static pa_hook_result_t card_unlink_hook_callback(pa_core *c, pa_card *card, void *userdata) {
321    pa_assert(c);
322    pa_assert(card);
323    switch_profile(card, true, userdata);
324    return PA_HOOK_OK;
325}
326
327static pa_card_profile *find_best_profile(pa_card *card) {
328    void *state;
329    pa_card_profile *profile;
330    pa_card_profile *result = card->active_profile;
331
332    PA_HASHMAP_FOREACH(profile, card->profiles, state) {
333        if (profile->available == PA_AVAILABLE_NO)
334            continue;
335
336        if (result == NULL ||
337            (profile->available == PA_AVAILABLE_YES && result->available == PA_AVAILABLE_UNKNOWN) ||
338            (profile->available == result->available && profile->priority > result->priority))
339            result = profile;
340    }
341
342    return result;
343}
344
345static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_profile *profile, void *userdata) {
346    pa_card *card;
347    const char *s;
348    bool is_active_profile;
349    pa_card_profile *selected_profile;
350
351    pa_assert(c);
352    pa_assert(profile);
353    pa_assert_se((card = profile->card));
354
355    /* Only consider bluetooth cards */
356    s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS);
357    if (!s || !pa_streq(s, "bluetooth"))
358        return PA_HOOK_OK;
359
360    /* Do not automatically switch profiles for headsets, just in case */
361    if (pa_streq(profile->name, "a2dp_sink") ||
362        pa_streq(profile->name, "headset_head_unit") ||
363        pa_streq(profile->name, "handsfree_head_unit"))
364        return PA_HOOK_OK;
365
366    is_active_profile = card->active_profile == profile;
367
368    if (profile->available == PA_AVAILABLE_YES) {
369        if (is_active_profile)
370            return PA_HOOK_OK;
371
372        if (card->active_profile->available == PA_AVAILABLE_YES && card->active_profile->priority >= profile->priority)
373            return PA_HOOK_OK;
374
375        selected_profile = profile;
376    } else {
377        if (!is_active_profile)
378            return PA_HOOK_OK;
379
380        pa_assert_se((selected_profile = find_best_profile(card)));
381
382        if (selected_profile == card->active_profile)
383            return PA_HOOK_OK;
384    }
385
386    pa_log_debug("Setting card '%s' to profile '%s'", card->name, selected_profile->name);
387
388    if (pa_card_set_profile(card, selected_profile, false) != 0)
389        pa_log_warn("Could not set profile '%s'", selected_profile->name);
390
391    return PA_HOOK_OK;
392}
393
394static void handle_all_profiles(pa_core *core) {
395    pa_card *card;
396    uint32_t state;
397
398    PA_IDXSET_FOREACH(card, core->cards, state) {
399        pa_card_profile *profile;
400        void *state2;
401
402        PA_HASHMAP_FOREACH(profile, card->profiles, state2)
403            profile_available_hook_callback(core, profile, NULL);
404    }
405}
406
407int pa__init(pa_module *m) {
408    pa_modargs *ma;
409    struct userdata *u;
410
411    pa_assert(m);
412
413    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
414        pa_log_error("Failed to parse module arguments");
415        goto fail;
416    }
417
418    m->userdata = u = pa_xnew0(struct userdata, 1);
419
420    u->auto_switch = 1;
421
422    if (pa_modargs_get_value(ma, "auto_switch", NULL)) {
423        bool auto_switch_bool;
424
425        /* auto_switch originally took a boolean value, let's keep
426         * compatibility with configuration files that still pass a boolean. */
427        if (pa_modargs_get_value_boolean(ma, "auto_switch", &auto_switch_bool) >= 0) {
428            if (auto_switch_bool)
429                u->auto_switch = 1;
430            else
431                u->auto_switch = 0;
432
433        } else if (pa_modargs_get_value_u32(ma, "auto_switch", &u->auto_switch) < 0) {
434            pa_log("Failed to parse auto_switch argument.");
435            goto fail;
436        }
437    }
438
439    u->enable_a2dp_source = true;
440    if (pa_modargs_get_value_boolean(ma, "a2dp_source", &u->enable_a2dp_source) < 0) {
441        pa_log("Failed to parse a2dp_source argument.");
442        goto fail;
443    }
444
445    u->enable_ag = true;
446    if (pa_modargs_get_value_boolean(ma, "ag", &u->enable_ag) < 0) {
447        pa_log("Failed to parse ag argument.");
448        goto fail;
449    }
450
451    u->will_need_revert_card_map = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
452
453    u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL,
454                                         (pa_hook_cb_t) source_put_hook_callback, u);
455
456    u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL,
457                                       (pa_hook_cb_t) sink_put_hook_callback, u);
458
459    if (u->auto_switch) {
460        u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL,
461                                                    (pa_hook_cb_t) source_output_put_hook_callback, u);
462
463        u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_NORMAL,
464                                                       (pa_hook_cb_t) source_output_unlink_hook_callback, u);
465
466        u->card_init_profile_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], PA_HOOK_NORMAL,
467                                           (pa_hook_cb_t) card_init_profile_hook_callback, u);
468
469        u->card_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_UNLINK], PA_HOOK_NORMAL,
470                                           (pa_hook_cb_t) card_unlink_hook_callback, u);
471    }
472
473    u->profile_available_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED],
474                                                        PA_HOOK_NORMAL, (pa_hook_cb_t) profile_available_hook_callback, u);
475
476    handle_all_profiles(m->core);
477
478    pa_modargs_free(ma);
479    return 0;
480
481fail:
482    if (ma)
483        pa_modargs_free(ma);
484    return -1;
485}
486
487void pa__done(pa_module *m) {
488    struct userdata *u;
489
490    pa_assert(m);
491
492    if (!(u = m->userdata))
493        return;
494
495    if (u->source_put_slot)
496        pa_hook_slot_free(u->source_put_slot);
497
498    if (u->sink_put_slot)
499        pa_hook_slot_free(u->sink_put_slot);
500
501    if (u->source_output_put_slot)
502        pa_hook_slot_free(u->source_output_put_slot);
503
504    if (u->source_output_unlink_slot)
505        pa_hook_slot_free(u->source_output_unlink_slot);
506
507    if (u->card_init_profile_slot)
508        pa_hook_slot_free(u->card_init_profile_slot);
509
510    if (u->card_unlink_slot)
511        pa_hook_slot_free(u->card_unlink_slot);
512
513    if (u->profile_available_changed_slot)
514        pa_hook_slot_free(u->profile_available_changed_slot);
515
516    pa_hashmap_free(u->will_need_revert_card_map);
517
518    pa_xfree(u);
519}
520