2 This file is part of PulseAudio.
4 Copyright 2006-2008 Lennart Poettering
5 Copyright 2009 Colin Guthrie
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.
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.
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
30 #include <sys/types.h>
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>
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>
52 #include "module-device-manager-symdef.h"
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");
60 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
62 static const char* const valid_modargs
[] = {
69 pa_subscription
*subscription
;
72 *source_new_hook_slot
;
73 pa_time_event
*save_time_event
;
74 pa_database
*database
;
77 #define ENTRY_VERSION 1
81 char description
[PA_NAME_MAX
];
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
;
91 pa_assert(e
== u
->save_time_event
);
92 u
->core
->mainloop
->time_free(u
->save_time_event
);
93 u
->save_time_event
= NULL
;
95 pa_database_sync(u
->database
);
96 pa_log_info("Synced.");
99 static struct entry
* read_entry(struct userdata
*u
, const char *name
) {
106 key
.data
= (char*) name
;
107 key
.size
= strlen(name
);
111 if (!pa_database_get(u
->database
, &key
, &data
))
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
));
119 e
= (struct entry
*) data
.data
;
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
);
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
);
135 pa_datum_free(&data
);
139 static void trigger_save(struct userdata
*u
) {
140 if (u
->save_time_event
)
143 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
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
)))
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
;
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
))
169 entry
.version
= ENTRY_VERSION
;
171 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
174 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
177 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
179 if ((old
= read_entry(u
, name
)))
182 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
187 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
189 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
192 name
= pa_sprintf_malloc("source:%s", source
->name
);
194 if ((old
= read_entry(u
, name
)))
197 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
202 if (entries_equal(old
, &entry
)) {
212 key
.size
= strlen(name
);
215 data
.size
= sizeof(entry
);
217 pa_log_info("Storing device description for %s.", name
);
219 pa_database_set(u
->database
, &key
, &data
, TRUE
);
226 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
234 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
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
);
250 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
258 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
260 if ((e
= read_entry(u
, name
))) {
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
);
275 int pa__init(pa_module
*m
) {
276 pa_modargs
*ma
= NULL
;
285 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
286 pa_log("Failed to parse module arguments");
290 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
294 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_SINK
|PA_SUBSCRIPTION_MASK_SOURCE
, subscribe_callback
, u
);
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
);
299 if (!(fname
= pa_state_path("device-manager", TRUE
)))
302 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
303 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
308 pa_log_info("Sucessfully opened database file '%s'.", fname
);
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
);
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
);
329 void pa__done(pa_module
*m
) {
334 if (!(u
= m
->userdata
))
338 pa_subscription_free(u
->subscription
);
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
);
345 if (u
->save_time_event
)
346 u
->core
->mainloop
->time_free(u
->save_time_event
);
349 pa_database_close(u
->database
);