diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index e8ff7293..9edbe9c3 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -27,7 +27,6 @@ target_sources(app PRIVATE src/behavior.c)
 target_sources_ifdef(CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS app PRIVATE src/kscan_sideband_behaviors.c)
 target_sources(app PRIVATE src/matrix_transform.c)
 target_sources(app PRIVATE src/physical_layouts.c)
-target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/input_config.c)
 target_sources(app PRIVATE src/sensors.c)
 target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c)
 target_sources(app PRIVATE src/event_manager.c)
@@ -44,7 +43,7 @@ target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext
 target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SOFT_OFF app PRIVATE src/behaviors/behavior_soft_off.c)
 if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
   target_sources(app PRIVATE src/hid.c)
-  target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/hid_input_listener.c)
+  target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/input_listener.c)
   target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
   target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c)
   target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)
diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi
index 27bc78a6..6695af40 100644
--- a/app/dts/behaviors.dtsi
+++ b/app/dts/behaviors.dtsi
@@ -21,6 +21,4 @@
 #include <behaviors/macros.dtsi>
 #include <behaviors/mouse_key_press.dtsi>
 #include <behaviors/soft_off.dtsi>
-#include <behaviors/mouse_move.dtsi>
-#include <behaviors/mouse_scroll.dtsi>
 #include <behaviors/macros.dtsi>
diff --git a/app/dts/behaviors/mouse_move.dtsi b/app/dts/behaviors/mouse_move.dtsi
index 0bfc87d0..c43455c3 100644
--- a/app/dts/behaviors/mouse_move.dtsi
+++ b/app/dts/behaviors/mouse_move.dtsi
@@ -11,4 +11,9 @@
             acceleration-exponent = <1>;
         };
     };
+
+    mmv_input_listener {
+        compatible = "zmk,input-listener";
+        device = <&mmv>;
+    };
 };
diff --git a/app/dts/behaviors/mouse_scroll.dtsi b/app/dts/behaviors/mouse_scroll.dtsi
index 69c48fee..27b6aafe 100644
--- a/app/dts/behaviors/mouse_scroll.dtsi
+++ b/app/dts/behaviors/mouse_scroll.dtsi
@@ -11,4 +11,9 @@
             acceleration-exponent = <0>;
         };
     };
+
+    msc_input_listener {
+        compatible = "zmk,input-listener";
+        device = <&msc>;
+    };
 };
