M builddefs/common_features.mk => builddefs/common_features.mk +1 -0
@@ 620,6 620,7 @@ ifeq ($(strip $(VIA_ENABLE)), yes)
DYNAMIC_KEYMAP_ENABLE := yes
RAW_ENABLE := yes
BOOTMAGIC_ENABLE := yes
+ TRI_LAYER_ENABLE := yes
SRC += $(QUANTUM_DIR)/via.c
OPT_DEFS += -DVIA_ENABLE
endif
M builddefs/generic_features.mk => builddefs/generic_features.mk +1 -0
@@ 39,6 39,7 @@ GENERIC_FEATURES = \
VELOCIKEY \
WPM \
DYNAMIC_TAPPING_TERM \
+ TRI_LAYER
define HANDLE_GENERIC_FEATURE
# $$(info "Processing: $1_ENABLE $2.c")
M builddefs/show_options.mk => builddefs/show_options.mk +2 -1
@@ 84,7 84,8 @@ OTHER_OPTION_NAMES = \
PROGRAMMABLE_BUTTON_ENABLE \
SECURE_ENABLE \
CAPS_WORD_ENABLE \
- AUTOCORRECT_ENABLE
+ AUTOCORRECT_ENABLE \
+ TRI_LAYER_ENABLE
define NAME_ECHO
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
A data/constants/keycodes/keycodes_0.0.2_quantum.hjson => data/constants/keycodes/keycodes_0.0.2_quantum.hjson +18 -0
@@ 0,0 1,18 @@
+{
+ "keycodes": {
+ "0x7C77": {
+ "group": "quantum",
+ "key": "QK_TRI_LAYER_LOWER",
+ "aliases": [
+ "TL_LOWR"
+ ]
+ },
+ "0x7C78": {
+ "group": "quantum",
+ "key": "QK_TRI_LAYER_UPPER",
+ "aliases": [
+ "TL_UPPR"
+ ]
+ }
+ }
+}
M docs/_summary.md => docs/_summary.md +1 -0
@@ 93,6 93,7 @@
* [Swap Hands](feature_swap_hands.md)
* [Tap Dance](feature_tap_dance.md)
* [Tap-Hold Configuration](tap_hold.md)
+ * [Tri Layer](feature_tri_layer.md)
* [Unicode](feature_unicode.md)
* [Userspace](feature_userspace.md)
* [WPM Calculation](feature_wpm.md)
A docs/feature_tri_layer.md => docs/feature_tri_layer.md +48 -0
@@ 0,0 1,48 @@
+# Tri Layers :id=tri-layers
+
+This enables support for the OLKB style "Tri Layer" keycodes. These function similar to the `MO` (momentary) function key, but if both the "Lower" and "Upper" keys are pressed, it activates a third "Adjust" layer. To enable this functionality, add this line to your `rules.mk`:
+
+```make
+TRI_LAYER_ENABLE = yes
+```
+
+Note that the "upper", "lower" and "adjust" names don't have a particular significance, they are just used to identify and clarify the behavior. Layers are processed from highest numeric value to lowest, however the values are not required to be consecutive.
+
+For a detailed explanation of how the layer stack works, check out [Keymap Overview](keymap.md#keymap-and-layers).
+
+## Keycodes :id=keycodes
+
+| Keycode | Alias | Description |
+|----------------------|-----------|---------------------------------------------------------------------------------------------------------|
+| `QK_TRI_LAYER_LOWER` | `TL_LOWR` | Momentarily enables the "lower" layer. Enables the "adjust" layer if the "upper" layer is also enabled" |
+| `QK_TRI_LAYER_UPPER` | `TL_UPPR` | Momentarily enables the "upper" layer. Enables the "adjust" layer if the "lower" layer is also enabled" |
+
+## Configuration
+
+To change the default values for the layers, you can change these defines, in your `config.h`
+
+| Config name | Default | Description |
+|--------------------------|---------|------------------------------------------|
+| `TRI_LAYER_LOWER_LAYER` | `1` | Sets the default for the "lower" layer. |
+| `TRI_LAYER_UPPER_LAYER` | `2` | Sets the default for the "upper" layer. |
+| `TRI_LAYER_ADJUST_LAYER` | `3` | Sets the default for the "adjust" layer. |
+
+Eg, if you wanted to set the "Adjust" layer to be layer 5, you'd add this to your `config.h`:
+
+```c
+#define TRI_LAYER_ADJUST_LAYER 5
+```
+
+## Functions
+
+| Function name | Description |
+|----------------------------------------------|-------------------------------------------------|
+| `set_tri_layer_lower_layer(layer)` | Changes the "lower" layer*. |
+| `set_tri_layer_upper_layer(layer)` | Changes the "upper" layer*. |
+| `set_tri_layer_adjust_layer(layer)` | Changes the "adjust" layer*. |
+| `set_tri_layer_layers(lower, upper, adjust)` | Stes the "lower", "upper" and "adjust" layers*. |
+| `get_tri_layer_lower_layer()` | Gets the current "lower" layer. |
+| `get_tri_layer_upper_layer()` | Gets the current "upper" layer. |
+| `get_tri_layer_adjust_layer()` | Gets the current "adjust" layer. |
+
+!> Note: these settings are not persisent, and will be reset to the default on power loss or power cycling of the controller.
M quantum/action_layer.c => quantum/action_layer.c +10 -0
@@ 349,3 349,13 @@ uint8_t layer_switch_get_layer(keypos_t key) {
action_t layer_switch_get_action(keypos_t key) {
return action_for_key(layer_switch_get_layer(key), key);
}
+
+layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3) {
+ layer_state_t mask12 = ((layer_state_t)1 << layer1) | ((layer_state_t)1 << layer2);
+ layer_state_t mask3 = (layer_state_t)1 << layer3;
+ return (state & mask12) == mask12 ? (state | mask3) : (state & ~mask3);
+}
+
+void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3) {
+ layer_state_set(update_tri_layer_state(layer_state, layer1, layer2, layer3));
+}
M quantum/action_layer.h => quantum/action_layer.h +21 -0
@@ 113,6 113,25 @@ void layer_and(layer_state_t state);
void layer_xor(layer_state_t state);
layer_state_t layer_state_set_user(layer_state_t state);
layer_state_t layer_state_set_kb(layer_state_t state);
+
+/**
+ * @brief Applies the tri layer to global layer state. Not be used in layer_state_set_(kb|user) functions.
+ *
+ * @param layer1 First layer to check for tri layer
+ * @param layer2 Second layer to check for tri layer
+ * @param layer3 Layer to activate if both other layers are enabled
+ */
+void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3);
+/**
+ * @brief Applies the tri layer behavior to supplied layer bitmask, without using layer functions.
+ *
+ * @param state Original layer bitmask to check and modify
+ * @param layer1 First layer to check for tri layer
+ * @param layer2 Second layer to check for tri layer
+ * @param layer3 Layer to activate if both other layers are enabled
+ * @return layer_state_t returns a modified layer bitmask with tri layer modifications applied
+ */
+layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3);
#else
# define layer_state 0
@@ 131,6 150,8 @@ layer_state_t layer_state_set_kb(layer_state_t state);
# define layer_xor(state) (void)state
# define layer_state_set_kb(state) (void)state
# define layer_state_set_user(state) (void)state
+# define update_tri_layer(layer1, layer2, layer3)
+# define update_tri_layer_state(state, layer1, layer2, layer3) (void)state
#endif
/* pressed actions cache */
M quantum/keycodes.h => quantum/keycodes.h +5 -1
@@ 717,6 717,8 @@ enum qk_keycode_defines {
QK_AUTOCORRECT_ON = 0x7C74,
QK_AUTOCORRECT_OFF = 0x7C75,
QK_AUTOCORRECT_TOGGLE = 0x7C76,
+ QK_TRI_LAYER_LOWER = 0x7C77,
+ QK_TRI_LAYER_UPPER = 0x7C78,
SAFE_RANGE = 0x7E00,
// Alias
@@ 1282,6 1284,8 @@ enum qk_keycode_defines {
AC_ON = QK_AUTOCORRECT_ON,
AC_OFF = QK_AUTOCORRECT_OFF,
AC_TOGG = QK_AUTOCORRECT_TOGGLE,
+ TL_LOWR = QK_TRI_LAYER_LOWER,
+ TL_UPPR = QK_TRI_LAYER_UPPER,
};
// Range Helpers
@@ 1333,4 1337,4 @@ enum qk_keycode_defines {
#define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31)
#define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING)
#define IS_RGB_KEYCODE(code) ((code) >= RGB_TOG && (code) <= RGB_MODE_TWINKLE)
-#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_AUTOCORRECT_TOGGLE)
+#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_TRI_LAYER_UPPER)
A quantum/process_keycode/process_tri_layer.c => quantum/process_keycode/process_tri_layer.c +30 -0
@@ 0,0 1,30 @@
+// Copyright 2023 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "process_tri_layer.h"
+#include "tri_layer.h"
+#include "action_layer.h"
+
+bool process_tri_layer(uint16_t keycode, keyrecord_t *record) {
+ switch (keycode) {
+ case QK_TRI_LAYER_LOWER:
+ if (record->event.pressed) {
+ layer_on(get_tri_layer_lower_layer());
+ update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer());
+ } else {
+ layer_off(get_tri_layer_lower_layer());
+ update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer());
+ }
+ return false;
+ case QK_TRI_LAYER_UPPER:
+ if (record->event.pressed) {
+ layer_on(get_tri_layer_upper_layer());
+ update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer());
+ } else {
+ layer_off(get_tri_layer_upper_layer());
+ update_tri_layer(get_tri_layer_lower_layer(), get_tri_layer_upper_layer(), get_tri_layer_adjust_layer());
+ }
+ return false;
+ }
+ return true;
+}
A quantum/process_keycode/process_tri_layer.h => quantum/process_keycode/process_tri_layer.h +16 -0
@@ 0,0 1,16 @@
+// Copyright 2023 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "action.h"
+
+/**
+ * @brief Handles tri layer behavior
+ *
+ * @param keycode the keycode
+ * @param record the key record structure
+ * @return true continue handling keycodes
+ * @return false stop handling keycodes
+ */
+bool process_tri_layer(uint16_t keycode, keyrecord_t *record);
M quantum/quantum.c => quantum/quantum.c +3 -10
@@ 343,6 343,9 @@ bool process_record_quantum(keyrecord_t *record) {
#ifdef AUTOCORRECT_ENABLE
process_autocorrect(keycode, record) &&
#endif
+#ifdef TRI_LAYER_ENABLE
+ process_tri_layer(keycode, record) &&
+#endif
true)) {
return false;
}
@@ 443,16 446,6 @@ void set_single_persistent_default_layer(uint8_t default_layer) {
default_layer_set((layer_state_t)1 << default_layer);
}
-layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3) {
- layer_state_t mask12 = ((layer_state_t)1 << layer1) | ((layer_state_t)1 << layer2);
- layer_state_t mask3 = (layer_state_t)1 << layer3;
- return (state & mask12) == mask12 ? (state | mask3) : (state & ~mask3);
-}
-
-void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3) {
- layer_state_set(update_tri_layer_state(layer_state, layer1, layer2, layer3));
-}
-
//------------------------------------------------------------------------------
// Override these functions in your keymap file to play different tunes on
// different events such as startup and bootloader jump
M quantum/quantum.h => quantum/quantum.h +4 -3
@@ 240,9 240,10 @@ extern layer_state_t layer_state;
# include "process_autocorrect.h"
#endif
-// For tri-layer
-void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3);
-layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3);
+#ifdef TRI_LAYER_ENABLE
+# include "tri_layer.h"
+# include "process_tri_layer.h"
+#endif
void set_single_persistent_default_layer(uint8_t default_layer);
A quantum/tri_layer.c => quantum/tri_layer.c +39 -0
@@ 0,0 1,39 @@
+// Copyright 2023 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "tri_layer.h"
+#include <stdint.h>
+
+static uint8_t tri_layer_lower_layer = TRI_LAYER_LOWER_LAYER;
+static uint8_t tri_layer_upper_layer = TRI_LAYER_UPPER_LAYER;
+static uint8_t tri_layer_adjust_layer = TRI_LAYER_ADJUST_LAYER;
+
+void set_tri_layer_lower_layer(uint8_t layer) {
+ tri_layer_lower_layer = layer;
+}
+
+void set_tri_layer_upper_layer(uint8_t layer) {
+ tri_layer_upper_layer = layer;
+}
+
+void set_tri_layer_adjust_layer(uint8_t layer) {
+ tri_layer_adjust_layer = layer;
+}
+
+void set_tri_layer_layers(uint8_t lower, uint8_t raise, uint8_t adjust) {
+ tri_layer_lower_layer = lower;
+ tri_layer_upper_layer = raise;
+ tri_layer_adjust_layer = adjust;
+}
+
+uint8_t get_tri_layer_lower_layer(void) {
+ return tri_layer_lower_layer;
+}
+
+uint8_t get_tri_layer_upper_layer(void) {
+ return tri_layer_upper_layer;
+}
+
+uint8_t get_tri_layer_adjust_layer(void) {
+ return tri_layer_adjust_layer;
+}
A quantum/tri_layer.h => quantum/tri_layer.h +59 -0
@@ 0,0 1,59 @@
+// Copyright 2023 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <stdint.h>
+
+#ifndef TRI_LAYER_LOWER_LAYER
+# define TRI_LAYER_LOWER_LAYER 1
+#endif
+#ifndef TRI_LAYER_UPPER_LAYER
+# define TRI_LAYER_UPPER_LAYER 2
+#endif
+#ifndef TRI_LAYER_ADJUST_LAYER
+# define TRI_LAYER_ADJUST_LAYER 3
+#endif
+
+/**
+ * @brief Set the tri layer lower layer index
+ *
+ * @param layer
+ */
+void set_tri_layer_lower_layer(uint8_t layer);
+/**
+ * @brief Set the tri layer upper layer index
+ *
+ * @param layer
+ */
+void set_tri_layer_upper_layer(uint8_t layer);
+/**
+ * @brief Set the tri layer adjust layer index
+ *
+ * @param layer
+ */
+void set_tri_layer_adjust_layer(uint8_t layer);
+/**
+ * @brief Set the tri layer indices
+ *
+ * @param lower
+ * @param upper
+ * @param adjust
+ */
+void set_tri_layer_layers(uint8_t lower, uint8_t upper, uint8_t adjust);
+/**
+ * @brief Get the tri layer lower layer index
+ *
+ * @return uint8_t
+ */
+uint8_t get_tri_layer_lower_layer(void);
+/**
+ * @brief Get the tri layer upper layer index
+ *
+ * @return uint8_t
+ */
+uint8_t get_tri_layer_upper_layer(void);
+/**
+ * @brief Get the tri layer adjust layer index
+ *
+ * @return uint8_t
+ */
+uint8_t get_tri_layer_adjust_layer(void);
M tests/test_common/keycode_table.cpp => tests/test_common/keycode_table.cpp +2 -0
@@ 659,5 659,7 @@ std::map<uint16_t, std::string> KEYCODE_ID_TABLE = {
{QK_AUTOCORRECT_ON, "QK_AUTOCORRECT_ON"},
{QK_AUTOCORRECT_OFF, "QK_AUTOCORRECT_OFF"},
{QK_AUTOCORRECT_TOGGLE, "QK_AUTOCORRECT_TOGGLE"},
+ {QK_TRI_LAYER_LOWER, "QK_TRI_LAYER_LOWER"},
+ {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"},
{SAFE_RANGE, "SAFE_RANGE"},
};
A tests/tri_layer/config.h => tests/tri_layer/config.h +6 -0
@@ 0,0 1,6 @@
+// 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"
A tests/tri_layer/test.mk => tests/tri_layer/test.mk +8 -0
@@ 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
+# --------------------------------------------------------------------------------
+
+TRI_LAYER_ENABLE = yes
A tests/tri_layer/test_tri_layer.cpp => tests/tri_layer/test_tri_layer.cpp +103 -0
@@ 0,0 1,103 @@
+// Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "test_common.hpp"
+
+using testing::_;
+using testing::InSequence;
+
+class TriLayer : public TestFixture {};
+
+TEST_F(TriLayer, TriLayerLowerTest) {
+ TestDriver driver;
+ KeymapKey lower_layer_key = KeymapKey{0, 0, 0, QK_TRI_LAYER_LOWER};
+
+ set_keymap({lower_layer_key, KeymapKey{1, 0, 0, KC_TRNS}});
+
+ /* Press Lower. */
+ EXPECT_NO_REPORT(driver);
+ lower_layer_key.press();
+ run_one_scan_loop();
+ EXPECT_TRUE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer()));
+ VERIFY_AND_CLEAR(driver);
+
+ /* Release Lower. */
+ EXPECT_NO_REPORT(driver);
+ lower_layer_key.release();
+ run_one_scan_loop();
+ EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer()));
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(TriLayer, TriLayerUpperTest) {
+ TestDriver driver;
+ KeymapKey upper_layer_key = KeymapKey{0, 0, 0, QK_TRI_LAYER_UPPER};
+
+ set_keymap({upper_layer_key, KeymapKey{2, 0, 0, KC_TRNS}});
+
+ /* Press Raise. */
+ EXPECT_NO_REPORT(driver);
+ upper_layer_key.press();
+ run_one_scan_loop();
+ EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_TRUE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer()));
+ VERIFY_AND_CLEAR(driver);
+
+ /* Release Raise. */
+ EXPECT_NO_REPORT(driver);
+ upper_layer_key.release();
+ run_one_scan_loop();
+ EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer()));
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(TriLayer, TriLayerAdjustTest) {
+ TestDriver driver;
+ KeymapKey lower_layer_key = KeymapKey{0, 0, 0, QK_TRI_LAYER_LOWER};
+ KeymapKey upper_layer_key = KeymapKey{0, 1, 0, QK_TRI_LAYER_UPPER};
+
+ set_keymap({
+ upper_layer_key,
+ lower_layer_key,
+ KeymapKey{1, 0, 0, KC_TRNS},
+ KeymapKey{1, 1, 0, KC_TRNS},
+ KeymapKey{2, 0, 0, KC_TRNS},
+ KeymapKey{2, 1, 0, KC_TRNS},
+ KeymapKey{3, 0, 0, KC_TRNS},
+ KeymapKey{3, 1, 0, KC_TRNS},
+ });
+
+ /* Press Lower, then upper, and release upper and then lower. */
+ EXPECT_NO_REPORT(driver);
+ lower_layer_key.press();
+ run_one_scan_loop();
+ EXPECT_TRUE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer()));
+
+ upper_layer_key.press();
+ run_one_scan_loop();
+ EXPECT_TRUE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_TRUE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_TRUE(layer_state_is(get_tri_layer_adjust_layer()));
+
+ lower_layer_key.release();
+ run_one_scan_loop();
+ EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_TRUE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer()));
+
+ upper_layer_key.release();
+ run_one_scan_loop();
+ EXPECT_FALSE(layer_state_is(get_tri_layer_lower_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_upper_layer()));
+ EXPECT_FALSE(layer_state_is(get_tri_layer_adjust_layer()));
+ VERIFY_AND_CLEAR(driver);
+}