sct

set color temperature
git clone anongit@rnpnr.xyz:sct.git
Log | Files | Refs | Feed | README | LICENSE

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 }