From 24e8179cebbdf76ff93df67c16a7ee1f1d97606b Mon Sep 17 00:00:00 2001 From: Nebuleon Fumika <2391500+Nebuleon@users.noreply.github.com> Date: Sat, 3 Feb 2024 20:49:38 -0500 Subject: [PATCH] Implement Toggle-Hold Mod keys as tap dances These keys act like the accessibility feature Sticky Keys, in that they allow the user to press the keys to lock or unlock the modifier held, but they also allow the user to simply press the key until it is depressed if holding it for longer than the `TAPPING_TERM` or if another key is pressed along with the Toggle-Hold Mod key. --- .../zsa/moonlander/keymaps/nebuleon/common.h | 12 +++ .../zsa/moonlander/keymaps/nebuleon/keymap.c | 12 +-- .../zsa/moonlander/keymaps/nebuleon/rgb.c | 37 ++++++++++ .../moonlander/keymaps/nebuleon/tap_dances.c | 73 ++++++++++++++++++- .../moonlander/keymaps/nebuleon/tap_dances.h | 11 +++ 5 files changed, 137 insertions(+), 8 deletions(-) diff --git a/keyboards/zsa/moonlander/keymaps/nebuleon/common.h b/keyboards/zsa/moonlander/keymaps/nebuleon/common.h index ade1049b74..77f9bc3679 100644 --- a/keyboards/zsa/moonlander/keymaps/nebuleon/common.h +++ b/keyboards/zsa/moonlander/keymaps/nebuleon/common.h @@ -54,4 +54,16 @@ #define IL_TOGG LED_LEVEL /* stands for Indicator Led TOGGle */ #define LC_TOGG TOGGLE_LAYER_COLOR /* stands for Layer Color TOGGle */ +/* \brief Converts a modifier bitfield for use with MT, i.e. where the side + * of all modifiers, left or right, is represented with one bit, to + * a modifier bitfield where each modifier on each side gets its own + * bit, for use with get_mods() and related functions. + * \param mods the 5-bit modifiers to convert + * \return the modifiers in 'mods', converted into an 8-bit bitfield + */ +uint8_t mt_to_mod_bits(uint8_t mods); + extern const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS]; + +extern uint8_t toggle_hold_held_mods; +extern uint8_t toggle_hold_locked_mods; diff --git a/keyboards/zsa/moonlander/keymaps/nebuleon/keymap.c b/keyboards/zsa/moonlander/keymaps/nebuleon/keymap.c index 94329c86b5..93f611dd42 100644 --- a/keyboards/zsa/moonlander/keymaps/nebuleon/keymap.c +++ b/keyboards/zsa/moonlander/keymaps/nebuleon/keymap.c @@ -29,8 +29,8 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { _______, TD_SCLC, TD_CMLA, TD_DTLP, TD_P_LB, TD_Y_UN, KC_UP, KC_LEFT, TD_F_EQ, TD_G_RB, TD_C_RP, TD_R_RA, TD_X_RC, TT_Web, KC_BSPC, TD_A_DL, TD_O_DQ, TD_E_MI, TD_U_PC, TD_I_AM, KC_DOWN, KC_RGHT, TD_D_PI, TD_H_AT, TD_T_PL, TD_N_SQ, TD_S_TI, TT_Phrs, KC_DEL, TD_CLLD, TD_Q_EX, TD_J_SL, TD_K_BS, TD_L_NB, TD_B_DG, TD_M_HA, TD_W_AS, TD_V_QU, TD_Z_RD, _______, - _______, KC_LGUI, SC_LAAP, TT_Move, TT_Nums, TT_FnMs, KC_LGUI, TT_Syms, TT_DvAc, KC_RALT, _______, TT_RGBC, - SC_LSSP, SC_LCEN, SC_TAWT, SC_LAAP, KC_LCTL, KC_LSFT + _______, KC_LGUI, SC_LAAP, TT_Move, TT_Nums, TT_FnMs, TD_LGUI, TT_Syms, TT_DvAc, KC_RALT, _______, TT_RGBC, + SC_LSSP, SC_LCEN, SC_TAWT, TD_LALT, TD_LCTL, TD_LSFT ), [L_Control] = LAYOUT_moonlander( _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, @@ -85,16 +85,16 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { _______, FR_SCLN, FR_7, FR_8, FR_9, FR_COMM, _______, _______, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, _______, _______, FR_COLN, FR_4, FR_5, FR_6, FR_DOT, _______, _______, FR_EQL, FR_PLUS, FR_MINS, FR_ASTR, FR_SLSH, _______, _______, FR_0, FR_1, FR_2, FR_3, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, _______, - _______, _______, _______, _______, _leave_, _______, KC_RGUI, _______, _______, _______, _______, _______, - _______, _______, _______, SC_LAAP, KC_RCTL, KC_RSFT + _______, _______, _______, _______, _leave_, _______, TD_LGUI, _______, _______, _______, _______, _______, + _______, _______, _______, TD_LALT, TD_LCTL, TD_LSFT ), [L_FnMouse] = LAYOUT_moonlander( _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_F9, KC_F10, KC_F11, KC_F12, XXXXXXX, _______, _______, XXXXXXX, KC_BTN1, KC_MS_U, KC_BTN2, KC_WH_U, _______, _______, KC_F5, KC_F6, KC_F7, KC_F8, XXXXXXX, _______, _______, XXXXXXX, KC_MS_L, KC_MS_D, KC_MS_R, KC_WH_D, _______, _______, KC_F1, KC_F2, KC_F3, KC_F4, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, _______, - _______, _______, _______, _______, _______, _leave_, KC_LGUI, _______, _______, _______, _______, _______, - _______, _______, _______, SC_LAAP, KC_LCTL, KC_LSFT + _______, _______, _______, _______, _______, _leave_, TD_LGUI, _______, _______, _______, _______, _______, + _______, _______, _______, TD_LALT, TD_LCTL, TD_LSFT ), [L_Web] = LAYOUT_moonlander( _______, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, _______, _______, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, _______, diff --git a/keyboards/zsa/moonlander/keymaps/nebuleon/rgb.c b/keyboards/zsa/moonlander/keymaps/nebuleon/rgb.c index 48c6bbf238..c2ebb8a3a3 100644 --- a/keyboards/zsa/moonlander/keymaps/nebuleon/rgb.c +++ b/keyboards/zsa/moonlander/keymaps/nebuleon/rgb.c @@ -414,6 +414,43 @@ void set_layer_color(void) { } break; #endif + /* Tap Dance keys implementing Toggle-Hold Mods. */ + case TD_LSFT: + case TD_LCTL: + case TD_LALT: + case TD_LGUI: { + uint8_t key_toggle_hold_mods; + switch (keycode) { + case TD_LSFT: + key_toggle_hold_mods = MOD_BIT(KC_LSFT); + break; + case TD_LCTL: + key_toggle_hold_mods = MOD_BIT(KC_LCTL); + break; + case TD_LALT: + key_toggle_hold_mods = MOD_BIT(KC_LALT); + break; + case TD_LGUI: + key_toggle_hold_mods = MOD_BIT(KC_LGUI); + break; + } + /* a) Keep the brightness of the glow if all of the + * modifiers it toggle-holds are currently locked + * and make it blink. */ + if ((toggle_hold_locked_mods & key_toggle_hold_mods) == key_toggle_hold_mods) { + hsv.v = BLINK_BRIGHTNESS(hsv.v); + } + /* b) Keep the brightness of the glow if all of the + * modifiers it toggle-holds are currently held. */ + else if ((toggle_hold_held_mods & key_toggle_hold_mods) == key_toggle_hold_mods) { + } + /* c) Reduce the brightness of the glow if any of the + * modifiers it toggle-holds isn't currently active. */ + else { + hsv.v = REDUCE_BRIGHTNESS(hsv.v); + } + break; + } default: break; } diff --git a/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.c b/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.c index 83cc0edee4..e615957fbe 100644 --- a/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.c +++ b/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.c @@ -17,8 +17,6 @@ typedef struct { uint16_t kc_hold; /* Keycode to be sent on a held key */ } tap_dance_tap_hold_t; -static_assert(GENERATE_TAP_DANCES_TAP_HOLD(LENGTH_TAP_HOLD) <= UINT8_MAX + 1, "Number of tap dances must fit within a byte"); - static tap dance_state[GENERATE_TAP_DANCES_TAP_HOLD(LENGTH_TAP_HOLD)]; void tapdance_lowlatency_press(tap_dance_state_t *state, void *user_data) { @@ -112,11 +110,82 @@ void clear_lock_reset(tap_dance_state_t *state, void *user_data) { } } +/* - - - TAP-TOGGLE MODS IMPLEMENTATION - - - */ + +typedef struct { + uint8_t mods; /* Modifiers to be toggled by this dance */ +} tap_dance_toggle_hold_mod_t; + +uint8_t toggle_hold_held_mods; +uint8_t toggle_hold_locked_mods; + +void toggle_hold_mod_press(tap_dance_state_t *state, void *user_data) { + tap_dance_toggle_hold_mod_t *dance_data = (tap_dance_toggle_hold_mod_t *) user_data; + uint8_t mods = mt_to_mod_bits(pgm_read_byte(&dance_data->mods)); + + bool are_locked = (toggle_hold_locked_mods & mods) == mods; + + toggle_hold_held_mods |= mods; + if (!are_locked) { + register_mods(mods); + } +} + +void toggle_hold_mod_release(tap_dance_state_t *state, void *user_data) { + tap_dance_toggle_hold_mod_t *dance_data = (tap_dance_toggle_hold_mod_t *) user_data; + uint8_t mods = mt_to_mod_bits(pgm_read_byte(&dance_data->mods)); + + bool are_locked = (toggle_hold_locked_mods & mods) == mods; + + toggle_hold_held_mods &= ~mods; + + /* As of when a tap dance starts, the weak mods are preserved but unapplied. + * Apply them here, otherwise we will send a report with modifiers released. + */ + add_weak_mods(state->weak_mods); +#ifndef NO_ACTION_ONESHOT + add_mods(state->oneshot_mods); +#endif + + if (state->interrupted || state->finished) { + /* If the toggle-mod key was either pressed along with another key or + * held for longer than the tapping term, do not toggle locking, but + * instead release the modifiers at the host if they are not locked. */ + if (!are_locked) { + unregister_mods(mods); + } + } else { + /* If the toggle-mod key was tapped, toggle locking. */ + if (are_locked) { + toggle_hold_locked_mods &= ~mods; + unregister_mods(mods); + } else { + toggle_hold_locked_mods |= mods; + } + } +} + +#define DATA_TOGGLE_HOLD_MOD(dance_name, mods) { (mods) }, + +#define ACTION_TOGGLE_HOLD_MOD(dance_name, mods) \ + [DN##dance_name] = { .fn = { toggle_hold_mod_press, NULL, NULL, toggle_hold_mod_release }, .user_data = (void *) &toggle_hold_mod_data[DN##dance_name - (GENERATE_TAP_DANCES_TAP_HOLD(LENGTH_TAP_HOLD))], }, + +#define LENGTH_TOGGLE_HOLD_MOD(dance_name, mods) + 1 + +const tap_dance_toggle_hold_mod_t PROGMEM toggle_hold_mod_data[] = { + GENERATE_TAP_DANCES_TOGGLE_HOLD_MOD(DATA_TOGGLE_HOLD_MOD) +}; + +/* - - - END TAP-TOGGLE MODS IMPLEMENTATION - - - */ + +static_assert(GENERATE_TAP_DANCES_TAP_HOLD(LENGTH_TAP_HOLD) + GENERATE_TAP_DANCES_TOGGLE_HOLD_MOD(LENGTH_TOGGLE_HOLD_MOD) + 1 /* the Lock dance */ <= UINT8_MAX + 1, "Number of tap dances must fit within a byte"); + const tap_dance_tap_hold_t PROGMEM tap_hold_data[] = { GENERATE_TAP_DANCES_TAP_HOLD(DATA_TAP_HOLD) }; tap_dance_action_t tap_dance_actions[] = { GENERATE_TAP_DANCES_TAP_HOLD(ACTION_TAP_HOLD) + GENERATE_TAP_DANCES_TOGGLE_HOLD_MOD(ACTION_TOGGLE_HOLD_MOD) [DN_LOCK] = { .fn = { clear_lock_press, NULL, clear_lock_reset } }, }; diff --git a/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.h b/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.h index 189270b852..b20670eefa 100644 --- a/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.h +++ b/keyboards/zsa/moonlander/keymaps/nebuleon/tap_dances.h @@ -59,18 +59,29 @@ /* On L_Web */ \ GENERATE(_RFSH, KC_F5, LCTL(KC_F5)) \ +#define GENERATE_TAP_DANCES_TOGGLE_HOLD_MOD(GENERATE) \ + /* On L_DvorakLX, right thumb cluster */ \ + GENERATE(_LCTL, MOD_LCTL) \ + GENERATE(_LSFT, MOD_LSFT) \ + GENERATE(_LALT, MOD_LALT) \ + GENERATE(_LGUI, MOD_LGUI) \ + #define DN_ENUM_TAP_DOUBLE_TAP_CUSTOM(dance_name) DN##dance_name, #define DN_ENUM_TAP_HOLD(dance_name, kc_tap, kc_hold) DN##dance_name, +#define DN_ENUM_TOGGLE_HOLD_MOD(dance_name, mods) DN##dance_name, enum tap_dances_dn { GENERATE_TAP_DANCES_TAP_HOLD(DN_ENUM_TAP_HOLD) + GENERATE_TAP_DANCES_TOGGLE_HOLD_MOD(DN_ENUM_TOGGLE_HOLD_MOD) GENERATE_TAP_DANCES_TAP_DOUBLE_TAP_CUSTOM(DN_ENUM_TAP_DOUBLE_TAP_CUSTOM) }; #define TD_ENUM_TAP_DOUBLE_TAP_CUSTOM(dance_name) TD##dance_name = TD(DN##dance_name), #define TD_ENUM_TAP_HOLD(dance_name, kc_tap, kc_hold) TD##dance_name = TD(DN##dance_name), +#define TD_ENUM_TOGGLE_HOLD_MOD(dance_name, mods) TD##dance_name = TD(DN##dance_name), enum tap_dances_td { GENERATE_TAP_DANCES_TAP_HOLD(TD_ENUM_TAP_HOLD) + GENERATE_TAP_DANCES_TOGGLE_HOLD_MOD(TD_ENUM_TOGGLE_HOLD_MOD) GENERATE_TAP_DANCES_TAP_DOUBLE_TAP_CUSTOM(TD_ENUM_TAP_DOUBLE_TAP_CUSTOM) }; \ No newline at end of file