diff --git a/keyboards/bastardkb/skeletyl/keymaps/quotengrote/features/layer_lock.c b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/features/layer_lock.c new file mode 100644 index 0000000000..1a58378e75 --- /dev/null +++ b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/features/layer_lock.c @@ -0,0 +1,146 @@ +// 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/bastardkb/skeletyl/keymaps/quotengrote/features/layer_lock.h b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/features/layer_lock.h new file mode 100644 index 0000000000..a0f1455ac8 --- /dev/null +++ b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/features/layer_lock.h @@ -0,0 +1,136 @@ +// 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/bastardkb/skeletyl/keymaps/quotengrote/keymap.c b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/keymap.c index 1d44274dff..888ef84485 100644 --- a/keyboards/bastardkb/skeletyl/keymaps/quotengrote/keymap.c +++ b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/keymap.c @@ -1,5 +1,6 @@ #include QMK_KEYBOARD_H #include "keymap_german.h" +#include "features/layer_lock.h" // rgb void keyboard_post_init_user(void) { @@ -36,6 +37,12 @@ bool rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) { return false; } +// layer lock +// https://getreuer.info/posts/keyboards/layer-lock/index.html +enum custom_keycodes { + LLOCK = SAFE_RANGE, +}; + // combos; https://github.com/qmk/qmk_firmware/blob/master/docs/feature_combo.md enum combos { C_AE, @@ -99,7 +106,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [3] = LAYOUT_split_3x5_3( XXXXXXX, KC_HOME, KC_UP, KC_PGUP, XXXXXXX, XXXXXXX, KC_PGUP, KC_UP, KC_HOME, KC_BSPC, KC_TAB, KC_LEFT, KC_DOWN, KC_RIGHT, XXXXXXX, XXXXXXX, KC_LEFT, KC_DOWN, KC_RIGHT, KC_DEL, - XXXXXXX, KC_END, KC_PGDN, XXXXXXX, XXXXXXX, XXXXXXX, KC_PGDN, XXXXXXX, KC_END, XXXXXXX, + XXXXXXX, KC_END, KC_PGDN, XXXXXXX, LLOCK, LLOCK, KC_PGDN, XXXXXXX, KC_END, XXXXXXX, XXXXXXX, _______, _______, _______, _______, XXXXXXX ), // Media - yellow @@ -157,6 +164,8 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { return false; } return true; + // layer lock + if (!process_layer_lock(keycode, record, LLOCK)) { return false; } } return true; } diff --git a/keyboards/bastardkb/skeletyl/keymaps/quotengrote/rules.mk b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/rules.mk index dc68f099de..26c4522491 100644 --- a/keyboards/bastardkb/skeletyl/keymaps/quotengrote/rules.mk +++ b/keyboards/bastardkb/skeletyl/keymaps/quotengrote/rules.mk @@ -2,3 +2,4 @@ CAPS_WORD_ENABLE = yes AUTO_SHIFT_ENABLE = yes COMBO_ENABLE = yes RGB_MATRIX_ENABLE = yes +SRC += features/layer_lock.c