aboutsummaryrefslogblamecommitdiffstats
path: root/cg-base.c
blob: ad0393774cc1adfd8645d88fbf3a53031a74e9b3 (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 <alloca.h>
#include <errno.h>
#include <inttypes.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>



/**
 * The process's name
 */
const char *argv0 = NULL;

/**
 * The libcoopgamma context
 */
libcoopgamma_context_t cg;

/**
 * The names of the selected CRTC:s
 */
char **crtcs = NULL;

/**
 * Gamma ramp updates for each CRTC
 */
filter_update_t *crtc_updates = NULL;

/**
 * CRTC and monitor information about
 * each selected CRTC and connect monitor
 */
libcoopgamma_crtc_info_t *crtc_info = NULL;

/**
 * The number of selected CRTC:s
 */
size_t crtcs_n = 0;

/**
 * The number of filters
 */
size_t filters_n = 0;


/**
 * Contexts for asynchronous ramp updates
 */
static libcoopgamma_async_context_t *asyncs = NULL;

/**
 * The number of pending receives
 */
static size_t pending_recvs = 0;

/**
 * Whether message must be flushed
 */
static int flush_pending = 0;



/**
 * Data used to sort CRTC:s
 */
struct crtc_sort_data
{
	/**
	 * The gamma ramp type
	 */
	libcoopgamma_depth_t depth;

	/**
	 * Should be 0
	 */
	int __padding;

	/**
	 * The size of the red gamma ramp
	 */
	size_t red_size;

	/**
	 * The size of the green gamma ramp
	 */
	size_t green_size;

	/**
	 * The size of the blue gamma ramp
	 */
	size_t blue_size;

	/**
	 * The index of the CRTC
	 */
	size_t index;
};



/**
 * Compare two strings
 * 
 * @param   a  Return -1 if this string is `NULL` or less than `b`
 * @param   b  Return +1 if this string is less than `a`
 * @return     See `a` and `b`, 0 is returned if `a` and `b` are equal
 */
static int
nulstrcmp(const char *a, const char *b)
{
	return !a ? -1 : strcmp(a, b);
}


/**
 * Compare two instances of `crtc_sort_data`
 * 
 * @param   a_  Return -1 if this one is lower
 * @param   b_  Return +1 if this one is higher
 * @return      See `a_` and `b_`, only -1 or +1 can be returned
 */
static int
crtc_sort_data_cmp(const void *a_, const void *b_)
{
	const struct crtc_sort_data *a = a_;
	const struct crtc_sort_data *b = b_;
	int cmp = memcmp(a, b, sizeof(*a) - sizeof(a->index));
	return cmp ? cmp : a->index < b->index ? -1 : +1;
}


/**
 * Make elements in `crtc_updates` slaves where appropriate
 * 
 * @return  Zero on success, -1 on error
 */
int
make_slaves(void)
{
	struct crtc_sort_data *data;
	size_t i, j, n = 0, master = 0, master_i;

	data = alloca(filters_n * sizeof(*data));
	memset(data, 0, filters_n * sizeof(*data));
	for (i = 0; i < filters_n; i++) {
		if (!crtc_info[crtc_updates[i].crtc].supported)
			continue;

		data[n].depth      = crtc_updates[i].filter.depth;
		data[n].red_size   = crtc_updates[i].filter.ramps.u8.red_size;
		data[n].green_size = crtc_updates[i].filter.ramps.u8.green_size;
		data[n].blue_size  = crtc_updates[i].filter.ramps.u8.blue_size;
		data[n].index      = i;
		n++;
	}

	qsort(data, n, sizeof(*data), crtc_sort_data_cmp);
	if (!n)
		return 0;

	master_i = data[0].index;
	for (i = 1; i < n; i++) {
		if (memcmp(data + i, data + master, sizeof(*data) - sizeof(data->index))) {
			if (master + 1 < i) {
				crtc_updates[master_i].slaves = calloc(i - master, sizeof(size_t));
				if (!crtc_updates[master_i].slaves)
					return -1;
				for (j = 1; master + j < i; j++)
					crtc_updates[master_i].slaves[j - 1] = data[master + j].index;
			}
			master = i;
			master_i = data[master].index;
		} else {
			libcoopgamma_ramps_destroy(&crtc_updates[data[i].index].filter.ramps.u8);
			crtc_updates[data[i].index].master = 0;
			crtc_updates[data[i].index].filter.ramps.u8 = crtc_updates[master_i].filter.ramps.u8;
		}
	}

	if (master + 1 < i) {
		crtc_updates[master_i].slaves = calloc(i - master, sizeof(size_t));
		if (!crtc_updates[master_i].slaves)
			return -1;
		for (j = 1; master + j < i; j++)
			crtc_updates[master_i].slaves[j - 1] = data[master + j].index;
	}

	return 0;
}


/**
 * Update a filter and synchronise calls
 * 
 * @param   index    The index of the CRTC
 * @param   timeout  The number of milliseconds a call to `poll` may block,
 *                   -1 if it may block forever
 * @return           1: Success, no pending synchronisations
 *                   0: Success, with still pending synchronisations
 *                   -1: Error, `errno` set
 *                   -2: Error, `cg.error` set
 * 
 * @throws  EINTR   Call to `poll` was interrupted by a signal
 * @throws  EAGAIN  Call to `poll` timed out
 */
int
update_filter(size_t index, int timeout)
{
	filter_update_t *filter = crtc_updates + index;

	if (!filter->synced || filter->failed)
		abort();

	pending_recvs += 1;

	if (libcoopgamma_set_gamma_send(&filter->filter, &cg, asyncs + index) < 0) {
		switch (errno) {
		case EINTR:
		case EAGAIN:
#if EAGAIN != EWOULDBLOCK
		case EWOULDBLOCK:
#endif
			flush_pending = 1;
			break;
		default:
			return -1;
		}
	}
  
	filter->synced = 0;
	return synchronise(timeout);
}


/**
 * Synchronised calls
 * 
 * @param   timeout  The number of milliseconds a call to `poll` may block,
 *                   -1 if it may block forever
 * @return           1: Success, no pending synchronisations
 *                   0: Success, with still pending synchronisations
 *                   -1: Error, `errno` set
 *                   -2: Error, `cg.error` set
 * 
 * @throws  EINTR   Call to `poll` was interrupted by a signal
 * @throws  EAGAIN  Call to `poll` timed out
 */
int
synchronise(int timeout)
{
	struct pollfd pollfd;
	size_t selected;

	pollfd.fd = cg.fd;
	pollfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;
	if (flush_pending > 0)
		pollfd.events |= POLLOUT;

	pollfd.revents = 0;
	if (poll(&pollfd, (nfds_t)1, timeout) < 0)
		return -1;

	if (pollfd.revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL)) {
		if (libcoopgamma_flush(&cg) < 0)
			goto sync;
		flush_pending = 0;
	}

	if (timeout < 0 && pending_recvs > 0) {
		if (!(pollfd.revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI))) {
			pollfd.revents = 0;
			if (poll(&pollfd, (nfds_t)1, -1) < 0)
				return -1;
		}
	}

 sync:
	if (pollfd.revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP | POLLNVAL)) {
		for (;;) {
			if (libcoopgamma_synchronise(&cg, asyncs, filters_n, &selected) < 0) {
				if (!errno)
					continue;
				goto fail;
			}
			if (crtc_updates[selected].synced)
				continue;
			crtc_updates[selected].synced = 1;
			pending_recvs -= 1;
			if (libcoopgamma_set_gamma_recv(&cg, asyncs + selected) < 0) {
				if (cg.error.server_side) {
					crtc_updates[selected].error = cg.error;
					crtc_updates[selected].failed = 1;
					memset(&cg.error, 0, sizeof(cg.error));
				} else {
					goto cg_fail;
				}
			}
		}
	}

	return !pending_recvs;
