/*
 * Copyright © 2006 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

#include "randrstr.h"
#include "swaprep.h"

RESTYPE RRCrtcType;

/*
 * Notify the CRTC of some change
 */
void
RRCrtcChanged(RRCrtcPtr crtc, Bool layoutChanged)
{
    ScreenPtr pScreen = crtc->pScreen;

    crtc->changed = TRUE;
    if (pScreen) {
        rrScrPriv(pScreen);

        pScrPriv->changed = TRUE;
        /*
         * Send ConfigureNotify on any layout change
         */
        if (layoutChanged)
            pScrPriv->layoutChanged = TRUE;
    }
}

/*
 * Create a CRTC
 */
RRCrtcPtr
RRCrtcCreate(void *devPrivate)
{
    RRCrtcPtr crtc;

    if (!RRInit())
        return NULL;
    crtc = malloc(sizeof(RRCrtcRec));
    if (!crtc)
        return NULL;
    crtc->id = FakeClientID(0);
    crtc->pScreen = NULL;
    crtc->mode = NULL;
    crtc->x = 0;
    crtc->y = 0;
    crtc->rotation = RR_Rotate_0;
    crtc->rotations = RR_Rotate_0;
    crtc->outputs = NULL;
    crtc->numOutputs = 0;
    crtc->gammaSize = 0;
    crtc->gammaRed = crtc->gammaBlue = crtc->gammaGreen = NULL;
    crtc->changed = FALSE;
    crtc->devPrivate = devPrivate;

    if (!AddResource(crtc->id, RRCrtcType, (pointer) crtc))
        return NULL;

    return crtc;
}

/*
 * Set the allowed rotations on a CRTC
 */
void
RRCrtcSetRotations(RRCrtcPtr crtc, Rotation rotations)
{
    crtc->rotations = rotations;
}

/*
 * Attach a Crtc to a screen. This is done as a separate step
 * so that an xf86-based driver can create CRTCs in PreInit
 * before the Screen has been created
 */

Bool
RRCrtcAttachScreen(RRCrtcPtr crtc, ScreenPtr pScreen)
{
    rrScrPriv(pScreen);
    RRCrtcPtr *crtcs;

    /* make space for the crtc pointer */
    if (pScrPriv->numCrtcs)
        crtcs = realloc(pScrPriv->crtcs,
                         (pScrPriv->numCrtcs + 1) * sizeof(RRCrtcPtr));
    else
        crtcs = malloc(sizeof(RRCrtcPtr));
    if (!crtcs)
        return FALSE;

    /* attach the screen and crtc together */
    crtc->pScreen = pScreen;
    pScrPriv->crtcs = crtcs;
    pScrPriv->crtcs[pScrPriv->numCrtcs++] = crtc;

    RRCrtcChanged(crtc, TRUE);
    return TRUE;
}

/*
 * Notify the extension that the Crtc has been reconfigured,
 * the driver calls this whenever it has updated the mode
 */
