feat(mouse keys): add events, smoothing and acceleration
This commit is contained in:
parent
728f42e27d
commit
8fc5962251
33 changed files with 653 additions and 268 deletions
|
@ -27,6 +27,8 @@ target_sources(app PRIVATE src/activity.c)
|
||||||
target_sources(app PRIVATE src/kscan.c)
|
target_sources(app PRIVATE src/kscan.c)
|
||||||
target_sources(app PRIVATE src/matrix_transform.c)
|
target_sources(app PRIVATE src/matrix_transform.c)
|
||||||
target_sources(app PRIVATE src/hid.c)
|
target_sources(app PRIVATE src/hid.c)
|
||||||
|
target_sources(app PRIVATE src/mouse/key_listener.c)
|
||||||
|
target_sources(app PRIVATE src/mouse/tick_listener.c)
|
||||||
target_sources(app PRIVATE src/sensors.c)
|
target_sources(app PRIVATE src/sensors.c)
|
||||||
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c)
|
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c)
|
||||||
target_sources(app PRIVATE src/event_manager.c)
|
target_sources(app PRIVATE src/event_manager.c)
|
||||||
|
@ -38,7 +40,10 @@ target_sources(app PRIVATE src/events/keycode_state_changed.c)
|
||||||
target_sources(app PRIVATE src/events/modifiers_state_changed.c)
|
target_sources(app PRIVATE src/events/modifiers_state_changed.c)
|
||||||
target_sources(app PRIVATE src/events/endpoint_selection_changed.c)
|
target_sources(app PRIVATE src/events/endpoint_selection_changed.c)
|
||||||
target_sources(app PRIVATE src/events/sensor_event.c)
|
target_sources(app PRIVATE src/events/sensor_event.c)
|
||||||
target_sources(app PRIVATE src/events/mouse_state_changed.c)
|
target_sources(app PRIVATE src/events/mouse_button_state_changed.c)
|
||||||
|
target_sources(app PRIVATE src/events/mouse_move_state_changed.c)
|
||||||
|
target_sources(app PRIVATE src/events/mouse_tick.c)
|
||||||
|
target_sources(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_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c)
|
||||||
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c)
|
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c)
|
||||||
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c)
|
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c)
|
||||||
|
@ -59,7 +64,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
|
||||||
target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c)
|
target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c)
|
||||||
target_sources(app PRIVATE src/behaviors/behavior_mouse_key_press.c)
|
target_sources(app PRIVATE src/behaviors/behavior_mouse_key_press.c)
|
||||||
target_sources(app PRIVATE src/behaviors/behavior_mouse_move.c)
|
target_sources(app PRIVATE src/behaviors/behavior_mouse_move.c)
|
||||||
target_sources(app PRIVATE src/behaviors/behavior_mouse_wheel.c)
|
target_sources(app PRIVATE src/behaviors/behavior_mouse_scroll.c)
|
||||||
target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c)
|
target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c)
|
||||||
target_sources(app PRIVATE src/combo.c)
|
target_sources(app PRIVATE src/combo.c)
|
||||||
target_sources(app PRIVATE src/conditional_layer.c)
|
target_sources(app PRIVATE src/conditional_layer.c)
|
||||||
|
|
|
@ -17,4 +17,4 @@
|
||||||
#include <behaviors/caps_word.dtsi>
|
#include <behaviors/caps_word.dtsi>
|
||||||
#include <behaviors/mouse_key_press.dtsi>
|
#include <behaviors/mouse_key_press.dtsi>
|
||||||
#include <behaviors/mouse_move.dtsi>
|
#include <behaviors/mouse_move.dtsi>
|
||||||
#include <behaviors/mouse_wheel.dtsi>
|
#include <behaviors/mouse_scroll.dtsi>
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
compatible = "zmk,behavior-mouse-move";
|
compatible = "zmk,behavior-mouse-move";
|
||||||
label = "MOUSE_MOVE";
|
label = "MOUSE_MOVE";
|
||||||
#binding-cells = <1>;
|
#binding-cells = <1>;
|
||||||
|
delay-ms = <100>;
|
||||||
|
time-to-max-speed-ms = <1000>;
|
||||||
|
acceleration-exponent = <2000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
12
app/dts/behaviors/mouse_scroll.dtsi
Normal file
12
app/dts/behaviors/mouse_scroll.dtsi
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/ {
|
||||||
|
behaviors {
|
||||||
|
/omit-if-no-ref/ mwh: msc: behavior_mouse_scroll {
|
||||||
|
compatible = "zmk,behavior-mouse-scroll";
|
||||||
|
label = "MOUSE_SCROLL";
|
||||||
|
#binding-cells = <1>;
|
||||||
|
delay-ms = <0>;
|
||||||
|
time-to-max-speed-ms = <1000>;
|
||||||
|
acceleration-exponent = <0>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,9 +0,0 @@
|
||||||
/ {
|
|
||||||
behaviors {
|
|
||||||
/omit-if-no-ref/ mwh: behavior_mouse_wheel {
|
|
||||||
compatible = "zmk,behavior-mouse-wheel";
|
|
||||||
label = "MOUSE_WHEEL";
|
|
||||||
#binding-cells = <1>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -3,3 +3,11 @@ description: Mouse move
|
||||||
compatible: "zmk,behavior-mouse-move"
|
compatible: "zmk,behavior-mouse-move"
|
||||||
|
|
||||||
include: one_param.yaml
|
include: one_param.yaml
|
||||||
|
|
||||||
|
properties:
|
||||||
|
delay-ms:
|
||||||
|
type: int
|
||||||
|
time-to-max-speed-ms:
|
||||||
|
type: int
|
||||||
|
acceleration-exponent:
|
||||||
|
type: int
|
||||||
|
|
13
app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml
Normal file
13
app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml
Normal 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
|
|
@ -1,5 +0,0 @@
|
||||||
description: Mouse wheel
|
|
||||||
|
|
||||||
compatible: "zmk,behavior-mouse-wheel"
|
|
||||||
|
|
||||||
include: one_param.yaml
|
|
|
@ -29,35 +29,27 @@
|
||||||
#define MB8 (0x80)
|
#define MB8 (0x80)
|
||||||
|
|
||||||
/* Mouse move behavior */
|
/* Mouse move behavior */
|
||||||
|
#define MOVE_VERT(vert) ((vert)&0xFFFF)
|
||||||
#define MOVE_UP MOVE_VERT(1)
|
#define MOVE_VERT_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF)
|
||||||
|
|
||||||
#define MOVE_DOWN MOVE_VERT(-1)
|
|
||||||
|
|
||||||
#define MOVE_LEFT MOVE_HOR(-1)
|
|
||||||
|
|
||||||
#define MOVE_RIGHT MOVE_HOR(1)
|
|
||||||
|
|
||||||
/* -32767 to 32767, barely usable beyond about 50 (probably depends on screen resolution) */
|
|
||||||
#define MOVE_VERT(vert) ((-(vert)) & 0xFFFF)
|
|
||||||
|
|
||||||
#define MOVE_HOR(hor) (((hor)&0xFFFF) << 16)
|
#define MOVE_HOR(hor) (((hor)&0xFFFF) << 16)
|
||||||
|
#define MOVE_HOR_DECODE(encoded) (int16_t)(((encoded)&0xFFFF0000) >> 16)
|
||||||
|
|
||||||
#define MOVE(hor, vert) (MOVE_HOR(hor) + MOVE_VERT(vert))
|
#define MOVE(hor, vert) (MOVE_HOR(hor) + MOVE_VERT(vert))
|
||||||
|
|
||||||
/* Mouse wheel behavior */
|
#define MOVE_UP MOVE_VERT(-600)
|
||||||
|
#define MOVE_DOWN MOVE_VERT(600)
|
||||||
|
#define MOVE_LEFT MOVE_HOR(-600)
|
||||||
|
#define MOVE_RIGHT MOVE_HOR(600)
|
||||||
|
|
||||||
#define WHEEL_UP WHEEL_VERT(1)
|
/* Mouse scroll behavior */
|
||||||
|
#define SCROLL_VERT(vert) ((vert)&0xFFFF)
|
||||||
|
#define SCROLL_VERT_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF)
|
||||||
|
#define SCROLL_HOR(hor) (((hor)&0xFFFF) << 16)
|
||||||
|
#define SCROLL_HOR_DECODE(encoded) (int16_t)(((encoded)&0xFFFF0000) >> 16)
|
||||||
|
|
||||||
#define WHEEL_DOWN WHEEL_VERT(-1)
|
#define SCROLL(hor, vert) (SCROLL_HOR(hor) + SCROLL_VERT(vert))
|
||||||
|
|
||||||
#define WHEEL_LEFT WHEEL_HOR(-1)
|
#define SCROLL_UP SCROLL_VERT(10)
|
||||||
|
#define SCROLL_DOWN SCROLL_VERT(-10)
|
||||||
#define WHEEL_RIGHT WHEEL_HOR(1)
|
#define SCROLL_LEFT SCROLL_HOR(-10)
|
||||||
|
#define SCROLL_RIGHT SCROLL_HOR(10)
|
||||||
/* -127 to 127, barely usable beyond about 10 */
|
|
||||||
#define WHEEL_VERT(vert) ((vert)&0xFF)
|
|
||||||
|
|
||||||
#define WHEEL_HOR(hor) (((hor)&0xFF) << 8)
|
|
||||||
|
|
||||||
#define WHEEL(hor, vert) (WHEEL_HOR(hor) + WHEEL_VERT(vert))
|
|
||||||
|
|
27
app/include/zmk/events/mouse_button_state_changed.h
Normal file
27
app/include/zmk/events/mouse_button_state_changed.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zephyr.h>
|
||||||
|
#include <zmk/hid.h>
|
||||||
|
#include <zmk/event_manager.h>
|
||||||
|
#include <zmk/mouse.h>
|
||||||
|
|
||||||
|
struct zmk_mouse_button_state_changed {
|
||||||
|
zmk_mouse_button_t buttons;
|
||||||
|
bool state;
|
||||||
|
int64_t timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
ZMK_EVENT_DECLARE(zmk_mouse_button_state_changed);
|
||||||
|
|
||||||
|
static inline struct zmk_mouse_button_state_changed_event *
|
||||||
|
zmk_mouse_button_state_changed_from_encoded(uint32_t encoded, bool pressed, int64_t timestamp) {
|
||||||
|
return new_zmk_mouse_button_state_changed((struct zmk_mouse_button_state_changed){
|
||||||
|
.buttons = HID_USAGE_ID(encoded), .state = pressed, .timestamp = timestamp});
|
||||||
|
}
|
33
app/include/zmk/events/mouse_move_state_changed.h
Normal file
33
app/include/zmk/events/mouse_move_state_changed.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zephyr.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_HOR_DECODE(encoded),
|
||||||
|
.y = MOVE_VERT_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});
|
||||||
|
}
|
34
app/include/zmk/events/mouse_scroll_state_changed.h
Normal file
34
app/include/zmk/events/mouse_scroll_state_changed.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zephyr.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 = SCROLL_HOR_DECODE(encoded),
|
||||||
|
.y = SCROLL_VERT_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});
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 The ZMK Contributors
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <zephyr.h>
|
|
||||||
#include <zmk/event_manager.h>
|
|
||||||
|
|
||||||
struct zmk_mouse_state_changed {
|
|
||||||
int32_t x;
|
|
||||||
int32_t y;
|
|
||||||
bool state;
|
|
||||||
int64_t timestamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
ZMK_EVENT_DECLARE(zmk_mouse_state_changed);
|
|
||||||
|
|
||||||
static inline struct zmk_mouse_state_changed_event *
|
|
||||||
zmk_mouse_state_changed_from_encoded(uint32_t encoded, bool pressed, int64_t timestamp) {
|
|
||||||
|
|
||||||
int32_t x = (encoded & 0xFFFF0000) >> 16;
|
|
||||||
int32_t y = encoded & 0x0000FFFF;
|
|
||||||
|
|
||||||
return new_zmk_mouse_state_changed(
|
|
||||||
(struct zmk_mouse_state_changed){.x = x, .y = y, .state = pressed, .timestamp = timestamp});
|
|
||||||
}
|
|
36
app/include/zmk/events/mouse_tick.h
Normal file
36
app/include/zmk/events/mouse_tick.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dt-bindings/zmk/mouse.h>
|
||||||
|
#include <zephyr.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 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) {
|
||||||
|
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,
|
||||||
|
.timestamp = k_uptime_get(),
|
||||||
|
});
|
||||||
|
}
|
|
@ -244,7 +244,7 @@ static const uint8_t zmk_hid_report_desc[] = {
|
||||||
/* REPORT_COUNT (2) */
|
/* REPORT_COUNT (2) */
|
||||||
HID_GI_REPORT_COUNT,
|
HID_GI_REPORT_COUNT,
|
||||||
0x02,
|
0x02,
|
||||||
/* USAGE (X) */
|
/* USAGE (X) */ // Vertical scroll
|
||||||
HID_LI_USAGE,
|
HID_LI_USAGE,
|
||||||
HID_USAGE_GD_X,
|
HID_USAGE_GD_X,
|
||||||
/* USAGE (Y) */
|
/* USAGE (Y) */
|
||||||
|
@ -271,7 +271,7 @@ static const uint8_t zmk_hid_report_desc[] = {
|
||||||
/* Input (Data,Var,Rel) */
|
/* Input (Data,Var,Rel) */
|
||||||
HID_MI_INPUT,
|
HID_MI_INPUT,
|
||||||
0x06,
|
0x06,
|
||||||
/* USAGE_PAGE (Consumer) */ // Horizontal wheel
|
/* USAGE_PAGE (Consumer) */ // Horizontal scroll
|
||||||
HID_GI_USAGE_PAGE,
|
HID_GI_USAGE_PAGE,
|
||||||
HID_USAGE_CONSUMER,
|
HID_USAGE_CONSUMER,
|
||||||
/* USAGE (AC Pan) */
|
/* USAGE (AC Pan) */
|
||||||
|
@ -335,8 +335,8 @@ struct zmk_hid_mouse_report_body {
|
||||||
zmk_mouse_button_flags_t buttons;
|
zmk_mouse_button_flags_t buttons;
|
||||||
int16_t x;
|
int16_t x;
|
||||||
int16_t y;
|
int16_t y;
|
||||||
int8_t wheel_vert;
|
int8_t scroll_y;
|
||||||
int8_t wheel_hor;
|
int8_t scroll_x;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
struct zmk_hid_mouse_report {
|
struct zmk_hid_mouse_report {
|
||||||
|
@ -363,10 +363,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_button_release(zmk_mouse_button_t button);
|
||||||
int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons);
|
int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons);
|
||||||
int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons);
|
int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons);
|
||||||
int zmk_hid_mouse_movement_press(int16_t x, int16_t y);
|
void zmk_hid_mouse_movement_set(int16_t x, int16_t y);
|
||||||
int zmk_hid_mouse_movement_release(int16_t x, int16_t y);
|
void zmk_hid_mouse_scroll_set(int8_t x, int8_t y);
|
||||||
int zmk_hid_mouse_wheel_press(int8_t hor, int8_t vert);
|
void zmk_hid_mouse_movement_update(int16_t x, int16_t y);
|
||||||
int zmk_hid_mouse_wheel_release(int8_t hor, int8_t vert);
|
void zmk_hid_mouse_scroll_update(int8_t x, int8_t y);
|
||||||
void zmk_hid_mouse_clear();
|
void zmk_hid_mouse_clear();
|
||||||
|
|
||||||
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report();
|
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report();
|
||||||
|
|
|
@ -11,3 +11,17 @@
|
||||||
|
|
||||||
typedef uint16_t zmk_mouse_button_flags_t;
|
typedef uint16_t zmk_mouse_button_flags_t;
|
||||||
typedef uint16_t zmk_mouse_button_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
|
||||||
|
float acceleration_exponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct vector2d {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
|
|
@ -10,28 +10,29 @@
|
||||||
#include <drivers/behavior.h>
|
#include <drivers/behavior.h>
|
||||||
#include <logging/log.h>
|
#include <logging/log.h>
|
||||||
|
|
||||||
#include <zmk/event_manager.h>
|
|
||||||
#include <zmk/events/keycode_state_changed.h>
|
|
||||||
#include <zmk/behavior.h>
|
#include <zmk/behavior.h>
|
||||||
#include <zmk/hid.h>
|
#include <zmk/event_manager.h>
|
||||||
#include <zmk/endpoints.h>
|
#include <zmk/events/mouse_button_state_changed.h>
|
||||||
|
|
||||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||||
|
|
||||||
static int behavior_mouse_key_press_init(const struct device *dev) { return 0; };
|
static int behavior_mouse_key_press_init(const struct device *dev) { return 0; };
|
||||||
|
|
||||||
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
struct zmk_behavior_binding_event event) {
|
struct zmk_behavior_binding_event event) {
|
||||||
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
||||||
zmk_hid_mouse_buttons_press(HID_USAGE_ID(binding->param1));
|
|
||||||
return zmk_endpoints_send_mouse_report();
|
return ZMK_EVENT_RAISE(
|
||||||
|
zmk_mouse_button_state_changed_from_encoded(binding->param1, true, event.timestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
|
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
|
||||||
struct zmk_behavior_binding_event event) {
|
struct zmk_behavior_binding_event event) {
|
||||||
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
||||||
zmk_hid_mouse_buttons_release(HID_USAGE_ID(binding->param1));
|
return ZMK_EVENT_RAISE(
|
||||||
return zmk_endpoints_send_mouse_report();
|
zmk_mouse_button_state_changed_from_encoded(binding->param1, false, event.timestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct behavior_driver_api behavior_mouse_key_press_driver_api = {
|
static const struct behavior_driver_api behavior_mouse_key_press_driver_api = {
|
||||||
|
@ -43,3 +44,5 @@ static const struct behavior_driver_api behavior_mouse_key_press_driver_api = {
|
||||||
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_key_press_driver_api);
|
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_key_press_driver_api);
|
||||||
|
|
||||||
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
|
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
|
||||||
|
|
||||||
|
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
|
|
@ -10,37 +10,48 @@
|
||||||
#include <drivers/behavior.h>
|
#include <drivers/behavior.h>
|
||||||
#include <logging/log.h>
|
#include <logging/log.h>
|
||||||
|
|
||||||
#include <zmk/event_manager.h>
|
|
||||||
#include <zmk/events/keycode_state_changed.h>
|
|
||||||
#include <zmk/behavior.h>
|
#include <zmk/behavior.h>
|
||||||
#include <zmk/events/mouse_state_changed.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);
|
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 behavior_mouse_move_init(const struct device *dev) { return 0; };
|
||||||
|
|
||||||
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
struct zmk_behavior_binding_event event) {
|
struct zmk_behavior_binding_event event) {
|
||||||
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
||||||
int res = ZMK_EVENT_RAISE(
|
const struct device *dev = device_get_binding(binding->behavior_dev);
|
||||||
zmk_mouse_state_changed_from_encoded(binding->param1, true, event.timestamp));
|
const struct mouse_config *config = dev->config;
|
||||||
return res;
|
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,
|
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
|
||||||
struct zmk_behavior_binding_event event) {
|
struct zmk_behavior_binding_event event) {
|
||||||
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
||||||
|
const struct device *dev = device_get_binding(binding->behavior_dev);
|
||||||
return ZMK_EVENT_RAISE(
|
const struct mouse_config *config = dev->config;
|
||||||
zmk_mouse_state_changed_from_encoded(binding->param1, false, event.timestamp));
|
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 = {
|
static const struct behavior_driver_api behavior_mouse_move_driver_api = {
|
||||||
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
|
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
|
||||||
|
|
||||||
#define KP_INST(n) \
|
#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 = (float)DT_INST_PROP(n, acceleration_exponent) / 1000.0f, \
|
||||||
|
}; \
|
||||||
DEVICE_AND_API_INIT(behavior_mouse_move_##n, DT_INST_LABEL(n), behavior_mouse_move_init, NULL, \
|
DEVICE_AND_API_INIT(behavior_mouse_move_##n, DT_INST_LABEL(n), behavior_mouse_move_init, NULL, \
|
||||||
NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
&behavior_mouse_move_config_##n, APPLICATION, \
|
||||||
&behavior_mouse_move_driver_api);
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_move_driver_api);
|
||||||
|
|
||||||
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
|
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
|
||||||
|
|
||||||
|
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
|
||||||
|
|
58
app/src/behaviors/behavior_mouse_scroll.c
Normal file
58
app/src/behaviors/behavior_mouse_scroll.c
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_behavior_mouse_scroll
|
||||||
|
|
||||||
|
#include <device.h>
|
||||||
|
#include <drivers/behavior.h>
|
||||||
|
#include <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 = (float)DT_INST_PROP(n, acceleration_exponent) / 1000.0f, \
|
||||||
|
}; \
|
||||||
|
DEVICE_AND_API_INIT(behavior_mouse_scroll_##n, DT_INST_LABEL(n), behavior_mouse_scroll_init, \
|
||||||
|
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) */
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021 The ZMK Contributors
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DT_DRV_COMPAT zmk_behavior_mouse_wheel
|
|
||||||
|
|
||||||
#include <device.h>
|
|
||||||
#include <drivers/behavior.h>
|
|
||||||
#include <logging/log.h>
|
|
||||||
|
|
||||||
#include <zmk/event_manager.h>
|
|
||||||
#include <zmk/events/keycode_state_changed.h>
|
|
||||||
#include <zmk/behavior.h>
|
|
||||||
#include <zmk/hid.h>
|
|
||||||
#include <zmk/endpoints.h>
|
|
||||||
|
|
||||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
|
||||||
|
|
||||||
#define WHEEL_HORIZONTAL(encoded) (((encoded)&0xFF00) >> 8)
|
|
||||||
#define WHEEL_VERTICAL(encoded) ((encoded)&0x00FF)
|
|
||||||
|
|
||||||
static int behavior_mouse_wheel_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);
|
|
||||||
int32_t x = WHEEL_HORIZONTAL(binding->param1);
|
|
||||||
int32_t y = WHEEL_VERTICAL(binding->param1);
|
|
||||||
zmk_hid_mouse_wheel_press(x, y);
|
|
||||||
return zmk_endpoints_send_mouse_report();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
int32_t x = WHEEL_HORIZONTAL(binding->param1);
|
|
||||||
int32_t y = WHEEL_VERTICAL(binding->param1);
|
|
||||||
zmk_hid_mouse_wheel_release(x, y);
|
|
||||||
return zmk_endpoints_send_mouse_report();
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct behavior_driver_api behavior_mouse_wheel_driver_api = {
|
|
||||||
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
|
|
||||||
|
|
||||||
#define KP_INST(n) \
|
|
||||||
DEVICE_AND_API_INIT(behavior_mouse_wheel_##n, DT_INST_LABEL(n), behavior_mouse_wheel_init, \
|
|
||||||
NULL, NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
||||||
&behavior_mouse_wheel_driver_api);
|
|
||||||
|
|
||||||
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
|
|
|
@ -146,7 +146,7 @@ int zmk_endpoints_send_report(uint16_t usage_page) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int zmk_endpoints_send_mouse_report() {
|
int zmk_endpoints_send_mouse_report() {
|
||||||
LOG_ERR("SENDING MOUSE REPORT");
|
LOG_DBG("SENDING MOUSE REPORT");
|
||||||
struct zmk_hid_mouse_report *mouse_report = zmk_hid_get_mouse_report();
|
struct zmk_hid_mouse_report *mouse_report = zmk_hid_get_mouse_report();
|
||||||
|
|
||||||
switch (current_endpoint) {
|
switch (current_endpoint) {
|
||||||
|
|
10
app/src/events/mouse_button_state_changed.c
Normal file
10
app/src/events/mouse_button_state_changed.c
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <zmk/events/mouse_button_state_changed.h>
|
||||||
|
|
||||||
|
ZMK_EVENT_IMPL(zmk_mouse_button_state_changed);
|
10
app/src/events/mouse_move_state_changed.c
Normal file
10
app/src/events/mouse_move_state_changed.c
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <zmk/events/mouse_move_state_changed.h>
|
||||||
|
|
||||||
|
ZMK_EVENT_IMPL(zmk_mouse_move_state_changed);
|
10
app/src/events/mouse_scroll_state_changed.c
Normal file
10
app/src/events/mouse_scroll_state_changed.c
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <zmk/events/mouse_scroll_state_changed.h>
|
||||||
|
|
||||||
|
ZMK_EVENT_IMPL(zmk_mouse_scroll_state_changed);
|
|
@ -5,6 +5,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <kernel.h>
|
#include <kernel.h>
|
||||||
#include <zmk/events/mouse_state_changed.h>
|
#include <zmk/events/mouse_tick.h>
|
||||||
|
|
||||||
ZMK_EVENT_IMPL(zmk_mouse_state_changed);
|
ZMK_EVENT_IMPL(zmk_mouse_tick);
|
|
@ -16,7 +16,7 @@ static struct zmk_hid_keyboard_report keyboard_report = {
|
||||||
static struct zmk_hid_consumer_report consumer_report = {.report_id = 2, .body = {.keys = {0}}};
|
static struct zmk_hid_consumer_report consumer_report = {.report_id = 2, .body = {.keys = {0}}};
|
||||||
|
|
||||||
static struct zmk_hid_mouse_report mouse_report = {
|
static struct zmk_hid_mouse_report mouse_report = {
|
||||||
.report_id = 4, .body = {.buttons = 0, .x = 0, .y = 0, .wheel_vert = 0, .wheel_hor = 0}};
|
.report_id = 4, .body = {.buttons = 0, .x = 0, .y = 0, .scroll_x = 0, .scroll_y = 0}};
|
||||||
|
|
||||||
// Keep track of how often a modifier was pressed.
|
// Keep track of how often a modifier was pressed.
|
||||||
// Only release the modifier if the count is 0.
|
// Only release the modifier if the count is 0.
|
||||||
|
@ -185,10 +185,6 @@ void zmk_hid_consumer_clear() { memset(&consumer_report.body, 0, sizeof(consumer
|
||||||
// Only release the button if the count is 0.
|
// Only release the button if the count is 0.
|
||||||
static int explicit_button_counts[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
static int explicit_button_counts[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
static zmk_mod_flags_t explicit_buttons = 0;
|
static zmk_mod_flags_t explicit_buttons = 0;
|
||||||
static int16_t curr_x = 0;
|
|
||||||
static int16_t curr_y = 0;
|
|
||||||
static int8_t curr_hor = 0;
|
|
||||||
static int8_t curr_vert = 0;
|
|
||||||
|
|
||||||
#define SET_MOUSE_BUTTONS(btns) \
|
#define SET_MOUSE_BUTTONS(btns) \
|
||||||
{ \
|
{ \
|
||||||
|
@ -237,50 +233,31 @@ int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SET_MOUSE_MOVEMENT(coor_x, coor_y) \
|
void zmk_hid_mouse_movement_set(int16_t x, int16_t y) {
|
||||||
{ \
|
mouse_report.body.x = x;
|
||||||
mouse_report.body.x = coor_x; \
|
mouse_report.body.y = y;
|
||||||
LOG_DBG("Mouse movement x set to 0x%02X", mouse_report.body.x); \
|
LOG_DBG("Mouse movement set to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y);
|
||||||
mouse_report.body.y = coor_y; \
|
|
||||||
LOG_DBG("Mouse movement y set to 0x%02X", mouse_report.body.y); \
|
|
||||||
}
|
|
||||||
|
|
||||||
int zmk_hid_mouse_movement_press(int16_t x, int16_t y) {
|
|
||||||
curr_x += x;
|
|
||||||
curr_y += y;
|
|
||||||
SET_MOUSE_MOVEMENT(curr_x, curr_y);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int zmk_hid_mouse_movement_release(int16_t x, int16_t y) {
|
void zmk_hid_mouse_movement_update(int16_t x, int16_t y) {
|
||||||
curr_x -= x;
|
mouse_report.body.x += x;
|
||||||
curr_y -= y;
|
mouse_report.body.y += y;
|
||||||
SET_MOUSE_MOVEMENT(curr_x, curr_y);
|
LOG_DBG("Mouse movement updated to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SET_MOUSE_WHEEL(horiz, vertic) \
|
void zmk_hid_mouse_scroll_set(int8_t x, int8_t y) {
|
||||||
{ \
|
mouse_report.body.scroll_x = x;
|
||||||
mouse_report.body.wheel_hor = horiz; \
|
mouse_report.body.scroll_y = y;
|
||||||
LOG_DBG("Mouse wheel hor set to 0x%02X", mouse_report.body.wheel_hor); \
|
LOG_DBG("Mouse scroll set to 0x%02X 0x%02X ", mouse_report.body.scroll_x,
|
||||||
mouse_report.body.wheel_vert = vertic; \
|
mouse_report.body.scroll_y);
|
||||||
LOG_DBG("Mouse wheel vert set to 0x%02X", mouse_report.body.wheel_vert); \
|
|
||||||
}
|
|
||||||
|
|
||||||
int zmk_hid_mouse_wheel_press(int8_t hor, int8_t vert) {
|
|
||||||
curr_hor += hor;
|
|
||||||
curr_vert += vert;
|
|
||||||
SET_MOUSE_WHEEL(curr_hor, curr_vert);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int zmk_hid_mouse_wheel_release(int8_t hor, int8_t vert) {
|
void zmk_hid_mouse_scroll_update(int8_t x, int8_t y) {
|
||||||
curr_hor -= hor;
|
mouse_report.body.scroll_x += x;
|
||||||
curr_vert -= vert;
|
mouse_report.body.scroll_y += y;
|
||||||
SET_MOUSE_WHEEL(curr_hor, curr_vert);
|
LOG_DBG("Mouse scroll updated to 0x%02X 0x%02X ", mouse_report.body.scroll_x,
|
||||||
return 0;
|
mouse_report.body.scroll_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void zmk_hid_mouse_clear() { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); }
|
void zmk_hid_mouse_clear() { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); }
|
||||||
|
|
||||||
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() {
|
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() {
|
||||||
|
|
|
@ -11,8 +11,6 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
#include <zmk/event_manager.h>
|
#include <zmk/event_manager.h>
|
||||||
#include <zmk/events/keycode_state_changed.h>
|
#include <zmk/events/keycode_state_changed.h>
|
||||||
#include <zmk/events/mouse_state_changed.h>
|
|
||||||
#include <zmk/events/modifiers_state_changed.h>
|
|
||||||
#include <zmk/hid.h>
|
#include <zmk/hid.h>
|
||||||
#include <dt-bindings/zmk/hid_usage_pages.h>
|
#include <dt-bindings/zmk/hid_usage_pages.h>
|
||||||
#include <zmk/endpoints.h>
|
#include <zmk/endpoints.h>
|
||||||
|
@ -92,62 +90,6 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed
|
||||||
return zmk_endpoints_send_report(ev->usage_page);
|
return zmk_endpoints_send_report(ev->usage_page);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zmk_mouse_work(struct k_work *work) {
|
|
||||||
int rc = zmk_endpoints_send_mouse_report();
|
|
||||||
if (rc != 0) {
|
|
||||||
LOG_ERR("Failed to send mouse report, error: %d", rc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
K_WORK_DEFINE(mouse_work, &zmk_mouse_work);
|
|
||||||
|
|
||||||
void mouse_timer_cb(struct k_timer *dummy) { k_work_submit(&mouse_work); }
|
|
||||||
|
|
||||||
K_TIMER_DEFINE(mouse_timer, mouse_timer_cb, mouse_timer_cb);
|
|
||||||
|
|
||||||
static int mouse_timer_ref_count = 0;
|
|
||||||
|
|
||||||
void mouse_timer_ref() {
|
|
||||||
if (mouse_timer_ref_count == 0) {
|
|
||||||
k_timer_start(&mouse_timer, K_NO_WAIT, K_MSEC(10));
|
|
||||||
}
|
|
||||||
mouse_timer_ref_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mouse_timer_unref() {
|
|
||||||
if (mouse_timer_ref_count > 0) {
|
|
||||||
mouse_timer_ref_count -= 1;
|
|
||||||
}
|
|
||||||
if (mouse_timer_ref_count == 0) {
|
|
||||||
k_timer_stop(&mouse_timer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int hid_listener_mouse_pressed(const struct zmk_mouse_state_changed *ev) {
|
|
||||||
int err;
|
|
||||||
LOG_DBG("x: 0x%02X, y: 0x%02X", ev->x, ev->y);
|
|
||||||
err = zmk_hid_mouse_movement_press(ev->x, ev->y);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Unable to press button");
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
mouse_timer_ref();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int hid_listener_mouse_released(const struct zmk_mouse_state_changed *ev) {
|
|
||||||
int err;
|
|
||||||
LOG_DBG("x: 0x%02X, y: 0x%02X", ev->x, ev->y);
|
|
||||||
err = zmk_hid_mouse_movement_release(ev->x, ev->y);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Unable to release button");
|
|
||||||
mouse_timer_unref();
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
mouse_timer_unref();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int hid_listener(const zmk_event_t *eh) {
|
int hid_listener(const zmk_event_t *eh) {
|
||||||
const struct zmk_keycode_state_changed *kc_ev = as_zmk_keycode_state_changed(eh);
|
const struct zmk_keycode_state_changed *kc_ev = as_zmk_keycode_state_changed(eh);
|
||||||
if (kc_ev) {
|
if (kc_ev) {
|
||||||
|
@ -158,18 +100,8 @@ int hid_listener(const zmk_event_t *eh) {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const struct zmk_mouse_state_changed *ms_ev = as_zmk_mouse_state_changed(eh);
|
|
||||||
if (ms_ev) {
|
|
||||||
if (ms_ev->state) {
|
|
||||||
hid_listener_mouse_pressed(ms_ev);
|
|
||||||
} else {
|
|
||||||
hid_listener_mouse_released(ms_ev);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZMK_LISTENER(hid_listener, hid_listener);
|
ZMK_LISTENER(hid_listener, hid_listener);
|
||||||
ZMK_SUBSCRIPTION(hid_listener, zmk_keycode_state_changed);
|
ZMK_SUBSCRIPTION(hid_listener, zmk_keycode_state_changed);
|
||||||
ZMK_SUBSCRIPTION(hid_listener, zmk_mouse_state_changed);
|
|
135
app/src/mouse/key_listener.c
Normal file
135
app/src/mouse/key_listener.c
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <drivers/behavior.h>
|
||||||
|
#include <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>
|
||||||
|
|
||||||
|
static struct vector2d move_speed = {0};
|
||||||
|
static struct vector2d scroll_speed = {0};
|
||||||
|
static struct mouse_config move_config = {0};
|
||||||
|
static struct mouse_config scroll_config = {0};
|
||||||
|
|
||||||
|
static void clear_mouse_state() {
|
||||||
|
move_speed = (struct vector2d){0};
|
||||||
|
scroll_speed = (struct vector2d){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mouse_tick_timer_handler(struct k_work *work) {
|
||||||
|
zmk_hid_mouse_movement_set(0, 0);
|
||||||
|
zmk_hid_mouse_scroll_set(0, 0);
|
||||||
|
ZMK_EVENT_RAISE(zmk_mouse_tick(move_speed, scroll_speed, move_config, scroll_config));
|
||||||
|
zmk_endpoints_send_mouse_report();
|
||||||
|
}
|
||||||
|
|
||||||
|
K_WORK_DEFINE(mouse_tick, &mouse_tick_timer_handler);
|
||||||
|
|
||||||
|
void mouse_timer_cb(struct k_timer *dummy) { k_work_submit(&mouse_tick); }
|
||||||
|
|
||||||
|
K_TIMER_DEFINE(mouse_timer, mouse_timer_cb, mouse_timer_cb);
|
||||||
|
|
||||||
|
static int mouse_timer_ref_count = 0;
|
||||||
|
|
||||||
|
void mouse_timer_ref() {
|
||||||
|
if (mouse_timer_ref_count == 0) {
|
||||||
|
k_timer_start(&mouse_timer, K_NO_WAIT, K_MSEC(10));
|
||||||
|
}
|
||||||
|
mouse_timer_ref_count += 1;
|
||||||
|
// trigger the first mouse tick event immediately
|
||||||
|
mouse_tick_timer_handler(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
clear_mouse_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (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 (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);
|
129
app/src/mouse/tick_listener.c
Normal file
129
app/src/mouse/tick_listener.c
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <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/mouse.h>
|
||||||
|
|
||||||
|
#include <sys/util.h>
|
||||||
|
|
||||||
|
// CLAMP will be provided by sys/util.h from zephyr 2.6 onward
|
||||||
|
#define CLAMP(x, min, max) MIN(MAX(x, min), max)
|
||||||
|
|
||||||
|
#if CONFIG_MINIMAL_LIBC
|
||||||
|
static float powf(float base, float exponent) {
|
||||||
|
// poor man's power implementation rounds the exponent down to the nearest integer.
|
||||||
|
LOG_DBG("falling back to integer exponent %d instead of %f", (int)exponent, exponent);
|
||||||
|
float power = 1.0f;
|
||||||
|
for (; exponent < 1.0f; exponent--) {
|
||||||
|
power = power * base;
|
||||||
|
}
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#include <math.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct movement_state {
|
||||||
|
int64_t start;
|
||||||
|
int64_t last_tick;
|
||||||
|
struct vector2d fractional_remainder;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct movement_state move_state = {0};
|
||||||
|
static struct mouse_config move_config = (struct mouse_config){
|
||||||
|
.delay_ms = 0,
|
||||||
|
.time_to_max_speed_ms = 300,
|
||||||
|
.acceleration_exponent = 2.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct movement_state scroll_state = {0};
|
||||||
|
static struct mouse_config scroll_config = (struct mouse_config){
|
||||||
|
.delay_ms = 0,
|
||||||
|
.time_to_max_speed_ms = 300,
|
||||||
|
.acceleration_exponent = 2.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int64_t ms_since_last_tick(int64_t last_tick, int64_t now) { return now - last_tick; }
|
||||||
|
|
||||||
|
static int64_t ms_since_start(int64_t start, int64_t now) {
|
||||||
|
int64_t move_duration = now - start;
|
||||||
|
// start can be in the future if there's a delay
|
||||||
|
if (move_duration < 0) {
|
||||||
|
move_duration = 0;
|
||||||
|
}
|
||||||
|
return move_duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float speed(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) {
|
||||||
|
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 movement_state *state, struct mouse_config *config,
|
||||||
|
struct vector2d max_speed, int64_t now) {
|
||||||
|
struct vector2d move = {0};
|
||||||
|
if (max_speed.x == 0 && max_speed.y == 0) {
|
||||||
|
*state = (struct movement_state){0};
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
if (state->start == 0) {
|
||||||
|
state->start = now + config->delay_ms;
|
||||||
|
state->last_tick = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t tick_duration = ms_since_last_tick(state->last_tick, now);
|
||||||
|
int64_t move_duration = ms_since_start(state->start, now);
|
||||||
|
move = (struct vector2d){
|
||||||
|
.x = speed(config, max_speed.x, move_duration) * tick_duration / 1000,
|
||||||
|
.y = speed(config, max_speed.y, move_duration) * tick_duration / 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
track_remainder(&(move.x), &state->fractional_remainder.x);
|
||||||
|
track_remainder(&(move.y), &state->fractional_remainder.y);
|
||||||
|
|
||||||
|
state->last_tick = now;
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mouse_tick_handler(const struct zmk_mouse_tick *tick) {
|
||||||
|
struct vector2d move =
|
||||||
|
update_movement(&move_state, &move_config, tick->max_move, tick->timestamp);
|
||||||
|
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_state, &scroll_config, tick->max_scroll, tick->timestamp);
|
||||||
|
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);
|
1
app/tests/mouse-keys/mmv/events.patterns
Normal file
1
app/tests/mouse-keys/mmv/events.patterns
Normal file
|
@ -0,0 +1 @@
|
||||||
|
s/.*hid_listener_keycode_//p
|
2
app/tests/mouse-keys/mmv/keycode_events.snapshot
Normal file
2
app/tests/mouse-keys/mmv/keycode_events.snapshot
Normal 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
|
26
app/tests/mouse-keys/mmv/native_posix.keymap
Normal file
26
app/tests/mouse-keys/mmv/native_posix.keymap
Normal 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)
|
||||||
|
>;
|
||||||
|
};
|
|
@ -5,7 +5,7 @@ sidebar_label: Mouse Emulation
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Mouse emulation behaviors send mouse movements, button presses or wheel actions.
|
Mouse emulation behaviors send mouse movements, button presses or scroll actions.
|
||||||
|
|
||||||
Please view [`dt-bindings/zmk/mouse.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/mouse.h) for a comprehensive list of signals.
|
Please view [`dt-bindings/zmk/mouse.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/mouse.h) for a comprehensive list of signals.
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ provided by ZMK near the top:
|
||||||
#include <dt-bindings/zmk/mouse.h>
|
#include <dt-bindings/zmk/mouse.h>
|
||||||
```
|
```
|
||||||
|
|
||||||
Doing so allows using a set of defines such as `MOVE_UP`, `MOVE_DOWN`, `LCLK` and `WHEEL_UP` with these behaviors.
|
Doing so allows using a set of defines such as `MOVE_UP`, `MOVE_DOWN`, `LCLK` and `SCROLL_UP` with these behaviors.
|
||||||
|
|
||||||
## Mouse Button Press
|
## Mouse Button Press
|
||||||
|
|
||||||
|
@ -65,5 +65,5 @@ This behaviour is used to scroll, both horizontally and vertically.
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
&mwh WHEEL_UP
|
&mwh SCROLL_UP
|
||||||
```
|
```
|
||||||
|
|
Loading…
Add table
Reference in a new issue