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 pa_bool_t user_set_description
;
137 char icon
[PA_NAME_MAX
];
138 role_indexes_t priority
;
146 SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
,
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
);
185 if (!memchr(e
->icon
, 0, sizeof(e
->icon
))) {
186 pa_log_warn("Database contains entry for device %s with missing NUL byte in icon", name
);
194 pa_datum_free(&data
);
199 static void dump_database_helper(struct userdata
*u
, uint32_t role_index
, const char* human
, pa_bool_t sink_mode
) {
205 if (PA_INVALID_INDEX
!= u
->preferred_sinks
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sinks
, u
->preferred_sinks
[role_index
])))
206 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
208 pa_log_debug(" %s No sink specified", human
);
211 if (PA_INVALID_INDEX
!= u
->preferred_sources
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sources
, u
->preferred_sources
[role_index
])))
212 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
214 pa_log_debug(" %s No source specified", human
);
218 static void dump_database(struct userdata
*u
) {
224 done
= !pa_database_first(u
->database
, &key
, NULL
);
226 pa_log_debug("Dumping database");
232 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
234 name
= pa_xstrndup(key
.data
, key
.size
);
236 if ((e
= read_entry(u
, name
))) {
237 pa_log_debug(" Got entry: %s", name
);
238 pa_log_debug(" Description: %s", e
->description
);
239 pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u",
240 e
->priority
[ROLE_NONE
], e
->priority
[ROLE_VIDEO
], e
->priority
[ROLE_MUSIC
], e
->priority
[ROLE_GAME
], e
->priority
[ROLE_EVENT
]);
241 pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u",
242 e
->priority
[ROLE_PHONE
], e
->priority
[ROLE_ANIMATION
], e
->priority
[ROLE_PRODUCTION
], e
->priority
[ROLE_A11Y
]);
253 pa_log_debug(" Highest priority devices per-role:");
255 pa_log_debug(" Sinks:");
256 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
258 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
259 strncpy(name
, role_names
[role
], len
);
260 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
261 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
262 dump_database_helper(u
, role
, name
, TRUE
);
265 pa_log_debug(" Sources:");
266 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
268 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
269 strncpy(name
, role_names
[role
], len
);
270 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
271 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
272 dump_database_helper(u
, role
, name
, FALSE
);
276 pa_log_debug("Completed database dump");
280 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
281 struct userdata
*u
= userdata
;
287 pa_assert(e
== u
->save_time_event
);
288 u
->core
->mainloop
->time_free(u
->save_time_event
);
289 u
->save_time_event
= NULL
;
291 pa_database_sync(u
->database
);
292 pa_log_info("Synced.");
299 static void notify_subscribers(struct userdata
*u
) {
301 pa_native_connection
*c
;
306 for (c
= pa_idxset_first(u
->subscribed
, &idx
); c
; c
= pa_idxset_next(u
->subscribed
, &idx
)) {
309 t
= pa_tagstruct_new(NULL
, 0);
310 pa_tagstruct_putu32(t
, PA_COMMAND_EXTENSION
);
311 pa_tagstruct_putu32(t
, 0);
312 pa_tagstruct_putu32(t
, u
->module
->index
);
313 pa_tagstruct_puts(t
, u
->module
->name
);
314 pa_tagstruct_putu32(t
, SUBCOMMAND_EVENT
);
316 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), t
);
320 static void trigger_save(struct userdata
*u
) {
324 notify_subscribers(u
);
326 if (u
->save_time_event
)
329 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
332 static pa_bool_t
entries_equal(const struct entry
*a
, const struct entry
*b
) {
337 if (strncmp(a
->description
, b
->description
, sizeof(a
->description
))
338 || a
->user_set_description
!= b
->user_set_description
339 || strncmp(a
->icon
, b
->icon
, sizeof(a
->icon
)))
342 for (int i
=0; i
< NUM_ROLES
; ++i
)
343 if (a
->priority
[i
] != b
->priority
[i
])
349 static char *get_name(const char *key
, const char *prefix
) {
352 if (strncmp(key
, prefix
, strlen(prefix
)))
355 t
= pa_xstrdup(key
+ strlen(prefix
));
359 static inline struct entry
*load_or_initialize_entry(struct userdata
*u
, struct entry
*entry
, const char *name
, const char *prefix
) {
367 if ((old
= read_entry(u
, name
)))
370 /* This is a new device, so make sure we write it's priority list correctly */
371 role_indexes_t max_priority
;
375 pa_zero(max_priority
);
376 done
= !pa_database_first(u
->database
, &key
, NULL
);
378 /* Find all existing devices with the same prefix so we calculate the current max priority for each role */
382 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
384 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
388 name2
= pa_xstrndup(key
.data
, key
.size
);
390 if ((e
= read_entry(u
, name2
))) {
391 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
392 max_priority
[i
] = PA_MAX(max_priority
[i
], e
->priority
[i
]);
404 /* Actually initialise our entry now we've calculated it */
405 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
406 entry
->priority
[i
] = max_priority
[i
] + 1;
408 entry
->user_set_description
= FALSE
;
414 static uint32_t get_role_index(const char* role
) {
417 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
)
418 if (strcmp(role
, role_names
[i
]) == 0)
421 return PA_INVALID_INDEX
;
424 static void update_highest_priority_device_indexes(struct userdata
*u
, const char *prefix
, void *ignore_device
) {
425 role_indexes_t
*indexes
, highest_priority_available
;
427 pa_bool_t done
, sink_mode
;
432 sink_mode
= (strcmp(prefix
, "sink:") == 0);
435 indexes
= &u
->preferred_sinks
;
437 indexes
= &u
->preferred_sources
;
439 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
440 (*indexes
)[i
] = PA_INVALID_INDEX
;
442 pa_zero(highest_priority_available
);
444 done
= !pa_database_first(u
->database
, &key
, NULL
);
446 /* Find all existing devices with the same prefix so we find the highest priority device for each role */
450 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
452 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
453 char *name
, *device_name
;
456 name
= pa_xstrndup(key
.data
, key
.size
);
457 device_name
= get_name(name
, prefix
);
459 if ((e
= read_entry(u
, name
))) {
460 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
461 if (!highest_priority_available
[i
] || e
->priority
[i
] < highest_priority_available
[i
]) {
462 /* We've found a device with a higher priority than that we've currently got,
463 so see if it is currently available or not and update our list */
465 pa_bool_t found
= FALSE
;
470 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
) {
471 if ((pa_sink
*) ignore_device
== sink
)
473 if (strcmp(sink
->name
, device_name
) == 0) {
475 idx
= sink
->index
; /* Is this needed? */
482 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
) {
483 if ((pa_source
*) ignore_device
== source
)
485 if (strcmp(source
->name
, device_name
) == 0) {
487 idx
= source
->index
; /* Is this needed? */
493 highest_priority_available
[i
] = e
->priority
[i
];
504 pa_xfree(device_name
);
513 static void route_sink_input(struct userdata
*u
, pa_sink_input
*si
) {
515 uint32_t role_index
, device_index
;
519 pa_assert(u
->do_routing
);
521 /* Skip this if it is already in the process of being moved anyway */
525 /* It might happen that a stream and a sink are set up at the
526 same time, in which case we want to make sure we don't
527 interfere with that */
528 if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si
)))
531 if (!(role
= pa_proplist_gets(si
->proplist
, PA_PROP_MEDIA_ROLE
)))
532 role_index
= get_role_index("none");
534 role_index
= get_role_index(role
);
536 if (PA_INVALID_INDEX
== role_index
)
539 device_index
= u
->preferred_sinks
[role_index
];
540 if (PA_INVALID_INDEX
== device_index
)
543 if (!(sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
)))
546 if (si
->sink
!= sink
)
547 pa_sink_input_move_to(si
, sink
, TRUE
);
550 static pa_hook_result_t
route_sink_inputs(struct userdata
*u
, pa_sink
*ignore_sink
) {
559 update_highest_priority_device_indexes(u
, "sink:", ignore_sink
);
561 PA_IDXSET_FOREACH(si
, u
->core
->sink_inputs
, idx
) {
562 route_sink_input(u
, si
);
568 static void route_source_output(struct userdata
*u
, pa_source_output
*so
) {
570 uint32_t role_index
, device_index
;
574 pa_assert(u
->do_routing
);
576 if (so
->direct_on_input
)
579 /* Skip this if it is already in the process of being moved anyway */
583 /* It might happen that a stream and a source are set up at the
584 same time, in which case we want to make sure we don't
585 interfere with that */
586 if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so
)))
589 if (!(role
= pa_proplist_gets(so
->proplist
, PA_PROP_MEDIA_ROLE
)))
590 role_index
= get_role_index("none");
592 role_index
= get_role_index(role
);
594 if (PA_INVALID_INDEX
== role_index
)
597 device_index
= u
->preferred_sources
[role_index
];
598 if (PA_INVALID_INDEX
== device_index
)
601 if (!(source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
604 if (so
->source
!= source
)
605 pa_source_output_move_to(so
, source
, TRUE
);
608 static pa_hook_result_t
route_source_outputs(struct userdata
*u
, pa_source
* ignore_source
) {
609 pa_source_output
*so
;
617 update_highest_priority_device_indexes(u
, "source:", ignore_source
);
619 PA_IDXSET_FOREACH(so
, u
->core
->source_outputs
, idx
) {
620 route_source_output(u
, so
);
626 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
627 struct userdata
*u
= userdata
;
628 struct entry entry
, *old
= NULL
;
635 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
636 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
637 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
638 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
640 /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
641 t
!= (PA_SUBSCRIPTION_EVENT_SINK_INPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
642 /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
643 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
))
647 entry
.version
= ENTRY_VERSION
;
649 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK_INPUT
) {
654 if (!(si
= pa_idxset_get_by_index(c
->sink_inputs
, idx
)))
657 /* The role may change mid-stream, so we reroute */
658 route_sink_input(u
, si
);
661 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
) {
662 pa_source_output
*so
;
666 if (!(so
= pa_idxset_get_by_index(c
->source_outputs
, idx
)))
669 /* The role may change mid-stream, so we reroute */
670 route_source_output(u
, so
);
673 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
676 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
679 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
681 old
= load_or_initialize_entry(u
, &entry
, name
, "sink:");
683 if (!entry
.user_set_description
)
684 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
685 else if (strncmp(entry
.description
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
)) != 0) {
686 /* Warning: If two modules fight over the description, this could cause an infinite loop.
687 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
688 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
689 the description, this will fail... */
690 pa_sink_set_description(sink
, entry
.description
);
693 pa_strlcpy(entry
.icon
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_ICON_NAME
)), sizeof(entry
.icon
));
695 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
) {
698 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
700 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
703 if (source
->monitor_of
)
706 name
= pa_sprintf_malloc("source:%s", source
->name
);
708 old
= load_or_initialize_entry(u
, &entry
, name
, "source:");
710 if (!entry
.user_set_description
)
711 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
712 else if (strncmp(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
)) != 0) {
713 /* Warning: If two modules fight over the description, this could cause an infinite loop.
714 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
715 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
716 the description, this will fail... */
717 pa_source_set_description(source
, entry
.description
);
720 pa_strlcpy(entry
.icon
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_ICON_NAME
)), sizeof(entry
.icon
));
727 if (entries_equal(old
, &entry
)) {
738 key
.size
= strlen(name
);
741 data
.size
= sizeof(entry
);
743 pa_log_info("Storing device %s.", name
);
745 if (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0)
748 pa_log_warn("Could not save device");;
753 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
761 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
763 if ((e
= read_entry(u
, name
))) {
764 if (e
->user_set_description
&& strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
765 pa_log_info("Restoring description for sink %s.", new_data
->name
);
766 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
777 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
785 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
787 if ((e
= read_entry(u
, name
))) {
788 if (e
->user_set_description
&& strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
789 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
790 pa_log_info("Restoring description for source %s.", new_data
->name
);
791 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
802 static pa_hook_result_t
sink_input_new_hook_callback(pa_core
*c
, pa_sink_input_new_data
*new_data
, struct userdata
*u
) {
814 pa_log_debug("Overriding device for stream, even although it is already set. I am evil that way...");
816 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
817 role_index
= get_role_index("none");
819 role_index
= get_role_index(role
);
821 if (PA_INVALID_INDEX
!= role_index
) {
822 uint32_t device_index
;
824 device_index
= u
->preferred_sinks
[role_index
];
825 if (PA_INVALID_INDEX
!= device_index
) {
828 if ((sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
))) {
829 new_data
->sink
= sink
;
837 static pa_hook_result_t
source_output_new_hook_callback(pa_core
*c
, pa_source_output_new_data
*new_data
, struct userdata
*u
) {
848 if (new_data
->direct_on_input
)
851 if (new_data
->source
)
852 pa_log_debug("Overriding device for stream, even although it is already set. I am evil that way...");
854 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
855 role_index
= get_role_index("none");
857 role_index
= get_role_index(role
);
859 if (PA_INVALID_INDEX
!= role_index
) {
860 uint32_t device_index
;
862 device_index
= u
->preferred_sources
[role_index
];
863 if (PA_INVALID_INDEX
!= device_index
) {
866 if ((source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
))) {
867 new_data
->source
= source
;
876 static pa_hook_result_t
sink_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_sink
*sink
, struct userdata
*u
) {
879 pa_assert(u
->core
== c
);
880 pa_assert(u
->on_hotplug
);
882 notify_subscribers(u
);
884 return route_sink_inputs(u
, NULL
);
887 static pa_hook_result_t
source_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_source
*source
, struct userdata
*u
) {
890 pa_assert(u
->core
== c
);
891 pa_assert(u
->on_hotplug
);
893 notify_subscribers(u
);
895 return route_source_outputs(u
, NULL
);
898 static pa_hook_result_t
sink_unlink_hook_callback(pa_core
*c
, pa_sink
*sink
, struct userdata
*u
) {
902 pa_assert(u
->core
== c
);
903 pa_assert(u
->on_rescue
);
905 /* There's no point in doing anything if the core is shut down anyway */
906 if (c
->state
== PA_CORE_SHUTDOWN
)
909 notify_subscribers(u
);
911 return route_sink_inputs(u
, sink
);
914 static pa_hook_result_t
source_unlink_hook_callback(pa_core
*c
, pa_source
*source
, struct userdata
*u
) {
918 pa_assert(u
->core
== c
);
919 pa_assert(u
->on_rescue
);
921 /* There's no point in doing anything if the core is shut down anyway */
922 if (c
->state
== PA_CORE_SHUTDOWN
)
925 notify_subscribers(u
);
927 return route_source_outputs(u
, source
);
931 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
941 if (!e
->user_set_description
)
944 if ((n
= get_name(name
, "sink:"))) {
945 for (sink
= pa_idxset_first(u
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(u
->core
->sinks
, &idx
)) {
946 if (!pa_streq(sink
->name
, n
)) {
950 pa_log_info("Setting description for sink %s to '%s'", sink
->name
, e
->description
);
951 pa_sink_set_description(sink
, e
->description
);
955 else if ((n
= get_name(name
, "source:"))) {
956 for (source
= pa_idxset_first(u
->core
->sources
, &idx
); source
; source
= pa_idxset_next(u
->core
->sources
, &idx
)) {
957 if (!pa_streq(source
->name
, n
)) {
961 if (source
->monitor_of
) {
962 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source
->name
);
966 pa_log_info("Setting description for source %s to '%s'", source
->name
, e
->description
);
967 pa_source_set_description(source
, e
->description
);
974 #define EXT_VERSION 1
976 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
979 pa_tagstruct
*reply
= NULL
;
988 if (pa_tagstruct_getu32(t
, &command
) < 0)
991 reply
= pa_tagstruct_new(NULL
, 0);
992 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
993 pa_tagstruct_putu32(reply
, tag
);
996 case SUBCOMMAND_TEST
: {
997 if (!pa_tagstruct_eof(t
))
1000 pa_tagstruct_putu32(reply
, EXT_VERSION
);
1004 case SUBCOMMAND_READ
: {
1008 if (!pa_tagstruct_eof(t
))
1011 done
= !pa_database_first(u
->database
, &key
, NULL
);
1018 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1020 name
= pa_xstrndup(key
.data
, key
.size
);
1021 pa_datum_free(&key
);
1023 if ((e
= read_entry(u
, name
))) {
1026 pa_bool_t available
= FALSE
;
1028 if ((devname
= get_name(name
, "sink:"))) {
1030 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1031 if (strcmp(s
->name
, devname
) == 0) {
1037 } else if ((devname
= get_name(name
, "source:"))) {
1039 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1040 if (strcmp(s
->name
, devname
) == 0) {
1048 pa_tagstruct_puts(reply
, name
);
1049 pa_tagstruct_puts(reply
, e
->description
);
1050 pa_tagstruct_puts(reply
, e
->icon
);
1051 pa_tagstruct_put_boolean(reply
, available
);
1052 pa_tagstruct_putu32(reply
, NUM_ROLES
);
1054 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
) {
1055 pa_tagstruct_puts(reply
, role_names
[i
]);
1056 pa_tagstruct_putu32(reply
, e
->priority
[i
]);
1070 case SUBCOMMAND_RENAME
: {
1073 const char *device
, *description
;
1075 if (pa_tagstruct_gets(t
, &device
) < 0 ||
1076 pa_tagstruct_gets(t
, &description
) < 0)
1079 if (!device
|| !*device
|| !description
|| !*description
)
1082 if ((e
= read_entry(u
, device
))) {
1085 pa_strlcpy(e
->description
, description
, sizeof(e
->description
));
1086 e
->user_set_description
= TRUE
;
1088 key
.data
= (char *) device
;
1089 key
.size
= strlen(device
);
1092 data
.size
= sizeof(*e
);
1094 if (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0) {
1095 apply_entry(u
, device
, e
);
1100 pa_log_warn("Could not save device");
1105 pa_log_warn("Could not rename device %s, no entry in database", device
);
1110 case SUBCOMMAND_DELETE
:
1112 while (!pa_tagstruct_eof(t
)) {
1116 if (pa_tagstruct_gets(t
, &name
) < 0)
1119 key
.data
= (char*) name
;
1120 key
.size
= strlen(name
);
1122 /** @todo: Reindex the priorities */
1123 pa_database_unset(u
->database
, &key
);
1130 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
: {
1134 if (pa_tagstruct_get_boolean(t
, &enable
) < 0)
1137 if ((u
->do_routing
= enable
)) {
1138 /* Update our caches */
1139 update_highest_priority_device_indexes(u
, "sink:", NULL
);
1140 update_highest_priority_device_indexes(u
, "source:", NULL
);
1146 case SUBCOMMAND_REORDER
: {
1150 uint32_t role_index
, n_devices
;
1152 pa_bool_t done
, sink_mode
= TRUE
;
1153 struct device_t
{ uint32_t prio
; char *device
; };
1154 struct device_t
*device
;
1155 struct device_t
**devices
;
1156 uint32_t i
, idx
, offset
;
1161 if (pa_tagstruct_gets(t
, &role
) < 0 ||
1162 pa_tagstruct_getu32(t
, &n_devices
) < 0 ||
1166 if (PA_INVALID_INDEX
== (role_index
= get_role_index(role
)))
1169 /* Cycle through the devices given and make sure they exist */
1170 h
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1173 for (i
= 0; i
< n_devices
; ++i
) {
1175 if (pa_tagstruct_gets(t
, &s
) < 0) {
1176 while ((device
= pa_hashmap_steal_first(h
))) {
1177 pa_xfree(device
->device
);
1181 pa_hashmap_free(h
, NULL
, NULL
);
1182 pa_log_error("Protocol error on reorder");
1186 /* Ensure this is a valid entry */
1187 if (!(e
= read_entry(u
, s
))) {
1188 while ((device
= pa_hashmap_steal_first(h
))) {
1189 pa_xfree(device
->device
);
1193 pa_hashmap_free(h
, NULL
, NULL
);
1194 pa_log_error("Client specified an unknown device in it's reorder list.");
1201 sink_mode
= (0 == strncmp("sink:", s
, 5));
1202 } else if ((sink_mode
&& 0 != strncmp("sink:", s
, 5))
1203 || (!sink_mode
&& 0 != strncmp("source:", s
, 7)))
1205 while ((device
= pa_hashmap_steal_first(h
))) {
1206 pa_xfree(device
->device
);
1210 pa_hashmap_free(h
, NULL
, NULL
);
1211 pa_log_error("Attempted to reorder mixed devices (sinks and sources)");
1215 /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
1216 device
= pa_xnew(struct device_t
, 1);
1217 device
->device
= pa_xstrdup(s
);
1218 if (pa_hashmap_put(h
, device
->device
, device
) == 0) {
1222 pa_xfree(device
->device
);
1227 /*pa_log_debug("Hashmap contents (received from client)");
1228 PA_HASHMAP_FOREACH(device, h, state) {
1229 pa_log_debug(" - %s (%d)", device->device, device->prio);
1232 /* Now cycle through our list and add all the devices.
1233 This has the effect of addign in any in our DB,
1234 not specified in the device list (and thus will be
1235 tacked on at the end) */
1237 done
= !pa_database_first(u
->database
, &key
, NULL
);
1239 while (!done
&& idx
< 256) {
1242 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1244 device
= pa_xnew(struct device_t
, 1);
1245 device
->device
= pa_xstrndup(key
.data
, key
.size
);
1246 if ((sink_mode
&& 0 == strncmp("sink:", device
->device
, 5))
1247 || (!sink_mode
&& 0 == strncmp("source:", device
->device
, 7))) {
1249 /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
1250 if (pa_hashmap_put(h
, device
->device
, device
) == 0
1251 && (e
= read_entry(u
, device
->device
))) {
1252 /* We add offset on to the existing priorirty so that when we order, the
1253 existing entries are always lower priority than the new ones. */
1254 device
->prio
= (offset
+ e
->priority
[role_index
]);
1258 pa_xfree(device
->device
);
1262 pa_xfree(device
->device
);
1266 pa_datum_free(&key
);
1271 /*pa_log_debug("Hashmap contents (combined with database)");
1272 PA_HASHMAP_FOREACH(device, h, state) {
1273 pa_log_debug(" - %s (%d)", device->device, device->prio);
1276 /* Now we put all the entries in a simple list for sorting it. */
1277 n_devices
= pa_hashmap_size(h
);
1278 devices
= pa_xnew(struct device_t
*, n_devices
);
1280 while ((device
= pa_hashmap_steal_first(h
))) {
1281 devices
[idx
++] = device
;
1283 pa_hashmap_free(h
, NULL
, NULL
);
1285 /* Simple bubble sort */
1286 for (i
= 0; i
< n_devices
; ++i
) {
1287 for (uint32_t j
= i
; j
< n_devices
; ++j
) {
1288 if (devices
[i
]->prio
> devices
[j
]->prio
) {
1289 struct device_t
*tmp
;
1291 devices
[i
] = devices
[j
];
1297 /*pa_log_debug("Sorted device list");
1298 for (i = 0; i < n_devices; ++i) {
1299 pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio);
1302 /* Go through in order and write the new entry and cleanup our own list */
1305 for (i
= 0; i
< n_devices
; ++i
) {
1306 if ((e
= read_entry(u
, devices
[i
]->device
))) {
1307 if (e
->priority
[role_index
] == idx
)
1310 e
->priority
[role_index
] = idx
;
1312 key
.data
= (char *) devices
[i
]->device
;
1313 key
.size
= strlen(devices
[i
]->device
);
1316 data
.size
= sizeof(*e
);
1318 if (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0) {
1326 pa_xfree(devices
[i
]->device
);
1327 pa_xfree(devices
[i
]);
1334 route_sink_inputs(u
, NULL
);
1336 route_source_outputs(u
, NULL
);
1342 case SUBCOMMAND_SUBSCRIBE
: {
1346 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
1347 !pa_tagstruct_eof(t
))
1351 pa_idxset_put(u
->subscribed
, c
, NULL
);
1353 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1362 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
1368 pa_tagstruct_free(reply
);
1373 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
1378 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1382 int pa__init(pa_module
*m
) {
1383 pa_modargs
*ma
= NULL
;
1389 pa_bool_t do_routing
= FALSE
, on_hotplug
= TRUE
, on_rescue
= TRUE
;
1393 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
1394 pa_log("Failed to parse module arguments");
1398 if (pa_modargs_get_value_boolean(ma
, "do_routing", &do_routing
) < 0 ||
1399 pa_modargs_get_value_boolean(ma
, "on_hotplug", &on_hotplug
) < 0 ||
1400 pa_modargs_get_value_boolean(ma
, "on_rescue", &on_rescue
) < 0) {
1401 pa_log("on_hotplug= and on_rescue= expect boolean arguments");
1405 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
1408 u
->do_routing
= do_routing
;
1409 u
->on_hotplug
= on_hotplug
;
1410 u
->on_rescue
= on_rescue
;
1411 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
1413 u
->protocol
= pa_native_protocol_get(m
->core
);
1414 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
1416 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
);
1418 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
);
1420 /* Used to handle device description management */
1421 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
);
1422 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
);
1424 /* The following slots are used to deal with routing */
1425 /* A little bit later than module-stream-restore, module-intended-roles */
1426 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
);
1427 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
);
1430 /* A little bit later than module-stream-restore, module-intended-roles */
1431 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
);
1432 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
);
1436 /* A little bit later than module-stream-restore, module-intended-roles, a little bit earlier than module-rescue-streams, ... */
1437 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
);
1438 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
);
1441 if (!(fname
= pa_state_path("device-manager", TRUE
)))
1444 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
1445 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
1450 pa_log_info("Sucessfully opened database file '%s'.", fname
);
1453 /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
1454 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
)
1455 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
1457 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
)
1458 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
1460 /* Perform the routing (if it's enabled) which will update our priority list cache too */
1461 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
1462 u
->preferred_sinks
[i
] = u
->preferred_sources
[i
] = PA_INVALID_INDEX
;
1465 route_sink_inputs(u
, NULL
);
1466 route_source_outputs(u
, NULL
);
1468 #ifdef DUMP_DATABASE
1472 pa_modargs_free(ma
);
1479 pa_modargs_free(ma
);
1484 void pa__done(pa_module
*m
) {
1489 if (!(u
= m
->userdata
))
1492 if (u
->subscription
)
1493 pa_subscription_free(u
->subscription
);
1495 if (u
->sink_new_hook_slot
)
1496 pa_hook_slot_free(u
->sink_new_hook_slot
);
1497 if (u
->source_new_hook_slot
)
1498 pa_hook_slot_free(u
->source_new_hook_slot
);
1500 if (u
->sink_input_new_hook_slot
)
1501 pa_hook_slot_free(u
->sink_input_new_hook_slot
);
1502 if (u
->source_output_new_hook_slot
)
1503 pa_hook_slot_free(u
->source_output_new_hook_slot
);
1505 if (u
->sink_put_hook_slot
)
1506 pa_hook_slot_free(u
->sink_put_hook_slot
);
1507 if (u
->source_put_hook_slot
)
1508 pa_hook_slot_free(u
->source_put_hook_slot
);
1510 if (u
->sink_unlink_hook_slot
)
1511 pa_hook_slot_free(u
->sink_unlink_hook_slot
);
1512 if (u
->source_unlink_hook_slot
)
1513 pa_hook_slot_free(u
->source_unlink_hook_slot
);
1515 if (u
->save_time_event
)
1516 u
->core
->mainloop
->time_free(u
->save_time_event
);
1519 pa_database_close(u
->database
);
1522 pa_native_protocol_remove_ext(u
->protocol
, m
);
1523 pa_native_protocol_unref(u
->protocol
);
1527 pa_idxset_free(u
->subscribed
, NULL
, NULL
);