Skip to content

Instantly share code, notes, and snippets.

Created January 19, 2018 03:26
Show Gist options
  • Save t184256/f4994037a2a204774ef3b9a2b38736dc to your computer and use it in GitHub Desktop.
Save t184256/f4994037a2a204774ef3b9a2b38736dc to your computer and use it in GitHub Desktop.
A thoroughly annotated example on keyboard remapping with evdev/uinput.
# CC0, originally written by t184256.
# This is an example Python program for Linux that remaps a keyboard.
# The events (key presses releases and repeats), are captured with evdev,
# and then injected back with uinput.
# This approach should work in X, Wayland, anywhere!
# Also it is not limited to keyboards, may be adapted to any input devices.
# The program should be easily portable to other languages or extendable to
# run really any code in 'macros', e.g., fetching and typing current weather.
# The ones eager to do it in C can take a look at (overengineered) caps2esc:
# Import necessary libraries.
import atexit
# You need to install evdev with a package manager or pip3.
import evdev # (sudo pip3 install evdev)
# Define an example dictionary describing the remaps.
# Let's swap A and B...
evdev.ecodes.KEY_A: evdev.ecodes.KEY_B,
evdev.ecodes.KEY_B: evdev.ecodes.KEY_A,
# ... and make the right Shift into a second Space.
evdev.ecodes.KEY_RIGHTSHIFT: evdev.ecodes.KEY_SPACE,
# We'll also remap CapsLock to Control when held ...
evdev.ecodes.KEY_CAPSLOCK: evdev.ecodes.KEY_LEFTCTRL,
# ... but to Esc when pressed solo, xcape style! See below.
# The names can be found with evtest or in evdev docs.
# The keyboard name we will intercept the events for. Obtainable with evtest.
MATCH = 'AT Translated Set 2 keyboard'
# Find all input devices.
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
# Limit the list to those containing MATCH and pick the first one.
kbd = [d for d in devices if MATCH in][0]
atexit.register(kbd.ungrab) # Don't forget to ungrab the keyboard on exit!
kbd.grab() # Grab, i.e. prevent the keyboard from emitting original events.
soloing_caps = False # A flag needed for CapsLock example later.
# Create a new keyboard mimicking the original one.
with evdev.UInput.from_device(kbd, name='kbdremap') as ui:
for ev in kbd.read_loop(): # Read events from original keyboard.
if ev.type == evdev.ecodes.EV_KEY: # Process key events.
if ev.code == evdev.ecodes.KEY_PAUSE and ev.value == 1:
# Exit on pressing PAUSE.
# Useful if that is your only keyboard. =)
# Also if you bind that script to PAUSE, it'll be a toggle.
elif ev.code in REMAP_TABLE:
# Lookup the key we want to press/release instead...
remapped_code = REMAP_TABLE[ev.code]
# And do it.
ui.write(evdev.ecodes.EV_KEY, remapped_code, ev.value)
# Also, remap a 'solo CapsLock' into an Escape as promised.
if ev.code == evdev.ecodes.KEY_CAPSLOCK and ev.value == 0:
if soloing_caps:
# Single-press Escape.
ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_ESC, 1)
ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_ESC, 0)
# Passthrough other key events unmodified.
ui.write(evdev.ecodes.EV_KEY, ev.code, ev.value)
# If we just pressed (or held) CapsLock, remember it.
# Other keys will reset this flag.
soloing_caps = (ev.code == evdev.ecodes.KEY_CAPSLOCK and ev.value)
# Passthrough other events unmodified (e.g. SYNs).
ui.write(ev.type, ev.code, ev.value)
Copy link

t184256 commented Apr 27, 2023

Hey, this works like a charm, thanks! Is it possible to map a key to a combination of keys (like A to Shift+A)? If yes, how?

The way you press them on your keyboard. So, something like

ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_RIGHTSHIFT, 1)
ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_A, 1)
ui.write(evdev.ecodes.EV_SYN, evdev.ecodes.SYN_REPORT, 0)  # not sure if needed, may work without it
ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_A, 0)
ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_RIGHTSHIFT, 0)

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