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
,
153 /* Forward declarations */
155 static void dump_database(struct userdata
*);
157 static void notify_subscribers(struct userdata
*);
160 static void save_time_callback(pa_mainloop_api
*a
, pa_time_event
* e
, const struct timeval
*t
, void *userdata
) {
161 struct userdata
*u
= userdata
;
167 pa_assert(e
== u
->save_time_event
);
168 u
->core
->mainloop
->time_free(u
->save_time_event
);
169 u
->save_time_event
= NULL
;
171 pa_database_sync(u
->database
);
172 pa_log_info("Synced.");
179 static void trigger_save(struct userdata
*u
) {
183 notify_subscribers(u
);
185 if (u
->save_time_event
)
188 u
->save_time_event
= pa_core_rttime_new(u
->core
, pa_rtclock_now() + SAVE_INTERVAL
, save_time_callback
, u
);
191 static struct entry
* entry_new(void) {
192 struct entry
*r
= pa_xnew0(struct entry
, 1);
193 r
->version
= ENTRY_VERSION
;
197 static void entry_free(struct entry
* e
) {
200 pa_xfree(e
->description
);
204 static pa_bool_t
entry_write(struct userdata
*u
, const char *name
, const struct entry
*e
) {
213 t
= pa_tagstruct_new(NULL
, 0);
214 pa_tagstruct_putu8(t
, e
->version
);
215 pa_tagstruct_puts(t
, e
->description
);
216 pa_tagstruct_put_boolean(t
, e
->user_set_description
);
217 pa_tagstruct_puts(t
, e
->icon
);
218 for (uint8_t i
=0; i
<ROLE_MAX
; ++i
)
219 pa_tagstruct_putu32(t
, e
->priority
[i
]);
221 key
.data
= (char *) name
;
222 key
.size
= strlen(name
);
224 data
.data
= (void*)pa_tagstruct_data(t
, &data
.size
);
226 r
= (pa_database_set(u
->database
, &key
, &data
, TRUE
) == 0);
228 pa_tagstruct_free(t
);
233 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
235 #define LEGACY_ENTRY_VERSION 1
236 static struct entry
* legacy_entry_read(struct userdata
*u
, pa_datum
*data
) {
237 struct legacy_entry
{
239 char description
[PA_NAME_MAX
];
240 pa_bool_t user_set_description
;
241 char icon
[PA_NAME_MAX
];
242 role_indexes_t priority
;
244 struct legacy_entry
*le
;
250 if (data
->size
!= sizeof(struct legacy_entry
)) {
251 pa_log_debug("Size does not match.");
255 le
= (struct legacy_entry
*)data
->data
;
257 if (le
->version
!= LEGACY_ENTRY_VERSION
) {
258 pa_log_debug("Version mismatch.");
262 if (!memchr(le
->description
, 0, sizeof(le
->description
))) {
263 pa_log_warn("Description has missing NUL byte.");
267 if (!memchr(le
->icon
, 0, sizeof(le
->icon
))) {
268 pa_log_warn("Icon has missing NUL byte.");
273 e
->description
= pa_xstrdup(le
->description
);
274 e
->icon
= pa_xstrdup(le
->icon
);
279 static struct entry
* entry_read(struct userdata
*u
, const char *name
) {
281 struct entry
*e
= NULL
;
282 pa_tagstruct
*t
= NULL
;
283 const char *description
, *icon
;
288 key
.data
= (char*) name
;
289 key
.size
= strlen(name
);
293 if (!pa_database_get(u
->database
, &key
, &data
))
296 t
= pa_tagstruct_new(data
.data
, data
.size
);
299 if (pa_tagstruct_getu8(t
, &e
->version
) < 0 ||
300 e
->version
> ENTRY_VERSION
||
301 pa_tagstruct_gets(t
, &description
) < 0 ||
302 pa_tagstruct_get_boolean(t
, &e
->user_set_description
) < 0 ||
303 pa_tagstruct_gets(t
, &icon
) < 0) {
308 e
->description
= pa_xstrdup(description
);
309 e
->icon
= pa_xstrdup(icon
);
311 for (uint8_t i
=0; i
<ROLE_MAX
; ++i
) {
312 if (pa_tagstruct_getu32(t
, &e
->priority
[i
]) < 0)
316 if (!pa_tagstruct_eof(t
))
319 pa_tagstruct_free(t
);
320 pa_datum_free(&data
);
325 pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name
);
330 pa_tagstruct_free(t
);
332 #ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
333 pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name
);
334 if ((e
= legacy_entry_read(u
, &data
))) {
335 pa_log_debug("Success. Saving new format for key: %s", name
);
336 if (entry_write(u
, name
, e
))
338 pa_datum_free(&data
);
341 pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name
);
344 pa_datum_free(&data
);
349 static void dump_database_helper(struct userdata
*u
, uint32_t role_index
, const char* human
, pa_bool_t sink_mode
) {
355 if (PA_INVALID_INDEX
!= u
->preferred_sinks
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sinks
, u
->preferred_sinks
[role_index
])))
356 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
358 pa_log_debug(" %s No sink specified", human
);
361 if (PA_INVALID_INDEX
!= u
->preferred_sources
[role_index
] && (s
= pa_idxset_get_by_index(u
->core
->sources
, u
->preferred_sources
[role_index
])))
362 pa_log_debug(" %s %s (%s)", human
, pa_strnull(pa_proplist_gets(s
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)), s
->name
);
364 pa_log_debug(" %s No source specified", human
);
368 static void dump_database(struct userdata
*u
) {
374 done
= !pa_database_first(u
->database
, &key
, NULL
);
376 pa_log_debug("Dumping database");
382 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
384 name
= pa_xstrndup(key
.data
, key
.size
);
386 if ((e
= entry_read(u
, name
))) {
387 pa_log_debug(" Got entry: %s", name
);
388 pa_log_debug(" Description: %s", e
->description
);
389 pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u",
390 e
->priority
[ROLE_NONE
], e
->priority
[ROLE_VIDEO
], e
->priority
[ROLE_MUSIC
], e
->priority
[ROLE_GAME
], e
->priority
[ROLE_EVENT
]);
391 pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u",
392 e
->priority
[ROLE_PHONE
], e
->priority
[ROLE_ANIMATION
], e
->priority
[ROLE_PRODUCTION
], e
->priority
[ROLE_A11Y
]);
403 pa_log_debug(" Highest priority devices per-role:");
405 pa_log_debug(" Sinks:");
406 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
408 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
409 strncpy(name
, role_names
[role
], len
);
410 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
411 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
412 dump_database_helper(u
, role
, name
, TRUE
);
415 pa_log_debug(" Sources:");
416 for (uint32_t role
= ROLE_NONE
; role
< NUM_ROLES
; ++role
) {
418 uint32_t len
= PA_MIN(12u, strlen(role_names
[role
]));
419 strncpy(name
, role_names
[role
], len
);
420 for (int i
= len
+1; i
< 12; ++i
) name
[i
] = ' ';
421 name
[len
] = ':'; name
[0] -= 32; name
[12] = '\0';
422 dump_database_helper(u
, role
, name
, FALSE
);
426 pa_log_debug("Completed database dump");
430 static void notify_subscribers(struct userdata
*u
) {
432 pa_native_connection
*c
;
437 for (c
= pa_idxset_first(u
->subscribed
, &idx
); c
; c
= pa_idxset_next(u
->subscribed
, &idx
)) {
440 t
= pa_tagstruct_new(NULL
, 0);
441 pa_tagstruct_putu32(t
, PA_COMMAND_EXTENSION
);
442 pa_tagstruct_putu32(t
, 0);
443 pa_tagstruct_putu32(t
, u
->module
->index
);
444 pa_tagstruct_puts(t
, u
->module
->name
);
445 pa_tagstruct_putu32(t
, SUBCOMMAND_EVENT
);
447 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), t
);
451 static pa_bool_t
entries_equal(const struct entry
*a
, const struct entry
*b
) {
456 if (!pa_streq(a
->description
, b
->description
)
457 || a
->user_set_description
!= b
->user_set_description
458 || !pa_streq(a
->icon
, b
->icon
))
461 for (int i
=0; i
< NUM_ROLES
; ++i
)
462 if (a
->priority
[i
] != b
->priority
[i
])
468 static char *get_name(const char *key
, const char *prefix
) {
471 if (strncmp(key
, prefix
, strlen(prefix
)))
474 t
= pa_xstrdup(key
+ strlen(prefix
));
478 static inline struct entry
*load_or_initialize_entry(struct userdata
*u
, struct entry
*entry
, const char *name
, const char *prefix
) {
486 if ((old
= entry_read(u
, name
))) {
488 entry
->description
= pa_xstrdup(old
->description
);
489 entry
->icon
= pa_xstrdup(old
->icon
);
491 /* This is a new device, so make sure we write it's priority list correctly */
492 role_indexes_t max_priority
;
496 pa_zero(max_priority
);
497 done
= !pa_database_first(u
->database
, &key
, NULL
);
499 /* Find all existing devices with the same prefix so we calculate the current max priority for each role */
503 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
505 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
509 name2
= pa_xstrndup(key
.data
, key
.size
);
511 if ((e
= entry_read(u
, name2
))) {
512 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
513 max_priority
[i
] = PA_MAX(max_priority
[i
], e
->priority
[i
]);
525 /* Actually initialise our entry now we've calculated it */
526 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
527 entry
->priority
[i
] = max_priority
[i
] + 1;
529 entry
->user_set_description
= FALSE
;
535 static uint32_t get_role_index(const char* role
) {
538 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
)
539 if (strcmp(role
, role_names
[i
]) == 0)
542 return PA_INVALID_INDEX
;
545 static void update_highest_priority_device_indexes(struct userdata
*u
, const char *prefix
, void *ignore_device
) {
546 role_indexes_t
*indexes
, highest_priority_available
;
548 pa_bool_t done
, sink_mode
;
553 sink_mode
= (strcmp(prefix
, "sink:") == 0);
556 indexes
= &u
->preferred_sinks
;
558 indexes
= &u
->preferred_sources
;
560 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
561 (*indexes
)[i
] = PA_INVALID_INDEX
;
563 pa_zero(highest_priority_available
);
565 done
= !pa_database_first(u
->database
, &key
, NULL
);
567 /* Find all existing devices with the same prefix so we find the highest priority device for each role */
571 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
573 if (key
.size
> strlen(prefix
) && strncmp(key
.data
, prefix
, strlen(prefix
)) == 0) {
574 char *name
, *device_name
;
577 name
= pa_xstrndup(key
.data
, key
.size
);
578 device_name
= get_name(name
, prefix
);
580 if ((e
= entry_read(u
, name
))) {
581 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
582 if (!highest_priority_available
[i
] || e
->priority
[i
] < highest_priority_available
[i
]) {
583 /* We've found a device with a higher priority than that we've currently got,
584 so see if it is currently available or not and update our list */
586 pa_bool_t found
= FALSE
;
591 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
) {
592 if ((pa_sink
*) ignore_device
== sink
)
594 if (strcmp(sink
->name
, device_name
) == 0) {
596 idx
= sink
->index
; /* Is this needed? */
603 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
) {
604 if ((pa_source
*) ignore_device
== source
)
606 if (strcmp(source
->name
, device_name
) == 0) {
608 idx
= source
->index
; /* Is this needed? */
614 highest_priority_available
[i
] = e
->priority
[i
];
625 pa_xfree(device_name
);
634 static void route_sink_input(struct userdata
*u
, pa_sink_input
*si
) {
636 uint32_t role_index
, device_index
;
640 pa_assert(u
->do_routing
);
645 /* Skip this if it is already in the process of being moved anyway */
649 /* It might happen that a stream and a sink are set up at the
650 same time, in which case we want to make sure we don't
651 interfere with that */
652 if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si
)))
655 if (!(role
= pa_proplist_gets(si
->proplist
, PA_PROP_MEDIA_ROLE
)))
656 role_index
= get_role_index("none");
658 role_index
= get_role_index(role
);
660 if (PA_INVALID_INDEX
== role_index
)
663 device_index
= u
->preferred_sinks
[role_index
];
664 if (PA_INVALID_INDEX
== device_index
)
667 if (!(sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
)))
670 if (si
->sink
!= sink
)
671 pa_sink_input_move_to(si
, sink
, FALSE
);
674 static pa_hook_result_t
route_sink_inputs(struct userdata
*u
, pa_sink
*ignore_sink
) {
683 update_highest_priority_device_indexes(u
, "sink:", ignore_sink
);
685 PA_IDXSET_FOREACH(si
, u
->core
->sink_inputs
, idx
) {
686 route_sink_input(u
, si
);
692 static void route_source_output(struct userdata
*u
, pa_source_output
*so
) {
694 uint32_t role_index
, device_index
;
698 pa_assert(u
->do_routing
);
703 if (so
->direct_on_input
)
706 /* Skip this if it is already in the process of being moved anyway */
710 /* It might happen that a stream and a source are set up at the
711 same time, in which case we want to make sure we don't
712 interfere with that */
713 if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so
)))
716 if (!(role
= pa_proplist_gets(so
->proplist
, PA_PROP_MEDIA_ROLE
)))
717 role_index
= get_role_index("none");
719 role_index
= get_role_index(role
);
721 if (PA_INVALID_INDEX
== role_index
)
724 device_index
= u
->preferred_sources
[role_index
];
725 if (PA_INVALID_INDEX
== device_index
)
728 if (!(source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
731 if (so
->source
!= source
)
732 pa_source_output_move_to(so
, source
, FALSE
);
735 static pa_hook_result_t
route_source_outputs(struct userdata
*u
, pa_source
* ignore_source
) {
736 pa_source_output
*so
;
744 update_highest_priority_device_indexes(u
, "source:", ignore_source
);
746 PA_IDXSET_FOREACH(so
, u
->core
->source_outputs
, idx
) {
747 route_source_output(u
, so
);
753 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
754 struct userdata
*u
= userdata
;
755 struct entry
*entry
, *old
= NULL
;
761 if (t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
) &&
762 t
!= (PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
763 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
) &&
764 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
766 /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
767 t
!= (PA_SUBSCRIPTION_EVENT_SINK_INPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
) &&
768 /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/
769 t
!= (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
|PA_SUBSCRIPTION_EVENT_CHANGE
))
774 if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK_INPUT
) {
779 if (!(si
= pa_idxset_get_by_index(c
->sink_inputs
, idx
)))
782 /* The role may change mid-stream, so we reroute */
783 route_sink_input(u
, si
);
786 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
) {
787 pa_source_output
*so
;
791 if (!(so
= pa_idxset_get_by_index(c
->source_outputs
, idx
)))
794 /* The role may change mid-stream, so we reroute */
795 route_source_output(u
, so
);
798 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SINK
) {
801 if (!(sink
= pa_idxset_get_by_index(c
->sinks
, idx
)))
804 name
= pa_sprintf_malloc("sink:%s", sink
->name
);
806 old
= load_or_initialize_entry(u
, entry
, name
, "sink:");
808 if (!entry
->user_set_description
) {
809 pa_xfree(entry
->description
);
810 entry
->description
= pa_xstrdup(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
));
811 } else if (!pa_streq(entry
->description
, pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
812 /* Warning: If two modules fight over the description, this could cause an infinite loop.
813 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
814 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
815 the description, this will fail... */
816 pa_sink_set_description(sink
, entry
->description
);
819 pa_xfree(entry
->icon
);
820 entry
->icon
= pa_xstrdup(pa_proplist_gets(sink
->proplist
, PA_PROP_DEVICE_ICON_NAME
));
822 } else if ((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
) {
825 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_SOURCE
);
827 if (!(source
= pa_idxset_get_by_index(c
->sources
, idx
)))
830 if (source
->monitor_of
)
833 name
= pa_sprintf_malloc("source:%s", source
->name
);
835 old
= load_or_initialize_entry(u
, entry
, name
, "source:");
837 if (!entry
->user_set_description
) {
838 pa_xfree(entry
->description
);
839 entry
->description
= pa_xstrdup(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
));
840 } else if (!pa_streq(entry
->description
, pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
))) {
841 /* Warning: If two modules fight over the description, this could cause an infinite loop.
842 by changing the description here, we retrigger this subscription callback. The only thing stopping us from
843 looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
844 the description, this will fail... */
845 pa_source_set_description(source
, entry
->description
);
848 pa_xfree(entry
->icon
);
849 entry
->icon
= pa_xstrdup(pa_proplist_gets(source
->proplist
, PA_PROP_DEVICE_ICON_NAME
));
856 if (entries_equal(old
, entry
)) {
867 pa_log_info("Storing device %s.", name
);
869 if (entry_write(u
, name
, entry
))
872 pa_log_warn("Could not save device");;
878 static pa_hook_result_t
sink_new_hook_callback(pa_core
*c
, pa_sink_new_data
*new_data
, struct userdata
*u
) {
886 name
= pa_sprintf_malloc("sink:%s", new_data
->name
);
888 if ((e
= entry_read(u
, name
))) {
889 if (e
->user_set_description
&& strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
890 pa_log_info("Restoring description for sink %s.", new_data
->name
);
891 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
902 static pa_hook_result_t
source_new_hook_callback(pa_core
*c
, pa_source_new_data
*new_data
, struct userdata
*u
) {
910 name
= pa_sprintf_malloc("source:%s", new_data
->name
);
912 if ((e
= entry_read(u
, name
))) {
913 if (e
->user_set_description
&& strncmp(e
->description
, pa_proplist_gets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
), sizeof(e
->description
)) != 0) {
914 /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
915 pa_log_info("Restoring description for source %s.", new_data
->name
);
916 pa_proplist_sets(new_data
->proplist
, PA_PROP_DEVICE_DESCRIPTION
, e
->description
);
927 static pa_hook_result_t
sink_input_new_hook_callback(pa_core
*c
, pa_sink_input_new_data
*new_data
, struct userdata
*u
) {
939 pa_log_debug("Not restoring device for stream because already set.");
941 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
942 role_index
= get_role_index("none");
944 role_index
= get_role_index(role
);
946 if (PA_INVALID_INDEX
!= role_index
) {
947 uint32_t device_index
;
949 device_index
= u
->preferred_sinks
[role_index
];
950 if (PA_INVALID_INDEX
!= device_index
) {
953 if ((sink
= pa_idxset_get_by_index(u
->core
->sinks
, device_index
))) {
954 if (!pa_sink_input_new_data_set_sink(new_data
, sink
, FALSE
))
955 pa_log_debug("Not restoring device for stream because no supported format was found");
964 static pa_hook_result_t
source_output_new_hook_callback(pa_core
*c
, pa_source_output_new_data
*new_data
, struct userdata
*u
) {
975 if (new_data
->direct_on_input
)
978 if (new_data
->source
)
979 pa_log_debug("Not restoring device for stream because already set.");
981 if (!(role
= pa_proplist_gets(new_data
->proplist
, PA_PROP_MEDIA_ROLE
)))
982 role_index
= get_role_index("none");
984 role_index
= get_role_index(role
);
986 if (PA_INVALID_INDEX
!= role_index
) {
987 uint32_t device_index
;
989 device_index
= u
->preferred_sources
[role_index
];
990 if (PA_INVALID_INDEX
!= device_index
) {
993 if ((source
= pa_idxset_get_by_index(u
->core
->sources
, device_index
)))
994 if (!pa_source_output_new_data_set_source(new_data
, source
, FALSE
))
995 pa_log_debug("Not restoring device for stream because no supported format was found");
1004 static pa_hook_result_t
sink_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_sink
*sink
, struct userdata
*u
) {
1007 pa_assert(u
->core
== c
);
1008 pa_assert(u
->on_hotplug
);
1010 notify_subscribers(u
);
1012 return route_sink_inputs(u
, NULL
);
1015 static pa_hook_result_t
source_put_hook_callback(pa_core
*c
, PA_GCC_UNUSED pa_source
*source
, struct userdata
*u
) {
1018 pa_assert(u
->core
== c
);
1019 pa_assert(u
->on_hotplug
);
1021 notify_subscribers(u
);
1023 return route_source_outputs(u
, NULL
);
1026 static pa_hook_result_t
sink_unlink_hook_callback(pa_core
*c
, pa_sink
*sink
, struct userdata
*u
) {
1030 pa_assert(u
->core
== c
);
1031 pa_assert(u
->on_rescue
);
1033 /* There's no point in doing anything if the core is shut down anyway */
1034 if (c
->state
== PA_CORE_SHUTDOWN
)
1037 notify_subscribers(u
);
1039 return route_sink_inputs(u
, sink
);
1042 static pa_hook_result_t
source_unlink_hook_callback(pa_core
*c
, pa_source
*source
, struct userdata
*u
) {
1046 pa_assert(u
->core
== c
);
1047 pa_assert(u
->on_rescue
);
1049 /* There's no point in doing anything if the core is shut down anyway */
1050 if (c
->state
== PA_CORE_SHUTDOWN
)
1053 notify_subscribers(u
);
1055 return route_source_outputs(u
, source
);
1059 static void apply_entry(struct userdata
*u
, const char *name
, struct entry
*e
) {
1067 if (!e
->user_set_description
)
1070 if ((n
= get_name(name
, "sink:"))) {
1072 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1073 if (!pa_streq(s
->name
, n
)) {
1077 pa_log_info("Setting description for sink %s to '%s'", s
->name
, e
->description
);
1078 pa_sink_set_description(s
, e
->description
);
1082 else if ((n
= get_name(name
, "source:"))) {
1084 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1085 if (!pa_streq(s
->name
, n
)) {
1089 if (s
->monitor_of
) {
1090 pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s
->name
);
1094 pa_log_info("Setting description for source %s to '%s'", s
->name
, e
->description
);
1095 pa_source_set_description(s
, e
->description
);
1102 #define EXT_VERSION 1
1104 static int extension_cb(pa_native_protocol
*p
, pa_module
*m
, pa_native_connection
*c
, uint32_t tag
, pa_tagstruct
*t
) {
1107 pa_tagstruct
*reply
= NULL
;
1116 if (pa_tagstruct_getu32(t
, &command
) < 0)
1119 reply
= pa_tagstruct_new(NULL
, 0);
1120 pa_tagstruct_putu32(reply
, PA_COMMAND_REPLY
);
1121 pa_tagstruct_putu32(reply
, tag
);
1124 case SUBCOMMAND_TEST
: {
1125 if (!pa_tagstruct_eof(t
))
1128 pa_tagstruct_putu32(reply
, EXT_VERSION
);
1132 case SUBCOMMAND_READ
: {
1136 if (!pa_tagstruct_eof(t
))
1139 done
= !pa_database_first(u
->database
, &key
, NULL
);
1146 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1148 name
= pa_xstrndup(key
.data
, key
.size
);
1149 pa_datum_free(&key
);
1151 if ((e
= entry_read(u
, name
))) {
1154 uint32_t found_index
= PA_INVALID_INDEX
;
1156 if ((device_name
= get_name(name
, "sink:"))) {
1158 PA_IDXSET_FOREACH(s
, u
->core
->sinks
, idx
) {
1159 if (strcmp(s
->name
, device_name
) == 0) {
1160 found_index
= s
->index
;
1164 pa_xfree(device_name
);
1165 } else if ((device_name
= get_name(name
, "source:"))) {
1167 PA_IDXSET_FOREACH(s
, u
->core
->sources
, idx
) {
1168 if (strcmp(s
->name
, device_name
) == 0) {
1169 found_index
= s
->index
;
1173 pa_xfree(device_name
);
1176 pa_tagstruct_puts(reply
, name
);
1177 pa_tagstruct_puts(reply
, e
->description
);
1178 pa_tagstruct_puts(reply
, e
->icon
);
1179 pa_tagstruct_putu32(reply
, found_index
);
1180 pa_tagstruct_putu32(reply
, NUM_ROLES
);
1182 for (uint32_t i
= ROLE_NONE
; i
< NUM_ROLES
; ++i
) {
1183 pa_tagstruct_puts(reply
, role_names
[i
]);
1184 pa_tagstruct_putu32(reply
, e
->priority
[i
]);
1198 case SUBCOMMAND_RENAME
: {
1201 const char *device
, *description
;
1203 if (pa_tagstruct_gets(t
, &device
) < 0 ||
1204 pa_tagstruct_gets(t
, &description
) < 0)
1207 if (!device
|| !*device
|| !description
|| !*description
)
1210 if ((e
= entry_read(u
, device
))) {
1211 pa_xfree(e
->description
);
1212 e
->description
= pa_xstrdup(description
);
1213 e
->user_set_description
= TRUE
;
1215 if (entry_write(u
, (char *)device
, e
)) {
1216 apply_entry(u
, device
, e
);
1221 pa_log_warn("Could not save device");
1226 pa_log_warn("Could not rename device %s, no entry in database", device
);
1231 case SUBCOMMAND_DELETE
:
1233 while (!pa_tagstruct_eof(t
)) {
1237 if (pa_tagstruct_gets(t
, &name
) < 0)
1240 key
.data
= (char*) name
;
1241 key
.size
= strlen(name
);
1243 /** @todo: Reindex the priorities */
1244 pa_database_unset(u
->database
, &key
);
1251 case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING
: {
1255 if (pa_tagstruct_get_boolean(t
, &enable
) < 0)
1258 if ((u
->do_routing
= enable
)) {
1259 /* Update our caches */
1260 update_highest_priority_device_indexes(u
, "sink:", NULL
);
1261 update_highest_priority_device_indexes(u
, "source:", NULL
);
1267 case SUBCOMMAND_REORDER
: {
1271 uint32_t role_index
, n_devices
;
1273 pa_bool_t done
, sink_mode
= TRUE
;
1274 struct device_t
{ uint32_t prio
; char *device
; };
1275 struct device_t
*device
;
1276 struct device_t
**devices
;
1277 uint32_t i
, idx
, offset
;
1282 if (pa_tagstruct_gets(t
, &role
) < 0 ||
1283 pa_tagstruct_getu32(t
, &n_devices
) < 0 ||
1287 if (PA_INVALID_INDEX
== (role_index
= get_role_index(role
)))
1290 /* Cycle through the devices given and make sure they exist */
1291 h
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
1294 for (i
= 0; i
< n_devices
; ++i
) {
1296 if (pa_tagstruct_gets(t
, &s
) < 0) {
1297 while ((device
= pa_hashmap_steal_first(h
))) {
1298 pa_xfree(device
->device
);
1302 pa_hashmap_free(h
, NULL
, NULL
);
1303 pa_log_error("Protocol error on reorder");
1307 /* Ensure this is a valid entry */
1308 if (!(e
= entry_read(u
, s
))) {
1309 while ((device
= pa_hashmap_steal_first(h
))) {
1310 pa_xfree(device
->device
);
1314 pa_hashmap_free(h
, NULL
, NULL
);
1315 pa_log_error("Client specified an unknown device in it's reorder list.");
1322 sink_mode
= (0 == strncmp("sink:", s
, 5));
1323 } else if ((sink_mode
&& 0 != strncmp("sink:", s
, 5)) || (!sink_mode
&& 0 != strncmp("source:", s
, 7))) {
1324 while ((device
= pa_hashmap_steal_first(h
))) {
1325 pa_xfree(device
->device
);
1329 pa_hashmap_free(h
, NULL
, NULL
);
1330 pa_log_error("Attempted to reorder mixed devices (sinks and sources)");
1334 /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
1335 device
= pa_xnew(struct device_t
, 1);
1336 device
->device
= pa_xstrdup(s
);
1337 if (pa_hashmap_put(h
, device
->device
, device
) == 0) {
1341 pa_xfree(device
->device
);
1346 /*pa_log_debug("Hashmap contents (received from client)");
1347 PA_HASHMAP_FOREACH(device, h, state) {
1348 pa_log_debug(" - %s (%d)", device->device, device->prio);
1351 /* Now cycle through our list and add all the devices.
1352 This has the effect of addign in any in our DB,
1353 not specified in the device list (and thus will be
1354 tacked on at the end) */
1356 done
= !pa_database_first(u
->database
, &key
, NULL
);
1358 while (!done
&& idx
< 256) {
1361 done
= !pa_database_next(u
->database
, &key
, &next_key
, NULL
);
1363 device
= pa_xnew(struct device_t
, 1);
1364 device
->device
= pa_xstrndup(key
.data
, key
.size
);
1365 if ((sink_mode
&& 0 == strncmp("sink:", device
->device
, 5))
1366 || (!sink_mode
&& 0 == strncmp("source:", device
->device
, 7))) {
1368 /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
1369 if (pa_hashmap_put(h
, device
->device
, device
) == 0
1370 && (e
= entry_read(u
, device
->device
))) {
1371 /* We add offset on to the existing priorirty so that when we order, the
1372 existing entries are always lower priority than the new ones. */
1373 device
->prio
= (offset
+ e
->priority
[role_index
]);
1377 pa_xfree(device
->device
);
1381 pa_xfree(device
->device
);
1385 pa_datum_free(&key
);
1390 /*pa_log_debug("Hashmap contents (combined with database)");
1391 PA_HASHMAP_FOREACH(device, h, state) {
1392 pa_log_debug(" - %s (%d)", device->device, device->prio);
1395 /* Now we put all the entries in a simple list for sorting it. */
1396 n_devices
= pa_hashmap_size(h
);
1397 devices
= pa_xnew(struct device_t
*, n_devices
);
1399 while ((device
= pa_hashmap_steal_first(h
))) {
1400 devices
[idx
++] = device
;
1402 pa_hashmap_free(h
, NULL
, NULL
);
1404 /* Simple bubble sort */
1405 for (i
= 0; i
< n_devices
; ++i
) {
1406 for (uint32_t j
= i
; j
< n_devices
; ++j
) {
1407 if (devices
[i
]->prio
> devices
[j
]->prio
) {
1408 struct device_t
*tmp
;
1410 devices
[i
] = devices
[j
];
1416 /*pa_log_debug("Sorted device list");
1417 for (i = 0; i < n_devices; ++i) {
1418 pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio);
1421 /* Go through in order and write the new entry and cleanup our own list */
1424 for (i
= 0; i
< n_devices
; ++i
) {
1425 if ((e
= entry_read(u
, devices
[i
]->device
))) {
1426 if (e
->priority
[role_index
] == idx
)
1429 e
->priority
[role_index
] = idx
;
1431 if (entry_write(u
, (char *) devices
[i
]->device
, e
)) {
1439 pa_xfree(devices
[i
]->device
);
1440 pa_xfree(devices
[i
]);
1447 route_sink_inputs(u
, NULL
);
1449 route_source_outputs(u
, NULL
);
1455 case SUBCOMMAND_SUBSCRIBE
: {
1459 if (pa_tagstruct_get_boolean(t
, &enabled
) < 0 ||
1460 !pa_tagstruct_eof(t
))
1464 pa_idxset_put(u
->subscribed
, c
, NULL
);
1466 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1475 pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c
), reply
);
1481 pa_tagstruct_free(reply
);
1486 static pa_hook_result_t
connection_unlink_hook_cb(pa_native_protocol
*p
, pa_native_connection
*c
, struct userdata
*u
) {
1491 pa_idxset_remove_by_data(u
->subscribed
, c
, NULL
);
1495 struct prioritised_indexes
{
1500 int pa__init(pa_module
*m
) {
1501 pa_modargs
*ma
= NULL
;
1507 pa_bool_t do_routing
= FALSE
, on_hotplug
= TRUE
, on_rescue
= TRUE
;
1508 uint32_t total_devices
;
1512 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
1513 pa_log("Failed to parse module arguments");
1517 if (pa_modargs_get_value_boolean(ma
, "do_routing", &do_routing
) < 0 ||
1518 pa_modargs_get_value_boolean(ma
, "on_hotplug", &on_hotplug
) < 0 ||
1519 pa_modargs_get_value_boolean(ma
, "on_rescue", &on_rescue
) < 0) {
1520 pa_log("on_hotplug= and on_rescue= expect boolean arguments");
1524 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
1527 u
->do_routing
= do_routing
;
1528 u
->on_hotplug
= on_hotplug
;
1529 u
->on_rescue
= on_rescue
;
1530 u
->subscribed
= pa_idxset_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
1532 u
->protocol
= pa_native_protocol_get(m
->core
);
1533 pa_native_protocol_install_ext(u
->protocol
, m
, extension_cb
);
1535 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
);
1537 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
);
1539 /* Used to handle device description management */
1540 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
);
1541 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
);
1543 /* The following slots are used to deal with routing */
1544 /* A little bit later than module-stream-restore, but before module-intended-roles */
1545 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
);
1546 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
);
1549 /* A little bit later than module-stream-restore, but before module-intended-roles */
1550 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
);
1551 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
);
1555 /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */
1556 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
);
1557 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
);
1560 if (!(fname
= pa_state_path("device-manager", TRUE
)))
1563 if (!(u
->database
= pa_database_open(fname
, TRUE
))) {
1564 pa_log("Failed to open volume database '%s': %s", fname
, pa_cstrerror(errno
));
1569 pa_log_info("Successfully opened database file '%s'.", fname
);
1572 /* Attempt to inject the devices into the list in priority order */
1573 total_devices
= PA_MAX(pa_idxset_size(m
->core
->sinks
), pa_idxset_size(m
->core
->sources
));
1574 if (total_devices
> 0 && total_devices
< 128) {
1576 struct prioritised_indexes p_i
[128];
1578 /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
1580 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
) {
1581 pa_log_debug("Found sink index %u", sink
->index
);
1582 p_i
[i
].index
= sink
->index
;
1583 p_i
[i
++].priority
= sink
->priority
;
1585 /* Bubble sort it (only really useful for first time creation) */
1587 for (uint32_t j
= 0; j
< i
; ++j
)
1588 for (uint32_t k
= 0; k
< i
; ++k
)
1589 if (p_i
[j
].priority
> p_i
[k
].priority
) {
1590 struct prioritised_indexes tmp_pi
= p_i
[k
];
1595 for (uint32_t j
= 0; j
< i
; ++j
)
1596 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, p_i
[j
].index
, u
);
1599 /* We cycle over all the available sources so that they are added to our database if they are not in it yet */
1601 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
) {
1602 p_i
[i
].index
= source
->index
;
1603 p_i
[i
++].priority
= source
->priority
;
1605 /* Bubble sort it (only really useful for first time creation) */
1607 for (uint32_t j
= 0; j
< i
; ++j
)
1608 for (uint32_t k
= 0; k
< i
; ++k
)
1609 if (p_i
[j
].priority
> p_i
[k
].priority
) {
1610 struct prioritised_indexes tmp_pi
= p_i
[k
];
1615 for (uint32_t j
= 0; j
< i
; ++j
)
1616 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, p_i
[j
].index
, u
);
1618 else if (total_devices
> 0) {
1619 /* This user has a *lot* of devices... */
1620 PA_IDXSET_FOREACH(sink
, m
->core
->sinks
, idx
)
1621 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SINK
|PA_SUBSCRIPTION_EVENT_NEW
, sink
->index
, u
);
1623 PA_IDXSET_FOREACH(source
, m
->core
->sources
, idx
)
1624 subscribe_callback(m
->core
, PA_SUBSCRIPTION_EVENT_SOURCE
|PA_SUBSCRIPTION_EVENT_NEW
, source
->index
, u
);
1627 /* Perform the routing (if it's enabled) which will update our priority list cache too */
1628 for (uint32_t i
= 0; i
< NUM_ROLES
; ++i
) {
1629 u
->preferred_sinks
[i
] = u
->preferred_sources
[i
] = PA_INVALID_INDEX
;
1632 route_sink_inputs(u
, NULL
);
1633 route_source_outputs(u
, NULL
);
1635 #ifdef DUMP_DATABASE
1639 pa_modargs_free(ma
);
1646 pa_modargs_free(ma
);
1651 void pa__done(pa_module
*m
) {
1656 if (!(u
= m
->userdata
))
1659 if (u
->subscription
)
1660 pa_subscription_free(u
->subscription
);
1662 if (u
->sink_new_hook_slot
)
1663 pa_hook_slot_free(u
->sink_new_hook_slot
);
1664 if (u
->source_new_hook_slot
)
1665 pa_hook_slot_free(u
->source_new_hook_slot
);
1667 if (u
->sink_input_new_hook_slot
)
1668 pa_hook_slot_free(u
->sink_input_new_hook_slot
);
1669 if (u
->source_output_new_hook_slot
)
1670 pa_hook_slot_free(u
->source_output_new_hook_slot
);
1672 if (u
->sink_put_hook_slot
)
1673 pa_hook_slot_free(u
->sink_put_hook_slot
);
1674 if (u
->source_put_hook_slot
)
1675 pa_hook_slot_free(u
->source_put_hook_slot
);
1677 if (u
->sink_unlink_hook_slot
)
1678 pa_hook_slot_free(u
->sink_unlink_hook_slot
);
1679 if (u
->source_unlink_hook_slot
)
1680 pa_hook_slot_free(u
->source_unlink_hook_slot
);
1682 if (u
->connection_unlink_hook_slot
)
1683 pa_hook_slot_free(u
->connection_unlink_hook_slot
);
1685 if (u
->save_time_event
)
1686 u
->core
->mainloop
->time_free(u
->save_time_event
);
1689 pa_database_close(u
->database
);
1692 pa_native_protocol_remove_ext(u
->protocol
, m
);
1693 pa_native_protocol_unref(u
->protocol
);
1697 pa_idxset_free(u
->subscribed
, NULL
, NULL
);