-
Notifications
You must be signed in to change notification settings - Fork 0
/
inputEventCreator.py
257 lines (183 loc) · 9.21 KB
/
inputEventCreator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import time
from evdev import UInput, ecodes as e
import config
from operator import itemgetter
from keymaps import modKeys, sModKeys, outputMap, inputMap
import warnings
# Create an event source that can write input events to the system
ui = UInput()
# Create an inputQueue where input events (with key coordinates) are stored
inputQueue = []
# Turn off numlock (program starts with numlock on automatically)
ui.write(e.EV_KEY, e.KEY_NUMLOCK, 1)
ui.write(e.EV_KEY, e.KEY_NUMLOCK, 0)
ui.syn()
def add_input_to_queue(evdevEvent):
# Input from the key device is grabbed as evdev events
# Process this event into a dictionary that
keycode = evdevEvent.code
# Catch unexpected keys
try:
coord = inputMap[keycode]
if coord:
eventTime = evdevEvent.timestamp()
keyState = evdevEvent.value
inputQueue.append({'coord': coord, 'time': eventTime, 'state': keyState, 'keycode': keycode})
except KeyError:
warnings.warn('Unexpected input')
pass
def process_queue():
# Sort the queue by time of events
global inputQueue
inputQueue = sorted(inputQueue, key=itemgetter('time'))
(coord_list, indices_to_delete, mark_used) = process_queue_helper(inputQueue)
write_from_coords(coord_list)
for i in mark_used:
inputQueue[i]['used'] = True
for i in sorted(indices_to_delete, reverse=True):
del inputQueue[i]
def process_queue_helper(queue):
nKeys = len(queue)
# By default send nothing, delete nothing, and mark nothing unless explicitly stated
coord_list = ()
indices_to_delete = ()
mark_used = ()
# Return if input queue is empty
if nKeys == 0:
return (),(),()
# If the top event on the queue is a key release action, just delete the entry.
if queue[0]['state'] == 0:
return (),(0,),()
# Get which type of key the first key is
if queue[0]['coord'] in sModKeys.values():
keyType = 'SMOD'
elif queue[0]['coord'] in modKeys.values():
keyType = 'MOD'
else:
keyType = 'NORMAL'
if nKeys == 1:
# Only one event in queue
# Output the key if it is NORMAL and sufficient time waiting for key chords has passed
if keyType == 'NORMAL' and time.time() - queue[0]['time'] > 2 * config.chordTime:
coord_list = (queue[0]['coord'],)
indices_to_delete = (0,)
# Otherwise, just wait for more key presses to come in
else: # More than 1 event in the queue
# Do different things depending on which kind of key this is
if keyType == 'NORMAL':
# If the key type is NORMAL, then one of two things can happen, either the key is by itself or
# part of a chord
# If the second event is just releasing the button (no chord has occurred)
if queue[1]['coord'] == queue[0]['coord']:
# Return just key
coord_list = (queue[0]['coord'],)
indices_to_delete = (0,1)
else: # The event is some other key
# This is a key chord if the second press is a key press (not release) and occurs within the key chord time
if queue[1]['state'] == 1 and queue[1]['time'] - queue[0]['time'] <= config.chordTime:
# Call process_queue_helper recursively to allow for arbitrarily big key chords
[sub_coords, sub_inds, sub_marks] = process_queue_helper(queue[1:])
# Only send the key chord if the recursive call returns anything
if sub_coords:
coord_list = (queue[0]['coord'],) + sub_coords
indices_to_delete = (0,) + tuple(i + 1 for i in sub_inds)
else:
# Just delete what the subroutine says to
indices_to_delete = tuple(i + 1 for i in sub_inds)
# Otherwise, just return the first key
else:
coord_list = (queue[0]['coord'],)
indices_to_delete = (0,)
elif keyType == 'MOD':
# MOD keys must be pressed and held for them to stay active
# If the next key is just the mod key being released, then send the MOD key by itself
# That is, unless it has been used with some other keys first
if queue[1]['coord'] == queue[0]['coord']:
# If the MOD key has been used in conjunction with other keys, just delete the key down and up events
if 'used' in queue[0] and queue[0]['used']:
indices_to_delete = (0,1)
# Otherwise play the MOD key
else:
coord_list = (queue[0]['coord'],)
indices_to_delete = (0,1)
# Recursively call the this process_queue_helper function to process key coords that occur with the MOD key attached
else:
[sub_coords, sub_inds, sub_marks] = process_queue_helper(queue[1:])
# Only send key input if the recursive call returns anything
if sub_coords:
coord_list = (queue[0]['coord'],) + sub_coords
# Mark the mod key as used so it won't return its own input later
mark_used = (0,)
indices_to_delete = tuple(i + 1 for i in sub_inds)
mark_used = mark_used + tuple(i + 1 for i in sub_marks)
elif keyType == 'SMOD':
# If the SMOD key is pressed and released, it acts like it sticks
# allowing another key or key chord to be pressed afterwards for smodStickTime
if queue[1]['coord'] == queue[0]['coord']:
# If the SMOD key has already been used in conjunction with other (i.e. not in a sticky way),
# just delete the key down and up events
if 'used' in queue[0] and queue[0]['used']:
indices_to_delete = (0, 1)
elif time.time() - queue[0]['time'] <= config.smodStickTime:
# Recursively call the this process_queue_helper function to process the first key or key chord that occurs after the SMOD key
[sub_coords, sub_inds, sub_marks] = process_queue_helper(queue[2:])
if sub_coords:
coord_list = (queue[0]['coord'],) + sub_coords
indices_to_delete = (0,1) + tuple(i + 2 for i in sub_inds)
else:
indices_to_delete = tuple(i + 2 for i in sub_inds)
mark_used = tuple(i + 2 for i in sub_marks)
else: # Time has expired, just play the SMOD key by itself
coord_list = (queue[0]['coord'],)
indices_to_delete = (0,1)
else: # SMOD key is being held and acts just like a MOD key
[sub_coords, sub_inds, sub_marks] = process_queue_helper(queue[1:])
# Only send key input if the recursive call returns anything
if sub_coords:
coord_list = (queue[0]['coord'],) + sub_coords
# Mark the SMOD key as used so it won't act sticky or return its own input later
mark_used = (0,)
indices_to_delete = tuple(i + 1 for i in sub_inds)
mark_used = mark_used + tuple(i + 1 for i in sub_marks)
return coord_list, indices_to_delete, mark_used
def write_from_coords(coord_list):
# Output the key stroke events to the computer
# Only do this if the coord_list is not empty
if coord_list:
# Turn the coordinates into their appropriate list of key names to be outputted
ecode_list = get_ecode_list(coord_list)
# Get the integer key value for each key name (e.g., KEY_A -> 30)
ecodes = [e.ecodes[str] for str in ecode_list if str]
# First press down each key sequentially
for ecode in ecodes:
ui.write(e.EV_KEY, ecode, 1)
# Then release each key sequentially
for ecode in ecodes:
ui.write(e.EV_KEY, ecode, 0)
# Now send these writes to the system
ui.syn()
def get_ecode_list(coord_list):
# Return [] if coord_list is empty
if not coord_list:
return []
# Catch Key Errors, if some key has an undefined output
try:
# Searches through the keymap for the given chord
if coord_list in outputMap:
return outputMap[coord_list]
else:
# If not directly in the keymap test the use of any of the keys as modifiers and then pull them out front
for key_coord in coord_list:
if key_coord in modKeys.values() or key_coord in sModKeys.values():
coord_list = tuple(coord for coord in coord_list if coord != key_coord)
return outputMap[(key_coord,)] + get_ecode_list(coord_list)
# Otherwise, just return the each key independently
else:
return_list = []
for coord in coord_list:
return_list += outputMap[(coord,)]
return return_list
except KeyError:
warnings.warn('Some undefined key has been used')
return ()
pass