Add XInput2 tablet support

This commit is contained in:
Anaël Seghezzi 2019-03-08 18:07:44 +01:00
parent 58d3c0fd45
commit 6ab20fa776
3 changed files with 259 additions and 20 deletions

View File

@ -490,6 +490,10 @@ static GLFWbool initExtensions(void)
_glfw_dlsym(_glfw.x11.xi.handle, "XIQueryVersion");
_glfw.x11.xi.SelectEvents = (PFN_XISelectEvents)
_glfw_dlsym(_glfw.x11.xi.handle, "XISelectEvents");
_glfw.x11.xi.QueryDevice = (PFN_XIQueryDevice)
_glfw_dlsym(_glfw.x11.xi.handle, "XIQueryDevice");
_glfw.x11.xi.GetProperty = (PFN_XIGetProperty)
_glfw_dlsym(_glfw.x11.xi.handle, "XIGetProperty");
if (XQueryExtension(_glfw.x11.display,
"XInputExtension",
@ -845,11 +849,99 @@ static int errorHandler(Display *display, XErrorEvent* event)
return 0;
}
// Coordinate Transformation Matrix
//
static float * getDeviceCoordinateTransformationMatrix(Display *display, int deviceid)
{
float *data = NULL;
Atom type;
int format;
unsigned long num_items, bytes_after;
if (XIGetProperty(
display, deviceid,
XInternAtom(display, "Coordinate Transformation Matrix", False),
0, 9, False,
XInternAtom(display, "FLOAT", False),
&type, &format, &num_items, &bytes_after,
(unsigned char **)&data
) != Success)
return NULL;
return data;
}
// XInput2 init pen tablet
//
static void initPenTablet(Display *display)
{
_glfw.x11.xi.stylus_deviceid = 0;
_glfw.x11.xi.eraser_deviceid = 0;
if (_glfw.x11.xi.available)
{
int i, n;
XIDeviceInfo *dev_info = XIQueryDevice(display, XIAllDevices, &n);
for (i = 0; i < n; i++)
{
XIDeviceInfo *dev = &dev_info[i];
if (strstr(dev->name, "stylus") || strstr(dev->name, "eraser"))
{
XIEventMask mask;
mask.deviceid = dev->deviceid;
mask.mask_len = XIMaskLen(XI_RawMotion);
mask.mask = calloc(mask.mask_len, sizeof(char));
XISetMask(mask.mask, XI_PropertyEvent); // property event to catch proximity change
XISetMask(mask.mask, XI_RawMotion); // raw motion to catch x, y, pressure and tilt
XISelectEvents(display, DefaultRootWindow(display), &mask, 1);
if (strstr(dev->name, "stylus"))
{
int c;
_glfw.x11.xi.stylus_deviceid = dev->deviceid;
for (c = 0; c < dev->num_classes; c++)
{
if (dev->classes[c]->type == XIValuatorClass)
{
XIValuatorClassInfo *v = (XIValuatorClassInfo*)dev->classes[c];
if (v->number < 32) _glfw.x11.xi.stylus_ClassInfo[v->number] = *v;
}
}
_glfw.x11.xi.stylus_CTMatrix = getDeviceCoordinateTransformationMatrix(display, dev->deviceid);
}
else
{
_glfw.x11.xi.eraser_deviceid = dev->deviceid;
_glfw.x11.xi.eraser_CTMatrix = getDeviceCoordinateTransformationMatrix(display, dev->deviceid);
}
free(mask.mask);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Apply Coordinate Transformation Matrix to a 2d vector
//
void _glfwApplyCoordTransformMatrix(float *matrix, float *px, float *py)
{
float x = *px;
float y = *py;
*px = x * matrix[0] + y * matrix[1] + matrix[2];
*py = x * matrix[3] + y * matrix[4] + matrix[5];
}
// Sets the X error handler callback
//
void _glfwGrabErrorHandlerX11(void)
@ -987,6 +1079,8 @@ int _glfwPlatformInit(void)
_glfwInitTimerPOSIX();
_glfwPollMonitorsX11();
initPenTablet(_glfw.x11.display);
return GLFW_TRUE;
}

View File

@ -113,8 +113,12 @@ typedef Bool (* PFN_XF86VidModeGetGammaRampSize)(Display*,int,int*);
typedef Status (* PFN_XIQueryVersion)(Display*,int*,int*);
typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int);
typedef XIDeviceInfo* (* PFN_XIQueryDevice)(Display*,int,int*);
typedef Status (* PFN_XIGetProperty)(Display*,int,Atom,long,long,Bool,Atom,Atom*,int*,unsigned long*,unsigned long*,unsigned char**);
#define XIQueryVersion _glfw.x11.xi.QueryVersion
#define XISelectEvents _glfw.x11.xi.SelectEvents
#define XIQueryDevice _glfw.x11.xi.QueryDevice
#define XIGetProperty _glfw.x11.xi.GetProperty
typedef Bool (* PFN_XRenderQueryExtension)(Display*,int*,int*);
typedef Status (* PFN_XRenderQueryVersion)(Display*dpy,int*,int*);
@ -385,6 +389,13 @@ typedef struct _GLFWlibraryX11
int minor;
PFN_XIQueryVersion QueryVersion;
PFN_XISelectEvents SelectEvents;
PFN_XIQueryDevice QueryDevice;
PFN_XIGetProperty GetProperty;
XIValuatorClassInfo stylus_ClassInfo[32];
float *stylus_CTMatrix;
float *eraser_CTMatrix;
int stylus_deviceid;
int eraser_deviceid;
} xi;
struct {
@ -436,6 +447,7 @@ unsigned long _glfwGetWindowPropertyX11(Window window,
unsigned char** value);
GLFWbool _glfwIsVisualTransparentX11(Visual* visual);
void _glfwApplyCoordTransformMatrix(float *matrix, float *px, float *py);
void _glfwGrabErrorHandlerX11(void);
void _glfwReleaseErrorHandlerX11(void);
void _glfwInputErrorX11(int error, const char* message);

View File

@ -32,6 +32,7 @@
#include <sys/select.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
@ -1192,33 +1193,165 @@ static void processEvent(XEvent *event)
{
_GLFWwindow* window = _glfw.x11.disabledCursorWindow;
if (window &&
window->rawMouseMotion &&
event->xcookie.extension == _glfw.x11.xi.majorOpcode &&
XGetEventData(_glfw.x11.display, &event->xcookie) &&
event->xcookie.evtype == XI_RawMotion)
if (event->xcookie.extension == _glfw.x11.xi.majorOpcode &&
XGetEventData(_glfw.x11.display, &event->xcookie))
{
XIRawEvent* re = event->xcookie.data;
if (re->valuators.mask_len)
if (event->xcookie.evtype == XI_PropertyEvent)
{
const double* values = re->raw_values;
double xpos = window->virtualCursorPosX;
double ypos = window->virtualCursorPosY;
// the first proximity event seems to arrive one later AFTER XI_RawMotion...
XIPropertyEvent* re = event->xcookie.data;
if (XIMaskIsSet(re->valuators.mask, 0))
// pen tablet property
if (re->deviceid == _glfw.x11.xi.stylus_deviceid ||
re->deviceid == _glfw.x11.xi.eraser_deviceid)
{
xpos += *values;
values++;
if (re->what == XIPropertyModified)
{
float *data = NULL;
Atom type;
int format;
unsigned long num_items, bytes_after;
if (XIGetProperty(
re->display, re->deviceid,
re->property,
0, 5, False,
XIAnyPropertyType,
&type, &format, &num_items, &bytes_after,
(unsigned char **)&data
) != Success)
data = NULL;
if (data && format == 32 && num_items > 4)
{
_glfwInputPenTabletProximity(((unsigned int *)data)[4] != 0);
XFree(data);
}
}
}
if (XIMaskIsSet(re->valuators.mask, 1))
ypos += *values;
_glfwInputCursorPos(window, xpos, ypos);
}
}
else if (event->xcookie.evtype == XI_RawMotion)
{
XIRawEvent* re = event->xcookie.data;
if (re->valuators.mask_len)
{
const double* values = re->raw_values;
XFreeEventData(_glfw.x11.display, &event->xcookie);
// pen tablet raw motion
if (re->deviceid == _glfw.x11.xi.stylus_deviceid ||
re->deviceid == _glfw.x11.xi.eraser_deviceid)
{
static unsigned int s_cursor = 0;
unsigned int cursor = re->deviceid == _glfw.x11.xi.stylus_deviceid ? 1 : 2;
float x = 0.0f;
float y = 0.0f;
double pressure = 0.0;
double tiltx = 0.0;
double tilty = 0.0;
if (cursor != s_cursor)
{
_glfwInputPenTabletCursor(cursor);
s_cursor = cursor;
}
if (XIMaskIsSet(re->valuators.mask, 0))
{
XIValuatorClassInfo *v = &_glfw.x11.xi.stylus_ClassInfo[0];
x = (*values - v->min) / (v->max - v->min);
values++;
}
if (XIMaskIsSet(re->valuators.mask, 1))
{
XIValuatorClassInfo *v = &_glfw.x11.xi.stylus_ClassInfo[1];
y = (*values - v->min) / (v->max - v->min);
values++;
}
if (XIMaskIsSet(re->valuators.mask, 2))
{
XIValuatorClassInfo *v = &_glfw.x11.xi.stylus_ClassInfo[2];
pressure = (*values - v->min) / (v->max - v->min);
values++;
}
if (XIMaskIsSet(re->valuators.mask, 3))
{
XIValuatorClassInfo *v = &_glfw.x11.xi.stylus_ClassInfo[3];
tiltx = (*values - v->min) / (v->max - v->min) * 2.0 - 1.0;
values++;
}
if (XIMaskIsSet(re->valuators.mask, 4))
{
XIValuatorClassInfo *v = &_glfw.x11.xi.stylus_ClassInfo[4];
tilty = (*values - v->min) / (v->max - v->min) * 2.0 - 1.0;
values++;
}
// apply coordinate transform matrix depending on current cursor
if (re->deviceid == _glfw.x11.xi.stylus_deviceid)
{
if (_glfw.x11.xi.stylus_CTMatrix)
_glfwApplyCoordTransformMatrix(_glfw.x11.xi.stylus_CTMatrix, &x, &y);
}
else
{
if (_glfw.x11.xi.eraser_CTMatrix)
_glfwApplyCoordTransformMatrix(_glfw.x11.xi.eraser_CTMatrix, &x, &y);
}
// send data
{
double tx = tiltx * 1.5707963267949;
double ty = -tilty * 1.5707963267949;
double sinx = sin(tx);
double siny = sin(ty);
double cosx = cos(tx);
double cosy = cos(ty);
/*double matrix[9] = { // full matrix for reference
0.0, -cosy, siny,
cosx, -sinx*siny, -sinx*cosy,
sinx, cosx*siny, cosx*cosy
};*/
double v[3] = {sinx, cosx*siny, cosx*cosy};
double yaw = atan2(v[0], v[1]);
double pitch = 3.141592653589793 - acos(v[2]);
if (yaw < 0.0) yaw += 6.28318530717959;
_glfwInputPenTabletData(
(double)x * DisplayWidth(_glfw.x11.display, _glfw.x11.screen),
(double)y * DisplayHeight(_glfw.x11.display, _glfw.x11.screen),
0.0, // can't find z coordinate
pressure,
pitch,
yaw,
0.0);
}
}
// mouse raw motion
else if (window && window->rawMouseMotion)
{
double xpos = window->virtualCursorPosX;
double ypos = window->virtualCursorPosY;
if (XIMaskIsSet(re->valuators.mask, 0))
{
xpos += *values;
values++;
}
if (XIMaskIsSet(re->valuators.mask, 1))
ypos += *values;
_glfwInputCursorPos(window, xpos, ypos);
}
}
}
XFreeEventData(_glfw.x11.display, &event->xcookie);
}
}
return;