aboutsummaryrefslogblamecommitdiffstats
path: root/radharc.c
blob: 0805b33019b6f1d7e39402a9c63610285bfd8549 (plain) (tree)
1
2
3
4
5
6




                                                         
                   






                    







                                        
















































































                                                                














                                                      



















                                                       





















                                                      
 






                                   
                                                                                       




                                                                                                     
 



















                                                                    
 








































                                                                         
                                                             







                                                                 
                                                            
















                                                                                               






                                                                            













                                  
 











                                                              

                                                                                      





                        
 














                                                                                          
                                                                                                  
                                                                                              
                                                                                                    







                        
 

















                                                     
                                                                                          
                                 



                                                                       



                                                                          


                                                                                        







                                                                            

                                                      



                 
 
   



























                                                                       










                                                                    





                                                                







                                          

















































































































                                                                                                          

























                                                  























                                                    









                                                  
                                        
                 
                                  
                                             

                                            
                         


                                      




                                   





                                        



                                         










                                                                                     
                   

                                            
 











                                                               





                                          

        
                        
                              






                                                                                                
                                     







                                                                   
                                   
                                      








                                                                     


                                                 
                                 
                                                                           
                                         

                                                                                                                                   



                                                          


                                                                                 
                                  
                 

                                              


                             
             

                   

         
                  
                                      
                                      
                                             



                                                                                             




                                              

                                                                   
                                 
                                                                   
                                                                         






                                                          



                                                                                                      
                                                                                                               






                                              
 
         


                               





                                                                                                
                                      

                                                   










                                                                            
 
                                                                                                                 
                                                                         








                                                                                 

                                              
                             
         
 
              

                   

          

                                                                      


















                                                                                      































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

#include <sys/timerfd.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <libclut.h>
#include <libred.h>


#if 0
# define IF_LINEARISING(...) __VA_ARGS__
#else
# define IF_LINEARISING(...) ((void)0)
#endif


/**
 * The default filter priority for the program
 */
const int64_t default_priority = (int64_t)7 << 61;

/**
 * The default class for the program
 */
char default_class[] = "radharc::radharc::standard";

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

/**
 * The effect fade-in time, in centiseconds
 */
static unsigned long int fade_in_cs = 0;

/**
 * The effect fade-out time, in centiseconds
 */
static unsigned long int fade_out_cs = 0;

/**
 * The highest elevation of the Sun where the lowest
 * colour temperature is applied
 */
static double low_elev = -6;

/**
 * The lowest colour temperature that may be applied
 */
static double low_temp = 2500;

/**
 * The lowest elevation of the Sun where the highest
 * colour temperature is applied
 */
static double high_elev = 3;

/**
 * The highest colour temperature that may be applied
 */
static double high_temp = 5000;

/**
 * The temperature choosen with the -f flag, negative if none
 */
static double choosen_temperature = -1;

/**
 * The latitude coordiate of the GPS coordiates of
 * the user's location
 */
static double latitude;

/**
 * The longitude coordiate of the GPS coordiates of
 * the user's location
 */
static double longitude;

/**
 * Whether the user's location has been specified
 */
static int have_location = 0;

/**
 * Whether the -d flag (keep process running and remove
 * effect when killed) has been specified
 */
static int dflag = 0;

/**
 * Whether the -x flag (remove applied effect)
 * has been specified
 */
static int xflag = 0;

/**
 * Whether the currently applied temperature should be
 * printed when the program starts
 */
static int print_temperature = 0;


/**
 * Set to 1 by `handle_args` if the used arguments
 * does not describe an action that requires the
 * temperature to be modified; that is, only "-t get"
 * (or "-t ?" is specified)
 */
static int no_temperature_change = 0;


/**
 * If 0, SIGINT has not been received,
 * if 1, SIGINT has been received once as the effect
 *       should be faded out and then the effect should
 *       be removed and the program terminated,
 * if 2, SIGINT has been received at least twice and
 *       the effect should be removed immediately
 *       and the program terminated
 */
static volatile sig_atomic_t sigint_received = 0;

/**
 * If 0, SIGHUP has not been received,
 * if 1, SIGHUP has been received and the effect
 *       shall be made `LIBCOOPGAMMA_UNTIL_REMOVAL` and
 *       the program terminated
 */
static volatile sig_atomic_t sighup_received = 0;

/**
 * If 0, SIGUSR1 has not been received,
 * if 1, SIGUSR1 has been received once as the effect
 *       should be faded out and removed, but the
 *       the process shall not be terminated
 * if 2, SIGUSR1 has been received at least twice and
 *       the effect be removed immediately, but the
 *       the process shall not be terminated
 */
static volatile sig_atomic_t sigusr1_received = 0;

/**
 * If 0, SIGUSR2 has not been received,
 * if 1, SIGUSR2 has been received once as the effect
 *       should be faded back in (from SIGUSR1), or
 *       if during initial fade-in, the fade-in should
 *       be skipped over
 * if 2, SIGUSR2 has been received at least twice and
 *       the effect be immediately restored
 */
static volatile sig_atomic_t sigusr2_received = 0;


/**
 * Print usage information and exit
 */
void
usage(void)
{
	fprintf(stderr,
	        "usage: %s [-M method] [-S site] [-c crtc] ... [-R rule] [-p priority]"
	        " [-f fade-in] [-F fade-out] [-h [high-temp][@high-elev]] [-l [low-temp][@low-elev]]"
	        " (-L latitude:longitude | -t temperature [-d] | -x)\n", argv0);
	exit(1);
}


/**
 * Parse a non-negative double encoded as a string
 * 
 * @param   out  Output parameter for the value
 * @param   str  The string
 * @return       Zero on success, -1 if the string is invalid
 */
static int
parse_double(double *out, const char *str)
{
	char *end;
	errno = 0;
	*out = strtod(str, &end);
	if (errno || *out < 0 || isinf(*out) || isnan(*out) || *end)
		return -1;
	if (!*str || !strchr("0123456789.", *str))
		return -1;
	return 0;
}


/**
 * 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)
{
	double t;
	char *p;
	if (opt[0] == '-') {
		switch (opt[1]) {
		case 'd':
			dflag = 1;
			xflag = 0;
			break;
		case 'f':
			if (parse_double(&t, arg))
				usage();
			fade_in_cs = (unsigned long int)(t * 100 + 0.5);
			return 1;
		case 'F':
			if (parse_double(&t, arg))
				usage();
			fade_out_cs = (unsigned long int)(t * 100 + 0.5);
			return 1;
		case 'h':
			p = strchr(arg, '@');
			if (p)
				*p++ = '\0';
			if (*arg && parse_double(&high_temp, arg))
				usage();
			if (p && parse_double(&high_elev, p))
				usage();
			return 1;
		case 'l':
			p = strchr(arg, '@');
			if (p)
				*p++ = '\0';
			if (*arg && parse_double(&low_temp, arg))
				usage();
			if (p && parse_double(&low_elev, p))
				usage();
			return 1;
		case 'L':
			p = strchr(arg, ':');
			if (!p)
				usage();
			*p++ = '\0';
			if (parse_double(&latitude, arg) || latitude < -90 || latitude > 90)
				usage();
			if (parse_double(&longitude, p) || longitude < -180 || longitude > 180)
				usage();
			choosen_temperature = -1;
			have_location = 1;
			dflag = 0;
			xflag = 0;
			return 1;
		case 't':
			if (!strcmp(arg, "get") || !strcmp(arg, "?")) {
				print_temperature = 1;
			} else {
				if (parse_double(&choosen_temperature, arg))
					usage();
				xflag = 0;
			}
			return 1;
		case 'x':
			xflag = 1;
			dflag = 0;
			break;
		default:
			usage();
		}
	} else {
		usage();
	}
	return 0;
}


/**
 * 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)
{
	no_temperature_change = (!xflag && !have_location && choosen_temperature < 0);
	if (argc || (no_temperature_change && !print_temperature))
		usage();
	return 0;
	(void) argv;
	(void) prio;
}


/**
 * Fill a filter
 * 
 * @param  filter  The filter to fill
 * @param  red     The red brightness
 * @param  green   The green brightness
 * @param  blue    The blue brightness
 */
