mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-16 08:56:45 -05:00
Implement config/state file handling
Make methods to load_config and load/save state. For now the config and state directories are the same but it might not be. Implement the search path for all config/state files as: $XDG_CONFIG_HOME/[$prefix]/$name $HOME/.config/[$prefix]/$name $PIPEWIRE_CONFIG_DIR/pipewire/[$prefix]/$name /etc/pipewire/[$prefix]/$name Make some config files for jack and RT clients. Make pw-cat use the client-rt config. Use core state and config management in media-session. Move all session manager state and config files to the build dir and set the PIPEWIRE_CONFIG_DIR to this build dir.
This commit is contained in:
parent
c605672d43
commit
fc90a4e48a
13 changed files with 380 additions and 216 deletions
|
|
@ -14,7 +14,7 @@ run: all
|
||||||
SPA_PLUGIN_DIR=@BUILD_ROOT@/spa/plugins \
|
SPA_PLUGIN_DIR=@BUILD_ROOT@/spa/plugins \
|
||||||
PIPEWIRE_MODULE_DIR=@BUILD_ROOT@/src/modules/ \
|
PIPEWIRE_MODULE_DIR=@BUILD_ROOT@/src/modules/ \
|
||||||
PATH=@BUILD_ROOT@/src/examples:$(PATH) \
|
PATH=@BUILD_ROOT@/src/examples:$(PATH) \
|
||||||
PIPEWIRE_CONFIG_DIR=@SOURCE_ROOT@/src/daemon/ \
|
PIPEWIRE_CONFIG_DIR=@BUILD_ROOT@/src/daemon/ \
|
||||||
ACP_PATHS_DIR=@SOURCE_ROOT@/spa/plugins/alsa/mixer/paths \
|
ACP_PATHS_DIR=@SOURCE_ROOT@/spa/plugins/alsa/mixer/paths \
|
||||||
ACP_PROFILES_DIR=@SOURCE_ROOT@/spa/plugins/alsa/mixer/profile-sets \
|
ACP_PROFILES_DIR=@SOURCE_ROOT@/spa/plugins/alsa/mixer/profile-sets \
|
||||||
$(DBG) @BUILD_ROOT@/src/daemon/pipewire
|
$(DBG) @BUILD_ROOT@/src/daemon/pipewire
|
||||||
|
|
|
||||||
|
|
@ -2410,15 +2410,11 @@ jack_client_t * jack_client_open (const char *client_name,
|
||||||
if (client->props == NULL)
|
if (client->props == NULL)
|
||||||
goto no_props;
|
goto no_props;
|
||||||
|
|
||||||
if (pw_properties_get(client->props, PW_KEY_CONTEXT_PROFILE_MODULES) == NULL) {
|
|
||||||
if ((str = getenv("PIPEWIRE_PROFILE_MODULES")) == NULL)
|
|
||||||
str = "default,rtkit";
|
|
||||||
pw_properties_set(client->props, PW_KEY_CONTEXT_PROFILE_MODULES, str);
|
|
||||||
}
|
|
||||||
pw_properties_set(client->props, "loop.cancel", "true");
|
pw_properties_set(client->props, "loop.cancel", "true");
|
||||||
pw_properties_set(client->props, PW_KEY_REMOTE_NAME, client->server_name);
|
pw_properties_set(client->props, PW_KEY_REMOTE_NAME, client->server_name);
|
||||||
pw_properties_set(client->props, PW_KEY_CLIENT_NAME, client_name);
|
pw_properties_set(client->props, PW_KEY_CLIENT_NAME, client_name);
|
||||||
pw_properties_set(client->props, PW_KEY_CLIENT_API, "jack");
|
pw_properties_set(client->props, PW_KEY_CLIENT_API, "jack");
|
||||||
|
pw_properties_set(client->props, PW_KEY_CONFIG_NAME, "jack.conf");
|
||||||
|
|
||||||
client->node_id = SPA_ID_INVALID;
|
client->node_id = SPA_ID_INVALID;
|
||||||
strncpy(client->name, client_name, JACK_CLIENT_NAME_SIZE);
|
strncpy(client->name, client_name, JACK_CLIENT_NAME_SIZE);
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ if [ ! -d ${BUILDDIR} ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# the config file read by the daemon
|
# the config file read by the daemon
|
||||||
export PIPEWIRE_CONFIG_DIR="src/daemon/"
|
export PIPEWIRE_CONFIG_DIR="${BUILDDIR}/src/daemon"
|
||||||
# the directory with SPA plugins
|
# the directory with SPA plugins
|
||||||
export SPA_PLUGIN_DIR="${BUILDDIR}/spa/plugins"
|
export SPA_PLUGIN_DIR="${BUILDDIR}/spa/plugins"
|
||||||
# the directory with pipewire modules
|
# the directory with pipewire modules
|
||||||
|
|
|
||||||
63
src/daemon/client-rt.conf.in
Normal file
63
src/daemon/client-rt.conf.in
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Daemon config file for PipeWire RT 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 = {
|
||||||
|
## <factory-name regex> = <library-name>
|
||||||
|
#
|
||||||
|
# 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 = {
|
||||||
|
## <module-name> = { [args = { <key>=<value> ... }]
|
||||||
|
# [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
|
||||||
|
}
|
||||||
52
src/daemon/jack.conf.in
Normal file
52
src/daemon/jack.conf.in
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Daemon config file for PipeWire JACK 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 = {
|
||||||
|
## <factory-name regex> = <library-name>
|
||||||
|
#
|
||||||
|
# Used to find spa factory names. It maps an spa factory name
|
||||||
|
# regular expression to a library name that should contain
|
||||||
|
# that factory.
|
||||||
|
#
|
||||||
|
support.* = support/libspa-support
|
||||||
|
}
|
||||||
|
|
||||||
|
modules = {
|
||||||
|
## <module-name> = { [args = { <key>=<value> ... }]
|
||||||
|
# [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 applications to create metadata objects. It creates
|
||||||
|
# a factory for Metadata objects.
|
||||||
|
libpipewire-module-metadata = null
|
||||||
|
}
|
||||||
21
src/daemon/media-session.d/meson.build
Normal file
21
src/daemon/media-session.d/meson.build
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
conf_files = [
|
||||||
|
[ 'bluez-monitor.conf', 'bluez-monitor.conf' ],
|
||||||
|
[ 'v4l2-monitor.conf', 'v4l2-monitor.conf' ],
|
||||||
|
[ 'media-session.conf', 'media-session.conf' ],
|
||||||
|
[ 'alsa-monitor.conf', 'alsa-monitor.conf' ],
|
||||||
|
[ 'with-jack', 'with-jack' ],
|
||||||
|
[ 'with-pulseaudio', 'with-pulseaudio' ],
|
||||||
|
]
|
||||||
|
|
||||||
|
foreach c : conf_files
|
||||||
|
configure_file(input : c.get(0),
|
||||||
|
output : c.get(1),
|
||||||
|
configuration : conf_config,
|
||||||
|
install_dir : join_paths(conf_install_dir, 'media-session.d'))
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
install_data(
|
||||||
|
sources : [
|
||||||
|
'with-jack',
|
||||||
|
'with-pulseaudio' ],
|
||||||
|
install_dir : join_paths(conf_install_dir, 'media-session.d'))
|
||||||
|
|
@ -14,36 +14,27 @@ conf_config.set('media_session_path', join_paths(pipewire_bindir, 'pipewire-medi
|
||||||
conf_config.set('pipewire_pulse_path', join_paths(pipewire_bindir, 'pipewire-pulse'))
|
conf_config.set('pipewire_pulse_path', join_paths(pipewire_bindir, 'pipewire-pulse'))
|
||||||
conf_install_dir = join_paths(get_option('sysconfdir'), 'pipewire')
|
conf_install_dir = join_paths(get_option('sysconfdir'), 'pipewire')
|
||||||
|
|
||||||
install_data(
|
|
||||||
sources : [
|
|
||||||
'media-session.d/with-jack',
|
|
||||||
'media-session.d/with-pulseaudio',
|
|
||||||
'media-session.d/bluez-monitor.conf',
|
|
||||||
'media-session.d/v4l2-monitor.conf',
|
|
||||||
'media-session.d/media-session.conf',
|
|
||||||
'media-session.d/alsa-monitor.conf' ],
|
|
||||||
install_dir : join_paths(conf_install_dir, 'media-session.d'))
|
|
||||||
|
|
||||||
conf_config_uninstalled = conf_config
|
conf_config_uninstalled = conf_config
|
||||||
conf_config_uninstalled.set('media_session_path',
|
conf_config_uninstalled.set('media_session_path',
|
||||||
join_paths(meson.build_root(), 'src', 'examples', 'pipewire-media-session'))
|
join_paths(meson.build_root(), 'src', 'examples', 'pipewire-media-session'))
|
||||||
conf_config_uninstalled.set('pipewire_pulse_path',
|
conf_config_uninstalled.set('pipewire_pulse_path',
|
||||||
join_paths(meson.build_root(), 'src', 'daemon', 'pipewire-pulse'))
|
join_paths(meson.build_root(), 'src', 'daemon', 'pipewire-pulse'))
|
||||||
|
|
||||||
configure_file(input : 'pipewire.conf.in',
|
|
||||||
output : 'pipewire.conf',
|
|
||||||
configuration : conf_config,
|
|
||||||
install_dir : conf_install_dir)
|
|
||||||
|
|
||||||
configure_file(input : 'client.conf.in',
|
conf_files = [
|
||||||
output : 'client.conf',
|
[ 'pipewire.conf.in', 'pipewire.conf' ],
|
||||||
configuration : conf_config,
|
[ 'client.conf.in', 'client.conf' ],
|
||||||
install_dir : conf_install_dir)
|
[ 'client-rt.conf.in', 'client-rt.conf' ],
|
||||||
|
[ 'jack.conf.in', 'jack.conf' ],
|
||||||
|
[ 'pipewire-pulse.conf.in', 'pipewire-pulse.conf' ]
|
||||||
|
]
|
||||||
|
|
||||||
configure_file(input : 'pipewire-pulse.conf.in',
|
foreach c : conf_files
|
||||||
output : 'pipewire-pulse.conf',
|
configure_file(input : c.get(0),
|
||||||
|
output : c.get(1),
|
||||||
configuration : conf_config,
|
configuration : conf_config,
|
||||||
install_dir : conf_install_dir)
|
install_dir : conf_install_dir)
|
||||||
|
endforeach
|
||||||
|
|
||||||
configure_file(input : 'pipewire.conf.in',
|
configure_file(input : 'pipewire.conf.in',
|
||||||
output : 'pipewire-uninstalled.conf',
|
output : 'pipewire-uninstalled.conf',
|
||||||
|
|
@ -81,6 +72,7 @@ executable('pipewire-pulse',
|
||||||
# )
|
# )
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
subdir('media-session.d')
|
||||||
if get_option('systemd') and systemd.found()
|
if get_option('systemd') and systemd.found()
|
||||||
subdir('systemd')
|
subdir('systemd')
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,6 @@ struct impl {
|
||||||
int rescan_seq;
|
int rescan_seq;
|
||||||
int last_seq;
|
int last_seq;
|
||||||
|
|
||||||
int state_dir_fd;
|
|
||||||
char state_dir[PATH_MAX];
|
|
||||||
|
|
||||||
unsigned int scanning:1;
|
unsigned int scanning:1;
|
||||||
unsigned int rescan_pending:1;
|
unsigned int rescan_pending:1;
|
||||||
};
|
};
|
||||||
|
|
@ -1786,171 +1783,19 @@ int sm_media_session_remove_links(struct sm_media_session *sess,
|
||||||
int sm_media_session_load_conf(struct sm_media_session *sess, const char *name,
|
int sm_media_session_load_conf(struct sm_media_session *sess, const char *name,
|
||||||
struct pw_properties *conf)
|
struct pw_properties *conf)
|
||||||
{
|
{
|
||||||
const char *dir;
|
return pw_conf_load_conf(SESSION_PREFIX, name, conf);
|
||||||
char path[PATH_MAX];
|
|
||||||
int count, fd;
|
|
||||||
struct stat sbuf;
|
|
||||||
char *data;
|
|
||||||
|
|
||||||
if ((count = sm_media_session_load_state(sess, name, NULL, conf)) >= 0)
|
|
||||||
return count;
|
|
||||||
|
|
||||||
if ((dir = getenv("PIPEWIRE_CONFIG_DIR")) == NULL)
|
|
||||||
dir = PIPEWIRE_CONFIG_DIR;
|
|
||||||
if (dir == NULL)
|
|
||||||
return -ENOENT;
|
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "%s/media-session.d/%s", dir, name);
|
|
||||||
if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) {
|
|
||||||
pw_log_warn(NAME" %p: error loading config '%s': %m", sess, path);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
pw_log_info(NAME" %p: loading config '%s'", sess, 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);
|
|
||||||
|
|
||||||
count = pw_properties_update_string(conf, data, sbuf.st_size);
|
|
||||||
munmap(data, sbuf.st_size);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
|
|
||||||
error_close:
|
|
||||||
pw_log_debug("can't read file %s: %m", path);
|
|
||||||
close(fd);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int state_dir(struct sm_media_session *sess)
|
|
||||||
{
|
|
||||||
struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this);
|
|
||||||
const char *home_dir;
|
|
||||||
int res;
|
|
||||||
|
|
||||||
if (impl->state_dir_fd != -1)
|
|
||||||
return impl->state_dir_fd;
|
|
||||||
|
|
||||||
home_dir = getenv("XDG_CONFIG_HOME");
|
|
||||||
if (home_dir != NULL)
|
|
||||||
snprintf(impl->state_dir, sizeof(impl->state_dir),
|
|
||||||
"%s/pipewire-media-session/", home_dir);
|
|
||||||
else {
|
|
||||||
home_dir = getenv("HOME");
|
|
||||||
if (home_dir == NULL)
|
|
||||||
home_dir = getenv("USERPROFILE");
|
|
||||||
if (home_dir == NULL) {
|
|
||||||
struct passwd pwd, *result = NULL;
|
|
||||||
char buffer[4096];
|
|
||||||
if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0)
|
|
||||||
home_dir = result ? result->pw_dir : NULL;
|
|
||||||
}
|
|
||||||
if (home_dir == NULL) {
|
|
||||||
pw_log_error("Can't determine home directory");
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
snprintf(impl->state_dir, sizeof(impl->state_dir),
|
|
||||||
"%s/.config/pipewire-media-session/", home_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef O_PATH
|
|
||||||
#define O_PATH 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if ((res = open(impl->state_dir, O_CLOEXEC | O_DIRECTORY | O_PATH)) < 0) {
|
|
||||||
if (errno == ENOENT) {
|
|
||||||
pw_log_info("creating state directory %s", impl->state_dir);
|
|
||||||
if (mkdir(impl->state_dir, 0700) < 0) {
|
|
||||||
pw_log_info("Can't create state directory %s: %m", impl->state_dir);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pw_log_error("Can't open state directory %s: %m", impl->state_dir);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
if ((res = open(impl->state_dir, O_CLOEXEC | O_DIRECTORY | O_PATH)) < 0) {
|
|
||||||
pw_log_error("Can't open state directory %s: %m", impl->state_dir);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl->state_dir_fd = res;
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int sm_media_session_load_state(struct sm_media_session *sess,
|
int sm_media_session_load_state(struct sm_media_session *sess,
|
||||||
const char *name, const char *prefix, struct pw_properties *props)
|
const char *name, const char *prefix, struct pw_properties *props)
|
||||||
{
|
{
|
||||||
struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this);
|
return pw_conf_load_state(SESSION_PREFIX, name, props);
|
||||||
int count, sfd, fd;
|
|
||||||
struct stat sbuf;
|
|
||||||
void *data;
|
|
||||||
|
|
||||||
if ((sfd = state_dir(sess)) < 0)
|
|
||||||
return sfd;
|
|
||||||
|
|
||||||
if ((fd = openat(sfd, name, O_CLOEXEC | O_RDONLY)) < 0) {
|
|
||||||
pw_log_debug("can't open file %s%s: %m", impl->state_dir, name);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
pw_log_info(NAME" %p: loading state '%s%s'", sess, impl->state_dir, name);
|
|
||||||
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);
|
|
||||||
|
|
||||||
count = pw_properties_update_string(props, data, sbuf.st_size);
|
|
||||||
munmap(data, sbuf.st_size);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
|
|
||||||
error_close:
|
|
||||||
pw_log_debug("can't read file %s: %m", name);
|
|
||||||
close(fd);
|
|
||||||
return -errno;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int sm_media_session_save_state(struct sm_media_session *sess,
|
int sm_media_session_save_state(struct sm_media_session *sess,
|
||||||
const char *name, const char *prefix, const struct pw_properties *props)
|
const char *name, const char *prefix, const struct pw_properties *props)
|
||||||
{
|
{
|
||||||
const struct spa_dict_item *it;
|
return pw_conf_save_state(SESSION_PREFIX, name, props);
|
||||||
char *tmp_name;
|
|
||||||
int sfd, fd;
|
|
||||||
FILE *f;
|
|
||||||
|
|
||||||
pw_log_info(NAME" %p: saving state '%s'", sess, name);
|
|
||||||
if ((sfd = state_dir(sess)) < 0)
|
|
||||||
return sfd;
|
|
||||||
|
|
||||||
tmp_name = alloca(strlen(name)+5);
|
|
||||||
sprintf(tmp_name, "%s.tmp", name);
|
|
||||||
if ((fd = openat(sfd, tmp_name, O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0700)) < 0) {
|
|
||||||
pw_log_error("can't open file '%s': %m", tmp_name);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
f = fdopen(fd, "w");
|
|
||||||
fprintf(f, "{ \n");
|
|
||||||
spa_dict_for_each(it, &props->dict) {
|
|
||||||
char key[1024];
|
|
||||||
if (prefix != NULL && strstr(it->key, prefix) != it->key)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (spa_json_encode_string(key, sizeof(key)-1, it->key) >= (int)sizeof(key)-1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
fprintf(f, " %s: %s\n", key, it->value);
|
|
||||||
}
|
|
||||||
fprintf(f, "}\n");
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
if (renameat(sfd, tmp_name, sfd, name) < 0) {
|
|
||||||
pw_log_error("can't rename temp file '%s': %m", tmp_name);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void monitor_core_done(void *data, uint32_t id, int seq)
|
static void monitor_core_done(void *data, uint32_t id, int seq)
|
||||||
|
|
@ -2170,7 +2015,7 @@ again:
|
||||||
add = true;
|
add = true;
|
||||||
} else {
|
} else {
|
||||||
snprintf(check_path, sizeof(check_path),
|
snprintf(check_path, sizeof(check_path),
|
||||||
"%s/media-session.d/%s", dir, key);
|
"%s/"SESSION_PREFIX"/%s", dir, key);
|
||||||
add = (stat(check_path, &statbuf) == 0);
|
add = (stat(check_path, &statbuf) == 0);
|
||||||
}
|
}
|
||||||
if (add) {
|
if (add) {
|
||||||
|
|
@ -2259,7 +2104,6 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
pw_init(&argc, &argv);
|
pw_init(&argc, &argv);
|
||||||
|
|
||||||
impl.state_dir_fd = -1;
|
|
||||||
impl.this.props = pw_properties_new(
|
impl.this.props = pw_properties_new(
|
||||||
PW_KEY_CONFIG_PREFIX, SESSION_PREFIX,
|
PW_KEY_CONFIG_PREFIX, SESSION_PREFIX,
|
||||||
PW_KEY_CONFIG_NAME, SESSION_CONF,
|
PW_KEY_CONFIG_NAME, SESSION_CONF,
|
||||||
|
|
@ -2270,14 +2114,14 @@ int main(int argc, char *argv[])
|
||||||
if ((impl.conf = pw_properties_new(NULL, NULL)) == NULL)
|
if ((impl.conf = pw_properties_new(NULL, NULL)) == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
pw_conf_load(SESSION_PREFIX, SESSION_CONF, impl.conf);
|
pw_conf_load_conf(SESSION_PREFIX, SESSION_CONF, impl.conf);
|
||||||
|
|
||||||
if ((str = pw_properties_get(impl.conf, "properties")) != NULL)
|
if ((str = pw_properties_get(impl.conf, "properties")) != NULL)
|
||||||
pw_properties_update_string(impl.this.props, str, strlen(str));
|
pw_properties_update_string(impl.this.props, str, strlen(str));
|
||||||
|
|
||||||
if ((impl.modules = pw_properties_new("default", "true", NULL)) == NULL)
|
if ((impl.modules = pw_properties_new("default", "true", NULL)) == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
if ((str = pw_properties_get(impl.conf, "session-modules")) != NULL)
|
if ((str = pw_properties_get(impl.conf, "session.modules")) != NULL)
|
||||||
collect_modules(&impl, str);
|
collect_modules(&impl, str);
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, "hV", long_options, NULL)) != -1) {
|
while ((c = getopt_long(argc, argv, "hV", long_options, NULL)) != -1) {
|
||||||
|
|
@ -2368,9 +2212,6 @@ exit:
|
||||||
pw_properties_free(impl.conf);
|
pw_properties_free(impl.conf);
|
||||||
pw_properties_free(impl.modules);
|
pw_properties_free(impl.modules);
|
||||||
|
|
||||||
if (impl.state_dir_fd != -1)
|
|
||||||
close(impl.state_dir_fd);
|
|
||||||
|
|
||||||
pw_deinit();
|
pw_deinit();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
* DEALINGS IN THE SOFTWARE.
|
* DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
@ -31,38 +33,205 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
#if HAVE_PWD_H
|
||||||
|
#include <pwd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <spa/utils/result.h>
|
#include <spa/utils/result.h>
|
||||||
#include <spa/utils/json.h>
|
#include <spa/utils/json.h>
|
||||||
|
|
||||||
#include <pipewire/impl.h>
|
#include <pipewire/impl.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#define NAME "config"
|
#define NAME "config"
|
||||||
|
|
||||||
SPA_EXPORT
|
static int make_path(char *path, int size, const char *paths[])
|
||||||
int pw_conf_load(const char *prefix, const char *name, struct pw_properties *conf)
|
{
|
||||||
|
int i, len;
|
||||||
|
char *p = path;
|
||||||
|
for (i = 0; paths[i] != NULL; i++) {
|
||||||
|
len = snprintf(p, size, "%s%s", i == 0 ? "" : "/", paths[i]);
|
||||||
|
if (len < 0)
|
||||||
|
return -errno;
|
||||||
|
if (len >= size)
|
||||||
|
return -ENOSPC;
|
||||||
|
p += len;
|
||||||
|
size -= len;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_read_path(char *path, size_t size, const char *prefix, const char *name)
|
||||||
{
|
{
|
||||||
const char *path;
|
|
||||||
char filename[PATH_MAX], *data;
|
|
||||||
struct stat sbuf;
|
|
||||||
int fd;
|
|
||||||
const char *dir;
|
const char *dir;
|
||||||
|
char buffer[4096];
|
||||||
|
|
||||||
|
if (prefix[0] == '/') {
|
||||||
|
const char *paths[] = { prefix, name, NULL };
|
||||||
|
if (make_path(path, size, paths) == 0 &&
|
||||||
|
access(path, R_OK) == 0)
|
||||||
|
return 1;
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = getenv("XDG_CONFIG_HOME");
|
||||||
|
if (dir != NULL) {
|
||||||
|
const char *paths[] = { dir, "pipewire", prefix, name, NULL };
|
||||||
|
if (make_path(path, size, paths) == 0 &&
|
||||||
|
access(path, R_OK) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
dir = getenv("HOME");
|
||||||
|
if (dir == NULL) {
|
||||||
|
struct passwd pwd, *result = NULL;
|
||||||
|
if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0)
|
||||||
|
dir = result ? result->pw_dir : NULL;
|
||||||
|
}
|
||||||
|
if (dir != NULL) {
|
||||||
|
const char *paths[] = { dir, ".config", "pipewire", prefix, name, NULL };
|
||||||
|
if (make_path(path, size, paths) == 0 &&
|
||||||
|
access(path, R_OK) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
dir = getenv("PIPEWIRE_CONFIG_DIR");
|
dir = getenv("PIPEWIRE_CONFIG_DIR");
|
||||||
if (dir == NULL)
|
if (dir == NULL)
|
||||||
dir = PIPEWIRE_CONFIG_DIR;
|
dir = PIPEWIRE_CONFIG_DIR;
|
||||||
if (dir == NULL)
|
if (dir != NULL) {
|
||||||
|
const char *paths[] = { dir, prefix, name, NULL };
|
||||||
|
if (make_path(path, size, paths) == 0 &&
|
||||||
|
access(path, R_OK) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ensure_path(char *path, int size, const char *paths[])
|
||||||
|
{
|
||||||
|
int i, len, res;
|
||||||
|
char *p = path;
|
||||||
|
|
||||||
|
for (i = 0; paths[i] != NULL; i++) {
|
||||||
|
len = snprintf(p, size, "%s/", paths[i]);
|
||||||
|
if (len < 0)
|
||||||
|
return -errno;
|
||||||
|
if (len >= size)
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
|
p += len;
|
||||||
|
size -= len;
|
||||||
|
|
||||||
|
if ((res = access(path, R_OK | W_OK | X_OK)) < 0) {
|
||||||
|
if (errno != ENOENT)
|
||||||
|
return -errno;
|
||||||
|
if ((res = mkdir(path, 0700)) < 0) {
|
||||||
|
pw_log_info("Can't create directory %s: %m", path);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
if ((res = access(path, R_OK | W_OK | X_OK)) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
pw_log_info("created directory %s", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int open_write_dir(char *path, int size, const char *prefix)
|
||||||
|
{
|
||||||
|
const char *dir;
|
||||||
|
char buffer[4096];
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (prefix != NULL && prefix[0] == '/') {
|
||||||
|
const char *paths[] = { prefix, NULL };
|
||||||
|
if (ensure_path(path, size, paths) == 0)
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
dir = getenv("XDG_CONFIG_HOME");
|
||||||
|
if (dir != NULL) {
|
||||||
|
const char *paths[] = { dir, "pipewire", prefix, NULL };
|
||||||
|
if (ensure_path(path, size, paths) == 0)
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
dir = getenv("HOME");
|
||||||
|
if (dir == NULL) {
|
||||||
|
struct passwd pwd, *result = NULL;
|
||||||
|
if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0)
|
||||||
|
dir = result ? result->pw_dir : NULL;
|
||||||
|
}
|
||||||
|
if (dir != NULL) {
|
||||||
|
const char *paths[] = { dir, ".config", "pipewire", prefix, NULL };
|
||||||
|
if (ensure_path(path, size, paths) == 0)
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
|
found:
|
||||||
|
if ((res = open(path, O_CLOEXEC | O_DIRECTORY | O_PATH)) < 0) {
|
||||||
|
pw_log_error("Can't open state directory %s: %m", path);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
if (prefix)
|
SPA_EXPORT
|
||||||
snprintf(filename, sizeof(filename), "%s/%s/%s",
|
int pw_conf_save_state(const char *prefix, const char *name, struct pw_properties *conf)
|
||||||
dir, prefix, name);
|
{
|
||||||
else
|
const struct spa_dict_item *it;
|
||||||
snprintf(filename, sizeof(filename), "%s/%s",
|
char path[PATH_MAX];
|
||||||
dir, name);
|
char *tmp_name;
|
||||||
path = filename;
|
int res, sfd, fd;
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
if ((sfd = open_write_dir(path, sizeof(path), prefix)) < 0)
|
||||||
|
return sfd;
|
||||||
|
|
||||||
|
tmp_name = alloca(strlen(name)+5);
|
||||||
|
sprintf(tmp_name, "%s.tmp", name);
|
||||||
|
if ((fd = openat(sfd, tmp_name, O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0700)) < 0) {
|
||||||
|
pw_log_error("can't open file '%s': %m", tmp_name);
|
||||||
|
res = -errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fdopen(fd, "w");
|
||||||
|
fprintf(f, "{ \n");
|
||||||
|
spa_dict_for_each(it, &conf->dict) {
|
||||||
|
char key[1024];
|
||||||
|
|
||||||
|
if (spa_json_encode_string(key, sizeof(key)-1, it->key) >= (int)sizeof(key)-1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fprintf(f, " %s: %s\n", key, it->value);
|
||||||
|
}
|
||||||
|
fprintf(f, "}\n");
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (renameat(sfd, tmp_name, sfd, name) < 0) {
|
||||||
|
pw_log_error("can't rename temp file '%s': %m", tmp_name);
|
||||||
|
res = -errno;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
res = 0;
|
||||||
|
pw_log_info(NAME" %p: saved state '%s%s'", conf, path, name);
|
||||||
|
error:
|
||||||
|
close(sfd);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int conf_load(const char *prefix, const char *name, struct pw_properties *conf)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX], *data;
|
||||||
|
struct stat sbuf;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
if (prefix == NULL) {
|
||||||
|
prefix = name;
|
||||||
|
name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_read_path(path, sizeof(path), prefix, name) == 0) {
|
||||||
|
pw_log_debug(NAME" %p: can't load config '%s': %m", conf, path);
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) {
|
if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) {
|
||||||
pw_log_warn(NAME" %p: error loading config '%s': %m", conf, path);
|
pw_log_warn(NAME" %p: error loading config '%s': %m", conf, path);
|
||||||
return -errno;
|
return -errno;
|
||||||
|
|
@ -85,6 +254,18 @@ error_close:
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SPA_EXPORT
|
||||||
|
int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties *conf)
|
||||||
|
{
|
||||||
|
return conf_load(prefix, name, conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
SPA_EXPORT
|
||||||
|
int pw_conf_load_state(const char *prefix, const char *name, struct pw_properties *conf)
|
||||||
|
{
|
||||||
|
return conf_load(prefix, name, conf);
|
||||||
|
}
|
||||||
|
|
||||||
static int parse_spa_libs(struct pw_context *context, const char *str)
|
static int parse_spa_libs(struct pw_context *context, const char *str)
|
||||||
{
|
{
|
||||||
struct spa_json it[2];
|
struct spa_json it[2];
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,9 @@
|
||||||
|
|
||||||
#include <pipewire/context.h>
|
#include <pipewire/context.h>
|
||||||
|
|
||||||
int pw_conf_load(const char *prefix, const char *name, struct pw_properties *conf);
|
int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties *conf);
|
||||||
|
int pw_conf_load_state(const char *prefix, const char *name, struct pw_properties *conf);
|
||||||
|
int pw_conf_save_state(const char *prefix, const char *name, const struct pw_properties *conf);
|
||||||
|
|
||||||
int pw_context_parse_conf_section(struct pw_context *context,
|
int pw_context_parse_conf_section(struct pw_context *context,
|
||||||
struct pw_properties *conf, const char *section);
|
struct pw_properties *conf, const char *section);
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,23 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop,
|
||||||
res = -errno;
|
res = -errno;
|
||||||
goto error_free;
|
goto error_free;
|
||||||
}
|
}
|
||||||
pw_conf_load(conf_prefix, conf_name, conf);
|
if (strcmp(conf_name, "null") != 0 &&
|
||||||
|
(res = pw_conf_load_conf(conf_prefix, conf_name, conf)) < 0) {
|
||||||
|
if (conf_prefix == NULL && strcmp(conf_name, "client.conf") == 0) {
|
||||||
|
pw_log_error(NAME" %p: can't load config %s: %s",
|
||||||
|
this, conf_name, spa_strerror(res));
|
||||||
|
goto error_free;
|
||||||
|
} else {
|
||||||
|
pw_log_warn(NAME" %p: can't load config %s: %s. Using client.conf fallback",
|
||||||
|
this, conf_name, spa_strerror(res));
|
||||||
|
}
|
||||||
|
conf_prefix = NULL;
|
||||||
|
if ((res = pw_conf_load_conf(NULL, "client.conf", conf)) < 0) {
|
||||||
|
pw_log_error(NAME" %p: can't load client.conf config: %s",
|
||||||
|
this, spa_strerror(res));
|
||||||
|
goto error_free;
|
||||||
|
}
|
||||||
|
}
|
||||||
this->conf = conf;
|
this->conf = conf;
|
||||||
|
|
||||||
if ((str = pw_properties_get(conf, "properties")) != NULL)
|
if ((str = pw_properties_get(conf, "properties")) != NULL)
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ static void test_create(void)
|
||||||
|
|
||||||
context = pw_context_new(pw_main_loop_get_loop(loop),
|
context = pw_context_new(pw_main_loop_get_loop(loop),
|
||||||
pw_properties_new(
|
pw_properties_new(
|
||||||
PW_KEY_CONTEXT_PROFILE_MODULES, "none",
|
PW_KEY_CONFIG_NAME, "null",
|
||||||
NULL), 12);
|
NULL), 12);
|
||||||
spa_assert(context != NULL);
|
spa_assert(context != NULL);
|
||||||
pw_context_add_listener(context, &listener, &context_events, context);
|
pw_context_add_listener(context, &listener, &context_events, context);
|
||||||
|
|
|
||||||
|
|
@ -1553,7 +1553,7 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
data.context = pw_context_new(l,
|
data.context = pw_context_new(l,
|
||||||
pw_properties_new(
|
pw_properties_new(
|
||||||
PW_KEY_CONTEXT_PROFILE_MODULES, "default,rtkit",
|
PW_KEY_CONFIG_NAME, "client-rt.conf",
|
||||||
NULL),
|
NULL),
|
||||||
0);
|
0);
|
||||||
if (!data.context) {
|
if (!data.context) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue