diff --git a/modules/juce_gui_basics/native/juce_Windowing_linux.cpp b/modules/juce_gui_basics/native/juce_Windowing_linux.cpp index 4db6a46607f..e56fbab1ec2 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_linux.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_linux.cpp @@ -410,13 +410,17 @@ class LinuxComponentPeer final : public ComponentPeer, return; } - if (isConstrainedNativeWindow()) - XWindowSystem::getInstance()->updateConstraints (windowH); - physicalBounds = XWindowSystem::getInstance()->getWindowBounds (windowH, parentWindow); fullScreen = XWindowSystem::getInstance()->isFullScreen (windowH); updateScaleFactorFromNewBounds (physicalBounds, true); + // After the bounds and scale factor have been refreshed, not before: + // the hints restate the window's geometry as well as its size limits + // (they share one property -- see XWindowSystem::updateSizeHints), and + // both are derived from the values just read back. + if (isConstrainedNativeWindow()) + XWindowSystem::getInstance()->updateSizeHints (windowH, *this, physicalBounds); + updateVBlankTimer(); } diff --git a/modules/juce_gui_basics/native/juce_XWindowSystem_linux.cpp b/modules/juce_gui_basics/native/juce_XWindowSystem_linux.cpp index f1bcf88d890..167ce63292a 100644 --- a/modules/juce_gui_basics/native/juce_XWindowSystem_linux.cpp +++ b/modules/juce_gui_basics/native/juce_XWindowSystem_linux.cpp @@ -1902,20 +1902,19 @@ std::optional XWindowSystem::setBounds (::Window windowH, Rectang } } - updateConstraints (windowH, *peer); + // The user position/size and the min/max constraints have to go out in + // a single write: XSetWMNormalHints replaces the whole WM_NORMAL_HINTS + // property, so writing them separately makes whichever call comes + // second erase the other's fields. Dropping PMinSize/PMaxSize leaves + // the window manager free to resize the window below its minimum size; + // dropping USPosition/USSize leaves it free to ignore the requested + // position entirely and place the window wherever its own policy says + // (screen centre under mutter, a corner under openbox), which is how a + // dialog centred on its owner ends up somewhere else. + updateSizeHints (windowH, *peer, newBounds); XWindowSystemUtilities::ScopedXLock xLock; - if (auto hints = makeXFreePtr (X11Symbols::getInstance()->xAllocSizeHints())) - { - hints->flags = USSize | USPosition; - hints->x = newBounds.getX(); - hints->y = newBounds.getY(); - hints->width = newBounds.getWidth(); - hints->height = newBounds.getHeight(); - X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints.get()); - } - const auto nativeWindowBorder = std::invoke ([&]() -> BorderSize { if (const auto& frameSize = peer->getFrameSizeIfPresent()) @@ -1998,23 +1997,27 @@ void XWindowSystem::startHostManagedResize (::Window windowH, unalignedPointerCast (&clientMsg)); } -void XWindowSystem::updateConstraints (::Window windowH) const -{ - if (auto* peer = getPeerFor (windowH)) - updateConstraints (windowH, *peer); -} - -void XWindowSystem::updateConstraints (::Window windowH, ComponentPeer& peer) const +// Writes the whole of WM_NORMAL_HINTS: the window's requested position and +// size, and the size limits its style flags or constrainer impose. These have +// to be written together because XSetWMNormalHints replaces the entire +// property -- see the call in setBounds() for what each half is holding up. +void XWindowSystem::updateSizeHints (::Window windowH, ComponentPeer& peer, Rectangle physicalBounds) const { XWindowSystemUtilities::ScopedXLock xLock; if (auto hints = makeXFreePtr (X11Symbols::getInstance()->xAllocSizeHints())) { + hints->flags = USSize | USPosition; + hints->x = physicalBounds.getX(); + hints->y = physicalBounds.getY(); + hints->width = physicalBounds.getWidth(); + hints->height = physicalBounds.getHeight(); + if ((peer.getStyleFlags() & ComponentPeer::windowIsResizable) == 0) { hints->min_width = hints->max_width = (int) (peer.getPlatformScaleFactor() * peer.getBounds().getWidth()); hints->min_height = hints->max_height = (int) (peer.getPlatformScaleFactor() * peer.getBounds().getHeight()); - hints->flags = PMinSize | PMaxSize; + hints->flags |= PMinSize | PMaxSize; } else if (auto* c = peer.getConstrainer()) { @@ -2029,11 +2032,23 @@ void XWindowSystem::updateConstraints (::Window windowH, ComponentPeer& peer) co const auto factor = peer.getPlatformScaleFactor(); const auto leftAndRight = windowBorder.getLeftAndRight(); const auto topAndBottom = windowBorder.getTopAndBottom(); - hints->min_width = jmax (1, (int) (factor * c->getMinimumWidth()) - leftAndRight); - hints->max_width = jmax (1, (int) (factor * c->getMaximumWidth()) - leftAndRight); - hints->min_height = jmax (1, (int) (factor * c->getMinimumHeight()) - topAndBottom); - hints->max_height = jmax (1, (int) (factor * c->getMaximumHeight()) - topAndBottom); - hints->flags = PMinSize | PMaxSize; + + // Constrainers commonly use the maximum int value to mean + // "unbounded", so the scaled limit must be clamped before the + // conversion back to int: for scale factors greater than 1.0 the + // conversion is otherwise undefined behaviour, and the garbage + // maximum sizes it produced made some window managers pin the + // window to its minimum size on scaled displays. + const auto scaledLimit = [factor] (int limit, int border) + { + const auto scaled = jmin ((double) std::numeric_limits::max(), factor * limit); + return jmax (1, (int) scaled - border); + }; + hints->min_width = scaledLimit (c->getMinimumWidth(), leftAndRight); + hints->max_width = scaledLimit (c->getMaximumWidth(), leftAndRight); + hints->min_height = scaledLimit (c->getMinimumHeight(), topAndBottom); + hints->max_height = scaledLimit (c->getMaximumHeight(), topAndBottom); + hints->flags |= PMinSize | PMaxSize; } X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints.get()); diff --git a/modules/juce_gui_basics/native/juce_XWindowSystem_linux.h b/modules/juce_gui_basics/native/juce_XWindowSystem_linux.h index e419e88a31b..f0a64cb59bc 100644 --- a/modules/juce_gui_basics/native/juce_XWindowSystem_linux.h +++ b/modules/juce_gui_basics/native/juce_XWindowSystem_linux.h @@ -189,7 +189,7 @@ class XWindowSystem : public DeletedAtShutdown void setIcon (::Window , const Image&) const; void setVisible (::Window, bool shouldBeVisible) const; [[nodiscard]] std::optional setBounds (::Window, Rectangle, bool fullScreen) const; - void updateConstraints (::Window) const; + void updateSizeHints (::Window, ComponentPeer&, Rectangle physicalBounds) const; ComponentPeer::OptionalBorderSize getBorderSize (::Window) const; Rectangle getWindowBounds (::Window, ::Window parentWindow); @@ -344,7 +344,6 @@ class XWindowSystem : public DeletedAtShutdown void dismissBlockingModals (LinuxComponentPeer*) const; void dismissBlockingModals (LinuxComponentPeer*, const XConfigureEvent&) const; - void updateConstraints (::Window, ComponentPeer&) const; ::Window findTopLevelWindowOf (::Window) const;