cg_fail:
	return -2;
fail:
	switch (errno) {
	case EINTR:
	case EAGAIN:
#if EAGAIN != EWOULDBLOCK
	case EWOULDBLOCK:
#endif
		return !pending_recvs;
	default:
		return -1;
	}
}


/**
 * Initialise the process, specifically
 * reset the signal mask and signal handlers
 * 
 * @return  Zero on success, -1 on error
 */
static int
initialise_proc(void)
{
	sigset_t sigmask;
	int sig;

	for (sig = 1; sig < _NSIG; sig++)
		if (signal(sig, SIG_DFL) == SIG_ERR)
			if (sig == SIGCHLD)
				return -1;

	if (sigemptyset(&sigmask) < 0)
		return -1;
	if (sigprocmask(SIG_SETMASK, &sigmask, NULL) < 0)
		return -1;

	return 0;
}


/**
 * Print, to stdout, a list of all
 * recognised adjustment methods
 * 
 * @return  Zero on success, -1 on error
 */
static int
list_methods(void)
{
	char **list;
	size_t i;

	list = libcoopgamma_get_methods();
	if (!list)
		return -1;
	for (i = 0; list[i]; i++)
		printf("%s\n", list[i]);
	free(list);
	if (fflush(stdout) < 0)
		return -1;

	return 0;
}


