]> code.delx.au - pulseaudio/blob - src/modules/module-device-manager.c
device-manager: Add a new module to keep track of the names and descriptions of vario...
[pulseaudio] / src / modules / module-device-manager.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2006-2008 Lennart Poettering
5 Copyright 2009 Colin Guthrie
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 <unistd.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <ctype.h>
34
35 #include <pulse/xmalloc.h>
36 #include <pulse/volume.h>
37 #include <pulse/timeval.h>
38 #include <pulse/util.h>
39 #include <pulse/rtclock.h>
40
41 #include <pulsecore/core-error.h>
42 #include <pulsecore/module.h>
43 #include <pulsecore/core-util.h>
44 #include <pulsecore/modargs.h>
45 #include <pulsecore/log.h>
46 #include <pulsecore/core-subscribe.h>
47 #include <pulsecore/sink-input.h>
48 #include <pulsecore/source-output.h>
49 #include <pulsecore/namereg.h>
50 #include <pulsecore/database.h>
51
52 #include "module-device-manager-symdef.h"
53
54 PA_MODULE_AUTHOR("Colin Guthrie");
55 PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present");
56 PA_MODULE_VERSION(PACKAGE_VERSION);
57 PA_MODULE_LOAD_ONCE(TRUE);
58 PA_MODULE_USAGE("This module does not take any arguments");
59
60 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
61
62 static const char* const valid_modargs[] = {
63 NULL
64 };
65
66 struct userdata {
67 pa_core *core;
68 pa_module *module;
69 pa_subscription *subscription;
70 pa_hook_slot
71 *sink_new_hook_slot,
72 *source_new_hook_slot;
73 pa_time_event *save_time_event;
74 pa_database *database;
75 };
76
77 #define ENTRY_VERSION 1
78
79 struct entry {
80 uint8_t version;
81 char description[PA_NAME_MAX];
82 } PA_GCC_PACKED;
83
84 static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
85 struct userdata *u = userdata;
86
87 pa_assert(a);
88 pa_assert(e);
89 pa_assert(u);
90
91 pa_assert(e == u->save_time_event);
92 u->core->mainloop->time_free(u->save_time_event);
93 u->save_time_event = NULL;
94
95 pa_database_sync(u->database);
96 pa_log_info("Synced.");
97 }
98
99 static struct entry* read_entry(struct userdata *u, const char *name) {
100 pa_datum key, data;
101 struct entry *e;
102
103 pa_assert(u);
104 pa_assert(name);
105
106 key.data = (char*) name;
107 key.size = strlen(name);
108
109 pa_zero(data);
110
111 if (!pa_database_get(u->database, &key, &data))
112 goto fail;
113
114 if (data.size != sizeof(struct entry)) {
115 pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry));
116 goto fail;
117 }
118
119 e = (struct entry*) data.data;
120
121 if (e->version != ENTRY_VERSION) {
122 pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name);
123 goto fail;
124 }
125
126 if (!memchr(e->description, 0, sizeof(e->description))) {
127 pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name);
128 goto fail;
129 }
130
131 return e;
132
133 fail:
134
135 pa_datum_free(&data);
136 return NULL;
137 }
138
139 static void trigger_save(struct userdata *u) {
140 if (u->save_time_event)
141 return;
142
143 u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
144 }
145
146 static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
147 if (strncmp(a->description, b->description, sizeof(a->description)))
148 return FALSE;
149
150 return TRUE;
151 }
152
153 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
154 struct userdata *u = userdata;
155 struct entry entry, *old;
156 char *name;
157 pa_datum key, data;
158
159 pa_assert(c);
160 pa_assert(u);
161
162 if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) &&
163 t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) &&
164 t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) &&
165 t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE))
166 return;
167
168 pa_zero(entry);
169 entry.version = ENTRY_VERSION;
170
171 if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
172 pa_sink *sink;
173
174 if (!(sink = pa_idxset_get_by_index(c->sinks, idx)))
175 return;
176
177 name = pa_sprintf_malloc("sink:%s", sink->name);
178
179 if ((old = read_entry(u, name)))
180 entry = *old;
181
182 pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
183
184 } else {
185 pa_source *source;
186
187 pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
188
189 if (!(source = pa_idxset_get_by_index(c->sources, idx)))
190 return;
191
192 name = pa_sprintf_malloc("source:%s", source->name);
193
194 if ((old = read_entry(u, name)))
195 entry = *old;
196
197 pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
198 }
199
200 if (old) {
201
202 if (entries_equal(old, &entry)) {
203 pa_xfree(old);
204 pa_xfree(name);
205 return;
206 }
207
208 pa_xfree(old);
209 }
210
211 key.data = name;
212 key.size = strlen(name);
213
214 data.data = &entry;
215 data.size = sizeof(entry);
216
217 pa_log_info("Storing device description for %s.", name);
218
219 pa_database_set(u->database, &key, &data, TRUE);
220
221 pa_xfree(name);
222
223 trigger_save(u);
224 }
225
226 static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
227 char *name;
228 struct entry *e;
229
230 pa_assert(c);
231 pa_assert(new_data);
232 pa_assert(u);
233
234 name = pa_sprintf_malloc("sink:%s", new_data->name);
235
236 if ((e = read_entry(u, name))) {
237 if (strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
238 pa_log_info("Restoring description for sink %s.", name);
239 pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
240 }
241
242 pa_xfree(e);
243 }
244
245 pa_xfree(name);
246
247 return PA_HOOK_OK;
248 }
249
250 static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
251 char *name;
252 struct entry *e;
253
254 pa_assert(c);
255 pa_assert(new_data);
256 pa_assert(u);
257
258 name = pa_sprintf_malloc("source:%s", new_data->name);
259
260 if ((e = read_entry(u, name))) {
261
262 if (strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
263 pa_log_info("Restoring description for sink %s.", name);
264 pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
265 }
266
267 pa_xfree(e);
268 }
269
270 pa_xfree(name);
271
272 return PA_HOOK_OK;
273 }
274
275 int pa__init(pa_module*m) {
276 pa_modargs *ma = NULL;
277 struct userdata *u;
278 char *fname;
279 pa_sink *sink;
280 pa_source *source;
281 uint32_t idx;
282
283 pa_assert(m);
284
285 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
286 pa_log("Failed to parse module arguments");
287 goto fail;
288 }
289
290 m->userdata = u = pa_xnew0(struct userdata, 1);
291 u->core = m->core;
292 u->module = m;
293
294 u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u);
295
296 u->sink_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_new_hook_callback, u);
297 u->source_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u);
298
299 if (!(fname = pa_state_path("device-manager", TRUE)))
300 goto fail;
301
302 if (!(u->database = pa_database_open(fname, TRUE))) {
303 pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
304 pa_xfree(fname);
305 goto fail;
306 }
307
308 pa_log_info("Sucessfully opened database file '%s'.", fname);
309 pa_xfree(fname);
310
311 for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx))
312 subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
313
314 for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx))
315 subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u);
316
317 pa_modargs_free(ma);
318 return 0;
319
320 fail:
321 pa__done(m);
322
323 if (ma)
324 pa_modargs_free(ma);
325
326 return -1;
327 }
328
329 void pa__done(pa_module*m) {
330 struct userdata* u;
331
332 pa_assert(m);
333
334 if (!(u = m->userdata))
335 return;
336
337 if (u->subscription)
338 pa_subscription_free(u->subscription);
339
340 if (u->sink_new_hook_slot)
341 pa_hook_slot_free(u->sink_new_hook_slot);
342 if (u->source_new_hook_slot)
343 pa_hook_slot_free(u->source_new_hook_slot);
344
345 if (u->save_time_event)
346 u->core->mainloop->time_free(u->save_time_event);
347
348 if (u->database)
349 pa_database_close(u->database);
350
351 pa_xfree(u);
352 }