From 109334f9a7fd49385d421991e2ea809bfbb0fdf7 Mon Sep 17 00:00:00 2001 From: sajenim Date: Thu, 28 Mar 2024 22:03:55 +0800 Subject: [PATCH] add layer lock feature --- .../keymaps/sajenim/features/layer_lock.c | 147 ++++++++++++++++++ .../keymaps/sajenim/features/layer_lock.h | 137 ++++++++++++++++ keyboards/crkbd/keymaps/sajenim/keymap.c | 3 + keyboards/crkbd/keymaps/sajenim/rules.mk | 1 + 4 files changed, 288 insertions(+) create mode 100644 keyboards/crkbd/keymaps/sajenim/features/layer_lock.c create mode 100644 keyboards/crkbd/keymaps/sajenim/features/layer_lock.h diff --git a/keyboards/crkbd/keymaps/sajenim/features/layer_lock.c b/keyboards/crkbd/keymaps/sajenim/features/layer_lock.c new file mode 100644 index 0000000..2c23e84 --- /dev/null +++ b/keyboards/crkbd/keymaps/sajenim/features/layer_lock.c @@ -0,0 +1,147 @@ +// Copyright 2022-2023 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. + +/** + * @file layer_lock.c + * @brief Layer Lock implementation + * + * For full documentation, see + * + */ + +#include "layer_lock.h" + +// The current lock state. The kth bit is on if layer k is locked. +static layer_state_t locked_layers = 0; + +// Layer Lock timer to disable layer lock after X seconds inactivity +#if LAYER_LOCK_IDLE_TIMEOUT > 0 +static uint32_t layer_lock_timer = 0; + +void layer_lock_task(void) { + if (locked_layers && + timer_elapsed32(layer_lock_timer) > LAYER_LOCK_IDLE_TIMEOUT) { + layer_lock_all_off(); + layer_lock_timer = timer_read32(); + } +} +#endif // LAYER_LOCK_IDLE_TIMEOUT > 0 + +// Handles an event on an `MO` or `TT` layer switch key. +static bool handle_mo_or_tt(uint8_t layer, keyrecord_t* record) { + if (is_layer_locked(layer)) { + if (record->event.pressed) { // On press, unlock the layer. + layer_lock_invert(layer); + } + return false; // Skip default handling. + } + return true; +} + +bool process_layer_lock(uint16_t keycode, keyrecord_t* record, + uint16_t lock_keycode) { +#if LAYER_LOCK_IDLE_TIMEOUT > 0 + layer_lock_timer = timer_read32(); +#endif // LAYER_LOCK_IDLE_TIMEOUT > 0 + + // The intention is that locked layers remain on. If something outside of + // this feature turned any locked layers off, unlock them. + if ((locked_layers & ~layer_state) != 0) { + layer_lock_set_user(locked_layers &= layer_state); + } + + if (keycode == lock_keycode) { + if (record->event.pressed) { // The layer lock key was pressed. + layer_lock_invert(get_highest_layer(layer_state)); + } + return false; + } + + switch (keycode) { + case QK_MOMENTARY ... QK_MOMENTARY_MAX: // `MO(layer)` keys. + return handle_mo_or_tt(QK_MOMENTARY_GET_LAYER(keycode), record); + + case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: // `TT(layer)`. + return handle_mo_or_tt(QK_LAYER_TAP_TOGGLE_GET_LAYER(keycode), record); + + case QK_LAYER_MOD ... QK_LAYER_MOD_MAX: { // `LM(layer, mod)`. + uint8_t layer = QK_LAYER_MOD_GET_LAYER(keycode); + if (is_layer_locked(layer)) { + if (record->event.pressed) { // On press, unlock the layer. + layer_lock_invert(layer); + } else { // On release, clear the mods. + clear_mods(); + send_keyboard_report(); + } + return false; // Skip default handling. + } + } break; + +#ifndef NO_ACTION_TAPPING + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: // `LT(layer, key)` keys. + if (record->tap.count == 0 && !record->event.pressed && + is_layer_locked(QK_LAYER_TAP_GET_LAYER(keycode))) { + // Release event on a held layer-tap key where the layer is locked. + return false; // Skip default handling so that layer stays on. + } + break; +#endif // NO_ACTION_TAPPING + } + + return true; +} + +bool is_layer_locked(uint8_t layer) { + return locked_layers & ((layer_state_t)1 << layer); +} + +void layer_lock_invert(uint8_t layer) { + const layer_state_t mask = (layer_state_t)1 << layer; + if ((locked_layers & mask) == 0) { // Layer is being locked. +#ifndef NO_ACTION_ONESHOT + if (layer == get_oneshot_layer()) { + reset_oneshot_layer(); // Reset so that OSL doesn't turn layer off. + } +#endif // NO_ACTION_ONESHOT + layer_on(layer); +#if LAYER_LOCK_IDLE_TIMEOUT > 0 + layer_lock_timer = timer_read32(); +#endif // LAYER_LOCK_IDLE_TIMEOUT > 0 + } else { // Layer is being unlocked. + layer_off(layer); + } + layer_lock_set_user(locked_layers ^= mask); +} + +// Implement layer_lock_on/off by deferring to layer_lock_invert. +void layer_lock_on(uint8_t layer) { + if (!is_layer_locked(layer)) { + layer_lock_invert(layer); + } +} + +void layer_lock_off(uint8_t layer) { + if (is_layer_locked(layer)) { + layer_lock_invert(layer); + } +} + +void layer_lock_all_off(void) { + layer_and(~locked_layers); + locked_layers = 0; + layer_lock_set_user(locked_layers); +} + +__attribute__((weak)) void layer_lock_set_user(layer_state_t locked_layers) {} + diff --git a/keyboards/crkbd/keymaps/sajenim/features/layer_lock.h b/keyboards/crkbd/keymaps/sajenim/features/layer_lock.h new file mode 100644 index 0000000..eafc184 --- /dev/null +++ b/keyboards/crkbd/keymaps/sajenim/features/layer_lock.h @@ -0,0 +1,137 @@ +// Copyright 2022-2023 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. + +/** + * @file layer_lock.h + * @brief Layer Lock, a key to stay in the current layer. + * + * Overview + * -------- + * + * Layers are often accessed by holding a button, e.g. with a momentary layer + * switch `MO(layer)` or layer tap `LT(layer, key)` key. But you may sometimes + * want to "lock" or "toggle" the layer so that it stays on without having to + * hold down a button. One way to do that is with a tap-toggle `TT` layer key, + * but here is an alternative. + * + * This library implements a "Layer Lock key". When tapped, it "locks" the + * highest layer to stay active, assuming the layer was activated by one of the + * following keys: + * + * * `MO(layer)` momentary layer switch + * * `LT(layer, key)` layer tap + * * `OSL(layer)` one-shot layer + * * `TT(layer)` layer tap toggle + * * `LM(layer, mod)` layer-mod key (the layer is locked, but not the mods) + * + * Tapping the Layer Lock key again unlocks and turns off the layer. + * + * @note When a layer is "locked", other layer keys such as `TO(layer)` or + * manually calling `layer_off(layer)` will override and unlock the layer. + * + * Configuration + * ------------- + * + * Optionally, a timeout may be defined so that Layer Lock disables + * automatically if not keys are pressed for `LAYER_LOCK_IDLE_TIMEOUT` + * milliseconds. Define `LAYER_LOCK_IDLE_TIMEOUT` in your config.h, for instance + * + * #define LAYER_LOCK_IDLE_TIMEOUT 60000 // Turn off after 60 seconds. + * + * and call `layer_lock_task()` from your `matrix_scan_user()` in keymap.c: + * + * void matrix_scan_user(void) { + * layer_lock_task(); + * // Other tasks... + * } + * + * For full documentation, see + * + */ + +#pragma once + +#include "quantum.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Handler function for Layer Lock. + * + * In your keymap, define a custom keycode to use for Layer Lock. Then handle + * Layer Lock from your `process_record_user` function by calling + * `process_layer_lock`, passing your custom keycode for the `lock_keycode` arg: + * + * #include "features/layer_lock.h" + * + * bool process_record_user(uint16_t keycode, keyrecord_t* record) { + * if (!process_layer_lock(keycode, record, LLOCK)) { return false; } + * // Your macros ... + * + * return true; + * } + */ +bool process_layer_lock(uint16_t keycode, keyrecord_t* record, + uint16_t lock_keycode); + +/** Returns true if `layer` is currently locked. */ +bool is_layer_locked(uint8_t layer); + +/** Locks and turns on `layer`. */ +void layer_lock_on(uint8_t layer); + +/** Unlocks and turns off `layer`. */ +void layer_lock_off(uint8_t layer); + +/** Unlocks and turns off all locked layers. */ +void layer_lock_all_off(void); + +/** Toggles whether `layer` is locked. */ +void layer_lock_invert(uint8_t layer); + +/** + * Optional callback that gets called when a layer is locked or unlocked. + * + * This is useful to represent the current lock state, e.g. by setting an LED or + * playing a sound. In your keymap, define + * + * void layer_lock_set_user(layer_state_t locked_layers) { + * // Do something like `set_led(is_layer_locked(NAV));` + * } + * + * @param locked_layers Bitfield in which the kth bit represents whether the + * kth layer is on. + */ +void layer_lock_set_user(layer_state_t locked_layers); + +/** + * @fn layer_lock_task(void) + * Matrix task function for Layer Lock. + * + * If using `LAYER_LOCK_IDLE_TIMEOUT`, call this function from your + * `matrix_scan_user()` function in keymap.c. (If no timeout is set, calling + * `layer_lock_task()` has no effect.) + */ +#if LAYER_LOCK_IDLE_TIMEOUT > 0 +void layer_lock_task(void); +#else +static inline void layer_lock_task(void) {} +#endif // LAYER_LOCK_IDLE_TIMEOUT > 0 + +#ifdef __cplusplus +} +#endif + diff --git a/keyboards/crkbd/keymaps/sajenim/keymap.c b/keyboards/crkbd/keymaps/sajenim/keymap.c index 95e4cd8..5c9b93f 100644 --- a/keyboards/crkbd/keymaps/sajenim/keymap.c +++ b/keyboards/crkbd/keymaps/sajenim/keymap.c @@ -23,6 +23,7 @@ #include "features/sentence_case.h" #include "features/achordion.h" #include "features/select_word.h" +#include "features/layer_lock.h" // https://github.com/getreuer/qmk-keymap/tree/main/features // Our super useful layers, why we run qmk exclusively. @@ -37,6 +38,7 @@ enum layers { // Our custom keycodes enum custom_keycodes { SC_TOGG = SAFE_RANGE, + LLOCK, SELWORD, MAGIC2, }; @@ -219,6 +221,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t* record) { if (!process_sentence_case(keycode, record)) { return false; } if (!process_achordion(keycode, record)) { return false; } if (!process_select_word(keycode, record, SELWORD)) { return false; } + if (!process_layer_lock(keycode, record, LLOCK)) { return false; } if (record->event.pressed) { int rep_count = get_repeat_key_count(); diff --git a/keyboards/crkbd/keymaps/sajenim/rules.mk b/keyboards/crkbd/keymaps/sajenim/rules.mk index 8cf4840..34cf9c1 100644 --- a/keyboards/crkbd/keymaps/sajenim/rules.mk +++ b/keyboards/crkbd/keymaps/sajenim/rules.mk @@ -19,6 +19,7 @@ RGBLIGHT_ENABLE = yes SRC += features/sentence_case.c SRC += features/achordion.c SRC += features/select_word.c +SRC += features/layer_lock.c # Oled display configuration ifeq ($(OLED_ENABLE),yes)