aboutsummaryrefslogblamecommitdiffstats
path: root/radharc.c
blob: 12f5ce898fbf4d7477b89378a6b1757a202e92a5 (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>

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


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


/**
 * 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 (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)
{
	if (argc || (!xflag && !have_location && choosen_temperature < 0))
		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);\
		libclut_linearise(&(filter->ramps.MEMBER), MAX, TYPE, 1, 1, 1);\
		libclut_rgb_brightness(&(filter->ramps.MEMBER), MAX, TYPE, red, green, blue);\
		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;

	libclut_model_standard_to_linear(&red, &green, &blue);
	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 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;
}


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


/**
 * 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;
	size_t i;
	double target_temperature;
	long int original_temperature;
	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;

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = &sigint_handler;
	sigaction(SIGINT, &sa, NULL);
	sa.sa_handler = &sighup_handler;
	sigaction(SIGHUP, &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 (xflag)
		return set_ramps(1, 1, 1);

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

	if (!fade_in_cs)
		goto no_fade_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;

	for (i = 0; i < (size_t)fade_in_cs;) {
		if (sigint_received) {
			double cs = (double)i / fade_in_cs * fade_out_cs + 0.5;
			fade_out_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)(6500 - (6500 - target_temperature) * i / fade_in_cs);
		if (libred_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_in_cs - i)
			overrun = fade_in_cs - i;
		i += overrun;
	}

	close(tfd);

no_fade_in:
	for (;;) {
		if (sigint_received)
			goto fade_out;

		if (sighup_received) {
			for (i = 0; i < filters_n; i++)
				crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL;
			return set_ramps(red, green, blue);
		}

		if ((r = get_temperature(&target_temperature)) < 0)
			return r;
		current_temperature = (long int)target_temperature;
		if (libred_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)
					break;
				continue;
			}
			return -1;
		}
	}

fade_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_out_have_timer:
	original_temperature = current_temperature;
	for (i = 0; i < (size_t)fade_out_cs;) {
		if (sigint_received > 1)
			break;

		current_temperature = original_temperature + (double)(6500 - original_temperature) * i / fade_out_cs;
		if (libred_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_in_cs - i)
			overrun = fade_in_cs - i;
		i += overrun;
	}

	close(tfd);

	for (i = 0; i < filters_n; i++)
		crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_REMOVE;
	return set_ramps(1, 1, 1);

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