@@ -23,39 +23,50 @@ static keyrecord_t tap_hold_record;
2323static uint16_t tap_hold_keycode = KC_NO ;
2424// Timeout timer. When it expires, the key is considered held.
2525static uint16_t hold_timer = 0 ;
26- // Mods applied if holding a mod-tap.
27- static uint8_t hold_mods = 0 ;
28- // Layer activated if holding a layer-tap.
29- static uint8_t hold_layer = 0 ;
30- // Whether the tap-hold decision has been "settled" yet.
31- static bool settled = false;
32-
33- // Applies the mods or layer switch for the tap-hold key's hold action.
34- static void apply_hold_action (void ) {
35- if (QK_MOD_TAP <= tap_hold_keycode && tap_hold_keycode <= QK_MOD_TAP_MAX ) {
36- // Extract mods from mod-tap keycode.
37- hold_mods = (tap_hold_keycode >> 8 ) & 0xf ;
38- if ((tap_hold_keycode & 0x1000 ) != 0 ) { hold_mods <<= 4 ; }
39- register_mods (hold_mods );
40- } else {
41- // Extract layer from layer-tap keycode.
42- hold_layer = (tap_hold_keycode >> 8 ) & 0xf ;
43- layer_on (hold_layer );
44- }
26+ // Eagerly applied mods, if any.
27+ static uint8_t eager_mods = 0 ;
28+
29+ // Achordion's current state.
30+ enum {
31+ // A tap-hold key is pressed, but hasn't yet been settled as tapped or held.
32+ STATE_UNSETTLED ,
33+ // Achordion is inactive.
34+ STATE_RELEASED ,
35+ // Active tap-hold key has been settled as tapped.
36+ STATE_TAPPING ,
37+ // Active tap-hold key has been settled as held.
38+ STATE_HOLDING ,
39+ // This state is set while calling `process_record()`, which will recursively
40+ // call `process_achordion()`. This state is checked so that we don't process
41+ // events generated by Achordion and potentially create an infinite loop.
42+ STATE_RECURSING ,
43+ };
44+ static uint8_t achordion_state = STATE_RELEASED ;
45+
46+ // Calls `process_record()` with state set to RECURSING.
47+ static void recursively_process_record (keyrecord_t * record , uint8_t state ) {
48+ achordion_state = STATE_RECURSING ;
49+ process_record (record );
50+ achordion_state = state ;
4551}
4652
47- // Clears the mods or layer set by `apply_hold_action()`.
48- static void clear_hold_action (void ) {
49- if (hold_mods ) {
50- unregister_mods (hold_mods );
51- hold_mods = 0 ;
52- } else if (hold_layer ) {
53- layer_off (hold_layer );
54- hold_layer = 0 ;
55- }
53+ // Sends hold press event and settles the active tap-hold key as held.
54+ static void settle_as_hold (void ) {
55+ eager_mods = 0 ;
56+ // Create hold press event.
57+ recursively_process_record (& tap_hold_record , STATE_HOLDING );
58+ }
59+
60+ // Clears eagerly-applied mods.
61+ static void clear_eager_mods (void ) {
62+ unregister_mods (eager_mods );
63+ eager_mods = 0 ;
5664}
5765
5866bool process_achordion (uint16_t keycode , keyrecord_t * record ) {
67+ // Don't process events that Achordion generated.
68+ if (achordion_state == STATE_RECURSING ) { return true; }
69+
5970 // Determine whether the current event is for a mod-tap or layer-tap key.
6071 const bool is_mt = QK_MOD_TAP <= keycode && keycode <= QK_MOD_TAP_MAX ;
6172 const bool is_tap_hold =
@@ -64,22 +75,28 @@ bool process_achordion(uint16_t keycode, keyrecord_t* record) {
6475 const bool is_physical_pos = (record -> event .key .row < 254
6576 && record -> event .key .col < 254 );
6677
67- if (tap_hold_keycode == KC_NO ) {
68- if (record -> event . pressed && is_physical_pos
69- && is_tap_hold && record -> tap . count == 0 ) {
78+ if (achordion_state == STATE_RELEASED ) {
79+ if (is_tap_hold && record -> tap . count == 0 &&
80+ record -> event . pressed && is_physical_pos ) {
7081 // A tap-hold key is pressed and considered by QMK as "held".
7182 const uint16_t timeout = achordion_timeout (keycode );
7283 if (timeout > 0 ) {
73- settled = false ;
84+ achordion_state = STATE_UNSETTLED ;
7485 // Save info about this key.
7586 tap_hold_keycode = keycode ;
7687 tap_hold_record = * record ;
7788 hold_timer = record -> event .time + timeout ;
7889
79- // For an "eager" mod, apply it immediately.
80- if (is_mt && achordion_eager_mod ((keycode >> 8 ) & 0x1f )) {
81- apply_hold_action ();
90+ if (is_mt ) { // Apply mods immediately if they are "eager."
91+ uint8_t mod = (tap_hold_keycode >> 8 ) & 0x1f ;
92+ if (achordion_eager_mod (mod )) {
93+ eager_mods = ((mod & 0x10 ) == 0 ) ? mod : (mod << 4 );
94+ register_mods (eager_mods );
95+ }
8296 }
97+
98+ dprintf ("Achordion: Key 0x%04X pressed.%s\n" ,
99+ keycode , eager_mods ? " Set eager mods." : "" );
83100 return false; // Skip default handling.
84101 }
85102 }
@@ -89,14 +106,25 @@ bool process_achordion(uint16_t keycode, keyrecord_t* record) {
89106
90107 if (keycode == tap_hold_keycode && !record -> event .pressed ) {
91108 // The active tap-hold key is being released.
92- tap_hold_keycode = KC_NO ;
93- clear_hold_action ();
109+ if (achordion_state == STATE_HOLDING ) {
110+ dprintln ("Achordion: Key released. Plumbing hold release." );
111+ tap_hold_record .event .pressed = false;
112+ // Plumb hold release event.
113+ recursively_process_record (& tap_hold_record , STATE_RELEASED );
114+ } else {
115+ dprintf ("Achordion: Key released.%s\n" ,
116+ eager_mods ? " Clearing eager mods." : "" );
117+ if (is_mt ) {
118+ clear_eager_mods ();
119+ }
120+ }
121+
122+ achordion_state = STATE_RELEASED ;
94123 return false;
95124 }
96125
97- if (! settled && record -> event .pressed ) {
126+ if (achordion_state == STATE_UNSETTLED && record -> event .pressed ) {
98127 // Press event occurred on a key other than the active tap-hold key.
99- settled = true;
100128
101129 // If the other key is *also* a tap-hold key and considered by QMK to be
102130 // held, then we settle the active key as held. This way, things like
@@ -107,35 +135,42 @@ bool process_achordion(uint16_t keycode, keyrecord_t* record) {
107135 // tap-hold key as tapped vs. held. We implement the tap or hold by plumbing
108136 // events back into the handling pipeline so that QMK features and other
109137 // user code can see them. This is done by calling `process_record()`, which
110- // in turn calls most handlers including `process_record_user()`. Note that
111- // this makes this function recursive, as it is called by
112- // `process_record_user()`, so care is needed. We set `settled = true` above
113- // to prevent infinite loops.
138+ // in turn calls most handlers including `process_record_user()`.
114139 if (!is_physical_pos || (is_tap_hold && record -> tap .count == 0 ) ||
115140 achordion_chord (tap_hold_keycode , & tap_hold_record , keycode , record )) {
116- apply_hold_action ();
141+ dprintln ("Achordion: Plumbing hold press." );
142+ settle_as_hold ();
117143 } else {
118- clear_hold_action (); // Clear in case mod was eagerly applied .
144+ clear_eager_mods (); // Clear in case eager mods were set .
119145
146+ dprintln ("Achordion: Plumbing tap press." );
120147 tap_hold_record .tap .count = 1 ; // Revise event as a tap.
121- process_record (& tap_hold_record ); // Create tap press event.
148+ tap_hold_record .tap .interrupted = true;
149+ // Plumb tap press event.
150+ recursively_process_record (& tap_hold_record , STATE_TAPPING );
151+
122152#if TAP_CODE_DELAY > 0
123153 wait_ms (TAP_CODE_DELAY );
124154#endif // TAP_CODE_DELAY > 0
155+
156+ dprintln ("Achordion: Plumbing tap release." );
125157 tap_hold_record .event .pressed = false;
126- process_record (& tap_hold_record ); // Create tap release event.
158+ // Plumb tap release event.
159+ recursively_process_record (& tap_hold_record , STATE_TAPPING );
127160 }
128- process_record (record ); // Re-process the event.
161+
162+ recursively_process_record (record , achordion_state ); // Re-process event.
129163 return false; // Block the original event.
130164 }
165+
131166 return true;
132167}
133168
134169void achordion_task (void ) {
135- if (! settled && timer_expired ( timer_read (), hold_timer )) {
136- // Timeout expired, settle the key as held.
137- settled = true ;
138- apply_hold_action ();
170+ if (achordion_state == STATE_UNSETTLED &&
171+ timer_expired ( timer_read (), hold_timer )) {
172+ dprintln ( "Achordion: Timeout. Plumbing hold press." ) ;
173+ settle_as_hold (); // Timeout expired, settle the key as held.
139174 }
140175}
141176
0 commit comments