refactor(keys): Unify usage page.

* Remove need for separate `&cp` behavior, but
  keep it for now for backward compat.
* Refactor sensor inc/dec as well.
This commit is contained in:
Pete Johanson 2020-10-26 00:30:24 -04:00
parent 5f83568a93
commit eff1b8223b
20 changed files with 474 additions and 492 deletions

View file

@ -3,17 +3,10 @@
/ { / {
behaviors { behaviors {
kp: behavior_key_press { /* DEPRECATED: `cp` will be removed in the future */
cp: kp: behavior_key_press {
compatible = "zmk,behavior-key-press"; compatible = "zmk,behavior-key-press";
label = "KEY_PRESS"; label = "KEY_PRESS";
usage_page = <HID_USAGE_KEY>;
#binding-cells = <1>;
};
cp: behavior_consumer_press {
compatible = "zmk,behavior-key-press";
label = "CONSUMER_PRESS";
usage_page = <HID_USAGE_CONSUMER>;
#binding-cells = <1>; #binding-cells = <1>;
}; };
}; };

View file

@ -3,17 +3,10 @@
/ { / {
behaviors { behaviors {
inc_dec_kp: behavior_sensor_rotate_key_press { /* DEPRECATED: `inc_dec_cp` will be removed in the future */
inc_dec_cp: inc_dec_kp: behavior_sensor_rotate_key_press {
compatible = "zmk,behavior-sensor-rotate-key-press"; compatible = "zmk,behavior-sensor-rotate-key-press";
label = "ENC_KEY_PRESS"; label = "ENC_KEY_PRESS";
usage_page = <HID_USAGE_KEY>;
#sensor-binding-cells = <2>;
};
inc_dec_cp: behavior_sensor_rotate_consumer_press {
compatible = "zmk,behavior-sensor-rotate-key-press";
label = "ENC_CONSUMER_PRESS";
usage_page = <HID_USAGE_CONSUMER>;
#sensor-binding-cells = <2>; #sensor-binding-cells = <2>;
}; };
}; };

View file

@ -6,8 +6,3 @@ description: Key press/release behavior
compatible: "zmk,behavior-key-press" compatible: "zmk,behavior-key-press"
include: one_param.yaml include: one_param.yaml
properties:
usage_page:
type: int
default: 0

View file

@ -13,9 +13,6 @@ properties:
type: int type: int
required: true required: true
const: 2 const: 2
usage_page:
type: int
default: 0
sensor-binding-cells: sensor-binding-cells:
- param1 - param1

View file

@ -10,6 +10,10 @@
#pragma once #pragma once
#define HID_EXT_USAGE(page, id) ((page << 16) | id)
#define HID_EXT_USAGE_ID(ext) (ext & 0xFFFF)
#define HID_EXT_USAGE_PAGE(ext) (ext >> 16)
/* WARNING: DEPRECATED from dt-bindings/zmk/keys.h */ /* WARNING: DEPRECATED from dt-bindings/zmk/keys.h */
#define USAGE_KEYPAD (0x07) // WARNING: DEPRECATED (DO NOT USE) #define USAGE_KEYPAD (0x07) // WARNING: DEPRECATED (DO NOT USE)
#define USAGE_CONSUMER (0x0C) // WARNING: DEPRECATED (DO NOT USE) #define USAGE_CONSUMER (0x0C) // WARNING: DEPRECATED (DO NOT USE)

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,9 @@
#include <zephyr.h> #include <zephyr.h>
#include <dt-bindings/zmk/modifiers.h> #include <dt-bindings/zmk/modifiers.h>
#include <dt-bindings/zmk/hid_usage_pages.h>
#include <zmk/event-manager.h> #include <zmk/event-manager.h>
#include <zmk/keys.h>
struct keycode_state_changed { struct keycode_state_changed {
struct zmk_event_header header; struct zmk_event_header header;
@ -20,12 +22,20 @@ struct keycode_state_changed {
ZMK_EVENT_DECLARE(keycode_state_changed); ZMK_EVENT_DECLARE(keycode_state_changed);
static inline struct keycode_state_changed * static inline struct keycode_state_changed *keycode_state_changed_from_encoded(u32_t encoded,
create_keycode_state_changed(u8_t usage_page, u32_t keycode, bool state) { bool pressed) {
u16_t page = HID_EXT_USAGE_PAGE(encoded) & 0xFF;
u16_t id = HID_EXT_USAGE_ID(encoded);
zmk_mod_flags implicit_mods = SELECT_MODS(encoded);
if (!page) {
page = HID_USAGE_KEY;
}
struct keycode_state_changed *ev = new_keycode_state_changed(); struct keycode_state_changed *ev = new_keycode_state_changed();
ev->usage_page = usage_page; ev->usage_page = page;
ev->keycode = STRIP_MODS(keycode); ev->keycode = id;
ev->implicit_modifiers = SELECT_MODS(keycode); ev->implicit_modifiers = implicit_mods;
ev->state = state; ev->state = pressed;
return ev; return ev;
} }

View file

@ -429,8 +429,8 @@ static int position_state_changed_listener(const struct zmk_event_header *eh) {
} }
static inline bool only_mods(struct keycode_state_changed *ev) { static inline bool only_mods(struct keycode_state_changed *ev) {
return ev->usage_page == HID_USAGE_KEY && ev->keycode >= LEFT_CONTROL && return ev->usage_page == HID_USAGE_KEY && ev->keycode >= HID_USAGE_KEY_KEYBOARD_LEFTCONTROL &&
ev->keycode <= RIGHT_GUI; ev->keycode <= HID_USAGE_KEY_KEYBOARD_RIGHT_GUI;
} }
static int keycode_state_changed_listener(const struct zmk_event_header *eh) { static int keycode_state_changed_listener(const struct zmk_event_header *eh) {

View file

@ -16,42 +16,28 @@
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct behavior_key_press_config {
u8_t usage_page;
};
struct behavior_key_press_data {};
static int behavior_key_press_init(struct device *dev) { return 0; }; static int behavior_key_press_init(struct device *dev) { return 0; };
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) { struct zmk_behavior_binding_event event) {
struct device *dev = device_get_binding(binding->behavior_dev); LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
const struct behavior_key_press_config *cfg = dev->config_info;
LOG_DBG("position %d usage_page 0x%02X keycode 0x%02X", event.position, cfg->usage_page,
binding->param1);
return ZMK_EVENT_RAISE(create_keycode_state_changed(cfg->usage_page, binding->param1, true)); return ZMK_EVENT_RAISE(keycode_state_changed_from_encoded(binding->param1, true));
} }
static int on_keymap_binding_released(struct zmk_behavior_binding *binding, static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) { struct zmk_behavior_binding_event event) {
struct device *dev = device_get_binding(binding->behavior_dev); LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
const struct behavior_key_press_config *cfg = dev->config_info;
LOG_DBG("position %d usage_page 0x%02X keycode 0x%02X", event.position, cfg->usage_page,
binding->param1);
return ZMK_EVENT_RAISE(create_keycode_state_changed(cfg->usage_page, binding->param1, false)); return ZMK_EVENT_RAISE(keycode_state_changed_from_encoded(binding->param1, false));
} }
static const struct behavior_driver_api behavior_key_press_driver_api = { static const struct behavior_driver_api behavior_key_press_driver_api = {
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
#define KP_INST(n) \ #define KP_INST(n) \
static const struct behavior_key_press_config behavior_key_press_config_##n = { \ DEVICE_AND_API_INIT(behavior_key_press_##n, DT_INST_LABEL(n), behavior_key_press_init, NULL, \
.usage_page = DT_INST_PROP(n, usage_page)}; \ NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
static struct behavior_key_press_data behavior_key_press_data_##n; \ &behavior_key_press_driver_api);
DEVICE_AND_API_INIT(behavior_key_press_##n, DT_INST_LABEL(n), behavior_key_press_init, \
&behavior_key_press_data_##n, &behavior_key_press_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_key_press_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST) DT_INST_FOREACH_STATUS_OKAY(KP_INST)

View file

@ -16,23 +16,14 @@
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct behavior_sensor_rotate_key_press_config {
u8_t usage_page;
};
struct behavior_sensor_rotate_key_press_data {};
static int behavior_sensor_rotate_key_press_init(struct device *dev) { return 0; }; static int behavior_sensor_rotate_key_press_init(struct device *dev) { return 0; };
static int on_sensor_binding_triggered(struct zmk_behavior_binding *binding, static int on_sensor_binding_triggered(struct zmk_behavior_binding *binding,
struct device *sensor) { struct device *sensor) {
struct device *dev = device_get_binding(binding->behavior_dev);
const struct behavior_sensor_rotate_key_press_config *cfg = dev->config_info;
struct sensor_value value; struct sensor_value value;
int err; int err;
u32_t keycode; u32_t keycode;
struct keycode_state_changed *ev; LOG_DBG("inc keycode 0x%02X dec keycode 0x%02X", binding->param1, binding->param2);
LOG_DBG("usage_page 0x%02X inc keycode 0x%02X dec keycode 0x%02X", cfg->usage_page,
binding->param1, binding->param2);
err = sensor_channel_get(sensor, SENSOR_CHAN_ROTATION, &value); err = sensor_channel_get(sensor, SENSOR_CHAN_ROTATION, &value);
@ -54,33 +45,21 @@ static int on_sensor_binding_triggered(struct zmk_behavior_binding *binding,
LOG_DBG("SEND %d", keycode); LOG_DBG("SEND %d", keycode);
ev = new_keycode_state_changed(); ZMK_EVENT_RAISE(keycode_state_changed_from_encoded(keycode, true));
ev->usage_page = cfg->usage_page;
ev->keycode = keycode;
ev->state = true;
ZMK_EVENT_RAISE(ev);
// TODO: Better way to do this? // TODO: Better way to do this?
k_msleep(5); k_msleep(5);
ev = new_keycode_state_changed(); return ZMK_EVENT_RAISE(keycode_state_changed_from_encoded(keycode, false));
ev->usage_page = cfg->usage_page;
ev->keycode = keycode;
ev->state = false;
return ZMK_EVENT_RAISE(ev);
} }
static const struct behavior_driver_api behavior_sensor_rotate_key_press_driver_api = { static const struct behavior_driver_api behavior_sensor_rotate_key_press_driver_api = {
.sensor_binding_triggered = on_sensor_binding_triggered}; .sensor_binding_triggered = on_sensor_binding_triggered};
#define KP_INST(n) \ #define KP_INST(n) \
static const struct behavior_sensor_rotate_key_press_config \ DEVICE_AND_API_INIT(behavior_sensor_rotate_key_press_##n, DT_INST_LABEL(n), \
behavior_sensor_rotate_key_press_config_##n = {.usage_page = DT_INST_PROP(n, usage_page)}; \ behavior_sensor_rotate_key_press_init, NULL, NULL, APPLICATION, \
static struct behavior_sensor_rotate_key_press_data behavior_sensor_rotate_key_press_data_##n; \ CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
DEVICE_AND_API_INIT( \ &behavior_sensor_rotate_key_press_driver_api);
behavior_sensor_rotate_key_press_##n, DT_INST_LABEL(n), \
behavior_sensor_rotate_key_press_init, &behavior_sensor_rotate_key_press_data_##n, \
&behavior_sensor_rotate_key_press_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_sensor_rotate_key_press_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST) DT_INST_FOREACH_STATUS_OKAY(KP_INST)

View file

@ -9,6 +9,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/hid.h> #include <zmk/hid.h>
#include <dt-bindings/zmk/modifiers.h> #include <dt-bindings/zmk/modifiers.h>
#include <dt-bindings/zmk/hid_usage_pages.h>
static struct zmk_hid_keypad_report kp_report = { static struct zmk_hid_keypad_report kp_report = {
.report_id = 1, .body = {.modifiers = 0, ._reserved = 0, .keys = {0}}}; .report_id = 1, .body = {.modifiers = 0, ._reserved = 0, .keys = {0}}};
@ -78,16 +79,16 @@ int zmk_hid_implicit_modifiers_release() {
} }
int zmk_hid_keypad_press(zmk_key code) { int zmk_hid_keypad_press(zmk_key code) {
if (code >= LEFT_CONTROL && code <= RIGHT_GUI) { if (code >= HID_USAGE_KEY_KEYBOARD_LEFTCONTROL && code <= HID_USAGE_KEY_KEYBOARD_RIGHT_GUI) {
return zmk_hid_register_mod(code - LEFT_CONTROL); return zmk_hid_register_mod(code - HID_USAGE_KEY_KEYBOARD_LEFTCONTROL);
} }
TOGGLE_KEYPAD(0U, code); TOGGLE_KEYPAD(0U, code);
return 0; return 0;
}; };
int zmk_hid_keypad_release(zmk_key code) { int zmk_hid_keypad_release(zmk_key code) {
if (code >= LEFT_CONTROL && code <= RIGHT_GUI) { if (code >= HID_USAGE_KEY_KEYBOARD_LEFTCONTROL && code <= HID_USAGE_KEY_KEYBOARD_RIGHT_GUI) {
return zmk_hid_unregister_mod(code - LEFT_CONTROL); return zmk_hid_unregister_mod(code - HID_USAGE_KEY_KEYBOARD_LEFTCONTROL);
} }
TOGGLE_KEYPAD(code, 0U); TOGGLE_KEYPAD(code, 0U);
return 0; return 0;

View file

@ -10,7 +10,7 @@
default_layer { default_layer {
bindings = < bindings = <
&kp B &none &kp B &none
&cp C_NEXT &none>; &kp C_NEXT &none>;
}; };
}; };
}; };

View file

@ -15,7 +15,7 @@
lower_layer { lower_layer {
bindings = < bindings = <
&cp C_NEXT &trans &kp C_NEXT &trans
&kp L &kp J>; &kp L &kp J>;
}; };

View file

@ -15,7 +15,7 @@
lower_layer { lower_layer {
bindings = < bindings = <
&cp C_NEXT &trans &kp C_NEXT &trans
&kp L &kp J>; &kp L &kp J>;
}; };

View file

@ -62,7 +62,7 @@ A code example which configures a mod-tap setting that works with homerow mods:
If this config does not work for you, try the flavor "tap-preferred" and a short tapping_term_ms such as 120ms. If this config does not work for you, try the flavor "tap-preferred" and a short tapping_term_ms such as 120ms.
If you want to use a tap-hold with a keycode from a different code page, you have to define another behavior with another "bindings" parameter.For example, if you want to use SHIFT and volume up, define the bindings like `bindings = <&kp>, <&cp>;`. Only single-argument behaviors are supported at the moment. If you want to use a tap-hold with a keycode from a different code page, you have to define another behavior with another "bindings" parameter.For example, if you want to use SHIFT and volume up, define the bindings like `bindings = <&kp>, <&kp>;`. Only single-argument behaviors are supported at the moment.
#### Comparison to QMK #### Comparison to QMK

View file

@ -46,22 +46,3 @@ Example:
``` ```
&kp A &kp A
``` ```
## Consumer Key Press
The "consumer key press" behavior allows you to send "consumer" usage page keycodes on press/release.
These are mostly used for media and power related keycodes, such as sending "Pause", "Scan Track Next",
"Scan Track Previous", etc.
There are a subset of the full consumer usage IDs found in the `keys.h` include, prefixed with `C_`, e.g. `C_PREV`.
### Behavior Binding
- Reference: `&cp`
- Parameter: The keycode usage ID from the consumer usage page, e.g. `C_PREV` or `C_EJECT`
Example:
```
&cp C_PREV
```

View file

@ -377,7 +377,7 @@ Here is an example simple keymap for the Kyria, with only one layer:
&kp LGUI &kp DEL &kp RET &kp SPACE &kp ESC &kp RET &kp SPACE &kp TAB &kp BSPC &kp RALT &kp LGUI &kp DEL &kp RET &kp SPACE &kp ESC &kp RET &kp SPACE &kp TAB &kp BSPC &kp RALT
>; >;
sensor-bindings = <&inc_dec_cp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>; sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>;
}; };
}; };
}; };
@ -385,15 +385,14 @@ Here is an example simple keymap for the Kyria, with only one layer:
``` ```
:::note :::note
The two `#include` lines at the top of the keymap are required in order to bring in the default set of behaviors to make them available to bind, and to import a set of defines for the HID keycodes, so keymaps can use parameters like `N2` or `K` instead of the raw keycode numeric values. The two `#include` lines at the top of the keymap are required in order to bring in the default set of behaviors to make them available to bind, and to import a set of defines for the key codes, so keymaps can use parameters like `N2` or `K` instead of the raw keycode numeric values.
::: :::
### Keymap Behaviors ### Keymap Behaviors
Further documentation on behaviors and bindings is forthcoming, but a summary of the current behaviors you can bind to key positions is as follows: Further documentation on behaviors and bindings is forthcoming, but a summary of the current behaviors you can bind to key positions is as follows:
- `kp` is the "key press" behavior, and takes a single binding argument of the HID keycode from the 'keyboard/keypad" HID usage table. - `kp` is the "key press" behavior, and takes a single binding argument of the key code from the 'keyboard/keypad" HID usage table.
- `cp` is the "consumer key press" behavior, and takes a single binding argument of the HID keycode from the "consumer page" HID usage table. This is mostly useful for media keys.
- `mo` is the "momentary layer" behaviour, and takes a single binding argument of the numeric ID of the layer to momentarily enable when that key is held. - `mo` is the "momentary layer" behaviour, and takes a single binding argument of the numeric ID of the layer to momentarily enable when that key is held.
- `trans` is the "transparent" behavior, useful to be place in higher layers above `mo` bindings to be sure the key release is handled by the lower layer. No binding arguments are required. - `trans` is the "transparent" behavior, useful to be place in higher layers above `mo` bindings to be sure the key release is handled by the lower layer. No binding arguments are required.
- `mt` is the "mod-tap" behavior, and takes two binding arguments, the modifier to use if held, and the keycode to send if tapped. - `mt` is the "mod-tap" behavior, and takes two binding arguments, the modifier to use if held, and the keycode to send if tapped.
@ -476,7 +475,7 @@ For split keyboards, make sure to add left hand encoders to the left .overlay fi
Add the following line to your keymap file to add default encoder behavior bindings: Add the following line to your keymap file to add default encoder behavior bindings:
``` ```
sensor-bindings = <&inc_dec_cp C_VOL_UP C_VOL_DN>; sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN>;
``` ```
Add additional bindings as necessary to match the default number of encoders on your board. See the [Encoders](/docs/feature/encoders) and [Keymap](/docs/feature/keymaps) feature documentation for more details. Add additional bindings as necessary to match the default number of encoders on your board. See the [Encoders](/docs/feature/encoders) and [Keymap](/docs/feature/keymaps) feature documentation for more details.