static void
fill_filter(libcoopgamma_filter_t *restrict filter, double red, double green, double blue)
{
	switch (filter->depth) {
#define X(CONST, MEMBER, MAX, TYPE)\
	case CONST:\
		libclut_start_over(&(filter->ramps.MEMBER), MAX, TYPE, 1, 1, 1);\
		IF_LINEARISING(libclut_linearise(&(filter->ramps.MEMBER), (MAX), TYPE, 1, 1, 1));\
		libclut_rgb_brightness(&(filter->ramps.MEMBER), MAX, TYPE, red, green, blue);\
		IF_LINEARISING(libclut_standardise(&(filter->ramps.MEMBER), (MAX), TYPE, 1, 1, 1));\
		break;
LIST_DEPTHS
#undef X
	default:
		abort();
	}
}


/**
 * Set the gamma ramps
 * 
 * @param   red    The red brightness
 * @param   green  The green brightness
 * @param   blue   The blue brightness
 * @return         0: Success
 *                 -1: Error, `errno` set
 *                 -2: Error, `cg.error` set
 *                 -3: Error, message already printed
 */
static int
set_ramps(double red, double green, double blue)
{
	int r;
	size_t i, j;

	for (i = 0, r = 1; i < filters_n; i++) {
		if (!crtc_updates[i].master || !crtc_info[crtc_updates[i].crtc].supported)
			continue;
		fill_filter(&crtc_updates[i].filter, red, green, blue);
		do {
			r = update_filter(i, 0);
		} while (r == -1 && errno == EINTR);
		if (r == -2 || (r == -1 && errno != EAGAIN))
			return r;
		if (crtc_updates[i].slaves) {
			for (j = 0; crtc_updates[i].slaves[j] != 0; j++) {
				do {
					r = update_filter(crtc_updates[i].slaves[j], 0);
				} while (r == -1 && errno == EINTR);
				if (r == -2 || (r == -1 && errno != EAGAIN))
					return r;
			}
		}
	}

	while (r != 1)
		if ((r = synchronise(-1)) < 0)
			if (r != -1 || errno != EINTR)
				return r;

	return 0;
}


/**
 * Get the colour of a temperature
 * 
 * @param   t      The temperature, in Kelvin
 * @param   r_out  Output parameter for the red channel multiplier
 * @param   g_out  Output parameter for the green channel multiplier
 * @param   b_out  Output parameter for the blue channel multiplier
 * @return         0 on success, -1 on failure
 */
static int
get_colour(long int t, double *r_out, double *g_out, double *b_out)
{
	double x, y, z, max;
	if (libred_get_colour_xy(t, &x, &y))
		return -1;
	libclut_model_ciexyy_to_ciexyz(x, y, 1.0, &x, &z);
	libclut_model_ciexyz_to_linear(x, 1.0, z, r_out, g_out, b_out);
	*r_out = fmax(0.0, *r_out);
	*g_out = fmax(0.0, *g_out);
	*b_out = fmax(0.0, *b_out);
	max = fmax(fmax(*r_out, *g_out), *b_out);
	*r_out /= max;
	*g_out /= max;
	*b_out /= max;
	return 0;
}


/**
 * Get the colour temperature for the current time
 * 
 * @param   tp  Output parameter for the colour temperature
 * @return      0 on success, -1 on failure
 */
static int
get_temperature(double *tp)
{
	if (choosen_temperature < 0) {
		if (libred_solar_elevation(latitude, longitude, tp))
			return -1;
		if (*tp < low_elev)
			*tp = low_elev;
		if (*tp > high_elev)
			*tp = high_elev;
		*tp = (*tp - low_elev) / (high_elev - low_elev);
		*tp = low_temp + *tp * (high_temp - low_temp);
	} else {
		*tp = choosen_temperature;
	}
	return 0;
}


/**
 * Get the currently applied colour temperature
 * 
 * @param   tp  Output parameter for the colour temperature
 * @return      0: Success
 *              -1: Error, `errno` set
 *              -2: Error, `cg.error` set
 */
static int
get_applied_temperature(double *tp)
{
	libcoopgamma_filter_table_t table;
	libcoopgamma_filter_query_t query;
	libcoopgamma_ramps_t *restrict ramps;
	size_t filter_i, i, tn = 0;
	long double lred, lgreen, lblue;
	double red, green, blue, t, tsum = 0;
	double x, y, z;

	if (libcoopgamma_set_nonblocking(&cg, 0) < 0)
		return -1;

	if (libcoopgamma_filter_table_initialise(&table) < 0)
		return -1;
	if (libcoopgamma_filter_query_initialise(&query) < 0)
		return -1;
	query.coalesce = 0;

	for (filter_i = 0; filter_i < filters_n; filter_i++) {
		query.crtc = crtc_updates[filter_i].filter.crtc;
		while (libcoopgamma_get_gamma_sync(&query, &table, &cg) < 0) {
			if (errno == EINTR)
				continue;
			return -1;
		}

		for (i = 0; i < table.filter_count; i++)
			if (!strcmp(table.filters[i].class, crtc_updates[filter_i].filter.class))
				break;
		if (i == table.filter_count) {
		no_filter:
#if 0
			fprintf(stderr, "%s: temperature on %s: not found\n", argv0, query.crtc);
#endif
			continue;
		}

		ramps = &table.filters[i].ramps;
		if (!table.red_size || !table.green_size || !table.blue_size)
			goto no_filter;

		switch (table.depth) {
#define X(CONST, MEMBER, MAX, TYPE)\
		case CONST:\
			IF_LINEARISING(libclut_linearise(&(ramps->MEMBER), (MAX), TYPE, 1, 1, 1));\
			lred = (long double)ramps->MEMBER.red[table.red_size - 1U] / (MAX);\
			lgreen = (long double)ramps->MEMBER.green[table.green_size - 1U] / (MAX);\
			lblue = (long double)ramps->MEMBER.blue[table.blue_size - 1U] / (MAX);\
			break
		X(LIBCOOPGAMMA_DOUBLE, d,   1.0,        double);
		X(LIBCOOPGAMMA_FLOAT,  f,   1.0,        float);
		X(LIBCOOPGAMMA_UINT8,  u8,  UINT8_MAX,  uint8_t);
		X(LIBCOOPGAMMA_UINT16, u16, UINT16_MAX, uint16_t);
		X(LIBCOOPGAMMA_UINT32, u32, UINT32_MAX, uint32_t);
		X(LIBCOOPGAMMA_UINT64, u64, UINT64_MAX, uint64_t);
#undef X
		default:
			errno = EPROTO;
			return -1;
		}
		red = (double)lred;
		green = (double)lgreen;
		blue = (double)lblue;

		if (red > 0.996 && blue < 0.004) { /* out of gamut (1000K to 1900K) */
			long int t1 = LIBRED_LOWEST_TEMPERATURE, t2;
			double g1 = 0, r2, g2, b2;
			for (t2 = LIBRED_LOWEST_TEMPERATURE; t2 <= 1900; t2 += LIBRED_DELTA_TEMPERATURE) {
				libred_get_colour_xy(t2, &x, &y);
				libclut_model_ciexyy_to_ciexyz(x, y, 1.0, &x, &z);
				libclut_model_ciexyz_to_linear(x, 1.0, z, &r2, &g2, &b2);
				g2 /= fmax(fmax(r2, g2), b2);
				if (green <= g2) {
					t = (green - g1) / (g2 - g1);
					t = t1 + (t2 - t1) * t;
					goto estimated;
				}
				t1 = t2;
				g1 = g2;
			}
			t = 1900;
		}

		libclut_model_linear_to_ciexyz(red, green, blue, &x, &y, &z);
		libclut_model_ciexyz_to_ciexyy(x, y, z, &x, &y);
		t = libred_get_temperature_xy(x, y, &x, &y);
#if 0
		libred_get_colour_xy((long int)t, &x, &y);
		x = sqrt(x*x + y*y);
		fprintf(stderr, "%s: temperature on %s: %gK (error: %g)\n", argv0, query.crtc, t, x);
#endif
	estimated:
		tsum += t;
		tn += 1U;
	}

	if (libcoopgamma_set_nonblocking(&cg, 1) < 0)
		return -1;

	*tp = tn ? tsum / (double)tn : 6500;
	return 0;
}


