Skip to content

Commit

Permalink
Merge pull request #114 from balshetzer/master
Browse files Browse the repository at this point in the history
Improve keyboard input and output in windows
  • Loading branch information
balshetzer committed Aug 1, 2013
2 parents 3b84cbf + bfcc819 commit 28ea021
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 23 deletions.
65 changes: 42 additions & 23 deletions plover/oslayer/winkeyboardcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,58 @@
import pythoncom
import threading
import win32api
import win32com.client
import win32con
from pywinauto.SendKeysCtypes import SendKeys as _SendKeys

def SendKeys(s):
_SendKeys(s, with_spaces=True)

# For the purposes of this class, we'll only report key presses that
# result in these outputs in order to exclude special key combos.

WATCHED_KEYS = set(['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-',
'=', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[',
']', '\\', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
';', '\'', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.',
'/', ' '])
KEY_TO_ASCII = {
41: '`', 2: '1', 3: '2', 4: '3', 5: '4', 6: '5', 7: '6', 8: '7',
9: '8', 10: '9', 11: '0', 12: '-', 13: '=', 16: 'q',
17: 'w', 18: 'e', 19: 'r', 20: 't', 21: 'y', 22: 'u', 23: 'i',
24: 'o', 25: 'p', 26: '[', 27: ']', 43: '\\',
30: 'a', 31: 's', 32: 'd', 33: 'f', 34: 'g', 35: 'h', 36: 'j',
37: 'k', 38: 'l', 39: ';', 40: '\'', 44: 'z', 45: 'x',
46: 'c', 47: 'v', 48: 'b', 49: 'n', 50: 'm', 51: ',',
52: '.', 53: '/', 57: ' ',
}


class KeyboardCapture(threading.Thread):
"""Listen to all keyboard events."""

CONTROL_KEYS = set(('Lcontrol', 'Rcontrol'))
SHIFT_KEYS = set(('Lshift', 'Rshift'))
ALT_KEYS = set(('Lmenu', 'Rmenu'))

def __init__(self):
threading.Thread.__init__(self)

self.suppress_keyboard(True)

self.shift = False
self.ctrl = False
self.alt = False

# NOTE(hesky): Does this need to be more efficient and less
# general if it will be called for every keystroke?
def on_key_event(func_name, event):
if not event.Injected and chr(event.Ascii) in WATCHED_KEYS:
getattr(self, func_name,
lambda x: True)(KeyboardEvent(event.Ascii))
return not self.is_keyboard_suppressed()
else:
return True
ascii = KEY_TO_ASCII.get(event.ScanCode, None)
if not event.Injected:
if event.Key in self.CONTROL_KEYS:
self.ctrl = func_name == 'key_down'
if event.Key in self.SHIFT_KEYS:
self.shift = func_name == 'key_down'
if event.Key in self.ALT_KEYS:
self.alt = func_name == 'key_down'
if ascii and not self.ctrl and not self.alt and not self.shift:
getattr(self, func_name, lambda x: True)(KeyboardEvent(ascii))
return not self.is_keyboard_suppressed()

return True

self.hm = pyHook.HookManager()
self.hm.KeyDown = functools.partial(on_key_event, 'key_down')
Expand Down Expand Up @@ -139,18 +162,14 @@ class KeyboardEmulation:
"F16": "{F16}",
}

def __init__(self):
# Todo(Hesky): Is this hacky? Should we use keybd_event (deprecated) or
# its replacement SendInput?
self.shell = win32com.client.Dispatch("WScript.Shell")
self.SPECIAL_CHARS_PATTERN = re.compile(r'([{}[\]()+^%~])')

SPECIAL_CHARS_PATTERN = re.compile(r'([{}[\]()+^%~])')

def send_backspaces(self, number_of_backspaces):
for _ in xrange(number_of_backspaces):
self.shell.SendKeys(self.keymap_single['BackSpace'])
SendKeys(self.keymap_single['BackSpace'])

def send_string(self, s):
self.shell.SendKeys(re.sub(self.SPECIAL_CHARS_PATTERN, r'{\1}', s))
SendKeys(re.sub(self.SPECIAL_CHARS_PATTERN, r'{\1}', s))

def send_key_combination(self, s):
combo = []
Expand All @@ -164,14 +183,14 @@ def send_key_combination(self, s):
pass
else:
combo.append(token)
self.shell.SendKeys(''.join(combo))
SendKeys(''.join(combo))


class KeyboardEvent(object):
"""A keyboard event."""

def __init__(self, char):
self.keystring = chr(char)
self.keystring = char

if __name__ == '__main__':
kc = KeyboardCapture()
Expand Down
1 change: 1 addition & 0 deletions windows/DevReadme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ To create a windows dev environment you need to install:
- pywinusb
- mock
- pyhook
- pywinauto

The starting point for plover is application\plover. You will need to add the root dir of the distribution to the PYTHONPATH environment variable. Alternatively, you can temporarily move application\plover to the root and change its name to launch.py (it may not be called plover.py) and call it from there.

Expand Down

0 comments on commit 28ea021

Please sign in to comment.