forked from akkana/scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpykey.py
executable file
·265 lines (231 loc) · 7.26 KB
/
pykey.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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/usr/bin/env python
"""
Simulate keypresses under X. A simpler, Python version of Crikey,
http://shallowsky.com/software/crikey/
"""
# Copyright 2018 by Akkana Peck; share and enjoy under the GPLv2 or later.
import Xlib.display
import Xlib.X
import Xlib.XK
import Xlib.protocol.event
UseXTest = True
try:
import Xlib.ext.xtest
except ImportError:
UseXTest = False
print "no XTest extension; using XSendEvent"
import sys, time
display = None
def init_display():
global display, UseXTest
display = Xlib.display.Display()
# window = display.get_input_focus()._data["focus"]
if UseXTest and not display.query_extension("XTEST"):
UseXTest = False
special_X_keysyms = {
' ': "space",
'\t': "Tab",
'\n': "Return", # for some reason this needs to be cr, not lf
'\r': "Return",
'\e': "Escape",
'!': "exclam",
'#': "numbersign",
'%': "percent",
'$': "dollar",
'&': "ampersand",
'"': "quotedbl",
'\'': "apostrophe",
'(': "parenleft",
')': "parenright",
'*': "asterisk",
'=': "equal",
'+': "plus",
',': "comma",
'-': "minus",
'.': "period",
'/': "slash",
':': "colon",
';': "semicolon",
'<': "less",
'>': "greater",
'?': "question",
'@': "at",
'[': "bracketleft",
']': "bracketright",
'\\': "backslash",
'^': "asciicircum",
'_': "underscore",
'`': "grave",
'{': "braceleft",
'|': "bar",
'}': "braceright",
'~': "asciitilde"
}
# A few characters that aren't easy to map:
special_X_keycodes = {
'b' : 22,
'n' : 36,
't' : 23
}
def get_keysym(ch):
keysym = Xlib.XK.string_to_keysym(ch)
if keysym == 0:
# Unfortunately, although this works to get the correct keysym
# i.e. keysym for '#' is returned as "numbersign"
# the subsequent display.keysym_to_keycode("numbersign") is 0.
keysym = Xlib.XK.string_to_keysym(special_X_keysyms[ch])
return keysym
def is_shifted(ch):
'''Return False if the character isn't shifted.
If it is, return the unshifted character.
'''
if ch.isupper():
return ch.lower()
if "~!@#$%^&*()_+{}|:\"<>?".find(ch) >= 0:
return ch # XXX There's no way to easily map these
return False
def char_to_keycode(ch):
keysym = get_keysym(ch)
keycode = display.keysym_to_keycode(keysym)
if keycode == 0:
print "Sorry, can't map", ch
return keycode
# Bit masks for modifier keys
MOD_SHIFT = 1
MOD_CTRL = 2
MOD_ALT = 4
MOD_META = 8
# Get these from xev:
SHIFT_KEYCODE = 50
CTRL_KEYCODE = 37
ALT_KEYCODE = 64
META_KEYCODE = 133
def send_modifier(keycode, down):
"""This only applies with UseXTest.
With Xlib.protocol.event, modifiers are flags on each key.
"""
print "send_modifier", keycode, down
if not UseXTest:
return
if down:
Xlib.ext.xtest.fake_input(display, Xlib.X.KeyPress, keycode)
else:
Xlib.ext.xtest.fake_input(display, Xlib.X.KeyRelease, keycode)
def send_key(keycode, shift_down, ctrl_down, alt_down, meta_down):
print "send_key", keycode, shift_down, ctrl_down, alt_down, meta_down
if UseXTest:
Xlib.ext.xtest.fake_input(display, Xlib.X.KeyPress, keycode)
Xlib.ext.xtest.fake_input(display, Xlib.X.KeyRelease, keycode)
else:
mod_mask = 0
if shift_down:
mod_mask |= Xlib.X.ShiftMask
if ctrl_down:
mod_mask |= Xlib.X.ControlMask
# I don't know which masks alt and meta/windows correspond to;
# these are just guesses.
if alt_down:
mod_mask |= Xlib.X.Mod1Mask
if meta_down:
mod_mask |= Xlib.X.Mod2Mask
window = display.get_input_focus()._data["focus"]
event = Xlib.protocol.event.KeyPress(
time = int(time.time()),
root = display.screen().root,
window = window,
same_screen = 0, child = Xlib.X.NONE,
root_x = 0, root_y = 0, event_x = 0, event_y = 0,
state = mod_mask,
detail = keycode
)
window.send_event(event, propagate = True)
event = Xlib.protocol.event.KeyRelease(
time = int(time.time()),
root = display.screen().root,
window = window,
same_screen = 0, child = Xlib.X.NONE,
root_x = 0, root_y = 0, event_x = 0, event_y = 0,
state = mod_mask,
detail = keycode
)
window.send_event(event, propagate = True)
def send_string(s):
"""Generate fake keypresses to send the given string,
using XTest if possible, otherwise Xlib.
\C means press ctrl, \c means release it; same for s (shift),
a (alt) and m or w (meta/windows).
All modifiers will be released at the end of a string if the
release code isn't already in the specifier.
"""
init_display()
# Make lists of all the keycodes and whether they're shifted:
keycodes = []
modifiers = []
shift_down = False
ctrl_down = False
alt_down = False
meta_down = False
backslash = False
for ch in s:
# print ("\n============ Considering '%s'" % ch)
if ch == '\\':
backslash = True
continue
if backslash:
backslash = False
# Most backslash escapes will be modifiers:
if ch == 'C':
ctrl_down = True
send_modifier(CTRL_KEYCODE, True)
continue
elif ch == 'A':
alt_down = True
send_modifier(ALT_KEYCODE, True)
continue
elif ch == 'M' or ch == 'W':
meta_down = True
send_modifier(META_KEYCODE, True)
continue
elif ch == 'c':
ctrl_down = False
send_modifier(CTRL_KEYCODE, False)
continue
elif ch == 'a':
alt_down = False
send_modifier(ALT_KEYCODE, False)
continue
elif ch == 'm' or ch == 'w':
meta_down = False
send_modifier(META_KEYCODE, False)
continue
# but a few may be other characters:
elif ch in special_X_keycodes:
keycode = special_X_keycodes[ch]
send_key(keycode, shift_down, ctrl_down, alt_down, meta_down)
continue
# Else just ignore the backslash and use the letter.
ch_unshifted = is_shifted(ch)
if ch_unshifted:
if not shift_down:
send_modifier(SHIFT_KEYCODE, True)
shift_down = True
ch = ch_unshifted
else:
if shift_down:
send_modifier(SHIFT_KEYCODE, False)
shift_down = False
keycode = char_to_keycode(ch)
send_key(keycode, shift_down, ctrl_down, alt_down, meta_down)
# Is anything still pressed?
if shift_down:
send_modifier(SHIFT_KEYCODE, False)
if ctrl_down:
send_modifier(CTRL_KEYCODE, False)
if alt_down:
send_modifier(ALT_KEYCODE, False)
if meta_down:
send_modifier(META_KEYCODE, False)
display.sync()
if __name__ == '__main__':
for arg in range(1, len(sys.argv)):
send_string(sys.argv[arg])