[PATCH] evdev: add 3x3 transformation matrix xinput property for multi-head handling
Peter Korsgaard
peter.korsgaard at barco.com
Tue Apr 20 07:53:01 PDT 2010
For absolute input devices (E.G. touchscreens) in multi-head setups, we
need a way to bind the device to an randr output. This adds the
infrastructure to evdev to allow us to do so.
The X server scales input coordinates to the dimensions of the shared
(total) frame buffer, so to restrict motion to an output we need to
scale/rotate/translate device coordinates to a subset of the frame buffer
before passing them on to the X server.
This is done here using a 3x3 transformation matrix, which is applied to
the device coordinates using homogeneous coordinates, E.G.:
[ c0 c1 c2 ] [ x ]
[ c3 c4 c5 ] * [ y ]
[ c6 c7 c8 ] * [ 1 ]
Notice: As input devices have varying input ranges, the coordinates are first
scaled to the [0..1] range for generality, and afterwards scaled back up.
E.G. for a dual head setup (using same resolution) next to each other, you
would want to scale the X coordinates of touchscreen connected to the
both heads by 50%, and translate (offset) the coordinates of the rightmost
head by 50%, or in matrix form:
left: right:
[ 0.5 0 0 ] [ 0.5 0 0.5 ]
[ 0 1 0 ] [ 0 1 0 ]
[ 0 0 1 ] [ 0 0 0 ]
Which can be done using xinput:
xinput set-prop <left> --type=float "Evdev Coordinate Transformation Matrix" \
0.5 0 0 0 1 0 0 0 1
xinput set-prop <right> --type=float "Evdev Coordinate Transformation Matrix" \
0.5 0 0.5 0 1 0 0 0 1
This is general enough to replace the various tweaks we have in evdev
(InvertX/Y, Calibration, SwapAxes), but I have left them for now.
Signed-off-by: Peter Korsgaard <peter.korsgaard at barco.com>
---
include/evdev-properties.h | 7 ++
man/evdev.man | 13 ++++
src/evdev.c | 138 +++++++++++++++++++++++++++++++++++++++++++-
src/evdev.h | 2 +
4 files changed, 159 insertions(+), 1 deletions(-)
diff --git a/include/evdev-properties.h b/include/evdev-properties.h
index 7df2876..7417a5b 100644
--- a/include/evdev-properties.h
+++ b/include/evdev-properties.h
@@ -66,4 +66,11 @@
/* BOOL */
#define EVDEV_PROP_SWAP_AXES "Evdev Axes Swap"
+/* Coordinate transformation matrix */
+/* FLOAT, 9 values in row-major order, coordinates in 0..1 range: */
+/* [c0 c1 c2] [x] */
+/* [c5 c4 c5] * [y] */
+/* [c6 c7 c8] [1] */
+#define EVDEV_PROP_TRANSFORM "Evdev Coordinate Transformation Matrix"
+
#endif
diff --git a/man/evdev.man b/man/evdev.man
index 49ab12c..21d7aa4 100644
--- a/man/evdev.man
+++ b/man/evdev.man
@@ -161,6 +161,15 @@ originally reported by the kernel (e.g. touchscreens). The scaling to the
custom coordinate system is done in-driver and the X server is unaware of
the transformation. Property: "Evdev Axis Calibration".
.TP 7
+.BI "Option \*qTransformationMatrix\*q \*q" "c0 c1 c2 c3 c4 c5 c6 c7 c8" \*q
+Applies the 3x3 transformation matrix defined by c0..c8 in row-major
+order to the device coordinates before passing them on to the X
+server. The coefficients are floating point values, and the
+coordinates are scaled to the 0..1 range before transformed. This
+feature can be used to restrict absolute devices (e.g. touchscreens)
+to specific outputs in multi-head setups. Property: "Evdev Coordinate
+Transformation Matrix".
+.TP 7
.B Option \*qMode\*q \*qRelative\*q\fP|\fP\*qAbsolute\*q
Sets the mode of the device if device has absolute axes.
The default value for touchpads is relative, for other absolute.
@@ -196,6 +205,10 @@ driver.
4 32-bit values, order min-x, max-x, min-y, max-y or 0 values to disable
in-driver axis calibration.
.TP 7
+.BI "Evdev Coordinate Transformation Matrix"
+9 floating point values, defining the coordinate transformation matrix
+in row-major order.
+.TP 7
.BI "Evdev Axis Inversion"
2 boolean values (8 bit, 0 or 1), order X, Y. 1 inverts the axis.
.TP 7
diff --git a/src/evdev.c b/src/evdev.c
index 0678edf..c21ba0d 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -32,6 +32,8 @@
#endif
#include <xorg-server.h>
+#include <math.h>
+
#include <X11/keysym.h>
#include <X11/extensions/XI.h>
@@ -119,6 +121,7 @@ static Atom prop_calibration = 0;
static Atom prop_swap = 0;
static Atom prop_axis_label = 0;
static Atom prop_btn_label = 0;
+static Atom prop_transform = 0;
#endif
/* All devices the evdev driver has allocated and knows about.
@@ -347,6 +350,35 @@ EvdevQueueButtonClicks(InputInfoPtr pInfo, int button, int count)
}
}
+static int
+EvdevClip(InputInfoPtr pInfo, float f, int axis)
+{
+ EvdevPtr pEvdev = pInfo->private;
+ int v;
+
+ v = lroundf(f);
+
+ if (v > pEvdev->absinfo[axis].maximum)
+ v = pEvdev->absinfo[axis].maximum;
+ else if (v < pEvdev->absinfo[axis].minimum)
+ v = pEvdev->absinfo[axis].minimum;
+
+ return v;
+}
+
+static void
+EvdevTransformAbsolute(InputInfoPtr pInfo, int v[MAX_VALUATORS])
+{
+ EvdevPtr pEvdev = pInfo->private;
+ float x, y, *t = pEvdev->transform;
+
+ x = t[0]*v[0] + t[1]*v[1] + t[2];
+ y = t[3]*v[0] + t[4]*v[1] + t[5];
+
+ v[0] = EvdevClip(pInfo, x, ABS_X);
+ v[1] = EvdevClip(pInfo, y, ABS_Y);
+}
+
#define ABS_X_VALUE 0x1
#define ABS_Y_VALUE 0x2
#define ABS_VALUE 0x4
@@ -447,6 +479,8 @@ EvdevProcessValuators(InputInfoPtr pInfo, int v[MAX_VALUATORS], int *num_v,
v[1] = (pEvdev->absinfo[ABS_Y].maximum - v[1] +
pEvdev->absinfo[ABS_Y].minimum);
+ EvdevTransformAbsolute(pInfo, v);
+
*num_v = pEvdev->num_vals;
*first_v = 0;
}
@@ -1993,12 +2027,77 @@ EvdevSetCalibration(InputInfoPtr pInfo, int num_calibration, int calibration[4])
}
}
+/* a *= b */
+static void
+EvdevMatrixMul(float *a, float *b)
+{
+ int i, j, k;
+ float t[3];
+
+ for (i=0; i<3; i++)
+ {
+ for (j=0; j<3; j++)
+ {
+ t[j] = 0;
+
+ for (k=0; k<3; k++)
+ {
+ t[j] += a[i*3 + k]*b[k*3 + j];
+ }
+ }
+ memcpy(&a[i*3], t, sizeof(t));
+ }
+}
+
+static void
+EvdevSetTransform(InputInfoPtr pInfo, float *transform)
+{
+ EvdevPtr pEvdev = pInfo->private;
+ float scale[9], *t;
+ float sx, sy;
+
+ /* calculate combined transformation matrix:
+
+ M = InvScale * Transform * Scale
+
+ So we can later transform points using M * P
+
+ Where:
+ Scale scales coordinates into 0..1 range
+ Transform is the user supplied (affine) transform
+ InvScale scales coordinates back up into their native range
+ */
+ sx = pEvdev->absinfo[ABS_X].maximum - pEvdev->absinfo[ABS_X].minimum;
+ sy = pEvdev->absinfo[ABS_Y].maximum - pEvdev->absinfo[ABS_Y].minimum;
+
+ t = pEvdev->transform;
+ memset(t, 0, sizeof(pEvdev->transform));
+
+ t[0] = sx;
+ t[2] = pEvdev->absinfo[ABS_X].minimum;
+ t[4] = sy;
+ t[5] = pEvdev->absinfo[ABS_Y].minimum;
+ t[8] = 1.0;
+
+ EvdevMatrixMul(t, transform);
+
+ memset(scale, 0, sizeof(scale));
+ scale[0] = 1.0 / sx;
+ scale[2] = -pEvdev->absinfo[ABS_X].minimum / sx;
+ scale[4] = 1.0 / sy;
+ scale[5] = -pEvdev->absinfo[ABS_Y].minimum / sy;
+ scale[8] = 1.0;
+
+ EvdevMatrixMul(t, scale);
+}
+
static InputInfoPtr
EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
{
InputInfoPtr pInfo;
const char *device, *str;
- int num_calibration = 0, calibration[4] = { 0, 0, 0, 0 };
+ int num_transform, num_calibration = 0, calibration[4] = { 0, 0, 0, 0 };
+ float transform[9];
EvdevPtr pEvdev;
if (!(pInfo = xf86AllocateInput(drv, 0)))
@@ -2035,6 +2134,9 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
*/
pEvdev->tool = 1;
+ /* unity matrix */
+ pEvdev->transform[0] = pEvdev->transform[4] = pEvdev->transform[8] = 1.0f;
+
device = xf86CheckStrOption(dev->commonOptions, "Device", NULL);
if (!device) {
xf86Msg(X_ERROR, "%s: No device specified.\n", pInfo->name);
@@ -2084,6 +2186,20 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
pInfo->name, num_calibration);
}
+ str = xf86CheckStrOption(pInfo->options, "TransformationMatrix", NULL);
+ if (str) {
+ num_transform = sscanf(str, "%f %f %f %f %f %f %f %f %f",
+ &transform[0], &transform[1], &transform[2],
+ &transform[3], &transform[4], &transform[5],
+ &transform[6], &transform[7], &transform[8]);
+ if (num_transform == 9)
+ EvdevSetTransform(pInfo, transform);
+ else
+ xf86Msg(X_ERROR,
+ "%s: Insufficient transformation factors (%d). Ignoring transformation\n",
+ pInfo->name, num_transform);
+ }
+
/* Grabbing the event device stops in-kernel event forwarding. In other
words, it disables rfkill and the "Macintosh mouse button emulation".
Note that this needs a server that sets the console to RAW mode. */
@@ -2523,6 +2639,18 @@ EvdevInitProperty(DeviceIntPtr dev)
XISetDevicePropertyDeletable(dev, prop_btn_label, FALSE);
}
#endif /* HAVE_LABELS */
+
+ prop_transform = MakeAtom(EVDEV_PROP_TRANSFORM,
+ strlen(EVDEV_PROP_TRANSFORM), TRUE);
+
+ rc = XIChangeDeviceProperty(dev, prop_transform,
+ XIGetKnownProperty(XATOM_FLOAT), 32,
+ PropModeReplace, 9, pEvdev->transform,
+ FALSE);
+ if (rc != Success)
+ return;
+
+ XISetDevicePropertyDeletable(dev, prop_transform, FALSE);
}
}
@@ -2564,6 +2692,14 @@ EvdevSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
pEvdev->swap_axes = *((BOOL*)val->data);
} else if (atom == prop_axis_label || atom == prop_btn_label)
return BadAccess; /* Axis/Button labels can't be changed */
+ else if (atom == prop_transform) {
+ if (val->format != 32 || val->size != 9 ||
+ val->type != XIGetKnownProperty(XATOM_FLOAT))
+ return BadMatch;
+
+ if (!checkonly)
+ EvdevSetTransform(pInfo, val->data);
+ }
return Success;
}
diff --git a/src/evdev.h b/src/evdev.h
index 1133985..258c302 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -176,6 +176,8 @@ typedef struct {
int reopen_attempts; /* max attempts to re-open after read failure */
int reopen_left; /* number of attempts left to re-open the device */
OsTimerPtr reopen_timer;
+ float transform[9]; /* 3x3 coordinate transformation matrix in row major
+ order */
/* Cached info from device. */
char name[1024];
--
1.7.0
More information about the xorg
mailing list