config: support merging multiple config files

Add the -m|--merge-config command line option to iterate backwards over
XDG Base Dir paths and read config/theme files multiple times.

For example if both ~/.config/labwc/rc.xml and /etc/xdg/labwc/rc.xml
exist, the latter will be read first and then the former (if
--merge-config is enabled).

When $XDG_CONFIG_HOME is defined, make it replace (not augment)
$HOME/.config. Similarly, make $XDG_CONFIG_DIRS replace /etc/xdg when
defined.

XDG Base Dir Spec does not specify whether or not an application (or a
compositor!) should (a) define that only the file under the most important
base directory should be used, or (b) define rules for merging the
information from the different files.

ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html

In the case of labwc there is a use-case for both positions, just to be
clear, the default behaviour, described by position (a) above, does NOT
change.

This change affects the following config/theme files:
  - rc.xml
  - menu.xml
  - autostart
  - environment
  - themerc
  - themerc-override
  - Theme buttons, for example max.xbm

Instead of caching global config/theme directories, create lists of paths
(e.g.  '/home/foo/.config/labwc/rc.xml', '/etc/xdg/labwc/rc.xml', etc).
This creates more common parsing logic and just reversing the direction
of iteration and breaks early if config-merge is not wanted.

Enable better fallback for themes. For example if a particular theme does
not exist in $HOME/.local/share/themes, it will be searched for in
~/.themes/ and so on. This also applies to theme buttons which now
fallback on an individual basis.

Avoid using stat() in most situations and just go straight to fopen().

Fixes #1406
This commit is contained in:
Johan Malm 2024-01-09 22:00:45 +00:00 committed by Johan Malm
parent d0aff49c81
commit 698c7ace07
14 changed files with 330 additions and 240 deletions

View file

@ -18,6 +18,19 @@ searched for in the following order:
- ${XDG_CONFIG_HOME:-$HOME/.config}/labwc - ${XDG_CONFIG_HOME:-$HOME/.config}/labwc
- ${XDG_CONFIG_DIRS:-/etc/xdg}/labwc - ${XDG_CONFIG_DIRS:-/etc/xdg}/labwc
When $XDG_CONFIG_HOME is defined, it replaces (rather than augments)
$HOME/.config. The same is the case for $XDG_CONFIG_DIRS and /etc/xdg.
The XDG Base Directory Specification does not specify whether or not programs
should (a) allow the first-identified configuration file to supersede any
others, or (b) define rules for merging the information from more than one file.
By default, labwc uses option (a), reading only the first file identified. With
the --merge-config option, the search order is reserved, but every configuration
file encountered is processed in turn. Thus, user-specific files will augment
system-wide configurations, with conflicts favoring the user-specific
alternative.
The configuration directory location can be override with the -C command line The configuration directory location can be override with the -C command line
option. option.

View file

@ -15,6 +15,9 @@ searched for in the following order:
- /usr/local/share/themes/<theme-name>/openbox-3/ - /usr/local/share/themes/<theme-name>/openbox-3/
- /opt/share/themes/<theme-name>/openbox-3/ - /opt/share/themes/<theme-name>/openbox-3/
When $XDG_DATA_HOME is defined, it replaces (rather than augments)
$HOME/.local/share. The same is the case for $XDG_DATA_DIRS and /usr/share/.
Choosing a theme is done by editing the <name> key in the <theme> section of Choosing a theme is done by editing the <name> key in the <theme> section of
the rc.xml configuration file (labwc-config(5)). the rc.xml configuration file (labwc-config(5)).

View file

@ -45,6 +45,9 @@ the `--exit` and `--reconfigure` options use.
*-h, --help* *-h, --help*
Show help message and quit Show help message and quit
*-m, --merge-config*
Merge user config/theme files in all XDG Base Directories
*-r, --reconfigure* *-r, --reconfigure*
Reload the compositor configuration Reload the compositor configuration

