mirror of
https://github.com/ocornut/imgui.git
synced 2026-11-06 06:25:54 +01:00
Fix GLFW X11 viewport synchronization
This commit is contained in:
parent
83f668625a
commit
b3ea4243b1
@ -31,6 +31,8 @@
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2026-08-12: [Docking] Multi-viewport: Synchronize window manager adjustments instead of treating them as setter echoes. On X11, when the native API is available, observe synthetic ConfigureNotify events so fully rejected requests also synchronize without a timeout. (#9356, #9398, #9442)
|
||||
// 2026-08-12: [Docking] Multi-viewport: Disable support on Wayland with legacy GLFW versions as global window positioning is unavailable.
|
||||
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
|
||||
// 2026-04-21: Added a Win32-specific implementation of ImGui_ImplGlfw_GetContentScaleXXXX functions for legacy GLFW 3.2.
|
||||
// 2026-03-25: Mouse cursor is properly restored if changed by user app/code while using glfwSetInputMode(..., GLFW_CURSOR_DISABLED) or ImGuiConfigFlags_NoMouseCursorChange. Amend change from 2025-12-10.
|
||||
@ -235,6 +237,12 @@ typedef Atom (*PFN_XInternAtom)(Display*, const char* ,Bool);
|
||||
typedef int (*PFN_XChangeProperty)(Display*, Window, Atom, Atom, int, int, const unsigned char*, int);
|
||||
typedef int (*PFN_XChangeWindowAttributes)(Display*, Window, unsigned long, XSetWindowAttributes*);
|
||||
typedef int (*PFN_XFlush)(Display*);
|
||||
typedef Display* (*PFN_XOpenDisplay)(const char*);
|
||||
typedef int (*PFN_XCloseDisplay)(Display*);
|
||||
typedef int (*PFN_XSelectInput)(Display*, Window, long);
|
||||
typedef int (*PFN_XPending)(Display*);
|
||||
typedef int (*PFN_XNextEvent)(Display*, XEvent*);
|
||||
typedef int (*PFN_XSync)(Display*, Bool);
|
||||
#endif
|
||||
|
||||
// GLFW data
|
||||
@ -281,6 +289,13 @@ struct ImGui_ImplGlfw_Data
|
||||
PFN_XChangeProperty XChangeProperty;
|
||||
PFN_XChangeWindowAttributes XChangeWindowAttributes;
|
||||
PFN_XFlush XFlush;
|
||||
PFN_XOpenDisplay XOpenDisplay;
|
||||
PFN_XCloseDisplay XCloseDisplay;
|
||||
PFN_XSelectInput XSelectInput;
|
||||
PFN_XPending XPending;
|
||||
PFN_XNextEvent XNextEvent;
|
||||
PFN_XSync XSync;
|
||||
Display* X11EventObserverDisplay;
|
||||
#endif
|
||||
|
||||
ImGui_ImplGlfw_Data() { memset((void*)this, 0, sizeof(*this)); }
|
||||
@ -310,6 +325,9 @@ static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData(GLFWwindow* window)
|
||||
static void ImGui_ImplGlfw_UpdateMonitors();
|
||||
static void ImGui_ImplGlfw_InitMultiViewportSupport();
|
||||
static void ImGui_ImplGlfw_ShutdownMultiViewportSupport();
|
||||
#if GLFW_HAS_X11
|
||||
static void ImGui_ImplGlfw_UpdateX11ConfigureEvents();
|
||||
#endif
|
||||
|
||||
// Functions
|
||||
static bool ImGui_ImplGlfw_IsWayland()
|
||||
@ -739,11 +757,7 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
|
||||
|
||||
bool has_viewports = false;
|
||||
#ifndef __EMSCRIPTEN__
|
||||
has_viewports = true;
|
||||
#if GLFW_HAS_GETPLATFORM
|
||||
if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND)
|
||||
has_viewports = false;
|
||||
#endif
|
||||
has_viewports = !bd->IsWayland;
|
||||
if (has_viewports)
|
||||
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
|
||||
#endif
|
||||
@ -835,11 +849,20 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
|
||||
const char* x11_module_path = "libX11.so.6";
|
||||
#endif
|
||||
bd->X11Module = dlopen(x11_module_path, RTLD_LAZY | RTLD_LOCAL);
|
||||
bd->XInternAtom = (PFN_XInternAtom)dlsym(bd->X11Module, "XInternAtom");
|
||||
bd->XChangeProperty = (PFN_XChangeProperty)dlsym(bd->X11Module, "XChangeProperty");
|
||||
bd->XChangeWindowAttributes = (PFN_XChangeWindowAttributes)dlsym(bd->X11Module, "XChangeWindowAttributes");
|
||||
bd->XFlush = (PFN_XFlush)dlsym(bd->X11Module, "XFlush");
|
||||
IM_ASSERT(bd->XInternAtom != nullptr && bd->XChangeProperty != nullptr && bd->XChangeWindowAttributes != nullptr && bd->XFlush != nullptr);
|
||||
if (bd->X11Module != nullptr)
|
||||
{
|
||||
bd->XInternAtom = (PFN_XInternAtom)dlsym(bd->X11Module, "XInternAtom");
|
||||
bd->XChangeProperty = (PFN_XChangeProperty)dlsym(bd->X11Module, "XChangeProperty");
|
||||
bd->XChangeWindowAttributes = (PFN_XChangeWindowAttributes)dlsym(bd->X11Module, "XChangeWindowAttributes");
|
||||
bd->XFlush = (PFN_XFlush)dlsym(bd->X11Module, "XFlush");
|
||||
bd->XOpenDisplay = (PFN_XOpenDisplay)dlsym(bd->X11Module, "XOpenDisplay");
|
||||
bd->XCloseDisplay = (PFN_XCloseDisplay)dlsym(bd->X11Module, "XCloseDisplay");
|
||||
bd->XSelectInput = (PFN_XSelectInput)dlsym(bd->X11Module, "XSelectInput");
|
||||
bd->XPending = (PFN_XPending)dlsym(bd->X11Module, "XPending");
|
||||
bd->XNextEvent = (PFN_XNextEvent)dlsym(bd->X11Module, "XNextEvent");
|
||||
bd->XSync = (PFN_XSync)dlsym(bd->X11Module, "XSync");
|
||||
IM_ASSERT(bd->XInternAtom != nullptr && bd->XChangeProperty != nullptr && bd->XChangeWindowAttributes != nullptr && bd->XFlush != nullptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -907,6 +930,11 @@ void ImGui_ImplGlfw_Shutdown()
|
||||
#endif
|
||||
|
||||
#if GLFW_HAS_X11
|
||||
if (bd->X11EventObserverDisplay != nullptr)
|
||||
{
|
||||
bd->XCloseDisplay(bd->X11EventObserverDisplay);
|
||||
bd->X11EventObserverDisplay = nullptr;
|
||||
}
|
||||
if (bd->X11Module != nullptr)
|
||||
dlclose(bd->X11Module);
|
||||
#endif
|
||||
@ -1201,6 +1229,9 @@ void ImGui_ImplGlfw_NewFrame()
|
||||
// Setup main viewport size (every frame to accommodate for window resizing)
|
||||
ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale);
|
||||
ImGui_ImplGlfw_UpdateMonitors();
|
||||
#if GLFW_HAS_X11
|
||||
ImGui_ImplGlfw_UpdateX11ConfigureEvents();
|
||||
#endif
|
||||
|
||||
// Setup time step
|
||||
// (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644)
|
||||
@ -1294,6 +1325,11 @@ struct ImGui_ImplGlfw_ViewportData
|
||||
bool WindowOwned;
|
||||
int IgnoreWindowPosEventFrame;
|
||||
int IgnoreWindowSizeEventFrame;
|
||||
int LastWindowPosRequest[2];
|
||||
int LastWindowSizeRequest[2];
|
||||
#if GLFW_HAS_X11
|
||||
::Window X11Window;
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
WNDPROC PrevWndProc;
|
||||
#endif
|
||||
@ -1314,30 +1350,75 @@ static void ImGui_ImplGlfw_WindowCloseCallback(GLFWwindow* window)
|
||||
// - on Linux it is queued and invoked during glfwPollEvents()
|
||||
// Because the event doesn't always fire on glfwSetWindowXXX() we use a frame counter tag to only
|
||||
// ignore recent glfwSetWindowXXX() calls.
|
||||
static void ImGui_ImplGlfw_WindowPosCallback(GLFWwindow* window, int, int)
|
||||
static void ImGui_ImplGlfw_WindowPosCallback(GLFWwindow* window, int x, int y)
|
||||
{
|
||||
if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(window))
|
||||
{
|
||||
if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData)
|
||||
{
|
||||
bool ignore_event = (ImGui::GetFrameCount() <= vd->IgnoreWindowPosEventFrame + 1);
|
||||
//data->IgnoreWindowPosEventFrame = -1;
|
||||
if (ignore_event)
|
||||
// A window manager may adjust the requested position. Only ignore the callback when it reports
|
||||
// the latest position passed to glfwSetWindowPos() and the viewport still expects that position.
|
||||
// Otherwise query and apply the authoritative platform position.
|
||||
const bool is_latest_request = (x == vd->LastWindowPosRequest[0] && y == vd->LastWindowPosRequest[1]);
|
||||
const bool is_current_viewport_pos = (x == (int)viewport->Pos.x && y == (int)viewport->Pos.y);
|
||||
if (ignore_event && is_latest_request && is_current_viewport_pos)
|
||||
return;
|
||||
}
|
||||
viewport->PlatformRequestMove = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int, int)
|
||||
#if GLFW_HAS_X11
|
||||
// X11 window managers report a completely rejected ConfigureRequest with a synthetic ConfigureNotify containing the
|
||||
// unchanged geometry. GLFW consumes that event but doesn't invoke its public callback when the cached geometry didn't
|
||||
// change. Observe synthetic ConfigureNotify events through a separate X connection so those rejections still reach
|
||||
// Dear ImGui without relying on a timing heuristic. The application should call glfwPollEvents() before this NewFrame().
|
||||
static void ImGui_ImplGlfw_UpdateX11ConfigureEvents()
|
||||
{
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (bd->X11EventObserverDisplay == nullptr)
|
||||
return;
|
||||
|
||||
while (bd->XPending(bd->X11EventObserverDisplay) > 0)
|
||||
{
|
||||
XEvent event;
|
||||
bd->XNextEvent(bd->X11EventObserverDisplay, &event);
|
||||
if (event.type != ConfigureNotify || !event.xconfigure.send_event)
|
||||
continue;
|
||||
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
for (int viewport_n = 1; viewport_n < platform_io.Viewports.Size; viewport_n++)
|
||||
{
|
||||
ImGuiViewport* viewport = platform_io.Viewports[viewport_n];
|
||||
ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData;
|
||||
if (vd == nullptr || vd->X11Window != event.xconfigure.window)
|
||||
continue;
|
||||
|
||||
int x, y, width, height;
|
||||
glfwGetWindowPos(vd->Window, &x, &y);
|
||||
glfwGetWindowSize(vd->Window, &width, &height);
|
||||
if (x != (int)viewport->Pos.x || y != (int)viewport->Pos.y)
|
||||
viewport->PlatformRequestMove = true;
|
||||
if (width != (int)viewport->Size.x || height != (int)viewport->Size.y)
|
||||
viewport->PlatformRequestResize = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(window))
|
||||
{
|
||||
if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData)
|
||||
{
|
||||
bool ignore_event = (ImGui::GetFrameCount() <= vd->IgnoreWindowSizeEventFrame + 1);
|
||||
//data->IgnoreWindowSizeEventFrame = -1;
|
||||
if (ignore_event)
|
||||
// Preserve delayed setter-echo suppression only while the viewport still expects that exact size.
|
||||
const bool is_latest_request = (width == vd->LastWindowSizeRequest[0] && height == vd->LastWindowSizeRequest[1]);
|
||||
const bool is_current_viewport_size = (width == (int)viewport->Size.x && height == (int)viewport->Size.y);
|
||||
if (ignore_event && is_latest_request && is_current_viewport_size)
|
||||
return;
|
||||
}
|
||||
viewport->PlatformRequestResize = true;
|
||||
@ -1349,7 +1430,7 @@ static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int, int)
|
||||
static void ImGui_ImplGlfw_SetWindowFloating(ImGui_ImplGlfw_Data* bd, GLFWwindow* window)
|
||||
{
|
||||
#ifdef GLFW_EXPOSE_NATIVE_X11
|
||||
if (glfwGetPlatform() == GLFW_PLATFORM_X11)
|
||||
if (glfwGetPlatform() == GLFW_PLATFORM_X11 && bd->XInternAtom != nullptr && bd->XChangeProperty != nullptr && bd->XChangeWindowAttributes != nullptr && bd->XFlush != nullptr)
|
||||
{
|
||||
Display* display = glfwGetX11Display();
|
||||
Window xwindow = glfwGetX11Window(window);
|
||||
@ -1405,6 +1486,26 @@ static void ImGui_ImplGlfw_CreateWindow(ImGuiViewport* viewport)
|
||||
viewport->PlatformHandleRaw = (void*)glfwGetCocoaWindow(vd->Window);
|
||||
#endif
|
||||
glfwSetWindowPos(vd->Window, (int)viewport->Pos.x, (int)viewport->Pos.y);
|
||||
#if GLFW_HAS_X11
|
||||
#if GLFW_HAS_GETPLATFORM
|
||||
const bool is_x11 = (glfwGetPlatform() == GLFW_PLATFORM_X11);
|
||||
#else
|
||||
const bool is_x11 = !bd->IsWayland;
|
||||
#endif
|
||||
const bool has_x11_observer_api = (bd->XOpenDisplay != nullptr && bd->XCloseDisplay != nullptr && bd->XSelectInput != nullptr && bd->XPending != nullptr && bd->XNextEvent != nullptr && bd->XSync != nullptr);
|
||||
if (is_x11 && has_x11_observer_api && bd->X11EventObserverDisplay == nullptr)
|
||||
bd->X11EventObserverDisplay = bd->XOpenDisplay(DisplayString(glfwGetX11Display()));
|
||||
if (bd->X11EventObserverDisplay != nullptr)
|
||||
{
|
||||
bd->XSync(glfwGetX11Display(), False);
|
||||
vd->X11Window = glfwGetX11Window(vd->Window);
|
||||
if (vd->X11Window != None)
|
||||
{
|
||||
bd->XSelectInput(bd->X11EventObserverDisplay, vd->X11Window, StructureNotifyMask);
|
||||
bd->XSync(bd->X11EventObserverDisplay, False);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Install GLFW callbacks for secondary viewports
|
||||
glfwSetWindowFocusCallback(vd->Window, ImGui_ImplGlfw_WindowFocusCallback);
|
||||
@ -1429,6 +1530,14 @@ static void ImGui_ImplGlfw_DestroyWindow(ImGuiViewport* viewport)
|
||||
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
|
||||
if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData)
|
||||
{
|
||||
#if GLFW_HAS_X11
|
||||
if (bd->X11EventObserverDisplay != nullptr && vd->X11Window != 0)
|
||||
{
|
||||
bd->XSelectInput(bd->X11EventObserverDisplay, vd->X11Window, NoEventMask);
|
||||
bd->XSync(bd->X11EventObserverDisplay, False);
|
||||
ImGui_ImplGlfw_UpdateX11ConfigureEvents();
|
||||
}
|
||||
#endif
|
||||
if (vd->WindowOwned)
|
||||
{
|
||||
#if !GLFW_HAS_MOUSE_PASSTHROUGH && GLFW_HAS_WINDOW_HOVERED && defined(_WIN32)
|
||||
@ -1499,7 +1608,9 @@ static void ImGui_ImplGlfw_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
|
||||
{
|
||||
ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData;
|
||||
vd->IgnoreWindowPosEventFrame = ImGui::GetFrameCount();
|
||||
glfwSetWindowPos(vd->Window, (int)pos.x, (int)pos.y);
|
||||
vd->LastWindowPosRequest[0] = (int)pos.x;
|
||||
vd->LastWindowPosRequest[1] = (int)pos.y;
|
||||
glfwSetWindowPos(vd->Window, vd->LastWindowPosRequest[0], vd->LastWindowPosRequest[1]);
|
||||
}
|
||||
|
||||
static ImVec2 ImGui_ImplGlfw_GetWindowSize(ImGuiViewport* viewport)
|
||||
@ -1521,10 +1632,15 @@ static void ImGui_ImplGlfw_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
|
||||
int x, y, width, height;
|
||||
glfwGetWindowPos(vd->Window, &x, &y);
|
||||
glfwGetWindowSize(vd->Window, &width, &height);
|
||||
glfwSetWindowPos(vd->Window, x, y - height + size.y);
|
||||
vd->IgnoreWindowPosEventFrame = ImGui::GetFrameCount();
|
||||
vd->LastWindowPosRequest[0] = x;
|
||||
vd->LastWindowPosRequest[1] = (int)(y - height + size.y);
|
||||
glfwSetWindowPos(vd->Window, vd->LastWindowPosRequest[0], vd->LastWindowPosRequest[1]);
|
||||
#endif
|
||||
vd->IgnoreWindowSizeEventFrame = ImGui::GetFrameCount();
|
||||
glfwSetWindowSize(vd->Window, (int)size.x, (int)size.y);
|
||||
vd->LastWindowSizeRequest[0] = (int)size.x;
|
||||
vd->LastWindowSizeRequest[1] = (int)size.y;
|
||||
glfwSetWindowSize(vd->Window, vd->LastWindowSizeRequest[0], vd->LastWindowSizeRequest[1]);
|
||||
}
|
||||
|
||||
static ImVec2 ImGui_ImplGlfw_GetWindowFramebufferScale(ImGuiViewport* viewport)
|
||||
|
||||
@ -70,7 +70,15 @@ Other Changes:
|
||||
|
||||
Docking+Viewports Branch:
|
||||
|
||||
- Viewports: fixed platform position/size requests adjusted or rejected by a window manager
|
||||
leaving Dear ImGui geometry out of sync with the native window. Active window dragging now
|
||||
continues from the platform-accepted position instead of repeatedly requesting rejected
|
||||
geometry. (#9356, #9398, #9442)
|
||||
- Backends:
|
||||
- GLFW: on X11, observe synthetic ConfigureNotify events when the native API is available
|
||||
so completely rejected viewport geometry requests are synchronized without a timeout.
|
||||
- GLFW: disabled multi-viewports on Wayland with legacy GLFW versions, where global window
|
||||
positioning is unavailable.
|
||||
- Metal: fixed resizing windows losing framebuffer scale. (#6828, #8856) [@laikoken]
|
||||
- Examples:
|
||||
- SDL3+Metal, GLFW+Metal: fix multi-viewport support.
|
||||
@ -9671,4 +9679,3 @@ Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v
|
||||
Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.00
|
||||
|
||||
- Initial release.
|
||||
|
||||
|
||||
94
docs/GLFW_X11_VIEWPORT_SYNCHRONIZATION.md
Normal file
94
docs/GLFW_X11_VIEWPORT_SYNCHRONIZATION.md
Normal file
@ -0,0 +1,94 @@
|
||||
# GLFW/X11 multi-viewport geometry synchronization
|
||||
|
||||
## Problem
|
||||
|
||||
Dear ImGui's GLFW backend drives secondary viewport geometry through
|
||||
`glfwSetWindowPos()` and `glfwSetWindowSize()`. Those calls are asynchronous on
|
||||
X11: the window manager may accept, adjust, or reject the request after GLFW has
|
||||
returned.
|
||||
|
||||
The portable GLFW API does not expose a request identifier or an acknowledgement
|
||||
containing the geometry accepted by the window manager. Its position and size
|
||||
callbacks are change notifications, not completion notifications.
|
||||
|
||||
This matters for a completely rejected X11 `ConfigureRequest`. ICCCM-compliant
|
||||
window managers send a synthetic `ConfigureNotify` containing the unchanged
|
||||
geometry, but GLFW only invokes its public position and size callbacks when that
|
||||
geometry differs from GLFW's cached state. GLFW consumes the synthetic event, so
|
||||
the application receives no callback. Dear ImGui can consequently retain the
|
||||
requested geometry while the native window remains at its previous geometry.
|
||||
Rendering, hit testing, mouse coordinates, and subsequent drag requests then use
|
||||
inconsistent origins.
|
||||
|
||||
This is best described as a GLFW API limitation rather than an unambiguous GLFW
|
||||
bug: an unchanged position is not necessarily expected to produce a "position
|
||||
changed" callback. However, the current API is insufficient for robust
|
||||
requested-versus-observed window geometry synchronization on asynchronous window
|
||||
systems.
|
||||
|
||||
## Why the obvious alternatives are insufficient
|
||||
|
||||
- **Immediate readback after a setter:** the window manager is a separate X11
|
||||
client. `glfwGetWindowPos()` may still return the old geometry before the
|
||||
window manager processes the request, even when the request will be accepted.
|
||||
- **Readback after a fixed timeout:** reduces the race but does not eliminate it.
|
||||
It also introduces latency and frame-rate-, load-, and window-manager-dependent
|
||||
behavior.
|
||||
- **Removing GLFW's setter-echo suppression:** regresses delayed position/size
|
||||
callback cases previously fixed for #2117 and #2756.
|
||||
- **Observing GLFW's X11 connection:** GLFW owns and drains that event queue; the
|
||||
backend has no public native-event hook through which to observe consumed
|
||||
events.
|
||||
|
||||
## Current compatibility approach
|
||||
|
||||
The backend keeps the existing callback path and classifies a recent callback as
|
||||
a harmless setter echo only when its integer geometry matches both the latest
|
||||
issued setter and the geometry still expected by the viewport. Adjusted or stale
|
||||
geometry requests an authoritative readback during the next Dear ImGui frame.
|
||||
|
||||
For the unchanged-rejection case, the X11 backend optionally opens a second X11
|
||||
connection and selects `StructureNotifyMask` on secondary viewport windows. This
|
||||
connection receives the synthetic `ConfigureNotify` that GLFW otherwise filters.
|
||||
The backend uses it only as a signal to compare native and viewport geometry; the
|
||||
normal GLFW getters remain the authoritative source.
|
||||
|
||||
Using a second X11 connection requires explicit cross-connection ordering during
|
||||
window creation and destruction. It is deliberately optional: if the native X11
|
||||
symbols or observer connection are unavailable, the backend falls back to the
|
||||
ordinary GLFW callback behavior rather than failing initialization.
|
||||
|
||||
This avoids a timing heuristic, but it is compatibility machinery that would be
|
||||
preferable to remove once supported GLFW versions expose an adequate event or
|
||||
acknowledgement facility.
|
||||
|
||||
## Preferred GLFW-side capability
|
||||
|
||||
A long-term GLFW solution could provide any one of:
|
||||
|
||||
1. a configure-request completion notification, including unchanged rejection;
|
||||
2. a native event hook that observes X11 events before GLFW filters them; or
|
||||
3. documented delivery of synthetic `ConfigureNotify` through an additional
|
||||
callback whose semantics do not imply that geometry changed.
|
||||
|
||||
With such a capability, Dear ImGui could retain its portable requested/observed
|
||||
state handling while removing the separate X11 observer for newer GLFW versions.
|
||||
|
||||
## Required regression coverage
|
||||
|
||||
The synchronization contract should cover:
|
||||
|
||||
- synchronous and delayed exact setter echoes;
|
||||
- adjusted/clamped position and size;
|
||||
- complete rejection with unchanged native geometry;
|
||||
- two outstanding requests whose responses arrive in separate frames;
|
||||
- simultaneous position and size changes;
|
||||
- active dragging at screen boundaries without snap-back or setter oscillation;
|
||||
- collapse/uncollapse and delayed-size behavior from #2117/#2756; and
|
||||
- native Xorg with Mutter, since XWayland is useful but not equivalent evidence
|
||||
for this event path.
|
||||
|
||||
The Test Engine mock can deterministically validate Dear ImGui's core ownership
|
||||
and drag-rebasing behavior. It cannot replace backend-level or native-X11 tests,
|
||||
because `-viewport-mock` replaces the GLFW platform handlers and therefore does
|
||||
not execute GLFW's callback filtering or the X11 observer.
|
||||
47
imgui.cpp
47
imgui.cpp
@ -4334,6 +4334,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
|
||||
HoveredWindowUnderMovingWindow = NULL;
|
||||
HoveredWindowBeforeClear = NULL;
|
||||
MovingWindow = NULL;
|
||||
MovingWindowClickOffset = ImVec2(-1, -1);
|
||||
WheelingWindow = NULL;
|
||||
WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1;
|
||||
WheelingWindowReleaseTimer = 0.0f;
|
||||
@ -5465,6 +5466,7 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
|
||||
if (g.IO.ConfigNavCursorVisibleAuto)
|
||||
g.NavCursorVisible = false;
|
||||
g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos;
|
||||
g.MovingWindowClickOffset = g.ActiveIdClickOffset;
|
||||
g.ActiveIdNoClearOnFocusLoss = true;
|
||||
SetActiveIdUsingAllKeyboardKeys();
|
||||
|
||||
@ -5550,7 +5552,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame()
|
||||
const bool window_disappeared = (!moving_window->WasActive && !moving_window->Active);
|
||||
if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappeared)
|
||||
{
|
||||
ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
|
||||
ImVec2 pos = g.IO.MousePos - g.MovingWindowClickOffset;
|
||||
if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
|
||||
{
|
||||
SetWindowPos(moving_window, pos, ImGuiCond_Always);
|
||||
@ -17210,7 +17212,22 @@ static void ImGui::UpdateViewportsNewFrame()
|
||||
{
|
||||
// Viewport->WorkPos and WorkSize will be updated below
|
||||
if (viewport->PlatformRequestMove)
|
||||
viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport);
|
||||
{
|
||||
const ImVec2 requested_pos = viewport->Pos;
|
||||
const ImVec2 platform_pos = g.PlatformIO.Platform_GetWindowPos(viewport);
|
||||
// When a window manager adjusts an actively dragged viewport, rebase the mutable drag offset.
|
||||
// Only rebase adjusted axes: accepted axes should retain any mouse movement received this frame.
|
||||
// Keep ActiveIdClickOffset unchanged: docking uses it as the original click location.
|
||||
ImGuiWindow* moving_root = g.MovingWindow ? g.MovingWindow->RootWindowDockTree : NULL;
|
||||
if (moving_root && viewport->Window == moving_root && moving_root->ViewportOwned && IsMousePosValid(&g.IO.MousePos))
|
||||
{
|
||||
if (platform_pos.x != requested_pos.x)
|
||||
g.MovingWindowClickOffset.x = g.IO.MousePos.x - platform_pos.x;
|
||||
if (platform_pos.y != requested_pos.y)
|
||||
g.MovingWindowClickOffset.y = g.IO.MousePos.y - platform_pos.y;
|
||||
}
|
||||
viewport->Pos = viewport->LastPlatformPos = platform_pos;
|
||||
}
|
||||
if (viewport->PlatformRequestResize)
|
||||
viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport);
|
||||
if (g.PlatformIO.Platform_GetWindowFramebufferScale != NULL)
|
||||
@ -17763,15 +17780,26 @@ void ImGui::UpdatePlatformWindows()
|
||||
viewport->PlatformWindowCreated = true;
|
||||
}
|
||||
|
||||
// Consume requests received before this update. Callbacks raised synchronously by setters below must survive
|
||||
// until the next NewFrame(), so clear the old requests before invoking platform functions instead of afterward.
|
||||
const bool platform_request_move = viewport->PlatformRequestMove;
|
||||
const bool platform_request_resize = viewport->PlatformRequestResize;
|
||||
viewport->PlatformRequestMove = viewport->PlatformRequestResize = false;
|
||||
|
||||
// Apply Position and Size (from ImGui to Platform/Renderer backends)
|
||||
if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !viewport->PlatformRequestMove)
|
||||
if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !platform_request_move)
|
||||
{
|
||||
g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos);
|
||||
if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !viewport->PlatformRequestResize)
|
||||
viewport->LastPlatformPos = viewport->Pos;
|
||||
}
|
||||
if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !platform_request_resize)
|
||||
{
|
||||
g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size);
|
||||
viewport->LastPlatformSize = viewport->Size;
|
||||
}
|
||||
if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) && g.PlatformIO.Renderer_SetWindowSize)
|
||||
g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size);
|
||||
viewport->LastPlatformPos = viewport->Pos;
|
||||
viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size;
|
||||
viewport->LastRendererSize = viewport->Size;
|
||||
|
||||
// Update title bar (if it changed)
|
||||
if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(viewport->Window))
|
||||
@ -17808,8 +17836,10 @@ void ImGui::UpdatePlatformWindows()
|
||||
g.PlatformIO.Platform_ShowWindow(viewport);
|
||||
}
|
||||
|
||||
// Clear request flags
|
||||
viewport->ClearRequestFlags();
|
||||
// Position/size requests raised synchronously above need to survive until the next NewFrame().
|
||||
// Close requests keep their previous lifetime and are consumed once per platform update.
|
||||
viewport->PlatformRequestClose = false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -19259,6 +19289,7 @@ static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWind
|
||||
IM_ASSERT(node->WantMouseMove == true);
|
||||
StartMouseMovingWindow(window);
|
||||
g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos;
|
||||
g.MovingWindowClickOffset = g.ActiveIdClickOffset;
|
||||
g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set g.MovingWindow. Override that decision.
|
||||
node->WantMouseMove = false;
|
||||
}
|
||||
|
||||
@ -2492,6 +2492,7 @@ struct ImGuiContext
|
||||
ImGuiWindow* HoveredWindowUnderMovingWindow; // Hovered window ignoring MovingWindow. Only set if MovingWindow is set.
|
||||
ImGuiWindow* HoveredWindowBeforeClear; // Window the mouse is hovering. Filled even with _NoMouse. This is currently useful for multi-context compositors.
|
||||
ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindowDockTree.
|
||||
ImVec2 MovingWindowClickOffset; // Click offset used to position MovingWindow. May be rebased when a platform window manager adjusts our requested position.
|
||||
ImGuiWindow* WheelingWindow; // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same window even if during the course of scrolling the mouse ends up hovering a child window.
|
||||
ImVec2 WheelingWindowRefMousePos;
|
||||
int WheelingWindowStartFrame; // This may be set one frame before WheelingWindow is != NULL
|
||||
|
||||
@ -10908,6 +10908,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
||||
g.MovingWindow = docked_window;
|
||||
SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
|
||||
g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
|
||||
g.MovingWindowClickOffset = g.ActiveIdClickOffset;
|
||||
g.ActiveIdNoClearOnFocusLoss = true;
|
||||
SetActiveIdUsingAllKeyboardKeys();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user