diff --git a/Makefile.in b/Makefile.in index c9b2df02f..695e7704e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -14,7 +14,6 @@ run: all SPA_PLUGIN_DIR=@BUILD_ROOT@/spa/plugins \ PIPEWIRE_MODULE_DIR=@BUILD_ROOT@/src/modules/ \ PATH=@BUILD_ROOT@/src/examples:$(PATH) \ - PIPEWIRE_CONFIG_FILE=@BUILD_ROOT@/src/daemon/pipewire-uninstalled.conf \ PIPEWIRE_CONFIG_DIR=@SOURCE_ROOT@/src/daemon/ \ ACP_PATHS_DIR=@SOURCE_ROOT@/spa/plugins/alsa/mixer/paths \ ACP_PROFILES_DIR=@SOURCE_ROOT@/spa/plugins/alsa/mixer/profile-sets \ diff --git a/pw-uninstalled.sh b/pw-uninstalled.sh index d137232db..d7717d009 100755 --- a/pw-uninstalled.sh +++ b/pw-uninstalled.sh @@ -32,7 +32,6 @@ if [ ! -d ${BUILDDIR} ]; then fi # the config file read by the daemon -export PIPEWIRE_CONFIG_FILE="${BUILDDIR}/src/daemon/pipewire-uninstalled.conf" export PIPEWIRE_CONFIG_DIR="src/daemon/" # the directory with SPA plugins export SPA_PLUGIN_DIR="${BUILDDIR}/spa/plugins" diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in new file mode 100644 index 000000000..6ce408369 --- /dev/null +++ b/src/daemon/client.conf.in @@ -0,0 +1,53 @@ +# Daemon config file for PipeWire clients version @VERSION@ # + +properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #log.level = 2 +} + +spa-libs = { + ## = + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +modules = { + ## = { [args = { = ... }] + # [flags = [ [ifexists] [nofail] ]} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + + # The native communication protocol. + libpipewire-module-protocol-native = null + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + libpipewire-module-client-node = null + + # Allows creating devices that run in the context of the + # client. Is used by the session manager. + libpipewire-module-client-device = null + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + libpipewire-module-adapter = null + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + libpipewire-module-metadata = null + + # Provides factories to make session manager objects. + libpipewire-module-session-manager = null +} diff --git a/src/daemon/main.c b/src/daemon/main.c index 8d465299c..8fd58f40a 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -48,311 +48,8 @@ struct data { struct pw_main_loop *loop; const char *daemon_name; - struct pw_properties *conf; }; -static int load_config(struct data *d) -{ - const char *path; - char filename[PATH_MAX], *data; - struct stat sbuf; - int fd; - - path = getenv("PIPEWIRE_CONFIG_FILE"); - if (path == NULL) { - const char *dir; - dir = getenv("PIPEWIRE_CONFIG_DIR"); - if (dir == NULL) - dir = PIPEWIRE_CONFIG_DIR; - if (dir == NULL) - return -ENOENT; - - snprintf(filename, sizeof(filename), "%s/%s", - dir, DEFAULT_CONFIG_FILE); - path = filename; - } - - if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) { - pw_log_warn(NAME" %p: error loading config '%s': %m", d, path); - return -errno; - } - - pw_log_info(NAME" %p: loading config '%s'", d, path); - if (fstat(fd, &sbuf) < 0) - goto error_close; - if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) - goto error_close; - close(fd); - - pw_properties_update_string(d->conf, data, sbuf.st_size); - munmap(data, sbuf.st_size); - - return 0; - -error_close: - close(fd); - return -errno; -} - -static int parse_spa_libs(struct data *d, const char *str) -{ - struct spa_json it[2]; - char key[512], value[512]; - - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_object(&it[0], &it[1]) < 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - const char *val; - if (key[0] == '#') { - if (spa_json_next(&it[1], &val) <= 0) - break; - } - else if (spa_json_get_string(&it[1], value, sizeof(value)-1) > 0) { - pw_context_add_spa_lib(d->context, key, value); - } - } - return 0; -} - -static int load_module(struct data *d, const char *key, const char *args, const char *flags) -{ - if (pw_context_load_module(d->context, key, args, NULL) == NULL) { - if (errno == ENOENT && flags && strstr(flags, "ifexists") != NULL) { - pw_log_debug(NAME" %p: skipping unavailable module %s", - d, key); - } else if (flags == NULL || strstr(flags, "nofail") == NULL) { - pw_log_error(NAME" %p: could not load module \"%s\": %m", - d, key); - return -errno; - } else { - pw_log_info(NAME" %p: could not load module \"%s\": %m", - d, key); - } - } - return 0; -} - -static int parse_modules(struct data *d, const char *str) -{ - struct spa_json it[3]; - char key[512]; - int res = 0; - - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_object(&it[0], &it[1]) < 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - const char *val, *aval; - char *args = NULL, *flags = NULL; - int len, alen; - - if ((len = spa_json_next(&it[1], &val)) <= 0) - break; - - if (key[0] == '#') - continue; - - if (spa_json_is_object(val, len)) { - char arg[512]; - - spa_json_enter(&it[1], &it[2]); - - while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) { - if ((alen = spa_json_next(&it[2], &aval)) <= 0) - break; - - if (strcmp(arg, "args") == 0) { - if (spa_json_is_container(aval, alen)) - alen = spa_json_container_len(&it[2], aval, alen); - - args = malloc(alen + 1); - spa_json_parse_string(aval, alen, args); - } else if (strcmp(arg, "flags") == 0) { - if (spa_json_is_container(aval, alen)) - alen = spa_json_container_len(&it[2], aval, alen); - - flags = malloc(alen + 1); - spa_json_parse_string(aval, alen, flags); - } - } - } - else if (!spa_json_is_null(val, len)) - break; - - res = load_module(d, key, args, flags); - - free(args); - free(flags); - - if (res < 0) - break; - } - return res; -} - -static int create_object(struct data *d, const char *key, const char *args, const char *flags) -{ - struct pw_impl_factory *factory; - void *obj; - - pw_log_debug("find factory %s", key); - factory = pw_context_find_factory(d->context, key); - if (factory == NULL) { - if (flags && strstr(flags, "nofail") != NULL) - return 0; - pw_log_error("can't find factory %s", key); - return -ENOENT; - } - pw_log_debug("create object with args %s", args); - obj = pw_impl_factory_create_object(factory, - NULL, NULL, 0, - args ? pw_properties_new_string(args) : NULL, - SPA_ID_INVALID); - if (obj == NULL) { - if (flags && strstr(flags, "nofail") != NULL) - return 0; - pw_log_error("can't create object from factory %s: %m", key); - return -errno; - } - return 0; -} - -static int parse_objects(struct data *d, const char *str) -{ - struct spa_json it[3]; - char key[512]; - int res = 0; - - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_object(&it[0], &it[1]) < 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - const char *val, *aval; - char *args = NULL, *flags = NULL; - int len, alen; - - if ((len = spa_json_next(&it[1], &val)) <= 0) - break; - - if (key[0] == '#') - continue; - - if (spa_json_is_object(val, len)) { - char arg[512]; - - spa_json_enter(&it[1], &it[2]); - - while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) { - if ((alen = spa_json_next(&it[2], &aval)) <= 0) - break; - - if (strcmp(arg, "args") == 0) { - if (spa_json_is_container(aval, alen)) - alen = spa_json_container_len(&it[2], aval, alen); - - args = malloc(alen + 1); - spa_json_parse_string(aval, alen, args); - } else if (strcmp(arg, "flags") == 0) { - flags = strndup(aval, alen); - } - } - } - else if (!spa_json_is_null(val, len)) - break; - - res = create_object(d, key, args, flags); - - free(args); - free(flags); - - if (res < 0) - break; - } - return res; -} - -static int do_exec(struct data *d, const char *key, const char *args) -{ - int pid, res, n_args; - - pid = fork(); - - if (pid == 0) { - char *cmd, **argv; - - cmd = spa_aprintf("%s %s", key, args ? args : ""); - argv = pw_split_strv(cmd, " \t", INT_MAX, &n_args); - free(cmd); - - pw_log_info("exec %s '%s'", key, args); - res = execvp(key, argv); - pw_free_strv(argv); - - if (res == -1) { - res = -errno; - pw_log_error("execvp error '%s': %m", key); - return res; - } - } - else { - int status; - res = waitpid(pid, &status, WNOHANG); - pw_log_info("exec got pid %d res:%d status:%d", pid, res, status); - } - return 0; -} - -static int parse_exec(struct data *d, const char *str) -{ - struct spa_json it[3]; - char key[512]; - int res = 0; - - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_object(&it[0], &it[1]) < 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - const char *val; - char *args = NULL; - int len; - - if ((len = spa_json_next(&it[1], &val)) <= 0) - break; - - if (key[0] == '#') - continue; - - if (spa_json_is_object(val, len)) { - char arg[512], aval[1024]; - - spa_json_enter(&it[1], &it[2]); - - while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) { - if (spa_json_get_string(&it[2], aval, sizeof(aval)-1) <= 0) - break; - - if (strcmp(arg, "args") == 0) - args = strdup(aval); - } - } - else if (!spa_json_is_null(val, len)) - break; - - res = do_exec(d, key, args); - - free(args); - - if (res < 0) - break; - } - return res; -} - static void do_quit(void *data, int signal_number) { struct data *d = data; @@ -373,7 +70,6 @@ int main(int argc, char *argv[]) { struct data d; struct pw_properties *properties; - const char *str; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, @@ -381,7 +77,7 @@ int main(int argc, char *argv[]) { NULL, 0, NULL, 0} }; - int c, res; + int c; if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0) fprintf(stderr, "can't set PIPEWIRE_INTERNAL env: %m"); @@ -389,16 +85,6 @@ int main(int argc, char *argv[]) spa_zero(d); pw_init(&argc, &argv); - if ((d.conf = pw_properties_new(NULL, NULL)) == NULL) { - pw_log_error("failed to create config: %m"); - return -1; - } - - if ((res = load_config(&d)) < 0) { - pw_log_error("failed to load config: %s", spa_strerror(res)); - return -1; - } - d.daemon_name = getenv("PIPEWIRE_CORE"); if (d.daemon_name == NULL) d.daemon_name = PW_DEFAULT_REMOTE; @@ -427,13 +113,9 @@ int main(int argc, char *argv[]) properties = pw_properties_new( PW_KEY_CORE_NAME, d.daemon_name, - PW_KEY_CONTEXT_PROFILE_MODULES, "none", + PW_KEY_CONFIG_NAME, "pipewire-uninstalled.conf", PW_KEY_CORE_DAEMON, "true", NULL); - /* parse configuration */ - if ((str = pw_properties_get(d.conf, "properties")) != NULL) - pw_properties_update_string(properties, str, strlen(str)); - d.loop = pw_main_loop_new(&properties->dict); if (d.loop == NULL) { pw_log_error("failed to create main-loop: %m"); @@ -449,32 +131,10 @@ int main(int argc, char *argv[]) return -1; } - if ((str = pw_properties_get(d.conf, "spa-libs")) != NULL) - parse_spa_libs(&d, str); - if ((str = pw_properties_get(d.conf, "modules")) != NULL) { - if ((res = parse_modules(&d, str)) < 0) { - pw_log_error("failed to load modules: %s", spa_strerror(res)); - return -1; - } - } - if ((str = pw_properties_get(d.conf, "objects")) != NULL) { - if ((res = parse_objects(&d, str)) < 0) { - pw_log_error("failed to load objects: %s", spa_strerror(res)); - return -1; - } - } - if ((str = pw_properties_get(d.conf, "exec")) != NULL) { - if ((res = parse_exec(&d, str)) < 0) { - pw_log_error("failed to exec: %s", spa_strerror(res)); - return -1; - } - } - pw_log_info("start main loop"); pw_main_loop_run(d.loop); pw_log_info("leave main loop"); - pw_properties_free(d.conf); pw_context_destroy(d.context); pw_main_loop_destroy(d.loop); pw_deinit(); diff --git a/src/daemon/media-session.d/media-session.conf b/src/daemon/media-session.d/media-session.conf index 1aaf1f9ce..324d612b7 100644 --- a/src/daemon/media-session.d/media-session.conf +++ b/src/daemon/media-session.d/media-session.conf @@ -17,6 +17,49 @@ spa-libs = { } modules = { + ## = { [args = { = ... }] + # [flags = [ [ifexists] [nofail] ]} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + # Uses RTKit to boost the data thread priority. + libpipewire-module-rtkit = { + args = { + #nice.level = -11 + #rt.prio = 20 + #rt.time.soft = 200000 + #rt.time.hard = 200000 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + libpipewire-module-protocol-native = null + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + libpipewire-module-client-node = null + + # Allows creating devices that run in the context of the + # client. Is used by the session manager. + libpipewire-module-client-device = null + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + libpipewire-module-adapter = null + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + libpipewire-module-metadata = null + + # Provides factories to make session manager objects. + libpipewire-module-session-manager = null +} + +session.modules = { # These are the modules that are enabled when a file with # the key name is found in the media-session.d config directory. # the default bundle is always enabled. diff --git a/src/daemon/meson.build b/src/daemon/meson.build index c66421106..fb5f953eb 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -35,6 +35,16 @@ configure_file(input : 'pipewire.conf.in', configuration : conf_config, install_dir : conf_install_dir) +configure_file(input : 'client.conf.in', + output : 'client.conf', + configuration : conf_config, + install_dir : conf_install_dir) + +configure_file(input : 'pipewire-pulse.conf.in', + output : 'pipewire-pulse.conf', + configuration : conf_config, + install_dir : conf_install_dir) + configure_file(input : 'pipewire.conf.in', output : 'pipewire-uninstalled.conf', configuration : conf_config_uninstalled) diff --git a/src/daemon/pipewire-pulse.c b/src/daemon/pipewire-pulse.c index fee977f6c..95d576db8 100644 --- a/src/daemon/pipewire-pulse.c +++ b/src/daemon/pipewire-pulse.c @@ -93,7 +93,7 @@ int main(int argc, char *argv[]) } properties = pw_properties_new( - PW_KEY_CONTEXT_PROFILE_MODULES, "default,rtkit", + PW_KEY_CONFIG_NAME, "pipewire-pulse.conf", NULL); loop = pw_main_loop_new(&properties->dict); diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in new file mode 100644 index 000000000..19475ad29 --- /dev/null +++ b/src/daemon/pipewire-pulse.conf.in @@ -0,0 +1,56 @@ +# Daemon config file for PipeWire pulse version @VERSION@ # + +properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #log.level = 2 +} + +spa-libs = { + ## = + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +modules = { + ## = { [args = { = ... }] + # [flags = [ [ifexists] [nofail] ]} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + # Uses RTKit to boost the data thread priority. + libpipewire-module-rtkit = { + args = { + nice.level = -14 + #rt.prio = 20 + #rt.time.soft = 200000 + #rt.time.hard = 200000 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + libpipewire-module-protocol-native = null + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + libpipewire-module-client-node = null + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + libpipewire-module-adapter = null + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + libpipewire-module-metadata = null +} diff --git a/src/examples/media-session/media-session.c b/src/examples/media-session/media-session.c index 84a9dfd9c..8598fd673 100644 --- a/src/examples/media-session/media-session.c +++ b/src/examples/media-session/media-session.c @@ -53,6 +53,7 @@ #include "pipewire/pipewire.h" #include "pipewire/private.h" +#include "pipewire/conf.h" #include "extensions/session-manager.h" #include "extensions/client-node.h" @@ -61,6 +62,7 @@ #include "media-session.h" #define NAME "media-session" +#define SESSION_PREFIX "media-session.d" #define SESSION_CONF "media-session.conf" #define sm_object_emit(o,m,v,...) spa_hook_list_call(&(o)->hooks, struct sm_object_events, m, v, ##__VA_ARGS__) @@ -2140,29 +2142,6 @@ static void do_quit(void *data, int signal_number) pw_main_loop_quit(impl->loop); } -static int load_spa_libs(struct impl *impl, const char *str) -{ - struct spa_json it[2]; - char key[512], value[512]; - - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_object(&it[0], &it[1]) < 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - const char *val; - if (key[0] == '#') { - if (spa_json_next(&it[1], &val) <= 0) - break; - } - else if (spa_json_get_string(&it[1], value, sizeof(value)-1) > 0) { - pw_log_debug("spa-libs: '%s' -> '%s'", key, value); - pw_context_add_spa_lib(impl->this.context, key, value); - } - } - return 0; -} - static int collect_modules(struct impl *impl, const char *str) { struct spa_json it[3]; @@ -2282,20 +2261,23 @@ int main(int argc, char *argv[]) impl.state_dir_fd = -1; impl.this.props = pw_properties_new( - PW_KEY_CONTEXT_PROFILE_MODULES, "default,rtkit", + PW_KEY_CONFIG_PREFIX, SESSION_PREFIX, + PW_KEY_CONFIG_NAME, SESSION_CONF, NULL); if (impl.this.props == NULL) return -1; if ((impl.conf = pw_properties_new(NULL, NULL)) == NULL) return -1; - sm_media_session_load_conf(&impl.this, SESSION_CONF, impl.conf); + + pw_conf_load(SESSION_PREFIX, SESSION_CONF, impl.conf); + if ((str = pw_properties_get(impl.conf, "properties")) != NULL) pw_properties_update_string(impl.this.props, str, strlen(str)); if ((impl.modules = pw_properties_new("default", "true", NULL)) == NULL) return -1; - if ((str = pw_properties_get(impl.conf, "modules")) != NULL) + if ((str = pw_properties_get(impl.conf, "session-modules")) != NULL) collect_modules(&impl, str); while ((c = getopt_long(argc, argv, "hV", long_options, NULL)) != -1) { @@ -2334,9 +2316,6 @@ int main(int argc, char *argv[]) if (impl.this.context == NULL) return -1; - if ((str = pw_properties_get(impl.conf, "spa-libs")) != NULL) - load_spa_libs(&impl, str); - pw_context_set_object(impl.this.context, SM_TYPE_MEDIA_SESSION, &impl); pw_map_init(&impl.globals, 64, 64); diff --git a/src/modules/module-rtkit.c b/src/modules/module-rtkit.c index ae6f698cd..b26b1ce32 100644 --- a/src/modules/module-rtkit.c +++ b/src/modules/module-rtkit.c @@ -505,7 +505,7 @@ static void idle_func(struct spa_source *source) if ((r = pw_rtkit_make_realtime(impl->system_bus, 0, rtprio)) < 0) { pw_log_warn("could not make thread realtime: %s", spa_strerror(r)); } else { - pw_log_info("processing thread made realtime"); + pw_log_info("processing thread made realtime prio:%d", rtprio); } exit: pw_rtkit_bus_free(impl->system_bus); diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c new file mode 100644 index 000000000..fb9420946 --- /dev/null +++ b/src/pipewire/conf.c @@ -0,0 +1,369 @@ +/* PipeWire + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "config.h" + +#define NAME "config" + +SPA_EXPORT +int pw_conf_load(const char *prefix, const char *name, struct pw_properties *conf) +{ + const char *path; + char filename[PATH_MAX], *data; + struct stat sbuf; + int fd; + const char *dir; + dir = getenv("PIPEWIRE_CONFIG_DIR"); + if (dir == NULL) + dir = PIPEWIRE_CONFIG_DIR; + if (dir == NULL) + return -ENOENT; + + if (prefix) + snprintf(filename, sizeof(filename), "%s/%s/%s", + dir, prefix, name); + else + snprintf(filename, sizeof(filename), "%s/%s", + dir, name); + path = filename; + + if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) { + pw_log_warn(NAME" %p: error loading config '%s': %m", conf, path); + return -errno; + } + + pw_log_info(NAME" %p: loading config '%s'", conf, path); + if (fstat(fd, &sbuf) < 0) + goto error_close; + if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) + goto error_close; + close(fd); + + pw_properties_update_string(conf, data, sbuf.st_size); + munmap(data, sbuf.st_size); + + return 0; + +error_close: + close(fd); + return -errno; +} + +static int parse_spa_libs(struct pw_context *context, const char *str) +{ + struct spa_json it[2]; + char key[512], value[512]; + + spa_json_init(&it[0], str, strlen(str)); + if (spa_json_enter_object(&it[0], &it[1]) < 0) + return -EINVAL; + + while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { + const char *val; + if (key[0] == '#') { + if (spa_json_next(&it[1], &val) <= 0) + break; + } + else if (spa_json_get_string(&it[1], value, sizeof(value)-1) > 0) { + pw_context_add_spa_lib(context, key, value); + } + } + return 0; +} + +static int load_module(struct pw_context *context, const char *key, const char *args, const char *flags) +{ + if (pw_context_load_module(context, key, args, NULL) == NULL) { + if (errno == ENOENT && flags && strstr(flags, "ifexists") != NULL) { + pw_log_debug(NAME" %p: skipping unavailable module %s", + context, key); + } else if (flags == NULL || strstr(flags, "nofail") == NULL) { + pw_log_error(NAME" %p: could not load mandatory module \"%s\": %m", + context, key); + return -errno; + } else { + pw_log_info(NAME" %p: could not load optional module \"%s\": %m", + context, key); + } + } + return 0; +} + +static int parse_modules(struct pw_context *context, const char *str) +{ + struct spa_json it[3]; + char key[512]; + int res = 0; + + spa_json_init(&it[0], str, strlen(str)); + if (spa_json_enter_object(&it[0], &it[1]) < 0) + return -EINVAL; + + while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { + const char *val, *aval; + char *args = NULL, *flags = NULL; + int len, alen; + + if ((len = spa_json_next(&it[1], &val)) <= 0) + break; + + if (key[0] == '#') + continue; + + if (spa_json_is_object(val, len)) { + char arg[512]; + + spa_json_enter(&it[1], &it[2]); + + while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) { + if ((alen = spa_json_next(&it[2], &aval)) <= 0) + break; + + if (strcmp(arg, "args") == 0) { + if (spa_json_is_container(aval, alen)) + alen = spa_json_container_len(&it[2], aval, alen); + + args = malloc(alen + 1); + spa_json_parse_string(aval, alen, args); + } else if (strcmp(arg, "flags") == 0) { + if (spa_json_is_container(aval, alen)) + alen = spa_json_container_len(&it[2], aval, alen); + + flags = malloc(alen + 1); + spa_json_parse_string(aval, alen, flags); + } + } + } + else if (!spa_json_is_null(val, len)) + break; + + res = load_module(context, key, args, flags); + + free(args); + free(flags); + + if (res < 0) + break; + } + return res; +} + +static int create_object(struct pw_context *context, const char *key, const char *args, const char *flags) +{ + struct pw_impl_factory *factory; + void *obj; + + pw_log_debug("find factory %s", key); + factory = pw_context_find_factory(context, key); + if (factory == NULL) { + if (flags && strstr(flags, "nofail") != NULL) + return 0; + pw_log_error("can't find factory %s", key); + return -ENOENT; + } + pw_log_debug("create object with args %s", args); + obj = pw_impl_factory_create_object(factory, + NULL, NULL, 0, + args ? pw_properties_new_string(args) : NULL, + SPA_ID_INVALID); + if (obj == NULL) { + if (flags && strstr(flags, "nofail") != NULL) + return 0; + pw_log_error("can't create object from factory %s: %m", key); + return -errno; + } + return 0; +} + +static int parse_objects(struct pw_context *context, const char *str) +{ + struct spa_json it[3]; + char key[512]; + int res = 0; + + spa_json_init(&it[0], str, strlen(str)); + if (spa_json_enter_object(&it[0], &it[1]) < 0) + return -EINVAL; + + while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { + const char *val, *aval; + char *args = NULL, *flags = NULL; + int len, alen; + + if ((len = spa_json_next(&it[1], &val)) <= 0) + break; + + if (key[0] == '#') + continue; + + if (spa_json_is_object(val, len)) { + char arg[512]; + + spa_json_enter(&it[1], &it[2]); + + while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) { + if ((alen = spa_json_next(&it[2], &aval)) <= 0) + break; + + if (strcmp(arg, "args") == 0) { + if (spa_json_is_container(aval, alen)) + alen = spa_json_container_len(&it[2], aval, alen); + + args = malloc(alen + 1); + spa_json_parse_string(aval, alen, args); + } else if (strcmp(arg, "flags") == 0) { + flags = strndup(aval, alen); + } + } + } + else if (!spa_json_is_null(val, len)) + break; + + res = create_object(context, key, args, flags); + + free(args); + free(flags); + + if (res < 0) + break; + } + return res; +} + +static int do_exec(struct pw_context *context, const char *key, const char *args) +{ + int pid, res, n_args; + + pid = fork(); + + if (pid == 0) { + char *cmd, **argv; + + cmd = spa_aprintf("%s %s", key, args ? args : ""); + argv = pw_split_strv(cmd, " \t", INT_MAX, &n_args); + free(cmd); + + pw_log_info("exec %s '%s'", key, args); + res = execvp(key, argv); + pw_free_strv(argv); + + if (res == -1) { + res = -errno; + pw_log_error("execvp error '%s': %m", key); + return res; + } + } + else { + int status; + res = waitpid(pid, &status, WNOHANG); + pw_log_info("exec got pid %d res:%d status:%d", pid, res, status); + } + return 0; +} + +static int parse_exec(struct pw_context *context, const char *str) +{ + struct spa_json it[3]; + char key[512]; + int res = 0; + + spa_json_init(&it[0], str, strlen(str)); + if (spa_json_enter_object(&it[0], &it[1]) < 0) + return -EINVAL; + + while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { + const char *val; + char *args = NULL; + int len; + + if ((len = spa_json_next(&it[1], &val)) <= 0) + break; + + if (key[0] == '#') + continue; + + if (spa_json_is_object(val, len)) { + char arg[512], aval[1024]; + + spa_json_enter(&it[1], &it[2]); + + while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) { + if (spa_json_get_string(&it[2], aval, sizeof(aval)-1) <= 0) + break; + + if (strcmp(arg, "args") == 0) + args = strdup(aval); + } + } + else if (!spa_json_is_null(val, len)) + break; + + res = do_exec(context, key, args); + + free(args); + + if (res < 0) + break; + } + return res; +} + +SPA_EXPORT +int pw_context_parse_conf_section(struct pw_context *context, + struct pw_properties *conf, const char *section) +{ + const char *str; + int res; + + if ((str = pw_properties_get(conf, section)) == NULL) + return -ENOENT; + + if (strcmp(section, "spa-libs") == 0) + res = parse_spa_libs(context, str); + else if (strcmp(section, "modules") == 0) + res = parse_modules(context, str); + else if (strcmp(section, "objects") == 0) + res = parse_objects(context, str); + else if (strcmp(section, "exec") == 0) + res = parse_exec(context, str); + else + res = -EINVAL; + + return res; +} diff --git a/src/pipewire/conf.h b/src/pipewire/conf.h new file mode 100644 index 000000000..e7f88ee7c --- /dev/null +++ b/src/pipewire/conf.h @@ -0,0 +1,30 @@ +/* PipeWire + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +int pw_conf_load(const char *prefix, const char *name, struct pw_properties *conf); + +int pw_context_parse_conf_section(struct pw_context *context, + struct pw_properties *conf, const char *section); diff --git a/src/pipewire/context.c b/src/pipewire/context.c index 7082da105..0b2e57064 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -40,6 +40,7 @@ #include #include +#include #include @@ -73,30 +74,6 @@ struct factory_entry { char *lib; }; -static int load_module_profiles(struct pw_context *this, char **profiles, const char *args) -{ - int i; - - for (i = 0; profiles[i]; i++) { - const char *str = profiles[i]; - if (strcmp(str, "default") == 0) { - pw_log_debug(NAME" %p: loading default profile", this); - pw_context_load_module(this, "libpipewire-module-protocol-native", args, NULL); - pw_context_load_module(this, "libpipewire-module-client-node", args, NULL); - pw_context_load_module(this, "libpipewire-module-client-device", args, NULL); - pw_context_load_module(this, "libpipewire-module-adapter", args, NULL); - pw_context_load_module(this, "libpipewire-module-metadata", args, NULL); - pw_context_load_module(this, "libpipewire-module-session-manager", args, NULL); - } else if (strcmp(str, "rtkit") == 0) { - pw_log_debug(NAME" %p: loading rtkit profile", this); - pw_context_load_module(this, "libpipewire-module-rtkit", args, NULL); - } else if (strcmp(str, "none") != 0) { - pw_log_warn(NAME" %p: unknown profile %s", this, str); - } - } - return 0; -} - static void fill_properties(struct pw_context *context) { struct pw_properties *properties = context->properties; @@ -195,11 +172,10 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, { struct impl *impl; struct pw_context *this; - const char *lib, *str, *args; - char **profiles; + const char *lib, *str, *conf_prefix, *conf_name; void *dbus_iface = NULL; uint32_t n_support; - struct pw_properties *pr; + struct pw_properties *pr, *conf = NULL; struct spa_cpu *cpu; int res = 0; @@ -223,6 +199,25 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, goto error_free; } + conf_prefix = pw_properties_get(properties, PW_KEY_CONFIG_PREFIX); + + conf_name = getenv("PIPEWIRE_CONFIG_NAME"); + if (conf_name == NULL) + conf_name = pw_properties_get(properties, PW_KEY_CONFIG_NAME); + if (conf_name == NULL) + conf_name = "client.conf"; + + conf = pw_properties_new(NULL, NULL); + if (conf == NULL) { + res = -errno; + goto error_free; + } + pw_conf_load(conf_prefix, conf_name, conf); + this->conf = conf; + + if ((str = pw_properties_get(conf, "properties")) != NULL) + pw_properties_update_string(properties, str, strlen(str)); + if ((str = pw_properties_get(properties, "mem.mlock-all")) != NULL && pw_properties_parse_bool(str)) { if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0) @@ -318,19 +313,10 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, this->sc_pagesize = sysconf(_SC_PAGESIZE); - str = pw_properties_get(properties, PW_KEY_CONTEXT_PROFILE_MODULES); - if (str == NULL) - str = getenv("PIPEWIRE_PROFILE_MODULES"); - if (str == NULL) - str = "default"; - - pw_log_debug(NAME" %p: module profile %s", this, str); - args = pw_properties_get(properties, PW_KEY_CONTEXT_MODULES_ARGS); - - /* make a copy, in case the properties get changed when loading a module */ - profiles = pw_split_strv(str, ", ", INT_MAX, &res); - load_module_profiles(this, profiles, args); - pw_free_strv(profiles); + pw_context_parse_conf_section(this, conf, "spa-libs"); + pw_context_parse_conf_section(this, conf, "modules"); + pw_context_parse_conf_section(this, conf, "objects"); + pw_context_parse_conf_section(this, conf, "exec"); pw_log_debug(NAME" %p: created", this); @@ -341,6 +327,8 @@ error_free_loop: error_free: free(this); error_cleanup: + if (conf) + pw_properties_free(conf); if (properties) pw_properties_free(properties); errno = -res; @@ -402,6 +390,7 @@ void pw_context_destroy(struct pw_context *context) pw_data_loop_destroy(context->data_loop_impl); pw_properties_free(context->properties); + pw_properties_free(context->conf); if (impl->dbus_handle) pw_unload_spa_handle(impl->dbus_handle); @@ -456,6 +445,12 @@ const struct pw_properties *pw_context_get_properties(struct pw_context *context return context->properties; } +SPA_EXPORT +const struct pw_properties *pw_context_get_config(struct pw_context *context) +{ + return context->conf; +} + /** Update context properties * * \param context a context diff --git a/src/pipewire/context.h b/src/pipewire/context.h index 7a2a369b8..bbc0505bb 100644 --- a/src/pipewire/context.h +++ b/src/pipewire/context.h @@ -129,6 +129,9 @@ void pw_context_add_listener(struct pw_context *context, /** Get the context properties */ const struct pw_properties *pw_context_get_properties(struct pw_context *context); +/** Get the config for this context. Since 0.3.22 */ +const struct pw_properties *pw_context_get_config(struct pw_context *context); + /** Update the context properties */ int pw_context_update_properties(struct pw_context *context, const struct spa_dict *dict); diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index 588b7bbdc..9bf4ed7ad 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -59,8 +59,12 @@ extern "C" { #define PW_KEY_OBJECT_PATH "object.path" /**< unique path to construct the object */ #define PW_KEY_OBJECT_ID "object.id" /**< a global object id */ +/* config */ +#define PW_KEY_CONFIG_PREFIX "config.prefix" /**< a config prefix directory */ +#define PW_KEY_CONFIG_NAME "config.name" /**< a config file name */ + /* context */ -#define PW_KEY_CONTEXT_PROFILE_MODULES "context.profile.modules" /**< a context profile for modules */ +#define PW_KEY_CONTEXT_PROFILE_MODULES "context.profile.modules" /**< a context profile for modules, deprecated */ #define PW_KEY_CONTEXT_MODULES_ARGS "context.modules.args" /**< arguments for context modules */ #define PW_KEY_USER_NAME "context.user-name" /**< The user name that runs pipewire */ #define PW_KEY_HOST_NAME "context.host-name" /**< The host name of the machine */ diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build index 40f478bf4..bd444d5e0 100644 --- a/src/pipewire/meson.build +++ b/src/pipewire/meson.build @@ -4,6 +4,7 @@ pipewire_headers = [ 'impl-core.h', 'impl-client.h', 'client.h', + 'conf.h', 'context.h', 'control.h', 'core.h', @@ -46,6 +47,7 @@ pipewire_sources = [ 'buffers.c', 'impl-core.c', 'impl-client.c', + 'conf.c', 'context.c', 'control.c', 'core.c', diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 9dcf40159..4c3140300 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -373,6 +373,7 @@ struct pw_context_driver_events { struct pw_context { struct pw_impl_core *core; /**< core object */ + struct pw_properties *conf; /**< configuration of the context */ struct pw_properties *properties; /**< properties of the context */ struct defaults defaults; /**< default parameters */