]> code.delx.au - pulseaudio/blob - src/modules/module-switch-on-port-available.c
8c49db85eb37fc592a3e501bbeaa217164967a58
[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->name, 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 pa_bool_t 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 find_sink_and_source(card, port, &sink, &source);
177
178 is_active_profile = card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name);
179 is_active_port = (sink && sink->active_port == port) || (source && source->active_port == port);
180
181 if (port->available == PA_AVAILABLE_NO && !is_active_port)
182 return PA_HOOK_OK;
183
184 if (port->available == PA_AVAILABLE_YES) {
185 if (is_active_port)
186 return PA_HOOK_OK;
187
188 if (!is_active_profile) {
189 if (try_to_switch_profile(port) < 0)
190 return PA_HOOK_OK;
191
192 pa_assert(card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name));
193
194 /* Now that profile has changed, our sink and source pointers must be updated */
195 find_sink_and_source(card, port, &sink, &source);
196 }
197
198 if (source)
199 pa_source_set_port(source, port->name, FALSE);
200 if (sink)
201 pa_sink_set_port(sink, port->name, FALSE);
202 }
203
204 if (port->available == PA_AVAILABLE_NO) {
205 if (sink) {
206 pa_device_port *p2 = find_best_port(sink->ports);
207
208 if (p2 && p2->available != PA_AVAILABLE_NO)
209 pa_sink_set_port(sink, p2->name, FALSE);
210 else {
211 /* Maybe try to switch to another profile? */
212 }
213 }
214
215 if (source) {
216 pa_device_port *p2 = find_best_port(source->ports);
217
218 if (p2 && p2->available != PA_AVAILABLE_NO)
219 pa_source_set_port(source, p2->name, FALSE);
220 else {
221 /* Maybe try to switch to another profile? */
222 }
223 }
224 }
225
226 return PA_HOOK_OK;
227 }
228
229 static void handle_all_unavailable(pa_core *core) {
230 pa_card *card;
231 uint32_t state;
232
233 PA_IDXSET_FOREACH(card, core->cards, state) {
234 pa_device_port *port;
235 void *state2;
236
237 PA_HASHMAP_FOREACH(port, card->ports, state2) {
238 if (port->available == PA_AVAILABLE_NO)
239 port_available_hook_callback(core, port, NULL);
240 }
241 }
242 }
243
244 static pa_device_port *new_sink_source(pa_hashmap *ports, const char *name) {
245
246 void *state;
247 pa_device_port *i, *p = NULL;
248
249 if (!ports)
250 return NULL;
251 if (name)
252 p = pa_hashmap_get(ports, name);
253 if (!p)
254 PA_HASHMAP_FOREACH(i, ports, state)
255 if (!p || i->priority > p->priority)
256 p = i;
257 if (!p)
258 return NULL;
259 if (p->available != PA_AVAILABLE_NO)
260 return NULL;
261
262 pa_assert_se(p = find_best_port(ports));
263 return p;
264 }
265
266 static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
267
268 pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port);
269
270 if (p) {
271 pa_log_debug("Switching initial port for sink '%s' to '%s'", new_data->name, p->name);
272 pa_sink_new_data_set_port(new_data, p->name);
273 }
274 return PA_HOOK_OK;
275 }
276
277 static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
278
279 pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port);
280
281 if (p) {
282 pa_log_debug("Switching initial port for source '%s' to '%s'", new_data->name,
283 new_data->active_port);
284 pa_source_new_data_set_port(new_data, p->name);
285 }
286 return PA_HOOK_OK;
287 }
288
289
290 int pa__init(pa_module*m) {
291 struct userdata *u;
292
293 pa_assert(m);
294
295 m->userdata = u = pa_xnew(struct userdata, 1);
296
297 /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */
298 u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW],
299 PA_HOOK_NORMAL, (pa_hook_cb_t) sink_new_hook_callback, u);
300 u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW],
301 PA_HOOK_NORMAL, (pa_hook_cb_t) source_new_hook_callback, u);
302 u->available_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED],
303 PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, u);
304
305 handle_all_unavailable(m->core);
306
307 return 0;
308 }
309
310 void pa__done(pa_module*m) {
311 struct userdata *u;
312
313 pa_assert(m);
314
315 if (!(u = m->userdata))
316 return;
317
318 if (u->available_slot)
319 pa_hook_slot_free(u->available_slot);
320 if (u->sink_new_slot)
321 pa_hook_slot_free(u->sink_new_slot);
322 if (u->source_new_slot)
323 pa_hook_slot_free(u->source_new_slot);
324
325 pa_xfree(u);
326 }