Bool
RRCrtcNotify(RRCrtcPtr crtc,
             RRModePtr mode,
             int x,
             int y, Rotation rotation, int numOutputs, RROutputPtr * outputs)
{
    int i, j;

    /*
     * Check to see if any of the new outputs were
     * not in the old list and mark them as changed
     */
    for (i = 0; i < numOutputs; i++) {
        for (j = 0; j < crtc->numOutputs; j++)
            if (outputs[i] == crtc->outputs[j])
                break;
        if (j == crtc->numOutputs) {
            RROutputChanged(outputs[i], FALSE);
            RRCrtcChanged(crtc, FALSE);
        }
    }
    /*
     * Check to see if any of the old outputs are
     * not in the new list and mark them as changed
     */
    for (j = 0; j < crtc->numOutputs; j++) {
        for (i = 0; i < numOutputs; i++)
            if (outputs[i] == crtc->outputs[j])
                break;
        if (i == numOutputs) {
            RROutputChanged(crtc->outputs[j], FALSE);
            RRCrtcChanged(crtc, FALSE);
        }
    }
    /*
     * Reallocate the crtc output array if necessary
     */
    if (numOutputs != crtc->numOutputs) {
        RROutputPtr *newoutputs;

        if (numOutputs) {
            if (crtc->numOutputs)
                newoutputs = realloc(crtc->outputs,
                                      numOutputs * sizeof(RROutputPtr));
            else
                newoutputs = malloc(numOutputs * sizeof(RROutputPtr));
            if (!newoutputs)
                return FALSE;
        }
        else {
            if (crtc->outputs)
                free(crtc->outputs);
            newoutputs = NULL;
        }
        crtc->outputs = newoutputs;
        crtc->numOutputs = numOutputs;
    }
    /*
     * Copy the new list of outputs into the crtc
     */
    memcpy(crtc->outputs, outputs, numOutputs * sizeof(RROutputPtr));
    /*
     * Update remaining crtc fields
     */
    if (mode != crtc->mode) {
        if (crtc->mode)
            RRModeDestroy(crtc->mode);
        crtc->mode = mode;
        if (mode != NULL)
            mode->refcnt++;
        RRCrtcChanged(crtc, TRUE);
    }
    if (x != crtc->x) {
        crtc->x = x;
        RRCrtcChanged(crtc, TRUE);
    }
    if (y != crtc->y) {
        crtc->y = y;
        RRCrtcChanged(crtc, TRUE);
    }
    if (rotation != crtc->rotation) {
        crtc->rotation = rotation;
        RRCrtcChanged(crtc, TRUE);
    }
    return TRUE;
}

void
RRDeliverCrtcEvent(ClientPtr client, WindowPtr pWin, RRCrtcPtr crtc)
{
    ScreenPtr pScreen = pWin->drawable.pScreen;

    rrScrPriv(pScreen);
    xRRCrtcChangeNotifyEvent ce;

    RRModePtr mode = crtc->mode;

    ce.type = RRNotify + RREventBase;
    ce.subCode = RRNotify_CrtcChange;
    ce.timestamp = pScrPriv->lastSetTime.milliseconds;
    ce.window = pWin->drawable.id;
    ce.crtc = crtc->id;
    ce.rotation = crtc->rotation;
    if (mode) {
        ce.mode = mode->mode.id;
        ce.x = crtc->x;
        ce.y = crtc->y;
        ce.width = mode->mode.width;
        ce.height = mode->mode.height;
    }
    else {
        ce.mode = None;
        ce.x = 0;
        ce.y = 0;
        ce.width = 0;
        ce.height = 0;
    }
    WriteEventsToClient(client, 1, (xEvent *) &ce);
}

/*
 * Request that the Crtc be reconfigured
 */
Bool
RRCrtcSet(RRCrtcPtr crtc,
          RRModePtr mode,
          int x,
          int y, Rotation rotation, int numOutputs, RROutputPtr * outputs)
{
    ScreenPtr pScreen = crtc->pScreen;

    /* See if nothing changed */
    if (crtc->mode == mode &&
        crtc->x == x &&
        crtc->y == y &&
        crtc->rotation == rotation &&
        crtc->numOutputs == numOutputs &&
        !memcmp(crtc->outputs, outputs, numOutputs * sizeof(RROutputPtr))) {
        return TRUE;
    }
    if (pScreen) {
#if RANDR_12_INTERFACE
        rrScrPriv(pScreen);
        if (pScrPriv->rrCrtcSet) {
            return (*pScrPriv->rrCrtcSet) (pScreen, crtc, mode, x, y,
                                           rotation, numOutputs, outputs);
        }
#endif
#if RANDR_10_INTERFACE
        if (pScrPriv->rrSetConfig) {
            RRScreenSize size;
            RRScreenRate rate;

	    if (!mode) {
	        RRCrtcNotify(crtc, NULL, x, y, rotation, 0, NULL);
	        return TRUE;
	    }

            Bool ret;

            size.width = mode->mode.width;
            size.height = mode->mode.height;
            if (outputs[0]->mmWidth && outputs[0]->mmHeight) {
                size.mmWidth = outputs[0]->mmWidth;
                size.mmHeight = outputs[0]->mmHeight;
            }
            else {
                size.mmWidth = pScreen->mmWidth;
                size.mmHeight = pScreen->mmHeight;
            }
            size.nRates = 1;
            rate.rate = RRVerticalRefresh(&mode->mode);
            size.pRates = &rate;
            ret =
                (*pScrPriv->rrSetConfig) (pScreen, rotation, rate.rate, &size);
            /*
             * Old 1.0 interface tied screen size to mode size
             */
            if (ret) {
                RRCrtcNotify(crtc, mode, x, y, rotation, 1, outputs);
                RRScreenSizeNotify(pScreen);
            }
            return ret;
        }
#endif
        RRTellChanged(pScreen);
    }
    return FALSE;
}

/*
 * Destroy a Crtc at shutdown
 */
void
RRCrtcDestroy(RRCrtcPtr crtc)
{
    FreeResource(crtc->id, 0);
}

static int
RRCrtcDestroyResource(pointer value, XID pid)
{
    RRCrtcPtr crtc = (RRCrtcPtr) value;

    ScreenPtr pScreen = crtc->pScreen;

    if (pScreen) {
        rrScrPriv(pScreen);
        int i;

        for (i = 0; i < pScrPriv->numCrtcs; i++) {
            if (pScrPriv->crtcs[i] == crtc) {
                memmove(pScrPriv->crtcs + i, pScrPriv->crtcs + i + 1,
                        (pScrPriv->numCrtcs - (i + 1)) * sizeof(RRCrtcPtr));
                --pScrPriv->numCrtcs;
                break;
            }
        }
    }
    if (crtc->gammaRed)
        free(crtc->gammaRed);
    free(crtc);
    return 1;
}

/*
 * Request that the Crtc gamma be changed
 */

Bool
RRCrtcGammaSet(RRCrtcPtr crtc, CARD16 *red, CARD16 *green, CARD16 *blue)
{
    Bool ret = TRUE;

#if RANDR_12_INTERFACE
    ScreenPtr pScreen = crtc->pScreen;
#endif

    memcpy(crtc->gammaRed, red, crtc->gammaSize * sizeof(CARD16));
    memcpy(crtc->gammaGreen, green, crtc->gammaSize * sizeof(CARD16));
    memcpy(crtc->gammaBlue, blue, crtc->gammaSize * sizeof(CARD16));
#if RANDR_12_INTERFACE
    if (pScreen) {
        rrScrPriv(pScreen);
        if (pScrPriv->rrCrtcSetGamma)
            ret = (*pScrPriv->rrCrtcSetGamma) (pScreen, crtc);
    }
#endif
    return ret;
}

/*
 * Notify the extension that the Crtc gamma has been changed
 * The driver calls this whenever it has changed the gamma values
 * in the RRCrtcRec
 */

Bool
RRCrtcGammaNotify(RRCrtcPtr crtc)
{
    return TRUE;                /* not much going on here */
}

/**
 * Returns the width/height that the crtc scans out from the framebuffer
 */
void
RRCrtcGetScanoutSize(RRCrtcPtr crtc, int *width, int *height)
{
    if (crtc->mode == NULL) {
        *width = 0;
        *height = 0;
        return;
    }

    switch (crtc->rotation & 0xf) {
    case RR_Rotate_0:
    case RR_Rotate_180:
        *width = crtc->mode->mode.width;
        *height = crtc->mode->mode.height;
        break;
    case RR_Rotate_90:
    case RR_Rotate_270:
        *width = crtc->mode->mode.height;
        *height = crtc->mode->mode.width;
        break;
    }
}

/*
 * Set the size of the gamma table at server startup time
 */

Bool
RRCrtcGammaSetSize(RRCrtcPtr crtc, int size)
{
    CARD16 *gamma;

    if (size == crtc->gammaSize)
        return TRUE;
    if (size) {
        gamma = malloc(size * 3 * sizeof(CARD16));
        if (!gamma)
            return FALSE;
    }
    else
        gamma = NULL;
    if (crtc->gammaRed)
        free(crtc->gammaRed);
    crtc->gammaRed = gamma;
    crtc->gammaGreen = gamma + size;
    crtc->gammaBlue = gamma + size * 2;
    crtc->gammaSize = size;
    return TRUE;
}

/*
 * Initialize crtc type
 */
