~aperezdc/drmgamma

54a1455a36a15a246dab57bdb437b5923ebbaf09 — Adrian Perez de Castro 4 years ago
Initial import
2 files changed, 267 insertions(+), 0 deletions(-)

A drmgamma.c
A meson.build
A  => drmgamma.c +253 -0
@@ 1,253 @@
/*
 * drmgamma.c
 * Copyright (C) 2020 Adrian Perez de Castro <aperez@igalia.com>
 *
 * Distributed under terms of the MIT license.
 */

#define _POSIX_C_SOURCE 2

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <math.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

static int
usage(int code)
{
    FILE *f = (code == EXIT_SUCCESS) ? stdout : stderr;
    fputs("usage: drmgamma -d DEVICE [-r NUM] [-g NUM] [-b NUM]\n", f);
    return code;
}

static int
error(const char *fmt, ...)
{
    assert(fmt);

    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fputc('\n', stderr);
    fflush(stderr);
    return EXIT_FAILURE;
}

static bool
optdouble(double *opt, const char *str)
{
    assert(opt);
    assert(str);

    errno = 0;
    char *endptr = NULL;
    *opt = strtod(str, &endptr);

    /* Conversion error */
    if (errno != 0 || str == endptr || *endptr != '\0')
        return false;

    /* Invalid values for gamma */
    if (isinf(*opt) || isnan(*opt) || *opt <= 0.0e-12)
        return false;

    return true;
}

static const char*
connector_type_string (uint32_t type)
{
    switch (type) {
#ifdef DRM_MODE_CONNECTOR_DPI
        case DRM_MODE_CONNECTOR_DPI: return "DPI";
#endif /* DRM_MODE_CONNECTOR_DPI */
#ifdef DRM_MODE_CONNECTOR_DSI
        case DRM_MODE_CONNECTOR_DSI: return "DSI";
#endif /* DRM_MODE_CONNECTOR_DSI */
#ifdef DRM_MODE_CONNECTOR_VIRTUAL
        case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual";
#endif /* DRM_MODE_CONNECTOR_VIRTUAL */
#ifdef DRM_MODE_CONNECTOR_eDP
        case DRM_MODE_CONNECTOR_eDP: return "eDP";
#endif /* DRM_MODE_CONNECTOR_eDP */
#ifdef DRM_MODE_CONNECTOR_TV
        case DRM_MODE_CONNECTOR_TV: return "TV";
#endif /* DRM_MODE_CONNECTOR_TV */
#ifdef DRM_MODE_CONNECTOR_HDMIB
        case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B";
#endif /* DRM_MODE_CONNECTOR_HDMIB */
#ifdef DRM_MODE_CONNECTOR_HDMIA
        case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A";
#endif /* DRM_MODE_CONNECTOR_HDMIA */
#ifdef DRM_MODE_CONNECTOR_DisplayPort
        case DRM_MODE_CONNECTOR_DisplayPort: return "DisplayPort";
#endif /* DRM_MODE_CONNECTOR_DisplayPort */
#ifdef DRM_MODE_CONNECTOR_9PinDIN
        case DRM_MODE_CONNECTOR_9PinDIN: return "9-pin DIN";
#endif /* DRM_MODE_CONNECTOR_9PinDIN */
#ifdef DRM_MODE_CONNECTOR_Component
        case DRM_MODE_CONNECTOR_Component: return "Component";
#endif /* DRM_MODE_CONNECTOR_Component */
#ifdef DRM_MODE_CONNECTOR_LVDS
        case DRM_MODE_CONNECTOR_LVDS: return "LVDS";
#endif /* DRM_MODE_CONNECTOR_LVDS */
#ifdef DRM_MODE_CONNECTOR_SVIDEO
        case DRM_MODE_CONNECTOR_SVIDEO: return "S-Video";
#endif /* DRM_MODE_CONNECTOR_SVIDEO */
#ifdef DRM_MODE_CONNECTOR_Composite
        case DRM_MODE_CONNECTOR_Composite: return "Composite";
#endif /* DRM_MODE_CONNECTOR_Composite */
#ifdef DRM_MODE_CONNECTOR_DVIA
        case DRM_MODE_CONNECTOR_DVIA: return "DVI-A";
#endif /* DRM_MODE_CONNECTOR_DVIA */
#ifdef DRM_MODE_CONNECTOR_DVID
        case DRM_MODE_CONNECTOR_DVID: return "DVI-D";
#endif /* DRM_MODE_CONNECTOR_DVID */
#ifdef DRM_MODE_CONNECTOR_DVII
        case DRM_MODE_CONNECTOR_DVII: return "DVI-I";
#endif /* DRM_MODE_CONNECTOR_DVII */
#ifdef DRM_MODE_CONNECTOR_VGA
        case DRM_MODE_CONNECTOR_VGA: return "VGA";
#endif /* DRM_MODE_CONNECTOR_VGA */
#ifdef DRM_MODE_CONNECTOR_Unknown
        case DRM_MODE_CONNECTOR_Unknown:
#endif /* DRM_MODE_CONNECTOR_Unknown */
        default: return "Unknown";
    }
}

