X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/d7ce59de88eda4eb638f977975374359254731f7..ef4ae785aa1d4d67b5df1c9414f6c1a144bc3460:/src/modules/module-stream-restore.c diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index df48dce2..38d6aac6 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -30,12 +30,11 @@ #include #include #include -#include +#include #include #include #include -#include #include #include @@ -51,6 +50,8 @@ #include #include #include +#include +#include #ifdef HAVE_DBUS #include @@ -62,23 +63,30 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Automatically restore the volume/mute/device state of streams"); PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( "restore_device= " "restore_volume= " "restore_muted= " "on_hotplug= " - "on_rescue="); + "on_rescue= " + "fallback_table="); #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC) #define IDENTIFICATION_PROPERTY "module-stream-restore.id" +#define DEFAULT_FALLBACK_FILE PA_DEFAULT_CONFIG_DIR"/stream-restore.table" +#define DEFAULT_FALLBACK_FILE_USER "stream-restore.table" + +#define WHITESPACE "\n\r \t" + static const char* const valid_modargs[] = { "restore_device", "restore_volume", "restore_muted", "on_hotplug", "on_rescue", + "fallback_table", NULL }; @@ -90,6 +98,7 @@ struct userdata { *sink_input_new_hook_slot, *sink_input_fixate_hook_slot, *source_output_new_hook_slot, + *source_output_fixate_hook_slot, *sink_put_hook_slot, *source_put_hook_slot, *sink_unlink_hook_slot, @@ -98,11 +107,11 @@ struct userdata { pa_time_event *save_time_event; pa_database* database; - pa_bool_t restore_device:1; - pa_bool_t restore_volume:1; - pa_bool_t restore_muted:1; - pa_bool_t on_hotplug:1; - pa_bool_t on_rescue:1; + bool restore_device:1; + bool restore_volume:1; + bool restore_muted:1; + bool on_hotplug:1; + bool on_rescue:1; pa_native_protocol *protocol; pa_idxset *subscribed; @@ -114,17 +123,17 @@ struct userdata { #endif }; -#define ENTRY_VERSION 3 +#define ENTRY_VERSION 1 struct entry { uint8_t version; - pa_bool_t muted_valid:1, volume_valid:1, device_valid:1, card_valid:1; - pa_bool_t muted:1; + bool muted_valid, volume_valid, device_valid, card_valid; + bool muted; pa_channel_map channel_map; pa_cvolume volume; - char device[PA_NAME_MAX]; - char card[PA_NAME_MAX]; -} PA_GCC_PACKED; + char* device; + char* card; +}; enum { SUBCOMMAND_TEST, @@ -135,8 +144,12 @@ enum { SUBCOMMAND_EVENT }; -static struct entry *read_entry(struct userdata *u, const char *name); -static void apply_entry(struct userdata *u, const char *name, struct entry *e); +static struct entry* entry_new(void); +static void entry_free(struct entry *e); +static struct entry *entry_read(struct userdata *u, const char *name); +static bool entry_write(struct userdata *u, const char *name, const struct entry *e, bool replace); +static struct entry* entry_copy(const struct entry *e); +static void entry_apply(struct userdata *u, const char *name, struct entry *e); static void trigger_save(struct userdata *u); #ifdef HAVE_DBUS @@ -216,11 +229,12 @@ enum entry_method_handler_index { ENTRY_METHOD_HANDLER_MAX }; -static pa_dbus_arg_info add_entry_args[] = { { "name", "s", "in" }, - { "device", "s", "in" }, - { "volume", "a(uu)", "in" }, - { "mute", "b", "in" }, - { "entry", "o", "out" } }; +static pa_dbus_arg_info add_entry_args[] = { { "name", "s", "in" }, + { "device", "s", "in" }, + { "volume", "a(uu)", "in" }, + { "mute", "b", "in" }, + { "apply_immediately", "b", "in" }, + { "entry", "o", "out" } }; static pa_dbus_arg_info get_entry_by_name_args[] = { { "name", "s", "in" }, { "entry", "o", "out" } }; static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { @@ -322,6 +336,7 @@ static void dbus_entry_free(struct dbus_entry *de) { pa_xfree(de->entry_name); pa_xfree(de->object_path); + pa_xfree(de); } /* Reads an array [(UInt32, UInt32)] from the iterator. The struct items are @@ -590,8 +605,6 @@ static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userd pa_cvolume vol; dbus_bool_t muted = FALSE; dbus_bool_t apply_immediately = FALSE; - pa_datum key; - pa_datum value; struct dbus_entry *dbus_entry = NULL; struct entry *e = NULL; @@ -620,14 +633,14 @@ static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userd } if ((dbus_entry = pa_hashmap_get(u->dbus_entries, name))) { - pa_bool_t mute_updated = FALSE; - pa_bool_t volume_updated = FALSE; - pa_bool_t device_updated = FALSE; + bool mute_updated = false; + bool volume_updated = false; + bool device_updated = false; - pa_assert_se(e = read_entry(u, name)); + pa_assert_se(e = entry_read(u, name)); mute_updated = e->muted != muted; e->muted = muted; - e->muted_valid = TRUE; + e->muted_valid = true; volume_updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol); e->volume = vol; @@ -635,7 +648,8 @@ static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userd e->volume_valid = !!map.channels; device_updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); - pa_strlcpy(e->device, device, sizeof(e->device)); + pa_xfree(e->device); + e->device = pa_xstrdup(device); e->device_valid = !!device[0]; if (mute_updated) @@ -649,33 +663,28 @@ static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userd dbus_entry = dbus_entry_new(u, name); pa_assert_se(pa_hashmap_put(u->dbus_entries, dbus_entry->entry_name, dbus_entry) == 0); - e = pa_xnew0(struct entry, 1); - e->muted_valid = TRUE; + e = entry_new(); + e->muted_valid = true; e->volume_valid = !!map.channels; e->device_valid = !!device[0]; e->muted = muted; e->volume = vol; e->channel_map = map; - pa_strlcpy(e->device, device, sizeof(e->device)); + e->device = pa_xstrdup(device); send_new_entry_signal(dbus_entry); } - key.data = (char *) name; - key.size = strlen(name); + pa_assert_se(entry_write(u, name, e, true)); - value.data = e; - value.size = sizeof(struct entry); - - pa_assert_se(pa_database_set(u->database, &key, &value, TRUE) == 0); if (apply_immediately) - apply_entry(u, name, e); + entry_apply(u, name, e); trigger_save(u); pa_dbus_send_empty_reply(conn, msg); - pa_xfree(e); + entry_free(e); } static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { @@ -726,20 +735,20 @@ static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void pa_assert(msg); pa_assert(de); - pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); device = e->device_valid ? e->device : ""; pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &device); - pa_xfree(e); + entry_free(e); } static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { struct dbus_entry *de = userdata; const char *device; struct entry *e; - pa_bool_t updated; + bool updated; pa_assert(conn); pa_assert(msg); @@ -748,31 +757,25 @@ static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBus dbus_message_iter_get_basic(iter, &device); - pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device); if (updated) { - pa_datum key; - pa_datum value; - - pa_strlcpy(e->device, device, sizeof(e->device)); + pa_xfree(e->device); + e->device = pa_xstrdup(device); e->device_valid = !!device[0]; - key.data = de->entry_name; - key.size = strlen(de->entry_name); - value.data = e; - value.size = sizeof(struct entry); - pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0); + pa_assert_se(entry_write(de->userdata, de->entry_name, e, true)); - apply_entry(de->userdata, de->entry_name, e); + entry_apply(de->userdata, de->entry_name, e); send_device_updated_signal(de, e); trigger_save(de->userdata); } pa_dbus_send_empty_reply(conn, msg); - pa_xfree(e); + entry_free(e); } static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { @@ -785,7 +788,7 @@ static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void pa_assert(msg); pa_assert(de); - pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); pa_assert_se(reply = dbus_message_new_method_return(msg)); @@ -794,7 +797,7 @@ static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void pa_assert_se(dbus_connection_send(conn, reply, NULL)); - pa_xfree(e); + entry_free(e); } static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { @@ -802,7 +805,7 @@ static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBus pa_channel_map map; pa_cvolume vol; struct entry *e = NULL; - pa_bool_t updated = FALSE; + bool updated = false; pa_assert(conn); pa_assert(msg); @@ -812,32 +815,25 @@ static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBus if (get_volume_arg(conn, msg, iter, &map, &vol) < 0) return; - pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol); if (updated) { - pa_datum key; - pa_datum value; - e->volume = vol; e->channel_map = map; e->volume_valid = !!map.channels; - key.data = de->entry_name; - key.size = strlen(de->entry_name); - value.data = e; - value.size = sizeof(struct entry); - pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0); + pa_assert_se(entry_write(de->userdata, de->entry_name, e, true)); - apply_entry(de->userdata, de->entry_name, e); + entry_apply(de->userdata, de->entry_name, e); send_volume_updated_signal(de, e); trigger_save(de->userdata); } pa_dbus_send_empty_reply(conn, msg); - pa_xfree(e); + entry_free(e); } static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { @@ -849,20 +845,20 @@ static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void * pa_assert(msg); pa_assert(de); - pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); mute = e->muted_valid ? e->muted : FALSE; pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &mute); - pa_xfree(e); + entry_free(e); } static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { struct dbus_entry *de = userdata; dbus_bool_t mute; struct entry *e; - pa_bool_t updated; + bool updated; pa_assert(conn); pa_assert(msg); @@ -871,31 +867,24 @@ static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMe dbus_message_iter_get_basic(iter, &mute); - pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); updated = !e->muted_valid || e->muted != mute; if (updated) { - pa_datum key; - pa_datum value; - e->muted = mute; - e->muted_valid = TRUE; + e->muted_valid = true; - key.data = de->entry_name; - key.size = strlen(de->entry_name); - value.data = e; - value.size = sizeof(struct entry); - pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0); + pa_assert_se(entry_write(de->userdata, de->entry_name, e, true)); - apply_entry(de->userdata, de->entry_name, e); + entry_apply(de->userdata, de->entry_name, e); send_mute_updated_signal(de, e); trigger_save(de->userdata); } pa_dbus_send_empty_reply(conn, msg); - pa_xfree(e); + entry_free(e); } static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { @@ -912,7 +901,7 @@ static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *u pa_assert(msg); pa_assert(de); - pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + pa_assert_se(e = entry_read(de->userdata, de->entry_name)); device = e->device_valid ? e->device : ""; mute = e->muted_valid ? e->muted : FALSE; @@ -941,7 +930,7 @@ static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *u dbus_message_unref(reply); - pa_xfree(e); + entry_free(e); } static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) { @@ -960,8 +949,7 @@ static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *us send_entry_removed_signal(de); trigger_save(de->userdata); - pa_assert_se(pa_hashmap_remove(de->userdata->dbus_entries, de->entry_name)); - dbus_entry_free(de); + pa_assert_se(pa_hashmap_remove_and_free(de->userdata->dbus_entries, de->entry_name) >= 0); pa_dbus_send_empty_reply(conn, msg); } @@ -983,39 +971,76 @@ static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct pa_log_info("Synced."); } -static char *get_name(pa_proplist *p, const char *prefix) { - const char *r; - char *t; - - if (!p) - return NULL; +static struct entry* entry_new(void) { + struct entry *r = pa_xnew0(struct entry, 1); + r->version = ENTRY_VERSION; + return r; +} - if ((r = pa_proplist_gets(p, IDENTIFICATION_PROPERTY))) - return pa_xstrdup(r); - - if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_ROLE))) - t = pa_sprintf_malloc("%s-by-media-role:%s", prefix, r); - else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_ID))) - t = pa_sprintf_malloc("%s-by-application-id:%s", prefix, r); - else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_NAME))) - t = pa_sprintf_malloc("%s-by-application-name:%s", prefix, r); - else if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_NAME))) - t = pa_sprintf_malloc("%s-by-media-name:%s", prefix, r); - else - t = pa_sprintf_malloc("%s-fallback:%s", prefix, r); +static void entry_free(struct entry* e) { + pa_assert(e); - pa_proplist_sets(p, IDENTIFICATION_PROPERTY, t); - return t; + pa_xfree(e->device); + pa_xfree(e->card); + pa_xfree(e); } -static struct entry *read_entry(struct userdata *u, const char *name) { +static bool entry_write(struct userdata *u, const char *name, const struct entry *e, bool replace) { + 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_put_boolean(t, e->volume_valid); + pa_tagstruct_put_channel_map(t, &e->channel_map); + pa_tagstruct_put_cvolume(t, &e->volume); + pa_tagstruct_put_boolean(t, e->muted_valid); + pa_tagstruct_put_boolean(t, e->muted); + pa_tagstruct_put_boolean(t, e->device_valid); + pa_tagstruct_puts(t, e->device); + pa_tagstruct_put_boolean(t, e->card_valid); + pa_tagstruct_puts(t, e->card); + + 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, replace) == 0); + + pa_tagstruct_free(t); + + return r; +} + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + +#define LEGACY_ENTRY_VERSION 3 +static struct entry *legacy_entry_read(struct userdata *u, const char *name) { + struct legacy_entry { + uint8_t version; + bool muted_valid:1, volume_valid:1, device_valid:1, card_valid:1; + bool muted:1; + pa_channel_map channel_map; + pa_cvolume volume; + char device[PA_NAME_MAX]; + char card[PA_NAME_MAX]; + } PA_GCC_PACKED; + + pa_datum key; + pa_datum data; + struct legacy_entry *le; struct entry *e; pa_assert(u); pa_assert(name); - key.data = (char*) name; + key.data = (char *) name; key.size = strlen(name); pa_zero(data); @@ -1023,30 +1048,108 @@ 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)) { - /* This is probably just a database upgrade, hence let's not - * consider this more than a debug message */ - pa_log_debug("Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry)); + if (data.size != sizeof(struct legacy_entry)) { + pa_log_debug("Size does not match."); + goto fail; + } + + le = (struct legacy_entry *) data.data; + + if (le->version != LEGACY_ENTRY_VERSION) { + pa_log_debug("Version mismatch."); + goto fail; + } + + if (!memchr(le->device, 0, sizeof(le->device))) { + pa_log_warn("Device has missing NUL byte."); + goto fail; + } + + if (!memchr(le->card, 0, sizeof(le->card))) { + pa_log_warn("Card has missing NUL byte."); goto fail; } - e = (struct entry*) data.data; + if (le->device_valid && !pa_namereg_is_valid_name(le->device)) { + pa_log_warn("Invalid device name stored in database for legacy stream"); + goto fail; + } - if (e->version != ENTRY_VERSION) { - pa_log_debug("Version of database entry for stream %s doesn't match our version. Probably due to upgrade, ignoring.", name); + if (le->card_valid && !pa_namereg_is_valid_name(le->card)) { + pa_log_warn("Invalid card name stored in database for legacy stream"); goto fail; } - if (!memchr(e->device, 0, sizeof(e->device))) { - pa_log_warn("Database contains entry for stream %s with missing NUL byte in device name", name); + if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) { + pa_log_warn("Invalid channel map stored in database for legacy stream"); goto fail; } - if (!memchr(e->card, 0, sizeof(e->card))) { - pa_log_warn("Database contains entry for stream %s with missing NUL byte in card name", name); + if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) { + pa_log_warn("Invalid volume stored in database for legacy stream"); goto fail; } + e = entry_new(); + e->muted_valid = le->muted_valid; + e->muted = le->muted; + e->volume_valid = le->volume_valid; + e->channel_map = le->channel_map; + e->volume = le->volume; + e->device_valid = le->device_valid; + e->device = pa_xstrdup(le->device); + e->card_valid = le->card_valid; + e->card = pa_xstrdup(le->card); + return e; + +fail: + pa_datum_free(&data); + + return NULL; +} +#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 *device, *card; + + pa_assert(u); + pa_assert(name); + + key.data = (char*) name; + key.size = strlen(name); + + pa_zero(data); + + if (!pa_database_get(u->database, &key, &data)) + goto fail; + + 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_get_boolean(t, &e->volume_valid) < 0 || + pa_tagstruct_get_channel_map(t, &e->channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &e->volume) < 0 || + pa_tagstruct_get_boolean(t, &e->muted_valid) < 0 || + pa_tagstruct_get_boolean(t, &e->muted) < 0 || + pa_tagstruct_get_boolean(t, &e->device_valid) < 0 || + pa_tagstruct_gets(t, &device) < 0 || + pa_tagstruct_get_boolean(t, &e->card_valid) < 0 || + pa_tagstruct_gets(t, &card) < 0) { + + goto fail; + } + + e->device = pa_xstrdup(device); + e->card = pa_xstrdup(card); + + if (!pa_tagstruct_eof(t)) + goto fail; + if (e->device_valid && !pa_namereg_is_valid_name(e->device)) { pa_log_warn("Invalid device name stored in database for stream %s", name); goto fail; @@ -1067,19 +1170,37 @@ static struct entry *read_entry(struct userdata *u, const char *name) { goto fail; } + pa_tagstruct_free(t); + pa_datum_free(&data); + return e; fail: + if (e) + entry_free(e); + if (t) + pa_tagstruct_free(t); pa_datum_free(&data); return NULL; } +static struct entry* entry_copy(const struct entry *e) { + struct entry* r; + + pa_assert(e); + r = entry_new(); + *r = *e; + r->device = pa_xstrdup(e->device); + r->card = pa_xstrdup(e->card); + return r; +} + static void trigger_save(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_IDXSET_FOREACH(c, u->subscribed, idx) { pa_tagstruct *t; t = pa_tagstruct_new(NULL, 0); @@ -1098,44 +1219,43 @@ static void trigger_save(struct userdata *u) { 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_cvolume t; pa_assert(a); pa_assert(b); if (a->device_valid != b->device_valid || - (a->device_valid && strncmp(a->device, b->device, sizeof(a->device)))) - return FALSE; + (a->device_valid && !pa_streq(a->device, b->device))) + return false; if (a->card_valid != b->card_valid || - (a->card_valid && strncmp(a->card, b->card, sizeof(a->card)))) - return FALSE; + (a->card_valid && !pa_streq(a->card, b->card))) + return false; if (a->muted_valid != b->muted_valid || (a->muted_valid && (a->muted != b->muted))) - return FALSE; + return false; t = b->volume; if (a->volume_valid != b->volume_valid || (a->volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->volume))) - return FALSE; + return false; - return TRUE; + return true; } 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; /* These are only used when D-Bus is enabled, but in order to reduce ifdef * clutter these are defined here unconditionally. */ - pa_bool_t created_new_entry = TRUE; - pa_bool_t device_updated = FALSE; - pa_bool_t volume_updated = FALSE; - pa_bool_t mute_updated = FALSE; + bool created_new_entry = true; + bool device_updated = false; + bool volume_updated = false; + bool mute_updated = false; #ifdef HAVE_DBUS struct dbus_entry *de = NULL; @@ -1150,51 +1270,51 @@ 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 *sink_input; if (!(sink_input = pa_idxset_get_by_index(c->sink_inputs, idx))) return; - if (!(name = get_name(sink_input->proplist, "sink-input"))) + if (!(name = pa_proplist_get_stream_group(sink_input->proplist, "sink-input", IDENTIFICATION_PROPERTY))) return; - if ((old = read_entry(u, name))) { - entry = *old; - created_new_entry = FALSE; - } + if ((old = entry_read(u, name))) { + entry = entry_copy(old); + created_new_entry = false; + } else + entry = entry_new(); - if (sink_input->save_volume) { - pa_assert(pa_sink_input_is_volume_writable(sink_input)); + if (sink_input->save_volume && pa_sink_input_is_volume_readable(sink_input)) { + pa_assert(sink_input->volume_writable); - entry.channel_map = sink_input->channel_map; - pa_sink_input_get_volume(sink_input, &entry.volume, FALSE); - entry.volume_valid = TRUE; + entry->channel_map = sink_input->channel_map; + pa_sink_input_get_volume(sink_input, &entry->volume, false); + entry->volume_valid = true; volume_updated = !created_new_entry && (!old->volume_valid - || !pa_channel_map_equal(&entry.channel_map, &old->channel_map) - || !pa_cvolume_equal(&entry.volume, &old->volume)); + || !pa_channel_map_equal(&entry->channel_map, &old->channel_map) + || !pa_cvolume_equal(&entry->volume, &old->volume)); } if (sink_input->save_muted) { - entry.muted = pa_sink_input_get_mute(sink_input); - entry.muted_valid = TRUE; + entry->muted = sink_input->muted; + entry->muted_valid = true; - mute_updated = !created_new_entry && (!old->muted_valid || entry.muted != old->muted); + mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted); } if (sink_input->save_sink) { - pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device)); - entry.device_valid = TRUE; + pa_xfree(entry->device); + entry->device = pa_xstrdup(sink_input->sink->name); + entry->device_valid = true; - device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device)); + device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry->device, old->device)); if (sink_input->sink->card) { - pa_strlcpy(entry.card, sink_input->sink->card->name, sizeof(entry.card)); - entry.card_valid = TRUE; + pa_xfree(entry->card); + entry->card = pa_xstrdup(sink_input->sink->card->name); + entry->card_valid = true; } } @@ -1206,47 +1326,68 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!(source_output = pa_idxset_get_by_index(c->source_outputs, idx))) return; - if (!(name = get_name(source_output->proplist, "source-output"))) + if (!(name = pa_proplist_get_stream_group(source_output->proplist, "source-output", IDENTIFICATION_PROPERTY))) return; - if ((old = read_entry(u, name))) { - entry = *old; - created_new_entry = FALSE; + if ((old = entry_read(u, name))) { + entry = entry_copy(old); + created_new_entry = false; + } else + entry = entry_new(); + + if (source_output->save_volume && pa_source_output_is_volume_readable(source_output)) { + pa_assert(source_output->volume_writable); + + entry->channel_map = source_output->channel_map; + pa_source_output_get_volume(source_output, &entry->volume, false); + entry->volume_valid = true; + + volume_updated = !created_new_entry + && (!old->volume_valid + || !pa_channel_map_equal(&entry->channel_map, &old->channel_map) + || !pa_cvolume_equal(&entry->volume, &old->volume)); + } + + if (source_output->save_muted) { + entry->muted = source_output->muted; + entry->muted_valid = true; + + mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted); } if (source_output->save_source) { - pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device)); - entry.device_valid = source_output->save_source; + pa_xfree(entry->device); + entry->device = pa_xstrdup(source_output->source->name); + entry->device_valid = true; - device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device)); + device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry->device, old->device)); if (source_output->source->card) { - pa_strlcpy(entry.card, source_output->source->card->name, sizeof(entry.card)); - entry.card_valid = TRUE; + pa_xfree(entry->card); + entry->card = pa_xstrdup(source_output->source->card->name); + entry->card_valid = true; } } } + pa_assert(entry); + 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 volume/mute/device for stream %s.", name); - pa_database_set(u->database, &key, &data, TRUE); + if (entry_write(u, name, entry, true)) + trigger_save(u); #ifdef HAVE_DBUS if (created_new_entry) { @@ -1257,17 +1398,16 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_assert_se(de = pa_hashmap_get(u->dbus_entries, name)); if (device_updated) - send_device_updated_signal(de, &entry); + send_device_updated_signal(de, entry); if (volume_updated) - send_volume_updated_signal(de, &entry); + send_volume_updated_signal(de, entry); if (mute_updated) - send_mute_updated_signal(de, &entry); + send_mute_updated_signal(de, entry); } #endif + entry_free(entry); pa_xfree(name); - - trigger_save(u); } static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { @@ -1279,12 +1419,12 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n pa_assert(u); pa_assert(u->restore_device); - if (!(name = get_name(new_data->proplist, "sink-input"))) + if (!(name = pa_proplist_get_stream_group(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY))) return PA_HOOK_OK; if (new_data->sink) pa_log_debug("Not restoring device for stream %s, because already set to '%s'.", name, new_data->sink->name); - else if ((e = read_entry(u, name))) { + else if ((e = entry_read(u, name))) { pa_sink *s = NULL; if (e->device_valid) @@ -1300,13 +1440,11 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n /* 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 (s && PA_SINK_IS_LINKED(pa_sink_get_state(s))) { - pa_log_info("Restoring device for stream %s.", name); - new_data->sink = s; - new_data->save_sink = TRUE; - } + if (s && PA_SINK_IS_LINKED(pa_sink_get_state(s))) + if (pa_sink_input_new_data_set_sink(new_data, s, true)) + pa_log_info("Restoring device for stream %s.", name); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1323,13 +1461,13 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu pa_assert(u); pa_assert(u->restore_volume || u->restore_muted); - if (!(name = get_name(new_data->proplist, "sink-input"))) + if (!(name = pa_proplist_get_stream_group(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY))) return PA_HOOK_OK; - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { if (u->restore_volume && e->volume_valid) { - if (!pa_sink_input_new_data_is_volume_writable(new_data)) + if (!new_data->volume_writable) pa_log_debug("Not restoring volume for sink input %s, because its volume can't be changed.", name); else if (new_data->volume_is_set) pa_log_debug("Not restoring volume for sink input %s, because already set.", name); @@ -1342,8 +1480,8 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); pa_sink_input_new_data_set_volume(new_data, &v); - new_data->volume_is_absolute = FALSE; - new_data->save_volume = TRUE; + new_data->volume_is_absolute = false; + new_data->save_volume = true; } } @@ -1352,12 +1490,12 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu if (!new_data->muted_is_set) { pa_log_info("Restoring mute state for sink input %s.", name); pa_sink_input_new_data_set_muted(new_data, e->muted); - new_data->save_muted = TRUE; + new_data->save_muted = true; } else pa_log_debug("Not restoring mute state for sink input %s, because already set.", name); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1377,12 +1515,12 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou if (new_data->direct_on_input) return PA_HOOK_OK; - if (!(name = get_name(new_data->proplist, "source-output"))) + if (!(name = pa_proplist_get_stream_group(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY))) return PA_HOOK_OK; if (new_data->source) pa_log_debug("Not restoring device for stream %s, because already set", name); - else if ((e = read_entry(u, name))) { + else if ((e = entry_read(u, name))) { pa_source *s = NULL; if (e->device_valid) @@ -1400,11 +1538,61 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou interfere with that */ if (s && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) { pa_log_info("Restoring device for stream %s.", name); - new_data->source = s; - new_data->save_source = TRUE; + pa_source_output_new_data_set_source(new_data, s, true); } - pa_xfree(e); + entry_free(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_fixate_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + pa_assert(u->restore_volume || u->restore_muted); + + if (!(name = pa_proplist_get_stream_group(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY))) + return PA_HOOK_OK; + + if ((e = entry_read(u, name))) { + + if (u->restore_volume && e->volume_valid) { + if (!new_data->volume_writable) + pa_log_debug("Not restoring volume for source output %s, because its volume can't be changed.", name); + else if (new_data->volume_is_set) + pa_log_debug("Not restoring volume for source output %s, because already set.", name); + else { + pa_cvolume v; + + pa_log_info("Restoring volume for source output %s.", name); + + v = e->volume; + pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map); + pa_source_output_new_data_set_volume(new_data, &v); + + new_data->volume_is_absolute = false; + new_data->save_volume = true; + } + } + + if (u->restore_muted && e->muted_valid) { + + if (!new_data->muted_is_set) { + pa_log_info("Restoring mute state for source output %s.", name); + pa_source_output_new_data_set_muted(new_data, e->muted); + new_data->save_muted = true; + } else + pa_log_debug("Not restoring mute state for source output %s, because already set.", name); + } + + entry_free(e); } pa_xfree(name); @@ -1442,14 +1630,14 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) continue; - if (!(name = get_name(si->proplist, "sink-input"))) + if (!(name = pa_proplist_get_stream_group(si->proplist, "sink-input", IDENTIFICATION_PROPERTY))) continue; - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { if (e->device_valid && pa_streq(e->device, sink->name)) - pa_sink_input_move_to(si, sink, TRUE); + pa_sink_input_move_to(si, sink, true); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1490,14 +1678,14 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(so))) continue; - if (!(name = get_name(so->proplist, "source-output"))) + if (!(name = pa_proplist_get_stream_group(so->proplist, "source-output", IDENTIFICATION_PROPERTY))) continue; - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { if (e->device_valid && pa_streq(e->device, source->name)) - pa_source_output_move_to(so, source, TRUE); + pa_source_output_move_to(so, source, true); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1526,10 +1714,10 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str if (!si->sink) continue; - if (!(name = get_name(si->proplist, "sink-input"))) + if (!(name = pa_proplist_get_stream_group(si->proplist, "sink-input", IDENTIFICATION_PROPERTY))) continue; - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { if (e->device_valid) { pa_sink *d; @@ -1537,10 +1725,10 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SINK)) && d != sink && PA_SINK_IS_LINKED(pa_sink_get_state(d))) - pa_sink_input_move_to(si, d, TRUE); + pa_sink_input_move_to(si, d, true); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1572,10 +1760,10 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc if (!so->source) continue; - if (!(name = get_name(so->proplist, "source-output"))) + if (!(name = pa_proplist_get_stream_group(so->proplist, "source-output", IDENTIFICATION_PROPERTY))) continue; - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { if (e->device_valid) { pa_source *d; @@ -1583,10 +1771,10 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc if ((d = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE)) && d != source && PA_SOURCE_IS_LINKED(pa_source_get_state(d))) - pa_source_output_move_to(so, d, TRUE); + pa_source_output_move_to(so, d, true); } - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1595,9 +1783,90 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc return PA_HOOK_OK; } -#define EXT_VERSION 1 +static int fill_db(struct userdata *u, const char *filename) { + FILE *f; + int n = 0; + int ret = -1; + char *fn = NULL; + + pa_assert(u); + + if (filename) + f = fopen(fn = pa_xstrdup(filename), "r"); + else + f = pa_open_config_file(DEFAULT_FALLBACK_FILE, DEFAULT_FALLBACK_FILE_USER, NULL, &fn); + + if (!f) { + if (filename) + pa_log("Failed to open %s: %s", filename, pa_cstrerror(errno)); + else + ret = 0; + + goto finish; + } + + while (!feof(f)) { + char ln[256]; + char *d, *v; + double db; + + if (!fgets(ln, sizeof(ln), f)) + break; -static void apply_entry(struct userdata *u, const char *name, struct entry *e) { + n++; + + pa_strip_nl(ln); + + if (!*ln || ln[0] == '#' || ln[0] == ';') + continue; + + d = ln+strcspn(ln, WHITESPACE); + v = d+strspn(d, WHITESPACE); + + if (!*v) { + pa_log("[%s:%u] failed to parse line - too few words", fn, n); + goto finish; + } + + *d = 0; + if (pa_atod(v, &db) >= 0) { + if (db <= 0.0) { + pa_datum key, data; + struct entry e; + + pa_zero(e); + e.version = ENTRY_VERSION; + e.volume_valid = true; + pa_cvolume_set(&e.volume, 1, pa_sw_volume_from_dB(db)); + pa_channel_map_init_mono(&e.channel_map); + + key.data = (void *) ln; + key.size = strlen(ln); + + data.data = (void *) &e; + data.size = sizeof(e); + + if (pa_database_set(u->database, &key, &data, false) == 0) + pa_log_debug("Setting %s to %0.2f dB.", ln, db); + } else + pa_log_warn("[%s:%u] Positive dB values are not allowed, not setting entry %s.", fn, n, ln); + } else + pa_log_warn("[%s:%u] Couldn't parse '%s' as a double, not setting entry %s.", fn, n, v, ln); + } + + trigger_save(u); + ret = 0; + +finish: + if (f) + fclose(f); + + pa_xfree(fn); + + return ret; +} + +static void entry_apply(struct userdata *u, const char *name, struct entry *e) { pa_sink_input *si; pa_source_output *so; uint32_t idx; @@ -1610,7 +1879,7 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { char *n; pa_sink *s; - if (!(n = get_name(si->proplist, "sink-input"))) + if (!(n = pa_proplist_get_stream_group(si->proplist, "sink-input", IDENTIFICATION_PROPERTY))) continue; if (!pa_streq(name, n)) { @@ -1619,18 +1888,18 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { } pa_xfree(n); - if (u->restore_volume && e->volume_valid && pa_sink_input_is_volume_writable(si)) { + if (u->restore_volume && e->volume_valid && si->volume_writable) { pa_cvolume v; v = e->volume; pa_log_info("Restoring volume for sink input %s.", name); pa_cvolume_remap(&v, &e->channel_map, &si->channel_map); - pa_sink_input_set_volume(si, &v, TRUE, FALSE); + pa_sink_input_set_volume(si, &v, true, false); } if (u->restore_muted && e->muted_valid) { pa_log_info("Restoring mute state for sink input %s.", name); - pa_sink_input_set_mute(si, e->muted, TRUE); + pa_sink_input_set_mute(si, e->muted, true); } if (u->restore_device) { @@ -1640,16 +1909,16 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { /* If the device is not valid we should make sure the save flag is cleared as the user may have specifically removed the sink element from the rule. */ - si->save_sink = FALSE; + si->save_sink = false; /* This is cheating a bit. The sink input itself has not changed - but the rules governing it's routing have, so we fire this event + but the rules governing its routing have, so we fire this event such that other routing modules (e.g. module-device-manager) will pick up the change and reapply their routing */ pa_subscription_post(si->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, si->index); } } else if ((s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SINK))) { pa_log_info("Restoring device for stream %s.", name); - pa_sink_input_move_to(si, s, TRUE); + pa_sink_input_move_to(si, s, true); } } } @@ -1658,7 +1927,7 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { char *n; pa_source *s; - if (!(n = get_name(so->proplist, "source-output"))) + if (!(n = pa_proplist_get_stream_group(so->proplist, "source-output", IDENTIFICATION_PROPERTY))) continue; if (!pa_streq(name, n)) { @@ -1667,6 +1936,20 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { } pa_xfree(n); + if (u->restore_volume && e->volume_valid && so->volume_writable) { + pa_cvolume v; + + v = e->volume; + pa_log_info("Restoring volume for source output %s.", name); + pa_cvolume_remap(&v, &e->channel_map, &so->channel_map); + pa_source_output_set_volume(so, &v, true, false); + } + + if (u->restore_muted && e->muted_valid) { + pa_log_info("Restoring mute state for source output %s.", name); + pa_source_output_set_mute(so, e->muted, true); + } + if (u->restore_device) { if (!e->device_valid) { if (so->save_source) { @@ -1674,25 +1957,25 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { /* If the device is not valid we should make sure the save flag is cleared as the user may have specifically removed the source element from the rule. */ - so->save_source = FALSE; + so->save_source = false; /* This is cheating a bit. The source output itself has not changed - but the rules governing it's routing have, so we fire this event + but the rules governing its routing have, so we fire this event such that other routing modules (e.g. module-device-manager) will pick up the change and reapply their routing */ pa_subscription_post(so->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, so->index); } } else if ((s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SOURCE))) { pa_log_info("Restoring device for stream %s.", name); - pa_source_output_move_to(so, s, TRUE); + pa_source_output_move_to(so, s, true); } } } } -#if 0 -static void dump_database(struct userdata *u) { +#ifdef DEBUG_VOLUME +PA_GCC_UNUSED static void stream_restore_dump_database(struct userdata *u) { pa_datum key; - pa_bool_t done; + bool done; done = !pa_database_first(u->database, &key, NULL); @@ -1706,14 +1989,16 @@ static void dump_database(struct userdata *u) { name = pa_xstrndup(key.data, key.size); pa_datum_free(&key); - if ((e = read_entry(u, name))) { + if ((e = entry_read(u, name))) { char t[256]; pa_log("name=%s", name); pa_log("device=%s %s", e->device, pa_yes_no(e->device_valid)); pa_log("channel_map=%s", pa_channel_map_snprint(t, sizeof(t), &e->channel_map)); - pa_log("volume=%s %s", pa_cvolume_snprint(t, sizeof(t), &e->volume), pa_yes_no(e->volume_valid)); + pa_log("volume=%s %s", + pa_cvolume_snprint_verbose(t, sizeof(t), &e->volume, &e->channel_map, true), + pa_yes_no(e->volume_valid)); pa_log("mute=%s %s", pa_yes_no(e->muted), pa_yes_no(e->volume_valid)); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1723,6 +2008,8 @@ static void dump_database(struct userdata *u) { } #endif +#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) { struct userdata *u; uint32_t command; @@ -1753,7 +2040,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; @@ -1770,7 +2057,7 @@ 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))) { pa_cvolume r; pa_channel_map cm; @@ -1778,9 +2065,9 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_tagstruct_put_channel_map(reply, e->volume_valid ? &e->channel_map : pa_channel_map_init(&cm)); pa_tagstruct_put_cvolume(reply, e->volume_valid ? &e->volume : pa_cvolume_init(&r)); pa_tagstruct_puts(reply, e->device_valid ? e->device : NULL); - pa_tagstruct_put_boolean(reply, e->muted_valid ? e->muted : FALSE); + pa_tagstruct_put_boolean(reply, e->muted_valid ? e->muted : false); - pa_xfree(e); + entry_free(e); } pa_xfree(name); @@ -1793,7 +2080,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio case SUBCOMMAND_WRITE: { uint32_t mode; - pa_bool_t apply_immediately = FALSE; + bool apply_immediately = false; if (pa_tagstruct_getu32(t, &mode) < 0 || pa_tagstruct_get_boolean(t, &apply_immediately) < 0) @@ -1811,7 +2098,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio PA_HASHMAP_FOREACH(de, u->dbus_entries, state) { send_entry_removed_signal(de); - dbus_entry_free(pa_hashmap_remove(u->dbus_entries, de->entry_name)); + pa_hashmap_remove_and_free(u->dbus_entries, de->entry_name); } #endif pa_database_clear(u->database); @@ -1819,75 +2106,73 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio while (!pa_tagstruct_eof(t)) { const char *name, *device; - pa_bool_t muted; - struct entry entry; - pa_datum key, data; + bool muted; + struct entry *entry; #ifdef HAVE_DBUS struct entry *old; #endif - pa_zero(entry); - entry.version = ENTRY_VERSION; + entry = entry_new(); if (pa_tagstruct_gets(t, &name) < 0 || - pa_tagstruct_get_channel_map(t, &entry.channel_map) || - pa_tagstruct_get_cvolume(t, &entry.volume) < 0 || + pa_tagstruct_get_channel_map(t, &entry->channel_map) || + pa_tagstruct_get_cvolume(t, &entry->volume) < 0 || pa_tagstruct_gets(t, &device) < 0 || - pa_tagstruct_get_boolean(t, &muted) < 0) + pa_tagstruct_get_boolean(t, &muted) < 0) { + entry_free(entry); goto fail; + } - if (!name || !*name) + if (!name || !*name) { + entry_free(entry); goto fail; + } - entry.volume_valid = entry.volume.channels > 0; + entry->volume_valid = entry->volume.channels > 0; - if (entry.volume_valid) - if (!pa_cvolume_compatible_with_channel_map(&entry.volume, &entry.channel_map)) + if (entry->volume_valid) + if (!pa_cvolume_compatible_with_channel_map(&entry->volume, &entry->channel_map)) { + entry_free(entry); goto fail; + } - entry.muted = muted; - entry.muted_valid = TRUE; + entry->muted = muted; + entry->muted_valid = true; - if (device) - pa_strlcpy(entry.device, device, sizeof(entry.device)); - entry.device_valid = !!entry.device[0]; + entry->device = pa_xstrdup(device); + entry->device_valid = device && !!entry->device[0]; - if (entry.device_valid && - !pa_namereg_is_valid_name(entry.device)) + if (entry->device_valid && !pa_namereg_is_valid_name(entry->device)) { + entry_free(entry); goto fail; + } #ifdef HAVE_DBUS - old = read_entry(u, name); + old = entry_read(u, name); #endif - key.data = (char*) name; - key.size = strlen(name); - - data.data = &entry; - data.size = sizeof(entry); - pa_log_debug("Client %s changes entry %s.", pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)), name); - if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) { + if (entry_write(u, name, entry, mode == PA_UPDATE_REPLACE)) { #ifdef HAVE_DBUS struct dbus_entry *de; if (old) { pa_assert_se((de = pa_hashmap_get(u->dbus_entries, name))); - if ((old->device_valid != entry.device_valid) - || (entry.device_valid && !pa_streq(entry.device, old->device))) - send_device_updated_signal(de, &entry); + if ((old->device_valid != entry->device_valid) + || (entry->device_valid && !pa_streq(entry->device, old->device))) + send_device_updated_signal(de, entry); - if ((old->volume_valid != entry.volume_valid) - || (entry.volume_valid && (!pa_cvolume_equal(&entry.volume, &old->volume) - || !pa_channel_map_equal(&entry.channel_map, &old->channel_map)))) - send_volume_updated_signal(de, &entry); + if ((old->volume_valid != entry->volume_valid) + || (entry->volume_valid && (!pa_cvolume_equal(&entry->volume, &old->volume) + || !pa_channel_map_equal(&entry->channel_map, &old->channel_map)))) + send_volume_updated_signal(de, entry); - if (!old->muted_valid || (entry.muted != old->muted)) - send_mute_updated_signal(de, &entry); + if (!old->muted_valid || (entry->muted != old->muted)) + send_mute_updated_signal(de, entry); } else { de = dbus_entry_new(u, name); @@ -1897,13 +2182,14 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio #endif if (apply_immediately) - apply_entry(u, name, &entry); + entry_apply(u, name, entry); } #ifdef HAVE_DBUS if (old) - pa_xfree(old); + entry_free(old); #endif + entry_free(entry); } trigger_save(u); @@ -1926,7 +2212,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio #ifdef HAVE_DBUS if ((de = pa_hashmap_get(u->dbus_entries, name))) { send_entry_removed_signal(de); - dbus_entry_free(pa_hashmap_remove(u->dbus_entries, name)); + pa_hashmap_remove_and_free(u->dbus_entries, name); } #endif @@ -1942,7 +2228,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)) @@ -1980,6 +2266,101 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati return PA_HOOK_OK; } +static void clean_up_db(struct userdata *u) { + struct clean_up_item { + PA_LLIST_FIELDS(struct clean_up_item); + char *entry_name; + struct entry *entry; + }; + + PA_LLIST_HEAD(struct clean_up_item, to_be_removed); +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + PA_LLIST_HEAD(struct clean_up_item, to_be_converted); +#endif + bool done = false; + pa_datum key; + struct clean_up_item *item = NULL; + struct clean_up_item *next = NULL; + + pa_assert(u); + + /* It would be convenient to remove or replace the entries in the database + * in the same loop that iterates through the database, but modifying the + * database is not supported while iterating through it. That's why we + * collect the entries that need to be removed or replaced to these + * lists. */ + PA_LLIST_HEAD_INIT(struct clean_up_item, to_be_removed); +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + PA_LLIST_HEAD_INIT(struct clean_up_item, to_be_converted); +#endif + + done = !pa_database_first(u->database, &key, NULL); + while (!done) { + pa_datum next_key; + char *entry_name = NULL; + struct entry *e = NULL; + + entry_name = pa_xstrndup(key.data, key.size); + + /* Use entry_read() to check whether this entry is valid. */ + if (!(e = entry_read(u, entry_name))) { + item = pa_xnew0(struct clean_up_item, 1); + PA_LLIST_INIT(struct clean_up_item, item); + item->entry_name = entry_name; + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + /* entry_read() failed, but what about legacy_entry_read()? */ + if (!(e = legacy_entry_read(u, entry_name))) + /* Not a legacy entry either, let's remove this. */ + PA_LLIST_PREPEND(struct clean_up_item, to_be_removed, item); + else { + /* Yay, it's valid after all! Now let's convert the entry to the current format. */ + item->entry = e; + PA_LLIST_PREPEND(struct clean_up_item, to_be_converted, item); + } +#else + /* Invalid entry, let's remove this. */ + PA_LLIST_PREPEND(struct clean_up_item, to_be_removed, item); +#endif + } else { + pa_xfree(entry_name); + entry_free(e); + } + + done = !pa_database_next(u->database, &key, &next_key, NULL); + pa_datum_free(&key); + key = next_key; + } + + PA_LLIST_FOREACH_SAFE(item, next, to_be_removed) { + key.data = item->entry_name; + key.size = strlen(item->entry_name); + + pa_log_debug("Removing an invalid entry: %s", item->entry_name); + + pa_assert_se(pa_database_unset(u->database, &key) >= 0); + trigger_save(u); + + PA_LLIST_REMOVE(struct clean_up_item, to_be_removed, item); + pa_xfree(item->entry_name); + pa_xfree(item); + } + +#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT + PA_LLIST_FOREACH_SAFE(item, next, to_be_converted) { + pa_log_debug("Upgrading a legacy entry to the current format: %s", item->entry_name); + + pa_assert_se(entry_write(u, item->entry_name, item->entry, true) >= 0); + trigger_save(u); + + PA_LLIST_REMOVE(struct clean_up_item, to_be_converted, item); + pa_xfree(item->entry_name); + entry_free(item->entry); + pa_xfree(item); + } +#endif +} + int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; @@ -1987,10 +2368,10 @@ int pa__init(pa_module*m) { pa_sink_input *si; pa_source_output *so; uint32_t idx; - pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE, on_hotplug = TRUE, on_rescue = TRUE; + bool restore_device = true, restore_volume = true, restore_muted = true, on_hotplug = true, on_rescue = true; #ifdef HAVE_DBUS pa_datum key; - pa_bool_t done; + bool done; #endif pa_assert(m); @@ -2047,13 +2428,15 @@ int pa__init(pa_module*m) { u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_unlink_hook_callback, u); } - if (restore_volume || restore_muted) + if (restore_volume || restore_muted) { u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); + u->source_output_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_fixate_hook_callback, u); + } - if (!(fname = pa_state_path("stream-volumes", TRUE))) + if (!(fname = pa_state_path("stream-volumes", 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; @@ -2062,9 +2445,14 @@ int pa__init(pa_module*m) { pa_log_info("Successfully opened database file '%s'.", fname); pa_xfree(fname); + clean_up_db(u); + + if (fill_db(u, pa_modargs_get_value(ma, "fallback_table", NULL)) < 0) + goto fail; + #ifdef HAVE_DBUS u->dbus_protocol = pa_dbus_protocol_get(u->core); - u->dbus_entries = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->dbus_entries = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) dbus_entry_free); pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, OBJECT_PATH, &stream_restore_interface_info, u) >= 0); pa_assert_se(pa_dbus_protocol_register_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0); @@ -2075,22 +2463,14 @@ int pa__init(pa_module*m) { pa_datum next_key; char *name; struct dbus_entry *de; - struct entry *e; - - done = !pa_database_next(u->database, &key, &next_key, NULL); name = pa_xstrndup(key.data, key.size); - pa_datum_free(&key); - - /* Use read_entry() for checking that the entry is valid. */ - if ((e = read_entry(u, name))) { - de = dbus_entry_new(u, name); - pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0); - pa_xfree(e); - } - + de = dbus_entry_new(u, name); + pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0); pa_xfree(name); + done = !pa_database_next(u->database, &key, &next_key, NULL); + pa_datum_free(&key); key = next_key; } #endif @@ -2113,16 +2493,6 @@ fail: return -1; } -#ifdef HAVE_DBUS -static void free_dbus_entry_cb(void *p, void *userdata) { - struct dbus_entry *de = p; - - pa_assert(de); - - dbus_entry_free(de); -} -#endif - void pa__done(pa_module*m) { struct userdata* u; @@ -2138,7 +2508,7 @@ void pa__done(pa_module*m) { pa_assert_se(pa_dbus_protocol_unregister_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0); pa_assert_se(pa_dbus_protocol_remove_interface(u->dbus_protocol, OBJECT_PATH, stream_restore_interface_info.name) >= 0); - pa_hashmap_free(u->dbus_entries, free_dbus_entry_cb, NULL); + pa_hashmap_free(u->dbus_entries); pa_dbus_protocol_unref(u->dbus_protocol); } @@ -2153,6 +2523,8 @@ void pa__done(pa_module*m) { pa_hook_slot_free(u->sink_input_fixate_hook_slot); if (u->source_output_new_hook_slot) pa_hook_slot_free(u->source_output_new_hook_slot); + if (u->source_output_fixate_hook_slot) + pa_hook_slot_free(u->source_output_fixate_hook_slot); if (u->sink_put_hook_slot) pa_hook_slot_free(u->sink_put_hook_slot); @@ -2179,7 +2551,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); }