1 /* gamma-randr.c -- X RANDR gamma adjustment source
2    This file is part of Redshift.
3 
4    Redshift is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    Redshift is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with Redshift.  If not, see <http://www.gnu.org/licenses/>.
16 
17    Copyright (c) 2010-2017  Jon Lund Steffensen <jonlst@gmail.com>
18 */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdint.h>
23 #include <string.h>
24 #include <errno.h>
25 
26 #ifdef ENABLE_NLS
27 # include <libintl.h>
28 # define _(s) gettext(s)
29 #else
30 # define _(s) s
31 #endif
32 
33 #include <xcb/xcb.h>
34 #include <xcb/randr.h>
35 
36 #include "gamma-randr.h"
37 #include "redshift.h"
38 #include "colorramp.h"
39 
40 
41 #define RANDR_VERSION_MAJOR  1
42 #define RANDR_VERSION_MINOR  3
43 
44 
45 typedef struct {
46 	xcb_randr_crtc_t crtc;
47 	unsigned int ramp_size;
48 	uint16_t *saved_ramps;
49 } randr_crtc_state_t;
50 
51 typedef struct {
52 	xcb_connection_t *conn;
53 	xcb_screen_t *screen;
54 	int preferred_screen;
55 	int screen_num;
56 	int crtc_num_count;
57 	int* crtc_num;
58 	unsigned int crtc_count;
59 	randr_crtc_state_t *crtcs;
60 } randr_state_t;
61 
62 
63 static int
randr_init(randr_state_t ** state)64 randr_init(randr_state_t **state)
65 {
66 	/* Initialize state. */
67 	*state = malloc(sizeof(randr_state_t));
68 	if (*state == NULL) return -1;
69 
70 	randr_state_t *s = *state;
71 	s->screen_num = -1;
72 	s->crtc_num = NULL;
73 
74 	s->crtc_num_count = 0;
75 	s->crtc_count = 0;
76 	s->crtcs = NULL;
77 
78 	xcb_generic_error_t *error;
79 
80 	/* Open X server connection */
81 	s->conn = xcb_connect(NULL, &s->preferred_screen);
82 
83 	/* Query RandR version */
84 	xcb_randr_query_version_cookie_t ver_cookie =
85 		xcb_randr_query_version(s->conn, RANDR_VERSION_MAJOR,
86 					RANDR_VERSION_MINOR);
87 	xcb_randr_query_version_reply_t *ver_reply =
88 		xcb_randr_query_version_reply(s->conn, ver_cookie, &error);
89 
90 	/* TODO What does it mean when both error and ver_reply is NULL?
91 	   Apparently, we have to check both to avoid seg faults. */
92 	if (error || ver_reply == NULL) {
93 		int ec = (error != 0) ? error->error_code : -1;
94 		fprintf(stderr, _("`%s' returned error %d\n"),
95 			"RANDR Query Version", ec);
96 		xcb_disconnect(s->conn);
97 		free(s);
98 		return -1;
99 	}
100 
101 	if (ver_reply->major_version != RANDR_VERSION_MAJOR ||
102 	    ver_reply->minor_version < RANDR_VERSION_MINOR) {
103 		fprintf(stderr, _("Unsupported RANDR version (%u.%u)\n"),
104 			ver_reply->major_version, ver_reply->minor_version);
105 		free(ver_reply);
106 		xcb_disconnect(s->conn);
107 		free(s);
108 		return -1;
109 	}
110 
111 	free(ver_reply);
112 
113 	return 0;
114 }
115 
116 static int
randr_start(randr_state_t * state)117 randr_start(randr_state_t *state)
118 {
119 	xcb_generic_error_t *error;
120 
121 	int screen_num = state->screen_num;
122 	if (screen_num < 0) screen_num = state->preferred_screen;
123 
124 	/* Get screen */
125 	const xcb_setup_t *setup = xcb_get_setup(state->conn);
126 	xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
127 	state->screen = NULL;
128 
129 	for (int i = 0; iter.rem > 0; i++) {
130 		if (i == screen_num) {
131 			state->screen = iter.data;
132 			break;
133 		}
134 		xcb_screen_next(&iter);
135 	}
136 
137 	if (state->screen == NULL) {
138 		fprintf(stderr, _("Screen %i could not be found.\n"),
139 			screen_num);
140 		return -1;
141 	}
142 
143 	/* Get list of CRTCs for the screen */
144 	xcb_randr_get_screen_resources_current_cookie_t res_cookie =
145 		xcb_randr_get_screen_resources_current(state->conn,
146 						       state->screen->root);
147 	xcb_randr_get_screen_resources_current_reply_t *res_reply =
148 		xcb_randr_get_screen_resources_current_reply(state->conn,
149 							     res_cookie,
150 							     &error);
151 
152 	if (error) {
153 		fprintf(stderr, _("`%s' returned error %d\n"),
154 			"RANDR Get Screen Resources Current",
155 			error->error_code);
156 		return -1;
157 	}
158 
159 	state->crtc_count = res_reply->num_crtcs;
160 	state->crtcs = calloc(state->crtc_count, sizeof(randr_crtc_state_t));
161 	if (state->crtcs == NULL) {
162 		perror("malloc");
163 		state->crtc_count = 0;
164 		return -1;
165 	}
166 
167 	xcb_randr_crtc_t *crtcs =
168 		xcb_randr_get_screen_resources_current_crtcs(res_reply);
169 
170 	/* Save CRTC identifier in state */
171 	for (int i = 0; i < state->crtc_count; i++) {
172 		state->crtcs[i].crtc = crtcs[i];
173 	}
174 
175 	free(res_reply);
176 
177 	/* Save size and gamma ramps of all CRTCs.
178 	   Current gamma ramps are saved so we can restore them
179 	   at program exit. */
180 	for (int i = 0; i < state->crtc_count; i++) {
181 		xcb_randr_crtc_t crtc = state->crtcs[i].crtc;
182 
183 		/* Request size of gamma ramps */
184 		xcb_randr_get_crtc_gamma_size_cookie_t gamma_size_cookie =
185 			xcb_randr_get_crtc_gamma_size(state->conn, crtc);
186 		xcb_randr_get_crtc_gamma_size_reply_t *gamma_size_reply =
187 			xcb_randr_get_crtc_gamma_size_reply(state->conn,
188 							    gamma_size_cookie,
189 							    &error);
190 
191 		if (error) {
192 			fprintf(stderr, _("`%s' returned error %d\n"),
193 				"RANDR Get CRTC Gamma Size",
194 				error->error_code);
195 			return -1;
196 		}
197 
198 		unsigned int ramp_size = gamma_size_reply->size;
199 		state->crtcs[i].ramp_size = ramp_size;
200 
201 		free(gamma_size_reply);
202 
203 		if (ramp_size == 0) {
204 			fprintf(stderr, _("Gamma ramp size too small: %i\n"),
205 				ramp_size);
206 			return -1;
207 		}
208 
209 		/* Request current gamma ramps */
210 		xcb_randr_get_crtc_gamma_cookie_t gamma_get_cookie =
211 			xcb_randr_get_crtc_gamma(state->conn, crtc);
212 		xcb_randr_get_crtc_gamma_reply_t *gamma_get_reply =
213 			xcb_randr_get_crtc_gamma_reply(state->conn,
214 						       gamma_get_cookie,
215 						       &error);
216 
217 		if (error) {
218 			fprintf(stderr, _("`%s' returned error %d\n"),
219 				"RANDR Get CRTC Gamma", error->error_code);
220 			return -1;
221 		}
222 
223 		uint16_t *gamma_r =
224 			xcb_randr_get_crtc_gamma_red(gamma_get_reply);
225 		uint16_t *gamma_g =
226 			xcb_randr_get_crtc_gamma_green(gamma_get_reply);
227 		uint16_t *gamma_b =
228 			xcb_randr_get_crtc_gamma_blue(gamma_get_reply);
229 
230 		/* Allocate space for saved gamma ramps */
231 		state->crtcs[i].saved_ramps =
232 			malloc(3*ramp_size*sizeof(uint16_t));
233 		if (state->crtcs[i].saved_ramps == NULL) {
234 			perror("malloc");
235 			free(gamma_get_reply);
236 			return -1;
237 		}
238 
239 		/* Copy gamma ramps into CRTC state */
240 		memcpy(&state->crtcs[i].saved_ramps[0*ramp_size], gamma_r,
241 		       ramp_size*sizeof(uint16_t));
242 		memcpy(&state->crtcs[i].saved_ramps[1*ramp_size], gamma_g,
243 		       ramp_size*sizeof(uint16_t));
244 		memcpy(&state->crtcs[i].saved_ramps[2*ramp_size], gamma_b,
245 		       ramp_size*sizeof(uint16_t));
246 
247 		free(gamma_get_reply);
248 	}
249 
250 	return 0;
251 }
252 
253 static void
randr_restore(randr_state_t * state)254 randr_restore(randr_state_t *state)
255 {
256 	xcb_generic_error_t *error;
257 
258 	/* Restore CRTC gamma ramps */
259 	for (int i = 0; i < state->crtc_count; i++) {
260 		xcb_randr_crtc_t crtc = state->crtcs[i].crtc;
261 
262 		unsigned int ramp_size = state->crtcs[i].ramp_size;
263 		uint16_t *gamma_r = &state->crtcs[i].saved_ramps[0*ramp_size];
264 		uint16_t *gamma_g = &state->crtcs[i].saved_ramps[1*ramp_size];
265 		uint16_t *gamma_b = &state->crtcs[i].saved_ramps[2*ramp_size];
266 
267 		/* Set gamma ramps */
268 		xcb_void_cookie_t gamma_set_cookie =
269 			xcb_randr_set_crtc_gamma_checked(state->conn, crtc,
270 							 ramp_size, gamma_r,
271 							 gamma_g, gamma_b);
272 		error = xcb_request_check(state->conn, gamma_set_cookie);
273 
274 		if (error) {
275 			fprintf(stderr, _("`%s' returned error %d\n"),
276 				"RANDR Set CRTC Gamma", error->error_code);
277 			fprintf(stderr, _("Unable to restore CRTC %i\n"), i);
278 		}
279 	}
280 }
281 
282 static void
randr_free(randr_state_t * state)283 randr_free(randr_state_t *state)
284 {
285 	/* Free CRTC state */
286 	for (int i = 0; i < state->crtc_count; i++) {
287 		free(state->crtcs[i].saved_ramps);
288 	}
289 	free(state->crtcs);
290 	free(state->crtc_num);
291 
292 	/* Close connection */
293 	xcb_disconnect(state->conn);
294 
295 	free(state);
296 }
297 
298 static void
randr_print_help(FILE * f)299 randr_print_help(FILE *f)
300 {
301 	fputs(_("Adjust gamma ramps with the X RANDR extension.\n"), f);
302 	fputs("\n", f);
303 
304 	/* TRANSLATORS: RANDR help output
305 	   left column must not be translated */
306 	fputs(_("  screen=N\t\tX screen to apply adjustments to\n"
307 		"  crtc=N\tList of comma separated CRTCs to apply"
308 		" adjustments to\n"),
309 	      f);
310 	fputs("\n", f);
311 }
312 
313 static int
randr_set_option(randr_state_t * state,const char * key,const char * value)314 randr_set_option(randr_state_t *state, const char *key, const char *value)
315 {
316 	if (strcasecmp(key, "screen") == 0) {
317 		state->screen_num = atoi(value);
318 	} else if (strcasecmp(key, "crtc") == 0) {
319 		char *tail;
320 
321 		/* Check how many crtcs are configured */
322 		const char *local_value = value;
323 		while (1) {
324 			errno = 0;
325 			int parsed = strtol(local_value, &tail, 0);
326 			if (parsed == 0 && (errno != 0 ||
327 					    tail == local_value)) {
328 				fprintf(stderr, _("Unable to read screen"
329 						  " number: `%s'.\n"), value);
330 				return -1;
331 			} else {
332 				state->crtc_num_count += 1;
333 			}
334 			local_value = tail;
335 
336 			if (*local_value == ',') {
337 				local_value += 1;
338 			} else if (*local_value == '\0') {
339 				break;
340 			}
341 		}
342 
343 		/* Configure all given crtcs */
344 		state->crtc_num = calloc(state->crtc_num_count, sizeof(int));
345 		local_value = value;
346 		for (int i = 0; i < state->crtc_num_count; i++) {
347 			errno = 0;
348 			int parsed = strtol(local_value, &tail, 0);
349 			if (parsed == 0 && (errno != 0 ||
350 					    tail == local_value)) {
351 				return -1;
352 			} else {
353 				state->crtc_num[i] = parsed;
354 			}
355 			local_value = tail;
356 
357 			if (*local_value == ',') {
358 				local_value += 1;
359 			} else if (*local_value == '\0') {
360 				break;
361 			}
362 		}
363 	} else if (strcasecmp(key, "preserve") == 0) {
364 		fprintf(stderr, _("Parameter `%s` is now always on; "
365 				  " Use the `%s` command-line option"
366 				  " to disable.\n"),
367 			key, "-P");
368 	} else {
369 		fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
370 		return -1;
371 	}
372 
373 	return 0;
374 }
375 
376 static int
randr_set_temperature_for_crtc(randr_state_t * state,int crtc_num,const color_setting_t * setting,int preserve)377 randr_set_temperature_for_crtc(
378 	randr_state_t *state, int crtc_num, const color_setting_t *setting,
379 	int preserve)
380 {
381 	xcb_generic_error_t *error;
382 
383 	if (crtc_num >= state->crtc_count || crtc_num < 0) {
384 		fprintf(stderr, _("CRTC %d does not exist. "),
385 			crtc_num);
386 		if (state->crtc_count > 1) {
387 			fprintf(stderr, _("Valid CRTCs are [0-%d].\n"),
388 				state->crtc_count-1);
389 		} else {
390 			fprintf(stderr, _("Only CRTC 0 exists.\n"));
391 		}
392 
393 		return -1;
394 	}
395 
396 	xcb_randr_crtc_t crtc = state->crtcs[crtc_num].crtc;
397 	unsigned int ramp_size = state->crtcs[crtc_num].ramp_size;
398 
399 	/* Create new gamma ramps */
400 	uint16_t *gamma_ramps = malloc(3*ramp_size*sizeof(uint16_t));
401 	if (gamma_ramps == NULL) {
402 		perror("malloc");
403 		return -1;
404 	}
405 
406 	uint16_t *gamma_r = &gamma_ramps[0*ramp_size];
407 	uint16_t *gamma_g = &gamma_ramps[1*ramp_size];
408 	uint16_t *gamma_b = &gamma_ramps[2*ramp_size];
409 
410 	if (preserve) {
411 		/* Initialize gamma ramps from saved state */
412 		memcpy(gamma_ramps, state->crtcs[crtc_num].saved_ramps,
413 		       3*ramp_size*sizeof(uint16_t));
414 	} else {
415 		/* Initialize gamma ramps to pure state */
416 		for (int i = 0; i < ramp_size; i++) {
417 			uint16_t value = (double)i/ramp_size * (UINT16_MAX+1);
418 			gamma_r[i] = value;
419 			gamma_g[i] = value;
420 			gamma_b[i] = value;
421 		}
422 	}
423 
424 	colorramp_fill(gamma_r, gamma_g, gamma_b, ramp_size,
425 		       setting);
426 
427 	/* Set new gamma ramps */
428 	xcb_void_cookie_t gamma_set_cookie =
429 		xcb_randr_set_crtc_gamma_checked(state->conn, crtc,
430 						 ramp_size, gamma_r,
431 						 gamma_g, gamma_b);
432 	error = xcb_request_check(state->conn, gamma_set_cookie);
433 
434 	if (error) {
435 		fprintf(stderr, _("`%s' returned error %d\n"),
436 			"RANDR Set CRTC Gamma", error->error_code);
437 		free(gamma_ramps);
438 		return -1;
439 	}
440 
441 	free(gamma_ramps);
442 
443 	return 0;
444 }
445 
446 static int
randr_set_temperature(randr_state_t * state,const color_setting_t * setting,int preserve)447 randr_set_temperature(
448 	randr_state_t *state, const color_setting_t *setting, int preserve)
449 {
450 	int r;
451 
452 	/* If no CRTC numbers have been specified,
453 	   set temperature on all CRTCs. */
454 	if (state->crtc_num_count == 0) {
455 		for (int i = 0; i < state->crtc_count; i++) {
456 			r = randr_set_temperature_for_crtc(
457 				state, i, setting, preserve);
458 			if (r < 0) return -1;
459 		}
460 	} else {
461 		for (int i = 0; i < state->crtc_num_count; ++i) {
462 			r = randr_set_temperature_for_crtc(
463 				state, state->crtc_num[i], setting, preserve);
464 			if (r < 0) return -1;
465 		}
466 	}
467 
468 	return 0;
469 }
470 
471 
472 const gamma_method_t randr_gamma_method = {
473 	"randr", 1,
474 	(gamma_method_init_func *)randr_init,
475 	(gamma_method_start_func *)randr_start,
476 	(gamma_method_free_func *)randr_free,
477 	(gamma_method_print_help_func *)randr_print_help,
478 	(gamma_method_set_option_func *)randr_set_option,
479 	(gamma_method_restore_func *)randr_restore,
480 	(gamma_method_set_temperature_func *)randr_set_temperature
481 };
482