Merge pull request #1 from urob/main

Timeless Homerow Mods
This commit is contained in:
Kyriel Abad 2023-10-19 13:37:48 +08:00 committed by GitHub
commit c24ef37743
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 2976 additions and 62 deletions

View file

@ -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
```

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,163 @@
/*
*
* Copyright (c) 2021 Polarity Works
* SPDX-License-Identifier: MIT
*
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include <dt-bindings/led/led.h>
#include <dt-bindings/zmk/matrix_transform.h>
#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 = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
};

View file

@ -0,0 +1,52 @@
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/bt.h>
#include <dt-bindings/zmk/rgb.h>
#include <dt-bindings/zmk/backlight.h>
/ {
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
>;
};
};
};

View file

@ -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

View file

@ -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

View file

@ -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>
;
};
};

View file

@ -0,0 +1,8 @@
/*
*
* Copyright (c) 2022 The ZMK Contributors
* SPDX-License-Identifier: MIT
*
*/
#include "adv360pro.keymap"

View file

@ -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

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 The ZMK Contributors
* SPDX-License-Identifier: MIT
*/
&pinctrl {
spi3_default: spi3_default {
group1 {
psels = <NRF_PSEL(SPIM_MOSI, 0, 20)>;
};
};
spi3_sleep: spi3_sleep {
group1 {
psels = <NRF_PSEL(SPIM_MOSI, 0, 20)>;
low-power-enable;
};
};
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 17)>;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 17)>;
low-power-enable;
};
};
};

View file

@ -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>;
};

View file

@ -0,0 +1,8 @@
/*
*
* Copyright (c) 2022 The ZMK Contributors
* SPDX-License-Identifier: MIT
*
*/
#include "adv360pro.keymap"

View file

@ -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

View file

@ -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)

View file

@ -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>;
};

View file

@ -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

View file

@ -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;

View file

@ -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) */
}

View file

@ -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] = {};

View file

@ -18,4 +18,7 @@
#include <behaviors/caps_word.dtsi>
#include <behaviors/key_repeat.dtsi>
#include <behaviors/backlight.dtsi>
#include <behaviors/macros.dtsi>
#include <behaviors/macros.dtsi>
#include <behaviors/mouse_key_press.dtsi>
#include <behaviors/mouse_move.dtsi>
#include <behaviors/mouse_scroll.dtsi>

View file

@ -12,7 +12,22 @@
compatible = "zmk,behavior-caps-word";
label = "CAPS_WORD";
#binding-cells = <0>;
mods = <MOD_LSFT>;
continue-list = <UNDERSCORE BACKSPACE DELETE>;
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 = <xx>; // to be specified in user config using "&num_word { layers = <xx>; };"
continue-list = <BACKSPACE DELETE DOT COMMA>;
ignore-numbers;
};
};
};

View file

@ -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>;
};
};
};

View file

@ -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>;
};
};
};

View file

@ -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>;
};
};
};

View file

@ -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

View file

@ -0,0 +1,5 @@
description: Mouse key press/release behavior
compatible: "zmk,behavior-mouse-key-press"
include: one_param.yaml

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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();

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <zmk/hid.h>
#include <zmk/event_manager.h>
#include <zmk/mouse.h>
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});
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <zmk/event_manager.h>
#include <zmk/mouse.h>
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});
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <zmk/event_manager.h>
#include <zmk/mouse.h>
#include <dt-bindings/zmk/mouse.h>
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});
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <dt-bindings/zmk/mouse.h>
#include <zephyr/kernel.h>
#include <zmk/event_manager.h>
#include <zmk/mouse.h>
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(),
});
}

View file

@ -10,6 +10,7 @@
#include <zephyr/usb/class/usb_hid.h>
#include <zmk/keys.h>
#include <zmk/mouse.h>
#include <dt-bindings/zmk/hid_usage.h>
#include <dt-bindings/zmk/hid_usage_pages.h>
@ -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();

View file

@ -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);

View file

@ -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);

30
app/include/zmk/mouse.h Normal file
View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <dt-bindings/zmk/mouse.h>
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();

View file

@ -8,4 +8,12 @@ config IL0323
depends on SPI
depends on HEAP_MEM_POOL_SIZE != 0
help
Enable driver for IL0323 compatible controller.
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

View file

@ -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;
}

View file

@ -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), \
}; \

View file

@ -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!");

View file

@ -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,

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_mouse_key_press
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/behavior.h>
#include <zmk/event_manager.h>
#include <zmk/events/mouse_button_state_changed.h>
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) */

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_mouse_move
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/behavior.h>
#include <zmk/event_manager.h>
#include <zmk/events/mouse_move_state_changed.h>
#include <zmk/mouse.h>
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) */

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_mouse_scroll
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/event_manager.h>
#include <zmk/events/mouse_scroll_state_changed.h>
#include <zmk/behavior.h>
#include <zmk/hid.h>
#include <zmk/endpoints.h>
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) */

View file

@ -0,0 +1,301 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_tri_state
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/behavior.h>
#include <zmk/behavior_queue.h>
#include <zmk/keymap.h>
#include <zmk/matrix.h>
#include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/events/layer_state_changed.h>
#include <zmk/hid.h>
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)

View file

@ -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);
}

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/mouse_button_state_changed.h>
ZMK_EVENT_IMPL(zmk_mouse_button_state_changed);

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/mouse_move_state_changed.h>
ZMK_EVENT_IMPL(zmk_mouse_move_state_changed);

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/mouse_scroll_state_changed.h>
ZMK_EVENT_IMPL(zmk_mouse_scroll_state_changed);

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/mouse_tick.h>
ZMK_EVENT_IMPL(zmk_mouse_tick);

View file

@ -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

View file

@ -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, &notify_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, &notify_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),

View file

@ -27,6 +27,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/events/sensor_event.h>
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;
}

View file

@ -17,6 +17,10 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/display.h>
#include <drivers/ext_power.h>
#ifdef CONFIG_ZMK_MOUSE
#include <zmk/mouse.h>
#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 */
}

38
app/src/mouse/Kconfig Normal file
View file

@ -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

View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/mouse_button_state_changed.h>
#include <zmk/events/mouse_move_state_changed.h>
#include <zmk/events/mouse_scroll_state_changed.h>
#include <zmk/events/mouse_tick.h>
#include <zmk/hid.h>
#include <zmk/endpoints.h>
#include <zmk/mouse.h>
#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) */

30
app/src/mouse/main.c Normal file
View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/mouse.h>
#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;
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/mouse_tick.h>
#include <zmk/endpoints.h>
#include <zmk/hid.h>
#include <zmk/mouse.h>
#include <zephyr/sys/util.h> // 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 <math.h>
#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) */

View file

@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
s/.*caps_word_enhance_usage/enhance_usage/p

View file

@ -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

View file

@ -0,0 +1,35 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
&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)
>;
};

View file

@ -0,0 +1 @@
s/.*hid_listener_keycode_//p

View file

@ -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

View file

@ -0,0 +1,26 @@
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/kscan_mock.h>
#include <dt-bindings/zmk/mouse.h>
/ {
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)
>;
};

View file

@ -0,0 +1,40 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
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>;
};
};
};

View file

@ -0,0 +1,2 @@
s/.*hid_listener_keycode/kp/p
s/.*on_tri_state_binding/tri_state_binding/p

View file

@ -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

View file

@ -0,0 +1,19 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#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)
>;
};

View file

@ -0,0 +1,2 @@
s/.*hid_listener_keycode/kp/p
s/.*on_tri_state_binding/tri_state_binding/p

View file

@ -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

View file

@ -0,0 +1,21 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#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)
>;
};

View file

@ -0,0 +1,2 @@
s/.*hid_listener_keycode/kp/p
s/.*on_tri_state_binding/tri_state_binding/p

View file

@ -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

View file

@ -0,0 +1,17 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#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)
>;
};

View file

@ -0,0 +1,2 @@
s/.*hid_listener_keycode/kp/p
s/.*on_tri_state_binding/tri_state_binding/p

View file

@ -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

View file

@ -0,0 +1,15 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#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)
>;
};

View file

@ -0,0 +1,2 @@
s/.*hid_listener_keycode/kp/p
s/.*on_tri_state_binding/tri_state_binding/p

View file

@ -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

View file

@ -0,0 +1,11 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,1000)
>;
};

View file

@ -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:

View file

@ -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 <dt-bindings/zmk/mouse.h>
```
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 {
...
};
};
```

View file

@ -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`.

View file

@ -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) | ✅ | ✅ | |

View file

@ -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",