Bool
RRCrtcInit(void)
{
    RRCrtcType = CreateNewResourceType(RRCrtcDestroyResource);
    if (!RRCrtcType)
        return FALSE;
#ifdef XResExtension
    RegisterResourceName(RRCrtcType, "CRTC");
#endif
    return TRUE;
}

int
ProcRRGetCrtcInfo(ClientPtr client)
{
    REQUEST(xRRGetCrtcInfoReq);;
    xRRGetCrtcInfoReply rep;

    RRCrtcPtr crtc;

    CARD8 *extra;

    unsigned long extraLen;

    ScreenPtr pScreen;

    rrScrPrivPtr pScrPriv;

    RRModePtr mode;

    RROutput *outputs;

    RROutput *possible;

    int i, j, k;

    REQUEST_SIZE_MATCH(xRRGetCrtcInfoReq);
    crtc = LookupCrtc(client, stuff->crtc, SecurityReadAccess);

    if (!crtc)
        return RRErrorBase + BadRRCrtc;

    /* All crtcs must be associated with screens before client
     * requests are processed
     */
    pScreen = crtc->pScreen;
    pScrPriv = rrGetScrPriv(pScreen);

    mode = crtc->mode;

    rep.type = X_Reply;
    rep.status = RRSetConfigSuccess;
    rep.sequenceNumber = client->sequence;
    rep.length = 0;
    rep.timestamp = pScrPriv->lastSetTime.milliseconds;
    rep.x = crtc->x;
    rep.y = crtc->y;
    rep.width = mode ? mode->mode.width : 0;
    rep.height = mode ? mode->mode.height : 0;
    rep.mode = mode ? mode->mode.id : 0;
    rep.rotation = crtc->rotation;
    rep.rotations = crtc->rotations;
    rep.nOutput = crtc->numOutputs;
    k = 0;
    for (i = 0; i < pScrPriv->numOutputs; i++)
        for (j = 0; j < pScrPriv->outputs[i]->numCrtcs; j++)
            if (pScrPriv->outputs[i]->crtcs[j] == crtc)
                k++;
    rep.nPossibleOutput = k;

    rep.length = rep.nOutput + rep.nPossibleOutput;

    extraLen = rep.length << 2;
    if (extraLen) {
        extra = malloc(extraLen);
        if (!extra)
            return BadAlloc;
    }
    else
        extra = NULL;

    outputs = (RROutput *) extra;
    possible = (RROutput *) (outputs + rep.nOutput);

    for (i = 0; i < crtc->numOutputs; i++) {
        outputs[i] = crtc->outputs[i]->id;
        if (client->swapped)
            swapl(&outputs[i]);
    }
    k = 0;
    for (i = 0; i < pScrPriv->numOutputs; i++)
        for (j = 0; j < pScrPriv->outputs[i]->numCrtcs; j++)
            if (pScrPriv->outputs[i]->crtcs[j] == crtc) {
                possible[k] = pScrPriv->outputs[i]->id;
                if (client->swapped)
                    swapl(&possible[k]);
                k++;
            }

    if (client->swapped) {
        swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swapl(&rep.timestamp);
        swaps(&rep.x);
        swaps(&rep.y);
        swaps(&rep.width);
        swaps(&rep.height);
        swapl(&rep.mode);
        swaps(&rep.rotation);
        swaps(&rep.rotations);
        swaps(&rep.nOutput);
        swaps(&rep.nPossibleOutput);
    }
    WriteToClient(client, sizeof(xRRGetCrtcInfoReply), (char *) &rep);
    if (extraLen) {
        WriteToClient(client, extraLen, (char *) extra);
        free(extra);
    }

    return client->noClientException;
}

