aboutsummaryrefslogblamecommitdiffstats
path: root/cg-icc.c
blob: 3a29fbc74ee83ff22f951ddd44d1a2d7ce930b2b (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                         



                    



                     

                   




                   


                                                        





                                                                   
                            




                                                                
                            
 


                                         
                       
 









                                              
                                                    
 


                 
                                                                
 


   









                                                    

                                          
                                       













                                                       
                                            




                                            
                                           








                                              
                                   




                                                  
                                     



   

                                   

           
 




                                                                         



   




                                              

                
 



















                                                                



   












                                                                    

                                
 

















                                           



   





                                                              

                                           
 































































                                                                                                                   
         


















                                            



   


                                         



                                                              
   

                                               
 













































                                                                       


 





                                                         

                                        
 









                                                                                              


 





                                                         

                                        
 





                                                                                              


 





                                                         

                                        
 



                                                                                             


 





                                                         

                                       
 
                                     


 






                                                                        

                                                      
 







                                                                       


 











                                                                               

                                                                                                           
 




                                                                                   
  






































































































































































































                                                                                                                                  
                 
         

                  

 











                                                                             

                                                                                    
 













                                                                                       
         


















                                                             
         










                                                  








                                                 

                         
 





                                                                









                                                       

                                                                                                         
 
                                
                                    






















                                                                                                                
        


                        










                                                  

           
 






























































                                                                                                       
         



















                                                                                             
         








                                                     
                                   










                                                                     
         



                           
 
/* See LICENSE file for copyright and license details. */
#include "cg-base.h"

#include <libclut.h>

#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>



/* Note, that EDID:s are at least 256 hexadecimals long,
 * sometimes 512 hexadecimals long, and a filename can
 * often only be 255 characters long. */



/**
 * Magic number for dual-byte precision lookup table based profiles
 */
#define MLUT_TAG 0x6D4C5554L

/**
 * Magic number for gamma–brightness–contrast based profiles
 * and for variable precision lookup table profiles
 */
#define VCGT_TAG 0x76636774L

/**
 * The filename of the configuration file
 */
#define ICCTAB "icctab"



/**
 * The default filter priority for the program
 */
const int64_t default_priority = 0;

/**
 * The default class for the program
 */
char default_class[] = PKGNAME "::cg-icc::standard";

/**
 * Class suffixes
 */
const char *const *class_suffixes = (const char *const[]){NULL};



/**
 * -d: keep process alive and remove filter on death
 */
static int dflag = 0;

/**
 * -x: remove filter rather than adding it
 */
static int xflag = 0;

/**
 * The panhame of the selected ICC profile
 */
static const char *icc_pathname = NULL;

/**
 * Gamma ramps loaded from `icc_pathname`
 */
static libcoopgamma_ramps_t uniramps;

/**
 * The datatype of the stops in the ramps of `uniramps`
 */
static libcoopgamma_depth_t unidepth = 0;

/**
 * Parsed ICC profiles for each CRTC
 */
static libcoopgamma_ramps_t *rampses = NULL;

/**
 * The datatype of the stops in the ramps of
 * corresponding element in `rampses`
 */
static libcoopgamma_depth_t *depths = NULL;

/**
 * File descriptor for configuration directory
 */
static int confdirfd = -1;

/**
 * List of CRTC:s
 */
static char **crtc_icc_keys = NULL;

/**
 * List of ICC profile pathnames for corresponding
 * CRTC in `crtc_icc_keys`
 */
static char **crtc_icc_values = NULL;



/**
 * Print usage information and exit
 */
void
usage(void)
{
	fprintf(stderr,
	        "usage: %s [-M method] [-S site] [-c crtc]... [-R rule] "
	        "(-x | [-p priority] [-d] [file])\n",
	        argv0);
	exit(1);
}


/**
 * Perform cleanup so valgrind output is clean
 * 
 * @param   ret  The value to return
 * @return       `ret` is returned as is
 */
static int
cleanup(int ret)
{
	int saved_errno = errno;
	size_t i;
	libcoopgamma_ramps_destroy(&uniramps);
	if (confdirfd >= 0)
		close(confdirfd);
	if (rampses)
		for (i = 0; i < crtcs_n; i++)
			libcoopgamma_ramps_destroy(rampses + i);
	free(rampses);
	free(depths);
	if (crtc_icc_keys)
		for (i = 0; crtc_icc_keys[i]; i++)
			free(crtc_icc_keys[i]);
	free(crtc_icc_keys);
	if (crtc_icc_values)
		for (i = 0; crtc_icc_values[i]; i++)
			free(crtc_icc_values[i]);
	free(crtc_icc_values);
	errno = saved_errno;
	return ret;
}


/**
 * Handle a command line option
 * 
 * @param   opt  The option, it is a NUL-terminate two-character
 *               string starting with either '-' or '+', if the
 *               argument is not recognised, call `usage`. This
 *               string will not be "-M", "-S", "-c", "-p", or "-R".
 * @param   arg  The argument associated with `opt`,
 *               `NULL` there is no next argument, if this
 *               parameter is `NULL` but needed, call `usage`
 * @return       0 if `arg` was not used,
 *               1 if `arg` was used,
 *               -1 on error
 */
int
handle_opt(char *opt, char *arg)
{
	if (opt[0] == '-') {
		switch (opt[1]) {
		case 'd':
			if (dflag || xflag)
				usage();
			dflag = 1;
			break;
		case 'x':
			if (xflag || dflag)
				usage();
			xflag = 1;
			break;
		default:
			usage();
		}
	}
	return 0;
	(void) arg;
}


/**
 * Populate `crtc_icc_keys` and `crtc_icc_value`
 * 
 * @path    fd       File descriptor for the ICC profile table
 * @path    dirname  The dirname of the ICC profile table
 * @return           Zero on success, -1 on error
 */
static int
load_icc_table(int fd, const char *dirname)
{
	FILE *fp;
	ssize_t len;
	size_t lineno = 1, size = 0;
	char *p, *q, *line = NULL;
	int saved_errno;
	size_t ptr = 0, siz = 0;
	void *new;
	size_t dirname_len = strlen(dirname);
	fp = fdopen(fd, "rb");
	if (!fp)
		return -1;
	for (; (len = getline(&line, &size, fp)) >= 0; lineno++) {
		if (len && line[len - 1] == '\n')
			line[--len] = '\0';
		p = line + strspn(line, " \t");
		if (!*p || *p == '#')
			continue;
		q = p + strspn(p, "0123456789abcdefABCDEF");
		if (*q != ' ' && *q != '\t') {
			fprintf(stderr, "%s: warning: line %zu is malformated in %s/%s\n",
			        argv0, lineno, dirname, ICCTAB);
			continue;
		}
		*q = '\0';
		if ((size_t)(q - p) != 256) {
			fprintf(stderr, "%s: warning: EDID on line %zu in %s/%s looks to be of wrong length: %s\n",
			        argv0, lineno, dirname, ICCTAB, p);
		}
		q++;
		q += strspn(p, " \t");
		if (!*q) {
			fprintf(stderr, "%s: warning: line %zu is malformated in %s/%s\n",
			        argv0, lineno, dirname, ICCTAB);
			continue;
		}
		if (strchr(" \t", strchr(q, '\0')[-1])) {
			fprintf(stderr, "%s: warning: filename on line %zu in %s/%s ends with white space: %s\n",
			        argv0, lineno, dirname, ICCTAB, q);
		}
		if (ptr == siz) {
			new = realloc(crtc_icc_keys, (siz + 5) * sizeof(*crtc_icc_keys));
			if (!new)
				goto fail;
			crtc_icc_keys = new;
			new = realloc(crtc_icc_values, (siz + 5) * sizeof(*crtc_icc_values));
			if (!new)
				goto fail;
			crtc_icc_values = new;
			siz += 4;
		}
		crtc_icc_values[ptr] = malloc((*q == '/' ? 1 : dirname_len + sizeof("/")) + strlen(q));
		if (!crtc_icc_values[ptr])
			goto fail;
		if (*q == '/')
			strcpy(crtc_icc_values[ptr], q);
		else
			stpcpy(stpcpy(stpcpy(crtc_icc_values[ptr], dirname), "/"), q);
		crtc_icc_keys[ptr] = malloc(strlen(p) + 1);
		if (!crtc_icc_keys[ptr]) {
			ptr++;
			goto fail;
		}
		strcpy(crtc_icc_keys[ptr], p);
		ptr++;
	}
	if (ferror(fp))
		goto fail;
	if (crtc_icc_keys)
		crtc_icc_keys[ptr] = NULL;
	if (crtc_icc_values)
		crtc_icc_values[ptr] = NULL;
	fclose(fp);
	free(line);
	return 0;
fail:
	saved_errno = errno;
	if (crtc_icc_keys)
		crtc_icc_keys[ptr] = NULL;
	if (crtc_icc_values)
		crtc_icc_values[ptr] = NULL;
	fclose(fp);
	free(line);
	errno = saved_errno;
	return -1;
}


/**
 * This function is called after the last
 * call to `handle_opt`
 * 
 * @param   argc  The number of unparsed arguments
 * @param   argv  `NULL` terminated list of unparsed arguments
 * @param   prio  The argument associated with the "-p" option
 * @return        Zero on success, -1 on error
 */
int
handle_args(int argc, char *argv[], char *prio)
{
	struct passwd *pw;
	char *path = NULL;
	int saved_errno;
	int fd = -1, q = xflag + dflag;
	if ((q > 1) || (xflag && (argc > 0 || prio)) || argc > 1)
		usage();
	icc_pathname = *argv;
	memset(&uniramps, 0, sizeof(uniramps));
	if (!xflag && !icc_pathname) {
		pw = getpwuid(getuid());
		if (!pw || !pw->pw_dir)
			goto fail;

		path = malloc(strlen(pw->pw_dir) + sizeof("/.config"));
		if (!path)
			goto fail;

		sprintf(path, "%s/.config", pw->pw_dir);

		if (access(path, F_OK) < 0)
			sprintf(path, "/etc");

		confdirfd = open(path, O_DIRECTORY);
		if (confdirfd < 0)
			goto fail;

		fd = openat(confdirfd, ICCTAB, O_RDONLY);
		if (fd < 0)
			goto fail;

		if (load_icc_table(fd, path) < 0)
			goto fail;

		free(path);
		path = NULL;
		close(fd), fd = -1;
	}
	return 0;
fail:
	saved_errno = errno;
	free(path);
	path = NULL;
	if (fd >= 0)
		close(fd);
	errno = saved_errno;
	return cleanup(-1);
}


/**
 * Read an unsigned 64-bit integer
 * 
 * @param   content  The beginning of the encoded integer
 * @return           The integer, decoded
 */
static uint64_t
icc_uint64(const char *restrict content)
{
	uint64_t rc;
	rc =                 (uint64_t)(unsigned char)(content[0]),  rc = (uint64_t)(rc << 8);
	rc = (uint64_t)(rc | (uint64_t)(unsigned char)(content[1])), rc = (uint64_t)(rc << 8);
	rc = (uint64_t)(rc | (uint64_t)(unsigned char)(content[2])), rc = (uint64_t)(rc << 8);
	rc = (uint64_t)(rc | (uint64_t)(unsigned char)(content[3])), rc = (uint64_t)(rc << 8);
	rc = (uint64_t)(rc | (uint64_t)(unsigned char)(content[4])), rc = (uint64_t)(rc << 8);
	rc = (uint64_t)(rc | (uint64_t)(unsigned char)(content[5])), rc = (uint64_t)(rc << 8);
	rc = (uint64_t)(rc | (uint64_t)(unsigned char)(content[6])), rc = (uint64_t)(rc << 8);
	rc = (uint64_t)(rc | (uint64_t)(unsigned char)(content[7]));
	return rc;
}


/**
 * Read an unsigned 32-bit integer
 * 
 * @param   content  The beginning of the encoded integer
 * @return           The integer, decoded
 */
static uint32_t
icc_uint32(const char *restrict content)
{
	uint32_t rc;
	rc =                 (uint32_t)(unsigned char)(content[0]),  rc = (uint32_t)(rc << 8);
	rc = (uint32_t)(rc | (uint32_t)(unsigned char)(content[1])), rc = (uint32_t)(rc << 8);
	rc = (uint32_t)(rc | (uint32_t)(unsigned char)(content[2])), rc = (uint32_t)(rc << 8);
	rc = (uint32_t)(rc | (uint32_t)(unsigned char)(content[3]));
	return rc;
}


/**
 * Read an unsigned 16-bit integer
 * 
 * @param   content  The beginning of the encoded integer
 * @return           The integer, decoded
 */
static uint16_t
icc_uint16(const char *restrict content)
{
	uint16_t rc;
	rc =                 (uint16_t)(unsigned char)(content[0]), rc = (uint16_t)(rc << 8);
	rc = (uint16_t)(rc | (uint16_t)(unsigned char)(content[1]));
	return rc;
}


/**
 * Read an unsigned 8-bit integer
 * 
 * @param   content  The beginning of the encoded integer
 * @return           The integer, decoded
 */
static uint8_t
icc_uint8(const char *restrict content)
{
	return (uint8_t)(content[0]);
}


/**
 * Read a floating-point value
 * 
 * @param   content  The beginning of the encoded value
 * @param   width    The number of bytes with which the value is encoded
 * @return           The value, decoded
 */
static double
icc_double(const char *restrict content, size_t width)
{
	double ret = 0;
	size_t i;
	for (i = 0; i < width; i++) {
		ret /= 256;
		ret += (double)(unsigned char)(content[width - 1 - i]);
	}
	ret /= 255;
	return ret;
}


/**
 * Parse an ICC profile
 * 
 * @param   content  The content of the ICC profile file
 * @param   n        The byte-size of `content`
 * @param   ramps    Output parameter for the filter stored in the ICC profile,
 *                   `.red_size`, `.green_size`, `.blue_size` should already be
 *                   set (these values can however be modified.)
 * @param   depth    Output parameter for ramps stop value type
 * @return           Zero on success, -1 on error, -2 if no usable data is
 *                   available in the profile.
 */
static int
parse_icc(const char *restrict content, size_t n, libcoopgamma_ramps_t *ramps, libcoopgamma_depth_t *depth)
{
	uint32_t tag_name, tag_offset, tag_size, gamma_type;
	size_t n_channels, n_entries, entry_size;
	double r_gamma, r_min, r_max, g_gamma, g_min, g_max, b_gamma, b_min, b_max;
	uint32_t i_tag, n_tags;
	size_t i, ptr = 0, xptr;
  
	/* Skip header */
	if (n - ptr < 128)
		return -2;
	ptr += 128;

	/* Get the number of tags */
	if (n - ptr < 4)
		return -2;
	n_tags = icc_uint32(content + ptr), ptr += 4;

	for (i_tag = 0, xptr = ptr; i_tag < n_tags; i_tag++, ptr = xptr) {
		/* Get profile encoding type, offset to the profile and the encoding size of its data */
		if (n - ptr < 12)
			return -2;
		tag_name   = icc_uint32(content + ptr), ptr += 4;
		tag_offset = icc_uint32(content + ptr), ptr += 4;
		tag_size   = icc_uint32(content + ptr), ptr += 4;
		xptr = ptr;

		/* Jump to the profile data */
		if (tag_offset > INT32_MAX - tag_size)
			return -2;
		if (tag_offset + tag_size > n)
			return -2;
		ptr = tag_offset;

		if (tag_name == MLUT_TAG) {
			/* The profile is encododed as an dual-byte precision lookup table */

			/* Initialise ramps */
			*depth = LIBCOOPGAMMA_UINT16;
			ramps->u16.red_size   = 256;
			ramps->u16.green_size = 256;
			ramps->u16.blue_size  = 256;
			if (libcoopgamma_ramps_initialise(&ramps->u16) < 0)
				return -1;

			/* Get the lookup table */
			if (n - ptr < 3 * 256 * 2)
				continue;
			for (i = 0; i < 256; i++, ptr += 2)
				ramps->u16.red[i]   = icc_uint16(content + ptr);
			for (i = 0; i < 256; i++, ptr += 2)
				ramps->u16.green[i] = icc_uint16(content + ptr);
			for (i = 0; i < 256; i++, ptr += 2)
				ramps->u16.blue[i]  = icc_uint16(content + ptr);

			return 0;
		} else if (tag_name == VCGT_TAG) {
			/* The profile is encoded as with gamma, brightness and contrast values
			 * or as a variable precision lookup table profile */

			/* VCGT profiles starts where their magic number */
			if (n - ptr < 4)
				continue;
			tag_name = icc_uint32(content + ptr), ptr += 4;
			if (tag_name != VCGT_TAG)
				continue;

			/* Skip four bytes */
			if (n - ptr < 4)
				continue;
			ptr += 4;

			/* Get the actual encoding type */
			if (n - ptr < 4)
				continue;
			gamma_type = icc_uint32(content + ptr), ptr += 4;

			if (!gamma_type) {
				/* The profile is encoded as a variable precision lookup table */

				/* Get metadata */
				if (n - ptr < 3 * 4)
					continue;
				n_channels = (size_t)icc_uint16(content + ptr), ptr += 2;
				n_entries  = (size_t)icc_uint16(content + ptr), ptr += 2;
				entry_size = (size_t)icc_uint16(content + ptr), ptr += 2;
				if (tag_size == 1584)
					n_channels = 3, n_entries = 256, entry_size = 2;
				if (n_channels != 3)
					/* Assuming sRGB, can only be an correct assumption if there are exactly three channels */
					continue;

				/* Check data availability */
				if (n_channels > SIZE_MAX / n_entries)
					continue;
				if (entry_size > SIZE_MAX / (n_entries * n_channels))
					continue;
				if (n - ptr < n_channels * n_entries * entry_size)
					continue;

				/* Initialise ramps */
				ramps->u8.red_size   = n_entries;
				ramps->u8.green_size = n_entries;
				ramps->u8.blue_size  = n_entries;
				switch (entry_size) {
				case 1:
					*depth = LIBCOOPGAMMA_UINT8;
					if (libcoopgamma_ramps_initialise(&ramps->u8) < 0)
						return -1;
					break;
				case 2:
					*depth = LIBCOOPGAMMA_UINT16;
					if (libcoopgamma_ramps_initialise(&ramps->u16) < 0)
						return -1;
					break;
				case 4:
					*depth = LIBCOOPGAMMA_UINT32;
					if (libcoopgamma_ramps_initialise(&ramps->u32) < 0)
						return -1;
					break;
				case 8:
					*depth = LIBCOOPGAMMA_UINT64;
					if (libcoopgamma_ramps_initialise(&ramps->u64) < 0)
						return -1;
					break;
				default:
					*depth = LIBCOOPGAMMA_DOUBLE;
					if (libcoopgamma_ramps_initialise(&ramps->d) < 0)
						return -1;
					break;
				}

				/* Get the lookup table */
				switch (*depth) {
				case LIBCOOPGAMMA_UINT8:
					for (i = 0; i < ramps->u8.red_size;   i++, ptr += 1)
						ramps->u8.red[i]   = icc_uint8(content + ptr);
					for (i = 0; i < ramps->u8.green_size; i++, ptr += 1)
						ramps->u8.green[i] = icc_uint8(content + ptr);
					for (i = 0; i < ramps->u8.blue_size;  i++, ptr += 1)
						ramps->u8.blue[i]  = icc_uint8(content + ptr);
					break;
				case LIBCOOPGAMMA_UINT16:
					for (i = 0; i < ramps->u16.red_size;   i++, ptr += 2)
						ramps->u16.red[i]   = icc_uint16(content + ptr);
					for (i = 0; i < ramps->u16.green_size; i++, ptr += 2)
						ramps->u16.green[i] = icc_uint16(content + ptr);
					for (i = 0; i < ramps->u16.blue_size;  i++, ptr += 2)
						ramps->u16.blue[i]  = icc_uint16(content + ptr);
					break;
				case LIBCOOPGAMMA_UINT32:
					for (i = 0; i < ramps->u32.red_size;   i++, ptr += 4)
						ramps->u32.red[i]   = icc_uint32(content + ptr);
					for (i = 0; i < ramps->u32.green_size; i++, ptr += 4)
						ramps->u32.green[i] = icc_uint32(content + ptr);
					for (i = 0; i < ramps->u32.blue_size;  i++, ptr += 4)
						ramps->u32.blue[i]  = icc_uint32(content + ptr);
					break;
				case LIBCOOPGAMMA_UINT64:
					for (i = 0; i < ramps->u64.red_size;   i++, ptr += 8)
						ramps->u64.red[i]   = icc_uint64(content + ptr);
					for (i = 0; i < ramps->u64.green_size; i++, ptr += 8)
						ramps->u64.green[i] = icc_uint64(content + ptr);
					for (i = 0; i < ramps->u64.blue_size;  i++, ptr += 8)
						ramps->u64.blue[i]  = icc_uint64(content + ptr);
					break;
				case LIBCOOPGAMMA_FLOAT:
				case LIBCOOPGAMMA_DOUBLE:
				default:
					for (i = 0; i < ramps->d.red_size;   i++, ptr += entry_size)
						ramps->d.red[i]   = icc_double(content + ptr, entry_size);
					for (i = 0; i < ramps->d.green_size; i++, ptr += entry_size)
						ramps->d.green[i] = icc_double(content + ptr, entry_size);
					for (i = 0; i < ramps->d.blue_size;  i++, ptr += entry_size)
						ramps->d.blue[i]  = icc_double(content + ptr, entry_size);
					break;
				}

				return 0;
			} else if (gamma_type == 1) {
				/* The profile is encoded with gamma, brightness and contrast values */

				/* Get the gamma, brightness and contrast */
				if (n - ptr < 9 * 4)
					continue;
				r_gamma = icc_uint32(content + ptr), r_gamma /= 65536L, ptr += 4;
				r_min   = icc_uint32(content + ptr), r_min   /= 65536L, ptr += 4;
				r_max   = icc_uint32(content + ptr), r_max   /= 65536L, ptr += 4;
				g_gamma = icc_uint32(content + ptr), g_gamma /= 65536L, ptr += 4;
				g_min   = icc_uint32(content + ptr), g_min   /= 65536L, ptr += 4;
				g_max   = icc_uint32(content + ptr), g_max   /= 65536L, ptr += 4;
				b_gamma = icc_uint32(content + ptr), b_gamma /= 65536L, ptr += 4;
				b_min   = icc_uint32(content + ptr), b_min   /= 65536L, ptr += 4;
				b_max   = icc_uint32(content + ptr), b_max   /= 65536L, ptr += 4;

				/* Initialise ramps */
				*depth = LIBCOOPGAMMA_DOUBLE;
				if (libcoopgamma_ramps_initialise(&ramps->d) < 0)
					return -1;

				/* Set ramps */
				libclut_start_over(&ramps->d, (double)1, double, 1, 1, 1);
				libclut_gamma(&ramps->d, (double)1, double, r_gamma, g_gamma, b_gamma);
				libclut_rgb_limits(&ramps->d, (double)1, double, r_min, r_max, g_min, g_max, b_min, b_max);

				return 0;
			}
		}
	}

	return -2;
}


/**
 * Load an ICC profile
 * 
 * @param   file   The ICC-profile file
 * @param   ramps  Output parameter for the filter stored in the ICC profile,
 *                 `.red_size`, `.green_size`, `.blue_size` should already be
 *                 set (these values can however be modified.)
 * @param   depth  Output parameter for ramps stop value type
 * @return         Zero on success, -1 on error, -2 if no usable data is
 *                 available in the profile.
 */
static int
load_icc(const char *file, libcoopgamma_ramps_t *ramps, libcoopgamma_depth_t *depth)
{
	char *content = NULL;
	size_t ptr = 0, size = 0;
	ssize_t got;
	int fd = -1, r = -1, saved_errno;
	size_t new_size;
	void *new;

	fd = open(file, O_RDONLY);
	if (fd < 0) {
		if (errno == ENOENT) {
			fprintf(stderr, "%s: %s: %s\n", argv0, strerror(ENOENT), file);
			errno = 0;
		}
		goto fail;
	}

	for (;;) {
		if (ptr == size) {
			new_size = size ? (size << 1) : 4098;
			new = realloc(content, new_size);
			if (!new)
				goto fail;
			content = new;
			size = new_size;
		}
		got = read(fd, content + ptr, size - ptr);
		if (got < 0) {
			if (errno == EINTR)
				continue;
			goto fail;
		}
		if (!got)
			break;
		ptr += (size_t)got;
	}

	close(fd), fd = -1;

	r = parse_icc(content, ptr, ramps, depth);
fail:
	saved_errno = errno;
	if (fd >= 0)
		close(fd);
	free(content);
	errno = saved_errno;
	return r;
}


/**
 * Get the pathname of the ICC profile for a CRTC
 * 
 * @param   crtc  The CRTC name
 * @return        The ICC profile file
 */
static const char *
get_icc(const char *crtc)
{
	size_t i;
	if (crtc_icc_keys)
		for (i = 0; crtc_icc_keys[i]; i++)
			if (!strcasecmp(crtc, crtc_icc_keys[i]))
				return crtc_icc_values[i];
	return NULL;
}


/**
 * Fill a filter
 * 
 * @param  filter  The filter to fill
 * @param  ramps   The prototype filter
 * @param  depth   The prototype filter's stop datatype
 */
static void
fill_filter(libcoopgamma_filter_t *filter, const libcoopgamma_ramps_t *ramps, libcoopgamma_depth_t depth)
{
	switch (filter->depth) {
#define X(CONST, MEMBER, MAX, TYPE)\
	case CONST:\
		switch (depth) {\
		case LIBCOOPGAMMA_UINT8:\
			libclut_translate(&filter->ramps.MEMBER, MAX, TYPE, &ramps->u8, UINT8_MAX, uint8_t);\
			break;\
		case LIBCOOPGAMMA_UINT16:\
			libclut_translate(&filter->ramps.MEMBER, MAX, TYPE, &ramps->u16, UINT16_MAX, uint16_t);\
			break;\
		case LIBCOOPGAMMA_UINT32:\
			libclut_translate(&filter->ramps.MEMBER, MAX, TYPE, &ramps->u32, UINT32_MAX, uint32_t);\
			break;\
		case LIBCOOPGAMMA_UINT64:\
			libclut_translate(&filter->ramps.MEMBER, MAX, TYPE, &ramps->u64, UINT64_MAX, uint64_t);\
			break;\
		case LIBCOOPGAMMA_FLOAT:\
			libclut_translate(&filter->ramps.MEMBER, MAX, TYPE, &ramps->f, (float)1, float);\
			break;\
		case LIBCOOPGAMMA_DOUBLE:\
			libclut_translate(&filter->ramps.MEMBER, MAX, TYPE, &ramps->d, (double)1, double);\
			break;\
		}\
		break;
		LIST_DEPTHS
#undef X
	default:
		abort();
	}
}


/**
 * The main function for the program-specific code
 * 
 * @return  0: Success
 *          -1: Error, `errno` set
 *          -2: Error, `cg.error` set
 *          -3: Error, message already printed
 */
int
start(void)
{
	int r;
	size_t i, j;
	const char *path;

	if (xflag)
		for (i = 0; i < crtcs_n; i++)
			crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_REMOVE;
	else if (dflag)
		for (i = 0; i < crtcs_n; i++)
			crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_DEATH;
	else
		for (i = 0; i < crtcs_n; i++)
			crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL;

	if (!xflag && !icc_pathname)
		if ((r = make_slaves()) < 0)
			return cleanup(r);

	if (icc_pathname) {
		uniramps.u8.red_size = uniramps.u8.green_size = uniramps.u8.blue_size = 1;
		for (i = 0; i < crtcs_n; i++) {
			if (uniramps.u8.red_size   < crtc_updates[i].filter.ramps.u8.red_size)
				uniramps.  u8.red_size   = crtc_updates[i].filter.ramps.u8.red_size;
			if (uniramps.u8.green_size < crtc_updates[i].filter.ramps.u8.green_size)
				uniramps.  u8.green_size = crtc_updates[i].filter.ramps.u8.green_size;
			if (uniramps.u8.blue_size  < crtc_updates[i].filter.ramps.u8.blue_size)
				uniramps.  u8.blue_size  = crtc_updates[i].filter.ramps.u8.blue_size;
		}
		switch (load_icc(icc_pathname, &uniramps, &unidepth)) {
		case 0:
			break;
		case -1:
			return cleanup(-1);
		case -2:
			fprintf(stderr, "%s: unusable ICC profile: %s\n", argv0, icc_pathname);
			return cleanup(-3);
		}
	} else {
		rampses = calloc(crtcs_n, sizeof(*rampses));
		if (!rampses)
			return cleanup(-1);
		depths = malloc(crtcs_n * sizeof(*depths));
		if (!depths)
			return cleanup(-1);
		for (i = 0; i < crtcs_n; i++) {
			rampses[i].u8.red_size   = crtc_updates[i].filter.ramps.u8.red_size;
			rampses[i].u8.green_size = crtc_updates[i].filter.ramps.u8.green_size;
			rampses[i].u8.blue_size  = crtc_updates[i].filter.ramps.u8.blue_size;
			path = get_icc(crtc_updates[i].filter.crtc);
			if (!path) {
				/* TODO remove CRTC */
			} else {
				switch (load_icc(path, rampses + i, depths + i)) {
				case 0:
					break;
				case -1:
					return cleanup(-1);
				case -2:
					fprintf(stderr, "%s: unusable ICC profile: %s\n", argv0, path);
					return cleanup(-3);
				}
			}
		}
	}

	for (i = 0, r = 1; i < crtcs_n; i++) {
		if (!crtc_updates[i].master || !crtc_info[i].supported)
			continue;
		if (!xflag) {
			if (icc_pathname)
				fill_filter(&crtc_updates[i].filter, &uniramps, unidepth);
			else
				fill_filter(&crtc_updates[i].filter, rampses + i, depths[i]);
		}
		r = update_filter(i, 0);
		if (r == -2 || (r == -1 && errno != EAGAIN))
			return cleanup(r);
		if (crtc_updates[i].slaves) {
			for (j = 0; crtc_updates[i].slaves[j]; j++) {
				r = update_filter(crtc_updates[i].slaves[j], 0);
				if (r == -2 || (r == -1 && errno != EAGAIN))
					return cleanup(r);
			}
		}
	}

	while (r != 1)
		if ((r = synchronise(-1)) < 0)
			return cleanup(r);

	if (!dflag)
		return cleanup(0);

	if (libcoopgamma_set_nonblocking(&cg, 0) < 0)
		return cleanup(-1);
	for (;;) {
		if (libcoopgamma_synchronise(&cg, NULL, 0, &j) < 0) {
			switch (errno) {
			case 0:
				break;
			case ENOTRECOVERABLE:
				goto enotrecoverable;
			default:
				return cleanup(-1);
			}
		}
	}

enotrecoverable:
	pause();
	return cleanup(-1);
}