/*
 * 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"

static const int padlength[4] = { 0, 3, 2, 1 };

static CARD16
 RR10CurrentSizeID(ScreenPtr pScreen);

/*
 * Edit connection information block so that new clients
 * see the current screen size on connect
 */
static void
RREditConnectionInfo(ScreenPtr pScreen)
{
    xConnSetup *connSetup;
    char *vendor;
    xPixmapFormat *formats;
    xWindowRoot *root;
    xDepth *depth;
    xVisualType *visual;
    int screen = 0;
    int d;

    connSetup = (xConnSetup *) ConnectionInfo;
    vendor = (char *) connSetup + sizeof(xConnSetup);
    formats = (xPixmapFormat *) ((char *) vendor +
                                 connSetup->nbytesVendor +
                                 padlength[connSetup->nbytesVendor & 3]);
    root = (xWindowRoot *) ((char *) formats +
                            sizeof(xPixmapFormat) *
                            screenInfo.numPixmapFormats);
    while (screen != pScreen->myNum) {
        depth = (xDepth *) ((char *) root + sizeof(xWindowRoot));
        for (d = 0; d < root->nDepths; d++) {
            visual = (xVisualType *) ((char *) depth + sizeof(xDepth));
            depth = (xDepth *) ((char *) visual +
                                depth->nVisuals * sizeof(xVisualType));
        }
        root = (xWindowRoot *) ((char *) depth);
        screen++;
    }
    root->pixWidth = pScreen->width;
    root->pixHeight = pScreen->height;
    root->mmWidth = pScreen->mmWidth;
    root->mmHeight = pScreen->mmHeight;
}

void
RRSendConfigNotify(ScreenPtr pScreen)
{
    WindowPtr pWin = pScreen->root;
    xEvent event;

    event.u.u.type = ConfigureNotify;
    event.u.configureNotify.window = pWin->drawable.id;
    event.u.configureNotify.aboveSibling = None;
    event.u.configureNotify.x = 0;
    event.u.configureNotify.y = 0;

    /* XXX xinerama stuff ? */

    event.u.configureNotify.width = pWin->drawable.width;
    event.u.configureNotify.height = pWin->drawable.height;
    event.u.configureNotify.borderWidth = wBorderWidth(pWin);
    event.u.configureNotify.override = pWin->overrideRedirect;
    DeliverEvents(pWin, &event, 1, NullWindow);
}

void
RRDeliverScreenEvent(ClientPtr client, WindowPtr pWin, ScreenPtr pScreen)
{
    rrScrPriv(pScreen);
    xRRScreenChangeNotifyEvent se;
    RRCrtcPtr crtc = pScrPriv->numCrtcs ? pScrPriv->crtcs[0] : NULL;
    WindowPtr pRoot = pScreen->root;

    se.type = RRScreenChangeNotify + RREventBase;
    se.rotation = (CARD8) (crtc ? crtc->rotation : RR_Rotate_0);
    se.timestamp = pScrPriv->lastSetTime.milliseconds;
    se.configTimestamp = pScrPriv->lastConfigTime.milliseconds;
    se.root = pRoot->drawable.id;
    se.window = pWin->drawable.id;
    se.subpixelOrder = PictureGetSubpixelOrder(pScreen);

    se.sizeID = RR10CurrentSizeID(pScreen);

    if (se.rotation & (RR_Rotate_90 | RR_Rotate_270)) {
        se.widthInPixels = pScreen->height;
        se.heightInPixels = pScreen->width;
        se.widthInMillimeters = pScreen->mmHeight;
        se.heightInMillimeters = pScreen->mmWidth;
    }
    else {
        se.widthInPixels = pScreen->width;
        se.heightInPixels = pScreen->height;
        se.widthInMillimeters = pScreen->mmWidth;
        se.heightInMillimeters = pScreen->mmHeight;
    }

    WriteEventsToClient(client, 1, (xEvent *) &se);
}

/*
 * Notify the extension that the screen size has been changed.
 * The driver is responsible for calling this whenever it has changed
 * the size of the screen
 */
