aboutsummaryrefslogblamecommitdiffstats
path: root/test.c
blob: c00c5bc290c3ecd7856b210ff5ad93cdc9608698 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289



                                                         

                   



                   
                    

                   



                    
 

                                                 







































































































































































                                                                                                    
                                   
                               
                                                       













                                                                  






                                                                               
                     

























































                                                                                                     
                                                 





                                                                             




                                                                                                                 




                                                                        














                                                                                                                           















                                                                  

                                                                                                   








































































































                                                                                                      
                                               




















                                                                                                        



                                    


                                                    

                                  

                           

                                








                                                  
                                                      












                                                               
                                                      












                                                               
                                          
















                                                      
                                                           
 


                                                 



                                                    




                                                                                                 


                                                                                                 
                                                                                    
                                                                                      





                                                     
                                                                             
                                                                             



                                                                                              

                                                  
                                                                  
                                                                                                      

                                                                          






















                                                                                             



                                                                                            
                                          



                                                                                                                   














                                                                                                  

                                                                                        






                                                                                                  


                                                                                                         
                                                   







                                                                                                         
 


                     

             







                                  
                                                                           
   
           
                       
 





















                                                                                                    
           































































































                                                                                                                           
         


 


                                     
           




















































































                                                                                                                           
           



















































































































































































































































































































































































































































                                                                                                                              
                                                                                                       



                                          
                                                                                                       


                                    
                                                                                                       










































                                                                                                                  
                                                                                                       



                                          
                                                                                                       


                                    
                                                                                                       










                                      









                                                              



                                                                                  
                 

                                                              





                                                     



                               



                              








                                                                        
                                                                                                      








































































                                                                                          
                                                                                       




                                       


                                                                                      



                                                                              

                                                                                              



                                                                              

                                                                                          





















                                                                                  
/* 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. */



/**
 * 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) / sizeof(char), stdin);
	method = atoi(buf);


	/* -- Site -- */

	/* Let the user select site */
	printf("Select site: ");
	fflush(stdout);
	fgets(buf, sizeof(buf) / sizeof(char), 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) / sizeof(char), 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) / sizeof(char), 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) / sizeof(char), "  (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;

#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);\
		}\
		if (libgamma_value_of_connector_type(#CONST) != CONST) {\
			fprintf(stderr, "libgamma_value_of_connector_type(\"%s\") != %s\n", #CONST, #CONST);\
			exit(1);\
		}\
		if (libgamma_value_of_connector_type(libgamma_name_of_connector_type(CONST)) != CONST) {\
			fprintf(stderr, "libgamma_value_of_connector_type(libgamma_name_of_connector_type(%s)) != %s\n",\
			        #CONST, #CONST);\
			exit(1);\
		}\
		if (libgamma_value_of_connector_type(#CONST) != 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("") != 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;

#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);\
		}\
		if (libgamma_value_of_subpixel_order(#CONST) != CONST) {\
			fprintf(stderr, "libgamma_value_of_subpixel_order(\"%s\") != %s\n", #CONST, #CONST);\
			exit(1);\
		}\
		if (libgamma_value_of_subpixel_order(libgamma_name_of_subpixel_order(CONST)) != CONST) {\
			fprintf(stderr, "libgamma_value_of_subpixel_order(libgamma_name_of_subpixel_order(%s)) != %s\n",\
			        #CONST, #CONST);\
			exit(1);\
		}\
		if (libgamma_value_of_subpixel_order(#CONST) != 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("") != 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;
	char buf[1024], buf2[1100];
	FILE *fp, *err = stderr;
	ssize_t r;

	alarm(2);
	pipe(fds);
	fp = fdopen(fds[1], "w");
	flags = fcntl(fds[0], F_GETFL);
	fcntl(fds[0], F_SETFL, flags | O_NONBLOCK);

#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);\
			}\
			stderr = fp;\
			libgamma_perror(NULL, CONST);\
			stderr = err;\
			fflush(fp);\
			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);\
			}\
			stderr = fp;\
			libgamma_perror("", CONST);\
			stderr = err;\
			fflush(fp);\
			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);\
			}\
			stderr = fp;\
			libgamma_perror("prefix", CONST);\
			stderr = err;\
			fflush(fp);\
			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;
	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);
	}
	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);
	}
	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);
	}
	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));

	stderr = fp;
	libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP);
	stderr = err;
	fflush(fp);
	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);
	}

	stderr = fp;
	libgamma_perror("", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	stderr = err;
	fflush(fp);
	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);
	}

	stderr = fp;
	libgamma_perror("prefix", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	stderr = err;
	fflush(fp);
	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));

	stderr = fp;
	libgamma_perror(NULL, LIBGAMMA_DEVICE_REQUIRE_GROUP);
	stderr = err;
	fflush(fp);
	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);
	}

	stderr = fp;
	libgamma_perror("", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	stderr = err;
	fflush(fp);
	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);
	}

	stderr = fp;
	libgamma_perror("prefix", LIBGAMMA_DEVICE_REQUIRE_GROUP);
	stderr = err;
	fflush(fp);
	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);

	fclose(fp);
	close(fds[0]);
	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 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);
	if ((rr |= r = libgamma_crtc_set_gamma_ramps64(crtc_state, &old_ramps64)))
		libgamma_perror("libgamma_crtc_set_gamma_ramps64", r);
	printf("Done!\n");

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