fix: set_outer_position uses target monitor's scale factor#4505
Open
natepiano wants to merge 6 commits intorust-windowing:masterfrom
Open
fix: set_outer_position uses target monitor's scale factor#4505natepiano wants to merge 6 commits intorust-windowing:masterfrom
natepiano wants to merge 6 commits intorust-windowing:masterfrom
Conversation
set_outer_position converts between physical and logical coordinates using the window's current scale factor. When the target position is on a different monitor with a different scale factor, this produces incorrect coordinates and the window lands at the wrong position. Add resolve_scale_factor() to winit-core that determines which monitor a position targets, and use it in the macOS, Windows, and X11 implementations of set_outer_position. Add cross_monitor_position example to demonstrate and verify the fix.
- Added documentation section explaining how to force X11 backend on Wayland systems - Includes shell command to unset WAYLAND_DISPLAY environment variable for testing - Improves usability of the example on Linux systems with Wayland as default session Why: Users on modern Linux distributions may have Wayland set as the default display server, which can affect monitor positioning behavior. Explicit instructions help users reproduce consistent test results across different window manager configurations. Impact: Makes the example more accessible and provides clear guidance for cross-platform testing on Linux systems. Also includes minor formatting improvements: - Reformatted long lines in function signatures for consistency - Adjusted print statement formatting for better readability
X11 does not support per-monitor scale factors, so the scale_factor_for fix is a no-op on this platform.
- X11 does not expose per-monitor scale factors, so the cross-monitor scale factor bug does not apply — remove all X11 mentions from the example docs and runtime output - Remove the Wayland-override instructions for forcing the X11 backend - Remove X11 row from the "reproducing the bug" platform table - Includes minor formatting changes in monitor.rs test helpers from stable cargo fmt (no logic changes)
scale_factor_for is not a trait method — it belongs in a separate impl Window block.
github-merge-queue bot
pushed a commit
to bevyengine/bevy
that referenced
this pull request
Mar 9, 2026
## Summary When a bevy app starts, winit creates the window on a default monitor. If the app then sets the window's position to a location on a different monitor with a different scale factor, both position and size may be applied in the same pass through `changed_windows()`. Currently, size is processed first (via `request_inner_size`), then position (via `set_outer_position`). Since the window hasn't moved yet when `request_inner_size` runs, it uses the default monitor's scale factor for the size calculation — not the scale factor of the monitor the window is about to move to. This produces the wrong size when the two monitors have different scale factors. This PR moves the position block above the size block so the window is on the target monitor before `request_inner_size` runs. ## Context This change is a **no-op with current released versions of winit**. Today, winit's `set_outer_position` has a bug where it uses the window's current monitor's scale factor instead of the target monitor's ([rust-windowing/winit#4505](rust-windowing/winit#4505)), so the window doesn't reliably land at the correct position regardless of ordering. Once winit merges that fix and bevy takes a dependency on a version that includes it, the ordering will matter: `set_outer_position` will correctly move the window to the target monitor, and `request_inner_size` needs to run *after* that move so it sees the correct `scale_factor()`. The change is safe and backwards-compatible — it just reorders two independent blocks within the same function. ## Testing Tested on macOS (1x + 2x) and Windows (1x + 1.5x) with the patched winit. Window appears at the correct position and size on the target monitor.
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.
Summary
set_outer_positionconverts between physical and logical coordinates using the window's current monitor's scale factor. When the window is on one monitor and the requested position is on a different monitor with a different scale factor, the conversion uses the wrong scale factor and the window lands at the wrong position.For example: a window is on a 1x monitor. The caller passes physical coordinates targeting a position on a 2x monitor. On macOS, the Physical → Logical conversion divides by 1.0 (the current monitor's scale) instead of 2.0 (the target monitor's scale), producing logical coordinates that are 2x too large. The window overshoots dramatically.
The platform determines which coordinate type triggers the bug:
I did not apply this fix to the X11 backend. X11 typically reports the same scale factor for all monitors (via the global
Xft.dpisetting), so the bug does not manifest in practice.Approach
I'm not deeply familiar with winit's internal patterns, so I tried to keep the API surface minimal and make the logic easy to review:
resolve_scale_factor()inwinit-core/src/monitor.rsis a pure function that determines which monitor contains a given position and returns its scale factor. It has 16 unit tests covering physical/logical inputs, single/multi-monitor layouts, and edge cases.MonitorBoundsis a simple struct thatresolve_scale_factoroperates on, decoupled from platform-specific monitor types.scale_factor_for()helper that collects monitor bounds and callsresolve_scale_factor, falling back to the current window's scale factor if no monitor matches — preserving existing behavior.set_outer_position:self.scale_factor()→self.scale_factor_for(&position).Example
cross_monitor_positiondemonstrates and verifies the fix. It moves a window between monitors with different scale factors and compares the target position against the actual position. With this PR applied, the example passes on both macOS and Windows. The example's doc comment includes instructions for reverting the one-line fix to reproduce the bug.Testing
Tested on macOS (1x + 2x) and Windows (1x + 1.5x).
Fixes #4440