void
RRScreenSizeNotify(ScreenPtr pScreen)
{
    rrScrPriv(pScreen);
    /*
     * Deliver ConfigureNotify events when root changes
     * pixel size
     */
    if (pScrPriv->width == pScreen->width &&
        pScrPriv->height == pScreen->height &&
        pScrPriv->mmWidth == pScreen->mmWidth &&
        pScrPriv->mmHeight == pScreen->mmHeight)
        return;

    pScrPriv->width = pScreen->width;
    pScrPriv->height = pScreen->height;
    pScrPriv->mmWidth = pScreen->mmWidth;
    pScrPriv->mmHeight = pScreen->mmHeight;
    pScrPriv->changed = TRUE;
/*    pScrPriv->sizeChanged = TRUE; */

    RRTellChanged(pScreen);
    RRSendConfigNotify(pScreen);
    RREditConnectionInfo(pScreen);

    RRPointerScreenConfigured(pScreen);
    /*
     * Fix pointer bounds and location
     */
    ScreenRestructured(pScreen);
}

/*
 * Request that the screen be resized
 */
Bool
RRScreenSizeSet(ScreenPtr pScreen,
                CARD16 width, CARD16 height, CARD32 mmWidth, CARD32 mmHeight)
{
    rrScrPriv(pScreen);

#if RANDR_12_INTERFACE
    if (pScrPriv->rrScreenSetSize) {
        return (*pScrPriv->rrScreenSetSize) (pScreen,
                                             width, height, mmWidth, mmHeight);
    }
#endif
#if RANDR_10_INTERFACE
    if (pScrPriv->rrSetConfig) {
        return TRUE;            /* can't set size separately */
    }
#endif
    return FALSE;
}

/*
 * Retrieve valid screen size range
 */
int
ProcRRGetScreenSizeRange(ClientPtr client)
{
    REQUEST(xRRGetScreenSizeRangeReq);
    xRRGetScreenSizeRangeReply rep;
    WindowPtr pWin;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    int rc;

    REQUEST_SIZE_MATCH(xRRGetScreenInfoReq);
    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
        return rc;

    pScreen = pWin->drawable.pScreen;
    pScrPriv = rrGetScrPriv(pScreen);

    rep.type = X_Reply;
    rep.pad = 0;
    rep.sequenceNumber = client->sequence;
    rep.length = 0;

    if (pScrPriv) {
        if (!RRGetInfo(pScreen, FALSE))
            return BadAlloc;
        rep.minWidth = pScrPriv->minWidth;
        rep.minHeight = pScrPriv->minHeight;
        rep.maxWidth = pScrPriv->maxWidth;
        rep.maxHeight = pScrPriv->maxHeight;
    }
    else {
        rep.maxWidth = rep.minWidth = pScreen->width;
        rep.maxHeight = rep.minHeight = pScreen->height;
    }
    if (client->swapped) {
        swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swaps(&rep.minWidth);
        swaps(&rep.minHeight);
        swaps(&rep.maxWidth);
        swaps(&rep.maxHeight);
    }
    WriteToClient(client, sizeof(xRRGetScreenSizeRangeReply), (char *) &rep);
    return Success;
}

int
ProcRRSetScreenSize(ClientPtr client)
{
    REQUEST(xRRSetScreenSizeReq);
    WindowPtr pWin;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    int i, rc;

    REQUEST_SIZE_MATCH(xRRSetScreenSizeReq);
    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
        return rc;

    pScreen = pWin->drawable.pScreen;
    pScrPriv = rrGetScrPriv(pScreen);
    if (stuff->width < pScrPriv->minWidth || pScrPriv->maxWidth < stuff->width) {
        client->errorValue = stuff->width;
        return BadValue;
    }
    if (stuff->height < pScrPriv->minHeight ||
        pScrPriv->maxHeight < stuff->height) {
        client->errorValue = stuff->height;
        return BadValue;
    }
    for (i = 0; i < pScrPriv->numCrtcs; i++) {
        RRCrtcPtr crtc = pScrPriv->crtcs[i];
        RRModePtr mode = crtc->mode;

        if (mode) {
            int source_width = mode->mode.width;
            int source_height = mode->mode.height;
            Rotation rotation = crtc->rotation;

            if (rotation == RR_Rotate_90 || rotation == RR_Rotate_270) {
                source_width = mode->mode.height;
                source_height = mode->mode.width;
            }

            if (crtc->x + source_width > stuff->width ||
                crtc->y + source_height > stuff->height)
                return BadMatch;
        }
    }
    if (stuff->widthInMillimeters == 0 || stuff->heightInMillimeters == 0) {
        client->errorValue = 0;
        return BadValue;
    }
    if (!RRScreenSizeSet(pScreen,
                         stuff->width, stuff->height,
                         stuff->widthInMillimeters,
                         stuff->heightInMillimeters)) {
        return BadMatch;
    }
    return Success;
}

