Skip to content

Instantly share code, notes, and snippets.

@yinonburgansky
Last active March 9, 2024 19:04
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yinonburgansky/7be4d0489a0df8c06a923240b8eb0191 to your computer and use it in GitHub Desktop.
Save yinonburgansky/7be4d0489a0df8c06a923240b8eb0191 to your computer and use it in GitHub Desktop.
windows acceleration function
# calculation are based on http://www.esreality.com/index.php?a=post&id=1945096
# assuming windows 10 uses the same calculation as windows 7.
# guesses have been made calculation is not accurate
# touchpad users make sure your touchpad is calibrated with `sudo libinput measure touchpad-size`
# import matplotlib.pyplot as plt
import struct
# set according to your device:
xinput_device_id = 13
device_dpi = 1000 # mouse dpi
screen_dpi = 157
screen_scaling_factor = 1
sample_point_count = 21 # should be enough but you can try to increase for accuracy of windows function
sensitivity_factor = 1.0
# sensitivity factor translation table: (windows slider notches)
# 1 = 0.1
# 2 = 0.2
# 3 = 0.4
# 4 = 0.6
# 5 = 0.8
# 6 = 1.0 default
# 7 = 1.2
# 8 = 1.4
# 9 = 1.6
# 10 = 1.8
# 11 = 2.0
# TODO: find accurate formulas for scale x and scale y
# mouse speed: inch/s to device-units/millisecond
scale_x = device_dpi / 1e3
# pointer speed: inch/s to screen pixels/millisecond
scale_y = screen_dpi / 1e3 / screen_scaling_factor * sensitivity_factor
print(f'scale_x={scale_x}, scale_y={scale_y}')
def float16x16(num):
return struct.unpack('<i', num[:-4])[0] / int(0xffff)
# windows 10 registry values:
# HKEY_CURRENT_USER\Control Panel\Mouse\SmoothMouseXCurve
X = [
b'\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x15\x6e\x00\x00\x00\x00\x00\x00',
b'\x00\x40\x01\x00\x00\x00\x00\x00',
b'\x29\xdc\x03\x00\x00\x00\x00\x00',
b'\x00\x00\x28\x00\x00\x00\x00\x00',
]
# HKEY_CURRENT_USER\Control Panel\Mouse\SmoothMouseYCurve
Y=[
b'\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xfd\x11\x01\x00\x00\x00\x00\x00',
b'\x00\x24\x04\x00\x00\x00\x00\x00',
b'\x00\xfc\x12\x00\x00\x00\x00\x00',
b'\x00\xc0\xbb\x01\x00\x00\x00\x00',
]
windows_points = [[float16x16(x), float16x16(y)] for x,y in zip(X,Y)]
print('Windows original points:')
for point in windows_points:
print(point)
# scale windows points according to device config
points = [[x * scale_x, y * scale_y] for x, y in windows_points]
print('Windows scaled points')
for point in points:
print(point)
# plt.plot(*list(zip(*windows_points)), label=f'windows points')
# plt.plot(*list(zip(*points)), label=f'scaled points')
# plt.xlabel('device-speed')
# plt.ylabel('pointer-speed')
# plt.legend(loc='best')
# plt.show()
# exit()
def find2points(x):
i = 0
while i < len(points) - 2 and x >= points[i+1][0]:
i +=1
assert -1e6 + points[i][0] <= x <= points[i+1][0]+1e6, f'{points[i][0]} <= {x} <= {points[i+1][0]}'
return points[i], points[i+1]
def interpolate(x):
(x0, y0), (x1, y1) = find2points(x)
y = ((x-x0)*y1+(x1-x)*y0)/(x1-x0)
return y
def sample_points(count):
# use linear extrapolation for last point to get better accuracy for lower points
last_point = -2
max_x = points[last_point][0]
step = max_x / (count + last_point) # we need another point for 0
sample_points_x = [si * step for si in range(count)]
sample_points_y = [interpolate(x) for x in sample_points_x]
return sample_points_x, sample_points_y
sample_points_x, sample_points_y = sample_points(sample_point_count)
step = sample_points_x[1] - sample_points_x[0]
# plt.plot(sample_points_x, sample_points_y, label=f'windows {sample_point_count} points')
# plt.plot(*sample_points(1024), label=f'windows 1024 points')
# plt.xlabel('device-speed')
# plt.ylabel('pointer-speed')
# plt.legend(loc='best')
# plt.show()
# exit()
sample_points_str = ";".join(["%.3f" % number for number in sample_points_y])
print(f"libinput custom-step: {step}")
print(f"libinput custom-points ({sample_point_count}):")
print("\t", sample_points_str)
print("libinput test:")
print("\t", f"sudo ./builddir/libinput-debug-gui --verbose --set-profile=custom --set-custom-points=\"{sample_points_str}\" --set-custom-step={step:0.10f} --set-custom-type=motion")
print('\nxinput set-props commands:')
print(f'\txinput set-prop {xinput_device_id} "libinput Accel Profile Enabled" 0, 0, 1')
print(f'\txinput set-prop {xinput_device_id} "libinput Accel Custom Motion Points" {sample_points_str.replace(";", ", ")}')
print(f'\txinput set-prop {xinput_device_id} "libinput Accel Custom Motion Step" {step:0.10f}')
print('\nxinput libinput.conf Options:')
print('\tOption "AccelProfile" "custom"')
print(f'\tOption "AccelPointsMotion" "{sample_points_str.replace(";", " ")}"')
print(f'\tOption "AccelStepMotion" "{step:0.10f}"')
@mateidragony
Copy link

Beautiful thank you

@JustCauseWhyNot
Copy link

Is this replicating windows mouse sensitivity with or without enhanced pointer precision?

@yinonburgansky
Copy link
Author

Is this replicating windows mouse sensitivity with or without enhanced pointer precision?

With enhanced pointer precision

Unfortunately I don't have the exact formula windows 10 uses, so it might feel a bit off compared to windows 10.
you can try to manually change scale_x and scale_y values until you get close enough to the desired result, e.g.

scale_x = 3.5
scale_y = 2.7 

@JustCauseWhyNot
Copy link

Thank you very much for the answer. I'll mess around with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment