From a9d724f296224e10989fbcb13b2f43b9db6cc700 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Wed, 27 Mar 2024 12:27:49 -0700 Subject: [PATCH] feat(behaviors): Add behavior metadata information. * For upcoming ZMK studio work, make a set of rich metadata available to provide a friendly name for a behavior, and allow super flexible descriptions of the parameters the behaviors take. --- app/Kconfig.behaviors | 4 + app/dts/behaviors/backlight.dtsi | 1 + app/dts/behaviors/bluetooth.dtsi | 1 + app/dts/behaviors/caps_word.dtsi | 1 + app/dts/behaviors/ext_power.dtsi | 1 + app/dts/behaviors/gresc.dtsi | 1 + app/dts/behaviors/key_press.dtsi | 1 + app/dts/behaviors/key_repeat.dtsi | 1 + app/dts/behaviors/key_toggle.dtsi | 1 + app/dts/behaviors/layer_tap.dtsi | 1 + app/dts/behaviors/mod_tap.dtsi | 1 + app/dts/behaviors/momentary_layer.dtsi | 1 + app/dts/behaviors/none.dtsi | 1 + app/dts/behaviors/outputs.dtsi | 1 + app/dts/behaviors/reset.dtsi | 2 + app/dts/behaviors/rgb_underglow.dtsi | 1 + app/dts/behaviors/sticky_key.dtsi | 2 + app/dts/behaviors/to_layer.dtsi | 1 + app/dts/behaviors/toggle_layer.dtsi | 1 + app/dts/behaviors/transparent.dtsi | 1 + .../bindings/behaviors/behavior-metadata.yaml | 6 + app/dts/bindings/behaviors/one_param.yaml | 2 + app/dts/bindings/behaviors/two_param.yaml | 2 + app/dts/bindings/behaviors/zero_param.yaml | 2 + app/include/drivers/behavior.h | 142 +++++++++++++++++- app/src/behaviors/behavior_bt.c | 70 +++++++++ app/src/behaviors/behavior_hold_tap.c | 49 ++++++ app/src/behaviors/behavior_key_press.c | 7 +- app/src/behaviors/behavior_key_toggle.c | 3 + app/src/behaviors/behavior_macro.c | 63 ++++++++ app/src/behaviors/behavior_momentary_layer.c | 7 +- app/src/behaviors/behavior_outputs.c | 42 ++++++ app/src/behaviors/behavior_rgb_underglow.c | 118 +++++++++++++++ app/src/behaviors/behavior_sticky_key.c | 55 +++++++ app/src/behaviors/behavior_to_layer.c | 3 + app/src/behaviors/behavior_toggle_layer.c | 3 + 36 files changed, 592 insertions(+), 7 deletions(-) create mode 100644 app/dts/bindings/behaviors/behavior-metadata.yaml diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors index c9754bf7d83..92b8ebaa9c7 100644 --- a/app/Kconfig.behaviors +++ b/app/Kconfig.behaviors @@ -1,6 +1,10 @@ # Copyright (c) 2023 The ZMK Contributors # SPDX-License-Identifier: MIT +config ZMK_BEHAVIOR_METADATA + bool "Metadata" + default y + config ZMK_BEHAVIOR_KEY_TOGGLE bool default y diff --git a/app/dts/behaviors/backlight.dtsi b/app/dts/behaviors/backlight.dtsi index 54c83ff44c1..ef7d5a45931 100644 --- a/app/dts/behaviors/backlight.dtsi +++ b/app/dts/behaviors/backlight.dtsi @@ -10,6 +10,7 @@ /omit-if-no-ref/ bl: bcklight { compatible = "zmk,behavior-backlight"; #binding-cells = <2>; + friendly-name = "Backlight"; }; }; }; diff --git a/app/dts/behaviors/bluetooth.dtsi b/app/dts/behaviors/bluetooth.dtsi index 40557b7a28c..c2b1b0a4355 100644 --- a/app/dts/behaviors/bluetooth.dtsi +++ b/app/dts/behaviors/bluetooth.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ bt: bluetooth { compatible = "zmk,behavior-bluetooth"; #binding-cells = <2>; + friendly-name = "Bluetooth"; }; }; }; diff --git a/app/dts/behaviors/caps_word.dtsi b/app/dts/behaviors/caps_word.dtsi index 795fbc08439..5d119016b6f 100644 --- a/app/dts/behaviors/caps_word.dtsi +++ b/app/dts/behaviors/caps_word.dtsi @@ -12,6 +12,7 @@ compatible = "zmk,behavior-caps-word"; #binding-cells = <0>; continue-list = ; + friendly-name = "Caps Word"; }; }; }; diff --git a/app/dts/behaviors/ext_power.dtsi b/app/dts/behaviors/ext_power.dtsi index 2ae1daf84a8..ce345bfec10 100644 --- a/app/dts/behaviors/ext_power.dtsi +++ b/app/dts/behaviors/ext_power.dtsi @@ -10,6 +10,7 @@ ext_power: extpower { compatible = "zmk,behavior-ext-power"; #binding-cells = <1>; + friendly-name = "External Power"; }; }; }; diff --git a/app/dts/behaviors/gresc.dtsi b/app/dts/behaviors/gresc.dtsi index 59a7329178f..43698a23a4f 100644 --- a/app/dts/behaviors/gresc.dtsi +++ b/app/dts/behaviors/gresc.dtsi @@ -13,6 +13,7 @@ #binding-cells = <0>; bindings = <&kp ESC>, <&kp GRAVE>; mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; + friendly-name = "Grave/Escape"; }; }; }; diff --git a/app/dts/behaviors/key_press.dtsi b/app/dts/behaviors/key_press.dtsi index ddaf7eed374..2ea8fc0ca5b 100644 --- a/app/dts/behaviors/key_press.dtsi +++ b/app/dts/behaviors/key_press.dtsi @@ -10,6 +10,7 @@ /omit-if-no-ref/ cp: kp: key_press { compatible = "zmk,behavior-key-press"; #binding-cells = <1>; + friendly-name = "Key Press"; }; }; }; diff --git a/app/dts/behaviors/key_repeat.dtsi b/app/dts/behaviors/key_repeat.dtsi index 88910f6271c..6fb74c89cbb 100644 --- a/app/dts/behaviors/key_repeat.dtsi +++ b/app/dts/behaviors/key_repeat.dtsi @@ -12,6 +12,7 @@ compatible = "zmk,behavior-key-repeat"; #binding-cells = <0>; usage-pages = ; + friendly-name = "Key Repeat"; }; }; }; diff --git a/app/dts/behaviors/key_toggle.dtsi b/app/dts/behaviors/key_toggle.dtsi index a3e3f36f270..4a05bcc1f35 100644 --- a/app/dts/behaviors/key_toggle.dtsi +++ b/app/dts/behaviors/key_toggle.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ kt: key_toggle { compatible = "zmk,behavior-key-toggle"; #binding-cells = <1>; + friendly-name = "Key Toggle"; }; }; }; diff --git a/app/dts/behaviors/layer_tap.dtsi b/app/dts/behaviors/layer_tap.dtsi index dc953e9358b..df77a7ba04a 100644 --- a/app/dts/behaviors/layer_tap.dtsi +++ b/app/dts/behaviors/layer_tap.dtsi @@ -12,6 +12,7 @@ flavor = "tap-preferred"; tapping-term-ms = <200>; bindings = <&mo>, <&kp>; + friendly-name = "Layer-Tap"; }; }; }; diff --git a/app/dts/behaviors/mod_tap.dtsi b/app/dts/behaviors/mod_tap.dtsi index 38bb34fe5c4..7cac5ae3532 100644 --- a/app/dts/behaviors/mod_tap.dtsi +++ b/app/dts/behaviors/mod_tap.dtsi @@ -12,6 +12,7 @@ flavor = "hold-preferred"; tapping-term-ms = <200>; bindings = <&kp>, <&kp>; + friendly-name = "Mod-Tap"; }; }; }; diff --git a/app/dts/behaviors/momentary_layer.dtsi b/app/dts/behaviors/momentary_layer.dtsi index 6d85165dbb3..15c1dea84f6 100644 --- a/app/dts/behaviors/momentary_layer.dtsi +++ b/app/dts/behaviors/momentary_layer.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ mo: momentary_layer { compatible = "zmk,behavior-momentary-layer"; #binding-cells = <1>; + friendly-name = "Momentary Layer"; }; }; }; diff --git a/app/dts/behaviors/none.dtsi b/app/dts/behaviors/none.dtsi index 13d056f0cf2..d7ffbb472fe 100644 --- a/app/dts/behaviors/none.dtsi +++ b/app/dts/behaviors/none.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ none: none { compatible = "zmk,behavior-none"; #binding-cells = <0>; + friendly-name = "None"; }; }; }; diff --git a/app/dts/behaviors/outputs.dtsi b/app/dts/behaviors/outputs.dtsi index f7737196719..8405072738a 100644 --- a/app/dts/behaviors/outputs.dtsi +++ b/app/dts/behaviors/outputs.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ out: outputs { compatible = "zmk,behavior-outputs"; #binding-cells = <1>; + friendly-name = "Output Selection"; }; }; }; diff --git a/app/dts/behaviors/reset.dtsi b/app/dts/behaviors/reset.dtsi index e407b107b90..c90d4b2c9ab 100644 --- a/app/dts/behaviors/reset.dtsi +++ b/app/dts/behaviors/reset.dtsi @@ -12,6 +12,7 @@ sys_reset: sysreset { compatible = "zmk,behavior-reset"; #binding-cells = <0>; + friendly-name = "Reset"; }; // Behavior can be invoked on peripherals, so name must be <= 8 characters. @@ -19,6 +20,7 @@ compatible = "zmk,behavior-reset"; type = ; #binding-cells = <0>; + friendly-name = "Bootloader"; }; }; }; diff --git a/app/dts/behaviors/rgb_underglow.dtsi b/app/dts/behaviors/rgb_underglow.dtsi index 969518a6ff3..979d0ff2088 100644 --- a/app/dts/behaviors/rgb_underglow.dtsi +++ b/app/dts/behaviors/rgb_underglow.dtsi @@ -10,6 +10,7 @@ rgb_ug: rgb_ug { compatible = "zmk,behavior-rgb-underglow"; #binding-cells = <2>; + friendly-name = "Underglow"; }; }; }; diff --git a/app/dts/behaviors/sticky_key.dtsi b/app/dts/behaviors/sticky_key.dtsi index c8973d4df2c..ad1269e064b 100644 --- a/app/dts/behaviors/sticky_key.dtsi +++ b/app/dts/behaviors/sticky_key.dtsi @@ -12,6 +12,7 @@ release-after-ms = <1000>; bindings = <&kp>; ignore-modifiers; + friendly-name = "Sticky Key"; }; /omit-if-no-ref/ sl: sticky_layer { compatible = "zmk,behavior-sticky-key"; @@ -19,6 +20,7 @@ release-after-ms = <1000>; bindings = <&mo>; quick-release; + friendly-name = "Sticky Layer"; }; }; diff --git a/app/dts/behaviors/to_layer.dtsi b/app/dts/behaviors/to_layer.dtsi index 904f023da53..fb9e05f3b1a 100644 --- a/app/dts/behaviors/to_layer.dtsi +++ b/app/dts/behaviors/to_layer.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ to: to_layer { compatible = "zmk,behavior-to-layer"; #binding-cells = <1>; + friendly-name = "To Layer"; }; }; }; diff --git a/app/dts/behaviors/toggle_layer.dtsi b/app/dts/behaviors/toggle_layer.dtsi index 05f2988e08c..5942b8e7b92 100644 --- a/app/dts/behaviors/toggle_layer.dtsi +++ b/app/dts/behaviors/toggle_layer.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ tog: toggle_layer { compatible = "zmk,behavior-toggle-layer"; #binding-cells = <1>; + friendly-name = "Toggle Layer"; }; }; }; diff --git a/app/dts/behaviors/transparent.dtsi b/app/dts/behaviors/transparent.dtsi index 3586f02afab..bd92c2b6787 100644 --- a/app/dts/behaviors/transparent.dtsi +++ b/app/dts/behaviors/transparent.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ trans: transparent { compatible = "zmk,behavior-transparent"; #binding-cells = <0>; + friendly-name = "Transparent"; }; }; }; diff --git a/app/dts/bindings/behaviors/behavior-metadata.yaml b/app/dts/bindings/behaviors/behavior-metadata.yaml new file mode 100644 index 00000000000..746f09b820e --- /dev/null +++ b/app/dts/bindings/behaviors/behavior-metadata.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +properties: + friendly-name: + type: string diff --git a/app/dts/bindings/behaviors/one_param.yaml b/app/dts/bindings/behaviors/one_param.yaml index 9a503e8a815..fa4c2dc0b44 100644 --- a/app/dts/bindings/behaviors/one_param.yaml +++ b/app/dts/bindings/behaviors/one_param.yaml @@ -1,6 +1,8 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +include: behavior-metadata.yaml + properties: label: type: string diff --git a/app/dts/bindings/behaviors/two_param.yaml b/app/dts/bindings/behaviors/two_param.yaml index 4f342301bb3..af9618e1624 100644 --- a/app/dts/bindings/behaviors/two_param.yaml +++ b/app/dts/bindings/behaviors/two_param.yaml @@ -1,6 +1,8 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +include: behavior-metadata.yaml + properties: label: type: string diff --git a/app/dts/bindings/behaviors/zero_param.yaml b/app/dts/bindings/behaviors/zero_param.yaml index 79d0dcaed3f..deed5a1218b 100644 --- a/app/dts/bindings/behaviors/zero_param.yaml +++ b/app/dts/bindings/behaviors/zero_param.yaml @@ -1,6 +1,8 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +include: behavior-metadata.yaml + properties: label: type: string diff --git a/app/include/drivers/behavior.h b/app/include/drivers/behavior.h index 3936da5e4be..a96cb823360 100644 --- a/app/include/drivers/behavior.h +++ b/app/include/drivers/behavior.h @@ -23,6 +23,64 @@ * (Internal use only.) */ +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +enum behavior_parameter_standard_domain { + BEHAVIOR_PARAMETER_STANDARD_DOMAIN_NULL = 0, + BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HID_USAGE = 1, + BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX = 2, + BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV = 3, +}; + +struct behavior_parameter_value_metadata { + char *friendly_name; + uint8_t position; + + union { + uint32_t value; + struct { + uint16_t min; + uint16_t max; + } range; + + enum behavior_parameter_standard_domain standard; + }; + + enum { + BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE = 0, + BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_RANGE = 1, + BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_STANDARD = 2, + } type; +}; + +struct behavior_parameter_metadata_custom_set { + size_t values_len; + const struct behavior_parameter_value_metadata *values; +}; + +struct behavior_parameter_metadata_custom { + size_t sets_len; + struct behavior_parameter_metadata_custom_set sets[]; +}; + +struct behavior_parameter_metadata { + union { + struct { + enum behavior_parameter_standard_domain param1; + enum behavior_parameter_standard_domain param2; + } standard; + + const struct behavior_parameter_metadata_custom *custom; + }; + + enum { + BEHAVIOR_PARAMETER_METADATA_STANDARD = 0, + BEHAVIOR_PARAMETER_METADATA_CUSTOM = 1, + } type; +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + enum behavior_sensor_binding_process_mode { BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_TRIGGER, BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_DISCARD, @@ -37,6 +95,10 @@ typedef int (*behavior_sensor_keymap_binding_accept_data_callback_t)( struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, const struct zmk_sensor_config *sensor_config, size_t channel_data_size, const struct zmk_sensor_channel_data channel_data[channel_data_size]); +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +typedef int (*behavior_get_parameter_domains_t)(const struct device *behavior, + struct behavior_parameter_metadata *param_metadata); +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) enum behavior_locality { BEHAVIOR_LOCALITY_CENTRAL, @@ -51,23 +113,56 @@ __subsystem struct behavior_driver_api { behavior_keymap_binding_callback_t binding_released; behavior_sensor_keymap_binding_accept_data_callback_t sensor_binding_accept_data; behavior_sensor_keymap_binding_process_callback_t sensor_binding_process; +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + behavior_get_parameter_domains_t get_parameter_domains; + const struct behavior_parameter_metadata_custom *custom_parameters; + const enum behavior_parameter_standard_domain param1_standard_domain; + const enum behavior_parameter_standard_domain param2_standard_domain; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; /** * @endcond */ +struct zmk_behavior_metadata { +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + const char *friendly_name; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; + struct zmk_behavior_ref { const struct device *device; + const struct zmk_behavior_metadata metadata; }; +#define ZMK_BEHAVIOR_REF_DT_NAME(node_id) _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id)) + +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +#define ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id) \ + { .friendly_name = DT_PROP_OR(node_id, friendly_name, DEVICE_DT_NAME(node_id)), } + +#else + +#define ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id) \ + {} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +#define ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev) \ + { .device = _dev, .metadata = ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id), } + +#define ZMK_BEHAVIOR_REF_DEFINE(name, node_id, _dev) \ + static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref, name) = \ + ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev) + +#define ZMK_BEHAVIOR_REF_DT_DEFINE(node_id) \ + ZMK_BEHAVIOR_REF_DEFINE(ZMK_BEHAVIOR_REF_DT_NAME(node_id), node_id, DEVICE_DT_GET(node_id)) + /** * Registers @p node_id as a behavior. */ -#define BEHAVIOR_DEFINE(node_id) \ - static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref, \ - _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id))) = { \ - .device = DEVICE_DT_GET(node_id), \ - } +#define BEHAVIOR_DEFINE(node_id) ZMK_BEHAVIOR_REF_DT_DEFINE(node_id) /** * @brief Like DEVICE_DT_DEFINE(), but also registers the device as a behavior. @@ -120,6 +215,43 @@ static inline int z_impl_behavior_keymap_binding_convert_central_state_dependent return api->binding_convert_central_state_dependent_params(binding, event); } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +/** + * @brief Determine where the behavior should be run + * @param behavior Pointer to the device structure for the driver instance. + * + * @retval Zero if successful. + * @retval Negative errno code if failure. + */ +__syscall int behavior_get_parameter_domains(const struct device *behavior, + struct behavior_parameter_metadata *param_metadata); + +static inline int +z_impl_behavior_get_parameter_domains(const struct device *behavior, + struct behavior_parameter_metadata *param_metadata) { + if (behavior == NULL || param_metadata == NULL) { + return -EINVAL; + } + + const struct behavior_driver_api *api = (const struct behavior_driver_api *)behavior->api; + + if (api->get_parameter_domains) { + return api->get_parameter_domains(behavior, param_metadata); + } else if (api->custom_parameters) { + param_metadata->type = BEHAVIOR_PARAMETER_METADATA_CUSTOM; + param_metadata->custom = api->custom_parameters; + } else { + param_metadata->type = BEHAVIOR_PARAMETER_METADATA_STANDARD; + param_metadata->standard.param1 = api->param1_standard_domain; + param_metadata->standard.param2 = api->param2_standard_domain; + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + /** * @brief Determine where the behavior should be run * @param behavior Pointer to the device structure for the driver instance. diff --git a/app/src/behaviors/behavior_bt.c b/app/src/behaviors/behavior_bt.c index 03bb7d8c898..6166156f685 100644 --- a/app/src/behaviors/behavior_bt.c +++ b/app/src/behaviors/behavior_bt.c @@ -20,6 +20,73 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata no_arg_values[] = { + { + .position = 0, + .friendly_name = "Next Profile", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = BT_NXT_CMD, + }, + { + .position = 0, + .friendly_name = "Previous Profile", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = BT_PRV_CMD, + }, + { + .position = 0, + .friendly_name = "Clear All Profiles", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = BT_CLR_ALL_CMD, + }, + { + .position = 0, + .friendly_name = "Clear Selected Profile", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = BT_CLR_CMD, + }, +}; + +static const struct behavior_parameter_metadata_custom_set no_args_set = { + .values = no_arg_values, + .values_len = ARRAY_SIZE(no_arg_values), +}; + +static const struct behavior_parameter_value_metadata prof_index_values[] = { + { + .position = 0, + .friendly_name = "Select Profile", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = BT_SEL_CMD, + }, + { + .position = 0, + .friendly_name = "Disconnect Profile", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = BT_DISC_CMD, + }, + { + .position = 1, + .friendly_name = "Profile", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_RANGE, + .range = {.min = 0, .max = ZMK_BLE_PROFILE_COUNT}, + }, +}; + +static const struct behavior_parameter_metadata_custom_set profile_index_metadata_set = { + .values = prof_index_values, + .values_len = ARRAY_SIZE(prof_index_values), +}; + +static const struct behavior_parameter_metadata_custom metadata = { + .sets_len = 2, + .sets = {no_args_set, profile_index_metadata_set}, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { switch (binding->param1) { @@ -54,6 +121,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_bt_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .custom_parameters = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_bt_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c index 204e50f478b..147ab580be0 100644 --- a/app/src/behaviors/behavior_hold_tap.c +++ b/app/src/behaviors/behavior_hold_tap.c @@ -652,9 +652,58 @@ static int on_hold_tap_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +static int hold_tap_parameter_domains(const struct device *hold_tap, + struct behavior_parameter_metadata *param_metadata) { + const struct behavior_hold_tap_config *cfg = hold_tap->config; + int err; + struct behavior_parameter_metadata child_meta; + + err = behavior_get_parameter_domains(zmk_behavior_get_binding(cfg->hold_behavior_dev), + &child_meta); + if (err < 0) { + LOG_WRN("Failed to get the hold behavior parameter: %d", err); + return err; + } + + switch (child_meta.type) { + case BEHAVIOR_PARAMETER_METADATA_STANDARD: + param_metadata->type = BEHAVIOR_PARAMETER_METADATA_STANDARD; + param_metadata->standard.param1 = child_meta.standard.param1; + break; + default: + LOG_WRN("No handling for hold-taps with custom behaviors (yet)"); + return -ENOTSUP; + } + + err = behavior_get_parameter_domains(zmk_behavior_get_binding(cfg->tap_behavior_dev), + &child_meta); + if (err < 0) { + LOG_WRN("Failed to get the tap behavior parameter: %d", err); + return err; + } + + switch (child_meta.type) { + case BEHAVIOR_PARAMETER_METADATA_STANDARD: + param_metadata->type = BEHAVIOR_PARAMETER_METADATA_STANDARD; + param_metadata->standard.param2 = child_meta.standard.param1; + break; + default: + LOG_WRN("No handling for hold-taps with custom behaviors (yet)"); + return -ENOTSUP; + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static const struct behavior_driver_api behavior_hold_tap_driver_api = { .binding_pressed = on_hold_tap_binding_pressed, .binding_released = on_hold_tap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_domains = hold_tap_parameter_domains, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int position_state_changed_listener(const zmk_event_t *eh) { diff --git a/app/src/behaviors/behavior_key_press.c b/app/src/behaviors/behavior_key_press.c index 566cfcfba77..8db331d06fb 100644 --- a/app/src/behaviors/behavior_key_press.c +++ b/app/src/behaviors/behavior_key_press.c @@ -31,7 +31,12 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, } 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, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .param1_standard_domain = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HID_USAGE, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; #define KP_INST(n) \ BEHAVIOR_DT_INST_DEFINE(n, behavior_key_press_init, NULL, NULL, NULL, POST_KERNEL, \ diff --git a/app/src/behaviors/behavior_key_toggle.c b/app/src/behaviors/behavior_key_toggle.c index 0dc0f5abfdc..1faa5d89372 100644 --- a/app/src/behaviors/behavior_key_toggle.c +++ b/app/src/behaviors/behavior_key_toggle.c @@ -34,6 +34,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_key_toggle_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .param1_standard_domain = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HID_USAGE, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; #define KT_INST(n) \ diff --git a/app/src/behaviors/behavior_macro.c b/app/src/behaviors/behavior_macro.c index acffe3d8857..971bc88fdcd 100644 --- a/app/src/behaviors/behavior_macro.c +++ b/app/src/behaviors/behavior_macro.c @@ -209,9 +209,72 @@ static int on_macro_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static int get_macro_parameter_domains(const struct device *macro, + struct behavior_parameter_metadata *param_metadata) { + const struct behavior_macro_config *cfg = macro->config; + struct behavior_macro_trigger_state state = {0}; + + bool param1_found = false, param2_found = false; + + for (int i = 0; (i < cfg->count) && (!param1_found || !param2_found); i++) { + LOG_DBG("i %d", i); + if (!handle_control_binding(&state, &cfg->bindings[i])) { + LOG_DBG("checking %d for the given state", i); + const struct zmk_behavior_binding *binding = &cfg->bindings[i]; + + if (state.param1_source != PARAM_SOURCE_BINDING || + state.param2_source != PARAM_SOURCE_BINDING) { + LOG_DBG("Found a parameter that's from somewhere else!"); + struct behavior_parameter_metadata binding_meta; + int err = behavior_get_parameter_domains( + zmk_behavior_get_binding(binding->behavior_dev), &binding_meta); + if (err < 0) { + LOG_WRN("Failed to fetch macro binding parameter details %d", err); + continue; + } + + if (binding_meta.type != BEHAVIOR_PARAMETER_METADATA_STANDARD) { + LOG_WRN("Macro metadata for custom child behavior not yet supported"); + return -ENOTSUP; + } + + param_metadata->type = BEHAVIOR_PARAMETER_METADATA_STANDARD; + + if (state.param1_source == PARAM_SOURCE_MACRO_1ST) { + param_metadata->standard.param1 = binding_meta.standard.param1; + param1_found = true; + } else if (state.param1_source == PARAM_SOURCE_MACRO_2ND) { + param_metadata->standard.param2 = binding_meta.standard.param1; + param2_found = true; + } + + if (state.param2_source == PARAM_SOURCE_MACRO_1ST) { + param_metadata->standard.param1 = binding_meta.standard.param2; + param1_found = true; + } else if (state.param2_source == PARAM_SOURCE_MACRO_2ND) { + param_metadata->standard.param2 = binding_meta.standard.param2; + param2_found = true; + } + + state.param1_source = PARAM_SOURCE_BINDING; + state.param2_source = PARAM_SOURCE_BINDING; + } + } + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static const struct behavior_driver_api behavior_macro_driver_api = { .binding_pressed = on_macro_binding_pressed, .binding_released = on_macro_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_domains = get_macro_parameter_domains, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; #define TRANSFORMED_BEHAVIORS(n) \ diff --git a/app/src/behaviors/behavior_momentary_layer.c b/app/src/behaviors/behavior_momentary_layer.c index 0c86e605b55..44f82917797 100644 --- a/app/src/behaviors/behavior_momentary_layer.c +++ b/app/src/behaviors/behavior_momentary_layer.c @@ -33,7 +33,12 @@ static int mo_keymap_binding_released(struct zmk_behavior_binding *binding, } static const struct behavior_driver_api behavior_mo_driver_api = { - .binding_pressed = mo_keymap_binding_pressed, .binding_released = mo_keymap_binding_released}; + .binding_pressed = mo_keymap_binding_pressed, + .binding_released = mo_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .param1_standard_domain = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; static const struct behavior_mo_config behavior_mo_config = {}; diff --git a/app/src/behaviors/behavior_outputs.c b/app/src/behaviors/behavior_outputs.c index d172c3a11b8..cb6e8fa1284 100644 --- a/app/src/behaviors/behavior_outputs.c +++ b/app/src/behaviors/behavior_outputs.c @@ -20,6 +20,45 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata std_values[] = { + { + .position = 0, + .value = OUT_TOG, + .friendly_name = "Toggle Outputs", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + }, +#if IS_ENABLED(CONFIG_ZMK_USB) + { + .position = 0, + .value = OUT_USB, + .friendly_name = "USB Output", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + }, +#endif // IS_ENABLED(CONFIG_ZMK_USB) +#if IS_ENABLED(CONFIG_ZMK_BLE) + { + .position = 0, + .value = OUT_BLE, + .friendly_name = "BLE Output", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + }, +#endif // IS_ENABLED(CONFIG_ZMK_BLE) +}; + +static const struct behavior_parameter_metadata_custom_set std_set = { + .values = std_values, + .values_len = ARRAY_SIZE(std_values), +}; + +static const struct behavior_parameter_metadata_custom metadata = { + .sets_len = 1, + .sets = {std_set}, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { switch (binding->param1) { @@ -40,6 +79,9 @@ static int behavior_out_init(const struct device *dev) { return 0; } static const struct behavior_driver_api behavior_outputs_driver_api = { .binding_pressed = on_keymap_binding_pressed, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .custom_parameters = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_out_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index a16ee591ed4..a894690276b 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -18,6 +18,121 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata no_arg_values[] = { + { + .position = 0, + .friendly_name = "Toggle On/Off", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_TOG_CMD, + }, + { + .position = 0, + .friendly_name = "Turn On", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_ON_CMD, + }, + { + .position = 0, + .friendly_name = "Turn OFF", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_OFF_CMD, + }, + { + .position = 0, + .friendly_name = "Hue Up", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_HUI_CMD, + }, + { + .position = 0, + .friendly_name = "Hue Down", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_HUD_CMD, + }, + { + .position = 0, + .friendly_name = "Saturation Up", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_SAI_CMD, + }, + { + .position = 0, + .friendly_name = "Saturation Down", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_SAD_CMD, + }, + { + .position = 0, + .friendly_name = "Brightness Up", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_BRI_CMD, + }, + { + .position = 0, + .friendly_name = "Brightness Down", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_BRD_CMD, + }, + { + .position = 0, + .friendly_name = "Speed Up", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_SPI_CMD, + }, + { + .position = 0, + .friendly_name = "Speed Down", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_SPD_CMD, + }, + { + .position = 0, + .friendly_name = "Next Effect", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_EFF_CMD, + }, + { + .position = 0, + .friendly_name = "Previous Effect", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_EFR_CMD, + }, +}; + +static const struct behavior_parameter_metadata_custom_set no_args_set = { + .values = no_arg_values, + .values_len = ARRAY_SIZE(no_arg_values), +}; + +static const struct behavior_parameter_value_metadata hsv_value_metadata_values[] = { + { + .position = 0, + .friendly_name = "Set Color", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE, + .value = RGB_COLOR_HSB_CMD, + }, + { + .position = 1, + .friendly_name = "Profile", + .type = BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_STANDARD, + .standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV, + }, +}; + +static const struct behavior_parameter_metadata_custom_set hsv_value_metadata_set = { + .values = hsv_value_metadata_values, + .values_len = ARRAY_SIZE(hsv_value_metadata_values), +}; + +static const struct behavior_parameter_metadata_custom metadata = { + .sets_len = 2, + .sets = {no_args_set, hsv_value_metadata_set}, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static int behavior_rgb_underglow_init(const struct device *dev) { return 0; } static int @@ -147,6 +262,9 @@ static const struct behavior_driver_api behavior_rgb_underglow_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .custom_parameters = &metadata, +#endif }; BEHAVIOR_DT_INST_DEFINE(0, behavior_rgb_underglow_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_sticky_key.c b/app/src/behaviors/behavior_sticky_key.c index b0e9f3ed0c0..7a6238ac529 100644 --- a/app/src/behaviors/behavior_sticky_key.c +++ b/app/src/behaviors/behavior_sticky_key.c @@ -188,9 +188,64 @@ static int on_sticky_key_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static int sticky_key_parameter_domains(const struct device *sk, + struct behavior_parameter_metadata *param_metadata) { + const struct behavior_sticky_key_config *cfg = sk->config; + + struct behavior_parameter_metadata child_metadata; + + int err = behavior_get_parameter_domains(zmk_behavior_get_binding(cfg->behavior.behavior_dev), + &child_metadata); + if (err < 0) { + LOG_WRN("Failed to get the sticky key bound behavior parameter: %d", err); + } + + switch (child_metadata.type) { + case BEHAVIOR_PARAMETER_METADATA_STANDARD: + if (child_metadata.standard.param2 != 0) { + LOG_WRN("Sticky-key bound to behavior that expects 2 params"); + return -ENOTSUP; + } + + break; + case BEHAVIOR_PARAMETER_METADATA_CUSTOM: + if (!child_metadata.custom) { + LOG_WRN("Child reports custom metadata but none provided"); + return -ENODEV; + } + + for (int s = 0; s < child_metadata.custom->sets_len; s++) { + const struct behavior_parameter_metadata_custom_set *set = + &child_metadata.custom->sets[s]; + + for (int v = 0; v < set->values_len; v++) { + if (set->values[v].position != 0) { + LOG_WRN("Sticky-key bound behavior has custom metadata for two parameters"); + return -ENOTSUP; + } + } + } + break; + default: + LOG_ERR("Unexpected parameter metadata type %d", child_metadata.type); + return -ENOTSUP; + } + + *param_metadata = child_metadata; + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static const struct behavior_driver_api behavior_sticky_key_driver_api = { .binding_pressed = on_sticky_key_binding_pressed, .binding_released = on_sticky_key_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_domains = sticky_key_parameter_domains, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh); diff --git a/app/src/behaviors/behavior_to_layer.c b/app/src/behaviors/behavior_to_layer.c index 1c87a925905..501a6934c0c 100644 --- a/app/src/behaviors/behavior_to_layer.c +++ b/app/src/behaviors/behavior_to_layer.c @@ -35,6 +35,9 @@ static int to_keymap_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_to_driver_api = { .binding_pressed = to_keymap_binding_pressed, .binding_released = to_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .param1_standard_domain = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_to_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_toggle_layer.c b/app/src/behaviors/behavior_toggle_layer.c index 817462df5aa..099d6d9e21f 100644 --- a/app/src/behaviors/behavior_toggle_layer.c +++ b/app/src/behaviors/behavior_toggle_layer.c @@ -37,6 +37,9 @@ static int tog_keymap_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_tog_driver_api = { .binding_pressed = tog_keymap_binding_pressed, .binding_released = tog_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .param1_standard_domain = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static const struct behavior_tog_config behavior_tog_config = {};