View file

@ -25,7 +25,7 @@ Rotation is handled separately as a type of sensor. The behavior for this is set
sensor-bindings = <BINDING CW_KEY CCW_KEY>; sensor-bindings = <BINDING CW_KEY CCW_KEY>;
``` ```
- `BINDING` is one of two rotation bindings that are currently defined, `&inc_dec_cp` for consumer key presses or `&inc_dec_kp` for normal key presses (see [Key Press](/docs/behavior/key-press) for the difference between the two). - `BINDING`, for now, has only one behavior available; `&inc_dec_kp` for key presses (see [Key Press](/docs/behavior/key-press) for details on available keycodes).
- `CW_KEY` is the keycode activated by a clockwise turn. - `CW_KEY` is the keycode activated by a clockwise turn.
- `CCW_KEY` is the keycode activated by a counter-clockwise turn. - `CCW_KEY` is the keycode activated by a counter-clockwise turn.
@ -34,7 +34,7 @@ Additional encoders can be configured by adding more `BINDING CW_KEY CCW_KEY` se
As an example, a complete `sensor-bindings` for a Kyria with two encoders could look like: As an example, a complete `sensor-bindings` for a Kyria with two encoders could look like:
``` ```
sensor-bindings = <&inc_dec_cp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>; sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>;
``` ```
Here, the left encoder is configured to control volume up and down while the right encoder sends either Page Up or Page Down. Here, the left encoder is configured to control volume up and down while the right encoder sends either Page Up or Page Down.

