From the documentation for mouse_event
function:
Relative mouse motion is subject to the settings for mouse speed and acceleration level. An end user sets these values using the Mouse application in Control Panel. An application obtains and sets these values with the SystemParametersInfo function.
I believe these refer to the following two queries:
SPI_GETMOUSE
(= 0x0003
),
Retrieves the two mouse threshold values and the mouse acceleration. The pvParam parameter must point to an array of three integers that receives these values.
The three values are the first threshold, second threshold, and acceleration.
SPI_GETMOUSESPEED
( = 0x0070
),
Retrieves the current mouse speed. The mouse speed determines how far the pointer will move based on the distance the mouse moves. The pvParam parameter must point to an integer that receives a value which ranges between 1 (slowest) and 20 (fastest). A value of 10 is the default.
This seems to follow a somewhat strange formula to scale the movement.
The system applies two tests to the specified relative mouse motion when applying acceleration. If the specified distance along either the x or y axis is greater than the first mouse threshold value, and the mouse acceleration level is not zero, the operating system doubles the distance. If the specified distance along either the x- or y-axis is greater than the second mouse threshold value, and the mouse acceleration level is equal to two, the operating system doubles the distance that resulted from applying the first threshold test. It is thus possible for the operating system to multiply relatively-specified mouse motion along the x- or y-axis by up to four times.
Unfortunately I have not been able to crack this one out (it applies when "enhance pointer precision" is enabled in the "mouse properties"). It definitely does not seem to just double the value.
For values three times as high as the threshold, the scaling seems accurate (it doubles your input), but beyond that the factor of 2 keeps growing. These were tested with a mouse speed of 10:
- For your input of 20, the final distance traveled is 40.
- For 40, the final distance is 93 and not 80 (~1.16 higher).
- For 80, the final distance is 201 and not 160 (~1.25 higher).
- For 160, the final distance is 416 and not 320 (~1.30 higher).
- For 320, the final distance is 846 and not 640 (~1.32 higher).
- For higher values the ratio actual / expected hardly grows.
To make matters worse, with a speed of 20 (assuming the speed scaling as described later is the same):
- For your input of 20, the final distance traveled is 66.
- For 40, the final distance is 173 and not 280 (~0.62 lower).
- For 80, the final distance is 388 and not 560 (~0.69 lower).
- For 160, the final distance is 416 and not 320 (~0.73 lower).
- For 320, the final distance is 1677 and not 2240 (~0.75 higher).
- For higher values, my monitor is not large enough.
Once acceleration has been applied, the system scales the resultant value by the desired mouse speed. Mouse speed can range from 1 (slowest) to 20 (fastest) and represents how much the pointer moves based on the distance the mouse moves. The default value is 10, which results in no additional modification to the mouse motion.
From my testing, it appears 10 means no scaling (scale of 1). Every value above 10 seems to increase the scale factor by 1/4, and every value below 10 decreases it by 1/8 (except for the value of 2 where it would have reached 0, so the scaling is 1/16, and the scaling at 1 unit seems to be 0/32). The following algorithm (Python) predicts where the mouse ends if "enhance pointer precision" is disabled:
import ctypes
speed = ctypes.c_int()
ctypes.windll.user32.SystemParametersInfoA(0x0070, 0, ctypes.byref(speed), 0) # SPI_GETMOUSESPEED
speed = out.value
# x is how much you want to move the mouse in the x axis (the same is true for the y axis)
if speed == 1:
predict_x = x // 32
elif speed == 2:
predict_x = x // 16
elif speed <= 10:
predict_x = (x * (speed - 2)) // 8
else:
predict_x = (x * (speed - 6)) // 4
If you wanted to move the mouse exactly x
, you would need the reverse code:
if speed == 1:
used_x = x * 32
elif speed == 2:
used_x = x * 16
elif speed <= 10:
used_x = int(round((x * 8) / (speed - 2)))
else:
used_x = int(round((x * 4) / (speed - 6)))
My conclusion is it's not worth it to use relative movement for accurate mouse movements. You are better off querying the current position, adding your delta, and setting the absolute position. Otherwise you have to query mouse configuration which can change any time and is designed to feel good, not be exact in a single movement.