aboutsummaryrefslogblamecommitdiffstats
path: root/test.c
blob: a546467a16f2dffb2e6c2e7392bd75774c54070c (plain) (tree)
1
2
3
4
5
6
7
8
9
10



                                                         

                   



                   
                    

                   



                    
 

                                                 






























                                                                        




















                                                                                                        








































































































































                                                                                                    
                                   
                               
                                                       













                                                                  






                                                                               
                     

























































                                                                                                     
                                                 





                                                                             




                                                                                                                 




                                                                        














                                                                                                                           















                                                                  

                                                                                                   















                                                                     
                                       







                                      
                                       





























                                                                                    
                                       





















                                                                                              
                                       
























                                                                                                      
                                               





                                                                                                    
                                                                                         













                                                                                     



                                    


                                                    

                                  

                           

                                








                                                  
                                                      












                                                               
                                                      












                                                               
                                          
















                                                      
                                                           
 


                                                 



                                                    




                                                                                                 


                                                                                                 
                                                                                    
                                                                                      





                                                     
                                                                             
                                                                             



                                                                                              

                                                  
                                                                  
                                                                                                      

                                                                          






















                                                                                             



                                                                                            
                                          



                                                                                                                   














                                                                                                  

                                                                                        






                                                                                                  


                                                                                                         
                                                   







                                                                                                         
 


                     

             







                                  
                                                                           
   
           
                       
 





















                                                                                                    
           




                          
                                          


















                                                                                                             
                                                         
                                                                                        


                                                                                                             
                                                         
                                                                                                                        
                                                                                                                         
                                                  

                                 

                                                                                         


























































                                                                                                                     
                                                                                                    

                                                                                                                           
         


 


                                     
           




                          
                                           


















                                                                                                             
                                                          
                                                                                          


                                                                                                             
                                                          
                                                                                                                          



                                                                                                                         
                                                          
                                                                                          








































                                                                                                                        
                                                                                                     








                                                                                                                           
           

                 
                                      
                                   



                  

                                                   
                                 






































                                                                                                                        
                                                     
                                                      

                                                  









                                                                                               
                                                     
                                                    

                                                  









                                                                                               
                                                     
                                                          

                                                  


















































































































































































































































                                                                                                                        
                          



                                                                                             
      











                                                                                                       
                          




                                                                                                                    
      




















































                                                                                                                              
                                    
                                                             

                                 










                                                                                                  
                                    
                                                           

                                 










                                                                                                 
                                    
                                                                 

                                 

                                           
                                                                                                       



                                          
                                                                                                       


                                    
                                                                                                       






                                                                                                                  
                                    
                                                             

                                 










                                                                                                  
                                    
                                                           

                                 










                                                                                                 
                                    
                                                                 

                                 

                                           
                                                                                                       



                                          
                                                                                                       


                                    
                                                                                                       





                                      
                      

                      


                 









                                                              



                                                                                  
                 

                                                              





                                                     



                               



                              








                                                                        
                                                                                                      








































































                                                                                          
                                                                                       




                                       


                                                                                      



                                                                              

                                                                                              



                                                                              

                                                                                          
                                                                 






                                                                                  









                                                                                                               













                                                    
/* See LICENSE file for copyright and license details. */
#include "libgamma.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#ifdef __WIN32__
# define gid_t short
#endif


#if LIBGAMMA_CRTC_INFO_COUNT != 15
# error CRTC information fields have been updated
#endif



/**
 * X macro of all integer gamma ramps
 * 
 * @param  _:macro(identifer)  Macro to expand
 */
#define LIST_INTEGER_RAMPS(_) _(ramps8) _(ramps32) _(ramps64) _(ramps16)

/**
 * X macro of all floating-point gamma ramps
 * 
 * @param  _:macro(identifer)  Macro to expand
 */
#define LIST_FLOAT_RAMPS(_) _(rampsf) _(rampsd)

/**
 * X macro of all gamma ramps
 * 
 * @param  _:macro(identifer)  Macro to expand
 */
#define LIST_RAMPS(_) LIST_FLOAT_RAMPS(_) LIST_INTEGER_RAMPS(_)

/* ramps16 is last because we want to make sure that the gamma ramps are
   preserved exactly on exit, and we assume X RandR is used. */



/**
 * Invertion mapping function from [0, 1] float encoding value to [0, 2¹⁶ − 1] integer output value
 * 
 * @param   encoding  [0, 1] float encoding value
 * @return            [0, 2¹⁶ − 1] integer output value
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static uint16_t
inv_ramps16(float encoding)
{
	double i_encoding = (double)(1.f - encoding);
	double f_output = (double)UINT16_MAX * i_encoding;
	uint16_t output = (uint16_t)f_output;
	if (i_encoding < (double)0.25f && output > UINT16_MAX / 2)
		output = 0;
	if (i_encoding > (double)0.75f && output < UINT16_MAX / 2)
		output = UINT16_MAX;
	return output;
}


/**
 * Test mapping function from [0, 1] float encoding value to [0, 2⁸ − 1] integer output value
 * 
 * @param   encoding  [0, 1] float encoding value
 * @return            [0, 2⁸ − 1] integer output value
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static uint8_t
dim_ramps8(float encoding)
{
	double i_encoding = (double)(encoding / 2.f);
	double f_output = (double)UINT8_MAX * i_encoding;
	uint8_t output = (uint8_t)f_output;
	if (i_encoding < (double)0.25f && output > UINT8_MAX / 2)
		output = 0;
	if (i_encoding > (double)0.75f && output < UINT8_MAX / 2)
		output = UINT8_MAX;
	return output;
}

/**
 * Test mapping function from [0, 1] float encoding value to [0, 2¹⁶ − 1] integer output value
 * 
 * @param   encoding  [0, 1] float encoding value
 * @return            [0, 2¹⁶ − 1] integer output value
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static uint16_t
dim_ramps16(float encoding)
{
	double i_encoding = (double)(encoding / 2.f);
	double f_output = (double)UINT16_MAX * i_encoding;
	uint16_t output = (uint16_t)f_output;
	if (i_encoding < (double)0.25f && output > UINT16_MAX / 2)
		output = 0;
	if (i_encoding > (double)0.75f && output < UINT16_MAX / 2)
		output = UINT16_MAX;
	return output;
}

/**
 * Test mapping function from [0, 1] float encoding value to [0, 2³² − 1] integer output value
 * 
 * @param   encoding  [0, 1] float encoding value
 * @return            [0, 2³² − 1] integer output value
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static uint32_t
dim_ramps32(float encoding)
{
	double i_encoding = (double)(encoding / 2.f);
	double f_output = (double)UINT32_MAX * i_encoding;
	uint32_t output = (uint32_t)f_output;
	if (i_encoding < (double)0.25f && output > UINT32_MAX / 2)
		output = 0;
	if (i_encoding > (double)0.75f && output < UINT32_MAX / 2)
		output = UINT32_MAX;
	return output;
}

/**
 * Test mapping function from [0, 1] float encoding value to [0, 2⁶⁴ − 1] integer output value
 * 
 * @param   encoding  [0, 1] float encoding value
 * @return            [0, 2⁶⁴ − 1] integer output value
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static uint64_t
dim_ramps64(float encoding)
{
	double i_encoding = (double)(encoding / 2.f);
	double f_output = (double)UINT64_MAX * i_encoding;
	uint64_t output = (uint64_t)f_output;
	if (i_encoding < (double)0.25f && output > UINT64_MAX / 2)
		output = 0;
	if (i_encoding > (double)0.75f && output < UINT64_MAX / 2)
		output = UINT64_MAX;
	return output;
}

/**
 * Test mapping function from [0, 1] float encoding value to [0, 1] float output value
 * 
 * @param   encoding  [0, 1] float encoding value
 * @return            [0, 1] float output value
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static float
dim_rampsf(float encoding)
{
	return encoding / 2.f;
}

/**
 * Test mapping function from [0, 1] double precision float encoding
 * value to [0, 1] double precision float output value
 * 
 * @param   encoding  [0, 1] float encoding value
 * @return            [0, 1] float output value
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static double
dim_rampsd(double encoding)
{
	return encoding / (double)2.f;
}


/**
 * Get the name representation of an
 * adjustment method by its identifier
 * 
 * @param   method  The identifier of the adjustment method
 * @return          The name of the adjustment
 */
LIBGAMMA_GCC_ONLY__(__attribute__((__const__)))
static const char *
method_name(int method)
{
	const char *r = libgamma_name_of_method(method);
	return r ? r : "(unknown)";
}