View file

@ -2,12 +2,19 @@
#ifndef LABWC_DIR_H #ifndef LABWC_DIR_H
#define LABWC_DIR_H #define LABWC_DIR_H
char *config_dir(void); #include <wayland-server-core.h>
/** struct path {
* theme_dir - find theme directory containing theme @theme_name char *string;
* @theme_name: theme to search for struct wl_list link;
*/ };
char *theme_dir(const char *theme_name);
struct wl_list *paths_get_prev(struct wl_list *elm);
struct wl_list *paths_get_next(struct wl_list *elm);
void paths_config_create(struct wl_list *paths, const char *filename);
void paths_theme_create(struct wl_list *paths, const char *theme_name,
const char *filename);
void paths_destroy(struct wl_list *paths);
#endif /* LABWC_DIR_H */ #endif /* LABWC_DIR_H */

View file

@ -48,6 +48,7 @@ struct window_switcher_field {
struct rcxml { struct rcxml {
char *config_dir; char *config_dir;
bool merge_config;
/* core */ /* core */
bool xdg_shell_server_side_deco; bool xdg_shell_server_side_deco;

View file

@ -4,17 +4,15 @@
/** /**
* session_environment_init - set enrivonment variables based on <key>=<value> * session_environment_init - set enrivonment variables based on <key>=<value>
* @dir: path to config directory
* pairs in `${XDG_CONFIG_DIRS:-/etc/xdg}/lawbc/environment` with user override * pairs in `${XDG_CONFIG_DIRS:-/etc/xdg}/lawbc/environment` with user override
* in `${XDG_CONFIG_HOME:-$HOME/.config}` * in `${XDG_CONFIG_HOME:-$HOME/.config}`
*/ */
void session_environment_init(const char *dir); void session_environment_init(void);
/** /**
* session_autostart_init - run autostart file as shell script * session_autostart_init - run autostart file as shell script
* @dir: path to config directory
* Note: Same as `sh ~/.config/labwc/autostart` (or equivalent XDG config dir) * Note: Same as `sh ~/.config/labwc/autostart` (or equivalent XDG config dir)
*/ */
void session_autostart_init(const char *dir); void session_autostart_init(void);
#endif /* LABWC_SESSION_H */ #endif /* LABWC_SESSION_H */

View file

@ -1,11 +1,27 @@
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
#include <stdio.h> #include <stdio.h>
#include <unistd.h>
#include "button/common.h" #include "button/common.h"
#include "common/dir.h" #include "common/dir.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "labwc.h"
void void
button_filename(const char *name, char *buf, size_t len) button_filename(const char *name, char *buf, size_t len)
{ {
snprintf(buf, len, "%s/%s", theme_dir(rc.theme_name), name); struct wl_list paths;
paths_theme_create(&paths, rc.theme_name, name);
/*
* You can't really merge buttons, so let's just iterate forwards
* and stop on the first hit
*/
struct path *path;
wl_list_for_each(path, &paths, link) {
if (access(path->string, R_OK) == 0) {
snprintf(buf, len, "%s", path->string);
break;
}
}
paths_destroy(&paths);
} }

View file

@ -4,143 +4,190 @@
* *
* Copyright Johan Malm 2020 * Copyright Johan Malm 2020
*/ */
#include <assert.h>
#include <glib.h> #include <glib.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/stat.h> #include <sys/stat.h>
#include "common/dir.h" #include "common/dir.h"
#include "common/buf.h"
#include "common/list.h"
#include "common/mem.h"
#include "common/string-helpers.h"
#include "labwc.h"
struct dir { struct dir {
const char *prefix; const char *prefix;
const char *default_prefix;
const char *path; const char *path;
}; };
static struct dir config_dirs[] = { static struct dir config_dirs[] = {
{ "XDG_CONFIG_HOME", "labwc" }, {
{ "HOME", ".config/labwc" }, .prefix = "XDG_CONFIG_HOME",
{ "XDG_CONFIG_DIRS", "labwc" }, .default_prefix = "$HOME/.config",
{ NULL, "/etc/xdg/labwc" }, .path = "labwc"
{ NULL, NULL } }, {
.prefix = "XDG_CONFIG_DIRS",
.default_prefix = "/etc/xdg",
.path = "labwc",
}, {
.path = NULL,
}
}; };
static struct dir theme_dirs[] = { static struct dir theme_dirs[] = {
{ "XDG_DATA_HOME", "themes" }, {
{ "HOME", ".local/share/themes" }, .prefix = "XDG_DATA_HOME",
{ "HOME", ".themes" }, .default_prefix = "$HOME/.local/share",
{ "XDG_DATA_DIRS", "themes" }, .path = "themes",
{ NULL, "/usr/share/themes" }, }, {
{ NULL, "/usr/local/share/themes" }, .prefix = "HOME",
{ NULL, "/opt/share/themes" }, .path = ".themes",
{ NULL, NULL } }, {
.prefix = "XDG_DATA_DIRS",
.default_prefix = "/usr/share:/usr/local/share:/opt/share",
.path = "themes",
}, {
.path = NULL,
}
}; };
static bool
isdir(const char *path)
{
struct stat st;
return (!stat(path, &st) && S_ISDIR(st.st_mode));
}
struct ctx { struct ctx {
void (*build_path_fn)(struct ctx *ctx, char *prefix, const char *path); void (*build_path_fn)(struct ctx *ctx, char *prefix, const char *path);
const char *filename;
char *buf; char *buf;
size_t len; size_t len;
struct dir *dirs; struct dir *dirs;
const char *theme_name; const char *theme_name;
struct wl_list *list;
}; };
struct wl_list *paths_get_prev(struct wl_list *elm) { return elm->prev; }
struct wl_list *paths_get_next(struct wl_list *elm) { return elm->next; }
static void static void
build_config_path(struct ctx *ctx, char *prefix, const char *path) build_config_path(struct ctx *ctx, char *prefix, const char *path)
{ {
if (!prefix) { assert(prefix);
snprintf(ctx->buf, ctx->len, "%s", path); snprintf(ctx->buf, ctx->len, "%s/%s/%s", prefix, path, ctx->filename);
} else {
snprintf(ctx->buf, ctx->len, "%s/%s", prefix, path);
}
} }
static void static void
build_theme_path(struct ctx *ctx, char *prefix, const char *path) build_theme_path(struct ctx *ctx, char *prefix, const char *path)
{ {
if (!prefix) { assert(prefix);
snprintf(ctx->buf, ctx->len, "%s/%s/openbox-3", path, snprintf(ctx->buf, ctx->len, "%s/%s/%s/openbox-3/%s", prefix, path,
ctx->theme_name); ctx->theme_name, ctx->filename);
} else {
snprintf(ctx->buf, ctx->len, "%s/%s/%s/openbox-3", prefix, path,
ctx->theme_name);
}
} }
static char * static void
find_dir(struct ctx *ctx) find_dir(struct ctx *ctx)
{ {
char *debug = getenv("LABWC_DEBUG_DIR_CONFIG_AND_THEME"); char *debug = getenv("LABWC_DEBUG_DIR_CONFIG_AND_THEME");
for (int i = 0; ctx->dirs[i].path; i++) { for (int i = 0; ctx->dirs[i].path; i++) {
struct dir d = ctx->dirs[i]; struct dir d = ctx->dirs[i];
if (!d.prefix) { struct buf prefix;
/* handle /etc/xdg... */ buf_init(&prefix);
ctx->build_path_fn(ctx, NULL, d.path);
/*
* Replace (rather than augment) $HOME/.config with
* $XDG_CONFIG_HOME if defined, and so on for the other
* XDG Base Directories.
*/
char *pfxenv = getenv(d.prefix);
buf_add(&prefix, pfxenv ? pfxenv : d.default_prefix);
if (!prefix.len) {
free(prefix.buf);
continue;
}
/* Handle .default_prefix shell variables such as $HOME */
buf_expand_shell_variables(&prefix);
/*
* Respect that $XDG_DATA_DIRS can contain multiple colon
* separated paths and that we have structured the
* .default_prefix in the same way.
*/
gchar * *prefixes;
prefixes = g_strsplit(prefix.buf, ":", -1);
for (gchar * *p = prefixes; *p; p++) {
ctx->build_path_fn(ctx, *p, d.path);
if (debug) { if (debug) {
fprintf(stderr, "%s\n", ctx->buf); fprintf(stderr, "%s\n", ctx->buf);
} }
if (isdir(ctx->buf)) {
return ctx->buf; /*
} * TODO: We could stat() and continue here if we really
} else { * wanted to only respect only the first hit, but feels
/* handle $HOME/.config/... and $XDG_* */ * like it is probably overkill.
char *prefix = getenv(d.prefix); */
if (!prefix) { struct path *path = znew(*path);
continue; path->string = xstrdup(ctx->buf);
} wl_list_append(ctx->list, &path->link);
gchar * *prefixes;
prefixes = g_strsplit(prefix, ":", -1);
for (gchar * *p = prefixes; *p; p++) {
ctx->build_path_fn(ctx, *p, d.path);
if (debug) {
fprintf(stderr, "%s\n", ctx->buf);
}
if (isdir(ctx->buf)) {
g_strfreev(prefixes);
return ctx->buf;
}
}
g_strfreev(prefixes);
} }
g_strfreev(prefixes);
free(prefix.buf);
} }
/* no directory was found */
ctx->buf[0] = '\0';
return ctx->buf;
} }
char * void
config_dir(void) paths_config_create(struct wl_list *paths, const char *filename)
{ {
static char buf[4096] = { 0 }; char buf[4096] = { 0 };
if (buf[0] != '\0') { wl_list_init(paths);
return buf;
/*
* If user provided a config directory with the -C command line option,
* then that trumps everything else and we do not create the
* XDG-Base-Dir list.
*/
if (rc.config_dir) {
struct path *path = znew(*path);
path->string = strdup_printf("%s/%s", rc.config_dir, filename);
wl_list_append(paths, &path->link);
return;
} }
struct ctx ctx = { struct ctx ctx = {
.build_path_fn = build_config_path, .build_path_fn = build_config_path,
.filename = filename,
.buf = buf, .buf = buf,
.len = sizeof(buf), .len = sizeof(buf),
.dirs = config_dirs .dirs = config_dirs,
.list = paths,
}; };
return find_dir(&ctx); find_dir(&ctx);
} }
char * void
theme_dir(const char *theme_name) paths_theme_create(struct wl_list *paths, const char *theme_name,
const char *filename)
{ {
static char buf[4096] = { 0 }; static char buf[4096] = { 0 };
wl_list_init(paths);
struct ctx ctx = { struct ctx ctx = {
.build_path_fn = build_theme_path, .build_path_fn = build_theme_path,
.filename = filename,
.buf = buf, .buf = buf,
.len = sizeof(buf), .len = sizeof(buf),
.dirs = theme_dirs, .dirs = theme_dirs,
.theme_name = theme_name .theme_name = theme_name,
.list = paths,
}; };
return find_dir(&ctx); find_dir(&ctx);
}
void
paths_destroy(struct wl_list *paths)
{
struct path *path, *next;
wl_list_for_each_safe(path, next, paths, link) {
free(path->string);
wl_list_remove(&path->link);
free(path);
}
} }

View file

@ -14,6 +14,7 @@
#include <wlr/util/box.h> #include <wlr/util/box.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "action.h" #include "action.h"
#include "common/dir.h"
#include "common/list.h" #include "common/list.h"
#include "common/macros.h" #include "common/macros.h"
#include "common/mem.h" #include "common/mem.h"
@ -1473,69 +1474,68 @@ validate(void)
validate_actions(); validate_actions();
} }
static void
rcxml_path(char *buf, size_t len)
{
if (!rc.config_dir) {
return;
}
snprintf(buf, len, "%s/rc.xml", rc.config_dir);
}
static void
find_config_file(char *buffer, size_t len, const char *filename)
{
if (filename) {
snprintf(buffer, len, "%s", filename);
return;
}
rcxml_path(buffer, len);
}
void void
rcxml_read(const char *filename) rcxml_read(const char *filename)
{ {
FILE *stream;
char *line = NULL;
size_t len = 0;
struct buf b;
static char rcxml[4096] = {0};
rcxml_init(); rcxml_init();
/* struct wl_list paths;
* rcxml_read() can be called multiple times, but we only set rcxml[]
* the first time. The specified 'filename' is only respected the first if (filename) {
* time. /* Honour command line argument -c <filename> */
*/ wl_list_init(&paths);
if (rcxml[0] == '\0') { struct path *path = znew(*path);
find_config_file(rcxml, sizeof(rcxml), filename); path->string = xstrdup(filename);
} wl_list_append(&paths, &path->link);
if (rcxml[0] == '\0') { } else {
wlr_log(WLR_INFO, "cannot find rc.xml config file"); paths_config_create(&paths, "rc.xml");
goto no_config;
} }
/* Reading file into buffer before parsing - better for unit tests */ /* Reading file into buffer before parsing - better for unit tests */
stream = fopen(rcxml, "r"); struct buf b;
if (!stream) {
wlr_log(WLR_ERROR, "cannot read (%s)", rcxml); bool should_merge_config = rc.merge_config;
goto no_config; struct wl_list *(*iter)(struct wl_list *list);
} iter = should_merge_config ? paths_get_prev : paths_get_next;
wlr_log(WLR_INFO, "read config file %s", rcxml);
buf_init(&b); /*
while (getline(&line, &len, stream) != -1) { * This is the equivalent of a wl_list_for_each() which optionally
char *p = strrchr(line, '\n'); * iterates in reverse depending on 'should_merge_config'
if (p) { *
*p = '\0'; * If not merging, we iterate forwards and break after the first
* iteration.
*
* If merging, we iterate backwards (least important XDG Base Dir first)
* and keep going.
*/
for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) {
struct path *path = wl_container_of(elm, path, link);
FILE *stream = fopen(path->string, "r");
if (!stream) {
continue;
} }
buf_add(&b, line);
} wlr_log(WLR_INFO, "read config file %s", path->string);
free(line);
fclose(stream); buf_init(&b);
rcxml_parse_xml(&b); char *line = NULL;
free(b.buf); size_t len = 0;
no_config: while (getline(&line, &len, stream) != -1) {
char *p = strrchr(line, '\n');
if (p) {
*p = '\0';
}
buf_add(&b, line);
}
zfree(line);
fclose(stream);
rcxml_parse_xml(&b);
zfree(b.buf);
if (!should_merge_config) {
break;
}
};
paths_destroy(&paths);
post_processing(); post_processing();
validate(); validate();
} }

