From 800c269d43e4c362f4f70f6b87dd844859c7ca7e Mon Sep 17 00:00:00 2001 From: Ko ByeongGeon Date: Fri, 12 May 2023 23:29:56 +0900 Subject: [PATCH] update mouse branch --- .vscode/settings.json | 3 +- app/boards/shields/dokkaebi/Kconfig.defconfig | 16 ++ app/boards/shields/dokkaebi/Kconfig.shield | 5 + app/boards/shields/dokkaebi/dokkaebi.dtsi | 42 ++++ app/boards/shields/dokkaebi/dokkaebi.keymap | 154 ++++++++++++++ app/boards/shields/dokkaebi/dokkaebi.zmk.yml | 11 + .../shields/dokkaebi/dokkaebi_left.overlay | 13 ++ .../shields/dokkaebi/dokkaebi_right.overlay | 17 ++ .../sensor/trackball_pim447/CMakeLists.txt | 4 + app/drivers/sensor/trackball_pim447/Kconfig | 8 + .../trackball_pim447/trackball_pim447.c | 167 +++++++++++++++ .../sensor/pimoroni,trackball_pim447.yaml | 58 +++++ app/dts/behaviors/mouse_key_press.dtsi | 9 + app/dts/behaviors/mouse_move.dtsi | 12 ++ app/dts/behaviors/mouse_scroll.dtsi | 12 ++ app/dts/behaviors/trackball_pim447.dtsi | 56 +++++ .../zmk,behavior-mouse-key-press.yaml | 5 + .../behaviors/zmk,behavior-mouse-move.yaml | 13 ++ .../behaviors/zmk,behavior-mouse-scroll.yaml | 13 ++ .../zmk,behavior-trackball_pim447.yaml | 15 ++ app/include/dt-bindings/zmk/mouse.h | 30 +++ .../dt-bindings/zmk/trackball_pim447.h | 10 + .../zmk/events/mouse_button_state_changed.h | 27 +++ .../zmk/events/mouse_move_state_changed.h | 33 +++ .../zmk/events/mouse_scroll_state_changed.h | 34 +++ app/include/zmk/events/mouse_tick.h | 39 ++++ app/include/zmk/mouse.h | 30 +++ app/include/zmk/trackball_pim447.h | 10 + app/src/behaviors/behavior_mouse_key_press.c | 48 +++++ app/src/behaviors/behavior_mouse_move.c | 57 +++++ app/src/behaviors/behavior_mouse_scroll.c | 58 +++++ app/src/behaviors/behavior_trackball_pim447.c | 61 ++++++ app/src/events/mouse_button_state_changed.c | 10 + app/src/events/mouse_move_state_changed.c | 10 + app/src/events/mouse_scroll_state_changed.c | 10 + app/src/events/mouse_tick.c | 10 + app/src/mouse/Kconfig | 38 ++++ app/src/mouse/key_listener.c | 160 ++++++++++++++ app/src/mouse/main.c | 30 +++ app/src/mouse/tick_listener.c | 102 +++++++++ app/src/mouse/trackball_pim447.c | 198 ++++++++++++++++++ app/tests/mouse-keys/mmv/events.patterns | 1 + .../mouse-keys/mmv/keycode_events.snapshot | 2 + app/tests/mouse-keys/mmv/native_posix.keymap | 26 +++ 44 files changed, 1666 insertions(+), 1 deletion(-) create mode 100644 app/boards/shields/dokkaebi/Kconfig.defconfig create mode 100644 app/boards/shields/dokkaebi/Kconfig.shield create mode 100644 app/boards/shields/dokkaebi/dokkaebi.dtsi create mode 100644 app/boards/shields/dokkaebi/dokkaebi.keymap create mode 100644 app/boards/shields/dokkaebi/dokkaebi.zmk.yml create mode 100644 app/boards/shields/dokkaebi/dokkaebi_left.overlay create mode 100644 app/boards/shields/dokkaebi/dokkaebi_right.overlay create mode 100644 app/drivers/sensor/trackball_pim447/CMakeLists.txt create mode 100644 app/drivers/sensor/trackball_pim447/Kconfig create mode 100644 app/drivers/sensor/trackball_pim447/trackball_pim447.c create mode 100644 app/drivers/zephyr/dts/bindings/sensor/pimoroni,trackball_pim447.yaml create mode 100644 app/dts/behaviors/mouse_key_press.dtsi create mode 100644 app/dts/behaviors/mouse_move.dtsi create mode 100644 app/dts/behaviors/mouse_scroll.dtsi create mode 100644 app/dts/behaviors/trackball_pim447.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-trackball_pim447.yaml create mode 100644 app/include/dt-bindings/zmk/mouse.h create mode 100644 app/include/dt-bindings/zmk/trackball_pim447.h create mode 100644 app/include/zmk/events/mouse_button_state_changed.h create mode 100644 app/include/zmk/events/mouse_move_state_changed.h create mode 100644 app/include/zmk/events/mouse_scroll_state_changed.h create mode 100644 app/include/zmk/events/mouse_tick.h create mode 100644 app/include/zmk/mouse.h create mode 100644 app/include/zmk/trackball_pim447.h create mode 100644 app/src/behaviors/behavior_mouse_key_press.c create mode 100644 app/src/behaviors/behavior_mouse_move.c create mode 100644 app/src/behaviors/behavior_mouse_scroll.c create mode 100644 app/src/behaviors/behavior_trackball_pim447.c create mode 100644 app/src/events/mouse_button_state_changed.c create mode 100644 app/src/events/mouse_move_state_changed.c create mode 100644 app/src/events/mouse_scroll_state_changed.c create mode 100644 app/src/events/mouse_tick.c create mode 100644 app/src/mouse/Kconfig create mode 100644 app/src/mouse/key_listener.c create mode 100644 app/src/mouse/main.c create mode 100644 app/src/mouse/tick_listener.c create mode 100644 app/src/mouse/trackball_pim447.c create mode 100644 app/tests/mouse-keys/mmv/events.patterns create mode 100644 app/tests/mouse-keys/mmv/keycode_events.snapshot create mode 100644 app/tests/mouse-keys/mmv/native_posix.keymap diff --git a/.vscode/settings.json b/.vscode/settings.json index aea29cf0..82a8d167 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "*.overlay": "dts", "*.keymap": "dts" }, - "python.formatting.provider": "black" + "python.formatting.provider": "black", + "cmake.configureOnOpen": false } diff --git a/app/boards/shields/dokkaebi/Kconfig.defconfig b/app/boards/shields/dokkaebi/Kconfig.defconfig new file mode 100644 index 00000000..518264fb --- /dev/null +++ b/app/boards/shields/dokkaebi/Kconfig.defconfig @@ -0,0 +1,16 @@ +if SHIELD_DOKKAEBI_LEFT + +config ZMK_KEYBOARD_NAME + default "dokkaebi" + +config ZMK_SPLIT_ROLE_CENTRAL + default y + +endif + +if SHIELD_DOKKAEBI_LEFT || SHIELD_DOKKAEBI_RIGHT + +config ZMK_SPLIT + default y + +endif \ No newline at end of file diff --git a/app/boards/shields/dokkaebi/Kconfig.shield b/app/boards/shields/dokkaebi/Kconfig.shield new file mode 100644 index 00000000..6c791f00 --- /dev/null +++ b/app/boards/shields/dokkaebi/Kconfig.shield @@ -0,0 +1,5 @@ +config SHIELD_DOKKAEBI_LEFT + def_bool $(shields_list_contains,dokkaebi_left) + +config SHIELD_DOKKAEBI_RIGHT + def_bool $(shields_list_contains,dokkaebi_right) \ No newline at end of file diff --git a/app/boards/shields/dokkaebi/dokkaebi.dtsi b/app/boards/shields/dokkaebi/dokkaebi.dtsi new file mode 100644 index 00000000..1312a21d --- /dev/null +++ b/app/boards/shields/dokkaebi/dokkaebi.dtsi @@ -0,0 +1,42 @@ +#include + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <14>; + rows = <4>; +// | SW6 | SW5 | SW4 | SW3 | SW2 | SW1 | | SW1 | SW2 | SW3 | SW4 | SW5 | SW6 | +// | SW12 | SW11 | SW10 | SW9 | SW8 | SW7 | | SW7 | SW8 | SW9 | SW10 | SW11 | SW12 | +// | SW18 | SW17 | SW16 | SW15 | SW14 | SW13 | | SW13 | SW14 | SW15 | SW16 | SW17 | SW18 | +// | SW24 | SW23 | SW22 | SW21 | SW20 | SW19 | SW25 | | SW25 | SW19 | SW20 | SW21 | SW22 | SW23 | SW24 | +// | SW29 | SW28 | SW27 | SW26 | | SW26 | SW27 | SW28 | SW29 | + map = < +RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,8) RC(0,9) RC(0,10) RC(0,11) RC(0,12) RC(0,13) +RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,8) RC(1,9) RC(1,10) RC(1,11) RC(1,12) RC(1,13) +RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,8) RC(2,9) RC(2,10) RC(2,11) RC(2,12) RC(2,13) +RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,8) RC(3,9) RC(3,10) RC(3,11) RC(3,12) RC(3,13) + RC(3,6) RC(2,6) RC(1,6) RC(1,7) RC(2,7) RC(3,7) + >; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + label = "KSCAN"; + + diode-direction = "col2row"; + debounce-press-ms = <4>; + debounce-release-ms = <20>; + row-gpios + = <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 9 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + + }; +}; diff --git a/app/boards/shields/dokkaebi/dokkaebi.keymap b/app/boards/shields/dokkaebi/dokkaebi.keymap new file mode 100644 index 00000000..b90c3181 --- /dev/null +++ b/app/boards/shields/dokkaebi/dokkaebi.keymap @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include + +&mt { + tapping-term-ms = <160>; +}; + +< { + tapping-term-ms = <175>; + quick-tap-ms = <120>; +}; + +/ { + combos { + compatible = "zmk,combos"; + combo_lbrc { + timeout-ms = <50>; + key-positions = <24 25>; + bindings = <&kp LBKT>; + }; + combo_rbrc { + timeout-ms = <50>; + key-positions = <34 35>; + bindings = <&kp RBKT>; + }; + combo_lt2 { + timeout-ms = <50>; + key-positions = <48 49 50>; + bindings = <&mo 2>; + }; + }; + + behaviors { + gresc: grave_escape { + compatible = "zmk,behavior-mod-morph"; + label = "GRAVE_ESCAPE"; + #binding-cells = <0>; + bindings = <&kp ESC>, <&kp GRAVE>; + mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT|MOD_RALT)>; + keep-mods = <(MOD_RSFT)>; + }; + td_lsftbkt: tap_dance_lsft_bkt { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_LSFT_BKT"; + #binding-cells = <0>; + tapping-term-ms = <175>; + bindings = <&kp LSHFT>, <&kp LBKT>; + }; + td_rsftbkt: tap_dance_rsft_bkt { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_RSFT_BKT"; + #binding-cells = <0>; + tapping-term-ms = <175>; + bindings = <&kp RSHFT>, <&kp RBKT>; + }; + gqt: global-quick-tap { + compatible = "zmk,behavior-hold-tap"; + label = "GLOBAL_QUICK_TAP"; + #binding-cells = <2>; + flavor = "tap-preferred"; + tapping-term-ms = <160>; + quick-tap-ms = <120>; + global-quick-tap; + bindings = <&kp>, <&kp>; + }; + }; + + macros { + bt_0: bt_profile_macro_0 { + label = "BT_0"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <&out OUT_BLE> + , <&bt BT_SEL 0>; + }; + + bt_1: bt_profile_macro_1 { + label = "BT_1"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <&out OUT_BLE> + , <&bt BT_SEL 1>; + }; + + bt_2: bt_profile_macro_2 { + label = "BT_2"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <&out OUT_BLE> + , <&bt BT_SEL 2>; + }; + + bt_3: bt_profile_macro_3 { + label = "BT_3"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <&out OUT_BLE> + , <&bt BT_SEL 3>; + }; + + bt_4: bt_profile_macro_4 { + label = "BT_4"; + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <&out OUT_BLE> + , <&bt BT_SEL 4>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < +&gresc &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp DEL +&kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp EQUAL +&mt LGUI MINUS &kp A &kp S &kp D &kp F &kp G &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 N &kp M &kp COMMA &kp DOT &kp FSLH &kp RSHFT + &kp LALT < 1 SPACE &gqt LCTRL BSPC &kp ENTER < 1 SPACE &kp RALT + >; + + }; + + lower_layer { + bindings = < +&trans &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 &kp HOME +&trans &trans &trans &trans &trans &kp F11 &kp F12 &trans &kp UP &trans &trans &kp END +&trans &trans &trans &trans &trans &kp C_VOL_UP &trans &kp LEFT &kp DOWN &kp RIGHT &trans &kp PG_UP +&trans &trans &trans &trans &trans &kp C_VOL_DN &trans &trans &trans &trans &kp BSLH &kp PG_DN + &trans &kp SPACE &kp LCTRL &trans &kp SPACE &kp RCTRL + >; + + }; + + raise_layer { + bindings = < +&bt BT_CLR &bt_0 &bt_1 &bt_2 &bt_3 &bt_4 &trans &trans &trans &trans &trans &trans +&out OUT_TOG &key_repeat &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 &trans &trans &trans &trans + >; + + }; + }; +}; \ No newline at end of file diff --git a/app/boards/shields/dokkaebi/dokkaebi.zmk.yml b/app/boards/shields/dokkaebi/dokkaebi.zmk.yml new file mode 100644 index 00000000..8b10abfa --- /dev/null +++ b/app/boards/shields/dokkaebi/dokkaebi.zmk.yml @@ -0,0 +1,11 @@ +file_format: "1" +id: dokkaebi +name: dokkaebi +type: shield +url: https://github.com/zenithistk/dokkaebi +requires: [pro_micro] +features: + - keys +siblings: + - dokkaebi_left + - dokkaebi_right \ No newline at end of file diff --git a/app/boards/shields/dokkaebi/dokkaebi_left.overlay b/app/boards/shields/dokkaebi/dokkaebi_left.overlay new file mode 100644 index 00000000..34bb2583 --- /dev/null +++ b/app/boards/shields/dokkaebi/dokkaebi_left.overlay @@ -0,0 +1,13 @@ +#include "dokkaebi.dtsi" + +&kscan0 { + col-gpios + = <&pro_micro 1 GPIO_ACTIVE_HIGH> + , <&pro_micro 0 GPIO_ACTIVE_HIGH> + , <&pro_micro 21 GPIO_ACTIVE_HIGH> + , <&pro_micro 20 GPIO_ACTIVE_HIGH> + , <&pro_micro 4 GPIO_ACTIVE_HIGH> + , <&pro_micro 19 GPIO_ACTIVE_HIGH> + , <&pro_micro 5 GPIO_ACTIVE_HIGH> + ; +}; diff --git a/app/boards/shields/dokkaebi/dokkaebi_right.overlay b/app/boards/shields/dokkaebi/dokkaebi_right.overlay new file mode 100644 index 00000000..0e79e807 --- /dev/null +++ b/app/boards/shields/dokkaebi/dokkaebi_right.overlay @@ -0,0 +1,17 @@ +#include "dokkaebi.dtsi" + +&default_transform { + col-offset = <7>; +}; + +&kscan0 { + col-gpios + = <&pro_micro 5 GPIO_ACTIVE_HIGH> + , <&pro_micro 19 GPIO_ACTIVE_HIGH> + , <&pro_micro 4 GPIO_ACTIVE_HIGH> + , <&pro_micro 20 GPIO_ACTIVE_HIGH> + , <&pro_micro 21 GPIO_ACTIVE_HIGH> + , <&pro_micro 0 GPIO_ACTIVE_HIGH> + , <&pro_micro 1 GPIO_ACTIVE_HIGH> + ; +}; diff --git a/app/drivers/sensor/trackball_pim447/CMakeLists.txt b/app/drivers/sensor/trackball_pim447/CMakeLists.txt new file mode 100644 index 00000000..03e20c65 --- /dev/null +++ b/app/drivers/sensor/trackball_pim447/CMakeLists.txt @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(trackball_pim447.c) diff --git a/app/drivers/sensor/trackball_pim447/Kconfig b/app/drivers/sensor/trackball_pim447/Kconfig new file mode 100644 index 00000000..ac837b93 --- /dev/null +++ b/app/drivers/sensor/trackball_pim447/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config ZMK_TRACKBALL_PIM447 + bool "ZMK PIM447 trackball" + depends on I2C + help + Enable I2C-based driver for Pimoroni PIM447 trackball. diff --git a/app/drivers/sensor/trackball_pim447/trackball_pim447.c b/app/drivers/sensor/trackball_pim447/trackball_pim447.c new file mode 100644 index 00000000..6efb1ef8 --- /dev/null +++ b/app/drivers/sensor/trackball_pim447/trackball_pim447.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT pimoroni_trackball_pim447 + +#include +#include + +#define LOG_LEVEL CONFIG_SENSOR_LOG_LEVEL +#include +LOG_MODULE_REGISTER(trackball_pim447); + +#define TRACKBALL_PIM447_REG_LEFT 0x04 +#define TRACKBALL_PIM447_REG_RIGHT 0x05 +#define TRACKBALL_PIM447_REG_UP 0x06 +#define TRACKBALL_PIM447_REG_DOWN 0x07 +#define TRACKBALL_PIM447_REG_SWITCH 0x08 + +#define TRACKBALL_PIM447_REG_MIN TRACKBALL_PIM447_REG_LEFT +#define TRACKBALL_PIM447_REG_MAX TRACKBALL_PIM447_REG_SWITCH + +struct trackball_pim447_data { + const struct device *i2c_dev; + int32_t dx; + int32_t dy; + int32_t dz; +}; + +static int trackball_pim447_read_reg(const struct device *dev, + uint8_t reg, uint8_t *value) +{ + struct trackball_pim447_data *data = dev->data; + + if (reg < TRACKBALL_PIM447_REG_MIN || reg > TRACKBALL_PIM447_REG_MAX) { + return -ENOTSUP; + } + + int status = i2c_reg_read_byte(data->i2c_dev, DT_INST_REG_ADDR(0), + reg, value); + if (status < 0) { + LOG_ERR("Sensor reg read byte failed"); + return status; + } + + return 0; +} + +static int trackball_pim447_read_axis(const struct device *dev, + uint8_t reg_negative, + uint8_t reg_positive, + int32_t *value) +{ + uint8_t value_negative; + uint8_t value_positive; + int status; + + status = trackball_pim447_read_reg(dev, reg_negative, &value_negative); + if (status < 0) { + return status; + } + + status = trackball_pim447_read_reg(dev, reg_positive, &value_positive); + if (status < 0) { + return status; + } + + *value = value_positive - value_negative; + + return 0; +} + +static int trackball_pim447_sample_fetch(const struct device *dev, + enum sensor_channel chan) +{ + struct trackball_pim447_data *data = dev->data; + int status; + + if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_POS_DX) { + status = trackball_pim447_read_axis(dev, + TRACKBALL_PIM447_REG_LEFT, + TRACKBALL_PIM447_REG_RIGHT, + &data->dx); + if (status < 0) { + return status; + } + } + + if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_POS_DY) { + status = trackball_pim447_read_axis(dev, + TRACKBALL_PIM447_REG_UP, + TRACKBALL_PIM447_REG_DOWN, + &data->dy); + if (status < 0) { + return status; + } + } + + if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_POS_DZ) { + uint8_t value; + status = trackball_pim447_read_reg(dev, + TRACKBALL_PIM447_REG_SWITCH, + &value); + if (status < 0) { + return status; + } + + data->dz = value; + } + + return 0; +} + +static int trackball_pim447_channel_get(const struct device *dev, + enum sensor_channel chan, + struct sensor_value *val) +{ + struct trackball_pim447_data *data = dev->data; + + /* Not used. */ + val->val2 = 0; + + switch (chan) { + case SENSOR_CHAN_POS_DX: + val->val1 = data->dx; + break; + + case SENSOR_CHAN_POS_DY: + val->val1 = data->dy; + break; + + case SENSOR_CHAN_POS_DZ: + val->val1 = data->dz; + break; + + default: + return -ENOTSUP; + } + + return 0; +} + +static int trackball_pim447_init(const struct device *dev) +{ + struct trackball_pim447_data *data = dev->data; + + data->i2c_dev = device_get_binding(DT_INST_BUS_LABEL(0)); + if (data->i2c_dev == NULL) { + LOG_ERR("Failed to get I2C device"); + return -EINVAL; + } + + return 0; +} + +static struct trackball_pim447_data trackball_pim447_data; + +static const struct sensor_driver_api trackball_pim447_api = { + .sample_fetch = trackball_pim447_sample_fetch, + .channel_get = trackball_pim447_channel_get, +}; + +DEVICE_DT_INST_DEFINE(0, &trackball_pim447_init, device_pm_control_nop, + &trackball_pim447_data, NULL, POST_KERNEL, + CONFIG_SENSOR_INIT_PRIORITY, &trackball_pim447_api); diff --git a/app/drivers/zephyr/dts/bindings/sensor/pimoroni,trackball_pim447.yaml b/app/drivers/zephyr/dts/bindings/sensor/pimoroni,trackball_pim447.yaml new file mode 100644 index 00000000..2e1796e7 --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/sensor/pimoroni,trackball_pim447.yaml @@ -0,0 +1,58 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Trackball PIM447 + +compatible: "pimoroni,trackball_pim447" + +properties: + label: + type: string + required: true + + reg: + type: int + required: true + + move-factor: + type: int + required: false + default: 1 + + invert-move-x: + type: boolean + required: false + + invert-move-y: + type: boolean + required: false + + button: + type: int + required: false + default: 0 + + swap-axes: + type: boolean + required: false + + scroll-divisor: + type: int + required: false + default: 2 + + invert-scroll-x: + type: boolean + required: false + + invert-scroll-y: + type: boolean + required: false + + mode: + type: int + required: false + default: 1 + enum: + - 1 + - 2 diff --git a/app/dts/behaviors/mouse_key_press.dtsi b/app/dts/behaviors/mouse_key_press.dtsi new file mode 100644 index 00000000..8b2aacb3 --- /dev/null +++ b/app/dts/behaviors/mouse_key_press.dtsi @@ -0,0 +1,9 @@ +/ { + behaviors { + /omit-if-no-ref/ mkp: behavior_mouse_key_press { + compatible = "zmk,behavior-mouse-key-press"; + label = "MOUSE_KEY_PRESS"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/behaviors/mouse_move.dtsi b/app/dts/behaviors/mouse_move.dtsi new file mode 100644 index 00000000..d34329c8 --- /dev/null +++ b/app/dts/behaviors/mouse_move.dtsi @@ -0,0 +1,12 @@ +/ { + behaviors { + /omit-if-no-ref/ mmv: behavior_mouse_move { + compatible = "zmk,behavior-mouse-move"; + label = "MOUSE_MOVE"; + #binding-cells = <1>; + delay-ms = <0>; + time-to-max-speed-ms = <300>; + acceleration-exponent = <1>; + }; + }; +}; diff --git a/app/dts/behaviors/mouse_scroll.dtsi b/app/dts/behaviors/mouse_scroll.dtsi new file mode 100644 index 00000000..fb54886d --- /dev/null +++ b/app/dts/behaviors/mouse_scroll.dtsi @@ -0,0 +1,12 @@ +/ { + behaviors { + /omit-if-no-ref/ mwh: msc: behavior_mouse_scroll { + compatible = "zmk,behavior-mouse-scroll"; + label = "MOUSE_SCROLL"; + #binding-cells = <1>; + delay-ms = <0>; + time-to-max-speed-ms = <300>; + acceleration-exponent = <0>; + }; + }; +}; diff --git a/app/dts/behaviors/trackball_pim447.dtsi b/app/dts/behaviors/trackball_pim447.dtsi new file mode 100644 index 00000000..dc95c692 --- /dev/null +++ b/app/dts/behaviors/trackball_pim447.dtsi @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { + /omit-if-no-ref/ pim447_move: behavior_trackball_pim447_move { + compatible = "zmk,behavior-trackball-pim447"; + label = "PIM447_MOVE"; + mode = ; + #binding-cells = <0>; + }; + + /omit-if-no-ref/ pim447_scroll: behavior_trackball_pim447_scroll { + compatible = "zmk,behavior-trackball-pim447"; + label = "PIM447_SCROLL"; + mode = ; + #binding-cells = <0>; + }; + + /omit-if-no-ref/ pim447_toggle: behavior_trackball_pim447_toggle { + compatible = "zmk,behavior-trackball-pim447"; + label = "PIM447_TOGGLE"; + mode = ; + #binding-cells = <0>; + }; + + /omit-if-no-ref/ pim447_move_scroll: behavior_trackball_pim447_move_scroll { + compatible = "zmk,behavior-trackball-pim447"; + label = "PIM447_MOVE_SCROLL"; + mode = ; + momentary; + #binding-cells = <0>; + }; + + /omit-if-no-ref/ pim447_scroll_move: behavior_trackball_pim447_scroll_move { + compatible = "zmk,behavior-trackball-pim447"; + label = "PIM447_SCROLL_MOVE"; + mode = ; + momentary; + #binding-cells = <0>; + }; + + /omit-if-no-ref/ pim447_toggle_toggle: behavior_trackball_pim447_toggle_toggle { + compatible = "zmk,behavior-trackball-pim447"; + label = "PIM447_TOGGLE_TOGGLE"; + mode = ; + momentary; + #binding-cells = <0>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml new file mode 100644 index 00000000..8540916b --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-key-press.yaml @@ -0,0 +1,5 @@ +description: Mouse key press/release behavior + +compatible: "zmk,behavior-mouse-key-press" + +include: one_param.yaml diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml new file mode 100644 index 00000000..73ec34ec --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-move.yaml @@ -0,0 +1,13 @@ +description: Mouse move + +compatible: "zmk,behavior-mouse-move" + +include: one_param.yaml + +properties: + delay-ms: + type: int + time-to-max-speed-ms: + type: int + acceleration-exponent: + type: int diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml new file mode 100644 index 00000000..5a932bc5 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-scroll.yaml @@ -0,0 +1,13 @@ +description: Mouse scroll + +compatible: "zmk,behavior-mouse-scroll" + +include: one_param.yaml + +properties: + delay-ms: + type: int + time-to-max-speed-ms: + type: int + acceleration-exponent: + type: int diff --git a/app/dts/bindings/behaviors/zmk,behavior-trackball_pim447.yaml b/app/dts/bindings/behaviors/zmk,behavior-trackball_pim447.yaml new file mode 100644 index 00000000..0c34117b --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-trackball_pim447.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Trackball PIM447 behavior + +compatible: "zmk,behavior-trackball-pim447" + +include: zero_param.yaml + +properties: + mode: + type: int + required: true + momentary: + type: boolean diff --git a/app/include/dt-bindings/zmk/mouse.h b/app/include/dt-bindings/zmk/mouse.h new file mode 100644 index 00000000..f8f85744 --- /dev/null +++ b/app/include/dt-bindings/zmk/mouse.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +typedef uint16_t zmk_mouse_button_flags_t; +typedef uint16_t zmk_mouse_button_t; + +struct mouse_config { + int delay_ms; + int time_to_max_speed_ms; + // acceleration exponent 0: uniform speed + // acceleration exponent 1: uniform acceleration + // acceleration exponent 2: uniform jerk + int acceleration_exponent; +}; + +struct vector2d { + float x; + float y; +}; + +struct k_work_q *zmk_mouse_work_q(); +int zmk_mouse_init(); \ No newline at end of file diff --git a/app/include/dt-bindings/zmk/trackball_pim447.h b/app/include/dt-bindings/zmk/trackball_pim447.h new file mode 100644 index 00000000..08620821 --- /dev/null +++ b/app/include/dt-bindings/zmk/trackball_pim447.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +#include + +extern void zmk_trackball_pim447_set_mode(int mode); diff --git a/app/include/zmk/events/mouse_button_state_changed.h b/app/include/zmk/events/mouse_button_state_changed.h new file mode 100644 index 00000000..7ec4d208 --- /dev/null +++ b/app/include/zmk/events/mouse_button_state_changed.h @@ -0,0 +1,27 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +struct zmk_mouse_button_state_changed { + zmk_mouse_button_t buttons; + bool state; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_button_state_changed); + +static inline struct zmk_mouse_button_state_changed_event * +zmk_mouse_button_state_changed_from_encoded(uint32_t encoded, bool pressed, int64_t timestamp) { + return new_zmk_mouse_button_state_changed((struct zmk_mouse_button_state_changed){ + .buttons = HID_USAGE_ID(encoded), .state = pressed, .timestamp = timestamp}); +} diff --git a/app/include/zmk/events/mouse_move_state_changed.h b/app/include/zmk/events/mouse_move_state_changed.h new file mode 100644 index 00000000..8866f81d --- /dev/null +++ b/app/include/zmk/events/mouse_move_state_changed.h @@ -0,0 +1,33 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include + +struct zmk_mouse_move_state_changed { + struct vector2d max_speed; + struct mouse_config config; + bool state; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_move_state_changed); + +static inline struct zmk_mouse_move_state_changed_event * +zmk_mouse_move_state_changed_from_encoded(uint32_t encoded, struct mouse_config config, + bool pressed, int64_t timestamp) { + struct vector2d max_speed = (struct vector2d){ + .x = MOVE_HOR_DECODE(encoded), + .y = MOVE_VERT_DECODE(encoded), + }; + + return new_zmk_mouse_move_state_changed((struct zmk_mouse_move_state_changed){ + .max_speed = max_speed, .config = config, .state = pressed, .timestamp = timestamp}); +} diff --git a/app/include/zmk/events/mouse_scroll_state_changed.h b/app/include/zmk/events/mouse_scroll_state_changed.h new file mode 100644 index 00000000..fa60e8a7 --- /dev/null +++ b/app/include/zmk/events/mouse_scroll_state_changed.h @@ -0,0 +1,34 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +struct zmk_mouse_scroll_state_changed { + struct vector2d max_speed; + struct mouse_config config; + bool state; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_scroll_state_changed); + +static inline struct zmk_mouse_scroll_state_changed_event * +zmk_mouse_scroll_state_changed_from_encoded(uint32_t encoded, struct mouse_config config, + bool pressed, int64_t timestamp) { + struct vector2d max_speed = (struct vector2d){ + .x = SCROLL_HOR_DECODE(encoded), + .y = SCROLL_VERT_DECODE(encoded), + }; + + return new_zmk_mouse_scroll_state_changed((struct zmk_mouse_scroll_state_changed){ + .max_speed = max_speed, .config = config, .state = pressed, .timestamp = timestamp}); +} diff --git a/app/include/zmk/events/mouse_tick.h b/app/include/zmk/events/mouse_tick.h new file mode 100644 index 00000000..c75b9b4f --- /dev/null +++ b/app/include/zmk/events/mouse_tick.h @@ -0,0 +1,39 @@ + +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +struct zmk_mouse_tick { + struct vector2d max_move; + struct vector2d max_scroll; + struct mouse_config move_config; + struct mouse_config scroll_config; + int64_t *start_time; + int64_t timestamp; +}; + +ZMK_EVENT_DECLARE(zmk_mouse_tick); + +static inline struct zmk_mouse_tick_event *zmk_mouse_tick(struct vector2d max_move, + struct vector2d max_scroll, + struct mouse_config move_config, + struct mouse_config scroll_config, + int64_t *movement_start) { + return new_zmk_mouse_tick((struct zmk_mouse_tick){ + .max_move = max_move, + .max_scroll = max_scroll, + .move_config = move_config, + .scroll_config = scroll_config, + .start_time = movement_start, + .timestamp = k_uptime_get(), + }); +} diff --git a/app/include/zmk/mouse.h b/app/include/zmk/mouse.h new file mode 100644 index 00000000..f8f85744 --- /dev/null +++ b/app/include/zmk/mouse.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +typedef uint16_t zmk_mouse_button_flags_t; +typedef uint16_t zmk_mouse_button_t; + +struct mouse_config { + int delay_ms; + int time_to_max_speed_ms; + // acceleration exponent 0: uniform speed + // acceleration exponent 1: uniform acceleration + // acceleration exponent 2: uniform jerk + int acceleration_exponent; +}; + +struct vector2d { + float x; + float y; +}; + +struct k_work_q *zmk_mouse_work_q(); +int zmk_mouse_init(); \ No newline at end of file diff --git a/app/include/zmk/trackball_pim447.h b/app/include/zmk/trackball_pim447.h new file mode 100644 index 00000000..08620821 --- /dev/null +++ b/app/include/zmk/trackball_pim447.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +#include + +extern void zmk_trackball_pim447_set_mode(int mode); diff --git a/app/src/behaviors/behavior_mouse_key_press.c b/app/src/behaviors/behavior_mouse_key_press.c new file mode 100644 index 00000000..e5f2709c --- /dev/null +++ b/app/src/behaviors/behavior_mouse_key_press.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_mouse_key_press + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_mouse_key_press_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + + return ZMK_EVENT_RAISE( + zmk_mouse_button_state_changed_from_encoded(binding->param1, true, event.timestamp)); +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + return ZMK_EVENT_RAISE( + zmk_mouse_button_state_changed_from_encoded(binding->param1, false, event.timestamp)); +} + +static const struct behavior_driver_api behavior_mouse_key_press_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define KP_INST(n) \ + DEVICE_DT_INST_DEFINE(n, behavior_mouse_key_press_init, device_pm_control_nop, NULL, NULL, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_mouse_key_press_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ \ No newline at end of file diff --git a/app/src/behaviors/behavior_mouse_move.c b/app/src/behaviors/behavior_mouse_move.c new file mode 100644 index 00000000..5977a039 --- /dev/null +++ b/app/src/behaviors/behavior_mouse_move.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_mouse_move + +#include +#include +#include + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_mouse_move_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE( + zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, true, event.timestamp)); +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE(zmk_mouse_move_state_changed_from_encoded(binding->param1, *config, + false, event.timestamp)); +} + +static const struct behavior_driver_api behavior_mouse_move_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define KP_INST(n) \ + static struct mouse_config behavior_mouse_move_config_##n = { \ + .delay_ms = DT_INST_PROP(n, delay_ms), \ + .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ + .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ + }; \ + DEVICE_DT_INST_DEFINE(n, behavior_mouse_move_init, device_pm_control_nop, NULL, \ + &behavior_mouse_move_config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_move_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_mouse_scroll.c b/app/src/behaviors/behavior_mouse_scroll.c new file mode 100644 index 00000000..64162352 --- /dev/null +++ b/app/src/behaviors/behavior_mouse_scroll.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_mouse_scroll + +#include +#include +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +static int behavior_mouse_scroll_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, + true, event.timestamp)); +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1); + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct mouse_config *config = dev->config; + return ZMK_EVENT_RAISE(zmk_mouse_scroll_state_changed_from_encoded(binding->param1, *config, + false, event.timestamp)); +} + +static const struct behavior_driver_api behavior_mouse_scroll_driver_api = { + .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + +#define KP_INST(n) \ + static struct mouse_config behavior_mouse_scroll_config_##n = { \ + .delay_ms = DT_INST_PROP(n, delay_ms), \ + .time_to_max_speed_ms = DT_INST_PROP(n, time_to_max_speed_ms), \ + .acceleration_exponent = DT_INST_PROP(n, acceleration_exponent), \ + }; \ + DEVICE_DT_INST_DEFINE(n, behavior_mouse_scroll_init, device_pm_control_nop, NULL, \ + &behavior_mouse_scroll_config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mouse_scroll_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_trackball_pim447.c b/app/src/behaviors/behavior_trackball_pim447.c new file mode 100644 index 00000000..d2eec9bc --- /dev/null +++ b/app/src/behaviors/behavior_trackball_pim447.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_trackball_pim447 + +#include +#include +#include + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +struct config { + int mode; + bool momentary; +}; + +static int behavior_trackball_pim447_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *device = device_get_binding(binding->behavior_dev); + const struct config *config = device->config; + + zmk_trackball_pim447_set_mode(config->mode); + + return ZMK_BEHAVIOR_OPAQUE; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *device = device_get_binding(binding->behavior_dev); + const struct config *config = device->config; + + if (config->momentary) { + zmk_trackball_pim447_set_mode(PIM447_TOGGLE); + } + + return ZMK_BEHAVIOR_OPAQUE; +} + +static const struct behavior_driver_api behavior_trackball_pim447_driver_api = { + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released, +}; + +#define PIM447_INST(n) \ + static const struct config config_##n = { \ + .mode = DT_INST_PROP(n, mode), \ + .momentary = DT_INST_PROP(n, momentary) \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, behavior_trackball_pim447_init, device_pm_control_nop, \ + NULL, &config_##n, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_trackball_pim447_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PIM447_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/events/mouse_button_state_changed.c b/app/src/events/mouse_button_state_changed.c new file mode 100644 index 00000000..e1ede414 --- /dev/null +++ b/app/src/events/mouse_button_state_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_button_state_changed); diff --git a/app/src/events/mouse_move_state_changed.c b/app/src/events/mouse_move_state_changed.c new file mode 100644 index 00000000..faf89cb8 --- /dev/null +++ b/app/src/events/mouse_move_state_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_move_state_changed); diff --git a/app/src/events/mouse_scroll_state_changed.c b/app/src/events/mouse_scroll_state_changed.c new file mode 100644 index 00000000..4b4170fe --- /dev/null +++ b/app/src/events/mouse_scroll_state_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_scroll_state_changed); diff --git a/app/src/events/mouse_tick.c b/app/src/events/mouse_tick.c new file mode 100644 index 00000000..0930b9fb --- /dev/null +++ b/app/src/events/mouse_tick.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_mouse_tick); diff --git a/app/src/mouse/Kconfig b/app/src/mouse/Kconfig new file mode 100644 index 00000000..1161b86b --- /dev/null +++ b/app/src/mouse/Kconfig @@ -0,0 +1,38 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig ZMK_MOUSE + bool "Enable ZMK mouse emulation" + default n + +config ZMK_MOUSE_TICK_DURATION + int "Mouse tick duration in ms" + default 8 + +if ZMK_MOUSE + +choice ZMK_MOUSE_WORK_QUEUE + prompt "Work queue selection for mouse events" + default ZMK_MOUSE_WORK_QUEUE_DEDICATED + +config ZMK_MOUSE_WORK_QUEUE_SYSTEM + bool "Use default system work queue for mouse events" + +config ZMK_MOUSE_WORK_QUEUE_DEDICATED + bool "Use dedicated work queue for mouse events" + +endchoice + +if ZMK_MOUSE_WORK_QUEUE_DEDICATED + +config ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE + int "Stack size for dedicated mouse thread/queue" + default 2048 + +config ZMK_MOUSE_DEDICATED_THREAD_PRIORITY + int "Thread priority for dedicated mouse thread/queue" + default 3 + +endif # ZMK_MOUSE_WORK_QUEUE_DEDICATED + +endif diff --git a/app/src/mouse/key_listener.c b/app/src/mouse/key_listener.c new file mode 100644 index 00000000..713d0323 --- /dev/null +++ b/app/src/mouse/key_listener.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct vector2d move_speed = {0}; +static struct vector2d scroll_speed = {0}; +static struct mouse_config move_config = (struct mouse_config){0}; +static struct mouse_config scroll_config = (struct mouse_config){0}; +static int64_t start_time = 0; + +bool equals(const struct mouse_config *one, const struct mouse_config *other) { + return one->delay_ms == other->delay_ms && + one->time_to_max_speed_ms == other->time_to_max_speed_ms && + one->acceleration_exponent == other->acceleration_exponent; +} + +static void clear_mouse_state(struct k_work *work) { + move_speed = (struct vector2d){0}; + scroll_speed = (struct vector2d){0}; + start_time = 0; + zmk_hid_mouse_movement_set(0, 0); + zmk_hid_mouse_scroll_set(0, 0); + LOG_DBG("Clearing state"); +} + +K_WORK_DEFINE(mouse_clear, &clear_mouse_state); + +void mouse_clear_cb(struct k_timer *dummy) { + k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_clear); +} + +static void mouse_tick_timer_handler(struct k_work *work) { + zmk_hid_mouse_movement_set(0, 0); + zmk_hid_mouse_scroll_set(0, 0); + LOG_DBG("Raising mouse tick event"); + ZMK_EVENT_RAISE( + zmk_mouse_tick(move_speed, scroll_speed, move_config, scroll_config, &start_time)); + zmk_endpoints_send_mouse_report(); +} + +K_WORK_DEFINE(mouse_tick, &mouse_tick_timer_handler); + +void mouse_timer_cb(struct k_timer *dummy) { + LOG_DBG("Submitting mouse work to queue"); + k_work_submit_to_queue(zmk_mouse_work_q(), &mouse_tick); +} + +K_TIMER_DEFINE(mouse_timer, mouse_timer_cb, mouse_clear_cb); + +static int mouse_timer_ref_count = 0; + +void mouse_timer_ref() { + if (mouse_timer_ref_count == 0) { + start_time = k_uptime_get(); + k_timer_start(&mouse_timer, K_NO_WAIT, K_MSEC(CONFIG_ZMK_MOUSE_TICK_DURATION)); + } + mouse_timer_ref_count += 1; +} + +void mouse_timer_unref() { + if (mouse_timer_ref_count > 0) { + mouse_timer_ref_count--; + } + if (mouse_timer_ref_count == 0) { + k_timer_stop(&mouse_timer); + } +} + +static void listener_mouse_move_pressed(const struct zmk_mouse_move_state_changed *ev) { + move_speed.x += ev->max_speed.x; + move_speed.y += ev->max_speed.y; + mouse_timer_ref(); +} + +static void listener_mouse_move_released(const struct zmk_mouse_move_state_changed *ev) { + move_speed.x -= ev->max_speed.x; + move_speed.y -= ev->max_speed.y; + mouse_timer_unref(); +} + +static void listener_mouse_scroll_pressed(const struct zmk_mouse_scroll_state_changed *ev) { + scroll_speed.x += ev->max_speed.x; + scroll_speed.y += ev->max_speed.y; + mouse_timer_ref(); +} + +static void listener_mouse_scroll_released(const struct zmk_mouse_scroll_state_changed *ev) { + scroll_speed.x -= ev->max_speed.x; + scroll_speed.y -= ev->max_speed.y; + mouse_timer_unref(); +} + +static void listener_mouse_button_pressed(const struct zmk_mouse_button_state_changed *ev) { + LOG_DBG("buttons: 0x%02X", ev->buttons); + zmk_hid_mouse_buttons_press(ev->buttons); + zmk_endpoints_send_mouse_report(); +} + +static void listener_mouse_button_released(const struct zmk_mouse_button_state_changed *ev) { + LOG_DBG("buttons: 0x%02X", ev->buttons); + zmk_hid_mouse_buttons_release(ev->buttons); + zmk_endpoints_send_mouse_report(); +} + +int mouse_listener(const zmk_event_t *eh) { + const struct zmk_mouse_move_state_changed *mmv_ev = as_zmk_mouse_move_state_changed(eh); + if (mmv_ev) { + if (!equals(&move_config, &(mmv_ev->config))) + move_config = mmv_ev->config; + + if (mmv_ev->state) { + listener_mouse_move_pressed(mmv_ev); + } else { + listener_mouse_move_released(mmv_ev); + } + return 0; + } + const struct zmk_mouse_scroll_state_changed *msc_ev = as_zmk_mouse_scroll_state_changed(eh); + if (msc_ev) { + if (!equals(&scroll_config, &(msc_ev->config))) + scroll_config = msc_ev->config; + if (msc_ev->state) { + listener_mouse_scroll_pressed(msc_ev); + } else { + listener_mouse_scroll_released(msc_ev); + } + return 0; + } + const struct zmk_mouse_button_state_changed *mbt_ev = as_zmk_mouse_button_state_changed(eh); + if (mbt_ev) { + if (mbt_ev->state) { + listener_mouse_button_pressed(mbt_ev); + } else { + listener_mouse_button_released(mbt_ev); + } + return 0; + } + return 0; +} + +ZMK_LISTENER(mouse_listener, mouse_listener); +ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_button_state_changed); +ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_move_state_changed); +ZMK_SUBSCRIPTION(mouse_listener, zmk_mouse_scroll_state_changed); diff --git a/app/src/mouse/main.c b/app/src/mouse/main.c new file mode 100644 index 00000000..49208a76 --- /dev/null +++ b/app/src/mouse/main.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) +K_THREAD_STACK_DEFINE(mouse_work_stack_area, CONFIG_ZMK_MOUSE_DEDICATED_THREAD_STACK_SIZE); +static struct k_work_q mouse_work_q; +#endif + +struct k_work_q *zmk_mouse_work_q() { +#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) + return &mouse_work_q; +#else + return &k_sys_work_q; +#endif +} + +int zmk_mouse_init() { +#if IS_ENABLED(CONFIG_ZMK_MOUSE_WORK_QUEUE_DEDICATED) + k_work_q_start(&mouse_work_q, mouse_work_stack_area, + K_THREAD_STACK_SIZEOF(mouse_work_stack_area), + CONFIG_ZMK_MOUSE_DEDICATED_THREAD_PRIORITY); +#endif + return 0; +} \ No newline at end of file diff --git a/app/src/mouse/tick_listener.c b/app/src/mouse/tick_listener.c new file mode 100644 index 00000000..9c76bd5d --- /dev/null +++ b/app/src/mouse/tick_listener.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include +#include + +#include // CLAMP + +#if CONFIG_MINIMAL_LIBC +static float powf(float base, float exponent) { + // poor man's power implementation rounds the exponent down to the nearest integer. + float power = 1.0f; + for (; exponent >= 1.0f; exponent--) { + power = power * base; + } + return power; +} +#else +#include +#endif + +struct vector2d move_remainder = {0}; +struct vector2d scroll_remainder = {0}; + +static int64_t ms_since_start(int64_t start, int64_t now, int64_t delay) { + int64_t move_duration = now - (start + delay); + // start can be in the future if there's a delay + if (move_duration < 0) { + move_duration = 0; + } + return move_duration; +} + +static float speed(const struct mouse_config *config, float max_speed, int64_t duration_ms) { + // Calculate the speed based on MouseKeysAccel + // See https://en.wikipedia.org/wiki/Mouse_keys + if (duration_ms > config->time_to_max_speed_ms || config->time_to_max_speed_ms == 0 || + config->acceleration_exponent == 0) { + return max_speed; + } + float time_fraction = (float)duration_ms / config->time_to_max_speed_ms; + return max_speed * powf(time_fraction, config->acceleration_exponent); +} + +static void track_remainder(float *move, float *remainder) { + float new_move = *move + *remainder; + *remainder = new_move - (int)new_move; + *move = (int)new_move; +} + +static struct vector2d update_movement(struct vector2d *remainder, + const struct mouse_config *config, struct vector2d max_speed, + int64_t now, int64_t *start_time) { + struct vector2d move = {0}; + if (max_speed.x == 0 && max_speed.y == 0) { + *remainder = (struct vector2d){0}; + return move; + } + + int64_t move_duration = ms_since_start(*start_time, now, config->delay_ms); + move = (struct vector2d){ + .x = speed(config, max_speed.x, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, + .y = speed(config, max_speed.y, move_duration) * CONFIG_ZMK_MOUSE_TICK_DURATION / 1000, + }; + + track_remainder(&(move.x), &(remainder->x)); + track_remainder(&(move.y), &(remainder->y)); + + return move; +} + +static void mouse_tick_handler(const struct zmk_mouse_tick *tick) { + struct vector2d move = update_movement(&move_remainder, &(tick->move_config), tick->max_move, + tick->timestamp, tick->start_time); + zmk_hid_mouse_movement_update((int16_t)CLAMP(move.x, INT16_MIN, INT16_MAX), + (int16_t)CLAMP(move.y, INT16_MIN, INT16_MAX)); + struct vector2d scroll = update_movement(&scroll_remainder, &(tick->scroll_config), + tick->max_scroll, tick->timestamp, tick->start_time); + zmk_hid_mouse_scroll_update((int8_t)CLAMP(scroll.x, INT8_MIN, INT8_MAX), + (int8_t)CLAMP(scroll.y, INT8_MIN, INT8_MAX)); +} + +int zmk_mouse_tick_listener(const zmk_event_t *eh) { + const struct zmk_mouse_tick *tick = as_zmk_mouse_tick(eh); + if (tick) { + mouse_tick_handler(tick); + return 0; + } + return 0; +} + +ZMK_LISTENER(zmk_mouse_tick_listener, zmk_mouse_tick_listener); +ZMK_SUBSCRIPTION(zmk_mouse_tick_listener, zmk_mouse_tick); \ No newline at end of file diff --git a/app/src/mouse/trackball_pim447.c b/app/src/mouse/trackball_pim447.c new file mode 100644 index 00000000..36374be9 --- /dev/null +++ b/app/src/mouse/trackball_pim447.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2021 Cedric VINCENT + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(PIM447, CONFIG_SENSOR_LOG_LEVEL); + +#define MOVE_FACTOR DT_PROP(DT_INST(0, pimoroni_trackball_pim447), move_factor) +#define MOVE_X_INVERT DT_PROP(DT_INST(0, pimoroni_trackball_pim447), invert_move_x) +#define MOVE_Y_INVERT DT_PROP(DT_INST(0, pimoroni_trackball_pim447), invert_move_y) +#define MOVE_X_FACTOR (MOVE_FACTOR * (MOVE_X_INVERT ? -1 : 1)) +#define MOVE_Y_FACTOR (MOVE_FACTOR * (MOVE_Y_INVERT ? -1 : 1)) + +#define SCROLL_DIVISOR DT_PROP(DT_INST(0, pimoroni_trackball_pim447), scroll_divisor) +#define SCROLL_X_INVERT DT_PROP(DT_INST(0, pimoroni_trackball_pim447), invert_scroll_x) +#define SCROLL_Y_INVERT DT_PROP(DT_INST(0, pimoroni_trackball_pim447), invert_scroll_y) +#define SCROLL_X_DIVISOR (SCROLL_DIVISOR * (SCROLL_X_INVERT ? -1 : 1)) +#define SCROLL_Y_DIVISOR (SCROLL_DIVISOR * (SCROLL_Y_INVERT ? 1 : -1)) + +#define BUTTON DT_PROP(DT_INST(0, pimoroni_trackball_pim447), button) +#define SWAP_AXES DT_PROP(DT_INST(0, pimoroni_trackball_pim447), swap_axes) + +static int mode = DT_PROP(DT_INST(0, pimoroni_trackball_pim447), mode); + +void zmk_trackball_pim447_set_mode(int new_mode) +{ + switch (new_mode) { + case PIM447_MOVE: + case PIM447_SCROLL: + mode = new_mode; + break; + + case PIM447_TOGGLE: + mode = mode == PIM447_MOVE + ? PIM447_SCROLL + : PIM447_MOVE; + break; + + default: + break; + } +} + +/* + * It feels more natural and more confortable to convert the speed + * reported by the PIM447 trackball. + */ +static int16_t convert_speed(int32_t value) +{ + bool negative = (value < 0); + + if (negative) { + value = -value; + } + + switch (value) { + case 0: value = 0; break; + case 1: value = 1; break; + case 2: value = 4; break; + case 3: value = 8; break; + case 4: value = 18; break; + case 5: value = 32; break; + case 6: value = 50; break; + case 7: value = 72; break; + case 8: value = 98; break; + default: value = 127; break; + } + + if (negative) { + value = -value; + } + + return value; +} + +static void thread_code(void *p1, void *p2, void *p3) +{ + const struct device *dev; + int result; + + /* PIM447 trackball initialization. */ + + const char *label = DT_LABEL(DT_INST(0, pimoroni_trackball_pim447)); + dev = device_get_binding(label); + if (dev == NULL) { + LOG_ERR("Cannot get TRACKBALL_PIM447 device"); + return; + } + + /* Event loop. */ + + bool button_press_sent = false; + bool button_release_sent = false; + + while (true) { + struct sensor_value pos_dx, pos_dy, pos_dz; + bool send_report = false; + int clear = PIM447_NONE; + + result = sensor_sample_fetch(dev); + if (result < 0) { + LOG_ERR("Failed to fetch TRACKBALL_PIM447 sample"); + return; + } + + result = sensor_channel_get(dev, SENSOR_CHAN_POS_DX, &pos_dx); + if (result < 0) { + LOG_ERR("Failed to get TRACKBALL_PIM447 pos_dx channel value"); + return; + } + + result = sensor_channel_get(dev, SENSOR_CHAN_POS_DY, &pos_dy); + if (result < 0) { + LOG_ERR("Failed to get TRACKBALL_PIM447 pos_dy channel value"); + return; + } + + result = sensor_channel_get(dev, SENSOR_CHAN_POS_DZ, &pos_dz); + if (result < 0) { + LOG_ERR("Failed to get TRACKBALL_PIM447 pos_dz channel value"); + return; + } + + if (pos_dx.val1 != 0 || pos_dy.val1 != 0) { + if (SWAP_AXES) { + int32_t tmp = pos_dx.val1; + pos_dx.val1 = pos_dy.val1; + pos_dy.val1 = tmp; + } + + switch(mode) { + default: + case PIM447_MOVE: { + int dx = convert_speed(pos_dx.val1) * MOVE_X_FACTOR; + int dy = convert_speed(pos_dy.val1) * MOVE_Y_FACTOR; + zmk_hid_mouse_movement_set(dx, dy); + send_report = true; + clear = PIM447_MOVE; + break; + } + + case PIM447_SCROLL: { + int dx = pos_dx.val1 / SCROLL_X_DIVISOR; + int dy = pos_dy.val1 / SCROLL_Y_DIVISOR; + zmk_hid_mouse_scroll_set(dx, dy); + send_report = true; + clear = PIM447_SCROLL; + break; + } + } + } + + if (pos_dz.val1 == 0x80 && button_press_sent == false) { + zmk_hid_mouse_button_press(BUTTON); + button_press_sent = true; + button_release_sent = false; + send_report = true; + } else if (pos_dz.val1 == 0x01 && button_release_sent == false) { + zmk_hid_mouse_button_release(BUTTON); + button_press_sent = false; + button_release_sent = true; + send_report = true; + } + + if (send_report) { + zmk_endpoints_send_mouse_report(); + + switch (clear) { + case PIM447_MOVE: zmk_hid_mouse_movement_set(0, 0); break; + case PIM447_SCROLL: zmk_hid_mouse_scroll_set(0, 0); break; + default: break; + } + } + + k_sleep(K_MSEC(10)); + } +} + +#define STACK_SIZE 1024 + +static K_THREAD_STACK_DEFINE(thread_stack, STACK_SIZE); +static struct k_thread thread; + +int zmk_trackball_pim447_init() +{ + k_thread_create(&thread, thread_stack, STACK_SIZE, thread_code, + NULL, NULL, NULL, K_PRIO_PREEMPT(8), 0, K_NO_WAIT); + return 0; +} + +SYS_INIT(zmk_trackball_pim447_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/tests/mouse-keys/mmv/events.patterns b/app/tests/mouse-keys/mmv/events.patterns new file mode 100644 index 00000000..833100f6 --- /dev/null +++ b/app/tests/mouse-keys/mmv/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/mouse-keys/mmv/keycode_events.snapshot b/app/tests/mouse-keys/mmv/keycode_events.snapshot new file mode 100644 index 00000000..259501ba --- /dev/null +++ b/app/tests/mouse-keys/mmv/keycode_events.snapshot @@ -0,0 +1,2 @@ +pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/mouse-keys/mmv/native_posix.keymap b/app/tests/mouse-keys/mmv/native_posix.keymap new file mode 100644 index 00000000..ecf06601 --- /dev/null +++ b/app/tests/mouse-keys/mmv/native_posix.keymap @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &mmv MOVE_LEFT &none + &none &none + >; + }; + }; +}; + + +&kscan { + events = < + ZMK_MOCK_PRESS(0,0,100) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file