X11: Write WM_NORMAL_HINTS in a single call - #1709
Draft
emezeske wants to merge 1 commit into
Draft
Conversation
On Linux a window does not open where it is put. A DialogWindow launched with componentToCentreAround set, which should appear centred on its owner, opens at the centre of the screen under mutter or in a bottom corner under openbox, wherever the window manager's own placement policy decides to put it. The same property is also responsible for windows being resizable below their constrainer minimum. To reproduce, run on Xorg under a window manager that has a placement policy, move the main window well off the centre of the display, and open a dialog through DialogWindow::LaunchOptions with componentToCentreAround pointing at a component in that window. The dialog lands somewhere unrelated to its owner, and nothing reports anything wrong: the peer's own idea of its bounds stays correct right up until the manager's ConfigureNotify arrives and overwrites it. WM_NORMAL_HINTS on the dialog carries the size limits and no user position at all. XSetWMNormalHints replaces the whole property, and the peer writes that property from two places: setBounds writes USSize and USPosition, and updateConstraints writes PMinSize and PMaxSize. Whichever one runs second wipes the other one's fields, so a window never carries both. Without the constraints the window manager may resize a window below its minimum; without USPosition it may ignore the requested geometry entirely. Reordering the two writes only trades one symptom for the other. Both halves are now filled into one XSizeHints and written once, by updateSizeHints. updateWindowBounds calls it after refreshing the bounds and the scale factor rather than before, since the hints restate the geometry it has just read back. Also clamp the constrainer limits before scaling them. Constrainers commonly use the maximum int value to mean unbounded, so for scale factors greater than 1.0 the conversion back to int was undefined behaviour and advertised garbage maximum sizes; combined with a zero horizontal frame border that pinned windows to their minimum width on scaled displays.
emezeske
force-pushed
the
pr/x11-wm-normal-hints
branch
from
August 11, 2026 17:57
a5b121a to
33beded
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On Linux a window does not open where JUCE puts it. A DialogWindow launched with componentToCentreAround set, which should appear centred on its owner, instead opens at the centre of the screen under mutter or in a bottom corner under openbox, wherever the window manager's own placement policy decides to put it. The same property is also responsible for windows being resizable below their constrainer minimum.
To reproduce, run any JUCE app on Xorg under a window manager that has a placement policy (mutter and openbox both do), move its main window well off the centre of the display, and open a dialog through DialogWindow::LaunchOptions with componentToCentreAround pointing at a component in that window. The dialog lands somewhere unrelated to its owner. Nothing in JUCE reports anything wrong, because the peer's own idea of its bounds stays correct right up until the window manager's ConfigureNotify arrives and overwrites it. Reading WM_NORMAL_HINTS off the dialog (xprop -id) shows the size limits and no user position at all.
The cause is that XSetWMNormalHints replaces the whole property, and the X11 peer writes that property from two places: setBounds writes USSize and USPosition, and updateConstraints writes PMinSize and PMaxSize. Whichever runs second wipes the other one's fields, so a window never carries both. Without the constraints the window manager may resize the window below its minimum; without USPosition it may ignore the requested geometry entirely. Reordering the two writes only trades one symptom for the other.
This fills both halves into a single XSizeHints and writes it once, in updateSizeHints. updateWindowBounds now calls that after refreshing the bounds and the scale factor rather than before, since the hints restate the geometry it has just read back. The commit also clamps the constrainer limits before scaling them, since constrainers commonly use the maximum int value to mean unbounded and for scale factors above 1.0 the conversion back to int is undefined behaviour that advertises garbage maximum sizes.
Tested on Xorg under both mutter and openbox, with a dialog centred on its owner window: before the change the manager placed it by its own policy in both, after it the window lands where it was asked to.