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
];
91 static const char* role_names
[NUM_ROLES
] = {
106 pa_subscription
*subscription
;
109 *source_new_hook_slot
,
110 *sink_input_new_hook_slot
,
111 *source_output_new_hook_slot
,
113 *source_put_hook_slot
,
114 *sink_unlink_hook_slot
,
115 *source_unlink_hook_slot
,
116 *connection_unlink_hook_slot
;
117 pa_time_event
*save_time_event
;
118 pa_database
*database
;
120 pa_native_protocol
*protocol
;
121 pa_idxset
*subscribed
;
123 pa_bool_t on_hotplug
;
125 pa_bool_t do_routing
;
127 role_indexes_t preferred_sinks
;
128 role_indexes_t preferred_sources
;
131 #define ENTRY_VERSION 1
135 char description
[PA_NAME_MAX
];
136 char icon
[PA_NAME_MAX
];
137 role_indexes_t priority
;
145 SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
,
146 SUBCOMMAND_PREFER_DEVICE
,
147 SUBCOMMAND_DEFER_DEVICE
,
148 SUBCOMMAND_SUBSCRIBE
,
153 static struct entry
* read_entry(struct userdata
*u
, const char *name
) {
160 key
.data
= (char*) name
;
161 key
.size
= strlen(name
);
165 if (!pa_database_get(u
->database
, &key
, &data
))
168 if (data
.size
!= sizeof(struct entry
)) {
169 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
));
173 e
= (struct entry
*) data
.data
;
175 if (e
->version
!= ENTRY_VERSION
) {
176 pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name
);
180 if (!memchr(e
->description
, 0, sizeof(e
->description
))) {
181 pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name
);
189 pa_datum_free(&data
);
194 static void dump_database_helper(struct userdata
*u
, uint32_t role_index
, const char* human
, pa_bool_t sink_mode
) {
200 if (PA_INVALID_INDEX
!= u
->preferred_sinks
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sinks
, u
->preferred_sinks
[role_index
])))
201 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
203 pa_log_debug(" %s No sink specified", human
);
206 if (PA_INVALID_INDEX
!= u
->preferred_sinks
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sinks
, u
->preferred_sinks
[role_index
])))
207 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
209 pa_log_debug(" %s No source specified", human
);
213 static void dump_database(struct userdata
*u
) {
219 done
= !pa_database_first(u
->database
, &key
, NULL
);
221 pa_log_debug("Dumping database");
227 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
229 name
= pa_xstrndup(key
.data
, key
.size
);
231 if ((e
= read_entry(u
, name
))) {
232 pa_log_debug(" Got entry: %s", name
);
233 pa_log_debug(" Description: %s", e
->description
);
234 pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u",
235 e
->priority
[ROLE_NONE
], e
->priority
[ROLE_VIDEO
], e
->priority
[ROLE_MUSIC
], e
->priority
[ROLE_GAME
], e
->priority
[ROLE_EVENT
]);
236 pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u",
237 e
->priority
[ROLE_PHONE
], e
->priority
[ROLE_ANIMATION
], e
->priority
[ROLE_PRODUCTION
], e
->priority
[ROLE_A11Y
]);
247 pa_log_debug(" Highest priority devices per-role:");
249 pa_log_debug(" Sinks:");
250 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
252 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
253 strncpy(name
, role_names
[role
], len
);
254 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
255 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
256 dump_database_helper(u
, role
, name
, TRUE
);
259 pa_log_debug(" Sources:");
260 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
262 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
263 strncpy(name
, role_names
[role
], len
);
264 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
265 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
266 dump_database_helper(u
, role
, name
, FALSE
);
269 pa_log_debug("Completed database dump");
273 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
274 struct userdata
*u
= userdata
;
280 pa_assert(e
== u
->save_time_event
);
281 u
->core
->mainloop
->time_free(u
->save_time_event
);
282 u
->save_time_event
= NULL
;
284 pa_database_sync(u
->database
);
285 pa_log_info("Synced.");
292 static void notify_subscribers(struct userdata
*u
) {
294 pa_native_connection
*c
;
299 for (c
= pa_idxset_first(u
->subscribed
, &idx
); c
; c
= pa_idxset_next(u
->subscribed
, &idx
)) {
302 t
= pa_tagstruct_new(NULL
, 0);
303 pa_tagstruct_putu32(t
, PA_COMMAND_EXTENSION
);
304 pa_tagstruct_putu32(t
, 0);
305 pa_tagstruct_putu32(t
, u
->module
->index
);
306 pa_tagstruct_puts(t
, u
->module
->name
);
307 pa_tagstruct_putu32(t
, SUBCOMMAND_EVENT
);
309 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), t
);
313 static void trigger_save(struct userdata
*u
) {
317 notify_subscribers(u
);
319 if (u
->save_time_event
)
322 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
325 static pa_bool_t
entries_equal(const struct entry
*a
, const struct entry
*b
) {
330 if (strncmp(a
->description
, b
->description
, sizeof(a
->description
))
331 || strncmp(a
->icon
, b
->icon
, sizeof(a
->icon
)))
334 for (int i
=0; i
< NUM_ROLES
; ++i
)
335 if (a
->priority
[i
] != b
->priority
[i
])
341 static char *get_name(const char *key
, const char *prefix
) {
344 if (strncmp(key
, prefix
, strlen(prefix
)))
347 t
= pa_xstrdup(key
+ strlen(prefix
));
351 static inline struct entry
*load_or_initialize_entry(struct userdata
*u
, struct entry
*entry
, const char *name
, const char *prefix
) {
359 if ((old
= read_entry(u
, name
)))
362 /* This is a new device, so make sure we write it's priority list correctly */
363 role_indexes_t max_priority
;
367 pa_zero(max_priority
);
368 done
= !pa_database_first(u
->database
, &key
, NULL
);
370 /* Find all existing devices with the same prefix so we calculate the current max priority for each role */
374 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
376 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
380 name2
= pa_xstrndup(key
.data
, key
.size
);
382 if ((e
= read_entry(u
, name2
))) {
383 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
384 max_priority
[i
] = PA_MAX(max_priority
[i
], e
->priority
[i
]);
396 /* Actually initialise our entry now we've calculated it */
397 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
398 entry
->priority
[i
] = max_priority
[i
] + 1;
405 static uint32_t get_role_index(const char* role
) {
408 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
)
409 if (strcmp(role
, role_names
[i
]) == 0)
412 return PA_INVALID_INDEX
;
415 static void update_highest_priority_device_indexes(struct userdata
*u
, const char *prefix
, void *ignore_device
) {
416 role_indexes_t
*indexes
, highest_priority_available
;
418 pa_bool_t done
, sink_mode
;
423 sink_mode
= (strcmp(prefix
, "sink:") == 0);
426 indexes
= &u
->preferred_sinks
;
428 indexes
= &u
->preferred_sources
;
430 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
431 *indexes
[i
] = PA_INVALID_INDEX
;
433 pa_zero(highest_priority_available
);
435 done
= !pa_database_first(u
->database
, &key
, NULL
);
437 /* Find all existing devices with the same prefix so we find the highest priority device for each role */
441 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
443 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
447 name
= pa_xstrndup(key
.data
, key
.size
);
449 if ((e
= read_entry(u
, name
))) {
450 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
451 if (highest_priority_available
[i
] && e
->priority
[i
] < highest_priority_available
[i
]) {
452 /* We've found a device with a higher priority than that we've currently got,
453 so see if it is currently available or not and update our list */
455 pa_bool_t found
= FALSE
;
456 char *device_name
= get_name(name
, prefix
);
461 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
) {
462 if ((pa_sink
*) ignore_device
== sink
)
464 if (strcmp(sink
->name
, device_name
) == 0) {
466 idx
= sink
->index
; /* Is this needed? */
473 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
) {
474 if ((pa_source
*) ignore_device
== source
)
476 if (strcmp(source
->name
, device_name
) == 0) {
478 idx
= source
->index
; /* Is this needed? */
484 highest_priority_available
[i
] = e
->priority
[i
];
488 pa_xfree(device_name
);
504 static void route_sink_input(struct userdata
*u
, pa_sink_input
*si
) {
506 uint32_t role_index
, device_index
;
510 pa_assert(u
->do_routing
);
515 /* Skip this if it is already in the process of being moved anyway */
519 /* It might happen that a stream and a sink are set up at the
520 same time, in which case we want to make sure we don't
521 interfere with that */
522 if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si
)))
525 if (!(role
= pa_proplist_gets(si
->proplist
, PA_PROP_MEDIA_ROLE
)))
526 role_index
= get_role_index("none");
528 role_index
= get_role_index(role
);
530 if (PA_INVALID_INDEX
== role_index
)
533 device_index
= u
->preferred_sinks
[role_index
];
534 if (PA_INVALID_INDEX
== device_index
)
537 if (!(sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
)))
540 if (si
->sink
!= sink
)
541 pa_sink_input_move_to(si
, sink
, TRUE
);
544 static pa_hook_result_t
route_sink_inputs(struct userdata
*u
, pa_sink
*ignore_sink
) {
553 update_highest_priority_device_indexes(u
, "sink:", ignore_sink
);
555 PA_IDXSET_FOREACH(si
, u
->core
->sink_inputs
, idx
) {
556 route_sink_input(u
, si
);
562 static void route_source_output(struct userdata
*u
, pa_source_output
*so
) {
564 uint32_t role_index
, device_index
;
568 pa_assert(u
->do_routing
);
573 if (so
->direct_on_input
)
576 /* Skip this if it is already in the process of being moved anyway */
580 /* It might happen that a stream and a source are set up at the
581 same time, in which case we want to make sure we don't
582 interfere with that */
583 if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so
)))
586 if (!(role
= pa_proplist_gets(so
->proplist
, PA_PROP_MEDIA_ROLE
)))
587 role_index
= get_role_index("none");
589 role_index
= get_role_index(role
);
591 if (PA_INVALID_INDEX
== role_index
)
594 device_index
= u
->preferred_sources
[role_index
];
595 if (PA_INVALID_INDEX
== device_index
)
598 if (!(source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
601 if (so
->source
!= source
)
602 pa_source_output_move_to(so
, source
, TRUE
);
605 static pa_hook_result_t
route_source_outputs(struct userdata
*u
, pa_source
* ignore_source
) {
606 pa_source_output
*so
;
614 update_highest_priority_device_indexes(u
, "source:", ignore_source
);
616 PA_IDXSET_FOREACH(so
, u
->core
->source_outputs
, idx
) {
617 route_source_output(u
, so
);
623 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
624 struct userdata
*u
= userdata
;
625 struct entry entry
, *old
= NULL
;
632 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
633 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
634 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
635 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
637 /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
638 t
!= (PA_SUBSCRIPTION_EVENT_SINK_INPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
639 /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
640 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
))
644 entry
.version
= ENTRY_VERSION
;
646 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK_INPUT
) {
651 if (!(si
= pa_idxset_get_by_index(c
->sink_inputs
, idx
)))
654 /* The role may change mid-stream, so we reroute */
655 route_sink_input(u
, si
);
658 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
) {
659 pa_source_output
*so
;
663 if (!(so
= pa_idxset_get_by_index(c
->source_outputs
, idx
)))
666 /* The role may change mid-stream, so we reroute */
667 route_source_output(u
, so
);
670 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
673 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
676 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
678 old
= load_or_initialize_entry(u
, &entry
, name
, "sink:");
680 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
681 pa_strlcpy(entry
.icon
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_ICON_NAME
)), sizeof(entry
.icon
));
683 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
) {
686 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
688 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
691 if (source
->monitor_of
)
694 name
= pa_sprintf_malloc("source:%s", source
->name
);
696 old
= load_or_initialize_entry(u
, &entry
, name
, "source:");
698 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
699 pa_strlcpy(entry
.icon
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_ICON_NAME
)), sizeof(entry
.icon
));
706 if (entries_equal(old
, &entry
)) {
717 key
.size
= strlen(name
);
720 data
.size
= sizeof(entry
);
722 pa_log_info("Storing device %s.", name
);
724 pa_database_set(u
->database
, &key
, &data
, TRUE
);
731 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
739 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
741 if ((e
= read_entry(u
, name
))) {
742 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
743 pa_log_info("Restoring description for sink %s.", new_data
->name
);
744 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
755 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
763 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
765 if ((e
= read_entry(u
, name
))) {
766 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
767 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
768 pa_log_info("Restoring description for source %s.", new_data
->name
);
769 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
780 static pa_hook_result_t
sink_input_new_hook_callback(pa_core
*c
, pa_sink_input_new_data
*new_data
, struct userdata
*u
) {
789 pa_log_debug("Not restoring device for stream, because already set.");
794 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
795 role_index
= get_role_index("");
797 role_index
= get_role_index(role
);
799 if (PA_INVALID_INDEX
!= role_index
) {
800 uint32_t device_index
;
802 device_index
= u
->preferred_sinks
[role_index
];
803 if (PA_INVALID_INDEX
!= device_index
) {
806 if ((sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
))) {
807 new_data
->sink
= sink
;
808 new_data
->save_sink
= TRUE
;
817 static pa_hook_result_t
source_output_new_hook_callback(pa_core
*c
, pa_source_output_new_data
*new_data
, struct userdata
*u
) {
825 if (new_data
->direct_on_input
)
828 if (new_data
->source
)
829 pa_log_debug("Not restoring device for stream, because already set");
834 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
835 role_index
= get_role_index("");
837 role_index
= get_role_index(role
);
839 if (PA_INVALID_INDEX
!= role_index
) {
840 uint32_t device_index
;
842 device_index
= u
->preferred_sources
[role_index
];
843 if (PA_INVALID_INDEX
!= device_index
) {
846 if ((source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
))) {
847 new_data
->source
= source
;
848 new_data
->save_source
= TRUE
;
858 static pa_hook_result_t
sink_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_sink
*sink
, struct userdata
*u
) {
861 pa_assert(u
->core
== c
);
862 pa_assert(u
->on_hotplug
);
864 notify_subscribers(u
);
866 return route_sink_inputs(u
, NULL
);
869 static pa_hook_result_t
source_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_source
*source
, struct userdata
*u
) {
872 pa_assert(u
->core
== c
);
873 pa_assert(u
->on_hotplug
);
875 notify_subscribers(u
);
877 return route_source_outputs(u
, NULL
);
880 static pa_hook_result_t
sink_unlink_hook_callback(pa_core
*c
, pa_sink
*sink
, struct userdata
*u
) {
884 pa_assert(u
->core
== c
);
885 pa_assert(u
->on_rescue
);
887 /* There's no point in doing anything if the core is shut down anyway */
888 if (c
->state
== PA_CORE_SHUTDOWN
)
891 notify_subscribers(u
);
893 return route_sink_inputs(u
, sink
);
896 static pa_hook_result_t
source_unlink_hook_callback(pa_core
*c
, pa_source
*source
, struct userdata
*u
) {
900 pa_assert(u
->core
== c
);
901 pa_assert(u
->on_rescue
);
903 /* There's no point in doing anything if the core is shut down anyway */
904 if (c
->state
== PA_CORE_SHUTDOWN
)
907 notify_subscribers(u
);
909 return route_source_outputs(u
, source
);
913 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
923 if ((n
= get_name(name
, "sink:"))) {
924 for (sink
= pa_idxset_first(u
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(u
->core
->sinks
, &idx
)) {
925 if (!pa_streq(sink
->name
, n
)) {
929 pa_log_info("Setting description for sink %s.", sink
->name
);
930 pa_sink_set_description(sink
, e
->description
);
934 else if ((n
= get_name(name
, "source:"))) {
935 for (source
= pa_idxset_first(u
->core
->sources
, &idx
); source
; source
= pa_idxset_next(u
->core
->sources
, &idx
)) {
936 if (!pa_streq(source
->name
, n
)) {
940 if (source
->monitor_of
) {
941 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source
->name
);
945 pa_log_info("Setting description for source %s.", source
->name
);
946 pa_source_set_description(source
, e
->description
);
953 #define EXT_VERSION 1
955 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
958 pa_tagstruct
*reply
= NULL
;
967 if (pa_tagstruct_getu32(t
, &command
) < 0)
970 reply
= pa_tagstruct_new(NULL
, 0);
971 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
972 pa_tagstruct_putu32(reply
, tag
);
975 case SUBCOMMAND_TEST
: {
976 if (!pa_tagstruct_eof(t
))
979 pa_tagstruct_putu32(reply
, EXT_VERSION
);
983 case SUBCOMMAND_READ
: {
987 if (!pa_tagstruct_eof(t
))
990 done
= !pa_database_first(u
->database
, &key
, NULL
);
997 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
999 name
= pa_xstrndup(key
.data
, key
.size
);
1000 pa_datum_free(&key
);
1002 if ((e
= read_entry(u
, name
))) {
1005 pa_bool_t available
= FALSE
;
1007 if ((devname
= get_name(name
, "sink:"))) {
1009 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1010 if (strcmp(s
->name
, devname
) == 0) {
1016 } else if ((devname
= get_name(name
, "source:"))) {
1018 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1019 if (strcmp(s
->name
, devname
) == 0) {
1027 pa_tagstruct_puts(reply
, name
);
1028 pa_tagstruct_puts(reply
, e
->description
);
1029 pa_tagstruct_puts(reply
, e
->icon
);
1030 pa_tagstruct_put_boolean(reply
, available
);
1031 pa_tagstruct_putu32(reply
, NUM_ROLES
);
1033 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
) {
1034 pa_tagstruct_puts(reply
, role_names
[i
]);
1035 pa_tagstruct_putu32(reply
, e
->priority
[i
]);
1049 case SUBCOMMAND_RENAME
: {
1052 const char *device
, *description
;
1054 if (pa_tagstruct_gets(t
, &device
) < 0 ||
1055 pa_tagstruct_gets(t
, &description
) < 0)
1058 if (!device
|| !*device
|| !description
|| !*description
)
1061 if ((e
= read_entry(u
, device
)) && ENTRY_VERSION
== e
->version
) {
1064 pa_strlcpy(e
->description
, description
, sizeof(e
->description
));
1066 key
.data
= (char *) device
;
1067 key
.size
= strlen(device
);
1070 data
.size
= sizeof(*e
);
1072 if (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0) {
1073 apply_entry(u
, device
, e
);
1078 pa_log_warn("Could not save device");
1083 pa_log_warn("Could not rename device %s, no entry in database", device
);
1088 case SUBCOMMAND_DELETE
:
1090 while (!pa_tagstruct_eof(t
)) {
1094 if (pa_tagstruct_gets(t
, &name
) < 0)
1097 key
.data
= (char*) name
;
1098 key
.size
= strlen(name
);
1100 /** @todo: Reindex the priorities */
1101 pa_database_unset(u
->database
, &key
);
1108 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
: {
1112 if (pa_tagstruct_get_boolean(t
, &enable
) < 0)
1115 if ((u
->do_routing
= enable
)) {
1116 /* Update our caches */
1117 update_highest_priority_device_indexes(u
, "sink:", NULL
);
1118 update_highest_priority_device_indexes(u
, "source:", NULL
);
1124 case SUBCOMMAND_PREFER_DEVICE
:
1125 case SUBCOMMAND_DEFER_DEVICE
: {
1127 const char *role
, *device
;
1129 uint32_t role_index
;
1131 if (pa_tagstruct_gets(t
, &role
) < 0 ||
1132 pa_tagstruct_gets(t
, &device
) < 0)
1135 if (!role
|| !device
|| !*device
)
1138 role_index
= get_role_index(role
);
1139 if (PA_INVALID_INDEX
== role_index
)
1142 if ((e
= read_entry(u
, device
)) && ENTRY_VERSION
== e
->version
) {
1145 char* prefix
= NULL
;
1147 pa_bool_t haschanged
= FALSE
;
1149 if (strncmp(device
, "sink:", 5) == 0)
1150 prefix
= pa_xstrdup("sink:");
1151 else if (strncmp(device
, "source:", 7) == 0)
1152 prefix
= pa_xstrdup("source:");
1157 priority
= e
->priority
[role_index
];
1159 /* Now we need to load up all the other entries of this type and shuffle the priroities around */
1161 done
= !pa_database_first(u
->database
, &key
, NULL
);
1163 while (!done
&& !haschanged
) {
1166 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1168 /* Only read devices with the right prefix */
1169 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
1173 name
= pa_xstrndup(key
.data
, key
.size
);
1175 if ((e2
= read_entry(u
, name
))) {
1176 if (SUBCOMMAND_PREFER_DEVICE
== command
) {
1178 if (e2
->priority
[role_index
] == (priority
- 1)) {
1179 e2
->priority
[role_index
]++;
1184 if (e2
->priority
[role_index
] == (priority
+ 1)) {
1185 e2
->priority
[role_index
]--;
1192 data
.size
= sizeof(*e2
);
1194 if (pa_database_set(u
->database
, &key
, &data
, TRUE
))
1195 pa_log_warn("Could not save device");
1204 pa_datum_free(&key
);
1208 /* Now write out our actual entry */
1210 if (SUBCOMMAND_PREFER_DEVICE
== command
)
1211 e
->priority
[role_index
]--;
1213 e
->priority
[role_index
]++;
1215 key
.data
= (char *) device
;
1216 key
.size
= strlen(device
);
1219 data
.size
= sizeof(*e
);
1221 if (pa_database_set(u
->database
, &key
, &data
, TRUE
))
1222 pa_log_warn("Could not save device");
1232 pa_log_warn("Could not reorder device %s, no entry in database", device
);
1237 case SUBCOMMAND_SUBSCRIBE
: {
1241 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
1242 !pa_tagstruct_eof(t
))
1246 pa_idxset_put(u
->subscribed
, c
, NULL
);
1248 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1257 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
1263 pa_tagstruct_free(reply
);
1268 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
1273 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1277 int pa__init(pa_module
*m
) {
1278 pa_modargs
*ma
= NULL
;
1284 pa_bool_t do_routing
= FALSE
, on_hotplug
= TRUE
, on_rescue
= TRUE
;
1288 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
1289 pa_log("Failed to parse module arguments");
1293 if (pa_modargs_get_value_boolean(ma
, "do_routing", &do_routing
) < 0 ||
1294 pa_modargs_get_value_boolean(ma
, "on_hotplug", &on_hotplug
) < 0 ||
1295 pa_modargs_get_value_boolean(ma
, "on_rescue", &on_rescue
) < 0) {
1296 pa_log("on_hotplug= and on_rescue= expect boolean arguments");
1300 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
1303 u
->do_routing
= do_routing
;
1304 u
->on_hotplug
= on_hotplug
;
1305 u
->on_rescue
= on_rescue
;
1306 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
1308 u
->protocol
= pa_native_protocol_get(m
->core
);
1309 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
1311 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
);
1313 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
);
1315 /* Used to handle device description management */
1316 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
);
1317 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
);
1319 /* The following slots are used to deal with routing */
1320 /* A little bit later than module-stream-restore, module-intended-roles */
1321 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
);
1322 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
);
1325 /* A little bit later than module-stream-restore, module-intended-roles */
1326 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
);
1327 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
);
1331 /* A little bit later than module-stream-restore, module-intended-roles, a little bit earlier than module-rescue-streams, ... */
1332 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
);
1333 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
);
1336 if (!(fname
= pa_state_path("device-manager", TRUE
)))
1339 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
1340 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
1345 pa_log_info("Sucessfully opened database file '%s'.", fname
);
1348 /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
1349 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
)
1350 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
1352 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
)
1353 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
1355 /* Perform the routing (if it's enabled) which will update our priority list cache too */
1356 route_sink_inputs(u
, NULL
);
1357 route_source_outputs(u
, NULL
);
1359 #ifdef DUMP_DATABASE
1363 pa_modargs_free(ma
);
1370 pa_modargs_free(ma
);
1375 void pa__done(pa_module
*m
) {
1380 if (!(u
= m
->userdata
))
1383 if (u
->subscription
)
1384 pa_subscription_free(u
->subscription
);
1386 if (u
->sink_new_hook_slot
)
1387 pa_hook_slot_free(u
->sink_new_hook_slot
);
1388 if (u
->source_new_hook_slot
)
1389 pa_hook_slot_free(u
->source_new_hook_slot
);
1391 if (u
->sink_input_new_hook_slot
)
1392 pa_hook_slot_free(u
->sink_input_new_hook_slot
);
1393 if (u
->source_output_new_hook_slot
)
1394 pa_hook_slot_free(u
->source_output_new_hook_slot
);
1396 if (u
->sink_put_hook_slot
)
1397 pa_hook_slot_free(u
->sink_put_hook_slot
);
1398 if (u
->source_put_hook_slot
)
1399 pa_hook_slot_free(u
->source_put_hook_slot
);
1401 if (u
->sink_unlink_hook_slot
)
1402 pa_hook_slot_free(u
->sink_unlink_hook_slot
);
1403 if (u
->source_unlink_hook_slot
)
1404 pa_hook_slot_free(u
->source_unlink_hook_slot
);
1406 if (u
->save_time_event
)
1407 u
->core
->mainloop
->time_free(u
->save_time_event
);
1410 pa_database_close(u
->database
);
1413 pa_native_protocol_remove_ext(u
->protocol
, m
);
1414 pa_native_protocol_unref(u
->protocol
);
1418 pa_idxset_free(u
->subscribed
, NULL
, NULL
);