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:
Peter Johanson 2024-04-24 18:14:02 -07:00 committed by Pete Johanson
parent 80173f8ea3
commit c5cca5b34f
13 changed files with 604 additions and 141 deletions

View file

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

View 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

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

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

View 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>;
};
};

View file

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

View file

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

View file

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

View 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);

View file

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

View file

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

View file

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