/**
 * Print, to stdout, a list of all CRTC:s
 * 
 * A connection to the coopgamma server
 * must have been made
 * 
 * @return  Zero on success, -1 on error, -2
 *          on libcoopgamma error
 */
static int
list_crtcs(void)
{
	char **list;
	size_t i;

	list = libcoopgamma_get_crtcs_sync(&cg);
	if (!list)
		return -2;
	for (i = 0; list[i]; i++)
		printf("%s\n", list[i]);
	free(list);
	if (fflush(stdout) < 0)
		return -1;

	return 0;
}


/**
 * Fill the list of CRTC information
 * 
 * @return  Zero on success, -1 on error, -2
 *          on libcoopgamma error
 */
static int
get_crtc_info(void)
{
	size_t i, unsynced = 0, selected;
	char *synced;
	int need_flush = 0;
	struct pollfd pollfd;

	synced = alloca(crtcs_n * sizeof(*synced));
	memset(synced, 0, crtcs_n * sizeof(*synced));

	i = 0;
	pollfd.fd = cg.fd;
	pollfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;

	while (unsynced > 0 || i < crtcs_n) {
	wait:
		if (i < crtcs_n)
			pollfd.events |= POLLOUT;
		else
			pollfd.events &= ~POLLOUT;
      
		pollfd.revents = 0;
		if (poll(&pollfd, (nfds_t)1, -1) < 0)
			goto fail;
      
		if (pollfd.revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL)) {
			if (need_flush && (libcoopgamma_flush(&cg) < 0))
				goto send_fail;
			need_flush = 0;
			for (; i < crtcs_n; i++)
				if (unsynced++, libcoopgamma_get_gamma_info_send(crtcs[i], &cg, asyncs + i) < 0)
					goto send_fail;
			goto send_done;
		send_fail:
			switch (errno) {
			case EINTR:
			case EAGAIN:
#if EAGAIN != EWOULDBLOCK
			case EWOULDBLOCK:
#endif
				i++;
				need_flush = 1;
				break;
			default:
				goto fail;
			}
		}
	send_done:

		if (!unsynced && i == crtcs_n)
			break;
      
		if (pollfd.revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
			while (unsynced > 0) {
				switch (libcoopgamma_synchronise(&cg, asyncs, i, &selected)) {
				case 0:
					if (synced[selected]) {
						libcoopgamma_skip_message(&cg);
						break;
					}
					synced[selected] = 1;
					unsynced -= 1;
					if (libcoopgamma_get_gamma_info_recv(crtc_info + selected, &cg, asyncs + selected) < 0)
						goto cg_fail;
					break;
				case -1:
					switch (errno)
						{
						case 0:
							break;
						case EINTR:
						case EAGAIN:
#if EAGAIN != EWOULDBLOCK
						case EWOULDBLOCK:
#endif
							goto wait;
						default:
							goto fail;
						}
					break;
				}
			}
		}
	}

	return 0;
fail:
	return -1;
cg_fail:
	return -2;
}


/**
 * -M METHOD
 *     Select adjustment method. If METHOD is "?",
 *     available methods will be printed to stdout.
 * 
 * -S SITE
 *     Select site (display server instance).
 * 
 * -c CRTC
 *     Select CRT controller. If CRTC is "?", CRTC:s
 *     will be printed to stdout.
 *     
 *     This option can be used multiple times. If it
 *     is not used at all, all CRTC:s will be selected.
 * 
 * -p PRIORITY
 *     Select the priority for the filter, this should
 *     be a signed two's-complement integer. If
 *     PRIORITY is "?", the default priority for the
 *     program is printed to stdout.
 * 
 * -R RULE
 *     The rule of the filter, that is, the last part
 *     of the class which is its identifier. If RULE
 *     is "?" the default rule is printed to stdout,
 *     if RULE is "??" the default class is printed
 *     to stdout.
 * 
 * @param   argc  The number of command line arguments
 * @param   argv  The command line arguments
 * @return        0 on success, 1 on error
 */
int
main(int argc, char *argv[])
{
	int stage = 0;
	int dealloc_crtcs = 0;
	int rc = 0;
	char *method = NULL;
	char *site = NULL;
	size_t crtc_i = 0;
	int64_t priority = default_priority;
	char *prio = NULL;
	char *rule = NULL;
	char *class = default_class;
	char **classes = NULL;
	size_t classes_n = 0;
	int explicit_crtcs = 0;
	int have_crtc_q = 0;
	size_t i, filter_i;
	const char *side, *crtc;
	size_t len, n;
	char *args, *arg, *end, *p, opt[3];
	int at_end;

	argv0 = *argv++, argc--;

	if (initialise_proc() < 0)
		goto fail;

	crtcs = alloca((size_t)argc * sizeof(*crtcs));

	for (; *argv; argv++, argc--) {
		args = *argv;
		if (!strcmp(args, "--")) {
			argv++, argc--;
			break;
		}
		opt[0] = *args++;
		opt[2] = '\0';
		if (*opt != '-' && *opt != '+')
			break;
		if (!*args)
			break;
		while (*args) {
			opt[1] = *args++;
			arg = args;
			if ((at_end = !*arg))
				arg = argv[1];
			if (!strcmp(opt, "-M")) {
				if (method || !(method = arg))
					usage();
			} else if (!strcmp(opt, "-S")) {
				if (site || !(site = arg))
					usage();
			} else if (!strcmp(opt, "-c")) {
				if (!arg)
					usage();
				crtcs[crtc_i++] = arg;
				explicit_crtcs = 1;
				if (!have_crtc_q && !strcmp(arg, "?"))
					have_crtc_q = 1;
			} else if (!strcmp(opt, "-p")) {
				if (prio || !(prio = arg))
					usage();
			} else if (!strcmp(opt, "-R")) {
				if (rule || !(rule = arg))
					usage();
			} else {
				switch (handle_opt(opt, arg)) {
				case 0:
					goto next_opt;
				case 1:
					break;
				default:
					goto fail;
				}
			}
			argv += at_end;
			argc -= at_end;
			break;
		next_opt:;
		}
	}

	crtcs_n = crtc_i;
	crtcs[crtc_i] = NULL;
	if (!have_crtc_q && nulstrcmp(method, "?") &&
	    nulstrcmp(rule, "?") && nulstrcmp(rule, "??") &&
	    (default_priority == NO_DEFAULT_PRIORITY || nulstrcmp(prio, "?")))
		if (handle_args(argc, argv, prio) < 0)
			goto fail;

	if (default_priority != NO_DEFAULT_PRIORITY) {
		if (!nulstrcmp(prio, "?")) {
			printf("%" PRIi64 "\n", priority);
			return 0;
		} else if (prio) {
			errno = 0;
			priority = (int64_t)strtoll(prio, &end, 10);
			if (errno || *end || !*prio)
				usage();
		}
	}

	if (!nulstrcmp(rule, "??")) {
		if (!*class_suffixes)
			printf("%s\n", class);
		else
			for (i = 0; class_suffixes[i]; i++)
				printf("%s%s\n", class, class_suffixes[i]);
		return 0;
	} else if (!nulstrcmp(rule, "?")) {
		printf("%s\n", strstr(strstr(class, "::") + 2, "::") + 2);
		return 0;
	} else if (rule) {
		p = strstr(strstr(class, "::") + 2, "::") + 2;
		n = (size_t)(p - class);
		class = alloca(strlen(rule) + n + (size_t)1);
		memcpy(class, default_class, n);
		strcpy(class + n, rule);
		if (strchr(class, '\n')) {
			fprintf(stderr, "%s: LF character is not allowed in the filter's class\n", argv0);
			goto custom_fail;
		}
	}

	if (!nulstrcmp(method, "?")) {
		if (list_methods() < 0)
			goto fail;
		return 0;
	}

	if (libcoopgamma_context_initialise(&cg) < 0)
		goto fail;
	stage++;
	if (libcoopgamma_connect(method, site, &cg) < 0) {
		fprintf(stderr, "%s: server failed to initialise\n", argv0);
		goto custom_fail;
	}
	stage++;

	if (have_crtc_q) {
		switch (list_crtcs()) {
		case 0:
			goto done;
		case -1:
			goto fail;
		default:
			goto cg_fail;
		}
	}

	if (!crtcs_n) {
		crtcs = libcoopgamma_get_crtcs_sync(&cg);
		if (!crtcs)
			goto cg_fail;
		dealloc_crtcs = 1;
		for (; crtcs[crtcs_n]; crtcs_n++);
	}

	if (!crtcs_n) {
		fprintf(stderr, "%s: no CRTC:s are available\n", argv0);
		goto custom_fail;
	}
	
	if (!*class_suffixes) {
		classes = &class;
		classes_n = 1;
	} else {
		len = strlen(class);
		while (class_suffixes[classes_n])
			classes_n++;
		classes = alloca(classes_n * sizeof(*classes));
		for (i = 0; i < classes_n; i++) {
			classes[i] = alloca(len + strlen(class_suffixes[i]) + sizeof(":"));
			stpcpy(stpcpy(stpcpy(classes[i], class), ":"), class_suffixes[i]);
		}
	}
	filters_n = classes_n * crtcs_n;

	crtc_info = alloca(crtcs_n * sizeof(*crtc_info));
	memset(crtc_info, 0, crtcs_n * sizeof(*crtc_info));
	for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++)
		if (libcoopgamma_crtc_info_initialise(crtc_info + crtc_i) < 0)
			goto cg_fail;

	if (libcoopgamma_set_nonblocking(&cg, 1) < 0)
		goto fail;

	asyncs = alloca(filters_n * sizeof(*asyncs));
	memset(asyncs, 0, filters_n * sizeof(*asyncs));
	for (filter_i = 0; filter_i < filters_n; filter_i++)
		if (libcoopgamma_async_context_initialise(asyncs + filter_i) < 0)
			goto fail;

	switch (get_crtc_info()) {
	case 0:
		break;
	case -1:
		goto fail;
	case -2:
		goto cg_fail;
	}

	for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++) {
		if (explicit_crtcs && !crtc_info[crtc_i].supported) {
			fprintf(stderr, "%s: warning: gamma adjustments not supported on CRTC: %s\n",
			        argv0, crtcs[crtc_i]);
		}
		if (!crtc_info[crtc_i].cooperative) {
			fprintf(stderr, "%s: warning: cooperative gamma server not running for CRTC: %s\n",
			        argv0, crtcs[crtc_i]);
		}
	}

	crtc_updates = alloca(filters_n * sizeof(*crtc_updates));
	memset(crtc_updates, 0, filters_n * sizeof(*crtc_updates));
	for (filter_i = i = 0; i < classes_n; i++) {
		for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++, filter_i++) {
			if (libcoopgamma_filter_initialise(&crtc_updates[filter_i].filter) < 0)
				goto fail;
			if (libcoopgamma_error_initialise(&crtc_updates[filter_i].error) < 0)
				goto fail;
			crtc_updates[filter_i].crtc = crtc_i;
			crtc_updates[filter_i].synced = 1;
			crtc_updates[filter_i].failed = 0;
			crtc_updates[filter_i].master = 1;
			crtc_updates[filter_i].slaves = NULL;
			crtc_updates[filter_i].filter.crtc                = crtcs[crtc_i];
			crtc_updates[filter_i].filter.class               = classes[i];
			crtc_updates[filter_i].filter.priority            = priority;
			crtc_updates[filter_i].filter.depth               = crtc_info[crtc_i].depth;
			crtc_updates[filter_i].filter.ramps.u8.red_size   = crtc_info[crtc_i].red_size;
			crtc_updates[filter_i].filter.ramps.u8.green_size = crtc_info[crtc_i].green_size;
			crtc_updates[filter_i].filter.ramps.u8.blue_size  = crtc_info[crtc_i].blue_size;
			switch (crtc_updates[filter_i].filter.depth) {
#define X(CONST, MEMBER, MAX, TYPE)\
			case CONST:\
				libcoopgamma_ramps_initialise(&crtc_updates[filter_i].filter.ramps.MEMBER);\
				libclut_start_over(&crtc_updates[filter_i].filter.ramps.MEMBER, MAX, TYPE, 1, 1, 1);\
				break;
			LIST_DEPTHS
#undef X
			default:
				fprintf(stderr, "%s: internal error: gamma ramp type is unrecognised: %i\n",
				        argv0, crtc_updates[filter_i].filter.depth);
				goto custom_fail;
			}
		}
	}

	switch (start()) {
	case 0:
		break;
	case -1:
		goto fail;
	case -2:
		goto cg_fail;
	case -3:
		goto custom_fail;
	}

	for (filter_i = 0; filter_i < filters_n; filter_i++) {
		if (crtc_updates[filter_i].failed) {
			side = cg.error.server_side ? "server" : "client";
			crtc = crtc_updates[filter_i].filter.crtc;
			if (cg.error.custom) {
				if (cg.error.number && cg.error.description) {
					fprintf(stderr, "%s: %s-side error number %" PRIu64 " for CRTC %s: %s\n",
						argv0, side, cg.error.number, crtc, cg.error.description);
				} else if (cg.error.number) {
					fprintf(stderr, "%s: %s-side error number %" PRIu64 " for CRTC %s\n",
						argv0, side, cg.error.number, crtc);
				} else if (cg.error.description) {
					fprintf(stderr, "%s: %s-side error for CRTC %s: %s\n",
						argv0, side, crtc, cg.error.description);
				}
			} else if (cg.error.description) {
				fprintf(stderr, "%s: %s-side error for CRTC %s: %s\n",
				        argv0, side, crtc, cg.error.description);
			} else {
				fprintf(stderr, "%s: %s-side error for CRTC %s: %s\n",
				        argv0, side, crtc, strerror((int)cg.error.number));
			}
		}
	}