static bool opt_verbose = false;

static void
verbose(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    if (opt_verbose)
        vprintf(fmt, args);
    va_end(args);
}

int
main(int argc, char *argv[])
{
    const char *devnode = NULL;

    double gamma_r = 1.0;
    double gamma_g = 1.0;
    double gamma_b = 1.0;

    int opt;
    while ((opt = getopt(argc, argv, "hvd:r:g:b:")) != -1) {
        switch (opt) {
            case 'v':
                opt_verbose = true;
                break;
            case 'd':
                devnode = optarg;
                break;
            case 'r':
                if (!optdouble(&gamma_r, optarg))
                    return error("Invalid number: '%s'", optarg);
                break;
            case 'g':
                if (!optdouble(&gamma_g, optarg))
                    return error("Invalid number: '%s'", optarg);
                break;
            case 'b':
                if (!optdouble(&gamma_b, optarg))
                    return error("Invalid number: '%s'", optarg);
                break;
            case 'h':
                return usage(EXIT_SUCCESS);
            default:
                return usage(EXIT_FAILURE);
        }
    }

    if (!devnode)
        return error("DRM device not specified.");

    if (!drmAvailable())
        return error("DRM unavailable.");

    int fd = open(devnode, O_RDWR, 0);
    if (fd < 0)
        return error("Cannot open '%s': %s.", devnode, strerror(errno));

    if (!drmIsMaster(fd)) {
        if (drmSetMaster(fd))
            return error("Could not become DRM master: %s.", strerror(errno));
    }

    drmModeRes *resources = drmModeGetResources(fd);
    if (resources && resources->count_connectors) {
        for (int i = 0; i < resources->count_connectors; ++i) {
            drmModeConnector *connector =
                drmModeGetConnector(fd, resources->connectors[i]);
            if (connector->connection != DRM_MODE_CONNECTED) {
                verbose("Skipping %s: disconnected.\n",
                        connector_type_string(connector->connector_type));
            } else if (connector->count_modes == 0) {
                verbose("Skipping %s: no usable modes.\n",
                        connector_type_string(connector->connector_type));
            } else {
                /* Find the encoder that is driving this connector. */
                drmModeEncoder *encoder = NULL;
                for (int j = 0; j < resources->count_encoders; ++j) {
                    drmModeEncoder *cur_encoder =
                        drmModeGetEncoder(fd, resources->encoders[j]);
                    if (cur_encoder->encoder_id == connector->encoder_id) {
                        encoder = cur_encoder;
                        break;
                    }
                    drmModeFreeEncoder(cur_encoder);
                }

                if (encoder) {
                    drmModeCrtc *crtc = drmModeGetCrtc(fd, encoder->crtc_id);
                    verbose("Setting %s, CRTC %" PRIu32 ", %d gamma stops.\n",
                            connector_type_string(connector->connector_type),
                            encoder->crtc_id, crtc->gamma_size);

                    const uint32_t size = crtc->gamma_size;

                    /* Calculate gamma ramp LUTs. */
                    uint16_t lut_r[size], lut_g[size], lut_b[size];

                    for (size_t k = 0; k < size; ++k) {
                        const double g = (double) UINT16_MAX * k / size;
                        lut_r[k] = g * gamma_r;
                        lut_g[k] = g * gamma_g;
                        lut_b[k] = g * gamma_b;
                    }

                    if (drmModeCrtcSetGamma(fd, crtc->crtc_id, size,
                                            lut_r, lut_g, lut_b) != 0) {
                        verbose("Could not set gamma: %s.\n", strerror(errno));
                    }

                    drmModeFreeCrtc(crtc);
                    drmModeFreeEncoder(encoder);
                } else {
                    verbose("Skipping %s: could not find encoder.\n",
                            connector_type_string(connector->connector_type));
                }
            }
            drmModeFreeConnector(connector);
        }
    }

    close(fd);
    return EXIT_SUCCESS;
}

A  => meson.build +14 -0
@@ 1,14 @@
project('drmgamma', 'c',
	meson_version: '>=0.49',
	default_options: [
		'b_ndebug=if-release',
		'c_std=c99',
	],
	license: 'MIT',
	version: '1',
)

executable('drmgamma',
	'drmgamma.c',
	dependencies: dependency('libdrm'),
)