View file

@ -7,10 +7,12 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "common/buf.h" #include "common/buf.h"
#include "common/dir.h"
#include "common/file-helpers.h" #include "common/file-helpers.h"
#include "common/spawn.h" #include "common/spawn.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "config/session.h" #include "config/session.h"
#include "labwc.h"
static bool static bool
string_empty(const char *s) string_empty(const char *s)
@ -45,14 +47,15 @@ error:
free(value.buf); free(value.buf);
} }
static void /* return true on successful read */
static bool
read_environment_file(const char *filename) read_environment_file(const char *filename)
{ {
char *line = NULL; char *line = NULL;
size_t len = 0; size_t len = 0;
FILE *stream = fopen(filename, "r"); FILE *stream = fopen(filename, "r");
if (!stream) { if (!stream) {
return; return false;
} }
wlr_log(WLR_INFO, "read environment file %s", filename); wlr_log(WLR_INFO, "read environment file %s", filename);
while (getline(&line, &len, stream) != -1) { while (getline(&line, &len, stream) != -1) {
@ -64,15 +67,7 @@ read_environment_file(const char *filename)
} }
free(line); free(line);
fclose(stream); fclose(stream);
} return true;
static char *
build_path(const char *dir, const char *filename)
{
if (string_empty(dir) || string_empty(filename)) {
return NULL;
}
return strdup_printf("%s/%s", dir, filename);
} }
static void static void
@ -96,7 +91,7 @@ update_activation_env(const char *env_keys)
} }
void void
session_environment_init(const char *dir) session_environment_init(void)
{ {
/* /*
* Set default for XDG_CURRENT_DESKTOP so xdg-desktop-portal-wlr is happy. * Set default for XDG_CURRENT_DESKTOP so xdg-desktop-portal-wlr is happy.
@ -114,32 +109,49 @@ session_environment_init(const char *dir)
*/ */
setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 0); setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 0);
char *environment = build_path(dir, "environment"); struct wl_list paths;
if (!environment) { paths_config_create(&paths, "environment");
return;
bool should_merge_config = rc.merge_config;
struct wl_list *(*iter)(struct wl_list *list);
iter = should_merge_config ? paths_get_prev : paths_get_next;
for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) {
struct path *path = wl_container_of(elm, path, link);
bool success = read_environment_file(path->string);
if (success && !should_merge_config) {
break;
}
} }
read_environment_file(environment); paths_destroy(&paths);
free(environment);
} }
void void
session_autostart_init(const char *dir) session_autostart_init(void)
{ {
/* Update dbus and systemd user environment, each may fail gracefully */ /* Update dbus and systemd user environment, each may fail gracefully */
update_activation_env("DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"); update_activation_env("DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP");
char *autostart = build_path(dir, "autostart"); struct wl_list paths;
if (!autostart) { paths_config_create(&paths, "autostart");
return;
bool should_merge_config = rc.merge_config;
struct wl_list *(*iter)(struct wl_list *list);
iter = should_merge_config ? paths_get_prev : paths_get_next;
for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) {
struct path *path = wl_container_of(elm, path, link);
if (!file_exists(path->string)) {
continue;
}
wlr_log(WLR_INFO, "run autostart file %s", path->string);
char *cmd = strdup_printf("sh %s", path->string);
spawn_async_no_shell(cmd);
free(cmd);
if (!should_merge_config) {
break;
}
} }
if (!file_exists(autostart)) { paths_destroy(&paths);
wlr_log(WLR_ERROR, "no autostart file");
goto out;
}
wlr_log(WLR_INFO, "run autostart file %s", autostart);
char *cmd = strdup_printf("sh %s", autostart);
spawn_async_no_shell(cmd);
free(cmd);
out:
free(autostart);
} }

