Skip to content

fix: improve screen edge mouse pointer accuracy - #2568

Merged
Drakulix merged 1 commit into
pop-os:masterfrom
RipleyTom:pointer_accuracy
Jul 21, 2026
Merged

Drakulix merged 1 commit into
pop-os:masterfrom
RipleyTom:pointer_accuracy

Conversation

@RipleyTom

@RipleyTom RipleyTom commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

This fixes scrolling by moving to the edge in games when using scaling.
For testing purposes I encountered the initial issue in XCOM 2 and The Guild - Europa 1410 Demo in 3840*2160 at 175% scale but I think a lot of games were affected.

The current implementation removed 1 from the logical size as the upper margin of the clamp, which could easily remove several physical pixels and the mouse pointer would never reach the edges which resulted in games not scrolling down and right properly.

We now use 2 extra steps, first we use a margin of (last logical pixel + edge) / 2.
Then we limit the size to the floating value of the logical size. This is necessary as the values we get from Wayland are rounded and we can easily overflow into the upper bound otherwise.

I made a simple rust test:

Test code
const RESOLUTIONS: [(f64, f64); 5] = [(1280.0, 720.0), (1920.0, 1080.0), (2560.0, 1440.0), (3840.0, 2160.0), (7680.0, 4320.0)];
const SCALINGS: [f64; 11] = [0.5, 0.75, 1.0, 1.25, 1.50, 1.75, 2.0, 2.25, 2.50, 2.75, 3.0];

fn ret_result(b: bool) -> &'static str {
	if b {
		return "\x1b[92mSuccess\x1b[0m";
	}

	return "\x1b[91mFailure\x1b[0m";
}

struct Counters {
	success_old: u64,
	success_new: u64,
	total: u64,
}

impl Counters {
	fn new() -> Counters {
		Counters {
			success_old: 0,
			success_new: 0,
			total: 0,
		}
	}

	fn test_old(&mut self, b: bool) -> &'static str {
		if b {
			self.success_old += 1;
		}

		return ret_result(b);
	}

	fn test_new(&mut self, b: bool) -> &'static str {
		if b {
			self.success_new += 1;
		}

		return ret_result(b);
	}
}

fn main() {
	let mut count = Counters::new();
	for scaling in SCALINGS {
		println!("At {}% scaling:", scaling * 100.0);
		for resolution in RESOLUTIONS {
			println!("{}x{}", resolution.0, resolution.1);
			let logical_size = (resolution.0 / scaling, resolution.1 / scaling);
			let logical_geometry_size = (logical_size.0.round(), logical_size.1.round());
			let physical_last_pixels = (resolution.0 - 1.0, resolution.1 - 1.0);
			let current_actual_last_pixels = (((logical_geometry_size.0 - 1.0) * scaling).floor(), ((logical_geometry_size.1 - 1.0) * scaling).floor());

			let logical_last_pixels_with_scaled_edge = (
				((resolution.0 - 1.0) / scaling + (logical_geometry_size.0.min(logical_size.0))) / 2.0,
				((resolution.1 - 1.0) / scaling + (logical_geometry_size.1.min(logical_size.1))) / 2.0,
			);
			let physical_last_pixel_with_scaled_edge = ((logical_last_pixels_with_scaled_edge.0 * scaling).floor(), (logical_last_pixels_with_scaled_edge.1 * scaling).floor());

			let old = count.test_old(current_actual_last_pixels == physical_last_pixels);
			let success_new = physical_last_pixel_with_scaled_edge == physical_last_pixels;
			let new = count.test_new(success_new);
			count.total += 1;
			println!("old: {}, logical_edge: {}", old, new);

			if !success_new {
				println!("logical_size: {:?}", logical_size);
				println!("geometry_size: {:?}", logical_geometry_size);
				println!("real_last_pixels: {:?}", physical_last_pixels);
				println!("current_actual_last_pixels: {:?}", current_actual_last_pixels);
				println!("physical_last_pixel_with_scaled_edge: {:?}", physical_last_pixel_with_scaled_edge);
			}
		}
		println!("=====================================================")
	}

	println!("old: {}/{}", count.success_old, count.total);
	println!("new: {}/{}", count.success_new, count.total);
}
Results
At 50% scaling:
1280x720
old: Success, logical_edge: Success
1920x1080
old: Success, logical_edge: Success
2560x1440
old: Success, logical_edge: Success
3840x2160
old: Success, logical_edge: Success
7680x4320
old: Success, logical_edge: Success
=====================================================
At 75% scaling:
1280x720
old: Success, logical_edge: Success
1920x1080
old: Success, logical_edge: Success
2560x1440
old: Success, logical_edge: Success
3840x2160
old: Success, logical_edge: Success
7680x4320
old: Success, logical_edge: Success
=====================================================
At 100% scaling:
1280x720
old: Success, logical_edge: Success
1920x1080
old: Success, logical_edge: Success
2560x1440
old: Success, logical_edge: Success
3840x2160
old: Success, logical_edge: Success
7680x4320
old: Success, logical_edge: Success
=====================================================
At 125% scaling:
1280x720
old: Failure, logical_edge: Success
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Success
7680x4320
old: Failure, logical_edge: Success
=====================================================
At 150% scaling:
1280x720
old: Failure, logical_edge: Success
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Success
7680x4320
old: Failure, logical_edge: Success
=====================================================
At 175% scaling:
1280x720
old: Failure, logical_edge: Success
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Success
7680x4320
old: Success, logical_edge: Success
=====================================================
At 200% scaling:
1280x720
old: Failure, logical_edge: Success
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Success
7680x4320
old: Failure, logical_edge: Success
=====================================================
At 225% scaling:
1280x720
old: Failure, logical_edge: Success
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Success
7680x4320
old: Failure, logical_edge: Success
=====================================================
At 250% scaling:
1280x720
old: Failure, logical_edge: Success
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Success
7680x4320
old: Failure, logical_edge: Success
=====================================================
At 275% scaling:
1280x720
old: Failure, logical_edge: Failure
logical_size: (465.45454545454544, 261.8181818181818)
geometry_size: (465.0, 262.0)
real_last_pixels: (1279.0, 719.0)
current_actual_last_pixels: (1276.0, 717.0)
physical_last_pixel_with_scaled_edge: (1278.0, 719.0)
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Failure
logical_size: (1396.3636363636363, 785.4545454545455)
geometry_size: (1396.0, 785.0)
real_last_pixels: (3839.0, 2159.0)
current_actual_last_pixels: (3836.0, 2156.0)
physical_last_pixel_with_scaled_edge: (3839.0, 2158.0)
7680x4320
old: Failure, logical_edge: Success
=====================================================
At 300% scaling:
1280x720
old: Failure, logical_edge: Success
1920x1080
old: Failure, logical_edge: Success
2560x1440
old: Failure, logical_edge: Success
3840x2160
old: Failure, logical_edge: Success
7680x4320
old: Failure, logical_edge: Success
=====================================================
old: 16/55
new: 53/55

