]> code.delx.au - pulseaudio/blob - src/modules/module-switch-on-port-available.c
module-switch-on-port-available: Use port->card pointer
[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 pa_card* card;
157 pa_sink *sink;
158 pa_source *source;
159 bool is_active_profile, is_active_port;
160
161 if (port->available == PA_AVAILABLE_UNKNOWN)
162 return PA_HOOK_OK;
163
164 card = port->card;
165
166 if (!card) {
167 pa_log_warn("Port %s does not have a card", port->name);
168 return PA_HOOK_OK;
169 }
170
171 if (pa_idxset_size(card->sinks) == 0 && pa_idxset_size(card->sources) == 0)
172 /* This card is not initialized yet. We'll handle it in
173 sink_new / source_new callbacks later. */
174 return PA_HOOK_OK;
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 int pa__init(pa_module*m) {
290 struct userdata *u;
291
292 pa_assert(m);
293
294 m->userdata = u = pa_xnew(struct userdata, 1);
295
296 /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */
297 u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW],
298 PA_HOOK_NORMAL, (pa_hook_cb_t) sink_new_hook_callback, u);
299 u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW],
300 PA_HOOK_NORMAL, (pa_hook_cb_t) source_new_hook_callback, u);
301 u->available_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED],
302 PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, u);
303
304 handle_all_unavailable(m->core);
305
306 return 0;
307 }
308
309 void pa__done(pa_module*m) {
310 struct userdata *u;
311
312 pa_assert(m);
313
314 if (!(u = m->userdata))
315 return;
316
317 if (u->available_slot)
318 pa_hook_slot_free(u->available_slot);
319 if (u->sink_new_slot)
320 pa_hook_slot_free(u->sink_new_slot);
321 if (u->source_new_slot)
322 pa_hook_slot_free(u->source_new_slot);
323
324 pa_xfree(u);
325 }