done:
	if (dealloc_crtcs)
		free(crtcs);
	if (crtc_info)
		for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++)
			libcoopgamma_crtc_info_destroy(crtc_info + crtc_i);
	if (asyncs)
		for (filter_i = 0; filter_i < filters_n; filter_i++)
			libcoopgamma_async_context_destroy(asyncs + filter_i);
	if (stage >= 1)
		libcoopgamma_context_destroy(&cg, stage >= 2);
	if (crtc_updates) {
		for (filter_i = 0; filter_i < filters_n; filter_i++) {
			if (!crtc_updates[filter_i].master) {
				memset(&crtc_updates[filter_i].filter.ramps.u8, 0,
				       sizeof(crtc_updates[filter_i].filter.ramps.u8));
			}
			crtc_updates[filter_i].filter.crtc = NULL;
			crtc_updates[filter_i].filter.class = NULL;
			libcoopgamma_filter_destroy(&crtc_updates[filter_i].filter);
			libcoopgamma_error_destroy(&crtc_updates[filter_i].error);
			free(crtc_updates[filter_i].slaves);
		}
	}
	return rc;

custom_fail:
	rc = 1;
	goto done;

fail:
	rc = 1;
	if (errno)
		perror(argv0);
	goto done;

cg_fail:
	rc = 1;
	side = cg.error.server_side ? "server" : "client";
	if (cg.error.custom) {
		if (cg.error.number && cg.error.description) {
			fprintf(stderr, "%s: %s-side error number %" PRIu64 ": %s\n",
			        argv0, side, cg.error.number, cg.error.description);
		} else if (cg.error.number) {
			fprintf(stderr, "%s: %s-side error number %" PRIu64 "\n", argv0, side, cg.error.number);
		} else if (cg.error.description) {
			fprintf(stderr, "%s: %s-side error: %s\n", argv0, side, cg.error.description);
		}
	} else if (cg.error.description) {
		fprintf(stderr, "%s: %s-side error: %s\n", argv0, side, cg.error.description);
	} else {
		fprintf(stderr, "%s: %s-side error: %s\n", argv0, side, strerror((int)cg.error.number));
	}
	goto done;
}