Merge 2bc4f23e89
into 0f972f1cc3
This commit is contained in:
commit
eca9c7f9a7
15 changed files with 208 additions and 112 deletions
|
@ -26,6 +26,9 @@ struct zmk_behavior_binding_event {
|
||||||
int layer;
|
int layer;
|
||||||
uint32_t position;
|
uint32_t position;
|
||||||
int64_t timestamp;
|
int64_t timestamp;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
uint8_t source;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,6 +45,19 @@ struct zmk_behavior_binding_event {
|
||||||
*/
|
*/
|
||||||
const struct device *zmk_behavior_get_binding(const char *name);
|
const struct device *zmk_behavior_get_binding(const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Invoke a behavior given its binding and invoking event details.
|
||||||
|
*
|
||||||
|
* @param src_binding Behavior binding to invoke.
|
||||||
|
* @param event The binding event struct containing details of the event that invoked it.
|
||||||
|
* @param pressed Whether the binding is pressed or released.
|
||||||
|
*
|
||||||
|
* @retval 0 If successful.
|
||||||
|
* @retval Negative errno code if failure.
|
||||||
|
*/
|
||||||
|
int zmk_invoke_behavior_binding(const struct zmk_behavior_binding *src_binding,
|
||||||
|
struct zmk_behavior_binding_event event, bool pressed);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a local ID for a behavior from its @p name field.
|
* @brief Get a local ID for a behavior from its @p name field.
|
||||||
*
|
*
|
||||||
|
|
|
@ -10,5 +10,5 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <zmk/behavior.h>
|
#include <zmk/behavior.h>
|
||||||
|
|
||||||
int zmk_behavior_queue_add(uint32_t position, const struct zmk_behavior_binding behavior,
|
int zmk_behavior_queue_add(struct zmk_behavior_binding_event *event,
|
||||||
bool press, uint32_t wait);
|
const struct zmk_behavior_binding behavior, bool press, uint32_t wait);
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct sensor_event {
|
||||||
|
|
||||||
struct zmk_split_run_behavior_data {
|
struct zmk_split_run_behavior_data {
|
||||||
uint8_t position;
|
uint8_t position;
|
||||||
|
uint8_t source;
|
||||||
uint8_t state;
|
uint8_t state;
|
||||||
uint32_t param1;
|
uint32_t param1;
|
||||||
uint32_t param2;
|
uint32_t param2;
|
||||||
|
|
|
@ -17,11 +17,18 @@
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <zmk/ble.h>
|
||||||
|
#if ZMK_BLE_IS_CENTRAL
|
||||||
|
#include <zmk/split/bluetooth/central.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <drivers/behavior.h>
|
#include <drivers/behavior.h>
|
||||||
#include <zmk/behavior.h>
|
#include <zmk/behavior.h>
|
||||||
#include <zmk/hid.h>
|
#include <zmk/hid.h>
|
||||||
#include <zmk/matrix.h>
|
#include <zmk/matrix.h>
|
||||||
|
|
||||||
|
#include <zmk/events/position_state_changed.h>
|
||||||
|
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
@ -49,6 +56,66 @@ const struct device *z_impl_behavior_get_binding(const char *name) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int invoke_locally(struct zmk_behavior_binding *binding,
|
||||||
|
struct zmk_behavior_binding_event event, bool pressed) {
|
||||||
|
if (pressed) {
|
||||||
|
return behavior_keymap_binding_pressed(binding, event);
|
||||||
|
} else {
|
||||||
|
return behavior_keymap_binding_released(binding, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int zmk_invoke_behavior_binding(const struct zmk_behavior_binding *src_binding,
|
||||||
|
struct zmk_behavior_binding_event event, bool pressed) {
|
||||||
|
// We want to make a copy of this, since it may be converted from
|
||||||
|
// relative to absolute before being invoked
|
||||||
|
struct zmk_behavior_binding binding = *src_binding;
|
||||||
|
|
||||||
|
const struct device *behavior = zmk_behavior_get_binding(binding.behavior_dev);
|
||||||
|
|
||||||
|
if (!behavior) {
|
||||||
|
LOG_WRN("No behavior assigned to %d on layer %d", event.position, event.layer);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = behavior_keymap_binding_convert_central_state_dependent_params(&binding, event);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to convert relative to absolute behavior binding (err %d)", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum behavior_locality locality = BEHAVIOR_LOCALITY_CENTRAL;
|
||||||
|
err = behavior_get_locality(behavior, &locality);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to get behavior locality %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (locality) {
|
||||||
|
case BEHAVIOR_LOCALITY_CENTRAL:
|
||||||
|
return invoke_locally(&binding, event, pressed);
|
||||||
|
case BEHAVIOR_LOCALITY_EVENT_SOURCE:
|
||||||
|
#if ZMK_BLE_IS_CENTRAL // source is a member of event because CONFIG_ZMK_SPLIT is enabled
|
||||||
|
if (event.source == ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL) {
|
||||||
|
return invoke_locally(&binding, event, pressed);
|
||||||
|
} else {
|
||||||
|
return zmk_split_bt_invoke_behavior(event.source, &binding, event, pressed);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return invoke_locally(&binding, event, pressed);
|
||||||
|
#endif
|
||||||
|
case BEHAVIOR_LOCALITY_GLOBAL:
|
||||||
|
#if ZMK_BLE_IS_CENTRAL
|
||||||
|
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||||
|
zmk_split_bt_invoke_behavior(i, &binding, event, pressed);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return invoke_locally(&binding, event, pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||||
|
|
||||||
int zmk_behavior_get_empty_param_metadata(const struct device *dev,
|
int zmk_behavior_get_empty_param_metadata(const struct device *dev,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <zmk/behavior_queue.h>
|
#include <zmk/behavior_queue.h>
|
||||||
|
#include <zmk/behavior.h>
|
||||||
|
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
|
@ -14,6 +15,9 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
struct q_item {
|
struct q_item {
|
||||||
uint32_t position;
|
uint32_t position;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
uint8_t source;
|
||||||
|
#endif
|
||||||
struct zmk_behavior_binding binding;
|
struct zmk_behavior_binding binding;
|
||||||
bool press : 1;
|
bool press : 1;
|
||||||
uint32_t wait : 31;
|
uint32_t wait : 31;
|
||||||
|
@ -31,13 +35,18 @@ static void behavior_queue_process_next(struct k_work *work) {
|
||||||
LOG_DBG("Invoking %s: 0x%02x 0x%02x", item.binding.behavior_dev, item.binding.param1,
|
LOG_DBG("Invoking %s: 0x%02x 0x%02x", item.binding.behavior_dev, item.binding.param1,
|
||||||
item.binding.param2);
|
item.binding.param2);
|
||||||
|
|
||||||
struct zmk_behavior_binding_event event = {.position = item.position,
|
struct zmk_behavior_binding_event event = {
|
||||||
.timestamp = k_uptime_get()};
|
.position = item.position,
|
||||||
|
.timestamp = k_uptime_get(),
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = item.source
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
if (item.press) {
|
if (item.press) {
|
||||||
behavior_keymap_binding_pressed(&item.binding, event);
|
zmk_invoke_behavior_binding(&item.binding, event, true);
|
||||||
} else {
|
} else {
|
||||||
behavior_keymap_binding_released(&item.binding, event);
|
zmk_invoke_behavior_binding(&item.binding, event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("Processing next queued behavior in %dms", item.wait);
|
LOG_DBG("Processing next queued behavior in %dms", item.wait);
|
||||||
|
@ -49,9 +58,17 @@ static void behavior_queue_process_next(struct k_work *work) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int zmk_behavior_queue_add(uint32_t position, const struct zmk_behavior_binding binding, bool press,
|
int zmk_behavior_queue_add(struct zmk_behavior_binding_event *event,
|
||||||
uint32_t wait) {
|
const struct zmk_behavior_binding binding, bool press, uint32_t wait) {
|
||||||
struct q_item item = {.press = press, .binding = binding, .wait = wait};
|
struct q_item item = {
|
||||||
|
.press = press,
|
||||||
|
.binding = binding,
|
||||||
|
.wait = wait,
|
||||||
|
.position = event->position,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = event->source,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
const int ret = k_msgq_put(&zmk_behavior_queue_msgq, &item, K_NO_WAIT);
|
const int ret = k_msgq_put(&zmk_behavior_queue_msgq, &item, K_NO_WAIT);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
#include <zmk/events/position_state_changed.h>
|
#include <zmk/events/position_state_changed.h>
|
||||||
#include <zmk/events/keycode_state_changed.h>
|
#include <zmk/events/keycode_state_changed.h>
|
||||||
#include <zmk/behavior.h>
|
#include <zmk/behavior.h>
|
||||||
#include <zmk/keymap.h>
|
|
||||||
|
|
||||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
@ -77,6 +76,9 @@ struct behavior_hold_tap_data {
|
||||||
// this data is specific for each hold-tap
|
// this data is specific for each hold-tap
|
||||||
struct active_hold_tap {
|
struct active_hold_tap {
|
||||||
int32_t position;
|
int32_t position;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
uint8_t source;
|
||||||
|
#endif
|
||||||
uint32_t param_hold;
|
uint32_t param_hold;
|
||||||
uint32_t param_tap;
|
uint32_t param_tap;
|
||||||
int64_t timestamp;
|
int64_t timestamp;
|
||||||
|
@ -250,19 +252,22 @@ static struct active_hold_tap *find_hold_tap(uint32_t position) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct active_hold_tap *store_hold_tap(uint32_t position, uint32_t param_hold,
|
static struct active_hold_tap *store_hold_tap(struct zmk_behavior_binding_event *event,
|
||||||
uint32_t param_tap, int64_t timestamp,
|
uint32_t param_hold, uint32_t param_tap,
|
||||||
const struct behavior_hold_tap_config *config) {
|
const struct behavior_hold_tap_config *config) {
|
||||||
for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_HELD; i++) {
|
for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_HELD; i++) {
|
||||||
if (active_hold_taps[i].position != ZMK_BHV_HOLD_TAP_POSITION_NOT_USED) {
|
if (active_hold_taps[i].position != ZMK_BHV_HOLD_TAP_POSITION_NOT_USED) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
active_hold_taps[i].position = position;
|
active_hold_taps[i].position = event->position;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
active_hold_taps[i].source = event->source;
|
||||||
|
#endif
|
||||||
active_hold_taps[i].status = STATUS_UNDECIDED;
|
active_hold_taps[i].status = STATUS_UNDECIDED;
|
||||||
active_hold_taps[i].config = config;
|
active_hold_taps[i].config = config;
|
||||||
active_hold_taps[i].param_hold = param_hold;
|
active_hold_taps[i].param_hold = param_hold;
|
||||||
active_hold_taps[i].param_tap = param_tap;
|
active_hold_taps[i].param_tap = param_tap;
|
||||||
active_hold_taps[i].timestamp = timestamp;
|
active_hold_taps[i].timestamp = event->timestamp;
|
||||||
active_hold_taps[i].position_of_first_other_key_pressed = -1;
|
active_hold_taps[i].position_of_first_other_key_pressed = -1;
|
||||||
return &active_hold_taps[i];
|
return &active_hold_taps[i];
|
||||||
}
|
}
|
||||||
|
@ -400,45 +405,57 @@ static int press_hold_binding(struct active_hold_tap *hold_tap) {
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = hold_tap->position,
|
.position = hold_tap->position,
|
||||||
.timestamp = hold_tap->timestamp,
|
.timestamp = hold_tap->timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = hold_tap->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->hold_behavior_dev,
|
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->hold_behavior_dev,
|
||||||
.param1 = hold_tap->param_hold};
|
.param1 = hold_tap->param_hold};
|
||||||
return behavior_keymap_binding_pressed(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int press_tap_binding(struct active_hold_tap *hold_tap) {
|
static int press_tap_binding(struct active_hold_tap *hold_tap) {
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = hold_tap->position,
|
.position = hold_tap->position,
|
||||||
.timestamp = hold_tap->timestamp,
|
.timestamp = hold_tap->timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = hold_tap->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->tap_behavior_dev,
|
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->tap_behavior_dev,
|
||||||
.param1 = hold_tap->param_tap};
|
.param1 = hold_tap->param_tap};
|
||||||
store_last_hold_tapped(hold_tap);
|
store_last_hold_tapped(hold_tap);
|
||||||
return behavior_keymap_binding_pressed(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int release_hold_binding(struct active_hold_tap *hold_tap) {
|
static int release_hold_binding(struct active_hold_tap *hold_tap) {
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = hold_tap->position,
|
.position = hold_tap->position,
|
||||||
.timestamp = hold_tap->timestamp,
|
.timestamp = hold_tap->timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = hold_tap->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->hold_behavior_dev,
|
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->hold_behavior_dev,
|
||||||
.param1 = hold_tap->param_hold};
|
.param1 = hold_tap->param_hold};
|
||||||
return behavior_keymap_binding_released(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int release_tap_binding(struct active_hold_tap *hold_tap) {
|
static int release_tap_binding(struct active_hold_tap *hold_tap) {
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = hold_tap->position,
|
.position = hold_tap->position,
|
||||||
.timestamp = hold_tap->timestamp,
|
.timestamp = hold_tap->timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = hold_tap->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->tap_behavior_dev,
|
struct zmk_behavior_binding binding = {.behavior_dev = hold_tap->config->tap_behavior_dev,
|
||||||
.param1 = hold_tap->param_tap};
|
.param1 = hold_tap->param_tap};
|
||||||
return behavior_keymap_binding_released(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int press_binding(struct active_hold_tap *hold_tap) {
|
static int press_binding(struct active_hold_tap *hold_tap) {
|
||||||
|
@ -598,7 +615,8 @@ static int on_hold_tap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct active_hold_tap *hold_tap =
|
struct active_hold_tap *hold_tap =
|
||||||
store_hold_tap(event.position, binding->param1, binding->param2, event.timestamp, cfg);
|
store_hold_tap(&event, binding->param1, binding->param2, cfg);
|
||||||
|
|
||||||
if (hold_tap == NULL) {
|
if (hold_tap == NULL) {
|
||||||
LOG_ERR("unable to store hold-tap info, did you press more than %d hold-taps?",
|
LOG_ERR("unable to store hold-tap info, did you press more than %d hold-taps?",
|
||||||
ZMK_BHV_HOLD_TAP_MAX_HELD);
|
ZMK_BHV_HOLD_TAP_MAX_HELD);
|
||||||
|
|
|
@ -158,7 +158,8 @@ static void replace_params(struct behavior_macro_trigger_state *state,
|
||||||
state->param2_source = PARAM_SOURCE_BINDING;
|
state->param2_source = PARAM_SOURCE_BINDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void queue_macro(uint32_t position, const struct zmk_behavior_binding bindings[],
|
static void queue_macro(struct zmk_behavior_binding_event *event,
|
||||||
|
const struct zmk_behavior_binding bindings[],
|
||||||
struct behavior_macro_trigger_state state,
|
struct behavior_macro_trigger_state state,
|
||||||
const struct zmk_behavior_binding *macro_binding) {
|
const struct zmk_behavior_binding *macro_binding) {
|
||||||
LOG_DBG("Iterating macro bindings - starting: %d, count: %d", state.start_index, state.count);
|
LOG_DBG("Iterating macro bindings - starting: %d, count: %d", state.start_index, state.count);
|
||||||
|
@ -169,14 +170,14 @@ static void queue_macro(uint32_t position, const struct zmk_behavior_binding bin
|
||||||
|
|
||||||
switch (state.mode) {
|
switch (state.mode) {
|
||||||
case MACRO_MODE_TAP:
|
case MACRO_MODE_TAP:
|
||||||
zmk_behavior_queue_add(position, binding, true, state.tap_ms);
|
zmk_behavior_queue_add(event, binding, true, state.tap_ms);
|
||||||
zmk_behavior_queue_add(position, binding, false, state.wait_ms);
|
zmk_behavior_queue_add(event, binding, false, state.wait_ms);
|
||||||
break;
|
break;
|
||||||
case MACRO_MODE_PRESS:
|
case MACRO_MODE_PRESS:
|
||||||
zmk_behavior_queue_add(position, binding, true, state.wait_ms);
|
zmk_behavior_queue_add(event, binding, true, state.wait_ms);
|
||||||
break;
|
break;
|
||||||
case MACRO_MODE_RELEASE:
|
case MACRO_MODE_RELEASE:
|
||||||
zmk_behavior_queue_add(position, binding, false, state.wait_ms);
|
zmk_behavior_queue_add(event, binding, false, state.wait_ms);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_ERR("Unknown macro mode: %d", state.mode);
|
LOG_ERR("Unknown macro mode: %d", state.mode);
|
||||||
|
@ -197,7 +198,7 @@ static int on_macro_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
.start_index = 0,
|
.start_index = 0,
|
||||||
.count = state->press_bindings_count};
|
.count = state->press_bindings_count};
|
||||||
|
|
||||||
queue_macro(event.position, cfg->bindings, trigger_state, binding);
|
queue_macro(&event, cfg->bindings, trigger_state, binding);
|
||||||
|
|
||||||
return ZMK_BEHAVIOR_OPAQUE;
|
return ZMK_BEHAVIOR_OPAQUE;
|
||||||
}
|
}
|
||||||
|
@ -208,7 +209,7 @@ static int on_macro_binding_released(struct zmk_behavior_binding *binding,
|
||||||
const struct behavior_macro_config *cfg = dev->config;
|
const struct behavior_macro_config *cfg = dev->config;
|
||||||
struct behavior_macro_state *state = dev->data;
|
struct behavior_macro_state *state = dev->data;
|
||||||
|
|
||||||
queue_macro(event.position, cfg->bindings, state->release_state, binding);
|
queue_macro(&event, cfg->bindings, state->release_state, binding);
|
||||||
|
|
||||||
return ZMK_BEHAVIOR_OPAQUE;
|
return ZMK_BEHAVIOR_OPAQUE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ static int on_mod_morph_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
} else {
|
} else {
|
||||||
data->pressed_binding = (struct zmk_behavior_binding *)&cfg->normal_binding;
|
data->pressed_binding = (struct zmk_behavior_binding *)&cfg->normal_binding;
|
||||||
}
|
}
|
||||||
return behavior_keymap_binding_pressed(data->pressed_binding, event);
|
return zmk_invoke_behavior_binding(data->pressed_binding, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int on_mod_morph_binding_released(struct zmk_behavior_binding *binding,
|
static int on_mod_morph_binding_released(struct zmk_behavior_binding *binding,
|
||||||
|
@ -67,7 +67,7 @@ static int on_mod_morph_binding_released(struct zmk_behavior_binding *binding,
|
||||||
struct zmk_behavior_binding *pressed_binding = data->pressed_binding;
|
struct zmk_behavior_binding *pressed_binding = data->pressed_binding;
|
||||||
data->pressed_binding = NULL;
|
data->pressed_binding = NULL;
|
||||||
int err;
|
int err;
|
||||||
err = behavior_keymap_binding_released(pressed_binding, event);
|
err = zmk_invoke_behavior_binding(pressed_binding, event, false);
|
||||||
zmk_hid_masked_modifiers_clear();
|
zmk_hid_masked_modifiers_clear();
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <zmk/behavior_queue.h>
|
#include <zmk/behavior_queue.h>
|
||||||
#include <zmk/virtual_key_position.h>
|
#include <zmk/virtual_key_position.h>
|
||||||
|
#include <zmk/events/position_state_changed.h>
|
||||||
|
|
||||||
#include "behavior_sensor_rotate_common.h"
|
#include "behavior_sensor_rotate_common.h"
|
||||||
|
|
||||||
|
@ -89,9 +90,14 @@ int zmk_behavior_sensor_rotate_common_process(struct zmk_behavior_binding *bindi
|
||||||
|
|
||||||
LOG_DBG("Sensor binding: %s", binding->behavior_dev);
|
LOG_DBG("Sensor binding: %s", binding->behavior_dev);
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
// set this value so that it always triggers on central, can be handled more properly later
|
||||||
|
event.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL;
|
||||||
|
#endif
|
||||||
|
|
||||||
for (int i = 0; i < triggers; i++) {
|
for (int i = 0; i < triggers; i++) {
|
||||||
zmk_behavior_queue_add(event.position, triggered_binding, true, cfg->tap_ms);
|
zmk_behavior_queue_add(&event, triggered_binding, true, cfg->tap_ms);
|
||||||
zmk_behavior_queue_add(event.position, triggered_binding, false, 0);
|
zmk_behavior_queue_add(&event, triggered_binding, false, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ZMK_BEHAVIOR_OPAQUE;
|
return ZMK_BEHAVIOR_OPAQUE;
|
||||||
|
|
|
@ -40,6 +40,9 @@ struct behavior_sticky_key_config {
|
||||||
|
|
||||||
struct active_sticky_key {
|
struct active_sticky_key {
|
||||||
uint32_t position;
|
uint32_t position;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
uint8_t source;
|
||||||
|
#endif
|
||||||
uint32_t param1;
|
uint32_t param1;
|
||||||
uint32_t param2;
|
uint32_t param2;
|
||||||
const struct behavior_sticky_key_config *config;
|
const struct behavior_sticky_key_config *config;
|
||||||
|
@ -55,8 +58,8 @@ struct active_sticky_key {
|
||||||
|
|
||||||
struct active_sticky_key active_sticky_keys[ZMK_BHV_STICKY_KEY_MAX_HELD] = {};
|
struct active_sticky_key active_sticky_keys[ZMK_BHV_STICKY_KEY_MAX_HELD] = {};
|
||||||
|
|
||||||
static struct active_sticky_key *store_sticky_key(uint32_t position, uint32_t param1,
|
static struct active_sticky_key *store_sticky_key(struct zmk_behavior_binding_event *event,
|
||||||
uint32_t param2,
|
uint32_t param1, uint32_t param2,
|
||||||
const struct behavior_sticky_key_config *config) {
|
const struct behavior_sticky_key_config *config) {
|
||||||
for (int i = 0; i < ZMK_BHV_STICKY_KEY_MAX_HELD; i++) {
|
for (int i = 0; i < ZMK_BHV_STICKY_KEY_MAX_HELD; i++) {
|
||||||
struct active_sticky_key *const sticky_key = &active_sticky_keys[i];
|
struct active_sticky_key *const sticky_key = &active_sticky_keys[i];
|
||||||
|
@ -64,7 +67,10 @@ static struct active_sticky_key *store_sticky_key(uint32_t position, uint32_t pa
|
||||||
sticky_key->timer_cancelled) {
|
sticky_key->timer_cancelled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sticky_key->position = position;
|
sticky_key->position = event->position;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
sticky_key->source = event->source;
|
||||||
|
#endif
|
||||||
sticky_key->param1 = param1;
|
sticky_key->param1 = param1;
|
||||||
sticky_key->param2 = param2;
|
sticky_key->param2 = param2;
|
||||||
sticky_key->config = config;
|
sticky_key->config = config;
|
||||||
|
@ -101,8 +107,11 @@ static inline int press_sticky_key_behavior(struct active_sticky_key *sticky_key
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = sticky_key->position,
|
.position = sticky_key->position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = sticky_key->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
return behavior_keymap_binding_pressed(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int release_sticky_key_behavior(struct active_sticky_key *sticky_key,
|
static inline int release_sticky_key_behavior(struct active_sticky_key *sticky_key,
|
||||||
|
@ -115,10 +124,13 @@ static inline int release_sticky_key_behavior(struct active_sticky_key *sticky_k
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = sticky_key->position,
|
.position = sticky_key->position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = sticky_key->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
clear_sticky_key(sticky_key);
|
clear_sticky_key(sticky_key);
|
||||||
return behavior_keymap_binding_released(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void on_sticky_key_timeout(struct active_sticky_key *sticky_key) {
|
static inline void on_sticky_key_timeout(struct active_sticky_key *sticky_key) {
|
||||||
|
@ -149,7 +161,7 @@ static int on_sticky_key_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
stop_timer(sticky_key);
|
stop_timer(sticky_key);
|
||||||
release_sticky_key_behavior(sticky_key, event.timestamp);
|
release_sticky_key_behavior(sticky_key, event.timestamp);
|
||||||
}
|
}
|
||||||
sticky_key = store_sticky_key(event.position, binding->param1, binding->param2, cfg);
|
sticky_key = store_sticky_key(&event, binding->param1, binding->param2, cfg);
|
||||||
if (sticky_key == NULL) {
|
if (sticky_key == NULL) {
|
||||||
LOG_ERR("unable to store sticky key, did you press more than %d sticky_key?",
|
LOG_ERR("unable to store sticky key, did you press more than %d sticky_key?",
|
||||||
ZMK_BHV_STICKY_KEY_MAX_HELD);
|
ZMK_BHV_STICKY_KEY_MAX_HELD);
|
||||||
|
|
|
@ -35,6 +35,9 @@ struct active_tap_dance {
|
||||||
// Tap Dance Data
|
// Tap Dance Data
|
||||||
int counter;
|
int counter;
|
||||||
uint32_t position;
|
uint32_t position;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
uint8_t source;
|
||||||
|
#endif
|
||||||
uint32_t param1;
|
uint32_t param1;
|
||||||
uint32_t param2;
|
uint32_t param2;
|
||||||
bool is_pressed;
|
bool is_pressed;
|
||||||
|
@ -59,13 +62,17 @@ static struct active_tap_dance *find_tap_dance(uint32_t position) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int new_tap_dance(uint32_t position, const struct behavior_tap_dance_config *config,
|
static int new_tap_dance(struct zmk_behavior_binding_event *event,
|
||||||
|
const struct behavior_tap_dance_config *config,
|
||||||
struct active_tap_dance **tap_dance) {
|
struct active_tap_dance **tap_dance) {
|
||||||
for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) {
|
for (int i = 0; i < ZMK_BHV_TAP_DANCE_MAX_HELD; i++) {
|
||||||
struct active_tap_dance *const ref_dance = &active_tap_dances[i];
|
struct active_tap_dance *const ref_dance = &active_tap_dances[i];
|
||||||
if (ref_dance->position == ZMK_BHV_TAP_DANCE_POSITION_FREE) {
|
if (ref_dance->position == ZMK_BHV_TAP_DANCE_POSITION_FREE) {
|
||||||
ref_dance->counter = 0;
|
ref_dance->counter = 0;
|
||||||
ref_dance->position = position;
|
ref_dance->position = event->position;
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
ref_dance->source = event->source;
|
||||||
|
#endif
|
||||||
ref_dance->config = config;
|
ref_dance->config = config;
|
||||||
ref_dance->release_at = 0;
|
ref_dance->release_at = 0;
|
||||||
ref_dance->is_pressed = true;
|
ref_dance->is_pressed = true;
|
||||||
|
@ -108,8 +115,11 @@ static inline int press_tap_dance_behavior(struct active_tap_dance *tap_dance, i
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = tap_dance->position,
|
.position = tap_dance->position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = tap_dance->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
return behavior_keymap_binding_pressed(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance,
|
static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance,
|
||||||
|
@ -118,9 +128,12 @@ static inline int release_tap_dance_behavior(struct active_tap_dance *tap_dance,
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = tap_dance->position,
|
.position = tap_dance->position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = tap_dance->source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
clear_tap_dance(tap_dance);
|
clear_tap_dance(tap_dance);
|
||||||
return behavior_keymap_binding_released(&binding, event);
|
return zmk_invoke_behavior_binding(&binding, event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding,
|
static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
|
@ -130,7 +143,7 @@ static int on_tap_dance_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
struct active_tap_dance *tap_dance;
|
struct active_tap_dance *tap_dance;
|
||||||
tap_dance = find_tap_dance(event.position);
|
tap_dance = find_tap_dance(event.position);
|
||||||
if (tap_dance == NULL) {
|
if (tap_dance == NULL) {
|
||||||
if (new_tap_dance(event.position, cfg, &tap_dance) == -ENOMEM) {
|
if (new_tap_dance(&event, cfg, &tap_dance) == -ENOMEM) {
|
||||||
LOG_ERR("Unable to create new tap dance. Insufficient space in active_tap_dances[].");
|
LOG_ERR("Unable to create new tap dance. Insufficient space in active_tap_dances[].");
|
||||||
return ZMK_BEHAVIOR_OPAQUE;
|
return ZMK_BEHAVIOR_OPAQUE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,20 +292,26 @@ static inline int press_combo_behavior(struct combo_cfg *combo, int32_t timestam
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = combo->virtual_key_position,
|
.position = combo->virtual_key_position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
last_combo_timestamp = timestamp;
|
last_combo_timestamp = timestamp;
|
||||||
|
|
||||||
return behavior_keymap_binding_pressed(&combo->behavior, event);
|
return zmk_invoke_behavior_binding(&combo->behavior, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int release_combo_behavior(struct combo_cfg *combo, int32_t timestamp) {
|
static inline int release_combo_behavior(struct combo_cfg *combo, int32_t timestamp) {
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = combo->virtual_key_position,
|
.position = combo->virtual_key_position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
return behavior_keymap_binding_released(&combo->behavior, event);
|
return zmk_invoke_behavior_binding(&combo->behavior, event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void move_pressed_keys_to_active_combo(struct active_combo *active_combo) {
|
static void move_pressed_keys_to_active_combo(struct active_combo *active_combo) {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
#include <drivers/behavior.h>
|
#include <drivers/behavior.h>
|
||||||
#include <zephyr/sys/util.h>
|
#include <zephyr/sys/util.h>
|
||||||
#include <zephyr/bluetooth/bluetooth.h>
|
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
@ -16,11 +15,6 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
#include <zmk/sensors.h>
|
#include <zmk/sensors.h>
|
||||||
#include <zmk/virtual_key_position.h>
|
#include <zmk/virtual_key_position.h>
|
||||||
|
|
||||||
#include <zmk/ble.h>
|
|
||||||
#if ZMK_BLE_IS_CENTRAL
|
|
||||||
#include <zmk/split/bluetooth/central.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <zmk/event_manager.h>
|
#include <zmk/event_manager.h>
|
||||||
#include <zmk/events/position_state_changed.h>
|
#include <zmk/events/position_state_changed.h>
|
||||||
#include <zmk/events/layer_state_changed.h>
|
#include <zmk/events/layer_state_changed.h>
|
||||||
|
@ -163,72 +157,21 @@ const char *zmk_keymap_layer_name(uint8_t layer) {
|
||||||
return zmk_keymap_layer_names[layer];
|
return zmk_keymap_layer_names[layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
int invoke_locally(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event,
|
|
||||||
bool pressed) {
|
|
||||||
if (pressed) {
|
|
||||||
return behavior_keymap_binding_pressed(binding, event);
|
|
||||||
} else {
|
|
||||||
return behavior_keymap_binding_released(binding, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int zmk_keymap_apply_position_state(uint8_t source, int layer, uint32_t position, bool pressed,
|
int zmk_keymap_apply_position_state(uint8_t source, int layer, uint32_t position, bool pressed,
|
||||||
int64_t timestamp) {
|
int64_t timestamp) {
|
||||||
// We want to make a copy of this, since it may be converted from
|
struct zmk_behavior_binding *binding = &zmk_keymap[layer][position];
|
||||||
// relative to absolute before being invoked
|
|
||||||
struct zmk_behavior_binding binding = zmk_keymap[layer][position];
|
|
||||||
const struct device *behavior;
|
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.layer = layer,
|
.layer = layer,
|
||||||
.position = position,
|
.position = position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
|
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
|
||||||
|
.source = source,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
LOG_DBG("layer: %d position: %d, binding name: %s", layer, position, binding.behavior_dev);
|
LOG_DBG("layer: %d position: %d, binding name: %s", layer, position, binding->behavior_dev);
|
||||||
|
|
||||||
behavior = zmk_behavior_get_binding(binding.behavior_dev);
|
return zmk_invoke_behavior_binding(binding, event, pressed);
|
||||||
|
|
||||||
if (!behavior) {
|
|
||||||
LOG_WRN("No behavior assigned to %d on layer %d", position, layer);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int err = behavior_keymap_binding_convert_central_state_dependent_params(&binding, event);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to convert relative to absolute behavior binding (err %d)", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum behavior_locality locality = BEHAVIOR_LOCALITY_CENTRAL;
|
|
||||||
err = behavior_get_locality(behavior, &locality);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to get behavior locality %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (locality) {
|
|
||||||
case BEHAVIOR_LOCALITY_CENTRAL:
|
|
||||||
return invoke_locally(&binding, event, pressed);
|
|
||||||
case BEHAVIOR_LOCALITY_EVENT_SOURCE:
|
|
||||||
#if ZMK_BLE_IS_CENTRAL
|
|
||||||
if (source == ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL) {
|
|
||||||
return invoke_locally(&binding, event, pressed);
|
|
||||||
} else {
|
|
||||||
return zmk_split_bt_invoke_behavior(source, &binding, event, pressed);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return invoke_locally(&binding, event, pressed);
|
|
||||||
#endif
|
|
||||||
case BEHAVIOR_LOCALITY_GLOBAL:
|
|
||||||
#if ZMK_BLE_IS_CENTRAL
|
|
||||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
|
||||||
zmk_split_bt_invoke_behavior(i, &binding, event, pressed);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return invoke_locally(&binding, event, pressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pressed,
|
int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pressed,
|
||||||
|
|
|
@ -816,6 +816,7 @@ int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *bi
|
||||||
.param1 = binding->param1,
|
.param1 = binding->param1,
|
||||||
.param2 = binding->param2,
|
.param2 = binding->param2,
|
||||||
.position = event.position,
|
.position = event.position,
|
||||||
|
.source = event.source,
|
||||||
.state = state ? 1 : 0,
|
.state = state ? 1 : 0,
|
||||||
}};
|
}};
|
||||||
const size_t payload_dev_size = sizeof(payload.behavior_dev);
|
const size_t payload_dev_size = sizeof(payload.behavior_dev);
|
||||||
|
|
|
@ -86,11 +86,6 @@ These behaviors only affect the keyboard part that they are invoked from:
|
||||||
|
|
||||||
- [Reset behaviors](../keymaps/behaviors/reset.md)
|
- [Reset behaviors](../keymaps/behaviors/reset.md)
|
||||||
|
|
||||||
:::warning[Nesting behaviors with locality]
|
|
||||||
Currently there is [an issue](https://github.com/zmkfirmware/zmk/issues/1494) preventing both global and source locality behaviors from working as expected if they are invoked from another behavior, such as a hold-tap, tap dance or a mod-morph.
|
|
||||||
For this reason it is recommended that these behaviors are placed directly on a keymap layer.
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::note[Peripheral invocation]
|
:::note[Peripheral invocation]
|
||||||
Peripherals must be paired and connected to the central in order to be able to activate these behaviors, even if it is possible to trigger the behavior using only keys on a particular peripheral.
|
Peripherals must be paired and connected to the central in order to be able to activate these behaviors, even if it is possible to trigger the behavior using only keys on a particular peripheral.
|
||||||
This is because the key bindings are processed on the central side which would then instruct the peripheral side to run the behavior's effect.
|
This is because the key bindings are processed on the central side which would then instruct the peripheral side to run the behavior's effect.
|
||||||
|
|
Loading…
Add table
Reference in a new issue