aboutsummaryrefslogtreecommitdiffstats
path: root/libgamma_x_randr_get_crtc_information.c
blob: 75885e6398b8a0e66949d6eded1031483618df68 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/* See LICENSE file for copyright and license details. */
#define IN_LIBGAMMA_X_RANDR
#include "common.h"


/**
 * Get the gamma ramp size of a CRTC
 * 
 * @param   this  Instance of a data structure to fill with the information about the CRTC
 * @param   crtc  The state of the CRTC whose information should be read
 * @return        Non-zero on error
 */
static int
get_gamma_ramp_size(struct libgamma_crtc_information *restrict out, struct libgamma_crtc_state *restrict crtc)
{
	xcb_connection_t *restrict connection = crtc->partition->site->data;
	xcb_randr_crtc_t *restrict crtc_id = crtc->data;
	xcb_randr_get_crtc_gamma_size_cookie_t cookie;
	xcb_randr_get_crtc_gamma_size_reply_t *restrict reply;
	xcb_generic_error_t *error;

	/* Query gamma ramp size */
	out->gamma_size_error = 0;
	cookie = xcb_randr_get_crtc_gamma_size(connection, *crtc_id);
	reply = xcb_randr_get_crtc_gamma_size_reply(connection, cookie, &error);
	if (error) {
		out->gamma_size_error = libgamma_x_randr_internal_translate_error(error->error_code,
		                                                                  LIBGAMMA_GAMMA_RAMPS_SIZE_QUERY_FAILED, 1);
		return out->gamma_size_error;
	}
	/* Sanity check gamma ramp size */
	if (reply->size < 2)
		out->gamma_size_error = LIBGAMMA_SINGLETON_GAMMA_RAMP;
	/* Store gamma ramp size */
	out->red_gamma_size = out->green_gamma_size = out->blue_gamma_size = reply->size;
	/* Release resources and return successfulnes */
	free(reply);
	return out->gamma_size_error;
}


/**
 * Read information from the CRTC's output
 * 
 * @param   out     Instance of a data structure to fill with the information about the CRTC
 * @param   output  The CRTC's output information
 * @return          Non-zero if at least on error occured
 */
static int
read_output_data(struct libgamma_crtc_information *restrict out, xcb_randr_get_output_info_reply_t *restrict output)
{
	switch (output->connection) {
	case XCB_RANDR_CONNECTION_CONNECTED:
		/* We have a monitor connected, report that and store information that is provided to us */
		out->active = 1;
		out->width_mm = output->mm_width;
		out->height_mm = output->mm_height;
		switch (output->subpixel_order) {
#define X(CONST, NAME, RENDER_SUFFIX)\
		case XCB_RENDER_SUB_PIXEL_##RENDER_SUFFIX:\
			out->subpixel_order = CONST;\
			break;
		LIST_SUBPIXEL_ORDERS(X)
#undef X
		default:
			out->subpixel_order_error = LIBGAMMA_SUBPIXEL_ORDER_NOT_RECOGNISED;
			break;
		}
		return 0;

	case XCB_RANDR_CONNECTION_UNKNOWN:
		/* If we do know whether a monitor is connected report that and assume it is not */
		out->active_error = LIBGAMMA_STATE_UNKNOWN;
		/* fall through */
	default:
		/* If no monitor is connected, report that on fails that require it */
		out->width_mm_error       = LIBGAMMA_NOT_CONNECTED;
		out->height_mm_error      = LIBGAMMA_NOT_CONNECTED;
		out->subpixel_order_error = LIBGAMMA_NOT_CONNECTED;
		/* And store that we are not connected */
		out->active = 0;
		/* This fuction only failed if we could not figure out whether a monitor is connected */
		return output->connection != XCB_RANDR_CONNECTION_UNKNOWN ? 0 : -1;
	}
}


/**
 * Determine the connector type from the connector name
 * 
 * @param  this  The CRTC information to use and extend
 * @param        Non-zero on error
 */
