diff --git a/README.md b/README.md index e9f54ef..dded4a8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ Who knew a keyboard could do so much? Click](https://getreuer.info/posts/keyboards/mouse-turbo-click/index.html) – QMK macro that clicks the mouse rapidly +* [Layer Lock key](https://getreuer.info/posts/keyboards/layer-lock/index.html) + – QMK macro to stay in the current layer + ## My keymap diff --git a/features/layer_lock.c b/features/layer_lock.c new file mode 100644 index 0000000..8ca74d3 --- /dev/null +++ b/features/layer_lock.c @@ -0,0 +1,95 @@ +// Copyright 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. +// +// +// For full documentation, see +// https://getreuer.info/posts/keyboards/layer-lock + +#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; + +bool process_layer_lock(uint16_t keycode, keyrecord_t* record, + uint16_t lock_keycode) { + // 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. + case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: { // `TT(layer)`. + const uint8_t layer = keycode & 255; + if (is_layer_locked(layer)) { + // Event on `MO` or `TT` key where layer is locked. + if (record->event.pressed) { // On press, unlock the layer. + layer_lock_invert(layer); + } + 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((keycode >> 8) & 15)) { + // 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); + } 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); } +} + +__attribute__((weak)) void layer_lock_set_user(layer_state_t locked_layers) {} + diff --git a/features/layer_lock.h b/features/layer_lock.h new file mode 100644 index 0000000..6e3ffb8 --- /dev/null +++ b/features/layer_lock.h @@ -0,0 +1,80 @@ +// Copyright 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. +// +// +// Layer Lock, a key to stay in the current layer. +// +// 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 +// +// 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. +// +// For full documentation, see +// https://getreuer.info/posts/keyboards/layer-lock + +#pragma once + +#include QMK_KEYBOARD_H + +// 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); + +// Toggles whether `layer` is locked. +void layer_lock_invert(uint8_t layer); + +// An 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));` +// } +void layer_lock_set_user(layer_state_t locked_layers); +