]> code.delx.au - pulseaudio/blob - src/modules/module-switch-on-port-available.c
card: Ensure that there's always at least one profile.
[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 *callback_slot;
35 };
36
37 static pa_device_port* find_best_port(pa_hashmap *ports) {
38 void *state;
39 pa_device_port* port, *result = NULL;
40
41 PA_HASHMAP_FOREACH(port, ports, state) {
42 if (result == NULL ||
43 result->available == PA_PORT_AVAILABLE_NO ||
44 (port->available != PA_PORT_AVAILABLE_NO && port->priority > result->priority)) {
45 result = port;
46 }
47 }
48
49 return result;
50 }
51
52 static pa_bool_t try_to_switch_profile(pa_card *card, pa_device_port *port) {
53 pa_card_profile *best_profile = NULL, *profile;
54 void *state;
55
56 pa_log_debug("Finding best profile");
57
58 if (port->profiles)
59 PA_HASHMAP_FOREACH(profile, port->profiles, state) {
60 if (best_profile && best_profile->priority >= profile->priority)
61 continue;
62
63
64 /* We make a best effort to keep other direction unchanged */
65 if (!port->is_input) {
66 if (card->active_profile->n_sources != profile->n_sources)
67 continue;
68
69 if (card->active_profile->max_source_channels != profile->max_source_channels)
70 continue;
71 }
72
73 if (!port->is_output) {
74 if (card->active_profile->n_sinks != profile->n_sinks)
75 continue;
76
77 if (card->active_profile->max_sink_channels != profile->max_sink_channels)
78 continue;
79 }
80
81 if (port->is_output) {
82 /* Try not to switch to HDMI sinks from analog when HDMI is becoming available */
83 uint32_t state2;
84 pa_sink *sink;
85 pa_bool_t found_active_port = FALSE;
86 PA_IDXSET_FOREACH(sink, card->sinks, state2) {
87 if (!sink->active_port)
88 continue;
89 if (sink->active_port->available != PA_PORT_AVAILABLE_NO)
90 found_active_port = TRUE;
91 }
92 if (found_active_port)
93 continue;
94 }
95
96 best_profile = profile;
97 }
98
99 if (!best_profile) {
100 pa_log_debug("No suitable profile found");
101 return FALSE;
102 }
103
104 if (pa_card_set_profile(card, best_profile->name, FALSE) != 0) {
105 pa_log_debug("Could not set profile %s", best_profile->name);
106 return FALSE;
107 }
108
109 return TRUE;
110 }
111
112 static void find_sink_and_source(pa_card *card, pa_device_port *port, pa_sink **si, pa_source **so)
113 {
114 pa_sink *sink = NULL;
115 pa_source *source = NULL;
116 uint32_t state;
117
118 if (port->is_output)
119 PA_IDXSET_FOREACH(sink, card->sinks, state)
120 if (port == pa_hashmap_get(sink->ports, port->name))
121 break;
122
123 if (port->is_input)
124 PA_IDXSET_FOREACH(source, card->sources, state)
125 if (port == pa_hashmap_get(source->ports, port->name))
126 break;
127
128 *si = sink;
129 *so = source;
130 }
131
132 static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) {
133 uint32_t state;
134 pa_card* card;
135 pa_sink *sink;
136 pa_source *source;
137 pa_bool_t is_active_profile, is_active_port;
138
139 if (port->available == PA_PORT_AVAILABLE_UNKNOWN)
140 return PA_HOOK_OK;
141
142 pa_log_debug("finding port %s", port->name);
143
144 PA_IDXSET_FOREACH(card, c->cards, state)
145 if (card->ports && port == pa_hashmap_get(card->ports, port->name))
146 break;
147
148 if (!card) {
149 pa_log_warn("Did not find port %s in array of cards", port->name);
150 return PA_HOOK_OK;
151 }
152
153 find_sink_and_source(card, port, &sink, &source);
154
155 is_active_profile = port->profiles &&
156 card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name);
157 is_active_port = (sink && sink->active_port == port) || (source && source->active_port == port);
158
159 if (port->available == PA_PORT_AVAILABLE_NO && !is_active_port)
160 return PA_HOOK_OK;
161
162 if (port->available == PA_PORT_AVAILABLE_YES) {
163 if (is_active_port)
164 return PA_HOOK_OK;
165
166 if (!is_active_profile) {
167 if (!try_to_switch_profile(card, port))
168 return PA_HOOK_OK;
169
170 pa_assert(card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name));
171
172 /* Now that profile has changed, our sink and source pointers must be updated */
173 find_sink_and_source(card, port, &sink, &source);
174 }
175
176 if (source)
177 pa_source_set_port(source, port->name, FALSE);
178 if (sink)
179 pa_sink_set_port(sink, port->name, FALSE);
180 }
181
182 if (port->available == PA_PORT_AVAILABLE_NO) {
183 if (sink) {
184 pa_device_port *p2 = find_best_port(sink->ports);
185
186 if (p2 && p2->available != PA_PORT_AVAILABLE_NO)
187 pa_sink_set_port(sink, p2->name, FALSE);
188 else {
189 /* Maybe try to switch to another profile? */
190 }
191 }
192
193 if (source) {
194 pa_device_port *p2 = find_best_port(source->ports);
195
196 if (p2 && p2->available != PA_PORT_AVAILABLE_NO)
197 pa_source_set_port(source, p2->name, FALSE);
198 else {
199 /* Maybe try to switch to another profile? */
200 }
201 }
202 }
203
204 return PA_HOOK_OK;
205 }
206
207 static void handle_all_unavailable(pa_core *core) {
208 pa_card *card;
209 uint32_t state;
210
211 PA_IDXSET_FOREACH(card, core->cards, state) {
212 pa_device_port *port;
213 void *state2;
214
215 if (!card->ports)
216 continue;
217
218 PA_HASHMAP_FOREACH(port, card->ports, state2) {
219 if (port->available == PA_PORT_AVAILABLE_NO)
220 port_available_hook_callback(core, port, NULL);
221 }
222 }
223 }
224
225 int pa__init(pa_module*m) {
226 struct userdata *u;
227
228 pa_assert(m);
229
230 m->userdata = u = pa_xnew(struct userdata, 1);
231
232 u->callback_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED],
233 PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, u);
234
235 handle_all_unavailable(m->core);
236
237 return 0;
238 }
239
240 void pa__done(pa_module*m) {
241 struct userdata *u;
242
243 pa_assert(m);
244
245 if (!(u = m->userdata))
246 return;
247
248 if (u->callback_slot)
249 pa_hook_slot_free(u->callback_slot);
250
251 pa_xfree(u);
252 }