diff --git a/src/tools/meson.build b/src/tools/meson.build index 808f63103..b91038462 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -1,5 +1,6 @@ tools_sources = [ [ 'pw-mon', [ 'pw-mon.c' ] ], + [ 'pw-config', [ 'pw-config.c' ] ], [ 'pw-dot', [ 'pw-dot.c' ] ], [ 'pw-dump', [ 'pw-dump.c' ] ], [ 'pw-profiler', [ 'pw-profiler.c' ] ], diff --git a/src/tools/pw-config.c b/src/tools/pw-config.c new file mode 100644 index 000000000..6d1b67a8c --- /dev/null +++ b/src/tools/pw-config.c @@ -0,0 +1,228 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include "pipewire/pipewire.h" +#include "pipewire/log.h" + +#define DEFAULT_NAME "pipewire.conf" +#define DEFAULT_PREFIX "" + +struct data { + const char *opt_name; + const char *opt_prefix; + const char *opt_cmd; + bool opt_recurse; + bool opt_newline; + struct pw_properties *conf; + struct pw_properties *assemble; + int count; + bool array; +}; + +static void print_all_properties(struct data *d, struct pw_properties *props) +{ + pw_properties_serialize_dict(stdout, + &props->dict, + (d->opt_newline ? PW_PROPERTIES_FLAG_NL : 0) | + (d->opt_recurse ? PW_PROPERTIES_FLAG_RECURSE : 0) | + (d->array ? PW_PROPERTIES_FLAG_ARRAY : 0) | + PW_PROPERTIES_FLAG_ENCLOSE); + fprintf(stdout, "\n"); +} + +static void list_paths(struct data *d) +{ + const struct spa_dict_item *it; + + spa_dict_for_each(it, &d->conf->dict) { + if (spa_strstartswith(it->key, "config.path")) { + pw_properties_set(d->assemble, it->key, it->value); + } + if (spa_strstartswith(it->key, "override.") && + spa_strendswith(it->key, ".config.path")) { + pw_properties_set(d->assemble, it->key, it->value); + } + } +} + +static int do_merge_section(void *data, const char *location, const char *section, + const char *str, size_t len) +{ + struct data *d = data; + struct spa_json it[2]; + int l; + const char *value; + + spa_json_init(&it[0], str, len); + if ((l = spa_json_next(&it[0], &value)) <= 0) + return 0; + + if (spa_json_is_array(value, l)) { + char key[128]; + + spa_json_enter(&it[0], &it[1]); + while ((l = spa_json_next(&it[1], &value)) > 0) { + if (spa_json_is_container(value, l)) + l = spa_json_container_len(&it[1], value, l); + + snprintf(key, sizeof(key), "%d-%s", d->count++, location); + pw_properties_setf(d->assemble, key, "%.*s", l, value); + } + d->array = true; + } + else if (spa_json_is_object(value, l)) { + pw_properties_update_string(d->assemble, str, len); + } + return 0; +} + +static int do_list_section(void *data, const char *location, const char *section, + const char *str, size_t len) +{ + struct data *d = data; + char key[128]; + snprintf(key, sizeof(key), "%d-%s", d->count++, location); + pw_properties_setf(d->assemble, key, "%.*s", (int)len, str); + return 0; +} + +static void section_for_each(struct data *d, const char *section, + int (*callback) (void *data, const char *location, const char *section, + const char *str, size_t len)) +{ + const char *str; + char key[128]; + + pw_conf_section_for_each(&d->conf->dict, section, callback, d); + str = pw_properties_get(d->assemble, "config.ext"); + if (str != NULL) { + snprintf(key, sizeof(key), "%s.%s", section, str); + pw_conf_section_for_each(&d->conf->dict, key, callback, d); + } +} + +static void show_help(const char *name, bool error) +{ + fprintf(error ? stderr : stdout, "%1$s : PipeWire config manager.\n" + "Usage:\n" + " %1$s [options] paths List config paths (default action)\n" + " %1$s [options] list [SECTION] List config section\n" + " %1$s [options] merge SECTION Merge a config section\n\n" + "Options:\n" + " -h, --help Show this help\n" + " --version Show version\n" + " -n, --name Config Name (default '%2$s')\n" + " -p, --prefix Config Prefix (default '%3$s')\n" + " -L, --no-newline Omit newline after values\n" + " -r, --recurse Dump values recursively\n", + name, DEFAULT_NAME, DEFAULT_PREFIX); +} + +int main(int argc, char *argv[]) +{ + struct data d = { 0, }; + struct pw_properties *props = NULL; + int res = 0, c; + static const struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "name", required_argument, NULL, 'n' }, + { "prefix", required_argument, NULL, 'p' }, + { "no-newline", no_argument, NULL, 'L' }, + { "recurse", no_argument, NULL, 'r' }, + { NULL, 0, NULL, 0} + }; + + d.opt_name = DEFAULT_NAME; + d.opt_prefix = NULL; + d.opt_recurse = false; + d.opt_newline = true; + d.opt_cmd = "paths"; + + pw_init(&argc, &argv); + + while ((c = getopt_long(argc, argv, "hVn:p:Lr", long_options, NULL)) != -1) { + switch (c) { + case 'h': + show_help(argv[0], false); + return 0; + case 'V': + printf("%s\n" + "Compiled with libpipewire %s\n" + "Linked with libpipewire %s\n", + argv[0], + pw_get_headers_version(), + pw_get_library_version()); + return 0; + case 'n': + d.opt_name = optarg; + break; + case 'p': + d.opt_prefix = optarg; + break; + case 'L': + d.opt_newline = false; + break; + case 'r': + d.opt_recurse = true; + break; + default: + show_help(argv[0], true); + return -1; + } + } + + if (optind < argc) + d.opt_cmd = argv[optind++]; + + props = pw_properties_new( + PW_KEY_CONFIG_NAME, d.opt_name, + PW_KEY_CONFIG_PREFIX, d.opt_prefix, + NULL); + + d.conf = pw_properties_new(NULL, NULL); + if ((res = pw_conf_load_conf_for_context (props, d.conf)) < 0) { + fprintf(stderr, "error loading config: %s\n", spa_strerror(res)); + goto done; + } + + d.assemble = pw_properties_new(NULL, NULL); + + if (spa_streq(d.opt_cmd, "paths")) { + list_paths(&d); + } + else if (spa_streq(d.opt_cmd, "list")) { + if (optind == argc) { + pw_properties_update(d.assemble, &d.conf->dict); + } else { + section_for_each(&d, argv[optind++], do_list_section); + } + } + else if (spa_streq(d.opt_cmd, "merge")) { + if (optind == argc) { + fprintf(stderr, "%s requires a section\n", d.opt_cmd); + res = -EINVAL; + goto done; + } + section_for_each(&d, argv[optind++], do_merge_section); + } + print_all_properties(&d, d.assemble); + +done: + pw_properties_free(d.conf); + pw_properties_free(d.assemble); + pw_properties_free(props); + + pw_deinit(); + return res; +}