1 | # Copyright (c) 2010 Joshua Harlan Lifton.
|
---|
2 | # See LICENSE.txt for details.
|
---|
3 |
|
---|
4 | # TODO: add options to remap keys
|
---|
5 | # TODO: look into programmatically pasting into other applications
|
---|
6 |
|
---|
7 | "For use with a Microsoft Sidewinder X4 keyboard used as stenotype machine."
|
---|
8 |
|
---|
9 | # TODO: Change name to NKRO Keyboard.
|
---|
10 |
|
---|
11 | from plover.machine.base import StenotypeBase
|
---|
12 | from plover.oslayer import keyboardcontrol
|
---|
13 |
|
---|
14 | KEYSTRING_TO_STENO_KEY = {"a": "S-",
|
---|
15 | "q": "S-",
|
---|
16 | "w": "T-",
|
---|
17 | "s": "K-",
|
---|
18 | "e": "P-",
|
---|
19 | "d": "W-",
|
---|
20 | "r": "H-",
|
---|
21 | "f": "R-",
|
---|
22 | "c": "A-",
|
---|
23 | "v": "O-",
|
---|
24 | "t": "#",
|
---|
25 | "g": "*",
|
---|
26 | "y": "-F",
|
---|
27 | "h": "-R",
|
---|
28 | "m": "-E",
|
---|
29 | ",": "-U",
|
---|
30 | "u": "-P",
|
---|
31 | "j": "-B",
|
---|
32 | "i": "-L",
|
---|
33 | "k": "-G",
|
---|
34 | "o": "-T",
|
---|
35 | "l": "-S",
|
---|
36 | "p": "-D",
|
---|
37 | ";": "-Z",
|
---|
38 | "1": "#",
|
---|
39 | "2": "#",
|
---|
40 | "3": "#",
|
---|
41 | "4": "#",
|
---|
42 | "5": "#",
|
---|
43 | "6": "#",
|
---|
44 | "7": "#",
|
---|
45 | "8": "#",
|
---|
46 | "9": "#",
|
---|
47 | "0": "#",
|
---|
48 | "-": "#",
|
---|
49 | "=": "#",
|
---|
50 | }
|
---|
51 |
|
---|
52 |
|
---|
53 | class Stenotype(StenotypeBase):
|
---|
54 | """Standard stenotype interface for a Microsoft Sidewinder X4 keyboard.
|
---|
55 |
|
---|
56 | This class implements the three methods necessary for a standard
|
---|
57 | stenotype interface: start_capture, stop_capture, and
|
---|
58 | add_callback.
|
---|
59 |
|
---|
60 | """
|
---|
61 |
|
---|
62 | def __init__(self, params):
|
---|
63 | """Monitor a Microsoft Sidewinder X4 keyboard via X events."""
|
---|
64 | StenotypeBase.__init__(self)
|
---|
65 | self._keyboard_emulation = keyboardcontrol.KeyboardEmulation()
|
---|
66 | self._keyboard_capture = keyboardcontrol.KeyboardCapture()
|
---|
67 | self._keyboard_capture.key_down = self._key_down
|
---|
68 | self._keyboard_capture.key_up = self._key_up
|
---|
69 | self.suppress_keyboard(True)
|
---|
70 | self._down_keys = set()
|
---|
71 | self._released_keys = set()
|
---|
72 | self.arpeggiate = params['arpeggiate']
|
---|
73 |
|
---|
74 | def start_capture(self):
|
---|
75 | """Begin listening for output from the stenotype machine."""
|
---|
76 | self._keyboard_capture.start()
|
---|
77 | self._ready()
|
---|
78 |
|
---|
79 | def stop_capture(self):
|
---|
80 | """Stop listening for output from the stenotype machine."""
|
---|
81 | self._keyboard_capture.cancel()
|
---|
82 | self._stopped()
|
---|
83 |
|
---|
84 | def suppress_keyboard(self, suppress):
|
---|
85 | self._is_keyboard_suppressed = suppress
|
---|
86 | self._keyboard_capture.suppress_keyboard(suppress)
|
---|
87 |
|
---|
88 | def _key_down(self, event):
|
---|
89 | """Called when a key is pressed."""
|
---|
90 | if (self._is_keyboard_suppressed
|
---|
91 | and event.keystring is not None
|
---|
92 | and not self._keyboard_capture.is_keyboard_suppressed()):
|
---|
93 | self._keyboard_emulation.send_backspaces(1)
|
---|
94 | if event.keystring in KEYSTRING_TO_STENO_KEY:
|
---|
95 | self._down_keys.add(event.keystring)
|
---|
96 |
|
---|
97 | def _post_suppress(self, suppress, steno_keys):
|
---|
98 | """Backspace the last stroke since it matched a command.
|
---|
99 |
|
---|
100 | The suppress function is passed in to prevent threading issues with the
|
---|
101 | gui.
|
---|
102 | """
|
---|
103 | n = len(steno_keys)
|
---|
104 | if self.arpeggiate:
|
---|
105 | n += 1
|
---|
106 | suppress(n)
|
---|
107 |
|
---|
108 | def _key_up(self, event):
|
---|
109 | """Called when a key is released."""
|
---|
110 | if event.keystring in KEYSTRING_TO_STENO_KEY:
|
---|
111 | # Process the newly released key.
|
---|
112 | self._released_keys.add(event.keystring)
|
---|
113 | # Remove invalid released keys.
|
---|
114 | self._released_keys = self._released_keys.intersection(self._down_keys)
|
---|
115 |
|
---|
116 | # A stroke is complete if all pressed keys have been released.
|
---|
117 | # If we are in arpeggiate mode then only send stroke when spacebar is pressed.
|
---|
118 | send_strokes = bool(self._down_keys and
|
---|
119 | self._down_keys == self._released_keys)
|
---|
120 | if self.arpeggiate:
|
---|
121 | send_strokes &= event.keystring == ' '
|
---|
122 | if send_strokes:
|
---|
123 | steno_keys = [KEYSTRING_TO_STENO_KEY[k] for k in self._down_keys
|
---|
124 | if k in KEYSTRING_TO_STENO_KEY]
|
---|
125 | if steno_keys:
|
---|
126 | self._down_keys.clear()
|
---|
127 | self._released_keys.clear()
|
---|
128 | self._notify(steno_keys)
|
---|
129 |
|
---|
130 | @staticmethod
|
---|
131 | def get_option_info():
|
---|
132 | bool_converter = lambda s: s == 'True'
|
---|
133 | return {
|
---|
134 | 'arpeggiate': (False, bool_converter),
|
---|
135 | }
|
---|