/* gamma-quartz.c -- Quartz (OSX) gamma adjustment 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) 2014-2017 Jon Lund Steffensen Copyright (c) 2025 Mattias Andrée */ #include "common.h" #include struct quartz_display_state { CGDirectDisplayID display; uint32_t ramp_size; float *saved_ramps; }; struct gamma_state { struct quartz_display_state *displays; uint32_t display_count; }; static int quartz_init(struct gamma_state **state) { *state = emalloc(sizeof(**state)); (*state)->displays = NULL; return 0; } static int quartz_start(struct gamma_state *state, program_mode_t mode) { float *gamma_r, *gamma_g, *gamma_b; uint32_t i, display_count, ramp_size, sample_count; CGDirectDisplayID *displays, display; CGError error; /* Get display count */ error = CGGetOnlineDisplayList(0, NULL, &display_count); if (error != kCGErrorSuccess) return -1; state->display_count = display_count; displays = emalloc(sizeof(CGDirectDisplayID) * display_count); /* Get list of displays */ error = CGGetOnlineDisplayList(display_count, displays, &display_count); if (error != kCGErrorSuccess) { free(displays); return -1; } /* Allocate list of display state */ state->displays = emalloc(display_count * sizeof(struct quartz_display_state)); /* Copy display indentifiers to display state */ for (i = 0; i < display_count; i++) { state->displays[i].display = displays[i]; state->displays[i].saved_ramps = NULL; } free(displays); /* Save gamma ramps for all displays in display state */ for (i = 0; i < display_count; i++) { display = state->displays[i].display; ramp_size = CGDisplayGammaTableCapacity(display); if (!ramp_size) { weprintf(_("Gamma ramp size too small: %i"), ramp_size); return -1; } state->displays[i].ramp_size = ramp_size; /* Allocate space for saved ramps */ state->displays[i].saved_ramps = emalloc(3 * ramp_size * sizeof(float)); gamma_r = &state->displays[i].saved_ramps[0 * ramp_size]; gamma_g = &state->displays[i].saved_ramps[1 * ramp_size]; gamma_b = &state->displays[i].saved_ramps[2 * ramp_size]; /* Copy the ramps to allocated space */ error = CGGetDisplayTransferByTable(display, ramp_size, gamma_r, gamma_g, gamma_b, &sample_count); if (error != kCGErrorSuccess || sample_count != ramp_size) { weprintf(_("Unable to save current gamma ramp.")); return -1; } } return 0; } static void quartz_restore(struct gamma_state *state) { CGDisplayRestoreColorSyncSettings(); } static void quartz_free(struct gamma_state *state) { uint32_t i; if (state->displays) for (i = 0; i < state->display_count; i++) free(state->displays[i].saved_ramps); free(state->displays); free(state); } static void quartz_print_help(FILE *f) { fputs(_("Adjust gamma ramps on macOS using Quartz.\n"), f); fputs("\n", f); } static int quartz_set_option(struct gamma_state *state, const char *key, const char *value) { if (!strcasecmp(key, "preserve")) { weprintf(_("Parameter `%s' is now always on; use the `%s' command-line option to disable."), key, "-P"); } else { weprintf(_("Unknown method parameter: `%s'."), key); return -1; } return 0; } static void quartz_set_temperature_for_display(struct gamma_state *state, int display_index, const color_setting_t *setting, int preserve) { float *gamma_ramps, *gamma_r, *gamma_g, *gamma_b, value; CGDirectDisplayID display = state->displays[display_index].display; uint32_t ramp_size = state->displays[display_index].ramp_size; CGError error; uint32_t i; /* Create new gamma ramps */ gamma_ramps = emalloc(3 * ramp_size * sizeof(float)); gamma_r = &gamma_ramps[0 * ramp_size]; gamma_g = &gamma_ramps[1 * ramp_size]; gamma_b = &gamma_ramps[2 * ramp_size]; if (preserve) { /* Initialize gamma ramps from saved state */ memcpy(gamma_ramps, state->displays[display_index].saved_ramps, 3 * ramp_size * sizeof(float)); } else { /* Initialize gamma ramps to pure state */ for (i = 0; i < ramp_size; i++) { value = (float)i / ramp_size; gamma_r[i] = value; gamma_g[i] = value; gamma_b[i] = value; } } colorramp_fill_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) { free(gamma_ramps); return; } free(gamma_ramps); } static int quartz_set_temperature( struct gamma_state *state, const color_setting_t *setting, int preserve) { uint32_t i; for (i = 0; i < state->display_count; i++) { quartz_set_temperature_for_display( state, i, setting, preserve); } return 0; } const struct gamma_method quartz_gamma_method = { "quartz", 1, &quartz_init, &quartz_start, &quartz_free, &quartz_print_help, &quartz_set_option, &quartz_restore, &quartz_set_temperature };