hashmap: Add pa_hashmap_remove_all()
[platform/upstream/pulseaudio.git] / 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 }