static int
rrGetScreenResources(ClientPtr client, Bool query)
{
    REQUEST(xRRGetScreenResourcesReq);
    xRRGetScreenResourcesReply rep;
    WindowPtr pWin;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    CARD8 *extra;
    unsigned long extraLen;
    int i, rc, has_primary = 0;
    RRCrtc *crtcs;
    RROutput *outputs;
    xRRModeInfo *modeinfos;
    CARD8 *names;

    REQUEST_SIZE_MATCH(xRRGetScreenResourcesReq);
    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
        return rc;

    pScreen = pWin->drawable.pScreen;
    pScrPriv = rrGetScrPriv(pScreen);
    rep.pad = 0;

    if (query && pScrPriv)
        if (!RRGetInfo(pScreen, query))
            return BadAlloc;

    if (!pScrPriv) {
        rep.type = X_Reply;
        rep.sequenceNumber = client->sequence;
        rep.length = 0;
        rep.timestamp = currentTime.milliseconds;
        rep.configTimestamp = currentTime.milliseconds;
        rep.nCrtcs = 0;
        rep.nOutputs = 0;
        rep.nModes = 0;
        rep.nbytesNames = 0;
        extra = NULL;
        extraLen = 0;
    }
    else {
        RRModePtr *modes;
        int num_modes;

        modes = RRModesForScreen(pScreen, &num_modes);
        if (!modes)
            return BadAlloc;

        rep.type = X_Reply;
        rep.sequenceNumber = client->sequence;
        rep.length = 0;
        rep.timestamp = pScrPriv->lastSetTime.milliseconds;
        rep.configTimestamp = pScrPriv->lastConfigTime.milliseconds;
        rep.nCrtcs = pScrPriv->numCrtcs;
        rep.nOutputs = pScrPriv->numOutputs;
        rep.nModes = num_modes;
        rep.nbytesNames = 0;

        for (i = 0; i < num_modes; i++)
            rep.nbytesNames += modes[i]->mode.nameLength;

        rep.length = (pScrPriv->numCrtcs +
                      pScrPriv->numOutputs +
                      num_modes * bytes_to_int32(SIZEOF(xRRModeInfo)) +
                      bytes_to_int32(rep.nbytesNames));

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

        crtcs = (RRCrtc *) extra;
        outputs = (RROutput *) (crtcs + pScrPriv->numCrtcs);
        modeinfos = (xRRModeInfo *) (outputs + pScrPriv->numOutputs);
        names = (CARD8 *) (modeinfos + num_modes);

        if (pScrPriv->primaryOutput && pScrPriv->primaryOutput->crtc) {
            has_primary = 1;
            crtcs[0] = pScrPriv->primaryOutput->crtc->id;
            if (client->swapped)
                swapl(&crtcs[0]);
        }

        for (i = 0; i < pScrPriv->numCrtcs; i++) {
            if (has_primary &&
                pScrPriv->primaryOutput->crtc == pScrPriv->crtcs[i]) {
                has_primary = 0;
                continue;
            }
            crtcs[i + has_primary] = pScrPriv->crtcs[i]->id;
            if (client->swapped)
                swapl(&crtcs[i + has_primary]);
        }

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

        for (i = 0; i < num_modes; i++) {
            RRModePtr mode = modes[i];

            modeinfos[i] = mode->mode;
            if (client->swapped) {
                swapl(&modeinfos[i].id);
                swaps(&modeinfos[i].width);
                swaps(&modeinfos[i].height);
                swapl(&modeinfos[i].dotClock);
                swaps(&modeinfos[i].hSyncStart);
                swaps(&modeinfos[i].hSyncEnd);
                swaps(&modeinfos[i].hTotal);
                swaps(&modeinfos[i].hSkew);
                swaps(&modeinfos[i].vSyncStart);
                swaps(&modeinfos[i].vSyncEnd);
                swaps(&modeinfos[i].vTotal);
                swaps(&modeinfos[i].nameLength);
                swapl(&modeinfos[i].modeFlags);
            }
            memcpy(names, mode->name, mode->mode.nameLength);
            names += mode->mode.nameLength;
        }
        free(modes);
        assert(bytes_to_int32((char *) names - (char *) extra) == rep.length);
    }

    if (client->swapped) {
        swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swapl(&rep.timestamp);
        swapl(&rep.configTimestamp);
        swaps(&rep.nCrtcs);
        swaps(&rep.nOutputs);
        swaps(&rep.nModes);
        swaps(&rep.nbytesNames);
    }
    WriteToClient(client, sizeof(xRRGetScreenResourcesReply), (char *) &rep);
    if (extraLen) {
        WriteToClient(client, extraLen, (char *) extra);
        free(extra);
    }
    return Success;
}

