/*
 * 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 <dix-config.h>

#include "dix/dix_priv.h"
#include "dix/request_priv.h"
#include "dix/server_priv.h"
#include "randr/randrstr_priv.h"
#include "randr/rrdispatch_priv.h"

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;

    if (ConnectionInfo == NULL)
        return;

    connSetup = (xConnSetup *) ConnectionInfo;
    vendor = (char *) connSetup + sizeof(xConnSetup);
    formats = (xPixmapFormat *) ((char *) vendor +
                                 pad_to_int32(connSetup->nbytesVendor));
    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 = {
        .u.configureNotify.window = pWin->drawable.id,
        .u.configureNotify.aboveSibling = None,

    /* XXX xinerama stuff ? */

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

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

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

        .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;
    RRSetChanged(pScreen);
/*    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);
    REQUEST_SIZE_MATCH(xRRGetScreenSizeRangeReq);
    if (client->swapped)
        swapl(&stuff->window);

    WindowPtr pWin;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    int rc;

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

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

    xRRGetScreenSizeRangeReply reply = { 0 };

    if (pScrPriv) {
        if (!RRGetInfo(pScreen, FALSE))
            return BadAlloc;
        reply.minWidth = pScrPriv->minWidth;
        reply.minHeight = pScrPriv->minHeight;
        reply.maxWidth = pScrPriv->maxWidth;
        reply.maxHeight = pScrPriv->maxHeight;
    }
    else {
        reply.maxWidth = reply.minWidth = pScreen->width;
        reply.maxHeight = reply.minHeight = pScreen->height;
    }
    if (client->swapped) {
        swaps(&reply.minWidth);
        swaps(&reply.minHeight);
        swaps(&reply.maxWidth);
        swaps(&reply.maxHeight);
    }
    return X_SEND_REPLY_SIMPLE(client, reply);
}

int
ProcRRSetScreenSize(ClientPtr client)
{
    REQUEST(xRRSetScreenSizeReq);
    REQUEST_SIZE_MATCH(xRRSetScreenSizeReq);
    if (client->swapped) {
        swapl(&stuff->window);
        swaps(&stuff->width);
        swaps(&stuff->height);
        swapl(&stuff->widthInMillimeters);
        swapl(&stuff->heightInMillimeters);
    }

    WindowPtr pWin;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    int i, rc;

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

    pScreen = pWin->drawable.pScreen;
    pScrPriv = rrGetScrPriv(pScreen);
    if (!pScrPriv)
        return BadMatch;

    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 (!RRCrtcIsLeased(crtc) && mode) {
            int source_width = mode->mode.width;
            int source_height = mode->mode.height;
            Rotation rotation = crtc->rotation;

            if (rotation & (RR_Rotate_90 | 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;
}


#define update_totals(gpuscreen, pScrPriv) do {       \
    total_crtcs += pScrPriv->numCrtcs;                \
    total_outputs += pScrPriv->numOutputs;            \
    modes = RRModesForScreen(gpuscreen, &num_modes);  \
    if (!modes)                                       \
        return BadAlloc;                              \
    for (j = 0; j < num_modes; j++)                   \
        total_name_len += modes[j]->mode.nameLength;  \
    total_modes += num_modes;                         \
    free(modes);                                      \
} while(0)

static inline void swap_modeinfos(xRRModeInfo *modeinfos, int i)
{
    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);
}

#define update_arrays(gpuscreen, pScrPriv, primary_crtc, has_primary) do {            \
    for (j = 0; j < pScrPriv->numCrtcs; j++) {             \
        if (has_primary && \
            primary_crtc == pScrPriv->crtcs[j]) { \
            has_primary = 0;   \
            continue; \
        }\
        crtcs[crtc_count] = pScrPriv->crtcs[j]->id;        \
        if (client->swapped)                               \
            swapl(&crtcs[crtc_count]);                     \
        crtc_count++;                                      \
    }                                                      \
    for (j = 0; j < pScrPriv->numOutputs; j++) {           \
        outputs[output_count] = pScrPriv->outputs[j]->id;  \
        if (client->swapped)                               \
            swapl(&outputs[output_count]);                 \
        output_count++;                                    \
    }                                                      \
    {                                                      \
        RRModePtr mode;                                    \
        modes = RRModesForScreen(gpuscreen, &num_modes);   \
        for (j = 0; j < num_modes; j++) {                  \
            mode = modes[j];                               \
            modeinfos[mode_count] = mode->mode;            \
            if (client->swapped) {                         \
                swap_modeinfos(modeinfos, mode_count);     \
            }                                              \
            memcpy(names, mode->name, mode->mode.nameLength); \
            names += mode->mode.nameLength;                \
            mode_count++;                                  \
        }                                                  \
        free(modes);                                       \
    }                                                      \
    } while (0)

static int
rrGetMultiScreenResources(ClientPtr client, Bool query, ScreenPtr pScreen)
{
    int j;
    int total_crtcs, total_outputs, total_modes, total_name_len;
    int crtc_count, output_count, mode_count;
    ScreenPtr iter;
    rrScrPrivPtr pScrPriv;
    int num_modes;
    RRModePtr *modes;
    unsigned long extraLen;
    CARD8 *extra;
    RRCrtc *crtcs;
    RRCrtcPtr primary_crtc = NULL;
    RROutput *outputs;
    xRRModeInfo *modeinfos;
    CARD8 *names;
    int has_primary = 0;

    /* we need to iterate all the GPU primarys and all their output secondarys */
    total_crtcs = 0;
    total_outputs = 0;
    total_modes = 0;
    total_name_len = 0;

    pScrPriv = rrGetScrPriv(pScreen);

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

    update_totals(pScreen, pScrPriv);

    xorg_list_for_each_entry(iter, &pScreen->secondary_list, secondary_head) {
        if (!iter->is_output_secondary)
            continue;

        pScrPriv = rrGetScrPriv(iter);

        if (query)
          if (!RRGetInfo(iter, query))
            return BadAlloc;
        update_totals(iter, pScrPriv);
    }

    pScrPriv = rrGetScrPriv(pScreen);

    xRRGetScreenResourcesReply reply = {
        .timestamp = pScrPriv->lastSetTime.milliseconds,
        .configTimestamp = pScrPriv->lastConfigTime.milliseconds,
        .nCrtcs = total_crtcs,
        .nOutputs = total_outputs,
        .nModes = total_modes,
        .nbytesNames = total_name_len
    };

    reply.length = (total_crtcs + total_outputs +
                  total_modes * bytes_to_int32(SIZEOF(xRRModeInfo)) +
                  bytes_to_int32(total_name_len));

    x_rpcbuf_t rpcbuf = { .swapped = client->swapped, .err_clear = TRUE };

    extraLen = reply.length << 2;
    if (extraLen) {
        extra = x_rpcbuf_reserve(&rpcbuf, extraLen);
        if (!extra) {
            return BadAlloc;
        }
    }
    else
        extra = NULL;

    crtcs = (RRCrtc *)extra;
    outputs = (RROutput *)(crtcs + total_crtcs);
    modeinfos = (xRRModeInfo *)(outputs + total_outputs);
    names = (CARD8 *)(modeinfos + total_modes);

    crtc_count = 0;
    output_count = 0;
    mode_count = 0;

    pScrPriv = rrGetScrPriv(pScreen);
    if (pScrPriv->primaryOutput && pScrPriv->primaryOutput->crtc) {
        has_primary = 1;
        primary_crtc = pScrPriv->primaryOutput->crtc;
        crtcs[0] = pScrPriv->primaryOutput->crtc->id;
        if (client->swapped)
            swapl(&crtcs[0]);
        crtc_count = 1;
    }
    update_arrays(pScreen, pScrPriv, primary_crtc, has_primary);

    xorg_list_for_each_entry(iter, &pScreen->secondary_list, secondary_head) {
        if (!iter->is_output_secondary)
            continue;

        pScrPriv = rrGetScrPriv(iter);

        update_arrays(iter, pScrPriv, primary_crtc, has_primary);
    }

    assert(bytes_to_int32((char *) names - (char *) extra) == reply.length);

    if (client->swapped) {
        swapl(&reply.timestamp);
        swapl(&reply.configTimestamp);
        swaps(&reply.nCrtcs);
        swaps(&reply.nOutputs);
        swaps(&reply.nModes);
        swaps(&reply.nbytesNames);
    }

    return X_SEND_REPLY_WITH_RPCBUF(client, reply, rpcbuf);
}

