forked from qmk/qmk_firmware
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Core] Add Layer Lock feature (qmk#23430)
Co-authored-by: Daniel <[email protected]> Co-authored-by: Pascal Getreuer <[email protected]> Co-authored-by: Pascal Getreuer <[email protected]>
- Loading branch information
1 parent
39161b9
commit 36b5559
Showing
20 changed files
with
871 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,7 @@ GENERIC_FEATURES = \ | |
HAPTIC \ | ||
KEY_LOCK \ | ||
KEY_OVERRIDE \ | ||
LAYER_LOCK \ | ||
LEADER \ | ||
MAGIC \ | ||
MOUSEKEY \ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
![Base layer with a MO(NAV) key.](https://i.imgur.com/DkEhj9x.png) | ||
|
||
The highlighted key is a momentary layer switch `MO(NAV)`. Holding it accesses a | ||
navigation layer. | ||
|
||
![Nav layer with a Layer Lock key.](https://i.imgur.com/2wUZNWk.png) | ||
|
||
|
||
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. | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.