aboutsummaryrefslogblamecommitdiffstats
path: root/src/lib/gamma-x-randr.c
blob: fce65d142a21dd65ca2af5c8bdda5da1e779aa1d (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                         

                                                                      

      
                          
 
                           
                 


                   

                   


                   





                      
                                                 



                              
                                                 


                              


   
                                    
   




























                                                            




                                    
                                               
   


                                                                                       
   

                                                   
 










                                                                                                


 
   
                                               
   



                                                                                       
   

                                                                    
 

                                                            

 

   
                                                   
   
                                                                          
   

                                                                                   
 




























                                                                                                  


 

                                                    

                                                    
      

 
   
                                     
   
                                                
                                                                      
                                                                        




                                                                         
                                                             
   

                                                                                           
 

































                                                                                                             
            

                                                                                                                       
      





















                                                                       



   
                                             
   
                               
   

                                                                   
 
                                                       



   
                                                                        
   
                                
                                                                       
                                                           
   

                                                                   
 


                                  


 
   
                          
   


                                                                                 
   

                                        
 







                               

 

   
                                          
   
                                                        
                                                                                 
                                                                     
                                                                            
                                                                
   


                                                                                             
 


















































                                                                                         
         



































































                                                                                                                  



   
                                                  
   
                                    
   

                                                                             
 




                                                                      



   
                                                                             
   
                                     
                                                                       
                                                           
   

                                                                             
 


                                  




   
                                     
   
                                                   
                                                                                      
                                                                
                                                                            
                                                                
   


                                                                                             
 



                                                                                  



   
                                             
   
                               
   

                                                                   
 
                    



   
                                                                          
   
                                
                                                                       
                                                           
   

                                                                   
 


                                  




   
                                    
   


                                                                                          
   

                                                                                                    
 





















                                                                                                                      



   
                                          
   


                                                                                            
   

                                                                                                               
 

























                                                                                                           
                







                                                                                                        
         
 
               



   
                                                       
   

                                                       
   

                                                              
 




























                                                                                             
               



                                                                                             



   
                                
   


                                                                                            
   

                                                                                                              
 
























                                                                                              



   
                                                                                                
   



                                                                                            
   

                                                                                                                    
 




























                                                                                                                
         






























































                                                                                                                                 
         

                                                         



   
                                
   



                                                                                               
   


                                                                                           
 








































































































                                                                                                              
         



   
                                                                     
   

                                                                  
                                                                        
                                                            
   

                                                                                                                       
 






                                                                            

            


                                                                                        
      



















                                                                                              



   
                                                             
   

                                           
                                                                        
                                                            
   

                                                                                                             
 


                                                                            
            


                                                                                    
      








                                                                                                                

 
 
               
                           
      
/* See LICENSE file for copyright and license details. */
#ifndef HAVE_LIBGAMMA_METHOD_X_RANDR
# error Compiling gamma-x-randr.c without HAVE_LIBGAMMA_METHOD_X_RANDR
#endif

#include "gamma-x-randr.h"

#include "libgamma-error.h"
#include "edid.h"

#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#ifdef DEBUG
# include <stdio.h>
#endif

#include <xcb/xcb.h>
#include <xcb/randr.h>


/**
 * The major version of RandR the library expects
 */
#define RANDR_VERSION_MAJOR  1

/**
 * The minor version of RandR the library expects
 */
#define RANDR_VERSION_MINOR  3



/**
 * Data structure for partition data
 */
typedef struct libgamma_x_randr_partition_data {
	/**
	 * Mapping from CRTC indices to CRTC identifiers
	 */
	xcb_randr_crtc_t *crtcs;

	/**
	 * Mapping from output indices to output identifiers
	 */
	xcb_randr_output_t *outputs;

	/**
	 * The number of outputs available
	 */
	size_t outputs_count;

	/**
	 * Mapping from CRTC indices to output indices.
	 * CRTC's without an output (should be impossible)
	 * have the value `SIZE_MAX` which is impossible
	 * for an existing mapping
	 */
	size_t *crtc_to_output;

	/**
	 * Screen configuration timestamp
	 */
	xcb_timestamp_t config_timestamp;

} libgamma_x_randr_partition_data_t;



/**
 * Translate an xcb error into a libgamma error
 * 
 * @param   error_code     The xcb error
 * @param   default_error  The libgamma error to use if the xcb error is not recognised
 * @return                 The libgamma error
 */
static int
translate_error_(int error_code, int default_error)
{
	switch (error_code) {
	case XCB_CONN_ERROR:                    return errno = ECONNABORTED, LIBGAMMA_ERRNO_SET;
	case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:  return errno = ENOPROTOOPT, LIBGAMMA_ERRNO_SET;
	case XCB_CONN_CLOSED_MEM_INSUFFICIENT:  return errno = ENOMEM, LIBGAMMA_ERRNO_SET;
	case XCB_CONN_CLOSED_REQ_LEN_EXCEED:    return errno = EMSGSIZE, LIBGAMMA_ERRNO_SET;
	case XCB_CONN_CLOSED_PARSE_ERR:         return LIBGAMMA_NO_SUCH_SITE;
	case XCB_CONN_CLOSED_INVALID_SCREEN:    return LIBGAMMA_NO_SUCH_PARTITION;
	case XCB_CONN_CLOSED_FDPASSING_FAILED:  return errno = EIO, LIBGAMMA_ERRNO_SET;
	default:
		return default_error;
	}
}


/**
 * Translate an xcb error into a libgamma error
 * 
 * @param   error_code     The xcb error
 * @param   default_error  The libgamma error to use if the xcb error is not recognised
 * @param   return_errno   Whether an `errno` value may be returned
 * @return                 The libgamma error
 */
static int
translate_error(int error_code, int default_error, int return_errno)
{
	int r = translate_error_(error_code, default_error);
	return (return_errno && r > 0) ? errno : r;
}


/**
 * Return the capabilities of the adjustment method
 * 
 * @param  this  The data structure to fill with the method's capabilities
 */
void
libgamma_x_randr_method_capabilities(libgamma_method_capabilities_t *restrict this)
{
	char *display = getenv("DISPLAY");
	/* Support for all information except active status and gamma ramp support.
	   Active status can be queried but it is not guaranteed produces an up to date result. */
	this->crtc_information = LIBGAMMA_CRTC_INFO_MACRO_EDID
	                       | LIBGAMMA_CRTC_INFO_MACRO_VIEWPORT
	                       | LIBGAMMA_CRTC_INFO_MACRO_RAMP
	                       | LIBGAMMA_CRTC_INFO_SUBPIXEL_ORDER
	                       | LIBGAMMA_CRTC_INFO_MACRO_CONNECTOR;
	/* X RandR supports multiple sites, partitions and CRTC:s */
	this->default_site_known = display && *display;
	this->multiple_sites = 1;
	this->multiple_partitions = 1;
	this->multiple_crtcs = 1;
	/* Partitions are screens and not graphics cards in X */
	this->partitions_are_graphics_cards = 0;
	/* X does not have system restore capabilities */
	this->site_restore = 0;
	this->partition_restore = 0;
	this->crtc_restore = 0;
	/* Gamma ramp sizes are identical but not fixed */
	this->identical_gamma_sizes = 1;
	this->fixed_gamma_size = 0;
	/* Gamma ramp depths are fixed */
	this->fixed_gamma_depth = 1;
	/* X RandR is a real non-faked adjustment method */
	this->real = 1;
	this->fake = 0;
	/* Gamma ramp adjustments are persistent */
	this->auto_restore = 0;
}


/* xcb violates the rule to never return struct:s */
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Waggregate-return"
#endif


/**
 * Initialise an allocated site state
 * 
 * @param   this    The site state to initialise
 * @param   site    The site identifier, unless it is `NULL` it must a
 *                  `free`:able. Once the state is destroyed the library
 *                  will attempt to free it. There you should not free
 *                  it yourself, and it must not be a string constant
 *                  or allocate on the stack. Note however that it will
 *                  not be free:d if this function fails.
 * @return          Zero on success, otherwise (negative) the value of an
 *                  error identifier provided by this library
 */
int
libgamma_x_randr_site_initialise(libgamma_site_state_t *restrict this, char *restrict site)
{
	xcb_generic_error_t *error = NULL;
	xcb_connection_t *restrict connection;
	xcb_randr_query_version_cookie_t cookie;
	xcb_randr_query_version_reply_t *restrict reply;
	const xcb_setup_t *restrict setup;
	xcb_screen_iterator_t iter;

	/* Connect to the display server */
	this->data = connection = xcb_connect(site, NULL);
	if (!connection || xcb_connection_has_error(connection))
		return LIBGAMMA_OPEN_SITE_FAILED;

	/* Query the version of the X RandR extension protocol */
	cookie = xcb_randr_query_version(connection, RANDR_VERSION_MAJOR, RANDR_VERSION_MINOR);
	reply = xcb_randr_query_version_reply(connection, cookie, &error);

	/* Check for version query failure */
	if (error || !reply) {
		/* Release resources */
		free(reply);
		/* If `xcb_connect` failed, both `error` and `reply` will be `NULL`.
		   XXX: Can both be `NULL` for any other reason? */
		if (!error && !reply)
			return LIBGAMMA_OPEN_SITE_FAILED;
		xcb_disconnect(connection);
		/* Translate and report error. */
		if (error != NULL)
			return translate_error(error->error_code, LIBGAMMA_PROTOCOL_VERSION_QUERY_FAILED, 0);
		return LIBGAMMA_PROTOCOL_VERSION_QUERY_FAILED;
	}

	/* Check protocol compatibility,
	   we require 1.3 but 2.x may not be backwards compatible */
	if (reply->major_version != RANDR_VERSION_MAJOR || reply->minor_version < RANDR_VERSION_MINOR) {
#ifdef DEBUG
		/* Print used protocol */
		fprintf(stderr, "libgamma: RandR protocol version: %u.%u", reply->major_version, reply->minor_version);
#endif
		/* Release resources */
		free(reply);
		xcb_disconnect(connection);
		/* Report error */
		return LIBGAMMA_PROTOCOL_VERSION_NOT_SUPPORTED;
	}

	/* We do not longer need to know the version of the protocol */
	free(reply);

	/* Get available screens */
	setup = xcb_get_setup(connection);
	if (!setup) {
		xcb_disconnect(connection);
		return LIBGAMMA_LIST_PARTITIONS_FAILED;
	}
	iter = xcb_setup_roots_iterator(setup);
	/* Get the number of available screens */
	this->partitions_available = (size_t)iter.rem;

	/* Sanity check the number of available screens. */
	return iter.rem >= ? 0 : LIBGAMMA_NEGATIVE_PARTITION_COUNT;
}


/**
 * Release all resources held by a site state
 * 
 * @param  this  The site state
 */
void
libgamma_x_randr_site_destroy(libgamma_site_state_t *restrict this)
{
	xcb_disconnect((xcb_connection_t *)this->data);
}


/**
 * Restore the gamma ramps all CRTC:s with a site to the system settings
 * 
 * @param   this  The site state
 * @return        Zero on success, otherwise (negative) the value of an
 *                error identifier provided by this library
 */
int
libgamma_x_randr_site_restore(libgamma_site_state_t *restrict this)
{
	(void) this;
	errno = ENOTSUP;
	return LIBGAMMA_ERRNO_SET;
}


/**
 * Duplicate a memory area
 * 
 * @param   ptr    The memory area
 * @param   bytes  The size, in bytes, of the memory area
 * @return         A duplication of the memory, `NULL` if zero-length or on error
 */
static inline void *
memdup(void *restrict ptr, size_t bytes)
{
	char *restrict rc;
	if (!bytes)
		return NULL;
	rc = malloc(bytes);
	if (!rc)
		return NULL;
	memcpy(rc, ptr, bytes);
	return rc;
}


/**
 * Initialise an allocated partition state
 * 
 * @param   this       The partition state to initialise
 * @param   site       The site state for the site that the partition belongs to.
 * @param   partition  The the index of the partition within the site
 * @return             Zero on success, otherwise (negative) the value of an
 *                     error identifier provided by this library
 */
int
libgamma_x_randr_partition_initialise(libgamma_partition_state_t *restrict this,
                                      libgamma_site_state_t *restrict site, size_t partition)
{
	int fail_rc = LIBGAMMA_ERRNO_SET;
	xcb_connection_t *restrict connection = site->data;
	xcb_screen_t *restrict screen = NULL;
	xcb_generic_error_t *error = NULL;
	const xcb_setup_t *restrict setup;
	xcb_screen_iterator_t iter;
	xcb_randr_get_screen_resources_current_cookie_t cookie;
	xcb_randr_get_screen_resources_current_reply_t *restrict reply;
	xcb_randr_crtc_t *restrict crtcs;
	xcb_randr_output_t *restrict outputs;
	libgamma_x_randr_partition_data_t *restrict data;
	xcb_randr_get_output_info_cookie_t out_cookie;
	xcb_randr_get_output_info_reply_t *out_reply;
	size_t i;
	uint16_t j;

	/* Get screen list */
	setup = xcb_get_setup(connection);
	if (!setup)
		return LIBGAMMA_LIST_PARTITIONS_FAILED;
	iter = xcb_setup_roots_iterator(setup);

	/* Get the screen */
	for (i = 0; iter.rem > 0; i++, xcb_screen_next(&iter))
		if (i == partition) {
			screen = iter.data;
			break;
		}
	/* Report failure if we did not find the screen */
	if (!iter.rem)
		return LIBGAMMA_NO_SUCH_PARTITION;

	/* Check that the screen is not `NULL`.
	 * (Do not think this can happen, but why not.) */
	if (!screen)
		return LIBGAMMA_NULL_PARTITION;

	/* Get the current resources of the screen */
	cookie = xcb_randr_get_screen_resources_current(connection, screen->root);
	reply = xcb_randr_get_screen_resources_current_reply(connection, cookie, &error);
	if (error)
		return translate_error(error->error_code, LIBGAMMA_LIST_CRTCS_FAILED, 0);

	/* Get the number of available CRTC:s */
	this->crtcs_available = reply->num_crtcs;
	/* Get the CRTC and output lists */
	crtcs = xcb_randr_get_screen_resources_current_crtcs(reply);
	outputs = xcb_randr_get_screen_resources_current_outputs(reply);
	if (!crtcs || !outputs) {
		free(reply);
		return LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED;
	}

	/* Allocate adjustment method dependent data memory area.
	   We use `calloc` because we want `data`'s pointers to be `NULL` if not allocated at `fail`. */
	data = calloc(1, sizeof(libgamma_x_randr_partition_data_t));
	if (!data)
		goto fail;

	/* Copy the CRTC:s, just so we do not have to keep the reply in memory */
	data->crtcs = memdup(crtcs, (size_t)(reply->num_crtcs) * sizeof(xcb_randr_crtc_t));
	if (!data->crtcs && reply->num_crtcs > 0)
		goto fail;

	/* Copy the outputs as well */
	data->outputs = memdup(outputs, (size_t)reply->num_outputs * sizeof(xcb_randr_output_t));
	if (!data->outputs && reply->num_outputs > 0)
		goto fail;

	/* Get the number of available outputs */
	data->outputs_count = (size_t)reply->num_outputs;

	/* Create mapping table from CRTC indices to output indicies. (injection) */
	data->crtc_to_output = malloc((size_t)reply->num_crtcs * sizeof(size_t));
	if (!data->crtc)
		goto fail;
	/* All CRTC:s should be mapped, but incase they are not, all unmapped CRTC:s should have
	   an invalid target, namely `SIZE_MAX`, which is 1 more than the theoretical limit */
	for (i = 0; i < (size_t)reply->num_crtcs; i++)
		data->crtc_to_output[i] = SIZE_MAX;
	/* Fill the table */
	for (i = 0; i < (size_t)reply->num_outputs; i++) {
		/* Query output (target) information */
		out_cookie = xcb_randr_get_output_info(connection, outputs[i], reply->config_timestamp);
		out_reply = xcb_randr_get_output_info_reply(connection, out_cookie, &error);
		if (error) {
			fail_rc = translate_error(error->error_code, LIBGAMMA_OUTPUT_INFORMATION_QUERY_FAILED, 0);
			goto fail;
		}

		/* Find CRTC (source) */
		for (j = 0; j < reply->num_crtcs; j++) {
			if (crtcs[j] == out_reply->crtc) {
				data->crtc_to_output[j] = i;
				break;
			}
		}

		/* Release output information */
		free(out_reply);
	}

	/* Store the configuration timestamp */
	data->config_timestamp = reply->config_timestamp;
	/* Store the adjustment method dependent data */
	this->data = data;
	/* Release resources and return successfully */
	free(reply);
	return 0;

fail:
	/* Release resources and return with an error */
	if (data) {
		free(data->crtcs);
		free(data->outputs);
		free(data->crtc_to_output);
		free(data);
	}
	free(reply);
	return fail_rc;
}


/**
 * Release all resources held by a partition state
 * 
 * @param  this  The partition state
 */
void
libgamma_x_randr_partition_destroy(libgamma_partition_state_t *restrict this)
{
	libgamma_x_randr_partition_data_t *restrict data = this->data;
	free(data->crtcs);
	free(data->outputs);
	free(data->crtc_to_output);
	free(data);
}


/**
 * Restore the gamma ramps all CRTC:s with a partition to the system settings
 * 
 * @param   this  The partition state
 * @return        Zero on success, otherwise (negative) the value of an
 *                error identifier provided by this library
 */
int
libgamma_x_randr_partition_restore(libgamma_partition_state_t *restrict this)
{
	(void) this;
	errno = ENOTSUP;
	return LIBGAMMA_ERRNO_SET;
}



/**
 * Initialise an allocated CRTC state
 * 
 * @param   this       The CRTC state to initialise
 * @param   partition  The partition state for the partition that the CRTC belongs to.
 * @param   crtc       The the index of the CRTC within the site
 * @return             Zero on success, otherwise (negative) the value of an
 *                     error identifier provided by this library
 */
int
libgamma_x_randr_crtc_initialise(libgamma_crtc_state_t *restrict this,
                                 libgamma_partition_state_t *restrict partition, size_t crtc)
{
	libgamma_x_randr_partition_data_t *restrict screen_data = partition->data;
	xcb_randr_crtc_t *restrict crtc_ids = screen_data->crtcs;
	this->data = crtc_ids + crtc;
	return crtc < partition->crtcs_available ? 0 : LIBGAMMA_NO_SUCH_CRTC;
}


/**
 * Release all resources held by a CRTC state
 * 
 * @param  this  The CRTC state
 */
void
libgamma_x_randr_crtc_destroy(libgamma_crtc_state_t *restrict this)
{
	(void) this;
}


/**
 * Restore the gamma ramps for a CRTC to the system settings for that CRTC
 * 
 * @param   this  The CRTC state
 * @return        Zero on success, otherwise (negative) the value of an
 *                error identifier provided by this library
 */
int
libgamma_x_randr_crtc_restore(libgamma_crtc_state_t *restrict this)
{
	(void) this;
	errno = ENOTSUP;
	return LIBGAMMA_ERRNO_SET;
}



/**
 * 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(libgamma_crtc_information_t *restrict out, libgamma_crtc_state_t *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 = 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(libgamma_crtc_information_t *restrict out, xcb_randr_get_output_info_reply_t *restrict output)
{
#define __select(value)\
	case XCB_RENDER_SUB_PIXEL_##value:  out->subpixel_order = LIBGAMMA_SUBPIXEL_ORDER_##value;  break

	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) {
		__select (UNKNOWN);
		__select (HORIZONTAL_RGB);
		__select (HORIZONTAL_BGR);
		__select (VERTICAL_RGB);
		__select (VERTICAL_BGR);
		__select (NONE);
		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;
	}

#undef __select
}


/**
 * 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(libgamma_crtc_information_t *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(libgamma_crtc_information_t *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 */
	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(libgamma_crtc_information_t *restrict out, libgamma_crtc_state_t *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 = 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(libgamma_crtc_information_t *restrict this,
                                      libgamma_crtc_state_t *restrict crtc, int32_t 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;
	libgamma_x_randr_partition_data_t *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(libgamma_crtc_information_t));

	/* 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_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
}


/**
 * Get the current gamma ramps for a CRTC, 16-bit gamma-depth version
 * 
 * @param   this   The CRTC state
 * @param   ramps  The gamma ramps to fill with the current values
 * @return         Zero on success, otherwise (negative) the value of an
 *                 error identifier provided by this library
 */
int
libgamma_x_randr_crtc_get_gamma_ramps16(libgamma_crtc_state_t *restrict this, libgamma_gamma_ramps16_t *restrict ramps)
{
	xcb_connection_t *restrict connection = this->partition->site->data;
	xcb_randr_get_crtc_gamma_cookie_t cookie;
	xcb_randr_get_crtc_gamma_reply_t *restrict reply;
	xcb_generic_error_t *error;
	uint16_t *restrict red;
	uint16_t *restrict green;
	uint16_t *restrict blue;
  
#ifdef DEBUG
	/* Gamma ramp sizes are identical but not fixed */
	if (ramps->red_size != ramps->green_size || ramps->red_size != ramps->blue_size)
		return LIBGAMMA_MIXED_GAMMA_RAMP_SIZE;
#endif

	/* Read current gamma ramps */
	cookie = xcb_randr_get_crtc_gamma(connection, *(xcb_randr_crtc_t *)this->data);
	reply = xcb_randr_get_crtc_gamma_reply(connection, cookie, &error);

	/* Check for errors */
	if (error)
		return translate_error(error->error_code, LIBGAMMA_GAMMA_RAMP_READ_FAILED, 0);

	/* Get current gamma ramps from response */
	red   = xcb_randr_get_crtc_gamma_red(reply);
	green = xcb_randr_get_crtc_gamma_green(reply);
	blue  = xcb_randr_get_crtc_gamma_blue(reply);
	/* Copy over the gamma ramps to our memory */
	memcpy(ramps->red,   red,   ramps->red_size   * sizeof(uint16_t));
	memcpy(ramps->green, green, ramps->green_size * sizeof(uint16_t));
	memcpy(ramps->blue,  blue,  ramps->blue_size  * sizeof(uint16_t));

	free(reply);
	return 0;
}


/**
 * Set the gamma ramps for a CRTC, 16-bit gamma-depth version
 * 
 * @param   this   The CRTC state
 * @param   ramps  The gamma ramps to apply
 * @return         Zero on success, otherwise (negative) the value of an
 *                 error identifier provided by this library
 */
int
libgamma_x_randr_crtc_set_gamma_ramps16(libgamma_crtc_state_t *restrict this, libgamma_gamma_ramps16_t ramps)
{
	xcb_connection_t *restrict connection = this->partition->site->data;
	xcb_void_cookie_t cookie;
	xcb_generic_error_t *restrict error;
#ifdef DEBUG
	/* Gamma ramp sizes are identical but not fixed */
	if (ramps.red_size != ramps.green_size || ramps.red_size != ramps.blue_size)
		return LIBGAMMA_MIXED_GAMMA_RAMP_SIZE;
#endif

	/* Apply gamma ramps */
	cookie = xcb_randr_set_crtc_gamma_checked(connection, *(xcb_randr_crtc_t*)this->data,
	                                          (uint16_t)ramps.red_size, ramps.red, ramps.green, ramps.blue);
	/* Check for errors */
	error = xcb_request_check(connection, cookie);
	if (error)
		return translate_error(error->error_code, LIBGAMMA_GAMMA_RAMP_WRITE_FAILED, 0);
	return 0;
}


#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif