diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 05e76a9a..07bad760 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -3366,6 +3366,27 @@ GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, */ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun); +/*! @brief Returns the time of the last input event. + * + * This function returns the time, in seconds, of the last event occurence. The + * only events queried are the input events button-press, button-release, + * key-press, key-release and cursor motion, and the proper place to call this + * function is in one of the input callbacks. + * + * @return The value, in seconds, of the last input event occurence. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref events + * + * @since Added in version 3.3. + * + * @ingroup window + */ +GLFWAPI double glfwGetEventTime(void); + /*! @brief Processes all pending events. * * This function processes only those events that are already in the event diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index f0ba4e85..9449f82e 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -118,6 +118,9 @@ typedef struct _GLFWlibraryNS // The window whose disabled cursor mode is active _GLFWwindow* disabledCursorWindow; + // The time of the last event + double lastEventTime; + struct { CFBundleRef bundle; PFN_TISCopyCurrentKeyboardLayoutInputSource CopyCurrentKeyboardLayoutInputSource; diff --git a/src/cocoa_window.m b/src/cocoa_window.m index cd4f3f89..7e950ae2 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -446,6 +446,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)mouseDown:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, @@ -454,11 +456,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)mouseDragged:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + [self mouseMoved:event]; } - (void)mouseUp:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, @@ -467,6 +473,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)mouseMoved:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + if (window->cursorMode == GLFW_CURSOR_DISABLED) { const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; @@ -490,6 +498,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)rightMouseDown:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, @@ -498,11 +508,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)rightMouseDragged:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + [self mouseMoved:event]; } - (void)rightMouseUp:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, @@ -511,6 +525,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)otherMouseDown:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_PRESS, @@ -519,11 +535,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)otherMouseDragged:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + [self mouseMoved:event]; } - (void)otherMouseUp:(NSEvent *)event { + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_RELEASE, @@ -588,6 +608,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; const int key = translateKey([event keyCode]); const int mods = translateFlags([event modifierFlags]); + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); [self interpretKeyEvents:[NSArray arrayWithObject:event]]; @@ -619,6 +641,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; { const int key = translateKey([event keyCode]); const int mods = translateFlags([event modifierFlags]); + + _glfw.ns.lastEventTime = [event timestamp]; + _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); } @@ -1462,6 +1487,12 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) [window->ns.object setLevel:NSNormalWindowLevel]; } +double _glfwPlatformGetEventTime(void) +{ + /* Windows events are stored in seconds */ + return _glfw.ns.lastEventTime; +} + void _glfwPlatformPollEvents(void) { for (;;) diff --git a/src/internal.h b/src/internal.h index a95d1f86..2c42fd26 100644 --- a/src/internal.h +++ b/src/internal.h @@ -685,6 +685,7 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled); +double _glfwPlatformGetEventTime(void); void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); void _glfwPlatformWaitEventsTimeout(double timeout); diff --git a/src/mir_platform.h b/src/mir_platform.h index da00a326..eb8de4b6 100644 --- a/src/mir_platform.h +++ b/src/mir_platform.h @@ -114,6 +114,9 @@ typedef struct _GLFWlibraryMir // The window whose disabled cursor mode is active _GLFWwindow* disabledCursorWindow; + // The time of the last event + int64_t lastEventTime; + } _GLFWlibraryMir; // Mir-specific per-cursor data diff --git a/src/mir_window.c b/src/mir_window.c index 3e8d5c5b..ad5e28ee 100644 --- a/src/mir_window.c +++ b/src/mir_window.c @@ -147,6 +147,8 @@ static void handleKeyEvent(const MirKeyboardEvent* key_event, _GLFWwindow* windo const long text = _glfwKeySym2Unicode(key_code); const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); + _glfw.mir.lastEventTime = mir_input_event_get_event_time((MirInputEvent *) key_event); + _glfwInputKey(window, toGLFWKeyCode(scan_code), scan_code, pressed, mods); if (text != -1) @@ -164,6 +166,8 @@ static void handlePointerButton(_GLFWwindow* window, uint32_t newButtonStates = mir_pointer_event_buttons(pointer_event); int publicButton = GLFW_MOUSE_BUTTON_LEFT; + _glfw.mir.lastEventTime = mir_input_event_get_event_time((MirEvent *) pointer_event); + // XOR our old button states our new states to figure out what was added or removed button = newButtonStates ^ oldButtonStates; @@ -201,6 +205,8 @@ static void handlePointerMotion(_GLFWwindow* window, const int hscroll = mir_pointer_event_axis_value(pointer_event, mir_pointer_axis_hscroll); const int vscroll = mir_pointer_event_axis_value(pointer_event, mir_pointer_axis_vscroll); + _glfw.mir.lastEventTime = mir_input_event_get_event_time((MirEvent *) pointer_event); + if (window->cursorMode == GLFW_CURSOR_DISABLED) { if (_glfw.mir.disabledCursorWindow != window) @@ -632,6 +638,12 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) "Mir: Unsupported function %s", __PRETTY_FUNCTION__); } +double _glfwPlatformGetEventTime(void) +{ + /* Mir events are stored in nanoseconds */ + return (double) _glfw.mir.lastEventTime / 1000000000.0; +} + void _glfwPlatformPollEvents(void) { EventNode* node = NULL; diff --git a/src/null_window.c b/src/null_window.c index 2e627672..4f4ea3f2 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -204,6 +204,11 @@ int _glfwPlatformWindowVisible(_GLFWwindow* window) return GLFW_FALSE; } +double _glfwPlatformGetEventTime(void) +{ + return 0.0; +} + void _glfwPlatformPollEvents(void) { } diff --git a/src/win32_platform.h b/src/win32_platform.h index 63aaa94d..02e9192e 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -259,6 +259,9 @@ typedef struct _GLFWlibraryWin32 RAWINPUT* rawInput; int rawInputSize; + // The time of the last event + LONG lastEventTime; + struct { HINSTANCE instance; PFN_timeGetTime GetTime; diff --git a/src/win32_window.c b/src/win32_window.c index aa7efc84..eabc1d3e 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -595,6 +595,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, case WM_UNICHAR: { const GLFWbool plain = (uMsg != WM_SYSCHAR); + _glfw.win32.lastEventTime = GetMessageTime(); if (uMsg == WM_UNICHAR && wParam == UNICODE_NOCHAR) { @@ -617,6 +618,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, const int scancode = (lParam >> 16) & 0x1ff; const int action = ((lParam >> 31) & 1) ? GLFW_RELEASE : GLFW_PRESS; const int mods = getKeyMods(); + _glfw.win32.lastEventTime = GetMessageTime(); if (key == _GLFW_KEY_INVALID) break; @@ -651,6 +653,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, case WM_XBUTTONUP: { int i, button, action; + _glfw.win32.lastEventTime = GetMessageTime(); if (uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP) button = GLFW_MOUSE_BUTTON_LEFT; @@ -701,6 +704,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, { const int x = GET_X_LPARAM(lParam); const int y = GET_Y_LPARAM(lParam); + _glfw.win32.lastEventTime = GetMessageTime(); // Disabled cursor motion input is provided by WM_INPUT if (window->cursorMode == GLFW_CURSOR_DISABLED) @@ -1498,6 +1502,12 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); } +double _glfwPlatformGetEventTime(void) +{ + /* Windows events are stored in milliseconds */ + return (double) _glfw.win32.lastEventTime / 1000.0; +} + void _glfwPlatformPollEvents(void) { MSG msg; diff --git a/src/window.c b/src/window.c index 5c33217d..047b3e05 100644 --- a/src/window.c +++ b/src/window.c @@ -956,6 +956,13 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* handle return cbfun; } +GLFWAPI double glfwGetEventTime(void) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(0.0); + + return _glfwPlatformGetEventTime(); +} + GLFWAPI void glfwPollEvents(void) { _GLFW_REQUIRE_INIT(); diff --git a/src/wl_init.c b/src/wl_init.c index 8e42ce6e..38083d97 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -83,6 +83,7 @@ static void pointerHandleMotion(void* data, if (!window) return; + _glfw.wl.lastEventTime = time; if (window->cursorMode == GLFW_CURSOR_DISABLED) return; else @@ -109,6 +110,7 @@ static void pointerHandleButton(void* data, if (!window) return; + _glfw.wl.lastEventTime = time; _glfw.wl.pointerSerial = serial; /* Makes left, right and middle 0, 1 and 2. Overall order follows evdev @@ -136,6 +138,7 @@ static void pointerHandleAxis(void* data, if (!window) return; + _glfw.wl.lastEventTime = time; /* Wayland scroll events are in pointer motion coordinate space (think * two finger scroll). The factor 10 is commonly used to convert to * "scroll step means 1.0. */ @@ -349,6 +352,7 @@ static void keyboardHandleKey(void* data, if (!window) return; + _glfw.wl.lastEventTime = time; keyCode = toGLFWKeyCode(key); action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE; diff --git a/src/wl_platform.h b/src/wl_platform.h index c543d82c..f237cdae 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -97,6 +97,7 @@ typedef struct _GLFWwindowWayland struct zwp_relative_pointer_v1* relativePointer; struct zwp_locked_pointer_v1* lockedPointer; } pointerLock; + } _GLFWwindowWayland; // Wayland-specific global data @@ -138,6 +139,9 @@ typedef struct _GLFWlibraryWayland _GLFWwindow* pointerFocus; _GLFWwindow* keyboardFocus; + // The time of the last event + unsigned int lastEventTime; + } _GLFWlibraryWayland; // Wayland-specific per-monitor data diff --git a/src/wl_window.c b/src/wl_window.c index fbd58ffb..01d04b5e 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -675,6 +675,11 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) "Wayland: Window attribute setting not implemented yet"); } +double _glfwPlatformGetEventTime(void) +{ + return (double) _glfw.wl.lastEventTime / 1000.0; +} + void _glfwPlatformPollEvents(void) { handleEvents(0); diff --git a/src/x11_platform.h b/src/x11_platform.h index 48d15bc9..cb710cf3 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -226,6 +226,9 @@ typedef struct _GLFWlibraryX11 // The window whose disabled cursor mode is active _GLFWwindow* disabledCursorWindow; + // The time of the last event + Time lastEventTime; + // Window manager atoms Atom WM_PROTOCOLS; Atom WM_STATE; diff --git a/src/x11_window.c b/src/x11_window.c index aee89d40..d6a96247 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1121,6 +1121,7 @@ static void processEvent(XEvent *event) const int key = translateKey(keycode); const int mods = translateState(event->xkey.state); const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); + _glfw.x11.lastEventTime = event->xkey.time; if (window->x11.ic) { @@ -1214,6 +1215,7 @@ static void processEvent(XEvent *event) { const int key = translateKey(keycode); const int mods = translateState(event->xkey.state); + _glfw.x11.lastEventTime = event->xkey.time; if (!_glfw.x11.xkb.detectable) { @@ -1254,6 +1256,7 @@ static void processEvent(XEvent *event) case ButtonPress: { const int mods = translateState(event->xbutton.state); + _glfw.x11.lastEventTime = event->xbutton.time; if (event->xbutton.button == Button1) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, mods); @@ -1288,6 +1291,7 @@ static void processEvent(XEvent *event) case ButtonRelease: { const int mods = translateState(event->xbutton.state); + _glfw.x11.lastEventTime = event->xbutton.time; if (event->xbutton.button == Button1) { @@ -1344,6 +1348,7 @@ static void processEvent(XEvent *event) { const int x = event->xmotion.x; const int y = event->xmotion.y; + _glfw.x11.lastEventTime = event->xmotion.time; if (x != window->x11.warpCursorPosX || y != window->x11.warpCursorPosY) { @@ -2428,6 +2433,12 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) XFlush(_glfw.x11.display); } +double _glfwPlatformGetEventTime(void) +{ + /* X11 events are stored in milliseconds */ + return (double) _glfw.x11.lastEventTime / 1000.0; +} + void _glfwPlatformPollEvents(void) { _GLFWwindow* window; diff --git a/tests/events.c b/tests/events.c index 7b42e4fd..7d3ce245 100644 --- a/tests/events.c +++ b/tests/events.c @@ -269,8 +269,8 @@ static void error_callback(int error, const char* description) static void window_pos_callback(GLFWwindow* window, int x, int y) { Slot* slot = glfwGetWindowUserPointer(window); - printf("%08x to %i at %0.3f: Window position: %i %i\n", - counter++, slot->number, glfwGetTime(), x, y); + printf("%08x to %i at %0.3f (event time: %0.3f): Window position: %i %i\n", + counter++, slot->number, glfwGetTime(), glfwGetEventTime(), x, y); } static void window_size_callback(GLFWwindow* window, int width, int height) @@ -336,8 +336,8 @@ static void window_maximize_callback(GLFWwindow* window, int maximized) static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) { Slot* slot = glfwGetWindowUserPointer(window); - printf("%08x to %i at %0.3f: Mouse button %i (%s) (with%s) was %s\n", - counter++, slot->number, glfwGetTime(), button, + printf("%08x to %i at %0.3f (event time: %0.3f): Mouse button %i (%s) (with%s) was %s\n", + counter++, slot->number, glfwGetTime(), glfwGetEventTime(), button, get_button_name(button), get_mods_name(mods), get_action_name(action)); @@ -346,8 +346,8 @@ static void mouse_button_callback(GLFWwindow* window, int button, int action, in static void cursor_position_callback(GLFWwindow* window, double x, double y) { Slot* slot = glfwGetWindowUserPointer(window); - printf("%08x to %i at %0.3f: Cursor position: %f %f\n", - counter++, slot->number, glfwGetTime(), x, y); + printf("%08x to %i at %0.3f (event time: %0.3f): Cursor position: %f %f\n", + counter++, slot->number, glfwGetTime(), glfwGetEventTime(), x, y); } static void cursor_enter_callback(GLFWwindow* window, int entered) @@ -372,8 +372,8 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, if (name) { - printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (%s) (with%s) was %s\n", - counter++, slot->number, glfwGetTime(), key, scancode, + printf("%08x to %i at %0.3f (event time: %0.3f): Key 0x%04x Scancode 0x%04x (%s) (%s) (with%s) was %s\n", + counter++, slot->number, glfwGetTime(), glfwGetEventTime(), key, scancode, get_key_name(key), name, get_mods_name(mods), @@ -381,8 +381,8 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, } else { - printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (with%s) was %s\n", - counter++, slot->number, glfwGetTime(), key, scancode, + printf("%08x to %i at %0.3f (event time: %0.3f): Key 0x%04x Scancode 0x%04x (%s) (with%s) was %s\n", + counter++, slot->number, glfwGetTime(), glfwGetEventTime(), key, scancode, get_key_name(key), get_mods_name(mods), get_action_name(action)); @@ -406,8 +406,8 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action, static void char_callback(GLFWwindow* window, unsigned int codepoint) { Slot* slot = glfwGetWindowUserPointer(window); - printf("%08x to %i at %0.3f: Character 0x%08x (%s) input\n", - counter++, slot->number, glfwGetTime(), codepoint, + printf("%08x to %i at %0.3f (event time: %0.3f): Character 0x%08x (%s) input\n", + counter++, slot->number, glfwGetTime(), glfwGetEventTime(), codepoint, get_character_string(codepoint)); }