Description
In pkg/application/screen_darwin.go, cScreenToScreen multiplies screen.x and screen.y by the screen's own backingScaleFactor before assigning them to Bounds.X / Bounds.Y:
sf := float64(screen.scaleFactor)
toPhysical := func(points C.int) int { return int(float64(points) * sf) }
return &Screen{
X: toPhysical(screen.x),
Y: toPhysical(screen.y),
Bounds: Rect{
X: toPhysical(screen.x),
Y: toPhysical(screen.y),
...
},
...
}
The intent (per the code comment) is a round-trip workaround for applyDPIScaling in screenmanager.go, which divides Bounds.Width / Bounds.Height by the scale factor. But Bounds.X / Bounds.Y are not divided again in applyDPIScaling — they're left in points × ownScaleFactor space, while Bounds.Width / Bounds.Height end up in logical points.
After #5304 + the GetScreens normalization, Position() / SetPosition() are in logical points across screens. So on mixed-DPI setups (e.g., a Retina secondary above a 1x primary), SetPosition(s.Bounds.X, s.Bounds.Y) for a non-primary screen lands at the wrong location by a factor of that screen's ScaleFactor.
Steps to Reproduce
- Primary monitor: 1x (non-Retina).
- Secondary monitor: 2x Retina, positioned above the primary in macOS Displays settings.
- Read
secondary.Bounds.Y from GetScreens().
- Call
SetPosition(secondary.Bounds.X, secondary.Bounds.Y).
- Window does not land at the top-left of the secondary; it's off by
secondary.ScaleFactor.
The same ScaleFactor doubling exists today but only becomes user-visible once Position/SetPosition are normalized to logical points (which is the goal of #5117 / #5304).
Notes
Environment
- Wails v3 (
pkg/application/screen_darwin.go, pkg/application/screenmanager.go)
- macOS, mixed-DPI multi-monitor
Related
Description
In
pkg/application/screen_darwin.go,cScreenToScreenmultipliesscreen.xandscreen.yby the screen's ownbackingScaleFactorbefore assigning them toBounds.X/Bounds.Y:The intent (per the code comment) is a round-trip workaround for
applyDPIScalinginscreenmanager.go, which dividesBounds.Width/Bounds.Heightby the scale factor. ButBounds.X/Bounds.Yare not divided again inapplyDPIScaling— they're left inpoints × ownScaleFactorspace, whileBounds.Width/Bounds.Heightend up in logical points.After #5304 + the GetScreens normalization,
Position()/SetPosition()are in logical points across screens. So on mixed-DPI setups (e.g., a Retina secondary above a 1x primary),SetPosition(s.Bounds.X, s.Bounds.Y)for a non-primary screen lands at the wrong location by a factor of that screen'sScaleFactor.Steps to Reproduce
secondary.Bounds.YfromGetScreens().SetPosition(secondary.Bounds.X, secondary.Bounds.Y).secondary.ScaleFactor.The same
ScaleFactordoubling exists today but only becomes user-visible oncePosition/SetPositionare normalized to logical points (which is the goal of #5117 / #5304).Notes
ScaleFactor=1and the multiplication is a no-op.Boundsunit bug — exposed (not introduced) by the GetScreens normalization landing alongside fix(darwin): use primary screen height for SetPosition Y conversion #5304.* sfmultiplication on X/Y incScreenToScreen(keep it forPhysicalBounds.X/Y), and verify nothing inscreenmanager.go(e.g.,move()placement adjustment,getScreenAlignment) regresses on the cross-DPI tests.Environment
pkg/application/screen_darwin.go,pkg/application/screenmanager.go)Related