Implement keymap support into input-read, along with directly writing to devices without piping to input-create

This commit is contained in:
moparisthebest 2013-07-18 23:12:12 -04:00
parent 5b62ccd15a
commit aced80c40d
4 changed files with 270 additions and 21 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
**.pyc
uinputmapper/uinput_gen.py

View File

@ -12,28 +12,42 @@ try:
except ImportError:
import pickle
import optparse
import argparse
_usage = 'input-read /dev/input/event<0> ... /dev/input/event<N>'
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<N>')
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()

36
keymaps/dvorak.py Normal file
View File

@ -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
""",
]

131
uinputmapper/keymapper.py Normal file
View File

@ -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