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