static int
get_connector_type(struct libgamma_crtc_information *restrict this)
{
	/* Since we require the name of the output of get the type of the connected,
	 * copy any reported error on the output's name to the connector's type,
	 * and report failure if there was an error */
	if ((this->connector_type_error = this->connector_name_error))
		return -1;

#define SELECT(name, type)\
	do {\
		if (strstr(this->connector_name, name "-") == this->connector_name) {\
			this->connector_type = LIBGAMMA_CONNECTOR_TYPE_##type;\
			return 0;\
		}\
	} while (0)

	/* Check begin on the name of the output to find out what type the connector is of */
	SELECT("None",        Unknown);
	SELECT("VGA",         VGA);
	SELECT("DVI-I",       DVII);
	SELECT("DVI-D",       DVID);
	SELECT("DVI-A",       DVIA);
	SELECT("DVI",         DVI);
	SELECT("Composite",   Composite);
	SELECT("S-Video",     SVIDEO);
	SELECT("Component",   Component);
	SELECT("LFP",         LFP);
	SELECT("Proprietary", Unknown);
	SELECT("HDMI",        HDMI);
	SELECT("DisplayPort", DisplayPort);

#undef SELECT

	/* If there was no matching output name pattern report that and exit with an error */
	this->connector_name_error = LIBGAMMA_CONNECTOR_TYPE_NOT_RECOGNISED;
	return -1;
}


/**
 * Get the output name of a CRTC
 * 
 * @param   this    Instance of a data structure to fill with the information about the CRTC
 * @param   output  The CRTC's output's information
 * @return          Non-zero on error
 */
static int
get_output_name(struct libgamma_crtc_information *restrict out, xcb_randr_get_output_info_reply_t *restrict output)
{
	char *restrict store;
	uint8_t *restrict name;
	uint16_t length;
	size_t i;

	/* Get the name of the output and the length of that name */
	name = xcb_randr_get_output_info_name(output);
	length = output->name_len; /* There is no NUL-termination */
	if (!name)
		return out->connector_name_error = LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED;

	/* Allocate a memory area for a NUL-terminated copy of the name */
	if (length > SIZE_MAX / sizeof(char) - 1) {
		out->connector_name = NULL;
		out->connector_name_error = errno = ENOMEM;
		return -1;
	}
	store = out->connector_name = malloc(((size_t)length + 1) * sizeof(char));
	if (!store) {
		out->connector_name_error = errno;
		return -1;
	}

	/* char is guaranteed to be (u)int_least8_t, but it is only guaranteed to be (u)int8_t
	 * on POSIX, so to be truly portable we will not assume that char is (u)int8_t */
	for (i = 0; i < (size_t)length; i++)
		store[i] = (char)name[i];
	store[length] = '\0';

	return 0;
}


/**
 * Get the Extended Display Information Data of the monitor connected to the connector of a CRTC
 * 
 * @param   out     Instance of a data structure to fill with the information about the CRTC
 * @param   crtc    The state of the CRTC whose information should be read
 * @param   output  The CRTC's output
 * @return          Non-zero on error
 */
