aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-03-16 14:45:03 +0100
committerMattias Andrée <m@maandree.se>2025-03-16 15:58:16 +0100
commitb55234a74d17503ca2fecb273cfcc44549f9e43e (patch)
tree321ef06dec317ff0e92011e1d680965e058b10fc /src
parentUnlist redshift/issues/846: rejected on technical grounds (diff)
downloadredshift-ng-b55234a74d17503ca2fecb273cfcc44549f9e43e.tar.gz
redshift-ng-b55234a74d17503ca2fecb273cfcc44549f9e43e.tar.bz2
redshift-ng-b55234a74d17503ca2fecb273cfcc44549f9e43e.tar.xz
Major refactoring and some fixes
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'src')
-rw-r--r--src/Makefile19
-rw-r--r--src/colour.c (renamed from src/colourramp.c)16
-rw-r--r--src/common.h413
-rw-r--r--src/config-ini.c209
-rw-r--r--src/config.c935
-rw-r--r--src/config.mk3
-rw-r--r--src/gamma-coopgamma.c22
-rw-r--r--src/gamma-drm.c11
-rw-r--r--src/gamma-dummy.c8
-rw-r--r--src/gamma-quartz.c11
-rw-r--r--src/gamma-randr.c11
-rw-r--r--src/gamma-vidmode.c11
-rw-r--r--src/gamma-w32gdi.c11
-rw-r--r--src/gamma.c134
-rw-r--r--src/hooks.c214
-rw-r--r--src/location-corelocation.m21
-rw-r--r--src/location-geoclue2.c21
-rw-r--r--src/location-manual.c9
-rw-r--r--src/location.c187
-rw-r--r--src/options.c558
-rw-r--r--src/pipeutils.c83
-rw-r--r--src/redshift.c657
-rw-r--r--src/signals.c11
-rw-r--r--src/systemtime.c61
-rw-r--r--src/util.c232
25 files changed, 2234 insertions, 1634 deletions
diff --git a/src/Makefile b/src/Makefile
index 164ed4f..36bf18b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -4,32 +4,39 @@ CONFIGFILE = config.mk
include $(CONFIGFILE)
-PACKAGE_STRING = redshift-ng 1.13
+PACKAGE_STRING = $(PACKAGE) 1.13
OBJ =\
- colourramp.o\
+ colour.o\
+ config.o\
config-ini.o\
+ gamma.o\
gamma-coopgamma.o\
gamma-drm.o\
gamma-dummy.o\
gamma-randr.o\
gamma-vidmode.o\
hooks.o\
+ location.o\
location-geoclue2.o\
location-manual.o\
- options.o\
- pipeutils.o\
redshift.o\
signals.o\
- systemtime.o
+ util.o
+
+
+CPPFLAGS_STRINGS =\
+ -D'PACKAGE="$(PACKAGE)"'\
+ -D'PACKAGE_STRING="$(PACKAGE_STRING)"'\
+ -D'LOCALEDIR="$(LOCALEDIR)"'
all: redshift
$(OBJ): common.h
.c.o:
- $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -D'PACKAGE_STRING="$(PACKAGE_STRING)"'
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) $(CPPFLAGS_STRINGS)
redshift: $(OBJ)
$(CC) -o $@ $(OBJ) $(LDFLAGS)
diff --git a/src/colourramp.c b/src/colour.c
index 1aa7f27..d77c6ab 100644
--- a/src/colourramp.c
+++ b/src/colour.c
@@ -1,5 +1,7 @@
-/* colourramp.c -- colour temperature calculation source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,10 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2013, 2014 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2013 Ingo Thies <ithies@astro.uni-bonn.de> [historical, no longer applies]
- * Copyright (c) 2016, 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -56,9 +54,9 @@
}\
\
void\
- colourramp_fill_##SUFFIX(TYPE *gamma_r, TYPE *gamma_g, TYPE *gamma_b,\
- size_t size_r, size_t size_g, size_t size_b,\
- const struct colour_setting *setting)\
+ fill_ramps_##SUFFIX(TYPE *gamma_r, TYPE *gamma_g, TYPE *gamma_b,\
+ size_t size_r, size_t size_g, size_t size_b,\
+ const struct colour_setting *setting)\
{\
double r = 1, g = 1, b = 1;\
libred_get_colour(setting->temperature, &r, &g, &b);\
diff --git a/src/common.h b/src/common.h
index ebf63e0..704e64a 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,5 +1,7 @@
-/* common.h -- Common header file for Redshift source files
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2009-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2014, 2015, 2016, 2025 Mattias Andrée <m@maandree.se>
*/
#ifndef REDSHIFT_COMMON_H
#define REDSHIFT_COMMON_H
@@ -31,6 +30,7 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@@ -116,56 +116,82 @@
*/
#define WITHIN(LO, X, UP) ((LO) <= (X) && (X) <= (UP))
+/**
+ * Check whether a value is within a bounded, open range
+ *
+ * @param LO The lower bound
+ * @param X The value to check
+ * @param UP The upper bound
+ * @return :int 1 if `X` is within (`LO`, `UP`), 0 otherwise
+ */
+#define BETWEEN(LO, X, UP) ((LO) < (X) && (X) < (UP))
+
+
+/**
+ * Symbol used to delimit paths in environment
+ * variables listing multiple paths
+ */
+#ifdef WINDOWS
+# define PATH_DELIMITER ';'
+#else
+# define PATH_DELIMITER ':'
+#endif
+
+/**
+ * The number of seconds in a day
+ */
+#define ONE_DAY ((time_t)(24L * 60L * 60L))
+
/**
* Minimum valid latitude
*/
-#define MIN_LATITUDE -90.0
+#define MIN_LATITUDE -90.0
/**
* Maximum valid latitude
*/
-#define MAX_LATITUDE 90.0
+#define MAX_LATITUDE 90.0
/**
* Minimum valid longitude
*/
-#define MIN_LONGITUDE -180.0
+#define MIN_LONGITUDE -180.0
/**
* Maximum valid longitude
*/
-#define MAX_LONGITUDE 180.0
+#define MAX_LONGITUDE 180.0
/**
* Minimum allowed colour temperature
*/
-#define MIN_TEMPERATURE ((unsigned long int)LIBRED_LOWEST_TEMPERATURE)
+#define MIN_TEMPERATURE ((unsigned long int)LIBRED_LOWEST_TEMPERATURE)
/**
* Maximum allowed colour temperature
*/
-#define MAX_TEMPERATURE ULONG_MAX
+#define MAX_TEMPERATURE ULONG_MAX
/**
* Minimum allowed whitepoint brightness
*/
-#define MIN_BRIGHTNESS 0.1
+#define MIN_BRIGHTNESS 0.1
/**
* Maximum allowed whitepoint brightness
*/
-#define MAX_BRIGHTNESS 1.0
+#define MAX_BRIGHTNESS 1.0
/**
* Minimum allowed gamma
*/
-#define MIN_GAMMA 0.1
+#define MIN_GAMMA 0.1
/**
* Maximum allowed gamma
*/
-#define MAX_GAMMA 10.0
+#define MAX_GAMMA 10.0
/**
@@ -186,6 +212,49 @@
/**
+ * Default daytime colour temperature
+ */
+#define DEFAULT_DAY_TEMPERATURE 6500UL
+
+/**
+ * Default night colour temperature
+ */
+#define DEFAULT_NIGHT_TEMPERATURE 4500UL
+
+/**
+ * Default daytime whitepoint brightness level
+ */
+#define DEFAULT_DAY_BRIGHTNESS NEUTRAL_BRIGHTNESS
+
+/**
+ * Default night whitepoint brightness level
+ */
+#define DEFAULT_NIGHT_BRIGHTNESS NEUTRAL_BRIGHTNESS
+
+/**
+ * Default daytime gamma value
+ */
+#define DEFAULT_DAY_GAMMA NEUTRAL_GAMMA
+
+/**
+ * Default night gamma value
+ */
+#define DEFAULT_NIGHT_GAMMA NEUTRAL_GAMMA
+
+/**
+ * The solar elevation, in degrees, that marks the
+ * threshold to daytime
+ */
+#define DEFAULT_HIGH_ELEVATION 3.0
+
+/**
+ * The solar elevation, in degrees, that marks the
+ * threshold to night
+ */
+#define DEFAULT_LOW_ELEVATION LIBRED_SOLAR_ELEVATION_CIVIL_DUSK_DAWN
+
+
+/**
* Initialiser for `struct colour_setting`
*
* Sets all values to their neutral values (no effects applied)
@@ -230,6 +299,83 @@ enum program_mode {
/**
+ * By what the effects of the application change
+ */
+enum scheme_type {
+ /**
+ * Effects are dependent on the Sun's elevation
+ */
+ SOLAR_SCHEME,
+
+ /**
+ * Effects are dependent on the wall clock time
+ */
+ CLOCK_SCHEME,
+
+ /**
+ * Effects do not change
+ */
+ STATIC_SCHEME
+};
+
+
+/**
+ * The sources where an setting was been loaded from
+ *
+ * Higher valued sources have higher priority
+ *
+ * This is a bitmask `enum`
+ */
+enum setting_source {
+ /**
+ * No setting loaded, default value set
+ */
+ SETTING_DEFAULT = 0x00,
+
+ /**
+ * Setting loaded from configuration file
+ */
+ SETTING_CONFIGFILE = 0x01,
+
+ /**
+ * Setting loaded from command line arguments
+ */
+ SETTING_CMDLINE = 0x02
+};
+
+
+/**
+ * Specification for a path that consists of a two parts:
+ * the first being defined by the environment, and the
+ * seocnd being a static string
+ */
+struct env_path {
+ /**
+ * Whether the environment variable referenced by `.prefix`
+ * should be split at each colon (:) into multiple paths to
+ * test
+ *
+ * On Windows semicolon (;) is used instead of colon
+ */
+ int multidir_env;
+
+ /**
+ * Environment variable to use as the first part of the path
+ *
+ * `NULL` if the user's home directory should be used
+ *
+ * The empty string if `.suffix` should be used as is
+ */
+ const char *prefix_env;
+
+ /**
+ * The second part of the path
+ */
+ const char *suffix;
+};
+
+
+/**
* Geographical location, using GPS coordinates
*/
struct location {
@@ -270,22 +416,34 @@ struct colour_setting {
/* Time range.
Fields are offsets from midnight in seconds. */
struct time_range {
- int start;
- int end;
+ time_t start;
+ time_t end;
+};
+
+
+struct time_period {
+ time_t start;
+ double day_level;
+ double diff_over_duration;
+ const struct time_period *next;
};
/* Transition scheme.
- The solar elevations at which the transition begins/ends,
- and the association colour settings. */
-struct transition_scheme {
- double high;
- double low;
- int use_time; /* When enabled, ignore elevation and use time ranges. */
- struct time_range dawn;
- struct time_range dusk;
- struct colour_setting day;
- struct colour_setting night;
+ The solar elevations at which the transition begins/ends */
+union scheme {
+ enum scheme_type type;
+ struct {
+ enum scheme_type type;
+ double high;
+ double low;
+ double range;
+ } elevation;
+ struct {
+ enum scheme_type type;
+ const struct time_period *periods;
+ struct time_period periods_array[4];
+ } time;
};
@@ -308,20 +466,51 @@ struct config_ini_state {
};
-struct options {
+struct setting_i {
+ enum setting_source source;
+ int value;
+};
+
+struct setting_lu {
+ enum setting_source source;
+ unsigned long int value;
+};
+
+struct setting_f {
+ enum setting_source source;
+ double value;
+};
+
+struct setting_f3 {
+ enum setting_source source;
+ double value[3];
+};
+
+struct setting_time {
+ enum setting_source source;
+ time_t value;
+};
+
+struct settings {
/* Path to config file */
- char *config_filepath;
+ const char *config_file;
- struct transition_scheme scheme;
- enum program_mode mode;
- int verbose;
+ struct config_ini_state config;
+
+ struct {
+ struct setting_lu temperature;
+ struct setting_f brightness;
+ struct setting_f3 gamma;
+ } day, night;
+ struct setting_i preserve_gamma; /* Whether to preserve gamma ramps if supported by gamma method. */
+ struct setting_i use_fade; /* Whether to fade between large skips in colour temperature. */
- /* Temperature to set in manual mode. */
- unsigned long int temp_set;
- /* Whether to fade between large skips in colour temperature. */
- int use_fade;
- /* Whether to preserve gamma ramps if supported by gamma method. */
- int preserve_gamma;
+ struct setting_f elevation_high; /* TODO no cmdline option */
+ struct setting_f elevation_low; /* TODO no cmdline option */
+ struct {
+ struct setting_time start;
+ struct setting_time end;
+ } dawn, dusk; /* TODO no cmdline option */
/* Selected gamma method. */
const struct gamma_method *method;
@@ -332,6 +521,10 @@ struct options {
const struct location_provider *provider;
/* Arguments for location provider. */
char *provider_args;
+
+ enum program_mode mode;
+ int verbose;
+ enum scheme_type scheme_type;
};
@@ -397,6 +590,33 @@ extern const struct gamma_method *gamma_methods[];
*/
extern const struct location_provider *location_providers[];
+/**
+ * Set to 1 once the process has received a signal to terminate
+ */
+extern volatile sig_atomic_t exiting;
+
+/**
+ * Set to 1 once the process has received a signal to remove its effect
+ */
+extern volatile sig_atomic_t disable;
+
+/**
+ * The colour settings applied at daytime
+ */
+extern struct colour_setting day_settings;
+
+/**
+ * The colour settings applied at nighttime
+ */
+extern struct colour_setting night_settings;
+
+/**
+ * The colour settings applied at nighttime
+ */
+extern union scheme scheme;
+
+
+/* colour.c */
#define LIST_RAMPS_STOP_VALUE_TYPES(X, D)\
X(u8, uint8_t, UINT8_MAX, 8) D\
@@ -418,13 +638,15 @@ extern const struct location_provider *location_providers[];
* @param size_b The number of stops in `gamma_b`
* @param settings The colour settings to apply (temperature, brightness, gamma)
*/\
- void colourramp_fill_##SUFFIX(TYPE *gamma_r, TYPE *gamma_g, TYPE *gamma_b,\
- size_t size_r, size_t size_g, size_t size_b,\
- const struct colour_setting *setting)
+ void fill_ramps_##SUFFIX(TYPE *gamma_r, TYPE *gamma_g, TYPE *gamma_b,\
+ size_t size_r, size_t size_g, size_t size_b,\
+ const struct colour_setting *setting)
LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
#undef X
+/* config-ini.c */
+
/**
* Load the configuration file
*
@@ -453,61 +675,112 @@ GCC_ONLY(__attribute__((__pure__)))
struct config_ini_section *config_ini_get_section(struct config_ini_state *state, const char *name);
-void options_init(struct options *options);
-void options_parse_args(struct options *options, int argc, char *argv[]);
-void options_parse_config_file(struct options *options, struct config_ini_state *config_state);
-void options_set_defaults(struct options *options);
+/* config.c */
+
+/**
+ * Load settings
+ *
+ * @param settings Output parameter for the settings
+ * @param argc Number of command line arguments
+ * @param argv `NULL` terminated list of command line arguments,
+ * including argument zero
+ */
+void load_settings(struct settings *settings, int argc, char *argv[]);
+
+
+/* gamma.c */
+
+void acquire_adjustment_method(struct settings *settings, GAMMA_STATE **method_state_out);
-void hooks_signal_period_change(enum period prev_period, enum period period);
+/* location.c */
+int get_location(const struct location_provider *provider, LOCATION_STATE *state, int timeout, struct location *loc);
+
+void acquire_location_provider(struct settings *settings, LOCATION_STATE **location_state_out);
+
+
+/* hooks.c */
/**
- * Create a pipe(7) where both ends have O_NONBLOCK applied
+ * Run hooks with a signal that the period changed
*
- * @param pipefds Output parameter for the pipe's file descriptors:
- * 0) reading file descriptor, and
- * 1) writing file descriptor.
- * @return 0 on success, -1 on failure
+ * @param prev_period The previous period
+ * @param period The new current period
*/
-int pipeutils_create_nonblocking(int pipefds[2]);
+void run_period_change_hooks(enum period prev_period, enum period period);
-void pipeutils_signal(int write_fd);
-void pipeutils_handle_signal(int read_fd);
+/* signals.c */
/**
- * Set to 1 once the process has received a signal to terminate
+ * Install signal handlers for the process
*/
-extern volatile sig_atomic_t exiting;
+void install_signal_handlers(void);
+
+
+/* util.c */
/**
- * Set to 1 once the process has received a signal to remove its effect
+ * Remove trailing whitespace
+ *
+ * @param s The string to trim, will be truncated
+ * @param end The current end of `s`; will be looked up if `NULL`
+ * @return `s`
*/
-extern volatile sig_atomic_t disable;
+char *rtrim(char *s, char *end);
/**
- * Install signal handlers for the process
+ * Remove leading whitespace
+ *
+ * @param s The string to trim (will not be modified)
+ * @return `s` with an offset
*/
-void signals_install_handlers(void);
-
+GCC_ONLY(__attribute__((__warn_unused_result__, __pure__)))
+char *ltrim(char *s);
/**
- * Get the current time in seconds since the Unix epoch
+ * Get the user's home directory
*
- * @return The current time
+ * This function looks up the user's home directory
+ * once and caches the result, later calls to this
+ * function will use the cached result
+ *
+ * @return The user's home directory; the empty string if not found
*/
-double systemtime_get_time(void);
+const char *get_home(void);
/**
- * Suspend the process for a short time
- *
- * The process may be resumed earily, specifically
- * if it receives a signal
+ * Search for a file and open it for reading
+ *
+ * @param path_spec Specification for the path to try
+ * @param path_out Output parameter for the found file
+ * @param pathbuf_out Output parameter for the memory allocation for `*path_out`;
+ * shall be free(3)d by the caller
+ * @return `FILE` object for the reading the file; `NULL` if not found
+ */
+FILE *try_path_fopen(const struct env_path *path_spec, const char **path_out, char **pathbuf_out);
+
+/**
+ * Search for a directory and open it for reading
+ *
+ * @param path_spec Specification for the path to try
+ * @param path_out Output parameter for the found directory
+ * @param pathbuf_out Output parameter for the memory allocation for `*path_out`;
+ * shall be free(3)d by the caller
+ * @return `DIR` object for the reading the directory; `NULL` if not found
+ */
+DIR *try_path_opendir(const struct env_path *path_spec, const char **path_out, char **pathbuf_out);
+
+/**
+ * Create a pipe(7) where both ends have `O_NONBLOCK` applied
*
- * @param msecs The number of milliseconds to sleep
+ * @param pipefds Output parameter for the pipe's file descriptors:
+ * 0) reading file descriptor, and
+ * 1) writing file descriptor
+ * @return 0 on success, -1 on failure
*/
-void systemtime_msleep(unsigned int msecs);
+int pipe_nonblock(int pipefds[2]);
extern const struct gamma_method dummy_gamma_method;
diff --git a/src/config-ini.c b/src/config-ini.c
index c524605..1a78903 100644
--- a/src/config-ini.c
+++ b/src/config-ini.c
@@ -1,5 +1,7 @@
-/* config-ini.c -- INI config file parser
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,43 +15,11 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2010-2018 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
/**
- * Specification for a path that consists of a two parts:
- * the first being defined by the environment, and the
- * seocnd being a static string
- */
-struct env_path {
- /**
- * Whether the environment variable referenced by `.prefix`
- * should be split at each colon (:) into multiple paths to
- * test
- */
- int multidir_env;
-
- /**
- * Environment variable to use as the first part of the path
- *
- * `NULL` if the user's home directory should be used
- *
- * The empty string if `.suffix` should be used as is
- */
- const char *prefix_env;
-
- /**
- * The second part of the path
- */
- const char *suffix;
-};
-
-
-/**
* Paths, in order of priority, to test when looking for
* the configuration file for redshift
*/
@@ -80,152 +50,14 @@ static const struct env_path paths[] = {
/**
- * Remove trailing whitespace
- *
- * @param s The string to trim, will be truncated
- * @param end The current end of `s`; will be looked up if `NULL`
- * @return `s`
- */
-static char *
-rtrim(char *s, char *end)
-{
- end = end ? end : strchr(s, '\0');
- while (end != s && (end[-1] == ' ' || end[-1] == '\t'))
- end--;
- *end = '\0';
- return s;
-}
-
-
-/**
- * Remove leading whitespace
- *
- * @param s The string to trim (will not be modified)
- * @return An offset of `s`
- */
-GCC_ONLY(__attribute__((__warn_unused_result__)))
-static char *
-ltrim(char *s)
-{
- return &s[strspn(s, " \t")];
-}
-
-
-/**
- * Get the user's home directory
- *
- * This function looks up the user's home directory
- * once and caches the result, later calls to this
- * function will use the cached result
- *
- * @param The user's home directory; the empty string if not found
- */
-static const char *
-get_home(void)
-{
-#ifdef WINDOWS
- return NULL;
-#else
- static const char *home = NULL;
- struct passwd *pw;
- if (!home) {
- pw = getpwuid(getuid());
- if (pw) {
- home = pw->pw_dir;
- if (home && *home)
- return home;
- weprintf(_("Cannot determine your home directory, "
- "it is from the system's user table."));
- } else if (errno) {
- weprintf("getpwuid:");
- } else {
- weprintf(_("Cannot determine your home directory, your"
- " user ID is missing from the system's user table."));
- /* `errno` can either be set to any number of error codes,
- * or be zero if the user does not have a passwd entry */
- }
- home = "";
- }
- return home;
-#endif
-}
-
-
-/**
- * Search for a file and open it for reading
- *
- * @param path_spec Specification for the path to try
- * @param path_out Output parameter for the file path
- * @return `FILE` object for the reading the file; `NULL` if not found
- */
-static FILE *
-try_path(const struct env_path *path_spec, char **path_out)
-{
- const char *prefix, *p, *q;
- char *path;
- size_t len;
- FILE *f;
-
- if (!path_spec->prefix_env)
- prefix = get_home();
- else if (!*path_spec->prefix_env)
- return fopen(path_spec->suffix, "r");
- else
- prefix = getenv(path_spec->prefix_env);
- if (!prefix || !*prefix)
- return NULL;
-
- path = emalloc(strlen(prefix) + strlen(path_spec->suffix) + 1U);
-
- if (path_spec->multidir_env) {
- for (p = prefix; *p; p = &q[!!*q]) {
-#ifdef strchrnul
- q = strchrnul(p, ':');
-#else
- q = strchr(p, ':');
- q = q ? q : strchr(p, '\0');
-#endif
- len = (size_t)(q - p);
- if (!len)
- continue;
-
- memcpy(path, p, len);
- stpcpy(&path[len], path_spec->suffix);
- f = fopen(path, "r");
- if (f) {
- weprintf(_("Found configuration file `%s'."), path);
- break;
- } else if (errno != ENOENT) {
- eprintf("fopen %s \"r\":", path);
- }
- }
- } else {
- stpcpy(stpcpy(path, prefix), path_spec->suffix);
- f = fopen(path, "r");
- if (f)
- weprintf(_("Found configuration file `%s'."), path);
- else if (errno != ENOENT)
- eprintf("fopen %s \"r\":", path);
- }
-
- if (f) {
- *path_out = path;
- } else {
- free(path);
- *path_out = NULL;
- }
- return f;
-}
-
-
-/**
* Open the configuration file for reading
*
* @param path The path to the configuration file, or `NULL` if the
* application should look for it in the default paths
- * @param path_out Output parameter for the configuration file
+ * @param path_out Output parameter for the configuration file path
* @param pathbuf_out Output parameter for the memory allocation for `*path_out`;
- * will be set to `NULL` unless `path` is `NULL`
+ * will be set to `NULL` unless `path` is `NULL`; shall be
+ * free(3)d by the caller
* @return `FILE` object for the reading the file; `NULL` if not
* found and `path` is `NULL`
*/
@@ -235,18 +67,20 @@ open_config_file(const char *path, const char **path_out, char **pathbuf_out)
FILE *f = NULL;
size_t i;
+ *path_out = path;
+ *pathbuf_out = NULL;
+
if (!path) {
for (i = 0; !f && i < ELEMSOF(paths); i++)
- f = try_path(&paths[i], pathbuf_out);
- if (!f)
+ f = try_path_fopen(&paths[i], path_out, pathbuf_out);
+ if (f)
+ weprintf(_("Found configuration file `%s'."), *path_out);
+ else
weprintf(_("No configuration file found."));
- *path_out = *pathbuf_out;
} else {
f = fopen(path, "r");
if (!f)
eprintf("fopen %s \"r\":", path);
- *path_out = path;
- *pathbuf_out = NULL;
}
return f;
@@ -269,6 +103,10 @@ config_ini_init(struct config_ini_state *state, const char *path)
f = open_config_file(path, &path, &pathbuf);
if (!f)
return;
+
+#ifndef WINDOWS
+again:
+#endif
while ((s = next_line) || (len = getline(&line, &size, f)) >= 0) {
if (!s && (s = line, strlen(s) != (size_t)len))
eprintf(_("Config file contains NUL byte."));
@@ -319,8 +157,16 @@ config_ini_init(struct config_ini_state *state, const char *path)
break;
}
}
- if (ferror(f))
+ if (ferror(f)) {
+#ifndef WINDOWS
+ if (errno == EINTR) {
+ clearerr(f);
+ goto again;
+ }
+#endif
eprintf("getline %s:", path);
+ }
+
free(pathbuf);
free(line);
fclose(f);
@@ -349,6 +195,7 @@ config_ini_free(struct config_ini_state *state)
struct config_ini_section *
config_ini_get_section(struct config_ini_state *state, const char *name)
{
+ /* TODO deal with multiple section definitions */
struct config_ini_section *section;
for (section = state->sections; section; section = section->next)
if (!strcasecmp(section->name, name))
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..443991d
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,935 @@
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
+ *
+ * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "common.h"
+
+
+/* TODO missing translation */
+USAGE("[-b day:night] [-c file] [-g r:g:b] [-l latitude:longitude | -l provider[:options]]"
+ " [-m method[:options]] [-o | -O temperature | -t day:night | -x] [-pPrv] | -hV");
+
+
+struct colour_setting day_settings;
+struct colour_setting night_settings;
+union scheme scheme;
+
+
+/**
+ * Print general help text
+ */
+static void
+print_help(void) /* TODO clean up */
+{
+ /* 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"), argv0);
+ printf("\n");
+
+ /* TRANSLATORS: help output 2
+ no-wrap */
+ printf(_("Set color temperature of display according to time of day.\n"));
+ printf("\n");
+
+ /* TRANSLATORS: help output 3
+ no-wrap */
+ printf(_(" -h\t\tDisplay this help message\n"
+ " -v\t\tVerbose output\n"
+ " -V\t\tShow program version\n"));
+ printf("\n");
+
+ /* TRANSLATORS: help output 4
+ `list' must not be translated
+ no-wrap */
+ printf(_(" -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"));
+ printf("\n");
+
+ /* TRANSLATORS: help output 5 */
+ printf(_("The neutral temperature is %luK. Using this value will not change the color\n"
+ "temperature of the display. Setting the color temperature to a value higher\n"
+ "than this results in more blue light, and setting a lower value will result in\n"
+ "more red light.\n"),
+ NEUTRAL_TEMPERATURE);
+
+ printf("\n");
+
+ /* TRANSLATORS: help output 6 */
+ printf(_("Default values:\n\n"
+ " Daytime temperature: %luK\n"
+ " Night temperature: %luK\n"),
+ DEFAULT_DAY_TEMPERATURE, DEFAULT_NIGHT_TEMPERATURE);
+
+ printf("\n");
+}
+
+
+/**
+ * Split a list of values, and remove leading and trailing whitespace
+ * from each value
+ *
+ * @param s The string to split; will be modified
+ * @param count The number of despired strings
+ * @param strs Output array for the strings; each element will
+ * be set to `NULL` or `s` with an offset
+ * @param delim The character `s` shall be split by, cannot be
+ * a whitespace character
+ * @return 1 if `s` is valid, 0 otherwise
+ *
+ * If `count` is 1,
+ * the value most be specified,
+ * if `count` is 2,
+ * at least one of values must be specified
+ * if `count` is 3,
+ * all values most be specified, otherwise `s` must only contain
+ * on value, and no colon, which will be used from each output value, or
+ * if `count` is 6,
+ * `s` must be on one of the formats:
+ * `a`:
+ * the specified value is used for each output value,
+ * `a:b`:
+ * the three lower output values will be set to `a`, which may be empty/`NULL`, and
+ * the three upper output values will be set to `b`, which may be empty/`NULL`;
+ * at least `a` or `b` must be non-empty,
+ * `a:b:c`:
+ * each value most be specified, `s` will be interpreted as `a:b:c:a:b:c`,
+ * `:a:b:c`:
+ * each value most be specified, the lower three values be set to `NULL`,
+ * and `a`, `b`, `c` will be used fo the upper three values,
+ * `a:b:c:`:
+ * each value most be specified, the upper three values be set to `NULL`,
+ * and `a`, `b`, `c` will be used fo the lower three values, or
+ * `a:b:c:d:e:f`:
+ * each value most be specified;
+ * where ':' represents `delim`
+ *
+ * Summarily said, `s` may contain a scalar value or a 3-tuple, and it may
+ * also contain a value or one value for daytime and one value or nighttime.
+ * If configured to use 3-tuple but scalar is provided, the provided value is
+ * used for each of the 3 requested values. If configured to use daytime and
+ * nighttime, but only one is specified it is used for both, but if `s`
+ * starts with `delim`, daytime is skipped but if `s` ends with `delim`,
+ * nighttime, is skipped; but both cannot be skipped.
+ */
+static int
+get_strings(char *s, int count, char *strs[], char delim)
+{
+ int i = 0, n;
+
+ /* Split by colon and left-trim */
+ for (i = 0; i < count;) {
+ strs[i++] = s = ltrim(s);
+ s = strchr(s, delim);
+ if (!s)
+ break;
+ *s++ = '\0';
+ }
+ n = i;
+
+ /* Confirm no excess strings */
+ if (s && *ltrim(s))
+ return 0;
+
+ /* Right-trim and replace empty strings with NULL */
+ for (i = 0; i < n; i++)
+ if (!*rtrim(strs[i], NULL))
+ strs[i] = NULL;
+
+ /* Validate NULLs */
+ switch (n) {
+ case 1:
+ /* must be specified */
+ if (!strs[0])
+ return 0;
+ break;
+ case 2:
+ /* at least one most be specified */
+ if (!strs[0] && !strs[1])
+ return 0;
+ break;
+ case 3:
+ /* each most be specified */
+ if (!strs[0] || !strs[1] || !strs[2])
+ return 0;
+ break;
+ case 4:
+ /* exactly either the first or the last shall be NULL */
+ if (!strs[0] == !strs[3] || !strs[1] || !strs[2])
+ return 0;
+ break;
+ case 6:
+ /* each most be specified */
+ if (!strs[0] || !strs[1] || !strs[2] || !strs[3] || !strs[4] || !strs[5])
+ return 0;
+ break;
+ default:
+ /* n==5 is always invalid */
+ return 0;
+ }
+
+ /* Duplicate to fill `strs` */
+ switch (count) {
+ case 2:
+ if (n == 1)
+ strs[1] = strs[0];
+ break;
+ case 3:
+ if (n == 1)
+ strs[2] = strs[1] = strs[0];
+ else if (n == 2)
+ return 0;
+ break;
+ case 6:
+ if (n == 1) {
+ strs[5] = strs[4] = strs[3] = strs[2] = strs[1] = strs[0];
+ } else if (n == 2) {
+ strs[5] = strs[4] = strs[3] = strs[1];
+ strs[2] = strs[1] = strs[0];
+ } else if (n == 3) {
+ strs[5] = strs[2];
+ strs[4] = strs[1];
+ strs[3] = strs[0];
+ } else if (n == 4 && !strs[0]) {
+ strs[5] = strs[3];
+ strs[4] = strs[2];
+ strs[3] = strs[1];
+ strs[2] = NULL;
+ strs[1] = NULL;
+ } else if (n == 4) {
+ strs[5] = NULL;
+ strs[4] = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+
+/**
+ * Parse and set temperature settings
+ *
+ * @param str The temperature specification to parse
+ * @param day The currently specified temperature for daytime,
+ * will be updated; `NULL` if it shall not be set
+ * @param night The currently specified temperature for nighttime,
+ * will be updated; `NULL` if it shall not be set
+ * @param key The configuration file setting being parsed,
+ * `NULL` if parsing the command line
+ */
+static void
+set_temperature(char *str, struct setting_lu *day, struct setting_lu *night, const char *key)
+{
+ struct setting_lu *settings[] = {day, night};
+ enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE;
+ char *strs[2], *end;
+ size_t i, j;
+
+ if (!get_strings(str, !!day + !!night, strs, ':')) {
+ invalid:
+ weprintf(_("Malformed temperature argument."));
+ eprintf(_("Try `-h' for more information."));
+ }
+
+ errno = 0;
+ for (i = 0, j = 0; i < ELEMSOF(settings); i++, j += settings[i] ? 1 : 0) {
+ if (!settings[i] || !strs[j] || settings[i]->source > source)
+ continue;
+ if (settings[i]->source & SETTING_CONFIGFILE) {
+ if (i == 0)
+ weprintf(_("Daytime temperature specified multiple times in configuration file."));
+ else
+ weprintf(_("Night temperature specified multiple times in configuration file."));
+ }
+ settings[i]->source |= source;
+ settings[i]->value = strtoul(strs[j], &end, 10);
+ if (errno || end[*end == 'K'])
+ goto invalid;
+ if (!WITHIN(MIN_TEMPERATURE, settings[i]->value, MAX_TEMPERATURE))
+ eprintf(_("Temperature must be between %luK and %luK."), MIN_TEMPERATURE, MAX_TEMPERATURE);
+ }
+}
+
+
+/**
+ * Parse and set whitepoint brightness settings
+ *
+ * @param str The brightness specification to parse
+ * @param day The currently specified brightness for daytime,
+ * will be updated; `NULL` if it shall not be set
+ * @param night The currently specified brightness for nighttime,
+ * will be updated; `NULL` if it shall not be set
+ * @param key The configuration file setting being parsed,
+ * `NULL` if parsing the command line
+ */
+static void
+set_brightness(char *str, struct setting_f *day, struct setting_f *night, const char *key)
+{
+ struct setting_f *settings[] = {day, night};
+ enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE;
+ char *strs[2], *end;
+ size_t i, j;
+
+ if (!get_strings(str, !!day + !!night, strs, ':')) {
+ invalid:
+ weprintf(_("Malformed brightness argument."));
+ eprintf(_("Try `-h' for more information."));
+ }
+
+ errno = 0;
+ for (i = 0, j = 0; i < ELEMSOF(settings); i++, j += settings[i] ? 1 : 0) {
+ if (!settings[i] || !strs[j] || settings[i]->source > source)
+ continue;
+ if (settings[i]->source & SETTING_CONFIGFILE) {
+ if (i == 0)
+ weprintf(_("Daytime brightness specified multiple times in configuration file."));
+ else
+ weprintf(_("Night brightness specified multiple times in configuration file."));
+ }
+ settings[i]->source |= source;
+ settings[i]->value = strtod(strs[j], &end);
+ if (errno || *end)
+ goto invalid;
+ if (!WITHIN(MIN_BRIGHTNESS, settings[i]->value, MAX_BRIGHTNESS))
+ eprintf(_("Brightness values must be between %.1f and %.1f."), MIN_BRIGHTNESS, MAX_BRIGHTNESS);
+ }
+}
+
+
+/**
+ * Parse and set gamma settings
+ *
+ * @param str The gamma specification to parse
+ * @param day The currently specified gamma for daytime,
+ * will be updated; `NULL` if it shall not be set
+ * @param night The currently specified gamma for nighttime,
+ * will be updated; `NULL` if it shall not be set
+ * @param key The configuration file setting being parsed,
+ * `NULL` if parsing the command line
+ */
+static void
+set_gamma(char *str, struct setting_f3 *day, struct setting_f3 *night, const char *key)
+{
+ struct setting_f3 *settings[] = {day, night};
+ enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE;
+ char *strs[6], *end;
+ size_t i, j, k;
+
+ if (!get_strings(str, 3 * (!!day + !!night), strs, ':')) {
+ invalid:
+ weprintf(_("Malformed gamma argument."));
+ eprintf(_("Try `-h' for more information."));
+ }
+
+ errno = 0;
+ for (i = 0, j = 0; i < ELEMSOF(settings); i++, j += settings[i] ? 3 : 0) {
+ if (!settings[i] || !strs[j] || settings[i]->source > source)
+ continue;
+ if (settings[i]->source & SETTING_CONFIGFILE) {
+ if (i == 0)
+ weprintf(_("Daytime gamma specified multiple times in configuration file."));
+ else
+ weprintf(_("Night gamma specified multiple times in configuration file."));
+ }
+ settings[i]->source |= source;
+ for (k = 0; k < 3; k++) {
+ settings[i]->value[k] = strtod(strs[j + k], &end);
+ if (errno || *end)
+ goto invalid;
+ if (!WITHIN(MIN_GAMMA, settings[i]->value[k], MAX_GAMMA))
+ eprintf(_("Gamma values must be between %.1f and %.1f."), MIN_GAMMA, MAX_GAMMA);
+ }
+ }
+}
+
+
+/**
+ * Parse a time string on either of the formats "HH:MM" and "HH:MM:SS"
+ *
+ * Times up to, but excluding, 48:00 are supported.
+ *
+ * Leap seconds are not supported
+ *
+ * @param str String to parse
+ * @return The represented time, -1 if malformed
+ */
+static time_t
+parse_time(char *str)
+{
+ time_t ret;
+ unsigned long int v;
+
+ errno = 0;
+
+ if (!isdigit(*str))
+ return -1;
+ v = strtoul(str, &str, 10);
+ if (errno || *str++ != ':' || v >= 48UL)
+ return -1;
+ ret = (time_t)(v * 60UL * 60UL);
+
+ if (!isdigit(*str))
+ return -1;
+ v = strtoul(str, &str, 10);
+ if (errno || v >= 60UL)
+ return -1;
+ ret += (time_t)(v * 60UL);
+
+ if (*str) {
+ if (*str++ != ':')
+ return -1;
+ if (!isdigit(*str))
+ return -1;
+ v = strtoul(str, &str, 10);
+ if (errno || *str || v >= 60UL)
+ return -1;
+ ret += (time_t)v;
+ }
+
+ return ret;
+}
+
+
+/**
+ * Parse and set a transition time setting
+ *
+ * @param str The transition time to parse
+ * @param start The currently specified transition start, will be updated
+ * @param end The currently specified transition end, will be updated
+ * @param key The configuration file setting being parsed,
+ * `NULL` if parsing the command line
+ * @return Normally 1, 0 if `str` is malformeda
+ */
+static int
+set_transition_time(char *str, struct setting_time *start, struct setting_time *end, const char *key)
+{
+ struct setting_time *settings[] = {start, end};
+ enum setting_source source = key ? SETTING_CONFIGFILE : SETTING_CMDLINE;
+ char *strs[ELEMSOF(settings)];
+ int i;
+
+ if (!get_strings(str, ELEMSOF(strs), strs, '-'))
+ return 0;
+
+ for (i = 0; i < ELEMSOF(settings); i++) {
+ if (!strs[i] || settings[i]->source > source)
+ continue;
+ if (settings[i]->source & SETTING_CONFIGFILE) {
+ /* TODO */
+ }
+ settings[i]->source |= source;
+ settings[i]->value = parse_time(strs[i]);
+ if (settings[i]->value < 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/**
+ * Print list of available adjustment methods,
+ * and some helpful information
+ */
+static void
+print_method_list(void)
+{
+ size_t i;
+ printf(_("Available adjustment methods:\n"));
+ for (i = 0; gamma_methods[i]; i++)
+ printf(" %s\n", gamma_methods[i]->name);
+
+ printf("\n");
+ printf(_("Specify colon-separated options with `-m METHOD:OPTIONS'.\n"));
+ /* TRANSLATORS: `help' must not be translated. */
+ printf(_("Try `-m METHOD:help' for help.\n"));
+}
+
+
+/**
+ * Print list of available location providers,
+ * and some helpful information
+ */
+static void
+print_provider_list(void)
+{
+ size_t i;
+ printf(_("Available location providers:\n"));
+ for (i = 0; location_providers[i]; i++)
+ printf(" %s\n", location_providers[i]->name);
+
+ printf("\n");
+ printf(_("Specify colon-separated options with`-l PROVIDER:OPTIONS'.\n"));
+ /* TRANSLATORS: `help' must not be translated. */
+ printf(_("Try `-l PROVIDER:help' for help.\n"));
+}
+
+
+/**
+ * Get adjustment method by name
+ *
+ * @param name The name of the adjustment method to return
+ * @return The adjustment method
+ */
+GCC_ONLY(__attribute__((__pure__, __returns_nonnull__)))
+static const struct gamma_method *
+find_gamma_method(const char *name)
+{
+ size_t i;
+ for (i = 0; gamma_methods[i]; i++)
+ if (!strcasecmp(name, gamma_methods[i]->name))
+ return gamma_methods[i];
+ /* TRANSLATORS: This refers to the method used to adjust colours e.g. VidMode */
+ eprintf(_("Unknown adjustment method `%s'."), name);
+}
+
+
+/**
+ * Get location provider by name
+ *
+ * @param name The name of the location provider to return
+ * @return The location provider
+ */
+GCC_ONLY(__attribute__((__pure__, __returns_nonnull__)))
+static const struct location_provider *
+find_location_provider(const char *name)
+{
+ size_t i;
+ for (i = 0; location_providers[i]; i++)
+ if (!strcasecmp(name, location_providers[i]->name))
+ return location_providers[i];
+ eprintf(_("Unknown location provider `%s'."), name);
+}
+
+
+/**
+ * Load default settings
+ *
+ * @param settings Output parameter for the settings
+ */
+static void
+load_defaults(struct settings *settings)
+{
+ memset(settings, 0, sizeof(*settings)); /* set each `.source` to `SETTING_DEFAULT` and booleans to 0 */
+
+ settings->config_file = NULL;
+
+ settings->day.temperature.value = DEFAULT_DAY_TEMPERATURE;
+ settings->day.brightness.value = DEFAULT_DAY_BRIGHTNESS;
+ settings->day.gamma.value[0] = DEFAULT_DAY_GAMMA;
+ settings->day.gamma.value[1] = DEFAULT_DAY_GAMMA;
+ settings->day.gamma.value[2] = DEFAULT_DAY_GAMMA;
+
+ settings->night.temperature.value = DEFAULT_NIGHT_TEMPERATURE;
+ settings->night.brightness.value = DEFAULT_NIGHT_BRIGHTNESS;
+ settings->night.gamma.value[0] = DEFAULT_NIGHT_GAMMA;
+ settings->night.gamma.value[1] = DEFAULT_NIGHT_GAMMA;
+ settings->night.gamma.value[2] = DEFAULT_NIGHT_GAMMA;
+
+ settings->preserve_gamma.value = 1;
+ settings->use_fade.value = 1;
+
+ settings->elevation_high.value = DEFAULT_HIGH_ELEVATION;
+ settings->elevation_low.value = DEFAULT_LOW_ELEVATION;
+
+ settings->method = NULL;
+ settings->method_args = NULL;
+
+ settings->provider = NULL;
+ settings->provider_args = NULL;
+
+ settings->mode = PROGRAM_MODE_CONTINUAL;
+ settings->scheme_type = SOLAR_SCHEME;
+}
+
+
+/**
+ * Load settings from the command line
+ *
+ * @param settings The currently loaded settings, will be updated
+ * @param argc Number of command line arguments
+ * @param argv `NULL` terminated list of command line arguments,
+ * including argument zero
+ */
+static void
+load_from_cmdline(struct settings *settings, int argc, char *argv[])
+{
+ const char *provider_name;
+ char *s, *end, *value;
+
+ ARGBEGIN {
+ case 'b':
+ set_brightness(ARG(), &settings->day.brightness, &settings->night.brightness, NULL);
+ break;
+
+ case 'c':
+ settings->config_file = ARG();
+ break;
+
+ case 'g':
+ set_gamma(ARG(), &settings->day.gamma, &settings->night.gamma, NULL);
+ break;
+
+ case 'h':
+ print_help();
+ exit(0);
+
+ case 'l':
+ value = ARG();
+
+ /* Print list of providers if argument is `list' */
+ if (!strcasecmp(value, "list")) {
+ print_provider_list();
+ exit(0);
+ }
+
+ 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;
+ strtof(value, &end);
+ if (!errno && *end == ':') {
+ /* Use instead as arguments to `manual'. */
+ provider_name = "manual";
+ settings->provider_args = value;
+ } else {
+ /* Split off provider arguments. */
+ s = strchr(value, ':');
+ if (s) {
+ *s++ = '\0';
+ settings->provider_args = s;
+ }
+
+ provider_name = value;
+ }
+
+ /* Lookup provider from name. */
+ settings->provider = find_location_provider(provider_name);
+
+ /* Print provider help if arg is `help'. */
+ if (settings->provider_args && !strcasecmp(settings->provider_args, "help")) {
+ settings->provider->print_help(stdout);
+ exit(0);
+ }
+ break;
+
+ case 'm':
+ value = ARG();
+
+ /* Print list of methods if argument is `list' */
+ if (!strcasecmp(value, "list")) {
+ print_method_list();
+ exit(0);
+ }
+
+ /* Split off method arguments. */
+ s = strchr(value, ':');
+ if (s) {
+ *s++ = '\0';
+ settings->method_args = s;
+ }
+
+ /* Find adjustment method by name. */
+ settings->method = find_gamma_method(value);
+
+ /* Print method help if arg is `help'. */
+ if (settings->method_args && !strcasecmp(settings->method_args, "help")) {
+ settings->method->print_help(stdout);
+ exit(0);
+ }
+ break;
+
+ case 'o':
+ settings->mode = PROGRAM_MODE_ONE_SHOT;
+ break;
+
+ case 'O':
+ settings->mode = PROGRAM_MODE_MANUAL;
+ set_temperature(ARG(), &settings->day.temperature, &settings->night.temperature, NULL);
+ break;
+
+ case 'p':
+ settings->mode = PROGRAM_MODE_PRINT;
+ break;
+
+ case 'P':
+ settings->preserve_gamma.source |= SETTING_CMDLINE;
+ settings->preserve_gamma.value = 0;
+ break;
+
+ case 'r':
+ settings->use_fade.source |= SETTING_CMDLINE;
+ settings->use_fade.value = 0;
+ break;
+
+ case 't':
+ set_temperature(ARG(), &settings->day.temperature, &settings->night.temperature, NULL);
+ break;
+
+ case 'v':
+ settings->verbose = 1;
+ break;
+
+ case 'V':
+ printf("%s\n", PACKAGE_STRING);
+ exit(0);
+ break;
+
+ case 'x':
+ settings->mode = PROGRAM_MODE_RESET;
+ break;
+
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc)
+ usage();
+}
+
+
+/**
+ * Load an setting, form the "redshift" section, from the configuration file
+ *
+ * @param settings The currently loaded settings, will be updated
+ * @param key The name of the configuration
+ * @param value The value of the configuration
+ */
+static void
+load_from_config_ini(struct settings *settings, const char *key, char *value)
+{
+ /* TODO add "temperature" as alias to "temp" (with {,-day,-night} suffix) */
+
+ if (!strcasecmp(key, "temp")) { /* TODO new entry */
+ set_temperature(value, &settings->day.temperature, &settings->night.temperature, key);
+ } else if (!strcasecmp(key, "temp-day")) {
+ set_temperature(value, &settings->day.temperature, NULL, key);
+ } else if (!strcasecmp(key, "temp-night")) {
+ set_temperature(value, NULL, &settings->night.temperature, key);
+
+ } else if (!strcasecmp(key, "brightness")) {
+ set_brightness(value, &settings->day.brightness, &settings->night.brightness, key);
+ } else if (!strcasecmp(key, "brightness-day")) {
+ set_brightness(value, &settings->day.brightness, NULL, key);
+ } else if (!strcasecmp(key, "brightness-night")) {
+ set_brightness(value, NULL, &settings->night.brightness, key);
+
+ } else if (!strcasecmp(key, "gamma")) {
+ set_gamma(value, &settings->day.gamma, &settings->night.gamma, key);
+ } else if (!strcasecmp(key, "gamma-day")) {
+ set_gamma(value, &settings->day.gamma, NULL, key);
+ } else if (!strcasecmp(key, "gamma-night")) {
+ set_gamma(value, NULL, &settings->night.gamma, key);
+
+ } else if (!strcasecmp(key, "transition")) {
+ weprintf(_("`transition' is deprecated and has been replaced with `fade'."));
+ goto set_use_fade;
+ } else if (!strcasecmp(key, "fade")) {
+ set_use_fade:
+ if (settings->use_fade.source & SETTING_CONFIGFILE)
+ weprintf(_("`fade' setting specified multiple times in configuration file."));
+ settings->use_fade.source |= SETTING_CONFIGFILE;
+ if (settings->use_fade.source <= SETTING_CONFIGFILE)
+ settings->use_fade.value = !!atoi(value); /* TODO */
+
+ } else if (!strcasecmp(key, "preserve-gamma")) {
+ if (settings->preserve_gamma.source & SETTING_CONFIGFILE)
+ weprintf(_("`preserve-gamma' setting specified multiple times in configuration file."));
+ settings->preserve_gamma.source |= SETTING_CONFIGFILE;
+ if (settings->preserve_gamma.source <= SETTING_CONFIGFILE)
+ settings->preserve_gamma.value = !!atoi(value); /* TODO */
+
+ } else if (!strcasecmp(key, "elevation-high")) {
+ if (settings->elevation_high.source & SETTING_CONFIGFILE)
+ weprintf(_("`elevation-high' setting specified multiple times in configuration file."));
+ settings->elevation_high.source |= SETTING_CONFIGFILE;
+ if (settings->elevation_high.source <= SETTING_CONFIGFILE)
+ settings->elevation_high.value = atof(value); /* TODO */
+
+ } else if (!strcasecmp(key, "elevation-low")) {
+ if (settings->elevation_low.source & SETTING_CONFIGFILE)
+ weprintf(_("`elevation-low' setting specified multiple times in configuration file."));
+ settings->elevation_low.source |= SETTING_CONFIGFILE;
+ if (settings->elevation_low.source <= SETTING_CONFIGFILE)
+ settings->elevation_low.value = atof(value); /* TODO */
+
+ } else if (!strcasecmp(key, "dawn-time")) {
+ if (!set_transition_time(value, &settings->dawn.start, &settings->dawn.end, key))
+ eprintf(_("Malformed dawn-time setting `%s'."), value);
+
+ } else if (!strcasecmp(key, "dusk-time")) {
+ if (!set_transition_time(value, &settings->dusk.start, &settings->dusk.end, key))
+ eprintf(_("Malformed dusk-time setting `%s'."), value);
+
+ } else if (!strcasecmp(key, "adjustment-method")) {
+ if (!settings->method)
+ settings->method = find_gamma_method(value);
+
+ } else if (!strcasecmp(key, "location-provider")) {
+ if (!settings->provider)
+ settings->provider = find_location_provider(value);
+
+ } else {
+ weprintf(_("Unknown configuration setting `%s'."), key);
+ }
+}
+
+
+void
+load_settings(struct settings *settings, int argc, char *argv[])
+{
+ struct config_ini_section *section;
+ struct config_ini_setting *setting;
+ int i, j, n;
+ time_t duration;
+
+ /* Load settings; some validation takes place */
+ load_defaults(settings);
+ load_from_cmdline(settings, argc, argv);
+ config_ini_init(&settings->config, settings->config_file);
+ if ((section = config_ini_get_section(&settings->config, "redshift")))
+ for (setting = section->settings; setting; setting = setting->next)
+ load_from_config_ini(settings, setting->name, setting->value);
+
+ /* Further validate settings */
+ n = !settings->dawn.start.source + !settings->dawn.end.source;
+ n += !settings->dusk.start.source + !settings->dusk.end.source;
+ if (n) {
+ settings->scheme_type = CLOCK_SCHEME;
+ if (n != 4)
+ eprintf(_("Partial time-configuration not supported!"));
+
+ if (settings->dawn.start.value >= ONE_DAY || settings->dusk.start.value >= ONE_DAY ||
+ labs((long)settings->dawn.end.value - (long)settings->dawn.start.value) > (long)ONE_DAY ||
+ labs((long)settings->dusk.end.value - (long)settings->dusk.start.value) > (long)ONE_DAY)
+ goto invalid_twilight;
+
+ /* TODO deal with edge-case where one of the twilights last 24 hour */
+ settings->dawn.end.value %= ONE_DAY;
+ settings->dusk.end.value %= ONE_DAY;
+ if (settings->dawn.start.value <= settings->dawn.end.value) {
+ if (BETWEEN(settings->dawn.start.value, settings->dusk.start.value, settings->dawn.end.value) ||
+ BETWEEN(settings->dawn.start.value, settings->dusk.end.value, settings->dawn.end.value))
+ goto invalid_twilight;
+ } else {
+ if (!WITHIN(settings->dawn.end.value, settings->dusk.start.value, settings->dawn.start.value) ||
+ !WITHIN(settings->dawn.end.value, settings->dusk.end.value, settings->dawn.start.value))
+ goto invalid_twilight;
+ }
+ }
+ if (settings->elevation_high.value < settings->elevation_low.value)
+ eprintf(_("High transition elevation cannot be lower than the low transition elevation."));
+
+ /* If the effects are the same throughout the day, do not use a transition scheme */
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+ if (settings->mode == PROGRAM_MODE_RESET) {
+ settings->scheme_type = STATIC_SCHEME;
+ } else if (settings->day.temperature.value == settings->night.temperature.value &&
+ settings->day.brightness.value == settings->night.brightness.value &&
+ settings->day.gamma.value[0] == settings->night.gamma.value[0] &&
+ settings->day.gamma.value[1] == settings->night.gamma.value[1] &&
+ settings->day.gamma.value[2] == settings->night.gamma.value[2]) {
+ settings->scheme_type = STATIC_SCHEME;
+ }
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ /* Publish loaded settings */
+ day_settings.temperature = settings->day.temperature.value;
+ day_settings.brightness = settings->day.brightness.value;
+ day_settings.gamma[0] = settings->day.gamma.value[0];
+ day_settings.gamma[1] = settings->day.gamma.value[1];
+ day_settings.gamma[2] = settings->day.gamma.value[2];
+ night_settings.temperature = settings->night.temperature.value;
+ night_settings.brightness = settings->night.brightness.value;
+ night_settings.gamma[0] = settings->night.gamma.value[0];
+ night_settings.gamma[1] = settings->night.gamma.value[1];
+ night_settings.gamma[2] = settings->night.gamma.value[2];
+ scheme.type = settings->scheme_type;
+ if (scheme.type == SOLAR_SCHEME) {
+ scheme.elevation.high = settings->elevation_high.value;
+ scheme.elevation.low = settings->elevation_low.value;
+ scheme.elevation.range = scheme.elevation.high - scheme.elevation.low;
+ } else if (scheme.type == CLOCK_SCHEME) {
+ scheme.time.periods = &scheme.time.periods_array[0];
+ scheme.time.periods_array[0].start = settings->dawn.start.value;
+ scheme.time.periods_array[0].day_level = 0.0;
+ scheme.time.periods_array[1].start = settings->dawn.end.value;
+ scheme.time.periods_array[1].day_level = 1.0;
+ scheme.time.periods_array[2].start = settings->dusk.start.value;
+ scheme.time.periods_array[2].day_level = 1.0;
+ scheme.time.periods_array[3].start = settings->dusk.end.value;
+ scheme.time.periods_array[3].day_level = 0.0;
+ for (i = 0; i < 4; i++) {
+ j = (i + 1) % 4;
+ scheme.time.periods_array[i].next = &scheme.time.periods_array[j];
+ duration = scheme.time.periods_array[j].start - scheme.time.periods_array[i].start;
+ if (duration < 0)
+ duration += ONE_DAY;
+ scheme.time.periods_array[i].diff_over_duration = scheme.time.periods_array[j].day_level;
+ scheme.time.periods_array[i].diff_over_duration -= scheme.time.periods_array[i].day_level;
+ scheme.time.periods_array[i].diff_over_duration /= duration ? (double)duration : 1.0;
+ }
+ }
+
+ /* Output settings */
+ if (settings->verbose) {
+ if (scheme.type == SOLAR_SCHEME) {
+ /* TRANSLATORS: Append degree symbols if possible. */
+ printf(_("Solar elevations: day above %.1f, night below %.1f\n"),
+ scheme.elevation.high, scheme.elevation.low);
+ }
+ if (scheme.type != STATIC_SCHEME) {
+ printf(_("Temperatures: %luK at day, %luK at night\n"),
+ day_settings.temperature, night_settings.temperature);
+ }
+ printf(_("Brightness: %.2f:%.2f\n"), settings->day.brightness.value, settings->night.brightness.value);
+ /* TRANSLATORS: The string in parenthesis is either Daytime or Night (translated). */
+ printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), _("Daytime"),
+ day_settings.gamma[0], day_settings.gamma[1], day_settings.gamma[2]);
+ printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"), _("Night"),
+ night_settings.gamma[0], night_settings.gamma[1], night_settings.gamma[2]);
+ }
+
+ return;
+
+invalid_twilight:
+ eprintf(_("Invalid dawn/dusk time configuration!"));
+}
diff --git a/src/config.mk b/src/config.mk
index f50e741..f26a6a3 100644
--- a/src/config.mk
+++ b/src/config.mk
@@ -1,5 +1,6 @@
PREFIX = /usr
MANPREFIX = $(PREFIX)/share/man
+LOCALEDIR = $(PREFIX)/share/locale
PACKAGE = redshift-ng
@@ -18,6 +19,6 @@ 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 -D'PACKAGE="$(PACKAGE)"'
+ -DENABLE_COOPGAMMA
CFLAGS = $$($(PKGCONFIG_CFLAGS) $(LIBS_PKGCONFIG))
LDFLAGS = $$($(PKGCONFIG_LDFLAGS) $(LIBS_PKGCONFIG)) -lm -lcoopgamma -lred -lsimple
diff --git a/src/gamma-coopgamma.c b/src/gamma-coopgamma.c
index ae2c1f9..2f2b0d0 100644
--- a/src/gamma-coopgamma.c
+++ b/src/gamma-coopgamma.c
@@ -1,5 +1,7 @@
-/* gamma-coopgamma.h -- coopgamma gamma adjustment source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,8 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2016, 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -497,13 +497,13 @@ coopgamma_set_temperature(struct gamma_state *state, const struct colour_setting
switch (filter->depth) {
#define X(SUFFIX, TYPE, MAX, DEPTH)\
case DEPTH:\
- colourramp_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);\
+ fill_ramps_##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(X, ;);
#undef X
diff --git a/src/gamma-drm.c b/src/gamma-drm.c
index 73fd718..ea31367 100644
--- a/src/gamma-drm.c
+++ b/src/gamma-drm.c
@@ -1,5 +1,7 @@
-/* gamma-drm.c -- DRM gamma adjustment source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2014, 2025 Mattias Andrée <m@maandree.se>
- * Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#include "common.h"
@@ -283,7 +282,7 @@ drm_set_temperature(
b_gamma[i] = value;
}
- colourramp_fill_u16(r_gamma, g_gamma, b_gamma, crtcs->gamma_size, crtcs->gamma_size, crtcs->gamma_size, setting);
+ fill_ramps_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);
}
diff --git a/src/gamma-dummy.c b/src/gamma-dummy.c
index 32f6605..abb5d22 100644
--- a/src/gamma-dummy.c
+++ b/src/gamma-dummy.c
@@ -1,5 +1,7 @@
-/* gamma-dummy.c -- No-op gamma adjustment
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,8 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#include "common.h"
diff --git a/src/gamma-quartz.c b/src/gamma-quartz.c
index 805bb03..c708105 100644
--- a/src/gamma-quartz.c
+++ b/src/gamma-quartz.c
@@ -1,5 +1,7 @@
-/* gamma-quartz.c -- Quartz (OSX) gamma adjustment
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -174,7 +173,7 @@ quartz_set_temperature_for_display(struct gamma_state *state, int display_index,
}
}
- colourramp_fill_float(gamma_r, gamma_g, gamma_b, ramp_size, ramp_size, ramp_size, setting);
+ fill_ramps_float(gamma_r, gamma_g, gamma_b, ramp_size, ramp_size, ramp_size, setting);
error = CGSetDisplayTransferByTable(display, ramp_size, gamma_r, gamma_g, gamma_b);
if (error != kCGErrorSuccess) {
diff --git a/src/gamma-randr.c b/src/gamma-randr.c
index 384a9e2..6e5f154 100644
--- a/src/gamma-randr.c
+++ b/src/gamma-randr.c
@@ -1,5 +1,7 @@
-/* gamma-randr.c -- X RANDR gamma adjustment source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -369,7 +368,7 @@ randr_set_temperature_for_crtc(struct gamma_state *state, int crtc_num,
}
}
- colourramp_fill_u16(gamma_r, gamma_g, gamma_b, ramp_size, ramp_size, ramp_size, setting);
+ fill_ramps_u16(gamma_r, gamma_g, gamma_b, ramp_size, ramp_size, ramp_size, setting);
/* Set new gamma ramps */
gamma_set_cookie = xcb_randr_set_crtc_gamma_checked(state->conn, crtc, ramp_size, gamma_r, gamma_g, gamma_b);
diff --git a/src/gamma-vidmode.c b/src/gamma-vidmode.c
index 7f8434c..042f90c 100644
--- a/src/gamma-vidmode.c
+++ b/src/gamma-vidmode.c
@@ -1,5 +1,7 @@
-/* gamma-vidmode.c -- X VidMode gamma adjustment source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -178,7 +177,7 @@ vidmode_set_temperature(
}
}
- colourramp_fill_u16(gamma_r, gamma_g, gamma_b, state->ramp_size, state->ramp_size, state->ramp_size, setting);
+ fill_ramps_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,
diff --git a/src/gamma-w32gdi.c b/src/gamma-w32gdi.c
index bc6326c..e216a23 100644
--- a/src/gamma-w32gdi.c
+++ b/src/gamma-w32gdi.c
@@ -1,5 +1,7 @@
-/* gamma-w32gdi.c -- Windows GDI gamma adjustment source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#ifndef WINVER
# define WINVER 0x0500
@@ -168,7 +167,7 @@ w32gdi_set_temperature(
}
}
- colourramp_fill_u16(gamma_r, gamma_g, gamma_b, GAMMA_RAMP_SIZE, GAMMA_RAMP_SIZE, GAMMA_RAMP_SIZE, setting);
+ fill_ramps_u16(gamma_r, gamma_g, gamma_b, GAMMA_RAMP_SIZE, GAMMA_RAMP_SIZE, GAMMA_RAMP_SIZE, setting);
/* Set new gamma ramps */
for (i = 0; i < MAX_ATTEMPTS; i++) {
diff --git a/src/gamma.c b/src/gamma.c
new file mode 100644
index 0000000..508374b
--- /dev/null
+++ b/src/gamma.c
@@ -0,0 +1,134 @@
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
+ *
+ * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "common.h"
+
+
+const struct gamma_method *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
+};
+
+
+static int
+try_start(const struct gamma_method *method, GAMMA_STATE **state,
+ enum program_mode mode, struct config_ini_state *config, char *args)
+{
+ struct config_ini_section *section;
+ struct config_ini_setting *setting;
+ char *next_arg, *value;
+ const char *key;
+
+ if (method->init(state) < 0) {
+ weprintf(_("Initialization of %s failed."), method->name);
+ return -1;
+ }
+
+ /* Set method options from config file. */
+ if ((section = config_ini_get_section(config, method->name)))
+ for (setting = section->settings; setting; setting = setting->next)
+ if (method->set_option(*state, setting->name, setting->value) < 0)
+ goto set_option_fail;
+
+ /* Set method options from command line. */
+ while (args) {
+ next_arg = strchr(args, ':');
+ if (next_arg)
+ *next_arg++ = '\0';
+
+ key = args;
+ value = strchr(args, '=');
+ if (!value) {
+ weprintf(_("Failed to parse option `%s'."), args);
+ return -1;
+ }
+ *value++ = '\0';
+
+ if (method->set_option(*state, key, value) < 0)
+ goto set_option_fail;
+
+ args = next_arg;
+ }
+
+ /* Start method. */
+ if (method->start(*state, mode) < 0) {
+ method->free(*state);
+ weprintf(_("Failed to start adjustment method %s."), method->name);
+ return -1;
+ }
+
+ return 0;
+
+set_option_fail:
+ method->free(*state);
+ weprintf(_("Failed to set %s option."), method->name);
+ /* TRANSLATORS: `help' must not be translated. */
+ weprintf(_("Try `-m %s:help' for more information."), method->name);
+ return -1;
+}
+
+
+void
+acquire_adjustment_method(struct settings *settings, GAMMA_STATE **method_state_out)
+{
+ size_t i;
+
+ if (settings->method) {
+ /* Use method specified on command line. */
+ if (try_start(settings->method, method_state_out, settings->mode, &settings->config, settings->method_args) < 0)
+ exit(1);
+ } else {
+ /* Try all methods, use the first that works. */
+ for (i = 0; gamma_methods[i]; i++) {
+ if (!gamma_methods[i]->autostart)
+ continue;
+
+ if (try_start(gamma_methods[i], method_state_out, settings->mode, &settings->config, NULL) < 0) {
+ weprintf(_("Trying next method..."));
+ continue;
+ }
+
+ /* Found method that works. */
+ printf(_("Using method `%s'.\n"), gamma_methods[i]->name);
+ settings->method = gamma_methods[i];
+ break;
+ }
+
+ /* Failure if no methods were successful at this point. */
+ if (!settings->method)
+ eprintf(_("No more methods to try."));
+ }
+}
diff --git a/src/hooks.c b/src/hooks.c
index a19c60d..aaddd4f 100644
--- a/src/hooks.c
+++ b/src/hooks.c
@@ -1,5 +1,7 @@
-/* hooks.c -- Hooks triggered by events
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,103 +15,179 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com>
*/
#include "common.h"
-#define MAX_HOOK_PATH 4096
-
-/* Names of periods supplied to scripts. */
+/**
+ * Names of periods supplied to scripts
+ */
static const char *period_names[] = {
- "none",
- "daytime",
- "night",
- "transition"
+ [PERIOD_NONE] = "none",
+ [PERIOD_DAYTIME] = "daytime",
+ [PERIOD_NIGHT] = "night",
+ [PERIOD_TRANSITION] = "transition"
};
-/* Try to open the directory containing hooks. HP is a string
- of MAX_HOOK_PATH length that will be filled with the path
- of the returned directory. */
-static DIR *
-open_hooks_dir(char *hp)
-{
- char *env;
+/**
+ * Path name of the hook directory, `NULL` if not found
+ */
+static char *dirpath = NULL;
-#ifndef WINDOWS
- struct passwd *pwd;
-#endif
+/**
+ * The allocation size of `dirpath`
+ */
+static size_t dirpathsize;
- env = getenv("XDG_CONFIG_HOME");
- if (env && *env) {
- snprintf(hp, MAX_HOOK_PATH, "%s/redshift/hooks", env);
- return opendir(hp);
- }
+/**
+ * The length of the string in `dirpath`
+ */
+static size_t dirpathlen;
- env = getenv("HOME");
- if (env && *env) {
- snprintf(hp, MAX_HOOK_PATH, "%s/.config/redshift/hooks", env);
- return opendir(hp);
- }
-#ifndef WINDOWS
- pwd = getpwuid(getuid()); /* TODO check failure */
- snprintf(hp, MAX_HOOK_PATH, "%s/.config/redshift/hooks", pwd->pw_dir);
- return opendir(hp);
-#else
- return NULL;
+/**
+ * Paths, in order of priority, to test when looking for
+ * the hooks directory for redshift
+ */
+static const struct env_path paths[] = {
+ {0, "XDG_CONFIG_HOME", "/redshift-ng/hooks"},
+ {0, "XDG_CONFIG_HOME", "/redshift/hooks"},
+#ifdef WINDOWS
+ {0, "localappdata", "/redshift-ng/hooks"},
+ {0, "localappdata", "/redshift/hooks"},
#endif
+ {0, "HOME", "/.config/redshift-ng/hooks"},
+ {0, "HOME", "/.config/redshift/hooks"},
+ {0, NULL, "/.config/redshift-ng/hooks"},
+ {0, NULL, "/.config/redshift/hooks"},
+ {1, "XDG_CONFIG_DIRS", "/redshift-ng/hooks"},
+ {1, "XDG_CONFIG_DIRS", "/redshift/hooks"},
+ {0, "", "/etc/redshift-ng/hooks"},
+ {0, "", "/etc/redshift/hooks"}
+};
+
+
+/**
+ * Deallocates `dirpath`
+ */
+static void
+cleanup(void)
+{
+ free(dirpath);
+ dirpath = NULL;
}
-/* Run hooks with a signal that the period changed. */
-void
-hooks_signal_period_change(enum period prev_period, enum period period)
+
+/**
+ * Search for and open the hook directory for reading
+ *
+ * @param path_out Output parameter for the hook directroy path
+ * @param pathbuf_out Output parameter for the memory allocation for `*path_out`;
+ * will be set to `NULL`; shall be free(3)d by the caller
+ * @return `DIR` object for the reading the directory; `NULL` if not found
+ */
+static DIR *
+open_hooks_dir(const char **path_out, char **pathbuf_out)
+{
+ DIR *dir = NULL;
+ size_t i;
+
+ *path_out = NULL;
+ *pathbuf_out = NULL;
+
+ for (i = 0; !dir && i < ELEMSOF(paths); i++)
+ dir = try_path_opendir(&paths[i], path_out, pathbuf_out);
+ if (dir)
+ weprintf(_("Found hook directory `%s'."), *path_out);
+ else
+ weprintf(_("No hook directory found."));
+
+ return dir;
+}
+
+
+/**
+ * Run hooks
+ *
+ * @param argv `NULL` terminated list of command line arguments
+ * for the hooks; must contain an unused initial slot,
+ * which the function will use to provide the zeroth
+ * argument
+ */
+static void
+run_hooks(const char *argv[])
{
- char hooksdir_path[MAX_HOOK_PATH];
- DIR *hooks_dir;
- struct dirent *ent;
- char *hook_name;
- char hook_path[MAX_HOOK_PATH];
- int r;
-
- hooks_dir = open_hooks_dir(hooksdir_path);
- if (!hooks_dir)
+ static int looked_up_dir = 0;
+
+ DIR *dir;
+ struct dirent *f;
+ size_t required;
+ const char *dirpath_static;
+
+ if (!looked_up_dir) {
+ looked_up_dir = 1;
+ dir = open_hooks_dir(&dirpath_static, &dirpath);
+ if (!dir)
+ return;
+ if (!dirpath)
+ dirpath = estrdup(dirpath_static);
+ dirpathsize = dirpathlen = strlen(dirpath);
+ atexit(&cleanup);
+ } else if (dirpath) {
+ dir = opendir(dirpath);
+ if (!dir) {
+ weprintf("opendir %s:", dirpath);
+ cleanup();
+ return;
+ }
+ } else {
return;
+ }
- while ((ent = readdir(hooks_dir))) {
- /* Skip hidden and special files (., ..) */
- if (ent->d_name[0] == '\0' || ent->d_name[0] == '.')
+ while ((errno = 0, f = readdir(dir))) {
+ if (f->d_name[0] == '.' || !f->d_name[0] || strchr(f->d_name, '\0')[-1] == '~')
continue;
- hook_name = ent->d_name;
- snprintf(hook_path, sizeof(hook_path), "%s/%s", hooksdir_path, hook_name);
+ required = dirpathlen + sizeof("/") + strlen(f->d_name);
+ if (required > dirpathsize)
+ dirpath = erealloc(dirpath, dirpathsize = required);
-#ifndef WINDOWS
- /* Fork and exec the hook. We close stdout
- so the hook cannot interfere with the normal
- output. */
+#ifdef WINDOWS
+ /* TODO [Windows] hooks are not support on Windows */
+#else
switch (fork()) {
case -1:
weprintf("fork:");
break;
- case 0:
- close(STDOUT_FILENO);
-
- r = execl(hook_path, hook_name,
- "period-changed",
- period_names[prev_period],
- period_names[period], NULL);
- if (r < 0 && errno != EACCES)
- weprintf("execl %s:", hook_path);
- /* Only reached on error */
+ case 0:
+ if (dup2(STDOUT_FILENO, STDERR_FILENO) != STDERR_FILENO)
+ _eprintf("dup2 <stdout> <stderr>:");
+ stpcpy(stpcpy(&dirpath[dirpathlen], "/"), f->d_name);
+ argv[0] = dirpath;
+ execv(dirpath, (const void *)argv);
+ if (errno != EACCES)
+ weprintf("execv %s:", dirpath);
_exit(1);
+
default:
/* SIGCHLD is ignored */
break;
}
#endif
}
+
+ if (errno)
+ weprintf("readdir %s:", dirpath);
+
+ closedir(dir);
+}
+
+
+void
+run_period_change_hooks(enum period prev_period, enum period period)
+{
+ const char *argv[] = {NULL, "period-changed", period_names[prev_period], period_names[period], NULL};
+ run_hooks(argv);
}
diff --git a/src/location-corelocation.m b/src/location-corelocation.m
index f4a3afb..435697d 100644
--- a/src/location-corelocation.m
+++ b/src/location-corelocation.m
@@ -1,5 +1,7 @@
-/* location-corelocation.m -- CoreLocation (OSX) location provider source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -67,7 +66,7 @@ struct location_state {
self.state->error = 1;
[self.state->lock unlock];
- pipeutils_signal(self.state->pipe_fd_write);
+ write(self.state->pipe_fd_write, "", 1);
}
- (void)markUnavailable
@@ -76,7 +75,7 @@ struct location_state {
self.state->available = 0;
[self.state->lock unlock];
- pipeutils_signal(self.state->pipe_fd_write);
+ write(self.state->pipe_fd_write, "", 1);
}
- (void)locationManager:(CLLocationManager *)manager
@@ -92,7 +91,7 @@ struct location_state {
[self.state->lock unlock];
- pipeutils_signal(self.state->pipe_fd_write);
+ write(self.state->pipe_fd_write, "", 1);
}
- (void)locationManager:(CLLocationManager *)manager
@@ -191,7 +190,7 @@ location_corelocation_start(struct location_state *state)
state->location.lat = 0;
state->location.lon = 0;
- if (pipeutils_create_nonblocking(pipefds)) {
+ if (pipe_nonblock(pipefds)) {
weprintf(_("Failed to start CoreLocation provider!"));
return -1;
}
@@ -199,7 +198,7 @@ location_corelocation_start(struct location_state *state)
state->pipe_fd_read = pipefds[0];
state->pipe_fd_write = pipefds[1];
- pipeutils_signal(state->pipe_fd_write);
+ write(state->pipe_fd_write, "", 1);
state->lock = [[NSLock alloc] init];
@@ -244,7 +243,7 @@ location_corelocation_handle(struct location_state *state, location_t *location,
{
int error;
- pipeutils_handle_signal(state->pipe_fd_read);
+ read(state->pipe_fd_read, &(char){0}, 1);
[state->lock lock];
error = state->error;
diff --git a/src/location-geoclue2.c b/src/location-geoclue2.c
index 1fcfa51..b6238df 100644
--- a/src/location-geoclue2.c
+++ b/src/location-geoclue2.c
@@ -1,5 +1,7 @@
-/* location-geoclue2.c -- GeoClue2 location provider source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -69,7 +68,7 @@ mark_error(struct location_state *state)
state->error = 1;
g_mutex_unlock(&state->lock);
- pipeutils_signal(state->pipe_fd_write);
+ write(state->pipe_fd_write, "", 1);
}
/* Handle position change callbacks */
@@ -116,7 +115,7 @@ geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name, gchar *signal_n
g_mutex_unlock(&state->lock);
- pipeutils_signal(state->pipe_fd_write);
+ write(state->pipe_fd_write, "", 1);
}
/* Callback when GeoClue name appears on the bus */
@@ -244,7 +243,7 @@ on_name_vanished(GDBusConnection *connection, const gchar *name, gpointer user_d
state->available = 0;
g_mutex_unlock(&state->lock);
- pipeutils_signal(state->pipe_fd_write);
+ write(state->pipe_fd_write, "", 1);
}
/* Callback when the pipe to the main thread is closed. */
@@ -321,7 +320,7 @@ location_geoclue2_start(struct location_state *state)
state->location.latitude = 0;
state->location.longitude = 0;
- if (pipeutils_create_nonblocking(pipefds)) {
+ if (pipe_nonblock(pipefds)) {
weprintf(_("Failed to start GeoClue2 provider!"));
return -1;
}
@@ -329,7 +328,7 @@ location_geoclue2_start(struct location_state *state)
state->pipe_fd_read = pipefds[0];
state->pipe_fd_write = pipefds[1];
- pipeutils_signal(state->pipe_fd_write);
+ write(state->pipe_fd_write, "", 1);
g_mutex_init(&state->lock);
state->thread = g_thread_new("geoclue2", run_geoclue2_loop, state);
@@ -378,7 +377,7 @@ location_geoclue2_handle(struct location_state *state, struct location *location
{
int error;
- pipeutils_handle_signal(state->pipe_fd_read);
+ read(state->pipe_fd_read, &(char){0}, 1);
g_mutex_lock(&state->lock);
error = state->error;
diff --git a/src/location-manual.c b/src/location-manual.c
index da34ea2..8df1874 100644
--- a/src/location-manual.c
+++ b/src/location-manual.c
@@ -1,5 +1,7 @@
-/* location-manual.c -- Manual location provider source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
diff --git a/src/location.c b/src/location.c
new file mode 100644
index 0000000..216b7a9
--- /dev/null
+++ b/src/location.c
@@ -0,0 +1,187 @@
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
+ *
+ * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "common.h"
+
+
+const struct location_provider *location_providers[] = {
+#ifdef ENABLE_GEOCLUE2
+ &geoclue2_location_provider,
+#endif
+#ifdef ENABLE_CORELOCATION
+ &corelocation_location_provider,
+#endif
+ &manual_location_provider,
+ NULL
+};
+
+
+
+static int
+try_start(const struct location_provider *provider, LOCATION_STATE **state, struct config_ini_state *config, char *args)
+{
+ const char *manual_keys[] = {"lat", "lon"};
+ struct config_ini_section *section;
+ struct config_ini_setting *setting;
+ char *next_arg, *value;
+ const char *key;
+ int i;
+
+ if (provider->init(state) < 0) {
+ weprintf(_("Initialization of %s failed."), provider->name);
+ return -1;
+ }
+
+ /* Set provider options from config file. */
+ if ((section = config_ini_get_section(config, provider->name)))
+ for (setting = section->settings; setting; setting = setting->next)
+ if (provider->set_option(*state, setting->name, setting->value) < 0)
+ goto set_option_fail;
+
+ /* Set provider options from command line. */
+ for (i = 0; args; i++) {
+ next_arg = strchr(args, ':');
+ if (next_arg)
+ *next_arg++ = '\0';
+
+ key = args;
+ value = strchr(args, '=');
+ if (!value) {
+ /* The options for the "manual" method can be set
+ without keys on the command line for convencience
+ and for backwards compatability. We add the proper
+ keys here before calling set_option(). */
+ if (!strcmp(provider->name, "manual") && i < ELEMSOF(manual_keys)) {
+ key = manual_keys[i];
+ value = args;
+ } else {
+ weprintf(_("Failed to parse option `%s'."), args);
+ return -1;
+ }
+ } else {
+ *value++ = '\0';
+ }
+
+ if (provider->set_option(*state, key, value) < 0)
+ goto set_option_fail;
+
+ args = next_arg;
+ }
+
+ /* Start provider. */
+ if (provider->start(*state) < 0) {
+ provider->free(*state);
+ weprintf(_("Failed to start provider %s."), provider->name);
+ return -1;
+ }
+
+ return 0;
+
+set_option_fail:
+ provider->free(*state);
+ weprintf(_("Failed to set %s option."), provider->name);
+ /* TRANSLATORS: `help' must not be translated. */
+ weprintf(_("Try `-l %s:help' for more information."), provider->name);
+ return -1;
+}
+
+
+static long long int
+get_monotonic_millis(void)
+{
+#if defined(WINDOWS)
+ return (long long int)GetTickCount64();
+#else
+ struct timespec now;
+ if (clock_gettime(CLOCK_MONOTONIC, &now))
+ eprintf("clock_gettime CLOCK_MONOTONIC:");
+ return (long long int)now.tv_sec * 1000LL + (long long int)now.tv_nsec / 1000000LL;
+#endif
+}
+
+
+/* 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. */
+int
+get_location(const struct location_provider *provider, LOCATION_STATE *state, int timeout, struct location *loc)
+{
+ int r, available;
+ struct pollfd pollfds[1];
+ long long int now = get_monotonic_millis();
+ long long int end = now + (long long int)timeout;
+
+ do {
+ pollfds[0].fd = provider->get_fd(state);
+ if (pollfds[0].fd >= 0) {
+ /* Poll on file descriptor until ready. */
+ pollfds[0].events = POLLIN;
+ timeout = (int)MAX(end - now, 0);
+ r = poll(pollfds, 1, timeout);
+ if (r > 0) {
+ now = get_monotonic_millis();
+ } else if (r < 0) {
+#ifndef WINDOWS
+ if (errno == EINTR)
+ continue;
+#endif
+ weprintf("poll {{.fd=<location provider>, .events=EPOLLIN}} 1 %i:", timeout);
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ if (provider->handle(state, loc, &available) < 0)
+ return -1;
+ } while (!available);
+
+ return 1;
+}
+
+
+void
+acquire_location_provider(struct settings *settings, LOCATION_STATE **location_state_out)
+{
+ size_t i;
+
+ if (settings->provider) {
+ /* Use provider specified on command line. */
+ if (try_start(settings->provider, location_state_out, &settings->config, settings->provider_args) < 0)
+ exit(1);
+ } else {
+ /* Try all providers, use the first that works. */
+ for (i = 0; location_providers[i]; i++) {
+ weprintf(_("Trying location provider `%s'..."), location_providers[i]->name);
+ if (try_start(location_providers[i], location_state_out, &settings->config, NULL) < 0) {
+ weprintf(_("Trying next provider..."));
+ continue;
+ }
+
+ /* Found provider that works. */
+ printf(_("Using provider `%s'.\n"), location_providers[i]->name);
+ settings->provider = location_providers[i];
+ break;
+ }
+
+ /* Failure if no providers were successful at this point. */
+ if (!settings->provider)
+ eprintf(_("No more location providers to try."));
+ }
+}
diff --git a/src/options.c b/src/options.c
deleted file mode 100644
index ed5886a..0000000
--- a/src/options.c
+++ /dev/null
@@ -1,558 +0,0 @@
-/* options.c -- Program options
- * This file is part of redshift-ng.
- *
- * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
- */
-#include "common.h"
-
-/* Angular elevation of the sun at which the colour 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 LIBRED_SOLAR_ELEVATION_CIVIL_DUSK_DAWN
-#define TRANSITION_HIGH 3.0
-
-/* Default values for parameters. */
-#define DEFAULT_DAY_TEMPERATURE 6500UL
-#define DEFAULT_NIGHT_TEMPERATURE 4500UL
-#define DEFAULT_BRIGHTNESS NEUTRAL_BRIGHTNESS
-#define DEFAULT_GAMMA NEUTRAL_GAMMA
-
-
-/* TODO missing translation */
-USAGE("[-b day:night] [-c file] [-g r:g:b] [-l latitude:longitude | -l provider[:options]]"
- " [-m method[:options]] [-o | -O temperature | -t day:night | -x] [-pPrv] | -hV");
-
-
-/* A brightness string contains either one floating point value,
- or two values separated by a colon. */
-static void
-parse_brightness_string(const char *str, double *bright_day, double *bright_night)
-{
- char *s = strchr(str, ':');
- if (s) {
- *s++ = '\0';
- *bright_day = atof(str);
- *bright_night = atof(s);
- } else {
- *bright_day = *bright_night = atof(str);
- }
-}
-
-/* A gamma string contains either one floating point value,
- or three values separated by colon. */
-static int
-parse_gamma_string(const char *str, double gamma[3])
-{
- char *s = strchr(str, ':');
- if (!s) {
- /* Use value for all channels */
- double g = atof(str);
- gamma[0] = gamma[1] = gamma[2] = g;
- } else {
- /* Parse separate value for each channel */
- char *g_s;
- *s++ = '\0';
- g_s = s;
- s = strchr(s, ':');
- if (!s)
- 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)
-{
- char *min = NULL;
- long hours, minutes;
-
- errno = 0;
- hours = strtol(str, (void *)&min, 10);
- if (errno || min == str || min[0] != ':' || hours < 0 || hours >= 24)
- return -1;
-
- min += 1;
- errno = 0;
- minutes = strtol(min, (void *)end, 10);
- if (errno || *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, struct time_range *range)
-{
- const char *next = NULL, *end = NULL;
- int start_time, end_time;
-
- start_time = parse_transition_time(str, &next);
- if (start_time < 0)
- return -1;
-
- if (!*next) {
- end_time = start_time;
- } else if (*next == '-') {
- end_time = parse_transition_time(&next[1], &end);
- if (end_time < 0 || *end)
- return -1;
- } else {
- return -1;
- }
-
- range->start = start_time;
- range->end = end_time;
-
- return 0;
-}
-
-/* Print help text. */
-static void
-print_help(void)
-{
- /* 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"), argv0);
- printf("\n");
-
- /* TRANSLATORS: help output 2
- no-wrap */
- printf(_("Set color temperature of display according to time of day.\n"));
- printf("\n");
-
- /* TRANSLATORS: help output 3
- no-wrap */
- printf(_(" -h\t\tDisplay this help message\n"
- " -v\t\tVerbose output\n"
- " -V\t\tShow program version\n"));
- printf("\n");
-
- /* TRANSLATORS: help output 4
- `list' must not be translated
- no-wrap */
- printf(_(" -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"));
- printf("\n");
-
- /* TRANSLATORS: help output 5 */
- printf(_("The neutral temperature is %luK. Using this value will not change the color\n"
- "temperature of the display. Setting the color temperature to a value higher\n"
- "than this results in more blue light, and setting a lower value will result in\n"
- "more red light.\n"),
- NEUTRAL_TEMPERATURE);
-
- printf("\n");
-
- /* TRANSLATORS: help output 6 */
- printf(_("Default values:\n\n"
- " Daytime temperature: %luK\n"
- " Night temperature: %luK\n"),
- DEFAULT_DAY_TEMPERATURE, DEFAULT_NIGHT_TEMPERATURE);
-
- printf("\n");
-}
-
-/* Print list of adjustment methods. */
-static void
-print_method_list(void)
-{
- size_t i;
- fputs(_("Available adjustment methods:\n"), stdout);
- for (i = 0; gamma_methods[i]; 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(void)
-{
- size_t i;
- fputs(_("Available location providers:\n"), stdout);
- for (i = 0; location_providers[i]; 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. */
-GCC_ONLY(__attribute__((__pure__, __returns_nonnull__)))
-static const struct gamma_method *
-find_gamma_method(const char *name)
-{
- size_t i;
- for (i = 0; gamma_methods[i]; i++)
- if (!strcasecmp(name, gamma_methods[i]->name))
- return gamma_methods[i];
- /* TRANSLATORS: This refers to the method used to adjust colours e.g. VidMode */
- eprintf(_("Unknown adjustment method `%s'."), name);
-}
-
-/* Return location provider with the given name. */
-GCC_ONLY(__attribute__((__pure__, __returns_nonnull__)))
-static const struct location_provider *
-find_location_provider(const char *name)
-{
- size_t i;
- for (i = 0; location_providers[i]; i++)
- if (!strcasecmp(name, location_providers[i]->name))
- return location_providers[i];
- eprintf(_("Unknown location provider `%s'."), name);
-}
-
-/* Initialize options struct. */
-void
-options_init(struct options *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 = 0;
- options->scheme.day.gamma[0] = NAN;
- options->scheme.day.brightness = NAN;
-
- options->scheme.night.temperature = 0;
- options->scheme.night.gamma[0] = NAN;
- options->scheme.night.brightness = NAN;
-
- /* Temperature for manual mode */
- options->temp_set = 0;
-
- 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 command line arguments. */
-void
-options_parse_args(struct options *options, int argc, char *argv[])
-{
- const char *provider_name;
- char *s, *end, *value;
- int r;
-
- ARGBEGIN {
- case 'b':
- parse_brightness_string(ARG(), &options->scheme.day.brightness, &options->scheme.night.brightness);
- break;
-
- case 'c':
- free(options->config_filepath);
- options->config_filepath = estrdup(ARG());
- break;
-
- case 'g':
- r = parse_gamma_string(ARG(), options->scheme.day.gamma);
- if (r < 0) {
- weprintf(_("Malformed gamma argument."));
- eprintf(_("Try `-h' for more information."));
- }
- /* 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();
- exit(0);
-
- case 'l':
- value = ARG();
-
- /* Print list of providers if argument is `list' */
- if (!strcasecmp(value, "list")) {
- print_provider_list();
- exit(0);
- }
-
- 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;
- strtof(value, &end);
- if (!errno && *end == ':') {
- /* Use instead as arguments to `manual'. */
- provider_name = "manual";
- options->provider_args = value;
- } else {
- /* Split off provider arguments. */
- s = strchr(value, ':');
- if (s) {
- *s++ = '\0';
- options->provider_args = s;
- }
-
- provider_name = value;
- }
-
- /* Lookup provider from name. */
- options->provider = find_location_provider(provider_name);
-
- /* Print provider help if arg is `help'. */
- if (options->provider_args && !strcasecmp(options->provider_args, "help")) {
- options->provider->print_help(stdout);
- exit(0);
- }
- break;
-
- case 'm':
- value = ARG();
-
- /* Print list of methods if argument is `list' */
- if (!strcasecmp(value, "list")) {
- print_method_list();
- exit(0);
- }
-
- /* Split off method arguments. */
- s = strchr(value, ':');
- if (s) {
- *s++ = '\0';
- options->method_args = s;
- }
-
- /* Find adjustment method by name. */
- options->method = find_gamma_method(value);
-
- /* Print method help if arg is `help'. */
- if (options->method_args && !strcasecmp(options->method_args, "help")) {
- options->method->print_help(stdout);
- exit(0);
- }
- break;
-
- case 'o':
- options->mode = PROGRAM_MODE_ONE_SHOT;
- break;
-
- case 'O':
- options->mode = PROGRAM_MODE_MANUAL;
- options->temp_set = atoi(ARG());
- 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':
- value = ARG();
- s = strchr(value, ':');
- if (!s) {
- weprintf(_("Malformed temperature argument."));
- eprintf(_("Try `-h' for more information."));
- }
- *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(0);
- break;
-
- case 'x':
- options->mode = PROGRAM_MODE_RESET;
- break;
-
- default:
- usage();
- } ARGEND;
-
- if (argc)
- usage();
-}
-
-/* Parse a single key-value pair from the configuration file. */
-static void
-parse_config_file_option(const char *key, const char *value, struct options *options)
-{
- if (!strcasecmp(key, "temp-day")) {
- if (!options->scheme.day.temperature)
- options->scheme.day.temperature = atoi(value);
- } else if (!strcasecmp(key, "temp-night")) {
- if (!options->scheme.night.temperature)
- options->scheme.night.temperature = atoi(value);
- } else if (!strcasecmp(key, "transition") || !strcasecmp(key, "fade")) {
- /* "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")) {
- 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")) {
- if (isnan(options->scheme.day.brightness))
- options->scheme.day.brightness = atof(value);
- } else if (!strcasecmp(key, "brightness-night")) {
- if (isnan(options->scheme.night.brightness))
- options->scheme.night.brightness = atof(value);
- } else if (!strcasecmp(key, "elevation-high")) {
- options->scheme.high = atof(value);
- } else if (!strcasecmp(key, "elevation-low")) {
- options->scheme.low = atof(value);
- } else if (!strcasecmp(key, "gamma")) {
- if (isnan(options->scheme.day.gamma[0])) {
- if (parse_gamma_string(value, options->scheme.day.gamma) < 0)
- eprintf(_("Malformed gamma setting."));
- memcpy(options->scheme.night.gamma, options->scheme.day.gamma, sizeof(options->scheme.night.gamma));
- }
- } else if (!strcasecmp(key, "gamma-day")) {
- if (isnan(options->scheme.day.gamma[0])) {
- if (parse_gamma_string(value, options->scheme.day.gamma) < 0)
- eprintf(_("Malformed gamma setting."));
- }
- } else if (!strcasecmp(key, "gamma-night")) {
- if (isnan(options->scheme.night.gamma[0])) {
- if (parse_gamma_string(value, options->scheme.night.gamma) < 0)
- eprintf(_("Malformed gamma setting."));
- }
- } else if (!strcasecmp(key, "preserve-gamma")) {
- if (options->preserve_gamma == 1)
- options->preserve_gamma = !!atoi(value);
- } else if (!strcasecmp(key, "adjustment-method")) {
- if (!options->method)
- options->method = find_gamma_method(value);
- } else if (!strcasecmp(key, "location-provider")) {
- if (!options->provider)
- options->provider = find_location_provider(value);
- } else if (!strcasecmp(key, "dawn-time")) {
- if (options->scheme.dawn.start < 0) {
- if (parse_transition_range(value, &options->scheme.dawn) < 0)
- eprintf(_("Malformed dawn-time setting `%s'."), value);
- }
- } else if (!strcasecmp(key, "dusk-time")) {
- if (options->scheme.dusk.start < 0) {
- if (parse_transition_range(value, &options->scheme.dusk) < 0)
- eprintf(_("Malformed dusk-time setting `%s'."), value);
- }
- } else {
- weprintf(_("Unknown configuration setting `%s'."), key);
- }
-}
-
-/* Parse options defined in the config file. */
-void
-options_parse_config_file(struct options *options, struct config_ini_state *config_state)
-{
- struct config_ini_section *section;
- struct config_ini_setting *setting;
-
- /* Read global config settings. */
- section = config_ini_get_section(config_state, "redshift");
- if (!section)
- return;
-
- for (setting = section->settings; setting; setting = setting->next)
- parse_config_file_option(setting->name, setting->value, options);
-}
-
-/* Replace unspecified options with default values. */
-void
-options_set_defaults(struct options *options)
-{
- if (!options->scheme.day.temperature)
- options->scheme.day.temperature = DEFAULT_DAY_TEMPERATURE;
- if (!options->scheme.night.temperature)
- options->scheme.night.temperature = DEFAULT_NIGHT_TEMPERATURE;
-
- 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/pipeutils.c b/src/pipeutils.c
deleted file mode 100644
index 7f157c5..0000000
--- a/src/pipeutils.c
+++ /dev/null
@@ -1,83 +0,0 @@
-/* pipeutils.c -- Utilities for using pipes as signals
- * This file is part of redshift-ng.
- *
- * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
- */
-#include "common.h"
-
-
-int
-pipeutils_create_nonblocking(int pipefds[2])
-{
-#ifdef WINDOWS
- (void) pipefds;
- return -1;
-#else
-
- int i, flags;
-
-# if defined(__linux__) && !defined(MISSING_PIPE2)
- if (!pipe2(pipefds, O_NONBLOCK)) {
- return 0;
- } else if (errno != ENOSYS) {
- weprintf("pipe2 <buffer> O_NONBLOCK:");
- return -1;
- }
-# endif
-
- if (pipe(pipefds)) {
- weprintf("pipe:");
- return -1;
- }
-
- for (i = 0; i < 2; i++) {
- flags = fcntl(pipefds[0], F_GETFL);
- if (flags == -1) {
- weprintf("fcntl <pipe> F_GETFL:");
- goto fail;
- }
- if (fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK)) {
- weprintf("fcntl <pipe> F_SETFL +O_NONBLOCK:");
- goto fail;
- }
- }
-
- return 0;
-
-fail:
- close(pipefds[0]);
- close(pipefds[1]);
- 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/redshift.c b/src/redshift.c
index d8969af..9a44b0b 100644
--- a/src/redshift.c
+++ b/src/redshift.c
@@ -1,5 +1,7 @@
-/* redshift.c -- Main program source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2009-2017 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -43,42 +42,6 @@ int poll(struct pollfd *fds, int nfds, int timeout) { abort(); }
#define FADE_LENGTH 40
-const struct gamma_method *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
-};
-
-
-const struct location_provider *location_providers[] = {
-#ifdef ENABLE_GEOCLUE2
- &geoclue2_location_provider,
-#endif
-#ifdef ENABLE_CORELOCATION
- &corelocation_location_provider,
-#endif
- &manual_location_provider,
- NULL
-};
-
-
/* Names of periods of day */
static const char *period_names[] = {
/* TRANSLATORS: Name printed when period of day is unknown */
@@ -103,72 +66,47 @@ exact_eq(double a, double b)
#endif
-/* Determine which period we are currently in based on time offset. */
-static enum period
-get_period_from_time(const struct transition_scheme *transition, int time_offset)
-{
- 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;
-}
-
-/* Determine which period we are currently in based on solar elevation. */
-static enum period
-get_period_from_elevation(const struct transition_scheme *transition, double elevation)
-{
- if (elevation < transition->low)
- return PERIOD_NIGHT;
- else if (elevation < transition->high)
- return PERIOD_TRANSITION;
- else
- return PERIOD_DAYTIME;
-}
-
-/* Determine how far through the transition we are based on time offset. */
-static double
-get_transition_progress_from_time(const struct transition_scheme *transition, int time_offset)
+/**
+ * Suspend the process for a short time
+ *
+ * The process may be resumed earily, specifically
+ * if it receives a signal
+ *
+ * @param msecs The number of milliseconds to sleep
+ */
+static void
+millisleep(unsigned int msecs)
{
- 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;
+#ifdef WINDOWS
+ Sleep(msecs); /* TODO [Windows] not interruptible */
+#else
+ struct timespec ts;
+ ts.tv_sec = (time_t)(msecs / 1000U);
+ ts.tv_nsec = (long)(msecs % 1000U) * 1000000L;
+ nanosleep(&ts, NULL);
+#endif
}
-/* Determine how far through the transition we are based on elevation. */
-static double
-get_transition_progress_from_elevation(const struct transition_scheme *transition, double elevation)
-{
- if (elevation < transition->low)
- return 0.0;
- else if (elevation < transition->high)
- return (transition->low - elevation) / (transition->low - transition->high);
- else
- return 1.0;
-}
/* Return number of seconds since midnight from timestamp. */
-static int
-get_seconds_since_midnight(double timestamp)
+static time_t
+get_time_since_midnight(void)
{
- time_t t = (time_t)timestamp;
+ time_t t = time(NULL);
struct tm tm;
localtime_r(&t, &tm);
- return tm.tm_sec + tm.tm_min * 60 + tm.tm_hour * 3600;
+ t = (time_t)tm.tm_sec;
+ t += (time_t)tm.tm_min * 60;
+ t += (time_t)tm.tm_hour * 3600;
+ return t;
}
/* Print verbose description of the given period. */
static void
-print_period(enum period period, double transition)
+print_period(enum period period, double day_level)
{
if (period == PERIOD_TRANSITION)
- printf(_("Period: %s (%.2f%% day)\n"), gettext(period_names[period]), transition * 100);
+ printf(_("Period: %s (%.2f%% day)\n"), gettext(period_names[period]), day_level * 100);
else
printf(_("Period: %s\n"), gettext(period_names[period]));
}
@@ -207,13 +145,6 @@ interpolate_colour_settings(const struct colour_setting *first, const struct col
result->gamma[i] = (1.0 - alpha) * first->gamma[i] + alpha * second->gamma[i];
}
-/* Interpolate colour setting structs transition scheme. */
-static void
-interpolate_transition_scheme(const struct transition_scheme *transition, double alpha, struct colour_setting *result)
-{
- interpolate_colour_settings(&transition->night, &transition->day, alpha, result);
-}
-
/* Return 1 if colour settings have major differences, otherwise 0.
Used to determine if a fade should be applied in continual mode. */
static int
@@ -226,153 +157,69 @@ colour_setting_diff_is_major(const struct colour_setting *first, const struct co
fabs(first->gamma[2] - second->gamma[2]) > 0.1;
}
-
-static int
-provider_try_start(const struct location_provider *provider, LOCATION_STATE **state,
- struct config_ini_state *config, char *args)
-{
- const char *manual_keys[] = {"lat", "lon"};
- struct config_ini_section *section;
- struct config_ini_setting *setting;
- char *next_arg, *value;
- const char *key;
- int i;
-
- if (provider->init(state) < 0) {
- weprintf(_("Initialization of %s failed."), provider->name);
- return -1;
- }
-
- /* Set provider options from config file. */
- if ((section = config_ini_get_section(config, provider->name))) {
- for (setting = section->settings; setting; setting = setting->next) {
- if (provider->set_option(*state, setting->name, setting->value) < 0) {
- provider->free(*state);
- weprintf(_("Failed to set %s option."), provider->name);
- /* TRANSLATORS: `help' must not be translated. */
- weprintf(_("Try `-l %s:help' for more information."), provider->name);
- return -1;
- }
- }
- }
-
- /* Set provider options from command line. */
- for (i = 0; args; i++) {
- next_arg = strchr(args, ':');
- if (next_arg)
- *next_arg++ = '\0';
-
- key = args;
- value = strchr(args, '=');
- if (!value) {
- /* The options for the "manual" method can be set
- without keys on the command line for convencience
- and for backwards compatability. We add the proper
- keys here before calling set_option(). */
- if (!strcmp(provider->name, "manual") && i < ELEMSOF(manual_keys)) {
- key = manual_keys[i];
- value = args;
- } else {
- weprintf(_("Failed to parse option `%s'."), args);
- return -1;
- }
- } else {
- *value++ = '\0';
- }
-
- if (provider->set_option(*state, key, value) < 0) {
- provider->free(*state);
- weprintf(_("Failed to set %s option."), provider->name);
- /* TRANSLATORS: `help' must not be translated. */
- weprintf(_("Try `-l %s:help' for more information."), provider->name);
- return -1;
- }
-
- args = next_arg;
- }
-
- /* Start provider. */
- if (provider->start(*state) < 0) {
- provider->free(*state);
- weprintf(_("Failed to start provider %s.\n"), provider->name);
- return -1;
- }
-
- return 0;
-}
-
-static int
-method_try_start(const struct gamma_method *method, GAMMA_STATE **state,
- enum program_mode mode, struct config_ini_state *config, char *args)
+/**
+ * Get the current period of day and the colour settings
+ * applicable to the current time of the day
+ *
+ * @param location Geographical user location
+ * @param colour_out Output parameter for the colour settings
+ * @param period_out Output parameter for the period of the day
+ * @param day_level_out Output parameter for the dayness level
+ * @param verbose Whether the application is running in verbose mode
+ */
+static void
+get_colour_settings(const struct location *location, struct colour_setting *colour_out,
+ enum period *period_out, double *day_level_out, int verbose)
{
- struct config_ini_section *section;
- struct config_ini_setting *setting;
- char *next_arg, *value;
- const char *key;
-
- if (method->init(state) < 0) {
- weprintf(_("Initialization of %s failed."), method->name);
- return -1;
- }
-
- /* Set method options from config file. */
- if ((section = config_ini_get_section(config, method->name))) {
- for (setting = section->settings; setting; setting = setting->next) {
- if (method->set_option(*state, setting->name, setting->value) < 0) {
- method->free(*state);
- weprintf(_("Failed to set %s option."), method->name);
- /* TRANSLATORS: `help' must not be translated. */
- weprintf(_("Try `-m %s:help' for more information.\n"), method->name); /* TODO \n */
- return -1;
- }
+ time_t time_offset;
+ double t, elevation;
+
+ /* Get dayness level */
+ if (scheme.type == CLOCK_SCHEME) {
+ time_offset = get_time_since_midnight();
+ while (time_offset >= scheme.time.periods->next->start)
+ scheme.time.periods = scheme.time.periods->next;
+ time_offset -= scheme.time.periods->start;
+ if (time_offset < 0)
+ time_offset += ONE_DAY;
+ t = (double)time_offset;
+ *day_level_out = fma(t, scheme.time.periods->diff_over_duration, scheme.time.periods->day_level);
+
+ } else if (scheme.type == SOLAR_SCHEME) {
+ if (libred_solar_elevation(location->latitude, location->longitude, &elevation))
+ eprintf("libred_solar_elevation:");
+ if (verbose) {
+ /* TRANSLATORS: Append degree symbol if possible. */
+ printf(_("Solar elevation: %f\n"), elevation);
}
+ *day_level_out = (elevation - scheme.elevation.low) / scheme.elevation.range;
+
+ } else {
+ /* Static scheme, no dayness-level or peroid; day_settings == nigh_settings, use either */
+ *day_level_out = NAN;
+ *period_out = PERIOD_NONE;
+ *colour_out = day_settings;
+ return;
}
- /* Set method options from command line. */
- while (args) {
- next_arg = strchr(args, ':');
- if (next_arg)
- *next_arg++ = '\0';
-
- key = args;
- value = strchr(args, '=');
- if (!value) {
- weprintf(_("Failed to parse option `%s'."), args);
- return -1;
- }
- *value++ = '\0';
-
- if (method->set_option(*state, key, value) < 0) {
- method->free(*state);
- weprintf(_("Failed to set %s option."), method->name);
- /* TRANSLATORS: `help' must not be translated. */
- weprintf(_("Try -m %s:help' for more information.\n"), method->name); /* TODO missing ` and \n */
- return -1;
- }
+ /* Clamp dayness level and get colour */
+ if (*day_level_out <= 0.0) {
+ *day_level_out = 0.0;
+ *period_out = PERIOD_NIGHT;
+ *colour_out = night_settings;
- args = next_arg;
- }
+ } else if (*day_level_out >= 1.0) {
+ *day_level_out = 1.0;
+ *period_out = PERIOD_DAYTIME;
+ *colour_out = day_settings;
- /* Start method. */
- if (method->start(*state, mode) < 0) {
- method->free(*state);
- weprintf(_("Failed to start adjustment method %s.\n"), method->name); /* TODO \n */
- return -1;
+ } else {
+ *period_out = PERIOD_TRANSITION;
+ interpolate_colour_settings(&night_settings, &day_settings, *day_level_out, colour_out);
}
-
- return 0;
}
-/* Check whether gamma is within allowed levels. */
-static int
-gamma_is_valid(const double gamma[3])
-{
- return WITHIN(MIN_GAMMA, gamma[0], MAX_GAMMA) &&
- WITHIN(MIN_GAMMA, gamma[1], MAX_GAMMA) &&
- WITHIN(MIN_GAMMA, gamma[2], MAX_GAMMA);
-}
-
/* Check whether location is valid.
Prints error message on stderr and returns 0 if invalid, otherwise
returns 1. */
@@ -381,62 +228,17 @@ location_is_valid(const struct location *location)
{
if (!WITHIN(MIN_LATITUDE, location->latitude, MAX_LATITUDE)) {
/* TRANSLATORS: Append degree symbols if possible. */
- weprintf(_("Latitude must be between %.1f and %.1f.\n"), MIN_LATITUDE, MAX_LATITUDE); /* TODO \n */
+ weprintf(_("Latitude must be between %.1f and %.1f."), MIN_LATITUDE, MAX_LATITUDE);
return 0;
}
if (!WITHIN(MIN_LONGITUDE, location->longitude, MAX_LONGITUDE)) {
/* TRANSLATORS: Append degree symbols if possible. */
- weprintf(_("Longitude must be between %.1f and %.1f.\n"), MIN_LONGITUDE, MAX_LONGITUDE); /* TODO \n */
+ weprintf(_("Longitude must be between %.1f and %.1f."), MIN_LONGITUDE, MAX_LONGITUDE);
return 0;
}
return 1;
}
-/* 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 struct location_provider *provider, LOCATION_STATE *state, int timeout, struct location *loc)
-{
- int r, available, loc_fd;
- struct pollfd pollfds[1];
- double now, later;
-
- do {
- loc_fd = provider->get_fd(state);
- if (loc_fd >= 0) {
- /* Provider is dynamic. */
- /* TODO: This should use a monotonic time source. */
- now = systemtime_get_time();
-
- /* Poll on file descriptor until ready. */
- pollfds[0].fd = loc_fd;
- pollfds[0].events = POLLIN;
- r = poll(pollfds, 1, timeout);
- if (r < 0) {
- weprintf("poll {{.fd=<location provider>, .events=EPOLLIN}} 1 %i:", timeout);
- return -1;
- } else if (r == 0) {
- return 0;
- }
-
- later = systemtime_get_time();
-
- /* Adjust timeout by elapsed time */
- if (timeout >= 0) {
- timeout -= (later - now) * 1000;
- timeout = MAX(timeout, 0);
- }
- }
-
- if (provider->handle(state, loc, &available) < 0)
- return -1;
- } while (!available);
-
- return 1;
-}
-
/* Easing function for fade.
See https://github.com/mietek/ease-tween */
static double
@@ -455,8 +257,8 @@ ease_fade(double t)
colour temperature. */
static void
run_continual_mode(const struct location_provider *provider, LOCATION_STATE *location_state,
- const struct transition_scheme *scheme, const struct gamma_method *method,
- GAMMA_STATE *method_state, int use_fade, int preserve_gamma, int verbose)
+ const struct gamma_method *method, GAMMA_STATE *method_state, int use_fade,
+ int preserve_gamma, int verbose)
{
int done = 0;
int prev_disabled = 1;
@@ -466,7 +268,6 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
struct colour_setting prev_target_interp;
struct colour_setting interp;
struct location loc;
- int need_location;
/* Short fade parameters */
int fade_length = 0;
@@ -476,7 +277,7 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
the values did not change. */
enum period prev_period = PERIOD_NONE;
- signals_install_handlers();
+ install_signal_handlers();
/* Previous target colour setting and current actual colour setting.
Actual colour setting takes into account the current colour fade. */
@@ -485,16 +286,15 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
interp = COLOUR_SETTING_NEUTRAL;
loc = (struct location){NAN, NAN};
- need_location = !scheme->use_time;
- if (need_location) {
- weprintf(_("Waiting for initial location to become available...\n")); /* TODO \n */
+ if (scheme.type == SOLAR_SCHEME) {
+ weprintf(_("Waiting for initial location to become available..."));
/* Get initial location from provider */
- if (provider_get_location(provider, location_state, -1, &loc) < 0)
+ if (get_location(provider, location_state, -1, &loc) < 0)
eprintf(_("Unable to get location from provider."));
if (!location_is_valid(&loc))
- eprintf(_("Invalid location returned from provider.\n")); /* TODO \n */
+ eprintf(_("Invalid location returned from provider."));
print_location(&loc);
}
@@ -506,9 +306,8 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
/* Continuously adjust colour temperature */
for (;;) {
- double now;
enum period period;
- double transition_prog;
+ double day_level;
struct colour_setting target_interp;
int delay, loc_fd;
@@ -533,26 +332,7 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
prev_disabled = disabled;
- /* Read timestamp */
- now = systemtime_get_time();
-
- 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;
- if (libred_solar_elevation(loc.latitude, loc.longitude, &elevation))
- eprintf("libred_solar_elevation:");
-
- period = get_period_from_elevation(scheme, elevation);
- transition_prog = get_transition_progress_from_elevation(scheme, elevation);
- }
-
- /* Use transition progress to get target colour temperature. */
- interpolate_transition_scheme(scheme, transition_prog, &target_interp);
+ get_colour_settings(&loc, &target_interp, &period, &day_level, verbose);
if (disabled) {
period = PERIOD_NONE;
@@ -567,21 +347,18 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
print the progress, so we always print it in
that case. */
if (verbose && (period != prev_period || period == PERIOD_TRANSITION))
- print_period(period, transition_prog);
+ print_period(period, day_level);
/* Activate hooks if period changed */
if (period != prev_period)
- hooks_signal_period_change(prev_period, period);
+ run_period_change_hooks(prev_period, period);
/* Start fade if the parameter differences are too big to apply
instantly. */
- if (use_fade) {
- if (fade_length ? colour_setting_diff_is_major(&target_interp, &prev_target_interp)
- : colour_setting_diff_is_major(&interp, &target_interp)) {
- fade_length = FADE_LENGTH;
- fade_time = 0;
- fade_start_interp = interp;
- }
+ if (use_fade && colour_setting_diff_is_major(&target_interp, fade_length ? &prev_target_interp : &interp)) {
+ fade_length = FADE_LENGTH;
+ fade_time = 0;
+ fade_start_interp = interp;
}
/* Handle ongoing fade */
@@ -622,7 +399,7 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
delay = fade_length ? SLEEP_DURATION_SHORT : SLEEP_DURATION;
/* Update location. */
- loc_fd = need_location ? provider->get_fd(location_state) : -1;
+ loc_fd = scheme.type == SOLAR_SCHEME ? provider->get_fd(location_state) : -1;
if (loc_fd >= 0) {
struct pollfd pollfds[1];
@@ -634,22 +411,23 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
pollfds[0].events = POLLIN;
r = poll(pollfds, 1, delay);
if (r < 0) {
+#ifndef WINDOWS
if (errno == EINTR)
continue;
+#endif
weprintf("poll:");
eprintf(_("Unable to get location from provider."));
} else if (!r) {
continue;
}
- /* Get new location and availability
- information. */
+ /* Get new location and availability information. */
if (provider->handle(location_state, &new_loc, &new_available) < 0)
eprintf(_("Unable to get location from provider."));
if (!new_available && new_available != location_available) {
- weprintf(_("Location is temporarily unavailable; Using previous" /* TODO captial U efter ; and \n*/
- " location until it becomes available...\n"));
+ weprintf(_("Location is temporarily unavailable; using previous"
+ " location until it becomes available..."));
}
if (new_available &&
@@ -665,7 +443,7 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
if (!location_is_valid(&loc))
eprintf(_("Invalid location returned from provider."));
} else {
- systemtime_msleep(delay);
+ millisleep(delay);
}
}
@@ -677,15 +455,11 @@ run_continual_mode(const struct location_provider *provider, LOCATION_STATE *loc
int
main(int argc, char *argv[])
{
- struct options options;
- struct config_ini_state config_state;
- struct transition_scheme *scheme;
+ struct settings settings;
GAMMA_STATE *method_state;
LOCATION_STATE *location_state;
- int need_location;
- size_t i;
- struct location loc = { NAN, NAN };
- double now, transition_prog;
+ struct location loc = {NAN, NAN};
+ double day_level;
enum period period;
struct colour_setting colour;
@@ -698,167 +472,32 @@ main(int argc, char *argv[])
textdomain(PACKAGE);
#endif
- options_init(&options);
- options_parse_args(&options, argc, argv);
-
- /* Load settings from config file. */
- config_ini_init(&config_state, options.config_filepath);
-
- free(options.config_filepath);
-
- options_parse_config_file(&options, &config_state);
-
- options_set_defaults(&options);
-
- 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)
- eprintf(_("Partial time-configuration not supported!"));
-
- 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)
- eprintf(_("Invalid dawn/dusk time configuration!"));
-
- options.scheme.use_time = 1;
- }
+ load_settings(&settings, argc, argv);
/* Initialize location provider if needed. If provider is NULL
try all providers until one that works is found. */
/* Location is not needed for reset mode and manual mode. */
- need_location = options.mode != PROGRAM_MODE_RESET &&
- options.mode != PROGRAM_MODE_MANUAL &&
- !options.scheme.use_time;
- if (need_location) {
- if (options.provider) {
- /* Use provider specified on command line. */
- if (provider_try_start(options.provider, &location_state, &config_state, options.provider_args) < 0)
- exit(1);
- } else {
- /* Try all providers, use the first that works. */
- for (i = 0; location_providers[i]; i++) {
- const struct location_provider *p = location_providers[i];
- weprintf(_("Trying location provider `%s'..."), p->name);
- if (provider_try_start(p, &location_state, &config_state, NULL) < 0) {
- weprintf(_("Trying next provider..."));
- continue;
- }
-
- /* Found provider that works. */
- printf(_("Using provider `%s'.\n"), p->name);
- options.provider = p;
- break;
- }
-
- /* Failure if no providers were successful at this point. */
- if (!options.provider)
- eprintf(_("No more location providers to try."));
- }
-
- /* Solar elevations */
- if (options.scheme.high < options.scheme.low) {
- eprintf(_("High transition elevation cannot be lower than the low transition elevation."));
- }
-
- if (options.verbose) {
- /* TRANSLATORS: Append degree symbols if possible. */
- printf(_("Solar elevations: day above %.1f, night below %.1f\n"),
- options.scheme.high, options.scheme.low);
- }
- }
-
- if (options.mode != PROGRAM_MODE_RESET &&
- options.mode != PROGRAM_MODE_MANUAL) {
- if (options.verbose) {
- printf(_("Temperatures: %luK at day, %luK at night\n"),
- options.scheme.day.temperature, options.scheme.night.temperature);
- }
-
- /* Colour temperature */
- if (!WITHIN(MIN_TEMPERATURE, options.scheme.day.temperature, MAX_TEMPERATURE) ||
- !WITHIN(MIN_TEMPERATURE, options.scheme.night.temperature, MAX_TEMPERATURE))
- eprintf(_("Temperature must be between %luK and %luK."), MIN_TEMPERATURE, MAX_TEMPERATURE);
- }
-
- if (options.mode == PROGRAM_MODE_MANUAL) {
- /* Check colour temperature to be set */
- if (!WITHIN(MIN_TEMPERATURE, options.temp_set, MAX_TEMPERATURE))
- eprintf(_("Temperature must be between %luK and %luK."), MIN_TEMPERATURE, MAX_TEMPERATURE);
- }
-
- /* Brightness */
- if (!WITHIN(MIN_BRIGHTNESS, options.scheme.day.brightness, MAX_BRIGHTNESS) ||
- !WITHIN(MIN_BRIGHTNESS, options.scheme.night.brightness, MAX_BRIGHTNESS))
- eprintf(_("Brightness values must be between %.1f and %.1f."), MIN_BRIGHTNESS, MAX_BRIGHTNESS);
-
- if (options.verbose)
- printf(_("Brightness: %.2f:%.2f\n"), options.scheme.day.brightness, options.scheme.night.brightness);
-
- /* Gamma */
- if (!gamma_is_valid(options.scheme.day.gamma) ||
- !gamma_is_valid(options.scheme.night.gamma))
- eprintf(_("Gamma value must be between %.1f and %.1f."), MIN_GAMMA, MAX_GAMMA);
-
- if (options.verbose) {
- /* TRANSLATORS: The string in parenthesis is either
- Daytime or Night (translated). */
- printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"),
- _("Daytime"), options.scheme.day.gamma[0],
- options.scheme.day.gamma[1],
- options.scheme.day.gamma[2]);
- printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"),
- _("Night"), options.scheme.night.gamma[0],
- options.scheme.night.gamma[1],
- options.scheme.night.gamma[2]);
- }
-
- scheme = &options.scheme;
+ if (settings.scheme_type == SOLAR_SCHEME)
+ acquire_location_provider(&settings, &location_state);
/* Initialize gamma adjustment method. If method is NULL
try all methods until one that works is found. */
/* Gamma adjustment not needed for print mode */
- if (options.mode != PROGRAM_MODE_PRINT) {
- if (options.method) {
- /* Use method specified on command line. */
- if (method_try_start(options.method, &method_state, options.mode, &config_state, options.method_args) < 0)
- exit(1);
- } else {
- /* Try all methods, use the first that works. */
- for (i = 0; gamma_methods[i]; i++) {
- const struct gamma_method *m = gamma_methods[i];
- if (!m->autostart)
- continue;
+ if (settings.mode != PROGRAM_MODE_PRINT)
+ acquire_adjustment_method(&settings, &method_state);
- if (method_try_start(m, &method_state, options.mode, &config_state, NULL) < 0) {
- weprintf(_("Trying next method..."));
- continue;
- }
-
- /* Found method that works. */
- printf(_("Using method `%s'.\n"), m->name);
- options.method = m;
- break;
- }
+ config_ini_free(&settings.config);
- /* Failure if no methods were successful at this point. */
- if (!options.method)
- eprintf(_("No more methods to try."));
- }
- }
-
- config_ini_free(&config_state);
-
- switch (options.mode) {
+ switch (settings.mode) {
case PROGRAM_MODE_ONE_SHOT:
case PROGRAM_MODE_PRINT:
- if (need_location) {
+ if (settings.scheme_type == SOLAR_SCHEME) {
weprintf(_("Waiting for current location to become available..."));
/* Wait for location provider. */
- if (provider_get_location(options.provider, location_state, -1, &loc) < 0)
+ if (get_location(settings.provider, location_state, -1, &loc) < 0)
eprintf(_("Unable to get location from provider."));
if (!location_is_valid(&loc))
@@ -867,46 +506,26 @@ main(int argc, char *argv[])
print_location(&loc);
}
- now = systemtime_get_time();
-
- 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;
- if (libred_solar_elevation(loc.latitude, loc.longitude, &elevation))
- eprintf("libred_solar_elevation:");
- if (options.verbose) {
- /* TRANSLATORS: Append degree symbol if possible. */
- printf(_("Solar elevation: %f\n"), elevation);
- }
+ get_colour_settings(&loc, &colour, &period, &day_level, settings.verbose);
- period = get_period_from_elevation(scheme, elevation);
- transition_prog = get_transition_progress_from_elevation(scheme, elevation);
- }
-
- /* Use transition progress to set colour temperature */
- interpolate_transition_scheme(scheme, transition_prog, &colour);
-
- if (options.verbose || options.mode == PROGRAM_MODE_PRINT) {
- print_period(period, transition_prog);
+ if (settings.verbose || settings.mode == PROGRAM_MODE_PRINT) {
+ if (settings.scheme_type != STATIC_SCHEME)
+ print_period(period, day_level);
printf(_("Color temperature: %luK\n"), colour.temperature);
printf(_("Brightness: %.2f\n"), colour.brightness);
}
- if (options.mode == PROGRAM_MODE_PRINT)
+ if (settings.mode == PROGRAM_MODE_PRINT)
break;
apply:
- if (options.method->set_temperature(method_state, &colour, options.preserve_gamma) < 0)
+ if (settings.method->set_temperature(method_state, &colour, settings.preserve_gamma.value) < 0)
eprintf(_("Temperature adjustment failed."));
#ifndef WINDOWS
/* 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(options.method->name, "quartz")) {
+ if (!strcmp(settings.method->name, "quartz")) {
weprintf(_("Press ctrl-c to stop..."));
while (!exiting)
pause();
@@ -915,26 +534,26 @@ main(int argc, char *argv[])
break;
case PROGRAM_MODE_MANUAL:
- if (options.verbose)
- printf(_("Color temperature: %luK\n"), options.temp_set);
- colour = scheme->day;
- colour.temperature = options.temp_set;
+ /* TODO interpolate for current time if a value range has been specified */
+ colour = day_settings;
+ if (settings.verbose)
+ printf(_("Color temperature: %luK\n"), colour.temperature);
goto apply;
case PROGRAM_MODE_RESET:
colour = COLOUR_SETTING_NEUTRAL;
- options.preserve_gamma = 0;
+ settings.preserve_gamma.value = 0;
goto apply;
case PROGRAM_MODE_CONTINUAL:
- run_continual_mode(options.provider, location_state, scheme, options.method, method_state,
- options.use_fade, options.preserve_gamma, options.verbose);
+ run_continual_mode(settings.provider, location_state, settings.method, method_state,
+ settings.use_fade.value, settings.preserve_gamma.value, settings.verbose);
break;
}
- if (options.mode != PROGRAM_MODE_PRINT)
- options.method->free(method_state);
- if (need_location)
- options.provider->free(location_state);
+ if (settings.mode != PROGRAM_MODE_PRINT)
+ settings.method->free(method_state);
+ if (scheme.type == SOLAR_SCHEME)
+ settings.provider->free(location_state);
return 0;
}
diff --git a/src/signals.c b/src/signals.c
index dec5626..ca88425 100644
--- a/src/signals.c
+++ b/src/signals.c
@@ -1,5 +1,7 @@
-/* signals.c -- Signal processing source
- * This file is part of redshift-ng.
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
*
* redshift-ng is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,9 +15,6 @@
*
* You should have received a copy of the GNU General Public License
* along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2009-2015 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2015, 2025 Mattias Andrée <m@maandree.se>
*/
#include "common.h"
@@ -56,7 +55,7 @@ sigdisable(int signo)
void
-signals_install_handlers(void)
+install_signal_handlers(void)
{
#ifdef WINDOWS
if (signal(SIGINT, &sigexit) == SIG_ERR)
diff --git a/src/systemtime.c b/src/systemtime.c
deleted file mode 100644
index 17f516b..0000000
--- a/src/systemtime.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/* systemtime.c -- Portable system time source
- * This file is part of redshift-ng.
- *
- * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com>
- * Copyright (c) 2025 Mattias Andrée <m@maandree.se>
- */
-#include "common.h"
-
-
-double
-systemtime_get_time(void)
-{
-#if defined(WINDOWS)
- FILETIME now;
- ULARGE_INTEGER i;
- GetSystemTimeAsFileTime(&now);
- i.LowPart = now.dwLowDateTime;
- i.HighPart = now.dwHighDateTime;
- /* FILETIME is tenths of microseconds since 1601-01-01 UTC */
- return (i.QuadPart / 10000000.0) - 11644473600.0;
-
-#elif defined(_POSIX_TIMERS)
- struct timespec now;
- if (clock_gettime(CLOCK_REALTIME, &now))
- eprintf("clock_gettime CLOCK_REALTIME:");
- return now.tv_sec + (now.tv_nsec / 1000000000.0);
-
-#else
- struct timeval now;
- if (gettimeofday(&now, NULL))
- eprintf("gettimeofday:");
- return now.tv_sec + (now.tv_usec / 1000000.0);
-#endif
-}
-
-
-void
-systemtime_msleep(unsigned int msecs)
-{
-#ifdef WINDOWS
- Sleep(msecs);
-#else
- struct timespec sleep;
- sleep.tv_sec = (time_t)(msecs / 1000U);
- sleep.tv_nsec = (long)(msecs % 1000U) * 1000000L;
- nanosleep(&sleep, NULL);
-#endif
-}
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..fc6dc2c
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,232 @@
+/* redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
+ *
+ * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "common.h"
+
+
+char *
+rtrim(char *s, char *end)
+{
+ end = end ? end : strchr(s, '\0');
+ while (end != s && (end[-1] == ' ' || end[-1] == '\t'))
+ end--;
+ *end = '\0';
+ return s;
+}
+
+
+char *
+ltrim(char *s)
+{
+ while (*s == ' ' || *s == '\t')
+ s++;
+ return s;
+}
+
+
+const char *
+get_home(void)
+{
+#ifdef WINDOWS
+ return NULL;
+#else
+ static const char *home = NULL;
+ struct passwd *pw;
+ if (!home) {
+ pw = getpwuid(getuid());
+ if (pw) {
+ home = pw->pw_dir;
+ if (home && *home)
+ return home;
+ weprintf(_("Cannot determine your home directory, "
+ "it is from the system's user table."));
+ } else if (errno) {
+ weprintf("getpwuid:");
+ } else {
+ weprintf(_("Cannot determine your home directory, your"
+ " user ID is missing from the system's user table."));
+ /* `errno` can either be set to any number of error codes,
+ * or be zero if the user does not have a passwd entry */
+ }
+ home = "";
+ }
+ return home;
+#endif
+}
+
+
+/**
+ * Search for a file and open it in some manner
+ *
+ * @param path_spec Specification for the path to try
+ * @param path_out Output parameter for the found file
+ * @param pathbuf_out Output parameter for the memory allocation for `*path_out`;
+ * shall be free(3)d by the caller
+ * @param open_cb Pointer to function used to open the file, unless it
+ * returns `NULL` it's return value is returned by the
+ * function (`try_path`); `NULL` shall be returned if the
+ * file `path` does not exist
+ * @return File access object to the found file; `NULL` if not found
+ */
+static void *
+try_path(const struct env_path *path_spec, const char **path_out, char **pathbuf_out, void *(*open_cb)(const char *path))
+{
+ const char *prefix, *p, *q;
+ char *path;
+ size_t len;
+ void *f = NULL;
+
+ *path_out = NULL;
+ *pathbuf_out = NULL;
+
+ if (!path_spec->prefix_env) {
+ prefix = get_home();
+ } else if (*path_spec->prefix_env) {
+ prefix = getenv(path_spec->prefix_env);
+ } else {
+ f = (*open_cb)(path_spec->suffix);
+ if (f)
+ *path_out = path_spec->suffix;
+ return f;
+ }
+ if (!prefix || !*prefix)
+ return NULL;
+
+ path = emalloc(strlen(prefix) + strlen(path_spec->suffix) + 1U);
+
+ if (path_spec->multidir_env) {
+ for (p = prefix; !f && *p; p = &q[!!*q]) {
+#ifdef strchrnul
+ q = strchrnul(p, PATH_DELIMITER);
+#else
+ q = strchr(p, PATH_DELIMITER);
+ q = q ? q : strchr(p, '\0');
+#endif
+ len = (size_t)(q - p);
+ if (!len)
+ continue;
+
+ memcpy(path, p, len);
+ stpcpy(&path[len], path_spec->suffix);
+ f = (*open_cb)(path);
+ }
+ } else {
+ stpcpy(stpcpy(path, prefix), path_spec->suffix);
+ f = (*open_cb)(path);
+ }
+
+ if (f)
+ *path_out = *pathbuf_out = path;
+ else
+ free(path);
+ return f;
+}
+
+
+/**
+ * Open a file for reading, if it exists
+ *
+ * @param path The path to the file
+ * @return `FILE` object for reading the file,
+ * `NULL` if it doesn't exist
+ */
+static void *
+open_file(const char *path)
+{
+ FILE *f = fopen(path, "r");
+ if (!f && errno != ENOENT)
+ eprintf("fopen %s \"r\":", path);
+ return f;
+}
+
+
+FILE *
+try_path_fopen(const struct env_path *path_spec, const char **path_out, char **pathbuf_out)
+{
+ return try_path(path_spec, path_out, pathbuf_out, &open_file);
+}
+
+
+/**
+ * Open a directory for reading, if it exists
+ *
+ * @param path The path to the directory
+ * @return `DIR` object for reading the directory,
+ * `NULL` if it doesn't exist
+ */
+static void *
+open_dir(const char *path)
+{
+ DIR *f = opendir(path);
+ if (!f && errno != ENOENT)
+ eprintf("opendir %s:", path);
+ return f;
+}
+
+
+DIR *
+try_path_opendir(const struct env_path *path_spec, const char **path_out, char **pathbuf_out)
+{
+ return try_path(path_spec, path_out, pathbuf_out, &open_dir);
+}
+
+
+int
+pipe_nonblock(int pipefds[2])
+{
+#ifdef WINDOWS
+ (void) pipefds;
+ return -1;
+#else
+
+ int i, flags;
+
+# if defined(__linux__) && !defined(MISSING_PIPE2)
+ if (!pipe2(pipefds, O_NONBLOCK)) {
+ return 0;
+ } else if (errno != ENOSYS) {
+ weprintf("pipe2 <buffer> O_NONBLOCK:");
+ return -1;
+ }
+# endif
+
+ if (pipe(pipefds)) {
+ weprintf("pipe:");
+ return -1;
+ }
+
+ for (i = 0; i < 2; i++) {
+ flags = fcntl(pipefds[0], F_GETFL);
+ if (flags == -1) {
+ weprintf("fcntl <pipe> F_GETFL:");
+ goto fail;
+ }
+ if (fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK)) {
+ weprintf("fcntl <pipe> F_SETFL +O_NONBLOCK:");
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return -1;
+#endif
+}