diff --git a/.gitignore b/.gitignore index 84636a0a..1cc9b59c 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,6 @@ tests/triangle-vulkan tests/window tests/windows +/.idea +/cmake-build-debug +/.gitignore diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index eb98dafd..c3d01e09 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1974,6 +1974,23 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor* monitor, int event); */ typedef void (* GLFWjoystickfun)(int jid, int event); +/*! @brief The function pointer type for empty event callbacks. + * + * This is the function pointer type for empty event callbacks. + * An empty event callback function has the following signature: + * @code + * void function_name() + * @endcode + * + * @sa @ref empty_event + * @sa @ref glfwSetEmptyEventCallback + * + * @since Added in version 3.2. + * + * @ingroup input + */ +typedef void (* GLFWemptyeventfun)(void); + /*! @brief Video mode type. * * This describes a single video mode. @@ -5697,7 +5714,36 @@ GLFWAPI int glfwJoystickIsGamepad(int jid); * * @ingroup input */ -GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun callback); +GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun callback);/*! @brief Sets the joystick configuration callback. + * + * + * This function sets the empty event callback, or removes the currently + * set callback. This is called on the GLFW thread at some point in the future + * when glfwPostEmptyEvent is called from any thread. + * + * @param[in] callback 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). + * + * @callback_signature + * @code + * void function_name() + * @endcode + * For more information about the callback parameters, see the + * [function pointer type](@ref GLFWemptyeventfun). + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function may be called from any thread + * + * @sa @ref empty_event + * + * @since Added in version TODO + * + * @ingroup input + */ +GLFWAPI GLFWemptyeventfun glfwSetEmptyEventCallback(GLFWemptyeventfun callback); /*! @brief Adds the specified SDL_GameControllerDB gamepad mappings. * diff --git a/src/input.c b/src/input.c index 8b7ef29c..2f5b3255 100644 --- a/src/input.c +++ b/src/input.c @@ -1253,6 +1253,13 @@ GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun) return cbfun; } +GLFWAPI GLFWemptyeventfun glfwSetEmptyEventCallback(GLFWemptyeventfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP(GLFWemptyeventfun, _glfw.callbacks.emptyEvent, cbfun); + return cbfun; +} + GLFWAPI int glfwUpdateGamepadMappings(const char* string) { int jid; diff --git a/src/internal.h b/src/internal.h index fe0369aa..32192b6c 100644 --- a/src/internal.h +++ b/src/internal.h @@ -866,6 +866,7 @@ struct _GLFWlibrary struct { GLFWmonitorfun monitor; GLFWjoystickfun joystick; + GLFWemptyeventfun emptyEvent; } callbacks; // These are defined in platform.h diff --git a/src/win32_init.c b/src/win32_init.c index ef2615f1..5f7c3bdf 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -363,6 +363,19 @@ static LRESULT CALLBACK helperWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LP break; } + + default: + { + // this check may not be necessary, but it checks just in case the message + // has not been registered. this skips having to check uMsg != WM_NULL + UINT emptyEventMsg = _glfw.win32.emptyEventMessage; + if (emptyEventMsg != 0 && uMsg == emptyEventMsg) + { + if (_glfw.callbacks.emptyEvent) + _glfw.callbacks.emptyEvent(); + } + break; + } } return DefWindowProcW(hWnd, uMsg, wParam, lParam); @@ -405,6 +418,14 @@ static GLFWbool createHelperWindow(void) return GLFW_FALSE; } + _glfw.win32.emptyEventMessage = RegisterWindowMessageW(L"GLFW.EmptyEventMessage"); + if (!_glfw.win32.emptyEventMessage) + { + _glfwInputErrorWin32(GLFW_PLATFORM_ERROR, + "Win32: Failed to register empty event message"); + return GLFW_FALSE; + } + // HACK: The command to the first ShowWindow call is ignored if the parent // process passed along a STARTUPINFO, so clear that with a no-op call ShowWindow(_glfw.win32.helperWindowHandle, SW_HIDE); diff --git a/src/win32_platform.h b/src/win32_platform.h index 82b34bb9..32fff7bf 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -457,6 +457,7 @@ typedef struct _GLFWlibraryWin32 RAWINPUT* rawInput; int rawInputSize; UINT mouseTrailSize; + UINT emptyEventMessage; struct { HINSTANCE instance; diff --git a/src/win32_window.c b/src/win32_window.c index a4a18171..84753ec7 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -2128,7 +2128,7 @@ void _glfwWaitEventsTimeoutWin32(double timeout) void _glfwPostEmptyEventWin32(void) { - PostMessageW(_glfw.win32.helperWindowHandle, WM_NULL, 0, 0); + PostMessageW(_glfw.win32.helperWindowHandle, _glfw.win32.emptyEventMessage, 0, 0); } void _glfwGetCursorPosWin32(_GLFWwindow* window, double* xpos, double* ypos) diff --git a/tests/window.c b/tests/window.c index 83baff46..7525b55f 100644 --- a/tests/window.c +++ b/tests/window.c @@ -23,6 +23,16 @@ // //======================================================================== +// TODO: Implement the postEmptyEvent test on multiple platforms. Works for Windows so far +#ifdef WIN32 +#define USE_WIN32_THREAD_EMPTY_EVENT_TEST +#endif // WIN32 + +#ifdef USE_WIN32_THREAD_EMPTY_EVENT_TEST +#include +#endif // USE_WIN32_THREAD_EMPTY_EVENT_TEST + + #define GLAD_GL_IMPLEMENTATION #include #define GLFW_INCLUDE_NONE @@ -48,6 +58,35 @@ #include #include +#ifdef USE_WIN32_THREAD_EMPTY_EVENT_TEST + +DWORD WINAPI win32ThreadEmptyEventTest(void) +{ + // Sleep for 3 seconds, and then post an empty event. The onEmptyEventPosted + // method should then be fired shortly after, on the GLFW main thread. + + Sleep(3000); + glfwPostEmptyEvent(); + return 0; +} + +void onEmptyEventPosted(void) +{ + // If you hold your LMB down on the window border (aka initialise the resize + // phase), you will see a big difference in stack trace here compared to + // when not resizing. This is because DefWindowProc seems to use its own + // message loop temporarily, and passes any unhandled messages back to the + // dummy window's wndproc, which detects the empty event message, firing the + // event callback. + // This is the only effective way to run code on the main thread during the + // window resize phase while the user isn't moving their mouse, apart from the + // actual resize events obviously + MessageBoxExW(NULL, L"Callback event received!", L"Empty Event Callback", NULL, NULL); + // printf("Empty event received! Put a break point here, and this will be fired on the main thread"); +} + +#endif // USE_WIN32_THREAD_EMPTY_EVENT_TEST + int main(int argc, char** argv) { int windowed_x, windowed_y, windowed_width, windowed_height; @@ -66,12 +105,14 @@ int main(int argc, char** argv) if (!glfwInit()) exit(EXIT_FAILURE); + glfwSetEmptyEventCallback(onEmptyEventPosted); + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); glfwWindowHint(GLFW_WIN32_KEYBOARD_MENU, GLFW_TRUE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - GLFWwindow* window = glfwCreateWindow(600, 600, "Window Features", NULL, NULL); + GLFWwindow* window = glfwCreateWindow(750, 600, "Window Features", NULL, NULL); if (!window) { glfwTerminate(); @@ -121,7 +162,7 @@ int main(int argc, char** argv) nk_glfw3_new_frame(); if (nk_begin(nk, "main", area, 0)) { - nk_layout_row_dynamic(nk, 30, 5); + nk_layout_row_dynamic(nk, 30, 6); if (nk_button_label(nk, "Toggle Fullscreen")) { @@ -156,10 +197,16 @@ int main(int argc, char** argv) const double time = glfwGetTime() + 3.0; while (glfwGetTime() < time) glfwWaitEventsTimeout(1.0); - glfwShowWindow(window); } +#ifdef USE_WIN32_THREAD_EMPTY_EVENT_TEST + if (nk_button_label(nk, "Empty Event(3s)")) + { + CreateThread(NULL, 0, win32ThreadEmptyEventTest, NULL, 0, NULL); + } +#endif // USE_WIN32_THREAD_EMPTY_EVENT_TEST + nk_layout_row_dynamic(nk, 30, 1); if (glfwGetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH))