aboutsummaryrefslogtreecommitdiffstats
path: root/redshift/gamma-coopgamma.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-03-27 18:36:26 +0100
committerMattias Andrée <m@maandree.se>2025-03-27 18:36:26 +0100
commit037b945a9f253b97faffc02d8475574e75203516 (patch)
treeb008e7d77e9daaeaaa8e7854728d715df5aafb77 /redshift/gamma-coopgamma.c
parenttodo list housekeeping (diff)
downloadredshift-ng-037b945a9f253b97faffc02d8475574e75203516.tar.gz
redshift-ng-037b945a9f253b97faffc02d8475574e75203516.tar.bz2
redshift-ng-037b945a9f253b97faffc02d8475574e75203516.tar.xz
one dir per subproject
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'redshift/gamma-coopgamma.c')
-rw-r--r--redshift/gamma-coopgamma.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/redshift/gamma-coopgamma.c b/redshift/gamma-coopgamma.c
new file mode 100644
index 0000000..f51c0ee
--- /dev/null
+++ b/redshift/gamma-coopgamma.c
@@ -0,0 +1,535 @@
+/*-
+ * 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"
+
+#include <libcoopgamma.h>
+
+#if defined(__clang__)
+# pragma clang diagnostic ignored "-Wkeyword-macro"
+#endif
+
+
+struct coopgamma_output_id {
+ char *edid;
+ size_t index;
+};
+
+
+struct coopgamma_crtc_state {
+ libcoopgamma_filter_t filter;
+ libcoopgamma_ramps_t plain_ramps;
+ size_t rampsize;
+};
+
+
+struct gamma_state {
+ libcoopgamma_context_t ctx;
+ struct coopgamma_crtc_state *crtcs;
+ size_t n_crtcs;
+ char **methods;
+ char *method;
+ char *site;
+ int64_t priority;
+ int list_outputs;
+ struct coopgamma_output_id *outputs;
+ size_t n_outputs;
+ size_t a_outputs;
+};
+
+
+struct signal_blockage {int dummy;};
+
+
+static int
+unblocked_signal(int signo, struct signal_blockage *prev)
+{
+ /* TODO */
+ (void) signo;
+ (void) prev;
+ return 0;
+}
+
+
+static int
+restore_signal_blockage(int signo, const struct signal_blockage *blockage)
+{
+ /* TODO */
+ (void) signo;
+ (void) blockage;
+ return 0;
+}
+
+
+static int
+update(struct gamma_state *state)
+{
+ size_t i;
+ for (i = 0; i < state->n_crtcs; i++)
+ libcoopgamma_set_gamma_sync(&state->crtcs[i].filter, &state->ctx);
+ return 0;
+}
+
+
+static void
+print_error(struct gamma_state *state)
+{
+ unsigned long long int ec = (unsigned long long int)state->ctx.error.number;
+ if (state->ctx.error.custom) {
+ if (ec && state->ctx.error.description) {
+ if (state->ctx.error.server_side)
+ weprintf(_("Server-side error number %llu: %s."), ec, state->ctx.error.description);
+ else
+ weprintf(_("Client-side error number %llu: %s."), ec, state->ctx.error.description);
+ } else if (ec) {
+ if (state->ctx.error.server_side)
+ weprintf(_("Server-side error number %llu."), ec);
+ else
+ weprintf(_("Client-side error number %llu."), ec);
+ } else if (state->ctx.error.description) {
+ if (state->ctx.error.server_side)
+ weprintf(_("Server-side error: %s."), state->ctx.error.description);
+ else
+ weprintf(_("Client-side error: %s."), state->ctx.error.description);
+ }
+ } else if (state->ctx.error.description) {
+ if (state->ctx.error.server_side)
+ weprintf(_("Server-side error: %s."), state->ctx.error.description);
+ else
+ weprintf(_("Client-side error: %s."), state->ctx.error.description);
+ } else {
+ if (state->ctx.error.server_side)
+ weprintf(_("Server-side error: %s."), strerror(state->ctx.error.number));
+ else
+ weprintf(_("Client-side error: %s."), strerror(state->ctx.error.number));
+ }
+}
+
+
+static int
+coopgamma_is_available(void)
+{
+ return 1;
+}
+
+
+static int
+coopgamma_create(struct gamma_state **state_out)
+{
+ struct gamma_state *state;
+ struct signal_blockage signal_blockage;
+
+ state = *state_out = ecalloc(1, sizeof(**state_out));
+
+ if (libcoopgamma_context_initialise(&state->ctx)) {
+ weprintf("libcoopgamma_context_initialise:");
+ return -1;
+ }
+
+ /* This is done this early to check if coopgamma is available */
+ if (unblocked_signal(SIGCHLD, &signal_blockage) < 0)
+ return -1;
+ state->methods = libcoopgamma_get_methods();
+ if (state->methods == NULL) {
+ weprintf("libcoopgamma_get_methods:");
+ if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0)
+ exit(1);
+ return -1;
+ }
+ if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0)
+ return -1;
+
+ state->priority = 0x0800000000000000LL;
+
+ return 0;
+}
+
+
+static int
+coopgamma_start(struct gamma_state *state)
+{
+ struct signal_blockage signal_blockage;
+ libcoopgamma_lifespan_t lifespan;
+ char** outputs;
+ size_t i, j, n_outputs;
+ int r;
+ double d;
+
+ switch (mode) {
+ case PROGRAM_MODE_RESET:
+ lifespan = LIBCOOPGAMMA_REMOVE;
+ break;
+ case PROGRAM_MODE_ONE_SHOT:
+ lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL;
+ break;
+ case PROGRAM_MODE_CONTINUAL:
+ case PROGRAM_MODE_UNTIL_DEATH:
+ lifespan = LIBCOOPGAMMA_UNTIL_DEATH;
+ break;
+ default:
+ case PROGRAM_MODE_PRINT:
+ abort();
+ }
+
+ free(state->methods);
+ state->methods = NULL;
+
+ /* Connect to server */
+ if (unblocked_signal(SIGCHLD, &signal_blockage) < 0)
+ return -1;
+ if (libcoopgamma_connect(state->method, state->site, &state->ctx) < 0) {
+ if (errno)
+ weprintf("libcoopgamma_connect:");
+ else
+ weprintf(_("libcoopgamma_connect: could not start coopgamma server."));
+ if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0)
+ exit(1);
+ return -1;
+ }
+ if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0)
+ return -1;
+ free(state->method);
+ state->method = NULL;
+ free(state->site);
+ state->site = NULL;
+
+ /* Get available outputs */
+ outputs = libcoopgamma_get_crtcs_sync(&state->ctx);
+ for (n_outputs = 0; outputs[n_outputs]; n_outputs++);
+
+ /* List available output if edid=list was used */
+ if (state->list_outputs) {
+ if (!outputs) {
+ print_error(state);
+ return -1;
+ }
+ printf(_("Available outputs:\n"));
+ for (i = 0; outputs[i]; i++)
+ printf(" %s\n", outputs[i]);
+ if (ferror(stdout))
+ eprintf("printf:");
+ exit(0);
+ }
+
+ /* Translate crtc=N to edid=EDID */
+ for (i = 0; i < state->n_outputs; i++) {
+ if (state->outputs[i].edid)
+ continue;
+ if (state->outputs[i].index >= n_outputs) {
+ weprintf(_("Monitor number %zu does not exist, available monitors are [0, %zu]"),
+ state->outputs[i].index, n_outputs - 1);
+ return -1;
+ }
+ state->outputs[i].edid = estrdup(outputs[state->outputs[i].index]);
+ }
+
+ /* Use all outputs if none were specified */
+ if (state->n_outputs == 0) {
+ state->n_outputs = state->a_outputs = n_outputs;
+ state->outputs = emalloc(n_outputs * sizeof(*state->outputs));
+ for (i = 0; i < n_outputs; i++)
+ state->outputs[i].edid = estrdup(outputs[i]);
+ }
+
+ free(outputs);
+
+ /* Initialise information for each output */
+ state->crtcs = ecalloc(state->n_outputs, sizeof(*state->crtcs));
+ for (i = 0; i < state->n_outputs; i++) {
+ libcoopgamma_crtc_info_t info;
+ struct coopgamma_crtc_state *crtc = state->crtcs + state->n_crtcs;
+
+ crtc->filter.priority = state->priority;
+ crtc->filter.crtc = state->outputs[i].edid;
+ crtc->filter.lifespan = lifespan;
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
+#endif
+ crtc->filter.class = PACKAGE "::redshift::standard";
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+ if (libcoopgamma_get_gamma_info_sync(crtc->filter.crtc, &info, &state->ctx) < 0) {
+ int saved_errno = errno;
+ weprintf(_("Failed to retrieve information for output `%s':\n"), outputs[i]);
+ errno = saved_errno;
+ print_error(state);
+ return -1;
+ }
+ if (!info.cooperative) {
+ weprintf(_("coopgamma is not available.\n"));
+ return -1;
+ }
+ if (info.supported == LIBCOOPGAMMA_NO) {
+ weprintf(_("Output `%s' does not support gamma adjustments, skipping."), outputs[i]);
+ continue;
+ }
+
+ /* Get total size of the ramps */
+ switch (info.depth) {
+#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
+ case DEPTH:\
+ crtc->rampsize = sizeof(TYPE);\
+ break
+ LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
+#undef X
+ default:
+ if (info.depth > 0) {
+ weprintf(_("output `%s' uses an unsupported depth "
+ "for its gamma ramps: %i bits, skipping\n"),
+ outputs[i], info.depth);
+ } else {
+ weprintf(_("output `%s' uses an unrecognised depth, "
+ "for its gamma ramps, with the code %i, "
+ "skipping\n"), outputs[i], info.depth);
+ }
+ continue;
+ }
+ crtc->rampsize *= info.red_size + info.green_size + info.blue_size;
+
+ crtc->filter.depth = info.depth;
+ crtc->filter.ramps.u8.red_size = info.red_size;
+ crtc->filter.ramps.u8.green_size = info.green_size;
+ crtc->filter.ramps.u8.blue_size = info.blue_size;
+ crtc->plain_ramps.u8.red_size = info.red_size;
+ crtc->plain_ramps.u8.green_size = info.green_size;
+ crtc->plain_ramps.u8.blue_size = info.blue_size;
+
+ /* Initialise plain ramp and working ramp */
+#define float f
+#define double d
+ switch (info.depth) {
+#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
+ case DEPTH:\
+ r = libcoopgamma_ramps_initialise(&crtc->filter.ramps.SUFFIX);\
+ if (r < 0) {\
+ perror("libcoopgamma_ramps_initialise");\
+ return -1;\
+ }\
+ r = libcoopgamma_ramps_initialise(&crtc->plain_ramps.SUFFIX);\
+ if (r < 0) {\
+ perror("libcoopgamma_ramps_initialise");\
+ return -1;\
+ }\
+ for (j = 0; j < crtc->plain_ramps.SUFFIX.red_size; j++) {\
+ d = j;\
+ d /= crtc->plain_ramps.SUFFIX.red_size;\
+ crtc->plain_ramps.SUFFIX.red[j] = d * (MAX);\
+ }\
+ for (j = 0; j < crtc->plain_ramps.SUFFIX.green_size; j++) {\
+ d = j;\
+ d /= crtc->plain_ramps.SUFFIX.green_size;\
+ crtc->plain_ramps.SUFFIX.green[j] = d * (MAX);\
+ }\
+ for (j = 0; j < crtc->plain_ramps.SUFFIX.blue_size; j++) {\
+ d = j;\
+ d /= crtc->plain_ramps.SUFFIX.blue_size;\
+ crtc->plain_ramps.SUFFIX.blue[j] = d * (MAX);\
+ }\
+ break
+ LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
+#undef X
+ default:
+ abort();
+ }
+#undef float
+#undef double
+
+ state->outputs[i].edid = NULL;
+ state->n_crtcs++;
+ }
+
+ free(state->outputs);
+ state->outputs = NULL;
+ state->n_outputs = 0;
+
+ return 0;
+}
+
+
+static void
+coopgamma_free(struct gamma_state *state)
+{
+ free(state->methods);
+ free(state->method);
+ free(state->site);
+
+ while (state->n_crtcs--) {
+ state->crtcs[state->n_crtcs].filter.class = NULL;
+ libcoopgamma_filter_destroy(&state->crtcs[state->n_crtcs].filter);
+ libcoopgamma_ramps_destroy(&state->crtcs[state->n_crtcs].plain_ramps);
+ }
+ free(state->crtcs);
+
+ libcoopgamma_context_destroy(&state->ctx, 1);
+ while (state->n_outputs--)
+ free(state->outputs[state->n_outputs].edid);
+ state->n_outputs = 0;
+ free(state->outputs);
+
+ free(state);
+}
+
+
+static void
+coopgamma_print_help(void) /* TODO not documented in readme and manpage */
+{
+ printf(_("Adjust gamma ramps with coopgamma.\n"));
+ printf("\n");
+
+ printf(" display=%s %s\n", _("NAME "), _("Display server instance to apply adjustments to"));
+ printf(" crtc=%s %s\n", _("N "), _("Index of CRTC to apply adjustments to"));
+ printf(" edid=%s %s\n", _("EDID "), _("EDID of monitor to apply adjustments to, "
+ "enter `list' to list available monitors"));
+ printf(" priority=%s %s\n", _("N "), _("The application order of the adjustments, "
+ "default value is 576460752303423488"));
+ printf(" method=%s %s\n", _("NAME "), _("Underlaying adjustment method, "
+ "enter `list' to list available methods"));
+ printf("\n");
+}
+
+
+static int
+coopgamma_set_option(struct gamma_state *state, const char *key, const char *value)
+{
+ size_t i;
+ char *end;
+ long long int priority;
+
+ if (!strcasecmp(key, "priority")) {
+ errno = 0;
+ priority = strtoll(value, &end, 10);
+ if (errno || *end || priority < INT64_MIN || priority > INT64_MAX) {
+ weprintf(_("Value of method parameter `crtc' must be a integer in [%lli, %lli]."),
+ (long long int)INT64_MIN, (long long int)INT64_MAX);
+ return -1;
+ }
+ state->priority = priority;
+ } else if (!strcasecmp(key, "method")) {
+ if (state->method != NULL) {
+ weprintf(_("Method parameter `method' can only be used once."));
+ return -1;
+ }
+ if (!strcasecmp(value, "list")) {
+ /* TRANSLATORS: coopgamma help output the word "coopgamma" must not be translated */
+ printf(_("Available adjustment methods for coopgamma:\n"));
+ for (i = 0; state->methods[i]; i++)
+ printf(" %s\n", state->methods[i]);
+ if (ferror(stdout))
+ eprintf("printf:");
+ exit(0);
+ }
+ state->method = estrdup(value);
+ } else if (!strcasecmp(key, "display")) {
+ if (state->site != NULL) {
+ weprintf(_("Method parameter `display' can only be used once."));
+ return -1;
+ }
+ state->site = estrdup(value);
+ } else if (!strcasecmp(key, "edid") || !strcasecmp(key, "crtc")) {
+ if (state->n_outputs == state->a_outputs) {
+ state->a_outputs += 8;
+ state->outputs = erealloc(state->outputs, state->a_outputs * sizeof(*state->outputs));
+ }
+ if (!strcasecmp(key, "edid")) {
+ state->outputs[state->n_outputs].edid = estrdup(value);
+ if (!strcasecmp(state->outputs[state->n_outputs].edid, "list"))
+ state->list_outputs = 1;
+ } else {
+ state->outputs[state->n_outputs].edid = NULL;
+ errno = 0;
+ state->outputs[state->n_outputs].index = (size_t)strtoul(value, &end, 10);
+ if (!*end && errno == ERANGE && state->outputs[state->n_outputs].index == SIZE_MAX) {
+ state->outputs[state->n_outputs].index = SIZE_MAX;
+ } else if (errno || *end) {
+ weprintf(_("Value of method parameter `crtc' must be a non-negative integer."));
+ return -1;
+ }
+ }
+ state->n_outputs++;
+ } else {
+ weprintf(_("Unknown method parameter: `%s'."), key);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void
+coopgamma_restore(struct gamma_state *state)
+{
+ size_t i;
+ for (i = 0; i < state->n_crtcs; i++)
+ state->crtcs[i].filter.lifespan = LIBCOOPGAMMA_REMOVE;
+ update(state);
+ for (i = 0; i < state->n_crtcs; i++)
+ state->crtcs[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_DEATH;
+}
+
+
+static int
+coopgamma_apply(struct gamma_state *state, const struct colour_setting *setting, int perserve)
+{
+ libcoopgamma_filter_t *filter;
+ libcoopgamma_filter_t *last_filter = NULL;
+ size_t i;
+
+ (void) perserve;
+
+ for (i = 0; i < state->n_crtcs; i++, last_filter = filter) {
+ filter = &state->crtcs[i].filter;
+
+ /* Copy ramps for previous CRTC if its ramps is of same size and depth */
+ if (last_filter &&
+ last_filter->ramps.u8.red_size == filter->ramps.u8.red_size &&
+ last_filter->ramps.u8.green_size == filter->ramps.u8.green_size &&
+ last_filter->ramps.u8.blue_size == filter->ramps.u8.blue_size) {
+ memcpy(filter->ramps.u8.red, last_filter->ramps.u8.red, state->crtcs[i].rampsize);
+ continue;
+ }
+
+ /* Otherwise, create calculate the ramps */
+ memcpy(filter->ramps.u8.red, state->crtcs[i].plain_ramps.u8.red, state->crtcs[i].rampsize);
+ switch (filter->depth) {
+#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
+ case DEPTH:\
+ fill_ramps_##SUFFIX((void *)(filter->ramps.u8.red),\
+ (void *)(filter->ramps.u8.green),\
+ (void *)(filter->ramps.u8.blue),\
+ NULL, NULL, NULL,\
+ 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
+ default:
+ abort();
+ }
+ }
+
+ return update(state);
+}
+
+
+const struct gamma_method coopgamma_gamma_method = GAMMA_METHOD_INIT("coopgamma", 1, 0, coopgamma);