From b466913009110289a68bf4a63c393247e4fa24f4 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sat, 29 Mar 2025 00:44:11 -0600 Subject: [PATCH] feat(core): (Optionally) use Zephyr keyboard input devices Add ability to assign a keyboard `input` device to a physical layout, or use a chosen `zmk,matrix-input`. --- app/dts/bindings/zmk,physical-layout.yaml | 3 + app/include/zmk/physical_layouts.h | 3 + app/module/drivers/input/input_mock.c | 9 +- app/src/physical_layouts.c | 92 ++++++++++++++++++- .../kp-press-release/events.patterns | 1 + .../kp-press-release/keycode_events.snapshot | 2 + .../kp-press-release/native_sim.conf | 1 + .../kp-press-release/native_sim.keymap | 60 ++++++++++++ 8 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 app/tests/matrix-input/kp-press-release/events.patterns create mode 100644 app/tests/matrix-input/kp-press-release/keycode_events.snapshot create mode 100644 app/tests/matrix-input/kp-press-release/native_sim.conf create mode 100644 app/tests/matrix-input/kp-press-release/native_sim.keymap diff --git a/app/dts/bindings/zmk,physical-layout.yaml b/app/dts/bindings/zmk,physical-layout.yaml index 3f9b8c244..7ed4cdc3d 100644 --- a/app/dts/bindings/zmk,physical-layout.yaml +++ b/app/dts/bindings/zmk,physical-layout.yaml @@ -21,6 +21,9 @@ properties: kscan: type: phandle description: The kscan to use along with this layout. The `zmk,kscan` chosen will be used as a fallback if this property is omitted. + input: + type: phandle + description: The input device to use along with this layout. The `zmk,matrix-input` chosen will be used as a fallback if this property is omitted. keys: type: phandle-array description: Array of key physical attributes. diff --git a/app/include/zmk/physical_layouts.h b/app/include/zmk/physical_layouts.h index d5cf65273..e28c194fe 100644 --- a/app/include/zmk/physical_layouts.h +++ b/app/include/zmk/physical_layouts.h @@ -33,6 +33,9 @@ struct zmk_physical_layout { zmk_matrix_transform_t matrix_transform; const struct device *kscan; +#if IS_ENABLED(CONFIG_INPUT) + const struct device *input; +#endif const struct zmk_key_physical_attrs *keys; size_t keys_len; diff --git a/app/module/drivers/input/input_mock.c b/app/module/drivers/input/input_mock.c index 855b7dc37..23282cd04 100644 --- a/app/module/drivers/input/input_mock.c +++ b/app/module/drivers/input/input_mock.c @@ -48,10 +48,15 @@ static void input_mock_work_cb(struct k_work *work) { } } + bool sync = cfg->events[base_idx + 3]; input_report(dev, cfg->events[base_idx], cfg->events[base_idx + 1], cfg->events[base_idx + 2], - cfg->events[base_idx + 3], K_NO_WAIT); + sync, K_NO_WAIT); - k_work_schedule(&data->work, K_MSEC(cfg->event_period)); + if (sync) { + k_work_schedule(&data->work, K_MSEC(cfg->event_period)); + } else { + k_work_schedule(&data->work, K_NO_WAIT); + } } int input_mock_init(const struct device *dev) { diff --git a/app/src/physical_layouts.c b/app/src/physical_layouts.c index 8d3843088..15268b941 100644 --- a/app/src/physical_layouts.c +++ b/app/src/physical_layouts.c @@ -9,6 +9,7 @@ #include #include #include +#include #if IS_ENABLED(CONFIG_SETTINGS) #include @@ -27,6 +28,16 @@ ZMK_EVENT_IMPL(zmk_physical_layout_selection_changed); #define DT_DRV_COMPAT zmk_physical_layout +#define MATRIX_INPUT_SUPPORT \ + UTIL_AND(IS_ENABLED(CONFIG_INPUT), \ + UTIL_OR(DT_ANY_INST_HAS_PROP_STATUS_OKAY(input), DT_HAS_CHOSEN(zmk_matrix_input))) + +#if MATRIX_INPUT_SUPPORT + +static void zmk_physical_layout_input_event_cb(struct input_event *evt, void *user_data); + +#endif + #define USE_PHY_LAYOUTS \ (DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) && !DT_HAS_CHOSEN(zmk_matrix_transform)) @@ -53,6 +64,10 @@ BUILD_ASSERT( ()) \ } +#define INPUT_FOR_INST(n) \ + DEVICE_DT_GET(COND_CODE_1(DT_INST_PROP_LEN(n, input), (DT_INST_PHANDLE(n, input)), \ + (DT_CHOSEN(zmk_matrix_input)))) + #define ZMK_LAYOUT_INST(n) \ BUILD_ASSERT(!IS_ENABLED(CONFIG_ZMK_STUDIO) || DT_INST_NODE_HAS_PROP(n, keys), \ "ZMK Studio requires physical layouts with key positions. See " \ @@ -67,8 +82,18 @@ BUILD_ASSERT( .matrix_transform = ZMK_MATRIX_TRANSFORM_T_FOR_NODE(DT_INST_PHANDLE(n, transform)), \ .keys = _CONCAT(_zmk_physical_layout_keys_, n), \ .keys_len = DT_INST_PROP_LEN_OR(n, keys, 0), \ - .kscan = DEVICE_DT_GET(COND_CODE_1(DT_INST_PROP_LEN(n, kscan), \ - (DT_INST_PHANDLE(n, kscan)), (DT_CHOSEN(zmk_kscan))))}; + COND_CODE_1(UTIL_AND(MATRIX_INPUT_SUPPORT, DT_INST_PROP_LEN(n, input)), \ + (.input = INPUT_FOR_INST(n)), ()) \ + COND_CODE_1(UTIL_OR(DT_HAS_CHOSEN(zmk_kscan), DT_INST_PROP_LEN(n, kscan)), \ + (.kscan = DEVICE_DT_GET(COND_CODE_1(DT_INST_PROP_LEN(n, kscan), \ + (DT_INST_PHANDLE(n, kscan)), \ + (DT_CHOSEN(zmk_kscan))))), \ + ())}; \ + COND_CODE_1( \ + UTIL_AND(MATRIX_INPUT_SUPPORT, DT_INST_PROP_LEN(n, input)), \ + (INPUT_CALLBACK_DEFINE(INPUT_FOR_INST(n), zmk_physical_layout_input_event_cb, \ + (void *)&(_CONCAT(_zmk_physical_layout_, DT_DRV_INST(n))));), \ + ()) DT_INST_FOREACH_STATUS_OKAY(ZMK_LAYOUT_INST) @@ -135,12 +160,12 @@ static const struct zmk_physical_layout _CONCAT(_zmk_physical_layout_, chosen) = static const struct zmk_physical_layout *const layouts[] = { &_CONCAT(_zmk_physical_layout_, chosen)}; -#elif DT_HAS_CHOSEN(zmk_kscan) +#elif UTIL_OR(DT_HAS_CHOSEN(zmk_kscan), DT_HAS_CHOSEN(zmk_matrix_input)) #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) #warning \ - "Ignoring the physical layouts and using the chosen kscan with a synthetic transform. Consider setting a chosen physical layout instead." + "Ignoring the physical layouts and using the chosen kscan/matrix-input with a synthetic transform. Consider setting a chosen physical layout instead." #endif @@ -148,9 +173,19 @@ ZMK_MATRIX_TRANSFORM_DEFAULT_EXTERN(); static const struct zmk_physical_layout _CONCAT(_zmk_physical_layout_, chosen) = { .display_name = "Default", .matrix_transform = &zmk_matrix_transform_default, +#if DT_HAS_CHOSEN(zmk_matrix_input) + .input = DEVICE_DT_GET(DT_CHOSEN(zmk_matrix_input)), +#elif DT_HAS_CHOSEN(zmk_kscan) .kscan = DEVICE_DT_GET(DT_CHOSEN(zmk_kscan)), +#endif }; +#if DT_HAS_CHOSEN(zmk_matrix_input) +INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_CHOSEN(zmk_matrix_input)), + zmk_physical_layout_input_event_cb, + (void *)&(_CONCAT(_zmk_physical_layout_, chosen))); +#endif + static const struct zmk_physical_layout *const layouts[] = { &_CONCAT(_zmk_physical_layout_, chosen)}; @@ -180,6 +215,55 @@ static struct zmk_kscan_msg_processor { K_MSGQ_DEFINE(physical_layouts_kscan_msgq, sizeof(struct zmk_kscan_event), CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4); +#if MATRIX_INPUT_SUPPORT + +static struct zmk_kscan_event pending_input_event; + +static void zmk_physical_layout_input_event_cb(struct input_event *evt, void *user_data) { + const struct zmk_physical_layout *layout = (const struct zmk_physical_layout *)user_data; + if (layout != active) { + LOG_WRN("Ignoring input event from non-active layout"); + return; + } + + switch (evt->type) { + case INPUT_EV_ABS: + switch (evt->code) { + case INPUT_ABS_X: + pending_input_event.column = evt->value; + break; + case INPUT_ABS_Y: + pending_input_event.row = evt->value; + break; + default: + LOG_WRN("Unknown abs code"); + return; + } + break; + case INPUT_EV_KEY: + switch (evt->code) { + case INPUT_BTN_TOUCH: + pending_input_event.state = + (evt->value ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED); + break; + default: + LOG_WRN("Unknown key code"); + return; + } + break; + default: + LOG_WRN("Unknown type"); + return; + } + + if (evt->sync) { + k_msgq_put(&physical_layouts_kscan_msgq, &pending_input_event, K_NO_WAIT); + k_work_submit(&msg_processor.work); + } +} + +#endif + static void zmk_physical_layout_kscan_callback(const struct device *dev, uint32_t row, uint32_t column, bool pressed) { if (dev != active->kscan) { diff --git a/app/tests/matrix-input/kp-press-release/events.patterns b/app/tests/matrix-input/kp-press-release/events.patterns new file mode 100644 index 000000000..833100f6a --- /dev/null +++ b/app/tests/matrix-input/kp-press-release/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/matrix-input/kp-press-release/keycode_events.snapshot b/app/tests/matrix-input/kp-press-release/keycode_events.snapshot new file mode 100644 index 000000000..259501ba3 --- /dev/null +++ b/app/tests/matrix-input/kp-press-release/keycode_events.snapshot @@ -0,0 +1,2 @@ +pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/matrix-input/kp-press-release/native_sim.conf b/app/tests/matrix-input/kp-press-release/native_sim.conf new file mode 100644 index 000000000..9ce5b96b3 --- /dev/null +++ b/app/tests/matrix-input/kp-press-release/native_sim.conf @@ -0,0 +1 @@ +CONFIG_INPUT=y \ No newline at end of file diff --git a/app/tests/matrix-input/kp-press-release/native_sim.keymap b/app/tests/matrix-input/kp-press-release/native_sim.keymap new file mode 100644 index 000000000..14e82d13f --- /dev/null +++ b/app/tests/matrix-input/kp-press-release/native_sim.keymap @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +/delete-node/ &kscan; + +/ { + physical_layout: physical_layout { + display-name = "Layout"; + compatible = "zmk,physical-layout"; + input = <&mock_matrix_input>; + transform = <&matrix_transform0>; + }; + + chosen { + /delete-property/ zmk,kscan; + + // zmk,matrix-input = &mock_matrix_input; + }; + + matrix_transform0: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <20>; + rows = <5>; + + + map = < + RC(0,0) RC(0,1) + RC(1,0) RC(1,1) + >; + }; + + mock_matrix_input: mock_matrix_input { + compatible = "zmk,input-mock"; + + event-startup-delay = <100>; + event-period = <2000>; + events + = + , + , + , + , + , + ; + exit-after; + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp B &kp C + &kp D &kp E + >; + }; + }; +};