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):
| Output | Logical position | Logical size |
|---|---|---|
| HDMI-A-1 (streamed) | 0x0 | 3840x2160 |
| DP-1 | -2560x360 | 2560x1440 |
| DP-4 (portrait, scale 1.5) | 3840x0 | 1440x2560 |
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 | -1134 | 292 | 1719 | ~3142 (max reachable) |
| v2026.1018.5342 + this PR | -1 | 960 | 1920 | 2880 | 3836 |
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.5342with this change, then installed and run on the host above. The table shows real cursor positions read withhyprctl cursorposwhile a Moonlight-protocol client sent absolute moves, before and after. clang-format --dry-runis 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
masteritself: 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
3Quality 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
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.