diff --git a/app/dts/bindings/zmk,input-configs.yaml b/app/dts/bindings/zmk,input-configs.yaml
deleted file mode 100644
index 83bd4a19..00000000
--- a/app/dts/bindings/zmk,input-configs.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-description: |
-  Allows post-processing of input events based on the configuration
-
-compatible: "zmk,input-configs"
-
-child-binding:
-  description: "A configuration for a given input device"
-
-  properties:
-    device:
-      type: phandle
-      required: true
-    xy-swap:
-      type: boolean
-    x-invert:
-      type: boolean
-    y-invert:
-      type: boolean
-    scale-multiplier:
-      type: int
-      default: 1
-    scale-divisor:
-      type: int
-      default: 1
diff --git a/app/dts/bindings/zmk,input-listener.yaml b/app/dts/bindings/zmk,input-listener.yaml
new file mode 100644
index 00000000..a883557d
--- /dev/null
+++ b/app/dts/bindings/zmk,input-listener.yaml
@@ -0,0 +1,21 @@
+description: |
+  Listener to subscribe to input events and send HID updates after processing
+
+compatible: "zmk,input-listener"
+
+properties:
+  device:
+    type: phandle
+    required: true
+  xy-swap:
+    type: boolean
+  x-invert:
+    type: boolean
+  y-invert:
+    type: boolean
+  scale-multiplier:
+    type: int
+    default: 1
+  scale-divisor:
+    type: int
+    default: 1
diff --git a/app/include/zmk/mouse/input_config.h b/app/include/zmk/mouse/input_config.h
deleted file mode 100644
index 0a37b346..00000000
--- a/app/include/zmk/mouse/input_config.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (c) 2023 The ZMK Contributors
- *
- * SPDX-License-Identifier: MIT
- */
-
-#pragma once
-
-#include <zephyr/kernel.h>
-#include <zephyr/device.h>
-
-struct zmk_input_config {
-    const struct device *dev;
-    bool xy_swap;
-    bool x_invert;
-    bool y_invert;
-    uint16_t scale_multiplier;
-    uint16_t scale_divisor;
-};
-
-const struct zmk_input_config *zmk_input_config_get_for_device(const struct device *dev);
\ No newline at end of file
diff --git a/app/src/mouse/hid_input_listener.c b/app/src/mouse/hid_input_listener.c
deleted file mode 100644
index 712bb567..00000000
--- a/app/src/mouse/hid_input_listener.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (c) 2020 The ZMK Contributors
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <zephyr/kernel.h>
-#include <zephyr/input/input.h>
-#include <zephyr/dt-bindings/input/input-event-codes.h>
-
-#include <zmk/mouse.h>
-#include <zmk/mouse/input_config.h>
-#include <zmk/endpoints.h>
-#include <zmk/hid.h>
-
-void handle_rel_code(struct input_event *evt) {
-    switch (evt->code) {
-    case INPUT_REL_X:
-        zmk_hid_mouse_movement_update(evt->value, 0);
-        break;
-    case INPUT_REL_Y:
-        zmk_hid_mouse_movement_update(0, evt->value);
-        break;
-    case INPUT_REL_WHEEL:
-        zmk_hid_mouse_scroll_update(0, evt->value);
-        break;
-    case INPUT_REL_HWHEEL:
-        zmk_hid_mouse_scroll_update(evt->value, 0);
-        break;
-    default:
-        break;
-    }
-}
-
-void handle_key_code(struct input_event *evt) {
-    int8_t btn;
-
-    switch (evt->code) {
-    case INPUT_BTN_0:
-    case INPUT_BTN_1:
-    case INPUT_BTN_2:
-    case INPUT_BTN_3:
-    case INPUT_BTN_4:
-        btn = evt->code - INPUT_BTN_0;
-        if (evt->value > 0) {
-            zmk_hid_mouse_button_press(btn);
-        } else {
-            zmk_hid_mouse_button_release(btn);
-        }
-        break;
-    default:
-        break;
-    }
-}
-
-static void swap_xy(struct input_event *evt) {
-    switch (evt->code) {
-    case INPUT_REL_X:
-        evt->code = INPUT_REL_Y;
-        break;
-    case INPUT_REL_Y:
-        evt->code = INPUT_REL_X;
-        break;
-    }
-}
-
-static void filter_with_input_config(struct input_event *evt) {
-    if (!evt->dev) {
-        return;
-    }
-
-    const struct zmk_input_config *cfg = zmk_input_config_get_for_device(evt->dev);
-
-    if (!cfg) {
-        return;
-    }
-
-    if (cfg->xy_swap) {
-        swap_xy(evt);
-    }
-
-    if ((cfg->x_invert && evt->code == INPUT_REL_X) ||
-        (cfg->y_invert && evt->code == INPUT_REL_Y)) {
-        evt->value = -(evt->value);
-    }
-
-    evt->value = (int16_t)((evt->value * cfg->scale_multiplier) / cfg->scale_divisor);
-}
-
-void input_handler(struct input_event *evt) {
-    // First, filter to update the event data as needed.
-    filter_with_input_config(evt);
-
-    switch (evt->type) {
-    case INPUT_EV_REL:
-        handle_rel_code(evt);
-        break;
-    case INPUT_EV_KEY:
-        handle_key_code(evt);
-        break;
-    }
-
-    if (evt->sync) {
-        zmk_endpoints_send_mouse_report();
-        zmk_hid_mouse_scroll_set(0, 0);
-        zmk_hid_mouse_movement_set(0, 0);
-    }
-}
-
-INPUT_CALLBACK_DEFINE(NULL, input_handler);
diff --git a/app/src/mouse/input_config.c b/app/src/mouse/input_config.c
deleted file mode 100644
index 745fb49b..00000000
--- a/app/src/mouse/input_config.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (c) 2023 The ZMK Contributors
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <zmk/mouse/input_config.h>
-#include <zephyr/device.h>
-
-#define DT_DRV_COMPAT zmk_input_configs
-
-#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
-
-#define CHILD_CONFIG(inst)                                                                         \
-    {                                                                                              \
-        .dev = DEVICE_DT_GET(DT_PHANDLE(inst, device)),                                            \
-        .xy_swap = DT_PROP(inst, xy_swap),                                                         \
-        .x_invert = DT_PROP(inst, x_invert),                                                       \
-        .y_invert = DT_PROP(inst, y_invert),                                                       \
-        .scale_multiplier = DT_PROP(inst, scale_multiplier),                                       \
-        .scale_divisor = DT_PROP(inst, scale_divisor),                                             \
-    },
-
-const struct zmk_input_config configs[] = {DT_INST_FOREACH_CHILD(0, CHILD_CONFIG)};
-
-const struct zmk_input_config *zmk_input_config_get_for_device(const struct device *dev) {
-    for (int i = 0; i < ARRAY_SIZE(configs); i++) {
-        if (configs[i].dev == dev) {
-            return &configs[i];
-        }
-    }
-
-    return NULL;
-}
-
-#else
-
-const struct zmk_input_config *zmk_input_config_get_for_device(const struct device *dev) {
-    return NULL;
-}
-
-#endif
\ No newline at end of file
diff --git a/app/src/mouse/input_listener.c b/app/src/mouse/input_listener.c
new file mode 100644
index 00000000..a00455bc
--- /dev/null
+++ b/app/src/mouse/input_listener.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_input_listener
+
+#include <zephyr/device.h>
+#include <zephyr/kernel.h>
+#include <zephyr/input/input.h>
+#include <zephyr/dt-bindings/input/input-event-codes.h>
+
+#include <zmk/mouse.h>
+#include <zmk/endpoints.h>
+#include <zmk/hid.h>
+
+enum input_listener_xy_data_mode {
+    INPUT_LISTENER_XY_DATA_MODE_NONE,
+    INPUT_LISTENER_XY_DATA_MODE_REL,
+    INPUT_LISTENER_XY_DATA_MODE_ABS,
+};
+
+struct input_listener_xy_data {
+    enum input_listener_xy_data_mode mode;
+    int16_t x;
+    int16_t y;
+};
+
+struct input_listener_data {
+    struct input_listener_xy_data data;
+    struct input_listener_xy_data wheel_data;
+
+    uint8_t button_set;
+    uint8_t button_clear;
+};
+
+struct input_listener_config {
+    bool xy_swap;
+    bool x_invert;
+    bool y_invert;
+    uint16_t scale_multiplier;
+    uint16_t scale_divisor;
+};
+
+static void handle_rel_code(struct input_listener_data *data, struct input_event *evt) {
+    switch (evt->code) {
+    case INPUT_REL_X:
+        data->data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
+        data->data.x += evt->value;
+        break;
+    case INPUT_REL_Y:
+        data->data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
+        data->data.y += evt->value;
+        break;
+    case INPUT_REL_WHEEL:
+        data->wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
+        data->wheel_data.y += evt->value;
+        break;
+    case INPUT_REL_HWHEEL:
+        data->wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
+        data->wheel_data.x += evt->value;
+        break;
+    default:
+        break;
+    }
+}
+
+static void handle_key_code(struct input_listener_data *data, struct input_event *evt) {
+    int8_t btn;
+
+    switch (evt->code) {
+    case INPUT_BTN_0:
+    case INPUT_BTN_1:
+    case INPUT_BTN_2:
+    case INPUT_BTN_3:
+    case INPUT_BTN_4:
+        btn = evt->code - INPUT_BTN_0;
+        if (evt->value > 0) {
+            WRITE_BIT(data->button_set, btn, 1);
+        } else {
+            WRITE_BIT(data->button_clear, btn, 1);
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static void swap_xy(struct input_event *evt) {
+    switch (evt->code) {
+    case INPUT_REL_X:
+        evt->code = INPUT_REL_Y;
+        break;
+    case INPUT_REL_Y:
+        evt->code = INPUT_REL_X;
+        break;
+    }
+}
+
+static void filter_with_input_config(const struct input_listener_config *cfg,
+                                     struct input_event *evt) {
+    if (!evt->dev) {
+        return;
+    }
+
+    if (cfg->xy_swap) {
+        swap_xy(evt);
+    }
+
+    if ((cfg->x_invert && evt->code == INPUT_REL_X) ||
+        (cfg->y_invert && evt->code == INPUT_REL_Y)) {
+        evt->value = -(evt->value);
+    }
+
+    evt->value = (int16_t)((evt->value * cfg->scale_multiplier) / cfg->scale_divisor);
+}
+
+static void clear_xy_data(struct input_listener_xy_data *data) {
+    data->x = data->y = 0;
+    data->mode = INPUT_LISTENER_XY_DATA_MODE_NONE;
+}
+
+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, evt);
+
+    switch (evt->type) {
+    case INPUT_EV_REL:
+        handle_rel_code(data, evt);
+        break;
+    case INPUT_EV_KEY:
+        handle_key_code(data, evt);
+        break;
+    }
+
+    if (evt->sync) {
+        if (data->wheel_data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) {
+            zmk_hid_mouse_scroll_set(data->wheel_data.x, data->wheel_data.y);
+        }
+
+        if (data->data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) {
+            zmk_hid_mouse_movement_set(data->data.x, data->data.y);
+        }
+
+        if (data->button_set != 0) {
+            for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) {
+                if ((data->button_set & BIT(i)) != 0) {
+                    zmk_hid_mouse_button_press(i);
+                }
+            }
+        }
+
+        if (data->button_clear != 0) {
+            for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) {
+                if ((data->button_set & BIT(i)) != 0) {
+                    zmk_hid_mouse_button_release(i);
+                }
+            }
+        }
+
+        zmk_endpoints_send_mouse_report();
+        zmk_hid_mouse_scroll_set(0, 0);
+        zmk_hid_mouse_movement_set(0, 0);
+
+        clear_xy_data(&data->data);
+        clear_xy_data(&data->wheel_data);
+
+        data->button_set = data->button_clear = 0;
+    }
+}
+
+#define IL_INST(n)                                                                                 \
+    static const struct input_listener_config config_##n = {                                       \
+        .xy_swap = DT_INST_PROP(n, xy_swap),                                                       \
+        .x_invert = DT_INST_PROP(n, x_invert),                                                     \
+        .y_invert = DT_INST_PROP(n, y_invert),                                                     \
+        .scale_multiplier = DT_INST_PROP(n, scale_multiplier),                                     \
+        .scale_divisor = DT_INST_PROP(n, scale_divisor),                                           \
+    };                                                                                             \
+    static struct input_listener_data data_##n = {};                                               \
+    void input_handler_##n(struct input_event *evt) {                                              \
+        input_handler(&config_##n, &data_##n, evt);                                                \
+    }                                                                                              \
+    INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(n, device)), input_handler_##n);
+
+DT_INST_FOREACH_STATUS_OKAY(IL_INST)