summaryrefslogtreecommitdiff
path: root/quantum
diff options
context:
space:
mode:
authorPascal Getreuer <50221757+getreuer@users.noreply.github.com>2022-05-13 23:00:32 -0700
committerGitHub <noreply@github.com>2022-05-14 16:00:32 +1000
commit68b16bba68d79c63bbd649a5be58756e937c96da (patch)
treebe0d3d672c13c080f4da58687b5b1a261f465911 /quantum
parent90eef4cd153cdc1b00d973e6abb029828f656294 (diff)
[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>
Diffstat (limited to 'quantum')
-rw-r--r--quantum/caps_word.c80
-rw-r--r--quantum/caps_word.h43
-rw-r--r--quantum/keyboard.c7
-rw-r--r--quantum/process_keycode/process_caps_word.c160
-rw-r--r--quantum/process_keycode/process_caps_word.h37
-rw-r--r--quantum/quantum.c3
-rw-r--r--quantum/quantum.h5
-rw-r--r--quantum/quantum_keycodes.h3
8 files changed, 338 insertions, 0 deletions
diff --git a/quantum/caps_word.c b/quantum/caps_word.c
new file mode 100644
index 0000000000..5b83659f28
--- /dev/null
+++ b/quantum/caps_word.c
@@ -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) {}
diff --git a/quantum/caps_word.h b/quantum/caps_word.h
new file mode 100644
index 0000000000..b83f73371e
--- /dev/null
+++ b/quantum/caps_word.h
@@ -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);
diff --git a/quantum/keyboard.c b/quantum/keyboard.c
index 1dccd48e2f..a65f9d6d18 100644
--- a/quantum/keyboard.c
+++ b/quantum/keyboard.c
@@ -108,6 +108,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifdef BLUETOOTH_ENABLE
# include "outputselect.h"
#endif
+#ifdef CAPS_WORD_ENABLE
+# include "caps_word.h"
+#endif
static uint32_t last_input_modification_time = 0;
uint32_t last_input_activity_time(void) {
@@ -549,6 +552,10 @@ void quantum_task(void) {
autoshift_matrix_scan();
#endif
+#ifdef CAPS_WORD_ENABLE
+ caps_word_task();
+#endif
+
#ifdef SECURE_ENABLE
secure_task();
#endif
diff --git a/quantum/process_keycode/process_caps_word.c b/quantum/process_keycode/process_caps_word.c
new file mode 100644
index 0000000000..15238f04a1
--- /dev/null
+++ b/quantum/process_keycode/process_caps_word.c
@@ -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.
+ }
+}
diff --git a/quantum/process_keycode/process_caps_word.h b/quantum/process_keycode/process_caps_word.h
new file mode 100644
index 0000000000..f215bbc3a3
--- /dev/null
+++ b/quantum/process_keycode/process_caps_word.h
@@ -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);
diff --git a/quantum/quantum.c b/quantum/quantum.c
index c0e801a4bb..b54b46760c 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -307,6 +307,9 @@ bool process_record_quantum(keyrecord_t *record) {
#ifdef TERMINAL_ENABLE
process_terminal(keycode, record) &&
#endif
+#ifdef CAPS_WORD_ENABLE
+ process_caps_word(keycode, record) &&
+#endif
#ifdef SPACE_CADET_ENABLE
process_space_cadet(keycode, record) &&
#endif
diff --git a/quantum/quantum.h b/quantum/quantum.h
index 7829ec7e0c..92e1af1c40 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -233,6 +233,11 @@ extern layer_state_t layer_state;
# include "pointing_device.h"
#endif
+#ifdef CAPS_WORD_ENABLE
+# include "caps_word.h"
+# include "process_caps_word.h"
+#endif
+
// For tri-layer
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);
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h
index 5d5c4ed8c4..2f8ee2322e 100644
--- a/quantum/quantum_keycodes.h
+++ b/quantum/quantum_keycodes.h
@@ -602,6 +602,8 @@ enum quantum_keycodes {
SECURE_UNLOCK,
SECURE_TOGGLE,
+ CAPS_WORD,
+
// Start of custom keycode range for keyboards and keymaps - always leave at the end
SAFE_RANGE
};
@@ -964,5 +966,6 @@ enum quantum_keycodes {
#define PB_32 PROGRAMMABLE_BUTTON_32
#define PROGRAMMABLE_BUTTON_MIN PROGRAMMABLE_BUTTON_1
#define PROGRAMMABLE_BUTTON_MAX PROGRAMMABLE_BUTTON_32
+#define CAPSWRD CAPS_WORD
#include "quantum_keycodes_legacy.h"