1
+ from micropython import const
2
+
1
3
from kmk .keys import KC , make_argumented_key , make_key
2
4
from kmk .modules import Module
3
5
from kmk .scheduler import create_task
4
6
from kmk .utils import Debug
5
7
6
8
debug = Debug (__name__ )
7
9
10
+ _IDLE = const (0 )
11
+ _ON_PRESS = const (1 )
12
+ _ON_HOLD = const (2 )
13
+ _RELEASE = const (3 )
14
+ _ON_RELEASE = const (4 )
15
+
8
16
9
17
class MacroMeta :
10
- def __init__ (self , * macro , ** kwargs ):
11
- self .macro = macro
18
+ def __init__ (
19
+ self ,
20
+ * args ,
21
+ on_press = None ,
22
+ on_hold = None ,
23
+ on_release = None ,
24
+ blocking = True ,
25
+ ):
26
+ if on_press is not None :
27
+ self .on_press = on_press
28
+ else :
29
+ self .on_press = args
30
+ self .on_hold = on_hold
31
+ self .on_release = on_release
32
+ self .blocking = blocking
33
+ self .state = _IDLE
34
+ self ._task = None
12
35
13
36
14
37
def Delay (delay ):
@@ -127,7 +150,7 @@ def MacroIter(keyboard, macro, unicode_mode):
127
150
128
151
class Macros (Module ):
129
152
def __init__ (self , unicode_mode = UnicodeModeIBus , delay = 10 ):
130
- self ._active = False
153
+ self ._active = []
131
154
self .key_buffer = []
132
155
self .unicode_mode = unicode_mode
133
156
self .delay = delay
@@ -136,6 +159,7 @@ def __init__(self, unicode_mode=UnicodeModeIBus, delay=10):
136
159
validator = MacroMeta ,
137
160
names = ('MACRO' ,),
138
161
on_press = self .on_press_macro ,
162
+ on_release = self .on_release_macro ,
139
163
)
140
164
make_key (
141
165
names = ('UC_MODE_IBUS' ,),
@@ -163,7 +187,7 @@ def after_matrix_scan(self, keyboard):
163
187
return
164
188
165
189
def process_key (self , keyboard , key , is_pressed , int_coord ):
166
- if not self ._active :
190
+ if not self ._active or key in self . _active :
167
191
return key
168
192
169
193
self .key_buffer .append ((int_coord , key , is_pressed ))
@@ -184,27 +208,82 @@ def on_press_unicode_mode(self, key, keyboard, *args, **kwargs):
184
208
self .unicode_mode = key .meta
185
209
186
210
def on_press_macro (self , key , keyboard , * args , ** kwargs ):
187
- self ._active = True
188
-
189
- _iter = MacroIter (keyboard , key .meta .macro , self .unicode_mode )
190
-
191
- def process_macro_async ():
192
- delay = self .delay
193
- try :
194
- # any not None value the iterator yields is a delay value in ms.
195
- ret = next (_iter )
196
- if ret is not None :
197
- delay = ret
198
- keyboard ._send_hid ()
199
- create_task (process_macro_async , after_ms = delay )
200
- except StopIteration :
211
+ key .meta .state = _ON_PRESS
212
+ self .process_macro_async (keyboard , key )
213
+
214
+ def on_release_macro (self , key , keyboard , * args , ** kwargs ):
215
+ key .meta .state = _RELEASE
216
+ if key .meta ._task is None :
217
+ self .process_macro_async (keyboard , key )
218
+
219
+ def process_macro_async (self , keyboard , key , _iter = None ):
220
+ # There's no active macro iterator: select the next one.
221
+ if _iter is None :
222
+ key .meta ._task = None
223
+
224
+ if key .meta .state == _ON_PRESS :
225
+ if key .meta .blocking :
226
+ self ._active .append (key )
227
+ if (macro := key .meta .on_press ) is None :
228
+ key .meta .state = _ON_HOLD
229
+ elif debug .enabled :
230
+ debug ('on_press' )
231
+
232
+ if key .meta .state == _ON_HOLD :
233
+ if (macro := key .meta .on_hold ) is None :
234
+ return
235
+ elif debug .enabled :
236
+ debug ('on_hold' )
237
+
238
+ if key .meta .state == _RELEASE :
239
+ key .meta .state = _ON_RELEASE
240
+
241
+ if key .meta .state == _ON_RELEASE :
242
+ if (macro := key .meta .on_release ) is None :
243
+ macro = ()
244
+ elif debug .enabled :
245
+ debug ('on_release' )
246
+
247
+ _iter = MacroIter (keyboard , macro , self .unicode_mode )
248
+
249
+ # Run one step in the macro sequence.
250
+ delay = self .delay
251
+ try :
252
+ # any not None value the iterator yields is a delay value in ms.
253
+ ret = next (_iter )
254
+ if ret is not None :
255
+ delay = ret
256
+ keyboard ._send_hid ()
257
+
258
+ # The sequence has reached its end: advance the macro state.
259
+ except StopIteration :
260
+ _iter = None
261
+ delay = 0
262
+ key .meta ._task = None
263
+
264
+ if key .meta .state == _ON_PRESS :
265
+ key .meta .state = _ON_HOLD
266
+
267
+ elif key .meta .state == _ON_RELEASE :
268
+ if debug .enabled :
269
+ debug ('deactivate' )
270
+ key .meta .state = _IDLE
271
+ if key .meta .blocking :
272
+ self ._active .remove (key )
201
273
self .send_key_buffer (keyboard )
274
+ return
202
275
203
- process_macro_async ()
276
+ # Schedule the next step.
277
+ # Reuse existing task objects and save a couple of bytes and cycles for the gc.
278
+ if key .meta ._task :
279
+ task = key .meta ._task
280
+ else :
281
+ def task ():
282
+ self .process_macro_async (keyboard , key , _iter )
283
+ key .meta ._task = create_task (task , after_ms = delay )
204
284
205
285
def send_key_buffer (self , keyboard ):
206
- self ._active = False
207
- if not self .key_buffer :
286
+ if not self .key_buffer or self ._active :
208
287
return
209
288
210
289
for int_coord , key , is_pressed in self .key_buffer :
0 commit comments