Skip to content

Instantly share code, notes, and snippets.

@timo
Created September 27, 2025 17:16
Show Gist options
  • Save timo/5dc9e5ddc3020c8f19dc2722ac762345 to your computer and use it in GitHub Desktop.
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
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