chore: Add basic tests for Studio's layer manipulation (#3164)

chore: Add test behaviors for Studio testing

chore: Add basic tests for studio layer adjustment

chore: Fixes from code review
This commit is contained in:
Nicolas Munnich
2026-01-08 01:01:05 +01:00
committed by GitHub
parent 70ab6b243a
commit 19582174f3
38 changed files with 573 additions and 3 deletions

View File

@@ -1,4 +1,5 @@
zephyr_include_directories(include)
add_subdirectory(drivers)
add_subdirectory(test-behaviors)
add_subdirectory(lib)

View File

@@ -1,3 +1,4 @@
rsource "drivers/Kconfig"
rsource "lib/Kconfig"
rsource "lib/Kconfig"
rsource "test-behaviors/Kconfig"

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2025 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Layer Adder Behavior
compatible: "zmk,behavior-add-layer"
include: zero_param.yaml

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2025 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Layer Mover Behavior
compatible: "zmk,behavior-move-layer"
include: two_param.yaml

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2025 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Layer Remover Behavior
compatible: "zmk,behavior-remove-layer"
include: one_param.yaml

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2025 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Layer Binding Setter Behavior
compatible: "zmk,behavior-set-layer-binding-at-idx"
include: two_param.yaml
properties:
bindings:
type: phandle-array
required: true

View File

@@ -0,0 +1,6 @@
if (((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) AND CONFIG_ZMK_TEST_BEHAVIORS)
target_sources(app PRIVATE behavior_add_layer.c)
target_sources(app PRIVATE behavior_move_layer.c)
target_sources(app PRIVATE behavior_remove_layer.c)
target_sources(app PRIVATE behavior_set_layer_binding_at_idx.c)
endif()

View File

@@ -0,0 +1,2 @@
config ZMK_TEST_BEHAVIORS
bool "Include behaviors used for testing purposes"

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_add_layer
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/keymap.h>
#include <zmk/behavior.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if (IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING)) && (DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT))
static int on_add_layer_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
int new_layer = zmk_keymap_add_layer();
if (new_layer >= 0) {
LOG_DBG("Added layer %d", new_layer);
return 0;
}
switch (new_layer) {
case -ENOSPC:
LOG_ERR("No more layers can be added. Out of memory.");
return -ENOSPC;
default:
LOG_ERR("Unknown error adding layer: %d", new_layer);
return new_layer;
}
}
static int on_add_layer_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return 0;
}
static const struct behavior_driver_api behavior_add_layer_driver_api = {
.binding_pressed = on_add_layer_binding_pressed,
.binding_released = on_add_layer_binding_released};
BEHAVIOR_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_add_layer_driver_api);
#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) AND
// DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_move_layer
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/keymap.h>
#include <zmk/behavior.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if (IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING)) && (DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT))
static int on_move_layer_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
int result = zmk_keymap_move_layer(binding->param1, binding->param2);
if (result < 0) {
LOG_ERR("Failed to move layer from index %d to index %d (err: %d)", binding->param1,
binding->param2, result);
return result;
}
LOG_DBG("Moved layer from index %d to index %d", binding->param1, binding->param2);
return 0;
}
static int on_move_layer_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return 0;
}
static const struct behavior_driver_api behavior_move_layer_driver_api = {
.binding_pressed = on_move_layer_binding_pressed,
.binding_released = on_move_layer_binding_released};
BEHAVIOR_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_move_layer_driver_api);
#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) AND
// DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_remove_layer
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/keymap.h>
#include <zmk/behavior.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if (IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING)) && (DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT))
static int on_remove_layer_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
int result = zmk_keymap_remove_layer(binding->param1);
if (result >= 0) {
LOG_DBG("Removed layer at index %d", binding->param1);
return 0;
}
switch (result) {
case -EINVAL:
LOG_ERR("Layer at index %d not found", binding->param1);
return -EINVAL;
default:
LOG_DBG("Unknown error removing layer at index %d: %d", binding->param1, result);
return result;
}
}
static int on_remove_layer_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return 0;
}
static const struct behavior_driver_api behavior_remove_layer_driver_api = {
.binding_pressed = on_remove_layer_binding_pressed,
.binding_released = on_remove_layer_binding_released};
BEHAVIOR_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_remove_layer_driver_api);
#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) AND
// DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_set_layer_binding_at_idx
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/keymap.h>
#include <zmk/behavior.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
struct behavior_set_layer_binding_at_idx_config {
size_t bindings_len;
struct zmk_behavior_binding *bindings;
};
struct behavior_set_layer_binding_at_idx_data {
size_t current_idx;
};
static int on_set_layer_binding_at_idx_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev);
const struct behavior_set_layer_binding_at_idx_config *cfg = dev->config;
struct behavior_set_layer_binding_at_idx_data *data = dev->data;
if (cfg->bindings_len == 0) {
LOG_ERR("No bindings configured");
return -EINVAL;
}
struct zmk_behavior_binding *binding_to_set = &cfg->bindings[data->current_idx];
int result =
zmk_keymap_set_layer_binding_at_idx(binding->param1, binding->param2, *binding_to_set);
if (result < 0) {
LOG_ERR("Failed to set binding at layer %d, index %d (err: %d)", binding->param1,
binding->param2, result);
return result;
}
LOG_DBG("Set binding at layer %d, index %d to binding %zu/%zu", binding->param1,
binding->param2, data->current_idx + 1, cfg->bindings_len);
// Move to next binding, wrap around if at end
data->current_idx = (data->current_idx + 1) % cfg->bindings_len;
return 0;
}
static int on_set_layer_binding_at_idx_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return 0;
}
static const struct behavior_driver_api behavior_set_layer_binding_at_idx_driver_api = {
.binding_pressed = on_set_layer_binding_at_idx_binding_pressed,
.binding_released = on_set_layer_binding_at_idx_binding_released};
static int behavior_set_layer_binding_at_idx_init(const struct device *dev) {
struct behavior_set_layer_binding_at_idx_data *data = dev->data;
data->current_idx = 0;
return 0;
};
#define _TRANSFORM_ENTRY(idx, node) ZMK_KEYMAP_EXTRACT_BINDING(idx, node)
#define TRANSFORMED_BINDINGS(node) \
{LISTIFY(DT_INST_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, (, ), DT_DRV_INST(node))}
#define SET_LAYER_BINDING_AT_IDX_INST(n) \
static struct behavior_set_layer_binding_at_idx_data \
behavior_set_layer_binding_at_idx_data_##n = {}; \
\
static const struct zmk_behavior_binding \
behavior_set_layer_binding_at_idx_config_##n##_bindings[DT_INST_PROP_LEN(n, bindings)] = \
TRANSFORMED_BINDINGS(n); \
static const struct behavior_set_layer_binding_at_idx_config \
behavior_set_layer_binding_at_idx_config_##n = { \
.bindings_len = DT_INST_PROP_LEN(n, bindings), \
.bindings = behavior_set_layer_binding_at_idx_config_##n##_bindings}; \
\
BEHAVIOR_DT_INST_DEFINE(n, behavior_set_layer_binding_at_idx_init, NULL, \
&behavior_set_layer_binding_at_idx_data_##n, \
&behavior_set_layer_binding_at_idx_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_set_layer_binding_at_idx_driver_api);
DT_INST_FOREACH_STATUS_OKAY(SET_LAYER_BINDING_AT_IDX_INST)
#endif // DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)