diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index fd4b7ab5..a5778e51 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -59,6 +59,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 fcb4a63d..26f9dc37 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -22,3 +22,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/include/zmk/keymap.h b/app/include/zmk/keymap.h index 0d7dbaf3..c8a1da63 100644 --- a/app/include/zmk/keymap.h +++ b/app/include/zmk/keymap.h @@ -15,6 +15,7 @@ typedef uint32_t zmk_keymap_layers_state_t; uint8_t zmk_keymap_layer_default(void); +int zmk_keymap_layer_set_default(uint8_t layer); zmk_keymap_layers_state_t zmk_keymap_layer_state(void); bool zmk_keymap_layer_active(uint8_t layer); uint8_t zmk_keymap_highest_layer_active(void); diff --git a/app/src/behaviors/behavior_default_layer.c b/app/src/behaviors/behavior_default_layer.c new file mode 100644 index 00000000..b186462f --- /dev/null +++ b/app/src/behaviors/behavior_default_layer.c @@ -0,0 +1,158 @@ +/* + * 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); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +struct default_layer_settings_t { + bool using_global_setting; + uint8_t global_default; + uint8_t endpoint_defaults[ZMK_ENDPOINT_COUNT]; +}; + +static struct default_layer_settings_t default_layers = {0}; + +static struct k_work_delayable df_layers_save_work; + +static void zmk_default_layers_save_state_work(struct k_work *_work) { + settings_save_one("default_layer/settings", &default_layers, sizeof(default_layers)); +} + +static int apply_default_layer_config(struct zmk_endpoint_instance endpoint) { + uint8_t index = zmk_endpoint_instance_to_index(endpoint); + uint8_t layer = default_layers.endpoint_defaults[index]; + + 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; + } + + k_work_init_delayable(&df_layers_save_work, zmk_default_layers_save_state_work); + + settings_load_subtree("default_layer"); + + return apply_default_layer_config(zmk_endpoints_selected()); +} +SYS_INIT(default_layer_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +static int save_default_layer_setting(uint8_t layer, struct zmk_endpoint_instance endpoint) { + if (layer >= ZMK_KEYMAP_LAYERS_LEN) { + return -EINVAL; + } + + uint8_t index = zmk_endpoint_instance_to_index(endpoint); + default_layers.endpoint_defaults[index] = layer; + + char endpoint_str[ZMK_ENDPOINT_STR_LEN]; + zmk_endpoint_instance_to_str(endpoint, endpoint_str, sizeof(endpoint_str)); + LOG_INF("Updated default layer (%d) for %s.", layer, endpoint_str); + + int ret = k_work_reschedule(&df_layers_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); + return MIN(0, ret); +} + +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 94bd1204..50f1fedc 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -108,6 +108,29 @@ static inline int set_layer_state(uint8_t layer, bool state) { uint8_t zmk_keymap_layer_default(void) { return _zmk_keymap_layer_default; } +int zmk_keymap_layer_set_default(uint8_t layer) { + int ret = 0; + uint8_t prev_default = _zmk_keymap_layer_default; + + ret = set_layer_state(layer, true); + if (ret < 0) { + LOG_WRN("Failed to activate the new default layer; aborting changes."); + return ret; + } + + _zmk_keymap_layer_default = layer; + ret = set_layer_state(prev_default, false); + if (ret < 0) { + LOG_WRN("Unable to deactivate the current default layer; reverting changes."); + _zmk_keymap_layer_default = prev_default; + set_layer_state(layer, false); + return ret; + } + + LOG_DBG("Default layer changed to: %d", layer); + return 0; +} + zmk_keymap_layers_state_t zmk_keymap_layer_state(void) { return _zmk_keymap_layer_state; } bool zmk_keymap_layer_active_with_state(uint8_t layer, zmk_keymap_layers_state_t state_to_test) { diff --git a/docs/docs/behaviors/index.mdx b/docs/docs/behaviors/index.mdx index bdacc209..9aa2f339 100644 --- a/docs/docs/behaviors/index.mdx +++ b/docs/docs/behaviors/index.mdx @@ -37,6 +37,7 @@ Below is a summary of pre-defined behavior bindings and user-definable behaviors | `&to` | [To Layer](layers.md#to-layer) | Enables a layer and disables all other layers except the default layer | | `&tog` | [Toggle Layer](layers.md#toggle-layer) | Enables a layer until the layer is manually disabled | | `&sl` | [Sticky Layer](sticky-layer.md) | Activates a layer until another key is pressed, then deactivates it | +| `&df` | [Default Layer](layers.md#default-layer) | Change the default layer. Persisted across power reset. | ## Mouse Emulation Behaviors diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 7c95246d..b04174cc 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -120,3 +120,36 @@ Example: 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 + +?> What is the default layer? + +It is the first one you define on your keymap (unless changed using this behavior). It is _special_ in two aspects: + +- It can't be disabled by other behaviors. +- It is the only one active when the board starts running. + +This 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 setting 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 +``` diff --git a/docs/docs/config/keymap.md b/docs/docs/config/keymap.md index f0498b8c..b02c40e2 100644 --- a/docs/docs/config/keymap.md +++ b/docs/docs/config/keymap.md @@ -13,7 +13,7 @@ Applies to: `compatible = "zmk,keymap"` Definition file: [zmk/app/dts/bindings/zmk,keymap.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ckeymap.yaml) -The `zmk,keymap` node itself has no properties. It should have one child node per layer of the keymap, starting with the default layer (layer 0). +The `zmk,keymap` node itself has no properties. It should have one child node per layer of the keymap, the first one being the default layer (until changed with [`&df`](layers.md#default-layer)). Each child node can have the following properties: