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
= NULL
;
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 if (source
->monitor_of
)
211 name
= pa_sprintf_malloc("source:%s", source
->name
);
213 if ((old
= read_entry(u
, name
)))
216 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
221 if (entries_equal(old
, &entry
)) {
231 key
.size
= strlen(name
);
234 data
.size
= sizeof(entry
);
236 pa_log_info("Storing device description for %s.", name
);
238 pa_database_set(u
->database
, &key
, &data
, TRUE
);
245 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
253 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
255 if ((e
= read_entry(u
, name
))) {
256 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
257 pa_log_info("Restoring description for sink %s.", new_data
->name
);
258 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
269 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
277 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
279 if ((e
= read_entry(u
, name
))) {
281 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
282 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
283 pa_log_info("Restoring description for sink %s.", new_data
->name
);
284 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
295 static char *get_name(const char *key
, const char *prefix
) {
298 if (strncmp(key
, prefix
, strlen(prefix
)))
301 t
= pa_xstrdup(key
+ strlen(prefix
));
305 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
315 if ((n
= get_name(name
, "sink:"))) {
316 for (sink
= pa_idxset_first(u
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(u
->core
->sinks
, &idx
)) {
317 if (!pa_streq(sink
->name
, n
)) {
321 pa_log_info("Setting description for sink %s.", sink
->name
);
322 pa_sink_set_description(sink
, e
->description
);
326 else if ((n
= get_name(name
, "source:"))) {
327 for (source
= pa_idxset_first(u
->core
->sources
, &idx
); source
; source
= pa_idxset_next(u
->core
->sources
, &idx
)) {
328 if (!pa_streq(source
->name
, n
)) {
332 if (source
->monitor_of
) {
333 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source
->name
);
337 pa_log_info("Setting description for source %s.", source
->name
);
338 pa_source_set_description(source
, e
->description
);
344 #define EXT_VERSION 1
346 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
349 pa_tagstruct
*reply
= NULL
;
358 if (pa_tagstruct_getu32(t
, &command
) < 0)
361 reply
= pa_tagstruct_new(NULL
, 0);
362 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
363 pa_tagstruct_putu32(reply
, tag
);
366 case SUBCOMMAND_TEST
: {
367 if (!pa_tagstruct_eof(t
))
370 pa_tagstruct_putu32(reply
, EXT_VERSION
);
374 case SUBCOMMAND_READ
: {
378 if (!pa_tagstruct_eof(t
))
381 done
= !pa_database_first(u
->database
, &key
, NULL
);
388 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
390 name
= pa_xstrndup(key
.data
, key
.size
);
393 if ((e
= read_entry(u
, name
))) {
394 pa_tagstruct_puts(reply
, name
);
395 pa_tagstruct_puts(reply
, e
->description
);
408 case SUBCOMMAND_WRITE
: {
410 pa_bool_t apply_immediately
= FALSE
;
412 if (pa_tagstruct_getu32(t
, &mode
) < 0 ||
413 pa_tagstruct_get_boolean(t
, &apply_immediately
) < 0)
416 if (mode
!= PA_UPDATE_MERGE
&&
417 mode
!= PA_UPDATE_REPLACE
&&
418 mode
!= PA_UPDATE_SET
)
421 if (mode
== PA_UPDATE_SET
)
422 pa_database_clear(u
->database
);
424 while (!pa_tagstruct_eof(t
)) {
425 const char *name
, *description
;
430 entry
.version
= ENTRY_VERSION
;
432 if (pa_tagstruct_gets(t
, &name
) < 0 ||
433 pa_tagstruct_gets(t
, &description
) < 0)
439 pa_strlcpy(entry
.description
, description
, sizeof(entry
.description
));
441 key
.data
= (char*) name
;
442 key
.size
= strlen(name
);
445 data
.size
= sizeof(entry
);
447 if (pa_database_set(u
->database
, &key
, &data
, mode
== PA_UPDATE_REPLACE
) == 0)
448 if (apply_immediately
)
449 apply_entry(u
, name
, &entry
);
457 case SUBCOMMAND_DELETE
:
459 while (!pa_tagstruct_eof(t
)) {
463 if (pa_tagstruct_gets(t
, &name
) < 0)
466 key
.data
= (char*) name
;
467 key
.size
= strlen(name
);
469 pa_database_unset(u
->database
, &key
);
476 case SUBCOMMAND_SUBSCRIBE
: {
480 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
481 !pa_tagstruct_eof(t
))
485 pa_idxset_put(u
->subscribed
, c
, NULL
);
487 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
496 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
502 pa_tagstruct_free(reply
);
507 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
512 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
516 int pa__init(pa_module
*m
) {
517 pa_modargs
*ma
= NULL
;
526 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
527 pa_log("Failed to parse module arguments");
531 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
534 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
536 u
->protocol
= pa_native_protocol_get(m
->core
);
537 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
539 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
);
541 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_SINK
|PA_SUBSCRIPTION_MASK_SOURCE
, subscribe_callback
, u
);
543 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
);
544 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
);
546 if (!(fname
= pa_state_path("device-manager", TRUE
)))
549 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
550 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
555 pa_log_info("Sucessfully opened database file '%s'.", fname
);
558 for (sink
= pa_idxset_first(m
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(m
->core
->sinks
, &idx
))
559 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
561 for (source
= pa_idxset_first(m
->core
->sources
, &idx
); source
; source
= pa_idxset_next(m
->core
->sources
, &idx
))
562 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
576 void pa__done(pa_module
*m
) {
581 if (!(u
= m
->userdata
))
585 pa_subscription_free(u
->subscription
);
587 if (u
->sink_new_hook_slot
)
588 pa_hook_slot_free(u
->sink_new_hook_slot
);
589 if (u
->source_new_hook_slot
)
590 pa_hook_slot_free(u
->source_new_hook_slot
);
592 if (u
->save_time_event
)
593 u
->core
->mainloop
->time_free(u
->save_time_event
);
596 pa_database_close(u
->database
);
599 pa_native_protocol_remove_ext(u
->protocol
, m
);
600 pa_native_protocol_unref(u
->protocol
);
604 pa_idxset_free(u
->subscribed
, NULL
, NULL
);