]> code.delx.au - pulseaudio/blob - src/modules/module-switch-on-port-available.c
module-switch-on-port-available: Don't switch profiles on uninitialized cards
[pulseaudio] / src / modules / module-switch-on-port-available.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2006 Lennart Poettering
5 Copyright 2011 Canonical Ltd
6
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.
11
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.
16
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
20 USA.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <pulsecore/core.h>
28 #include <pulsecore/device-port.h>
29 #include <pulsecore/hashmap.h>
30
31 #include "module-switch-on-port-available-symdef.h"
32
33 struct userdata {
34 pa_hook_slot *available_slot;
35 pa_hook_slot *sink_new_slot;
36 pa_hook_slot *source_new_slot;
37 };
38
39 static pa_device_port* find_best_port(pa_hashmap *ports) {
40 void *state;
41 pa_device_port* port, *result = NULL;
42
43 PA_HASHMAP_FOREACH(port, ports, state) {
44 if (result == NULL ||
45 result->available == PA_AVAILABLE_NO ||
46 (port->available != PA_AVAILABLE_NO && port->priority > result->priority)) {
47 result = port;
48 }
49 }
50
51 return result;
52 }
53
54 static bool profile_good_for_output(pa_card_profile *profile) {
55 pa_sink *sink;
56 uint32_t idx;
57
58 pa_assert(profile);
59
60 if (profile->card->active_profile->n_sources != profile->n_sources)
61 return false;
62
63 if (profile->card->active_profile->max_source_channels != profile->max_source_channels)
64 return false;
65
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)
69 continue;
70
71 if (sink->active_port->available != PA_AVAILABLE_NO)
72 return false;
73 }
74
75 return true;
76 }
77
78 static bool profile_good_for_input(pa_card_profile *profile) {
79 pa_assert(profile);
80
81 if (profile->card->active_profile->n_sinks != profile->n_sinks)
82 return false;
83
84 if (profile->card->active_profile->max_sink_channels != profile->max_sink_channels)
85 return false;
86
87 return true;
88 }
89
90 static int try_to_switch_profile(pa_device_port *port) {
91 pa_card_profile *best_profile = NULL, *profile;
92 void *state;
93
94 pa_log_debug("Finding best profile");
95
96 PA_HASHMAP_FOREACH(profile, port->profiles, state) {
97 bool good = false;
98
99 if (best_profile && best_profile->priority >= profile->priority)
100 continue;
101
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);
106 break;
107
108 case PA_DIRECTION_INPUT:
109 good = profile_good_for_input(profile);
110 break;
111 }
112
113 if (!good)
114 continue;
115
116 best_profile = profile;
117 }
118
119 if (!best_profile) {
120 pa_log_debug("No suitable profile found");
121 return -1;
122 }
123
124 if (pa_card_set_profile(port->card, best_profile, false) != 0) {
125 pa_log_debug("Could not set profile %s", best_profile->name);
126 return -1;
127 }
128
129 return 0;
130 }
131
132 static void find_sink_and_source(pa_card *card, pa_device_port *port, pa_sink **si, pa_source **so) {
133 pa_sink *sink = NULL;
134 pa_source *source = NULL;
135 uint32_t state;
136
137 switch (port->direction) {
138 case PA_DIRECTION_OUTPUT:
139 PA_IDXSET_FOREACH(sink, card->sinks, state)
140 if (port == pa_hashmap_get(sink->ports, port->name))
141 break;
142 break;
143
144 case PA_DIRECTION_INPUT:
145 PA_IDXSET_FOREACH(source, card->sources, state)
146 if (port == pa_hashmap_get(source->ports, port->name))
147 break;
148 break;
149 }
150
151 *si = sink;
152 *so = source;
153 }
154
155 static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) {
156 uint32_t state;
157 pa_card* card;
158 pa_sink *sink;
159 pa_source *source;
160 bool is_active_profile, is_active_port;
161
162 if (port->available == PA_AVAILABLE_UNKNOWN)
163 return PA_HOOK_OK;
164
165 pa_log_debug("finding port %s", port->name);
166
167 PA_IDXSET_FOREACH(card, c->cards, state)
168 if (port == pa_hashmap_get(card->ports, port->name))
169 break;
170
171 if (!card) {
172 pa_log_warn("Did not find port %s in array of cards", port->name);
173 return PA_HOOK_OK;
174 }
175
176 if (pa_idxset_size(card->sinks) == 0 && pa_idxset_size(card->sources) == 0)
177 /* This card is not initialized yet. We'll handle it in
178 sink_new / source_new callbacks later. */
179 return PA_HOOK_OK;
180
181 find_sink_and_source(card, port, &sink, &source);
182
183 is_active_profile = card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name);
184 is_active_port = (sink && sink->active_port == port) || (source && source->active_port == port);
185
186 if (port->available == PA_AVAILABLE_NO && !is_active_port)
187 return PA_HOOK_OK;
188
189 if (port->available == PA_AVAILABLE_YES) {
190 if (is_active_port)
191 return PA_HOOK_OK;
192
193 if (!is_active_profile) {
194 if (try_to_switch_profile(port) < 0)
195 return PA_HOOK_OK;
196
197 pa_assert(card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name));
198
199 /* Now that profile has changed, our sink and source pointers must be updated */
200 find_sink_and_source(card, port, &sink, &source);
201 }
202
203 if (source)
204 pa_source_set_port(source, port->name, false);
205 if (sink)
206 pa_sink_set_port(sink, port->name, false);
207 }
208
209 if (port->available == PA_AVAILABLE_NO) {
210 if (sink) {
211 pa_device_port *p2 = find_best_port(sink->ports);
212
213 if (p2 && p2->available != PA_AVAILABLE_NO)
214 pa_sink_set_port(sink, p2->name, false);
215 else {
216 /* Maybe try to switch to another profile? */
217 }
218 }
219
220 if (source) {
221 pa_device_port *p2 = find_best_port(source->ports);
222
223 if (p2 && p2->available != PA_AVAILABLE_NO)
224 pa_source_set_port(source, p2->name, false);
225 else {
226 /* Maybe try to switch to another profile? */
227 }
228 }
229 }
230
231 return PA_HOOK_OK;
232 }
233
234 static void handle_all_unavailable(pa_core *core) {
235 pa_card *card;
236 uint32_t state;
237
238 PA_IDXSET_FOREACH(card, core->cards, state) {
239 pa_device_port *port;
240 void *state2;
241
242 PA_HASHMAP_FOREACH(port, card->ports, state2) {
243 if (port->available == PA_AVAILABLE_NO)
244 port_available_hook_callback(core, port, NULL);
245 }
246 }
247 }
248
249 static pa_device_port *new_sink_source(pa_hashmap *ports, const char *name) {
250
251 void *state;
252 pa_device_port *i, *p = NULL;
253
254 if (!ports)
255 return NULL;
256 if (name)
257 p = pa_hashmap_get(ports, name);
258 if (!p)
259 PA_HASHMAP_FOREACH(i, ports, state)
260 if (!p || i->priority > p->priority)
261 p = i;
262 if (!p)
263 return NULL;
264 if (p->available != PA_AVAILABLE_NO)
265 return NULL;
266
267 pa_assert_se(p = find_best_port(ports));
268 return p;
269 }
270
271 static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
272
273 pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port);
274
275 if (p) {
276 pa_log_debug("Switching initial port for sink '%s' to '%s'", new_data->name, p->name);
277 pa_sink_new_data_set_port(new_data, p->name);
278 }
279 return PA_HOOK_OK;
280 }
281
282 static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
283
284 pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port);
285
286 if (p) {
287 pa_log_debug("Switching initial port for source '%s' to '%s'", new_data->name,
288 new_data->active_port);
289 pa_source_new_data_set_port(new_data, p->name);
290 }
291 return PA_HOOK_OK;
292 }
293
294 int pa__init(pa_module*m) {
295 struct userdata *u;
296
297 pa_assert(m);
298
299 m->userdata = u = pa_xnew(struct userdata, 1);
300
301 /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */
302 u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW],
303 PA_HOOK_NORMAL, (pa_hook_cb_t) sink_new_hook_callback, u);
304 u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW],
305 PA_HOOK_NORMAL, (pa_hook_cb_t) source_new_hook_callback, u);
306 u->available_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED],
307 PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, u);
308
309 handle_all_unavailable(m->core);
310
311 return 0;
312 }
313
314 void pa__done(pa_module*m) {
315 struct userdata *u;
316
317 pa_assert(m);
318
319 if (!(u = m->userdata))
320 return;
321
322 if (u->available_slot)
323 pa_hook_slot_free(u->available_slot);
324 if (u->sink_new_slot)
325 pa_hook_slot_free(u->sink_new_slot);
326 if (u->source_new_slot)
327 pa_hook_slot_free(u->source_new_slot);
328
329 pa_xfree(u);
330 }