]> code.delx.au - pulseaudio/blob - src/modules/module-switch-on-port-available.c
Assume that the ports hashmap of cards is always non-NULL.
[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 PA_HASHMAP_FOREACH(profile, port->profiles, state) {
59 if (best_profile && best_profile->priority >= profile->priority)
60 continue;
61
62 /* We make a best effort to keep other direction unchanged */
63 if (!port->is_input) {
64 if (card->active_profile->n_sources != profile->n_sources)
65 continue;
66
67 if (card->active_profile->max_source_channels != profile->max_source_channels)
68 continue;
69 }
70
71 if (!port->is_output) {
72 if (card->active_profile->n_sinks != profile->n_sinks)
73 continue;
74
75 if (card->active_profile->max_sink_channels != profile->max_sink_channels)
76 continue;
77 }
78
79 if (port->is_output) {
80 /* Try not to switch to HDMI sinks from analog when HDMI is becoming available */
81 uint32_t state2;
82 pa_sink *sink;
83 pa_bool_t found_active_port = FALSE;
84
85 PA_IDXSET_FOREACH(sink, card->sinks, state2) {
86 if (!sink->active_port)
87 continue;
88 if (sink->active_port->available != PA_PORT_AVAILABLE_NO)
89 found_active_port = TRUE;
90 }
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 (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 = card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name);
156 is_active_port = (sink && sink->active_port == port) || (source && source->active_port == port);
157
158 if (port->available == PA_PORT_AVAILABLE_NO && !is_active_port)
159 return PA_HOOK_OK;
160
161 if (port->available == PA_PORT_AVAILABLE_YES) {
162 if (is_active_port)
163 return PA_HOOK_OK;
164
165 if (!is_active_profile) {
166 if (!try_to_switch_profile(card, port))
167 return PA_HOOK_OK;
168
169 pa_assert(card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name));
170
171 /* Now that profile has changed, our sink and source pointers must be updated */
172 find_sink_and_source(card, port, &sink, &source);
173 }
174
175 if (source)
176 pa_source_set_port(source, port->name, FALSE);
177 if (sink)
178 pa_sink_set_port(sink, port->name, FALSE);
179 }
180
181 if (port->available == PA_PORT_AVAILABLE_NO) {
182 if (sink) {
183 pa_device_port *p2 = find_best_port(sink->ports);
184
185 if (p2 && p2->available != PA_PORT_AVAILABLE_NO)
186 pa_sink_set_port(sink, p2->name, FALSE);
187 else {
188 /* Maybe try to switch to another profile? */
189 }
190 }
191
192 if (source) {
193 pa_device_port *p2 = find_best_port(source->ports);
194
195 if (p2 && p2->available != PA_PORT_AVAILABLE_NO)
196 pa_source_set_port(source, p2->name, FALSE);
197 else {
198 /* Maybe try to switch to another profile? */
199 }
200 }
201 }
202
203 return PA_HOOK_OK;
204 }
205
206 static void handle_all_unavailable(pa_core *core) {
207 pa_card *card;
208 uint32_t state;
209
210 PA_IDXSET_FOREACH(card, core->cards, state) {
211 pa_device_port *port;
212 void *state2;
213
214 PA_HASHMAP_FOREACH(port, card->ports, state2) {
215 if (port->available == PA_PORT_AVAILABLE_NO)
216 port_available_hook_callback(core, port, NULL);
217 }
218 }
219 }
220
221 int pa__init(pa_module*m) {
222 struct userdata *u;
223
224 pa_assert(m);
225
226 m->userdata = u = pa_xnew(struct userdata, 1);
227
228 u->callback_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED],
229 PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, u);
230
231 handle_all_unavailable(m->core);
232
233 return 0;
234 }
235
236 void pa__done(pa_module*m) {
237 struct userdata *u;
238
239 pa_assert(m);
240
241 if (!(u = m->userdata))
242 return;
243
244 if (u->callback_slot)
245 pa_hook_slot_free(u->callback_slot);
246
247 pa_xfree(u);
248 }