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>
54 #include <pulsecore/tagstruct.h>
56 #include "module-device-manager-symdef.h"
58 PA_MODULE_AUTHOR("Colin Guthrie");
59 PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role");
60 PA_MODULE_VERSION(PACKAGE_VERSION
);
61 PA_MODULE_LOAD_ONCE(TRUE
);
63 "do_routing=<Automatically route streams based on a priority list (unique per-role)?> "
64 "on_hotplug=<When new device becomes available, recheck streams?> "
65 "on_rescue=<When device becomes unavailable, recheck streams?>");
67 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
70 static const char* const valid_modargs
[] = {
91 typedef uint32_t role_indexes_t
[NUM_ROLES
];
93 static const char* role_names
[NUM_ROLES
] = {
108 pa_subscription
*subscription
;
111 *source_new_hook_slot
,
112 *sink_input_new_hook_slot
,
113 *source_output_new_hook_slot
,
115 *source_put_hook_slot
,
116 *sink_unlink_hook_slot
,
117 *source_unlink_hook_slot
,
118 *connection_unlink_hook_slot
;
119 pa_time_event
*save_time_event
;
120 pa_database
*database
;
122 pa_native_protocol
*protocol
;
123 pa_idxset
*subscribed
;
125 pa_bool_t on_hotplug
;
127 pa_bool_t do_routing
;
129 role_indexes_t preferred_sinks
;
130 role_indexes_t preferred_sources
;
133 #define ENTRY_VERSION 2
138 pa_bool_t user_set_description
;
140 role_indexes_t priority
;
148 SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
,
150 SUBCOMMAND_SUBSCRIBE
,
155 static struct entry
* entry_new(void) {
156 struct entry
*r
= pa_xnew0(struct entry
, 1);
157 r
->version
= ENTRY_VERSION
;
161 static void entry_free(struct entry
* e
) {
164 pa_xfree(e
->description
);
168 static struct entry
* entry_read(struct userdata
*u
, const char *name
) {
170 struct entry
*e
= NULL
;
171 pa_tagstruct
*t
= NULL
;
172 const char *description
, *icon
;
177 key
.data
= (char*) name
;
178 key
.size
= strlen(name
);
182 if (!pa_database_get(u
->database
, &key
, &data
))
185 t
= pa_tagstruct_new(data
.data
, data
.size
);
188 if (pa_tagstruct_getu8(t
, &e
->version
) < 0 ||
189 e
->version
> ENTRY_VERSION
||
190 pa_tagstruct_gets(t
, &description
) < 0 ||
191 pa_tagstruct_get_boolean(t
, &e
->user_set_description
) < 0 ||
192 pa_tagstruct_gets(t
, &icon
) < 0) {
197 e
->description
= pa_xstrdup(description
);
198 e
->icon
= pa_xstrdup(icon
);
200 for (uint8_t i
=0; i
<ROLE_MAX
; ++i
) {
201 if (pa_tagstruct_getu32(t
, &e
->priority
[i
]) < 0)
205 if (!pa_tagstruct_eof(t
))
208 pa_tagstruct_free(t
);
209 pa_datum_free(&data
);
214 pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name
);
219 pa_tagstruct_free(t
);
220 pa_datum_free(&data
);
224 static pa_bool_t
entry_write(struct userdata
*u
, const char *name
, const struct entry
*e
) {
233 t
= pa_tagstruct_new(NULL
, 0);
234 pa_tagstruct_putu8(t
, e
->version
);
235 pa_tagstruct_puts(t
, e
->description
);
236 pa_tagstruct_put_boolean(t
, e
->user_set_description
);
237 pa_tagstruct_puts(t
, e
->icon
);
238 for (uint8_t i
=0; i
<ROLE_MAX
; ++i
)
239 pa_tagstruct_putu32(t
, e
->priority
[i
]);
241 key
.data
= (char *) name
;
242 key
.size
= strlen(name
);
244 data
.data
= (void*)pa_tagstruct_data(t
, &data
.size
);
246 r
= (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0);
248 pa_tagstruct_free(t
);
254 static void dump_database_helper(struct userdata
*u
, uint32_t role_index
, const char* human
, pa_bool_t sink_mode
) {
260 if (PA_INVALID_INDEX
!= u
->preferred_sinks
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sinks
, u
->preferred_sinks
[role_index
])))
261 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
263 pa_log_debug(" %s No sink specified", human
);
266 if (PA_INVALID_INDEX
!= u
->preferred_sources
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sources
, u
->preferred_sources
[role_index
])))
267 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
269 pa_log_debug(" %s No source specified", human
);
273 static void dump_database(struct userdata
*u
) {
279 done
= !pa_database_first(u
->database
, &key
, NULL
);
281 pa_log_debug("Dumping database");
287 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
289 name
= pa_xstrndup(key
.data
, key
.size
);
291 if ((e
= entry_read(u
, name
))) {
292 pa_log_debug(" Got entry: %s", name
);
293 pa_log_debug(" Description: %s", e
->description
);
294 pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u",
295 e
->priority
[ROLE_NONE
], e
->priority
[ROLE_VIDEO
], e
->priority
[ROLE_MUSIC
], e
->priority
[ROLE_GAME
], e
->priority
[ROLE_EVENT
]);
296 pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u",
297 e
->priority
[ROLE_PHONE
], e
->priority
[ROLE_ANIMATION
], e
->priority
[ROLE_PRODUCTION
], e
->priority
[ROLE_A11Y
]);
308 pa_log_debug(" Highest priority devices per-role:");
310 pa_log_debug(" Sinks:");
311 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
313 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
314 strncpy(name
, role_names
[role
], len
);
315 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
316 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
317 dump_database_helper(u
, role
, name
, TRUE
);
320 pa_log_debug(" Sources:");
321 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
323 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
324 strncpy(name
, role_names
[role
], len
);
325 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
326 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
327 dump_database_helper(u
, role
, name
, FALSE
);
331 pa_log_debug("Completed database dump");
335 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
336 struct userdata
*u
= userdata
;
342 pa_assert(e
== u
->save_time_event
);
343 u
->core
->mainloop
->time_free(u
->save_time_event
);
344 u
->save_time_event
= NULL
;
346 pa_database_sync(u
->database
);
347 pa_log_info("Synced.");
354 static void notify_subscribers(struct userdata
*u
) {
356 pa_native_connection
*c
;
361 for (c
= pa_idxset_first(u
->subscribed
, &idx
); c
; c
= pa_idxset_next(u
->subscribed
, &idx
)) {
364 t
= pa_tagstruct_new(NULL
, 0);
365 pa_tagstruct_putu32(t
, PA_COMMAND_EXTENSION
);
366 pa_tagstruct_putu32(t
, 0);
367 pa_tagstruct_putu32(t
, u
->module
->index
);
368 pa_tagstruct_puts(t
, u
->module
->name
);
369 pa_tagstruct_putu32(t
, SUBCOMMAND_EVENT
);
371 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), t
);
375 static void trigger_save(struct userdata
*u
) {
379 notify_subscribers(u
);
381 if (u
->save_time_event
)
384 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
387 static pa_bool_t
entries_equal(const struct entry
*a
, const struct entry
*b
) {
392 if (!pa_streq(a
->description
, b
->description
)
393 || a
->user_set_description
!= b
->user_set_description
394 || !pa_streq(a
->icon
, b
->icon
))
397 for (int i
=0; i
< NUM_ROLES
; ++i
)
398 if (a
->priority
[i
] != b
->priority
[i
])
404 static char *get_name(const char *key
, const char *prefix
) {
407 if (strncmp(key
, prefix
, strlen(prefix
)))
410 t
= pa_xstrdup(key
+ strlen(prefix
));
414 static inline struct entry
*load_or_initialize_entry(struct userdata
*u
, struct entry
*entry
, const char *name
, const char *prefix
) {
422 if ((old
= entry_read(u
, name
))) {
424 entry
->description
= pa_xstrdup(old
->description
);
425 entry
->icon
= pa_xstrdup(old
->icon
);
427 /* This is a new device, so make sure we write it's priority list correctly */
428 role_indexes_t max_priority
;
432 pa_zero(max_priority
);
433 done
= !pa_database_first(u
->database
, &key
, NULL
);
435 /* Find all existing devices with the same prefix so we calculate the current max priority for each role */
439 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
441 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
445 name2
= pa_xstrndup(key
.data
, key
.size
);
447 if ((e
= entry_read(u
, name2
))) {
448 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
449 max_priority
[i
] = PA_MAX(max_priority
[i
], e
->priority
[i
]);
461 /* Actually initialise our entry now we've calculated it */
462 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
463 entry
->priority
[i
] = max_priority
[i
] + 1;
465 entry
->user_set_description
= FALSE
;
471 static uint32_t get_role_index(const char* role
) {
474 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
)
475 if (strcmp(role
, role_names
[i
]) == 0)
478 return PA_INVALID_INDEX
;
481 static void update_highest_priority_device_indexes(struct userdata
*u
, const char *prefix
, void *ignore_device
) {
482 role_indexes_t
*indexes
, highest_priority_available
;
484 pa_bool_t done
, sink_mode
;
489 sink_mode
= (strcmp(prefix
, "sink:") == 0);
492 indexes
= &u
->preferred_sinks
;
494 indexes
= &u
->preferred_sources
;
496 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
497 (*indexes
)[i
] = PA_INVALID_INDEX
;
499 pa_zero(highest_priority_available
);
501 done
= !pa_database_first(u
->database
, &key
, NULL
);
503 /* Find all existing devices with the same prefix so we find the highest priority device for each role */
507 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
509 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
510 char *name
, *device_name
;
513 name
= pa_xstrndup(key
.data
, key
.size
);
514 device_name
= get_name(name
, prefix
);
516 if ((e
= entry_read(u
, name
))) {
517 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
518 if (!highest_priority_available
[i
] || e
->priority
[i
] < highest_priority_available
[i
]) {
519 /* We've found a device with a higher priority than that we've currently got,
520 so see if it is currently available or not and update our list */
522 pa_bool_t found
= FALSE
;
527 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
) {
528 if ((pa_sink
*) ignore_device
== sink
)
530 if (strcmp(sink
->name
, device_name
) == 0) {
532 idx
= sink
->index
; /* Is this needed? */
539 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
) {
540 if ((pa_source
*) ignore_device
== source
)
542 if (strcmp(source
->name
, device_name
) == 0) {
544 idx
= source
->index
; /* Is this needed? */
550 highest_priority_available
[i
] = e
->priority
[i
];
561 pa_xfree(device_name
);
570 static void route_sink_input(struct userdata
*u
, pa_sink_input
*si
) {
572 uint32_t role_index
, device_index
;
576 pa_assert(u
->do_routing
);
581 /* Skip this if it is already in the process of being moved anyway */
585 /* It might happen that a stream and a sink are set up at the
586 same time, in which case we want to make sure we don't
587 interfere with that */
588 if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si
)))
591 if (!(role
= pa_proplist_gets(si
->proplist
, PA_PROP_MEDIA_ROLE
)))
592 role_index
= get_role_index("none");
594 role_index
= get_role_index(role
);
596 if (PA_INVALID_INDEX
== role_index
)
599 device_index
= u
->preferred_sinks
[role_index
];
600 if (PA_INVALID_INDEX
== device_index
)
603 if (!(sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
)))
606 if (si
->sink
!= sink
)
607 pa_sink_input_move_to(si
, sink
, FALSE
);
610 static pa_hook_result_t
route_sink_inputs(struct userdata
*u
, pa_sink
*ignore_sink
) {
619 update_highest_priority_device_indexes(u
, "sink:", ignore_sink
);
621 PA_IDXSET_FOREACH(si
, u
->core
->sink_inputs
, idx
) {
622 route_sink_input(u
, si
);
628 static void route_source_output(struct userdata
*u
, pa_source_output
*so
) {
630 uint32_t role_index
, device_index
;
634 pa_assert(u
->do_routing
);
639 if (so
->direct_on_input
)
642 /* Skip this if it is already in the process of being moved anyway */
646 /* It might happen that a stream and a source are set up at the
647 same time, in which case we want to make sure we don't
648 interfere with that */
649 if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so
)))
652 if (!(role
= pa_proplist_gets(so
->proplist
, PA_PROP_MEDIA_ROLE
)))
653 role_index
= get_role_index("none");
655 role_index
= get_role_index(role
);
657 if (PA_INVALID_INDEX
== role_index
)
660 device_index
= u
->preferred_sources
[role_index
];
661 if (PA_INVALID_INDEX
== device_index
)
664 if (!(source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
667 if (so
->source
!= source
)
668 pa_source_output_move_to(so
, source
, FALSE
);
671 static pa_hook_result_t
route_source_outputs(struct userdata
*u
, pa_source
* ignore_source
) {
672 pa_source_output
*so
;
680 update_highest_priority_device_indexes(u
, "source:", ignore_source
);
682 PA_IDXSET_FOREACH(so
, u
->core
->source_outputs
, idx
) {
683 route_source_output(u
, so
);
689 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
690 struct userdata
*u
= userdata
;
691 struct entry
*entry
, *old
= NULL
;
697 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
698 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
699 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
700 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
702 /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
703 t
!= (PA_SUBSCRIPTION_EVENT_SINK_INPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
704 /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
705 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
))
710 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK_INPUT
) {
715 if (!(si
= pa_idxset_get_by_index(c
->sink_inputs
, idx
)))
718 /* The role may change mid-stream, so we reroute */
719 route_sink_input(u
, si
);
722 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
) {
723 pa_source_output
*so
;
727 if (!(so
= pa_idxset_get_by_index(c
->source_outputs
, idx
)))
730 /* The role may change mid-stream, so we reroute */
731 route_source_output(u
, so
);
734 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
737 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
740 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
742 old
= load_or_initialize_entry(u
, entry
, name
, "sink:");
744 if (!entry
->user_set_description
) {
745 pa_xfree(entry
->description
);
746 entry
->description
= pa_xstrdup(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
));
747 } else if (!pa_streq(entry
->description
, pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
748 /* Warning: If two modules fight over the description, this could cause an infinite loop.
749 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
750 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
751 the description, this will fail... */
752 pa_sink_set_description(sink
, entry
->description
);
755 pa_xfree(entry
->icon
);
756 entry
->icon
= pa_xstrdup(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_ICON_NAME
));
758 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
) {
761 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
763 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
766 if (source
->monitor_of
)
769 name
= pa_sprintf_malloc("source:%s", source
->name
);
771 old
= load_or_initialize_entry(u
, entry
, name
, "source:");
773 if (!entry
->user_set_description
) {
774 pa_xfree(entry
->description
);
775 entry
->description
= pa_xstrdup(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
));
776 } else if (!pa_streq(entry
->description
, pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
777 /* Warning: If two modules fight over the description, this could cause an infinite loop.
778 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
779 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
780 the description, this will fail... */
781 pa_source_set_description(source
, entry
->description
);
784 pa_xfree(entry
->icon
);
785 entry
->icon
= pa_xstrdup(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_ICON_NAME
));
792 if (entries_equal(old
, entry
)) {
803 pa_log_info("Storing device %s.", name
);
805 if (entry_write(u
, name
, entry
))
808 pa_log_warn("Could not save device");;
814 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
822 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
824 if ((e
= entry_read(u
, name
))) {
825 if (e
->user_set_description
&& strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
826 pa_log_info("Restoring description for sink %s.", new_data
->name
);
827 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
838 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
846 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
848 if ((e
= entry_read(u
, name
))) {
849 if (e
->user_set_description
&& strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
850 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
851 pa_log_info("Restoring description for source %s.", new_data
->name
);
852 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
863 static pa_hook_result_t
sink_input_new_hook_callback(pa_core
*c
, pa_sink_input_new_data
*new_data
, struct userdata
*u
) {
875 pa_log_debug("Not restoring device for stream because already set.");
877 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
878 role_index
= get_role_index("none");
880 role_index
= get_role_index(role
);
882 if (PA_INVALID_INDEX
!= role_index
) {
883 uint32_t device_index
;
885 device_index
= u
->preferred_sinks
[role_index
];
886 if (PA_INVALID_INDEX
!= device_index
) {
889 if ((sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
))) {
890 if (!pa_sink_input_new_data_set_sink(new_data
, sink
, FALSE
))
891 pa_log_debug("Not restoring device for stream because no supported format was found");
900 static pa_hook_result_t
source_output_new_hook_callback(pa_core
*c
, pa_source_output_new_data
*new_data
, struct userdata
*u
) {
911 if (new_data
->direct_on_input
)
914 if (new_data
->source
)
915 pa_log_debug("Not restoring device for stream because already set.");
917 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
918 role_index
= get_role_index("none");
920 role_index
= get_role_index(role
);
922 if (PA_INVALID_INDEX
!= role_index
) {
923 uint32_t device_index
;
925 device_index
= u
->preferred_sources
[role_index
];
926 if (PA_INVALID_INDEX
!= device_index
) {
929 if ((source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
930 if (!pa_source_output_new_data_set_source(new_data
, source
, FALSE
))
931 pa_log_debug("Not restoring device for stream because no supported format was found");
940 static pa_hook_result_t
sink_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_sink
*sink
, struct userdata
*u
) {
943 pa_assert(u
->core
== c
);
944 pa_assert(u
->on_hotplug
);
946 notify_subscribers(u
);
948 return route_sink_inputs(u
, NULL
);
951 static pa_hook_result_t
source_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_source
*source
, struct userdata
*u
) {
954 pa_assert(u
->core
== c
);
955 pa_assert(u
->on_hotplug
);
957 notify_subscribers(u
);
959 return route_source_outputs(u
, NULL
);
962 static pa_hook_result_t
sink_unlink_hook_callback(pa_core
*c
, pa_sink
*sink
, struct userdata
*u
) {
966 pa_assert(u
->core
== c
);
967 pa_assert(u
->on_rescue
);
969 /* There's no point in doing anything if the core is shut down anyway */
970 if (c
->state
== PA_CORE_SHUTDOWN
)
973 notify_subscribers(u
);
975 return route_sink_inputs(u
, sink
);
978 static pa_hook_result_t
source_unlink_hook_callback(pa_core
*c
, pa_source
*source
, struct userdata
*u
) {
982 pa_assert(u
->core
== c
);
983 pa_assert(u
->on_rescue
);
985 /* There's no point in doing anything if the core is shut down anyway */
986 if (c
->state
== PA_CORE_SHUTDOWN
)
989 notify_subscribers(u
);
991 return route_source_outputs(u
, source
);
995 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
1003 if (!e
->user_set_description
)
1006 if ((n
= get_name(name
, "sink:"))) {
1008 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1009 if (!pa_streq(s
->name
, n
)) {
1013 pa_log_info("Setting description for sink %s to '%s'", s
->name
, e
->description
);
1014 pa_sink_set_description(s
, e
->description
);
1018 else if ((n
= get_name(name
, "source:"))) {
1020 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1021 if (!pa_streq(s
->name
, n
)) {
1025 if (s
->monitor_of
) {
1026 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s
->name
);
1030 pa_log_info("Setting description for source %s to '%s'", s
->name
, e
->description
);
1031 pa_source_set_description(s
, e
->description
);
1038 #define EXT_VERSION 1
1040 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
1043 pa_tagstruct
*reply
= NULL
;
1052 if (pa_tagstruct_getu32(t
, &command
) < 0)
1055 reply
= pa_tagstruct_new(NULL
, 0);
1056 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
1057 pa_tagstruct_putu32(reply
, tag
);
1060 case SUBCOMMAND_TEST
: {
1061 if (!pa_tagstruct_eof(t
))
1064 pa_tagstruct_putu32(reply
, EXT_VERSION
);
1068 case SUBCOMMAND_READ
: {
1072 if (!pa_tagstruct_eof(t
))
1075 done
= !pa_database_first(u
->database
, &key
, NULL
);
1082 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1084 name
= pa_xstrndup(key
.data
, key
.size
);
1085 pa_datum_free(&key
);
1087 if ((e
= entry_read(u
, name
))) {
1090 uint32_t found_index
= PA_INVALID_INDEX
;
1092 if ((device_name
= get_name(name
, "sink:"))) {
1094 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1095 if (strcmp(s
->name
, device_name
) == 0) {
1096 found_index
= s
->index
;
1100 pa_xfree(device_name
);
1101 } else if ((device_name
= get_name(name
, "source:"))) {
1103 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1104 if (strcmp(s
->name
, device_name
) == 0) {
1105 found_index
= s
->index
;
1109 pa_xfree(device_name
);
1112 pa_tagstruct_puts(reply
, name
);
1113 pa_tagstruct_puts(reply
, e
->description
);
1114 pa_tagstruct_puts(reply
, e
->icon
);
1115 pa_tagstruct_putu32(reply
, found_index
);
1116 pa_tagstruct_putu32(reply
, NUM_ROLES
);
1118 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
) {
1119 pa_tagstruct_puts(reply
, role_names
[i
]);
1120 pa_tagstruct_putu32(reply
, e
->priority
[i
]);
1134 case SUBCOMMAND_RENAME
: {
1137 const char *device
, *description
;
1139 if (pa_tagstruct_gets(t
, &device
) < 0 ||
1140 pa_tagstruct_gets(t
, &description
) < 0)
1143 if (!device
|| !*device
|| !description
|| !*description
)
1146 if ((e
= entry_read(u
, device
))) {
1147 pa_xfree(e
->description
);
1148 e
->description
= pa_xstrdup(description
);
1149 e
->user_set_description
= TRUE
;
1151 if (entry_write(u
, (char *)device
, e
)) {
1152 apply_entry(u
, device
, e
);
1157 pa_log_warn("Could not save device");
1162 pa_log_warn("Could not rename device %s, no entry in database", device
);
1167 case SUBCOMMAND_DELETE
:
1169 while (!pa_tagstruct_eof(t
)) {
1173 if (pa_tagstruct_gets(t
, &name
) < 0)
1176 key
.data
= (char*) name
;
1177 key
.size
= strlen(name
);
1179 /** @todo: Reindex the priorities */
1180 pa_database_unset(u
->database
, &key
);
1187 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
: {
1191 if (pa_tagstruct_get_boolean(t
, &enable
) < 0)
1194 if ((u
->do_routing
= enable
)) {
1195 /* Update our caches */
1196 update_highest_priority_device_indexes(u
, "sink:", NULL
);
1197 update_highest_priority_device_indexes(u
, "source:", NULL
);
1203 case SUBCOMMAND_REORDER
: {
1207 uint32_t role_index
, n_devices
;
1209 pa_bool_t done
, sink_mode
= TRUE
;
1210 struct device_t
{ uint32_t prio
; char *device
; };
1211 struct device_t
*device
;
1212 struct device_t
**devices
;
1213 uint32_t i
, idx
, offset
;
1218 if (pa_tagstruct_gets(t
, &role
) < 0 ||
1219 pa_tagstruct_getu32(t
, &n_devices
) < 0 ||
1223 if (PA_INVALID_INDEX
== (role_index
= get_role_index(role
)))
1226 /* Cycle through the devices given and make sure they exist */
1227 h
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1230 for (i
= 0; i
< n_devices
; ++i
) {
1232 if (pa_tagstruct_gets(t
, &s
) < 0) {
1233 while ((device
= pa_hashmap_steal_first(h
))) {
1234 pa_xfree(device
->device
);
1238 pa_hashmap_free(h
, NULL
, NULL
);
1239 pa_log_error("Protocol error on reorder");
1243 /* Ensure this is a valid entry */
1244 if (!(e
= entry_read(u
, s
))) {
1245 while ((device
= pa_hashmap_steal_first(h
))) {
1246 pa_xfree(device
->device
);
1250 pa_hashmap_free(h
, NULL
, NULL
);
1251 pa_log_error("Client specified an unknown device in it's reorder list.");
1258 sink_mode
= (0 == strncmp("sink:", s
, 5));
1259 } else if ((sink_mode
&& 0 != strncmp("sink:", s
, 5)) || (!sink_mode
&& 0 != strncmp("source:", s
, 7))) {
1260 while ((device
= pa_hashmap_steal_first(h
))) {
1261 pa_xfree(device
->device
);
1265 pa_hashmap_free(h
, NULL
, NULL
);
1266 pa_log_error("Attempted to reorder mixed devices (sinks and sources)");
1270 /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
1271 device
= pa_xnew(struct device_t
, 1);
1272 device
->device
= pa_xstrdup(s
);
1273 if (pa_hashmap_put(h
, device
->device
, device
) == 0) {
1277 pa_xfree(device
->device
);
1282 /*pa_log_debug("Hashmap contents (received from client)");
1283 PA_HASHMAP_FOREACH(device, h, state) {
1284 pa_log_debug(" - %s (%d)", device->device, device->prio);
1287 /* Now cycle through our list and add all the devices.
1288 This has the effect of addign in any in our DB,
1289 not specified in the device list (and thus will be
1290 tacked on at the end) */
1292 done
= !pa_database_first(u
->database
, &key
, NULL
);
1294 while (!done
&& idx
< 256) {
1297 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1299 device
= pa_xnew(struct device_t
, 1);
1300 device
->device
= pa_xstrndup(key
.data
, key
.size
);
1301 if ((sink_mode
&& 0 == strncmp("sink:", device
->device
, 5))
1302 || (!sink_mode
&& 0 == strncmp("source:", device
->device
, 7))) {
1304 /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
1305 if (pa_hashmap_put(h
, device
->device
, device
) == 0
1306 && (e
= entry_read(u
, device
->device
))) {
1307 /* We add offset on to the existing priorirty so that when we order, the
1308 existing entries are always lower priority than the new ones. */
1309 device
->prio
= (offset
+ e
->priority
[role_index
]);
1313 pa_xfree(device
->device
);
1317 pa_xfree(device
->device
);
1321 pa_datum_free(&key
);
1326 /*pa_log_debug("Hashmap contents (combined with database)");
1327 PA_HASHMAP_FOREACH(device, h, state) {
1328 pa_log_debug(" - %s (%d)", device->device, device->prio);
1331 /* Now we put all the entries in a simple list for sorting it. */
1332 n_devices
= pa_hashmap_size(h
);
1333 devices
= pa_xnew(struct device_t
*, n_devices
);
1335 while ((device
= pa_hashmap_steal_first(h
))) {
1336 devices
[idx
++] = device
;
1338 pa_hashmap_free(h
, NULL
, NULL
);
1340 /* Simple bubble sort */
1341 for (i
= 0; i
< n_devices
; ++i
) {
1342 for (uint32_t j
= i
; j
< n_devices
; ++j
) {
1343 if (devices
[i
]->prio
> devices
[j
]->prio
) {
1344 struct device_t
*tmp
;
1346 devices
[i
] = devices
[j
];
1352 /*pa_log_debug("Sorted device list");
1353 for (i = 0; i < n_devices; ++i) {
1354 pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio);
1357 /* Go through in order and write the new entry and cleanup our own list */
1360 for (i
= 0; i
< n_devices
; ++i
) {
1361 if ((e
= entry_read(u
, devices
[i
]->device
))) {
1362 if (e
->priority
[role_index
] == idx
)
1365 e
->priority
[role_index
] = idx
;
1367 if (entry_write(u
, (char *) devices
[i
]->device
, e
)) {
1375 pa_xfree(devices
[i
]->device
);
1376 pa_xfree(devices
[i
]);
1383 route_sink_inputs(u
, NULL
);
1385 route_source_outputs(u
, NULL
);
1391 case SUBCOMMAND_SUBSCRIBE
: {
1395 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
1396 !pa_tagstruct_eof(t
))
1400 pa_idxset_put(u
->subscribed
, c
, NULL
);
1402 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1411 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
1417 pa_tagstruct_free(reply
);
1422 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
1427 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1431 struct prioritised_indexes
{
1436 int pa__init(pa_module
*m
) {
1437 pa_modargs
*ma
= NULL
;
1443 pa_bool_t do_routing
= FALSE
, on_hotplug
= TRUE
, on_rescue
= TRUE
;
1444 uint32_t total_devices
;
1448 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
1449 pa_log("Failed to parse module arguments");
1453 if (pa_modargs_get_value_boolean(ma
, "do_routing", &do_routing
) < 0 ||
1454 pa_modargs_get_value_boolean(ma
, "on_hotplug", &on_hotplug
) < 0 ||
1455 pa_modargs_get_value_boolean(ma
, "on_rescue", &on_rescue
) < 0) {
1456 pa_log("on_hotplug= and on_rescue= expect boolean arguments");
1460 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
1463 u
->do_routing
= do_routing
;
1464 u
->on_hotplug
= on_hotplug
;
1465 u
->on_rescue
= on_rescue
;
1466 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
1468 u
->protocol
= pa_native_protocol_get(m
->core
);
1469 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
1471 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
);
1473 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
);
1475 /* Used to handle device description management */
1476 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
);
1477 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
);
1479 /* The following slots are used to deal with routing */
1480 /* A little bit later than module-stream-restore, but before module-intended-roles */
1481 u
->sink_input_new_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SINK_INPUT_NEW
], PA_HOOK_EARLY
+5, (pa_hook_cb_t
) sink_input_new_hook_callback
, u
);
1482 u
->source_output_new_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SOURCE_OUTPUT_NEW
], PA_HOOK_EARLY
+5, (pa_hook_cb_t
) source_output_new_hook_callback
, u
);
1485 /* A little bit later than module-stream-restore, but before module-intended-roles */
1486 u
->sink_put_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SINK_PUT
], PA_HOOK_LATE
+5, (pa_hook_cb_t
) sink_put_hook_callback
, u
);
1487 u
->source_put_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SOURCE_PUT
], PA_HOOK_LATE
+5, (pa_hook_cb_t
) source_put_hook_callback
, u
);
1491 /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */
1492 u
->sink_unlink_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SINK_UNLINK
], PA_HOOK_LATE
+5, (pa_hook_cb_t
) sink_unlink_hook_callback
, u
);
1493 u
->source_unlink_hook_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SOURCE_UNLINK
], PA_HOOK_LATE
+5, (pa_hook_cb_t
) source_unlink_hook_callback
, u
);
1496 if (!(fname
= pa_state_path("device-manager", TRUE
)))
1499 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
1500 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
1505 pa_log_info("Successfully opened database file '%s'.", fname
);
1508 /* Attempt to inject the devices into the list in priority order */
1509 total_devices
= PA_MAX(pa_idxset_size(m
->core
->sinks
), pa_idxset_size(m
->core
->sources
));
1510 if (total_devices
> 0 && total_devices
< 128) {
1512 struct prioritised_indexes p_i
[128];
1514 /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
1516 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
) {
1517 pa_log_debug("Found sink index %u", sink
->index
);
1518 p_i
[i
].index
= sink
->index
;
1519 p_i
[i
++].priority
= sink
->priority
;
1521 /* Bubble sort it (only really useful for first time creation) */
1523 for (uint32_t j
= 0; j
< i
; ++j
)
1524 for (uint32_t k
= 0; k
< i
; ++k
)
1525 if (p_i
[j
].priority
> p_i
[k
].priority
) {
1526 struct prioritised_indexes tmp_pi
= p_i
[k
];
1531 for (uint32_t j
= 0; j
< i
; ++j
)
1532 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, p_i
[j
].index
, u
);
1535 /* We cycle over all the available sources so that they are added to our database if they are not in it yet */
1537 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
) {
1538 p_i
[i
].index
= source
->index
;
1539 p_i
[i
++].priority
= source
->priority
;
1541 /* Bubble sort it (only really useful for first time creation) */
1543 for (uint32_t j
= 0; j
< i
; ++j
)
1544 for (uint32_t k
= 0; k
< i
; ++k
)
1545 if (p_i
[j
].priority
> p_i
[k
].priority
) {
1546 struct prioritised_indexes tmp_pi
= p_i
[k
];
1551 for (uint32_t j
= 0; j
< i
; ++j
)
1552 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, p_i
[j
].index
, u
);
1554 else if (total_devices
> 0) {
1555 /* This user has a *lot* of devices... */
1556 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
)
1557 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
1559 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
)
1560 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
1563 /* Perform the routing (if it's enabled) which will update our priority list cache too */
1564 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
1565 u
->preferred_sinks
[i
] = u
->preferred_sources
[i
] = PA_INVALID_INDEX
;
1568 route_sink_inputs(u
, NULL
);
1569 route_source_outputs(u
, NULL
);
1571 #ifdef DUMP_DATABASE
1575 pa_modargs_free(ma
);
1582 pa_modargs_free(ma
);
1587 void pa__done(pa_module
*m
) {
1592 if (!(u
= m
->userdata
))
1595 if (u
->subscription
)
1596 pa_subscription_free(u
->subscription
);
1598 if (u
->sink_new_hook_slot
)
1599 pa_hook_slot_free(u
->sink_new_hook_slot
);
1600 if (u
->source_new_hook_slot
)
1601 pa_hook_slot_free(u
->source_new_hook_slot
);
1603 if (u
->sink_input_new_hook_slot
)
1604 pa_hook_slot_free(u
->sink_input_new_hook_slot
);
1605 if (u
->source_output_new_hook_slot
)
1606 pa_hook_slot_free(u
->source_output_new_hook_slot
);
1608 if (u
->sink_put_hook_slot
)
1609 pa_hook_slot_free(u
->sink_put_hook_slot
);
1610 if (u
->source_put_hook_slot
)
1611 pa_hook_slot_free(u
->source_put_hook_slot
);
1613 if (u
->sink_unlink_hook_slot
)
1614 pa_hook_slot_free(u
->sink_unlink_hook_slot
);
1615 if (u
->source_unlink_hook_slot
)
1616 pa_hook_slot_free(u
->source_unlink_hook_slot
);
1618 if (u
->connection_unlink_hook_slot
)
1619 pa_hook_slot_free(u
->connection_unlink_hook_slot
);
1621 if (u
->save_time_event
)
1622 u
->core
->mainloop
->time_free(u
->save_time_event
);
1625 pa_database_close(u
->database
);
1628 pa_native_protocol_remove_ext(u
->protocol
, m
);
1629 pa_native_protocol_unref(u
->protocol
);
1633 pa_idxset_free(u
->subscribed
, NULL
, NULL
);