Skip to content

Fix libei mousemove double-scale at fractional DPI - #55

Open
cushycush wants to merge 1 commit into
mainfrom
fix/libei-fractional-scale-mousemove
Open

Fix libei mousemove double-scale at fractional DPI#55
cushycush wants to merge 1 commit into
mainfrom
fix/libei-fractional-scale-mousemove

Conversation

@cushycush

@cushycush cushycush commented May 23, 2026

Copy link
Copy Markdown
Owner

The bug
@hron reported during the KDE Plasma 6 verification walk in #50: on a 4K display at 125% scale, wdotool mousemove 1920 0 was landing the cursor at physical (2400, 0) instead of (1920, 0). Filed as #53.

Root cause
KWin advertises its EIS region in logical pixels with a scale field that's physical-per-logical (1.25 for 125% scale, 1.75 for 175%), and the libei spec says motion_absolute and motion_relative coordinates are in those logical pixels. wdotool's user-facing coordinate convention is device pixels, matching xdotool (which inherits from X11, where there's no compositor scale). So when hron asked for the cursor at device-pixel 1920, wdotool sent 1920 raw, KWin interpreted it as a logical-pixel position within its 3072x1728 logical region, and the cursor landed at physical (2400, 0) because KWin re-applied the scale.

The fix
In wdotool-core/src/backend/libei.rs, divide incoming coordinates by the active region's scale before sending. For absolute motion, convert_absolute_to_logical picks the region whose device-pixel extent contains the target point and divides by its scale, which lands the cursor on the right monitor in mixed-scale setups and does the obvious thing for uniform-scale ones. Relative motion has no positional context to pick a region from, so convert_relative_to_logical divides by the first region's scale; uniform-scale multi-monitor still works, and mixed-scale stays best-effort. While I was in there I also dropped a debug log of the announced regions on PointerAbsolute device add, so the next person debugging coord issues can see what the compositor advertised. The --backend wlroots path is untouched (wlroots' zwlr_virtual_pointer_v1 uses per-output mode dimensions directly, no scale to undo), and xdotool's device-pixel convention is preserved end-to-end; the fix is internal to libei.

Verification
Seven new unit tests in libei::tests cover the math: identity for no-regions and unit scale, the 125% case from hron's report, the multi-region containment pick, the out-of-bounds fallback, and both flavors of motion_relative. End-to-end verification still needs KDE Plasma 6 hardware (I'm on Hyprland), but the logic falls out of the libei spec cleanly and matches what KWin docs and reis's Region struct say about logical-pixel coordinates. If @hron is able to spin this up on his box and check that mousemove 1920 0 lands at physical (1920, 0) instead of (2400, 0), that would close the loop.

Closes #53.

KWin advertises its EIS region in logical pixels with a scale field
(physical-per-logical, 1.25 for 125% display scale), and libei takes
motion_absolute / motion_relative coords in those logical pixels.
wdotool's user-facing coordinate convention is device pixels
(matching xdotool, which inherits from X11). So on hron's 4K @ 125%
KDE box, wdotool was sending 1920 thinking "device pixel halfway",
KWin was interpreting 1920 as a logical-pixel position within its
3072x1728 region, and the cursor was landing at physical (2400, 0)
instead of (1920, 0).

Divide incoming coords by the region's scale before calling
motion_absolute / motion_relative. Multi-region setups pick the
region whose device-pixel extent contains the target point and use
its scale (uniform-scale dual-monitor works; mixed-scale
multi-monitor falls back to the first region's scale and is
best-effort).

Seven new unit tests cover the math. End-to-end verification still
needs hron's KDE box; the logic falls out of the libei spec cleanly.
Also logs the announced regions at debug level on PointerAbsolute
device add so the next debugger has visibility.

Closes: #53
Refs: WDOTOOL-14
@cushycush

Copy link
Copy Markdown
Owner Author

@hron, fix candidate for the fractional-scale row. If you have a minute on the 4K @ 125% box:

gh pr checkout 55
cargo build --release
sleep 2 && ./target/release/wdotool mousemove 1920 0

Cursor should land at physical (1920, 0) instead of the (2400, 0) you were seeing. Same divide-by-scale fix applies to relative motion. I can't reproduce locally (Hyprland, no fractional scale), so a real-hardware confirmation before tagging v0.6.0 would close the loop.

@cushycush

Copy link
Copy Markdown
Owner Author

Heads up: I'm rebuilding wflow now against current wdotool-core, which pulls in the v0.5.2 libei flush + two-stage seat.bind fix and the v0.5.3 KWin scripting fix. That should fix your wflow 5-step workflow row from #50 (which failed on the pre-v0.5.2 libei timeout signature). I'll ping again with the wflow release version when it's out, so you can re-test both rows in one pass.

@cushycush

Copy link
Copy Markdown
Owner Author

it's out. update wflow and it should work fine now

@hron

hron commented May 23, 2026

Copy link
Copy Markdown
Contributor

@cushycush, it works great!

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.

libei backend: mousemove coordinates double-scaled on KDE at fractional DPI

2 participants