diff options
author | Mattias Andrée <m@maandree.se> | 2025-03-27 18:36:26 +0100 |
---|---|---|
committer | Mattias Andrée <m@maandree.se> | 2025-03-27 18:36:26 +0100 |
commit | 037b945a9f253b97faffc02d8475574e75203516 (patch) | |
tree | b008e7d77e9daaeaaa8e7854728d715df5aafb77 /redshift/gamma-coopgamma.c | |
parent | todo list housekeeping (diff) | |
download | redshift-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.c | 535 |
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); |