Skip to content

X11: Write WM_NORMAL_HINTS in a single call - #1709

Draft
emezeske wants to merge 1 commit into
juce-framework:developfrom
emezeske:pr/x11-wm-normal-hints
Draft

X11: Write WM_NORMAL_HINTS in a single call#1709
emezeske wants to merge 1 commit into
juce-framework:developfrom
emezeske:pr/x11-wm-normal-hints

Conversation

@emezeske

@emezeske emezeske commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

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.

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
emezeske force-pushed the pr/x11-wm-normal-hints branch from a5b121a to 33beded Compare August 11, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant