X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/ce0b2bdc0718cfaec58d9809bd97a123a9fe07a4..4f972aa993954c92d3d09fa4047906b931aab1af:/src/modules/module-device-manager.c diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c index 3f4418ae..9df3d8e2 100644 --- a/src/modules/module-device-manager.c +++ b/src/modules/module-device-manager.c @@ -30,12 +30,10 @@ #include #include #include -#include +#include #include -#include #include -#include #include #include @@ -51,19 +49,21 @@ #include #include #include +#include #include "module-device-manager-symdef.h" PA_MODULE_AUTHOR("Colin Guthrie"); -PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present"); +PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role"); PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( "do_routing= " "on_hotplug= " "on_rescue="); #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) +#define DUMP_DATABASE static const char* const valid_modargs[] = { "do_routing", @@ -72,6 +72,34 @@ static const char* const valid_modargs[] = { NULL }; +#define NUM_ROLES 9 +enum { + ROLE_NONE, + ROLE_VIDEO, + ROLE_MUSIC, + ROLE_GAME, + ROLE_EVENT, + ROLE_PHONE, + ROLE_ANIMATION, + ROLE_PRODUCTION, + ROLE_A11Y, + ROLE_MAX +}; + +typedef uint32_t role_indexes_t[NUM_ROLES]; + +static const char* role_names[NUM_ROLES] = { + "none", + "video", + "music", + "game", + "event", + "phone", + "animation", + "production", + "a11y", +}; + struct userdata { pa_core *core; pa_module *module; @@ -92,33 +120,23 @@ struct userdata { pa_native_protocol *protocol; pa_idxset *subscribed; - pa_bool_t on_hotplug; - pa_bool_t on_rescue; - pa_bool_t do_routing; -}; - -#define ENTRY_VERSION 1 + bool on_hotplug; + bool on_rescue; + bool do_routing; -#define NUM_ROLES 9 -enum { - ROLE_NONE, - ROLE_VIDEO, - ROLE_MUSIC, - ROLE_GAME, - ROLE_EVENT, - ROLE_PHONE, - ROLE_ANIMATION, - ROLE_PRODUCTION, - ROLE_A11Y, + role_indexes_t preferred_sinks; + role_indexes_t preferred_sources; }; -typedef uint32_t role_indexes_t[NUM_ROLES]; +#define ENTRY_VERSION 1 struct entry { uint8_t version; - char description[PA_NAME_MAX]; + char *description; + bool user_set_description; + char *icon; role_indexes_t priority; -} PA_GCC_PACKED; +}; enum { SUBCOMMAND_TEST, @@ -126,12 +144,17 @@ enum { SUBCOMMAND_RENAME, SUBCOMMAND_DELETE, SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING, - SUBCOMMAND_PREFER_DEVICE, - SUBCOMMAND_DEFER_DEVICE, + SUBCOMMAND_REORDER, SUBCOMMAND_SUBSCRIBE, SUBCOMMAND_EVENT }; +/* Forward declarations */ +#ifdef DUMP_DATABASE +static void dump_database(struct userdata *); +#endif +static void notify_subscribers(struct userdata *); + static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { struct userdata *u = userdata; @@ -145,12 +168,124 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct pa_database_sync(u->database); pa_log_info("Synced."); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif } -static struct entry* read_entry(struct userdata *u, const char *name) { +static void trigger_save(struct userdata *u) { + + pa_assert(u); + + notify_subscribers(u); + + if (u->save_time_event) + return; + + u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); +} + +static struct entry* entry_new(void) { + struct entry *r = pa_xnew0(struct entry, 1); + r->version = ENTRY_VERSION; + return r; +} + +static void entry_free(struct entry* e) { + pa_assert(e); + + pa_xfree(e->description); + pa_xfree(e->icon); + pa_xfree(e); +} + +static bool entry_write(struct userdata *u, const char *name, const struct entry *e) { + pa_tagstruct *t; pa_datum key, data; + bool r; + + pa_assert(u); + pa_assert(name); + pa_assert(e); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu8(t, e->version); + pa_tagstruct_puts(t, e->description); + pa_tagstruct_put_boolean(t, e->user_set_description); + pa_tagstruct_puts(t, e->icon); + for (uint8_t i=0; ipriority[i]); + + key.data = (char *) name; + key.size = strlen(name); + + data.data = (void*)pa_tagstruct_data(t, &data.size); + + r = (pa_database_set(u->database, &key, &data, true) == 0); + + pa_tagstruct_free(t); + + return r; +} + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + +#define LEGACY_ENTRY_VERSION 1 +static struct entry* legacy_entry_read(struct userdata *u, pa_datum *data) { + struct legacy_entry { + uint8_t version; + char description[PA_NAME_MAX]; + bool user_set_description; + char icon[PA_NAME_MAX]; + role_indexes_t priority; + } PA_GCC_PACKED; + struct legacy_entry *le; struct entry *e; + pa_assert(u); + pa_assert(data); + + if (data->size != sizeof(struct legacy_entry)) { + pa_log_debug("Size does not match."); + return NULL; + } + + le = (struct legacy_entry*)data->data; + + if (le->version != LEGACY_ENTRY_VERSION) { + pa_log_debug("Version mismatch."); + return NULL; + } + + if (!memchr(le->description, 0, sizeof(le->description))) { + pa_log_warn("Description has missing NUL byte."); + return NULL; + } + + if (!le->description[0]) { + pa_log_warn("Description is empty."); + return NULL; + } + + if (!memchr(le->icon, 0, sizeof(le->icon))) { + pa_log_warn("Icon has missing NUL byte."); + return NULL; + } + + e = entry_new(); + e->description = pa_xstrdup(le->description); + e->icon = pa_xstrdup(le->icon); + return e; +} +#endif + +static struct entry* entry_read(struct userdata *u, const char *name) { + pa_datum key, data; + struct entry *e = NULL; + pa_tagstruct *t = NULL; + const char *description, *icon; + pa_assert(u); pa_assert(name); @@ -162,36 +297,158 @@ static struct entry* read_entry(struct userdata *u, const char *name) { if (!pa_database_get(u->database, &key, &data)) goto fail; - if (data.size != sizeof(struct entry)) { - 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)); + t = pa_tagstruct_new(data.data, data.size); + e = entry_new(); + + if (pa_tagstruct_getu8(t, &e->version) < 0 || + e->version > ENTRY_VERSION || + pa_tagstruct_gets(t, &description) < 0 || + pa_tagstruct_get_boolean(t, &e->user_set_description) < 0 || + pa_tagstruct_gets(t, &icon) < 0) { + goto fail; } - e = (struct entry*) data.data; - - if (e->version != ENTRY_VERSION) { - pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name); + if (e->user_set_description && !description) { + pa_log("Entry has user_set_description set, but the description is NULL."); goto fail; } - if (!memchr(e->description, 0, sizeof(e->description))) { - pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name); + if (e->user_set_description && !*description) { + pa_log("Entry has user_set_description set, but the description is empty."); goto fail; } + e->description = pa_xstrdup(description); + e->icon = pa_xstrdup(icon); + + for (uint8_t i=0; ipriority[i]) < 0) + goto fail; + } + + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_free(t); + pa_datum_free(&data); + return e; fail: + pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name); + + if (e) + entry_free(e); + if (t) + pa_tagstruct_free(t); + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name); + if ((e = legacy_entry_read(u, &data))) { + pa_log_debug("Success. Saving new format for key: %s", name); + if (entry_write(u, name, e)) + trigger_save(u); + pa_datum_free(&data); + return e; + } else + pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name); +#endif pa_datum_free(&data); return NULL; } -static void trigger_save(struct userdata *u) { +#ifdef DUMP_DATABASE +static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, bool sink_mode) { + pa_assert(u); + pa_assert(human); + + if (sink_mode) { + pa_sink *s; + if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No sink specified", human); + } else { + pa_source *s; + if (PA_INVALID_INDEX != u->preferred_sources[role_index] && (s = pa_idxset_get_by_index(u->core->sources, u->preferred_sources[role_index]))) + pa_log_debug(" %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name); + else + pa_log_debug(" %s No source specified", human); + } +} + +static void dump_database(struct userdata *u) { + pa_datum key; + bool done; + + pa_assert(u); + + done = !pa_database_first(u->database, &key, NULL); + + pa_log_debug("Dumping database"); + while (!done) { + char *name; + struct entry *e; + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + name = pa_xstrndup(key.data, key.size); + + if ((e = entry_read(u, name))) { + pa_log_debug(" Got entry: %s", name); + pa_log_debug(" Description: %s", e->description); + pa_log_debug(" Priorities: None: %3u, Video: %3u, Music: %3u, Game: %3u, Event: %3u", + e->priority[ROLE_NONE], e->priority[ROLE_VIDEO], e->priority[ROLE_MUSIC], e->priority[ROLE_GAME], e->priority[ROLE_EVENT]); + pa_log_debug(" Phone: %3u, Anim: %3u, Prodtn: %3u, A11y: %3u", + e->priority[ROLE_PHONE], e->priority[ROLE_ANIMATION], e->priority[ROLE_PRODUCTION], e->priority[ROLE_A11Y]); + entry_free(e); + } + + pa_xfree(name); + + pa_datum_free(&key); + key = next_key; + } + + if (u->do_routing) { + pa_log_debug(" Highest priority devices per-role:"); + + pa_log_debug(" Sinks:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, true); + } + + pa_log_debug(" Sources:"); + for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) { + char name[13]; + uint32_t len = PA_MIN(12u, strlen(role_names[role])); + strncpy(name, role_names[role], len); + for (int i = len+1; i < 12; ++i) name[i] = ' '; + name[len] = ':'; name[0] -= 32; name[12] = '\0'; + dump_database_helper(u, role, name, false); + } + } + + pa_log_debug("Completed database dump"); +} +#endif + +static void notify_subscribers(struct userdata *u) { + pa_native_connection *c; uint32_t idx; - for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) { + pa_assert(u); + + PA_IDXSET_FOREACH(c, u->subscribed, idx) { pa_tagstruct *t; t = pa_tagstruct_new(NULL, 0); @@ -203,18 +460,33 @@ static void trigger_save(struct userdata *u) { pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t); } +} - if (u->save_time_event) - return; +static bool entries_equal(const struct entry *a, const struct entry *b) { - u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u); + pa_assert(a); + pa_assert(b); + + if (!pa_streq(a->description, b->description) + || a->user_set_description != b->user_set_description + || !pa_streq(a->icon, b->icon)) + return false; + + for (int i=0; i < NUM_ROLES; ++i) + if (a->priority[i] != b->priority[i]) + return false; + + return true; } -static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { - if (strncmp(a->description, b->description, sizeof(a->description))) - return FALSE; +static char *get_name(const char *key, const char *prefix) { + char *t; + + if (strncmp(key, prefix, strlen(prefix))) + return NULL; - return TRUE; + t = pa_xstrdup(key + strlen(prefix)); + return t; } static inline struct entry *load_or_initialize_entry(struct userdata *u, struct entry *entry, const char *name, const char *prefix) { @@ -225,13 +497,15 @@ static inline struct entry *load_or_initialize_entry(struct userdata *u, struct pa_assert(name); pa_assert(prefix); - if ((old = read_entry(u, name))) + if ((old = entry_read(u, name))) { *entry = *old; - else { + entry->description = pa_xstrdup(old->description); + entry->icon = pa_xstrdup(old->icon); + } else { /* This is a new device, so make sure we write it's priority list correctly */ role_indexes_t max_priority; pa_datum key; - pa_bool_t done; + bool done; pa_zero(max_priority); done = !pa_database_first(u->database, &key, NULL); @@ -248,12 +522,12 @@ static inline struct entry *load_or_initialize_entry(struct userdata *u, struct name2 = pa_xstrndup(key.data, key.size); - if ((e = read_entry(u, name2))) { + if ((e = entry_read(u, name2))) { for (uint32_t i = 0; i < NUM_ROLES; ++i) { max_priority[i] = PA_MAX(max_priority[i], e->priority[i]); } - pa_xfree(e); + entry_free(e); } pa_xfree(name2); @@ -266,16 +540,233 @@ static inline struct entry *load_or_initialize_entry(struct userdata *u, struct for (uint32_t i = 0; i < NUM_ROLES; ++i) { entry->priority[i] = max_priority[i] + 1; } + entry->user_set_description = false; + } + + return old; +} + +static uint32_t get_role_index(const char* role) { + pa_assert(role); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) + if (pa_streq(role, role_names[i])) + return i; + + return PA_INVALID_INDEX; +} + +static void update_highest_priority_device_indexes(struct userdata *u, const char *prefix, void *ignore_device) { + role_indexes_t *indexes, highest_priority_available; + pa_datum key; + bool done, sink_mode; + + pa_assert(u); + pa_assert(prefix); + + sink_mode = pa_streq(prefix, "sink:"); + + if (sink_mode) + indexes = &u->preferred_sinks; + else + indexes = &u->preferred_sources; + + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + (*indexes)[i] = PA_INVALID_INDEX; + } + pa_zero(highest_priority_available); + + done = !pa_database_first(u->database, &key, NULL); + + /* Find all existing devices with the same prefix so we find the highest priority device for each role */ + while (!done) { + pa_datum next_key; + + done = !pa_database_next(u->database, &key, &next_key, NULL); + + if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { + char *name, *device_name; + struct entry *e; + + name = pa_xstrndup(key.data, key.size); + pa_assert_se(device_name = get_name(name, prefix)); + + if ((e = entry_read(u, name))) { + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + if (!highest_priority_available[i] || e->priority[i] < highest_priority_available[i]) { + /* We've found a device with a higher priority than that we've currently got, + so see if it is currently available or not and update our list */ + uint32_t idx; + bool found = false; + + if (sink_mode) { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { + if ((pa_sink*) ignore_device == sink) + continue; + if (pa_streq(sink->name, device_name)) { + found = true; + idx = sink->index; /* Is this needed? */ + break; + } + } + } else { + pa_source *source; + + PA_IDXSET_FOREACH(source, u->core->sources, idx) { + if ((pa_source*) ignore_device == source) + continue; + if (pa_streq(source->name, device_name)) { + found = true; + idx = source->index; /* Is this needed? */ + break; + } + } + } + if (found) { + highest_priority_available[i] = e->priority[i]; + (*indexes)[i] = idx; + } + + } + } + + entry_free(e); + } + + pa_xfree(name); + pa_xfree(device_name); + } + + pa_datum_free(&key); + key = next_key; + } +} + +static void route_sink_input(struct userdata *u, pa_sink_input *si) { + const char *role; + uint32_t role_index, device_index; + pa_sink *sink; + + pa_assert(u); + pa_assert(u->do_routing); + + if (si->save_sink) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!si->sink) + return; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + return; + + if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sinks[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index))) + return; + + if (si->sink != sink) + pa_sink_input_move_to(si, sink, false); +} + +static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "sink:", ignore_sink); + + PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) { + route_sink_input(u, si); + } + + return PA_HOOK_OK; +} + +static void route_source_output(struct userdata *u, pa_source_output *so) { + const char *role; + uint32_t role_index, device_index; + pa_source *source; + + pa_assert(u); + pa_assert(u->do_routing); + + if (so->save_source) + return; + + if (so->direct_on_input) + return; + + /* Skip this if it is already in the process of being moved anyway */ + if (!so->source) + return; + + /* It might happen that a stream and a source are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) + return; + + if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); + + if (PA_INVALID_INDEX == role_index) + return; + + device_index = u->preferred_sources[role_index]; + if (PA_INVALID_INDEX == device_index) + return; + + if (!(source = pa_idxset_get_by_index(u->core->sources, device_index))) + return; + + if (so->source != source) + pa_source_output_move_to(so, source, false); +} + +static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) { + pa_source_output *so; + uint32_t idx; + + pa_assert(u); + + if (!u->do_routing) + return PA_HOOK_OK; + + update_highest_priority_device_indexes(u, "source:", ignore_source); + + PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) { + route_source_output(u, so); } - return old; + return PA_HOOK_OK; } static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { struct userdata *u = userdata; - struct entry entry, *old = NULL; + struct entry *entry, *old = NULL; char *name = NULL; - pa_datum key, data; pa_assert(c); pa_assert(u); @@ -283,25 +774,64 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) && t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) && t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) && - t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) && + + /*t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + /*t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&*/ + t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { + pa_sink_input *si; + + if (!u->do_routing) + return; + if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) + return; + + /* The role may change mid-stream, so we reroute */ + route_sink_input(u, si); + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) { + pa_source_output *so; + + if (!u->do_routing) + return; + if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) + return; - pa_zero(entry); - entry.version = ENTRY_VERSION; + /* The role may change mid-stream, so we reroute */ + route_source_output(u, so); - if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + return; + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { pa_sink *sink; if (!(sink = pa_idxset_get_by_index(c->sinks, idx))) return; + entry = entry_new(); name = pa_sprintf_malloc("sink:%s", sink->name); - old = load_or_initialize_entry(u, &entry, name, "sink:"); + old = load_or_initialize_entry(u, entry, name, "sink:"); + + if (!entry->user_set_description) { + pa_xfree(entry->description); + entry->description = pa_xstrdup(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } else if (!pa_streq(entry->description, pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_sink_set_description(sink, entry->description); + } - pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)); + pa_xfree(entry->icon); + entry->icon = pa_xstrdup(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)); - } else { + } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { pa_source *source; pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); @@ -312,37 +842,52 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (source->monitor_of) return; + entry = entry_new(); name = pa_sprintf_malloc("source:%s", source->name); - old = load_or_initialize_entry(u, &entry, name, "source:"); + old = load_or_initialize_entry(u, entry, name, "source:"); + + if (!entry->user_set_description) { + pa_xfree(entry->description); + entry->description = pa_xstrdup(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)); + } else if (!pa_streq(entry->description, pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))) { + /* Warning: If two modules fight over the description, this could cause an infinite loop. + by changing the description here, we retrigger this subscription callback. The only thing stopping us from + looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage + the description, this will fail... */ + pa_source_set_description(source, entry->description); + } - pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)); + pa_xfree(entry->icon); + entry->icon = pa_xstrdup(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)); + } else { + pa_assert_not_reached(); } + pa_assert(name); + if (old) { - if (entries_equal(old, &entry)) { - pa_xfree(old); + if (entries_equal(old, entry)) { + entry_free(old); + entry_free(entry); pa_xfree(name); + return; } - pa_xfree(old); + entry_free(old); } - key.data = name; - key.size = strlen(name); - - data.data = &entry; - data.size = sizeof(entry); - pa_log_info("Storing device %s.", name); - pa_database_set(u->database, &key, &data, TRUE); + if (entry_write(u, name, entry)) + trigger_save(u); + else + pa_log_warn("Could not save device");; + entry_free(entry); pa_xfree(name); - - trigger_save(u); } static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { @@ -355,13 +900,13 @@ static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new name = pa_sprintf_malloc("sink:%s", new_data->name); - if ((e = read_entry(u, name))) { - if (strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + if ((e = entry_read(u, name))) { + if (e->user_set_description && !pa_safe_streq(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION))) { pa_log_info("Restoring description for sink %s.", new_data->name); pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -379,14 +924,14 @@ static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data name = pa_sprintf_malloc("source:%s", new_data->name); - if ((e = read_entry(u, name))) { - if (strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) { + if ((e = entry_read(u, name))) { + if (e->user_set_description && !pa_safe_streq(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION))) { /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */ pa_log_info("Restoring description for source %s.", new_data->name); pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -394,124 +939,10 @@ static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data return PA_HOOK_OK; } -static char *get_name(const char *key, const char *prefix) { - char *t; - - if (strncmp(key, prefix, strlen(prefix))) - return NULL; - - t = pa_xstrdup(key + strlen(prefix)); - return t; -} - -static uint32_t get_role_index(const char* role) { - pa_assert(role); - - if (strcmp(role, "") == 0) - return ROLE_NONE; - if (strcmp(role, "video") == 0) - return ROLE_VIDEO; - if (strcmp(role, "music") == 0) - return ROLE_MUSIC; - if (strcmp(role, "game") == 0) - return ROLE_GAME; - if (strcmp(role, "event") == 0) - return ROLE_EVENT; - if (strcmp(role, "phone") == 0) - return ROLE_PHONE; - if (strcmp(role, "animation") == 0) - return ROLE_ANIMATION; - if (strcmp(role, "production") == 0) - return ROLE_PRODUCTION; - if (strcmp(role, "a11y") == 0) - return ROLE_A11Y; - return PA_INVALID_INDEX; -} - -static role_indexes_t *get_highest_priority_device_indexes(struct userdata *u, const char *prefix) { - role_indexes_t *indexes, highest_priority_available; - pa_datum key; - pa_bool_t done, sink_mode; - - pa_assert(u); - pa_assert(prefix); - - indexes = pa_xnew(role_indexes_t, 1); - for (uint32_t i = 0; i < NUM_ROLES; ++i) { - *indexes[i] = PA_INVALID_INDEX; - } - pa_zero(highest_priority_available); - - sink_mode = (strcmp(prefix, "sink:") == 0); - - done = !pa_database_first(u->database, &key, NULL); - - /* Find all existing devices with the same prefix so we find the highest priority device for each role */ - while (!done) { - pa_datum next_key; - - done = !pa_database_next(u->database, &key, &next_key, NULL); - - if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { - char *name; - struct entry *e; - - name = pa_xstrndup(key.data, key.size); - - if ((e = read_entry(u, name))) { - for (uint32_t i = 0; i < NUM_ROLES; ++i) { - if (highest_priority_available[i] && e->priority[i] < highest_priority_available[i]) { - /* We've found a device with a higher priority than that we've currently got, - so see if it is currently available or not and update our list */ - uint32_t idx; - pa_bool_t found = FALSE; - char *device_name = get_name(name, prefix); - - if (sink_mode) { - pa_sink *sink; - - PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { - if (strcmp(sink->name, device_name) == 0) { - found = TRUE; - idx = sink->index; /* Is this needed? */ - break; - } - } - } else { - pa_source *source; - - PA_IDXSET_FOREACH(source, u->core->sources, idx) { - if (strcmp(source->name, device_name) == 0) { - found = TRUE; - idx = source->index; /* Is this needed? */ - break; - } - } - } - if (found) { - highest_priority_available[i] = e->priority[i]; - *indexes[i] = idx; - } - - pa_xfree(device_name); - } - } - - pa_xfree(e); - } - - pa_xfree(name); - } - - pa_datum_free(&key); - key = next_key; - } - - return indexes; -} - - static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + pa_assert(c); pa_assert(new_data); pa_assert(u); @@ -520,29 +951,23 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n return PA_HOOK_OK; if (new_data->sink) - pa_log_debug("Not restoring device for stream, because already set."); + pa_log_debug("Not restoring device for stream because already set."); else { - const char *role; - uint32_t role_index; - if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) - role_index = get_role_index(""); + role_index = get_role_index("none"); else role_index = get_role_index(role); if (PA_INVALID_INDEX != role_index) { - role_indexes_t *indexes; uint32_t device_index; - pa_assert_se(indexes = get_highest_priority_device_indexes(u, "sink:")); - - device_index = *indexes[role_index]; + device_index = u->preferred_sinks[role_index]; if (PA_INVALID_INDEX != device_index) { pa_sink *sink; if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) { - new_data->sink = sink; - new_data->save_sink = TRUE; + if (!pa_sink_input_new_data_set_sink(new_data, sink, false)) + pa_log_debug("Not restoring device for stream because no supported format was found"); } } } @@ -552,6 +977,9 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n } static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { + const char *role; + uint32_t role_index; + pa_assert(c); pa_assert(new_data); pa_assert(u); @@ -563,30 +991,23 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou return PA_HOOK_OK; if (new_data->source) - pa_log_debug("Not restoring device for stream, because already set"); + pa_log_debug("Not restoring device for stream because already set."); else { - const char *role; - uint32_t role_index; - if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) - role_index = get_role_index(""); + role_index = get_role_index("none"); else role_index = get_role_index(role); if (PA_INVALID_INDEX != role_index) { - role_indexes_t *indexes; uint32_t device_index; - pa_assert_se(indexes = get_highest_priority_device_indexes(u, "source:")); - - device_index = *indexes[role_index]; + device_index = u->preferred_sources[role_index]; if (PA_INVALID_INDEX != device_index) { pa_source *source; - if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) { - new_data->source = source; - new_data->save_source = TRUE; - } + if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) + if (!pa_source_output_new_data_set_source(new_data, source, false)) + pa_log_debug("Not restoring device for stream because no supported format was found"); } } } @@ -594,124 +1015,15 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou return PA_HOOK_OK; } -static pa_hook_result_t reroute_sinks(struct userdata *u) { - pa_sink_input *si; - role_indexes_t *indexes; - uint32_t idx; - - pa_assert(u); - - if (!u->do_routing) - return PA_HOOK_OK; - - pa_assert_se(indexes = get_highest_priority_device_indexes(u, "sink:")); - - PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) { - const char *role; - uint32_t role_index, device_index; - pa_sink *sink; - - if (si->save_sink) - continue; - - /* Skip this if it is already in the process of being moved anyway */ - if (!si->sink) - continue; - - /* It might happen that a stream and a sink are set up at the - same time, in which case we want to make sure we don't - interfere with that */ - if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) - continue; - - if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) - role_index = get_role_index(""); - else - role_index = get_role_index(role); - - if (PA_INVALID_INDEX == role_index) - continue; - - device_index = *indexes[role_index]; - if (PA_INVALID_INDEX == device_index) - continue; - - if (!(sink = pa_idxset_get_by_index(u->core->sinks, device_index))) - continue; - - if (si->sink != sink) - pa_sink_input_move_to(si, sink, TRUE); - } - - pa_xfree(indexes); - - return PA_HOOK_OK; -} - -static pa_hook_result_t reroute_sources(struct userdata *u) { - pa_source_output *so; - role_indexes_t *indexes; - uint32_t idx; - - pa_assert(u); - - if (!u->do_routing) - return PA_HOOK_OK; - - pa_assert_se(indexes = get_highest_priority_device_indexes(u, "source:")); - - PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) { - const char *role; - uint32_t role_index, device_index; - pa_source *source; - - if (so->save_source) - continue; - - if (so->direct_on_input) - continue; - - /* Skip this if it is already in the process of being moved anyway */ - if (!so->source) - continue; - - /* It might happen that a stream and a source are set up at the - same time, in which case we want to make sure we don't - interfere with that */ - if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) - continue; - - if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) - role_index = get_role_index(""); - else - role_index = get_role_index(role); - - if (PA_INVALID_INDEX == role_index) - continue; - - device_index = *indexes[role_index]; - if (PA_INVALID_INDEX == device_index) - continue; - - if (!(source = pa_idxset_get_by_index(u->core->sources, device_index))) - continue; - - if (so->source != source) - pa_source_output_move_to(so, source, TRUE); - } - - pa_xfree(indexes); - - return PA_HOOK_OK; -} - static pa_hook_result_t sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) { pa_assert(c); pa_assert(u); pa_assert(u->core == c); pa_assert(u->on_hotplug); - return reroute_sinks(u); + notify_subscribers(u); + + return route_sink_inputs(u, NULL); } static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_source *source, struct userdata *u) { @@ -720,11 +1032,14 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_so pa_assert(u->core == c); pa_assert(u->on_hotplug); - return reroute_sources(u); + notify_subscribers(u); + + return route_source_outputs(u, NULL); } -static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) { +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { pa_assert(c); + pa_assert(sink); pa_assert(u); pa_assert(u->core == c); pa_assert(u->on_rescue); @@ -733,11 +1048,14 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, PA_GCC_UNUSED pa_s if (c->state == PA_CORE_SHUTDOWN) return PA_HOOK_OK; - return reroute_sinks(u); + notify_subscribers(u); + + return route_sink_inputs(u, sink); } -static pa_hook_result_t source_unlink_hook_callback(pa_core *c, PA_GCC_UNUSED pa_source *source, struct userdata *u) { +static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { pa_assert(c); + pa_assert(source); pa_assert(u); pa_assert(u->core == c); pa_assert(u->on_rescue); @@ -746,13 +1064,12 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, PA_GCC_UNUSED pa if (c->state == PA_CORE_SHUTDOWN) return PA_HOOK_OK; - return reroute_sinks(u); -} + notify_subscribers(u); + return route_source_outputs(u, source); +} static void apply_entry(struct userdata *u, const char *name, struct entry *e) { - pa_sink *sink; - pa_source *source; uint32_t idx; char *n; @@ -760,36 +1077,40 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { pa_assert(name); pa_assert(e); + if (!e->user_set_description) + return; + if ((n = get_name(name, "sink:"))) { - for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx)) { - if (!pa_streq(sink->name, n)) { + pa_sink *s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (!pa_streq(s->name, n)) { continue; } - pa_log_info("Setting description for sink %s.", sink->name); - pa_sink_set_description(sink, e->description); + pa_log_info("Setting description for sink %s to '%s'", s->name, e->description); + pa_sink_set_description(s, e->description); } pa_xfree(n); } else if ((n = get_name(name, "source:"))) { - for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx)) { - if (!pa_streq(source->name, n)) { + pa_source *s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (!pa_streq(s->name, n)) { continue; } - if (source->monitor_of) { - pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source->name); + if (s->monitor_of) { + pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s->name); continue; } - pa_log_info("Setting description for source %s.", source->name); - pa_source_set_description(source, e->description); + pa_log_info("Setting description for source %s to '%s'", s->name, e->description); + pa_source_set_description(s, e->description); } pa_xfree(n); } } - #define EXT_VERSION 1 static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { @@ -822,7 +1143,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio case SUBCOMMAND_READ: { pa_datum key; - pa_bool_t done; + bool done; if (!pa_tagstruct_eof(t)) goto fail; @@ -839,11 +1160,43 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio name = pa_xstrndup(key.data, key.size); pa_datum_free(&key); - if ((e = read_entry(u, name))) { - pa_tagstruct_puts(reply, name); - pa_tagstruct_puts(reply, e->description); + if ((e = entry_read(u, name))) { + uint32_t idx; + char *device_name; + uint32_t found_index = PA_INVALID_INDEX; + + if ((device_name = get_name(name, "sink:"))) { + pa_sink* s; + PA_IDXSET_FOREACH(s, u->core->sinks, idx) { + if (pa_streq(s->name, device_name)) { + found_index = s->index; + break; + } + } + pa_xfree(device_name); + } else if ((device_name = get_name(name, "source:"))) { + pa_source* s; + PA_IDXSET_FOREACH(s, u->core->sources, idx) { + if (pa_streq(s->name, device_name)) { + found_index = s->index; + break; + } + } + pa_xfree(device_name); + } + + pa_tagstruct_puts(reply, name); + pa_tagstruct_puts(reply, e->description); + pa_tagstruct_puts(reply, e->icon); + pa_tagstruct_putu32(reply, found_index); + pa_tagstruct_putu32(reply, NUM_ROLES); + + for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) { + pa_tagstruct_puts(reply, role_names[i]); + pa_tagstruct_putu32(reply, e->priority[i]); + } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -866,18 +1219,12 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio if (!device || !*device || !description || !*description) goto fail; - if ((e = read_entry(u, device)) && ENTRY_VERSION == e->version) { - pa_datum key, data; - - pa_strlcpy(e->description, description, sizeof(e->description)); + if ((e = entry_read(u, device))) { + pa_xfree(e->description); + e->description = pa_xstrdup(description); + e->user_set_description = true; - key.data = (char *) device; - key.size = strlen(device); - - data.data = e; - data.size = sizeof(*e); - - if (pa_database_set(u->database, &key, &data, TRUE) == 0) { + if (entry_write(u, (char *)device, e)) { apply_entry(u, device, e); trigger_save(u); @@ -885,7 +1232,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio else pa_log_warn("Could not save device"); - pa_xfree(e); + entry_free(e); } else pa_log_warn("Could not rename device %s, no entry in database", device); @@ -915,132 +1262,213 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING: { - pa_bool_t enable; + bool enable; if (pa_tagstruct_get_boolean(t, &enable) < 0) goto fail; - u->do_routing = enable; + if ((u->do_routing = enable)) { + /* Update our caches */ + update_highest_priority_device_indexes(u, "sink:", NULL); + update_highest_priority_device_indexes(u, "source:", NULL); + } break; } - case SUBCOMMAND_PREFER_DEVICE: - case SUBCOMMAND_DEFER_DEVICE: { + case SUBCOMMAND_REORDER: { - const char *role, *device; + const char *role; struct entry *e; - uint32_t role_index; + uint32_t role_index, n_devices; + pa_datum key; + bool done, sink_mode = true; + struct device_t { uint32_t prio; char *device; }; + struct device_t *device; + struct device_t **devices; + uint32_t i, idx, offset; + pa_hashmap *h; + /*void *state;*/ + bool first; if (pa_tagstruct_gets(t, &role) < 0 || - pa_tagstruct_gets(t, &device) < 0) + pa_tagstruct_getu32(t, &n_devices) < 0 || + n_devices < 1) goto fail; - if (!role || !device || !*device) + if (PA_INVALID_INDEX == (role_index = get_role_index(role))) goto fail; - role_index = get_role_index(role); - if (PA_INVALID_INDEX == role_index) - goto fail; + /* Cycle through the devices given and make sure they exist */ + h = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + first = true; + idx = 0; + for (i = 0; i < n_devices; ++i) { + const char *s; + if (pa_tagstruct_gets(t, &s) < 0) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } - if ((e = read_entry(u, device)) && ENTRY_VERSION == e->version) { - pa_datum key, data; - pa_bool_t done; - char* prefix = NULL; - uint32_t priority; - pa_bool_t haschanged = FALSE; + pa_hashmap_free(h); + pa_log_error("Protocol error on reorder"); + goto fail; + } - if (strncmp(device, "sink:", 5) == 0) - prefix = pa_xstrdup("sink:"); - else if (strncmp(device, "source:", 7) == 0) - prefix = pa_xstrdup("source:"); + /* Ensure this is a valid entry */ + if (!(e = entry_read(u, s))) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } - if (!prefix) + pa_hashmap_free(h); + pa_log_error("Client specified an unknown device in it's reorder list."); goto fail; + } + entry_free(e); + + if (first) { + first = false; + sink_mode = (0 == strncmp("sink:", s, 5)); + } else if ((sink_mode && 0 != strncmp("sink:", s, 5)) || (!sink_mode && 0 != strncmp("source:", s, 7))) { + while ((device = pa_hashmap_steal_first(h))) { + pa_xfree(device->device); + pa_xfree(device); + } - priority = e->priority[role_index]; + pa_hashmap_free(h); + pa_log_error("Attempted to reorder mixed devices (sinks and sources)"); + goto fail; + } - /* Now we need to load up all the other entries of this type and shuffle the priroities around */ + /* Add the device to our hashmap. If it's already in it, free it now and carry on */ + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrdup(s); + if (pa_hashmap_put(h, device->device, device) == 0) { + device->prio = idx; + idx++; + } else { + pa_xfree(device->device); + pa_xfree(device); + } + } - done = !pa_database_first(u->database, &key, NULL); + /*pa_log_debug("Hashmap contents (received from client)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ - while (!done && !haschanged) { - pa_datum next_key; + /* Now cycle through our list and add all the devices. + This has the effect of adding in any in our DB, + not specified in the device list (and thus will be + tacked on at the end) */ + offset = idx; + done = !pa_database_first(u->database, &key, NULL); - done = !pa_database_next(u->database, &key, &next_key, NULL); + while (!done && idx < 256) { + pa_datum next_key; - /* Only read devices with the right prefix */ - if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) { - char *name; - struct entry *e2; + done = !pa_database_next(u->database, &key, &next_key, NULL); - name = pa_xstrndup(key.data, key.size); + device = pa_xnew(struct device_t, 1); + device->device = pa_xstrndup(key.data, key.size); + if ((sink_mode && 0 == strncmp("sink:", device->device, 5)) + || (!sink_mode && 0 == strncmp("source:", device->device, 7))) { + + /* Add the device to our hashmap. If it's already in it, free it now and carry on */ + if (pa_hashmap_put(h, device->device, device) == 0 + && (e = entry_read(u, device->device))) { + /* We add offset on to the existing priority so that when we order, the + existing entries are always lower priority than the new ones. */ + device->prio = (offset + e->priority[role_index]); + pa_xfree(e); + } + else { + pa_xfree(device->device); + pa_xfree(device); + } + } else { + pa_xfree(device->device); + pa_xfree(device); + } - if ((e2 = read_entry(u, name))) { - if (SUBCOMMAND_PREFER_DEVICE == command) { - /* PREFER */ - if (e2->priority[role_index] == (priority - 1)) { - e2->priority[role_index]++; - haschanged = TRUE; - } - } else { - /* DEFER */ - if (e2->priority[role_index] == (priority + 1)) { - e2->priority[role_index]--; - haschanged = TRUE; - } - } + pa_datum_free(&key); - if (haschanged) { - data.data = e2; - data.size = sizeof(*e2); + key = next_key; + } - if (pa_database_set(u->database, &key, &data, TRUE)) - pa_log_warn("Could not save device"); - } + /*pa_log_debug("Hashmap contents (combined with database)"); + PA_HASHMAP_FOREACH(device, h, state) { + pa_log_debug(" - %s (%d)", device->device, device->prio); + }*/ + + /* Now we put all the entries in a simple list for sorting it. */ + n_devices = pa_hashmap_size(h); + devices = pa_xnew(struct device_t *, n_devices); + idx = 0; + while ((device = pa_hashmap_steal_first(h))) { + devices[idx++] = device; + } + pa_hashmap_free(h); + + /* Simple bubble sort */ + for (i = 0; i < n_devices; ++i) { + for (uint32_t j = i; j < n_devices; ++j) { + if (devices[i]->prio > devices[j]->prio) { + struct device_t *tmp; + tmp = devices[i]; + devices[i] = devices[j]; + devices[j] = tmp; + } + } + } - pa_xfree(e2); + /*pa_log_debug("Sorted device list"); + for (i = 0; i < n_devices; ++i) { + pa_log_debug(" - %s (%d)", devices[i]->device, devices[i]->prio); + }*/ + + /* Go through in order and write the new entry and cleanup our own list */ + idx = 1; + first = true; + for (i = 0; i < n_devices; ++i) { + if ((e = entry_read(u, devices[i]->device))) { + if (e->priority[role_index] == idx) + idx++; + else { + e->priority[role_index] = idx; + + if (entry_write(u, (char *) devices[i]->device, e)) { + first = false; + idx++; } - - pa_xfree(name); } - pa_datum_free(&key); - key = next_key; + pa_xfree(e); } + pa_xfree(devices[i]->device); + pa_xfree(devices[i]); + } - /* Now write out our actual entry */ - if (haschanged) { - if (SUBCOMMAND_PREFER_DEVICE == command) - e->priority[role_index]--; - else - e->priority[role_index]++; - - key.data = (char *) device; - key.size = strlen(device); - - data.data = e; - data.size = sizeof(*e); - - if (pa_database_set(u->database, &key, &data, TRUE)) - pa_log_warn("Could not save device"); - - trigger_save(u); - } + pa_xfree(devices); - pa_xfree(e); + if (!first) { + trigger_save(u); - pa_xfree(prefix); + if (sink_mode) + route_sink_inputs(u, NULL); + else + route_source_outputs(u, NULL); } - else - pa_log_warn("Could not reorder device %s, no entry in database", device); break; } case SUBCOMMAND_SUBSCRIBE: { - pa_bool_t enabled; + bool enabled; if (pa_tagstruct_get_boolean(t, &enabled) < 0 || !pa_tagstruct_eof(t)) @@ -1078,16 +1506,20 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati return PA_HOOK_OK; } +struct prioritised_indexes { + uint32_t index; + int32_t priority; +}; + int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; char *fname; pa_sink *sink; pa_source *source; - pa_sink_input *si; - pa_source_output *so; uint32_t idx; - pa_bool_t do_routing = FALSE, on_hotplug = TRUE, on_rescue = TRUE; + bool do_routing = false, on_hotplug = true, on_rescue = true; + uint32_t total_devices; pa_assert(m); @@ -1116,52 +1548,106 @@ int pa__init(pa_module*m) { 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); - u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u); + 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); /* Used to handle device description management */ 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); 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); /* The following slots are used to deal with routing */ - /* A little bit later than module-stream-restore, module-intended-roles */ - 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); - 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); + /* A little bit later than module-stream-restore, but before module-intended-roles */ + 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); + 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); if (on_hotplug) { - /* A little bit later than module-stream-restore, module-intended-roles */ - 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); - 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); + /* A little bit later than module-stream-restore, but before module-intended-roles */ + 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); + 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); } if (on_rescue) { - /* A little bit later than module-stream-restore, module-intended-roles, a little bit earlier than module-rescue-streams, ... */ - 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); - 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); + /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */ + 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); + 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); } - if (!(fname = pa_state_path("device-manager", TRUE))) + if (!(fname = pa_state_path("device-manager", true))) goto fail; - if (!(u->database = pa_database_open(fname, TRUE))) { + if (!(u->database = pa_database_open(fname, true))) { pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno)); pa_xfree(fname); goto fail; } - pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_log_info("Successfully opened database file '%s'.", fname); pa_xfree(fname); - PA_IDXSET_FOREACH(sink, m->core->sinks, idx) - subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); + /* Attempt to inject the devices into the list in priority order */ + total_devices = PA_MAX(pa_idxset_size(m->core->sinks), pa_idxset_size(m->core->sources)); + if (total_devices > 0 && total_devices < 128) { + uint32_t i; + struct prioritised_indexes p_i[128]; + + /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */ + i = 0; + PA_IDXSET_FOREACH(sink, m->core->sinks, idx) { + pa_log_debug("Found sink index %u", sink->index); + p_i[i ].index = sink->index; + p_i[i++].priority = sink->priority; + } + /* Bubble sort it (only really useful for first time creation) */ + if (i > 1) + for (uint32_t j = 0; j < i; ++j) + for (uint32_t k = 0; k < i; ++k) + if (p_i[j].priority > p_i[k].priority) { + struct prioritised_indexes tmp_pi = p_i[k]; + p_i[k] = p_i[j]; + p_i[j] = tmp_pi; + } + /* Register it */ + for (uint32_t j = 0; j < i; ++j) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, p_i[j].index, u); + + /* We cycle over all the available sources so that they are added to our database if they are not in it yet */ + i = 0; + PA_IDXSET_FOREACH(source, m->core->sources, idx) { + p_i[i ].index = source->index; + p_i[i++].priority = source->priority; + } + /* Bubble sort it (only really useful for first time creation) */ + if (i > 1) + for (uint32_t j = 0; j < i; ++j) + for (uint32_t k = 0; k < i; ++k) + if (p_i[j].priority > p_i[k].priority) { + struct prioritised_indexes tmp_pi = p_i[k]; + p_i[k] = p_i[j]; + p_i[j] = tmp_pi; + } + /* Register it */ + for (uint32_t j = 0; j < i; ++j) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, p_i[j].index, u); + } + else if (total_devices > 0) { + /* This user has a *lot* of devices... */ + PA_IDXSET_FOREACH(sink, m->core->sinks, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); + + PA_IDXSET_FOREACH(source, m->core->sources, idx) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u); + } - PA_IDXSET_FOREACH(source, m->core->sources, idx) - subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u); + /* Perform the routing (if it's enabled) which will update our priority list cache too */ + for (uint32_t i = 0; i < NUM_ROLES; ++i) { + u->preferred_sinks[i] = u->preferred_sources[i] = PA_INVALID_INDEX; + } - PA_IDXSET_FOREACH(si, m->core->sink_inputs, idx) - subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, si->index, u); + route_sink_inputs(u, NULL); + route_source_outputs(u, NULL); - PA_IDXSET_FOREACH(so, m->core->source_outputs, idx) - subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, so->index, u); +#ifdef DUMP_DATABASE + dump_database(u); +#endif pa_modargs_free(ma); return 0; @@ -1172,7 +1658,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } void pa__done(pa_module*m) { @@ -1206,6 +1692,9 @@ void pa__done(pa_module*m) { if (u->source_unlink_hook_slot) pa_hook_slot_free(u->source_unlink_hook_slot); + if (u->connection_unlink_hook_slot) + pa_hook_slot_free(u->connection_unlink_hook_slot); + if (u->save_time_event) u->core->mainloop->time_free(u->save_time_event); @@ -1218,7 +1707,7 @@ void pa__done(pa_module*m) { } if (u->subscribed) - pa_idxset_free(u->subscribed, NULL, NULL); + pa_idxset_free(u->subscribed, NULL); pa_xfree(u); }