int
ProcRRGetScreenResources(ClientPtr client)
{
    return rrGetScreenResources(client, TRUE);
}

int
ProcRRGetScreenResourcesCurrent(ClientPtr client)
{
    return rrGetScreenResources(client, FALSE);
}

typedef struct _RR10Data {
    RRScreenSizePtr sizes;
    int nsize;
    int nrefresh;
    int size;
    CARD16 refresh;
} RR10DataRec, *RR10DataPtr;

/*
 * Convert 1.2 monitor data into 1.0 screen data
 */
static RR10DataPtr
RR10GetData(ScreenPtr pScreen, RROutputPtr output)
{
    RR10DataPtr data;
    RRScreenSizePtr size;
    int nmode = output->numModes + output->numUserModes;
    int o, os, l, r;
    RRScreenRatePtr refresh;
    CARD16 vRefresh;
    RRModePtr mode;
    Bool *used;

    /* Make sure there is plenty of space for any combination */
    data = malloc(sizeof(RR10DataRec) +
                  sizeof(RRScreenSize) * nmode +
                  sizeof(RRScreenRate) * nmode + sizeof(Bool) * nmode);
    if (!data)
        return NULL;
    size = (RRScreenSizePtr) (data + 1);
    refresh = (RRScreenRatePtr) (size + nmode);
    used = (Bool *) (refresh + nmode);
    memset(used, '\0', sizeof(Bool) * nmode);
    data->sizes = size;
    data->nsize = 0;
    data->nrefresh = 0;
    data->size = 0;
    data->refresh = 0;

    /*
     * find modes not yet listed
     */
    for (o = 0; o < output->numModes + output->numUserModes; o++) {
        if (used[o])
            continue;

        if (o < output->numModes)
            mode = output->modes[o];
        else
            mode = output->userModes[o - output->numModes];

        l = data->nsize;
        size[l].id = data->nsize;
        size[l].width = mode->mode.width;
        size[l].height = mode->mode.height;
        if (output->mmWidth && output->mmHeight) {
            size[l].mmWidth = output->mmWidth;
            size[l].mmHeight = output->mmHeight;
        }
        else {
            size[l].mmWidth = pScreen->mmWidth;
            size[l].mmHeight = pScreen->mmHeight;
        }
        size[l].nRates = 0;
        size[l].pRates = &refresh[data->nrefresh];
        data->nsize++;

        /*
         * Find all modes with matching size
         */
        for (os = o; os < output->numModes + output->numUserModes; os++) {
            if (os < output->numModes)
                mode = output->modes[os];
            else
                mode = output->userModes[os - output->numModes];
            if (mode->mode.width == size[l].width &&
                mode->mode.height == size[l].height) {
                vRefresh = RRVerticalRefresh(&mode->mode);
                used[os] = TRUE;

                for (r = 0; r < size[l].nRates; r++)
                    if (vRefresh == size[l].pRates[r].rate)
                        break;
                if (r == size[l].nRates) {
                    size[l].pRates[r].rate = vRefresh;
                    size[l].pRates[r].mode = mode;
                    size[l].nRates++;
                    data->nrefresh++;
                }
                if (mode == output->crtc->mode) {
                    data->size = l;
                    data->refresh = vRefresh;
                }
            }
        }
    }
    return data;
}

