Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@logiclrd
Copy link
Contributor

@logiclrd logiclrd commented Oct 30, 2025

I have encountered a bug related to remote cursor movement. When a remote cursor movement is detected, the cursorModel is updated so that gotMouseControl is false. The _checkPeerControlProtected method in input_model.dart includes logic to allow you to regain control if the cursor moves more than a certain distance. However, this logic doesn't work as expected because after each check, it latches the new mouse position. As a result, the mouse movement isn't cumulative. The cursor must move kMouseControlDistance pixels in a single event in order to regain control. It turns out, at least on my Windows 10 machine, that it takes considerable effort to move the mouse fast enough to trigger this. So what ends up happening is that input is just locked until the cursor leaves the RustDesk remote view window. Then, later, when the mouse cursor re-enters the view, it's usually more than kMouseControlDistance pixels away from where it left the window so the selfGetControl logic finally triggers.

This PR fixes the bug by removing the line that latches each new mouse event location every time the selfGetControl test fails. As a result, now once the mouse has moved a cumulative kMouseControlDistance pixels, control is regained.

In addition, the selfGetControl calculation uses an imprecise model that checks the distance moved on the X and Y axes separately. As such, it is possible to move the mouse up to 1.414 (sqrt(2)) times kMouseControlDistance before selfGetControl activates, if the movement is diagonal. This PR updates the calculation to compute the actual distance travelled, so that it triggers after kMouseControlDistance in any direction, not just orthogonal directions. (The comparison is done between the square of the distance and the square of the threshold, to avoid performing a square root computation. On any computer less than 30 years old I'm fairly certain this optimization is beyond unnecessary, but it's the principle of the matter. 😛 In another PR, Copilot actually told me to use this exact optimization in another context earlier today, for what it's worth.)

@rustdesk rustdesk requested a review from fufesou October 30, 2025 08:00
… distance rather than checking each axis separately.
@rustdesk
Copy link
Owner

The cursor must move kMouseControlDistance pixels in a single event in order to regain control

I do not think we should change this behavior.

@logiclrd
Copy link
Contributor Author

The cursor must move kMouseControlDistance pixels in a single event in order to regain control

I do not think we should change this behavior.

Really? It looks 100% like a bug on my end. I move the mouse around and nothing happens. I have to fling the mouse to get it to trigger.

@logiclrd
Copy link
Contributor Author

Here's a video of my experience:

self-get-control.mp4

@logiclrd
Copy link
Contributor Author

And this is with the changes in this PR:

self-get-control-2.mp4

if (selfGetControl) {
cursorModel.gotMouseControl = true;
} else {
lastMousePos = ui.Offset(x, y);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is intented.

local control is only regained if kMouseControlDistance is exceeded in a single mouse event.

As you guessed,
the user on the controlling side has to move the mouse a significant amount (in a short time) to take control.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mm, okay. To me, intuitively, it seemed to make more sense that control would be regained when the mouse had moved more than a certain amount from the starting position. But it's intentional that the mouse position is irrelevant, and that control is regained when the mouse speed exceeds a certain threshold?

If so, then I think two things:

  1. The current setting requires me to move the mouse far too quickly for it to be discoverable. I would absolutely just assume that I was locked out and had no way to regain control.

  2. The instantaneous mouse speed is probably not the best metric for this. You say that it must move a significant amount "in a short time", but using the instantaneous speed means that the definition of "a short time" is the hardware's reporting interval. I did a quick look-up and haven't verified the numbers, but from what I read, the typical baseline for this is 8 ms, and high-precision gaming mice can have intervals well into the sub-millisecond range.

There's absolutely no way I'm going to move my mouse cursor kMouseControlDistance pixels in 125 microseconds.

If the design here (subject to tuning the travel radius) isn't desired, then I propose an alternative: Maintain a queue of timestamped mouse movements as well as the sum of their distance. On each new event that is subject to selfGetControl checking, append a new entry and delete any entries outside of a determined interval (say 250 ms). Then, selfGetControl is true if the sum exceeds a threshold.

This algorithm will behave identically no matter what direction the mouse is moving (even if the path is curved) and no matter what the rate of mouse position updates is.

Updated input_model.dart to import mouse_speed_analyzer.dart and to incorporate an instance into InputModel.
Updated the _checkPeerControlProtected method of InputModel to use the MouseSpeedAnalyzer to track mousement and determine if/when it exceeds the threshold to regain control.
@logiclrd
Copy link
Contributor Author

logiclrd commented Nov 5, 2025

I've got an updated implementation that does what I outlined in the thread earlier: It tracks a short history of the mouse's movement and tallies up the actual distance travelled in a particular span of time. It triggers the self-get-control functionality when the velocity of the mouse, irrespective of direction, exceeds a configured threshold. The configuration is presently hardcoded but could be exposed as user-configurable options in the future.

The implementation adds a class MouseSpeedAnalyzer which uses a Queue of mouse events. When the mouse cursor is locked, each event gets added to the queue. Events are timestamped, and the timestamp is used to determine when events should be removed from the other end of the queue. As such, if the MouseSpeedAnalyzer is configured with a 250ms window, then the queue never contains more than a 250ms span of mouse events, so its size is well-bounded.

This video shows it in action:

mouse-speed-analyzer.mp4

You can see that it doesn't matter what direction the mouse cursor is moving in, or even if it is a straight line. If it has travelled the requisite distance within the time window, then control is regained.

As described earlier, this algorithm is resilient to different systems having potentially different mouse reporting rates. It doesn't matter how quickly or slowly the mouse events come in for the purpose of calculating the travel distance over time.

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.

3 participants