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 int try_to_switch_profile(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
) {
99 if (best_profile
&& best_profile
->priority
>= profile
->priority
)
102 /* We make a best effort to keep other direction unchanged */
103 switch (port
->direction
) {
104 case PA_DIRECTION_OUTPUT
:
105 good
= profile_good_for_output(profile
);
108 case PA_DIRECTION_INPUT
:
109 good
= profile_good_for_input(profile
);
116 best_profile
= profile
;
120 pa_log_debug("No suitable profile found");
124 if (pa_card_set_profile(port
->card
, best_profile
->name
, FALSE
) != 0) {
125 pa_log_debug("Could not set profile %s", best_profile
->name
);
132 static void find_sink_and_source(pa_card
*card
, pa_device_port
*port
, pa_sink
**si
, pa_source
**so
)
134 pa_sink
*sink
= NULL
;
135 pa_source
*source
= NULL
;
138 switch (port
->direction
) {
139 case PA_DIRECTION_OUTPUT
:
140 PA_IDXSET_FOREACH(sink
, card
->sinks
, state
)
141 if (port
== pa_hashmap_get(sink
->ports
, port
->name
))
145 case PA_DIRECTION_INPUT
:
146 PA_IDXSET_FOREACH(source
, card
->sources
, state
)
147 if (port
== pa_hashmap_get(source
->ports
, port
->name
))
156 static pa_hook_result_t
port_available_hook_callback(pa_core
*c
, pa_device_port
*port
, void* userdata
) {
161 pa_bool_t is_active_profile
, is_active_port
;
163 if (port
->available
== PA_AVAILABLE_UNKNOWN
)
166 pa_log_debug("finding port %s", port
->name
);
168 PA_IDXSET_FOREACH(card
, c
->cards
, state
)
169 if (port
== pa_hashmap_get(card
->ports
, port
->name
))
173 pa_log_warn("Did not find port %s in array of cards", port
->name
);
177 find_sink_and_source(card
, port
, &sink
, &source
);
179 is_active_profile
= card
->active_profile
== pa_hashmap_get(port
->profiles
, card
->active_profile
->name
);
180 is_active_port
= (sink
&& sink
->active_port
== port
) || (source
&& source
->active_port
== port
);
182 if (port
->available
== PA_AVAILABLE_NO
&& !is_active_port
)
185 if (port
->available
== PA_AVAILABLE_YES
) {
189 if (!is_active_profile
) {
190 if (try_to_switch_profile(port
) < 0)
193 pa_assert(card
->active_profile
== pa_hashmap_get(port
->profiles
, card
->active_profile
->name
));
195 /* Now that profile has changed, our sink and source pointers must be updated */
196 find_sink_and_source(card
, port
, &sink
, &source
);
200 pa_source_set_port(source
, port
->name
, FALSE
);
202 pa_sink_set_port(sink
, port
->name
, FALSE
);
205 if (port
->available
== PA_AVAILABLE_NO
) {
207 pa_device_port
*p2
= find_best_port(sink
->ports
);
209 if (p2
&& p2
->available
!= PA_AVAILABLE_NO
)
210 pa_sink_set_port(sink
, p2
->name
, FALSE
);
212 /* Maybe try to switch to another profile? */
217 pa_device_port
*p2
= find_best_port(source
->ports
);
219 if (p2
&& p2
->available
!= PA_AVAILABLE_NO
)
220 pa_source_set_port(source
, p2
->name
, FALSE
);
222 /* Maybe try to switch to another profile? */
230 static void handle_all_unavailable(pa_core
*core
) {
234 PA_IDXSET_FOREACH(card
, core
->cards
, state
) {
235 pa_device_port
*port
;
238 PA_HASHMAP_FOREACH(port
, card
->ports
, state2
) {
239 if (port
->available
== PA_AVAILABLE_NO
)
240 port_available_hook_callback(core
, port
, NULL
);
245 static pa_device_port
*new_sink_source(pa_hashmap
*ports
, const char *name
) {
248 pa_device_port
*i
, *p
= NULL
;
253 p
= pa_hashmap_get(ports
, name
);
255 PA_HASHMAP_FOREACH(i
, ports
, state
)
256 if (!p
|| i
->priority
> p
->priority
)
260 if (p
->available
!= PA_AVAILABLE_NO
)
263 pa_assert_se(p
= find_best_port(ports
));
267 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
269 pa_device_port
*p
= new_sink_source(new_data
->ports
, new_data
->active_port
);
272 pa_log_debug("Switching initial port for sink '%s' to '%s'", new_data
->name
, p
->name
);
273 pa_sink_new_data_set_port(new_data
, p
->name
);
278 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
280 pa_device_port
*p
= new_sink_source(new_data
->ports
, new_data
->active_port
);
283 pa_log_debug("Switching initial port for source '%s' to '%s'", new_data
->name
,
284 new_data
->active_port
);
285 pa_source_new_data_set_port(new_data
, p
->name
);
291 int pa__init(pa_module
*m
) {
296 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
298 /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */
299 u
->sink_new_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SINK_NEW
],
300 PA_HOOK_NORMAL
, (pa_hook_cb_t
) sink_new_hook_callback
, u
);
301 u
->source_new_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SOURCE_NEW
],
302 PA_HOOK_NORMAL
, (pa_hook_cb_t
) source_new_hook_callback
, u
);
303 u
->available_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED
],
304 PA_HOOK_LATE
, (pa_hook_cb_t
) port_available_hook_callback
, u
);
306 handle_all_unavailable(m
->core
);
311 void pa__done(pa_module
*m
) {
316 if (!(u
= m
->userdata
))
319 if (u
->available_slot
)
320 pa_hook_slot_free(u
->available_slot
);
321 if (u
->sink_new_slot
)
322 pa_hook_slot_free(u
->sink_new_slot
);
323 if (u
->source_new_slot
)
324 pa_hook_slot_free(u
->source_new_slot
);