xpl host (win32): first RESIZE after frame creation reported physical px (#29) - #30
Merged
Merged
Conversation
… px (#29) CreateWindowExW sends WM_SIZE before it returns (for a WS_CHILD embedded PLUGWINDOW it always does), but the xpl host only assigned wd.dpi *after* the call returned. The WM_SIZE handler converts the physical client size to logical with that dpi, so at its 96 default the conversion was an identity and the client's first NEUI_EVENT_RESIZE carried PHYSICAL pixels: a frame created 940x400 reported 1410x600 at 150%. A client laying out from the resize event - the documented way to react to a size change - then laid out 1.5x too big, which in an embedded editor pushes the whole UI off-screen. The win32 native host does not have the bug because it seeds wd->dpi in its WM_CREATE, one message earlier; the xpl host now does the same. - WM_CREATE: seed wd->dpi from GetDpiForWindow(hwnd) before the creation-time WM_SIZE can read it. - create_native_window: seed wd.dpi before CreateWindowExW from the best pre-creation estimate (embed parent / dialog owner DPI, else system DPI) and size the outer window from it. WM_GETMINMAXINFO arrives before WM_NCCREATE, so it too was scaling MIN_/MAX_ tracking sizes as if the frame were at 96 DPI; an embedded frame now also lands at the parent's real scale on the first try instead of via the correction round-trip. - WM_SIZE: convert with MulDiv (round-to-nearest, the exact inverse of the MulDiv used when sizing the window) instead of a truncating float divide, which lost a pixel at 125% (logical 941 -> 1176 phys -> 940). - Harden the division-by-zero paths GetDpiForWindow's 0-on-failure return feeds (the issue's aside): phys_to_log, platform_get_scale_factor, the post-creation read-back, Session::on_dpi_changed, d2d_create_context. Adds tests/embed_smoke_win32.cpp, a fake-DAW harness mirroring the existing Linux / macOS embed smokes (built, not ctest-registered): it embeds a PLUGWINDOW in a foreign HWND, drives it from the fake DAW's own pump, and asserts the client-area contract plus a logical-px first RESIZE. On this 150% display it fails before the fix (first RESIZE 1410x600) and passes after (940x400). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Fixes #29.
Cause
CreateWindowExWsendsWM_SIZEbefore it returns - and for aWS_CHILDembedded
PLUGWINDOWit always does - butcreate_native_windowonly assignedwd.dpiafter the call returned. TheWM_SIZEhandler converts the physicalclient size to logical with that dpi, so at its 96 default the conversion was an
identity and the client's first
NEUI_EVENT_RESIZEcarried physical pixels:a frame created 940x400 reported 1410x600 at 150%. A client laying out from the
resize event then lays out 1.5x too big, which in an embedded editor pushes the
whole UI off the visible area.
The win32 native host does not have the bug because it seeds
wd->dpiin itsWM_CREATE(hosts/win32/window.cpp), one message earlier. The xpl host nowdoes the same - that is the whole fix; the rest is hardening in the same area.
One correction to the issue text: on a single-monitor 150% setup a plain
NEUI_W_APPWINDOWdoes not reproduce. Instrumenting the message order showsboth
WM_SIZEs for aWS_OVERLAPPEDWINDOWarrive afterCreateWindowExWreturns (
native_handleset, dpi already read back), so the pre-fix buildreported the correct logical size there. The creation-time
WM_SIZEis specificto the embedded
WS_CHILDpath.Changes
WM_CREATE: seedwd->dpifromGetDpiForWindow(hwnd)before thecreation-time
WM_SIZEcan read it. Chosen over an early-out on!native_handle(suggestion 1 in the issue) so the initialRESIZEstillfires - with correct units - rather than being dropped at matching DPI while
still firing in the mismatch case.
create_native_window: seedwd.dpibeforeCreateWindowExWfrom thebest pre-creation estimate (embed parent / dialog owner DPI, else system DPI)
and size the outer window from it.
WM_GETMINMAXINFOarrives beforeWM_NCCREATE, so it too was scalingNEUI_ATTR_MIN_/MAX_tracking sizes asif the frame were at 96 DPI; an embedded frame also lands at its parent's real
scale on the first try now, instead of via the post-creation
SetWindowPoscorrection.
WM_SIZE: convert withMulDiv(round-to-nearest, the exact inverse ofthe
MulDivused when sizing the window) instead of a truncating floatdivide, which lost a pixel at 125% (logical 941 -> 1176 phys -> 940).
GetDpiForWindowreturns0 on failure):
phys_to_log,platform_get_scale_factor, the post-creationread-back,
Session::on_dpi_changed, andd2d_create_context(which wasstoring 0 and would later call
SetDpi(0, 0)).No public API or event-contract change.
Verification
Windows 10 19045, MSVC, display at 150% (system DPI 144),
neui-xplhost+neui-backend-d2d.New
tests/embed_smoke_win32.cpp- a fake-DAW harness mirroring the existingLinux / macOS embed smokes (built, not ctest-registered: it needs a GUI session
and only differs from an identity check on a scaled display). It embeds a
PLUGWINDOWin a foreign HWND, drives it from the fake DAW's own message pump,and asserts the client-area contract plus a logical-px first
RESIZE.Before:
After:
Also:
neui_example(APPWINDOW + menubar, xpl host) still comes up with aclient of exactly 1010x645 logical / 1515x968 physical, so the
sys_dpi->create_dpiswap is a no-op on a single-DPI setup; warning-clean full build;ctestgreen.Not measured: the mixed-DPI multi-monitor dialog path (no second monitor here).
It is a no-op by construction on one monitor -
GetDpiForWindow(owner) == GetDpiForSystem()- and only diverges, in the correct direction, when owner andprimary differ.
🤖 Generated with Claude Code