[PATCH 6/6] device-restore: Add support for "factory settings".

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The idea of the feature is that a device vendor may want to initialize
device volumes to known good defaults. The factory settings are read
from a configuration that contains entries that look like this:

[Entry sink:foodevicename:barportname]
volume = front-left:70%,front-right:70%

The settings are written to the device-restore database during module
loading, but only if the database doesn't already contain an entry
with matching key, so user choices are not overwritten.
---
 src/modules/module-device-restore.c |  143 ++++++++++++++++++++++++++++++++++-
 1 file changed, 142 insertions(+), 1 deletion(-)

diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c
index e334ff6..b1b2d38 100644
--- a/src/modules/module-device-restore.c
+++ b/src/modules/module-device-restore.c
@@ -53,6 +53,7 @@
 #include <pulsecore/pstream-util.h>
 #include <pulsecore/database.h>
 #include <pulsecore/tagstruct.h>
+#include <pulsecore/conf-parser.h>
 
 #include "module-device-restore-symdef.h"
 
@@ -64,15 +65,19 @@ PA_MODULE_USAGE(
         "restore_port=<Save/restore port?> "
         "restore_volume=<Save/restore volumes?> "
         "restore_muted=<Save/restore muted states?> "
-        "restore_formats=<Save/restore saved formats?>");
+        "restore_formats=<Save/restore saved formats?> "
+        "factory_settings=<file>");
 
 #define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
+#define DEFAULT_FACTORY_SETTINGS_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "device-restore-factory-settings.conf"
+#define DEFAULT_FACTORY_SETTINGS_FILE_USER "device-restore-factory-settings.conf"
 
 static const char* const valid_modargs[] = {
     "restore_volume",
     "restore_muted",
     "restore_port",
     "restore_formats",
+    "factory_settings",
     NULL
 };
 
@@ -131,6 +136,13 @@ struct perportentry {
     pa_idxset *formats;
 };
 
+struct factory_settings_entry {
+    char *key;
+    pa_cvolume volume;
+    pa_channel_map channel_map;
+    bool volume_valid;
+};
+
 static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
     struct userdata *u = userdata;
 
@@ -1230,6 +1242,130 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati
     return PA_HOOK_OK;
 }
 
+static struct factory_settings_entry *factory_settings_entry_get(pa_hashmap *factory_settings, const char *section) {
+    struct factory_settings_entry *entry;
+
+    pa_assert(factory_settings);
+
+    if (!section || !pa_startswith(section, "Entry "))
+        return NULL;
+
+    section += 6;
+
+    if ((entry = pa_hashmap_get(factory_settings, section)))
+        return entry;
+
+    entry = pa_xnew0(struct factory_settings_entry, 1);
+    entry->key = pa_xstrdup(section);
+    pa_hashmap_put(factory_settings, entry->key, entry);
+
+    return entry;
+}
+
+static int parse_volume_cb(pa_config_parser_state *state) {
+    pa_hashmap *factory_settings;
+    struct factory_settings_entry *entry;
+
+    pa_assert(state);
+
+    factory_settings = state->userdata;
+
+    if (!(entry = factory_settings_entry_get(factory_settings, state->section))) {
+        pa_log("[%s:%u] volume option not expected in section '%s'.", state->filename, state->lineno, pa_strnull(state->section));
+        return -1;
+    }
+
+    if (pa_parse_cvolume(state->rvalue, &entry->volume, &entry->channel_map) < 0) {
+        pa_log("[%s:%u] failed to parse volume.", state->filename, state->lineno);
+        return -1;
+    }
+
+    entry->volume_valid = true;
+
+    return 0;
+}
+
+static int load_factory_settings(struct userdata *u, const char *filename) {
+    FILE *f = NULL;
+    char *fn;
+    int r;
+    pa_hashmap *factory_settings;
+    struct factory_settings_entry *entry;
+    void *state;
+
+    pa_config_item config_items[] = {
+        { "volume", parse_volume_cb, NULL, NULL },
+        { NULL,     NULL,            NULL, NULL }
+    };
+
+    if (!filename && !(f = pa_open_config_file(DEFAULT_FACTORY_SETTINGS_FILE, DEFAULT_FACTORY_SETTINGS_FILE_USER, NULL, &fn))) {
+        if (errno != ENOENT)
+            return -1;
+
+        return 0;
+    }
+
+    /* pa_config_parse() would open the file for us if we didn't open it
+     * ourselves, but if pa_config_parse() has to open the file, it will not
+     * return an error if the file is not found. That's supposedly useful in
+     * other situations, but we want to report failure if filename was
+     * explicitly configured and the file is not found, so we have to open the
+     * file ourselves. */
+    if (filename && !(f = pa_fopen_cloexec(filename, "r"))) {
+        pa_log("Failed to open '%s': %s", filename, pa_cstrerror(errno));
+        return -1;
+    }
+
+    if (!filename)
+        filename = fn;
+
+    factory_settings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    if ((r = pa_config_parse(filename, f, config_items, NULL, factory_settings)) < 0)
+        goto finish;
+
+    PA_HASHMAP_FOREACH(entry, factory_settings, state) {
+        struct perportentry *ppe;
+
+        if (!entry->volume_valid)
+            continue;
+
+        ppe = perportentry_read_raw_name(u, entry->key);
+
+        if (!ppe || !ppe->volume_valid) {
+            if (!ppe)
+                ppe = perportentry_new(FALSE);
+
+            ppe->volume = entry->volume;
+            ppe->channel_map = entry->channel_map;
+            ppe->volume_valid = true;
+
+            perportentry_write_raw_name(u, entry->key, ppe);
+            pa_log_debug("Added factory settings to the database for entry %s.", entry->key);
+
+            /* Note that the device type doesn't matter when we give PA_INVALID_INDEX. */
+            trigger_save(u, PA_DEVICE_TYPE_SINK, PA_INVALID_INDEX);
+        }
+
+        perportentry_free(ppe);
+    }
+
+finish:
+
+    while ((entry = pa_hashmap_steal_first(factory_settings))) {
+        pa_xfree(entry->key);
+        pa_xfree(entry);
+    }
+
+    pa_hashmap_free(factory_settings, NULL, NULL);
+    pa_xfree(fn);
+
+    if (f)
+        fclose(f);
+
+    return r;
+}
+
 int pa__init(pa_module*m) {
     pa_modargs *ma = NULL;
     struct userdata *u;
@@ -1302,6 +1438,11 @@ int pa__init(pa_module*m) {
     pa_log_info("Successfully opened database file '%s'.", fname);
     pa_xfree(fname);
 
+    if (load_factory_settings(u, pa_modargs_get_value(ma, "factory_settings", NULL)) < 0) {
+        pa_log("Loading factory settings failed.");
+        goto fail;
+    }
+
     PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
         subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
 
-- 
1.7.10.4



[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux