]>
Commit | Line | Data |
---|---|---|
a074364c | 1 | /* |
2 | Copyright 2012,2013 Jun Wako <wakojun@gmail.com> | |
3 | ||
4 | This program is free software: you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License as published by | |
6 | the Free Software Foundation, either version 2 of the License, or | |
7 | (at your option) any later version. | |
8 | ||
9 | This program is distributed in the hope that it will be useful, | |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | GNU General Public License for more details. | |
13 | ||
14 | You should have received a copy of the GNU General Public License | |
15 | along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | */ | |
17 | #include "host.h" | |
18 | #include "keycode.h" | |
19 | #include "keyboard.h" | |
20 | #include "mousekey.h" | |
21 | #include "command.h" | |
22 | #include "led.h" | |
23 | #include "backlight.h" | |
24 | #include "action_layer.h" | |
25 | #include "action_tapping.h" | |
26 | #include "action_macro.h" | |
27 | #include "action_util.h" | |
28 | #include "action.h" | |
71381457 | 29 | #include "hook.h" |
88c95615 | 30 | #include "wait.h" |
a074364c | 31 | |
32 | #ifdef DEBUG_ACTION | |
33 | #include "debug.h" | |
34 | #else | |
35 | #include "nodebug.h" | |
36 | #endif | |
37 | ||
38 | ||
39 | void action_exec(keyevent_t event) | |
40 | { | |
41 | if (!IS_NOEVENT(event)) { | |
42 | dprint("\n---- action_exec: start -----\n"); | |
43 | dprint("EVENT: "); debug_event(event); dprintln(); | |
71381457 | 44 | hook_matrix_change(event); |
a074364c | 45 | } |
46 | ||
47 | keyrecord_t record = { .event = event }; | |
48 | ||
49 | #ifndef NO_ACTION_TAPPING | |
50 | action_tapping_process(record); | |
51 | #else | |
52 | process_action(&record); | |
53 | if (!IS_NOEVENT(record.event)) { | |
54 | dprint("processed: "); debug_record(record); dprintln(); | |
55 | } | |
56 | #endif | |
57 | } | |
58 | ||
59 | void process_action(keyrecord_t *record) | |
60 | { | |
61 | keyevent_t event = record->event; | |
62 | #ifndef NO_ACTION_TAPPING | |
63 | uint8_t tap_count = record->tap.count; | |
64 | #endif | |
65 | ||
66 | if (IS_NOEVENT(event)) { return; } | |
67 | ||
ba2883fd | 68 | action_t action = layer_switch_get_action(event); |
a074364c | 69 | dprint("ACTION: "); debug_action(action); |
70 | #ifndef NO_ACTION_LAYER | |
71 | dprint(" layer_state: "); layer_debug(); | |
72 | dprint(" default_layer_state: "); default_layer_debug(); | |
73 | #endif | |
74 | dprintln(); | |
75 | ||
76 | switch (action.kind.id) { | |
77 | /* Key and Mods */ | |
78 | case ACT_LMODS: | |
79 | case ACT_RMODS: | |
80 | { | |
81 | uint8_t mods = (action.kind.id == ACT_LMODS) ? action.key.mods : | |
82 | action.key.mods<<4; | |
83 | if (event.pressed) { | |
84 | if (mods) { | |
85 | add_weak_mods(mods); | |
86 | send_keyboard_report(); | |
87 | } | |
88 | register_code(action.key.code); | |
89 | } else { | |
90 | unregister_code(action.key.code); | |
91 | if (mods) { | |
92 | del_weak_mods(mods); | |
93 | send_keyboard_report(); | |
94 | } | |
95 | } | |
96 | } | |
97 | break; | |
98 | #ifndef NO_ACTION_TAPPING | |
99 | case ACT_LMODS_TAP: | |
100 | case ACT_RMODS_TAP: | |
101 | { | |
102 | uint8_t mods = (action.kind.id == ACT_LMODS_TAP) ? action.key.mods : | |
103 | action.key.mods<<4; | |
c0f09090 | 104 | switch (action.key.code) { |
a074364c | 105 | #ifndef NO_ACTION_ONESHOT |
106 | case MODS_ONESHOT: | |
107 | // Oneshot modifier | |
108 | if (event.pressed) { | |
109 | if (tap_count == 0) { | |
110 | register_mods(mods); | |
111 | } | |
112 | else if (tap_count == 1) { | |
113 | dprint("MODS_TAP: Oneshot: start\n"); | |
114 | set_oneshot_mods(mods); | |
115 | } | |
116 | else { | |
117 | register_mods(mods); | |
118 | } | |
119 | } else { | |
120 | if (tap_count == 0) { | |
121 | clear_oneshot_mods(); | |
122 | unregister_mods(mods); | |
123 | } | |
124 | else if (tap_count == 1) { | |
125 | // Retain Oneshot mods | |
126 | } | |
127 | else { | |
128 | clear_oneshot_mods(); | |
129 | unregister_mods(mods); | |
130 | } | |
131 | } | |
132 | break; | |
133 | #endif | |
134 | case MODS_TAP_TOGGLE: | |
135 | if (event.pressed) { | |
136 | if (tap_count <= TAPPING_TOGGLE) { | |
28203e90 | 137 | if (mods & get_mods()) { |
138 | dprint("MODS_TAP_TOGGLE: toggle mods off\n"); | |
139 | unregister_mods(mods); | |
140 | } else { | |
141 | dprint("MODS_TAP_TOGGLE: toggle mods on\n"); | |
142 | register_mods(mods); | |
143 | } | |
a074364c | 144 | } |
145 | } else { | |
146 | if (tap_count < TAPPING_TOGGLE) { | |
28203e90 | 147 | dprint("MODS_TAP_TOGGLE: release : unregister_mods\n"); |
a074364c | 148 | unregister_mods(mods); |
149 | } | |
150 | } | |
151 | break; | |
152 | default: | |
153 | if (event.pressed) { | |
154 | if (tap_count > 0) { | |
155 | if (record->tap.interrupted) { | |
156 | dprint("MODS_TAP: Tap: Cancel: add_mods\n"); | |
157 | // ad hoc: set 0 to cancel tap | |
158 | record->tap.count = 0; | |
159 | register_mods(mods); | |
160 | } else { | |
161 | dprint("MODS_TAP: Tap: register_code\n"); | |
162 | register_code(action.key.code); | |
163 | } | |
164 | } else { | |
165 | dprint("MODS_TAP: No tap: add_mods\n"); | |
166 | register_mods(mods); | |
167 | } | |
168 | } else { | |
169 | if (tap_count > 0) { | |
170 | dprint("MODS_TAP: Tap: unregister_code\n"); | |
171 | unregister_code(action.key.code); | |
172 | } else { | |
173 | dprint("MODS_TAP: No tap: add_mods\n"); | |
174 | unregister_mods(mods); | |
175 | } | |
176 | } | |
177 | break; | |
178 | } | |
179 | } | |
180 | break; | |
181 | #endif | |
182 | #ifdef EXTRAKEY_ENABLE | |
183 | /* other HID usage */ | |
184 | case ACT_USAGE: | |
185 | switch (action.usage.page) { | |
186 | case PAGE_SYSTEM: | |
187 | if (event.pressed) { | |
188 | host_system_send(action.usage.code); | |
189 | } else { | |
190 | host_system_send(0); | |
191 | } | |
192 | break; | |
193 | case PAGE_CONSUMER: | |
194 | if (event.pressed) { | |
195 | host_consumer_send(action.usage.code); | |
196 | } else { | |
197 | host_consumer_send(0); | |
198 | } | |
199 | break; | |
200 | } | |
201 | break; | |
202 | #endif | |
203 | #ifdef MOUSEKEY_ENABLE | |
204 | /* Mouse key */ | |
205 | case ACT_MOUSEKEY: | |
206 | if (event.pressed) { | |
207 | mousekey_on(action.key.code); | |
208 | mousekey_send(); | |
209 | } else { | |
210 | mousekey_off(action.key.code); | |
211 | mousekey_send(); | |
212 | } | |
213 | break; | |
214 | #endif | |
215 | #ifndef NO_ACTION_LAYER | |
216 | case ACT_LAYER: | |
217 | if (action.layer_bitop.on == 0) { | |
218 | /* Default Layer Bitwise Operation */ | |
219 | if (!event.pressed) { | |
220 | uint8_t shift = action.layer_bitop.part*4; | |
221 | uint32_t bits = ((uint32_t)action.layer_bitop.bits)<<shift; | |
222 | uint32_t mask = (action.layer_bitop.xbit) ? ~(((uint32_t)0xf)<<shift) : 0; | |
223 | switch (action.layer_bitop.op) { | |
224 | case OP_BIT_AND: default_layer_and(bits | mask); break; | |
225 | case OP_BIT_OR: default_layer_or(bits | mask); break; | |
226 | case OP_BIT_XOR: default_layer_xor(bits | mask); break; | |
227 | case OP_BIT_SET: default_layer_and(mask); default_layer_or(bits); break; | |
228 | } | |
229 | } | |
230 | } else { | |
231 | /* Layer Bitwise Operation */ | |
232 | if (event.pressed ? (action.layer_bitop.on & ON_PRESS) : | |
233 | (action.layer_bitop.on & ON_RELEASE)) { | |
234 | uint8_t shift = action.layer_bitop.part*4; | |
235 | uint32_t bits = ((uint32_t)action.layer_bitop.bits)<<shift; | |
236 | uint32_t mask = (action.layer_bitop.xbit) ? ~(((uint32_t)0xf)<<shift) : 0; | |
237 | switch (action.layer_bitop.op) { | |
238 | case OP_BIT_AND: layer_and(bits | mask); break; | |
239 | case OP_BIT_OR: layer_or(bits | mask); break; | |
240 | case OP_BIT_XOR: layer_xor(bits | mask); break; | |
241 | case OP_BIT_SET: layer_and(mask); layer_or(bits); break; | |
242 | } | |
243 | } | |
244 | } | |
245 | break; | |
246 | #ifndef NO_ACTION_TAPPING | |
247 | case ACT_LAYER_TAP: | |
248 | case ACT_LAYER_TAP_EXT: | |
249 | switch (action.layer_tap.code) { | |
c0f09090 | 250 | case 0xc0 ... 0xdf: |
251 | /* layer On/Off with modifiers */ | |
a074364c | 252 | if (event.pressed) { |
253 | layer_on(action.layer_tap.val); | |
c0f09090 | 254 | register_mods((action.layer_tap.code & 0x10) ? |
255 | (action.layer_tap.code & 0x0f) << 4 : | |
256 | (action.layer_tap.code & 0x0f)); | |
a074364c | 257 | } else { |
258 | layer_off(action.layer_tap.val); | |
c0f09090 | 259 | unregister_mods((action.layer_tap.code & 0x10) ? |
260 | (action.layer_tap.code & 0x0f) << 4 : | |
261 | (action.layer_tap.code & 0x0f)); | |
a074364c | 262 | } |
263 | break; | |
264 | case OP_TAP_TOGGLE: | |
265 | /* tap toggle */ | |
266 | if (event.pressed) { | |
267 | if (tap_count < TAPPING_TOGGLE) { | |
268 | layer_invert(action.layer_tap.val); | |
269 | } | |
270 | } else { | |
271 | if (tap_count <= TAPPING_TOGGLE) { | |
272 | layer_invert(action.layer_tap.val); | |
273 | } | |
274 | } | |
275 | break; | |
276 | case OP_ON_OFF: | |
277 | event.pressed ? layer_on(action.layer_tap.val) : | |
278 | layer_off(action.layer_tap.val); | |
279 | break; | |
280 | case OP_OFF_ON: | |
281 | event.pressed ? layer_off(action.layer_tap.val) : | |
282 | layer_on(action.layer_tap.val); | |
283 | break; | |
284 | case OP_SET_CLEAR: | |
285 | event.pressed ? layer_move(action.layer_tap.val) : | |
286 | layer_clear(); | |
287 | break; | |
288 | default: | |
289 | /* tap key */ | |
290 | if (event.pressed) { | |
291 | if (tap_count > 0) { | |
292 | dprint("KEYMAP_TAP_KEY: Tap: register_code\n"); | |
293 | register_code(action.layer_tap.code); | |
294 | } else { | |
295 | dprint("KEYMAP_TAP_KEY: No tap: On on press\n"); | |
296 | layer_on(action.layer_tap.val); | |
297 | } | |
298 | } else { | |
299 | if (tap_count > 0) { | |
300 | dprint("KEYMAP_TAP_KEY: Tap: unregister_code\n"); | |
301 | unregister_code(action.layer_tap.code); | |
302 | } else { | |
303 | dprint("KEYMAP_TAP_KEY: No tap: Off on release\n"); | |
304 | layer_off(action.layer_tap.val); | |
305 | } | |
306 | } | |
307 | break; | |
308 | } | |
309 | break; | |
310 | #endif | |
311 | #endif | |
312 | /* Extentions */ | |
313 | #ifndef NO_ACTION_MACRO | |
314 | case ACT_MACRO: | |
315 | action_macro_play(action_get_macro(record, action.func.id, action.func.opt)); | |
316 | break; | |
317 | #endif | |
318 | #ifdef BACKLIGHT_ENABLE | |
319 | case ACT_BACKLIGHT: | |
320 | if (!event.pressed) { | |
321 | switch (action.backlight.opt) { | |
322 | case BACKLIGHT_INCREASE: | |
323 | backlight_increase(); | |
324 | break; | |
325 | case BACKLIGHT_DECREASE: | |
326 | backlight_decrease(); | |
327 | break; | |
328 | case BACKLIGHT_TOGGLE: | |
329 | backlight_toggle(); | |
330 | break; | |
331 | case BACKLIGHT_STEP: | |
332 | backlight_step(); | |
333 | break; | |
334 | case BACKLIGHT_LEVEL: | |
335 | backlight_level(action.backlight.level); | |
336 | break; | |
337 | } | |
338 | } | |
339 | break; | |
340 | #endif | |
341 | case ACT_COMMAND: | |
342 | break; | |
343 | #ifndef NO_ACTION_FUNCTION | |
344 | case ACT_FUNCTION: | |
345 | action_function(record, action.func.id, action.func.opt); | |
346 | break; | |
347 | #endif | |
348 | default: | |
349 | break; | |
350 | } | |
351 | } | |
352 | ||
353 | ||
354 | ||
355 | ||
356 | /* | |
357 | * Utilities for actions. | |
358 | */ | |
359 | void register_code(uint8_t code) | |
360 | { | |
361 | if (code == KC_NO) { | |
362 | return; | |
363 | } | |
364 | ||
365 | #ifdef LOCKING_SUPPORT_ENABLE | |
366 | else if (KC_LOCKING_CAPS == code) { | |
367 | #ifdef LOCKING_RESYNC_ENABLE | |
368 | // Resync: ignore if caps lock already is on | |
369 | if (host_keyboard_leds() & (1<<USB_LED_CAPS_LOCK)) return; | |
370 | #endif | |
371 | add_key(KC_CAPSLOCK); | |
372 | send_keyboard_report(); | |
88c95615 | 373 | wait_ms(100); |
a074364c | 374 | del_key(KC_CAPSLOCK); |
375 | send_keyboard_report(); | |
376 | } | |
377 | ||
378 | else if (KC_LOCKING_NUM == code) { | |
379 | #ifdef LOCKING_RESYNC_ENABLE | |
380 | if (host_keyboard_leds() & (1<<USB_LED_NUM_LOCK)) return; | |
381 | #endif | |
382 | add_key(KC_NUMLOCK); | |
383 | send_keyboard_report(); | |
88c95615 | 384 | wait_ms(100); |
a074364c | 385 | del_key(KC_NUMLOCK); |
386 | send_keyboard_report(); | |
387 | } | |
388 | ||
389 | else if (KC_LOCKING_SCROLL == code) { | |
390 | #ifdef LOCKING_RESYNC_ENABLE | |
391 | if (host_keyboard_leds() & (1<<USB_LED_SCROLL_LOCK)) return; | |
392 | #endif | |
393 | add_key(KC_SCROLLLOCK); | |
394 | send_keyboard_report(); | |
88c95615 | 395 | wait_ms(100); |
a074364c | 396 | del_key(KC_SCROLLLOCK); |
397 | send_keyboard_report(); | |
398 | } | |
399 | #endif | |
400 | ||
401 | else if IS_KEY(code) { | |
402 | // TODO: should push command_proc out of this block? | |
403 | if (command_proc(code)) return; | |
404 | ||
405 | #ifndef NO_ACTION_ONESHOT | |
406 | /* TODO: remove | |
407 | if (oneshot_state.mods && !oneshot_state.disabled) { | |
408 | uint8_t tmp_mods = get_mods(); | |
409 | add_mods(oneshot_state.mods); | |
410 | ||
411 | add_key(code); | |
412 | send_keyboard_report(); | |
413 | ||
414 | set_mods(tmp_mods); | |
415 | send_keyboard_report(); | |
416 | oneshot_cancel(); | |
417 | } else | |
418 | */ | |
419 | #endif | |
420 | { | |
421 | add_key(code); | |
422 | send_keyboard_report(); | |
423 | } | |
424 | } | |
425 | else if IS_MOD(code) { | |
426 | add_mods(MOD_BIT(code)); | |
427 | send_keyboard_report(); | |
428 | } | |
429 | else if IS_SYSTEM(code) { | |
430 | host_system_send(KEYCODE2SYSTEM(code)); | |
431 | } | |
432 | else if IS_CONSUMER(code) { | |
433 | host_consumer_send(KEYCODE2CONSUMER(code)); | |
434 | } | |
435 | } | |
436 | ||
437 | void unregister_code(uint8_t code) | |
438 | { | |
439 | if (code == KC_NO) { | |
440 | return; | |
441 | } | |
442 | ||
443 | #ifdef LOCKING_SUPPORT_ENABLE | |
444 | else if (KC_LOCKING_CAPS == code) { | |
445 | #ifdef LOCKING_RESYNC_ENABLE | |
446 | // Resync: ignore if caps lock already is off | |
447 | if (!(host_keyboard_leds() & (1<<USB_LED_CAPS_LOCK))) return; | |
448 | #endif | |
449 | add_key(KC_CAPSLOCK); | |
450 | send_keyboard_report(); | |
88c95615 | 451 | wait_ms(100); |
a074364c | 452 | del_key(KC_CAPSLOCK); |
453 | send_keyboard_report(); | |
454 | } | |
455 | ||
456 | else if (KC_LOCKING_NUM == code) { | |
457 | #ifdef LOCKING_RESYNC_ENABLE | |
458 | if (!(host_keyboard_leds() & (1<<USB_LED_NUM_LOCK))) return; | |
459 | #endif | |
460 | add_key(KC_NUMLOCK); | |
461 | send_keyboard_report(); | |
88c95615 | 462 | wait_ms(100); |
a074364c | 463 | del_key(KC_NUMLOCK); |
464 | send_keyboard_report(); | |
465 | } | |
466 | ||
467 | else if (KC_LOCKING_SCROLL == code) { | |
468 | #ifdef LOCKING_RESYNC_ENABLE | |
469 | if (!(host_keyboard_leds() & (1<<USB_LED_SCROLL_LOCK))) return; | |
470 | #endif | |
471 | add_key(KC_SCROLLLOCK); | |
472 | send_keyboard_report(); | |
88c95615 | 473 | wait_ms(100); |
a074364c | 474 | del_key(KC_SCROLLLOCK); |
475 | send_keyboard_report(); | |
476 | } | |
477 | #endif | |
478 | ||
479 | else if IS_KEY(code) { | |
480 | del_key(code); | |
481 | send_keyboard_report(); | |
482 | } | |
483 | else if IS_MOD(code) { | |
484 | del_mods(MOD_BIT(code)); | |
485 | send_keyboard_report(); | |
486 | } | |
487 | else if IS_SYSTEM(code) { | |
488 | host_system_send(0); | |
489 | } | |
490 | else if IS_CONSUMER(code) { | |
491 | host_consumer_send(0); | |
492 | } | |
493 | } | |
494 | ||
495 | void register_mods(uint8_t mods) | |
496 | { | |
497 | if (mods) { | |
498 | add_mods(mods); | |
499 | send_keyboard_report(); | |
500 | } | |
501 | } | |
502 | ||
503 | void unregister_mods(uint8_t mods) | |
504 | { | |
505 | if (mods) { | |
506 | del_mods(mods); | |
507 | send_keyboard_report(); | |
508 | } | |
509 | } | |
510 | ||
511 | void clear_keyboard(void) | |
512 | { | |
513 | clear_mods(); | |
514 | clear_keyboard_but_mods(); | |
515 | } | |
516 | ||
517 | void clear_keyboard_but_mods(void) | |
518 | { | |
519 | clear_weak_mods(); | |
520 | clear_keys(); | |
521 | send_keyboard_report(); | |
522 | #ifdef MOUSEKEY_ENABLE | |
523 | mousekey_clear(); | |
524 | mousekey_send(); | |
525 | #endif | |
526 | #ifdef EXTRAKEY_ENABLE | |
527 | host_system_send(0); | |
528 | host_consumer_send(0); | |
529 | #endif | |
530 | } | |
531 | ||
ba2883fd | 532 | bool is_tap_key(keyevent_t event) |
a074364c | 533 | { |
df7ce59d | 534 | if (IS_NOEVENT(event)) { return false; } |
535 | ||
ba2883fd | 536 | action_t action = layer_switch_get_action(event); |
a074364c | 537 | |
538 | switch (action.kind.id) { | |
539 | case ACT_LMODS_TAP: | |
540 | case ACT_RMODS_TAP: | |
c0f09090 | 541 | switch (action.key.code) { |
542 | case MODS_ONESHOT: | |
543 | case MODS_TAP_TOGGLE: | |
544 | case KC_A ... KC_EXSEL: // tap key | |
545 | case KC_LCTRL ... KC_RGUI: // tap key | |
546 | return true; | |
547 | } | |
a074364c | 548 | case ACT_LAYER_TAP: |
549 | case ACT_LAYER_TAP_EXT: | |
4d116a04 | 550 | switch (action.layer_tap.code) { |
c0f09090 | 551 | case 0xc0 ... 0xdf: // with modifiers |
552 | return false; | |
553 | case KC_A ... KC_EXSEL: // tap key | |
554 | case KC_LCTRL ... KC_RGUI: // tap key | |
4d116a04 | 555 | case OP_TAP_TOGGLE: |
556 | return true; | |
557 | } | |
558 | return false; | |
a074364c | 559 | case ACT_MACRO: |
560 | case ACT_FUNCTION: | |
561 | if (action.func.opt & FUNC_TAP) { return true; } | |
562 | return false; | |
563 | } | |
564 | return false; | |
565 | } | |
566 | ||
567 | ||
568 | /* | |
569 | * debug print | |
570 | */ | |
571 | void debug_event(keyevent_t event) | |
572 | { | |
573 | dprintf("%04X%c(%u)", (event.key.row<<8 | event.key.col), (event.pressed ? 'd' : 'u'), event.time); | |
574 | } | |
575 | ||
576 | void debug_record(keyrecord_t record) | |
577 | { | |
578 | debug_event(record.event); | |
579 | #ifndef NO_ACTION_TAPPING | |
580 | dprintf(":%u%c", record.tap.count, (record.tap.interrupted ? '-' : ' ')); | |
581 | #endif | |
582 | } | |
583 | ||
584 | void debug_action(action_t action) | |
585 | { | |
586 | switch (action.kind.id) { | |
587 | case ACT_LMODS: dprint("ACT_LMODS"); break; | |
588 | case ACT_RMODS: dprint("ACT_RMODS"); break; | |
589 | case ACT_LMODS_TAP: dprint("ACT_LMODS_TAP"); break; | |
590 | case ACT_RMODS_TAP: dprint("ACT_RMODS_TAP"); break; | |
591 | case ACT_USAGE: dprint("ACT_USAGE"); break; | |
592 | case ACT_MOUSEKEY: dprint("ACT_MOUSEKEY"); break; | |
593 | case ACT_LAYER: dprint("ACT_LAYER"); break; | |
594 | case ACT_LAYER_TAP: dprint("ACT_LAYER_TAP"); break; | |
595 | case ACT_LAYER_TAP_EXT: dprint("ACT_LAYER_TAP_EXT"); break; | |
596 | case ACT_MACRO: dprint("ACT_MACRO"); break; | |
597 | case ACT_COMMAND: dprint("ACT_COMMAND"); break; | |
598 | case ACT_FUNCTION: dprint("ACT_FUNCTION"); break; | |
599 | default: dprint("UNKNOWN"); break; | |
600 | } | |
601 | dprintf("[%X:%02X]", action.kind.param>>8, action.kind.param&0xff); | |
602 | } |