Window applies a new preferred scale only at the next resize: pointer, touch and caret positions are off until then #165
Labels
No labels
claude:blocked
claude:done
claude:failed
claude:in-progress
claude:ready
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Catcrafts/Crafter.Graphics#165
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Window::xdg_surface_handle_preferred_scalestores the compositor's new fractional scale inWindow::scaleand does nothing else. The swapchain size and the viewport destination are recomputed only when something resizes the window, so after a scale change the window keeps rendering at the old scale while every conversion between surface-local and physical pixels already uses the new one. Pointer and touch positions and the text-input caret rectangle are off bynew / olduntil the window is resized. Line numbers refer tobcdc885.Details
The window keeps
width x heightphysical pixels in its swapchain and shows them through the viewport atceil(width / scale) x ceil(height / scale)surface-local units (implementations/Crafter.Graphics-Window.cpp:378in the constructor,:733-737inResize). Input arrives in surface-local units and is multiplied byscale: pointer motion and enter atimplementations/Crafter.Graphics-Device.cpp:497and:510, touch down and motion at:690and:729.ApplyTextInputdivides the caret rectangle by it (:450-453). All of that holds only whilescaleis the ratio the current swapchain and viewport were made with.Window.cpp:1784-1787:xdg_surface_handle_preferred_scalesetsscaleand leaveswidth,heightand the viewport as they were.:1770-1776: a laterxdg_surface.configurerestores the ratio only when its toplevel configure carries a size, which becomesResize(ceil(logical * scale)). A(0, 0)configure keeps the current size. Neither protocol promises a configure on a scale change, since the logical size does not change.Concrete case
KWin 6.8.0 on the development desktop, with outputs at 1.75 (DP-1, DP-2) and 1.0 (DP-7).
WAYLAND_DEBUG=clienton a test window shows:preferred_scale(210)arrives right afterget_fractional_scale, before the first configure, so the constructor's viewport already uses 1.75 (set_destination(183, 138)for 320 x 240). Start-up is fine on KWin.xdg_toplevel.configure(0, 0, ...), including one carrying a state. So on KWin a configure restores the ratio only when something resizes the window (an interactive resize, maximizing).By the code, a 1280 x 720 window moved from a 1.75 output (viewport 732 x 412) to the 1.0 one gets
scale = 1.0and keeps its 1280 x 720 buffer at 732 x 412. A pointer at the middle of the window, surface-local (366, 206), is reported at (366, 206) instead of (640, 360), so hit tests against the UI miss. The opposite move reports positions 1.75 times too far out and shows the old buffer upscaled.Found by reading the code. Not reproduced by moving a window between outputs; the event order above is from the trace. A compositor that sends the first preferred scale only once the surface has entered an output (after its first buffer) would show this at start-up too.
Suggested fix
ceil(logical * newScale)asResizedoes (viewport destination at the logical size,onResize), then commit. Keep the logical size as state, since it is what the viewport destination says, instead of re-deriving it fromwidth / scale, so the configure path and the scale path compute from the same number and rounding can't drift.CreateSwapchain), only store the scale, as now.xdg_surface_handle_preferred_scaleon a live window, asPointerEnterLeavedrives the pointer callbacks, and checks thatwidthfollows and that a motion at the surface's middle lands at the buffer's middle.Related: #162. Its fix (in progress) makes the fractional scale only alongside a viewport; without one
scalestays 1 and this does not arise.Another consequence of deriving the logical size from
width / scale, found while reviewing670df31: the conversion rounds up in both directions. The viewport destination isceil(width / scale)(implementations/Crafter.Graphics-Window.cpp:395in the constructor,:751-755inResize), and a sized configure becomesceil(logical * scale)physical pixels (:1788-1794). fractional-scale-v1 says: "For toplevel surfaces, the size is rounded halfway away from zero."At 1.5, a configure of 853 wide gives a buffer of
ceil(1279.5) = 1280(rounding gives the same) and then a destination ofceil(853.33) = 854: one logical pixel wider than configured, which a maximized window must not be, and the compositor resamples the 1280-pixel buffer over 1281 device pixels. At 1.25, 101 wide givesceil(126.25) = 127, where the protocol's rounding gives 126.The suggested fix above covers it: with the logical size kept as state, the destination is exactly the configured size, and the buffer is
round(logical * scale), halfway away from zero. Line numbers refer to670df31.