+static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ struct dbus_entry *de = userdata;
+ pa_channel_map map;
+ pa_cvolume vol;
+ struct entry *e = NULL;
+ bool updated = false;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(de);
+
+ if (get_volume_arg(conn, msg, iter, &map, &vol) < 0)
+ return;
+
+ 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) {
+ e->volume = vol;
+ e->channel_map = map;
+ e->volume_valid = !!map.channels;
+
+ pa_assert_se(entry_write(de->userdata, de->entry_name, e, true));
+
+ 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);
+
+ entry_free(e);
+}
+
+static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ struct entry *e;
+ dbus_bool_t mute;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ 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);
+
+ 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;
+ bool updated;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(de);
+
+ dbus_message_iter_get_basic(iter, &mute);
+
+ pa_assert_se(e = entry_read(de->userdata, de->entry_name));
+
+ updated = !e->muted_valid || e->muted != mute;
+
+ if (updated) {
+ e->muted = mute;
+ e->muted_valid = true;
+
+ pa_assert_se(entry_write(de->userdata, de->entry_name, e, true));
+
+ 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);
+
+ entry_free(e);
+}
+
+static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ struct entry *e;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ const char *device;
+ dbus_bool_t mute;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_assert_se(e = entry_read(de->userdata, de->entry_name));
+
+ device = e->device_valid ? e->device : "";
+ mute = e->muted_valid ? e->muted : FALSE;
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &de->index);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &de->entry_name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_STRING, &device);
+
+ pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &entry_property_handlers[ENTRY_PROPERTY_HANDLER_VOLUME].property_name));
+ append_volume_variant(&dict_entry_iter, e);
+
+ pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &mute);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ entry_free(e);
+}
+
+static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ pa_datum key;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ key.data = de->entry_name;
+ key.size = strlen(de->entry_name);
+
+ pa_assert_se(pa_database_unset(de->userdata->database, &key) == 0);
+
+ send_entry_removed_signal(de);
+ trigger_save(de->userdata);
+
+ pa_assert_se(pa_hashmap_remove_and_free(de->userdata->dbus_entries, de->entry_name) >= 0);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+#endif /* HAVE_DBUS */
+
+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.");
+}
+
+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->device);
+ pa_xfree(e->card);
+ pa_xfree(e);
+}
+
+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.size = strlen(name);
+
+ pa_zero(data);
+
+ if (!pa_database_get(u->database, &key, &data))
+ goto fail;
+
+ 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;
+ }
+
+ 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 (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 (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 (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;
+ }
+
+ if (e->card_valid && !pa_namereg_is_valid_name(e->card)) {
+ pa_log_warn("Invalid card name stored in database for stream %s", name);
+ goto fail;
+ }
+
+ if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) {
+ pa_log_warn("Invalid channel map stored in database for stream %s", name);
+ goto fail;
+ }
+
+ if (e->volume_valid && (!pa_cvolume_valid(&e->volume) || !pa_cvolume_compatible_with_channel_map(&e->volume, &e->channel_map))) {
+ pa_log_warn("Invalid volume stored in database for stream %s", 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;
+
+ PA_IDXSET_FOREACH(c, u->subscribed, idx) {
+ pa_tagstruct *t;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION);
+ pa_tagstruct_putu32(t, 0);
+ pa_tagstruct_putu32(t, u->module->index);
+ pa_tagstruct_puts(t, u->module->name);
+ pa_tagstruct_putu32(t, SUBCOMMAND_EVENT);
+
+ pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t);
+ }
+
+ 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 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 && !pa_streq(a->device, b->device)))
+ return false;
+
+ if (a->card_valid != b->card_valid ||
+ (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;
+
+ 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 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;
+ char *name = NULL;
+
+ /* These are only used when D-Bus is enabled, but in order to reduce ifdef
+ * clutter these are defined here unconditionally. */
+ 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;
+#endif
+
+ pa_assert(c);
+ pa_assert(u);
+
+ if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
+ t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+ t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
+ t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
+ pa_sink_input *sink_input;
+
+ if (!(sink_input = pa_idxset_get_by_index(c->sink_inputs, idx)))
+ return;
+
+ if (!(name = pa_proplist_get_stream_group(sink_input->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
+ return;
+
+ if ((old = entry_read(u, name))) {
+ entry = entry_copy(old);
+ created_new_entry = false;
+ } else
+ entry = entry_new();
+
+ 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;
+
+ 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 (sink_input->save_muted) {
+ entry->muted = pa_sink_input_get_mute(sink_input);
+ entry->muted_valid = true;
+
+ mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted);
+ }
+
+ if (sink_input->save_sink) {
+ 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));
+ if (sink_input->sink->card) {
+ pa_xfree(entry->card);
+ entry->card = pa_xstrdup(sink_input->sink->card->name);
+ entry->card_valid = true;
+ }
+ }
+
+ } else {
+ pa_source_output *source_output;
+
+ pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT);
+
+ if (!(source_output = pa_idxset_get_by_index(c->source_outputs, idx)))
+ return;
+
+ if (!(name = pa_proplist_get_stream_group(source_output->proplist, "source-output", IDENTIFICATION_PROPERTY)))
+ return;
+
+ 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 = pa_source_output_get_mute(source_output);
+ entry->muted_valid = true;
+
+ mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted);
+ }
+
+ if (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));
+
+ if (source_output->source->card) {
+ 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)) {
+ entry_free(old);
+ entry_free(entry);
+ pa_xfree(name);
+ return;
+ }
+
+ entry_free(old);
+ }
+
+ pa_log_info("Storing volume/mute/device for stream %s.", name);
+
+ if (entry_write(u, name, entry, true))
+ trigger_save(u);
+
+#ifdef HAVE_DBUS
+ if (created_new_entry) {
+ de = dbus_entry_new(u, name);
+ pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0);
+ send_new_entry_signal(de);
+ } else {
+ pa_assert_se(de = pa_hashmap_get(u->dbus_entries, name));
+
+ if (device_updated)
+ send_device_updated_signal(de, entry);
+ if (volume_updated)
+ send_volume_updated_signal(de, entry);
+ if (mute_updated)
+ send_mute_updated_signal(de, entry);
+ }
+#endif
+
+ entry_free(entry);
+ pa_xfree(name);
+}
+
+static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_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_device);
+
+ 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 = entry_read(u, name))) {
+ pa_sink *s = NULL;
+
+ if (e->device_valid)
+ s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK);
+
+ if (!s && e->card_valid) {
+ pa_card *card;
+
+ if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
+ s = pa_idxset_first(card->sinks, NULL);
+ }
+
+ /* 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)))
+ if (pa_sink_input_new_data_set_sink(new_data, s, true))
+ pa_log_info("Restoring device for stream %s.", name);
+
+ entry_free(e);
+ }
+
+ pa_xfree(name);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_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, "sink-input", 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 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);
+ else {
+ pa_cvolume v;
+
+ pa_log_info("Restoring volume for sink input %s.", name);
+
+ v = e->volume;
+ 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;
+ }
+ }
+
+ if (u->restore_muted && e->muted_valid) {
+
+ 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;
+ } else
+ pa_log_debug("Not restoring mute state for sink input %s, because already set.", name);
+ }
+
+ entry_free(e);
+ }
+
+ pa_xfree(name);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_new_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_device);
+
+ if (new_data->direct_on_input)
+ return PA_HOOK_OK;
+
+ 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 = entry_read(u, name))) {
+ pa_source *s = NULL;
+
+ if (e->device_valid)
+ s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE);
+
+ if (!s && e->card_valid) {
+ pa_card *card;
+
+ if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
+ s = pa_idxset_first(card->sources, NULL);
+ }
+
+ /* 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_SOURCE_IS_LINKED(pa_source_get_state(s))) {
+ pa_log_info("Restoring device for stream %s.", name);
+ pa_source_output_new_data_set_source(new_data, s, true);
+ }
+
+ 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);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) {
+ pa_sink_input *si;
+ uint32_t idx;