static int
get_edid(struct libgamma_crtc_information *restrict out, struct libgamma_crtc_state *restrict crtc, xcb_randr_output_t output)
{
	xcb_connection_t *restrict connection = crtc->partition->site->data;
	xcb_randr_list_output_properties_cookie_t prop_cookie;
	xcb_randr_list_output_properties_reply_t *restrict prop_reply;
	xcb_atom_t *atoms;
	xcb_atom_t *atoms_end;
	xcb_generic_error_t *error;
	xcb_get_atom_name_cookie_t atom_name_cookie;
	xcb_get_atom_name_reply_t *restrict atom_name_reply;
	char *restrict atom_name;
	int atom_name_len;
	xcb_randr_get_output_property_cookie_t atom_cookie;
	xcb_randr_get_output_property_reply_t *restrict atom_reply;
	unsigned char *restrict atom_data;
	int length;

	/* Acquire a list of all properties of the output */
	prop_cookie = xcb_randr_list_output_properties(connection, output);
	prop_reply = xcb_randr_list_output_properties_reply(connection, prop_cookie, &error);
	if (error) {
		return out->edid_error = libgamma_x_randr_internal_translate_error(error->error_code,
		                                                                   LIBGAMMA_LIST_PROPERTIES_FAILED, 1);
	}
  
	/* Extract the properties form the data structure that holds them, */
	atoms = xcb_randr_list_output_properties_atoms(prop_reply);
	/* and get the last one so that we can iterate over them nicely */
	atoms_end = atoms + xcb_randr_list_output_properties_atoms_length(prop_reply);

	if (!atoms) {
		free(prop_reply);
		return out->edid_error = LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED;
	}

	/* For each property */
	for (; atoms != atoms_end; atoms++) {
		/* Acquire the atom name */
		atom_name_cookie = xcb_get_atom_name(connection, *atoms);
		atom_name_reply = xcb_get_atom_name_reply(connection, atom_name_cookie, &error);
		if (error)
			continue;

		/* Extract the atom name from the data structure that holds it */
		atom_name = xcb_get_atom_name_name(atom_name_reply);
		/* As well as the length of the name; it is not NUL-termianted */
		atom_name_len = xcb_get_atom_name_name_length(atom_name_reply);

		if (/* Check for errors */
		    !atom_name || /* atom_name_len < 1 || */
		    /* Check that the length is the expected length for the EDID property */
		    atom_name_len != 4 ||
		    /* Check that the property is the EDID property */
		    atom_name[0] != 'E' || atom_name[1] != 'D' || atom_name[2] != 'I' || atom_name[3] != 'D') {
			free(atom_name_reply);
			continue;
		}

		/* Acquire the property's value, we know that it is either 128 or 256 byte long */
		atom_cookie = xcb_randr_get_output_property(connection, output, *atoms, XCB_GET_PROPERTY_TYPE_ANY, 0, 256, 0, 0);
		atom_reply = xcb_randr_get_output_property_reply(connection, atom_cookie, &error);
		/* (*) EDID version 1.0 through 1.4 define it as 128 bytes long,
		 * but version 2.0 define it as 256 bytes long. However,
		 * version 2.0 is rare(?) and has been deprecated and replaced
		 * by version 1.3 (I guess that is with a new version epoch,
		 * but I do not know.) */
		if (error) {
			free(atom_name_reply);
			free(prop_reply);
			return out->edid_error = LIBGAMMA_PROPERTY_VALUE_QUERY_FAILED;
		}

		/* Extract the property's value */
		atom_data = xcb_randr_get_output_property_data(atom_reply);
		/* and its actual length */
		length = xcb_randr_get_output_property_data_length(atom_reply);
		if (!atom_data || length < 1) {
			free(atom_reply);
			free(atom_name_reply);
			free(prop_reply);
			return out->edid_error = LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED;
		}

		/* Store the EDID */
		out->edid_length = (size_t)length;
		out->edid = malloc((size_t)length * sizeof(unsigned char));
		if (!out->edid)
			out->edid_error = errno;
		else
			memcpy(out->edid, atom_data, (size_t)length * sizeof(unsigned char));

		/* Release resouces */
		free(atom_reply);
		free(atom_name_reply);
		free(prop_reply);

		return out->edid_error;
	}

	return out->edid_error = LIBGAMMA_EDID_NOT_FOUND;
}


/**
 * Read information about a CRTC
 * 
 * @param   this    Instance of a data structure to fill with the information about the CRTC
 * @param   crtc    The state of the CRTC whose information should be read
 * @param   fields  OR:ed identifiers for the information about the CRTC that should be read
 * @return          Zero on success, -1 on error; on error refer to the error reports in `this`
 */
int
libgamma_x_randr_get_crtc_information(struct libgamma_crtc_information *restrict this,
                                      struct libgamma_crtc_state *restrict crtc, unsigned long long fields)
{
#define _E(FIELD) ((fields & FIELD) ? LIBGAMMA_CRTC_INFO_NOT_SUPPORTED : 0)

	int e = 0;
	xcb_randr_get_output_info_reply_t *restrict output_info = NULL;
	xcb_randr_output_t output;
	int free_edid, free_name;
	xcb_connection_t *restrict connection;
	struct libgamma_x_randr_partition_data *restrict screen_data;
	size_t output_index;
	xcb_randr_get_output_info_cookie_t cookie;
	xcb_generic_error_t *error;

	/* Wipe all error indicators */
	memset(this, 0, sizeof(*this));

