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>
34 #include <pulse/gccmacro.h>
35 #include <pulse/xmalloc.h>
36 #include <pulse/timeval.h>
37 #include <pulse/rtclock.h>
39 #include <pulsecore/core-error.h>
40 #include <pulsecore/module.h>
41 #include <pulsecore/core-util.h>
42 #include <pulsecore/modargs.h>
43 #include <pulsecore/log.h>
44 #include <pulsecore/core-subscribe.h>
45 #include <pulsecore/sink-input.h>
46 #include <pulsecore/source-output.h>
47 #include <pulsecore/namereg.h>
48 #include <pulsecore/protocol-native.h>
49 #include <pulsecore/pstream.h>
50 #include <pulsecore/pstream-util.h>
51 #include <pulsecore/database.h>
52 #include <pulsecore/tagstruct.h>
54 #include "module-device-manager-symdef.h"
56 PA_MODULE_AUTHOR("Colin Guthrie");
57 PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role");
58 PA_MODULE_VERSION(PACKAGE_VERSION
);
59 PA_MODULE_LOAD_ONCE(TRUE
);
61 "do_routing=<Automatically route streams based on a priority list (unique per-role)?> "
62 "on_hotplug=<When new device becomes available, recheck streams?> "
63 "on_rescue=<When device becomes unavailable, recheck streams?>");
65 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
68 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
136 pa_bool_t user_set_description
;
138 role_indexes_t priority
;
146 SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
,
148 SUBCOMMAND_SUBSCRIBE
,
152 /* Forward declarations */
154 static void dump_database(struct userdata
*);
156 static void notify_subscribers(struct userdata
*);
158 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
159 struct userdata
*u
= userdata
;
165 pa_assert(e
== u
->save_time_event
);
166 u
->core
->mainloop
->time_free(u
->save_time_event
);
167 u
->save_time_event
= NULL
;
169 pa_database_sync(u
->database
);
170 pa_log_info("Synced.");
177 static void trigger_save(struct userdata
*u
) {
181 notify_subscribers(u
);
183 if (u
->save_time_event
)
186 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
189 static struct entry
* entry_new(void) {
190 struct entry
*r
= pa_xnew0(struct entry
, 1);
191 r
->version
= ENTRY_VERSION
;
195 static void entry_free(struct entry
* e
) {
198 pa_xfree(e
->description
);
203 static pa_bool_t
entry_write(struct userdata
*u
, const char *name
, const struct entry
*e
) {
212 t
= pa_tagstruct_new(NULL
, 0);
213 pa_tagstruct_putu8(t
, e
->version
);
214 pa_tagstruct_puts(t
, e
->description
);
215 pa_tagstruct_put_boolean(t
, e
->user_set_description
);
216 pa_tagstruct_puts(t
, e
->icon
);
217 for (uint8_t i
=0; i
<ROLE_MAX
; ++i
)
218 pa_tagstruct_putu32(t
, e
->priority
[i
]);
220 key
.data
= (char *) name
;
221 key
.size
= strlen(name
);
223 data
.data
= (void*)pa_tagstruct_data(t
, &data
.size
);
225 r
= (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0);
227 pa_tagstruct_free(t
);
232 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
234 #define LEGACY_ENTRY_VERSION 1
235 static struct entry
* legacy_entry_read(struct userdata
*u
, pa_datum
*data
) {
236 struct legacy_entry
{
238 char description
[PA_NAME_MAX
];
239 pa_bool_t user_set_description
;
240 char icon
[PA_NAME_MAX
];
241 role_indexes_t priority
;
243 struct legacy_entry
*le
;
249 if (data
->size
!= sizeof(struct legacy_entry
)) {
250 pa_log_debug("Size does not match.");
254 le
= (struct legacy_entry
*)data
->data
;
256 if (le
->version
!= LEGACY_ENTRY_VERSION
) {
257 pa_log_debug("Version mismatch.");
261 if (!memchr(le
->description
, 0, sizeof(le
->description
))) {
262 pa_log_warn("Description has missing NUL byte.");
266 if (!memchr(le
->icon
, 0, sizeof(le
->icon
))) {
267 pa_log_warn("Icon has missing NUL byte.");
272 e
->description
= pa_xstrdup(le
->description
);
273 e
->icon
= pa_xstrdup(le
->icon
);
278 static struct entry
* entry_read(struct userdata
*u
, const char *name
) {
280 struct entry
*e
= NULL
;
281 pa_tagstruct
*t
= NULL
;
282 const char *description
, *icon
;
287 key
.data
= (char*) name
;
288 key
.size
= strlen(name
);
292 if (!pa_database_get(u
->database
, &key
, &data
))
295 t
= pa_tagstruct_new(data
.data
, data
.size
);
298 if (pa_tagstruct_getu8(t
, &e
->version
) < 0 ||
299 e
->version
> ENTRY_VERSION
||
300 pa_tagstruct_gets(t
, &description
) < 0 ||
301 pa_tagstruct_get_boolean(t
, &e
->user_set_description
) < 0 ||
302 pa_tagstruct_gets(t
, &icon
) < 0) {
307 if (e
->user_set_description
&& !description
) {
308 pa_log("Entry has user_set_description set, but the description is NULL.");
312 e
->description
= pa_xstrdup(description
);
313 e
->icon
= pa_xstrdup(icon
);
315 for (uint8_t i
=0; i
<ROLE_MAX
; ++i
) {
316 if (pa_tagstruct_getu32(t
, &e
->priority
[i
]) < 0)
320 if (!pa_tagstruct_eof(t
))
323 pa_tagstruct_free(t
);
324 pa_datum_free(&data
);
329 pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name
);
334 pa_tagstruct_free(t
);
336 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
337 pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name
);
338 if ((e
= legacy_entry_read(u
, &data
))) {
339 pa_log_debug("Success. Saving new format for key: %s", name
);
340 if (entry_write(u
, name
, e
))
342 pa_datum_free(&data
);
345 pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name
);
348 pa_datum_free(&data
);
353 static void dump_database_helper(struct userdata
*u
, uint32_t role_index
, const char* human
, pa_bool_t sink_mode
) {
359 if (PA_INVALID_INDEX
!= u
->preferred_sinks
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sinks
, u
->preferred_sinks
[role_index
])))
360 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
362 pa_log_debug(" %s No sink specified", human
);
365 if (PA_INVALID_INDEX
!= u
->preferred_sources
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sources
, u
->preferred_sources
[role_index
])))
366 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
368 pa_log_debug(" %s No source specified", human
);
372 static void dump_database(struct userdata
*u
) {
378 done
= !pa_database_first(u
->database
, &key
, NULL
);
380 pa_log_debug("Dumping database");
386 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
388 name
= pa_xstrndup(key
.data
, key
.size
);
390 if ((e
= entry_read(u
, name
))) {
391 pa_log_debug(" Got entry: %s", name
);
392 pa_log_debug(" Description: %s", e
->description
);
393 pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u",
394 e
->priority
[ROLE_NONE
], e
->priority
[ROLE_VIDEO
], e
->priority
[ROLE_MUSIC
], e
->priority
[ROLE_GAME
], e
->priority
[ROLE_EVENT
]);
395 pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u",
396 e
->priority
[ROLE_PHONE
], e
->priority
[ROLE_ANIMATION
], e
->priority
[ROLE_PRODUCTION
], e
->priority
[ROLE_A11Y
]);
407 pa_log_debug(" Highest priority devices per-role:");
409 pa_log_debug(" Sinks:");
410 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
412 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
413 strncpy(name
, role_names
[role
], len
);
414 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
415 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
416 dump_database_helper(u
, role
, name
, TRUE
);
419 pa_log_debug(" Sources:");
420 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
422 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
423 strncpy(name
, role_names
[role
], len
);
424 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
425 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
426 dump_database_helper(u
, role
, name
, FALSE
);
430 pa_log_debug("Completed database dump");
434 static void notify_subscribers(struct userdata
*u
) {
436 pa_native_connection
*c
;
441 PA_IDXSET_FOREACH(c
, u
->subscribed
, idx
) {
444 t
= pa_tagstruct_new(NULL
, 0);
445 pa_tagstruct_putu32(t
, PA_COMMAND_EXTENSION
);
446 pa_tagstruct_putu32(t
, 0);
447 pa_tagstruct_putu32(t
, u
->module
->index
);
448 pa_tagstruct_puts(t
, u
->module
->name
);
449 pa_tagstruct_putu32(t
, SUBCOMMAND_EVENT
);
451 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), t
);
455 static pa_bool_t
entries_equal(const struct entry
*a
, const struct entry
*b
) {
460 if (!pa_streq(a
->description
, b
->description
)
461 || a
->user_set_description
!= b
->user_set_description
462 || !pa_streq(a
->icon
, b
->icon
))
465 for (int i
=0; i
< NUM_ROLES
; ++i
)
466 if (a
->priority
[i
] != b
->priority
[i
])
472 static char *get_name(const char *key
, const char *prefix
) {
475 if (strncmp(key
, prefix
, strlen(prefix
)))
478 t
= pa_xstrdup(key
+ strlen(prefix
));
482 static inline struct entry
*load_or_initialize_entry(struct userdata
*u
, struct entry
*entry
, const char *name
, const char *prefix
) {
490 if ((old
= entry_read(u
, name
))) {
492 entry
->description
= pa_xstrdup(old
->description
);
493 entry
->icon
= pa_xstrdup(old
->icon
);
495 /* This is a new device, so make sure we write it's priority list correctly */
496 role_indexes_t max_priority
;
500 pa_zero(max_priority
);
501 done
= !pa_database_first(u
->database
, &key
, NULL
);
503 /* Find all existing devices with the same prefix so we calculate the current max priority 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) {
513 name2
= pa_xstrndup(key
.data
, key
.size
);
515 if ((e
= entry_read(u
, name2
))) {
516 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
517 max_priority
[i
] = PA_MAX(max_priority
[i
], e
->priority
[i
]);
529 /* Actually initialise our entry now we've calculated it */
530 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
531 entry
->priority
[i
] = max_priority
[i
] + 1;
533 entry
->user_set_description
= FALSE
;
539 static uint32_t get_role_index(const char* role
) {
542 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
)
543 if (pa_streq(role
, role_names
[i
]))
546 return PA_INVALID_INDEX
;
549 static void update_highest_priority_device_indexes(struct userdata
*u
, const char *prefix
, void *ignore_device
) {
550 role_indexes_t
*indexes
, highest_priority_available
;
552 pa_bool_t done
, sink_mode
;
557 sink_mode
= pa_streq(prefix
, "sink:");
560 indexes
= &u
->preferred_sinks
;
562 indexes
= &u
->preferred_sources
;
564 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
565 (*indexes
)[i
] = PA_INVALID_INDEX
;
567 pa_zero(highest_priority_available
);
569 done
= !pa_database_first(u
->database
, &key
, NULL
);
571 /* Find all existing devices with the same prefix so we find the highest priority device for each role */
575 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
577 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
578 char *name
, *device_name
;
581 name
= pa_xstrndup(key
.data
, key
.size
);
582 pa_assert_se(device_name
= get_name(name
, prefix
));
584 if ((e
= entry_read(u
, name
))) {
585 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
586 if (!highest_priority_available
[i
] || e
->priority
[i
] < highest_priority_available
[i
]) {
587 /* We've found a device with a higher priority than that we've currently got,
588 so see if it is currently available or not and update our list */
590 pa_bool_t found
= FALSE
;
595 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
) {
596 if ((pa_sink
*) ignore_device
== sink
)
598 if (pa_streq(sink
->name
, device_name
)) {
600 idx
= sink
->index
; /* Is this needed? */
607 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
) {
608 if ((pa_source
*) ignore_device
== source
)
610 if (pa_streq(source
->name
, device_name
)) {
612 idx
= source
->index
; /* Is this needed? */
618 highest_priority_available
[i
] = e
->priority
[i
];
629 pa_xfree(device_name
);
637 static void route_sink_input(struct userdata
*u
, pa_sink_input
*si
) {
639 uint32_t role_index
, device_index
;
643 pa_assert(u
->do_routing
);
648 /* Skip this if it is already in the process of being moved anyway */
652 /* It might happen that a stream and a sink are set up at the
653 same time, in which case we want to make sure we don't
654 interfere with that */
655 if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si
)))
658 if (!(role
= pa_proplist_gets(si
->proplist
, PA_PROP_MEDIA_ROLE
)))
659 role_index
= get_role_index("none");
661 role_index
= get_role_index(role
);
663 if (PA_INVALID_INDEX
== role_index
)
666 device_index
= u
->preferred_sinks
[role_index
];
667 if (PA_INVALID_INDEX
== device_index
)
670 if (!(sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
)))
673 if (si
->sink
!= sink
)
674 pa_sink_input_move_to(si
, sink
, FALSE
);
677 static pa_hook_result_t
route_sink_inputs(struct userdata
*u
, pa_sink
*ignore_sink
) {
686 update_highest_priority_device_indexes(u
, "sink:", ignore_sink
);
688 PA_IDXSET_FOREACH(si
, u
->core
->sink_inputs
, idx
) {
689 route_sink_input(u
, si
);
695 static void route_source_output(struct userdata
*u
, pa_source_output
*so
) {
697 uint32_t role_index
, device_index
;
701 pa_assert(u
->do_routing
);
706 if (so
->direct_on_input
)
709 /* Skip this if it is already in the process of being moved anyway */
713 /* It might happen that a stream and a source are set up at the
714 same time, in which case we want to make sure we don't
715 interfere with that */
716 if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so
)))
719 if (!(role
= pa_proplist_gets(so
->proplist
, PA_PROP_MEDIA_ROLE
)))
720 role_index
= get_role_index("none");
722 role_index
= get_role_index(role
);
724 if (PA_INVALID_INDEX
== role_index
)
727 device_index
= u
->preferred_sources
[role_index
];
728 if (PA_INVALID_INDEX
== device_index
)
731 if (!(source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
734 if (so
->source
!= source
)
735 pa_source_output_move_to(so
, source
, FALSE
);
738 static pa_hook_result_t
route_source_outputs(struct userdata
*u
, pa_source
* ignore_source
) {
739 pa_source_output
*so
;
747 update_highest_priority_device_indexes(u
, "source:", ignore_source
);
749 PA_IDXSET_FOREACH(so
, u
->core
->source_outputs
, idx
) {
750 route_source_output(u
, so
);
756 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
757 struct userdata
*u
= userdata
;
758 struct entry
*entry
, *old
= NULL
;
764 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
765 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
766 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
767 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
769 /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
770 t
!= (PA_SUBSCRIPTION_EVENT_SINK_INPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
771 /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
772 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
))
775 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK_INPUT
) {
780 if (!(si
= pa_idxset_get_by_index(c
->sink_inputs
, idx
)))
783 /* The role may change mid-stream, so we reroute */
784 route_sink_input(u
, si
);
787 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
) {
788 pa_source_output
*so
;
792 if (!(so
= pa_idxset_get_by_index(c
->source_outputs
, idx
)))
795 /* The role may change mid-stream, so we reroute */
796 route_source_output(u
, so
);
799 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
802 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
806 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
808 old
= load_or_initialize_entry(u
, entry
, name
, "sink:");
810 if (!entry
->user_set_description
) {
811 pa_xfree(entry
->description
);
812 entry
->description
= pa_xstrdup(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
));
813 } else if (!pa_streq(entry
->description
, pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
814 /* Warning: If two modules fight over the description, this could cause an infinite loop.
815 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
816 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
817 the description, this will fail... */
818 pa_sink_set_description(sink
, entry
->description
);
821 pa_xfree(entry
->icon
);
822 entry
->icon
= pa_xstrdup(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_ICON_NAME
));
824 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
) {
827 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
829 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
832 if (source
->monitor_of
)
836 name
= pa_sprintf_malloc("source:%s", source
->name
);
838 old
= load_or_initialize_entry(u
, entry
, name
, "source:");
840 if (!entry
->user_set_description
) {
841 pa_xfree(entry
->description
);
842 entry
->description
= pa_xstrdup(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
));
843 } else if (!pa_streq(entry
->description
, pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
844 /* Warning: If two modules fight over the description, this could cause an infinite loop.
845 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
846 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
847 the description, this will fail... */
848 pa_source_set_description(source
, entry
->description
);
851 pa_xfree(entry
->icon
);
852 entry
->icon
= pa_xstrdup(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_ICON_NAME
));
854 pa_assert_not_reached();
861 if (entries_equal(old
, entry
)) {
872 pa_log_info("Storing device %s.", name
);
874 if (entry_write(u
, name
, entry
))
877 pa_log_warn("Could not save device");;
883 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
891 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
893 if ((e
= entry_read(u
, name
))) {
894 if (e
->user_set_description
&& !pa_safe_streq(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
895 pa_log_info("Restoring description for sink %s.", new_data
->name
);
896 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
907 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
915 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
917 if ((e
= entry_read(u
, name
))) {
918 if (e
->user_set_description
&& !pa_safe_streq(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
919 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
920 pa_log_info("Restoring description for source %s.", new_data
->name
);
921 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
932 static pa_hook_result_t
sink_input_new_hook_callback(pa_core
*c
, pa_sink_input_new_data
*new_data
, struct userdata
*u
) {
944 pa_log_debug("Not restoring device for stream because already set.");
946 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
947 role_index
= get_role_index("none");
949 role_index
= get_role_index(role
);
951 if (PA_INVALID_INDEX
!= role_index
) {
952 uint32_t device_index
;
954 device_index
= u
->preferred_sinks
[role_index
];
955 if (PA_INVALID_INDEX
!= device_index
) {
958 if ((sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
))) {
959 if (!pa_sink_input_new_data_set_sink(new_data
, sink
, FALSE
))
960 pa_log_debug("Not restoring device for stream because no supported format was found");
969 static pa_hook_result_t
source_output_new_hook_callback(pa_core
*c
, pa_source_output_new_data
*new_data
, struct userdata
*u
) {
980 if (new_data
->direct_on_input
)
983 if (new_data
->source
)
984 pa_log_debug("Not restoring device for stream because already set.");
986 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
987 role_index
= get_role_index("none");
989 role_index
= get_role_index(role
);
991 if (PA_INVALID_INDEX
!= role_index
) {
992 uint32_t device_index
;
994 device_index
= u
->preferred_sources
[role_index
];
995 if (PA_INVALID_INDEX
!= device_index
) {
998 if ((source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
999 if (!pa_source_output_new_data_set_source(new_data
, source
, FALSE
))
1000 pa_log_debug("Not restoring device for stream because no supported format was found");
1008 static pa_hook_result_t
sink_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_sink
*sink
, struct userdata
*u
) {
1011 pa_assert(u
->core
== c
);
1012 pa_assert(u
->on_hotplug
);
1014 notify_subscribers(u
);
1016 return route_sink_inputs(u
, NULL
);
1019 static pa_hook_result_t
source_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_source
*source
, struct userdata
*u
) {
1022 pa_assert(u
->core
== c
);
1023 pa_assert(u
->on_hotplug
);
1025 notify_subscribers(u
);
1027 return route_source_outputs(u
, NULL
);
1030 static pa_hook_result_t
sink_unlink_hook_callback(pa_core
*c
, pa_sink
*sink
, struct userdata
*u
) {
1034 pa_assert(u
->core
== c
);
1035 pa_assert(u
->on_rescue
);
1037 /* There's no point in doing anything if the core is shut down anyway */
1038 if (c
->state
== PA_CORE_SHUTDOWN
)
1041 notify_subscribers(u
);
1043 return route_sink_inputs(u
, sink
);
1046 static pa_hook_result_t
source_unlink_hook_callback(pa_core
*c
, pa_source
*source
, struct userdata
*u
) {
1050 pa_assert(u
->core
== c
);
1051 pa_assert(u
->on_rescue
);
1053 /* There's no point in doing anything if the core is shut down anyway */
1054 if (c
->state
== PA_CORE_SHUTDOWN
)
1057 notify_subscribers(u
);
1059 return route_source_outputs(u
, source
);
1062 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
1070 if (!e
->user_set_description
)
1073 if ((n
= get_name(name
, "sink:"))) {
1075 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1076 if (!pa_streq(s
->name
, n
)) {
1080 pa_log_info("Setting description for sink %s to '%s'", s
->name
, e
->description
);
1081 pa_sink_set_description(s
, e
->description
);
1085 else if ((n
= get_name(name
, "source:"))) {
1087 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1088 if (!pa_streq(s
->name
, n
)) {
1092 if (s
->monitor_of
) {
1093 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s
->name
);
1097 pa_log_info("Setting description for source %s to '%s'", s
->name
, e
->description
);
1098 pa_source_set_description(s
, e
->description
);
1104 #define EXT_VERSION 1
1106 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
1109 pa_tagstruct
*reply
= NULL
;
1118 if (pa_tagstruct_getu32(t
, &command
) < 0)
1121 reply
= pa_tagstruct_new(NULL
, 0);
1122 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
1123 pa_tagstruct_putu32(reply
, tag
);
1126 case SUBCOMMAND_TEST
: {
1127 if (!pa_tagstruct_eof(t
))
1130 pa_tagstruct_putu32(reply
, EXT_VERSION
);
1134 case SUBCOMMAND_READ
: {
1138 if (!pa_tagstruct_eof(t
))
1141 done
= !pa_database_first(u
->database
, &key
, NULL
);
1148 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1150 name
= pa_xstrndup(key
.data
, key
.size
);
1151 pa_datum_free(&key
);
1153 if ((e
= entry_read(u
, name
))) {
1156 uint32_t found_index
= PA_INVALID_INDEX
;
1158 if ((device_name
= get_name(name
, "sink:"))) {
1160 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1161 if (pa_streq(s
->name
, device_name
)) {
1162 found_index
= s
->index
;
1166 pa_xfree(device_name
);
1167 } else if ((device_name
= get_name(name
, "source:"))) {
1169 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1170 if (pa_streq(s
->name
, device_name
)) {
1171 found_index
= s
->index
;
1175 pa_xfree(device_name
);
1178 pa_tagstruct_puts(reply
, name
);
1179 pa_tagstruct_puts(reply
, e
->description
);
1180 pa_tagstruct_puts(reply
, e
->icon
);
1181 pa_tagstruct_putu32(reply
, found_index
);
1182 pa_tagstruct_putu32(reply
, NUM_ROLES
);
1184 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
) {
1185 pa_tagstruct_puts(reply
, role_names
[i
]);
1186 pa_tagstruct_putu32(reply
, e
->priority
[i
]);
1200 case SUBCOMMAND_RENAME
: {
1203 const char *device
, *description
;
1205 if (pa_tagstruct_gets(t
, &device
) < 0 ||
1206 pa_tagstruct_gets(t
, &description
) < 0)
1209 if (!device
|| !*device
|| !description
|| !*description
)
1212 if ((e
= entry_read(u
, device
))) {
1213 pa_xfree(e
->description
);
1214 e
->description
= pa_xstrdup(description
);
1215 e
->user_set_description
= TRUE
;
1217 if (entry_write(u
, (char *)device
, e
)) {
1218 apply_entry(u
, device
, e
);
1223 pa_log_warn("Could not save device");
1228 pa_log_warn("Could not rename device %s, no entry in database", device
);
1233 case SUBCOMMAND_DELETE
:
1235 while (!pa_tagstruct_eof(t
)) {
1239 if (pa_tagstruct_gets(t
, &name
) < 0)
1242 key
.data
= (char*) name
;
1243 key
.size
= strlen(name
);
1245 /** @todo: Reindex the priorities */
1246 pa_database_unset(u
->database
, &key
);
1253 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
: {
1257 if (pa_tagstruct_get_boolean(t
, &enable
) < 0)
1260 if ((u
->do_routing
= enable
)) {
1261 /* Update our caches */
1262 update_highest_priority_device_indexes(u
, "sink:", NULL
);
1263 update_highest_priority_device_indexes(u
, "source:", NULL
);
1269 case SUBCOMMAND_REORDER
: {
1273 uint32_t role_index
, n_devices
;
1275 pa_bool_t done
, sink_mode
= TRUE
;
1276 struct device_t
{ uint32_t prio
; char *device
; };
1277 struct device_t
*device
;
1278 struct device_t
**devices
;
1279 uint32_t i
, idx
, offset
;
1284 if (pa_tagstruct_gets(t
, &role
) < 0 ||
1285 pa_tagstruct_getu32(t
, &n_devices
) < 0 ||
1289 if (PA_INVALID_INDEX
== (role_index
= get_role_index(role
)))
1292 /* Cycle through the devices given and make sure they exist */
1293 h
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1296 for (i
= 0; i
< n_devices
; ++i
) {
1298 if (pa_tagstruct_gets(t
, &s
) < 0) {
1299 while ((device
= pa_hashmap_steal_first(h
))) {
1300 pa_xfree(device
->device
);
1304 pa_hashmap_free(h
, NULL
);
1305 pa_log_error("Protocol error on reorder");
1309 /* Ensure this is a valid entry */
1310 if (!(e
= entry_read(u
, s
))) {
1311 while ((device
= pa_hashmap_steal_first(h
))) {
1312 pa_xfree(device
->device
);
1316 pa_hashmap_free(h
, NULL
);
1317 pa_log_error("Client specified an unknown device in it's reorder list.");
1324 sink_mode
= (0 == strncmp("sink:", s
, 5));
1325 } else if ((sink_mode
&& 0 != strncmp("sink:", s
, 5)) || (!sink_mode
&& 0 != strncmp("source:", s
, 7))) {
1326 while ((device
= pa_hashmap_steal_first(h
))) {
1327 pa_xfree(device
->device
);
1331 pa_hashmap_free(h
, NULL
);
1332 pa_log_error("Attempted to reorder mixed devices (sinks and sources)");
1336 /* Add the device to our hashmap. If it's already in it, free it now and carry on */
1337 device
= pa_xnew(struct device_t
, 1);
1338 device
->device
= pa_xstrdup(s
);
1339 if (pa_hashmap_put(h
, device
->device
, device
) == 0) {
1343 pa_xfree(device
->device
);
1348 /*pa_log_debug("Hashmap contents (received from client)");
1349 PA_HASHMAP_FOREACH(device, h, state) {
1350 pa_log_debug(" - %s (%d)", device->device, device->prio);
1353 /* Now cycle through our list and add all the devices.
1354 This has the effect of adding in any in our DB,
1355 not specified in the device list (and thus will be
1356 tacked on at the end) */
1358 done
= !pa_database_first(u
->database
, &key
, NULL
);
1360 while (!done
&& idx
< 256) {
1363 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1365 device
= pa_xnew(struct device_t
, 1);
1366 device
->device
= pa_xstrndup(key
.data
, key
.size
);
1367 if ((sink_mode
&& 0 == strncmp("sink:", device
->device
, 5))
1368 || (!sink_mode
&& 0 == strncmp("source:", device
->device
, 7))) {
1370 /* Add the device to our hashmap. If it's already in it, free it now and carry on */
1371 if (pa_hashmap_put(h
, device
->device
, device
) == 0
1372 && (e
= entry_read(u
, device
->device
))) {
1373 /* We add offset on to the existing priority so that when we order, the
1374 existing entries are always lower priority than the new ones. */
1375 device
->prio
= (offset
+ e
->priority
[role_index
]);
1379 pa_xfree(device
->device
);
1383 pa_xfree(device
->device
);
1387 pa_datum_free(&key
);
1392 /*pa_log_debug("Hashmap contents (combined with database)");
1393 PA_HASHMAP_FOREACH(device, h, state) {
1394 pa_log_debug(" - %s (%d)", device->device, device->prio);
1397 /* Now we put all the entries in a simple list for sorting it. */
1398 n_devices
= pa_hashmap_size(h
);
1399 devices
= pa_xnew(struct device_t
*, n_devices
);
1401 while ((device
= pa_hashmap_steal_first(h
))) {
1402 devices
[idx
++] = device
;
1404 pa_hashmap_free(h
, NULL
);
1406 /* Simple bubble sort */
1407 for (i
= 0; i
< n_devices
; ++i
) {
1408 for (uint32_t j
= i
; j
< n_devices
; ++j
) {
1409 if (devices
[i
]->prio
> devices
[j
]->prio
) {
1410 struct device_t
*tmp
;
1412 devices
[i
] = devices
[j
];
1418 /*pa_log_debug("Sorted device list");
1419 for (i = 0; i < n_devices; ++i) {
1420 pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio);
1423 /* Go through in order and write the new entry and cleanup our own list */
1426 for (i
= 0; i
< n_devices
; ++i
) {
1427 if ((e
= entry_read(u
, devices
[i
]->device
))) {
1428 if (e
->priority
[role_index
] == idx
)
1431 e
->priority
[role_index
] = idx
;
1433 if (entry_write(u
, (char *) devices
[i
]->device
, e
)) {
1441 pa_xfree(devices
[i
]->device
);
1442 pa_xfree(devices
[i
]);
1451 route_sink_inputs(u
, NULL
);
1453 route_source_outputs(u
, NULL
);
1459 case SUBCOMMAND_SUBSCRIBE
: {
1463 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
1464 !pa_tagstruct_eof(t
))
1468 pa_idxset_put(u
->subscribed
, c
, NULL
);
1470 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1479 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
1485 pa_tagstruct_free(reply
);
1490 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
1495 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1499 struct prioritised_indexes
{
1504 int pa__init(pa_module
*m
) {
1505 pa_modargs
*ma
= NULL
;
1511 pa_bool_t do_routing
= FALSE
, on_hotplug
= TRUE
, on_rescue
= TRUE
;
1512 uint32_t total_devices
;
1516 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
1517 pa_log("Failed to parse module arguments");
1521 if (pa_modargs_get_value_boolean(ma
, "do_routing", &do_routing
) < 0 ||
1522 pa_modargs_get_value_boolean(ma
, "on_hotplug", &on_hotplug
) < 0 ||
1523 pa_modargs_get_value_boolean(ma
, "on_rescue", &on_rescue
) < 0) {
1524 pa_log("on_hotplug= and on_rescue= expect boolean arguments");
1528 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
1531 u
->do_routing
= do_routing
;
1532 u
->on_hotplug
= on_hotplug
;
1533 u
->on_rescue
= on_rescue
;
1534 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
1536 u
->protocol
= pa_native_protocol_get(m
->core
);
1537 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
1539 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
);
1541 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
);
1543 /* Used to handle device description management */
1544 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
);
1545 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
);
1547 /* The following slots are used to deal with routing */
1548 /* A little bit later than module-stream-restore, but before module-intended-roles */
1549 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
);
1550 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
);
1553 /* A little bit later than module-stream-restore, but before module-intended-roles */
1554 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
);
1555 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
);
1559 /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */
1560 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
);
1561 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
);
1564 if (!(fname
= pa_state_path("device-manager", TRUE
)))
1567 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
1568 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
1573 pa_log_info("Successfully opened database file '%s'.", fname
);
1576 /* Attempt to inject the devices into the list in priority order */
1577 total_devices
= PA_MAX(pa_idxset_size(m
->core
->sinks
), pa_idxset_size(m
->core
->sources
));
1578 if (total_devices
> 0 && total_devices
< 128) {
1580 struct prioritised_indexes p_i
[128];
1582 /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
1584 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
) {
1585 pa_log_debug("Found sink index %u", sink
->index
);
1586 p_i
[i
].index
= sink
->index
;
1587 p_i
[i
++].priority
= sink
->priority
;
1589 /* Bubble sort it (only really useful for first time creation) */
1591 for (uint32_t j
= 0; j
< i
; ++j
)
1592 for (uint32_t k
= 0; k
< i
; ++k
)
1593 if (p_i
[j
].priority
> p_i
[k
].priority
) {
1594 struct prioritised_indexes tmp_pi
= p_i
[k
];
1599 for (uint32_t j
= 0; j
< i
; ++j
)
1600 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, p_i
[j
].index
, u
);
1602 /* We cycle over all the available sources so that they are added to our database if they are not in it yet */
1604 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
) {
1605 p_i
[i
].index
= source
->index
;
1606 p_i
[i
++].priority
= source
->priority
;
1608 /* Bubble sort it (only really useful for first time creation) */
1610 for (uint32_t j
= 0; j
< i
; ++j
)
1611 for (uint32_t k
= 0; k
< i
; ++k
)
1612 if (p_i
[j
].priority
> p_i
[k
].priority
) {
1613 struct prioritised_indexes tmp_pi
= p_i
[k
];
1618 for (uint32_t j
= 0; j
< i
; ++j
)
1619 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, p_i
[j
].index
, u
);
1621 else if (total_devices
> 0) {
1622 /* This user has a *lot* of devices... */
1623 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
)
1624 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
1626 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
)
1627 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
1630 /* Perform the routing (if it's enabled) which will update our priority list cache too */
1631 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
1632 u
->preferred_sinks
[i
] = u
->preferred_sources
[i
] = PA_INVALID_INDEX
;
1635 route_sink_inputs(u
, NULL
);
1636 route_source_outputs(u
, NULL
);
1638 #ifdef DUMP_DATABASE
1642 pa_modargs_free(ma
);
1649 pa_modargs_free(ma
);
1654 void pa__done(pa_module
*m
) {
1659 if (!(u
= m
->userdata
))
1662 if (u
->subscription
)
1663 pa_subscription_free(u
->subscription
);
1665 if (u
->sink_new_hook_slot
)
1666 pa_hook_slot_free(u
->sink_new_hook_slot
);
1667 if (u
->source_new_hook_slot
)
1668 pa_hook_slot_free(u
->source_new_hook_slot
);
1670 if (u
->sink_input_new_hook_slot
)
1671 pa_hook_slot_free(u
->sink_input_new_hook_slot
);
1672 if (u
->source_output_new_hook_slot
)
1673 pa_hook_slot_free(u
->source_output_new_hook_slot
);
1675 if (u
->sink_put_hook_slot
)
1676 pa_hook_slot_free(u
->sink_put_hook_slot
);
1677 if (u
->source_put_hook_slot
)
1678 pa_hook_slot_free(u
->source_put_hook_slot
);
1680 if (u
->sink_unlink_hook_slot
)
1681 pa_hook_slot_free(u
->sink_unlink_hook_slot
);
1682 if (u
->source_unlink_hook_slot
)
1683 pa_hook_slot_free(u
->source_unlink_hook_slot
);
1685 if (u
->connection_unlink_hook_slot
)
1686 pa_hook_slot_free(u
->connection_unlink_hook_slot
);
1688 if (u
->save_time_event
)
1689 u
->core
->mainloop
->time_free(u
->save_time_event
);
1692 pa_database_close(u
->database
);
1695 pa_native_protocol_remove_ext(u
->protocol
, m
);
1696 pa_native_protocol_unref(u
->protocol
);
1700 pa_idxset_free(u
->subscribed
, NULL
);