static int
rrGetScreenResources(ClientPtr client, Bool query)
{
    REQUEST(xRRGetScreenResourcesReq);
    REQUEST_SIZE_MATCH(xRRGetScreenResourcesReq);

    if (client->swapped)
        swapl(&stuff->window);

    xRRGetScreenResourcesReply reply;
    WindowPtr pWin;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    CARD8 *extra = NULL;
    unsigned long extraLen = 0;
    int i, rc, has_primary = 0;

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

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

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

    if (pScreen->output_secondarys)
        return rrGetMultiScreenResources(client, query, pScreen);

    x_rpcbuf_t rpcbuf = { .swapped = client->swapped, .err_clear = TRUE };

    if (!pScrPriv) {
        reply = (xRRGetScreenResourcesReply) {
            .timestamp = currentTime.milliseconds,
            .configTimestamp = currentTime.milliseconds,
        };
    }
    else {
        RRModePtr *modes;
        int num_modes;

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

        reply = (xRRGetScreenResourcesReply) {
            .timestamp = pScrPriv->lastSetTime.milliseconds,
            .configTimestamp = pScrPriv->lastConfigTime.milliseconds,
            .nCrtcs = pScrPriv->numCrtcs,
            .nOutputs = pScrPriv->numOutputs,
            .nModes = num_modes,
        };

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

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

        extraLen = reply.length << 2;
        if (!extraLen)
            goto finish;

        extra = x_rpcbuf_reserve(&rpcbuf, extraLen);
        if (!extra) {
            free(modes);
            return BadAlloc;
        }

        RRCrtc *crtcs = (RRCrtc *) extra;
        RROutput *outputs = (RROutput *) (crtcs + pScrPriv->numCrtcs);
        xRRModeInfo *modeinfos = (xRRModeInfo *) (outputs + pScrPriv->numOutputs);
        CARD8* 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;
        }
        assert(bytes_to_int32((char *) names - (char *) extra) == reply.length);
finish:
        free(modes);
    }

    if (client->swapped) {
        swapl(&reply.timestamp);
        swapl(&reply.configTimestamp);
        swaps(&reply.nCrtcs);
        swaps(&reply.nOutputs);
        swaps(&reply.nModes);
        swaps(&reply.nbytesNames);
    }
    return X_SEND_REPLY_WITH_RPCBUF(client, reply, rpcbuf);
}

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)
{
    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 */
    RR10DataPtr data = calloc(1, 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);
    REQUEST_SIZE_MATCH(xRRGetScreenInfoReq);
    if (client->swapped)
        swapl(&stuff->window);

    xRRGetScreenInfoReply reply;
    WindowPtr pWin;
    int rc;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    CARD8 *extra = NULL;
    unsigned long extraLen = 0;
    RROutputPtr output;

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

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

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

    output = RRFirstOutput(pScreen);

    x_rpcbuf_t rpcbuf = { .swapped = client->swapped, .err_clear = TRUE };

    if (!pScrPriv || !output) {
        reply = (xRRGetScreenInfoReply) {
            .setOfRotations = RR_Rotate_0,
            .root = pWin->drawable.pScreen->root->drawable.id,
            .timestamp = currentTime.milliseconds,
            .configTimestamp = currentTime.milliseconds,
            .rotation = RR_Rotate_0,
        };
    }
    else {
        int i, j;
        CARD8 *data8;
        Bool has_rate = RRClientKnowsRates(client);
        RR10DataPtr pData;
        RRScreenSizePtr pSize;

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

        reply = (xRRGetScreenInfoReply) {
            .setOfRotations = output->crtc->rotations,
            .root = pWin->drawable.pScreen->root->drawable.id,
            .timestamp = pScrPriv->lastSetTime.milliseconds,
            .configTimestamp = pScrPriv->lastConfigTime.milliseconds,
            .rotation = output->crtc->rotation,
            .nSizes = pData->nsize,
            .nrateEnts = pData->nrefresh + pData->nsize,
            .sizeID = pData->size,
            .rate = pData->refresh
        };

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

        if (!extraLen)
            goto finish; // no extra payload

        extra = x_rpcbuf_reserve(&rpcbuf, extraLen);
        if (!extra) {
            free(pData);
            return BadAlloc;
        }

        /*
         * First comes the size information
         */
        xScreenSizes *size = (xScreenSizes *) extra;
        CARD16 *rates = (CARD16 *) (size + reply.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++;
                }
            }
        }

        data8 = (CARD8 *) rates;

        if (data8 - (CARD8 *) extra != extraLen)
            FatalError("RRGetScreenInfo bad extra len %ld != %ld\n",
                       (unsigned long) (data8 - (CARD8 *) extra), extraLen);

