/* gamma-coopgamma.h -- coopgamma gamma adjustment source This file is part of Redshift. Redshift is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Redshift is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Redshift. If not, see . Copyright (c) 2016, 2025 Mattias Andrée */ #ifdef HAVE_CONFIG_H # include "config.h" #else # define PACKAGE "redshift-ng" #endif #include #include #include #include #include #include #ifdef ENABLE_NLS # include # define _(s) gettext(s) #else # define _(s) s #endif #include "common.h" #include typedef struct { char *edid; size_t index; } coopgamma_output_id_t; typedef struct { libcoopgamma_filter_t filter; libcoopgamma_ramps_t plain_ramps; size_t rampsize; } coopgamma_crtc_state_t; typedef struct { libcoopgamma_context_t ctx; coopgamma_crtc_state_t *crtcs; size_t n_crtcs; char **methods; char *method; char *site; int64_t priority; int list_outputs; coopgamma_output_id_t *outputs; size_t n_outputs; size_t a_outputs; } coopgamma_state_t; struct signal_blockage { }; static int unblocked_signal(int signo, struct signal_blockage *prev) { /* TODO */ return 0; } static int restore_signal_blockage(int signo, const struct signal_blockage *blockage) { /* TODO */ return 0; } static int update(coopgamma_state_t *state) { for (size_t i = 0; i < state->n_crtcs; i++) libcoopgamma_set_gamma_sync(&state->crtcs[i].filter, &state->ctx); return 0; } static void print_error(coopgamma_state_t *state) { const char* side = state->ctx.error.server_side ? _("server-side") : _("client-side"); if (state->ctx.error.custom) { if (state->ctx.error.number != 0 && state->ctx.error.description != NULL) fprintf(stderr, "%s error number %llu: %s\n", side, (unsigned long long int)state->ctx.error.number, state->ctx.error.description); else if (state->ctx.error.number != 0) fprintf(stderr, _("%s error number %llu\n"), side, (unsigned long long int)state->ctx.error.number); else if (state->ctx.error.description != NULL) fprintf(stderr, _("%s error: %s\n"), side, state->ctx.error.description); } else if (state->ctx.error.description != NULL) { fprintf(stderr, _("%s error: %s\n"), side, state->ctx.error.description); } else { fprintf(stderr, _("%s error: %s\n"), side, strerror(state->ctx.error.number)); } } static int coopgamma_init(coopgamma_state_t **state) { *state = malloc(sizeof(coopgamma_state_t)); if (*state == NULL) return -1; coopgamma_state_t *s = *state; struct signal_blockage signal_blockage; memset(s, 0, sizeof(*s)); if (libcoopgamma_context_initialise(&s->ctx)) { perror("libcoopgamma_context_initialise"); return -1; } /* This is done this early to check if coopgamma is available */ if (unblocked_signal(SIGCHLD, &signal_blockage) < 0) return -1; s->methods = libcoopgamma_get_methods(); if (s->methods == NULL) { perror("libcoopgamma_get_methods"); if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) exit(EXIT_FAILURE); return -1; } if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) return -1; s->priority = 0x0800000000000000LL; return 0; } static int coopgamma_start(coopgamma_state_t *state, program_mode_t mode) { struct signal_blockage signal_blockage; libcoopgamma_lifespan_t lifespan; char** outputs; size_t i, j, n_outputs; int r; double d; switch (mode) { case PROGRAM_MODE_RESET: lifespan = LIBCOOPGAMMA_REMOVE; break; case PROGRAM_MODE_ONE_SHOT: case PROGRAM_MODE_MANUAL: lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL; break; default: lifespan = LIBCOOPGAMMA_UNTIL_DEATH; break; } free(state->methods); state->methods = NULL; /* Connect to server */ if (unblocked_signal(SIGCHLD, &signal_blockage) < 0) return -1; if (libcoopgamma_connect(state->method, state->site, &state->ctx) < 0) { if (errno) perror("libcoopgamma_connect"); else fprintf(stderr, _("libcoopgamma_connect: could not " "start coopgamma server\n")); if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) exit(EXIT_FAILURE); return -1; } if (restore_signal_blockage(SIGCHLD, &signal_blockage) < 0) return -1; free(state->method); state->method = NULL; free(state->site); state->site = NULL; /* Get available outputs */ outputs = libcoopgamma_get_crtcs_sync(&state->ctx); for (n_outputs = 0; outputs[n_outputs]; n_outputs++); /* List available output if edid=list was used */ if (state->list_outputs) { if (outputs == NULL) { print_error(state); return -1; } printf(_("Available outputs:\n")); for (i = 0; outputs[i]; i++) printf(" %s\n", outputs[i]); if (ferror(stdout)) { perror("printf"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } /* Translate crtc=N to edid=EDID */ for (i = 0; i < state->n_outputs; i++) { if (state->outputs[i].edid != NULL) continue; if (state->outputs[i].index >= n_outputs) { fprintf(stderr, _("monitor number %zu does not exist," "available monitors are [0, %zu]\n"), state->outputs[i].index, n_outputs - 1); return -1; } state->outputs[i].edid = strdup(outputs[state->outputs[i].index]); if (state->outputs[i].edid == NULL) { perror("strdup"); return -1; } } /* Use all outputs if none were specified */ if (state->n_outputs == 0) { state->n_outputs = state->a_outputs = n_outputs; state->outputs = malloc(n_outputs * sizeof(*state->outputs)); if (state->outputs == NULL) { perror("malloc"); return -1; } for (i = 0; i < n_outputs; i++) { state->outputs[i].edid = strdup(outputs[i]); if (state->outputs[i].edid == NULL) { perror("strdup"); return -1; } } } free(outputs); /* Initialise information for each output */ state->crtcs = calloc(state->n_outputs, sizeof(*state->crtcs)); if (state->crtcs == NULL) { perror("calloc"); return -1; } for (i = 0; i < state->n_outputs; i++) { libcoopgamma_crtc_info_t info; coopgamma_crtc_state_t *crtc = state->crtcs + state->n_crtcs; crtc->filter.priority = state->priority; crtc->filter.crtc = state->outputs[i].edid; crtc->filter.class = PACKAGE "::redshift::standard"; crtc->filter.lifespan = lifespan; if (libcoopgamma_get_gamma_info_sync(crtc->filter.crtc, &info, &state->ctx) < 0) { int saved_errno = errno; fprintf(stderr, _("failed to retrieve information for output `%s':\n"), outputs[i]); errno = saved_errno; print_error(state); return -1; } if (!info.cooperative) { fprintf(stderr, _("coopgamma is not available\n")); return -1; } if (info.supported == LIBCOOPGAMMA_NO) { fprintf(stderr, _("output `%s' does not support gamma " "adjustments, skipping\n"), outputs[i]); continue; } /* Get total size of the ramps */ switch (info.depth) { #define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ case DEPTH:\ crtc->rampsize = sizeof(TYPE);\ break; LIST_RAMPS_STOP_VALUE_TYPES #undef X default: if (info.depth > 0) fprintf(stderr, _("output `%s' uses an unsupported depth " "for its gamma ramps: %i bits, skipping\n"), outputs[i], info.depth); else fprintf(stderr, _("output `%s' uses an unrecognised depth, " "for its gamma ramps, with the code %i, " "skipping\n"), outputs[i], info.depth); continue; } crtc->rampsize *= info.red_size + info.green_size + info.blue_size; crtc->filter.depth = info.depth; crtc->filter.ramps.u8.red_size = info.red_size; crtc->filter.ramps.u8.green_size = info.green_size; crtc->filter.ramps.u8.blue_size = info.blue_size; crtc->plain_ramps.u8.red_size = info.red_size; crtc->plain_ramps.u8.green_size = info.green_size; crtc->plain_ramps.u8.blue_size = info.blue_size; /* Initialise plain ramp and working ramp */ #define float f #define double d switch (info.depth) { #define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ case DEPTH:\ r = libcoopgamma_ramps_initialise(&crtc->filter.ramps.SUFFIX);\ if (r < 0) {\ perror("libcoopgamma_ramps_initialise");\ return -1;\ }\ r = libcoopgamma_ramps_initialise(&crtc->plain_ramps.SUFFIX);\ if (r < 0) {\ perror("libcoopgamma_ramps_initialise");\ return -1;\ }\ for (j = 0; j < crtc->plain_ramps.SUFFIX.red_size; j++) {\ d = j;\ d /= crtc->plain_ramps.SUFFIX.red_size;\ crtc->plain_ramps.SUFFIX.red[j] = d * TRUE_MAX;\ }\ for (j = 0; j < crtc->plain_ramps.SUFFIX.green_size; j++) {\ d = j;\ d /= crtc->plain_ramps.SUFFIX.green_size;\ crtc->plain_ramps.SUFFIX.green[j] = d * TRUE_MAX;\ }\ for (j = 0; j < crtc->plain_ramps.SUFFIX.blue_size; j++) {\ d = j;\ d /= crtc->plain_ramps.SUFFIX.blue_size;\ crtc->plain_ramps.SUFFIX.blue[j] = d * TRUE_MAX;\ }\ break; LIST_RAMPS_STOP_VALUE_TYPES #undef X default: abort(); } #undef float #undef double state->outputs[i].edid = NULL; state->n_crtcs++; } free(state->outputs); state->outputs = NULL; state->n_outputs = 0; return 0; } static void coopgamma_free(coopgamma_state_t *state) { free(state->methods); state->methods = NULL; free(state->method); state->method = NULL; free(state->site); state->site = NULL; while (state->n_crtcs--) { state->crtcs[state->n_crtcs].filter.class = NULL; libcoopgamma_filter_destroy(&state->crtcs[state->n_crtcs].filter); libcoopgamma_ramps_destroy(&state->crtcs[state->n_crtcs].plain_ramps); } state->n_crtcs = 0; free(state->crtcs); state->crtcs = NULL; libcoopgamma_context_destroy(&state->ctx, 1); while (state->n_outputs--) free(state->outputs[state->n_outputs].edid); state->n_outputs = 0; free(state->outputs); state->outputs = NULL; } static void coopgamma_print_help(FILE *f) { fputs(_("Adjust gamma ramps with coopgamma.\n"), f); fputs("\n", f); /* TRANSLATORS: coopgamma help output left column must not be translated */ fputs(_(" edid=EDID \tEDID of monitor to apply adjustments to, enter " "`list' to list available monitors\n" " crtc=N \tIndex of CRTC to apply adjustments to\n" " priority=N \tThe application order of the adjustments, " "default value is 576460752303423488.\n" " method=METHOD \tUnderlaying adjustment method, enter " "`list' to list available methods\n" " display=DISPLAY\tThe display to apply adjustments to\n"), f); fputs("\n", f); } static int coopgamma_set_option(coopgamma_state_t *state, const char *key, const char *value) { size_t i; char *end; long long int priority; if (!strcasecmp(key, "priority")) { errno = 0; priority = strtoll(value, &end, 10); if (errno || *end || priority < INT64_MIN || priority > INT64_MAX) { fprintf(stderr, _("value of method parameter `crtc' " "must be a integer in [%lli, %lli]\n"), (long long int)INT64_MIN, (long long int)INT64_MAX); return -1; } state->priority = priority; } else if (!strcasecmp(key, "method")) { if (state->method != NULL) { fprintf(stderr, _("method parameter `method' " "can only be used once\n")); return -1; } if (!strcasecmp(value, "list")) { /* TRANSLATORS: coopgamma help output the word "coopgamma" must not be translated */ printf(_("Available adjustment methods for coopgamma:\n")); for (i = 0; state->methods[i]; i++) printf(" %s\n", state->methods[i]); if (ferror(stdout)) { perror("printf"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } state->method = strdup(value); if (state->method == NULL) { perror("strdup"); return -1; } } else if (!strcasecmp(key, "display")) { if (state->site != NULL) { fprintf(stderr, _("method parameter `display' " "can only be used once\n")); return -1; } state->site = strdup(value); if (state->site == NULL) { perror("strdup"); return -1; } } else if (!strcasecmp(key, "edid") || !strcasecmp(key, "crtc")) { if (state->n_outputs == state->a_outputs) { state->a_outputs += 8; state->outputs = realloc(state->outputs, state->a_outputs * sizeof(*state->outputs)); if (state->outputs == NULL) { perror("realloc"); return -1; } } if (!strcasecmp(key, "edid")) { state->outputs[state->n_outputs].edid = strdup(value); if (state->outputs[state->n_outputs].edid == NULL) { perror("strdup"); return -1; } if (!strcasecmp(state->outputs[state->n_outputs].edid, "list")) state->list_outputs = 1; } else { state->outputs[state->n_outputs].edid = NULL; errno = 0; state->outputs[state->n_outputs].index = (size_t)strtoul(value, &end, 10); if (!*end && errno == ERANGE && state->outputs[state->n_outputs].index == SIZE_MAX) { state->outputs[state->n_outputs].index = SIZE_MAX; } else if (errno || *end) { fprintf(stderr, _("value of method parameter `crtc' " "must be a non-negative integer\n")); return -1; } } state->n_outputs++; } else { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; } return 0; } static void coopgamma_restore(coopgamma_state_t *state) { size_t i; for (i = 0; i < state->n_crtcs; i++) state->crtcs[i].filter.lifespan = LIBCOOPGAMMA_REMOVE; update(state); for (i = 0; i < state->n_crtcs; i++) state->crtcs[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_DEATH; } static int coopgamma_set_temperature(coopgamma_state_t *state, const color_setting_t *setting) { libcoopgamma_filter_t *filter; libcoopgamma_filter_t *last_filter = NULL; size_t i; for (i = 0; i < state->n_crtcs; i++, last_filter = filter) { filter = &state->crtcs[i].filter; /* Copy ramps for previous CRTC if its ramps is of same size and depth */ if (last_filter != NULL && last_filter->ramps.u8.red_size == filter->ramps.u8.red_size && last_filter->ramps.u8.green_size == filter->ramps.u8.green_size && last_filter->ramps.u8.blue_size == filter->ramps.u8.blue_size) { memcpy(filter->ramps.u8.red, last_filter->ramps.u8.red, state->crtcs[i].rampsize); continue; } /* Otherwise, create calculate the ramps */ memcpy(filter->ramps.u8.red, state->crtcs[i].plain_ramps.u8.red, state->crtcs[i].rampsize); switch (filter->depth) { #define X(SUFFIX, TYPE, MAX, TRUE_MAX, DEPTH)\ case DEPTH:\ colorramp_fill_##SUFFIX((void *)(filter->ramps.u8.red),\ (void *)(filter->ramps.u8.green),\ (void *)(filter->ramps.u8.blue),\ filter->ramps.u8.red_size,\ filter->ramps.u8.green_size,\ filter->ramps.u8.blue_size,\ setting);\ break; LIST_RAMPS_STOP_VALUE_TYPES #undef X default: abort(); } } return update(state); } const gamma_method_t coopgamma_gamma_method = { "coopgamma", 1, (gamma_method_init_func *)coopgamma_init, (gamma_method_start_func *)coopgamma_start, (gamma_method_free_func *)coopgamma_free, (gamma_method_print_help_func *)coopgamma_print_help, (gamma_method_set_option_func *)coopgamma_set_option, (gamma_method_restore_func *)coopgamma_restore, (gamma_method_set_temperature_func *)coopgamma_set_temperature };