/* 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) 2010 Jon Lund Steffensen */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #ifdef HAVE_SYS_SIGNAL_H # include #endif #ifdef ENABLE_NLS # include # define _(s) gettext(s) #else # define _(s) s #endif #include "solar.h" #include "systemtime.h" #define MIN(x,y) ((x) < (y) ? (x) : (y)) #define MAX(x,y) ((x) > (y) ? (x) : (y)) #if !(defined(ENABLE_RANDR) || \ defined(ENABLE_VIDMODE) || \ defined(ENABLE_WINGDI)) # error "At least one of RANDR, VidMode or WinGDI must be enabled." #endif #ifdef ENABLE_RANDR # include "randr.h" #endif #ifdef ENABLE_VIDMODE # include "vidmode.h" #endif #ifdef ENABLE_WINGDI # include "w32gdi.h" #endif /* Union of state data for gamma adjustment methods */ typedef union { #ifdef ENABLE_RANDR randr_state_t randr; #endif #ifdef ENABLE_VIDMODE vidmode_state_t vidmode; #endif #ifdef ENABLE_WINGDI w32gdi_state_t w32gdi; #endif } gamma_state_t; /* 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 10000 #define MIN_GAMMA 0.1 #define MAX_GAMMA 10.0 /* Default values for parameters. */ #define DEFAULT_DAY_TEMP 5500 #define DEFAULT_NIGHT_TEMP 3700 #define DEFAULT_GAMMA 1.0 /* 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 #ifdef HAVE_SYS_SIGNAL_H 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_SYS_SIGNAL_H */ static int exiting = 0; static int disable = 0; #endif /* ! HAVE_SYS_SIGNAL_H */ /* Restore saved gamma ramps with the appropriate adjustment method. */ static void gamma_state_restore(gamma_state_t *state, int use_randr) { switch (use_randr) { #ifdef ENABLE_VIDMODE case 0: vidmode_restore(&state->vidmode); break; #endif #ifdef ENABLE_RANDR case 1: randr_restore(&state->randr); break; #endif #ifdef ENABLE_WINGDI case 2: w32gdi_restore(&state->w32gdi); break; #endif } } /* Free the state associated with the appropriate adjustment method. */ static void gamma_state_free(gamma_state_t *state, int use_randr) { switch (use_randr) { #ifdef ENABLE_VIDMODE case 0: vidmode_free(&state->vidmode); break; #endif #ifdef ENABLE_RANDR case 1: randr_free(&state->randr); break; #endif #ifdef ENABLE_WINGDI case 2: w32gdi_free(&state->w32gdi); break; #endif } } /* Set temperature with the appropriate adjustment method. */ static int gamma_state_set_temperature(gamma_state_t *state, int use_randr, int temp, float gamma[3]) { switch (use_randr) { #ifdef ENABLE_VIDMODE case 0: return vidmode_set_temperature(&state->vidmode, temp, gamma); #endif #ifdef ENABLE_RANDR case 1: return randr_set_temperature(&state->randr, temp, gamma); #endif #ifdef ENABLE_WINGDI case 2: return w32gdi_set_temperature(&state->w32gdi, temp, gamma); #endif } return -1; } /* Calculate color temperature for the specified solar elevation. */ static int calculate_temp(double elevation, int temp_day, int temp_night, int verbose) { int temp = 0; if (elevation < TRANSITION_LOW) { temp = temp_night; if (verbose) printf(_("Period: Night\n")); } else if (elevation < TRANSITION_HIGH) { /* Transition period: interpolate */ float a = (TRANSITION_LOW - elevation) / (TRANSITION_LOW - TRANSITION_HIGH); temp = (1.0-a)*temp_night + a*temp_day; if (verbose) { printf(_("Period: Transition (%.2f%% day)\n"), a*100); } } else { temp = temp_day; if (verbose) printf(_("Period: Daytime\n")); } return temp; } 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"), stdout); fputs("\n", stdout); /* TRANSLATORS: help output 4 no-wrap */ fputs(_(" -g R:G:B\tAdditional gamma correction to apply\n" " -l LAT:LON\tYour current location\n" " -m METHOD\tMethod to use to set color temperature" " (RANDR, VidMode or WinGDI)\n" " -o\t\tOne shot mode (do not continously adjust" " color temperature)\n" " -r\t\tDisable temperature transitions\n" " -s SCREEN\tX screen to apply adjustments to\n" " -c CRTC\tCRTC to apply adjustments to (RANDR only)\n" " -t DAY:NIGHT\tColor temperature to set at daytime/night\n"), stdout); fputs("\n", stdout); /* TRANSLATORS: help output 5 */ 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 6 */ printf("Please report bugs to <%s>\n", PACKAGE_BUGREPORT); } 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 to defaults */ float lat = NAN; float lon = NAN; int temp_day = DEFAULT_DAY_TEMP; int temp_night = DEFAULT_NIGHT_TEMP; float gamma[3] = { DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA }; int use_randr = -1; int screen_num = -1; int crtc_num = -1; int transition = 1; int one_shot = 0; int verbose = 0; char *s; /* Parse arguments. */ int opt; while ((opt = getopt(argc, argv, "c:g:hl:m:ors:t:v")) != -1) { switch (opt) { case 'c': crtc_num = atoi(optarg); break; case 'g': s = strchr(optarg, ':'); if (s == NULL) { /* Use value for all channels */ float g = atof(optarg); gamma[0] = gamma[1] = gamma[2] = g; } else { /* Parse separate value for each channel */ *(s++) = '\0'; gamma[0] = atof(optarg); /* Red */ char *g_s = s; s = strchr(s, ':'); if (s == NULL) { fputs(_("Malformed gamma argument.\n"), stderr); fputs(_("Try `-h' for more" " information.\n"), stderr); exit(EXIT_FAILURE); } *(s++) = '\0'; gamma[1] = atof(g_s); /* Blue */ gamma[2] = atof(s); /* Green */ } break; case 'h': print_help(argv[0]); exit(EXIT_SUCCESS); break; case 'l': s = strchr(optarg, ':'); if (s == NULL) { fputs(_("Malformed location argument.\n"), stderr); fputs(_("Try `-h' for more information.\n"), stderr); exit(EXIT_FAILURE); } *(s++) = '\0'; lat = atof(optarg); lon = atof(s); break; case 'm': if (strcmp(optarg, "randr") == 0 || strcmp(optarg, "RANDR") == 0) { #ifdef ENABLE_RANDR use_randr = 1; #else fputs(_("RANDR method was not" " enabled at compile time.\n"), stderr); exit(EXIT_FAILURE); #endif } else if (strcmp(optarg, "vidmode") == 0 || strcmp(optarg, "VidMode") == 0) { #ifdef ENABLE_VIDMODE use_randr = 0; #else fputs(_("VidMode method was not" " enabled at compile time.\n"), stderr); exit(EXIT_FAILURE); #endif } else if (strcmp(optarg, "wingdi") == 0 || strcmp(optarg, "WinGDI") == 0) { #ifdef ENABLE_WINGDI use_randr = 2; #else fputs(_("WinGDI method was not" " enabled at compile time.\n"), stderr); exit(EXIT_FAILURE); #endif } else { /* TRANSLATORS: This refers to the method used to adjust colors e.g VidMode */ fprintf(stderr, _("Unknown method `%s'.\n"), optarg); exit(EXIT_FAILURE); } break; case 'o': one_shot = 1; break; case 'r': transition = 0; break; case 's': screen_num = atoi(optarg); 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'; temp_day = atoi(optarg); temp_night = atoi(s); break; case 'v': verbose = 1; break; case '?': fputs(_("Try `-h' for more information.\n"), stderr); exit(EXIT_FAILURE); break; } } /* Latitude and longitude must be set */ if (isnan(lat) || isnan(lon)) { fputs(_("Latitude and longitude must be set.\n"), stderr); fputs(_("Try `-h' for more information.\n"), stderr); exit(EXIT_FAILURE); } if (verbose) { /* TRANSLATORS: Append degree symbols if possible. */ printf(_("Location: %f, %f\n"), lat, lon); } /* Latitude */ if (lat < MIN_LAT || 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 (lon < MIN_LON || 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 at daytime */ if (temp_day < MIN_TEMP || temp_day >= MAX_TEMP) { fprintf(stderr, _("Temperature must be between %uK and %uK.\n"), MIN_TEMP, MAX_TEMP); exit(EXIT_FAILURE); } /* Color temperature at night */ if (temp_night < MIN_TEMP || temp_night >= MAX_TEMP) { fprintf(stderr, _("Temperature must be between %uK and %uK.\n"), MIN_TEMP, MAX_TEMP); exit(EXIT_FAILURE); } /* Gamma */ if (gamma[0] < MIN_GAMMA || gamma[0] > MAX_GAMMA || gamma[1] < MIN_GAMMA || gamma[1] > MAX_GAMMA || gamma[2] < MIN_GAMMA || gamma[2] > MAX_GAMMA) { fprintf(stderr, _("Gamma value must be between %.1f and %.1f.\n"), MIN_GAMMA, MAX_GAMMA); exit(EXIT_FAILURE); } if (verbose) { printf(_("Gamma: %.3f, %.3f, %.3f\n"), gamma[0], gamma[1], gamma[2]); } /* CRTC can only be selected for RANDR */ if (crtc_num > -1 && !use_randr) { fprintf(stderr, _("CRTC can only be selected" " with the RANDR method.\n")); exit(EXIT_FAILURE); } /* Initialize gamma adjustment method. If use_randr is negative try all methods until one that works is found. */ gamma_state_t state; #ifdef ENABLE_RANDR if (use_randr < 0 || use_randr == 1) { /* Initialize RANDR state */ r = randr_init(&state.randr, screen_num, crtc_num); if (r < 0) { fputs(_("Initialization of RANDR failed.\n"), stderr); if (use_randr < 0) { fputs(_("Trying other method...\n"), stderr); } else { exit(EXIT_FAILURE); } } else { use_randr = 1; } } #endif #ifdef ENABLE_VIDMODE if (use_randr < 0 || use_randr == 0) { /* Initialize VidMode state */ r = vidmode_init(&state.vidmode, screen_num); if (r < 0) { fputs(_("Initialization of VidMode failed.\n"), stderr); if (use_randr < 0) { fputs(_("Trying other method...\n"), stderr); } else { exit(EXIT_FAILURE); } } else { use_randr = 0; } } #endif #ifdef ENABLE_WINGDI if (use_randr < 0 || use_randr == 2) { /* Initialize WinGDI state */ r = w32gdi_init(&state.w32gdi); if (r < 0) { fputs(_("Initialization of WinGDI failed.\n"), stderr); if (use_randr < 0) { fputs(_("Trying other method...\n"), stderr); } else { exit(EXIT_FAILURE); } } else { use_randr = 2; } } #endif /* Failure if no methods were successful at this point. */ if (use_randr < 0) { fputs(_("No more methods to try.\n"), stderr); exit(EXIT_FAILURE); } if (one_shot) { /* Current angular elevation of the sun */ double now; r = systemtime_get_time(&now); if (r < 0) { fputs(_("Unable to read system time.\n"), stderr); gamma_state_free(&state, use_randr); exit(EXIT_FAILURE); } double elevation = solar_elevation(now, lat, lon); if (verbose) { /* TRANSLATORS: Append degree symbol if possible. */ printf(_("Solar elevation: %f\n"), elevation); } /* Use elevation of sun to set color temperature */ int temp = calculate_temp(elevation, temp_day, temp_night, verbose); if (verbose) printf(_("Color temperature: %uK\n"), temp); /* Adjust temperature */ r = gamma_state_set_temperature(&state, use_randr, temp, gamma); if (r < 0) { fputs(_("Temperature adjustment failed.\n"), stderr); gamma_state_free(&state, use_randr); exit(EXIT_FAILURE); } } else { /* Transition state */ double short_trans_end = 0; int short_trans = 0; int short_trans_done = 0; /* Make an initial transition from 6500K */ int short_trans_create = 1; int short_trans_begin = 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. */ float adjustment_alpha = 0.0; #ifdef HAVE_SYS_SIGNAL_H 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; sigaction(SIGINT, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); /* Install signal handler for USR1 singal */ sigact.sa_handler = sigdisable; sigact.sa_mask = sigset; sigact.sa_flags = 0; sigaction(SIGUSR1, &sigact, NULL); #endif /* HAVE_SYS_SIGNAL_H */ /* Continously adjust color temperature */ int done = 0; int disabled = 0; while (1) { /* Check to see if disable signal was caught */ if (disable) { short_trans_create = 1; short_trans_len = 2; if (!disabled) { /* Transition to disabled state */ short_trans_begin = 0; adjustment_alpha = 1.0; disabled = 1; } else { /* Transition back to enabled */ short_trans_begin = 1; adjustment_alpha = 0.0; disabled = 0; } disable = 0; } /* Check to see if exit signal was caught */ if (exiting) { if (done) { /* On second signal stop the ongoing transition */ short_trans = 0; } else { if (!disabled) { /* Make a short transition back to 6500K */ short_trans_create = 1; short_trans_begin = 0; short_trans_len = 2; adjustment_alpha = 1.0; } done = 1; } exiting = 0; } /* Read timestamp */ double now; r = systemtime_get_time(&now); if (r < 0) { fputs(_("Unable to read system time.\n"), stderr); gamma_state_free(&state, use_randr); exit(EXIT_FAILURE); } /* Set up a new transition */ if (short_trans_create) { if (transition) { short_trans_end = now; short_trans_end += short_trans_len; short_trans = 1; short_trans_create = 0; } else { short_trans_done = 1; } } /* Current angular elevation of the sun */ double elevation = solar_elevation(now, lat, lon); /* Use elevation of sun to set color temperature */ int temp = calculate_temp(elevation, temp_day, temp_night, verbose); /* Ongoing short transition */ if (short_trans) { double start = now; double end = short_trans_end; if (start > end) { /* Transisiton done */ short_trans = 0; short_trans_done = 1; } /* Calculate alpha */ adjustment_alpha = (end - start) / (float)short_trans_len; if (!short_trans_begin) { adjustment_alpha = 1.0 - adjustment_alpha; } /* Clamp alpha value */ adjustment_alpha = MAX(0.0, MIN(adjustment_alpha, 1.0)); } /* Handle end of transition */ if (short_trans_done) { if (disabled) { /* Restore saved gamma ramps */ gamma_state_restore(&state, use_randr); } short_trans_done = 0; } /* Interpolate between 6500K and calculated temperature */ temp = adjustment_alpha*6500 + (1.0-adjustment_alpha)*temp; /* Quit loop when done */ if (done && !short_trans) break; if (verbose) { printf(_("Color temperature: %uK\n"), temp); } /* Adjust temperature */ if (!disabled || short_trans) { r = gamma_state_set_temperature(&state, use_randr, temp, gamma); if (r < 0) { fputs(_("Temperature adjustment" " failed.\n"), stderr); gamma_state_free(&state, use_randr); exit(EXIT_FAILURE); } } /* Sleep for a while */ #ifndef _WIN32 if (short_trans) usleep(100000); else usleep(5000000); #else /* ! _WIN32 */ if (short_trans) Sleep(100); else Sleep(5000); #endif /* ! _WIN32 */ } /* Restore saved gamma ramps */ gamma_state_restore(&state, use_randr); } /* Clean up gamma adjustment state */ gamma_state_free(&state, use_randr); return EXIT_SUCCESS; }