sct.c (6088B)
1 /* 2 * sct - set color temperature (X11) 3 * 4 * Public domain, do as you wish. 5 */ 6 #include <math.h> 7 #include <stdarg.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 11 #include <X11/Xatom.h> 12 #include <X11/Xlib.h> 13 #include <X11/Xproto.h> 14 #include <X11/extensions/Xrandr.h> 15 16 #include "arg.h" 17 18 #define TEMPERATURE_NORM 6500 19 #define TEMPERATURE_ZERO 700 20 #define GAMMA_MULT 65535.0 21 // Approximation of the `redshift` table from 22 // https://github.com/jonls/redshift/blob/master/src/colorramp.c 23 // without limits: 24 // GAMMA = K0 + K1 * ln(T - T0) 25 #define GAMMA_K0GR -1.47751309139817 26 #define GAMMA_K1GR 0.28590164772055 27 #define GAMMA_K0BR -4.38321650114872 28 #define GAMMA_K1BR 0.6212158769447 29 #define GAMMA_K0RB 1.75390204039018 30 #define GAMMA_K1RB -0.1150805671482 31 #define GAMMA_K0GB 1.49221604915144 32 #define GAMMA_K1GB -0.07513509588921 33 #define BRIGHTHESS_DIV 65470.988 34 #define DELTA_MIN -1000000 35 36 #define CLAMP(x, a, b) ((x) < (a) ? (a) : (x) > (b) ? (b) : (x)) 37 38 typedef float f32; 39 40 static Display *dpy; 41 static int vflag; 42 char *argv0; 43 44 struct temp_status { f32 temp, brightness; }; 45 46 static void 47 die(const char *errstr, ...) 48 { 49 va_list ap; 50 51 va_start(ap, errstr); 52 vfprintf(stderr, errstr, ap); 53 va_end(ap); 54 exit(1); 55 } 56 57 static void 58 usage(void) 59 { 60 die("usage: %s [-v] [-d dT] [-c CRTC] [-s screen] [temperature] " 61 "[brightness]\n", argv0); 62 } 63 64 static struct temp_status 65 get_sct_for_screen(int screen, int icrtc) 66 { 67 Window root = RootWindow(dpy, screen); 68 XRRScreenResources *res = XRRGetScreenResourcesCurrent(dpy, root); 69 70 int n, c; 71 f32 t = TEMPERATURE_ZERO; 72 f32 gr = 0.0, gg = 0.0, gb = 0.0, gd = 0.0; 73 struct temp_status ts; 74 75 n = res->ncrtc; 76 if ((icrtc >= 0) && (icrtc < n)) 77 n = 1; 78 else 79 icrtc = 0; 80 for (c = icrtc; c < (icrtc + n); c++) { 81 XRRCrtcGamma *cg = XRRGetCrtcGamma(dpy, res->crtcs[c]); 82 gr += cg->red[cg->size - 1]; 83 gg += cg->green[cg->size - 1]; 84 gb += cg->blue[cg->size - 1]; 85 XRRFreeGamma(cg); 86 } 87 XFree(res); 88 ts.brightness = (gr > gg) ? gr : gg; 89 ts.brightness = (gb > ts.brightness) ? gb : ts.brightness; 90 if (ts.brightness > 0.0 && n > 0) { 91 gr /= ts.brightness; 92 gg /= ts.brightness; 93 gb /= ts.brightness; 94 ts.brightness /= n; 95 ts.brightness /= BRIGHTHESS_DIV; 96 if (vflag) 97 fprintf(stderr, 98 "%s: gamma: %f, %f, %f, brightness: %f\n", 99 argv0, gr, gg, gb, ts.brightness); 100 gd = gb - gr; 101 if (gd < 0.0) { 102 if (gb > 0.0) 103 t += expf((1.0 + gg + gd - (GAMMA_K0GR + GAMMA_K0BR)) / (GAMMA_K1GR + GAMMA_K1BR)); 104 else if (gg > 0.0) 105 t += expf((gg - GAMMA_K0GR) / GAMMA_K1GR); 106 } else { 107 t = expf((1.0 + gg - gd - (GAMMA_K0GB + GAMMA_K0RB)) / (GAMMA_K1GB + GAMMA_K1RB)) 108 + (TEMPERATURE_NORM - TEMPERATURE_ZERO); 109 } 110 } 111 112 ts.brightness = CLAMP(ts.brightness, 0.0, 1.0); 113 ts.temp = (int)(t + 0.5); 114 return ts; 115 } 116 117 static void 118 sct_for_screen(int screen, int icrtc, struct temp_status ts) 119 { 120 f32 t = 0.0, b = 1.0, g = 0.0, gr, gg, gb; 121 int n, c; 122 Window root = RootWindow(dpy, screen); 123 XRRScreenResources *res = XRRGetScreenResourcesCurrent(dpy, root); 124 125 if (ts.temp < TEMPERATURE_ZERO) { 126 fprintf(stderr, "%s: can't set temperature less than: %d\n", 127 argv0, TEMPERATURE_ZERO); 128 ts.temp = TEMPERATURE_ZERO; 129 } 130 t = ts.temp; 131 132 b = CLAMP(ts.brightness, 0.0, 1.0); 133 if (ts.temp < TEMPERATURE_NORM) { 134 g = logf(t - TEMPERATURE_ZERO); 135 gr = 1.0; 136 gg = CLAMP(GAMMA_K0GR + GAMMA_K1GR * g, 0.0, 1.0); 137 gb = CLAMP(GAMMA_K0BR + GAMMA_K1BR * g, 0.0, 1.0); 138 } else { 139 g = logf(t - (TEMPERATURE_NORM - TEMPERATURE_ZERO)); 140 gr = CLAMP(GAMMA_K0RB + GAMMA_K1RB * g, 0.0, 1.0); 141 gg = CLAMP(GAMMA_K0GB + GAMMA_K1GB * g, 0.0, 1.0); 142 gb = 1.0; 143 } 144 if (vflag) 145 fprintf(stderr, "%s: gamma: %f, %f, %f, brightness: %f\n", 146 argv0, gr, gg, gb, b); 147 n = res->ncrtc; 148 if ((icrtc >= 0) && (icrtc < n)) 149 n = 1; 150 else 151 icrtc = 0; 152 for (c = icrtc; c < (icrtc + n); c++) { 153 int i, size = XRRGetCrtcGammaSize(dpy, res->crtcs[c]); 154 XRRCrtcGamma *cg = XRRAllocGamma(size); 155 for (i = 0; i < size; i++) { 156 g = GAMMA_MULT * b * (f32)i / (f32)size; 157 cg->red[i] = (unsigned short int)(g * gr + 0.5); 158 cg->green[i] = (unsigned short int)(g * gg + 0.5); 159 cg->blue[i] = (unsigned short int)(g * gb + 0.5); 160 } 161 XRRSetCrtcGamma(dpy, res->crtcs[c], cg); 162 XRRFreeGamma(cg); 163 } 164 165 XFree(res); 166 } 167 168 int 169 main(int argc, char **argv) 170 { 171 int screen, screens; 172 int screen_specified = -1, screen_first = 0, screen_last = -1; 173 int crtc_specified = -1; 174 struct temp_status ts = { .temp = DELTA_MIN, .brightness = -1.0 }; 175 int delta = 0; 176 177 argv0 = argv[0]; 178 179 ARGBEGIN { 180 case 'd': 181 delta = atoi(EARGF(usage())); 182 if (delta == 0) 183 usage(); 184 break; 185 case 'v': vflag = 1; break; 186 case 's': screen_specified = atoi(EARGF(usage())); break; 187 case 'c': crtc_specified = atoi(EARGF(usage())); break; 188 default: usage(); break; 189 } ARGEND; 190 191 if (delta != 0 && argc) 192 usage(); 193 194 switch (argc) { 195 case 2: ts.brightness = atof(argv[1]); /* FALLTHROUGH */ 196 case 1: ts.temp = atoi(argv[0]); /* FALLTHROUGH */ 197 default: break; 198 } 199 200 if (!(dpy = XOpenDisplay(NULL))) 201 die("XOpenDisplay: can't open display\n"); 202 203 screens = XScreenCount(dpy); 204 screen_last = screens - 1; 205 206 if (screen_specified >= screens) { 207 XCloseDisplay(dpy); 208 die("Invalid screen: %d\n", screen_specified); 209 } 210 if (ts.brightness < 0.0) 211 ts.brightness = 1.0; 212 if (screen_specified >= 0) { 213 screen_first = screen_specified; 214 screen_last = screen_specified; 215 } 216 if (ts.temp < 0 && delta == 0) { 217 // No arguments, so print estimated temperature for each screen 218 for (screen = screen_first; screen <= screen_last; screen++) { 219 ts = get_sct_for_screen(screen, crtc_specified); 220 printf("Screen %d: temperature ~ %f\n", screen, ts.temp); 221 } 222 } else { 223 if (delta == 0 && ts.temp == 0) 224 ts.temp = TEMPERATURE_NORM; 225 for (screen = screen_first; screen <= screen_last; screen++) { 226 if (delta) { 227 ts = get_sct_for_screen(screen, crtc_specified); 228 ts.temp += delta; 229 } 230 sct_for_screen(screen, crtc_specified, ts); 231 } 232 } 233 234 XCloseDisplay(dpy); 235 236 return 0; 237 }