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
;
83 pa_bool_t role_device_priority_routing
;
84 pa_bool_t stream_restore_used
;
85 pa_bool_t checked_stream_restore
;
88 #define ENTRY_VERSION 1
92 char description
[PA_NAME_MAX
];
100 SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
,
101 SUBCOMMAND_SUBSCRIBE
,
105 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
106 struct userdata
*u
= userdata
;
112 pa_assert(e
== u
->save_time_event
);
113 u
->core
->mainloop
->time_free(u
->save_time_event
);
114 u
->save_time_event
= NULL
;
116 pa_database_sync(u
->database
);
117 pa_log_info("Synced.");
120 static struct entry
* read_entry(struct userdata
*u
, const char *name
) {
127 key
.data
= (char*) name
;
128 key
.size
= strlen(name
);
132 if (!pa_database_get(u
->database
, &key
, &data
))
135 if (data
.size
!= sizeof(struct entry
)) {
136 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
));
140 e
= (struct entry
*) data
.data
;
142 if (e
->version
!= ENTRY_VERSION
) {
143 pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name
);
147 if (!memchr(e
->description
, 0, sizeof(e
->description
))) {
148 pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name
);
156 pa_datum_free(&data
);
160 static void trigger_save(struct userdata
*u
) {
161 if (u
->save_time_event
)
164 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
167 static pa_bool_t
entries_equal(const struct entry
*a
, const struct entry
*b
) {
168 if (strncmp(a
->description
, b
->description
, sizeof(a
->description
)))
174 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
175 struct userdata
*u
= userdata
;
176 struct entry entry
, *old
= NULL
;
183 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
184 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
185 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
186 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
))
190 entry
.version
= ENTRY_VERSION
;
192 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
195 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
198 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
200 if ((old
= read_entry(u
, name
)))
203 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
208 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
210 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
213 if (source
->monitor_of
)
216 name
= pa_sprintf_malloc("source:%s", source
->name
);
218 if ((old
= read_entry(u
, name
)))
221 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
226 if (entries_equal(old
, &entry
)) {
236 key
.size
= strlen(name
);
239 data
.size
= sizeof(entry
);
241 pa_log_info("Storing device description for %s.", name
);
243 pa_database_set(u
->database
, &key
, &data
, TRUE
);
250 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
258 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
260 if ((e
= read_entry(u
, name
))) {
261 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
262 pa_log_info("Restoring description for sink %s.", new_data
->name
);
263 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
274 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
282 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
284 if ((e
= read_entry(u
, name
))) {
286 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
287 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
288 pa_log_info("Restoring description for sink %s.", new_data
->name
);
289 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
300 static char *get_name(const char *key
, const char *prefix
) {
303 if (strncmp(key
, prefix
, strlen(prefix
)))
306 t
= pa_xstrdup(key
+ strlen(prefix
));
310 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
320 if ((n
= get_name(name
, "sink:"))) {
321 for (sink
= pa_idxset_first(u
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(u
->core
->sinks
, &idx
)) {
322 if (!pa_streq(sink
->name
, n
)) {
326 pa_log_info("Setting description for sink %s.", sink
->name
);
327 pa_sink_set_description(sink
, e
->description
);
331 else if ((n
= get_name(name
, "source:"))) {
332 for (source
= pa_idxset_first(u
->core
->sources
, &idx
); source
; source
= pa_idxset_next(u
->core
->sources
, &idx
)) {
333 if (!pa_streq(source
->name
, n
)) {
337 if (source
->monitor_of
) {
338 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source
->name
);
342 pa_log_info("Setting description for source %s.", source
->name
);
343 pa_source_set_description(source
, e
->description
);
349 #define EXT_VERSION 1
351 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
354 pa_tagstruct
*reply
= NULL
;
363 if (pa_tagstruct_getu32(t
, &command
) < 0)
366 reply
= pa_tagstruct_new(NULL
, 0);
367 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
368 pa_tagstruct_putu32(reply
, tag
);
371 case SUBCOMMAND_TEST
: {
372 if (!pa_tagstruct_eof(t
))
375 pa_tagstruct_putu32(reply
, EXT_VERSION
);
379 case SUBCOMMAND_READ
: {
383 if (!pa_tagstruct_eof(t
))
386 done
= !pa_database_first(u
->database
, &key
, NULL
);
393 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
395 name
= pa_xstrndup(key
.data
, key
.size
);
398 if ((e
= read_entry(u
, name
))) {
399 pa_tagstruct_puts(reply
, name
);
400 pa_tagstruct_puts(reply
, e
->description
);
413 case SUBCOMMAND_WRITE
: {
415 pa_bool_t apply_immediately
= FALSE
;
417 if (pa_tagstruct_getu32(t
, &mode
) < 0 ||
418 pa_tagstruct_get_boolean(t
, &apply_immediately
) < 0)
421 if (mode
!= PA_UPDATE_MERGE
&&
422 mode
!= PA_UPDATE_REPLACE
&&
423 mode
!= PA_UPDATE_SET
)
426 if (mode
== PA_UPDATE_SET
)
427 pa_database_clear(u
->database
);
429 while (!pa_tagstruct_eof(t
)) {
430 const char *name
, *description
;
435 entry
.version
= ENTRY_VERSION
;
437 if (pa_tagstruct_gets(t
, &name
) < 0 ||
438 pa_tagstruct_gets(t
, &description
) < 0)
444 pa_strlcpy(entry
.description
, description
, sizeof(entry
.description
));
446 key
.data
= (char*) name
;
447 key
.size
= strlen(name
);
450 data
.size
= sizeof(entry
);
452 if (pa_database_set(u
->database
, &key
, &data
, mode
== PA_UPDATE_REPLACE
) == 0)
453 if (apply_immediately
)
454 apply_entry(u
, name
, &entry
);
462 case SUBCOMMAND_DELETE
:
464 while (!pa_tagstruct_eof(t
)) {
468 if (pa_tagstruct_gets(t
, &name
) < 0)
471 key
.data
= (char*) name
;
472 key
.size
= strlen(name
);
474 pa_database_unset(u
->database
, &key
);
481 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
: {
484 uint32_t sridx
= PA_INVALID_INDEX
;
488 if (pa_tagstruct_get_boolean(t
, &enable
) < 0)
491 /* If this is the first run, check for stream restore module */
492 if (!u
->checked_stream_restore
) {
493 u
->checked_stream_restore
= TRUE
;
495 for (module
= pa_idxset_first(u
->core
->modules
, &idx
); module
; module
= pa_idxset_next(u
->core
->modules
, &idx
)) {
496 if (strcmp(module
->name
, "module-stream-restore") == 0) {
497 pa_log_debug("Detected module-stream-restore is currently in use");
498 u
->stream_restore_used
= TRUE
;
499 sridx
= module
->index
;
504 u
->role_device_priority_routing
= enable
;
506 if (u
->stream_restore_used
) {
507 if (PA_INVALID_INDEX
== sridx
) {
508 /* As a shortcut on first load, we have sridx filled in, but otherwise we search for it. */
509 for (module
= pa_idxset_first(u
->core
->modules
, &idx
); module
; module
= pa_idxset_next(u
->core
->modules
, &idx
)) {
510 if (strcmp(module
->name
, "module-stream-restore") == 0) {
511 sridx
= module
->index
;
515 if (PA_INVALID_INDEX
!= sridx
) {
516 pa_log_debug("Unloading module-stream-restore to enable role-based device-priority routing");
517 pa_module_unload_request_by_index(u
->core
, sridx
, TRUE
);
520 } else if (u
->stream_restore_used
) {
521 /* We want to reload module-stream-restore */
522 if (!pa_module_load(u
->core
, "module-stream-restore", ""))
523 pa_log_warn("Failed to load module-stream-restore while disabling role-based device-priority routing");
529 case SUBCOMMAND_SUBSCRIBE
: {
533 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
534 !pa_tagstruct_eof(t
))
538 pa_idxset_put(u
->subscribed
, c
, NULL
);
540 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
549 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
555 pa_tagstruct_free(reply
);
560 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
565 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
569 int pa__init(pa_module
*m
) {
570 pa_modargs
*ma
= NULL
;
579 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
580 pa_log("Failed to parse module arguments");
584 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
587 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
589 u
->protocol
= pa_native_protocol_get(m
->core
);
590 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
592 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
);
594 u
->subscription
= pa_subscription_new(m
->core
, PA_SUBSCRIPTION_MASK_SINK
|PA_SUBSCRIPTION_MASK_SOURCE
, subscribe_callback
, u
);
596 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
);
597 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
);
599 if (!(fname
= pa_state_path("device-manager", TRUE
)))
602 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
603 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
608 pa_log_info("Sucessfully opened database file '%s'.", fname
);
611 for (sink
= pa_idxset_first(m
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(m
->core
->sinks
, &idx
))
612 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
614 for (source
= pa_idxset_first(m
->core
->sources
, &idx
); source
; source
= pa_idxset_next(m
->core
->sources
, &idx
))
615 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
629 void pa__done(pa_module
*m
) {
634 if (!(u
= m
->userdata
))
638 pa_subscription_free(u
->subscription
);
640 if (u
->sink_new_hook_slot
)
641 pa_hook_slot_free(u
->sink_new_hook_slot
);
642 if (u
->source_new_hook_slot
)
643 pa_hook_slot_free(u
->source_new_hook_slot
);
645 if (u
->save_time_event
)
646 u
->core
->mainloop
->time_free(u
->save_time_event
);
649 pa_database_close(u
->database
);
652 pa_native_protocol_remove_ext(u
->protocol
, m
);
653 pa_native_protocol_unref(u
->protocol
);
657 pa_idxset_free(u
->subscribed
, NULL
, NULL
);