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
, sizeof(prefix
)))
297 t
= pa_xstrdup(key
+ sizeof(prefix
));
301 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
310 for (sink
= pa_idxset_first(u
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(u
->core
->sinks
, &idx
)) {
313 if (!(n
= get_name(name
, "sink")))
316 if (!pa_streq(sink
->name
, n
)) {
322 pa_log_info("Restoring description for sink %s.", sink
->name
);
323 pa_proplist_sets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
326 for (source
= pa_idxset_first(u
->core
->sources
, &idx
); source
; source
= pa_idxset_next(u
->core
->sources
, &idx
)) {
329 if (!(n
= get_name(name
, "source")))
332 if (!pa_streq(source
->name
, n
)) {
338 pa_log_info("Restoring description for source %s.", source
->name
);
339 pa_proplist_sets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
343 #define EXT_VERSION 1
345 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
348 pa_tagstruct
*reply
= NULL
;
357 if (pa_tagstruct_getu32(t
, &command
) < 0)
360 reply
= pa_tagstruct_new(NULL
, 0);
361 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
362 pa_tagstruct_putu32(reply
, tag
);
365 case SUBCOMMAND_TEST
: {
366 if (!pa_tagstruct_eof(t
))
369 pa_tagstruct_putu32(reply
, EXT_VERSION
);
373 case SUBCOMMAND_READ
: {
377 if (!pa_tagstruct_eof(t
))
380 done
= !pa_database_first(u
->database
, &key
, NULL
);
387 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
389 name
= pa_xstrndup(key
.data
, key
.size
);
392 if ((e
= read_entry(u
, name
))) {
393 pa_tagstruct_puts(reply
, name
);
394 pa_tagstruct_puts(reply
, e
->description
);
407 case SUBCOMMAND_WRITE
: {
409 pa_bool_t apply_immediately
= FALSE
;
411 if (pa_tagstruct_getu32(t
, &mode
) < 0 ||
412 pa_tagstruct_get_boolean(t
, &apply_immediately
) < 0)
415 if (mode
!= PA_UPDATE_MERGE
&&
416 mode
!= PA_UPDATE_REPLACE
&&
417 mode
!= PA_UPDATE_SET
)
420 if (mode
== PA_UPDATE_SET
)
421 pa_database_clear(u
->database
);
423 while (!pa_tagstruct_eof(t
)) {
424 const char *name
, *description
;
429 entry
.version
= ENTRY_VERSION
;
431 if (pa_tagstruct_gets(t
, &name
) < 0 ||
432 pa_tagstruct_gets(reply
, &description
) < 0)
438 pa_strlcpy(entry
.description
, description
, sizeof(entry
.description
));
440 key
.data
= (char*) name
;
441 key
.size
= strlen(name
);
444 data
.size
= sizeof(entry
);
446 if (pa_database_set(u
->database
, &key
, &data
, mode
== PA_UPDATE_REPLACE
) == 0)
447 if (apply_immediately
)
448 apply_entry(u
, name
, &entry
);
456 case SUBCOMMAND_DELETE
:
458 while (!pa_tagstruct_eof(t
)) {
462 if (pa_tagstruct_gets(t
, &name
) < 0)
465 key
.data
= (char*) name
;
466 key
.size
= strlen(name
);
468 pa_database_unset(u
->database
, &key
);
475 case SUBCOMMAND_SUBSCRIBE
: {
479 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
480 !pa_tagstruct_eof(t
))
484 pa_idxset_put(u
->subscribed
, c
, NULL
);
486 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
495 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
501 pa_tagstruct_free(reply
);
506 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
511 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
515 int pa__init(pa_module
*m
) {
516 pa_modargs
*ma
= NULL
;
525 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
526 pa_log("Failed to parse module arguments");
530 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
533 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
535 u
->protocol
= pa_native_protocol_get(m
->core
);
536 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
538 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
);
540 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_SINK
|PA_SUBSCRIPTION_MASK_SOURCE
, subscribe_callback
, u
);
542 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
);
543 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
);
545 if (!(fname
= pa_state_path("device-manager", TRUE
)))
548 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
549 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
554 pa_log_info("Sucessfully opened database file '%s'.", fname
);
557 for (sink
= pa_idxset_first(m
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(m
->core
->sinks
, &idx
))
558 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
560 for (source
= pa_idxset_first(m
->core
->sources
, &idx
); source
; source
= pa_idxset_next(m
->core
->sources
, &idx
))
561 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
575 void pa__done(pa_module
*m
) {
580 if (!(u
= m
->userdata
))
584 pa_subscription_free(u
->subscription
);
586 if (u
->sink_new_hook_slot
)
587 pa_hook_slot_free(u
->sink_new_hook_slot
);
588 if (u
->source_new_hook_slot
)
589 pa_hook_slot_free(u
->source_new_hook_slot
);
591 if (u
->save_time_event
)
592 u
->core
->mainloop
->time_free(u
->save_time_event
);
595 pa_database_close(u
->database
);
598 pa_native_protocol_remove_ext(u
->protocol
, m
);
599 pa_native_protocol_unref(u
->protocol
);
603 pa_idxset_free(u
->subscribed
, NULL
, NULL
);