feat(mouse): Add mouse move and scroll support (#2477)

* feat(mouse): Add mouse move and scroll support

    * Use Zephyr input subsystem for all pointers.
    * Input processors for modifying events, e.g. scaling, swapping
      codes, temporary (mouse) layers, etc.
    * Mouse move/scroll behaviors.
    * Infrastructure in place for physical pointer input devices.

* feat: Add input split support.

* docs: Add initial pointer docs.

---------

Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com>
Co-authored-by: Alexander Krikun <krikun98@gmail.com>
Co-authored-by: Robert U <urob@users.noreply.github.com>
Co-authored-by: Shawn Meier <ftc@users.noreply.github.com>
Co-authored-by: Chris Andreae <chris@andreae.gen.nz>
Co-authored-by: Anant Thazhemadam <47104651+thazhemadam@users.noreply.github.com>
Co-authored-by: Erik Tollerud <erik.tollerud@gmail.com>
Co-authored-by: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com>
This commit is contained in:
Pete Johanson
2024-12-09 17:45:41 -07:00
committed by GitHub
parent 7e8c542c94
commit 6b40bfda53
119 changed files with 4223 additions and 229 deletions

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
target_sources_ifdef(CONFIG_ZMK_INPUT_LISTENER app PRIVATE input_listener.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_TRANSFORM app PRIVATE input_processor_transform.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_SCALER app PRIVATE input_processor_scaler.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_TEMP_LAYER app PRIVATE input_processor_temp_layer.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_CODE_MAPPER app PRIVATE input_processor_code_mapper.c)
target_sources_ifdef(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING app PRIVATE resolution_multipliers.c)
target_sources_ifdef(CONFIG_ZMK_INPUT_SPLIT app PRIVATE input_split.c)

75
app/src/pointing/Kconfig Normal file
View File

@@ -0,0 +1,75 @@
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
menu "Pointing Options"
# Deprecated old config, kept for backwards compat
config ZMK_MOUSE
bool "(Deprecated) Mouse Support"
config ZMK_POINTING
bool "Pointing Device Support"
default y if ZMK_MOUSE
select INPUT
select INPUT_THREAD_PRIORITY_OVERRIDE
if ZMK_POINTING
# Needed for anyone using gpio-keys for things like soft-off setup.
config INPUT_GPIO_KEYS
default n
config INPUT_THREAD_STACK_SIZE
default 1024 if ZMK_SPLIT && !ZMK_SPLIT_ROLE_CENTRAL
if !ZMK_SPLIT || ZMK_SPLIT_ROLE_CENTRAL
config ZMK_POINTING_SMOOTH_SCROLLING
bool "Smooth Scrolling"
help
Enable smooth scrolling, with hosts that support HID Resolution Multipliers
config ZMK_INPUT_LISTENER
bool "Input listener for processing input events in the system"
default y
depends on DT_HAS_ZMK_INPUT_LISTENER_ENABLED
config ZMK_INPUT_PROCESSOR_TEMP_LAYER
bool "Temporary Layer Input Processor"
default y
depends on DT_HAS_ZMK_INPUT_PROCESSOR_TEMP_LAYER_ENABLED
endif
config ZMK_INPUT_PROCESSOR_TRANSFORM
bool "Transform Input Processor"
default y
depends on DT_HAS_ZMK_INPUT_PROCESSOR_TRANSFORM_ENABLED
config ZMK_INPUT_PROCESSOR_SCALER
bool "Scaling Input Processor"
default y
depends on DT_HAS_ZMK_INPUT_PROCESSOR_SCALER_ENABLED
config ZMK_INPUT_PROCESSOR_CODE_MAPPER
bool "Code Mapper Input Processor"
default y
depends on DT_HAS_ZMK_INPUT_PROCESSOR_CODE_MAPPER_ENABLED
config ZMK_INPUT_SPLIT
bool "Split input support"
default y
depends on DT_HAS_ZMK_INPUT_SPLIT_ENABLED && ZMK_SPLIT
if ZMK_INPUT_SPLIT
config ZMK_INPUT_SPLIT_INIT_PRIORITY
int "Input Split initialization priority"
default INPUT_INIT_PRIORITY
endif # ZMK_INPUT_SPLIT
endif # ZMK_POINTING
endmenu # Mouse Options

View File

@@ -0,0 +1,373 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_input_listener
#include <zephyr/sys/util_macro.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/input/input.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zmk/endpoints.h>
#include <drivers/input_processor.h>
#include <zmk/pointing.h>
#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
#include <zmk/pointing/resolution_multipliers.h>
#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
#include <zmk/hid.h>
#include <zmk/keymap.h>
#define ONE_IF_DEV_OK(n) \
COND_CODE_1(DT_NODE_HAS_STATUS(DT_INST_PHANDLE(n, device), okay), (1 +), (0 +))
#define VALID_LISTENER_COUNT (DT_INST_FOREACH_STATUS_OKAY(ONE_IF_DEV_OK) 0)
#if VALID_LISTENER_COUNT > 0
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_axis_data {
int16_t value;
};
struct input_listener_xy_data {
enum input_listener_xy_data_mode mode;
struct input_listener_axis_data x;
struct input_listener_axis_data y;
};
struct input_listener_config_entry {
size_t processors_len;
const struct zmk_input_processor_entry *processors;
};
struct input_listener_layer_override {
uint32_t layer_mask;
bool process_next;
struct input_listener_config_entry config;
};
struct input_processor_remainder_data {
int16_t x, y, wheel, h_wheel;
};
struct input_listener_processor_data {
size_t remainders_len;
struct input_processor_remainder_data *remainders;
};
struct input_listener_config {
struct input_listener_config_entry base;
size_t layer_overrides_len;
struct input_listener_layer_override layer_overrides[];
};
struct input_listener_data {
union {
struct {
struct input_listener_xy_data data;
struct input_listener_xy_data wheel_data;
uint8_t button_set;
uint8_t button_clear;
} mouse;
};
#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
int16_t wheel_remainder;
int16_t h_wheel_remainder;
#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
struct input_listener_processor_data base_processor_data;
struct input_listener_processor_data layer_override_data[];
};
static void handle_rel_code(struct input_listener_data *data, struct input_event *evt) {
switch (evt->code) {
case INPUT_REL_X:
data->mouse.data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
data->mouse.data.x.value += evt->value;
break;
case INPUT_REL_Y:
data->mouse.data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
data->mouse.data.y.value += evt->value;
break;
case INPUT_REL_WHEEL:
data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
data->mouse.wheel_data.y.value += evt->value;
break;
case INPUT_REL_HWHEEL:
data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL;
data->mouse.wheel_data.x.value += evt->value;
break;
default:
break;
}
}
static void handle_abs_code(const struct input_listener_config *config,
struct input_listener_data *data, struct input_event *evt) {}
static void handle_key_code(const struct input_listener_config *config,
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->mouse.button_set, btn, 1);
} else {
WRITE_BIT(data->mouse.button_clear, btn, 1);
}
break;
default:
break;
}
}
static inline bool is_x_data(const struct input_event *evt) {
return evt->type == INPUT_EV_REL && evt->code == INPUT_REL_X;
}
static inline bool is_y_data(const struct input_event *evt) {
return evt->type == INPUT_EV_REL && evt->code == INPUT_REL_Y;
}
static void apply_config(const struct input_listener_config_entry *cfg,
struct input_listener_processor_data *processor_data,
struct input_listener_data *data, struct input_event *evt) {
size_t remainder_index = 0;
for (size_t p = 0; p < cfg->processors_len; p++) {
const struct zmk_input_processor_entry *proc_e = &cfg->processors[p];
struct input_processor_remainder_data *remainders = NULL;
if (proc_e->track_remainders) {
remainders = &processor_data->remainders[remainder_index++];
}
int16_t *remainder = NULL;
if (remainders) {
if (evt->type == INPUT_EV_REL) {
switch (evt->code) {
case INPUT_REL_X:
remainder = &remainders->x;
break;
case INPUT_REL_Y:
remainder = &remainders->y;
break;
case INPUT_REL_WHEEL:
remainder = &remainders->wheel;
break;
case INPUT_REL_HWHEEL:
remainder = &remainders->h_wheel;
break;
}
}
}
struct zmk_input_processor_state state = {.remainder = remainder};
zmk_input_processor_handle_event(proc_e->dev, evt, proc_e->param1, proc_e->param2, &state);
}
}
static void filter_with_input_config(const struct input_listener_config *cfg,
struct input_listener_data *data, struct input_event *evt) {
if (!evt->dev) {
return;
}
for (size_t oi = 0; oi < cfg->layer_overrides_len; oi++) {
const struct input_listener_layer_override *override = &cfg->layer_overrides[oi];
struct input_listener_processor_data *override_data = &data->layer_override_data[oi];
uint32_t mask = override->layer_mask;
uint8_t layer = 0;
while (mask != 0) {
if (mask & BIT(0) && zmk_keymap_layer_active(layer)) {
apply_config(&override->config, override_data, data, evt);
if (!override->process_next) {
return;
}
}
layer++;
mask = mask >> 1;
}
}
apply_config(&cfg->base, &data->base_processor_data, data, evt);
}
static void clear_xy_data(struct input_listener_xy_data *data) {
data->x.value = data->y.value = 0;
data->mode = INPUT_LISTENER_XY_DATA_MODE_NONE;
}
#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
static void apply_resolution_scaling(struct input_listener_data *data, struct input_event *evt) {
int16_t *remainder;
uint8_t div;
switch (evt->code) {
case INPUT_REL_WHEEL:
remainder = &data->wheel_remainder;
div = (16 - zmk_pointing_resolution_multipliers_get_current_profile().wheel);
break;
case INPUT_REL_HWHEEL:
remainder = &data->h_wheel_remainder;
div = (16 - zmk_pointing_resolution_multipliers_get_current_profile().hor_wheel);
break;
default:
return;
}
int16_t val = evt->value + *remainder;
int16_t scaled = val / (int16_t)div;
*remainder = val - (scaled * (int16_t)div);
evt->value = val;
}
#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
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, data, evt);
#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
apply_resolution_scaling(data, evt);
#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
switch (evt->type) {
case INPUT_EV_REL:
handle_rel_code(data, evt);
break;
case INPUT_EV_ABS:
handle_abs_code(config, data, evt);
break;
case INPUT_EV_KEY:
handle_key_code(config, data, evt);
break;
}
if (evt->sync) {
if (data->mouse.wheel_data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) {
zmk_hid_mouse_scroll_set(data->mouse.wheel_data.x.value,
data->mouse.wheel_data.y.value);
}
if (data->mouse.data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) {
zmk_hid_mouse_movement_set(data->mouse.data.x.value, data->mouse.data.y.value);
}
if (data->mouse.button_set != 0) {
for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) {
if ((data->mouse.button_set & BIT(i)) != 0) {
zmk_hid_mouse_button_press(i);
}
}
}
if (data->mouse.button_clear != 0) {
for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) {
if ((data->mouse.button_clear & 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->mouse.data);
clear_xy_data(&data->mouse.wheel_data);
data->mouse.button_set = data->mouse.button_clear = 0;
}
}
#endif // VALID_LISTENER_COUNT > 0
#define ONE_FOR_TRACKED(n, elem, idx) \
+DT_PROP(DT_PHANDLE_BY_IDX(n, input_processors, idx), track_remainders)
#define PROCESSOR_REM_TRACKERS(n) (0 DT_FOREACH_PROP_ELEM(n, input_processors, ONE_FOR_TRACKED))
#define SCOPED_PROCESSOR(scope, n, id) \
COND_CODE_1(DT_NODE_HAS_PROP(n, input_processors), \
(static struct input_processor_remainder_data _CONCAT( \
input_processor_remainders_##id, scope)[PROCESSOR_REM_TRACKERS(n)] = {};), \
()) \
static const struct zmk_input_processor_entry _CONCAT( \
processor_##id, scope)[DT_PROP_LEN_OR(n, input_processors, 0)] = \
COND_CODE_1(DT_NODE_HAS_PROP(n, input_processors), \
({LISTIFY(DT_PROP_LEN(n, input_processors), ZMK_INPUT_PROCESSOR_ENTRY_AT_IDX, \
(, ), n)}), \
({}));
#define IL_EXTRACT_CONFIG(n, id, scope) \
{ \
.processors_len = DT_PROP_LEN_OR(n, input_processors, 0), \
.processors = _CONCAT(processor_##id, scope), \
}
#define IL_EXTRACT_DATA(n, id, scope) \
{COND_CODE_1(DT_NODE_HAS_PROP(n, input_processors), \
(.remainders_len = PROCESSOR_REM_TRACKERS(n), \
.remainders = _CONCAT(input_processor_remainders_##id, scope), ), \
())}
#define IL_ONE(...) +1
#define CHILD_CONFIG(node, parent) SCOPED_PROCESSOR(node, node, parent)
#define OVERRIDE_LAYER_BIT(node, prop, idx) BIT(DT_PROP_BY_IDX(node, prop, idx))
#define IL_OVERRIDE(node, parent) \
{ \
.layer_mask = DT_FOREACH_PROP_ELEM_SEP(node, layers, OVERRIDE_LAYER_BIT, (|)), \
.process_next = DT_PROP_OR(node, process_next, false), \
.config = IL_EXTRACT_CONFIG(node, parent, node), \
}
#define IL_OVERRIDE_DATA(node, parent) IL_EXTRACT_DATA(node, parent, node)
#define IL_INST(n) \
COND_CODE_1( \
DT_NODE_HAS_STATUS(DT_INST_PHANDLE(n, device), okay), \
(SCOPED_PROCESSOR(base, DT_DRV_INST(n), n); \
DT_INST_FOREACH_CHILD_VARGS(n, CHILD_CONFIG, \
n) static const struct input_listener_config config_##n = \
{ \
.base = IL_EXTRACT_CONFIG(DT_DRV_INST(n), n, base), \
.layer_overrides_len = (0 DT_INST_FOREACH_CHILD(n, IL_ONE)), \
.layer_overrides = {DT_INST_FOREACH_CHILD_SEP_VARGS(n, IL_OVERRIDE, (, ), n)}, \
}; \
static struct input_listener_data data_##n = \
{ \
.base_processor_data = IL_EXTRACT_DATA(DT_DRV_INST(n), n, base), \
.layer_override_data = {DT_INST_FOREACH_CHILD_SEP_VARGS(n, IL_OVERRIDE_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)

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_input_processor_code_mapper
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <drivers/input_processor.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct cm_config {
uint8_t type;
size_t mapping_size;
uint16_t mapping[];
};
static int cm_handle_event(const struct device *dev, struct input_event *event, uint32_t param1,
uint32_t param2, struct zmk_input_processor_state *state) {
const struct cm_config *cfg = dev->config;
if (event->type != cfg->type) {
return 0;
}
for (int i = 0; i < cfg->mapping_size / 2; i++) {
if (cfg->mapping[i * 2] == event->code) {
uint16_t orig = event->code;
event->code = cfg->mapping[(i * 2) + 1];
LOG_DBG("Remapped %d to %d", orig, event->code);
break;
}
}
return 0;
}
static struct zmk_input_processor_driver_api cm_driver_api = {
.handle_event = cm_handle_event,
};
#define TL_INST(n) \
static const struct cm_config cm_config_##n = { \
.type = DT_INST_PROP_OR(n, type, INPUT_EV_REL), \
.mapping_size = DT_INST_PROP_LEN(n, map), \
.mapping = DT_INST_PROP(n, map), \
}; \
BUILD_ASSERT(DT_INST_PROP_LEN(n, map) % 2 == 0, \
"Must have an even number of mapping entries"); \
DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, &cm_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &cm_driver_api);
DT_INST_FOREACH_STATUS_OKAY(TL_INST)

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_input_processor_scaler
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <drivers/input_processor.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct scaler_config {
uint8_t type;
size_t codes_len;
uint16_t codes[];
};
static int scale_val(struct input_event *event, uint32_t mul, uint32_t div,
struct zmk_input_processor_state *state) {
int16_t value_mul = event->value * (int16_t)mul;
if (state && state->remainder) {
value_mul += *state->remainder;
}
int16_t scaled = value_mul / (int16_t)div;
if (state && state->remainder) {
*state->remainder = value_mul - (scaled * (int16_t)div);
}
LOG_DBG("scaled %d with %d/%d to %d with remainder %d", event->value, mul, div, scaled,
(state && state->remainder) ? *state->remainder : 0);
event->value = scaled;
return 0;
}
static int scaler_handle_event(const struct device *dev, struct input_event *event, uint32_t param1,
uint32_t param2, struct zmk_input_processor_state *state) {
const struct scaler_config *cfg = dev->config;
if (event->type != cfg->type) {
return 0;
}
for (int i = 0; i < cfg->codes_len; i++) {
if (cfg->codes[i] == event->code) {
return scale_val(event, param1, param2, state);
}
}
return 0;
}
static struct zmk_input_processor_driver_api scaler_driver_api = {
.handle_event = scaler_handle_event,
};
#define SCALER_INST(n) \
static const struct scaler_config scaler_config_##n = { \
.type = DT_INST_PROP_OR(n, type, INPUT_EV_REL), \
.codes_len = DT_INST_PROP_LEN(n, codes), \
.codes = DT_INST_PROP(n, codes), \
}; \
DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, &scaler_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &scaler_driver_api);
DT_INST_FOREACH_STATUS_OKAY(SCALER_INST)

View File

@@ -0,0 +1,213 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_input_processor_temp_layer
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <drivers/input_processor.h>
#include <zephyr/logging/log.h>
#include <zmk/keymap.h>
#include <zmk/behavior.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/events/keycode_state_changed.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
/* Constants and Types */
#define MAX_LAYERS ZMK_KEYMAP_LAYERS_LEN
struct temp_layer_config {
int16_t require_prior_idle_ms;
const uint16_t *excluded_positions;
size_t num_positions;
};
struct temp_layer_state {
uint8_t toggle_layer;
bool is_active;
int64_t last_tapped_timestamp;
};
struct temp_layer_data {
const struct device *dev;
struct temp_layer_state state;
};
/* Static Work Queue Items */
static struct k_work_delayable layer_disable_works[MAX_LAYERS];
/* Position Search */
static bool position_is_excluded(const struct temp_layer_config *config, uint32_t position) {
if (!config->excluded_positions || !config->num_positions) {
return false;
}
const uint16_t *end = config->excluded_positions + config->num_positions;
for (const uint16_t *pos = config->excluded_positions; pos < end; pos++) {
if (*pos == position) {
return true;
}
}
return false;
}
/* Timing Check */
static bool should_quick_tap(const struct temp_layer_config *config, int64_t last_tapped,
int64_t current_time) {
return (last_tapped + config->require_prior_idle_ms) > current_time;
}
/* Layer State Management */
static void update_layer_state(struct temp_layer_state *state, bool activate) {
if (state->is_active == activate) {
return;
}
state->is_active = activate;
if (activate) {
zmk_keymap_layer_activate(state->toggle_layer);
LOG_DBG("Layer %d activated", state->toggle_layer);
} else {
zmk_keymap_layer_deactivate(state->toggle_layer);
LOG_DBG("Layer %d deactivated", state->toggle_layer);
}
}
/* Work Queue Callback */
static void layer_disable_callback(struct k_work *work) {
struct k_work_delayable *d_work = k_work_delayable_from_work(work);
int layer_index = ARRAY_INDEX(layer_disable_works, d_work);
const struct device *dev = DEVICE_DT_INST_GET(0);
struct temp_layer_data *data = (struct temp_layer_data *)dev->data;
if (zmk_keymap_layer_active(layer_index)) {
update_layer_state(&data->state, false);
}
}
/* Event Handlers */
static int handle_position_state_changed(const zmk_event_t *eh) {
const struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh);
if (!ev->state) {
return ZMK_EV_EVENT_BUBBLE;
}
const struct device *dev = DEVICE_DT_INST_GET(0);
struct temp_layer_data *data = (struct temp_layer_data *)dev->data;
const struct temp_layer_config *cfg = dev->config;
if (data->state.is_active && cfg->excluded_positions && cfg->num_positions > 0) {
if (!position_is_excluded(cfg, ev->position)) {
LOG_DBG("Position not excluded, deactivating layer");
update_layer_state(&data->state, false);
}
}
LOG_DBG("Position excluded, continuing");
return ZMK_EV_EVENT_BUBBLE;
}
static int handle_keycode_state_changed(const zmk_event_t *eh) {
const struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
if (!ev->state) {
return ZMK_EV_EVENT_BUBBLE;
}
const struct device *dev = DEVICE_DT_INST_GET(0);
struct temp_layer_data *data = (struct temp_layer_data *)dev->data;
LOG_DBG("Setting last_tapped_timestamp to: %d", ev->timestamp);
data->state.last_tapped_timestamp = ev->timestamp;
return ZMK_EV_EVENT_BUBBLE;
}
static int handle_state_changed_dispatcher(const zmk_event_t *eh) {
if (as_zmk_position_state_changed(eh) != NULL) {
LOG_DBG("Dispatching handle_position_state_changed");
return handle_position_state_changed(eh);
} else if (as_zmk_keycode_state_changed(eh) != NULL) {
LOG_DBG("Dispatching handle_keycode_state_changed");
return handle_keycode_state_changed(eh);
}
return ZMK_EV_EVENT_BUBBLE;
}
/* Driver Implementation */
static int temp_layer_handle_event(const struct device *dev, struct input_event *event,
uint32_t param1, uint32_t param2,
struct zmk_input_processor_state *state) {
if (param1 >= MAX_LAYERS) {
LOG_ERR("Invalid layer index: %d", param1);
return -EINVAL;
}
struct temp_layer_data *data = (struct temp_layer_data *)dev->data;
const struct temp_layer_config *cfg = dev->config;
data->state.toggle_layer = param1;
if (!data->state.is_active &&
!should_quick_tap(cfg, data->state.last_tapped_timestamp, k_uptime_get())) {
update_layer_state(&data->state, true);
}
if (param2 > 0) {
k_work_reschedule(&layer_disable_works[param1], K_MSEC(param2));
}
return 0;
}
static int temp_layer_init(const struct device *dev) {
for (int i = 0; i < MAX_LAYERS; i++) {
k_work_init_delayable(&layer_disable_works[i], layer_disable_callback);
}
return 0;
}
/* Driver API */
static const struct zmk_input_processor_driver_api temp_layer_driver_api = {
.handle_event = temp_layer_handle_event,
};
/* Event Listeners Conditions */
#define NEEDS_POSITION_HANDLERS(n, ...) DT_INST_PROP_HAS_IDX(n, excluded_positions, 0)
#define NEEDS_KEYCODE_HANDLERS(n, ...) (DT_INST_PROP_OR(n, require_prior_idle_ms, 0) > 0)
/* Event Handlers Registration */
#if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_POSITION_HANDLERS, ||) || \
DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_KEYCODE_HANDLERS, ||)
ZMK_LISTENER(processor_temp_layer, handle_state_changed_dispatcher);
#endif
/* Individual Subscriptions */
#if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_POSITION_HANDLERS, ||)
ZMK_SUBSCRIPTION(processor_temp_layer, zmk_position_state_changed);
#endif
#if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_KEYCODE_HANDLERS, ||)
ZMK_SUBSCRIPTION(processor_temp_layer, zmk_keycode_state_changed);
#endif
/* Device Instantiation */
#define TEMP_LAYER_INST(n) \
static struct temp_layer_data processor_temp_layer_data_##n = {}; \
static const uint16_t excluded_positions_##n[] = DT_INST_PROP(n, excluded_positions); \
static const struct temp_layer_config processor_temp_layer_config_##n = { \
.require_prior_idle_ms = DT_INST_PROP_OR(n, require_prior_idle_ms, 0), \
.excluded_positions = excluded_positions_##n, \
.num_positions = DT_INST_PROP_LEN(n, excluded_positions), \
}; \
DEVICE_DT_INST_DEFINE(n, temp_layer_init, NULL, &processor_temp_layer_data_##n, \
&processor_temp_layer_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &temp_layer_driver_api);
DT_INST_FOREACH_STATUS_OKAY(TEMP_LAYER_INST)

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_input_processor_transform
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <drivers/input_processor.h>
#include <dt-bindings/zmk/input_transform.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/keymap.h>
struct ipt_config {
size_t x_codes_size;
size_t y_codes_size;
uint8_t type;
const uint16_t *x_codes;
const uint16_t *y_codes;
};
static int code_idx(uint16_t code, const uint16_t *list, size_t len) {
for (int i = 0; i < len; i++) {
if (list[i] == code) {
return i;
}
}
return -ENODEV;
}
static int ipt_handle_event(const struct device *dev, struct input_event *event, uint32_t param1,
uint32_t param2, struct zmk_input_processor_state *state) {
const struct ipt_config *cfg = dev->config;
if (event->type != cfg->type) {
return 0;
}
if (param1 & INPUT_TRANSFORM_XY_SWAP) {
int idx = code_idx(event->code, cfg->x_codes, cfg->x_codes_size);
if (idx >= 0) {
event->code = cfg->y_codes[idx];
} else {
idx = code_idx(event->code, cfg->y_codes, cfg->y_codes_size);
if (idx >= 0) {
event->code = cfg->x_codes[idx];
}
}
}
if ((param1 & INPUT_TRANSFORM_X_INVERT &&
code_idx(event->code, cfg->x_codes, cfg->x_codes_size) >= 0) ||
(param1 & INPUT_TRANSFORM_Y_INVERT &&
code_idx(event->code, cfg->y_codes, cfg->y_codes_size) >= 0)) {
event->value = -event->value;
}
return 0;
}
static struct zmk_input_processor_driver_api ipt_driver_api = {
.handle_event = ipt_handle_event,
};
static int ipt_init(const struct device *dev) { return 0; }
#define IPT_INST(n) \
static const uint16_t ipt_x_codes_##n[] = DT_INST_PROP(n, x_codes); \
static const uint16_t ipt_y_codes_##n[] = DT_INST_PROP(n, y_codes); \
BUILD_ASSERT(ARRAY_SIZE(ipt_x_codes_##n) == ARRAY_SIZE(ipt_x_codes_##n), \
"X and Y codes need to be the same size"); \
static const struct ipt_config ipt_config_##n = { \
.type = DT_INST_PROP_OR(n, type, INPUT_EV_REL), \
.x_codes_size = DT_INST_PROP_LEN(n, x_codes), \
.y_codes_size = DT_INST_PROP_LEN(n, y_codes), \
.x_codes = ipt_x_codes_##n, \
.y_codes = ipt_y_codes_##n, \
}; \
DEVICE_DT_INST_DEFINE(n, &ipt_init, NULL, NULL, &ipt_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &ipt_driver_api);
DT_INST_FOREACH_STATUS_OKAY(IPT_INST)

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_input_split
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/input/input.h>
#include <drivers/input_processor.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
struct zis_entry {
uint8_t reg;
const struct device *dev;
};
#define ZIS_ENTRY(n) {.reg = DT_INST_REG_ADDR(n), .dev = DEVICE_DT_GET(DT_DRV_INST(n))},
static const struct zis_entry proxy_inputs[] = {DT_INST_FOREACH_STATUS_OKAY(ZIS_ENTRY)};
int zmk_input_split_report_peripheral_event(uint8_t reg, uint8_t type, uint16_t code, int32_t value,
bool sync) {
LOG_DBG("Got peripheral event for %d!", reg);
for (size_t i = 0; i < ARRAY_SIZE(proxy_inputs); i++) {
if (reg == proxy_inputs[i].reg) {
return input_report(proxy_inputs[i].dev, type, code, value, sync, K_NO_WAIT);
}
}
return -ENODEV;
}
#define ZIS_INST(n) \
DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, NULL, POST_KERNEL, \
CONFIG_ZMK_INPUT_SPLIT_INIT_PRIORITY, NULL);
#else
#include <zmk/split/bluetooth/service.h>
#define ZIS_INST(n) \
static const struct zmk_input_processor_entry processors_##n[] = \
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, input_processors), \
({LISTIFY(DT_INST_PROP_LEN(n, input_processors), \
ZMK_INPUT_PROCESSOR_ENTRY_AT_IDX, (, ), DT_DRV_INST(n))}), \
({})); \
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(n, device), \
"Peripheral input splits need an `input` property set"); \
void split_input_handler_##n(struct input_event *evt) { \
for (size_t i = 0; i < ARRAY_SIZE(processors_##n); i++) { \
zmk_input_processor_handle_event(processors_##n[i].dev, evt, processors_##n[i].param1, \
processors_##n[i].param2, NULL); \
} \
zmk_split_bt_report_input(DT_INST_REG_ADDR(n), evt->type, evt->code, evt->value, \
evt->sync); \
} \
INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(n, device)), split_input_handler_##n);
#endif
DT_INST_FOREACH_STATUS_OKAY(ZIS_INST)

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zmk/ble.h>
#include <zmk/endpoints.h>
#include <zmk/pointing/resolution_multipliers.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
static struct zmk_pointing_resolution_multipliers multipliers[ZMK_ENDPOINT_COUNT];
struct zmk_pointing_resolution_multipliers
zmk_pointing_resolution_multipliers_get_current_profile(void) {
return zmk_pointing_resolution_multipliers_get_profile(zmk_endpoints_selected());
}
struct zmk_pointing_resolution_multipliers
zmk_pointing_resolution_multipliers_get_profile(struct zmk_endpoint_instance endpoint) {
const int profile = zmk_endpoint_instance_to_index(endpoint);
return multipliers[profile];
}
void zmk_pointing_resolution_multipliers_set_profile(struct zmk_pointing_resolution_multipliers m,
struct zmk_endpoint_instance endpoint) {
int profile = zmk_endpoint_instance_to_index(endpoint);
// This write is not happening on the main thread. To prevent potential data races, every
// operation involving hid_indicators must be atomic. Currently, each function either reads
// or writes only one entry at a time, so it is safe to do these operations without a lock.
multipliers[profile] = m;
}
void zmk_pointing_resolution_multipliers_process_report(
struct zmk_hid_mouse_resolution_feature_report_body *report,
struct zmk_endpoint_instance endpoint) {
struct zmk_pointing_resolution_multipliers vals = {
.wheel = report->wheel_res,
.hor_wheel = report->hwheel_res,
};
zmk_pointing_resolution_multipliers_set_profile(vals, endpoint);
LOG_DBG("Update resolution multipliers: endpoint=%d, wheel=%d, hor_wheel=%d",
endpoint.transport, vals.wheel, vals.hor_wheel);
}