diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 0b681ea95312..88dc737aabd5 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -32,7 +32,6 @@ target_sources_ifdef(CONFIG_ZMK_GPIO_KEY_WAKEUP_TRIGGER app PRIVATE src/gpio_key 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_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) @@ -40,7 +39,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/input_listener.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(app PRIVATE src/behaviors/behavior_hold_tap.c) @@ -59,6 +58,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR app PRIVATE src/behaviors/behavior_sensor_rotate_var.c) 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_INPUT_TWO_AXIS app PRIVATE src/behaviors/behavior_input_two_axis.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) @@ -100,5 +100,6 @@ target_sources(app PRIVATE src/main.c) add_subdirectory(src/display/) add_subdirectory_ifdef(CONFIG_SETTINGS src/settings/) +add_subdirectory(src/mouse/) zephyr_cc_option(-Wfatal-errors) diff --git a/app/Kconfig b/app/Kconfig index b5bf2b80e8c8..c47c573aec93 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -376,13 +376,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" diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors index c9754bf7d83f..8f633b3df39f 100644 --- a/app/Kconfig.behaviors +++ b/app/Kconfig.behaviors @@ -9,14 +9,18 @@ config ZMK_BEHAVIOR_KEY_TOGGLE config ZMK_BEHAVIOR_MOUSE_KEY_PRESS bool default y - depends on DT_HAS_ZMK_BEHAVIOR_MOUSE_KEY_PRESS_ENABLED - imply ZMK_MOUSE + depends on DT_HAS_ZMK_BEHAVIOR_MOUSE_KEY_PRESS_ENABLED && ZMK_MOUSE config ZMK_BEHAVIOR_SOFT_OFF bool default y depends on DT_HAS_ZMK_BEHAVIOR_SOFT_OFF_ENABLED && ZMK_PM_SOFT_OFF +config ZMK_BEHAVIOR_INPUT_TWO_AXIS + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_INPUT_TWO_AXIS_ENABLED && ZMK_MOUSE + config ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON bool @@ -35,4 +39,4 @@ config ZMK_BEHAVIOR_SENSOR_ROTATE_VAR config ZMK_BEHAVIOR_MACRO bool default y - depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED \ No newline at end of file + depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index fde75271891f..7009d2fff8f8 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + #include #include #include @@ -19,5 +25,5 @@ #include #include #include -#include #include +#include diff --git a/app/dts/behaviors/mouse_key_press.dtsi b/app/dts/behaviors/mouse_key_press.dtsi index 975c24aaafbd..66e327e849ec 100644 --- a/app/dts/behaviors/mouse_key_press.dtsi +++ b/app/dts/behaviors/mouse_key_press.dtsi @@ -5,4 +5,9 @@ #binding-cells = <1>; }; }; + + mkp_input_listener: mkp_input_listener { + compatible = "zmk,input-listener"; + device = <&mkp>; + }; }; diff --git a/app/dts/behaviors/mouse_keys.dtsi b/app/dts/behaviors/mouse_keys.dtsi new file mode 100644 index 000000000000..f9a99fede0cf --- /dev/null +++ b/app/dts/behaviors/mouse_keys.dtsi @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "mouse_key_press.dtsi" +#include "mouse_move.dtsi" +#include "mouse_scroll.dtsi" \ No newline at end of file diff --git a/app/dts/behaviors/mouse_move.dtsi b/app/dts/behaviors/mouse_move.dtsi new file mode 100644 index 000000000000..09b93520c1f2 --- /dev/null +++ b/app/dts/behaviors/mouse_move.dtsi @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { + /omit-if-no-ref/ mmv: mouse_move { + compatible = "zmk,behavior-input-two-axis"; + #binding-cells = <1>; + x-input-code = ; + y-input-code = ; + time-to-max-speed-ms = <300>; + acceleration-exponent = <1>; + }; + }; + + mmv_input_listener: mmv_input_listener { + compatible = "zmk,input-listener"; + device = <&mmv>; + }; +}; diff --git a/app/dts/behaviors/mouse_scroll.dtsi b/app/dts/behaviors/mouse_scroll.dtsi new file mode 100644 index 000000000000..b482efded67d --- /dev/null +++ b/app/dts/behaviors/mouse_scroll.dtsi @@ -0,0 +1,26 @@ + +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { + /omit-if-no-ref/ msc: mouse_scroll { + compatible = "zmk,behavior-input-two-axis"; + #binding-cells = <1>; + x-input-code = ; + y-input-code = ; + time-to-max-speed-ms = <300>; + acceleration-exponent = <0>; + }; + }; + + msc_input_listener: msc_input_listener { + compatible = "zmk,input-listener"; + device = <&msc>; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml b/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml new file mode 100644 index 000000000000..0c138e039299 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml @@ -0,0 +1,25 @@ +description: Two axis input behavior + +compatible: "zmk,behavior-input-two-axis" + +include: one_param.yaml + +properties: + x-input-code: + type: int + required: true + y-input-code: + type: int + required: true + trigger-period-ms: + type: int + default: 16 + description: The time (in ms) between generated inputs when an input has non-zero speed. + delay-ms: + type: int + time-to-max-speed-ms: + type: int + required: true + acceleration-exponent: + type: int + default: 1 diff --git a/app/dts/bindings/zmk,input-listener.yaml b/app/dts/bindings/zmk,input-listener.yaml new file mode 100644 index 000000000000..a883557db396 --- /dev/null +++ b/app/dts/bindings/zmk,input-listener.yaml @@ -0,0 +1,21 @@ +description: | + Listener to subscribe to input events and send HID updates after processing + +compatible: "zmk,input-listener" + +properties: + device: + type: phandle + required: true + xy-swap: + type: boolean + x-invert: + type: boolean + y-invert: + type: boolean + scale-multiplier: + type: int + default: 1 + scale-divisor: + type: int + default: 1 diff --git a/app/include/dt-bindings/zmk/mouse.h b/app/include/dt-bindings/zmk/mouse.h index 582518aff7e6..ea34e1243f16 100644 --- a/app/include/dt-bindings/zmk/mouse.h +++ b/app/include/dt-bindings/zmk/mouse.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 The ZMK Contributors + * Copyright (c) 2023 The ZMK Contributors * * SPDX-License-Identifier: MIT */ @@ -22,3 +22,29 @@ #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) + +#define SCRL_UP MOVE_Y(ZMK_MOUSE_DEFAULT_SCRL_VAL) +#define SCRL_DOWN MOVE_Y(-ZMK_MOUSE_DEFAULT_SCRL_VAL) +#define SCRL_LEFT MOVE_X(-ZMK_MOUSE_DEFAULT_SCRL_VAL) +#define SCRL_RIGHT MOVE_X(ZMK_MOUSE_DEFAULT_SCRL_VAL) diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index f2aff2bcc2df..0177416d10d1 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -72,6 +72,6 @@ int zmk_endpoints_send_report(uint16_t usage_page); #if IS_ENABLED(CONFIG_ZMK_MOUSE) int zmk_endpoints_send_mouse_report(); -#endif // IS_ENABLE(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) void zmk_endpoints_clear_current(void); diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 41f559b5189e..3601a56594d9 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -12,9 +12,6 @@ #include #include -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -#include -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #include #include @@ -25,8 +22,6 @@ #define ZMK_HID_KEYBOARD_NKRO_MAX_USAGE HID_USAGE_KEY_KEYPAD_EQUAL #endif -#define ZMK_HID_MOUSE_NUM_BUTTONS 0x05 - // See https://www.usb.org/sites/default/files/hid1_11.pdf section 6.2.2.4 Main Items #define ZMK_HID_MAIN_VAL_DATA (0x00 << 0) @@ -59,7 +54,6 @@ #define ZMK_HID_REPORT_ID_KEYBOARD 0x01 #define ZMK_HID_REPORT_ID_LEDS 0x01 #define ZMK_HID_REPORT_ID_CONSUMER 0x02 -#define ZMK_HID_REPORT_ID_MOUSE 0x03 static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), @@ -145,38 +139,6 @@ static const uint8_t zmk_hid_report_desc[] = { HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_ARRAY | ZMK_HID_MAIN_VAL_ABS), HID_END_COLLECTION, -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - HID_USAGE_PAGE(HID_USAGE_GD), - HID_USAGE(HID_USAGE_GD_MOUSE), - HID_COLLECTION(HID_COLLECTION_APPLICATION), - HID_REPORT_ID(ZMK_HID_REPORT_ID_MOUSE), - HID_USAGE(HID_USAGE_GD_POINTER), - HID_COLLECTION(HID_COLLECTION_PHYSICAL), - HID_USAGE_PAGE(HID_USAGE_BUTTON), - HID_USAGE_MIN8(0x1), - HID_USAGE_MAX8(ZMK_HID_MOUSE_NUM_BUTTONS), - HID_LOGICAL_MIN8(0x00), - HID_LOGICAL_MAX8(0x01), - HID_REPORT_SIZE(0x01), - HID_REPORT_COUNT(0x5), - HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), - // Constant padding for the last 3 bits. - HID_REPORT_SIZE(0x03), - HID_REPORT_COUNT(0x01), - HID_INPUT(ZMK_HID_MAIN_VAL_CONST | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), - // Some OSes ignore pointer devices without X/Y data. - HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), - 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_REPORT_COUNT(0x03), - 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) }; #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) @@ -239,21 +201,6 @@ struct zmk_hid_consumer_report { struct zmk_hid_consumer_report_body body; } __packed; -#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; -} __packed; - -struct zmk_hid_mouse_report { - uint8_t report_id; - struct zmk_hid_mouse_report_body body; -} __packed; - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - zmk_mod_flags_t zmk_hid_get_explicit_mods(void); int zmk_hid_register_mod(zmk_mod_t modifier); int zmk_hid_unregister_mod(zmk_mod_t modifier); @@ -280,21 +227,9 @@ int zmk_hid_press(uint32_t usage); int zmk_hid_release(uint32_t usage); bool zmk_hid_is_pressed(uint32_t usage); -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -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_clear(void); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void); #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) zmk_hid_boot_report_t *zmk_hid_get_boot_report(); #endif - -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index eb6e653f772e..4e1603037a54 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -11,7 +11,3 @@ int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *body); int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); - -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/include/zmk/mouse/hid.h b/app/include/zmk/mouse/hid.h new file mode 100644 index 000000000000..01fcc9ad7f3f --- /dev/null +++ b/app/include/zmk/mouse/hid.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#include +#include + +#include + +#include +#include + +#define ZMK_MOUSE_HID_NUM_BUTTONS 0x05 + +#define ZMK_MOUSE_HID_REPORT_ID_MOUSE 0x01 + +// 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_mouse_hid_report_desc[] = { + HID_USAGE_PAGE(HID_USAGE_GD), + HID_USAGE(HID_USAGE_GD_MOUSE), + HID_COLLECTION(HID_COLLECTION_APPLICATION), + HID_REPORT_ID(ZMK_MOUSE_HID_REPORT_ID_MOUSE), + HID_USAGE(HID_USAGE_GD_POINTER), + HID_COLLECTION(HID_COLLECTION_PHYSICAL), + HID_USAGE_PAGE(HID_USAGE_BUTTON), + HID_USAGE_MIN8(0x1), + HID_USAGE_MAX8(ZMK_MOUSE_HID_NUM_BUTTONS), + HID_LOGICAL_MIN8(0x00), + HID_LOGICAL_MAX8(0x01), + HID_REPORT_SIZE(0x01), + HID_REPORT_COUNT(0x5), + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + // Constant padding for the last 3 bits. + HID_REPORT_SIZE(0x03), + HID_REPORT_COUNT(0x01), + HID_INPUT(ZMK_HID_MAIN_VAL_CONST | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + // Some OSes ignore pointer devices without X/Y data. + HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), + HID_USAGE(HID_USAGE_GD_X), + HID_USAGE(HID_USAGE_GD_Y), + HID_USAGE(HID_USAGE_GD_WHEEL), + 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_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, +}; + +struct zmk_hid_mouse_report_body { + zmk_mouse_button_flags_t buttons; + int16_t d_x; + int16_t d_y; + int16_t d_scroll_y; + int16_t d_scroll_x; +} __packed; + +struct zmk_hid_mouse_report { + uint8_t report_id; + struct zmk_hid_mouse_report_body body; +} __packed; + +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); + +struct zmk_hid_mouse_report *zmk_mouse_hid_get_mouse_report(); diff --git a/app/include/zmk/mouse/hog.h b/app/include/zmk/mouse/hog.h new file mode 100644 index 000000000000..48a214e554ac --- /dev/null +++ b/app/include/zmk/mouse/hog.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +int zmk_mouse_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); +#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/include/zmk/mouse.h b/app/include/zmk/mouse/types.h similarity index 80% rename from app/include/zmk/mouse.h rename to app/include/zmk/mouse/types.h index d873f15689a7..c898f0010980 100644 --- a/app/include/zmk/mouse.h +++ b/app/include/zmk/mouse/types.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The ZMK Contributors + * Copyright (c) 2023 The ZMK Contributors * * SPDX-License-Identifier: MIT */ diff --git a/app/include/zmk/mouse/usb_hid.h b/app/include/zmk/mouse/usb_hid.h new file mode 100644 index 000000000000..12205f28e4c9 --- /dev/null +++ b/app/include/zmk/mouse/usb_hid.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +int zmk_mouse_usb_hid_send_mouse_report(void); +#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/src/behaviors/behavior_input_two_axis.c b/app/src/behaviors/behavior_input_two_axis.c new file mode 100644 index 000000000000..eaf84a766af9 --- /dev/null +++ b/app/src/behaviors/behavior_input_two_axis.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_input_two_axis + +#include +#include +#include +#include +#include // CLAMP + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct vector2d { + float x; + float y; +}; + +struct movement_state_1d { + float remainder; + int16_t speed; + uint64_t start_time; +}; + +struct movement_state_2d { + struct movement_state_1d x; + struct movement_state_1d y; +}; + +struct behavior_input_two_axis_data { + struct k_work_delayable tick_work; + const struct device *dev; + + struct movement_state_2d state; +}; + +struct behavior_input_two_axis_config { + int16_t x_code; + int16_t y_code; + uint16_t delay_ms; + uint16_t time_to_max_speed_ms; + uint8_t trigger_period_ms; + // acceleration exponent 0: uniform speed + // acceleration exponent 1: uniform acceleration + // acceleration exponent 2: uniform jerk + uint8_t acceleration_exponent; +}; + +#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 +#endif + +static int64_t ms_since_start(int64_t start, int64_t now, int64_t delay) { + if (start == 0) { + return 0; + } + 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 behavior_input_two_axis_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 == 0) { + return 0; + } + + 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 float update_movement_1d(const struct behavior_input_two_axis_config *config, + struct movement_state_1d *state, int64_t now) { + float move = 0; + if (state->speed == 0) { + state->remainder = 0; + return move; + } + + int64_t move_duration = ms_since_start(state->start_time, now, config->delay_ms); + move = (move_duration > 0) + ? (speed(config, state->speed, move_duration) * config->trigger_period_ms / 1000) + : 0; + + track_remainder(&(move), &(state->remainder)); + + return move; +} +static struct vector2d update_movement_2d(const struct behavior_input_two_axis_config *config, + struct movement_state_2d *state, int64_t now) { + struct vector2d move = {0}; + + move = (struct vector2d){ + .x = update_movement_1d(config, &state->x, now), + .y = update_movement_1d(config, &state->y, now), + }; + + return move; +} + +static bool is_non_zero_1d_movement(int16_t speed) { return speed != 0; } + +static bool is_non_zero_2d_movement(struct movement_state_2d *state) { + return is_non_zero_1d_movement(state->x.speed) || is_non_zero_1d_movement(state->y.speed); +} + +static bool should_be_working(struct behavior_input_two_axis_data *data) { + return is_non_zero_2d_movement(&data->state); +} + +static void tick_work_cb(struct k_work *work) { + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct behavior_input_two_axis_data *data = + CONTAINER_OF(d_work, struct behavior_input_two_axis_data, tick_work); + const struct device *dev = data->dev; + const struct behavior_input_two_axis_config *cfg = dev->config; + + uint64_t timestamp = k_uptime_get(); + + LOG_INF("x start: %llu, y start: %llu, current timestamp: %llu", data->state.x.start_time, + data->state.y.start_time, timestamp); + + struct vector2d move = update_movement_2d(cfg, &data->state, timestamp); + + int ret = 0; + bool have_x = is_non_zero_1d_movement(move.x); + bool have_y = is_non_zero_1d_movement(move.y); + if (have_x) { + ret = input_report_rel(dev, cfg->x_code, (int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX), + !have_y, K_NO_WAIT); + } + if (have_y) { + ret = input_report_rel(dev, cfg->y_code, (int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX), true, + K_NO_WAIT); + } + + if (should_be_working(data)) { + k_work_schedule(&data->tick_work, K_MSEC(cfg->trigger_period_ms)); + } +} + +static void set_start_times_for_activity_1d(struct movement_state_1d *state) { + if (state->speed != 0 && state->start_time == 0) { + state->start_time = k_uptime_get(); + } else if (state->speed == 0) { + state->start_time = 0; + } +} +static void set_start_times_for_activity(struct movement_state_2d *state) { + set_start_times_for_activity_1d(&state->x); + set_start_times_for_activity_1d(&state->y); +} + +static void update_work_scheduling(const struct device *dev) { + struct behavior_input_two_axis_data *data = dev->data; + const struct behavior_input_two_axis_config *cfg = dev->config; + + set_start_times_for_activity(&data->state); + + if (should_be_working(data)) { + k_work_schedule(&data->tick_work, K_MSEC(cfg->trigger_period_ms)); + } else { + k_work_cancel_delayable(&data->tick_work); + } +} + +int behavior_input_two_axis_adjust_speed(const struct device *dev, int16_t dx, int16_t dy) { + struct behavior_input_two_axis_data *data = dev->data; + + LOG_DBG("Adjusting: %d %d", dx, dy); + data->state.x.speed += dx; + data->state.y.speed += dy; + + LOG_DBG("After: %d %d", data->state.x.speed, data->state.y.speed); + + update_work_scheduling(dev); + + return 0; +} + +static int behavior_input_two_axis_init(const struct device *dev) { + struct behavior_input_two_axis_data *data = dev->data; + + data->dev = dev; + k_work_init_delayable(&data->tick_work, tick_work_cb); + + return 0; +}; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + + const struct device *behavior_dev = zmk_behavior_get_binding(binding->behavior_dev); + + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + int16_t x = MOVE_X_DECODE(binding->param1); + int16_t y = MOVE_Y_DECODE(binding->param1); + + behavior_input_two_axis_adjust_speed(behavior_dev, x, y); + return 0; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *behavior_dev = zmk_behavior_get_binding(binding->behavior_dev); + + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + int16_t x = MOVE_X_DECODE(binding->param1); + int16_t y = MOVE_Y_DECODE(binding->param1); + + behavior_input_two_axis_adjust_speed(behavior_dev, -x, -y); + return 0; +} + +static const struct behavior_driver_api behavior_input_two_axis_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define ITA_INST(n) \ + static struct behavior_input_two_axis_data behavior_input_two_axis_data_##n = {}; \ + static struct behavior_input_two_axis_config behavior_input_two_axis_config_##n = { \ + .x_code = DT_INST_PROP(n, x_input_code), \ + .y_code = DT_INST_PROP(n, y_input_code), \ + .trigger_period_ms = DT_INST_PROP(n, trigger_period_ms), \ + .delay_ms = DT_INST_PROP_OR(n, delay_ms, 0), \ + .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ + .acceleration_exponent = DT_INST_PROP_OR(n, acceleration_exponent, 1), \ + }; \ + BEHAVIOR_DT_INST_DEFINE( \ + n, behavior_input_two_axis_init, NULL, &behavior_input_two_axis_data_##n, \ + &behavior_input_two_axis_config_##n, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_input_two_axis_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(ITA_INST) diff --git a/app/src/behaviors/behavior_mouse_key_press.c b/app/src/behaviors/behavior_mouse_key_press.c index 9064a1aa5c84..8f8df2a72f36 100644 --- a/app/src/behaviors/behavior_mouse_key_press.c +++ b/app/src/behaviors/behavior_mouse_key_press.c @@ -11,8 +11,9 @@ #include #include -#include -#include +#include +#include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -20,19 +21,31 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static int behavior_mouse_key_press_init(const struct device *dev) { return 0; }; +static void process_key_state(const struct device *dev, int32_t val, bool pressed) { + for (int i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if (val & BIT(i)) { + WRITE_BIT(val, i, 0); + input_report_key(dev, INPUT_BTN_0 + i, pressed ? 1 : 0, val == 0, K_FOREVER); + } + } +} + 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); - return raise_zmk_mouse_button_state_changed_from_encoded(binding->param1, true, - event.timestamp); + process_key_state(zmk_behavior_get_binding(binding->behavior_dev), binding->param1, true); + + return 0; } 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); - return raise_zmk_mouse_button_state_changed_from_encoded(binding->param1, false, - event.timestamp); + + process_key_state(zmk_behavior_get_binding(binding->behavior_dev), binding->param1, false); + + return 0; } static const struct behavior_driver_api behavior_mouse_key_press_driver_api = { diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 7c9d15a31fee..1e09eaf56422 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -208,7 +211,7 @@ int zmk_endpoints_send_mouse_report() { switch (current_instance.transport) { case ZMK_TRANSPORT_USB: { #if IS_ENABLED(CONFIG_ZMK_USB) - int err = zmk_usb_hid_send_mouse_report(); + int err = zmk_mouse_usb_hid_send_mouse_report(); if (err) { LOG_ERR("FAILED TO SEND OVER USB: %d", err); } @@ -221,8 +224,8 @@ int zmk_endpoints_send_mouse_report() { case ZMK_TRANSPORT_BLE: { #if IS_ENABLED(CONFIG_ZMK_BLE) - struct zmk_hid_mouse_report *mouse_report = zmk_hid_get_mouse_report(); - int err = zmk_hog_send_mouse_report(&mouse_report->body); + struct zmk_hid_mouse_report *mouse_report = zmk_mouse_hid_get_mouse_report(); + int err = zmk_mouse_hog_send_mouse_report(&mouse_report->body); if (err) { LOG_ERR("FAILED TO SEND OVER HOG: %d", err); } diff --git a/app/src/hid.c b/app/src/hid.c index 582db6763dec..34106c4ca13c 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -25,13 +25,6 @@ static uint8_t keys_held = 0; #endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - -static struct zmk_hid_mouse_report mouse_report = {.report_id = ZMK_HID_REPORT_ID_MOUSE, - .body = {.buttons = 0}}; - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - // Keep track of how often a modifier was pressed. // Only release the modifier if the count is 0. static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0}; @@ -369,71 +362,6 @@ bool zmk_hid_is_pressed(uint32_t usage) { return false; } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - -// Keep track of how often a button was pressed. -// Only release the button if the count is 0. -static int explicit_button_counts[5] = {0, 0, 0, 0, 0}; -static zmk_mod_flags_t explicit_buttons = 0; - -#define SET_MOUSE_BUTTONS(btns) \ - { \ - mouse_report.body.buttons = btns; \ - LOG_DBG("Mouse buttons set to 0x%02X", mouse_report.body.buttons); \ - } - -int zmk_hid_mouse_button_press(zmk_mouse_button_t button) { - if (button >= ZMK_HID_MOUSE_NUM_BUTTONS) { - return -EINVAL; - } - - explicit_button_counts[button]++; - LOG_DBG("Button %d count %d", button, explicit_button_counts[button]); - WRITE_BIT(explicit_buttons, button, true); - SET_MOUSE_BUTTONS(explicit_buttons); - return 0; -} - -int zmk_hid_mouse_button_release(zmk_mouse_button_t button) { - if (button >= ZMK_HID_MOUSE_NUM_BUTTONS) { - return -EINVAL; - } - - if (explicit_button_counts[button] <= 0) { - LOG_ERR("Tried to release button %d too often", button); - return -EINVAL; - } - explicit_button_counts[button]--; - LOG_DBG("Button %d count: %d", button, explicit_button_counts[button]); - if (explicit_button_counts[button] == 0) { - LOG_DBG("Button %d released", button); - WRITE_BIT(explicit_buttons, button, false); - } - SET_MOUSE_BUTTONS(explicit_buttons); - return 0; -} - -int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons) { - for (zmk_mouse_button_t i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) { - if (buttons & BIT(i)) { - zmk_hid_mouse_button_press(i); - } - } - return 0; -} - -int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { - for (zmk_mouse_button_t i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) { - if (buttons & BIT(i)) { - zmk_hid_mouse_button_release(i); - } - } - return 0; -} -void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { return &keyboard_report; } @@ -441,11 +369,3 @@ struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { return &consumer_report; } - -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - -struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { - return &mouse_report; -} - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/src/hog.c b/app/src/hog.c index f17f759c9085..65679586e091 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -69,15 +69,6 @@ static struct hids_report consumer_input = { .type = HIDS_INPUT, }; -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - -static struct hids_report mouse_input = { - .id = ZMK_HID_REPORT_ID_MOUSE, - .type = HIDS_INPUT, -}; - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - static bool host_requests_notification = false; static uint8_t ctrl_point; // static uint8_t proto_mode; @@ -143,15 +134,6 @@ static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, sizeof(struct zmk_hid_consumer_report_body)); } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, - void *buf, uint16_t len, uint16_t offset) { - struct zmk_hid_mouse_report_body *report_body = &zmk_hid_get_mouse_report()->body; - return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, - sizeof(struct zmk_hid_mouse_report_body)); -} -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - // static ssize_t write_proto_mode(struct bt_conn *conn, // const struct bt_gatt_attr *attr, // const void *buf, uint16_t len, uint16_t offset, @@ -200,14 +182,6 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, NULL, &consumer_input), -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, - BT_GATT_PERM_READ_ENCRYPT, read_hids_mouse_input_report, NULL, NULL), - BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), - BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, - NULL, &mouse_input), -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - #if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, @@ -220,7 +194,7 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); -struct bt_conn *destination_connection(void) { +static struct bt_conn *destination_connection(void) { struct bt_conn *conn; bt_addr_le_t *addr = zmk_ble_active_profile_addr(); LOG_DBG("Address pointer %p", addr); @@ -237,7 +211,7 @@ struct bt_conn *destination_connection(void) { K_THREAD_STACK_DEFINE(hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE); -struct k_work_q hog_work_q; +static struct k_work_q hog_work_q; K_MSGQ_DEFINE(zmk_hog_keyboard_msgq, sizeof(struct zmk_hid_keyboard_report_body), CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE, 4); @@ -343,61 +317,6 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { return 0; }; -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - -K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body), - CONFIG_ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE, 4); - -void send_mouse_report_callback(struct k_work *work) { - struct zmk_hid_mouse_report_body report; - while (k_msgq_get(&zmk_hog_mouse_msgq, &report, K_NO_WAIT) == 0) { - struct bt_conn *conn = destination_connection(); - if (conn == NULL) { - return; - } - - struct bt_gatt_notify_params notify_params = { - .attr = &hog_svc.attrs[13], - .data = &report, - .len = sizeof(report), - }; - - int err = bt_gatt_notify_cb(conn, ¬ify_params); - if (err == -EPERM) { - bt_conn_set_security(conn, BT_SECURITY_L2); - } else if (err) { - LOG_DBG("Error notifying %d", err); - } - - bt_conn_unref(conn); - } -}; - -K_WORK_DEFINE(hog_mouse_work, send_mouse_report_callback); - -int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { - int err = k_msgq_put(&zmk_hog_mouse_msgq, report, K_MSEC(100)); - if (err) { - switch (err) { - case -EAGAIN: { - LOG_WRN("Consumer message queue full, popping first message and queueing again"); - struct zmk_hid_mouse_report_body discarded_report; - k_msgq_get(&zmk_hog_mouse_msgq, &discarded_report, K_NO_WAIT); - return zmk_hog_send_mouse_report(report); - } - default: - LOG_WRN("Failed to queue mouse report to send (%d)", err); - return err; - } - } - - k_work_submit_to_queue(&hog_work_q, &hog_mouse_work); - - return 0; -}; - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - static int zmk_hog_init(void) { static const struct k_work_queue_config queue_config = {.name = "HID Over GATT Send Work"}; k_work_queue_start(&hog_work_q, hog_q_stack, K_THREAD_STACK_SIZEOF(hog_q_stack), diff --git a/app/src/mouse.c b/app/src/mouse.c deleted file mode 100644 index c1b9ac0261e2..000000000000 --- a/app/src/mouse.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#include -#include - -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -#include -#include -#include -#include - -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); diff --git a/app/src/mouse/CMakeLists.txt b/app/src/mouse/CMakeLists.txt new file mode 100644 index 000000000000..405c10866a2e --- /dev/null +++ b/app/src/mouse/CMakeLists.txt @@ -0,0 +1,6 @@ +if(CONFIG_ZMK_MOUSE) + target_sources(app PRIVATE hid.c) + + target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE usb_hid.c) + target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE hog.c) +endif() \ No newline at end of file diff --git a/app/src/mouse/Kconfig b/app/src/mouse/Kconfig new file mode 100644 index 000000000000..d2f9d5259952 --- /dev/null +++ b/app/src/mouse/Kconfig @@ -0,0 +1,19 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig ZMK_MOUSE + bool "Mouse Emulation" + select INPUT + select INPUT_THREAD_PRIORITY_OVERRIDE + select USB_COMPOSITE_DEVICE if ZMK_USB + +if ZMK_MOUSE + +if ZMK_USB + +config USB_HID_DEVICE_COUNT + default 2 + +endif + +endif # ZMK_MOUSE \ No newline at end of file diff --git a/app/src/mouse/hid.c b/app/src/mouse/hid.c new file mode 100644 index 000000000000..24faf779b348 --- /dev/null +++ b/app/src/mouse/hid.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include + +static struct zmk_hid_mouse_report mouse_report = { + .report_id = ZMK_MOUSE_HID_REPORT_ID_MOUSE, + .body = {.buttons = 0, .d_x = 0, .d_y = 0, .d_scroll_y = 0}}; + +// Keep track of how often a button was pressed. +// Only release the button if the count is 0. +static int explicit_button_counts[5] = {0, 0, 0, 0, 0}; +static zmk_mod_flags_t explicit_buttons = 0; + +#define SET_MOUSE_BUTTONS(btns) \ + { \ + mouse_report.body.buttons = btns; \ + LOG_DBG("Mouse buttons set to 0x%02X", mouse_report.body.buttons); \ + } + +int zmk_hid_mouse_button_press(zmk_mouse_button_t button) { + if (button >= ZMK_MOUSE_HID_NUM_BUTTONS) { + return -EINVAL; + } + + explicit_button_counts[button]++; + LOG_DBG("Button %d count %d", button, explicit_button_counts[button]); + WRITE_BIT(explicit_buttons, button, true); + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_button_release(zmk_mouse_button_t button) { + if (button >= ZMK_MOUSE_HID_NUM_BUTTONS) { + return -EINVAL; + } + + if (explicit_button_counts[button] <= 0) { + LOG_ERR("Tried to release button %d too often", button); + return -EINVAL; + } + explicit_button_counts[button]--; + LOG_DBG("Button %d count: %d", button, explicit_button_counts[button]); + if (explicit_button_counts[button] == 0) { + LOG_DBG("Button %d released", button); + WRITE_BIT(explicit_buttons, button, false); + } + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons) { + for (zmk_mouse_button_t i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if (buttons & BIT(i)) { + zmk_hid_mouse_button_press(i); + } + } + return 0; +} + +int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { + for (zmk_mouse_button_t i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if (buttons & BIT(i)) { + zmk_hid_mouse_button_release(i); + } + } + 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 %d/%d", 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 %d/%d", 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 %d/%d", 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: %d/%d", mouse_report.body.d_scroll_x, + mouse_report.body.d_scroll_y); +} + +void zmk_hid_mouse_clear(void) { + LOG_DBG("Mouse report cleared"); + memset(&mouse_report.body, 0, sizeof(mouse_report.body)); +} + +struct zmk_hid_mouse_report *zmk_mouse_hid_get_mouse_report(void) { + return &mouse_report; +} diff --git a/app/src/mouse/hog.c b/app/src/mouse/hog.c new file mode 100644 index 000000000000..5ae67a4ba96c --- /dev/null +++ b/app/src/mouse/hog.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include + +#include +#include +#include +#include + +enum { + HIDS_REMOTE_WAKE = BIT(0), + HIDS_NORMALLY_CONNECTABLE = BIT(1), +}; + +struct hids_info { + uint16_t version; /* version number of base USB HID Specification */ + uint8_t code; /* country HID Device hardware is localized for. */ + uint8_t flags; +} __packed; + +struct hids_report { + uint8_t id; /* report id */ + uint8_t type; /* report type */ +} __packed; + +static struct hids_info info = { + .version = 0x1101, + .code = 0x00, + .flags = HIDS_NORMALLY_CONNECTABLE | HIDS_REMOTE_WAKE, +}; + +enum { + HIDS_INPUT = 0x01, + HIDS_OUTPUT = 0x02, + HIDS_FEATURE = 0x03, +}; + +static struct hids_report mouse_input = { + .id = ZMK_MOUSE_HID_REPORT_ID_MOUSE, + .type = HIDS_INPUT, +}; + +static bool host_requests_notification = false; +static uint8_t ctrl_point; + +static ssize_t read_hids_info(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_info)); +} + +static ssize_t read_hids_report_ref(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_report)); +} + +static ssize_t read_hids_report_map(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, zmk_mouse_hid_report_desc, + sizeof(zmk_mouse_hid_report_desc)); +} + +static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + struct zmk_hid_mouse_report_body *report_body = &zmk_mouse_hid_get_mouse_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_mouse_report_body)); +} + +static void input_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) { + LOG_DBG("Input CC changed for %d", attr->handle); + host_requests_notification = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0; +} + +static ssize_t write_ctrl_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + uint8_t *value = attr->user_data; + + if (offset + len > sizeof(ctrl_point)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + memcpy(value + offset, buf, len); + + return len; +} + +/* HID Service Declaration */ +BT_GATT_SERVICE_DEFINE( + mouse_hog_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT_MAP, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, + read_hids_report_map, NULL, NULL), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, read_hids_mouse_input_report, NULL, NULL), + BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &mouse_input), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_INFO, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_hids_info, + NULL, &info), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); + +static struct bt_conn *destination_connection(void) { + struct bt_conn *conn; + bt_addr_le_t *addr = zmk_ble_active_profile_addr(); + + if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { + LOG_WRN("Not sending, no active address for current profile"); + return NULL; + } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { + LOG_WRN("Not sending, not connected to active profile"); + return NULL; + } + + return conn; +} + +K_THREAD_STACK_DEFINE(mouse_hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE); + +static struct k_work_q mouse_hog_work_q; + +K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body), + CONFIG_ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE, 4); + +void send_mouse_report_callback(struct k_work *work) { + struct zmk_hid_mouse_report_body report; + while (k_msgq_get(&zmk_hog_mouse_msgq, &report, K_NO_WAIT) == 0) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &mouse_hog_svc.attrs[3], + .data = &report, + .len = sizeof(report), + }; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err == -EPERM) { + bt_conn_set_security(conn, BT_SECURITY_L2); + } else if (err) { + LOG_DBG("Error notifying %d", err); + } + + bt_conn_unref(conn); + } +}; + +K_WORK_DEFINE(hog_mouse_work, send_mouse_report_callback); + +int zmk_mouse_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { + int err = k_msgq_put(&zmk_hog_mouse_msgq, report, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Consumer message queue full, popping first message and queueing again"); + struct zmk_hid_mouse_report_body discarded_report; + k_msgq_get(&zmk_hog_mouse_msgq, &discarded_report, K_NO_WAIT); + return zmk_mouse_hog_send_mouse_report(report); + } + default: + LOG_WRN("Failed to queue mouse report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&mouse_hog_work_q, &hog_mouse_work); + + return 0; +}; + +static int zmk_mouse_hog_init(void) { + static const struct k_work_queue_config queue_config = {.name = + "Mouse HID Over GATT Send Work"}; + k_work_queue_start(&mouse_hog_work_q, mouse_hog_q_stack, + K_THREAD_STACK_SIZEOF(mouse_hog_q_stack), CONFIG_ZMK_BLE_THREAD_PRIORITY, + &queue_config); + + return 0; +} + +SYS_INIT(zmk_mouse_hog_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); diff --git a/app/src/mouse/input_listener.c b/app/src/mouse/input_listener.c new file mode 100644 index 000000000000..4ddca057c3ab --- /dev/null +++ b/app/src/mouse/input_listener.c @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_listener + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include + +#include +#include +#include + +#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_xy_data { + enum input_listener_xy_data_mode mode; + int16_t x; + int16_t y; +}; + +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; + }; +}; + +struct input_listener_config { + bool xy_swap; + bool x_invert; + bool y_invert; + uint16_t scale_multiplier; + uint16_t scale_divisor; +}; + +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 += evt->value; + break; + case INPUT_REL_Y: + data->mouse.data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.data.y += evt->value; + break; + case INPUT_REL_WHEEL: + data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.wheel_data.y += evt->value; + break; + case INPUT_REL_HWHEEL: + data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.wheel_data.x += 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 void swap_xy(struct input_event *evt) { + switch (evt->code) { + case INPUT_REL_X: + evt->code = INPUT_REL_Y; + break; + case INPUT_REL_Y: + evt->code = INPUT_REL_X; + 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 filter_with_input_config(const struct input_listener_config *cfg, + struct input_event *evt) { + if (!evt->dev) { + return; + } + + if (cfg->xy_swap) { + swap_xy(evt); + } + + if ((cfg->x_invert && is_x_data(evt)) || (cfg->y_invert && is_y_data(evt))) { + evt->value = -(evt->value); + } + + evt->value = (int16_t)((evt->value * cfg->scale_multiplier) / cfg->scale_divisor); +} + +static void clear_xy_data(struct input_listener_xy_data *data) { + data->x = data->y = 0; + data->mode = INPUT_LISTENER_XY_DATA_MODE_NONE; +} + +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, evt); + + 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, data->mouse.wheel_data.y); + } + + if (data->mouse.data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) { + zmk_hid_mouse_movement_set(data->mouse.data.x, data->mouse.data.y); + } + + if (data->mouse.button_set != 0) { + for (int i = 0; i < ZMK_MOUSE_HID_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_MOUSE_HID_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 IL_INST(n) \ + COND_CODE_1( \ + DT_NODE_HAS_STATUS(DT_INST_PHANDLE(n, device), okay), \ + (static const struct input_listener_config config_##n = \ + { \ + .xy_swap = DT_INST_PROP(n, xy_swap), \ + .x_invert = DT_INST_PROP(n, x_invert), \ + .y_invert = DT_INST_PROP(n, y_invert), \ + .scale_multiplier = DT_INST_PROP(n, scale_multiplier), \ + .scale_divisor = DT_INST_PROP(n, scale_divisor), \ + }; \ + static struct input_listener_data 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) diff --git a/app/src/mouse/usb_hid.c b/app/src/mouse/usb_hid.c new file mode 100644 index 000000000000..1bfdd4608cec --- /dev/null +++ b/app/src/mouse/usb_hid.c @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +static const struct device *hid_dev; + +static K_SEM_DEFINE(hid_sem, 1, 1); + +static void in_ready_cb(const struct device *dev) { k_sem_give(&hid_sem); } + +#define HID_GET_REPORT_TYPE_MASK 0xff00 +#define HID_GET_REPORT_ID_MASK 0x00ff + +#define HID_REPORT_TYPE_INPUT 0x100 +#define HID_REPORT_TYPE_OUTPUT 0x200 +#define HID_REPORT_TYPE_FEATURE 0x300 + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +static uint8_t hid_protocol = HID_PROTOCOL_REPORT; + +static void set_proto_cb(const struct device *dev, uint8_t protocol) { hid_protocol = protocol; } + +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + +static int get_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, + uint8_t **data) { + + /* + * 7.2.1 of the HID v1.11 spec is unclear about handling requests for reports that do not exist + * For requested reports that aren't input reports, return -ENOTSUP like the Zephyr subsys does + */ + if ((setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_INPUT && + (setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_FEATURE) { + LOG_ERR("Get: Unsupported report type %d requested", + (setup->wValue & HID_GET_REPORT_TYPE_MASK) >> 8); + return -ENOTSUP; + } + + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { + case ZMK_MOUSE_HID_REPORT_ID_MOUSE: + struct zmk_hid_mouse_report *report = zmk_mouse_hid_get_mouse_report(); + *data = (uint8_t *)report; + *len = sizeof(*report); + break; + default: + LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); + return -EINVAL; + } + + return 0; +} + +static int set_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, + uint8_t **data) { + if ((setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_OUTPUT && + (setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_FEATURE) { + LOG_ERR("Set: Unsupported report type %d requested", + (setup->wValue & HID_GET_REPORT_TYPE_MASK) >> 8); + return -ENOTSUP; + } + + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { + default: + LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); + return -EINVAL; + } + + return 0; +} + +static const struct hid_ops ops = { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + .protocol_change = set_proto_cb, +#endif + .int_in_ready = in_ready_cb, + .get_report = get_report_cb, + .set_report = set_report_cb, +}; + +static int zmk_mouse_usb_hid_send_report(const uint8_t *report, size_t len) { + switch (zmk_usb_get_status()) { + case USB_DC_SUSPEND: + return usb_wakeup_request(); + case USB_DC_ERROR: + case USB_DC_RESET: + case USB_DC_DISCONNECTED: + case USB_DC_UNKNOWN: + return -ENODEV; + default: + k_sem_take(&hid_sem, K_MSEC(30)); + LOG_HEXDUMP_DBG(report, len, "Mouse HID report"); + int err = hid_int_ep_write(hid_dev, report, len, NULL); + + if (err) { + LOG_ERR("Failed to write %d", err); + k_sem_give(&hid_sem); + } + + return err; + } +} + +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +int zmk_mouse_usb_hid_send_mouse_report() { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol == HID_PROTOCOL_BOOT) { + return -ENOTSUP; + } +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + + struct zmk_hid_mouse_report *report = zmk_mouse_hid_get_mouse_report(); + return zmk_mouse_usb_hid_send_report((uint8_t *)report, sizeof(*report)); +} +#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +static int zmk_mouse_usb_hid_init(void) { + hid_dev = device_get_binding("HID_1"); + if (hid_dev == NULL) { + LOG_ERR("Unable to locate HID device"); + return -EINVAL; + } + + usb_hid_register_device(hid_dev, zmk_mouse_hid_report_desc, sizeof(zmk_mouse_hid_report_desc), + &ops); + + // usb_hid_set_proto_code(hid_dev, HID_BOOT_IFACE_CODE_MOUSE); + + usb_hid_init(hid_dev); + + return 0; +} + +SYS_INIT(zmk_mouse_usb_hid_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c index cd3ef9203910..383aeb812724 100644 --- a/app/src/usb_hid.c +++ b/app/src/usb_hid.c @@ -164,19 +164,6 @@ int zmk_usb_hid_send_consumer_report(void) { return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -int zmk_usb_hid_send_mouse_report() { -#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) - if (hid_protocol == HID_PROTOCOL_BOOT) { - return -ENOTSUP; - } -#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ - - struct zmk_hid_mouse_report *report = zmk_hid_get_mouse_report(); - return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); -} -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - static int zmk_usb_hid_init(void) { hid_dev = device_get_binding("HID_0"); if (hid_dev == NULL) { diff --git a/app/tests/mouse-keys/mkp/native_posix_64.conf b/app/tests/mouse-keys/mkp/native_posix_64.conf new file mode 100644 index 000000000000..65ed54bb2047 --- /dev/null +++ b/app/tests/mouse-keys/mkp/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/events.patterns b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/events.patterns new file mode 100644 index 000000000000..812126fb8285 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/keycode_events.snapshot new file mode 100644 index 000000000000..15d31600960e --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -5/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -5/-5 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-5 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.conf b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.conf new file mode 100644 index 000000000000..65ed54bb2047 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.keymap new file mode 100644 index 000000000000..df8cda8cfdf2 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.keymap @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +&mmv_input_listener { + scale-multiplier = <5>; + scale-divisor = <3>; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/events.patterns b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/events.patterns new file mode 100644 index 000000000000..812126fb8285 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/keycode_events.snapshot new file mode 100644 index 000000000000..33bb267b0730 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to 1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 3/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 3/3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.conf b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.conf new file mode 100644 index 000000000000..65ed54bb2047 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.keymap new file mode 100644 index 000000000000..9b07e1b98088 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.keymap @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +&mmv_input_listener { + x-invert; + y-invert; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/events.patterns b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/events.patterns new file mode 100644 index 000000000000..812126fb8285 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/keycode_events.snapshot new file mode 100644 index 000000000000..40daa64f0f65 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to 0/-1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.conf b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.conf new file mode 100644 index 000000000000..65ed54bb2047 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.keymap new file mode 100644 index 000000000000..719bca98f146 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.keymap @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +&mmv_input_listener { + xy-swap; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_diagonal/events.patterns b/app/tests/mouse-keys/mouse-move/move_diagonal/events.patterns new file mode 100644 index 000000000000..812126fb8285 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_diagonal/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_diagonal/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/move_diagonal/keycode_events.snapshot new file mode 100644 index 000000000000..6b9fa770b115 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_diagonal/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.conf b/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.conf new file mode 100644 index 000000000000..65ed54bb2047 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.keymap new file mode 100644 index 000000000000..7e4d7af2a1d6 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_x/events.patterns b/app/tests/mouse-keys/mouse-move/move_x/events.patterns new file mode 100644 index 000000000000..812126fb8285 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_x/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_x/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/move_x/keycode_events.snapshot new file mode 100644 index 000000000000..678f71c9ac2c --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_x/keycode_events.snapshot @@ -0,0 +1,24 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.conf b/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.conf new file mode 100644 index 000000000000..65ed54bb2047 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.keymap new file mode 100644 index 000000000000..89d50e2b8392 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_RIGHT + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_y/events.patterns b/app/tests/mouse-keys/mouse-move/move_y/events.patterns new file mode 100644 index 000000000000..812126fb8285 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_y/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_y/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/move_y/keycode_events.snapshot new file mode 100644 index 000000000000..d20154d55073 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_y/keycode_events.snapshot @@ -0,0 +1,24 @@ +movement_set: Mouse movement set to 0/-1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.conf b/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.conf new file mode 100644 index 000000000000..65ed54bb2047 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.keymap new file mode 100644 index 000000000000..5b02246b05f5 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_UP &mmv MOVE_DOWN + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/docs/docs/behaviors/index.mdx b/docs/docs/behaviors/index.mdx index 4a05f5653a0c..b11211291769 100644 --- a/docs/docs/behaviors/index.mdx +++ b/docs/docs/behaviors/index.mdx @@ -43,6 +43,8 @@ Below is a summary of pre-defined behavior bindings and user-definable behaviors | Binding | Behavior | Description | | ------- | ----------------------------------------------------------- | ------------------------------- | | `&mkp` | [Mouse Button Press](mouse-emulation.md#mouse-button-press) | Emulates pressing mouse buttons | +| `&mmv` | [Mouse Button Press](mouse-emulation.md#mouse-move) | Emulates mouse movement | +| `&msc` | [Mouse Button Press](mouse-emulation.md#mouse-scroll) | Emulates mouse scrolling | ## Reset behaviors diff --git a/docs/docs/behaviors/mouse-emulation.md b/docs/docs/behaviors/mouse-emulation.md index 7b80bae65d92..db14925265a4 100644 --- a/docs/docs/behaviors/mouse-emulation.md +++ b/docs/docs/behaviors/mouse-emulation.md @@ -5,8 +5,7 @@ sidebar_label: Mouse Emulation ## Summary -Mouse emulation behaviors send mouse events. Currently, only mouse button presses are supported, but movement -and scroll action support is planned for the future. +Mouse emulation behaviors send mouse events, including mouse button presses, cursor movement and scrolling. :::warning[Refreshing the HID descriptor] @@ -17,14 +16,12 @@ The mouse functionality will not work over BLE until that is done. ## Configuration Option -This feature can be enabled or disabled explicitly via a config option: +To use any of the behaviors documented here, the ZMK mouse feature must be enabled explicitly via a config option: ``` CONFIG_ZMK_MOUSE=y ``` -If you use the mouse key press behavior in your keymap, the feature will automatically be enabled for you. - ## Mouse Button Defines To make it easier to encode the HID mouse button numeric values, include @@ -69,3 +66,67 @@ This example will send press of the fourth mouse button when the binding is trig ``` &mkp MB4 ``` + +## Mouse Move + +This behavior sends mouse X/Y movement events to the connected host. + +### Behavior Binding + +- Reference: `&mmv` +- Parameter: A `uint32` with 16-bits each used for vertical and horizontal velocity. + +The following defines can be passed for the parameter: + +| Define | Action | +| :----------- | :--------- | +| `MOVE_UP` | Move up | +| `MOVE_DOWN` | Move down | +| `MOVE_LEFT` | Move left | +| `MOVE_RIGHT` | Move right | + +### Examples + +The following will send a scroll down event to the host when pressed/held: + +``` +&mmv MOVE_DOWN +``` + +The following will send a scroll left event to the host when pressed/held: + +``` +&mmv MOVE_LEFT +``` + +## Mouse Scroll + +This behavior sends vertical and horizontal scroll events to the connected host. + +### Behavior Binding + +- Reference: `&msc` +- Parameter: A `uint32` with 16-bits each used for vertical and horizontal velocity. + +The following defines can be passed for the parameter: + +| Define | Action | +| :----------- | :--------- | +| `MOVE_UP` | Move up | +| `MOVE_DOWN` | Move down | +| `MOVE_LEFT` | Move left | +| `MOVE_RIGHT` | Move right | + +### Examples + +The following will send a scroll down event to the host when pressed/held: + +``` +&msc MOVE_DOWN +``` + +The following will send a scroll left event to the host when pressed/held: + +``` +&msc MOVE_LEFT +``` diff --git a/docs/docs/intro.md b/docs/docs/intro.md index da01e8297e72..6ac1569563b2 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -33,7 +33,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | One Shot Keys | ✅ | ✅ | ✅ | | [Combo Keys](features/combos.md) | ✅ | | ✅ | | [Macros](behaviors/macros.md) | ✅ | ✅ | ✅ | -| Mouse Keys | 🚧 | ✅ | ✅ | +| Mouse Keys | ✅ | ✅ | ✅ | | Low Active Power Usage | ✅ | | | | Low Power Sleep States | ✅ | ✅ | | | [Low Power Mode (VCC Shutoff)](behaviors/power.md) | ✅ | ✅ | |