diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index fb0980ab3..e2235084c 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1510,6 +1510,59 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); */ typedef void (* GLFWjoystickfun)(int,int); +/*! @brief The function signature for pen tablet data callbacks. + * + * This is the function signature for pen tablet data callback functions. + * + * @param[in] x pen position relative to the screen. + * @param[in] y pen position relative to the screen. + * @param[in] z pen position relative to the tablet. + * @param[in] pen pressure from 0.0 to 1.0. + * @param[in] pen pitch in radian. + * @param[in] pen yaw in radian. + * @param[in] pen roll in radian. + * + * @sa @ref pen_tablet_data + * @sa @ref glfwSetPenTabletDataCallback + * + * @since Added in version 3.3. + * + * @ingroup input + */ +typedef void (* GLFWpentabletdatafun)(double,double,double,double,double,double,double); + +/*! @brief The function signature for pen tablet cursor callbacks. + * + * This is the function signature for pen tablet cursor callback functions. + * It is called when the tablet cursor is changed, from stylus to eraser for example. + * + * @param[in] pen cursor identifier. 1 is usually a stylus, 2 and 3 an eraser. + * + * @sa @ref pen_tablet_cursor + * @sa @ref glfwSetPenTabletCursorCallback + * + * @since Added in version 3.3. + * + * @ingroup input + */ +typedef void (* GLFWpentabletcursorfun)(unsigned int); + +/*! @brief The function signature for pen tablet proximity callbacks. + * + * This is the function signature for pen tablet proximity callback functions. + * It is called when a tablet device (pen etc) entering or is exiting tablet proximity. + * + * @param[in] pen proximity state. 1 = entering, 0 = exiting. + * + * @sa @ref pen_tablet_proximity + * @sa @ref glfwSetPenTabletProximityCallback + * + * @since Added in version 3.3. + * + * @ingroup input + */ +typedef void (* GLFWpentabletproximityfun)(int); + /*! @brief Video mode type. * * This describes a single video mode. @@ -4994,6 +5047,72 @@ GLFWAPI const char* glfwGetGamepadName(int jid); */ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state); +/*! @brief Sets the pen tablet data callback. + * + * This function sets the pen tablet data callback, or removes the + * currently set callback. This is called when the pen tablet data is updated. + * + * @param[in] cbfun The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref pen_tablet_event + * + * @since Added in version 3.3. + * + * @ingroup input + */ +GLFWAPI GLFWpentabletdatafun glfwSetPenTabletDataCallback(GLFWpentabletdatafun cbfun); + +/*! @brief Sets the pen tablet cursor callback. + * + * This function sets the pen tablet cursor callback, or removes the + * currently set callback. This is called when the pen tablet cursor has changed. + * + * @param[in] cbfun The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref pen_tablet_event + * + * @since Added in version 3.3. + * + * @ingroup input + */ +GLFWAPI GLFWpentabletcursorfun glfwSetPenTabletCursorCallback(GLFWpentabletcursorfun cbfun); + +/*! @brief Sets the pen tablet proximity callback. + * + * This function sets the pen tablet proximity callback, or removes the + * currently set callback. This is called when the pen tablet proximity has changed. + * + * @param[in] cbfun The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref pen_tablet_event + * + * @since Added in version 3.3. + * + * @ingroup input + */ +GLFWAPI GLFWpentabletproximityfun glfwSetPenTabletProximityCallback(GLFWpentabletproximityfun cbfun); + /*! @brief Sets the clipboard to the specified string. * * This function sets the system clipboard to the specified, UTF-8 encoded diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 888edc9a6..e5761d0b2 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -26,6 +26,7 @@ #include "internal.h" +#include #include #include @@ -44,6 +45,8 @@ #define NSEventMaskAny NSAnyEventMask #define NSEventTypeApplicationDefined NSApplicationDefined #define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat + #define NSEventSubtypeTabletPoint NSTabletPointEventSubtype + #define NSEventSubtypeTabletProximity NSTabletProximityEventSubtype #endif // Returns the style mask corresponding to the window settings @@ -437,6 +440,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)mouseDown:(NSEvent *)event { + [self handlePenTablet:event]; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, @@ -450,14 +454,69 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)mouseUp:(NSEvent *)event { + [self handlePenTablet:event]; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, translateFlags([event modifierFlags])); } +- (void)handlePenTablet:(NSEvent *)event +{ + if ([window->ns.object isKeyWindow]) + { + if ([event subtype] == NSEventSubtypeTabletPoint) + { + const double pressure = [event pressure]; + const NSPoint tilt = [event tilt]; + const NSPoint pos = [NSEvent mouseLocation]; + const double posz = [event absoluteZ]; + + double tx = tilt.x * 1.5707963267949; + double ty = tilt.y * 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( + pos.x, + CGDisplayBounds(CGMainDisplayID()).size.height - pos.y, + posz / 1024.0, + pressure, + pitch, + yaw, + 0.0); + } + + if ([event subtype] == NSEventSubtypeTabletProximity) + { + static unsigned int s_cursor = 0; + unsigned int cursor = [event pointingDeviceType]; + + _glfwInputPenTabletProximity([event isEnteringProximity]); + if (cursor != s_cursor) + { + _glfwInputPenTabletCursor(cursor); + s_cursor = cursor; + } + } + } +} + - (void)mouseMoved:(NSEvent *)event { + [self handlePenTablet:event]; + if (window->cursorMode == GLFW_CURSOR_DISABLED) { const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; @@ -481,6 +540,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)rightMouseDown:(NSEvent *)event { + [self handlePenTablet:event]; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, @@ -494,6 +554,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)rightMouseUp:(NSEvent *)event { + [self handlePenTablet:event]; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, @@ -502,6 +563,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)otherMouseDown:(NSEvent *)event { + [self handlePenTablet:event]; _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_PRESS, @@ -515,6 +577,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)otherMouseUp:(NSEvent *)event { + [self handlePenTablet:event]; _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_RELEASE, diff --git a/src/input.c b/src/input.c index 7ee428117..b146c1001 100644 --- a/src/input.c +++ b/src/input.c @@ -401,6 +401,29 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value) js->hats[hat] = value; } +// Notifies shared code of the new value of the pen tablet data +// +void _glfwInputPenTabletData(double x, double y, double z, double pressure, double pitch, double yaw, double roll) +{ + if (_glfw.callbacks.pentabletdata) + _glfw.callbacks.pentabletdata(x, y, z, pressure, pitch, yaw, roll); +} + +// Notifies shared code of the new value of the pen tablet cursor +// +void _glfwInputPenTabletCursor(unsigned int cursor) +{ + if (_glfw.callbacks.pentabletcursor) + _glfw.callbacks.pentabletcursor(cursor); +} + +// Notifies shared code of the new value of the pen tablet proximity +// +void _glfwInputPenTabletProximity(int proximity) +{ + if (_glfw.callbacks.pentabletproximity) + _glfw.callbacks.pentabletproximity(proximity); +} ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// @@ -1294,6 +1317,27 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state) return GLFW_TRUE; } +GLFWAPI GLFWpentabletdatafun glfwSetPenTabletDataCallback(GLFWpentabletdatafun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.pentabletdata, cbfun); + return cbfun; +} + +GLFWAPI GLFWpentabletcursorfun glfwSetPenTabletCursorCallback(GLFWpentabletcursorfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.pentabletcursor, cbfun); + return cbfun; +} + +GLFWAPI GLFWpentabletproximityfun glfwSetPenTabletProximityCallback(GLFWpentabletproximityfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.pentabletproximity, cbfun); + return cbfun; +} + GLFWAPI void glfwSetClipboardString(GLFWwindow* handle, const char* string) { assert(string != NULL); diff --git a/src/internal.h b/src/internal.h index 3d5e22f7e..9e4bf3466 100644 --- a/src/internal.h +++ b/src/internal.h @@ -567,6 +567,9 @@ struct _GLFWlibrary struct { GLFWmonitorfun monitor; GLFWjoystickfun joystick; + GLFWpentabletdatafun pentabletdata; + GLFWpentabletcursorfun pentabletcursor; + GLFWpentabletproximityfun pentabletproximity; } callbacks; // This is defined in the window API's platform.h @@ -729,6 +732,10 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value); void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement); void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window); +void _glfwInputPenTabletData(double x, double y, double z, double pressure, double pitch, double yaw, double roll); +void _glfwInputPenTabletCursor(unsigned int cursor); +void _glfwInputPenTabletProximity(int proximity); + #if defined(__GNUC__) void _glfwInputError(int code, const char* format, ...) __attribute__((format(printf, 2, 3))); diff --git a/src/win32_init.c b/src/win32_init.c index 3ee5eb853..0fa995be1 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -159,6 +159,21 @@ static GLFWbool loadLibraries(void) GetProcAddress(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo"); } + _glfw.win32.wintab32.instance = LoadLibraryA("Wintab32.dll"); + if (_glfw.win32.wintab32.instance) + { + _glfw.win32.wintab32.WTInfoA = (PFN_WTInfoA) + GetProcAddress(_glfw.win32.wintab32.instance, "WTInfoA"); + _glfw.win32.wintab32.WTOpenA = (PFN_WTOpenA) + GetProcAddress(_glfw.win32.wintab32.instance, "WTOpenA"); + _glfw.win32.wintab32.WTQueueSizeSet = (PFN_WTQueueSizeSet) + GetProcAddress(_glfw.win32.wintab32.instance, "WTQueueSizeSet"); + _glfw.win32.wintab32.WTClose = (PFN_WTClose) + GetProcAddress(_glfw.win32.wintab32.instance, "WTClose"); + _glfw.win32.wintab32.WTPacket = (PFN_WTPacket) + GetProcAddress(_glfw.win32.wintab32.instance, "WTPacket"); + } + return GLFW_TRUE; } @@ -186,6 +201,9 @@ static void freeLibraries(void) if (_glfw.win32.ntdll.instance) FreeLibrary(_glfw.win32.ntdll.instance); + + if (_glfw.win32.wintab32.instance) + FreeLibrary(_glfw.win32.wintab32.instance); } // Create key code translation tables @@ -327,6 +345,46 @@ static void createKeyTables(void) } } +// Init wintab context see https://developer-docs.wacom.com/display/DevDocs/Windows+Wintab+Documentation +// +static void initWintabContext(HWND hwnd) +{ + if (_glfw.win32.wintab32.instance) { + LOGCONTEXTA context = {0}; + + _glfw.win32.wintab32.WTInfoA(4, 0, &context); + context.lcPktData = 0x0080 | 0x0100 | 0x0200 | 0x0040 | 0x0400 | 0x1000 | 0x0008 | 0x0020; // X Y Z BUTTONS NPRESSURE ORIENTATION CHANGED CURSOR + context.lcPktMode = 0; + context.lcMoveMask = context.lcPktData; + context.lcBtnUpMask = context.lcBtnDnMask; + context.lcOptions |= 0x0004; // CXO MESSAGES + context.lcOutOrgX = context.lcInOrgX; + context.lcOutOrgY = context.lcInOrgY; + context.lcOutExtX = context.lcInExtX; + context.lcOutExtY = -context.lcInExtY; + + // open wintab context + _glfw.win32.wintab32.context = _glfw.win32.wintab32.WTOpenA(hwnd, &context, TRUE); + if (_glfw.win32.wintab32.context) { + _glfw.win32.wintab32.WTQueueSizeSet(_glfw.win32.wintab32.context, 256); + _glfw.win32.wintab32.WTInfoA(4, 0, &_glfw.win32.wintab32.contextInfo); + _glfw.win32.wintab32.WTInfoA(100, 15, &_glfw.win32.wintab32.pressureInfo); + _glfw.win32.wintab32.WTInfoA(100, 17, &_glfw.win32.wintab32.orientationInfo); + } + } + else { + _glfw.win32.wintab32.context = 0; + } +} + +// Terminate wintab context +// +static void terminateWintabContext(void) +{ + if (_glfw.win32.wintab32.instance && _glfw.win32.wintab32.context) + _glfw.win32.wintab32.WTClose(_glfw.win32.wintab32.context); +} + // Creates a dummy window for behind-the-scenes work // static HWND createHelperWindow(void) @@ -577,6 +635,7 @@ int _glfwPlatformInit(void) _glfwInitTimerWin32(); _glfwInitJoysticksWin32(); + initWintabContext(_glfw.win32.helperWindowHandle); _glfwPollMonitorsWin32(); return GLFW_TRUE; @@ -604,6 +663,7 @@ void _glfwPlatformTerminate(void) _glfwTerminateEGL(); _glfwTerminateJoysticksWin32(); + terminateWintabContext(); freeLibraries(); } diff --git a/src/win32_platform.h b/src/win32_platform.h index 9a867c357..e925e9965 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -258,6 +258,78 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*, typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG); #define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_ +// wintab.dll function pointer typedefs +#define WT_PACKET 0x7FF0 +#define WT_PROXIMITY 0x7FF5 +DECLARE_HANDLE(HCTX); + +typedef struct tagAXIS { + LONG min; + LONG max; + UINT units; + DWORD resolution; +} AXIS; + +typedef struct tagORIENTATION { + int azimuth; + int altitude; + int twist; +} ORIENTATION; + +typedef struct tagPACKET { + DWORD changed; + UINT cursor; + DWORD buttons; + DWORD x; + DWORD y; + DWORD z; + UINT normalPressure; + ORIENTATION orientation; +} PACKET; + +typedef struct LOGCONTEXTA { + char lcName[40]; + UINT lcOptions; + UINT lcStatus; + UINT lcLocks; + UINT lcMsgBase; + UINT lcDevice; + UINT lcPktRate; + DWORD lcPktData; + DWORD lcPktMode; + DWORD lcMoveMask; + DWORD lcBtnDnMask; + DWORD lcBtnUpMask; + LONG lcInOrgX; + LONG lcInOrgY; + LONG lcInOrgZ; + LONG lcInExtX; + LONG lcInExtY; + LONG lcInExtZ; + LONG lcOutOrgX; + LONG lcOutOrgY; + LONG lcOutOrgZ; + LONG lcOutExtX; + LONG lcOutExtY; + LONG lcOutExtZ; + DWORD lcSensX; + DWORD lcSensY; + DWORD lcSensZ; + BOOL lcSysMode; + int lcSysOrgX; + int lcSysOrgY; + int lcSysExtX; + int lcSysExtY; + DWORD lcSysSensX; + DWORD lcSysSensY; +} LOGCONTEXTA, *PLOGCONTEXTA, NEAR *NPLOGCONTEXTA, FAR *LPLOGCONTEXTA; + +typedef UINT (WINAPI * PFN_WTInfoA)(UINT,UINT,LPVOID); +typedef HCTX (WINAPI * PFN_WTOpenA)(HWND,LPLOGCONTEXTA,BOOL); +typedef BOOL (WINAPI * PFN_WTQueueSizeSet)(HCTX,int); +typedef BOOL (WINAPI * PFN_WTClose)(HCTX); +typedef BOOL (WINAPI * PFN_WTPacket)(HCTX,UINT,LPVOID); + typedef VkFlags VkWin32SurfaceCreateFlagsKHR; typedef struct VkWin32SurfaceCreateInfoKHR @@ -383,6 +455,19 @@ typedef struct _GLFWlibraryWin32 PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_; } ntdll; + struct { + HINSTANCE instance; + PFN_WTInfoA WTInfoA; + PFN_WTOpenA WTOpenA; + PFN_WTQueueSizeSet WTQueueSizeSet; + PFN_WTClose WTClose; + PFN_WTPacket WTPacket; + HCTX context; + LOGCONTEXTA contextInfo; + AXIS pressureInfo; + AXIS orientationInfo[3]; + } wintab32; + } _GLFWlibraryWin32; // Win32-specific per-monitor data diff --git a/src/win32_window.c b/src/win32_window.c index 728b6e07a..edc8e4c95 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -616,6 +616,48 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, break; } + + case WT_PROXIMITY: + { + _glfwInputPenTabletProximity(lParam != 0); + } + + case WT_PACKET: + if (_glfw.win32.wintab32.instance) + { + #define FIX2DOUBLE(x) ((double)(HIWORD(x))+((double)LOWORD(x)/65536)) + PACKET packet; + LOGCONTEXTA contextInfo = _glfw.win32.wintab32.contextInfo; + AXIS pressureInfo = _glfw.win32.wintab32.pressureInfo; + AXIS altInfo = _glfw.win32.wintab32.orientationInfo[1]; + AXIS aziInfo = _glfw.win32.wintab32.orientationInfo[0]; + AXIS rollInfo = _glfw.win32.wintab32.orientationInfo[2]; + + while (_glfw.win32.wintab32.WTPacket(_glfw.win32.wintab32.context, (UINT)wParam, &packet)) { + + double x, y, z, pressure, pitch=0, yaw=0, roll=0; + + x = ((double)(packet.x - contextInfo.lcInOrgX) / contextInfo.lcInExtX) * contextInfo.lcSysExtX + contextInfo.lcSysOrgX; + y = ((double)(packet.y - contextInfo.lcInOrgY) / contextInfo.lcInExtY) * contextInfo.lcSysExtY + contextInfo.lcSysOrgY; + z = packet.z / 1024.0; + pressure = (double)(packet.normalPressure - pressureInfo.min) / (pressureInfo.max - pressureInfo.min); + if (aziInfo.resolution && altInfo.resolution) { + double alt = (double)(packet.orientation.altitude - altInfo.min) / (altInfo.max - altInfo.min); + double azi = (double)(packet.orientation.azimuth - aziInfo.min) / (aziInfo.max - aziInfo.min); + pitch = alt * 3.14159265359; + yaw = azi * 6.28318530718; + } + if (rollInfo.resolution) { // roll seems to be mostly unsupported so this is untested + roll = (double)(packet.orientation.twist - rollInfo.min) / (rollInfo.max - rollInfo.min) * 360.0; + } + + if (packet.changed & 0x0020) { // CURSOR changed + _glfwInputPenTabletCursor(packet.cursor); + } + + _glfwInputPenTabletData(x, y, z, pressure, pitch, yaw, roll); + } + } } return DefWindowProcW(hWnd, uMsg, wParam, lParam); diff --git a/src/x11_init.c b/src/x11_init.c index 6c951c045..9ccab3f81 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -494,6 +494,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", @@ -849,11 +853,119 @@ 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.tablet_cursor = 0; + _glfw.x11.xi.stylus_deviceid = 0; + _glfw.x11.xi.eraser_deviceid = 0; + _glfw.x11.xi.stylus_CTMatrix = NULL; + _glfw.x11.xi.eraser_CTMatrix = NULL; + + 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); + } + } + } +} + +// XInput2 terminate pen tablet +// +static void terminatePenTablet(void) +{ + if (_glfw.x11.xi.stylus_CTMatrix) + { + XFree(_glfw.x11.xi.stylus_CTMatrix); + _glfw.x11.xi.stylus_CTMatrix = NULL; + } + + if (_glfw.x11.xi.eraser_CTMatrix) + { + XFree(_glfw.x11.xi.eraser_CTMatrix); + _glfw.x11.xi.eraser_CTMatrix = NULL; + } +} + ////////////////////////////////////////////////////////////////////////// ////// 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) @@ -991,11 +1103,15 @@ int _glfwPlatformInit(void) _glfwInitTimerPOSIX(); _glfwPollMonitorsX11(); + + initPenTablet(_glfw.x11.display); return GLFW_TRUE; } void _glfwPlatformTerminate(void) { + terminatePenTablet(); + if (_glfw.x11.helperWindowHandle) { if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) == diff --git a/src/x11_platform.h b/src/x11_platform.h index f9e7194ce..2290bede4 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*); @@ -387,6 +391,14 @@ 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; + unsigned int tablet_cursor; + int stylus_deviceid; + int eraser_deviceid; } xi; struct { @@ -438,6 +450,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..941a2fdcc 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -32,6 +32,7 @@ #include +#include #include #include #include @@ -1192,33 +1193,161 @@ 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( + _glfw.x11.display, re->deviceid, + re->property, + 0, 5, False, + XIAnyPropertyType, + &type, &format, &num_items, &bytes_after, + (unsigned char **)&data) == Success) + { + if (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) + { + 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 != _glfw.x11.xi.tablet_cursor) + { + _glfwInputPenTabletCursor(cursor); + _glfw.x11.xi.tablet_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; diff --git a/tests/events.c b/tests/events.c index 5a0fde04a..9bcb5f666 100644 --- a/tests/events.c +++ b/tests/events.c @@ -42,6 +42,10 @@ #include "getopt.h" +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define snprintf _snprintf +#endif + // Event index static unsigned int counter = 0; @@ -493,6 +497,24 @@ static void joystick_callback(int jid, int event) } } +void penTabletData_callback(double x, double y, double z, double pressure, double pitch, double yaw, double roll) +{ + printf("%08x at %0.3f: pen: %f %f %f %f %f %f %f\n", + counter++, glfwGetTime(), x, y, z, pressure, pitch, yaw, roll); +} + +void penTabletCursor_callback(unsigned int cursor) +{ + printf("%08x at %0.3f: pen cursor: %d\n", + counter++, glfwGetTime(), cursor); +} + +void penTabletProximity_callback(int proximity) +{ + printf("%08x at %0.3f: pen proximity: %d\n", + counter++, glfwGetTime(), proximity); +} + int main(int argc, char** argv) { Slot* slots; @@ -510,6 +532,9 @@ int main(int argc, char** argv) glfwSetMonitorCallback(monitor_callback); glfwSetJoystickCallback(joystick_callback); + glfwSetPenTabletDataCallback(penTabletData_callback); + glfwSetPenTabletCursorCallback(penTabletCursor_callback); + glfwSetPenTabletProximityCallback(penTabletProximity_callback); while ((ch = getopt(argc, argv, "hfn:")) != -1) {