forked from forks/qmk_firmware
[Core] Add Caps Word feature to core (#16588)
Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> Co-authored-by: Drashna Jaelre <drashna@live.com>
This commit is contained in:
parent
90eef4cd15
commit
68b16bba68
|
@ -17,6 +17,7 @@ SPACE_CADET_ENABLE ?= yes
|
||||||
GRAVE_ESC_ENABLE ?= yes
|
GRAVE_ESC_ENABLE ?= yes
|
||||||
|
|
||||||
GENERIC_FEATURES = \
|
GENERIC_FEATURES = \
|
||||||
|
CAPS_WORD \
|
||||||
COMBO \
|
COMBO \
|
||||||
COMMAND \
|
COMMAND \
|
||||||
DEFERRED_EXEC \
|
DEFERRED_EXEC \
|
||||||
|
|
|
@ -81,7 +81,8 @@ OTHER_OPTION_NAMES = \
|
||||||
RGBLIGHT_FULL_POWER \
|
RGBLIGHT_FULL_POWER \
|
||||||
LTO_ENABLE \
|
LTO_ENABLE \
|
||||||
PROGRAMMABLE_BUTTON_ENABLE \
|
PROGRAMMABLE_BUTTON_ENABLE \
|
||||||
SECURE_ENABLE
|
SECURE_ENABLE \
|
||||||
|
CAPS_WORD_ENABLE
|
||||||
|
|
||||||
define NAME_ECHO
|
define NAME_ECHO
|
||||||
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
|
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"BACKLIGHT_BREATHING": {"info_key": "backlight.breathing", "value_type": "bool"},
|
"BACKLIGHT_BREATHING": {"info_key": "backlight.breathing", "value_type": "bool"},
|
||||||
"BREATHING_PERIOD": {"info_key": "backlight.breathing_period", "value_type": "int"},
|
"BREATHING_PERIOD": {"info_key": "backlight.breathing_period", "value_type": "int"},
|
||||||
"BACKLIGHT_PIN": {"info_key": "backlight.pin"},
|
"BACKLIGHT_PIN": {"info_key": "backlight.pin"},
|
||||||
|
"BOTH_SHIFTS_TURNS_ON_CAPS_WORD": {"info_key": "caps_word.both_shifts_turns_on", "value_type": "bool"},
|
||||||
|
"CAPS_WORD_IDLE_TIMEOUT": {"info_key": "caps_word.idle_timeout", "value_type": "int"},
|
||||||
"COMBO_COUNT": {"info_key": "combo.count", "value_type": "int"},
|
"COMBO_COUNT": {"info_key": "combo.count", "value_type": "int"},
|
||||||
"COMBO_TERM": {"info_key": "combo.term", "value_type": "int"},
|
"COMBO_TERM": {"info_key": "combo.term", "value_type": "int"},
|
||||||
"DEBOUNCE": {"info_key": "debounce", "value_type": "int"},
|
"DEBOUNCE": {"info_key": "debounce", "value_type": "int"},
|
||||||
|
@ -19,6 +21,7 @@
|
||||||
#"DEVICE_VER": {"info_key": "usb.device_version", "value_type": "bcd_version"},
|
#"DEVICE_VER": {"info_key": "usb.device_version", "value_type": "bcd_version"},
|
||||||
"DESCRIPTION": {"info_key": "keyboard_folder", "value_type": "str", "to_json": false},
|
"DESCRIPTION": {"info_key": "keyboard_folder", "value_type": "str", "to_json": false},
|
||||||
"DIODE_DIRECTION": {"info_key": "diode_direction"},
|
"DIODE_DIRECTION": {"info_key": "diode_direction"},
|
||||||
|
"DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD": {"info_key": "caps_word.double_tap_shift_turns_on", "value_type": "bool"},
|
||||||
"FORCE_NKRO": {"info_key": "usb.force_nkro", "value_type": "bool"},
|
"FORCE_NKRO": {"info_key": "usb.force_nkro", "value_type": "bool"},
|
||||||
"DYNAMIC_KEYMAP_EEPROM_MAX_ADDR": {"info_key": "dynamic_keymap.eeprom_max_addr", "value_type": "int"},
|
"DYNAMIC_KEYMAP_EEPROM_MAX_ADDR": {"info_key": "dynamic_keymap.eeprom_max_addr", "value_type": "int"},
|
||||||
"DYNAMIC_KEYMAP_LAYER_COUNT": {"info_key": "dynamic_keymap.layer_count", "value_type": "int"},
|
"DYNAMIC_KEYMAP_LAYER_COUNT": {"info_key": "dynamic_keymap.layer_count", "value_type": "int"},
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"BOARD": {"info_key": "board"},
|
"BOARD": {"info_key": "board"},
|
||||||
"BOOTLOADER": {"info_key": "bootloader", "warn_duplicate": false},
|
"BOOTLOADER": {"info_key": "bootloader", "warn_duplicate": false},
|
||||||
"BLUETOOTH": {"info_key": "bluetooth.driver"},
|
"BLUETOOTH": {"info_key": "bluetooth.driver"},
|
||||||
|
"CAPS_WORD_ENABLE": {"info_key": "caps_word.enabled", "value_type": "bool"},
|
||||||
"FIRMWARE_FORMAT": {"info_key": "build.firmware_format"},
|
"FIRMWARE_FORMAT": {"info_key": "build.firmware_format"},
|
||||||
"KEYBOARD_SHARED_EP": {"info_key": "usb.shared_endpoint.keyboard", "value_type": "bool"},
|
"KEYBOARD_SHARED_EP": {"info_key": "usb.shared_endpoint.keyboard", "value_type": "bool"},
|
||||||
"MOUSE_SHARED_EP": {"info_key": "usb.shared_endpoint.mouse", "value_type": "bool"},
|
"MOUSE_SHARED_EP": {"info_key": "usb.shared_endpoint.mouse", "value_type": "bool"},
|
||||||
|
|
|
@ -92,6 +92,16 @@
|
||||||
"enum": ["COL2ROW", "ROW2COL"]
|
"enum": ["COL2ROW", "ROW2COL"]
|
||||||
},
|
},
|
||||||
"debounce": {"$ref": "qmk.definitions.v1#/unsigned_int"},
|
"debounce": {"$ref": "qmk.definitions.v1#/unsigned_int"},
|
||||||
|
"caps_word": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {"type": "boolean"},
|
||||||
|
"both_shifts_turns_on": {"type": "boolean"},
|
||||||
|
"double_tap_shift_turns_on": {"type": "boolean"},
|
||||||
|
"idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
|
||||||
|
},
|
||||||
|
},
|
||||||
"combo": {
|
"combo": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
|
|
||||||
* Software Features
|
* Software Features
|
||||||
* [Auto Shift](feature_auto_shift.md)
|
* [Auto Shift](feature_auto_shift.md)
|
||||||
|
* [Caps Word](feature_caps_word.md)
|
||||||
* [Combos](feature_combo.md)
|
* [Combos](feature_combo.md)
|
||||||
* [Debounce API](feature_debounce_type.md)
|
* [Debounce API](feature_debounce_type.md)
|
||||||
* [Key Lock](feature_key_lock.md)
|
* [Key Lock](feature_key_lock.md)
|
||||||
|
|
156
docs/feature_caps_word.md
Normal file
156
docs/feature_caps_word.md
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
# Caps Word
|
||||||
|
|
||||||
|
It is often useful to type a single word in all capitals, for instance
|
||||||
|
abbreviations like "QMK", or in code, identifiers like `KC_SPC`. "Caps Word" is
|
||||||
|
a modern alternative to Caps Lock:
|
||||||
|
|
||||||
|
* Letters are capitalized while active, and Caps Word automatically disables
|
||||||
|
itself at the end of the word. That is, it stops by default once a space or
|
||||||
|
any key other than `a`--`z`, `0`--`9`, `-`, `_`, delete, or backspace is
|
||||||
|
pressed. Caps Word also disables itself if the keyboard is idle for 5 seconds.
|
||||||
|
This is configurable, see below.
|
||||||
|
|
||||||
|
* To avoid requiring a dedicated key for Caps Word, there is an option
|
||||||
|
(`BOTH_SHIFTS_TURNS_ON_CAPS_WORD`) to activate Caps Word by simultaneously
|
||||||
|
pressing both shift keys. See below for other options.
|
||||||
|
|
||||||
|
* The implementation does not use the Caps Lock (`KC_CAPS`) keycode. Caps Word
|
||||||
|
works even if you're remapping Caps Lock at the OS level to Ctrl or something
|
||||||
|
else, as Emacs and Vim users often do.
|
||||||
|
|
||||||
|
|
||||||
|
## How do I enable Caps Word :id=how-do-i-enable-caps-word
|
||||||
|
|
||||||
|
In your `rules.mk`, add:
|
||||||
|
|
||||||
|
```make
|
||||||
|
CAPS_WORD_ENABLE = yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, use one the following methods to activate Caps Word:
|
||||||
|
|
||||||
|
* **Activate by pressing a key**: Use the `CAPS_WORD` keycode (short
|
||||||
|
alias `CAPSWRD`) in your keymap.
|
||||||
|
|
||||||
|
* **Activate by pressing Left Shift + Right Shift**: Add `#define
|
||||||
|
BOTH_SHIFTS_TURNS_ON_CAPS_WORD` to config.h. You may also need to disable or
|
||||||
|
reconfigure Command, details below. Then, simultaneously pressing both left
|
||||||
|
and right shifts turns on Caps Word. This method works with the plain
|
||||||
|
`KC_LSFT` and `KC_RSFT` keycodes as well as one-shot shifts and Space Cadet
|
||||||
|
shifts. If your shift keys are mod-taps, hold both shift mod-tap keys until
|
||||||
|
the tapping term, then release them.
|
||||||
|
|
||||||
|
* **Activate by double tapping Left Shift**: Add `#define
|
||||||
|
DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD` config.h. Then, double tapping Left Shift
|
||||||
|
turns on Caps Word. This method works with `KC_LSFT` or one-shot Left Shift
|
||||||
|
`OSM(MOD_LSFT)`. To count as a double tap, the maximum time in milliseconds
|
||||||
|
between taps is `TAPPING_TERM`, or if using `TAPPING_TERM_PER_KEY`, the time
|
||||||
|
returned by `get_tapping_term()` for the shift keycode being tapped.
|
||||||
|
|
||||||
|
* **Custom activation**: You can activate Caps Word from code by calling
|
||||||
|
`caps_word_on()`. This may be used to activate Caps Word through [a
|
||||||
|
combo](feature_combo.md) or [tap dance](feature_tap_dance.md) or any means
|
||||||
|
you like.
|
||||||
|
|
||||||
|
### Troubleshooting: Command :id=troubleshooting-command
|
||||||
|
|
||||||
|
When using `BOTH_SHIFTS_TURNS_ON_CAPS_WORD`, you might see a compile message
|
||||||
|
**"BOTH_SHIFTS_TURNS_ON_CAPS_WORD and Command should not be enabled at the same
|
||||||
|
time, since both use the Left Shift + Right Shift key combination."**
|
||||||
|
|
||||||
|
Many keyboards enable the [Command feature](feature_command.md), which by
|
||||||
|
default is also activated using the Left Shift + Right Shift key combination. To
|
||||||
|
fix this conflict, please disable Command by adding in rules.mk:
|
||||||
|
|
||||||
|
```make
|
||||||
|
COMMAND_ENABLE = no
|
||||||
|
```
|
||||||
|
|
||||||
|
Or configure Command to use another key combination like Left Ctrl + Right Ctrl
|
||||||
|
by defining `IS_COMMAND()` in config.h:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Activate Command with Left Ctrl + Right Ctrl.
|
||||||
|
#define IS_COMMAND() (get_mods() == MOD_MASK_CTRL)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Customizing Caps Word :id=customizing-caps-word
|
||||||
|
|
||||||
|
### Idle timeout :id=idle-timeout
|
||||||
|
|
||||||
|
Caps Word turns off automatically if no keys are pressed for
|
||||||
|
`CAPS_WORD_IDLE_TIMEOUT` milliseconds. The default is 5000 (5 seconds).
|
||||||
|
Configure the timeout duration in config.h, for instance
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define CAPS_WORD_IDLE_TIMEOUT 3000 // 3 seconds.
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting `CAPS_WORD_IDLE_TIMEOUT` to 0 configures Caps Word to never time out.
|
||||||
|
Caps Word then remains active indefinitely until a word breaking key is pressed.
|
||||||
|
|
||||||
|
|
||||||
|
### Functions :id=functions
|
||||||
|
|
||||||
|
Functions to manipulate Caps Word:
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|-------------------------|------------------------------------------------|
|
||||||
|
| `caps_word_on()` | Turns Caps Word on. |
|
||||||
|
| `caps_word_off()` | Turns Caps Word off. |
|
||||||
|
| `caps_word_toggle()` | Toggles Caps Word. |
|
||||||
|
| `is_caps_word_on()` | Returns true if Caps Word is currently on. |
|
||||||
|
|
||||||
|
|
||||||
|
### Configure which keys are "word breaking" :id=configure-which-keys-are-word-breaking
|
||||||
|
|
||||||
|
You can define the `caps_word_press_user(uint16_t keycode)` callback to
|
||||||
|
configure which keys should be shifted and which keys are considered "word
|
||||||
|
breaking" and stop Caps Word.
|
||||||
|
|
||||||
|
The callback is called on every key press while Caps Word is active. When the
|
||||||
|
key should be shifted (that is, a letter key), the callback should call
|
||||||
|
`add_weak_mods(MOD_BIT(KC_LSFT))` to shift the key. Returning true continues the
|
||||||
|
current "word," while returning false is "word breaking" and deactivates Caps
|
||||||
|
Word. The default callback is
|
||||||
|
|
||||||
|
```c
|
||||||
|
bool caps_word_press_user(uint16_t keycode) {
|
||||||
|
switch (keycode) {
|
||||||
|
// Keycodes that continue Caps Word, with shift applied.
|
||||||
|
case KC_A ... KC_Z:
|
||||||
|
case KC_MINS:
|
||||||
|
add_weak_mods(MOD_BIT(KC_LSFT)); // Apply shift to next key.
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Keycodes that continue Caps Word, without shifting.
|
||||||
|
case KC_1 ... KC_0:
|
||||||
|
case KC_BSPC:
|
||||||
|
case KC_DEL:
|
||||||
|
case KC_UNDS:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false; // Deactivate Caps Word.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Representing Caps Word state :id=representing-caps-word-state
|
||||||
|
|
||||||
|
Define `caps_word_set_user(bool active)` to get callbacks when Caps Word turns
|
||||||
|
on or off. This is useful to represent the current Caps Word state, e.g. by
|
||||||
|
setting an LED or playing a sound. In your keymap, define
|
||||||
|
|
||||||
|
```c
|
||||||
|
void caps_word_set_user(bool active) {
|
||||||
|
if (active) {
|
||||||
|
// Do something when Caps Word activates.
|
||||||
|
} else {
|
||||||
|
// Do something when Caps Word deactivates.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -269,6 +269,14 @@ See also: [Bluetooth](feature_bluetooth.md)
|
||||||
|`OUT_USB` |USB only |
|
|`OUT_USB` |USB only |
|
||||||
|`OUT_BT` |Bluetooth only |
|
|`OUT_BT` |Bluetooth only |
|
||||||
|
|
||||||
|
## Caps Word :id=caps-word
|
||||||
|
|
||||||
|
See also: [Caps Word](feature_caps_word.md)
|
||||||
|
|
||||||
|
|Key |Aliases |Description |
|
||||||
|
|-----------|---------|------------------------------|
|
||||||
|
|`CAPS_WORD`|`CAPSWRD`|Toggles Caps Word |
|
||||||
|
|
||||||
## Dynamic Macros :id=dynamic-macros
|
## Dynamic Macros :id=dynamic-macros
|
||||||
|
|
||||||
See also: [Dynamic Macros](feature_dynamic_macros.md)
|
See also: [Dynamic Macros](feature_dynamic_macros.md)
|
||||||
|
|
80
quantum/caps_word.c
Normal file
80
quantum/caps_word.c
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2021-2022 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.
|
||||||
|
|
||||||
|
#include "caps_word.h"
|
||||||
|
|
||||||
|
/** @brief True when Caps Word is active. */
|
||||||
|
static bool caps_word_active = false;
|
||||||
|
|
||||||
|
#if CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
// Constrain timeout to a sensible range. With 16-bit timers, the longest
|
||||||
|
// timeout possible is 32768 ms, rounded here to 30000 ms = half a minute.
|
||||||
|
# if CAPS_WORD_IDLE_TIMEOUT < 100 || CAPS_WORD_IDLE_TIMEOUT > 30000
|
||||||
|
# error "CAPS_WORD_IDLE_TIMEOUT must be between 100 and 30000 ms"
|
||||||
|
# endif
|
||||||
|
|
||||||
|
/** @brief Deadline for idle timeout. */
|
||||||
|
static uint16_t idle_timer = 0;
|
||||||
|
|
||||||
|
void caps_word_task(void) {
|
||||||
|
if (caps_word_active && timer_expired(timer_read(), idle_timer)) {
|
||||||
|
caps_word_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void caps_word_reset_idle_timer(void) {
|
||||||
|
idle_timer = timer_read() + CAPS_WORD_IDLE_TIMEOUT;
|
||||||
|
}
|
||||||
|
#endif // CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
|
||||||
|
void caps_word_on(void) {
|
||||||
|
if (caps_word_active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_mods();
|
||||||
|
#ifndef NO_ACTION_ONESHOT
|
||||||
|
clear_oneshot_mods();
|
||||||
|
#endif // NO_ACTION_ONESHOT
|
||||||
|
#if CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
caps_word_reset_idle_timer();
|
||||||
|
#endif // CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
|
||||||
|
caps_word_active = true;
|
||||||
|
caps_word_set_user(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void caps_word_off(void) {
|
||||||
|
if (!caps_word_active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unregister_weak_mods(MOD_MASK_SHIFT); // Make sure weak shift is off.
|
||||||
|
caps_word_active = false;
|
||||||
|
caps_word_set_user(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void caps_word_toggle(void) {
|
||||||
|
if (caps_word_active) {
|
||||||
|
caps_word_off();
|
||||||
|
} else {
|
||||||
|
caps_word_on();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_caps_word_on(void) {
|
||||||
|
return caps_word_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) void caps_word_set_user(bool active) {}
|
43
quantum/caps_word.h
Normal file
43
quantum/caps_word.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2021-2022 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "quantum.h"
|
||||||
|
|
||||||
|
#ifndef CAPS_WORD_IDLE_TIMEOUT
|
||||||
|
# define CAPS_WORD_IDLE_TIMEOUT 5000 // Default timeout of 5 seconds.
|
||||||
|
#endif // CAPS_WORD_IDLE_TIMEOUT
|
||||||
|
|
||||||
|
#if CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
/** @brief Matrix scan task for Caps Word feature */
|
||||||
|
void caps_word_task(void);
|
||||||
|
|
||||||
|
/** @brief Resets timer for Caps Word idle timeout. */
|
||||||
|
void caps_word_reset_idle_timer(void);
|
||||||
|
#else
|
||||||
|
static inline void caps_word_task(void) {}
|
||||||
|
#endif // CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
|
||||||
|
void caps_word_on(void); /**< Activates Caps Word. */
|
||||||
|
void caps_word_off(void); /**< Deactivates Caps Word. */
|
||||||
|
void caps_word_toggle(void); /**< Toggles Caps Word. */
|
||||||
|
bool is_caps_word_on(void); /**< Gets whether currently active. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Caps Word set callback.
|
||||||
|
*
|
||||||
|
* @param active True if Caps Word is active, false otherwise
|
||||||
|
*/
|
||||||
|
void caps_word_set_user(bool active);
|
|
@ -108,6 +108,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#ifdef BLUETOOTH_ENABLE
|
#ifdef BLUETOOTH_ENABLE
|
||||||
# include "outputselect.h"
|
# include "outputselect.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CAPS_WORD_ENABLE
|
||||||
|
# include "caps_word.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static uint32_t last_input_modification_time = 0;
|
static uint32_t last_input_modification_time = 0;
|
||||||
uint32_t last_input_activity_time(void) {
|
uint32_t last_input_activity_time(void) {
|
||||||
|
@ -549,6 +552,10 @@ void quantum_task(void) {
|
||||||
autoshift_matrix_scan();
|
autoshift_matrix_scan();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CAPS_WORD_ENABLE
|
||||||
|
caps_word_task();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SECURE_ENABLE
|
#ifdef SECURE_ENABLE
|
||||||
secure_task();
|
secure_task();
|
||||||
#endif
|
#endif
|
||||||
|
|
160
quantum/process_keycode/process_caps_word.c
Normal file
160
quantum/process_keycode/process_caps_word.c
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright 2021-2022 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.
|
||||||
|
|
||||||
|
#include "process_caps_word.h"
|
||||||
|
|
||||||
|
bool process_caps_word(uint16_t keycode, keyrecord_t* record) {
|
||||||
|
if (keycode == CAPSWRD) { // Pressing CAPSWRD toggles Caps Word.
|
||||||
|
if (record->event.pressed) {
|
||||||
|
caps_word_toggle();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NO_ACTION_ONESHOT
|
||||||
|
const uint8_t mods = get_mods() | get_oneshot_mods();
|
||||||
|
#else
|
||||||
|
const uint8_t mods = get_mods();
|
||||||
|
#endif // NO_ACTION_ONESHOT
|
||||||
|
|
||||||
|
if (!is_caps_word_on()) {
|
||||||
|
// The following optionally turns on Caps Word by holding left and
|
||||||
|
// right shifts or by double tapping left shift. This way Caps Word
|
||||||
|
// may be used without needing a dedicated key and also without
|
||||||
|
// needing combos or tap dance.
|
||||||
|
|
||||||
|
#ifdef BOTH_SHIFTS_TURNS_ON_CAPS_WORD
|
||||||
|
// Many keyboards enable the Command feature by default, which also
|
||||||
|
// uses left+right shift. It can be configured to use a different
|
||||||
|
// key combination by defining IS_COMMAND(). We make a non-fatal
|
||||||
|
// warning if Command is enabled but IS_COMMAND() is *not* defined.
|
||||||
|
# if defined(COMMAND_ENABLE) && !defined(IS_COMMAND)
|
||||||
|
# pragma message "BOTH_SHIFTS_TURNS_ON_CAPS_WORD and Command should not be enabled at the same time, since both use the Left Shift + Right Shift key combination. Please disable Command, or ensure that `IS_COMMAND` is not set to (get_mods() == MOD_MASK_SHIFT)."
|
||||||
|
# else
|
||||||
|
if (mods == MOD_MASK_SHIFT
|
||||||
|
# ifdef COMMAND_ENABLE
|
||||||
|
// Don't activate Caps Word at the same time as Command.
|
||||||
|
&& !(IS_COMMAND())
|
||||||
|
# endif // COMMAND_ENABLE
|
||||||
|
) {
|
||||||
|
caps_word_on();
|
||||||
|
}
|
||||||
|
# endif // defined(COMMAND_ENABLE) && !defined(IS_COMMAND)
|
||||||
|
#endif // BOTH_SHIFTS_TURNS_ON_CAPS_WORD
|
||||||
|
|
||||||
|
#ifdef DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD
|
||||||
|
// Double tapping left shift turns on Caps Word.
|
||||||
|
//
|
||||||
|
// NOTE: This works with KC_LSFT and one-shot left shift. It
|
||||||
|
// wouldn't make sense with mod-tap or Space Cadet shift since
|
||||||
|
// double tapping would of course trigger the tapping action.
|
||||||
|
if (record->event.pressed) {
|
||||||
|
static bool tapped = false;
|
||||||
|
static uint16_t timer = 0;
|
||||||
|
if (keycode == KC_LSFT || keycode == OSM(MOD_LSFT)) {
|
||||||
|
if (tapped && !timer_expired(record->event.time, timer)) {
|
||||||
|
// Left shift was double tapped, activate Caps Word.
|
||||||
|
caps_word_on();
|
||||||
|
}
|
||||||
|
tapped = true;
|
||||||
|
timer = record->event.time + GET_TAPPING_TERM(keycode, record);
|
||||||
|
} else {
|
||||||
|
tapped = false; // Reset when any other key is pressed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
caps_word_reset_idle_timer();
|
||||||
|
#endif // CAPS_WORD_IDLE_TIMEOUT > 0
|
||||||
|
|
||||||
|
// From here on, we only take action on press events.
|
||||||
|
if (!record->event.pressed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(mods & ~MOD_MASK_SHIFT)) {
|
||||||
|
switch (keycode) {
|
||||||
|
// Ignore MO, TO, TG, TT, and OSL layer switch keys.
|
||||||
|
case QK_MOMENTARY ... QK_MOMENTARY_MAX:
|
||||||
|
case QK_TO ... QK_TO_MAX:
|
||||||
|
case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
|
||||||
|
case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
|
||||||
|
case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
#ifndef NO_ACTION_TAPPING
|
||||||
|
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
|
||||||
|
if (record->tap.count == 0) {
|
||||||
|
// Deactivate if a mod becomes active through holding
|
||||||
|
// a mod-tap key.
|
||||||
|
caps_word_off();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
keycode &= 0xff;
|
||||||
|
break;
|
||||||
|
|
||||||
|
# ifndef NO_ACTION_LAYER
|
||||||
|
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
|
||||||
|
# endif // NO_ACTION_LAYER
|
||||||
|
if (record->tap.count == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
keycode &= 0xff;
|
||||||
|
break;
|
||||||
|
#endif // NO_ACTION_TAPPING
|
||||||
|
|
||||||
|
#ifdef SWAP_HANDS_ENABLE
|
||||||
|
case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
|
||||||
|
if (keycode > 0x56F0 || record->tap.count == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
keycode &= 0xff;
|
||||||
|
break;
|
||||||
|
#endif // SWAP_HANDS_ENABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_weak_mods();
|
||||||
|
if (caps_word_press_user(keycode)) {
|
||||||
|
send_keyboard_report();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
caps_word_off();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) bool caps_word_press_user(uint16_t keycode) {
|
||||||
|
switch (keycode) {
|
||||||
|
// Keycodes that continue Caps Word, with shift applied.
|
||||||
|
case KC_A ... KC_Z:
|
||||||
|
case KC_MINS:
|
||||||
|
add_weak_mods(MOD_BIT(KC_LSFT)); // Apply shift to next key.
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Keycodes that continue Caps Word, without shifting.
|
||||||
|
case KC_1 ... KC_0:
|
||||||
|
case KC_BSPC:
|
||||||
|
case KC_DEL:
|
||||||
|
case KC_UNDS:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false; // Deactivate Caps Word.
|
||||||
|
}
|
||||||
|
}
|
37
quantum/process_keycode/process_caps_word.h
Normal file
37
quantum/process_keycode/process_caps_word.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2021-2022 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "quantum.h"
|
||||||
|
#include "caps_word.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process handler for Caps Word feature.
|
||||||
|
*
|
||||||
|
* @param keycode Keycode registered by matrix press, per keymap
|
||||||
|
* @param record keyrecord_t structure
|
||||||
|
* @return true Continue processing keycodes, and send to host
|
||||||
|
* @return false Stop processing keycodes, and don't send to host
|
||||||
|
*/
|
||||||
|
bool process_caps_word(uint16_t keycode, keyrecord_t* record);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Weak function for user-level Caps Word press modification.
|
||||||
|
*
|
||||||
|
* @param keycode Keycode registered by matrix press, per keymap
|
||||||
|
* @return true Continue Caps Word
|
||||||
|
* @return false Stop Caps Word
|
||||||
|
*/
|
||||||
|
bool caps_word_press_user(uint16_t keycode);
|
|
@ -307,6 +307,9 @@ bool process_record_quantum(keyrecord_t *record) {
|
||||||
#ifdef TERMINAL_ENABLE
|
#ifdef TERMINAL_ENABLE
|
||||||
process_terminal(keycode, record) &&
|
process_terminal(keycode, record) &&
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CAPS_WORD_ENABLE
|
||||||
|
process_caps_word(keycode, record) &&
|
||||||
|
#endif
|
||||||
#ifdef SPACE_CADET_ENABLE
|
#ifdef SPACE_CADET_ENABLE
|
||||||
process_space_cadet(keycode, record) &&
|
process_space_cadet(keycode, record) &&
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -233,6 +233,11 @@ extern layer_state_t layer_state;
|
||||||
# include "pointing_device.h"
|
# include "pointing_device.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CAPS_WORD_ENABLE
|
||||||
|
# include "caps_word.h"
|
||||||
|
# include "process_caps_word.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// For tri-layer
|
// For tri-layer
|
||||||
void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3);
|
void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3);
|
||||||
layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3);
|
layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3);
|
||||||
|
|
|
@ -602,6 +602,8 @@ enum quantum_keycodes {
|
||||||
SECURE_UNLOCK,
|
SECURE_UNLOCK,
|
||||||
SECURE_TOGGLE,
|
SECURE_TOGGLE,
|
||||||
|
|
||||||
|
CAPS_WORD,
|
||||||
|
|
||||||
// Start of custom keycode range for keyboards and keymaps - always leave at the end
|
// Start of custom keycode range for keyboards and keymaps - always leave at the end
|
||||||
SAFE_RANGE
|
SAFE_RANGE
|
||||||
};
|
};
|
||||||
|
@ -964,5 +966,6 @@ enum quantum_keycodes {
|
||||||
#define PB_32 PROGRAMMABLE_BUTTON_32
|
#define PB_32 PROGRAMMABLE_BUTTON_32
|
||||||
#define PROGRAMMABLE_BUTTON_MIN PROGRAMMABLE_BUTTON_1
|
#define PROGRAMMABLE_BUTTON_MIN PROGRAMMABLE_BUTTON_1
|
||||||
#define PROGRAMMABLE_BUTTON_MAX PROGRAMMABLE_BUTTON_32
|
#define PROGRAMMABLE_BUTTON_MAX PROGRAMMABLE_BUTTON_32
|
||||||
|
#define CAPSWRD CAPS_WORD
|
||||||
|
|
||||||
#include "quantum_keycodes_legacy.h"
|
#include "quantum_keycodes_legacy.h"
|
||||||
|
|
21
tests/caps_word/config.h
Normal file
21
tests/caps_word/config.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2022 Google LLC
|
||||||
|
//
|
||||||
|
// 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 2 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "test_common.h"
|
||||||
|
|
||||||
|
#define BOTH_SHIFTS_TURNS_ON_CAPS_WORD
|
||||||
|
#define DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD
|
19
tests/caps_word/test.mk
Normal file
19
tests/caps_word/test.mk
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright 2022 Google LLC
|
||||||
|
#
|
||||||
|
# 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 2 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
CAPS_WORD_ENABLE = yes
|
||||||
|
COMMAND_ENABLE = no
|
||||||
|
SPACE_CADET_ENABLE = yes
|
||||||
|
|
423
tests/caps_word/test_caps_word.cpp
Normal file
423
tests/caps_word/test_caps_word.cpp
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
// Copyright 2022 Google LLC
|
||||||
|
//
|
||||||
|
// 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 2 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "keyboard_report_util.hpp"
|
||||||
|
#include "keycode.h"
|
||||||
|
#include "test_common.hpp"
|
||||||
|
#include "test_fixture.hpp"
|
||||||
|
#include "test_keymap_key.hpp"
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::AnyNumber;
|
||||||
|
using ::testing::AnyOf;
|
||||||
|
using ::testing::InSequence;
|
||||||
|
using ::testing::TestParamInfo;
|
||||||
|
|
||||||
|
class CapsWord : public TestFixture {
|
||||||
|
public:
|
||||||
|
void SetUp() override {
|
||||||
|
caps_word_off();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience function to tap `key`.
|
||||||
|
void TapKey(KeymapKey key) {
|
||||||
|
key.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taps in order each key in `keys`.
|
||||||
|
template <typename... Ts>
|
||||||
|
void TapKeys(Ts... keys) {
|
||||||
|
for (KeymapKey key : {keys...}) {
|
||||||
|
TapKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tests caps_word_on(), _off(), and _toggle() functions.
|
||||||
|
TEST_F(CapsWord, OnOffToggleFuns) {
|
||||||
|
TestDriver driver;
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
|
||||||
|
caps_word_on();
|
||||||
|
EXPECT_EQ(is_caps_word_on(), true);
|
||||||
|
caps_word_on();
|
||||||
|
EXPECT_EQ(is_caps_word_on(), true);
|
||||||
|
|
||||||
|
caps_word_off();
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
caps_word_off();
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
|
||||||
|
caps_word_toggle();
|
||||||
|
EXPECT_EQ(is_caps_word_on(), true);
|
||||||
|
caps_word_toggle();
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the default `caps_word_press_user()` function.
|
||||||
|
TEST_F(CapsWord, DefaultCapsWordPressUserFun) {
|
||||||
|
// Spot check some keycodes that continue Caps Word, with shift applied.
|
||||||
|
for (uint16_t keycode : {KC_A, KC_B, KC_Z, KC_MINS}) {
|
||||||
|
SCOPED_TRACE("keycode: " + testing::PrintToString(keycode));
|
||||||
|
clear_weak_mods();
|
||||||
|
EXPECT_TRUE(caps_word_press_user(keycode));
|
||||||
|
EXPECT_EQ(get_weak_mods(), MOD_BIT(KC_LSFT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some keycodes that continue Caps Word, without shifting.
|
||||||
|
for (uint16_t keycode : {KC_1, KC_9, KC_0, KC_BSPC, KC_DEL}) {
|
||||||
|
SCOPED_TRACE("keycode: " + testing::PrintToString(keycode));
|
||||||
|
clear_weak_mods();
|
||||||
|
EXPECT_TRUE(caps_word_press_user(keycode));
|
||||||
|
EXPECT_EQ(get_weak_mods(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some keycodes that turn off Caps Word.
|
||||||
|
for (uint16_t keycode : {KC_SPC, KC_DOT, KC_COMM, KC_TAB, KC_ESC, KC_ENT}) {
|
||||||
|
SCOPED_TRACE("keycode: " + testing::PrintToString(keycode));
|
||||||
|
EXPECT_FALSE(caps_word_press_user(keycode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that `CAPSWRD` key toggles Caps Word.
|
||||||
|
TEST_F(CapsWord, CapswrdKey) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey key_capswrd(0, 0, 0, CAPSWRD);
|
||||||
|
set_keymap({key_capswrd});
|
||||||
|
|
||||||
|
// No keyboard reports should be sent.
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(_)).Times(0);
|
||||||
|
|
||||||
|
TapKey(key_capswrd); // Tap the CAPSWRD key.
|
||||||
|
EXPECT_EQ(is_caps_word_on(), true);
|
||||||
|
|
||||||
|
TapKey(key_capswrd); // Tap the CAPSWRD key again.
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that being idle for CAPS_WORD_IDLE_TIMEOUT turns off Caps Word.
|
||||||
|
TEST_F(CapsWord, IdleTimeout) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey key_a(0, 0, 0, KC_A);
|
||||||
|
set_keymap({key_a});
|
||||||
|
|
||||||
|
// Allow any number of reports with no keys or only KC_LSFT.
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Expect "Shift+A".
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
|
||||||
|
|
||||||
|
// Turn on Caps Word and tap "A".
|
||||||
|
caps_word_on();
|
||||||
|
TapKey(key_a);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
|
||||||
|
idle_for(CAPS_WORD_IDLE_TIMEOUT);
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
// Caps Word should be off and mods should be clear.
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
EXPECT_EQ(get_mods() | get_weak_mods(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber());
|
||||||
|
// Expect unshifted "A".
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_A)));
|
||||||
|
TapKey(key_a);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that typing "A, 4, A, 4" produces "Shift+A, 4, Shift+A, 4".
|
||||||
|
TEST_F(CapsWord, ShiftsLettersButNotDigits) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey key_a(0, 0, 0, KC_A);
|
||||||
|
KeymapKey key_4(0, 1, 0, KC_4);
|
||||||
|
set_keymap({key_a, key_4});
|
||||||
|
|
||||||
|
// Allow any number of reports with no keys or only KC_LSFT.
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
{ // Expect: "Shift+A, 4, Shift+A, 4".
|
||||||
|
InSequence s;
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_4)));
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on Caps Word and tap "A, 4, A, 4".
|
||||||
|
caps_word_on();
|
||||||
|
TapKeys(key_a, key_4, key_a, key_4);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that typing "A, Space, A" produces "Shift+A, Space, A".
|
||||||
|
TEST_F(CapsWord, SpaceTurnsOffCapsWord) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey key_a(0, 0, 0, KC_A);
|
||||||
|
KeymapKey key_spc(0, 1, 0, KC_SPC);
|
||||||
|
set_keymap({key_a, key_spc});
|
||||||
|
|
||||||
|
// Allow any number of reports with no keys or only KC_LSFT.
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
{ // Expect: "Shift+A, Space, A".
|
||||||
|
InSequence seq;
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_SPC)));
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_A)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on Caps Word and tap "A, Space, A".
|
||||||
|
caps_word_on();
|
||||||
|
TapKeys(key_a, key_spc, key_a);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CapsWordBothShiftsParams {
|
||||||
|
std::string name;
|
||||||
|
uint16_t left_shift_keycode;
|
||||||
|
uint16_t right_shift_keycode;
|
||||||
|
|
||||||
|
static const std::string& GetName(const TestParamInfo<CapsWordBothShiftsParams>& info) {
|
||||||
|
return info.param.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tests the BOTH_SHIFTS_TURNS_ON_CAPS_WORD method to turn on Caps Word.
|
||||||
|
class CapsWordBothShifts : public ::testing::WithParamInterface<CapsWordBothShiftsParams>, public CapsWord {};
|
||||||
|
|
||||||
|
// Pressing shifts as "Left down, Right down, Left up, Right up".
|
||||||
|
TEST_P(CapsWordBothShifts, PressLRLR) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
|
||||||
|
KeymapKey right_shift(0, 1, 0, GetParam().right_shift_keycode);
|
||||||
|
set_keymap({left_shift, right_shift});
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT),
|
||||||
|
KeyboardReport(KC_RSFT),
|
||||||
|
KeyboardReport(KC_LSFT, KC_RSFT))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
|
||||||
|
left_shift.press(); // Press both shifts.
|
||||||
|
run_one_scan_loop();
|
||||||
|
right_shift.press();
|
||||||
|
|
||||||
|
// For mod-tap and Space Cadet keys, wait for the tapping term.
|
||||||
|
if (left_shift.code == LSFT_T(KC_A) || left_shift.code == KC_LSPO) {
|
||||||
|
idle_for(TAPPING_TERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
run_one_scan_loop();
|
||||||
|
left_shift.release(); // Release both.
|
||||||
|
run_one_scan_loop();
|
||||||
|
right_shift.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), true);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pressing shifts as "Left down, Right down, Right up, Left up".
|
||||||
|
TEST_P(CapsWordBothShifts, PressLRRL) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
|
||||||
|
KeymapKey right_shift(0, 1, 0, GetParam().right_shift_keycode);
|
||||||
|
set_keymap({left_shift, right_shift});
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT),
|
||||||
|
KeyboardReport(KC_RSFT),
|
||||||
|
KeyboardReport(KC_LSFT, KC_RSFT))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
|
||||||
|
left_shift.press(); // Press both shifts.
|
||||||
|
run_one_scan_loop();
|
||||||
|
right_shift.press();
|
||||||
|
|
||||||
|
if (left_shift.code == LSFT_T(KC_A) || left_shift.code == KC_LSPO) {
|
||||||
|
idle_for(TAPPING_TERM);
|
||||||
|
}
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
right_shift.release(); // Release both.
|
||||||
|
run_one_scan_loop();
|
||||||
|
left_shift.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), true);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
INSTANTIATE_TEST_CASE_P(
|
||||||
|
ShiftPairs,
|
||||||
|
CapsWordBothShifts,
|
||||||
|
::testing::Values(
|
||||||
|
CapsWordBothShiftsParams{
|
||||||
|
"PlainShifts", KC_LSFT, KC_RSFT},
|
||||||
|
CapsWordBothShiftsParams{
|
||||||
|
"OneshotShifts", OSM(MOD_LSFT), OSM(MOD_RSFT)},
|
||||||
|
CapsWordBothShiftsParams{
|
||||||
|
"SpaceCadetShifts", KC_LSPO, KC_RSPC},
|
||||||
|
CapsWordBothShiftsParams{
|
||||||
|
"ModTapShifts", LSFT_T(KC_A), RSFT_T(KC_B)}
|
||||||
|
),
|
||||||
|
CapsWordBothShiftsParams::GetName
|
||||||
|
);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
struct CapsWordDoubleTapShiftParams {
|
||||||
|
std::string name;
|
||||||
|
uint16_t left_shift_keycode;
|
||||||
|
|
||||||
|
static const std::string& GetName(const TestParamInfo<CapsWordDoubleTapShiftParams>& info) {
|
||||||
|
return info.param.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tests the DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD method to turn on Caps Word.
|
||||||
|
class CapsWordDoubleTapShift : public ::testing::WithParamInterface<CapsWordDoubleTapShiftParams>, public CapsWord {};
|
||||||
|
|
||||||
|
// Tests that double tapping activates Caps Word.
|
||||||
|
TEST_P(CapsWordDoubleTapShift, Activation) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
|
||||||
|
set_keymap({left_shift});
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false);
|
||||||
|
|
||||||
|
// Tapping shift twice within the tapping term turns on Caps Word.
|
||||||
|
TapKey(left_shift);
|
||||||
|
idle_for(TAPPING_TERM - 10);
|
||||||
|
TapKey(left_shift);
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), true);
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double tap doesn't count if another key is pressed between the taps.
|
||||||
|
TEST_P(CapsWordDoubleTapShift, Interrupted) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
|
||||||
|
KeymapKey key_a(0, 1, 0, KC_A);
|
||||||
|
set_keymap({left_shift, key_a});
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT),
|
||||||
|
KeyboardReport(KC_LSFT, KC_A))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
left_shift.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
TapKey(key_a); // 'A' key interrupts the double tap.
|
||||||
|
|
||||||
|
left_shift.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
idle_for(TAPPING_TERM - 10);
|
||||||
|
TapKey(left_shift);
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false); // Caps Word is still off.
|
||||||
|
clear_oneshot_mods();
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double tap doesn't count if taps are more than tapping term apart.
|
||||||
|
TEST_P(CapsWordDoubleTapShift, SlowTaps) {
|
||||||
|
TestDriver driver;
|
||||||
|
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
|
||||||
|
set_keymap({left_shift});
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
|
||||||
|
KeyboardReport(),
|
||||||
|
KeyboardReport(KC_LSFT))))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
TapKey(left_shift);
|
||||||
|
idle_for(TAPPING_TERM + 1);
|
||||||
|
TapKey(left_shift);
|
||||||
|
|
||||||
|
EXPECT_EQ(is_caps_word_on(), false); // Caps Word is still off.
|
||||||
|
clear_oneshot_mods();
|
||||||
|
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
INSTANTIATE_TEST_CASE_P(
|
||||||
|
Shifts,
|
||||||
|
CapsWordDoubleTapShift,
|
||||||
|
::testing::Values(
|
||||||
|
CapsWordDoubleTapShiftParams{"PlainShift", KC_LSFT},
|
||||||
|
CapsWordDoubleTapShiftParams{"OneshotShift", OSM(MOD_LSFT)}
|
||||||
|
),
|
||||||
|
CapsWordDoubleTapShiftParams::GetName
|
||||||
|
);
|
||||||
|
// clang-format on
|
Loading…
Reference in a new issue