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

extern char	*ConnectionInfo;

static 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 = WindowTable[pScreen->myNum];
    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 = WindowTable[pScreen->myNum];
    
    se.type = RRScreenChangeNotify + RREventBase;
    se.rotation = (CARD8) (crtc ? crtc->rotation : RR_Rotate_0);
    se.timestamp = pScrPriv->lastSetTime.milliseconds;
    se.sequenceNumber = client->sequence;
    se.configTimestamp = pScrPriv->lastConfigTime.milliseconds;
    se.root =  pRoot->drawable.id;
    se.window = pWin->drawable.id;
#ifdef RENDER
    se.subpixelOrder = PictureGetSubpixelOrder (pScreen);
#else
    se.subpixelOrder = SubPixelUnknown;
#endif

    se.sequenceNumber = client->sequence;
    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, DixReadAccess);
    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))
	    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) 
    {
	int n;
	
    	swaps(&rep.sequenceNumber, n);
    	swapl(&rep.length, n);
	swaps(&rep.minWidth, n);
	swaps(&rep.minHeight, n);
	swaps(&rep.maxWidth, n);
	swaps(&rep.maxHeight, n);
    }
    WriteToClient(client, sizeof(xRRGetScreenSizeRangeReply), (char *)&rep);
    return (client->noClientException);
}

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, DixReadAccess);
    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;
}

int
ProcRRGetScreenResources (ClientPtr client)
{
    REQUEST(xRRGetScreenResourcesReq);
    xRRGetScreenResourcesReply  rep;
    WindowPtr			pWin;
    ScreenPtr			pScreen;
    rrScrPrivPtr		pScrPriv;
    CARD8			*extra;
    unsigned long		extraLen;
    int				i, n, rc;
    RRCrtc			*crtcs;
    RROutput			*outputs;
    xRRModeInfo			*modeinfos;
    CARD8			*names;
    
    REQUEST_SIZE_MATCH(xRRGetScreenResourcesReq);
    rc = dixLookupWindow(&pWin, stuff->window, client, DixReadAccess);
    if (rc != Success)
	return rc;
    
    pScreen = pWin->drawable.pScreen;
    pScrPriv = rrGetScrPriv(pScreen);
    rep.pad = 0;
    
    if (pScrPriv)
	if (!RRGetInfo (pScreen))
	    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 * (SIZEOF(xRRModeInfo) >> 2) +
		      ((rep.nbytesNames + 3) >> 2));
	
	extraLen = rep.length << 2;
	if (extraLen)
	{
	    extra = xalloc (extraLen);
	    if (!extra)
	    {
		xfree (modes);
		return BadAlloc;
	    }
	}
	else
	    extra = NULL;

	crtcs = (RRCrtc *) extra;
	outputs = (RROutput *) (crtcs + pScrPriv->numCrtcs);
	modeinfos = (xRRModeInfo *) (outputs + pScrPriv->numOutputs);
	names = (CARD8 *) (modeinfos + num_modes);
	
	for (i = 0; i < pScrPriv->numCrtcs; i++)
	{
	    crtcs[i] = pScrPriv->crtcs[i]->id;
	    if (client->swapped)
		swapl (&crtcs[i], n);
	}
	
	for (i = 0; i < pScrPriv->numOutputs; i++)
	{
	    outputs[i] = pScrPriv->outputs[i]->id;
	    if (client->swapped)
		swapl (&outputs[i], n);
	}
	
	for (i = 0; i < num_modes; i++)
	{
	    RRModePtr	mode = modes[i];
	    modeinfos[i] = mode->mode;
	    if (client->swapped)
	    {
		swapl (&modeinfos[i].id, n);
		swaps (&modeinfos[i].width, n);
		swaps (&modeinfos[i].height, n);
		swapl (&modeinfos[i].dotClock, n);
		swaps (&modeinfos[i].hSyncStart, n);
		swaps (&modeinfos[i].hSyncEnd, n);
		swaps (&modeinfos[i].hTotal, n);
		swaps (&modeinfos[i].hSkew, n);
		swaps (&modeinfos[i].vSyncStart, n);
		swaps (&modeinfos[i].vSyncEnd, n);
		swaps (&modeinfos[i].vTotal, n);
		swaps (&modeinfos[i].nameLength, n);
		swapl (&modeinfos[i].modeFlags, n);
	    }
	    memcpy (names, mode->name, 
		    mode->mode.nameLength);
	    names += mode->mode.nameLength;
	}
    	xfree (modes);
	assert (((((char *) names - (char *) extra) + 3) >> 2) == rep.length);
    }
    
    if (client->swapped) {
	swaps(&rep.sequenceNumber, n);
	swapl(&rep.length, n);
	swapl(&rep.timestamp, n);
	swapl(&rep.configTimestamp, n);
	swaps(&rep.nCrtcs, n);
	swaps(&rep.nOutputs, n);
	swaps(&rep.nModes, n);
	swaps(&rep.nbytesNames, n);
    }
    WriteToClient(client, sizeof(xRRGetScreenResourcesReply), (char *)&rep);
    if (extraLen)
    {
	WriteToClient (client, extraLen, (char *) extra);
	xfree (extra);
    }
    return client->noClientException;
}

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			    n, rc;
    ScreenPtr		    pScreen;
    rrScrPrivPtr	    pScrPriv;
    CARD8		    *extra;
    unsigned long	    extraLen;
    RROutputPtr		    output;

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

    pScreen = pWin->drawable.pScreen;
    pScrPriv = rrGetScrPriv(pScreen);
    rep.pad = 0;
    
    if (pScrPriv)
	if (!RRGetInfo (pScreen))
	    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 = WindowTable[pWin->drawable.pScreen->myNum]->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 = WindowTable[pWin->drawable.pScreen->myNum]->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) +
		    rep.nrateEnts * sizeof (CARD16));

	if (extraLen)
	{
	    extra = (CARD8 *) xalloc (extraLen);
	    if (!extra)
	    {
		xfree (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, n);
	        swaps (&size->heightInPixels, n);
	        swaps (&size->widthInMillimeters, n);
	        swaps (&size->heightInMillimeters, n);
	    }
	    size++;
	    if (has_rate)
	    {
		*rates = pSize->nRates;
		if (client->swapped)
		{
		    swaps (rates, n);
		}
		rates++;
		for (j = 0; j < pSize->nRates; j++)
		{
		    *rates = pSize->pRates[j].rate;
		    if (client->swapped)
		    {
			swaps (rates, n);
		    }
		    rates++;
		}
	    }
	}
        xfree (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 =  (extraLen + 3) >> 2;
    }
    if (client->swapped) {
	swaps(&rep.sequenceNumber, n);
	swapl(&rep.length, n);
	swapl(&rep.timestamp, n);
	swaps(&rep.rotation, n);
	swaps(&rep.nSizes, n);
	swaps(&rep.sizeID, n);
	swaps(&rep.rate, n);
	swaps(&rep.nrateEnts, n);
    }
    WriteToClient(client, sizeof(xRRGetScreenInfoReply), (char *)&rep);
    if (extraLen)
    {
	WriteToClient (client, extraLen, (char *) extra);
	xfree (extra);
    }
    return (client->noClientException);
}

int
ProcRRSetScreenConfig (ClientPtr client)
{
    REQUEST(xRRSetScreenConfigReq);
    xRRSetScreenConfigReply rep;
    DrawablePtr		    pDraw;
    int			    n, 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))
	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;
	xfree (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;
	xfree (pData);
	return BadValue;
    }

    if ((~crtc->rotations) & rotation)
    {
	/*
	 * requested rotation or reflection not supported by screen
	 */
	client->errorValue = stuff->rotation;
	xfree (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;
	    xfree (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 (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
	rep.status = RRSetConfigSuccess;

    /*
     * XXX Configure other crtcs to mirror as much as possible
     */
    
sendReply:
    
    if (pData)
	xfree (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 = WindowTable[pDraw->pScreen->myNum]->drawable.id;

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

    return (client->noClientException);
}

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;
		}
	    xfree (data);
	}
    }
    return sizeID;
}