From 089ceae1f7c15d63efa29b78d58179e635990eca Mon Sep 17 00:00:00 2001 From: Christoph Cullmann Date: Sun, 15 Sep 2024 16:40:37 +0200 Subject: try sm_td --- common/achordion.c | 368 ----------------------- common/achordion.h | 193 ------------- common/config.h | 20 +- common/keymap.h | 61 ++-- common/rules.mk | 4 +- common/sm_td.h | 834 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 880 insertions(+), 600 deletions(-) delete mode 100644 common/achordion.c delete mode 100644 common/achordion.h create mode 100644 common/sm_td.h (limited to 'common') diff --git a/common/achordion.c b/common/achordion.c deleted file mode 100644 index 07c1835..0000000 --- a/common/achordion.c +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2022-2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file achordion.c - * @brief Achordion implementation - * - * For full documentation, see - * - */ - -#include "achordion.h" - -#if !defined(IS_QK_MOD_TAP) -// Attempt to detect out-of-date QMK installation, which would fail with -// implicit-function-declaration errors in the code below. -#error "achordion: QMK version is too old to build. Please update QMK." -#else - -// Copy of the `record` and `keycode` args for the current active tap-hold key. -static keyrecord_t tap_hold_record; -static uint16_t tap_hold_keycode = KC_NO; -// Timeout timer. When it expires, the key is considered held. -static uint16_t hold_timer = 0; -// Eagerly applied mods, if any. -static uint8_t eager_mods = 0; -// Flag to determine whether another key is pressed within the timeout. -static bool pressed_another_key_before_release = false; - -#ifdef ACHORDION_STREAK -// Timer for typing streak -static uint16_t streak_timer = 0; -#else -// When disabled, is_streak is never true -#define is_streak false -#endif - -// Achordion's current state. -enum { - // A tap-hold key is pressed, but hasn't yet been settled as tapped or held. - STATE_UNSETTLED, - // Achordion is inactive. - STATE_RELEASED, - // Active tap-hold key has been settled as tapped. - STATE_TAPPING, - // Active tap-hold key has been settled as held. - STATE_HOLDING, - // This state is set while calling `process_record()`, which will recursively - // call `process_achordion()`. This state is checked so that we don't process - // events generated by Achordion and potentially create an infinite loop. - STATE_RECURSING, -}; -static uint8_t achordion_state = STATE_RELEASED; - -#ifdef ACHORDION_STREAK -static void update_streak_timer(uint16_t keycode, keyrecord_t* record) { - if (achordion_streak_continue(keycode)) { - // We use 0 to represent an unset timer, so `| 1` to force a nonzero value. - streak_timer = record->event.time | 1; - } else { - streak_timer = 0; - } -} -#endif - -// Calls `process_record()` with state set to RECURSING. -static void recursively_process_record(keyrecord_t* record, uint8_t state) { - achordion_state = STATE_RECURSING; -#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE) - int8_t mouse_key_tracker = get_auto_mouse_key_tracker(); -#endif - process_record(record); -#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE) - set_auto_mouse_key_tracker(mouse_key_tracker); -#endif - achordion_state = state; -} - -// Sends hold press event and settles the active tap-hold key as held. -static void settle_as_hold(void) { - if (eager_mods) { - // If eager mods are being applied, nothing needs to be done besides - // updating the state. - achordion_state = STATE_HOLDING; - } else { - // Create hold press event. - recursively_process_record(&tap_hold_record, STATE_HOLDING); - } -} - -// Sends tap press and release and settles the active tap-hold key as tapped. -static void settle_as_tap(void) { - if (eager_mods) { // Clear eager mods if set. -#ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE - neutralize_flashing_modifiers(get_mods()); -#endif // DUMMY_MOD_NEUTRALIZER_KEYCODE - unregister_mods(eager_mods); - eager_mods = 0; - } - - dprintln("Achordion: Plumbing tap press."); - tap_hold_record.tap.count = 1; // Revise event as a tap. - tap_hold_record.tap.interrupted = true; - // Plumb tap press event. - recursively_process_record(&tap_hold_record, STATE_TAPPING); - - send_keyboard_report(); -#if TAP_CODE_DELAY > 0 - wait_ms(TAP_CODE_DELAY); -#endif // TAP_CODE_DELAY > 0 - - dprintln("Achordion: Plumbing tap release."); - tap_hold_record.event.pressed = false; - // Plumb tap release event. - recursively_process_record(&tap_hold_record, STATE_TAPPING); -} - -bool process_achordion(uint16_t keycode, keyrecord_t* record) { - // Don't process events that Achordion generated. - if (achordion_state == STATE_RECURSING) { - return true; - } - - // Determine whether the current event is for a mod-tap or layer-tap key. - const bool is_mt = IS_QK_MOD_TAP(keycode); - const bool is_tap_hold = is_mt || IS_QK_LAYER_TAP(keycode); - // Check that this is a normal key event, don't act on combos. - const bool is_key_event = IS_KEYEVENT(record->event); - - // Event while no tap-hold key is active. - if (achordion_state == STATE_RELEASED) { - if (is_tap_hold && record->tap.count == 0 && record->event.pressed && - is_key_event) { - // A tap-hold key is pressed and considered by QMK as "held". - const uint16_t timeout = achordion_timeout(keycode); - if (timeout > 0) { - achordion_state = STATE_UNSETTLED; - // Save info about this key. - tap_hold_keycode = keycode; - tap_hold_record = *record; - hold_timer = record->event.time + timeout; - pressed_another_key_before_release = false; - eager_mods = 0; - - if (is_mt) { // Apply mods immediately if they are "eager." - const uint8_t mod = mod_config(QK_MOD_TAP_GET_MODS(keycode)); - if (achordion_eager_mod(mod)) { - eager_mods = ((mod & 0x10) == 0) ? mod : (mod << 4); - register_mods(eager_mods); - } - } - - dprintf("Achordion: Key 0x%04X pressed.%s\n", keycode, - eager_mods ? " Set eager mods." : ""); - return false; // Skip default handling. - } - } - -#ifdef ACHORDION_STREAK - update_streak_timer(keycode, record); -#endif - return true; // Otherwise, continue with default handling. - } else if (record->event.pressed && tap_hold_keycode != keycode) { - // Track whether another key was pressed while using a tap-hold key. - pressed_another_key_before_release = true; - } - - // Release of the active tap-hold key. - if (keycode == tap_hold_keycode && !record->event.pressed) { - if (eager_mods) { - dprintln("Achordion: Key released. Clearing eager mods."); - // If Retro Tapping and no other key was pressed, settle as tapped. -#if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) - if (!pressed_another_key_before_release -#ifdef RETRO_TAPPING_PER_KEY - && get_retro_tapping(tap_hold_keycode, &tap_hold_record) -#endif // RETREO_TAPPING_PER_KEY - ) { - settle_as_tap(); - } -#endif // defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) - - unregister_mods(eager_mods); - } else if (achordion_state == STATE_HOLDING) { - dprintln("Achordion: Key released. Plumbing hold release."); - tap_hold_record.event.pressed = false; - // Plumb hold release event. - recursively_process_record(&tap_hold_record, STATE_RELEASED); - } else if (!pressed_another_key_before_release) { - // No other key was pressed between the press and release of the tap-hold - // key, plumb a hold press and then a release. - dprintln("Achordion: Key released. Plumbing hold press and release."); - recursively_process_record(&tap_hold_record, STATE_HOLDING); - tap_hold_record.event.pressed = false; - recursively_process_record(&tap_hold_record, STATE_RELEASED); - } else { - dprintln("Achordion: Key released."); - } - - achordion_state = STATE_RELEASED; - tap_hold_keycode = KC_NO; - return false; - } - - if (achordion_state == STATE_UNSETTLED && record->event.pressed) { -#ifdef ACHORDION_STREAK - const uint16_t s_timeout = - achordion_streak_chord_timeout(tap_hold_keycode, keycode); - const bool is_streak = - streak_timer && s_timeout && - !timer_expired(record->event.time, (streak_timer + s_timeout)); -#endif - - // Press event occurred on a key other than the active tap-hold key. - - // If the other key is *also* a tap-hold key and considered by QMK to be - // held, then we settle the active key as held. This way, things like - // chording multiple home row modifiers will work, but let's our logic - // consider simply a single tap-hold key as "active" at a time. - // - // Otherwise, we call `achordion_chord()` to determine whether to settle the - // tap-hold key as tapped vs. held. We implement the tap or hold by plumbing - // events back into the handling pipeline so that QMK features and other - // user code can see them. This is done by calling `process_record()`, which - // in turn calls most handlers including `process_record_user()`. - if (!is_streak && - (!is_key_event || (is_tap_hold && record->tap.count == 0) || - achordion_chord(tap_hold_keycode, &tap_hold_record, keycode, - record))) { - dprintln("Achordion: Plumbing hold press."); - settle_as_hold(); - -#ifdef REPEAT_KEY_ENABLE - // Edge case involving LT + Repeat Key: in a sequence of "LT down, other - // down" where "other" is on the other layer in the same position as - // Repeat or Alternate Repeat, the repeated keycode is set instead of the - // the one on the switched-to layer. Here we correct that. - if (get_repeat_key_count() != 0 && IS_QK_LAYER_TAP(tap_hold_keycode)) { - record->keycode = KC_NO; // Forget the repeated keycode. - clear_weak_mods(); - } -#endif // REPEAT_KEY_ENABLE - } else { - settle_as_tap(); - -#ifdef ACHORDION_STREAK - update_streak_timer(keycode, record); - if (is_streak && is_key_event && is_tap_hold && record->tap.count == 0) { - // If we are in a streak and resolved the current tap-hold key as a tap - // consider the next tap-hold key as active to be resolved next. - update_streak_timer(tap_hold_keycode, &tap_hold_record); - const uint16_t timeout = achordion_timeout(keycode); - tap_hold_keycode = keycode; - tap_hold_record = *record; - hold_timer = record->event.time + timeout; - achordion_state = STATE_UNSETTLED; - pressed_another_key_before_release = false; - return false; - } -#endif - } - - recursively_process_record(record, achordion_state); // Re-process event. - return false; // Block the original event. - } - -#ifdef ACHORDION_STREAK - // update idle timer on regular keys event - update_streak_timer(keycode, record); -#endif - return true; -} - -void achordion_task(void) { - if (achordion_state == STATE_UNSETTLED && - timer_expired(timer_read(), hold_timer)) { - dprintln("Achordion: Timeout. Plumbing hold press."); - settle_as_hold(); // Timeout expired, settle the key as held. - } - -#ifdef ACHORDION_STREAK -#define MAX_STREAK_TIMEOUT 800 - if (streak_timer && - timer_expired(timer_read(), (streak_timer + MAX_STREAK_TIMEOUT))) { - streak_timer = 0; // Expired. - } -#endif -} - -// Returns true if `pos` on the left hand of the keyboard, false if right. -static bool on_left_hand(keypos_t pos) { -#ifdef SPLIT_KEYBOARD - return pos.row < MATRIX_ROWS / 2; -#else - return (MATRIX_COLS > MATRIX_ROWS) ? pos.col < MATRIX_COLS / 2 - : pos.row < MATRIX_ROWS / 2; -#endif -} - -bool achordion_opposite_hands(const keyrecord_t* tap_hold_record, - const keyrecord_t* other_record) { - return on_left_hand(tap_hold_record->event.key) != - on_left_hand(other_record->event.key); -} - -// By default, use the BILATERAL_COMBINATIONS rule to consider the tap-hold key -// "held" only when it and the other key are on opposite hands. -__attribute__((weak)) bool achordion_chord(uint16_t tap_hold_keycode, - keyrecord_t* tap_hold_record, - uint16_t other_keycode, - keyrecord_t* other_record) { - return achordion_opposite_hands(tap_hold_record, other_record); -} - -// By default, the timeout is 1000 ms for all keys. -__attribute__((weak)) uint16_t achordion_timeout(uint16_t tap_hold_keycode) { - return 1000; -} - -// By default, Shift and Ctrl mods are eager, and Alt and GUI are not. -__attribute__((weak)) bool achordion_eager_mod(uint8_t mod) { - return (mod & (MOD_LALT | MOD_LGUI)) == 0; -} - -#ifdef ACHORDION_STREAK -__attribute__((weak)) bool achordion_streak_continue(uint16_t keycode) { - // If any mods other than shift or AltGr are held, don't continue the streak - if (get_mods() & (MOD_MASK_CG | MOD_BIT_LALT)) return false; - // This function doesn't get called for holds, so convert to tap version of - // keycodes - if (IS_QK_MOD_TAP(keycode)) keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode); - if (IS_QK_LAYER_TAP(keycode)) keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); - // Regular letters and punctuation continue the streak. - if (keycode >= KC_A && keycode <= KC_Z) return true; - switch (keycode) { - case KC_DOT: - case KC_COMMA: - case KC_QUOTE: - case KC_SPACE: - return true; - } - // All other keys end the streak - return false; -} - -__attribute__((weak)) uint16_t achordion_streak_chord_timeout( - uint16_t tap_hold_keycode, uint16_t next_keycode) { - return achordion_streak_timeout(tap_hold_keycode); -} - -__attribute__((weak)) uint16_t -achordion_streak_timeout(uint16_t tap_hold_keycode) { - return 200; -} -#endif - -#endif // version check diff --git a/common/achordion.h b/common/achordion.h deleted file mode 100644 index f1e08ec..0000000 --- a/common/achordion.h +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2022-2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file achordion.h - * @brief Achordion: Customizing the tap-hold decision. - * - * Overview - * -------- - * - * This library customizes when tap-hold keys are considered held vs. tapped - * based on the next pressed key, like Manna Harbour's Bilateral Combinations or - * ZMK's positional hold. The library works on top of QMK's existing tap-hold - * implementation. You define mod-tap and layer-tap keys as usual and use - * Achordion to fine-tune the behavior. - * - * When QMK settles a tap-hold key as held, Achordion intercepts the event. - * Achordion then revises the event as a tap or passes it along as a hold: - * - * * Chord condition: On the next key press, a customizable `achordion_chord()` - * function is called, which takes the tap-hold key and the next key pressed - * as args. When the function returns true, the tap-hold key is settled as - * held, and otherwise as tapped. - * - * * Timeout: If no other key press occurs within a timeout, the tap-hold key - * is settled as held. This is customizable with `achordion_timeout()`. - * - * Achordion only changes the behavior when QMK considered the key held. It - * changes some would-be holds to taps, but no taps to holds. - * - * @note Some QMK features handle events before the point where Achordion can - * intercept them, particularly: Combos, Key Lock, and Dynamic Macros. It's - * still possible to use these features and Achordion in your keymap, but beware - * they might behave poorly when used simultaneously with tap-hold keys. - * - * - * For full documentation, see - * - */ - -#pragma once - -#include "quantum.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Handler function for Achordion. - * - * Call this function from `process_record_user()` as - * - * #include "features/achordion.h" - * - * bool process_record_user(uint16_t keycode, keyrecord_t* record) { - * if (!process_achordion(keycode, record)) { return false; } - * // Your macros... - * return true; - * } - */ -bool process_achordion(uint16_t keycode, keyrecord_t* record); - -/** - * Matrix task function for Achordion. - * - * Call this function from `matrix_scan_user()` as - * - * void matrix_scan_user(void) { - * achordion_task(); - * } - */ -void achordion_task(void); - -/** - * Optional callback to customize which key chords are considered "held". - * - * In your keymap.c, define the callback - * - * bool achordion_chord(uint16_t tap_hold_keycode, - * keyrecord_t* tap_hold_record, - * uint16_t other_keycode, - * keyrecord_t* other_record) { - * // Conditions... - * } - * - * This callback is called if while `tap_hold_keycode` is pressed, - * `other_keycode` is pressed. Return true if the tap-hold key should be - * considered held, or false to consider it tapped. - * - * @param tap_hold_keycode Keycode of the tap-hold key. - * @param tap_hold_record keyrecord_t from the tap-hold press event. - * @param other_keycode Keycode of the other key. - * @param other_record keyrecord_t from the other key's press event. - * @return True if the tap-hold key should be considered held. - */ -bool achordion_chord(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record, - uint16_t other_keycode, keyrecord_t* other_record); - -/** - * Optional callback to define a timeout duration per keycode. - * - * In your keymap.c, define the callback - * - * uint16_t achordion_timeout(uint16_t tap_hold_keycode) { - * // ... - * } - * - * The callback determines Achordion's timeout duration for `tap_hold_keycode` - * in units of milliseconds. The timeout be in the range 0 to 32767 ms (upper - * bound is due to 16-bit timer limitations). Use a timeout of 0 to bypass - * Achordion. - * - * @param tap_hold_keycode Keycode of the tap-hold key. - * @return Timeout duration in milliseconds in the range 0 to 32767. - */ -uint16_t achordion_timeout(uint16_t tap_hold_keycode); - -/** - * Optional callback defining which mods are "eagerly" applied. - * - * This callback defines which mods are "eagerly" applied while a mod-tap - * key is still being settled. This is helpful to reduce delay particularly when - * using mod-tap keys with an external mouse. - * - * Define this callback in your keymap.c. The default callback is eager for - * Shift and Ctrl, and not for Alt and GUI: - * - * bool achordion_eager_mod(uint8_t mod) { - * return (mod & (MOD_LALT | MOD_LGUI)) == 0; - * } - * - * @note `mod` should be compared with `MOD_` prefixed codes, not `KC_` codes, - * described at . - * - * @param mod Modifier `MOD_` code. - * @return True if the modifier should be eagerly applied. - */ -bool achordion_eager_mod(uint8_t mod); - -/** - * Returns true if the args come from keys on opposite hands. - * - * @param tap_hold_record keyrecord_t from the tap-hold key's event. - * @param other_record keyrecord_t from the other key's event. - * @return True if the keys are on opposite hands. - */ -bool achordion_opposite_hands(const keyrecord_t* tap_hold_record, - const keyrecord_t* other_record); - -/** - * Suppress tap-hold mods within a *typing streak* by defining - * ACHORDION_STREAK. This can help preventing accidental mod - * activation when performing a fast tapping sequence. - * This is inspired by - * https://sunaku.github.io/home-row-mods.html#typing-streaks - * - * Enable with: - * - * #define ACHORDION_STREAK - * - * Adjust the maximum time between key events before modifiers can be enabled - * by defining the following callback in your keymap.c: - * - * uint16_t achordion_streak_chord_timeout( - * uint16_t tap_hold_keycode, uint16_t next_keycode) { - * return 200; // Default of 200 ms. - * } - */ -#ifdef ACHORDION_STREAK -uint16_t achordion_streak_chord_timeout(uint16_t tap_hold_keycode, - uint16_t next_keycode); - -bool achordion_streak_continue(uint16_t keycode); - -/** @deprecated Use `achordion_streak_chord_timeout()` instead. */ -uint16_t achordion_streak_timeout(uint16_t tap_hold_keycode); -#endif - -#ifdef __cplusplus -} -#endif diff --git a/common/config.h b/common/config.h index ab4aea0..20f7c5b 100644 --- a/common/config.h +++ b/common/config.h @@ -1,4 +1,4 @@ -/* Copyright 2022 Christoph Cullmann +/* Copyright 2024 Christoph Cullmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,19 +31,5 @@ // enable NKRO by default #define FORCE_NKRO -// settings for home row modifiers -// details see https://precondition.github.io/home-row-mods - -// detect typing streaks -#define ACHORDION_STREAK - -// Enable rapid switch from tap to hold, disables double tap hold auto-repeat. -#define QUICK_TAP_TERM 0 - -// use permissive hold together with achordion -#define PERMISSIVE_HOLD - -// delay hold/release to not mess up software -#define TAP_CODE_DELAY 10 - - +// needed for sm_td +#define MAX_DEFERRED_EXECUTORS 10 diff --git a/common/keymap.h b/common/keymap.h index b44cdf1..f09f0f5 100644 --- a/common/keymap.h +++ b/common/keymap.h @@ -1,4 +1,4 @@ -/* Copyright 2022 Christoph Cullmann +/* Copyright 2024 Christoph Cullmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,14 +23,30 @@ enum my_layers { _FN }; +// home row mods and Co. +enum custom_keycodes { + SMTD_KEYCODES_BEGIN = SAFE_RANGE, + CKC_S, + CKC_R, + CKC_N, + CKC_T, + CKC_D, + CKC_G, + CKC_C, + CKC_A, + CKC_E, + CKC_I, + SMTD_KEYCODES_END, +}; + // our keymap const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [_BASE] = LAYOUT( XXXXXXX, KC_V, KC_L, KC_H, KC_K, KC_Q, KC_J, KC_F, KC_O, KC_U, KC_COMM, XXXXXXX, - XXXXXXX, RALT_T(KC_S), LALT_T(KC_R), LCTL_T(KC_N), LSFT_T(KC_T), KC_W, KC_Y, RSFT_T(KC_C), RCTL_T(KC_A), LALT_T(KC_E), RALT_T(KC_I), XXXXXXX, - XXXXXXX, KC_Z, KC_X, KC_M, LGUI_T(KC_D), KC_B, KC_P, RGUI_T(KC_G), KC_QUOT, KC_SCLN, KC_DOT, XXXXXXX, - MO(_SYM), KC_SPC, MO(_NUM), MO(_NAV), KC_BSPC, MO(_FN) + XXXXXXX, CKC_S, CKC_R, CKC_N, CKC_T, KC_W, KC_Y, CKC_C, CKC_A, CKC_E, CKC_I, XXXXXXX, + XXXXXXX, KC_Z, KC_X, KC_M, CKC_D, KC_B, KC_P, CKC_G, KC_QUOT, KC_SCLN, KC_DOT, XXXXXXX, + MO(_SYM), KC_SPC, MO(_NUM), MO(_NAV), KC_BSPC, MO(_FN) ), [_NUM] = LAYOUT( @@ -63,23 +79,28 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { }; -#include "achordion.h" +// home row mods and Co. +// include needs above custom_keycodes declared +#include "sm_td.h" -bool process_record_user(uint16_t keycode, keyrecord_t* record) { - if (!process_achordion(keycode, record)) { return false; } - return true; +void on_smtd_action(uint16_t keycode, smtd_action action, uint8_t tap_count) { + switch (keycode) { + SMTD_MT(CKC_S, KC_S, KC_RIGHT_ALT) + SMTD_MT(CKC_R, KC_R, KC_LEFT_ALT) + SMTD_MT(CKC_N, KC_N, KC_LEFT_CTRL) + SMTD_MT(CKC_T, KC_T, KC_LSFT) + SMTD_MT(CKC_D, KC_D, KC_LEFT_GUI) + SMTD_MT(CKC_G, KC_G, KC_RIGHT_GUI) + SMTD_MT(CKC_C, KC_C, KC_RSFT) + SMTD_MT(CKC_A, KC_A, KC_RIGHT_CTRL) + SMTD_MT(CKC_E, KC_E, KC_LEFT_ALT) + SMTD_MT(CKC_I, KC_I, KC_RIGHT_ALT) + } } -void matrix_scan_user(void) { - achordion_task(); +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + if (!process_smtd(keycode, record)) { + return false; + } + return true; } - -bool achordion_chord(uint16_t tap_hold_keycode, - keyrecord_t* tap_hold_record, - uint16_t other_keycode, - keyrecord_t* other_record) { - // follow the opposite hands rule. - return on_left_hand(tap_hold_record->event.key) != - on_left_hand(other_record->event.key); -} - diff --git a/common/rules.mk b/common/rules.mk index 6854485..2042b54 100644 --- a/common/rules.mk +++ b/common/rules.mk @@ -10,8 +10,8 @@ MOUSEKEY_ENABLE = no MUSIC_ENABLE = no SPACE_CADET_ENABLE = no -# add achordion to improve home row modifiers -SRC += /home/cullmann/data/qmk/christoph-cullmann/common/achordion.c +# needed for sm_td +DEFERRED_EXEC_ENABLE = yes # good optimizations LTO_ENABLE = yes diff --git a/common/sm_td.h b/common/sm_td.h new file mode 100644 index 0000000..185fa9a --- /dev/null +++ b/common/sm_td.h @@ -0,0 +1,834 @@ +/* Copyright 2024 Stanislav Markin (https://github.com/stasmarkin) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + * Version: 0.4.0 + * Date: 2024-03-07 + */ +#pragma once + +#include QMK_KEYBOARD_H +#include "deferred_exec.h" + +#ifdef SMTD_DEBUG_ENABLED +#include "print.h" +#endif + +#ifdef SMTD_GLOBAL_SIMULTANEOUS_PRESSES_DELAY_MS +#include "timer.h" +#endif + +/* ************************************* * + * GLOBAL CONFIGURATION * + * ************************************* */ + +#ifndef SMTD_GLOBAL_SIMULTANEOUS_PRESSES_DELAY_MS +#define SMTD_GLOBAL_SIMULTANEOUS_PRESSES_DELAY_MS 0 +#endif + +#if SMTD_GLOBAL_SIMULTANEOUS_PRESSES_DELAY_MS > 0 +#define SMTD_SIMULTANEOUS_PRESSES_DELAY wait_ms(SMTD_GLOBAL_SIMULTANEOUS_PRESSES_DELAY_MS); +#else +#define SMTD_SIMULTANEOUS_PRESSES_DELAY +#endif + +#ifndef SMTD_GLOBAL_TAP_TERM +#define SMTD_GLOBAL_TAP_TERM TAPPING_TERM +#endif + +#ifndef SMTD_GLOBAL_SEQUENCE_TERM +#define SMTD_GLOBAL_SEQUENCE_TERM TAPPING_TERM / 2 +#endif + +#ifndef SMTD_GLOBAL_FOLLOWING_TAP_TERM +#define SMTD_GLOBAL_FOLLOWING_TAP_TERM TAPPING_TERM +#endif + +#ifndef SMTD_GLOBAL_RELEASE_TERM +#define SMTD_GLOBAL_RELEASE_TERM TAPPING_TERM / 4 +#endif + +#ifndef SMTD_GLOBAL_MODS_RECALL +#define SMTD_GLOBAL_MODS_RECALL true +#endif + +#ifndef SMTD_GLOBAL_AGGREGATE_TAPS +#define SMTD_GLOBAL_AGGREGATE_TAPS false +#endif + +/* ************************************* * + * DEBUG CONFIGURATION * + * ************************************* */ + +#ifdef SMTD_DEBUG_ENABLED +__attribute__((weak)) char* keycode_to_string_user(uint16_t keycode); + +char* keycode_to_string(uint16_t keycode) { + if (keycode_to_string_user) { + char* result = keycode_to_string_user(keycode); + if (result) { + return result; + } + } + + static char buffer[16]; + snprintf(buffer, sizeof(buffer), "KC_%d", keycode); + return buffer; +} +#endif + +/* ************************************* * + * USER TIMEOUT DEFINITIONS * + * ************************************* */ + +typedef enum { + SMTD_TIMEOUT_TAP, + SMTD_TIMEOUT_SEQUENCE, + SMTD_TIMEOUT_FOLLOWING_TAP, + SMTD_TIMEOUT_RELEASE, +} smtd_timeout; + +__attribute__((weak)) uint32_t get_smtd_timeout(uint16_t keycode, smtd_timeout timeout); + +uint32_t get_smtd_timeout_default(smtd_timeout timeout) { + switch (timeout) { + case SMTD_TIMEOUT_TAP: + return SMTD_GLOBAL_TAP_TERM; + case SMTD_TIMEOUT_SEQUENCE: + return SMTD_GLOBAL_SEQUENCE_TERM; + case SMTD_TIMEOUT_FOLLOWING_TAP: + return SMTD_GLOBAL_FOLLOWING_TAP_TERM; + case SMTD_TIMEOUT_RELEASE: + return SMTD_GLOBAL_RELEASE_TERM; + } + return 0; +} + +uint32_t get_smtd_timeout_or_default(uint16_t keycode, smtd_timeout timeout) { + if (get_smtd_timeout) { + return get_smtd_timeout(keycode, timeout); + } + return get_smtd_timeout_default(timeout); +} + +/* ************************************* * + * USER FEATURE FLAGS DEFINITIONS * + * ************************************* */ + +typedef enum { + SMTD_FEATURE_MODS_RECALL, + SMTD_FEATURE_AGGREGATE_TAPS, +} smtd_feature; + +__attribute__((weak)) bool smtd_feature_enabled(uint16_t keycode, smtd_feature feature); + +bool smtd_feature_enabled_default(smtd_feature feature) { + switch (feature) { + case SMTD_FEATURE_MODS_RECALL: + return SMTD_GLOBAL_MODS_RECALL; + case SMTD_FEATURE_AGGREGATE_TAPS: + return SMTD_GLOBAL_AGGREGATE_TAPS; + } + return false; +} + +bool smtd_feature_enabled_or_default(uint16_t keycode, smtd_feature feature) { + if (smtd_feature_enabled) { + return smtd_feature_enabled(keycode, feature); + } + return smtd_feature_enabled_default(feature); +} + +/* ************************************* * + * USER ACTION DEFINITIONS * + * ************************************* */ + +typedef enum { + SMTD_ACTION_TOUCH, + SMTD_ACTION_TAP, + SMTD_ACTION_HOLD, + SMTD_ACTION_RELEASE, +} smtd_action; + +#ifdef SMTD_DEBUG_ENABLED +char *smtd_action_to_string(smtd_action action) { + switch (action) { + case SMTD_ACTION_TOUCH: + return "ACT_TOUCH"; + case SMTD_ACTION_TAP: + return "ACT_TAP"; + case SMTD_ACTION_HOLD: + return "ACT_HOLD"; + case SMTD_ACTION_RELEASE: + return "ACT_RELEASE"; + } + return "ACT_UNKNOWN"; +} +#endif + +void on_smtd_action(uint16_t keycode, smtd_action action, uint8_t sequence_len); + +#ifdef SMTD_DEBUG_ENABLED +#define SMTD_ACTION(action, state) printf("%s by %s in %s\n", \ + smtd_action_to_string(action), keycode_to_string(state->macro_keycode), smtd_stage_to_string(state->stage)); \ + on_smtd_action(state->macro_keycode, action, state->sequence_len); +#else +#define SMTD_ACTION(action, state) on_smtd_action(state->macro_keycode, action, state->sequence_len); +#endif + +/* ************************************* * + * USER STATES DEFINITIONS * + * ************************************* */ + +typedef enum { + SMTD_STAGE_NONE, + SMTD_STAGE_TOUCH, + SMTD_STAGE_SEQUENCE, + SMTD_STAGE_FOLLOWING_TOUCH, + SMTD_STAGE_HOLD, + SMTD_STAGE_RELEASE, +} smtd_stage; + +#ifdef SMTD_DEBUG_ENABLED +char *smtd_stage_to_string(smtd_stage stage) { + switch (stage) { + case SMTD_STAGE_NONE: + return "STAGE_NONE"; + case SMTD_STAGE_TOUCH: + return "STAGE_TOUCH"; + case SMTD_STAGE_SEQUENCE: + return "STAGE_SEQUENCE"; + case SMTD_STAGE_FOLLOWING_TOUCH: + return "STAGE_FOL_TOUCH"; + case SMTD_STAGE_HOLD: + return "STAGE_HOLD"; + case SMTD_STAGE_RELEASE: + return "STAGE_RELEASE"; + } + return "STAGE_UNKNOWN"; +} +#endif + +typedef struct { + /** The keycode of the macro key */ + uint16_t macro_keycode; + + /** The mods before the touch action performed. Required for mod_recall feature */ + uint8_t modes_before_touch; + + /** Since touch can modify global mods, we need to save them separately to correctly restore a state before touch */ + uint8_t modes_with_touch; + + /** The length of the sequence of same key taps */ + uint8_t sequence_len; + + /** The position of key that was pressed after macro was pressed */ + keypos_t following_key; + + /** The keycode of the key that was pressed after macro was pressed */ + uint16_t following_keycode; + + /** The timeout of current stage */ + deferred_token timeout; + + /** The current stage of the state */ + smtd_stage stage; + + /** The flag that indicates that the state is frozen, so it won't handle any events */ + bool freeze; +} smtd_state; + +#define EMPTY_STATE { \ + .macro_keycode = 0, \ + .modes_before_touch = 0, \ + .modes_with_touch = 0, \ + .sequence_len = 0, \ + .following_key = MAKE_KEYPOS(0, 0), \ + .following_keycode = 0, \ + .timeout = INVALID_DEFERRED_TOKEN, \ + .stage = SMTD_STAGE_NONE, \ + .freeze = false \ +} + +/* ************************************* * + * LAYER UTILS * + * ************************************* */ + +#define RETURN_LAYER_NOT_SET 15 + +static uint8_t return_layer = RETURN_LAYER_NOT_SET; +static uint8_t return_layer_cnt = 0; + +void avoid_unused_variable_on_compile(void* ptr) { + // just touch them, so compiler won't throw "defined but not used" error + // that variables are used in macros that user may not use + if (return_layer == RETURN_LAYER_NOT_SET) return_layer = RETURN_LAYER_NOT_SET; + if (return_layer_cnt == 0) return_layer_cnt = 0; +} + +#define LAYER_PUSH(layer) \ + return_layer_cnt++; \ + if (return_layer == RETURN_LAYER_NOT_SET) { \ + return_layer = get_highest_layer(layer_state); \ + } \ + layer_move(layer); + +#define LAYER_RESTORE() \ + if (return_layer_cnt > 0) { \ + return_layer_cnt--; \ + if (return_layer_cnt == 0) { \ + layer_move(return_layer); \ + return_layer = RETURN_LAYER_NOT_SET; \ + } \ + } + +/* ************************************* * + * CORE LOGIC IMPLEMENTATION * + * ************************************* */ + +smtd_state smtd_active_states[10] = {EMPTY_STATE, EMPTY_STATE, EMPTY_STATE, EMPTY_STATE, EMPTY_STATE, + EMPTY_STATE, EMPTY_STATE, EMPTY_STATE, EMPTY_STATE, EMPTY_STATE}; +uint8_t smtd_active_states_size = 0; + + +#define DO_ACTION_TAP(state) \ + uint8_t current_mods = get_mods(); \ + if ( \ + smtd_feature_enabled_or_default(state->macro_keycode, SMTD_FEATURE_MODS_RECALL) \ + && state->modes_before_touch != current_mods \ + ) { \ + set_mods(state->modes_before_touch); \ + send_keyboard_report(); \ + \ + SMTD_SIMULTANEOUS_PRESSES_DELAY \ + SMTD_ACTION(SMTD_ACTION_TAP, state) \ + uint8_t mods_diff = get_mods() ^ state->modes_before_touch; \ + \ + SMTD_SIMULTANEOUS_PRESSES_DELAY \ + set_mods(current_mods ^ mods_diff); \ + del_mods(state->modes_with_touch); \ + send_keyboard_report(); \ + \ + state->modes_before_touch = 0; \ + state->modes_with_touch = 0; \ + } else { \ + SMTD_ACTION(SMTD_ACTION_TAP, state) \ + } + +void smtd_press_following_key(smtd_state *state, bool release) { + state->freeze = true; + keyevent_t event_press = MAKE_KEYEVENT(state->following_key.row, state->following_key.col, true); + keyrecord_t record_press = {.event = event_press}; + #ifdef SMTD_DEBUG_ENABLED + if (release) { + printf("FOLLOWING_TAP(%s) by %s in %s\n", keycode_to_string(state->following_keycode), + keycode_to_string(state->macro_keycode), smtd_stage_to_string(state->stage)); + } else { + printf("FOLLOWING_PRESS(%s) by %s in %s\n", keycode_to_string(state->following_keycode), + keycode_to_string(state->macro_keycode), smtd_stage_to_string(state->stage)); + } + #endif + process_record(&record_press); + if (release) { + keyevent_t event_release = MAKE_KEYEVENT(state->following_key.row, state->following_key.col, false); + keyrecord_t record_release = {.event = event_release}; + SMTD_SIMULTANEOUS_PRESSES_DELAY + process_record(&record_release); + } + state->freeze = false; +} + +void smtd_next_stage(smtd_state *state, smtd_stage next_stage); + +uint32_t timeout_reset_seq(uint32_t trigger_time, void *cb_arg) { + smtd_state *state = (smtd_state *) cb_arg; + state->sequence_len = 0; + + return 0; +} + +uint32_t timeout_touch(uint32_t trigger_time, void *cb_arg) { + smtd_state *state = (smtd_state *) cb_arg; + smtd_next_stage(state, SMTD_STAGE_HOLD); + return 0; +} + +uint32_t timeout_sequence(uint32_t trigger_time, void *cb_arg) { + smtd_state *state = (smtd_state *) cb_arg; + + if (smtd_feature_enabled_or_default(state->macro_keycode, SMTD_FEATURE_AGGREGATE_TAPS)) { + DO_ACTION_TAP(state); + } + + smtd_next_stage(state, SMTD_STAGE_NONE); + return 0; +} + +uint32_t timeout_following_touch(uint32_t trigger_time, void *cb_arg) { + smtd_state *state = (smtd_state *) cb_arg; + smtd_next_stage(state, SMTD_STAGE_HOLD); + + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_press_following_key(state, false); + return 0; +} + +uint32_t timeout_release(uint32_t trigger_time, void *cb_arg) { + smtd_state *state = (smtd_state *) cb_arg; + + DO_ACTION_TAP(state); + + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_press_following_key(state, false); + + smtd_next_stage(state, SMTD_STAGE_NONE); + return 0; +} + +void smtd_next_stage(smtd_state *state, smtd_stage next_stage) { + #ifdef SMTD_DEBUG_ENABLED + printf("STAGE by %s, %s -> %s\n", keycode_to_string(state->macro_keycode), + smtd_stage_to_string(state->stage),smtd_stage_to_string(next_stage)); + #endif + + deferred_token prev_token = state->timeout; + state->timeout = INVALID_DEFERRED_TOKEN; + state->stage = next_stage; + + switch (state->stage) { + case SMTD_STAGE_NONE: + for (uint8_t i = 0; i < smtd_active_states_size; i++) { + if (&smtd_active_states[i] != state) continue; + + for (uint8_t j = i; j < smtd_active_states_size - 1; j++) { + smtd_active_states[j].macro_keycode = smtd_active_states[j + 1].macro_keycode; + smtd_active_states[j].modes_before_touch = smtd_active_states[j + 1].modes_before_touch; + smtd_active_states[j].modes_with_touch = smtd_active_states[j + 1].modes_with_touch; + smtd_active_states[j].sequence_len = smtd_active_states[j + 1].sequence_len; + smtd_active_states[j].following_key = smtd_active_states[j + 1].following_key; + smtd_active_states[j].following_keycode = smtd_active_states[j + 1].following_keycode; + smtd_active_states[j].timeout = smtd_active_states[j + 1].timeout; + smtd_active_states[j].stage = smtd_active_states[j + 1].stage; + smtd_active_states[j].freeze = smtd_active_states[j + 1].freeze; + } + + smtd_active_states_size--; + smtd_state *last_state = &smtd_active_states[smtd_active_states_size]; + last_state->macro_keycode = 0; + last_state->modes_before_touch = 0; + last_state->modes_with_touch = 0; + last_state->sequence_len = 0; + last_state->following_key = MAKE_KEYPOS(0, 0); + last_state->following_keycode = 0; + last_state->timeout = INVALID_DEFERRED_TOKEN; + last_state->stage = SMTD_STAGE_NONE; + last_state->freeze = false; + + break; + } + break; + + case SMTD_STAGE_TOUCH: + state->modes_before_touch = get_mods(); + SMTD_ACTION(SMTD_ACTION_TOUCH, state) + state->modes_with_touch = get_mods() & ~state->modes_before_touch; + state->timeout = defer_exec(get_smtd_timeout_or_default(state->macro_keycode, SMTD_TIMEOUT_TAP), + timeout_touch, state); + break; + + case SMTD_STAGE_SEQUENCE: + state->timeout = defer_exec(get_smtd_timeout_or_default(state->macro_keycode, SMTD_TIMEOUT_SEQUENCE), + timeout_sequence, state); + break; + + case SMTD_STAGE_HOLD: + SMTD_ACTION(SMTD_ACTION_HOLD, state) + break; + + case SMTD_STAGE_FOLLOWING_TOUCH: + state->timeout = defer_exec(get_smtd_timeout_or_default(state->macro_keycode, SMTD_TIMEOUT_FOLLOWING_TAP), + timeout_following_touch, state); + break; + + case SMTD_STAGE_RELEASE: + state->timeout = defer_exec(get_smtd_timeout_or_default(state->macro_keycode, SMTD_TIMEOUT_RELEASE), + timeout_release, state); + break; + } + + // need to cancel after creating new timeout. There is a bug in QMK scheduling + cancel_deferred_exec(prev_token); +} + +bool process_smtd_state(uint16_t keycode, keyrecord_t *record, smtd_state *state) { + if (state->freeze) { + return true; + } + + switch (state->stage) { + case SMTD_STAGE_NONE: + if (keycode == state->macro_keycode && record->event.pressed) { + smtd_next_stage(state, SMTD_STAGE_TOUCH); + return false; + } + return true; + + case SMTD_STAGE_TOUCH: + if (keycode == state->macro_keycode && !record->event.pressed) { + smtd_next_stage(state, SMTD_STAGE_SEQUENCE); + + if (!smtd_feature_enabled_or_default(state->macro_keycode, SMTD_FEATURE_AGGREGATE_TAPS)) { + DO_ACTION_TAP(state); + } + + return false; + } + if (keycode != state->macro_keycode && record->event.pressed) { + state->following_key = record->event.key; + state->following_keycode = keycode; + smtd_next_stage(state, SMTD_STAGE_FOLLOWING_TOUCH); + return false; + } + return true; + + case SMTD_STAGE_SEQUENCE: + if (keycode == state->macro_keycode && record->event.pressed) { + state->sequence_len++; + smtd_next_stage(state, SMTD_STAGE_TOUCH); + return false; + } + if (record->event.pressed) { + if (smtd_feature_enabled_or_default(state->macro_keycode, SMTD_FEATURE_AGGREGATE_TAPS)) { + DO_ACTION_TAP(state); + } + + smtd_next_stage(state, SMTD_STAGE_NONE); + + return true; + } + return true; + + case SMTD_STAGE_FOLLOWING_TOUCH: + // At this stage, we have already pressed the macro key and the following key + // none of them is assumed to be held yet + + if (keycode == state->macro_keycode && !record->event.pressed) { + // Macro key is released, moving to the next stage + smtd_next_stage(state, SMTD_STAGE_RELEASE); + return false; + } + + if ( + keycode != state->macro_keycode + && (state->following_key.row == record->event.key.row && + state->following_key.col == record->event.key.col) + && !record->event.pressed + ) { + // Following key is released. Now we definitely know that macro key is held + // we need to execute hold the macro key and execute hold the following key + // and then press move to next stage + smtd_next_stage(state, SMTD_STAGE_HOLD); + + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_press_following_key(state, true); + + return false; + } + if ( + keycode != state->macro_keycode + && !(state->following_key.row == record->event.key.row && + state->following_key.col == record->event.key.col) + && record->event.pressed + ) { + // so, now we have 3rd key pressed + // we assume this to be hold macro key, hold following key and press the 3rd key + + // need to put first key state into HOLD stage + smtd_next_stage(state, SMTD_STAGE_HOLD); + + // then press and hold (without releasing) the following key + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_press_following_key(state, false); + + // then rerun the 3rd key press + // since we have just started hold stage, we need to simulate the press of the 3rd key again + // because by holding first two keys we might have changed a layer, so current keycode might be not actual + // if we don't do this, we might continue processing the wrong key + SMTD_SIMULTANEOUS_PRESSES_DELAY + state->freeze = true; + keyevent_t event_press = MAKE_KEYEVENT(record->event.key.row, record->event.key.col, true); + keyrecord_t record_press = {.event = event_press}; + process_record(&record_press); + state->freeze = false; + + // we have processed the 3rd key, so we intentionally return false to stop further processing + return false; + } + return true; + + case SMTD_STAGE_HOLD: + if (keycode == state->macro_keycode && !record->event.pressed) { + SMTD_ACTION(SMTD_ACTION_RELEASE, state) + + smtd_next_stage(state, SMTD_STAGE_NONE); + + return false; + } + return true; + + case SMTD_STAGE_RELEASE: + // At this stage we have just released the macro key and still holding the following key + + if (keycode == state->macro_keycode && record->event.pressed) { + DO_ACTION_TAP(state); + + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_press_following_key(state, false); + + //todo need to go to NONE stage and from NONE jump to TOUCH stage + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_next_stage(state, SMTD_STAGE_TOUCH); + + state->sequence_len = 0; + + return false; + } + if ( + keycode != state->macro_keycode + && (state->following_key.row == record->event.key.row && + state->following_key.col == record->event.key.col) + && !record->event.pressed + ) { + // Following key is released. Now we definitely know that macro key is held + // we need to execute hold the macro key and execute tap the following key + // then close the state + + SMTD_ACTION(SMTD_ACTION_HOLD, state) + + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_press_following_key(state, true); + + SMTD_SIMULTANEOUS_PRESSES_DELAY + SMTD_ACTION(SMTD_ACTION_RELEASE, state) + + smtd_next_stage(state, SMTD_STAGE_NONE); + + return false; + } + if ( + keycode != state->macro_keycode + && (state->following_key.row != record->event.key.row || + state->following_key.col != record->event.key.col) + && record->event.pressed + ) { + // at this point we have already released the macro key and still holding the following key + // and we get 3rd key pressed + // we assume this to be tap macro key, press (w/o release) following key and press (w/o release) the 3rd key + + // so we need to tap the macro key first + DO_ACTION_TAP(state) + + // then press and hold (without releasing) the following key + SMTD_SIMULTANEOUS_PRESSES_DELAY + smtd_press_following_key(state, false); + + // release current state, because the first key is already processed + smtd_next_stage(state, SMTD_STAGE_NONE); + + // then rerun the 3rd key press + // since we have just press following state, we need to simulate the press of the 3rd key again + // because by pressing second key we might have changed a layer, so current keycode might be not actual + // if we don't do this, we might continue processing the wrong key + SMTD_SIMULTANEOUS_PRESSES_DELAY + + // we also don't need to freeze the state here, because we are already put in NONE stage + keyevent_t event_press = MAKE_KEYEVENT(record->event.key.row, record->event.key.col, true); + keyrecord_t record_press = {.event = event_press}; + process_record(&record_press); + + // we have processed the 3rd key, so we intentionally return false to stop further processing + return false; + } + return true; + } + + return true; +} + +/* ************************************* * + * ENTRY POINT IMPLEMENTATION * + * ************************************* */ + +bool process_smtd(uint16_t keycode, keyrecord_t *record) { + #ifdef SMTD_DEBUG_ENABLED + printf("\n>> GOT KEY %s %s\n", keycode_to_string(keycode), record->event.pressed ? "PRESSED" : "RELEASED"); + #endif + + // check if any active state may process an event + for (uint8_t i = 0; i < smtd_active_states_size; i++) { + smtd_state *state = &smtd_active_states[i]; + if (!process_smtd_state(keycode, record, state)) { + #ifdef SMTD_DEBUG_ENABLED + printf("<< HANDLE KEY %s %s by %s\n", keycode_to_string(keycode), + record->event.pressed ? "PRESSED" : "RELEASED", keycode_to_string(state->macro_keycode)); + #endif + return false; + } + } + + // may be start a new state? A key must be just pressed + if (!record->event.pressed) { + #ifdef SMTD_DEBUG_ENABLED + printf("<< BYPASS KEY %s %s\n", keycode_to_string(keycode), record->event.pressed ? "PRESSED" : "RELEASED"); + #endif + return true; + } + + // check if the key is a macro key + if (keycode <= SMTD_KEYCODES_BEGIN || SMTD_KEYCODES_END <= keycode) { + #ifdef SMTD_DEBUG_ENABLED + printf("<< BYPASS KEY %s %s\n", keycode_to_string(keycode), record->event.pressed ? "PRESSED" : "RELEASED"); + #endif + return true; + } + + // check if the key is already handled + for (uint8_t i = 0; i < smtd_active_states_size; i++) { + if (smtd_active_states[i].macro_keycode == keycode) { + #ifdef SMTD_DEBUG_ENABLED + printf("<< ALREADY HANDELED KEY %s %s\n", keycode_to_string(keycode), record->event.pressed ? "PRESSED" : "RELEASED"); + #endif + return true; + } + } + + // create a new state and process the event + smtd_state *state = &smtd_active_states[smtd_active_states_size]; + state->macro_keycode = keycode; + smtd_active_states_size++; + + #ifdef SMTD_DEBUG_ENABLED + printf("<< CREATE STATE %s %s\n", keycode_to_string(keycode), record->event.pressed ? "PRESSED" : "RELEASED"); + #endif + return process_smtd_state(keycode, record, state); +} + +/* ************************************* * + * CUSTOMIZATION MACROS * + * ************************************* */ + +#ifdef CAPS_WORD_ENABLE +#define SMTD_TAP_16(use_cl, key) tap_code16(use_cl && is_caps_word_on() ? LSFT(key) : key) +#define SMTD_REGISTER_16(use_cl, key) register_code16(use_cl && is_caps_word_on() ? LSFT(key) : key) +#define SMTD_UNREGISTER_16(use_cl, key) unregister_code16(use_cl && is_caps_word_on() ? LSFT(key) : key) +#else +#define SMTD_TAP_16(use_cl, key) tap_code16(key) +#define SMTD_REGISTER_16(use_cl, key) register_code16(key) +#define SMTD_UNREGISTER_16(use_cl, key) unregister_code16(key) +#endif + +#define SMTD_GET_MACRO(_1, _2, _3, _4, _5, NAME, ...) NAME +#define SMTD_MT(...) SMTD_GET_MACRO(__VA_ARGS__, SMTD_MT5, SMTD_MT4, SMTD_MT3)(__VA_ARGS__) +#define SMTD_MTE(...) SMTD_GET_MACRO(__VA_ARGS__, SMTD_MTE5, SMTD_MTE4, SMTD_MTE3)(__VA_ARGS__) +#define SMTD_LT(...) SMTD_GET_MACRO(__VA_ARGS__, SMTD_LT5, SMTD_LT4, SMTD_LT3)(__VA_ARGS__) + +#define SMTD_MT3(macro_key, tap_key, mod) SMTD_MT4(macro_key, tap_key, mod, 1000) +#define SMTD_MTE3(macro_key, tap_key, mod) SMTD_MTE4(macro_key, tap_key, mod, 1000) +#define SMTD_LT3(macro_key, tap_key, layer) SMTD_LT4(macro_key, tap_key, layer, 1000) + +#define SMTD_MT4(macro_key, tap_key, mod, threshold) SMTD_MT5(macro_key, tap_key, mod, threshold, true) +#define SMTD_MTE4(macro_key, tap_key, mod, threshold) SMTD_MTE5(macro_key, tap_key, mod, threshold, true) +#define SMTD_LT4(macro_key, tap_key, layer, threshold) SMTD_LT5(macro_key, tap_key, layer, threshold, true) + +#define SMTD_MT5(macro_key, tap_key, mod, threshold, use_cl) \ + case macro_key: { \ + switch (action) { \ + case SMTD_ACTION_TOUCH: \ + break; \ + case SMTD_ACTION_TAP: \ + SMTD_TAP_16(use_cl, tap_key); \ + break; \ + case SMTD_ACTION_HOLD: \ + if (tap_count < threshold) { \ + register_mods(MOD_BIT(mod)); \ + } else { \ + SMTD_REGISTER_16(use_cl, tap_key); \ + } \ + break; \ + case SMTD_ACTION_RELEASE: \ + if (tap_count < threshold) { \ + unregister_mods(MOD_BIT(mod)); \ + } else { \ + SMTD_UNREGISTER_16(use_cl, tap_key); \ + send_keyboard_report(); \ + } \ + break; \ + } \ + break; \ + } + +#define SMTD_MTE5(macro_key, tap_key, mod, threshold, use_cl) \ + case macro_key: { \ + switch (action) { \ + case SMTD_ACTION_TOUCH: \ + register_mods(MOD_BIT(mod)); \ + break; \ + case SMTD_ACTION_TAP: \ + unregister_mods(MOD_BIT(mod)); \ + SMTD_TAP_16(use_cl, tap_key); \ + break; \ + case SMTD_ACTION_HOLD: \ + if (!(tap_count < threshold)) { \ + unregister_mods(MOD_BIT(mod)); \ + SMTD_REGISTER_16(use_cl, tap_key); \ + } \ + break; \ + case SMTD_ACTION_RELEASE: \ + if (tap_count < threshold) { \ + unregister_mods(MOD_BIT(mod)); \ + send_keyboard_report(); \ + } else { \ + SMTD_UNREGISTER_16(use_cl, tap_key); \ + } \ + break; \ + } \ + break; \ + } + +#define SMTD_LT5(macro_key, tap_key, layer, threshold, use_cl)\ + case macro_key: { \ + switch (action) { \ + case SMTD_ACTION_TOUCH: \ + break; \ + case SMTD_ACTION_TAP: \ + SMTD_TAP_16(use_cl, tap_key); \ + break; \ + case SMTD_ACTION_HOLD: \ + if (tap_count < threshold) { \ + LAYER_PUSH(layer); \ + } else { \ + SMTD_REGISTER_16(use_cl, tap_key); \ + } \ + break; \ + case SMTD_ACTION_RELEASE: \ + if (tap_count < threshold) { \ + LAYER_RESTORE(); \ + } \ + SMTD_UNREGISTER_16(use_cl, tap_key); \ + break; \ + } \ + break; \ + } + -- cgit v1.2.3