/*- * redshift-ng - Automatically adjust display colour temperature according the Sun * * Copyright (c) 2009-2018 Jon Lund Steffensen * Copyright (c) 2014-2016, 2025 Mattias Andrée * * 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 . */ #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!")); }