diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 0b681ea9..7d7fa6b1 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -55,6 +55,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_to_layer.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) + target_sources(app PRIVATE src/behaviors/behavior_default_layer.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE app PRIVATE src/behaviors/behavior_sensor_rotate.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR app PRIVATE src/behaviors/behavior_sensor_rotate_var.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON app PRIVATE src/behaviors/behavior_sensor_rotate_common.c) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index fde75271..0899134c 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -21,3 +21,4 @@ #include #include #include +#include diff --git a/app/dts/behaviors/default_layer.dtsi b/app/dts/behaviors/default_layer.dtsi new file mode 100644 index 00000000..e4df08b4 --- /dev/null +++ b/app/dts/behaviors/default_layer.dtsi @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + behaviors { + /omit-if-no-ref/ df: default_layer { + compatible = "zmk,behavior-default-layer"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-default-layer.yaml b/app/dts/bindings/behaviors/zmk,behavior-default-layer.yaml new file mode 100644 index 00000000..1dab8065 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-default-layer.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Behavior to change default layer for current endpoint + +compatible: "zmk,behavior-default-layer" + +include: one_param.yaml diff --git a/app/src/behaviors/behavior_default_layer.c b/app/src/behaviors/behavior_default_layer.c new file mode 100644 index 00000000..a5895b94 --- /dev/null +++ b/app/src/behaviors/behavior_default_layer.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_default_layer + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct default_layer_settings_t { + uint8_t usb[ZMK_ENDPOINT_USB_COUNT]; + uint8_t ble[ZMK_ENDPOINT_BLE_COUNT]; +}; + +static struct default_layer_settings_t default_layers = {0}; + +static int apply_default_layer_config(struct zmk_endpoint_instance endpoint) { + uint8_t layer = 0; + + switch (endpoint.transport) { + case ZMK_TRANSPORT_USB: + __ASSERT(ZMK_ENDPOINT_USB_COUNT == 1, "Unreachable"); + layer = default_layers.usb[0]; + break; + + case ZMK_TRANSPORT_BLE: + __ASSERT(endpoint.ble.profile_index < ZMK_ENDPOINT_BLE_COUNT, "Unreachable"); + layer = default_layers.ble[endpoint.ble.profile_index]; + break; + } + + int ret = zmk_keymap_layer_set_default(layer); + if (ret < 0) { + LOG_WRN("Could not apply default layer from settings. Perhaps something in the code/keymap " + "changed since configuration was saved."); + return ret; + } + + LOG_INF("Activated default layer (%d) for the current endpoint.", layer); + return 0; +} + +static int default_layer_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { + const char *next; + int rc; + + if (settings_name_steq(name, "settings", &next) && !next) { + if (len != sizeof(default_layers)) { + return -EINVAL; + } + + rc = read_cb(cb_arg, &default_layers, sizeof(default_layers)); + if (rc >= 0) { + return 0; + } + + return rc; + } + + return -ENOENT; +} + +struct settings_handler default_layer_conf = { + .name = "default_layer", + .h_set = default_layer_set, +}; + +static int default_layer_init(void) { + settings_subsys_init(); + + int ret = settings_register(&default_layer_conf); + if (ret) { + LOG_ERR("Could not register default layer settings (%d).", ret); + return ret; + } + + settings_load_subtree("default_layer"); + + return apply_default_layer_config(zmk_endpoints_selected()); +} +SYS_INIT(default_layer_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int save_default_layer_setting(uint8_t layer, struct zmk_endpoint_instance endpoint) { + if (layer >= ZMK_KEYMAP_LAYERS_LEN) { + return -EINVAL; + } + + switch (endpoint.transport) { + case ZMK_TRANSPORT_USB: + __ASSERT(ZMK_ENDPOINT_USB_COUNT == 1, "Unreachable"); + default_layers.usb[0] = layer; + break; + + case ZMK_TRANSPORT_BLE: + __ASSERT(endpoint.ble.profile_index < ZMK_ENDPOINT_BLE_COUNT, "Unreachable"); + default_layers.ble[endpoint.ble.profile_index] = layer; + break; + } + + int ret = settings_save_one("default_layer/settings", &default_layers, sizeof(default_layers)); + if (ret < 0) { + LOG_WRN("Could not update the settings."); + return ret; + } + + if (endpoint.transport == ZMK_TRANSPORT_USB) { + LOG_INF("Updated default layer (%d) for USB endpoint.", layer); + } else { + LOG_INF("Updated default layer (%d) for BLE endpoint %d.", layer, + endpoint.ble.profile_index); + } + return 0; +} + +static int behavior_default_layer_init(const struct device *dev) { return 0; } + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + int ret = 0; + struct zmk_endpoint_instance endpoint = zmk_endpoints_selected(); + + ret = save_default_layer_setting(binding->param1, endpoint); + if (ret < 0) { + return ret; + } + + ret = apply_default_layer_config(endpoint); + if (ret < 0) { + return ret; + } + + return 0; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + return ZMK_BEHAVIOR_OPAQUE; +} + +static const struct behavior_driver_api behavior_default_layer_driver_api = { + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released, +}; + +BEHAVIOR_DT_INST_DEFINE(0, behavior_default_layer_init, NULL, NULL, NULL, POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_default_layer_driver_api); + +#endif + +static int endpoint_changed_cb(const zmk_event_t *eh) { + struct zmk_endpoint_changed *evt = as_zmk_endpoint_changed(eh); + + if (evt != NULL) { + apply_default_layer_config(evt->endpoint); + } + + return ZMK_EV_EVENT_BUBBLE; +} + +ZMK_LISTENER(endpoint, endpoint_changed_cb); +ZMK_SUBSCRIPTION(endpoint, zmk_endpoint_changed); diff --git a/app/src/keymap.c b/app/src/keymap.c index d290f3bb..50f1fedc 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -127,7 +127,7 @@ int zmk_keymap_layer_set_default(uint8_t layer) { return ret; } - LOG_DBG("default_layer_changed: %d", layer); + LOG_DBG("Default layer changed to: %d", layer); return 0; } diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 7cfb4df7..3efb50bf 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -153,3 +153,29 @@ It is possible to use "toggle layer" to have keys that raise and lower the layer The "conditional layers" feature enables a particular layer when all layers in a specified set are active. For more information, see [conditional layers](../features/conditional-layers.md). + +## Default Layer + +The default layer behavior allows configuring a different default layer, for example to test DVORAK while keeping QWERTY on another layer, or moving a couple keycodes around for Windows/Mac usage. + +This is stored on a per-endpoint basis, so you can configure USB to use QWERTY, and the first BLE endpoint to use DVORAK. + +The stored settings are read and applied when the keyboard boots (receives powers) and also when the selected endpoint changes. + +### Behavior Binding + +- Reference: `&df` +- Parameter: The layer number to set as default for current endpoint, e.g. `1` + +Example: + +```dts +&df DVORAK +``` + +For a keymap with: + +```dts +#define QWERTY 0 +#define DVORAK 1 +```