diff --git a/README.md b/README.md index 166a6da..646f202 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,13 @@ Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your link --- ## Configuration -The daemon can currently be configured using a `gamemode.ini` file in `/usr/share/gamemode/`. [gamemode.ini](https://github.com/FeralInteractive/gamemode/blob/master/example/gamemode.ini) is an example of what this file would look like, with explanations for all the variables. +The daemon can currently be configured using a `gamemode.ini` file. [gamemode.ini](https://github.com/FeralInteractive/gamemode/blob/master/example/gamemode.ini) is an example of what this file would look like, with explanations for all the variables. + +Config files are loaded and merged from the following directories, in order: +1. `/usr/share/gamemode/` +2. `/etc/` +3. `$XDG_CONFIG_HOME` or `$HOME/.config/` +4. `$PWD` The file parsing uses [inih](https://github.com/benhoyt/inih). diff --git a/daemon/daemon_config.c b/daemon/daemon_config.c index dd3639d..71ca6c6 100644 --- a/daemon/daemon_config.c +++ b/daemon/daemon_config.c @@ -38,12 +38,13 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include +#include /* Name and possible location of the config file */ #define CONFIG_NAME "gamemode.ini" -#define CONFIG_DIR "/usr/share/gamemode/" /* Default value for the reaper frequency */ #define DEFAULT_REAPER_FREQ 5 @@ -160,8 +161,32 @@ static int inih_handler(void *user, const char *section, const char *name, const /* * Load the config file */ -static void load_config_file(GameModeConfig *self) +static void load_config_files(GameModeConfig *self) { + /* grab the current dir */ + char *config_location_local = get_current_dir_name(); + + /* Get home config location */ + char *config_location_home = NULL; + const char *cfg = getenv("XDG_CONFIG_HOME"); + if (cfg) { + config_location_home = realpath(cfg, NULL); + } else { + cfg = getenv("HOME"); + if (cfg) { + char *cfg_full = NULL; + if (asprintf(&cfg_full, "%s/.config", cfg) > 0) { + config_location_home = realpath(cfg_full, NULL); + free(cfg_full); + } + } else { + struct passwd *p = getpwuid(getuid()); + if (p) { + config_location_home = realpath(p->pw_dir, NULL); + } + } + } + /* Take the write lock for the internal data */ pthread_rwlock_wrlock(&self->rwlock); @@ -172,28 +197,38 @@ static void load_config_file(GameModeConfig *self) memset(self->endscripts, 0, sizeof(self->endscripts)); self->reaper_frequency = DEFAULT_REAPER_FREQ; - /* try locally first */ - FILE *f = fopen(CONFIG_NAME, "r"); - if (!f) { - f = fopen(CONFIG_DIR CONFIG_NAME, "r"); - if (!f) { - /* Failure here isn't fatal */ - LOG_ERROR("Note: No config file found [%s] in working directory or in [%s]\n", - CONFIG_NAME, - CONFIG_DIR); + /* + * Locations to load, in order + * Arrays merge and values overwrite + */ + const char *locations[] = { + "/usr/share/gamemode", /* shipped default config */ + "/etc", /* administrator config */ + config_location_home, /* user defined config eg. $XDG_CONFIG_HOME or $HOME/.config/ */ + config_location_local /* local data eg. $PWD */ + }; + + /* Load each file in order and overwrite values */ + for (unsigned int i = 0; i < sizeof(locations) / sizeof(locations[0]); i++) { + char *path = NULL; + if (locations[i] && asprintf(&path, "%s/" CONFIG_NAME, locations[i]) > 0) { + FILE *f = fopen(path, "r"); + if (f) { + LOG_MSG("Loading config file [%s]\n", path); + int error = ini_parse_file(f, inih_handler, (void *)self); + + /* Failure here isn't fatal */ + if (error) { + LOG_MSG("Failed to parse config file - error on line %d!\n", error); + } + } + free(path); } } - if (f) { - int error = ini_parse_file(f, inih_handler, (void *)self); - - /* Failure here isn't fatal */ - if (error) { - LOG_MSG("Failed to parse config file - error on line %d!\n", error); - } - - fclose(f); - } + /* clean up memory */ + free(config_location_home); + free(config_location_local); /* Release the lock */ pthread_rwlock_unlock(&self->rwlock); @@ -217,7 +252,7 @@ void config_init(GameModeConfig *self) pthread_rwlock_init(&self->rwlock, NULL); /* load the initial config */ - load_config_file(self); + load_config_files(self); } /* @@ -225,7 +260,7 @@ void config_init(GameModeConfig *self) */ void config_reload(GameModeConfig *self) { - load_config_file(self); + load_config_files(self); } /* diff --git a/data/gamemoded.1 b/data/gamemoded.1 index 7483282..43828dd 100644 --- a/data/gamemoded.1 +++ b/data/gamemoded.1 @@ -74,7 +74,20 @@ Or, distribute \fBlibgamemodeauto.so\fR and either link with \fB\-lgamemodeauto\ .SH CONFIG -\fBgamemoded\fR can be configured with a \fBgamemode.ini\fR file found in \fB/usr/share/gamemode/\fR. The daemon will load the config file on start-up if it exists. +\fBgamemoded\fR will load and merge \fBgamemode.ini\fR config files from these directories in the following order: + +.RS 4 +/usr/share/gamemode/ +.RE +.RS 4 +/etc/ +.RE +.RS 4 +$XDG_CONFIG_HOME or $HOME/.config/ +.RE +.RS 4 +$PWD +.RE Behaviour of the config file can be explained by presenting a commented example: