diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f1f3ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**.pyc +uinputmapper/uinput_gen.py diff --git a/input-read b/input-read index 0f9cb12..85389c6 100755 --- a/input-read +++ b/input-read @@ -12,28 +12,42 @@ try: except ImportError: import pickle -import optparse +import argparse _usage = 'input-read /dev/input/event<0> ... /dev/input/event' -parser = optparse.OptionParser(description='Read input devices.', - usage = _usage, - version='0.01') -parser.add_option('-D', '--dump', action='store_false', +parser = argparse.ArgumentParser(description='Read input devices.', + usage = _usage) +parser.add_argument('--version', action='version', version='0.01') +parser.add_argument('-D', '--dump', action='store_false', default=True, help='Dump will marshall all the events to stdout') -parser.add_option('-v', '--verbose', action='store_true', +parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Enable verbose mode (do not combine with -D)') -parser.add_option('-C', '--compat', action='store_true', +parser.add_argument('-k', '--keymap', dest='keymap', + help='Python keymap configuration to source') + +parser.add_argument('-C', '--compat', action='store_true', help='Enable compatibility mode; for Python < 2.7') -args, input_file = parser.parse_args() +parser.add_argument('-ng', '--no-grab', action='store_true', + default=False, help='Will not grab input devices, so other programs like X might still read from them') -if len(input_file) == 0: - parser.print_help() - exit(0) +parser.add_argument('-d', '--direct', action='store_true', + default=False, help='Will directly create input devices to send events to, instead of piping to input-create') +parser.add_argument('-c', '--config', metavar='config', nargs='*', + help='Path to config files you would normally send into input-create, implies --direct') + +parser.add_argument('input_file', metavar='input_file', nargs='+', + help='/dev/input/event<0> ... /dev/input/event') + +args = parser.parse_args() +args.has_config = args.config != None and len(args.config) > 0 + +#print 'args:',args +#exit(0) # Open input devices -fs = map(InputDevice, input_file) +fs = map(InputDevice, args.input_file) # Create configuration config = {} @@ -42,6 +56,13 @@ for idx, f in enumerate(fs): config.update(c) +if args.has_config: + # Allow configurations to change our current configuration + args.direct = True + for path in args.config: + config_merge = imp.load_source('', path).config_merge + config_merge(config) + if args.verbose: pretty_conf_print(config) @@ -62,17 +83,42 @@ if args.dump: d = f.get_exposed_events() for k, v in d.iteritems(): print k + ':', ', '.join(v) - else: # Dump initial information over pickle to stdout p = pickle.Pickler(sys.stdout) - p.dump(len(fs)) - p.dump(config) - sys.stdout.flush() +# parse keymap +if args.keymap: + from uinputmapper.keymapper import parse_keymap + runtime_keymaps, active_keymap_index, revert_keymap_index, active_keymap, revert_default_code, switch_layout_codes, switch_layout_mode, num_codes_to_index = parse_keymap(args, rev_event_keys, event_keys) + +if args.direct: + # setup a new input device + m = KeyMapper(config) + + # Get number of output devices (determined from config) + from uinputmapper.mapper import get_exported_device_count + nofd = get_exported_device_count(config) + # Create and expose uinput devices + ofs = [] + for fd in xrange(nofd): + d = UInputDevice() + m.expose(d, fd) + d.setup('keymapper input device'+str(fd)) + ofs.append(d) + +# grab all inputs so nothing else reads them :) +if not args.no_grab: + for idx, f in enumerate(fs): + f.grab() + +# try to free as much memory as we can before entering loop +from gc import collect as gc_collect +gc_collect() + while True: events = pp.poll() @@ -90,20 +136,54 @@ while True: ev = f.next_event() - if args.dump: + if args.keymap: + #if ev.type == 4: # MSC_SCAN, works for some keyboards (serio) but not others (usb), not needed if we grab the input + # ev.value = active_keymap[ev.value] + if ev.type == 1: # EV_KEY + ev.code = active_keymap[ev.code] + # we want some events to happen when a key is first pressed or lifted + if ev.value == 1: # pressed + if ev.code == revert_default_code: + active_keymap = runtime_keymaps[revert_keymap_index] + elif ev.code in switch_layout_codes: + switch_layout_codes[ev.code] = True + # so loop through switch_layout_codes and see if all are True + switch_layout_mode = True + for x,y in switch_layout_codes.iteritems(): + switch_layout_mode &= y + elif switch_layout_mode and ev.code in num_codes_to_index: + active_keymap_index = num_codes_to_index[ev.code] + active_keymap = runtime_keymaps[active_keymap_index] + # and continue so as to not send this key + continue + elif ev.value == 0: # lifted + if ev.code == revert_default_code: + active_keymap = runtime_keymaps[active_keymap_index] + elif ev.code in switch_layout_codes: + switch_layout_codes[ev.code] = False + switch_layout_mode = False + + if args.direct: + # try to fire our own events? + if args.has_config: + idx, ev = m.map_event(ev, i) + d = ofs[idx] + else: + d = ofs[i] + d.fire_event(ev) + elif args.dump: try: + #print 'ev.type:', ev.type print i, ev.time.tv_sec, ev.time.tv_usec - s = '%s %s %d' % (rev_events[ev.type], - rev_event_keys[ev.type][ev.code], ev.value) + s = '%s(%d) %s(%d) %d' % (rev_events[ev.type], ev.type, + rev_event_keys[ev.type][ev.code], ev.code, ev.value) print 'Event type:', s except KeyError: pass - else: if not args.compat: p.dump((i, ev)) else: p.dump((i, (ev.time.tv_sec, ev.time.tv_usec, ev.type, ev.code, ev.value))) - sys.stdout.flush() diff --git a/keymaps/dvorak.py b/keymaps/dvorak.py new file mode 100644 index 0000000..e7c63cf --- /dev/null +++ b/keymaps/dvorak.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# pressing all of these keys along with a number key representing the index of keymaps changes the layout +# ie, in this case pressing both and 0 would go QWERTY, while both and 1 would go dvorak +switch_layout_keys = ['LEFTSHIFT','RIGHTSHIFT'] + +# pressing QWERTY reverts to the index specified in revert_keymap_index for only the duration of the pressing +# used so QWERTY shortcuts like Ctrl+C still work +revert_default_key = 'LEFTCTRL' +revert_keymap_index = 0 + +# this is the default index to use when the program first starts +# in this case, 1 means Dvorak +default_keymap_index = 1 + +# these are the keymaps available, you can add as many as you want or re-order them, just be aware the mapping is +# always done from the first one to all subsequent ones, so you probably want to leave QWERTY or similar up top +keymaps = [ + # default key layout, QWERTY in this case + """ + ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK, + GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSPC, INS, HOME,PGUP, NLCK,PSLS,PAST,PMNS, + TAB, Q, W, E, R, T, Y, U, I, O, P, LBRC,RBRC,BSLS, DEL, END, PGDN, P7, P8, P9, + CAPS,A, S, D, F, G, H, J, K, L, SCLN,QUOT, ENT, P4, P5, P6, PPLS, + LSFT,Z, X, C, V, B, N, M, COMM,DOT, SLSH, RSFT, UP, P1, P2, P3, + LCTL,LGUI,LALT, SPC, RALT,RGUI,APP, RCTL, LEFT,DOWN,RGHT, P0, PDOT,PENT + """, + # Dvorak http://en.wikipedia.org/wiki/Dvorak_Simplified_Keyboard + """ + ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PSCR,SLCK,BRK, + GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, LBRC,RBRC,BSPC, INS, HOME,PGUP, NLCK,PSLS,PAST,PMNS, + TAB, QUOT,COMM,DOT, P, Y, F, G, C, R, L, SLSH,EQL, BSLS, DEL, END, PGDN, P7, P8, P9, + CAPS,A, O, E, U, I, D, H, T, N, S, MINS, ENT, P4, P5, P6, PPLS, + LSFT,SCLN,Q, J, K, X, B, M, W, V, Z, RSFT, UP, P1, P2, P3, + LCTL,LGUI,LALT, SPC, RALT,RGUI,APP, RCTL, LEFT,DOWN,RGHT, P0, PDOT,PENT + """, + ] diff --git a/uinputmapper/keymapper.py b/uinputmapper/keymapper.py new file mode 100644 index 0000000..4877bab --- /dev/null +++ b/uinputmapper/keymapper.py @@ -0,0 +1,131 @@ +# encoding: utf-8 +""" +Module to help with creating fast keycode mapping arrays +""" + +def parse_keymap(args, rev_event_keys, event_keys): + """ + Reads in a keymap configuration and returns structures to implement it + """ + keymap_config = {} + execfile(args.keymap, keymap_config) + switch_layout_keys = keymap_config['switch_layout_keys'] + revert_default_key = keymap_config['revert_default_key'] + revert_keymap_index = keymap_config['revert_keymap_index'] + default_keymap_index = keymap_config['default_keymap_index'] + keymaps = keymap_config['keymaps'] + #print keymap_config + #exit(0) + #import pprint + short_to_long = { + 'APP':'COMPOSE', + 'BRK':'BREAK', + 'BSLS':'BACKSLASH', + 'BSPC':'BACKSPACE', + 'CAPS':'CAPSLOCK', + 'COMM':'COMMA', + 'DEL':'DELETE', + 'ENT':'ENTER', + 'EQL':'EQUAL', + 'GRV':'GRAVE', + 'INS':'INSERT', + 'LALT':'LEFTALT', + 'LBRC':'LEFTBRACE', + 'LCTL':'LEFTCTRL', + 'LGUI':'LEFTMETA', + 'LSFT':'LEFTSHIFT', + 'MINS':'MINUS', + 'NLCK':'NUMLOCK', + 'P0':'KP0', + 'P1':'KP1', + 'P2':'KP2', + 'P3':'KP3', + 'P4':'KP4', + 'P5':'KP5', + 'P6':'KP6', + 'P7':'KP7', + 'P8':'KP8', + 'P9':'KP9', + 'PAST':'KPASTERISK', + 'PDOT':'KPDOT', + 'PENT':'KPENTER', + 'PGDN':'PAGEDOWN', + 'PGUP':'PAGEUP', + 'PMNS':'KPMINUS', + 'PPLS':'KPPLUS', + 'PSCR':'SYSRQ',#'PRINT', # not sure? + 'PSLS':'KPSLASH', + 'QUOT':'APOSTROPHE', + 'RALT':'RIGHTALT', + 'RBRC':'RIGHTBRACE', + 'RCTL':'RIGHTCTRL', + 'RGHT':'RIGHT', + 'RGUI':'RIGHTMETA', + 'RSFT':'RIGHTSHIFT', + 'SCLN':'SEMICOLON', + 'SLCK':'SCROLLLOCK', + 'SLSH':'SLASH', + 'SPC':'SPACE', + } + keymap_list = [] + for keymap in keymaps: + key_list = [] + keymap_list.append(key_list) + for key in keymap.split(','): + key = key.strip() + new_key = 'KEY_'+short_to_long.get(key, key) + if args.dump and new_key not in event_keys[1]: # todo: probably should exit with some helpful error here? + #print 'Key', key, 'does not exist!' + #print "'", key, "':''," + print "'%s':''," % key + key_list.append(new_key) + + #pprint.pprint(keymap_list) + #exit(0) + mykeymaps = [] + default_keymap = keymap_list[0] + keymap_range = range(0, len(default_keymap)) + from array import array + for keymap in keymap_list: + mykeymap = {} + mykeymaps.append(mykeymap) + for y in keymap_range: + if default_keymap[y] != keymap[y]: + mykeymap[default_keymap[y]] = keymap[y] + + #pprint.pprint(mykeymaps) + #exit(0) + #print 'mykeymap: ', mykeymap + #print 'rev_event_keys[1]: ', rev_event_keys[1] + #print 'event_keys[1]: ', event_keys[1] + #print 'event_keys[1]: ' + #pprint.pprint(event_keys[1]) + #exit(0) + # convert mykeymap once, at startup, to a faster array, possibly using a little more memory + keycnt_range = range(0, event_keys[1]['KEY_CNT']) + runtime_keymaps = [] + for mykeymap in mykeymaps: + mycodemap = array('H') + runtime_keymaps.append(mycodemap) + for x in keycnt_range: + key = rev_event_keys[1].get(x, 'NO_KEY_EXISTS_FOR_THIS_INDEX') + value = event_keys[1].get(mykeymap.get(key, key), x) + mycodemap.append(value) + #print 'mycodemap: ', mycodemap + #pprint.pprint(runtime_keymaps) + #exit(0) + active_keymap_index = default_keymap_index + revert_default_code = event_keys[1]['KEY_'+revert_default_key] + active_keymap = runtime_keymaps[active_keymap_index] + + switch_layout_codes = {} + for key in switch_layout_keys: + switch_layout_codes[event_keys[1]['KEY_'+key]] = False + switch_layout_mode = False + num_codes_to_index = {} + for x in range(0, len(runtime_keymaps)): + num_codes_to_index[event_keys[1].get('KEY_'+str(x))] = x + #pprint.pprint(num_codes_to_index) + #exit(0) + + return runtime_keymaps, active_keymap_index, revert_keymap_index, active_keymap, revert_default_code, switch_layout_codes, switch_layout_mode, num_codes_to_index