/**
 * Print a list of adjustment methods
 * 
 * @param  description  Precursory text for the list
 * @param  methods      An array allocated to fit all adjustment methods
 * @param  operation    See the `operation` parameter for `libgamma_list_methods`
 */
static void
list_methods(const char *description, int *methods, int operation)
{
	/* Get adjustment method list */
	size_t i, n = libgamma_list_methods(methods, LIBGAMMA_METHOD_COUNT, operation);

	/* Print adjustment method list */
	printf("%s:", description);
	for (i = 0; i < n; i++)
		printf(" %s", method_name(methods[i]));
	printf("\n");
}


/**
 * Print all lists, of adjustments methods, that
 * are made available by `libgamma_list_methods`
 */
static void
list_methods_lists(void)
{
	/* Allocate a list for adjustment methods that is large
	 * enough if the program is up to date with the library */
	int methods[LIBGAMMA_METHOD_COUNT];

	/* Print adjustment method lists. */
	list_methods("Available adjustment methods",               methods, 4);
	list_methods("Available real adjustment methods",          methods, 3);
	list_methods("Available real non-fake adjustment methods", methods, 2);
	list_methods("Recommended adjustment methods",             methods, 1);
	list_methods("Recommended non-fake adjustment methods",    methods, 0);
	printf("\n");
}


/**
 * Test the availability (determined at
 * compile-time) of all adjustment
 * methods and one that does not exist
 */
static void
method_availability(void)
{
	int method;
	/* The availability of an adjustmen method whose identifier
	 * is invalid. It should say it is not available. */
	printf("Testing the availability of a non-existing adjustment method: ");
	printf("%s\n", libgamma_is_method_available(-1) ? "available" : "not available");

	/* Test the availability of the adjustment methods that does exist */
	for (method = 0; method < LIBGAMMA_METHOD_COUNT; method++) {
		printf("Testing the availability of %s: ", method_name(method));
		printf("%s\n", libgamma_is_method_available(method) ? "available" : "not available");
	}
	printf("\n");
}


/**
 * List the default site and the environment
 * variable, if any, that determines the
 * default site, for all availiable adjustment
 * methods
 */
static void
list_default_sites(void)
{
	int method;
	for (method = 0; method < LIBGAMMA_METHOD_COUNT; method++) {
		/* Include only available adjustment methods.
		 * If an adjustment method is not available
		 * it should print "(null)", but as that can
		 * be misguiding we exclude those methods. */
		if (libgamma_is_method_available(method)) {
			printf("Default site for %s:\n", method_name(method));
			printf("  Variable: %s\n", libgamma_method_default_site_variable(method));
			printf("  Site name: %s\n", libgamma_method_default_site(method));
			printf("\n");
		}
	}
}


/**
 * Print the capabilities of all availiable
 * adjustment methods.
 */
static void
method_capabilities(void)
{
	struct libgamma_method_capabilities caps;
	int method;
	for (method = 0; method < LIBGAMMA_METHOD_COUNT; method++) {
		if (libgamma_is_method_available(method)) {
			/* Print adjustment method name and get the
			 * adjustment method's capabilities. */
			printf("Capabilities of %s:\n", method_name(method));
			libgamma_method_capabilities(&caps, sizeof(caps), method);
			if (caps.struct_version != LIBGAMMA_METHOD_CAPABILITIES_STRUCT_VERSION) {
				fprintf(stderr, "LIBGAMMA_METHOD_CAPABILITIES_STRUCT_VERSION must be updated\n");
				exit(1);
			}

			/* Print capabilities. The CRTC information
			 * capabilities is printed hexadecimal. See
			 * the `LIBGAMMA_CRTC_INFO_*` definitions in
			 * `libgamma.h` for what each bit represents. */
			printf("  %s: %llx\n", "CRTC information",      caps.crtc_information);
			printf("  %s: %s\n",   "Default site known",    caps.default_site_known            ? "yes" : "no");
			printf("  %s: %s\n",   "Multiple sites",        caps.multiple_sites                ? "yes" : "no");
			printf("  %s: %s\n",   "Multiple partitions",   caps.multiple_partitions           ? "yes" : "no");
			printf("  %s: %s\n",   "Multiple crtcs",        caps.multiple_crtcs                ? "yes" : "no");
			printf("  %s: %s\n",   "Graphics cards",        caps.partitions_are_graphics_cards ? "yes" : "no");
			printf("  %s: %s\n",   "Site restore",          caps.site_restore                  ? "yes" : "no");
			printf("  %s: %s\n",   "Partition restore",     caps.partition_restore             ? "yes" : "no");
			printf("  %s: %s\n",   "CRTC restore",          caps.crtc_restore                  ? "yes" : "no");
			printf("  %s: %s\n",   "Identical gamma sizes", caps.identical_gamma_sizes         ? "yes" : "no");
			printf("  %s: %s\n",   "Fixed gamma size",      caps.fixed_gamma_size              ? "yes" : "no");
			printf("  %s: %s\n",   "Fixed gamma depth",     caps.fixed_gamma_depth             ? "yes" : "no");
			printf("  %s: %s\n",   "Real method",           caps.real                          ? "yes" : "no");
			printf("  %s: %s\n",   "Fake method",           caps.fake                          ? "yes" : "no");
			printf("  %s: %s\n",   "Auto restore",          caps.auto_restore                  ? "yes" : "no");
			printf("\n");
		}
	}
}



/**
 * Let the user select adjustment method, site, partition and CRTC
 * 
 * @param   site_state  Output slot for the site
 * @param   part_state  Output slot for the partition
 * @param   crtc_state  Output slot for the CRTC
 * @return              Zero on and only on success
 */
static int
select_monitor(struct libgamma_site_state *site_state, struct libgamma_partition_state *part_state,
               struct libgamma_crtc_state *crtc_state)
{
	int method;
	char *site;
	char *tmp;
	char buf[256];
	int r;


	/* -- Adjustment method -- */

	/* Let the user select adjustment method */
	printf("Select adjustment method:\n");
	for (method = 0; method < LIBGAMMA_METHOD_COUNT; method++)
		printf("    %i:  %s\n", method, method_name(method));
	printf("> ");
	fflush(stdout);
	fgets(buf, sizeof(buf), stdin);
	method = atoi(buf);


	/* -- Site -- */

	/* Let the user select site */
	printf("Select site: ");
	fflush(stdout);
	fgets(buf, sizeof(buf), stdin);
	tmp = strchr(buf, '\n');
	if (tmp)
		*tmp = '\0';
	if (!buf[0]) {
		site = NULL;
	} else {
		site = malloc((strlen(buf) + 1) * sizeof(char));
		memcpy(site, buf, strlen(buf) + 1);
	}

	/* Initialise site state */
	if ((r = libgamma_site_initialise(site_state, method, site))) {
		free(site);
		libgamma_perror("error", r);
		return 1;
	}


	/* -- Partition -- */

	/* Check that the site has at least one partition */
	if (!site_state->partitions_available) {
		libgamma_site_free(site_state);
		printf("No partitions found\n");
		return 1;
	}

	/* Let the user select partition */
	printf("Select partition [0, %lu]: ", site_state->partitions_available - 1);
	fflush(stdout);
	fgets(buf, sizeof(buf), stdin);

	/* Initialise partition state */
	if ((r = libgamma_partition_initialise(part_state, site_state, (size_t)atoll(buf)))) {
		libgamma_site_free(site_state);
		libgamma_perror("error", r);
		return 1;
	}


	/* -- CRTC -- */

	/* Check that the partition has at least one CRTC */
	if (!part_state->crtcs_available) {
		libgamma_partition_free(part_state);
		libgamma_site_free(site_state);
		printf("No CRTC:s found\n");
		return 1;
	}

	/* Let the user select CRTC */
	printf("Select CRTC [0, %lu]: ", part_state->crtcs_available - 1);
	fflush(stdout);
	fgets(buf, sizeof(buf), stdin);

	/* Initialise CRTC state. */
	if ((r = libgamma_crtc_initialise(crtc_state, part_state, (size_t)atoll(buf)))) {
		libgamma_partition_free(part_state);
		libgamma_site_free(site_state);
		libgamma_perror("error", r);
		return 1;
	}

	printf("\n");
	return 0;
}



/**
 * Conditionally print a CRTC information field
 * 
 * @param  type:data-type           Data type for the value of the information field
 * @param  notation:string-literal  %-pattern for `printf` (excluding the %) that is used for `type`
 * @param  do_print                 Whether the information should be included in the process's output
 * @param  description              A description of the information field
 * @param  error                    The error of the information field
 * @param  value                    The value of the information field
 */
#define PRINT_CRTC_INFORMATION(type, notation)\
	static void\
	print_crtc_information_##type(int do_print, const char *description, int error, type value)\
	{\
		char buf[256];\
		if (do_print) {\
			if (error) {\
				snprintf(buf, sizeof(buf), "  (error) %s", description);\
				libgamma_perror(buf, error);\
			} else {\
				printf("  %s: %" notation "\n", description, value);\
			}\
		}\
	}


/**
 * A single [a-z] word alternative to `const char *`, we need it for the
 * function name of the string variant of `print_crtc_information_*`
 */
typedef const char *str;
/* Create `print_crtc_information_*` variants */
PRINT_CRTC_INFORMATION(size_t, "lu")
PRINT_CRTC_INFORMATION(signed, "i")
PRINT_CRTC_INFORMATION(int, "i")
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif
PRINT_CRTC_INFORMATION(float, "f")
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
PRINT_CRTC_INFORMATION(str, "s")
#undef PRINT_CRTC_INFORMATION


/**
 * Get a string representation of a subpixel order
 * 
 * @param   value  The subpixel order
 * @return         String representation
 */
static const char *
subpixel_order_str(enum libgamma_subpixel_order value)
{
	const char *r = libgamma_name_of_subpixel_order(value);
	return r ? r : "(unknown)";
}


/**
 * Get a string representation of a connector type
 * 
 * @param   value  The connector type
 * @return         String representation
 */
static const char *
connector_type_str(enum libgamma_connector_type value)
{
	const char *r = libgamma_name_of_connector_type(value);
	return r ? r : "(unknown)";
}


/**
 * Get a string representation of a decision
 * 
 * @param   value  The decision
 * @return         String representation
 */
static const char *
decision_str(enum libgamma_decision value)
{
	switch (value) {
	case LIBGAMMA_NO:    return "LIBGAMMA_NO"; 
	case LIBGAMMA_MAYBE: return "LIBGAMMA_MAYBE"; 
	case LIBGAMMA_YES:   return "LIBGAMMA_YES"; 
	default:
		return "(unknown)";
	}
}


/**
 * The CRTC information for a CRTC
 * 
 * @param  crtc  The CRTC
 */
