Created
September 27, 2025 17:16
-
-
Save timo/5dc9e5ddc3020c8f19dc2722ac762345 to your computer and use it in GitHub Desktop.
fix erratic volume wheel on mouse with python-evdev and its uinput module
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import evdev | |
import evdev.ecodes as e | |
device_name = "Areson SPEEDLINK SCELUS Consumer Control" | |
TURN_AROUND_TIME = 0.2 | |
devices = [evdev.InputDevice(p) for p in evdev.list_devices()] | |
right_ones = [d for d in devices if d.name == device_name] | |
if not right_ones: | |
raise Exception(f"Could not find a device matching {device_name}") | |
if len(right_ones) > 1: | |
raise Exception(f"Found more than one device matching {device_name}: {right_ones}") | |
mydev = right_ones[0] | |
with mydev.grab_context(): | |
output_dev = evdev.uinput.UInput.from_device(mydev) | |
last_timestamp_by_keycode = {} | |
last_timestamp_by_keycode[e.KEY_VOLUMEUP] = 0 | |
last_timestamp_by_keycode[e.KEY_VOLUMEDOWN] = 0 | |
current_key_state_by_keycode = {} | |
current_key_state_by_keycode[e.KEY_VOLUMEUP] = 0 | |
current_key_state_by_keycode[e.KEY_VOLUMEDOWN] = 0 | |
output_dev.write(e.EV_KEY, e.KEY_VOLUMEUP, 0) | |
output_dev.write(e.EV_KEY, e.KEY_VOLUMEDOWN, 0) | |
for orig_event in mydev.read_loop(): | |
event = evdev.util.categorize(orig_event) | |
# forward everything we don't care about through | |
if not isinstance(event, evdev.events.KeyEvent): | |
output_dev.write_event(orig_event) | |
continue | |
if event.scancode not in [e.KEY_VOLUMEUP, e.KEY_VOLUMEDOWN]: | |
output_dev.write_event(orig_event) | |
continue | |
if event.keystate == 0: | |
# allow through a release event if we think the key | |
# is currently not released | |
if current_key_state_by_keycode[event.scancode] != 0: | |
current_key_state_by_keycode[event.scancode] = 0 | |
output_dev.write_event(orig_event) | |
continue | |
if event.keystate == 2: | |
print("did not expect a keystate of 2, ignoring this event!", event) | |
continue | |
# we have a down event of one of the two keys | |
# if it's been half a second since the last event, let it through | |
most_recent_event_timestamp = max(last_timestamp_by_keycode.values()) | |
elapsed = orig_event.timestamp() - most_recent_event_timestamp | |
print("last event was ", elapsed, " seconds ago.") | |
if elapsed > TURN_AROUND_TIME: | |
last_timestamp_by_keycode[event.scancode] = orig_event.timestamp() | |
current_key_state_by_keycode[event.scancode] = event.keystate | |
output_dev.write_event(orig_event) | |
print("letting through ", event) | |
continue | |
else: | |
if last_timestamp_by_keycode[event.scancode] == most_recent_event_timestamp: | |
last_timestamp_by_keycode[event.scancode] = orig_event.timestamp() | |
current_key_state_by_keycode[event.scancode] = event.keystate | |
output_dev.write_event(orig_event) | |
print("letting through! ", event) | |
continue | |
else: | |
print("apparently wrong direction! ", event) | |
print("event made it through the filter and was not accepted: ", event) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment