diff options
Diffstat (limited to 'src')
43 files changed, 3840 insertions, 2313 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..c906e20 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,54 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + colorramp.o\ + config-ini.o\ + gamma-coopgamma.o\ + gamma-drm.o\ + gamma-dummy.o\ + gamma-randr.o\ + gamma-vidmode.o\ + hooks.o\ + location-geoclue2.o\ + location-manual.o\ + options.o\ + pipeutils.o\ + redshift.o\ + signals.o\ + solar.o\ + systemtime.o + +HDR = $(OBJ:.o=.h) + +PACKAGE_STRING = redshift-ng 1.13 + +all: redshift +$(OBJ): $(HDR) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -D'PACKAGE_STRING="$(PACKAGE_STRING)"' + +redshift: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) + +install: redshift + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- redshift "$(DESTDIR)$(PREFIX)/bin/" + cp -- redshift.1 "$(DESTDIR)$(MANPREFIX)/man1/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/redshift" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/redshift.1" + +clean: + -rm -f -- *.o *.a *.lo *.su + -rm -f -- redshift + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install uninstall clean diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index c7a5444..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,100 +0,0 @@ - -SUBDIRS = redshift-gtk - -# I18n -localedir = $(datadir)/locale -AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" - -# redshift Program -bin_PROGRAMS = redshift - -redshift_SOURCES = \ - redshift.c redshift.h \ - colorramp.c colorramp.h \ - config-ini.c config-ini.h \ - location-manual.c location-manual.h \ - solar.c solar.h \ - systemtime.c systemtime.h \ - hooks.c hooks.h \ - gamma-dummy.c gamma-dummy.h - -EXTRA_redshift_SOURCES = \ - gamma-drm.c gamma-drm.h \ - gamma-randr.c gamma-randr.h \ - gamma-vidmode.c gamma-vidmode.h \ - gamma-quartz.c gamma-quartz.h \ - gamma-w32gdi.c gamma-w32gdi.h \ - location-geoclue.c location-geoclue.h - -AM_CFLAGS = -redshift_LDADD = @LIBINTL@ -EXTRA_DIST = - -if ENABLE_DRM -redshift_SOURCES += gamma-drm.c gamma-drm.h -AM_CFLAGS += $(DRM_CFLAGS) -redshift_LDADD += \ - $(DRM_LIBS) $(DRM_CFLAGS) -endif - -if ENABLE_RANDR -redshift_SOURCES += gamma-randr.c gamma-randr.h -AM_CFLAGS += $(XCB_CFLAGS) $(XCB_RANDR_CFLAGS) -redshift_LDADD += \ - $(XCB_LIBS) $(XCB_CFLAGS) \ - $(XCB_RANDR_LIBS) $(XCB_RANDR_CFLAGS) -endif - -if ENABLE_VIDMODE -redshift_SOURCES += gamma-vidmode.c gamma-vidmode.h -AM_CFLAGS += $(X11_CFLAGS) $(XF86VM_CFLAGS) -redshift_LDADD += \ - $(X11_LIBS) $(X11_CFLAGS) \ - $(XF86VM_LIBS) $(XF86VM_CFLAGS) -endif - -if ENABLE_QUARTZ -redshift_SOURCES += gamma-quartz.c gamma-quartz.h -AM_CFLAGS += $(QUARTZ_CFLAGS) -redshift_LDADD += \ - $(QUARTZ_LIBS) $(QUARTZ_CFLAGS) -endif - -if ENABLE_WINGDI -redshift_SOURCES += gamma-w32gdi.c gamma-w32gdi.h -redshift_LDADD += -lgdi32 -endif - - -if ENABLE_GEOCLUE -redshift_SOURCES += location-geoclue.c location-geoclue.h -AM_CFLAGS += \ - $(GEOCLUE_CFLAGS) $(GEOCLUE_LIBS) \ - $(GLIB_CFLAGS) $(GLIB_LIBS) -redshift_LDADD += \ - $(GEOCLUE_LIBS) $(GEOCLUE_CFLAGS) - $(GLIB_LIBS) $(GLIB_CFLAGS) -endif - -if ENABLE_GEOCLUE2 -redshift_SOURCES += location-geoclue2.c location-geoclue2.h -AM_CFLAGS += \ - $(GEOCLUE2_CFLAGS) -redshift_LDADD += \ - $(GEOCLUE2_LIBS) $(GEOCLUE2_CFLAGS) -endif - -# Build CoreLocation module as a separate convenience -# library since it is using a separate compiler -# (Objective C). - -if ENABLE_CORELOCATION -noinst_LTLIBRARIES = liblocation-corelocation.la -liblocation_corelocation_la_SOURCES = \ - location-corelocation.m location-corelocation.h -liblocation_corelocation_la_OBJCFLAGS = \ - $(CORELOCATION_CFLAGS) -liblocation_corelocation_la_LIBADD = \ - $(CORELOCATION_CFLAGS) $(CORELOCATION_LIBS) -redshift_LDADD += liblocation-corelocation.la -endif diff --git a/src/colorramp.c b/src/colorramp.c index fda75f2..2c67969 100644 --- a/src/colorramp.c +++ b/src/colorramp.c @@ -21,6 +21,7 @@ #include <stdint.h> #include <math.h> +#include "colorramp.h" #include "redshift.h" /* Whitepoint values for temperatures at 100K intervals. @@ -285,43 +286,29 @@ interpolate_color(float a, const float *c1, const float *c2, float *c) #define F(Y, C) pow((Y) * setting->brightness * \ white_point[C], 1.0/setting->gamma[C]) -void -colorramp_fill(uint16_t *gamma_r, uint16_t *gamma_g, uint16_t *gamma_b, - int size, const color_setting_t *setting) -{ - /* Approximate white point */ - float white_point[3]; - float alpha = (setting->temperature % 100) / 100.0; - int temp_index = ((setting->temperature - 1000) / 100)*3; - interpolate_color(alpha, &blackbody_color[temp_index], - &blackbody_color[temp_index+3], white_point); - for (int i = 0; i < size; i++) { - gamma_r[i] = F((double)gamma_r[i]/(UINT16_MAX+1), 0) * - (UINT16_MAX+1); - gamma_g[i] = F((double)gamma_g[i]/(UINT16_MAX+1), 1) * - (UINT16_MAX+1); - gamma_b[i] = F((double)gamma_b[i]/(UINT16_MAX+1), 2) * - (UINT16_MAX+1); +#define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ + void\ + colorramp_fill_##SUFFIX(TYPE *gamma_r, TYPE *gamma_g, TYPE *gamma_b,\ + size_t size_r, size_t size_g, size_t size_b,\ + const color_setting_t *setting)\ + {\ + /* Approximate white point */\ + float white_point[3];\ + float alpha = (setting->temperature % 100) / 100.0;\ + int temp_index = ((setting->temperature - 1000) / 100) * 3;\ + interpolate_color(alpha, &blackbody_color[temp_index],\ + &blackbody_color[temp_index+3], white_point);\ + \ + for (size_t i = 0; i < size_r; i++)\ + gamma_r[i] = F((double)gamma_r[i] / (MAX), 0) * (MAX);\ + for (size_t i = 0; i < size_g; i++)\ + gamma_g[i] = F((double)gamma_g[i] / (MAX), 1) * (MAX);\ + for (size_t i = 0; i < size_b; i++)\ + gamma_b[i] = F((double)gamma_b[i] / (MAX), 2) * (MAX);\ } -} - -void -colorramp_fill_float(float *gamma_r, float *gamma_g, float *gamma_b, - int size, const color_setting_t *setting) -{ - /* Approximate white point */ - float white_point[3]; - float alpha = (setting->temperature % 100) / 100.0; - int temp_index = ((setting->temperature - 1000) / 100)*3; - interpolate_color(alpha, &blackbody_color[temp_index], - &blackbody_color[temp_index+3], white_point); - for (int i = 0; i < size; i++) { - gamma_r[i] = F((double)gamma_r[i], 0); - gamma_g[i] = F((double)gamma_g[i], 1); - gamma_b[i] = F((double)gamma_b[i], 2); - } -} +LIST_RAMPS_STOP_VALUE_TYPES +#undef X #undef F diff --git a/src/colorramp.h b/src/colorramp.h index 438c563..8802844 100644 --- a/src/colorramp.h +++ b/src/colorramp.h @@ -24,9 +24,19 @@ #include "redshift.h" -void colorramp_fill(uint16_t *gamma_r, uint16_t *gamma_g, uint16_t *gamma_b, - int size, const color_setting_t *setting); -void colorramp_fill_float(float *gamma_r, float *gamma_g, float *gamma_b, - int size, const color_setting_t *setting); +#define LIST_RAMPS_STOP_VALUE_TYPES\ + X(u8, uint8_t, UINT8_MAX + 1ULL, UINT8_MAX, 8)\ + X(u16, uint16_t, UINT16_MAX + 1ULL, UINT16_MAX, 16)\ + X(u32, uint32_t, UINT32_MAX + 1ULL, UINT32_MAX, 32)\ + X(u64, uint64_t, UINT64_MAX, UINT64_MAX, 64)\ + X(float, float, 1, 1, -1)\ + X(double, double, 1, 1, -2) + +#define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ + void colorramp_fill_##SUFFIX(TYPE *gamma_r, TYPE *gamma_g, TYPE *gamma_b,\ + size_t size_r, size_t size_g, size_t size_b,\ + const color_setting_t *setting); +LIST_RAMPS_STOP_VALUE_TYPES +#undef X #endif /* ! REDSHIFT_COLORRAMP_H */ diff --git a/src/config-ini.c b/src/config-ini.c index 334a541..4f12610 100644 --- a/src/config-ini.c +++ b/src/config-ini.c @@ -14,9 +14,12 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2018 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif #include <stdio.h> #include <stdlib.h> @@ -63,8 +66,15 @@ open_config_file(const char *filepath) if (f == NULL && (env = getenv("XDG_CONFIG_HOME")) != NULL && env[0] != '\0') { - snprintf(cp, sizeof(cp), "%s/redshift.conf", env); + snprintf(cp, sizeof(cp), + "%s/redshift/redshift.conf", env); f = fopen(cp, "r"); + if (f == NULL) { + /* Fall back to formerly used path. */ + snprintf(cp, sizeof(cp), + "%s/redshift.conf", env); + f = fopen(cp, "r"); + } } #ifdef _WIN32 @@ -78,8 +88,14 @@ open_config_file(const char *filepath) if (f == NULL && (env = getenv("HOME")) != NULL && env[0] != '\0') { snprintf(cp, sizeof(cp), - "%s/.config/redshift.conf", env); + "%s/.config/redshift/redshift.conf", env); f = fopen(cp, "r"); + if (f == NULL) { + /* Fall back to formerly used path. */ + snprintf(cp, sizeof(cp), + "%s/.config/redshift.conf", env); + f = fopen(cp, "r"); + } } #ifndef _WIN32 @@ -89,8 +105,14 @@ open_config_file(const char *filepath) char *home = pwd->pw_dir; if ((home != NULL) && (*home != '\0')) { snprintf(cp, sizeof(cp), - "%s/.config/redshift.conf", home); + "%s/.config/redshift/redshift.conf", home); f = fopen(cp, "r"); + if (f == NULL) { + /* Fall back to formerly used path. */ + snprintf(cp, sizeof(cp), + "%s/.config/redshift.conf", home); + f = fopen(cp, "r"); + } } else { fprintf(stderr, _("Cannot determine your home directory, " "it is from the system's user table.\n")); @@ -115,9 +137,14 @@ open_config_file(const char *filepath) int len = end - begin; if (len > 0) { snprintf(cp, sizeof(cp), - "%.*s/redshift.conf", len, begin); - + "%.*s/redshift/redshift.conf", len, begin); f = fopen(cp, "r"); + if (f != NULL) { + /* Fall back to formerly used path. */ + snprintf(cp, sizeof(cp), + "%.*s/redshift.conf", len, begin); + f = fopen(cp, "r"); + } if (f != NULL) break; } @@ -171,7 +198,7 @@ config_ini_init(config_ini_state_t *state, const char *filepath) s[strcspn(s, "\r\n")] = '\0'; /* Skip comments and empty lines. */ - if (s[0] == ';' || s[0] == '\0') continue; + if (s[0] == ';' || s[0] == '#' || s[0] == '\0') continue; if (s[0] == '[') { /* Read name of section. */ @@ -240,7 +267,7 @@ config_ini_init(config_ini_state_t *state, const char *filepath) config_ini_free(state); return -1; } - + /* Insert into section list. */ setting->name = NULL; setting->value = NULL; diff --git a/src/config.mk b/src/config.mk new file mode 100644 index 0000000..0e0b2fd --- /dev/null +++ b/src/config.mk @@ -0,0 +1,21 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +PKGCONFIG = pkg-config +PKGCONFIG_CFLAGS = $(PKGCONFIG) --cflags +PKGCONFIG_LDFLAGS = $(PKGCONFIG) --libs + +DRM_LIBS = libdrm +GEOCLUE_LIBS = glib-2.0 gio-2.0 +RANDR_LIBS = xcb xcb-randr +VIDMODE_LIBS = x11 xxf86vm + +LIBS_PKGCONFIG = $(DRM_LIBS) $(GEOCLUE_LIBS) $(RANDR_LIBS) $(VIDMODE_LIBS) + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE\ + -DENABLE_DRM -DENABLE_GEOCLUE2 -DENABLE_RANDR -DENABLE_VIDMODE\ + -DENABLE_COOPGAMMA +CFLAGS = $$($(PKGCONFIG_CFLAGS) $(LIBS_PKGCONFIG)) +LDFLAGS = $$($(PKGCONFIG_LDFLAGS) $(LIBS_PKGCONFIG)) -lm -lcoopgamma diff --git a/src/gamma-coopgamma.c b/src/gamma-coopgamma.c new file mode 100644 index 0000000..41a9d2b --- /dev/null +++ b/src/gamma-coopgamma.c @@ -0,0 +1,571 @@ +/* gamma-coopgamma.h -- coopgamma gamma adjustment source + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2016, 2025 Mattias Andrée <m@maandree.se> +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#else +# define PACKAGE "redshift" +#endif + +#include <errno.h> +#include <inttypes.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef ENABLE_NLS +# include <libintl.h> +# define _(s) gettext(s) +#else +# define _(s) s +#endif + +#include "gamma-coopgamma.h" +#include "colorramp.h" + + +typedef struct { + char *edid; + size_t index; +} coopgamma_output_id_t; + +typedef struct { + libcoopgamma_filter_t filter; + libcoopgamma_ramps_t plain_ramps; + size_t rampsize; +} coopgamma_crtc_state_t; + +typedef struct { + libcoopgamma_context_t ctx; + coopgamma_crtc_state_t *crtcs; + size_t n_crtcs; + char **methods; + char *method; + char *site; + int64_t priority; + int list_outputs; + coopgamma_output_id_t *outputs; + size_t n_outputs; + size_t a_outputs; +} coopgamma_state_t; + + +struct signal_blockage { +}; + +static int +unblocked_signal(int signo, struct signal_blockage *prev) +{ + /* TODO */ + return 0; +} + + +static int +restore_signal_blockage(int signo, const struct signal_blockage *blockage) +{ + /* TODO */ + return 0; +} + + +static int +update(coopgamma_state_t *state) +{ + for (size_t i = 0; i < state->n_crtcs; i++) + libcoopgamma_set_gamma_sync(&state->crtcs[i].filter, &state->ctx); + return 0; +} + + +static void +print_error(coopgamma_state_t *state) +{ + const char* side = state->ctx.error.server_side ? _("server-side") : _("client-side"); + if (state->ctx.error.custom) { + if (state->ctx.error.number != 0 && state->ctx.error.description != NULL) + fprintf(stderr, "%s error number %llu: %s\n", + side, (unsigned long long int)state->ctx.error.number, + state->ctx.error.description); + else if (state->ctx.error.number != 0) + fprintf(stderr, _("%s error number %llu\n"), + side, (unsigned long long int)state->ctx.error.number); + else if (state->ctx.error.description != NULL) + fprintf(stderr, _("%s error: %s\n"), side, state->ctx.error.description); + } else if (state->ctx.error.description != NULL) { + fprintf(stderr, _("%s error: %s\n"), side, state->ctx.error.description); + } else { + fprintf(stderr, _("%s error: %s\n"), side, strerror(state->ctx.error.number)); + } +} + + +static int +coopgamma_init(coopgamma_state_t **state) +{ + *state = malloc(sizeof(coopgamma_state_t)); + if (*state == NULL) return -1; + + coopgamma_state_t *s = *state; + + struct signal_blockage signal_blockage; + memset(s, 0, sizeof(*s)); + if (libcoopgamma_context_initialise(&s->ctx)) { + perror("libcoopgamma_context_initialise"); + return -1; + } + + /* This is done this early to check if coopgamma is available */ + if (unblocked_signal(SIGCHLD, &signal_blockage) < 0) + return -1; + s->methods = libcoopgamma_get_methods(); + if (s->methods == NULL) { + perror("libcoopgamma_get_methods"); + if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) + exit(EXIT_FAILURE); + return -1; + } + if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) + return -1; + + s->priority = 0x0800000000000000LL; + + return 0; +} + +static int +coopgamma_start(coopgamma_state_t *state, program_mode_t mode) +{ + struct signal_blockage signal_blockage; + libcoopgamma_lifespan_t lifespan; + char** outputs; + size_t i, j, n_outputs; + int r; + double d; + + switch (mode) { + case PROGRAM_MODE_RESET: + lifespan = LIBCOOPGAMMA_REMOVE; + break; + case PROGRAM_MODE_ONE_SHOT: + case PROGRAM_MODE_MANUAL: + lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL; + break; + default: + lifespan = LIBCOOPGAMMA_UNTIL_DEATH; + break; + } + + free(state->methods); + state->methods = NULL; + + /* Connect to server */ + if (unblocked_signal(SIGCHLD, &signal_blockage) < 0) + return -1; + if (libcoopgamma_connect(state->method, state->site, &state->ctx) < 0) { + if (errno) + perror("libcoopgamma_connect"); + else + fprintf(stderr, _("libcoopgamma_connect: could not " + "start coopgamma server\n")); + if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) + exit(EXIT_FAILURE); + return -1; + } + if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) + return -1; + free(state->method); + state->method = NULL; + free(state->site); + state->site = NULL; + + /* Get available outputs */ + outputs = libcoopgamma_get_crtcs_sync(&state->ctx); + for (n_outputs = 0; outputs[n_outputs]; n_outputs++); + + /* List available output if edid=list was used */ + if (state->list_outputs) { + if (outputs == NULL) { + print_error(state); + return -1; + } + printf(_("Available outputs:\n")); + for (i = 0; outputs[i]; i++) + printf(" %s\n", outputs[i]); + if (ferror(stdout)) { + perror("printf"); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); + } + + /* Translate crtc=N to edid=EDID */ + for (i = 0; i < state->n_outputs; i++) { + if (state->outputs[i].edid != NULL) + continue; + if (state->outputs[i].index >= n_outputs) { + fprintf(stderr, _("monitor number %zu does not exist," + "available monitors are [0, %zu]\n"), + state->outputs[i].index, n_outputs - 1); + return -1; + } + state->outputs[i].edid = strdup(outputs[state->outputs[i].index]); + if (state->outputs[i].edid == NULL) { + perror("strdup"); + return -1; + } + } + + /* Use all outputs if none were specified */ + if (state->n_outputs == 0) { + state->n_outputs = state->a_outputs = n_outputs; + state->outputs = malloc(n_outputs * sizeof(*state->outputs)); + if (state->outputs == NULL) { + perror("malloc"); + return -1; + } + for (i = 0; i < n_outputs; i++) { + state->outputs[i].edid = strdup(outputs[i]); + if (state->outputs[i].edid == NULL) { + perror("strdup"); + return -1; + } + } + } + + free(outputs); + + /* Initialise information for each output */ + state->crtcs = calloc(state->n_outputs, sizeof(*state->crtcs)); + if (state->crtcs == NULL) { + perror("calloc"); + return -1; + } + for (i = 0; i < state->n_outputs; i++) { + libcoopgamma_crtc_info_t info; + coopgamma_crtc_state_t *crtc = state->crtcs + state->n_crtcs; + + crtc->filter.priority = state->priority; + crtc->filter.crtc = state->outputs[i].edid; + crtc->filter.class = PACKAGE "::redshift::standard"; + crtc->filter.lifespan = lifespan; + + if (libcoopgamma_get_gamma_info_sync(crtc->filter.crtc, &info, &state->ctx) < 0) { + int saved_errno = errno; + fprintf(stderr, _("failed to retrieve information for output `%s':\n"), + outputs[i]); + errno = saved_errno; + print_error(state); + return -1; + } + if (!info.cooperative) { + fprintf(stderr, _("coopgamma is not available\n")); + return -1; + } + if (info.supported == LIBCOOPGAMMA_NO) { + fprintf(stderr, _("output `%s' does not support gamma " + "adjustments, skipping\n"), outputs[i]); + continue; + } + + /* Get total size of the ramps */ + switch (info.depth) { +#define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ + case DEPTH:\ + crtc->rampsize = sizeof(TYPE);\ + break; + LIST_RAMPS_STOP_VALUE_TYPES +#undef X + default: + if (info.depth > 0) + fprintf(stderr, _("output `%s' uses an unsupported depth " + "for its gamma ramps: %i bits, skipping\n"), + outputs[i], info.depth); + else + fprintf(stderr, _("output `%s' uses an unrecognised depth, " + "for its gamma ramps, with the code %i, " + "skipping\n"), outputs[i], info.depth); + continue; + } + crtc->rampsize *= info.red_size + info.green_size + info.blue_size; + + crtc->filter.depth = info.depth; + crtc->filter.ramps.u8.red_size = info.red_size; + crtc->filter.ramps.u8.green_size = info.green_size; + crtc->filter.ramps.u8.blue_size = info.blue_size; + crtc->plain_ramps.u8.red_size = info.red_size; + crtc->plain_ramps.u8.green_size = info.green_size; + crtc->plain_ramps.u8.blue_size = info.blue_size; + + /* Initialise plain ramp and working ramp */ +#define float f +#define double d + switch (info.depth) { +#define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ + case DEPTH:\ + r = libcoopgamma_ramps_initialise(&crtc->filter.ramps.SUFFIX);\ + if (r < 0) {\ + perror("libcoopgamma_ramps_initialise");\ + return -1;\ + }\ + r = libcoopgamma_ramps_initialise(&crtc->plain_ramps.SUFFIX);\ + if (r < 0) {\ + perror("libcoopgamma_ramps_initialise");\ + return -1;\ + }\ + for (j = 0; j < crtc->plain_ramps.SUFFIX.red_size; j++) {\ + d = j;\ + d /= crtc->plain_ramps.SUFFIX.red_size;\ + crtc->plain_ramps.SUFFIX.red[j] = d * TRUE_MAX;\ + }\ + for (j = 0; j < crtc->plain_ramps.SUFFIX.green_size; j++) {\ + d = j;\ + d /= crtc->plain_ramps.SUFFIX.green_size;\ + crtc->plain_ramps.SUFFIX.green[j] = d * TRUE_MAX;\ + }\ + for (j = 0; j < crtc->plain_ramps.SUFFIX.blue_size; j++) {\ + d = j;\ + d /= crtc->plain_ramps.SUFFIX.blue_size;\ + crtc->plain_ramps.SUFFIX.blue[j] = d * TRUE_MAX;\ + }\ + break; + LIST_RAMPS_STOP_VALUE_TYPES +#undef X + default: + abort(); + } +#undef float +#undef double + + state->outputs[i].edid = NULL; + state->n_crtcs++; + } + + free(state->outputs); + state->outputs = NULL; + state->n_outputs = 0; + + return 0; +} + +static void +coopgamma_free(coopgamma_state_t *state) +{ + free(state->methods); + state->methods = NULL; + free(state->method); + state->method = NULL; + free(state->site); + state->site = NULL; + + while (state->n_crtcs--) { + state->crtcs[state->n_crtcs].filter.class = NULL; + libcoopgamma_filter_destroy(&state->crtcs[state->n_crtcs].filter); + libcoopgamma_ramps_destroy(&state->crtcs[state->n_crtcs].plain_ramps); + } + state->n_crtcs = 0; + free(state->crtcs); + state->crtcs = NULL; + + libcoopgamma_context_destroy(&state->ctx, 1); + while (state->n_outputs--) + free(state->outputs[state->n_outputs].edid); + state->n_outputs = 0; + free(state->outputs); + state->outputs = NULL; +} + +static void +coopgamma_print_help(FILE *f) +{ + fputs(_("Adjust gamma ramps with coopgamma.\n"), f); + fputs("\n", f); + + /* TRANSLATORS: coopgamma help output + left column must not be translated */ + fputs(_(" edid=EDID \tEDID of monitor to apply adjustments to, enter " + "`list' to list available monitors\n" + " crtc=N \tIndex of CRTC to apply adjustments to\n" + " priority=N \tThe application order of the adjustments, " + "default value is 576460752303423488.\n" + " method=METHOD \tUnderlaying adjustment method, enter " + "`list' to list available methods\n" + " display=DISPLAY\tThe display to apply adjustments to\n"), + f); + fputs("\n", f); +} + +static int +coopgamma_set_option(coopgamma_state_t *state, const char *key, const char *value) +{ + size_t i; + char *end; + long long int priority; + + if (!strcasecmp(key, "priority")) { + errno = 0; + priority = strtoll(value, &end, 10); + if (errno || *end || priority < INT64_MIN || priority > INT64_MAX) { + fprintf(stderr, _("value of method parameter `crtc' " + "must be a integer in [%lli, %lli]\n"), + (long long int)INT64_MIN, (long long int)INT64_MAX); + return -1; + } + state->priority = priority; + } else if (!strcasecmp(key, "method")) { + if (state->method != NULL) { + fprintf(stderr, _("method parameter `method' " + "can only be used once\n")); + return -1; + } + if (!strcasecmp(value, "list")) { + /* TRANSLATORS: coopgamma help output + the word "coopgamma" must not be translated */ + printf(_("Available adjustment methods for coopgamma:\n")); + for (i = 0; state->methods[i]; i++) + printf(" %s\n", state->methods[i]); + if (ferror(stdout)) { + perror("printf"); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); + } + state->method = strdup(value); + if (state->method == NULL) { + perror("strdup"); + return -1; + } + } else if (!strcasecmp(key, "display")) { + if (state->site != NULL) { + fprintf(stderr, _("method parameter `display' " + "can only be used once\n")); + return -1; + } + state->site = strdup(value); + if (state->site == NULL) { + perror("strdup"); + return -1; + } + } else if (!strcasecmp(key, "edid") || !strcasecmp(key, "crtc")) { + if (state->n_outputs == state->a_outputs) { + state->a_outputs += 8; + state->outputs = realloc(state->outputs, + state->a_outputs * sizeof(*state->outputs)); + if (state->outputs == NULL) { + perror("realloc"); + return -1; + } + } + if (!strcasecmp(key, "edid")) { + state->outputs[state->n_outputs].edid = strdup(value); + if (state->outputs[state->n_outputs].edid == NULL) { + perror("strdup"); + return -1; + } + if (!strcasecmp(state->outputs[state->n_outputs].edid, "list")) + state->list_outputs = 1; + } else { + state->outputs[state->n_outputs].edid = NULL; + errno = 0; + state->outputs[state->n_outputs].index = (size_t)strtoul(value, &end, 10); + if (!*end && errno == ERANGE && + state->outputs[state->n_outputs].index == SIZE_MAX) { + state->outputs[state->n_outputs].index = SIZE_MAX; + } else if (errno || *end) { + fprintf(stderr, _("value of method parameter `crtc' " + "must be a non-negative integer\n")); + return -1; + } + } + state->n_outputs++; + } else { + fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); + return -1; + } + + return 0; +} + +static void +coopgamma_restore(coopgamma_state_t *state) +{ + size_t i; + for (i = 0; i < state->n_crtcs; i++) + state->crtcs[i].filter.lifespan = LIBCOOPGAMMA_REMOVE; + update(state); + for (i = 0; i < state->n_crtcs; i++) + state->crtcs[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_DEATH; +} + +static int +coopgamma_set_temperature(coopgamma_state_t *state, const color_setting_t *setting) +{ + libcoopgamma_filter_t *filter; + libcoopgamma_filter_t *last_filter = NULL; + size_t i; + + for (i = 0; i < state->n_crtcs; i++, last_filter = filter) { + filter = &state->crtcs[i].filter; + + /* Copy ramps for previous CRTC if its ramps is of same size and depth */ + if (last_filter != NULL && + last_filter->ramps.u8.red_size == filter->ramps.u8.red_size && + last_filter->ramps.u8.green_size == filter->ramps.u8.green_size && + last_filter->ramps.u8.blue_size == filter->ramps.u8.blue_size) { + memcpy(filter->ramps.u8.red, last_filter->ramps.u8.red, + state->crtcs[i].rampsize); + continue; + } + + /* Otherwise, create calculate the ramps */ + memcpy(filter->ramps.u8.red, state->crtcs[i].plain_ramps.u8.red, + state->crtcs[i].rampsize); + switch (filter->depth) { +#define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ + case DEPTH:\ + colorramp_fill_##SUFFIX((void *)(filter->ramps.u8.red),\ + (void *)(filter->ramps.u8.green),\ + (void *)(filter->ramps.u8.blue),\ + filter->ramps.u8.red_size,\ + filter->ramps.u8.green_size,\ + filter->ramps.u8.blue_size,\ + setting);\ + break; + LIST_RAMPS_STOP_VALUE_TYPES +#undef X + default: + abort(); + } + } + + return update(state); +} + + +const gamma_method_t coopgamma_gamma_method = { + "coopgamma", 1, + (gamma_method_init_func *)coopgamma_init, + (gamma_method_start_func *)coopgamma_start, + (gamma_method_free_func *)coopgamma_free, + (gamma_method_print_help_func *)coopgamma_print_help, + (gamma_method_set_option_func *)coopgamma_set_option, + (gamma_method_restore_func *)coopgamma_restore, + (gamma_method_set_temperature_func *)coopgamma_set_temperature +}; diff --git a/src/gamma-coopgamma.h b/src/gamma-coopgamma.h new file mode 100644 index 0000000..a46f863 --- /dev/null +++ b/src/gamma-coopgamma.h @@ -0,0 +1,29 @@ +/* gamma-coopgamma.h -- coopgamma gamma adjustment header + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2016, 2025 Mattias Andrée <m@maandree.se> +*/ + +#ifndef REDSHIFT_GAMMA_COOPGAMMA_H +#define REDSHIFT_GAMMA_COOPGAMMA_H + +#include <libcoopgamma.h> + +#include "redshift.h" + +extern const gamma_method_t coopgamma_gamma_method; + +#endif /* ! REDSHIFT_GAMMA_COOPGAMMA_H */ diff --git a/src/gamma-drm.c b/src/gamma-drm.c index d431395..dec7074 100644 --- a/src/gamma-drm.c +++ b/src/gamma-drm.c @@ -14,14 +14,18 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Mattias Andrée <maandree@member.fsf.org> + Copyright (c) 2014 Mattias Andrée <m@maandree.se> + Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> -#include <alloca.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -38,29 +42,54 @@ #define O_CLOEXEC 02000000 #endif +#include <xf86drm.h> +#include <xf86drmMode.h> + #include "gamma-drm.h" #include "colorramp.h" -int -drm_init(drm_state_t *state) +typedef struct { + int crtc_num; + int crtc_id; + int gamma_size; + uint16_t* r_gamma; + uint16_t* g_gamma; + uint16_t* b_gamma; +} drm_crtc_state_t; + +typedef struct { + int card_num; + int crtc_num; + int fd; + drmModeRes* res; + drm_crtc_state_t* crtcs; +} drm_state_t; + + +static int +drm_init(drm_state_t **state) { /* Initialize state. */ - state->card_num = 0; - state->crtc_num = -1; - state->fd = -1; - state->res = NULL; - state->crtcs = NULL; + *state = malloc(sizeof(drm_state_t)); + if (*state == NULL) return -1; + + drm_state_t *s = *state; + s->card_num = 0; + s->crtc_num = -1; + s->fd = -1; + s->res = NULL; + s->crtcs = NULL; return 0; } -int -drm_start(drm_state_t *state) +static int +drm_start(drm_state_t *state, program_mode_t mode) { /* Acquire access to a graphics card. */ long maxlen = strlen(DRM_DIR_NAME) + strlen(DRM_DEV_NAME) + 10; - char *pathname = alloca(maxlen * sizeof(char)); + char pathname[maxlen]; sprintf(pathname, DRM_DEV_NAME, DRM_DIR_NAME, state->card_num); @@ -69,6 +98,8 @@ drm_start(drm_state_t *state) /* TODO check if access permissions, normally root or membership of the video group is required. */ perror("open"); + fprintf(stderr, _("Failed to open DRM device: %s\n"), + pathname); return -1; } @@ -171,7 +202,7 @@ drm_start(drm_state_t *state) return 0; } -void +static void drm_restore(drm_state_t *state) { drm_crtc_state_t *crtcs = state->crtcs; @@ -184,14 +215,13 @@ drm_restore(drm_state_t *state) } } -void +static void drm_free(drm_state_t *state) { if (state->crtcs != NULL) { drm_crtc_state_t *crtcs = state->crtcs; while (crtcs->crtc_num >= 0) { - if (crtcs->r_gamma != NULL) - free(crtcs->r_gamma); + free(crtcs->r_gamma); crtcs->crtc_num = -1; crtcs++; } @@ -206,9 +236,11 @@ drm_free(drm_state_t *state) close(state->fd); state->fd = -1; } + + free(state); } -void +static void drm_print_help(FILE *f) { fputs(_("Adjust gamma ramps with Direct Rendering Manager.\n"), f); @@ -221,7 +253,7 @@ drm_print_help(FILE *f) fputs("\n", f); } -int +static int drm_set_option(drm_state_t *state, const char *key, const char *value) { if (strcasecmp(key, "card") == 0) { @@ -240,11 +272,12 @@ drm_set_option(drm_state_t *state, const char *key, const char *value) return 0; } -int -drm_set_temperature(drm_state_t *state, const color_setting_t *setting) +static int +drm_set_temperature( + drm_state_t *state, const color_setting_t *setting, int preserve) { drm_crtc_state_t *crtcs = state->crtcs; - int last_gamma_size = 0; + uint32_t last_gamma_size = 0; uint16_t *r_gamma = NULL; uint16_t *g_gamma = NULL; uint16_t *b_gamma = NULL; @@ -270,16 +303,16 @@ drm_set_temperature(drm_state_t *state, const color_setting_t *setting) } /* Initialize gamma ramps to pure state */ - int ramp_size = crtcs->gamma_size; - for (int i = 0; i < ramp_size; i++) { + uint32_t ramp_size = crtcs->gamma_size; + for (uint32_t i = 0; i < ramp_size; i++) { uint16_t value = (double)i/ramp_size * (UINT16_MAX+1); r_gamma[i] = value; g_gamma[i] = value; b_gamma[i] = value; } - colorramp_fill(r_gamma, g_gamma, b_gamma, crtcs->gamma_size, - setting); + colorramp_fill_u16(r_gamma, g_gamma, b_gamma, crtcs->gamma_size, + crtcs->gamma_size, crtcs->gamma_size, setting); drmModeCrtcSetGamma(state->fd, crtcs->crtc_id, crtcs->gamma_size, r_gamma, g_gamma, b_gamma); } @@ -288,3 +321,15 @@ drm_set_temperature(drm_state_t *state, const color_setting_t *setting) return 0; } + + +const gamma_method_t drm_gamma_method = { + "drm", 0, + (gamma_method_init_func *)drm_init, + (gamma_method_start_func *)drm_start, + (gamma_method_free_func *)drm_free, + (gamma_method_print_help_func *)drm_print_help, + (gamma_method_set_option_func *)drm_set_option, + (gamma_method_restore_func *)drm_restore, + (gamma_method_set_temperature_func *)drm_set_temperature +}; diff --git a/src/gamma-drm.h b/src/gamma-drm.h index ae97d00..96ee10c 100644 --- a/src/gamma-drm.h +++ b/src/gamma-drm.h @@ -14,48 +14,15 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Mattias Andrée <maandree@member.fsf.org> + Copyright (c) 2014 Mattias Andrée <m@maandree.se> + Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_GAMMA_DRM_H #define REDSHIFT_GAMMA_DRM_H -#include <stdint.h> - -#include <xf86drm.h> -#include <xf86drmMode.h> - #include "redshift.h" - -typedef struct { - int crtc_num; - int crtc_id; - int gamma_size; - uint16_t* r_gamma; - uint16_t* g_gamma; - uint16_t* b_gamma; -} drm_crtc_state_t; - -typedef struct { - int card_num; - int crtc_num; - int fd; - drmModeRes* res; - drm_crtc_state_t* crtcs; -} drm_state_t; - - -int drm_init(drm_state_t *state); -int drm_start(drm_state_t *state); -void drm_free(drm_state_t *state); - -void drm_print_help(FILE *f); -int drm_set_option(drm_state_t *state, const char *key, const char *value); - -void drm_restore(drm_state_t *state); -int drm_set_temperature(drm_state_t *state, - const color_setting_t *setting); - +extern const gamma_method_t drm_gamma_method; #endif /* ! REDSHIFT_GAMMA_DRM_H */ diff --git a/src/gamma-dummy.c b/src/gamma-dummy.c index ba62d93..559fcea 100644 --- a/src/gamma-dummy.c +++ b/src/gamma-dummy.c @@ -14,9 +14,13 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2013-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include <stdio.h> #include <stdlib.h> @@ -30,46 +34,60 @@ #include "redshift.h" -int -gamma_dummy_init(void *state) +static int +gamma_dummy_init(void **state) { + *state = NULL; return 0; } -int -gamma_dummy_start(void *state) +static int +gamma_dummy_start(void *state, program_mode_t mode) { fputs(_("WARNING: Using dummy gamma method! Display will not be affected by this gamma method.\n"), stderr); return 0; } -void +static void gamma_dummy_restore(void *state) { } -void +static void gamma_dummy_free(void *state) { } -void +static void gamma_dummy_print_help(FILE *f) { fputs(_("Does not affect the display but prints the color temperature to the terminal.\n"), f); fputs("\n", f); } -int +static int gamma_dummy_set_option(void *state, const char *key, const char *value) { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; } -int -gamma_dummy_set_temperature(void *state, const color_setting_t *setting) +static int +gamma_dummy_set_temperature( + void *state, const color_setting_t *setting, int preserve) { printf(_("Temperature: %i\n"), setting->temperature); return 0; } + + +const gamma_method_t dummy_gamma_method = { + "dummy", 0, + (gamma_method_init_func *)gamma_dummy_init, + (gamma_method_start_func *)gamma_dummy_start, + (gamma_method_free_func *)gamma_dummy_free, + (gamma_method_print_help_func *)gamma_dummy_print_help, + (gamma_method_set_option_func *)gamma_dummy_set_option, + (gamma_method_restore_func *)gamma_dummy_restore, + (gamma_method_set_temperature_func *)gamma_dummy_set_temperature +}; diff --git a/src/gamma-dummy.h b/src/gamma-dummy.h index 3e58ec1..c610d94 100644 --- a/src/gamma-dummy.h +++ b/src/gamma-dummy.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2013-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_GAMMA_DUMMY_H @@ -22,17 +22,6 @@ #include "redshift.h" - -int gamma_dummy_init(void *state); -int gamma_dummy_start(void *state); -void gamma_dummy_free(void *state); - -void gamma_dummy_print_help(FILE *f); -int gamma_dummy_set_option(void *state, const char *key, const char *value); - -void gamma_dummy_restore(void *state); -int gamma_dummy_set_temperature(void *state, - const color_setting_t *setting); - +extern const gamma_method_t dummy_gamma_method; #endif /* ! REDSHIFT_GAMMA_DUMMY_H */ diff --git a/src/gamma-quartz.c b/src/gamma-quartz.c index 6691c91..74ceaf0 100644 --- a/src/gamma-quartz.c +++ b/src/gamma-quartz.c @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifdef HAVE_CONFIG_H @@ -37,19 +37,33 @@ #include "colorramp.h" -int -quartz_init(quartz_state_t *state) +typedef struct { + CGDirectDisplayID display; + uint32_t ramp_size; + float *saved_ramps; +} quartz_display_state_t; + +typedef struct { + quartz_display_state_t *displays; + uint32_t display_count; +} quartz_state_t; + + +static int +quartz_init(quartz_state_t **state) { - state->preserve = 0; - state->displays = NULL; + *state = malloc(sizeof(quartz_state_t)); + if (*state == NULL) return -1; + + quartz_state_t *s = *state; + s->displays = NULL; return 0; } -int -quartz_start(quartz_state_t *state) +static int +quartz_start(quartz_state_t *state, program_mode_t mode) { - int r; CGError error; uint32_t display_count; @@ -132,13 +146,13 @@ quartz_start(quartz_state_t *state) return 0; } -void +static void quartz_restore(quartz_state_t *state) { CGDisplayRestoreColorSyncSettings(); } -void +static void quartz_free(quartz_state_t *state) { if (state->displays != NULL) { @@ -147,27 +161,24 @@ quartz_free(quartz_state_t *state) } } free(state->displays); + free(state); } -void +static void quartz_print_help(FILE *f) { - fputs(_("Adjust gamma ramps on OSX using Quartz.\n"), f); - fputs("\n", f); - - /* TRANSLATORS: Quartz help output - left column must not be translated */ - fputs(_(" preserve={0,1}\tWhether existing gamma should be" - " preserved\n"), - f); + fputs(_("Adjust gamma ramps on macOS using Quartz.\n"), f); fputs("\n", f); } -int +static int quartz_set_option(quartz_state_t *state, const char *key, const char *value) { if (strcasecmp(key, "preserve") == 0) { - state->preserve = atoi(value); + fprintf(stderr, _("Parameter `%s` is now always on; " + " Use the `%s` command-line option" + " to disable.\n"), + key, "-P"); } else { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; @@ -177,10 +188,12 @@ quartz_set_option(quartz_state_t *state, const char *key, const char *value) } static void -quartz_set_temperature_for_display(quartz_state_t *state, int display, - const color_setting_t *setting) +quartz_set_temperature_for_display( + quartz_state_t *state, int display_index, + const color_setting_t *setting, int preserve) { - uint32_t ramp_size = state->displays[display].ramp_size; + CGDirectDisplayID display = state->displays[display_index].display; + uint32_t ramp_size = state->displays[display_index].ramp_size; /* Create new gamma ramps */ float *gamma_ramps = malloc(3*ramp_size*sizeof(float)); @@ -193,9 +206,9 @@ quartz_set_temperature_for_display(quartz_state_t *state, int display, float *gamma_g = &gamma_ramps[1*ramp_size]; float *gamma_b = &gamma_ramps[2*ramp_size]; - if (state->preserve) { + if (preserve) { /* Initialize gamma ramps from saved state */ - memcpy(gamma_ramps, state->displays[display].saved_ramps, + memcpy(gamma_ramps, state->displays[display_index].saved_ramps, 3*ramp_size*sizeof(float)); } else { /* Initialize gamma ramps to pure state */ @@ -208,7 +221,7 @@ quartz_set_temperature_for_display(quartz_state_t *state, int display, } colorramp_fill_float(gamma_r, gamma_g, gamma_b, ramp_size, - setting); + ramp_size, ramp_size, setting); CGError error = CGSetDisplayTransferByTable(display, ramp_size, @@ -221,13 +234,26 @@ quartz_set_temperature_for_display(quartz_state_t *state, int display, free(gamma_ramps); } -int -quartz_set_temperature(quartz_state_t *state, - const color_setting_t *setting) +static int +quartz_set_temperature( + quartz_state_t *state, const color_setting_t *setting, int preserve) { for (int i = 0; i < state->display_count; i++) { - quartz_set_temperature_for_display(state, i, setting); + quartz_set_temperature_for_display( + state, i, setting, preserve); } return 0; } + + +const gamma_method_t quartz_gamma_method = { + "quartz", 1, + (gamma_method_init_func *)quartz_init, + (gamma_method_start_func *)quartz_start, + (gamma_method_free_func *)quartz_free, + (gamma_method_print_help_func *)quartz_print_help, + (gamma_method_set_option_func *)quartz_set_option, + (gamma_method_restore_func *)quartz_restore, + (gamma_method_set_temperature_func *)quartz_set_temperature +}; diff --git a/src/gamma-quartz.h b/src/gamma-quartz.h index cd29d54..9a40137 100644 --- a/src/gamma-quartz.h +++ b/src/gamma-quartz.h @@ -14,43 +14,14 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_GAMMA_QUARTZ_H #define REDSHIFT_GAMMA_QUARTZ_H -#include <stdint.h> - -#include <ApplicationServices/ApplicationServices.h> - #include "redshift.h" - -typedef struct { - CGDirectDisplayID display; - uint32_t ramp_size; - float *saved_ramps; -} quartz_display_state_t; - -typedef struct { - quartz_display_state_t *displays; - uint32_t display_count; - int preserve; -} quartz_state_t; - - -int quartz_init(quartz_state_t *state); -int quartz_start(quartz_state_t *state); -void quartz_free(quartz_state_t *state); - -void quartz_print_help(FILE *f); -int quartz_set_option(quartz_state_t *state, const char *key, - const char *value); - -void quartz_restore(quartz_state_t *state); -int quartz_set_temperature(quartz_state_t *state, - const color_setting_t *setting); - +extern const gamma_method_t quartz_gamma_method; #endif /* ! REDSHIFT_GAMMA_QUARTZ_H */ diff --git a/src/gamma-randr.c b/src/gamma-randr.c index 0594332..358ab58 100644 --- a/src/gamma-randr.c +++ b/src/gamma-randr.c @@ -14,13 +14,18 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> +#include <errno.h> #ifdef ENABLE_NLS # include <libintl.h> @@ -41,29 +46,50 @@ #define RANDR_VERSION_MINOR 3 -int -randr_init(randr_state_t *state) +typedef struct { + xcb_randr_crtc_t crtc; + unsigned int ramp_size; + uint16_t *saved_ramps; +} randr_crtc_state_t; + +typedef struct { + xcb_connection_t *conn; + xcb_screen_t *screen; + int preferred_screen; + int screen_num; + int crtc_num_count; + int* crtc_num; + unsigned int crtc_count; + randr_crtc_state_t *crtcs; +} randr_state_t; + + +static int +randr_init(randr_state_t **state) { /* Initialize state. */ - state->screen_num = -1; - state->crtc_num = -1; + *state = malloc(sizeof(randr_state_t)); + if (*state == NULL) return -1; - state->crtc_count = 0; - state->crtcs = NULL; + randr_state_t *s = *state; + s->screen_num = -1; + s->crtc_num = NULL; - state->preserve = 0; + s->crtc_num_count = 0; + s->crtc_count = 0; + s->crtcs = NULL; xcb_generic_error_t *error; /* Open X server connection */ - state->conn = xcb_connect(NULL, &state->preferred_screen); + s->conn = xcb_connect(NULL, &s->preferred_screen); /* Query RandR version */ xcb_randr_query_version_cookie_t ver_cookie = - xcb_randr_query_version(state->conn, RANDR_VERSION_MAJOR, + xcb_randr_query_version(s->conn, RANDR_VERSION_MAJOR, RANDR_VERSION_MINOR); xcb_randr_query_version_reply_t *ver_reply = - xcb_randr_query_version_reply(state->conn, ver_cookie, &error); + xcb_randr_query_version_reply(s->conn, ver_cookie, &error); /* TODO What does it mean when both error and ver_reply is NULL? Apparently, we have to check both to avoid seg faults. */ @@ -71,7 +97,8 @@ randr_init(randr_state_t *state) int ec = (error != 0) ? error->error_code : -1; fprintf(stderr, _("`%s' returned error %d\n"), "RANDR Query Version", ec); - xcb_disconnect(state->conn); + xcb_disconnect(s->conn); + free(s); return -1; } @@ -80,7 +107,8 @@ randr_init(randr_state_t *state) fprintf(stderr, _("Unsupported RANDR version (%u.%u)\n"), ver_reply->major_version, ver_reply->minor_version); free(ver_reply); - xcb_disconnect(state->conn); + xcb_disconnect(s->conn); + free(s); return -1; } @@ -89,8 +117,8 @@ randr_init(randr_state_t *state) return 0; } -int -randr_start(randr_state_t *state) +static int +randr_start(randr_state_t *state, program_mode_t mode) { xcb_generic_error_t *error; @@ -226,7 +254,7 @@ randr_start(randr_state_t *state) return 0; } -void +static void randr_restore(randr_state_t *state) { xcb_generic_error_t *error; @@ -255,7 +283,7 @@ randr_restore(randr_state_t *state) } } -void +static void randr_free(randr_state_t *state) { /* Free CRTC state */ @@ -263,12 +291,15 @@ randr_free(randr_state_t *state) free(state->crtcs[i].saved_ramps); } free(state->crtcs); + free(state->crtc_num); /* Close connection */ xcb_disconnect(state->conn); + + free(state); } -void +static void randr_print_help(FILE *f) { fputs(_("Adjust gamma ramps with the X RANDR extension.\n"), f); @@ -277,22 +308,67 @@ randr_print_help(FILE *f) /* TRANSLATORS: RANDR help output left column must not be translated */ fputs(_(" screen=N\t\tX screen to apply adjustments to\n" - " crtc=N\t\tCRTC to apply adjustments to\n" - " preserve={0,1}\tWhether existing gamma should be" - " preserved\n"), + " crtc=N\tList of comma separated CRTCs to apply" + " adjustments to\n"), f); fputs("\n", f); } -int +static int randr_set_option(randr_state_t *state, const char *key, const char *value) { if (strcasecmp(key, "screen") == 0) { state->screen_num = atoi(value); } else if (strcasecmp(key, "crtc") == 0) { - state->crtc_num = atoi(value); + char *tail; + + /* Check how many crtcs are configured */ + const char *local_value = value; + while (1) { + errno = 0; + int parsed = strtol(local_value, &tail, 0); + if (parsed == 0 && (errno != 0 || + tail == local_value)) { + fprintf(stderr, _("Unable to read screen" + " number: `%s'.\n"), value); + return -1; + } else { + state->crtc_num_count += 1; + } + local_value = tail; + + if (*local_value == ',') { + local_value += 1; + } else if (*local_value == '\0') { + break; + } + } + + /* Configure all given crtcs */ + state->crtc_num = calloc(state->crtc_num_count, sizeof(int)); + local_value = value; + for (int i = 0; i < state->crtc_num_count; i++) { + errno = 0; + int parsed = strtol(local_value, &tail, 0); + if (parsed == 0 && (errno != 0 || + tail == local_value)) { + return -1; + } else { + state->crtc_num[i] = parsed; + } + local_value = tail; + + if (*local_value == ',') { + local_value += 1; + } else if (*local_value == '\0') { + break; + } + } } else if (strcasecmp(key, "preserve") == 0) { - state->preserve = atoi(value); + fprintf(stderr, _("Parameter `%s` is now always on; " + " Use the `%s` command-line option" + " to disable.\n"), + key, "-P"); } else { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; @@ -302,14 +378,15 @@ randr_set_option(randr_state_t *state, const char *key, const char *value) } static int -randr_set_temperature_for_crtc(randr_state_t *state, int crtc_num, - const color_setting_t *setting) +randr_set_temperature_for_crtc( + randr_state_t *state, int crtc_num, const color_setting_t *setting, + int preserve) { xcb_generic_error_t *error; - + if (crtc_num >= state->crtc_count || crtc_num < 0) { fprintf(stderr, _("CRTC %d does not exist. "), - state->crtc_num); + crtc_num); if (state->crtc_count > 1) { fprintf(stderr, _("Valid CRTCs are [0-%d].\n"), state->crtc_count-1); @@ -334,7 +411,7 @@ randr_set_temperature_for_crtc(randr_state_t *state, int crtc_num, uint16_t *gamma_g = &gamma_ramps[1*ramp_size]; uint16_t *gamma_b = &gamma_ramps[2*ramp_size]; - if (state->preserve) { + if (preserve) { /* Initialize gamma ramps from saved state */ memcpy(gamma_ramps, state->crtcs[crtc_num].saved_ramps, 3*ramp_size*sizeof(uint16_t)); @@ -348,8 +425,8 @@ randr_set_temperature_for_crtc(randr_state_t *state, int crtc_num, } } - colorramp_fill(gamma_r, gamma_g, gamma_b, ramp_size, - setting); + colorramp_fill_u16(gamma_r, gamma_g, gamma_b, ramp_size, + ramp_size, ramp_size, setting); /* Set new gamma ramps */ xcb_void_cookie_t gamma_set_cookie = @@ -370,24 +447,39 @@ randr_set_temperature_for_crtc(randr_state_t *state, int crtc_num, return 0; } -int -randr_set_temperature(randr_state_t *state, - const color_setting_t *setting) +static int +randr_set_temperature( + randr_state_t *state, const color_setting_t *setting, int preserve) { int r; - /* If no CRTC number has been specified, + /* If no CRTC numbers have been specified, set temperature on all CRTCs. */ - if (state->crtc_num < 0) { + if (state->crtc_num_count == 0) { for (int i = 0; i < state->crtc_count; i++) { - r = randr_set_temperature_for_crtc(state, i, - setting); + r = randr_set_temperature_for_crtc( + state, i, setting, preserve); if (r < 0) return -1; } } else { - return randr_set_temperature_for_crtc(state, state->crtc_num, - setting); + for (int i = 0; i < state->crtc_num_count; ++i) { + r = randr_set_temperature_for_crtc( + state, state->crtc_num[i], setting, preserve); + if (r < 0) return -1; + } } return 0; } + + +const gamma_method_t randr_gamma_method = { + "randr", 1, + (gamma_method_init_func *)randr_init, + (gamma_method_start_func *)randr_start, + (gamma_method_free_func *)randr_free, + (gamma_method_print_help_func *)randr_print_help, + (gamma_method_set_option_func *)randr_set_option, + (gamma_method_restore_func *)randr_restore, + (gamma_method_set_temperature_func *)randr_set_temperature +}; diff --git a/src/gamma-randr.h b/src/gamma-randr.h index 093c41f..0545d2f 100644 --- a/src/gamma-randr.h +++ b/src/gamma-randr.h @@ -14,49 +14,14 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_GAMMA_RANDR_H #define REDSHIFT_GAMMA_RANDR_H -#include <stdio.h> -#include <stdint.h> - -#include <xcb/xcb.h> -#include <xcb/randr.h> - #include "redshift.h" - -typedef struct { - xcb_randr_crtc_t crtc; - unsigned int ramp_size; - uint16_t *saved_ramps; -} randr_crtc_state_t; - -typedef struct { - xcb_connection_t *conn; - xcb_screen_t *screen; - int preferred_screen; - int preserve; - int screen_num; - int crtc_num; - unsigned int crtc_count; - randr_crtc_state_t *crtcs; -} randr_state_t; - - -int randr_init(randr_state_t *state); -int randr_start(randr_state_t *state); -void randr_free(randr_state_t *state); - -void randr_print_help(FILE *f); -int randr_set_option(randr_state_t *state, const char *key, const char *value); - -void randr_restore(randr_state_t *state); -int randr_set_temperature(randr_state_t *state, - const color_setting_t *setting); - +extern const gamma_method_t randr_gamma_method; #endif /* ! REDSHIFT_GAMMA_RANDR_H */ diff --git a/src/gamma-vidmode.c b/src/gamma-vidmode.c index 254d065..1ea585e 100644 --- a/src/gamma-vidmode.c +++ b/src/gamma-vidmode.c @@ -14,9 +14,13 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include <stdlib.h> #include <stdio.h> #include <stdint.h> @@ -37,27 +41,36 @@ #include "colorramp.h" -int -vidmode_init(vidmode_state_t *state) +typedef struct { + Display *display; + int screen_num; + int ramp_size; + uint16_t *saved_ramps; +} vidmode_state_t; + + +static int +vidmode_init(vidmode_state_t **state) { - state->screen_num = -1; - state->saved_ramps = NULL; + *state = malloc(sizeof(vidmode_state_t)); + if (*state == NULL) return -1; - state->preserve = 0; + vidmode_state_t *s = *state; + s->screen_num = -1; + s->saved_ramps = NULL; /* Open display */ - state->display = XOpenDisplay(NULL); - if (state->display == NULL) { - fprintf(stderr, _("X request failed: %s\n"), - "XOpenDisplay"); + s->display = XOpenDisplay(NULL); + if (s->display == NULL) { + fprintf(stderr, _("X request failed: %s\n"), "XOpenDisplay"); return -1; } return 0; } -int -vidmode_start(vidmode_state_t *state) +static int +vidmode_start(vidmode_state_t *state, program_mode_t mode) { int r; int screen_num = state->screen_num; @@ -113,7 +126,7 @@ vidmode_start(vidmode_state_t *state) return 0; } -void +static void vidmode_free(vidmode_state_t *state) { /* Free saved ramps */ @@ -121,9 +134,11 @@ vidmode_free(vidmode_state_t *state) /* Close display connection */ XCloseDisplay(state->display); + + free(state); } -void +static void vidmode_print_help(FILE *f) { fputs(_("Adjust gamma ramps with the X VidMode extension.\n"), f); @@ -131,20 +146,21 @@ vidmode_print_help(FILE *f) /* TRANSLATORS: VidMode help output left column must not be translated */ - fputs(_(" screen=N\t\tX screen to apply adjustments to\n" - " preserve={0,1}\tWhether existing gamma should be" - " preserved\n"), + fputs(_(" screen=N\t\tX screen to apply adjustments to\n"), f); fputs("\n", f); } -int +static int vidmode_set_option(vidmode_state_t *state, const char *key, const char *value) { if (strcasecmp(key, "screen") == 0) { state->screen_num = atoi(value); } else if (strcasecmp(key, "preserve") == 0) { - state->preserve = atoi(value); + fprintf(stderr, _("Parameter `%s` is now always on; " + " Use the `%s` command-line option" + " to disable.\n"), + key, "-P"); } else { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; @@ -153,7 +169,7 @@ vidmode_set_option(vidmode_state_t *state, const char *key, const char *value) return 0; } -void +static void vidmode_restore(vidmode_state_t *state) { uint16_t *gamma_r = &state->saved_ramps[0*state->ramp_size]; @@ -167,12 +183,12 @@ vidmode_restore(vidmode_state_t *state) if (!r) { fprintf(stderr, _("X request failed: %s\n"), "XF86VidModeSetGammaRamp"); - } + } } -int -vidmode_set_temperature(vidmode_state_t *state, - const color_setting_t *setting) +static int +vidmode_set_temperature( + vidmode_state_t *state, const color_setting_t *setting, int preserve) { int r; @@ -187,7 +203,7 @@ vidmode_set_temperature(vidmode_state_t *state, uint16_t *gamma_g = &gamma_ramps[1*state->ramp_size]; uint16_t *gamma_b = &gamma_ramps[2*state->ramp_size]; - if (state->preserve) { + if (preserve) { /* Initialize gamma ramps from saved state */ memcpy(gamma_ramps, state->saved_ramps, 3*state->ramp_size*sizeof(uint16_t)); @@ -202,8 +218,8 @@ vidmode_set_temperature(vidmode_state_t *state, } } - colorramp_fill(gamma_r, gamma_g, gamma_b, state->ramp_size, - setting); + colorramp_fill_u16(gamma_r, gamma_g, gamma_b, state->ramp_size, + state->ramp_size, state->ramp_size, setting); /* Set new gamma ramps */ r = XF86VidModeSetGammaRamp(state->display, state->screen_num, @@ -220,3 +236,15 @@ vidmode_set_temperature(vidmode_state_t *state, return 0; } + + +const gamma_method_t vidmode_gamma_method = { + "vidmode", 1, + (gamma_method_init_func *)vidmode_init, + (gamma_method_start_func *)vidmode_start, + (gamma_method_free_func *)vidmode_free, + (gamma_method_print_help_func *)vidmode_print_help, + (gamma_method_set_option_func *)vidmode_set_option, + (gamma_method_restore_func *)vidmode_restore, + (gamma_method_set_temperature_func *)vidmode_set_temperature +}; diff --git a/src/gamma-vidmode.h b/src/gamma-vidmode.h index 4b6cecc..6bad207 100644 --- a/src/gamma-vidmode.h +++ b/src/gamma-vidmode.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_GAMMA_VIDMODE_H @@ -22,31 +22,6 @@ #include "redshift.h" -#include <stdio.h> -#include <stdint.h> - -#include <X11/Xlib.h> - -typedef struct { - Display *display; - int preserve; - int screen_num; - int ramp_size; - uint16_t *saved_ramps; -} vidmode_state_t; - - -int vidmode_init(vidmode_state_t *state); -int vidmode_start(vidmode_state_t *state); -void vidmode_free(vidmode_state_t *state); - -void vidmode_print_help(FILE *f); -int vidmode_set_option(vidmode_state_t *state, const char *key, - const char *value); - -void vidmode_restore(vidmode_state_t *state); -int vidmode_set_temperature(vidmode_state_t *state, - const color_setting_t *setting); - +extern const gamma_method_t vidmode_gamma_method; #endif /* ! REDSHIFT_GAMMA_VIDMODE_H */ diff --git a/src/gamma-w32gdi.c b/src/gamma-w32gdi.c index c518fe6..da5461e 100644 --- a/src/gamma-w32gdi.c +++ b/src/gamma-w32gdi.c @@ -14,9 +14,13 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include <stdio.h> #include <stdlib.h> @@ -37,19 +41,28 @@ #include "colorramp.h" #define GAMMA_RAMP_SIZE 256 +#define MAX_ATTEMPTS 10 + +typedef struct { + WORD *saved_ramps; +} w32gdi_state_t; -int -w32gdi_init(w32gdi_state_t *state) + +static int +w32gdi_init(w32gdi_state_t **state) { - state->saved_ramps = NULL; - state->preserve = 0; + *state = malloc(sizeof(w32gdi_state_t)); + if (state == NULL) return -1; + + w32gdi_state_t *s = *state; + s->saved_ramps = NULL; return 0; } -int -w32gdi_start(w32gdi_state_t *state) +static int +w32gdi_start(w32gdi_state_t *state, program_mode_t mode) { BOOL r; @@ -90,33 +103,31 @@ w32gdi_start(w32gdi_state_t *state) return 0; } -void +static void w32gdi_free(w32gdi_state_t *state) { /* Free saved ramps */ free(state->saved_ramps); + + free(state); } -void +static void w32gdi_print_help(FILE *f) { fputs(_("Adjust gamma ramps with the Windows GDI.\n"), f); fputs("\n", f); - - /* TRANSLATORS: Windows GDI help output - left column must not be translated */ - fputs(_(" preserve={0,1}\tWhether existing gamma should be" - " preserved\n"), - f); - fputs("\n", f); } -int +static int w32gdi_set_option(w32gdi_state_t *state, const char *key, const char *value) { if (strcasecmp(key, "preserve") == 0) { - state->preserve = atoi(value); + fprintf(stderr, _("Parameter `%s` is now always on; " + " Use the `%s` command-line option" + " to disable.\n"), + key, "-P"); } else { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; @@ -125,7 +136,7 @@ w32gdi_set_option(w32gdi_state_t *state, const char *key, const char *value) return 0; } -void +static void w32gdi_restore(w32gdi_state_t *state) { /* Open device context */ @@ -136,16 +147,22 @@ w32gdi_restore(w32gdi_state_t *state) } /* Restore gamma ramps */ - BOOL r = SetDeviceGammaRamp(hDC, state->saved_ramps); + BOOL r = FALSE; + for (int i = 0; i < MAX_ATTEMPTS && !r; i++) { + /* We retry a few times before giving up because some + buggy drivers fail on the first invocation of + SetDeviceGammaRamp just to succeed on the second. */ + r = SetDeviceGammaRamp(hDC, state->saved_ramps); + } if (!r) fputs(_("Unable to restore gamma ramps.\n"), stderr); /* Release device context */ ReleaseDC(NULL, hDC); } -int -w32gdi_set_temperature(w32gdi_state_t *state, - const color_setting_t *setting) +static int +w32gdi_set_temperature( + w32gdi_state_t *state, const color_setting_t *setting, int preserve) { BOOL r; @@ -168,7 +185,7 @@ w32gdi_set_temperature(w32gdi_state_t *state, WORD *gamma_g = &gamma_ramps[1*GAMMA_RAMP_SIZE]; WORD *gamma_b = &gamma_ramps[2*GAMMA_RAMP_SIZE]; - if (state->preserve) { + if (preserve) { /* Initialize gamma ramps from saved state */ memcpy(gamma_ramps, state->saved_ramps, 3*GAMMA_RAMP_SIZE*sizeof(WORD)); @@ -183,15 +200,18 @@ w32gdi_set_temperature(w32gdi_state_t *state, } } - colorramp_fill(gamma_r, gamma_g, gamma_b, GAMMA_RAMP_SIZE, - setting); + colorramp_fill_u16(gamma_r, gamma_g, gamma_b, GAMMA_RAMP_SIZE, + GAMMA_RAMP_SIZE, GAMMA_RAMP_SIZE, setting); /* Set new gamma ramps */ - r = SetDeviceGammaRamp(hDC, gamma_ramps); + r = FALSE; + for (int i = 0; i < MAX_ATTEMPTS && !r; i++) { + /* We retry a few times before giving up because some + buggy drivers fail on the first invocation of + SetDeviceGammaRamp just to succeed on the second. */ + r = SetDeviceGammaRamp(hDC, gamma_ramps); + } if (!r) { - /* TODO it happens that SetDeviceGammaRamp returns FALSE on - occasions where the adjustment seems to be successful. - Does this only happen with multiple monitors connected? */ fputs(_("Unable to set gamma ramps.\n"), stderr); free(gamma_ramps); ReleaseDC(NULL, hDC); @@ -205,3 +225,15 @@ w32gdi_set_temperature(w32gdi_state_t *state, return 0; } + + +const gamma_method_t w32gdi_gamma_method = { + "wingdi", 1, + (gamma_method_init_func *)w32gdi_init, + (gamma_method_start_func *)w32gdi_start, + (gamma_method_free_func *)w32gdi_free, + (gamma_method_print_help_func *)w32gdi_print_help, + (gamma_method_set_option_func *)w32gdi_set_option, + (gamma_method_restore_func *)w32gdi_restore, + (gamma_method_set_temperature_func *)w32gdi_set_temperature +}; diff --git a/src/gamma-w32gdi.h b/src/gamma-w32gdi.h index 6e73cd1..ac6eb10 100644 --- a/src/gamma-w32gdi.h +++ b/src/gamma-w32gdi.h @@ -14,35 +14,14 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_GAMMA_W32GDI_H #define REDSHIFT_GAMMA_W32GDI_H -#include <windows.h> -#include <wingdi.h> - #include "redshift.h" - -typedef struct { - WORD *saved_ramps; - int preserve; -} w32gdi_state_t; - - -int w32gdi_init(w32gdi_state_t *state); -int w32gdi_start(w32gdi_state_t *state); -void w32gdi_free(w32gdi_state_t *state); - -void w32gdi_print_help(FILE *f); -int w32gdi_set_option(w32gdi_state_t *state, const char *key, - const char *value); - -void w32gdi_restore(w32gdi_state_t *state); -int w32gdi_set_temperature(w32gdi_state_t *state, - const color_setting_t *color); - +extern const gamma_method_t w32gdi_gamma_method; #endif /* ! REDSHIFT_GAMMA_W32GDI_H */ diff --git a/src/location-corelocation.h b/src/location-corelocation.h index 4b74382..4af302c 100644 --- a/src/location-corelocation.h +++ b/src/location-corelocation.h @@ -14,27 +14,14 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Jon Lund Steffense <jonlst@gmail.com> + Copyright (c) 2014-2017 Jon Lund Steffense <jonlst@gmail.com> */ #ifndef REDSHIFT_LOCATION_CORELOCATION_H #define REDSHIFT_LOCATION_CORELOCATION_H -#include <stdio.h> - #include "redshift.h" - -int location_corelocation_init(void *state); -int location_corelocation_start(void *state); -void location_corelocation_free(void *state); - -void location_corelocation_print_help(FILE *f); -int location_corelocation_set_option(void *state, - const char *key, const char *value); - -int location_corelocation_get_location(void *state, - location_t *location); - +extern const location_provider_t corelocation_location_provider; #endif /* ! REDSHIFT_LOCATION_CORELOCATION_H */ diff --git a/src/location-corelocation.m b/src/location-corelocation.m index 2f1768d..10d7acd 100644 --- a/src/location-corelocation.m +++ b/src/location-corelocation.m @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifdef HAVE_CONFIG_H @@ -25,9 +25,11 @@ #import <CoreLocation/CoreLocation.h> #include "location-corelocation.h" +#include "pipeutils.h" #include "redshift.h" #include <stdio.h> +#include <unistd.h> #ifdef ENABLE_NLS # include <libintl.h> @@ -37,128 +39,261 @@ #endif -@interface Delegate : NSObject <CLLocationManagerDelegate> +typedef struct { + NSThread *thread; + NSLock *lock; + int pipe_fd_read; + int pipe_fd_write; + int available; + int error; + float latitude; + float longitude; +} location_corelocation_state_t; + + +@interface LocationDelegate : NSObject <CLLocationManagerDelegate> @property (strong, nonatomic) CLLocationManager *locationManager; -@property (nonatomic) BOOL success; -@property (nonatomic) float latitude; -@property (nonatomic) float longitude; +@property (nonatomic) location_corelocation_state_t *state; @end -@implementation Delegate; +@implementation LocationDelegate; - (void)start { - self.locationManager = [[CLLocationManager alloc] init]; - self.locationManager.delegate = self; + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; + self.locationManager.distanceFilter = 50000; + self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer; + + CLAuthorizationStatus authStatus = + [CLLocationManager authorizationStatus]; + + if (authStatus != kCLAuthorizationStatusNotDetermined && + authStatus != kCLAuthorizationStatusAuthorized) { + fputs(_("Not authorized to obtain location" + " from CoreLocation.\n"), stderr); + [self markError]; + } else { + [self.locationManager startUpdatingLocation]; + } +} + +- (void)markError +{ + [self.state->lock lock]; - CLAuthorizationStatus authStatus = - [CLLocationManager authorizationStatus]; + self.state->error = 1; - if (authStatus != kCLAuthorizationStatusNotDetermined && - authStatus != kCLAuthorizationStatusAuthorized) { - fputs(_("Not authorized to obtain location" - " from CoreLocation.\n"), stderr); - CFRunLoopStop(CFRunLoopGetCurrent()); - } + [self.state->lock unlock]; - [self.locationManager startUpdatingLocation]; + pipeutils_signal(self.state->pipe_fd_write); } -- (void)stop +- (void)markUnavailable { - [self.locationManager stopUpdatingLocation]; - CFRunLoopStop(CFRunLoopGetCurrent()); + [self.state->lock lock]; + + self.state->available = 0; + + [self.state->lock unlock]; + + pipeutils_signal(self.state->pipe_fd_write); } - (void)locationManager:(CLLocationManager *)manager - didUpdateLocations:(NSArray *)locations + didUpdateLocations:(NSArray *)locations { - CLLocation *newLocation = [locations firstObject]; - self.latitude = newLocation.coordinate.latitude; - self.longitude = newLocation.coordinate.longitude; - self.success = YES; + CLLocation *newLocation = [locations firstObject]; + + [self.state->lock lock]; + + self.state->latitude = newLocation.coordinate.latitude; + self.state->longitude = newLocation.coordinate.longitude; + self.state->available = 1; - [self stop]; + [self.state->lock unlock]; + + pipeutils_signal(self.state->pipe_fd_write); } - (void)locationManager:(CLLocationManager *)manager - didFailWithError:(NSError *)error + didFailWithError:(NSError *)error { - fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"), - [[error localizedDescription] UTF8String]); - [self stop]; + fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"), + [[error localizedDescription] UTF8String]); + if ([error code] == kCLErrorDenied) { + [self markError]; + } else { + [self markUnavailable]; + } } - (void)locationManager:(CLLocationManager *)manager - didChangeAuthorizationStatus:(CLAuthorizationStatus)status + didChangeAuthorizationStatus:(CLAuthorizationStatus)status { - if (status == kCLAuthorizationStatusNotDetermined) { - fputs(_("Waiting for authorization to obtain location...\n"), - stderr); - } else if (status != kCLAuthorizationStatusAuthorized) { - fputs(_("Request for location was not authorized!\n"), - stderr); - [self stop]; - } + if (status == kCLAuthorizationStatusNotDetermined) { + fputs(_("Waiting for authorization to obtain location...\n"), stderr); + } else if (status != kCLAuthorizationStatusAuthorized) { + fputs(_("Request for location was not authorized!\n"), stderr); + [self markError]; + } } @end -int -location_corelocation_init(void *state) +// Callback when the pipe is closed. +// +// Stops the run loop causing the thread to end. +static void +pipe_close_callback( + CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info) { - return 0; + CFFileDescriptorInvalidate(fdref); + CFRelease(fdref); + + CFRunLoopStop(CFRunLoopGetCurrent()); } -int -location_corelocation_start(void *state) + +@interface LocationThread : NSThread +@property (nonatomic) location_corelocation_state_t *state; +@end + +@implementation LocationThread; + +// Run loop for location provider thread. +- (void)main { - return 0; + @autoreleasepool { + LocationDelegate *locationDelegate = [[LocationDelegate alloc] init]; + locationDelegate.state = self.state; + + // Start the location delegate on the run loop in this thread. + [locationDelegate performSelector:@selector(start) + withObject:nil afterDelay:0]; + + // Create a callback that is triggered when the pipe is closed. This will + // trigger the main loop to quit and the thread to stop. + CFFileDescriptorRef fdref = CFFileDescriptorCreate( + kCFAllocatorDefault, self.state->pipe_fd_write, false, + pipe_close_callback, NULL); + CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); + CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource( + kCFAllocatorDefault, fdref, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + + // Run the loop + CFRunLoopRun(); + + close(self.state->pipe_fd_write); + } } -void -location_corelocation_free(void *state) +@end + + +static int +location_corelocation_init(location_corelocation_state_t **state) { + *state = malloc(sizeof(location_corelocation_state_t)); + if (*state == NULL) return -1; + return 0; } -void +static int +location_corelocation_start(location_corelocation_state_t *state) +{ + state->pipe_fd_read = -1; + state->pipe_fd_write = -1; + + state->available = 0; + state->error = 0; + state->latitude = 0; + state->longitude = 0; + + int pipefds[2]; + int r = pipeutils_create_nonblocking(pipefds); + if (r < 0) { + fputs(_("Failed to start CoreLocation provider!\n"), stderr); + return -1; + } + + state->pipe_fd_read = pipefds[0]; + state->pipe_fd_write = pipefds[1]; + + pipeutils_signal(state->pipe_fd_write); + + state->lock = [[NSLock alloc] init]; + + LocationThread *thread = [[LocationThread alloc] init]; + thread.state = state; + [thread start]; + state->thread = thread; + + return 0; +} + +static void +location_corelocation_free(location_corelocation_state_t *state) +{ + if (state->pipe_fd_read != -1) { + close(state->pipe_fd_read); + } + + free(state); +} + +static void location_corelocation_print_help(FILE *f) { - fputs(_("Use the location as discovered by the Corelocation provider.\n"), f); - fputs("\n", f); + fputs(_("Use the location as discovered by the Corelocation provider.\n"), f); + fputs("\n", f); +} - fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" - "which means it has to be restarted to take notice after travel.\n"), - "CoreLocation"); - fputs("\n", f); +static int +location_corelocation_set_option( + location_corelocation_state_t *state, const char *key, const char *value) +{ + fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); + return -1; } -int -location_corelocation_set_option(void *state, - const char *key, const char *value) +static int +location_corelocation_get_fd(location_corelocation_state_t *state) { - fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); - return -1; + return state->pipe_fd_read; } -int -location_corelocation_get_location(void *state, - location_t *location) +static int +location_corelocation_handle( + location_corelocation_state_t *state, + location_t *location, int *available) { - int result = -1; + pipeutils_handle_signal(state->pipe_fd_read); - @autoreleasepool { - Delegate *delegate = [[Delegate alloc] init]; - [delegate performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; - CFRunLoopRun(); + [state->lock lock]; - if (delegate.success) { - location->lat = delegate.latitude; - location->lon = delegate.longitude; - result = 0; - } - } + int error = state->error; + location->lat = state->latitude; + location->lon = state->longitude; + *available = state->available; - return result; + [state->lock unlock]; + + if (error) return -1; + + return 0; } + + +const location_provider_t corelocation_location_provider = { + "corelocation", + (location_provider_init_func *)location_corelocation_init, + (location_provider_start_func *)location_corelocation_start, + (location_provider_free_func *)location_corelocation_free, + (location_provider_print_help_func *)location_corelocation_print_help, + (location_provider_set_option_func *)location_corelocation_set_option, + (location_provider_get_fd_func *)location_corelocation_get_fd, + (location_provider_handle_func *)location_corelocation_handle +}; diff --git a/src/location-geoclue.c b/src/location-geoclue.c deleted file mode 100644 index b2616bf..0000000 --- a/src/location-geoclue.c +++ /dev/null @@ -1,218 +0,0 @@ -/* location-geoclue.c -- Geoclue location provider source - This file is part of Redshift. - - Redshift is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Redshift is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Redshift. If not, see <http://www.gnu.org/licenses/>. - - Copyright (c) 2010 Mathieu Trudel-Lapierre <mathieu-tl@ubuntu.com> -*/ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <geoclue/geoclue-master.h> -#include <geoclue/geoclue-position.h> - -#include <glib.h> -#include <glib-object.h> - -#include "location-geoclue.h" -#include "redshift.h" - -#ifdef ENABLE_NLS -# include <libintl.h> -# define _(s) gettext(s) -#else -# define _(s) s -#endif - -#define DEFAULT_PROVIDER "org.freedesktop.Geoclue.Providers.UbuntuGeoIP" -#define DEFAULT_PROVIDER_PATH "/org/freedesktop/Geoclue/Providers/UbuntuGeoIP" - -int -location_geoclue_init(location_geoclue_state_t *state) -{ -#if !GLIB_CHECK_VERSION(2, 35, 0) - g_type_init(); -#endif - - state->position = NULL; - state->provider = NULL; - state->provider_path = NULL; - - return 0; -} - -int -location_geoclue_start(location_geoclue_state_t *state) -{ - if (state->provider && state->provider_path) { - state->position = geoclue_position_new(state->provider, - state->provider_path); - } else { - if (getenv("DISPLAY") == NULL || *getenv("DISPLAY") == '\0') { - /* TODO This (hack) should be removed when GeoClue has been patched. */ - putenv("DISPLAY=:0"); - } - GError *error = NULL; - GeoclueMaster *master = geoclue_master_get_default(); - GeoclueMasterClient *client = geoclue_master_create_client(master, - NULL, &error); - g_object_unref(master); - - if (client == NULL) { - if (error != NULL) { - g_printerr(_("Unable to obtain master client: %s\n"), - error->message); - g_error_free(error); - } else { - g_printerr(_("Unable to obtain master client\n")); - } - return -1; - } - - if (!geoclue_master_client_set_requirements(client, - GEOCLUE_ACCURACY_LEVEL_REGION, - 0, FALSE, - GEOCLUE_RESOURCE_NETWORK, - &error)) { - if (error != NULL) { - g_printerr(_("Can't set requirements for master: %s\n"), - error->message); - g_error_free(error); - } else { - g_printerr(_("Can't set requirements for master\n")); - } - g_object_unref(client); - - return -1; - } - - state->position = geoclue_master_client_create_position(client, NULL); - - g_object_unref(client); - } - - gchar *name = NULL; - - if (geoclue_provider_get_provider_info(GEOCLUE_PROVIDER(state->position), - &name, NULL, NULL)) { - fprintf(stdout, _("Started Geoclue provider `%s'.\n"), name); - g_free(name); - } else { - fputs(_("Could not find a usable Geoclue provider.\n"), stderr); - fputs(_("Try setting name and path to specify which to use.\n"), stderr); - return -1; - } - - return 0; -} - -void -location_geoclue_free(location_geoclue_state_t *state) -{ - if (state->position != NULL) g_object_unref(state->position); - if (state->provider != NULL) free(state->provider); - if (state->provider_path != NULL) free(state->provider_path); -} - -void -location_geoclue_print_help(FILE *f) -{ - fputs(_("Use the location as discovered by a Geoclue provider.\n"), f); - fputs("\n", f); - - /* TRANSLATORS: Geoclue help output - left column must not be translated */ - fputs(_(" name=N\tName of Geoclue provider (or `default')\n" - " path=N\tPath of Geoclue provider (or `default')\n"), f); - fputs("\n", f); - fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" - "which means it has to be restarted to take notice after travel.\n"), - "GeoClue"); - fputs("\n", f); -} - -int -location_geoclue_set_option(location_geoclue_state_t *state, - const char *key, const char *value) -{ - const char *provider = NULL; - const char *path = NULL; - - /* Parse string value */ - if (strcasecmp(key, "name") == 0) { - if (strcasecmp(value, "default") == 0) { - provider = DEFAULT_PROVIDER; - } else { - provider = value; - } - - state->provider = strdup(provider); - if (state->provider == NULL) { - perror("strdup"); - return -1; - } - } else if (strcasecmp(key, "path") == 0) { - if (value != NULL && strcasecmp(value, "default") == 0) { - path = DEFAULT_PROVIDER_PATH; - } else { - path = value; - } - - state->provider_path = strdup(path); - if (state->provider_path == NULL) { - perror("strdup"); - return -1; - } - } else { - fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); - return -1; - } - - return 0; -} - -int -location_geoclue_get_location(location_geoclue_state_t *state, - location_t *location) -{ - GeocluePositionFields fields; - GError *error = NULL; - double latitude = 0, longitude = 0; - - fields = geoclue_position_get_position(state->position, NULL, - &latitude, &longitude, NULL, - NULL, &error); - if (error) { - g_printerr(_("Could not get location: %s.\n"), error->message); - g_error_free(error); - return -1; - } - - if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE && - fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) { - fprintf(stdout, _("According to the geoclue provider" - " we're at: %.2f, %.2f\n"), - latitude, longitude); - } else { - g_warning(_("Provider does not have a valid location available.")); - return -1; - } - - location->lat = latitude; - location->lon = longitude; - - return 0; -} diff --git a/src/location-geoclue.h b/src/location-geoclue.h deleted file mode 100644 index 3847ee2..0000000 --- a/src/location-geoclue.h +++ /dev/null @@ -1,46 +0,0 @@ -/* location-geoclue.h -- Geoclue location provider header - This file is part of Redshift. - - Redshift is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Redshift is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Redshift. If not, see <http://www.gnu.org/licenses/>. - - Copyright (c) 2010 Mathieu Trudel-Lapierre <mathieu-tl@ubuntu.com> -*/ - -#ifndef REDSHIFT_LOCATION_GEOCLUE_H -#define REDSHIFT_LOCATION_GEOCLUE_H - -#include <stdio.h> -#include <geoclue/geoclue-position.h> - -#include "redshift.h" - -typedef struct { - GeocluePosition *position; /* main geoclue object */ - char *provider; /* name of a geoclue provider */ - char *provider_path; /* path of the geoclue provider */ -} location_geoclue_state_t; - -int location_geoclue_init(location_geoclue_state_t *state); -int location_geoclue_start(location_geoclue_state_t *state); -void location_geoclue_free(location_geoclue_state_t *state); - -void location_geoclue_print_help(FILE *f); -int location_geoclue_set_option(location_geoclue_state_t *state, - const char *key, const char *value); - -int location_geoclue_get_location(location_geoclue_state_t *state, - location_t *loc); - - -#endif /* ! REDSHIFT_LOCATION_GEOCLUE_H */ diff --git a/src/location-geoclue2.c b/src/location-geoclue2.c index abccbd3..06015c5 100644 --- a/src/location-geoclue2.c +++ b/src/location-geoclue2.c @@ -14,9 +14,13 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include <stdio.h> #include <stdlib.h> @@ -26,6 +30,7 @@ #include "location-geoclue2.h" #include "redshift.h" +#include "pipeutils.h" #ifdef ENABLE_NLS # include <libintl.h> @@ -34,62 +39,54 @@ # define _(s) s #endif +#define DBUS_ACCESS_ERROR "org.freedesktop.DBus.Error.AccessDenied" + typedef struct { GMainLoop *loop; - + GThread *thread; + GMutex lock; + int pipe_fd_read; + int pipe_fd_write; int available; - location_t location; -} get_location_data_t; - + int error; + float latitude; + float longitude; +} location_geoclue2_state_t; -int -location_geoclue2_init(void *state) -{ -#if !GLIB_CHECK_VERSION(2, 35, 0) - g_type_init(); -#endif - return 0; -} -int -location_geoclue2_start(void *state) +/* Print the message explaining denial from GeoClue. */ +static void +print_denial_message() { - return 0; + g_printerr(_( + "Access to the current location was denied by GeoClue!\n" + "Make sure that location services are enabled and that" + " Redshift is permitted\nto use location services." + " See https://github.com/jonls/redshift#faq for more\n" + "information.\n")); } -void -location_geoclue2_free(void *state) +/* Indicate an unrecoverable error during GeoClue2 communication. */ +static void +mark_error(location_geoclue2_state_t *state) { -} + g_mutex_lock(&state->lock); -void -location_geoclue2_print_help(FILE *f) -{ - fputs(_("Use the location as discovered by a GeoClue2 provider.\n"), f); - fputs("\n", f); + state->error = 1; - fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" - "which means it has to be restarted to take notice after travel.\n"), - "GeoClue2"); - fputs("\n", f); -} + g_mutex_unlock(&state->lock); -int -location_geoclue2_set_option(void *state, - const char *key, const char *value) -{ - fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); - return -1; + pipeutils_signal(state->pipe_fd_write); } /* Handle position change callbacks */ static void geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name, gchar *signal_name, GVariant *parameters, - gpointer *user_data) + gpointer user_data) { - get_location_data_t *data = (get_location_data_t *)user_data; + location_geoclue2_state_t *state = user_data; /* Only handle LocationUpdated signals */ if (g_strcmp0(signal_name, "LocationUpdated") != 0) { @@ -102,34 +99,38 @@ geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name, /* Obtain location */ GError *error = NULL; - GDBusProxy *location = - g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.GeoClue2", - location_path, - "org.freedesktop.GeoClue2.Location", - NULL, &error); + GDBusProxy *location = g_dbus_proxy_new_sync( + g_dbus_proxy_get_connection(client), + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.GeoClue2", + location_path, + "org.freedesktop.GeoClue2.Location", + NULL, &error); if (location == NULL) { g_printerr(_("Unable to obtain location: %s.\n"), error->message); g_error_free(error); + mark_error(state); return; } + g_mutex_lock(&state->lock); + /* Read location properties */ - GVariant *lat_v = g_dbus_proxy_get_cached_property(location, - "Latitude"); - data->location.lat = g_variant_get_double(lat_v); + GVariant *lat_v = g_dbus_proxy_get_cached_property( + location, "Latitude"); + state->latitude = g_variant_get_double(lat_v); - GVariant *lon_v = g_dbus_proxy_get_cached_property(location, - "Longitude"); - data->location.lon = g_variant_get_double(lon_v); + GVariant *lon_v = g_dbus_proxy_get_cached_property( + location, "Longitude"); + state->longitude = g_variant_get_double(lon_v); - data->available = 1; + state->available = 1; - /* Return from main loop */ - g_main_loop_quit(data->loop); + g_mutex_unlock(&state->lock); + + pipeutils_signal(state->pipe_fd_write); } /* Callback when GeoClue name appears on the bus */ @@ -137,22 +138,23 @@ static void on_name_appeared(GDBusConnection *conn, const gchar *name, const gchar *name_owner, gpointer user_data) { - get_location_data_t *data = (get_location_data_t *)user_data; + location_geoclue2_state_t *state = user_data; /* Obtain GeoClue Manager */ GError *error = NULL; - GDBusProxy *geoclue_manager = - g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.GeoClue2", - "/org/freedesktop/GeoClue2/Manager", - "org.freedesktop.GeoClue2.Manager", - NULL, &error); + GDBusProxy *geoclue_manager = g_dbus_proxy_new_sync( + conn, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.GeoClue2", + "/org/freedesktop/GeoClue2/Manager", + "org.freedesktop.GeoClue2.Manager", + NULL, &error); if (geoclue_manager == NULL) { g_printerr(_("Unable to obtain GeoClue Manager: %s.\n"), error->message); g_error_free(error); + mark_error(state); return; } @@ -169,6 +171,7 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, error->message); g_error_free(error); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -177,20 +180,21 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Obtain GeoClue client */ error = NULL; - GDBusProxy *geoclue_client = - g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.GeoClue2", - client_path, - "org.freedesktop.GeoClue2.Client", - NULL, &error); + GDBusProxy *geoclue_client = g_dbus_proxy_new_sync( + conn, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.GeoClue2", + client_path, + "org.freedesktop.GeoClue2.Client", + NULL, &error); if (geoclue_client == NULL) { g_printerr(_("Unable to obtain GeoClue Client: %s.\n"), error->message); g_error_free(error); g_variant_unref(client_path_v); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -198,15 +202,15 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Set desktop id (basename of the .desktop file) */ error = NULL; - GVariant *ret_v = - g_dbus_proxy_call_sync(geoclue_client, - "org.freedesktop.DBus.Properties.Set", - g_variant_new("(ssv)", - "org.freedesktop.GeoClue2.Client", - "DesktopId", - g_variant_new("s", "redshift")), - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, &error); + GVariant *ret_v = g_dbus_proxy_call_sync( + geoclue_client, + "org.freedesktop.DBus.Properties.Set", + g_variant_new("(ssv)", + "org.freedesktop.GeoClue2.Client", + "DesktopId", + g_variant_new("s", "redshift")), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); if (ret_v == NULL) { /* Ignore this error for now. The property is not available in early versions of GeoClue2. */ @@ -216,20 +220,22 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Set distance threshold */ error = NULL; - ret_v = g_dbus_proxy_call_sync(geoclue_client, - "org.freedesktop.DBus.Properties.Set", - g_variant_new("(ssv)", - "org.freedesktop.GeoClue2.Client", - "DistanceThreshold", - g_variant_new("u", 50000)), - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, &error); + ret_v = g_dbus_proxy_call_sync( + geoclue_client, + "org.freedesktop.DBus.Properties.Set", + g_variant_new("(ssv)", + "org.freedesktop.GeoClue2.Client", + "DistanceThreshold", + g_variant_new("u", 50000)), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); if (ret_v == NULL) { g_printerr(_("Unable to set distance threshold: %s.\n"), error->message); g_error_free(error); g_object_unref(geoclue_client); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -238,7 +244,7 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Attach signal callback to client */ g_signal_connect(geoclue_client, "g-signal", G_CALLBACK(geoclue_client_signal_cb), - data); + user_data); /* Start GeoClue client */ error = NULL; @@ -250,9 +256,18 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, if (ret_v == NULL) { g_printerr(_("Unable to start GeoClue client: %s.\n"), error->message); + if (g_dbus_error_is_remote_error(error)) { + gchar *dbus_error = g_dbus_error_get_remote_error( + error); + if (g_strcmp0(dbus_error, DBUS_ACCESS_ERROR) == 0) { + print_denial_message(); + } + g_free(dbus_error); + } g_error_free(error); g_object_unref(geoclue_client); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -264,34 +279,175 @@ static void on_name_vanished(GDBusConnection *connection, const gchar *name, gpointer user_data) { - get_location_data_t *data = (get_location_data_t *)user_data; + location_geoclue2_state_t *state = user_data; + + g_mutex_lock(&state->lock); + + state->available = 0; - g_fprintf(stderr, _("Unable to connect to GeoClue.\n")); + g_mutex_unlock(&state->lock); - g_main_loop_quit(data->loop); + pipeutils_signal(state->pipe_fd_write); } -int -location_geoclue2_get_location(void *state, - location_t *location) +/* Callback when the pipe to the main thread is closed. */ +static gboolean +on_pipe_closed(GIOChannel *channel, GIOCondition condition, gpointer user_data) { - get_location_data_t data; - data.available = 0; - - guint watcher_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, - "org.freedesktop.GeoClue2", - G_BUS_NAME_WATCHER_FLAGS_AUTO_START, - on_name_appeared, - on_name_vanished, - &data, NULL); - data.loop = g_main_loop_new(NULL, FALSE); - g_main_loop_run(data.loop); + location_geoclue2_state_t *state = user_data; + g_main_loop_quit(state->loop); + + return FALSE; +} + + +/* Run loop for location provider thread. */ +static void * +run_geoclue2_loop(void *state_) +{ + location_geoclue2_state_t *state = state_; + + GMainContext *context = g_main_context_new(); + g_main_context_push_thread_default(context); + state->loop = g_main_loop_new(context, FALSE); + + guint watcher_id = g_bus_watch_name( + G_BUS_TYPE_SYSTEM, + "org.freedesktop.GeoClue2", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + on_name_appeared, + on_name_vanished, + state, NULL); + + /* Listen for closure of pipe */ + GIOChannel *pipe_channel = g_io_channel_unix_new(state->pipe_fd_write); + GSource *pipe_source = g_io_create_watch( + pipe_channel, G_IO_IN | G_IO_HUP | G_IO_ERR); + g_source_set_callback( + pipe_source, (GSourceFunc)on_pipe_closed, state, NULL); + g_source_attach(pipe_source, context); + + g_main_loop_run(state->loop); + + g_source_unref(pipe_source); + g_io_channel_unref(pipe_channel); + close(state->pipe_fd_write); g_bus_unwatch_name(watcher_id); - if (!data.available) return -1; + g_main_loop_unref(state->loop); + g_main_context_unref(context); + + return NULL; +} + +static int +location_geoclue2_init(location_geoclue2_state_t **state) +{ +#if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); +#endif + *state = malloc(sizeof(location_geoclue2_state_t)); + if (*state == NULL) return -1; + return 0; +} + +static int +location_geoclue2_start(location_geoclue2_state_t *state) +{ + state->pipe_fd_read = -1; + state->pipe_fd_write = -1; + + state->available = 0; + state->error = 0; + state->latitude = 0; + state->longitude = 0; + + int pipefds[2]; + int r = pipeutils_create_nonblocking(pipefds); + if (r < 0) { + fputs(_("Failed to start GeoClue2 provider!\n"), stderr); + return -1; + } + + state->pipe_fd_read = pipefds[0]; + state->pipe_fd_write = pipefds[1]; + + pipeutils_signal(state->pipe_fd_write); + + g_mutex_init(&state->lock); + state->thread = g_thread_new("geoclue2", run_geoclue2_loop, state); + + return 0; +} + +static void +location_geoclue2_free(location_geoclue2_state_t *state) +{ + if (state->pipe_fd_read != -1) { + close(state->pipe_fd_read); + } + + /* Closing the pipe should cause the thread to exit. */ + g_thread_join(state->thread); + state->thread = NULL; + + g_mutex_clear(&state->lock); + + free(state); +} + +static void +location_geoclue2_print_help(FILE *f) +{ + fputs(_("Use the location as discovered by a GeoClue2 provider.\n"), + f); + fputs("\n", f); +} - *location = data.location; +static int +location_geoclue2_set_option(location_geoclue2_state_t *state, + const char *key, const char *value) +{ + fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); + return -1; +} + +static int +location_geoclue2_get_fd(location_geoclue2_state_t *state) +{ + return state->pipe_fd_read; +} + +static int +location_geoclue2_handle( + location_geoclue2_state_t *state, + location_t *location, int *available) +{ + pipeutils_handle_signal(state->pipe_fd_read); + + g_mutex_lock(&state->lock); + + int error = state->error; + location->lat = state->latitude; + location->lon = state->longitude; + *available = state->available; + + g_mutex_unlock(&state->lock); + + if (error) return -1; return 0; } + + +const location_provider_t geoclue2_location_provider = { + "geoclue2", + (location_provider_init_func *)location_geoclue2_init, + (location_provider_start_func *)location_geoclue2_start, + (location_provider_free_func *)location_geoclue2_free, + (location_provider_print_help_func *)location_geoclue2_print_help, + (location_provider_set_option_func *)location_geoclue2_set_option, + (location_provider_get_fd_func *)location_geoclue2_get_fd, + (location_provider_handle_func *)location_geoclue2_handle +}; diff --git a/src/location-geoclue2.h b/src/location-geoclue2.h index c3c377b..a7189a7 100644 --- a/src/location-geoclue2.h +++ b/src/location-geoclue2.h @@ -14,27 +14,14 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_LOCATION_GEOCLUE2_H #define REDSHIFT_LOCATION_GEOCLUE2_H -#include <stdio.h> - #include "redshift.h" - -int location_geoclue2_init(void *state); -int location_geoclue2_start(void *state); -void location_geoclue2_free(void *state); - -void location_geoclue2_print_help(FILE *f); -int location_geoclue2_set_option(void *state, - const char *key, const char *value); - -int location_geoclue2_get_location(void *state, - location_t *loc); - +extern const location_provider_t geoclue2_location_provider; #endif /* ! REDSHIFT_LOCATION_GEOCLUE2_H */ diff --git a/src/location-manual.c b/src/location-manual.c index c5da074..db3a8a9 100644 --- a/src/location-manual.c +++ b/src/location-manual.c @@ -14,9 +14,13 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include <stdio.h> #include <stdlib.h> #include <math.h> @@ -33,16 +37,25 @@ #endif -int -location_manual_init(location_manual_state_t *state) +typedef struct { + location_t loc; +} location_manual_state_t; + + +static int +location_manual_init(location_manual_state_t **state) { - state->loc.lat = NAN; - state->loc.lon = NAN; + *state = malloc(sizeof(location_manual_state_t)); + if (*state == NULL) return -1; + + location_manual_state_t *s = *state; + s->loc.lat = NAN; + s->loc.lon = NAN; return 0; } -int +static int location_manual_start(location_manual_state_t *state) { /* Latitude and longitude must be set */ @@ -54,12 +67,13 @@ location_manual_start(location_manual_state_t *state) return 0; } -void +static void location_manual_free(location_manual_state_t *state) { + free(state); } -void +static void location_manual_print_help(FILE *f) { fputs(_("Specify location manually.\n"), f); @@ -75,7 +89,7 @@ location_manual_print_help(FILE *f) fputs("\n", f); } -int +static int location_manual_set_option(location_manual_state_t *state, const char *key, const char *value) { @@ -100,11 +114,30 @@ location_manual_set_option(location_manual_state_t *state, const char *key, return 0; } -int -location_manual_get_location(location_manual_state_t *state, - location_t *loc) +static int +location_manual_get_fd(location_manual_state_t *state) { - *loc = state->loc; + return -1; +} + +static int +location_manual_handle( + location_manual_state_t *state, location_t *location, int *available) +{ + *location = state->loc; + *available = 1; return 0; } + + +const location_provider_t manual_location_provider = { + "manual", + (location_provider_init_func *)location_manual_init, + (location_provider_start_func *)location_manual_start, + (location_provider_free_func *)location_manual_free, + (location_provider_print_help_func *)location_manual_print_help, + (location_provider_set_option_func *)location_manual_set_option, + (location_provider_get_fd_func *)location_manual_get_fd, + (location_provider_handle_func *)location_manual_handle +}; diff --git a/src/location-manual.h b/src/location-manual.h index e70d9cf..c7ef2ad 100644 --- a/src/location-manual.h +++ b/src/location-manual.h @@ -14,32 +14,14 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_LOCATION_MANUAL_H #define REDSHIFT_LOCATION_MANUAL_H -#include <stdio.h> - #include "redshift.h" - -typedef struct { - location_t loc; -} location_manual_state_t; - - -int location_manual_init(location_manual_state_t *state); -int location_manual_start(location_manual_state_t *state); -void location_manual_free(location_manual_state_t *state); - -void location_manual_print_help(FILE *f); -int location_manual_set_option(location_manual_state_t *state, - const char *key, const char *value); - -int location_manual_get_location(location_manual_state_t *state, - location_t *loc); - +extern const location_provider_t manual_location_provider; #endif /* ! REDSHIFT_LOCATION_MANUAL_H */ diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..aadd317 --- /dev/null +++ b/src/options.c @@ -0,0 +1,680 @@ +/* options.c -- Program options + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com> +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <math.h> + +#ifdef ENABLE_NLS +# include <libintl.h> +# define _(s) gettext(s) +#else +# define _(s) s +#endif + +#include "redshift.h" +#include "config-ini.h" +#include "options.h" +#include "solar.h" + +/* Angular elevation of the sun at which the color temperature + transition period starts and ends (in degress). + Transition during twilight, and while the sun is lower than + 3.0 degrees above the horizon. */ +#define TRANSITION_LOW SOLAR_CIVIL_TWILIGHT_ELEV +#define TRANSITION_HIGH 3.0 + +/* Default values for parameters. */ +#define DEFAULT_DAY_TEMP 6500 +#define DEFAULT_NIGHT_TEMP 4500 +#define DEFAULT_BRIGHTNESS 1.0 +#define DEFAULT_GAMMA 1.0 + + +/* A brightness string contains either one floating point value, + or two values separated by a colon. */ +static void +parse_brightness_string( + const char *str, float *bright_day, float *bright_night) +{ + char *s = strchr(str, ':'); + if (s == NULL) { + /* Same value for day and night. */ + *bright_day = *bright_night = atof(str); + } else { + *(s++) = '\0'; + *bright_day = atof(str); + *bright_night = atof(s); + } +} + +/* A gamma string contains either one floating point value, + or three values separated by colon. */ +static int +parse_gamma_string(const char *str, float gamma[]) +{ + char *s = strchr(str, ':'); + if (s == NULL) { + /* Use value for all channels */ + float g = atof(str); + gamma[0] = gamma[1] = gamma[2] = g; + } else { + /* Parse separate value for each channel */ + *(s++) = '\0'; + char *g_s = s; + s = strchr(s, ':'); + if (s == NULL) return -1; + + *(s++) = '\0'; + gamma[0] = atof(str); /* Red */ + gamma[1] = atof(g_s); /* Blue */ + gamma[2] = atof(s); /* Green */ + } + + return 0; +} + +/* Parse transition time string e.g. "04:50". Returns negative on failure, + otherwise the parsed time is returned as seconds since midnight. */ +static int +parse_transition_time(const char *str, const char **end) +{ + const char *min = NULL; + errno = 0; + long hours = strtol(str, (char **)&min, 10); + if (errno != 0 || min == str || min[0] != ':' || + hours < 0 || hours >= 24) { + return -1; + } + + min += 1; + errno = 0; + long minutes = strtol(min, (char **)end, 10); + if (errno != 0 || *end == min || minutes < 0 || minutes >= 60) { + return -1; + } + + return minutes * 60 + hours * 3600; +} + +/* Parse transition range string e.g. "04:50-6:20". Returns negative on + failure, otherwise zero. Parsed start and end times are returned as seconds + since midnight. */ +static int +parse_transition_range(const char *str, time_range_t *range) +{ + const char *next = NULL; + int start_time = parse_transition_time(str, &next); + if (start_time < 0) return -1; + + int end_time; + if (next[0] == '\0') { + end_time = start_time; + } else if (next[0] == '-') { + next += 1; + const char *end = NULL; + end_time = parse_transition_time(next, &end); + if (end_time < 0 || end[0] != '\0') return -1; + } else { + return -1; + } + + range->start = start_time; + range->end = end_time; + + return 0; +} + +/* Print help text. */ +static void +print_help(const char *program_name) +{ + /* TRANSLATORS: help output 1 + LAT is latitude, LON is longitude, + DAY is temperature at daytime, + NIGHT is temperature at night + no-wrap */ + printf(_("Usage: %s -l LAT:LON -t DAY:NIGHT [OPTIONS...]\n"), + program_name); + fputs("\n", stdout); + + /* TRANSLATORS: help output 2 + no-wrap */ + fputs(_("Set color temperature of display" + " according to time of day.\n"), stdout); + fputs("\n", stdout); + + /* TRANSLATORS: help output 3 + no-wrap */ + fputs(_(" -h\t\tDisplay this help message\n" + " -v\t\tVerbose output\n" + " -V\t\tShow program version\n"), stdout); + fputs("\n", stdout); + + /* TRANSLATORS: help output 4 + `list' must not be translated + no-wrap */ + fputs(_(" -b DAY:NIGHT\tScreen brightness to apply (between 0.1 and 1.0)\n" + " -c FILE\tLoad settings from specified configuration file\n" + " -g R:G:B\tAdditional gamma correction to apply\n" + " -l LAT:LON\tYour current location\n" + " -l PROVIDER\tSelect provider for automatic" + " location updates\n" + " \t\t(Type `list' to see available providers)\n" + " -m METHOD\tMethod to use to set color temperature\n" + " \t\t(Type `list' to see available methods)\n" + " -o\t\tOne shot mode (do not continuously adjust" + " color temperature)\n" + " -O TEMP\tOne shot manual mode (set color temperature)\n" + " -p\t\tPrint mode (only print parameters and exit)\n" + " -P\t\tReset existing gamma ramps before applying new" + " color effect\n" + " -x\t\tReset mode (remove adjustment from screen)\n" + " -r\t\tDisable fading between color temperatures\n" + " -t DAY:NIGHT\tColor temperature to set at daytime/night\n"), + stdout); + fputs("\n", stdout); + + /* TRANSLATORS: help output 5 */ + printf(_("The neutral temperature is %uK. Using this value will not change " + "the color\ntemperature of the display. Setting the color temperature " + "to a value higher\nthan this results in more blue light, and setting " + "a lower value will result in\nmore red light.\n"), + NEUTRAL_TEMP); + + fputs("\n", stdout); + + /* TRANSLATORS: help output 6 */ + printf(_("Default values:\n\n" + " Daytime temperature: %uK\n" + " Night temperature: %uK\n"), + DEFAULT_DAY_TEMP, DEFAULT_NIGHT_TEMP); + + fputs("\n", stdout); +} + +/* Print list of adjustment methods. */ +static void +print_method_list(const gamma_method_t *gamma_methods) +{ + fputs(_("Available adjustment methods:\n"), stdout); + for (int i = 0; gamma_methods[i].name != NULL; i++) { + printf(" %s\n", gamma_methods[i].name); + } + + fputs("\n", stdout); + fputs(_("Specify colon-separated options with" + " `-m METHOD:OPTIONS'.\n"), stdout); + /* TRANSLATORS: `help' must not be translated. */ + fputs(_("Try `-m METHOD:help' for help.\n"), stdout); +} + +/* Print list of location providers. */ +static void +print_provider_list(const location_provider_t location_providers[]) +{ + fputs(_("Available location providers:\n"), stdout); + for (int i = 0; location_providers[i].name != NULL; i++) { + printf(" %s\n", location_providers[i].name); + } + + fputs("\n", stdout); + fputs(_("Specify colon-separated options with" + "`-l PROVIDER:OPTIONS'.\n"), stdout); + /* TRANSLATORS: `help' must not be translated. */ + fputs(_("Try `-l PROVIDER:help' for help.\n"), stdout); +} + +/* Return the gamma method with the given name. */ +static const gamma_method_t * +find_gamma_method(const gamma_method_t gamma_methods[], const char *name) +{ + const gamma_method_t *method = NULL; + for (int i = 0; gamma_methods[i].name != NULL; i++) { + const gamma_method_t *m = &gamma_methods[i]; + if (strcasecmp(name, m->name) == 0) { + method = m; + break; + } + } + + return method; +} + +/* Return location provider with the given name. */ +static const location_provider_t * +find_location_provider( + const location_provider_t location_providers[], const char *name) +{ + const location_provider_t *provider = NULL; + for (int i = 0; location_providers[i].name != NULL; i++) { + const location_provider_t *p = &location_providers[i]; + if (strcasecmp(name, p->name) == 0) { + provider = p; + break; + } + } + + return provider; +} + + +/* Initialize options struct. */ +void +options_init(options_t *options) +{ + options->config_filepath = NULL; + + /* Default elevation values. */ + options->scheme.high = TRANSITION_HIGH; + options->scheme.low = TRANSITION_LOW; + + /* Settings for day, night and transition period. + Initialized to indicate that the values are not set yet. */ + options->scheme.use_time = 0; + options->scheme.dawn.start = -1; + options->scheme.dawn.end = -1; + options->scheme.dusk.start = -1; + options->scheme.dusk.end = -1; + + options->scheme.day.temperature = -1; + options->scheme.day.gamma[0] = NAN; + options->scheme.day.brightness = NAN; + + options->scheme.night.temperature = -1; + options->scheme.night.gamma[0] = NAN; + options->scheme.night.brightness = NAN; + + /* Temperature for manual mode */ + options->temp_set = -1; + + options->method = NULL; + options->method_args = NULL; + + options->provider = NULL; + options->provider_args = NULL; + + options->use_fade = -1; + options->preserve_gamma = 1; + options->mode = PROGRAM_MODE_CONTINUAL; + options->verbose = 0; +} + +/* Parse a single option from the command-line. */ +static int +parse_command_line_option( + const char option, char *value, options_t *options, + const char *program_name, const gamma_method_t *gamma_methods, + const location_provider_t *location_providers) +{ + int r; + char *s; + + switch (option) { + case 'b': + parse_brightness_string( + value, &options->scheme.day.brightness, + &options->scheme.night.brightness); + break; + case 'c': + free(options->config_filepath); + options->config_filepath = strdup(value); + break; + case 'g': + r = parse_gamma_string(value, options->scheme.day.gamma); + if (r < 0) { + fputs(_("Malformed gamma argument.\n"), stderr); + fputs(_("Try `-h' for more information.\n"), stderr); + return -1; + } + + /* Set night gamma to the same value as day gamma. + To set these to distinct values use the config + file. */ + memcpy(options->scheme.night.gamma, + options->scheme.day.gamma, + sizeof(options->scheme.night.gamma)); + break; + case 'h': + print_help(program_name); + exit(EXIT_SUCCESS); + break; + case 'l': + /* Print list of providers if argument is `list' */ + if (strcasecmp(value, "list") == 0) { + print_provider_list(location_providers); + exit(EXIT_SUCCESS); + } + + char *provider_name = NULL; + + /* Don't save the result of strtof(); we simply want + to know if value can be parsed as a float. */ + errno = 0; + char *end; + strtof(value, &end); + if (errno == 0 && *end == ':') { + /* Use instead as arguments to `manual'. */ + provider_name = "manual"; + options->provider_args = value; + } else { + /* Split off provider arguments. */ + s = strchr(value, ':'); + if (s != NULL) { + *(s++) = '\0'; + options->provider_args = s; + } + + provider_name = value; + } + + /* Lookup provider from name. */ + options->provider = find_location_provider( + location_providers, provider_name); + if (options->provider == NULL) { + fprintf(stderr, _("Unknown location provider `%s'.\n"), + provider_name); + return -1; + } + + /* Print provider help if arg is `help'. */ + if (options->provider_args != NULL && + strcasecmp(options->provider_args, "help") == 0) { + options->provider->print_help(stdout); + exit(EXIT_SUCCESS); + } + break; + case 'm': + /* Print list of methods if argument is `list' */ + if (strcasecmp(value, "list") == 0) { + print_method_list(gamma_methods); + exit(EXIT_SUCCESS); + } + + /* Split off method arguments. */ + s = strchr(value, ':'); + if (s != NULL) { + *(s++) = '\0'; + options->method_args = s; + } + + /* Find adjustment method by name. */ + options->method = find_gamma_method(gamma_methods, value); + if (options->method == NULL) { + /* TRANSLATORS: This refers to the method + used to adjust colors e.g VidMode */ + fprintf(stderr, _("Unknown adjustment method `%s'.\n"), + value); + return -1; + } + + /* Print method help if arg is `help'. */ + if (options->method_args != NULL && + strcasecmp(options->method_args, "help") == 0) { + options->method->print_help(stdout); + exit(EXIT_SUCCESS); + } + break; + case 'o': + options->mode = PROGRAM_MODE_ONE_SHOT; + break; + case 'O': + options->mode = PROGRAM_MODE_MANUAL; + options->temp_set = atoi(value); + break; + case 'p': + options->mode = PROGRAM_MODE_PRINT; + break; + case 'P': + options->preserve_gamma = 0; + break; + case 'r': + options->use_fade = 0; + break; + case 't': + s = strchr(value, ':'); + if (s == NULL) { + fputs(_("Malformed temperature argument.\n"), stderr); + fputs(_("Try `-h' for more information.\n"), stderr); + return -1; + } + *(s++) = '\0'; + options->scheme.day.temperature = atoi(value); + options->scheme.night.temperature = atoi(s); + break; + case 'v': + options->verbose = 1; + break; + case 'V': + printf("%s\n", PACKAGE_STRING); + exit(EXIT_SUCCESS); + break; + case 'x': + options->mode = PROGRAM_MODE_RESET; + break; + case '?': + fputs(_("Try `-h' for more information.\n"), stderr); + return -1; + break; + } + + return 0; +} + +/* Parse command line arguments. */ +void +options_parse_args( + options_t *options, int argc, char *argv[], + const gamma_method_t *gamma_methods, + const location_provider_t *location_providers) +{ + const char* program_name = argv[0]; + int opt; + while ((opt = getopt(argc, argv, "b:c:g:hl:m:oO:pPrt:vVx")) != -1) { + char option = opt; + int r = parse_command_line_option( + option, optarg, options, program_name, gamma_methods, + location_providers); + if (r < 0) exit(EXIT_FAILURE); + } +} + +/* Parse a single key-value pair from the configuration file. */ +static int +parse_config_file_option( + const char *key, const char *value, options_t *options, + const gamma_method_t *gamma_methods, + const location_provider_t *location_providers) +{ + if (strcasecmp(key, "temp-day") == 0) { + if (options->scheme.day.temperature < 0) { + options->scheme.day.temperature = atoi(value); + } + } else if (strcasecmp(key, "temp-night") == 0) { + if (options->scheme.night.temperature < 0) { + options->scheme.night.temperature = atoi(value); + } + } else if (strcasecmp(key, "transition") == 0 || + strcasecmp(key, "fade") == 0) { + /* "fade" is preferred, "transition" is + deprecated as the setting key. */ + if (options->use_fade < 0) { + options->use_fade = !!atoi(value); + } + } else if (strcasecmp(key, "brightness") == 0) { + if (isnan(options->scheme.day.brightness)) { + options->scheme.day.brightness = atof(value); + } + if (isnan(options->scheme.night.brightness)) { + options->scheme.night.brightness = atof(value); + } + } else if (strcasecmp(key, "brightness-day") == 0) { + if (isnan(options->scheme.day.brightness)) { + options->scheme.day.brightness = atof(value); + } + } else if (strcasecmp(key, "brightness-night") == 0) { + if (isnan(options->scheme.night.brightness)) { + options->scheme.night.brightness = atof(value); + } + } else if (strcasecmp(key, "elevation-high") == 0) { + options->scheme.high = atof(value); + } else if (strcasecmp(key, "elevation-low") == 0) { + options->scheme.low = atof(value); + } else if (strcasecmp(key, "gamma") == 0) { + if (isnan(options->scheme.day.gamma[0])) { + int r = parse_gamma_string( + value, options->scheme.day.gamma); + if (r < 0) { + fputs(_("Malformed gamma setting.\n"), stderr); + return -1; + } + memcpy(options->scheme.night.gamma, + options->scheme.day.gamma, + sizeof(options->scheme.night.gamma)); + } + } else if (strcasecmp(key, "gamma-day") == 0) { + if (isnan(options->scheme.day.gamma[0])) { + int r = parse_gamma_string( + value, options->scheme.day.gamma); + if (r < 0) { + fputs(_("Malformed gamma setting.\n"), stderr); + return -1; + } + } + } else if (strcasecmp(key, "gamma-night") == 0) { + if (isnan(options->scheme.night.gamma[0])) { + int r = parse_gamma_string( + value, options->scheme.night.gamma); + if (r < 0) { + fputs(_("Malformed gamma setting.\n"), stderr); + return -1; + } + } + } else if (strcasecmp(key, "preserve-gamma") == 0) { + if (options->preserve_gamma == 1) { + options->preserve_gamma = !!atoi(value); + } + } else if (strcasecmp(key, "adjustment-method") == 0) { + if (options->method == NULL) { + options->method = find_gamma_method( + gamma_methods, value); + if (options->method == NULL) { + fprintf(stderr, _("Unknown adjustment" + " method `%s'.\n"), value); + return -1; + } + } + } else if (strcasecmp(key, "location-provider") == 0) { + if (options->provider == NULL) { + options->provider = find_location_provider( + location_providers, value); + if (options->provider == NULL) { + fprintf(stderr, _("Unknown location" + " provider `%s'.\n"), value); + return -1; + } + } + } else if (strcasecmp(key, "dawn-time") == 0) { + if (options->scheme.dawn.start < 0) { + int r = parse_transition_range( + value, &options->scheme.dawn); + if (r < 0) { + fprintf(stderr, _("Malformed dawn-time" + " setting `%s'.\n"), value); + return -1; + } + } + } else if (strcasecmp(key, "dusk-time") == 0) { + if (options->scheme.dusk.start < 0) { + int r = parse_transition_range( + value, &options->scheme.dusk); + if (r < 0) { + fprintf(stderr, _("Malformed dusk-time" + " setting `%s'.\n"), value); + return -1; + } + } + } else { + fprintf(stderr, _("Unknown configuration setting `%s'.\n"), + key); + } + + return 0; +} + +/* Parse options defined in the config file. */ +void +options_parse_config_file( + options_t *options, config_ini_state_t *config_state, + const gamma_method_t *gamma_methods, + const location_provider_t *location_providers) +{ + /* Read global config settings. */ + config_ini_section_t *section = config_ini_get_section( + config_state, "redshift"); + if (section == NULL) return; + + config_ini_setting_t *setting = section->settings; + while (setting != NULL) { + int r = parse_config_file_option( + setting->name, setting->value, options, + gamma_methods, location_providers); + if (r < 0) exit(EXIT_FAILURE); + + setting = setting->next; + } +} + +/* Replace unspecified options with default values. */ +void +options_set_defaults(options_t *options) +{ + if (options->scheme.day.temperature < 0) { + options->scheme.day.temperature = DEFAULT_DAY_TEMP; + } + if (options->scheme.night.temperature < 0) { + options->scheme.night.temperature = DEFAULT_NIGHT_TEMP; + } + + if (isnan(options->scheme.day.brightness)) { + options->scheme.day.brightness = DEFAULT_BRIGHTNESS; + } + if (isnan(options->scheme.night.brightness)) { + options->scheme.night.brightness = DEFAULT_BRIGHTNESS; + } + + if (isnan(options->scheme.day.gamma[0])) { + options->scheme.day.gamma[0] = DEFAULT_GAMMA; + options->scheme.day.gamma[1] = DEFAULT_GAMMA; + options->scheme.day.gamma[2] = DEFAULT_GAMMA; + } + if (isnan(options->scheme.night.gamma[0])) { + options->scheme.night.gamma[0] = DEFAULT_GAMMA; + options->scheme.night.gamma[1] = DEFAULT_GAMMA; + options->scheme.night.gamma[2] = DEFAULT_GAMMA; + } + + if (options->use_fade < 0) options->use_fade = 1; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..9993a07 --- /dev/null +++ b/src/options.h @@ -0,0 +1,63 @@ +/* options.h -- Program options header + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com> +*/ + +#ifndef REDSHIFT_OPTIONS_H +#define REDSHIFT_OPTIONS_H + +#include "redshift.h" + +typedef struct { + /* Path to config file */ + char *config_filepath; + + transition_scheme_t scheme; + program_mode_t mode; + int verbose; + + /* Temperature to set in manual mode. */ + int temp_set; + /* Whether to fade between large skips in color temperature. */ + int use_fade; + /* Whether to preserve gamma ramps if supported by gamma method. */ + int preserve_gamma; + + /* Selected gamma method. */ + const gamma_method_t *method; + /* Arguments for gamma method. */ + char *method_args; + + /* Selected location provider. */ + const location_provider_t *provider; + /* Arguments for location provider. */ + char *provider_args; +} options_t; + + +void options_init(options_t *options); +void options_parse_args( + options_t *options, int argc, char *argv[], + const gamma_method_t *gamma_methods, + const location_provider_t *location_providers); +void options_parse_config_file( + options_t *options, config_ini_state_t *config_state, + const gamma_method_t *gamma_methods, + const location_provider_t *location_providers); +void options_set_defaults(options_t *options); + +#endif /* ! REDSHIFT_OPTIONS_H */ diff --git a/src/pipeutils.c b/src/pipeutils.c new file mode 100644 index 0000000..75302cb --- /dev/null +++ b/src/pipeutils.c @@ -0,0 +1,98 @@ +/* pipeutils.c -- Utilities for using pipes as signals + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com> +*/ + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + + +#ifndef _WIN32 + +/* Create non-blocking set of pipe fds. */ +int +pipeutils_create_nonblocking(int pipefds[2]) +{ + int r = pipe(pipefds); + if (r == -1) { + perror("pipe"); + return -1; + } + + int flags = fcntl(pipefds[0], F_GETFL); + if (flags == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + r = fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK); + if (r == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + flags = fcntl(pipefds[1], F_GETFL); + if (flags == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + r = fcntl(pipefds[1], F_SETFL, flags | O_NONBLOCK); + if (r == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + return 0; +} + +#else /* _WIN32 */ + +/* Create non-blocking set of pipe fds. + + Not supported on Windows! Always fails. */ +int +pipeutils_create_nonblocking(int pipefds[2]) +{ + return -1; +} + +#endif + +/* Signal on write-end of pipe. */ +void +pipeutils_signal(int write_fd) +{ + write(write_fd, "", 1); +} + +/* Mark signal as handled on read-end of pipe. */ +void +pipeutils_handle_signal(int read_fd) +{ + char data; + read(read_fd, &data, 1); +} diff --git a/src/pipeutils.h b/src/pipeutils.h new file mode 100644 index 0000000..69c3350 --- /dev/null +++ b/src/pipeutils.h @@ -0,0 +1,28 @@ +/* pipeutils.h -- Utilities for using pipes as signals + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com> +*/ + +#ifndef REDSHIFT_PIPEUTILS_H +#define REDSHIFT_PIPEUTILS_H + +int pipeutils_create_nonblocking(int pipefds[2]); + +void pipeutils_signal(int write_fd); +void pipeutils_handle_signal(int read_fd); + +#endif /* ! REDSHIFT_PIPEUTILS_H */ diff --git a/src/redshift-gtk/Makefile.am b/src/redshift-gtk/Makefile.am index c4ab24f..b7303a2 100644 --- a/src/redshift-gtk/Makefile.am +++ b/src/redshift-gtk/Makefile.am @@ -2,8 +2,9 @@ if ENABLE_GUI redshift_gtk_PYTHON = \ __init__.py \ - utils.py \ - statusicon.py + controller.py \ + statusicon.py \ + utils.py nodist_redshift_gtk_PYTHON = \ defs.py redshift_gtkdir = $(pythondir)/redshift_gtk diff --git a/src/redshift-gtk/controller.py b/src/redshift-gtk/controller.py new file mode 100644 index 0000000..24c58ae --- /dev/null +++ b/src/redshift-gtk/controller.py @@ -0,0 +1,227 @@ +# controller.py -- Redshift child process controller +# This file is part of Redshift. + +# Redshift is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# Redshift is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with Redshift. If not, see <http://www.gnu.org/licenses/>. + +# Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com> + +import os +import re +import fcntl +import signal + +import gi +gi.require_version('GLib', '2.0') + +from gi.repository import GLib, GObject + +from . import defs + + +class RedshiftController(GObject.GObject): + """GObject wrapper around the Redshift child process.""" + + __gsignals__ = { + 'inhibit-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool,)), + 'temperature-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)), + 'period-changed': (GObject.SIGNAL_RUN_FIRST, None, (str,)), + 'location-changed': (GObject.SIGNAL_RUN_FIRST, None, (float, float)), + 'error-occured': (GObject.SIGNAL_RUN_FIRST, None, (str,)), + 'stopped': (GObject.SIGNAL_RUN_FIRST, None, ()), + } + + def __init__(self, args): + """Initialize controller and start child process. + + The parameter args is a list of command line arguments to pass on to + the child process. The "-v" argument is automatically added. + """ + GObject.GObject.__init__(self) + + # Initialize state variables + self._inhibited = False + self._temperature = 0 + self._period = 'Unknown' + self._location = (0.0, 0.0) + + # Start redshift with arguments + args.insert(0, os.path.join(defs.BINDIR, 'redshift')) + if '-v' not in args: + args.insert(1, '-v') + + # Start child process with C locale so we can parse the output + env = os.environ.copy() + for key in ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES'): + env[key] = 'C' + self._process = GLib.spawn_async( + args, envp=['{}={}'.format(k, v) for k, v in env.items()], + flags=GLib.SPAWN_DO_NOT_REAP_CHILD, + standard_output=True, standard_error=True) + + # Wrap remaining contructor in try..except to avoid that the child + # process is not closed properly. + try: + # Handle child input + # The buffer is encapsulated in a class so we + # can pass an instance to the child callback. + class InputBuffer(object): + buf = '' + + self._input_buffer = InputBuffer() + self._error_buffer = InputBuffer() + self._errors = '' + + # Set non blocking + fcntl.fcntl( + self._process[2], fcntl.F_SETFL, + fcntl.fcntl(self._process[2], fcntl.F_GETFL) | os.O_NONBLOCK) + + # Add watch on child process + GLib.child_watch_add( + GLib.PRIORITY_DEFAULT, self._process[0], self._child_cb) + GLib.io_add_watch( + self._process[2], GLib.PRIORITY_DEFAULT, GLib.IO_IN, + self._child_data_cb, (True, self._input_buffer)) + GLib.io_add_watch( + self._process[3], GLib.PRIORITY_DEFAULT, GLib.IO_IN, + self._child_data_cb, (False, self._error_buffer)) + + # Signal handler to relay USR1 signal to redshift process + def relay_signal_handler(signal): + os.kill(self._process[0], signal) + return True + + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGUSR1, + relay_signal_handler, signal.SIGUSR1) + except: + self.termwait() + raise + + @property + def inhibited(self): + """Current inhibition state.""" + return self._inhibited + + @property + def temperature(self): + """Current screen temperature.""" + return self._temperature + + @property + def period(self): + """Current period of day.""" + return self._period + + @property + def location(self): + """Current location.""" + return self._location + + def set_inhibit(self, inhibit): + """Set inhibition state.""" + if inhibit != self._inhibited: + self._child_toggle_inhibit() + + def _child_signal(self, sg): + """Send signal to child process.""" + os.kill(self._process[0], sg) + + def _child_toggle_inhibit(self): + """Sends a request to the child process to toggle state.""" + self._child_signal(signal.SIGUSR1) + + def _child_cb(self, pid, status, data=None): + """Called when the child process exists.""" + + # Empty stdout and stderr + for f in (self._process[2], self._process[3]): + while True: + buf = os.read(f, 256).decode('utf-8') + if buf == '': + break + if f == self._process[3]: # stderr + self._errors += buf + + # Check exit status of child + try: + GLib.spawn_check_exit_status(status) + except GLib.GError: + self.emit('error-occured', self._errors) + + GLib.spawn_close_pid(self._process[0]) + self.emit('stopped') + + def _child_key_change_cb(self, key, value): + """Called when the child process reports a change of internal state.""" + + def parse_coord(s): + """Parse coordinate like `42.0 N` or `91.5 W`.""" + v, d = s.split(' ') + return float(v) * (1 if d in 'NE' else -1) + + if key == 'Status': + new_inhibited = value != 'Enabled' + if new_inhibited != self._inhibited: + self._inhibited = new_inhibited + self.emit('inhibit-changed', new_inhibited) + elif key == 'Color temperature': + new_temperature = int(value.rstrip('K'), 10) + if new_temperature != self._temperature: + self._temperature = new_temperature + self.emit('temperature-changed', new_temperature) + elif key == 'Period': + new_period = value + if new_period != self._period: + self._period = new_period + self.emit('period-changed', new_period) + elif key == 'Location': + new_location = tuple(parse_coord(x) for x in value.split(', ')) + if new_location != self._location: + self._location = new_location + self.emit('location-changed', *new_location) + + def _child_stdout_line_cb(self, line): + """Called when the child process outputs a line to stdout.""" + if line: + m = re.match(r'([\w ]+): (.+)', line) + if m: + key = m.group(1) + value = m.group(2) + self._child_key_change_cb(key, value) + + def _child_data_cb(self, f, cond, data): + """Called when the child process has new data on stdout/stderr.""" + stdout, ib = data + ib.buf += os.read(f, 256).decode('utf-8') + + # Split input at line break + while True: + first, sep, last = ib.buf.partition('\n') + if sep == '': + break + ib.buf = last + if stdout: + self._child_stdout_line_cb(first) + else: + self._errors += first + '\n' + + return True + + def terminate_child(self): + """Send SIGINT to child process.""" + self._child_signal(signal.SIGINT) + + def kill_child(self): + """Send SIGKILL to child process.""" + self._child_signal(signal.SIGKILL) diff --git a/src/redshift-gtk/statusicon.py b/src/redshift-gtk/statusicon.py index b766175..b4adfb0 100644 --- a/src/redshift-gtk/statusicon.py +++ b/src/redshift-gtk/statusicon.py @@ -14,243 +14,61 @@ # You should have received a copy of the GNU General Public License # along with Redshift. If not, see <http://www.gnu.org/licenses/>. -# Copyright (c) 2013-2014 Jon Lund Steffensen <jonlst@gmail.com> +# Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com> -'''GUI status icon for Redshift. +"""GUI status icon for Redshift. The run method will try to start an appindicator for Redshift. If the appindicator module isn't present it will fall back to a GTK status icon. -''' +""" -import sys, os -import fcntl +import sys import signal -import re import gettext -from gi.repository import Gtk, GLib, GObject +import gi +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk, GLib try: + gi.require_version('AppIndicator3', '0.1') from gi.repository import AppIndicator3 as appindicator -except ImportError: +except (ImportError, ValueError): appindicator = None +from .controller import RedshiftController from . import defs from . import utils _ = gettext.gettext -class RedshiftController(GObject.GObject): - '''A GObject wrapper around the child process''' - - __gsignals__ = { - 'inhibit-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool,)), - 'temperature-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)), - 'period-changed': (GObject.SIGNAL_RUN_FIRST, None, (str,)), - 'location-changed': (GObject.SIGNAL_RUN_FIRST, None, (float, float)), - 'error-occured': (GObject.SIGNAL_RUN_FIRST, None, (str,)) - } - - def __init__(self, args): - '''Initialize controller and start child process - - The parameter args is a list of command line arguments to pass on to - the child process. The "-v" argument is automatically added.''' - - GObject.GObject.__init__(self) - - # Initialize state variables - self._inhibited = False - self._temperature = 0 - self._period = 'Unknown' - self._location = (0.0, 0.0) - - # Start redshift with arguments - args.insert(0, os.path.join(defs.BINDIR, 'redshift')) - if '-v' not in args: - args.insert(1, '-v') - - # Start child process with C locale so we can parse the output - env = os.environ.copy() - env['LANG'] = env['LANGUAGE'] = env['LC_ALL'] = env['LC_MESSAGES'] = 'C' - self._process = GLib.spawn_async(args, envp=['{}={}'.format(k,v) for k, v in env.items()], - flags=GLib.SPAWN_DO_NOT_REAP_CHILD, - standard_output=True, standard_error=True) - - # Wrap remaining contructor in try..except to avoid that the child - # process is not closed properly. - try: - # Handle child input - # The buffer is encapsulated in a class so we - # can pass an instance to the child callback. - class InputBuffer(object): - buf = '' - - self._input_buffer = InputBuffer() - self._error_buffer = InputBuffer() - self._errors = '' - - # Set non blocking - fcntl.fcntl(self._process[2], fcntl.F_SETFL, - fcntl.fcntl(self._process[2], fcntl.F_GETFL) | os.O_NONBLOCK) - - # Add watch on child process - GLib.child_watch_add(GLib.PRIORITY_DEFAULT, self._process[0], self._child_cb) - GLib.io_add_watch(self._process[2], GLib.PRIORITY_DEFAULT, GLib.IO_IN, - self._child_data_cb, (True, self._input_buffer)) - GLib.io_add_watch(self._process[3], GLib.PRIORITY_DEFAULT, GLib.IO_IN, - self._child_data_cb, (False, self._error_buffer)) - - # Signal handler to relay USR1 signal to redshift process - def relay_signal_handler(signal): - os.kill(self._process[0], signal) - return True - - GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGUSR1, - relay_signal_handler, signal.SIGUSR1) - except: - self.termwait() - raise - - @property - def inhibited(self): - '''Current inhibition state''' - return self._inhibited - - @property - def temperature(self): - '''Current screen temperature''' - return self._temperature - - @property - def period(self): - '''Current period of day''' - return self._period - - @property - def location(self): - '''Current location''' - return self._location - - def set_inhibit(self, inhibit): - '''Set inhibition state''' - if inhibit != self._inhibited: - self._child_toggle_inhibit() - - def _child_toggle_inhibit(self): - '''Sends a request to the child process to toggle state''' - os.kill(self._process[0], signal.SIGUSR1) - - def _child_cb(self, pid, status, data=None): - '''Called when the child process exists''' - - # Empty stdout and stderr - for f in (self._process[2], self._process[3]): - while True: - buf = os.read(f, 256).decode('utf-8') - if buf == '': - break - if f == self._process[3]: # stderr - self._errors += buf - - # Check exit status of child - report_errors = False - try: - GLib.spawn_check_exit_status(status) - Gtk.main_quit() - except GLib.GError: - report_errors = True - - if report_errors: - self.emit('error-occured', self._errors) - - def _child_key_change_cb(self, key, value): - '''Called when the child process reports a change of internal state''' - - def parse_coord(s): - '''Parse coordinate like `42.0 N` or `91.5 W`''' - v, d = s.split(' ') - return float(v) * (1 if d in 'NE' else -1) - - if key == 'Status': - new_inhibited = value != 'Enabled' - if new_inhibited != self._inhibited: - self._inhibited = new_inhibited - self.emit('inhibit-changed', new_inhibited) - elif key == 'Color temperature': - new_temperature = int(value.rstrip('K'), 10) - if new_temperature != self._temperature: - self._temperature = new_temperature - self.emit('temperature-changed', new_temperature) - elif key == 'Period': - new_period = value - if new_period != self._period: - self._period = new_period - self.emit('period-changed', new_period) - elif key == 'Location': - new_location = tuple(parse_coord(x) for x in value.split(', ')) - if new_location != self._location: - self._location = new_location - self.emit('location-changed', *new_location) - - def _child_stdout_line_cb(self, line): - '''Called when the child process outputs a line to stdout''' - if line: - m = re.match(r'([\w ]+): (.+)', line) - if m: - key = m.group(1) - value = m.group(2) - self._child_key_change_cb(key, value) - - def _child_data_cb(self, f, cond, data): - '''Called when the child process has new data on stdout/stderr''' - - stdout, ib = data - ib.buf += os.read(f, 256).decode('utf-8') - - # Split input at line break - while True: - first, sep, last = ib.buf.partition('\n') - if sep == '': - break - ib.buf = last - if stdout: - self._child_stdout_line_cb(first) - else: - self._errors += first + '\n' - - return True - - def termwait(self): - '''Send SIGINT and wait for the child process to quit''' - try: - os.kill(self._process[0], signal.SIGINT) - os.waitpid(self._process[0], 0) - except ProcessLookupError: - # Process has apparently already disappeared - pass - - class RedshiftStatusIcon(object): - '''The status icon tracking the RedshiftController''' + """The status icon tracking the RedshiftController.""" def __init__(self, controller): - '''Creates a new instance of the status icon''' + """Creates a new instance of the status icon.""" self._controller = controller + self.icon_theme = Gtk.IconTheme.get_default() + icon_name = 'redshift-status-on-symbolic' + if not self.icon_theme.has_icon(icon_name): + icon_name = 'redshift-status-on' + if appindicator: # Create indicator - self.indicator = appindicator.Indicator.new('redshift', - 'redshift-status-on', - appindicator.IndicatorCategory.APPLICATION_STATUS) + self.indicator = appindicator.Indicator.new( + 'redshift', + icon_name, + appindicator.IndicatorCategory.APPLICATION_STATUS) self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) else: # Create status icon self.status_icon = Gtk.StatusIcon() - self.status_icon.set_from_icon_name('redshift-status-on') + self.status_icon.set_from_icon_name(icon_name) self.status_icon.set_tooltip_text('Redshift') # Create popup menu @@ -266,7 +84,9 @@ class RedshiftStatusIcon(object): suspend_menu = Gtk.Menu() for minutes, label in [(30, _('30 minutes')), (60, _('1 hour')), - (120, _('2 hours'))]: + (120, _('2 hours')), + (240, _('4 hours')), + (480, _('8 hours'))]: suspend_item = Gtk.MenuItem.new_with_label(label) suspend_item.connect('activate', self.suspend_cb, minutes) suspend_menu.append(suspend_item) @@ -274,16 +94,17 @@ class RedshiftStatusIcon(object): self.status_menu.append(suspend_menu_item) # Add autostart option - autostart_item = Gtk.CheckMenuItem.new_with_label(_('Autostart')) - try: - autostart_item.set_active(utils.get_autostart()) - except IOError as strerror: - print(strerror) - autostart_item.set_property('sensitive', False) - else: - autostart_item.connect('toggled', self.autostart_cb) - finally: - self.status_menu.append(autostart_item) + if utils.supports_autostart(): + autostart_item = Gtk.CheckMenuItem.new_with_label(_('Autostart')) + try: + autostart_item.set_active(utils.get_autostart()) + except IOError as strerror: + print(strerror) + autostart_item.set_property('sensitive', False) + else: + autostart_item.connect('toggled', self.autostart_cb) + finally: + self.status_menu.append(autostart_item) # Add info action info_item = Gtk.MenuItem.new_with_label(_('Info')) @@ -296,44 +117,53 @@ class RedshiftStatusIcon(object): self.status_menu.append(quit_item) # Create info dialog - self.info_dialog = Gtk.Dialog() - self.info_dialog.set_title(_('Info')) - self.info_dialog.add_button(_('Close'), Gtk.ButtonsType.CLOSE) + self.info_dialog = Gtk.Window(title=_('Info')) self.info_dialog.set_resizable(False) self.info_dialog.set_property('border-width', 6) + self.info_dialog.connect('delete-event', self.close_info_dialog_cb) + + content_area = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + self.info_dialog.add(content_area) + content_area.show() self.status_label = Gtk.Label() self.status_label.set_alignment(0.0, 0.5) self.status_label.set_padding(6, 6) - self.info_dialog.get_content_area().pack_start(self.status_label, True, True, 0) + content_area.pack_start(self.status_label, True, True, 0) self.status_label.show() self.location_label = Gtk.Label() self.location_label.set_alignment(0.0, 0.5) self.location_label.set_padding(6, 6) - self.info_dialog.get_content_area().pack_start(self.location_label, True, True, 0) + content_area.pack_start(self.location_label, True, True, 0) self.location_label.show() self.temperature_label = Gtk.Label() self.temperature_label.set_alignment(0.0, 0.5) self.temperature_label.set_padding(6, 6) - self.info_dialog.get_content_area().pack_start(self.temperature_label, True, True, 0) + content_area.pack_start(self.temperature_label, True, True, 0) self.temperature_label.show() self.period_label = Gtk.Label() self.period_label.set_alignment(0.0, 0.5) self.period_label.set_padding(6, 6) - self.info_dialog.get_content_area().pack_start(self.period_label, True, True, 0) + content_area.pack_start(self.period_label, True, True, 0) self.period_label.show() - self.info_dialog.connect('response', self.response_info_cb) + self.close_button = Gtk.Button(label=_('Close')) + content_area.pack_start(self.close_button, True, True, 0) + self.close_button.connect('clicked', self.close_info_dialog_cb) + self.close_button.show() # Setup signals to property changes self._controller.connect('inhibit-changed', self.inhibit_change_cb) self._controller.connect('period-changed', self.period_change_cb) - self._controller.connect('temperature-changed', self.temperature_change_cb) + self._controller.connect( + 'temperature-changed', self.temperature_change_cb) self._controller.connect('location-changed', self.location_change_cb) self._controller.connect('error-occured', self.error_occured_cb) + self._controller.connect('stopped', self.controller_stopped_cb) + self.icon_theme.connect('changed', self.on_icon_theme_changed_cb) # Set info box text self.change_inhibited(self._controller.inhibited) @@ -356,18 +186,18 @@ class RedshiftStatusIcon(object): self.suspend_timer = None def remove_suspend_timer(self): - '''Disable any previously set suspend timer''' + """Disable any previously set suspend timer.""" if self.suspend_timer is not None: GLib.source_remove(self.suspend_timer) self.suspend_timer = None def suspend_cb(self, item, minutes): - '''Callback that handles activation of a suspend timer - - The minutes parameter is the number of minutes to suspend. Even if redshift - is not disabled when called, it will still set a suspend timer and - reactive redshift when the timer is up.''' + """Callback that handles activation of a suspend timer. + The minutes parameter is the number of minutes to suspend. Even if + redshift is not disabled when called, it will still set a suspend timer + and reactive redshift when the timer is up. + """ # Inhibit self._controller.set_inhibit(True) @@ -376,29 +206,30 @@ class RedshiftStatusIcon(object): self.remove_suspend_timer() # If redshift was already disabled we reenable it nonetheless. - self.suspend_timer = GLib.timeout_add_seconds(minutes * 60, self.reenable_cb) + self.suspend_timer = GLib.timeout_add_seconds( + minutes * 60, self.reenable_cb) def reenable_cb(self): - '''Callback to reenable redshift when a suspend timer expires''' + """Callback to reenable redshift when a suspend timer expires.""" self._controller.set_inhibit(False) def popup_menu_cb(self, widget, button, time, data=None): - '''Callback when the popup menu on the status icon has to open''' + """Callback when the popup menu on the status icon has to open.""" self.status_menu.show_all() self.status_menu.popup(None, None, Gtk.StatusIcon.position_menu, self.status_icon, button, time) def toggle_cb(self, widget, data=None): - '''Callback when a request to toggle redshift was made''' + """Callback when a request to toggle redshift was made.""" self.remove_suspend_timer() self._controller.set_inhibit(not self._controller.inhibited) def toggle_item_cb(self, widget, data=None): - '''Callback then a request to toggle redshift was made from a toggle item + """Callback when a request to toggle redshift was made. This ensures that the state of redshift is synchronised with - the toggle state of the widget (e.g. Gtk.CheckMenuItem).''' - + the toggle state of the widget (e.g. Gtk.CheckMenuItem). + """ active = not self._controller.inhibited if active != widget.get_active(): self.remove_suspend_timer() @@ -406,115 +237,150 @@ class RedshiftStatusIcon(object): # Info dialog callbacks def show_info_cb(self, widget, data=None): - '''Callback when the info dialog should be presented''' + """Callback when the info dialog should be presented.""" self.info_dialog.show() def response_info_cb(self, widget, data=None): - '''Callback when a button in the info dialog was activated''' + """Callback when a button in the info dialog was activated.""" self.info_dialog.hide() + def close_info_dialog_cb(self, widget, data=None): + """Callback when the info dialog is closed.""" + self.info_dialog.hide() + return True + + def on_icon_theme_changed_cb(self, theme): + self.update_status_icon() + def update_status_icon(self): - '''Update the status icon according to the internally recorded state + """Update the status icon according to the internally recorded state. This should be called whenever the internally recorded state - might have changed.''' + might have changed. + """ + if self._controller.inhibited: + icon_name = 'redshift-status-off-symbolic' + else: + icon_name = 'redshift-status-on-symbolic' + + if not self.icon_theme.has_icon(icon_name): + icon_name = icon_name.replace('-symbolic', '') - # Update status icon if appindicator: - if not self._controller.inhibited: - self.indicator.set_icon('redshift-status-on') - else: - self.indicator.set_icon('redshift-status-off') + self.indicator.set_icon(icon_name) else: - if not self._controller.inhibited: - self.status_icon.set_from_icon_name('redshift-status-on') - else: - self.status_icon.set_from_icon_name('redshift-status-off') + self.status_icon.set_from_icon_name(icon_name) # State update functions def inhibit_change_cb(self, controller, inhibit): - '''Callback when controller changes inhibition status''' + """Callback when controller changes inhibition status.""" self.change_inhibited(inhibit) def period_change_cb(self, controller, period): - '''Callback when controller changes period''' + """Callback when controller changes period.""" self.change_period(period) def temperature_change_cb(self, controller, temperature): - '''Callback when controller changes temperature''' + """Callback when controller changes temperature.""" self.change_temperature(temperature) def location_change_cb(self, controller, lat, lon): - '''Callback when controlled changes location''' + """Callback when controlled changes location.""" self.change_location((lat, lon)) def error_occured_cb(self, controller, error): - '''Callback when an error occurs in the controller''' - error_dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, - Gtk.ButtonsType.CLOSE, '') - error_dialog.set_markup('<b>Failed to run Redshift</b>\n<i>' + error + '</i>') + """Callback when an error occurs in the controller.""" + error_dialog = Gtk.MessageDialog( + None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, + Gtk.ButtonsType.CLOSE, '') + error_dialog.set_markup( + '<b>Failed to run Redshift</b>\n<i>' + error + '</i>') error_dialog.run() # Quit when the model dialog is closed sys.exit(-1) + def controller_stopped_cb(self, controller): + """Callback when controlled is stopped successfully.""" + Gtk.main_quit() + # Update interface def change_inhibited(self, inhibited): - '''Change interface to new inhibition status''' + """Change interface to new inhibition status.""" self.update_status_icon() self.toggle_item.set_active(not inhibited) - self.status_label.set_markup(_('<b>Status:</b> {}').format(_('Disabled') if inhibited else _('Enabled'))) + self.status_label.set_markup( + _('<b>Status:</b> {}').format( + _('Disabled') if inhibited else _('Enabled'))) def change_temperature(self, temperature): - '''Change interface to new temperature''' - self.temperature_label.set_markup('<b>{}:</b> {}K'.format(_('Color temperature'), temperature)) + """Change interface to new temperature.""" + self.temperature_label.set_markup( + '<b>{}:</b> {}K'.format(_('Color temperature'), temperature)) + self.update_tooltip_text() def change_period(self, period): - '''Change interface to new period''' - self.period_label.set_markup('<b>{}:</b> {}'.format(_('Period'), period)) + """Change interface to new period.""" + self.period_label.set_markup( + '<b>{}:</b> {}'.format(_('Period'), period)) + self.update_tooltip_text() def change_location(self, location): - '''Change interface to new location''' - self.location_label.set_markup('<b>{}:</b> {}, {}'.format(_('Location'), *location)) + """Change interface to new location.""" + self.location_label.set_markup( + '<b>{}:</b> {}, {}'.format(_('Location'), *location)) + def update_tooltip_text(self): + """Update text of tooltip status icon.""" + if not appindicator: + self.status_icon.set_tooltip_text('{}: {}K, {}: {}'.format( + _('Color temperature'), self._controller.temperature, + _('Period'), self._controller.period)) def autostart_cb(self, widget, data=None): - '''Callback when a request to toggle autostart is made''' + """Callback when a request to toggle autostart is made.""" utils.set_autostart(widget.get_active()) def destroy_cb(self, widget, data=None): - '''Callback when a request to quit the application is made''' + """Callback when a request to quit the application is made.""" if not appindicator: self.status_icon.set_visible(False) - Gtk.main_quit() + self.info_dialog.destroy() + self._controller.terminate_child() return False -def sigterm_handler(data=None): - sys.exit(0) - - def run(): utils.setproctitle('redshift-gtk') - # Install TERM signal handler - GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGTERM, - sigterm_handler, None) - GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, - sigterm_handler, None) - # Internationalisation gettext.bindtextdomain('redshift', defs.LOCALEDIR) gettext.textdomain('redshift') + for help_arg in ('-h', '--help'): + if help_arg in sys.argv: + print(_('Please run `redshift -h` for help output.')) + sys.exit(-1) + # Create redshift child process controller c = RedshiftController(sys.argv[1:]) + + def terminate_child(data=None): + c.terminate_child() + return False + + # Install signal handlers + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGTERM, + terminate_child, None) + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, + terminate_child, None) + try: # Create status icon - s = RedshiftStatusIcon(c) + RedshiftStatusIcon(c) # Run main loop Gtk.main() - finally: - # Always make sure that the child process is closed - c.termwait() + except: + c.kill_child() + raise diff --git a/src/redshift-gtk/utils.py b/src/redshift-gtk/utils.py index 73c95a9..9365356 100644 --- a/src/redshift-gtk/utils.py +++ b/src/redshift-gtk/utils.py @@ -20,8 +20,14 @@ import ctypes import os import sys -from xdg import BaseDirectory as base -from xdg import DesktopEntry as desktop + +try: + from xdg import BaseDirectory + from xdg import DesktopEntry + has_xdg = True +except ImportError: + has_xdg = False + REDSHIFT_DESKTOP = 'redshift-gtk.desktop' @@ -32,12 +38,13 @@ AUTOSTART_KEYS = (('Hidden', ('true', 'false')), def open_autostart_file(): - autostart_dir = base.save_config_path("autostart") + autostart_dir = BaseDirectory.save_config_path("autostart") autostart_file = os.path.join(autostart_dir, REDSHIFT_DESKTOP) if not os.path.exists(autostart_file): - desktop_files = list(base.load_data_paths("applications", - REDSHIFT_DESKTOP)) + desktop_files = list( + BaseDirectory.load_data_paths( + "applications", REDSHIFT_DESKTOP)) if not desktop_files: raise IOError("Installed redshift desktop file not found!") @@ -45,21 +52,33 @@ def open_autostart_file(): desktop_file_path = desktop_files[0] # Read installed file - dfile = desktop.DesktopEntry(desktop_file_path) + dfile = DesktopEntry.DesktopEntry(desktop_file_path) for key, values in AUTOSTART_KEYS: dfile.set(key, values[False]) dfile.write(filename=autostart_file) else: - dfile = desktop.DesktopEntry(autostart_file) + dfile = DesktopEntry.DesktopEntry(autostart_file) return dfile, autostart_file + +def supports_autostart(): + return has_xdg + + def get_autostart(): + if not has_xdg: + return False + dfile, path = open_autostart_file() check_key, check_values = AUTOSTART_KEYS[0] return dfile.get(check_key) == check_values[True] + def set_autostart(active): + if not has_xdg: + return + dfile, path = open_autostart_file() for key, values in AUTOSTART_KEYS: dfile.set(key, values[active]) @@ -67,6 +86,7 @@ def set_autostart(active): def setproctitle(title): + """Set process title.""" title_bytes = title.encode(sys.getdefaultencoding(), 'replace') buf = ctypes.create_string_buffer(title_bytes) if 'linux' in sys.platform: diff --git a/src/redshift.c b/src/redshift.c index 0fcb0ba..d2e79f6 100644 --- a/src/redshift.c +++ b/src/redshift.c @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2009-2015 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2009-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifdef HAVE_CONFIG_H @@ -28,6 +28,22 @@ #include <math.h> #include <locale.h> #include <errno.h> +#include <time.h> + +/* poll.h is not available on Windows but there is no Windows location provider + using polling. On Windows, we just define some stubs to make things compile. + */ +#ifndef _WIN32 +# include <poll.h> +#else +#define POLLIN 0 +struct pollfd { + int fd; + short events; + short revents; +}; +int poll(struct pollfd *fds, int nfds, int timeout) { abort(); return -1; } +#endif #if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) # include <signal.h> @@ -48,11 +64,8 @@ #include "solar.h" #include "systemtime.h" #include "hooks.h" - - -#define MIN(x,y) ((x) < (y) ? (x) : (y)) -#define MAX(x,y) ((x) > (y) ? (x) : (y)) -#define CLAMP(lo,x,up) (MAX((lo), MIN((x), (up)))) +#include "signals.h" +#include "options.h" /* pause() is not defined on windows platform but is not needed either. Use a noop macro instead. */ @@ -62,6 +75,10 @@ #include "gamma-dummy.h" +#ifdef ENABLE_COOPGAMMA +# include "gamma-coopgamma.h" +#endif + #ifdef ENABLE_DRM # include "gamma-drm.h" #endif @@ -85,10 +102,6 @@ #include "location-manual.h" -#ifdef ENABLE_GEOCLUE -# include "location-geoclue.h" -#endif - #ifdef ENABLE_GEOCLUE2 # include "location-geoclue2.h" #endif @@ -97,171 +110,10 @@ # include "location-corelocation.h" #endif - -/* Union of state data for gamma adjustment methods */ -typedef union { -#ifdef ENABLE_DRM - drm_state_t drm; -#endif -#ifdef ENABLE_RANDR - randr_state_t randr; -#endif -#ifdef ENABLE_VIDMODE - vidmode_state_t vidmode; -#endif -#ifdef ENABLE_QUARTZ - quartz_state_t quartz; -#endif -#ifdef ENABLE_WINGDI - w32gdi_state_t w32gdi; -#endif -} gamma_state_t; +#undef CLAMP +#define CLAMP(lo,mid,up) (((lo) > (mid)) ? (lo) : (((mid) < (up)) ? (mid) : (up))) -/* Gamma adjustment method structs */ -static const gamma_method_t gamma_methods[] = { -#ifdef ENABLE_DRM - { - "drm", 0, - (gamma_method_init_func *)drm_init, - (gamma_method_start_func *)drm_start, - (gamma_method_free_func *)drm_free, - (gamma_method_print_help_func *)drm_print_help, - (gamma_method_set_option_func *)drm_set_option, - (gamma_method_restore_func *)drm_restore, - (gamma_method_set_temperature_func *)drm_set_temperature - }, -#endif -#ifdef ENABLE_RANDR - { - "randr", 1, - (gamma_method_init_func *)randr_init, - (gamma_method_start_func *)randr_start, - (gamma_method_free_func *)randr_free, - (gamma_method_print_help_func *)randr_print_help, - (gamma_method_set_option_func *)randr_set_option, - (gamma_method_restore_func *)randr_restore, - (gamma_method_set_temperature_func *)randr_set_temperature - }, -#endif -#ifdef ENABLE_VIDMODE - { - "vidmode", 1, - (gamma_method_init_func *)vidmode_init, - (gamma_method_start_func *)vidmode_start, - (gamma_method_free_func *)vidmode_free, - (gamma_method_print_help_func *)vidmode_print_help, - (gamma_method_set_option_func *)vidmode_set_option, - (gamma_method_restore_func *)vidmode_restore, - (gamma_method_set_temperature_func *)vidmode_set_temperature - }, -#endif -#ifdef ENABLE_QUARTZ - { - "quartz", 1, - (gamma_method_init_func *)quartz_init, - (gamma_method_start_func *)quartz_start, - (gamma_method_free_func *)quartz_free, - (gamma_method_print_help_func *)quartz_print_help, - (gamma_method_set_option_func *)quartz_set_option, - (gamma_method_restore_func *)quartz_restore, - (gamma_method_set_temperature_func *)quartz_set_temperature - }, -#endif -#ifdef ENABLE_WINGDI - { - "wingdi", 1, - (gamma_method_init_func *)w32gdi_init, - (gamma_method_start_func *)w32gdi_start, - (gamma_method_free_func *)w32gdi_free, - (gamma_method_print_help_func *)w32gdi_print_help, - (gamma_method_set_option_func *)w32gdi_set_option, - (gamma_method_restore_func *)w32gdi_restore, - (gamma_method_set_temperature_func *)w32gdi_set_temperature - }, -#endif - { - "dummy", 0, - (gamma_method_init_func *)gamma_dummy_init, - (gamma_method_start_func *)gamma_dummy_start, - (gamma_method_free_func *)gamma_dummy_free, - (gamma_method_print_help_func *)gamma_dummy_print_help, - (gamma_method_set_option_func *)gamma_dummy_set_option, - (gamma_method_restore_func *)gamma_dummy_restore, - (gamma_method_set_temperature_func *)gamma_dummy_set_temperature - }, - { NULL } -}; - - -/* Union of state data for location providers */ -typedef union { - location_manual_state_t manual; -#ifdef ENABLE_GEOCLUE - location_geoclue_state_t geoclue; -#endif -} location_state_t; - - -/* Location provider method structs */ -static const location_provider_t location_providers[] = { -#ifdef ENABLE_GEOCLUE - { - "geoclue", - (location_provider_init_func *)location_geoclue_init, - (location_provider_start_func *)location_geoclue_start, - (location_provider_free_func *)location_geoclue_free, - (location_provider_print_help_func *) - location_geoclue_print_help, - (location_provider_set_option_func *) - location_geoclue_set_option, - (location_provider_get_location_func *) - location_geoclue_get_location - }, -#endif -#ifdef ENABLE_GEOCLUE2 - { - "geoclue2", - (location_provider_init_func *)location_geoclue2_init, - (location_provider_start_func *)location_geoclue2_start, - (location_provider_free_func *)location_geoclue2_free, - (location_provider_print_help_func *) - location_geoclue2_print_help, - (location_provider_set_option_func *) - location_geoclue2_set_option, - (location_provider_get_location_func *) - location_geoclue2_get_location - }, -#endif -#ifdef ENABLE_CORELOCATION - { - "corelocation", - (location_provider_init_func *)location_corelocation_init, - (location_provider_start_func *)location_corelocation_start, - (location_provider_free_func *)location_corelocation_free, - (location_provider_print_help_func *) - location_corelocation_print_help, - (location_provider_set_option_func *) - location_corelocation_set_option, - (location_provider_get_location_func *) - location_corelocation_get_location - }, -#endif - { - "manual", - (location_provider_init_func *)location_manual_init, - (location_provider_start_func *)location_manual_start, - (location_provider_free_func *)location_manual_free, - (location_provider_print_help_func *) - location_manual_print_help, - (location_provider_set_option_func *) - location_manual_set_option, - (location_provider_get_location_func *) - location_manual_get_location - }, - { NULL } -}; - /* Bounds for parameters. */ #define MIN_LAT -90.0 #define MAX_LAT 90.0 @@ -274,44 +126,12 @@ static const location_provider_t location_providers[] = { #define MIN_GAMMA 0.1 #define MAX_GAMMA 10.0 -/* Default values for parameters. */ -#define DEFAULT_DAY_TEMP 5500 -#define DEFAULT_NIGHT_TEMP 3500 -#define DEFAULT_BRIGHTNESS 1.0 -#define DEFAULT_GAMMA 1.0 - -/* The color temperature when no adjustment is applied. */ -#define NEUTRAL_TEMP 6500 - -/* Angular elevation of the sun at which the color temperature - transition period starts and ends (in degress). - Transition during twilight, and while the sun is lower than - 3.0 degrees above the horizon. */ -#define TRANSITION_LOW SOLAR_CIVIL_TWILIGHT_ELEV -#define TRANSITION_HIGH 3.0 - /* Duration of sleep between screen updates (milliseconds). */ #define SLEEP_DURATION 5000 #define SLEEP_DURATION_SHORT 100 -/* Program modes. */ -typedef enum { - PROGRAM_MODE_CONTINUAL, - PROGRAM_MODE_ONE_SHOT, - PROGRAM_MODE_PRINT, - PROGRAM_MODE_RESET, - PROGRAM_MODE_MANUAL -} program_mode_t; - -/* Transition scheme. - The solar elevations at which the transition begins/ends, - and the association color settings. */ -typedef struct { - double high; - double low; - color_setting_t day; - color_setting_t night; -} transition_scheme_t; +/* Length of fade in numbers of short sleep durations. */ +#define FADE_LENGTH 40 /* Names of periods of day */ static const char *period_names[] = { @@ -322,37 +142,26 @@ static const char *period_names[] = { N_("Transition") }; -#if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) - -static volatile sig_atomic_t exiting = 0; -static volatile sig_atomic_t disable = 0; -/* Signal handler for exit signals */ -static void -sigexit(int signo) -{ - exiting = 1; -} - -/* Signal handler for disable signal */ -static void -sigdisable(int signo) +/* Determine which period we are currently in based on time offset. */ +static period_t +get_period_from_time(const transition_scheme_t *transition, int time_offset) { - disable = 1; + if (time_offset < transition->dawn.start || + time_offset >= transition->dusk.end) { + return PERIOD_NIGHT; + } else if (time_offset >= transition->dawn.end && + time_offset < transition->dusk.start) { + return PERIOD_DAYTIME; + } else { + return PERIOD_TRANSITION; + } } -#else /* ! HAVE_SIGNAL_H || __WIN32__ */ - -static int exiting = 0; -static int disable = 0; - -#endif /* ! HAVE_SIGNAL_H || __WIN32__ */ - - -/* Determine which period we are currently in. */ +/* Determine which period we are currently in based on solar elevation. */ static period_t -get_period(const transition_scheme_t *transition, - double elevation) +get_period_from_elevation( + const transition_scheme_t *transition, double elevation) { if (elevation < transition->low) { return PERIOD_NIGHT; @@ -363,10 +172,31 @@ get_period(const transition_scheme_t *transition, } } -/* Determine how far through the transition we are. */ +/* Determine how far through the transition we are based on time offset. */ +static double +get_transition_progress_from_time( + const transition_scheme_t *transition, int time_offset) +{ + if (time_offset < transition->dawn.start || + time_offset >= transition->dusk.end) { + return 0.0; + } else if (time_offset < transition->dawn.end) { + return (transition->dawn.start - time_offset) / + (double)(transition->dawn.start - + transition->dawn.end); + } else if (time_offset > transition->dusk.start) { + return (transition->dusk.end - time_offset) / + (double)(transition->dusk.end - + transition->dusk.start); + } else { + return 1.0; + } +} + +/* Determine how far through the transition we are based on elevation. */ static double -get_transition_progress(const transition_scheme_t *transition, - double elevation) +get_transition_progress_from_elevation( + const transition_scheme_t *transition, double elevation) { if (elevation < transition->low) { return 0.0; @@ -378,6 +208,20 @@ get_transition_progress(const transition_scheme_t *transition, } } +/* Return number of seconds since midnight from timestamp. */ +static int +get_seconds_since_midnight(double timestamp) +{ + time_t t = (time_t)timestamp; + struct tm tm; +#ifdef _WIN32 + localtime_s(&tm, &t); +#else + localtime_r(&t, &tm); +#endif + return tm.tm_sec + tm.tm_min * 60 + tm.tm_hour * 3600; +} + /* Print verbose description of the given period. */ static void print_period(period_t period, double transition) @@ -417,134 +261,70 @@ print_location(const location_t *location) fabs(location->lon), location->lon >= 0.f ? east : west); } -/* Interpolate color setting structs based on solar elevation */ +/* Interpolate color setting structs given alpha. */ static void -interpolate_color_settings(const transition_scheme_t *transition, - double elevation, - color_setting_t *result) +interpolate_color_settings( + const color_setting_t *first, + const color_setting_t *second, + double alpha, + color_setting_t *result) { - const color_setting_t *day = &transition->day; - const color_setting_t *night = &transition->night; - - double alpha = (transition->low - elevation) / - (transition->low - transition->high); alpha = CLAMP(0.0, alpha, 1.0); - result->temperature = (1.0-alpha)*night->temperature + - alpha*day->temperature; - result->brightness = (1.0-alpha)*night->brightness + - alpha*day->brightness; + result->temperature = (1.0-alpha)*first->temperature + + alpha*second->temperature; + result->brightness = (1.0-alpha)*first->brightness + + alpha*second->brightness; for (int i = 0; i < 3; i++) { - result->gamma[i] = (1.0-alpha)*night->gamma[i] + - alpha*day->gamma[i]; + result->gamma[i] = (1.0-alpha)*first->gamma[i] + + alpha*second->gamma[i]; } } - +/* Interpolate color setting structs transition scheme. */ static void -print_help(const char *program_name) +interpolate_transition_scheme( + const transition_scheme_t *transition, + double alpha, + color_setting_t *result) { - /* TRANSLATORS: help output 1 - LAT is latitude, LON is longitude, - DAY is temperature at daytime, - NIGHT is temperature at night - no-wrap */ - printf(_("Usage: %s -l LAT:LON -t DAY:NIGHT [OPTIONS...]\n"), - program_name); - fputs("\n", stdout); - - /* TRANSLATORS: help output 2 - no-wrap */ - fputs(_("Set color temperature of display" - " according to time of day.\n"), stdout); - fputs("\n", stdout); - - /* TRANSLATORS: help output 3 - no-wrap */ - fputs(_(" -h\t\tDisplay this help message\n" - " -v\t\tVerbose output\n" - " -V\t\tShow program version\n"), stdout); - fputs("\n", stdout); - - /* TRANSLATORS: help output 4 - `list' must not be translated - no-wrap */ - fputs(_(" -b DAY:NIGHT\tScreen brightness to apply (between 0.1 and 1.0)\n" - " -c FILE\tLoad settings from specified configuration file\n" - " -g R:G:B\tAdditional gamma correction to apply\n" - " -l LAT:LON\tYour current location\n" - " -l PROVIDER\tSelect provider for automatic" - " location updates\n" - " \t\t(Type `list' to see available providers)\n" - " -m METHOD\tMethod to use to set color temperature\n" - " \t\t(Type `list' to see available methods)\n" - " -o\t\tOne shot mode (do not continuously adjust" - " color temperature)\n" - " -O TEMP\tOne shot manual mode (set color temperature)\n" - " -p\t\tPrint mode (only print parameters and exit)\n" - " -x\t\tReset mode (remove adjustment from screen)\n" - " -r\t\tDisable temperature transitions\n" - " -t DAY:NIGHT\tColor temperature to set at daytime/night\n"), - stdout); - fputs("\n", stdout); - - /* TRANSLATORS: help output 5 */ - printf(_("The neutral temperature is %uK. Using this value will not\n" - "change the color temperature of the display. Setting the\n" - "color temperature to a value higher than this results in\n" - "more blue light, and setting a lower value will result in\n" - "more red light.\n"), - NEUTRAL_TEMP); - - fputs("\n", stdout); - - /* TRANSLATORS: help output 6 */ - printf(_("Default values:\n\n" - " Daytime temperature: %uK\n" - " Night temperature: %uK\n"), - DEFAULT_DAY_TEMP, DEFAULT_NIGHT_TEMP); - - fputs("\n", stdout); - - /* TRANSLATORS: help output 7 */ - printf(_("Please report bugs to <%s>\n"), PACKAGE_BUGREPORT); + const color_setting_t *day = &transition->day; + const color_setting_t *night = &transition->night; + + alpha = CLAMP(0.0, alpha, 1.0); + interpolate_color_settings(night, day, alpha, result); } -static void -print_method_list() +/* Return 1 if color settings have major differences, otherwise 0. + Used to determine if a fade should be applied in continual mode. */ +static int +color_setting_diff_is_major( + const color_setting_t *first, + const color_setting_t *second) { - fputs(_("Available adjustment methods:\n"), stdout); - for (int i = 0; gamma_methods[i].name != NULL; i++) { - printf(" %s\n", gamma_methods[i].name); - } - - fputs("\n", stdout); - fputs(_("Specify colon-separated options with" - " `-m METHOD:OPTIONS'.\n"), stdout); - /* TRANSLATORS: `help' must not be translated. */ - fputs(_("Try `-m METHOD:help' for help.\n"), stdout); + return (abs(first->temperature - second->temperature) > 25 || + fabsf(first->brightness - second->brightness) > 0.1 || + fabsf(first->gamma[0] - second->gamma[0]) > 0.1 || + fabsf(first->gamma[1] - second->gamma[1]) > 0.1 || + fabsf(first->gamma[2] - second->gamma[2]) > 0.1); } +/* Reset color setting to default values. */ static void -print_provider_list() +color_setting_reset(color_setting_t *color) { - fputs(_("Available location providers:\n"), stdout); - for (int i = 0; location_providers[i].name != NULL; i++) { - printf(" %s\n", location_providers[i].name); - } - - fputs("\n", stdout); - fputs(_("Specify colon-separated options with" - "`-l PROVIDER:OPTIONS'.\n"), stdout); - /* TRANSLATORS: `help' must not be translated. */ - fputs(_("Try `-l PROVIDER:help' for help.\n"), stdout); + color->temperature = NEUTRAL_TEMP; + color->gamma[0] = 1.0; + color->gamma[1] = 1.0; + color->gamma[2] = 1.0; + color->brightness = 1.0; } static int provider_try_start(const location_provider_t *provider, - location_state_t *state, - config_ini_state_t *config, char *args) + location_state_t **state, config_ini_state_t *config, + char *args) { int r; @@ -561,10 +341,10 @@ provider_try_start(const location_provider_t *provider, if (section != NULL) { config_ini_setting_t *setting = section->settings; while (setting != NULL) { - r = provider->set_option(state, setting->name, + r = provider->set_option(*state, setting->name, setting->value); if (r < 0) { - provider->free(state); + provider->free(*state); fprintf(stderr, _("Failed to set %s" " option.\n"), provider->name); @@ -606,9 +386,9 @@ provider_try_start(const location_provider_t *provider, *(value++) = '\0'; } - r = provider->set_option(state, key, value); + r = provider->set_option(*state, key, value); if (r < 0) { - provider->free(state); + provider->free(*state); fprintf(stderr, _("Failed to set %s option.\n"), provider->name); /* TRANSLATORS: `help' must not be translated. */ @@ -622,9 +402,9 @@ provider_try_start(const location_provider_t *provider, } /* Start provider. */ - r = provider->start(state); + r = provider->start(*state); if (r < 0) { - provider->free(state); + provider->free(*state); fprintf(stderr, _("Failed to start provider %s.\n"), provider->name); return -1; @@ -634,9 +414,8 @@ provider_try_start(const location_provider_t *provider, } static int -method_try_start(const gamma_method_t *method, - gamma_state_t *state, - config_ini_state_t *config, char *args) +method_try_start(const gamma_method_t *method, gamma_state_t **state, + program_mode_t mode, config_ini_state_t *config, char *args) { int r; @@ -653,10 +432,10 @@ method_try_start(const gamma_method_t *method, if (section != NULL) { config_ini_setting_t *setting = section->settings; while (setting != NULL) { - r = method->set_option(state, setting->name, - setting->value); + r = method->set_option( + *state, setting->name, setting->value); if (r < 0) { - method->free(state); + method->free(*state); fprintf(stderr, _("Failed to set %s" " option.\n"), method->name); @@ -686,9 +465,9 @@ method_try_start(const gamma_method_t *method, *(value++) = '\0'; } - r = method->set_option(state, key, value); + r = method->set_option(*state, key, value); if (r < 0) { - method->free(state); + method->free(*state); fprintf(stderr, _("Failed to set %s option.\n"), method->name); /* TRANSLATORS: `help' must not be translated. */ @@ -701,9 +480,9 @@ method_try_start(const gamma_method_t *method, } /* Start method. */ - r = method->start(state); + r = method->start(*state, mode); if (r < 0) { - method->free(state); + method->free(*state); fprintf(stderr, _("Failed to start adjustment method %s.\n"), method->name); return -1; @@ -712,47 +491,6 @@ method_try_start(const gamma_method_t *method, return 0; } -/* A gamma string contains either one floating point value, - or three values separated by colon. */ -static int -parse_gamma_string(const char *str, float gamma[]) -{ - char *s = strchr(str, ':'); - if (s == NULL) { - /* Use value for all channels */ - float g = atof(str); - gamma[0] = gamma[1] = gamma[2] = g; - } else { - /* Parse separate value for each channel */ - *(s++) = '\0'; - char *g_s = s; - s = strchr(s, ':'); - if (s == NULL) return -1; - - *(s++) = '\0'; - gamma[0] = atof(str); /* Red */ - gamma[1] = atof(g_s); /* Blue */ - gamma[2] = atof(s); /* Green */ - } - - return 0; -} - -/* A brightness string contains either one floating point value, - or two values separated by a colon. */ -static void -parse_brightness_string(const char *str, float *bright_day, float *bright_night) -{ - char *s = strchr(str, ':'); - if (s == NULL) { - /* Same value for day and night. */ - *bright_day = *bright_night = atof(str); - } else { - *(s++) = '\0'; - *bright_day = atof(str); - *bright_night = atof(s); - } -} /* Check whether gamma is within allowed levels. */ static int @@ -764,37 +502,102 @@ gamma_is_valid(const float gamma[3]) gamma[1] > MAX_GAMMA || gamma[2] < MIN_GAMMA || gamma[2] > MAX_GAMMA); - } -static const gamma_method_t * -find_gamma_method(const char *name) +/* Check whether location is valid. + Prints error message on stderr and returns 0 if invalid, otherwise + returns 1. */ +static int +location_is_valid(const location_t *location) { - const gamma_method_t *method = NULL; - for (int i = 0; gamma_methods[i].name != NULL; i++) { - const gamma_method_t *m = &gamma_methods[i]; - if (strcasecmp(name, m->name) == 0) { - method = m; - break; - } + /* Latitude */ + if (location->lat < MIN_LAT || location->lat > MAX_LAT) { + /* TRANSLATORS: Append degree symbols if possible. */ + fprintf(stderr, + _("Latitude must be between %.1f and %.1f.\n"), + MIN_LAT, MAX_LAT); + return 0; + } + + /* Longitude */ + if (location->lon < MIN_LON || location->lon > MAX_LON) { + /* TRANSLATORS: Append degree symbols if possible. */ + fprintf(stderr, + _("Longitude must be between" + " %.1f and %.1f.\n"), MIN_LON, MAX_LON); + return 0; } - return method; + return 1; } -static const location_provider_t * -find_location_provider(const char *name) +/* Wait for location to become available from provider. + Waits until timeout (milliseconds) has elapsed or forever if timeout + is -1. Writes location to loc. Returns -1 on error, + 0 if timeout was reached, 1 if location became available. */ +static int +provider_get_location( + const location_provider_t *provider, location_state_t *state, + int timeout, location_t *loc) { - const location_provider_t *provider = NULL; - for (int i = 0; location_providers[i].name != NULL; i++) { - const location_provider_t *p = &location_providers[i]; - if (strcasecmp(name, p->name) == 0) { - provider = p; - break; + int available = 0; + struct pollfd pollfds[1]; + while (!available) { + int loc_fd = provider->get_fd(state); + if (loc_fd >= 0) { + /* Provider is dynamic. */ + /* TODO: This should use a monotonic time source. */ + double now; + int r = systemtime_get_time(&now); + if (r < 0) { + fputs(_("Unable to read system time.\n"), + stderr); + return -1; + } + + /* Poll on file descriptor until ready. */ + pollfds[0].fd = loc_fd; + pollfds[0].events = POLLIN; + r = poll(pollfds, 1, timeout); + if (r < 0) { + perror("poll"); + return -1; + } else if (r == 0) { + return 0; + } + + double later; + r = systemtime_get_time(&later); + if (r < 0) { + fputs(_("Unable to read system time.\n"), + stderr); + return -1; + } + + /* Adjust timeout by elapsed time */ + if (timeout >= 0) { + timeout -= (later - now) * 1000; + timeout = timeout < 0 ? 0 : timeout; + } } + + + int r = provider->handle(state, loc, &available); + if (r < 0) return -1; } - return provider; + return 1; +} + +/* Easing function for fade. + See https://github.com/mietek/ease-tween */ +static double +ease_fade(double t) +{ + if (t <= 0) return 0; + if (t >= 1) return 1; + return 1.0042954579734844 * exp( + -6.4041738958415664 * exp(-7.2908241330981340 * t)); } @@ -803,123 +606,97 @@ find_location_provider(const char *name) current time and continuously updates the screen to the appropriate color temperature. */ static int -run_continual_mode(const location_t *loc, +run_continual_mode(const location_provider_t *provider, + location_state_t *location_state, const transition_scheme_t *scheme, const gamma_method_t *method, - gamma_state_t *state, - int transition, int verbose) + gamma_state_t *method_state, + int use_fade, int preserve_gamma, int verbose) { int r; - /* Make an initial transition from 6500K */ - int short_trans_delta = -1; - int short_trans_len = 10; + /* Short fade parameters */ + int fade_length = 0; + int fade_time = 0; + color_setting_t fade_start_interp; - /* Amount of adjustment to apply. At zero the color - temperature will be exactly as calculated, and at one it - will be exactly 6500K. */ - double adjustment_alpha = 1.0; - -#if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) - struct sigaction sigact; - sigset_t sigset; - sigemptyset(&sigset); - - /* Install signal handler for INT and TERM signals */ - sigact.sa_handler = sigexit; - sigact.sa_mask = sigset; - sigact.sa_flags = 0; - - r = sigaction(SIGINT, &sigact, NULL); + r = signals_install_handlers(); if (r < 0) { - perror("sigaction"); - return -1; + return r; } - r = sigaction(SIGTERM, &sigact, NULL); - if (r < 0) { - perror("sigaction"); - return -1; - } + /* Save previous parameters so we can avoid printing status updates if + the values did not change. */ + period_t prev_period = PERIOD_NONE; - /* Install signal handler for USR1 signal */ - sigact.sa_handler = sigdisable; - sigact.sa_mask = sigset; - sigact.sa_flags = 0; + /* Previous target color setting and current actual color setting. + Actual color setting takes into account the current color fade. */ + color_setting_t prev_target_interp; + color_setting_reset(&prev_target_interp); - r = sigaction(SIGUSR1, &sigact, NULL); - if (r < 0) { - perror("sigaction"); - return -1; - } + color_setting_t interp; + color_setting_reset(&interp); - /* Ignore CHLD signal. This causes child processes - (hooks) to be reaped automatically. */ - sigact.sa_handler = SIG_IGN; - sigact.sa_mask = sigset; - sigact.sa_flags = 0; + location_t loc = { NAN, NAN }; + int need_location = !scheme->use_time; + if (need_location) { + fputs(_("Waiting for initial location" + " to become available...\n"), stderr); - r = sigaction(SIGCHLD, &sigact, NULL); - if (r < 0) { - perror("sigaction"); - return -1; + /* Get initial location from provider */ + r = provider_get_location(provider, location_state, -1, &loc); + if (r < 0) { + fputs(_("Unable to get location" + " from provider.\n"), stderr); + return -1; + } + + if (!location_is_valid(&loc)) { + fputs(_("Invalid location returned from provider.\n"), + stderr); + return -1; + } + + print_location(&loc); } -#endif /* HAVE_SIGNAL_H && ! __WIN32__ */ if (verbose) { - printf(_("Status: %s\n"), _("Enabled")); + printf(_("Color temperature: %uK\n"), interp.temperature); + printf(_("Brightness: %.2f\n"), interp.brightness); } - /* Save previous colors so we can avoid - printing status updates if the values - did not change. */ - period_t prev_period = PERIOD_NONE; - color_setting_t prev_interp = - { -1, { NAN, NAN, NAN }, NAN }; - /* Continuously adjust color temperature */ int done = 0; + int prev_disabled = 1; int disabled = 0; + int location_available = 1; while (1) { /* Check to see if disable signal was caught */ - if (disable) { - short_trans_len = 2; - if (!disabled) { - /* Transition to disabled state */ - short_trans_delta = 1; - } else { - /* Transition back to enabled */ - short_trans_delta = -1; - } + if (disable && !done) { disabled = !disabled; disable = 0; - - if (verbose) { - printf(_("Status: %s\n"), disabled ? - _("Disabled") : _("Enabled")); - } } /* Check to see if exit signal was caught */ if (exiting) { if (done) { - /* On second signal stop the ongoing - transition */ - short_trans_delta = 0; - adjustment_alpha = 0.0; + /* On second signal stop the ongoing fade. */ + break; } else { - if (!disabled) { - /* Make a short transition - back to 6500K */ - short_trans_delta = 1; - short_trans_len = 2; - } - done = 1; + disabled = 1; } exiting = 0; } + /* Print status change */ + if (verbose && disabled != prev_disabled) { + printf(_("Status: %s\n"), disabled ? + _("Disabled") : _("Enabled")); + } + + prev_disabled = disabled; + /* Read timestamp */ double now; r = systemtime_get_time(&now); @@ -928,36 +705,47 @@ run_continual_mode(const location_t *loc, return -1; } - /* Skip over transition if transitions are disabled */ - int set_adjustments = 0; - if (!transition) { - if (short_trans_delta) { - adjustment_alpha = short_trans_delta < 0 ? - 0.0 : 1.0; - short_trans_delta = 0; - set_adjustments = 1; - } + period_t period; + double transition_prog; + if (scheme->use_time) { + int time_offset = get_seconds_since_midnight(now); + + period = get_period_from_time(scheme, time_offset); + transition_prog = get_transition_progress_from_time( + scheme, time_offset); + } else { + /* Current angular elevation of the sun */ + double elevation = solar_elevation( + now, loc.lat, loc.lon); + + period = get_period_from_elevation(scheme, elevation); + transition_prog = + get_transition_progress_from_elevation( + scheme, elevation); } - /* Current angular elevation of the sun */ - double elevation = solar_elevation(now, loc->lat, - loc->lon); + /* Use transition progress to get target color + temperature. */ + color_setting_t target_interp; + interpolate_transition_scheme( + scheme, transition_prog, &target_interp); - /* Use elevation of sun to set color temperature */ - color_setting_t interp; - interpolate_color_settings(scheme, elevation, &interp); + if (disabled) { + period = PERIOD_NONE; + color_setting_reset(&target_interp); + } + + if (done) { + period = PERIOD_NONE; + } /* Print period if it changed during this update, - or if we are in transition. In transition we + or if we are in the transition period. In transition we print the progress, so we always print it in that case. */ - period_t period = get_period(scheme, elevation); if (verbose && (period != prev_period || period == PERIOD_TRANSITION)) { - double transition = - get_transition_progress(scheme, - elevation); - print_period(period, transition); + print_period(period, transition_prog); } /* Activate hooks if period changed */ @@ -965,76 +753,146 @@ run_continual_mode(const location_t *loc, hooks_signal_period_change(prev_period, period); } - /* Ongoing short transition */ - if (short_trans_delta) { - /* Calculate alpha */ - adjustment_alpha += short_trans_delta * 0.1 / - (float)short_trans_len; - - /* Stop transition when done */ - if (adjustment_alpha <= 0.0 || - adjustment_alpha >= 1.0) { - short_trans_delta = 0; + /* Start fade if the parameter differences are too big to apply + instantly. */ + if (use_fade) { + if ((fade_length == 0 && + color_setting_diff_is_major( + &interp, + &target_interp)) || + (fade_length != 0 && + color_setting_diff_is_major( + &target_interp, + &prev_target_interp))) { + fade_length = FADE_LENGTH; + fade_time = 0; + fade_start_interp = interp; } - - /* Clamp alpha value */ - adjustment_alpha = - MAX(0.0, MIN(adjustment_alpha, 1.0)); } - /* Interpolate between 6500K and calculated - temperature */ - interp.temperature = adjustment_alpha*6500 + - (1.0-adjustment_alpha)*interp.temperature; + /* Handle ongoing fade */ + if (fade_length != 0) { + fade_time += 1; + double frac = fade_time / (double)fade_length; + double alpha = CLAMP(0.0, ease_fade(frac), 1.0); - interp.brightness = adjustment_alpha*1.0 + - (1.0-adjustment_alpha)*interp.brightness; + interpolate_color_settings( + &fade_start_interp, &target_interp, alpha, + &interp); - /* Quit loop when done */ - if (done && !short_trans_delta) break; + if (fade_time > fade_length) { + fade_time = 0; + fade_length = 0; + } + } else { + interp = target_interp; + } + + /* Break loop when done and final fade is over */ + if (done && fade_length == 0) break; if (verbose) { - if (interp.temperature != - prev_interp.temperature) { + if (prev_target_interp.temperature != + target_interp.temperature) { printf(_("Color temperature: %uK\n"), - interp.temperature); + target_interp.temperature); } - if (interp.brightness != - prev_interp.brightness) { + if (prev_target_interp.brightness != + target_interp.brightness) { printf(_("Brightness: %.2f\n"), - interp.brightness); + target_interp.brightness); } } /* Adjust temperature */ - if (!disabled || short_trans_delta || set_adjustments) { - r = method->set_temperature(state, &interp); + r = method->set_temperature( + method_state, &interp, preserve_gamma); + if (r < 0) { + fputs(_("Temperature adjustment failed.\n"), + stderr); + return -1; + } + + /* Save period and target color setting as previous */ + prev_period = period; + prev_target_interp = target_interp; + + /* Sleep length depends on whether a fade is ongoing. */ + int delay = SLEEP_DURATION; + if (fade_length != 0) { + delay = SLEEP_DURATION_SHORT; + } + + /* Update location. */ + int loc_fd = -1; + if (need_location) { + loc_fd = provider->get_fd(location_state); + } + + if (loc_fd >= 0) { + /* Provider is dynamic. */ + struct pollfd pollfds[1]; + pollfds[0].fd = loc_fd; + pollfds[0].events = POLLIN; + int r = poll(pollfds, 1, delay); if (r < 0) { - fputs(_("Temperature adjustment" - " failed.\n"), stderr); + if (errno == EINTR) continue; + perror("poll"); + fputs(_("Unable to get location" + " from provider.\n"), stderr); return -1; + } else if (r == 0) { + continue; } - } - /* Save temperature as previous */ - prev_period = period; - memcpy(&prev_interp, &interp, - sizeof(color_setting_t)); + /* Get new location and availability + information. */ + location_t new_loc; + int new_available; + r = provider->handle( + location_state, &new_loc, + &new_available); + if (r < 0) { + fputs(_("Unable to get location" + " from provider.\n"), stderr); + return -1; + } + + if (!new_available && + new_available != location_available) { + fputs(_("Location is temporarily" + " unavailable; Using previous" + " location until it becomes" + " available...\n"), stderr); + } + + if (new_available && + (new_loc.lat != loc.lat || + new_loc.lon != loc.lon || + new_available != location_available)) { + loc = new_loc; + print_location(&loc); + } + + location_available = new_available; - /* Sleep for 5 seconds or 0.1 second. */ - if (short_trans_delta) { - systemtime_msleep(SLEEP_DURATION_SHORT); + if (!location_is_valid(&loc)) { + fputs(_("Invalid location returned" + " from provider.\n"), stderr); + return -1; + } } else { - systemtime_msleep(SLEEP_DURATION); + systemtime_msleep(delay); } } /* Restore saved gamma ramps */ - method->restore(state); + method->restore(method_state); return 0; } + int main(int argc, char *argv[]) { @@ -1050,35 +908,41 @@ main(int argc, char *argv[]) textdomain(PACKAGE); #endif - /* Initialize settings to NULL values. */ - char *config_filepath = NULL; - - /* Settings for day, night and transition. - Initialized to indicate that the values are not set yet. */ - transition_scheme_t scheme = - { TRANSITION_HIGH, TRANSITION_LOW }; - - scheme.day.temperature = -1; - scheme.day.gamma[0] = NAN; - scheme.day.brightness = NAN; - - scheme.night.temperature = -1; - scheme.night.gamma[0] = NAN; - scheme.night.brightness = NAN; - - /* Temperature for manual mode */ - int temp_set = -1; - - const gamma_method_t *method = NULL; - char *method_args = NULL; - - const location_provider_t *provider = NULL; - char *provider_args = NULL; + /* List of gamma methods. */ + const gamma_method_t gamma_methods[] = { +#ifdef ENABLE_COOPGAMMA + coopgamma_gamma_method, +#endif +#ifdef ENABLE_DRM + drm_gamma_method, +#endif +#ifdef ENABLE_RANDR + randr_gamma_method, +#endif +#ifdef ENABLE_VIDMODE + vidmode_gamma_method, +#endif +#ifdef ENABLE_QUARTZ + quartz_gamma_method, +#endif +#ifdef ENABLE_WINGDI + w32gdi_gamma_method, +#endif + dummy_gamma_method, + { NULL } + }; - int transition = -1; - program_mode_t mode = PROGRAM_MODE_CONTINUAL; - int verbose = 0; - char *s; + /* List of location providers. */ + const location_provider_t location_providers[] = { +#ifdef ENABLE_GEOCLUE2 + geoclue2_location_provider, +#endif +#ifdef ENABLE_CORELOCATION + corelocation_location_provider, +#endif + manual_location_provider, + { NULL } + }; /* Flush messages consistently even if redirected to a pipe or file. Change the flush behaviour to line-buffered, without @@ -1086,331 +950,63 @@ main(int argc, char *argv[]) setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stderr, NULL, _IOLBF, 0); - /* Parse command line arguments. */ - int opt; - while ((opt = getopt(argc, argv, "b:c:g:hl:m:oO:prt:vVx")) != -1) { - switch (opt) { - case 'b': - parse_brightness_string(optarg, - &scheme.day.brightness, - &scheme.night.brightness); - break; - case 'c': - if (config_filepath != NULL) free(config_filepath); - config_filepath = strdup(optarg); - break; - case 'g': - r = parse_gamma_string(optarg, scheme.day.gamma); - if (r < 0) { - fputs(_("Malformed gamma argument.\n"), - stderr); - fputs(_("Try `-h' for more" - " information.\n"), stderr); - exit(EXIT_FAILURE); - } - - /* Set night gamma to the same value as day gamma. - To set these to distinct values use the config - file. */ - memcpy(scheme.night.gamma, scheme.day.gamma, - sizeof(scheme.night.gamma)); - break; - case 'h': - print_help(argv[0]); - exit(EXIT_SUCCESS); - break; - case 'l': - /* Print list of providers if argument is `list' */ - if (strcasecmp(optarg, "list") == 0) { - print_provider_list(); - exit(EXIT_SUCCESS); - } - - char *provider_name = NULL; - - /* Don't save the result of strtof(); we simply want - to know if optarg can be parsed as a float. */ - errno = 0; - char *end; - strtof(optarg, &end); - if (errno == 0 && *end == ':') { - /* Use instead as arguments to `manual'. */ - provider_name = "manual"; - provider_args = optarg; - } else { - /* Split off provider arguments. */ - s = strchr(optarg, ':'); - if (s != NULL) { - *(s++) = '\0'; - provider_args = s; - } - - provider_name = optarg; - } - - /* Lookup provider from name. */ - provider = find_location_provider(provider_name); - if (provider == NULL) { - fprintf(stderr, _("Unknown location provider" - " `%s'.\n"), provider_name); - exit(EXIT_FAILURE); - } - - /* Print provider help if arg is `help'. */ - if (provider_args != NULL && - strcasecmp(provider_args, "help") == 0) { - provider->print_help(stdout); - exit(EXIT_SUCCESS); - } - break; - case 'm': - /* Print list of methods if argument is `list' */ - if (strcasecmp(optarg, "list") == 0) { - print_method_list(); - exit(EXIT_SUCCESS); - } - - /* Split off method arguments. */ - s = strchr(optarg, ':'); - if (s != NULL) { - *(s++) = '\0'; - method_args = s; - } - - /* Find adjustment method by name. */ - method = find_gamma_method(optarg); - if (method == NULL) { - /* TRANSLATORS: This refers to the method - used to adjust colors e.g VidMode */ - fprintf(stderr, _("Unknown adjustment method" - " `%s'.\n"), optarg); - exit(EXIT_FAILURE); - } - - /* Print method help if arg is `help'. */ - if (method_args != NULL && - strcasecmp(method_args, "help") == 0) { - method->print_help(stdout); - exit(EXIT_SUCCESS); - } - break; - case 'o': - mode = PROGRAM_MODE_ONE_SHOT; - break; - case 'O': - mode = PROGRAM_MODE_MANUAL; - temp_set = atoi(optarg); - break; - case 'p': - mode = PROGRAM_MODE_PRINT; - break; - case 'r': - transition = 0; - break; - case 't': - s = strchr(optarg, ':'); - if (s == NULL) { - fputs(_("Malformed temperature argument.\n"), - stderr); - fputs(_("Try `-h' for more information.\n"), - stderr); - exit(EXIT_FAILURE); - } - *(s++) = '\0'; - scheme.day.temperature = atoi(optarg); - scheme.night.temperature = atoi(s); - break; - case 'v': - verbose = 1; - break; - case 'V': - printf("%s\n", PACKAGE_STRING); - exit(EXIT_SUCCESS); - break; - case 'x': - mode = PROGRAM_MODE_RESET; - break; - case '?': - fputs(_("Try `-h' for more information.\n"), stderr); - exit(EXIT_FAILURE); - break; - } - } + options_t options; + options_init(&options); + options_parse_args( + &options, argc, argv, gamma_methods, location_providers); /* Load settings from config file. */ config_ini_state_t config_state; - r = config_ini_init(&config_state, config_filepath); + r = config_ini_init(&config_state, options.config_filepath); if (r < 0) { fputs("Unable to load config file.\n", stderr); exit(EXIT_FAILURE); } - if (config_filepath != NULL) free(config_filepath); + free(options.config_filepath); - /* Read global config settings. */ - config_ini_section_t *section = config_ini_get_section(&config_state, - "redshift"); - if (section != NULL) { - config_ini_setting_t *setting = section->settings; - while (setting != NULL) { - if (strcasecmp(setting->name, "temp-day") == 0) { - if (scheme.day.temperature < 0) { - scheme.day.temperature = - atoi(setting->value); - } - } else if (strcasecmp(setting->name, - "temp-night") == 0) { - if (scheme.night.temperature < 0) { - scheme.night.temperature = - atoi(setting->value); - } - } else if (strcasecmp(setting->name, - "transition") == 0) { - if (transition < 0) { - transition = !!atoi(setting->value); - } - } else if (strcasecmp(setting->name, - "brightness") == 0) { - if (isnan(scheme.day.brightness)) { - scheme.day.brightness = - atof(setting->value); - } - if (isnan(scheme.night.brightness)) { - scheme.night.brightness = - atof(setting->value); - } - } else if (strcasecmp(setting->name, - "brightness-day") == 0) { - if (isnan(scheme.day.brightness)) { - scheme.day.brightness = - atof(setting->value); - } - } else if (strcasecmp(setting->name, - "brightness-night") == 0) { - if (isnan(scheme.night.brightness)) { - scheme.night.brightness = - atof(setting->value); - } - } else if (strcasecmp(setting->name, - "elevation-high") == 0) { - scheme.high = atof(setting->value); - } else if (strcasecmp(setting->name, - "elevation-low") == 0) { - scheme.low = atof(setting->value); - } else if (strcasecmp(setting->name, "gamma") == 0) { - if (isnan(scheme.day.gamma[0])) { - r = parse_gamma_string(setting->value, - scheme.day.gamma); - if (r < 0) { - fputs(_("Malformed gamma" - " setting.\n"), - stderr); - exit(EXIT_FAILURE); - } - memcpy(scheme.night.gamma, scheme.day.gamma, - sizeof(scheme.night.gamma)); - } - } else if (strcasecmp(setting->name, "gamma-day") == 0) { - if (isnan(scheme.day.gamma[0])) { - r = parse_gamma_string(setting->value, - scheme.day.gamma); - if (r < 0) { - fputs(_("Malformed gamma" - " setting.\n"), - stderr); - exit(EXIT_FAILURE); - } - } - } else if (strcasecmp(setting->name, "gamma-night") == 0) { - if (isnan(scheme.night.gamma[0])) { - r = parse_gamma_string(setting->value, - scheme.night.gamma); - if (r < 0) { - fputs(_("Malformed gamma" - " setting.\n"), - stderr); - exit(EXIT_FAILURE); - } - } - } else if (strcasecmp(setting->name, - "adjustment-method") == 0) { - if (method == NULL) { - method = find_gamma_method( - setting->value); - if (method == NULL) { - fprintf(stderr, _("Unknown" - " adjustment" - " method" - " `%s'.\n"), - setting->value); - exit(EXIT_FAILURE); - } - } - } else if (strcasecmp(setting->name, - "location-provider") == 0) { - if (provider == NULL) { - provider = find_location_provider( - setting->value); - if (provider == NULL) { - fprintf(stderr, _("Unknown" - " location" - " provider" - " `%s'.\n"), - setting->value); - exit(EXIT_FAILURE); - } - } - } else { - fprintf(stderr, _("Unknown configuration" - " setting `%s'.\n"), - setting->name); - } - setting = setting->next; - } - } + options_parse_config_file( + &options, &config_state, gamma_methods, location_providers); - /* Use default values for settings that were neither defined in - the config file nor on the command line. */ - if (scheme.day.temperature < 0) { - scheme.day.temperature = DEFAULT_DAY_TEMP; - } - if (scheme.night.temperature < 0) { - scheme.night.temperature = DEFAULT_NIGHT_TEMP; - } + options_set_defaults(&options); - if (isnan(scheme.day.brightness)) { - scheme.day.brightness = DEFAULT_BRIGHTNESS; - } - if (isnan(scheme.night.brightness)) { - scheme.night.brightness = DEFAULT_BRIGHTNESS; - } - - if (isnan(scheme.day.gamma[0])) { - scheme.day.gamma[0] = DEFAULT_GAMMA; - scheme.day.gamma[1] = DEFAULT_GAMMA; - scheme.day.gamma[2] = DEFAULT_GAMMA; - } - if (isnan(scheme.night.gamma[0])) { - scheme.night.gamma[0] = DEFAULT_GAMMA; - scheme.night.gamma[1] = DEFAULT_GAMMA; - scheme.night.gamma[2] = DEFAULT_GAMMA; - } + if (options.scheme.dawn.start >= 0 || options.scheme.dawn.end >= 0 || + options.scheme.dusk.start >= 0 || options.scheme.dusk.end >= 0) { + if (options.scheme.dawn.start < 0 || + options.scheme.dawn.end < 0 || + options.scheme.dusk.start < 0 || + options.scheme.dusk.end < 0) { + fputs(_("Partial time-configuration not" + " supported!\n"), stderr); + exit(EXIT_FAILURE); + } - if (transition < 0) transition = 1; + if (options.scheme.dawn.start > options.scheme.dawn.end || + options.scheme.dawn.end > options.scheme.dusk.start || + options.scheme.dusk.start > options.scheme.dusk.end) { + fputs(_("Invalid dawn/dusk time configuration!\n"), + stderr); + exit(EXIT_FAILURE); + } - location_t loc = { NAN, NAN }; + options.scheme.use_time = 1; + } - /* Initialize location provider. If provider is NULL + /* Initialize location provider if needed. If provider is NULL try all providers until one that works is found. */ - location_state_t location_state; + location_state_t *location_state; /* Location is not needed for reset mode and manual mode. */ - if (mode != PROGRAM_MODE_RESET && - mode != PROGRAM_MODE_MANUAL) { - if (provider != NULL) { + int need_location = + options.mode != PROGRAM_MODE_RESET && + options.mode != PROGRAM_MODE_MANUAL && + !options.scheme.use_time; + if (need_location) { + if (options.provider != NULL) { /* Use provider specified on command line. */ - r = provider_try_start(provider, &location_state, - &config_state, provider_args); + r = provider_try_start( + options.provider, &location_state, + &config_state, options.provider_args); if (r < 0) exit(EXIT_FAILURE); } else { /* Try all providers, use the first that works. */ @@ -1431,82 +1027,58 @@ main(int argc, char *argv[]) /* Found provider that works. */ printf(_("Using provider `%s'.\n"), p->name); - provider = p; + options.provider = p; break; } /* Failure if no providers were successful at this point. */ - if (provider == NULL) { + if (options.provider == NULL) { fputs(_("No more location providers" " to try.\n"), stderr); exit(EXIT_FAILURE); } } - /* Get current location. */ - r = provider->get_location(&location_state, &loc); - if (r < 0) { - fputs(_("Unable to get location from provider.\n"), - stderr); - exit(EXIT_FAILURE); + /* Solar elevations */ + if (options.scheme.high < options.scheme.low) { + fprintf(stderr, + _("High transition elevation cannot be lower than" + " the low transition elevation.\n")); + exit(EXIT_FAILURE); } - - provider->free(&location_state); - - if (verbose) { - print_location(&loc); - printf(_("Temperatures: %dK at day, %dK at night\n"), - scheme.day.temperature, - scheme.night.temperature); - - /* TRANSLATORS: Append degree symbols if possible. */ + if (options.verbose) { + /* TRANSLATORS: Append degree symbols if possible. */ printf(_("Solar elevations: day above %.1f, night below %.1f\n"), - scheme.high, scheme.low); + options.scheme.high, options.scheme.low); } + } - /* Latitude */ - if (loc.lat < MIN_LAT || loc.lat > MAX_LAT) { - /* TRANSLATORS: Append degree symbols if possible. */ - fprintf(stderr, - _("Latitude must be between %.1f and %.1f.\n"), - MIN_LAT, MAX_LAT); - exit(EXIT_FAILURE); - } - - /* Longitude */ - if (loc.lon < MIN_LON || loc.lon > MAX_LON) { - /* TRANSLATORS: Append degree symbols if possible. */ - fprintf(stderr, - _("Longitude must be between" - " %.1f and %.1f.\n"), MIN_LON, MAX_LON); - exit(EXIT_FAILURE); + if (options.mode != PROGRAM_MODE_RESET && + options.mode != PROGRAM_MODE_MANUAL) { + if (options.verbose) { + printf(_("Temperatures: %dK at day, %dK at night\n"), + options.scheme.day.temperature, + options.scheme.night.temperature); } /* Color temperature */ - if (scheme.day.temperature < MIN_TEMP || - scheme.day.temperature > MAX_TEMP || - scheme.night.temperature < MIN_TEMP || - scheme.night.temperature > MAX_TEMP) { + if (options.scheme.day.temperature < MIN_TEMP || + options.scheme.day.temperature > MAX_TEMP || + options.scheme.night.temperature < MIN_TEMP || + options.scheme.night.temperature > MAX_TEMP) { fprintf(stderr, _("Temperature must be between %uK and %uK.\n"), MIN_TEMP, MAX_TEMP); exit(EXIT_FAILURE); } - - /* Solar elevations */ - if (scheme.high < scheme.low) { - fprintf(stderr, - _("High transition elevation cannot be lower than" - " the low transition elevation.\n")); - exit(EXIT_FAILURE); - } } - if (mode == PROGRAM_MODE_MANUAL) { + if (options.mode == PROGRAM_MODE_MANUAL) { /* Check color temperature to be set */ - if (temp_set < MIN_TEMP || temp_set > MAX_TEMP) { + if (options.temp_set < MIN_TEMP || + options.temp_set > MAX_TEMP) { fprintf(stderr, _("Temperature must be between %uK and %uK.\n"), MIN_TEMP, MAX_TEMP); @@ -1515,51 +1087,57 @@ main(int argc, char *argv[]) } /* Brightness */ - if (scheme.day.brightness < MIN_BRIGHTNESS || - scheme.day.brightness > MAX_BRIGHTNESS || - scheme.night.brightness < MIN_BRIGHTNESS || - scheme.night.brightness > MAX_BRIGHTNESS) { + if (options.scheme.day.brightness < MIN_BRIGHTNESS || + options.scheme.day.brightness > MAX_BRIGHTNESS || + options.scheme.night.brightness < MIN_BRIGHTNESS || + options.scheme.night.brightness > MAX_BRIGHTNESS) { fprintf(stderr, _("Brightness values must be between %.1f and %.1f.\n"), MIN_BRIGHTNESS, MAX_BRIGHTNESS); exit(EXIT_FAILURE); } - if (verbose) { + if (options.verbose) { printf(_("Brightness: %.2f:%.2f\n"), - scheme.day.brightness, scheme.night.brightness); + options.scheme.day.brightness, + options.scheme.night.brightness); } /* Gamma */ - if (!gamma_is_valid(scheme.day.gamma) || - !gamma_is_valid(scheme.night.gamma)) { + if (!gamma_is_valid(options.scheme.day.gamma) || + !gamma_is_valid(options.scheme.night.gamma)) { fprintf(stderr, _("Gamma value must be between %.1f and %.1f.\n"), MIN_GAMMA, MAX_GAMMA); exit(EXIT_FAILURE); } - if (verbose) { + if (options.verbose) { /* TRANSLATORS: The string in parenthesis is either Daytime or Night (translated). */ printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), - _("Daytime"), scheme.day.gamma[0], - scheme.day.gamma[1], scheme.day.gamma[2]); + _("Daytime"), options.scheme.day.gamma[0], + options.scheme.day.gamma[1], + options.scheme.day.gamma[2]); printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), - _("Night"), scheme.night.gamma[0], - scheme.night.gamma[1], scheme.night.gamma[2]); + _("Night"), options.scheme.night.gamma[0], + options.scheme.night.gamma[1], + options.scheme.night.gamma[2]); } + transition_scheme_t *scheme = &options.scheme; + /* Initialize gamma adjustment method. If method is NULL try all methods until one that works is found. */ - gamma_state_t state; + gamma_state_t *method_state; /* Gamma adjustment not needed for print mode */ - if (mode != PROGRAM_MODE_PRINT) { - if (method != NULL) { + if (options.mode != PROGRAM_MODE_PRINT) { + if (options.method != NULL) { /* Use method specified on command line. */ - r = method_try_start(method, &state, &config_state, - method_args); + r = method_try_start( + options.method, &method_state, options.mode, &config_state, + options.method_args); if (r < 0) exit(EXIT_FAILURE); } else { /* Try all methods, use the first that works. */ @@ -1567,7 +1145,8 @@ main(int argc, char *argv[]) const gamma_method_t *m = &gamma_methods[i]; if (!m->autostart) continue; - r = method_try_start(m, &state, &config_state, NULL); + r = method_try_start( + m, &method_state, options.mode, &config_state, NULL); if (r < 0) { fputs(_("Trying next method...\n"), stderr); continue; @@ -1575,12 +1154,12 @@ main(int argc, char *argv[]) /* Found method that works. */ printf(_("Using method `%s'.\n"), m->name); - method = m; + options.method = m; break; } /* Failure if no methods were successful at this point. */ - if (method == NULL) { + if (options.method == NULL) { fputs(_("No more methods to try.\n"), stderr); exit(EXIT_FAILURE); } @@ -1589,83 +1168,119 @@ main(int argc, char *argv[]) config_ini_free(&config_state); - switch (mode) { + switch (options.mode) { case PROGRAM_MODE_ONE_SHOT: case PROGRAM_MODE_PRINT: { - /* Current angular elevation of the sun */ + location_t loc = { NAN, NAN }; + if (need_location) { + fputs(_("Waiting for current location" + " to become available...\n"), stderr); + + /* Wait for location provider. */ + int r = provider_get_location( + options.provider, location_state, -1, &loc); + if (r < 0) { + fputs(_("Unable to get location" + " from provider.\n"), stderr); + exit(EXIT_FAILURE); + } + + if (!location_is_valid(&loc)) { + exit(EXIT_FAILURE); + } + + print_location(&loc); + } + double now; r = systemtime_get_time(&now); if (r < 0) { fputs(_("Unable to read system time.\n"), stderr); - method->free(&state); + options.method->free(method_state); exit(EXIT_FAILURE); } - double elevation = solar_elevation(now, loc.lat, loc.lon); + period_t period; + double transition_prog; + if (options.scheme.use_time) { + int time_offset = get_seconds_since_midnight(now); + period = get_period_from_time(scheme, time_offset); + transition_prog = get_transition_progress_from_time( + scheme, time_offset); + } else { + /* Current angular elevation of the sun */ + double elevation = solar_elevation( + now, loc.lat, loc.lon); + if (options.verbose) { + /* TRANSLATORS: Append degree symbol if + possible. */ + printf(_("Solar elevation: %f\n"), elevation); + } - if (verbose) { - /* TRANSLATORS: Append degree symbol if possible. */ - printf(_("Solar elevation: %f\n"), elevation); + period = get_period_from_elevation(scheme, elevation); + transition_prog = + get_transition_progress_from_elevation( + scheme, elevation); } - /* Use elevation of sun to set color temperature */ + /* Use transition progress to set color temperature */ color_setting_t interp; - interpolate_color_settings(&scheme, elevation, &interp); - - if (verbose || mode == PROGRAM_MODE_PRINT) { - period_t period = get_period(&scheme, - elevation); - double transition = - get_transition_progress(&scheme, - elevation); - print_period(period, transition); + interpolate_transition_scheme( + scheme, transition_prog, &interp); + + if (options.verbose || options.mode == PROGRAM_MODE_PRINT) { + print_period(period, transition_prog); printf(_("Color temperature: %uK\n"), interp.temperature); printf(_("Brightness: %.2f\n"), interp.brightness); } - if (mode == PROGRAM_MODE_PRINT) { - exit(EXIT_SUCCESS); - } - - /* Adjust temperature */ - r = method->set_temperature(&state, &interp); - if (r < 0) { - fputs(_("Temperature adjustment failed.\n"), stderr); - method->free(&state); - exit(EXIT_FAILURE); - } + if (options.mode != PROGRAM_MODE_PRINT) { + /* Adjust temperature */ + r = options.method->set_temperature( + method_state, &interp, options.preserve_gamma); + if (r < 0) { + fputs(_("Temperature adjustment failed.\n"), + stderr); + options.method->free(method_state); + exit(EXIT_FAILURE); + } - /* In Quartz (OSX) the gamma adjustments will automatically - revert when the process exits. Therefore, we have to loop - until CTRL-C is received. */ - if (strcmp(method->name, "quartz") == 0) { - fputs(_("Press ctrl-c to stop...\n"), stderr); - pause(); + /* In Quartz (macOS) the gamma adjustments will + automatically revert when the process exits. + Therefore, we have to loop until CTRL-C is received. + */ + if (strcmp(options.method->name, "quartz") == 0) { + fputs(_("Press ctrl-c to stop...\n"), stderr); + pause(); + } } } break; case PROGRAM_MODE_MANUAL: { - if (verbose) printf(_("Color temperature: %uK\n"), temp_set); + if (options.verbose) { + printf(_("Color temperature: %uK\n"), + options.temp_set); + } /* Adjust temperature */ - color_setting_t manual; - memcpy(&manual, &scheme.day, sizeof(color_setting_t)); - manual.temperature = temp_set; - r = method->set_temperature(&state, &manual); + color_setting_t manual = scheme->day; + manual.temperature = options.temp_set; + r = options.method->set_temperature( + method_state, &manual, options.preserve_gamma); if (r < 0) { fputs(_("Temperature adjustment failed.\n"), stderr); - method->free(&state); + options.method->free(method_state); exit(EXIT_FAILURE); } /* In Quartz (OSX) the gamma adjustments will automatically revert when the process exits. Therefore, we have to loop until CTRL-C is received. */ - if (strcmp(method->name, "quartz") == 0) { + if (strcmp(options.method->name, "quartz") == 0) { fputs(_("Press ctrl-c to stop...\n"), stderr); pause(); } @@ -1674,18 +1289,20 @@ main(int argc, char *argv[]) case PROGRAM_MODE_RESET: { /* Reset screen */ - color_setting_t reset = { NEUTRAL_TEMP, { 1.0, 1.0, 1.0 }, 1.0 }; - r = method->set_temperature(&state, &reset); + color_setting_t reset; + color_setting_reset(&reset); + + r = options.method->set_temperature(method_state, &reset, 0); if (r < 0) { fputs(_("Temperature adjustment failed.\n"), stderr); - method->free(&state); + options.method->free(method_state); exit(EXIT_FAILURE); } /* In Quartz (OSX) the gamma adjustments will automatically revert when the process exits. Therefore, we have to loop until CTRL-C is received. */ - if (strcmp(method->name, "quartz") == 0) { + if (strcmp(options.method->name, "quartz") == 0) { fputs(_("Press ctrl-c to stop...\n"), stderr); pause(); } @@ -1693,16 +1310,25 @@ main(int argc, char *argv[]) break; case PROGRAM_MODE_CONTINUAL: { - r = run_continual_mode(&loc, &scheme, - method, &state, - transition, verbose); + r = run_continual_mode( + options.provider, location_state, scheme, + options.method, method_state, + options.use_fade, options.preserve_gamma, + options.verbose); if (r < 0) exit(EXIT_FAILURE); } break; } /* Clean up gamma adjustment state */ - method->free(&state); + if (options.mode != PROGRAM_MODE_PRINT) { + options.method->free(method_state); + } + + /* Clean up location provider state */ + if (need_location) { + options.provider->free(location_state); + } return EXIT_SUCCESS; } diff --git a/src/redshift.h b/src/redshift.h index bac8e34..896ee28 100644 --- a/src/redshift.h +++ b/src/redshift.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see <http://www.gnu.org/licenses/>. - Copyright (c) 2013-2014 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com> */ #ifndef REDSHIFT_REDSHIFT_H @@ -23,6 +23,9 @@ #include <stdio.h> #include <stdlib.h> +/* The color temperature when no adjustment is applied. */ +#define NEUTRAL_TEMP 6500 + /* Location */ typedef struct { @@ -45,17 +48,48 @@ typedef struct { float brightness; } color_setting_t; +/* Program modes. */ +typedef enum { + PROGRAM_MODE_CONTINUAL, + PROGRAM_MODE_ONE_SHOT, + PROGRAM_MODE_PRINT, + PROGRAM_MODE_RESET, + PROGRAM_MODE_MANUAL +} program_mode_t; + +/* Time range. + Fields are offsets from midnight in seconds. */ +typedef struct { + int start; + int end; +} time_range_t; + +/* Transition scheme. + The solar elevations at which the transition begins/ends, + and the association color settings. */ +typedef struct { + double high; + double low; + int use_time; /* When enabled, ignore elevation and use time ranges. */ + time_range_t dawn; + time_range_t dusk; + color_setting_t day; + color_setting_t night; +} transition_scheme_t; + /* Gamma adjustment method */ -typedef int gamma_method_init_func(void *state); -typedef int gamma_method_start_func(void *state); -typedef void gamma_method_free_func(void *state); +typedef struct gamma_state gamma_state_t; + +typedef int gamma_method_init_func(gamma_state_t **state); +typedef int gamma_method_start_func(gamma_state_t *state, program_mode_t mode); +typedef void gamma_method_free_func(gamma_state_t *state); typedef void gamma_method_print_help_func(FILE *f); -typedef int gamma_method_set_option_func(void *state, const char *key, +typedef int gamma_method_set_option_func(gamma_state_t *state, const char *key, const char *value); -typedef void gamma_method_restore_func(void *state); -typedef int gamma_method_set_temperature_func(void *state, - const color_setting_t *setting); +typedef void gamma_method_restore_func(gamma_state_t *state); +typedef int gamma_method_set_temperature_func( + gamma_state_t *state, const color_setting_t *setting, int preserve); typedef struct { char *name; @@ -83,13 +117,17 @@ typedef struct { /* Location provider */ -typedef int location_provider_init_func(void *state); -typedef int location_provider_start_func(void *state); -typedef void location_provider_free_func(void *state); +typedef struct location_state location_state_t; + +typedef int location_provider_init_func(location_state_t **state); +typedef int location_provider_start_func(location_state_t *state); +typedef void location_provider_free_func(location_state_t *state); typedef void location_provider_print_help_func(FILE *f); -typedef int location_provider_set_option_func(void *state, const char *key, - const char *value); -typedef int location_provider_get_location_func(void *state, location_t *loc); +typedef int location_provider_set_option_func( + location_state_t *state, const char *key, const char *value); +typedef int location_provider_get_fd_func(location_state_t *state); +typedef int location_provider_handle_func( + location_state_t *state, location_t *location, int *available); typedef struct { char *name; @@ -106,8 +144,9 @@ typedef struct { /* Set an option key, value-pair. */ location_provider_set_option_func *set_option; - /* Get current location. */ - location_provider_get_location_func *get_location; + /* Listen and handle location updates. */ + location_provider_get_fd_func *get_fd; + location_provider_handle_func *handle; } location_provider_t; diff --git a/src/signals.c b/src/signals.c new file mode 100644 index 0000000..e2aa219 --- /dev/null +++ b/src/signals.c @@ -0,0 +1,118 @@ +/* signals.c -- Signal processing source + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2009-2015 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2015 Mattias Andrée <m@maandree.se> +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdio.h> +#if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) +# include <signal.h> +#endif + +#include "signals.h" + + +#if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) + +volatile sig_atomic_t exiting = 0; +volatile sig_atomic_t disable = 0; + + +/* Signal handler for exit signals */ +static void +sigexit(int signo) +{ + exiting = 1; +} + +/* Signal handler for disable signal */ +static void +sigdisable(int signo) +{ + disable = 1; +} + +#else /* ! HAVE_SIGNAL_H || __WIN32__ */ + +int disable = 0; +int exiting = 0; + +#endif /* ! HAVE_SIGNAL_H || __WIN32__ */ + + +int +signals_install_handlers(void) +{ +#if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) + struct sigaction sigact; + sigset_t sigset; + int r; + sigemptyset(&sigset); + + /* Install signal handler for INT and TERM signals */ + sigact.sa_handler = sigexit; + sigact.sa_mask = sigset; + sigact.sa_flags = 0; + + r = sigaction(SIGINT, &sigact, NULL); + if (r < 0) { + perror("sigaction"); + return -1; + } + + r = sigaction(SIGTERM, &sigact, NULL); + if (r < 0) { + perror("sigaction"); + return -1; + } + + r = sigaction(SIGQUIT, &sigact, NULL); + if (r < 0) { + perror("sigaction"); + return -1; + } + + /* Install signal handler for USR1 signal */ + sigact.sa_handler = sigdisable; + sigact.sa_mask = sigset; + sigact.sa_flags = 0; + + r = sigaction(SIGUSR1, &sigact, NULL); + if (r < 0) { + perror("sigaction"); + return -1; + } + + /* Ignore CHLD signal. This causes child processes + (hooks) to be reaped automatically. */ + sigact.sa_handler = SIG_IGN; + sigact.sa_mask = sigset; + sigact.sa_flags = 0; + + r = sigaction(SIGCHLD, &sigact, NULL); + if (r < 0) { + perror("sigaction"); + return -1; + } +#endif /* HAVE_SIGNAL_H && ! __WIN32__ */ + + return 0; +} diff --git a/src/signals.h b/src/signals.h new file mode 100644 index 0000000..0b0af53 --- /dev/null +++ b/src/signals.h @@ -0,0 +1,38 @@ +/* signals.h -- Signal processing header + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see <http://www.gnu.org/licenses/>. + + Copyright (c) 2009-2015 Jon Lund Steffensen <jonlst@gmail.com> + Copyright (c) 2015 Mattias Andrée <m@maandree.se> +*/ +#ifndef REDSHIFT_SIGNALS_H +#define REDSHIFT_SIGNALS_H + + +#if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) + +extern volatile sig_atomic_t exiting; +extern volatile sig_atomic_t disable; + +#else /* ! HAVE_SIGNAL_H || __WIN32__ */ +extern int exiting; +extern int disable; +#endif /* ! HAVE_SIGNAL_H || __WIN32__ */ + + +int signals_install_handlers(void); + + +#endif /* REDSHIFT_SIGNALS_H */ diff --git a/src/windows/appicon.rc b/src/windows/appicon.rc new file mode 100644 index 0000000..9980b7e --- /dev/null +++ b/src/windows/appicon.rc @@ -0,0 +1 @@ +AppIcon ICON redshift.ico diff --git a/src/windows/redshift.ico b/src/windows/redshift.ico Binary files differnew file mode 100644 index 0000000..751e6fa --- /dev/null +++ b/src/windows/redshift.ico diff --git a/src/windows/versioninfo.rc b/src/windows/versioninfo.rc new file mode 100644 index 0000000..9ede49d --- /dev/null +++ b/src/windows/versioninfo.rc @@ -0,0 +1,20 @@ +#include "config.h" + +1 VERSIONINFO +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "Redshift Open Source Project" + VALUE "FileDescription", "Redshift" + VALUE "OriginalFilename", "redshift.exe" + VALUE "ProductName", "Redshift" + VALUE "ProductVersion", PACKAGE_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END |