fixes: Add support for pointer barriers

Implements pointer barriers as specified by version 5 of the XFIXES
protocol. Barriers are axis-aligned, zero-width lines that block pointer
movement for relative input devices. Barriers may block motion in either
the positive or negative direction, or both.

v3:
- Fix off-by-one in version_requests array
- Port to non-glib test harness
- Fix review notes from Søren Sandmann Pedersen, add tests to match

Co-authored-by: Peter Hutterer <peter.hutterer@who-t.net>
Tested-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Adam Jackson <ajax@redhat.com>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Adam Jackson 2011-05-25 05:54:35 -04:00
commit d45f5b2493
8 changed files with 777 additions and 13 deletions

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* Copyright 2010 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@ -50,13 +51,16 @@
#include "cursorstr.h"
#include "dixevents.h"
#include "servermd.h"
#include "mipointer.h"
#include "inputstr.h"
#include "windowstr.h"
#include "xace.h"
#include "list.h"
static RESTYPE CursorClientType;
static RESTYPE CursorHideCountType;
static RESTYPE CursorWindowType;
RESTYPE PointerBarrierType;
static CursorPtr CursorCurrent[MAXDEVICES];
static DevPrivateKeyRec CursorScreenPrivateKeyRec;
@ -107,6 +111,14 @@ typedef struct _CursorHideCountRec {
XID resource;
} CursorHideCountRec;
typedef struct PointerBarrierClient *PointerBarrierClientPtr;
struct PointerBarrierClient {
ScreenPtr screen;
struct PointerBarrier barrier;
struct list entry;
};
/*
* Wrap DisplayCursor to catch cursor change events
*/
@ -114,7 +126,9 @@ typedef struct _CursorHideCountRec {
typedef struct _CursorScreen {
DisplayCursorProcPtr DisplayCursor;
CloseScreenProcPtr CloseScreen;
ConstrainCursorHarderProcPtr ConstrainCursorHarder;
CursorHideCountPtr pCursorHideCounts;
struct list barriers;
} CursorScreenRec, *CursorScreenPtr;
#define GetCursorScreen(s) ((CursorScreenPtr)dixLookupPrivate(&(s)->devPrivates, CursorScreenPrivateKey))
@ -184,9 +198,11 @@ CursorCloseScreen (int index, ScreenPtr pScreen)
Bool ret;
CloseScreenProcPtr close_proc;
DisplayCursorProcPtr display_proc;
ConstrainCursorHarderProcPtr constrain_proc;
Unwrap (cs, pScreen, CloseScreen, close_proc);
Unwrap (cs, pScreen, DisplayCursor, display_proc);
Unwrap (cs, pScreen, ConstrainCursorHarder, constrain_proc);
deleteCursorHideCountsForScreen(pScreen);
ret = (*pScreen->CloseScreen) (index, pScreen);
free(cs);
@ -1029,6 +1045,382 @@ CursorFreeWindow (pointer data, XID id)
return 1;
}
static BOOL
barrier_is_horizontal(const struct PointerBarrier *barrier)
{
return barrier->y1 == barrier->y2;
}
static BOOL
barrier_is_vertical(const struct PointerBarrier *barrier)
{
return barrier->x1 == barrier->x2;
}
/**
* @return The set of barrier movement directions the movement vector
* x1/y1 x2/y2 represents.
*/
int
barrier_get_direction(int x1, int y1, int x2, int y2)
{
int direction = 0;
/* which way are we trying to go */
if (x2 > x1)
direction |= BarrierPositiveX;
if (x2 < x1)
direction |= BarrierNegativeX;
if (y2 > y1)
direction |= BarrierPositiveY;
if (y2 < y1)
direction |= BarrierNegativeY;
return direction;
}
/**
* Test if the barrier may block movement in the direction defined by
* x1/y1 x2/y2. This function only tests whether the directions could be
* blocked, it does not test if the barrier actually blocks the movement.
*
* @return TRUE if the barrier blocks the direction of movement or FALSE
* otherwise.
*/
BOOL
barrier_is_blocking_direction(const struct PointerBarrier *barrier, int direction)
{
/* Barriers define which way is ok, not which way is blocking */
return (barrier->directions & direction) != direction;
}
/**
* Test if the movement vector x1/y1 x2/y2 is intersecting with the
* barrier. A movement vector with the startpoint or endpoint adjacent to
* the barrier itself counts as intersecting.
*
* @param x1 X start coordinate of movement vector
* @param y1 Y start coordinate of movement vector
* @param x2 X end coordinate of movement vector
* @param y2 Y end coordinate of movement vector
* @param[out] distance The distance between the start point and the
* intersection with the barrier (if applicable).
* @return TRUE if the barrier intersects with the given vector
*/
BOOL
barrier_is_blocking(const struct PointerBarrier *barrier,
int x1, int y1, int x2, int y2,
double *distance)
{
BOOL rc = FALSE;
float ua, ub, ud;
int dir = barrier_get_direction(x1, y1, x2, y2);
/* Algorithm below doesn't handle edge cases well, hence the extra
* checks. */
if (barrier_is_vertical(barrier)) {
/* handle immediate barrier adjacency, moving away */
if (dir & BarrierPositiveX && x1 == barrier->x1)
return FALSE;
if (dir & BarrierNegativeX && x1 == (barrier->x1 - 1))
return FALSE;
/* startpoint adjacent to barrier, moving towards -> block */
if (x1 == barrier->x1 && y1 >= barrier->y1 && y1 <= barrier->y2) {
*distance = 0;
return TRUE;
}
} else {
/* handle immediate barrier adjacency, moving away */
if (dir & BarrierPositiveY && y1 == barrier->y1)
return FALSE;
if (dir & BarrierNegativeY && y1 == (barrier->y1 - 1))
return FALSE;
/* startpoint adjacent to barrier, moving towards -> block */
if (y1 == barrier->y1 && x1 >= barrier->x1 && x1 <= barrier->x2) {
*distance = 0;
return TRUE;
}
}
/* not an edge case, compute distance */
ua = 0;
ud = (barrier->y2 - barrier->y1) * (x2 - x1) - (barrier->x2 - barrier->x1) * (y2 - y1);
if (ud != 0) {
ua = ((barrier->x2 - barrier->x1) * (y1 - barrier->y1) -
(barrier->y2 - barrier->y1) * (x1 - barrier->x1)) / ud;
ub = ((x2 - x1) * (y1 - barrier->y1) -
(y2 - y1) * (x1 - barrier->x1)) / ud;
if (ua < 0 || ua > 1 || ub < 0 || ub > 1)
ua = 0;
}
if (ua > 0 && ua <= 1)
{
double ix = barrier->x1 + ua * (barrier->x2 - barrier->x1);
double iy = barrier->y1 + ua * (barrier->y2 - barrier->y1);
*distance = sqrt(pow(x1 - ix, 2) + pow(y1 - iy, 2));
rc = TRUE;
}
return rc;
}
/**
* Find the nearest barrier that is blocking movement from x1/y1 to x2/y2.
*
* @param dir Only barriers blocking movement in direction dir are checked
* @param x1 X start coordinate of movement vector
* @param y1 Y start coordinate of movement vector
* @param x2 X end coordinate of movement vector
* @param y2 Y end coordinate of movement vector
* @return The barrier nearest to the movement origin that blocks this movement.
*/
static struct PointerBarrier*
barrier_find_nearest(CursorScreenPtr cs, int dir,
int x1, int y1, int x2, int y2)
{
struct PointerBarrierClient *c;
struct PointerBarrier *nearest = NULL;
double min_distance = INT_MAX; /* can't get higher than that in X anyway */
list_for_each_entry(c, &cs->barriers, entry) {
struct PointerBarrier *b = &c->barrier;
double distance;
if (!barrier_is_blocking_direction(b, dir))
continue;
if (barrier_is_blocking(b, x1, y1, x2, y2, &distance))
{
if (min_distance > distance)
{
min_distance = distance;
nearest = b;
}
}
}
return nearest;
}
/**
* Clamp to the given barrier given the movement direction specified in dir.
*
* @param barrier The barrier to clamp to
* @param dir The movement direction
* @param[out] x The clamped x coordinate.
* @param[out] y The clamped x coordinate.
*/
void
barrier_clamp_to_barrier(struct PointerBarrier *barrier, int dir, int *x, int *y)
{
if (barrier_is_vertical(barrier))
{
if ((dir & BarrierNegativeX) & ~barrier->directions)
*x = barrier->x1;
if ((dir & BarrierPositiveX) & ~barrier->directions)
*x = barrier->x1 - 1;
}
if (barrier_is_horizontal(barrier))
{
if ((dir & BarrierNegativeY) & ~barrier->directions)
*y = barrier->y1;
if ((dir & BarrierPositiveY) & ~barrier->directions)
*y = barrier->y1 - 1;
}
}
static void
CursorConstrainCursorHarder(DeviceIntPtr dev, ScreenPtr screen, int mode, int *x, int *y)
{
CursorScreenPtr cs = GetCursorScreen(screen);
if (!list_is_empty(&cs->barriers) && !IsFloating(dev) && mode == Relative) {
int ox, oy;
int dir;
struct PointerBarrier *nearest = NULL;
/* where are we coming from */
miPointerGetPosition(dev, &ox, &oy);
/* How this works:
* Given the origin and the movement vector, get the nearest barrier
* to the origin that is blocking the movement.
* Clamp to that barrier.
* Then, check from the clamped intersection to the original
* destination, again finding the nearest barrier and clamping.
*/
dir = barrier_get_direction(ox, oy, *x, *y);
nearest = barrier_find_nearest(cs, dir, ox, oy, *x, *y);
if (nearest) {
barrier_clamp_to_barrier(nearest, dir, x, y);
if (barrier_is_vertical(nearest)) {
dir &= ~(BarrierNegativeX | BarrierPositiveX);
ox = *x;
} else if (barrier_is_horizontal(nearest)) {
dir &= ~(BarrierNegativeY | BarrierPositiveY);
oy = *y;
}
nearest = barrier_find_nearest(cs, dir, ox, oy, *x, *y);
if (nearest) {
barrier_clamp_to_barrier(nearest, dir, x, y);
}
}
}
if (cs->ConstrainCursorHarder) {
screen->ConstrainCursorHarder = cs->ConstrainCursorHarder;
screen->ConstrainCursorHarder(dev, screen, mode, x, y);
screen->ConstrainCursorHarder = CursorConstrainCursorHarder;
}
}
static struct PointerBarrierClient *
CreatePointerBarrierClient(ScreenPtr screen, ClientPtr client,
xXFixesCreatePointerBarrierReq *stuff)
{
CursorScreenPtr cs = GetCursorScreen(screen);
struct PointerBarrierClient *ret = malloc(sizeof(*ret));
if (ret) {
ret->screen = screen;
ret->barrier.x1 = min(stuff->x1, stuff->x2);
ret->barrier.x2 = max(stuff->x1, stuff->x2);
ret->barrier.y1 = min(stuff->y1, stuff->y2);
ret->barrier.y2 = max(stuff->y1, stuff->y2);
ret->barrier.directions = stuff->directions & 0x0f;
if (barrier_is_horizontal(&ret->barrier))
ret->barrier.directions &= ~(BarrierPositiveX | BarrierNegativeX);
if (barrier_is_vertical(&ret->barrier))
ret->barrier.directions &= ~(BarrierPositiveY | BarrierNegativeY);
list_add(&ret->entry, &cs->barriers);
}
return ret;
}
int
ProcXFixesCreatePointerBarrier (ClientPtr client)
{
int err;
WindowPtr pWin;
struct PointerBarrierClient *barrier;
struct PointerBarrier b;
REQUEST (xXFixesCreatePointerBarrierReq);
REQUEST_SIZE_MATCH(xXFixesCreatePointerBarrierReq);
LEGAL_NEW_RESOURCE(stuff->barrier, client);
err = dixLookupWindow(&pWin, stuff->window, client, DixReadAccess);
if (err != Success) {
client->errorValue = stuff->window;
return err;
}
/* This sure does need fixing. */
if (stuff->num_devices)
return BadImplementation;
b.x1 = stuff->x1;
b.x2 = stuff->x2;
b.y1 = stuff->y1;
b.y2 = stuff->y2;
if (!barrier_is_horizontal(&b) && !barrier_is_vertical(&b))
return BadValue;
/* no 0-sized barriers */
if (barrier_is_horizontal(&b) && barrier_is_vertical(&b))
return BadValue;
if (!(barrier = CreatePointerBarrierClient(pWin->drawable.pScreen,
client, stuff)))
return BadAlloc;
if (!AddResource(stuff->barrier, PointerBarrierType, &barrier->barrier))
return BadAlloc;
return Success;
}
int
SProcXFixesCreatePointerBarrier (ClientPtr client)
{
int n;
REQUEST(xXFixesCreatePointerBarrierReq);
swaps(&stuff->length, n);
REQUEST_SIZE_MATCH(xXFixesCreatePointerBarrierReq);
swapl(&stuff->barrier, n);
swapl(&stuff->window, n);
swaps(&stuff->x1, n);
swaps(&stuff->y1, n);
swaps(&stuff->x2, n);
swaps(&stuff->y2, n);
swapl(&stuff->directions, n);
return ProcXFixesVector[stuff->xfixesReqType](client);
}
static int
CursorFreeBarrier(void *data, XID id)
{
struct PointerBarrierClient *b = NULL, *barrier;
ScreenPtr screen;
CursorScreenPtr cs;
barrier = container_of(data, struct PointerBarrierClient, barrier);
screen = barrier->screen;
cs = GetCursorScreen(screen);
/* find and unlink from the screen private */
list_for_each_entry(b, &cs->barriers, entry) {
if (b == barrier) {
list_del(&b->entry);
break;
}
}
free(barrier);
return Success;
}
int
ProcXFixesDestroyPointerBarrier (ClientPtr client)
{
int err;
void *barrier;
REQUEST (xXFixesDestroyPointerBarrierReq);
REQUEST_SIZE_MATCH(xXFixesDestroyPointerBarrierReq);
err = dixLookupResourceByType((void **)&barrier, stuff->barrier,
PointerBarrierType, client,
DixDestroyAccess);
if (err != Success) {
client->errorValue = stuff->barrier;
return err;
}
FreeResource(stuff->barrier, RT_NONE);
return Success;
}
int
SProcXFixesDestroyPointerBarrier (ClientPtr client)
{
int n;
REQUEST(xXFixesDestroyPointerBarrierReq);
swaps(&stuff->length, n);
REQUEST_SIZE_MATCH(xXFixesDestroyPointerBarrierReq);
swapl(&stuff->barrier, n);
return ProcXFixesVector[stuff->xfixesReqType](client);
}
Bool
XFixesCursorInit (void)
{
@ -1048,8 +1440,10 @@ XFixesCursorInit (void)
cs = (CursorScreenPtr) calloc(1, sizeof (CursorScreenRec));
if (!cs)
return FALSE;
list_init(&cs->barriers);
Wrap (cs, pScreen, CloseScreen, CursorCloseScreen);
Wrap (cs, pScreen, DisplayCursor, CursorDisplayCursor);
Wrap (cs, pScreen, ConstrainCursorHarder, CursorConstrainCursorHarder);
cs->pCursorHideCounts = NULL;
SetCursorScreen (pScreen, cs);
}
@ -1059,7 +1453,10 @@ XFixesCursorInit (void)
"XFixesCursorHideCount");
CursorWindowType = CreateNewResourceType(CursorFreeWindow,
"XFixesCursorWindow");
PointerBarrierType = CreateNewResourceType(CursorFreeBarrier,
"XFixesPointerBarrier");
return CursorClientType && CursorHideCountType && CursorWindowType;
return CursorClientType && CursorHideCountType && CursorWindowType &&
PointerBarrierType;
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* Copyright 2010 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@ -47,10 +48,6 @@
#include "xfixesint.h"
#include "protocol-versions.h"
/*
* Must use these instead of the constants from xfixeswire.h. They advertise
* what we implement, not what the protocol headers define.
*/
static unsigned char XFixesReqCode;
int XFixesEventBase;
@ -97,11 +94,12 @@ ProcXFixesQueryVersion(ClientPtr client)
/* Major version controls available requests */
static const int version_requests[] = {
X_XFixesQueryVersion, /* before client sends QueryVersion */
X_XFixesGetCursorImage, /* Version 1 */
X_XFixesChangeCursorByName, /* Version 2 */
X_XFixesExpandRegion, /* Version 3 */
X_XFixesShowCursor, /* Version 4 */
X_XFixesQueryVersion, /* before client sends QueryVersion */
X_XFixesGetCursorImage, /* Version 1 */
X_XFixesChangeCursorByName, /* Version 2 */
X_XFixesExpandRegion, /* Version 3 */
X_XFixesShowCursor, /* Version 4 */
X_XFixesDestroyPointerBarrier, /* Version 5 */
};
#define NUM_VERSION_REQUESTS (sizeof (version_requests) / sizeof (version_requests[0]))
@ -142,6 +140,9 @@ int (*ProcXFixesVector[XFixesNumberRequests])(ClientPtr) = {
/*************** Version 4 ****************/
ProcXFixesHideCursor,
ProcXFixesShowCursor,
/*************** Version 5 ****************/
ProcXFixesCreatePointerBarrier,
ProcXFixesDestroyPointerBarrier,
};
static int
@ -205,6 +206,9 @@ static int (*SProcXFixesVector[XFixesNumberRequests])(ClientPtr) = {
/*************** Version 4 ****************/
SProcXFixesHideCursor,
SProcXFixesShowCursor,
/*************** Version 5 ****************/
SProcXFixesCreatePointerBarrier,
SProcXFixesDestroyPointerBarrier,
};
static int
@ -260,6 +264,8 @@ XFixesExtensionInit(void)
EventSwapVector[XFixesEventBase + XFixesCursorNotify] =
(EventSwapPtr) SXFixesCursorNotifyEvent;
SetResourceTypeErrorValue(RegionResType, XFixesErrorBase + BadRegion);
SetResourceTypeErrorValue(PointerBarrierType,
XFixesErrorBase + BadBarrier);
}
}

View file

@ -30,6 +30,7 @@
#include "resource.h"
extern _X_EXPORT RESTYPE RegionResType;
extern _X_EXPORT RESTYPE PointerBarrierType;
extern _X_EXPORT int XFixesErrorBase;
#define VERIFY_REGION(pRegion, rid, client, mode) \
@ -51,5 +52,21 @@ extern _X_EXPORT int XFixesErrorBase;
extern _X_EXPORT RegionPtr
XFixesRegionCopy (RegionPtr pRegion);
struct PointerBarrier {
CARD16 x1, x2, y1, y2;
CARD32 directions;
};
extern int
barrier_get_direction(int, int, int, int);
extern BOOL
barrier_is_blocking(const struct PointerBarrier*, int, int, int, int, double*);
extern BOOL
barrier_is_blocking_direction(const struct PointerBarrier*, int);
extern void
barrier_clamp_to_barrier(struct PointerBarrier *barrier, int dir, int *x, int *y);
#endif /* _XFIXES_H_ */

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* Copyright 2010 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@ -278,6 +279,21 @@ ProcXFixesShowCursor (ClientPtr client);
int
SProcXFixesShowCursor (ClientPtr client);
/* Version 5 */
int
ProcXFixesCreatePointerBarrier (ClientPtr client);
int
SProcXFixesCreatePointerBarrier (ClientPtr client);
int
ProcXFixesDestroyPointerBarrier (ClientPtr client);
int
SProcXFixesDestroyPointerBarrier (ClientPtr client);
/* Xinerama */
extern int (*PanoramiXSaveXFixesVector[XFixesNumberRequests])(ClientPtr);
void PanoramiXFixesInit (void);
void PanoramiXFixesReset (void);