	/* We need to free the EDID after us if it is not explicitly requested */
	free_edid = !(fields & LIBGAMMA_CRTC_INFO_EDID);

	/* We need to free the output's name after us if it is not explicitly requested */
	free_name = !(fields & LIBGAMMA_CRTC_INFO_CONNECTOR_NAME);

	/* Jump if the output information is not required */
	if (!(fields & (LIBGAMMA_CRTC_INFO_MACRO_ACTIVE | LIBGAMMA_CRTC_INFO_MACRO_CONNECTOR)))
		goto cont;

	/* Get connector and connector information */
	connection = crtc->partition->site->data;
	screen_data = crtc->partition->data;
	output_index = screen_data->crtc_to_output[crtc->crtc];
	/* `SIZE_MAX` is used for CRTC:s that misses mapping to its output (should not happen),
	 * because `SIZE_MAX - 1` is the highest theoretical possible value */
	if (output_index == SIZE_MAX) {
		e |= this->edid_error = this->gamma_error = this->width_mm_edid_error
		   = this->height_mm_edid_error = this->connector_type_error
		   = this->connector_name_error = this->subpixel_order_error
		   = this->width_mm_error = this->height_mm_error
		   = this->active_error = LIBGAMMA_CONNECTOR_UNKNOWN;
		goto cont;
	}
	/* Get the output */
	output = screen_data->outputs[output_index];
	/* Query output information */
	cookie = xcb_randr_get_output_info(connection, output, screen_data->config_timestamp);
	output_info = xcb_randr_get_output_info_reply(connection, cookie, &error);
	if (error) {
		e |= this->edid_error = this->gamma_error = this->width_mm_edid_error
		   = this->height_mm_edid_error = this->connector_type_error
		   = this->connector_name_error = this->subpixel_order_error
		   = this->width_mm_error = this->height_mm_error
		   = this->active_error = LIBGAMMA_OUTPUT_INFORMATION_QUERY_FAILED;
		goto cont;
	}

	/* Get connector name */
	e |= get_output_name(this, output_info);
	/* Get connector type */
	if (fields & LIBGAMMA_CRTC_INFO_CONNECTOR_TYPE)
		e |= get_connector_type(this);
	/* Get additional output data, excluding EDID */
	e |= read_output_data(this, output_info);
	if (fields & LIBGAMMA_CRTC_INFO_MACRO_VIEWPORT)
		e |= this->width_mm_error | this->height_mm_error;
	e |= (fields & LIBGAMMA_CRTC_INFO_SUBPIXEL_ORDER) ? this->subpixel_order_error : 0;

	/* If we do not want any EDID information, jump */
	if (!(fields & LIBGAMMA_CRTC_INFO_MACRO_EDID))
		goto cont;
	/* If there is not monitor that report error in EDID related fields */
	if (!this->active) {
		e |= this->edid_error = this->gamma_error = this->width_mm_edid_error
		   = this->height_mm_edid_error = LIBGAMMA_NOT_CONNECTED;
		goto cont;
	}
	/* Get EDID */
	e |= get_edid(this, crtc, output);
	if (!this->edid) {
		this->gamma_error = this->width_mm_edid_error = this->height_mm_edid_error = this->edid_error;
		goto cont;
	}
	/* Parse EDID */
	if ((fields & (LIBGAMMA_CRTC_INFO_MACRO_EDID ^ LIBGAMMA_CRTC_INFO_EDID)))
		e |= libgamma_internal_parse_edid(this, fields);

cont:
	/* Get gamma ramp size */
	e |= (fields & LIBGAMMA_CRTC_INFO_GAMMA_SIZE) ? get_gamma_ramp_size(this, crtc) : 0;
	/* Store gamma ramp depth. */
	this->gamma_depth = 16;
	/* X RandR does not support quering gamma ramp support. */
	e |= this->gamma_support_error = _E(LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT);

	/* Free the EDID after us */
	if (free_edid) {
		free(this->edid);
		this->edid = NULL;
	}
	/* Free the output name after us */
	if (free_name) {
		free(this->connector_name);
		this->connector_name = NULL;
	}

	free(output_info);
	return e ? -1 : 0;

#undef _E
}