diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 7be9d3e7..9444bd76 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -37,13 +37,23 @@ option. All configuration and theme files except autostart and shutdown are re-loaded on receiving signal SIGHUP. -The *environment* file is parsed as *variable=value* and sets environment -variables accordingly. It is recommended to specify keyboard layout settings and -cursor size/theme here; see environment variable section below for details. Note -that the environment file is treated differently by openbox where it is simply -sourced prior to running openbox. -Note: Tilde (~) and environment variables in the value are expanded, but -subshell syntax and apostrophes are ignored. +Environment variables may be set within *environment* files, wherein each line +defines shell variables in the format *variable=value*. It is recommended to +specify keyboard layout settings and cursor size/theme here; see environment +variable section below for details. Within an XDG Base Directory, a file named +"environment" will be parsed first, followed by any file matching the glob +"environment.d/\*.env". Files within the environment.d directory are parsed in +an arbitrary order; any variables that must be set in a particular sequence +should be set within the same file. Unless the --merge-config option is +specified, labwc will consider a particular XDG Base Directory to have provided +an environment file if that directory contains either the "environment" +directory or at least one "environment.d/\*.env" file. + +Note: environment files are treated differently by Openbox, which will simply +source the file as a valid shell script before running the window manager. Files +are instead parsed directly by labwc, although any environment variables +referenced as $VARIABLE or ${VARIABLE} will be substituted and the tilde (~) +will be expanded as the user's home directory. The *autostart* file is executed as a shell script after labwc has read its configuration and set variables defined in the environment file. Additionally, diff --git a/include/common/string-helpers.h b/include/common/string-helpers.h index 6d0ec905..e95b4e4a 100644 --- a/include/common/string-helpers.h +++ b/include/common/string-helpers.h @@ -59,7 +59,18 @@ char *strdup_printf(const char *fmt, ...); * The separator is arbitrary. When the separator is NULL, a single space will * be used. */ -char *str_join(const char * const parts[], +char *str_join(const char *const parts[], const char *restrict fmt, const char *restrict sep); +/** + * str_endswith - indicate whether a string ends with a given suffix + * @string: string to test + * @suffix: suffix to expect in string + * + * If suffix is "" or NULL, this method always returns true; otherwise, this + * method returns true if and only if the full suffix exists at the end of the + * string. + */ +bool str_endswith(const char *const string, const char *const suffix); + #endif /* LABWC_STRING_HELPERS_H */ diff --git a/src/common/string-helpers.c b/src/common/string-helpers.c index 0a1a8c81..10978507 100644 --- a/src/common/string-helpers.c +++ b/src/common/string-helpers.c @@ -86,7 +86,7 @@ strdup_printf(const char *fmt, ...) } char * -str_join(const char * const parts[], +str_join(const char *const parts[], const char *restrict fmt, const char *restrict sep) { assert(parts); @@ -154,3 +154,19 @@ str_join(const char * const parts[], return buf; } +bool +str_endswith(const char *const string, const char *const suffix) +{ + size_t len_str = string ? strlen(string) : 0; + size_t len_sfx = suffix ? strlen(suffix) : 0; + + if (len_str < len_sfx) { + return false; + } + + if (len_sfx == 0) { + return true; + } + + return strcmp(string + len_str - len_sfx, suffix) == 0; +} diff --git a/src/config/session.c b/src/config/session.c index 56cef717..e657a9ab 100644 --- a/src/config/session.c +++ b/src/config/session.c @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include +#include #include #include #include #include #include +#include #include #include #include @@ -49,9 +51,10 @@ process_line(char *line) buf_add(&value, string_strip(++p)); buf_expand_shell_variables(&value); buf_expand_tilde(&value); - if (string_null_or_empty(key) || !value.len) { + if (string_null_or_empty(key)) { goto error; } + setenv(key, value.buf, 1); error: free(value.buf); @@ -80,6 +83,71 @@ read_environment_file(const char *filename) return true; } +static char * +strdup_env_path_validate(const char *prefix, struct dirent *dirent) +{ + assert(prefix); + + /* Valid environment files always end in '.env' */ + if (!str_endswith(dirent->d_name, ".env")) { + return NULL; + } + + char *full_path = strdup_printf("%s/%s", prefix, dirent->d_name); + if (!full_path) { + return NULL; + } + + /* Valid environment files must be regular files */ + struct stat statbuf; + if (stat(full_path, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) { + return full_path; + } + + free(full_path); + return NULL; +} + +static bool +read_environment_dir(const char *path_prefix) +{ + bool success = false; + char *path = strdup_printf("%s.d", path_prefix); + + errno = 0; + DIR *envdir = opendir(path); + + if (!envdir) { + if (errno != ENOENT) { + const char *err_msg = strerror(errno); + wlr_log(WLR_INFO, + "failed to read environment directory: %s", + err_msg ? err_msg : "reason unknown"); + } + + goto env_dir_cleanup; + } + + struct dirent *dirent; + while ((dirent = readdir(envdir)) != NULL) { + char *env_file_path = strdup_env_path_validate(path, dirent); + if (!env_file_path) { + continue; + } + + if (read_environment_file(env_file_path)) { + success = true; + } + + free(env_file_path); + } + +env_dir_cleanup: + closedir(envdir); + free(path); + return success; +} + static void backend_check_drm(struct wlr_backend *backend, void *is_drm) { @@ -176,7 +244,13 @@ session_environment_init(void) for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); + + /* Process an environment file itself */ bool success = read_environment_file(path->string); + + /* Process a correponding environment.d directory */ + success |= read_environment_dir(path->string); + if (success && !should_merge_config) { break; }