[PATCHv2] evdev: add 3x3 transformation matrix xinput property for multi-head handling
Simon Thum
simon.thum at gmx.de
Wed Apr 21 04:00:19 PDT 2010
Hi all,
> 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.
I like the idea, but just if you haven't considered it: more or less the
same thing could be done in dix (GetPointerEvents), where it would stand
a remote chance to sync with randr, if desired. Also, in dix one would
have more control when creating synthetic events, properly do Xi2
raw/screen coords etc.
Not striking, but it may be better in the long run.
Cheers,
Simon
Am 21.04.2010 08:47, schrieb Peter Korsgaard:
>
> 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>
> Acked-by: Oliver McFadden <oliver.mcfadden at nokia.com>
> ---
> Changes since V1:
> - Add missing xfree() noticed by Oliver
>
> include/evdev-properties.h | 7 ++
> man/evdev.man | 13 ++++
> src/evdev.c | 139 +++++++++++++++++++++++++++++++++++++++++++-
> src/evdev.h | 2 +
> 4 files changed, 160 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..31da4bf 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,21 @@ 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]);
> + xfree(str);
> + 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 +2640,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 +2693,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];
More information about the xorg-devel
mailing list