Constraining cursor to RandR crtcs

Andy Ritger aritger at nvidia.com
Sun Apr 1 19:33:33 PDT 2007


Hi Keith.

Interesting problem, though I'd like to raise the "Mechanism, not Policy"
warning.

Is it necessary for the X server to solve #1 (or #2) at all?  If the X
screen is just a rectangle of pixels, shouldn't the cursor be able to
move anywhere within that rectangle?

Maybe clamping cursor(s) to CRTCs should be left up to the window manager?
It's the window manager's job to clamp windows to the CRTCs' regions;
maybe the window manager should also own that responsibility for the
cursor(s)?

Also, how does your proposal handle arbitrary overlap of CRTCs?

Thanks,
- Andy



On Sat, 31 Mar 2007, Keith Packard wrote:

> One of the remaining DIX-level issues with RandR is how to keep the
> cursor within the area of the root window covered by the crtcs.
>
> It's not quite as easy as with zaphod or xinerama as those both have the
> use defining the linkage between monitor edges explicitly while RandR
> just places monitors within a single coordinate space, leaving the edge
> relationships unspecified.
>
> There are three times when constraints are applied:
>
>     1. Cursor motion - the cursor leaves the current monitor. Where
>        does it go now?
>     2. WarpPointer - a client moves the cursor directly. How is the
>        specified position modified?
>     3. Crtc configuration - the location and size of crtcs changes.
>        Where does the cursor go now?
>
> I decided to handle 2. and 3. precisely the same way -- just search for
> the closest crtc and warp the cursor to the nearest edge. That seems to
> work fine and isn't too confusing. The one case I was surprised by at
> first was when xrandr disables a crtc before resizing the screen. As the
> crtc is disabled, the cursor is warped to another crtc; re-enabling the
> crtc in the new configuration doesn't warp it back though. Whatever. I
> don't think this is terribly confusing.
>
> Case 1. turned out to be a bit harder; I thought I'd explain my first
> plan and then the current plan.
>
> My first thought was that leaving the edge of a monitor should cause the
> cursor to move to the nearest crtc in the specified direction, excepting
> the crtc just departed. The following layout confused this:
>
>
>   +------+----+
>   |      |    |
>   |     b|    |
>   +------+c   |
>          |a   |
>          +----+
>
> Moving the cursor left from 'a' would cause it to pop up to 'b', which
> seemed sensible. However, moving down from 'b' would cause it to pop out
> again at 'c', which was not at all intuitive. Especially when you drag
> left and down from 'a', you end up bouncing between 'b' and 'c'. Quite
> nasty.
>
> So, I decided another plan was needed. I decided that the key idea was
> to limit the transition from crtc 'A' to crtc 'B' to a single edge.
> Classifying the direction from the center of 'A' to the center of 'B'
> into four quadrants (left, above, right, below), departures from 'A'
> along the matching edge warp the cursor to the closest matching position
> in 'B'. Transitions from other edges will not warp to 'B'.
>
> Here's the proposed patch which implements this (the portion in
> rrpointer.c is mostly concerned with removing the existing
> implementation as it has moved to dix).
>
> The function DisjointRootConfine is called whereever the cursor
> constrains are tested to limit cursor positions to visible areas. It
> takes two HotSpot pointers, the first indicates the existing position of
> the cursor when the cursor is moving under user control, the second the
> proposed new cursor location which may be edited by the function.
>
> # On branch refs/heads/server-1.3-branch
> # Updated but not checked in:
> #   (will commit)
> #
> #	modified:   dix/events.c
> #	modified:   include/input.h
> #	modified:   randr/rrpointer.c
> #
> diff --git a/dix/events.c b/dix/events.c
> index c57a30e..42d05cd 100644
> --- a/dix/events.c
> +++ b/dix/events.c
> @@ -265,6 +265,11 @@ static WindowPtr XYToWindow(
>     int y
> );
>
> +#ifdef RANDR
> +static void
> +DisjointRootConfine (HotSpot *old, HotSpot *new);
> +#endif
> +
> extern int lastEvent;
>
> static Mask lastEventMask;
> @@ -412,6 +417,10 @@ XineramaCheckPhysLimits(
>
>     if (sprite.hotShape)  /* more work if the shape is a mess */
> 	ConfineToShape(sprite.hotShape, &new.x, &new.y);
> +
> +#ifdef RANDR
> +    DisjointRootConfine(NULL, &new);
> +#endif
>
>     if((new.x != sprite.hotPhys.x) || (new.y != sprite.hotPhys.y))
>     {
> @@ -454,9 +463,11 @@ XineramaCheckVirtualMotion(
>    QdEventPtr qe,
>    WindowPtr pWin
> ){
> -
> +    HotSpot old, *old_ptr = NULL;
>     if (qe)
>     {
> +	old = sprite.hot;
> +	old_ptr = &old;
> 	sprite.hot.pScreen = qe->pScreen;  /* should always be Screen 0 */
> 	sprite.hot.x = qe->event->u.keyButtonPointer.rootX;
> 	sprite.hot.y = qe->event->u.keyButtonPointer.rootY;
> @@ -506,6 +517,10 @@ XineramaCheckVirtualMotion(
> 	if (REGION_NUM_RECTS(&sprite.Reg2) > 1)
> 	    ConfineToShape(&sprite.Reg2, &sprite.hot.x, &sprite.hot.y);
>
> +#ifdef RANDR
> +	DisjointRootConfine(old_ptr, &sprite.hot);
> +#endif
> +
> 	if (qe)
> 	{
> 	    qe->pScreen = sprite.hot.pScreen;
> @@ -547,6 +562,9 @@ XineramaCheckMotion(xEvent *xE)
>
> 	if (sprite.hotShape)
> 	    ConfineToShape(sprite.hotShape, &sprite.hot.x, &sprite.hot.y);
> +#ifdef RANDR
> +	DisjointRootConfine(&sprite.hotPhys, &sprite.hot);
> +#endif
>
> 	sprite.hotPhys = sprite.hot;
> 	if ((sprite.hotPhys.x != XE_KBPTR.rootX) ||
> @@ -762,6 +780,9 @@ CheckPhysLimits(
>     if (sprite.hotShape)
> 	ConfineToShape(sprite.hotShape, &new.x, &new.y);
> #endif
> +#ifdef RANDR
> +    DisjointRootConfine(&sprite.hotPhys, &new);
> +#endif
>     if ((pScreen != sprite.hotPhys.pScreen) ||
> 	(new.x != sprite.hotPhys.x) || (new.y != sprite.hotPhys.y))
>     {
> @@ -778,6 +799,7 @@ CheckVirtualMotion(
>     register QdEventPtr qe,
>     register WindowPtr pWin)
> {
> +    HotSpot old, *old_ptr = NULL;
> #ifdef PANORAMIX
>     if(!noPanoramiXExtension) {
> 	XineramaCheckVirtualMotion(qe, pWin);
> @@ -786,6 +808,8 @@ CheckVirtualMotion(
> #endif
>     if (qe)
>     {
> +	old = sprite.hot;
> +	old_ptr = &old;
> 	sprite.hot.pScreen = qe->pScreen;
> 	sprite.hot.x = qe->event->u.keyButtonPointer.rootX;
> 	sprite.hot.y = qe->event->u.keyButtonPointer.rootY;
> @@ -814,6 +838,9 @@ CheckVirtualMotion(
> 	if (wBoundingShape(pWin))
> 	    ConfineToShape(&pWin->borderSize, &sprite.hot.x, &sprite.hot.y);
> #endif
> +#ifdef RANDR
> +	DisjointRootConfine(old_ptr, &sprite.hot);
> +#endif
> 	if (qe)
> 	{
> 	    qe->pScreen = sprite.hot.pScreen;
> @@ -2020,6 +2047,9 @@ CheckMotion(xEvent *xE)
> 	if (sprite.hotShape)
> 	    ConfineToShape(sprite.hotShape, &sprite.hot.x, &sprite.hot.y);
> #endif
> +#ifdef RANDR
> +	DisjointRootConfine(&sprite.hotPhys, &sprite.hot);
> +#endif
> #ifdef XEVIE
>         xeviehot.x = sprite.hot.x;
>         xeviehot.y = sprite.hot.y;
> @@ -2303,6 +2333,18 @@ XineramaWarpPointer(ClientPtr client)
> 	y = sprite.physLimits.y2 - 1;
>     if (sprite.hotShape)
> 	ConfineToShape(sprite.hotShape, &x, &y);
> +
> +#ifdef RANDR
> +    {
> +	HotSpot	new;
> +	new.pScreen = sprite.hotPhys.pScreen;
> +	new.x = x;
> +	new.y = y;
> +	DisjointRootConfine(NULL, &new);
> +	x = new.x;
> +	y = new.y;
> +    }
> +#endif
>
>     XineramaSetCursorPosition(x, y, TRUE);
>
> @@ -2392,6 +2434,17 @@ ProcWarpPointer(ClientPtr client)
> 	if (sprite.hotShape)
> 	    ConfineToShape(sprite.hotShape, &x, &y);
> #endif
> +#ifdef RANDR
> +	{
> +	    HotSpot	new;
> +	    new.pScreen = sprite.hotPhys.pScreen;
> +	    new.x = x;
> +	    new.y = y;
> +	    DisjointRootConfine(NULL, &new);
> +	    x = new.x;
> +	    y = new.y;
> +	}
> +#endif
> 	(*newScreen->SetCursorPosition)(newScreen, x, y, TRUE);
>     }
>     else if (!PointerConfinedToScreen())
> @@ -4597,3 +4650,236 @@ WriteEventsToClient(ClientPtr pClient, int count, xEvent *events)
> 	(void)WriteToClient(pClient, count * sizeof(xEvent), (char *) events);
>     }
> }
> +
> +#ifdef RANDR
> +typedef struct _boxes {
> +    int	num_box;
> +    BoxPtr  boxes;
> +} BoxesRec, *BoxesPtr;
> +
> +static BoxesPtr    root_boxes[MAXSCREENS];
> +
> +static int
> +int_abs (int x)
> +{
> +    if (x < 0) return -x;
> +    return x;
> +}
> +
> +static Bool
> +PointInBox (BoxPtr box, int x, int y)
> +{
> +    return box->x1 <= x && x < box->x2 && box->y1 <= y && y < box->y2;
> +}
> +
> +static BoxPtr
> +PointToDisjointBox (BoxesPtr boxes, int x, int y)
> +{
> +    int	b;
> +
> +    for (b = 0; b < boxes->num_box; b++)
> +	if (PointInBox (&boxes->boxes[b], x, y))
> +	    return &boxes->boxes[b];
> +    return NULL;
> +}
> +
> +static xPoint
> +BoxToPoint (BoxPtr box, int x, int y)
> +{
> +    xPoint  point;
> +
> +    if (x < box->x1)
> +	point.x = x - box->x1;
> +    else if (x >= box->x2)
> +	point.x = x - box->x2 + 1;
> +    else
> +	point.x = 0;
> +    if (y < box->y1)
> +	point.y = y - box->y1;
> +    else if (y >= box->y2)
> +	point.y = y - box->y2 + 1;
> +    else
> +	point.y = 0;
> +    return point;
> +}
> +
> +static xPoint
> +BoxCenter (BoxPtr a)
> +{
> +    xPoint  point;
> +
> +    point.x = (a->x1 + a->x2) >> 1;
> +    point.y = (a->y1 + a->y2) >> 1;
> +    return point;
> +}
> +
> +static xPoint
> +BoxToBox (BoxPtr a, BoxPtr b)
> +{
> +    xPoint  ac = BoxCenter (a);
> +    xPoint  bc = BoxCenter (b);
> +    xPoint  p;
> +
> +    p.x = bc.x - ac.x;
> +    p.y = bc.y - ac.y;
> +    return p;
> +}
> +
> +static int
> +Manhattan (xPoint point)
> +{
> +    return int_abs (point.x) + int_abs (point.y);
> +}
> +
> +static BoxPtr
> +PointToNearestDisjointBox (BoxesPtr boxes, int x, int y)
> +{
> +    int	b;
> +    BoxPtr nearest;
> +    int nearest_dist;
> +    int	dist;
> +
> +    nearest = &boxes->boxes[0];
> +    nearest_dist = Manhattan (BoxToPoint (nearest, x, y));
> +    for (b = 1; b < boxes->num_box; b++)
> +    {
> +	BoxPtr	box = &boxes->boxes[b];
> +	dist = Manhattan (BoxToPoint (box, x, y));
> +	if (dist < nearest_dist)
> +	    nearest = box;
> +    }
> +    return nearest;
> +}
> +
> +typedef enum _quadrant {
> +    QCenter, QLeft, QAbove, QRight, QBelow
> +} quadrant_t;
> +
> +static quadrant_t
> +quadrant (xPoint point)
> +{
> +    if (point.x == 0 && point.y == 0)
> +	return QCenter;
> +
> +    if (int_abs (point.x) > int_abs (point.y))
> +    {
> +	if (point.x < 0)
> +	    return QLeft;
> +	else
> +	    return QRight;
> +    }
> +    else
> +    {
> +	if (point.y < 0)
> +	    return QAbove;
> +	else
> +	    return QBelow;
> +    }
> +}
> +
> +static void
> +DisjointRootConfine (HotSpot *old, HotSpot *new)
> +{
> +    BoxesPtr	new_boxes = root_boxes[new->pScreen->myNum];
> +    BoxPtr	old_box;
> +    BoxPtr    	new_box = NULL;
> +
> +    if (!new_boxes)
> +	return;
> +
> +    /*
> +     * When leaving one box, find the next one in the right direction
> +     */
> +    if (old &&
> +	old->pScreen == new->pScreen &&
> +	(old_box = PointToDisjointBox (new_boxes, old->x, old->y)) &&
> +	!(new_box = PointToDisjointBox (new_boxes, new->x, new->y)))
> +    {
> +	int	    b;
> +	quadrant_t  qp;
> +	int	    best_dist = 0;
> +
> +	/*
> +	 * Figure out which direction we've moved
> +	 */
> +	qp = quadrant (BoxToPoint (old_box, new->x, new->y));
> +	for (b = 0; b < new_boxes->num_box; b++)
> +	{
> +	    BoxPtr	box = &new_boxes->boxes[b];
> +
> +	    if (box != old_box)
> +	    {
> +		quadrant_t	qb = quadrant (BoxToBox (old_box, box));
> +
> +		if (qp == qb)
> +		{
> +		    int	dist = Manhattan (BoxToPoint (box, new->x, new->y));
> +		    if (!new_box || dist < best_dist)
> +		    {
> +			new_box = box;
> +			best_dist = dist;
> +		    }
> +		}
> +	    }
> +	}
> +    }
> +
> +    /* If no box was found above, just confine to the nearest box */
> +    if (!new_box)
> +	new_box = PointToNearestDisjointBox (new_boxes, new->x, new->y);
> +
> +    /* confine to target box */
> +    if (new->x < new_box->x1)
> +	new->x = new_box->x1;
> +    else if (new->x >= new_box->x2)
> +	new->x = new_box->x2 - 1;
> +    if (new->y < new_box->y1)
> +	new->y = new_box->y1;
> +    else if (new->y >= new_box->y2)
> +	new->y = new_box->y2 - 1;
> +}
> +
> +Bool
> +SetDisjointRoot (ScreenPtr pScreen, int num_box, BoxPtr boxes)
> +{
> +    BoxesPtr	new_boxes;
> +    HotSpot	new;
> +
> +    if (num_box == 0)
> +    {
> +	if (root_boxes[pScreen->myNum])
> +	{
> +	    xfree (root_boxes[pScreen->myNum]);
> +	    root_boxes[pScreen->myNum] = NULL;
> +	}
> +	return TRUE;
> +    }
> +
> +    new_boxes = xalloc (sizeof (BoxesRec) + num_box * sizeof (BoxRec));
> +    if (!new_boxes)
> +	return FALSE;
> +    new_boxes->num_box = num_box;
> +    new_boxes->boxes = (BoxPtr) (new_boxes + 1);
> +    memcpy (new_boxes->boxes, boxes, num_box * sizeof (BoxRec));
> +
> +    if (root_boxes[pScreen->myNum])
> +	xfree (root_boxes[pScreen->myNum]);
> +
> +    root_boxes[pScreen->myNum] = new_boxes;
> +
> +    new = sprite.hotPhys;
> +
> +    DisjointRootConfine (NULL, &new);
> +    if (new.x != sprite.hotPhys.x || new.y !=sprite.hotPhys.y)
> +    {
> +#ifdef PANORAMIX
> +	if(!noPanoramiXExtension)
> +	    XineramaSetCursorPosition (new.x, new.y, TRUE);
> +	else
> +#endif
> +	    (*new.pScreen->SetCursorPosition)(new.pScreen, new.x, new.y, TRUE);
> +
> +    }
> +    return TRUE;
> +}
> +#endif
> diff --git a/include/input.h b/include/input.h
> index c0cee24..2599d31 100644
> --- a/include/input.h
> +++ b/include/input.h
> @@ -364,4 +364,7 @@ extern void InitInput(
>     int  /*argc*/,
>     char ** /*argv*/);
>
> +Bool
> +SetDisjointRoot (ScreenPtr pScreen, int num_box, BoxPtr boxes);
> +
> #endif /* INPUT_H */
> diff --git a/randr/rrpointer.c b/randr/rrpointer.c
> index 802dcb2..ac778c4 100644
> --- a/randr/rrpointer.c
> +++ b/randr/rrpointer.c
> @@ -23,123 +23,42 @@
> #include "randrstr.h"
>
> /*
> - * When the pointer moves, check to see if the specified position is outside
> - * any of theavailable CRTCs and move it to a 'sensible' place if so, where
> - * sensible is the closest monitor to the departing edge.
> - *
> - * Returns whether the position was adjusted
> - */
> -
> -static Bool
> -RRCrtcContainsPosition (RRCrtcPtr crtc, int x, int y)
> -{
> -    RRModePtr   mode = crtc->mode;
> -    int		scan_width, scan_height;
> -
> -    if (!mode)
> -	return FALSE;
> -
> -    RRCrtcGetScanoutSize (crtc, &scan_width, &scan_height);
> -
> -    if (crtc->x <= x && x < crtc->x + scan_width &&
> -	crtc->y <= y && y < crtc->y + scan_height)
> -	return TRUE;
> -    return FALSE;
> -}
> -
> -/*
> - * Find the CRTC nearest the specified position, ignoring 'skip'
> + * When the screen is reconfigured, reset the list of
> + * boxes defining this screen
>  */
> -static void
> -RRPointerToNearestCrtc (ScreenPtr pScreen, int x, int y, RRCrtcPtr skip)
> -{
> -    rrScrPriv (pScreen);
> -    int		c;
> -    RRCrtcPtr	nearest = NULL;
> -    int		best = 0;
> -    int		best_dx = 0, best_dy = 0;
> -
> -    for (c = 0; c < pScrPriv->numCrtcs; c++)
> -    {
> -	RRCrtcPtr   crtc = pScrPriv->crtcs[c];
> -	RRModePtr   mode = crtc->mode;
> -	int	    dx, dy;
> -	int	    dist;
> -	int	    scan_width, scan_height;
> -
> -	if (!mode)
> -	    continue;
> -	if (crtc == skip)
> -	    continue;
> -
> -	RRCrtcGetScanoutSize (crtc, &scan_width, &scan_height);
> -
> -	if (x < crtc->x)
> -	    dx = crtc->x - x;
> -	else if (x > crtc->x + scan_width)
> -	    dx = x - (crtc->x + scan_width);
> -	else
> -	    dx = 0;
> -	if (y < crtc->y)
> -	    dy = crtc->y - x;
> -	else if (y > crtc->y + scan_height)
> -	    dy = y - (crtc->y + scan_height);
> -	else
> -	    dy = 0;
> -	dist = dx + dy;
> -	if (!nearest || dist < best)
> -	{
> -	    nearest = crtc;
> -	    best_dx = dx;
> -	    best_dy = dy;
> -	}
> -    }
> -    if (best_dx || best_dy)
> -	(*pScreen->SetCursorPosition) (pScreen, x + best_dx, y + best_dy, TRUE);
> -    pScrPriv->pointerCrtc = nearest;
> -}
>
> void
> -RRPointerMoved (ScreenPtr pScreen, int x, int y)
> +RRPointerScreenConfigured (ScreenPtr pScreen)
> {
> -    rrScrPriv (pScreen);
> -    RRCrtcPtr	pointerCrtc = pScrPriv->pointerCrtc;;
> -    int	c;
> +    rrScrPriv(pScreen);
> +    BoxPtr  boxes = NULL;
> +    int	    c;
> +    int	    nboxes = 0;
>
> -    /* Check last known CRTC */
> -    if (pointerCrtc && RRCrtcContainsPosition (pointerCrtc, x, y))
> -	return;
> -
> -    /* Check all CRTCs */
> -    for (c = 0; c < pScrPriv->numCrtcs; c++)
> +    if (!pScrPriv) return;
> +    if (pScrPriv->numCrtcs)
>     {
> -	RRCrtcPtr   crtc = pScrPriv->crtcs[c];
> -
> -	if (RRCrtcContainsPosition (crtc, x, y))
> -	{
> -	    /* Remember containing CRTC */
> -	    pScrPriv->pointerCrtc = crtc;
> +	boxes = xalloc (pScrPriv->numCrtcs * sizeof (BoxRec));
> +	if (!boxes)
> 	    return;
> +	for (c = 0; c < pScrPriv->numCrtcs; c++)
> +	{
> +	    RRCrtcPtr	crtc = pScrPriv->crtcs[c];
> +	    int		width, height;
> +
> +	    if (crtc->mode)
> +	    {
> +		BoxPtr	box = &boxes[nboxes++];
> +
> +		RRCrtcGetScanoutSize (crtc, &width, &height);
> +		box->x1 = crtc->x;
> +		box->y1 = crtc->y;
> +		box->x2 = crtc->x + width;
> +		box->y2 = crtc->y + height;
> +	    }
> 	}
>     }
> -
> -    /* None contain pointer, find nearest */
> -    RRPointerToNearestCrtc (pScreen, x, y, pointerCrtc);
> -}
> -
> -/*
> - * When the screen is reconfigured, move the pointer to the nearest
> - * CRTC
> - */
> -void
> -RRPointerScreenConfigured (ScreenPtr pScreen)
> -{
> -    WindowPtr	pRoot = GetCurrentRootWindow ();
> -    ScreenPtr	pCurrentScreen = pRoot ? pRoot->drawable.pScreen : NULL;
> -    int		x, y;
> -
> -    if (pScreen != pCurrentScreen)
> -	return;
> -    GetSpritePosition (&x, &y);
> -    RRPointerToNearestCrtc (pScreen, x, y, NULL);
> +    (void) SetDisjointRoot (pScreen, nboxes, boxes);
> +    if (boxes)
> +	xfree (boxes);
> }
>
>
> -- 
> keith.packard at intel.com
>



More information about the xorg mailing list