finish:
        free(pData);
    }
    if (client->swapped) {
        swapl(&reply.timestamp);
        swapl(&reply.configTimestamp);
        swaps(&reply.rotation);
        swaps(&reply.nSizes);
        swaps(&reply.sizeID);
        swaps(&reply.rate);
        swaps(&reply.nrateEnts);
    }
    return X_SEND_REPLY_WITH_RPCBUF(client, reply, rpcbuf);
}

int
ProcRRSetScreenConfig(ClientPtr client)
{
    REQUEST(xRRSetScreenConfigReq);

    int rate = 0;
    if (RRClientKnowsRates(client)) {
        REQUEST_SIZE_MATCH(xRRSetScreenConfigReq);
        if (client->swapped) swaps(&stuff->rate);
        rate = stuff->rate;
    }
    else {
        REQUEST_SIZE_MATCH(xRR1_0SetScreenConfigReq);
    }

    if (client->swapped) {
        swapl(&stuff->drawable);
        swapl(&stuff->timestamp);
        swaps(&stuff->sizeID);
        swaps(&stuff->rotation);
    }

    DrawablePtr pDraw;
    int rc;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    TimeStamp time;
    int i;
    Rotation rotation;
    CARD8 status;
    RROutputPtr output;
    RRCrtcPtr crtc;
    RRModePtr mode;
    RR10DataPtr pData = NULL;
    RRScreenSizePtr pSize;
    int width, height;

    UpdateCurrentTime();

    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;
        status = RRSetConfigFailed;
        goto sendReply;
    }
    if (!RRGetInfo(pScreen, FALSE))
        return BadAlloc;

    output = RRFirstOutput(pScreen);
    if (!output) {
        time = currentTime;
        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) {
        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;
    }

    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) {
        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)) {
                status = RRSetConfigFailed;
                /* XXX recover from failure */
                goto sendReply;
            }
        }
        if (!RRScreenSizeSet(pScreen, width, height,
                             pScreen->mmWidth, pScreen->mmHeight)) {
            status = RRSetConfigFailed;
            /* XXX recover from failure */
            goto sendReply;
        }
    }

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

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

 sendReply:

    free(pData);

    xRRSetScreenConfigReply reply = {
        .status = status,
        .newTimestamp = pScrPriv->lastSetTime.milliseconds,
        .newConfigTimestamp = pScrPriv->lastConfigTime.milliseconds,
        .root = pDraw->pScreen->root->drawable.id,
        /* .subpixelOrder = ?? */
    };

    if (client->swapped) {
        swapl(&reply.newTimestamp);
        swapl(&reply.newConfigTimestamp);
        swapl(&reply.root);
    }
    return X_SEND_REPLY_SIMPLE(client, reply);
}

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;
}
