- ${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.
- /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)).
*-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
#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 */
struct rcxml {
char *config_dir;
+ bool merge_config;
/* core */
bool xdg_shell_server_side_deco;
/**
* 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 */
// 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);
}
*
* 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);
+ }
}
#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"
validate_actions();
}
-static void
-rcxml_path(char *buf, size_t len)
+void
+rcxml_read(const char *filename)
{
- if (!rc.config_dir) {
- return;
- }
- snprintf(buf, len, "%s/rc.xml", rc.config_dir);
-}
+ rcxml_init();
+
+ struct wl_list paths;
-static void
-find_config_file(char *buffer, size_t len, const char *filename)
-{
if (filename) {
- snprintf(buffer, len, "%s", filename);
- return;
+ /* 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");
}
- rcxml_path(buffer, len);
-}
-void
-rcxml_read(const char *filename)
-{
- FILE *stream;
- char *line = NULL;
- size_t len = 0;
+ /* Reading file into buffer before parsing - better for unit tests */
struct buf b;
- static char rcxml[4096] = {0};
- rcxml_init();
+ 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;
/*
- * rcxml_read() can be called multiple times, but we only set rcxml[]
- * the first time. The specified 'filename' is only respected the first
- * time.
+ * 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.
*/
- 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;
- }
+ 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;
+ }
- /* 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';
+ 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);
}
- buf_add(&b, line);
- }
- free(line);
- fclose(stream);
- rcxml_parse_xml(&b);
- free(b.buf);
-no_config:
+ zfree(line);
+ fclose(stream);
+ rcxml_parse_xml(&b);
+ zfree(b.buf);
+ if (!should_merge_config) {
+ break;
+ }
+ };
+ paths_destroy(&paths);
post_processing();
validate();
}
#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)
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) {
}
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
}
void
-session_environment_init(const char *dir)
+session_environment_init(void)
{
/*
* Set default for XDG_CURRENT_DESKTOP so xdg-desktop-portal-wlr is happy.
*/
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;
- }
- if (!file_exists(autostart)) {
- wlr_log(WLR_ERROR, "no autostart file");
- goto out;
+ 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;
+ }
}
- 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);
}
{"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'},
" -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"
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;
}
config_file = optarg;
break;
case 'C':
- rc.config_dir = xstrdup(optarg);
+ rc.config_dir = optarg;
break;
case 'd':
verbosity = WLR_DEBUG;
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);
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);
/*
menu_init(&server);
- session_autostart_init(rc.config_dir);
+ session_autostart_init();
if (startup_cmd) {
spawn_async_no_shell(startup_cmd);
}
#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"
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
static int
handle_sighup(int signal, void *data)
{
- session_environment_init(rc.config_dir);
+ session_environment_init();
reload_config_and_theme();
return 0;
}
#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"
}
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];
-
- 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);
- }
- return;
- }
- wlr_log(WLR_INFO, "read theme %s", themerc);
- while (getline(&line, &len, stream) != -1) {
- char *p = strrchr(line, '\n');
- if (p) {
- *p = '\0';
+ 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);
+ FILE *stream = fopen(path->string, "r");
+ if (!stream) {
+ continue;
}
- 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 %s", path->string);
- 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';
+ 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);
+ }
+ zfree(line);
+ fclose(stream);
+ if (!should_merge_config) {
+ break;
}
- process_line(theme, line);
}
- free(line);
- fclose(stream);
}
struct rounded_corner_ctx {
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);