diff --git a/README.md b/README.md index f112b0f9..17e28671 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,33 @@ -# Zephyr™ Mechanical Keyboard (ZMK) Firmware +# ZMK Firmware: Personal fork -[![Discord](https://img.shields.io/discord/719497620560543766)](https://zmk.dev/community/discord/invite) -[![Build](https://github.com/zmkfirmware/zmk/workflows/Build/badge.svg)](https://github.com/zmkfirmware/zmk/actions) -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) +This is my personal ZMK fork containing various experimental features used in +my [zmk-config](https://github.com/urob/zmk-config/). It is regularly rebased +onto the latest upstream. -[ZMK Firmware](https://zmk.dev/) is an open source ([MIT](LICENSE)) keyboard firmware built on the [Zephyr™ Project](https://www.zephyrproject.org/) Real Time Operating System (RTOS). ZMK's goal is to provide a modern, wireless, and powerful firmware free of licensing issues. +Below is a list of features currently included in the `main` branch _on top of_ +the official ZMK master branch. -Check out the website to learn more: https://zmk.dev/. +- **mouse** (PR [#778](https://github.com/zmkfirmware/zmk/pull/778)) - official PR + ftc's update + [update to Zephyr 3.2](https://github.com/urob/zmk/tree/mouse-3.2) + some safeguards + enforce hog device fix +- **swapper** (PR [#1366](https://github.com/zmkfirmware/zmk/pull/1366)) - official PR + fixes needed for Zephyr 3.2 +- **smart-word** (PR [#1451](https://github.com/zmkfirmware/zmk/pull/1451)) - official PR, updated to Zephyr-3.2 +- **fix-key-repeat** - fix [key-repeat rolling issue](https://github.com/zmkfirmware/zmk/issues/1207) +- **on-release-for-tap-preferred** - [on-release option for tap-preferred](https://github.com/celejewski/zmk/commit/d7a8482712d87963e59b74238667346221199293) by Andrzej +- **adv360pro** (PR [#1454](https://github.com/zmkfirmware/zmk/pull/1454)) - offical PR +- **zen-tweaks** - [display & battery improvements](https://github.com/caksoylar/zmk/tree/caksoylar/zen-v1%2Bv2) by Cem Aksoylar -You can also come join our [ZMK Discord Server](https://zmk.dev/community/discord/invite). +In order to use this branch with Github Actions, replace the contents of `west.yml` in +your `zmk-config/config` directory with the following contents: -To review features, check out the [feature overview](https://zmk.dev/docs/). ZMK is under active development, and new features are listed with the [enhancement label](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) in GitHub. Please feel free to add 👍 to the issue description of any requests to upvote the feature. +``` +manifest: + remotes: + - name: urob + url-base: https://github.com/urob + projects: + - name: zmk + remote: urob + revision: main + import: app/west.yml + self: + path: config +``` diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 793f386d..c819ddc0 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -24,6 +24,9 @@ target_sources(app PRIVATE src/stdlib.c) target_sources(app PRIVATE src/activity.c) target_sources(app PRIVATE src/kscan.c) target_sources(app PRIVATE src/matrix_transform.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/key_listener.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/main.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse/tick_listener.c) target_sources(app PRIVATE src/sensors.c) target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c) target_sources(app PRIVATE src/event_manager.c) @@ -31,6 +34,10 @@ target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/ext_power_generic.c) target_sources(app PRIVATE src/events/activity_state_changed.c) target_sources(app PRIVATE src/events/position_state_changed.c) target_sources(app PRIVATE src/events/sensor_event.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/events/mouse_button_state_changed.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/events/mouse_move_state_changed.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/events/mouse_tick.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/events/mouse_scroll_state_changed.c) target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c) target_sources(app PRIVATE src/behaviors/behavior_reset.c) @@ -50,10 +57,14 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c) target_sources(app PRIVATE src/behaviors/behavior_to_layer.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_TRI_STATE app PRIVATE src/behaviors/behavior_tri_state.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE app PRIVATE src/behaviors/behavior_sensor_rotate.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR app PRIVATE src/behaviors/behavior_sensor_rotate_var.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON app PRIVATE src/behaviors/behavior_sensor_rotate_common.c) + target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/behaviors/behavior_mouse_key_press.c) + target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/behaviors/behavior_mouse_move.c) + target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/behaviors/behavior_mouse_scroll.c) target_sources(app PRIVATE src/combo.c) target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c) target_sources(app PRIVATE src/behavior_queue.c) diff --git a/app/Kconfig b/app/Kconfig index 0dd9316a..9fbe678e 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -149,6 +149,10 @@ config ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE int "Max number of consumer HID reports to queue for sending over BLE" default 5 +config ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE + int "Max number of mouse HID reports to queue for sending over BLE" + default 20 + config ZMK_BLE_CLEAR_BONDS_ON_START bool "Configuration that clears all bond information from the keyboard on startup." default n @@ -308,6 +312,13 @@ endif #Display/LED Options endmenu +menu "Mouse Options" + +rsource "src/mouse/Kconfig" + +#Mouse Options +endmenu + menu "Power Management" config ZMK_BATTERY_REPORTING @@ -376,6 +387,12 @@ config ZMK_MACRO_DEFAULT_TAP_MS int "Default time to wait (in milliseconds) between the press and release events of a tapped behavior in macros" default 30 +DT_COMPAT_ZMK_BEHAVIOR_TRI_STATE := zmk,behavior-tri-state + +config ZMK_BEHAVIOR_TRI_STATE + bool + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_BEHAVIOR_TRI_STATE)) + endmenu menu "Advanced" diff --git a/app/boards/arm/adv360pro/Kconfig b/app/boards/arm/adv360pro/Kconfig new file mode 100644 index 00000000..1840851c --- /dev/null +++ b/app/boards/arm/adv360pro/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_ADV360PRO_LEFT || BOARD_ADV360PRO_RIGHT diff --git a/app/boards/arm/adv360pro/Kconfig.board b/app/boards/arm/adv360pro/Kconfig.board new file mode 100644 index 00000000..9ccf6920 --- /dev/null +++ b/app/boards/arm/adv360pro/Kconfig.board @@ -0,0 +1,12 @@ +# +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +config BOARD_ADV360PRO_LEFT + bool "adv360pro_left" + depends on SOC_NRF52840_QIAA + +config BOARD_ADV360PRO_RIGHT + bool "adv360pro_right" + depends on SOC_NRF52840_QIAA diff --git a/app/boards/arm/adv360pro/Kconfig.defconfig b/app/boards/arm/adv360pro/Kconfig.defconfig new file mode 100644 index 00000000..4cc0d85d --- /dev/null +++ b/app/boards/arm/adv360pro/Kconfig.defconfig @@ -0,0 +1,64 @@ +# +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +if BOARD_ADV360PRO_LEFT + + config ZMK_KEYBOARD_NAME + default "Adv360 Pro" + + config ZMK_SPLIT_ROLE_CENTRAL + default y + + +endif # BOARD_ADV360PRO_LEFT + +if BOARD_ADV360PRO_RIGHT + + config ZMK_KEYBOARD_NAME + default "Adv360 Pro rt" + +endif # BOARD_ADV360PRO_RIGHT + + +if BOARD_ADV360PRO_LEFT || BOARD_ADV360PRO_RIGHT + +config BOARD + default "adv360pro" + +config ZMK_SPLIT + default y + +config SPI + bool + default y + +config BT_CTLR + default BT + +config ZMK_BLE + default y + +config ZMK_USB + default y + +if USB + +config USB_NRFX + default y + +config USB_DEVICE_STACK + default y + +endif # USB + +config ZMK_BATTERY_VOLTAGE_DIVIDER + default y + +config SPI + default y + + + +endif # BOARD_ADV360PRO_LEFT || BOARD_ADV360PRO_RIGHT diff --git a/app/boards/arm/adv360pro/adv360pro.dtsi b/app/boards/arm/adv360pro/adv360pro.dtsi new file mode 100644 index 00000000..6bfd0458 --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro.dtsi @@ -0,0 +1,163 @@ +/* +* +* Copyright (c) 2021 Polarity Works +* SPDX-License-Identifier: MIT +* +*/ + +/dts-v1/; +#include + +#include +#include + +#include "adv360pro_pinctrl.dtsi" + +/ { + model = "Adv360"; + compatible = "kinesis,adv360pro"; + + chosen { + zephyr,code-partition = &code_partition; + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zmk,kscan = &kscan0; + zmk,backlight = &backlight; + zmk,battery = &vbatt; + zmk,matrix_transform = &default_transform; + zmk,underglow = &led_strip; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <20>; + rows = <5>; + + + map = < + RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5) RC(4,6) RC(4,13) RC(4,14) RC(4,15) RC(4,16) RC(4,17) RC(4,18) RC(4,19) + RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,13) RC(3,14) RC(3,15) RC(3,16) RC(3,17) RC(3,18) RC(3,19) + RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,8) RC(2,9) RC(2,10) RC(2,11) RC(2,13) RC(2,14) RC(2,15) RC(2,16) RC(2,17) RC(2,18) RC(2,19) + RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,9) RC(1,10) RC(1,14) RC(1,15) RC(1,16) RC(1,17) RC(1,18) RC(1,19) + RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11) RC(0,12) RC(0,15) RC(0,16) RC(0,17) RC(0,18) RC(0,19) + + >; + }; + ext-power { + compatible = "zmk,ext-power-generic"; + label = "EXT_POWER"; + control-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + }; + + vbatt: vbatt { + compatible = "zmk,battery-voltage-divider"; + io-channels = <&adc 2>; + output-ohms = <100000>; + full-ohms = <(100000 + 100000)>; + }; + + backlight: pwmleds { + compatible = "pwm-leds"; + pwm_led_0 { + pwms = <&pwm0 0 10000 PWM_POLARITY_NORMAL>; + }; + }; + +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&adc { + status = "okay"; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&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>; + }; + }; +}; + +&usbd { + status = "okay"; +}; + +&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"; + label = "WS2812"; + + /* SPI */ + reg = <0>; + spi-max-frequency = <4000000>; + + /* WS2812 */ + chain-length = <3>; /* number of LEDs */ + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + + color-mapping = ; + }; +}; diff --git a/app/boards/arm/adv360pro/adv360pro.keymap b/app/boards/arm/adv360pro/adv360pro.keymap new file mode 100644 index 00000000..b554b234 --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro.keymap @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +/ { + behaviors { + #include "macros.dtsi" + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp EQUAL &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &tog 1 &mo 3 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp MINUS + &kp TAB &kp Q &kp W &kp E &kp R &kp T &none &none &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp ESC &kp A &kp S &kp D &kp F &kp G &none &kp LCTRL &kp LALT &kp LGUI &kp RCTRL &none &kp H &kp J &kp K &kp L &kp SEMI &kp SQT + &kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp HOME &kp PG_UP &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp RSHFT + &mo 2 &kp GRAVE &kp CAPS &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp END &kp PG_DN &kp ENTER &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &mo 2 + >; + }; + keypad { + bindings = < + &kp EQUAL &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &trans &mo 3 &kp N6 &kp KP_NUM &kp KP_EQUAL &kp KP_DIVIDE &kp KP_MULTIPLY &kp MINUS + &kp TAB &kp Q &kp W &kp E &kp R &kp T &none &none &kp Y &kp KP_N7 &kp KP_N8 &kp KP_N9 &kp KP_MINUS &kp BSLH + &kp ESC &kp A &kp S &kp D &kp F &kp G &none &kp LCTRL &kp LALT &kp LGUI &kp RCTRL &none &kp H &kp KP_N4 &kp KP_N5 &kp KP_N6 &kp KP_PLUS &kp SQT + &kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp HOME &kp PG_UP &kp N &kp KP_N1 &kp KP_N2 &kp KP_N3 &kp KP_ENTER &kp RSHFT + &mo 2 &kp GRAVE &kp CAPS &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp END &kp PG_DN &kp ENTER &kp KP_N0 &kp UP &kp DOWN &kp KP_DOT &kp RBKT &mo 2 + >; + }; + fn { + bindings = < + &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &tog 1 &mo 3 &kp F7 &kp F8 &kp F9 &kp F10 &kp F11 &kp F12 + &trans &trans &trans &trans &trans &trans &none &none &trans &trans &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans &none &trans &trans &trans &trans &none &trans &trans &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans + &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans + >; + }; + mod { + bindings = < + &none &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 &bt BT_SEL 3 &bt BT_SEL 4 &none &trans &none &none &none &none &none &none + &none &none &none &none &none &none &bootloader &bootloader &none &none &none &none &none &none + &none &none &none &none &none &none &none &none &none &bt BT_CLR &none &none &none &none &none &none &none &none + &none &none &none &none &none &none &none &none &none &none &none &none &none &none + &none &none &none &none &none &none &none &none &none &bl BL_TOG &rgb_ug RGB_TOG &bl BL_INC &bl BL_DEC &none &none &none + >; + }; + }; +}; diff --git a/app/boards/arm/adv360pro/adv360pro.yaml b/app/boards/arm/adv360pro/adv360pro.yaml new file mode 100644 index 00000000..2d555d4e --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro.yaml @@ -0,0 +1,19 @@ +identifier: adv360pro +name: Advantage 360 Pro +type: keyboard +arch: arm +toolchain: + - zephyr + - gnuarmemb + - xtools +supported: + - gpio + - i2c + - counter + - spi + - usb_device + - nvs + - can + - kscan + - ble + - pwm diff --git a/app/boards/arm/adv360pro/adv360pro.zmk.yml b/app/boards/arm/adv360pro/adv360pro.zmk.yml new file mode 100644 index 00000000..7d4a4b44 --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro.zmk.yml @@ -0,0 +1,16 @@ +file_format: "1" +id: adv360pro +name: Advantage 360 Pro +type: board +url: https://kinesis-ergo.com/keyboards/advantage360 +arch: arm +features: + - keys + - underglow + - backlight +outputs: + - usb + - ble +siblings: + - adv360pro_left + - adv360pro_right diff --git a/app/boards/arm/adv360pro/adv360pro_left.dts b/app/boards/arm/adv360pro/adv360pro_left.dts new file mode 100644 index 00000000..ab21d7f2 --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro_left.dts @@ -0,0 +1,36 @@ +/* +* +* Copyright (c) 2022 The ZMK Contributors +* SPDX-License-Identifier: MIT +* +*/ + +#include "adv360pro.dtsi" + +/{ + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + + diode-direction = "col2row"; + row-gpios + = <&gpio1 11 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio1 15 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio1 14 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio1 12 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + + col-gpios + = <&gpio0 25 GPIO_ACTIVE_HIGH> + , <&gpio0 11 GPIO_ACTIVE_HIGH> + , <&gpio0 2 GPIO_ACTIVE_HIGH> + , <&gpio0 28 GPIO_ACTIVE_HIGH> + , <&gpio0 29 GPIO_ACTIVE_HIGH> + , <&gpio0 30 GPIO_ACTIVE_HIGH> + , <&gpio0 31 GPIO_ACTIVE_HIGH> + , <&gpio1 9 GPIO_ACTIVE_HIGH> + , <&gpio0 12 GPIO_ACTIVE_HIGH> + , <&gpio0 7 GPIO_ACTIVE_HIGH> + ; + }; +}; diff --git a/app/boards/arm/adv360pro/adv360pro_left.keymap b/app/boards/arm/adv360pro/adv360pro_left.keymap new file mode 100644 index 00000000..4ff878bf --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro_left.keymap @@ -0,0 +1,8 @@ +/* +* +* Copyright (c) 2022 The ZMK Contributors +* SPDX-License-Identifier: MIT +* +*/ + +#include "adv360pro.keymap" diff --git a/app/boards/arm/adv360pro/adv360pro_left_defconfig b/app/boards/arm/adv360pro/adv360pro_left_defconfig new file mode 100644 index 00000000..5589cf2e --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro_left_defconfig @@ -0,0 +1,53 @@ +# +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_ADV360PRO_LEFT=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# enable GPIO +CONFIG_GPIO=y + +# Enable SPI for LEDS +CONFIG_PINCTRL=y +CONFIG_SPI=y +CONFIG_SPI_NRFX=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_WS2812_STRIP=y +CONFIG_ZMK_RGB_UNDERGLOW=y +CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y +CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n +CONFIG_ZMK_RGB_UNDERGLOW_EFF_START=0 +CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE=y + +#Backlighting configuration +CONFIG_PWM=y +CONFIG_LED_PWM=y +CONFIG_ZMK_BACKLIGHT=y +CONFIG_ZMK_BACKLIGHT_BRT_START=20 +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE=y + +#Misc configuration +CONFIG_BT_CTLR_TX_PWR_PLUS_8=y +CONFIG_ZMK_HID_REPORT_TYPE_NKRO=y +CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC=y +CONFIG_BUILD_OUTPUT_UF2=y diff --git a/app/boards/arm/adv360pro/adv360pro_pinctrl.dtsi b/app/boards/arm/adv360pro/adv360pro_pinctrl.dtsi new file mode 100644 index 00000000..7dafcdce --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro_pinctrl.dtsi @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 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; + }; + }; +}; \ No newline at end of file diff --git a/app/boards/arm/adv360pro/adv360pro_right.dts b/app/boards/arm/adv360pro/adv360pro_right.dts new file mode 100644 index 00000000..03e6d6bb --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro_right.dts @@ -0,0 +1,43 @@ +/* +* +* Copyright (c) 2022 The ZMK Contributors +* SPDX-License-Identifier: MIT +* +*/ + +#include "adv360pro.dtsi" + +/{ + + + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + + diode-direction = "col2row"; + row-gpios + = <&gpio0 19 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 31 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 30 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&gpio0 29 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + + col-gpios + = <&gpio0 12 GPIO_ACTIVE_HIGH> + , <&gpio1 9 GPIO_ACTIVE_HIGH> + , <&gpio0 7 GPIO_ACTIVE_HIGH> + , <&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> + ; + }; +}; + +&default_transform { + col-offset = <10>; + }; diff --git a/app/boards/arm/adv360pro/adv360pro_right.keymap b/app/boards/arm/adv360pro/adv360pro_right.keymap new file mode 100644 index 00000000..4ff878bf --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro_right.keymap @@ -0,0 +1,8 @@ +/* +* +* Copyright (c) 2022 The ZMK Contributors +* SPDX-License-Identifier: MIT +* +*/ + +#include "adv360pro.keymap" diff --git a/app/boards/arm/adv360pro/adv360pro_right_defconfig b/app/boards/arm/adv360pro/adv360pro_right_defconfig new file mode 100644 index 00000000..eaad5966 --- /dev/null +++ b/app/boards/arm/adv360pro/adv360pro_right_defconfig @@ -0,0 +1,53 @@ +# +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_ADV360PRO_RIGHT=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# enable GPIO +CONFIG_GPIO=y + +# Enable SPI for LEDS +CONFIG_PINCTRL=y +CONFIG_SPI=y +CONFIG_SPI_NRFX=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_WS2812_STRIP=y +CONFIG_ZMK_RGB_UNDERGLOW=y +CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y +CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n +CONFIG_ZMK_RGB_UNDERGLOW_EFF_START=0 +CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE=y + +#Backlighting configuration +CONFIG_PWM=y +CONFIG_LED_PWM=y +CONFIG_ZMK_BACKLIGHT=y +CONFIG_ZMK_BACKLIGHT_BRT_START=20 +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE=y + +#Misc configuration +CONFIG_BT_CTLR_TX_PWR_PLUS_8=y +CONFIG_ZMK_HID_REPORT_TYPE_NKRO=y +CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC=y +CONFIG_BUILD_OUTPUT_UF2=y diff --git a/app/boards/arm/adv360pro/board.cmake b/app/boards/arm/adv360pro/board.cmake new file mode 100644 index 00000000..34652907 --- /dev/null +++ b/app/boards/arm/adv360pro/board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +board_runner_args(nrfjprog "--nrf-family=NRF52" "--softreset") + +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) +#include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake) diff --git a/app/boards/arm/adv360pro/macros.dtsi b/app/boards/arm/adv360pro/macros.dtsi new file mode 100644 index 00000000..69f6c4f3 --- /dev/null +++ b/app/boards/arm/adv360pro/macros.dtsi @@ -0,0 +1,36 @@ + macro_quotes: macro_quotes { + compatible = "zmk,behavior-macro"; + label = "macro_quotes"; + #binding-cells = <0>; + bindings = <&kp SQT>, <&kp SQT>, <&kp LEFT>; + }; + macro_dquotes: macro_dquotes { + compatible = "zmk,behavior-macro"; + label = "macro_dquotes"; + #binding-cells = <0>; + bindings = <&kp DQT>, <&kp DQT>, <&kp LEFT>; + }; + macro_braces: macro_braces { + compatible = "zmk,behavior-macro"; + label = "macro_braces"; + #binding-cells = <0>; + bindings = <&kp LBRC>, <&kp RBRC>, <&kp LEFT>; + }; + macro_parens: macro_parens { + compatible = "zmk,behavior-macro"; + label = "macro_parens"; + #binding-cells = <0>; + bindings = <&kp LPAR>, <&kp RPAR>, <&kp LEFT>; + }; + macro_brackets: macro_brackets { + compatible = "zmk,behavior-macro"; + label = "macro_brackets"; + #binding-cells = <0>; + bindings = <&kp LBKT>, <&kp RBKT>, <&kp LEFT>; + }; + macro_kinesis: macro_kinesis { + compatible = "zmk,behavior-macro"; + label = "macro_kinesis"; + #binding-cells = <0>; + bindings = <&kp K>, <&kp I>, <&kp N>, <&kp E>, <&kp S>, <&kp I>, <&kp S>; + }; diff --git a/app/boards/arm/corneish_zen/Kconfig.defconfig b/app/boards/arm/corneish_zen/Kconfig.defconfig index feab3eca..8b0cab87 100644 --- a/app/boards/arm/corneish_zen/Kconfig.defconfig +++ b/app/boards/arm/corneish_zen/Kconfig.defconfig @@ -77,6 +77,9 @@ menuconfig CUSTOM_WIDGET_LAYER_STATUS menuconfig CUSTOM_WIDGET_PERIPHERAL_STATUS bool "custom peripheral status widget" +config CUSTOM_WIDGET_LAYER_STATUS_HIDE_HEADING + bool "Hide heading strip for layer widget and center widgets on 1/3rds" + endif # BOARD_CORNEISH_ZEN_LEFT || BOARD_CORNEISH_ZEN_RIGHT if BOARD_CORNEISH_ZEN_V1_LEFT || BOARD_CORNEISH_ZEN_V1_RIGHT diff --git a/app/boards/arm/corneish_zen/custom_status_screen.c b/app/boards/arm/corneish_zen/custom_status_screen.c index 492239c8..159803b6 100644 --- a/app/boards/arm/corneish_zen/custom_status_screen.c +++ b/app/boards/arm/corneish_zen/custom_status_screen.c @@ -40,37 +40,60 @@ lv_obj_t *zmk_display_status_screen() { #if IS_ENABLED(CONFIG_CUSTOM_WIDGET_BATTERY_STATUS) zmk_widget_battery_status_init(&battery_status_widget, screen); +#if IS_ENABLED(CONFIG_CUSTOM_WIDGET_LAYER_STATUS_HIDE_HEADING) + lv_obj_align(zmk_widget_battery_status_obj(&battery_status_widget), LV_ALIGN_CENTER, 0, -43); +#else lv_obj_align(zmk_widget_battery_status_obj(&battery_status_widget), LV_ALIGN_TOP_MID, 0, 2); #endif +#endif #if IS_ENABLED(CONFIG_CUSTOM_WIDGET_OUTPUT_STATUS) zmk_widget_output_status_init(&output_status_widget, screen); +#if IS_ENABLED(CONFIG_CUSTOM_WIDGET_LAYER_STATUS_HIDE_HEADING) + lv_obj_align(zmk_widget_output_status_obj(&output_status_widget), LV_ALIGN_CENTER, 0, 0); +#else lv_obj_align(zmk_widget_output_status_obj(&output_status_widget), LV_ALIGN_TOP_MID, 0, 41); #endif +#endif #if IS_ENABLED(CONFIG_CUSTOM_WIDGET_PERIPHERAL_STATUS) zmk_widget_peripheral_status_init(&peripheral_status_widget, screen); +#if IS_ENABLED(CONFIG_CUSTOM_WIDGET_LAYER_STATUS_HIDE_HEADING) + lv_obj_align(zmk_widget_peripheral_status_obj(&peripheral_status_widget), LV_ALIGN_CENTER, 0, + 0); +#else lv_obj_align(zmk_widget_peripheral_status_obj(&peripheral_status_widget), LV_ALIGN_TOP_MID, 0, 41); #endif +#endif #if IS_ENABLED(CONFIG_CUSTOM_WIDGET_LAYER_STATUS) +#if !IS_ENABLED(CONFIG_CUSTOM_WIDGET_LAYER_STATUS_HIDE_HEADING) lv_obj_t *LayersHeading; LayersHeading = lv_img_create(screen); lv_obj_align(LayersHeading, LV_ALIGN_BOTTOM_MID, 0, -30); lv_img_set_src(LayersHeading, &layers2); +#endif zmk_widget_layer_status_init(&layer_status_widget, screen); lv_obj_set_style_text_font(zmk_widget_layer_status_obj(&layer_status_widget), &lv_font_montserrat_16, LV_PART_MAIN); +#if IS_ENABLED(CONFIG_CUSTOM_WIDGET_LAYER_STATUS_HIDE_HEADING) + lv_obj_align(zmk_widget_layer_status_obj(&layer_status_widget), LV_ALIGN_CENTER, 0, 43); +#else lv_obj_align(zmk_widget_layer_status_obj(&layer_status_widget), LV_ALIGN_BOTTOM_MID, 0, -5); #endif +#endif #if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) lv_obj_t *zenlogo_icon; zenlogo_icon = lv_img_create(screen); lv_img_set_src(zenlogo_icon, &zenlogo); +#if IS_ENABLED(CONFIG_CUSTOM_WIDGET_LAYER_STATUS_HIDE_HEADING) + lv_obj_align(zenlogo_icon, LV_ALIGN_CENTER, 0, 43); +#else lv_obj_align(zenlogo_icon, LV_ALIGN_BOTTOM_MID, 0, -5); +#endif #endif return screen; diff --git a/app/boards/arm/corneish_zen/widgets/battery_status.c b/app/boards/arm/corneish_zen/widgets/battery_status.c index 9a2189d1..bf2bd15c 100644 --- a/app/boards/arm/corneish_zen/widgets/battery_status.c +++ b/app/boards/arm/corneish_zen/widgets/battery_status.c @@ -41,21 +41,52 @@ LV_IMG_DECLARE(batt_0); LV_IMG_DECLARE(batt_0_chg); static void set_battery_symbol(lv_obj_t *icon, struct battery_status_state state) { - uint8_t level = state.level; - #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) - if (level > 95) { - lv_img_set_src(icon, state.usb_present ? &batt_100_chg : &batt_100); - } else if (level > 74) { - lv_img_set_src(icon, state.usb_present ? &batt_75_chg : &batt_75); - } else if (level > 49) { - lv_img_set_src(icon, state.usb_present ? &batt_50_chg : &batt_50); - } else if (level > 24) { - lv_img_set_src(icon, state.usb_present ? &batt_25_chg : &batt_25); + static uint8_t stage_prev = 255; + static bool usb_prev = false; + + uint8_t level = state.level; + bool usb_present = state.usb_present; + uint8_t stage; + + if (level > 87) { + stage = 5; + } else if (level > 62) { + stage = 4; + } else if (level > 37) { + stage = 3; + } else if (level > 12) { + stage = 2; } else if (level > 5) { - lv_img_set_src(icon, state.usb_present ? &batt_5_chg : &batt_5); + stage = 1; } else { - lv_img_set_src(icon, state.usb_present ? &batt_0_chg : &batt_0); + stage = 0; + } + + // check if there is a change requiring an update + if (usb_present != usb_prev || stage != stage_prev) { + switch (stage) { + case 5: + lv_img_set_src(icon, usb_present ? &batt_100_chg : &batt_100); + break; + case 4: + lv_img_set_src(icon, usb_present ? &batt_75_chg : &batt_75); + break; + case 3: + lv_img_set_src(icon, usb_present ? &batt_50_chg : &batt_50); + break; + case 2: + lv_img_set_src(icon, usb_present ? &batt_25_chg : &batt_25); + break; + case 1: + lv_img_set_src(icon, usb_present ? &batt_5_chg : &batt_5); + break; + default: + lv_img_set_src(icon, usb_present ? &batt_0_chg : &batt_0); + break; + } + usb_prev = usb_present; + stage_prev = stage; } #endif /* IS_ENABLED(CONFIG_USB_DEVICE_STACK) */ } diff --git a/app/boards/arm/corneish_zen/widgets/layer_status.c b/app/boards/arm/corneish_zen/widgets/layer_status.c index 3dc33613..b7c78121 100644 --- a/app/boards/arm/corneish_zen/widgets/layer_status.c +++ b/app/boards/arm/corneish_zen/widgets/layer_status.c @@ -27,6 +27,16 @@ static void set_layer_symbol(lv_obj_t *label, struct layer_status_state state) { const char *layer_label = state.label; uint8_t active_layer_index = state.index; +#if IS_ENABLED(CONFIG_ZMK_DISPLAY_HIDE_MOMENTARY_LAYERS) + static uint8_t last_perm_index = 255; + if (!zmk_keymap_layer_momentary(active_layer_index) && last_perm_index != active_layer_index) { + last_perm_index = active_layer_index; + LOG_DBG("Last perm layer index updated to %i", active_layer_index); + } else { + return; + } +#endif + if (layer_label == NULL) { char text[6] = {}; diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index b3502cbb..77eccf91 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -18,4 +18,7 @@ #include #include #include -#include \ No newline at end of file +#include +#include +#include +#include diff --git a/app/dts/behaviors/caps_word.dtsi b/app/dts/behaviors/caps_word.dtsi index 219300dc..8dc7858a 100644 --- a/app/dts/behaviors/caps_word.dtsi +++ b/app/dts/behaviors/caps_word.dtsi @@ -12,7 +12,22 @@ compatible = "zmk,behavior-caps-word"; label = "CAPS_WORD"; #binding-cells = <0>; + mods = ; continue-list = ; + ignore-alphas; + ignore-numbers; + ignore-modifiers; + }; + }; + + behaviors { + /omit-if-no-ref/ num_word: behavior_num_word { + compatible = "zmk,behavior-caps-word"; + label = "NUM_WORD"; + #binding-cells = <0>; + // layers = ; // to be specified in user config using "&num_word { layers = ; };" + continue-list = ; + ignore-numbers; }; }; }; diff --git a/app/dts/behaviors/mouse_key_press.dtsi b/app/dts/behaviors/mouse_key_press.dtsi new file mode 100644 index 00000000..9cc16e81 --- /dev/null +++ b/app/dts/behaviors/mouse_key_press.dtsi @@ -0,0 +1,9 @@ +/ { + behaviors { + /omit-if-no-ref/ mkp: behavior_mouse_key_press { + compatible = "zmk,behavior-mouse-key-press"; + label = "MOUSE_KEY_PRESS"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/behaviors/mouse_move.dtsi b/app/dts/behaviors/mouse_move.dtsi new file mode 100644 index 00000000..2e165f3a --- /dev/null +++ b/app/dts/behaviors/mouse_move.dtsi @@ -0,0 +1,12 @@ +/ { + behaviors { + /omit-if-no-ref/ mmv: behavior_mouse_move { + compatible = "zmk,behavior-mouse-move"; + label = "MOUSE_MOVE"; + #binding-cells = <1>; + delay-ms = <0>; + time-to-max-speed-ms = <300>; + acceleration-exponent = <1>; + }; + }; +}; diff --git a/app/dts/behaviors/mouse_scroll.dtsi b/app/dts/behaviors/mouse_scroll.dtsi new file mode 100644 index 00000000..efc9fedc --- /dev/null +++ b/app/dts/behaviors/mouse_scroll.dtsi @@ -0,0 +1,12 @@ +/ { + behaviors { + /omit-if-no-ref/ mwh: msc: behavior_mouse_scroll { + compatible = "zmk,behavior-mouse-scroll"; + label = "MOUSE_SCROLL"; + #binding-cells = <1>; + delay-ms = <0>; + time-to-max-speed-ms = <300>; + acceleration-exponent = <0>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml b/app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml index cc1dda01..eb67a203 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml @@ -13,3 +13,11 @@ properties: required: true mods: type: int + layers: + type: int + ignore-alphas: + type: boolean + ignore-numbers: + type: boolean + ignore-modifiers: + type: boolean diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml new file mode 100644 index 00000000..8540916b --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml @@ -0,0 +1,5 @@ +description: Mouse key press/release behavior + +compatible: "zmk,behavior-mouse-key-press" + +include: one_param.yaml diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml new file mode 100644 index 00000000..73ec34ec --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml @@ -0,0 +1,13 @@ +description: Mouse move + +compatible: "zmk,behavior-mouse-move" + +include: one_param.yaml + +properties: + delay-ms: + type: int + time-to-max-speed-ms: + type: int + acceleration-exponent: + type: int diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml new file mode 100644 index 00000000..5a932bc5 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml @@ -0,0 +1,13 @@ +description: Mouse scroll + +compatible: "zmk,behavior-mouse-scroll" + +include: one_param.yaml + +properties: + delay-ms: + type: int + time-to-max-speed-ms: + type: int + acceleration-exponent: + type: int diff --git a/app/dts/bindings/behaviors/zmk,behavior-tri-state.yaml b/app/dts/bindings/behaviors/zmk,behavior-tri-state.yaml new file mode 100644 index 00000000..bf9eb5d5 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-tri-state.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Tri-State Behavior + +compatible: "zmk,behavior-tri-state" + +include: zero_param.yaml + +properties: + bindings: + type: phandle-array + required: true + ignored-key-positions: + type: array + required: false + default: [] + ignored-layers: + type: array + required: false + default: [] + timeout-ms: + type: int + default: -1 + tap-ms: + type: int + default: 5 diff --git a/app/include/dt-bindings/zmk/hid_usage_pages.h b/app/include/dt-bindings/zmk/hid_usage_pages.h index 2ccdba55..7fa54fd8 100644 --- a/app/include/dt-bindings/zmk/hid_usage_pages.h +++ b/app/include/dt-bindings/zmk/hid_usage_pages.h @@ -26,6 +26,7 @@ #define HID_USAGE_GDV (0x06) // Generic Device Controls #define HID_USAGE_KEY (0x07) // Keyboard/Keypad #define HID_USAGE_LED (0x08) // LED +#define HID_USAGE_BUTTON (0x09) // Button #define HID_USAGE_TELEPHONY (0x0B) // Telephony Device #define HID_USAGE_CONSUMER (0x0C) // Consumer #define HID_USAGE_DIGITIZERS (0x0D) // Digitizers diff --git a/app/include/dt-bindings/zmk/mouse.h b/app/include/dt-bindings/zmk/mouse.h new file mode 100644 index 00000000..cf0415c9 --- /dev/null +++ b/app/include/dt-bindings/zmk/mouse.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +/* Mouse press behavior */ +/* Left click */ +#define MB1 (0x01) +#define LCLK (MB1) + +/* Right click */ +#define MB2 (0x02) +#define RCLK (MB2) + +/* Middle click */ +#define MB3 (0x04) +#define MCLK (MB3) + +#define MB4 (0x08) + +#define MB5 (0x10) + +#define MB6 (0x20) + +#define MB7 (0x40) + +#define MB8 (0x80) + +/* Mouse move behavior */ +#define MOVE_VERT(vert) ((vert)&0xFFFF) +#define MOVE_VERT_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF) +#define MOVE_HOR(hor) (((hor)&0xFFFF) << 16) +#define MOVE_HOR_DECODE(encoded) (int16_t)(((encoded)&0xFFFF0000) >> 16) + +#define MOVE(hor, vert) (MOVE_HOR(hor) + MOVE_VERT(vert)) + +#define MOVE_UP MOVE_VERT(-600) +#define MOVE_DOWN MOVE_VERT(600) +#define MOVE_LEFT MOVE_HOR(-600) +#define MOVE_RIGHT MOVE_HOR(600) + +/* Mouse scroll behavior */ +#define SCROLL_VERT(vert) ((vert)&0xFFFF) +#define SCROLL_VERT_DECODE(encoded) (int16_t)((encoded)&0x0000FFFF) +#define SCROLL_HOR(hor) (((hor)&0xFFFF) << 16) +#define SCROLL_HOR_DECODE(encoded) (int16_t)(((encoded)&0xFFFF0000) >> 16) + +#define SCROLL(hor, vert) (SCROLL_HOR(hor) + SCROLL_VERT(vert)) + +#define SCROLL_UP SCROLL_VERT(10) +#define SCROLL_DOWN SCROLL_VERT(-10) +#define SCROLL_LEFT SCROLL_HOR(-10) +#define SCROLL_RIGHT SCROLL_HOR(10) diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index c5964ff8..1e412e8b 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -69,3 +69,4 @@ int zmk_endpoints_toggle_transport(void); struct zmk_endpoint_instance zmk_endpoints_selected(void); int zmk_endpoints_send_report(uint16_t usage_page); +int zmk_endpoints_send_mouse_report(); diff --git a/app/include/zmk/events/mouse_button_state_changed.h b/app/include/zmk/events/mouse_button_state_changed.h new file mode 100644 index 00000000..346ed1c8 --- /dev/null +++ b/app/include/zmk/events/mouse_button_state_changed.h @@ -0,0 +1,27 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +struct zmk_mouse_button_state_changed { + zmk_mouse_button_t buttons; + bool state; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_button_state_changed); + +static inline struct zmk_mouse_button_state_changed_event * +zmk_mouse_button_state_changed_from_encoded(uint32_t encoded, bool pressed, int64_t timestamp) { + return new_zmk_mouse_button_state_changed((struct zmk_mouse_button_state_changed){ + .buttons = ZMK_HID_USAGE_ID(encoded), .state = pressed, .timestamp = timestamp}); +} diff --git a/app/include/zmk/events/mouse_move_state_changed.h b/app/include/zmk/events/mouse_move_state_changed.h new file mode 100644 index 00000000..b1a4ffa7 --- /dev/null +++ b/app/include/zmk/events/mouse_move_state_changed.h @@ -0,0 +1,33 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include + +struct zmk_mouse_move_state_changed { + struct vector2d max_speed; + struct mouse_config config; + bool state; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_move_state_changed); + +static inline struct zmk_mouse_move_state_changed_event * +zmk_mouse_move_state_changed_from_encoded(uint32_t encoded, struct mouse_config config, + bool pressed, int64_t timestamp) { + struct vector2d max_speed = (struct vector2d){ + .x = MOVE_HOR_DECODE(encoded), + .y = MOVE_VERT_DECODE(encoded), + }; + + return new_zmk_mouse_move_state_changed((struct zmk_mouse_move_state_changed){ + .max_speed = max_speed, .config = config, .state = pressed, .timestamp = timestamp}); +} diff --git a/app/include/zmk/events/mouse_scroll_state_changed.h b/app/include/zmk/events/mouse_scroll_state_changed.h new file mode 100644 index 00000000..0f96e82b --- /dev/null +++ b/app/include/zmk/events/mouse_scroll_state_changed.h @@ -0,0 +1,34 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +struct zmk_mouse_scroll_state_changed { + struct vector2d max_speed; + struct mouse_config config; + bool state; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_scroll_state_changed); + +static inline struct zmk_mouse_scroll_state_changed_event * +zmk_mouse_scroll_state_changed_from_encoded(uint32_t encoded, struct mouse_config config, + bool pressed, int64_t timestamp) { + struct vector2d max_speed = (struct vector2d){ + .x = SCROLL_HOR_DECODE(encoded), + .y = SCROLL_VERT_DECODE(encoded), + }; + + return new_zmk_mouse_scroll_state_changed((struct zmk_mouse_scroll_state_changed){ + .max_speed = max_speed, .config = config, .state = pressed, .timestamp = timestamp}); +} diff --git a/app/include/zmk/events/mouse_tick.h b/app/include/zmk/events/mouse_tick.h new file mode 100644 index 00000000..2f69b045 --- /dev/null +++ b/app/include/zmk/events/mouse_tick.h @@ -0,0 +1,39 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +struct zmk_mouse_tick { + struct vector2d max_move; + struct vector2d max_scroll; + struct mouse_config move_config; + struct mouse_config scroll_config; + int64_t *start_time; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_tick); + +static inline struct zmk_mouse_tick_event *zmk_mouse_tick(struct vector2d max_move, + struct vector2d max_scroll, + struct mouse_config move_config, + struct mouse_config scroll_config, + int64_t *movement_start) { + return new_zmk_mouse_tick((struct zmk_mouse_tick){ + .max_move = max_move, + .max_scroll = max_scroll, + .move_config = move_config, + .scroll_config = scroll_config, + .start_time = movement_start, + .timestamp = k_uptime_get(), + }); +} diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index ab42adaa..217e384c 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -85,9 +86,85 @@ static const uint8_t zmk_hid_report_desc[] = { #else #error "A proper consumer HID report usage range must be selected" #endif + /* REPORT_COUNT (CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE) */ HID_REPORT_COUNT(CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE), - /* INPUT (Data,Ary,Abs) */ HID_INPUT(0x00), + /* END COLLECTION */ + HID_END_COLLECTION, + + /* USAGE_PAGE (Generic Desktop) */ + HID_USAGE_PAGE(HID_USAGE_GD), + /* USAGE (Mouse) */ + HID_USAGE(HID_USAGE_GD_MOUSE), + /* COLLECTION (Application) */ + HID_COLLECTION(HID_COLLECTION_APPLICATION), + /* REPORT ID (4) */ + HID_REPORT_ID(0x04), + /* USAGE (Pointer) */ + HID_USAGE(HID_USAGE_GD_POINTER), + /* COLLECTION (Physical) */ + HID_COLLECTION(HID_COLLECTION_PHYSICAL), + /* USAGE_PAGE (Button) */ + HID_USAGE_PAGE(HID_USAGE_BUTTON), + /* USAGE_MINIMUM (0x1) (button 1?) */ + HID_USAGE_MIN8(0x01), + /* USAGE_MAXIMUM (0x10) (button 5? Buttons up to 8 still work) */ + HID_USAGE_MAX8(0x10), + /* LOGICAL_MINIMUM (0) */ + HID_LOGICAL_MIN8(0x00), + /* LOGICAL_MAXIMUM (1) */ + HID_LOGICAL_MAX8(0x01), + /* REPORT_SIZE (1) */ + HID_REPORT_SIZE(0x01), + /* REPORT_COUNT (16) */ + HID_REPORT_COUNT(0x10), + /* INPUT (Data,Var,Abs) */ + HID_INPUT(0x02), + /* USAGE_PAGE (Generic Desktop) */ + HID_USAGE_PAGE(HID_USAGE_GD), + /* LOGICAL_MINIMUM (-32767) */ + HID_LOGICAL_MIN16(0x01, 0x80), + /* LOGICAL_MAXIMUM (32767) */ + HID_LOGICAL_MAX16(0xFF, 0x7F), + /* REPORT_SIZE (16) */ + HID_REPORT_SIZE(0x10), + /* REPORT_COUNT (2) */ + HID_REPORT_COUNT(0x02), + /* USAGE (X) */ // Vertical scroll + HID_USAGE(HID_USAGE_GD_X), + /* USAGE (Y) */ + HID_USAGE(HID_USAGE_GD_Y), + /* Input (Data,Var,Rel) */ + HID_INPUT(0x06), + /* LOGICAL_MINIMUM (-127) */ + HID_LOGICAL_MIN8(0x81), + /* LOGICAL_MAXIMUM (127) */ + HID_LOGICAL_MAX8(0x7F), + /* REPORT_SIZE (8) */ + HID_REPORT_SIZE(0x08), + /* REPORT_COUNT (1) */ + HID_REPORT_COUNT(0x01), + /* USAGE (Wheel) */ + HID_USAGE(HID_USAGE_GD_WHEEL), + /* Input (Data,Var,Rel) */ + HID_INPUT(0x06), + /* USAGE_PAGE (Consumer) */ // Horizontal scroll + HID_USAGE_PAGE(HID_USAGE_CONSUMER), + /* USAGE (AC Pan) */ + 0x0A, + 0x38, + 0x02, + /* LOGICAL_MINIMUM (-127) */ + HID_LOGICAL_MIN8(0x81), + /* LOGICAL_MAXIMUM (127) */ + HID_LOGICAL_MAX8(0x7F), + /* REPORT_COUNT (1) */ + HID_REPORT_COUNT(0x01), + /* Input (Data,Var,Rel) */ + HID_INPUT(0x06), + /* END COLLECTION */ + HID_END_COLLECTION, + /* END COLLECTION */ HID_END_COLLECTION, }; @@ -126,6 +203,19 @@ struct zmk_hid_consumer_report { struct zmk_hid_consumer_report_body body; } __packed; +struct zmk_hid_mouse_report_body { + zmk_mouse_button_flags_t buttons; + int16_t x; + int16_t y; + int8_t scroll_y; + int8_t scroll_x; +} __packed; + +struct zmk_hid_mouse_report { + uint8_t report_id; + struct zmk_hid_mouse_report_body body; +} __packed; + zmk_mod_flags_t zmk_hid_get_explicit_mods(); int zmk_hid_register_mod(zmk_mod_t modifier); int zmk_hid_unregister_mod(zmk_mod_t modifier); @@ -152,5 +242,16 @@ int zmk_hid_press(uint32_t usage); int zmk_hid_release(uint32_t usage); bool zmk_hid_is_pressed(uint32_t usage); +int zmk_hid_mouse_button_press(zmk_mouse_button_t button); +int zmk_hid_mouse_button_release(zmk_mouse_button_t button); +int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons); +int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons); +void zmk_hid_mouse_movement_set(int16_t x, int16_t y); +void zmk_hid_mouse_scroll_set(int8_t x, int8_t y); +void zmk_hid_mouse_movement_update(int16_t x, int16_t y); +void zmk_hid_mouse_scroll_update(int8_t x, int8_t y); +void zmk_hid_mouse_clear(); + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); +struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index 7523fb66..1b262427 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -13,3 +13,5 @@ int zmk_hog_init(); int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *body); int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); +int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); +int zmk_hog_send_mouse_report_direct(struct zmk_hid_mouse_report_body *body); diff --git a/app/include/zmk/keymap.h b/app/include/zmk/keymap.h index 9ce140bf..6f3df7f5 100644 --- a/app/include/zmk/keymap.h +++ b/app/include/zmk/keymap.h @@ -17,8 +17,10 @@ typedef uint32_t zmk_keymap_layers_state_t; uint8_t zmk_keymap_layer_default(); zmk_keymap_layers_state_t zmk_keymap_layer_state(); bool zmk_keymap_layer_active(uint8_t layer); +bool zmk_keymap_layer_momentary(uint8_t layer); +bool zmk_keymap_layers_any_momentary(zmk_keymap_layers_state_t layers_mask); uint8_t zmk_keymap_highest_layer_active(); -int zmk_keymap_layer_activate(uint8_t layer); +int zmk_keymap_layer_activate(uint8_t layer, bool momentary); int zmk_keymap_layer_deactivate(uint8_t layer); int zmk_keymap_layer_toggle(uint8_t layer); int zmk_keymap_layer_to(uint8_t layer); diff --git a/app/include/zmk/mouse.h b/app/include/zmk/mouse.h new file mode 100644 index 00000000..c3851773 --- /dev/null +++ b/app/include/zmk/mouse.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +typedef uint16_t zmk_mouse_button_flags_t; +typedef uint16_t zmk_mouse_button_t; + +struct mouse_config { + int delay_ms; + int time_to_max_speed_ms; + // acceleration exponent 0: uniform speed + // acceleration exponent 1: uniform acceleration + // acceleration exponent 2: uniform jerk + int acceleration_exponent; +}; + +struct vector2d { + float x; + float y; +}; + +struct k_work_q *zmk_mouse_work_q(); +int zmk_mouse_init(); \ No newline at end of file diff --git a/app/module/drivers/display/Kconfig.il0323 b/app/module/drivers/display/Kconfig.il0323 index f3308c16..2704023c 100644 --- a/app/module/drivers/display/Kconfig.il0323 +++ b/app/module/drivers/display/Kconfig.il0323 @@ -8,4 +8,12 @@ config IL0323 depends on SPI depends on HEAP_MEM_POOL_SIZE != 0 help - Enable driver for IL0323 compatible controller. \ No newline at end of file + Enable driver for IL0323 compatible controller. + +config IL0323_INVERT + bool "Invert display" + default n + +config IL0323_ALTERNATIVE_REFRESH + bool "Use an alternative approach for partial refreshes" + default n diff --git a/app/module/drivers/display/il0323.c b/app/module/drivers/display/il0323.c index 6555e5c1..9f16d229 100644 --- a/app/module/drivers/display/il0323.c +++ b/app/module/drivers/display/il0323.c @@ -124,7 +124,11 @@ static int il0323_write(const struct device *dev, const uint16_t x, const uint16 ptl[IL0323_PTL_HRED_IDX] = x_end_idx; ptl[IL0323_PTL_VRST_IDX] = y; ptl[IL0323_PTL_VRED_IDX] = y_end_idx; +#if IS_ENABLED(CONFIG_IL0323_ALTERNATIVE_REFRESH) + ptl[sizeof(ptl) - 1] = 0; // limits fading outside of refresh window +#else ptl[sizeof(ptl) - 1] = IL0323_PTL_PT_SCAN; +#endif LOG_HEXDUMP_DBG(ptl, sizeof(ptl), "ptl"); il0323_busy_wait(cfg); @@ -242,8 +246,12 @@ static void il0323_get_capabilities(const struct device *dev, struct display_cap memset(caps, 0, sizeof(struct display_capabilities)); caps->x_resolution = EPD_PANEL_WIDTH; caps->y_resolution = EPD_PANEL_HEIGHT; - caps->supported_pixel_formats = PIXEL_FORMAT_MONO10; + caps->supported_pixel_formats = PIXEL_FORMAT_MONO10 | PIXEL_FORMAT_MONO01; +#if IS_ENABLED(CONFIG_IL0323_INVERT) + caps->current_pixel_format = PIXEL_FORMAT_MONO01; +#else caps->current_pixel_format = PIXEL_FORMAT_MONO10; +#endif caps->screen_info = SCREEN_INFO_MONO_MSB_FIRST | SCREEN_INFO_EPD; } @@ -254,7 +262,7 @@ static int il0323_set_orientation(const struct device *dev, } static int il0323_set_pixel_format(const struct device *dev, const enum display_pixel_format pf) { - if (pf == PIXEL_FORMAT_MONO10) { + if ((pf == PIXEL_FORMAT_MONO10) || (pf == PIXEL_FORMAT_MONO10)) { return 0; } diff --git a/app/src/behaviors/behavior_caps_word.c b/app/src/behaviors/behavior_caps_word.c index 4c9fd711..4f058a08 100644 --- a/app/src/behaviors/behavior_caps_word.c +++ b/app/src/behaviors/behavior_caps_word.c @@ -32,6 +32,10 @@ struct caps_word_continue_item { struct behavior_caps_word_config { zmk_mod_flags_t mods; + int8_t layers; + bool ignore_alphas; + bool ignore_numbers; + bool ignore_modifiers; uint8_t index; uint8_t continuations_count; struct caps_word_continue_item continuations[]; @@ -44,12 +48,22 @@ struct behavior_caps_word_data { static void activate_caps_word(const struct device *dev) { struct behavior_caps_word_data *data = dev->data; + const struct behavior_caps_word_config *config = dev->config; + + if (config->layers > -1) { + zmk_keymap_layer_activate(config->layers, false); + } data->active = true; } static void deactivate_caps_word(const struct device *dev) { struct behavior_caps_word_data *data = dev->data; + const struct behavior_caps_word_config *config = dev->config; + + if (config->layers > -1) { + zmk_keymap_layer_deactivate(config->layers); + } data->active = false; } @@ -120,8 +134,10 @@ static void caps_word_enhance_usage(const struct behavior_caps_word_config *conf return; } - LOG_DBG("Enhancing usage 0x%02X with modifiers: 0x%02X", ev->keycode, config->mods); - ev->implicit_modifiers |= config->mods; + if (config->mods != 0) { + LOG_DBG("Enhancing usage 0x%02X with modifiers: 0x%02X", ev->keycode, config->mods); + ev->implicit_modifiers |= config->mods; + } } static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) { @@ -145,8 +161,9 @@ static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) { caps_word_enhance_usage(config, ev); - if (!caps_word_is_alpha(ev->keycode) && !caps_word_is_numeric(ev->keycode) && - !is_mod(ev->usage_page, ev->keycode) && + if ((!caps_word_is_alpha(ev->keycode) || !config->ignore_alphas) && + (!caps_word_is_numeric(ev->keycode) || !config->ignore_numbers) && + (!is_mod(ev->usage_page, ev->keycode) || !config->ignore_modifiers) && !caps_word_is_caps_includelist(config, ev->usage_page, ev->keycode, ev->implicit_modifiers)) { LOG_DBG("Deactivating caps_word for 0x%02X - 0x%02X", ev->usage_page, ev->keycode); @@ -177,7 +194,11 @@ static int behavior_caps_word_init(const struct device *dev) { static struct behavior_caps_word_data behavior_caps_word_data_##n = {.active = false}; \ static struct behavior_caps_word_config behavior_caps_word_config_##n = { \ .index = n, \ - .mods = DT_INST_PROP_OR(n, mods, MOD_LSFT), \ + .mods = DT_INST_PROP_OR(n, mods, 0), \ + .layers = DT_INST_PROP_OR(n, layers, -1), \ + .ignore_alphas = DT_INST_PROP(n, ignore_alphas), \ + .ignore_numbers = DT_INST_PROP(n, ignore_numbers), \ + .ignore_modifiers = DT_INST_PROP(n, ignore_modifiers), \ .continuations = {LISTIFY(DT_INST_PROP_LEN(n, continue_list), BREAK_ITEM, (, ), n)}, \ .continuations_count = DT_INST_PROP_LEN(n, continue_list), \ }; \ diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c index d4aa0dce..182d56b9 100644 --- a/app/src/behaviors/behavior_hold_tap.c +++ b/app/src/behaviors/behavior_hold_tap.c @@ -78,6 +78,7 @@ struct active_hold_tap { // initialized to -1, which is to be interpreted as "no other key has been pressed yet" int32_t position_of_first_other_key_pressed; + int32_t position_of_first_other_key_released; }; // The undecided hold tap is the hold tap that needs to be decided before @@ -229,6 +230,7 @@ static struct active_hold_tap *store_hold_tap(uint32_t position, uint32_t param_ active_hold_taps[i].param_tap = param_tap; active_hold_taps[i].timestamp = timestamp; active_hold_taps[i].position_of_first_other_key_pressed = -1; + active_hold_taps[i].position_of_first_other_key_released = -1; return &active_hold_taps[i]; } return NULL; @@ -414,27 +416,73 @@ static bool is_first_other_key_pressed_trigger_key(struct active_hold_tap *hold_ return false; } -// Force a tap decision if the positional conditions for a hold decision are not met. +static bool is_first_other_key_released_trigger_key(struct active_hold_tap *hold_tap) { + for (int i = 0; i < hold_tap->config->hold_trigger_key_positions_len; i++) { + if (hold_tap->config->hold_trigger_key_positions[i] == + hold_tap->position_of_first_other_key_released) { + return true; + } + } + return false; +} + + static void decide_positional_hold(struct active_hold_tap *hold_tap) { - // Only force a tap decision if the positional hold/tap feature is enabled. + + // Positional conditions is not active? if (!(hold_tap->config->hold_trigger_key_positions_len > 0)) { - return; + return; // apply flavour } - - // Only force a tap decision if another key was pressed after - // the hold/tap key. + + // Pressed key is not set? if (hold_tap->position_of_first_other_key_pressed == -1) { - return; + return; // apply flavor } - - // Only force a tap decision if the first other key to be pressed - // (after the hold/tap key) is not one of the trigger keys. + + // Pressed key is included in positions? if (is_first_other_key_pressed_trigger_key(hold_tap)) { - return; + return; // apply flavor } - // Since the positional key conditions have failed, force a TAP decision. + // Pressed key is not included in positions. + // We act on press? + if (undecided_hold_tap->config->hold_trigger_on_release == false) { + hold_tap->status = STATUS_TAP; + return; // ignore flavor, set TAP + } + + // We act on release. + // Released key is not set? + if (hold_tap->position_of_first_other_key_released == -1) + { + // Is current decision hold based on key pressed? + if (hold_tap->status == STATUS_HOLD_INTERRUPT) { + + // We can't decide yet if key which will be released: + // - not in positions + // - be released before timer + // So we can't decide yet if we should overwrite decision to TAP. + // We have to wait for key release. + + hold_tap->status = STATUS_UNDECIDED; + return; // remove flavor + } + + // There decision is decision: + // - STATUS_HOLD_TIMER - tapping term reached, apply flavor + // - STATUS_TAP - even if we set TAP later it will not change decision + return; // apply flavor + } + + + // Released key is included in positions? + if (is_first_other_key_released_trigger_key(hold_tap)) { + return; // apply flavor + } + + // Released key is not included in positions. hold_tap->status = STATUS_TAP; + return; // ignore flavor, set TAP } static void decide_hold_tap(struct active_hold_tap *hold_tap, @@ -470,6 +518,9 @@ static void decide_hold_tap(struct active_hold_tap *hold_tap, decide_positional_hold(hold_tap); + if (hold_tap->status == STATUS_UNDECIDED) { + return; + } // Since the hold-tap has been decided, clean up undecided_hold_tap and // execute the decided behavior. LOG_DBG("%d decided %s (%s decision moment %s)", hold_tap->position, @@ -591,15 +642,16 @@ static int position_state_changed_listener(const zmk_event_t *eh) { } // Store the position of pressed key for positional hold-tap purposes. - if ((undecided_hold_tap->config->hold_trigger_on_release != - ev->state) // key has been pressed and hold_trigger_on_release is not set, or key - // has been released and hold_trigger_on_release is set - && (undecided_hold_tap->position_of_first_other_key_pressed == - -1) // no other key has been pressed yet - ) { + if (ev->state && (undecided_hold_tap->position_of_first_other_key_pressed == -1)) { undecided_hold_tap->position_of_first_other_key_pressed = ev->position; } + // Store the position of pressed key for positional hold-tap purposes. + if (!ev->state && undecided_hold_tap->config->hold_trigger_on_release && + (undecided_hold_tap->position_of_first_other_key_released == -1)) { + undecided_hold_tap->position_of_first_other_key_released = ev->position; + } + if (undecided_hold_tap->position == ev->position) { if (ev->state) { // keydown LOG_ERR("hold-tap listener should be called before before most other listeners!"); diff --git a/app/src/behaviors/behavior_momentary_layer.c b/app/src/behaviors/behavior_momentary_layer.c index c2bd0ffc..21a9d8d8 100644 --- a/app/src/behaviors/behavior_momentary_layer.c +++ b/app/src/behaviors/behavior_momentary_layer.c @@ -23,7 +23,7 @@ static int behavior_mo_init(const struct device *dev) { return 0; }; static int mo_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { LOG_DBG("position %d layer %d", event.position, binding->param1); - return zmk_keymap_layer_activate(binding->param1); + return zmk_keymap_layer_activate(binding->param1, true); } static int mo_keymap_binding_released(struct zmk_behavior_binding *binding, diff --git a/app/src/behaviors/behavior_mouse_key_press.c b/app/src/behaviors/behavior_mouse_key_press.c new file mode 100644 index 00000000..5c51099b --- /dev/null +++ b/app/src/behaviors/behavior_mouse_key_press.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_mouse_key_press + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_mouse_key_press_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + return ZMK_EVENT_RAISE( + zmk_mouse_button_state_changed_from_encoded(binding->param1, true, event.timestamp)); +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + return ZMK_EVENT_RAISE( + zmk_mouse_button_state_changed_from_encoded(binding->param1, false, event.timestamp)); +} + +static const struct behavior_driver_api behavior_mouse_key_press_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define KP_INST(n) \ + DEVICE_DT_INST_DEFINE(n, behavior_mouse_key_press_init, NULL, NULL, NULL, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_mouse_key_press_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ \ No newline at end of file diff --git a/app/src/behaviors/behavior_mouse_move.c b/app/src/behaviors/behavior_mouse_move.c new file mode 100644 index 00000000..0d591f00 --- /dev/null +++ b/app/src/behaviors/behavior_mouse_move.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_mouse_move + +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_mouse_move_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE( + zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, true, event.timestamp)); +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE(zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, + false, event.timestamp)); +} + +static const struct behavior_driver_api behavior_mouse_move_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define KP_INST(n) \ + static struct mouse_config behavior_mouse_move_config_##n = { \ + .delay_ms = DT_INST_PROP(n, delay_ms), \ + .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ + .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ + }; \ + DEVICE_DT_INST_DEFINE(n, behavior_mouse_move_init, NULL, NULL, \ + &behavior_mouse_move_config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_move_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_mouse_scroll.c b/app/src/behaviors/behavior_mouse_scroll.c new file mode 100644 index 00000000..d7167172 --- /dev/null +++ b/app/src/behaviors/behavior_mouse_scroll.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_mouse_scroll + +#include +#include +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_mouse_scroll_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, + true, event.timestamp)); +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, + false, event.timestamp)); +} + +static const struct behavior_driver_api behavior_mouse_scroll_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define KP_INST(n) \ + static struct mouse_config behavior_mouse_scroll_config_##n = { \ + .delay_ms = DT_INST_PROP(n, delay_ms), \ + .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ + .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ + }; \ + DEVICE_DT_INST_DEFINE(n, behavior_mouse_scroll_init, NULL, NULL, \ + &behavior_mouse_scroll_config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_scroll_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_tri_state.c b/app/src/behaviors/behavior_tri_state.c new file mode 100644 index 00000000..463694e4 --- /dev/null +++ b/app/src/behaviors/behavior_tri_state.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_tri_state + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define ZMK_BHV_MAX_ACTIVE_TRI_STATES 10 + +struct behavior_tri_state_config { + int32_t ignored_key_positions_len; + int32_t ignored_layers_len; + struct zmk_behavior_binding start_behavior; + struct zmk_behavior_binding continue_behavior; + struct zmk_behavior_binding end_behavior; + int32_t ignored_layers; + int32_t timeout_ms; + int tap_ms; + uint8_t ignored_key_positions[]; +}; + +struct active_tri_state { + bool is_active; + bool is_pressed; + bool first_press; + uint32_t position; + const struct behavior_tri_state_config *config; + struct k_work_delayable release_timer; + int64_t release_at; + bool timer_started; + bool timer_cancelled; +}; + +static int stop_timer(struct active_tri_state *tri_state) { + int timer_cancel_result = k_work_cancel_delayable(&tri_state->release_timer); + if (timer_cancel_result == -EINPROGRESS) { + // too late to cancel, we'll let the timer handler clear up. + tri_state->timer_cancelled = true; + } + return timer_cancel_result; +} + +static void reset_timer(int32_t timestamp, struct active_tri_state *tri_state) { + tri_state->release_at = timestamp + tri_state->config->timeout_ms; + int32_t ms_left = tri_state->release_at - k_uptime_get(); + if (ms_left > 0) { + k_work_schedule(&tri_state->release_timer, K_MSEC(ms_left)); + LOG_DBG("Successfully reset tri-state timer"); + } +} + +void trigger_end_behavior(struct active_tri_state *si) { + zmk_behavior_queue_add(si->position, si->config->end_behavior, true, si->config->tap_ms); + zmk_behavior_queue_add(si->position, si->config->end_behavior, false, 0); +} + +void behavior_tri_state_timer_handler(struct k_work *item) { + struct active_tri_state *tri_state = CONTAINER_OF(item, struct active_tri_state, release_timer); + if (!tri_state->is_active || tri_state->timer_cancelled || tri_state->is_pressed) { + return; + } + LOG_DBG("Tri-state deactivated due to timer"); + tri_state->is_active = false; + trigger_end_behavior(tri_state); +} + +static void clear_tri_state(struct active_tri_state *tri_state) { tri_state->is_active = false; } + +struct active_tri_state active_tri_states[ZMK_BHV_MAX_ACTIVE_TRI_STATES] = {}; + +static struct active_tri_state *find_tri_state(uint32_t position) { + for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) { + if (active_tri_states[i].position == position && active_tri_states[i].is_active) { + return &active_tri_states[i]; + } + } + return NULL; +} + +static int new_tri_state(uint32_t position, const struct behavior_tri_state_config *config, + struct active_tri_state **tri_state) { + for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) { + struct active_tri_state *const ref_tri_state = &active_tri_states[i]; + if (!ref_tri_state->is_active) { + ref_tri_state->position = position; + ref_tri_state->config = config; + ref_tri_state->is_active = true; + ref_tri_state->is_pressed = false; + ref_tri_state->first_press = true; + *tri_state = ref_tri_state; + return 0; + } + } + return -ENOMEM; +} + +static bool is_other_key_ignored(struct active_tri_state *tri_state, int32_t position) { + for (int i = 0; i < tri_state->config->ignored_key_positions_len; i++) { + if (tri_state->config->ignored_key_positions[i] == position) { + return true; + } + } + return false; +} + +static bool is_layer_ignored(struct active_tri_state *tri_state, int32_t layer) { + if ((BIT(layer) & tri_state->config->ignored_layers) != 0U) { + return true; + } + return false; +} + +static int on_tri_state_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_tri_state_config *cfg = dev->config; + struct active_tri_state *tri_state; + tri_state = find_tri_state(event.position); + if (tri_state == NULL) { + if (new_tri_state(event.position, cfg, &tri_state) == -ENOMEM) { + LOG_ERR("Unable to create new tri_state. Insufficient space in " + "active_tri_states[]."); + return ZMK_BEHAVIOR_OPAQUE; + } + LOG_DBG("%d created new tri_state", event.position); + } + LOG_DBG("%d tri_state pressed", event.position); + tri_state->is_pressed = true; + if (tri_state->first_press) { + behavior_keymap_binding_pressed((struct zmk_behavior_binding *)&cfg->start_behavior, event); + behavior_keymap_binding_released((struct zmk_behavior_binding *)&cfg->start_behavior, + event); + tri_state->first_press = false; + } + behavior_keymap_binding_pressed((struct zmk_behavior_binding *)&cfg->continue_behavior, event); + return ZMK_BEHAVIOR_OPAQUE; +} + +static void release_tri_state(struct zmk_behavior_binding_event event, + struct zmk_behavior_binding *continue_behavior) { + struct active_tri_state *tri_state = find_tri_state(event.position); + if (tri_state == NULL) { + return; + } + tri_state->is_pressed = false; + behavior_keymap_binding_released(continue_behavior, event); + reset_timer(k_uptime_get(), tri_state); +} + +static int on_tri_state_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_tri_state_config *cfg = dev->config; + LOG_DBG("%d tri_state keybind released", event.position); + release_tri_state(event, (struct zmk_behavior_binding *)&cfg->continue_behavior); + return ZMK_BEHAVIOR_OPAQUE; +} + +static int behavior_tri_state_init(const struct device *dev) { + static bool init_first_run = true; + if (init_first_run) { + for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) { + k_work_init_delayable(&active_tri_states[i].release_timer, + behavior_tri_state_timer_handler); + clear_tri_state(&active_tri_states[i]); + } + } + init_first_run = false; + return 0; +} + +static const struct behavior_driver_api behavior_tri_state_driver_api = { + .binding_pressed = on_tri_state_binding_pressed, + .binding_released = on_tri_state_binding_released, +}; + +static int tri_state_listener(const zmk_event_t *eh); +static int tri_state_position_state_changed_listener(const zmk_event_t *eh); +static int tri_state_layer_state_changed_listener(const zmk_event_t *eh); + +ZMK_LISTENER(behavior_tri_state, tri_state_listener); +ZMK_SUBSCRIPTION(behavior_tri_state, zmk_position_state_changed); +ZMK_SUBSCRIPTION(behavior_tri_state, zmk_layer_state_changed); + +static int tri_state_listener(const zmk_event_t *eh) { + if (as_zmk_position_state_changed(eh) != NULL) { + return tri_state_position_state_changed_listener(eh); + } else if (as_zmk_layer_state_changed(eh) != NULL) { + return tri_state_layer_state_changed_listener(eh); + } + return ZMK_EV_EVENT_BUBBLE; +} + +static int tri_state_position_state_changed_listener(const zmk_event_t *eh) { + struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh); + if (ev == NULL) { + return ZMK_EV_EVENT_BUBBLE; + } + for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) { + struct active_tri_state *tri_state = &active_tri_states[i]; + if (!tri_state->is_active) { + continue; + } + if (tri_state->position == ev->position) { + continue; + } + if (!is_other_key_ignored(tri_state, ev->position)) { + LOG_DBG("Tri-State interrupted, ending at %d %d", tri_state->position, ev->position); + tri_state->is_active = false; + struct zmk_behavior_binding_event event = {.position = tri_state->position, + .timestamp = k_uptime_get()}; + if (tri_state->is_pressed) { + behavior_keymap_binding_released( + (struct zmk_behavior_binding *)&tri_state->config->continue_behavior, event); + } + trigger_end_behavior(tri_state); + return ZMK_EV_EVENT_BUBBLE; + } + if (ev->state) { + stop_timer(tri_state); + } else { + reset_timer(ev->timestamp, tri_state); + } + } + return ZMK_EV_EVENT_BUBBLE; +} + +static int tri_state_layer_state_changed_listener(const zmk_event_t *eh) { + struct zmk_layer_state_changed *ev = as_zmk_layer_state_changed(eh); + if (ev == NULL) { + return ZMK_EV_EVENT_BUBBLE; + } + if (!ev->state) { + return ZMK_EV_EVENT_BUBBLE; + } + for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) { + struct active_tri_state *tri_state = &active_tri_states[i]; + if (!tri_state->is_active) { + continue; + } + if (!is_layer_ignored(tri_state, ev->layer)) { + LOG_DBG("Tri-State layer changed, ending at %d %d", tri_state->position, ev->layer); + tri_state->is_active = false; + struct zmk_behavior_binding_event event = {.position = tri_state->position, + .timestamp = k_uptime_get()}; + if (tri_state->is_pressed) { + behavior_keymap_binding_released( + (struct zmk_behavior_binding *)&tri_state->config->continue_behavior, event); + } + behavior_keymap_binding_pressed( + (struct zmk_behavior_binding *)&tri_state->config->end_behavior, event); + behavior_keymap_binding_released( + (struct zmk_behavior_binding *)&tri_state->config->end_behavior, event); + return ZMK_EV_EVENT_BUBBLE; + } + } + return ZMK_EV_EVENT_BUBBLE; +} + +#define _TRANSFORM_ENTRY(idx, node) \ + { \ + .behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \ + .param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \ + (DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \ + .param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \ + (DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \ + } + +#define IF_BIT(n, prop, i) BIT(DT_PROP_BY_IDX(n, prop, i)) | + +#define TRI_STATE_INST(n) \ + static struct behavior_tri_state_config behavior_tri_state_config_##n = { \ + .ignored_key_positions = DT_INST_PROP(n, ignored_key_positions), \ + .ignored_key_positions_len = DT_INST_PROP_LEN(n, ignored_key_positions), \ + .ignored_layers = DT_INST_FOREACH_PROP_ELEM(n, ignored_layers, IF_BIT) 0, \ + .ignored_layers_len = DT_INST_PROP_LEN(n, ignored_layers), \ + .timeout_ms = DT_INST_PROP(n, timeout_ms), \ + .tap_ms = DT_INST_PROP(n, tap_ms), \ + .start_behavior = _TRANSFORM_ENTRY(0, n), \ + .continue_behavior = _TRANSFORM_ENTRY(1, n), \ + .end_behavior = _TRANSFORM_ENTRY(2, n)}; \ + DEVICE_DT_INST_DEFINE(n, behavior_tri_state_init, NULL, NULL, &behavior_tri_state_config_##n, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_tri_state_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(TRI_STATE_INST) diff --git a/app/src/conditional_layer.c b/app/src/conditional_layer.c index 9ba02a5c..f575205a 100644 --- a/app/src/conditional_layer.c +++ b/app/src/conditional_layer.c @@ -49,12 +49,12 @@ static const struct conditional_layer_cfg CONDITIONAL_LAYER_CFGS[] = { static const int32_t NUM_CONDITIONAL_LAYER_CFGS = sizeof(CONDITIONAL_LAYER_CFGS) / sizeof(*CONDITIONAL_LAYER_CFGS); -static void conditional_layer_activate(int8_t layer) { +static void conditional_layer_activate(int8_t layer, bool momentary) { // This may trigger another event that could, in turn, activate additional then-layers. However, // the process will eventually terminate (at worst, when every layer is active). if (!zmk_keymap_layer_active(layer)) { LOG_DBG("layer %d", layer); - zmk_keymap_layer_activate(layer); + zmk_keymap_layer_activate(layer, momentary); } } @@ -84,6 +84,7 @@ static int layer_state_changed_listener(const zmk_event_t *ev) { int8_t max_then_layer = -1; uint32_t then_layers = 0; uint32_t then_layer_state = 0; + uint32_t momentariness_state = 0; conditional_layer_updates_needed = false; @@ -100,13 +101,17 @@ static int layer_state_changed_listener(const zmk_event_t *ev) { // also trigger activation of another. if ((zmk_keymap_layer_state() & mask) == mask) { then_layer_state |= BIT(cfg->then_layer); + if (zmk_keymap_layers_any_momentary(mask)) { + momentariness_state |= BIT(cfg->then_layer); + } } } for (uint8_t layer = 0; layer <= max_then_layer; layer++) { if ((BIT(layer) & then_layers) != 0U) { if ((BIT(layer) & then_layer_state) != 0U) { - conditional_layer_activate(layer); + bool momentary = BIT(layer) & momentariness_state; + conditional_layer_activate(layer, momentary); } else { conditional_layer_deactivate(layer); } diff --git a/app/src/display/Kconfig b/app/src/display/Kconfig index a2029481..1613cfe4 100644 --- a/app/src/display/Kconfig +++ b/app/src/display/Kconfig @@ -176,6 +176,17 @@ choice ZMK_LV_FONT_DEFAULT_SMALL select LV_FONT_UNSCII_16 endchoice +config ZMK_DISPLAY_FULL_REFRESH_PERIOD + int "(Optional) Period to issue a full refresh to the display (in seconds)" + default 0 + help + Period in seconds for how often to completely refresh/redraw the whole screen. + Most useful for e-ink/EPD displays that require occasional full redraws. + +config ZMK_DISPLAY_HIDE_MOMENTARY_LAYERS + bool "Do not update layer widget for momentary layer changes" + default n + rsource "widgets/Kconfig" endif diff --git a/app/src/display/main.c b/app/src/display/main.c index e15e2de0..7666784b 100644 --- a/app/src/display/main.c +++ b/app/src/display/main.c @@ -50,6 +50,17 @@ struct k_work_q *zmk_display_work_q() { #endif } +#if CONFIG_ZMK_DISPLAY_FULL_REFRESH_PERIOD > 0 +void full_refresh_work_cb(struct k_work *work) { lv_obj_invalidate(lv_scr_act()); } + +K_WORK_DEFINE(full_refresh_work, full_refresh_work_cb); + +void full_refresh_timer_cb() { k_work_submit_to_queue(zmk_display_work_q(), &full_refresh_work); } + +K_TIMER_DEFINE(full_refresh_timer, full_refresh_timer_cb, NULL); + +#endif + void display_timer_cb() { k_work_submit_to_queue(zmk_display_work_q(), &display_tick_work); } K_TIMER_DEFINE(display_timer, display_timer_cb, NULL); @@ -57,6 +68,10 @@ K_TIMER_DEFINE(display_timer, display_timer_cb, NULL); void unblank_display_cb(struct k_work *work) { display_blanking_off(display); k_timer_start(&display_timer, K_MSEC(TICK_MS), K_MSEC(TICK_MS)); +#if CONFIG_ZMK_DISPLAY_FULL_REFRESH_PERIOD > 0 + k_timer_start(&full_refresh_timer, K_SECONDS(CONFIG_ZMK_DISPLAY_FULL_REFRESH_PERIOD), + K_SECONDS(CONFIG_ZMK_DISPLAY_FULL_REFRESH_PERIOD)); +#endif } #if IS_ENABLED(CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE) @@ -64,6 +79,9 @@ void unblank_display_cb(struct k_work *work) { void blank_display_cb(struct k_work *work) { k_timer_stop(&display_timer); display_blanking_on(display); +#if CONFIG_ZMK_DISPLAY_FULL_REFRESH_PERIOD > 0 + k_timer_stop(&full_refresh_timer); +#endif } K_WORK_DEFINE(blank_display_work, blank_display_cb); K_WORK_DEFINE(unblank_display_work, unblank_display_cb); diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 138e790f..4336f30f 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -116,9 +116,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) { struct zmk_hid_keyboard_report *keyboard_report = zmk_hid_get_keyboard_report(); @@ -193,6 +191,38 @@ int zmk_endpoints_send_report(uint16_t usage_page) { return -ENOTSUP; } +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +int zmk_endpoints_send_mouse_report() { + struct zmk_hid_mouse_report *mouse_report = zmk_hid_get_mouse_report(); + + switch (current_instance.transport) { +#if IS_ENABLED(CONFIG_ZMK_USB) + case ZMK_TRANSPORT_USB: { + int err = zmk_usb_hid_send_report((uint8_t *)mouse_report, sizeof(*mouse_report)); + if (err) { + LOG_ERR("FAILED TO SEND OVER USB: %d", err); + } + return err; + } +#endif /* IS_ENABLED(CONFIG_ZMK_USB) */ + +#if IS_ENABLED(CONFIG_ZMK_BLE) + case ZMK_TRANSPORT_BLE: { + int err = zmk_hog_send_mouse_report(&mouse_report->body); + if (err) { + LOG_ERR("FAILED TO SEND OVER HOG: %d", err); + } + return err; + } +#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */ + + default: + LOG_ERR("Unsupported endpoint %d", current_instance.transport); + return -ENOTSUP; + } +} +#endif /* IS_ENABLED(CONFIG_ZMK_MOUSE) */ + #if IS_ENABLED(CONFIG_SETTINGS) static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb, @@ -297,6 +327,9 @@ static int zmk_endpoints_init(const struct device *_arg) { static void disconnect_current_endpoint() { zmk_hid_keyboard_clear(); zmk_hid_consumer_clear(); +#if IS_ENABLED(CONFIG_ZMK_MOUSE) + zmk_hid_mouse_clear(); +#endif zmk_endpoints_send_report(HID_USAGE_KEY); zmk_endpoints_send_report(HID_USAGE_CONSUMER); diff --git a/app/src/events/mouse_button_state_changed.c b/app/src/events/mouse_button_state_changed.c new file mode 100644 index 00000000..28ab0508 --- /dev/null +++ b/app/src/events/mouse_button_state_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_button_state_changed); diff --git a/app/src/events/mouse_move_state_changed.c b/app/src/events/mouse_move_state_changed.c new file mode 100644 index 00000000..07a789b2 --- /dev/null +++ b/app/src/events/mouse_move_state_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_move_state_changed); diff --git a/app/src/events/mouse_scroll_state_changed.c b/app/src/events/mouse_scroll_state_changed.c new file mode 100644 index 00000000..f6216d39 --- /dev/null +++ b/app/src/events/mouse_scroll_state_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_scroll_state_changed); diff --git a/app/src/events/mouse_tick.c b/app/src/events/mouse_tick.c new file mode 100644 index 00000000..846b3fae --- /dev/null +++ b/app/src/events/mouse_tick.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_tick); diff --git a/app/src/hid.c b/app/src/hid.c index 2a6b5d39..3232a704 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -16,6 +16,11 @@ static struct zmk_hid_keyboard_report keyboard_report = { static struct zmk_hid_consumer_report consumer_report = {.report_id = 2, .body = {.keys = {0}}}; +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +static struct zmk_hid_mouse_report mouse_report = { + .report_id = 4, .body = {.buttons = 0, .x = 0, .y = 0, .scroll_x = 0, .scroll_y = 0}}; +#endif + // Keep track of how often a modifier was pressed. // Only release the modifier if the count is 0. static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0}; @@ -264,6 +269,87 @@ 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[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static zmk_mod_flags_t explicit_buttons = 0; + +#define SET_MOUSE_BUTTONS(btns) \ + { \ + mouse_report.body.buttons = btns; \ + LOG_DBG("Mouse buttons set to 0x%02X", mouse_report.body.buttons); \ + } + +int zmk_hid_mouse_button_press(zmk_mouse_button_t button) { + explicit_button_counts[button]++; + LOG_DBG("Button %d count %d", button, explicit_button_counts[button]); + WRITE_BIT(explicit_buttons, button, true); + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_button_release(zmk_mouse_button_t button) { + if (explicit_button_counts[button] <= 0) { + LOG_ERR("Tried to release button %d too often", button); + return -EINVAL; + } + explicit_button_counts[button]--; + LOG_DBG("Button %d count: %d", button, explicit_button_counts[button]); + if (explicit_button_counts[button] == 0) { + LOG_DBG("Button %d released", button); + WRITE_BIT(explicit_buttons, button, false); + } + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons) { + for (zmk_mod_t i = 0; i < 16; i++) { + if (buttons & (1 << i)) { + zmk_hid_mouse_button_press(i); + } + } + return 0; +} + +int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { + for (zmk_mod_t i = 0; i < 16; i++) { + if (buttons & (1 << i)) { + zmk_hid_mouse_button_release(i); + } + } + return 0; +} + +void zmk_hid_mouse_movement_set(int16_t x, int16_t y) { + mouse_report.body.x = x; + mouse_report.body.y = y; + LOG_DBG("Mouse movement set to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y); +} + +void zmk_hid_mouse_movement_update(int16_t x, int16_t y) { + mouse_report.body.x += x; + mouse_report.body.y += y; + LOG_DBG("Mouse movement updated to 0x%02X 0x%02X ", mouse_report.body.x, mouse_report.body.y); +} + +void zmk_hid_mouse_scroll_set(int8_t x, int8_t y) { + mouse_report.body.scroll_x = x; + mouse_report.body.scroll_y = y; + LOG_DBG("Mouse scroll set to 0x%02X 0x%02X ", mouse_report.body.scroll_x, + mouse_report.body.scroll_y); +} + +void zmk_hid_mouse_scroll_update(int8_t x, int8_t y) { + mouse_report.body.scroll_x += x; + mouse_report.body.scroll_y += y; + LOG_DBG("Mouse scroll updated to 0x%02X 0x%02X ", mouse_report.body.scroll_x, + mouse_report.body.scroll_y); +} +void zmk_hid_mouse_clear() { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } +#endif /* IS_ENABLED(CONFIG_ZMK_MOUSE) */ + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { return &keyboard_report; } @@ -271,3 +357,9 @@ struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { struct zmk_hid_consumer_report *zmk_hid_get_consumer_report() { return &consumer_report; } + +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +struct zmk_hid_mouse_report *zmk_hid_get_mouse_report() { + return &mouse_report; +} +#endif diff --git a/app/src/hog.c b/app/src/hog.c index 930714b0..dc5cd210 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -56,6 +56,13 @@ static struct hids_report consumer_input = { .type = HIDS_INPUT, }; +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +static struct hids_report mouse_input = { + .id = 0x04, + .type = HIDS_INPUT, +}; +#endif + static bool host_requests_notification = false; static uint8_t ctrl_point; // static uint8_t proto_mode; @@ -93,6 +100,15 @@ 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 + // static ssize_t write_proto_mode(struct bt_conn *conn, // const struct bt_gatt_attr *attr, // const void *buf, uint16_t len, uint16_t offset, @@ -139,6 +155,15 @@ 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 + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); @@ -261,6 +286,78 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { return 0; }; +#if IS_ENABLED(CONFIG_ZMK_MOUSE) +K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body), + CONFIG_ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE, 4); + +void send_mouse_report_callback(struct k_work *work) { + struct zmk_hid_mouse_report_body report; + while (k_msgq_get(&zmk_hog_mouse_msgq, &report, K_NO_WAIT) == 0) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &hog_svc.attrs[13], + .data = &report, + .len = sizeof(report), + }; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err) { + LOG_DBG("Error notifying %d", err); + } + + bt_conn_unref(conn); + } +}; + +K_WORK_DEFINE(hog_mouse_work, send_mouse_report_callback); + +int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { + int err = k_msgq_put(&zmk_hog_mouse_msgq, report, K_NO_WAIT); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Mouse message queue full, dropping report"); + return err; + } + default: + LOG_WRN("Failed to queue mouse report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&hog_work_q, &hog_mouse_work); + + return 0; +}; + +int zmk_hog_send_mouse_report_direct(struct zmk_hid_mouse_report_body *report) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return 1; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &hog_svc.attrs[13], + .data = report, + .len = sizeof(*report), + }; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err) { + LOG_DBG("Error notifying %d", err); + return err; + } + + bt_conn_unref(conn); + + return 0; +}; +#endif /* IS_ENABLED(CONFIG_ZMK_MOUSE) */ + int zmk_hog_init(const struct device *_arg) { static const struct k_work_queue_config queue_config = {.name = "HID Over GATT Send Work"}; k_work_queue_start(&hog_work_q, hog_q_stack, K_THREAD_STACK_SIZEOF(hog_q_stack), diff --git a/app/src/keymap.c b/app/src/keymap.c index bda69427..18dce60e 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -27,6 +27,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include static zmk_keymap_layers_state_t _zmk_keymap_layer_state = 0; +static zmk_keymap_layers_state_t _zmk_keymap_layer_momentary = 0; static uint8_t _zmk_keymap_layer_default = 0; #define DT_DRV_COMPAT zmk_keymap @@ -78,7 +79,7 @@ static struct zmk_behavior_binding zmk_sensor_keymap[ZMK_KEYMAP_LAYERS_LEN] #endif /* ZMK_KEYMAP_HAS_SENSORS */ -static inline int set_layer_state(uint8_t layer, bool state) { +static inline int set_layer_state(uint8_t layer, bool state, bool momentary) { if (layer >= ZMK_KEYMAP_LAYERS_LEN) { return -EINVAL; } @@ -93,6 +94,7 @@ static inline int set_layer_state(uint8_t layer, bool state) { // Don't send state changes unless there was an actual change if (old_state != _zmk_keymap_layer_state) { LOG_DBG("layer_changed: layer %d state %d", layer, state); + WRITE_BIT(_zmk_keymap_layer_momentary, layer, momentary); ZMK_EVENT_RAISE(create_layer_state_changed(layer, state)); } @@ -113,6 +115,15 @@ bool zmk_keymap_layer_active(uint8_t layer) { return zmk_keymap_layer_active_with_state(layer, _zmk_keymap_layer_state); }; +bool zmk_keymap_layer_momentary(uint8_t layer) { + return layer != _zmk_keymap_layer_default && + (_zmk_keymap_layer_momentary & (BIT(layer))) == (BIT(layer)); +}; + +bool zmk_keymap_layers_any_momentary(zmk_keymap_layers_state_t layers_mask) { + return (_zmk_keymap_layer_momentary & layers_mask) > 0; +}; + uint8_t zmk_keymap_highest_layer_active() { for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) { if (zmk_keymap_layer_active(layer)) { @@ -122,16 +133,18 @@ uint8_t zmk_keymap_highest_layer_active() { return zmk_keymap_layer_default(); } -int zmk_keymap_layer_activate(uint8_t layer) { return set_layer_state(layer, true); }; +int zmk_keymap_layer_activate(uint8_t layer, bool momentary) { + return set_layer_state(layer, true, momentary); +}; -int zmk_keymap_layer_deactivate(uint8_t layer) { return set_layer_state(layer, false); }; +int zmk_keymap_layer_deactivate(uint8_t layer) { return set_layer_state(layer, false, false); }; int zmk_keymap_layer_toggle(uint8_t layer) { if (zmk_keymap_layer_active(layer)) { return zmk_keymap_layer_deactivate(layer); } - return zmk_keymap_layer_activate(layer); + return zmk_keymap_layer_activate(layer, false); }; int zmk_keymap_layer_to(uint8_t layer) { @@ -139,7 +152,7 @@ int zmk_keymap_layer_to(uint8_t layer) { zmk_keymap_layer_deactivate(i); } - zmk_keymap_layer_activate(layer); + zmk_keymap_layer_activate(layer, false); return 0; } diff --git a/app/src/main.c b/app/src/main.c index 3fd6b116..b62cd596 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -17,6 +17,10 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include +#ifdef CONFIG_ZMK_MOUSE +#include +#endif /* CONFIG_ZMK_MOUSE */ + void main(void) { LOG_INF("Welcome to ZMK!\n"); @@ -27,4 +31,8 @@ void main(void) { #ifdef CONFIG_ZMK_DISPLAY zmk_display_init(); #endif /* CONFIG_ZMK_DISPLAY */ + +#ifdef CONFIG_ZMK_MOUSE + zmk_mouse_init(); +#endif /* CONFIG_ZMK_MOUSE */ } diff --git a/app/src/mouse/Kconfig b/app/src/mouse/Kconfig new file mode 100644 index 00000000..1161b86b --- /dev/null +++ b/app/src/mouse/Kconfig @@ -0,0 +1,38 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig ZMK_MOUSE + bool "Enable ZMK mouse emulation" + default n + +config ZMK_MOUSE_TICK_DURATION + int "Mouse tick duration in ms" + default 8 + +if ZMK_MOUSE + +choice ZMK_MOUSE_WORK_QUEUE + prompt "Work queue selection for mouse events" + default ZMK_MOUSE_WORK_QUEUE_DEDICATED + +config ZMK_MOUSE_WORK_QUEUE_SYSTEM + bool "Use default system work queue for mouse events" + +config ZMK_MOUSE_WORK_QUEUE_DEDICATED + bool "Use dedicated work queue for mouse events" + +endchoice + +if ZMK_MOUSE_WORK_QUEUE_DEDICATED + +config ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE + int "Stack size for dedicated mouse thread/queue" + default 2048 + +config ZMK_MOUSE_DEDICATED_THREAD_PRIORITY + int "Thread priority for dedicated mouse thread/queue" + default 3 + +endif # ZMK_MOUSE_WORK_QUEUE_DEDICATED + +endif diff --git a/app/src/mouse/key_listener.c b/app/src/mouse/key_listener.c new file mode 100644 index 00000000..91c4c5ce --- /dev/null +++ b/app/src/mouse/key_listener.c @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) +static struct vector2d move_speed = {0}; +static struct vector2d scroll_speed = {0}; +static struct mouse_config move_config = (struct mouse_config){0}; +static struct mouse_config scroll_config = (struct mouse_config){0}; +static int64_t start_time = 0; + +bool equals(const struct mouse_config *one, const struct mouse_config *other) { + return one->delay_ms == other->delay_ms && + one->time_to_max_speed_ms == other->time_to_max_speed_ms && + one->acceleration_exponent == other->acceleration_exponent; +} + +static void clear_mouse_state(struct k_work *work) { + move_speed = (struct vector2d){0}; + scroll_speed = (struct vector2d){0}; + start_time = 0; + zmk_hid_mouse_movement_set(0, 0); + zmk_hid_mouse_scroll_set(0, 0); + LOG_DBG("Clearing state"); +} + +K_WORK_DEFINE(mouse_clear, &clear_mouse_state); + +void mouse_clear_cb(struct k_timer *dummy) { + k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_clear); +} + +static void mouse_tick_timer_handler(struct k_work *work) { + zmk_hid_mouse_movement_set(0, 0); + zmk_hid_mouse_scroll_set(0, 0); + LOG_DBG("Raising mouse tick event"); + ZMK_EVENT_RAISE( + zmk_mouse_tick(move_speed, scroll_speed, move_config, scroll_config, &start_time)); + zmk_endpoints_send_mouse_report(); +} + +K_WORK_DEFINE(mouse_tick, &mouse_tick_timer_handler); + +void mouse_timer_cb(struct k_timer *dummy) { + LOG_DBG("Submitting mouse work to queue"); + k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_tick); +} + +K_TIMER_DEFINE(mouse_timer, mouse_timer_cb, mouse_clear_cb); + +static int mouse_timer_ref_count = 0; + +void mouse_timer_ref() { + if (mouse_timer_ref_count == 0) { + start_time = k_uptime_get(); + k_timer_start(&mouse_timer, K_NO_WAIT, K_MSEC(CONFIG_ZMK_MOUSE_TICK_DURATION)); + } + mouse_timer_ref_count += 1; +} + +void mouse_timer_unref() { + if (mouse_timer_ref_count > 0) { + mouse_timer_ref_count--; + } + if (mouse_timer_ref_count == 0) { + k_timer_stop(&mouse_timer); + } +} + +static void listener_mouse_move_pressed(const struct zmk_mouse_move_state_changed *ev) { + move_speed.x += ev->max_speed.x; + move_speed.y += ev->max_speed.y; + mouse_timer_ref(); +} + +static void listener_mouse_move_released(const struct zmk_mouse_move_state_changed *ev) { + move_speed.x -= ev->max_speed.x; + move_speed.y -= ev->max_speed.y; + mouse_timer_unref(); +} + +static void listener_mouse_scroll_pressed(const struct zmk_mouse_scroll_state_changed *ev) { + scroll_speed.x += ev->max_speed.x; + scroll_speed.y += ev->max_speed.y; + mouse_timer_ref(); +} + +static void listener_mouse_scroll_released(const struct zmk_mouse_scroll_state_changed *ev) { + scroll_speed.x -= ev->max_speed.x; + scroll_speed.y -= ev->max_speed.y; + mouse_timer_unref(); +} + +static void listener_mouse_button_pressed(const struct zmk_mouse_button_state_changed *ev) { + LOG_DBG("buttons: 0x%02X", ev->buttons); + zmk_hid_mouse_buttons_press(ev->buttons); + zmk_endpoints_send_mouse_report(); +} + +static void listener_mouse_button_released(const struct zmk_mouse_button_state_changed *ev) { + LOG_DBG("buttons: 0x%02X", ev->buttons); + zmk_hid_mouse_buttons_release(ev->buttons); + zmk_endpoints_send_mouse_report(); +} + +int mouse_listener(const zmk_event_t *eh) { + const struct zmk_mouse_move_state_changed *mmv_ev = as_zmk_mouse_move_state_changed(eh); + if (mmv_ev) { + if (!equals(&move_config, &(mmv_ev->config))) + move_config = mmv_ev->config; + + if (mmv_ev->state) { + listener_mouse_move_pressed(mmv_ev); + } else { + listener_mouse_move_released(mmv_ev); + } + return 0; + } + const struct zmk_mouse_scroll_state_changed *msc_ev = as_zmk_mouse_scroll_state_changed(eh); + if (msc_ev) { + if (!equals(&scroll_config, &(msc_ev->config))) + scroll_config = msc_ev->config; + if (msc_ev->state) { + listener_mouse_scroll_pressed(msc_ev); + } else { + listener_mouse_scroll_released(msc_ev); + } + return 0; + } + const struct zmk_mouse_button_state_changed *mbt_ev = as_zmk_mouse_button_state_changed(eh); + if (mbt_ev) { + if (mbt_ev->state) { + listener_mouse_button_pressed(mbt_ev); + } else { + listener_mouse_button_released(mbt_ev); + } + return 0; + } + return 0; +} + +ZMK_LISTENER(mouse_listener, mouse_listener); +ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_button_state_changed); +ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_move_state_changed); +ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_scroll_state_changed); + +#endif /*!defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */ diff --git a/app/src/mouse/main.c b/app/src/mouse/main.c new file mode 100644 index 00000000..e5b5252f --- /dev/null +++ b/app/src/mouse/main.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) +K_THREAD_STACK_DEFINE(mouse_work_stack_area, CONFIG_ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE); +static struct k_work_q mouse_work_q; +#endif + +struct k_work_q *zmk_mouse_work_q() { +#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) + return &mouse_work_q; +#else + return &k_sys_work_q; +#endif +} + +int zmk_mouse_init() { +#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) + k_work_queue_start(&mouse_work_q, mouse_work_stack_area, + K_THREAD_STACK_SIZEOF(mouse_work_stack_area), + CONFIG_ZMK_MOUSE_DEDICATED_THREAD_PRIORITY, NULL); +#endif + return 0; +} \ No newline at end of file diff --git a/app/src/mouse/tick_listener.c b/app/src/mouse/tick_listener.c new file mode 100644 index 00000000..f84b29f7 --- /dev/null +++ b/app/src/mouse/tick_listener.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include // CLAMP + +#if !defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) +#if CONFIG_MINIMAL_LIBC +static float powf(float base, float exponent) { + // poor man's power implementation rounds the exponent down to the nearest integer. + float power = 1.0f; + for (; exponent >= 1.0f; exponent--) { + power = power * base; + } + return power; +} +#else +#include +#endif + +struct vector2d move_remainder = {0}; +struct vector2d scroll_remainder = {0}; + +static int64_t ms_since_start(int64_t start, int64_t now, int64_t delay) { + int64_t move_duration = now - (start + delay); + // start can be in the future if there's a delay + if (move_duration < 0) { + move_duration = 0; + } + return move_duration; +} + +static float speed(const struct mouse_config *config, float max_speed, int64_t duration_ms) { + // Calculate the speed based on MouseKeysAccel + // See https://en.wikipedia.org/wiki/Mouse_keys + if (duration_ms > config->time_to_max_speed_ms || config->time_to_max_speed_ms == 0 || + config->acceleration_exponent == 0) { + return max_speed; + } + float time_fraction = (float)duration_ms / config->time_to_max_speed_ms; + return max_speed * powf(time_fraction, config->acceleration_exponent); +} + +static void track_remainder(float *move, float *remainder) { + float new_move = *move + *remainder; + *remainder = new_move - (int)new_move; + *move = (int)new_move; +} + +static struct vector2d update_movement(struct vector2d *remainder, + const struct mouse_config *config, struct vector2d max_speed, + int64_t now, int64_t *start_time) { + struct vector2d move = {0}; + if (max_speed.x == 0 && max_speed.y == 0) { + *remainder = (struct vector2d){0}; + return move; + } + + int64_t move_duration = ms_since_start(*start_time, now, config->delay_ms); + move = (struct vector2d){ + .x = speed(config, max_speed.x, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, + .y = speed(config, max_speed.y, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, + }; + + track_remainder(&(move.x), &(remainder->x)); + track_remainder(&(move.y), &(remainder->y)); + + return move; +} + +static void mouse_tick_handler(const struct zmk_mouse_tick *tick) { + struct vector2d move = update_movement(&move_remainder, &(tick->move_config), tick->max_move, + tick->timestamp, tick->start_time); + zmk_hid_mouse_movement_update((int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX), + (int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX)); + struct vector2d scroll = update_movement(&scroll_remainder, &(tick->scroll_config), + tick->max_scroll, tick->timestamp, tick->start_time); + zmk_hid_mouse_scroll_update((int8_t)CLAMP(scroll.x, INT8_MIN, INT8_MAX), + (int8_t)CLAMP(scroll.y, INT8_MIN, INT8_MAX)); +} + +int zmk_mouse_tick_listener(const zmk_event_t *eh) { + const struct zmk_mouse_tick *tick = as_zmk_mouse_tick(eh); + if (tick) { + mouse_tick_handler(tick); + return 0; + } + return 0; +} + +ZMK_LISTENER(zmk_mouse_tick_listener, zmk_mouse_tick_listener); +ZMK_SUBSCRIPTION(zmk_mouse_tick_listener, zmk_mouse_tick); + +#endif /* !defined(CONFIG_ZMK_SPLIT) || defined(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */ diff --git a/app/tests/caps-word/deactivate-by-mod/events.patterns b/app/tests/caps-word/deactivate-by-mod/events.patterns new file mode 100644 index 00000000..e9b216a8 --- /dev/null +++ b/app/tests/caps-word/deactivate-by-mod/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode_//p +s/.*hid_implicit_modifiers_//p +s/.*caps_word_enhance_usage/enhance_usage/p diff --git a/app/tests/caps-word/deactivate-by-mod/keycode_events.snapshot b/app/tests/caps-word/deactivate-by-mod/keycode_events.snapshot new file mode 100644 index 00000000..4f27b89f --- /dev/null +++ b/app/tests/caps-word/deactivate-by-mod/keycode_events.snapshot @@ -0,0 +1,13 @@ +enhance_usage: Enhancing usage 0x04 with modifiers: 0x02 +pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x02 explicit_mods 0x00 +press: Modifiers set to 0x02 +released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +release: Modifiers set to 0x00 +pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +press: Modifiers set to 0x02 +released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +release: Modifiers set to 0x00 +pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +press: Modifiers set to 0x00 +released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +release: Modifiers set to 0x00 diff --git a/app/tests/caps-word/deactivate-by-mod/native_posix_64.keymap b/app/tests/caps-word/deactivate-by-mod/native_posix_64.keymap new file mode 100644 index 00000000..c77f5d47 --- /dev/null +++ b/app/tests/caps-word/deactivate-by-mod/native_posix_64.keymap @@ -0,0 +1,35 @@ +#include +#include +#include + +&caps_word { + /delete-property/ ignore-modifiers; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label = "Default keymap"; + + default_layer { + bindings = < + &caps_word &kp A + &kp LSHFT &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; diff --git a/app/tests/mouse-keys/mmv/events.patterns b/app/tests/mouse-keys/mmv/events.patterns new file mode 100644 index 00000000..833100f6 --- /dev/null +++ b/app/tests/mouse-keys/mmv/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mmv/keycode_events.snapshot b/app/tests/mouse-keys/mmv/keycode_events.snapshot new file mode 100644 index 00000000..259501ba --- /dev/null +++ b/app/tests/mouse-keys/mmv/keycode_events.snapshot @@ -0,0 +1,2 @@ +pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/mouse-keys/mmv/native_posix.keymap b/app/tests/mouse-keys/mmv/native_posix.keymap new file mode 100644 index 00000000..dbb3f014 --- /dev/null +++ b/app/tests/mouse-keys/mmv/native_posix.keymap @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &none + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/tri-state/behavior_keymap.dtsi b/app/tests/tri-state/behavior_keymap.dtsi new file mode 100644 index 00000000..7250566e --- /dev/null +++ b/app/tests/tri-state/behavior_keymap.dtsi @@ -0,0 +1,40 @@ +#include +#include +#include + +/ { + behaviors { + swap: swap { + compatible = "zmk,behavior-tri-state"; + label = "SWAPPER"; + #binding-cells = <0>; + bindings = <&kt LALT>, <&kp TAB>, <&kt LALT>; + ignored-key-positions = <2 3>; + ignored-layers = <1>; + timeout-ms = <200>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &swap &kp A + &kp B &tog 1>; + }; + + extra_layer { + bindings = < + &kp A &kp B + &tog 2 &trans>; + }; + + extra_layer2 { + bindings = < + &kp N1 &kp N2 + &trans &kp N3>; + }; + }; +}; diff --git a/app/tests/tri-state/swapper-int-shared-key/events.patterns b/app/tests/tri-state/swapper-int-shared-key/events.patterns new file mode 100644 index 00000000..44d77404 --- /dev/null +++ b/app/tests/tri-state/swapper-int-shared-key/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tri_state_binding/tri_state_binding/p diff --git a/app/tests/tri-state/swapper-int-shared-key/keycode_events.snapshot b/app/tests/tri-state/swapper-int-shared-key/keycode_events.snapshot new file mode 100644 index 00000000..688683b4 --- /dev/null +++ b/app/tests/tri-state/swapper-int-shared-key/keycode_events.snapshot @@ -0,0 +1,19 @@ +tri_state_binding_pressed: 0 created new tri_state +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tri-state/swapper-int-shared-key/native_posix_64.keymap b/app/tests/tri-state/swapper-int-shared-key/native_posix_64.keymap new file mode 100644 index 00000000..af9e044b --- /dev/null +++ b/app/tests/tri-state/swapper-int-shared-key/native_posix_64.keymap @@ -0,0 +1,19 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,1000) + >; +}; diff --git a/app/tests/tri-state/swapper-int-shared-layer/events.patterns b/app/tests/tri-state/swapper-int-shared-layer/events.patterns new file mode 100644 index 00000000..44d77404 --- /dev/null +++ b/app/tests/tri-state/swapper-int-shared-layer/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tri_state_binding/tri_state_binding/p diff --git a/app/tests/tri-state/swapper-int-shared-layer/keycode_events.snapshot b/app/tests/tri-state/swapper-int-shared-layer/keycode_events.snapshot new file mode 100644 index 00000000..6f08cc1f --- /dev/null +++ b/app/tests/tri-state/swapper-int-shared-layer/keycode_events.snapshot @@ -0,0 +1,17 @@ +tri_state_binding_pressed: 0 created new tri_state +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tri-state/swapper-int-shared-layer/native_posix_64.keymap b/app/tests/tri-state/swapper-int-shared-layer/native_posix_64.keymap new file mode 100644 index 00000000..92663480 --- /dev/null +++ b/app/tests/tri-state/swapper-int-shared-layer/native_posix_64.keymap @@ -0,0 +1,21 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + >; +}; diff --git a/app/tests/tri-state/swapper-int/events.patterns b/app/tests/tri-state/swapper-int/events.patterns new file mode 100644 index 00000000..44d77404 --- /dev/null +++ b/app/tests/tri-state/swapper-int/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tri_state_binding/tri_state_binding/p diff --git a/app/tests/tri-state/swapper-int/keycode_events.snapshot b/app/tests/tri-state/swapper-int/keycode_events.snapshot new file mode 100644 index 00000000..6dc67300 --- /dev/null +++ b/app/tests/tri-state/swapper-int/keycode_events.snapshot @@ -0,0 +1,17 @@ +tri_state_binding_pressed: 0 created new tri_state +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tri-state/swapper-int/native_posix_64.keymap b/app/tests/tri-state/swapper-int/native_posix_64.keymap new file mode 100644 index 00000000..6d98c9bd --- /dev/null +++ b/app/tests/tri-state/swapper-int/native_posix_64.keymap @@ -0,0 +1,17 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,1000) + >; +}; diff --git a/app/tests/tri-state/swapper/events.patterns b/app/tests/tri-state/swapper/events.patterns new file mode 100644 index 00000000..44d77404 --- /dev/null +++ b/app/tests/tri-state/swapper/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tri_state_binding/tri_state_binding/p diff --git a/app/tests/tri-state/swapper/keycode_events.snapshot b/app/tests/tri-state/swapper/keycode_events.snapshot new file mode 100644 index 00000000..67935076 --- /dev/null +++ b/app/tests/tri-state/swapper/keycode_events.snapshot @@ -0,0 +1,14 @@ +tri_state_binding_pressed: 0 created new tri_state +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tri-state/swapper/native_posix_64.keymap b/app/tests/tri-state/swapper/native_posix_64.keymap new file mode 100644 index 00000000..33ffc075 --- /dev/null +++ b/app/tests/tri-state/swapper/native_posix_64.keymap @@ -0,0 +1,15 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; diff --git a/app/tests/tri-state/timeout/events.patterns b/app/tests/tri-state/timeout/events.patterns new file mode 100644 index 00000000..44d77404 --- /dev/null +++ b/app/tests/tri-state/timeout/events.patterns @@ -0,0 +1,2 @@ +s/.*hid_listener_keycode/kp/p +s/.*on_tri_state_binding/tri_state_binding/p diff --git a/app/tests/tri-state/timeout/keycode_events.snapshot b/app/tests/tri-state/timeout/keycode_events.snapshot new file mode 100644 index 00000000..6403bfc8 --- /dev/null +++ b/app/tests/tri-state/timeout/keycode_events.snapshot @@ -0,0 +1,7 @@ +tri_state_binding_pressed: 0 created new tri_state +tri_state_binding_pressed: 0 tri_state pressed +kp_pressed: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +tri_state_binding_released: 0 tri_state keybind released +kp_released: usage_page 0x07 keycode 0x2B implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xE2 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/tri-state/timeout/native_posix_64.keymap b/app/tests/tri-state/timeout/native_posix_64.keymap new file mode 100644 index 00000000..5c3bf895 --- /dev/null +++ b/app/tests/tri-state/timeout/native_posix_64.keymap @@ -0,0 +1,11 @@ +#include +#include +#include +#include "../behavior_keymap.dtsi" + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,1000) + >; +}; diff --git a/docs/docs/behaviors/caps-word.md b/docs/docs/behaviors/caps-word.md index 0233de52..de134b47 100644 --- a/docs/docs/behaviors/caps-word.md +++ b/docs/docs/behaviors/caps-word.md @@ -37,6 +37,18 @@ By default, the caps word will remain active when any alphanumeric character or }; ``` +#### Continue on modifiers + +By default, the caps word will remain active when any modifiers are pressed. If you +would like to deactivate caps word when modifiers are pressed, you can delete the +`ignored-modifiers` property in your keymap: + +``` +&caps_word { + /delete-property/ ignore-modifiers; +}; +``` + #### Applied Modifier(s) In addition, if you would like _multiple_ modifiers, instead of just `MOD_LSFT`, you can override the `mods` property: diff --git a/docs/docs/behaviors/mouse-emulation.md b/docs/docs/behaviors/mouse-emulation.md new file mode 100644 index 00000000..efe095e7 --- /dev/null +++ b/docs/docs/behaviors/mouse-emulation.md @@ -0,0 +1,110 @@ +--- +title: Mouse Emulation Behaviors +sidebar_label: Mouse Emulation +--- + +## Summary + +Mouse emulation behaviors send mouse movements, button presses or scroll actions. + +Please view [`dt-bindings/zmk/mouse.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/mouse.h) for a comprehensive list of signals. + +## Configuration options + +This feature should be enabled via a config option: + +``` +CONFIG_ZMK_MOUSE=y +``` + +This option enables several others. + +### Dedicated thread processing + +`CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED` is enabled by default and separates the processing of mouse signals into a dedicated thread, significantly improving performance. + +### Tick rate configuration + +`CONFIG_ZMK_MOUSE_TICK_DURATION` sets the tick rate for mouse polling. It is set to 8 ms. by default. + +## Keycode Defines + +To make it easier to encode the HID keycode numeric values, most keymaps include +the [`dt-bindings/zmk/mouse.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/mouse.h) header +provided by ZMK near the top: + +``` +#include +``` + +Doing so allows using a set of defines such as `MOVE_UP`, `MOVE_DOWN`, `LCLK` and `SCROLL_UP` with these behaviors. + +## Mouse Button Press + +This behavior can press/release up to 16 mouse buttons. + +### Behavior Binding + +- Reference: `&mkp` +- Parameter: A `uint16` with each bit referring to a button. + +Example: + +``` +&mkp LCLK +``` + +## Mouse Movement + +This behavior is used to manipulate the cursor. + +### Behavior Binding + +- Reference: `&mmv` +- Parameter: A `uint32` with the first 16 bits relating to horizontal movement + and the last 16 - to vertical movement. + +Example: + +``` +&mmv MOVE_UP +``` + +## Mouse Scrolling + +This behaviour is used to scroll, both horizontally and vertically. + +### Behavior Binding + +- Reference: `&mwh` +- Parameter: A `uint16` with the first 8 bits relating to horizontal movement + and the last 8 - to vertical movement. + +Example: + +``` +&mwh SCROLL_UP +``` + +## Acceleration + +Both mouse movement and scrolling have independently configurable acceleration profiles with three parameters: delay before movement, time to max speed and the acceleration exponent. +The exponent is usually set to 0 for constant speed, 1 for uniform acceleration or 2 for uniform jerk. + +These profiles can be configured inside your keymap: + +``` +&mmv { + time-to-max-speed-ms = <500>; +}; + +&mwh { + acceleration-exponent=<1>; +}; + +/ { + keymap { + ... + }; +}; +``` diff --git a/docs/docs/behaviors/tri-state.md b/docs/docs/behaviors/tri-state.md new file mode 100644 index 00000000..2848a141 --- /dev/null +++ b/docs/docs/behaviors/tri-state.md @@ -0,0 +1,130 @@ +--- +title: Tri-State Behavior +sidebar_label: Tri-State +--- + +## Summary + +Tri-States are a way to have something persist while other behaviors occur. + +The tri-state key will fire the 'start' behavior when the key is pressed for the first time. Subsequent presses of the same key will output the second, 'continue' behavior, and any key position or layer state change that is not specified (see below) will trigger the 'interrupt behavior'. + +### Basic Usage + +The following is a basic definition of a tri-state: + +``` +/ { + behaviors { + tri-state: tri-state { + compatible = "zmk,behavior-tri-state"; + label = "TRI-STATE"; + #binding-cells = <0>; + bindings = <&kp A>, <&kp B>, <&kt C>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &tri-state &kp D + &kp E &kp F>; + }; + }; +}; +``` + +Pressing `tri-state` will fire the first behavior, and output `A`, as well as the second behavior, outputting `B`. Subsequent presses of `tri-state` will output `B`. When another key is pressed or a layer change occurs, the third, 'interrupt' behavior will fire. + +### Advanced Configuration + +#### `timeout-ms` + +Setting `timeout-ms` will cause the deactivation behavior to fire when the time has elapsed after releasing the Tri-State or a ignored key. + +#### `ignored-key-positions` + +- Including `ignored-key-positions` in your tri-state definition will let the key positions specified NOT trigger the interrupt behavior when a tri-state is active. +- Pressing any key **NOT** listed in `ignored-key-positions` will cause the interrupt behavior to fire. +- Note that `ignored-key-positions` is an array of key position indexes. Key positions are numbered according to your keymap, starting with 0. So if the first key in your keymap is Q, this key is in position 0. The next key (probably W) will be in position 1, et cetera. +- See the following example, which is an implementation of the popular [Swapper](https://github.com/callum-oakley/qmk_firmware/tree/master/users/callum) from Callum Oakley: + +``` +/ { + behaviors { + swap: swapper { + compatible = "zmk,behavior-tri-state"; + label = "SWAPPER"; + #binding-cells = <0>; + bindings = <&kt LALT>, <&kp TAB>, <&kt LALT>; + ignored-key-positions = <1>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &swap &kp LS(TAB) + &kp B &kp C>; + }; + }; +}; +``` + +- The sequence `(swap, swap, LS(TAB))` produces `(LA(TAB), LA(TAB), LA(LS(TAB)))`. The LS(TAB) behavior does not fire the interrupt behavior, because it is included in `ignored-key-positions`. +- The sequence `(swap, swap, B)` produces `(LA(TAB), LA(TAB), B)`. The B behavior **does** fire the interrupt behavior, because it is **not** included in `ignored-key-positions`. + +#### `ignored-layers` + +- By default, any layer change will trigger the end behavior. +- Including `ignored-layers` in your tri-state definition will let the specified layers NOT trigger the end behavior when they become active (include the layer the behavior is on to accommodate for layer toggling). +- Activating any layer **NOT** listed in `ignored-layers` will cause the interrupt behavior to fire. +- Note that `ignored-layers` is an array of layer indexes. Layers are numbered according to your keymap, starting with 0. The first layer in your keymap is layer 0. The next layer will be layer 1, et cetera. +- Looking back at the swapper implementation, we can see how `ignored-layers` can affect things + +``` +/ { + behaviors { + swap: swapper { + compatible = "zmk,behavior-tri-state"; + label = "SWAPPER"; + #binding-cells = <0>; + bindings = <&kt LALT>, <&kp TAB>, <&kt LALT>; + ignored-key-positions = <1 2 3>; + ignored-layers = <1>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &swap &kp LS(TAB) + &kp B &tog 1>; + }; + + layer2 { + bindings = < + &kp DOWN &kp B + &tog 2 &trans>; + }; + + layer3 { + bindings = < + &kp LEFT &kp N2 + &trans &kp N3>; + }; + }; +}; +``` + +- The sequence `(swap, tog 1, DOWN)` produces `(LA(TAB), LA(DOWN))`. The change to layer 1 does not fire the interrupt behavior, because it is included in `ignored-layers`, and DOWN is in the same position as the tri-state, also not firing the interrupt behavior. +- The sequence `(swap, tog 1, tog 2, LEFT)` produces `(LA(TAB), LEFT`. The change to layer 2 **does** fire the interrupt behavior, because it is not included in `ignored-layers`. diff --git a/docs/docs/intro.md b/docs/docs/intro.md index d65ac46e..cfbcf695 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) | ✅ | ✅ | | diff --git a/docs/sidebars.js b/docs/sidebars.js index 43f17b41..de80bbea 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -32,10 +32,12 @@ module.exports = { "behaviors/key-toggle", "behaviors/sticky-key", "behaviors/sticky-layer", + "behaviors/tri-state", "behaviors/tap-dance", "behaviors/caps-word", "behaviors/key-repeat", "behaviors/sensor-rotate", + "behaviors/mouse-emulation", "behaviors/reset", "behaviors/bluetooth", "behaviors/outputs",