config: add include_one directive

Adds include_one <pattern1> <pattern2>, which loads all files
matching pattern1, then loads files from pattern2 only if their
basename was not already loaded from pattern1.

This allows user configs in ~/.config/sway/conf.d/ to shadow
distro-provided defaults in /etc/sway/conf.d/ by filename without
completely overriding the include path — a common pain point for
packagers on NixOS, Fedora, and similar distributions.

Edge cases handled:
- pattern1 matches nothing: all pattern2 files load normally
- Same basename in both: pattern1 file wins, pattern2 skipped
- Neither pattern matches: silently succeeds (matches include behavior)
This commit is contained in:
Abhijith Dayadi 2026-03-18 22:32:15 +00:00
parent 6d25b100a2
commit d5940c1cc8
7 changed files with 96 additions and 0 deletions

View file

@ -145,6 +145,7 @@ sway_cmd cmd_fullscreen;
sway_cmd cmd_gaps;
sway_cmd cmd_hide_edge_borders;
sway_cmd cmd_include;
sway_cmd cmd_include_one;
sway_cmd cmd_inhibit_idle;
sway_cmd cmd_input;
sway_cmd cmd_seat;

View file

@ -621,6 +621,14 @@ bool load_main_config(const char *path, bool is_active, bool validating);
void load_include_configs(const char *path, struct sway_config *config,
struct swaynag_instance *swaynag);
/**
* Loads included configs using basename-based shadowing.
* Files from pattern1 are always loaded. Files from pattern2 are loaded
* only if their basename was not already loaded from pattern1.
*/
void load_include_one_configs(const char *pattern1, const char *pattern2,
struct sway_config *config, struct swaynag_instance *swaynag);
/**
* Reads the config from the given FILE.
*/

View file

@ -103,6 +103,7 @@ static const struct cmd_handler handlers[] = {
static const struct cmd_handler config_handlers[] = {
{ "default_orientation", cmd_default_orientation },
{ "include", cmd_include },
{ "include_one", cmd_include_one },
{ "primary_selection", cmd_primary_selection },
{ "swaybg_command", cmd_swaybg_command },
{ "swaynag_command", cmd_swaynag_command },

View file

@ -0,0 +1,12 @@
#include "sway/commands.h"
#include "sway/config.h"
struct cmd_results *cmd_include_one(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "include_one", EXPECTED_EQUAL_TO, 2))) {
return error;
}
load_include_one_configs(argv[0], argv[1], config,
&config->swaynag_config_errors);
return cmd_results_new(CMD_SUCCESS, NULL);
}

View file

@ -623,6 +623,72 @@ cleanup:
free(wd);
}
void load_include_one_configs(const char *pattern1, const char *pattern2,
struct sway_config *config, struct swaynag_instance *swaynag) {
char *wd = getcwd(NULL, 0);
if (!wd) {
sway_log(SWAY_ERROR, "include_one: failed to get working directory");
return;
}
char *parent_path = strdup(config->current_config_path);
const char *parent_dir = dirname(parent_path);
if (chdir(parent_dir) < 0) {
sway_log(SWAY_ERROR, "include_one: failed to change working directory");
goto cleanup;
}
list_t *loaded_basenames = create_list();
if (!loaded_basenames) {
sway_log(SWAY_ERROR, "include_one: failed to allocate list");
goto cleanup;
}
wordexp_t p;
if (wordexp(pattern1, &p, 0) == 0) {
char **w = p.we_wordv;
for (size_t i = 0; i < p.we_wordc; i++) {
if (load_include_config(w[i], config, swaynag)) {
char *tmp = strdup(w[i]);
list_add(loaded_basenames, strdup(basename(tmp)));
free(tmp);
}
}
wordfree(&p);
}
if (wordexp(pattern2, &p, 0) == 0) {
char **w = p.we_wordv;
for (size_t i = 0; i < p.we_wordc; i++) {
char *tmp = strdup(w[i]);
char *base = basename(tmp);
bool already_loaded = false;
for (int j = 0; j < loaded_basenames->length; j++) {
if (strcmp(loaded_basenames->items[j], base) == 0) {
already_loaded = true;
break;
}
}
if (!already_loaded) {
load_include_config(w[i], config, swaynag);
}
free(tmp);
}
wordfree(&p);
}
for (int i = 0; i < loaded_basenames->length; i++) {
free(loaded_basenames->items[i]);
}
list_free(loaded_basenames);
if (chdir(wd) < 0) {
sway_log(SWAY_ERROR, "failed to change working directory");
}
cleanup:
free(parent_path);
free(wd);
}
void run_deferred_commands(void) {
if (!config->cmd_queue->length) {
return;

View file

@ -76,6 +76,7 @@ sway_sources = files(
'commands/max_render_time.c',
'commands/opacity.c',
'commands/include.c',
'commands/include_one.c',
'commands/input.c',
'commands/layout.c',
'commands/mode.c',

View file

@ -72,6 +72,13 @@ The following commands may only be used in the configuration file.
*wordexp*(3) for details). The same include file can only be included once;
subsequent attempts will be ignored.
*include_one* <pattern1> <pattern2>
Include files from _pattern1_. Then include files from _pattern2_ only
if a file with the same basename was not already included from
_pattern1_. Both patterns expand shell syntax (see *wordexp*(3) for
details). This allows user configs to shadow distro-provided defaults
by filename.
*swaybg_command* <command>
Executes custom background _command_. Default is _swaybg_. Refer to
*sway-output*(5) for more information.