2 This file is part of PulseAudio.
4 Copyright 2006 Lennart Poettering
5 Copyright 2011 Canonical Ltd
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
27 #include <pulsecore/core.h>
28 #include <pulsecore/device-port.h>
29 #include <pulsecore/hashmap.h>
31 #include "module-switch-on-port-available-symdef.h"
34 pa_hook_slot
*available_slot
;
35 pa_hook_slot
*sink_new_slot
;
36 pa_hook_slot
*source_new_slot
;
39 static pa_device_port
* find_best_port(pa_hashmap
*ports
) {
41 pa_device_port
* port
, *result
= NULL
;
43 PA_HASHMAP_FOREACH(port
, ports
, state
) {
45 result
->available
== PA_AVAILABLE_NO
||
46 (port
->available
!= PA_AVAILABLE_NO
&& port
->priority
> result
->priority
)) {
54 static bool profile_good_for_output(pa_card_profile
*profile
) {
60 if (profile
->card
->active_profile
->n_sources
!= profile
->n_sources
)
63 if (profile
->card
->active_profile
->max_source_channels
!= profile
->max_source_channels
)
66 /* Try not to switch to HDMI sinks from analog when HDMI is becoming available */
67 PA_IDXSET_FOREACH(sink
, profile
->card
->sinks
, idx
) {
68 if (!sink
->active_port
)
71 if (sink
->active_port
->available
!= PA_AVAILABLE_NO
)
78 static bool profile_good_for_input(pa_card_profile
*profile
) {
81 if (profile
->card
->active_profile
->n_sinks
!= profile
->n_sinks
)
84 if (profile
->card
->active_profile
->max_sink_channels
!= profile
->max_sink_channels
)
90 static pa_bool_t
try_to_switch_profile(pa_card
*card
, pa_device_port
*port
) {
91 pa_card_profile
*best_profile
= NULL
, *profile
;
94 pa_log_debug("Finding best profile");
96 PA_HASHMAP_FOREACH(profile
, port
->profiles
, state
) {
97 pa_direction_t direction
= port
->is_output
? PA_DIRECTION_OUTPUT
: PA_DIRECTION_INPUT
;
100 if (best_profile
&& best_profile
->priority
>= profile
->priority
)
103 /* We make a best effort to keep other direction unchanged */
105 case PA_DIRECTION_OUTPUT
:
106 good
= profile_good_for_output(profile
);
109 case PA_DIRECTION_INPUT
:
110 good
= profile_good_for_input(profile
);
117 best_profile
= profile
;
121 pa_log_debug("No suitable profile found");
125 if (pa_card_set_profile(card
, best_profile
->name
, FALSE
) != 0) {
126 pa_log_debug("Could not set profile %s", best_profile
->name
);
133 static void find_sink_and_source(pa_card
*card
, pa_device_port
*port
, pa_sink
**si
, pa_source
**so
)
135 pa_sink
*sink
= NULL
;
136 pa_source
*source
= NULL
;
140 PA_IDXSET_FOREACH(sink
, card
->sinks
, state
)
141 if (port
== pa_hashmap_get(sink
->ports
, port
->name
))
145 PA_IDXSET_FOREACH(source
, card
->sources
, state
)
146 if (port
== pa_hashmap_get(source
->ports
, port
->name
))
153 static pa_hook_result_t
port_available_hook_callback(pa_core
*c
, pa_device_port
*port
, void* userdata
) {
158 pa_bool_t is_active_profile
, is_active_port
;
160 if (port
->available
== PA_AVAILABLE_UNKNOWN
)
163 pa_log_debug("finding port %s", port
->name
);
165 PA_IDXSET_FOREACH(card
, c
->cards
, state
)
166 if (port
== pa_hashmap_get(card
->ports
, port
->name
))
170 pa_log_warn("Did not find port %s in array of cards", port
->name
);
174 find_sink_and_source(card
, port
, &sink
, &source
);
176 is_active_profile
= card
->active_profile
== pa_hashmap_get(port
->profiles
, card
->active_profile
->name
);
177 is_active_port
= (sink
&& sink
->active_port
== port
) || (source
&& source
->active_port
== port
);
179 if (port
->available
== PA_AVAILABLE_NO
&& !is_active_port
)
182 if (port
->available
== PA_AVAILABLE_YES
) {
186 if (!is_active_profile
) {
187 if (!try_to_switch_profile(card
, port
))
190 pa_assert(card
->active_profile
== pa_hashmap_get(port
->profiles
, card
->active_profile
->name
));
192 /* Now that profile has changed, our sink and source pointers must be updated */
193 find_sink_and_source(card
, port
, &sink
, &source
);
197 pa_source_set_port(source
, port
->name
, FALSE
);
199 pa_sink_set_port(sink
, port
->name
, FALSE
);
202 if (port
->available
== PA_AVAILABLE_NO
) {
204 pa_device_port
*p2
= find_best_port(sink
->ports
);
206 if (p2
&& p2
->available
!= PA_AVAILABLE_NO
)
207 pa_sink_set_port(sink
, p2
->name
, FALSE
);
209 /* Maybe try to switch to another profile? */
214 pa_device_port
*p2
= find_best_port(source
->ports
);
216 if (p2
&& p2
->available
!= PA_AVAILABLE_NO
)
217 pa_source_set_port(source
, p2
->name
, FALSE
);
219 /* Maybe try to switch to another profile? */
227 static void handle_all_unavailable(pa_core
*core
) {
231 PA_IDXSET_FOREACH(card
, core
->cards
, state
) {
232 pa_device_port
*port
;
235 PA_HASHMAP_FOREACH(port
, card
->ports
, state2
) {
236 if (port
->available
== PA_AVAILABLE_NO
)
237 port_available_hook_callback(core
, port
, NULL
);
242 static pa_device_port
*new_sink_source(pa_hashmap
*ports
, const char *name
) {
245 pa_device_port
*i
, *p
= NULL
;
250 p
= pa_hashmap_get(ports
, name
);
252 PA_HASHMAP_FOREACH(i
, ports
, state
)
253 if (!p
|| i
->priority
> p
->priority
)
257 if (p
->available
!= PA_AVAILABLE_NO
)
260 pa_assert_se(p
= find_best_port(ports
));
264 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
266 pa_device_port
*p
= new_sink_source(new_data
->ports
, new_data
->active_port
);
269 pa_log_debug("Switching initial port for sink '%s' to '%s'", new_data
->name
, p
->name
);
270 pa_sink_new_data_set_port(new_data
, p
->name
);
275 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
277 pa_device_port
*p
= new_sink_source(new_data
->ports
, new_data
->active_port
);
280 pa_log_debug("Switching initial port for source '%s' to '%s'", new_data
->name
,
281 new_data
->active_port
);
282 pa_source_new_data_set_port(new_data
, p
->name
);
288 int pa__init(pa_module
*m
) {
293 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
295 /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */
296 u
->sink_new_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SINK_NEW
],
297 PA_HOOK_NORMAL
, (pa_hook_cb_t
) sink_new_hook_callback
, u
);
298 u
->source_new_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SOURCE_NEW
],
299 PA_HOOK_NORMAL
, (pa_hook_cb_t
) source_new_hook_callback
, u
);
300 u
->available_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED
],
301 PA_HOOK_LATE
, (pa_hook_cb_t
) port_available_hook_callback
, u
);
303 handle_all_unavailable(m
->core
);
308 void pa__done(pa_module
*m
) {
313 if (!(u
= m
->userdata
))
316 if (u
->available_slot
)
317 pa_hook_slot_free(u
->available_slot
);
318 if (u
->sink_new_slot
)
319 pa_hook_slot_free(u
->sink_new_slot
);
320 if (u
->source_new_slot
)
321 pa_hook_slot_free(u
->source_new_slot
);