View file

@ -21,6 +21,7 @@ static const struct option long_options[] = {
{"debug", no_argument, NULL, 'd'}, {"debug", no_argument, NULL, 'd'},
{"exit", no_argument, NULL, 'e'}, {"exit", no_argument, NULL, 'e'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"merge-config", no_argument, NULL, 'm'},
{"reconfigure", no_argument, NULL, 'r'}, {"reconfigure", no_argument, NULL, 'r'},
{"startup", required_argument, NULL, 's'}, {"startup", required_argument, NULL, 's'},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
@ -35,6 +36,7 @@ static const char labwc_usage[] =
" -d, --debug Enable full logging, including debug information\n" " -d, --debug Enable full logging, including debug information\n"
" -e, --exit Exit the compositor\n" " -e, --exit Exit the compositor\n"
" -h, --help Show help message and quit\n" " -h, --help Show help message and quit\n"
" -m, --merge-config Merge user config files/theme in all XDG Base Dirs\n"
" -r, --reconfigure Reload the compositor configuration\n" " -r, --reconfigure Reload the compositor configuration\n"
" -s, --startup <command> Run command on startup\n" " -s, --startup <command> Run command on startup\n"
" -v, --version Show version number and quit\n" " -v, --version Show version number and quit\n"
@ -91,7 +93,7 @@ main(int argc, char *argv[])
int c; int c;
while (1) { while (1) {
int index = 0; int index = 0;
c = getopt_long(argc, argv, "c:C:dehrs:vV", long_options, &index); c = getopt_long(argc, argv, "c:C:dehmrs:vV", long_options, &index);
if (c == -1) { if (c == -1) {
break; break;
} }
@ -100,7 +102,7 @@ main(int argc, char *argv[])
config_file = optarg; config_file = optarg;
break; break;
case 'C': case 'C':
rc.config_dir = xstrdup(optarg); rc.config_dir = optarg;
break; break;
case 'd': case 'd':
verbosity = WLR_DEBUG; verbosity = WLR_DEBUG;
@ -108,6 +110,9 @@ main(int argc, char *argv[])
case 'e': case 'e':
send_signal_to_labwc_pid(SIGTERM); send_signal_to_labwc_pid(SIGTERM);
exit(0); exit(0);
case 'm':
rc.merge_config = true;
break;
case 'r': case 'r':
send_signal_to_labwc_pid(SIGHUP); send_signal_to_labwc_pid(SIGHUP);
exit(0); exit(0);
@ -133,11 +138,7 @@ main(int argc, char *argv[])
die_on_detecting_suid(); die_on_detecting_suid();
if (!rc.config_dir) { session_environment_init();
rc.config_dir = config_dir();
}
wlr_log(WLR_INFO, "using config dir (%s)\n", rc.config_dir);
session_environment_init(rc.config_dir);
rcxml_read(config_file); rcxml_read(config_file);
/* /*
@ -171,7 +172,7 @@ main(int argc, char *argv[])
menu_init(&server); menu_init(&server);
session_autostart_init(rc.config_dir); session_autostart_init();
if (startup_cmd) { if (startup_cmd) {
spawn_async_no_shell(startup_cmd); spawn_async_no_shell(startup_cmd);
} }

View file

@ -11,6 +11,7 @@
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "action.h" #include "action.h"
#include "common/buf.h" #include "common/buf.h"
#include "common/dir.h"
#include "common/font.h" #include "common/font.h"
#include "common/list.h" #include "common/list.h"
#include "common/match.h" #include "common/match.h"
@ -548,21 +549,27 @@ err:
static void static void
parse_xml(const char *filename, struct server *server) parse_xml(const char *filename, struct server *server)
{ {
static char buf[4096] = { 0 }; struct wl_list paths;
paths_config_create(&paths, filename);
if (!rc.config_dir) { bool should_merge_config = rc.merge_config;
return; struct wl_list *(*iter)(struct wl_list *list);
} iter = should_merge_config ? paths_get_prev : paths_get_next;
snprintf(buf, sizeof(buf), "%s/%s", rc.config_dir, filename);
FILE *stream = fopen(buf, "r"); for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) {
if (!stream) { struct path *path = wl_container_of(elm, path, link);
wlr_log(WLR_ERROR, "cannot read %s", buf); FILE *stream = fopen(path->string, "r");
return; if (!stream) {
return;
}
wlr_log(WLR_INFO, "read menu file %s", path->string);
parse(server, stream);
fclose(stream);
if (!should_merge_config) {
break;
}
} }
wlr_log(WLR_INFO, "read menu file %s", buf); paths_destroy(&paths);
parse(server, stream);
fclose(stream);
} }
static int static int

View file

@ -67,7 +67,7 @@ reload_config_and_theme(void)
static int static int
handle_sighup(int signal, void *data) handle_sighup(int signal, void *data)
{ {
session_environment_init(rc.config_dir); session_environment_init();
reload_config_and_theme(); reload_config_and_theme();
return 0; return 0;
} }

View file

@ -23,6 +23,7 @@
#include "common/font.h" #include "common/font.h"
#include "common/graphic-helpers.h" #include "common/graphic-helpers.h"
#include "common/match.h" #include "common/match.h"
#include "common/mem.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "button/button-png.h" #include "button/button-png.h"
@ -664,60 +665,36 @@ process_line(struct theme *theme, char *line)
} }
static void static void
theme_read(struct theme *theme, const char *theme_name) theme_read(struct theme *theme, struct wl_list *paths)
{ {
FILE *stream = NULL; bool should_merge_config = rc.merge_config;
char *line = NULL; struct wl_list *(*iter)(struct wl_list *list);
size_t len = 0; iter = should_merge_config ? paths_get_prev : paths_get_next;
char themerc[4096];
if (strlen(theme_dir(theme_name))) { for (struct wl_list *elm = iter(paths); elm != paths; elm = iter(elm)) {
snprintf(themerc, sizeof(themerc), "%s/themerc", struct path *path = wl_container_of(elm, path, link);
theme_dir(theme_name)); FILE *stream = fopen(path->string, "r");
stream = fopen(themerc, "r"); if (!stream) {
} continue;
if (!stream) {
if (theme_name) {
wlr_log(WLR_INFO, "cannot find theme %s", theme_name);
} }
return;
} wlr_log(WLR_INFO, "read theme %s", path->string);
wlr_log(WLR_INFO, "read theme %s", themerc);
while (getline(&line, &len, stream) != -1) { char *line = NULL;
char *p = strrchr(line, '\n'); size_t len = 0;
if (p) { while (getline(&line, &len, stream) != -1) {
*p = '\0'; char *p = strrchr(line, '\n');
if (p) {
*p = '\0';
}
process_line(theme, line);
} }
process_line(theme, line); zfree(line);
} fclose(stream);
free(line); if (!should_merge_config) {
fclose(stream); break;
}
static void
theme_read_override(struct theme *theme)
{
char f[4096] = { 0 };
snprintf(f, sizeof(f), "%s/themerc-override", rc.config_dir);
FILE *stream = fopen(f, "r");
if (!stream) {
wlr_log(WLR_INFO, "no theme override '%s'", f);
return;
}
wlr_log(WLR_INFO, "read theme-override %s", f);
char *line = NULL;
size_t len = 0;
while (getline(&line, &len, stream) != -1) {
char *p = strrchr(line, '\n');
if (p) {
*p = '\0';
} }
process_line(theme, line);
} }
free(line);
fclose(stream);
} }
struct rounded_corner_ctx { struct rounded_corner_ctx {
@ -1001,10 +978,15 @@ theme_init(struct theme *theme, const char *theme_name)
theme_builtin(theme); theme_builtin(theme);
/* Read <data-dir>/share/themes/$theme_name/openbox-3/themerc */ /* Read <data-dir>/share/themes/$theme_name/openbox-3/themerc */
theme_read(theme, theme_name); struct wl_list paths;
paths_theme_create(&paths, theme_name, "themerc");
theme_read(theme, &paths);
paths_destroy(&paths);
/* Read <config-dir>/labwc/themerc-override */ /* Read <config-dir>/labwc/themerc-override */
theme_read_override(theme); paths_config_create(&paths, "themerc-override");
theme_read(theme, &paths);
paths_destroy(&paths);
post_processing(theme); post_processing(theme);
create_corners(theme); create_corners(theme);