int
ProcRRSetCrtcConfig(ClientPtr client)
{
    REQUEST(xRRSetCrtcConfigReq);
    xRRSetCrtcConfigReply rep;

    ScreenPtr pScreen;

    rrScrPrivPtr pScrPriv;

    RRCrtcPtr crtc;

    RRModePtr mode;

    int numOutputs;

    RROutputPtr *outputs = NULL;

    RROutput *outputIds;

    TimeStamp configTime;

    TimeStamp time;

    Rotation rotation;

    int i, j;

    REQUEST_AT_LEAST_SIZE(xRRSetCrtcConfigReq);
    numOutputs = (stuff->length - (SIZEOF(xRRSetCrtcConfigReq) >> 2));

    crtc = LookupIDByType(stuff->crtc, RRCrtcType);
    if (!crtc) {
        client->errorValue = stuff->crtc;
        return RRErrorBase + BadRRCrtc;
    }
    if (stuff->mode == None) {
        mode = NULL;
        if (numOutputs > 0)
            return BadMatch;
    }
    else {
        mode = LookupIDByType(stuff->mode, RRModeType);
        if (!mode) {
            client->errorValue = stuff->mode;
            return RRErrorBase + BadRRMode;
        }
        if (numOutputs == 0)
            return BadMatch;
    }
    if (numOutputs) {
        outputs = malloc(numOutputs * sizeof(RROutputPtr));
        if (!outputs)
            return BadAlloc;
    }
    else
        outputs = NULL;

    outputIds = (RROutput *) (stuff + 1);
    for (i = 0; i < numOutputs; i++) {
        outputs[i] = (RROutputPtr) LookupIDByType(outputIds[i], RROutputType);
        if (!outputs[i]) {
            client->errorValue = outputIds[i];
            if (outputs)
                free(outputs);
            return RRErrorBase + BadRROutput;
        }
        /* validate crtc for this output */
        for (j = 0; j < outputs[i]->numCrtcs; j++)
            if (outputs[i]->crtcs[j] == crtc)
                break;
        if (j == outputs[i]->numCrtcs) {
            if (outputs)
                free(outputs);
            return BadMatch;
        }
        /* validate mode for this output */
        for (j = 0; j < outputs[i]->numModes; j++)
            if (outputs[i]->modes[j] == mode)
                break;
        if (j == outputs[i]->numModes) {
            if (outputs)
                free(outputs);
            return BadMatch;
        }
    }

    pScreen = crtc->pScreen;
    pScrPriv = rrGetScrPriv(pScreen);

    time = ClientTimeToServerTime(stuff->timestamp);
    configTime = ClientTimeToServerTime(stuff->configTimestamp);

    if (!pScrPriv) {
        rep.status = RRSetConfigFailed;
        goto sendReply;
    }

    /*
     * if the client's config timestamp is not the same as the last config
     * timestamp, then the config information isn't up-to-date and
     * can't even be validated
     */
    if (CompareTimeStamps(configTime, pScrPriv->lastConfigTime) != 0) {
        rep.status = RRSetConfigInvalidConfigTime;
        goto sendReply;
    }

    /*
     * Validate requested rotation
     */
    rotation = (Rotation) stuff->rotation;

    /* test the rotation bits only! */
    switch (rotation & 0xf) {
    case RR_Rotate_0:
    case RR_Rotate_90:
    case RR_Rotate_180:
    case RR_Rotate_270:
        break;
    default:
        /*
         * Invalid rotation
         */
        client->errorValue = stuff->rotation;
        if (outputs)
            free(outputs);
        return BadValue;
    }

    if (mode) {
        if ((~crtc->rotations) & rotation) {
            /*
             * requested rotation or reflection not supported by screen
             */
            client->errorValue = stuff->rotation;
            if (outputs)
                free(outputs);
            return BadMatch;
        }

#ifdef RANDR_12_INTERFACE
        /*
         * Check screen size bounds if the DDX provides a 1.2 interface
         * for setting screen size. Else, assume the CrtcSet sets
         * the size along with the mode
         */
        if (pScrPriv->rrScreenSetSize) {
            int source_width = mode->mode.width;

            int source_height = mode->mode.height;

            if (rotation == RR_Rotate_90 || rotation == RR_Rotate_270) {
                source_width = mode->mode.height;
                source_height = mode->mode.width;
            }
            if (stuff->x + source_width > pScreen->width) {
                client->errorValue = stuff->x;
                if (outputs)
                    free(outputs);
                return BadValue;
            }

            if (stuff->y + source_height > pScreen->height) {
                client->errorValue = stuff->y;
                if (outputs)
                    free(outputs);
                return BadValue;
            }
        }
#endif
    }

    /*
     * Make sure the requested set-time is not older than
     * the last set-time
     */
    if (CompareTimeStamps(time, pScrPriv->lastSetTime) < 0) {
        rep.status = RRSetConfigInvalidTime;
        goto sendReply;
    }

    if (!RRCrtcSet(crtc, mode, stuff->x, stuff->y,
                   rotation, numOutputs, outputs)) {
        rep.status = RRSetConfigFailed;
        goto sendReply;
    }
    rep.status = RRSetConfigSuccess;

 sendReply:
    if (outputs)
        free(outputs);

    rep.type = X_Reply;
    /* rep.status has already been filled in */
    rep.length = 0;
    rep.sequenceNumber = client->sequence;
    rep.newTimestamp = pScrPriv->lastConfigTime.milliseconds;

    if (client->swapped) {
        swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swapl(&rep.newTimestamp);
    }
    WriteToClient(client, sizeof(xRRSetCrtcConfigReply), (char *) &rep);

    return client->noClientException;
}