So we go from 16/55 tests passed to 53/55, the 2 failures are at very high scaling(275%) and are the result of the rounding of the geometry_size where the shortfall can be >= 1 pixels at those scales. I have no easy solution for this.

  • I have disclosed use of any AI generated code in my commit messages.
    • If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
    • In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
  • I understand these changes in full and will be able to respond to review comments.
  • My change is accurately described in the commit message.
  • My contribution is tested and working as described.
  • I have read the Developer Certificate of Origin and certify my contribution under its conditions.

Comment thread src/input/mod.rs Resolved
Comment thread src/input/mod.rs Resolved
Drakulix
Drakulix previously approved these changes Jul 20, 2026

@Drakulix Drakulix left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good apart from two small nitpicks.

Comment thread src/input/mod.rs Resolved
Comment thread src/input/mod.rs Resolved
@Drakulix
Drakulix requested a review from a team July 20, 2026 13:59
@git-f0x

git-f0x commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

This has a regression in that you can't grab the scrollbar of a maximized window at the right screen edge, with the resize indicator showing up (which was the reason I originally added the -1, since the cursor goes off-screen) :(

The same is true for the bottom edge.

@RipleyTom

Copy link
Copy Markdown
Contributor Author

This has a regression in that you can't grab the scrollbar of a maximized window at the right screen edge, with the resize indicator showing up (which was the reason I originally added the -1, since the cursor goes off-screen) :(

The same is true for the bottom edge.

I have a separate PR for that issue, pop-os/iced#367 .

@git-f0x

git-f0x commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Not iced related. This is with all apps (e.g. Firefox).

@RipleyTom

Copy link
Copy Markdown
Contributor Author

I see, added a separate commit for it as it is a separate issue: rounding was used in calculations which could result in it rounding up and putting the relative position on edge while it actually being below.

@RipleyTom

Copy link
Copy Markdown
Contributor Author

I added one more commit to address possible issues brought to life by the new pointer accuracy in tiling/mod.rs. The issue is still the same, let location = location_f64.to_i32_round(); can round up location and put it on the edge. It wasn't an issue before as pointer was constrained by -1 logical so could never end up close enough to the edge to round up to it.

I think that will be the last commit unless @git-f0x finds another issue.

@RipleyTom

Copy link
Copy Markdown
Contributor Author

Actually relooking at it, I can probably fix most of those with just a to_i32_floor() without changing everything to f64 🤦 . Will rework it.

@RipleyTom

Copy link
Copy Markdown
Contributor Author

I did a full sweep where location and to_i32_round() was used and fixed them.
I think it's pretty much done now.

@Drakulix Drakulix left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@git-f0x

git-f0x commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

I think the only very minor regression is that a 1 pixel line of the cursor isn't visible anymore when it's all the way at the right or bottom edges (like on other compositors). Might be related to the behavior in maximized Firefox when the cursor is all the way to the right and clicking the scrollbar with the stationary cursor to go to a point, the scrollbar proceeds to shrink after the click, instead of staying fully revealed like before. It gets revealed again if the cursor is moved (including pushing further into the edge).

But that's very minor, and this fixes bigger issues.

@Drakulix
Drakulix merged commit 8955024 into pop-os:master Jul 21, 2026
9 of 10 checks passed
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.

4 participants