[Core] Add Layer Lock feature (#23430)
Co-authored-by: Daniel <1767914+iamdanielv@users.noreply.github.com> Co-authored-by: Pascal Getreuer <getreuer@google.com> Co-authored-by: Pascal Getreuer <50221757+getreuer@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									39161b9ee7
								
							
						
					
					
						commit
						36b5559b99
					
				| @ -36,6 +36,7 @@ GENERIC_FEATURES = \ | ||||
|     HAPTIC \
 | ||||
|     KEY_LOCK \
 | ||||
|     KEY_OVERRIDE \
 | ||||
|     LAYER_LOCK \
 | ||||
|     LEADER \
 | ||||
|     MAGIC \
 | ||||
|     MOUSEKEY \
 | ||||
|  | ||||
| @ -3,5 +3,12 @@ | ||||
|         "0x7C20": "!delete!", // old QK_OUTPUT_AUTO | ||||
|         "0x7C21": "!delete!", // old QK_OUTPUT_USB | ||||
|         "0x7C22": "!delete!", // old QK_OUTPUT_BLUETOOTH | ||||
|         "0x7C7B": { | ||||
|             "group": "quantum", | ||||
|             "key": "QK_LAYER_LOCK", | ||||
|             "aliases": [ | ||||
|                 "QK_LLCK" | ||||
|             ] | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -64,6 +64,9 @@ | ||||
|     "WEAR_LEVELING_BACKING_SIZE": {"info_key": "eeprom.wear_leveling.backing_size", "value_type": "int", "to_json": false}, | ||||
|     "WEAR_LEVELING_LOGICAL_SIZE": {"info_key": "eeprom.wear_leveling.logical_size", "value_type": "int", "to_json": false}, | ||||
| 
 | ||||
|     // Layer locking | ||||
|     "LAYER_LOCK_IDLE_TIMEOUT": {"info_key": "layer_lock.timeout", "value_type": "int"}, | ||||
| 
 | ||||
|     // Indicators | ||||
|     "LED_CAPS_LOCK_PIN": {"info_key": "indicators.caps_lock"}, | ||||
|     "LED_NUM_LOCK_PIN": {"info_key": "indicators.num_lock"}, | ||||
|  | ||||
| @ -375,6 +375,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"}, | ||||
|         "layer_lock": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"} | ||||
|             } | ||||
|         }, | ||||
|         "layout_aliases": { | ||||
|             "type": "object", | ||||
|             "additionalProperties": {"$ref": "qmk.definitions.v1#/layout_macro"} | ||||
|  | ||||
| @ -123,6 +123,7 @@ | ||||
|                     { "text": "Key Lock", "link": "/features/key_lock" }, | ||||
|                     { "text": "Key Overrides", "link": "/features/key_overrides" }, | ||||
|                     { "text": "Layers", "link": "/feature_layers" }, | ||||
|                     { "text": "Layer Lock", "link": "/features/layer_lock" }, | ||||
|                     { "text": "One Shot Keys", "link": "/one_shot_keys" }, | ||||
|                     { "text": "OS Detection", "link": "/features/os_detection" }, | ||||
|                     { "text": "Raw HID", "link": "/features/rawhid" }, | ||||
|  | ||||
| @ -17,6 +17,9 @@ These functions allow you to activate layers in various ways. Note that layers a | ||||
| * `TO(layer)` - activates *layer* and de-activates all other layers (except your default layer). This function is special, because instead of just adding/removing one layer to your active layer stack, it will completely replace your current active layers, uniquely allowing you to replace higher layers with a lower one. This is activated on keydown (as soon as the key is pressed). | ||||
| * `TT(layer)` - Layer Tap-Toggle. If you hold the key down, *layer* is activated, and then is de-activated when you let go (like `MO`). If you repeatedly tap it, the layer will be toggled on or off (like `TG`). It needs 5 taps by default, but you can change this by defining `TAPPING_TOGGLE` -- for example, `#define TAPPING_TOGGLE 2` to toggle on just two taps. | ||||
| 
 | ||||
| See also the [Layer Lock key](features/layer_lock), which locks the highest | ||||
| active layer until pressed again. | ||||
| 
 | ||||
| ### Caveats {#caveats} | ||||
| 
 | ||||
| Currently, the `layer` argument of `LT()` is limited to layers 0-15, and the `kc` argument to the [Basic Keycode set](keycodes_basic), meaning you can't use keycodes like `LCTL()`, `KC_TILD`, or anything greater than `0xFF`. This is because QMK uses 16-bit keycodes, of which 4 bits are used for the function identifier and 4 bits for the layer, leaving only 8 bits for the keycode. | ||||
|  | ||||
							
								
								
									
										139
									
								
								docs/features/layer_lock.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								docs/features/layer_lock.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,139 @@ | ||||
| # Layer Lock | ||||
| 
 | ||||
| Some [layer switches](../feature_layers#switching-and-toggling-layers) access | ||||
| the layer by holding the key, including momentary layer `MO(layer)` and layer | ||||
| tap `LT(layer, key)` keys. You may sometimes need to stay on the layer for a | ||||
| long period of time. Layer Lock "locks" the current layer to stay on, supposing | ||||
| it was accessed by one of: | ||||
| 
 | ||||
|  * `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) | ||||
| 
 | ||||
| Press the Layer Lock key again to unlock the layer. Additionally, when a layer | ||||
| is locked, layer switch keys that turn off the layer such as `TO(other_layer)` | ||||
| will unlock it. | ||||
| 
 | ||||
| 
 | ||||
| ## How do I enable Layer Lock | ||||
| 
 | ||||
| In your rules.mk, add: | ||||
| 
 | ||||
| ```make | ||||
| LAYER_LOCK_ENABLE = yes | ||||
| ``` | ||||
| 
 | ||||
| Pick a key in your keymap on a layer you intend to lock, and assign it the | ||||
| keycode `QK_LAYER_LOCK` (short alias `QK_LLCK`). Note that locking the base | ||||
| layer has no effect, so typically, this key is used on layers above the base | ||||
| layer. | ||||
| 
 | ||||
| 
 | ||||
| ## Example use | ||||
| 
 | ||||
| Consider a keymap with the following base layer. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| The highlighted key is a momentary layer switch `MO(NAV)`. Holding it accesses a | ||||
| navigation layer. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| 
 | ||||
| Holding the NAV key is fine for brief use, but awkward to continue holding when | ||||
| using navigation functions continuously. The Layer Lock key comes to the rescue: | ||||
| 
 | ||||
| 1. Hold the NAV key, activating the navigation layer. | ||||
| 2. Tap Layer Lock. | ||||
| 3. Release NAV. The navigation layer stays on. | ||||
| 4. Make use of the arrow keys, etc. | ||||
| 5. Tap Layer Lock or NAV again to turn the navigation layer back off. | ||||
| 
 | ||||
| A variation that would also work is to put the Layer Lock key on the base layer | ||||
| and make other layers transparent (`KC_TRNS`) in that position. Pressing the | ||||
| Layer Lock key locks (or unlocks) the highest active layer, regardless of which | ||||
| layer the Layer Lock key is on. | ||||
| 
 | ||||
| 
 | ||||
| ## Idle timeout | ||||
| 
 | ||||
| Optionally, Layer Lock may be configured to unlock if the keyboard is idle | ||||
| for some time. In config.h, define `LAYER_LOCK_IDLE_TIMEOUT` in units of | ||||
| milliseconds: | ||||
| 
 | ||||
| ```c | ||||
| #define LAYER_LOCK_IDLE_TIMEOUT 60000  // Turn off after 60 seconds. | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ## Functions | ||||
| 
 | ||||
| Use the following functions to query and manipulate the layer lock state. | ||||
| 
 | ||||
| | Function                   | Description                        | | ||||
| |----------------------------|------------------------------------| | ||||
| | `is_layer_locked(layer)`   | Checks whether `layer` is locked.  | | ||||
| | `layer_lock_on(layer)`     | Locks and turns on `layer`.        | | ||||
| | `layer_lock_off(layer)`    | Unlocks and turns off `layer`.     | | ||||
| | `layer_lock_invert(layer)` | Toggles whether `layer` is locked. | | ||||
| 
 | ||||
| 
 | ||||
| ## Representing the current Layer Lock state | ||||
| 
 | ||||
| There is an optional callback `layer_lock_set_user()` that gets called when a | ||||
| layer is locked or unlocked. This is useful to represent the current lock state | ||||
| for instance by setting an LED. In keymap.c, define | ||||
| 
 | ||||
| ```c | ||||
| bool layer_lock_set_user(layer_state_t locked_layers) { | ||||
|   // Do something like `set_led(is_layer_locked(NAV));` | ||||
|   return true; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| The argument `locked_layers` is a bitfield in which the kth bit is on if the kth | ||||
| layer is locked. Alternatively, you can use `is_layer_locked(layer)` to check if | ||||
| a given layer is locked. | ||||
| 
 | ||||
| 
 | ||||
| ## Combine Layer Lock with a mod-tap | ||||
| 
 | ||||
| It is possible to create a [mod-tap MT key](../mod_tap) that acts as a modifier | ||||
| on hold and Layer Lock on tap. Since Layer Lock is not a [basic | ||||
| keycode](../keycodes_basic), attempting `MT(mod, QK_LLCK)` is invalid does not | ||||
| work directly, yet this effect can be achieved through [changing the tap | ||||
| function](../mod_tap#changing-tap-function). For example, the following | ||||
| implements a `SFTLLCK` key that acts as Shift on hold and Layer Lock on tap: | ||||
| 
 | ||||
| ```c | ||||
| #define SFTLLCK LSFT_T(KC_0) | ||||
| 
 | ||||
| // Use SFTLLCK in your keymap... | ||||
| 
 | ||||
| bool process_record_user(uint16_t keycode, keyrecord_t *record) { | ||||
|     switch (keycode) { | ||||
|         case SFTLLCK: | ||||
|             if (record->tap.count) { | ||||
|                 if (record->event.pressed) { | ||||
|                     // Toggle the lock on the highest layer. | ||||
|                     layer_lock_invert(get_highest_layer(layer_state)); | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         // Other macros... | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| In the above, `KC_0` is an arbitrary placeholder for the tapping keycode. This | ||||
| keycode will never be sent, so any basic keycode will do. In | ||||
| `process_record_user()`, the tap press event is changed to toggle the lock on | ||||
| the highest layer. Layer Lock can be combined with a [layer-tap LT | ||||
| key](../feature_layers#switching-and-toggling-layers) similarly. | ||||
| 
 | ||||
| @ -387,6 +387,14 @@ See also: [Key Lock](features/key_lock) | ||||
| |---------|--------------------------------------------------------------| | ||||
| |`QK_LOCK`|Hold down the next key pressed, until the key is pressed again| | ||||
| 
 | ||||
| ## Layer Lock {#layer-lock} | ||||
| 
 | ||||
| See also: [Layer Lock](features/layer_lock) | ||||
| 
 | ||||
| |Key            |Aliases  |Description                       | | ||||
| |---------------|---------|----------------------------------| | ||||
| |`QK_LAYER_LOCK`|`QK_LLCK`|Locks or unlocks the highest layer| | ||||
| 
 | ||||
| ## Layer Switching {#layer-switching} | ||||
| 
 | ||||
| See also: [Layer Switching](feature_layers#switching-and-toggling-layers) | ||||
|  | ||||
| @ -140,6 +140,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| #ifdef OS_DETECTION_ENABLE | ||||
| #    include "os_detection.h" | ||||
| #endif | ||||
| #if defined(LAYER_LOCK_ENABLE) && LAYER_LOCK_IDLE_TIMEOUT > 0 | ||||
| #    include "layer_lock.h" | ||||
| #endif // LAYER_LOCK_ENABLE
 | ||||
| 
 | ||||
| static uint32_t last_input_modification_time = 0; | ||||
| uint32_t        last_input_activity_time(void) { | ||||
| @ -655,6 +658,10 @@ void quantum_task(void) { | ||||
| #ifdef SECURE_ENABLE | ||||
|     secure_task(); | ||||
| #endif | ||||
| 
 | ||||
| #if defined(LAYER_LOCK_ENABLE) && LAYER_LOCK_IDLE_TIMEOUT > 0 | ||||
|     layer_lock_task(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| /** \brief Main task that is repeatedly called as fast as possible. */ | ||||
|  | ||||
| @ -759,6 +759,7 @@ enum qk_keycode_defines { | ||||
|     QK_TRI_LAYER_UPPER = 0x7C78, | ||||
|     QK_REPEAT_KEY = 0x7C79, | ||||
|     QK_ALT_REPEAT_KEY = 0x7C7A, | ||||
|     QK_LAYER_LOCK = 0x7C7B, | ||||
|     QK_KB_0 = 0x7E00, | ||||
|     QK_KB_1 = 0x7E01, | ||||
|     QK_KB_2 = 0x7E02, | ||||
| @ -1445,6 +1446,7 @@ enum qk_keycode_defines { | ||||
|     TL_UPPR    = QK_TRI_LAYER_UPPER, | ||||
|     QK_REP     = QK_REPEAT_KEY, | ||||
|     QK_AREP    = QK_ALT_REPEAT_KEY, | ||||
|     QK_LLCK    = QK_LAYER_LOCK, | ||||
| }; | ||||
| 
 | ||||
| // Range Helpers
 | ||||
| @ -1501,7 +1503,7 @@ enum qk_keycode_defines { | ||||
| #define IS_UNDERGLOW_KEYCODE(code) ((code) >= QK_UNDERGLOW_TOGGLE && (code) <= QK_UNDERGLOW_SPEED_DOWN) | ||||
| #define IS_RGB_KEYCODE(code) ((code) >= RGB_MODE_PLAIN && (code) <= RGB_MODE_TWINKLE) | ||||
| #define IS_RGB_MATRIX_KEYCODE(code) ((code) >= QK_RGB_MATRIX_ON && (code) <= QK_RGB_MATRIX_SPEED_DOWN) | ||||
| #define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_ALT_REPEAT_KEY) | ||||
| #define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_LAYER_LOCK) | ||||
| #define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31) | ||||
| #define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31) | ||||
| 
 | ||||
| @ -1527,6 +1529,6 @@ enum qk_keycode_defines { | ||||
| #define UNDERGLOW_KEYCODE_RANGE             QK_UNDERGLOW_TOGGLE ... QK_UNDERGLOW_SPEED_DOWN | ||||
| #define RGB_KEYCODE_RANGE                   RGB_MODE_PLAIN ... RGB_MODE_TWINKLE | ||||
| #define RGB_MATRIX_KEYCODE_RANGE            QK_RGB_MATRIX_ON ... QK_RGB_MATRIX_SPEED_DOWN | ||||
| #define QUANTUM_KEYCODE_RANGE               QK_BOOTLOADER ... QK_ALT_REPEAT_KEY | ||||
| #define QUANTUM_KEYCODE_RANGE               QK_BOOTLOADER ... QK_LAYER_LOCK | ||||
| #define KB_KEYCODE_RANGE                    QK_KB_0 ... QK_KB_31 | ||||
| #define USER_KEYCODE_RANGE                  QK_USER_0 ... QK_USER_31 | ||||
|  | ||||
							
								
								
									
										81
									
								
								quantum/layer_lock.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								quantum/layer_lock.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| // 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.
 | ||||
| 
 | ||||
| #include "layer_lock.h" | ||||
| #include "quantum_keycodes.h" | ||||
| 
 | ||||
| #ifndef NO_ACTION_LAYER | ||||
| // The current lock state. The kth bit is on if layer k is locked.
 | ||||
| layer_state_t locked_layers = 0; | ||||
| 
 | ||||
| // Layer Lock timer to disable layer lock after X seconds inactivity
 | ||||
| #    if defined(LAYER_LOCK_IDLE_TIMEOUT) && LAYER_LOCK_IDLE_TIMEOUT > 0 | ||||
| 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
 | ||||
| 
 | ||||
| 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 defined(LAYER_LOCK_IDLE_TIMEOUT) && 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_kb(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_kb(locked_layers); | ||||
| } | ||||
| 
 | ||||
| __attribute__((weak)) bool layer_lock_set_kb(layer_state_t locked_layers) { | ||||
|     return layer_lock_set_user(locked_layers); | ||||
| } | ||||
| __attribute__((weak)) bool layer_lock_set_user(layer_state_t locked_layers) { | ||||
|     return true; | ||||
| } | ||||
| #endif // NO_ACTION_LAYER
 | ||||
							
								
								
									
										135
									
								
								quantum/layer_lock.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								quantum/layer_lock.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,135 @@ | ||||
| // 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 | ||||
|  * <https://getreuer.info/posts/keyboards/layer-lock>
 | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include "action_layer.h" | ||||
| #include "action_util.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * 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; | ||||
|  *     } | ||||
|  */ | ||||
| 
 | ||||
| #ifndef NO_ACTION_LAYER | ||||
| /** 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. | ||||
|  */ | ||||
| bool layer_lock_set_kb(layer_state_t locked_layers); | ||||
| bool layer_lock_set_user(layer_state_t locked_layers); | ||||
| 
 | ||||
| void layer_lock_task(void); | ||||
| #else  // NO_ACTION_LAYER
 | ||||
| static inline bool is_layer_locked(uint8_t layer) { | ||||
|     return false; | ||||
| } | ||||
| static inline void layer_lock_on(uint8_t layer) {} | ||||
| static inline void layer_lock_off(uint8_t layer) {} | ||||
| static inline void layer_lock_all_off(void) {} | ||||
| static inline void layer_lock_invert(uint8_t layer) {} | ||||
| static inline bool layer_lock_set_kb(layer_state_t locked_layers) { | ||||
|     return true; | ||||
| } | ||||
| static inline bool layer_lock_set_user(layer_state_t locked_layers) { | ||||
|     return true; | ||||
| } | ||||
| static inline void layer_lock_task(void) {} | ||||
| #endif // NO_ACTION_LAYER
 | ||||
							
								
								
									
										95
									
								
								quantum/process_keycode/process_layer_lock.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								quantum/process_keycode/process_layer_lock.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | ||||
| // 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 | ||||
|  * <https://getreuer.info/posts/keyboards/layer-lock>
 | ||||
|  */ | ||||
| 
 | ||||
| #include "layer_lock.h" | ||||
| #include "process_layer_lock.h" | ||||
| #include "quantum_keycodes.h" | ||||
| #include "action_util.h" | ||||
| 
 | ||||
| // The current lock state. The kth bit is on if layer k is locked.
 | ||||
| extern layer_state_t locked_layers; | ||||
| #if defined(LAYER_LOCK_IDLE_TIMEOUT) && LAYER_LOCK_IDLE_TIMEOUT > 0 | ||||
| extern uint32_t layer_lock_timer; | ||||
| #endif | ||||
| 
 | ||||
| // 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) { | ||||
| #ifndef NO_ACTION_LAYER | ||||
| #    if defined(LAYER_LOCK_IDLE_TIMEOUT) && 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_kb(locked_layers &= layer_state); | ||||
|     } | ||||
| 
 | ||||
|     if (keycode == QK_LAYER_LOCK) { | ||||
|         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
 | ||||
|     } | ||||
| #endif // NO_ACTION_LAYER
 | ||||
|     return true; | ||||
| } | ||||
							
								
								
									
										69
									
								
								quantum/process_keycode/process_layer_lock.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								quantum/process_keycode/process_layer_lock.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| // 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 | ||||
|  * <https://getreuer.info/posts/keyboards/layer-lock>
 | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include "action.h" | ||||
| 
 | ||||
| bool process_layer_lock(uint16_t keycode, keyrecord_t* record); | ||||
| @ -76,6 +76,10 @@ | ||||
| #    include "process_unicode_common.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef LAYER_LOCK_ENABLE | ||||
| #    include "process_layer_lock.h" | ||||
| #endif // LAYER_LOCK_ENABLE
 | ||||
| 
 | ||||
| #ifdef AUDIO_ENABLE | ||||
| #    ifndef GOODBYE_SONG | ||||
| #        define GOODBYE_SONG SONG(GOODBYE_SOUND) | ||||
| @ -400,6 +404,9 @@ bool process_record_quantum(keyrecord_t *record) { | ||||
| #ifdef TRI_LAYER_ENABLE | ||||
|             process_tri_layer(keycode, record) && | ||||
| #endif | ||||
| #ifdef LAYER_LOCK_ENABLE | ||||
|             process_layer_lock(keycode, record) && | ||||
| #endif | ||||
| #ifdef BLUETOOTH_ENABLE | ||||
|             process_connection(keycode, record) && | ||||
| #endif | ||||
|  | ||||
| @ -240,6 +240,10 @@ extern layer_state_t layer_state; | ||||
| #    include "os_detection.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef LAYER_LOCK_ENABLE | ||||
| #    include "layer_lock.h" | ||||
| #endif // LAYER_LOCK_ENABLE
 | ||||
| 
 | ||||
| void set_single_default_layer(uint8_t default_layer); | ||||
| void set_single_persistent_default_layer(uint8_t default_layer); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										8
									
								
								tests/layer_lock/config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tests/layer_lock/config.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| // Copyright 2021 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
 | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "test_common.h" | ||||
| 
 | ||||
| #define LAYER_LOCK_IDLE_TIMEOUT 1000 | ||||
							
								
								
									
										8
									
								
								tests/layer_lock/test.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tests/layer_lock/test.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| # Copyright 2021 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
 | ||||
| # SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| 
 | ||||
| # --------------------------------------------------------------------------------
 | ||||
| # Keep this file, even if it is empty, as a marker that this folder contains tests
 | ||||
| # --------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| LAYER_LOCK_ENABLE = yes | ||||
							
								
								
									
										284
									
								
								tests/layer_lock/test_layer_lock.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								tests/layer_lock/test_layer_lock.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,284 @@ | ||||
| // Copyright 2021 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
 | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| 
 | ||||
| #include "keycodes.h" | ||||
| #include "test_common.hpp" | ||||
| 
 | ||||
| using testing::_; | ||||
| 
 | ||||
| class LayerLock : public TestFixture {}; | ||||
| 
 | ||||
| TEST_F(LayerLock, LayerLockState) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_a = KeymapKey(0, 0, 0, KC_A); | ||||
|     KeymapKey  key_b = KeymapKey(1, 0, 0, KC_B); | ||||
|     KeymapKey  key_c = KeymapKey(2, 0, 0, KC_C); | ||||
|     KeymapKey  key_d = KeymapKey(3, 0, 0, KC_C); | ||||
| 
 | ||||
|     set_keymap({key_a, key_b, key_c, key_d}); | ||||
| 
 | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(2)); | ||||
|     EXPECT_FALSE(is_layer_locked(3)); | ||||
| 
 | ||||
|     layer_lock_invert(1); // Layer 1: unlocked -> locked
 | ||||
|     layer_lock_on(2);     // Layer 2: unlocked -> locked
 | ||||
|     layer_lock_off(3);    // Layer 3: stays unlocked
 | ||||
| 
 | ||||
|     // Layers 1 and 2 are now on.
 | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(layer_state_is(2)); | ||||
|     // Layers 1 and 2 are now locked.
 | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(2)); | ||||
|     EXPECT_FALSE(is_layer_locked(3)); | ||||
| 
 | ||||
|     layer_lock_invert(1); // Layer 1: locked -> unlocked
 | ||||
|     layer_lock_on(2);     // Layer 2: stays locked
 | ||||
|     layer_lock_on(3);     // Layer 3: unlocked -> locked
 | ||||
| 
 | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(layer_state_is(2)); | ||||
|     EXPECT_TRUE(layer_state_is(3)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(2)); | ||||
|     EXPECT_TRUE(is_layer_locked(3)); | ||||
| 
 | ||||
|     layer_lock_invert(1); // Layer 1: unlocked -> locked
 | ||||
|     layer_lock_off(2);    // Layer 2: locked -> unlocked
 | ||||
| 
 | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(layer_state_is(2)); | ||||
|     EXPECT_TRUE(layer_state_is(3)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(2)); | ||||
|     EXPECT_TRUE(is_layer_locked(3)); | ||||
| 
 | ||||
|     layer_lock_all_off(); // Layers 1 and 3: locked -> unlocked
 | ||||
| 
 | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(layer_state_is(2)); | ||||
|     EXPECT_FALSE(layer_state_is(3)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(2)); | ||||
|     EXPECT_FALSE(is_layer_locked(3)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(LayerLock, LayerLockMomentaryTest) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_layer = KeymapKey(0, 0, 0, MO(1)); | ||||
|     KeymapKey  key_a     = KeymapKey(0, 1, 0, KC_A); | ||||
|     KeymapKey  key_trns  = KeymapKey(1, 0, 0, KC_TRNS); | ||||
|     KeymapKey  key_ll    = KeymapKey(1, 1, 0, QK_LAYER_LOCK); | ||||
| 
 | ||||
|     set_keymap({key_layer, key_a, key_trns, key_ll}); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_layer.press(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     tap_key(key_ll); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_layer.release(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     // Pressing Layer Lock again unlocks the lock.
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_ll.press(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(LayerLock, LayerLockLayerTapTest) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_layer = KeymapKey(0, 0, 0, LT(1, KC_B)); | ||||
|     KeymapKey  key_a     = KeymapKey(0, 1, 0, KC_A); | ||||
|     KeymapKey  key_trns  = KeymapKey(1, 0, 0, KC_TRNS); | ||||
|     KeymapKey  key_ll    = KeymapKey(1, 1, 0, QK_LAYER_LOCK); | ||||
| 
 | ||||
|     set_keymap({key_layer, key_a, key_trns, key_ll}); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_layer.press(); | ||||
|     idle_for(TAPPING_TERM); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     tap_key(key_ll); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     // Pressing Layer Lock again unlocks the lock.
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_ll.press(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(LayerLock, LayerLockOneshotTapTest) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_layer = KeymapKey(0, 0, 0, OSL(1)); | ||||
|     KeymapKey  key_a     = KeymapKey(0, 1, 0, KC_A); | ||||
|     KeymapKey  key_trns  = KeymapKey(1, 0, 0, KC_TRNS); | ||||
|     KeymapKey  key_ll    = KeymapKey(1, 1, 0, QK_LAYER_LOCK); | ||||
| 
 | ||||
|     set_keymap({key_layer, key_a, key_trns, key_ll}); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     tap_key(key_layer); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     tap_key(key_ll); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     // Pressing Layer Lock again unlocks the lock.
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_ll.press(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(LayerLock, LayerLockOneshotHoldTest) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_layer = KeymapKey(0, 0, 0, OSL(1)); | ||||
|     KeymapKey  key_a     = KeymapKey(0, 1, 0, KC_A); | ||||
|     KeymapKey  key_trns  = KeymapKey(1, 0, 0, KC_TRNS); | ||||
|     KeymapKey  key_ll    = KeymapKey(1, 1, 0, QK_LAYER_LOCK); | ||||
| 
 | ||||
|     set_keymap({key_layer, key_a, key_trns, key_ll}); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_layer.press(); | ||||
|     idle_for(TAPPING_TERM); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     tap_key(key_ll); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_layer.release(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     // Pressing Layer Lock again unlocks the lock.
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_ll.press(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(LayerLock, LayerLockTimeoutTest) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_layer = KeymapKey(0, 0, 0, MO(1)); | ||||
|     KeymapKey  key_a     = KeymapKey(0, 1, 0, KC_A); | ||||
|     KeymapKey  key_trns  = KeymapKey(1, 0, 0, KC_TRNS); | ||||
|     KeymapKey  key_ll    = KeymapKey(1, 1, 0, QK_LAYER_LOCK); | ||||
| 
 | ||||
|     set_keymap({key_layer, key_a, key_trns, key_ll}); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_layer.press(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     tap_key(key_ll); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     key_layer.release(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     idle_for(LAYER_LOCK_IDLE_TIMEOUT); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(LayerLock, ToKeyOverridesLayerLock) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_layer = KeymapKey(0, 0, 0, MO(1)); | ||||
|     KeymapKey  key_to0   = KeymapKey(1, 0, 0, TO(0)); | ||||
|     KeymapKey  key_ll    = KeymapKey(1, 1, 0, QK_LAYER_LOCK); | ||||
| 
 | ||||
|     set_keymap({key_layer, key_to0, key_ll}); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     layer_lock_on(1); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     tap_key(key_to0); // TO(0) overrides Layer Lock and unlocks layer 1.
 | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| } | ||||
| 
 | ||||
| TEST_F(LayerLock, LayerClearOverridesLayerLock) { | ||||
|     TestDriver driver; | ||||
|     KeymapKey  key_layer = KeymapKey(0, 0, 0, MO(1)); | ||||
|     KeymapKey  key_a     = KeymapKey(0, 1, 0, KC_A); | ||||
|     KeymapKey  key_ll    = KeymapKey(1, 1, 0, QK_LAYER_LOCK); | ||||
| 
 | ||||
|     set_keymap({key_layer, key_a, key_ll}); | ||||
| 
 | ||||
|     EXPECT_NO_REPORT(driver); | ||||
|     layer_lock_on(1); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_TRUE(layer_state_is(1)); | ||||
|     EXPECT_TRUE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| 
 | ||||
|     EXPECT_REPORT(driver, (KC_A)); | ||||
|     layer_clear(); // layer_clear() overrides Layer Lock and unlocks layer 1.
 | ||||
|     key_a.press(); | ||||
|     run_one_scan_loop(); | ||||
|     EXPECT_FALSE(layer_state_is(1)); | ||||
|     EXPECT_FALSE(is_layer_locked(1)); | ||||
|     VERIFY_AND_CLEAR(driver); | ||||
| } | ||||
| @ -699,6 +699,7 @@ std::map<uint16_t, std::string> KEYCODE_ID_TABLE = { | ||||
|     {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"}, | ||||
|     {QK_REPEAT_KEY, "QK_REPEAT_KEY"}, | ||||
|     {QK_ALT_REPEAT_KEY, "QK_ALT_REPEAT_KEY"}, | ||||
|     {QK_LAYER_LOCK, "QK_LAYER_LOCK"}, | ||||
|     {QK_KB_0, "QK_KB_0"}, | ||||
|     {QK_KB_1, "QK_KB_1"}, | ||||
|     {QK_KB_2, "QK_KB_2"}, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user