mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-04 13:30:12 -05:00
daemon: rework config parsing
Replace config parsing for something more flexible based on json.
This commit is contained in:
parent
1bd90dc666
commit
49d11acde0
7 changed files with 458 additions and 905 deletions
|
|
@ -1,445 +0,0 @@
|
||||||
/* PipeWire
|
|
||||||
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
|
|
||||||
* @author Linus Svensson <linus.svensson@axis.com>
|
|
||||||
* Copyright © 2018 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 <errno.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
#include <pipewire/impl.h>
|
|
||||||
|
|
||||||
#include "command.h"
|
|
||||||
|
|
||||||
/** \cond */
|
|
||||||
|
|
||||||
static struct pw_command *parse_command_help(struct pw_properties *properties, const char *line, char **err);
|
|
||||||
static struct pw_command *parse_command_set_prop(struct pw_properties *properties, const char *line, char **err);
|
|
||||||
static struct pw_command *parse_command_add_spa_lib(struct pw_properties *properties, const char *line, char **err);
|
|
||||||
static struct pw_command *parse_command_module_load(struct pw_properties *properties, const char *line, char **err);
|
|
||||||
static struct pw_command *parse_command_create_object(struct pw_properties *properties, const char *line, char **err);
|
|
||||||
static struct pw_command *parse_command_exec(struct pw_properties *properties, const char *line, char **err);
|
|
||||||
|
|
||||||
struct impl {
|
|
||||||
struct pw_command this;
|
|
||||||
int first_arg;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct pw_command *(*pw_command_parse_func_t) (struct pw_properties *properties, const char *line, char **err);
|
|
||||||
|
|
||||||
struct command_parse {
|
|
||||||
const char *name;
|
|
||||||
const char *description;
|
|
||||||
pw_command_parse_func_t func;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct command_parse parsers[] = {
|
|
||||||
{"help", "Show this help", parse_command_help},
|
|
||||||
{"set-prop", "Set a property", parse_command_set_prop},
|
|
||||||
{"add-spa-lib", "Add a library that provides a spa factory name regex", parse_command_add_spa_lib},
|
|
||||||
{"load-module", "Load a module", parse_command_module_load},
|
|
||||||
{"create-object", "Create an object from a factory", parse_command_create_object},
|
|
||||||
{"exec", "Execute a program", parse_command_exec},
|
|
||||||
{NULL, NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char whitespace[] = " \t";
|
|
||||||
/** \endcond */
|
|
||||||
|
|
||||||
static int
|
|
||||||
execute_command_help(struct pw_command *command, struct pw_context *context, char **err)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
fputs("Available commands:\n", stdout);
|
|
||||||
for (i = 0; parsers[i].name; i++)
|
|
||||||
fprintf(stdout, " %20.20s\t%s\n", parsers[i].name, parsers[i].description);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct pw_command *parse_command_help(struct pw_properties *properties, const char *line, char **err)
|
|
||||||
{
|
|
||||||
struct impl *impl;
|
|
||||||
struct pw_command *this;
|
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl));
|
|
||||||
if (impl == NULL)
|
|
||||||
goto no_mem;
|
|
||||||
|
|
||||||
this = &impl->this;
|
|
||||||
this->func = execute_command_help;
|
|
||||||
this->args = pw_split_strv(line, whitespace, 1, &this->n_args);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
no_mem:
|
|
||||||
*err = spa_aprintf("alloc failed: %m");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
execute_command_set_prop(struct pw_command *command, struct pw_context *context, char **err)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct pw_command *parse_command_set_prop(struct pw_properties *properties, const char *line, char **err)
|
|
||||||
{
|
|
||||||
struct impl *impl;
|
|
||||||
struct pw_command *this;
|
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl));
|
|
||||||
if (impl == NULL)
|
|
||||||
goto error_alloc;
|
|
||||||
|
|
||||||
this = &impl->this;
|
|
||||||
this->func = execute_command_set_prop;
|
|
||||||
this->args = pw_split_strv(line, whitespace, 4, &this->n_args);
|
|
||||||
|
|
||||||
if (this->n_args < 3)
|
|
||||||
goto error_arguments;
|
|
||||||
|
|
||||||
pw_log_debug("set property: '%s' = '%s'", this->args[1], this->args[2]);
|
|
||||||
pw_properties_set(properties, this->args[1], this->args[2]);
|
|
||||||
|
|
||||||
if (strcmp(this->args[1], SPA_KEY_LOG_LEVEL) == 0) {
|
|
||||||
pw_log_set_level(atoi(this->args[2]));
|
|
||||||
setenv("PIPEWIRE_DEBUG", this->args[2], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
error_arguments:
|
|
||||||
*err = spa_aprintf("%s requires <property-name> <value>", this->args[0]);
|
|
||||||
pw_free_strv(this->args);
|
|
||||||
free(impl);
|
|
||||||
return NULL;
|
|
||||||
error_alloc:
|
|
||||||
*err = spa_aprintf("alloc failed: %m");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
execute_command_add_spa_lib(struct pw_command *command, struct pw_context *context, char **err)
|
|
||||||
{
|
|
||||||
int res;
|
|
||||||
|
|
||||||
res = pw_context_add_spa_lib(context, command->args[1], command->args[2]);
|
|
||||||
if (res < 0) {
|
|
||||||
*err = spa_aprintf("could not add spa library \"%s\"", command->args[1]);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct pw_command *parse_command_add_spa_lib(struct pw_properties *properties, const char *line, char **err)
|
|
||||||
{
|
|
||||||
struct impl *impl;
|
|
||||||
struct pw_command *this;
|
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl));
|
|
||||||
if (impl == NULL)
|
|
||||||
goto no_mem;
|
|
||||||
|
|
||||||
this = &impl->this;
|
|
||||||
this->func = execute_command_add_spa_lib;
|
|
||||||
this->args = pw_split_strv(line, whitespace, 4, &this->n_args);
|
|
||||||
|
|
||||||
if (this->n_args < 3)
|
|
||||||
goto no_library;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
no_library:
|
|
||||||
*err = spa_aprintf("%s requires <factory-regex> <library-name>", this->args[0]);
|
|
||||||
pw_free_strv(this->args);
|
|
||||||
free(impl);
|
|
||||||
return NULL;
|
|
||||||
no_mem:
|
|
||||||
*err = spa_aprintf("alloc failed: %m");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool has_option(struct pw_command *this, int first_arg, const char *option)
|
|
||||||
{
|
|
||||||
int arg;
|
|
||||||
for (arg = 1; arg < first_arg; arg++) {
|
|
||||||
if (strstr(this->args[arg], "-") == this->args[arg]) {
|
|
||||||
if (strcmp(this->args[arg], option) == 0)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
execute_command_module_load(struct pw_command *command, struct pw_context *context, char **err)
|
|
||||||
{
|
|
||||||
struct pw_impl_module *module;
|
|
||||||
struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
|
|
||||||
int arg = impl->first_arg;
|
|
||||||
|
|
||||||
module = pw_context_load_module(context, command->args[arg], command->args[arg+1], NULL);
|
|
||||||
if (module == NULL) {
|
|
||||||
if (errno == ENOENT && has_option(command, arg, "-ifexists")) {
|
|
||||||
pw_log_debug("skipping unavailable module %s", command->args[arg]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*err = spa_aprintf("could not load module \"%s\": %m", command->args[arg]);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct pw_command *parse_command_module_load(struct pw_properties *properties, const char *line, char **err)
|
|
||||||
{
|
|
||||||
struct impl *impl;
|
|
||||||
struct pw_command *this;
|
|
||||||
int arg;
|
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl));
|
|
||||||
if (impl == NULL)
|
|
||||||
goto no_mem;
|
|
||||||
|
|
||||||
this = &impl->this;
|
|
||||||
this->func = execute_command_module_load;
|
|
||||||
|
|
||||||
this->args = pw_split_strv(line, whitespace, INT_MAX, &this->n_args);
|
|
||||||
|
|
||||||
for (arg = 1; arg < this->n_args; arg++) {
|
|
||||||
if (strstr(this->args[arg], "-") != this->args[arg])
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (arg + 1 > this->n_args)
|
|
||||||
goto no_module;
|
|
||||||
|
|
||||||
pw_free_strv(this->args);
|
|
||||||
this->args = pw_split_strv(line, whitespace, arg + 2, &this->n_args);
|
|
||||||
|
|
||||||
impl->first_arg = arg;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
no_module:
|
|
||||||
*err = spa_aprintf("%s requires a module name", this->args[0]);
|
|
||||||
pw_free_strv(this->args);
|
|
||||||
free(impl);
|
|
||||||
return NULL;
|
|
||||||
no_mem:
|
|
||||||
*err = spa_aprintf("alloc failed: %m");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
execute_command_create_object(struct pw_command *command, struct pw_context *context, char **err)
|
|
||||||
{
|
|
||||||
struct pw_impl_factory *factory;
|
|
||||||
struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
|
|
||||||
int arg = impl->first_arg;
|
|
||||||
void *obj;
|
|
||||||
|
|
||||||
pw_log_debug("find factory %s", command->args[arg]);
|
|
||||||
factory = pw_context_find_factory(context, command->args[arg]);
|
|
||||||
if (factory == NULL) {
|
|
||||||
if (has_option(command, arg, "-nofail"))
|
|
||||||
return 0;
|
|
||||||
pw_log_error("can't find factory %s", command->args[arg]);
|
|
||||||
return -ENOENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
pw_log_debug("create object with args %s", command->args[arg+1]);
|
|
||||||
obj = pw_impl_factory_create_object(factory,
|
|
||||||
NULL, NULL, 0,
|
|
||||||
pw_properties_new_string(command->args[arg+1]),
|
|
||||||
SPA_ID_INVALID);
|
|
||||||
if (obj == NULL) {
|
|
||||||
if (has_option(command, arg, "-nofail"))
|
|
||||||
return 0;
|
|
||||||
pw_log_error("can't create object from factory %s: %m", command->args[arg]);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct pw_command *parse_command_create_object(struct pw_properties *properties, const char *line, char **err)
|
|
||||||
{
|
|
||||||
struct impl *impl;
|
|
||||||
struct pw_command *this;
|
|
||||||
int arg;
|
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl));
|
|
||||||
if (impl == NULL)
|
|
||||||
goto no_mem;
|
|
||||||
|
|
||||||
this = &impl->this;
|
|
||||||
this->func = execute_command_create_object;
|
|
||||||
this->args = pw_split_strv(line, whitespace, INT_MAX, &this->n_args);
|
|
||||||
|
|
||||||
for (arg = 1; arg < this->n_args; arg++) {
|
|
||||||
if (strstr(this->args[arg], "-") != this->args[arg])
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (arg > this->n_args)
|
|
||||||
goto no_factory;
|
|
||||||
|
|
||||||
pw_free_strv(this->args);
|
|
||||||
this->args = pw_split_strv(line, whitespace, arg + 2, &this->n_args);
|
|
||||||
|
|
||||||
impl->first_arg = arg;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
no_factory:
|
|
||||||
*err = spa_aprintf("%s requires <factory-name> [<key>=<value> ...]", this->args[0]);
|
|
||||||
pw_free_strv(this->args);
|
|
||||||
free(impl);
|
|
||||||
return NULL;
|
|
||||||
no_mem:
|
|
||||||
*err = spa_aprintf("alloc failed: %m");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
execute_command_exec(struct pw_command *command, struct pw_context *context, char **err)
|
|
||||||
{
|
|
||||||
int pid, res;
|
|
||||||
|
|
||||||
pid = fork();
|
|
||||||
|
|
||||||
if (pid == 0) {
|
|
||||||
pw_log_info("exec %s", command->args[1]);
|
|
||||||
res = execvp(command->args[1], &command->args[1]);
|
|
||||||
if (res == -1) {
|
|
||||||
res = -errno;
|
|
||||||
*err = spa_aprintf("'%s': %m", command->args[1]);
|
|
||||||
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 struct pw_command *parse_command_exec(struct pw_properties *properties, const char *line, char **err)
|
|
||||||
{
|
|
||||||
struct impl *impl;
|
|
||||||
struct pw_command *this;
|
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl));
|
|
||||||
if (impl == NULL)
|
|
||||||
goto no_mem;
|
|
||||||
|
|
||||||
this = &impl->this;
|
|
||||||
this->func = execute_command_exec;
|
|
||||||
this->args = pw_split_strv(line, whitespace, INT_MAX, &this->n_args);
|
|
||||||
|
|
||||||
if (this->n_args < 1)
|
|
||||||
goto no_executable;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
no_executable:
|
|
||||||
*err = spa_aprintf("requires an executable name");
|
|
||||||
pw_free_strv(this->args);
|
|
||||||
free(impl);
|
|
||||||
return NULL;
|
|
||||||
no_mem:
|
|
||||||
*err = spa_aprintf("alloc failed: %m");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Free command
|
|
||||||
*
|
|
||||||
* \param command a command to free
|
|
||||||
*
|
|
||||||
* Free all resources assicated with \a command.
|
|
||||||
*
|
|
||||||
* \memberof pw_command
|
|
||||||
*/
|
|
||||||
SPA_EXPORT
|
|
||||||
void pw_command_free(struct pw_command *command)
|
|
||||||
{
|
|
||||||
struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
|
|
||||||
|
|
||||||
spa_list_remove(&command->link);
|
|
||||||
pw_free_strv(command->args);
|
|
||||||
free(impl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parses a command line
|
|
||||||
* \param line command line to parse
|
|
||||||
* \param[out] err Return location for an error
|
|
||||||
* \return The command or NULL when \a err is set.
|
|
||||||
*
|
|
||||||
* Parses a command line, \a line, and return the parsed command.
|
|
||||||
* A command can later be executed with \ref pw_command_run()
|
|
||||||
*
|
|
||||||
* \memberof pw_command
|
|
||||||
*/
|
|
||||||
SPA_EXPORT
|
|
||||||
struct pw_command *pw_command_parse(struct pw_properties *properties, const char *line, char **err)
|
|
||||||
{
|
|
||||||
struct pw_command *command = NULL;
|
|
||||||
const struct command_parse *parse;
|
|
||||||
char *name;
|
|
||||||
size_t len;
|
|
||||||
|
|
||||||
len = strcspn(line, whitespace);
|
|
||||||
|
|
||||||
name = strndup(line, len);
|
|
||||||
|
|
||||||
for (parse = parsers; parse->name != NULL; parse++) {
|
|
||||||
if (strcmp(name, parse->name) == 0) {
|
|
||||||
command = parse->func(properties, line, err);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*err = spa_aprintf("Command \"%s\" does not exist", name);
|
|
||||||
out:
|
|
||||||
free(name);
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Run a command
|
|
||||||
*
|
|
||||||
* \param command A \ref pw_command
|
|
||||||
* \param context A \ref pw_context
|
|
||||||
* \param err Return location for an error string, or NULL
|
|
||||||
* \return 0 on success, < 0 on error
|
|
||||||
*
|
|
||||||
* \memberof pw_command
|
|
||||||
*/
|
|
||||||
SPA_EXPORT
|
|
||||||
int pw_command_run(struct pw_command *command, struct pw_context *context, char **err)
|
|
||||||
{
|
|
||||||
return command->func(command, context, err);
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/* PipeWire
|
|
||||||
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
|
|
||||||
* @author Linus Svensson <linus.svensson@axis.com>
|
|
||||||
* Copyright © 2018 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PIPEWIRE_COMMAND_H
|
|
||||||
#define PIPEWIRE_COMMAND_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <pipewire/context.h>
|
|
||||||
|
|
||||||
struct pw_command;
|
|
||||||
|
|
||||||
typedef int (*pw_command_func_t) (struct pw_command *command, struct pw_context *context, char **err);
|
|
||||||
|
|
||||||
/** \class pw_command
|
|
||||||
*
|
|
||||||
* A configuration command
|
|
||||||
*/
|
|
||||||
struct pw_command {
|
|
||||||
struct spa_list link; /**< link in list of commands */
|
|
||||||
pw_command_func_t func;
|
|
||||||
char **args;
|
|
||||||
uint32_t id; /**< id of command */
|
|
||||||
int n_args;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pw_command *
|
|
||||||
pw_command_parse(struct pw_properties *properties, const char *line, char **err);
|
|
||||||
|
|
||||||
void
|
|
||||||
pw_command_free(struct pw_command *command);
|
|
||||||
|
|
||||||
int pw_command_run(struct pw_command *command, struct pw_context *context, char **err);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* PIPEWIRE_COMMAND_H */
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
/* PipeWire
|
|
||||||
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
|
|
||||||
* @author Linus Svensson <linus.svensson@axis.com>
|
|
||||||
* Copyright © 2018 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include "config.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
|
||||||
|
|
||||||
#include "daemon/command.h"
|
|
||||||
#include "daemon/daemon-config.h"
|
|
||||||
|
|
||||||
#define DEFAULT_CONFIG_FILE PIPEWIRE_CONFIG_DIR "/pipewire.conf"
|
|
||||||
|
|
||||||
static int
|
|
||||||
parse_line(struct pw_daemon_config *config,
|
|
||||||
const char *filename, char *line, unsigned int lineno, char **err)
|
|
||||||
{
|
|
||||||
struct pw_command *command = NULL;
|
|
||||||
char *p;
|
|
||||||
char *local_err = NULL;
|
|
||||||
|
|
||||||
/* search for comments */
|
|
||||||
if ((p = strchr(line, '#')))
|
|
||||||
*p = '\0';
|
|
||||||
|
|
||||||
/* remove whitespaces */
|
|
||||||
line = pw_strip(line, "\n\r \t");
|
|
||||||
if (*line == '\0') /* empty line */
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
if ((command = pw_command_parse(config->properties, line, &local_err)) == NULL)
|
|
||||||
goto error_parse;
|
|
||||||
|
|
||||||
spa_list_append(&config->commands, &command->link);
|
|
||||||
|
|
||||||
out:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error_parse:
|
|
||||||
*err = spa_aprintf("%s:%u: %s", filename, lineno, local_err);
|
|
||||||
free(local_err);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pw_daemon_config_new:
|
|
||||||
*
|
|
||||||
* Returns a new empty #struct pw_daemon_config.
|
|
||||||
*/
|
|
||||||
struct pw_daemon_config *pw_daemon_config_new(struct pw_properties *properties)
|
|
||||||
{
|
|
||||||
struct pw_daemon_config *config;
|
|
||||||
|
|
||||||
config = calloc(1, sizeof(struct pw_daemon_config));
|
|
||||||
if (config == NULL)
|
|
||||||
goto error_exit;
|
|
||||||
|
|
||||||
config->properties = properties;
|
|
||||||
spa_list_init(&config->commands);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
|
|
||||||
error_exit:
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pw_daemon_config_free:
|
|
||||||
* @config: A #struct pw_daemon_config
|
|
||||||
*
|
|
||||||
* Free all resources associated to @config.
|
|
||||||
*/
|
|
||||||
void pw_daemon_config_free(struct pw_daemon_config *config)
|
|
||||||
{
|
|
||||||
struct pw_command *cmd;
|
|
||||||
|
|
||||||
spa_list_consume(cmd, &config->commands, link)
|
|
||||||
pw_command_free(cmd);
|
|
||||||
|
|
||||||
free(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pw_daemon_config_load_file:
|
|
||||||
* @config: A #struct pw_daemon_config
|
|
||||||
* @filename: A filename
|
|
||||||
* @err: Return location for an error string
|
|
||||||
*
|
|
||||||
* Loads PipeWire config from @filename.
|
|
||||||
*
|
|
||||||
* Returns: 0 on success, otherwise < 0 and @err is set.
|
|
||||||
*/
|
|
||||||
int pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err)
|
|
||||||
{
|
|
||||||
unsigned int line;
|
|
||||||
FILE *f;
|
|
||||||
char buf[4096];
|
|
||||||
|
|
||||||
pw_log_debug("deamon-config %p: loading configuration file '%s'", config, filename);
|
|
||||||
|
|
||||||
if ((f = fopen(filename, "r")) == NULL) {
|
|
||||||
*err = spa_aprintf("failed to open configuration file '%s': %s", filename,
|
|
||||||
strerror(errno));
|
|
||||||
goto open_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
line = 0;
|
|
||||||
|
|
||||||
while (!feof(f)) {
|
|
||||||
if (!fgets(buf, sizeof(buf), f)) {
|
|
||||||
if (feof(f))
|
|
||||||
break;
|
|
||||||
*err = spa_aprintf("failed to read configuration file '%s': %s",
|
|
||||||
filename, strerror(errno));
|
|
||||||
goto read_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
line++;
|
|
||||||
|
|
||||||
if (parse_line(config, filename, buf, line, err) != 0)
|
|
||||||
goto parse_failed;
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
parse_failed:
|
|
||||||
read_error:
|
|
||||||
fclose(f);
|
|
||||||
open_error:
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pw_daemon_config_load:
|
|
||||||
* @config: A #struct pw_daemon_config
|
|
||||||
* @err: Return location for a #GError, or %NULL
|
|
||||||
*
|
|
||||||
* Loads the default config file for PipeWire. The filename can be overridden with
|
|
||||||
* an environment variable PIPEWIRE_CONFIG_FILE.
|
|
||||||
*
|
|
||||||
* Return: 0 on success, otherwise < 0 and @err is set.
|
|
||||||
*/
|
|
||||||
int pw_daemon_config_load(struct pw_daemon_config *config, char **err)
|
|
||||||
{
|
|
||||||
const char *filename;
|
|
||||||
|
|
||||||
filename = getenv("PIPEWIRE_CONFIG_FILE");
|
|
||||||
if (filename != NULL && *filename != '\0') {
|
|
||||||
pw_log_debug("PIPEWIRE_CONFIG_FILE set to: %s", filename);
|
|
||||||
} else {
|
|
||||||
filename = DEFAULT_CONFIG_FILE;
|
|
||||||
}
|
|
||||||
return pw_daemon_config_load_file(config, filename, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pw_daemon_config_run_commands:
|
|
||||||
* @config: A #struct pw_daemon_config
|
|
||||||
* @context: A #struct pw_context
|
|
||||||
*
|
|
||||||
* Run all commands that have been parsed. The list of commands will be cleared
|
|
||||||
* when this function has been called.
|
|
||||||
*
|
|
||||||
* Returns: 0 if all commands where executed with success, otherwise < 0.
|
|
||||||
*/
|
|
||||||
int pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_context *context)
|
|
||||||
{
|
|
||||||
char *err = NULL;
|
|
||||||
int ret = 0;
|
|
||||||
struct pw_command *command;
|
|
||||||
|
|
||||||
spa_list_for_each(command, &config->commands, link) {
|
|
||||||
if ((ret = pw_command_run(command, context, &err)) < 0) {
|
|
||||||
pw_log_error("could not run command %s: %s", command->args[0], err);
|
|
||||||
free(err);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spa_list_consume(command, &config->commands, link)
|
|
||||||
pw_command_free(command);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
/* PipeWire
|
|
||||||
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
|
|
||||||
* @author Linus Svensson <linus.svensson@axis.com>
|
|
||||||
* Copyright © 2018 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PIPEWIRE_DAEMON_CONFIG_H
|
|
||||||
#define PIPEWIRE_DAEMON_CONFIG_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <pipewire/context.h>
|
|
||||||
|
|
||||||
struct pw_daemon_config {
|
|
||||||
struct spa_list commands;
|
|
||||||
struct pw_properties *properties;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pw_daemon_config * pw_daemon_config_new(struct pw_properties *properties);
|
|
||||||
|
|
||||||
void pw_daemon_config_free(struct pw_daemon_config *config);
|
|
||||||
|
|
||||||
int pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err);
|
|
||||||
|
|
||||||
int pw_daemon_config_load(struct pw_daemon_config *config, char **err);
|
|
||||||
|
|
||||||
int pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_context *context);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* PIPEWIRE_DAEMON_CONFIG_H */
|
|
||||||
|
|
@ -24,39 +24,341 @@
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include <spa/utils/result.h>
|
#include <spa/utils/result.h>
|
||||||
|
#include <spa/utils/json.h>
|
||||||
|
|
||||||
#include <pipewire/impl.h>
|
#include <pipewire/impl.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "daemon-config.h"
|
|
||||||
|
|
||||||
static const char *daemon_name;
|
#define NAME "daemon"
|
||||||
|
|
||||||
|
#define DEFAULT_CONFIG_FILE "pipewire.conf"
|
||||||
|
|
||||||
|
struct data {
|
||||||
|
struct pw_context *context;
|
||||||
|
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 {
|
||||||
|
pw_log_error(NAME" %p: could not load module \"%s\": %m",
|
||||||
|
d, key);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
char *args = NULL, *flags = 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 (strcmp(arg, "flags") == 0) {
|
||||||
|
flags = strdup(aval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
char *args = NULL, *flags = 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 (strcmp(arg, "flags") == 0) {
|
||||||
|
flags = strdup(aval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
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)
|
static void do_quit(void *data, int signal_number)
|
||||||
{
|
{
|
||||||
struct pw_main_loop *loop = data;
|
struct data *d = data;
|
||||||
pw_main_loop_quit(loop);
|
pw_main_loop_quit(d->loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_help(const char *name)
|
static void show_help(struct data *d, const char *name)
|
||||||
{
|
{
|
||||||
fprintf(stdout, "%s [options]\n"
|
fprintf(stdout, "%s [options]\n"
|
||||||
" -h, --help Show this help\n"
|
" -h, --help Show this help\n"
|
||||||
" --version Show version\n"
|
" --version Show version\n"
|
||||||
" -n, --name Daemon name (Default %s)\n",
|
" -n, --name Daemon name (Default %s)\n",
|
||||||
name,
|
name,
|
||||||
daemon_name);
|
d->daemon_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct pw_context *context;
|
struct data d;
|
||||||
struct pw_main_loop *loop;
|
|
||||||
struct pw_daemon_config *config;
|
|
||||||
struct pw_properties *properties;
|
struct pw_properties *properties;
|
||||||
char *err = NULL;
|
const char *str;
|
||||||
static const struct option long_options[] = {
|
static const struct option long_options[] = {
|
||||||
{ "help", no_argument, NULL, 'h' },
|
{ "help", no_argument, NULL, 'h' },
|
||||||
{ "version", no_argument, NULL, 'V' },
|
{ "version", no_argument, NULL, 'V' },
|
||||||
|
|
@ -66,19 +368,30 @@ int main(int argc, char *argv[])
|
||||||
};
|
};
|
||||||
int c, res;
|
int c, res;
|
||||||
|
|
||||||
|
if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0)
|
||||||
|
fprintf(stderr, "can't set PIPEWIRE_INTERNAL env: %m");
|
||||||
|
|
||||||
|
spa_zero(d);
|
||||||
pw_init(&argc, &argv);
|
pw_init(&argc, &argv);
|
||||||
|
|
||||||
if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0)
|
if ((d.conf = pw_properties_new(NULL, NULL)) == NULL) {
|
||||||
fprintf(stderr, "can't PIPEWIRE_INTERNAL env: %m");
|
pw_log_error("failed to create config: %m");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
daemon_name = getenv("PIPEWIRE_CORE");
|
if ((res = load_config(&d)) < 0) {
|
||||||
if (daemon_name == NULL)
|
pw_log_error("failed to load config: %s", spa_strerror(res));
|
||||||
daemon_name = PW_DEFAULT_REMOTE;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
d.daemon_name = getenv("PIPEWIRE_CORE");
|
||||||
|
if (d.daemon_name == NULL)
|
||||||
|
d.daemon_name = PW_DEFAULT_REMOTE;
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, "hVn:", long_options, NULL)) != -1) {
|
while ((c = getopt_long(argc, argv, "hVn:", long_options, NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'h' :
|
case 'h' :
|
||||||
show_help(argv[0]);
|
show_help(&d, argv[0]);
|
||||||
return 0;
|
return 0;
|
||||||
case 'V' :
|
case 'V' :
|
||||||
fprintf(stdout, "%s\n"
|
fprintf(stdout, "%s\n"
|
||||||
|
|
@ -89,8 +402,8 @@ int main(int argc, char *argv[])
|
||||||
pw_get_library_version());
|
pw_get_library_version());
|
||||||
return 0;
|
return 0;
|
||||||
case 'n' :
|
case 'n' :
|
||||||
daemon_name = optarg;
|
d.daemon_name = optarg;
|
||||||
fprintf(stdout, "set name %s\n", daemon_name);
|
fprintf(stdout, "set name %s\n", d.daemon_name);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -98,47 +411,57 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
properties = pw_properties_new(
|
properties = pw_properties_new(
|
||||||
PW_KEY_CORE_NAME, daemon_name,
|
PW_KEY_CORE_NAME, d.daemon_name,
|
||||||
PW_KEY_CONTEXT_PROFILE_MODULES, "none",
|
PW_KEY_CONTEXT_PROFILE_MODULES, "none",
|
||||||
PW_KEY_CORE_DAEMON, "true", NULL);
|
PW_KEY_CORE_DAEMON, "true", NULL);
|
||||||
|
|
||||||
/* parse configuration */
|
/* parse configuration */
|
||||||
config = pw_daemon_config_new(properties);
|
if ((str = pw_properties_get(d.conf, "properties")) != NULL)
|
||||||
if (pw_daemon_config_load(config, &err) < 0) {
|
pw_properties_update_string(properties, str, strlen(str));
|
||||||
pw_log_error("failed to parse config: %s", err);
|
|
||||||
free(err);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
d.loop = pw_main_loop_new(&properties->dict);
|
||||||
loop = pw_main_loop_new(&properties->dict);
|
if (d.loop == NULL) {
|
||||||
if (loop == NULL) {
|
|
||||||
pw_log_error("failed to create main-loop: %m");
|
pw_log_error("failed to create main-loop: %m");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGINT, do_quit, loop);
|
pw_loop_add_signal(pw_main_loop_get_loop(d.loop), SIGINT, do_quit, &d);
|
||||||
pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGTERM, do_quit, loop);
|
pw_loop_add_signal(pw_main_loop_get_loop(d.loop), SIGTERM, do_quit, &d);
|
||||||
|
|
||||||
context = pw_context_new(pw_main_loop_get_loop(loop), properties, 0);
|
d.context = pw_context_new(pw_main_loop_get_loop(d.loop), properties, 0);
|
||||||
if (context == NULL) {
|
if (d.context == NULL) {
|
||||||
pw_log_error("failed to create context: %m");
|
pw_log_error("failed to create context: %m");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((res = pw_daemon_config_run_commands(config, context)) < 0) {
|
if ((str = pw_properties_get(d.conf, "spa-libs")) != NULL)
|
||||||
pw_log_error("failed to run config commands: %s", spa_strerror(res));
|
parse_spa_libs(&d, str);
|
||||||
pw_main_loop_quit(loop);
|
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;
|
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_log_info("start main loop");
|
||||||
pw_main_loop_run(loop);
|
pw_main_loop_run(d.loop);
|
||||||
pw_log_info("leave main loop");
|
pw_log_info("leave main loop");
|
||||||
|
|
||||||
pw_daemon_config_free(config);
|
pw_properties_free(d.conf);
|
||||||
pw_context_destroy(context);
|
pw_context_destroy(d.context);
|
||||||
pw_main_loop_destroy(loop);
|
pw_main_loop_destroy(d.loop);
|
||||||
pw_deinit();
|
pw_deinit();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
pipewire_daemon_sources = [
|
pipewire_daemon_sources = [
|
||||||
'command.c',
|
|
||||||
'daemon-config.c',
|
|
||||||
'main.c',
|
'main.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
pipewire_daemon_headers = [
|
|
||||||
'command.h',
|
|
||||||
'daemon-config.h',
|
|
||||||
]
|
|
||||||
|
|
||||||
pipewire_c_args = [
|
pipewire_c_args = [
|
||||||
'-DHAVE_CONFIG_H',
|
'-DHAVE_CONFIG_H',
|
||||||
'-D_GNU_SOURCE',
|
'-D_GNU_SOURCE',
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,86 @@
|
||||||
#daemon config file for PipeWire version @VERSION@
|
#daemon config file for PipeWire version @VERSION@
|
||||||
|
{
|
||||||
## set-prop is used to configure properties in the system
|
properties = {
|
||||||
#
|
## configure properties in the system
|
||||||
#set-prop library.name.system support/libspa-support
|
#library.name.system = support/libspa-support
|
||||||
#set-prop context.data-loop.library.name.system support/libspa-support
|
#context.data-loop.library.name.system = support/libspa-support
|
||||||
#set-prop link.max-buffers 64
|
#link.max-buffers = 64
|
||||||
set-prop link.max-buffers 16 # version < 3 clients can't handle more
|
link.max-buffers = 16 # version < 3 clients can't handle more
|
||||||
#set-prop mem.allow-mlock true
|
#mem.allow-mlock = true
|
||||||
#set-prop log.level 2
|
#log.level = 2
|
||||||
|
|
||||||
## Properties for the DSP configuration
|
## Properties for the DSP configuration
|
||||||
#
|
#default.clock.rate = 48000
|
||||||
#set-prop default.clock.rate 48000
|
#default.clock.quantum = 1024
|
||||||
#set-prop default.clock.quantum 1024
|
#default.clock.min-quantum = 32
|
||||||
#set-prop default.clock.min-quantum 32
|
#default.clock.max-quantum = 8192
|
||||||
#set-prop default.clock.max-quantum 8192
|
#default.video.width = 640
|
||||||
#set-prop default.video.width 640
|
#default.video.height = 480
|
||||||
#set-prop default.video.height 480
|
#default.video.rate.num = 25
|
||||||
#set-prop default.video.rate.num 25
|
#default.video.rate.denom = 1
|
||||||
#set-prop default.video.rate.denom 1
|
}
|
||||||
|
|
||||||
## add-spa-lib <factory-name regex> <library-name>
|
spa-libs = {
|
||||||
|
## <factory-name regex> = <library-name>
|
||||||
#
|
#
|
||||||
# used to find spa factory names. It maps an spa factory name
|
# used to find spa factory names. It maps an spa factory name
|
||||||
# regular expression to a library name that should contain
|
# regular expression to a library name that should contain
|
||||||
# that factory.
|
# that factory.
|
||||||
#
|
#
|
||||||
add-spa-lib audio.convert* audioconvert/libspa-audioconvert
|
audio.convert* = audioconvert/libspa-audioconvert
|
||||||
add-spa-lib api.alsa.* alsa/libspa-alsa
|
api.alsa.* = alsa/libspa-alsa
|
||||||
add-spa-lib api.v4l2.* v4l2/libspa-v4l2
|
api.v4l2.* = v4l2/libspa-v4l2
|
||||||
add-spa-lib api.libcamera.* libcamera/libspa-libcamera
|
api.libcamera.* = libcamera/libspa-libcamera
|
||||||
add-spa-lib api.bluez5.* bluez5/libspa-bluez5
|
api.bluez5.* = bluez5/libspa-bluez5
|
||||||
add-spa-lib api.vulkan.* vulkan/libspa-vulkan
|
api.vulkan.* = vulkan/libspa-vulkan
|
||||||
add-spa-lib api.jack.* jack/libspa-jack
|
api.jack.* = jack/libspa-jack
|
||||||
add-spa-lib support.* support/libspa-support
|
support.* = support/libspa-support
|
||||||
#add-spa-lib videotestsrc videotestsrc/libspa-videotestsrc
|
#videotestsrc = videotestsrc/libspa-videotestsrc
|
||||||
#add-spa-lib audiotestsrc audiotestsrc/libspa-audiotestsrc
|
#audiotestsrc = audiotestsrc/libspa-audiotestsrc
|
||||||
|
}
|
||||||
|
|
||||||
## load-module [-ifexists] <module-name> [<key>=<value> ...]
|
modules = {
|
||||||
|
## <module-name> = { [args = "<key>=<value> ..."]
|
||||||
|
# [flags = ifexists] }
|
||||||
#
|
#
|
||||||
# Loads a module with the given parameters. Normally failure is
|
# Loads a module with the given parameters. Normally failure is
|
||||||
# fatal if the module is not found, unless -ifexists is given.
|
# fatal if the module is not found, unless -ifexists is given.
|
||||||
#
|
#
|
||||||
load-module libpipewire-module-rtkit # rt.prio=20 rt.time.soft=200000 rt.time.hard=200000
|
libpipewire-module-rtkit = { "#args" = "rt.prio=20 rt.time.soft=200000 rt.time.hard=200000" flags=ifexists }
|
||||||
load-module libpipewire-module-protocol-native
|
libpipewire-module-protocol-native = null
|
||||||
load-module libpipewire-module-profiler
|
libpipewire-module-profiler = null
|
||||||
load-module libpipewire-module-metadata
|
libpipewire-module-metadata = null
|
||||||
load-module libpipewire-module-spa-device-factory
|
libpipewire-module-spa-device-factory = null
|
||||||
load-module libpipewire-module-spa-node-factory
|
libpipewire-module-spa-node-factory = null
|
||||||
load-module libpipewire-module-client-node
|
libpipewire-module-client-node = null
|
||||||
load-module libpipewire-module-client-device
|
libpipewire-module-client-device = null
|
||||||
load-module libpipewire-module-portal
|
libpipewire-module-portal = null
|
||||||
load-module libpipewire-module-access # access.allowed=@media_session_path@ access.force=flatpak
|
libpipewire-module-access = { "#args" = "access.allowed=@media_session_path@ access.force=flatpak" }
|
||||||
load-module libpipewire-module-adapter
|
libpipewire-module-adapter = null
|
||||||
load-module libpipewire-module-link-factory
|
libpipewire-module-link-factory = null
|
||||||
load-module libpipewire-module-session-manager
|
libpipewire-module-session-manager = null
|
||||||
|
}
|
||||||
|
|
||||||
## create-object [-nofail] <factory-name> [<key>=<value> ...]
|
objects = {
|
||||||
|
## <factory-name> = { [args = "<key>=<value> ..."]
|
||||||
|
# [flags = nofail] }
|
||||||
#
|
#
|
||||||
# Creates an object from a PipeWire factory with the given parameters.
|
# Creates an object from a PipeWire factory with the given parameters.
|
||||||
# If -nofail is given, errors are ignored (and no object is created)
|
# If nofail is given, errors are ignored (and no object is created)
|
||||||
#
|
#
|
||||||
#create-object spa-node-factory factory.name=videotestsrc node.name=videotestsrc Spa:Pod:Object:Param:Props:patternType=1
|
#spa-node-factory = { args = "factory.name=videotestsrc node.name=videotestsrc Spa:Pod:Object:Param:Props:patternType=1" }
|
||||||
#create-object -nofail spa-device-factory factory.name=api.jack.device foo=bar
|
#spa-device-factory = { args = "factory.name=api.jack.device foo=bar" flags = nofail }
|
||||||
#create-object spa-device-factory factory.name=api.alsa.enum.udev
|
#spa-device-factory = { args = "factory.name=api.alsa.enum.udev" }
|
||||||
#create-object spa-device-factory factory.name=api.alsa.seq.bridge node.name=Internal-MIDI-Bridge
|
#spa-device-factory = { args = "factory.name=api.alsa.seq.bridge node.name=Internal-MIDI-Bridge" }
|
||||||
#create-object adapter factory.name=audiotestsrc node.name=my-test
|
#adapter = { args = "factory.name=audiotestsrc node.name=my-test" }
|
||||||
#create-object spa-node-factory factory.name=api.vulkan.compute.source node.name=my-compute-source
|
#spa-node-factory = { args = "factory.name=api.vulkan.compute.source node.name=my-compute-source" }
|
||||||
create-object spa-node-factory factory.name=support.node.driver node.name=Dummy priority.driver=8000
|
spa-node-factory = { args = "factory.name=support.node.driver node.name=Dummy priority.driver=8000" }
|
||||||
|
}
|
||||||
|
|
||||||
## exec <program-name>
|
exec = {
|
||||||
|
## <program-name> = { [args = "<arguments>"] }
|
||||||
#
|
#
|
||||||
# Execute the given program.
|
# Execute the given program with arguments.
|
||||||
#
|
#
|
||||||
# Start the session manager. Run the session manager with -h for
|
# Start the session manager. Run the session manager with -h for
|
||||||
# options.
|
# options.
|
||||||
|
|
@ -80,11 +89,13 @@ create-object spa-node-factory factory.name=support.node.driver node.name=Dummy
|
||||||
# conflicts with PulseAudio. If you disable PulseAudio or don't
|
# conflicts with PulseAudio. If you disable PulseAudio or don't
|
||||||
# load its bluetooth module, you can enable it here with -e bluez5
|
# load its bluetooth module, you can enable it here with -e bluez5
|
||||||
#
|
#
|
||||||
exec @media_session_path@
|
"@media_session_path@" = { args = ""}
|
||||||
#
|
#
|
||||||
# You can optionally start the pulseaudio-server here as well
|
# You can optionally start the pulseaudio-server here as well
|
||||||
# but it better to start it as a systemd service.
|
# but it better to start it as a systemd service.
|
||||||
# It can be interesting to start another daemon here that listens
|
# It can be interesting to start another daemon here that listens
|
||||||
# on another address with the -a option (eg. -a tcp:4713)
|
# on another address with the -a option (eg. -a tcp:4713)
|
||||||
#
|
#
|
||||||
#exec @pipewire_pulse_path@
|
#"@pipewire_pulse_path@" = { "#args" = "-a tcp:4713" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue