From 6ab20fa776bfff3dac563cc6ec22bcdc889e9619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=ABl=20Seghezzi?= Date: Fri, 8 Mar 2019 18:07:44 +0100 Subject: [PATCH] Add XInput2 tablet support --- src/x11_init.c | 94 ++++++++++++++++++++++++ src/x11_platform.h | 12 ++++ src/x11_window.c | 173 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 259 insertions(+), 20 deletions(-) diff --git a/src/x11_init.c b/src/x11_init.c index e3e3ad518..436151753 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -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; } diff --git a/src/x11_platform.h b/src/x11_platform.h index c37c740e4..5a05701e2 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -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); diff --git a/src/x11_window.c b/src/x11_window.c index 92a137f48..8b131361c 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -32,6 +32,7 @@ #include +#include #include #include #include @@ -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;