int
ProcRRGetScreenInfo(ClientPtr client)
{
    REQUEST(xRRGetScreenInfoReq);
    xRRGetScreenInfoReply rep;
    WindowPtr pWin;
    int rc;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    CARD8 *extra;
    unsigned long extraLen;
    RROutputPtr output;

    REQUEST_SIZE_MATCH(xRRGetScreenInfoReq);
    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
        return rc;

    pScreen = pWin->drawable.pScreen;
    pScrPriv = rrGetScrPriv(pScreen);
    rep.pad = 0;

    if (pScrPriv)
        if (!RRGetInfo(pScreen, TRUE))
            return BadAlloc;

    output = RRFirstOutput(pScreen);

    if (!pScrPriv || !output) {
        rep.type = X_Reply;
        rep.setOfRotations = RR_Rotate_0;
        rep.sequenceNumber = client->sequence;
        rep.length = 0;
        rep.root = pWin->drawable.pScreen->root->drawable.id;
        rep.timestamp = currentTime.milliseconds;
        rep.configTimestamp = currentTime.milliseconds;
        rep.nSizes = 0;
        rep.sizeID = 0;
        rep.rotation = RR_Rotate_0;
        rep.rate = 0;
        rep.nrateEnts = 0;
        extra = 0;
        extraLen = 0;
    }
    else {
        int i, j;
        xScreenSizes *size;
        CARD16 *rates;
        CARD8 *data8;
        Bool has_rate = RRClientKnowsRates(client);
        RR10DataPtr pData;
        RRScreenSizePtr pSize;

        pData = RR10GetData(pScreen, output);
        if (!pData)
            return BadAlloc;

        rep.type = X_Reply;
        rep.setOfRotations = output->crtc->rotations;
        rep.sequenceNumber = client->sequence;
        rep.length = 0;
        rep.root = pWin->drawable.pScreen->root->drawable.id;
        rep.timestamp = pScrPriv->lastSetTime.milliseconds;
        rep.configTimestamp = pScrPriv->lastConfigTime.milliseconds;
        rep.rotation = output->crtc->rotation;
        rep.nSizes = pData->nsize;
        rep.nrateEnts = pData->nrefresh + pData->nsize;
        rep.sizeID = pData->size;
        rep.rate = pData->refresh;

        extraLen = rep.nSizes * sizeof(xScreenSizes);
        if (has_rate)
            extraLen += rep.nrateEnts * sizeof(CARD16);

        if (extraLen) {
            extra = (CARD8 *) malloc(extraLen);
            if (!extra) {
                free(pData);
                return BadAlloc;
            }
        }
        else
            extra = NULL;

        /*
         * First comes the size information
         */
        size = (xScreenSizes *) extra;
        rates = (CARD16 *) (size + rep.nSizes);
        for (i = 0; i < pData->nsize; i++) {
            pSize = &pData->sizes[i];
            size->widthInPixels = pSize->width;
            size->heightInPixels = pSize->height;
            size->widthInMillimeters = pSize->mmWidth;
            size->heightInMillimeters = pSize->mmHeight;
            if (client->swapped) {
                swaps(&size->widthInPixels);
                swaps(&size->heightInPixels);
                swaps(&size->widthInMillimeters);
                swaps(&size->heightInMillimeters);
            }
            size++;
            if (has_rate) {
                *rates = pSize->nRates;
                if (client->swapped) {
                    swaps(rates);
                }
                rates++;
                for (j = 0; j < pSize->nRates; j++) {
                    *rates = pSize->pRates[j].rate;
                    if (client->swapped) {
                        swaps(rates);
                    }
                    rates++;
                }
            }
        }
        free(pData);

        data8 = (CARD8 *) rates;

        if (data8 - (CARD8 *) extra != extraLen)
            FatalError("RRGetScreenInfo bad extra len %ld != %ld\n",
                       (unsigned long) (data8 - (CARD8 *) extra), extraLen);
        rep.length = bytes_to_int32(extraLen);
    }
    if (client->swapped) {
        swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swapl(&rep.timestamp);
        swaps(&rep.rotation);
        swaps(&rep.nSizes);
        swaps(&rep.sizeID);
        swaps(&rep.rate);
        swaps(&rep.nrateEnts);
    }
    WriteToClient(client, sizeof(xRRGetScreenInfoReply), (char *) &rep);
    if (extraLen) {
        WriteToClient(client, extraLen, (char *) extra);
        free(extra);
    }
    return Success;
}

int
ProcRRSetScreenConfig(ClientPtr client)
{
    REQUEST(xRRSetScreenConfigReq);
    xRRSetScreenConfigReply rep;
    DrawablePtr pDraw;
    int rc;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    TimeStamp time;
    int i;
    Rotation rotation;
    int rate;
    Bool has_rate;
    RROutputPtr output;
    RRCrtcPtr crtc;
    RRModePtr mode;
    RR10DataPtr pData = NULL;
    RRScreenSizePtr pSize;
    int width, height;

    UpdateCurrentTime();

    if (RRClientKnowsRates(client)) {
        REQUEST_SIZE_MATCH(xRRSetScreenConfigReq);
        has_rate = TRUE;
    }
    else {
        REQUEST_SIZE_MATCH(xRR1_0SetScreenConfigReq);
        has_rate = FALSE;
    }

    rc = dixLookupDrawable(&pDraw, stuff->drawable, client, 0, DixWriteAccess);
    if (rc != Success)
        return rc;

    pScreen = pDraw->pScreen;

    pScrPriv = rrGetScrPriv(pScreen);

    time = ClientTimeToServerTime(stuff->timestamp);

    if (!pScrPriv) {
        time = currentTime;
        rep.status = RRSetConfigFailed;
        goto sendReply;
    }
    if (!RRGetInfo(pScreen, FALSE))
        return BadAlloc;

    output = RRFirstOutput(pScreen);
    if (!output) {
        time = currentTime;
        rep.status = RRSetConfigFailed;
        goto sendReply;
    }

    crtc = output->crtc;

    /*
     * 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.
     *
     * Note that the client only knows about the milliseconds part of the
     * timestamp, so using CompareTimeStamps here would cause randr to suddenly
     * stop working after several hours have passed (freedesktop bug #6502).
     */
    if (stuff->configTimestamp != pScrPriv->lastConfigTime.milliseconds) {
        rep.status = RRSetConfigInvalidConfigTime;
        goto sendReply;
    }

    pData = RR10GetData(pScreen, output);
    if (!pData)
        return BadAlloc;

    if (stuff->sizeID >= pData->nsize) {
        /*
         * Invalid size ID
         */
        client->errorValue = stuff->sizeID;
        free(pData);
        return BadValue;
    }
    pSize = &pData->sizes[stuff->sizeID];

    /*
     * 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;
        free(pData);
        return BadValue;
    }

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

    /*
     * Validate requested refresh
     */
    if (has_rate)
        rate = (int) stuff->rate;
    else
        rate = 0;

    if (rate) {
        for (i = 0; i < pSize->nRates; i++) {
            if (pSize->pRates[i].rate == rate)
                break;
        }
        if (i == pSize->nRates) {
            /*
             * Invalid rate
             */
            client->errorValue = rate;
            free(pData);
            return BadValue;
        }
        mode = pSize->pRates[i].mode;
    }
    else
        mode = pSize->pRates[0].mode;

    /*
     * 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 the screen size is changing, adjust all of the other outputs
     * to fit the new size, mirroring as much as possible
     */
    width = mode->mode.width;
    height = mode->mode.height;
    if (width < pScrPriv->minWidth || pScrPriv->maxWidth < width) {
        client->errorValue = width;
        free(pData);
        return BadValue;
    }
    if (height < pScrPriv->minHeight || pScrPriv->maxHeight < height) {
        client->errorValue = height;
        free(pData);
        return BadValue;
    }

    if (rotation & (RR_Rotate_90 | RR_Rotate_270)) {
        width = mode->mode.height;
        height = mode->mode.width;
    }

    if (width != pScreen->width || height != pScreen->height) {
        int c;

        for (c = 0; c < pScrPriv->numCrtcs; c++) {
            if (!RRCrtcSet(pScrPriv->crtcs[c], NULL, 0, 0, RR_Rotate_0,
                           0, NULL)) {
                rep.status = RRSetConfigFailed;
                /* XXX recover from failure */
                goto sendReply;
            }
        }
        if (!RRScreenSizeSet(pScreen, width, height,
                             pScreen->mmWidth, pScreen->mmHeight)) {
            rep.status = RRSetConfigFailed;
            /* XXX recover from failure */
            goto sendReply;
        }
    }

    if (!RRCrtcSet(crtc, mode, 0, 0, stuff->rotation, 1, &output))
        rep.status = RRSetConfigFailed;
    else {
        pScrPriv->lastSetTime = time;
        rep.status = RRSetConfigSuccess;
    }

    /*
     * XXX Configure other crtcs to mirror as much as possible
     */

 sendReply:

    free(pData);

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

    rep.newTimestamp = pScrPriv->lastSetTime.milliseconds;
    rep.newConfigTimestamp = pScrPriv->lastConfigTime.milliseconds;
    rep.root = pDraw->pScreen->root->drawable.id;

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

    return Success;
}

static CARD16
RR10CurrentSizeID(ScreenPtr pScreen)
{
    CARD16 sizeID = 0xffff;
    RROutputPtr output = RRFirstOutput(pScreen);

    if (output) {
        RR10DataPtr data = RR10GetData(pScreen, output);

        if (data) {
            int i;

            for (i = 0; i < data->nsize; i++)
                if (data->sizes[i].width == pScreen->width &&
                    data->sizes[i].height == pScreen->height) {
                    sizeID = (CARD16) i;
                    break;
                }
            free(data);
        }
    }
    return sizeID;
}
