-
Notifications
You must be signed in to change notification settings - Fork 65
/
keyboard.py
211 lines (174 loc) · 6.16 KB
/
keyboard.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
import ast
import warnings
from ahk.script import ScriptEngine
from ahk.utils import escape_sequence_replace
from ahk.keys import Key
from ahk.directives import InstallKeybdHook, InstallMouseHook
class Hotkey:
def __init__(self, engine: ScriptEngine, hotkey: str, script: str):
self.hotkey = hotkey
self.script = script
self.engine = engine
@property
def running(self):
return hasattr(self, '_proc')
def _start(self, script):
try:
proc = self.engine.run_script(script, blocking=False)
yield proc
finally:
self._stop()
def start(self):
"""
Starts an AutoHotkey process with the hotkey script
"""
if self.running:
raise RuntimeError('Hotkey is already running')
script = self.engine.render_template('hotkey.ahk', blocking=False, script=self.script, hotkey=self.hotkey)
self._gen = self._start(script)
proc = next(self._gen)
self._proc = proc
def _stop(self):
if not self.running:
return
self._proc.terminate()
del self._proc
def stop(self):
"""
Stops the process if it is running
"""
if not self.running:
raise RuntimeError('Hotkey is not running')
try:
next(self._gen)
except StopIteration:
pass
finally:
del self._gen
class KeyboardMixin(ScriptEngine):
def hotkey(self, *args, **kwargs):
"""
Convenience function for creating ``Hotkey`` instance using current engine.
:param args:
:param kwargs:
:return:
"""
return Hotkey(engine=self, *args, **kwargs)
def key_state(self, key_name, mode=None) -> bool:
"""
Check the state of a key.
https://autohotkey.com/docs/commands/GetKeyState.htm
:param key_name: the name of the key (or virtual key code)
:param mode: see AHK docs
:return: True if pressed down, else False
"""
script = self.render_template('keyboard/key_state.ahk', key_name=key_name, mode=mode, directives=(InstallMouseHook, InstallKeybdHook))
result = ast.literal_eval(self.run_script(script))
return bool(result)
def key_wait(self, key_name, timeout: int=None, logical_state=False, released=False):
"""
Wait for key to be pressed or released (default is pressed; specify ``released=True`` to wait for key release).
https://autohotkey.com/docs/commands/KeyWait.htm
:param key_name: The name of the key
:param timeout: how long (in seconds) to wait for the key. If not specified, waits indefinitely
:param logical_state: Check the logical state of the key, which is the state that the OS and the active window believe the key to be in (not necessarily the same as the physical state). This option is ignored for joystick buttons.
:param released: Set to True to wait for the key to be released rather than pressed
:return:
:raises TimeoutError: if the key was not pressed (or released, if specified) within timeout
"""
options = ''
if not released:
options += 'D'
if logical_state:
options += 'L'
if timeout:
options += f'T{timeout}'
script = self.render_template('keyboard/key_wait.ahk', key_name=key_name, options=options)
result = self.run_script(script)
if result == "1":
raise TimeoutError(f'timed out waiting for {key_name}')
def type(self, s):
"""
Sends keystrokes using send_input, also escaping the string for use in AHK.
"""
s = escape_sequence_replace(s)
self.send_input(s)
def send(self, s, raw=False, delay=None):
"""
https://autohotkey.com/docs/commands/Send.htm
:param s:
:param raw:
:param delay:
:return:
"""
script = self.render_template('keyboard/send.ahk', s=s, raw=raw, delay=delay)
self.run_script(script)
def send_raw(self, s, delay=None):
"""
https://autohotkey.com/docs/commands/Send.htm
:param s:
:param delay:
:return:
"""
return self.send(s, raw=True, delay=delay)
def send_input(self, s):
"""
https://autohotkey.com/docs/commands/Send.htm
:param s:
:return:
"""
if len(s) > 5000:
warnings.warn('String length greater than allowed. Characters beyond 5000 may not be sent. '
'See https://autohotkey.com/docs/commands/Send.htm#SendInputDetail for details.')
script = self.render_template('keyboard/send_input.ahk', s=s)
self.run_script(script)
def send_play(self, s):
"""
https://autohotkey.com/docs/commands/Send.htm
:param s:
:return:
"""
script = self.render_template('keyboard/send_play.ahk', s=s)
self.run_script(script)
def send_event(self, s, delay=None):
"""
https://autohotkey.com/docs/commands/Send.htm
:param s:
:param delay:
:return:
"""
script = self.render_template('keyboard/send_event.ahk', s=s, delay=delay)
self.run_script(script)
def key_press(self, key, release=True):
"""
Press and (optionally) release a single key
:param key:
:param release:
:return:
"""
self.key_down(key)
if release:
self.key_up(key)
def key_release(self, key):
"""
Release a key that is currently in pressed down state
:param key:
:return:
"""
if isinstance(key, str):
key = Key(key_name=key)
return self.send_input(key.UP)
def key_down(self, key):
"""
Press down a key (without releasing it)
:param key:
:return:
"""
if isinstance(key, str):
key = Key(key_name=key)
return self.send_input(key.DOWN)
def key_up(self, key):
"""
Alias for :meth:~`KeyboardMixin.key_release`
"""
return self.key_release(key)