Constraining cursor to RandR crtcs

Keith Packard keithp at keithp.com
Sat Mar 31 16:26:55 PDT 2007


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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part
URL: <http://lists.x.org/archives/xorg/attachments/20070331/1f0397df/attachment.pgp>


More information about the xorg mailing list