X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/20eedb24163884612c0fe81846ccf2983f336b7c..5f7dfd9b91755a2b8a26951de44a656e3c99533f:/src/modules/module-device-manager.c?ds=sidebyside diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c index 86ea95d0..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,13 +49,14 @@ #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 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= " @@ -84,6 +83,7 @@ enum { ROLE_ANIMATION, ROLE_PRODUCTION, ROLE_A11Y, + ROLE_MAX }; typedef uint32_t role_indexes_t[NUM_ROLES]; @@ -120,9 +120,9 @@ struct userdata { pa_native_protocol *protocol; pa_idxset *subscribed; - pa_bool_t on_hotplug; - pa_bool_t on_rescue; - pa_bool_t do_routing; + bool on_hotplug; + bool on_rescue; + bool do_routing; role_indexes_t preferred_sinks; role_indexes_t preferred_sources; @@ -132,10 +132,11 @@ struct userdata { struct entry { uint8_t version; - char description[PA_NAME_MAX]; - char icon[PA_NAME_MAX]; + char *description; + bool user_set_description; + char *icon; role_indexes_t priority; -} PA_GCC_PACKED; +}; enum { SUBCOMMAND_TEST, @@ -148,11 +149,143 @@ enum { 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; + + pa_assert(a); + pa_assert(e); + pa_assert(u); -static struct entry* read_entry(struct userdata *u, const char *name) { + pa_assert(e == u->save_time_event); + u->core->mainloop->time_free(u->save_time_event); + u->save_time_event = NULL; + + pa_database_sync(u->database); + pa_log_info("Synced."); + +#ifdef DUMP_DATABASE + dump_database(u); +#endif +} + +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); @@ -164,33 +297,70 @@ 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; } #ifdef DUMP_DATABASE -static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, pa_bool_t sink_mode) { +static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, bool sink_mode) { pa_assert(u); pa_assert(human); @@ -211,7 +381,7 @@ static void dump_database_helper(struct userdata *u, uint32_t role_index, const static void dump_database(struct userdata *u) { pa_datum key; - pa_bool_t done; + bool done; pa_assert(u); @@ -227,14 +397,14 @@ static void dump_database(struct userdata *u) { name = pa_xstrndup(key.data, key.size); - if ((e = read_entry(u, name))) { + 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]); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -253,7 +423,7 @@ static void dump_database(struct userdata *u) { 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); + dump_database_helper(u, role, name, true); } pa_log_debug(" Sources:"); @@ -263,7 +433,7 @@ static void dump_database(struct userdata *u) { 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); + dump_database_helper(u, role, name, false); } } @@ -271,25 +441,6 @@ static void dump_database(struct userdata *u) { } #endif -static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { - struct userdata *u = userdata; - - pa_assert(a); - pa_assert(e); - pa_assert(u); - - pa_assert(e == u->save_time_event); - u->core->mainloop->time_free(u->save_time_event); - u->save_time_event = NULL; - - pa_database_sync(u->database); - pa_log_info("Synced."); - -#ifdef DUMP_DATABASE - dump_database(u); -#endif -} - static void notify_subscribers(struct userdata *u) { pa_native_connection *c; @@ -297,7 +448,7 @@ static void notify_subscribers(struct userdata *u) { pa_assert(u); - for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) { + PA_IDXSET_FOREACH(c, u->subscribed, idx) { pa_tagstruct *t; t = pa_tagstruct_new(NULL, 0); @@ -311,32 +462,21 @@ static void notify_subscribers(struct userdata *u) { } } -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 pa_bool_t entries_equal(const struct entry *a, const struct entry *b) { +static bool entries_equal(const struct entry *a, const struct entry *b) { pa_assert(a); pa_assert(b); - if (strncmp(a->description, b->description, sizeof(a->description)) - || strncmp(a->icon, b->icon, sizeof(a->icon))) - return FALSE; + 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 false; - return TRUE; + return true; } static char *get_name(const char *key, const char *prefix) { @@ -357,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); @@ -380,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); @@ -398,6 +540,7 @@ 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; @@ -407,7 +550,7 @@ static uint32_t get_role_index(const char* role) { pa_assert(role); for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) - if (strcmp(role, role_names[i]) == 0) + if (pa_streq(role, role_names[i])) return i; return PA_INVALID_INDEX; @@ -416,12 +559,12 @@ static uint32_t get_role_index(const char* role) { 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; - pa_bool_t done, sink_mode; + bool done, sink_mode; pa_assert(u); pa_assert(prefix); - sink_mode = (strcmp(prefix, "sink:") == 0); + sink_mode = pa_streq(prefix, "sink:"); if (sink_mode) indexes = &u->preferred_sinks; @@ -446,15 +589,15 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha struct entry *e; name = pa_xstrndup(key.data, key.size); - device_name = get_name(name, prefix); + pa_assert_se(device_name = get_name(name, prefix)); - if ((e = read_entry(u, name)) && ENTRY_VERSION == e->version) { + 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; - pa_bool_t found = FALSE; + bool found = false; if (sink_mode) { pa_sink *sink; @@ -462,8 +605,8 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { if ((pa_sink*) ignore_device == sink) continue; - if (strcmp(sink->name, device_name) == 0) { - found = TRUE; + if (pa_streq(sink->name, device_name)) { + found = true; idx = sink->index; /* Is this needed? */ break; } @@ -474,8 +617,8 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha PA_IDXSET_FOREACH(source, u->core->sources, idx) { if ((pa_source*) ignore_device == source) continue; - if (strcmp(source->name, device_name) == 0) { - found = TRUE; + if (pa_streq(source->name, device_name)) { + found = true; idx = source->index; /* Is this needed? */ break; } @@ -489,7 +632,7 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha } } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -501,7 +644,6 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha } } - static void route_sink_input(struct userdata *u, pa_sink_input *si) { const char *role; uint32_t role_index, device_index; @@ -510,6 +652,9 @@ static void route_sink_input(struct userdata *u, pa_sink_input *si) { 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; @@ -536,7 +681,7 @@ static void route_sink_input(struct userdata *u, pa_sink_input *si) { return; if (si->sink != sink) - pa_sink_input_move_to(si, sink, TRUE); + pa_sink_input_move_to(si, sink, false); } static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) { @@ -565,6 +710,9 @@ static void route_source_output(struct userdata *u, pa_source_output *so) { pa_assert(u); pa_assert(u->do_routing); + if (so->save_source) + return; + if (so->direct_on_input) return; @@ -594,7 +742,7 @@ static void route_source_output(struct userdata *u, pa_source_output *so) { return; if (so->source != source) - pa_source_output_move_to(so, source, TRUE); + pa_source_output_move_to(so, source, false); } static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) { @@ -617,9 +765,8 @@ static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* igno 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); @@ -635,9 +782,6 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) return; - pa_zero(entry); - entry.version = ENTRY_VERSION; - if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { pa_sink_input *si; @@ -668,14 +812,26 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 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_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon)); + pa_xfree(entry->icon); + entry->icon = pa_xstrdup(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)); - } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { + } 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); @@ -686,41 +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_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon)); + 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) { @@ -733,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); @@ -757,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); @@ -784,22 +951,24 @@ 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("Overriding device for stream, even although it is already set. I am evil that way..."); - - if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) - role_index = get_role_index("none"); - else - role_index = get_role_index(role); + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); - if (PA_INVALID_INDEX != role_index) { - uint32_t device_index; + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; - device_index = u->preferred_sinks[role_index]; - if (PA_INVALID_INDEX != device_index) { - pa_sink *sink; + 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; + if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) { + 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"); + } } } } @@ -822,22 +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("Overriding device for stream, even although it is already set. I am evil that way..."); - - if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) - role_index = get_role_index("none"); - else - role_index = get_role_index(role); + pa_log_debug("Not restoring device for stream because already set."); + else { + if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) + role_index = get_role_index("none"); + else + role_index = get_role_index(role); - if (PA_INVALID_INDEX != role_index) { - uint32_t device_index; + if (PA_INVALID_INDEX != role_index) { + uint32_t device_index; - device_index = u->preferred_sources[role_index]; - if (PA_INVALID_INDEX != device_index) { - pa_source *source; + 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; + 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"); } } } @@ -845,7 +1015,6 @@ 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 sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) { pa_assert(c); pa_assert(u); @@ -900,10 +1069,7 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc 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; @@ -911,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) { @@ -973,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; @@ -990,35 +1160,35 @@ 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))) { + if ((e = entry_read(u, name))) { uint32_t idx; - char *devname; - pa_bool_t available = FALSE; + char *device_name; + uint32_t found_index = PA_INVALID_INDEX; - if ((devname = get_name(name, "sink:"))) { + if ((device_name = get_name(name, "sink:"))) { pa_sink* s; PA_IDXSET_FOREACH(s, u->core->sinks, idx) { - if (strcmp(s->name, devname) == 0) { - available = TRUE; + if (pa_streq(s->name, device_name)) { + found_index = s->index; break; } } - pa_xfree(devname); - } else if ((devname = get_name(name, "source:"))) { + pa_xfree(device_name); + } else if ((device_name = get_name(name, "source:"))) { pa_source* s; PA_IDXSET_FOREACH(s, u->core->sources, idx) { - if (strcmp(s->name, devname) == 0) { - available = TRUE; + if (pa_streq(s->name, device_name)) { + found_index = s->index; break; } } - pa_xfree(devname); + pa_xfree(device_name); } pa_tagstruct_puts(reply, name); pa_tagstruct_puts(reply, e->description); pa_tagstruct_puts(reply, e->icon); - pa_tagstruct_put_boolean(reply, available); + pa_tagstruct_putu32(reply, found_index); pa_tagstruct_putu32(reply, NUM_ROLES); for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) { @@ -1026,7 +1196,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_tagstruct_putu32(reply, e->priority[i]); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1049,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)); - - key.data = (char *) device; - key.size = strlen(device); + if ((e = entry_read(u, device))) { + pa_xfree(e->description); + e->description = pa_xstrdup(description); + e->user_set_description = true; - 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); @@ -1068,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); @@ -1098,7 +1262,7 @@ 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; @@ -1117,15 +1281,15 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio const char *role; struct entry *e; uint32_t role_index, n_devices; - pa_datum key, data; - pa_bool_t done, sink_mode = TRUE; + 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;*/ - pa_bool_t first; + bool first; if (pa_tagstruct_gets(t, &role) < 0 || pa_tagstruct_getu32(t, &n_devices) < 0 || @@ -1133,11 +1297,11 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio goto fail; if (PA_INVALID_INDEX == (role_index = get_role_index(role))) - goto fail; + 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; + first = true; idx = 0; for (i = 0; i < n_devices; ++i) { const char *s; @@ -1147,41 +1311,39 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_xfree(device); } - pa_hashmap_free(h, NULL, NULL); + pa_hashmap_free(h); pa_log_error("Protocol error on reorder"); goto fail; } /* Ensure this is a valid entry */ - if (!(e = read_entry(u, s))) { + if (!(e = entry_read(u, s))) { while ((device = pa_hashmap_steal_first(h))) { pa_xfree(device->device); pa_xfree(device); } - pa_hashmap_free(h, NULL, NULL); + pa_hashmap_free(h); pa_log_error("Client specified an unknown device in it's reorder list."); goto fail; } - pa_xfree(e); + entry_free(e); if (first) { - first = FALSE; + 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))) - { + } 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); } - pa_hashmap_free(h, NULL, NULL); + pa_hashmap_free(h); pa_log_error("Attempted to reorder mixed devices (sinks and sources)"); goto fail; } - /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */ + /* 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) { @@ -1199,7 +1361,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio }*/ /* Now cycle through our list and add all the devices. - This has the effect of addign in any in our DB, + 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; @@ -1215,10 +1377,10 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio 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 alredy in it, free it now and carry on */ + /* 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 = read_entry(u, device->device)) && ENTRY_VERSION == e->version) { - /* We add offset on to the existing priorirty so that when we order, the + && (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); @@ -1249,7 +1411,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio while ((device = pa_hashmap_steal_first(h))) { devices[idx++] = device; } - pa_hashmap_free(h, NULL, NULL); + pa_hashmap_free(h); /* Simple bubble sort */ for (i = 0; i < n_devices; ++i) { @@ -1270,22 +1432,16 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio /* Go through in order and write the new entry and cleanup our own list */ idx = 1; - first = TRUE; + first = true; for (i = 0; i < n_devices; ++i) { - if ((e = read_entry(u, devices[i]->device)) && ENTRY_VERSION == e->version) { + if ((e = entry_read(u, devices[i]->device))) { if (e->priority[role_index] == idx) idx++; else { e->priority[role_index] = idx; - key.data = (char *) devices[i]->device; - key.size = strlen(devices[i]->device); - - data.data = e; - data.size = sizeof(*e); - - if (pa_database_set(u->database, &key, &data, TRUE) == 0) { - first = FALSE; + if (entry_write(u, (char *) devices[i]->device, e)) { + first = false; idx++; } } @@ -1296,6 +1452,8 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_xfree(devices[i]); } + pa_xfree(devices); + if (!first) { trigger_save(u); @@ -1310,7 +1468,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio case SUBCOMMAND_SUBSCRIBE: { - pa_bool_t enabled; + bool enabled; if (pa_tagstruct_get_boolean(t, &enabled) < 0 || !pa_tagstruct_eof(t)) @@ -1348,6 +1506,11 @@ 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; @@ -1355,7 +1518,8 @@ int pa__init(pa_module*m) { pa_sink *sink; pa_source *source; 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); @@ -1391,40 +1555,87 @@ int pa__init(pa_module*m) { 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); - /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */ - 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) { @@ -1447,7 +1658,7 @@ fail: if (ma) pa_modargs_free(ma); - return -1; + return -1; } void pa__done(pa_module*m) { @@ -1481,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); @@ -1493,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); }