]> code.delx.au - pulseaudio/blob - src/modules/module-switch-on-port-available.c
switch-on-port-available: Prepare for dual-direction ports going away
[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 pa_bool_t try_to_switch_profile(pa_card *card, 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 pa_direction_t direction = port->is_output ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT;
98 bool good;
99
100 if (best_profile && best_profile->priority >= profile->priority)
101 continue;
102
103 /* We make a best effort to keep other direction unchanged */
104 switch (direction) {
105 case PA_DIRECTION_OUTPUT:
106 good = profile_good_for_output(profile);
107 break;
108
109 case PA_DIRECTION_INPUT:
110 good = profile_good_for_input(profile);
111 break;
112 }
113
114 if (!good)
115 continue;
116
117 best_profile = profile;
118 }
119
120 if (!best_profile) {
121 pa_log_debug("No suitable profile found");
122 return FALSE;
123 }
124
125 if (pa_card_set_profile(card, best_profile->name, FALSE) != 0) {
126 pa_log_debug("Could not set profile %s", best_profile->name);
127 return FALSE;
128 }
129
130 return TRUE;
131 }
132
133 static void find_sink_and_source(pa_card *card, pa_device_port *port, pa_sink **si, pa_source **so)
134 {
135 pa_sink *sink = NULL;
136 pa_source *source = NULL;
137 uint32_t state;
138
139 if (port->is_output)
140 PA_IDXSET_FOREACH(sink, card->sinks, state)
141 if (port == pa_hashmap_get(sink->ports, port->name))
142 break;
143
144 if (port->is_input)
145 PA_IDXSET_FOREACH(source, card->sources, state)
146 if (port == pa_hashmap_get(source->ports, port->name))
147 break;
148
149 *si = sink;
150 *so = source;
151 }
152
153 static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) {
154 uint32_t state;
155 pa_card* card;
156 pa_sink *sink;
157 pa_source *source;
158 pa_bool_t is_active_profile, is_active_port;
159
160 if (port->available == PA_AVAILABLE_UNKNOWN)
161 return PA_HOOK_OK;
162
163 pa_log_debug("finding port %s", port->name);
164
165 PA_IDXSET_FOREACH(card, c->cards, state)
166 if (port == pa_hashmap_get(card->ports, port->name))
167 break;
168
169 if (!card) {
170 pa_log_warn("Did not find port %s in array of cards", port->name);
171 return PA_HOOK_OK;
172 }
173
174 find_sink_and_source(card, port, &sink, &source);
175
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);
178
179 if (port->available == PA_AVAILABLE_NO && !is_active_port)
180 return PA_HOOK_OK;
181
182 if (port->available == PA_AVAILABLE_YES) {
183 if (is_active_port)
184 return PA_HOOK_OK;
185
186 if (!is_active_profile) {
187 if (!try_to_switch_profile(card, port))
188 return PA_HOOK_OK;
189
190 pa_assert(card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name));
191
192 /* Now that profile has changed, our sink and source pointers must be updated */
193 find_sink_and_source(card, port, &sink, &source);
194 }
195
196 if (source)
197 pa_source_set_port(source, port->name, FALSE);
198 if (sink)
199 pa_sink_set_port(sink, port->name, FALSE);
200 }
201
202 if (port->available == PA_AVAILABLE_NO) {
203 if (sink) {
204 pa_device_port *p2 = find_best_port(sink->ports);
205
206 if (p2 && p2->available != PA_AVAILABLE_NO)
207 pa_sink_set_port(sink, p2->name, FALSE);
208 else {
209 /* Maybe try to switch to another profile? */
210 }
211 }
212
213 if (source) {
214 pa_device_port *p2 = find_best_port(source->ports);
215
216 if (p2 && p2->available != PA_AVAILABLE_NO)
217 pa_source_set_port(source, p2->name, FALSE);
218 else {
219 /* Maybe try to switch to another profile? */
220 }
221 }
222 }
223
224 return PA_HOOK_OK;
225 }
226
227 static void handle_all_unavailable(pa_core *core) {
228 pa_card *card;
229 uint32_t state;
230
231 PA_IDXSET_FOREACH(card, core->cards, state) {
232 pa_device_port *port;
233 void *state2;
234
235 PA_HASHMAP_FOREACH(port, card->ports, state2) {
236 if (port->available == PA_AVAILABLE_NO)
237 port_available_hook_callback(core, port, NULL);
238 }
239 }
240 }
241
242 static pa_device_port *new_sink_source(pa_hashmap *ports, const char *name) {
243
244 void *state;
245 pa_device_port *i, *p = NULL;
246
247 if (!ports)
248 return NULL;
249 if (name)
250 p = pa_hashmap_get(ports, name);
251 if (!p)
252 PA_HASHMAP_FOREACH(i, ports, state)
253 if (!p || i->priority > p->priority)
254 p = i;
255 if (!p)
256 return NULL;
257 if (p->available != PA_AVAILABLE_NO)
258 return NULL;
259
260 pa_assert_se(p = find_best_port(ports));
261 return p;
262 }
263
264 static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
265
266 pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port);
267
268 if (p) {
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);
271 }
272 return PA_HOOK_OK;
273 }
274
275 static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
276
277 pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port);
278
279 if (p) {
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);
283 }
284 return PA_HOOK_OK;
285 }
286
287
288 int pa__init(pa_module*m) {
289 struct userdata *u;
290
291 pa_assert(m);
292
293 m->userdata = u = pa_xnew(struct userdata, 1);
294
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);
302
303 handle_all_unavailable(m->core);
304
305 return 0;
306 }
307
308 void pa__done(pa_module*m) {
309 struct userdata *u;
310
311 pa_assert(m);
312
313 if (!(u = m->userdata))
314 return;
315
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);
322
323 pa_xfree(u);
324 }