/* redshift.c -- Main program source This file is part of Redshift. Redshift 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 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. If not, see . Copyright (c) 2009-2015 Jon Lund Steffensen */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) # include #endif #ifdef ENABLE_NLS # include # define _(s) gettext(s) # define N_(s) (s) #else # define _(s) s # define N_(s) s # define gettext(s) s #endif #include "redshift.h" #include "config-ini.h" #include "solar.h" #include "systemtime.h" #include "hooks.h" #define MIN(x,y) ((x) < (y) ? (x) : (y)) #define MAX(x,y) ((x) > (y) ? (x) : (y)) #define CLAMP(lo,x,up) (MAX((lo), MIN((x), (up)))) /* pause() is not defined on windows platform but is not needed either. Use a noop macro instead. */ #ifdef __WIN32__ # define pause() #endif #include "gamma-dummy.h" #ifdef ENABLE_DRM # include "gamma-drm.h" #endif #ifdef ENABLE_RANDR # include "gamma-randr.h" #endif #ifdef ENABLE_VIDMODE # include "gamma-vidmode.h" #endif #ifdef ENABLE_QUARTZ # include "gamma-quartz.h" #endif #ifdef ENABLE_WINGDI # include "gamma-w32gdi.h" #endif #include "location-manual.h" #ifdef ENABLE_GEOCLUE # include "location-geoclue.h" #endif #ifdef ENABLE_GEOCLUE2 # include "location-geoclue2.h" #endif #ifdef ENABLE_CORELOCATION # include "location-corelocation.h" #endif /* Union of state data for gamma adjustment methods */ typedef union { #ifdef ENABLE_DRM drm_state_t drm; #endif #ifdef ENABLE_RANDR randr_state_t randr; #endif #ifdef ENABLE_VIDMODE vidmode_state_t vidmode; #endif #ifdef ENABLE_QUARTZ quartz_state_t quartz; #endif #ifdef ENABLE_WINGDI w32gdi_state_t w32gdi; #endif } gamma_state_t; /* Gamma adjustment method structs */ static const gamma_method_t gamma_methods[] = { #ifdef ENABLE_DRM { "drm", 0, (gamma_method_init_func *)drm_init, (gamma_method_start_func *)drm_start, (gamma_method_free_func *)drm_free, (gamma_method_print_help_func *)drm_print_help, (gamma_method_set_option_func *)drm_set_option, (gamma_method_restore_func *)drm_restore, (gamma_method_set_temperature_func *)drm_set_temperature }, #endif #ifdef ENABLE_RANDR { "randr", 1, (gamma_method_init_func *)randr_init, (gamma_method_start_func *)randr_start, (gamma_method_free_func *)randr_free, (gamma_method_print_help_func *)randr_print_help, (gamma_method_set_option_func *)randr_set_option, (gamma_method_restore_func *)randr_restore, (gamma_method_set_temperature_func *)randr_set_temperature }, #endif #ifdef ENABLE_VIDMODE { "vidmode", 1, (gamma_method_init_func *)vidmode_init, (gamma_method_start_func *)vidmode_start, (gamma_method_free_func *)vidmode_free, (gamma_method_print_help_func *)vidmode_print_help, (gamma_method_set_option_func *)vidmode_set_option, (gamma_method_restore_func *)vidmode_restore, (gamma_method_set_temperature_func *)vidmode_set_temperature }, #endif #ifdef ENABLE_QUARTZ { "quartz", 1, (gamma_method_init_func *)quartz_init, (gamma_method_start_func *)quartz_start, (gamma_method_free_func *)quartz_free, (gamma_method_print_help_func *)quartz_print_help, (gamma_method_set_option_func *)quartz_set_option, (gamma_method_restore_func *)quartz_restore, (gamma_method_set_temperature_func *)quartz_set_temperature }, #endif #ifdef ENABLE_WINGDI { "wingdi", 1, (gamma_method_init_func *)w32gdi_init, (gamma_method_start_func *)w32gdi_start, (gamma_method_free_func *)w32gdi_free, (gamma_method_print_help_func *)w32gdi_print_help, (gamma_method_set_option_func *)w32gdi_set_option, (gamma_method_restore_func *)w32gdi_restore, (gamma_method_set_temperature_func *)w32gdi_set_temperature }, #endif { "dummy", 0, (gamma_method_init_func *)gamma_dummy_init, (gamma_method_start_func *)gamma_dummy_start, (gamma_method_free_func *)gamma_dummy_free, (gamma_method_print_help_func *)gamma_dummy_print_help, (gamma_method_set_option_func *)gamma_dummy_set_option, (gamma_method_restore_func *)gamma_dummy_restore, (gamma_method_set_temperature_func *)gamma_dummy_set_temperature }, { NULL } }; /* Union of state data for location providers */ typedef union { location_manual_state_t manual; #ifdef ENABLE_GEOCLUE location_geoclue_state_t geoclue; #endif } location_state_t; /* Location provider method structs */ static const location_provider_t location_providers[] = { #ifdef ENABLE_GEOCLUE { "geoclue", (location_provider_init_func *)location_geoclue_init, (location_provider_start_func *)location_geoclue_start, (location_provider_free_func *)location_geoclue_free, (location_provider_print_help_func *) location_geoclue_print_help, (location_provider_set_option_func *) location_geoclue_set_option, (location_provider_get_location_func *) location_geoclue_get_location }, #endif #ifdef ENABLE_GEOCLUE2 { "geoclue2", (location_provider_init_func *)location_geoclue2_init, (location_provider_start_func *)location_geoclue2_start, (location_provider_free_func *)location_geoclue2_free, (location_provider_print_help_func *) location_geoclue2_print_help, (location_provider_set_option_func *) location_geoclue2_set_option, (location_provider_get_location_func *) location_geoclue2_get_location }, #endif #ifdef ENABLE_CORELOCATION { "corelocation", (location_provider_init_func *)location_corelocation_init, (location_provider_start_func *)location_corelocation_start, (location_provider_free_func *)location_corelocation_free, (location_provider_print_help_func *) location_corelocation_print_help, (location_provider_set_option_func *) location_corelocation_set_option, (location_provider_get_location_func *) location_corelocation_get_location }, #endif { "manual", (location_provider_init_func *)location_manual_init, (location_provider_start_func *)location_manual_start, (location_provider_free_func *)location_manual_free, (location_provider_print_help_func *) location_manual_print_help, (location_provider_set_option_func *) location_manual_set_option, (location_provider_get_location_func *) location_manual_get_location }, { NULL } }; /* Bounds for parameters. */ #define MIN_LAT -90.0 #define MAX_LAT 90.0 #define MIN_LON -180.0 #define MAX_LON 180.0 #define MIN_TEMP 1000 #define MAX_TEMP 25000 #define MIN_BRIGHTNESS 0.1 #define MAX_BRIGHTNESS 1.0 #define MIN_GAMMA 0.1 #define MAX_GAMMA 10.0 /* Default values for parameters. */ #define DEFAULT_DAY_TEMP 5500 #define DEFAULT_NIGHT_TEMP 3500 #define DEFAULT_BRIGHTNESS 1.0 #define DEFAULT_GAMMA 1.0 /* The color temperature when no adjustment is applied. */ #define NEUTRAL_TEMP 6500 /* Angular elevation of the sun at which the color temperature transition period starts and ends (in degress). Transition during twilight, and while the sun is lower than 3.0 degrees above the horizon. */ #define TRANSITION_LOW SOLAR_CIVIL_TWILIGHT_ELEV #define TRANSITION_HIGH 3.0 /* Duration of sleep between screen updates (milliseconds). */ #define SLEEP_DURATION 5000 #define SLEEP_DURATION_SHORT 100 /* Program modes. */ typedef enum { PROGRAM_MODE_CONTINUAL, PROGRAM_MODE_ONE_SHOT, PROGRAM_MODE_PRINT, PROGRAM_MODE_RESET, PROGRAM_MODE_MANUAL } program_mode_t; /* Transition scheme. The solar elevations at which the transition begins/ends, and the association color settings. */ typedef struct { double high; double low; color_setting_t day; color_setting_t night; } transition_scheme_t; /* Names of periods of day */ static const char *period_names[] = { /* TRANSLATORS: Name printed when period of day is unknown */ N_("None"), N_("Daytime"), N_("Night"), N_("Transition") }; #if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) static volatile sig_atomic_t exiting = 0; static volatile sig_atomic_t disable = 0; /* Signal handler for exit signals */ static void sigexit(int signo) { exiting = 1; } /* Signal handler for disable signal */ static void sigdisable(int signo) { disable = 1; } #else /* ! HAVE_SIGNAL_H || __WIN32__ */ static int exiting = 0; static int disable = 0; #endif /* ! HAVE_SIGNAL_H || __WIN32__ */ /* Determine which period we are currently in. */ static period_t get_period(const transition_scheme_t *transition, double elevation) { if (elevation < transition->low) { return PERIOD_NIGHT; } else if (elevation < transition->high) { return PERIOD_TRANSITION; } else { return PERIOD_DAYTIME; } } /* Determine how far through the transition we are. */ static double get_transition_progress(const transition_scheme_t *transition, double elevation) { if (elevation < transition->low) { return 0.0; } else if (elevation < transition->high) { return (transition->low - elevation) / (transition->low - transition->high); } else { return 1.0; } } /* Print verbose description of the given period. */ static void print_period(period_t period, double transition) { switch (period) { case PERIOD_NONE: case PERIOD_NIGHT: case PERIOD_DAYTIME: printf(_("Period: %s\n"), gettext(period_names[period])); break; case PERIOD_TRANSITION: printf(_("Period: %s (%.2f%% day)\n"), gettext(period_names[period]), transition*100); break; } } /* Print location */ static void print_location(const location_t *location) { /* TRANSLATORS: Abbreviation for `north' */ const char *north = _("N"); /* TRANSLATORS: Abbreviation for `south' */ const char *south = _("S"); /* TRANSLATORS: Abbreviation for `east' */ const char *east = _("E"); /* TRANSLATORS: Abbreviation for `west' */ const char *west = _("W"); /* TRANSLATORS: Append degree symbols after %f if possible. The string following each number is an abreviation for north, source, east or west (N, S, E, W). */ printf(_("Location: %.2f %s, %.2f %s\n"), fabs(location->lat), location->lat >= 0.f ? north : south, fabs(location->lon), location->lon >= 0.f ? east : west); } /* Interpolate color setting structs based on solar elevation */ static void interpolate_color_settings(const transition_scheme_t *transition, double elevation, color_setting_t *result) { const color_setting_t *day = &transition->day; const color_setting_t *night = &transition->night; double alpha = (transition->low - elevation) / (transition->low - transition->high); alpha = CLAMP(0.0, alpha, 1.0); result->temperature = (1.0-alpha)*night->temperature + alpha*day->temperature; result->brightness = (1.0-alpha)*night->brightness + alpha*day->brightness; for (int i = 0; i < 3; i++) { result->gamma[i] = (1.0-alpha)*night->gamma[i] + alpha*day->gamma[i]; } } static void print_help(const char *program_name) { /* TRANSLATORS: help output 1 LAT is latitude, LON is longitude, DAY is temperature at daytime, NIGHT is temperature at night no-wrap */ printf(_("Usage: %s -l LAT:LON -t DAY:NIGHT [OPTIONS...]\n"), program_name); fputs("\n", stdout); /* TRANSLATORS: help output 2 no-wrap */ fputs(_("Set color temperature of display" " according to time of day.\n"), stdout); fputs("\n", stdout); /* TRANSLATORS: help output 3 no-wrap */ fputs(_(" -h\t\tDisplay this help message\n" " -v\t\tVerbose output\n" " -V\t\tShow program version\n"), stdout); fputs("\n", stdout); /* TRANSLATORS: help output 4 `list' must not be translated no-wrap */ fputs(_(" -b DAY:NIGHT\tScreen brightness to apply (between 0.1 and 1.0)\n" " -c FILE\tLoad settings from specified configuration file\n" " -g R:G:B\tAdditional gamma correction to apply\n" " -l LAT:LON\tYour current location\n" " -l PROVIDER\tSelect provider for automatic" " location updates\n" " \t\t(Type `list' to see available providers)\n" " -m METHOD\tMethod to use to set color temperature\n" " \t\t(Type `list' to see available methods)\n" " -o\t\tOne shot mode (do not continuously adjust" " color temperature)\n" " -O TEMP\tOne shot manual mode (set color temperature)\n" " -p\t\tPrint mode (only print parameters and exit)\n" " -x\t\tReset mode (remove adjustment from screen)\n" " -r\t\tDisable temperature transitions\n" " -t DAY:NIGHT\tColor temperature to set at daytime/night\n"), stdout); fputs("\n", stdout); /* TRANSLATORS: help output 5 */ printf(_("The neutral temperature is %uK. Using this value will not\n" "change the color temperature of the display. Setting the\n" "color temperature to a value higher than this results in\n" "more blue light, and setting a lower value will result in\n" "more red light.\n"), NEUTRAL_TEMP); fputs("\n", stdout); /* TRANSLATORS: help output 6 */ printf(_("Default values:\n\n" " Daytime temperature: %uK\n" " Night temperature: %uK\n"), DEFAULT_DAY_TEMP, DEFAULT_NIGHT_TEMP); fputs("\n", stdout); /* TRANSLATORS: help output 7 */ printf(_("Please report bugs to <%s>\n"), PACKAGE_BUGREPORT); } static void print_method_list() { fputs(_("Available adjustment methods:\n"), stdout); for (int i = 0; gamma_methods[i].name != NULL; i++) { printf(" %s\n", gamma_methods[i].name); } fputs("\n", stdout); fputs(_("Specify colon-separated options with" " `-m METHOD:OPTIONS'.\n"), stdout); /* TRANSLATORS: `help' must not be translated. */ fputs(_("Try `-m METHOD:help' for help.\n"), stdout); } static void print_provider_list() { fputs(_("Available location providers:\n"), stdout); for (int i = 0; location_providers[i].name != NULL; i++) { printf(" %s\n", location_providers[i].name); } fputs("\n", stdout); fputs(_("Specify colon-separated options with" "`-l PROVIDER:OPTIONS'.\n"), stdout); /* TRANSLATORS: `help' must not be translated. */ fputs(_("Try `-l PROVIDER:help' for help.\n"), stdout); } static int provider_try_start(const location_provider_t *provider, location_state_t *state, config_ini_state_t *config, char *args) { int r; r = provider->init(state); if (r < 0) { fprintf(stderr, _("Initialization of %s failed.\n"), provider->name); return -1; } /* Set provider options from config file. */ config_ini_section_t *section = config_ini_get_section(config, provider->name); if (section != NULL) { config_ini_setting_t *setting = section->settings; while (setting != NULL) { r = provider->set_option(state, setting->name, setting->value); if (r < 0) { provider->free(state); fprintf(stderr, _("Failed to set %s" " option.\n"), provider->name); /* TRANSLATORS: `help' must not be translated. */ fprintf(stderr, _("Try `-l %s:help' for more" " information.\n"), provider->name); return -1; } setting = setting->next; } } /* Set provider options from command line. */ const char *manual_keys[] = { "lat", "lon" }; int i = 0; while (args != NULL) { char *next_arg = strchr(args, ':'); if (next_arg != NULL) *(next_arg++) = '\0'; const char *key = args; char *value = strchr(args, '='); if (value == NULL) { /* The options for the "manual" method can be set without keys on the command line for convencience and for backwards compatability. We add the proper keys here before calling set_option(). */ if (strcmp(provider->name, "manual") == 0 && i < sizeof(manual_keys)/sizeof(manual_keys[0])) { key = manual_keys[i]; value = args; } else { fprintf(stderr, _("Failed to parse option `%s'.\n"), args); return -1; } } else { *(value++) = '\0'; } r = provider->set_option(state, key, value); if (r < 0) { provider->free(state); fprintf(stderr, _("Failed to set %s option.\n"), provider->name); /* TRANSLATORS: `help' must not be translated. */ fprintf(stderr, _("Try `-l %s:help' for more" " information.\n"), provider->name); return -1; } args = next_arg; i += 1; } /* Start provider. */ r = provider->start(state); if (r < 0) { provider->free(state); fprintf(stderr, _("Failed to start provider %s.\n"), provider->name); return -1; } return 0; } static int method_try_start(const gamma_method_t *method, gamma_state_t *state, config_ini_state_t *config, char *args) { int r; r = method->init(state); if (r < 0) { fprintf(stderr, _("Initialization of %s failed.\n"), method->name); return -1; } /* Set method options from config file. */ config_ini_section_t *section = config_ini_get_section(config, method->name); if (section != NULL) { config_ini_setting_t *setting = section->settings; while (setting != NULL) { r = method->set_option(state, setting->name, setting->value); if (r < 0) { method->free(state); fprintf(stderr, _("Failed to set %s" " option.\n"), method->name); /* TRANSLATORS: `help' must not be translated. */ fprintf(stderr, _("Try `-m %s:help' for more" " information.\n"), method->name); return -1; } setting = setting->next; } } /* Set method options from command line. */ while (args != NULL) { char *next_arg = strchr(args, ':'); if (next_arg != NULL) *(next_arg++) = '\0'; const char *key = args; char *value = strchr(args, '='); if (value == NULL) { fprintf(stderr, _("Failed to parse option `%s'.\n"), args); return -1; } else { *(value++) = '\0'; } r = method->set_option(state, key, value); if (r < 0) { method->free(state); fprintf(stderr, _("Failed to set %s option.\n"), method->name); /* TRANSLATORS: `help' must not be translated. */ fprintf(stderr, _("Try -m %s:help' for more" " information.\n"), method->name); return -1; } args = next_arg; } /* Start method. */ r = method->start(state); if (r < 0) { method->free(state); fprintf(stderr, _("Failed to start adjustment method %s.\n"), method->name); return -1; } return 0; } /* A gamma string contains either one floating point value, or three values separated by colon. */ static int parse_gamma_string(const char *str, float gamma[]) { char *s = strchr(str, ':'); if (s == NULL) { /* Use value for all channels */ float g = atof(str); gamma[0] = gamma[1] = gamma[2] = g; } else { /* Parse separate value for each channel */ *(s++) = '\0'; char *g_s = s; s = strchr(s, ':'); if (s == NULL) return -1; *(s++) = '\0'; gamma[0] = atof(str); /* Red */ gamma[1] = atof(g_s); /* Blue */ gamma[2] = atof(s); /* Green */ } return 0; } /* A brightness string contains either one floating point value, or two values separated by a colon. */ static void parse_brightness_string(const char *str, float *bright_day, float *bright_night) { char *s = strchr(str, ':'); if (s == NULL) { /* Same value for day and night. */ *bright_day = *bright_night = atof(str); } else { *(s++) = '\0'; *bright_day = atof(str); *bright_night = atof(s); } } /* Check whether gamma is within allowed levels. */ static int gamma_is_valid(const float gamma[3]) { return !(gamma[0] < MIN_GAMMA || gamma[0] > MAX_GAMMA || gamma[1] < MIN_GAMMA || gamma[1] > MAX_GAMMA || gamma[2] < MIN_GAMMA || gamma[2] > MAX_GAMMA); } static const gamma_method_t * find_gamma_method(const char *name) { const gamma_method_t *method = NULL; for (int i = 0; gamma_methods[i].name != NULL; i++) { const gamma_method_t *m = &gamma_methods[i]; if (strcasecmp(name, m->name) == 0) { method = m; break; } } return method; } static const location_provider_t * find_location_provider(const char *name) { const location_provider_t *provider = NULL; for (int i = 0; location_providers[i].name != NULL; i++) { const location_provider_t *p = &location_providers[i]; if (strcasecmp(name, p->name) == 0) { provider = p; break; } } return provider; } /* Run continual mode loop This is the main loop of the continual mode which keeps track of the current time and continuously updates the screen to the appropriate color temperature. */ static int run_continual_mode(const location_t *loc, const transition_scheme_t *scheme, const gamma_method_t *method, gamma_state_t *state, int transition, int verbose) { int r; /* Make an initial transition from 6500K */ int short_trans_delta = -1; int short_trans_len = 10; /* Amount of adjustment to apply. At zero the color temperature will be exactly as calculated, and at one it will be exactly 6500K. */ double adjustment_alpha = 1.0; #if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) struct sigaction sigact; sigset_t sigset; sigemptyset(&sigset); /* Install signal handler for INT and TERM signals */ sigact.sa_handler = sigexit; sigact.sa_mask = sigset; sigact.sa_flags = 0; r = sigaction(SIGINT, &sigact, NULL); if (r < 0) { perror("sigaction"); return -1; } r = sigaction(SIGTERM, &sigact, NULL); if (r < 0) { perror("sigaction"); return -1; } /* Install signal handler for USR1 signal */ sigact.sa_handler = sigdisable; sigact.sa_mask = sigset; sigact.sa_flags = 0; r = sigaction(SIGUSR1, &sigact, NULL); if (r < 0) { perror("sigaction"); return -1; } /* Ignore CHLD signal. This causes child processes (hooks) to be reaped automatically. */ sigact.sa_handler = SIG_IGN; sigact.sa_mask = sigset; sigact.sa_flags = 0; r = sigaction(SIGCHLD, &sigact, NULL); if (r < 0) { perror("sigaction"); return -1; } #endif /* HAVE_SIGNAL_H && ! __WIN32__ */ if (verbose) { printf(_("Status: %s\n"), _("Enabled")); } /* Save previous colors so we can avoid printing status updates if the values did not change. */ period_t prev_period = PERIOD_NONE; color_setting_t prev_interp = { -1, { NAN, NAN, NAN }, NAN }; /* Continuously adjust color temperature */ int done = 0; int disabled = 0; while (1) { /* Check to see if disable signal was caught */ if (disable) { short_trans_len = 2; if (!disabled) { /* Transition to disabled state */ short_trans_delta = 1; } else { /* Transition back to enabled */ short_trans_delta = -1; } disabled = !disabled; disable = 0; if (verbose) { printf(_("Status: %s\n"), disabled ? _("Disabled") : _("Enabled")); } } /* Check to see if exit signal was caught */ if (exiting) { if (done) { /* On second signal stop the ongoing transition */ short_trans_delta = 0; adjustment_alpha = 0.0; } else { if (!disabled) { /* Make a short transition back to 6500K */ short_trans_delta = 1; short_trans_len = 2; } done = 1; } exiting = 0; } /* Read timestamp */ double now; r = systemtime_get_time(&now); if (r < 0) { fputs(_("Unable to read system time.\n"), stderr); return -1; } /* Skip over transition if transitions are disabled */ int set_adjustments = 0; if (!transition) { if (short_trans_delta) { adjustment_alpha = short_trans_delta < 0 ? 0.0 : 1.0; short_trans_delta = 0; set_adjustments = 1; } } /* Current angular elevation of the sun */ double elevation = solar_elevation(now, loc->lat, loc->lon); /* Use elevation of sun to set color temperature */ color_setting_t interp; interpolate_color_settings(scheme, elevation, &interp); /* Print period if it changed during this update, or if we are in transition. In transition we print the progress, so we always print it in that case. */ period_t period = get_period(scheme, elevation); if (verbose && (period != prev_period || period == PERIOD_TRANSITION)) { double transition = get_transition_progress(scheme, elevation); print_period(period, transition); } /* Activate hooks if period changed */ if (period != prev_period) { hooks_signal_period_change(prev_period, period); } /* Ongoing short transition */ if (short_trans_delta) { /* Calculate alpha */ adjustment_alpha += short_trans_delta * 0.1 / (float)short_trans_len; /* Stop transition when done */ if (adjustment_alpha <= 0.0 || adjustment_alpha >= 1.0) { short_trans_delta = 0; } /* Clamp alpha value */ adjustment_alpha = MAX(0.0, MIN(adjustment_alpha, 1.0)); } /* Interpolate between 6500K and calculated temperature */ interp.temperature = adjustment_alpha*6500 + (1.0-adjustment_alpha)*interp.temperature; interp.brightness = adjustment_alpha*1.0 + (1.0-adjustment_alpha)*interp.brightness; /* Quit loop when done */ if (done && !short_trans_delta) break; if (verbose) { if (interp.temperature != prev_interp.temperature) { printf(_("Color temperature: %uK\n"), interp.temperature); } if (interp.brightness != prev_interp.brightness) { printf(_("Brightness: %.2f\n"), interp.brightness); } } /* Adjust temperature */ if (!disabled || short_trans_delta || set_adjustments) { r = method->set_temperature(state, &interp); if (r < 0) { fputs(_("Temperature adjustment" " failed.\n"), stderr); return -1; } } /* Save temperature as previous */ prev_period = period; memcpy(&prev_interp, &interp, sizeof(color_setting_t)); /* Sleep for 5 seconds or 0.1 second. */ if (short_trans_delta) { systemtime_msleep(SLEEP_DURATION_SHORT); } else { systemtime_msleep(SLEEP_DURATION); } } /* Restore saved gamma ramps */ method->restore(state); return 0; } int main(int argc, char *argv[]) { int r; #ifdef ENABLE_NLS /* Init locale */ setlocale(LC_CTYPE, ""); setlocale(LC_MESSAGES, ""); /* Internationalisation */ bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif /* Initialize settings to NULL values. */ char *config_filepath = NULL; /* Settings for day, night and transition. Initialized to indicate that the values are not set yet. */ transition_scheme_t scheme = { TRANSITION_HIGH, TRANSITION_LOW }; scheme.day.temperature = -1; scheme.day.gamma[0] = NAN; scheme.day.brightness = NAN; scheme.night.temperature = -1; scheme.night.gamma[0] = NAN; scheme.night.brightness = NAN; /* Temperature for manual mode */ int temp_set = -1; const gamma_method_t *method = NULL; char *method_args = NULL; const location_provider_t *provider = NULL; char *provider_args = NULL; int transition = -1; program_mode_t mode = PROGRAM_MODE_CONTINUAL; int verbose = 0; char *s; /* Flush messages consistently even if redirected to a pipe or file. Change the flush behaviour to line-buffered, without changing the actual buffers being used. */ setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stderr, NULL, _IOLBF, 0); /* Parse command line arguments. */ int opt; while ((opt = getopt(argc, argv, "b:c:g:hl:m:oO:prt:vVx")) != -1) { switch (opt) { case 'b': parse_brightness_string(optarg, &scheme.day.brightness, &scheme.night.brightness); break; case 'c': if (config_filepath != NULL) free(config_filepath); config_filepath = strdup(optarg); break; case 'g': r = parse_gamma_string(optarg, scheme.day.gamma); if (r < 0) { fputs(_("Malformed gamma argument.\n"), stderr); fputs(_("Try `-h' for more" " information.\n"), stderr); exit(EXIT_FAILURE); } /* Set night gamma to the same value as day gamma. To set these to distinct values use the config file. */ memcpy(scheme.night.gamma, scheme.day.gamma, sizeof(scheme.night.gamma)); break; case 'h': print_help(argv[0]); exit(EXIT_SUCCESS); break; case 'l': /* Print list of providers if argument is `list' */ if (strcasecmp(optarg, "list") == 0) { print_provider_list(); exit(EXIT_SUCCESS); } char *provider_name = NULL; /* Don't save the result of strtof(); we simply want to know if optarg can be parsed as a float. */ errno = 0; char *end; strtof(optarg, &end); if (errno == 0 && *end == ':') { /* Use instead as arguments to `manual'. */ provider_name = "manual"; provider_args = optarg; } else { /* Split off provider arguments. */ s = strchr(optarg, ':'); if (s != NULL) { *(s++) = '\0'; provider_args = s; } provider_name = optarg; } /* Lookup provider from name. */ provider = find_location_provider(provider_name); if (provider == NULL) { fprintf(stderr, _("Unknown location provider" " `%s'.\n"), provider_name); exit(EXIT_FAILURE); } /* Print provider help if arg is `help'. */ if (provider_args != NULL && strcasecmp(provider_args, "help") == 0) { provider->print_help(stdout); exit(EXIT_SUCCESS); } break; case 'm': /* Print list of methods if argument is `list' */ if (strcasecmp(optarg, "list") == 0) { print_method_list(); exit(EXIT_SUCCESS); } /* Split off method arguments. */ s = strchr(optarg, ':'); if (s != NULL) { *(s++) = '\0'; method_args = s; } /* Find adjustment method by name. */ method = find_gamma_method(optarg); if (method == NULL) { /* TRANSLATORS: This refers to the method used to adjust colors e.g VidMode */ fprintf(stderr, _("Unknown adjustment method" " `%s'.\n"), optarg); exit(EXIT_FAILURE); } /* Print method help if arg is `help'. */ if (method_args != NULL && strcasecmp(method_args, "help") == 0) { method->print_help(stdout); exit(EXIT_SUCCESS); } break; case 'o': mode = PROGRAM_MODE_ONE_SHOT; break; case 'O': mode = PROGRAM_MODE_MANUAL; temp_set = atoi(optarg); break; case 'p': mode = PROGRAM_MODE_PRINT; break; case 'r': transition = 0; break; case 't': s = strchr(optarg, ':'); if (s == NULL) { fputs(_("Malformed temperature argument.\n"), stderr); fputs(_("Try `-h' for more information.\n"), stderr); exit(EXIT_FAILURE); } *(s++) = '\0'; scheme.day.temperature = atoi(optarg); scheme.night.temperature = atoi(s); break; case 'v': verbose = 1; break; case 'V': printf("%s\n", PACKAGE_STRING); exit(EXIT_SUCCESS); break; case 'x': mode = PROGRAM_MODE_RESET; break; case '?': fputs(_("Try `-h' for more information.\n"), stderr); exit(EXIT_FAILURE); break; } } /* Load settings from config file. */ config_ini_state_t config_state; r = config_ini_init(&config_state, config_filepath); if (r < 0) { fputs("Unable to load config file.\n", stderr); exit(EXIT_FAILURE); } if (config_filepath != NULL) free(config_filepath); /* Read global config settings. */ config_ini_section_t *section = config_ini_get_section(&config_state, "redshift"); if (section != NULL) { config_ini_setting_t *setting = section->settings; while (setting != NULL) { if (strcasecmp(setting->name, "temp-day") == 0) { if (scheme.day.temperature < 0) { scheme.day.temperature = atoi(setting->value); } } else if (strcasecmp(setting->name, "temp-night") == 0) { if (scheme.night.temperature < 0) { scheme.night.temperature = atoi(setting->value); } } else if (strcasecmp(setting->name, "transition") == 0) { if (transition < 0) { transition = !!atoi(setting->value); } } else if (strcasecmp(setting->name, "brightness") == 0) { if (isnan(scheme.day.brightness)) { scheme.day.brightness = atof(setting->value); } if (isnan(scheme.night.brightness)) { scheme.night.brightness = atof(setting->value); } } else if (strcasecmp(setting->name, "brightness-day") == 0) { if (isnan(scheme.day.brightness)) { scheme.day.brightness = atof(setting->value); } } else if (strcasecmp(setting->name, "brightness-night") == 0) { if (isnan(scheme.night.brightness)) { scheme.night.brightness = atof(setting->value); } } else if (strcasecmp(setting->name, "elevation-high") == 0) { scheme.high = atof(setting->value); } else if (strcasecmp(setting->name, "elevation-low") == 0) { scheme.low = atof(setting->value); } else if (strcasecmp(setting->name, "gamma") == 0) { if (isnan(scheme.day.gamma[0])) { r = parse_gamma_string(setting->value, scheme.day.gamma); if (r < 0) { fputs(_("Malformed gamma" " setting.\n"), stderr); exit(EXIT_FAILURE); } memcpy(scheme.night.gamma, scheme.day.gamma, sizeof(scheme.night.gamma)); } } else if (strcasecmp(setting->name, "gamma-day") == 0) { if (isnan(scheme.day.gamma[0])) { r = parse_gamma_string(setting->value, scheme.day.gamma); if (r < 0) { fputs(_("Malformed gamma" " setting.\n"), stderr); exit(EXIT_FAILURE); } } } else if (strcasecmp(setting->name, "gamma-night") == 0) { if (isnan(scheme.night.gamma[0])) { r = parse_gamma_string(setting->value, scheme.night.gamma); if (r < 0) { fputs(_("Malformed gamma" " setting.\n"), stderr); exit(EXIT_FAILURE); } } } else if (strcasecmp(setting->name, "adjustment-method") == 0) { if (method == NULL) { method = find_gamma_method( setting->value); if (method == NULL) { fprintf(stderr, _("Unknown" " adjustment" " method" " `%s'.\n"), setting->value); exit(EXIT_FAILURE); } } } else if (strcasecmp(setting->name, "location-provider") == 0) { if (provider == NULL) { provider = find_location_provider( setting->value); if (provider == NULL) { fprintf(stderr, _("Unknown" " location" " provider" " `%s'.\n"), setting->value); exit(EXIT_FAILURE); } } } else { fprintf(stderr, _("Unknown configuration" " setting `%s'.\n"), setting->name); } setting = setting->next; } } /* Use default values for settings that were neither defined in the config file nor on the command line. */ if (scheme.day.temperature < 0) { scheme.day.temperature = DEFAULT_DAY_TEMP; } if (scheme.night.temperature < 0) { scheme.night.temperature = DEFAULT_NIGHT_TEMP; } if (isnan(scheme.day.brightness)) { scheme.day.brightness = DEFAULT_BRIGHTNESS; } if (isnan(scheme.night.brightness)) { scheme.night.brightness = DEFAULT_BRIGHTNESS; } if (isnan(scheme.day.gamma[0])) { scheme.day.gamma[0] = DEFAULT_GAMMA; scheme.day.gamma[1] = DEFAULT_GAMMA; scheme.day.gamma[2] = DEFAULT_GAMMA; } if (isnan(scheme.night.gamma[0])) { scheme.night.gamma[0] = DEFAULT_GAMMA; scheme.night.gamma[1] = DEFAULT_GAMMA; scheme.night.gamma[2] = DEFAULT_GAMMA; } if (transition < 0) transition = 1; location_t loc = { NAN, NAN }; /* Initialize location provider. If provider is NULL try all providers until one that works is found. */ location_state_t location_state; /* Location is not needed for reset mode and manual mode. */ if (mode != PROGRAM_MODE_RESET && mode != PROGRAM_MODE_MANUAL) { if (provider != NULL) { /* Use provider specified on command line. */ r = provider_try_start(provider, &location_state, &config_state, provider_args); if (r < 0) exit(EXIT_FAILURE); } else { /* Try all providers, use the first that works. */ for (int i = 0; location_providers[i].name != NULL; i++) { const location_provider_t *p = &location_providers[i]; fprintf(stderr, _("Trying location provider `%s'...\n"), p->name); r = provider_try_start(p, &location_state, &config_state, NULL); if (r < 0) { fputs(_("Trying next provider...\n"), stderr); continue; } /* Found provider that works. */ printf(_("Using provider `%s'.\n"), p->name); provider = p; break; } /* Failure if no providers were successful at this point. */ if (provider == NULL) { fputs(_("No more location providers" " to try.\n"), stderr); exit(EXIT_FAILURE); } } /* Get current location. */ r = provider->get_location(&location_state, &loc); if (r < 0) { fputs(_("Unable to get location from provider.\n"), stderr); exit(EXIT_FAILURE); } provider->free(&location_state); if (verbose) { print_location(&loc); printf(_("Temperatures: %dK at day, %dK at night\n"), scheme.day.temperature, scheme.night.temperature); /* TRANSLATORS: Append degree symbols if possible. */ printf(_("Solar elevations: day above %.1f, night below %.1f\n"), scheme.high, scheme.low); } /* Latitude */ if (loc.lat < MIN_LAT || loc.lat > MAX_LAT) { /* TRANSLATORS: Append degree symbols if possible. */ fprintf(stderr, _("Latitude must be between %.1f and %.1f.\n"), MIN_LAT, MAX_LAT); exit(EXIT_FAILURE); } /* Longitude */ if (loc.lon < MIN_LON || loc.lon > MAX_LON) { /* TRANSLATORS: Append degree symbols if possible. */ fprintf(stderr, _("Longitude must be between" " %.1f and %.1f.\n"), MIN_LON, MAX_LON); exit(EXIT_FAILURE); } /* Color temperature */ if (scheme.day.temperature < MIN_TEMP || scheme.day.temperature > MAX_TEMP || scheme.night.temperature < MIN_TEMP || scheme.night.temperature > MAX_TEMP) { fprintf(stderr, _("Temperature must be between %uK and %uK.\n"), MIN_TEMP, MAX_TEMP); exit(EXIT_FAILURE); } /* Solar elevations */ if (scheme.high < scheme.low) { fprintf(stderr, _("High transition elevation cannot be lower than" " the low transition elevation.\n")); exit(EXIT_FAILURE); } } if (mode == PROGRAM_MODE_MANUAL) { /* Check color temperature to be set */ if (temp_set < MIN_TEMP || temp_set > MAX_TEMP) { fprintf(stderr, _("Temperature must be between %uK and %uK.\n"), MIN_TEMP, MAX_TEMP); exit(EXIT_FAILURE); } } /* Brightness */ if (scheme.day.brightness < MIN_BRIGHTNESS || scheme.day.brightness > MAX_BRIGHTNESS || scheme.night.brightness < MIN_BRIGHTNESS || scheme.night.brightness > MAX_BRIGHTNESS) { fprintf(stderr, _("Brightness values must be between %.1f and %.1f.\n"), MIN_BRIGHTNESS, MAX_BRIGHTNESS); exit(EXIT_FAILURE); } if (verbose) { printf(_("Brightness: %.2f:%.2f\n"), scheme.day.brightness, scheme.night.brightness); } /* Gamma */ if (!gamma_is_valid(scheme.day.gamma) || !gamma_is_valid(scheme.night.gamma)) { fprintf(stderr, _("Gamma value must be between %.1f and %.1f.\n"), MIN_GAMMA, MAX_GAMMA); exit(EXIT_FAILURE); } if (verbose) { /* TRANSLATORS: The string in parenthesis is either Daytime or Night (translated). */ printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), _("Daytime"), scheme.day.gamma[0], scheme.day.gamma[1], scheme.day.gamma[2]); printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), _("Night"), scheme.night.gamma[0], scheme.night.gamma[1], scheme.night.gamma[2]); } /* Initialize gamma adjustment method. If method is NULL try all methods until one that works is found. */ gamma_state_t state; /* Gamma adjustment not needed for print mode */ if (mode != PROGRAM_MODE_PRINT) { if (method != NULL) { /* Use method specified on command line. */ r = method_try_start(method, &state, &config_state, method_args); if (r < 0) exit(EXIT_FAILURE); } else { /* Try all methods, use the first that works. */ for (int i = 0; gamma_methods[i].name != NULL; i++) { const gamma_method_t *m = &gamma_methods[i]; if (!m->autostart) continue; r = method_try_start(m, &state, &config_state, NULL); if (r < 0) { fputs(_("Trying next method...\n"), stderr); continue; } /* Found method that works. */ printf(_("Using method `%s'.\n"), m->name); method = m; break; } /* Failure if no methods were successful at this point. */ if (method == NULL) { fputs(_("No more methods to try.\n"), stderr); exit(EXIT_FAILURE); } } } config_ini_free(&config_state); switch (mode) { case PROGRAM_MODE_ONE_SHOT: case PROGRAM_MODE_PRINT: { /* Current angular elevation of the sun */ double now; r = systemtime_get_time(&now); if (r < 0) { fputs(_("Unable to read system time.\n"), stderr); method->free(&state); exit(EXIT_FAILURE); } double elevation = solar_elevation(now, loc.lat, loc.lon); if (verbose) { /* TRANSLATORS: Append degree symbol if possible. */ printf(_("Solar elevation: %f\n"), elevation); } /* Use elevation of sun to set color temperature */ color_setting_t interp; interpolate_color_settings(&scheme, elevation, &interp); if (verbose || mode == PROGRAM_MODE_PRINT) { period_t period = get_period(&scheme, elevation); double transition = get_transition_progress(&scheme, elevation); print_period(period, transition); printf(_("Color temperature: %uK\n"), interp.temperature); printf(_("Brightness: %.2f\n"), interp.brightness); } if (mode == PROGRAM_MODE_PRINT) { exit(EXIT_SUCCESS); } /* Adjust temperature */ r = method->set_temperature(&state, &interp); if (r < 0) { fputs(_("Temperature adjustment failed.\n"), stderr); method->free(&state); exit(EXIT_FAILURE); } /* In Quartz (OSX) the gamma adjustments will automatically revert when the process exits. Therefore, we have to loop until CTRL-C is received. */ if (strcmp(method->name, "quartz") == 0) { fputs(_("Press ctrl-c to stop...\n"), stderr); pause(); } } break; case PROGRAM_MODE_MANUAL: { if (verbose) printf(_("Color temperature: %uK\n"), temp_set); /* Adjust temperature */ color_setting_t manual; memcpy(&manual, &scheme.day, sizeof(color_setting_t)); manual.temperature = temp_set; r = method->set_temperature(&state, &manual); if (r < 0) { fputs(_("Temperature adjustment failed.\n"), stderr); method->free(&state); exit(EXIT_FAILURE); } /* In Quartz (OSX) the gamma adjustments will automatically revert when the process exits. Therefore, we have to loop until CTRL-C is received. */ if (strcmp(method->name, "quartz") == 0) { fputs(_("Press ctrl-c to stop...\n"), stderr); pause(); } } break; case PROGRAM_MODE_RESET: { /* Reset screen */ color_setting_t reset = { NEUTRAL_TEMP, { 1.0, 1.0, 1.0 }, 1.0 }; r = method->set_temperature(&state, &reset); if (r < 0) { fputs(_("Temperature adjustment failed.\n"), stderr); method->free(&state); exit(EXIT_FAILURE); } /* In Quartz (OSX) the gamma adjustments will automatically revert when the process exits. Therefore, we have to loop until CTRL-C is received. */ if (strcmp(method->name, "quartz") == 0) { fputs(_("Press ctrl-c to stop...\n"), stderr); pause(); } } break; case PROGRAM_MODE_CONTINUAL: { r = run_continual_mode(&loc, &scheme, method, &state, transition, verbose); if (r < 0) exit(EXIT_FAILURE); } break; } /* Clean up gamma adjustment state */ method->free(&state); return EXIT_SUCCESS; }