From c65a3e2d6ea99de98ba0e1e06ceeda0d18e2a02b Mon Sep 17 00:00:00 2001 From: Bjorn Schobben Date: Wed, 19 Aug 2026 01:05:05 +0200 Subject: [PATCH] Viewports: consume exact setter echoes before Begin. Leftover PlatformRequestMove/Resize from a successful Set (Win32 WM_MOVE) stayed authoritative through the following Begin, so SetNextWindowPos/Size, clamp, and nav updates on a floating viewport were discarded. Apply the platform rect in NewFrame and clear the flag when geometry matches the last submitted request. Keep the flag when the OS actually adjusted the rect. Drag rebase compares truncated LastPlatform* so a clamped axis does not hitch the free one. GLFW: drop the second X connection. Poll live geometry after a stable unanswered X11 Set instead. Pre-3.4 IsWayland() no longer assumes Wayland when X11 native headers are missing. --- backends/imgui_impl_glfw.cpp | 182 ++++++++++++++++++----------------- docs/CHANGELOG.txt | 4 +- imgui.cpp | 45 ++++++--- 3 files changed, 128 insertions(+), 103 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 2f360b75f..c3901ab1c 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -31,7 +31,7 @@ // 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: Synchronize window manager adjustments instead of treating them as setter echoes. On X11, poll live geometry after a setter so fully rejected requests still synchronize. (#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. @@ -237,12 +237,6 @@ 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 @@ -289,13 +283,6 @@ 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)); } @@ -326,7 +313,7 @@ static void ImGui_ImplGlfw_UpdateMonitors(); static void ImGui_ImplGlfw_InitMultiViewportSupport(); static void ImGui_ImplGlfw_ShutdownMultiViewportSupport(); #if GLFW_HAS_X11 -static void ImGui_ImplGlfw_UpdateX11ConfigureEvents(); +static void ImGui_ImplGlfw_UpdateWindowGeometryRequests(); #endif // Functions @@ -341,10 +328,10 @@ static bool ImGui_ImplGlfw_IsWayland() if (strstr(version, "Wayland") == nullptr) // e.g. Ubuntu 22.04 ships with GLFW 3.3.6 compiled without Wayland return false; #ifdef GLFW_EXPOSE_NATIVE_X11 - if (glfwGetX11Display() != nullptr) - return false; + return glfwGetX11Display() == nullptr; +#else + return false; // Missing X11 native API: do not assume Wayland (keeps viewports on X11). #endif - return true; #endif } @@ -855,12 +842,6 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw 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); } } @@ -930,11 +911,6 @@ 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 @@ -1230,7 +1206,7 @@ void ImGui_ImplGlfw_NewFrame() ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale); ImGui_ImplGlfw_UpdateMonitors(); #if GLFW_HAS_X11 - ImGui_ImplGlfw_UpdateX11ConfigureEvents(); + ImGui_ImplGlfw_UpdateWindowGeometryRequests(); #endif // Setup time step @@ -1328,7 +1304,14 @@ struct ImGui_ImplGlfw_ViewportData int LastWindowPosRequest[2]; int LastWindowSizeRequest[2]; #if GLFW_HAS_X11 - ::Window X11Window; + int LastObservedWindowPos[2]; + int LastObservedWindowSize[2]; + int WindowPosMismatchFrameCount; + int WindowSizeMismatchFrameCount; + double WindowPosMismatchStartTime; + double WindowSizeMismatchStartTime; + bool WindowPosRequestPending; + bool WindowSizeRequestPending; #endif #ifdef _WIN32 WNDPROC PrevWndProc; @@ -1357,11 +1340,14 @@ static void ImGui_ImplGlfw_WindowPosCallback(GLFWwindow* window, int x, int y) if (ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData) { bool ignore_event = (ImGui::GetFrameCount() <= vd->IgnoreWindowPosEventFrame + 1); - // 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. + // Ignore only the latest matching setter echo. Adjusted or stale geometry requests a platform readback. 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 GLFW_HAS_X11 + vd->WindowPosRequestPending = false; + vd->WindowPosMismatchFrameCount = 0; + vd->WindowPosMismatchStartTime = 0.0; +#endif if (ignore_event && is_latest_request && is_current_viewport_pos) return; } @@ -1370,39 +1356,56 @@ static void ImGui_ImplGlfw_WindowPosCallback(GLFWwindow* window, int x, int y) } #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() +static bool ImGui_ImplGlfw_UpdateWindowGeometryRequest(int value_0, int value_1, const int request[2], int observed[2], int* mismatch_frame_count, double* mismatch_start_time, bool* request_pending, double current_time) { - ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); - if (bd->X11EventObserverDisplay == nullptr) - return; - - while (bd->XPending(bd->X11EventObserverDisplay) > 0) + if (value_0 == request[0] && value_1 == request[1]) { - XEvent event; - bd->XNextEvent(bd->X11EventObserverDisplay, &event); - if (event.type != ConfigureNotify || !event.xconfigure.send_event) + *request_pending = false; + *mismatch_frame_count = 0; + *mismatch_start_time = 0.0; + return false; + } + if (*mismatch_frame_count > 0 && value_0 == observed[0] && value_1 == observed[1]) + (*mismatch_frame_count)++; + else + { + *mismatch_frame_count = 1; + *mismatch_start_time = current_time; + } + observed[0] = value_0; + observed[1] = value_1; + if (*mismatch_frame_count < 2 || current_time - *mismatch_start_time < 0.050) + return false; + *request_pending = false; + *mismatch_frame_count = 0; + *mismatch_start_time = 0.0; + return true; +} + +// GLFW swallows unchanged synthetic ConfigureNotify. Poll live geometry until it matches the last Set or stays rejected. +static void ImGui_ImplGlfw_UpdateWindowGeometryRequests() +{ + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + const double current_time = glfwGetTime(); + 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) continue; - - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - for (int viewport_n = 1; viewport_n < platform_io.Viewports.Size; viewport_n++) + if (vd->WindowPosRequestPending) { - 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; + int x, y; glfwGetWindowPos(vd->Window, &x, &y); - glfwGetWindowSize(vd->Window, &width, &height); - if (x != (int)viewport->Pos.x || y != (int)viewport->Pos.y) + if (ImGui_ImplGlfw_UpdateWindowGeometryRequest(x, y, vd->LastWindowPosRequest, vd->LastObservedWindowPos, &vd->WindowPosMismatchFrameCount, &vd->WindowPosMismatchStartTime, &vd->WindowPosRequestPending, current_time)) viewport->PlatformRequestMove = true; - if (width != (int)viewport->Size.x || height != (int)viewport->Size.y) + } + if (vd->WindowSizeRequestPending) + { + int width, height; + glfwGetWindowSize(vd->Window, &width, &height); + if (ImGui_ImplGlfw_UpdateWindowGeometryRequest(width, height, vd->LastWindowSizeRequest, vd->LastObservedWindowSize, &vd->WindowSizeMismatchFrameCount, &vd->WindowSizeMismatchStartTime, &vd->WindowSizeRequestPending, current_time)) viewport->PlatformRequestResize = true; - break; } } } @@ -1418,6 +1421,11 @@ static void ImGui_ImplGlfw_WindowSizeCallback(GLFWwindow* window, int width, int // 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 GLFW_HAS_X11 + vd->WindowSizeRequestPending = false; + vd->WindowSizeMismatchFrameCount = 0; + vd->WindowSizeMismatchStartTime = 0.0; +#endif if (ignore_event && is_latest_request && is_current_viewport_size) return; } @@ -1486,26 +1494,6 @@ 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); @@ -1530,14 +1518,6 @@ 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) @@ -1608,8 +1588,21 @@ static void ImGui_ImplGlfw_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) { ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; vd->IgnoreWindowPosEventFrame = ImGui::GetFrameCount(); +#if GLFW_HAS_X11 + const int request_x = (int)pos.x; + const int request_y = (int)pos.y; + if (request_x != vd->LastWindowPosRequest[0] || request_y != vd->LastWindowPosRequest[1]) + { + vd->WindowPosMismatchFrameCount = 0; + vd->WindowPosMismatchStartTime = 0.0; + } + vd->LastWindowPosRequest[0] = request_x; + vd->LastWindowPosRequest[1] = request_y; + vd->WindowPosRequestPending = true; +#else vd->LastWindowPosRequest[0] = (int)pos.x; vd->LastWindowPosRequest[1] = (int)pos.y; +#endif glfwSetWindowPos(vd->Window, vd->LastWindowPosRequest[0], vd->LastWindowPosRequest[1]); } @@ -1638,8 +1631,21 @@ static void ImGui_ImplGlfw_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) glfwSetWindowPos(vd->Window, vd->LastWindowPosRequest[0], vd->LastWindowPosRequest[1]); #endif vd->IgnoreWindowSizeEventFrame = ImGui::GetFrameCount(); +#if GLFW_HAS_X11 + const int request_w = (int)size.x; + const int request_h = (int)size.y; + if (request_w != vd->LastWindowSizeRequest[0] || request_h != vd->LastWindowSizeRequest[1]) + { + vd->WindowSizeMismatchFrameCount = 0; + vd->WindowSizeMismatchStartTime = 0.0; + } + vd->LastWindowSizeRequest[0] = request_w; + vd->LastWindowSizeRequest[1] = request_h; + vd->WindowSizeRequestPending = true; +#else vd->LastWindowSizeRequest[0] = (int)size.x; vd->LastWindowSizeRequest[1] = (int)size.y; +#endif glfwSetWindowSize(vd->Window, vd->LastWindowSizeRequest[0], vd->LastWindowSizeRequest[1]); } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 35027cb00..543465d7a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -75,8 +75,8 @@ Docking+Viewports Branch: 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: on X11, poll live geometry after a setter so completely rejected viewport + requests are synchronized. (#9356, #9398, #9442) - 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] diff --git a/imgui.cpp b/imgui.cpp index 4399b77f4..44cd7bf29 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -17213,23 +17213,42 @@ static void ImGui::UpdateViewportsNewFrame() // Viewport->WorkPos and WorkSize will be updated below if (viewport->PlatformRequestMove) { - const ImVec2 requested_pos = viewport->Pos; + const ImVec2 requested_pos = viewport->LastPlatformPos; 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. + // Rebase only axes adjusted by the window manager. Platform coordinates are integer pixels. + const bool platform_pos_adjusted = (ImTrunc(platform_pos.x) != ImTrunc(requested_pos.x) || ImTrunc(platform_pos.y) != ImTrunc(requested_pos.y)); ImGuiWindow* moving_root = g.MovingWindow ? g.MovingWindow->RootWindowDockTree : NULL; - if (moving_root && viewport->Window == moving_root && moving_root->ViewportOwned && IsMousePosValid(&g.IO.MousePos)) + const bool moving_owned_viewport = (moving_root && viewport->Window == moving_root && moving_root->ViewportOwned && IsMousePosValid(&g.IO.MousePos)); + if (moving_owned_viewport) { - if (platform_pos.x != requested_pos.x) + if (ImTrunc(platform_pos.x) != ImTrunc(requested_pos.x)) g.MovingWindowClickOffset.x = g.IO.MousePos.x - platform_pos.x; - if (platform_pos.y != requested_pos.y) + if (ImTrunc(platform_pos.y) != ImTrunc(requested_pos.y)) g.MovingWindowClickOffset.y = g.IO.MousePos.y - platform_pos.y; } viewport->Pos = viewport->LastPlatformPos = platform_pos; + if (platform_pos_adjusted && viewport->Window != NULL) + { + viewport->Window->Pos = platform_pos; + MarkIniSettingsDirty(viewport->Window); + } + if (!platform_pos_adjusted || moving_owned_viewport) + viewport->PlatformRequestMove = false; } if (viewport->PlatformRequestResize) - viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport); + { + const ImVec2 requested_size = viewport->LastPlatformSize; + const ImVec2 platform_size = g.PlatformIO.Platform_GetWindowSize(viewport); + const bool platform_size_adjusted = (ImTrunc(platform_size.x) != ImTrunc(requested_size.x) || ImTrunc(platform_size.y) != ImTrunc(requested_size.y)); + viewport->Size = viewport->LastPlatformSize = platform_size; + if (platform_size_adjusted && viewport->Window != NULL) + { + viewport->Window->Size = viewport->Window->SizeFull = platform_size; + MarkIniSettingsDirty(viewport->Window); + } + if (!platform_size_adjusted) + viewport->PlatformRequestResize = false; + } if (g.PlatformIO.Platform_GetWindowFramebufferScale != NULL) viewport->FramebufferScale = g.PlatformIO.Platform_GetWindowFramebufferScale(viewport); } @@ -17780,18 +17799,18 @@ void ImGui::UpdatePlatformWindows() viewport->PlatformWindowCreated = true; } - // Consume requests received before this update. NewFrame already applied them to Pos/Size/LastPlatform*. - // A leftover flag must not suppress a later imgui-initiated Set (e.g. continued drag after a setter echo). - // Clear before invoking platform functions so synchronous setter callbacks survive until the next NewFrame(). + // Consume requests applied by NewFrame(), before setters can raise synchronous callbacks. + 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) + if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !platform_request_move) { g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos); viewport->LastPlatformPos = viewport->Pos; } - if (viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) + 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;