feat(mouse): Add mouse move and scroll support [WIP]

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>
This commit is contained in:
Cem Aksoylar 2023-11-15 21:11:41 -08:00 committed by kB01
parent 6b4d591c37
commit fe32108532
30 changed files with 821 additions and 90 deletions

View file

@ -27,6 +27,9 @@ target_sources(app PRIVATE src/behavior.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_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/key_listener.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/main.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/tick_listener.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)
@ -37,6 +40,9 @@ target_sources(app PRIVATE src/events/activity_state_changed.c)
target_sources(app PRIVATE src/events/position_state_changed.c)
target_sources(app PRIVATE src/events/sensor_event.c)
target_sources(app PRIVATE src/events/mouse_button_state_changed.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/events/mouse_move_state_changed.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/events/mouse_tick.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/events/mouse_scroll_state_changed.c)
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c)
target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c)
target_sources(app PRIVATE src/behaviors/behavior_reset.c)
@ -44,7 +50,7 @@ target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SOFT_OFF app PRIVATE src/behaviors/behavior_soft_off.c)
if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/hid.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/main.c)
target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_HOLD_TAP app PRIVATE src/behaviors/behavior_hold_tap.c)
@ -64,6 +70,8 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON app PRIVATE src/behaviors/behavior_sensor_rotate_common.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_MOUSE_KEY_PRESS app PRIVATE src/behaviors/behavior_mouse_key_press.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_STUDIO_UNLOCK app PRIVATE src/behaviors/behavior_studio_unlock.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/behaviors/behavior_mouse_move.c)
target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/behaviors/behavior_mouse_scroll.c)
target_sources(app PRIVATE src/combo.c)
target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c)
target_sources(app PRIVATE src/behavior_queue.c)

View file

@ -395,13 +395,7 @@ endif
#Display/LED Options
endmenu
menu "Mouse Options"
config ZMK_MOUSE
bool "Enable ZMK mouse emulation"
#Mouse Options
endmenu
rsource "src/mouse/Kconfig"
menu "Power Management"

View file

@ -22,3 +22,6 @@
#include <behaviors/mouse_key_press.dtsi>
#include <behaviors/soft_off.dtsi>
#include <behaviors/studio_unlock.dtsi>
#include <behaviors/mouse_move.dtsi>
#include <behaviors/mouse_scroll.dtsi>
#include <behaviors/macros.dtsi>

View file

@ -0,0 +1,12 @@
/ {
behaviors {
/omit-if-no-ref/ mmv: behavior_mouse_move {
compatible = "zmk,behavior-mouse-move";
label = "MOUSE_MOVE";
#binding-cells = <1>;
delay-ms = <0>;
time-to-max-speed-ms = <300>;
acceleration-exponent = <1>;
};
};
};

View file

@ -0,0 +1,12 @@
/ {
behaviors {
/omit-if-no-ref/ msc: behavior_mouse_scroll {
compatible = "zmk,behavior-mouse-scroll";
label = "MOUSE_SCROLL";
#binding-cells = <1>;
delay-ms = <0>;
time-to-max-speed-ms = <300>;
acceleration-exponent = <0>;
};
};
};

View file

@ -0,0 +1,13 @@
description: Mouse move
compatible: "zmk,behavior-mouse-move"
include: one_param.yaml
properties:
delay-ms:
type: int
time-to-max-speed-ms:
type: int
acceleration-exponent:
type: int

View file

@ -0,0 +1,13 @@
description: Mouse scroll
compatible: "zmk,behavior-mouse-scroll"
include: one_param.yaml
properties:
delay-ms:
type: int
time-to-max-speed-ms:
type: int
acceleration-exponent:
type: int

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 The ZMK Contributors
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
@ -22,3 +22,37 @@
#define MB4 BIT(3)
#define MB5 BIT(4)
#ifndef ZMK_MOUSE_DEFAULT_MOVE_VAL
#define ZMK_MOUSE_DEFAULT_MOVE_VAL 600
#endif
#ifndef ZMK_MOUSE_DEFAULT_SCRL_VAL
#define ZMK_MOUSE_DEFAULT_SCRL_VAL 10
#endif
/* Mouse move behavior */
#define MOVE_Y(vert) ((vert)&0xFFFF)
#define MOVE_Y_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF)
#define MOVE_X(hor) (((hor)&0xFFFF) << 16)
#define MOVE_X_DECODE(encoded) (int16_t)(((encoded)&0xFFFF0000) >> 16)
#define MOVE(hor, vert) (MOVE_X(hor) + MOVE_Y(vert))
#define MOVE_UP MOVE_Y(-ZMK_MOUSE_DEFAULT_MOVE_VAL)
#define MOVE_DOWN MOVE_Y(ZMK_MOUSE_DEFAULT_MOVE_VAL)
#define MOVE_LEFT MOVE_X(-ZMK_MOUSE_DEFAULT_MOVE_VAL)
#define MOVE_RIGHT MOVE_X(ZMK_MOUSE_DEFAULT_MOVE_VAL)
/* Mouse scroll behavior */
#define SCRL_Y(vert) ((vert)&0xFFFF)
#define SCRL_Y_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF)
#define SCRL_X(hor) (((hor)&0xFFFF) << 16)
#define SCRL_X_DECODE(encoded) (int16_t)(((encoded)&0xFFFF0000) >> 16)
#define SCROLL(hor, vert) (SCRL_X(hor) + SCRL_Y(vert))
#define SCRL_UP SCRL_Y(ZMK_MOUSE_DEFAULT_SCRL_VAL)
#define SCRL_DOWN SCRL_Y(-ZMK_MOUSE_DEFAULT_SCRL_VAL)
#define SCRL_LEFT SCRL_X(-ZMK_MOUSE_DEFAULT_SCRL_VAL)
#define SCRL_RIGHT SCRL_X(ZMK_MOUSE_DEFAULT_SCRL_VAL)

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <zmk/event_manager.h>
#include <zmk/mouse.h>
struct zmk_mouse_move_state_changed {
struct vector2d max_speed;
struct mouse_config config;
bool state;
int64_t timestamp;
};
ZMK_EVENT_DECLARE(zmk_mouse_move_state_changed);
static inline struct zmk_mouse_move_state_changed_event *
zmk_mouse_move_state_changed_from_encoded(uint32_t encoded, struct mouse_config config,
bool pressed, int64_t timestamp) {
struct vector2d max_speed = (struct vector2d){
.x = MOVE_X_DECODE(encoded),
.y = MOVE_Y_DECODE(encoded),
};
return new_zmk_mouse_move_state_changed((struct zmk_mouse_move_state_changed){
.max_speed = max_speed, .config = config, .state = pressed, .timestamp = timestamp});
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <zmk/event_manager.h>
#include <zmk/mouse.h>
#include <dt-bindings/zmk/mouse.h>
struct zmk_mouse_scroll_state_changed {
struct vector2d max_speed;
struct mouse_config config;
bool state;
int64_t timestamp;
};
ZMK_EVENT_DECLARE(zmk_mouse_scroll_state_changed);
static inline struct zmk_mouse_scroll_state_changed_event *
zmk_mouse_scroll_state_changed_from_encoded(uint32_t encoded, struct mouse_config config,
bool pressed, int64_t timestamp) {
struct vector2d max_speed = (struct vector2d){
.x = SCRL_X_DECODE(encoded),
.y = SCRL_Y_DECODE(encoded),
};
return new_zmk_mouse_scroll_state_changed((struct zmk_mouse_scroll_state_changed){
.max_speed = max_speed, .config = config, .state = pressed, .timestamp = timestamp});
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <dt-bindings/zmk/mouse.h>
#include <zephyr/kernel.h>
#include <zmk/event_manager.h>
#include <zmk/mouse.h>
struct zmk_mouse_tick {
struct vector2d max_move;
struct vector2d max_scroll;
struct mouse_config move_config;
struct mouse_config scroll_config;
int64_t *start_time;
int64_t timestamp;
};
ZMK_EVENT_DECLARE(zmk_mouse_tick);
static inline struct zmk_mouse_tick_event *zmk_mouse_tick(struct vector2d max_move,
struct vector2d max_scroll,
struct mouse_config move_config,
struct mouse_config scroll_config,
int64_t *movement_start) {
return new_zmk_mouse_tick((struct zmk_mouse_tick){
.max_move = max_move,
.max_scroll = max_scroll,
.move_config = move_config,
.scroll_config = scroll_config,
.start_time = movement_start,
.timestamp = k_uptime_get(),
});
}

View file

@ -77,6 +77,10 @@
#define ZMK_HID_REPORT_ID_CONSUMER 0x02
#define ZMK_HID_REPORT_ID_MOUSE 0x03
// Needed until Zephyr offers a 2 byte usage macro
#define HID_USAGE16(idx) \
HID_ITEM(HID_ITEM_TAG_USAGE, HID_ITEM_TYPE_LOCAL, 2), (idx & 0xFF), (idx >> 8 & 0xFF)
static const uint8_t zmk_hid_report_desc[] = {
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP),
HID_USAGE(HID_USAGE_GD_KEYBOARD),
@ -185,11 +189,18 @@ static const uint8_t zmk_hid_report_desc[] = {
HID_USAGE(HID_USAGE_GD_X),
HID_USAGE(HID_USAGE_GD_Y),
HID_USAGE(HID_USAGE_GD_WHEEL),
HID_LOGICAL_MIN8(-0x7F),
HID_LOGICAL_MAX8(0x7F),
HID_REPORT_SIZE(0x08),
HID_LOGICAL_MIN16(0xFF, -0x7F),
HID_LOGICAL_MAX16(0xFF, 0x7F),
HID_REPORT_SIZE(0x10),
HID_REPORT_COUNT(0x03),
HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_REL),
HID_USAGE_PAGE(HID_USAGE_CONSUMER),
HID_USAGE16(HID_USAGE_CONSUMER_AC_PAN),
HID_LOGICAL_MIN16(0xFF, -0x7F),
HID_LOGICAL_MAX16(0xFF, 0x7F),
HID_REPORT_SIZE(0x08),
HID_REPORT_COUNT(0x01),
HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_REL),
HID_END_COLLECTION,
HID_END_COLLECTION,
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
@ -258,9 +269,10 @@ struct zmk_hid_consumer_report {
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
struct zmk_hid_mouse_report_body {
zmk_mouse_button_flags_t buttons;
int8_t d_x;
int8_t d_y;
int8_t d_wheel;
int16_t d_x;
int16_t d_y;
int16_t d_scroll_y;
int16_t d_scroll_x;
} __packed;
struct zmk_hid_mouse_report {
@ -301,6 +313,10 @@ int zmk_hid_mouse_button_press(zmk_mouse_button_t button);
int zmk_hid_mouse_button_release(zmk_mouse_button_t button);
int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons);
int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons);
void zmk_hid_mouse_movement_set(int16_t x, int16_t y);
void zmk_hid_mouse_scroll_set(int8_t x, int8_t y);
void zmk_hid_mouse_movement_update(int16_t x, int16_t y);
void zmk_hid_mouse_scroll_update(int8_t x, int8_t y);
void zmk_hid_mouse_clear(void);
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 The ZMK Contributors
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
@ -10,3 +10,20 @@
typedef uint8_t zmk_mouse_button_flags_t;
typedef uint16_t zmk_mouse_button_t;
struct mouse_config {
int delay_ms;
int time_to_max_speed_ms;
// acceleration exponent 0: uniform speed
// acceleration exponent 1: uniform acceleration
// acceleration exponent 2: uniform jerk
int acceleration_exponent;
};
struct vector2d {
float x;
float y;
};
struct k_work_q *zmk_mouse_work_q();
int zmk_mouse_init();

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_mouse_move
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/behavior.h>
#include <zmk/event_manager.h>
#include <zmk/events/mouse_move_state_changed.h>
#include <zmk/mouse.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
static int behavior_mouse_move_init(const struct device *dev) { return 0; };
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct mouse_config *config = dev->config;
return ZMK_EVENT_RAISE(
zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, true, event.timestamp));
}
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct mouse_config *config = dev->config;
return ZMK_EVENT_RAISE(zmk_mouse_move_state_changed_from_encoded(binding->param1, *config,
false, event.timestamp));
}
static const struct behavior_driver_api behavior_mouse_move_driver_api = {
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
#define KP_INST(n) \
static struct mouse_config behavior_mouse_move_config_##n = { \
.delay_ms = DT_INST_PROP(n, delay_ms), \
.time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \
.acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \
}; \
DEVICE_DT_INST_DEFINE(n, behavior_mouse_move_init, NULL, NULL, \
&behavior_mouse_move_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_move_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_mouse_scroll
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/event_manager.h>
#include <zmk/events/mouse_scroll_state_changed.h>
#include <zmk/behavior.h>
#include <zmk/hid.h>
#include <zmk/endpoints.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
static int behavior_mouse_scroll_init(const struct device *dev) { return 0; };
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct mouse_config *config = dev->config;
return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config,
true, event.timestamp));
}
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct mouse_config *config = dev->config;
return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config,
false, event.timestamp));
}
static const struct behavior_driver_api behavior_mouse_scroll_driver_api = {
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
#define KP_INST(n) \
static struct mouse_config behavior_mouse_scroll_config_##n = { \
.delay_ms = DT_INST_PROP(n, delay_ms), \
.time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \
.acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \
}; \
DEVICE_DT_INST_DEFINE(n, behavior_mouse_scroll_init, NULL, NULL, \
&behavior_mouse_scroll_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_scroll_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/mouse_move_state_changed.h>
ZMK_EVENT_IMPL(zmk_mouse_move_state_changed);

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/mouse_scroll_state_changed.h>
ZMK_EVENT_IMPL(zmk_mouse_scroll_state_changed);

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/mouse_tick.h>
ZMK_EVENT_IMPL(zmk_mouse_tick);

View file

@ -27,8 +27,9 @@ static uint8_t keys_held = 0;
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
static struct zmk_hid_mouse_report mouse_report = {.report_id = ZMK_HID_REPORT_ID_MOUSE,
.body = {.buttons = 0}};
static struct zmk_hid_mouse_report mouse_report = {
.report_id = ZMK_HID_REPORT_ID_MOUSE,
.body = {.buttons = 0, .d_x = 0, .d_y = 0, .d_scroll_y = 0}};
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
@ -431,6 +432,34 @@ int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) {
}
return 0;
}
void zmk_hid_mouse_movement_set(int16_t x, int16_t y) {
mouse_report.body.d_x = x;
mouse_report.body.d_y = y;
LOG_DBG("Mouse movement set to 0x%02X 0x%02X ", mouse_report.body.d_x, mouse_report.body.d_y);
}
void zmk_hid_mouse_movement_update(int16_t x, int16_t y) {
mouse_report.body.d_x += x;
mouse_report.body.d_y += y;
LOG_DBG("Mouse movement updated to 0x%02X 0x%02X ", mouse_report.body.d_x,
mouse_report.body.d_y);
}
void zmk_hid_mouse_scroll_set(int8_t x, int8_t y) {
mouse_report.body.d_scroll_x = x;
mouse_report.body.d_scroll_y = y;
LOG_DBG("Mouse scroll set to X: 0x%02X Y: 0x%02X", mouse_report.body.d_scroll_x,
mouse_report.body.d_scroll_y);
}
void zmk_hid_mouse_scroll_update(int8_t x, int8_t y) {
mouse_report.body.d_scroll_x += x;
mouse_report.body.d_scroll_y += y;
LOG_DBG("Mouse scroll updated to X: 0x%02X Y: 0x%02X", mouse_report.body.d_scroll_x,
mouse_report.body.d_scroll_y);
}
void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); }
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)

View file

@ -380,7 +380,6 @@ int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) {
return 0;
};
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
static int zmk_hog_init(void) {

View file

@ -14,6 +14,10 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/display.h>
#ifdef CONFIG_ZMK_MOUSE
#include <zmk/mouse.h>
#endif /* CONFIG_ZMK_MOUSE */
int main(void) {
LOG_INF("Welcome to ZMK!\n");
@ -26,5 +30,8 @@ int main(void) {
zmk_display_init();
#endif /* CONFIG_ZMK_DISPLAY */
#ifdef CONFIG_ZMK_MOUSE
zmk_mouse_init();
#endif /* CONFIG_ZMK_MOUSE */
return 0;
}

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/events/mouse_button_state_changed.h>
#include <zmk/hid.h>
#include <zmk/endpoints.h>
#include <zmk/mouse.h>
static void listener_mouse_button_pressed(const struct zmk_mouse_button_state_changed *ev) {
LOG_DBG("buttons: 0x%02X", ev->buttons);
zmk_hid_mouse_buttons_press(ev->buttons);
zmk_endpoints_send_mouse_report();
}
static void listener_mouse_button_released(const struct zmk_mouse_button_state_changed *ev) {
LOG_DBG("buttons: 0x%02X", ev->buttons);
zmk_hid_mouse_buttons_release(ev->buttons);
zmk_endpoints_send_mouse_report();
}
int mouse_listener(const zmk_event_t *eh) {
const struct zmk_mouse_button_state_changed *mbt_ev = as_zmk_mouse_button_state_changed(eh);
if (mbt_ev) {
if (mbt_ev->state) {
listener_mouse_button_pressed(mbt_ev);
} else {
listener_mouse_button_released(mbt_ev);
}
return 0;
}
return 0;
}
ZMK_LISTENER(mouse_listener, mouse_listener);
ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_button_state_changed);

38
app/src/mouse/Kconfig Normal file
View file

@ -0,0 +1,38 @@
# Copyright (c) 2023 The ZMK Contributors
# SPDX-License-Identifier: MIT
menuconfig ZMK_MOUSE
bool "Mouse Emulation"
if ZMK_MOUSE
config ZMK_MOUSE_TICK_DURATION
int "Mouse tick duration in ms"
default 8
choice ZMK_MOUSE_WORK_QUEUE
prompt "Work queue selection for mouse events"
default ZMK_MOUSE_WORK_QUEUE_DEDICATED
config ZMK_MOUSE_WORK_QUEUE_SYSTEM
bool "Use default system work queue for mouse events"
config ZMK_MOUSE_WORK_QUEUE_DEDICATED
bool "Use dedicated work queue for mouse events"
endchoice
if ZMK_MOUSE_WORK_QUEUE_DEDICATED
config ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE
int "Stack size for dedicated mouse thread/queue"
default 2048
config ZMK_MOUSE_DEDICATED_THREAD_PRIORITY
int "Thread priority for dedicated mouse thread/queue"
default 3
endif # ZMK_MOUSE_WORK_QUEUE_DEDICATED
endif

View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/mouse_button_state_changed.h>
#include <zmk/events/mouse_move_state_changed.h>
#include <zmk/events/mouse_scroll_state_changed.h>
#include <zmk/events/mouse_tick.h>
#include <zmk/hid.h>
#include <zmk/endpoints.h>
#include <zmk/mouse.h>
#if !defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
static struct vector2d move_speed = {0};
static struct vector2d scroll_speed = {0};
static struct mouse_config move_config = (struct mouse_config){0};
static struct mouse_config scroll_config = (struct mouse_config){0};
static int64_t start_time = 0;
bool equals(const struct mouse_config *one, const struct mouse_config *other) {
return one->delay_ms == other->delay_ms &&
one->time_to_max_speed_ms == other->time_to_max_speed_ms &&
one->acceleration_exponent == other->acceleration_exponent;
}
static void clear_mouse_state(struct k_work *work) {
move_speed = (struct vector2d){0};
scroll_speed = (struct vector2d){0};
start_time = 0;
zmk_hid_mouse_movement_set(0, 0);
zmk_hid_mouse_scroll_set(0, 0);
LOG_DBG("Clearing state");
}
K_WORK_DEFINE(mouse_clear, &clear_mouse_state);
void mouse_clear_cb(struct k_timer *dummy) {
k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_clear);
}
static void mouse_tick_timer_handler(struct k_work *work) {
zmk_hid_mouse_movement_set(0, 0);
zmk_hid_mouse_scroll_set(0, 0);
LOG_DBG("Raising mouse tick event");
ZMK_EVENT_RAISE(
zmk_mouse_tick(move_speed, scroll_speed, move_config, scroll_config, &start_time));
zmk_endpoints_send_mouse_report();
}
K_WORK_DEFINE(mouse_tick, &mouse_tick_timer_handler);
void mouse_timer_cb(struct k_timer *dummy) {
LOG_DBG("Submitting mouse work to queue");
k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_tick);
}
K_TIMER_DEFINE(mouse_timer, mouse_timer_cb, mouse_clear_cb);
static int mouse_timer_ref_count = 0;
void mouse_timer_ref() {
if (mouse_timer_ref_count == 0) {
start_time = k_uptime_get();
k_timer_start(&mouse_timer, K_NO_WAIT, K_MSEC(CONFIG_ZMK_MOUSE_TICK_DURATION));
}
mouse_timer_ref_count += 1;
}
void mouse_timer_unref() {
if (mouse_timer_ref_count > 0) {
mouse_timer_ref_count--;
}
if (mouse_timer_ref_count == 0) {
k_timer_stop(&mouse_timer);
}
}
static void listener_mouse_move_pressed(const struct zmk_mouse_move_state_changed *ev) {
move_speed.x += ev->max_speed.x;
move_speed.y += ev->max_speed.y;
mouse_timer_ref();
}
static void listener_mouse_move_released(const struct zmk_mouse_move_state_changed *ev) {
move_speed.x -= ev->max_speed.x;
move_speed.y -= ev->max_speed.y;
mouse_timer_unref();
}
static void listener_mouse_scroll_pressed(const struct zmk_mouse_scroll_state_changed *ev) {
scroll_speed.x += ev->max_speed.x;
scroll_speed.y += ev->max_speed.y;
mouse_timer_ref();
}
static void listener_mouse_scroll_released(const struct zmk_mouse_scroll_state_changed *ev) {
scroll_speed.x -= ev->max_speed.x;
scroll_speed.y -= ev->max_speed.y;
mouse_timer_unref();
}
static void listener_mouse_button_pressed(const struct zmk_mouse_button_state_changed *ev) {
LOG_DBG("buttons: 0x%02X", ev->buttons);
zmk_hid_mouse_buttons_press(ev->buttons);
zmk_endpoints_send_mouse_report();
}
static void listener_mouse_button_released(const struct zmk_mouse_button_state_changed *ev) {
LOG_DBG("buttons: 0x%02X", ev->buttons);
zmk_hid_mouse_buttons_release(ev->buttons);
zmk_endpoints_send_mouse_report();
}
int mouse_listener(const zmk_event_t *eh) {
const struct zmk_mouse_move_state_changed *mmv_ev = as_zmk_mouse_move_state_changed(eh);
if (mmv_ev) {
if (!equals(&move_config, &(mmv_ev->config)))
move_config = mmv_ev->config;
if (mmv_ev->state) {
listener_mouse_move_pressed(mmv_ev);
} else {
listener_mouse_move_released(mmv_ev);
}
return 0;
}
const struct zmk_mouse_scroll_state_changed *msc_ev = as_zmk_mouse_scroll_state_changed(eh);
if (msc_ev) {
if (!equals(&scroll_config, &(msc_ev->config)))
scroll_config = msc_ev->config;
if (msc_ev->state) {
listener_mouse_scroll_pressed(msc_ev);
} else {
listener_mouse_scroll_released(msc_ev);
}
return 0;
}
const struct zmk_mouse_button_state_changed *mbt_ev = as_zmk_mouse_button_state_changed(eh);
if (mbt_ev) {
if (mbt_ev->state) {
listener_mouse_button_pressed(mbt_ev);
} else {
listener_mouse_button_released(mbt_ev);
}
return 0;
}
return 0;
}
ZMK_LISTENER(mouse_listener, mouse_listener);
ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_button_state_changed);
ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_move_state_changed);
ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_scroll_state_changed);
#endif /*!defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */

30
app/src/mouse/main.c Normal file
View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/mouse.h>
#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED)
K_THREAD_STACK_DEFINE(mouse_work_stack_area, CONFIG_ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE);
static struct k_work_q mouse_work_q;
#endif
struct k_work_q *zmk_mouse_work_q() {
#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED)
return &mouse_work_q;
#else
return &k_sys_work_q;
#endif
}
int zmk_mouse_init() {
#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED)
k_work_queue_start(&mouse_work_q, mouse_work_stack_area,
K_THREAD_STACK_SIZEOF(mouse_work_stack_area),
CONFIG_ZMK_MOUSE_DEDICATED_THREAD_PRIORITY, NULL);
#endif
return 0;
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/mouse_tick.h>
#include <zmk/endpoints.h>
#include <zmk/hid.h>
#include <zmk/mouse.h>
#include <zephyr/sys/util.h> // CLAMP
#if !defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
#if CONFIG_MINIMAL_LIBC
static float powf(float base, float exponent) {
// poor man's power implementation rounds the exponent down to the nearest integer.
float power = 1.0f;
for (; exponent >= 1.0f; exponent--) {
power = power * base;
}
return power;
}
#else
#include <math.h>
#endif
struct vector2d move_remainder = {0};
struct vector2d scroll_remainder = {0};
static int64_t ms_since_start(int64_t start, int64_t now, int64_t delay) {
int64_t move_duration = now - (start + delay);
// start can be in the future if there's a delay
if (move_duration < 0) {
move_duration = 0;
}
return move_duration;
}
static float speed(const struct mouse_config *config, float max_speed, int64_t duration_ms) {
// Calculate the speed based on MouseKeysAccel
// See https://en.wikipedia.org/wiki/Mouse_keys
if (duration_ms > config->time_to_max_speed_ms || config->time_to_max_speed_ms == 0 ||
config->acceleration_exponent == 0) {
return max_speed;
}
float time_fraction = (float)duration_ms / config->time_to_max_speed_ms;
return max_speed * powf(time_fraction, config->acceleration_exponent);
}
static void track_remainder(float *move, float *remainder) {
float new_move = *move + *remainder;
*remainder = new_move - (int)new_move;
*move = (int)new_move;
}
static struct vector2d update_movement(struct vector2d *remainder,
const struct mouse_config *config, struct vector2d max_speed,
int64_t now, int64_t *start_time) {
struct vector2d move = {0};
if (max_speed.x == 0 && max_speed.y == 0) {
*remainder = (struct vector2d){0};
return move;
}
int64_t move_duration = ms_since_start(*start_time, now, config->delay_ms);
move = (struct vector2d){
.x = speed(config, max_speed.x, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000,
.y = speed(config, max_speed.y, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000,
};
track_remainder(&(move.x), &(remainder->x));
track_remainder(&(move.y), &(remainder->y));
return move;
}
static void mouse_tick_handler(const struct zmk_mouse_tick *tick) {
struct vector2d move = update_movement(&move_remainder, &(tick->move_config), tick->max_move,
tick->timestamp, tick->start_time);
zmk_hid_mouse_movement_update((int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX),
(int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX));
struct vector2d scroll = update_movement(&scroll_remainder, &(tick->scroll_config),
tick->max_scroll, tick->timestamp, tick->start_time);
zmk_hid_mouse_scroll_update((int8_t)CLAMP(scroll.x, INT8_MIN, INT8_MAX),
(int8_t)CLAMP(scroll.y, INT8_MIN, INT8_MAX));
}
int zmk_mouse_tick_listener(const zmk_event_t *eh) {
const struct zmk_mouse_tick *tick = as_zmk_mouse_tick(eh);
if (tick) {
mouse_tick_handler(tick);
return 0;
}
return 0;
}
ZMK_LISTENER(zmk_mouse_tick_listener, zmk_mouse_tick_listener);
ZMK_SUBSCRIPTION(zmk_mouse_tick_listener, zmk_mouse_tick);
#endif /* !defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */

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,26 @@
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/kscan_mock.h>
#include <dt-bindings/zmk/mouse.h>
/ {
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&mmv MOVE_LEFT &none
&none &none
>;
};
};
};
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,100)
ZMK_MOCK_RELEASE(0,0,10)
>;
};

View file

@ -15,28 +15,28 @@ ZMK is currently missing some features found in other popular firmware. This tab
| :------ | :----------- | :------------------- | :--------- |
| **Feature** | ZMK | BlueMicro | QMK |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | :-: | :-------: | :-: |
| ---------------------------------------------------------------------------------------------------------------------------------- | :-: | :-------: | :-: |
| Low Latency BLE Support | ✅ | ✅ | |
| Multi-Device BLE Support | ✅ | | |
| [USB Connectivity](keymaps/behaviors/outputs.md) | ✅ | ✅ | ✅ |
| [USB Connectivity](behaviors/outputs.md) | ✅ | ✅ | ✅ |
| User Configuration Repositories | ✅ | | |
| Split Keyboard Support | ✅ | ✅ | ✅ |
| [Keymaps and Layers](keymaps/behaviors/layers.md) | ✅ | ✅ | ✅ |
| [Hold-Tap](keymaps/behaviors/hold-tap.mdx) (which includes [Mod-Tap](keymaps/behaviors/mod-tap.md) and [Layer-Tap](keymaps/behaviors/layers.md#layer-tap)) | ✅ | ✅ | ✅ |
| [Tap-Dance](keymaps/behaviors/tap-dance.mdx) | ✅ | ✅[^2] | ✅ |
| [Keyboard Keycodes](keymaps/list-of-keycodes.mdx#keyboard) | ✅ | ✅ | ✅ |
| [Media](keymaps/list-of-keycodes.mdx#media-controls) & [Consumer](keymaps/list-of-keycodes.mdx#consumer-controls) Codes | ✅ | ✅ | ✅ |
| [Keymaps and Layers](behaviors/layers.md) | ✅ | ✅ | ✅ |
| [Hold-Tap](behaviors/hold-tap.mdx) (which includes [Mod-Tap](behaviors/mod-tap.md) and [Layer-Tap](behaviors/layers.md#layer-tap)) | ✅ | ✅ | ✅ |
| [Tap-Dance](behaviors/tap-dance.mdx) | ✅ | ✅[^2] | ✅ |
| [Keyboard Codes](codes/index.mdx#keyboard) | ✅ | ✅ | ✅ |
| [Media](codes/index.mdx#media-controls) & [Consumer](codes/index.mdx#consumer-controls) Codes | ✅ | ✅ | ✅ |
| [Encoders](features/encoders.md) | ✅ | ✅ | ✅ |
| [Display Support](features/displays.md)[^1] | 🚧 | 🚧 | ✅ |
| [RGB Underglow](features/underglow.md) | ✅ | ✅ | ✅ |
| [Backlight](features/backlight.mdx) | ✅ | ✅ | ✅ |
| One Shot Keys | ✅ | ✅ | ✅ |
| [Combo Keys](keymaps/combos.md) | ✅ | | ✅ |
| [Macros](keymaps/behaviors/macros.md) | ✅ | ✅ | ✅ |
| Mouse Keys | 🚧 | ✅ | ✅ |
| [Combo Keys](features/combos.md) | ✅ | | ✅ |
| [Macros](behaviors/macros.md) | ✅ | ✅ | ✅ |
| Mouse Keys | ✅ | ✅ | ✅ |
| Low Active Power Usage | ✅ | | |
| Low Power Sleep States | ✅ | ✅ | |
| [Low Power Mode (VCC Shutoff)](keymaps/behaviors/power.md) | ✅ | ✅ | |
| [Low Power Mode (VCC Shutoff)](behaviors/power.md) | ✅ | ✅ | |
| Battery Reporting | ✅ | ✅ | |
| Shell over BLE | 💡 | | |
| Realtime Keymap Updating | 💡 | | ✅ |