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.
  • Loading branch information
petejohanson committed Apr 10, 2024
1 parent d79d916 commit 99fd090
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/include/zmk/behavior.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ struct zmk_behavior_binding_event {
* unrelated node which shares the same name as a behavior.
*/
const struct device *zmk_behavior_get_binding(const char *name);

int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding);
5 changes: 5 additions & 0 deletions app/include/zmk/keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ 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_position_state_changed(uint8_t source, uint32_t position, bool pressed,
int64_t timestamp);

Expand Down
131 changes: 131 additions & 0 deletions app/src/behavior.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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 @@ -39,6 +41,135 @@ const struct device *z_impl_behavior_get_binding(const char *name) {
return NULL;
}

#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
25 changes: 25 additions & 0 deletions app/src/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,31 @@ const char *zmk_keymap_layer_name(uint8_t layer) {
return zmk_keymap_layer_names[layer];
}

const struct zmk_behavior_binding *zmk_keymap_get_layer_binding_at_idx(uint8_t layer,
uint8_t binding_idx) {
if (layer >= ZMK_KEYMAP_LAYERS_LEN) {
return NULL;
}

if (binding_idx >= ZMK_KEYMAP_LEN) {
return NULL;
}

return &zmk_keymap[layer][binding_idx];
}

int zmk_keymap_set_layer_binding_at_idx(uint8_t layer, uint8_t binding_idx,
struct zmk_behavior_binding binding) {
if (layer >= ZMK_KEYMAP_LAYERS_LEN || binding_idx >= ZMK_KEYMAP_LEN) {
return -EINVAL;
}

// TODO: Need a mutex to protect access to the keymap data?
memcpy(&zmk_keymap[layer][binding_idx], &binding, sizeof(binding));

return 0;
}

int invoke_locally(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event,
bool pressed) {
if (pressed) {
Expand Down

0 comments on commit 99fd090

Please sign in to comment.