View file

@ -139,7 +139,7 @@ that defines just one layer for this keymap:
&kp LGUI &kp DEL &kp RET &kp SPACE &kp ESC &kp RET &kp SPACE &kp TAB &kp BSPC &kp RALT &kp LGUI &kp DEL &kp RET &kp SPACE &kp ESC &kp RET &kp SPACE &kp TAB &kp BSPC &kp RALT
>; >;
sensor-bindings = <&inc_dec_cp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>; sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>;
}; };
}; };
``` ```
@ -176,7 +176,7 @@ Putting this all together, a complete [`kyria.keymap`](https://github.com/zmkfir
&kp LGUI &kp DEL &kp RET &kp SPACE &kp ESC &kp RET &kp SPACE &kp TAB &kp BSPC &kp RALT &kp LGUI &kp DEL &kp RET &kp SPACE &kp ESC &kp RET &kp SPACE &kp TAB &kp BSPC &kp RALT
>; >;
sensor-bindings = <&inc_dec_cp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>; sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>;
}; };
}; };
}; };

View file

@ -22,7 +22,7 @@ ZMK is currently missing some features found in other popular firmware. This tab
| [Keymaps and Layers](behavior/layers) | ✅ | ✅ | ✅ | | [Keymaps and Layers](behavior/layers) | ✅ | ✅ | ✅ |
| [Hold-Tap](behavior/hold-tap) (which includes [Mod-Tap](behavior/mod-tap) and [Layer-Tap](behavior/layers/#layer-tap)) | ✅ | ✅ | ✅ | | [Hold-Tap](behavior/hold-tap) (which includes [Mod-Tap](behavior/mod-tap) and [Layer-Tap](behavior/layers/#layer-tap)) | ✅ | ✅ | ✅ |
| [Basic Keycodes](behavior/key-press) | ✅ | ✅ | ✅ | | [Basic Keycodes](behavior/key-press) | ✅ | ✅ | ✅ |
| [Basic consumer (Media) Keycodes](behavior/key-press#consumer-key-press) | ✅ | ✅ | ✅ | | [Basic consumer (Media) Keycodes](behavior/key-press) | ✅ | ✅ | ✅ |
| [Encoders](feature/encoders)[^1] | ✅ | | ✅ | | [Encoders](feature/encoders)[^1] | ✅ | | ✅ |
| [OLED Display Support](feature/displays)[^2] | 🚧 | 🚧 | ✅ | | [OLED Display Support](feature/displays)[^2] | 🚧 | 🚧 | ✅ |
| [RGB Underglow](feature/underglow) | ✅ | ✅ | ✅ | | [RGB Underglow](feature/underglow) | ✅ | ✅ | ✅ |