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 and prioritise by role");
59 PA_MODULE_VERSION(PACKAGE_VERSION);
60 PA_MODULE_LOAD_ONCE(TRUE);
62 "do_routing=<Automatically route streams based on a priority list (unique per-role)?> "
63 "on_hotplug=<When new device becomes available, recheck streams?> "
64 "on_rescue=<When device becomes unavailable, recheck streams?>");
66 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
69 static const char* const valid_modargs[] = {
89 typedef uint32_t role_indexes_t[NUM_ROLES];
94 pa_subscription *subscription;
97 *source_new_hook_slot,
98 *sink_input_new_hook_slot,
99 *source_output_new_hook_slot,
101 *source_put_hook_slot,
102 *sink_unlink_hook_slot,
103 *source_unlink_hook_slot,
104 *connection_unlink_hook_slot;
105 pa_time_event *save_time_event;
106 pa_database *database;
108 pa_native_protocol *protocol;
109 pa_idxset *subscribed;
111 pa_bool_t on_hotplug;
113 pa_bool_t do_routing;
115 role_indexes_t preferred_sinks;
116 role_indexes_t preferred_sources;
119 #define ENTRY_VERSION 1
123 char description[PA_NAME_MAX];
124 role_indexes_t priority;
132 SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING,
133 SUBCOMMAND_PREFER_DEVICE,
134 SUBCOMMAND_DEFER_DEVICE,
135 SUBCOMMAND_SUBSCRIBE,
140 static struct entry* read_entry(struct userdata *u, const char *name) {
147 key.data = (char*) name;
148 key.size = strlen(name);
152 if (!pa_database_get(u->database, &key, &data))
155 if (data.size != sizeof(struct entry)) {
156 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));
160 e = (struct entry*) data.data;
162 if (e->version != ENTRY_VERSION) {
163 pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name);
167 if (!memchr(e->description, 0, sizeof(e->description))) {
168 pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name);
176 pa_datum_free(&data);
181 static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, pa_bool_t sink_mode) {
187 if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index])))
188 pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name);
190 pa_log_debug(" %s No sink specified", human);
193 if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index])))
194 pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name);
196 pa_log_debug(" %s No source specified", human);
200 static void dump_database(struct userdata *u) {
206 done = !pa_database_first(u->database, &key, NULL);
208 pa_log_debug("Dumping database");
214 done = !pa_database_next(u->database, &key, &next_key, NULL);
216 name = pa_xstrndup(key.data, key.size);
218 if ((e = read_entry(u, name))) {
219 pa_log_debug(" Got entry: %s", name);
220 pa_log_debug(" Description: %s", e->description);
221 pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u",
222 e->priority[ROLE_NONE], e->priority[ROLE_VIDEO], e->priority[ROLE_MUSIC], e->priority[ROLE_GAME], e->priority[ROLE_EVENT]);
223 pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u",
224 e->priority[ROLE_PHONE], e->priority[ROLE_ANIMATION], e->priority[ROLE_PRODUCTION], e->priority[ROLE_A11Y]);
234 pa_log_debug(" Highest priority devices per-role:");
236 pa_log_debug(" Sinks:");
237 dump_database_helper(u, ROLE_NONE, "None: ", TRUE);
238 dump_database_helper(u, ROLE_NONE, "Video: ", TRUE);
239 dump_database_helper(u, ROLE_NONE, "Music: ", TRUE);
240 dump_database_helper(u, ROLE_NONE, "Game: ", TRUE);
241 dump_database_helper(u, ROLE_NONE, "Event: ", TRUE);
242 dump_database_helper(u, ROLE_NONE, "Phone: ", TRUE);
243 dump_database_helper(u, ROLE_NONE, "Anim: ", TRUE);
244 dump_database_helper(u, ROLE_NONE, "Prodtn:", TRUE);
245 dump_database_helper(u, ROLE_NONE, "Ally: ", TRUE);
247 pa_log_debug(" Sources:");
248 dump_database_helper(u, ROLE_NONE, "None: ", FALSE);
249 dump_database_helper(u, ROLE_NONE, "Video: ", FALSE);
250 dump_database_helper(u, ROLE_NONE, "Music: ", FALSE);
251 dump_database_helper(u, ROLE_NONE, "Game: ", FALSE);
252 dump_database_helper(u, ROLE_NONE, "Event: ", FALSE);
253 dump_database_helper(u, ROLE_NONE, "Phone: ", FALSE);
254 dump_database_helper(u, ROLE_NONE, "Anim: ", FALSE);
255 dump_database_helper(u, ROLE_NONE, "Prodtn:", FALSE);
256 dump_database_helper(u, ROLE_NONE, "Ally: ", FALSE);
258 pa_log_debug("Completed database dump");
262 static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
263 struct userdata *u = userdata;
269 pa_assert(e == u->save_time_event);
270 u->core->mainloop->time_free(u->save_time_event);
271 u->save_time_event = NULL;
273 pa_database_sync(u->database);
274 pa_log_info("Synced.");
281 static void trigger_save(struct userdata *u) {
282 pa_native_connection *c;
285 for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) {
288 t = pa_tagstruct_new(NULL, 0);
289 pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION);
290 pa_tagstruct_putu32(t, 0);
291 pa_tagstruct_putu32(t, u->module->index);
292 pa_tagstruct_puts(t, u->module->name);
293 pa_tagstruct_putu32(t, SUBCOMMAND_EVENT);
295 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t);
298 if (u->save_time_event)
301 u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
304 static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
305 /** @todo: Compare the priority lists too */
306 if (strncmp(a->description, b->description, sizeof(a->description)))
312 static char *get_name(const char *key, const char *prefix) {
315 if (strncmp(key, prefix, strlen(prefix)))
318 t = pa_xstrdup(key + strlen(prefix));
322 static inline struct entry *load_or_initialize_entry(struct userdata *u, struct entry *entry, const char *name, const char *prefix) {
330 if ((old = read_entry(u, name)))
333 /* This is a new device, so make sure we write it's priority list correctly */
334 role_indexes_t max_priority;
338 pa_zero(max_priority);
339 done = !pa_database_first(u->database, &key, NULL);
341 /* Find all existing devices with the same prefix so we calculate the current max priority for each role */
345 done = !pa_database_next(u->database, &key, &next_key, NULL);
347 if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) {
351 name2 = pa_xstrndup(key.data, key.size);
353 if ((e = read_entry(u, name2))) {
354 for (uint32_t i = 0; i < NUM_ROLES; ++i) {
355 max_priority[i] = PA_MAX(max_priority[i], e->priority[i]);
367 /* Actually initialise our entry now we've calculated it */
368 for (uint32_t i = 0; i < NUM_ROLES; ++i) {
369 entry->priority[i] = max_priority[i] + 1;
376 static uint32_t get_role_index(const char* role) {
379 if (strcmp(role, "") == 0)
381 if (strcmp(role, "video") == 0)
383 if (strcmp(role, "music") == 0)
385 if (strcmp(role, "game") == 0)
387 if (strcmp(role, "event") == 0)
389 if (strcmp(role, "phone") == 0)
391 if (strcmp(role, "animation") == 0)
392 return ROLE_ANIMATION;
393 if (strcmp(role, "production") == 0)
394 return ROLE_PRODUCTION;
395 if (strcmp(role, "a11y") == 0)
397 return PA_INVALID_INDEX;
400 static void update_highest_priority_device_indexes(struct userdata *u, const char *prefix, void *ignore_device) {
401 role_indexes_t *indexes, highest_priority_available;
403 pa_bool_t done, sink_mode;
408 sink_mode = (strcmp(prefix, "sink:") == 0);
411 indexes = &u->preferred_sinks;
413 indexes = &u->preferred_sources;
415 for (uint32_t i = 0; i < NUM_ROLES; ++i) {
416 *indexes[i] = PA_INVALID_INDEX;
418 pa_zero(highest_priority_available);
420 done = !pa_database_first(u->database, &key, NULL);
422 /* Find all existing devices with the same prefix so we find the highest priority device for each role */
426 done = !pa_database_next(u->database, &key, &next_key, NULL);
428 if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) {
432 name = pa_xstrndup(key.data, key.size);
434 if ((e = read_entry(u, name))) {
435 for (uint32_t i = 0; i < NUM_ROLES; ++i) {
436 if (highest_priority_available[i] && e->priority[i] < highest_priority_available[i]) {
437 /* We've found a device with a higher priority than that we've currently got,
438 so see if it is currently available or not and update our list */
440 pa_bool_t found = FALSE;
441 char *device_name = get_name(name, prefix);
446 PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
447 if ((pa_sink*) ignore_device == sink)
449 if (strcmp(sink->name, device_name) == 0) {
451 idx = sink->index; /* Is this needed? */
458 PA_IDXSET_FOREACH(source, u->core->sources, idx) {
459 if ((pa_source*) ignore_device == source)
461 if (strcmp(source->name, device_name) == 0) {
463 idx = source->index; /* Is this needed? */
469 highest_priority_available[i] = e->priority[i];
473 pa_xfree(device_name);
489 static void route_sink_input(struct userdata *u, pa_sink_input *si) {
491 uint32_t role_index, device_index;
495 pa_assert(u->do_routing);
500 /* Skip this if it is already in the process of being moved anyway */
504 /* It might happen that a stream and a sink are set up at the
505 same time, in which case we want to make sure we don't
506 interfere with that */
507 if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si)))
510 if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE)))
511 role_index = get_role_index("");
513 role_index = get_role_index(role);
515 if (PA_INVALID_INDEX == role_index)
518 device_index = u->preferred_sinks[role_index];
519 if (PA_INVALID_INDEX == device_index)
522 if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index)))
525 if (si->sink != sink)
526 pa_sink_input_move_to(si, sink, TRUE);
529 static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) {
538 update_highest_priority_device_indexes(u, "sink:", ignore_sink);
540 PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) {
541 route_sink_input(u, si);
547 static void route_source_output(struct userdata *u, pa_source_output *so) {
549 uint32_t role_index, device_index;
553 pa_assert(u->do_routing);
558 if (so->direct_on_input)
561 /* Skip this if it is already in the process of being moved anyway */
565 /* It might happen that a stream and a source are set up at the
566 same time, in which case we want to make sure we don't
567 interfere with that */
568 if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so)))
571 if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE)))
572 role_index = get_role_index("");
574 role_index = get_role_index(role);
576 if (PA_INVALID_INDEX == role_index)
579 device_index = u->preferred_sources[role_index];
580 if (PA_INVALID_INDEX == device_index)
583 if (!(source = pa_idxset_get_by_index(u->core->sources, device_index)))
586 if (so->source != source)
587 pa_source_output_move_to(so, source, TRUE);
590 static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) {
591 pa_source_output *so;
599 update_highest_priority_device_indexes(u, "source:", ignore_source);
601 PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) {
602 route_source_output(u, so);
608 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
609 struct userdata *u = userdata;
610 struct entry entry, *old = NULL;
617 if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) &&
618 t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) &&
619 t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) &&
620 t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) &&
622 /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
623 t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
624 /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
625 t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
629 entry.version = ENTRY_VERSION;
631 if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
636 if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
639 /* The role may change mid-stream, so we reroute */
640 route_sink_input(u, si);
643 } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
644 pa_source_output *so;
648 if (!(so = pa_idxset_get_by_index(c->source_outputs, idx)))
651 /* The role may change mid-stream, so we reroute */
652 route_source_output(u, so);
655 } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
658 if (!(sink = pa_idxset_get_by_index(c->sinks, idx)))
661 name = pa_sprintf_malloc("sink:%s", sink->name);
663 old = load_or_initialize_entry(u, &entry, name, "sink:");
665 pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
667 } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
670 pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
672 if (!(source = pa_idxset_get_by_index(c->sources, idx)))
675 if (source->monitor_of)
678 name = pa_sprintf_malloc("source:%s", source->name);
680 old = load_or_initialize_entry(u, &entry, name, "source:");
682 pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
689 if (entries_equal(old, &entry)) {
699 key.size = strlen(name);
702 data.size = sizeof(entry);
704 pa_log_info("Storing device %s.", name);
706 pa_database_set(u->database, &key, &data, TRUE);
713 static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
721 name = pa_sprintf_malloc("sink:%s", new_data->name);
723 if ((e = read_entry(u, name))) {
724 if (strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
725 pa_log_info("Restoring description for sink %s.", new_data->name);
726 pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
737 static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
745 name = pa_sprintf_malloc("source:%s", new_data->name);
747 if ((e = read_entry(u, name))) {
748 if (strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
749 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
750 pa_log_info("Restoring description for source %s.", new_data->name);
751 pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
762 static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) {
771 pa_log_debug("Not restoring device for stream, because already set.");
776 if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE)))
777 role_index = get_role_index("");
779 role_index = get_role_index(role);
781 if (PA_INVALID_INDEX != role_index) {
782 uint32_t device_index;
784 device_index = u->preferred_sinks[role_index];
785 if (PA_INVALID_INDEX != device_index) {
788 if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) {
789 new_data->sink = sink;
790 new_data->save_sink = TRUE;
799 static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
807 if (new_data->direct_on_input)
810 if (new_data->source)
811 pa_log_debug("Not restoring device for stream, because already set");
816 if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE)))
817 role_index = get_role_index("");
819 role_index = get_role_index(role);
821 if (PA_INVALID_INDEX != role_index) {
822 uint32_t device_index;
824 device_index = u->preferred_sources[role_index];
825 if (PA_INVALID_INDEX != device_index) {
828 if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) {
829 new_data->source = source;
830 new_data->save_source = TRUE;
840 static pa_hook_result_t sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) {
843 pa_assert(u->core == c);
844 pa_assert(u->on_hotplug);
846 return route_sink_inputs(u, NULL);
849 static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_source *source, struct userdata *u) {
852 pa_assert(u->core == c);
853 pa_assert(u->on_hotplug);
855 return route_source_outputs(u, NULL);
858 static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) {
862 pa_assert(u->core == c);
863 pa_assert(u->on_rescue);
865 /* There's no point in doing anything if the core is shut down anyway */
866 if (c->state == PA_CORE_SHUTDOWN)
869 return route_sink_inputs(u, sink);
872 static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) {
876 pa_assert(u->core == c);
877 pa_assert(u->on_rescue);
879 /* There's no point in doing anything if the core is shut down anyway */
880 if (c->state == PA_CORE_SHUTDOWN)
883 return route_source_outputs(u, source);
887 static void apply_entry(struct userdata *u, const char *name, struct entry *e) {
897 if ((n = get_name(name, "sink:"))) {
898 for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx)) {
899 if (!pa_streq(sink->name, n)) {
903 pa_log_info("Setting description for sink %s.", sink->name);
904 pa_sink_set_description(sink, e->description);
908 else if ((n = get_name(name, "source:"))) {
909 for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx)) {
910 if (!pa_streq(source->name, n)) {
914 if (source->monitor_of) {
915 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source->name);
919 pa_log_info("Setting description for source %s.", source->name);
920 pa_source_set_description(source, e->description);
927 #define EXT_VERSION 1
929 static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) {
932 pa_tagstruct *reply = NULL;
941 if (pa_tagstruct_getu32(t, &command) < 0)
944 reply = pa_tagstruct_new(NULL, 0);
945 pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
946 pa_tagstruct_putu32(reply, tag);
949 case SUBCOMMAND_TEST: {
950 if (!pa_tagstruct_eof(t))
953 pa_tagstruct_putu32(reply, EXT_VERSION);
957 case SUBCOMMAND_READ: {
961 if (!pa_tagstruct_eof(t))
964 done = !pa_database_first(u->database, &key, NULL);
971 done = !pa_database_next(u->database, &key, &next_key, NULL);
973 name = pa_xstrndup(key.data, key.size);
976 if ((e = read_entry(u, name))) {
977 pa_tagstruct_puts(reply, name);
978 pa_tagstruct_puts(reply, e->description);
991 case SUBCOMMAND_RENAME: {
994 const char *device, *description;
996 if (pa_tagstruct_gets(t, &device) < 0 ||
997 pa_tagstruct_gets(t, &description) < 0)
1000 if (!device || !*device || !description || !*description)
1003 if ((e = read_entry(u, device)) && ENTRY_VERSION == e->version) {
1006 pa_strlcpy(e->description, description, sizeof(e->description));
1008 key.data = (char *) device;
1009 key.size = strlen(device);
1012 data.size = sizeof(*e);
1014 if (pa_database_set(u->database, &key, &data, TRUE) == 0) {
1015 apply_entry(u, device, e);
1020 pa_log_warn("Could not save device");
1025 pa_log_warn("Could not rename device %s, no entry in database", device);
1030 case SUBCOMMAND_DELETE:
1032 while (!pa_tagstruct_eof(t)) {
1036 if (pa_tagstruct_gets(t, &name) < 0)
1039 key.data = (char*) name;
1040 key.size = strlen(name);
1042 /** @todo: Reindex the priorities */
1043 pa_database_unset(u->database, &key);
1050 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING: {
1054 if (pa_tagstruct_get_boolean(t, &enable) < 0)
1057 if ((u->do_routing = enable)) {
1058 /* Update our caches */
1059 update_highest_priority_device_indexes(u, "sink:", NULL);
1060 update_highest_priority_device_indexes(u, "source:", NULL);
1066 case SUBCOMMAND_PREFER_DEVICE:
1067 case SUBCOMMAND_DEFER_DEVICE: {
1069 const char *role, *device;
1071 uint32_t role_index;
1073 if (pa_tagstruct_gets(t, &role) < 0 ||
1074 pa_tagstruct_gets(t, &device) < 0)
1077 if (!role || !device || !*device)
1080 role_index = get_role_index(role);
1081 if (PA_INVALID_INDEX == role_index)
1084 if ((e = read_entry(u, device)) && ENTRY_VERSION == e->version) {
1087 char* prefix = NULL;
1089 pa_bool_t haschanged = FALSE;
1091 if (strncmp(device, "sink:", 5) == 0)
1092 prefix = pa_xstrdup("sink:");
1093 else if (strncmp(device, "source:", 7) == 0)
1094 prefix = pa_xstrdup("source:");
1099 priority = e->priority[role_index];
1101 /* Now we need to load up all the other entries of this type and shuffle the priroities around */
1103 done = !pa_database_first(u->database, &key, NULL);
1105 while (!done && !haschanged) {
1108 done = !pa_database_next(u->database, &key, &next_key, NULL);
1110 /* Only read devices with the right prefix */
1111 if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) {
1115 name = pa_xstrndup(key.data, key.size);
1117 if ((e2 = read_entry(u, name))) {
1118 if (SUBCOMMAND_PREFER_DEVICE == command) {
1120 if (e2->priority[role_index] == (priority - 1)) {
1121 e2->priority[role_index]++;
1126 if (e2->priority[role_index] == (priority + 1)) {
1127 e2->priority[role_index]--;
1134 data.size = sizeof(*e2);
1136 if (pa_database_set(u->database, &key, &data, TRUE))
1137 pa_log_warn("Could not save device");
1146 pa_datum_free(&key);
1150 /* Now write out our actual entry */
1152 if (SUBCOMMAND_PREFER_DEVICE == command)
1153 e->priority[role_index]--;
1155 e->priority[role_index]++;
1157 key.data = (char *) device;
1158 key.size = strlen(device);
1161 data.size = sizeof(*e);
1163 if (pa_database_set(u->database, &key, &data, TRUE))
1164 pa_log_warn("Could not save device");
1174 pa_log_warn("Could not reorder device %s, no entry in database", device);
1179 case SUBCOMMAND_SUBSCRIBE: {
1183 if (pa_tagstruct_get_boolean(t, &enabled) < 0 ||
1184 !pa_tagstruct_eof(t))
1188 pa_idxset_put(u->subscribed, c, NULL);
1190 pa_idxset_remove_by_data(u->subscribed, c, NULL);
1199 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply);
1205 pa_tagstruct_free(reply);
1210 static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) {
1215 pa_idxset_remove_by_data(u->subscribed, c, NULL);
1219 int pa__init(pa_module*m) {
1220 pa_modargs *ma = NULL;
1226 pa_bool_t do_routing = FALSE, on_hotplug = TRUE, on_rescue = TRUE;
1230 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
1231 pa_log("Failed to parse module arguments");
1235 if (pa_modargs_get_value_boolean(ma, "do_routing", &do_routing) < 0 ||
1236 pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 ||
1237 pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) {
1238 pa_log("on_hotplug= and on_rescue= expect boolean arguments");
1242 m->userdata = u = pa_xnew0(struct userdata, 1);
1245 u->do_routing = do_routing;
1246 u->on_hotplug = on_hotplug;
1247 u->on_rescue = on_rescue;
1248 u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
1250 u->protocol = pa_native_protocol_get(m->core);
1251 pa_native_protocol_install_ext(u->protocol, m, extension_cb);
1253 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);
1255 u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
1257 /* Used to handle device description management */
1258 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);
1259 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);
1261 /* The following slots are used to deal with routing */
1262 /* A little bit later than module-stream-restore, module-intended-roles */
1263 u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+15, (pa_hook_cb_t) sink_input_new_hook_callback, u);
1264 u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+15, (pa_hook_cb_t) source_output_new_hook_callback, u);
1267 /* A little bit later than module-stream-restore, module-intended-roles */
1268 u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+15, (pa_hook_cb_t) sink_put_hook_callback, u);
1269 u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+15, (pa_hook_cb_t) source_put_hook_callback, u);
1273 /* A little bit later than module-stream-restore, module-intended-roles, a little bit earlier than module-rescue-streams, ... */
1274 u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+15, (pa_hook_cb_t) sink_unlink_hook_callback, u);
1275 u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+15, (pa_hook_cb_t) source_unlink_hook_callback, u);
1278 if (!(fname = pa_state_path("device-manager", TRUE)))
1281 if (!(u->database = pa_database_open(fname, TRUE))) {
1282 pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
1287 pa_log_info("Sucessfully opened database file '%s'.", fname);
1290 /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
1291 PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
1292 subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
1294 PA_IDXSET_FOREACH(source, m->core->sources, idx)
1295 subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u);
1297 /* Perform the routing (if it's enabled) which will update our priority list cache too */
1298 route_sink_inputs(u, NULL);
1299 route_source_outputs(u, NULL);
1301 #ifdef DUMP_DATABASE
1305 pa_modargs_free(ma);
1312 pa_modargs_free(ma);
1317 void pa__done(pa_module*m) {
1322 if (!(u = m->userdata))
1325 if (u->subscription)
1326 pa_subscription_free(u->subscription);
1328 if (u->sink_new_hook_slot)
1329 pa_hook_slot_free(u->sink_new_hook_slot);
1330 if (u->source_new_hook_slot)
1331 pa_hook_slot_free(u->source_new_hook_slot);
1333 if (u->sink_input_new_hook_slot)
1334 pa_hook_slot_free(u->sink_input_new_hook_slot);
1335 if (u->source_output_new_hook_slot)
1336 pa_hook_slot_free(u->source_output_new_hook_slot);
1338 if (u->sink_put_hook_slot)
1339 pa_hook_slot_free(u->sink_put_hook_slot);
1340 if (u->source_put_hook_slot)
1341 pa_hook_slot_free(u->source_put_hook_slot);
1343 if (u->sink_unlink_hook_slot)
1344 pa_hook_slot_free(u->sink_unlink_hook_slot);
1345 if (u->source_unlink_hook_slot)
1346 pa_hook_slot_free(u->source_unlink_hook_slot);
1348 if (u->save_time_event)
1349 u->core->mainloop->time_free(u->save_time_event);
1352 pa_database_close(u->database);
1355 pa_native_protocol_remove_ext(u->protocol, m);
1356 pa_native_protocol_unref(u->protocol);
1360 pa_idxset_free(u->subscribed, NULL, NULL);