/**
 * Called when SIGINT is received
 * 
 * @param  sig  Always `SIGINT`
 */
static void
sigint_handler(int sig)
{
	(void) sig;
	sigint_received = sigint_received ? 2 : 1;
}


/**
 * Called when SIGHUP is received
 * 
 * @param  sig  Always `SIGHUP`
 */
static void
sighup_handler(int sig)
{
	(void) sig;
	sighup_received = 1;
}


/**
 * Called when SIGUSR1 is received
 * 
 * @param  sig  Always `SIGUSR1`
 */
static void
sigusr1_handler(int sig)
{
	sigusr1_received = sigusr1_received ? 2 : 1;
}


/**
 * Called when SIGUSR2 is received
 * 
 * @param  sig  Always `SIGUSR2`
 */
static void
sigusr2_handler(int sig)
{
	sigusr2_received = sigusr2_received ? 2 : 1;
}


/**
 * 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, tfd, initial_fade_in = 1;
	size_t i;
	double target_temperature;
	long int original_temperature = 6500;
	long int current_temperature = 6500;
	double red = 1, green = 1, blue = 1;
	uint64_t overrun;
	struct sigaction sa;
	struct timespec sleep_timeout;
	const char *side;
	size_t fade_cs;
	double cs;
	sigset_t empty_sigset;

	sigemptyset(&empty_sigset);

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = &sigint_handler;
	sigaction(SIGINT, &sa, NULL);
	sa.sa_handler = &sighup_handler;
	sigaction(SIGHUP, &sa, NULL);
	sa.sa_handler = &sigusr1_handler;
	sigaction(SIGUSR1, &sa, NULL);
	sa.sa_handler = &sigusr2_handler;
	sigaction(SIGUSR2, &sa, NULL);

	if (xflag)
		for (i = 0; i < filters_n; i++)
			crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_REMOVE;
	else if (choosen_temperature >= 0 && !dflag)
		for (i = 0; i < filters_n; i++)
			crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL;
	else
		for (i = 0; i < filters_n; i++)
			crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_DEATH;

	if (!xflag)
		if (choosen_temperature < 0)
			dflag = 1;

	if (fade_in_cs || print_temperature) {
		double t;
		if ((r = get_applied_temperature(&t)))
			return r;
		original_temperature = (long int)(t + 0.5);
		if (print_temperature)
			printf("%liK\n", original_temperature);
	}

	if (no_temperature_change)
		return 0;

	if (xflag)
		return set_ramps(1, 1, 1);

	if ((r = make_slaves()) < 0)
		return r;


fade_in:
	if (!fade_in_cs)
		goto faded_in;

	tfd = timerfd_create(CLOCK_MONOTONIC, 0);
	if (tfd < 0)
		return -1;
	if (timerfd_settime(tfd, 0, &(struct itimerspec){{0, 10000000L}, {0, 10000000L}}, NULL))
		return -1;

	fade_cs = (size_t)fade_in_cs;
	if (initial_fade_in) {
		initial_fade_in = 0;
		if ((r = get_temperature(&target_temperature)) < 0)
			return r;
	} else {
	fade_in_have_timer:
		original_temperature = 6500;
	}
	for (i = 0; i < fade_cs;) {
		if (sigint_received) {
			goto reverse_fade_in;
		} else if (sigusr2_received) {
			sigusr2_received = 0;
			goto skip_fade_in;
		} else if (sigusr1_received) {
			sigusr1_received -= 1;
		reverse_fade_in:
			cs = (double)i / fade_cs * fade_out_cs + 0.5;
			fade_cs = (unsigned long int)cs;
			goto fade_out_have_timer;
		}

		if (i % 600 == 0)
			if ((r = get_temperature(&target_temperature)) < 0)
				return r;
		current_temperature = (long int)(original_temperature - (original_temperature - target_temperature) * i / fade_cs);
		if (get_colour(current_temperature, &red, &green, &blue))
			return -1;
		if ((r = set_ramps(red, green, blue)) < 0)
			return r;

		while (read(tfd, &overrun, sizeof(overrun)) != sizeof(overrun)) {
			if (errno == EINTR)
				continue;
			return -1;
		}
		if (overrun > fade_cs - i)
			overrun = fade_cs - i;
		i += overrun;
	}

skip_fade_in:
	close(tfd);


faded_in:
	for (;;) {
		if (sigint_received) {
			goto fade_out;
		} else if (sighup_received) {
			for (i = 0; i < filters_n; i++)
				crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL;
			return set_ramps(red, green, blue);
		}
		sigusr2_received = 0;
		if (sigusr1_received) {
			sigusr1_received -= 1;
			goto fade_out;
		}

		if ((r = get_temperature(&target_temperature)) < 0)
			return r;
		current_temperature = (long int)target_temperature;
		if (get_colour(current_temperature, &red, &green, &blue))
			return -1;
		if ((r = set_ramps(red, green, blue)) < 0)
			return r;

		if (!dflag)
			return 0;

		sleep_timeout.tv_sec = 6;
		sleep_timeout.tv_nsec = 0;
		while ((errno = clock_nanosleep(CLOCK_BOOTTIME, 0, &sleep_timeout, &sleep_timeout))) {
			if (errno == EINTR) {
				if (sigint_received || sighup_received || sigusr1_received || sigusr2_received)
					break;
				continue;
			}
			return -1;
		}
	}


fade_out:
	if (!fade_out_cs)
		goto faded_out;

	tfd = timerfd_create(CLOCK_MONOTONIC, 0);
	if (tfd < 0)
		goto fade_out_fail;
	if (timerfd_settime(tfd, 0, &(struct itimerspec){{0, 10000000L}, {0, 10000000L}}, NULL))
		goto fade_out_fail;

	fade_cs = (size_t)fade_out_cs;
fade_out_have_timer:
	original_temperature = current_temperature;
	for (i = 0; i < fade_cs;) {
		if (sigint_received > 1 || sigusr1_received) {
			goto skip_fade_out;
		} else if (!sigint_received && !sighup_received) {
			if (sigusr2_received) {
				sigusr2_received -= 1;
				cs = (double)i / fade_cs * fade_in_cs + 0.5;
				fade_cs = (unsigned long int)cs;
				goto fade_in_have_timer;
			}
		}

		current_temperature = original_temperature + (double)(6500 - original_temperature) * i / fade_cs;
		if (get_colour(current_temperature, &red, &green, &blue))
			goto fade_out_fail;
		if ((r = set_ramps(red, green, blue)) < 0)
			goto fade_out_fail_use_r;

		while (read(tfd, &overrun, sizeof(overrun)) != sizeof(overrun)) {
			if (errno == EINTR)
				continue;
			goto fade_out_fail;
		}
		if (overrun > fade_cs - i)
			overrun = fade_cs - i;
		i += overrun;
	}

skip_fade_out:
	close(tfd);


faded_out:
	for (i = 0; i < filters_n; i++)
		crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_REMOVE;
	if ((r = set_ramps(red, green, blue)) < 0)
		return r;
	for (i = 0; i < filters_n; i++)
		crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL;
	for (;;) {
		sigusr1_received = 0;
		if (sigint_received || sighup_received) {
			for (i = 0; i < filters_n; i++)
				crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_REMOVE;
			return set_ramps(1, 1, 1);
		} else if (sigusr2_received) {
			sigusr2_received -= 1;
			goto fade_in;
		}

		if (sigsuspend(&empty_sigset) && errno == EFAULT)
			abort();
	}


fade_out_fail_use_r:
	switch (r) {
	case -1:
	fade_out_fail:
		perror(argv0);
		break;
	case -2:
		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));
		}
		break;
	default:
		break;
	}

	for (i = 0; i < filters_n; i++)
		crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_REMOVE;
	r = set_ramps(1, 1, 1);
	return r ? r : -3;
}