Add Retro Shift (Auto Shift for Tap Hold via Retro Tapping) and Custom Auto Shifts (#11059)

* Add Retro Shift and Custom Auto Shifts

* Fix compilation errors with no RETRO_SHIFT value
This commit is contained in:
Isaac Elenbaas 2021-11-25 07:12:14 -05:00 committed by GitHub
parent 282e916d86
commit d9393b8684
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 623 additions and 127 deletions

View File

@ -26,20 +26,26 @@ down will repeat the shifted key, though this can be disabled with
once then immediately (within `TAPPING_TERM`) hold it down again (this works once then immediately (within `TAPPING_TERM`) hold it down again (this works
with the shifted value as well if auto-repeat is disabled). with the shifted value as well if auto-repeat is disabled).
There are also the `get_auto_shift_repeat` and `get_auto_shift_no_auto_repeat`
functions for more granular control. Neither will have an effect unless
`AUTO_SHIFT_REPEAT_PER_KEY` or `AUTO_SHIFT_NO_AUTO_REPEAT_PER_KEY` respectively
are defined.
## Are There Limitations to Auto Shift? ## Are There Limitations to Auto Shift?
Yes, unfortunately. Yes, unfortunately.
You will have characters that are shifted when you did not intend on shifting, and 1. You will have characters that are shifted when you did not intend on shifting, and
other characters you wanted shifted, but were not. This simply comes down to other characters you wanted shifted, but were not. This simply comes down to
practice. As we get in a hurry, we think we have hit the key long enough for a practice. As we get in a hurry, we think we have hit the key long enough for a
shifted version, but we did not. On the other hand, we may think we are tapping shifted version, but we did not. On the other hand, we may think we are tapping
the keys, but really we have held it for a little longer than anticipated. the keys, but really we have held it for a little longer than anticipated.
2. Additionally, with keyrepeat the desired shift state can get mixed up. It will
Additionally, with keyrepeat the desired shift state can get mixed up. It will always 'belong' to the last key pressed. For example, keyrepeating a capital
always 'belong' to the last key pressed. For example, keyrepeating a capital and then tapping something lowercase (whether or not it's an Auto Shift key)
and then tapping something lowercase (whether or not it's an Auto Shift key) will result in the capital's *key* still being held, but shift not.
will result in the capital's *key* still being held, but shift not. 3. Auto Shift does not apply to Tap Hold keys. For automatic shifting of Tap Hold
keys see [Retro Shift](#retro-shift).
## How Do I Enable Auto Shift? ## How Do I Enable Auto Shift?
@ -96,6 +102,34 @@ quicker than normal and you will be set.
?> Auto Shift has three special keys that can help you get this value right very quick. See "Auto Shift Setup" for more details! ?> Auto Shift has three special keys that can help you get this value right very quick. See "Auto Shift Setup" for more details!
For more granular control of this feature, you can add the following to your `config.h`:
```c
#define AUTO_SHIFT_TIMEOUT_PER_KEY
```
You can then add the following function to your keymap:
```c
uint16_t get_autoshift_timeout(uint16_t keycode, keyrecord_t *record) {
switch(keycode) {
case AUTO_SHIFT_NUMERIC:
return 2 * get_generic_autoshift_timeout();
case AUTO_SHIFT_SPECIAL:
return get_generic_autoshift_timeout() + 50;
case AUTO_SHIFT_ALPHA:
default:
return get_generic_autoshift_timeout();
}
}
```
Note that you cannot override individual keys that are in one of those groups
if you are using them; trying to add a case for `KC_A` in the above example will
not compile as `AUTO_SHIFT_ALPHA` is there. A possible solution is a second switch
above to handle individual keys with no default case and only referencing the
groups in the below fallback switch.
### NO_AUTO_SHIFT_SPECIAL (simple define) ### NO_AUTO_SHIFT_SPECIAL (simple define)
Do not Auto Shift special keys, which include -\_, =+, [{, ]}, ;:, '", ,<, .>, Do not Auto Shift special keys, which include -\_, =+, [{, ]}, ;:, '", ,<, .>,
@ -111,9 +145,22 @@ Do not Auto Shift alpha characters, which include A through Z.
### Auto Shift Per Key ### Auto Shift Per Key
This is a function that allows you to determine which keys shold be autoshifted, much like the tap-hold keys. There are functions that allows you to determine which keys shold be autoshifted, much like the tap-hold keys.
The default function looks like this: The first of these, used to simply add a key to Auto Shift, is `get_custom_auto_shifted_key`:
```c
bool get_custom_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
switch(keycode) {
case KC_DOT:
return true;
default:
return false;
}
}
```
For more granular control, there is `get_auto_shifted_key`. The default function looks like this:
```c ```c
bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
@ -131,9 +178,10 @@ bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
# endif # endif
return true; return true;
} }
return false; return get_custom_auto_shifted_key(keycode, record);
} }
``` ```
This functionality is enabled by default, and does not need a define. This functionality is enabled by default, and does not need a define.
### AUTO_SHIFT_REPEAT (simple define) ### AUTO_SHIFT_REPEAT (simple define)
@ -144,6 +192,106 @@ Enables keyrepeat.
Disables automatically keyrepeating when `AUTO_SHIFT_TIMEOUT` is exceeded. Disables automatically keyrepeating when `AUTO_SHIFT_TIMEOUT` is exceeded.
## Custom Shifted Values
Especially on small keyboards, the default shifted value for many keys is not
optimal. To provide more customizability, there are two user-definable
functions, `autoshift_press/release_user`. These register or unregister the
correct value for the passed key. Below is an example adding period to Auto
Shift and making its shifted value exclamation point. Make sure to use weak
mods - setting real would make any keys following it use their shifted values
as if you were holding the key. Clearing of modifiers is handled by Auto Shift,
and the OS-sent shift value if keyrepeating multiple keys is always that of
the last key pressed (whether or not it's an Auto Shift key).
You can also have non-shifted keys for the shifted values (or even no shifted
value), just don't set a shift modifier!
```c
bool get_custom_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
switch(keycode) {
case KC_DOT:
return true;
default:
return false;
}
}
void autoshift_press_user(uint16_t keycode, bool shifted, keyrecord_t *record) {
switch(keycode) {
case KC_DOT:
register_code16((!shifted) ? KC_DOT : KC_EXLM);
break;
default:
if (shifted) {
add_weak_mods(MOD_BIT(KC_LSFT));
}
// & 0xFF gets the Tap key for Tap Holds, required when using Retro Shift
register_code16((IS_RETRO(keycode)) ? keycode & 0xFF : keycode);
}
}
void autoshift_release_user(uint16_t keycode, bool shifted, keyrecord_t *record) {
switch(keycode) {
case KC_DOT:
unregister_code16((!shifted) ? KC_DOT : KC_EXLM);
break;
default:
// & 0xFF gets the Tap key for Tap Holds, required when using Retro Shift
// The IS_RETRO check isn't really necessary here, always using
// keycode & 0xFF would be fine.
unregister_code16((IS_RETRO(keycode)) ? keycode & 0xFF : keycode);
}
}
```
## Retro Shift
Holding and releasing a Tap Hold key without pressing another key will ordinarily
result in only the hold. With `retro shift` enabled this action will instead
produce a shifted version of the tap keycode on release.
It does not require [Retro Tapping](tap_hold.md#retro-tapping) to be enabled, and
if both are enabled the state of `retro tapping` will only apply if the tap keycode
is not matched by Auto Shift. `RETRO_TAPPING_PER_KEY` and its corresponding
function, however, are checked before `retro shift` is applied.
To enable `retro shift`, add the following to your `config.h`:
```c
#define RETRO_SHIFT
```
If `RETRO_SHIFT` is defined to a value, hold times greater than that value will
not produce a tap on release for Mod Taps, and instead triggers the hold action.
This enables modifiers to be held for combining with mouse clicks without
generating taps on release. For example:
```c
#define RETRO_SHIFT 500
```
This value (if set) must be greater than one's `TAPPING_TERM`, as the key press
must be designated as a 'hold' by `process_tapping` before we send the modifier.
There is no such limitation in regards to `AUTO_SHIFT_TIMEOUT` for normal keys.
### Retro Shift and Tap Hold Configurations
Tap Hold Configurations work a little differently when using Retro Shift.
Referencing `TAPPING_TERM` makes little sense, as holding longer would result in
shifting one of the keys.
`IGNORE_MOD_TAP_INTERRUPT` changes *only* rolling from a mod tap (releasing it
first), sending both keys instead of the modifier on the second. Its effects on
nested presses are ignored.
As nested taps were changed to act as though `PERMISSIVE_HOLD` is set unless only
`IGNORE_MOD_TAP_INTERRUPT` is (outside of Retro Shift), and Retro Shift ignores
`IGNORE_MOD_TAP_INTERRUPT`, `PERMISSIVE_HOLD` has no effect on Mod Taps.
Nested taps will *always* act as though the `TAPPING_TERM` was exceeded for both
Mod and Layer Tap keys.
## Using Auto Shift Setup ## Using Auto Shift Setup
This will enable you to define three keys temporarily to increase, decrease and report your `AUTO_SHIFT_TIMEOUT`. This will enable you to define three keys temporarily to increase, decrease and report your `AUTO_SHIFT_TIMEOUT`.

View File

@ -268,6 +268,10 @@ bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) {
} }
``` ```
### Retro Shift
[Auto Shift,](feature_auto_shift.md) has its own version of `retro tapping` called `retro shift`. It is extremely similar to `retro tapping`, but holding the key past `AUTO_SHIFT_TIMEOUT` results in the value it sends being shifted. Other configurations also affect it differently; see [here](feature_auto_shift.md#retro-shift) for more information.
## Why do we include the key record for the per key functions? ## Why do we include the key record for the per key functions?
One thing that you may notice is that we include the key record for all of the "per key" functions, and may be wondering why we do that. One thing that you may notice is that we include the key record for all of the "per key" functions, and may be wondering why we do that.

View File

@ -45,10 +45,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
int tp_buttons; int tp_buttons;
#if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) #if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) || (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
int retro_tapping_counter = 0; int retro_tapping_counter = 0;
#endif #endif
#if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT) && !defined(NO_ACTION_TAPPING)
# include "process_auto_shift.h"
#endif
#ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY #ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY
__attribute__((weak)) bool get_ignore_mod_tap_interrupt(uint16_t keycode, keyrecord_t *record) { return false; } __attribute__((weak)) bool get_ignore_mod_tap_interrupt(uint16_t keycode, keyrecord_t *record) { return false; }
#endif #endif
@ -69,7 +73,7 @@ void action_exec(keyevent_t event) {
dprint("EVENT: "); dprint("EVENT: ");
debug_event(event); debug_event(event);
dprintln(); dprintln();
#if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) #if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) || (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
retro_tapping_counter++; retro_tapping_counter++;
#endif #endif
} }
@ -106,6 +110,11 @@ void action_exec(keyevent_t event) {
#endif #endif
#ifndef NO_ACTION_TAPPING #ifndef NO_ACTION_TAPPING
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
if (event.pressed) {
retroshift_poll_time(&event);
}
# endif
if (IS_NOEVENT(record.event) || pre_process_record_quantum(&record)) { if (IS_NOEVENT(record.event) || pre_process_record_quantum(&record)) {
action_tapping_process(record); action_tapping_process(record);
} }
@ -730,7 +739,7 @@ void process_action(keyrecord_t *record, action_t action) {
#endif #endif
#ifndef NO_ACTION_TAPPING #ifndef NO_ACTION_TAPPING
# if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) # if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) || (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
if (!is_tap_action(action)) { if (!is_tap_action(action)) {
retro_tapping_counter = 0; retro_tapping_counter = 0;
} else { } else {
@ -747,7 +756,11 @@ void process_action(keyrecord_t *record, action_t action) {
get_retro_tapping(get_event_keycode(record->event, false), record) && get_retro_tapping(get_event_keycode(record->event, false), record) &&
# endif # endif
retro_tapping_counter == 2) { retro_tapping_counter == 2) {
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
process_auto_shift(action.layer_tap.code, record);
# else
tap_code(action.layer_tap.code); tap_code(action.layer_tap.code);
# endif
} }
retro_tapping_counter = 0; retro_tapping_counter = 0;
} }

View File

@ -44,6 +44,10 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re
__attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { return false; } __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { return false; }
# endif # endif
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
# include "process_auto_shift.h"
# endif
static keyrecord_t tapping_key = {}; static keyrecord_t tapping_key = {};
static keyrecord_t waiting_buffer[WAITING_BUFFER_SIZE] = {}; static keyrecord_t waiting_buffer[WAITING_BUFFER_SIZE] = {};
static uint8_t waiting_buffer_head = 0; static uint8_t waiting_buffer_head = 0;
@ -107,12 +111,29 @@ void action_tapping_process(keyrecord_t record) {
/* return true when key event is processed or consumed. */ /* return true when key event is processed or consumed. */
bool process_tapping(keyrecord_t *keyp) { bool process_tapping(keyrecord_t *keyp) {
keyevent_t event = keyp->event; keyevent_t event = keyp->event;
# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(TAPPING_TERM_PER_KEY) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(TAPPING_FORCE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEYPRESS_PER_KEY)
uint16_t tapping_keycode = get_record_keycode(&tapping_key, false);
# endif
// if tapping // if tapping
if (IS_TAPPING_PRESSED()) { if (IS_TAPPING_PRESSED()) {
if (WITHIN_TAPPING_TERM(event)) { // clang-format off
if (WITHIN_TAPPING_TERM(event)
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
|| (
# ifdef RETRO_TAPPING_PER_KEY
get_retro_tapping(tapping_keycode, keyp) &&
# endif
(RETRO_SHIFT + 0) != 0 && TIMER_DIFF_16(event.time, tapping_key.event.time) < (RETRO_SHIFT + 0)
)
# endif
) {
// clang-format on
if (tapping_key.tap.count == 0) { if (tapping_key.tap.count == 0) {
if (IS_TAPPING_RECORD(keyp) && !event.pressed) { if (IS_TAPPING_RECORD(keyp) && !event.pressed) {
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
retroshift_swap_times();
# endif
// first tap! // first tap!
debug("Tapping: First tap(0->1).\n"); debug("Tapping: First tap(0->1).\n");
tapping_key.tap.count = 1; tapping_key.tap.count = 1;
@ -128,22 +149,70 @@ bool process_tapping(keyrecord_t *keyp) {
* This can register the key before settlement of tapping, * This can register the key before settlement of tapping,
* useful for long TAPPING_TERM but may prevent fast typing. * useful for long TAPPING_TERM but may prevent fast typing.
*/ */
# if defined(TAPPING_TERM_PER_KEY) || (TAPPING_TERM >= 500) || defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY) // clang-format off
else if ((( # if defined(TAPPING_TERM_PER_KEY) || (TAPPING_TERM >= 500) || defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY) || (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
else if (
(
(
(
# ifdef TAPPING_TERM_PER_KEY # ifdef TAPPING_TERM_PER_KEY
get_tapping_term(get_record_keycode(&tapping_key, false), keyp) get_tapping_term(tapping_keycode, keyp)
# else # else
TAPPING_TERM TAPPING_TERM
# endif # endif
>= 500) >= 500
)
# ifdef PERMISSIVE_HOLD_PER_KEY # ifdef PERMISSIVE_HOLD_PER_KEY
|| get_permissive_hold(get_record_keycode(&tapping_key, false), keyp) || get_permissive_hold(tapping_keycode, keyp)
# elif defined(PERMISSIVE_HOLD) # elif defined(PERMISSIVE_HOLD)
|| true || true
# endif # endif
) && ) && IS_RELEASED(event) && waiting_buffer_typed(event)
IS_RELEASED(event) && waiting_buffer_typed(event)) { )
// Causes nested taps to not wait past TAPPING_TERM/RETRO_SHIFT
// unnecessarily and fixes them for Layer Taps.
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
|| (
# ifdef RETRO_TAPPING_PER_KEY
get_retro_tapping(tapping_keycode, keyp) &&
# endif
(
// Rolled over the two keys.
(
(
false
# if defined(HOLD_ON_OTHER_KEYPRESS) || defined(HOLD_ON_OTHER_KEYPRESS_PER_KEY)
|| (
IS_LT(tapping_keycode)
# ifdef HOLD_ON_OTHER_KEYPRESS_PER_KEY
&& get_hold_on_other_keypress(tapping_keycode, keyp)
# endif
)
# endif
# if !defined(IGNORE_MOD_TAP_INTERRUPT) || defined(IGNORE_MOD_TAP_INTERRUPT_PER_KEY)
|| (
IS_MT(tapping_keycode)
# ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY
&& !get_ignore_mod_tap_interrupt(tapping_keycode, keyp)
# endif
)
# endif
) && tapping_key.tap.interrupted == true
)
// Makes Retro Shift ignore [IGNORE_MOD_TAP_INTERRUPT's
// effects on nested taps for MTs and the default
// behavior of LTs] below TAPPING_TERM or RETRO_SHIFT.
|| (
IS_RETRO(tapping_keycode)
&& (event.key.col != tapping_key.event.key.col || event.key.row != tapping_key.event.key.row)
&& IS_RELEASED(event) && waiting_buffer_typed(event)
)
)
)
# endif
) {
// clang-format on
debug("Tapping: End. No tap. Interfered by typing key\n"); debug("Tapping: End. No tap. Interfered by typing key\n");
process_record(&tapping_key); process_record(&tapping_key);
tapping_key = (keyrecord_t){}; tapping_key = (keyrecord_t){};
@ -181,7 +250,7 @@ bool process_tapping(keyrecord_t *keyp) {
tapping_key.tap.interrupted = true; tapping_key.tap.interrupted = true;
# if defined(HOLD_ON_OTHER_KEY_PRESS) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY) # if defined(HOLD_ON_OTHER_KEY_PRESS) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
# if defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY) # if defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
if (get_hold_on_other_key_press(get_record_keycode(&tapping_key, false), keyp)) if (get_hold_on_other_key_press(tapping_keycode, keyp))
# endif # endif
{ {
debug("Tapping: End. No tap. Interfered by pressed key\n"); debug("Tapping: End. No tap. Interfered by pressed key\n");
@ -284,14 +353,25 @@ bool process_tapping(keyrecord_t *keyp) {
} }
} }
} else if (IS_TAPPING_RELEASED()) { } else if (IS_TAPPING_RELEASED()) {
if (WITHIN_TAPPING_TERM(event)) { // clang-format off
if (WITHIN_TAPPING_TERM(event)
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
|| (
# ifdef RETRO_TAPPING_PER_KEY
get_retro_tapping(tapping_keycode, keyp) &&
# endif
(RETRO_SHIFT + 0) != 0 && TIMER_DIFF_16(event.time, tapping_key.event.time) < (RETRO_SHIFT + 0)
)
# endif
) {
// clang-format on
if (event.pressed) { if (event.pressed) {
if (IS_TAPPING_RECORD(keyp)) { if (IS_TAPPING_RECORD(keyp)) {
//# ifndef TAPPING_FORCE_HOLD //# ifndef TAPPING_FORCE_HOLD
# if !defined(TAPPING_FORCE_HOLD) || defined(TAPPING_FORCE_HOLD_PER_KEY) # if !defined(TAPPING_FORCE_HOLD) || defined(TAPPING_FORCE_HOLD_PER_KEY)
if ( if (
# ifdef TAPPING_FORCE_HOLD_PER_KEY # ifdef TAPPING_FORCE_HOLD_PER_KEY
!get_tapping_force_hold(get_record_keycode(&tapping_key, false), keyp) && !get_tapping_force_hold(tapping_keycode, keyp) &&
# endif # endif
!tapping_key.tap.interrupted && tapping_key.tap.count > 0) { !tapping_key.tap.interrupted && tapping_key.tap.count > 0) {
// sequential tap. // sequential tap.

View File

@ -18,7 +18,6 @@
# include <stdbool.h> # include <stdbool.h>
# include <stdio.h> # include <stdio.h>
# include "process_auto_shift.h" # include "process_auto_shift.h"
# ifndef AUTO_SHIFT_DISABLED_AT_STARTUP # ifndef AUTO_SHIFT_DISABLED_AT_STARTUP
@ -27,11 +26,25 @@
# define AUTO_SHIFT_STARTUP_STATE false /* disabled */ # define AUTO_SHIFT_STARTUP_STATE false /* disabled */
# endif # endif
static uint16_t autoshift_time = 0; // Stores the last Auto Shift key's up or down time, for evaluation or keyrepeat.
static uint16_t autoshift_timeout = AUTO_SHIFT_TIMEOUT; static uint16_t autoshift_time = 0;
static uint16_t autoshift_lastkey = KC_NO; # if defined(RETRO_SHIFT) && !defined(NO_ACTION_TAPPING)
// Stores the last key's up or down time, to replace autoshift_time so that Tap Hold times are accurate.
static uint16_t retroshift_time = 0;
// Stores a possibly Retro Shift key's up or down time, as retroshift_time needs
// to be set before the Retro Shift key is evaluated if it is interrupted by an
// Auto Shifted key.
static uint16_t last_retroshift_time;
# endif
static uint16_t autoshift_timeout = AUTO_SHIFT_TIMEOUT;
static uint16_t autoshift_lastkey = KC_NO;
static keyrecord_t autoshift_lastrecord;
// Keys take 8 bits if modifiers are excluded. This records the shift state
// when pressed for each key, so that can be passed to the release function
// and it knows which key needs to be released (if shifted is different base).
static uint16_t autoshift_shift_states[((1 << 8) + 15) / 16];
static struct { static struct {
// Whether autoshift is enabled. // Whether Auto Shift is enabled.
bool enabled : 1; bool enabled : 1;
// Whether the last auto-shifted key was released after the timeout. This // Whether the last auto-shifted key was released after the timeout. This
// is used to replicate the last key for a tap-then-hold. // is used to replicate the last key for a tap-then-hold.
@ -40,43 +53,157 @@ static struct {
bool in_progress : 1; bool in_progress : 1;
// Whether the auto-shifted keypress has been registered. // Whether the auto-shifted keypress has been registered.
bool holding_shift : 1; bool holding_shift : 1;
} autoshift_flags = {AUTO_SHIFT_STARTUP_STATE, false, false, false}; // Whether the user is holding a shift and we removed it.
bool cancelling_lshift : 1;
bool cancelling_rshift : 1;
// clang-format wants to remove the true for some reason.
// clang-format off
} autoshift_flags = {AUTO_SHIFT_STARTUP_STATE, false, false, false, false, false};
// clang-format on
/** \brief Called on physical press, returns whether key should be added to Auto Shift */
__attribute__((weak)) bool get_custom_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { return false; }
/** \brief Called on physical press, returns whether is Auto Shift key */
__attribute__((weak)) bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
# ifndef NO_AUTO_SHIFT_ALPHA
case AUTO_SHIFT_ALPHA:
# endif
# ifndef NO_AUTO_SHIFT_NUMERIC
case AUTO_SHIFT_NUMERIC:
# endif
# ifndef NO_AUTO_SHIFT_SPECIAL
case AUTO_SHIFT_SPECIAL:
# endif
return true;
}
return get_custom_auto_shifted_key(keycode, record);
}
/** \brief Called to check whether defines should apply if PER_KEY is set for it */
__attribute__((weak)) bool get_auto_shift_repeat(uint16_t keycode, keyrecord_t *record) { return true; }
__attribute__((weak)) bool get_auto_shift_no_auto_repeat(uint16_t keycode, keyrecord_t *record) { return true; }
/** \brief Called when an Auto Shift key needs to be pressed */
__attribute__((weak)) void autoshift_press_user(uint16_t keycode, bool shifted, keyrecord_t *record) {
if (shifted) {
add_weak_mods(MOD_BIT(KC_LSFT));
}
register_code16((IS_RETRO(keycode)) ? keycode & 0xFF : keycode);
}
/** \brief Called when an Auto Shift key needs to be released */
__attribute__((weak)) void autoshift_release_user(uint16_t keycode, bool shifted, keyrecord_t *record) { unregister_code16((IS_RETRO(keycode)) ? keycode & 0xFF : keycode); }
/** \brief Sets the shift state to use when keyrepeating, required by custom shifts */
void set_autoshift_shift_state(uint16_t keycode, bool shifted) {
keycode = keycode & 0xFF;
if (shifted) {
autoshift_shift_states[keycode / 16] |= (uint16_t)1 << keycode % 16;
} else {
autoshift_shift_states[keycode / 16] &= ~((uint16_t)1 << keycode % 16);
}
}
/** \brief Gets the shift state to use when keyrepeating, required by custom shifts */
bool get_autoshift_shift_state(uint16_t keycode) {
keycode = keycode & 0xFF;
return (autoshift_shift_states[keycode / 16] & (uint16_t)1 << keycode % 16) != (uint16_t)0;
}
/** \brief Restores the shift key if it was cancelled by Auto Shift */
static void autoshift_flush_shift(void) {
autoshift_flags.holding_shift = false;
del_weak_mods(MOD_BIT(KC_LSFT));
if (autoshift_flags.cancelling_lshift) {
autoshift_flags.cancelling_lshift = false;
add_mods(MOD_BIT(KC_LSFT));
}
if (autoshift_flags.cancelling_rshift) {
autoshift_flags.cancelling_rshift = false;
add_mods(MOD_BIT(KC_RSFT));
}
send_keyboard_report();
}
/** \brief Record the press of an autoshiftable key /** \brief Record the press of an autoshiftable key
* *
* \return Whether the record should be further processed. * \return Whether the record should be further processed.
*/ */
static bool autoshift_press(uint16_t keycode, uint16_t now, keyrecord_t *record) { static bool autoshift_press(uint16_t keycode, uint16_t now, keyrecord_t *record) {
if (!autoshift_flags.enabled) { // clang-format off
return true; if ((get_mods()
} # if !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING)
| get_oneshot_mods()
# endif
) & (~MOD_BIT(KC_LSFT))
) {
// clang-format on
// Prevents keyrepeating unshifted value of key after using it in a key combo.
autoshift_lastkey = KC_NO;
# ifndef AUTO_SHIFT_MODIFIERS # ifndef AUTO_SHIFT_MODIFIERS
if (get_mods()) { // We can't return true here anymore because custom unshifted values are
return true; // possible and there's no good way to tell whether the press returned
} // true upon release.
set_autoshift_shift_state(keycode, false);
autoshift_press_user(keycode, false, record);
# if !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING)
set_oneshot_mods(get_oneshot_mods() & (~MOD_BIT(KC_LSFT)));
clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED);
# endif
return false;
# endif # endif
# ifdef AUTO_SHIFT_REPEAT
const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time);
# ifndef AUTO_SHIFT_NO_AUTO_REPEAT
if (!autoshift_flags.lastshifted) {
# endif
if (elapsed < TAPPING_TERM && keycode == autoshift_lastkey) {
// Allow a tap-then-hold for keyrepeat.
if (!autoshift_flags.lastshifted) {
register_code(autoshift_lastkey);
} else {
// Simulate pressing the shift key.
add_weak_mods(MOD_BIT(KC_LEFT_SHIFT));
register_code(autoshift_lastkey);
}
return false;
}
# ifndef AUTO_SHIFT_NO_AUTO_REPEAT
} }
// Store record to be sent to user functions if there's no release record then.
autoshift_lastrecord = *record;
autoshift_lastrecord.event.pressed = false;
autoshift_lastrecord.event.time = 0;
// clang-format off
# if defined(AUTO_SHIFT_REPEAT) || defined(AUTO_SHIFT_REPEAT_PER_KEY)
if (keycode == autoshift_lastkey &&
# ifdef AUTO_SHIFT_REPEAT_PER_KEY
get_auto_shift_repeat(autoshift_lastkey, record) &&
# endif # endif
# if !defined(AUTO_SHIFT_NO_AUTO_REPEAT) || defined(AUTO_SHIFT_NO_AUTO_REPEAT_PER_KEY)
(
!autoshift_flags.lastshifted
# ifdef AUTO_SHIFT_NO_AUTO_REPEAT_PER_KEY
|| get_auto_shift_no_auto_repeat(autoshift_lastkey, record)
# endif
) &&
# endif
TIMER_DIFF_16(now, autoshift_time) <
# ifdef TAPPING_TERM_PER_KEY
get_tapping_term(autoshift_lastkey, record)
# else
TAPPING_TERM
# endif
) {
// clang-format on
// Allow a tap-then-hold for keyrepeat.
if (get_mods() & MOD_BIT(KC_LSFT)) {
autoshift_flags.cancelling_lshift = true;
del_mods(MOD_BIT(KC_LSFT));
}
if (get_mods() & MOD_BIT(KC_RSFT)) {
autoshift_flags.cancelling_rshift = true;
del_mods(MOD_BIT(KC_RSFT));
}
// autoshift_shift_state doesn't need to be changed.
autoshift_press_user(autoshift_lastkey, autoshift_flags.lastshifted, record);
return false;
}
# endif # endif
// Use physical shift state of press event to be more like normal typing.
# if !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING)
autoshift_flags.lastshifted = (get_mods() | get_oneshot_mods()) & MOD_BIT(KC_LSFT);
set_oneshot_mods(get_oneshot_mods() & (~MOD_BIT(KC_LSFT)));
# else
autoshift_flags.lastshifted = get_mods() & MOD_BIT(KC_LSFT);
# endif
// Record the keycode so we can simulate it later. // Record the keycode so we can simulate it later.
autoshift_lastkey = keycode; autoshift_lastkey = keycode;
autoshift_time = now; autoshift_time = now;
@ -90,51 +217,70 @@ static bool autoshift_press(uint16_t keycode, uint16_t now, keyrecord_t *record)
/** \brief Registers an autoshiftable key under the right conditions /** \brief Registers an autoshiftable key under the right conditions
* *
* If the autoshift delay has elapsed, register a shift and the key. * If autoshift_timeout has elapsed, register a shift and the key.
* *
* If the autoshift key is released before the delay has elapsed, register the * If the Auto Shift key is released before the delay has elapsed, register the
* key without a shift. * key without a shift.
*
* Called on key down with keycode=KC_NO, auto-shifted key up, and timeout.
*/ */
static void autoshift_end(uint16_t keycode, uint16_t now, bool matrix_trigger) { static void autoshift_end(uint16_t keycode, uint16_t now, bool matrix_trigger, keyrecord_t *record) {
// Called on key down with KC_NO, auto-shifted key up, and timeout. if (autoshift_flags.in_progress && (keycode == autoshift_lastkey || keycode == KC_NO)) {
if (autoshift_flags.in_progress) {
// Process the auto-shiftable key. // Process the auto-shiftable key.
autoshift_flags.in_progress = false; autoshift_flags.in_progress = false;
// clang-format off
// Time since the initial press was recorded. autoshift_flags.lastshifted =
const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time); autoshift_flags.lastshifted
if (elapsed < autoshift_timeout) { || TIMER_DIFF_16(now, autoshift_time) >=
register_code(autoshift_lastkey); # ifdef AUTO_SHIFT_TIMEOUT_PER_KEY
autoshift_flags.lastshifted = false; get_autoshift_timeout(autoshift_lastkey, record)
} else { # else
// Simulate pressing the shift key. autoshift_timeout
add_weak_mods(MOD_BIT(KC_LEFT_SHIFT));
register_code(autoshift_lastkey);
autoshift_flags.lastshifted = true;
# if defined(AUTO_SHIFT_REPEAT) && !defined(AUTO_SHIFT_NO_AUTO_REPEAT)
if (matrix_trigger) {
// Prevents release.
return;
}
# endif # endif
;
// clang-format on
set_autoshift_shift_state(autoshift_lastkey, autoshift_flags.lastshifted);
if (get_mods() & MOD_BIT(KC_LSFT)) {
autoshift_flags.cancelling_lshift = true;
del_mods(MOD_BIT(KC_LSFT));
} }
if (get_mods() & MOD_BIT(KC_RSFT)) {
autoshift_flags.cancelling_rshift = true;
del_mods(MOD_BIT(KC_RSFT));
}
autoshift_press_user(autoshift_lastkey, autoshift_flags.lastshifted, record);
// clang-format off
# if (defined(AUTO_SHIFT_REPEAT) || defined(AUTO_SHIFT_REPEAT_PER_KEY)) && (!defined(AUTO_SHIFT_NO_AUTO_REPEAT) || defined(AUTO_SHIFT_NO_AUTO_REPEAT_PER_KEY))
if (matrix_trigger
# ifdef AUTO_SHIFT_REPEAT_PER_KEY
&& get_auto_shift_repeat(autoshift_lastkey, record)
# endif
# ifdef AUTO_SHIFT_NO_AUTO_REPEAT_PER_KEY
&& !get_auto_shift_no_auto_repeat(autoshift_lastkey, record)
# endif
) {
// Prevents release.
return;
}
# endif
// clang-format on
# if TAP_CODE_DELAY > 0 # if TAP_CODE_DELAY > 0
wait_ms(TAP_CODE_DELAY); wait_ms(TAP_CODE_DELAY);
# endif # endif
unregister_code(autoshift_lastkey);
del_weak_mods(MOD_BIT(KC_LEFT_SHIFT)); autoshift_release_user(autoshift_lastkey, autoshift_flags.lastshifted, record);
autoshift_flush_shift();
} else { } else {
// Release after keyrepeat. // Release after keyrepeat.
unregister_code(keycode); autoshift_release_user(keycode, get_autoshift_shift_state(keycode), record);
if (keycode == autoshift_lastkey) { if (keycode == autoshift_lastkey) {
// This will only fire when the key was the last auto-shiftable // This will only fire when the key was the last auto-shiftable
// pressed. That prevents aaaaBBBB then releasing a from unshifting // pressed. That prevents 'aaaaBBBB' then releasing a from unshifting
// later Bs (if B wasn't auto-shiftable). // later 'B's (if 'B' wasn't auto-shiftable).
del_weak_mods(MOD_BIT(KC_LEFT_SHIFT)); autoshift_flush_shift();
} }
} }
send_keyboard_report(); // del_weak_mods doesn't send one.
// Roll the autoshift_time forward for detecting tap-and-hold. // Roll the autoshift_time forward for detecting tap-and-hold.
autoshift_time = now; autoshift_time = now;
} }
@ -147,24 +293,29 @@ static void autoshift_end(uint16_t keycode, uint16_t now, bool matrix_trigger) {
*/ */
void autoshift_matrix_scan(void) { void autoshift_matrix_scan(void) {
if (autoshift_flags.in_progress) { if (autoshift_flags.in_progress) {
const uint16_t now = timer_read(); const uint16_t now = timer_read();
const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time); if (TIMER_DIFF_16(now, autoshift_time) >=
if (elapsed >= autoshift_timeout) { # ifdef AUTO_SHIFT_TIMEOUT_PER_KEY
autoshift_end(autoshift_lastkey, now, true); get_autoshift_timeout(autoshift_lastkey, &autoshift_lastrecord)
# else
autoshift_timeout
# endif
) {
autoshift_end(autoshift_lastkey, now, true, &autoshift_lastrecord);
} }
} }
} }
void autoshift_toggle(void) { void autoshift_toggle(void) {
autoshift_flags.enabled = !autoshift_flags.enabled; autoshift_flags.enabled = !autoshift_flags.enabled;
del_weak_mods(MOD_BIT(KC_LEFT_SHIFT)); autoshift_flush_shift();
} }
void autoshift_enable(void) { autoshift_flags.enabled = true; } void autoshift_enable(void) { autoshift_flags.enabled = true; }
void autoshift_disable(void) { void autoshift_disable(void) {
autoshift_flags.enabled = false; autoshift_flags.enabled = false;
del_weak_mods(MOD_BIT(KC_LEFT_SHIFT)); autoshift_flush_shift();
} }
# ifndef AUTO_SHIFT_NO_SETUP # ifndef AUTO_SHIFT_NO_SETUP
@ -179,76 +330,158 @@ void autoshift_timer_report(void) {
bool get_autoshift_state(void) { return autoshift_flags.enabled; } bool get_autoshift_state(void) { return autoshift_flags.enabled; }
uint16_t get_autoshift_timeout(void) { return autoshift_timeout; } uint16_t get_generic_autoshift_timeout() { return autoshift_timeout; }
__attribute__((weak)) uint16_t get_autoshift_timeout(uint16_t keycode, keyrecord_t *record) { return autoshift_timeout; }
void set_autoshift_timeout(uint16_t timeout) { autoshift_timeout = timeout; } void set_autoshift_timeout(uint16_t timeout) { autoshift_timeout = timeout; }
bool process_auto_shift(uint16_t keycode, keyrecord_t *record) { bool process_auto_shift(uint16_t keycode, keyrecord_t *record) {
// Note that record->event.time isn't reliable, see: // Note that record->event.time isn't reliable, see:
// https://github.com/qmk/qmk_firmware/pull/9826#issuecomment-733559550 // https://github.com/qmk/qmk_firmware/pull/9826#issuecomment-733559550
const uint16_t now = timer_read(); // clang-format off
const uint16_t now =
# if !defined(RETRO_SHIFT) || defined(NO_ACTION_TAPPING)
timer_read()
# else
(record->event.pressed) ? retroshift_time : timer_read()
# endif
;
// clang-format on
if (record->event.pressed) { if (record->event.pressed) {
if (autoshift_flags.in_progress) { if (autoshift_flags.in_progress) {
// Evaluate previous key if there is one. Doing this elsewhere is // Evaluate previous key if there is one.
// more complicated and easier to break. autoshift_end(KC_NO, now, false, &autoshift_lastrecord);
autoshift_end(KC_NO, now, false);
} }
// For pressing another key while keyrepeating shifted autoshift.
del_weak_mods(MOD_BIT(KC_LEFT_SHIFT));
switch (keycode) { switch (keycode) {
case KC_ASTG: case KC_ASTG:
autoshift_toggle(); autoshift_toggle();
return true; break;
case KC_ASON: case KC_ASON:
autoshift_enable(); autoshift_enable();
return true; break;
case KC_ASOFF: case KC_ASOFF:
autoshift_disable(); autoshift_disable();
return true; break;
# ifndef AUTO_SHIFT_NO_SETUP # ifndef AUTO_SHIFT_NO_SETUP
case KC_ASUP: case KC_ASUP:
autoshift_timeout += 5; autoshift_timeout += 5;
return true; break;
case KC_ASDN: case KC_ASDN:
autoshift_timeout -= 5; autoshift_timeout -= 5;
return true; break;
case KC_ASRP: case KC_ASRP:
autoshift_timer_report(); autoshift_timer_report();
return true; break;
# endif # endif
} }
// If Retro Shift is disabled, possible custom actions shouldn't happen.
// clang-format off
if (IS_RETRO(keycode)
# if defined(RETRO_SHIFT) && !defined(NO_ACTION_TAPPING)
// Not tapped or #defines mean that rolls should use hold action.
&& (
record->tap.count == 0
# ifdef RETRO_TAPPING_PER_KEY
|| !get_retro_tapping(keycode, record)
# endif
|| (record->tap.interrupted && (IS_LT(keycode)
# if defined(HOLD_ON_OTHER_KEY_PRESS) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
# ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
? get_hold_on_other_key_press(keycode, record)
# else
? true
# endif
# else
? false
# endif
# if defined(IGNORE_MOD_TAP_INTERRUPT) || defined(IGNORE_MOD_TAP_INTERRUPT_PER_KEY)
# ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY
: !get_ignore_mod_tap_interrupt(keycode, record)
# else
: false
# endif
# else
: true
# endif
))
)
# endif
) {
// clang-format on
autoshift_lastkey = KC_NO;
return true;
}
} else {
if (keycode == KC_LSFT) {
autoshift_flags.cancelling_lshift = false;
} else if (keycode == KC_RSFT) {
autoshift_flags.cancelling_rshift = false;
}
// Same as above (for pressed), additional checks are not needed because
// tap.count gets set to 0 in process_action
// clang-format off
else if (IS_RETRO(keycode)
# if defined(RETRO_SHIFT) && !defined(NO_ACTION_TAPPING)
&& (
record->tap.count == 0
# ifdef RETRO_TAPPING_PER_KEY
|| !get_retro_tapping(keycode, record)
# endif
)
# endif
) {
// Fixes modifiers not being applied to rolls with AUTO_SHIFT_MODIFIERS set.
# if !defined(IGNORE_MOD_TAP_INTERRUPT) || defined(IGNORE_MOD_TAP_INTERRUPT_PER_KEY)
if (autoshift_flags.in_progress
# ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY
&& !get_ignore_mod_tap_interrupt(keycode, record)
# endif
) {
autoshift_end(KC_NO, now, false, &autoshift_lastrecord);
}
# endif
// clang-format on
return true;
}
}
if (!autoshift_flags.enabled) {
return true;
} }
if (get_auto_shifted_key(keycode, record)) { if (get_auto_shifted_key(keycode, record)) {
if (record->event.pressed) { if (record->event.pressed) {
return autoshift_press(keycode, now, record); return autoshift_press(keycode, now, record);
} else { } else {
autoshift_end(keycode, now, false); autoshift_end(keycode, now, false, record);
return false; return false;
} }
} }
// Prevent keyrepeating of older keys upon non-AS key event.
// Not commented at above returns but they serve the same function.
if (record->event.pressed) {
autoshift_lastkey = KC_NO;
}
return true; return true;
} }
__attribute__((weak)) bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { # if defined(RETRO_SHIFT) && !defined(NO_ACTION_TAPPING)
switch (keycode) { // Called to record time before possible delays by action_tapping_process.
# ifndef NO_AUTO_SHIFT_ALPHA void retroshift_poll_time(keyevent_t *event) {
case KC_A ... KC_Z: last_retroshift_time = retroshift_time;
# endif retroshift_time = timer_read();
# ifndef NO_AUTO_SHIFT_NUMERIC
case KC_1 ... KC_0:
# endif
# ifndef NO_AUTO_SHIFT_SPECIAL
case KC_TAB:
case KC_MINUS ... KC_SLASH:
case KC_NONUS_BACKSLASH:
# endif
return true;
}
return false;
} }
// Used to swap the times of Retro Shifted key and Auto Shift key that interrupted it.
void retroshift_swap_times() {
if (last_retroshift_time != 0 && autoshift_flags.in_progress) {
uint16_t temp = retroshift_time;
retroshift_time = last_retroshift_time;
last_retroshift_time = temp;
}
}
# endif
#endif #endif

View File

@ -22,13 +22,31 @@
# define AUTO_SHIFT_TIMEOUT 175 # define AUTO_SHIFT_TIMEOUT 175
#endif #endif
#define IS_LT(kc) ((kc) >= QK_LAYER_TAP && (kc) <= QK_LAYER_TAP_MAX)
#define IS_MT(kc) ((kc) >= QK_MOD_TAP && (kc) <= QK_MOD_TAP_MAX)
#define IS_RETRO(kc) (IS_MT(kc) || IS_LT(kc))
#define DO_GET_AUTOSHIFT_TIMEOUT(keycode, record, ...) record
// clang-format off
#define AUTO_SHIFT_ALPHA KC_A ... KC_Z
#define AUTO_SHIFT_NUMERIC KC_1 ... KC_0
#define AUTO_SHIFT_SPECIAL \
KC_TAB: \
case KC_MINUS ... KC_SLASH: \
case KC_NONUS_BSLASH
// clang-format on
bool process_auto_shift(uint16_t keycode, keyrecord_t *record); bool process_auto_shift(uint16_t keycode, keyrecord_t *record);
void retroshift_poll_time(keyevent_t *event);
void retroshift_swap_times(void);
void autoshift_enable(void); void autoshift_enable(void);
void autoshift_disable(void); void autoshift_disable(void);
void autoshift_toggle(void); void autoshift_toggle(void);
bool get_autoshift_state(void); bool get_autoshift_state(void);
uint16_t get_autoshift_timeout(void); uint16_t get_generic_autoshift_timeout(void);
// clang-format off
uint16_t (get_autoshift_timeout)(uint16_t keycode, keyrecord_t *record);
void set_autoshift_timeout(uint16_t timeout); void set_autoshift_timeout(uint16_t timeout);
void autoshift_matrix_scan(void); void autoshift_matrix_scan(void);
bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record); bool get_custom_auto_shifted_key(uint16_t keycode, keyrecord_t *record);
// clang-format on