feat: Add keyboard physical layout system.
* Add bindings to allow creating multiple physical layouts that specify their key's physical attributes, and the matching matrix transform and dependant kscan to use. * Synthesize a basic physical layout if none specified, for backwards compatibility. * Update matrix transform API to explicitly pass in the selected transform to the API instead of using a fixed chosen transform. * Move kscan subscription and handling into the physical layout code, so that selecting a different physical layout at runtime can also use the correct kscan instance. * Add `physical_layouts.dtsi` file to include so you can use the pre-configured `&key_physical_attrs` for adding you layout keys.
This commit is contained in:
parent
80173f8ea3
commit
c5cca5b34f
13 changed files with 604 additions and 141 deletions
|
@ -24,9 +24,9 @@ target_include_directories(app PRIVATE include)
|
|||
target_sources(app PRIVATE src/stdlib.c)
|
||||
target_sources(app PRIVATE src/activity.c)
|
||||
target_sources(app PRIVATE src/behavior.c)
|
||||
target_sources(app PRIVATE src/kscan.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(app PRIVATE src/sensors.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c)
|
||||
target_sources(app PRIVATE src/event_manager.c)
|
||||
|
|
24
app/dts/bindings/zmk,key-physical-attrs.yaml
Normal file
24
app/dts/bindings/zmk,key-physical-attrs.yaml
Normal file
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Copyright (c) 2024 The ZMK Contributors
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
description: |
|
||||
The physical attributes of a key, including size, location, and rotation
|
||||
|
||||
compatible: "zmk,key-physical-attrs"
|
||||
|
||||
properties:
|
||||
"#key-cells":
|
||||
type: int
|
||||
required: true
|
||||
const: 7
|
||||
|
||||
key-cells:
|
||||
- width
|
||||
- height
|
||||
- x
|
||||
- y
|
||||
- r
|
||||
- rx
|
||||
- ry
|
23
app/dts/bindings/zmk,physical-layout-position-map.yaml
Normal file
23
app/dts/bindings/zmk,physical-layout-position-map.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
#
|
||||
# Copyright (c) 2024 The ZMK Contributors
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
description: |
|
||||
Describes how to correlate equivalent keys between layouts that don't have the exact same X,Y location.
|
||||
|
||||
compatible: "zmk,physical-layout-position-map"
|
||||
|
||||
properties:
|
||||
complete:
|
||||
type: boolean
|
||||
description: If the mapping complete describes the key mapping, and no position based mapping should be used.
|
||||
|
||||
child-binding:
|
||||
properties:
|
||||
physical-layout:
|
||||
type: phandle
|
||||
description: The physical layout that corresponds to this mapping entry.
|
||||
positions:
|
||||
type: array
|
||||
description: Array of key positions that match the same array entry in the other sibling nodes.
|
26
app/dts/bindings/zmk,physical-layout.yaml
Normal file
26
app/dts/bindings/zmk,physical-layout.yaml
Normal file
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# Copyright (c) 2024 The ZMK Contributors
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
description: |
|
||||
Describe the physical layout of a keyboard, including deps like the transform and kscan
|
||||
that are needed for that layout to work.
|
||||
|
||||
compatible: "zmk,physical-layout"
|
||||
|
||||
properties:
|
||||
display-name:
|
||||
type: string
|
||||
required: true
|
||||
description: The name of this layout to display in the UI
|
||||
transform:
|
||||
type: phandle
|
||||
required: true
|
||||
description: The matrix transform to use along with this layout.
|
||||
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.
|
||||
keys:
|
||||
type: phandle-array
|
||||
description: Array of key physical attributes.
|
13
app/dts/physical_layouts.dtsi
Normal file
13
app/dts/physical_layouts.dtsi
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/ {
|
||||
key_physical_attrs: key_physical_attrs {
|
||||
compatible = "zmk,key-physical-attrs";
|
||||
|
||||
#key-cells = <7>;
|
||||
};
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/device.h>
|
||||
|
||||
int zmk_kscan_init(const struct device *dev);
|
|
@ -9,15 +9,25 @@
|
|||
#include <zephyr/devicetree.h>
|
||||
|
||||
#define ZMK_MATRIX_NODE_ID DT_CHOSEN(zmk_kscan)
|
||||
#define ZMK_MATRIX_HAS_TRANSFORM DT_HAS_CHOSEN(zmk_matrix_transform)
|
||||
|
||||
#if DT_HAS_CHOSEN(zmk_matrix_transform)
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(zmk_physical_layout)
|
||||
|
||||
#if ZMK_MATRIX_HAS_TRANSFORM
|
||||
#error "To use physical layouts, remove the chosen `zmk,matrix-transform` value."
|
||||
#endif
|
||||
|
||||
#define ZMK_PHYSICAL_LAYOUT_BYTE_ARRAY(node_id) \
|
||||
uint8_t _CONCAT(prop_, node_id)[DT_PROP_LEN(DT_PHANDLE(node_id, transform), map)];
|
||||
|
||||
#define ZMK_KEYMAP_LEN \
|
||||
sizeof(union {DT_FOREACH_STATUS_OKAY(zmk_physical_layout, ZMK_PHYSICAL_LAYOUT_BYTE_ARRAY)})
|
||||
|
||||
#elif ZMK_MATRIX_HAS_TRANSFORM
|
||||
|
||||
#define ZMK_KEYMAP_TRANSFORM_NODE DT_CHOSEN(zmk_matrix_transform)
|
||||
#define ZMK_KEYMAP_LEN DT_PROP_LEN(ZMK_KEYMAP_TRANSFORM_NODE, map)
|
||||
|
||||
#define ZMK_MATRIX_ROWS DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, rows)
|
||||
#define ZMK_MATRIX_COLS DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, columns)
|
||||
|
||||
#else /* DT_HAS_CHOSEN(zmk_matrix_transform) */
|
||||
|
||||
#if DT_NODE_HAS_PROP(ZMK_MATRIX_NODE_ID, row_gpios)
|
||||
|
|
|
@ -6,4 +6,16 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
int32_t zmk_matrix_transform_row_column_to_position(uint32_t row, uint32_t column);
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
typedef const struct zmk_matrix_transform *zmk_matrix_transform_t;
|
||||
|
||||
#define ZMK_MATRIX_TRANSFORM_DEFAULT_EXTERN() \
|
||||
extern const struct zmk_matrix_transform zmk_matrix_transform_default
|
||||
#define ZMK_MATRIX_TRANSFORM_EXTERN(node_id) \
|
||||
extern const struct zmk_matrix_transform _CONCAT(zmk_matrix_transform_, node_id)
|
||||
|
||||
#define ZMK_MATRIX_TRANSFORM_T_FOR_NODE(node_id) &_CONCAT(zmk_matrix_transform_, node_id)
|
||||
|
||||
int32_t zmk_matrix_transform_row_column_to_position(zmk_matrix_transform_t mt, uint32_t row,
|
||||
uint32_t column);
|
||||
|
|
43
app/include/zmk/physical_layouts.h
Normal file
43
app/include/zmk/physical_layouts.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zmk/matrix_transform.h>
|
||||
|
||||
struct zmk_key_physical_attrs {
|
||||
int16_t width;
|
||||
int16_t height;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t rx;
|
||||
int16_t ry;
|
||||
int16_t r;
|
||||
};
|
||||
|
||||
struct zmk_physical_layout {
|
||||
const char *display_name;
|
||||
|
||||
zmk_matrix_transform_t matrix_transform;
|
||||
const struct device *kscan;
|
||||
|
||||
const struct zmk_key_physical_attrs *keys;
|
||||
size_t keys_len;
|
||||
};
|
||||
|
||||
#define ZMK_PHYS_LAYOUTS_FOREACH(_ref) STRUCT_SECTION_FOREACH(zmk_physical_layout, _ref)
|
||||
|
||||
size_t zmk_physical_layouts_get_list(struct zmk_physical_layout const *const **phys_layouts);
|
||||
|
||||
int zmk_physical_layouts_select(uint8_t index);
|
||||
int zmk_physical_layouts_get_selected(void);
|
||||
|
||||
int zmk_physical_layouts_check_unsaved_selection(void);
|
||||
int zmk_physical_layouts_save_selected(void);
|
||||
int zmk_physical_layouts_revert_selected(void);
|
||||
|
||||
int zmk_physical_layouts_get_position_map(uint8_t source, uint8_t dest, uint32_t *map);
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/bluetooth/addr.h>
|
||||
#include <zephyr/drivers/kscan.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zmk/matrix_transform.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
|
||||
#define ZMK_KSCAN_EVENT_STATE_PRESSED 0
|
||||
#define ZMK_KSCAN_EVENT_STATE_RELEASED 1
|
||||
|
||||
struct zmk_kscan_event {
|
||||
uint32_t row;
|
||||
uint32_t column;
|
||||
uint32_t state;
|
||||
};
|
||||
|
||||
struct zmk_kscan_msg_processor {
|
||||
struct k_work work;
|
||||
} msg_processor;
|
||||
|
||||
K_MSGQ_DEFINE(zmk_kscan_msgq, sizeof(struct zmk_kscan_event), CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4);
|
||||
|
||||
static void zmk_kscan_callback(const struct device *dev, uint32_t row, uint32_t column,
|
||||
bool pressed) {
|
||||
struct zmk_kscan_event ev = {
|
||||
.row = row,
|
||||
.column = column,
|
||||
.state = (pressed ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED)};
|
||||
|
||||
k_msgq_put(&zmk_kscan_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&msg_processor.work);
|
||||
}
|
||||
|
||||
void zmk_kscan_process_msgq(struct k_work *item) {
|
||||
struct zmk_kscan_event ev;
|
||||
|
||||
while (k_msgq_get(&zmk_kscan_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED);
|
||||
int32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column);
|
||||
|
||||
if (position < 0) {
|
||||
LOG_WRN("Not found in transform: row: %d, col: %d, pressed: %s", ev.row, ev.column,
|
||||
(pressed ? "true" : "false"));
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position,
|
||||
(pressed ? "true" : "false"));
|
||||
raise_zmk_position_state_changed(
|
||||
(struct zmk_position_state_changed){.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
|
||||
.state = pressed,
|
||||
.position = position,
|
||||
.timestamp = k_uptime_get()});
|
||||
}
|
||||
}
|
||||
|
||||
int zmk_kscan_init(const struct device *dev) {
|
||||
if (dev == NULL) {
|
||||
LOG_ERR("Failed to get the KSCAN device");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_work_init(&msg_processor.work, zmk_kscan_process_msgq);
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
if (pm_device_wakeup_is_capable(dev)) {
|
||||
pm_device_wakeup_enable(dev, true);
|
||||
}
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
kscan_config(dev, zmk_kscan_callback);
|
||||
kscan_enable_callback(dev);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -12,18 +12,11 @@
|
|||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zmk/matrix.h>
|
||||
#include <zmk/kscan.h>
|
||||
#include <zmk/display.h>
|
||||
#include <drivers/ext_power.h>
|
||||
|
||||
int main(void) {
|
||||
LOG_INF("Welcome to ZMK!\n");
|
||||
|
||||
if (zmk_kscan_init(DEVICE_DT_GET(ZMK_MATRIX_NODE_ID)) != 0) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
settings_subsys_init();
|
||||
settings_load();
|
||||
|
|
|
@ -4,12 +4,23 @@
|
|||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zmk/matrix_transform.h>
|
||||
#include <zmk/matrix.h>
|
||||
#include <dt-bindings/zmk/matrix_transform.h>
|
||||
|
||||
#ifdef ZMK_KEYMAP_TRANSFORM_NODE
|
||||
#define DT_DRV_COMPAT zmk_matrix_transform
|
||||
|
||||
struct zmk_matrix_transform {
|
||||
uint32_t const *lookup_table;
|
||||
size_t len;
|
||||
uint8_t rows;
|
||||
uint8_t columns;
|
||||
uint8_t col_offset;
|
||||
uint8_t row_offset;
|
||||
};
|
||||
|
||||
/* the transform in the device tree is a list of (row,column) pairs that is
|
||||
* indexed by by the keymap position of that key. We want to invert this in
|
||||
|
@ -28,38 +39,58 @@
|
|||
|
||||
#define INDEX_OFFSET 1
|
||||
|
||||
#define TRANSFORM_ENTRY(i, _) \
|
||||
[(KT_ROW(DT_PROP_BY_IDX(ZMK_KEYMAP_TRANSFORM_NODE, map, i)) * ZMK_MATRIX_COLS) + \
|
||||
KT_COL(DT_PROP_BY_IDX(ZMK_KEYMAP_TRANSFORM_NODE, map, i))] = i + INDEX_OFFSET
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(zmk_matrix_transform)
|
||||
|
||||
static uint32_t transform[] = {LISTIFY(ZMK_KEYMAP_LEN, TRANSFORM_ENTRY, (, ), 0)};
|
||||
#define TRANSFORM_LOOKUP_ENTRY(i, n) \
|
||||
[(KT_ROW(DT_INST_PROP_BY_IDX(n, map, i)) * DT_INST_PROP(n, columns)) + \
|
||||
KT_COL(DT_INST_PROP_BY_IDX(n, map, i))] = i + INDEX_OFFSET
|
||||
|
||||
#endif
|
||||
#define MATRIX_TRANSFORM_INIT(n) \
|
||||
static const uint32_t _CONCAT(zmk_transform_lookup_table_, n)[] = { \
|
||||
LISTIFY(DT_INST_PROP_LEN(n, map), TRANSFORM_LOOKUP_ENTRY, (, ), n)}; \
|
||||
const struct zmk_matrix_transform _CONCAT(zmk_matrix_transform_, DT_DRV_INST(n)) = { \
|
||||
.rows = DT_INST_PROP(n, rows), \
|
||||
.columns = DT_INST_PROP(n, columns), \
|
||||
.col_offset = DT_INST_PROP(n, col_offset), \
|
||||
.row_offset = DT_INST_PROP(n, row_offset), \
|
||||
.lookup_table = _CONCAT(zmk_transform_lookup_table_, n), \
|
||||
.len = ARRAY_SIZE(_CONCAT(zmk_transform_lookup_table_, n)), \
|
||||
};
|
||||
|
||||
int32_t zmk_matrix_transform_row_column_to_position(uint32_t row, uint32_t column) {
|
||||
#if DT_NODE_HAS_PROP(ZMK_KEYMAP_TRANSFORM_NODE, col_offset)
|
||||
column += DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, col_offset);
|
||||
#endif
|
||||
DT_INST_FOREACH_STATUS_OKAY(MATRIX_TRANSFORM_INIT);
|
||||
|
||||
#if DT_NODE_HAS_PROP(ZMK_KEYMAP_TRANSFORM_NODE, row_offset)
|
||||
row += DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, row_offset);
|
||||
#endif
|
||||
#elif DT_HAS_CHOSEN(zmk_kscan) && defined(ZMK_MATRIX_COLS) && defined(ZMK_MATRIX_ROWS)
|
||||
|
||||
const uint32_t matrix_index = (row * ZMK_MATRIX_COLS) + column;
|
||||
|
||||
#ifdef ZMK_KEYMAP_TRANSFORM_NODE
|
||||
if (matrix_index >= ARRAY_SIZE(transform)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
const uint32_t value = transform[matrix_index];
|
||||
|
||||
if (!value) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return value - INDEX_OFFSET;
|
||||
#else
|
||||
return matrix_index;
|
||||
#endif /* ZMK_KEYMAP_TRANSFORM_NODE */
|
||||
const struct zmk_matrix_transform zmk_matrix_transform_default = {
|
||||
.rows = ZMK_MATRIX_ROWS,
|
||||
.columns = ZMK_MATRIX_COLS,
|
||||
.len = ZMK_KEYMAP_LEN,
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
#error "Need a matrix transform or compatible kscan selected to determine keymap size!"
|
||||
`
|
||||
#endif // DT_HAS_COMPAT_STATUS_OKAY(zmk_matrix_transform)
|
||||
|
||||
int32_t zmk_matrix_transform_row_column_to_position(zmk_matrix_transform_t mt, uint32_t row,
|
||||
uint32_t column) {
|
||||
column += mt->col_offset;
|
||||
row += mt->row_offset;
|
||||
|
||||
if (!mt->lookup_table) {
|
||||
return (row * mt->columns) + column;
|
||||
}
|
||||
|
||||
uint16_t lookup_index = ((row * mt->columns) + column);
|
||||
if (lookup_index >= mt->len) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int32_t val = mt->lookup_table[lookup_index];
|
||||
if (val == 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return val - INDEX_OFFSET;
|
||||
};
|
386
app/src/physical_layouts.c
Normal file
386
app/src/physical_layouts.c
Normal file
|
@ -0,0 +1,386 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/pm/device_runtime.h>
|
||||
#include <zephyr/drivers/kscan.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
#include <zephyr/settings/settings.h>
|
||||
#endif
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zmk/physical_layouts.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
|
||||
#define DT_DRV_COMPAT zmk_physical_layout
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
#define ZKPA_INIT(i, n) \
|
||||
(const struct zmk_key_physical_attrs) { \
|
||||
.width = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, width), \
|
||||
.height = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, height), \
|
||||
.x = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, x), \
|
||||
.y = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, y), \
|
||||
.rx = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, rx), \
|
||||
.ry = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, ry), \
|
||||
.r = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, r), \
|
||||
}
|
||||
|
||||
#define ZMK_LAYOUT_INST(n) \
|
||||
static const struct zmk_key_physical_attrs const _CONCAT( \
|
||||
_zmk_physical_layout_keys_, n)[DT_INST_PROP_LEN_OR(n, keys, 0)] = { \
|
||||
LISTIFY(DT_INST_PROP_LEN_OR(n, keys, 0), ZKPA_INIT, (, ), n)}; \
|
||||
ZMK_MATRIX_TRANSFORM_EXTERN(DT_INST_PHANDLE(n, transform)); \
|
||||
static const struct zmk_physical_layout const _CONCAT(_zmk_physical_layout_, \
|
||||
DT_DRV_INST(n)) = { \
|
||||
.display_name = DT_INST_PROP_OR(n, display_name, "Layout #" #n), \
|
||||
.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))))};
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(ZMK_LAYOUT_INST)
|
||||
|
||||
#define POS_MAP_COMPAT zmk_physical_layout_position_map
|
||||
#define HAVE_POS_MAP DT_HAS_COMPAT_STATUS_OKAY(POS_MAP_COMPAT)
|
||||
|
||||
#define POS_MAP_COMPLETE (HAVE_POS_MAP && DT_PROP(DT_INST(0, POS_MAP_COMPAT), complete))
|
||||
|
||||
#if HAVE_POS_MAP
|
||||
|
||||
// Using sizeof + union trick to calculate the "positions" length statically.
|
||||
#define ZMK_POS_MAP_POSITIONS_ARRAY(node_id) \
|
||||
uint8_t _CONCAT(prop_, node_id)[DT_PROP_LEN(node_id, positions)];
|
||||
|
||||
#define ZMK_POS_MAP_LEN \
|
||||
sizeof(union {DT_FOREACH_CHILD(DT_INST(0, POS_MAP_COMPAT), ZMK_POS_MAP_POSITIONS_ARRAY)})
|
||||
|
||||
struct position_map_entry {
|
||||
const struct zmk_physical_layout *layout;
|
||||
const uint32_t positions[ZMK_POS_MAP_LEN];
|
||||
};
|
||||
|
||||
#define ZMK_POS_MAP_ENTRY(node_id) \
|
||||
{ \
|
||||
.layout = &_CONCAT(_zmk_physical_layout_, DT_PHANDLE(node_id, physical_layout)), \
|
||||
.positions = DT_PROP(node_id, positions), \
|
||||
}
|
||||
|
||||
static const struct position_map_entry positions_maps[] = {
|
||||
DT_FOREACH_CHILD_SEP(DT_INST(0, POS_MAP_COMPAT), ZMK_POS_MAP_ENTRY, (, ))};
|
||||
|
||||
#endif
|
||||
|
||||
#define ZMK_LAYOUT_REF(n) &_CONCAT(_zmk_physical_layout_, DT_DRV_INST(n)),
|
||||
|
||||
static const struct zmk_physical_layout *const layouts[] = {
|
||||
DT_INST_FOREACH_STATUS_OKAY(ZMK_LAYOUT_REF)};
|
||||
|
||||
#elif DT_HAS_CHOSEN(zmk_matrix_transform)
|
||||
|
||||
ZMK_MATRIX_TRANSFORM_EXTERN(DT_CHOSEN(zmk_matrix_transform));
|
||||
|
||||
static const struct zmk_physical_layout _CONCAT(_zmk_physical_layout_, chosen) = {
|
||||
.display_name = "Default",
|
||||
.matrix_transform = ZMK_MATRIX_TRANSFORM_T_FOR_NODE(DT_CHOSEN(zmk_matrix_transform)),
|
||||
COND_CODE_1(DT_HAS_CHOSEN(zmk_kscan), (.kscan = DEVICE_DT_GET(DT_CHOSEN(zmk_kscan)), ), ())};
|
||||
|
||||
static const struct zmk_physical_layout *const layouts[] = {
|
||||
&_CONCAT(_zmk_physical_layout_, chosen)};
|
||||
|
||||
#elif DT_HAS_CHOSEN(zmk_kscan)
|
||||
|
||||
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,
|
||||
.kscan = DEVICE_DT_GET(DT_CHOSEN(zmk_kscan)),
|
||||
};
|
||||
|
||||
static const struct zmk_physical_layout *const layouts[] = {
|
||||
&_CONCAT(_zmk_physical_layout_, chosen)};
|
||||
|
||||
#endif
|
||||
|
||||
const struct zmk_physical_layout *active;
|
||||
|
||||
size_t zmk_physical_layouts_get_list(struct zmk_physical_layout const *const **dest_layouts) {
|
||||
*dest_layouts = &layouts[0];
|
||||
|
||||
return ARRAY_SIZE(layouts);
|
||||
}
|
||||
|
||||
#define ZMK_KSCAN_EVENT_STATE_PRESSED 0
|
||||
#define ZMK_KSCAN_EVENT_STATE_RELEASED 1
|
||||
|
||||
struct zmk_kscan_event {
|
||||
uint32_t row;
|
||||
uint32_t column;
|
||||
uint32_t state;
|
||||
};
|
||||
|
||||
static struct zmk_kscan_msg_processor { struct k_work work; } msg_processor;
|
||||
|
||||
K_MSGQ_DEFINE(physical_layouts_kscan_msgq, sizeof(struct zmk_kscan_event),
|
||||
CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4);
|
||||
|
||||
static void zmk_physical_layout_kscan_callback(const struct device *dev, uint32_t row,
|
||||
uint32_t column, bool pressed) {
|
||||
if (dev != active->kscan) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct zmk_kscan_event ev = {
|
||||
.row = row,
|
||||
.column = column,
|
||||
.state = (pressed ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED)};
|
||||
|
||||
k_msgq_put(&physical_layouts_kscan_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&msg_processor.work);
|
||||
}
|
||||
|
||||
static void zmk_physical_layouts_kscan_process_msgq(struct k_work *item) {
|
||||
struct zmk_kscan_event ev;
|
||||
|
||||
while (k_msgq_get(&physical_layouts_kscan_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED);
|
||||
int32_t position = zmk_matrix_transform_row_column_to_position(active->matrix_transform,
|
||||
ev.row, ev.column);
|
||||
|
||||
if (position < 0) {
|
||||
LOG_WRN("Not found in transform: row: %d, col: %d, pressed: %s", ev.row, ev.column,
|
||||
(pressed ? "true" : "false"));
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position,
|
||||
(pressed ? "true" : "false"));
|
||||
raise_zmk_position_state_changed(
|
||||
(struct zmk_position_state_changed){.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
|
||||
.state = pressed,
|
||||
.position = position,
|
||||
.timestamp = k_uptime_get()});
|
||||
}
|
||||
}
|
||||
|
||||
int zmk_physical_layouts_select_layout(const struct zmk_physical_layout *dest_layout) {
|
||||
if (!dest_layout) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (dest_layout == active) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
if (active->kscan) {
|
||||
kscan_disable_callback(active->kscan);
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_put(active->kscan);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(active->kscan, PM_DEVICE_ACTION_SUSPEND);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
active = dest_layout;
|
||||
|
||||
if (active->kscan) {
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
int err = pm_device_runtime_get(active->kscan);
|
||||
if (err < 0) {
|
||||
LOG_WRN("PM runtime get of kscan device to enable it %d", err);
|
||||
return err;
|
||||
}
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(active->kscan, PM_DEVICE_ACTION_RESUME);
|
||||
#endif
|
||||
kscan_config(active->kscan, zmk_physical_layout_kscan_callback);
|
||||
kscan_enable_callback(active->kscan);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_physical_layouts_select(uint8_t index) {
|
||||
if (index >= ARRAY_SIZE(layouts)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return zmk_physical_layouts_select_layout(layouts[index]);
|
||||
}
|
||||
|
||||
int zmk_physical_layouts_get_selected(void) {
|
||||
for (int i = 0; i < ARRAY_SIZE(layouts); i++) {
|
||||
if (layouts[i] == active) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
|
||||
static int8_t saved_selected_index = -1;
|
||||
|
||||
#endif
|
||||
|
||||
int zmk_physical_layouts_select_initial(void) {
|
||||
const struct zmk_physical_layout *initial;
|
||||
|
||||
#if DT_HAS_CHOSEN(zmk_physical_layout)
|
||||
initial = &_CONCAT(_zmk_physical_layout_, DT_CHOSEN(zmk_physical_layout));
|
||||
#else
|
||||
initial = layouts[0];
|
||||
#endif
|
||||
|
||||
int ret = zmk_physical_layouts_select_layout(initial);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int zmk_physical_layouts_check_unsaved_selection(void) {
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
return saved_selected_index < 0 ||
|
||||
saved_selected_index == (uint8_t)zmk_physical_layouts_get_selected()
|
||||
? 0
|
||||
: 1;
|
||||
#else
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
int zmk_physical_layouts_save_selected(void) {
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
uint8_t val = (uint8_t)zmk_physical_layouts_get_selected();
|
||||
|
||||
return settings_save_one("physical_layouts/selected", &val, sizeof(val));
|
||||
#else
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
int zmk_physical_layouts_revert_selected(void) { return zmk_physical_layouts_select_initial(); }
|
||||
|
||||
int zmk_physical_layouts_get_position_map(uint8_t source, uint8_t dest, uint32_t *map) {
|
||||
if (source >= ARRAY_SIZE(layouts) || dest >= ARRAY_SIZE(layouts)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
const struct zmk_physical_layout *src_layout = layouts[source];
|
||||
const struct zmk_physical_layout *dest_layout = layouts[dest];
|
||||
|
||||
#if HAVE_POS_MAP
|
||||
const struct position_map_entry *src_pos_map = NULL;
|
||||
const struct position_map_entry *dest_pos_map = NULL;
|
||||
|
||||
for (int pm = 0; pm < ARRAY_SIZE(positions_maps); pm++) {
|
||||
if (positions_maps[pm].layout == src_layout) {
|
||||
src_pos_map = &positions_maps[pm];
|
||||
}
|
||||
|
||||
if (positions_maps[pm].layout == dest_layout) {
|
||||
dest_pos_map = &positions_maps[pm];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(map, UINT32_MAX, dest_layout->keys_len);
|
||||
|
||||
for (int b = 0; b < dest_layout->keys_len; b++) {
|
||||
bool found = false;
|
||||
|
||||
#if HAVE_POS_MAP
|
||||
if (src_pos_map && dest_pos_map) {
|
||||
for (int m = 0; m < ZMK_POS_MAP_LEN; m++) {
|
||||
if (dest_pos_map->positions[m] == b) {
|
||||
map[b] = src_pos_map->positions[m];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !POS_MAP_COMPLETE
|
||||
if (!found) {
|
||||
const struct zmk_key_physical_attrs *key = &dest_layout->keys[b];
|
||||
for (int old_b = 0; old_b < src_layout->keys_len; old_b++) {
|
||||
const struct zmk_key_physical_attrs *candidate_key = &src_layout->keys[old_b];
|
||||
|
||||
if (candidate_key->x == key->x && candidate_key->y == key->y) {
|
||||
map[b] = old_b;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!found || map[b] >= src_layout->keys_len) {
|
||||
map[b] = UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
return dest_layout->keys_len;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
|
||||
static int physical_layouts_handle_set(const char *name, size_t len, settings_read_cb read_cb,
|
||||
void *cb_arg) {
|
||||
const char *next;
|
||||
|
||||
if (settings_name_steq(name, "selected", &next) && !next) {
|
||||
if (len != sizeof(saved_selected_index)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int err = read_cb(cb_arg, &saved_selected_index, len);
|
||||
if (err <= 0) {
|
||||
LOG_ERR("Failed to handle selected physical dest_layout from settings (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return zmk_physical_layouts_select(saved_selected_index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
SETTINGS_STATIC_HANDLER_DEFINE(physical_layouts, "physical_layouts", NULL,
|
||||
physical_layouts_handle_set, NULL, NULL);
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_SETTINGS)
|
||||
|
||||
static int zmk_physical_layouts_init(void) {
|
||||
k_work_init(&msg_processor.work, zmk_physical_layouts_kscan_process_msgq);
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
for (int l = 0; l < ARRAY_SIZE(layouts); l++) {
|
||||
const struct zmk_physical_layout *pl = layouts[l];
|
||||
if (pl->kscan) {
|
||||
if (pm_device_wakeup_is_capable(pl->kscan)) {
|
||||
pm_device_wakeup_enable(pl->kscan, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
return zmk_physical_layouts_select_initial();
|
||||
}
|
||||
|
||||
SYS_INIT(zmk_physical_layouts_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
|
Loading…
Add table
Reference in a new issue