2 This file is part of PulseAudio.
4 Copyright 2006 Lennart Poettering
5 Copyright 2009 Canonical Ltd
6 Copyright (C) 2012 Intel Corporation
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.
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.
18 You should have received a copy of the GNU Lesser General Public License
19 along with PulseAudio; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
28 #include <pulse/xmalloc.h>
30 #include <pulsecore/core.h>
31 #include <pulsecore/modargs.h>
32 #include <pulsecore/source-output.h>
33 #include <pulsecore/source.h>
34 #include <pulsecore/core-util.h>
36 #include "module-bluetooth-policy-symdef.h"
38 PA_MODULE_AUTHOR("Frédéric Dalleau");
39 PA_MODULE_DESCRIPTION("When a bluetooth sink or source is added, load module-loopback");
40 PA_MODULE_VERSION(PACKAGE_VERSION);
41 PA_MODULE_LOAD_ONCE(true);
43 "a2dp_source=<Handle a2dp_source card profile (sink role)?> "
44 "hfgw=<Handle hfgw card profile (headset role)?>");
46 static const char* const valid_modargs[] = {
53 bool enable_a2dp_source;
55 pa_hook_slot *source_put_slot;
56 pa_hook_slot *sink_put_slot;
57 pa_hook_slot *profile_available_changed_slot;
60 /* When a source is created, loopback it to default sink */
61 static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void *userdata) {
62 struct userdata *u = userdata;
70 /* Only consider bluetooth sinks and sources */
71 s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_BUS);
75 if (!pa_streq(s, "bluetooth"))
78 s = pa_proplist_gets(source->proplist, "bluetooth.protocol");
82 if (u->enable_a2dp_source && pa_streq(s, "a2dp_source")) /* A2DP profile (we're doing sink role) */
88 else if (u->enable_hfgw && pa_streq(s, "hfgw")) /* HFP profile (we're doing headset role) */
91 pa_log_debug("Profile %s cannot be selected for loopback", s);
95 /* Load module-loopback */
96 args = pa_sprintf_malloc("source=\"%s\" source_dont_move=\"true\" sink_input_properties=\"media.role=%s\" adjust_time=0", source->name, role);
97 (void) pa_module_load(c, "module-loopback", args);
103 /* When a sink is created, loopback it to default source */
104 static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *userdata) {
105 struct userdata *u = userdata;
113 /* Only consider bluetooth sinks and sources */
114 s = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_BUS);
118 if (!pa_streq(s, "bluetooth"))
121 s = pa_proplist_gets(sink->proplist, "bluetooth.protocol");
125 if (u->enable_hfgw && pa_streq(s, "hfgw")) /* HFP profile (we're doing headset role) */
128 pa_log_debug("Profile %s cannot be selected for loopback", s);
132 /* Load module-loopback */
133 args = pa_sprintf_malloc("sink=\"%s\" sink_dont_move=\"true\" source_output_properties=\"media.role=%s\" adjust_time=0", sink->name, role);
134 (void) pa_module_load(c, "module-loopback", args);
140 static pa_card_profile *find_best_profile(pa_card *card) {
142 pa_card_profile *profile;
143 pa_card_profile *result = card->active_profile;
144 pa_card_profile *off;
146 pa_assert_se(off = pa_hashmap_get(card->profiles, "off"));
148 PA_HASHMAP_FOREACH(profile, card->profiles, state) {
149 if (profile->available == PA_AVAILABLE_NO || profile == off)
152 if (result == NULL ||
153 (profile->available == PA_AVAILABLE_YES && result->available == PA_AVAILABLE_UNKNOWN) ||
154 (profile->available == result->available && profile->priority > result->priority))
158 return result ? result : off;
161 static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_profile *profile, void *userdata) {
164 bool is_active_profile;
165 pa_card_profile *selected_profile;
169 pa_assert_se((card = profile->card));
171 /* Only consider bluetooth cards */
172 s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS);
173 if (!s || !pa_streq(s, "bluetooth"))
176 /* Do not automatically switch profiles for headsets, just in case */
177 if (pa_streq(profile->name, "hsp") || pa_streq(profile->name, "a2dp_sink"))
180 is_active_profile = card->active_profile == profile;
182 if (profile->available == PA_AVAILABLE_YES) {
183 if (is_active_profile)
186 if (card->active_profile->available == PA_AVAILABLE_YES && card->active_profile->priority >= profile->priority)
189 selected_profile = profile;
191 if (!is_active_profile)
194 pa_assert_se((selected_profile = find_best_profile(card)));
196 if (selected_profile == card->active_profile)
200 pa_log_debug("Setting card '%s' to profile '%s'", card->name, selected_profile->name);
202 if (pa_card_set_profile(card, selected_profile, false) != 0)
203 pa_log_warn("Could not set profile '%s'", selected_profile->name);
208 static void handle_all_profiles(pa_core *core) {
212 PA_IDXSET_FOREACH(card, core->cards, state) {
213 pa_card_profile *profile;
216 PA_HASHMAP_FOREACH(profile, card->profiles, state2)
217 profile_available_hook_callback(core, profile, NULL);
221 int pa__init(pa_module *m) {
227 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
228 pa_log_error("Failed to parse module arguments");
232 m->userdata = u = pa_xnew0(struct userdata, 1);
234 u->enable_a2dp_source = true;
235 if (pa_modargs_get_value_boolean(ma, "a2dp_source", &u->enable_a2dp_source) < 0) {
236 pa_log("Failed to parse a2dp_source argument.");
240 u->enable_hfgw = true;
241 if (pa_modargs_get_value_boolean(ma, "hfgw", &u->enable_hfgw) < 0) {
242 pa_log("Failed to parse hfgw argument.");
246 u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) source_put_hook_callback, u);
248 u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_put_hook_callback, u);
250 u->profile_available_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED],
251 PA_HOOK_NORMAL, (pa_hook_cb_t) profile_available_hook_callback, u);
253 handle_all_profiles(m->core);
263 void pa__done(pa_module *m) {
268 if (!(u = m->userdata))
271 if (u->source_put_slot)
272 pa_hook_slot_free(u->source_put_slot);
274 if (u->sink_put_slot)
275 pa_hook_slot_free(u->sink_put_slot);
277 if (u->profile_available_changed_slot)
278 pa_hook_slot_free(u->profile_available_changed_slot);