Skip to content

Commit

Permalink
feat(keymap): Add behavior binding validation, keymap APIs.
Browse files Browse the repository at this point in the history
* Add ability to validate a zmk_behavior_binding against
  the behavior metadata available.
* Add keymap API for getting/setting a bindings
  in keymap layers.
* Add settings storage for keymap layer bindings.
  • Loading branch information
petejohanson committed Apr 18, 2024
1 parent 13a6e9e commit 8aff373
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 8 deletions.
8 changes: 8 additions & 0 deletions app/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ rsource "src/split/Kconfig"
#Basic Keyboard Setup
endmenu

menu "Keymaps"

config ZMK_KEYMAP_SETTINGS_STORAGE
bool "Settings Save/Load"
depends on SETTINGS

endmenu # Keymaps

rsource "src/studio/Kconfig"

menu "Display/LED Options"
Expand Down
15 changes: 14 additions & 1 deletion app/include/zmk/behavior.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#define ZMK_BEHAVIOR_TRANSPARENT 1

struct zmk_behavior_binding {
char *behavior_dev;
const char *behavior_dev;
uint32_t param1;
uint32_t param2;
};
Expand Down Expand Up @@ -58,3 +58,16 @@ zmk_behavior_local_id_t zmk_behavior_get_local_id(const char *name);
* @retval NULL if the behavior is not found or its initialization function failed.
*/
const char *zmk_behavior_find_behavior_name_from_local_id(zmk_behavior_local_id_t local_id);


/**
* @brief Validate a given behavior binding matches the behavior metadata describing valid parameters.
*
* @param binding The behavior binding to validate
*
* @retval 0 if the passed in binding is valid.
* @retval -ENODEV if binding is NULL.
* @retval -EINVAL if binding parameters are not valid for the behavior metadata.
*/
int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding);

8 changes: 8 additions & 0 deletions app/include/zmk/keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ int zmk_keymap_layer_toggle(uint8_t layer);
int zmk_keymap_layer_to(uint8_t layer);
const char *zmk_keymap_layer_name(uint8_t layer);

const struct zmk_behavior_binding *zmk_keymap_get_layer_binding_at_idx(uint8_t layer,
uint8_t binding_idx);
int zmk_keymap_set_layer_binding_at_idx(uint8_t layer, uint8_t binding_idx,
const struct zmk_behavior_binding binding);

int zmk_keymap_save_changes(void);
int zmk_keymap_discard_changes(void);

int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pressed,
int64_t timestamp);

Expand Down
139 changes: 132 additions & 7 deletions app/src/behavior.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#include <drivers/behavior.h>
#include <zmk/behavior.h>
#include <zmk/hid.h>
#include <zmk/matrix.h>

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
Expand Down Expand Up @@ -118,12 +120,11 @@ static int behavior_handle_set(const char *name, size_t len, settings_read_cb re
return 0;
}
}

return -EINVAL;
}

return 0;
};
}

static int behavior_handle_commit(void) {
LOG_DBG("COMMIT!");
Expand All @@ -139,18 +140,13 @@ static int behavior_handle_commit(void) {
char setting_name[32];
sprintf(setting_name, "behavior/local_id/%d", item->local_id);
settings_save_one(setting_name, item->device->name, strlen(item->device->name));
}

return 0;
}

SETTINGS_STATIC_HANDLER_DEFINE(behavior, "behavior", NULL, behavior_handle_set,
behavior_handle_commit, NULL);

static int behavior_local_id_init(void) {
// LOAD SETTINGS!
settings_load_subtree("behavior");
// TODO: Populate the local IDs from the settings table

return 0;
}
Expand All @@ -165,6 +161,135 @@ SYS_INIT(behavior_local_id_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

#endif

#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
static int validate_hid_usage(uint16_t usage_page, uint16_t usage_id) {
LOG_DBG("Validate usage %d in page %d", usage_id, usage_page);
switch (usage_page) {
case HID_USAGE_KEY:
if (usage_id == 0 || usage_id > ZMK_HID_KEYBOARD_NKRO_MAX_USAGE) {
return -EINVAL;
}
break;
case HID_USAGE_CONSUMER:
if (usage_id >
COND_CODE_1(IS_ENABLED(CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC), (0xFF), (0xFFF))) {
return -EINVAL;
}
break;
default:
LOG_WRN("Unsupported HID usage page %d", usage_page);
return -EINVAL;
}

return 0;
}

static int validate_standard_param(enum behavior_parameter_standard_domain standard_domain,
uint32_t val) {
switch (standard_domain) {
case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_NULL:
if (val != 0) {
return -EINVAL;
}
break;
case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HID_USAGE:
return validate_hid_usage(ZMK_HID_USAGE_PAGE(val), ZMK_HID_USAGE_ID(val));
case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX:
if (val >= ZMK_KEYMAP_LEN) {
return -EINVAL;
}
break;
case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV:
// TODO: No real way to validate? Maybe max brightness?
break;
}

return 0;
}

static int validate_custom_params(const struct behavior_parameter_metadata_custom *custom,
uint32_t param1, uint32_t param2) {
if (!custom) {
return -ENODEV;
}

for (int s = 0; s < custom->sets_len; s++) {
const struct behavior_parameter_metadata_custom_set *set = &custom->sets[s];

bool had_param1_metadata = false, had_param2_metadata = false;
bool param1_matched = false, param2_matched = false;

for (int v = 0; v < set->values_len && (!param1_matched || !param2_matched); v++) {
const struct behavior_parameter_value_metadata *value_meta = &set->values[v];
uint32_t param = value_meta->position == 0 ? param1 : param2;
bool *matched = value_meta->position == 0 ? &param1_matched : &param2_matched;

*(value_meta->position == 0 ? &had_param1_metadata : &had_param2_metadata) = true;

switch (value_meta->type) {
case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_STANDARD:
if (validate_standard_param(value_meta->standard, param) == 0) {
*matched = true;
}
break;
case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE:
if (param == value_meta->value) {
*matched = true;
}
break;
case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_RANGE:
if (param >= value_meta->range.min && param <= value_meta->range.max) {
*matched = true;
}
break;
}
}

if ((param1_matched || (!had_param1_metadata && param1 == 0)) &&
(param2_matched || (!had_param2_metadata && param2 == 0))) {
return 0;
}
}

return -EINVAL;
}

#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)

int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding) {
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
const struct device *behavior = zmk_behavior_get_binding(binding->behavior_dev);

if (!behavior) {
return -ENODEV;
}

struct behavior_parameter_metadata metadata;
int ret = behavior_get_parameter_domains(behavior, &metadata);

if (ret < 0) {
return ret;
}

switch (metadata.type) {
case BEHAVIOR_PARAMETER_METADATA_STANDARD:
int ret = validate_standard_param(metadata.standard.param1, binding->param1);

if (ret < 0) {
return ret;
}

return validate_standard_param(metadata.standard.param2, binding->param2);
case BEHAVIOR_PARAMETER_METADATA_CUSTOM:
return validate_custom_params(metadata.custom, binding->param1, binding->param2);
default:
return -ENOTSUP;
}
#else
return 0;
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
}

#if IS_ENABLED(CONFIG_LOG)
static int check_behavior_names(void) {
// Behavior names must be unique, but we don't have a good way to enforce this
Expand Down
Loading

0 comments on commit 8aff373

Please sign in to comment.