int
ProcRRGetCrtcGammaSize(ClientPtr client)
{
    REQUEST(xRRGetCrtcGammaSizeReq);
    xRRGetCrtcGammaSizeReply reply;

    RRCrtcPtr crtc;

    REQUEST_SIZE_MATCH(xRRGetCrtcGammaSizeReq);
    crtc = LookupCrtc(client, stuff->crtc, SecurityReadAccess);
    if (!crtc)
        return RRErrorBase + BadRRCrtc;

    reply.type = X_Reply;
    reply.sequenceNumber = client->sequence;
    reply.length = 0;
    reply.size = crtc->gammaSize;
    if (client->swapped) {
        swaps(&reply.sequenceNumber);
        swapl(&reply.length);
        swaps(&reply.size);
    }
    WriteToClient(client, sizeof(xRRGetCrtcGammaSizeReply), (char *) &reply);
    return client->noClientException;
}

int
ProcRRGetCrtcGamma(ClientPtr client)
{
    REQUEST(xRRGetCrtcGammaReq);
    xRRGetCrtcGammaReply reply;

    RRCrtcPtr crtc;

    unsigned long len;

    REQUEST_SIZE_MATCH(xRRGetCrtcGammaReq);
    crtc = LookupCrtc(client, stuff->crtc, SecurityReadAccess);
    if (!crtc)
        return RRErrorBase + BadRRCrtc;

    len = crtc->gammaSize * 3 * 2;

    reply.type = X_Reply;
    reply.sequenceNumber = client->sequence;
    reply.length = (len + 3) >> 2;
    reply.size = crtc->gammaSize;
    if (client->swapped) {
        swaps(&reply.sequenceNumber);
        swapl(&reply.length);
        swaps(&reply.size);
    }
    WriteToClient(client, sizeof(xRRGetCrtcGammaReply), (char *) &reply);
    if (crtc->gammaSize) {
        client->pSwapReplyFunc = (ReplySwapPtr) CopySwap16Write;
        WriteSwappedDataToClient(client, len, (char *) crtc->gammaRed);
    }
    return client->noClientException;
}

int
ProcRRSetCrtcGamma(ClientPtr client)
{
    REQUEST(xRRSetCrtcGammaReq);
    RRCrtcPtr crtc;

    unsigned long len;

    CARD16 *red, *green, *blue;

    REQUEST_AT_LEAST_SIZE(xRRSetCrtcGammaReq);
    crtc = LookupCrtc(client, stuff->crtc, SecurityWriteAccess);
    if (!crtc)
        return RRErrorBase + BadRRCrtc;

    len = client->req_len - (sizeof(xRRSetCrtcGammaReq) >> 2);
    if (len < (stuff->size * 3 + 1) >> 1)
        return BadLength;

    if (stuff->size != crtc->gammaSize)
        return BadMatch;

    red = (CARD16 *) (stuff + 1);
    green = red + crtc->gammaSize;
    blue = green + crtc->gammaSize;

    RRCrtcGammaSet(crtc, red, green, blue);

    return Success;
}
