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_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
option.

View file

@ -15,6 +15,9 @@ searched for in the following order:
- /usr/local/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
the rc.xml configuration file (labwc-config(5)).

View file

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

View file

@ -2,12 +2,19 @@
#ifndef LABWC_DIR_H
#define LABWC_DIR_H
char *config_dir(void);
#include <wayland-server-core.h>
/**
* theme_dir - find theme directory containing theme @theme_name
* @theme_name: theme to search for
*/
char *theme_dir(const char *theme_name);
struct path {
char *string;
struct wl_list link;
};
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 */

View file

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

View file

@ -4,17 +4,15 @@
/**
* 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
* 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
* @dir: path to config directory
* 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 */

View file

@ -1,11 +1,27 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <stdio.h>
#include <unistd.h>
#include "button/common.h"
#include "common/dir.h"
#include "config/rcxml.h"
#include "labwc.h"
void
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
*/
#include <assert.h>
#include <glib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.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 {
const char *prefix;
const char *default_prefix;
const char *path;
};
static struct dir config_dirs[] = {
{ "XDG_CONFIG_HOME", "labwc" },
{ "HOME", ".config/labwc" },
{ "XDG_CONFIG_DIRS", "labwc" },
{ NULL, "/etc/xdg/labwc" },
{ NULL, NULL }
{
.prefix = "XDG_CONFIG_HOME",
.default_prefix = "$HOME/.config",
.path = "labwc"
}, {
.prefix = "XDG_CONFIG_DIRS",
.default_prefix = "/etc/xdg",
.path = "labwc",
}, {
.path = NULL,
}
};
static struct dir theme_dirs[] = {
{ "XDG_DATA_HOME", "themes" },
{ "HOME", ".local/share/themes" },
{ "HOME", ".themes" },
{ "XDG_DATA_DIRS", "themes" },
{ NULL, "/usr/share/themes" },
{ NULL, "/usr/local/share/themes" },
{ NULL, "/opt/share/themes" },
{ NULL, NULL }
{
.prefix = "XDG_DATA_HOME",
.default_prefix = "$HOME/.local/share",
.path = "themes",
}, {
.prefix = "HOME",
.path = ".themes",
}, {
.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 {
void (*build_path_fn)(struct ctx *ctx, char *prefix, const char *path);
const char *filename;
char *buf;
size_t len;
struct dir *dirs;
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
build_config_path(struct ctx *ctx, char *prefix, const char *path)
{
if (!prefix) {
snprintf(ctx->buf, ctx->len, "%s", path);
} else {
snprintf(ctx->buf, ctx->len, "%s/%s", prefix, path);
}
assert(prefix);
snprintf(ctx->buf, ctx->len, "%s/%s/%s", prefix, path, ctx->filename);
}
static void
build_theme_path(struct ctx *ctx, char *prefix, const char *path)
{
if (!prefix) {
snprintf(ctx->buf, ctx->len, "%s/%s/openbox-3", path,
ctx->theme_name);
} else {
snprintf(ctx->buf, ctx->len, "%s/%s/%s/openbox-3", prefix, path,
ctx->theme_name);
}
assert(prefix);
snprintf(ctx->buf, ctx->len, "%s/%s/%s/openbox-3/%s", prefix, path,
ctx->theme_name, ctx->filename);
}
static char *
static void
find_dir(struct ctx *ctx)
{
char *debug = getenv("LABWC_DEBUG_DIR_CONFIG_AND_THEME");
for (int i = 0; ctx->dirs[i].path; i++) {
struct dir d = ctx->dirs[i];
if (!d.prefix) {
/* handle /etc/xdg... */
ctx->build_path_fn(ctx, NULL, d.path);
struct buf prefix;
buf_init(&prefix);
/*
* 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) {
fprintf(stderr, "%s\n", ctx->buf);
}
if (isdir(ctx->buf)) {
return ctx->buf;
}
} else {
/* handle $HOME/.config/... and $XDG_* */
char *prefix = getenv(d.prefix);
if (!prefix) {
continue;
}
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);
/*
* TODO: We could stat() and continue here if we really
* wanted to only respect only the first hit, but feels
* like it is probably overkill.
*/
struct path *path = znew(*path);
path->string = xstrdup(ctx->buf);
wl_list_append(ctx->list, &path->link);
}
g_strfreev(prefixes);
free(prefix.buf);
}
/* no directory was found */
ctx->buf[0] = '\0';
return ctx->buf;
}
char *
config_dir(void)
void
paths_config_create(struct wl_list *paths, const char *filename)
{
static char buf[4096] = { 0 };
if (buf[0] != '\0') {
return buf;
char buf[4096] = { 0 };
wl_list_init(paths);
/*
* 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 = {
.build_path_fn = build_config_path,
.filename = filename,
.buf = buf,
.len = sizeof(buf),
.dirs = config_dirs
.dirs = config_dirs,
.list = paths,
};
return find_dir(&ctx);
find_dir(&ctx);
}
char *
theme_dir(const char *theme_name)
void
paths_theme_create(struct wl_list *paths, const char *theme_name,
const char *filename)
{
static char buf[4096] = { 0 };
wl_list_init(paths);
struct ctx ctx = {
.build_path_fn = build_theme_path,
.filename = filename,
.buf = buf,
.len = sizeof(buf),
.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/log.h>
#include "action.h"
#include "common/dir.h"
#include "common/list.h"
#include "common/macros.h"
#include "common/mem.h"
@ -1473,69 +1474,68 @@ validate(void)
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
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_read() can be called multiple times, but we only set rcxml[]
* the first time. The specified 'filename' is only respected the first
* time.
*/
if (rcxml[0] == '\0') {
find_config_file(rcxml, sizeof(rcxml), filename);
}
if (rcxml[0] == '\0') {
wlr_log(WLR_INFO, "cannot find rc.xml config file");
goto no_config;
struct wl_list paths;
if (filename) {
/* Honour command line argument -c <filename> */
wl_list_init(&paths);
struct path *path = znew(*path);
path->string = xstrdup(filename);
wl_list_append(&paths, &path->link);
} else {
paths_config_create(&paths, "rc.xml");
}
/* Reading file into buffer before parsing - better for unit tests */
stream = fopen(rcxml, "r");
if (!stream) {
wlr_log(WLR_ERROR, "cannot read (%s)", rcxml);
goto no_config;
}
wlr_log(WLR_INFO, "read config file %s", rcxml);
buf_init(&b);
while (getline(&line, &len, stream) != -1) {
char *p = strrchr(line, '\n');
if (p) {
*p = '\0';
struct buf b;
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;
/*
* This is the equivalent of a wl_list_for_each() which optionally
* iterates in reverse depending on 'should_merge_config'
*
* 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);
}
free(line);
fclose(stream);
rcxml_parse_xml(&b);
free(b.buf);
no_config:
wlr_log(WLR_INFO, "read config file %s", path->string);
buf_init(&b);
char *line = NULL;
size_t len = 0;
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();
validate();
}

View file

@ -7,10 +7,12 @@
#include <sys/stat.h>
#include <wlr/util/log.h>
#include "common/buf.h"
#include "common/dir.h"
#include "common/file-helpers.h"
#include "common/spawn.h"
#include "common/string-helpers.h"
#include "config/session.h"
#include "labwc.h"
static bool
string_empty(const char *s)
@ -45,14 +47,15 @@ error:
free(value.buf);
}
static void
/* return true on successful read */
static bool
read_environment_file(const char *filename)
{
char *line = NULL;
size_t len = 0;
FILE *stream = fopen(filename, "r");
if (!stream) {
return;
return false;
}
wlr_log(WLR_INFO, "read environment file %s", filename);
while (getline(&line, &len, stream) != -1) {
@ -64,15 +67,7 @@ read_environment_file(const char *filename)
}
free(line);
fclose(stream);
}
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);
return true;
}
static void
@ -96,7 +91,7 @@ update_activation_env(const char *env_keys)
}
void
session_environment_init(const char *dir)
session_environment_init(void)
{
/*
* 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);
char *environment = build_path(dir, "environment");
if (!environment) {
return;
struct wl_list paths;
paths_config_create(&paths, "environment");
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);
free(environment);
paths_destroy(&paths);
}
void
session_autostart_init(const char *dir)
session_autostart_init(void)
{
/* Update dbus and systemd user environment, each may fail gracefully */
update_activation_env("DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP");
char *autostart = build_path(dir, "autostart");
if (!autostart) {
return;
struct wl_list paths;
paths_config_create(&paths, "autostart");
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)) {
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);
paths_destroy(&paths);
}

View file

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

View file

@ -11,6 +11,7 @@
#include <wlr/util/log.h>
#include "action.h"
#include "common/buf.h"
#include "common/dir.h"
#include "common/font.h"
#include "common/list.h"
#include "common/match.h"
@ -548,21 +549,27 @@ err:
static void
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) {
return;
}
snprintf(buf, sizeof(buf), "%s/%s", rc.config_dir, filename);
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;
FILE *stream = fopen(buf, "r");
if (!stream) {
wlr_log(WLR_ERROR, "cannot read %s", buf);
return;
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) {
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);
parse(server, stream);
fclose(stream);
paths_destroy(&paths);
}
static int

View file

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

View file

@ -23,6 +23,7 @@
#include "common/font.h"
#include "common/graphic-helpers.h"
#include "common/match.h"
#include "common/mem.h"
#include "common/string-helpers.h"
#include "config/rcxml.h"
#include "button/button-png.h"
@ -664,60 +665,36 @@ process_line(struct theme *theme, char *line)
}
static void
theme_read(struct theme *theme, const char *theme_name)
theme_read(struct theme *theme, struct wl_list *paths)
{
FILE *stream = NULL;
char *line = NULL;
size_t len = 0;
char themerc[4096];
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;
if (strlen(theme_dir(theme_name))) {
snprintf(themerc, sizeof(themerc), "%s/themerc",
theme_dir(theme_name));
stream = fopen(themerc, "r");
}
if (!stream) {
if (theme_name) {
wlr_log(WLR_INFO, "cannot find theme %s", theme_name);
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;
}
return;
}
wlr_log(WLR_INFO, "read theme %s", themerc);
while (getline(&line, &len, stream) != -1) {
char *p = strrchr(line, '\n');
if (p) {
*p = '\0';
wlr_log(WLR_INFO, "read theme %s", path->string);
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);
}
process_line(theme, line);
}
free(line);
fclose(stream);
}
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';
zfree(line);
fclose(stream);
if (!should_merge_config) {
break;
}
process_line(theme, line);
}
free(line);
fclose(stream);
}
struct rounded_corner_ctx {
@ -1001,10 +978,15 @@ theme_init(struct theme *theme, const char *theme_name)
theme_builtin(theme);
/* 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 */
theme_read_override(theme);
paths_config_create(&paths, "themerc-override");
theme_read(theme, &paths);
paths_destroy(&paths);
post_processing(theme);
create_corners(theme);