mirror of
https://github.com/glfw/glfw
synced 2026-09-27 00:28:42 +03:00
Added glfwDragWindow function to all platforms to begin a system window drag operation
This commit is contained in:
parent
d0be6518f5
commit
198614840f
18 changed files with 266 additions and 31 deletions
|
|
@ -3998,6 +3998,33 @@ GLFWAPI void glfwHideWindow(GLFWwindow* window);
|
|||
*/
|
||||
GLFWAPI void glfwFocusWindow(GLFWwindow* window);
|
||||
|
||||
/*! @brief Hand off an in-progress pointer drag to the window manager so it
|
||||
* moves this window until the user releases the pointer button.
|
||||
*
|
||||
* This is the equivalent of the standard custom-titlebar drag pattern, but
|
||||
* initiated explicitly rather than from a titlebar hit test. It's primarily
|
||||
* useful when a window is created mid-drag (e.g. tearing out a docked tab
|
||||
* into a new viewport) and should seamlessly follow the cursor as if the
|
||||
* initial button press had been on this new window.
|
||||
*
|
||||
* Requires a pointer button to currently be held. On Wayland this issues
|
||||
* xdg_toplevel.move (or libdecor_frame_move) using the latest user-event
|
||||
* serial. On Win32 it issues ReleaseCapture + WM_SYSCOMMAND SC_MOVE. On
|
||||
* X11 it sends a _NET_WM_MOVERESIZE ClientMessage. All of these use the
|
||||
* cursor's current location to anchor the drag, so no position needs to
|
||||
* be passed in.
|
||||
*
|
||||
* @param[in] window The window to move.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
GLFWAPI void glfwDragWindow(GLFWwindow* window);
|
||||
|
||||
/*! @brief Requests user attention to the specified window.
|
||||
*
|
||||
* This function requests user attention to the specified window. On
|
||||
|
|
|
|||
|
|
@ -507,6 +507,7 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform)
|
|||
.hideWindow = _glfwHideWindowCocoa,
|
||||
.requestWindowAttention = _glfwRequestWindowAttentionCocoa,
|
||||
.focusWindow = _glfwFocusWindowCocoa,
|
||||
.dragWindow = _glfwDragWindowCocoa,
|
||||
.setWindowMonitor = _glfwSetWindowMonitorCocoa,
|
||||
.windowFocused = _glfwWindowFocusedCocoa,
|
||||
.windowIconified = _glfwWindowIconifiedCocoa,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1240,6 +1240,22 @@ void _glfwFocusWindowCocoa(_GLFWwindow* window)
|
|||
} // autoreleasepool
|
||||
}
|
||||
|
||||
void _glfwDragWindowCocoa(_GLFWwindow* window)
|
||||
{
|
||||
@autoreleasepool {
|
||||
// -[NSWindow performWindowDragWithEvent:] requires a currently-dispatching
|
||||
// mouse-down NSEvent. We don't have easy access to the original event here,
|
||||
// so fall back to the closest available current event. If there isn't a
|
||||
// live drag event, this is a no-op — matching Wayland's "no serial" gate.
|
||||
NSEvent* event = [NSApp currentEvent];
|
||||
if (event && ([event type] == NSEventTypeLeftMouseDown ||
|
||||
[event type] == NSEventTypeLeftMouseDragged))
|
||||
{
|
||||
[window->ns.object performWindowDragWithEvent:event];
|
||||
}
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
void _glfwSetWindowMonitorCocoa(_GLFWwindow* window,
|
||||
_GLFWmonitor* monitor,
|
||||
int xpos, int ypos,
|
||||
|
|
|
|||
|
|
@ -737,6 +737,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*);
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform)
|
|||
.hideWindow = _glfwHideWindowNull,
|
||||
.requestWindowAttention = _glfwRequestWindowAttentionNull,
|
||||
.focusWindow = _glfwFocusWindowNull,
|
||||
.dragWindow = _glfwDragWindowNull,
|
||||
.setWindowMonitor = _glfwSetWindowMonitorNull,
|
||||
.windowFocused = _glfwWindowFocusedNull,
|
||||
.windowIconified = _glfwWindowIconifiedNull,
|
||||
|
|
|
|||
|
|
@ -252,6 +252,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);
|
||||
|
|
|
|||
|
|
@ -474,6 +474,11 @@ void _glfwFocusWindowNull(_GLFWwindow* window)
|
|||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||
}
|
||||
|
||||
void _glfwDragWindowNull(_GLFWwindow* window)
|
||||
{
|
||||
// No windowing system to hand the drag off to.
|
||||
}
|
||||
|
||||
GLFWbool _glfwWindowFocusedNull(_GLFWwindow* window)
|
||||
{
|
||||
return _glfw.null.focusedWindow == window;
|
||||
|
|
|
|||
|
|
@ -625,6 +625,7 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform)
|
|||
.hideWindow = _glfwHideWindowWin32,
|
||||
.requestWindowAttention = _glfwRequestWindowAttentionWin32,
|
||||
.focusWindow = _glfwFocusWindowWin32,
|
||||
.dragWindow = _glfwDragWindowWin32,
|
||||
.setWindowMonitor = _glfwSetWindowMonitorWin32,
|
||||
.windowFocused = _glfwWindowFocusedWin32,
|
||||
.windowIconified = _glfwWindowIconifiedWin32,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1972,6 +1972,16 @@ void _glfwFocusWindowWin32(_GLFWwindow* window)
|
|||
SetFocus(window->win32.handle);
|
||||
}
|
||||
|
||||
void _glfwDragWindowWin32(_GLFWwindow* window)
|
||||
{
|
||||
// Hand the in-progress pointer drag off to the window manager. Windows
|
||||
// uses the cursor's current location to anchor the move, same as
|
||||
// xdg_toplevel.move on Wayland. Standard pattern for custom titlebar
|
||||
// drag (Chrome, Electron, anything with a non-native caption).
|
||||
ReleaseCapture();
|
||||
SendMessageW(window->win32.handle, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);
|
||||
}
|
||||
|
||||
void _glfwSetWindowMonitorWin32(_GLFWwindow* window,
|
||||
_GLFWmonitor* monitor,
|
||||
int xpos, int ypos,
|
||||
|
|
|
|||
11
src/window.c
11
src/window.c
|
|
@ -888,6 +888,17 @@ GLFWAPI void glfwFocusWindow(GLFWwindow* handle)
|
|||
_glfw.platform.focusWindow(window);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwDragWindow(GLFWwindow* handle)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
if (_glfw.platform.dragWindow)
|
||||
_glfw.platform.dragWindow(window);
|
||||
}
|
||||
|
||||
GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
|
|
|
|||
|
|
@ -503,6 +503,7 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform)
|
|||
.hideWindow = _glfwHideWindowWayland,
|
||||
.requestWindowAttention = _glfwRequestWindowAttentionWayland,
|
||||
.focusWindow = _glfwFocusWindowWayland,
|
||||
.dragWindow = _glfwDragWindowWayland,
|
||||
.setWindowMonitor = _glfwSetWindowMonitorWayland,
|
||||
.windowFocused = _glfwWindowFocusedWayland,
|
||||
.windowIconified = _glfwWindowIconifiedWayland,
|
||||
|
|
|
|||
|
|
@ -409,12 +409,17 @@ typedef struct _GLFWwindowWayland
|
|||
int pendingPosX, pendingPosY;
|
||||
GLFWbool pendingPosSet;
|
||||
|
||||
// Captured from wndconfig.focused at create time. ImGui's GLFW backend
|
||||
// unconditionally calls glfwWindowHint(GLFW_FOCUSED, false) for viewports;
|
||||
// app-created toplevels (splash, main editor) leave the default `true`.
|
||||
// That gives us a reliable "this is an ImGui viewport" signal that doesn't
|
||||
// depend on version-gated hints like GLFW_FOCUS_ON_SHOW.
|
||||
GLFWbool createdUnfocused;
|
||||
// Set on the first wl_surface.frame fire, which only happens after the
|
||||
// compositor has processed a real buffer commit (Vulkan's first present).
|
||||
// xdg_toplevel.move against an unmapped surface has been observed to
|
||||
// crash KWin, so we defer the request until this is true.
|
||||
GLFWbool mapped;
|
||||
struct wl_callback* mappedCallback;
|
||||
|
||||
// If glfwDragWindow was called before the surface was mapped, the press
|
||||
// serial is stashed here and the move is issued from the map callback.
|
||||
GLFWbool dragPending;
|
||||
uint32_t dragPendingSerial;
|
||||
|
||||
struct {
|
||||
struct libdecor_frame* frame;
|
||||
|
|
@ -497,6 +502,16 @@ typedef struct _GLFWlibraryWayland
|
|||
int cursorTimerfd;
|
||||
uint32_t serial;
|
||||
uint32_t pointerEnterSerial;
|
||||
// Most-recently-received wl_pointer.button press serial. xdg_toplevel.move
|
||||
// and xdg_popup.grab both require a serial tied to a user press event;
|
||||
// _glfw.wl.serial is the latest serial of *any* kind (motion, enter,
|
||||
// keyboard), which is invalid for those requests and can crash strict
|
||||
// compositors (KWin has been observed to crash).
|
||||
uint32_t pointerButtonSerial;
|
||||
// Count of currently-held pointer buttons. Deferred drag-moves only fire
|
||||
// while this is non-zero; otherwise the press serial is stale and passing
|
||||
// it to xdg_toplevel.move is undefined per spec.
|
||||
int pointerButtonsDown;
|
||||
|
||||
int keyRepeatTimerfd;
|
||||
int32_t keyRepeatRate;
|
||||
|
|
@ -696,6 +711,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);
|
||||
|
|
|
|||
156
src/wl_window.c
156
src/wl_window.c
|
|
@ -1391,6 +1391,14 @@ static GLFWbool createXdgPopupShellObjects(_GLFWwindow* window,
|
|||
}
|
||||
xdg_popup_add_listener(window->wl.xdg.popup, &xdgPopupListener, window);
|
||||
|
||||
// Request a grab tied to the most recent pointer-button press. This is
|
||||
// what makes the compositor auto-dismiss the popup (sending popup_done)
|
||||
// on any outside click, app switch, etc. — matching native menu behavior.
|
||||
// Must use a press serial; any other kind is undefined per the spec and
|
||||
// strict compositors may reject or crash.
|
||||
if (_glfw.wl.seat && _glfw.wl.pointerButtonSerial)
|
||||
xdg_popup_grab(window->wl.xdg.popup, _glfw.wl.seat, _glfw.wl.pointerButtonSerial);
|
||||
|
||||
wl_surface_commit(window->wl.surface);
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
return GLFW_TRUE;
|
||||
|
|
@ -1468,28 +1476,84 @@ static GLFWbool createXdgShellObjects(_GLFWwindow* window)
|
|||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static void issuePendingDragMove(_GLFWwindow* window)
|
||||
{
|
||||
if (!window->wl.dragPending || !_glfw.wl.seat)
|
||||
return;
|
||||
window->wl.dragPending = GLFW_FALSE;
|
||||
|
||||
// If the user released the button while we were waiting for map, the
|
||||
// stashed serial is stale — passing it to move is undefined per spec.
|
||||
if (_glfw.wl.pointerButtonsDown <= 0)
|
||||
return;
|
||||
|
||||
struct xdg_toplevel* toplevel = window->wl.xdg.toplevel;
|
||||
if (!toplevel && window->wl.libdecor.frame)
|
||||
toplevel = libdecor_frame_get_xdg_toplevel(window->wl.libdecor.frame);
|
||||
if (!toplevel)
|
||||
return;
|
||||
|
||||
xdg_toplevel_move(toplevel, _glfw.wl.seat, window->wl.dragPendingSerial);
|
||||
}
|
||||
|
||||
static void mappedFrameHandleDone(void* userData,
|
||||
struct wl_callback* callback,
|
||||
uint32_t data)
|
||||
{
|
||||
_GLFWwindow* window = userData;
|
||||
wl_callback_destroy(callback);
|
||||
window->wl.mappedCallback = NULL;
|
||||
window->wl.mapped = GLFW_TRUE;
|
||||
|
||||
// A pending drag request queued before mapping fires now; the compositor
|
||||
// has processed at least one buffer so xdg_toplevel.move is safe.
|
||||
issuePendingDragMove(window);
|
||||
}
|
||||
|
||||
static const struct wl_callback_listener mappedFrameListener =
|
||||
{
|
||||
mappedFrameHandleDone
|
||||
};
|
||||
|
||||
static void armMappedFrameCallback(_GLFWwindow* window)
|
||||
{
|
||||
if (window->wl.mapped || window->wl.mappedCallback)
|
||||
return;
|
||||
window->wl.mappedCallback = wl_surface_frame(window->wl.surface);
|
||||
if (window->wl.mappedCallback)
|
||||
{
|
||||
wl_callback_add_listener(window->wl.mappedCallback,
|
||||
&mappedFrameListener, window);
|
||||
}
|
||||
}
|
||||
|
||||
static GLFWbool createShellObjects(_GLFWwindow* window)
|
||||
{
|
||||
// Treat ImGui-style transient viewports as xdg_popups so they appear
|
||||
// relative to the main window on Wayland (toplevel positioning is
|
||||
// protocol-forbidden). The signal is `createdUnfocused` — ImGui's GLFW
|
||||
// backend unconditionally sets GLFW_FOCUSED=false for viewport windows,
|
||||
// whereas app-created toplevels (splash, main editor) leave the default
|
||||
// `true`, so they stay as real toplevels.
|
||||
if (window->wl.createdUnfocused && !window->decorated && !window->monitor)
|
||||
// Opt into xdg_popup for windows the caller marked as menu-style via
|
||||
// GLFW_FOCUS_ON_SHOW=false. Undecorated alone isn't enough — detached
|
||||
// dockable viewports are also undecorated but should remain real
|
||||
// toplevels so they can be moved by the compositor (xdg_toplevel.move)
|
||||
// and participate in normal window stacking.
|
||||
if (!window->focusOnShow && !window->decorated && !window->monitor)
|
||||
{
|
||||
_GLFWwindow* parent = findWaylandPopupParent(window);
|
||||
if (parent)
|
||||
return createXdgPopupShellObjects(window, parent);
|
||||
}
|
||||
|
||||
if (_glfw.wl.libdecor.context)
|
||||
{
|
||||
if (createLibdecorFrame(window))
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
GLFWbool ok;
|
||||
if (_glfw.wl.libdecor.context && createLibdecorFrame(window))
|
||||
ok = GLFW_TRUE;
|
||||
else
|
||||
ok = createXdgShellObjects(window);
|
||||
|
||||
return createXdgShellObjects(window);
|
||||
// Register a one-shot frame callback so we learn when the compositor has
|
||||
// first processed a buffer — our "truly mapped" signal. Any deferred
|
||||
// drag-move waits for this before firing.
|
||||
if (ok)
|
||||
armMappedFrameCallback(window);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void destroyShellObjects(_GLFWwindow* window)
|
||||
|
|
@ -1511,12 +1575,18 @@ static void destroyShellObjects(_GLFWwindow* window)
|
|||
if (window->wl.xdg.surface)
|
||||
xdg_surface_destroy(window->wl.xdg.surface);
|
||||
|
||||
if (window->wl.mappedCallback)
|
||||
wl_callback_destroy(window->wl.mappedCallback);
|
||||
|
||||
window->wl.libdecor.frame = NULL;
|
||||
window->wl.xdg.decoration = NULL;
|
||||
window->wl.xdg.decorationMode = 0;
|
||||
window->wl.xdg.popup = NULL;
|
||||
window->wl.xdg.toplevel = NULL;
|
||||
window->wl.xdg.surface = NULL;
|
||||
window->wl.mappedCallback = NULL;
|
||||
window->wl.mapped = GLFW_FALSE;
|
||||
window->wl.dragPending = GLFW_FALSE;
|
||||
}
|
||||
|
||||
static GLFWbool createNativeSurface(_GLFWwindow* window,
|
||||
|
|
@ -2046,6 +2116,15 @@ static void pointerHandleButton(void* userData,
|
|||
return;
|
||||
|
||||
_glfw.wl.serial = serial;
|
||||
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
|
||||
{
|
||||
_glfw.wl.pointerButtonSerial = serial;
|
||||
_glfw.wl.pointerButtonsDown++;
|
||||
}
|
||||
else if (_glfw.wl.pointerButtonsDown > 0)
|
||||
{
|
||||
_glfw.wl.pointerButtonsDown--;
|
||||
}
|
||||
|
||||
const int button = buttonID - BTN_LEFT;
|
||||
const int action = (state == WL_POINTER_BUTTON_STATE_PRESSED);
|
||||
|
|
@ -2747,8 +2826,6 @@ GLFWbool _glfwCreateWindowWayland(_GLFWwindow* window,
|
|||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
window->wl.createdUnfocused = !wndconfig->focused;
|
||||
|
||||
if (!createNativeSurface(window, wndconfig, fbconfig))
|
||||
return GLFW_FALSE;
|
||||
|
||||
|
|
@ -2910,16 +2987,18 @@ void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
|
|||
|
||||
void _glfwSetWindowPosWayland(_GLFWwindow* window, int xpos, int ypos)
|
||||
{
|
||||
if (!window->wl.xdg.surface && !window->wl.libdecor.frame)
|
||||
{
|
||||
window->wl.pendingPosX = xpos;
|
||||
window->wl.pendingPosY = ypos;
|
||||
window->wl.pendingPosSet = GLFW_TRUE;
|
||||
return;
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: The platform does not support setting the window position after it has been mapped");
|
||||
// Wayland has no global coordinate space, so we can't physically move a
|
||||
// mapped toplevel. But we still mirror whatever the caller writes:
|
||||
// ImGui's multi-viewport input-forwarding adds glfwGetWindowPos to each
|
||||
// per-surface pointer coord to produce absolute coordinates, and its
|
||||
// hit-testing assumes viewport->Pos matches that same value. The two
|
||||
// drift together harmlessly as long as we always mirror the write;
|
||||
// making one stable while the other isn't breaks widget clicks.
|
||||
// Before mapping, this stashed value also feeds the xdg_popup
|
||||
// positioner as the anchor-rect origin.
|
||||
window->wl.pendingPosX = xpos;
|
||||
window->wl.pendingPosY = ypos;
|
||||
window->wl.pendingPosSet = GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwGetWindowSizeWayland(_GLFWwindow* window, int* width, int* height)
|
||||
|
|
@ -3168,6 +3247,33 @@ void _glfwFocusWindowWayland(_GLFWwindow* window)
|
|||
xdg_activation_token_v1_commit(window->wl.activationToken);
|
||||
}
|
||||
|
||||
void _glfwDragWindowWayland(_GLFWwindow* window)
|
||||
{
|
||||
if (!_glfw.wl.seat || !_glfw.wl.pointerButtonSerial)
|
||||
return;
|
||||
|
||||
// Fire immediately when the toplevel is already mapped; otherwise stash
|
||||
// the press serial and let the mapped-frame callback issue the move once
|
||||
// the compositor has processed the first buffer. The pre-show case
|
||||
// matters for tab-tear (caller invokes this before glfwShowWindow maps
|
||||
// anything), and the pre-map case matters because calling
|
||||
// xdg_toplevel.move on an unmapped surface crashes KWin.
|
||||
if (!window->wl.mapped)
|
||||
{
|
||||
window->wl.dragPendingSerial = _glfw.wl.pointerButtonSerial;
|
||||
window->wl.dragPending = GLFW_TRUE;
|
||||
return;
|
||||
}
|
||||
|
||||
struct xdg_toplevel* toplevel = window->wl.xdg.toplevel;
|
||||
if (!toplevel && window->wl.libdecor.frame)
|
||||
toplevel = libdecor_frame_get_xdg_toplevel(window->wl.libdecor.frame);
|
||||
if (!toplevel)
|
||||
return;
|
||||
|
||||
xdg_toplevel_move(toplevel, _glfw.wl.seat, _glfw.wl.pointerButtonSerial);
|
||||
}
|
||||
|
||||
void _glfwSetWindowMonitorWayland(_GLFWwindow* window,
|
||||
_GLFWmonitor* monitor,
|
||||
int xpos, int ypos,
|
||||
|
|
|
|||
|
|
@ -1226,6 +1226,7 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform)
|
|||
.hideWindow = _glfwHideWindowX11,
|
||||
.requestWindowAttention = _glfwRequestWindowAttentionX11,
|
||||
.focusWindow = _glfwFocusWindowX11,
|
||||
.dragWindow = _glfwDragWindowX11,
|
||||
.setWindowMonitor = _glfwSetWindowMonitorX11,
|
||||
.windowFocused = _glfwWindowFocusedX11,
|
||||
.windowIconified = _glfwWindowIconifiedX11,
|
||||
|
|
|
|||
|
|
@ -921,6 +921,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);
|
||||
|
|
|
|||
|
|
@ -2613,6 +2613,40 @@ void _glfwFocusWindowX11(_GLFWwindow* window)
|
|||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwDragWindowX11(_GLFWwindow* window)
|
||||
{
|
||||
// Equivalent of xdg_toplevel.move / WM_SYSCOMMAND SC_MOVE for X11: send
|
||||
// _NET_WM_MOVERESIZE with direction=MOVE, and the WM takes over pointer
|
||||
// tracking until the user releases the button. Matches the behaviour
|
||||
// used by the titlebar-drag path in the button-press handler.
|
||||
if (!_glfw.x11.NET_WM_MOVERESIZE)
|
||||
return;
|
||||
|
||||
Window root, child;
|
||||
int rootX, rootY, winX, winY;
|
||||
unsigned int mask;
|
||||
if (!XQueryPointer(_glfw.x11.display, window->x11.handle,
|
||||
&root, &child, &rootX, &rootY, &winX, &winY, &mask))
|
||||
return;
|
||||
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
|
||||
XEvent xev = { 0 };
|
||||
xev.type = ClientMessage;
|
||||
xev.xclient.window = window->x11.handle;
|
||||
xev.xclient.message_type = _glfw.x11.NET_WM_MOVERESIZE;
|
||||
xev.xclient.format = 32;
|
||||
xev.xclient.data.l[0] = rootX;
|
||||
xev.xclient.data.l[1] = rootY;
|
||||
xev.xclient.data.l[2] = 8; // _NET_WM_MOVERESIZE_MOVE
|
||||
xev.xclient.data.l[3] = Button1;
|
||||
xev.xclient.data.l[4] = 1; // source: normal app
|
||||
|
||||
XSendEvent(_glfw.x11.display, _glfw.x11.root, False,
|
||||
SubstructureRedirectMask | SubstructureNotifyMask, &xev);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwSetWindowMonitorX11(_GLFWwindow* window,
|
||||
_GLFWmonitor* monitor,
|
||||
int xpos, int ypos,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue