diff --git a/.gitignore b/.gitignore index 9d2d504b..297ed156 100644 --- a/.gitignore +++ b/.gitignore @@ -101,4 +101,3 @@ tests/title tests/triangle-vulkan tests/window tests/windows - diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 5e6fad42..3f37e7e5 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 b5bb69d6..7b493353 100644 --- a/src/input.c +++ b/src/input.c @@ -1251,6 +1251,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 4cb01adb..2050da60 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -361,6 +361,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); @@ -403,6 +416,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 f7feb32d..7ba9b06c 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -2126,7 +2126,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..3b191e91 100644 --- a/tests/window.c +++ b/tests/window.c @@ -23,6 +23,15 @@ // //======================================================================== +// 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 +57,35 @@ #include #include +#ifdef USE_WIN32_THREAD_EMPTY_EVENT_TEST + +DWORD WINAPI win32ThreadEmptyEventTest(LPVOID lpThreadParameter) +{ + // 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 + MessageBoxW((HWND)NULL, L"Callback event received!", L"Empty Event Callback", (UINT)0); + // 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 +104,16 @@ int main(int argc, char** argv) if (!glfwInit()) exit(EXIT_FAILURE); +#ifdef USE_WIN32_THREAD_EMPTY_EVENT_TEST + glfwSetEmptyEventCallback(onEmptyEventPosted); +#endif // USE_WIN32_THREAD_EMPTY_EVENT_TEST + 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(); @@ -115,21 +157,21 @@ int main(int argc, char** argv) glfwGetWindowSize(window, &width, &height); - struct nk_rect area = nk_rect(0.f, 0.f, (float) width, (float) height); + struct nk_rect area = nk_rect(0.f, 0.f, (float)width, (float)height); nk_window_set_bounds(nk, "main", area); 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")) { if (glfwGetWindowMonitor(window)) { glfwSetWindowMonitor(window, NULL, - windowed_x, windowed_y, - windowed_width, windowed_height, 0); + windowed_x, windowed_y, + windowed_width, windowed_height, 0); } else { @@ -138,8 +180,8 @@ int main(int argc, char** argv) glfwGetWindowPos(window, &windowed_x, &windowed_y); glfwGetWindowSize(window, &windowed_width, &windowed_height); glfwSetWindowMonitor(window, monitor, - 0, 0, mode->width, mode->height, - mode->refreshRate); + 0, 0, mode->width, mode->height, + mode->refreshRate); } } @@ -156,10 +198,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)) @@ -174,8 +222,8 @@ int main(int argc, char** argv) nk_flags events; const nk_flags flags = NK_EDIT_FIELD | - NK_EDIT_SIG_ENTER | - NK_EDIT_GOTO_END_ON_ACTIVATE; + NK_EDIT_SIG_ENTER | + NK_EDIT_GOTO_END_ON_ACTIVATE; if (position_supported) { @@ -186,8 +234,8 @@ int main(int argc, char** argv) nk_label(nk, "Position", NK_TEXT_LEFT); events = nk_edit_string_zero_terminated(nk, flags, xpos_buffer, - sizeof(xpos_buffer), - nk_filter_decimal); + sizeof(xpos_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { xpos = atoi(xpos_buffer); @@ -197,8 +245,8 @@ int main(int argc, char** argv) sprintf(xpos_buffer, "%i", xpos); events = nk_edit_string_zero_terminated(nk, flags, ypos_buffer, - sizeof(ypos_buffer), - nk_filter_decimal); + sizeof(ypos_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { ypos = atoi(ypos_buffer); @@ -217,8 +265,8 @@ int main(int argc, char** argv) nk_label(nk, "Size", NK_TEXT_LEFT); events = nk_edit_string_zero_terminated(nk, flags, width_buffer, - sizeof(width_buffer), - nk_filter_decimal); + sizeof(width_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { width = atoi(width_buffer); @@ -228,8 +276,8 @@ int main(int argc, char** argv) sprintf(width_buffer, "%i", width); events = nk_edit_string_zero_terminated(nk, flags, height_buffer, - sizeof(height_buffer), - nk_filter_decimal); + sizeof(height_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { height = atoi(height_buffer); @@ -246,8 +294,8 @@ int main(int argc, char** argv) update_ratio_limit = true; events = nk_edit_string_zero_terminated(nk, flags, numer_buffer, - sizeof(numer_buffer), - nk_filter_decimal); + sizeof(numer_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { aspect_numer = abs(atoi(numer_buffer)); @@ -257,8 +305,8 @@ int main(int argc, char** argv) sprintf(numer_buffer, "%i", aspect_numer); events = nk_edit_string_zero_terminated(nk, flags, denom_buffer, - sizeof(denom_buffer), - nk_filter_decimal); + sizeof(denom_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { aspect_denom = abs(atoi(denom_buffer)); @@ -281,8 +329,8 @@ int main(int argc, char** argv) update_size_limit = true; events = nk_edit_string_zero_terminated(nk, flags, min_width_buffer, - sizeof(min_width_buffer), - nk_filter_decimal); + sizeof(min_width_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { min_width = abs(atoi(min_width_buffer)); @@ -292,8 +340,8 @@ int main(int argc, char** argv) sprintf(min_width_buffer, "%i", min_width); events = nk_edit_string_zero_terminated(nk, flags, min_height_buffer, - sizeof(min_height_buffer), - nk_filter_decimal); + sizeof(min_height_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { min_height = abs(atoi(min_height_buffer)); @@ -306,8 +354,8 @@ int main(int argc, char** argv) update_size_limit = true; events = nk_edit_string_zero_terminated(nk, flags, max_width_buffer, - sizeof(max_width_buffer), - nk_filter_decimal); + sizeof(max_width_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { max_width = abs(atoi(max_width_buffer)); @@ -317,8 +365,8 @@ int main(int argc, char** argv) sprintf(max_width_buffer, "%i", max_width); events = nk_edit_string_zero_terminated(nk, flags, max_height_buffer, - sizeof(max_height_buffer), - nk_filter_decimal); + sizeof(max_height_buffer), + nk_filter_decimal); if (events & NK_EDIT_COMMITED) { max_height = abs(atoi(max_height_buffer)); @@ -330,10 +378,10 @@ int main(int argc, char** argv) if (update_size_limit) { glfwSetWindowSizeLimits(window, - limit_min_size ? min_width : GLFW_DONT_CARE, - limit_min_size ? min_height : GLFW_DONT_CARE, - limit_max_size ? max_width : GLFW_DONT_CARE, - limit_max_size ? max_height : GLFW_DONT_CARE); + limit_min_size ? min_width : GLFW_DONT_CARE, + limit_min_size ? min_height : GLFW_DONT_CARE, + limit_max_size ? max_width : GLFW_DONT_CARE, + limit_max_size ? max_height : GLFW_DONT_CARE); } int fb_width, fb_height;