Created
January 19, 2018 01:41
-
-
Save t184256/f6eae48b1a9eb584f2498191ebc0a260 to your computer and use it in GitHub Desktop.
An admittedly messy keyboard layout remapper for a Japanese X220.
This file contains 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
#!/usr/bin/python3 | |
# Sorry everyone, it's a real mess. | |
import atexit | |
import inspect | |
import evdev | |
import evdev.ecodes as ec | |
MATCH = 'AT Translated Set 2 keyboard' | |
SHORT = { | |
'C_LOCK': 'CAPSLOCK', | |
'INS': 'INSERT', 'DEL': 'DELETE', | |
'LEFT': 'LEFT_ARROW', 'RIGHT': 'RIGHT_ARROW', | |
'UP': 'UP_ARROW', 'DOWN': 'DOWN_ARROW', | |
'LSHIFT': 'LEFTSHIFT', 'RSHIFT': 'RIGHTSHIFT', | |
'LCTRL': 'LEFTCTRL', 'RCTRL': 'RIGHTCTRL', | |
'LALT': 'LEFTALT', 'RALT': 'RIGHTALT', | |
'LWIN': 'LEFTWINDOWS', 'RWIN': 'RIGHTWINDOWS', | |
'PGUP': 'PAGE_UP', 'PGDN': 'PAGE_DOWN', | |
'BS': 'BACKSPACE', 'RET': 'ENTER', | |
'`': 'GRAVE', '\'': 'APOSTROPHE', '=': 'EQUAL', | |
',': 'COMMA', '.': 'DOT', '-': 'MINUS', | |
'/': 'SLASH', '\\': 'BACKSLASH', | |
';': 'SEMICOLON', | |
'[': 'LEFTBRACE', ']': 'RIGHTBRACE', | |
'#': None, | |
'CTRLESC': 'LEFTCTRL', | |
'SPS': 'RIGHTSHIFT', | |
'FN': 'WAKEUP', | |
'ENT': 'ENTER', | |
'WIN': 'LEFTMETA', | |
'ALT': 'LEFTALT', 'RA': 'RIGHTALT', 'CP': 'COMPOSE', | |
'XX1': 'MUHENKAN', 'XX2': 'HENKAN', 'XX3': 'KATAKANAHIRAGANA', | |
'LANG/LEAD': 'RIGHTALT', | |
} | |
ex = lambda k: SHORT[k] if k in SHORT else k | |
expand = lambda layout: [ex(k) for k in layout] | |
keyify = lambda s: eval('ec.KEY_' + s) if isinstance(s, str) else s | |
HARDWARE = r''' | |
ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 PAUSE | |
` 1 2 3 4 5 6 7 8 9 0 - = YEN BS | |
TAB Q W E R T Y U I O P [ ] | |
C_LOCK A S D F G H J K L ; ' \ RET | |
LSHIFT Z X C V B N M , . / RO RSHIFT | |
LCTRL FN WIN ALT XX1 SPACE XX2 XX3 RA CP RCTRL BACK | |
''' | |
QWERTY_RU = r''' | |
ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 PAUSE | |
` 1 2 3 4 5 # 6 7 8 9 0 - # # | |
TAB Q W E R T # Y U I O P [ | |
CTRLESC A S D F G # H J K L ; ' ] | |
Z X C V B # # N M , . / RSHIFT | |
# = WIN ALT BS LANG/LEAD ENT SPS # # # # | |
''' | |
COLEMAK = r''' | |
ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 PAUSE | |
` 1 2 3 4 5 # 6 7 8 9 0 - # # | |
TAB Q W F P G # J L U Y ; \ | |
CTRLESC A R S T D # H N E I O ' # | |
Z X C V B # # K M , . / RSHIFT | |
# = WIN ALT BS LANG/LEAD ENT SPS # # [ ] | |
''' | |
MODBUTTONS = { | |
'CTRLESC': 'ESC', | |
'SPS': 'SPACE', | |
'LANG/LEAD': 'SPACE', | |
} | |
BACK_PAIRS = dict(zip(COLEMAK.split(), HARDWARE.split())) | |
MODBUTTONS = {keyify(ex(BACK_PAIRS[orig])): keyify(to) | |
for orig, to in MODBUTTONS.items()} | |
LANGUAGE_SWITCH_KEY = keyify(BACK_PAIRS['LANG/LEAD']) | |
def make_layout(target, hardware): | |
target, hardware = target.split(), hardware.split() | |
remap_pairs = dict(zip(expand(hardware), expand(target))) | |
return {keyify(orig): keyify(to) for orig, to in remap_pairs.items()} | |
COLEMAK, QWERTY_RU = make_layout(COLEMAK, HARDWARE), make_layout(QWERTY_RU, HARDWARE) | |
def is_abort(ev): | |
return (ev.type == ec.EV_KEY and ev.code == ec.KEY_PAUSE and ev.value == 1) | |
devices = [evdev.InputDevice(fn).name for fn in evdev.list_devices()] | |
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] | |
kbd = [d for d in devices if MATCH in d.name][0] | |
atexit.register(kbd.ungrab) | |
kbd.grab() | |
current_layout = COLEMAK | |
prev_ev = None | |
solo = None | |
with evdev.UInput.from_device(kbd, name='kbdremap') as ui: | |
for ev in kbd.read_loop(): | |
if is_abort(ev): | |
break | |
sec, usec = ev.sec, ev.usec | |
etype, code, value = ev.type, ev.code, ev.value | |
if etype == ec.EV_KEY: | |
#print('-', evdev.categorize(ev), ev.value) | |
if code in current_layout: | |
remap = current_layout[ev.code] | |
if remap is None: | |
pass | |
else: | |
if ev.code not in MODBUTTONS: | |
code = remap | |
ui.write(etype, code, value) | |
if value == 1: | |
solo = None | |
else: | |
if ev.code == LANGUAGE_SWITCH_KEY: | |
current_layout = QWERTY_RU if ev.value else COLEMAK | |
mod_code, solo_code = remap, MODBUTTONS[ev.code] | |
if solo != solo_code and value == 1: | |
solo = solo_code | |
ui.write(etype, mod_code, value) | |
elif solo == solo_code and ev.value == 0: | |
solo = None | |
ui.write(etype, mod_code, 0) | |
ui.write(etype, solo_code, 1) | |
ui.write(etype, solo_code, 0) | |
elif solo == solo_code and value == 2: | |
ui.write(etype, mod_code, value) | |
solo = None # abort soloing on long presses | |
else: | |
solo = None | |
ui.write(etype, mod_code, value) | |
else: | |
ui.write(etype, code, value) | |
prev_ev = ev | |
else: | |
ui.write(etype, code, value) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment