aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorPete Johanson <[email protected]>2024-12-17 18:50:06 -0700
committerGitHub <[email protected]>2024-12-17 20:50:06 -0500
commitcb867f92dbe4e32675c2137fc6aa914a44ecc8dc (patch)
tree84ed7a733e5de6d00b3129d3c76f917e250f7095
parentd0016b34f88b76bd0c1fb5964bbae9d1916dcf76 (diff)
downloadzmk-cb867f92dbe4e32675c2137fc6aa914a44ecc8dc.tar.gz
zmk-cb867f92dbe4e32675c2137fc6aa914a44ecc8dc.zip
Feature: input processor behavior invocation (#2714)
refactor(pointing): Allow stopping event propagation Allow input processors to return a special value if a given input event should not be further processed/propagated. feat(pointing): Add behavior input processor Add the ability to intercept certain input events and trigger behaviors when they occur. Co-authored-by: Jorge Villalobos <[email protected]> Co-authored-by: Cem Aksoylar <[email protected]>
-rw-r--r--app/dts/bindings/input_processors/zmk,input-processor-behaviors.yaml18
-rw-r--r--app/dts/input/processors.dtsi3
-rw-r--r--app/dts/input/processors/behaviors.dtsi16
-rw-r--r--app/include/drivers/input_processor.h4
-rw-r--r--app/include/zmk/combos.h16
-rw-r--r--app/include/zmk/input_listeners.h14
-rw-r--r--app/include/zmk/virtual_key_position.h9
-rw-r--r--app/src/pointing/CMakeLists.txt1
-rw-r--r--app/src/pointing/Kconfig5
-rw-r--r--app/src/pointing/input_listener.c54
-rw-r--r--app/src/pointing/input_processor_behaviors.c92
-rw-r--r--app/src/pointing/input_processor_code_mapper.c4
-rw-r--r--app/src/pointing/input_processor_scaler.c4
-rw-r--r--app/src/pointing/input_processor_temp_layer.c2
-rw-r--r--app/src/pointing/input_processor_transform.c4
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_basic/events.patterns2
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_basic/keycode_events.snapshot6
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.conf6
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.keymap44
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_hold_tap/events.patterns2
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_hold_tap/keycode_events.snapshot16
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.conf6
-rw-r--r--app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.keymap54
-rw-r--r--docs/docs/keymaps/input-processors/behaviors.md81
-rw-r--r--docs/docs/keymaps/input-processors/index.md30
25 files changed, 456 insertions, 37 deletions
diff --git a/app/dts/bindings/input_processors/zmk,input-processor-behaviors.yaml b/app/dts/bindings/input_processors/zmk,input-processor-behaviors.yaml
new file mode 100644
index 0000000000..c8c54c557a
--- /dev/null
+++ b/app/dts/bindings/input_processors/zmk,input-processor-behaviors.yaml
@@ -0,0 +1,18 @@
+# Copyright (c) 2024, The ZMK Contributors
+# SPDX-License-Identifier: MIT
+
+description: Input Processor for invoking behaviors on certain events
+
+compatible: "zmk,input-processor-behaviors"
+
+include: ip_zero_param.yaml
+
+properties:
+ type:
+ type: int
+ codes:
+ type: array
+ required: true
+ bindings:
+ type: phandle-array
+ required: true
diff --git a/app/dts/input/processors.dtsi b/app/dts/input/processors.dtsi
index d072c0fcfc..17398bada7 100644
--- a/app/dts/input/processors.dtsi
+++ b/app/dts/input/processors.dtsi
@@ -7,4 +7,5 @@
#include <input/processors/scaler.dtsi>
#include <input/processors/code_mapper.dtsi>
#include <input/processors/transform.dtsi>
-#include <input/processors/temp_layer.dtsi> \ No newline at end of file
+#include <input/processors/temp_layer.dtsi>
+#include <input/processors/behaviors.dtsi> \ No newline at end of file
diff --git a/app/dts/input/processors/behaviors.dtsi b/app/dts/input/processors/behaviors.dtsi
new file mode 100644
index 0000000000..a50f9384b0
--- /dev/null
+++ b/app/dts/input/processors/behaviors.dtsi
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2024 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <zephyr/dt-bindings/input/input-event-codes.h>
+
+/ {
+ zip_button_behaviors: zip_button_behaviors {
+ compatible = "zmk,input-processor-behaviors";
+ #input-processor-cells = <0>;
+ codes = <INPUT_BTN_0 INPUT_BTN_1 INPUT_BTN_2>;
+ bindings = <&none &none &none>;
+ };
+}; \ No newline at end of file
diff --git a/app/include/drivers/input_processor.h b/app/include/drivers/input_processor.h
index aea57476ae..fac4459b91 100644
--- a/app/include/drivers/input_processor.h
+++ b/app/include/drivers/input_processor.h
@@ -13,6 +13,9 @@
#include <zephyr/device.h>
#include <zephyr/input/input.h>
+#define ZMK_INPUT_PROC_CONTINUE 0
+#define ZMK_INPUT_PROC_STOP 1
+
struct zmk_input_processor_entry {
const struct device *dev;
uint32_t param1;
@@ -33,6 +36,7 @@ struct zmk_input_processor_entry {
}
struct zmk_input_processor_state {
+ uint8_t input_device_index;
int16_t *remainder;
};
diff --git a/app/include/zmk/combos.h b/app/include/zmk/combos.h
new file mode 100644
index 0000000000..a7166dff58
--- /dev/null
+++ b/app/include/zmk/combos.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <zephyr/devicetree.h>
+
+#define ZMK_COMBOS_UTIL_ONE(n) 1
+
+#define ZMK_COMBOS_LEN \
+ COND_CODE_1( \
+ DT_HAS_COMPAT_STATUS_OKAY(zmk_combos), \
+ (DT_FOREACH_CHILD_STATUS_OKAY_SEP(DT_INST(0, zmk_combos), ZMK_COMBOS_UTIL_ONE, (+))), (0)) \ No newline at end of file
diff --git a/app/include/zmk/input_listeners.h b/app/include/zmk/input_listeners.h
new file mode 100644
index 0000000000..446706fb31
--- /dev/null
+++ b/app/include/zmk/input_listeners.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <zephyr/devicetree.h>
+
+#define ZMK_INPUT_LISTENERS_UTIL_ONE(n) 1 +
+
+#define ZMK_INPUT_LISTENERS_LEN \
+ (DT_FOREACH_STATUS_OKAY(zmk_input_listener, ZMK_INPUT_LISTENERS_UTIL_ONE) 0) \ No newline at end of file
diff --git a/app/include/zmk/virtual_key_position.h b/app/include/zmk/virtual_key_position.h
index 563f951eba..466ea3efc1 100644
--- a/app/include/zmk/virtual_key_position.h
+++ b/app/include/zmk/virtual_key_position.h
@@ -7,6 +7,8 @@
#pragma once
#include <zmk/matrix.h>
+#include <zmk/combos.h>
+#include <zmk/input_listeners.h>
#include <zmk/sensors.h>
/**
@@ -22,4 +24,9 @@
/**
* Gets the virtual key position to use for the combo with the given index.
*/
-#define ZMK_VIRTUAL_KEY_POSITION_COMBO(index) (ZMK_KEYMAP_LEN + ZMK_KEYMAP_SENSORS_LEN + (index))
+#define ZMK_VIRTUAL_KEY_POSITION_COMBO(index) \
+ (ZMK_VIRTUAL_KEY_POSITION_SENSOR(ZMK_KEYMAP_SENSORS_LEN) + (index))
+
+#define ZMK_VIRTUAL_KEY_POSITION_BEHAVIOR_INPUT_PROCESSOR(listener_index, processor_index) \
+ (ZMK_VIRTUAL_KEY_POSITION_COMBO(ZMK_COMBOS_LEN) + \
+ (ZMK_INPUT_LISTENERS_LEN * (processor_index)) + (listener_index))
diff --git a/app/src/pointing/CMakeLists.txt b/app/src/pointing/CMakeLists.txt
index ad74c1ea78..8609277d6e 100644
--- a/app/src/pointing/CMakeLists.txt
+++ b/app/src/pointing/CMakeLists.txt
@@ -6,5 +6,6 @@ target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_TRANSFORM app PRIVATE input_proc
target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_SCALER app PRIVATE input_processor_scaler.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_TEMP_LAYER app PRIVATE input_processor_temp_layer.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_CODE_MAPPER app PRIVATE input_processor_code_mapper.c)
+target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_BEHAVIORS app PRIVATE input_processor_behaviors.c)
target_sources_ifdef(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING app PRIVATE resolution_multipliers.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_SPLIT app PRIVATE input_split.c)
diff --git a/app/src/pointing/Kconfig b/app/src/pointing/Kconfig
index 42cf8e8c07..b4051e1fce 100644
--- a/app/src/pointing/Kconfig
+++ b/app/src/pointing/Kconfig
@@ -57,6 +57,11 @@ config ZMK_INPUT_PROCESSOR_CODE_MAPPER
default y
depends on DT_HAS_ZMK_INPUT_PROCESSOR_CODE_MAPPER_ENABLED
+config ZMK_INPUT_PROCESSOR_BEHAVIORS
+ bool "Behaviors Input Processor"
+ default y
+ depends on DT_HAS_ZMK_INPUT_PROCESSOR_BEHAVIORS_ENABLED
+
config ZMK_INPUT_SPLIT
bool "Split input support"
default y
diff --git a/app/src/pointing/input_listener.c b/app/src/pointing/input_listener.c
index 97e39285d3..28f2530003 100644
--- a/app/src/pointing/input_listener.c
+++ b/app/src/pointing/input_listener.c
@@ -71,6 +71,7 @@ struct input_listener_processor_data {
};
struct input_listener_config {
+ uint8_t listener_index;
struct input_listener_config_entry base;
size_t layer_overrides_len;
struct input_listener_layer_override layer_overrides[];
@@ -152,9 +153,9 @@ static inline bool is_y_data(const struct input_event *evt) {
return evt->type == INPUT_EV_REL && evt->code == INPUT_REL_Y;
}
-static void apply_config(const struct input_listener_config_entry *cfg,
- struct input_listener_processor_data *processor_data,
- struct input_listener_data *data, struct input_event *evt) {
+static int apply_config(uint8_t listener_index, const struct input_listener_config_entry *cfg,
+ struct input_listener_processor_data *processor_data,
+ struct input_listener_data *data, struct input_event *evt) {
size_t remainder_index = 0;
for (size_t p = 0; p < cfg->processors_len; p++) {
const struct zmk_input_processor_entry *proc_e = &cfg->processors[p];
@@ -183,15 +184,27 @@ static void apply_config(const struct input_listener_config_entry *cfg,
}
}
- struct zmk_input_processor_state state = {.remainder = remainder};
-
- zmk_input_processor_handle_event(proc_e->dev, evt, proc_e->param1, proc_e->param2, &state);
+ LOG_DBG("LISTENER INDEX: %d", listener_index);
+ struct zmk_input_processor_state state = {.input_device_index = listener_index,
+ .remainder = remainder};
+
+ int ret = zmk_input_processor_handle_event(proc_e->dev, evt, proc_e->param1, proc_e->param2,
+ &state);
+ switch (ret) {
+ case ZMK_INPUT_PROC_CONTINUE:
+ continue;
+ default:
+ return ret;
+ }
}
+
+ return ZMK_INPUT_PROC_CONTINUE;
}
-static void filter_with_input_config(const struct input_listener_config *cfg,
- struct input_listener_data *data, struct input_event *evt) {
+
+static int filter_with_input_config(const struct input_listener_config *cfg,
+ struct input_listener_data *data, struct input_event *evt) {
if (!evt->dev) {
- return;
+ return -ENODEV;
}
for (size_t oi = 0; oi < cfg->layer_overrides_len; oi++) {
@@ -201,9 +214,14 @@ static void filter_with_input_config(const struct input_listener_config *cfg,
uint8_t layer = 0;
while (mask != 0) {
if (mask & BIT(0) && zmk_keymap_layer_active(layer)) {
- apply_config(&override->config, override_data, data, evt);
+ int ret =
+ apply_config(cfg->listener_index, &override->config, override_data, data, evt);
+
+ if (ret < 0) {
+ return ret;
+ }
if (!override->process_next) {
- return;
+ return 0;
}
}
@@ -212,7 +230,7 @@ static void filter_with_input_config(const struct input_listener_config *cfg,
}
}
- apply_config(&cfg->base, &data->base_processor_data, data, evt);
+ return apply_config(cfg->listener_index, &cfg->base, &data->base_processor_data, data, evt);
}
static void clear_xy_data(struct input_listener_xy_data *data) {
@@ -247,8 +265,15 @@ static void apply_resolution_scaling(struct input_listener_data *data, struct in
static void input_handler(const struct input_listener_config *config,
struct input_listener_data *data, struct input_event *evt) {
- // First, filter to update the event data as needed.
- filter_with_input_config(config, data, evt);
+ // First, process to update the event data as needed.
+ int ret = filter_with_input_config(config, data, evt);
+
+ if (ret < 0) {
+ LOG_ERR("Error applying input processors: %d", ret);
+ return;
+ } else if (ret == ZMK_INPUT_PROC_STOP) {
+ return;
+ }
#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
apply_resolution_scaling(data, evt);
@@ -355,6 +380,7 @@ static void input_handler(const struct input_listener_config *config,
DT_INST_FOREACH_CHILD_VARGS(n, CHILD_CONFIG, \
n) static const struct input_listener_config config_##n = \
{ \
+ .listener_index = n, \
.base = IL_EXTRACT_CONFIG(DT_DRV_INST(n), n, base), \
.layer_overrides_len = (0 DT_INST_FOREACH_CHILD(n, IL_ONE)), \
.layer_overrides = {DT_INST_FOREACH_CHILD_SEP_VARGS(n, IL_OVERRIDE, (, ), n)}, \
diff --git a/app/src/pointing/input_processor_behaviors.c b/app/src/pointing/input_processor_behaviors.c
new file mode 100644
index 0000000000..0c8d75cdc3
--- /dev/null
+++ b/app/src/pointing/input_processor_behaviors.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2024 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_input_processor_behaviors
+
+#include <zephyr/dt-bindings/input/input-event-codes.h>
+
+#include <zephyr/kernel.h>
+#include <zephyr/device.h>
+#include <drivers/input_processor.h>
+
+#include <zephyr/logging/log.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#include <zmk/keymap.h>
+#include <zmk/behavior.h>
+#include <zmk/virtual_key_position.h>
+
+struct ip_behaviors_config {
+ uint8_t index;
+ size_t size;
+ uint16_t type;
+
+ const uint16_t *codes;
+ const struct zmk_behavior_binding *bindings;
+};
+
+static int ip_behaviors_handle_event(const struct device *dev, struct input_event *event,
+ uint32_t param1, uint32_t param2,
+ struct zmk_input_processor_state *state) {
+ const struct ip_behaviors_config *cfg = dev->config;
+
+ if (event->type != cfg->type) {
+ return 0;
+ }
+
+ for (size_t i = 0; i < cfg->size; i++) {
+ if (cfg->codes[i] == event->code) {
+ struct zmk_behavior_binding_event behavior_event = {
+ .position = ZMK_VIRTUAL_KEY_POSITION_BEHAVIOR_INPUT_PROCESSOR(
+ state->input_device_index, cfg->index),
+ .timestamp = k_uptime_get(),
+#if IS_ENABLED(CONFIG_ZMK_SPLIT)
+ .source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
+#endif
+ };
+
+ LOG_DBG("FOUND A MATCHING CODE, invoke %s for position %d with %d listeners",
+ cfg->bindings[i].behavior_dev, behavior_event.position,
+ ZMK_INPUT_LISTENERS_LEN);
+ int ret = zmk_behavior_invoke_binding(&cfg->bindings[i], behavior_event, event->value);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return ZMK_INPUT_PROC_STOP;
+ }
+ }
+
+ return 0;
+}
+
+static struct zmk_input_processor_driver_api ip_behaviors_driver_api = {
+ .handle_event = ip_behaviors_handle_event,
+};
+
+static int ip_behaviors_init(const struct device *dev) { return 0; }
+
+#define ENTRY(i, node) ZMK_KEYMAP_EXTRACT_BINDING(i, node)
+
+#define IP_BEHAVIORS_INST(n) \
+ static const uint16_t ip_behaviors_codes_##n[] = DT_INST_PROP(n, codes); \
+ static const struct zmk_behavior_binding ip_behaviors_bindings_##n[] = { \
+ LISTIFY(DT_INST_PROP_LEN(n, bindings), ZMK_KEYMAP_EXTRACT_BINDING, (, ), DT_DRV_INST(n))}; \
+ BUILD_ASSERT(ARRAY_SIZE(ip_behaviors_codes_##n) == ARRAY_SIZE(ip_behaviors_bindings_##n), \
+ "codes and bindings need to be the same length"); \
+ static const struct ip_behaviors_config ip_behaviors_config_##n = { \
+ .index = n, \
+ .type = DT_INST_PROP_OR(n, type, INPUT_EV_KEY), \
+ .size = DT_INST_PROP_LEN(n, codes), \
+ .codes = ip_behaviors_codes_##n, \
+ .bindings = ip_behaviors_bindings_##n, \
+ }; \
+ DEVICE_DT_INST_DEFINE(n, &ip_behaviors_init, NULL, NULL, &ip_behaviors_config_##n, \
+ POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
+ &ip_behaviors_driver_api);
+
+DT_INST_FOREACH_STATUS_OKAY(IP_BEHAVIORS_INST) \ No newline at end of file
diff --git a/app/src/pointing/input_processor_code_mapper.c b/app/src/pointing/input_processor_code_mapper.c
index 20b5744ea9..b42d3478e8 100644
--- a/app/src/pointing/input_processor_code_mapper.c
+++ b/app/src/pointing/input_processor_code_mapper.c
@@ -25,7 +25,7 @@ static int cm_handle_event(const struct device *dev, struct input_event *event,
const struct cm_config *cfg = dev->config;
if (event->type != cfg->type) {
- return 0;
+ return ZMK_INPUT_PROC_CONTINUE;
}
for (int i = 0; i < cfg->mapping_size / 2; i++) {
@@ -37,7 +37,7 @@ static int cm_handle_event(const struct device *dev, struct input_event *event,
}
}
- return 0;
+ return ZMK_INPUT_PROC_CONTINUE;
}
static struct zmk_input_processor_driver_api cm_driver_api = {
diff --git a/app/src/pointing/input_processor_scaler.c b/app/src/pointing/input_processor_scaler.c
index eb006cd254..c7e08ecdb0 100644
--- a/app/src/pointing/input_processor_scaler.c
+++ b/app/src/pointing/input_processor_scaler.c
@@ -47,7 +47,7 @@ static int scaler_handle_event(const struct device *dev, struct input_event *eve
const struct scaler_config *cfg = dev->config;
if (event->type != cfg->type) {
- return 0;
+ return ZMK_INPUT_PROC_CONTINUE;
}
for (int i = 0; i < cfg->codes_len; i++) {
@@ -56,7 +56,7 @@ static int scaler_handle_event(const struct device *dev, struct input_event *eve
}
}
- return 0;
+ return ZMK_INPUT_PROC_CONTINUE;
}
static struct zmk_input_processor_driver_api scaler_driver_api = {
diff --git a/app/src/pointing/input_processor_temp_layer.c b/app/src/pointing/input_processor_temp_layer.c
index 85a394a693..b106527115 100644
--- a/app/src/pointing/input_processor_temp_layer.c
+++ b/app/src/pointing/input_processor_temp_layer.c
@@ -162,7 +162,7 @@ static int temp_layer_handle_event(const struct device *dev, struct input_event
k_work_reschedule(&layer_disable_works[param1], K_MSEC(param2));
}
- return 0;
+ return ZMK_INPUT_PROC_CONTINUE;
}
static int temp_layer_init(const struct device *dev) {
diff --git a/app/src/pointing/input_processor_transform.c b/app/src/pointing/input_processor_transform.c
index 10828de3f3..c214baee26 100644
--- a/app/src/pointing/input_processor_transform.c
+++ b/app/src/pointing/input_processor_transform.c
@@ -42,7 +42,7 @@ static int ipt_handle_event(const struct device *dev, struct input_event *event,
const struct ipt_config *cfg = dev->config;
if (event->type != cfg->type) {
- return 0;
+ return ZMK_INPUT_PROC_CONTINUE;
}
if (param1 & INPUT_TRANSFORM_XY_SWAP) {
@@ -65,7 +65,7 @@ static int ipt_handle_event(const struct device *dev, struct input_event *event,
event->value = -event->value;
}
- return 0;
+ return ZMK_INPUT_PROC_CONTINUE;
}
static struct zmk_input_processor_driver_api ipt_driver_api = {
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_basic/events.patterns b/app/tests/pointing/mouse-move/processors/behaviors_basic/events.patterns
new file mode 100644
index 0000000000..7374badf0f
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_basic/events.patterns
@@ -0,0 +1,2 @@
+s/.*hid_mouse_//p
+s/.*hid_listener_keycode_//p \ No newline at end of file
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_basic/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/behaviors_basic/keycode_events.snapshot
new file mode 100644
index 0000000000..7ad856db60
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_basic/keycode_events.snapshot
@@ -0,0 +1,6 @@
+pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.conf
new file mode 100644
index 0000000000..fa514727ba
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.conf
@@ -0,0 +1,6 @@
+CONFIG_GPIO=n
+CONFIG_ZMK_BLE=n
+CONFIG_LOG=y
+CONFIG_LOG_BACKEND_SHOW_COLOR=n
+CONFIG_ZMK_LOG_LEVEL_DBG=y
+CONFIG_ZMK_POINTING=y
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.keymap
new file mode 100644
index 0000000000..e5079d331a
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_basic/native_posix_64.keymap
@@ -0,0 +1,44 @@
+
+#include <dt-bindings/zmk/input_transform.h>
+#include <zephyr/dt-bindings/input/input-event-codes.h>
+
+#include <behaviors.dtsi>
+#include <input/processors.dtsi>
+#include <dt-bindings/zmk/keys.h>
+#include <dt-bindings/zmk/kscan_mock.h>
+#include <dt-bindings/zmk/pointing.h>
+
+
+&zip_button_behaviors {
+ bindings = <&kp A &kp B &kp C>;
+};
+
+&mkp_input_listener {
+ input-processors = <&zip_button_behaviors>;
+};
+
+/ {
+ keymap {
+ compatible = "zmk,keymap";
+ label ="Default keymap";
+
+ default_layer {
+ bindings = <
+ &mkp LCLK &mkp RCLK
+ &mkp MCLK &none
+ >;
+ };
+ };
+};
+
+
+&kscan {
+ events = <
+ ZMK_MOCK_PRESS(0,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ ZMK_MOCK_PRESS(0,1,10)
+ ZMK_MOCK_RELEASE(0,1,10)
+ ZMK_MOCK_PRESS(1,0,10)
+ ZMK_MOCK_RELEASE(1,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/events.patterns b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/events.patterns
new file mode 100644
index 0000000000..7374badf0f
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/events.patterns
@@ -0,0 +1,2 @@
+s/.*hid_mouse_//p
+s/.*hid_listener_keycode_//p \ No newline at end of file
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/keycode_events.snapshot
new file mode 100644
index 0000000000..0712fa120c
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/keycode_events.snapshot
@@ -0,0 +1,16 @@
+pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
+pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
+released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.conf
new file mode 100644
index 0000000000..fa514727ba
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.conf
@@ -0,0 +1,6 @@
+CONFIG_GPIO=n
+CONFIG_ZMK_BLE=n
+CONFIG_LOG=y
+CONFIG_LOG_BACKEND_SHOW_COLOR=n
+CONFIG_ZMK_LOG_LEVEL_DBG=y
+CONFIG_ZMK_POINTING=y
diff --git a/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.keymap
new file mode 100644
index 0000000000..eb1d8f0279
--- /dev/null
+++ b/app/tests/pointing/mouse-move/processors/behaviors_hold_tap/native_posix_64.keymap
@@ -0,0 +1,54 @@
+
+#include <dt-bindings/zmk/input_transform.h>
+#include <zephyr/dt-bindings/input/input-event-codes.h>
+
+#include <behaviors.dtsi>
+#include <input/processors.dtsi>
+#include <dt-bindings/zmk/keys.h>
+#include <dt-bindings/zmk/kscan_mock.h>
+#include <dt-bindings/zmk/pointing.h>
+
+
+&zip_button_behaviors {
+ bindings = <&mt LCTL A &mt LSHFT B &mt LALT C>;
+};
+
+&mkp_input_listener {
+ input-processors = <&zip_button_behaviors>;
+};
+
+/ {
+ keymap {
+ compatible = "zmk,keymap";
+ label ="Default keymap";
+
+ default_layer {
+ bindings = <
+ &mkp LCLK &mkp RCLK
+ &mkp MCLK &none
+ >;
+ };
+ };
+};
+
+
+&kscan {
+ events = <
+ ZMK_MOCK_PRESS(0,0,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ ZMK_MOCK_PRESS(0,1,10)
+ ZMK_MOCK_RELEASE(0,1,10)
+ ZMK_MOCK_PRESS(1,0,10)
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_PRESS(0,0,500)
+ ZMK_MOCK_RELEASE(0,0,10)
+ ZMK_MOCK_PRESS(0,1,500)
+ ZMK_MOCK_RELEASE(0,1,10)
+ ZMK_MOCK_PRESS(1,0,500)
+ ZMK_MOCK_RELEASE(1,0,10)
+ ZMK_MOCK_PRESS(0,0,10)
+ ZMK_MOCK_PRESS(0,1,10)
+ ZMK_MOCK_RELEASE(0,1,10)
+ ZMK_MOCK_RELEASE(0,0,10)
+ >;
+}; \ No newline at end of file
diff --git a/docs/docs/keymaps/input-processors/behaviors.md b/docs/docs/keymaps/input-processors/behaviors.md
new file mode 100644
index 0000000000..a6244ed377
--- /dev/null
+++ b/docs/docs/keymaps/input-processors/behaviors.md
@@ -0,0 +1,81 @@
+---
+title: Behaviors Input Processor
+sidebar_label: Behaviors
+---
+
+## Overview
+
+The behaviors input processor is used invoke standard behaviors when certain input events occur; most frequently this is used to trigger behaviors when certain mouse buttons are triggered by physical pointing devices.
+
+:::note
+
+This input processor is primarily intended for `INPUT_EV_KEY` type of events that have a binary on/off state, not vector types for relative or absolute movements.
+
+:::
+
+:::note[Source-specific behaviors on split keyboards]
+Invoking a [source-specific behavior](../../features/split-keyboards.md#source-locality-behaviors) such as one of the [reset behaviors](../behaviors/reset.md) using this input processor will always trigger it on the central side of the keyboard, regardless of the side includes the input device that originally generated the input event.
+:::
+
+## Usage
+
+When used, this input processor takes no parameters, as the event code to behavior mapping is specified in the definition of the specific processor instance, e.g.:
+
+```dts
+&zip_button_behaviors
+```
+
+## Pre-Defined Instances
+
+One pre-defined instance of the out-of-band behaviors input processor is available:
+
+| Reference | Description |
+| ----------------------- | -------------------------------------------------- |
+| `&zip_button_behaviors` | Maps left/right/middle clicks to a given behavior. |
+
+Should you wish to update the existing instance to trigger different behaviors for each mouse button, you can override the `bindings` property, e.g.:
+
+```dts
+&zip_button_behaviors {
+ bindings = <&kp A &kp B &kp C>;
+};
+```
+
+By default, the `bindings` property maps all the buttons to [`&none`](../behaviors/misc.md#none), so you will want to override the `bindings` property like above if you use this processor by assigning it to an [input listener](usage.md).
+
+## User-Defined Instances
+
+Users can define new instances of the out-of-band behaviors input processor if they want to target different codes or assign different behaviors.
+
+### Example
+
+Below example maps the left mouse button code to the middle mouse button.
+
+```dts
+#include <zephyr/dt-bindings/input/input-event-codes.h>
+
+/ {
+ input_processors {
+ zip_right_click_trigger_paste: zip_right_click_trigger_paste {
+ compatible = "zmk,input-processor-behaviors";
+ #input-processor-cells = <0>;
+ codes = <INPUT_BTN_1>;
+ bindings = <&kp LC(V) >;
+ };
+ };
+}
+```
+
+### Compatible
+
+The behaviors input processor uses a `compatible` property of `"zmk,input-processor-behaviors"`.
+
+### Standard Properties
+
+- `#input-processor-cells` - required to be constant value of `<0>`.
+
+### User Properties
+
+- `type` - The [type](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L25) of events to scale. Usually, this is `INPUT_EV_KEY` for key/button events. The default value if omitted is `INPUT_EV_KEY`.
+- `codes` - The specific codes of the given type to capture, e.g. [button event codes](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L180). This list must be the same length as the `bindings` property.
+- `bindings` - The bindings to trigger when an event with the corresponding code is processed.
diff --git a/docs/docs/keymaps/input-processors/index.md b/docs/docs/keymaps/input-processors/index.md
index 489003b830..c854b5369e 100644
--- a/docs/docs/keymaps/input-processors/index.md
+++ b/docs/docs/keymaps/input-processors/index.md
@@ -25,25 +25,27 @@ A set of predefined input processors is available by adding the following at the
Once included, you can use the following:
-| Binding | Processor | Description |
-| -------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- |
-| `&zip_xy_scaler` | [XY Scaler](scaler.md#pre-defined-instances) | Scale a the X/Y input events using a multiplier and divisor |
-| `&zip_x_scaler` | [X Scaler](scaler.md#pre-defined-instances) | Scale a the X input events using a multiplier and divisor |
-| `&zip_y_scaler` | [Y Scaler](scaler.md#pre-defined-instances) | Scale a the Y input events using a multiplier and divisor |
-| `&zip_xy_transform` | [XY Transform](transformer.md#pre-defined-instances) | Transform X/Y values, e.g. inverting or swapping |
-| `&zip_scroll_transform` | [Scroll Transform](transformer.md#pre-defined-instances) | Transform wheel/horizontal wheel values, e.g. inverting or swapping |
-| `&zip_xy_to_scroll_mapper` | [XY To Scroll Mapper](code-mapper.md#pre-defined-instances) | Map X/Y values to scroll wheel/horizontal wheel events |
-| `&zip_xy_swap_mapper` | [XY Swap Mapper](code-mapper.md#pre-defined-instances) | Swap X/Y values |
-| `&zip_temp_layer` | [Temporary Layer](temp-layer.md#pre-defined-instances) | Temporarily enable a layer during pointer use |
+| Binding | Processor | Description |
+| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------- |
+| `&zip_xy_scaler` | [XY Scaler](scaler.md#pre-defined-instances) | Scale a the X/Y input events using a multiplier and divisor |
+| `&zip_x_scaler` | [X Scaler](scaler.md#pre-defined-instances) | Scale a the X input events using a multiplier and divisor |
+| `&zip_y_scaler` | [Y Scaler](scaler.md#pre-defined-instances) | Scale a the Y input events using a multiplier and divisor |
+| `&zip_xy_transform` | [XY Transform](transformer.md#pre-defined-instances) | Transform X/Y values, e.g. inverting or swapping |
+| `&zip_scroll_transform` | [Scroll Transform](transformer.md#pre-defined-instances) | Transform wheel/horizontal wheel values, e.g. inverting or swapping |
+| `&zip_xy_to_scroll_mapper` | [XY To Scroll Mapper](code-mapper.md#pre-defined-instances) | Map X/Y values to scroll wheel/horizontal wheel events |
+| `&zip_xy_swap_mapper` | [XY Swap Mapper](code-mapper.md#pre-defined-instances) | Swap X/Y values |
+| `&zip_temp_layer` | [Temporary Layer](temp-layer.md#pre-defined-instances) | Temporarily enable a layer during pointer use |
+| `&zip_button_behaviors` | [Mouse Button Behaviors](behaviors.md#pre-defined-instances) | Trigger behaviors when certain mouse buttons are pressed |
### User-Defined Processors
Several of the input processors that have predefined instances, e.g. `&zip_xy_scaler` or `&zip_xy_to_scroll_mapper` can also have new instances created with custom properties around which input codes to scale, or which codes to map, etc.
-| Compatible | Processor | Description |
-| --------------------------------- | ---------------------------------------------------- | ------------------------------------------------ |
-| `zmk,input-processor-transform` | [Transform](transformer.md#user-defined-instances) | Perform various transforms like inverting values |
-| `zmk,input-processor-code-mapper` | [Code Mapper](code-mapper.md#user-defined-instances) | Map one event code to another type |
+| Compatible | Processor | Description |
+| --------------------------------- | ---------------------------------------------------- | --------------------------------------------------- |
+| `zmk,input-processor-transform` | [Transform](transformer.md#user-defined-instances) | Perform various transforms like inverting values |
+| `zmk,input-processor-code-mapper` | [Code Mapper](code-mapper.md#user-defined-instances) | Map one event code to another type |
+| `zmk,input-processor-behaviors` | [Behaviors](behaviors.md#user-defined-instances) | Trigger behaviors for certain matching input events |
## External Processors