From 6b40bfda53571f7a960ccc448aa87f29da7496ac Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 9 Dec 2024 17:45:41 -0700 Subject: [PATCH] feat(mouse): Add mouse move and scroll support (#2477) * feat(mouse): Add mouse move and scroll support * Use Zephyr input subsystem for all pointers. * Input processors for modifying events, e.g. scaling, swapping codes, temporary (mouse) layers, etc. * Mouse move/scroll behaviors. * Infrastructure in place for physical pointer input devices. * feat: Add input split support. * docs: Add initial pointer docs. --------- Co-authored-by: Cem Aksoylar Co-authored-by: Alexander Krikun Co-authored-by: Robert U Co-authored-by: Shawn Meier Co-authored-by: Chris Andreae Co-authored-by: Anant Thazhemadam <47104651+thazhemadam@users.noreply.github.com> Co-authored-by: Erik Tollerud Co-authored-by: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> --- app/CMakeLists.txt | 5 +- app/Kconfig | 7 +- app/Kconfig.behaviors | 8 +- app/core-coverage.yml | 2 +- app/dts/behaviors.dtsi | 8 +- app/dts/behaviors/mouse_key_press.dtsi | 5 + app/dts/behaviors/mouse_keys.dtsi | 9 + app/dts/behaviors/mouse_move.dtsi | 25 ++ app/dts/behaviors/mouse_scroll.dtsi | 26 ++ .../zmk,behavior-input-two-axis.yaml | 28 ++ .../bindings/input_processors/ip_common.yaml | 6 + .../input_processors/ip_one_param.yaml | 13 + .../input_processors/ip_two_param.yaml | 14 + .../input_processors/ip_zero_param.yaml | 10 + .../zmk,input-processor-code-mapper.yaml | 16 + .../zmk,input-processor-scaler.yaml | 16 + .../zmk,input-processor-temp-layer.yaml | 21 + .../zmk,input-processor-transform.yaml | 18 + app/dts/bindings/zmk,input-listener.yaml | 26 ++ app/dts/bindings/zmk,input-split.yaml | 18 + app/dts/input/processors.dtsi | 10 + app/dts/input/processors/code_mapper.dtsi | 29 ++ app/dts/input/processors/scaler.dtsi | 33 ++ app/dts/input/processors/temp_layer.dtsi | 14 + app/dts/input/processors/transform.dtsi | 25 ++ app/include/drivers/input_processor.h | 67 ++++ app/include/dt-bindings/input.h | 12 + app/include/dt-bindings/zmk/input_transform.h | 11 + app/include/dt-bindings/zmk/mouse.h | 21 +- app/include/dt-bindings/zmk/pointing.h | 50 +++ app/include/zmk/endpoints.h | 4 +- .../zmk/events/mouse_button_state_changed.h | 2 +- app/include/zmk/hid.h | 116 +++++- app/include/zmk/hog.h | 4 +- app/include/zmk/input.h | 14 + app/include/zmk/{mouse.h => pointing.h} | 4 +- app/include/zmk/pointing/input_split.h | 10 + .../zmk/pointing/resolution_multipliers.h | 26 ++ app/include/zmk/split/bluetooth/service.h | 9 + app/include/zmk/split/bluetooth/uuid.h | 1 + app/include/zmk/usb_hid.h | 4 +- app/src/activity.c | 20 +- app/src/behaviors/behavior_input_two_axis.c | 304 ++++++++++++++ app/src/behaviors/behavior_mouse_key_press.c | 25 +- app/src/endpoints.c | 8 +- app/src/hid.c | 51 ++- app/src/hog.c | 78 +++- app/src/mouse.c | 43 -- app/src/pointing/CMakeLists.txt | 10 + app/src/pointing/Kconfig | 75 ++++ app/src/pointing/input_listener.c | 373 ++++++++++++++++++ .../pointing/input_processor_code_mapper.c | 58 +++ app/src/pointing/input_processor_scaler.c | 75 ++++ app/src/pointing/input_processor_temp_layer.c | 213 ++++++++++ app/src/pointing/input_processor_transform.c | 92 +++++ app/src/pointing/input_split.c | 69 ++++ app/src/pointing/resolution_multipliers.c | 50 +++ app/src/split/bluetooth/central.c | 311 ++++++++++++--- app/src/split/bluetooth/service.c | 70 +++- app/src/usb_hid.c | 134 +++++-- .../mkp/events.patterns | 0 .../mkp/keycode_events.snapshot | 0 .../mkp/native_posix.keymap | 0 app/tests/pointing/mkp/native_posix_64.conf | 6 + .../mkp/native_posix_64.keymap | 0 .../mouse-move/move_diagonal/events.patterns | 1 + .../move_diagonal/keycode_events.snapshot | 18 + .../move_diagonal/native_posix_64.conf | 6 + .../move_diagonal/native_posix_64.keymap | 29 ++ .../mouse-move/move_x/events.patterns | 1 + .../mouse-move/move_x/keycode_events.snapshot | 24 ++ .../mouse-move/move_x/native_posix_64.conf | 6 + .../mouse-move/move_x/native_posix_64.keymap | 29 ++ .../mouse-move/move_y/events.patterns | 1 + .../mouse-move/move_y/keycode_events.snapshot | 24 ++ .../mouse-move/move_y/native_posix_64.conf | 6 + .../mouse-move/move_y/native_posix_64.keymap | 29 ++ .../move_diagonal_scaling/events.patterns | 1 + .../keycode_events.snapshot | 18 + .../native_posix_64.conf | 6 + .../native_posix_64.keymap | 37 ++ .../move_diagonal_xy_invert/events.patterns | 1 + .../keycode_events.snapshot | 18 + .../native_posix_64.conf | 6 + .../native_posix_64.keymap | 34 ++ .../move_diagonal_xy_swap/events.patterns | 1 + .../keycode_events.snapshot | 18 + .../native_posix_64.conf | 6 + .../native_posix_64.keymap | 33 ++ .../events.patterns | 5 + .../keycode_events.snapshot | 14 + .../native_posix_64.conf | 6 + .../native_posix_64.keymap | 41 ++ .../events.patterns | 7 + .../keycode_events.snapshot | 21 + .../native_posix_64.conf | 6 + .../native_posix_64.keymap | 46 +++ .../events.patterns | 7 + .../keycode_events.snapshot | 28 ++ .../native_posix_64.conf | 6 + .../native_posix_64.keymap | 46 +++ .../3-require-prior-idle-ms/events.patterns | 6 + .../keycode_events.snapshot | 148 +++++++ .../native_posix_64.conf | 6 + .../native_posix_64.keymap | 50 +++ docs/docs/config/behaviors.md | 20 + docs/docs/config/pointing.md | 67 ++++ .../hardware-integration/pointing.mdx | 168 ++++++++ docs/docs/features/pointing.md | 39 ++ docs/docs/intro.md | 2 +- docs/docs/keymaps/behaviors/index.mdx | 2 + .../docs/keymaps/behaviors/mouse-emulation.md | 152 ++++++- .../keymaps/input-processors/code-mapper.md | 63 +++ docs/docs/keymaps/input-processors/index.md | 50 +++ docs/docs/keymaps/input-processors/scaler.md | 76 ++++ .../keymaps/input-processors/temp-layer.md | 58 +++ .../keymaps/input-processors/transformer.md | 75 ++++ docs/docs/keymaps/input-processors/usage.md | 59 +++ docs/sidebars.js | 19 + 119 files changed, 4226 insertions(+), 232 deletions(-) create mode 100644 app/dts/behaviors/mouse_keys.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-input-two-axis.yaml create mode 100644 app/dts/bindings/input_processors/ip_common.yaml create mode 100644 app/dts/bindings/input_processors/ip_one_param.yaml create mode 100644 app/dts/bindings/input_processors/ip_two_param.yaml create mode 100644 app/dts/bindings/input_processors/ip_zero_param.yaml create mode 100644 app/dts/bindings/input_processors/zmk,input-processor-code-mapper.yaml create mode 100644 app/dts/bindings/input_processors/zmk,input-processor-scaler.yaml create mode 100644 app/dts/bindings/input_processors/zmk,input-processor-temp-layer.yaml create mode 100644 app/dts/bindings/input_processors/zmk,input-processor-transform.yaml create mode 100644 app/dts/bindings/zmk,input-listener.yaml create mode 100644 app/dts/bindings/zmk,input-split.yaml create mode 100644 app/dts/input/processors.dtsi create mode 100644 app/dts/input/processors/code_mapper.dtsi create mode 100644 app/dts/input/processors/scaler.dtsi create mode 100644 app/dts/input/processors/temp_layer.dtsi create mode 100644 app/dts/input/processors/transform.dtsi create mode 100644 app/include/drivers/input_processor.h create mode 100644 app/include/dt-bindings/input.h create mode 100644 app/include/dt-bindings/zmk/input_transform.h create mode 100644 app/include/dt-bindings/zmk/pointing.h create mode 100644 app/include/zmk/input.h rename app/include/zmk/{mouse.h => pointing.h} (62%) create mode 100644 app/include/zmk/pointing/input_split.h create mode 100644 app/include/zmk/pointing/resolution_multipliers.h create mode 100644 app/src/behaviors/behavior_input_two_axis.c delete mode 100644 app/src/mouse.c create mode 100644 app/src/pointing/CMakeLists.txt create mode 100644 app/src/pointing/Kconfig create mode 100644 app/src/pointing/input_listener.c create mode 100644 app/src/pointing/input_processor_code_mapper.c create mode 100644 app/src/pointing/input_processor_scaler.c create mode 100644 app/src/pointing/input_processor_temp_layer.c create mode 100644 app/src/pointing/input_processor_transform.c create mode 100644 app/src/pointing/input_split.c create mode 100644 app/src/pointing/resolution_multipliers.c rename app/tests/{mouse-keys => pointing}/mkp/events.patterns (100%) rename app/tests/{mouse-keys => pointing}/mkp/keycode_events.snapshot (100%) rename app/tests/{mouse-keys => pointing}/mkp/native_posix.keymap (100%) create mode 100644 app/tests/pointing/mkp/native_posix_64.conf rename app/tests/{mouse-keys => pointing}/mkp/native_posix_64.keymap (100%) create mode 100644 app/tests/pointing/mouse-move/move_diagonal/events.patterns create mode 100644 app/tests/pointing/mouse-move/move_diagonal/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/move_diagonal/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/move_diagonal/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/move_x/events.patterns create mode 100644 app/tests/pointing/mouse-move/move_x/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/move_x/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/move_x/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/move_y/events.patterns create mode 100644 app/tests/pointing/mouse-move/move_y/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/move_y/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/move_y/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_scaling/events.patterns create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_scaling/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/events.patterns create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/events.patterns create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/events.patterns create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/events.patterns create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/events.patterns create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.keymap create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/events.patterns create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/keycode_events.snapshot create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.conf create mode 100644 app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.keymap create mode 100644 docs/docs/config/pointing.md create mode 100644 docs/docs/development/hardware-integration/pointing.mdx create mode 100644 docs/docs/features/pointing.md create mode 100644 docs/docs/keymaps/input-processors/code-mapper.md create mode 100644 docs/docs/keymaps/input-processors/index.md create mode 100644 docs/docs/keymaps/input-processors/scaler.md create mode 100644 docs/docs/keymaps/input-processors/temp-layer.md create mode 100644 docs/docs/keymaps/input-processors/transformer.md create mode 100644 docs/docs/keymaps/input-processors/usage.md diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 75925b7e0d3..072d5672912 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -17,6 +17,7 @@ if(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) endif() zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/behavior.h) +zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/input_processor.h) zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/ext_power.h) # Add your source file to the "app" target. This must come after @@ -37,15 +38,14 @@ target_sources_ifdef(CONFIG_ZMK_GPIO_KEY_WAKEUP_TRIGGER app PRIVATE src/gpio_key target_sources(app PRIVATE src/events/activity_state_changed.c) target_sources(app PRIVATE src/events/position_state_changed.c) target_sources(app PRIVATE src/events/sensor_event.c) -target_sources(app PRIVATE src/events/mouse_button_state_changed.c) target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c) target_sources(app PRIVATE src/behaviors/behavior_reset.c) target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SOFT_OFF app PRIVATE src/behaviors/behavior_soft_off.c) +add_subdirectory_ifdef(CONFIG_ZMK_POINTING src/pointing/) if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/hid.c) - target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse.c) target_sources(app PRIVATE src/behaviors/behavior_key_press.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_HOLD_TAP app PRIVATE src/behaviors/behavior_hold_tap.c) @@ -65,6 +65,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON app PRIVATE src/behaviors/behavior_sensor_rotate_common.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_MOUSE_KEY_PRESS app PRIVATE src/behaviors/behavior_mouse_key_press.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_STUDIO_UNLOCK app PRIVATE src/behaviors/behavior_studio_unlock.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_INPUT_TWO_AXIS app PRIVATE src/behaviors/behavior_input_two_axis.c) target_sources(app PRIVATE src/combo.c) target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c) target_sources(app PRIVATE src/behavior_queue.c) diff --git a/app/Kconfig b/app/Kconfig index 47ae73b25d1..45270d141a0 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -363,12 +363,7 @@ endif # ZMK_BACKLIGHT endmenu # Display/LED Options -menu "Mouse Options" - -config ZMK_MOUSE - bool "Enable ZMK mouse emulation" - -endmenu # Mouse Options +rsource "src/pointing/Kconfig" menu "Power Management" diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors index 69419a2f17e..5002bcac4b6 100644 --- a/app/Kconfig.behaviors +++ b/app/Kconfig.behaviors @@ -71,8 +71,7 @@ config ZMK_BEHAVIOR_KEY_TOGGLE config ZMK_BEHAVIOR_MOUSE_KEY_PRESS bool default y - depends on DT_HAS_ZMK_BEHAVIOR_MOUSE_KEY_PRESS_ENABLED - imply ZMK_MOUSE + depends on DT_HAS_ZMK_BEHAVIOR_MOUSE_KEY_PRESS_ENABLED && ZMK_POINTING config ZMK_BEHAVIOR_STICKY_KEY bool @@ -94,6 +93,11 @@ config ZMK_BEHAVIOR_SOFT_OFF default y depends on DT_HAS_ZMK_BEHAVIOR_SOFT_OFF_ENABLED && ZMK_PM_SOFT_OFF +config ZMK_BEHAVIOR_INPUT_TWO_AXIS + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_INPUT_TWO_AXIS_ENABLED && ZMK_POINTING + config ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON bool diff --git a/app/core-coverage.yml b/app/core-coverage.yml index 28a8788c9f2..bc36495a758 100644 --- a/app/core-coverage.yml +++ b/app/core-coverage.yml @@ -24,7 +24,7 @@ include: nickname: "display" - board: nice_nano_v2 shield: kyria_left - cmake-args: "-DCONFIG_ZMK_MOUSE=y" + cmake-args: "-DCONFIG_ZMK_POINTING=y" nickname: "mouse" - board: sparkfun_pro_micro_rp2040 shield: reviung41 diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index fcb4a63d450..653b085d5c5 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + #include #include #include @@ -19,6 +25,6 @@ #include #include #include -#include #include #include +#include diff --git a/app/dts/behaviors/mouse_key_press.dtsi b/app/dts/behaviors/mouse_key_press.dtsi index 4fbc2eb4cf7..fcb7666493a 100644 --- a/app/dts/behaviors/mouse_key_press.dtsi +++ b/app/dts/behaviors/mouse_key_press.dtsi @@ -16,4 +16,9 @@ #binding-cells = <1>; }; }; + + mkp_input_listener: mkp_input_listener { + compatible = "zmk,input-listener"; + device = <&mkp>; + }; }; diff --git a/app/dts/behaviors/mouse_keys.dtsi b/app/dts/behaviors/mouse_keys.dtsi new file mode 100644 index 00000000000..f9a99fede0c --- /dev/null +++ b/app/dts/behaviors/mouse_keys.dtsi @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "mouse_key_press.dtsi" +#include "mouse_move.dtsi" +#include "mouse_scroll.dtsi" \ No newline at end of file diff --git a/app/dts/behaviors/mouse_move.dtsi b/app/dts/behaviors/mouse_move.dtsi new file mode 100644 index 00000000000..09b93520c1f --- /dev/null +++ b/app/dts/behaviors/mouse_move.dtsi @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { + /omit-if-no-ref/ mmv: mouse_move { + compatible = "zmk,behavior-input-two-axis"; + #binding-cells = <1>; + x-input-code = ; + y-input-code = ; + time-to-max-speed-ms = <300>; + acceleration-exponent = <1>; + }; + }; + + mmv_input_listener: mmv_input_listener { + compatible = "zmk,input-listener"; + device = <&mmv>; + }; +}; diff --git a/app/dts/behaviors/mouse_scroll.dtsi b/app/dts/behaviors/mouse_scroll.dtsi new file mode 100644 index 00000000000..b482efded67 --- /dev/null +++ b/app/dts/behaviors/mouse_scroll.dtsi @@ -0,0 +1,26 @@ + +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { + /omit-if-no-ref/ msc: mouse_scroll { + compatible = "zmk,behavior-input-two-axis"; + #binding-cells = <1>; + x-input-code = ; + y-input-code = ; + time-to-max-speed-ms = <300>; + acceleration-exponent = <0>; + }; + }; + + msc_input_listener: msc_input_listener { + compatible = "zmk,input-listener"; + device = <&msc>; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml b/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml new file mode 100644 index 00000000000..80972212ce0 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml @@ -0,0 +1,28 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Two axis input behavior + +compatible: "zmk,behavior-input-two-axis" + +include: one_param.yaml + +properties: + x-input-code: + type: int + required: true + y-input-code: + type: int + required: true + trigger-period-ms: + type: int + default: 16 + description: The time (in ms) between generated inputs when an input has non-zero speed. + delay-ms: + type: int + time-to-max-speed-ms: + type: int + required: true + acceleration-exponent: + type: int + default: 1 diff --git a/app/dts/bindings/input_processors/ip_common.yaml b/app/dts/bindings/input_processors/ip_common.yaml new file mode 100644 index 00000000000..9add07acab9 --- /dev/null +++ b/app/dts/bindings/input_processors/ip_common.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +properties: + track-remainders: + type: boolean diff --git a/app/dts/bindings/input_processors/ip_one_param.yaml b/app/dts/bindings/input_processors/ip_one_param.yaml new file mode 100644 index 00000000000..6ef83b146e9 --- /dev/null +++ b/app/dts/bindings/input_processors/ip_one_param.yaml @@ -0,0 +1,13 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +include: ip_common.yaml + +properties: + "#input-processor-cells": + type: int + required: true + const: 1 + +input-processor-cells: + - param1 diff --git a/app/dts/bindings/input_processors/ip_two_param.yaml b/app/dts/bindings/input_processors/ip_two_param.yaml new file mode 100644 index 00000000000..f9538eb1040 --- /dev/null +++ b/app/dts/bindings/input_processors/ip_two_param.yaml @@ -0,0 +1,14 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +include: ip_common.yaml + +properties: + "#input-processor-cells": + type: int + required: true + const: 2 + +input-processor-cells: + - param1 + - param2 diff --git a/app/dts/bindings/input_processors/ip_zero_param.yaml b/app/dts/bindings/input_processors/ip_zero_param.yaml new file mode 100644 index 00000000000..c22f6ec8100 --- /dev/null +++ b/app/dts/bindings/input_processors/ip_zero_param.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +include: ip_common.yaml + +properties: + "#input-processor-cells": + type: int + required: true + const: 0 diff --git a/app/dts/bindings/input_processors/zmk,input-processor-code-mapper.yaml b/app/dts/bindings/input_processors/zmk,input-processor-code-mapper.yaml new file mode 100644 index 00000000000..6cad85a8460 --- /dev/null +++ b/app/dts/bindings/input_processors/zmk,input-processor-code-mapper.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2024, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Input Processor for remapping certain input codes to other codes + +compatible: "zmk,input-processor-code-mapper" + +include: ip_zero_param.yaml + +properties: + type: + type: int + required: true + map: + type: array + required: true diff --git a/app/dts/bindings/input_processors/zmk,input-processor-scaler.yaml b/app/dts/bindings/input_processors/zmk,input-processor-scaler.yaml new file mode 100644 index 00000000000..da7ca13f6bc --- /dev/null +++ b/app/dts/bindings/input_processors/zmk,input-processor-scaler.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2024, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Input Processor for scaling values + +compatible: "zmk,input-processor-scaler" + +include: ip_two_param.yaml + +properties: + type: + type: int + required: true + codes: + type: array + required: true diff --git a/app/dts/bindings/input_processors/zmk,input-processor-temp-layer.yaml b/app/dts/bindings/input_processors/zmk,input-processor-temp-layer.yaml new file mode 100644 index 00000000000..e69cd58d9a3 --- /dev/null +++ b/app/dts/bindings/input_processors/zmk,input-processor-temp-layer.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2024, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Input Processor for temporarily enabling a layer after input events + +compatible: "zmk,input-processor-temp-layer" + +include: ip_two_param.yaml + +properties: + require-prior-idle-ms: + type: int + required: false + default: 0 + description: Time in milliseconds that must pass after the last keystroke before the layer can be toggled + + excluded-positions: + type: array + required: false + default: [] + description: Array of key positions that will NOT trigger layer deactivation when pressed diff --git a/app/dts/bindings/input_processors/zmk,input-processor-transform.yaml b/app/dts/bindings/input_processors/zmk,input-processor-transform.yaml new file mode 100644 index 00000000000..24fbd28815b --- /dev/null +++ b/app/dts/bindings/input_processors/zmk,input-processor-transform.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2024, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Input Processor for transforming values in various ways + +compatible: "zmk,input-processor-transform" + +include: ip_one_param.yaml + +properties: + type: + type: int + x-codes: + type: array + required: true + y-codes: + type: array + required: true diff --git a/app/dts/bindings/zmk,input-listener.yaml b/app/dts/bindings/zmk,input-listener.yaml new file mode 100644 index 00000000000..5516794c61b --- /dev/null +++ b/app/dts/bindings/zmk,input-listener.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: | + Listener to subscribe to input events and send HID updates after processing + +compatible: "zmk,input-listener" + +properties: + device: + type: phandle + required: true + input-processors: + type: phandle-array + +child-binding: + description: "Listener overrides for certain layers" + + properties: + layers: + type: array + required: true + process-next: + type: boolean + input-processors: + type: phandle-array diff --git a/app/dts/bindings/zmk,input-split.yaml b/app/dts/bindings/zmk,input-split.yaml new file mode 100644 index 00000000000..76d15a36753 --- /dev/null +++ b/app/dts/bindings/zmk,input-split.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +include: [base.yaml] + +compatible: "zmk,input-split" + +description: Device to wire up an input device for split use. + +properties: + reg: + required: true + + device: + type: phandle + + input-processors: + type: phandle-array diff --git a/app/dts/input/processors.dtsi b/app/dts/input/processors.dtsi new file mode 100644 index 00000000000..d072c0fcfcf --- /dev/null +++ b/app/dts/input/processors.dtsi @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include \ No newline at end of file diff --git a/app/dts/input/processors/code_mapper.dtsi b/app/dts/input/processors/code_mapper.dtsi new file mode 100644 index 00000000000..6a9b5d164ed --- /dev/null +++ b/app/dts/input/processors/code_mapper.dtsi @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + /omit-if-no-ref/ zip_xy_swap_mapper: zip_xy_swap_mapper { + compatible = "zmk,input-processor-code-mapper"; + #input-processor-cells = <0>; + type = ; + map + = + , + ; + }; + + /omit-if-no-ref/ zip_xy_to_scroll_mapper: zip_xy_to_scroll_mapper { + compatible = "zmk,input-processor-code-mapper"; + #input-processor-cells = <0>; + type = ; + map + = + , + ; + }; +}; \ No newline at end of file diff --git a/app/dts/input/processors/scaler.dtsi b/app/dts/input/processors/scaler.dtsi new file mode 100644 index 00000000000..4cd329ac7cc --- /dev/null +++ b/app/dts/input/processors/scaler.dtsi @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + + #include + +/ { + /omit-if-no-ref/ zip_x_scaler: zip_x_scaler { + compatible = "zmk,input-processor-scaler"; + #input-processor-cells = <2>; + type = ; + codes = ; + track-remainders; + }; + + /omit-if-no-ref/ zip_y_scaler: zip_y_scaler { + compatible = "zmk,input-processor-scaler"; + #input-processor-cells = <2>; + type = ; + codes = ; + track-remainders; + }; + + /omit-if-no-ref/ zip_xy_scaler: zip_xy_scaler { + compatible = "zmk,input-processor-scaler"; + #input-processor-cells = <2>; + type = ; + codes = ; + track-remainders; + }; +}; \ No newline at end of file diff --git a/app/dts/input/processors/temp_layer.dtsi b/app/dts/input/processors/temp_layer.dtsi new file mode 100644 index 00000000000..228ad4beffb --- /dev/null +++ b/app/dts/input/processors/temp_layer.dtsi @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + /omit-if-no-ref/ zip_temp_layer: zip_temp_layer { + compatible = "zmk,input-processor-temp-layer"; + #input-processor-cells = <2>; + }; +}; \ No newline at end of file diff --git a/app/dts/input/processors/transform.dtsi b/app/dts/input/processors/transform.dtsi new file mode 100644 index 00000000000..541e47d3304 --- /dev/null +++ b/app/dts/input/processors/transform.dtsi @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + /omit-if-no-ref/ zip_xy_transform: zip_xy_transform { + compatible = "zmk,input-processor-transform"; + #input-processor-cells = <1>; + type = ; + y-codes = ; + x-codes = ; + }; + + /omit-if-no-ref/ zip_scroll_transform: zip_scroll_transform { + compatible = "zmk,input-processor-transform"; + #input-processor-cells = <1>; + type = ; + y-codes = ; + x-codes = ; + }; +}; \ No newline at end of file diff --git a/app/include/drivers/input_processor.h b/app/include/drivers/input_processor.h new file mode 100644 index 00000000000..aea57476ae4 --- /dev/null +++ b/app/include/drivers/input_processor.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +struct zmk_input_processor_entry { + const struct device *dev; + uint32_t param1; + uint32_t param2; + bool track_remainders; +}; + +#define ZMK_INPUT_PROCESSOR_ENTRY_AT_IDX(idx, n) \ + { \ + .dev = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(n, input_processors, idx)), \ + .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(n, input_processors, idx, param1), (0), \ + (DT_PHA_BY_IDX(n, input_processors, idx, param1))), \ + .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(n, input_processors, idx, param2), (0), \ + (DT_PHA_BY_IDX(n, input_processors, idx, param2))), \ + .track_remainders = \ + COND_CODE_1(DT_PROP(DT_PHANDLE_BY_IDX(n, input_processors, idx), track_remainders), \ + (true), (false)), \ + } + +struct zmk_input_processor_state { + int16_t *remainder; +}; + +// TODO: Need the ability to store remainders? Some data passed in? +typedef int (*zmk_input_processor_handle_event_callback_t)(const struct device *dev, + struct input_event *event, + uint32_t param1, uint32_t param2, + struct zmk_input_processor_state *state); + +__subsystem struct zmk_input_processor_driver_api { + zmk_input_processor_handle_event_callback_t handle_event; +}; + +__syscall int zmk_input_processor_handle_event(const struct device *dev, struct input_event *event, + uint32_t param1, uint32_t param2, + struct zmk_input_processor_state *state); + +static inline int z_impl_zmk_input_processor_handle_event(const struct device *dev, + struct input_event *event, + uint32_t param1, uint32_t param2, + struct zmk_input_processor_state *state) { + const struct zmk_input_processor_driver_api *api = + (const struct zmk_input_processor_driver_api *)dev->api; + + if (api->handle_event == NULL) { + return -ENOTSUP; + } + + return api->handle_event(dev, event, param1, param2, state); +} + +#include diff --git a/app/include/dt-bindings/input.h b/app/include/dt-bindings/input.h new file mode 100644 index 00000000000..2437b50461f --- /dev/null +++ b/app/include/dt-bindings/input.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#define ZMK_INPUT_EXPLICIT_CODE(type, code) ((type << 16) & code) + +#define ZMK_INPUT_EXPLICIT_CODE_TYPE(val) ((val >> 16) & 0xFF) +#define ZMK_INPUT_EXPLICIT_CODE_CODE(val) (val & 0xFFFF) \ No newline at end of file diff --git a/app/include/dt-bindings/zmk/input_transform.h b/app/include/dt-bindings/zmk/input_transform.h new file mode 100644 index 00000000000..f75c6dabefd --- /dev/null +++ b/app/include/dt-bindings/zmk/input_transform.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +#define INPUT_TRANSFORM_XY_SWAP BIT(0) +#define INPUT_TRANSFORM_X_INVERT BIT(1) +#define INPUT_TRANSFORM_Y_INVERT BIT(2) \ No newline at end of file diff --git a/app/include/dt-bindings/zmk/mouse.h b/app/include/dt-bindings/zmk/mouse.h index 582518aff7e..ce41e7f7458 100644 --- a/app/include/dt-bindings/zmk/mouse.h +++ b/app/include/dt-bindings/zmk/mouse.h @@ -1,24 +1,9 @@ + /* - * Copyright (c) 2020 The ZMK Contributors + * Copyright (c) 2024 The ZMK Contributors * * SPDX-License-Identifier: MIT */ #pragma once -#include - -/* Mouse press behavior */ -/* Left click */ -#define MB1 BIT(0) -#define LCLK (MB1) - -/* Right click */ -#define MB2 BIT(1) -#define RCLK (MB2) - -/* Middle click */ -#define MB3 BIT(2) -#define MCLK (MB3) - -#define MB4 BIT(3) -#define MB5 BIT(4) +#include "pointing.h" \ No newline at end of file diff --git a/app/include/dt-bindings/zmk/pointing.h b/app/include/dt-bindings/zmk/pointing.h new file mode 100644 index 00000000000..48246504399 --- /dev/null +++ b/app/include/dt-bindings/zmk/pointing.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +#include + +/* Mouse press behavior */ +/* Left click */ +#define MB1 BIT(0) +#define LCLK (MB1) + +/* Right click */ +#define MB2 BIT(1) +#define RCLK (MB2) + +/* Middle click */ +#define MB3 BIT(2) +#define MCLK (MB3) + +#define MB4 BIT(3) +#define MB5 BIT(4) + +#ifndef ZMK_POINTING_DEFAULT_MOVE_VAL +#define ZMK_POINTING_DEFAULT_MOVE_VAL 600 +#endif + +#ifndef ZMK_POINTING_DEFAULT_SCRL_VAL +#define ZMK_POINTING_DEFAULT_SCRL_VAL 10 +#endif + +/* Mouse move behavior */ +#define MOVE_Y(vert) ((vert) & 0xFFFF) +#define MOVE_Y_DECODE(encoded) (int16_t)((encoded) & 0x0000FFFF) +#define MOVE_X(hor) (((hor) & 0xFFFF) << 16) +#define MOVE_X_DECODE(encoded) (int16_t)(((encoded) & 0xFFFF0000) >> 16) + +#define MOVE(hor, vert) (MOVE_X(hor) + MOVE_Y(vert)) + +#define MOVE_UP MOVE_Y(-ZMK_POINTING_DEFAULT_MOVE_VAL) +#define MOVE_DOWN MOVE_Y(ZMK_POINTING_DEFAULT_MOVE_VAL) +#define MOVE_LEFT MOVE_X(-ZMK_POINTING_DEFAULT_MOVE_VAL) +#define MOVE_RIGHT MOVE_X(ZMK_POINTING_DEFAULT_MOVE_VAL) + +#define SCRL_UP MOVE_Y(ZMK_POINTING_DEFAULT_SCRL_VAL) +#define SCRL_DOWN MOVE_Y(-ZMK_POINTING_DEFAULT_SCRL_VAL) +#define SCRL_LEFT MOVE_X(-ZMK_POINTING_DEFAULT_SCRL_VAL) +#define SCRL_RIGHT MOVE_X(ZMK_POINTING_DEFAULT_SCRL_VAL) diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index f2aff2bcc2d..a2ef3181a58 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -70,8 +70,8 @@ struct zmk_endpoint_instance zmk_endpoints_selected(void); int zmk_endpoints_send_report(uint16_t usage_page); -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) int zmk_endpoints_send_mouse_report(); -#endif // IS_ENABLE(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) void zmk_endpoints_clear_current(void); diff --git a/app/include/zmk/events/mouse_button_state_changed.h b/app/include/zmk/events/mouse_button_state_changed.h index ff3ccecd714..336b6ca9f9f 100644 --- a/app/include/zmk/events/mouse_button_state_changed.h +++ b/app/include/zmk/events/mouse_button_state_changed.h @@ -9,7 +9,7 @@ #include #include -#include +#include struct zmk_mouse_button_state_changed { zmk_mouse_button_t buttons; diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 766fb9c4632..4c963be6399 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -12,9 +12,9 @@ #include #include -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -#include -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) +#include +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) #include #include @@ -77,6 +77,30 @@ #define ZMK_HID_REPORT_ID_CONSUMER 0x02 #define ZMK_HID_REPORT_ID_MOUSE 0x03 +#ifndef HID_ITEM_TAG_PUSH +#define HID_ITEM_TAG_PUSH 0xA +#endif + +#ifndef HID_ITEM_TAG_POP +#define HID_ITEM_TAG_POP 0xB +#endif + +#define HID_PUSH HID_ITEM(HID_ITEM_TAG_PUSH, HID_ITEM_TYPE_GLOBAL, 0) + +#define HID_POP HID_ITEM(HID_ITEM_TAG_POP, HID_ITEM_TYPE_GLOBAL, 0) + +#ifndef HID_PHYSICAL_MIN8 +#define HID_PHYSICAL_MIN8(a) HID_ITEM(HID_ITEM_TAG_PHYSICAL_MIN, HID_ITEM_TYPE_GLOBAL, 1), a +#endif + +#ifndef HID_PHYSICAL_MAX8 +#define HID_PHYSICAL_MAX8(a) HID_ITEM(HID_ITEM_TAG_PHYSICAL_MAX, HID_ITEM_TYPE_GLOBAL, 1), a +#endif + +#define HID_USAGE16(a, b) HID_ITEM(HID_ITEM_TAG_USAGE, HID_ITEM_TYPE_LOCAL, 2), a, b + +#define HID_USAGE16_SINGLE(a) HID_USAGE16((a & 0xFF), ((a >> 8) & 0xFF)) + static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), HID_USAGE(HID_USAGE_GD_KEYBOARD), @@ -161,7 +185,7 @@ static const uint8_t zmk_hid_report_desc[] = { HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_ARRAY | ZMK_HID_MAIN_VAL_ABS), HID_END_COLLECTION, -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) HID_USAGE_PAGE(HID_USAGE_GD), HID_USAGE(HID_USAGE_GD_MOUSE), HID_COLLECTION(HID_COLLECTION_APPLICATION), @@ -184,15 +208,51 @@ static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), HID_USAGE(HID_USAGE_GD_X), HID_USAGE(HID_USAGE_GD_Y), + HID_LOGICAL_MIN16(0xFF, -0x7F), + HID_LOGICAL_MAX16(0xFF, 0x7F), + HID_REPORT_SIZE(0x10), + HID_REPORT_COUNT(0x02), + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_REL), + HID_COLLECTION(HID_COLLECTION_LOGICAL), +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + HID_USAGE(HID_USAGE_GD_RESOLUTION_MULTIPLIER), + HID_LOGICAL_MIN8(0x00), + HID_LOGICAL_MAX8(0x0F), + HID_PHYSICAL_MIN8(0x01), + HID_PHYSICAL_MAX8(0x10), + HID_REPORT_SIZE(0x04), + HID_REPORT_COUNT(0x01), + HID_PUSH, + HID_FEATURE(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) HID_USAGE(HID_USAGE_GD_WHEEL), - HID_LOGICAL_MIN8(-0x7F), - HID_LOGICAL_MAX8(0x7F), - HID_REPORT_SIZE(0x08), - HID_REPORT_COUNT(0x03), + HID_LOGICAL_MIN16(0xFF, -0x7F), + HID_LOGICAL_MAX16(0xFF, 0x7F), + HID_PHYSICAL_MIN8(0x00), + HID_PHYSICAL_MAX8(0x00), + HID_REPORT_SIZE(0x10), + HID_REPORT_COUNT(0x01), + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_REL), + HID_END_COLLECTION, + HID_COLLECTION(HID_COLLECTION_LOGICAL), +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + HID_USAGE(HID_USAGE_GD_RESOLUTION_MULTIPLIER), + HID_POP, + HID_FEATURE(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + HID_USAGE_PAGE(HID_USAGE_CONSUMER), + HID_USAGE16_SINGLE(HID_USAGE_CONSUMER_AC_PAN), + HID_LOGICAL_MIN16(0xFF, -0x7F), + HID_LOGICAL_MAX16(0xFF, 0x7F), + HID_PHYSICAL_MIN8(0x00), + HID_PHYSICAL_MAX8(0x00), + HID_REPORT_SIZE(0x10), + HID_REPORT_COUNT(0x01), HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_REL), HID_END_COLLECTION, HID_END_COLLECTION, -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + HID_END_COLLECTION, +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) }; #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) @@ -255,12 +315,13 @@ struct zmk_hid_consumer_report { struct zmk_hid_consumer_report_body body; } __packed; -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) struct zmk_hid_mouse_report_body { zmk_mouse_button_flags_t buttons; - int8_t d_x; - int8_t d_y; - int8_t d_wheel; + int16_t d_x; + int16_t d_y; + int16_t d_scroll_y; + int16_t d_scroll_x; } __packed; struct zmk_hid_mouse_report { @@ -268,7 +329,21 @@ struct zmk_hid_mouse_report { struct zmk_hid_mouse_report_body body; } __packed; -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +struct zmk_hid_mouse_resolution_feature_report_body { + uint8_t wheel_res : 4; + uint8_t hwheel_res : 4; +} __packed; + +struct zmk_hid_mouse_resolution_feature_report { + uint8_t report_id; + struct zmk_hid_mouse_resolution_feature_report_body body; +} __packed; + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) zmk_mod_flags_t zmk_hid_get_explicit_mods(void); int zmk_hid_register_mod(zmk_mod_t modifier); @@ -296,13 +371,18 @@ int zmk_hid_press(uint32_t usage); int zmk_hid_release(uint32_t usage); bool zmk_hid_is_pressed(uint32_t usage); -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) int zmk_hid_mouse_button_press(zmk_mouse_button_t button); int zmk_hid_mouse_button_release(zmk_mouse_button_t button); int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons); int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons); +void zmk_hid_mouse_movement_set(int16_t x, int16_t y); +void zmk_hid_mouse_scroll_set(int8_t x, int8_t y); +void zmk_hid_mouse_movement_update(int16_t x, int16_t y); +void zmk_hid_mouse_scroll_update(int8_t x, int8_t y); void zmk_hid_mouse_clear(void); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void); @@ -311,6 +391,6 @@ struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void); zmk_hid_boot_report_t *zmk_hid_get_boot_report(); #endif -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index eb6e653f772..0b79decbf97 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -12,6 +12,6 @@ int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *body); int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) diff --git a/app/include/zmk/input.h b/app/include/zmk/input.h new file mode 100644 index 00000000000..b1cbae6f99d --- /dev/null +++ b/app/include/zmk/input.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +struct zmk_input_explicit_code { + uint8_t type; + uint16_t code; +}; diff --git a/app/include/zmk/mouse.h b/app/include/zmk/pointing.h similarity index 62% rename from app/include/zmk/mouse.h rename to app/include/zmk/pointing.h index d873f15689a..1bd53d63b99 100644 --- a/app/include/zmk/mouse.h +++ b/app/include/zmk/pointing.h @@ -1,12 +1,12 @@ /* - * Copyright (c) 2021 The ZMK Contributors + * Copyright (c) 2023 The ZMK Contributors * * SPDX-License-Identifier: MIT */ #pragma once -#include +#include typedef uint8_t zmk_mouse_button_flags_t; typedef uint16_t zmk_mouse_button_t; diff --git a/app/include/zmk/pointing/input_split.h b/app/include/zmk/pointing/input_split.h new file mode 100644 index 00000000000..812035477e2 --- /dev/null +++ b/app/include/zmk/pointing/input_split.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +int zmk_input_split_report_peripheral_event(uint8_t reg, uint8_t type, uint16_t code, int32_t value, + bool sync); \ No newline at end of file diff --git a/app/include/zmk/pointing/resolution_multipliers.h b/app/include/zmk/pointing/resolution_multipliers.h new file mode 100644 index 00000000000..1e50b7814cf --- /dev/null +++ b/app/include/zmk/pointing/resolution_multipliers.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct zmk_pointing_resolution_multipliers { + uint8_t wheel; + uint8_t hor_wheel; +}; + +struct zmk_pointing_resolution_multipliers +zmk_pointing_resolution_multipliers_get_current_profile(void); +struct zmk_pointing_resolution_multipliers +zmk_pointing_resolution_multipliers_get_profile(struct zmk_endpoint_instance endpoint); +void zmk_pointing_resolution_multipliers_set_profile( + struct zmk_pointing_resolution_multipliers multipliers, struct zmk_endpoint_instance endpoint); + +void zmk_pointing_resolution_multipliers_process_report( + struct zmk_hid_mouse_resolution_feature_report_body *report, + struct zmk_endpoint_instance endpoint); diff --git a/app/include/zmk/split/bluetooth/service.h b/app/include/zmk/split/bluetooth/service.h index 1c9e75226ad..90e5dd7ee8e 100644 --- a/app/include/zmk/split/bluetooth/service.h +++ b/app/include/zmk/split/bluetooth/service.h @@ -31,8 +31,17 @@ struct zmk_split_run_behavior_payload { char behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN]; } __packed; +struct zmk_split_input_event_payload { + uint8_t type; + uint16_t code; + uint32_t value; + uint8_t sync; +} __packed; + int zmk_split_bt_position_pressed(uint8_t position); int zmk_split_bt_position_released(uint8_t position); int zmk_split_bt_sensor_triggered(uint8_t sensor_index, const struct zmk_sensor_channel_data channel_data[], size_t channel_data_size); + +int zmk_split_bt_report_input(uint8_t reg, uint8_t type, uint16_t code, int32_t value, bool sync); diff --git a/app/include/zmk/split/bluetooth/uuid.h b/app/include/zmk/split/bluetooth/uuid.h index 4a653c73b83..c9a63efa702 100644 --- a/app/include/zmk/split/bluetooth/uuid.h +++ b/app/include/zmk/split/bluetooth/uuid.h @@ -19,3 +19,4 @@ #define ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000003) #define ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID ZMK_BT_SPLIT_UUID(0x00000004) #define ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID ZMK_BT_SPLIT_UUID(0x00000005) +#define ZMK_SPLIT_BT_INPUT_EVENT_UUID ZMK_BT_SPLIT_UUID(0x00000006) diff --git a/app/include/zmk/usb_hid.h b/app/include/zmk/usb_hid.h index c0cbc08a7ad..785f6af8404 100644 --- a/app/include/zmk/usb_hid.h +++ b/app/include/zmk/usb_hid.h @@ -10,7 +10,7 @@ int zmk_usb_hid_send_keyboard_report(void); int zmk_usb_hid_send_consumer_report(void); -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) int zmk_usb_hid_send_mouse_report(void); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) void zmk_usb_hid_set_protocol(uint8_t protocol); diff --git a/app/src/activity.c b/app/src/activity.c index 454e91e5da0..b109d46d841 100644 --- a/app/src/activity.c +++ b/app/src/activity.c @@ -26,6 +26,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #endif +#if IS_ENABLED(CONFIG_ZMK_POINTING) +#include +#endif + bool is_usb_power_present(void) { #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) return zmk_usb_is_powered(); @@ -59,12 +63,14 @@ int set_state(enum zmk_activity_state state) { enum zmk_activity_state zmk_activity_get_state(void) { return activity_state; } -int activity_event_listener(const zmk_event_t *eh) { +static int note_activity(void) { activity_last_uptime = k_uptime_get(); return set_state(ZMK_ACTIVITY_ACTIVE); } +static int activity_event_listener(const zmk_event_t *eh) { return note_activity(); } + void activity_work_handler(struct k_work *work) { int32_t current = k_uptime_get(); int32_t inactive_time = current - activity_last_uptime; @@ -104,4 +110,16 @@ ZMK_LISTENER(activity, activity_event_listener); ZMK_SUBSCRIPTION(activity, zmk_position_state_changed); ZMK_SUBSCRIPTION(activity, zmk_sensor_event); +#if IS_ENABLED(CONFIG_ZMK_POINTING) + +static void note_activity_work_cb(struct k_work *_work) { note_activity(); } + +K_WORK_DEFINE(note_activity_work, note_activity_work_cb); + +static void activity_input_listener(struct input_event *ev) { k_work_submit(¬e_activity_work); } + +INPUT_CALLBACK_DEFINE(NULL, activity_input_listener); + +#endif + SYS_INIT(activity_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/behaviors/behavior_input_two_axis.c b/app/src/behaviors/behavior_input_two_axis.c new file mode 100644 index 00000000000..ea8e948d21c --- /dev/null +++ b/app/src/behaviors/behavior_input_two_axis.c @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_input_two_axis + +#include +#include +#include +#include +#include // CLAMP + +#include +#include + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) +#include +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct vector2d { + float x; + float y; +}; + +struct movement_state_1d { + float remainder; + int16_t speed; + int64_t start_time; +}; + +struct movement_state_2d { + struct movement_state_1d x; + struct movement_state_1d y; +}; + +struct behavior_input_two_axis_data { + struct k_work_delayable tick_work; + const struct device *dev; + + struct movement_state_2d state; +}; + +struct behavior_input_two_axis_config { + int16_t x_code; + int16_t y_code; + uint16_t delay_ms; + uint16_t time_to_max_speed_ms; + uint8_t trigger_period_ms; + // acceleration exponent 0: uniform speed + // acceleration exponent 1: uniform acceleration + // acceleration exponent 2: uniform jerk + uint8_t acceleration_exponent; +}; + +#if CONFIG_MINIMAL_LIBC +static float powf(float base, float exponent) { + // poor man's power implementation rounds the exponent down to the nearest integer. + float power = 1.0f; + for (; exponent >= 1.0f; exponent--) { + power = power * base; + } + return power; +} +#else +#include +#endif + +static int64_t ticks_since_start(int64_t start, int64_t now, int64_t delay) { + if (start == 0) { + return 0; + } + int64_t move_duration = now - (start + delay); + // start can be in the future if there's a delay + if (move_duration < 0) { + move_duration = 0; + } + return move_duration; +} + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +static uint8_t get_acceleration_exponent(const struct behavior_input_two_axis_config *config, + uint16_t code) { + switch (code) { + case INPUT_REL_WHEEL: + return (zmk_pointing_resolution_multipliers_get_current_profile().wheel > 0) + ? 0 + : config->acceleration_exponent; + case INPUT_REL_HWHEEL: + return (zmk_pointing_resolution_multipliers_get_current_profile().hor_wheel > 0) + ? 0 + : config->acceleration_exponent; + default: + return config->acceleration_exponent; + } +} + +#else + +static inline uint8_t get_acceleration_exponent(const struct behavior_input_two_axis_config *config, + uint16_t code) { + return config->acceleration_exponent; +} + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +static float speed(const struct behavior_input_two_axis_config *config, uint16_t code, + float max_speed, int64_t duration_ticks) { + uint8_t accel_exp = get_acceleration_exponent(config, code); + + if ((1000 * duration_ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC) > config->time_to_max_speed_ms || + config->time_to_max_speed_ms == 0 || accel_exp == 0) { + return max_speed; + } + + // Calculate the speed based on MouseKeysAccel + // See https://en.wikipedia.org/wiki/Mouse_keys + if (duration_ticks == 0) { + return 0; + } + + float time_fraction = (float)(1000 * duration_ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC) / + config->time_to_max_speed_ms; + return max_speed * powf(time_fraction, accel_exp); +} + +static void track_remainder(float *move, float *remainder) { + float new_move = *move + *remainder; + *remainder = new_move - (int)new_move; + *move = (int)new_move; +} + +static float update_movement_1d(const struct behavior_input_two_axis_config *config, uint16_t code, + struct movement_state_1d *state, int64_t now) { + float move = 0; + if (state->speed == 0) { + state->remainder = 0; + return move; + } + + int64_t move_duration = ticks_since_start(state->start_time, now, config->delay_ms); + LOG_DBG("Calculated speed: %f", speed(config, code, state->speed, move_duration)); + move = + (move_duration > 0) + ? (speed(config, code, state->speed, move_duration) * config->trigger_period_ms / 1000) + : 0; + + track_remainder(&(move), &(state->remainder)); + + return move; +} +static struct vector2d update_movement_2d(const struct behavior_input_two_axis_config *config, + struct movement_state_2d *state, int64_t now) { + struct vector2d move = {0}; + + move = (struct vector2d){ + .x = update_movement_1d(config, config->x_code, &state->x, now), + .y = update_movement_1d(config, config->y_code, &state->y, now), + }; + + return move; +} + +static bool is_non_zero_1d_movement(int16_t speed) { return speed != 0; } + +static bool is_non_zero_2d_movement(struct movement_state_2d *state) { + return is_non_zero_1d_movement(state->x.speed) || is_non_zero_1d_movement(state->y.speed); +} + +static bool should_be_working(struct behavior_input_two_axis_data *data) { + return is_non_zero_2d_movement(&data->state); +} + +static void tick_work_cb(struct k_work *work) { + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct behavior_input_two_axis_data *data = + CONTAINER_OF(d_work, struct behavior_input_two_axis_data, tick_work); + const struct device *dev = data->dev; + const struct behavior_input_two_axis_config *cfg = dev->config; + + uint64_t timestamp = k_uptime_ticks(); + + // LOG_INF("x start: %llu, y start: %llu, current timestamp: %llu", data->state.x.start_time, + // data->state.y.start_time, timestamp); + + struct vector2d move = update_movement_2d(cfg, &data->state, timestamp); + + int ret = 0; + bool have_x = is_non_zero_1d_movement(move.x); + bool have_y = is_non_zero_1d_movement(move.y); + if (have_x) { + ret = input_report_rel(dev, cfg->x_code, (int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX), + !have_y, K_NO_WAIT); + } + if (have_y) { + ret = input_report_rel(dev, cfg->y_code, (int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX), true, + K_NO_WAIT); + } + + if (should_be_working(data)) { + k_work_schedule(&data->tick_work, K_MSEC(cfg->trigger_period_ms)); + } +} + +static void set_start_times_for_activity_1d(struct movement_state_1d *state) { + if (state->speed != 0 && state->start_time == 0) { + state->start_time = k_uptime_ticks(); + } else if (state->speed == 0) { + state->start_time = 0; + } +} +static void set_start_times_for_activity(struct movement_state_2d *state) { + set_start_times_for_activity_1d(&state->x); + set_start_times_for_activity_1d(&state->y); +} + +static void update_work_scheduling(const struct device *dev) { + struct behavior_input_two_axis_data *data = dev->data; + const struct behavior_input_two_axis_config *cfg = dev->config; + + set_start_times_for_activity(&data->state); + + if (should_be_working(data)) { + k_work_schedule(&data->tick_work, K_MSEC(cfg->trigger_period_ms)); + } else { + k_work_cancel_delayable(&data->tick_work); + data->state.y.remainder = 0; + data->state.x.remainder = 0; + } +} + +int behavior_input_two_axis_adjust_speed(const struct device *dev, int16_t dx, int16_t dy) { + struct behavior_input_two_axis_data *data = dev->data; + + LOG_DBG("Adjusting: %d %d", dx, dy); + data->state.x.speed += dx; + data->state.y.speed += dy; + + LOG_DBG("After: %d %d", data->state.x.speed, data->state.y.speed); + + update_work_scheduling(dev); + + return 0; +} + +static int behavior_input_two_axis_init(const struct device *dev) { + struct behavior_input_two_axis_data *data = dev->data; + + data->dev = dev; + k_work_init_delayable(&data->tick_work, tick_work_cb); + + return 0; +}; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + + const struct device *behavior_dev = zmk_behavior_get_binding(binding->behavior_dev); + + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + int16_t x = MOVE_X_DECODE(binding->param1); + int16_t y = MOVE_Y_DECODE(binding->param1); + + behavior_input_two_axis_adjust_speed(behavior_dev, x, y); + return 0; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *behavior_dev = zmk_behavior_get_binding(binding->behavior_dev); + + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + int16_t x = MOVE_X_DECODE(binding->param1); + int16_t y = MOVE_Y_DECODE(binding->param1); + + behavior_input_two_axis_adjust_speed(behavior_dev, -x, -y); + return 0; +} + +static const struct behavior_driver_api behavior_input_two_axis_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define ITA_INST(n) \ + static struct behavior_input_two_axis_data behavior_input_two_axis_data_##n = {}; \ + static struct behavior_input_two_axis_config behavior_input_two_axis_config_##n = { \ + .x_code = DT_INST_PROP(n, x_input_code), \ + .y_code = DT_INST_PROP(n, y_input_code), \ + .trigger_period_ms = DT_INST_PROP(n, trigger_period_ms), \ + .delay_ms = DT_INST_PROP_OR(n, delay_ms, 0), \ + .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ + .acceleration_exponent = DT_INST_PROP_OR(n, acceleration_exponent, 1), \ + }; \ + BEHAVIOR_DT_INST_DEFINE( \ + n, behavior_input_two_axis_init, NULL, &behavior_input_two_axis_data_##n, \ + &behavior_input_two_axis_config_##n, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_input_two_axis_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(ITA_INST) diff --git a/app/src/behaviors/behavior_mouse_key_press.c b/app/src/behaviors/behavior_mouse_key_press.c index 9064a1aa5c8..66b54fce05b 100644 --- a/app/src/behaviors/behavior_mouse_key_press.c +++ b/app/src/behaviors/behavior_mouse_key_press.c @@ -11,8 +11,9 @@ #include #include -#include -#include +#include +#include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -20,19 +21,31 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static int behavior_mouse_key_press_init(const struct device *dev) { return 0; }; +static void process_key_state(const struct device *dev, int32_t val, bool pressed) { + for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) { + if (val & BIT(i)) { + WRITE_BIT(val, i, 0); + input_report_key(dev, INPUT_BTN_0 + i, pressed ? 1 : 0, val == 0, K_FOREVER); + } + } +} + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); - return raise_zmk_mouse_button_state_changed_from_encoded(binding->param1, true, - event.timestamp); + process_key_state(zmk_behavior_get_binding(binding->behavior_dev), binding->param1, true); + + return 0; } static int on_keymap_binding_released(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); - return raise_zmk_mouse_button_state_changed_from_encoded(binding->param1, false, - event.timestamp); + + process_key_state(zmk_behavior_get_binding(binding->behavior_dev), binding->param1, false); + + return 0; } static const struct behavior_driver_api behavior_mouse_key_press_driver_api = { diff --git a/app/src/endpoints.c b/app/src/endpoints.c index b17a664646d..ae0e5e7fd88 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -201,7 +201,7 @@ int zmk_endpoints_send_report(uint16_t usage_page) { return -ENOTSUP; } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) int zmk_endpoints_send_mouse_report() { switch (current_instance.transport) { case ZMK_TRANSPORT_USB: { @@ -235,7 +235,7 @@ int zmk_endpoints_send_mouse_report() { LOG_ERR("Unhandled endpoint transport %d", current_instance.transport); return -ENOTSUP; } -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) #if IS_ENABLED(CONFIG_SETTINGS) @@ -332,9 +332,9 @@ static int zmk_endpoints_init(void) { void zmk_endpoints_clear_current(void) { zmk_hid_keyboard_clear(); zmk_hid_consumer_clear(); -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) zmk_hid_mouse_clear(); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) zmk_endpoints_send_report(HID_USAGE_KEY); zmk_endpoints_send_report(HID_USAGE_CONSUMER); diff --git a/app/src/hid.c b/app/src/hid.c index df0715ee487..c050f062a81 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -25,12 +25,13 @@ static uint8_t keys_held = 0; #endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) -static struct zmk_hid_mouse_report mouse_report = {.report_id = ZMK_HID_REPORT_ID_MOUSE, - .body = {.buttons = 0}}; +static struct zmk_hid_mouse_report mouse_report = { + .report_id = ZMK_HID_REPORT_ID_MOUSE, + .body = {.buttons = 0, .d_x = 0, .d_y = 0, .d_scroll_y = 0}}; -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) // Keep track of how often a modifier was pressed. // Only release the modifier if the count is 0. @@ -370,7 +371,7 @@ bool zmk_hid_is_pressed(uint32_t usage) { return false; } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) // Keep track of how often a button was pressed. // Only release the button if the count is 0. @@ -431,16 +432,48 @@ int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { } return 0; } -void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +void zmk_hid_mouse_movement_set(int16_t hwheel, int16_t wheel) { + mouse_report.body.d_x = hwheel; + mouse_report.body.d_y = wheel; + LOG_DBG("Mouse movement set to %d/%d", mouse_report.body.d_x, mouse_report.body.d_y); +} + +void zmk_hid_mouse_movement_update(int16_t hwheel, int16_t wheel) { + mouse_report.body.d_x += hwheel; + mouse_report.body.d_y += wheel; + LOG_DBG("Mouse movement updated to %d/%d", mouse_report.body.d_x, mouse_report.body.d_y); +} + +void zmk_hid_mouse_scroll_set(int8_t hwheel, int8_t wheel) { + mouse_report.body.d_scroll_x = hwheel; + mouse_report.body.d_scroll_y = wheel; + + LOG_DBG("Mouse scroll set to %d/%d", mouse_report.body.d_scroll_x, + mouse_report.body.d_scroll_y); +} + +void zmk_hid_mouse_scroll_update(int8_t hwheel, int8_t wheel) { + mouse_report.body.d_scroll_x += hwheel; + mouse_report.body.d_scroll_y += wheel; + + LOG_DBG("Mouse scroll updated to X: %d/%d", mouse_report.body.d_scroll_x, + mouse_report.body.d_scroll_y); +} + +void zmk_hid_mouse_clear(void) { + LOG_DBG("Mouse report cleared"); + memset(&mouse_report.body, 0, sizeof(mouse_report.body)); +} + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { return &keyboard_report; } struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { return &consumer_report; } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { return &mouse_report; } -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) diff --git a/app/src/hog.c b/app/src/hog.c index 82fafc29cd7..d56eacf76c0 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -18,6 +18,9 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) +#include +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) #if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) #include #endif // IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) @@ -69,14 +72,23 @@ static struct hids_report consumer_input = { .type = HIDS_INPUT, }; -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) static struct hids_report mouse_input = { .id = ZMK_HID_REPORT_ID_MOUSE, .type = HIDS_INPUT, }; -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +static struct hids_report mouse_feature = { + .id = ZMK_HID_REPORT_ID_MOUSE, + .type = HIDS_FEATURE, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) static bool host_requests_notification = false; static uint8_t ctrl_point; @@ -143,14 +155,53 @@ static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, sizeof(struct zmk_hid_consumer_report_body)); } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) + static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { struct zmk_hid_mouse_report_body *report_body = &zmk_hid_get_mouse_report()->body; return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, sizeof(struct zmk_hid_mouse_report_body)); } -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +static ssize_t read_hids_mouse_feature_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_hids_mouse_feature_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) { + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + if (len != sizeof(struct zmk_hid_mouse_resolution_feature_report_body)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + struct zmk_hid_mouse_resolution_feature_report_body *report = + (struct zmk_hid_mouse_resolution_feature_report_body *)buf; + int profile = zmk_ble_profile_index(bt_conn_get_dst(conn)); + if (profile < 0) { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + struct zmk_endpoint_instance endpoint = {.transport = ZMK_TRANSPORT_BLE, + .ble = { + .profile_index = profile, + }}; + zmk_pointing_resolution_multipliers_process_report(report, endpoint); + + return len; +} + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) // static ssize_t write_proto_mode(struct bt_conn *conn, // const struct bt_gatt_attr *attr, @@ -200,13 +251,23 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, NULL, &consumer_input), -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, read_hids_mouse_input_report, NULL, NULL), BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, NULL, &mouse_input), -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, + read_hids_mouse_feature_report, write_hids_mouse_feature_report, NULL), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &mouse_feature), +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) #if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, @@ -328,7 +389,7 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { return 0; }; -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body), CONFIG_ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE, 4); @@ -380,8 +441,7 @@ int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { return 0; }; - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) static int zmk_hog_init(void) { static const struct k_work_queue_config queue_config = {.name = "HID Over GATT Send Work"}; diff --git a/app/src/mouse.c b/app/src/mouse.c deleted file mode 100644 index c1b9ac0261e..00000000000 --- a/app/src/mouse.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#include -#include - -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -#include -#include -#include -#include - -static void listener_mouse_button_pressed(const struct zmk_mouse_button_state_changed *ev) { - LOG_DBG("buttons: 0x%02X", ev->buttons); - zmk_hid_mouse_buttons_press(ev->buttons); - zmk_endpoints_send_mouse_report(); -} - -static void listener_mouse_button_released(const struct zmk_mouse_button_state_changed *ev) { - LOG_DBG("buttons: 0x%02X", ev->buttons); - zmk_hid_mouse_buttons_release(ev->buttons); - zmk_endpoints_send_mouse_report(); -} - -int mouse_listener(const zmk_event_t *eh) { - const struct zmk_mouse_button_state_changed *mbt_ev = as_zmk_mouse_button_state_changed(eh); - if (mbt_ev) { - if (mbt_ev->state) { - listener_mouse_button_pressed(mbt_ev); - } else { - listener_mouse_button_released(mbt_ev); - } - return 0; - } - return 0; -} - -ZMK_LISTENER(mouse_listener, mouse_listener); -ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_button_state_changed); diff --git a/app/src/pointing/CMakeLists.txt b/app/src/pointing/CMakeLists.txt new file mode 100644 index 00000000000..ad74c1ea786 --- /dev/null +++ b/app/src/pointing/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +target_sources_ifdef(CONFIG_ZMK_INPUT_LISTENER app PRIVATE input_listener.c) +target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_TRANSFORM app PRIVATE input_processor_transform.c) +target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_SCALER app PRIVATE input_processor_scaler.c) +target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_TEMP_LAYER app PRIVATE input_processor_temp_layer.c) +target_sources_ifdef(CONFIG_ZMK_INPUT_PROCESSOR_CODE_MAPPER app PRIVATE input_processor_code_mapper.c) +target_sources_ifdef(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING app PRIVATE resolution_multipliers.c) +target_sources_ifdef(CONFIG_ZMK_INPUT_SPLIT app PRIVATE input_split.c) diff --git a/app/src/pointing/Kconfig b/app/src/pointing/Kconfig new file mode 100644 index 00000000000..42cf8e8c072 --- /dev/null +++ b/app/src/pointing/Kconfig @@ -0,0 +1,75 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menu "Pointing Options" + +# Deprecated old config, kept for backwards compat +config ZMK_MOUSE + bool "(Deprecated) Mouse Support" + +config ZMK_POINTING + bool "Pointing Device Support" + default y if ZMK_MOUSE + select INPUT + select INPUT_THREAD_PRIORITY_OVERRIDE + +if ZMK_POINTING + +# Needed for anyone using gpio-keys for things like soft-off setup. +config INPUT_GPIO_KEYS + default n + +config INPUT_THREAD_STACK_SIZE + default 1024 if ZMK_SPLIT && !ZMK_SPLIT_ROLE_CENTRAL + +if !ZMK_SPLIT || ZMK_SPLIT_ROLE_CENTRAL + +config ZMK_POINTING_SMOOTH_SCROLLING + bool "Smooth Scrolling" + help + Enable smooth scrolling, with hosts that support HID Resolution Multipliers + +config ZMK_INPUT_LISTENER + bool "Input listener for processing input events in the system" + default y + depends on DT_HAS_ZMK_INPUT_LISTENER_ENABLED + + +config ZMK_INPUT_PROCESSOR_TEMP_LAYER + bool "Temporary Layer Input Processor" + default y + depends on DT_HAS_ZMK_INPUT_PROCESSOR_TEMP_LAYER_ENABLED + +endif + +config ZMK_INPUT_PROCESSOR_TRANSFORM + bool "Transform Input Processor" + default y + depends on DT_HAS_ZMK_INPUT_PROCESSOR_TRANSFORM_ENABLED + +config ZMK_INPUT_PROCESSOR_SCALER + bool "Scaling Input Processor" + default y + depends on DT_HAS_ZMK_INPUT_PROCESSOR_SCALER_ENABLED + +config ZMK_INPUT_PROCESSOR_CODE_MAPPER + bool "Code Mapper Input Processor" + default y + depends on DT_HAS_ZMK_INPUT_PROCESSOR_CODE_MAPPER_ENABLED + +config ZMK_INPUT_SPLIT + bool "Split input support" + default y + depends on DT_HAS_ZMK_INPUT_SPLIT_ENABLED && ZMK_SPLIT + +if ZMK_INPUT_SPLIT + +config ZMK_INPUT_SPLIT_INIT_PRIORITY + int "Input Split initialization priority" + default INPUT_INIT_PRIORITY + +endif # ZMK_INPUT_SPLIT + +endif # ZMK_POINTING + +endmenu # Mouse Options diff --git a/app/src/pointing/input_listener.c b/app/src/pointing/input_listener.c new file mode 100644 index 00000000000..97e39285d33 --- /dev/null +++ b/app/src/pointing/input_listener.c @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_listener + +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include + +#include +#include +#include + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) +#include +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +#include +#include + +#define ONE_IF_DEV_OK(n) \ + COND_CODE_1(DT_NODE_HAS_STATUS(DT_INST_PHANDLE(n, device), okay), (1 +), (0 +)) + +#define VALID_LISTENER_COUNT (DT_INST_FOREACH_STATUS_OKAY(ONE_IF_DEV_OK) 0) + +#if VALID_LISTENER_COUNT > 0 + +enum input_listener_xy_data_mode { + INPUT_LISTENER_XY_DATA_MODE_NONE, + INPUT_LISTENER_XY_DATA_MODE_REL, + INPUT_LISTENER_XY_DATA_MODE_ABS, +}; + +struct input_listener_axis_data { + int16_t value; +}; + +struct input_listener_xy_data { + enum input_listener_xy_data_mode mode; + struct input_listener_axis_data x; + struct input_listener_axis_data y; +}; + +struct input_listener_config_entry { + size_t processors_len; + const struct zmk_input_processor_entry *processors; +}; + +struct input_listener_layer_override { + uint32_t layer_mask; + bool process_next; + struct input_listener_config_entry config; +}; + +struct input_processor_remainder_data { + int16_t x, y, wheel, h_wheel; +}; + +struct input_listener_processor_data { + size_t remainders_len; + struct input_processor_remainder_data *remainders; +}; + +struct input_listener_config { + struct input_listener_config_entry base; + size_t layer_overrides_len; + struct input_listener_layer_override layer_overrides[]; +}; + +struct input_listener_data { + union { + struct { + struct input_listener_xy_data data; + struct input_listener_xy_data wheel_data; + + uint8_t button_set; + uint8_t button_clear; + } mouse; + }; + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + int16_t wheel_remainder; + int16_t h_wheel_remainder; +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + + struct input_listener_processor_data base_processor_data; + struct input_listener_processor_data layer_override_data[]; +}; + +static void handle_rel_code(struct input_listener_data *data, struct input_event *evt) { + switch (evt->code) { + case INPUT_REL_X: + data->mouse.data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.data.x.value += evt->value; + break; + case INPUT_REL_Y: + data->mouse.data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.data.y.value += evt->value; + break; + case INPUT_REL_WHEEL: + data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.wheel_data.y.value += evt->value; + break; + case INPUT_REL_HWHEEL: + data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.wheel_data.x.value += evt->value; + break; + default: + break; + } +} + +static void handle_abs_code(const struct input_listener_config *config, + struct input_listener_data *data, struct input_event *evt) {} + +static void handle_key_code(const struct input_listener_config *config, + struct input_listener_data *data, struct input_event *evt) { + int8_t btn; + + switch (evt->code) { + case INPUT_BTN_0: + case INPUT_BTN_1: + case INPUT_BTN_2: + case INPUT_BTN_3: + case INPUT_BTN_4: + btn = evt->code - INPUT_BTN_0; + if (evt->value > 0) { + WRITE_BIT(data->mouse.button_set, btn, 1); + } else { + WRITE_BIT(data->mouse.button_clear, btn, 1); + } + break; + default: + break; + } +} + +static inline bool is_x_data(const struct input_event *evt) { + return evt->type == INPUT_EV_REL && evt->code == INPUT_REL_X; +} + +static inline bool is_y_data(const struct input_event *evt) { + return evt->type == INPUT_EV_REL && evt->code == INPUT_REL_Y; +} + +static void apply_config(const struct input_listener_config_entry *cfg, + struct input_listener_processor_data *processor_data, + struct input_listener_data *data, struct input_event *evt) { + size_t remainder_index = 0; + for (size_t p = 0; p < cfg->processors_len; p++) { + const struct zmk_input_processor_entry *proc_e = &cfg->processors[p]; + struct input_processor_remainder_data *remainders = NULL; + if (proc_e->track_remainders) { + remainders = &processor_data->remainders[remainder_index++]; + } + + int16_t *remainder = NULL; + if (remainders) { + if (evt->type == INPUT_EV_REL) { + switch (evt->code) { + case INPUT_REL_X: + remainder = &remainders->x; + break; + case INPUT_REL_Y: + remainder = &remainders->y; + break; + case INPUT_REL_WHEEL: + remainder = &remainders->wheel; + break; + case INPUT_REL_HWHEEL: + remainder = &remainders->h_wheel; + break; + } + } + } + + struct zmk_input_processor_state state = {.remainder = remainder}; + + zmk_input_processor_handle_event(proc_e->dev, evt, proc_e->param1, proc_e->param2, &state); + } +} +static void filter_with_input_config(const struct input_listener_config *cfg, + struct input_listener_data *data, struct input_event *evt) { + if (!evt->dev) { + return; + } + + for (size_t oi = 0; oi < cfg->layer_overrides_len; oi++) { + const struct input_listener_layer_override *override = &cfg->layer_overrides[oi]; + struct input_listener_processor_data *override_data = &data->layer_override_data[oi]; + uint32_t mask = override->layer_mask; + uint8_t layer = 0; + while (mask != 0) { + if (mask & BIT(0) && zmk_keymap_layer_active(layer)) { + apply_config(&override->config, override_data, data, evt); + if (!override->process_next) { + return; + } + } + + layer++; + mask = mask >> 1; + } + } + + apply_config(&cfg->base, &data->base_processor_data, data, evt); +} + +static void clear_xy_data(struct input_listener_xy_data *data) { + data->x.value = data->y.value = 0; + data->mode = INPUT_LISTENER_XY_DATA_MODE_NONE; +} + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) +static void apply_resolution_scaling(struct input_listener_data *data, struct input_event *evt) { + int16_t *remainder; + uint8_t div; + + switch (evt->code) { + case INPUT_REL_WHEEL: + remainder = &data->wheel_remainder; + div = (16 - zmk_pointing_resolution_multipliers_get_current_profile().wheel); + break; + case INPUT_REL_HWHEEL: + remainder = &data->h_wheel_remainder; + div = (16 - zmk_pointing_resolution_multipliers_get_current_profile().hor_wheel); + break; + default: + return; + } + + int16_t val = evt->value + *remainder; + int16_t scaled = val / (int16_t)div; + *remainder = val - (scaled * (int16_t)div); + evt->value = val; +} +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + +static void input_handler(const struct input_listener_config *config, + struct input_listener_data *data, struct input_event *evt) { + // First, filter to update the event data as needed. + filter_with_input_config(config, data, evt); + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + apply_resolution_scaling(data, evt); +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + + switch (evt->type) { + case INPUT_EV_REL: + handle_rel_code(data, evt); + break; + case INPUT_EV_ABS: + handle_abs_code(config, data, evt); + break; + case INPUT_EV_KEY: + handle_key_code(config, data, evt); + break; + } + + if (evt->sync) { + if (data->mouse.wheel_data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) { + zmk_hid_mouse_scroll_set(data->mouse.wheel_data.x.value, + data->mouse.wheel_data.y.value); + } + + if (data->mouse.data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) { + zmk_hid_mouse_movement_set(data->mouse.data.x.value, data->mouse.data.y.value); + } + + if (data->mouse.button_set != 0) { + for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) { + if ((data->mouse.button_set & BIT(i)) != 0) { + zmk_hid_mouse_button_press(i); + } + } + } + + if (data->mouse.button_clear != 0) { + for (int i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) { + if ((data->mouse.button_clear & BIT(i)) != 0) { + zmk_hid_mouse_button_release(i); + } + } + } + + zmk_endpoints_send_mouse_report(); + zmk_hid_mouse_scroll_set(0, 0); + zmk_hid_mouse_movement_set(0, 0); + + clear_xy_data(&data->mouse.data); + clear_xy_data(&data->mouse.wheel_data); + + data->mouse.button_set = data->mouse.button_clear = 0; + } +} + +#endif // VALID_LISTENER_COUNT > 0 + +#define ONE_FOR_TRACKED(n, elem, idx) \ + +DT_PROP(DT_PHANDLE_BY_IDX(n, input_processors, idx), track_remainders) +#define PROCESSOR_REM_TRACKERS(n) (0 DT_FOREACH_PROP_ELEM(n, input_processors, ONE_FOR_TRACKED)) + +#define SCOPED_PROCESSOR(scope, n, id) \ + COND_CODE_1(DT_NODE_HAS_PROP(n, input_processors), \ + (static struct input_processor_remainder_data _CONCAT( \ + input_processor_remainders_##id, scope)[PROCESSOR_REM_TRACKERS(n)] = {};), \ + ()) \ + static const struct zmk_input_processor_entry _CONCAT( \ + processor_##id, scope)[DT_PROP_LEN_OR(n, input_processors, 0)] = \ + COND_CODE_1(DT_NODE_HAS_PROP(n, input_processors), \ + ({LISTIFY(DT_PROP_LEN(n, input_processors), ZMK_INPUT_PROCESSOR_ENTRY_AT_IDX, \ + (, ), n)}), \ + ({})); + +#define IL_EXTRACT_CONFIG(n, id, scope) \ + { \ + .processors_len = DT_PROP_LEN_OR(n, input_processors, 0), \ + .processors = _CONCAT(processor_##id, scope), \ + } + +#define IL_EXTRACT_DATA(n, id, scope) \ + {COND_CODE_1(DT_NODE_HAS_PROP(n, input_processors), \ + (.remainders_len = PROCESSOR_REM_TRACKERS(n), \ + .remainders = _CONCAT(input_processor_remainders_##id, scope), ), \ + ())} + +#define IL_ONE(...) +1 + +#define CHILD_CONFIG(node, parent) SCOPED_PROCESSOR(node, node, parent) + +#define OVERRIDE_LAYER_BIT(node, prop, idx) BIT(DT_PROP_BY_IDX(node, prop, idx)) + +#define IL_OVERRIDE(node, parent) \ + { \ + .layer_mask = DT_FOREACH_PROP_ELEM_SEP(node, layers, OVERRIDE_LAYER_BIT, (|)), \ + .process_next = DT_PROP_OR(node, process_next, false), \ + .config = IL_EXTRACT_CONFIG(node, parent, node), \ + } + +#define IL_OVERRIDE_DATA(node, parent) IL_EXTRACT_DATA(node, parent, node) + +#define IL_INST(n) \ + COND_CODE_1( \ + DT_NODE_HAS_STATUS(DT_INST_PHANDLE(n, device), okay), \ + (SCOPED_PROCESSOR(base, DT_DRV_INST(n), n); \ + DT_INST_FOREACH_CHILD_VARGS(n, CHILD_CONFIG, \ + n) static const struct input_listener_config config_##n = \ + { \ + .base = IL_EXTRACT_CONFIG(DT_DRV_INST(n), n, base), \ + .layer_overrides_len = (0 DT_INST_FOREACH_CHILD(n, IL_ONE)), \ + .layer_overrides = {DT_INST_FOREACH_CHILD_SEP_VARGS(n, IL_OVERRIDE, (, ), n)}, \ + }; \ + static struct input_listener_data data_##n = \ + { \ + .base_processor_data = IL_EXTRACT_DATA(DT_DRV_INST(n), n, base), \ + .layer_override_data = {DT_INST_FOREACH_CHILD_SEP_VARGS(n, IL_OVERRIDE_DATA, \ + (, ), n)}, \ + }; \ + void input_handler_##n(struct input_event *evt) { \ + input_handler(&config_##n, &data_##n, evt); \ + } INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(n, device)), input_handler_##n);), \ + ()) + +DT_INST_FOREACH_STATUS_OKAY(IL_INST) diff --git a/app/src/pointing/input_processor_code_mapper.c b/app/src/pointing/input_processor_code_mapper.c new file mode 100644 index 00000000000..20b5744ea96 --- /dev/null +++ b/app/src/pointing/input_processor_code_mapper.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_processor_code_mapper + +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct cm_config { + uint8_t type; + size_t mapping_size; + uint16_t mapping[]; +}; + +static int cm_handle_event(const struct device *dev, struct input_event *event, uint32_t param1, + uint32_t param2, struct zmk_input_processor_state *state) { + const struct cm_config *cfg = dev->config; + + if (event->type != cfg->type) { + return 0; + } + + for (int i = 0; i < cfg->mapping_size / 2; i++) { + if (cfg->mapping[i * 2] == event->code) { + uint16_t orig = event->code; + event->code = cfg->mapping[(i * 2) + 1]; + LOG_DBG("Remapped %d to %d", orig, event->code); + break; + } + } + + return 0; +} + +static struct zmk_input_processor_driver_api cm_driver_api = { + .handle_event = cm_handle_event, +}; + +#define TL_INST(n) \ + static const struct cm_config cm_config_##n = { \ + .type = DT_INST_PROP_OR(n, type, INPUT_EV_REL), \ + .mapping_size = DT_INST_PROP_LEN(n, map), \ + .mapping = DT_INST_PROP(n, map), \ + }; \ + BUILD_ASSERT(DT_INST_PROP_LEN(n, map) % 2 == 0, \ + "Must have an even number of mapping entries"); \ + DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, &cm_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &cm_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(TL_INST) \ No newline at end of file diff --git a/app/src/pointing/input_processor_scaler.c b/app/src/pointing/input_processor_scaler.c new file mode 100644 index 00000000000..eb006cd2540 --- /dev/null +++ b/app/src/pointing/input_processor_scaler.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_processor_scaler + +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct scaler_config { + uint8_t type; + size_t codes_len; + uint16_t codes[]; +}; + +static int scale_val(struct input_event *event, uint32_t mul, uint32_t div, + struct zmk_input_processor_state *state) { + int16_t value_mul = event->value * (int16_t)mul; + + if (state && state->remainder) { + value_mul += *state->remainder; + } + + int16_t scaled = value_mul / (int16_t)div; + + if (state && state->remainder) { + *state->remainder = value_mul - (scaled * (int16_t)div); + } + + LOG_DBG("scaled %d with %d/%d to %d with remainder %d", event->value, mul, div, scaled, + (state && state->remainder) ? *state->remainder : 0); + + event->value = scaled; + + return 0; +} + +static int scaler_handle_event(const struct device *dev, struct input_event *event, uint32_t param1, + uint32_t param2, struct zmk_input_processor_state *state) { + const struct scaler_config *cfg = dev->config; + + if (event->type != cfg->type) { + return 0; + } + + for (int i = 0; i < cfg->codes_len; i++) { + if (cfg->codes[i] == event->code) { + return scale_val(event, param1, param2, state); + } + } + + return 0; +} + +static struct zmk_input_processor_driver_api scaler_driver_api = { + .handle_event = scaler_handle_event, +}; + +#define SCALER_INST(n) \ + static const struct scaler_config scaler_config_##n = { \ + .type = DT_INST_PROP_OR(n, type, INPUT_EV_REL), \ + .codes_len = DT_INST_PROP_LEN(n, codes), \ + .codes = DT_INST_PROP(n, codes), \ + }; \ + DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, &scaler_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &scaler_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SCALER_INST) \ No newline at end of file diff --git a/app/src/pointing/input_processor_temp_layer.c b/app/src/pointing/input_processor_temp_layer.c new file mode 100644 index 00000000000..85a394a693f --- /dev/null +++ b/app/src/pointing/input_processor_temp_layer.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_processor_temp_layer + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +/* Constants and Types */ +#define MAX_LAYERS ZMK_KEYMAP_LAYERS_LEN + +struct temp_layer_config { + int16_t require_prior_idle_ms; + const uint16_t *excluded_positions; + size_t num_positions; +}; + +struct temp_layer_state { + uint8_t toggle_layer; + bool is_active; + int64_t last_tapped_timestamp; +}; + +struct temp_layer_data { + const struct device *dev; + struct temp_layer_state state; +}; + +/* Static Work Queue Items */ +static struct k_work_delayable layer_disable_works[MAX_LAYERS]; + +/* Position Search */ +static bool position_is_excluded(const struct temp_layer_config *config, uint32_t position) { + if (!config->excluded_positions || !config->num_positions) { + return false; + } + + const uint16_t *end = config->excluded_positions + config->num_positions; + for (const uint16_t *pos = config->excluded_positions; pos < end; pos++) { + if (*pos == position) { + return true; + } + } + + return false; +} + +/* Timing Check */ +static bool should_quick_tap(const struct temp_layer_config *config, int64_t last_tapped, + int64_t current_time) { + return (last_tapped + config->require_prior_idle_ms) > current_time; +} + +/* Layer State Management */ +static void update_layer_state(struct temp_layer_state *state, bool activate) { + if (state->is_active == activate) { + return; + } + + state->is_active = activate; + if (activate) { + zmk_keymap_layer_activate(state->toggle_layer); + LOG_DBG("Layer %d activated", state->toggle_layer); + } else { + zmk_keymap_layer_deactivate(state->toggle_layer); + LOG_DBG("Layer %d deactivated", state->toggle_layer); + } +} + +/* Work Queue Callback */ +static void layer_disable_callback(struct k_work *work) { + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + int layer_index = ARRAY_INDEX(layer_disable_works, d_work); + + const struct device *dev = DEVICE_DT_INST_GET(0); + struct temp_layer_data *data = (struct temp_layer_data *)dev->data; + + if (zmk_keymap_layer_active(layer_index)) { + update_layer_state(&data->state, false); + } +} + +/* Event Handlers */ +static int handle_position_state_changed(const zmk_event_t *eh) { + const struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh); + if (!ev->state) { + return ZMK_EV_EVENT_BUBBLE; + } + + const struct device *dev = DEVICE_DT_INST_GET(0); + struct temp_layer_data *data = (struct temp_layer_data *)dev->data; + const struct temp_layer_config *cfg = dev->config; + + if (data->state.is_active && cfg->excluded_positions && cfg->num_positions > 0) { + if (!position_is_excluded(cfg, ev->position)) { + LOG_DBG("Position not excluded, deactivating layer"); + update_layer_state(&data->state, false); + } + } + LOG_DBG("Position excluded, continuing"); + + return ZMK_EV_EVENT_BUBBLE; +} + +static int handle_keycode_state_changed(const zmk_event_t *eh) { + const struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh); + if (!ev->state) { + return ZMK_EV_EVENT_BUBBLE; + } + + const struct device *dev = DEVICE_DT_INST_GET(0); + struct temp_layer_data *data = (struct temp_layer_data *)dev->data; + LOG_DBG("Setting last_tapped_timestamp to: %d", ev->timestamp); + data->state.last_tapped_timestamp = ev->timestamp; + + return ZMK_EV_EVENT_BUBBLE; +} + +static int handle_state_changed_dispatcher(const zmk_event_t *eh) { + if (as_zmk_position_state_changed(eh) != NULL) { + LOG_DBG("Dispatching handle_position_state_changed"); + return handle_position_state_changed(eh); + } else if (as_zmk_keycode_state_changed(eh) != NULL) { + LOG_DBG("Dispatching handle_keycode_state_changed"); + return handle_keycode_state_changed(eh); + } + + return ZMK_EV_EVENT_BUBBLE; +} + +/* Driver Implementation */ +static int temp_layer_handle_event(const struct device *dev, struct input_event *event, + uint32_t param1, uint32_t param2, + struct zmk_input_processor_state *state) { + if (param1 >= MAX_LAYERS) { + LOG_ERR("Invalid layer index: %d", param1); + return -EINVAL; + } + + struct temp_layer_data *data = (struct temp_layer_data *)dev->data; + const struct temp_layer_config *cfg = dev->config; + + data->state.toggle_layer = param1; + + if (!data->state.is_active && + !should_quick_tap(cfg, data->state.last_tapped_timestamp, k_uptime_get())) { + update_layer_state(&data->state, true); + } + + if (param2 > 0) { + k_work_reschedule(&layer_disable_works[param1], K_MSEC(param2)); + } + + return 0; +} + +static int temp_layer_init(const struct device *dev) { + for (int i = 0; i < MAX_LAYERS; i++) { + k_work_init_delayable(&layer_disable_works[i], layer_disable_callback); + } + + return 0; +} + +/* Driver API */ +static const struct zmk_input_processor_driver_api temp_layer_driver_api = { + .handle_event = temp_layer_handle_event, +}; + +/* Event Listeners Conditions */ +#define NEEDS_POSITION_HANDLERS(n, ...) DT_INST_PROP_HAS_IDX(n, excluded_positions, 0) +#define NEEDS_KEYCODE_HANDLERS(n, ...) (DT_INST_PROP_OR(n, require_prior_idle_ms, 0) > 0) + +/* Event Handlers Registration */ +#if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_POSITION_HANDLERS, ||) || \ + DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_KEYCODE_HANDLERS, ||) +ZMK_LISTENER(processor_temp_layer, handle_state_changed_dispatcher); +#endif + +/* Individual Subscriptions */ +#if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_POSITION_HANDLERS, ||) +ZMK_SUBSCRIPTION(processor_temp_layer, zmk_position_state_changed); +#endif + +#if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_KEYCODE_HANDLERS, ||) +ZMK_SUBSCRIPTION(processor_temp_layer, zmk_keycode_state_changed); +#endif + +/* Device Instantiation */ +#define TEMP_LAYER_INST(n) \ + static struct temp_layer_data processor_temp_layer_data_##n = {}; \ + static const uint16_t excluded_positions_##n[] = DT_INST_PROP(n, excluded_positions); \ + static const struct temp_layer_config processor_temp_layer_config_##n = { \ + .require_prior_idle_ms = DT_INST_PROP_OR(n, require_prior_idle_ms, 0), \ + .excluded_positions = excluded_positions_##n, \ + .num_positions = DT_INST_PROP_LEN(n, excluded_positions), \ + }; \ + DEVICE_DT_INST_DEFINE(n, temp_layer_init, NULL, &processor_temp_layer_data_##n, \ + &processor_temp_layer_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &temp_layer_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(TEMP_LAYER_INST) diff --git a/app/src/pointing/input_processor_transform.c b/app/src/pointing/input_processor_transform.c new file mode 100644 index 00000000000..10828de3f39 --- /dev/null +++ b/app/src/pointing/input_processor_transform.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_processor_transform + +#include +#include +#include + +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include + +struct ipt_config { + size_t x_codes_size; + size_t y_codes_size; + uint8_t type; + + const uint16_t *x_codes; + const uint16_t *y_codes; +}; + +static int code_idx(uint16_t code, const uint16_t *list, size_t len) { + for (int i = 0; i < len; i++) { + if (list[i] == code) { + return i; + } + } + + return -ENODEV; +} + +static int ipt_handle_event(const struct device *dev, struct input_event *event, uint32_t param1, + uint32_t param2, struct zmk_input_processor_state *state) { + const struct ipt_config *cfg = dev->config; + + if (event->type != cfg->type) { + return 0; + } + + if (param1 & INPUT_TRANSFORM_XY_SWAP) { + int idx = code_idx(event->code, cfg->x_codes, cfg->x_codes_size); + if (idx >= 0) { + event->code = cfg->y_codes[idx]; + } else { + idx = code_idx(event->code, cfg->y_codes, cfg->y_codes_size); + + if (idx >= 0) { + event->code = cfg->x_codes[idx]; + } + } + } + + if ((param1 & INPUT_TRANSFORM_X_INVERT && + code_idx(event->code, cfg->x_codes, cfg->x_codes_size) >= 0) || + (param1 & INPUT_TRANSFORM_Y_INVERT && + code_idx(event->code, cfg->y_codes, cfg->y_codes_size) >= 0)) { + event->value = -event->value; + } + + return 0; +} + +static struct zmk_input_processor_driver_api ipt_driver_api = { + .handle_event = ipt_handle_event, +}; + +static int ipt_init(const struct device *dev) { return 0; } + +#define IPT_INST(n) \ + static const uint16_t ipt_x_codes_##n[] = DT_INST_PROP(n, x_codes); \ + static const uint16_t ipt_y_codes_##n[] = DT_INST_PROP(n, y_codes); \ + BUILD_ASSERT(ARRAY_SIZE(ipt_x_codes_##n) == ARRAY_SIZE(ipt_x_codes_##n), \ + "X and Y codes need to be the same size"); \ + static const struct ipt_config ipt_config_##n = { \ + .type = DT_INST_PROP_OR(n, type, INPUT_EV_REL), \ + .x_codes_size = DT_INST_PROP_LEN(n, x_codes), \ + .y_codes_size = DT_INST_PROP_LEN(n, y_codes), \ + .x_codes = ipt_x_codes_##n, \ + .y_codes = ipt_y_codes_##n, \ + }; \ + DEVICE_DT_INST_DEFINE(n, &ipt_init, NULL, NULL, &ipt_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &ipt_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(IPT_INST) \ No newline at end of file diff --git a/app/src/pointing/input_split.c b/app/src/pointing/input_split.c new file mode 100644 index 00000000000..55a84ea4624 --- /dev/null +++ b/app/src/pointing/input_split.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_split + +#include +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + +struct zis_entry { + uint8_t reg; + const struct device *dev; +}; + +#define ZIS_ENTRY(n) {.reg = DT_INST_REG_ADDR(n), .dev = DEVICE_DT_GET(DT_DRV_INST(n))}, + +static const struct zis_entry proxy_inputs[] = {DT_INST_FOREACH_STATUS_OKAY(ZIS_ENTRY)}; + +int zmk_input_split_report_peripheral_event(uint8_t reg, uint8_t type, uint16_t code, int32_t value, + bool sync) { + LOG_DBG("Got peripheral event for %d!", reg); + for (size_t i = 0; i < ARRAY_SIZE(proxy_inputs); i++) { + if (reg == proxy_inputs[i].reg) { + return input_report(proxy_inputs[i].dev, type, code, value, sync, K_NO_WAIT); + } + } + + return -ENODEV; +} + +#define ZIS_INST(n) \ + DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, NULL, POST_KERNEL, \ + CONFIG_ZMK_INPUT_SPLIT_INIT_PRIORITY, NULL); + +#else + +#include + +#define ZIS_INST(n) \ + static const struct zmk_input_processor_entry processors_##n[] = \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(n, input_processors), \ + ({LISTIFY(DT_INST_PROP_LEN(n, input_processors), \ + ZMK_INPUT_PROCESSOR_ENTRY_AT_IDX, (, ), DT_DRV_INST(n))}), \ + ({})); \ + BUILD_ASSERT(DT_INST_NODE_HAS_PROP(n, device), \ + "Peripheral input splits need an `input` property set"); \ + void split_input_handler_##n(struct input_event *evt) { \ + for (size_t i = 0; i < ARRAY_SIZE(processors_##n); i++) { \ + zmk_input_processor_handle_event(processors_##n[i].dev, evt, processors_##n[i].param1, \ + processors_##n[i].param2, NULL); \ + } \ + zmk_split_bt_report_input(DT_INST_REG_ADDR(n), evt->type, evt->code, evt->value, \ + evt->sync); \ + } \ + INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(n, device)), split_input_handler_##n); + +#endif + +DT_INST_FOREACH_STATUS_OKAY(ZIS_INST) \ No newline at end of file diff --git a/app/src/pointing/resolution_multipliers.c b/app/src/pointing/resolution_multipliers.c new file mode 100644 index 00000000000..951d104af5b --- /dev/null +++ b/app/src/pointing/resolution_multipliers.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +static struct zmk_pointing_resolution_multipliers multipliers[ZMK_ENDPOINT_COUNT]; + +struct zmk_pointing_resolution_multipliers +zmk_pointing_resolution_multipliers_get_current_profile(void) { + return zmk_pointing_resolution_multipliers_get_profile(zmk_endpoints_selected()); +} + +struct zmk_pointing_resolution_multipliers +zmk_pointing_resolution_multipliers_get_profile(struct zmk_endpoint_instance endpoint) { + const int profile = zmk_endpoint_instance_to_index(endpoint); + return multipliers[profile]; +} + +void zmk_pointing_resolution_multipliers_set_profile(struct zmk_pointing_resolution_multipliers m, + struct zmk_endpoint_instance endpoint) { + int profile = zmk_endpoint_instance_to_index(endpoint); + + // This write is not happening on the main thread. To prevent potential data races, every + // operation involving hid_indicators must be atomic. Currently, each function either reads + // or writes only one entry at a time, so it is safe to do these operations without a lock. + multipliers[profile] = m; +} + +void zmk_pointing_resolution_multipliers_process_report( + struct zmk_hid_mouse_resolution_feature_report_body *report, + struct zmk_endpoint_instance endpoint) { + struct zmk_pointing_resolution_multipliers vals = { + .wheel = report->wheel_res, + .hor_wheel = report->hwheel_res, + }; + zmk_pointing_resolution_multipliers_set_profile(vals, endpoint); + + LOG_DBG("Update resolution multipliers: endpoint=%d, wheel=%d, hor_wheel=%d", + endpoint.transport, vals.wheel, vals.hor_wheel); +} diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 21ff611f568..4c354b36b16 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -29,6 +29,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include #include #include @@ -62,6 +63,71 @@ struct peripheral_slot { uint8_t changed_positions[POSITION_STATE_DATA_LEN]; }; +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + +static const struct bt_uuid *gatt_ccc_uuid = BT_UUID_GATT_CCC; +static const struct bt_uuid *gatt_cpf_uuid = BT_UUID_GATT_CPF; + +struct peripheral_input_slot { + struct bt_conn *conn; + struct bt_gatt_subscribe_params sub; + uint8_t reg; +}; + +#define COUNT_INPUT_SPLIT(n) +1 + +static struct peripheral_input_slot + peripheral_input_slots[(0 DT_FOREACH_STATUS_OKAY(zmk_input_split, COUNT_INPUT_SPLIT))]; + +static bool input_slot_is_open(size_t i) { + return i < ARRAY_SIZE(peripheral_input_slots) && peripheral_input_slots[i].conn == NULL; +} + +static bool input_slot_is_pending(size_t i) { + return i < ARRAY_SIZE(peripheral_input_slots) && peripheral_input_slots[i].conn != NULL && + (!peripheral_input_slots[i].sub.value_handle || + !peripheral_input_slots[i].sub.ccc_handle || !peripheral_input_slots[i].reg); +} + +static int reserve_next_open_input_slot(struct peripheral_input_slot **slot, struct bt_conn *conn) { + for (size_t i = 0; i < ARRAY_SIZE(peripheral_input_slots); i++) { + if (input_slot_is_open(i)) { + peripheral_input_slots[i].conn = conn; + + // Clear out any previously set values + peripheral_input_slots[i].sub.value_handle = 0; + peripheral_input_slots[i].sub.ccc_handle = 0; + peripheral_input_slots[i].reg = 0; + *slot = &peripheral_input_slots[i]; + return i; + } + } + + return -ENOMEM; +} + +static int find_pending_input_slot(struct peripheral_input_slot **slot, struct bt_conn *conn) { + for (size_t i = 0; i < ARRAY_SIZE(peripheral_input_slots); i++) { + if (peripheral_input_slots[i].conn == conn && input_slot_is_pending(i)) { + *slot = &peripheral_input_slots[i]; + return i; + } + } + + return -ENODEV; +} + +void release_peripheral_input_subs(struct bt_conn *conn) { + for (size_t i = 0; i < ARRAY_SIZE(peripheral_input_slots); i++) { + if (peripheral_input_slots[i].conn == conn) { + peripheral_input_slots[i].conn = NULL; + // memset(&peripheral_input_slots[i], 0, sizeof(struct peripheral_input_slot)); + } + } +} + +#endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + static struct peripheral_slot peripherals[ZMK_SPLIT_BLE_PERIPHERAL_COUNT]; static bool is_scanning = false; @@ -230,6 +296,65 @@ static uint8_t split_central_sensor_notify_func(struct bt_conn *conn, } #endif /* ZMK_KEYMAP_HAS_SENSORS */ +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + +struct zmk_input_event_msg { + uint8_t reg; + struct zmk_split_input_event_payload payload; +}; + +K_MSGQ_DEFINE(peripheral_input_event_msgq, sizeof(struct zmk_input_event_msg), 5, 4); +// CONFIG_ZMK_SPLIT_BLE_CENTRAL_INPUT_QUEUE_SIZE, 4); + +void peripheral_input_event_work_callback(struct k_work *work) { + struct zmk_input_event_msg msg; + while (k_msgq_get(&peripheral_input_event_msgq, &msg, K_NO_WAIT) == 0) { + int ret = zmk_input_split_report_peripheral_event( + msg.reg, msg.payload.type, msg.payload.code, msg.payload.value, msg.payload.sync); + if (ret < 0) { + LOG_WRN("Failed to report peripheral event %d", ret); + } + } +} + +K_WORK_DEFINE(input_event_work, peripheral_input_event_work_callback); + +static uint8_t peripheral_input_event_notify_cb(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) { + if (!data) { + LOG_DBG("[UNSUBSCRIBED]"); + params->value_handle = 0U; + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[INPUT EVENT] data %p length %u", data, length); + + if (length != sizeof(struct zmk_split_input_event_payload)) { + LOG_WRN("Ignoring input event notify with incorrect data length (%d)", length); + return BT_GATT_ITER_STOP; + } + + struct zmk_input_event_msg msg; + + memcpy(&msg.payload, data, MIN(length, sizeof(struct zmk_split_input_event_payload))); + + LOG_DBG("Got an input event with type %d, code %d, value %d, sync %d", msg.payload.type, + msg.payload.code, msg.payload.value, msg.payload.sync); + + for (size_t i = 0; i < ARRAY_SIZE(peripheral_input_slots); i++) { + if (&peripheral_input_slots[i].sub == params) { + msg.reg = peripheral_input_slots[i].reg; + k_msgq_put(&peripheral_input_event_msgq, &msg, K_NO_WAIT); + k_work_submit(&input_event_work); + } + } + + return BT_GATT_ITER_CONTINUE; +} + +#endif + static uint8_t split_central_notify_func(struct bt_conn *conn, struct bt_gatt_subscribe_params *params, const void *data, uint16_t length) { @@ -379,6 +504,7 @@ static uint8_t split_central_battery_level_read_func(struct bt_conn *conn, uint8 #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ static int split_central_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *params) { + atomic_set(params->flags, BT_GATT_SUBSCRIBE_FLAG_NO_RESUB); int err = bt_gatt_subscribe(conn, params); switch (err) { case -EALREADY: @@ -455,64 +581,133 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, } LOG_DBG("[ATTRIBUTE] handle %u", attr->handle); - const struct bt_uuid *chrc_uuid = ((struct bt_gatt_chrc *)attr->user_data)->uuid; - - if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID)) == 0) { - LOG_DBG("Found position state characteristic"); - slot->subscribe_params.disc_params = &slot->sub_discover_params; - slot->subscribe_params.end_handle = slot->discover_params.end_handle; - slot->subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); - slot->subscribe_params.notify = split_central_notify_func; - slot->subscribe_params.value = BT_GATT_CCC_NOTIFY; - split_central_subscribe(conn, &slot->subscribe_params); + switch (params->type) { + case BT_GATT_DISCOVER_CHARACTERISTIC: + const struct bt_uuid *chrc_uuid = ((struct bt_gatt_chrc *)attr->user_data)->uuid; + + if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID)) == + 0) { + LOG_DBG("Found position state characteristic"); + slot->subscribe_params.disc_params = &slot->sub_discover_params; + slot->subscribe_params.end_handle = slot->discover_params.end_handle; + slot->subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + slot->subscribe_params.notify = split_central_notify_func; + slot->subscribe_params.value = BT_GATT_CCC_NOTIFY; + split_central_subscribe(conn, &slot->subscribe_params); #if ZMK_KEYMAP_HAS_SENSORS - } else if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID)) == - 0) { - slot->discover_params.uuid = NULL; - slot->discover_params.start_handle = attr->handle + 2; - slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; - - slot->sensor_subscribe_params.disc_params = &slot->sub_discover_params; - slot->sensor_subscribe_params.end_handle = slot->discover_params.end_handle; - slot->sensor_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); - slot->sensor_subscribe_params.notify = split_central_sensor_notify_func; - slot->sensor_subscribe_params.value = BT_GATT_CCC_NOTIFY; - split_central_subscribe(conn, &slot->sensor_subscribe_params); + } else if (bt_uuid_cmp(chrc_uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID)) == 0) { + slot->discover_params.uuid = NULL; + slot->discover_params.start_handle = attr->handle + 2; + slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + slot->sensor_subscribe_params.disc_params = &slot->sub_discover_params; + slot->sensor_subscribe_params.end_handle = slot->discover_params.end_handle; + slot->sensor_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + slot->sensor_subscribe_params.notify = split_central_sensor_notify_func; + slot->sensor_subscribe_params.value = BT_GATT_CCC_NOTIFY; + split_central_subscribe(conn, &slot->sensor_subscribe_params); #endif /* ZMK_KEYMAP_HAS_SENSORS */ - } else if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID)) == - 0) { - LOG_DBG("Found run behavior handle"); - slot->discover_params.uuid = NULL; - slot->discover_params.start_handle = attr->handle + 2; - slot->run_behavior_handle = bt_gatt_attr_value_handle(attr); - } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, - BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID))) { - LOG_DBG("Found select physical layout handle"); - slot->selected_physical_layout_handle = bt_gatt_attr_value_handle(attr); - k_work_submit(&update_peripherals_selected_layouts_work); +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + } else if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_INPUT_EVENT_UUID)) == + 0) { + LOG_DBG("Found an input characteristic"); + struct peripheral_input_slot *input_slot; + int ret = reserve_next_open_input_slot(&input_slot, conn); + if (ret < 0) { + LOG_WRN("No available slot for peripheral input subscriptions (%d)", ret); + + slot->discover_params.uuid = NULL; + slot->discover_params.start_handle = attr->handle + 1; + slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + } else { + LOG_DBG("Reserved a slot for the input subscription"); + input_slot->sub.value_handle = bt_gatt_attr_value_handle(attr); + + slot->discover_params.uuid = gatt_ccc_uuid; + slot->discover_params.start_handle = attr->handle; + slot->discover_params.type = BT_GATT_DISCOVER_STD_CHAR_DESC; + } +#endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + } else if (bt_uuid_cmp(chrc_uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID)) == 0) { + LOG_DBG("Found run behavior handle"); + slot->discover_params.uuid = NULL; + slot->discover_params.start_handle = attr->handle + 2; + slot->run_behavior_handle = bt_gatt_attr_value_handle(attr); + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID))) { + LOG_DBG("Found select physical layout handle"); + slot->selected_physical_layout_handle = bt_gatt_attr_value_handle(attr); + k_work_submit(&update_peripherals_selected_layouts_work); #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, - BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID))) { - LOG_DBG("Found update HID indicators handle"); - slot->update_hid_indicators = bt_gatt_attr_value_handle(attr); + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID))) { + LOG_DBG("Found update HID indicators handle"); + slot->update_hid_indicators = bt_gatt_attr_value_handle(attr); #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) - } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, - BT_UUID_BAS_BATTERY_LEVEL)) { - LOG_DBG("Found battery level characteristics"); - slot->batt_lvl_subscribe_params.disc_params = &slot->sub_discover_params; - slot->batt_lvl_subscribe_params.end_handle = slot->discover_params.end_handle; - slot->batt_lvl_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); - slot->batt_lvl_subscribe_params.notify = split_central_battery_level_notify_func; - slot->batt_lvl_subscribe_params.value = BT_GATT_CCC_NOTIFY; - split_central_subscribe(conn, &slot->batt_lvl_subscribe_params); - - slot->batt_lvl_read_params.func = split_central_battery_level_read_func; - slot->batt_lvl_read_params.handle_count = 1; - slot->batt_lvl_read_params.single.handle = bt_gatt_attr_value_handle(attr); - slot->batt_lvl_read_params.single.offset = 0; - bt_gatt_read(conn, &slot->batt_lvl_read_params); + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_BAS_BATTERY_LEVEL)) { + LOG_DBG("Found battery level characteristics"); + slot->batt_lvl_subscribe_params.disc_params = &slot->sub_discover_params; + slot->batt_lvl_subscribe_params.end_handle = slot->discover_params.end_handle; + slot->batt_lvl_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + slot->batt_lvl_subscribe_params.notify = split_central_battery_level_notify_func; + slot->batt_lvl_subscribe_params.value = BT_GATT_CCC_NOTIFY; + split_central_subscribe(conn, &slot->batt_lvl_subscribe_params); + + slot->batt_lvl_read_params.func = split_central_battery_level_read_func; + slot->batt_lvl_read_params.handle_count = 1; + slot->batt_lvl_read_params.single.handle = bt_gatt_attr_value_handle(attr); + slot->batt_lvl_read_params.single.offset = 0; + bt_gatt_read(conn, &slot->batt_lvl_read_params); #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ + } + break; + case BT_GATT_DISCOVER_STD_CHAR_DESC: +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + if (bt_uuid_cmp(slot->discover_params.uuid, BT_UUID_GATT_CCC) == 0) { + LOG_DBG("Found input CCC descriptor"); + struct peripheral_input_slot *input_slot; + int ret = find_pending_input_slot(&input_slot, conn); + if (ret < 0) { + LOG_DBG("No pending input slot (%d)", ret); + slot->discover_params.uuid = NULL; + slot->discover_params.start_handle = attr->handle + 1; + slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + } else { + LOG_DBG("Found pending input slot"); + input_slot->sub.ccc_handle = attr->handle; + + slot->discover_params.uuid = gatt_cpf_uuid; + slot->discover_params.start_handle = attr->handle + 1; + slot->discover_params.type = BT_GATT_DISCOVER_STD_CHAR_DESC; + } + } else if (bt_uuid_cmp(slot->discover_params.uuid, BT_UUID_GATT_CPF) == 0) { + LOG_DBG("Found input CPF descriptor"); + struct bt_gatt_cpf *cpf = attr->user_data; + struct peripheral_input_slot *input_slot; + int ret = find_pending_input_slot(&input_slot, conn); + if (ret < 0) { + LOG_DBG("No pending input slot (%d)", ret); + } else { + LOG_DBG("Found pending input slot"); + input_slot->reg = cpf->description; + input_slot->sub.notify = peripheral_input_event_notify_cb; + input_slot->sub.value = BT_GATT_CCC_NOTIFY; + int err = split_central_subscribe(conn, &input_slot->sub); + if (err < 0) { + LOG_WRN("Failed to subscribe to input notifications %d", err); + } + } + + slot->discover_params.uuid = NULL; + slot->discover_params.start_handle = attr->handle + 1; + slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + } +#endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + break; } bool subscribed = slot->run_behavior_handle && slot->subscribe_params.value_handle && @@ -528,6 +723,14 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) subscribed = subscribed && slot->batt_lvl_subscribe_params.value_handle; #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + for (size_t i = 0; i < ARRAY_SIZE(peripheral_input_slots); i++) { + if (input_slot_is_open(i) || input_slot_is_pending(i)) { + subscribed = false; + break; + } + } +#endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; } @@ -779,6 +982,10 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { k_work_submit(&peripheral_batt_lvl_work); #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + release_peripheral_input_subs(conn); +#endif + err = release_peripheral_slot_for_conn(conn); if (err < 0) { diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index 9529d51613c..def883b0c08 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -175,6 +176,45 @@ static ssize_t split_svc_get_selected_phys_layout(struct bt_conn *conn, return bt_gatt_attr_read(conn, attrs, buf, len, offset, &selected, sizeof(selected)); } +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + +static void split_input_events_ccc(const struct bt_gatt_attr *attr, uint16_t value) { + LOG_DBG("value %d", value); +} + +// Duplicated from Zephyr, since it is internal there +struct gatt_cpf { + uint8_t format; + int8_t exponent; + uint16_t unit; + uint8_t name_space; + uint16_t description; +} __packed; + +ssize_t bt_gatt_attr_read_input_split_cpf(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + uint16_t reg = (uint16_t)(uint32_t)attr->user_data; + struct gatt_cpf value; + + value.format = 0x1B; // Struct + value.exponent = 0; + value.unit = sys_cpu_to_le16(0x2700); // Unitless + value.name_space = 0x01; // Bluetooth SIG + value.description = sys_cpu_to_le16(reg); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &value, sizeof(value)); +} + +#define INPUT_SPLIT_CHARS(node_id) \ + BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_INPUT_EVENT_UUID), \ + BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, NULL, NULL, NULL), \ + BT_GATT_CCC(split_input_events_ccc, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), \ + BT_GATT_DESCRIPTOR(BT_UUID_GATT_CPF, BT_GATT_PERM_READ, bt_gatt_attr_read_input_split_cpf, \ + NULL, (void *)DT_REG_ADDR(node_id)), + +#endif + BT_GATT_SERVICE_DEFINE( split_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID)), BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID), @@ -192,10 +232,11 @@ BT_GATT_SERVICE_DEFINE( split_svc_sensor_state, NULL, &last_sensor_event), BT_GATT_CCC(split_svc_sensor_state_ccc, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), #endif /* ZMK_KEYMAP_HAS_SENSORS */ + DT_FOREACH_STATUS_OKAY(zmk_input_split, INPUT_SPLIT_CHARS) #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) - BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID), - BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, - split_svc_update_indicators, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID), + BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, + split_svc_update_indicators, NULL), #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID), BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ, @@ -306,6 +347,29 @@ int zmk_split_bt_sensor_triggered(uint8_t sensor_index, } #endif /* ZMK_KEYMAP_HAS_SENSORS */ +#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + +int zmk_split_bt_report_input(uint8_t reg, uint8_t type, uint16_t code, int32_t value, bool sync) { + + for (size_t i = 0; i < split_svc.attr_count; i++) { + if (bt_uuid_cmp(split_svc.attrs[i].uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_INPUT_EVENT_UUID)) == 0 && + (uint8_t)(uint32_t)split_svc.attrs[i + 2].user_data == reg) { + struct zmk_split_input_event_payload payload = { + .type = type, + .code = code, + .value = value, + .sync = sync ? 1 : 0, + }; + + return bt_gatt_notify(NULL, &split_svc.attrs[i], &payload, sizeof(payload)); + } + } + return -ENODEV; +} + +#endif /* IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) */ + static int service_init(void) { static const struct k_work_queue_config queue_config = { .name = "Split Peripheral Notification Queue"}; diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c index 9db10952c95..945ae989007 100644 --- a/app/src/usb_hid.c +++ b/app/src/usb_hid.c @@ -13,9 +13,15 @@ #include #include #include + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) +#include +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + #if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) #include #endif // IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) + #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -56,31 +62,56 @@ static uint8_t *get_keyboard_report(size_t *len) { static int get_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, uint8_t **data) { + switch (setup->wValue & HID_GET_REPORT_TYPE_MASK) { + case HID_REPORT_TYPE_FEATURE: + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + case ZMK_HID_REPORT_ID_MOUSE: + static struct zmk_hid_mouse_resolution_feature_report res_feature_report; - /* - * 7.2.1 of the HID v1.11 spec is unclear about handling requests for reports that do not exist - * For requested reports that aren't input reports, return -ENOTSUP like the Zephyr subsys does - */ - if ((setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_INPUT) { - LOG_ERR("Unsupported report type %d requested", (setup->wValue & HID_GET_REPORT_TYPE_MASK) - << 8); - return -ENOTSUP; - } + struct zmk_endpoint_instance endpoint = { + .transport = ZMK_TRANSPORT_USB, + }; - switch (setup->wValue & HID_GET_REPORT_ID_MASK) { - case ZMK_HID_REPORT_ID_KEYBOARD: { - *data = get_keyboard_report(len); + *len = sizeof(struct zmk_hid_mouse_resolution_feature_report); + struct zmk_pointing_resolution_multipliers mult = + zmk_pointing_resolution_multipliers_get_profile(endpoint); + + res_feature_report.body.wheel_res = mult.wheel; + res_feature_report.body.hwheel_res = mult.hor_wheel; + *data = (uint8_t *)&res_feature_report; + break; +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + default: + return -ENOTSUP; + } break; - } - case ZMK_HID_REPORT_ID_CONSUMER: { - struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report(); - *data = (uint8_t *)report; - *len = sizeof(*report); + case HID_REPORT_TYPE_INPUT: + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { + case ZMK_HID_REPORT_ID_KEYBOARD: { + *data = get_keyboard_report(len); + break; + } + case ZMK_HID_REPORT_ID_CONSUMER: { + struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report(); + *data = (uint8_t *)report; + *len = sizeof(*report); + break; + } + default: + LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); + return -EINVAL; + } break; - } default: - LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); - return -EINVAL; + /* + * 7.2.1 of the HID v1.11 spec is unclear about handling requests for reports that do not + * exist For requested reports that aren't input reports, return -ENOTSUP like the Zephyr + * subsys does + */ + LOG_ERR("Unsupported report type %d requested", (setup->wValue & HID_GET_REPORT_TYPE_MASK) + << 8); + return -ENOTSUP; } return 0; @@ -88,30 +119,55 @@ static int get_report_cb(const struct device *dev, struct usb_setup_packet *setu static int set_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, uint8_t **data) { - if ((setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_OUTPUT) { - LOG_ERR("Unsupported report type %d requested", - (setup->wValue & HID_GET_REPORT_TYPE_MASK) >> 8); - return -ENOTSUP; - } - - switch (setup->wValue & HID_GET_REPORT_ID_MASK) { -#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) - case ZMK_HID_REPORT_ID_LEDS: - if (*len != sizeof(struct zmk_hid_led_report)) { - LOG_ERR("LED set report is malformed: length=%d", *len); - return -EINVAL; - } else { - struct zmk_hid_led_report *report = (struct zmk_hid_led_report *)*data; + switch (setup->wValue & HID_GET_REPORT_TYPE_MASK) { + case HID_REPORT_TYPE_FEATURE: + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + case ZMK_HID_REPORT_ID_MOUSE: + if (*len != sizeof(struct zmk_hid_mouse_resolution_feature_report)) { + return -EINVAL; + } + + struct zmk_hid_mouse_resolution_feature_report *report = + (struct zmk_hid_mouse_resolution_feature_report *)*data; struct zmk_endpoint_instance endpoint = { .transport = ZMK_TRANSPORT_USB, }; - zmk_hid_indicators_process_report(&report->body, endpoint); + + zmk_pointing_resolution_multipliers_process_report(&report->body, endpoint); + + break; +#endif // IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + default: + return -ENOTSUP; } break; + + case HID_REPORT_TYPE_OUTPUT: + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { +#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) + case ZMK_HID_REPORT_ID_LEDS: + if (*len != sizeof(struct zmk_hid_led_report)) { + LOG_ERR("LED set report is malformed: length=%d", *len); + return -EINVAL; + } else { + struct zmk_hid_led_report *report = (struct zmk_hid_led_report *)*data; + struct zmk_endpoint_instance endpoint = { + .transport = ZMK_TRANSPORT_USB, + }; + zmk_hid_indicators_process_report(&report->body, endpoint); + } + break; #endif // IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) + default: + LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); + return -EINVAL; + } + break; default: - LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); - return -EINVAL; + LOG_ERR("Unsupported report type %d requested", + (setup->wValue & HID_GET_REPORT_TYPE_MASK) >> 8); + return -ENOTSUP; } return 0; @@ -164,7 +220,7 @@ int zmk_usb_hid_send_consumer_report(void) { return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_POINTING) int zmk_usb_hid_send_mouse_report() { #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) if (hid_protocol == HID_PROTOCOL_BOOT) { @@ -175,7 +231,7 @@ int zmk_usb_hid_send_mouse_report() { struct zmk_hid_mouse_report *report = zmk_hid_get_mouse_report(); return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); } -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_POINTING) static int zmk_usb_hid_init(void) { hid_dev = device_get_binding("HID_0"); diff --git a/app/tests/mouse-keys/mkp/events.patterns b/app/tests/pointing/mkp/events.patterns similarity index 100% rename from app/tests/mouse-keys/mkp/events.patterns rename to app/tests/pointing/mkp/events.patterns diff --git a/app/tests/mouse-keys/mkp/keycode_events.snapshot b/app/tests/pointing/mkp/keycode_events.snapshot similarity index 100% rename from app/tests/mouse-keys/mkp/keycode_events.snapshot rename to app/tests/pointing/mkp/keycode_events.snapshot diff --git a/app/tests/mouse-keys/mkp/native_posix.keymap b/app/tests/pointing/mkp/native_posix.keymap similarity index 100% rename from app/tests/mouse-keys/mkp/native_posix.keymap rename to app/tests/pointing/mkp/native_posix.keymap diff --git a/app/tests/pointing/mkp/native_posix_64.conf b/app/tests/pointing/mkp/native_posix_64.conf new file mode 100644 index 00000000000..65ed54bb204 --- /dev/null +++ b/app/tests/pointing/mkp/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_MOUSE=y diff --git a/app/tests/mouse-keys/mkp/native_posix_64.keymap b/app/tests/pointing/mkp/native_posix_64.keymap similarity index 100% rename from app/tests/mouse-keys/mkp/native_posix_64.keymap rename to app/tests/pointing/mkp/native_posix_64.keymap diff --git a/app/tests/pointing/mouse-move/move_diagonal/events.patterns b/app/tests/pointing/mouse-move/move_diagonal/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_diagonal/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/move_diagonal/keycode_events.snapshot b/app/tests/pointing/mouse-move/move_diagonal/keycode_events.snapshot new file mode 100644 index 00000000000..6b9fa770b11 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_diagonal/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/pointing/mouse-move/move_diagonal/native_posix_64.conf b/app/tests/pointing/mouse-move/move_diagonal/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_diagonal/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/move_diagonal/native_posix_64.keymap b/app/tests/pointing/mouse-move/move_diagonal/native_posix_64.keymap new file mode 100644 index 00000000000..7e4d7af2a1d --- /dev/null +++ b/app/tests/pointing/mouse-move/move_diagonal/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/move_x/events.patterns b/app/tests/pointing/mouse-move/move_x/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_x/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/move_x/keycode_events.snapshot b/app/tests/pointing/mouse-move/move_x/keycode_events.snapshot new file mode 100644 index 00000000000..678f71c9ac2 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_x/keycode_events.snapshot @@ -0,0 +1,24 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/pointing/mouse-move/move_x/native_posix_64.conf b/app/tests/pointing/mouse-move/move_x/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_x/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/move_x/native_posix_64.keymap b/app/tests/pointing/mouse-move/move_x/native_posix_64.keymap new file mode 100644 index 00000000000..89d50e2b839 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_x/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_RIGHT + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/move_y/events.patterns b/app/tests/pointing/mouse-move/move_y/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_y/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/move_y/keycode_events.snapshot b/app/tests/pointing/mouse-move/move_y/keycode_events.snapshot new file mode 100644 index 00000000000..d20154d5507 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_y/keycode_events.snapshot @@ -0,0 +1,24 @@ +movement_set: Mouse movement set to 0/-1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/pointing/mouse-move/move_y/native_posix_64.conf b/app/tests/pointing/mouse-move/move_y/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/move_y/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/move_y/native_posix_64.keymap b/app/tests/pointing/mouse-move/move_y/native_posix_64.keymap new file mode 100644 index 00000000000..5b02246b05f --- /dev/null +++ b/app/tests/pointing/mouse-move/move_y/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_UP &mmv MOVE_DOWN + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/events.patterns b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/keycode_events.snapshot new file mode 100644 index 00000000000..77ffd6f8d7c --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -4/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -5/-4 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -5/-5 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-5 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.keymap new file mode 100644 index 00000000000..0ec7163f746 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_scaling/native_posix_64.keymap @@ -0,0 +1,37 @@ + +#include +#include + +#include +#include +#include +#include +#include + +&mmv_input_listener { + input-processors = <&zip_xy_scaler 5 3>; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/events.patterns b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/keycode_events.snapshot new file mode 100644 index 00000000000..33bb267b073 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to 1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 3/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 3/3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.keymap new file mode 100644 index 00000000000..51c8e505e26 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_invert/native_posix_64.keymap @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include + +&mmv_input_listener { + input-processors = <&zip_xy_transform (INPUT_TRANSFORM_X_INVERT | INPUT_TRANSFORM_Y_INVERT)>; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/events.patterns b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/keycode_events.snapshot new file mode 100644 index 00000000000..40daa64f0f6 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to 0/-1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.keymap new file mode 100644 index 00000000000..be3e9f73d6e --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/move_diagonal_xy_swap/native_posix_64.keymap @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + +&mmv_input_listener { + input-processors = <&zip_xy_swap_mapper>; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/events.patterns b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/events.patterns new file mode 100644 index 00000000000..e9f2583e0d9 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/events.patterns @@ -0,0 +1,5 @@ +s/.*hid_mouse_//p +s/.*set_layer_state: //p +s/.*handle_state_changed_dispatcher: //p +s/.*handle_position_state_changed: //p +s/.*handle_keycode_state_changed: //p diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/keycode_events.snapshot new file mode 100644 index 00000000000..e2b9544e54c --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/keycode_events.snapshot @@ -0,0 +1,14 @@ +layer_changed: layer 1 state 1 +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +layer_changed: layer 1 state 0 diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.keymap new file mode 100644 index 00000000000..166427675a5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/1-deactivate-layer-timeout/native_posix_64.keymap @@ -0,0 +1,41 @@ + +#include +#include + +#include +#include +#include +#include +#include + +&mmv_input_listener { + input-processors = <&zip_temp_layer 1 500>; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + + mkp_layer { + bindings = <&mkp LCLK &mkp RCLK &trans &trans>; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,150) + ZMK_MOCK_PRESS(1,0,200) + ZMK_MOCK_RELEASE(1,0,250) + >; +}; diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/events.patterns b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/events.patterns new file mode 100644 index 00000000000..27695039e11 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/events.patterns @@ -0,0 +1,7 @@ +s/.*hid_mouse_//p +s/.*set_layer_state: //p +s/.*hid_listener_keycode/kp/p +s/.*mo_keymap_binding/mo/p +s/.*handle_state_changed_dispatcher: //p +s/.*handle_position_state_changed: //p +s/.*handle_keycode_state_changed: //p diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/keycode_events.snapshot new file mode 100644 index 00000000000..b421bad2801 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/keycode_events.snapshot @@ -0,0 +1,21 @@ +Dispatching handle_position_state_changed +Position excluded, continuing +layer_changed: layer 1 state 1 +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +Dispatching handle_position_state_changed +Dispatching handle_position_state_changed +Position not excluded, deactivating layer +layer_changed: layer 1 state 0 +Position excluded, continuing +Dispatching handle_position_state_changed diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.keymap new file mode 100644 index 00000000000..5f3c0dbfa1a --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2a-deactivate-layer-position-trigger/native_posix_64.keymap @@ -0,0 +1,46 @@ + +#include +#include + +#include +#include +#include +#include +#include + +&mmv_input_listener { + input-processors = <&auto_mouse_layer 1 0>; +}; + +/ { + auto_mouse_layer: auto_mouse_layer { + compatible = "zmk,input-processor-temp-layer"; + #input-processor-cells = <2>; + excluded-positions = <0>; + }; + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + + mkp_layer { + bindings = <&mkp LCLK &mkp RCLK &trans &trans>; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,150) + ZMK_MOCK_PRESS(1,0,200) + ZMK_MOCK_RELEASE(1,0,250) + >; +}; diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/events.patterns b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/events.patterns new file mode 100644 index 00000000000..27695039e11 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/events.patterns @@ -0,0 +1,7 @@ +s/.*hid_mouse_//p +s/.*set_layer_state: //p +s/.*hid_listener_keycode/kp/p +s/.*mo_keymap_binding/mo/p +s/.*handle_state_changed_dispatcher: //p +s/.*handle_position_state_changed: //p +s/.*handle_keycode_state_changed: //p diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/keycode_events.snapshot new file mode 100644 index 00000000000..3cf641daa98 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/keycode_events.snapshot @@ -0,0 +1,28 @@ +Dispatching handle_position_state_changed +Position excluded, continuing +layer_changed: layer 1 state 1 +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +Dispatching handle_position_state_changed +Dispatching handle_position_state_changed +Position excluded, continuing +button_press: Button 0 count 1 +button_press: Mouse buttons set to 0x01 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +Dispatching handle_position_state_changed +button_release: Button 0 count: 0 +button_release: Button 0 released +button_release: Mouse buttons set to 0x00 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.keymap new file mode 100644 index 00000000000..675a8bce268 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/2b-deactivate-layer-position-not-trigger/native_posix_64.keymap @@ -0,0 +1,46 @@ + +#include +#include + +#include +#include +#include +#include +#include + +&mmv_input_listener { + input-processors = <&auto_mouse_layer 1 0>; +}; + +/ { + auto_mouse_layer: auto_mouse_layer { + compatible = "zmk,input-processor-temp-layer"; + #input-processor-cells = <2>; + excluded-positions = <0>; + }; + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + + mkp_layer { + bindings = <&mkp LCLK &mkp RCLK &trans &trans>; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,150) + ZMK_MOCK_PRESS(0,0,200) + ZMK_MOCK_RELEASE(0,0,250) + >; +}; diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/events.patterns b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/events.patterns new file mode 100644 index 00000000000..ae24cce016d --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/events.patterns @@ -0,0 +1,6 @@ +s/.*hid_mouse_//p +s/.*set_layer_state: //p +s/.*hid_listener_keycode/kp/p +s/.*handle_state_changed_dispatcher: //p +s/.*handle_position_state_changed: //p +s/.*handle_keycode_state_changed: //p diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/keycode_events.snapshot b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/keycode_events.snapshot new file mode 100644 index 00000000000..57d5dfc8c22 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/keycode_events.snapshot @@ -0,0 +1,148 @@ +Dispatching handle_keycode_state_changed +Setting last_tapped_timestamp to: 11 +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +Dispatching handle_keycode_state_changed +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -4/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -4/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +layer_changed: layer 1 state 1 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -4/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -4/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -5/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -5/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -6/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -7/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -7/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -8/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -8/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -8/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -10/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -9/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +layer_changed: layer 1 state 0 diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.conf b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.conf new file mode 100644 index 00000000000..fa514727ba5 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=n +CONFIG_ZMK_BLE=n +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=n +CONFIG_ZMK_LOG_LEVEL_DBG=y +CONFIG_ZMK_POINTING=y diff --git a/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.keymap b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.keymap new file mode 100644 index 00000000000..e641588acd3 --- /dev/null +++ b/app/tests/pointing/mouse-move/processors/temp_layer/3-require-prior-idle-ms/native_posix_64.keymap @@ -0,0 +1,50 @@ + +#include +#include + +#include +#include +#include +#include +#include + +&mmv_input_listener { + input-processors = <&auto_mouse_layer 1 500>; +}; + +/ { + auto_mouse_layer: auto_mouse_layer { + compatible = "zmk,input-processor-temp-layer"; + #input-processor-cells = <2>; + require-prior-idle-ms = <500>; + }; + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &kp A &none + >; + }; + + mkp_layer { + bindings = <&mkp LCLK &mkp RCLK &trans &trans>; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,100) + /* before idle */ + ZMK_MOCK_PRESS(0,0,150) + ZMK_MOCK_RELEASE(0,0,200) + /* after idle */ + ZMK_MOCK_PRESS(0,0,700) + ZMK_MOCK_RELEASE(0,0,800) + >; +}; diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index 527d73c6bae..e528325aeb9 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -282,3 +282,23 @@ Applies to: `compatible = "zmk,behavior-tap-dance"` | `#binding-cells` | int | Must be `<0>` | | | `bindings` | phandle array | A list of behaviors from which to select | | | `tapping-term-ms` | int | The maximum time (in milliseconds) between taps before an item from `bindings` is triggered. | 200 | + +## Two Axis Input + +This behavior is part of the core [pointing devices](../features/pointing.md) feature, and is used to generate X/Y and scroll input events. It is the underlying behavior used for the mouse [move](../keymaps/behaviors/mouse-emulation.md#mouse-move) and [scroll](../keymaps/behaviors/mouse-emulation.md#mouse-scroll) behaviors. + +### Devicetree + +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-input-two-axis.yaml) + +Applies to: `compatible = "zmk,behavior-input-two-axis"` + +| Property | Type | Description | Default | +| ----------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `#binding-cells` | int | Must be `<1>` | | +| `x-input-code` | int | The [relative event code](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L245) for generated input events for the X-axis. | | +| `y-input-code` | int | The [relative event code](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L245) for generated input events for the Y-axis. | | +| `trigger-period-ms` | int | How many milliseconds between generated input events based on the current speed/direction. | 16 | +| `delay-ms` | int | How many milliseconds to delay any processing or event generation when first pressed. | 0 | +| `time-to-max-speed-ms` | int | How many milliseconds it takes to accelerate to the curren max speed. | 0 | +| `acceleration-exponent` | int | The acceleration exponent to apply: `0` - uniform speed, `1` - uniform acceleration, `2` - linear acceleration | 1 | diff --git a/docs/docs/config/pointing.md b/docs/docs/config/pointing.md new file mode 100644 index 00000000000..167ad11b07f --- /dev/null +++ b/docs/docs/config/pointing.md @@ -0,0 +1,67 @@ +--- +title: Pointing Device Configuration +sidebar_label: Pointing +--- + +These are settings related to the pointing device/mouse support in ZMK. + +See [Configuration Overview](index.md) for instructions on how to change these settings. + +## Kconfig + +Definition file: [zmk/app/pointing/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/pointing/Kconfig) + +### General + +| Config | Type | Description | Default | +| -------------------------------------- | ---- | -------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_POINTING` | bool | Enable the general pointing/mouse functionality | n | +| `CONFIG_ZMK_POINTING_SMOOTH_SCROLLING` | bool | Enable smooth scrolling HID functionality (via HID Resolution Multipliers) | n | + +### Advanced Settings + +The following settings are from Zephyr and should be defaulted to sane values, but can be adjusted if you encounter problems. + +| Config | Type | Description | Default | +| -------------------------------- | ---- | ---------------------------------------------------------- | ------------------------------- | +| `CONFIG_INPUT_THREAD_STACK_SIZE` | int | Stack size for the dedicated input event processing thread | 512 (1024 on split peripherals) | + +## Input Listener + +The following documents settings related to [input listeners](../features/pointing.md#input-listeners). + +### Devicetree + +Applies to: `compatible = "zmk,input-listener"` + +Definition file: [zmk/app/dts/bindings/zmk,input-listener.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cinput-listener.yaml) + +| Property | Type | Description | +| ------------------ | ------------- | ------------------------------------------------------------------- | +| `device` | phandle | Input device handle | +| `input-processors` | phandle-array | List of input processors (with parameters) to apply to input events | + +#### Child Properties + +Additional properties can be set on child nodes, which allows changing the settings when certain layers are enabled: + +| Property | Type | Description | +| ------------------ | ------------- | ------------------------------------------------------------------------------------------ | +| `layers` | array | List of layer indexes. This config will apply if any layer in the list is active. | +| `input-processors` | phandle-array | List of input processors (with parameters) to apply to input events | +| `process-next` | bool | Whether to continue applying other input processors after this override if it takes effect | + +## Input Split + +Input splits are used for [pointing devices on split peripherals](../development/hardware-integration/pointing.mdx#split). + +### Devicetree + +Applies to: `compatible = "zmk,input-split"` + +Definition file: [zmk/app/dts/bindings/zmk,input-split.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cinput-split.yaml) + +| Property | Type | Description | +| ------------------ | ------------- | ------------------------------------------------------------------- | +| `device` | handle | Input device handle | +| `input-processors` | phandle-array | List of input processors (with parameters) to apply to input events | diff --git a/docs/docs/development/hardware-integration/pointing.mdx b/docs/docs/development/hardware-integration/pointing.mdx new file mode 100644 index 00000000000..df7dfaefb8c --- /dev/null +++ b/docs/docs/development/hardware-integration/pointing.mdx @@ -0,0 +1,168 @@ +--- +title: Pointing Devices +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +ZMK's pointing device support builds upon the Zephyr [input API](https://docs.zephyrproject.org/3.5.0/services/input/index.html) to offer pointing/mouse functionality with various hardware. A limited number of input drivers are available in the Zephyr version currently used by ZMK, but additional drivers can be found in [external modules](../../features/modules.mdx) for a variety of hardware. + +The details will depend on if you are adding a pointing device to a [split peripheral](../../features/split-keyboards.md#central-and-peripheral-roles) as opposed to a unibody keyboard or split central part: + + + + +## Input Device + +First, we must define the pointing device itself. The specifics of where this node goes will depend on the specific hardware. _Most_ pointing hardware uses either SPI or I2C for communication, and will be nested under a properly configured bus node, e.g. `&pro_micro_i2c` or for a complete onboard setup, `&i2c3`. See the documentation on [pin control](./pinctrl.mdx) if you need to configure the pins for an I2C or SPI bus. + +For example, if setting up an [SPI device](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/dts/bindings/spi/spi-device.yaml), you may have a node like: + +```dts +&pro_micro_spi { + status = "okay"; + cs-gpios = <&pro_micro 19 GPIO_ACTIVE_LOW>; + + glidepoint: glidepoint@0 { + compatible = "cirque,pinnacle"; + reg = <0>; + spi-max-frequency = <1000000>; + status = "okay"; + dr-gpios = <&pro_micro 5 (GPIO_ACTIVE_HIGH)>; + + sensitivity = "4x"; + sleep; + no-taps; + }; +}; +``` + +The specifics of the properties required to set for a given driver will vary; always consult the devicetree bindings file for the specific driver to see what properties can be set. + +## Listener + +Every input device needs an associated listener added that listens for events from the device and processes them before sending the events to the host using a HID mouse report. See [input listener configuration](../../config/pointing.md#input-listener) for the full details. For example, to add a listener for the above device: + +```dts +/ { + glidepoint_listener { + compatible = "zmk,input-listener"; + device = <&glidepoint>; + }; +}; +``` + +## Input Processors + +Some physical pointing devices may be generating input events that need adjustment before being sent to hosts. For example a trackpad might be integrated into a keyboard rotated 90° and need the X/Y data adjusted appropriately. This can be accomplished with [input processors](../../keymaps/input-processors/index.md). As an example, you could enhance the above listener with the following input processor that inverts and swaps the X/Y axes: + +```dts +#include + +/ { + glidepoint_listener { + compatible = "zmk,input-listener"; + device = <&glidepoint>; + input-processors = <&zip_xy_transform (INPUT_TRANSFORM_XY_SWAP | INPUT_TRANSFORM_X_INVERT | INPUT_TRANSFORM_Y_INVERT)>; + }; +}; +``` + + + + +## Split + +Pointing devices are supported on split peripherals, with some additional configuration using the [input split device](../../config/pointing.md#input-split). All split pointers are identified using a unique integer value, which is specified using the `reg` property and in the `@#` suffix for the node. If adding multiple peripheral pointers, be sure that each is given a unique identifier. + +### Shared + +Both peripheral and central make use of a `zmk,input-split` device, which functions differently depending on where it is used. To avoid duplicating work, this node can be defined in a common `.dtsi` file that is included into both central and peripheral `.overlay`/`.dts` files. Second, the input listener for the central side is added here, but disabled, so that keymaps (which are included for central and peripheral builds) can reference the listener to add input processors without issue. + +:::note + +Input splits need to be nested under a parent node that properly sets `#address-cells = <1>` and `#size-cells = <0>`. These settings are what allow us to use a single integer number for the `reg` value. + +::: + +```dts +/ { + split_inputs { + #address-cells = <1>; + #size-cells = <0>; + + glidepoint_split: glidepoint_split@0 { + compatible = "zmk,input-split"; + reg = <0>; + }; + }; + + glidepoint_listener: glidepoint_listener { + compatible = "zmk,input-listener"; + status = "disabled"; + device = <&glidepoint_split>; + }; +}; +``` + +### Peripheral + +In the peripheral .overlay/.dts file, we do the following: + +- Include the shared .dtsi file. +- Add the device node for the physical pointer. +- Update the input split with a reference to the new device node that should be proxied. + +```dts +#include "common.dtsi" + +&pro_micro_spi { + status = "okay"; + cs-gpios = <&pro_micro 19 GPIO_ACTIVE_LOW>; + + glidepoint: glidepoint@0 { + compatible = "cirque,pinnacle"; + reg = <0>; + spi-max-frequency = <1000000>; + status = "okay"; + dr-gpios = <&pro_micro 5 (GPIO_ACTIVE_HIGH)>; + + sensitivity = "4x"; + sleep; + no-taps; + }; +}; + +&glidepoint_split { + device = <&glidepoint>; + + input-processors = <&zip_xy_transform (INPUT_TRANSFORM_XY_SWAP | INPUT_TRANSFORM_X_INVERT | INPUT_TRANSFORM_Y_INVERT)>; +}; +``` + +The `input-processors` property on the input split is optional, and only necessary if the input needs to be fixed up before it is sent to the central. + +The specifics of where the pointing device node goes will depend on the specific hardware. _Most_ pointing hardware uses either SPI or I2C for communication, and will be nested under a properly configured bus node, e.g. `&pro_micro_i2c` or for a complete onboard setup, `&i2c3`. See the documentation on [pin control](./pinctrl.mdx) if you need to configure the pins for an I2C or SPI bus. + +The specifics of the properties required to set for a given driver will vary; always consult the devicetree bindings file for the specific driver to see what properties can be set. + +### Central + +On the central, the input split acts as an input device, receiving events from the peripheral and raising them locally. First, include the shared file, and then enabled the [input listener](#listener) that is created, but disabled, in our shared file: + +```dts +#include "common.dtsi" + +&glidepoint_listener { + status = "okay"; +}; +``` + + + diff --git a/docs/docs/features/pointing.md b/docs/docs/features/pointing.md new file mode 100644 index 00000000000..b22485ab4e1 --- /dev/null +++ b/docs/docs/features/pointing.md @@ -0,0 +1,39 @@ +--- +title: Pointing Devices +--- + +ZMK supports physical pointing devices, as well as [mouse emulation behaviors](../keymaps/behaviors/mouse-emulation.md) for sending HID pointing events to hosts. + +## Configuration + +To enable the pointer functionality, you must set `CONFIG_ZMK_POINTING=y` in your config. See the [pointer configuration](../config/pointing.md) for the full details. + +:::warning + +When enabling the feature, changes are made to the HID report descriptor which some hosts may not pick up automatically over BLE. Be sure to [remove and re-pair to your hosts](bluetooth.md#refreshing-the-hid-descriptor) once you enable the feature. + +::: + +## Mouse Emulation + +Mouse emulation allows you to use your keyboard as a pointing device without using dedicated pointer hardware, like an integrated trackpad, trackball, etc. By adding new bindings in your keymap like `&mmv MOVE_UP` you can make key presses send mouse HID events to your connected hosts. + +See the [mouse emulation behaviors](../keymaps/behaviors/mouse-emulation.md) for details. + +## Physical Pointing Devices + +There are a few drivers available for supporting physical pointing devices integrated into a ZMK powered device. When doing so, you can use your device as both a keyboard and a pointing device with any connected hosts. The functionality can be extended further, e.g. slow mode, scroll mode, temporary mouse layers, etc. by configuring [input processors](#input-processors) linked to the physical pointing device. + +For more information, refer to the [pointer hardware integration](../development/hardware-integration/pointing.mdx) documentation. + +## Input Processors + +Input processors are small pieces of functionality that process and optionally modify events generated from emulated and physical pointing devices. Processors can do things like scaling movement values to make them larger or smaller for detailed work, swapping the event types to turn movements into scroll events, or temporarily enabling an extra layer while the pointer is in use. + +For more details, see the [input processors](../keymaps/input-processors/index.md) section of the keymap documentation. + +## Input Listeners + +Listeners are the key piece that integrate the low level input devices to the rest of the ZMK system. In particular, listeners subscribe to input events from the linked device, and when a given event occurs (e.g. X/Y movement), apply any input processors before sending those events to the HID system for notification to the host. The main way to modify the way a pointer behaves is by configuring the input processors for a given listener. + +For more details on assigning processors to your listeners, see the [input processor usage](../keymaps/input-processors/usage.md) documentation. diff --git a/docs/docs/intro.md b/docs/docs/intro.md index d8d992e7663..d5bf527ca5d 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -33,7 +33,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | One Shot Keys | ✅ | ✅ | ✅ | | [Combo Keys](keymaps/combos.md) | ✅ | | ✅ | | [Macros](keymaps/behaviors/macros.md) | ✅ | ✅ | ✅ | -| Mouse Keys | 🚧 | ✅ | ✅ | +| Mouse Keys | ✅ | ✅ | ✅ | | Low Active Power Usage | ✅ | | | | Low Power Sleep States | ✅ | ✅ | | | [Low Power Mode (VCC Shutoff)](keymaps/behaviors/power.md) | ✅ | ✅ | | diff --git a/docs/docs/keymaps/behaviors/index.mdx b/docs/docs/keymaps/behaviors/index.mdx index c9d0fbe49c7..53ed4200c54 100644 --- a/docs/docs/keymaps/behaviors/index.mdx +++ b/docs/docs/keymaps/behaviors/index.mdx @@ -43,6 +43,8 @@ Below is a summary of pre-defined behavior bindings and user-definable behaviors | Binding | Behavior | Description | | ------- | ----------------------------------------------------------- | ------------------------------- | | `&mkp` | [Mouse Button Press](mouse-emulation.md#mouse-button-press) | Emulates pressing mouse buttons | +| `&mmv` | [Mouse Move](mouse-emulation.md#mouse-move) | Emulates mouse movement | +| `&msc` | [Mouse Scroll](mouse-emulation.md#mouse-scroll) | Emulates mouse scrolling | ## Reset Behaviors diff --git a/docs/docs/keymaps/behaviors/mouse-emulation.md b/docs/docs/keymaps/behaviors/mouse-emulation.md index 26bef8ccce8..a91c24b3788 100644 --- a/docs/docs/keymaps/behaviors/mouse-emulation.md +++ b/docs/docs/keymaps/behaviors/mouse-emulation.md @@ -5,8 +5,7 @@ sidebar_label: Mouse Emulation ## Summary -Mouse emulation behaviors send mouse events. Currently, only mouse button presses are supported, but movement -and scroll action support is planned for the future. +Mouse emulation behaviors send mouse events, including mouse button presses, cursor movement and scrolling. :::warning[Refreshing the HID descriptor] @@ -17,17 +16,15 @@ The mouse functionality will not work over BLE until that is done. ## Configuration Option -This feature can be enabled or disabled explicitly via a config option: +To use any of the behaviors documented here, the ZMK mouse feature must be enabled explicitly via a config option: ``` -CONFIG_ZMK_MOUSE=y +CONFIG_ZMK_POINTING=y ``` -If you use the mouse key press behavior in your keymap, the feature will automatically be enabled for you. +## Mouse Emulation Defines -## Mouse Button Defines - -To make it easier to encode the HID mouse button numeric values, include +To make it easier to encode the HID mouse button and move/scroll speed numeric values, 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: @@ -35,6 +32,15 @@ provided by ZMK near the top: #include ``` +Should you wish to override the default movement or scrolling max velocities, you can define the defaults before including the header, e.g.: + +```c +#define ZMK_POINTING_DEFAULT_MOVE_VAL 1500 // default: 600 +#define ZMK_POINTING_DEFAULT_SCRL_VAL 20 // default: 10 + +#include +``` + ## Mouse Button Press This behavior can press/release up to 5 mouse buttons. @@ -69,3 +75,133 @@ This example will send press of the fourth mouse button when the binding is trig ``` &mkp MB4 ``` + +### Input Processors + +If you want to apply any [input processors](../input-processors/index.md#input-processors-overview) to `&mkp` you can do so by referencing `&mkp_input_listener`, e.g.: + +```dts +&mkp_input_listener { + input-processors = <&zip_temp_layer 2 2000>; +} +``` + +## Mouse Move + +This behavior sends mouse X/Y movement events to the connected host. + +### Behavior Binding + +- Reference: `&mmv` +- Parameter: A `uint32` with 16-bits each used for vertical and horizontal max velocity. + +The following predefined values can be passed for the parameter: + +| Define | Action | +| :----------- | :--------- | +| `MOVE_UP` | Move up | +| `MOVE_DOWN` | Move down | +| `MOVE_LEFT` | Move left | +| `MOVE_RIGHT` | Move right | + +Additionally, if you want to pass a different max speed than the default for the `MOVE_*` defines, custom X and Y velocity values can be passed with `MOVE_X` and `MOVE_Y`, e.g. `MOVE_X(100)` or `MOVE_Y(-100)`. Positive values indicate movement directions right or down. Note that the default value of the max speed depends on [the value of `ZMK_POINTING_DEFAULT_MOVE_VAL`](#mouse-emulation-defines). + +### Examples + +The following will send a down mouse movement event to the host when pressed/held: + +``` +&mmv MOVE_DOWN +``` + +The following will send a left mouse movement event to the host when pressed/held: + +``` +&mmv MOVE_LEFT +``` + +### Input Processors + +If you want to apply any [input processors](../input-processors/index.md#input-processors-overview) to `&mmv` you can do so by referencing `&mmv_input_listener`, e.g.: + +```dts +&mmv_input_listener { + input-processors = <&zip_temp_layer 2 2000>; +} +``` + +## Mouse Scroll + +This behavior sends vertical and horizontal scroll events to the connected host. + +### Behavior Binding + +- Reference: `&msc` +- Parameter: A `uint32` with 16-bits each used for vertical and horizontal velocity. + +The following defines can be passed for the parameter: + +| Define | Action | +| :----------- | :----------- | +| `SCRL_UP` | Scroll up | +| `SCRL_DOWN` | Scroll down | +| `SCRL_LEFT` | Scroll left | +| `SCRL_RIGHT` | Scroll right | + +Additionally, if you want to pass a different max speed than the default for the `SCRL_*` defines, custom X and Y velocity values can be passed with `MOVE_X` and `MOVE_Y`, e.g. `MOVE_X(5)` or `MOVE_Y(-5)`. Positive values indicate scroll directions right or up. Note that the default value of the max speed depends on [the value of `ZMK_POINTING_DEFAULT_SCRL_VAL`](#mouse-emulation-defines). + +### Examples + +The following will send a scroll down event to the host when pressed/held: + +``` +&msc SCRL_DOWN +``` + +The following will send a scroll left event to the host when pressed/held: + +``` +&msc SCRL_LEFT +``` + +:::note + +If you enabled [smooth scrolling](../../config/pointing.md#kconfig) then you will want to use the same `MOVE_UP`, `MOVE_DOWN`, etc values instead of the smaller `SCRL_*` parameters for more sensible scroll speeds. + +::: + +### Input Processors + +If you want to apply any [input processors](../input-processors/index.md#input-processors-overview) to `&msc` you can do so by referencing `&msc_input_listener`, e.g.: + +```dts +&msc_input_listener { + input-processors = <&zip_temp_layer 2 2000>; +} +``` + +## Advanced Configuration + +Both `&mmv` and `&msc` are instances of the `"zmk,behavior-input-two-axis"` behavior and can be modified using the [two axis input behavior](../../config/behaviors.md#two-axis-input) configuration properties. The default settings are as follows: + +### Mouse Move + +```dts +&mmv { + x-input-code = ; + y-input-code = ; + time-to-max-speed-ms = <300>; + acceleration-exponent = <1>; +}; +``` + +### Mouse Scroll + +```dts +&msc { + x-input-code = ; + y-input-code = ; + time-to-max-speed-ms = <300>; + acceleration-exponent = <1>; +}; +``` diff --git a/docs/docs/keymaps/input-processors/code-mapper.md b/docs/docs/keymaps/input-processors/code-mapper.md new file mode 100644 index 00000000000..62201ab4075 --- /dev/null +++ b/docs/docs/keymaps/input-processors/code-mapper.md @@ -0,0 +1,63 @@ +--- +title: Code Mapper Input Processor +sidebar_label: Code Mapper +--- + +## Overview + +The code mapper input processor is used to map the code of an event to a new one, e.g. changing a vertical Y movement event into a scroll event. + +## Usage + +When used, a code mapper takes no parameters, as the code mappings are specified in the definition of the specific mapper instance, e.g.: + +```dts +&zip_xy_to_scroll_mapper +``` + +## Pre-Defined Instances + +Three pre-defined instance of the code mapper input processor are available: + +| Reference | Description | +| -------------------------- | ----------------------------------------------------------------------- | +| `&zip_xy_to_scroll_mapper` | Map X/Y movement events to horizontal wheel/wheel events, respectively. | +| `&zip_xy_swap_mapper` | Map X to Y, and Y to X for movements. | + +Note that swapping X and Y movements can also be accomplished with the [transformer](transformer.md#pre-defined-instances) processors. + +## User-Defined Instances + +Users can define new instances of the code mapper input processor if they want to target different codes. + +### Example + +Below example maps the left mouse button code to the middle mouse button. + +```dts +#include + +/ { + input_processors { + zip_click_to_middle_click_mapper: zip_click_to_middle_click_mapper { + compatible = "zmk,input-processor-code-mapper"; + #input-processor-cells = <0>; + type = ; + map = ; + }; + }; +} +``` + +### Compatible + +The code mapper input processor uses a `compatible` property of `"zmk,input-processor-code-mapper"`. + +### Standard Properties + +- `#input-processor-cells` - required to be constant value of `<0>`. + +### User Properties + +- `type` - The [type](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L25) of events to scale. Usually, this is `INPUT_EV_REL` for relative events and `INPUT_EV_KEY` for key/button events. +- `map` - The specific codes of the given type to map, e.g. [relative event codes](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L245). This list must be an even number of entries which is processed as a list of pairs of codes. The first code in the pair is the source code, and the second is the code to map it to. diff --git a/docs/docs/keymaps/input-processors/index.md b/docs/docs/keymaps/input-processors/index.md new file mode 100644 index 00000000000..489003b8303 --- /dev/null +++ b/docs/docs/keymaps/input-processors/index.md @@ -0,0 +1,50 @@ +--- +title: Input Processor Overview +sidebar_label: Overview +--- + +## Input Processors Overview + +"Input processors" are small pieces of functionality that process and optionally modify events generated from emulated and physical pointing devices. Processors can do things like scaling movement values to make them larger or smaller for detailed work, swapping the event types to turn movements into scroll events, or temporarily enabling an extra layer while the pointer is in use. + +## Usage + +For information on using input processors with a given pointing device, see [input processor usage](usage.md). + +## Available Processors + +Below is a summary of pre-defined input processors and user-definable input processors available in ZMK, with references to documentation pages describing them. + +### Pre-Defined Processors + +A set of predefined input processors is available by adding the following at the top of your keymap/overlay file: + +``` +#include +``` + +Once included, you can use the following: + +| Binding | Processor | Description | +| -------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- | +| `&zip_xy_scaler` | [XY Scaler](scaler.md#pre-defined-instances) | Scale a the X/Y input events using a multiplier and divisor | +| `&zip_x_scaler` | [X Scaler](scaler.md#pre-defined-instances) | Scale a the X input events using a multiplier and divisor | +| `&zip_y_scaler` | [Y Scaler](scaler.md#pre-defined-instances) | Scale a the Y input events using a multiplier and divisor | +| `&zip_xy_transform` | [XY Transform](transformer.md#pre-defined-instances) | Transform X/Y values, e.g. inverting or swapping | +| `&zip_scroll_transform` | [Scroll Transform](transformer.md#pre-defined-instances) | Transform wheel/horizontal wheel values, e.g. inverting or swapping | +| `&zip_xy_to_scroll_mapper` | [XY To Scroll Mapper](code-mapper.md#pre-defined-instances) | Map X/Y values to scroll wheel/horizontal wheel events | +| `&zip_xy_swap_mapper` | [XY Swap Mapper](code-mapper.md#pre-defined-instances) | Swap X/Y values | +| `&zip_temp_layer` | [Temporary Layer](temp-layer.md#pre-defined-instances) | Temporarily enable a layer during pointer use | + +### User-Defined Processors + +Several of the input processors that have predefined instances, e.g. `&zip_xy_scaler` or `&zip_xy_to_scroll_mapper` can also have new instances created with custom properties around which input codes to scale, or which codes to map, etc. + +| Compatible | Processor | Description | +| --------------------------------- | ---------------------------------------------------- | ------------------------------------------------ | +| `zmk,input-processor-transform` | [Transform](transformer.md#user-defined-instances) | Perform various transforms like inverting values | +| `zmk,input-processor-code-mapper` | [Code Mapper](code-mapper.md#user-defined-instances) | Map one event code to another type | + +## External Processors + +Much like behaviors, custom input processors can also be added to [external modules](../../features/modules.mdx) to allow complete control of the processing operation. See [`input_processor.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/drivers/input_processor.h) for the definition of the driver API. diff --git a/docs/docs/keymaps/input-processors/scaler.md b/docs/docs/keymaps/input-processors/scaler.md new file mode 100644 index 00000000000..518574d6205 --- /dev/null +++ b/docs/docs/keymaps/input-processors/scaler.md @@ -0,0 +1,76 @@ +--- +title: Scaler Input Processor +sidebar_label: Scaler +--- + +## Overview + +The scaler input processor is used to scale the value of an input event that has a code matching the codes set on the scaler. Events with other codes will be ignored. Values are scaled by multiplying by the multiplier parameter, and then dividing by the divisor parameter. + +## Usage + +When used, a scaler takes two parameters that are positive integers, a multiplier and a divisor, e.g.: + +```dts +&zip_xy_scaler 2 1 +``` + +which will double all the X/Y movement, or: + +```dts +&zip_xy_scaler 1 3 +``` + +which will make movements more granular by reducing the speed to one third. + +:::warning + +A maximum value of `16` should be used for the multiplier and divisor parameters to avoid overflows. + +::: + +## Pre-Defined Instances + +Three pre-defined instance of the scaler input processor are available: + +| Reference | Description | +| ---------------- | --------------------------------------------- | +| `&zip_xy_scaler` | Scale X- and Y-axis values by the same amount | +| `&zip_x_scaler` | Scale X-axis values | +| `&zip_y_scaler` | Scale Y-axis values | + +## User-Defined Instances + +Users can define new instances of the scaler input processor if they want to target different codes. + +### Example + +```dts +#include + +/ { + input_processors { + zip_wheel_scaler: zip_wheel_scaler { + compatible = "zmk,input-processor-scaler"; + #input-processor-cells = <2>; + type = ; + codes = ; + track-remainders; + }; + }; +} +``` + +### Compatible + +The scaler input processor uses a `compatible` property of `"zmk,input-processor-scaler"`. + +### Standard Properties + +- `#input-processor-cells` - required to be constant value of `<2>`. +- `track-remainders` - boolean flag that indicates callers should allow the processor to track remainders between events. + +### User Properties + +- `type` - The [type](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L25) of events to scale. Usually, this is `INPUT_EV_REL` for relative events. +- `codes` - The specific codes within the given type to scale, e.g. [relative event codes](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L245) diff --git a/docs/docs/keymaps/input-processors/temp-layer.md b/docs/docs/keymaps/input-processors/temp-layer.md new file mode 100644 index 00000000000..47f2fe6d672 --- /dev/null +++ b/docs/docs/keymaps/input-processors/temp-layer.md @@ -0,0 +1,58 @@ +--- +title: Temporary Layer Input Processor +sidebar_label: Temporary Layer +--- + +## Overview + +The temporary layer input processor is used to enable a layer when input events are received, and automatically disable it when no further events are received in the given timeout duration. This most frequently is used to temporarily enable a layer with a set of [mouse button emulation behaviors](../behaviors/mouse-emulation.md#mouse-button-press) on it, so you can press various mouse buttons with the normal keyboard keys while using a physical pointer device for X/Y movement. + +## Usage + +When used, the temporary layer input processor takes two parameters, the layer index to enable and a timeout value in milliseconds: + +```dts +&zip_temp_layer 2 2000 +``` + +Above example enables the third layer and automatically disables it again after 2 seconds with no events from this pointing device. + +## Pre-Defined Instances + +One pre-defined instance of the temporary layer input processor is available: + +| Reference | Description | +| ----------------- | --------------------------------------------------------------- | +| `&zip_temp_layer` | Enable a certain layer temporarily until no events are received | + +## User-Defined Instances + +Users can define new instances of the temporary layer input processor to use different settings. + +### Example + +```dts +#include + +/ { + /omit-if-no-ref/ zip_temp_layer: zip_temp_layer { + compatible = "zmk,input-processor-temp-layer"; + #input-processor-cells = <2>; + require-prior-idle-ms = <2000>; + excluded-positions = <1 2 3>; + }; +}; +``` + +### Compatible + +The temp layer input processor uses a `compatible` property of `"zmk,input-processor-temp-layer"`. + +### Standard Properties + +- `#input-processor-cells` - required to be constant value of `<2>`. + +### User Properties + +- `require-prior-idle-ms` - Only activate the layer if there have not been any key presses for at least the set number of milliseconds before the pointing device event +- `excluded-positions` - List of (zero-based) key positions to exclude from deactivating the layer once it is active. diff --git a/docs/docs/keymaps/input-processors/transformer.md b/docs/docs/keymaps/input-processors/transformer.md new file mode 100644 index 00000000000..155e63707dd --- /dev/null +++ b/docs/docs/keymaps/input-processors/transformer.md @@ -0,0 +1,75 @@ +--- +title: Transformer Input Processor +sidebar_label: Transformer +--- + +## Overview + +The transformer input processor is used to perform various transforms on the value of an input event that has a code matching the codes set on the transformer. Events with other codes will be ignored. + +## Available Transforms + +The following transforms are available, by including +the [`dt-bindings/zmk/input_transform.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/input_transform.h) header +provided by ZMK near the top of your keymap/overlay: + +``` +#include +``` + +- `INPUT_TRANSFORM_XY_SWAP` - When encountering a value with matching type, swap the type of the event to the other axis, e.g. change an event of type `INPUT_REL_X` to type `INPUT_REL_Y`. +- `INPUT_TRANSFORM_X_INVERT` - Invert the values of any events that match the configured `x-codes` of the processor, by multiplying by negative one. +- `INPUT_TRANSFORM_Y_INVERT` - Invert the values of any events that match the configured `y-codes` of the processor, by multiplying by negative one. + +## Usage + +When used, a transformer takes one parameter, a combination of flags indicating which transforms to apply: + +```dts +&zip_xy_transform (INPUT_TRANSFORM_X_INVERT | INPUT_TRANSFORM_Y_INVERT) +``` + +## Pre-Defined Instances + +Three pre-defined instance of the scaler input processor are available: + +| Reference | Description | +| ----------------------- | ------------------------------------------------------------- | +| `&zip_xy_transform` | Applies the given transforms to X/Y movement events | +| `&zip_scroll_transform` | Applies the given transforms to wheel/horizontal wheel events | + +## User Defined Instances + +Users can define new instances of the transform input processor if they want to target different codes. + +### Example + +```dts +#include + +/ { + input_processors { + my_rotation_event_transform: my_rotation_event_transform { + compatible = "zmk,input-processor-transform"; + #input-processor-cells = <1>; + type = ; + x-codes = ; + y-codes = ; + }; + }; +} +``` + +### Compatible + +The transform input processor uses a `compatible` property of `"zmk,input-processor-transform"`. + +### Standard Properties + +- `#input-processor-cells` - required to be constant value of `<1>`. + +### User Properties + +- `type` - The [type](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L25) of events to transform. Usually, this is `INPUT_EV_REL` for relative events. +- `x-codes` - The specific X codes within the given type to transform, e.g. [relative event codes](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L245) +- `y-codes` - The specific Y codes within the given type to transform, e.g. [relative event codes](https://github.com/zmkfirmware/zephyr/blob/v3.5.0%2Bzmk-fixes/include/zephyr/dt-bindings/input/input-event-codes.h#L245) diff --git a/docs/docs/keymaps/input-processors/usage.md b/docs/docs/keymaps/input-processors/usage.md new file mode 100644 index 00000000000..907066e9cbb --- /dev/null +++ b/docs/docs/keymaps/input-processors/usage.md @@ -0,0 +1,59 @@ +--- +title: Input Processor Usage +sidebar_label: Usage +--- + +Input processors are used by assigning them to a given [input listener](../../features/pointing.md#input-listeners). A base set of processors is assigned to a listener, and then overrides can be set that are only active when certain [layers](../index.mdx#layers) are active. The examples in the following assume you are adding processors to the `&trackpad` device which is set up with a `&trackpad_listener`. + +### Base Processors + +Base processors are assigned in the `input-processors` property, and when events are generated, the events are process in the sequence in the order the processors are listed. For example, if you wanted your trackpad to always scale the values to increase the movements, you would assign the [scaler](scaler.md#pre-defined-instances) input processor to the property: + +```dts +#include + +&trackpad_listener { + input-processors = <&zip_xy_scaler 3 2>; +} +``` + +### Layer Specific Overrides + +Additional overrides can be added that only apply when the associated layer is active. For example, to make the trackpad work as a scroll device when your layer `1` is active, nest a child node on the listener and set the `layers` and `input-processors` properties: + +```dts +#include + +&trackpad_listener { + input-processors = <&zip_xy_scaler 3 2>; + + scroller { + layers = <1>; + input-processors = <&zip_xy_to_scroll_mapper>; + }; +} +``` + +:::note + +Overrides are processed in the order they are declared, from top to bottom, followed by the base processors in the parent node. Their application order is _not_ in any way tied to the layers specified in the `layers` property. + +::: + +By default, the first-defined override node that matches the layer specification will apply, in which case and any other overrides or the base processors will be skipped. If you add the `process-next;` property to a child node, the other processors will continue to be checked and applied even if that node's layer filter matches. + +```dts +#include + +&trackpad_listener { + input-processors = <&zip_xy_scaler 3 2>; + + scroller { + layers = <1>; + input-processors = <&zip_xy_to_scroll_mapper>; + process-next; + }; +} +``` + +For more details, see the [Input Listener configuration](../../config/pointing.md#input-listener) section. diff --git a/docs/sidebars.js b/docs/sidebars.js index c480a907213..9eba3718a1c 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -39,6 +39,7 @@ module.exports = { "features/battery", "features/low-power-states", "features/encoders", + "features/pointing", "features/displays", "features/backlight", "features/underglow", @@ -92,6 +93,22 @@ module.exports = { "keymaps/combos", "keymaps/conditional-layers", "keymaps/list-of-keycodes", + { + type: "category", + label: "Input Processors", + link: { + type: "doc", + id: "keymaps/input-processors/index", + }, + collapsed: true, + items: [ + "keymaps/input-processors/usage", + "keymaps/input-processors/scaler", + "keymaps/input-processors/transformer", + "keymaps/input-processors/code-mapper", + "keymaps/input-processors/temp-layer", + ], + }, ], }, { @@ -110,6 +127,7 @@ module.exports = { "config/combos", "config/displays", "config/encoders", + "config/pointing", "config/keymap", "config/layout", "config/kscan", @@ -137,6 +155,7 @@ module.exports = { "development/hardware-integration/shift-registers", "development/hardware-integration/encoders", "development/hardware-integration/soft-off-setup", + "development/hardware-integration/pointing", ], }, {