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