/*- * redshift-ng - Automatically adjust display colour temperature according the Sun * * Copyright (c) 2009-2018 Jon Lund Steffensen * Copyright (c) 2014-2016, 2025 Mattias Andrée * * 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 . */ #include "common.h" #include #include #ifndef O_CLOEXEC # define O_CLOEXEC 02000000 #endif struct drm_crtc_state { int crtc_num; int crtc_id; int gamma_size; uint16_t *r_gamma; uint16_t *g_gamma; uint16_t *b_gamma; }; struct gamma_state { int card_num; int crtc_num; int fd; drmModeRes *res; struct drm_crtc_state *crtcs; }; static int drm_create(struct gamma_state **state_out) { struct gamma_state *state; state = *state_out = emalloc(sizeof(**state_out)); state->card_num = 0; state->crtc_num = -1; state->fd = -1; state->res = NULL; state->crtcs = NULL; return 0; } static int drm_start(struct gamma_state *state) { /* Acquire access to a graphics card. */ char pathname[sizeof(DRM_DIR_NAME"")-1U + sizeof(DRM_DEV_NAME"")-1U + 3U * sizeof(int) + 2U]; int crtc_count; struct drm_crtc_state *crtcs; sprintf(pathname, DRM_DEV_NAME, DRM_DIR_NAME, state->card_num); state->fd = open(pathname, O_RDWR | O_CLOEXEC); if (state->fd < 0) { /* TODO check if access permissions, normally root or membership of the video group is required. */ weprintf(_("Failed to open DRM device `%s': %s."), pathname, strerror(errno)); return -1; } /* Acquire mode resources. */ state->res = drmModeGetResources(state->fd); if (state->res == NULL) { weprintf(_("Failed to get DRM mode resources.")); close(state->fd); state->fd = -1; return -1; } /* Create entries for selected CRTCs. */ crtc_count = state->res->count_crtcs; if (state->crtc_num >= 0) { if (state->crtc_num >= crtc_count) { if (crtc_count > 1) weprintf(_("CRTC %i does not exist, valid CRTCs are [0, %i]."), state->crtc_num, crtc_count-1); else weprintf(_("CRTC %i does not exist, only CRTC 0 exists."), state->crtc_num); close(state->fd); state->fd = -1; drmModeFreeResources(state->res); state->res = NULL; return -1; } state->crtcs = malloc(2 * sizeof(struct drm_crtc_state)); state->crtcs[1].crtc_num = -1; state->crtcs->crtc_num = state->crtc_num; state->crtcs->crtc_id = -1; state->crtcs->gamma_size = -1; state->crtcs->r_gamma = NULL; state->crtcs->g_gamma = NULL; state->crtcs->b_gamma = NULL; } else { int crtc_num; state->crtcs = malloc((crtc_count + 1) * sizeof(struct drm_crtc_state)); state->crtcs[crtc_count].crtc_num = -1; for (crtc_num = 0; crtc_num < crtc_count; crtc_num++) { state->crtcs[crtc_num].crtc_num = crtc_num; state->crtcs[crtc_num].crtc_id = -1; state->crtcs[crtc_num].gamma_size = -1; state->crtcs[crtc_num].r_gamma = NULL; state->crtcs[crtc_num].g_gamma = NULL; state->crtcs[crtc_num].b_gamma = NULL; } } /* Load CRTC information and gamma ramps. */ crtcs = state->crtcs; for (; crtcs->crtc_num >= 0; crtcs++) { drmModeCrtc *crtc_info; crtcs->crtc_id = state->res->crtcs[crtcs->crtc_num]; crtc_info = drmModeGetCrtc(state->fd, crtcs->crtc_id); if (crtc_info == NULL) { weprintf(_("CRTC %i lost, skipping"), crtcs->crtc_num); continue; } crtcs->gamma_size = crtc_info->gamma_size; drmModeFreeCrtc(crtc_info); if (crtcs->gamma_size <= 1) { weprintf(_("Could not get gamma ramp size for CRTC %i on graphics card" " %i, ignoring device."), crtcs->crtc_num, state->card_num); continue; } /* Valgrind complains about us reading uninitialize memory if we just use malloc. */ crtcs->r_gamma = calloc(3 * crtcs->gamma_size, sizeof(uint16_t)); crtcs->g_gamma = crtcs->r_gamma + crtcs->gamma_size; crtcs->b_gamma = crtcs->g_gamma + crtcs->gamma_size; if (crtcs->r_gamma) { int r = drmModeCrtcGetGamma(state->fd, crtcs->crtc_id, crtcs->gamma_size, crtcs->r_gamma, crtcs->g_gamma, crtcs->b_gamma); if (r < 0) { weprintf(_("DRM could not read gamma ramps on CRTC %i on graphics card" " %i, ignoring device."), crtcs->crtc_num, state->card_num); free(crtcs->r_gamma); crtcs->r_gamma = NULL; } } else { weprintf("malloc:"); drmModeFreeResources(state->res); state->res = NULL; close(state->fd); state->fd = -1; while (crtcs-- != state->crtcs) free(crtcs->r_gamma); free(state->crtcs); state->crtcs = NULL; return -1; } } return 0; } static void drm_restore(struct gamma_state *state) { struct drm_crtc_state *crtcs = state->crtcs; while (crtcs->crtc_num >= 0) { if (crtcs->r_gamma) { drmModeCrtcSetGamma(state->fd, crtcs->crtc_id, crtcs->gamma_size, crtcs->r_gamma, crtcs->g_gamma, crtcs->b_gamma); } crtcs++; } } static void drm_free(struct gamma_state *state) { if (state->crtcs) { struct drm_crtc_state *crtcs = state->crtcs; while (crtcs->crtc_num >= 0) { free(crtcs->r_gamma); crtcs->crtc_num = -1; crtcs++; } free(state->crtcs); state->crtcs = NULL; } if (state->res) { drmModeFreeResources(state->res); state->res = NULL; } if (state->fd >= 0) { close(state->fd); state->fd = -1; } free(state); } static void drm_print_help(FILE *f) { fputs(_("Adjust gamma ramps with Direct Rendering Manager.\n"), f); fputs("\n", f); /* TRANSLATORS: DRM help output left column must not be translated */ fputs(_(" card=N\tGraphics card to apply adjustments to\n" " crtc=N\tCRTC to apply adjustments to\n"), f); /* TODO list is not supported */ fputs("\n", f); } static int drm_set_option(struct gamma_state *state, const char *key, const char *value) { if (!strcasecmp(key, "card")) { state->card_num = atoi(value); } else if (!strcasecmp(key, "crtc")) { if (!strcasecmp(value, "all")) { state->crtc_num = -1; } else { state->crtc_num = atoi(value); if (state->crtc_num < 0) { weprintf(_("CRTC must be a non-negative integer")); return -1; } } } else { weprintf(_("Unknown method parameter: `%s'."), key); return -1; } return 0; } static int drm_apply(struct gamma_state *state, const struct colour_setting *setting, int preserve) { struct drm_crtc_state *crtcs = state->crtcs; uint32_t last_gamma_size = 0; uint16_t *r_gamma = NULL; uint16_t *g_gamma = NULL; uint16_t *b_gamma = NULL; uint16_t value; uint32_t i, ramp_size; (void) preserve; /* TODO */ for (; crtcs->crtc_num >= 0; crtcs++) { if (crtcs->gamma_size <= 1) continue; if (crtcs->gamma_size != last_gamma_size) { if (crtcs->gamma_size > last_gamma_size) { r_gamma = erealloc(r_gamma, 3 * crtcs->gamma_size * sizeof(uint16_t)); g_gamma = r_gamma + crtcs->gamma_size; b_gamma = g_gamma + crtcs->gamma_size; } last_gamma_size = crtcs->gamma_size; } /* Initialize gamma ramps to pure state */ ramp_size = crtcs->gamma_size; for (i = 0; i < ramp_size; i++) { value = (uint16_t)((double)i / (ramp_size - 1) * UINT16_MAX); r_gamma[i] = value; g_gamma[i] = value; b_gamma[i] = value; } fill_ramps_u16(r_gamma, g_gamma, b_gamma, crtcs->gamma_size, crtcs->gamma_size, crtcs->gamma_size, setting); drmModeCrtcSetGamma(state->fd, crtcs->crtc_id, crtcs->gamma_size, r_gamma, g_gamma, b_gamma); } free(r_gamma); return 0; } const struct gamma_method drm_gamma_method = GAMMA_METHOD_INIT("drm", 0, 0, drm);