diff options
Diffstat (limited to 'redshift/config.c')
-rw-r--r-- | redshift/config.c | 1172 |
1 files changed, 1172 insertions, 0 deletions
diff --git a/redshift/config.c b/redshift/config.c new file mode 100644 index 0000000..29c5a53 --- /dev/null +++ b/redshift/config.c @@ -0,0 +1,1172 @@ +/*- + * redshift-ng - Automatically adjust display colour temperature according the Sun + * + * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com> + * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se> + * + * redshift-ng is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * redshift-ng is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with redshift-ng. If not, see <http://www.gnu.org/licenses/>. + */ +#include "common.h" + + +/** + * Output usage synopsis, without exiting + * + * @param f Output sink + */ +static void +usage_no_exit(FILE *f) +{ + fprintf(f, _("Usage: %s %s\n"), argv0, + _("[-b brightness] [-c config-file] [-D | +D] [-E | +E | -e elevations] " + "[-g gamma] [-H hook-file] [-l latitude:longitude | -l provider[:options]] " + "[-m method[:options]] [-P | +P] [-r | +r] [-dv] " + "[-O temperature | -o | -p | -t temperature | -x] | -h | -V")); +} + + +/** + * Output usage synopsis and exit + */ +static void +usage(void) +{ + usage_no_exit(stderr); + exit(1); +} + + +struct colour_setting day_settings; +struct colour_setting night_settings; +union scheme scheme = {.type = SOLAR_SCHEME}; +enum program_mode mode = PROGRAM_MODE_CONTINUAL; +int preserve_gamma; +int use_fade; +int verbose = 0; +char *hook_file; + + +/** + * Print general help text + */ +static void +print_help(void) +{ + usage_no_exit(stdout); + printf("\n"); + + printf(_("Automatically adjust display colour temperature according the Sun\n")); + printf("\n"); + + printf(_(" -b day:night Select whitepoint brightness (Default: 1:1)\n")); + printf(_(" -c file Load settings from specified configuration file\n")); + printf(_(" -D Start in enabled state (Default)\n")); + printf(_(" +D Start in disabled state\n")); + printf(_(" -d Keep the process alive and remove the colour effects when killed\n")); + printf(_(" -E Use wall-clock based schedule\n")); + printf(_(" +E Use solar elevation based schedule\n")); + printf(_(" -e day:night Select solar elevation thresholds for day and night (Default: %g:%g)\n"), + DEFAULT_HIGH_ELEVATION, DEFAULT_LOW_ELEVATION); + printf(_(" -g day:night Additional gamma correction (Default: 1:1:1:1:1:1)\n")); + printf(_(" -H hook-file Select hook file or directrory\n")); + printf(_(" -h Display this help message\n")); + printf(_(" -l lat:lon Specific geographical location\n")); + printf(_(" -l provider[:options] Select location provider to get geographical location\n")); + printf(_(" (Use `-l list' to list available providers)\n")); + printf(_(" -m method[:options] Select adjustment method for applying colour settings\n")); + printf(_(" (Use `-m list' to list adjustment methods)\n")); + printf(_(" -O day:night Select colour temperature and apply once\n")); + printf(_(" -o Apply colour settings once, then exit\n")); + printf(_(" -P Remove preexisting colour adjustments\n")); + printf(_(" +P Preserve preexisting colour adjustments (Default)\n")); + printf(_(" -p Print parameters and exit\n")); + printf(_(" -r Disable fading between colour adjustments\n")); + printf(_(" +r Enable fading between colour adjustments (Default)\n")); + printf(_(" -t day:night Select colour temperature and apply continually (Default: %lu:%lu\n"), + DEFAULT_DAY_TEMPERATURE, DEFAULT_NIGHT_TEMPERATURE); + printf(_(" -V Show program implementation and version\n")); + printf(_(" -v Enable verbose output\n")); + printf(_(" -x Remove adjustments from screen\n")); + + printf("\n"); + printf(_("This is a breif summary, see `%s' for more information.\n"), "man redshift"); + printf("\n"); + printf(_("The neutral temperature is %luK. Using this value will not change the color\n" + "temperature of the display. Setting the color temperature to a value higher\n" + "than this results in more blue light, and setting a lower value will result in\n" + "more red light.\n"), NEUTRAL_TEMPERATURE); + printf("\n"); +} + + +/** + * Parse a boolean string + * + * @param s The string to parse + * @param key The name of the configuration assigned the value `s` + * @return 1 if `s` represents true, 0 if `s` represents false + */ +GCC_ONLY(__attribute__((__pure__))) +static int +get_boolean(const char *s, const char *key) +{ + int ret; + if (s[0] == '0' || s[0] == '1') { + ret = s[0] - '0'; + if (s[1]) + goto bad; + } else { + bad: + eprintf(_("Value of configuration `%s' must be either `1' or `0'."), key); + } + return ret; +} + + +/** + * atof(3)-like wrapper for strtod(3) that checks that the string is valid + * + * @param s The string to parse + * @param key The name of the configuration assigned the value `s` + * @return The encoded value + */ +static double +checked_atof(const char *s, const char *key) +{ + double ret; + errno = 0; + ret = strtod(s, (void *)&s); + if (errno || *s) + eprintf(_("Value of configuration `%s' is not a value decimal value."), key); + return ret; +} + + +/** + * Split a list of values, and remove leading and trailing whitespace + * from each value + * + * @param s The string to split; will be modified + * @param count The number of despired strings + * @param strs Output array for the strings; each element will + * be set to `NULL` or `s` with an offset + * @param delim The character `s` shall be split by, cannot be + * a whitespace character + * @return 1 if `s` is valid, 0 otherwise + * + * If `count` is 1, + * the value most be specified, + * if `count` is 2, + * at least one of values must be specified + * if `count` is 3, + * all values most be specified, otherwise `s` must only contain + * on value, and no colon, which will be used from each output value, or + * if `count` is 6, + * `s` must be on one of the formats: + * `a`: + * the specified value is used for each output value, + * `a:b`: + * the three lower output values will be set to `a`, which may be empty/`NULL`, and + * the three upper output values will be set to `b`, which may be empty/`NULL`; + * at least `a` or `b` must be non-empty, + * `a:b:c`: + * each value most be specified, `s` will be interpreted as `a:b:c:a:b:c`, + * `:a:b:c`: + * each value most be specified, the lower three values be set to `NULL`, + * and `a`, `b`, `c` will be used fo the upper three values, + * `a:b:c:`: + * each value most be specified, the upper three values be set to `NULL`, + * and `a`, `b`, `c` will be used fo the lower three values, or + * `a:b:c:d:e:f`: + * each value most be specified; + * where ':' represents `delim` + * + * Summarily said, `s` may contain a scalar value or a 3-tuple, and it may + * also contain a value or one value for daytime and one value or nighttime. + * If configured to use 3-tuple but scalar is provided, the provided value is + * used for each of the 3 requested values. If configured to use daytime and + * nighttime, but only one is specified it is used for both, but if `s` + * starts with `delim`, daytime is skipped but if `s` ends with `delim`, + * nighttime, is skipped; but both cannot be skipped. + */ +static int +get_strings(char *s, int count, char *strs[], char delim) +{ + int i = 0, n; + + /* Split by colon and left-trim */ + for (i = 0; i < count;) { + strs[i++] = s = ltrim(s); + s = strchr(s, delim); + if (!s) + break; + *s++ = '\0'; + } + n = i; + + /* Confirm no excess strings */ + if (s && *ltrim(s)) + return 0; + + /* Right-trim and replace empty strings with NULL */ + for (i = 0; i < n; i++) + if (!*rtrim(strs[i], NULL)) + strs[i] = NULL; + + /* Validate NULLs */ + switch (n) { + case 1: + /* must be specified */ + if (!strs[0]) + return 0; + break; + case 2: + /* at least one most be specified */ + if (!strs[0] && !strs[1]) + return 0; + break; + case 3: + /* each most be specified */ + if (!strs[0] || !strs[1] || !strs[2]) + return 0; + break; + case 4: + /* exactly either the first or the last shall be NULL */ + if (!strs[0] == !strs[3] || !strs[1] || !strs[2]) + return 0; + break; + case 6: + /* each most be specified */ + if (!strs[0] || !strs[1] || !strs[2] || !strs[3] || !strs[4] || !strs[5]) + return 0; + break; + default: + /* n==5 is always invalid */ + return 0; + } + + /* Duplicate to fill `strs` */ + switch (count) { + case 2: + if (n == 1) + strs[1] = strs[0]; + break; + case 3: + if (n == 1) + strs[2] = strs[1] = strs[0]; + else if (n == 2) + return 0; + break; + case 6: + if (n == 1) { + strs[5] = strs[4] = strs[3] = strs[2] = strs[1] = strs[0]; + } else if (n == 2) { + strs[5] = strs[4] = strs[3] = strs[1]; + strs[2] = strs[1] = strs[0]; + } else if (n == 3) { + strs[5] = strs[2]; + strs[4] = strs[1]; + strs[3] = strs[0]; + } else if (n == 4 && !strs[0]) { + strs[5] = strs[3]; + strs[4] = strs[2]; + strs[3] = strs[1]; + strs[2] = NULL; + strs[1] = NULL; + } else if (n == 4) { + strs[5] = NULL; + strs[4] = NULL; + } + break; + default: + break; + } + + return 1; +} + + +/** + * Parse and set temperature settings + * + * @param str The temperature specification to parse + * @param day The currently specified temperature for daytime, + * will be updated; `NULL` if it shall not be set + * @param night The currently specified temperature for nighttime, + * will be updated; `NULL` if it shall not be set + * @param key The configuration file setting being parsed, + * `NULL` if parsing the command line + */ +static void +set_temperature(char *str, struct setting_lu *day, struct setting_lu *night, const char *key) +{ + struct setting_lu *settings[] = {day, night}; + enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE; + char *strs[2], *end; + size_t i, j; + + if (!get_strings(str, !!day + !!night, strs, ':')) { + invalid: + weprintf(_("Malformed temperature argument.")); + eprintf(_("Try `-h' for more information.")); + } + + errno = 0; + for (i = 0, j = 0; i < ELEMSOF(settings); j += settings[i++] ? 1 : 0) { + if (!settings[i] || !strs[j] || settings[i]->source > source) + continue; + if (settings[i]->source & SETTING_CONFIGFILE) { + if (i == 0) + weprintf(_("Daytime temperature specified multiple times in configuration file.")); + else + weprintf(_("Night temperature specified multiple times in configuration file.")); + } + settings[i]->source |= source; + settings[i]->value = strtoul(strs[j], &end, 10); + if (errno || end[*end == 'K']) + goto invalid; + if (!WITHIN(MIN_TEMPERATURE, settings[i]->value, MAX_TEMPERATURE)) + eprintf(_("Temperature must be between %luK and %luK."), MIN_TEMPERATURE, MAX_TEMPERATURE); + } +} + + +/** + * Parse and set whitepoint brightness settings + * + * @param str The brightness specification to parse + * @param day The currently specified brightness for daytime, + * will be updated; `NULL` if it shall not be set + * @param night The currently specified brightness for nighttime, + * will be updated; `NULL` if it shall not be set + * @param key The configuration file setting being parsed, + * `NULL` if parsing the command line + */ +static void +set_brightness(char *str, struct setting_f *day, struct setting_f *night, const char *key) +{ + struct setting_f *settings[] = {day, night}; + enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE; + char *strs[2], *end; + size_t i, j; + + if (!get_strings(str, !!day + !!night, strs, ':')) { + invalid: + weprintf(_("Malformed brightness argument.")); + eprintf(_("Try `-h' for more information.")); + } + + errno = 0; + for (i = 0, j = 0; i < ELEMSOF(settings); j += settings[i++] ? 1 : 0) { + if (!settings[i] || !strs[j] || settings[i]->source > source) + continue; + if (settings[i]->source & SETTING_CONFIGFILE) { + if (i == 0) + weprintf(_("Daytime brightness specified multiple times in configuration file.")); + else + weprintf(_("Night brightness specified multiple times in configuration file.")); + } + settings[i]->source |= source; + settings[i]->value = strtod(strs[j], &end); + if (errno || *end) + goto invalid; + if (!WITHIN(MIN_BRIGHTNESS, settings[i]->value, MAX_BRIGHTNESS)) + eprintf(_("Brightness values must be between %.1f and %.1f."), MIN_BRIGHTNESS, MAX_BRIGHTNESS); + } +} + + +/** + * Parse and set gamma settings + * + * @param str The gamma specification to parse + * @param day The currently specified gamma for daytime, + * will be updated; `NULL` if it shall not be set + * @param night The currently specified gamma for nighttime, + * will be updated; `NULL` if it shall not be set + * @param key The configuration file setting being parsed, + * `NULL` if parsing the command line + */ +static void +set_gamma(char *str, struct setting_f3 *day, struct setting_f3 *night, const char *key) +{ + struct setting_f3 *settings[] = {day, night}; + enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE; + char *strs[6], *end; + size_t i, j, k; + + if (!get_strings(str, 3 * (!!day + !!night), strs, ':')) { + invalid: + weprintf(_("Malformed gamma argument.")); + eprintf(_("Try `-h' for more information.")); + } + + errno = 0; + for (i = 0, j = 0; i < ELEMSOF(settings); j += settings[i++] ? 3 : 0) { + if (!settings[i] || !strs[j] || settings[i]->source > source) + continue; + if (settings[i]->source & SETTING_CONFIGFILE) { + if (i == 0) + weprintf(_("Daytime gamma specified multiple times in configuration file.")); + else + weprintf(_("Night gamma specified multiple times in configuration file.")); + } + settings[i]->source |= source; + for (k = 0; k < 3; k++) { + settings[i]->value[k] = strtod(strs[j + k], &end); + if (errno || *end) + goto invalid; + if (!WITHIN(MIN_GAMMA, settings[i]->value[k], MAX_GAMMA)) + eprintf(_("Gamma values must be between %.1f and %.1f."), MIN_GAMMA, MAX_GAMMA); + } + } +} + + +/** + * Parse and set solar elevation settings + * + * The fucntion assumes that the setting source is the command line + * + * @param str The gamma specification to parse + * @param day The currently specified lowest solar elevation for + * daytime, will be updated; `NULL` if it shall not be set + * @param night The currently specified highest solar elevation for + * nighttime, will be updated; `NULL` if it shall not be set + */ +static void +set_elevations(char *str, struct setting_f *day, struct setting_f *night) +{ + struct setting_f *settings[] = {day, night}; + const enum setting_source source = SETTING_CMDLINE; + char *strs[2], *end; + size_t i, j; + + if (!get_strings(str, !!day + !!night, strs, ':')) { + invalid: + weprintf(_("Malformed solar elevation argument.")); + eprintf(_("Try `-h' for more information.")); + } + + errno = 0; + for (i = 0, j = 0; i < ELEMSOF(settings); j += settings[i++] ? 1 : 0) { + if (!settings[i] || !strs[j] || settings[i]->source > source) + continue; + settings[i]->source |= source; + settings[i]->value = strtod(strs[j], &end); + if (errno || *end) + goto invalid; + } +} + + +/** + * Parse a time string on either of the formats "HH:MM" and "HH:MM:SS" + * + * Times up to, but excluding, 48:00 are supported. + * + * Leap seconds are not supported + * + * @param str String to parse + * @return The represented time, -1 if malformed + */ +static time_t +parse_time(char *str) +{ + time_t ret; + unsigned long int v; + + errno = 0; + + if (!isdigit(*str)) + return -1; + v = strtoul(str, &str, 10); + if (errno || *str++ != ':' || v >= 48UL) + return -1; + ret = (time_t)(v * 60UL * 60UL); + + if (!isdigit(*str)) + return -1; + v = strtoul(str, &str, 10); + if (errno || v >= 60UL) + return -1; + ret += (time_t)(v * 60UL); + + if (*str) { + if (*str++ != ':') + return -1; + if (!isdigit(*str)) + return -1; + v = strtoul(str, &str, 10); + if (errno || *str || v >= 60UL) + return -1; + ret += (time_t)v; + } + + return ret; +} + + +/** + * Parse and set a transition time setting + * + * @param str The transition time to parse + * @param start The currently specified transition start, will be updated + * @param end The currently specified transition end, will be updated + * @param key The configuration file setting being parsed, + * `NULL` if parsing the command line + * @return Normally 1, 0 if `str` is malformeda + */ +static int +set_transition_time(char *str, struct setting_time *start, struct setting_time *end, const char *key) +{ + struct setting_time *settings[] = {start, end}; + enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE; + char *strs[ELEMSOF(settings)]; + int i; + + if (!get_strings(str, ELEMSOF(strs), strs, '-')) + return 0; + + for (i = 0; i < (int)ELEMSOF(settings); i++) { + if (!strs[i] || settings[i]->source > source) + continue; + if (settings[i]->source & SETTING_CONFIGFILE) { + if (i == 0) + weprintf(_("Start value for `%s' specified multiple times in configuration file."), key); + else + weprintf(_("End value for `%s' specified multiple times in configuration file."), key); + } + settings[i]->source |= source; + settings[i]->value = parse_time(strs[i]); + if (settings[i]->value < 0) + return 0; + } + + return 1; +} + + +/** + * Print list of available adjustment methods, + * and some helpful information + */ +static void +print_method_list(void) +{ + size_t i; + printf(_("Available adjustment methods:\n")); + for (i = 0; gamma_methods[i]; i++) + if (gamma_methods[i]->is_available()) + printf(" %s\n", gamma_methods[i]->name); + + printf("\n"); + printf(_("Specify colon-separated options with `-m METHOD:OPTIONS'.\n")); + /* TRANSLATORS: `help' must not be translated. */ + printf(_("Try `-m METHOD:help' for help.\n")); +} + + +/** + * Print list of available location providers, + * and some helpful information + */ +static void +print_provider_list(void) +{ + size_t i; + printf(_("Available location providers:\n")); + for (i = 0; location_providers[i]; i++) + printf(" %s\n", location_providers[i]->name); + + printf("\n"); + printf(_("Specify colon-separated options with `-l PROVIDER:OPTIONS'.\n")); + /* TRANSLATORS: `help' must not be translated. */ + printf(_("Try `-l PROVIDER:help' for help.\n")); +} + + +/** + * Get adjustment method by name + * + * @param name The name of the adjustment method to return + * @return The adjustment method + */ +GCC_ONLY(__attribute__((__pure__, __returns_nonnull__))) +static const struct gamma_method * +find_gamma_method(const char *name) +{ + size_t i; + for (i = 0; gamma_methods[i]; i++) + if (!strcasecmp(name, gamma_methods[i]->name)) + return gamma_methods[i]; + /* TRANSLATORS: This refers to the method used to adjust colours e.g. VidMode */ + eprintf(_("Unknown adjustment method `%s'."), name); +} + + +/** + * Get location provider by name + * + * @param name The name of the location provider to return + * @return The location provider + */ +GCC_ONLY(__attribute__((__pure__, __returns_nonnull__))) +static const struct location_provider * +find_location_provider(const char *name) +{ + size_t i; + for (i = 0; location_providers[i]; i++) + if (!strcasecmp(name, location_providers[i]->name)) + return location_providers[i]; + eprintf(_("Unknown location provider `%s'."), name); +} + + +/** + * Load default settings + * + * @param settings Output parameter for the settings + */ +static void +load_defaults(struct settings *settings) +{ + memset(settings, 0, sizeof(*settings)); /* set each `.source` to `SETTING_DEFAULT` and booleans to 0 */ + + settings->config_file = NULL; + settings->scheme_type = UNSPECIFIED_SCHEME; + + settings->day.temperature.value = DEFAULT_DAY_TEMPERATURE; + settings->day.brightness.value = DEFAULT_DAY_BRIGHTNESS; + settings->day.gamma.value[0] = DEFAULT_DAY_GAMMA; + settings->day.gamma.value[1] = DEFAULT_DAY_GAMMA; + settings->day.gamma.value[2] = DEFAULT_DAY_GAMMA; + + settings->night.temperature.value = DEFAULT_NIGHT_TEMPERATURE; + settings->night.brightness.value = DEFAULT_NIGHT_BRIGHTNESS; + settings->night.gamma.value[0] = DEFAULT_NIGHT_GAMMA; + settings->night.gamma.value[1] = DEFAULT_NIGHT_GAMMA; + settings->night.gamma.value[2] = DEFAULT_NIGHT_GAMMA; + + settings->hook_file.value = NULL; + settings->preserve_gamma.value = 1; + settings->use_fade.value = 1; + + settings->elevation_high.value = DEFAULT_HIGH_ELEVATION; + settings->elevation_low.value = DEFAULT_LOW_ELEVATION; + + settings->method = NULL; + settings->method_args = NULL; + + settings->provider = NULL; + settings->provider_args = NULL; +} + + +/** + * Load settings from the command line + * + * @param settings The currently loaded settings, will be updated + * @param argc Number of command line arguments + * @param argv `NULL` terminated list of command line arguments, + * including argument zero + */ +static void +load_from_cmdline(struct settings *settings, int argc, char *argv[]) +{ + const char *provider_name; + char *s, *end, *value; + + ARGBEGIN { + case 'b': + set_brightness(ARG(), &settings->day.brightness, &settings->night.brightness, NULL); + break; + + case 'c': + settings->config_file = ARG(); + break; + + case 'D': + settings->disabled.source |= SETTING_CMDLINE; + settings->disabled.value = 0; + break; + + case 'd': + settings->until_death = 1; + break; + + case 'E': + settings->scheme_type = CLOCK_SCHEME; + break; + + case 'e': + set_elevations(ARG(), &settings->elevation_high, &settings->elevation_low); + settings->scheme_type = SOLAR_SCHEME; + break; + + case 'g': + set_gamma(ARG(), &settings->day.gamma, &settings->night.gamma, NULL); + break; + + case 'H': + settings->hook_file.source |= SETTING_CMDLINE; + free(settings->hook_file.value); + settings->hook_file.value = ARG(); + break; + + case 'h': + print_help(); + exit(0); + + case 'l': + value = ARG(); + + /* Print list of providers if argument is `list' */ + if (!strcasecmp(value, "list")) { + print_provider_list(); + exit(0); + } + + provider_name = NULL; + + /* Don't save the result of strtof(); we simply want + to know if value can be parsed as a float. */ + errno = 0; + strtof(value, &end); + if (!errno && *end == ':') { + /* Use instead as arguments to `manual'. */ + provider_name = "manual"; + settings->provider_args = value; + } else { + /* Split off provider arguments. */ + s = strchr(value, ':'); + if (s) { + *s++ = '\0'; + settings->provider_args = s; + } + + provider_name = value; + } + + /* Lookup provider from name. */ + settings->provider = find_location_provider(provider_name); + + /* Print provider help if arg is `help'. */ + if (settings->provider_args && !strcasecmp(settings->provider_args, "help")) { + settings->provider->print_help(); + exit(0); + } + break; + + case 'm': + value = ARG(); + + /* Print list of methods if argument is `list' */ + if (!strcasecmp(value, "list")) { + print_method_list(); + exit(0); + } + + /* Split off method arguments. */ + s = strchr(value, ':'); + if (s) { + *s++ = '\0'; + settings->method_args = s; + } + + /* Find adjustment method by name. */ + settings->method = find_gamma_method(value); + + /* Print method help if arg is `help'. */ + if (settings->method_args && !strcasecmp(settings->method_args, "help")) { + settings->method->print_help(); + exit(0); + } + break; + + case 'O': + mode = PROGRAM_MODE_ONE_SHOT; + set_temperature(ARG(), &settings->day.temperature, &settings->night.temperature, NULL); + break; + + case 'o': + mode = PROGRAM_MODE_ONE_SHOT; + break; + + case 'P': + settings->preserve_gamma.source |= SETTING_CMDLINE; + settings->preserve_gamma.value = 0; + break; + + case 'p': + mode = PROGRAM_MODE_PRINT; + break; + + case 'r': + settings->use_fade.source |= SETTING_CMDLINE; + settings->use_fade.value = 0; + break; + + case 't': + set_temperature(ARG(), &settings->day.temperature, &settings->night.temperature, NULL); + break; + + case 'V': + printf("%s\n", VERSION_STRING); + exit(0); + + case 'v': + verbose = 1; + break; + + case 'x': + mode = PROGRAM_MODE_RESET; + break; + + default: + usage(); + + } ARGALT('+') { + case 'D': + settings->disabled.source |= SETTING_CMDLINE; + settings->disabled.value = 1; + break; + + case 'E': + settings->scheme_type = SOLAR_SCHEME; + break; + + case 'P': + settings->preserve_gamma.source |= SETTING_CMDLINE; + settings->preserve_gamma.value = 1; + break; + + case 'r': + settings->use_fade.source |= SETTING_CMDLINE; + settings->use_fade.value = 1; + break; + + default: + usage(); + } ARGEND; + + if (argc) + usage(); +} + + +/** + * Load an setting, form the "redshift" section, from the configuration file + * + * @param settings The currently loaded settings, will be updated + * @param key The name of the configuration + * @param value The value of the configuration + */ +static void +load_from_config_ini(struct settings *settings, const char *key, char *value) +{ + if (!strcasecmp(key, "temp") || !strcasecmp(key, "temperature")) { + set_temperature(value, &settings->day.temperature, &settings->night.temperature, key); + } else if (!strcasecmp(key, "temp-day") || !strcasecmp(key, "temperature-day")) { + set_temperature(value, &settings->day.temperature, NULL, key); + } else if (!strcasecmp(key, "temp-night") || !strcasecmp(key, "temperature-night")) { + set_temperature(value, NULL, &settings->night.temperature, key); + + } else if (!strcasecmp(key, "brightness")) { + set_brightness(value, &settings->day.brightness, &settings->night.brightness, key); + } else if (!strcasecmp(key, "brightness-day")) { + set_brightness(value, &settings->day.brightness, NULL, key); + } else if (!strcasecmp(key, "brightness-night")) { + set_brightness(value, NULL, &settings->night.brightness, key); + + } else if (!strcasecmp(key, "gamma")) { + set_gamma(value, &settings->day.gamma, &settings->night.gamma, key); + } else if (!strcasecmp(key, "gamma-day")) { + set_gamma(value, &settings->day.gamma, NULL, key); + } else if (!strcasecmp(key, "gamma-night")) { + set_gamma(value, NULL, &settings->night.gamma, key); + + } else if (!strcasecmp(key, "transition")) { + weprintf(_("`transition' is deprecated and has been replaced with `fade'.")); + goto set_use_fade; + } else if (!strcasecmp(key, "fade")) { + set_use_fade: + if (settings->use_fade.source & SETTING_CONFIGFILE) + weprintf(_("`fade' setting specified multiple times in configuration file.")); + settings->use_fade.source |= SETTING_CONFIGFILE; + if (settings->use_fade.source <= SETTING_CONFIGFILE) + settings->use_fade.value = get_boolean(value, key); + + } else if (!strcasecmp(key, "hook")) { + if (settings->hook_file.source & SETTING_CONFIGFILE) + weprintf(_("`hook' setting specified multiple times in configuration file.")); + settings->hook_file.source |= SETTING_CONFIGFILE; + if (settings->hook_file.source <= SETTING_CONFIGFILE) { + free(settings->hook_file.value); + settings->hook_file.value = estrdup(value); + } + + } else if (!strcasecmp(key, "preserve-gamma")) { + if (settings->preserve_gamma.source & SETTING_CONFIGFILE) + weprintf(_("`preserve-gamma' setting specified multiple times in configuration file.")); + settings->preserve_gamma.source |= SETTING_CONFIGFILE; + if (settings->preserve_gamma.source <= SETTING_CONFIGFILE) + settings->preserve_gamma.value = get_boolean(value, key); + + } else if (!strcasecmp(key, "start-disabled")) { + if (settings->disabled.source & SETTING_CONFIGFILE) + weprintf(_("`start-disabled' setting specified multiple times in configuration file.")); + settings->disabled.source |= SETTING_CONFIGFILE; + if (settings->disabled.source <= SETTING_CONFIGFILE) + settings->disabled.value = get_boolean(value, key); + + } else if (!strcasecmp(key, "elevation-high")) { + if (settings->elevation_high.source & SETTING_CONFIGFILE) + weprintf(_("`elevation-high' setting specified multiple times in configuration file.")); + settings->elevation_high.source |= SETTING_CONFIGFILE; + if (settings->elevation_high.source <= SETTING_CONFIGFILE) + settings->elevation_high.value = checked_atof(value, key); + + } else if (!strcasecmp(key, "elevation-low")) { + if (settings->elevation_low.source & SETTING_CONFIGFILE) + weprintf(_("`elevation-low' setting specified multiple times in configuration file.")); + settings->elevation_low.source |= SETTING_CONFIGFILE; + if (settings->elevation_low.source <= SETTING_CONFIGFILE) + settings->elevation_low.value = checked_atof(value, key); + + } else if (!strcasecmp(key, "dawn-time")) { + if (!set_transition_time(value, &settings->dawn_start, &settings->dawn_end, key)) + eprintf(_("Malformed dawn-time setting `%s'."), value); + + } else if (!strcasecmp(key, "dusk-time")) { + if (!set_transition_time(value, &settings->dusk_start, &settings->dusk_end, key)) + eprintf(_("Malformed dusk-time setting `%s'."), value); + + } else if (!strcasecmp(key, "adjustment-method")) { + if (!settings->method) + settings->method = find_gamma_method(value); + + } else if (!strcasecmp(key, "location-provider")) { + if (!settings->provider) + settings->provider = find_location_provider(value); + + } else { + weprintf(_("Unknown configuration setting `%s'."), key); + } +} + + +void +load_settings(struct settings *settings, int argc, char *argv[]) +{ + struct config_ini_section *section; + struct config_ini_setting *setting; + const struct time_period *first, *current; + const char *conf_path, *p; + char *conf_pathbuf, *s; + int i, j, n; + time_t duration; + size_t len; + + /* Clear unused bit so they do not interfere with comparsion */ + memset(&day_settings, 0, sizeof(day_settings)); + memset(&night_settings, 0, sizeof(night_settings)); + + /* Load settings; some validation takes place */ + load_defaults(settings); + load_from_cmdline(settings, argc, argv); + conf_path = config_ini_init(&settings->config, settings->config_file, &conf_pathbuf); + if ((section = config_ini_get_section(&settings->config, "redshift"))) + for (setting = section->settings; setting; setting = setting->next) + load_from_config_ini(settings, setting->name, setting->value); + + /* Further validate settings and select scheme */ + n = !!settings->dawn_start.source + !!settings->dawn_end.source; + n += !!settings->dusk_start.source + !!settings->dusk_end.source; + if (settings->scheme_type == SOLAR_SCHEME) { + n = 0; + } else if (settings->scheme_type == CLOCK_SCHEME) { + if (!n) + eprintf(_("`-E' option used with a time-configuration configured.")); + } + if (n) { + scheme.type = CLOCK_SCHEME; + if (n != 4) + eprintf(_("Partial time-configuration not supported!")); + + if (settings->dawn_start.value >= ONE_DAY || settings->dusk_start.value >= ONE_DAY || + labs((long)settings->dawn_end.value - (long)settings->dawn_start.value) > (long)ONE_DAY || + labs((long)settings->dusk_end.value - (long)settings->dusk_start.value) > (long)ONE_DAY) + goto invalid_twilight; + + /* TODO deal with edge-case where one of the twilights last 24 hour */ + settings->dawn_end.value %= ONE_DAY; + settings->dusk_end.value %= ONE_DAY; + if (settings->dawn_start.value <= settings->dawn_end.value) { + if (BETWEEN(settings->dawn_start.value, settings->dusk_start.value, settings->dawn_end.value) || + BETWEEN(settings->dawn_start.value, settings->dusk_end.value, settings->dawn_end.value)) + goto invalid_twilight; + } else { + if (!WITHIN(settings->dawn_end.value, settings->dusk_start.value, settings->dawn_start.value) || + !WITHIN(settings->dawn_end.value, settings->dusk_end.value, settings->dawn_start.value)) + goto invalid_twilight; + } + } + if (settings->elevation_high.value < settings->elevation_low.value) + eprintf(_("High transition elevation cannot be lower than the low transition elevation.")); + + /* If resetting effects, use neutral colour settings (static scheme) and do not preserve gamma */ + if (mode == PROGRAM_MODE_RESET) { + scheme.type = STATIC_SCHEME; + settings->preserve_gamma.value = 0; + day_settings = COLOUR_SETTING_NEUTRAL; + night_settings = COLOUR_SETTING_NEUTRAL; + goto settings_published; + } + + /* Make hook file absolute if set from config file (relative to config file) */ + if (settings->hook_file.source == SETTING_CONFIGFILE && conf_path) { +#ifdef WINDOWS + /* Regular absolute path */ + if (isalpha(settings->hook_file.value[0]) && settings->hook_file.value[1] == ':') + goto absolute_hook_path; + /* Absolute extended path or network path (relative extended paths do not exist) */ + if (settings->hook_file.value[0] == '\\' && settings->hook_file.value[1] == '\\') + goto absolute_hook_path; + /* Path relative to root */ + if (settings->hook_file.value[0] == '\\' || settings->hook_file.value[0] == '/') { + /* This is safe as we know that `conf_path` is a valid path */ + if (isalpha(conf_path[0]) && conf_path[1] == ':') { + p = &conf_path[3]; + goto base_found; + } else if (conf_path[0] == '\\' && conf_path[1] == '\\') { + p = &conf_path[2]; + while (*p == '\\') + p++; + while (*p != '/' && *p != '\\') + p++; + goto base_found; + } + } +#else + if (settings->hook_file.value[0] == '/') + goto absolute_hook_path; +#endif + p = strrchr(conf_path, '/'); + p = p ? &p[1] : conf_path; +#ifdef WINDOWS + if (strrchr(p, '\\')) + p = &strrchr(p, '\\')[1]; + base_found: +#endif + len = (size_t)(p - conf_path); + s = emalloc(len + strlen(settings->hook_file.value) + 1U); + memcpy(s, conf_path, len); + stpcpy(&s[len], settings->hook_file.value); + free(settings->hook_file.value); + settings->hook_file.value = s; +#ifdef WINDOWS + /* Used extended path is too long */ + if (settings->hook_file.value[0] != '\\' && (len = strlen(settings->hook_file.value)) >= 260) { + /* We have already made sure the path is absolute, so \ prefix is always extended or network path */ + settings->hook_file.value = erealloc(settings->hook_file.value, len + sizeof("\\\\?\\")); + memmove(&settings->hook_file.value[4], &settings->hook_file.value, len + 1U); + settings->hook_file.value[0] = '\\'; + settings->hook_file.value[1] = '\\'; + settings->hook_file.value[2] = '?'; + settings->hook_file.value[3] = '\\'; + } +#endif + } + free(conf_pathbuf); +absolute_hook_path: + + /* Publish loaded settings */ + if (mode == PROGRAM_MODE_ONE_SHOT && settings->until_death) + mode = PROGRAM_MODE_UNTIL_DEATH; + hook_file = settings->hook_file.value, settings->hook_file.value = NULL; + preserve_gamma = settings->preserve_gamma.value; + use_fade = settings->use_fade.value; + disable ^= settings->disabled.value; + day_settings.temperature = settings->day.temperature.value; + day_settings.brightness = settings->day.brightness.value; + day_settings.gamma[0] = settings->day.gamma.value[0]; + day_settings.gamma[1] = settings->day.gamma.value[1]; + day_settings.gamma[2] = settings->day.gamma.value[2]; + night_settings.temperature = settings->night.temperature.value; + night_settings.brightness = settings->night.brightness.value; + night_settings.gamma[0] = settings->night.gamma.value[0]; + night_settings.gamma[1] = settings->night.gamma.value[1]; + night_settings.gamma[2] = settings->night.gamma.value[2]; + if (!memcmp(&day_settings, &night_settings, sizeof(day_settings))) { + /* If the effects are the same throughout the day, do not use a transition scheme */ + scheme.type = STATIC_SCHEME; + } else if (scheme.type == SOLAR_SCHEME) { + scheme.elevation.high = settings->elevation_high.value; + scheme.elevation.low = settings->elevation_low.value; + scheme.elevation.range = scheme.elevation.high - scheme.elevation.low; + } else if (scheme.type == CLOCK_SCHEME) { + scheme.time.periods = &scheme.time.periods_array[0]; + scheme.time.periods_array[0].start = settings->dawn_start.value; + scheme.time.periods_array[0].day_level = 0.0; + scheme.time.periods_array[1].start = settings->dawn_end.value; + scheme.time.periods_array[1].day_level = 1.0; + scheme.time.periods_array[2].start = settings->dusk_start.value; + scheme.time.periods_array[2].day_level = 1.0; + scheme.time.periods_array[3].start = settings->dusk_end.value; + scheme.time.periods_array[3].day_level = 0.0; + for (i = 0; i < 4; i++) { + j = (i + 1) % 4; + scheme.time.periods_array[i].next = &scheme.time.periods_array[j]; + duration = scheme.time.periods_array[j].start - scheme.time.periods_array[i].start; + if (duration < 0) + duration += ONE_DAY; + scheme.time.periods_array[i].diff_over_duration = scheme.time.periods_array[j].day_level; + scheme.time.periods_array[i].diff_over_duration -= scheme.time.periods_array[i].day_level; + scheme.time.periods_array[i].diff_over_duration /= duration ? (double)duration : 1.0; + } + } +settings_published: + + /* Output settings */ + if (verbose) { + if (scheme.type == SOLAR_SCHEME) { + /* TRANSLATORS: Append degree symbols if possible. */ + printf(_("Solar elevations: day above %.1f, night below %.1f\n"), + scheme.elevation.high, scheme.elevation.low); + } else if (scheme.type == CLOCK_SCHEME) { + printf(_("Schedule:\n")); + current = first = scheme.time.periods; + do { + printf(_(" %.2f%% day at %02u:%02u:%02u\n"), + current->day_level * 100, (unsigned)(current->start / 60 / 60 % 24), + (unsigned)(current->start / 60 % 60), (unsigned)(current->start % 60)); + } while ((current = current->next) != first); + printf(_("(End of schedule)\n")); + } + printf(_("Temperatures: %luK at day, %luK at night\n"), + day_settings.temperature, night_settings.temperature); + printf(_("Brightness: %.2f:%.2f\n"), settings->day.brightness.value, settings->night.brightness.value); + /* TRANSLATORS: The string in parenthesis is either Daytime or Night (translated). */ + printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), _("Daytime"), + day_settings.gamma[0], day_settings.gamma[1], day_settings.gamma[2]); + printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), _("Night"), + night_settings.gamma[0], night_settings.gamma[1], night_settings.gamma[2]); + } + + return; + +invalid_twilight: + eprintf(_("Invalid dawn/dusk time configuration!")); +} |