Problem
Absolute mouse input aimed at a non-primary display lands on the wrong monitor.
client_to_touchport() returns a position relative to the streamed monitor, and folds the monitor origin in only for the Linux backends:
// Linux input backends expect desktop-relative coordinates here.
// Windows/macOS apply monitor offsets in platform code, so adding offsets here
// would double-apply them and clamp input to edges.
#ifdef __linux__
float final_x = (x + touch_port.offset_x * touch_port.scalar_tpcoords) / touch_port.scalar_tpcoords;
#else
float final_x = x / touch_port.scalar_tpcoords;
#endifmacOS holds up its half of that contract, adding CGDisplayBounds(display).origin in abs_mouse. Windows stopped: fe1c1f07 dropped the offset term and justified it with a note claiming the opposite of what the code above does.
// Note: x and y already include the display offset (offset_x/offset_y) from client_to_touchport(),
// so we must not add offset_x/offset_y again here to avoid double-offsetting on multi-monitor setups.
auto scaled_x = std::lround(x * ((float) target_touch_port.width / (float) touch_port.width));MOUSEEVENTF_VIRTUALDESK addresses the entire desktop, so every position was then interpreted relative to the desktop origin rather than the monitor's. The pointer tracks correctly only while the streamed monitor happens to sit at the top-left of the virtual desktop — which is why this went unnoticed on single-monitor hosts.
Concretely, streaming a 1920x1080 monitor whose origin is at x = 1920 on a 3840-wide desktop: a click at the far right of the stream is sent to 1920 / 3840 of the desktop, landing in the middle of the other monitor.
Fix
The mapping moves into mouse_input as to_virtual_desktop(), which is portable, unit-testable, and now the single definition of the 0..65535 grid — replacing the target_touch_port constant that only existed to carry it. Windows abs_mouse calls it. An empty viewport maps to the origin instead of dividing by zero.
The touch and pen paths are unaffected: they already apply the offset themselves in populate_common_pointer_info.
Testing
Four cases in tests/unit/test_mouse.cpp (test_component_mouse_policy). Verified they fail against the pre-fix behaviour and pass after it:
[ FAILED ] MouseVirtualDesktopMapping.AddsTheMonitorOriginBeforeScaling
[ FAILED ] MouseVirtualDesktopMapping.HandlesAMonitorLeftOfThePrimaryNot yet exercised on hardware; it needs a Windows host with the streamed display placed somewhere other than the top-left of the desktop.
Note
Touches lines adjacent to #455. They are independent and can merge in either order; the textual conflict in abs_mouse's comment is a one-liner.
🤖 Generated with Claude Code
Activity
3Thanks for the mapping tests and the Windows build results. Development integration targets canary; could you update this original PR's base to canary, update the branch, and resolve the conflicts, including the adjacent abs_mouse changes? Please rerun the mouse mapping tests on the reconciled branch and check the CI result: the current run is marked failed, although its job details are unavailable here. Your earlier note about the locale test's working directory is helpful; no unrelated locale change is requested in this PR.
This pull request is a draft
Merging is unavailable until it is marked ready for review.
Built and tested on Windows since opening this.
Build: MSYS2 UCRT64 (gcc 16.2, CMake 4.4.2, Ninja),
-DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON.sunshine.exelinks clean, and none of the touched translation units emit a warning.Tests:
ctestreports 52/53. The one failure,test_component_resource_locale_catalog, is pre-existing and unrelated:test_locale_consistency.cpp:42resolvessrc_assets/common/assets/web/public/assets/localerelative to the working directory, and ctest runs the binary frombuild/tests, so it finds zero locale files and both cases fail on theirASSERT_FALSE(...empty())guard. Running the same binary from the repository root:Worth a
WORKING_DIRECTORYon thatadd_test, but not from this PR.Still unverified on hardware — that is why this is a draft.