diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 031962ecd..7c7850393 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -302,6 +302,9 @@ video tutorials. - Jonas Ådahl - Lasse Öörni - Leonard König + - Felipe da Silva + - Naveen Karuthedath + - Seneral - All the unmentioned and anonymous contributors in the GLFW community, for bug reports, patches, feedback, testing and encouragement diff --git a/README.md b/README.md index b27ea51dd..793f68376 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ information on what to include when reporting a bug. ## Changelog since 3.4 + - Added `glfwDragWindow` function for starting a drag operation on a window - Added `GLFW_UNLIMITED_MOUSE_BUTTONS` input mode that allows mouse buttons beyond the limit of the mouse button tokens to be reported (#2423) - Added `glfwGetEGLConfig` function to query the `EGLConfig` of a window (#2045) diff --git a/examples/windows.c b/examples/windows.c index 1589ffbfb..a81196ced 100644 --- a/examples/windows.c +++ b/examples/windows.c @@ -31,6 +31,12 @@ #include #include +void mouse_button_callback(GLFWwindow* window, int button, int action, int mods){ + if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS){ + glfwDragWindow(window); + } +} + int main(int argc, char** argv) { int xpos, ypos, height; @@ -78,6 +84,7 @@ int main(int argc, char** argv) } glfwSetInputMode(windows[i], GLFW_STICKY_KEYS, GLFW_TRUE); + glfwSetMouseButtonCallback(windows[i], mouse_button_callback); glfwMakeContextCurrent(windows[i]); gladLoadGL(glfwGetProcAddress); diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 3ce1c0191..a9c26fdd7 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -3969,6 +3969,23 @@ GLFWAPI void glfwHideWindow(GLFWwindow* window); */ GLFWAPI void glfwFocusWindow(GLFWwindow* window); +/*! @brief Starts drag operation to the specified window. + * + * This function starts the drag operation of the specified window. + * + * @param[in] window The window to start the dragging operation. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_PLATFORM_ERROR. + * + * @thread_safety This function must only be called from the main thread. + * + * @since Added in version 3.4. + * + * @ingroup window + */ +GLFWAPI void glfwDragWindow(GLFWwindow* handle); + /*! @brief Requests user attention to the specified window. * * This function requests user attention to the specified window. On diff --git a/src/cocoa_init.m b/src/cocoa_init.m index 15dc4ec4c..310b7d66a 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -542,6 +542,7 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) .hideWindow = _glfwHideWindowCocoa, .requestWindowAttention = _glfwRequestWindowAttentionCocoa, .focusWindow = _glfwFocusWindowCocoa, + .dragWindow = _glfwDragWindowCocoa, .setWindowMonitor = _glfwSetWindowMonitorCocoa, .windowFocused = _glfwWindowFocusedCocoa, .windowIconified = _glfwWindowIconifiedCocoa, diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 4d1d66ae0..cdb7f8605 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -234,6 +234,7 @@ void _glfwShowWindowCocoa(_GLFWwindow* window); void _glfwHideWindowCocoa(_GLFWwindow* window); void _glfwRequestWindowAttentionCocoa(_GLFWwindow* window); void _glfwFocusWindowCocoa(_GLFWwindow* window); +void _glfwDragWindowCocoa(_GLFWwindow* window); void _glfwSetWindowMonitorCocoa(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); GLFWbool _glfwWindowFocusedCocoa(_GLFWwindow* window); GLFWbool _glfwWindowIconifiedCocoa(_GLFWwindow* window); diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 2bff22a6b..ec3ffc3dc 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1240,6 +1240,11 @@ void _glfwFocusWindowCocoa(_GLFWwindow* window) } // autoreleasepool } +void _glfwDragWindowCocoa(_GLFWwindow* window) +{ + // TODO +} + void _glfwSetWindowMonitorCocoa(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, diff --git a/src/internal.h b/src/internal.h index de703740f..2f4adaad7 100644 --- a/src/internal.h +++ b/src/internal.h @@ -733,6 +733,7 @@ struct _GLFWplatform void (*hideWindow)(_GLFWwindow*); void (*requestWindowAttention)(_GLFWwindow*); void (*focusWindow)(_GLFWwindow*); + void (*dragWindow)(_GLFWwindow*); void (*setWindowMonitor)(_GLFWwindow*,_GLFWmonitor*,int,int,int,int,int); GLFWbool (*windowFocused)(_GLFWwindow*); GLFWbool (*windowIconified)(_GLFWwindow*); diff --git a/src/null_init.c b/src/null_init.c index 8c10f5e67..18912cb5f 100644 --- a/src/null_init.c +++ b/src/null_init.c @@ -88,6 +88,7 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) .hideWindow = _glfwHideWindowNull, .requestWindowAttention = _glfwRequestWindowAttentionNull, .focusWindow = _glfwFocusWindowNull, + .dragWindow = _glfwDragWindowNull, .setWindowMonitor = _glfwSetWindowMonitorNull, .windowFocused = _glfwWindowFocusedNull, .windowIconified = _glfwWindowIconifiedNull, diff --git a/src/null_platform.h b/src/null_platform.h index dbcb835b8..7ba3a466e 100644 --- a/src/null_platform.h +++ b/src/null_platform.h @@ -251,6 +251,7 @@ void _glfwShowWindowNull(_GLFWwindow* window); void _glfwRequestWindowAttentionNull(_GLFWwindow* window); void _glfwHideWindowNull(_GLFWwindow* window); void _glfwFocusWindowNull(_GLFWwindow* window); +void _glfwDragWindowNull(_GLFWwindow* window); GLFWbool _glfwWindowFocusedNull(_GLFWwindow* window); GLFWbool _glfwWindowIconifiedNull(_GLFWwindow* window); GLFWbool _glfwWindowVisibleNull(_GLFWwindow* window); diff --git a/src/null_window.c b/src/null_window.c index f0e1dcc9a..e0a37bf4c 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -470,6 +470,11 @@ void _glfwFocusWindowNull(_GLFWwindow* window) _glfwInputWindowFocus(window, GLFW_TRUE); } +void _glfwDragWindowNull(_GLFWwindow* window) +{ + // TODO +} + GLFWbool _glfwWindowFocusedNull(_GLFWwindow* window) { return _glfw.null.focusedWindow == window; diff --git a/src/win32_init.c b/src/win32_init.c index 90e47670e..4f78e0c7e 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -648,6 +648,7 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) .hideWindow = _glfwHideWindowWin32, .requestWindowAttention = _glfwRequestWindowAttentionWin32, .focusWindow = _glfwFocusWindowWin32, + .dragWindow = _glfwDragWindowWin32, .setWindowMonitor = _glfwSetWindowMonitorWin32, .windowFocused = _glfwWindowFocusedWin32, .windowIconified = _glfwWindowIconifiedWin32, diff --git a/src/win32_platform.h b/src/win32_platform.h index 49cceba6d..b0be9767e 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -502,6 +502,7 @@ void _glfwShowWindowWin32(_GLFWwindow* window); void _glfwHideWindowWin32(_GLFWwindow* window); void _glfwRequestWindowAttentionWin32(_GLFWwindow* window); void _glfwFocusWindowWin32(_GLFWwindow* window); +void _glfwDragWindowWin32(_GLFWwindow* window); void _glfwSetWindowMonitorWin32(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); GLFWbool _glfwWindowFocusedWin32(_GLFWwindow* window); GLFWbool _glfwWindowIconifiedWin32(_GLFWwindow* window); diff --git a/src/win32_window.c b/src/win32_window.c index 6427a673e..2b412fda3 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1828,6 +1828,13 @@ void _glfwFocusWindowWin32(_GLFWwindow* window) SetFocus(window->win32.handle); } +void _glfwDragWindowWin32(_GLFWwindow* window) +{ + ReleaseCapture(); + SendMessage(window->win32.handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, getKeyMods()); +} + void _glfwSetWindowMonitorWin32(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, diff --git a/src/window.c b/src/window.c index 3a3b66dfe..e686429fe 100644 --- a/src/window.c +++ b/src/window.c @@ -876,6 +876,16 @@ GLFWAPI void glfwFocusWindow(GLFWwindow* handle) _glfw.platform.focusWindow(window); } +GLFWAPI void glfwDragWindow(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT(); + + _glfw.platform.dragWindow(window); +} + GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib) { _GLFW_REQUIRE_INIT_OR_RETURN(0); diff --git a/src/wl_init.c b/src/wl_init.c index d32c4adf4..f7c13199a 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -493,6 +493,7 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) .hideWindow = _glfwHideWindowWayland, .requestWindowAttention = _glfwRequestWindowAttentionWayland, .focusWindow = _glfwFocusWindowWayland, + .dragWindow = _glfwDragWindowWayland, .setWindowMonitor = _glfwSetWindowMonitorWayland, .windowFocused = _glfwWindowFocusedWayland, .windowIconified = _glfwWindowIconifiedWayland, diff --git a/src/wl_platform.h b/src/wl_platform.h index bdadb657e..97a63a510 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -640,6 +640,7 @@ void _glfwShowWindowWayland(_GLFWwindow* window); void _glfwHideWindowWayland(_GLFWwindow* window); void _glfwRequestWindowAttentionWayland(_GLFWwindow* window); void _glfwFocusWindowWayland(_GLFWwindow* window); +void _glfwDragWindowWayland(_GLFWwindow* window); void _glfwSetWindowMonitorWayland(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); GLFWbool _glfwWindowFocusedWayland(_GLFWwindow* window); GLFWbool _glfwWindowIconifiedWayland(_GLFWwindow* window); diff --git a/src/wl_window.c b/src/wl_window.c index ad39b2e0e..daa05a062 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -1578,6 +1578,13 @@ static void pointerHandleLeave(void* userData, { window->wl.hovered = GLFW_FALSE; _glfwInputCursorEnter(window, GLFW_FALSE); + // Would like to silently set mouse button state to undefined without triggering a mouse button release event + // See https://gitlab.freedesktop.org/wayland/wayland/-/issues/280 + // But many underlying systems will keep their own state and only use GLFW events to keep it up to date + // So realistically, they NEED a mouse button event, even if wayland spec would like it to be implicitly undefined + for (int btn = 0; btn < GLFW_MOUSE_BUTTON_LAST+1; btn++) + if (window->mouseButtons[btn] != GLFW_RELEASE) + _glfwInputMouseClick(window, btn, GLFW_RELEASE, 0); } else { @@ -1801,6 +1808,14 @@ static void keyboardHandleLeave(void* userData, _glfw.wl.serial = serial; _glfw.wl.keyboardFocus = NULL; _glfwInputWindowFocus(window, GLFW_FALSE); + + // Would like to silently set key state to undefined without triggering a key release event + // See https://gitlab.freedesktop.org/wayland/wayland/-/issues/280 + // But many underlying systems will keep their own state and only use GLFW events to keep it up to date + // So realistically, they NEED a key event, even if wayland spec would like it to be implicitly undefined + for (int key = 0; key < GLFW_KEY_LAST+1; key++) + if (window->keys[key] != GLFW_RELEASE) + _glfwInputKey(window, key, _glfwGetKeyScancodeWayland(key), GLFW_RELEASE, 0); } static void keyboardHandleKey(void* userData, @@ -2551,6 +2566,37 @@ void _glfwFocusWindowWayland(_GLFWwindow* window) xdg_activation_token_v1_commit(window->wl.activationToken); } +void _glfwDragWindowWayland(_GLFWwindow* window) +{ + struct xdg_toplevel* toplevel = + window->wl.libdecor.frame ? + libdecor_frame_get_xdg_toplevel(window->wl.libdecor.frame) : + window->wl.xdg.toplevel; + + if (!toplevel) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Cannot drag window: xdg_toplevel not created yet"); + return; + } + + if (!_glfw.wl.seat) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Cannot drag window: no valid seat"); + return; + } + + if (_glfw.wl.serial == 0) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Cannot drag window: no valid serial"); + return; + } + + xdg_toplevel_move(toplevel, _glfw.wl.seat, _glfw.wl.serial); +} + void _glfwSetWindowMonitorWayland(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, diff --git a/src/x11_init.c b/src/x11_init.c index 6b34c2639..88dd23432 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -1224,6 +1224,7 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) .hideWindow = _glfwHideWindowX11, .requestWindowAttention = _glfwRequestWindowAttentionX11, .focusWindow = _glfwFocusWindowX11, + .dragWindow = _glfwDragWindowX11, .setWindowMonitor = _glfwSetWindowMonitorX11, .windowFocused = _glfwWindowFocusedX11, .windowIconified = _glfwWindowIconifiedX11, diff --git a/src/x11_platform.h b/src/x11_platform.h index 1bfeaab49..680967c76 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -922,6 +922,7 @@ void _glfwShowWindowX11(_GLFWwindow* window); void _glfwHideWindowX11(_GLFWwindow* window); void _glfwRequestWindowAttentionX11(_GLFWwindow* window); void _glfwFocusWindowX11(_GLFWwindow* window); +void _glfwDragWindowX11(_GLFWwindow* window); void _glfwSetWindowMonitorX11(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); GLFWbool _glfwWindowFocusedX11(_GLFWwindow* window); GLFWbool _glfwWindowIconifiedX11(_GLFWwindow* window); diff --git a/src/x11_window.c b/src/x11_window.c index 045878aa5..77d987c2f 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -45,6 +45,7 @@ #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 +#define _NET_WM_MOVERESIZE_MOVE 8 // Additional mouse button names for XButtonEvent #define Button6 6 @@ -2474,6 +2475,28 @@ void _glfwFocusWindowX11(_GLFWwindow* window) XFlush(_glfw.x11.display); } +void _glfwDragWindowX11(_GLFWwindow* window) +{ + int winXpos, winYpos; + double curXpos, curYpos; + XClientMessageEvent xclient; + memset(&xclient, 0, sizeof(XClientMessageEvent)); + XUngrabPointer(_glfw.x11.display, 0); + XFlush(_glfw.x11.display); + _glfwGetCursorPosX11(window, &curXpos, &curYpos); + _glfwGetWindowPosX11(window, &winXpos, &winYpos); + xclient.type = ClientMessage; + xclient.window = window->x11.handle; + xclient.message_type = XInternAtom(_glfw.x11.display, "_NET_WM_MOVERESIZE", False); + xclient.format = 32; + xclient.data.l[0] = winXpos + curXpos; + xclient.data.l[1] = winYpos + curYpos; + xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; + xclient.data.l[3] = 0; + xclient.data.l[4] = 0; + XSendEvent(_glfw.x11.display, _glfw.x11.root, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xclient); +} + void _glfwSetWindowMonitorX11(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos,