diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5e123bd59e4..4ac253f1254 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/zmkfirmware/zmk-dev-arm:3.5 +FROM docker.io/zmkfirmware/zmk-dev-arm:3.5-branch COPY .bashrc tmp RUN mv /tmp/.bashrc ~/.bashrc diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d7b725911d3..eb4d3c7db8c 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -36,7 +36,6 @@ target_sources_ifdef(CONFIG_ZMK_GPIO_KEY_WAKEUP_TRIGGER app PRIVATE src/gpio_key target_sources(app PRIVATE src/events/activity_state_changed.c) target_sources(app PRIVATE src/events/position_state_changed.c) target_sources(app PRIVATE src/events/sensor_event.c) -target_sources(app PRIVATE src/events/mouse_button_state_changed.c) target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c) target_sources(app PRIVATE src/behaviors/behavior_reset.c) @@ -44,7 +43,7 @@ target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SOFT_OFF app PRIVATE src/behaviors/behavior_soft_off.c) if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/hid.c) - target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse.c) + target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/input_listener.c) target_sources(app PRIVATE src/behaviors/behavior_key_press.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c) target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c) @@ -64,6 +63,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) @@ -87,6 +87,7 @@ endif() target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_backlight.c) +target_sources_ifdef(CONFIG_ZMK_STP_INDICATORS app PRIVATE src/behaviors/behavior_stp_indicators.c) target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/events/battery_state_changed.c) target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c) @@ -101,10 +102,14 @@ target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources_ifdef(CONFIG_ZMK_LOW_PRIORITY_WORK_QUEUE app PRIVATE src/workqueue.c) +target_sources_ifdef(CONFIG_ZMK_PROFILESWITCH app PRIVATE src/profile_switch.c) + +target_sources_ifdef(CONFIG_ZMK_STP_INDICATORS app PRIVATE src/stp_indicators.c) target_sources(app PRIVATE src/main.c) add_subdirectory(src/display/) add_subdirectory_ifdef(CONFIG_SETTINGS src/settings/) +add_subdirectory(src/mouse/) if (CONFIG_ZMK_STUDIO) # For some reason this is failing if run from a different sub-file. diff --git a/app/Kconfig b/app/Kconfig index 108fcbe7096..72b10900726 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -244,6 +244,10 @@ config BT_PERIPHERAL_PREF_LATENCY config BT_PERIPHERAL_PREF_TIMEOUT default 400 +config ZMK_PROFILESWITCH + bool "Enable two state profile switch" + default n + #ZMK_BLE endif @@ -363,6 +367,25 @@ config ZMK_RGB_UNDERGLOW_AUTO_OFF_USB #ZMK_RGB_UNDERGLOW endif +menuconfig ZMK_STP_INDICATORS + bool "STP Specific indicators" + select LED_STRIP + select ZMK_LOW_PRIORITY_WORK_QUEUE + select ZMK_HID_INDICATORS + +if ZMK_STP_INDICATORS + +# This default value cuts down on tons of excess .conf files, if you're using GPIO, manually disable this +config SPI + default y + +config ZMK_STP_INDICATORS_BRT_MAX + int "RGB underglow maximum brightness in percent" + range 0 100 + default 100 + +endif + menuconfig ZMK_BACKLIGHT bool "LED backlight" select LED @@ -395,13 +418,7 @@ endif #Display/LED Options endmenu -menu "Mouse Options" - -config ZMK_MOUSE - bool "Enable ZMK mouse emulation" - -#Mouse Options -endmenu +rsource "src/mouse/Kconfig" menu "Power Management" @@ -682,7 +699,7 @@ if ZMK_LOW_PRIORITY_WORK_QUEUE config ZMK_LOW_PRIORITY_THREAD_STACK_SIZE int "Low priority thread stack size" - default 768 + default 1024 config ZMK_LOW_PRIORITY_THREAD_PRIORITY int "Low priority thread priority" diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors index 1378e30333c..b48294ac621 100644 --- a/app/Kconfig.behaviors +++ b/app/Kconfig.behaviors @@ -57,6 +57,12 @@ 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 + imply ZMK_MOUSE + config ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON bool diff --git a/app/boards/arm/stp/Kconfig b/app/boards/arm/stp/Kconfig new file mode 100644 index 00000000000..d9e93dd0a13 --- /dev/null +++ b/app/boards/arm/stp/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT + +config BOARD_ENABLE_DCDC + bool "Enable DCDC mode" + select SOC_DCDC_NRF52X + default y + depends on BOARD_STP diff --git a/app/boards/arm/stp/Kconfig.board b/app/boards/arm/stp/Kconfig.board new file mode 100644 index 00000000000..3c855ac2d49 --- /dev/null +++ b/app/boards/arm/stp/Kconfig.board @@ -0,0 +1,8 @@ +# STP board configuration + +# Copyright (c) 2023 Polarity Works +# SPDX-License-Identifier: MIT + +config BOARD_STP + bool "stp" + depends on SOC_NRF52840_QIAA diff --git a/app/boards/arm/stp/Kconfig.defconfig b/app/boards/arm/stp/Kconfig.defconfig new file mode 100644 index 00000000000..1bf2d9fbd2c --- /dev/null +++ b/app/boards/arm/stp/Kconfig.defconfig @@ -0,0 +1,34 @@ +# Copyright (c) 2021 Polarity Works +# SPDX-License-Identifier: MIT + +if BOARD_STP + +config BOARD + default "stp" + +if USB + +config USB_NRFX + default y + +config USB_DEVICE_STACK + default y + +endif # USB + +config BT_CTLR + default BT + +config ZMK_BLE + default y + +config ZMK_USB + default y + +config ZMK_BATTERY_VOLTAGE_DIVIDER + default y + +config ZMK_KEYBOARD_NAME + default "Form" + +endif # BOARD_STP diff --git a/app/boards/arm/stp/board.cmake b/app/boards/arm/stp/board.cmake new file mode 100644 index 00000000000..73fa64a9aa0 --- /dev/null +++ b/app/boards/arm/stp/board.cmake @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MIT + +board_runner_args(nrfjprog "--nrf-family=NRF52" "--softreset") +include(${ZEPHYR_BASE}/boards/common/uf2.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) diff --git a/app/boards/arm/stp/pinctrl.dtsi b/app/boards/arm/stp/pinctrl.dtsi new file mode 100644 index 00000000000..27bd4145919 --- /dev/null +++ b/app/boards/arm/stp/pinctrl.dtsi @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * SPDX-License-Identifier: MIT + */ + +&pinctrl { + spi3_default: spi3_default { + group1 { + psels = ; + }; + }; + + spi3_sleep: spi3_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + pwm0_default: pwm0_default { + group1 { + psels = ; + }; + }; + pwm0_sleep: pwm0_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + i2c1_default: i2c1_default { + group1 { + psels = , + ; + }; + }; + + i2c1_sleep: i2c1_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; diff --git a/app/boards/arm/stp/stp.dts b/app/boards/arm/stp/stp.dts new file mode 100644 index 00000000000..61632819de9 --- /dev/null +++ b/app/boards/arm/stp/stp.dts @@ -0,0 +1,305 @@ +/* +* Copyright (c) 2023 Polarity Works +* +* SPDX-License-Identifier: MIT +*/ + +/dts-v1/; +#include +#include + +#include +#include + +#include "pinctrl.dtsi" + +/ { + model = "STP"; + compatible = "polarityworks,stp"; + + chosen { + zephyr,code-partition = &code_partition; + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zmk,physical_layout = &physical_layout; + zmk,backlight = &backlight; + zmk,battery = &vbatt; + zmk,profileswitch = &profileswitch; + zmk,indicators = &led_strip; + }; + + ansi_transform: ansi_transform { + compatible = "zmk,matrix-transform"; + columns = <16>; + rows = <6>; + map = < + RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11) RC(0,12) RC(0,13) RC(0,14) RC(0,15) + RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,11) RC(1,12) RC(1,14) RC(1,15) + RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,7) RC(2,8) RC(2,9) RC(2,10) RC(2,11) RC(2,12) RC(2,13) RC(2,14) RC(2,15) + RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,7) RC(3,8) RC(3,9) RC(3,10) RC(3,11) RC(3,12) RC(3,14) RC(3,15) + RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5) RC(4,7) RC(4,8) RC(4,9) RC(4,10) RC(4,11) RC(4,13) RC(4,14) RC(4,15) + RC(5,0) RC(5,1) RC(5,2) RC(5,4) RC(5,8) RC(5,10) RC(5,11) RC(5,12) RC(5,13) RC(5,14) RC(5,15) + >; + }; + + + kscan0: kscan_0 { + compatible = "zmk,kscan-gpio-matrix"; + diode-direction = "col2row"; + + col-gpios + = <&gpio1 11 GPIO_ACTIVE_HIGH> + , <&gpio1 10 GPIO_ACTIVE_HIGH> + , <&gpio1 13 GPIO_ACTIVE_HIGH> + , <&gpio1 15 GPIO_ACTIVE_HIGH> + , <&gpio0 3 GPIO_ACTIVE_HIGH> + , <&gpio0 2 GPIO_ACTIVE_HIGH> + , <&gpio0 28 GPIO_ACTIVE_HIGH> + , <&gpio0 19 GPIO_ACTIVE_HIGH> + , <&gpio0 22 GPIO_ACTIVE_HIGH> + , <&gpio1 0 GPIO_ACTIVE_HIGH> + , <&gpio1 3 GPIO_ACTIVE_HIGH> + , <&gpio1 1 GPIO_ACTIVE_HIGH> + , <&gpio1 2 GPIO_ACTIVE_HIGH> + , <&gpio1 4 GPIO_ACTIVE_HIGH> + , <&gpio1 6 GPIO_ACTIVE_HIGH> + , <&gpio0 9 GPIO_ACTIVE_HIGH> + ; + + row-gpios + = <&gpio0 31 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 30 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 23 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 10 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + }; + EXT_POWER { + compatible = "zmk,ext-power-generic"; + control-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + }; + backlight: pwmleds { + compatible = "pwm-leds"; + label = "Backlight LEDs"; + pwm_led_0 { + pwms = <&pwm0 0 10000 PWM_POLARITY_NORMAL>; + }; + }; + + vbatt: vbatt { + compatible = "zmk,battery-voltage-divider"; + io-channels = <&adc 2>; + output-ohms = <100000>; + full-ohms = <(100000 + 100000)>; + }; + + profileswitch: profileswitch { + compatible = "zmk,profile-switch"; + switch-gpios = <&gpio0 29 (GPIO_ACTIVE_HIGH | GPIO_PULL_UP)>; + }; + + physical_layout: physical_layout { + compatible = "zmk,physical-layout"; + display-name = "ANSI Layout"; + + kscan = <&kscan0>; + transform = <&ansi_transform>; + + keys // w h x y rot rx ry + = <&key_physical_attrs 100 100 0 0 0 0 0> + , <&key_physical_attrs 100 100 100 0 0 0 0> + , <&key_physical_attrs 100 100 200 0 0 0 0> + , <&key_physical_attrs 100 100 300 0 0 0 0> + , <&key_physical_attrs 100 100 400 0 0 0 0> + , <&key_physical_attrs 100 100 500 0 0 0 0> + , <&key_physical_attrs 100 100 600 0 0 0 0> + , <&key_physical_attrs 100 100 1000 0 0 0 0> + , <&key_physical_attrs 100 100 1100 0 0 0 0> + , <&key_physical_attrs 100 100 1200 0 0 0 0> + , <&key_physical_attrs 100 100 1300 0 0 0 0> + , <&key_physical_attrs 100 100 1400 0 0 0 0> + , <&key_physical_attrs 100 100 1500 0 0 0 0> + , <&key_physical_attrs 100 100 1600 0 0 0 0> + , <&key_physical_attrs 100 100 1700 0 0 0 0> + , <&key_physical_attrs 100 100 1800 0 0 0 0> + , <&key_physical_attrs 100 100 0 100 0 0 0> + , <&key_physical_attrs 100 100 100 100 0 0 0> + , <&key_physical_attrs 100 100 200 100 0 0 0> + , <&key_physical_attrs 100 100 300 100 0 0 0> + , <&key_physical_attrs 100 100 400 100 0 0 0> + , <&key_physical_attrs 100 100 500 100 0 0 0> + , <&key_physical_attrs 100 100 600 100 0 0 0> + , <&key_physical_attrs 100 100 1000 100 0 0 0> + , <&key_physical_attrs 100 100 1100 100 0 0 0> + , <&key_physical_attrs 100 100 1200 100 0 0 0> + , <&key_physical_attrs 100 100 1300 100 0 0 0> + , <&key_physical_attrs 100 100 1400 100 0 0 0> + , <&key_physical_attrs 100 100 1500 100 0 0 0> + , <&key_physical_attrs 200 100 1600 100 0 0 0> + , <&key_physical_attrs 100 100 1800 100 0 0 0> + , <&key_physical_attrs 150 100 0 200 0 0 0> + , <&key_physical_attrs 100 100 150 200 0 0 0> + , <&key_physical_attrs 100 100 250 200 0 0 0> + , <&key_physical_attrs 100 100 350 200 0 0 0> + , <&key_physical_attrs 100 100 450 200 0 0 0> + , <&key_physical_attrs 100 100 550 200 0 0 0> + , <&key_physical_attrs 100 100 950 200 0 0 0> + , <&key_physical_attrs 100 100 1050 200 0 0 0> + , <&key_physical_attrs 100 100 1150 200 0 0 0> + , <&key_physical_attrs 100 100 1250 200 0 0 0> + , <&key_physical_attrs 100 100 1350 200 0 0 0> + , <&key_physical_attrs 100 100 1450 200 0 0 0> + , <&key_physical_attrs 100 100 1550 200 0 0 0> + , <&key_physical_attrs 150 100 1650 200 0 0 0> + , <&key_physical_attrs 100 100 1800 200 0 0 0> + , <&key_physical_attrs 175 100 0 300 0 0 0> + , <&key_physical_attrs 100 100 175 300 0 0 0> + , <&key_physical_attrs 100 100 275 300 0 0 0> + , <&key_physical_attrs 100 100 375 300 0 0 0> + , <&key_physical_attrs 100 100 475 300 0 0 0> + , <&key_physical_attrs 100 100 575 300 0 0 0> + , <&key_physical_attrs 100 100 975 300 0 0 0> + , <&key_physical_attrs 100 100 1075 300 0 0 0> + , <&key_physical_attrs 100 100 1175 300 0 0 0> + , <&key_physical_attrs 100 100 1275 300 0 0 0> + , <&key_physical_attrs 100 100 1375 300 0 0 0> + , <&key_physical_attrs 100 100 1475 300 0 0 0> + , <&key_physical_attrs 225 100 1575 300 0 0 0> + , <&key_physical_attrs 100 100 1800 300 0 0 0> + , <&key_physical_attrs 225 100 0 400 0 0 0> + , <&key_physical_attrs 100 100 225 400 0 0 0> + , <&key_physical_attrs 100 100 325 400 0 0 0> + , <&key_physical_attrs 100 100 425 400 0 0 0> + , <&key_physical_attrs 100 100 525 400 0 0 0> + , <&key_physical_attrs 100 100 625 400 0 0 0> + , <&key_physical_attrs 100 100 1025 400 0 0 0> + , <&key_physical_attrs 100 100 1125 400 0 0 0> + , <&key_physical_attrs 100 100 1225 400 0 0 0> + , <&key_physical_attrs 100 100 1325 400 0 0 0> + , <&key_physical_attrs 100 100 1425 400 0 0 0> + , <&key_physical_attrs 175 100 1525 400 0 0 0> + , <&key_physical_attrs 100 100 1700 400 0 0 0> + , <&key_physical_attrs 100 100 1800 400 0 0 0> + , <&key_physical_attrs 125 100 0 500 0 0 0> + , <&key_physical_attrs 125 100 125 500 0 0 0> + , <&key_physical_attrs 125 100 250 500 0 0 0> + , <&key_physical_attrs 300 100 375 500 0 0 0> + , <&key_physical_attrs 300 100 1000 500 0 0 0> + , <&key_physical_attrs 100 100 1300 500 0 0 0> + , <&key_physical_attrs 100 100 1400 500 0 0 0> + , <&key_physical_attrs 100 100 1500 500 0 0 0> + , <&key_physical_attrs 100 100 1600 500 0 0 0> + , <&key_physical_attrs 100 100 1700 500 0 0 0> + , <&key_physical_attrs 100 100 1800 500 0 0 0> + ; + }; +}; + +&adc { + status = "okay"; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +zephyr_udc0: &usbd { + status = "okay"; +}; + + +&flash0 { + /* + * For more information, see: + * http://docs.zephyrproject.org/latest/devices/dts/flash_partitions.html + */ + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + sd_partition: partition@0 { + label = "softdevice"; + reg = <0x00000000 0x00026000>; + }; + code_partition: partition@26000 { + label = "code_partition"; + reg = <0x00026000 0x000c6000>; + }; + + /* + * The flash starting at 0x000ec000 and ending at + * 0x000f3fff is reserved for use by the application. + */ + + /* + * Storage partition will be used by FCB/LittleFS/NVS + * if enabled. + */ + storage_partition: partition@ec000 { + label = "storage"; + reg = <0x000ec000 0x00008000>; + }; + + boot_partition: partition@f4000 { + label = "adafruit_boot"; + reg = <0x000f4000 0x0000c000>; + }; + }; +}; + +&spi3 { + compatible = "nordic,nrf-spim"; + status = "okay"; + pinctrl-0 = <&spi3_default>; + pinctrl-1 = <&spi3_sleep>; + pinctrl-names = "default", "sleep"; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + + /* SPI */ + reg = <0>; + spi-max-frequency = <4000000>; + + /* WS2812 */ + chain-length = <2>; /* number of LEDs */ + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + color-mapping = ; + }; +}; + + +&i2c1 { + compatible = "nordic,nrf-twim"; + status = "okay"; + pinctrl-0 = <&i2c1_default>; + pinctrl-1 = <&i2c1_sleep>; + pinctrl-names = "default", "sleep"; + clock-frequency = ; + trackpad: trackpad@2c { + compatible = "cirque,gen4"; + status = "okay"; + reg = <0x2C>; + dr-gpios = <&gpio0 26 (GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/app/boards/arm/stp/stp.keymap b/app/boards/arm/stp/stp.keymap new file mode 100644 index 00000000000..5af2e486d38 --- /dev/null +++ b/app/boards/arm/stp/stp.keymap @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include + + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + // ------------------------------------------------------------------------------------------------------------ + // | ESC | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F9 | F9 | F10 | F11 | F12 | 1 | DEL | INS | + // | ` | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | = | BKSP | HOME | + // | TAB | Q | W | E | R | T | Y | U | I | O | P | [ | ] | \ | END | + // | CAPS | A | S | D | F | G | H | J | K | L | ; | ' | ENTER | PG UP | + // | SHIFT | Z | X | C | V | B | N | M | , | . | / | SHIFT | UP | PG DN | + // | CTL | WIN | ALT | SPACE | | SPACE | CTRL | MENU | ALT | LEFT | DOWN | RIGHT | + // ------------------------------------------------------------------------------------------------------------ + + bindings = < + &kp ESC &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 &kp F11 &kp F12 &mo 1 &kp DEL &kp INS + &kp GRAVE &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp MINUS &kp EQUAL &kp BACKSPACE &kp HOME + &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp LBKT &kp RBKT &kp BSLH &kp END + &kp CLCK &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT &kp RET &kp PG_UP + &kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp RSHFT &kp UP &kp PG_DN + &kp LCTRL &kp LGUI &kp LALT &kp SPACE &kp SPACE &kp RCTRL &kp K_APP &kp RALT &kp LEFT &kp DOWN &kp RIGHT + >; + }; + + raise { + bindings = < + &studio_unlock &kp C_MUTE &kp C_VOL_DN &kp C_VOL_UP &kp C_PREV &kp C_PP &kp C_NEXT &bl BL_DEC &bl BL_INC &kp C_BRI_DN &kp C_BRI_UP &bt BT_CLR &stp STP_BAT &trans &none &none + &none &none &none &none &none &none &none &none &none &none &none &none &none &none &none + &none &none &kp C_MEDIA_WWW &none &none &kp C_MEDIA_TV &none &none &none &none &none &none &none &sys_reset &none + &none &none &none &none &none &none &kp C_AC_HOME &none &none &none &none &none &bootloader &none + &none &none &none &none &none &kp C_AC_BACK &none &none &none &none &none &none &none &none + &none &none &none &none &none &none &none &none &none &none &none + >; + }; + }; +}; diff --git a/app/boards/arm/stp/stp.yaml b/app/boards/arm/stp/stp.yaml new file mode 100644 index 00000000000..f7f0712dd29 --- /dev/null +++ b/app/boards/arm/stp/stp.yaml @@ -0,0 +1,15 @@ +identifier: stp +name: stp +type: mcu +arch: arm +toolchain: + - zephyr + - gnuarmemb + - xtools +supported: + - adc + - usb_device + - ble + - ieee802154 + - pwm + - watchdog diff --git a/app/boards/arm/stp/stp_defconfig b/app/boards/arm/stp/stp_defconfig new file mode 100644 index 00000000000..593f3bf622a --- /dev/null +++ b/app/boards/arm/stp/stp_defconfig @@ -0,0 +1,82 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_STP=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# Enable GPIO and pinctrl +CONFIG_GPIO=y +CONFIG_PINCTRL=y + +# Enable SPI for LEDS +CONFIG_SPI=y +CONFIG_SPI_NRFX=y + +# Trackpad configuration +CONFIG_I2C=y +CONFIG_ZMK_MOUSE=y +CONFIG_ZMK_TRACKPAD=y +CONFIG_ZMK_TRACKPAD_PHYSICAL_X=650 +CONFIG_ZMK_TRACKPAD_PHYSICAL_Y=1065 +CONFIG_ZMK_TRACKPAD_LOGICAL_X=650 +CONFIG_ZMK_TRACKPAD_LOGICAL_Y=1065 +CONFIG_ZMK_TRACKPAD_REVERSE_SCROLL=y +CONFIG_ZMK_TRACKPAD_FINGERS=4 +CONFIG_ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE=30 +CONFIG_GEN4=y +CONFIG_GEN4_TRIGGER_GLOBAL_THREAD=y + +# Enable writing to flash +CONFIG_USE_DT_CODE_PARTITION=y +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y + +# Enable 32kHz crystal +CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL=y +CONFIG_CLOCK_CONTROL_NRF_K32SRC_150PPM=y + +# RGB leds config +CONFIG_ZMK_STP_INDICATORS=y +CONFIG_ZMK_STP_INDICATORS_BRT_MAX=10 +CONFIG_WS2812_STRIP=y + +# Backlighting configuration +CONFIG_PWM=y +CONFIG_LED_PWM=y +CONFIG_ZMK_BACKLIGHT=y +CONFIG_ZMK_BACKLIGHT_BRT_START=25 +CONFIG_ZMK_BACKLIGHT_BRT_STEP=25 +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE=y + +# BT configuration +CONFIG_ZMK_BLE=y +CONFIG_ZMK_BLE_EXPERIMENTAL_CONN=y +CONFIG_BT_CTLR_TX_PWR_PLUS_8=y +CONFIG_BT_DIS_MANUF="Kinesis Corporation" +CONFIG_BT_DIS_PNP_VID=0x29EA +CONFIG_BT_DIS_PNP_PID=0x1000 +CONFIG_BT_MAX_CONN=2 +CONFIG_BT_MAX_PAIRED=2 + +# USB configuration +CONFIG_ZMK_USB=y +CONFIG_USB_DEVICE_VID=0x29EA +CONFIG_USB_DEVICE_PID=0x1000 +CONFIG_USB_DEVICE_MANUFACTURER="Kinesis Corporation" + +# Misc configuration +CONFIG_ZMK_PROFILESWITCH=y +CONFIG_ZMK_HID_REPORT_TYPE_NKRO=n +CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_FULL=y +CONFIG_BUILD_OUTPUT_UF2=y + diff --git a/app/core-coverage.yml b/app/core-coverage.yml index cb551aae47e..694803c827b 100644 --- a/app/core-coverage.yml +++ b/app/core-coverage.yml @@ -1,61 +1,4 @@ -board: - - nice_nano_v2 - - nrfmicro_13 - - proton_c -shield: - - corne_left - - corne_right - - romac - - settings_reset - - tidbit include: - - board: bdn9_rev2 - - board: nice60 - - board: seeeduino_xiao_ble - shield: hummingbird - - board: nrf52840_m2 - shield: m60 - - board: planck_rev6 - - board: proton_c - shield: clueboard_california - - board: nice_nano_v2 - shield: kyria_left - cmake-args: "-DCONFIG_ZMK_DISPLAY=y" - nickname: "display" - - board: nice_nano_v2 - shield: kyria_left - cmake-args: "-DCONFIG_ZMK_MOUSE=y" - nickname: "mouse" - - board: sparkfun_pro_micro_rp2040 - shield: reviung41 - cmake-args: "-DSNIPPET='zmk-usb-logging'" - - board: nice_nano_v2 - shield: kyria_right - cmake-args: "-DCONFIG_ZMK_DISPLAY=y" - nickname: "display" - - board: nice_nano - shield: romac_plus - cmake-args: "-DCONFIG_ZMK_RGB_UNDERGLOW=y -DCONFIG_WS2812_STRIP=y" - nickname: "underglow" - - board: nice_nano_v2 - shield: lily58_left nice_view_adapter nice_view - nickname: "niceview" - - board: bdn9_rev2 + - board: stp snippet: studio-rpc-usb-uart cmake-args: "-DCONFIG_ZMK_STUDIO=y" - nickname: "stm32-studio" - - board: nice_nano_v2 - shield: reviung41 - snippet: studio-rpc-usb-uart - cmake-args: "-DCONFIG_ZMK_STUDIO=y" - nickname: "nrf52-studio" - - board: sparkfun_pro_micro_rp2040 - shield: reviung41 - snippet: studio-rpc-usb-uart - cmake-args: "-DCONFIG_ZMK_STUDIO=y" - nickname: "rp2040-studio" - - board: seeeduino_xiao - shield: hummingbird - snippet: studio-rpc-usb-uart - cmake-args: "-DCONFIG_ZMK_STUDIO=y" - nickname: "samd21-studio" diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index fcb4a63d450..3c8971222db 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,9 @@ #include #include #include -#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 872b43075ba..dcd3e92ccac 100644 --- a/app/dts/behaviors/mouse_key_press.dtsi +++ b/app/dts/behaviors/mouse_key_press.dtsi @@ -8,4 +8,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/behaviors/stp_indicators.dtsi b/app/dts/behaviors/stp_indicators.dtsi new file mode 100644 index 00000000000..c1d2b46898a --- /dev/null +++ b/app/dts/behaviors/stp_indicators.dtsi @@ -0,0 +1,9 @@ +/ { + behaviors { + stp: behavior_stp_indicators { + compatible = "zmk,behavior-stp-indicators"; + #binding-cells = <1>; + display-name = "Battery Indication"; + }; + }; +}; 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..0c138e03929 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-input-two-axis.yaml @@ -0,0 +1,25 @@ +description: Two axis input behavior + +compatible: "zmk,behavior-input-two-axis" + +include: one_param.yaml + +properties: + x-input-code: + type: int + required: true + y-input-code: + type: int + required: true + trigger-period-ms: + type: int + default: 16 + description: The time (in ms) between generated inputs when an input has non-zero speed. + delay-ms: + type: int + time-to-max-speed-ms: + type: int + required: true + acceleration-exponent: + type: int + default: 1 diff --git a/app/dts/bindings/behaviors/zmk,behavior-stp-indicators.yaml b/app/dts/bindings/behaviors/zmk,behavior-stp-indicators.yaml new file mode 100644 index 00000000000..d1994e1f480 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-stp-indicators.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: RGB indicators + +compatible: "zmk,behavior-stp-indicators" + +include: one_param.yaml diff --git a/app/dts/bindings/zmk,input-listener.yaml b/app/dts/bindings/zmk,input-listener.yaml new file mode 100644 index 00000000000..eb6e322ac4e --- /dev/null +++ b/app/dts/bindings/zmk,input-listener.yaml @@ -0,0 +1,27 @@ +description: | + Listener to subscribe to input events and send HID updates after processing + +compatible: "zmk,input-listener" + +properties: + mode: + type: string + default: "mouse" + enum: + - "mouse" + - "trackpad" + device: + type: phandle + required: true + xy-swap: + type: boolean + x-invert: + type: boolean + y-invert: + type: boolean + scale-multiplier: + type: int + default: 1 + scale-divisor: + type: int + default: 1 diff --git a/app/dts/bindings/zmk,profile-switch.yaml b/app/dts/bindings/zmk,profile-switch.yaml new file mode 100644 index 00000000000..2c093abe6e3 --- /dev/null +++ b/app/dts/bindings/zmk,profile-switch.yaml @@ -0,0 +1,14 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: | + Generic driver for controlling the bluetooth profile on the + switch-gpio pin status + (Only in supported hardware) + +compatible: "zmk,profile-switch" + +properties: + switch-gpios: + type: phandle-array + required: true diff --git a/app/include/drivers/sensor/gen4.h b/app/include/drivers/sensor/gen4.h new file mode 100644 index 00000000000..811da9001ae --- /dev/null +++ b/app/include/drivers/sensor/gen4.h @@ -0,0 +1,27 @@ +#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_GEN4_H_ +#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_GEN4_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +enum sensor_channel_gen4 { + SENSOR_CHAN_FINGER = SENSOR_CHAN_PRIV_START, + SENSOR_CHAN_X, + SENSOR_CHAN_Y, + SENSOR_CHAN_CONFIDENCE_TIP, + SENSOR_CHAN_CONTACTS, + SENSOR_CHAN_SCAN_TIME, + SENSOR_CHAN_BUTTONS, + SENSOR_CHAN_WHEEL, + SENSOR_CHAN_XDELTA, + SENSOR_CHAN_YDELTA, +}; + +#ifdef __cplusplus +} +#endif + +#endif \ 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..f9d0c939a24 100644 --- a/app/include/dt-bindings/zmk/mouse.h +++ b/app/include/dt-bindings/zmk/mouse.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 The ZMK Contributors + * Copyright (c) 2023 The ZMK Contributors * * SPDX-License-Identifier: MIT */ @@ -22,3 +22,29 @@ #define MB4 BIT(3) #define MB5 BIT(4) + +#ifndef ZMK_MOUSE_DEFAULT_MOVE_VAL +#define ZMK_MOUSE_DEFAULT_MOVE_VAL 600 +#endif + +#ifndef ZMK_MOUSE_DEFAULT_SCRL_VAL +#define ZMK_MOUSE_DEFAULT_SCRL_VAL 10 +#endif + +/* Mouse move behavior */ +#define MOVE_Y(vert) ((vert) & 0xFFFF) +#define MOVE_Y_DECODE(encoded) (int16_t)((encoded) & 0x0000FFFF) +#define MOVE_X(hor) (((hor) & 0xFFFF) << 16) +#define MOVE_X_DECODE(encoded) (int16_t)(((encoded) & 0xFFFF0000) >> 16) + +#define MOVE(hor, vert) (MOVE_X(hor) + MOVE_Y(vert)) + +#define MOVE_UP MOVE_Y(-ZMK_MOUSE_DEFAULT_MOVE_VAL) +#define MOVE_DOWN MOVE_Y(ZMK_MOUSE_DEFAULT_MOVE_VAL) +#define MOVE_LEFT MOVE_X(-ZMK_MOUSE_DEFAULT_MOVE_VAL) +#define MOVE_RIGHT MOVE_X(ZMK_MOUSE_DEFAULT_MOVE_VAL) + +#define SCRL_UP MOVE_Y(ZMK_MOUSE_DEFAULT_SCRL_VAL) +#define SCRL_DOWN MOVE_Y(-ZMK_MOUSE_DEFAULT_SCRL_VAL) +#define SCRL_LEFT MOVE_X(-ZMK_MOUSE_DEFAULT_SCRL_VAL) +#define SCRL_RIGHT MOVE_X(ZMK_MOUSE_DEFAULT_SCRL_VAL) diff --git a/app/include/dt-bindings/zmk/stp.h b/app/include/dt-bindings/zmk/stp.h new file mode 100644 index 00000000000..9fa640602ae --- /dev/null +++ b/app/include/dt-bindings/zmk/stp.h @@ -0,0 +1 @@ +#define STP_BAT 0 \ No newline at end of file diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index f2aff2bcc2d..8afc785a2f3 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -72,6 +72,10 @@ int zmk_endpoints_send_report(uint16_t usage_page); #if IS_ENABLED(CONFIG_ZMK_MOUSE) int zmk_endpoints_send_mouse_report(); -#endif // IS_ENABLE(CONFIG_ZMK_MOUSE) +#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_endpoints_send_ptp_report(); +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) void zmk_endpoints_clear_current(void); diff --git a/app/include/zmk/events/ble_active_profile_changed.h b/app/include/zmk/events/ble_active_profile_changed.h index 620e19b4b88..d5e8787c2f9 100644 --- a/app/include/zmk/events/ble_active_profile_changed.h +++ b/app/include/zmk/events/ble_active_profile_changed.h @@ -15,6 +15,8 @@ struct zmk_ble_active_profile_changed { uint8_t index; struct zmk_ble_profile *profile; + bool open; + bool connected; }; ZMK_EVENT_DECLARE(zmk_ble_active_profile_changed); diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 766fb9c4632..43207c87735 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -12,9 +12,8 @@ #include #include -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -#include -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + +#include #include #include @@ -75,7 +74,14 @@ #define ZMK_HID_REPORT_ID_KEYBOARD 0x01 #define ZMK_HID_REPORT_ID_LEDS 0x01 #define ZMK_HID_REPORT_ID_CONSUMER 0x02 -#define ZMK_HID_REPORT_ID_MOUSE 0x03 + +#define ZMK_MOUSE_HID_NUM_BUTTONS 0x05 + +#define ZMK_MOUSE_HID_REPORT_ID_MOUSE 0x05 + +// Needed until Zephyr offers a 2 byte usage macro +#define HID_USAGE16(idx) \ + HID_ITEM(HID_ITEM_TAG_USAGE, HID_ITEM_TYPE_LOCAL, 2), (idx & 0xFF), (idx >> 8 & 0xFF) static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), @@ -160,17 +166,16 @@ static const uint8_t zmk_hid_report_desc[] = { HID_REPORT_COUNT(CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE), HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_ARRAY | ZMK_HID_MAIN_VAL_ABS), HID_END_COLLECTION, - #if IS_ENABLED(CONFIG_ZMK_MOUSE) HID_USAGE_PAGE(HID_USAGE_GD), HID_USAGE(HID_USAGE_GD_MOUSE), HID_COLLECTION(HID_COLLECTION_APPLICATION), - HID_REPORT_ID(ZMK_HID_REPORT_ID_MOUSE), + HID_REPORT_ID(ZMK_MOUSE_HID_REPORT_ID_MOUSE), HID_USAGE(HID_USAGE_GD_POINTER), HID_COLLECTION(HID_COLLECTION_PHYSICAL), HID_USAGE_PAGE(HID_USAGE_BUTTON), HID_USAGE_MIN8(0x1), - HID_USAGE_MAX8(ZMK_HID_MOUSE_NUM_BUTTONS), + HID_USAGE_MAX8(ZMK_MOUSE_HID_NUM_BUTTONS), HID_LOGICAL_MIN8(0x00), HID_LOGICAL_MAX8(0x01), HID_REPORT_SIZE(0x01), @@ -185,9 +190,9 @@ static const uint8_t zmk_hid_report_desc[] = { HID_USAGE(HID_USAGE_GD_X), HID_USAGE(HID_USAGE_GD_Y), HID_USAGE(HID_USAGE_GD_WHEEL), - HID_LOGICAL_MIN8(-0x7F), - HID_LOGICAL_MAX8(0x7F), - HID_REPORT_SIZE(0x08), + HID_LOGICAL_MIN16(0xFF, -0x7F), + HID_LOGICAL_MAX16(0xFF, 0x7F), + HID_REPORT_SIZE(0x10), HID_REPORT_COUNT(0x03), HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_REL), HID_END_COLLECTION, @@ -256,11 +261,12 @@ struct zmk_hid_consumer_report { } __packed; #if IS_ENABLED(CONFIG_ZMK_MOUSE) + struct zmk_hid_mouse_report_body { zmk_mouse_button_flags_t buttons; - int8_t d_x; - int8_t d_y; - int8_t d_wheel; + int16_t d_x; + int16_t d_y; + int16_t d_scroll_y; } __packed; struct zmk_hid_mouse_report { @@ -296,21 +302,24 @@ int zmk_hid_press(uint32_t usage); int zmk_hid_release(uint32_t usage); bool zmk_hid_is_pressed(uint32_t usage); +struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void); +struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void); + #if IS_ENABLED(CONFIG_ZMK_MOUSE) int zmk_hid_mouse_button_press(zmk_mouse_button_t button); int zmk_hid_mouse_button_release(zmk_mouse_button_t button); int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons); int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons); +void zmk_hid_mouse_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_set(uint8_t buttons, int8_t xDelta, int8_t yDelta, int8_t scrollDelta); void zmk_hid_mouse_clear(void); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void); -struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void); +struct zmk_hid_mouse_report *zmk_mouse_hid_get_mouse_report(); +#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) zmk_hid_boot_report_t *zmk_hid_get_boot_report(); #endif - -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/include/zmk/hid_indicators.h b/app/include/zmk/hid_indicators.h index 7c7b89f53ce..cf78969e7db 100644 --- a/app/include/zmk/hid_indicators.h +++ b/app/include/zmk/hid_indicators.h @@ -10,6 +10,12 @@ #include #include +#define ZMK_LED_NUMLOCK_BIT BIT(0) +#define ZMK_LED_CAPSLOCK_BIT BIT(1) +#define ZMK_LED_SCROLLLOCK_BIT BIT(2) +#define ZMK_LED_COMPOSE_BIT BIT(3) +#define ZMK_LED_KANA_BIT BIT(4) + zmk_hid_indicators_t zmk_hid_indicators_get_current_profile(void); zmk_hid_indicators_t zmk_hid_indicators_get_profile(struct zmk_endpoint_instance endpoint); void zmk_hid_indicators_set_profile(zmk_hid_indicators_t indicators, diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index eb6e653f772..6293dc37a93 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -13,5 +13,5 @@ int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *body); int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); #if IS_ENABLED(CONFIG_ZMK_MOUSE) -int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); +int zmk_mouse_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/include/zmk/mouse/hid.h b/app/include/zmk/mouse/hid.h new file mode 100644 index 00000000000..11744433788 --- /dev/null +++ b/app/include/zmk/mouse/hid.h @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#include +#include + +#include + +#include +#include + +// Windows PTP Collection +// (https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchpad-windows-precision-touchpad-collection) +#define ZMK_MOUSE_HID_REPORT_ID_DIGITIZER 0x08 +#define ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_CAPABILITIES 0x04 +#define ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTPHQA 0x05 + +// Configuration Collection +// (https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchpad-configuration-collection) +#define ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_MODE 0x06 +#define ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_SELECTIVE 0x07 + +#define ZMK_HID_USAGE_PAGE16(page) \ + HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), (page & 0xFF), (page >> 8 & 0xFF) + +#define ZMK_HID_LOGICAL_MIN32(a) \ + HID_ITEM(HID_ITEM_TAG_LOGICAL_MIN, HID_ITEM_TYPE_GLOBAL, 3), (a & 0xFF), (a >> 8 & 0xFF), \ + (a >> 16 & 0xFF), (a >> 24 & 0xFF) +#define ZMK_HID_LOGICAL_MAX32(a) \ + HID_ITEM(HID_ITEM_TAG_LOGICAL_MAX, HID_ITEM_TYPE_GLOBAL, 3), (a & 0xFF), (a >> 8 & 0xFF), \ + (a >> 16 & 0xFF), (a >> 24 & 0xFF) + +#define ZMK_HID_PHYSICAL_MAX32(a) \ + HID_ITEM(HID_ITEM_TAG_PHYSICAL_MAX, HID_ITEM_TYPE_GLOBAL, 3), (a & 0xFF), (a >> 8 & 0xFF), \ + (a >> 16 & 0xFF), (a >> 24 & 0xFF) + +#define ZMK_HID_PHYSICAL_MIN8(a) HID_ITEM(HID_ITEM_TAG_PHYSICAL_MIN, HID_ITEM_TYPE_GLOBAL, 1), a +#define ZMK_HID_PHYSICAL_MAX8(a) HID_ITEM(HID_ITEM_TAG_PHYSICAL_MAX, HID_ITEM_TYPE_GLOBAL, 1), a +#define ZMK_HID_PHYSICAL_MAX16(a) \ + HID_ITEM(HID_ITEM_TAG_PHYSICAL_MAX, HID_ITEM_TYPE_GLOBAL, 2), (a & 0xFF), (a >> 8 & 0xFF) + +#define HID_REPORT_COUNT16(count) \ + HID_ITEM(HID_ITEM_TAG_REPORT_COUNT, HID_ITEM_TYPE_GLOBAL, 2), (count & 0xFF), \ + (count >> 8 & 0xFF) + +#define ZMK_HID_EXPONENT8(a) HID_ITEM(HID_ITEM_TAG_UNIT_EXPONENT, HID_ITEM_TYPE_GLOBAL, 1), a + +#define ZMK_HID_UNIT8(a) HID_ITEM(HID_ITEM_TAG_UNIT, HID_ITEM_TYPE_GLOBAL, 1), a +#define ZMK_HID_UNIT16(a) \ + HID_ITEM(HID_ITEM_TAG_UNIT, HID_ITEM_TYPE_GLOBAL, 2), (a & 0xFF), (a >> 8 & 0xFF) + +#define TRACKPAD_FINGER_DESC(n, c) \ + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), HID_USAGE(HID_USAGE_DIGITIZERS_FINGER), \ + HID_COLLECTION(HID_COLLECTION_LOGICAL), HID_LOGICAL_MIN8(0), HID_LOGICAL_MAX8(1), \ + HID_USAGE(HID_USAGE_DIGITIZERS_TOUCH_VALID), HID_USAGE(HID_USAGE_DIGITIZERS_TIP_SWITCH), \ + HID_REPORT_COUNT(2), HID_REPORT_SIZE(1), \ + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), \ + \ + HID_REPORT_COUNT(1), HID_REPORT_SIZE(4), HID_LOGICAL_MAX8(CONFIG_ZMK_TRACKPAD_FINGERS), \ + HID_USAGE(HID_USAGE_DIGITIZERS_CONTACT_IDENTIFIER), \ + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | \ + ZMK_HID_MAIN_VAL_ABS), /* Filler Bits */ \ + HID_REPORT_SIZE(1), HID_REPORT_COUNT(2), \ + HID_INPUT(ZMK_HID_MAIN_VAL_CONST | ZMK_HID_MAIN_VAL_ARRAY | ZMK_HID_MAIN_VAL_ABS), \ + \ + HID_USAGE_PAGE(HID_USAGE_GD), HID_LOGICAL_MIN8(0), \ + HID_LOGICAL_MAX16((CONFIG_ZMK_TRACKPAD_LOGICAL_X & 0xFF), \ + ((CONFIG_ZMK_TRACKPAD_LOGICAL_X >> 8) & 0xFF)), \ + HID_REPORT_SIZE(16), /* Exponent (-2) */ \ + ZMK_HID_EXPONENT8(0x0E), /* Unit (Linear CM) */ \ + ZMK_HID_UNIT8(0x11), HID_USAGE(HID_USAGE_GD_X), ZMK_HID_PHYSICAL_MIN8(0x00), \ + ZMK_HID_PHYSICAL_MAX16(CONFIG_ZMK_TRACKPAD_PHYSICAL_X), HID_REPORT_COUNT(1), \ + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), \ + HID_LOGICAL_MAX16((CONFIG_ZMK_TRACKPAD_LOGICAL_Y & 0xFF), \ + ((CONFIG_ZMK_TRACKPAD_LOGICAL_Y >> 8) & 0xFF)), \ + ZMK_HID_PHYSICAL_MAX16(CONFIG_ZMK_TRACKPAD_PHYSICAL_Y), HID_USAGE(HID_USAGE_GD_Y), \ + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), \ + HID_END_COLLECTION, + +static const uint8_t zmk_mouse_hid_report_desc[] = { +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + HID_USAGE(HID_USAGE_DIGITIZERS_TOUCH_PAD), + HID_COLLECTION(HID_COLLECTION_APPLICATION), + + // Windows Precision Touchpad Input Reports + HID_REPORT_ID(ZMK_MOUSE_HID_REPORT_ID_DIGITIZER), + + LISTIFY(CONFIG_ZMK_TRACKPAD_FINGERS, TRACKPAD_FINGER_DESC, ()) + + /* Exponent (-4) */ + ZMK_HID_EXPONENT8(0x0C), + /* Unit (Linear Seconds) */ + ZMK_HID_UNIT16(0x1001), + ZMK_HID_PHYSICAL_MAX32(0xFFFF), + ZMK_HID_LOGICAL_MAX32(0xFFFF), + HID_REPORT_SIZE(16), + HID_REPORT_COUNT(1), + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + HID_USAGE(HID_USAGE_DIGITIZERS_SCAN_TIME), + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + + // Contact Count + HID_USAGE(HID_USAGE_DIGITIZERS_CONTACT_COUNT), + + ZMK_HID_PHYSICAL_MAX8(0x00), + HID_LOGICAL_MAX8(0x05), + + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(4), + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + + // Button report + HID_USAGE_PAGE(HID_USAGE_GEN_BUTTON), + // Buttons usages (1, 2, 3) + HID_USAGE(0x01), + HID_USAGE(0x02), + HID_USAGE(0x03), + + HID_LOGICAL_MAX8(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(3), + HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + // Byte Padding + HID_REPORT_COUNT(1), + HID_INPUT(ZMK_HID_MAIN_VAL_CONST | ZMK_HID_MAIN_VAL_ARRAY | ZMK_HID_MAIN_VAL_ABS), + + // Device Capabilities Feature Report + + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + HID_REPORT_ID(ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_CAPABILITIES), + HID_USAGE(HID_USAGE_DIGITIZERS_CONTACT_COUNT_MAXIMUM), + HID_USAGE(HID_USAGE_DIGITIZERS_PAD_TYPE), + HID_REPORT_SIZE(4), + HID_REPORT_COUNT(2), + HID_LOGICAL_MAX8(0x0F), + HID_FEATURE(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + + // PTPHQA Blob: Necessary for < Windows 10 + + // USAGE_PAGE (Vendor Defined) + ZMK_HID_USAGE_PAGE16(0xFF00), + HID_REPORT_ID(ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTPHQA), + // Vendor Usage (0xC5) + HID_USAGE(0xC5), + + HID_LOGICAL_MIN8(0), + HID_LOGICAL_MAX16(0xFF, 0x00), + HID_REPORT_SIZE(8), + + // Report Count (256) + HID_REPORT_COUNT16(0x0100), + + HID_FEATURE(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + HID_END_COLLECTION, + + // // Configuration collection + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + HID_USAGE(HID_USAGE_DIGITIZERS_DEVICE_CONFIGURATION), + HID_COLLECTION(HID_COLLECTION_APPLICATION), + + // PTP input mode report + HID_REPORT_ID(ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_MODE), + HID_USAGE(HID_USAGE_DIGITIZERS_FINGER), + HID_COLLECTION(HID_COLLECTION_LOGICAL), + HID_USAGE(HID_USAGE_DIGITIZERS_DEVICE_MODE), + HID_LOGICAL_MIN8(0), + HID_LOGICAL_MAX8(10), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(1), + HID_FEATURE(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + HID_END_COLLECTION, + + // PTP Selective reporting report + HID_USAGE(HID_USAGE_DIGITIZERS_FINGER), + HID_COLLECTION(HID_COLLECTION_PHYSICAL), + HID_REPORT_ID(ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_SELECTIVE), + + HID_USAGE(HID_USAGE_DIGITIZERS_SURFACE_SWITCH), + HID_USAGE(HID_USAGE_DIGITIZERS_BUTTON_SWITCH), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(2), + + HID_LOGICAL_MIN8(0), + HID_LOGICAL_MAX8(1), + HID_FEATURE(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + // Byte Padding + HID_REPORT_COUNT(6), + HID_FEATURE(ZMK_HID_MAIN_VAL_CONST | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS), + HID_END_COLLECTION, + HID_END_COLLECTION, +#endif +}; + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +struct zmk_ptp_finger { + // Touch Valid (bit 0) and tip switch (bit 1) + uint8_t touch_valid : 1; + uint8_t tip_switch : 1; + // Contact ID + uint8_t contact_id : 4; + uint8_t padding : 2; + // X + uint16_t x; + // Y + uint16_t y; +} __packed; + +struct zmk_hid_ptp_report_body { + // Finger reporting + struct zmk_ptp_finger fingers[CONFIG_ZMK_TRACKPAD_FINGERS]; + // scantime + uint16_t scan_time; + // Contact count + uint8_t contact_count : 4; + // Buttons + uint8_t button1 : 1; + uint8_t button2 : 1; + uint8_t button3 : 1; + + uint8_t padding : 1; +} __packed; + +// Report containing finger data +struct zmk_hid_ptp_report { + uint8_t report_id; + struct zmk_hid_ptp_report_body body; +} __packed; + +// Feature report for configuration + +struct zmk_hid_ptp_feature_selective_report_body { + // Selective reporting: Surface switch (bit 0), Button switch (bit 1) + uint8_t surface_switch : 1; + uint8_t button_switch : 1; + uint8_t padding : 6; +} __packed; + +struct zmk_hid_ptp_feature_selective_report { + uint8_t report_id; + struct zmk_hid_ptp_feature_selective_report_body body; +} __packed; + +// Feature report for mode +struct zmk_hid_ptp_feature_mode_report { + uint8_t report_id; + // input mode, 0 for mouse, 3 for trackpad + uint8_t mode; +} __packed; + +// Feature report for certification +struct zmk_hid_ptp_feature_certification_report { + uint8_t report_id; + + uint8_t ptphqa_blob[256]; +} __packed; + +#define PTP_PAD_TYPE_DEPRESSIBLE 0x00 +#define PTP_PAD_TYPE_PRESSURE 0x01 +#define PTP_PAD_TYPE_NON_CLICKABLE 0x02 + +// Feature report for device capabilities +struct zmk_hid_ptp_feature_capabilities_report_body { + // Max touches (L 4bit) and pad type (H 4bit): + // Max touches: number 3-5 + // Pad type: 0 for Depressible, 1 for Non-depressible, 2 for Non-clickable + uint8_t max_touches : 4; + uint8_t pad_type : 4; +} __packed; + +// Feature report for device capabilities +struct zmk_hid_ptp_feature_capabilities_report { + uint8_t report_id; + struct zmk_hid_ptp_feature_capabilities_report_body body; +} __packed; + +#endif + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +void zmk_hid_ptp_set(struct zmk_ptp_finger finger0, struct zmk_ptp_finger finger1, + struct zmk_ptp_finger finger2, struct zmk_ptp_finger finger3, + struct zmk_ptp_finger finger4, uint8_t contact_count, uint16_t scan_time, + uint8_t buttons); +int zmk_mouse_hid_set_ptp_finger(struct zmk_ptp_finger finger); +void zmk_mouse_hid_ptp_update_scan_time(void); +void zmk_mouse_hid_ptp_clear_lifted_fingers(void); + +struct zmk_hid_ptp_report *zmk_mouse_hid_get_ptp_report(); + +struct zmk_hid_ptp_feature_selective_report *zmk_mouse_hid_ptp_get_feature_selective_report(); +void zmk_mouse_hid_ptp_set_feature_selective_report(bool surface_switch, bool button_switch); +struct zmk_hid_ptp_feature_mode_report *zmk_mouse_hid_ptp_get_feature_mode_report(); +void zmk_mouse_hid_ptp_set_feature_mode(uint8_t mode); +struct zmk_hid_ptp_feature_certification_report * +zmk_mouse_hid_ptp_get_feature_certification_report(); +struct zmk_hid_ptp_feature_capabilities_report *zmk_mouse_hid_ptp_get_feature_capabilities_report(); + +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) diff --git a/app/include/zmk/mouse/hog.h b/app/include/zmk/mouse/hog.h new file mode 100644 index 00000000000..729828dc651 --- /dev/null +++ b/app/include/zmk/mouse/hog.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_mouse_hog_send_ptp_report(struct zmk_hid_ptp_report_body *body); +#endif // IS_ENABLED(CONFIG_ZMK_TRAACKPAD) \ No newline at end of file diff --git a/app/include/zmk/mouse/trackpad.h b/app/include/zmk/mouse/trackpad.h new file mode 100644 index 00000000000..d478a679852 --- /dev/null +++ b/app/include/zmk/mouse/trackpad.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +typedef uint8_t zmk_trackpad_finger_contacts_t; + +void zmk_trackpad_set_enabled(bool enabled); + +bool zmk_trackpad_get_enabled(); + +void zmk_trackpad_set_selective_report(bool surface_switch, bool button_switch, + struct zmk_endpoint_instance endpoint); + +void zmk_trackpad_set_mode_report(uint8_t *report, struct zmk_endpoint_instance endpoint); \ No newline at end of file diff --git a/app/include/zmk/mouse.h b/app/include/zmk/mouse/types.h similarity index 80% rename from app/include/zmk/mouse.h rename to app/include/zmk/mouse/types.h index d873f15689a..c898f001098 100644 --- a/app/include/zmk/mouse.h +++ b/app/include/zmk/mouse/types.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The ZMK Contributors + * Copyright (c) 2023 The ZMK Contributors * * SPDX-License-Identifier: MIT */ diff --git a/app/include/zmk/mouse/usb_hid.h b/app/include/zmk/mouse/usb_hid.h new file mode 100644 index 00000000000..9c7f6cab368 --- /dev/null +++ b/app/include/zmk/mouse/usb_hid.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_mouse_usb_hid_send_ptp_report(void); +#endif // IS_ENABLED(CONFIG_ZMK_TRAACKPAD) \ No newline at end of file diff --git a/app/include/zmk/stp_indicators.h b/app/include/zmk/stp_indicators.h new file mode 100644 index 00000000000..b0e7e348578 --- /dev/null +++ b/app/include/zmk/stp_indicators.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +struct zmk_led_hsb { + uint16_t h; + uint8_t s; + uint8_t b; +}; + +int zmk_stp_indicators_enable_batt(); +int zmk_stp_indicators_disable_batt(); \ No newline at end of file diff --git a/app/include/zmk/usb_hid.h b/app/include/zmk/usb_hid.h index c0cbc08a7ad..ab786b9a77c 100644 --- a/app/include/zmk/usb_hid.h +++ b/app/include/zmk/usb_hid.h @@ -10,7 +10,9 @@ int zmk_usb_hid_send_keyboard_report(void); int zmk_usb_hid_send_consumer_report(void); + #if IS_ENABLED(CONFIG_ZMK_MOUSE) -int zmk_usb_hid_send_mouse_report(void); +int zmk_mouse_usb_hid_send_mouse_report(void); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) + void zmk_usb_hid_set_protocol(uint8_t protocol); diff --git a/app/module/drivers/sensor/CMakeLists.txt b/app/module/drivers/sensor/CMakeLists.txt index cd1a1c45066..08c05aee5b5 100644 --- a/app/module/drivers/sensor/CMakeLists.txt +++ b/app/module/drivers/sensor/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery) add_subdirectory_ifdef(CONFIG_EC11 ec11) add_subdirectory_ifdef(CONFIG_ZMK_MAX17048 max17048) +add_subdirectory_ifdef(CONFIG_GEN4 gen4) \ No newline at end of file diff --git a/app/module/drivers/sensor/Kconfig b/app/module/drivers/sensor/Kconfig index ad570c58c94..7cc4d35a539 100644 --- a/app/module/drivers/sensor/Kconfig +++ b/app/module/drivers/sensor/Kconfig @@ -6,5 +6,6 @@ if SENSOR rsource "battery/Kconfig" rsource "ec11/Kconfig" rsource "max17048/Kconfig" +rsource "gen4/Kconfig" endif # SENSOR diff --git a/app/module/drivers/sensor/gen4/CMakeLists.txt b/app/module/drivers/sensor/gen4/CMakeLists.txt new file mode 100644 index 00000000000..1aea0d1aa0d --- /dev/null +++ b/app/module/drivers/sensor/gen4/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_include_directories(.) +include_directories(${CMAKE_SOURCE_DIR}/include) + +zephyr_library() + +zephyr_library_sources(gen4.c) \ No newline at end of file diff --git a/app/module/drivers/sensor/gen4/Kconfig b/app/module/drivers/sensor/gen4/Kconfig new file mode 100644 index 00000000000..9a8aa9a97bf --- /dev/null +++ b/app/module/drivers/sensor/gen4/Kconfig @@ -0,0 +1,51 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig GEN4 + bool "Cirque Gen4 trackpads" + depends on GPIO + depends on I2C + help + Enable driver for Cirque Gen4 trackpads + +if GEN4 + +choice + prompt "Trigger mode" + default GEN4_TRIGGER_NONE + help + Specify the type of triggering to be used by the driver. + +config GEN4_TRIGGER_NONE + bool "No trigger" + +config GEN4_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + depends on GPIO + select GEN4_TRIGGER + +config GEN4_TRIGGER_OWN_THREAD + bool "Use own thread" + depends on GPIO + select GEN4_TRIGGER + +endchoice + +config GEN4_TRIGGER + bool + +config GEN4_THREAD_PRIORITY + int "Thread priority" + depends on GEN4_TRIGGER_OWN_THREAD + default 10 + help + Priority of thread used by the driver to handle interrupts. + +config GEN4_THREAD_STACK_SIZE + int "Thread stack size" + depends on GEN4_TRIGGER_OWN_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + +endif # GEN4 \ No newline at end of file diff --git a/app/module/drivers/sensor/gen4/gen4.c b/app/module/drivers/sensor/gen4/gen4.c new file mode 100644 index 00000000000..b362926d0e0 --- /dev/null +++ b/app/module/drivers/sensor/gen4/gen4.c @@ -0,0 +1,279 @@ +#define DT_DRV_COMPAT cirque_gen4 + +#include +#include +#include + +#include +#include "gen4.h" + +LOG_MODULE_REGISTER(gen4, CONFIG_SENSOR_LOG_LEVEL); + +static int gen4_normal_read(const struct device *dev, uint8_t *buf, const uint8_t len) { + const struct gen4_config *config = dev->config; + return i2c_read_dt(&config->bus, buf, len); +} + +static int gen4_mouse_mode(const struct device *dev) { + const struct gen4_config *cfg = dev->config; + // enable mouse mode + uint8_t request2[10] = {0x05, 0x00, 0x34, 0x03, 0x06, 0x00, 0x04, 0x00, 0x04, 0x00}; + int ret = i2c_write_dt(&cfg->bus, request2, 10); + if (ret < 0) { + LOG_ERR("ext read status: %d", ret); + return ret; + } + return 0; +} + +static int gen4_abs_mode(const struct device *dev) { + const struct gen4_config *cfg = dev->config; + // enable mouse mode + uint8_t request2[10] = {0x05, 0x00, 0x34, 0x03, 0x06, 0x00, 0x04, 0x00, 0x04, 0x03}; + LOG_DBG("Entering abs mode"); + int ret = i2c_write_dt(&cfg->bus, request2, 10); + if (ret < 0) { + LOG_ERR("ext read status: %d", ret); + return ret; + } + return 0; +} + +static int gen4_i2c_init(const struct device *dev) { + const struct gen4_config *cfg = dev->config; + uint8_t request[2] = {0x20, 0x00}; + uint8_t buffer[30]; + int ret = i2c_write_read_dt(&cfg->bus, request, 2, buffer, 30); + if (ret < 0) { + LOG_ERR("ext read status: %d", ret); + return ret; + } + LOG_DBG("received value %x", buffer[2]); + + return gen4_mouse_mode(dev); +} + +static int gen4_attr_set(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, const struct sensor_value *val) { + struct gen4_data *data = dev->data; + data->mousemode = val->val1; + switch (val->val1) { + case 0: + LOG_DBG("Entering abs mode"); + return gen4_abs_mode(dev); + break; + case 1: + LOG_DBG("Entering mouse mode"); + return gen4_mouse_mode(dev); + break; + + default: + break; + } + return -EIO; +} + +static int gen4_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) { + const struct gen4_data *data = dev->data; + switch ((enum sensor_channel_gen4)chan) { + case SENSOR_CHAN_CONTACTS: + val->val1 = data->contacts; + break; + case SENSOR_CHAN_X: + val->val1 = data->finger.x; + break; + case SENSOR_CHAN_Y: + val->val1 = data->finger.y; + break; + case SENSOR_CHAN_CONFIDENCE_TIP: + val->val1 = data->finger.confidence_tip; + break; + case SENSOR_CHAN_FINGER: + val->val1 = data->finger_id; + break; + case SENSOR_CHAN_SCAN_TIME: + val->val1 = data->scan_time; + break; + case SENSOR_CHAN_BUTTONS: + val->val1 = data->btns; + break; + case SENSOR_CHAN_XDELTA: + val->val1 = data->mouse.xDelta; + break; + case SENSOR_CHAN_YDELTA: + val->val1 = data->mouse.yDelta; + break; + case SENSOR_CHAN_WHEEL: + val->val1 = data->mouse.scrollDelta; + break; + default: + return -ENOTSUP; + } + return 0; +} + +static int gen4_sample_fetch(const struct device *dev, enum sensor_channel) { + uint8_t packet[53]; + int ret; + ret = gen4_normal_read(dev, packet, 52); + if (ret < 0) { + LOG_ERR("read status: %d, retrying", ret); + ret = gen4_normal_read(dev, packet, 52); + if (ret != 0) + return ret; + } + + struct gen4_data *data = dev->data; + + if (data->mousemode) { + if (!(packet[REPORT_ID_SHIFT] == MOUSE_REPORT_ID)) { + return -EAGAIN; + } + data->btns = packet[3]; + data->mouse.xDelta = packet[4]; + data->mouse.yDelta = packet[5]; + data->mouse.scrollDelta = packet[6]; + } else { + if (!(packet[REPORT_ID_SHIFT] == PTP_REPORT_ID)) { + return -EAGAIN; + } + + uint16_t report_length = + packet[LENGTH_LOWBYTE_SHIFT] | (packet[LENGTH_HIGHBYTE_SHIFT] << 8); + + if (report_length == 12) { + data->scan_time = (uint16_t)packet[8] | (uint16_t)(packet[9] << 8); + data->contacts = packet[10]; + data->btns = packet[11]; + } else { + data->scan_time = (uint16_t)packet[9] | (uint16_t)(packet[10] << 8); + data->contacts = packet[11]; + data->btns = packet[12]; + } + + data->finger_id = (packet[3] & 0xFC) >> 2; + // LOG_DBG("FINGER ID: %d", data->finger_id); + // Finger data + data->finger.confidence_tip = (packet[3] & 0x03); + data->finger.x = (uint16_t)packet[4] | (uint16_t)(packet[5] << 8); + data->finger.y = (uint16_t)packet[6] | (uint16_t)(packet[7] << 8); + + // LOG_DBG("Finger palm/detected: %d", data->finger.confidence_tip); + // LOG_DBG("Finger x: %d", data->finger.x); + // LOG_DBG("Finger y: %d", data->finger.y); + } + return 0; +} + +#ifdef CONFIG_GEN4_TRIGGER +static void set_int(const struct device *dev, const bool en) { + const struct gen4_config *config = dev->config; + int ret = + gpio_pin_interrupt_configure_dt(&config->dr, en ? GPIO_INT_LEVEL_ACTIVE : GPIO_INT_DISABLE); + if (ret < 0) { + LOG_ERR("can't set interrupt"); + } +} + +static int gen4_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) { + struct gen4_data *data = dev->data; + + set_int(dev, false); + if (trig->type != SENSOR_TRIG_DATA_READY) { + return -ENOTSUP; + } + data->data_ready_trigger = trig; + data->data_ready_handler = handler; + set_int(dev, true); + return 0; +} + +static void gen4_int_cb(const struct device *dev) { + struct gen4_data *data = dev->data; + + // LOG_DBG("Gen4 interrupt trigd: %d", 0); + data->data_ready_handler(dev, data->data_ready_trigger); + LOG_DBG("Setting int on %d", 0); + set_int(dev, true); +} + +#ifdef CONFIG_GEN4_TRIGGER_OWN_THREAD +static void gen4_thread(void *arg) { + const struct device *dev = arg; + struct gen4_data *data = dev->data; + set_int(dev, false); + while (1) { + k_sem_take(&data->gpio_sem, K_FOREVER); + gen4_int_cb(dev); + } +} +#elif defined(CONFIG_GEN4_TRIGGER_GLOBAL_THREAD) +static void gen4_work_cb(struct k_work *work) { + struct gen4_data *data = CONTAINER_OF(work, struct gen4_data, work); + gen4_int_cb(data->dev); +} +#endif + +static void gen4_gpio_cb(const struct device *port, struct gpio_callback *cb, uint32_t pins) { + struct gen4_data *data = CONTAINER_OF(cb, struct gen4_data, gpio_cb); + set_int(data->dev, false); + LOG_DBG("Interrupt trigd"); +#if defined(CONFIG_GEN4_TRIGGER_OWN_THREAD) + k_sem_give(&data->gpio_sem); +#elif defined(CONFIG_GEN4_TRIGGER_GLOBAL_THREAD) + k_work_submit(&data->work); +#endif +} +#endif + +static int gen4_init(const struct device *dev) { + struct gen4_data *data = dev->data; + const struct gen4_config *config = dev->config; + + gen4_i2c_init(dev); + gen4_sample_fetch(dev, 0); +#ifdef CONFIG_GEN4_TRIGGER + data->dev = dev; + gpio_pin_configure_dt(&config->dr, GPIO_INPUT); + gpio_init_callback(&data->gpio_cb, gen4_gpio_cb, BIT(config->dr.pin)); + int ret = gpio_add_callback(config->dr.port, &data->gpio_cb); + if (ret < 0) { + LOG_ERR("Failed to set DR callback: %d", ret); + return -EIO; + } + +#if defined(CONFIG_GEN4_TRIGGER_OWN_THREAD) + k_sem_init(&data->gpio_sem, 0, UINT_MAX); + + k_thread_create(&data->thread, data->thread_stack, CONFIG_GEN4_THREAD_STACK_SIZE, + (k_thread_entry_t)gen4_thread, (void *)dev, 0, NULL, + K_PRIO_COOP(CONFIG_GEN4_THREAD_PRIORITY), 0, K_NO_WAIT); +#elif defined(CONFIG_GEN4_TRIGGER_GLOBAL_THREAD) + k_work_init(&data->work, gen4_work_cb); +#endif + set_int(dev, true); +#endif + return 0; +} + +static const struct sensor_driver_api gen4_driver_api = { +#if CONFIG_GEN4_TRIGGER + .trigger_set = gen4_trigger_set, +#endif + .attr_set = gen4_attr_set, + .sample_fetch = gen4_sample_fetch, + .channel_get = gen4_channel_get, +}; + +#define GEN4_INST(n) \ + static struct gen4_data gen4_data_##n; \ + static const struct gen4_config gen4_config_##n = { \ + .bus = I2C_DT_SPEC_INST_GET(n), \ + COND_CODE_1(CONFIG_GEN4_TRIGGER, (.dr = GPIO_DT_SPEC_GET(DT_DRV_INST(0), dr_gpios), ), \ + ())}; \ + DEVICE_DT_INST_DEFINE(n, gen4_init, NULL, &gen4_data_##n, &gen4_config_##n, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &gen4_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(GEN4_INST) \ No newline at end of file diff --git a/app/module/drivers/sensor/gen4/gen4.h b/app/module/drivers/sensor/gen4/gen4.h new file mode 100644 index 00000000000..76012709ef4 --- /dev/null +++ b/app/module/drivers/sensor/gen4/gen4.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include + +#define LENGTH_LOWBYTE_SHIFT 0 +#define LENGTH_HIGHBYTE_SHIFT 1 +#define REPORT_ID_SHIFT 2 + +#define PTP_REPORT_ID (0x01) +#define MOUSE_REPORT_ID 0x06 + +#define GEN4_ADDRESS 0x2C + +struct gen4_finger_data { + uint8_t confidence_tip; + uint16_t x, y; +}; + +struct gen4_mouse_data { + int8_t xDelta; + int8_t yDelta; /**< Change in vertical movement */ + int8_t scrollDelta; /**< Vertical Scroll value */ +}; + +struct gen4_data { + uint8_t contacts, btns, finger_id; + uint16_t scan_time; + struct gen4_finger_data finger; + struct gen4_mouse_data mouse; + bool mousemode; + bool in_int; +#ifdef CONFIG_GEN4_TRIGGER + const struct device *dev; + const struct sensor_trigger *data_ready_trigger; + struct gpio_callback gpio_cb; + sensor_trigger_handler_t data_ready_handler; +#if defined(CONFIG_GEN4_TRIGGER_OWN_THREAD) + K_THREAD_STACK_MEMBER(thread_stack, CONFIG_GEN4_THREAD_STACK_SIZE); + struct k_sem gpio_sem; + struct k_thread thread; +#elif defined(CONFIG_GEN4_TRIGGER_GLOBAL_THREAD) + struct k_work work; +#endif +#endif +}; + +struct gen4_config { +#if DT_INST_ON_BUS(0, i2c) + const struct i2c_dt_spec bus; +#endif +#ifdef CONFIG_GEN4_TRIGGER + const struct gpio_dt_spec dr; +#endif +}; diff --git a/app/module/dts/bindings/sensor/cirque,gen4.yaml b/app/module/dts/bindings/sensor/cirque,gen4.yaml new file mode 100644 index 00000000000..92bdb4e8c2e --- /dev/null +++ b/app/module/dts/bindings/sensor/cirque,gen4.yaml @@ -0,0 +1,9 @@ +description: | + Sensor driver for the Cirque Gen4 trackpad ASICs +compatible: "cirque,gen4" +include: ["i2c-device.yaml"] + +properties: + dr-gpios: + type: phandle-array + description: Data ready pin for the trackpad diff --git a/app/module/dts/bindings/vendor-prefixes.txt b/app/module/dts/bindings/vendor-prefixes.txt new file mode 100644 index 00000000000..4466d52c1e9 --- /dev/null +++ b/app/module/dts/bindings/vendor-prefixes.txt @@ -0,0 +1 @@ +cirque Cirque Corporation 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..eaf84a766af --- /dev/null +++ b/app/src/behaviors/behavior_input_two_axis.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_input_two_axis + +#include +#include +#include +#include +#include // CLAMP + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +struct vector2d { + float x; + float y; +}; + +struct movement_state_1d { + float remainder; + int16_t speed; + uint64_t start_time; +}; + +struct movement_state_2d { + struct movement_state_1d x; + struct movement_state_1d y; +}; + +struct behavior_input_two_axis_data { + struct k_work_delayable tick_work; + const struct device *dev; + + struct movement_state_2d state; +}; + +struct behavior_input_two_axis_config { + int16_t x_code; + int16_t y_code; + uint16_t delay_ms; + uint16_t time_to_max_speed_ms; + uint8_t trigger_period_ms; + // acceleration exponent 0: uniform speed + // acceleration exponent 1: uniform acceleration + // acceleration exponent 2: uniform jerk + uint8_t acceleration_exponent; +}; + +#if CONFIG_MINIMAL_LIBC +static float powf(float base, float exponent) { + // poor man's power implementation rounds the exponent down to the nearest integer. + float power = 1.0f; + for (; exponent >= 1.0f; exponent--) { + power = power * base; + } + return power; +} +#else +#include +#endif + +static int64_t ms_since_start(int64_t start, int64_t now, int64_t delay) { + if (start == 0) { + return 0; + } + int64_t move_duration = now - (start + delay); + // start can be in the future if there's a delay + if (move_duration < 0) { + move_duration = 0; + } + return move_duration; +} + +static float speed(const struct behavior_input_two_axis_config *config, float max_speed, + int64_t duration_ms) { + // Calculate the speed based on MouseKeysAccel + // See https://en.wikipedia.org/wiki/Mouse_keys + if (duration_ms == 0) { + return 0; + } + + if (duration_ms > config->time_to_max_speed_ms || config->time_to_max_speed_ms == 0 || + config->acceleration_exponent == 0) { + return max_speed; + } + float time_fraction = (float)duration_ms / config->time_to_max_speed_ms; + return max_speed * powf(time_fraction, config->acceleration_exponent); +} + +static void track_remainder(float *move, float *remainder) { + float new_move = *move + *remainder; + *remainder = new_move - (int)new_move; + *move = (int)new_move; +} + +static float update_movement_1d(const struct behavior_input_two_axis_config *config, + struct movement_state_1d *state, int64_t now) { + float move = 0; + if (state->speed == 0) { + state->remainder = 0; + return move; + } + + int64_t move_duration = ms_since_start(state->start_time, now, config->delay_ms); + move = (move_duration > 0) + ? (speed(config, state->speed, move_duration) * config->trigger_period_ms / 1000) + : 0; + + track_remainder(&(move), &(state->remainder)); + + return move; +} +static struct vector2d update_movement_2d(const struct behavior_input_two_axis_config *config, + struct movement_state_2d *state, int64_t now) { + struct vector2d move = {0}; + + move = (struct vector2d){ + .x = update_movement_1d(config, &state->x, now), + .y = update_movement_1d(config, &state->y, now), + }; + + return move; +} + +static bool is_non_zero_1d_movement(int16_t speed) { return speed != 0; } + +static bool is_non_zero_2d_movement(struct movement_state_2d *state) { + return is_non_zero_1d_movement(state->x.speed) || is_non_zero_1d_movement(state->y.speed); +} + +static bool should_be_working(struct behavior_input_two_axis_data *data) { + return is_non_zero_2d_movement(&data->state); +} + +static void tick_work_cb(struct k_work *work) { + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct behavior_input_two_axis_data *data = + CONTAINER_OF(d_work, struct behavior_input_two_axis_data, tick_work); + const struct device *dev = data->dev; + const struct behavior_input_two_axis_config *cfg = dev->config; + + uint64_t timestamp = k_uptime_get(); + + LOG_INF("x start: %llu, y start: %llu, current timestamp: %llu", data->state.x.start_time, + data->state.y.start_time, timestamp); + + struct vector2d move = update_movement_2d(cfg, &data->state, timestamp); + + int ret = 0; + bool have_x = is_non_zero_1d_movement(move.x); + bool have_y = is_non_zero_1d_movement(move.y); + if (have_x) { + ret = input_report_rel(dev, cfg->x_code, (int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX), + !have_y, K_NO_WAIT); + } + if (have_y) { + ret = input_report_rel(dev, cfg->y_code, (int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX), true, + K_NO_WAIT); + } + + if (should_be_working(data)) { + k_work_schedule(&data->tick_work, K_MSEC(cfg->trigger_period_ms)); + } +} + +static void set_start_times_for_activity_1d(struct movement_state_1d *state) { + if (state->speed != 0 && state->start_time == 0) { + state->start_time = k_uptime_get(); + } else if (state->speed == 0) { + state->start_time = 0; + } +} +static void set_start_times_for_activity(struct movement_state_2d *state) { + set_start_times_for_activity_1d(&state->x); + set_start_times_for_activity_1d(&state->y); +} + +static void update_work_scheduling(const struct device *dev) { + struct behavior_input_two_axis_data *data = dev->data; + const struct behavior_input_two_axis_config *cfg = dev->config; + + set_start_times_for_activity(&data->state); + + if (should_be_working(data)) { + k_work_schedule(&data->tick_work, K_MSEC(cfg->trigger_period_ms)); + } else { + k_work_cancel_delayable(&data->tick_work); + } +} + +int behavior_input_two_axis_adjust_speed(const struct device *dev, int16_t dx, int16_t dy) { + struct behavior_input_two_axis_data *data = dev->data; + + LOG_DBG("Adjusting: %d %d", dx, dy); + data->state.x.speed += dx; + data->state.y.speed += dy; + + LOG_DBG("After: %d %d", data->state.x.speed, data->state.y.speed); + + update_work_scheduling(dev); + + return 0; +} + +static int behavior_input_two_axis_init(const struct device *dev) { + struct behavior_input_two_axis_data *data = dev->data; + + data->dev = dev; + k_work_init_delayable(&data->tick_work, tick_work_cb); + + return 0; +}; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + + const struct device *behavior_dev = zmk_behavior_get_binding(binding->behavior_dev); + + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + int16_t x = MOVE_X_DECODE(binding->param1); + int16_t y = MOVE_Y_DECODE(binding->param1); + + behavior_input_two_axis_adjust_speed(behavior_dev, x, y); + return 0; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *behavior_dev = zmk_behavior_get_binding(binding->behavior_dev); + + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + int16_t x = MOVE_X_DECODE(binding->param1); + int16_t y = MOVE_Y_DECODE(binding->param1); + + behavior_input_two_axis_adjust_speed(behavior_dev, -x, -y); + return 0; +} + +static const struct behavior_driver_api behavior_input_two_axis_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define ITA_INST(n) \ + static struct behavior_input_two_axis_data behavior_input_two_axis_data_##n = {}; \ + static struct behavior_input_two_axis_config behavior_input_two_axis_config_##n = { \ + .x_code = DT_INST_PROP(n, x_input_code), \ + .y_code = DT_INST_PROP(n, y_input_code), \ + .trigger_period_ms = DT_INST_PROP(n, trigger_period_ms), \ + .delay_ms = DT_INST_PROP_OR(n, delay_ms, 0), \ + .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ + .acceleration_exponent = DT_INST_PROP_OR(n, acceleration_exponent, 1), \ + }; \ + BEHAVIOR_DT_INST_DEFINE( \ + n, behavior_input_two_axis_init, NULL, &behavior_input_two_axis_data_##n, \ + &behavior_input_two_axis_config_##n, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_input_two_axis_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(ITA_INST) diff --git a/app/src/behaviors/behavior_mouse_key_press.c b/app/src/behaviors/behavior_mouse_key_press.c index 9064a1aa5c8..8f8df2a72f3 100644 --- a/app/src/behaviors/behavior_mouse_key_press.c +++ b/app/src/behaviors/behavior_mouse_key_press.c @@ -11,8 +11,9 @@ #include #include -#include -#include +#include +#include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -20,19 +21,31 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static int behavior_mouse_key_press_init(const struct device *dev) { return 0; }; +static void process_key_state(const struct device *dev, int32_t val, bool pressed) { + for (int i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if (val & BIT(i)) { + WRITE_BIT(val, i, 0); + input_report_key(dev, INPUT_BTN_0 + i, pressed ? 1 : 0, val == 0, K_FOREVER); + } + } +} + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); - return raise_zmk_mouse_button_state_changed_from_encoded(binding->param1, true, - event.timestamp); + process_key_state(zmk_behavior_get_binding(binding->behavior_dev), binding->param1, true); + + return 0; } static int on_keymap_binding_released(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); - return raise_zmk_mouse_button_state_changed_from_encoded(binding->param1, false, - event.timestamp); + + process_key_state(zmk_behavior_get_binding(binding->behavior_dev), binding->param1, false); + + return 0; } static const struct behavior_driver_api behavior_mouse_key_press_driver_api = { diff --git a/app/src/behaviors/behavior_stp_indicators.c b/app/src/behaviors/behavior_stp_indicators.c new file mode 100644 index 00000000000..0ed6f0d2a01 --- /dev/null +++ b/app/src/behaviors/behavior_stp_indicators.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_stp_indicators + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_stp_indicators_init(const struct device *dev) { return 0; } + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + switch (binding->param1) { + case STP_BAT: + return zmk_stp_indicators_enable_batt(); + } + + return -ENOTSUP; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + switch (binding->param1) { + case STP_BAT: + return zmk_stp_indicators_disable_batt(); + } + return ZMK_BEHAVIOR_OPAQUE; +} + +static const struct behavior_driver_api behavior_stp_indicators_driver_api = { + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released, +}; + +BEHAVIOR_DT_INST_DEFINE(0, behavior_stp_indicators_init, NULL, NULL, NULL, POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_stp_indicators_driver_api); + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/ble.c b/app/src/ble.c index 776730fe5c3..ff124dc9d11 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -84,7 +84,10 @@ static bt_addr_le_t peripheral_addrs[ZMK_SPLIT_BLE_PERIPHERAL_COUNT]; static void raise_profile_changed_event(void) { raise_zmk_ble_active_profile_changed((struct zmk_ble_active_profile_changed){ - .index = active_profile, .profile = &profiles[active_profile]}); + .index = active_profile, + .profile = &profiles[active_profile], + .open = zmk_ble_active_profile_is_open(), + .connected = zmk_ble_active_profile_is_connected()}); } static void raise_profile_changed_event_callback(struct k_work *work) { diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 652438531f0..e3150604c68 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -116,9 +119,7 @@ int zmk_endpoints_toggle_transport(void) { return zmk_endpoints_select_transport(new_transport); } -struct zmk_endpoint_instance zmk_endpoints_selected(void) { - return current_instance; -} +struct zmk_endpoint_instance zmk_endpoints_selected(void) { return current_instance; } static int send_keyboard_report(void) { switch (current_instance.transport) { @@ -208,7 +209,7 @@ int zmk_endpoints_send_mouse_report() { switch (current_instance.transport) { case ZMK_TRANSPORT_USB: { #if IS_ENABLED(CONFIG_ZMK_USB) - int err = zmk_usb_hid_send_mouse_report(); + int err = zmk_mouse_usb_hid_send_mouse_report(); if (err) { LOG_ERR("FAILED TO SEND OVER USB: %d", err); } @@ -221,8 +222,8 @@ int zmk_endpoints_send_mouse_report() { case ZMK_TRANSPORT_BLE: { #if IS_ENABLED(CONFIG_ZMK_BLE) - struct zmk_hid_mouse_report *mouse_report = zmk_hid_get_mouse_report(); - int err = zmk_hog_send_mouse_report(&mouse_report->body); + struct zmk_hid_mouse_report *mouse_report = zmk_mouse_hid_get_mouse_report(); + int err = zmk_mouse_hog_send_mouse_report(&mouse_report->body); if (err) { LOG_ERR("FAILED TO SEND OVER HOG: %d", err); } @@ -239,6 +240,43 @@ int zmk_endpoints_send_mouse_report() { } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_endpoints_send_ptp_report() { + switch (current_instance.transport) { + case ZMK_TRANSPORT_USB: { +#if IS_ENABLED(CONFIG_ZMK_USB) + LOG_DBG("Send PTP USB REPORT"); + int err = zmk_mouse_usb_hid_send_ptp_report(); + if (err) { + LOG_ERR("FAILED TO SEND OVER USB: %d", err); + } + return err; +#else + LOG_ERR("USB endpoint is not supported"); + return -ENOTSUP; +#endif /* IS_ENABLED(CONFIG_ZMK_USB) */ + } + + case ZMK_TRANSPORT_BLE: { +#if IS_ENABLED(CONFIG_ZMK_BLE) + struct zmk_hid_ptp_report *mouse_report = zmk_mouse_hid_get_ptp_report(); + int err = zmk_mouse_hog_send_ptp_report(&mouse_report->body); + if (err) { + LOG_ERR("FAILED TO SEND OVER HOG: %d", err); + } + return err; +#else + LOG_ERR("BLE HOG endpoint is not supported"); + return -ENOTSUP; +#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */ + } + } + + LOG_ERR("Unhandled endpoint transport %d", current_instance.transport); + return -ENOTSUP; +} +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + #if IS_ENABLED(CONFIG_SETTINGS) static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb, diff --git a/app/src/hid.c b/app/src/hid.c index 24572ad325b..b48afefa41c 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -27,8 +27,107 @@ static uint8_t keys_held = 0; #if IS_ENABLED(CONFIG_ZMK_MOUSE) -static struct zmk_hid_mouse_report mouse_report = {.report_id = ZMK_HID_REPORT_ID_MOUSE, - .body = {.buttons = 0}}; +static struct zmk_hid_mouse_report mouse_report = { + .report_id = ZMK_MOUSE_HID_REPORT_ID_MOUSE, + .body = {.buttons = 0, .d_x = 0, .d_y = 0, .d_scroll_y = 0}}; + +// Keep track of how often a button was pressed. +// Only release the button if the count is 0. +static int explicit_button_counts[5] = {0, 0, 0, 0, 0}; +static zmk_mod_flags_t explicit_buttons = 0; + +#define SET_MOUSE_BUTTONS(btns) \ + { \ + mouse_report.body.buttons = btns; \ + LOG_DBG("Mouse buttons set to 0x%02X", mouse_report.body.buttons); \ + } + +int zmk_hid_mouse_button_press(zmk_mouse_button_t button) { + if (button >= ZMK_MOUSE_HID_NUM_BUTTONS) { + return -EINVAL; + } + + explicit_button_counts[button]++; + LOG_DBG("Button %d count %d", button, explicit_button_counts[button]); + WRITE_BIT(explicit_buttons, button, true); + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_button_release(zmk_mouse_button_t button) { + if (button >= ZMK_MOUSE_HID_NUM_BUTTONS) { + return -EINVAL; + } + + if (explicit_button_counts[button] <= 0) { + LOG_ERR("Tried to release button %d too often", button); + return -EINVAL; + } + explicit_button_counts[button]--; + LOG_DBG("Button %d count: %d", button, explicit_button_counts[button]); + if (explicit_button_counts[button] == 0) { + LOG_DBG("Button %d released", button); + WRITE_BIT(explicit_buttons, button, false); + } + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons) { + for (zmk_mouse_button_t i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if (buttons & BIT(i)) { + zmk_hid_mouse_button_press(i); + } + } + return 0; +} + +int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { + for (zmk_mouse_button_t i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if (buttons & BIT(i)) { + zmk_hid_mouse_button_release(i); + } + } + return 0; +} + +void zmk_hid_mouse_movement_set(int16_t x, int16_t y) { + mouse_report.body.d_x = x; + mouse_report.body.d_y = y; + LOG_DBG("Mouse movement set to %d/%d", mouse_report.body.d_x, mouse_report.body.d_y); +} + +void zmk_hid_mouse_movement_update(int16_t x, int16_t y) { + mouse_report.body.d_x += x; + mouse_report.body.d_y += y; + LOG_DBG("Mouse movement updated to %d/%d", mouse_report.body.d_x, mouse_report.body.d_y); +} + +void zmk_hid_mouse_scroll_set(int8_t x, int8_t y) { + // mouse_report.body.d_scroll_x = x; + mouse_report.body.d_scroll_y = y; + LOG_DBG("Mouse scroll set to %d/%d", 0, mouse_report.body.d_scroll_y); +} + +void zmk_hid_mouse_scroll_update(int8_t x, int8_t y) { + // mouse_report.body.d_scroll_x += x; + mouse_report.body.d_scroll_y += y; + LOG_DBG("Mouse scroll updated to X: %d/%d", 0, 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)); +} + +void zmk_hid_mouse_set(uint8_t buttons, int8_t xDelta, int8_t yDelta, int8_t scrollDelta) { + mouse_report.body.buttons = buttons; + mouse_report.body.d_x = xDelta; + mouse_report.body.d_y = yDelta; + mouse_report.body.d_scroll_y = scrollDelta; +} + +struct zmk_hid_mouse_report *zmk_mouse_hid_get_mouse_report(void) { return &mouse_report; } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) @@ -370,83 +469,6 @@ bool zmk_hid_is_pressed(uint32_t usage) { return false; } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) - -// Keep track of how often a button was pressed. -// Only release the button if the count is 0. -static int explicit_button_counts[5] = {0, 0, 0, 0, 0}; -static zmk_mod_flags_t explicit_buttons = 0; - -#define SET_MOUSE_BUTTONS(btns) \ - { \ - mouse_report.body.buttons = btns; \ - LOG_DBG("Mouse buttons set to 0x%02X", mouse_report.body.buttons); \ - } - -int zmk_hid_mouse_button_press(zmk_mouse_button_t button) { - if (button >= ZMK_HID_MOUSE_NUM_BUTTONS) { - return -EINVAL; - } - - explicit_button_counts[button]++; - LOG_DBG("Button %d count %d", button, explicit_button_counts[button]); - WRITE_BIT(explicit_buttons, button, true); - SET_MOUSE_BUTTONS(explicit_buttons); - return 0; -} - -int zmk_hid_mouse_button_release(zmk_mouse_button_t button) { - if (button >= ZMK_HID_MOUSE_NUM_BUTTONS) { - return -EINVAL; - } - - if (explicit_button_counts[button] <= 0) { - LOG_ERR("Tried to release button %d too often", button); - return -EINVAL; - } - explicit_button_counts[button]--; - LOG_DBG("Button %d count: %d", button, explicit_button_counts[button]); - if (explicit_button_counts[button] == 0) { - LOG_DBG("Button %d released", button); - WRITE_BIT(explicit_buttons, button, false); - } - SET_MOUSE_BUTTONS(explicit_buttons); - return 0; -} - -int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons) { - for (zmk_mouse_button_t i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) { - if (buttons & BIT(i)) { - zmk_hid_mouse_button_press(i); - } - } - return 0; -} - -int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { - for (zmk_mouse_button_t i = 0; i < ZMK_HID_MOUSE_NUM_BUTTONS; i++) { - if (buttons & BIT(i)) { - zmk_hid_mouse_button_release(i); - } - } - return 0; -} -void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { return &keyboard_report; } -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) - -struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { - return &mouse_report; -} - -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { return &consumer_report; } diff --git a/app/src/hog.c b/app/src/hog.c index 82fafc29cd7..03b4b155803 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -72,11 +72,18 @@ static struct hids_report consumer_input = { #if IS_ENABLED(CONFIG_ZMK_MOUSE) static struct hids_report mouse_input = { - .id = ZMK_HID_REPORT_ID_MOUSE, + .id = ZMK_MOUSE_HID_REPORT_ID_MOUSE, .type = HIDS_INPUT, }; -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) +static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + struct zmk_hid_mouse_report_body *report_body = &zmk_mouse_hid_get_mouse_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_mouse_report_body)); +} + +#endif static bool host_requests_notification = false; static uint8_t ctrl_point; @@ -143,15 +150,6 @@ static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, sizeof(struct zmk_hid_consumer_report_body)); } -#if IS_ENABLED(CONFIG_ZMK_MOUSE) -static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, - void *buf, uint16_t len, uint16_t offset) { - struct zmk_hid_mouse_report_body *report_body = &zmk_hid_get_mouse_report()->body; - return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, - sizeof(struct zmk_hid_mouse_report_body)); -} -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - // static ssize_t write_proto_mode(struct bt_conn *conn, // const struct bt_gatt_attr *attr, // const void *buf, uint16_t len, uint16_t offset, @@ -199,15 +197,13 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, NULL, &consumer_input), - #if IS_ENABLED(CONFIG_ZMK_MOUSE) BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, read_hids_mouse_input_report, NULL, NULL), BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, NULL, &mouse_input), -#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) - +#endif #if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, @@ -220,9 +216,24 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); +static struct bt_conn *destination_connection(void) { + struct bt_conn *conn; + bt_addr_le_t *addr = zmk_ble_active_profile_addr(); + LOG_DBG("Address pointer %p", addr); + if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { + LOG_WRN("Not sending, no active address for current profile"); + return NULL; + } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { + LOG_WRN("Not sending, not connected to active profile"); + return NULL; + } + + return conn; +} + K_THREAD_STACK_DEFINE(hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE); -struct k_work_q hog_work_q; +static struct k_work_q hog_work_q; K_MSGQ_DEFINE(zmk_hog_keyboard_msgq, sizeof(struct zmk_hid_keyboard_report_body), CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE, 4); @@ -336,7 +347,7 @@ K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body), void send_mouse_report_callback(struct k_work *work) { struct zmk_hid_mouse_report_body report; while (k_msgq_get(&zmk_hog_mouse_msgq, &report, K_NO_WAIT) == 0) { - struct bt_conn *conn = zmk_ble_active_profile_conn(); + struct bt_conn *conn = destination_connection(); if (conn == NULL) { return; } @@ -360,7 +371,7 @@ void send_mouse_report_callback(struct k_work *work) { K_WORK_DEFINE(hog_mouse_work, send_mouse_report_callback); -int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { +int zmk_mouse_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { int err = k_msgq_put(&zmk_hog_mouse_msgq, report, K_MSEC(100)); if (err) { switch (err) { @@ -368,7 +379,7 @@ int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { LOG_WRN("Consumer message queue full, popping first message and queueing again"); struct zmk_hid_mouse_report_body discarded_report; k_msgq_get(&zmk_hog_mouse_msgq, &discarded_report, K_NO_WAIT); - return zmk_hog_send_mouse_report(report); + return zmk_mouse_hog_send_mouse_report(report); } default: LOG_WRN("Failed to queue mouse report to send (%d)", err); @@ -380,7 +391,6 @@ int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { return 0; }; - #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) static int zmk_hog_init(void) { 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/mouse/CMakeLists.txt b/app/src/mouse/CMakeLists.txt new file mode 100644 index 00000000000..bf6fda8b8a7 --- /dev/null +++ b/app/src/mouse/CMakeLists.txt @@ -0,0 +1,6 @@ +if(CONFIG_ZMK_MOUSE) + target_sources(app PRIVATE hid.c) + target_sources_ifdef(CONFIG_ZMK_TRACKPAD app PRIVATE trackpad.c) + target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE usb_hid.c) + target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE hog.c) +endif() \ No newline at end of file diff --git a/app/src/mouse/Kconfig b/app/src/mouse/Kconfig new file mode 100644 index 00000000000..1f0d2f1e878 --- /dev/null +++ b/app/src/mouse/Kconfig @@ -0,0 +1,57 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig ZMK_MOUSE + bool "Mouse Emulation" + select INPUT + select INPUT_THREAD_PRIORITY_OVERRIDE + select USB_COMPOSITE_DEVICE if ZMK_USB + +if ZMK_MOUSE + +if ZMK_USB + +config USB_HID_DEVICE_COUNT + default 2 + +endif + +config ZMK_TRACKPAD + bool "Trackpad support" + # select ENABLE_HID_INT_OUT_EP if ZMK_USB + +if ZMK_TRACKPAD + +if ZMK_USB + +config USB_REQUEST_BUFFER_SIZE + default 512 + +config HID_INTERRUPT_EP_MPS + default 64 + +endif + +config ZMK_TRACKPAD_FINGERS + int "Max simultaneous fingers" + range 3 5 + default 5 + +config ZMK_TRACKPAD_LOGICAL_X + int "Logical X" + +config ZMK_TRACKPAD_LOGICAL_Y + int "Logical Y" + +config ZMK_TRACKPAD_PHYSICAL_X + int "Physical X" + +config ZMK_TRACKPAD_PHYSICAL_Y + int "Physical Y" + +config ZMK_TRACKPAD_REVERSE_SCROLL + bool "Reverse scroll dir in mouse mode" + +endif # ZMK_TRACKPAD + +endif # ZMK_MOUSE \ No newline at end of file diff --git a/app/src/mouse/hid.c b/app/src/mouse/hid.c new file mode 100644 index 00000000000..ca910aab0d7 --- /dev/null +++ b/app/src/mouse/hid.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +#include + +#define FINGER_INIT(idx, _rest) \ + { .contact_id = idx } + +static struct zmk_hid_ptp_report ptp_report = { + .report_id = ZMK_MOUSE_HID_REPORT_ID_DIGITIZER, +}; + +void zmk_hid_ptp_set(struct zmk_ptp_finger finger0, struct zmk_ptp_finger finger1, + struct zmk_ptp_finger finger2, struct zmk_ptp_finger finger3, + struct zmk_ptp_finger finger4, uint8_t contact_count, uint16_t scan_time, + uint8_t buttons) { + ptp_report.body.fingers[0] = finger0; + ptp_report.body.fingers[1] = finger1; + ptp_report.body.fingers[2] = finger2; +#if CONFIG_ZMK_TRACKPAD_FINGERS > 3 + ptp_report.body.fingers[3] = finger3; +#endif +#if CONFIG_ZMK_TRACKPAD_FINGERS > 4 + ptp_report.body.fingers[4] = finger4; +#endif + ptp_report.body.contact_count = contact_count; + ptp_report.body.scan_time = scan_time; + ptp_report.body.button1 = buttons & BIT(0); + ptp_report.body.button2 = buttons & BIT(1); + ptp_report.body.button3 = buttons & BIT(2); +} + +int zmk_mouse_hid_set_ptp_finger(struct zmk_ptp_finger finger) { + // First try to update existing + for (int i = 0; i < ptp_report.body.contact_count; i++) { + if (ptp_report.body.fingers[i].contact_id == finger.contact_id) { + ptp_report.body.fingers[i] = finger; + return 0; + } + } + + if (ptp_report.body.contact_count == CONFIG_ZMK_TRACKPAD_FINGERS) { + return -ENOMEM; + } + + ptp_report.body.fingers[ptp_report.body.contact_count++] = finger; + + return 0; +} + +void zmk_mouse_hid_ptp_clear_lifted_fingers(void) { + int valid_count = 0; + for (int i = 0; i < ptp_report.body.contact_count; i++) { + if (!ptp_report.body.fingers[i].tip_switch) { + continue; + } + + ptp_report.body.fingers[valid_count++] = ptp_report.body.fingers[i]; + } + + for (int i = valid_count; i < ptp_report.body.contact_count; i++) { + memset(&ptp_report.body.fingers[i], 0, sizeof(struct zmk_ptp_finger)); + } + + ptp_report.body.contact_count = valid_count; + + if (ptp_report.body.contact_count == 0) { + ptp_report.body.scan_time = 0; + } +} + +void zmk_mouse_hid_ptp_update_scan_time(void) { + if (ptp_report.body.contact_count > 0) { + // scan time is in 100 microsecond units + ptp_report.body.scan_time = (uint16_t)((k_uptime_get_32() * 10) & 0xFFFF); + } else { + ptp_report.body.scan_time = 0; + } +} + +void zmk_mouse_hid_ptp_clear(void) { + memset(&ptp_report.body, sizeof(struct zmk_hid_ptp_report_body), 0); +} + +struct zmk_hid_ptp_report *zmk_mouse_hid_get_ptp_report() { return &ptp_report; } + +struct zmk_hid_ptp_feature_selective_report selective_report = { + .report_id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_SELECTIVE, + .body = + { + .surface_switch = 1, + .button_switch = 1, + }, +}; + +struct zmk_hid_ptp_feature_selective_report *zmk_mouse_hid_ptp_get_feature_selective_report() { + return &selective_report; +} + +void zmk_mouse_hid_ptp_set_feature_selective_report(bool surface_switch, bool button_switch) { + selective_report.body.surface_switch = surface_switch; + selective_report.body.button_switch = button_switch; + LOG_DBG("Setting selective reporting to: %d, %d", surface_switch, button_switch); +} + +struct zmk_hid_ptp_feature_mode_report mode_report = { + .report_id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_MODE, + .mode = 0, +}; + +struct zmk_hid_ptp_feature_mode_report *zmk_mouse_hid_ptp_get_feature_mode_report() { + return &mode_report; +} + +void zmk_mouse_hid_ptp_set_feature_mode(uint8_t mode) { mode_report.mode = mode; } + +static struct zmk_hid_ptp_feature_certification_report cert_report = { + .report_id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTPHQA, + .ptphqa_blob = {0xfc, 0x28, 0xfe, 0x84, 0x40, 0xcb, 0x9a, 0x87, 0x0d, 0xbe, 0x57, 0x3c, 0xb6, + 0x70, 0x09, 0x88, 0x07, 0x97, 0x2d, 0x2b, 0xe3, 0x38, 0x34, 0xb6, 0x6c, 0xed, + 0xb0, 0xf7, 0xe5, 0x9c, 0xf6, 0xc2, 0x2e, 0x84, 0x1b, 0xe8, 0xb4, 0x51, 0x78, + 0x43, 0x1f, 0x28, 0x4b, 0x7c, 0x2d, 0x53, 0xaf, 0xfc, 0x47, 0x70, 0x1b, 0x59, + 0x6f, 0x74, 0x43, 0xc4, 0xf3, 0x47, 0x18, 0x53, 0x1a, 0xa2, 0xa1, 0x71, 0xc7, + 0x95, 0x0e, 0x31, 0x55, 0x21, 0xd3, 0xb5, 0x1e, 0xe9, 0x0c, 0xba, 0xec, 0xb8, + 0x89, 0x19, 0x3e, 0xb3, 0xaf, 0x75, 0x81, 0x9d, 0x53, 0xb9, 0x41, 0x57, 0xf4, + 0x6d, 0x39, 0x25, 0x29, 0x7c, 0x87, 0xd9, 0xb4, 0x98, 0x45, 0x7d, 0xa7, 0x26, + 0x9c, 0x65, 0x3b, 0x85, 0x68, 0x89, 0xd7, 0x3b, 0xbd, 0xff, 0x14, 0x67, 0xf2, + 0x2b, 0xf0, 0x2a, 0x41, 0x54, 0xf0, 0xfd, 0x2c, 0x66, 0x7c, 0xf8, 0xc0, 0x8f, + 0x33, 0x13, 0x03, 0xf1, 0xd3, 0xc1, 0x0b, 0x89, 0xd9, 0x1b, 0x62, 0xcd, 0x51, + 0xb7, 0x80, 0xb8, 0xaf, 0x3a, 0x10, 0xc1, 0x8a, 0x5b, 0xe8, 0x8a, 0x56, 0xf0, + 0x8c, 0xaa, 0xfa, 0x35, 0xe9, 0x42, 0xc4, 0xd8, 0x55, 0xc3, 0x38, 0xcc, 0x2b, + 0x53, 0x5c, 0x69, 0x52, 0xd5, 0xc8, 0x73, 0x02, 0x38, 0x7c, 0x73, 0xb6, 0x41, + 0xe7, 0xff, 0x05, 0xd8, 0x2b, 0x79, 0x9a, 0xe2, 0x34, 0x60, 0x8f, 0xa3, 0x32, + 0x1f, 0x09, 0x78, 0x62, 0xbc, 0x80, 0xe3, 0x0f, 0xbd, 0x65, 0x20, 0x08, 0x13, + 0xc1, 0xe2, 0xee, 0x53, 0x2d, 0x86, 0x7e, 0xa7, 0x5a, 0xc5, 0xd3, 0x7d, 0x98, + 0xbe, 0x31, 0x48, 0x1f, 0xfb, 0xda, 0xaf, 0xa2, 0xa8, 0x6a, 0x89, 0xd6, 0xbf, + 0xf2, 0xd3, 0x32, 0x2a, 0x9a, 0xe4, 0xcf, 0x17, 0xb7, 0xb8, 0xf4, 0xe1, 0x33, + 0x08, 0x24, 0x8b, 0xc4, 0x43, 0xa5, 0xe5, 0x24, 0xc2}, +}; + +struct zmk_hid_ptp_feature_certification_report * +zmk_mouse_hid_ptp_get_feature_certification_report() { + return &cert_report; +} + +static struct zmk_hid_ptp_feature_capabilities_report cap_report = { + .report_id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_CAPABILITIES, + .body = + { + .max_touches = CONFIG_ZMK_TRACKPAD_FINGERS, + .pad_type = PTP_PAD_TYPE_NON_CLICKABLE, + }, +}; + +struct zmk_hid_ptp_feature_capabilities_report * +zmk_mouse_hid_ptp_get_feature_capabilities_report() { + return &cap_report; +} + +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) \ No newline at end of file diff --git a/app/src/mouse/hog.c b/app/src/mouse/hog.c new file mode 100644 index 00000000000..ebc901b7470 --- /dev/null +++ b/app/src/mouse/hog.c @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include + +#include +#include +#include +#include + +enum { + HIDS_REMOTE_WAKE = BIT(0), + HIDS_NORMALLY_CONNECTABLE = BIT(1), +}; + +struct hids_info { + uint16_t version; /* version number of base USB HID Specification */ + uint8_t code; /* country HID Device hardware is localized for. */ + uint8_t flags; +} __packed; + +struct hids_report { + uint8_t id; /* report id */ + uint8_t type; /* report type */ +} __packed; + +static struct hids_info info = { + .version = 0x1101, + .code = 0x00, + .flags = HIDS_NORMALLY_CONNECTABLE | HIDS_REMOTE_WAKE, +}; + +enum { + HIDS_INPUT = 0x01, + HIDS_OUTPUT = 0x02, + HIDS_FEATURE = 0x03, +}; + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +#include + +// Windows PTP Collection + +static struct hids_report ptp_input = { + .id = ZMK_MOUSE_HID_REPORT_ID_DIGITIZER, + .type = HIDS_INPUT, +}; + +static struct hids_report ptp_caps = { + .id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_CAPABILITIES, + .type = HIDS_FEATURE, +}; + +static struct hids_report ptp_hqa = { + .id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTPHQA, + .type = HIDS_FEATURE, +}; + +// Configuration Collection + +static struct hids_report ptp_selective_reporting = { + .id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_SELECTIVE, + .type = HIDS_FEATURE, +}; + +static struct hids_report ptp_mode = { + .id = ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_MODE, + .type = HIDS_FEATURE, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +static bool host_requests_notification = false; +static uint8_t ctrl_point; + +static ssize_t read_hids_info(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_info)); +} + +static ssize_t read_hids_report_ref(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + sizeof(struct hids_report)); +} + +static ssize_t read_hids_report_map(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + return bt_gatt_attr_read(conn, attr, buf, len, offset, zmk_mouse_hid_report_desc, + sizeof(zmk_mouse_hid_report_desc)); +} + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +static ssize_t read_hids_ptp_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + struct zmk_hid_ptp_report_body *report_body = &zmk_mouse_hid_get_ptp_report()->body; + LOG_DBG("Get PT Input at offset %d with len %d to fetch total size %d", offset, len, + sizeof(struct zmk_hid_ptp_report_body)); + LOG_HEXDUMP_DBG(report_body, sizeof(struct zmk_hid_ptp_report_body), "PTP report"); + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_ptp_report_body)); +} + +static ssize_t read_hids_ptp_caps_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + LOG_DBG("Get CAPS"); + struct zmk_hid_ptp_feature_capabilities_report_body *report_body = + &zmk_mouse_hid_ptp_get_feature_capabilities_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_ptp_feature_capabilities_report_body)); +} + +static ssize_t read_hids_ptp_hqa_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + struct zmk_hid_ptp_feature_certification_report *report = + zmk_mouse_hid_ptp_get_feature_certification_report(); + LOG_DBG("Get HQA at offset %d with len %d to fetch total size %d", offset, len, + sizeof(report->ptphqa_blob)); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &report->ptphqa_blob, + sizeof(report->ptphqa_blob)); +} + +static ssize_t read_hids_ptp_mode(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_ptp_feature_mode_report *report = zmk_mouse_hid_ptp_get_feature_mode_report(); + LOG_DBG("Get PTP MODE at offset %d with len %d", offset, len); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &report->mode, sizeof(uint8_t)); +} + +static ssize_t write_hids_ptp_mode(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) { + LOG_ERR("Funky offset for mode"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + if (len != sizeof(uint8_t)) { + LOG_ERR("Wrong size for mode"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + 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, + }}; + uint8_t *report = (uint8_t *)buf; + + LOG_DBG("Mode report set ble %d", *report); + zmk_trackpad_set_mode_report(report, endpoint); + + return len; +} + +static ssize_t read_hids_ptp_sel_reporting(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) { + struct zmk_hid_ptp_feature_selective_report *report = + zmk_mouse_hid_ptp_get_feature_selective_report(); + LOG_DBG("Get PTP sel mode at offset %d with len %d", offset, len); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &report->body, + sizeof(struct zmk_hid_ptp_feature_selective_report_body)); +} + +static ssize_t write_hids_ptp_sel_reporting(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) { + LOG_ERR("Funky offset for sel reporting"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + if (len != sizeof(struct zmk_hid_ptp_feature_selective_report_body)) { + LOG_ERR("Wrong size for sel reporting"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + int profile = zmk_ble_profile_index(bt_conn_get_dst(conn)); + if (profile < 0) { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + struct zmk_hid_ptp_feature_selective_report_body *report = + (struct zmk_hid_ptp_feature_selective_report_body *)buf; + LOG_DBG("Selective: surface: %d, button: %d", report->surface_switch, report->button_switch); + struct zmk_endpoint_instance endpoint = {.transport = ZMK_TRANSPORT_BLE, + .ble = { + .profile_index = profile, + }}; + zmk_trackpad_set_selective_report(report->surface_switch, report->button_switch, endpoint); + + return len; +} + +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +static void input_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) { + LOG_DBG("Input CC changed for %d", attr->handle); + host_requests_notification = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0; +} + +static ssize_t write_ctrl_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + uint8_t *value = attr->user_data; + + if (offset + len > sizeof(ctrl_point)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + memcpy(value + offset, buf, len); + + return len; +} + +/* HID Service Declaration */ +BT_GATT_SERVICE_DEFINE( + mouse_hog_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS), + // BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_PROTOCOL_MODE, BT_GATT_CHRC_WRITE_WITHOUT_RESP, + // BT_GATT_PERM_WRITE, NULL, write_proto_mode, &proto_mode), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT_MAP, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, + read_hids_report_map, NULL, NULL), + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, read_hids_ptp_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, &ptp_input), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, + read_hids_ptp_caps_report, NULL, NULL), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &ptp_caps), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, + read_hids_ptp_hqa_report, NULL, NULL), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &ptp_hqa), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, + read_hids_ptp_mode, write_hids_ptp_mode, NULL), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &ptp_mode), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, + read_hids_ptp_sel_reporting, write_hids_ptp_sel_reporting, NULL), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &ptp_selective_reporting), + +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_INFO, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_hids_info, + NULL, &info), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); + +static struct bt_conn *destination_connection(void) { + struct bt_conn *conn; + bt_addr_le_t *addr = zmk_ble_active_profile_addr(); + + if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { + LOG_WRN("Not sending, no active address for current profile"); + return NULL; + } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { + LOG_WRN("Not sending, not connected to active profile"); + return NULL; + } + + return conn; +} + +K_THREAD_STACK_DEFINE(mouse_hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE); + +static struct k_work_q mouse_hog_work_q; + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +K_MSGQ_DEFINE(zmk_hog_ptp_msgq, sizeof(struct zmk_hid_ptp_report_body), + CONFIG_ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE, 4); + +void send_ptp_report_callback(struct k_work *work) { + struct zmk_hid_ptp_report_body report; + while (k_msgq_get(&zmk_hog_ptp_msgq, &report, K_NO_WAIT) == 0) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &mouse_hog_svc.attrs[3], + .data = &report, + .len = sizeof(report), + }; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err == -EPERM) { + bt_conn_set_security(conn, BT_SECURITY_L2); + } else if (err) { + LOG_DBG("Error notifying %d", err); + } + + bt_conn_unref(conn); + } +}; + +K_WORK_DEFINE(hog_ptp_work, send_ptp_report_callback); + +int zmk_mouse_hog_send_ptp_report(struct zmk_hid_ptp_report_body *report) { + int err = k_msgq_put(&zmk_hog_ptp_msgq, report, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Consumer message queue full, popping first message and queueing again"); + struct zmk_hid_ptp_report_body discarded_report; + k_msgq_get(&zmk_hog_ptp_msgq, &discarded_report, K_NO_WAIT); + return zmk_mouse_hog_send_ptp_report(report); + } + default: + LOG_WRN("Failed to queue mouse report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&mouse_hog_work_q, &hog_ptp_work); + + return 0; +}; +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +static int zmk_mouse_hog_init(void) { + static const struct k_work_queue_config queue_config = {.name = + "Mouse HID Over GATT Send Work"}; + k_work_queue_start(&mouse_hog_work_q, mouse_hog_q_stack, + K_THREAD_STACK_SIZEOF(mouse_hog_q_stack), CONFIG_ZMK_BLE_THREAD_PRIORITY, + &queue_config); + + return 0; +} + +SYS_INIT(zmk_mouse_hog_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); diff --git a/app/src/mouse/input_listener.c b/app/src/mouse/input_listener.c new file mode 100644 index 00000000000..e2c633a71b0 --- /dev/null +++ b/app/src/mouse/input_listener.c @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_input_listener + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include + +#include +#include +#include + +#define ONE_IF_DEV_OK(n) \ + COND_CODE_1(DT_NODE_HAS_STATUS(DT_INST_PHANDLE(n, device), okay), (1 +), (0 +)) + +#define VALID_LISTENER_COUNT (DT_INST_FOREACH_STATUS_OKAY(ONE_IF_DEV_OK) 0) + +#if VALID_LISTENER_COUNT > 0 + +enum input_listener_xy_data_mode { + INPUT_LISTENER_XY_DATA_MODE_NONE, + INPUT_LISTENER_XY_DATA_MODE_REL, + INPUT_LISTENER_XY_DATA_MODE_ABS, +}; + +struct input_listener_xy_data { + enum input_listener_xy_data_mode mode; + int16_t x; + int16_t y; +}; + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +struct input_listener_ptp_finger { + int16_t x; + int16_t y; + bool active; +}; +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +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_TRACKPAD) + struct { + bool pending_data; + struct input_listener_xy_data data; + uint8_t finger_idx; + bool touched; + } ptp; +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + }; +}; + +enum input_listener_mode { + INPUT_LISTENER_MODE_MOUSE, + INPUT_LISTENER_MODE_PTP, +}; + +struct input_listener_config { + bool xy_swap; + bool x_invert; + bool y_invert; + uint16_t scale_multiplier; + uint16_t scale_divisor; + enum input_listener_mode mode; +}; + +static void handle_rel_code(struct input_listener_data *data, struct input_event *evt) { + switch (evt->code) { + case INPUT_REL_X: + data->mouse.data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.data.x += evt->value; + break; + case INPUT_REL_Y: + data->mouse.data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.data.y += evt->value; + break; + case INPUT_REL_WHEEL: + data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.wheel_data.y += evt->value; + break; + case INPUT_REL_HWHEEL: + data->mouse.wheel_data.mode = INPUT_LISTENER_XY_DATA_MODE_REL; + data->mouse.wheel_data.x += evt->value; + break; + default: + break; + } +} + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +static int send_ptp_finger_to_hid(struct input_listener_data *data) { + struct zmk_ptp_finger finger = {.contact_id = data->ptp.finger_idx}; + finger.x = data->ptp.data.x; + finger.y = data->ptp.data.y; + finger.touch_valid = 1; + finger.tip_switch = data->ptp.touched ? 1 : 0; + + int err = zmk_mouse_hid_set_ptp_finger(finger); + + data->ptp.pending_data = false; + data->ptp.data.y = data->ptp.data.x = 0; + data->ptp.touched = false; + + return err; +} + +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +static void handle_abs_code(const struct input_listener_config *config, + struct input_listener_data *data, struct input_event *evt) { + if (config->mode == INPUT_LISTENER_MODE_PTP) { +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + switch (evt->code) { + case INPUT_ABS_MT_SLOT: + if (data->ptp.pending_data) { + send_ptp_finger_to_hid(data); + } + + data->ptp.pending_data = true; + data->ptp.finger_idx = evt->value; + break; + case INPUT_ABS_X: + data->ptp.pending_data = true; + data->ptp.data.mode = INPUT_LISTENER_XY_DATA_MODE_ABS; + data->ptp.data.x = evt->value; + break; + case INPUT_ABS_Y: + data->ptp.pending_data = true; + data->ptp.data.mode = INPUT_LISTENER_XY_DATA_MODE_ABS; + data->ptp.data.y = evt->value; + break; + default: + break; + } +#else + LOG_ERR("ZMK_TRACKPAD not enabled"); +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + } +} + +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: + // TODO Handle PTP mode! + 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; +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + case INPUT_BTN_TOUCH: + if (config->mode == INPUT_LISTENER_MODE_PTP) { + data->ptp.touched = evt->value > 0; + } +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + default: + break; + } +} + +static void swap_xy(struct input_event *evt) { + switch (evt->code) { + case INPUT_REL_X: + evt->code = INPUT_REL_Y; + break; + case INPUT_REL_Y: + evt->code = INPUT_REL_X; + break; + } +} + +static inline bool is_x_data(const struct input_event *evt) { + return evt->type == INPUT_EV_REL && evt->code == INPUT_REL_X; +} + +static inline bool is_y_data(const struct input_event *evt) { + return evt->type == INPUT_EV_REL && evt->code == INPUT_REL_Y; +} + +static void filter_with_input_config(const struct input_listener_config *cfg, + struct input_event *evt) { + if (!evt->dev) { + return; + } + + if (cfg->xy_swap) { + swap_xy(evt); + } + + if ((cfg->x_invert && is_x_data(evt)) || (cfg->y_invert && is_y_data(evt))) { + evt->value = -(evt->value); + } + + evt->value = (int16_t)((evt->value * cfg->scale_multiplier) / cfg->scale_divisor); +} + +static void clear_xy_data(struct input_listener_xy_data *data) { + data->x = data->y = 0; + data->mode = INPUT_LISTENER_XY_DATA_MODE_NONE; +} + +static void input_handler(const struct input_listener_config *config, + struct input_listener_data *data, struct input_event *evt) { + // First, filter to update the event data as needed. + filter_with_input_config(config, evt); + + switch (evt->type) { + case INPUT_EV_REL: + handle_rel_code(data, evt); + break; + case INPUT_EV_ABS: + handle_abs_code(config, data, evt); + break; + case INPUT_EV_KEY: + handle_key_code(config, data, evt); + break; + } + + if (evt->sync) { + switch (config->mode) { + case INPUT_LISTENER_MODE_MOUSE: + if (data->mouse.wheel_data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) { + zmk_hid_mouse_scroll_set(data->mouse.wheel_data.x, data->mouse.wheel_data.y); + } + + if (data->mouse.data.mode == INPUT_LISTENER_XY_DATA_MODE_REL) { + zmk_hid_mouse_movement_set(data->mouse.data.x, data->mouse.data.y); + } + + if (data->mouse.button_set != 0) { + for (int i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if ((data->mouse.button_set & BIT(i)) != 0) { + zmk_hid_mouse_button_press(i); + } + } + } + + if (data->mouse.button_clear != 0) { + for (int i = 0; i < ZMK_MOUSE_HID_NUM_BUTTONS; i++) { + if ((data->mouse.button_clear & BIT(i)) != 0) { + zmk_hid_mouse_button_release(i); + } + } + } + + zmk_endpoints_send_mouse_report(); + zmk_hid_mouse_scroll_set(0, 0); + zmk_hid_mouse_movement_set(0, 0); + + clear_xy_data(&data->mouse.data); + clear_xy_data(&data->mouse.wheel_data); + + data->mouse.button_set = data->mouse.button_clear = 0; + + break; + case INPUT_LISTENER_MODE_PTP: +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + if (data->ptp.pending_data) { + send_ptp_finger_to_hid(data); + } + + // TODO: Button Data + + zmk_mouse_hid_ptp_update_scan_time(); + zmk_endpoints_send_ptp_report(); + + zmk_mouse_hid_ptp_clear_lifted_fingers(); +#else + LOG_WRN("PTP Mode not supported without CONFIG_ZMK_TRACKPAD=y"); +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + + break; + } + } +} + +#endif // VALID_LISTENER_COUNT > 0 + +#define IL_INST(n) \ + COND_CODE_1( \ + DT_NODE_HAS_STATUS(DT_INST_PHANDLE(n, device), okay), \ + (static const struct input_listener_config config_##n = \ + { \ + .xy_swap = DT_INST_PROP(n, xy_swap), \ + .x_invert = DT_INST_PROP(n, x_invert), \ + .y_invert = DT_INST_PROP(n, y_invert), \ + .scale_multiplier = DT_INST_PROP(n, scale_multiplier), \ + .scale_divisor = DT_INST_PROP(n, scale_divisor), \ + .mode = DT_INST_ENUM_IDX_OR(n, mode, 0), \ + }; \ + static struct input_listener_data data_##n = {}; \ + void input_handler_##n(struct input_event *evt) { \ + input_handler(&config_##n, &data_##n, evt); \ + } INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(n, device)), input_handler_##n);), \ + ()) + +DT_INST_FOREACH_STATUS_OKAY(IL_INST) diff --git a/app/src/mouse/trackpad.c b/app/src/mouse/trackpad.c new file mode 100644 index 00000000000..5868a2449d9 --- /dev/null +++ b/app/src/mouse/trackpad.c @@ -0,0 +1,296 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "drivers/sensor/gen4.h" + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +const struct device *trackpad = DEVICE_DT_GET(DT_INST(0, cirque_gen4)); + +static zmk_trackpad_finger_contacts_t present_contacts = 0; +static zmk_trackpad_finger_contacts_t contacts_to_send = 0; +static zmk_trackpad_finger_contacts_t received_contacts = 0; + +static uint8_t btns; +static uint16_t scantime; + +static bool trackpad_enabled; + +static bool mousemode; +static bool surface_mode; +static bool button_mode; + +static int16_t xDelta, yDelta, scrollDelta; + +static struct zmk_ptp_finger fingers[CONFIG_ZMK_TRACKPAD_FINGERS]; +static const struct zmk_ptp_finger empty_finger = {0}; + +static bool mouse_modes[ZMK_ENDPOINT_COUNT] = {0}; +static bool surface_modes[ZMK_ENDPOINT_COUNT] = {0}; +static bool button_modes[ZMK_ENDPOINT_COUNT] = {0}; + +static void handle_trackpad_ptp(const struct device *dev, const struct sensor_trigger *trig) { + int ret = sensor_sample_fetch(dev); + if (ret < 0) { + LOG_ERR("fetch: %d", ret); + return; + } + // LOG_DBG("Trackpad handler trigd %d", 0); + + struct sensor_value contacts, confidence_tip, id, x, y, buttons, scan_time; + sensor_channel_get(dev, SENSOR_CHAN_CONTACTS, &contacts); + sensor_channel_get(dev, SENSOR_CHAN_BUTTONS, &buttons); + sensor_channel_get(dev, SENSOR_CHAN_SCAN_TIME, &scan_time); + + present_contacts = contacts.val1 ? contacts.val1 : present_contacts; + // Buttons and scan time + btns = button_mode ? buttons.val1 : 0; + scantime = scan_time.val1; + // released Fingers + sensor_channel_get(dev, SENSOR_CHAN_X, &x); + sensor_channel_get(dev, SENSOR_CHAN_Y, &y); + sensor_channel_get(dev, SENSOR_CHAN_CONFIDENCE_TIP, &confidence_tip); + sensor_channel_get(dev, SENSOR_CHAN_FINGER, &id); + // If finger has changed + LOG_DBG("Confidence tip: %d", confidence_tip.val1); + fingers[id.val1].touch_valid = confidence_tip.val1 & 0x01; + fingers[id.val1].tip_switch = (confidence_tip.val1 & 0x02) >> 1; + fingers[id.val1].contact_id = id.val1; + fingers[id.val1].x = x.val1; + fingers[id.val1].y = y.val1; + contacts_to_send |= BIT(id.val1); + received_contacts++; + + // LOG_DBG("total contacts: %d, received contacts: %d", present_contacts, received_contacts); + + if ((present_contacts == received_contacts) && surface_mode) { + LOG_DBG("total contacts: %d, received contacts: %d, bitmap contacts %d", present_contacts, + received_contacts, contacts_to_send); +#if CONFIG_ZMK_TRACKPAD_FINGERS == 5 + zmk_hid_ptp_set((contacts_to_send & BIT(0)) ? fingers[0] : empty_finger, + (contacts_to_send & BIT(1)) ? fingers[1] : empty_finger, + (contacts_to_send & BIT(2)) ? fingers[2] : empty_finger, + (contacts_to_send & BIT(3)) ? fingers[3] : empty_finger, + (contacts_to_send & BIT(4)) ? fingers[4] : empty_finger, present_contacts, + scantime, button_mode ? btns : 0); +#elif CONFIG_ZMK_TRACKPAD_FINGERS == 4 + zmk_hid_ptp_set((contacts_to_send & BIT(0)) ? fingers[0] : empty_finger, + (contacts_to_send & BIT(1)) ? fingers[1] : empty_finger, + (contacts_to_send & BIT(2)) ? fingers[2] : empty_finger, + (contacts_to_send & BIT(3)) ? fingers[3] : empty_finger, empty_finger, + present_contacts, scantime, button_mode ? btns : 0); +#else + zmk_hid_ptp_set((contacts_to_send & BIT(0)) ? fingers[0] : empty_finger, + (contacts_to_send & BIT(1)) ? fingers[1] : empty_finger, + (contacts_to_send & BIT(2)) ? fingers[2] : empty_finger, empty_finger, + empty_finger, present_contacts, scantime, button_mode ? btns : 0); +#endif + zmk_endpoints_send_ptp_report(); + contacts_to_send = 0; + received_contacts = 0; + } else if (!surface_mode) { + zmk_hid_ptp_set(empty_finger, empty_finger, empty_finger, empty_finger, empty_finger, 0, + scantime, button_mode ? btns : 0); + zmk_endpoints_send_ptp_report(); + contacts_to_send = 0; + received_contacts = 0; + } + + raise_zmk_sensor_event( + (struct zmk_sensor_event){.sensor_index = 0, + .channel_data_size = 1, + .channel_data = {(struct zmk_sensor_channel_data){ + .value = buttons, .channel = SENSOR_CHAN_BUTTONS}}, + .timestamp = k_uptime_get()}); +} + +static void handle_mouse_mode(const struct device *dev, const struct sensor_trigger *trig) { + LOG_DBG("Trackpad handler trigd in mouse mode %d", 0); + int ret = sensor_sample_fetch(dev); + if (ret < 0) { + LOG_ERR("fetch: %d", ret); + return; + } + + struct sensor_value x, y, buttons, wheel; + sensor_channel_get(dev, SENSOR_CHAN_XDELTA, &x); + sensor_channel_get(dev, SENSOR_CHAN_YDELTA, &y); + sensor_channel_get(dev, SENSOR_CHAN_BUTTONS, &buttons); + sensor_channel_get(dev, SENSOR_CHAN_WHEEL, &wheel); + + btns = buttons.val1; + xDelta = x.val1; + yDelta = y.val1; +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD_REVERSE_SCROLL) + scrollDelta = -wheel.val1; +#else + scrollDelta = wheel.val1; +#endif + zmk_hid_mouse_set(btns, xDelta, yDelta, scrollDelta); + zmk_endpoints_send_mouse_report(); + + raise_zmk_sensor_event( + (struct zmk_sensor_event){.sensor_index = 0, + .channel_data_size = 1, + .channel_data = {(struct zmk_sensor_channel_data){ + .value = buttons, .channel = SENSOR_CHAN_BUTTONS}}, + .timestamp = k_uptime_get()}); +} + +static void zmk_trackpad_set_mouse_mode(bool mouse_mode) { + struct sensor_trigger trigger = { + .type = SENSOR_TRIG_DATA_READY, + .chan = SENSOR_CHAN_ALL, + }; + struct sensor_value attr; + attr.val1 = mouse_mode; + LOG_DBG("Setting attr %d", attr.val1); + mousemode = mouse_mode; + sensor_attr_set(trackpad, SENSOR_CHAN_ALL, SENSOR_ATTR_CONFIGURATION, &attr); + if (mouse_mode) { + zmk_hid_ptp_set(empty_finger, empty_finger, empty_finger, empty_finger, empty_finger, 0, + scantime + 1, 0); + zmk_endpoints_send_ptp_report(); + if (sensor_trigger_set(trackpad, &trigger, handle_mouse_mode) < 0) { + LOG_ERR("can't set trigger mouse mode"); + }; + } else { + zmk_hid_mouse_clear(); + zmk_endpoints_send_mouse_report(); + if (sensor_trigger_set(trackpad, &trigger, handle_trackpad_ptp) < 0) { + LOG_ERR("can't set trigger"); + }; + } +} + +void zmk_trackpad_set_enabled(bool enabled) { + if (trackpad_enabled == enabled) + return; + struct sensor_trigger trigger = { + .type = SENSOR_TRIG_DATA_READY, + .chan = SENSOR_CHAN_ALL, + }; + trackpad_enabled = enabled; + if (trackpad_enabled) { + // Activate everything + if (mousemode) { + if (sensor_trigger_set(trackpad, &trigger, handle_mouse_mode) < 0) { + LOG_ERR("can't set trigger mouse mode"); + }; + } else { + if (sensor_trigger_set(trackpad, &trigger, handle_trackpad_ptp) < 0) { + LOG_ERR("can't set trigger"); + }; + } + } else { + // Clear reports, stop trigger + if (mousemode) { + zmk_hid_mouse_clear(); + zmk_endpoints_send_mouse_report(); + } else { + zmk_hid_ptp_set(empty_finger, empty_finger, empty_finger, empty_finger, empty_finger, 0, + scantime + 1, 0); + zmk_endpoints_send_ptp_report(); + } + if (sensor_trigger_set(trackpad, &trigger, NULL) < 0) { + LOG_ERR("can't unset trigger"); + }; + } +} + +bool zmk_trackpad_get_enabled() { return trackpad_enabled; } + +static void process_mode_report(struct k_work *_work) { + bool state = mouse_modes[zmk_endpoint_instance_to_index(zmk_endpoints_selected())]; + LOG_DBG("Current state %d, new state %d", mousemode, state); + if (mousemode != state) { + LOG_DBG("Setting mouse mode to %d for endpoint %d", state, + zmk_endpoint_instance_to_index(zmk_endpoints_selected())); + zmk_trackpad_set_mouse_mode(state); + zmk_mouse_hid_ptp_set_feature_mode(state ? 0 : 3); + } +} + +static K_WORK_DEFINE(mode_changed_work, process_mode_report); + +static void process_selective_report(struct k_work *_work) { + surface_mode = surface_modes[zmk_endpoint_instance_to_index(zmk_endpoints_selected())]; + button_mode = button_modes[zmk_endpoint_instance_to_index(zmk_endpoints_selected())]; + zmk_mouse_hid_ptp_set_feature_selective_report(surface_mode, button_mode); +} + +static K_WORK_DEFINE(selective_changed_work, process_selective_report); + +static int trackpad_init(void) { + + for (int i = 0; i < ZMK_ENDPOINT_COUNT; i++) { + mouse_modes[i] = true; + surface_modes[i] = true; + button_modes[i] = true; + } + k_work_submit(&mode_changed_work); + k_work_submit(&selective_changed_work); + trackpad_enabled = true; + return 0; +} + +static int trackpad_event_listener(const zmk_event_t *eh) { + // Reset to mouse mode on usb disconnection + if (as_zmk_usb_conn_state_changed(eh)) { + struct zmk_usb_conn_state_changed *usb_state = as_zmk_usb_conn_state_changed(eh); + if (usb_state->conn_state == ZMK_USB_CONN_NONE) { + struct zmk_endpoint_instance endpoint = { + .transport = ZMK_TRANSPORT_USB, + }; + mouse_modes[zmk_endpoint_instance_to_index(endpoint)] = true; + surface_modes[zmk_endpoint_instance_to_index(endpoint)] = true; + button_modes[zmk_endpoint_instance_to_index(endpoint)] = true; + } + } + // reset to mouse mode on BLE profile disconnection or unpairing + if (as_zmk_ble_active_profile_changed(eh)) { + struct zmk_ble_active_profile_changed *ble_state = as_zmk_ble_active_profile_changed(eh); + if (ble_state->open || !ble_state->connected) { + struct zmk_endpoint_instance endpoint = {.transport = ZMK_TRANSPORT_BLE, + .ble = {.profile_index = ble_state->index}}; + mouse_modes[zmk_endpoint_instance_to_index(endpoint)] = true; + surface_modes[zmk_endpoint_instance_to_index(endpoint)] = true; + button_modes[zmk_endpoint_instance_to_index(endpoint)] = true; + } + } + k_work_submit(&mode_changed_work); + k_work_submit(&selective_changed_work); + LOG_DBG("Mode change evt triggered"); + return 0; +} + +ZMK_LISTENER(trackpad, trackpad_event_listener); +ZMK_SUBSCRIPTION(trackpad, zmk_endpoint_changed); +ZMK_SUBSCRIPTION(trackpad, zmk_usb_conn_state_changed); +ZMK_SUBSCRIPTION(trackpad, zmk_ble_active_profile_changed); + +void zmk_trackpad_set_mode_report(uint8_t *report, struct zmk_endpoint_instance endpoint) { + int profile = zmk_endpoint_instance_to_index(endpoint); + LOG_DBG("Received report %d on endpoint %d", *report, profile); + mouse_modes[profile] = *report ? false : true; + k_work_submit(&mode_changed_work); +} + +void zmk_trackpad_set_selective_report(bool surface_switch, bool button_switch, + struct zmk_endpoint_instance endpoint) { + surface_modes[zmk_endpoint_instance_to_index(endpoint)] = surface_switch; + button_modes[zmk_endpoint_instance_to_index(endpoint)] = button_switch; + LOG_DBG("Surface: %d, Button %d", surface_mode, button_mode); + k_work_submit(&selective_changed_work); +} + +SYS_INIT(trackpad_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); \ No newline at end of file diff --git a/app/src/mouse/usb_hid.c b/app/src/mouse/usb_hid.c new file mode 100644 index 00000000000..4f83608192e --- /dev/null +++ b/app/src/mouse/usb_hid.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +#include +#endif + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +static const struct device *hid_dev; + +static K_SEM_DEFINE(hid_sem, 1, 1); + +static void in_ready_cb(const struct device *dev) { k_sem_give(&hid_sem); } + +#define HID_GET_REPORT_TYPE_MASK 0xff00 +#define HID_GET_REPORT_ID_MASK 0x00ff + +#define HID_REPORT_TYPE_INPUT 0x100 +#define HID_REPORT_TYPE_OUTPUT 0x200 +#define HID_REPORT_TYPE_FEATURE 0x300 + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +static uint8_t hid_protocol = HID_PROTOCOL_REPORT; + +static void set_proto_cb(const struct device *dev, uint8_t protocol) { hid_protocol = protocol; } + +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + +static int get_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, + uint8_t **data) { + + /* + * 7.2.1 of the HID v1.11 spec is unclear about handling requests for reports that do not exist + * For requested reports that aren't input reports, return -ENOTSUP like the Zephyr subsys does + */ + if ((setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_INPUT && + (setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_FEATURE) { + LOG_ERR("Get: Unsupported report type %d requested", + (setup->wValue & HID_GET_REPORT_TYPE_MASK) >> 8); + return -ENOTSUP; + } + + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + case ZMK_MOUSE_HID_REPORT_ID_DIGITIZER: + struct zmk_hid_ptp_report *ptp_report = zmk_mouse_hid_get_ptp_report(); + LOG_WRN("Get PTP report"); + *data = (uint8_t *)ptp_report; + *len = sizeof(*ptp_report); + break; + case ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_SELECTIVE: + struct zmk_hid_ptp_feature_selective_report *sel_report = + zmk_mouse_hid_ptp_get_feature_selective_report(); + *data = (uint8_t *)sel_report; + LOG_DBG("Selective report get %d", 0); + *len = sizeof(*sel_report); + break; + case ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_CAPABILITIES: + LOG_WRN("Get CAPABILITIES"); + struct zmk_hid_ptp_feature_capabilities_report *cap_report = + zmk_mouse_hid_ptp_get_feature_capabilities_report(); + *data = (uint8_t *)cap_report; + *len = sizeof(*cap_report); + break; + case ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTPHQA: + LOG_WRN("Get HQA"); + struct zmk_hid_ptp_feature_certification_report *cert_report = + zmk_mouse_hid_ptp_get_feature_certification_report(); + *data = (uint8_t *)cert_report; + *len = sizeof(*cert_report); + break; + case ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_MODE: + struct zmk_hid_ptp_feature_mode_report *mode_report = + zmk_mouse_hid_ptp_get_feature_mode_report(); + *data = (uint8_t *)mode_report; + LOG_DBG("mode report get %d", 0); + *len = sizeof(*mode_report); + break; +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + default: + LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); + return -EINVAL; + } + + return 0; +} + +static int set_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, + uint8_t **data) { + if ((setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_OUTPUT && + (setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_FEATURE) { + LOG_ERR("Set: Unsupported report type %d requested", + (setup->wValue & HID_GET_REPORT_TYPE_MASK) >> 8); + return -ENOTSUP; + } + + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + case ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_MODE: + if (*len != sizeof(struct zmk_hid_ptp_feature_mode_report)) { + LOG_ERR("Mode set report is malformed: length=%d", *len); + return -EINVAL; + } else { + struct zmk_hid_ptp_feature_mode_report *report = + (struct zmk_hid_ptp_feature_mode_report *)*data; + struct zmk_endpoint_instance endpoint = { + .transport = ZMK_TRANSPORT_USB, + }; + zmk_trackpad_set_mode_report(&report->mode, endpoint); + } + break; + case ZMK_MOUSE_HID_REPORT_ID_FEATURE_PTP_SELECTIVE: + if (*len != sizeof(struct zmk_hid_ptp_feature_selective_report)) { + LOG_ERR("Mode set report is malformed: length=%d", *len); + return -EINVAL; + } else { + struct zmk_hid_ptp_feature_selective_report *report = + (struct zmk_hid_ptp_feature_selective_report *)*data; + LOG_DBG("PTP selective: surface: %d, button: %d", report->body.surface_switch, + report->body.button_switch); + struct zmk_endpoint_instance endpoint2 = { + .transport = ZMK_TRANSPORT_USB, + }; + zmk_trackpad_set_selective_report(report->body.surface_switch, + report->body.button_switch, endpoint2); + } + break; +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + default: + LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); + return -EINVAL; + } + + return 0; +} + +static const struct hid_ops ops = { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + .protocol_change = set_proto_cb, +#endif + .int_in_ready = in_ready_cb, + .get_report = get_report_cb, + .set_report = set_report_cb, +}; + +static int zmk_mouse_usb_hid_send_report(const uint8_t *report, size_t len) { + switch (zmk_usb_get_status()) { + case USB_DC_SUSPEND: + return usb_wakeup_request(); + case USB_DC_ERROR: + case USB_DC_RESET: + case USB_DC_DISCONNECTED: + case USB_DC_UNKNOWN: + return -ENODEV; + default: + k_sem_take(&hid_sem, K_MSEC(30)); + LOG_HEXDUMP_DBG(report, len, "Mouse HID report"); + int err = hid_int_ep_write(hid_dev, report, len, NULL); + + if (err) { + LOG_ERR("Failed to write %d", err); + k_sem_give(&hid_sem); + } + + return err; + } +} + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_mouse_usb_hid_send_ptp_report() { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol == HID_PROTOCOL_BOOT) { + return -ENOTSUP; + } +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + + struct zmk_hid_ptp_report *report = zmk_mouse_hid_get_ptp_report(); + return zmk_mouse_usb_hid_send_report((uint8_t *)report, sizeof(*report)); +} +#endif // IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +static int zmk_mouse_usb_hid_init(void) { + hid_dev = device_get_binding("HID_1"); + if (hid_dev == NULL) { + LOG_ERR("Unable to locate HID device"); + return -EINVAL; + } + + usb_hid_register_device(hid_dev, zmk_mouse_hid_report_desc, sizeof(zmk_mouse_hid_report_desc), + &ops); + + // usb_hid_set_proto_code(hid_dev, HID_BOOT_IFACE_CODE_MOUSE); + + usb_hid_init(hid_dev); + + return 0; +} + +SYS_INIT(zmk_mouse_usb_hid_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/profile_switch.c b/app/src/profile_switch.c new file mode 100644 index 00000000000..ee7c8f76059 --- /dev/null +++ b/app/src/profile_switch.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +BUILD_ASSERT(DT_HAS_CHOSEN(zmk_profileswitch), + "CONFIG_ZMK_PROFILESWITCH is enabled but no zmk,profileswitch chosen node found"); + +struct gpio_callback a_gpio_cb; +const struct gpio_dt_spec switchgpio = GPIO_DT_SPEC_GET(DT_CHOSEN(zmk_profileswitch), switch_gpios); + +static void zmk_profile_switch_read() { + uint8_t val = gpio_pin_get_dt(&switchgpio); + LOG_DBG("Setting BLE profile to %d", val); + zmk_ble_prof_select(val); + if (gpio_pin_interrupt_configure_dt(&switchgpio, GPIO_INT_EDGE_BOTH)) { + LOG_WRN("Unable to set A pin GPIO interrupt"); + } +} + +static void zmk_profile_switch_work_cb(struct k_work *work) { zmk_profile_switch_read(); } + +K_WORK_DEFINE(profileswitch_work, zmk_profile_switch_work_cb); + +static void zmk_profile_switch_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) { + + if (gpio_pin_interrupt_configure_dt(&switchgpio, GPIO_INT_DISABLE)) { + LOG_WRN("Unable to set A pin GPIO interrupt"); + } + LOG_DBG("interrupt triggered"); + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &profileswitch_work); +} + +static int zmk_profile_switch_init(void) { + + if (!device_is_ready(switchgpio.port)) { + LOG_ERR("A GPIO device is not ready"); + return -EINVAL; + } + if (gpio_pin_configure_dt(&switchgpio, GPIO_INPUT)) { + LOG_DBG("Failed to configure A pin"); + return -EIO; + } + gpio_init_callback(&a_gpio_cb, zmk_profile_switch_callback, BIT(switchgpio.pin)); + + if (gpio_add_callback(switchgpio.port, &a_gpio_cb) < 0) { + LOG_DBG("Failed to set A callback!"); + return -EIO; + } + if (gpio_pin_interrupt_configure_dt(&switchgpio, GPIO_INT_EDGE_BOTH)) { + LOG_WRN("Unable to set A pin GPIO interrupt"); + } + LOG_DBG("Setting profile now"); + zmk_profile_switch_read(); + return 0; +} + +SYS_INIT(zmk_profile_switch_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); \ No newline at end of file diff --git a/app/src/stp_indicators.c b/app/src/stp_indicators.c new file mode 100644 index 00000000000..63f366998f0 --- /dev/null +++ b/app/src/stp_indicators.c @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if !DT_HAS_CHOSEN(zmk_indicators) + +#error "A zmk,indicators chosen node must be declared" + +#endif + +#define STRIP_CHOSEN DT_CHOSEN(zmk_indicators) +#define STRIP_NUM_PIXELS DT_PROP(STRIP_CHOSEN, chain_length) + +#define HUE_MAX 360 +#define SAT_MAX 100 +#define BRT_MAX 100 + +struct zmk_stp_ble { + uint8_t prof; + bool open; + bool connected; +}; + +static struct zmk_led_hsb color0; // BLE Led +static struct zmk_led_hsb color1; // Caps + +static struct zmk_stp_ble ble_status; +static bool caps; +static bool usb; +static bool battery; +static bool events_en; + +static bool on; + +static const struct device *led_strip; + +static struct led_rgb pixels[STRIP_NUM_PIXELS]; + +static const struct device *ext_power; + +static struct led_rgb hsb_to_rgb(struct zmk_led_hsb hsb) { + float r, g, b; + + uint8_t i = hsb.h / 60; + float v = hsb.b / ((float)BRT_MAX); + float s = hsb.s / ((float)SAT_MAX); + float f = hsb.h / ((float)HUE_MAX) * 6 - i; + float p = v * (1 - s); + float q = v * (1 - f * s); + float t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + case 5: + r = v; + g = p; + b = q; + break; + } + + struct led_rgb rgb = {r : r * 255, g : g * 255, b : b * 255}; + + return rgb; +} + +static void zmk_stp_indicators_batt(struct k_work *work) { + // Get state of charge + uint8_t soc = zmk_battery_state_of_charge(); + LOG_DBG("State of charge: %d", soc); + struct led_rgb rgb; + if (soc > 80) { + rgb.r = 0; + rgb.g = 255; + rgb.b = 0; + } else if (soc > 50 && soc < 80) { + rgb.r = 255; + rgb.g = 255; + rgb.b = 0; + } else if (soc > 20 && soc < 51) { + rgb.r = 255; + rgb.g = 140; + rgb.b = 0; + } else { + rgb.r = 255; + rgb.g = 0; + rgb.b = 0; + } + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + pixels[i] = rgb; + } + int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } +} + +static void zmk_stp_indicators_blink_work(struct k_work *work) { + LOG_DBG("Blink work triggered"); + // If LED on turn off and vice cersa + if (color0.b) + color0.b = 0; + else + color0.b = CONFIG_ZMK_STP_INDICATORS_BRT_MAX; + // Convert HSB to RGB and update LEDs + pixels[0] = hsb_to_rgb(color0); + int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } +} + +K_WORK_DEFINE(blink_work, zmk_stp_indicators_blink_work); + +static void zmk_stp_indicators_blink_handler(struct k_timer *timer) { + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &blink_work); +} + +// Define timers for blinking and led timeout +K_TIMER_DEFINE(fast_blink_timer, zmk_stp_indicators_blink_handler, NULL); +K_TIMER_DEFINE(slow_blink_timer, zmk_stp_indicators_blink_handler, NULL); +K_TIMER_DEFINE(connected_timeout_timer, zmk_stp_indicators_blink_handler, NULL); + +static void zmk_stp_indicators_bluetooth(struct k_work *work) { + // Set LED to blue if profile one, set sat to 0 if profile 0 (white) + LOG_DBG("BLE PROFILE: %d", ble_status.prof); + + if (ble_status.prof) { + color0.h = 240; + color0.s = 100; + } else + color0.s = 0; + // If in USB HID mode + if (usb) { + LOG_DBG("USB MODE"); + // Stop all timers + k_timer_stop(&slow_blink_timer); + k_timer_stop(&fast_blink_timer); + k_timer_stop(&connected_timeout_timer); + // Set LED to green + color0.h = 120; + color0.s = 100; + color0.b = CONFIG_ZMK_STP_INDICATORS_BRT_MAX; + } else if (ble_status.open) { + LOG_DBG("BLE PROF OPEN"); + // If profile is open (unpaired) start fast blink timer and ensure LED turns on + color0.b = CONFIG_ZMK_STP_INDICATORS_BRT_MAX; + k_timer_stop(&slow_blink_timer); + k_timer_stop(&connected_timeout_timer); + k_timer_start(&fast_blink_timer, K_NO_WAIT, K_MSEC(200)); + } else if (!ble_status.connected) { + LOG_DBG("BLE PROF NOT CONN"); + // If profile paired but not connected start slow blink timer and ensure LED on + color0.b = CONFIG_ZMK_STP_INDICATORS_BRT_MAX; + k_timer_stop(&fast_blink_timer); + k_timer_stop(&connected_timeout_timer); + k_timer_start(&slow_blink_timer, K_NO_WAIT, K_MSEC(750)); + } else { + LOG_DBG("BLE PROF CONN"); + // If connected start the 3 second timeout to turn LED off + color0.b = CONFIG_ZMK_STP_INDICATORS_BRT_MAX; + k_timer_stop(&slow_blink_timer); + k_timer_stop(&fast_blink_timer); + k_timer_start(&connected_timeout_timer, K_SECONDS(3), K_NO_WAIT); + } + // Convert HSB to RGB and update the LEDs + + pixels[0] = hsb_to_rgb(color0); + int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } +} + +static void zmk_stp_indicators_caps(struct k_work *work) { + if (usb) { + color1.h = 120; + color1.s = 100; + } else if (ble_status.prof) { + color1.h = 240; + color1.s = 100; + } else + color1.s = 0; + // Set LED on if capslock pressed + if (caps) + color1.b = CONFIG_ZMK_STP_INDICATORS_BRT_MAX; + else + color1.b = 0; + // Convert HSB to RGB and update the LEDs + pixels[1] = hsb_to_rgb(color1); + int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } +} + +// Define work to update LEDs +K_WORK_DEFINE(battery_ind_work, zmk_stp_indicators_batt); +K_WORK_DEFINE(bluetooth_ind_work, zmk_stp_indicators_bluetooth); +K_WORK_DEFINE(caps_ind_work, zmk_stp_indicators_caps); + +int zmk_stp_indicators_enable_batt() { + // Stop blinking timers + k_timer_stop(&slow_blink_timer); + k_timer_stop(&fast_blink_timer); + k_timer_stop(&connected_timeout_timer); + // Set battery flag to prevent other things overriding + battery = true; + // Submit battery work to queue + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &battery_ind_work); + return 0; +} +int zmk_stp_indicators_disable_batt() { + // Unset battery flag to allow other events to override + battery = false; + // Submit works to update both LEDs + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &bluetooth_ind_work); + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &caps_ind_work); + return 0; +} + +static int zmk_stp_indicators_init(void) { + + LOG_DBG("Initialising STP indicators"); + + led_strip = DEVICE_DT_GET(STRIP_CHOSEN); + + ext_power = device_get_binding("EXT_POWER"); + if (ext_power == NULL) { + LOG_ERR("Unable to retrieve ext_power device: EXT_POWER"); + } + + int rc = ext_power_enable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to enable EXT_POWER: %d", rc); + } + + color0 = (struct zmk_led_hsb){ + h : 240, + s : 100, + b : CONFIG_ZMK_STP_INDICATORS_BRT_MAX, + }; + + color1 = (struct zmk_led_hsb){ + h : 240, + s : 0, + b : CONFIG_ZMK_STP_INDICATORS_BRT_MAX, + }; + + ble_status = (struct zmk_stp_ble){ + prof : zmk_ble_active_profile_index(), + open : zmk_ble_active_profile_is_open(), + connected : zmk_ble_active_profile_is_connected() + }; + caps = (zmk_hid_indicators_get_current_profile() & ZMK_LED_CAPSLOCK_BIT); + usb = false; + battery = false; + + on = true; + // Enable events + + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &bluetooth_ind_work); + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &caps_ind_work); + + if (!events_en) + events_en = true; + + return 0; +} + +int zmk_stp_indicators_on() { + if (!led_strip) + return -ENODEV; + + if (ext_power != NULL) { + int rc = ext_power_enable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to enable EXT_POWER: %d", rc); + } + } + + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &bluetooth_ind_work); + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &caps_ind_work); + + return 0; +} + +static void zmk_stp_indicators_off_handler(struct k_work *work) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } + + led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); +} + +K_WORK_DEFINE(underglow_off_work, zmk_stp_indicators_off_handler); + +int zmk_stp_indicators_off() { + if (!led_strip) + return -ENODEV; + + if (ext_power != NULL) { + int rc = ext_power_disable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to disable EXT_POWER: %d", rc); + } + } + + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &underglow_off_work); + on = false; + + return 0; +} + +static int stp_indicators_auto_state(bool *prev_state, bool new_state) { + if (on == new_state) { + return 0; + } + if (new_state) { + on = *prev_state; + *prev_state = false; + return zmk_stp_indicators_on(); + } else { + on = false; + *prev_state = true; + return zmk_stp_indicators_off(); + } +} + +static int stp_indicators_event_listener(const zmk_event_t *eh) { + // If going idle or waking up + if (as_zmk_activity_state_changed(eh) && events_en) { + static bool prev_state = false; + return stp_indicators_auto_state(&prev_state, + zmk_activity_get_state() == ZMK_ACTIVITY_ACTIVE); + } + // If USB state changed + if (as_zmk_usb_conn_state_changed(eh) && events_en) { + + // Get new USB state, HID state and set local flags + usb = zmk_usb_is_powered(); + LOG_DBG("USB EVENT: %d", usb); + + caps = (zmk_hid_indicators_get_current_profile() & ZMK_LED_CAPSLOCK_BIT); + // Update LEDs + // + if (!battery) { + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &bluetooth_ind_work); + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &caps_ind_work); + } + return 0; + } + + // If BLE state changed + if (as_zmk_ble_active_profile_changed(eh) && events_en) { + LOG_DBG("BLE CHANGE LOGGED"); + // Get BLE information, Caps state and set local flags + struct zmk_ble_active_profile_changed *ble_state = as_zmk_ble_active_profile_changed(eh); + ble_status.connected = ble_state->connected; + ble_status.open = ble_state->open; + ble_status.prof = ble_state->index; + caps = (zmk_hid_indicators_get_current_profile() & ZMK_LED_CAPSLOCK_BIT); + // Update LEDs + if (!battery) { + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &bluetooth_ind_work); + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &caps_ind_work); + } + return 0; + } + + if (as_zmk_hid_indicators_changed(eh) && events_en) { + // Get new HID state, set local flags + caps = (zmk_hid_indicators_get_current_profile() & ZMK_LED_CAPSLOCK_BIT); + LOG_DBG("INDICATOR CHANGED: %d", caps); + if (!battery) { + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &caps_ind_work); + // k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &bluetooth_ind_work); + } + return 0; + } + + return -ENOTSUP; +} + +ZMK_LISTENER(stp_indicators, stp_indicators_event_listener); + +ZMK_SUBSCRIPTION(stp_indicators, zmk_activity_state_changed); +ZMK_SUBSCRIPTION(stp_indicators, zmk_usb_conn_state_changed); +ZMK_SUBSCRIPTION(stp_indicators, zmk_ble_active_profile_changed); +ZMK_SUBSCRIPTION(stp_indicators, zmk_hid_indicators_changed); + +SYS_INIT(zmk_stp_indicators_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c index 9db10952c95..46a060a3706 100644 --- a/app/src/usb_hid.c +++ b/app/src/usb_hid.c @@ -78,6 +78,14 @@ static int get_report_cb(const struct device *dev, struct usb_setup_packet *setu *len = sizeof(*report); break; } +#if IS_ENABLED(CONFIG_ZMK_MOUSE) + case ZMK_MOUSE_HID_REPORT_ID_MOUSE: { + struct zmk_hid_mouse_report *report = zmk_mouse_hid_get_mouse_report(); + *data = (uint8_t *)report; + *len = sizeof(*report); + break; + } +#endif default: LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); return -EINVAL; @@ -165,14 +173,14 @@ int zmk_usb_hid_send_consumer_report(void) { } #if IS_ENABLED(CONFIG_ZMK_MOUSE) -int zmk_usb_hid_send_mouse_report() { +int zmk_mouse_usb_hid_send_mouse_report() { #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) if (hid_protocol == HID_PROTOCOL_BOOT) { return -ENOTSUP; } #endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ - struct zmk_hid_mouse_report *report = zmk_hid_get_mouse_report(); + struct zmk_hid_mouse_report *report = zmk_mouse_hid_get_mouse_report(); return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/tests/mouse-keys/mkp/native_posix_64.keymap b/app/tests/mouse-keys/mkp/native_posix_64.keymap index 8e3071d4317..8b955846963 100644 --- a/app/tests/mouse-keys/mkp/native_posix_64.keymap +++ b/app/tests/mouse-keys/mkp/native_posix_64.keymap @@ -1,4 +1,5 @@ #include +#include #include #include #include diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/events.patterns b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/keycode_events.snapshot new file mode 100644 index 00000000000..6b9fa770b11 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.keymap new file mode 100644 index 00000000000..6351799bd2e --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_scaling/native_posix_64.keymap @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; + + input_configs { + compatible = "zmk,input-configs"; + + mmv { + device = <&mmv>; + scale-multiplier = <5>; + scale-divisor = <3>; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/events.patterns b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/keycode_events.snapshot new file mode 100644 index 00000000000..6b9fa770b11 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.keymap new file mode 100644 index 00000000000..3392dd0de2e --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_invert/native_posix_64.keymap @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; + + input_configs { + compatible = "zmk,input-configs"; + + mmv { + device = <&mmv>; + x-invert; + y-invert; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/events.patterns b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/keycode_events.snapshot new file mode 100644 index 00000000000..6b9fa770b11 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.keymap new file mode 100644 index 00000000000..d4cf5031252 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/input-configs/move_diagonal_xy_swap/native_posix_64.keymap @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; + + input_configs { + compatible = "zmk,input-configs"; + + mmv { + device = <&mmv>; + xy-swap; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_diagonal/events.patterns b/app/tests/mouse-keys/mouse-move/move_diagonal/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_diagonal/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_diagonal/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/move_diagonal/keycode_events.snapshot new file mode 100644 index 00000000000..6b9fa770b11 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_diagonal/keycode_events.snapshot @@ -0,0 +1,18 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.keymap new file mode 100644 index 00000000000..7e4d7af2a1d --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_diagonal/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_UP + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_x/events.patterns b/app/tests/mouse-keys/mouse-move/move_x/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_x/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_x/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/move_x/keycode_events.snapshot new file mode 100644 index 00000000000..678f71c9ac2 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_x/keycode_events.snapshot @@ -0,0 +1,24 @@ +movement_set: Mouse movement set to -1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to -3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 1/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 2/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 3/0 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.keymap new file mode 100644 index 00000000000..89d50e2b839 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_x/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &mmv MOVE_RIGHT + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_y/events.patterns b/app/tests/mouse-keys/mouse-move/move_y/events.patterns new file mode 100644 index 00000000000..812126fb828 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_y/events.patterns @@ -0,0 +1 @@ +s/.*hid_mouse_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mouse-move/move_y/keycode_events.snapshot b/app/tests/mouse-keys/mouse-move/move_y/keycode_events.snapshot new file mode 100644 index 00000000000..d20154d5507 --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_y/keycode_events.snapshot @@ -0,0 +1,24 @@ +movement_set: Mouse movement set to 0/-1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/-3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/1 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/2 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 +movement_set: Mouse movement set to 0/3 +scroll_set: Mouse scroll set to 0/0 +movement_set: Mouse movement set to 0/0 diff --git a/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.keymap b/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.keymap new file mode 100644 index 00000000000..5b02246b05f --- /dev/null +++ b/app/tests/mouse-keys/mouse-move/move_y/native_posix_64.keymap @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_UP &mmv MOVE_DOWN + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,100) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; \ No newline at end of file diff --git a/docs/docs/intro.md b/docs/docs/intro.md index e11eda71f02..c0c70ab0b3f 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -33,7 +33,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | One Shot Keys | ✅ | ✅ | ✅ | | [Combo Keys](features/combos.md) | ✅ | | ✅ | | [Macros](behaviors/macros.md) | ✅ | ✅ | ✅ | -| Mouse Keys | 🚧 | ✅ | ✅ | +| Mouse Keys | ✅ | ✅ | ✅ | | Low Active Power Usage | ✅ | | | | Low Power Sleep States | ✅ | ✅ | | | [Low Power Mode (VCC Shutoff)](behaviors/power.md) | ✅ | ✅ | |