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`.
This commit is contained in:
Peter Johanson 2025-03-29 00:44:11 -06:00
parent 45aef02581
commit b466913009
8 changed files with 165 additions and 6 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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) {

View File

@ -9,6 +9,7 @@
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/input/input.h>
#if IS_ENABLED(CONFIG_SETTINGS)
#include <zephyr/settings/settings.h>
@ -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) {

View File

@ -0,0 +1 @@
s/.*hid_listener_keycode_//p

View File

@ -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

View File

@ -0,0 +1 @@
CONFIG_INPUT=y

View File

@ -0,0 +1,60 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include <dt-bindings/zmk/matrix_transform.h>
/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
= <INPUT_EV_ABS INPUT_ABS_X 0 0>
, <INPUT_EV_ABS INPUT_ABS_Y 0 0>
, <INPUT_EV_KEY INPUT_BTN_TOUCH 1 1>
, <INPUT_EV_ABS INPUT_ABS_X 0 0>
, <INPUT_EV_ABS INPUT_ABS_Y 0 0>
, <INPUT_EV_KEY INPUT_BTN_TOUCH 0 1>
;
exit-after;
};
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp B &kp C
&kp D &kp E
>;
};
};
};