diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8e4d80 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +zmk/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0d449a0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +from ubuntu:22.04 + +env ZSDK_VERSION=0.13.2 + +run apt update +run apt install -y \ + git \ + wget \ + autoconf \ + automake \ + build-essential \ + bzip2 \ + ccache \ + device-tree-compiler \ + dfu-util \ + g++ \ + gcc \ + libtool \ + make \ + ninja-build \ + cmake \ + python3-dev \ + python3-pip \ + python3-setuptools \ + xz-utils + +run groupadd -g 1000 builder +run useradd -m -u 1000 -g 1000 -s /bin/bash builder +user builder +workdir /home/builder diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddde03f --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +1. Run `build-env.sh` + - checks out a fresh `zmk` and applies patches + - creates and initializes a `zmk` docker image to use for builds + +2. Run `build-zmk.sh` + - uses the `zmk` image to build left and right binaries + +3. Run `flash.sh` + - flashes the binaries diff --git a/build-env.sh b/build-env.sh new file mode 100755 index 0000000..cc7ec03 --- /dev/null +++ b/build-env.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +rm -rf zmk +docker build -t zmk . + +git clone https://github.com/zmkfirmware/zmk.git +for p in ./patches/*.patch; do + patch -d zmk/ -p1 < "$p" +done + +docker run -it --rm -d --name zmk \ + -v "$(pwd)/zmk:/home/builder/zmk" \ + -v "$(pwd)/zmk-config:/home/builder/zmk-config" \ + zmk + +docker exec -it zmk \ + bash -c 'export PATH=~/.local/bin:$PATH && \ + pip3 install -U west pyelftools && \ + wget "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZSDK_VERSION}/zephyr-toolchain-arm-${ZSDK_VERSION}-linux-x86_64-setup.run" && \ + sh "zephyr-toolchain-arm-${ZSDK_VERSION}-linux-x86_64-setup.run" --quiet -- -d ~/.local/zephyr-sdk-${ZSDK_VERSION} && \ + rm "zephyr-toolchain-arm-${ZSDK_VERSION}-linux-x86_64-setup.run" && \ + cd /home/builder/zmk && west init -l app/ && west update && \ + west zephyr-export && pip3 install --user -r zephyr/scripts/requirements-base.txt' + +docker commit zmk zmk +docker rm -vf zmk diff --git a/build-env/Dockerfile b/build-env/Dockerfile deleted file mode 100644 index ff2410c..0000000 --- a/build-env/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -from ubuntu:20.04 - -run apt update -run DEBIAN_FRONTEND=noninteractive TZ=America/Chicago apt install -y \ - git \ - wget \ - autoconf \ - automake \ - build-essential \ - bzip2 \ - ccache \ - device-tree-compiler \ - dfu-util \ - g++ \ - gcc \ - libtool \ - make \ - ninja-build \ - cmake \ - python3-dev \ - python3-pip \ - python3-setuptools \ - xz-utils -run useradd -m rudism -user rudism -workdir /home/rudism -run pip3 install --user -U west -run echo 'export PATH=~/.local/bin:"$PATH"' >> ~/.bashrc -env ZSDK_VERSION=0.12.4 -run wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZSDK_VERSION}/zephyr-toolchain-arm-${ZSDK_VERSION}-x86_64-linux-setup.run" && \ - sh "zephyr-toolchain-arm-${ZSDK_VERSION}-x86_64-linux-setup.run" --quiet -- -d ~/.local/zephyr-sdk-${ZSDK_VERSION} && \ - rm "zephyr-toolchain-arm-${ZSDK_VERSION}-x86_64-linux-setup.run" -copy requirements.txt . -run pip3 install --user -r requirements.txt -run echo source zmk/zephyr/zephyr-env.sh >> ~/.bashrc -entrypoint bash diff --git a/build-env/build b/build-env/build deleted file mode 100755 index 92f7ef1..0000000 --- a/build-env/build +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -cd /home/rudism/zmk/app || exit -west build --pristine -b nice_nano_v2 -- -DZMK_CONFIG=/home/rudism/zmk-config/config -DSHIELD=corne_left -cp /home/rudism/zmk/app/build/zephyr/zmk.uf2 /home/rudism/out/corne_left.uf2 -west build --pristine -b nice_nano_v2 -- -DZMK_CONFIG=/home/rudism/zmk-config/config -DSHIELD=corne_right -cp /home/rudism/zmk/app/build/zephyr/zmk.uf2 /home/rudism/out/corne_right.uf2 diff --git a/build-env/run b/build-env/run deleted file mode 100755 index 5615bff..0000000 --- a/build-env/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -docker run -it --rm -v `pwd`/build:/home/rudism/build -v `pwd`/out:/home/rudism/out -v `pwd`/zmk:/home/rudism/zmk -v `pwd`/zmk-config:/home/rudism/zmk-config zmk diff --git a/build-zmk.sh b/build-zmk.sh new file mode 100755 index 0000000..94790bb --- /dev/null +++ b/build-zmk.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +docker run -it --rm \ + -v "$(pwd)/zmk:/home/builder/zmk" \ + -v "$(pwd)/zmk-config:/home/builder/zmk-config" \ + -v "$(pwd)/out:/home/builder/out" \ + zmk bash -c 'export PATH="~/.local/bin:$PATH" && \ + source /home/builder/zmk/zephyr/zephyr-env.sh && \ + cd /home/builder/zmk/app && \ + west build --pristine -b nice_nano_v2 -- -DZMK_CONFIG=/home/builder/zmk-config -DSHIELD=corne_left && \ + cp build/zephyr/zmk.uf2 ~/out/corne_left.uf2 && \ + west build --pristine -b nice_nano_v2 -- -DZMK_CONFIG=/home/builder/zmk-config -DSHIELD=corne_right && \ + cp build/zephyr/zmk.uf2 ~/out/corne_right.uf2' diff --git a/build-env/flash b/flash.sh similarity index 100% rename from build-env/flash rename to flash.sh diff --git a/out/corne_left.uf2 b/out/corne_left.uf2 new file mode 100644 index 0000000..99b682e Binary files /dev/null and b/out/corne_left.uf2 differ diff --git a/out/corne_right.uf2 b/out/corne_right.uf2 new file mode 100644 index 0000000..659930c Binary files /dev/null and b/out/corne_right.uf2 differ diff --git a/patches/mouse-keys.patch b/patches/mouse-keys.patch new file mode 100644 index 0000000..8e43ddb --- /dev/null +++ b/patches/mouse-keys.patch @@ -0,0 +1,3336 @@ +From b1deea5cc281d226e603f4b44462b9c661432816 Mon Sep 17 00:00:00 2001 +From: Alexander Krikun +Date: Tue, 27 Apr 2021 18:24:11 +0300 +Subject: [PATCH 1/2] The original changes from #778 + +A squashed version of the original changeset from +https://github.com/zmkfirmware/zmk/pull/778 rebased on main +--- + app/CMakeLists.txt | 10 ++ + app/Kconfig | 11 ++ + app/dts/behaviors.dtsi | 5 +- + app/dts/behaviors/mouse_key_press.dtsi | 9 + + app/dts/behaviors/mouse_move.dtsi | 12 ++ + app/dts/behaviors/mouse_scroll.dtsi | 12 ++ + .../zmk,behavior-mouse-key-press.yaml | 5 + + .../behaviors/zmk,behavior-mouse-move.yaml | 13 ++ + .../behaviors/zmk,behavior-mouse-scroll.yaml | 13 ++ + app/include/dt-bindings/zmk/hid_usage_pages.h | 1 + + app/include/dt-bindings/zmk/mouse.h | 55 ++++++ + app/include/zmk/endpoints.h | 1 + + .../zmk/events/mouse_button_state_changed.h | 27 +++ + .../zmk/events/mouse_move_state_changed.h | 33 ++++ + .../zmk/events/mouse_scroll_state_changed.h | 34 ++++ + app/include/zmk/events/mouse_tick.h | 39 +++++ + app/include/zmk/hid.h | 137 ++++++++++++++- + app/include/zmk/hog.h | 1 + + app/include/zmk/mouse.h | 30 ++++ + app/src/behaviors/behavior_mouse_key_press.c | 48 ++++++ + app/src/behaviors/behavior_mouse_move.c | 57 +++++++ + app/src/behaviors/behavior_mouse_scroll.c | 58 +++++++ + app/src/endpoints.c | 35 ++++ + app/src/events/mouse_button_state_changed.c | 10 ++ + app/src/events/mouse_move_state_changed.c | 10 ++ + app/src/events/mouse_scroll_state_changed.c | 10 ++ + app/src/events/mouse_tick.c | 10 ++ + app/src/hid.c | 86 ++++++++++ + app/src/hid_listener.c | 12 +- + app/src/hog.c | 89 ++++++++++ + app/src/main.c | 8 + + app/src/mouse/Kconfig | 38 +++++ + app/src/mouse/key_listener.c | 160 ++++++++++++++++++ + app/src/mouse/main.c | 30 ++++ + app/src/mouse/tick_listener.c | 102 +++++++++++ + app/tests/mouse-keys/mmv/events.patterns | 1 + + .../mouse-keys/mmv/keycode_events.snapshot | 2 + + app/tests/mouse-keys/mmv/native_posix.keymap | 26 +++ + docs/docs/behaviors/mouse-emulation.md | 110 ++++++++++++ + docs/sidebars.js | 1 + + 40 files changed, 1342 insertions(+), 9 deletions(-) + create mode 100644 app/dts/behaviors/mouse_key_press.dtsi + create mode 100644 app/dts/behaviors/mouse_move.dtsi + create mode 100644 app/dts/behaviors/mouse_scroll.dtsi + create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml + create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml + create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml + create mode 100644 app/include/dt-bindings/zmk/mouse.h + create mode 100644 app/include/zmk/events/mouse_button_state_changed.h + create mode 100644 app/include/zmk/events/mouse_move_state_changed.h + create mode 100644 app/include/zmk/events/mouse_scroll_state_changed.h + create mode 100644 app/include/zmk/events/mouse_tick.h + create mode 100644 app/include/zmk/mouse.h + create mode 100644 app/src/behaviors/behavior_mouse_key_press.c + create mode 100644 app/src/behaviors/behavior_mouse_move.c + create mode 100644 app/src/behaviors/behavior_mouse_scroll.c + create mode 100644 app/src/events/mouse_button_state_changed.c + create mode 100644 app/src/events/mouse_move_state_changed.c + create mode 100644 app/src/events/mouse_scroll_state_changed.c + create mode 100644 app/src/events/mouse_tick.c + create mode 100644 app/src/mouse/Kconfig + create mode 100644 app/src/mouse/key_listener.c + create mode 100644 app/src/mouse/main.c + create mode 100644 app/src/mouse/tick_listener.c + create mode 100644 app/tests/mouse-keys/mmv/events.patterns + create mode 100644 app/tests/mouse-keys/mmv/keycode_events.snapshot + create mode 100644 app/tests/mouse-keys/mmv/native_posix.keymap + create mode 100644 docs/docs/behaviors/mouse-emulation.md + +diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt +index 4b61fc7217..351505ad77 100644 +--- a/app/CMakeLists.txt ++++ b/app/CMakeLists.txt +@@ -24,6 +24,9 @@ target_sources(app PRIVATE src/stdlib.c) + target_sources(app PRIVATE src/activity.c) + target_sources(app PRIVATE src/kscan.c) + target_sources(app PRIVATE src/matrix_transform.c) ++target_sources(app PRIVATE src/mouse/key_listener.c) ++target_sources(app PRIVATE src/mouse/main.c) ++target_sources(app PRIVATE src/mouse/tick_listener.c) + target_sources(app PRIVATE src/sensors.c) + target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c) + target_sources(app PRIVATE src/event_manager.c) +@@ -31,6 +34,10 @@ target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/ext_power_generic.c) + 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(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_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c) + target_sources(app PRIVATE src/behaviors/behavior_reset.c) +@@ -53,6 +60,9 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + target_sources(app PRIVATE src/behaviors/behavior_transparent.c) + target_sources(app PRIVATE src/behaviors/behavior_none.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_move.c) ++ target_sources(app PRIVATE src/behaviors/behavior_mouse_scroll.c) + target_sources(app PRIVATE src/combo.c) + target_sources(app PRIVATE src/behavior_queue.c) + target_sources(app PRIVATE src/conditional_layer.c) +diff --git a/app/Kconfig b/app/Kconfig +index f89d3279d1..3c59605814 100644 +--- a/app/Kconfig ++++ b/app/Kconfig +@@ -127,6 +127,10 @@ config ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE + int "Max number of consumer HID reports to queue for sending over BLE" + default 5 + ++config ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE ++ int "Max number of mouse HID reports to queue for sending over BLE" ++ default 20 ++ + config ZMK_BLE_CLEAR_BONDS_ON_START + bool "Configuration that clears all bond information from the keyboard on startup." + default n +@@ -285,6 +289,13 @@ endif + #Display/LED Options + endmenu + ++menu "Mouse Options" ++ ++rsource "src/mouse/Kconfig" ++ ++#Mouse Options ++endmenu ++ + menu "Power Management" + + config ZMK_IDLE_TIMEOUT +diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi +index b3502cbbc1..77eccf912f 100644 +--- a/app/dts/behaviors.dtsi ++++ b/app/dts/behaviors.dtsi +@@ -18,4 +18,7 @@ + #include + #include + #include +-#include +\ No newline at end of file ++#include ++#include ++#include ++#include +diff --git a/app/dts/behaviors/mouse_key_press.dtsi b/app/dts/behaviors/mouse_key_press.dtsi +new file mode 100644 +index 0000000000..8b2aacb366 +--- /dev/null ++++ b/app/dts/behaviors/mouse_key_press.dtsi +@@ -0,0 +1,9 @@ ++/ { ++ behaviors { ++ /omit-if-no-ref/ mkp: behavior_mouse_key_press { ++ compatible = "zmk,behavior-mouse-key-press"; ++ label = "MOUSE_KEY_PRESS"; ++ #binding-cells = <1>; ++ }; ++ }; ++}; +diff --git a/app/dts/behaviors/mouse_move.dtsi b/app/dts/behaviors/mouse_move.dtsi +new file mode 100644 +index 0000000000..d34329c806 +--- /dev/null ++++ b/app/dts/behaviors/mouse_move.dtsi +@@ -0,0 +1,12 @@ ++/ { ++ behaviors { ++ /omit-if-no-ref/ mmv: behavior_mouse_move { ++ compatible = "zmk,behavior-mouse-move"; ++ label = "MOUSE_MOVE"; ++ #binding-cells = <1>; ++ delay-ms = <0>; ++ time-to-max-speed-ms = <300>; ++ acceleration-exponent = <1>; ++ }; ++ }; ++}; +diff --git a/app/dts/behaviors/mouse_scroll.dtsi b/app/dts/behaviors/mouse_scroll.dtsi +new file mode 100644 +index 0000000000..fb54886dcb +--- /dev/null ++++ b/app/dts/behaviors/mouse_scroll.dtsi +@@ -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 = <300>; ++ acceleration-exponent = <0>; ++ }; ++ }; ++}; +diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml +new file mode 100644 +index 0000000000..8540916b72 +--- /dev/null ++++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml +@@ -0,0 +1,5 @@ ++description: Mouse key press/release behavior ++ ++compatible: "zmk,behavior-mouse-key-press" ++ ++include: one_param.yaml +diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml +new file mode 100644 +index 0000000000..73ec34ec2d +--- /dev/null ++++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml +@@ -0,0 +1,13 @@ ++description: Mouse move ++ ++compatible: "zmk,behavior-mouse-move" ++ ++include: one_param.yaml ++ ++properties: ++ delay-ms: ++ type: int ++ time-to-max-speed-ms: ++ type: int ++ acceleration-exponent: ++ type: int +diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml +new file mode 100644 +index 0000000000..5a932bc590 +--- /dev/null ++++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml +@@ -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 +diff --git a/app/include/dt-bindings/zmk/hid_usage_pages.h b/app/include/dt-bindings/zmk/hid_usage_pages.h +index 2ccdba5540..7fa54fd88b 100644 +--- a/app/include/dt-bindings/zmk/hid_usage_pages.h ++++ b/app/include/dt-bindings/zmk/hid_usage_pages.h +@@ -26,6 +26,7 @@ + #define HID_USAGE_GDV (0x06) // Generic Device Controls + #define HID_USAGE_KEY (0x07) // Keyboard/Keypad + #define HID_USAGE_LED (0x08) // LED ++#define HID_USAGE_BUTTON (0x09) // Button + #define HID_USAGE_TELEPHONY (0x0B) // Telephony Device + #define HID_USAGE_CONSUMER (0x0C) // Consumer + #define HID_USAGE_DIGITIZERS (0x0D) // Digitizers +diff --git a/app/include/dt-bindings/zmk/mouse.h b/app/include/dt-bindings/zmk/mouse.h +new file mode 100644 +index 0000000000..cf0415c9b2 +--- /dev/null ++++ b/app/include/dt-bindings/zmk/mouse.h +@@ -0,0 +1,55 @@ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++#pragma once ++ ++/* Mouse press behavior */ ++/* Left click */ ++#define MB1 (0x01) ++#define LCLK (MB1) ++ ++/* Right click */ ++#define MB2 (0x02) ++#define RCLK (MB2) ++ ++/* Middle click */ ++#define MB3 (0x04) ++#define MCLK (MB3) ++ ++#define MB4 (0x08) ++ ++#define MB5 (0x10) ++ ++#define MB6 (0x20) ++ ++#define MB7 (0x40) ++ ++#define MB8 (0x80) ++ ++/* Mouse move behavior */ ++#define MOVE_VERT(vert) ((vert)&0xFFFF) ++#define MOVE_VERT_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF) ++#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_UP MOVE_VERT(-600) ++#define MOVE_DOWN MOVE_VERT(600) ++#define MOVE_LEFT MOVE_HOR(-600) ++#define MOVE_RIGHT MOVE_HOR(600) ++ ++/* 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 SCROLL(hor, vert) (SCROLL_HOR(hor) + SCROLL_VERT(vert)) ++ ++#define SCROLL_UP SCROLL_VERT(10) ++#define SCROLL_DOWN SCROLL_VERT(-10) ++#define SCROLL_LEFT SCROLL_HOR(-10) ++#define SCROLL_RIGHT SCROLL_HOR(10) +diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h +index c8860533e1..450d7ea370 100644 +--- a/app/include/zmk/endpoints.h ++++ b/app/include/zmk/endpoints.h +@@ -13,3 +13,4 @@ int zmk_endpoints_toggle(); + enum zmk_endpoint zmk_endpoints_selected(); + + int zmk_endpoints_send_report(uint16_t usage_page); ++int zmk_endpoints_send_mouse_report(); +diff --git a/app/include/zmk/events/mouse_button_state_changed.h b/app/include/zmk/events/mouse_button_state_changed.h +new file mode 100644 +index 0000000000..7ec4d2087c +--- /dev/null ++++ b/app/include/zmk/events/mouse_button_state_changed.h +@@ -0,0 +1,27 @@ ++ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++#include ++ ++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}); ++} +diff --git a/app/include/zmk/events/mouse_move_state_changed.h b/app/include/zmk/events/mouse_move_state_changed.h +new file mode 100644 +index 0000000000..8866f81d4e +--- /dev/null ++++ b/app/include/zmk/events/mouse_move_state_changed.h +@@ -0,0 +1,33 @@ ++ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++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}); ++} +diff --git a/app/include/zmk/events/mouse_scroll_state_changed.h b/app/include/zmk/events/mouse_scroll_state_changed.h +new file mode 100644 +index 0000000000..fa60e8a742 +--- /dev/null ++++ b/app/include/zmk/events/mouse_scroll_state_changed.h +@@ -0,0 +1,34 @@ ++ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++#include ++ ++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}); ++} +diff --git a/app/include/zmk/events/mouse_tick.h b/app/include/zmk/events/mouse_tick.h +new file mode 100644 +index 0000000000..c75b9b4f86 +--- /dev/null ++++ b/app/include/zmk/events/mouse_tick.h +@@ -0,0 +1,39 @@ ++ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++#include ++ ++struct zmk_mouse_tick { ++ struct vector2d max_move; ++ struct vector2d max_scroll; ++ struct mouse_config move_config; ++ struct mouse_config scroll_config; ++ int64_t *start_time; ++ int64_t timestamp; ++}; ++ ++ZMK_EVENT_DECLARE(zmk_mouse_tick); ++ ++static inline struct zmk_mouse_tick_event *zmk_mouse_tick(struct vector2d max_move, ++ struct vector2d max_scroll, ++ struct mouse_config move_config, ++ struct mouse_config scroll_config, ++ int64_t *movement_start) { ++ return new_zmk_mouse_tick((struct zmk_mouse_tick){ ++ .max_move = max_move, ++ .max_scroll = max_scroll, ++ .move_config = move_config, ++ .scroll_config = scroll_config, ++ .start_time = movement_start, ++ .timestamp = k_uptime_get(), ++ }); ++} +diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h +index 902b76d15a..aa26cd3a54 100644 +--- a/app/include/zmk/hid.h ++++ b/app/include/zmk/hid.h +@@ -10,11 +10,10 @@ + #include + + #include ++#include + #include + #include + +-#define ZMK_HID_KEYBOARD_NKRO_MAX_USAGE HID_USAGE_KEY_KEYPAD_EQUAL +- + #define COLLECTION_REPORT 0x03 + + static const uint8_t zmk_hid_report_desc[] = { +@@ -89,6 +88,116 @@ static const uint8_t zmk_hid_report_desc[] = { + /* INPUT (Data,Ary,Abs) */ + HID_INPUT(0x00), + HID_END_COLLECTION, ++ ++ /* USAGE_PAGE (Generic Desktop) */ ++ HID_GI_USAGE_PAGE, ++ HID_USAGE_GD, ++ /* USAGE (Mouse) */ ++ HID_LI_USAGE, ++ HID_USAGE_GD_MOUSE, ++ /* COLLECTION (Application) */ ++ HID_MI_COLLECTION, ++ COLLECTION_APPLICATION, ++ /* REPORT ID (4) */ ++ HID_GI_REPORT_ID, ++ 0x04, ++ /* USAGE (Pointer) */ ++ HID_LI_USAGE, ++ HID_USAGE_GD_POINTER, ++ /* COLLECTION (Physical) */ ++ HID_MI_COLLECTION, ++ COLLECTION_PHYSICAL, ++ /* USAGE_PAGE (Button) */ ++ HID_GI_USAGE_PAGE, ++ HID_USAGE_BUTTON, ++ /* USAGE_MINIMUM (0x1) (button 1?) */ ++ HID_LI_USAGE_MIN(1), ++ 0x1, ++ /* USAGE_MAXIMUM (0x10) (button 5? Buttons up to 8 still work) */ ++ HID_LI_USAGE_MAX(1), ++ 0x10, ++ /* LOGICAL_MINIMUM (0) */ ++ HID_GI_LOGICAL_MIN(1), ++ 0x00, ++ /* LOGICAL_MAXIMUM (1) */ ++ HID_GI_LOGICAL_MAX(1), ++ 0x01, ++ /* REPORT_SIZE (1) */ ++ HID_GI_REPORT_SIZE, ++ 0x01, ++ /* REPORT_COUNT (16) */ ++ HID_GI_REPORT_COUNT, ++ 0x10, ++ /* INPUT (Data,Var,Abs) */ ++ HID_MI_INPUT, ++ 0x02, ++ /* USAGE_PAGE (Generic Desktop) */ ++ HID_GI_USAGE_PAGE, ++ HID_USAGE_GD, ++ /* LOGICAL_MINIMUM (-32767) */ ++ HID_GI_LOGICAL_MIN(2), ++ 0x01, ++ 0x80, ++ /* LOGICAL_MAXIMUM (32767) */ ++ HID_GI_LOGICAL_MAX(2), ++ 0xFF, ++ 0x7F, ++ /* REPORT_SIZE (16) */ ++ HID_GI_REPORT_SIZE, ++ 0x10, ++ /* REPORT_COUNT (2) */ ++ HID_GI_REPORT_COUNT, ++ 0x02, ++ /* USAGE (X) */ // Vertical scroll ++ HID_LI_USAGE, ++ HID_USAGE_GD_X, ++ /* USAGE (Y) */ ++ HID_LI_USAGE, ++ HID_USAGE_GD_Y, ++ /* Input (Data,Var,Rel) */ ++ HID_MI_INPUT, ++ 0x06, ++ /* LOGICAL_MINIMUM (-127) */ ++ HID_GI_LOGICAL_MIN(1), ++ 0x81, ++ /* LOGICAL_MAXIMUM (127) */ ++ HID_GI_LOGICAL_MAX(1), ++ 0x7F, ++ /* REPORT_SIZE (8) */ ++ HID_GI_REPORT_SIZE, ++ 0x08, ++ /* REPORT_COUNT (1) */ ++ HID_GI_REPORT_COUNT, ++ 0x01, ++ /* USAGE (Wheel) */ ++ HID_LI_USAGE, ++ HID_USAGE_GD_WHEEL, ++ /* Input (Data,Var,Rel) */ ++ HID_MI_INPUT, ++ 0x06, ++ /* USAGE_PAGE (Consumer) */ // Horizontal scroll ++ HID_GI_USAGE_PAGE, ++ HID_USAGE_CONSUMER, ++ /* USAGE (AC Pan) */ ++ 0x0A, ++ 0x38, ++ 0x02, ++ /* LOGICAL_MINIMUM (-127) */ ++ HID_GI_LOGICAL_MIN(1), ++ 0x81, ++ /* LOGICAL_MAXIMUM (127) */ ++ HID_GI_LOGICAL_MAX(1), ++ 0x7F, ++ /* REPORT_COUNT (1) */ ++ HID_GI_REPORT_COUNT, ++ 0x01, ++ /* Input (Data,Var,Rel) */ ++ HID_MI_INPUT, ++ 0x06, ++ /* END COLLECTION */ ++ HID_MI_COLLECTION_END, ++ /* END COLLECTION */ ++ HID_MI_COLLECTION_END, + }; + + // struct zmk_hid_boot_report +@@ -126,6 +235,19 @@ struct zmk_hid_consumer_report { + struct zmk_hid_consumer_report_body body; + } __packed; + ++struct zmk_hid_mouse_report_body { ++ zmk_mouse_button_flags_t buttons; ++ int16_t x; ++ int16_t y; ++ int8_t scroll_y; ++ int8_t scroll_x; ++} __packed; ++ ++struct zmk_hid_mouse_report { ++ uint8_t report_id; ++ struct zmk_hid_mouse_report_body body; ++} __packed; ++ + zmk_mod_flags_t zmk_hid_get_explicit_mods(); + int zmk_hid_register_mod(zmk_mod_t modifier); + int zmk_hid_unregister_mod(zmk_mod_t modifier); +@@ -150,5 +272,16 @@ int zmk_hid_press(uint32_t usage); + int zmk_hid_release(uint32_t usage); + bool zmk_hid_is_pressed(uint32_t usage); + ++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(); ++ + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); + struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); ++struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); +diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h +index 7523fb661a..9debc3ff31 100644 +--- a/app/include/zmk/hog.h ++++ b/app/include/zmk/hog.h +@@ -13,3 +13,4 @@ int zmk_hog_init(); + + 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); ++int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); +diff --git a/app/include/zmk/mouse.h b/app/include/zmk/mouse.h +new file mode 100644 +index 0000000000..f8f857441e +--- /dev/null ++++ b/app/include/zmk/mouse.h +@@ -0,0 +1,30 @@ ++/* ++ * Copyright (c) 2021 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++typedef uint16_t zmk_mouse_button_flags_t; ++typedef uint16_t zmk_mouse_button_t; ++ ++struct mouse_config { ++ int delay_ms; ++ int time_to_max_speed_ms; ++ // acceleration exponent 0: uniform speed ++ // acceleration exponent 1: uniform acceleration ++ // acceleration exponent 2: uniform jerk ++ int acceleration_exponent; ++}; ++ ++struct vector2d { ++ float x; ++ float y; ++}; ++ ++struct k_work_q *zmk_mouse_work_q(); ++int zmk_mouse_init(); +\ No newline at end of file +diff --git a/app/src/behaviors/behavior_mouse_key_press.c b/app/src/behaviors/behavior_mouse_key_press.c +new file mode 100644 +index 0000000000..e5f2709cbf +--- /dev/null ++++ b/app/src/behaviors/behavior_mouse_key_press.c +@@ -0,0 +1,48 @@ ++/* ++ * Copyright (c) 2021 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#define DT_DRV_COMPAT zmk_behavior_mouse_key_press ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++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 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 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, ++ struct zmk_behavior_binding_event event) { ++ LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); ++ return ZMK_EVENT_RAISE( ++ zmk_mouse_button_state_changed_from_encoded(binding->param1, false, event.timestamp)); ++} ++ ++static const struct behavior_driver_api behavior_mouse_key_press_driver_api = { ++ .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; ++ ++#define KP_INST(n) \ ++ DEVICE_DT_INST_DEFINE(n, behavior_mouse_key_press_init, device_pm_control_nop, NULL, NULL, \ ++ APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ ++ &behavior_mouse_key_press_driver_api); ++ ++DT_INST_FOREACH_STATUS_OKAY(KP_INST) ++ ++#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ +\ No newline at end of file +diff --git a/app/src/behaviors/behavior_mouse_move.c b/app/src/behaviors/behavior_mouse_move.c +new file mode 100644 +index 0000000000..5977a039d1 +--- /dev/null ++++ b/app/src/behaviors/behavior_mouse_move.c +@@ -0,0 +1,57 @@ ++/* ++ * Copyright (c) 2021 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#define DT_DRV_COMPAT zmk_behavior_mouse_move ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); ++ ++#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) ++ ++static int behavior_mouse_move_init(const struct device *dev) { return 0; }; ++ ++static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, ++ struct zmk_behavior_binding_event event) { ++ LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); ++ const struct device *dev = device_get_binding(binding->behavior_dev); ++ const struct mouse_config *config = dev->config; ++ return ZMK_EVENT_RAISE( ++ zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, true, event.timestamp)); ++} ++ ++static int on_keymap_binding_released(struct zmk_behavior_binding *binding, ++ struct zmk_behavior_binding_event event) { ++ LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); ++ const struct device *dev = device_get_binding(binding->behavior_dev); ++ const struct mouse_config *config = dev->config; ++ return ZMK_EVENT_RAISE(zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, ++ false, event.timestamp)); ++} ++ ++static const struct behavior_driver_api behavior_mouse_move_driver_api = { ++ .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; ++ ++#define KP_INST(n) \ ++ static struct mouse_config behavior_mouse_move_config_##n = { \ ++ .delay_ms = DT_INST_PROP(n, delay_ms), \ ++ .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ ++ .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ ++ }; \ ++ DEVICE_DT_INST_DEFINE(n, behavior_mouse_move_init, device_pm_control_nop, NULL, \ ++ &behavior_mouse_move_config_##n, APPLICATION, \ ++ CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_move_driver_api); ++ ++DT_INST_FOREACH_STATUS_OKAY(KP_INST) ++ ++#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ +diff --git a/app/src/behaviors/behavior_mouse_scroll.c b/app/src/behaviors/behavior_mouse_scroll.c +new file mode 100644 +index 0000000000..6416235265 +--- /dev/null ++++ b/app/src/behaviors/behavior_mouse_scroll.c +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) 2021 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#define DT_DRV_COMPAT zmk_behavior_mouse_scroll ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); ++ ++#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) ++ ++static int behavior_mouse_scroll_init(const struct device *dev) { return 0; }; ++ ++static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, ++ struct zmk_behavior_binding_event event) { ++ LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); ++ const struct device *dev = device_get_binding(binding->behavior_dev); ++ const struct mouse_config *config = dev->config; ++ return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, ++ true, event.timestamp)); ++} ++ ++static int on_keymap_binding_released(struct zmk_behavior_binding *binding, ++ struct zmk_behavior_binding_event event) { ++ LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); ++ const struct device *dev = device_get_binding(binding->behavior_dev); ++ const struct mouse_config *config = dev->config; ++ return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, ++ false, event.timestamp)); ++} ++ ++static const struct behavior_driver_api behavior_mouse_scroll_driver_api = { ++ .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; ++ ++#define KP_INST(n) \ ++ static struct mouse_config behavior_mouse_scroll_config_##n = { \ ++ .delay_ms = DT_INST_PROP(n, delay_ms), \ ++ .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ ++ .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ ++ }; \ ++ DEVICE_DT_INST_DEFINE(n, behavior_mouse_scroll_init, device_pm_control_nop, 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) */ +diff --git a/app/src/endpoints.c b/app/src/endpoints.c +index 3376001060..0728fff3bd 100644 +--- a/app/src/endpoints.c ++++ b/app/src/endpoints.c +@@ -144,6 +144,40 @@ int zmk_endpoints_send_report(uint16_t usage_page) { + } + } + ++int zmk_endpoints_send_mouse_report() { ++ struct zmk_hid_mouse_report *mouse_report = zmk_hid_get_mouse_report(); ++ ++ switch (current_endpoint) { ++#if IS_ENABLED(CONFIG_ZMK_USB) ++ case ZMK_ENDPOINT_USB: { ++ int err = zmk_usb_hid_send_report((uint8_t *)mouse_report, sizeof(*mouse_report)); ++ if (err) { ++ LOG_ERR("FAILED TO SEND OVER USB: %d", err); ++ } ++ return err; ++ } ++#endif /* IS_ENABLED(CONFIG_ZMK_USB) */ ++ ++#if IS_ENABLED(CONFIG_ZMK_BLE) ++ case ZMK_ENDPOINT_BLE: { ++#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) ++ int err = zmk_hog_send_mouse_report_direct(&mouse_report->body); ++#else ++ int err = zmk_hog_send_mouse_report(&mouse_report->body); ++#endif ++ if (err) { ++ LOG_ERR("FAILED TO SEND OVER HOG: %d", err); ++ } ++ return err; ++ } ++#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */ ++ ++ default: ++ LOG_ERR("Unsupported endpoint %d", current_endpoint); ++ return -ENOTSUP; ++ } ++} ++ + #if IS_ENABLED(CONFIG_SETTINGS) + + static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb, +@@ -228,6 +262,7 @@ static enum zmk_endpoint get_selected_endpoint() { + static void disconnect_current_endpoint() { + zmk_hid_keyboard_clear(); + zmk_hid_consumer_clear(); ++ zmk_hid_mouse_clear(); + + zmk_endpoints_send_report(HID_USAGE_KEY); + zmk_endpoints_send_report(HID_USAGE_CONSUMER); +diff --git a/app/src/events/mouse_button_state_changed.c b/app/src/events/mouse_button_state_changed.c +new file mode 100644 +index 0000000000..e1ede41421 +--- /dev/null ++++ b/app/src/events/mouse_button_state_changed.c +@@ -0,0 +1,10 @@ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++#include ++ ++ZMK_EVENT_IMPL(zmk_mouse_button_state_changed); +diff --git a/app/src/events/mouse_move_state_changed.c b/app/src/events/mouse_move_state_changed.c +new file mode 100644 +index 0000000000..faf89cb8ab +--- /dev/null ++++ b/app/src/events/mouse_move_state_changed.c +@@ -0,0 +1,10 @@ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++#include ++ ++ZMK_EVENT_IMPL(zmk_mouse_move_state_changed); +diff --git a/app/src/events/mouse_scroll_state_changed.c b/app/src/events/mouse_scroll_state_changed.c +new file mode 100644 +index 0000000000..4b4170fe00 +--- /dev/null ++++ b/app/src/events/mouse_scroll_state_changed.c +@@ -0,0 +1,10 @@ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++#include ++ ++ZMK_EVENT_IMPL(zmk_mouse_scroll_state_changed); +diff --git a/app/src/events/mouse_tick.c b/app/src/events/mouse_tick.c +new file mode 100644 +index 0000000000..0930b9fb90 +--- /dev/null ++++ b/app/src/events/mouse_tick.c +@@ -0,0 +1,10 @@ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++#include ++ ++ZMK_EVENT_IMPL(zmk_mouse_tick); +diff --git a/app/src/hid.c b/app/src/hid.c +index c3462ddeb7..9e7451b7f4 100644 +--- a/app/src/hid.c ++++ b/app/src/hid.c +@@ -16,6 +16,9 @@ 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_mouse_report mouse_report = { ++ .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. + // Only release the modifier if the count is 0. + static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +@@ -246,6 +249,85 @@ bool zmk_hid_is_pressed(uint32_t usage) { + return false; + } + ++// Keep track of how often a button was pressed. ++// 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 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) { ++ 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 (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_mod_t i = 0; i < 16; i++) { ++ if (buttons & (1 << i)) { ++ zmk_hid_mouse_button_press(i); ++ } ++ } ++ return 0; ++} ++ ++int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { ++ for (zmk_mod_t i = 0; i < 16; i++) { ++ if (buttons & (1 << i)) { ++ zmk_hid_mouse_button_release(i); ++ } ++ } ++ return 0; ++} ++ ++void zmk_hid_mouse_movement_set(int16_t x, int16_t y) { ++ mouse_report.body.x = x; ++ mouse_report.body.y = y; ++ LOG_DBG("Mouse movement set to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y); ++} ++ ++void zmk_hid_mouse_movement_update(int16_t x, int16_t y) { ++ mouse_report.body.x += x; ++ mouse_report.body.y += y; ++ LOG_DBG("Mouse movement updated to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y); ++} ++ ++void zmk_hid_mouse_scroll_set(int8_t x, int8_t y) { ++ mouse_report.body.scroll_x = x; ++ mouse_report.body.scroll_y = y; ++ LOG_DBG("Mouse scroll set to 0x%02X 0x%02X ", mouse_report.body.scroll_x, ++ mouse_report.body.scroll_y); ++} ++ ++void zmk_hid_mouse_scroll_update(int8_t x, int8_t y) { ++ mouse_report.body.scroll_x += x; ++ mouse_report.body.scroll_y += y; ++ LOG_DBG("Mouse scroll updated to 0x%02X 0x%02X ", mouse_report.body.scroll_x, ++ mouse_report.body.scroll_y); ++} ++void zmk_hid_mouse_clear() { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } ++ + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { + return &keyboard_report; + } +@@ -253,3 +335,7 @@ struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { + struct zmk_hid_consumer_report *zmk_hid_get_consumer_report() { + return &consumer_report; + } ++ ++struct zmk_hid_mouse_report *zmk_hid_get_mouse_report() { ++ return &mouse_report; ++} +diff --git a/app/src/hid_listener.c b/app/src/hid_listener.c +index e233b0b8ed..8cde3a4323 100644 +--- a/app/src/hid_listener.c ++++ b/app/src/hid_listener.c +@@ -11,7 +11,6 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + + #include + #include +-#include + #include + #include + #include +@@ -71,13 +70,14 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed + } + + int hid_listener(const zmk_event_t *eh) { +- const struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh); +- if (ev) { +- if (ev->state) { +- hid_listener_keycode_pressed(ev); ++ const struct zmk_keycode_state_changed *kc_ev = as_zmk_keycode_state_changed(eh); ++ if (kc_ev) { ++ if (kc_ev->state) { ++ hid_listener_keycode_pressed(kc_ev); + } else { +- hid_listener_keycode_released(ev); ++ hid_listener_keycode_released(kc_ev); + } ++ return 0; + } + return 0; + } +diff --git a/app/src/hog.c b/app/src/hog.c +index 3dd3e874a5..f915d27a91 100644 +--- a/app/src/hog.c ++++ b/app/src/hog.c +@@ -56,6 +56,11 @@ static struct hids_report consumer_input = { + .type = HIDS_INPUT, + }; + ++static struct hids_report mouse_input = { ++ .id = 0x04, ++ .type = HIDS_INPUT, ++}; ++ + static bool host_requests_notification = false; + static uint8_t ctrl_point; + // static uint8_t proto_mode; +@@ -93,6 +98,13 @@ static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, + sizeof(struct zmk_hid_consumer_report_body)); + } + ++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)); ++} ++ + // 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, +@@ -139,6 +151,13 @@ BT_GATT_SERVICE_DEFINE( + 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, &consumer_input), ++ ++ 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_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); + +@@ -261,6 +280,76 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { + return 0; + }; + ++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) { ++ 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_NO_WAIT); ++ if (err) { ++ switch (err) { ++ case -EAGAIN: { ++ LOG_WRN("Mouse message queue full, dropping report"); ++ return err; ++ } ++ 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; ++}; ++ ++int zmk_hog_send_mouse_report_direct(struct zmk_hid_mouse_report_body *report) { ++ struct bt_conn *conn = destination_connection(); ++ if (conn == NULL) { ++ return 1; ++ } ++ ++ 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) { ++ LOG_DBG("Error notifying %d", err); ++ return err; ++ } ++ ++ bt_conn_unref(conn); ++ ++ return 0; ++}; ++ + int zmk_hog_init(const struct device *_arg) { + 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/main.c b/app/src/main.c +index ae604a7b9e..d3b3e578b3 100644 +--- a/app/src/main.c ++++ b/app/src/main.c +@@ -17,6 +17,10 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL); + #include + #include + ++#ifdef CONFIG_ZMK_MOUSE ++#include ++#endif /* CONFIG_ZMK_MOUSE */ ++ + #define ZMK_KSCAN_DEV DT_LABEL(ZMK_MATRIX_NODE_ID) + + void main(void) { +@@ -29,4 +33,8 @@ void main(void) { + #ifdef CONFIG_ZMK_DISPLAY + zmk_display_init(); + #endif /* CONFIG_ZMK_DISPLAY */ ++ ++#ifdef CONFIG_ZMK_MOUSE ++ zmk_mouse_init(); ++#endif /* CONFIG_ZMK_MOUSE */ + } +diff --git a/app/src/mouse/Kconfig b/app/src/mouse/Kconfig +new file mode 100644 +index 0000000000..1161b86b42 +--- /dev/null ++++ b/app/src/mouse/Kconfig +@@ -0,0 +1,38 @@ ++# Copyright (c) 2021 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++menuconfig ZMK_MOUSE ++ bool "Enable ZMK mouse emulation" ++ default n ++ ++config ZMK_MOUSE_TICK_DURATION ++ int "Mouse tick duration in ms" ++ default 8 ++ ++if ZMK_MOUSE ++ ++choice ZMK_MOUSE_WORK_QUEUE ++ prompt "Work queue selection for mouse events" ++ default ZMK_MOUSE_WORK_QUEUE_DEDICATED ++ ++config ZMK_MOUSE_WORK_QUEUE_SYSTEM ++ bool "Use default system work queue for mouse events" ++ ++config ZMK_MOUSE_WORK_QUEUE_DEDICATED ++ bool "Use dedicated work queue for mouse events" ++ ++endchoice ++ ++if ZMK_MOUSE_WORK_QUEUE_DEDICATED ++ ++config ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE ++ int "Stack size for dedicated mouse thread/queue" ++ default 2048 ++ ++config ZMK_MOUSE_DEDICATED_THREAD_PRIORITY ++ int "Thread priority for dedicated mouse thread/queue" ++ default 3 ++ ++endif # ZMK_MOUSE_WORK_QUEUE_DEDICATED ++ ++endif +diff --git a/app/src/mouse/key_listener.c b/app/src/mouse/key_listener.c +new file mode 100644 +index 0000000000..713d032352 +--- /dev/null ++++ b/app/src/mouse/key_listener.c +@@ -0,0 +1,160 @@ ++/* ++ * Copyright (c) 2021 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++#include ++ ++LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static struct vector2d move_speed = {0}; ++static struct vector2d scroll_speed = {0}; ++static struct mouse_config move_config = (struct mouse_config){0}; ++static struct mouse_config scroll_config = (struct mouse_config){0}; ++static int64_t start_time = 0; ++ ++bool equals(const struct mouse_config *one, const struct mouse_config *other) { ++ return one->delay_ms == other->delay_ms && ++ one->time_to_max_speed_ms == other->time_to_max_speed_ms && ++ one->acceleration_exponent == other->acceleration_exponent; ++} ++ ++static void clear_mouse_state(struct k_work *work) { ++ move_speed = (struct vector2d){0}; ++ scroll_speed = (struct vector2d){0}; ++ start_time = 0; ++ zmk_hid_mouse_movement_set(0, 0); ++ zmk_hid_mouse_scroll_set(0, 0); ++ LOG_DBG("Clearing state"); ++} ++ ++K_WORK_DEFINE(mouse_clear, &clear_mouse_state); ++ ++void mouse_clear_cb(struct k_timer *dummy) { ++ k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_clear); ++} ++ ++static void mouse_tick_timer_handler(struct k_work *work) { ++ zmk_hid_mouse_movement_set(0, 0); ++ zmk_hid_mouse_scroll_set(0, 0); ++ LOG_DBG("Raising mouse tick event"); ++ ZMK_EVENT_RAISE( ++ zmk_mouse_tick(move_speed, scroll_speed, move_config, scroll_config, &start_time)); ++ zmk_endpoints_send_mouse_report(); ++} ++ ++K_WORK_DEFINE(mouse_tick, &mouse_tick_timer_handler); ++ ++void mouse_timer_cb(struct k_timer *dummy) { ++ LOG_DBG("Submitting mouse work to queue"); ++ k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_tick); ++} ++ ++K_TIMER_DEFINE(mouse_timer, mouse_timer_cb, mouse_clear_cb); ++ ++static int mouse_timer_ref_count = 0; ++ ++void mouse_timer_ref() { ++ if (mouse_timer_ref_count == 0) { ++ start_time = k_uptime_get(); ++ k_timer_start(&mouse_timer, K_NO_WAIT, K_MSEC(CONFIG_ZMK_MOUSE_TICK_DURATION)); ++ } ++ mouse_timer_ref_count += 1; ++} ++ ++void mouse_timer_unref() { ++ if (mouse_timer_ref_count > 0) { ++ mouse_timer_ref_count--; ++ } ++ if (mouse_timer_ref_count == 0) { ++ k_timer_stop(&mouse_timer); ++ } ++} ++ ++static void listener_mouse_move_pressed(const struct zmk_mouse_move_state_changed *ev) { ++ move_speed.x += ev->max_speed.x; ++ move_speed.y += ev->max_speed.y; ++ mouse_timer_ref(); ++} ++ ++static void listener_mouse_move_released(const struct zmk_mouse_move_state_changed *ev) { ++ move_speed.x -= ev->max_speed.x; ++ move_speed.y -= ev->max_speed.y; ++ mouse_timer_unref(); ++} ++ ++static void listener_mouse_scroll_pressed(const struct zmk_mouse_scroll_state_changed *ev) { ++ scroll_speed.x += ev->max_speed.x; ++ scroll_speed.y += ev->max_speed.y; ++ mouse_timer_ref(); ++} ++ ++static void listener_mouse_scroll_released(const struct zmk_mouse_scroll_state_changed *ev) { ++ scroll_speed.x -= ev->max_speed.x; ++ scroll_speed.y -= ev->max_speed.y; ++ mouse_timer_unref(); ++} ++ ++static void listener_mouse_button_pressed(const struct zmk_mouse_button_state_changed *ev) { ++ LOG_DBG("buttons: 0x%02X", ev->buttons); ++ zmk_hid_mouse_buttons_press(ev->buttons); ++ zmk_endpoints_send_mouse_report(); ++} ++ ++static void listener_mouse_button_released(const struct zmk_mouse_button_state_changed *ev) { ++ LOG_DBG("buttons: 0x%02X", ev->buttons); ++ zmk_hid_mouse_buttons_release(ev->buttons); ++ zmk_endpoints_send_mouse_report(); ++} ++ ++int mouse_listener(const zmk_event_t *eh) { ++ const struct zmk_mouse_move_state_changed *mmv_ev = as_zmk_mouse_move_state_changed(eh); ++ if (mmv_ev) { ++ if (!equals(&move_config, &(mmv_ev->config))) ++ move_config = mmv_ev->config; ++ ++ if (mmv_ev->state) { ++ listener_mouse_move_pressed(mmv_ev); ++ } else { ++ listener_mouse_move_released(mmv_ev); ++ } ++ return 0; ++ } ++ const struct zmk_mouse_scroll_state_changed *msc_ev = as_zmk_mouse_scroll_state_changed(eh); ++ if (msc_ev) { ++ if (!equals(&scroll_config, &(msc_ev->config))) ++ scroll_config = msc_ev->config; ++ if (msc_ev->state) { ++ listener_mouse_scroll_pressed(msc_ev); ++ } else { ++ listener_mouse_scroll_released(msc_ev); ++ } ++ return 0; ++ } ++ const struct zmk_mouse_button_state_changed *mbt_ev = as_zmk_mouse_button_state_changed(eh); ++ if (mbt_ev) { ++ if (mbt_ev->state) { ++ listener_mouse_button_pressed(mbt_ev); ++ } else { ++ listener_mouse_button_released(mbt_ev); ++ } ++ return 0; ++ } ++ return 0; ++} ++ ++ZMK_LISTENER(mouse_listener, mouse_listener); ++ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_button_state_changed); ++ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_move_state_changed); ++ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_scroll_state_changed); +diff --git a/app/src/mouse/main.c b/app/src/mouse/main.c +new file mode 100644 +index 0000000000..49208a76ef +--- /dev/null ++++ b/app/src/mouse/main.c +@@ -0,0 +1,30 @@ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++#include ++ ++#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) ++K_THREAD_STACK_DEFINE(mouse_work_stack_area, CONFIG_ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE); ++static struct k_work_q mouse_work_q; ++#endif ++ ++struct k_work_q *zmk_mouse_work_q() { ++#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) ++ return &mouse_work_q; ++#else ++ return &k_sys_work_q; ++#endif ++} ++ ++int zmk_mouse_init() { ++#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) ++ k_work_q_start(&mouse_work_q, mouse_work_stack_area, ++ K_THREAD_STACK_SIZEOF(mouse_work_stack_area), ++ CONFIG_ZMK_MOUSE_DEDICATED_THREAD_PRIORITY); ++#endif ++ return 0; ++} +\ No newline at end of file +diff --git a/app/src/mouse/tick_listener.c b/app/src/mouse/tick_listener.c +new file mode 100644 +index 0000000000..9c76bd5d2a +--- /dev/null ++++ b/app/src/mouse/tick_listener.c +@@ -0,0 +1,102 @@ ++/* ++ * Copyright (c) 2020 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++ ++LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); ++ ++#include ++#include ++#include ++#include ++ ++#include // CLAMP ++ ++#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 ++ ++struct vector2d move_remainder = {0}; ++struct vector2d scroll_remainder = {0}; ++ ++static int64_t ms_since_start(int64_t start, int64_t now, int64_t delay) { ++ int64_t move_duration = now - (start + delay); ++ // start can be in the future if there's a delay ++ if (move_duration < 0) { ++ move_duration = 0; ++ } ++ return move_duration; ++} ++ ++static float speed(const struct mouse_config *config, float max_speed, int64_t duration_ms) { ++ // Calculate the speed based on MouseKeysAccel ++ // See https://en.wikipedia.org/wiki/Mouse_keys ++ if (duration_ms > config->time_to_max_speed_ms || config->time_to_max_speed_ms == 0 || ++ config->acceleration_exponent == 0) { ++ return max_speed; ++ } ++ float time_fraction = (float)duration_ms / config->time_to_max_speed_ms; ++ return max_speed * powf(time_fraction, config->acceleration_exponent); ++} ++ ++static void track_remainder(float *move, float *remainder) { ++ float new_move = *move + *remainder; ++ *remainder = new_move - (int)new_move; ++ *move = (int)new_move; ++} ++ ++static struct vector2d update_movement(struct vector2d *remainder, ++ const struct mouse_config *config, struct vector2d max_speed, ++ int64_t now, int64_t *start_time) { ++ struct vector2d move = {0}; ++ if (max_speed.x == 0 && max_speed.y == 0) { ++ *remainder = (struct vector2d){0}; ++ return move; ++ } ++ ++ int64_t move_duration = ms_since_start(*start_time, now, config->delay_ms); ++ move = (struct vector2d){ ++ .x = speed(config, max_speed.x, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, ++ .y = speed(config, max_speed.y, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, ++ }; ++ ++ track_remainder(&(move.x), &(remainder->x)); ++ track_remainder(&(move.y), &(remainder->y)); ++ ++ return move; ++} ++ ++static void mouse_tick_handler(const struct zmk_mouse_tick *tick) { ++ struct vector2d move = update_movement(&move_remainder, &(tick->move_config), tick->max_move, ++ tick->timestamp, tick->start_time); ++ zmk_hid_mouse_movement_update((int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX), ++ (int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX)); ++ struct vector2d scroll = update_movement(&scroll_remainder, &(tick->scroll_config), ++ tick->max_scroll, tick->timestamp, tick->start_time); ++ zmk_hid_mouse_scroll_update((int8_t)CLAMP(scroll.x, INT8_MIN, INT8_MAX), ++ (int8_t)CLAMP(scroll.y, INT8_MIN, INT8_MAX)); ++} ++ ++int zmk_mouse_tick_listener(const zmk_event_t *eh) { ++ const struct zmk_mouse_tick *tick = as_zmk_mouse_tick(eh); ++ if (tick) { ++ mouse_tick_handler(tick); ++ return 0; ++ } ++ return 0; ++} ++ ++ZMK_LISTENER(zmk_mouse_tick_listener, zmk_mouse_tick_listener); ++ZMK_SUBSCRIPTION(zmk_mouse_tick_listener, zmk_mouse_tick); +\ No newline at end of file +diff --git a/app/tests/mouse-keys/mmv/events.patterns b/app/tests/mouse-keys/mmv/events.patterns +new file mode 100644 +index 0000000000..833100f6ac +--- /dev/null ++++ b/app/tests/mouse-keys/mmv/events.patterns +@@ -0,0 +1 @@ ++s/.*hid_listener_keycode_//p +\ No newline at end of file +diff --git a/app/tests/mouse-keys/mmv/keycode_events.snapshot b/app/tests/mouse-keys/mmv/keycode_events.snapshot +new file mode 100644 +index 0000000000..259501ba3d +--- /dev/null ++++ b/app/tests/mouse-keys/mmv/keycode_events.snapshot +@@ -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 +diff --git a/app/tests/mouse-keys/mmv/native_posix.keymap b/app/tests/mouse-keys/mmv/native_posix.keymap +new file mode 100644 +index 0000000000..ecf06601c0 +--- /dev/null ++++ b/app/tests/mouse-keys/mmv/native_posix.keymap +@@ -0,0 +1,26 @@ ++#include ++#include ++#include ++#include ++ ++/ { ++ 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) ++ >; ++}; +\ No newline at end of file +diff --git a/docs/docs/behaviors/mouse-emulation.md b/docs/docs/behaviors/mouse-emulation.md +new file mode 100644 +index 0000000000..efe095e7a0 +--- /dev/null ++++ b/docs/docs/behaviors/mouse-emulation.md +@@ -0,0 +1,110 @@ ++--- ++title: Mouse Emulation Behaviors ++sidebar_label: Mouse Emulation ++--- ++ ++## Summary ++ ++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. ++ ++## Configuration options ++ ++This feature should be enabled via a config option: ++ ++``` ++CONFIG_ZMK_MOUSE=y ++``` ++ ++This option enables several others. ++ ++### Dedicated thread processing ++ ++`CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED` is enabled by default and separates the processing of mouse signals into a dedicated thread, significantly improving performance. ++ ++### Tick rate configuration ++ ++`CONFIG_ZMK_MOUSE_TICK_DURATION` sets the tick rate for mouse polling. It is set to 8 ms. by default. ++ ++## Keycode Defines ++ ++To make it easier to encode the HID keycode numeric values, most keymaps include ++the [`dt-bindings/zmk/mouse.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/mouse.h) header ++provided by ZMK near the top: ++ ++``` ++#include ++``` ++ ++Doing so allows using a set of defines such as `MOVE_UP`, `MOVE_DOWN`, `LCLK` and `SCROLL_UP` with these behaviors. ++ ++## Mouse Button Press ++ ++This behavior can press/release up to 16 mouse buttons. ++ ++### Behavior Binding ++ ++- Reference: `&mkp` ++- Parameter: A `uint16` with each bit referring to a button. ++ ++Example: ++ ++``` ++&mkp LCLK ++``` ++ ++## Mouse Movement ++ ++This behavior is used to manipulate the cursor. ++ ++### Behavior Binding ++ ++- Reference: `&mmv` ++- Parameter: A `uint32` with the first 16 bits relating to horizontal movement ++ and the last 16 - to vertical movement. ++ ++Example: ++ ++``` ++&mmv MOVE_UP ++``` ++ ++## Mouse Scrolling ++ ++This behaviour is used to scroll, both horizontally and vertically. ++ ++### Behavior Binding ++ ++- Reference: `&mwh` ++- Parameter: A `uint16` with the first 8 bits relating to horizontal movement ++ and the last 8 - to vertical movement. ++ ++Example: ++ ++``` ++&mwh SCROLL_UP ++``` ++ ++## Acceleration ++ ++Both mouse movement and scrolling have independently configurable acceleration profiles with three parameters: delay before movement, time to max speed and the acceleration exponent. ++The exponent is usually set to 0 for constant speed, 1 for uniform acceleration or 2 for uniform jerk. ++ ++These profiles can be configured inside your keymap: ++ ++``` ++&mmv { ++ time-to-max-speed-ms = <500>; ++}; ++ ++&mwh { ++ acceleration-exponent=<1>; ++}; ++ ++/ { ++ keymap { ++ ... ++ }; ++}; ++``` +diff --git a/docs/sidebars.js b/docs/sidebars.js +index 7b445a2901..5e18b05dd0 100644 +--- a/docs/sidebars.js ++++ b/docs/sidebars.js +@@ -34,6 +34,7 @@ module.exports = { + "behaviors/tap-dance", + "behaviors/caps-word", + "behaviors/key-repeat", ++ "behaviors/mouse-emulation", + "behaviors/reset", + "behaviors/bluetooth", + "behaviors/outputs", + +From f8a0c397e25cb4a4c8c1a2d91da2db0e9401ad73 Mon Sep 17 00:00:00 2001 +From: Jesper Jensen +Date: Wed, 17 Aug 2022 23:26:23 +0200 +Subject: [PATCH 2/2] Remove everything not required for buttons + +I've stripped out everything not strictly required for mouse button HID +support. This include dropping the worker thread completely, shaving +down the USB HID descriptor. Flattening the mouse/ source directory +structure and removing a bunch of event handling. + +I have kept the mouse event handling separate from the other HID event +handling since I figured that was a pretty neat split. If that's a bad +idea, do tell. + +I've also added a test case for mouse button emulation, since that was +untested before. + +The changes have been tested on a corne (split) in usb mode. Bindings on +both the left and the right side works (with the left side as master). +--- + app/CMakeLists.txt | 9 +- + app/Kconfig | 4 +- + app/dts/behaviors.dtsi | 2 - + app/dts/behaviors/mouse_move.dtsi | 12 -- + app/dts/behaviors/mouse_scroll.dtsi | 12 -- + .../behaviors/zmk,behavior-mouse-move.yaml | 13 -- + .../behaviors/zmk,behavior-mouse-scroll.yaml | 13 -- + app/include/dt-bindings/zmk/mouse.h | 26 --- + .../zmk/events/mouse_button_state_changed.h | 2 +- + .../zmk/events/mouse_move_state_changed.h | 33 ---- + .../zmk/events/mouse_scroll_state_changed.h | 34 ---- + app/include/zmk/events/mouse_tick.h | 39 ----- + app/include/zmk/hid.h | 135 ++------------- + app/include/zmk/mouse.h | 17 -- + app/src/behaviors/behavior_mouse_move.c | 57 ------- + app/src/behaviors/behavior_mouse_scroll.c | 58 ------- + app/src/endpoints.c | 4 - + app/src/events/mouse_move_state_changed.c | 10 -- + app/src/events/mouse_scroll_state_changed.c | 10 -- + app/src/events/mouse_tick.c | 10 -- + app/src/hid.c | 28 +-- + app/src/hid_listener.c | 12 +- + app/src/hog.c | 21 --- + app/src/main.c | 8 - + app/src/mouse.c | 43 +++++ + app/src/mouse/Kconfig | 38 ----- + app/src/mouse/key_listener.c | 160 ------------------ + app/src/mouse/main.c | 30 ---- + app/src/mouse/tick_listener.c | 102 ----------- + app/tests/mouse-keys/mkp/events.patterns | 1 + + .../mouse-keys/mkp/keycode_events.snapshot | 10 ++ + .../{mmv => mkp}/native_posix.keymap | 12 +- + .../mouse-keys/mkp/native_posix_64.keymap | 28 +++ + app/tests/mouse-keys/mmv/events.patterns | 1 - + .../mouse-keys/mmv/keycode_events.snapshot | 2 - + docs/docs/behaviors/mouse-emulation.md | 67 +------- + 36 files changed, 120 insertions(+), 943 deletions(-) + delete mode 100644 app/dts/behaviors/mouse_move.dtsi + delete mode 100644 app/dts/behaviors/mouse_scroll.dtsi + delete mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml + delete mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml + delete mode 100644 app/include/zmk/events/mouse_move_state_changed.h + delete mode 100644 app/include/zmk/events/mouse_scroll_state_changed.h + delete mode 100644 app/include/zmk/events/mouse_tick.h + delete mode 100644 app/src/behaviors/behavior_mouse_move.c + delete mode 100644 app/src/behaviors/behavior_mouse_scroll.c + delete mode 100644 app/src/events/mouse_move_state_changed.c + delete mode 100644 app/src/events/mouse_scroll_state_changed.c + delete mode 100644 app/src/events/mouse_tick.c + create mode 100644 app/src/mouse.c + delete mode 100644 app/src/mouse/Kconfig + delete mode 100644 app/src/mouse/key_listener.c + delete mode 100644 app/src/mouse/main.c + delete mode 100644 app/src/mouse/tick_listener.c + create mode 100644 app/tests/mouse-keys/mkp/events.patterns + create mode 100644 app/tests/mouse-keys/mkp/keycode_events.snapshot + rename app/tests/mouse-keys/{mmv => mkp}/native_posix.keymap (64%) + create mode 100644 app/tests/mouse-keys/mkp/native_posix_64.keymap + delete mode 100644 app/tests/mouse-keys/mmv/events.patterns + delete mode 100644 app/tests/mouse-keys/mmv/keycode_events.snapshot + +diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt +index 351505ad77..cf18f4c029 100644 +--- a/app/CMakeLists.txt ++++ b/app/CMakeLists.txt +@@ -24,9 +24,6 @@ target_sources(app PRIVATE src/stdlib.c) + target_sources(app PRIVATE src/activity.c) + target_sources(app PRIVATE src/kscan.c) + target_sources(app PRIVATE src/matrix_transform.c) +-target_sources(app PRIVATE src/mouse/key_listener.c) +-target_sources(app PRIVATE src/mouse/main.c) +-target_sources(app PRIVATE src/mouse/tick_listener.c) + target_sources(app PRIVATE src/sensors.c) + target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c) + target_sources(app PRIVATE src/event_manager.c) +@@ -35,15 +32,13 @@ 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(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_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c) + target_sources(app PRIVATE src/behaviors/behavior_reset.c) + target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c) + if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + target_sources(app PRIVATE src/hid.c) ++ target_sources(app PRIVATE src/mouse.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) +@@ -61,8 +56,6 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + target_sources(app PRIVATE src/behaviors/behavior_none.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_move.c) +- target_sources(app PRIVATE src/behaviors/behavior_mouse_scroll.c) + target_sources(app PRIVATE src/combo.c) + target_sources(app PRIVATE src/behavior_queue.c) + target_sources(app PRIVATE src/conditional_layer.c) +diff --git a/app/Kconfig b/app/Kconfig +index 3c59605814..7a422c2b9c 100644 +--- a/app/Kconfig ++++ b/app/Kconfig +@@ -291,7 +291,9 @@ endmenu + + menu "Mouse Options" + +-rsource "src/mouse/Kconfig" ++config ZMK_MOUSE ++ bool "Enable ZMK mouse emulation" ++ default n + + #Mouse Options + endmenu +diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi +index 77eccf912f..23f2fee280 100644 +--- a/app/dts/behaviors.dtsi ++++ b/app/dts/behaviors.dtsi +@@ -20,5 +20,3 @@ + #include + #include + #include +-#include +-#include +diff --git a/app/dts/behaviors/mouse_move.dtsi b/app/dts/behaviors/mouse_move.dtsi +deleted file mode 100644 +index d34329c806..0000000000 +--- a/app/dts/behaviors/mouse_move.dtsi ++++ /dev/null +@@ -1,12 +0,0 @@ +-/ { +- behaviors { +- /omit-if-no-ref/ mmv: behavior_mouse_move { +- compatible = "zmk,behavior-mouse-move"; +- label = "MOUSE_MOVE"; +- #binding-cells = <1>; +- delay-ms = <0>; +- time-to-max-speed-ms = <300>; +- acceleration-exponent = <1>; +- }; +- }; +-}; +diff --git a/app/dts/behaviors/mouse_scroll.dtsi b/app/dts/behaviors/mouse_scroll.dtsi +deleted file mode 100644 +index fb54886dcb..0000000000 +--- a/app/dts/behaviors/mouse_scroll.dtsi ++++ /dev/null +@@ -1,12 +0,0 @@ +-/ { +- 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 = <300>; +- acceleration-exponent = <0>; +- }; +- }; +-}; +diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml +deleted file mode 100644 +index 73ec34ec2d..0000000000 +--- a/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml ++++ /dev/null +@@ -1,13 +0,0 @@ +-description: Mouse move +- +-compatible: "zmk,behavior-mouse-move" +- +-include: one_param.yaml +- +-properties: +- delay-ms: +- type: int +- time-to-max-speed-ms: +- type: int +- acceleration-exponent: +- type: int +diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml +deleted file mode 100644 +index 5a932bc590..0000000000 +--- a/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml ++++ /dev/null +@@ -1,13 +0,0 @@ +-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 +diff --git a/app/include/dt-bindings/zmk/mouse.h b/app/include/dt-bindings/zmk/mouse.h +index cf0415c9b2..4bb3064dfc 100644 +--- a/app/include/dt-bindings/zmk/mouse.h ++++ b/app/include/dt-bindings/zmk/mouse.h +@@ -27,29 +27,3 @@ + #define MB7 (0x40) + + #define MB8 (0x80) +- +-/* Mouse move behavior */ +-#define MOVE_VERT(vert) ((vert)&0xFFFF) +-#define MOVE_VERT_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF) +-#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_UP MOVE_VERT(-600) +-#define MOVE_DOWN MOVE_VERT(600) +-#define MOVE_LEFT MOVE_HOR(-600) +-#define MOVE_RIGHT MOVE_HOR(600) +- +-/* 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 SCROLL(hor, vert) (SCROLL_HOR(hor) + SCROLL_VERT(vert)) +- +-#define SCROLL_UP SCROLL_VERT(10) +-#define SCROLL_DOWN SCROLL_VERT(-10) +-#define SCROLL_LEFT SCROLL_HOR(-10) +-#define SCROLL_RIGHT SCROLL_HOR(10) +diff --git a/app/include/zmk/events/mouse_button_state_changed.h b/app/include/zmk/events/mouse_button_state_changed.h +index 7ec4d2087c..6c3adae30d 100644 +--- a/app/include/zmk/events/mouse_button_state_changed.h ++++ b/app/include/zmk/events/mouse_button_state_changed.h +@@ -23,5 +23,5 @@ 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}); ++ .buttons = ZMK_HID_USAGE_ID(encoded), .state = pressed, .timestamp = timestamp}); + } +diff --git a/app/include/zmk/events/mouse_move_state_changed.h b/app/include/zmk/events/mouse_move_state_changed.h +deleted file mode 100644 +index 8866f81d4e..0000000000 +--- a/app/include/zmk/events/mouse_move_state_changed.h ++++ /dev/null +@@ -1,33 +0,0 @@ +- +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#pragma once +- +-#include +-#include +-#include +- +-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}); +-} +diff --git a/app/include/zmk/events/mouse_scroll_state_changed.h b/app/include/zmk/events/mouse_scroll_state_changed.h +deleted file mode 100644 +index fa60e8a742..0000000000 +--- a/app/include/zmk/events/mouse_scroll_state_changed.h ++++ /dev/null +@@ -1,34 +0,0 @@ +- +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#pragma once +- +-#include +-#include +-#include +-#include +- +-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}); +-} +diff --git a/app/include/zmk/events/mouse_tick.h b/app/include/zmk/events/mouse_tick.h +deleted file mode 100644 +index c75b9b4f86..0000000000 +--- a/app/include/zmk/events/mouse_tick.h ++++ /dev/null +@@ -1,39 +0,0 @@ +- +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#pragma once +- +-#include +-#include +-#include +-#include +- +-struct zmk_mouse_tick { +- struct vector2d max_move; +- struct vector2d max_scroll; +- struct mouse_config move_config; +- struct mouse_config scroll_config; +- int64_t *start_time; +- int64_t timestamp; +-}; +- +-ZMK_EVENT_DECLARE(zmk_mouse_tick); +- +-static inline struct zmk_mouse_tick_event *zmk_mouse_tick(struct vector2d max_move, +- struct vector2d max_scroll, +- struct mouse_config move_config, +- struct mouse_config scroll_config, +- int64_t *movement_start) { +- return new_zmk_mouse_tick((struct zmk_mouse_tick){ +- .max_move = max_move, +- .max_scroll = max_scroll, +- .move_config = move_config, +- .scroll_config = scroll_config, +- .start_time = movement_start, +- .timestamp = k_uptime_get(), +- }); +-} +diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h +index aa26cd3a54..f30db315d9 100644 +--- a/app/include/zmk/hid.h ++++ b/app/include/zmk/hid.h +@@ -14,6 +14,8 @@ + #include + #include + ++#define ZMK_HID_KEYBOARD_NKRO_MAX_USAGE HID_USAGE_KEY_KEYPAD_EQUAL ++ + #define COLLECTION_REPORT 0x03 + + static const uint8_t zmk_hid_report_desc[] = { +@@ -89,115 +91,22 @@ static const uint8_t zmk_hid_report_desc[] = { + HID_INPUT(0x00), + HID_END_COLLECTION, + +- /* USAGE_PAGE (Generic Desktop) */ +- HID_GI_USAGE_PAGE, +- HID_USAGE_GD, +- /* USAGE (Mouse) */ +- HID_LI_USAGE, +- HID_USAGE_GD_MOUSE, +- /* COLLECTION (Application) */ +- HID_MI_COLLECTION, +- COLLECTION_APPLICATION, +- /* REPORT ID (4) */ +- HID_GI_REPORT_ID, +- 0x04, +- /* USAGE (Pointer) */ +- HID_LI_USAGE, +- HID_USAGE_GD_POINTER, +- /* COLLECTION (Physical) */ +- HID_MI_COLLECTION, +- COLLECTION_PHYSICAL, +- /* USAGE_PAGE (Button) */ +- HID_GI_USAGE_PAGE, +- HID_USAGE_BUTTON, +- /* USAGE_MINIMUM (0x1) (button 1?) */ +- HID_LI_USAGE_MIN(1), +- 0x1, +- /* USAGE_MAXIMUM (0x10) (button 5? Buttons up to 8 still work) */ +- HID_LI_USAGE_MAX(1), +- 0x10, +- /* LOGICAL_MINIMUM (0) */ +- HID_GI_LOGICAL_MIN(1), +- 0x00, +- /* LOGICAL_MAXIMUM (1) */ +- HID_GI_LOGICAL_MAX(1), +- 0x01, +- /* REPORT_SIZE (1) */ +- HID_GI_REPORT_SIZE, +- 0x01, +- /* REPORT_COUNT (16) */ +- HID_GI_REPORT_COUNT, +- 0x10, +- /* INPUT (Data,Var,Abs) */ +- HID_MI_INPUT, +- 0x02, +- /* USAGE_PAGE (Generic Desktop) */ +- HID_GI_USAGE_PAGE, +- HID_USAGE_GD, +- /* LOGICAL_MINIMUM (-32767) */ +- HID_GI_LOGICAL_MIN(2), +- 0x01, +- 0x80, +- /* LOGICAL_MAXIMUM (32767) */ +- HID_GI_LOGICAL_MAX(2), +- 0xFF, +- 0x7F, +- /* REPORT_SIZE (16) */ +- HID_GI_REPORT_SIZE, +- 0x10, +- /* REPORT_COUNT (2) */ +- HID_GI_REPORT_COUNT, +- 0x02, +- /* USAGE (X) */ // Vertical scroll +- HID_LI_USAGE, +- HID_USAGE_GD_X, +- /* USAGE (Y) */ +- HID_LI_USAGE, +- HID_USAGE_GD_Y, +- /* Input (Data,Var,Rel) */ +- HID_MI_INPUT, +- 0x06, +- /* LOGICAL_MINIMUM (-127) */ +- HID_GI_LOGICAL_MIN(1), +- 0x81, +- /* LOGICAL_MAXIMUM (127) */ +- HID_GI_LOGICAL_MAX(1), +- 0x7F, +- /* REPORT_SIZE (8) */ +- HID_GI_REPORT_SIZE, +- 0x08, +- /* REPORT_COUNT (1) */ +- HID_GI_REPORT_COUNT, +- 0x01, +- /* USAGE (Wheel) */ +- HID_LI_USAGE, +- HID_USAGE_GD_WHEEL, +- /* Input (Data,Var,Rel) */ +- HID_MI_INPUT, +- 0x06, +- /* USAGE_PAGE (Consumer) */ // Horizontal scroll +- HID_GI_USAGE_PAGE, +- HID_USAGE_CONSUMER, +- /* USAGE (AC Pan) */ +- 0x0A, +- 0x38, +- 0x02, +- /* LOGICAL_MINIMUM (-127) */ +- HID_GI_LOGICAL_MIN(1), +- 0x81, +- /* LOGICAL_MAXIMUM (127) */ +- HID_GI_LOGICAL_MAX(1), +- 0x7F, +- /* REPORT_COUNT (1) */ +- HID_GI_REPORT_COUNT, +- 0x01, +- /* Input (Data,Var,Rel) */ +- HID_MI_INPUT, +- 0x06, +- /* END COLLECTION */ +- HID_MI_COLLECTION_END, +- /* END COLLECTION */ +- HID_MI_COLLECTION_END, ++ HID_USAGE_PAGE(HID_USAGE_GD), ++ HID_USAGE(HID_USAGE_GD_MOUSE), ++ HID_COLLECTION(HID_COLLECTION_APPLICATION), ++ HID_REPORT_ID(0x04), ++ HID_USAGE(HID_USAGE_GD_POINTER), ++ HID_COLLECTION(HID_COLLECTION_PHYSICAL), ++ HID_USAGE_PAGE(HID_USAGE_BUTTON), ++ HID_USAGE_MIN8(0x1), ++ HID_USAGE_MAX8(0x10), ++ HID_LOGICAL_MIN8(0x00), ++ HID_LOGICAL_MAX8(0x01), ++ HID_REPORT_SIZE(0x01), ++ HID_REPORT_COUNT(0x10), ++ HID_INPUT(0x02), ++ HID_END_COLLECTION, ++ HID_END_COLLECTION, + }; + + // struct zmk_hid_boot_report +@@ -237,10 +146,6 @@ struct zmk_hid_consumer_report { + + struct zmk_hid_mouse_report_body { + zmk_mouse_button_flags_t buttons; +- int16_t x; +- int16_t y; +- int8_t scroll_y; +- int8_t scroll_x; + } __packed; + + struct zmk_hid_mouse_report { +@@ -276,10 +181,6 @@ 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(); + + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); +diff --git a/app/include/zmk/mouse.h b/app/include/zmk/mouse.h +index f8f857441e..e749ac5d62 100644 +--- a/app/include/zmk/mouse.h ++++ b/app/include/zmk/mouse.h +@@ -11,20 +11,3 @@ + + typedef uint16_t zmk_mouse_button_flags_t; + typedef uint16_t zmk_mouse_button_t; +- +-struct mouse_config { +- int delay_ms; +- int time_to_max_speed_ms; +- // acceleration exponent 0: uniform speed +- // acceleration exponent 1: uniform acceleration +- // acceleration exponent 2: uniform jerk +- int acceleration_exponent; +-}; +- +-struct vector2d { +- float x; +- float y; +-}; +- +-struct k_work_q *zmk_mouse_work_q(); +-int zmk_mouse_init(); +\ No newline at end of file +diff --git a/app/src/behaviors/behavior_mouse_move.c b/app/src/behaviors/behavior_mouse_move.c +deleted file mode 100644 +index 5977a039d1..0000000000 +--- a/app/src/behaviors/behavior_mouse_move.c ++++ /dev/null +@@ -1,57 +0,0 @@ +-/* +- * Copyright (c) 2021 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#define DT_DRV_COMPAT zmk_behavior_mouse_move +- +-#include +-#include +-#include +- +-#include +-#include +-#include +-#include +- +-LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +- +-#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +- +-static int behavior_mouse_move_init(const struct device *dev) { return 0; }; +- +-static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, +- struct zmk_behavior_binding_event event) { +- LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); +- const struct device *dev = device_get_binding(binding->behavior_dev); +- const struct mouse_config *config = dev->config; +- return ZMK_EVENT_RAISE( +- zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, true, event.timestamp)); +-} +- +-static int on_keymap_binding_released(struct zmk_behavior_binding *binding, +- struct zmk_behavior_binding_event event) { +- LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); +- const struct device *dev = device_get_binding(binding->behavior_dev); +- const struct mouse_config *config = dev->config; +- return ZMK_EVENT_RAISE(zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, +- false, event.timestamp)); +-} +- +-static const struct behavior_driver_api behavior_mouse_move_driver_api = { +- .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; +- +-#define KP_INST(n) \ +- static struct mouse_config behavior_mouse_move_config_##n = { \ +- .delay_ms = DT_INST_PROP(n, delay_ms), \ +- .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ +- .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ +- }; \ +- DEVICE_DT_INST_DEFINE(n, behavior_mouse_move_init, device_pm_control_nop, NULL, \ +- &behavior_mouse_move_config_##n, APPLICATION, \ +- CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_move_driver_api); +- +-DT_INST_FOREACH_STATUS_OKAY(KP_INST) +- +-#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ +diff --git a/app/src/behaviors/behavior_mouse_scroll.c b/app/src/behaviors/behavior_mouse_scroll.c +deleted file mode 100644 +index 6416235265..0000000000 +--- a/app/src/behaviors/behavior_mouse_scroll.c ++++ /dev/null +@@ -1,58 +0,0 @@ +-/* +- * Copyright (c) 2021 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#define DT_DRV_COMPAT zmk_behavior_mouse_scroll +- +-#include +-#include +-#include +- +-#include +-#include +-#include +-#include +-#include +- +-LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +- +-#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +- +-static int behavior_mouse_scroll_init(const struct device *dev) { return 0; }; +- +-static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, +- struct zmk_behavior_binding_event event) { +- LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); +- const struct device *dev = device_get_binding(binding->behavior_dev); +- const struct mouse_config *config = dev->config; +- return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, +- true, event.timestamp)); +-} +- +-static int on_keymap_binding_released(struct zmk_behavior_binding *binding, +- struct zmk_behavior_binding_event event) { +- LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); +- const struct device *dev = device_get_binding(binding->behavior_dev); +- const struct mouse_config *config = dev->config; +- return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, +- false, event.timestamp)); +-} +- +-static const struct behavior_driver_api behavior_mouse_scroll_driver_api = { +- .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; +- +-#define KP_INST(n) \ +- static struct mouse_config behavior_mouse_scroll_config_##n = { \ +- .delay_ms = DT_INST_PROP(n, delay_ms), \ +- .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ +- .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ +- }; \ +- DEVICE_DT_INST_DEFINE(n, behavior_mouse_scroll_init, device_pm_control_nop, 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) */ +diff --git a/app/src/endpoints.c b/app/src/endpoints.c +index 0728fff3bd..6984faa99f 100644 +--- a/app/src/endpoints.c ++++ b/app/src/endpoints.c +@@ -160,11 +160,7 @@ int zmk_endpoints_send_mouse_report() { + + #if IS_ENABLED(CONFIG_ZMK_BLE) + case ZMK_ENDPOINT_BLE: { +-#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) +- int err = zmk_hog_send_mouse_report_direct(&mouse_report->body); +-#else + int err = zmk_hog_send_mouse_report(&mouse_report->body); +-#endif + if (err) { + LOG_ERR("FAILED TO SEND OVER HOG: %d", err); + } +diff --git a/app/src/events/mouse_move_state_changed.c b/app/src/events/mouse_move_state_changed.c +deleted file mode 100644 +index faf89cb8ab..0000000000 +--- a/app/src/events/mouse_move_state_changed.c ++++ /dev/null +@@ -1,10 +0,0 @@ +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#include +-#include +- +-ZMK_EVENT_IMPL(zmk_mouse_move_state_changed); +diff --git a/app/src/events/mouse_scroll_state_changed.c b/app/src/events/mouse_scroll_state_changed.c +deleted file mode 100644 +index 4b4170fe00..0000000000 +--- a/app/src/events/mouse_scroll_state_changed.c ++++ /dev/null +@@ -1,10 +0,0 @@ +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#include +-#include +- +-ZMK_EVENT_IMPL(zmk_mouse_scroll_state_changed); +diff --git a/app/src/events/mouse_tick.c b/app/src/events/mouse_tick.c +deleted file mode 100644 +index 0930b9fb90..0000000000 +--- a/app/src/events/mouse_tick.c ++++ /dev/null +@@ -1,10 +0,0 @@ +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#include +-#include +- +-ZMK_EVENT_IMPL(zmk_mouse_tick); +diff --git a/app/src/hid.c b/app/src/hid.c +index 9e7451b7f4..acd0a2b248 100644 +--- a/app/src/hid.c ++++ b/app/src/hid.c +@@ -17,7 +17,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_mouse_report mouse_report = { +- .report_id = 4, .body = {.buttons = 0, .x = 0, .y = 0, .scroll_x = 0, .scroll_y = 0}}; ++ .report_id = 4, .body = {.buttons = 0}}; + + // Keep track of how often a modifier was pressed. + // Only release the modifier if the count is 0. +@@ -300,32 +300,6 @@ int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { + } + return 0; + } +- +-void zmk_hid_mouse_movement_set(int16_t x, int16_t y) { +- mouse_report.body.x = x; +- mouse_report.body.y = y; +- LOG_DBG("Mouse movement set to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y); +-} +- +-void zmk_hid_mouse_movement_update(int16_t x, int16_t y) { +- mouse_report.body.x += x; +- mouse_report.body.y += y; +- LOG_DBG("Mouse movement updated to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y); +-} +- +-void zmk_hid_mouse_scroll_set(int8_t x, int8_t y) { +- mouse_report.body.scroll_x = x; +- mouse_report.body.scroll_y = y; +- LOG_DBG("Mouse scroll set to 0x%02X 0x%02X ", mouse_report.body.scroll_x, +- mouse_report.body.scroll_y); +-} +- +-void zmk_hid_mouse_scroll_update(int8_t x, int8_t y) { +- mouse_report.body.scroll_x += x; +- mouse_report.body.scroll_y += y; +- LOG_DBG("Mouse scroll updated to 0x%02X 0x%02X ", mouse_report.body.scroll_x, +- mouse_report.body.scroll_y); +-} + void zmk_hid_mouse_clear() { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } + + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { +diff --git a/app/src/hid_listener.c b/app/src/hid_listener.c +index 8cde3a4323..e233b0b8ed 100644 +--- a/app/src/hid_listener.c ++++ b/app/src/hid_listener.c +@@ -11,6 +11,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + + #include + #include ++#include + #include + #include + #include +@@ -70,14 +71,13 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed + } + + int hid_listener(const zmk_event_t *eh) { +- const struct zmk_keycode_state_changed *kc_ev = as_zmk_keycode_state_changed(eh); +- if (kc_ev) { +- if (kc_ev->state) { +- hid_listener_keycode_pressed(kc_ev); ++ const struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh); ++ if (ev) { ++ if (ev->state) { ++ hid_listener_keycode_pressed(ev); + } else { +- hid_listener_keycode_released(kc_ev); ++ hid_listener_keycode_released(ev); + } +- return 0; + } + return 0; + } +diff --git a/app/src/hog.c b/app/src/hog.c +index f915d27a91..1733ffa45c 100644 +--- a/app/src/hog.c ++++ b/app/src/hog.c +@@ -306,28 +306,7 @@ void send_mouse_report_callback(struct k_work *work) { + } + }; + +-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_NO_WAIT); +- if (err) { +- switch (err) { +- case -EAGAIN: { +- LOG_WRN("Mouse message queue full, dropping report"); +- return err; +- } +- 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; +-}; +- +-int zmk_hog_send_mouse_report_direct(struct zmk_hid_mouse_report_body *report) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return 1; +diff --git a/app/src/main.c b/app/src/main.c +index d3b3e578b3..ae604a7b9e 100644 +--- a/app/src/main.c ++++ b/app/src/main.c +@@ -17,10 +17,6 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL); + #include + #include + +-#ifdef CONFIG_ZMK_MOUSE +-#include +-#endif /* CONFIG_ZMK_MOUSE */ +- + #define ZMK_KSCAN_DEV DT_LABEL(ZMK_MATRIX_NODE_ID) + + void main(void) { +@@ -33,8 +29,4 @@ void main(void) { + #ifdef CONFIG_ZMK_DISPLAY + zmk_display_init(); + #endif /* CONFIG_ZMK_DISPLAY */ +- +-#ifdef CONFIG_ZMK_MOUSE +- zmk_mouse_init(); +-#endif /* CONFIG_ZMK_MOUSE */ + } +diff --git a/app/src/mouse.c b/app/src/mouse.c +new file mode 100644 +index 0000000000..a02d6dd0e9 +--- /dev/null ++++ b/app/src/mouse.c +@@ -0,0 +1,43 @@ ++/* ++ * 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/Kconfig b/app/src/mouse/Kconfig +deleted file mode 100644 +index 1161b86b42..0000000000 +--- a/app/src/mouse/Kconfig ++++ /dev/null +@@ -1,38 +0,0 @@ +-# Copyright (c) 2021 The ZMK Contributors +-# SPDX-License-Identifier: MIT +- +-menuconfig ZMK_MOUSE +- bool "Enable ZMK mouse emulation" +- default n +- +-config ZMK_MOUSE_TICK_DURATION +- int "Mouse tick duration in ms" +- default 8 +- +-if ZMK_MOUSE +- +-choice ZMK_MOUSE_WORK_QUEUE +- prompt "Work queue selection for mouse events" +- default ZMK_MOUSE_WORK_QUEUE_DEDICATED +- +-config ZMK_MOUSE_WORK_QUEUE_SYSTEM +- bool "Use default system work queue for mouse events" +- +-config ZMK_MOUSE_WORK_QUEUE_DEDICATED +- bool "Use dedicated work queue for mouse events" +- +-endchoice +- +-if ZMK_MOUSE_WORK_QUEUE_DEDICATED +- +-config ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE +- int "Stack size for dedicated mouse thread/queue" +- default 2048 +- +-config ZMK_MOUSE_DEDICATED_THREAD_PRIORITY +- int "Thread priority for dedicated mouse thread/queue" +- default 3 +- +-endif # ZMK_MOUSE_WORK_QUEUE_DEDICATED +- +-endif +diff --git a/app/src/mouse/key_listener.c b/app/src/mouse/key_listener.c +deleted file mode 100644 +index 713d032352..0000000000 +--- a/app/src/mouse/key_listener.c ++++ /dev/null +@@ -1,160 +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 +-#include +-#include +-#include +-#include +- +-static struct vector2d move_speed = {0}; +-static struct vector2d scroll_speed = {0}; +-static struct mouse_config move_config = (struct mouse_config){0}; +-static struct mouse_config scroll_config = (struct mouse_config){0}; +-static int64_t start_time = 0; +- +-bool equals(const struct mouse_config *one, const struct mouse_config *other) { +- return one->delay_ms == other->delay_ms && +- one->time_to_max_speed_ms == other->time_to_max_speed_ms && +- one->acceleration_exponent == other->acceleration_exponent; +-} +- +-static void clear_mouse_state(struct k_work *work) { +- move_speed = (struct vector2d){0}; +- scroll_speed = (struct vector2d){0}; +- start_time = 0; +- zmk_hid_mouse_movement_set(0, 0); +- zmk_hid_mouse_scroll_set(0, 0); +- LOG_DBG("Clearing state"); +-} +- +-K_WORK_DEFINE(mouse_clear, &clear_mouse_state); +- +-void mouse_clear_cb(struct k_timer *dummy) { +- k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_clear); +-} +- +-static void mouse_tick_timer_handler(struct k_work *work) { +- zmk_hid_mouse_movement_set(0, 0); +- zmk_hid_mouse_scroll_set(0, 0); +- LOG_DBG("Raising mouse tick event"); +- ZMK_EVENT_RAISE( +- zmk_mouse_tick(move_speed, scroll_speed, move_config, scroll_config, &start_time)); +- zmk_endpoints_send_mouse_report(); +-} +- +-K_WORK_DEFINE(mouse_tick, &mouse_tick_timer_handler); +- +-void mouse_timer_cb(struct k_timer *dummy) { +- LOG_DBG("Submitting mouse work to queue"); +- k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_tick); +-} +- +-K_TIMER_DEFINE(mouse_timer, mouse_timer_cb, mouse_clear_cb); +- +-static int mouse_timer_ref_count = 0; +- +-void mouse_timer_ref() { +- if (mouse_timer_ref_count == 0) { +- start_time = k_uptime_get(); +- k_timer_start(&mouse_timer, K_NO_WAIT, K_MSEC(CONFIG_ZMK_MOUSE_TICK_DURATION)); +- } +- mouse_timer_ref_count += 1; +-} +- +-void mouse_timer_unref() { +- if (mouse_timer_ref_count > 0) { +- mouse_timer_ref_count--; +- } +- if (mouse_timer_ref_count == 0) { +- k_timer_stop(&mouse_timer); +- } +-} +- +-static void listener_mouse_move_pressed(const struct zmk_mouse_move_state_changed *ev) { +- move_speed.x += ev->max_speed.x; +- move_speed.y += ev->max_speed.y; +- mouse_timer_ref(); +-} +- +-static void listener_mouse_move_released(const struct zmk_mouse_move_state_changed *ev) { +- move_speed.x -= ev->max_speed.x; +- move_speed.y -= ev->max_speed.y; +- mouse_timer_unref(); +-} +- +-static void listener_mouse_scroll_pressed(const struct zmk_mouse_scroll_state_changed *ev) { +- scroll_speed.x += ev->max_speed.x; +- scroll_speed.y += ev->max_speed.y; +- mouse_timer_ref(); +-} +- +-static void listener_mouse_scroll_released(const struct zmk_mouse_scroll_state_changed *ev) { +- scroll_speed.x -= ev->max_speed.x; +- scroll_speed.y -= ev->max_speed.y; +- mouse_timer_unref(); +-} +- +-static void listener_mouse_button_pressed(const struct zmk_mouse_button_state_changed *ev) { +- LOG_DBG("buttons: 0x%02X", ev->buttons); +- zmk_hid_mouse_buttons_press(ev->buttons); +- zmk_endpoints_send_mouse_report(); +-} +- +-static void listener_mouse_button_released(const struct zmk_mouse_button_state_changed *ev) { +- LOG_DBG("buttons: 0x%02X", ev->buttons); +- zmk_hid_mouse_buttons_release(ev->buttons); +- zmk_endpoints_send_mouse_report(); +-} +- +-int mouse_listener(const zmk_event_t *eh) { +- const struct zmk_mouse_move_state_changed *mmv_ev = as_zmk_mouse_move_state_changed(eh); +- if (mmv_ev) { +- if (!equals(&move_config, &(mmv_ev->config))) +- move_config = mmv_ev->config; +- +- if (mmv_ev->state) { +- listener_mouse_move_pressed(mmv_ev); +- } else { +- listener_mouse_move_released(mmv_ev); +- } +- return 0; +- } +- const struct zmk_mouse_scroll_state_changed *msc_ev = as_zmk_mouse_scroll_state_changed(eh); +- if (msc_ev) { +- if (!equals(&scroll_config, &(msc_ev->config))) +- scroll_config = msc_ev->config; +- if (msc_ev->state) { +- listener_mouse_scroll_pressed(msc_ev); +- } else { +- listener_mouse_scroll_released(msc_ev); +- } +- return 0; +- } +- const struct zmk_mouse_button_state_changed *mbt_ev = as_zmk_mouse_button_state_changed(eh); +- if (mbt_ev) { +- if (mbt_ev->state) { +- listener_mouse_button_pressed(mbt_ev); +- } else { +- listener_mouse_button_released(mbt_ev); +- } +- return 0; +- } +- return 0; +-} +- +-ZMK_LISTENER(mouse_listener, mouse_listener); +-ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_button_state_changed); +-ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_move_state_changed); +-ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_scroll_state_changed); +diff --git a/app/src/mouse/main.c b/app/src/mouse/main.c +deleted file mode 100644 +index 49208a76ef..0000000000 +--- a/app/src/mouse/main.c ++++ /dev/null +@@ -1,30 +0,0 @@ +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#include +-#include +- +-#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) +-K_THREAD_STACK_DEFINE(mouse_work_stack_area, CONFIG_ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE); +-static struct k_work_q mouse_work_q; +-#endif +- +-struct k_work_q *zmk_mouse_work_q() { +-#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) +- return &mouse_work_q; +-#else +- return &k_sys_work_q; +-#endif +-} +- +-int zmk_mouse_init() { +-#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) +- k_work_q_start(&mouse_work_q, mouse_work_stack_area, +- K_THREAD_STACK_SIZEOF(mouse_work_stack_area), +- CONFIG_ZMK_MOUSE_DEDICATED_THREAD_PRIORITY); +-#endif +- return 0; +-} +\ No newline at end of file +diff --git a/app/src/mouse/tick_listener.c b/app/src/mouse/tick_listener.c +deleted file mode 100644 +index 9c76bd5d2a..0000000000 +--- a/app/src/mouse/tick_listener.c ++++ /dev/null +@@ -1,102 +0,0 @@ +-/* +- * Copyright (c) 2020 The ZMK Contributors +- * +- * SPDX-License-Identifier: MIT +- */ +- +-#include +- +-LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +- +-#include +-#include +-#include +-#include +- +-#include // CLAMP +- +-#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 +- +-struct vector2d move_remainder = {0}; +-struct vector2d scroll_remainder = {0}; +- +-static int64_t ms_since_start(int64_t start, int64_t now, int64_t delay) { +- int64_t move_duration = now - (start + delay); +- // start can be in the future if there's a delay +- if (move_duration < 0) { +- move_duration = 0; +- } +- return move_duration; +-} +- +-static float speed(const struct mouse_config *config, float max_speed, int64_t duration_ms) { +- // Calculate the speed based on MouseKeysAccel +- // See https://en.wikipedia.org/wiki/Mouse_keys +- if (duration_ms > config->time_to_max_speed_ms || config->time_to_max_speed_ms == 0 || +- config->acceleration_exponent == 0) { +- return max_speed; +- } +- float time_fraction = (float)duration_ms / config->time_to_max_speed_ms; +- return max_speed * powf(time_fraction, config->acceleration_exponent); +-} +- +-static void track_remainder(float *move, float *remainder) { +- float new_move = *move + *remainder; +- *remainder = new_move - (int)new_move; +- *move = (int)new_move; +-} +- +-static struct vector2d update_movement(struct vector2d *remainder, +- const struct mouse_config *config, struct vector2d max_speed, +- int64_t now, int64_t *start_time) { +- struct vector2d move = {0}; +- if (max_speed.x == 0 && max_speed.y == 0) { +- *remainder = (struct vector2d){0}; +- return move; +- } +- +- int64_t move_duration = ms_since_start(*start_time, now, config->delay_ms); +- move = (struct vector2d){ +- .x = speed(config, max_speed.x, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, +- .y = speed(config, max_speed.y, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, +- }; +- +- track_remainder(&(move.x), &(remainder->x)); +- track_remainder(&(move.y), &(remainder->y)); +- +- return move; +-} +- +-static void mouse_tick_handler(const struct zmk_mouse_tick *tick) { +- struct vector2d move = update_movement(&move_remainder, &(tick->move_config), tick->max_move, +- tick->timestamp, tick->start_time); +- zmk_hid_mouse_movement_update((int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX), +- (int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX)); +- struct vector2d scroll = update_movement(&scroll_remainder, &(tick->scroll_config), +- tick->max_scroll, tick->timestamp, tick->start_time); +- zmk_hid_mouse_scroll_update((int8_t)CLAMP(scroll.x, INT8_MIN, INT8_MAX), +- (int8_t)CLAMP(scroll.y, INT8_MIN, INT8_MAX)); +-} +- +-int zmk_mouse_tick_listener(const zmk_event_t *eh) { +- const struct zmk_mouse_tick *tick = as_zmk_mouse_tick(eh); +- if (tick) { +- mouse_tick_handler(tick); +- return 0; +- } +- return 0; +-} +- +-ZMK_LISTENER(zmk_mouse_tick_listener, zmk_mouse_tick_listener); +-ZMK_SUBSCRIPTION(zmk_mouse_tick_listener, zmk_mouse_tick); +\ No newline at end of file +diff --git a/app/tests/mouse-keys/mkp/events.patterns b/app/tests/mouse-keys/mkp/events.patterns +new file mode 100644 +index 0000000000..2599345c2d +--- /dev/null ++++ b/app/tests/mouse-keys/mkp/events.patterns +@@ -0,0 +1 @@ ++s/.*zmk_hid_mouse_button_//p +diff --git a/app/tests/mouse-keys/mkp/keycode_events.snapshot b/app/tests/mouse-keys/mkp/keycode_events.snapshot +new file mode 100644 +index 0000000000..ab58cc9575 +--- /dev/null ++++ b/app/tests/mouse-keys/mkp/keycode_events.snapshot +@@ -0,0 +1,10 @@ ++press: Button 0 count 1 ++press: Mouse buttons set to 0x01 ++press: Button 1 count 1 ++press: Mouse buttons set to 0x03 ++release: Button 1 count: 0 ++release: Button 1 released ++release: Mouse buttons set to 0x01 ++release: Button 0 count: 0 ++release: Button 0 released ++release: Mouse buttons set to 0x00 +diff --git a/app/tests/mouse-keys/mmv/native_posix.keymap b/app/tests/mouse-keys/mkp/native_posix.keymap +similarity index 64% +rename from app/tests/mouse-keys/mmv/native_posix.keymap +rename to app/tests/mouse-keys/mkp/native_posix.keymap +index ecf06601c0..dd55b1640f 100644 +--- a/app/tests/mouse-keys/mmv/native_posix.keymap ++++ b/app/tests/mouse-keys/mkp/native_posix.keymap +@@ -10,8 +10,8 @@ + + default_layer { + bindings = < +- &mmv MOVE_LEFT &none +- &none &none ++ &mkp LCLK &none ++ &none &mkp RCLK + >; + }; + }; +@@ -20,7 +20,9 @@ + + &kscan { + events = < +- ZMK_MOCK_PRESS(0,0,100) +- ZMK_MOCK_RELEASE(0,0,10) ++ ZMK_MOCK_PRESS (0,0,100) ++ ZMK_MOCK_PRESS (1,1,100) ++ ZMK_MOCK_RELEASE(1,1, 10) ++ ZMK_MOCK_RELEASE(0,0, 10) + >; +-}; +\ No newline at end of file ++}; +diff --git a/app/tests/mouse-keys/mkp/native_posix_64.keymap b/app/tests/mouse-keys/mkp/native_posix_64.keymap +new file mode 100644 +index 0000000000..dd55b1640f +--- /dev/null ++++ b/app/tests/mouse-keys/mkp/native_posix_64.keymap +@@ -0,0 +1,28 @@ ++#include ++#include ++#include ++#include ++ ++/ { ++ keymap { ++ compatible = "zmk,keymap"; ++ label ="Default keymap"; ++ ++ default_layer { ++ bindings = < ++ &mkp LCLK &none ++ &none &mkp RCLK ++ >; ++ }; ++ }; ++}; ++ ++ ++&kscan { ++ events = < ++ ZMK_MOCK_PRESS (0,0,100) ++ ZMK_MOCK_PRESS (1,1,100) ++ ZMK_MOCK_RELEASE(1,1, 10) ++ ZMK_MOCK_RELEASE(0,0, 10) ++ >; ++}; +diff --git a/app/tests/mouse-keys/mmv/events.patterns b/app/tests/mouse-keys/mmv/events.patterns +deleted file mode 100644 +index 833100f6ac..0000000000 +--- a/app/tests/mouse-keys/mmv/events.patterns ++++ /dev/null +@@ -1 +0,0 @@ +-s/.*hid_listener_keycode_//p +\ No newline at end of file +diff --git a/app/tests/mouse-keys/mmv/keycode_events.snapshot b/app/tests/mouse-keys/mmv/keycode_events.snapshot +deleted file mode 100644 +index 259501ba3d..0000000000 +--- a/app/tests/mouse-keys/mmv/keycode_events.snapshot ++++ /dev/null +@@ -1,2 +0,0 @@ +-pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +-released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +diff --git a/docs/docs/behaviors/mouse-emulation.md b/docs/docs/behaviors/mouse-emulation.md +index efe095e7a0..299fad4b25 100644 +--- a/docs/docs/behaviors/mouse-emulation.md ++++ b/docs/docs/behaviors/mouse-emulation.md +@@ -17,16 +17,6 @@ This feature should be enabled via a config option: + CONFIG_ZMK_MOUSE=y + ``` + +-This option enables several others. +- +-### Dedicated thread processing +- +-`CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED` is enabled by default and separates the processing of mouse signals into a dedicated thread, significantly improving performance. +- +-### Tick rate configuration +- +-`CONFIG_ZMK_MOUSE_TICK_DURATION` sets the tick rate for mouse polling. It is set to 8 ms. by default. +- + ## Keycode Defines + + To make it easier to encode the HID keycode numeric values, most keymaps include +@@ -37,7 +27,7 @@ provided by ZMK near the top: + #include + ``` + +-Doing so allows using a set of defines such as `MOVE_UP`, `MOVE_DOWN`, `LCLK` and `SCROLL_UP` with these behaviors. ++Doing so allows using a set of defines such as `LCLK` and `RCLK` with these behaviors. + + ## Mouse Button Press + +@@ -53,58 +43,3 @@ Example: + ``` + &mkp LCLK + ``` +- +-## Mouse Movement +- +-This behavior is used to manipulate the cursor. +- +-### Behavior Binding +- +-- Reference: `&mmv` +-- Parameter: A `uint32` with the first 16 bits relating to horizontal movement +- and the last 16 - to vertical movement. +- +-Example: +- +-``` +-&mmv MOVE_UP +-``` +- +-## Mouse Scrolling +- +-This behaviour is used to scroll, both horizontally and vertically. +- +-### Behavior Binding +- +-- Reference: `&mwh` +-- Parameter: A `uint16` with the first 8 bits relating to horizontal movement +- and the last 8 - to vertical movement. +- +-Example: +- +-``` +-&mwh SCROLL_UP +-``` +- +-## Acceleration +- +-Both mouse movement and scrolling have independently configurable acceleration profiles with three parameters: delay before movement, time to max speed and the acceleration exponent. +-The exponent is usually set to 0 for constant speed, 1 for uniform acceleration or 2 for uniform jerk. +- +-These profiles can be configured inside your keymap: +- +-``` +-&mmv { +- time-to-max-speed-ms = <500>; +-}; +- +-&mwh { +- acceleration-exponent=<1>; +-}; +- +-/ { +- keymap { +- ... +- }; +-}; +-```