From 4c6818a86edc30c97ce3d22d9264c6d7ae29a044 Mon Sep 17 00:00:00 2001
From: elpekenin <elpekenin@elpekenin.dev>
Date: Thu, 4 Apr 2024 17:31:22 +0200
Subject: [PATCH] feat: `&df` behavior and docs

---
 app/CMakeLists.txt                            |   1 +
 app/dts/behaviors.dtsi                        |   1 +
 app/dts/behaviors/default_layer.dtsi          |  14 ++
 .../behaviors/zmk,behavior-default-layer.yaml |   8 +
 app/src/behaviors/behavior_default_layer.c    | 176 ++++++++++++++++++
 app/src/keymap.c                              |   2 +-
 docs/docs/behaviors/layers.md                 |  26 +++
 7 files changed, 227 insertions(+), 1 deletion(-)
 create mode 100644 app/dts/behaviors/default_layer.dtsi
 create mode 100644 app/dts/bindings/behaviors/zmk,behavior-default-layer.yaml
 create mode 100644 app/src/behaviors/behavior_default_layer.c

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 <behaviors/macros.dtsi>
 #include <behaviors/mouse_key_press.dtsi>
 #include <behaviors/soft_off.dtsi>
+#include <behaviors/default_layer.dtsi>
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 <zephyr/device.h>
+#include <drivers/behavior.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/settings/settings.h>
+
+#include <zmk/behavior.h>
+#include <zmk/endpoints.h>
+#include <zmk/keymap.h>
+
+#include <zmk/event_manager.h>
+#include <zmk/events/endpoint_changed.h>
+
+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
+```