aboutsummaryrefslogtreecommitdiffstats
path: root/redshift/hooks.c
diff options
context:
space:
mode:
Diffstat (limited to 'redshift/hooks.c')
-rw-r--r--redshift/hooks.c249
1 files changed, 249 insertions, 0 deletions
diff --git a/redshift/hooks.c b/redshift/hooks.c
new file mode 100644
index 0000000..96f6f83
--- /dev/null
+++ b/redshift/hooks.c
@@ -0,0 +1,249 @@
+/*-
+ * 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"
+
+
+/**
+ * Names of periods supplied to scripts
+ */
+static const char *period_names[] = {
+ [PERIOD_NONE] = "none",
+ [PERIOD_DAYTIME] = "daytime",
+ [PERIOD_NIGHT] = "night",
+ [PERIOD_TRANSITION] = "transition"
+};
+
+
+/**
+ * Path name of the hook directory, `NULL` if not found
+ */
+static char *dirpath = NULL;
+
+/**
+ * The allocation size of `dirpath`
+ */
+static size_t dirpathsize;
+
+/**
+ * The length of the string in `dirpath`
+ */
+static size_t dirpathlen;
+
+
+/**
+ * Paths, in order of priority, to test when looking for
+ * the hook directory for redshift
+ */
+static const struct env_path paths[] = {
+ {0, "XDG_CONFIG_HOME", "/redshift-ng/hooks"},
+ {0, "XDG_CONFIG_HOME", "/redshift/hooks"},
+#if defined(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"},
+#if !defined(WINDOWS)
+ {0, "", "/etc/redshift-ng/hooks"},
+ {0, "", "/etc/redshift/hooks"}
+#endif
+};
+
+
+/**
+ * Deallocates `dirpath`
+ */
+static void
+cleanup(void)
+{
+ free(dirpath);
+ dirpath = NULL;
+}
+
+
+/**
+ * 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 hook file
+ *
+ * @param path The path to the hook file
+ * @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_hook(const char *path, const char *argv[])
+{
+#ifdef WINDOWS
+ /* TODO [Windows] hooks are not support on Windows */
+#else
+ switch (fork()) {
+ case -1:
+ weprintf("fork:");
+ break;
+
+ case 0:
+ if (dup2(STDOUT_FILENO, STDERR_FILENO) != STDERR_FILENO) {
+ weprintf("dup2 <stdout> <stderr>:");
+ _exit(1);
+ }
+ argv[0] = path;
+ execv(path, (const void *)argv);
+ if (errno != EACCES)
+ weprintf("execv %s:", path);
+ _exit(1);
+
+ default:
+ /* SIGCHLD is ignored */
+ break;
+ }
+#endif
+}
+
+
+/**
+ * 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[])
+{
+ static int looked_up_dir = 0;
+ static int hook_file_is_regular = 0;
+ static int hook_file_checked = 0;
+
+ DIR *dir;
+ struct dirent *f;
+ size_t required;
+ const char *dirpath_static;
+
+ if (hook_file_is_regular) {
+ goto run_hook_file;
+
+ } else if (hook_file) {
+ if (!hook_file_checked) {
+ hook_file_checked = 1;
+ if (!strcmp(hook_file, "/dev/null") ||
+ !strcmp(hook_file, "/var/empty") ||
+ !strcmp(hook_file, "/var/empty/"))
+ goto no_hooks;
+ }
+
+ dir = opendir(hook_file);
+ if (!dir) {
+ if (errno == ENOTDIR) {
+ hook_file_is_regular = 1;
+ run_hook_file:
+ run_hook(hook_file, argv);
+ return;
+ }
+ weprintf("opendir %s:", hook_file);
+ no_hooks:
+ free(hook_file);
+ hook_file = NULL;
+ looked_up_dir = 1;
+ dirpath = NULL;
+ return;
+ }
+
+ } else 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 ((errno = 0, f = readdir(dir))) {
+ if (f->d_name[0] == '.' || !f->d_name[0] || strchr(f->d_name, '\0')[-1] == '~')
+ continue;
+
+ required = dirpathlen + sizeof("/") + strlen(f->d_name);
+ if (required > dirpathsize)
+ dirpath = erealloc(dirpath, dirpathsize = required);
+ stpcpy(stpcpy(&dirpath[dirpathlen], "/"), f->d_name);
+
+ run_hook(dirpath, argv);
+
+ dirpath[dirpathlen] = '\0';
+ }
+
+ 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);
+}