Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> Co-authored-by: Drashna Jaelre <drashna@live.com>pull/17095/head
@ -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. | |||
} | |||
} | |||
``` | |||
@ -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) {} |
@ -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); |
@ -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. | |||
} | |||
} |
@ -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); |
@ -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 |
@ -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 | |||
@ -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 |