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