diff --git a/README.md b/README.md index f847b020..5f7045ab 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ information on what to include when reporting a bug. - Added `GLFW_OSMESA_CONTEXT_API` for creating OpenGL contexts with [OSMesa](https://www.mesa3d.org/osmesa.html) (#281) - Added `GenerateMappings.cmake` script for updating gamepad mappings +- Added `GLFW_RAW_INPUT` input mode and `glfwRawInputSupported` function (#1401) - Made `glfwCreateWindowSurface` emit an error when the window has a context (#1194,#1205) - Deprecated window parameter of clipboard string functions @@ -489,6 +490,7 @@ skills. - Santi Zupancic - Jonas Ådahl - Lasse Öörni + - Nathan Poirier - All the unmentioned and anonymous contributors in the GLFW community, for bug reports, patches, feedback, testing and encouragement diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index efd38339..9c0eaa88 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1002,6 +1002,7 @@ extern "C" { #define GLFW_STICKY_KEYS 0x00033002 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_LOCK_KEY_MODS 0x00033004 +#define GLFW_RAW_INPUT 0x00033005 #define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_HIDDEN 0x00034002 @@ -3812,11 +3813,12 @@ GLFWAPI void glfwPostEmptyEvent(void); * * This function returns the value of an input option for the specified window. * The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, - * @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS. + * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or + * @ref GLFW_RAW_INPUT. * * @param[in] window The window to query. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, - * `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`. + * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or `GLFW_RAW_INPUT`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. @@ -3835,7 +3837,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * * This function sets an input mode option for the specified window. The mode * must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, - * @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS. + * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or + * @ref GLFW_RAW_INPUT. * * If the mode is `GLFW_CURSOR`, the value must be one of the following cursor * modes: @@ -3867,9 +3870,14 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * GLFW_MOD_CAPS_LOCK bit set when the event was generated with Caps Lock on, * and the @ref GLFW_MOD_NUM_LOCK bit when Num Lock was on. * + * If the mode is `GLFW_RAW_INPUT`, the value must be either `GLFW_TRUE` to + * enable the use of raw input, or `GLFW_FALSE` to disable it. If enabled and + * supported by the machine, the program will retrieve high-definition mouse + * movement when cursor is grabbed. + * * @param[in] window The window whose input mode to set. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, - * `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`. + * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or `GLFW_RAW_INPUT`. * @param[in] value The new value of the specified input mode. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref @@ -3885,6 +3893,38 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); */ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); +/*! @brief Returns whether the raw input is supported. + * + * This function returns whether the raw input is supported by the current + * machine. + * + * Raw input allow to retrieve high-definition movement from mouse. + * Input from a high-definition mouse is much more precise than that from a + * standard mouse. But often, they cannot be obtained through standard + * platforms API which transform mouse movement using their own improvements + * (like pointer acceleration). Platform's improvements are ideal for pointer + * control but it is not so good for moving a first-person camera. For this + * reason when the cursor of a window is grabbed by setting @ref GLFW_CURSOR + * to @ref GLFW_CURSOR_DISABLED, raw input is used. + * + * The use of raw input can be disabled using @ref glfwSetInputMode with + * @ref GLFW_RAW_INPUT mode. + * + * @return `GLFW_TRUE` if high-definition mouse movement is supported, or + * `GLFW_FALSE` otherwise. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function may be called from any thread. + * + * @sa @ref glfwSetInputMode + * + * @since Added in version 3.3. + * + * @ingroup input + */ +GLFWAPI int glfwRawInputSupported(void); + /*! @brief Returns the layout-specific name of the specified printable key. * * This function returns the name of the specified printable key, encoded as diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 156b97b1..e3e20de7 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1297,6 +1297,16 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) [window->ns.object setAlphaValue:opacity]; } +void _glfwPlatformSetRawInput(_GLFWwindow *window, GLFWbool enabled) +{ + window->useRawInput = enabled; +} + +GLFWbool _glfwPlatformRawInputSupported(void) +{ + return GLFW_FALSE; +} + void _glfwPlatformPollEvents(void) { for (;;) diff --git a/src/input.c b/src/input.c index 33ed0604..ef14fad5 100644 --- a/src/input.c +++ b/src/input.c @@ -484,6 +484,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode) return window->stickyMouseButtons; case GLFW_LOCK_KEY_MODS: return window->lockKeyMods; + case GLFW_RAW_INPUT: + return window->useRawInput; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); @@ -561,10 +563,18 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value) } else if (mode == GLFW_LOCK_KEY_MODS) window->lockKeyMods = value ? GLFW_TRUE : GLFW_FALSE; + else if (mode == GLFW_RAW_INPUT) + _glfwPlatformSetRawInput(window, value ? GLFW_TRUE : GLFW_FALSE); else _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); } +GLFWAPI int glfwRawInputSupported(void) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(0); + return _glfwPlatformRawInputSupported(); +} + GLFWAPI const char* glfwGetKeyName(int key, int scancode) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); @@ -1313,4 +1323,3 @@ GLFWAPI uint64_t glfwGetTimerFrequency(void) _GLFW_REQUIRE_INIT_OR_RETURN(0); return _glfwPlatformGetTimerFrequency(); } - diff --git a/src/internal.h b/src/internal.h index e8df80a0..65d77cc1 100644 --- a/src/internal.h +++ b/src/internal.h @@ -390,6 +390,7 @@ struct _GLFWwindow char keys[GLFW_KEY_LAST + 1]; // Virtual cursor position when cursor is disabled double virtualCursorPosX, virtualCursorPosY; + GLFWbool useRawInput; _GLFWcontext context; @@ -596,6 +597,8 @@ const char* _glfwPlatformGetVersionString(void); void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos); void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode); +void _glfwPlatformSetRawInput(_GLFWwindow *window, GLFWbool enabled); +GLFWbool _glfwPlatformRawInputSupported(void); int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot); int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape); diff --git a/src/null_window.c b/src/null_window.c index 6a54cfe5..cb976310 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -196,6 +196,16 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { } +void _glfwPlatformSetRawInput(_GLFWwindow *window, GLFWbool enabled) +{ + window->useRawInput = enabled; +} + +GLFWbool _glfwPlatformRawInputSupported(void) +{ + return GLFW_FALSE; +} + void _glfwPlatformShowWindow(_GLFWwindow* window) { } diff --git a/src/win32_window.c b/src/win32_window.c index 77338468..ec010f7e 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -281,7 +281,7 @@ static void disableCursor(_GLFWwindow* window) _glfwCenterCursorInContentArea(window); updateClipRect(window); - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) + if (window->useRawInput && !RegisterRawInputDevices(&rid, 1, sizeof(rid))) { _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to register raw input device"); @@ -301,7 +301,7 @@ static void enableCursor(_GLFWwindow* window) _glfw.win32.restoreCursorPosY); updateCursorImage(window); - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) + if (window->useRawInput && !RegisterRawInputDevices(&rid, 1, sizeof(rid))) { _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, "Win32: Failed to remove raw input device"); @@ -816,9 +816,18 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, // Disabled cursor motion input is provided by WM_INPUT if (window->cursorMode == GLFW_CURSOR_DISABLED) - break; + { + if (_glfw.win32.disabledCursorWindow != window || window->useRawInput) + break; - _glfwInputCursorPos(window, x, y); + const int dx = x - window->win32.lastCursorPosX; + const int dy = y - window->win32.lastCursorPosY; + _glfwInputCursorPos(window, + window->virtualCursorPosX + dx, + window->virtualCursorPosY + dy); + } + else + _glfwInputCursorPos(window, x, y); window->win32.lastCursorPosX = x; window->win32.lastCursorPosY = y; @@ -847,7 +856,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, int dx, dy; // Only process input when disabled cursor mode is applied - if (_glfw.win32.disabledCursorWindow != window) + if (_glfw.win32.disabledCursorWindow != window || !window->useRawInput) break; GetRawInputData(ri, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)); @@ -1845,6 +1854,24 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) } } +void _glfwPlatformSetRawInput(_GLFWwindow *window, GLFWbool enabled) +{ + if (window->useRawInput != enabled) + { + int update = (_glfw.win32.disabledCursorWindow == window); + if (update) + enableCursor(window); + window->useRawInput = enabled; + if (update) + disableCursor(window); + } +} + +GLFWbool _glfwPlatformRawInputSupported(void) +{ + return GLFW_TRUE; +} + void _glfwPlatformPollEvents(void) { MSG msg; diff --git a/src/window.c b/src/window.c index 24e60054..f8cb030e 100644 --- a/src/window.c +++ b/src/window.c @@ -202,6 +202,7 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, window->floating = wndconfig.floating; window->focusOnShow = wndconfig.focusOnShow; window->cursorMode = GLFW_CURSOR_NORMAL; + window->useRawInput = GLFW_TRUE; window->minwidth = GLFW_DONT_CARE; window->minheight = GLFW_DONT_CARE; @@ -1097,4 +1098,3 @@ GLFWAPI void glfwPostEmptyEvent(void) _GLFW_REQUIRE_INIT(); _glfwPlatformPostEmptyEvent(); } - diff --git a/src/wl_window.c b/src/wl_window.c index ebd76bb3..42645be5 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -1318,6 +1318,16 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { } +void _glfwPlatformSetRawInput(_GLFWwindow *window, GLFWbool enabled) +{ + window->useRawInput = enabled; +} + +GLFWbool _glfwPlatformRawInputSupported(void) +{ + return GLFW_FALSE; +} + void _glfwPlatformPollEvents(void) { handleEvents(0); diff --git a/src/x11_window.c b/src/x11_window.c index 8e587546..9e25fda3 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -526,7 +526,7 @@ static void updateCursorImage(_GLFWwindow* window) // static void disableCursor(_GLFWwindow* window) { - if (_glfw.x11.xi.available) + if (_glfw.x11.xi.available && window->useRawInput) { XIEventMask em; unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; @@ -557,7 +557,7 @@ static void disableCursor(_GLFWwindow* window) // static void enableCursor(_GLFWwindow* window) { - if (_glfw.x11.xi.available) + if (_glfw.x11.xi.available && window->useRawInput) { XIEventMask em; unsigned char mask[] = { 0 }; @@ -1183,6 +1183,7 @@ static void processEvent(XEvent *event) _GLFWwindow* window = _glfw.x11.disabledCursorWindow; if (window && + window->useRawInput && event->xcookie.extension == _glfw.x11.xi.majorOpcode && XGetEventData(_glfw.x11.display, &event->xcookie) && event->xcookie.evtype == XI_RawMotion) @@ -1483,7 +1484,7 @@ static void processEvent(XEvent *event) { if (_glfw.x11.disabledCursorWindow != window) return; - if (_glfw.x11.xi.available) + if (_glfw.x11.xi.available && window->useRawInput) return; const int dx = x - window->x11.lastCursorPosX; @@ -2652,6 +2653,24 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) PropModeReplace, (unsigned char*) &value, 1); } +void _glfwPlatformSetRawInput(_GLFWwindow *window, GLFWbool enabled) +{ + if (window->useRawInput != enabled) + { + int update = (_glfw.x11.disabledCursorWindow == window && _glfw.x11.xi.available); + if (update) + enableCursor(window); + window->useRawInput = enabled; + if (update) + disableCursor(window); + } +} + +GLFWbool _glfwPlatformRawInputSupported(void) +{ + return _glfw.x11.xi.available; +} + void _glfwPlatformPollEvents(void) { _GLFWwindow* window; diff --git a/tests/cursor.c b/tests/cursor.c index 4cc74ad5..a64c0bf8 100644 --- a/tests/cursor.c +++ b/tests/cursor.c @@ -163,6 +163,19 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, printf("(( cursor is hidden ))\n"); break; + case GLFW_KEY_R: + if (glfwGetInputMode(window, GLFW_RAW_INPUT)) + { + glfwSetInputMode(window, GLFW_RAW_INPUT, GLFW_FALSE); + printf("(( raw input is disabled ))\n"); + } + else + { + glfwSetInputMode(window, GLFW_RAW_INPUT, GLFW_TRUE); + printf("(( raw input is enabled ))\n"); + } + break; + case GLFW_KEY_SPACE: swap_interval = 1 - swap_interval; printf("(( swap interval: %i ))\n", swap_interval);