#5994

fix(linux): map absolute input across monitors left of or above the origin

Openjohnafarmerwants to merge intofrom

Description

On Linux, absolute mouse input lands in the wrong place when any monitor sits left of (or above) the layout origin.

Hyprland maps an absolute pointer device across the bounding box of all monitors, starting at the leftmost/topmost edge. The KMS and wlroots capture backends size the input environment as max(offset + size) and never set env_offset_x/y, so the environment is assumed to start at 0. With a monitor at a negative offset, every absolute move is shifted toward that monitor and squeezed, and the far edge of the streamed display can't be reached.

Example layout (Hyprland 0.56.2, KMS capture of HDMI-A-1):

OutputLogical positionLogical size
HDMI-A-1 (streamed)0x03840x2160
DP-1-2560x3602560x1440
DP-4 (portrait, scale 1.5)3840x01440x2560

Measured host cursor x for client absolute moves at 0 / 25 / 50 / 75 / 99.9% of the stream width (y unaffected here):

0%25%50%75%99.9%
v2026.914.233613-2560-11342921719~3142 (max reachable)
v2026.1018.5342 + this PR-1960192028803836

This PR computes the environment as the monitors' bounding box (origin and size) in kmsgrab.cpp and wlgrab.cpp, which is what the macOS backend already does with env_offset_x/y. A small desktop_bounds helper shared by both backends does the math. Layouts whose monitors all start at 0 are unchanged: the origin stays 0 and the size equals the old max(offset + size).

Screenshot

Issues Fixed or Closed

Roadmap Issues

Testing

  • Added tests/unit/platform/linux/test_desktop_bounds.cpp: empty layout, a layout at the origin (unchanged), a monitor left of the origin (the layout above), a monitor above the origin, and monitors without a logical size. DesktopBounds*: 5/5 pass. The math moved into the new helper, so the test checks the helper itself. The backends' own environment setup needs live DRM/Wayland outputs, which the unit tests can't provide, so there is no test of the old code failing.
  • Built on Arch Linux (CUDA/nvenc on) from v2026.1018.5342 with this change, then installed and run on the host above. The table shows real cursor positions read with hyprctl cursorpos while a Moonlight-protocol client sent absolute moves, before and after.
  • clang-format --dry-run is clean on the changed files.
  • Not run: the full unit suite. Some of its tests create virtual input devices and capture the live desktop of the machine I tested on. Not built from master itself: the change applies cleanly to it, but I built and tested it on the 1018 release tag.

Type of Change

  • feat: New feature (non-breaking change which adds functionality)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit
  • BREAKING CHANGE: Introduces a breaking change (can be combined with any type above)

Checklist

  • Code follows the style guidelines of this project
  • Code has been self-reviewed
  • Code has been commented, particularly in hard-to-understand areas
  • Code docstring/documentation-blocks for new or existing methods/components have been added or updated
  • Tests have been added or updated for new or modified functionality, including regression tests for bug fixes

AI Usage

See our AI usage policy.

  • AI tools were used while preparing this PR

Claude Code found the cause by reading Sunshine's and Hyprland's input mapping, wrote the patch and test, and measured the cursor positions above on real hardware.

🤖 Generated with Claude Code

Activity

3
copilot-pull-request-reviewer[bot]reviewed5d ago
copilot-pull-request-reviewer[bot]5d ago

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

sonarqubecloud[bot]5d ago

Quality Gate failed

Failed conditions
4 New issues
4 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE