153a5a1b3Sopenharmony_ci/***
253a5a1b3Sopenharmony_ci  This file is part of PulseAudio.
353a5a1b3Sopenharmony_ci
453a5a1b3Sopenharmony_ci  Copyright 2006-2008 Lennart Poettering
553a5a1b3Sopenharmony_ci  Copyright 2011 Colin Guthrie
653a5a1b3Sopenharmony_ci
753a5a1b3Sopenharmony_ci  PulseAudio is free software; you can redistribute it and/or modify
853a5a1b3Sopenharmony_ci  it under the terms of the GNU Lesser General Public License as published
953a5a1b3Sopenharmony_ci  by the Free Software Foundation; either version 2.1 of the License,
1053a5a1b3Sopenharmony_ci  or (at your option) any later version.
1153a5a1b3Sopenharmony_ci
1253a5a1b3Sopenharmony_ci  PulseAudio is distributed in the hope that it will be useful, but
1353a5a1b3Sopenharmony_ci  WITHOUT ANY WARRANTY; without even the implied warranty of
1453a5a1b3Sopenharmony_ci  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1553a5a1b3Sopenharmony_ci  General Public License for more details.
1653a5a1b3Sopenharmony_ci
1753a5a1b3Sopenharmony_ci  You should have received a copy of the GNU Lesser General Public License
1853a5a1b3Sopenharmony_ci  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
1953a5a1b3Sopenharmony_ci***/
2053a5a1b3Sopenharmony_ci
2153a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H
2253a5a1b3Sopenharmony_ci#include <config.h>
2353a5a1b3Sopenharmony_ci#endif
2453a5a1b3Sopenharmony_ci
2553a5a1b3Sopenharmony_ci#include <unistd.h>
2653a5a1b3Sopenharmony_ci#include <string.h>
2753a5a1b3Sopenharmony_ci#include <errno.h>
2853a5a1b3Sopenharmony_ci#include <sys/types.h>
2953a5a1b3Sopenharmony_ci#include <stdio.h>
3053a5a1b3Sopenharmony_ci#include <stdlib.h>
3153a5a1b3Sopenharmony_ci
3253a5a1b3Sopenharmony_ci#include <pulse/gccmacro.h>
3353a5a1b3Sopenharmony_ci#include <pulse/xmalloc.h>
3453a5a1b3Sopenharmony_ci#include <pulse/volume.h>
3553a5a1b3Sopenharmony_ci#include <pulse/timeval.h>
3653a5a1b3Sopenharmony_ci#include <pulse/rtclock.h>
3753a5a1b3Sopenharmony_ci#include <pulse/format.h>
3853a5a1b3Sopenharmony_ci#include <pulse/internal.h>
3953a5a1b3Sopenharmony_ci
4053a5a1b3Sopenharmony_ci#include <pulsecore/core-error.h>
4153a5a1b3Sopenharmony_ci#include <pulsecore/module.h>
4253a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h>
4353a5a1b3Sopenharmony_ci#include <pulsecore/modargs.h>
4453a5a1b3Sopenharmony_ci#include <pulsecore/log.h>
4553a5a1b3Sopenharmony_ci#include <pulsecore/core-subscribe.h>
4653a5a1b3Sopenharmony_ci#include <pulsecore/sink.h>
4753a5a1b3Sopenharmony_ci#include <pulsecore/source.h>
4853a5a1b3Sopenharmony_ci#include <pulsecore/namereg.h>
4953a5a1b3Sopenharmony_ci#include <pulsecore/protocol-native.h>
5053a5a1b3Sopenharmony_ci#include <pulsecore/pstream.h>
5153a5a1b3Sopenharmony_ci#include <pulsecore/pstream-util.h>
5253a5a1b3Sopenharmony_ci#include <pulsecore/database.h>
5353a5a1b3Sopenharmony_ci#include <pulsecore/tagstruct.h>
5453a5a1b3Sopenharmony_ci
5553a5a1b3Sopenharmony_ciPA_MODULE_AUTHOR("Lennart Poettering");
5653a5a1b3Sopenharmony_ciPA_MODULE_DESCRIPTION("Automatically restore the volume/mute state of devices");
5753a5a1b3Sopenharmony_ciPA_MODULE_VERSION(PACKAGE_VERSION);
5853a5a1b3Sopenharmony_ciPA_MODULE_LOAD_ONCE(true);
5953a5a1b3Sopenharmony_ciPA_MODULE_USAGE(
6053a5a1b3Sopenharmony_ci        "restore_port=<Save/restore port?> "
6153a5a1b3Sopenharmony_ci        "restore_volume=<Save/restore volumes?> "
6253a5a1b3Sopenharmony_ci        "restore_muted=<Save/restore muted states?> "
6353a5a1b3Sopenharmony_ci        "restore_formats=<Save/restore saved formats?>");
6453a5a1b3Sopenharmony_ci
6553a5a1b3Sopenharmony_ci#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
6653a5a1b3Sopenharmony_ci
6753a5a1b3Sopenharmony_cistatic const char* const valid_modargs[] = {
6853a5a1b3Sopenharmony_ci    "restore_volume",
6953a5a1b3Sopenharmony_ci    "restore_muted",
7053a5a1b3Sopenharmony_ci    "restore_port",
7153a5a1b3Sopenharmony_ci    "restore_formats",
7253a5a1b3Sopenharmony_ci    NULL
7353a5a1b3Sopenharmony_ci};
7453a5a1b3Sopenharmony_ci
7553a5a1b3Sopenharmony_cistruct userdata {
7653a5a1b3Sopenharmony_ci    pa_core *core;
7753a5a1b3Sopenharmony_ci    pa_module *module;
7853a5a1b3Sopenharmony_ci    pa_subscription *subscription;
7953a5a1b3Sopenharmony_ci    pa_time_event *save_time_event;
8053a5a1b3Sopenharmony_ci    pa_database *database;
8153a5a1b3Sopenharmony_ci
8253a5a1b3Sopenharmony_ci    pa_native_protocol *protocol;
8353a5a1b3Sopenharmony_ci    pa_idxset *subscribed;
8453a5a1b3Sopenharmony_ci
8553a5a1b3Sopenharmony_ci    bool restore_volume:1;
8653a5a1b3Sopenharmony_ci    bool restore_muted:1;
8753a5a1b3Sopenharmony_ci    bool restore_port:1;
8853a5a1b3Sopenharmony_ci    bool restore_formats:1;
8953a5a1b3Sopenharmony_ci};
9053a5a1b3Sopenharmony_ci
9153a5a1b3Sopenharmony_ci/* Protocol extension commands */
9253a5a1b3Sopenharmony_cienum {
9353a5a1b3Sopenharmony_ci    SUBCOMMAND_TEST,
9453a5a1b3Sopenharmony_ci    SUBCOMMAND_SUBSCRIBE,
9553a5a1b3Sopenharmony_ci    SUBCOMMAND_EVENT,
9653a5a1b3Sopenharmony_ci    SUBCOMMAND_READ_FORMATS_ALL,
9753a5a1b3Sopenharmony_ci    SUBCOMMAND_READ_FORMATS,
9853a5a1b3Sopenharmony_ci    SUBCOMMAND_SAVE_FORMATS
9953a5a1b3Sopenharmony_ci};
10053a5a1b3Sopenharmony_ci
10153a5a1b3Sopenharmony_ci#define ENTRY_VERSION 1
10253a5a1b3Sopenharmony_ci
10353a5a1b3Sopenharmony_cistruct entry {
10453a5a1b3Sopenharmony_ci    uint8_t version;
10553a5a1b3Sopenharmony_ci    bool port_valid;
10653a5a1b3Sopenharmony_ci    char *port;
10753a5a1b3Sopenharmony_ci};
10853a5a1b3Sopenharmony_ci
10953a5a1b3Sopenharmony_ci#define PERPORTENTRY_VERSION 1
11053a5a1b3Sopenharmony_ci
11153a5a1b3Sopenharmony_cistruct perportentry {
11253a5a1b3Sopenharmony_ci    uint8_t version;
11353a5a1b3Sopenharmony_ci    bool muted_valid, volume_valid;
11453a5a1b3Sopenharmony_ci    bool muted;
11553a5a1b3Sopenharmony_ci    pa_channel_map channel_map;
11653a5a1b3Sopenharmony_ci    pa_cvolume volume;
11753a5a1b3Sopenharmony_ci    pa_idxset *formats;
11853a5a1b3Sopenharmony_ci};
11953a5a1b3Sopenharmony_ci
12053a5a1b3Sopenharmony_cistatic void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
12153a5a1b3Sopenharmony_ci    struct userdata *u = userdata;
12253a5a1b3Sopenharmony_ci
12353a5a1b3Sopenharmony_ci    pa_assert(a);
12453a5a1b3Sopenharmony_ci    pa_assert(e);
12553a5a1b3Sopenharmony_ci    pa_assert(u);
12653a5a1b3Sopenharmony_ci
12753a5a1b3Sopenharmony_ci    pa_assert(e == u->save_time_event);
12853a5a1b3Sopenharmony_ci    u->core->mainloop->time_free(u->save_time_event);
12953a5a1b3Sopenharmony_ci    u->save_time_event = NULL;
13053a5a1b3Sopenharmony_ci
13153a5a1b3Sopenharmony_ci    pa_database_sync(u->database);
13253a5a1b3Sopenharmony_ci    pa_log_info("Synced.");
13353a5a1b3Sopenharmony_ci}
13453a5a1b3Sopenharmony_ci
13553a5a1b3Sopenharmony_cistatic void trigger_save(struct userdata *u, pa_device_type_t type, uint32_t sink_idx) {
13653a5a1b3Sopenharmony_ci    pa_native_connection *c;
13753a5a1b3Sopenharmony_ci    uint32_t idx;
13853a5a1b3Sopenharmony_ci
13953a5a1b3Sopenharmony_ci    if (sink_idx != PA_INVALID_INDEX) {
14053a5a1b3Sopenharmony_ci        PA_IDXSET_FOREACH(c, u->subscribed, idx) {
14153a5a1b3Sopenharmony_ci            pa_tagstruct *t;
14253a5a1b3Sopenharmony_ci
14353a5a1b3Sopenharmony_ci            t = pa_tagstruct_new();
14453a5a1b3Sopenharmony_ci            pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION);
14553a5a1b3Sopenharmony_ci            pa_tagstruct_putu32(t, 0);
14653a5a1b3Sopenharmony_ci            pa_tagstruct_putu32(t, u->module->index);
14753a5a1b3Sopenharmony_ci            pa_tagstruct_puts(t, u->module->name);
14853a5a1b3Sopenharmony_ci            pa_tagstruct_putu32(t, SUBCOMMAND_EVENT);
14953a5a1b3Sopenharmony_ci            pa_tagstruct_putu32(t, type);
15053a5a1b3Sopenharmony_ci            pa_tagstruct_putu32(t, sink_idx);
15153a5a1b3Sopenharmony_ci
15253a5a1b3Sopenharmony_ci            pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t);
15353a5a1b3Sopenharmony_ci        }
15453a5a1b3Sopenharmony_ci    }
15553a5a1b3Sopenharmony_ci
15653a5a1b3Sopenharmony_ci    if (u->save_time_event)
15753a5a1b3Sopenharmony_ci        return;
15853a5a1b3Sopenharmony_ci
15953a5a1b3Sopenharmony_ci    u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
16053a5a1b3Sopenharmony_ci}
16153a5a1b3Sopenharmony_ci
16253a5a1b3Sopenharmony_ci#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
16353a5a1b3Sopenharmony_ci/* Some forward declarations */
16453a5a1b3Sopenharmony_cistatic bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry **entry, struct perportentry **perportentry);
16553a5a1b3Sopenharmony_cistatic struct perportentry* perportentry_read(struct userdata *u, const char *basekeyname, const char *port);
16653a5a1b3Sopenharmony_cistatic bool perportentry_write(struct userdata *u, const char *basekeyname, const char *port, const struct perportentry *e);
16753a5a1b3Sopenharmony_cistatic void perportentry_free(struct perportentry* e);
16853a5a1b3Sopenharmony_ci#endif
16953a5a1b3Sopenharmony_ci
17053a5a1b3Sopenharmony_cistatic struct entry* entry_new(void) {
17153a5a1b3Sopenharmony_ci    struct entry *r = pa_xnew0(struct entry, 1);
17253a5a1b3Sopenharmony_ci    r->version = ENTRY_VERSION;
17353a5a1b3Sopenharmony_ci    return r;
17453a5a1b3Sopenharmony_ci}
17553a5a1b3Sopenharmony_ci
17653a5a1b3Sopenharmony_cistatic void entry_free(struct entry* e) {
17753a5a1b3Sopenharmony_ci    pa_assert(e);
17853a5a1b3Sopenharmony_ci
17953a5a1b3Sopenharmony_ci    pa_xfree(e->port);
18053a5a1b3Sopenharmony_ci    pa_xfree(e);
18153a5a1b3Sopenharmony_ci}
18253a5a1b3Sopenharmony_ci
18353a5a1b3Sopenharmony_cistatic bool entry_write(struct userdata *u, const char *name, const struct entry *e) {
18453a5a1b3Sopenharmony_ci    pa_tagstruct *t;
18553a5a1b3Sopenharmony_ci    pa_datum key, data;
18653a5a1b3Sopenharmony_ci    bool r;
18753a5a1b3Sopenharmony_ci
18853a5a1b3Sopenharmony_ci    pa_assert(u);
18953a5a1b3Sopenharmony_ci    pa_assert(name);
19053a5a1b3Sopenharmony_ci    pa_assert(e);
19153a5a1b3Sopenharmony_ci
19253a5a1b3Sopenharmony_ci    t = pa_tagstruct_new();
19353a5a1b3Sopenharmony_ci    pa_tagstruct_putu8(t, e->version);
19453a5a1b3Sopenharmony_ci    pa_tagstruct_put_boolean(t, e->port_valid);
19553a5a1b3Sopenharmony_ci    pa_tagstruct_puts(t, e->port);
19653a5a1b3Sopenharmony_ci
19753a5a1b3Sopenharmony_ci    key.data = (char *) name;
19853a5a1b3Sopenharmony_ci    key.size = strlen(name);
19953a5a1b3Sopenharmony_ci
20053a5a1b3Sopenharmony_ci    data.data = (void*)pa_tagstruct_data(t, &data.size);
20153a5a1b3Sopenharmony_ci
20253a5a1b3Sopenharmony_ci    r = (pa_database_set(u->database, &key, &data, true) == 0);
20353a5a1b3Sopenharmony_ci
20453a5a1b3Sopenharmony_ci    pa_tagstruct_free(t);
20553a5a1b3Sopenharmony_ci
20653a5a1b3Sopenharmony_ci    return r;
20753a5a1b3Sopenharmony_ci}
20853a5a1b3Sopenharmony_ci
20953a5a1b3Sopenharmony_cistatic struct entry* entry_read(struct userdata *u, const char *name) {
21053a5a1b3Sopenharmony_ci    pa_datum key, data;
21153a5a1b3Sopenharmony_ci    struct entry *e = NULL;
21253a5a1b3Sopenharmony_ci    pa_tagstruct *t = NULL;
21353a5a1b3Sopenharmony_ci    const char* port;
21453a5a1b3Sopenharmony_ci
21553a5a1b3Sopenharmony_ci    pa_assert(u);
21653a5a1b3Sopenharmony_ci    pa_assert(name);
21753a5a1b3Sopenharmony_ci
21853a5a1b3Sopenharmony_ci    key.data = (char*) name;
21953a5a1b3Sopenharmony_ci    key.size = strlen(name);
22053a5a1b3Sopenharmony_ci
22153a5a1b3Sopenharmony_ci    pa_zero(data);
22253a5a1b3Sopenharmony_ci
22353a5a1b3Sopenharmony_ci    if (!pa_database_get(u->database, &key, &data)) {
22453a5a1b3Sopenharmony_ci        pa_log_debug("Database contains no data for key: %s", name);
22553a5a1b3Sopenharmony_ci        return NULL;
22653a5a1b3Sopenharmony_ci    }
22753a5a1b3Sopenharmony_ci
22853a5a1b3Sopenharmony_ci    t = pa_tagstruct_new_fixed(data.data, data.size);
22953a5a1b3Sopenharmony_ci    e = entry_new();
23053a5a1b3Sopenharmony_ci
23153a5a1b3Sopenharmony_ci    if (pa_tagstruct_getu8(t, &e->version) < 0 ||
23253a5a1b3Sopenharmony_ci        e->version > ENTRY_VERSION ||
23353a5a1b3Sopenharmony_ci        pa_tagstruct_get_boolean(t, &e->port_valid) < 0 ||
23453a5a1b3Sopenharmony_ci        pa_tagstruct_gets(t, &port) < 0) {
23553a5a1b3Sopenharmony_ci
23653a5a1b3Sopenharmony_ci        goto fail;
23753a5a1b3Sopenharmony_ci    }
23853a5a1b3Sopenharmony_ci
23953a5a1b3Sopenharmony_ci    if (!pa_tagstruct_eof(t))
24053a5a1b3Sopenharmony_ci        goto fail;
24153a5a1b3Sopenharmony_ci
24253a5a1b3Sopenharmony_ci    e->port = pa_xstrdup(port);
24353a5a1b3Sopenharmony_ci
24453a5a1b3Sopenharmony_ci    pa_tagstruct_free(t);
24553a5a1b3Sopenharmony_ci    pa_datum_free(&data);
24653a5a1b3Sopenharmony_ci
24753a5a1b3Sopenharmony_ci    return e;
24853a5a1b3Sopenharmony_ci
24953a5a1b3Sopenharmony_cifail:
25053a5a1b3Sopenharmony_ci
25153a5a1b3Sopenharmony_ci    pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name);
25253a5a1b3Sopenharmony_ci
25353a5a1b3Sopenharmony_ci    if (e)
25453a5a1b3Sopenharmony_ci        entry_free(e);
25553a5a1b3Sopenharmony_ci    if (t)
25653a5a1b3Sopenharmony_ci        pa_tagstruct_free(t);
25753a5a1b3Sopenharmony_ci
25853a5a1b3Sopenharmony_ci#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
25953a5a1b3Sopenharmony_ci{
26053a5a1b3Sopenharmony_ci    struct perportentry *ppe;
26153a5a1b3Sopenharmony_ci    pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name);
26253a5a1b3Sopenharmony_ci    if (legacy_entry_read(u, &data, &e, &ppe)) {
26353a5a1b3Sopenharmony_ci        bool written = false;
26453a5a1b3Sopenharmony_ci
26553a5a1b3Sopenharmony_ci        pa_log_debug("Success. Saving new format for key: %s", name);
26653a5a1b3Sopenharmony_ci        written = entry_write(u, name, e);
26753a5a1b3Sopenharmony_ci
26853a5a1b3Sopenharmony_ci        /* Now convert the legacy entry into per-port entries */
26953a5a1b3Sopenharmony_ci        if (0 == strncmp("sink:", name, 5)) {
27053a5a1b3Sopenharmony_ci            pa_sink *sink;
27153a5a1b3Sopenharmony_ci
27253a5a1b3Sopenharmony_ci            if ((sink = pa_namereg_get(u->core, name+5, PA_NAMEREG_SINK))) {
27353a5a1b3Sopenharmony_ci                /* Write a "null" port entry. The read code will automatically try this
27453a5a1b3Sopenharmony_ci                 * if it cannot find a specific port-named entry. */
27553a5a1b3Sopenharmony_ci                written = perportentry_write(u, name, NULL, ppe) || written;
27653a5a1b3Sopenharmony_ci            }
27753a5a1b3Sopenharmony_ci        } else if (0 == strncmp("source:", name, 7)) {
27853a5a1b3Sopenharmony_ci            pa_source *source;
27953a5a1b3Sopenharmony_ci
28053a5a1b3Sopenharmony_ci            if ((source = pa_namereg_get(u->core, name+7, PA_NAMEREG_SOURCE))) {
28153a5a1b3Sopenharmony_ci                /* Write a "null" port entry. The read code will automatically try this
28253a5a1b3Sopenharmony_ci                 * if it cannot find a specific port-named entry. */
28353a5a1b3Sopenharmony_ci                written = perportentry_write(u, name, NULL, ppe) || written;
28453a5a1b3Sopenharmony_ci            }
28553a5a1b3Sopenharmony_ci        }
28653a5a1b3Sopenharmony_ci        perportentry_free(ppe);
28753a5a1b3Sopenharmony_ci
28853a5a1b3Sopenharmony_ci        if (written)
28953a5a1b3Sopenharmony_ci            /* NB The device type doesn't matter when we pass in an invalid index. */
29053a5a1b3Sopenharmony_ci            trigger_save(u, PA_DEVICE_TYPE_SINK, PA_INVALID_INDEX);
29153a5a1b3Sopenharmony_ci
29253a5a1b3Sopenharmony_ci        pa_datum_free(&data);
29353a5a1b3Sopenharmony_ci        return e;
29453a5a1b3Sopenharmony_ci    }
29553a5a1b3Sopenharmony_ci    pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name);
29653a5a1b3Sopenharmony_ci}
29753a5a1b3Sopenharmony_ci#endif
29853a5a1b3Sopenharmony_ci
29953a5a1b3Sopenharmony_ci    pa_datum_free(&data);
30053a5a1b3Sopenharmony_ci    return NULL;
30153a5a1b3Sopenharmony_ci}
30253a5a1b3Sopenharmony_ci
30353a5a1b3Sopenharmony_cistatic struct entry* entry_copy(const struct entry *e) {
30453a5a1b3Sopenharmony_ci    struct entry* r;
30553a5a1b3Sopenharmony_ci
30653a5a1b3Sopenharmony_ci    pa_assert(e);
30753a5a1b3Sopenharmony_ci    r = entry_new();
30853a5a1b3Sopenharmony_ci    r->version = e->version;
30953a5a1b3Sopenharmony_ci    r->port_valid = e->port_valid;
31053a5a1b3Sopenharmony_ci    r->port = pa_xstrdup(e->port);
31153a5a1b3Sopenharmony_ci
31253a5a1b3Sopenharmony_ci    return r;
31353a5a1b3Sopenharmony_ci}
31453a5a1b3Sopenharmony_ci
31553a5a1b3Sopenharmony_cistatic bool entries_equal(const struct entry *a, const struct entry *b) {
31653a5a1b3Sopenharmony_ci
31753a5a1b3Sopenharmony_ci    pa_assert(a && b);
31853a5a1b3Sopenharmony_ci
31953a5a1b3Sopenharmony_ci    if (a->port_valid != b->port_valid ||
32053a5a1b3Sopenharmony_ci        (a->port_valid && !pa_streq(a->port, b->port)))
32153a5a1b3Sopenharmony_ci        return false;
32253a5a1b3Sopenharmony_ci
32353a5a1b3Sopenharmony_ci    return true;
32453a5a1b3Sopenharmony_ci}
32553a5a1b3Sopenharmony_ci
32653a5a1b3Sopenharmony_cistatic struct perportentry* perportentry_new(bool add_pcm_format) {
32753a5a1b3Sopenharmony_ci    struct perportentry *r = pa_xnew0(struct perportentry, 1);
32853a5a1b3Sopenharmony_ci    r->version = PERPORTENTRY_VERSION;
32953a5a1b3Sopenharmony_ci    r->formats = pa_idxset_new(NULL, NULL);
33053a5a1b3Sopenharmony_ci    if (add_pcm_format) {
33153a5a1b3Sopenharmony_ci        pa_format_info *f = pa_format_info_new();
33253a5a1b3Sopenharmony_ci        f->encoding = PA_ENCODING_PCM;
33353a5a1b3Sopenharmony_ci        pa_idxset_put(r->formats, f, NULL);
33453a5a1b3Sopenharmony_ci    }
33553a5a1b3Sopenharmony_ci    return r;
33653a5a1b3Sopenharmony_ci}
33753a5a1b3Sopenharmony_ci
33853a5a1b3Sopenharmony_cistatic void perportentry_free(struct perportentry* e) {
33953a5a1b3Sopenharmony_ci    pa_assert(e);
34053a5a1b3Sopenharmony_ci
34153a5a1b3Sopenharmony_ci    pa_idxset_free(e->formats, (pa_free_cb_t) pa_format_info_free);
34253a5a1b3Sopenharmony_ci    pa_xfree(e);
34353a5a1b3Sopenharmony_ci}
34453a5a1b3Sopenharmony_ci
34553a5a1b3Sopenharmony_cistatic bool perportentry_write(struct userdata *u, const char *basekeyname, const char *port, const struct perportentry *e) {
34653a5a1b3Sopenharmony_ci    pa_tagstruct *t;
34753a5a1b3Sopenharmony_ci    pa_datum key, data;
34853a5a1b3Sopenharmony_ci    bool r;
34953a5a1b3Sopenharmony_ci    uint32_t i;
35053a5a1b3Sopenharmony_ci    pa_format_info *f;
35153a5a1b3Sopenharmony_ci    uint8_t n_formats;
35253a5a1b3Sopenharmony_ci    char *name;
35353a5a1b3Sopenharmony_ci
35453a5a1b3Sopenharmony_ci    pa_assert(u);
35553a5a1b3Sopenharmony_ci    pa_assert(basekeyname);
35653a5a1b3Sopenharmony_ci    pa_assert(e);
35753a5a1b3Sopenharmony_ci
35853a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("%s:%s", basekeyname, (port ? port : "null"));
35953a5a1b3Sopenharmony_ci
36053a5a1b3Sopenharmony_ci    n_formats = pa_idxset_size(e->formats);
36153a5a1b3Sopenharmony_ci    pa_assert(n_formats > 0);
36253a5a1b3Sopenharmony_ci
36353a5a1b3Sopenharmony_ci    t = pa_tagstruct_new();
36453a5a1b3Sopenharmony_ci    pa_tagstruct_putu8(t, e->version);
36553a5a1b3Sopenharmony_ci    pa_tagstruct_put_boolean(t, e->volume_valid);
36653a5a1b3Sopenharmony_ci    pa_tagstruct_put_channel_map(t, &e->channel_map);
36753a5a1b3Sopenharmony_ci    pa_tagstruct_put_cvolume(t, &e->volume);
36853a5a1b3Sopenharmony_ci    pa_tagstruct_put_boolean(t, e->muted_valid);
36953a5a1b3Sopenharmony_ci    pa_tagstruct_put_boolean(t, e->muted);
37053a5a1b3Sopenharmony_ci    pa_tagstruct_putu8(t, n_formats);
37153a5a1b3Sopenharmony_ci
37253a5a1b3Sopenharmony_ci    PA_IDXSET_FOREACH(f, e->formats, i) {
37353a5a1b3Sopenharmony_ci        pa_tagstruct_put_format_info(t, f);
37453a5a1b3Sopenharmony_ci    }
37553a5a1b3Sopenharmony_ci
37653a5a1b3Sopenharmony_ci    key.data = (char *) name;
37753a5a1b3Sopenharmony_ci    key.size = strlen(name);
37853a5a1b3Sopenharmony_ci
37953a5a1b3Sopenharmony_ci    data.data = (void*)pa_tagstruct_data(t, &data.size);
38053a5a1b3Sopenharmony_ci
38153a5a1b3Sopenharmony_ci    r = (pa_database_set(u->database, &key, &data, true) == 0);
38253a5a1b3Sopenharmony_ci
38353a5a1b3Sopenharmony_ci    pa_tagstruct_free(t);
38453a5a1b3Sopenharmony_ci    pa_xfree(name);
38553a5a1b3Sopenharmony_ci
38653a5a1b3Sopenharmony_ci    return r;
38753a5a1b3Sopenharmony_ci}
38853a5a1b3Sopenharmony_ci
38953a5a1b3Sopenharmony_cistatic struct perportentry* perportentry_read(struct userdata *u, const char *basekeyname, const char *port) {
39053a5a1b3Sopenharmony_ci    pa_datum key, data;
39153a5a1b3Sopenharmony_ci    struct perportentry *e = NULL;
39253a5a1b3Sopenharmony_ci    pa_tagstruct *t = NULL;
39353a5a1b3Sopenharmony_ci    uint8_t i, n_formats;
39453a5a1b3Sopenharmony_ci    char *name;
39553a5a1b3Sopenharmony_ci
39653a5a1b3Sopenharmony_ci    pa_assert(u);
39753a5a1b3Sopenharmony_ci    pa_assert(basekeyname);
39853a5a1b3Sopenharmony_ci
39953a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("%s:%s", basekeyname, (port ? port : "null"));
40053a5a1b3Sopenharmony_ci
40153a5a1b3Sopenharmony_ci    key.data = name;
40253a5a1b3Sopenharmony_ci    key.size = strlen(name);
40353a5a1b3Sopenharmony_ci
40453a5a1b3Sopenharmony_ci    pa_zero(data);
40553a5a1b3Sopenharmony_ci
40653a5a1b3Sopenharmony_ci    if (!pa_database_get(u->database, &key, &data))
40753a5a1b3Sopenharmony_ci        goto fail;
40853a5a1b3Sopenharmony_ci
40953a5a1b3Sopenharmony_ci    t = pa_tagstruct_new_fixed(data.data, data.size);
41053a5a1b3Sopenharmony_ci    e = perportentry_new(false);
41153a5a1b3Sopenharmony_ci
41253a5a1b3Sopenharmony_ci    if (pa_tagstruct_getu8(t, &e->version) < 0 ||
41353a5a1b3Sopenharmony_ci        e->version > PERPORTENTRY_VERSION ||
41453a5a1b3Sopenharmony_ci        pa_tagstruct_get_boolean(t, &e->volume_valid) < 0 ||
41553a5a1b3Sopenharmony_ci        pa_tagstruct_get_channel_map(t, &e->channel_map) < 0 ||
41653a5a1b3Sopenharmony_ci        pa_tagstruct_get_cvolume(t, &e->volume) < 0 ||
41753a5a1b3Sopenharmony_ci        pa_tagstruct_get_boolean(t, &e->muted_valid) < 0 ||
41853a5a1b3Sopenharmony_ci        pa_tagstruct_get_boolean(t, &e->muted) < 0 ||
41953a5a1b3Sopenharmony_ci        pa_tagstruct_getu8(t, &n_formats) < 0 || n_formats < 1) {
42053a5a1b3Sopenharmony_ci
42153a5a1b3Sopenharmony_ci        goto fail;
42253a5a1b3Sopenharmony_ci    }
42353a5a1b3Sopenharmony_ci
42453a5a1b3Sopenharmony_ci    for (i = 0; i < n_formats; ++i) {
42553a5a1b3Sopenharmony_ci        pa_format_info *f = pa_format_info_new();
42653a5a1b3Sopenharmony_ci        if (pa_tagstruct_get_format_info(t, f) < 0) {
42753a5a1b3Sopenharmony_ci            pa_format_info_free(f);
42853a5a1b3Sopenharmony_ci            goto fail;
42953a5a1b3Sopenharmony_ci        }
43053a5a1b3Sopenharmony_ci        pa_idxset_put(e->formats, f, NULL);
43153a5a1b3Sopenharmony_ci    }
43253a5a1b3Sopenharmony_ci
43353a5a1b3Sopenharmony_ci    if (!pa_tagstruct_eof(t))
43453a5a1b3Sopenharmony_ci        goto fail;
43553a5a1b3Sopenharmony_ci
43653a5a1b3Sopenharmony_ci    if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) {
43753a5a1b3Sopenharmony_ci        pa_log_warn("Invalid channel map stored in database for device %s", name);
43853a5a1b3Sopenharmony_ci        goto fail;
43953a5a1b3Sopenharmony_ci    }
44053a5a1b3Sopenharmony_ci
44153a5a1b3Sopenharmony_ci    if (e->volume_valid && (!pa_cvolume_valid(&e->volume) || !pa_cvolume_compatible_with_channel_map(&e->volume, &e->channel_map))) {
44253a5a1b3Sopenharmony_ci        pa_log_warn("Volume and channel map don't match in database entry for device %s", name);
44353a5a1b3Sopenharmony_ci        goto fail;
44453a5a1b3Sopenharmony_ci    }
44553a5a1b3Sopenharmony_ci
44653a5a1b3Sopenharmony_ci    pa_tagstruct_free(t);
44753a5a1b3Sopenharmony_ci    pa_datum_free(&data);
44853a5a1b3Sopenharmony_ci    pa_xfree(name);
44953a5a1b3Sopenharmony_ci
45053a5a1b3Sopenharmony_ci    return e;
45153a5a1b3Sopenharmony_ci
45253a5a1b3Sopenharmony_cifail:
45353a5a1b3Sopenharmony_ci
45453a5a1b3Sopenharmony_ci    if (e)
45553a5a1b3Sopenharmony_ci        perportentry_free(e);
45653a5a1b3Sopenharmony_ci    if (t)
45753a5a1b3Sopenharmony_ci        pa_tagstruct_free(t);
45853a5a1b3Sopenharmony_ci
45953a5a1b3Sopenharmony_ci    pa_datum_free(&data);
46053a5a1b3Sopenharmony_ci
46153a5a1b3Sopenharmony_ci#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
46253a5a1b3Sopenharmony_ci    /* Try again with a null port. This is used when dealing with migration from older versions */
46353a5a1b3Sopenharmony_ci    if (port) {
46453a5a1b3Sopenharmony_ci        pa_xfree(name);
46553a5a1b3Sopenharmony_ci        return perportentry_read(u, basekeyname, NULL);
46653a5a1b3Sopenharmony_ci    }
46753a5a1b3Sopenharmony_ci#endif
46853a5a1b3Sopenharmony_ci
46953a5a1b3Sopenharmony_ci    pa_log_debug("Database contains no (or invalid) data for key: %s", name);
47053a5a1b3Sopenharmony_ci
47153a5a1b3Sopenharmony_ci    pa_xfree(name);
47253a5a1b3Sopenharmony_ci
47353a5a1b3Sopenharmony_ci    return NULL;
47453a5a1b3Sopenharmony_ci}
47553a5a1b3Sopenharmony_ci
47653a5a1b3Sopenharmony_cistatic struct perportentry* perportentry_copy(const struct perportentry *e) {
47753a5a1b3Sopenharmony_ci    struct perportentry* r;
47853a5a1b3Sopenharmony_ci    uint32_t idx;
47953a5a1b3Sopenharmony_ci    pa_format_info *f;
48053a5a1b3Sopenharmony_ci
48153a5a1b3Sopenharmony_ci    pa_assert(e);
48253a5a1b3Sopenharmony_ci    r = perportentry_new(false);
48353a5a1b3Sopenharmony_ci    r->version = e->version;
48453a5a1b3Sopenharmony_ci    r->muted_valid = e->muted_valid;
48553a5a1b3Sopenharmony_ci    r->volume_valid = e->volume_valid;
48653a5a1b3Sopenharmony_ci    r->muted = e->muted;
48753a5a1b3Sopenharmony_ci    r->channel_map = e->channel_map;
48853a5a1b3Sopenharmony_ci    r->volume = e->volume;
48953a5a1b3Sopenharmony_ci
49053a5a1b3Sopenharmony_ci    PA_IDXSET_FOREACH(f, e->formats, idx) {
49153a5a1b3Sopenharmony_ci        pa_idxset_put(r->formats, pa_format_info_copy(f), NULL);
49253a5a1b3Sopenharmony_ci    }
49353a5a1b3Sopenharmony_ci    return r;
49453a5a1b3Sopenharmony_ci}
49553a5a1b3Sopenharmony_ci
49653a5a1b3Sopenharmony_cistatic bool perportentries_equal(const struct perportentry *a, const struct perportentry *b) {
49753a5a1b3Sopenharmony_ci    pa_cvolume t;
49853a5a1b3Sopenharmony_ci
49953a5a1b3Sopenharmony_ci    pa_assert(a && b);
50053a5a1b3Sopenharmony_ci
50153a5a1b3Sopenharmony_ci    if (a->muted_valid != b->muted_valid ||
50253a5a1b3Sopenharmony_ci        (a->muted_valid && (a->muted != b->muted)))
50353a5a1b3Sopenharmony_ci        return false;
50453a5a1b3Sopenharmony_ci
50553a5a1b3Sopenharmony_ci    t = b->volume;
50653a5a1b3Sopenharmony_ci    if (a->volume_valid != b->volume_valid ||
50753a5a1b3Sopenharmony_ci        (a->volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->volume)))
50853a5a1b3Sopenharmony_ci        return false;
50953a5a1b3Sopenharmony_ci
51053a5a1b3Sopenharmony_ci    if (pa_idxset_size(a->formats) != pa_idxset_size(b->formats))
51153a5a1b3Sopenharmony_ci        return false;
51253a5a1b3Sopenharmony_ci
51353a5a1b3Sopenharmony_ci    /** TODO: Compare a bit better */
51453a5a1b3Sopenharmony_ci
51553a5a1b3Sopenharmony_ci    return true;
51653a5a1b3Sopenharmony_ci}
51753a5a1b3Sopenharmony_ci
51853a5a1b3Sopenharmony_ci#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
51953a5a1b3Sopenharmony_ci
52053a5a1b3Sopenharmony_ci#define LEGACY_ENTRY_VERSION 2
52153a5a1b3Sopenharmony_cistatic bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry **entry, struct perportentry **perportentry) {
52253a5a1b3Sopenharmony_ci    struct legacy_entry {
52353a5a1b3Sopenharmony_ci        uint8_t version;
52453a5a1b3Sopenharmony_ci        bool muted_valid:1, volume_valid:1, port_valid:1;
52553a5a1b3Sopenharmony_ci        bool muted:1;
52653a5a1b3Sopenharmony_ci        pa_channel_map channel_map;
52753a5a1b3Sopenharmony_ci        pa_cvolume volume;
52853a5a1b3Sopenharmony_ci        char port[PA_NAME_MAX];
52953a5a1b3Sopenharmony_ci    } PA_GCC_PACKED;
53053a5a1b3Sopenharmony_ci    struct legacy_entry *le;
53153a5a1b3Sopenharmony_ci    pa_channel_map channel_map;
53253a5a1b3Sopenharmony_ci    pa_cvolume volume;
53353a5a1b3Sopenharmony_ci
53453a5a1b3Sopenharmony_ci    pa_assert(u);
53553a5a1b3Sopenharmony_ci    pa_assert(data);
53653a5a1b3Sopenharmony_ci    pa_assert(entry);
53753a5a1b3Sopenharmony_ci    pa_assert(perportentry);
53853a5a1b3Sopenharmony_ci
53953a5a1b3Sopenharmony_ci    if (data->size != sizeof(struct legacy_entry)) {
54053a5a1b3Sopenharmony_ci        pa_log_debug("Size does not match.");
54153a5a1b3Sopenharmony_ci        return false;
54253a5a1b3Sopenharmony_ci    }
54353a5a1b3Sopenharmony_ci
54453a5a1b3Sopenharmony_ci    le = (struct legacy_entry*)data->data;
54553a5a1b3Sopenharmony_ci
54653a5a1b3Sopenharmony_ci    if (le->version != LEGACY_ENTRY_VERSION) {
54753a5a1b3Sopenharmony_ci        pa_log_debug("Version mismatch.");
54853a5a1b3Sopenharmony_ci        return false;
54953a5a1b3Sopenharmony_ci    }
55053a5a1b3Sopenharmony_ci
55153a5a1b3Sopenharmony_ci    if (!memchr(le->port, 0, sizeof(le->port))) {
55253a5a1b3Sopenharmony_ci        pa_log_warn("Port has missing NUL byte.");
55353a5a1b3Sopenharmony_ci        return false;
55453a5a1b3Sopenharmony_ci    }
55553a5a1b3Sopenharmony_ci
55653a5a1b3Sopenharmony_ci    /* Read these out before accessing contents via pointers as struct legacy_entry may not be adequately aligned for these
55753a5a1b3Sopenharmony_ci     * members to be accessed directly */
55853a5a1b3Sopenharmony_ci    channel_map = le->channel_map;
55953a5a1b3Sopenharmony_ci    volume = le->volume;
56053a5a1b3Sopenharmony_ci
56153a5a1b3Sopenharmony_ci    if (le->volume_valid && !pa_channel_map_valid(&channel_map)) {
56253a5a1b3Sopenharmony_ci        pa_log_warn("Invalid channel map.");
56353a5a1b3Sopenharmony_ci        return false;
56453a5a1b3Sopenharmony_ci    }
56553a5a1b3Sopenharmony_ci
56653a5a1b3Sopenharmony_ci    if (le->volume_valid && (!pa_cvolume_valid(&volume) || !pa_cvolume_compatible_with_channel_map(&volume, &channel_map))) {
56753a5a1b3Sopenharmony_ci        pa_log_warn("Volume and channel map don't match.");
56853a5a1b3Sopenharmony_ci        return false;
56953a5a1b3Sopenharmony_ci    }
57053a5a1b3Sopenharmony_ci
57153a5a1b3Sopenharmony_ci    *entry = entry_new();
57253a5a1b3Sopenharmony_ci    (*entry)->port_valid = le->port_valid;
57353a5a1b3Sopenharmony_ci    (*entry)->port = pa_xstrdup(le->port);
57453a5a1b3Sopenharmony_ci
57553a5a1b3Sopenharmony_ci    *perportentry = perportentry_new(true);
57653a5a1b3Sopenharmony_ci    (*perportentry)->muted_valid = le->muted_valid;
57753a5a1b3Sopenharmony_ci    (*perportentry)->volume_valid = le->volume_valid;
57853a5a1b3Sopenharmony_ci    (*perportentry)->muted = le->muted;
57953a5a1b3Sopenharmony_ci    (*perportentry)->channel_map = le->channel_map;
58053a5a1b3Sopenharmony_ci    (*perportentry)->volume = le->volume;
58153a5a1b3Sopenharmony_ci
58253a5a1b3Sopenharmony_ci    return true;
58353a5a1b3Sopenharmony_ci}
58453a5a1b3Sopenharmony_ci#endif
58553a5a1b3Sopenharmony_ci
58653a5a1b3Sopenharmony_cistatic void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
58753a5a1b3Sopenharmony_ci    struct userdata *u = userdata;
58853a5a1b3Sopenharmony_ci    struct entry *e, *olde;
58953a5a1b3Sopenharmony_ci    struct perportentry *ppe, *oldppe;
59053a5a1b3Sopenharmony_ci    char *name;
59153a5a1b3Sopenharmony_ci    const char *port = NULL;
59253a5a1b3Sopenharmony_ci    pa_device_type_t type;
59353a5a1b3Sopenharmony_ci    bool written = false;
59453a5a1b3Sopenharmony_ci
59553a5a1b3Sopenharmony_ci    pa_assert(c);
59653a5a1b3Sopenharmony_ci    pa_assert(u);
59753a5a1b3Sopenharmony_ci
59853a5a1b3Sopenharmony_ci    if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) &&
59953a5a1b3Sopenharmony_ci        t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) &&
60053a5a1b3Sopenharmony_ci        t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) &&
60153a5a1b3Sopenharmony_ci        t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE))
60253a5a1b3Sopenharmony_ci        return;
60353a5a1b3Sopenharmony_ci
60453a5a1b3Sopenharmony_ci    if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
60553a5a1b3Sopenharmony_ci        pa_sink *sink;
60653a5a1b3Sopenharmony_ci
60753a5a1b3Sopenharmony_ci        if (!(sink = pa_idxset_get_by_index(c->sinks, idx)))
60853a5a1b3Sopenharmony_ci            return;
60953a5a1b3Sopenharmony_ci
61053a5a1b3Sopenharmony_ci        type = PA_DEVICE_TYPE_SINK;
61153a5a1b3Sopenharmony_ci        name = pa_sprintf_malloc("sink:%s", sink->name);
61253a5a1b3Sopenharmony_ci        if (sink->active_port)
61353a5a1b3Sopenharmony_ci            port = sink->active_port->name;
61453a5a1b3Sopenharmony_ci
61553a5a1b3Sopenharmony_ci        if ((olde = entry_read(u, name)))
61653a5a1b3Sopenharmony_ci            e = entry_copy(olde);
61753a5a1b3Sopenharmony_ci        else
61853a5a1b3Sopenharmony_ci            e = entry_new();
61953a5a1b3Sopenharmony_ci
62053a5a1b3Sopenharmony_ci        if (sink->save_port) {
62153a5a1b3Sopenharmony_ci            pa_xfree(e->port);
62253a5a1b3Sopenharmony_ci            e->port = pa_xstrdup(port ? port : "");
62353a5a1b3Sopenharmony_ci            e->port_valid = true;
62453a5a1b3Sopenharmony_ci        }
62553a5a1b3Sopenharmony_ci
62653a5a1b3Sopenharmony_ci        if ((oldppe = perportentry_read(u, name, port)))
62753a5a1b3Sopenharmony_ci            ppe = perportentry_copy(oldppe);
62853a5a1b3Sopenharmony_ci        else
62953a5a1b3Sopenharmony_ci            ppe = perportentry_new(true);
63053a5a1b3Sopenharmony_ci
63153a5a1b3Sopenharmony_ci        if (sink->save_volume) {
63253a5a1b3Sopenharmony_ci            ppe->channel_map = sink->channel_map;
63353a5a1b3Sopenharmony_ci            ppe->volume = *pa_sink_get_volume(sink, false);
63453a5a1b3Sopenharmony_ci            ppe->volume_valid = true;
63553a5a1b3Sopenharmony_ci        }
63653a5a1b3Sopenharmony_ci
63753a5a1b3Sopenharmony_ci        if (sink->save_muted) {
63853a5a1b3Sopenharmony_ci            ppe->muted = pa_sink_get_mute(sink, false);
63953a5a1b3Sopenharmony_ci            ppe->muted_valid = true;
64053a5a1b3Sopenharmony_ci        }
64153a5a1b3Sopenharmony_ci    } else {
64253a5a1b3Sopenharmony_ci        pa_source *source;
64353a5a1b3Sopenharmony_ci
64453a5a1b3Sopenharmony_ci        pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
64553a5a1b3Sopenharmony_ci
64653a5a1b3Sopenharmony_ci        if (!(source = pa_idxset_get_by_index(c->sources, idx)))
64753a5a1b3Sopenharmony_ci            return;
64853a5a1b3Sopenharmony_ci
64953a5a1b3Sopenharmony_ci        type = PA_DEVICE_TYPE_SOURCE;
65053a5a1b3Sopenharmony_ci        name = pa_sprintf_malloc("source:%s", source->name);
65153a5a1b3Sopenharmony_ci        if (source->active_port)
65253a5a1b3Sopenharmony_ci            port = source->active_port->name;
65353a5a1b3Sopenharmony_ci
65453a5a1b3Sopenharmony_ci        if ((olde = entry_read(u, name)))
65553a5a1b3Sopenharmony_ci            e = entry_copy(olde);
65653a5a1b3Sopenharmony_ci        else
65753a5a1b3Sopenharmony_ci            e = entry_new();
65853a5a1b3Sopenharmony_ci
65953a5a1b3Sopenharmony_ci        if (source->save_port) {
66053a5a1b3Sopenharmony_ci            pa_xfree(e->port);
66153a5a1b3Sopenharmony_ci            e->port = pa_xstrdup(port ? port : "");
66253a5a1b3Sopenharmony_ci            e->port_valid = true;
66353a5a1b3Sopenharmony_ci        }
66453a5a1b3Sopenharmony_ci
66553a5a1b3Sopenharmony_ci        if ((oldppe = perportentry_read(u, name, port)))
66653a5a1b3Sopenharmony_ci            ppe = perportentry_copy(oldppe);
66753a5a1b3Sopenharmony_ci        else
66853a5a1b3Sopenharmony_ci            ppe = perportentry_new(true);
66953a5a1b3Sopenharmony_ci
67053a5a1b3Sopenharmony_ci        if (source->save_volume) {
67153a5a1b3Sopenharmony_ci            ppe->channel_map = source->channel_map;
67253a5a1b3Sopenharmony_ci            ppe->volume = *pa_source_get_volume(source, false);
67353a5a1b3Sopenharmony_ci            ppe->volume_valid = true;
67453a5a1b3Sopenharmony_ci        }
67553a5a1b3Sopenharmony_ci
67653a5a1b3Sopenharmony_ci        if (source->save_muted) {
67753a5a1b3Sopenharmony_ci            ppe->muted = pa_source_get_mute(source, false);
67853a5a1b3Sopenharmony_ci            ppe->muted_valid = true;
67953a5a1b3Sopenharmony_ci        }
68053a5a1b3Sopenharmony_ci    }
68153a5a1b3Sopenharmony_ci
68253a5a1b3Sopenharmony_ci    pa_assert(e);
68353a5a1b3Sopenharmony_ci
68453a5a1b3Sopenharmony_ci    if (olde) {
68553a5a1b3Sopenharmony_ci
68653a5a1b3Sopenharmony_ci        if (entries_equal(olde, e)) {
68753a5a1b3Sopenharmony_ci            entry_free(olde);
68853a5a1b3Sopenharmony_ci            entry_free(e);
68953a5a1b3Sopenharmony_ci            e = NULL;
69053a5a1b3Sopenharmony_ci        } else
69153a5a1b3Sopenharmony_ci            entry_free(olde);
69253a5a1b3Sopenharmony_ci    }
69353a5a1b3Sopenharmony_ci
69453a5a1b3Sopenharmony_ci    if (e) {
69553a5a1b3Sopenharmony_ci        pa_log_info("Storing port for device %s.", name);
69653a5a1b3Sopenharmony_ci
69753a5a1b3Sopenharmony_ci        written = entry_write(u, name, e);
69853a5a1b3Sopenharmony_ci
69953a5a1b3Sopenharmony_ci        entry_free(e);
70053a5a1b3Sopenharmony_ci    }
70153a5a1b3Sopenharmony_ci
70253a5a1b3Sopenharmony_ci    pa_assert(ppe);
70353a5a1b3Sopenharmony_ci
70453a5a1b3Sopenharmony_ci    if (oldppe) {
70553a5a1b3Sopenharmony_ci
70653a5a1b3Sopenharmony_ci        if (perportentries_equal(oldppe, ppe)) {
70753a5a1b3Sopenharmony_ci            perportentry_free(oldppe);
70853a5a1b3Sopenharmony_ci            perportentry_free(ppe);
70953a5a1b3Sopenharmony_ci            ppe = NULL;
71053a5a1b3Sopenharmony_ci        } else
71153a5a1b3Sopenharmony_ci            perportentry_free(oldppe);
71253a5a1b3Sopenharmony_ci    }
71353a5a1b3Sopenharmony_ci
71453a5a1b3Sopenharmony_ci    if (ppe) {
71553a5a1b3Sopenharmony_ci        pa_log_info("Storing volume/mute for device+port %s:%s.", name, (port ? port : "null"));
71653a5a1b3Sopenharmony_ci
71753a5a1b3Sopenharmony_ci        written = perportentry_write(u, name, port, ppe) || written;
71853a5a1b3Sopenharmony_ci
71953a5a1b3Sopenharmony_ci        perportentry_free(ppe);
72053a5a1b3Sopenharmony_ci    }
72153a5a1b3Sopenharmony_ci    pa_xfree(name);
72253a5a1b3Sopenharmony_ci
72353a5a1b3Sopenharmony_ci    if (written)
72453a5a1b3Sopenharmony_ci        trigger_save(u, type, idx);
72553a5a1b3Sopenharmony_ci}
72653a5a1b3Sopenharmony_ci
72753a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
72853a5a1b3Sopenharmony_ci    char *name;
72953a5a1b3Sopenharmony_ci    struct entry *e;
73053a5a1b3Sopenharmony_ci
73153a5a1b3Sopenharmony_ci    pa_assert(c);
73253a5a1b3Sopenharmony_ci    pa_assert(new_data);
73353a5a1b3Sopenharmony_ci    pa_assert(u);
73453a5a1b3Sopenharmony_ci    pa_assert(u->restore_port);
73553a5a1b3Sopenharmony_ci
73653a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("sink:%s", new_data->name);
73753a5a1b3Sopenharmony_ci
73853a5a1b3Sopenharmony_ci    if ((e = entry_read(u, name))) {
73953a5a1b3Sopenharmony_ci
74053a5a1b3Sopenharmony_ci        if (e->port_valid) {
74153a5a1b3Sopenharmony_ci            if (!new_data->active_port) {
74253a5a1b3Sopenharmony_ci                pa_log_info("Restoring port '%s' for sink %s.", pa_strnull(e->port), name);
74353a5a1b3Sopenharmony_ci                pa_sink_new_data_set_port(new_data, e->port);
74453a5a1b3Sopenharmony_ci                new_data->save_port = true;
74553a5a1b3Sopenharmony_ci            } else
74653a5a1b3Sopenharmony_ci                pa_log_debug("Not restoring port for sink %s, because already set.", name);
74753a5a1b3Sopenharmony_ci        }
74853a5a1b3Sopenharmony_ci
74953a5a1b3Sopenharmony_ci        entry_free(e);
75053a5a1b3Sopenharmony_ci    }
75153a5a1b3Sopenharmony_ci
75253a5a1b3Sopenharmony_ci    pa_xfree(name);
75353a5a1b3Sopenharmony_ci
75453a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
75553a5a1b3Sopenharmony_ci}
75653a5a1b3Sopenharmony_ci
75753a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_fixate_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
75853a5a1b3Sopenharmony_ci    char *name;
75953a5a1b3Sopenharmony_ci    struct perportentry *e;
76053a5a1b3Sopenharmony_ci
76153a5a1b3Sopenharmony_ci    pa_assert(c);
76253a5a1b3Sopenharmony_ci    pa_assert(new_data);
76353a5a1b3Sopenharmony_ci    pa_assert(u);
76453a5a1b3Sopenharmony_ci    pa_assert(u->restore_volume || u->restore_muted);
76553a5a1b3Sopenharmony_ci
76653a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("sink:%s", new_data->name);
76753a5a1b3Sopenharmony_ci
76853a5a1b3Sopenharmony_ci    if ((e = perportentry_read(u, name, new_data->active_port))) {
76953a5a1b3Sopenharmony_ci
77053a5a1b3Sopenharmony_ci        if (u->restore_volume && e->volume_valid) {
77153a5a1b3Sopenharmony_ci
77253a5a1b3Sopenharmony_ci            if (!new_data->volume_is_set) {
77353a5a1b3Sopenharmony_ci                pa_cvolume v;
77453a5a1b3Sopenharmony_ci                char buf[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
77553a5a1b3Sopenharmony_ci
77653a5a1b3Sopenharmony_ci                v = e->volume;
77753a5a1b3Sopenharmony_ci                pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map);
77853a5a1b3Sopenharmony_ci                pa_sink_new_data_set_volume(new_data, &v);
77953a5a1b3Sopenharmony_ci                pa_log_info("Restoring volume for sink %s: %s", new_data->name,
78053a5a1b3Sopenharmony_ci                            pa_cvolume_snprint_verbose(buf, sizeof(buf), &new_data->volume, &new_data->channel_map, false));
78153a5a1b3Sopenharmony_ci
78253a5a1b3Sopenharmony_ci                new_data->save_volume = true;
78353a5a1b3Sopenharmony_ci            } else
78453a5a1b3Sopenharmony_ci                pa_log_debug("Not restoring volume for sink %s, because already set.", new_data->name);
78553a5a1b3Sopenharmony_ci        }
78653a5a1b3Sopenharmony_ci
78753a5a1b3Sopenharmony_ci        if (u->restore_muted && e->muted_valid) {
78853a5a1b3Sopenharmony_ci
78953a5a1b3Sopenharmony_ci            if (!new_data->muted_is_set) {
79053a5a1b3Sopenharmony_ci                pa_sink_new_data_set_muted(new_data, e->muted);
79153a5a1b3Sopenharmony_ci                new_data->save_muted = true;
79253a5a1b3Sopenharmony_ci                pa_log_info("Restoring mute state for sink %s: %smuted", new_data->name,
79353a5a1b3Sopenharmony_ci                            new_data->muted ? "" : "un");
79453a5a1b3Sopenharmony_ci            } else
79553a5a1b3Sopenharmony_ci                pa_log_debug("Not restoring mute state for sink %s, because already set.", new_data->name);
79653a5a1b3Sopenharmony_ci        }
79753a5a1b3Sopenharmony_ci
79853a5a1b3Sopenharmony_ci        perportentry_free(e);
79953a5a1b3Sopenharmony_ci    }
80053a5a1b3Sopenharmony_ci
80153a5a1b3Sopenharmony_ci    pa_xfree(name);
80253a5a1b3Sopenharmony_ci
80353a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
80453a5a1b3Sopenharmony_ci}
80553a5a1b3Sopenharmony_ci
80653a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_port_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) {
80753a5a1b3Sopenharmony_ci    char *name;
80853a5a1b3Sopenharmony_ci    struct perportentry *e;
80953a5a1b3Sopenharmony_ci
81053a5a1b3Sopenharmony_ci    pa_assert(c);
81153a5a1b3Sopenharmony_ci    pa_assert(sink);
81253a5a1b3Sopenharmony_ci    pa_assert(u);
81353a5a1b3Sopenharmony_ci    pa_assert(u->restore_volume || u->restore_muted);
81453a5a1b3Sopenharmony_ci
81553a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("sink:%s", sink->name);
81653a5a1b3Sopenharmony_ci
81753a5a1b3Sopenharmony_ci    if ((e = perportentry_read(u, name, (sink->active_port ? sink->active_port->name : NULL)))) {
81853a5a1b3Sopenharmony_ci
81953a5a1b3Sopenharmony_ci        if (u->restore_volume && e->volume_valid) {
82053a5a1b3Sopenharmony_ci            pa_cvolume v;
82153a5a1b3Sopenharmony_ci
82253a5a1b3Sopenharmony_ci            pa_log_info("Restoring volume for sink %s.", sink->name);
82353a5a1b3Sopenharmony_ci            v = e->volume;
82453a5a1b3Sopenharmony_ci            pa_cvolume_remap(&v, &e->channel_map, &sink->channel_map);
82553a5a1b3Sopenharmony_ci            pa_sink_set_volume(sink, &v, true, false);
82653a5a1b3Sopenharmony_ci
82753a5a1b3Sopenharmony_ci            sink->save_volume = true;
82853a5a1b3Sopenharmony_ci        }
82953a5a1b3Sopenharmony_ci
83053a5a1b3Sopenharmony_ci        if (u->restore_muted && e->muted_valid) {
83153a5a1b3Sopenharmony_ci
83253a5a1b3Sopenharmony_ci            pa_log_info("Restoring mute state for sink %s.", sink->name);
83353a5a1b3Sopenharmony_ci            pa_sink_set_mute(sink, e->muted, false);
83453a5a1b3Sopenharmony_ci            sink->save_muted = true;
83553a5a1b3Sopenharmony_ci        }
83653a5a1b3Sopenharmony_ci
83753a5a1b3Sopenharmony_ci        perportentry_free(e);
83853a5a1b3Sopenharmony_ci    }
83953a5a1b3Sopenharmony_ci
84053a5a1b3Sopenharmony_ci    pa_xfree(name);
84153a5a1b3Sopenharmony_ci
84253a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
84353a5a1b3Sopenharmony_ci}
84453a5a1b3Sopenharmony_ci
84553a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) {
84653a5a1b3Sopenharmony_ci    char *name;
84753a5a1b3Sopenharmony_ci    struct perportentry *e;
84853a5a1b3Sopenharmony_ci
84953a5a1b3Sopenharmony_ci    pa_assert(c);
85053a5a1b3Sopenharmony_ci    pa_assert(sink);
85153a5a1b3Sopenharmony_ci    pa_assert(u);
85253a5a1b3Sopenharmony_ci    pa_assert(u->restore_formats);
85353a5a1b3Sopenharmony_ci
85453a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("sink:%s", sink->name);
85553a5a1b3Sopenharmony_ci
85653a5a1b3Sopenharmony_ci    if ((e = perportentry_read(u, name, (sink->active_port ? sink->active_port->name : NULL)))) {
85753a5a1b3Sopenharmony_ci
85853a5a1b3Sopenharmony_ci        if (!pa_sink_set_formats(sink, e->formats))
85953a5a1b3Sopenharmony_ci            pa_log_debug("Could not set format on sink %s", sink->name);
86053a5a1b3Sopenharmony_ci
86153a5a1b3Sopenharmony_ci        perportentry_free(e);
86253a5a1b3Sopenharmony_ci    }
86353a5a1b3Sopenharmony_ci
86453a5a1b3Sopenharmony_ci    pa_xfree(name);
86553a5a1b3Sopenharmony_ci
86653a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
86753a5a1b3Sopenharmony_ci}
86853a5a1b3Sopenharmony_ci
86953a5a1b3Sopenharmony_cistatic pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
87053a5a1b3Sopenharmony_ci    char *name;
87153a5a1b3Sopenharmony_ci    struct entry *e;
87253a5a1b3Sopenharmony_ci
87353a5a1b3Sopenharmony_ci    pa_assert(c);
87453a5a1b3Sopenharmony_ci    pa_assert(new_data);
87553a5a1b3Sopenharmony_ci    pa_assert(u);
87653a5a1b3Sopenharmony_ci    pa_assert(u->restore_port);
87753a5a1b3Sopenharmony_ci
87853a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("source:%s", new_data->name);
87953a5a1b3Sopenharmony_ci
88053a5a1b3Sopenharmony_ci    if ((e = entry_read(u, name))) {
88153a5a1b3Sopenharmony_ci
88253a5a1b3Sopenharmony_ci        if (e->port_valid) {
88353a5a1b3Sopenharmony_ci            if (!new_data->active_port) {
88453a5a1b3Sopenharmony_ci                pa_log_info("Restoring port '%s' for source %s.", pa_strnull(e->port), name);
88553a5a1b3Sopenharmony_ci                pa_source_new_data_set_port(new_data, e->port);
88653a5a1b3Sopenharmony_ci                new_data->save_port = true;
88753a5a1b3Sopenharmony_ci            } else
88853a5a1b3Sopenharmony_ci                pa_log_debug("Not restoring port for source %s, because already set.", name);
88953a5a1b3Sopenharmony_ci        }
89053a5a1b3Sopenharmony_ci
89153a5a1b3Sopenharmony_ci        entry_free(e);
89253a5a1b3Sopenharmony_ci    }
89353a5a1b3Sopenharmony_ci
89453a5a1b3Sopenharmony_ci    pa_xfree(name);
89553a5a1b3Sopenharmony_ci
89653a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
89753a5a1b3Sopenharmony_ci}
89853a5a1b3Sopenharmony_ci
89953a5a1b3Sopenharmony_cistatic pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) {
90053a5a1b3Sopenharmony_ci    char *name;
90153a5a1b3Sopenharmony_ci    struct perportentry *e;
90253a5a1b3Sopenharmony_ci
90353a5a1b3Sopenharmony_ci    pa_assert(c);
90453a5a1b3Sopenharmony_ci    pa_assert(new_data);
90553a5a1b3Sopenharmony_ci    pa_assert(u);
90653a5a1b3Sopenharmony_ci    pa_assert(u->restore_volume || u->restore_muted);
90753a5a1b3Sopenharmony_ci
90853a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("source:%s", new_data->name);
90953a5a1b3Sopenharmony_ci
91053a5a1b3Sopenharmony_ci    if ((e = perportentry_read(u, name, new_data->active_port))) {
91153a5a1b3Sopenharmony_ci
91253a5a1b3Sopenharmony_ci        if (u->restore_volume && e->volume_valid) {
91353a5a1b3Sopenharmony_ci
91453a5a1b3Sopenharmony_ci            if (!new_data->volume_is_set) {
91553a5a1b3Sopenharmony_ci                pa_cvolume v;
91653a5a1b3Sopenharmony_ci                char buf[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
91753a5a1b3Sopenharmony_ci
91853a5a1b3Sopenharmony_ci                v = e->volume;
91953a5a1b3Sopenharmony_ci                pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map);
92053a5a1b3Sopenharmony_ci                pa_source_new_data_set_volume(new_data, &v);
92153a5a1b3Sopenharmony_ci                pa_log_info("Restoring volume for source %s: %s", new_data->name,
92253a5a1b3Sopenharmony_ci                            pa_cvolume_snprint_verbose(buf, sizeof(buf), &new_data->volume, &new_data->channel_map, false));
92353a5a1b3Sopenharmony_ci
92453a5a1b3Sopenharmony_ci                new_data->save_volume = true;
92553a5a1b3Sopenharmony_ci            } else
92653a5a1b3Sopenharmony_ci                pa_log_debug("Not restoring volume for source %s, because already set.", new_data->name);
92753a5a1b3Sopenharmony_ci        }
92853a5a1b3Sopenharmony_ci
92953a5a1b3Sopenharmony_ci        if (u->restore_muted && e->muted_valid) {
93053a5a1b3Sopenharmony_ci
93153a5a1b3Sopenharmony_ci            if (!new_data->muted_is_set) {
93253a5a1b3Sopenharmony_ci                pa_source_new_data_set_muted(new_data, e->muted);
93353a5a1b3Sopenharmony_ci                new_data->save_muted = true;
93453a5a1b3Sopenharmony_ci                pa_log_info("Restoring mute state for source %s: %smuted", new_data->name,
93553a5a1b3Sopenharmony_ci                            new_data->muted ? "" : "un");
93653a5a1b3Sopenharmony_ci            } else
93753a5a1b3Sopenharmony_ci                pa_log_debug("Not restoring mute state for source %s, because already set.", new_data->name);
93853a5a1b3Sopenharmony_ci        }
93953a5a1b3Sopenharmony_ci
94053a5a1b3Sopenharmony_ci        perportentry_free(e);
94153a5a1b3Sopenharmony_ci    }
94253a5a1b3Sopenharmony_ci
94353a5a1b3Sopenharmony_ci    pa_xfree(name);
94453a5a1b3Sopenharmony_ci
94553a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
94653a5a1b3Sopenharmony_ci}
94753a5a1b3Sopenharmony_ci
94853a5a1b3Sopenharmony_cistatic pa_hook_result_t source_port_hook_callback(pa_core *c, pa_source *source, struct userdata *u) {
94953a5a1b3Sopenharmony_ci    char *name;
95053a5a1b3Sopenharmony_ci    struct perportentry *e;
95153a5a1b3Sopenharmony_ci
95253a5a1b3Sopenharmony_ci    pa_assert(c);
95353a5a1b3Sopenharmony_ci    pa_assert(source);
95453a5a1b3Sopenharmony_ci    pa_assert(u);
95553a5a1b3Sopenharmony_ci    pa_assert(u->restore_volume || u->restore_muted);
95653a5a1b3Sopenharmony_ci
95753a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("source:%s", source->name);
95853a5a1b3Sopenharmony_ci
95953a5a1b3Sopenharmony_ci    if ((e = perportentry_read(u, name, (source->active_port ? source->active_port->name : NULL)))) {
96053a5a1b3Sopenharmony_ci
96153a5a1b3Sopenharmony_ci        if (u->restore_volume && e->volume_valid) {
96253a5a1b3Sopenharmony_ci            pa_cvolume v;
96353a5a1b3Sopenharmony_ci
96453a5a1b3Sopenharmony_ci            pa_log_info("Restoring volume for source %s.", source->name);
96553a5a1b3Sopenharmony_ci            v = e->volume;
96653a5a1b3Sopenharmony_ci            pa_cvolume_remap(&v, &e->channel_map, &source->channel_map);
96753a5a1b3Sopenharmony_ci            pa_source_set_volume(source, &v, true, false);
96853a5a1b3Sopenharmony_ci
96953a5a1b3Sopenharmony_ci            source->save_volume = true;
97053a5a1b3Sopenharmony_ci        }
97153a5a1b3Sopenharmony_ci
97253a5a1b3Sopenharmony_ci        if (u->restore_muted && e->muted_valid) {
97353a5a1b3Sopenharmony_ci
97453a5a1b3Sopenharmony_ci            pa_log_info("Restoring mute state for source %s.", source->name);
97553a5a1b3Sopenharmony_ci            pa_source_set_mute(source, e->muted, false);
97653a5a1b3Sopenharmony_ci            source->save_muted = true;
97753a5a1b3Sopenharmony_ci        }
97853a5a1b3Sopenharmony_ci
97953a5a1b3Sopenharmony_ci        perportentry_free(e);
98053a5a1b3Sopenharmony_ci    }
98153a5a1b3Sopenharmony_ci
98253a5a1b3Sopenharmony_ci    pa_xfree(name);
98353a5a1b3Sopenharmony_ci
98453a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
98553a5a1b3Sopenharmony_ci}
98653a5a1b3Sopenharmony_ci
98753a5a1b3Sopenharmony_ci#define EXT_VERSION 1
98853a5a1b3Sopenharmony_ci
98953a5a1b3Sopenharmony_cistatic void read_sink_format_reply(struct userdata *u, pa_tagstruct *reply, pa_sink *sink) {
99053a5a1b3Sopenharmony_ci    struct perportentry *e;
99153a5a1b3Sopenharmony_ci    char *name;
99253a5a1b3Sopenharmony_ci
99353a5a1b3Sopenharmony_ci    pa_assert(u);
99453a5a1b3Sopenharmony_ci    pa_assert(reply);
99553a5a1b3Sopenharmony_ci    pa_assert(sink);
99653a5a1b3Sopenharmony_ci
99753a5a1b3Sopenharmony_ci    pa_tagstruct_putu32(reply, PA_DEVICE_TYPE_SINK);
99853a5a1b3Sopenharmony_ci    pa_tagstruct_putu32(reply, sink->index);
99953a5a1b3Sopenharmony_ci
100053a5a1b3Sopenharmony_ci    /* Read or create an entry */
100153a5a1b3Sopenharmony_ci    name = pa_sprintf_malloc("sink:%s", sink->name);
100253a5a1b3Sopenharmony_ci    if (!(e = perportentry_read(u, name, (sink->active_port ? sink->active_port->name : NULL)))) {
100353a5a1b3Sopenharmony_ci        /* Fake a reply with PCM encoding supported */
100453a5a1b3Sopenharmony_ci        pa_format_info *f = pa_format_info_new();
100553a5a1b3Sopenharmony_ci
100653a5a1b3Sopenharmony_ci        pa_tagstruct_putu8(reply, 1);
100753a5a1b3Sopenharmony_ci        f->encoding = PA_ENCODING_PCM;
100853a5a1b3Sopenharmony_ci        pa_tagstruct_put_format_info(reply, f);
100953a5a1b3Sopenharmony_ci
101053a5a1b3Sopenharmony_ci        pa_format_info_free(f);
101153a5a1b3Sopenharmony_ci    } else {
101253a5a1b3Sopenharmony_ci        uint32_t idx;
101353a5a1b3Sopenharmony_ci        pa_format_info *f;
101453a5a1b3Sopenharmony_ci
101553a5a1b3Sopenharmony_ci        /* Write all the formats from the entry to the reply */
101653a5a1b3Sopenharmony_ci        pa_tagstruct_putu8(reply, pa_idxset_size(e->formats));
101753a5a1b3Sopenharmony_ci        PA_IDXSET_FOREACH(f, e->formats, idx) {
101853a5a1b3Sopenharmony_ci            pa_tagstruct_put_format_info(reply, f);
101953a5a1b3Sopenharmony_ci        }
102053a5a1b3Sopenharmony_ci        perportentry_free(e);
102153a5a1b3Sopenharmony_ci    }
102253a5a1b3Sopenharmony_ci    pa_xfree(name);
102353a5a1b3Sopenharmony_ci}
102453a5a1b3Sopenharmony_ci
102553a5a1b3Sopenharmony_cistatic int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) {
102653a5a1b3Sopenharmony_ci    struct userdata *u;
102753a5a1b3Sopenharmony_ci    uint32_t command;
102853a5a1b3Sopenharmony_ci    pa_tagstruct *reply = NULL;
102953a5a1b3Sopenharmony_ci
103053a5a1b3Sopenharmony_ci    pa_assert(p);
103153a5a1b3Sopenharmony_ci    pa_assert(m);
103253a5a1b3Sopenharmony_ci    pa_assert(c);
103353a5a1b3Sopenharmony_ci    pa_assert(t);
103453a5a1b3Sopenharmony_ci
103553a5a1b3Sopenharmony_ci    u = m->userdata;
103653a5a1b3Sopenharmony_ci
103753a5a1b3Sopenharmony_ci    if (pa_tagstruct_getu32(t, &command) < 0)
103853a5a1b3Sopenharmony_ci        goto fail;
103953a5a1b3Sopenharmony_ci
104053a5a1b3Sopenharmony_ci    reply = pa_tagstruct_new();
104153a5a1b3Sopenharmony_ci    pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
104253a5a1b3Sopenharmony_ci    pa_tagstruct_putu32(reply, tag);
104353a5a1b3Sopenharmony_ci
104453a5a1b3Sopenharmony_ci    switch (command) {
104553a5a1b3Sopenharmony_ci        case SUBCOMMAND_TEST: {
104653a5a1b3Sopenharmony_ci            if (!pa_tagstruct_eof(t))
104753a5a1b3Sopenharmony_ci                goto fail;
104853a5a1b3Sopenharmony_ci
104953a5a1b3Sopenharmony_ci            pa_tagstruct_putu32(reply, EXT_VERSION);
105053a5a1b3Sopenharmony_ci            break;
105153a5a1b3Sopenharmony_ci        }
105253a5a1b3Sopenharmony_ci
105353a5a1b3Sopenharmony_ci        case SUBCOMMAND_SUBSCRIBE: {
105453a5a1b3Sopenharmony_ci
105553a5a1b3Sopenharmony_ci            bool enabled;
105653a5a1b3Sopenharmony_ci
105753a5a1b3Sopenharmony_ci            if (pa_tagstruct_get_boolean(t, &enabled) < 0 ||
105853a5a1b3Sopenharmony_ci                !pa_tagstruct_eof(t))
105953a5a1b3Sopenharmony_ci                goto fail;
106053a5a1b3Sopenharmony_ci
106153a5a1b3Sopenharmony_ci            if (enabled)
106253a5a1b3Sopenharmony_ci                pa_idxset_put(u->subscribed, c, NULL);
106353a5a1b3Sopenharmony_ci            else
106453a5a1b3Sopenharmony_ci                pa_idxset_remove_by_data(u->subscribed, c, NULL);
106553a5a1b3Sopenharmony_ci
106653a5a1b3Sopenharmony_ci            break;
106753a5a1b3Sopenharmony_ci        }
106853a5a1b3Sopenharmony_ci
106953a5a1b3Sopenharmony_ci        case SUBCOMMAND_READ_FORMATS_ALL: {
107053a5a1b3Sopenharmony_ci            pa_sink *sink;
107153a5a1b3Sopenharmony_ci            uint32_t idx;
107253a5a1b3Sopenharmony_ci
107353a5a1b3Sopenharmony_ci            if (!pa_tagstruct_eof(t))
107453a5a1b3Sopenharmony_ci                goto fail;
107553a5a1b3Sopenharmony_ci
107653a5a1b3Sopenharmony_ci            PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
107753a5a1b3Sopenharmony_ci                read_sink_format_reply(u, reply, sink);
107853a5a1b3Sopenharmony_ci            }
107953a5a1b3Sopenharmony_ci
108053a5a1b3Sopenharmony_ci            break;
108153a5a1b3Sopenharmony_ci        }
108253a5a1b3Sopenharmony_ci        case SUBCOMMAND_READ_FORMATS: {
108353a5a1b3Sopenharmony_ci            pa_device_type_t type;
108453a5a1b3Sopenharmony_ci            uint32_t sink_index;
108553a5a1b3Sopenharmony_ci            pa_sink *sink;
108653a5a1b3Sopenharmony_ci
108753a5a1b3Sopenharmony_ci            pa_assert(reply);
108853a5a1b3Sopenharmony_ci
108953a5a1b3Sopenharmony_ci            /* Get the sink index and the number of formats from the tagstruct */
109053a5a1b3Sopenharmony_ci            if (pa_tagstruct_getu32(t, &type) < 0 ||
109153a5a1b3Sopenharmony_ci                pa_tagstruct_getu32(t, &sink_index) < 0)
109253a5a1b3Sopenharmony_ci                goto fail;
109353a5a1b3Sopenharmony_ci
109453a5a1b3Sopenharmony_ci            if (type != PA_DEVICE_TYPE_SINK) {
109553a5a1b3Sopenharmony_ci                pa_log("Device format reading is only supported on sinks");
109653a5a1b3Sopenharmony_ci                goto fail;
109753a5a1b3Sopenharmony_ci            }
109853a5a1b3Sopenharmony_ci
109953a5a1b3Sopenharmony_ci            if (!pa_tagstruct_eof(t))
110053a5a1b3Sopenharmony_ci                goto fail;
110153a5a1b3Sopenharmony_ci
110253a5a1b3Sopenharmony_ci            /* Now find our sink */
110353a5a1b3Sopenharmony_ci            if (!(sink = pa_idxset_get_by_index(u->core->sinks, sink_index)))
110453a5a1b3Sopenharmony_ci                goto fail;
110553a5a1b3Sopenharmony_ci
110653a5a1b3Sopenharmony_ci            read_sink_format_reply(u, reply, sink);
110753a5a1b3Sopenharmony_ci
110853a5a1b3Sopenharmony_ci            break;
110953a5a1b3Sopenharmony_ci        }
111053a5a1b3Sopenharmony_ci
111153a5a1b3Sopenharmony_ci        case SUBCOMMAND_SAVE_FORMATS: {
111253a5a1b3Sopenharmony_ci
111353a5a1b3Sopenharmony_ci            struct perportentry *e;
111453a5a1b3Sopenharmony_ci            pa_device_type_t type;
111553a5a1b3Sopenharmony_ci            uint32_t sink_index;
111653a5a1b3Sopenharmony_ci            char *name;
111753a5a1b3Sopenharmony_ci            pa_sink *sink;
111853a5a1b3Sopenharmony_ci            uint8_t i, n_formats;
111953a5a1b3Sopenharmony_ci
112053a5a1b3Sopenharmony_ci            /* Get the sink index and the number of formats from the tagstruct */
112153a5a1b3Sopenharmony_ci            if (pa_tagstruct_getu32(t, &type) < 0 ||
112253a5a1b3Sopenharmony_ci                pa_tagstruct_getu32(t, &sink_index) < 0 ||
112353a5a1b3Sopenharmony_ci                pa_tagstruct_getu8(t, &n_formats) < 0 || n_formats < 1) {
112453a5a1b3Sopenharmony_ci
112553a5a1b3Sopenharmony_ci                goto fail;
112653a5a1b3Sopenharmony_ci            }
112753a5a1b3Sopenharmony_ci
112853a5a1b3Sopenharmony_ci            if (type != PA_DEVICE_TYPE_SINK) {
112953a5a1b3Sopenharmony_ci                pa_log("Device format saving is only supported on sinks");
113053a5a1b3Sopenharmony_ci                goto fail;
113153a5a1b3Sopenharmony_ci            }
113253a5a1b3Sopenharmony_ci
113353a5a1b3Sopenharmony_ci            /* Now find our sink */
113453a5a1b3Sopenharmony_ci            if (!(sink = pa_idxset_get_by_index(u->core->sinks, sink_index))) {
113553a5a1b3Sopenharmony_ci                pa_log("Could not find sink #%d", sink_index);
113653a5a1b3Sopenharmony_ci                goto fail;
113753a5a1b3Sopenharmony_ci            }
113853a5a1b3Sopenharmony_ci
113953a5a1b3Sopenharmony_ci            /* Read or create an entry */
114053a5a1b3Sopenharmony_ci            name = pa_sprintf_malloc("sink:%s", sink->name);
114153a5a1b3Sopenharmony_ci            if (!(e = perportentry_read(u, name, (sink->active_port ? sink->active_port->name : NULL))))
114253a5a1b3Sopenharmony_ci                e = perportentry_new(false);
114353a5a1b3Sopenharmony_ci            else {
114453a5a1b3Sopenharmony_ci                /* Clean out any saved formats */
114553a5a1b3Sopenharmony_ci                pa_idxset_free(e->formats, (pa_free_cb_t) pa_format_info_free);
114653a5a1b3Sopenharmony_ci                e->formats = pa_idxset_new(NULL, NULL);
114753a5a1b3Sopenharmony_ci            }
114853a5a1b3Sopenharmony_ci
114953a5a1b3Sopenharmony_ci            /* Read all the formats from our tagstruct */
115053a5a1b3Sopenharmony_ci            for (i = 0; i < n_formats; ++i) {
115153a5a1b3Sopenharmony_ci                pa_format_info *f = pa_format_info_new();
115253a5a1b3Sopenharmony_ci                if (pa_tagstruct_get_format_info(t, f) < 0) {
115353a5a1b3Sopenharmony_ci                    pa_format_info_free(f);
115453a5a1b3Sopenharmony_ci                    perportentry_free(e);
115553a5a1b3Sopenharmony_ci                    pa_xfree(name);
115653a5a1b3Sopenharmony_ci                    goto fail;
115753a5a1b3Sopenharmony_ci                }
115853a5a1b3Sopenharmony_ci                pa_idxset_put(e->formats, f, NULL);
115953a5a1b3Sopenharmony_ci            }
116053a5a1b3Sopenharmony_ci
116153a5a1b3Sopenharmony_ci            if (!pa_tagstruct_eof(t)) {
116253a5a1b3Sopenharmony_ci                perportentry_free(e);
116353a5a1b3Sopenharmony_ci                pa_xfree(name);
116453a5a1b3Sopenharmony_ci                goto fail;
116553a5a1b3Sopenharmony_ci            }
116653a5a1b3Sopenharmony_ci
116753a5a1b3Sopenharmony_ci            if (pa_sink_set_formats(sink, e->formats) && perportentry_write(u, name, (sink->active_port ? sink->active_port->name : NULL), e))
116853a5a1b3Sopenharmony_ci                trigger_save(u, type, sink_index);
116953a5a1b3Sopenharmony_ci            else
117053a5a1b3Sopenharmony_ci                pa_log_warn("Could not save format info for sink %s", sink->name);
117153a5a1b3Sopenharmony_ci
117253a5a1b3Sopenharmony_ci            pa_xfree(name);
117353a5a1b3Sopenharmony_ci            perportentry_free(e);
117453a5a1b3Sopenharmony_ci
117553a5a1b3Sopenharmony_ci            break;
117653a5a1b3Sopenharmony_ci        }
117753a5a1b3Sopenharmony_ci
117853a5a1b3Sopenharmony_ci        default:
117953a5a1b3Sopenharmony_ci            goto fail;
118053a5a1b3Sopenharmony_ci    }
118153a5a1b3Sopenharmony_ci
118253a5a1b3Sopenharmony_ci    pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply);
118353a5a1b3Sopenharmony_ci    return 0;
118453a5a1b3Sopenharmony_ci
118553a5a1b3Sopenharmony_cifail:
118653a5a1b3Sopenharmony_ci
118753a5a1b3Sopenharmony_ci    if (reply)
118853a5a1b3Sopenharmony_ci        pa_tagstruct_free(reply);
118953a5a1b3Sopenharmony_ci
119053a5a1b3Sopenharmony_ci    return -1;
119153a5a1b3Sopenharmony_ci}
119253a5a1b3Sopenharmony_ci
119353a5a1b3Sopenharmony_cistatic pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) {
119453a5a1b3Sopenharmony_ci    pa_assert(p);
119553a5a1b3Sopenharmony_ci    pa_assert(c);
119653a5a1b3Sopenharmony_ci    pa_assert(u);
119753a5a1b3Sopenharmony_ci
119853a5a1b3Sopenharmony_ci    pa_idxset_remove_by_data(u->subscribed, c, NULL);
119953a5a1b3Sopenharmony_ci    return PA_HOOK_OK;
120053a5a1b3Sopenharmony_ci}
120153a5a1b3Sopenharmony_ci
120253a5a1b3Sopenharmony_ciint pa__init(pa_module*m) {
120353a5a1b3Sopenharmony_ci    pa_modargs *ma = NULL;
120453a5a1b3Sopenharmony_ci    struct userdata *u;
120553a5a1b3Sopenharmony_ci    char *state_path;
120653a5a1b3Sopenharmony_ci    pa_sink *sink;
120753a5a1b3Sopenharmony_ci    pa_source *source;
120853a5a1b3Sopenharmony_ci    uint32_t idx;
120953a5a1b3Sopenharmony_ci    bool restore_volume = true, restore_muted = true, restore_port = true, restore_formats = true;
121053a5a1b3Sopenharmony_ci
121153a5a1b3Sopenharmony_ci    pa_assert(m);
121253a5a1b3Sopenharmony_ci
121353a5a1b3Sopenharmony_ci    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
121453a5a1b3Sopenharmony_ci        pa_log("Failed to parse module arguments");
121553a5a1b3Sopenharmony_ci        goto fail;
121653a5a1b3Sopenharmony_ci    }
121753a5a1b3Sopenharmony_ci
121853a5a1b3Sopenharmony_ci    if (pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0 ||
121953a5a1b3Sopenharmony_ci        pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0 ||
122053a5a1b3Sopenharmony_ci        pa_modargs_get_value_boolean(ma, "restore_port", &restore_port) < 0 ||
122153a5a1b3Sopenharmony_ci        pa_modargs_get_value_boolean(ma, "restore_formats", &restore_formats) < 0) {
122253a5a1b3Sopenharmony_ci        pa_log("restore_port, restore_volume, restore_muted and restore_formats expect boolean arguments");
122353a5a1b3Sopenharmony_ci        goto fail;
122453a5a1b3Sopenharmony_ci    }
122553a5a1b3Sopenharmony_ci
122653a5a1b3Sopenharmony_ci    if (!restore_muted && !restore_volume && !restore_port && !restore_formats)
122753a5a1b3Sopenharmony_ci        pa_log_warn("Neither restoring volume, nor restoring muted, nor restoring port enabled!");
122853a5a1b3Sopenharmony_ci
122953a5a1b3Sopenharmony_ci    m->userdata = u = pa_xnew0(struct userdata, 1);
123053a5a1b3Sopenharmony_ci    u->core = m->core;
123153a5a1b3Sopenharmony_ci    u->module = m;
123253a5a1b3Sopenharmony_ci    u->restore_volume = restore_volume;
123353a5a1b3Sopenharmony_ci    u->restore_muted = restore_muted;
123453a5a1b3Sopenharmony_ci    u->restore_port = restore_port;
123553a5a1b3Sopenharmony_ci    u->restore_formats = restore_formats;
123653a5a1b3Sopenharmony_ci
123753a5a1b3Sopenharmony_ci    u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
123853a5a1b3Sopenharmony_ci
123953a5a1b3Sopenharmony_ci    u->protocol = pa_native_protocol_get(m->core);
124053a5a1b3Sopenharmony_ci    pa_native_protocol_install_ext(u->protocol, m, extension_cb);
124153a5a1b3Sopenharmony_ci
124253a5a1b3Sopenharmony_ci    pa_module_hook_connect(m, &pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_CONNECTION_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) connection_unlink_hook_cb, u);
124353a5a1b3Sopenharmony_ci
124453a5a1b3Sopenharmony_ci    u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u);
124553a5a1b3Sopenharmony_ci
124653a5a1b3Sopenharmony_ci    if (restore_port) {
124753a5a1b3Sopenharmony_ci        pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_new_hook_callback, u);
124853a5a1b3Sopenharmony_ci        pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u);
124953a5a1b3Sopenharmony_ci    }
125053a5a1b3Sopenharmony_ci
125153a5a1b3Sopenharmony_ci    if (restore_muted || restore_volume) {
125253a5a1b3Sopenharmony_ci        pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_fixate_hook_callback, u);
125353a5a1b3Sopenharmony_ci        pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_fixate_hook_callback, u);
125453a5a1b3Sopenharmony_ci
125553a5a1b3Sopenharmony_ci        pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) sink_port_hook_callback, u);
125653a5a1b3Sopenharmony_ci        pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) source_port_hook_callback, u);
125753a5a1b3Sopenharmony_ci    }
125853a5a1b3Sopenharmony_ci
125953a5a1b3Sopenharmony_ci    if (restore_formats)
126053a5a1b3Sopenharmony_ci        pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_put_hook_callback, u);
126153a5a1b3Sopenharmony_ci
126253a5a1b3Sopenharmony_ci    if (!(state_path = pa_state_path(NULL, true)))
126353a5a1b3Sopenharmony_ci        goto fail;
126453a5a1b3Sopenharmony_ci
126553a5a1b3Sopenharmony_ci    if (!(u->database = pa_database_open(state_path, "device-volumes", true, true))) {
126653a5a1b3Sopenharmony_ci        pa_xfree(state_path);
126753a5a1b3Sopenharmony_ci        goto fail;
126853a5a1b3Sopenharmony_ci    }
126953a5a1b3Sopenharmony_ci
127053a5a1b3Sopenharmony_ci    pa_xfree(state_path);
127153a5a1b3Sopenharmony_ci
127253a5a1b3Sopenharmony_ci    PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
127353a5a1b3Sopenharmony_ci        subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
127453a5a1b3Sopenharmony_ci
127553a5a1b3Sopenharmony_ci    PA_IDXSET_FOREACH(source, m->core->sources, idx)
127653a5a1b3Sopenharmony_ci        subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u);
127753a5a1b3Sopenharmony_ci
127853a5a1b3Sopenharmony_ci    pa_modargs_free(ma);
127953a5a1b3Sopenharmony_ci    return 0;
128053a5a1b3Sopenharmony_ci
128153a5a1b3Sopenharmony_cifail:
128253a5a1b3Sopenharmony_ci    pa__done(m);
128353a5a1b3Sopenharmony_ci
128453a5a1b3Sopenharmony_ci    if (ma)
128553a5a1b3Sopenharmony_ci        pa_modargs_free(ma);
128653a5a1b3Sopenharmony_ci
128753a5a1b3Sopenharmony_ci    return -1;
128853a5a1b3Sopenharmony_ci}
128953a5a1b3Sopenharmony_ci
129053a5a1b3Sopenharmony_civoid pa__done(pa_module*m) {
129153a5a1b3Sopenharmony_ci    struct userdata* u;
129253a5a1b3Sopenharmony_ci
129353a5a1b3Sopenharmony_ci    pa_assert(m);
129453a5a1b3Sopenharmony_ci
129553a5a1b3Sopenharmony_ci    if (!(u = m->userdata))
129653a5a1b3Sopenharmony_ci        return;
129753a5a1b3Sopenharmony_ci
129853a5a1b3Sopenharmony_ci    if (u->subscription)
129953a5a1b3Sopenharmony_ci        pa_subscription_free(u->subscription);
130053a5a1b3Sopenharmony_ci
130153a5a1b3Sopenharmony_ci    if (u->save_time_event) {
130253a5a1b3Sopenharmony_ci        u->core->mainloop->time_free(u->save_time_event);
130353a5a1b3Sopenharmony_ci        pa_database_sync(u->database);
130453a5a1b3Sopenharmony_ci    }
130553a5a1b3Sopenharmony_ci
130653a5a1b3Sopenharmony_ci    if (u->database)
130753a5a1b3Sopenharmony_ci        pa_database_close(u->database);
130853a5a1b3Sopenharmony_ci
130953a5a1b3Sopenharmony_ci    if (u->protocol) {
131053a5a1b3Sopenharmony_ci        pa_native_protocol_remove_ext(u->protocol, m);
131153a5a1b3Sopenharmony_ci        pa_native_protocol_unref(u->protocol);
131253a5a1b3Sopenharmony_ci    }
131353a5a1b3Sopenharmony_ci
131453a5a1b3Sopenharmony_ci    if (u->subscribed)
131553a5a1b3Sopenharmony_ci        pa_idxset_free(u->subscribed, NULL);
131653a5a1b3Sopenharmony_ci
131753a5a1b3Sopenharmony_ci    pa_xfree(u);
131853a5a1b3Sopenharmony_ci}
1319