static void
crtc_information(struct libgamma_crtc_state *restrict crtc)
{
	struct libgamma_method_capabilities caps;
	struct libgamma_crtc_information info;
	unsigned long long fields, field;
	char *edid_lc, *edid_uc;
	unsigned char *edid_raw;

	/* Get supported CRTC informations fields */
	libgamma_method_capabilities(&caps, sizeof(caps), crtc->partition->site->method);
	if (caps.struct_version != LIBGAMMA_METHOD_CAPABILITIES_STRUCT_VERSION) {
		fprintf(stderr, "LIBGAMMA_METHOD_CAPABILITIES_STRUCT_VERSION must be updated\n");
		exit(1);
	}

	/* List unsupport information fields by testing them one by one */
	for (fields = caps.crtc_information; field = fields & -fields, fields; fields ^= field) {
		if (libgamma_get_crtc_information(&info, sizeof(info), crtc, field))
			printf("Could not read CRTC information field %llu\n", field);
		free(info.edid);
		free(info.connector_name);
	}

	/* Get CRTC information, that is supported */
	fields = caps.crtc_information;
	if (libgamma_get_crtc_information(&info, sizeof(info), crtc, fields))
		printf("An error occurred while reading CRTC information\n");
	if (info.struct_version != LIBGAMMA_CRTC_INFORMATION_STRUCT_VERSION) {
		fprintf(stderr, "LIBGAMMA_CRTC_INFORMATION_STRUCT_VERSION must be updated\n");
		exit(1);
	}

	/* Macros for printing CRTC information */
#define PRINT2(TYPE, FIELD_ID, DESCRIPTION, FIELD_VAR, ERROR_VAR)\
	print_crtc_information_##TYPE(fields & FIELD_ID, DESCRIPTION, info.ERROR_VAR, info.FIELD_VAR);
#define PRINT(TYPE, FIELD_ID, DESCRIPTION, FIELD_VAR)\
	PRINT2(TYPE, FIELD_ID, DESCRIPTION, FIELD_VAR, FIELD_VAR##_error);
  
  
	/* Print CRTC information */

	printf("CRTC information:\n");

	/* Print the EDID field */
	if ((fields & LIBGAMMA_CRTC_INFO_EDID)) {
		if (info.edid_error) {
			libgamma_perror("  (error) EDID", info.edid_error);
		} else {
			edid_lc  = libgamma_behex_edid(info.edid, info.edid_length);
			edid_raw = libgamma_unhex_edid(edid_lc);
			edid_uc  = libgamma_behex_edid_uppercase(edid_raw, info.edid_length);
			printf("  EDID: %s\n", edid_lc);
			printf("  EDID (uppercase): %s\n", edid_uc);
			printf("  EDID (length): %lu\n", info.edid_length);
			free(edid_lc);
			free(edid_raw);
			free(edid_uc);
		}
	}
	/* Print physical dimensions of the monitor */
	PRINT(size_t, LIBGAMMA_CRTC_INFO_WIDTH_MM, "width", width_mm);
	PRINT(size_t, LIBGAMMA_CRTC_INFO_HEIGHT_MM, "height", height_mm);
	PRINT(size_t, LIBGAMMA_CRTC_INFO_WIDTH_MM_EDID, "width per EDID", width_mm_edid);
	PRINT(size_t, LIBGAMMA_CRTC_INFO_HEIGHT_MM_EDID, "height per EDID", height_mm_edid);
	/* Print gamma ramp information */
	PRINT2(size_t, LIBGAMMA_CRTC_INFO_GAMMA_SIZE, "red gamma ramp size", red_gamma_size, gamma_size_error);
	PRINT2(size_t, LIBGAMMA_CRTC_INFO_GAMMA_SIZE, "green gamma ramp size", green_gamma_size, gamma_size_error);
	PRINT2(size_t, LIBGAMMA_CRTC_INFO_GAMMA_SIZE, "blue gamma ramp size", blue_gamma_size, gamma_size_error);
	PRINT(signed, LIBGAMMA_CRTC_INFO_GAMMA_DEPTH, "gamma ramp depth", gamma_depth);
	/* Print gamma ramp support */
	if (fields & LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT) {
		if (info.gamma_support_error)
			libgamma_perror("  (error) gamma support", info.gamma_support_error);
		else
			printf("  gamma support: %s\n", decision_str(info.gamma_support));
	}
	/* Print subpixel order for the monitor */
	if (fields & LIBGAMMA_CRTC_INFO_SUBPIXEL_ORDER) {
		if (info.subpixel_order_error)
			libgamma_perror("  (error) subpixel order", info.subpixel_order_error);
		else
			printf("  subpixel order: %s\n", subpixel_order_str(info.subpixel_order));
	}
	/* Print connector information */
	PRINT(int, LIBGAMMA_CRTC_INFO_ACTIVE, "active", active);
	PRINT(str, LIBGAMMA_CRTC_INFO_CONNECTOR_NAME, "connector name", connector_name);
	if (fields & LIBGAMMA_CRTC_INFO_CONNECTOR_TYPE) {
		if (info.connector_type_error)
			libgamma_perror("  (error) subpixel order", info.connector_type_error);
		else
			printf("  subpixel order: %s\n", connector_type_str(info.connector_type));
	}
	/* Print the gamma characteristics of the monitor */
	PRINT2(float, LIBGAMMA_CRTC_INFO_GAMMA, "red gamma characteristics", gamma_red, gamma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_GAMMA, "green gamma characteristics", gamma_green, gamma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_GAMMA, "blue gamma characteristics", gamma_blue, gamma_error);
	/* Print the colour space of the monitor */
	PRINT2(float, LIBGAMMA_CRTC_INFO_CHROMA, "red chroma x", red_chroma_x, chroma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_CHROMA, "red chroma y", red_chroma_y, chroma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_CHROMA, "green chroma x", green_chroma_x, chroma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_CHROMA, "green chroma y", green_chroma_y, chroma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_CHROMA, "blue chroma x", blue_chroma_x, chroma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_CHROMA, "blue chroma y", blue_chroma_y, chroma_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_WHITE_POINT, "white point x", white_point_x, white_point_error);
	PRINT2(float, LIBGAMMA_CRTC_INFO_WHITE_POINT, "white point y", white_point_y, white_point_error);

	printf("\n");


#undef PRINT
#undef PRINT2

	/* Release resouces */
	free(info.edid);
	free(info.connector_name);
}


/**
 * Test that count macros are set to the same values as the count variables
 */
static void
test_count_consts(void)
{
	if (LIBGAMMA_ERROR_MIN != libgamma_error_min) {
		fprintf(stderr, "LIBGAMMA_ERROR_MIN != libgamma_error_min\n");
		exit(1);
	}
	if (LIBGAMMA_METHOD_COUNT != libgamma_method_count) {
		fprintf(stderr, "LIBGAMMA_METHOD_COUNT != libgamma_method_count\n");
		exit(1);
	}
	if (LIBGAMMA_CONNECTOR_TYPE_COUNT != libgamma_connector_type_count) {
		fprintf(stderr, "LIBGAMMA_CONNECTOR_TYPE_COUNT != libgamma_connector_type_count\n");
		exit(1);
	}
	if (LIBGAMMA_SUBPIXEL_ORDER_COUNT != libgamma_subpixel_order_count) {
		fprintf(stderr, "LIBGAMMA_SUBPIXEL_ORDER_COUNT != libgamma_subpixel_order_count\n");
		exit(1);
	}
}


/**
 * Test functions for connector types
 */
static void
test_connector_types(void)
{
	size_t n = 0;
	char buf[128], *w;
	const char *r;
	enum libgamma_connector_type type;

#define X(CONST)\
	do {\
		if ((unsigned int)CONST >= (unsigned int)LIBGAMMA_CONNECTOR_TYPE_COUNT) {\
			fprintf(stderr, "%s >= LIBGAMMA_CONNECTOR_TYPE_COUNT\n", #CONST);\
			exit(1);\
		}\
		if (!libgamma_const_of_connector_type(CONST)) {\
			fprintf(stderr, "libgamma_const_of_connector_type(%s) == NULL\n", #CONST);\
			exit(1);\
		}\
		if (!libgamma_name_of_connector_type(CONST)) {\
			fprintf(stderr, "libgamma_name_of_connector_type(%s) == NULL\n", #CONST);\
			exit(1);\
		}\
		if (strcmp(libgamma_const_of_connector_type(CONST), #CONST)) {\
			fprintf(stderr, "libgamma_const_of_connector_type(%s) != \"%s\"\n", #CONST, #CONST);\
			exit(1);\
		}\
		type = (enum libgamma_connector_type)-1;\
		if (libgamma_value_of_connector_type(#CONST, &type) || type != CONST) {\
			fprintf(stderr, "libgamma_value_of_connector_type(\"%s\") != %s\n", #CONST, #CONST);\
			exit(1);\
		}\
		type = (enum libgamma_connector_type)-1;\
		if (libgamma_value_of_connector_type(libgamma_name_of_connector_type(CONST), &type) || type != CONST) {\
			fprintf(stderr, "libgamma_value_of_connector_type(libgamma_name_of_connector_type(%s)) != %s\n",\
			        #CONST, #CONST); \
			exit(1);\
		}\
		type = (enum libgamma_connector_type)-1;\
		if (libgamma_value_of_connector_type(#CONST, &type) || type != CONST) { \
			fprintf(stderr, "libgamma_value_of_connector_type(\"%s\") != %s\n", #CONST, #CONST);\
			exit(1);\
		}\
		if (strlen(libgamma_name_of_connector_type(CONST)) >= sizeof(buf)) {\
			fprintf(stderr, "strlen(libgamma_name_of_connector_type(%s) >= %zu\n", #CONST, sizeof(buf));\
			exit(1);\
		}\
		r = libgamma_name_of_connector_type(CONST);\
		for (w = buf; *r; r++)\
			if (*r != ' ' && *r != '-')\
				*w++ = *r;\
		*w = '\0';\
		if (strcasecmp(buf, &#CONST[sizeof("LIBGAMMA_CONNECTOR_TYPE_") - 1])) {\
			fprintf(stderr, "libgamma_name_of_connector_type(%s) without [ -] != %s, ignoreing case\n",\
			        #CONST, &#CONST[sizeof("LIBGAMMA_CONNECTOR_TYPE_") - 1]);\
			exit(1);\
		}\
		n += 1;\
	} while (0)

	X(LIBGAMMA_CONNECTOR_TYPE_Unknown);
	X(LIBGAMMA_CONNECTOR_TYPE_VGA);
	X(LIBGAMMA_CONNECTOR_TYPE_DVI);
	X(LIBGAMMA_CONNECTOR_TYPE_DVII);
	X(LIBGAMMA_CONNECTOR_TYPE_DVID);
	X(LIBGAMMA_CONNECTOR_TYPE_DVIA);
	X(LIBGAMMA_CONNECTOR_TYPE_Composite);
	X(LIBGAMMA_CONNECTOR_TYPE_SVIDEO);
	X(LIBGAMMA_CONNECTOR_TYPE_LVDS);
	X(LIBGAMMA_CONNECTOR_TYPE_Component);
	X(LIBGAMMA_CONNECTOR_TYPE_9PinDIN);
	X(LIBGAMMA_CONNECTOR_TYPE_DisplayPort);
	X(LIBGAMMA_CONNECTOR_TYPE_HDMI);
	X(LIBGAMMA_CONNECTOR_TYPE_HDMIA);
	X(LIBGAMMA_CONNECTOR_TYPE_HDMIB);
	X(LIBGAMMA_CONNECTOR_TYPE_TV);
	X(LIBGAMMA_CONNECTOR_TYPE_eDP);
	X(LIBGAMMA_CONNECTOR_TYPE_VIRTUAL);
	X(LIBGAMMA_CONNECTOR_TYPE_DSI);
	X(LIBGAMMA_CONNECTOR_TYPE_LFP);
	X(LIBGAMMA_CONNECTOR_TYPE_DPI);
	X(LIBGAMMA_CONNECTOR_TYPE_WRITEBACK);
	X(LIBGAMMA_CONNECTOR_TYPE_SPI);

#undef X

	if (n != LIBGAMMA_CONNECTOR_TYPE_COUNT) {
		fprintf(stderr, "List of connector types in `test_connector_types` must be updated");
		exit(1);
	}

	if (libgamma_name_of_connector_type(-1)) {
		fprintf(stderr, "libgamma_name_of_connector_type(<invalid>) != NULL\n");
		exit(1);
	}
	if (libgamma_const_of_connector_type(-1)) {
		fprintf(stderr, "libgamma_const_of_connector_type(<invalid>) != NULL\n");
		exit(1);
	}
	if (libgamma_value_of_connector_type("", &type) != LIBGAMMA_CONNECTOR_TYPE_NOT_RECOGNISED) {
		fprintf(stderr, "libgamma_value_of_connector_type(<invalid>) != LIBGAMMA_CONNECTOR_TYPE_NOT_RECOGNISED\n");
		exit(1);
	}
}


/**
 * Test functions for subpixel orders
 */
static void
test_subpixel_orders(void)
{
	size_t n = 0;
	char buf[128], *w;
	const char *r;
	enum libgamma_subpixel_order order;

#define X(CONST)\
	do {\
		if ((unsigned int)CONST >= (unsigned int)LIBGAMMA_SUBPIXEL_ORDER_COUNT) {\
			fprintf(stderr, "%s >= LIBGAMMA_SUBPIXEL_ORDER_COUNT\n", #CONST);\
			exit(1);\
		}\
		if (!libgamma_const_of_subpixel_order(CONST)) {\
			fprintf(stderr, "libgamma_const_of_subpixel_order(%s) == NULL\n", #CONST);\
			exit(1);\
		}\
		if (!libgamma_name_of_subpixel_order(CONST)) {\
			fprintf(stderr, "libgamma_name_of_subpixel_order(%s) == NULL\n", #CONST);\
			exit(1);\
		}\
		if (strcmp(libgamma_const_of_subpixel_order(CONST), #CONST)) {\
			fprintf(stderr, "libgamma_const_of_subpixel_order(%s) != \"%s\"\n", #CONST, #CONST);\
			exit(1);\
		}\
		order = (enum libgamma_subpixel_order)-1;\
		if (libgamma_value_of_subpixel_order(#CONST, &order) || order != CONST) {\
			fprintf(stderr, "libgamma_value_of_subpixel_order(\"%s\") != %s\n", #CONST, #CONST);\
			exit(1);\
		}\
		order = (enum libgamma_subpixel_order)-1;\
		if (libgamma_value_of_subpixel_order(libgamma_name_of_subpixel_order(CONST), &order) || order != CONST) {\
			fprintf(stderr, "libgamma_value_of_subpixel_order(libgamma_name_of_subpixel_order(%s)) != %s\n",\
			        #CONST, #CONST);\
			exit(1);\
		}\
		order = (enum libgamma_subpixel_order)-1;\
		if (libgamma_value_of_subpixel_order(#CONST, &order) || order != CONST) {\
			fprintf(stderr, "libgamma_value_of_subpixel_order(\"%s\") != %s\n", #CONST, #CONST);\
			exit(1);\
		}\
		if (strlen(libgamma_name_of_subpixel_order(CONST)) >= sizeof(buf)) {\
			fprintf(stderr, "strlen(libgamma_name_of_subpixel_order(%s) >= %zu\n", #CONST, sizeof(buf));\
			exit(1);\
		}\
		r = libgamma_name_of_subpixel_order(CONST);\
		for (w = buf; *r; r++)\
			*w++ = *r == ' ' ? '_' : *r;\
		*w = '\0';\
		if (strcasecmp(buf, &#CONST[sizeof("LIBGAMMA_SUBPIXEL_ORDER_") - 1])) {\
			fprintf(stderr, "libgamma_name_of_subpixel_order(%s) with '_' for ' ' != %s, ignoreing case\n",\
			        #CONST, &#CONST[sizeof("LIBGAMMA_SUBPIXEL_ORDER_") - 1]);\
			exit(1);\
		}\
		n += 1;\
	} while (0)

	X(LIBGAMMA_SUBPIXEL_ORDER_UNKNOWN);
	X(LIBGAMMA_SUBPIXEL_ORDER_NONE);
	X(LIBGAMMA_SUBPIXEL_ORDER_HORIZONTAL_RGB);
	X(LIBGAMMA_SUBPIXEL_ORDER_HORIZONTAL_BGR);
	X(LIBGAMMA_SUBPIXEL_ORDER_VERTICAL_RGB);
	X(LIBGAMMA_SUBPIXEL_ORDER_VERTICAL_BGR);

#undef X

	if (n != LIBGAMMA_SUBPIXEL_ORDER_COUNT) {
		fprintf(stderr, "List of subpixel orders in `test_subpixel_orders` must be updated");
		exit(1);
	}

	if (libgamma_name_of_subpixel_order(-1)) {
		fprintf(stderr, "libgamma_name_of_subpixel_order(<invalid>) != NULL\n");
		exit(1);
	}
	if (libgamma_const_of_subpixel_order(-1)) {
		fprintf(stderr, "libgamma_const_of_subpixel_order(<invalid>) != NULL\n");
		exit(1);
	}
	if (libgamma_value_of_subpixel_order("", &order) != LIBGAMMA_SUBPIXEL_ORDER_NOT_RECOGNISED) {
		fprintf(stderr, "libgamma_value_of_subpixel_order(<invalid>) != LIBGAMMA_SUBPIXEL_ORDER_NOT_RECOGNISED\n");
		exit(1);
	}
}


/**
 * Test functions for errors
 */
static void
test_errors(void)
{
	int n = 0, fds[2], flags, err;
	char buf[1024], buf2[1100];
	ssize_t r;

	alarm(2);
	pipe(fds);
	flags = fcntl(fds[0], F_GETFL);
	fcntl(fds[0], F_SETFL, flags | O_NONBLOCK);
	err = dup(STDERR_FILENO);

#define X(CONST)\
	do {\
		if (CONST >= 0) {\
			fprintf(stderr, "%s >= 0\n", #CONST);\
			exit(1);\
		}\
		if (CONST < LIBGAMMA_ERROR_MIN) {\
			fprintf(stderr, "%s < LIBGAMMA_ERROR_MIN\n", #CONST);\
			exit(1);\
		}\
		if (!libgamma_name_of_error(CONST)) {\
			fprintf(stderr, "libgamma_name_of_error(%s) == NULL\n", #CONST);\
			exit(1);\
		}\
		if (strcmp(libgamma_name_of_error(CONST), #CONST)) {\
			fprintf(stderr, "libgamma_name_of_error(%s) != \"%s\"\n", #CONST, #CONST);\
			exit(1);\
		}\
		if (libgamma_value_of_error(#CONST) != CONST) {\
			fprintf(stderr, "libgamma_value_of_error(\"%s\") != %s\n", #CONST, #CONST);\
			exit(1);\
		}\
		libgamma_group_gid_set(0);\
		libgamma_group_name_set(NULL);\
		if (CONST != LIBGAMMA_ERRNO_SET) {\
			if (!libgamma_strerror(CONST)) {\
				fprintf(stderr, "libgamma_strerror(%s) == NULL\n", #CONST);\
				exit(1);\
			}\
			if (!libgamma_strerror_r(CONST, buf, sizeof(buf))) {\
				fprintf(stderr, "libgamma_strerror_r(%s, buf, sizeof(buf)) == NULL\n", #CONST);\
				exit(1);\
			}\
			if (strcmp(libgamma_strerror_r(CONST, buf, sizeof(buf)), libgamma_strerror(CONST))) {\
				fprintf(stderr, "libgamma_strerror_r(%s, buf, sizeof(buf)) != libgamma_strerror(%s)\n",\
				        #CONST, #CONST);\
				exit(1);\
			}\
			dup2(fds[1], STDERR_FILENO);\
			libgamma_perror(NULL, CONST);\
			fflush(stderr);\
			dup2(err, STDERR_FILENO);\
			r = read(fds[0], buf, sizeof(buf));\
			if (r <= 0 || buf[r - 1] != '\n') {\
				fprintf(stderr, "libgamma_perror(NULL, %s) failed\n", #CONST);\
				exit(1);\
			}\
			buf[r - 1] = '\0';\
			if (strcmp(buf, libgamma_strerror(CONST))) {\
				fprintf(stderr, "libgamma_perror(NULL, %s) failed\n", #CONST);\
				exit(1);\
			}\
			dup2(fds[1], STDERR_FILENO);\
			libgamma_perror("", CONST);\
			fflush(stderr);\
			dup2(err, STDERR_FILENO);\
			r = read(fds[0], buf, sizeof(buf));\
			if (r <= 0 || buf[r - 1] != '\n') {\
				fprintf(stderr, "libgamma_perror(\"\", %s) failed\n", #CONST);\
				exit(1);\
			}\
			buf[r - 1] = '\0';\
			if (strcmp(buf, libgamma_strerror(CONST))) {\
				fprintf(stderr, "libgamma_perror(\"\", %s) failed\n", #CONST);\
				exit(1);\
			}\
			dup2(fds[1], STDERR_FILENO);\
			libgamma_perror("prefix", CONST);\
			fflush(stderr);\
			dup2(err, STDERR_FILENO);\
			r = read(fds[0], buf, sizeof(buf));\
			if (r <= 0 || buf[r - 1] != '\n') {\
				fprintf(stderr, "libgamma_perror(\"prefix\", %s) failed\n", #CONST);\
				exit(1);\
			}\
			buf[r - 1] = '\0';\
			if (strncmp(buf, "prefix: ", 8)) {\
				fprintf(stderr, "libgamma_perror(\"prefix\", %s) failed\n", #CONST);\
				exit(1);\
			}\
			if (strcmp(&buf[8], libgamma_strerror(CONST))) {\
				fprintf(stderr, "libgamma_perror(\"prefix\", %s) failed\n", #CONST);\
				exit(1);\
			}\
		}\
		libgamma_group_gid_set((gid_t)n);\
		if (libgamma_group_gid_get() != (gid_t)n) {\
			fprintf(stderr, "libgamma_group_gid_get() != (gid_t)n\n");\
			exit(1);\
		}\
		n += 1;\
	} while (0)

	X(LIBGAMMA_ERRNO_SET);
	X(LIBGAMMA_NO_SUCH_ADJUSTMENT_METHOD);
	X(LIBGAMMA_NO_SUCH_SITE);
	X(LIBGAMMA_NO_SUCH_PARTITION);
	X(LIBGAMMA_NO_SUCH_CRTC);
	X(LIBGAMMA_IMPOSSIBLE_AMOUNT);
	X(LIBGAMMA_CONNECTOR_DISABLED);
	X(LIBGAMMA_OPEN_CRTC_FAILED);
	X(LIBGAMMA_CRTC_INFO_NOT_SUPPORTED);
	X(LIBGAMMA_GAMMA_RAMP_READ_FAILED);
	X(LIBGAMMA_GAMMA_RAMP_WRITE_FAILED);
	X(LIBGAMMA_GAMMA_RAMP_SIZE_CHANGED);
	X(LIBGAMMA_MIXED_GAMMA_RAMP_SIZE);
	X(LIBGAMMA_WRONG_GAMMA_RAMP_SIZE);
	X(LIBGAMMA_SINGLETON_GAMMA_RAMP);
	X(LIBGAMMA_LIST_CRTCS_FAILED);
	X(LIBGAMMA_ACQUIRING_MODE_RESOURCES_FAILED);
	X(LIBGAMMA_NEGATIVE_PARTITION_COUNT);
	X(LIBGAMMA_NEGATIVE_CRTC_COUNT);
	X(LIBGAMMA_DEVICE_RESTRICTED);
	X(LIBGAMMA_DEVICE_ACCESS_FAILED);
	X(LIBGAMMA_DEVICE_REQUIRE_GROUP);
	X(LIBGAMMA_GRAPHICS_CARD_REMOVED);
	X(LIBGAMMA_STATE_UNKNOWN);
	X(LIBGAMMA_CONNECTOR_UNKNOWN);
	X(LIBGAMMA_CONNECTOR_TYPE_NOT_RECOGNISED);
	X(LIBGAMMA_SUBPIXEL_ORDER_NOT_RECOGNISED);
	X(LIBGAMMA_EDID_LENGTH_UNSUPPORTED);
	X(LIBGAMMA_EDID_WRONG_MAGIC_NUMBER);
	X(LIBGAMMA_EDID_REVISION_UNSUPPORTED);
	X(LIBGAMMA_GAMMA_NOT_SPECIFIED);
	X(LIBGAMMA_EDID_CHECKSUM_ERROR);
	X(LIBGAMMA_GAMMA_NOT_SPECIFIED_AND_EDID_CHECKSUM_ERROR);
	X(LIBGAMMA_GAMMA_RAMPS_SIZE_QUERY_FAILED);
	X(LIBGAMMA_OPEN_PARTITION_FAILED);
	X(LIBGAMMA_OPEN_SITE_FAILED);
	X(LIBGAMMA_PROTOCOL_VERSION_QUERY_FAILED);
	X(LIBGAMMA_PROTOCOL_VERSION_NOT_SUPPORTED);
	X(LIBGAMMA_LIST_PARTITIONS_FAILED);
	X(LIBGAMMA_NULL_PARTITION);
	X(LIBGAMMA_NOT_CONNECTED);
	X(LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED);
	X(LIBGAMMA_EDID_NOT_FOUND);
	X(LIBGAMMA_LIST_PROPERTIES_FAILED);
	X(LIBGAMMA_PROPERTY_VALUE_QUERY_FAILED);
	X(LIBGAMMA_OUTPUT_INFORMATION_QUERY_FAILED);

#undef X

	if (-n != LIBGAMMA_ERROR_MIN) {
		fprintf(stderr, "List of errors in `test_errors` must be updated");
		exit(1);
	}

	if (libgamma_value_of_error(NULL)) {
		fprintf(stderr, "libgamma_value_of_error(NULL) != 0\n");
		exit(1);
	}
	if (libgamma_value_of_error("")) {
		fprintf(stderr, "libgamma_value_of_error(<invalid>) != 0\n");
		exit(1);
	}
	if (libgamma_name_of_error(1000)) {
		fprintf(stderr, "libgamma_name_of_error(<invalid>) != NULL\n");
		exit(1);
	}
	if (libgamma_strerror(LIBGAMMA_ERROR_MIN - 1)) {
		fprintf(stderr, "libgamma_strerror(LIBGAMMA_ERROR_MIN - 1) != NULL\n");
		exit(1);
	}
	if (!libgamma_strerror_r(LIBGAMMA_ERROR_MIN - 1, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(LIBGAMMA_ERROR_MIN - 1, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	if (!libgamma_strerror_r(INT_MAX, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(INT_MAX, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}

	/* Just a few of the errors */
	if (strcmp(libgamma_strerror(LIBGAMMA_NO_SUCH_SITE), "No such site")) {
		fprintf(stderr, "libgamma_strerror(LIBGAMMA_NO_SUCH_SITE) != \"No such site\"\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(LIBGAMMA_NO_SUCH_PARTITION), "No such partition")) {
		fprintf(stderr, "libgamma_strerror(LIBGAMMA_NO_SUCH_PARTITION) != \"No such partition\"\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(LIBGAMMA_NO_SUCH_CRTC), "No such CRTC")) {
		fprintf(stderr, "libgamma_strerror(LIBGAMMA_NO_SUCH_CRTC) != \"No such CRTC\"\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(LIBGAMMA_SINGLETON_GAMMA_RAMP), "Single-stop gamma ramp")) {
		fprintf(stderr, "libgamma_strerror(LIBGAMMA_SINGLETON_GAMMA_RAMP) != \"Single-stop gamma ramp\"\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(LIBGAMMA_GRAPHICS_CARD_REMOVED), "Graphics card was removed")) {
		fprintf(stderr, "libgamma_strerror(LIBGAMMA_GRAPHICS_CARD_REMOVED) != \"Graphics card was removed\"\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(LIBGAMMA_DEVICE_RESTRICTED), "Device is restricted to root")) {
		fprintf(stderr, "libgamma_strerror(LIBGAMMA_DEVICE_RESTRICTED) != \"Device is restricted to root\"\n");
		exit(1);
	}

	errno = -1;
	if (!libgamma_strerror(0)) {
		fprintf(stderr, "libgamma_strerror(0) == NULL\n");
		exit(1);
	}
	if (!libgamma_strerror(ENOMEM)) {
		fprintf(stderr, "libgamma_strerror(ENOMEM) == NULL\n");
		exit(1);
	}
	if (!libgamma_strerror(ERANGE)) {
		fprintf(stderr, "libgamma_strerror(ERANGE) == NULL\n");
		exit(1);
	}
	if (!libgamma_strerror(EDOM)) {
		fprintf(stderr, "libgamma_strerror(EDOM) == NULL\n");
		exit(1);
	}
	errno = 0;
	if (!libgamma_strerror(LIBGAMMA_ERRNO_SET)) {
		fprintf(stderr, "libgamma_strerror(0 via LIBGAMMA_ERRNO_SET) == NULL\n");
		exit(1);
	}
	errno = ENOMEM;
	if (!libgamma_strerror(LIBGAMMA_ERRNO_SET)) {
		fprintf(stderr, "libgamma_strerror(ENOMEM via LIBGAMMA_ERRNO_SET) == NULL\n");
		exit(1);
	}
	errno = ERANGE;
	if (!libgamma_strerror(LIBGAMMA_ERRNO_SET)) {
		fprintf(stderr, "libgamma_strerror(ERANGE via LIBGAMMA_ERRNO_SET) == NULL\n");
		exit(1);
	}
	errno = EDOM;
	if (!libgamma_strerror(LIBGAMMA_ERRNO_SET)) {
		fprintf(stderr, "libgamma_strerror(EDOM via LIBGAMMA_ERRNO_SET) == NULL\n");
		exit(1);
	}

	errno = -1;
	if (strcmp(libgamma_strerror(0), strerror(0))) {
		fprintf(stderr, "libgamma_strerror(0) != strerror(0)\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(ENOMEM), strerror(ENOMEM))) {
		fprintf(stderr, "libgamma_strerror(ENOMEM) != strerror(ENOMEM)\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(ERANGE), strerror(ERANGE))) {
		fprintf(stderr, "libgamma_strerror(ERANGE) != strerror(ERANGE)\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror(EDOM), strerror(EDOM))) {
		fprintf(stderr, "libgamma_strerror(EDOM) != strerror(EDOM)\n");
		exit(1);
	}
	errno = 0;
	if (strcmp(libgamma_strerror(LIBGAMMA_ERRNO_SET), strerror(0))) {
		fprintf(stderr, "libgamma_strerror(0 via LIBGAMMA_ERRNO_SET) != strerror(0)\n");
		exit(1);
	}
	errno = ENOMEM;
	if (strcmp(libgamma_strerror(LIBGAMMA_ERRNO_SET), strerror(ENOMEM))) {
		fprintf(stderr, "libgamma_strerror(ENOMEM via LIBGAMMA_ERRNO_SET) != strerror(ENOMEM)\n");
		exit(1);
	}
	errno = ERANGE;
	if (strcmp(libgamma_strerror(LIBGAMMA_ERRNO_SET), strerror(ERANGE))) {
		fprintf(stderr, "libgamma_strerror(ERANGE via LIBGAMMA_ERRNO_SET) != strerror(ERANGE)\n");
		exit(1);
	}
	errno = EDOM;
	if (strcmp(libgamma_strerror(LIBGAMMA_ERRNO_SET), strerror(EDOM))) {
		fprintf(stderr, "libgamma_strerror(EDOM via LIBGAMMA_ERRNO_SET) != strerror(EDOM)\n");
		exit(1);
	}

	errno = -1;
	if (!libgamma_strerror_r(0, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(0, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	if (!libgamma_strerror_r(ENOMEM, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(ENOMEM, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	if (!libgamma_strerror_r(ERANGE, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(ERANGE, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	if (!libgamma_strerror_r(EDOM, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(EDOM, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	errno = 0;
	if (!libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(0 via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	errno = ENOMEM;
	if (!libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(ENOMEM via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	errno = ERANGE;
	if (!libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(ERANGE via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}
	errno = EDOM;
	if (!libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf))) {
		fprintf(stderr, "libgamma_strerror_r(EDOM via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) == NULL\n");
		exit(1);
	}

	errno = -1;
#ifdef ERRNO_0_HAS_MESSAGE
	if (strcmp(libgamma_strerror_r(0, buf, sizeof(buf)), strerror(0))) {
		fprintf(stderr, "libgamma_strerror_r(0, buf, sizeof(buf)) != strerror(0)\n");
		exit(1);
	}
#endif
	if (strcmp(libgamma_strerror_r(ENOMEM, buf, sizeof(buf)), strerror(ENOMEM))) {
		fprintf(stderr, "libgamma_strerror_r(ENOMEM, buf, sizeof(buf)) != strerror(ENOMEM)\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror_r(ERANGE, buf, sizeof(buf)), strerror(ERANGE))) {
		fprintf(stderr, "libgamma_strerror_r(ERANGE, buf, sizeof(buf)) != strerror(ERANGE)\n");
		exit(1);
	}
	if (strcmp(libgamma_strerror_r(EDOM, buf, sizeof(buf)), strerror(EDOM))) {
		fprintf(stderr, "libgamma_strerror_r(EDOM, buf, sizeof(buf)) != strerror(EDOM)\n");
		exit(1);
	}
#ifdef ERRNO_0_HAS_MESSAGE
	errno = 0;
	if (strcmp(libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf)), strerror(0))) {
		fprintf(stderr, "libgamma_strerror_r(0 via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) != strerror(0)\n");
		exit(1);
	}
#endif
	errno = ENOMEM;
	if (strcmp(libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf)), strerror(ENOMEM))) {
		fprintf(stderr, "libgamma_strerror_r(ENOMEM via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) != strerror(ENOMEM)\n");
		exit(1);
	}
	errno = ERANGE;
	if (strcmp(libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf)), strerror(ERANGE))) {
		fprintf(stderr, "libgamma_strerror_r(ERANGE via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) != strerror(ERANGE)\n");
		exit(1);
	}
	errno = EDOM;
	if (strcmp(libgamma_strerror_r(LIBGAMMA_ERRNO_SET, buf, sizeof(buf)), strerror(EDOM))) {
		fprintf(stderr, "libgamma_strerror_r(EDOM via LIBGAMMA_ERRNO_SET, buf, sizeof(buf)) != strerror(EDOM)\n");
		exit(1);
	}

	libgamma_group_name_set(NULL);
	if (libgamma_group_name_get()) {
		fprintf(stderr, "libgamma_group_name_get() != NULL\n");
		exit(1);
	}
	libgamma_group_name_set("group");
	if (!libgamma_group_name_get()) {
		fprintf(stderr, "libgamma_group_name_get() == NULL\n");
		exit(1);
	}
	if (strcmp(libgamma_group_name_get(), "group")) {
		fprintf(stderr, "libgamma_group_name_get() != \"group\"\n");
		exit(1);
	}
	libgamma_group_name_set("");
	if (libgamma_group_name_get()) {
		fprintf(stderr, "libgamma_group_name_get() != NULL\n");
		exit(1);
	}
	libgamma_group_name_set("name");
	if (!libgamma_group_name_get()) {
		fprintf(stderr, "libgamma_group_name_get() == NULL\n");
		exit(1);
	}
	if (strcmp(libgamma_group_name_get(), "name")) {
		fprintf(stderr, "libgamma_group_name_get() != \"name\"\n");
		exit(1);
	}
	libgamma_group_name_set(NULL);
	if (libgamma_group_name_get()) {
		fprintf(stderr, "libgamma_group_name_get() != NULL\n");
		exit(1);
	}

	libgamma_group_gid_set(5);
	snprintf(buf2, sizeof(buf2), "%s in group 5", libgamma_strerror(LIBGAMMA_DEVICE_REQUIRE_GROUP));

	dup2(fds[1], STDERR_FILENO);
	libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP);
	fflush(stderr);
	dup2(err, STDERR_FILENO);
	r = read(fds[0], buf, sizeof(buf));
	if (r <= 0 || buf[r - 1] != '\n') {
		fprintf(stderr, "libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n"); 
		exit(1);
	}
	buf[r - 1] = '\0';
	if (strcmp(buf, buf2)) {
		fprintf(stderr, "libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}

	dup2(fds[1], STDERR_FILENO);
	libgamma_perror("", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	fflush(stderr);
	dup2(err, STDERR_FILENO);
	r = read(fds[0], buf, sizeof(buf));
	if (r <= 0 || buf[r - 1] != '\n') {
		fprintf(stderr, "libgamma_perror(\"\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}
	buf[r - 1] = '\0';
	if (strcmp(buf, buf2)) {
		fprintf(stderr, "libgamma_perror(\"\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}

	dup2(fds[1], STDERR_FILENO);
	libgamma_perror("prefix", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	fflush(stderr);
	dup2(err, STDERR_FILENO);
	r = read(fds[0], buf, sizeof(buf));
	if (r <= 0 || buf[r - 1] != '\n') {
		fprintf(stderr, "libgamma_perror(\"prefix\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}
	buf[r - 1] = '\0';
	if (strncmp(buf, "prefix: ", 8)) {
		fprintf(stderr, "libgamma_perror(\"prefix\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}
	if (strcmp(&buf[8], buf2)) {
		fprintf(stderr, "libgamma_perror(\"prefix\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}

	libgamma_group_gid_set(5);
	libgamma_group_name_set("grp");
	snprintf(buf2, sizeof(buf2), "%s in the grp group (5)", libgamma_strerror(LIBGAMMA_DEVICE_REQUIRE_GROUP));

	dup2(fds[1], STDERR_FILENO);
	libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP);
	fflush(stderr);
	dup2(err, STDERR_FILENO);
	r = read(fds[0], buf, sizeof(buf));
	if (r <= 0 || buf[r - 1] != '\n') {
		fprintf(stderr, "libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n"); 
		exit(1);
	}
	buf[r - 1] = '\0';
	if (strcmp(buf, buf2)) {
		fprintf(stderr, "libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}

	dup2(fds[1], STDERR_FILENO);
	libgamma_perror("", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	fflush(stderr);
	dup2(err, STDERR_FILENO);
	r = read(fds[0], buf, sizeof(buf));
	if (r <= 0 || buf[r - 1] != '\n') {
		fprintf(stderr, "libgamma_perror(\"\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}
	buf[r - 1] = '\0';
	if (strcmp(buf, buf2)) {
		fprintf(stderr, "libgamma_perror(\"\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}

	dup2(fds[1], STDERR_FILENO);
	libgamma_perror("prefix", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	fflush(stderr);
	dup2(err, STDERR_FILENO);
	r = read(fds[0], buf, sizeof(buf));
	if (r <= 0 || buf[r - 1] != '\n') {
		fprintf(stderr, "libgamma_perror(\"prefix\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}
	buf[r - 1] = '\0';
	if (strncmp(buf, "prefix: ", 8)) {
		fprintf(stderr, "libgamma_perror(\"prefix\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}
	if (strcmp(&buf[8], buf2)) {
		fprintf(stderr, "libgamma_perror(\"prefix\", LIBGAMMA_DEVICE_REQUIRE_GROUP) failed\n");
		exit(1);
	}

	libgamma_group_gid_set(0);
	libgamma_group_name_set(NULL);

	close(fds[0]);
	close(fds[1]);
	close(err);
	alarm(0);
}


/**
 * Test libgamma
 * 
 * @return  Non-zero on machine detectable error, this library
 *          may still be faulty if zero is returned
 */
int
main(void)
{
	struct libgamma_site_state      *site_state = malloc(sizeof(*site_state));
	struct libgamma_partition_state *part_state = malloc(sizeof(*part_state));
	struct libgamma_crtc_state      *crtc_state = malloc(sizeof(*crtc_state));
	struct libgamma_crtc_information info;
#define X(RAMPS)\
	struct libgamma_gamma_##RAMPS old_##RAMPS, RAMPS;\
	libgamma_gamma_##RAMPS##_fun *f_##RAMPS = dim_##RAMPS;
	LIST_RAMPS(X)
#undef X
	size_t i, n;
	int r, rr = 0;

	/* Test miscellaneous parts of the library */
	test_count_consts();
	test_connector_types();
	test_subpixel_orders();
	test_errors();
	list_methods_lists();
	method_availability();
	list_default_sites();
	method_capabilities();

	/* Select monitor for tests over CRTC:s, partitions and sites */
	if (select_monitor(site_state, part_state, crtc_state))
		return 1;

	/* Test CRTC information functions */
	crtc_information(crtc_state);

	/* Get the sizes of the gamma ramps for the selected CRTC */
	libgamma_get_crtc_information(&info, sizeof(info), crtc_state, LIBGAMMA_CRTC_INFO_GAMMA_SIZE);

	/* Create gamma ramps for each depth */
#define X(R)\
	old_##R.red_size = info.red_gamma_size;\
	old_##R.green_size = info.green_gamma_size;\
	old_##R.blue_size = info.blue_gamma_size;\
	R = old_##R;\
	libgamma_gamma_##R##_initialise(&old_##R);\
	libgamma_gamma_##R##_initialise(&R);
	LIST_RAMPS(X)
#undef X

	/* Fill gamma ramps, for each depth, with the CRTC:s current ramps */
#define X(R)\
	if ((rr |= r = libgamma_crtc_get_gamma_##R(crtc_state, &old_##R))) {\
		libgamma_perror("libgamma_crtc_get_gamma_" #R, r);\
		goto done;\
	}\
	if ((rr |= r = libgamma_crtc_get_gamma_##R(crtc_state, &R))) {\
		libgamma_perror("libgamma_crtc_get_gamma_" #R, r);\
		goto done;\
	}
	LIST_RAMPS(X)
#undef X
  
	/* Test function assisted gamma ramps setting */
#define X(R)\
	/* Dim the monitor for one second and the restore it. */\
	printf("Dimming monitor output for 1 second... (" #R ")\n");\
	if ((rr |= r = libgamma_crtc_set_gamma_##R##_f(crtc_state, f_##R, f_##R, f_##R)))\
		libgamma_perror("libgamma_crtc_set_gamma_" #R "_f", r);\
	sleep(1);\
	if ((rr |= r = libgamma_crtc_set_gamma_##R(crtc_state, &old_##R)))\
		libgamma_perror("libgamma_crtc_set_gamma_" #R, r);\
	printf("Done!\n");\
	/* Sleep for one second, we have more depths to test */\
	printf("Sleeping for 1 second...\n");\
	sleep(1);
	LIST_RAMPS(X)
#undef X

	/* Test getting and setting gamma ramps */
#define X(R)\
	/* Get the grand size of the gamma ramps */\
	n = R.red_size;\
	n = n > R.green_size ? n : R.green_size;\
	n = n > R.blue_size ? n : R.blue_size;\
	/* Print the current gamma ramps */\
	printf("Current gamma ramps (" #R "):\n");\
	for (i = 0; i < n; i++) {\
		if (i < R.red_size)    Y(R.red,   "1");  else  printf("      ");\
		if (i < R.green_size)  Y(R.green, "2");  else  printf("      ");\
		if (i < R.blue_size)   Y(R.blue,  "4");  else  printf("      ");\
		printf("\n");\
	}\
	printf("\n");\
	/* Adjust the gamma ramps for dimming the monitor */\
	for (i = 0; i < R.red_size + R.green_size + R.blue_size; i++)\
		R.red[i] /= 2;\
	/* Dim the monitor for one second and the restore it */\
	printf("Dimming monitor for 1 second...\n");\
	if ((rr |= r = libgamma_crtc_set_gamma_##R(crtc_state, &R)))\
		libgamma_perror("libgamma_crtc_set_gamma_" #R, r);\
	sleep(1);\
	if ((rr |= r = libgamma_crtc_set_gamma_##R(crtc_state, &old_##R)))\
		libgamma_perror("libgamma_crtc_set_gamma_" #R, r);\
	printf("Done!\n");\
	/* Sleep for one second, we have more depths to test */\
	printf("Sleeping for 1 second...\n");\
	sleep(1);
#define Y(R, C)  printf("  \033[3" C "m%1.8lf\033[00m", (double)(R[i]))
	LIST_FLOAT_RAMPS(X)
#undef Y
#define Y(R, C)  printf("  \033[3" C "m%16llX\033[00m", (unsigned long long int)(R[i]))
	LIST_INTEGER_RAMPS(X)
#undef Y
#undef X

	/* Test order of gamma ramps */
	memcpy(ramps16.red, old_ramps16.red, ramps16.red_size * sizeof(*ramps16.red));
	memset(ramps16.green, 0, ramps16.green_size * sizeof(*ramps16.green));
	memset(ramps16.blue, 0, ramps16.blue_size * sizeof(*ramps16.blue));
	printf("Making the monitor red-only for 1 second...\n");
	if ((rr |= r = libgamma_crtc_set_gamma_ramps16(crtc_state, &ramps16)))
		libgamma_perror("libgamma_crtc_set_gamma_ramps16", r);
	sleep(1);
	memset(ramps16.red, 0, ramps16.red_size * sizeof(*ramps16.red));
	memcpy(ramps16.green, old_ramps16.green, ramps16.green_size * sizeof(*ramps16.green));
	printf("Making the monitor green-only for 1 second...\n");
	if ((rr |= r = libgamma_crtc_set_gamma_ramps16(crtc_state, &ramps16)))
		libgamma_perror("libgamma_crtc_set_gamma_ramps16", r);
	sleep(1);
	memset(ramps16.green, 0, ramps16.green_size * sizeof(*ramps16.green));
	memcpy(ramps16.blue, old_ramps16.blue, ramps16.blue_size * sizeof(*ramps16.blue));
	printf("Making the monitor blue-only for 1 second...\n");
	if ((rr |= r = libgamma_crtc_set_gamma_ramps16(crtc_state, &ramps16)))
		libgamma_perror("libgamma_crtc_set_gamma_ramps16", r);
	sleep(1);
	if ((rr |= r = libgamma_crtc_set_gamma_ramps64(crtc_state, &old_ramps64)))
		libgamma_perror("libgamma_crtc_set_gamma_ramps64", r);
	printf("Done!\n");

	/* Test inversion (does not work properly on all devices) */
	printf("Inverting monitor output for 1 second... (does not work properly on all devices) (ramps16)\n");
	if ((rr |= r = libgamma_crtc_set_gamma_ramps16_f(crtc_state, inv_ramps16, inv_ramps16, inv_ramps16)))
		libgamma_perror("libgamma_crtc_set_gamma_ramps16_f", r);
	sleep(1);
	if ((rr |= r = libgamma_crtc_set_gamma_ramps16(crtc_state, &old_ramps16)))
		libgamma_perror("libgamma_crtc_set_gamma_ramps16", r);
	printf("Done!\n");
	sleep(1);

	/* TODO Test gamma ramp restore functions */
  
done:
	/* Release resouces */
#define X(R)\
	libgamma_gamma_##R##_destroy(&R);\
	libgamma_gamma_##R##_destroy(&old_##R);
	LIST_RAMPS(X)
#undef X
	libgamma_crtc_free(crtc_state);
	libgamma_partition_free(part_state);
	libgamma_site_free(site_state);
	return rr;
}