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
) {
326 /** @todo: Compare the priority lists too */
327 if (strncmp(a
->description
, b
->description
, sizeof(a
->description
)))
333 static char *get_name(const char *key
, const char *prefix
) {
336 if (strncmp(key
, prefix
, strlen(prefix
)))
339 t
= pa_xstrdup(key
+ strlen(prefix
));
343 static inline struct entry
*load_or_initialize_entry(struct userdata
*u
, struct entry
*entry
, const char *name
, const char *prefix
) {
351 if ((old
= read_entry(u
, name
)))
354 /* This is a new device, so make sure we write it's priority list correctly */
355 role_indexes_t max_priority
;
359 pa_zero(max_priority
);
360 done
= !pa_database_first(u
->database
, &key
, NULL
);
362 /* Find all existing devices with the same prefix so we calculate the current max priority for each role */
366 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
368 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
372 name2
= pa_xstrndup(key
.data
, key
.size
);
374 if ((e
= read_entry(u
, name2
))) {
375 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
376 max_priority
[i
] = PA_MAX(max_priority
[i
], e
->priority
[i
]);
388 /* Actually initialise our entry now we've calculated it */
389 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
390 entry
->priority
[i
] = max_priority
[i
] + 1;
397 static uint32_t get_role_index(const char* role
) {
400 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
)
401 if (strcmp(role
, role_names
[i
]) == 0)
404 return PA_INVALID_INDEX
;
407 static void update_highest_priority_device_indexes(struct userdata
*u
, const char *prefix
, void *ignore_device
) {
408 role_indexes_t
*indexes
, highest_priority_available
;
410 pa_bool_t done
, sink_mode
;
415 sink_mode
= (strcmp(prefix
, "sink:") == 0);
418 indexes
= &u
->preferred_sinks
;
420 indexes
= &u
->preferred_sources
;
422 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
423 *indexes
[i
] = PA_INVALID_INDEX
;
425 pa_zero(highest_priority_available
);
427 done
= !pa_database_first(u
->database
, &key
, NULL
);
429 /* Find all existing devices with the same prefix so we find the highest priority device for each role */
433 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
435 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
439 name
= pa_xstrndup(key
.data
, key
.size
);
441 if ((e
= read_entry(u
, name
))) {
442 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
443 if (highest_priority_available
[i
] && e
->priority
[i
] < highest_priority_available
[i
]) {
444 /* We've found a device with a higher priority than that we've currently got,
445 so see if it is currently available or not and update our list */
447 pa_bool_t found
= FALSE
;
448 char *device_name
= get_name(name
, prefix
);
453 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
) {
454 if ((pa_sink
*) ignore_device
== sink
)
456 if (strcmp(sink
->name
, device_name
) == 0) {
458 idx
= sink
->index
; /* Is this needed? */
465 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
) {
466 if ((pa_source
*) ignore_device
== source
)
468 if (strcmp(source
->name
, device_name
) == 0) {
470 idx
= source
->index
; /* Is this needed? */
476 highest_priority_available
[i
] = e
->priority
[i
];
480 pa_xfree(device_name
);
496 static void route_sink_input(struct userdata
*u
, pa_sink_input
*si
) {
498 uint32_t role_index
, device_index
;
502 pa_assert(u
->do_routing
);
507 /* Skip this if it is already in the process of being moved anyway */
511 /* It might happen that a stream and a sink are set up at the
512 same time, in which case we want to make sure we don't
513 interfere with that */
514 if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si
)))
517 if (!(role
= pa_proplist_gets(si
->proplist
, PA_PROP_MEDIA_ROLE
)))
518 role_index
= get_role_index("none");
520 role_index
= get_role_index(role
);
522 if (PA_INVALID_INDEX
== role_index
)
525 device_index
= u
->preferred_sinks
[role_index
];
526 if (PA_INVALID_INDEX
== device_index
)
529 if (!(sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
)))
532 if (si
->sink
!= sink
)
533 pa_sink_input_move_to(si
, sink
, TRUE
);
536 static pa_hook_result_t
route_sink_inputs(struct userdata
*u
, pa_sink
*ignore_sink
) {
545 update_highest_priority_device_indexes(u
, "sink:", ignore_sink
);
547 PA_IDXSET_FOREACH(si
, u
->core
->sink_inputs
, idx
) {
548 route_sink_input(u
, si
);
554 static void route_source_output(struct userdata
*u
, pa_source_output
*so
) {
556 uint32_t role_index
, device_index
;
560 pa_assert(u
->do_routing
);
565 if (so
->direct_on_input
)
568 /* Skip this if it is already in the process of being moved anyway */
572 /* It might happen that a stream and a source are set up at the
573 same time, in which case we want to make sure we don't
574 interfere with that */
575 if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so
)))
578 if (!(role
= pa_proplist_gets(so
->proplist
, PA_PROP_MEDIA_ROLE
)))
579 role_index
= get_role_index("none");
581 role_index
= get_role_index(role
);
583 if (PA_INVALID_INDEX
== role_index
)
586 device_index
= u
->preferred_sources
[role_index
];
587 if (PA_INVALID_INDEX
== device_index
)
590 if (!(source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
593 if (so
->source
!= source
)
594 pa_source_output_move_to(so
, source
, TRUE
);
597 static pa_hook_result_t
route_source_outputs(struct userdata
*u
, pa_source
* ignore_source
) {
598 pa_source_output
*so
;
606 update_highest_priority_device_indexes(u
, "source:", ignore_source
);
608 PA_IDXSET_FOREACH(so
, u
->core
->source_outputs
, idx
) {
609 route_source_output(u
, so
);
615 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
616 struct userdata
*u
= userdata
;
617 struct entry entry
, *old
= NULL
;
624 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
625 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
626 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
627 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
629 /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
630 t
!= (PA_SUBSCRIPTION_EVENT_SINK_INPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
631 /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
632 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
))
636 entry
.version
= ENTRY_VERSION
;
638 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK_INPUT
) {
643 if (!(si
= pa_idxset_get_by_index(c
->sink_inputs
, idx
)))
646 /* The role may change mid-stream, so we reroute */
647 route_sink_input(u
, si
);
650 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
) {
651 pa_source_output
*so
;
655 if (!(so
= pa_idxset_get_by_index(c
->source_outputs
, idx
)))
658 /* The role may change mid-stream, so we reroute */
659 route_source_output(u
, so
);
662 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
665 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
668 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
670 old
= load_or_initialize_entry(u
, &entry
, name
, "sink:");
672 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
673 pa_strlcpy(entry
.icon
, pa_strnull(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_ICON_NAME
)), sizeof(entry
.icon
));
675 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
) {
678 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
680 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
683 if (source
->monitor_of
)
686 name
= pa_sprintf_malloc("source:%s", source
->name
);
688 old
= load_or_initialize_entry(u
, &entry
, name
, "source:");
690 pa_strlcpy(entry
.description
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), sizeof(entry
.description
));
691 pa_strlcpy(entry
.icon
, pa_strnull(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_ICON_NAME
)), sizeof(entry
.icon
));
698 if (entries_equal(old
, &entry
)) {
702 /* Even if the entries are equal, the availability or otherwise
703 of the sink/source may have changed so we notify clients all the same */
704 notify_subscribers(u
);
713 key
.size
= strlen(name
);
716 data
.size
= sizeof(entry
);
718 pa_log_info("Storing device %s.", name
);
720 pa_database_set(u
->database
, &key
, &data
, TRUE
);
727 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
735 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
737 if ((e
= read_entry(u
, name
))) {
738 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
739 pa_log_info("Restoring description for sink %s.", new_data
->name
);
740 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
751 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
759 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
761 if ((e
= read_entry(u
, name
))) {
762 if (strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
763 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
764 pa_log_info("Restoring description for source %s.", new_data
->name
);
765 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
776 static pa_hook_result_t
sink_input_new_hook_callback(pa_core
*c
, pa_sink_input_new_data
*new_data
, struct userdata
*u
) {
785 pa_log_debug("Not restoring device for stream, because already set.");
790 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
791 role_index
= get_role_index("");
793 role_index
= get_role_index(role
);
795 if (PA_INVALID_INDEX
!= role_index
) {
796 uint32_t device_index
;
798 device_index
= u
->preferred_sinks
[role_index
];
799 if (PA_INVALID_INDEX
!= device_index
) {
802 if ((sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
))) {
803 new_data
->sink
= sink
;
804 new_data
->save_sink
= TRUE
;
813 static pa_hook_result_t
source_output_new_hook_callback(pa_core
*c
, pa_source_output_new_data
*new_data
, struct userdata
*u
) {
821 if (new_data
->direct_on_input
)
824 if (new_data
->source
)
825 pa_log_debug("Not restoring device for stream, because already set");
830 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
831 role_index
= get_role_index("");
833 role_index
= get_role_index(role
);
835 if (PA_INVALID_INDEX
!= role_index
) {
836 uint32_t device_index
;
838 device_index
= u
->preferred_sources
[role_index
];
839 if (PA_INVALID_INDEX
!= device_index
) {
842 if ((source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
))) {
843 new_data
->source
= source
;
844 new_data
->save_source
= TRUE
;
854 static pa_hook_result_t
sink_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_sink
*sink
, struct userdata
*u
) {
857 pa_assert(u
->core
== c
);
858 pa_assert(u
->on_hotplug
);
860 return route_sink_inputs(u
, NULL
);
863 static pa_hook_result_t
source_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_source
*source
, struct userdata
*u
) {
866 pa_assert(u
->core
== c
);
867 pa_assert(u
->on_hotplug
);
869 return route_source_outputs(u
, NULL
);
872 static pa_hook_result_t
sink_unlink_hook_callback(pa_core
*c
, pa_sink
*sink
, struct userdata
*u
) {
876 pa_assert(u
->core
== c
);
877 pa_assert(u
->on_rescue
);
879 /* There's no point in doing anything if the core is shut down anyway */
880 if (c
->state
== PA_CORE_SHUTDOWN
)
883 return route_sink_inputs(u
, sink
);
886 static pa_hook_result_t
source_unlink_hook_callback(pa_core
*c
, pa_source
*source
, struct userdata
*u
) {
890 pa_assert(u
->core
== c
);
891 pa_assert(u
->on_rescue
);
893 /* There's no point in doing anything if the core is shut down anyway */
894 if (c
->state
== PA_CORE_SHUTDOWN
)
897 return route_source_outputs(u
, source
);
901 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
911 if ((n
= get_name(name
, "sink:"))) {
912 for (sink
= pa_idxset_first(u
->core
->sinks
, &idx
); sink
; sink
= pa_idxset_next(u
->core
->sinks
, &idx
)) {
913 if (!pa_streq(sink
->name
, n
)) {
917 pa_log_info("Setting description for sink %s.", sink
->name
);
918 pa_sink_set_description(sink
, e
->description
);
922 else if ((n
= get_name(name
, "source:"))) {
923 for (source
= pa_idxset_first(u
->core
->sources
, &idx
); source
; source
= pa_idxset_next(u
->core
->sources
, &idx
)) {
924 if (!pa_streq(source
->name
, n
)) {
928 if (source
->monitor_of
) {
929 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source
->name
);
933 pa_log_info("Setting description for source %s.", source
->name
);
934 pa_source_set_description(source
, e
->description
);
941 #define EXT_VERSION 1
943 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
946 pa_tagstruct
*reply
= NULL
;
955 if (pa_tagstruct_getu32(t
, &command
) < 0)
958 reply
= pa_tagstruct_new(NULL
, 0);
959 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
960 pa_tagstruct_putu32(reply
, tag
);
963 case SUBCOMMAND_TEST
: {
964 if (!pa_tagstruct_eof(t
))
967 pa_tagstruct_putu32(reply
, EXT_VERSION
);
971 case SUBCOMMAND_READ
: {
975 if (!pa_tagstruct_eof(t
))
978 done
= !pa_database_first(u
->database
, &key
, NULL
);
985 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
987 name
= pa_xstrndup(key
.data
, key
.size
);
990 if ((e
= read_entry(u
, name
))) {
993 pa_bool_t available
= FALSE
;
995 if ((devname
= get_name(name
, "sink:"))) {
997 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
998 if (strcmp(s
->name
, devname
) == 0) {
1004 } else if ((devname
= get_name(name
, "source:"))) {
1006 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1007 if (strcmp(s
->name
, devname
) == 0) {
1015 pa_tagstruct_puts(reply
, name
);
1016 pa_tagstruct_puts(reply
, e
->description
);
1017 pa_tagstruct_puts(reply
, e
->icon
);
1018 pa_tagstruct_put_boolean(reply
, available
);
1019 pa_tagstruct_putu32(reply
, NUM_ROLES
);
1021 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
) {
1022 pa_tagstruct_puts(reply
, role_names
[i
]);
1023 pa_tagstruct_putu32(reply
, e
->priority
[i
]);
1037 case SUBCOMMAND_RENAME
: {
1040 const char *device
, *description
;
1042 if (pa_tagstruct_gets(t
, &device
) < 0 ||
1043 pa_tagstruct_gets(t
, &description
) < 0)
1046 if (!device
|| !*device
|| !description
|| !*description
)
1049 if ((e
= read_entry(u
, device
)) && ENTRY_VERSION
== e
->version
) {
1052 pa_strlcpy(e
->description
, description
, sizeof(e
->description
));
1054 key
.data
= (char *) device
;
1055 key
.size
= strlen(device
);
1058 data
.size
= sizeof(*e
);
1060 if (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0) {
1061 apply_entry(u
, device
, e
);
1066 pa_log_warn("Could not save device");
1071 pa_log_warn("Could not rename device %s, no entry in database", device
);
1076 case SUBCOMMAND_DELETE
:
1078 while (!pa_tagstruct_eof(t
)) {
1082 if (pa_tagstruct_gets(t
, &name
) < 0)
1085 key
.data
= (char*) name
;
1086 key
.size
= strlen(name
);
1088 /** @todo: Reindex the priorities */
1089 pa_database_unset(u
->database
, &key
);
1096 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
: {
1100 if (pa_tagstruct_get_boolean(t
, &enable
) < 0)
1103 if ((u
->do_routing
= enable
)) {
1104 /* Update our caches */
1105 update_highest_priority_device_indexes(u
, "sink:", NULL
);
1106 update_highest_priority_device_indexes(u
, "source:", NULL
);
1112 case SUBCOMMAND_PREFER_DEVICE
:
1113 case SUBCOMMAND_DEFER_DEVICE
: {
1115 const char *role
, *device
;
1117 uint32_t role_index
;
1119 if (pa_tagstruct_gets(t
, &role
) < 0 ||
1120 pa_tagstruct_gets(t
, &device
) < 0)
1123 if (!role
|| !device
|| !*device
)
1126 role_index
= get_role_index(role
);
1127 if (PA_INVALID_INDEX
== role_index
)
1130 if ((e
= read_entry(u
, device
)) && ENTRY_VERSION
== e
->version
) {
1133 char* prefix
= NULL
;
1135 pa_bool_t haschanged
= FALSE
;
1137 if (strncmp(device
, "sink:", 5) == 0)
1138 prefix
= pa_xstrdup("sink:");
1139 else if (strncmp(device
, "source:", 7) == 0)
1140 prefix
= pa_xstrdup("source:");
1145 priority
= e
->priority
[role_index
];
1147 /* Now we need to load up all the other entries of this type and shuffle the priroities around */
1149 done
= !pa_database_first(u
->database
, &key
, NULL
);
1151 while (!done
&& !haschanged
) {
1154 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1156 /* Only read devices with the right prefix */
1157 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
1161 name
= pa_xstrndup(key
.data
, key
.size
);
1163 if ((e2
= read_entry(u
, name
))) {
1164 if (SUBCOMMAND_PREFER_DEVICE
== command
) {
1166 if (e2
->priority
[role_index
] == (priority
- 1)) {
1167 e2
->priority
[role_index
]++;
1172 if (e2
->priority
[role_index
] == (priority
+ 1)) {
1173 e2
->priority
[role_index
]--;
1180 data
.size
= sizeof(*e2
);
1182 if (pa_database_set(u
->database
, &key
, &data
, TRUE
))
1183 pa_log_warn("Could not save device");
1192 pa_datum_free(&key
);
1196 /* Now write out our actual entry */
1198 if (SUBCOMMAND_PREFER_DEVICE
== command
)
1199 e
->priority
[role_index
]--;
1201 e
->priority
[role_index
]++;
1203 key
.data
= (char *) device
;
1204 key
.size
= strlen(device
);
1207 data
.size
= sizeof(*e
);
1209 if (pa_database_set(u
->database
, &key
, &data
, TRUE
))
1210 pa_log_warn("Could not save device");
1220 pa_log_warn("Could not reorder device %s, no entry in database", device
);
1225 case SUBCOMMAND_SUBSCRIBE
: {
1229 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
1230 !pa_tagstruct_eof(t
))
1234 pa_idxset_put(u
->subscribed
, c
, NULL
);
1236 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1245 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
1251 pa_tagstruct_free(reply
);
1256 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
1261 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1265 int pa__init(pa_module
*m
) {
1266 pa_modargs
*ma
= NULL
;
1272 pa_bool_t do_routing
= FALSE
, on_hotplug
= TRUE
, on_rescue
= TRUE
;
1276 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
1277 pa_log("Failed to parse module arguments");
1281 if (pa_modargs_get_value_boolean(ma
, "do_routing", &do_routing
) < 0 ||
1282 pa_modargs_get_value_boolean(ma
, "on_hotplug", &on_hotplug
) < 0 ||
1283 pa_modargs_get_value_boolean(ma
, "on_rescue", &on_rescue
) < 0) {
1284 pa_log("on_hotplug= and on_rescue= expect boolean arguments");
1288 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
1291 u
->do_routing
= do_routing
;
1292 u
->on_hotplug
= on_hotplug
;
1293 u
->on_rescue
= on_rescue
;
1294 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
1296 u
->protocol
= pa_native_protocol_get(m
->core
);
1297 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
1299 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
);
1301 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
);
1303 /* Used to handle device description management */
1304 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
);
1305 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
);
1307 /* The following slots are used to deal with routing */
1308 /* A little bit later than module-stream-restore, module-intended-roles */
1309 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
);
1310 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
);
1313 /* A little bit later than module-stream-restore, module-intended-roles */
1314 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
);
1315 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
);
1319 /* A little bit later than module-stream-restore, module-intended-roles, a little bit earlier than module-rescue-streams, ... */
1320 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
);
1321 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
);
1324 if (!(fname
= pa_state_path("device-manager", TRUE
)))
1327 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
1328 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
1333 pa_log_info("Sucessfully opened database file '%s'.", fname
);
1336 /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
1337 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
)
1338 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
1340 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
)
1341 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
1343 /* Perform the routing (if it's enabled) which will update our priority list cache too */
1344 route_sink_inputs(u
, NULL
);
1345 route_source_outputs(u
, NULL
);
1347 #ifdef DUMP_DATABASE
1351 pa_modargs_free(ma
);
1358 pa_modargs_free(ma
);
1363 void pa__done(pa_module
*m
) {
1368 if (!(u
= m
->userdata
))
1371 if (u
->subscription
)
1372 pa_subscription_free(u
->subscription
);
1374 if (u
->sink_new_hook_slot
)
1375 pa_hook_slot_free(u
->sink_new_hook_slot
);
1376 if (u
->source_new_hook_slot
)
1377 pa_hook_slot_free(u
->source_new_hook_slot
);
1379 if (u
->sink_input_new_hook_slot
)
1380 pa_hook_slot_free(u
->sink_input_new_hook_slot
);
1381 if (u
->source_output_new_hook_slot
)
1382 pa_hook_slot_free(u
->source_output_new_hook_slot
);
1384 if (u
->sink_put_hook_slot
)
1385 pa_hook_slot_free(u
->sink_put_hook_slot
);
1386 if (u
->source_put_hook_slot
)
1387 pa_hook_slot_free(u
->source_put_hook_slot
);
1389 if (u
->sink_unlink_hook_slot
)
1390 pa_hook_slot_free(u
->sink_unlink_hook_slot
);
1391 if (u
->source_unlink_hook_slot
)
1392 pa_hook_slot_free(u
->source_unlink_hook_slot
);
1394 if (u
->save_time_event
)
1395 u
->core
->mainloop
->time_free(u
->save_time_event
);
1398 pa_database_close(u
->database
);
1401 pa_native_protocol_remove_ext(u
->protocol
, m
);
1402 pa_native_protocol_unref(u
->protocol
);
1406 pa_idxset_free(u
->subscribed
, NULL
, NULL
);