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/protocol-native.h>
51 #include <pulsecore/pstream.h>
52 #include <pulsecore/pstream-util.h>
53 #include <pulsecore/database.h>
55 #include "module-device-manager-symdef.h"
57 PA_MODULE_AUTHOR("Colin Guthrie");
58 PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present");
59 PA_MODULE_VERSION(PACKAGE_VERSION
);
60 PA_MODULE_LOAD_ONCE(TRUE
);
61 PA_MODULE_USAGE("This module does not take any arguments");
63 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
65 static const char* const valid_modargs
[] = {
72 pa_subscription
*subscription
;
75 *source_new_hook_slot
,
76 *connection_unlink_hook_slot
;
77 pa_time_event
*save_time_event
;
78 pa_database
*database
;
80 pa_native_protocol
*protocol
;
81 pa_idxset
*subscribed
;
84 #define ENTRY_VERSION 1
88 char description
[PA_NAME_MAX
];
100 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
101 struct userdata
*u
= userdata
;
107 pa_assert(e
== u
->save_time_event
);
108 u
->core
->mainloop
->time_free(u
->save_time_event
);
109 u
->save_time_event
= NULL
;
111 pa_database_sync(u
->database
);
112 pa_log_info("Synced.");
115 static struct entry
* read_entry(struct userdata
*u
, const char *name
) {
122 key
.data
= (char*) name
;
123 key
.size
= strlen(name
);
127 if (!pa_database_get(u
->database
, &key
, &data
))
130 if (data
.size
!= sizeof(struct entry
)) {
131 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
));
135 e
= (struct entry
*) data
.data
;
137 if (e
->version
!= ENTRY_VERSION
) {
138 pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name
);
142 if (!memchr(e
->description
, 0, sizeof(e
->description
))) {
143 pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name
);
151 pa_datum_free(&data
);
155 static void trigger_save(struct userdata
*u
) {
156 if (u
->save_time_event
)
159 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
162 static pa_bool_t
entries_equal(const struct entry
*a
, const struct entry
*b
) {
163 if (strncmp(a
->description
, b
->description
, sizeof(a
->description
)))
169 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
170 struct userdata
*u
= userdata
;
171 struct entry entry
, *old
;
178 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
179 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
180 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
181 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
))
185 entry
.version
= ENTRY_VERSION
;
187 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
190 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
193 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
195 if ((old
= read_entry(u
, name
)))
198 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
203 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
205 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
208 name
= pa_sprintf_malloc("source:%s", source
->name
);
210 if ((old
= read_entry(u
, name
)))
213 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
218 if (entries_equal(old
, &entry
)) {
228 key
.size
= strlen(name
);
231 data
.size
= sizeof(entry
);
233 pa_log_info("Storing device description for %s.", name
);
235 pa_database_set(u
->database
, &key
, &data
, TRUE
);
242 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
250 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
252 if ((e
= read_entry(u
, name
))) {
253 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
254 pa_log_info("Restoring description for sink %s.", name
);
255 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
266 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
274 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
276 if ((e
= read_entry(u
, name
))) {
278 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
279 pa_log_info("Restoring description for sink %s.", name
);
280 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
291 static char *get_name(const char *key
, const char *prefix
) {
294 if (strncmp(key
, prefix
, strlen(prefix
)))
297 t
= pa_xstrdup(key
+ strlen(prefix
));
301 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
311 if ((n
= get_name(name
, "sink:"))) {
312 for (sink
= pa_idxset_first(u
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(u
->core
->sinks
, &idx
)) {
313 if (!pa_streq(sink
->name
, n
)) {
317 pa_log_info("Setting description for sink %s.", sink
->name
);
318 pa_sink_set_description(sink
, e
->description
);
322 else if ((n
= get_name(name
, "source:"))) {
323 for (source
= pa_idxset_first(u
->core
->sources
, &idx
); source
; source
= pa_idxset_next(u
->core
->sources
, &idx
)) {
324 if (!pa_streq(source
->name
, n
)) {
328 pa_log_info("Setting description for source %s.", source
->name
);
329 pa_source_set_description(source
, e
->description
);
335 #define EXT_VERSION 1
337 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
340 pa_tagstruct
*reply
= NULL
;
349 if (pa_tagstruct_getu32(t
, &command
) < 0)
352 reply
= pa_tagstruct_new(NULL
, 0);
353 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
354 pa_tagstruct_putu32(reply
, tag
);
357 case SUBCOMMAND_TEST
: {
358 if (!pa_tagstruct_eof(t
))
361 pa_tagstruct_putu32(reply
, EXT_VERSION
);
365 case SUBCOMMAND_READ
: {
369 if (!pa_tagstruct_eof(t
))
372 done
= !pa_database_first(u
->database
, &key
, NULL
);
379 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
381 name
= pa_xstrndup(key
.data
, key
.size
);
384 if ((e
= read_entry(u
, name
))) {
385 pa_tagstruct_puts(reply
, name
);
386 pa_tagstruct_puts(reply
, e
->description
);
399 case SUBCOMMAND_WRITE
: {
401 pa_bool_t apply_immediately
= FALSE
;
403 if (pa_tagstruct_getu32(t
, &mode
) < 0 ||
404 pa_tagstruct_get_boolean(t
, &apply_immediately
) < 0)
407 if (mode
!= PA_UPDATE_MERGE
&&
408 mode
!= PA_UPDATE_REPLACE
&&
409 mode
!= PA_UPDATE_SET
)
412 if (mode
== PA_UPDATE_SET
)
413 pa_database_clear(u
->database
);
415 while (!pa_tagstruct_eof(t
)) {
416 const char *name
, *description
;
421 entry
.version
= ENTRY_VERSION
;
423 if (pa_tagstruct_gets(t
, &name
) < 0 ||
424 pa_tagstruct_gets(t
, &description
) < 0)
430 pa_strlcpy(entry
.description
, description
, sizeof(entry
.description
));
432 key
.data
= (char*) name
;
433 key
.size
= strlen(name
);
436 data
.size
= sizeof(entry
);
438 if (pa_database_set(u
->database
, &key
, &data
, mode
== PA_UPDATE_REPLACE
) == 0)
439 if (apply_immediately
)
440 apply_entry(u
, name
, &entry
);
448 case SUBCOMMAND_DELETE
:
450 while (!pa_tagstruct_eof(t
)) {
454 if (pa_tagstruct_gets(t
, &name
) < 0)
457 key
.data
= (char*) name
;
458 key
.size
= strlen(name
);
460 pa_database_unset(u
->database
, &key
);
467 case SUBCOMMAND_SUBSCRIBE
: {
471 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
472 !pa_tagstruct_eof(t
))
476 pa_idxset_put(u
->subscribed
, c
, NULL
);
478 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
487 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
493 pa_tagstruct_free(reply
);
498 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
503 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
507 int pa__init(pa_module
*m
) {
508 pa_modargs
*ma
= NULL
;
517 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
518 pa_log("Failed to parse module arguments");
522 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
525 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
527 u
->protocol
= pa_native_protocol_get(m
->core
);
528 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
530 u
->connection_unlink_hook_slot
= pa_hook_connect(&pa_native_protocol_hooks(u
->protocol
)[PA_NATIVE_HOOK_CONNECTION_UNLINK
], PA_HOOK_NORMAL
, (pa_hook_cb_t
) connection_unlink_hook_cb
, u
);
532 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_SINK
|PA_SUBSCRIPTION_MASK_SOURCE
, subscribe_callback
, u
);
534 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
);
535 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
);
537 if (!(fname
= pa_state_path("device-manager", TRUE
)))
540 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
541 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
546 pa_log_info("Sucessfully opened database file '%s'.", fname
);
549 for (sink
= pa_idxset_first(m
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(m
->core
->sinks
, &idx
))
550 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
552 for (source
= pa_idxset_first(m
->core
->sources
, &idx
); source
; source
= pa_idxset_next(m
->core
->sources
, &idx
))
553 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
567 void pa__done(pa_module
*m
) {
572 if (!(u
= m
->userdata
))
576 pa_subscription_free(u
->subscription
);
578 if (u
->sink_new_hook_slot
)
579 pa_hook_slot_free(u
->sink_new_hook_slot
);
580 if (u
->source_new_hook_slot
)
581 pa_hook_slot_free(u
->source_new_hook_slot
);
583 if (u
->save_time_event
)
584 u
->core
->mainloop
->time_free(u
->save_time_event
);
587 pa_database_close(u
->database
);
590 pa_native_protocol_remove_ext(u
->protocol
, m
);
591 pa_native_protocol_unref(u
->protocol
);
595 pa_idxset_free(u
->subscribed
, NULL
, NULL
);