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