diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5974bd66..02adf09c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ fail_fast: false repos: - repo: https://github.com/pocc/pre-commit-hooks - rev: master + rev: v1.1.1 hooks: - id: clang-format args: - -i - - repo: https://github.com/prettier/prettier - rev: master + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.2.1 hooks: - id: prettier diff --git a/app/Kconfig b/app/Kconfig index aa6143ce..d83561b7 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -419,6 +419,9 @@ config ZMK_WPM bool "Calculate WPM" default n +config SENSOR + default y + module = ZMK module-str = zmk source "subsys/logging/Kconfig.template.log_config" diff --git a/app/boards/arm/bluemicro840/arduino_pro_micro_pins.dtsi b/app/boards/arm/bluemicro840/arduino_pro_micro_pins.dtsi index 940d8913..0dfe3be5 100644 --- a/app/boards/arm/bluemicro840/arduino_pro_micro_pins.dtsi +++ b/app/boards/arm/bluemicro840/arduino_pro_micro_pins.dtsi @@ -42,7 +42,7 @@ , <7 0 &gpio0 24 0> /* D6/A7 D7*/ , <8 0 &gpio0 10 0> /* D8/A8 B4*/ , <9 0 &gpio1 6 0> /* D9/A9 B5*/ - , <10 0 &gpio1 13 0> /* D10/A10 B6*/ + , <10 0 &gpio1 11 0> /* D10/A10 B6*/ ; }; }; diff --git a/app/boards/shields/nibble/Kconfig.defconfig b/app/boards/shields/nibble/Kconfig.defconfig index 22ef1c69..2df1d812 100644 --- a/app/boards/shields/nibble/Kconfig.defconfig +++ b/app/boards/shields/nibble/Kconfig.defconfig @@ -9,7 +9,6 @@ config ZMK_KEYBOARD_NAME config ZMK_USB default y -endif if ZMK_DISPLAY @@ -46,3 +45,5 @@ choice LVGL_COLOR_DEPTH endchoice endif # LVGL + +endif diff --git a/app/boards/shields/nibble/boards/proton_c.conf b/app/boards/shields/nibble/boards/proton_c.conf index ab2b50b6..e69de29b 100644 --- a/app/boards/shields/nibble/boards/proton_c.conf +++ b/app/boards/shields/nibble/boards/proton_c.conf @@ -1 +0,0 @@ -CONFIG_SENSOR=y \ No newline at end of file diff --git a/app/boards/shields/qaz/qaz.keymap b/app/boards/shields/qaz/qaz.keymap index 8ac61f61..a832860e 100644 --- a/app/boards/shields/qaz/qaz.keymap +++ b/app/boards/shields/qaz/qaz.keymap @@ -18,7 +18,7 @@ compatible = "zmk,behavior-hold-tap"; label = "homerow mods"; #binding-cells = <2>; - tapping_term_ms = <225>; + tapping-term-ms = <225>; flavor = "tap-preferred"; bindings = <&kp>, <&kp>; }; diff --git a/app/boards/shields/tg4x/tg4x.keymap b/app/boards/shields/tg4x/tg4x.keymap index d5e20094..84ca3273 100644 --- a/app/boards/shields/tg4x/tg4x.keymap +++ b/app/boards/shields/tg4x/tg4x.keymap @@ -14,7 +14,7 @@ compatible = "zmk,behavior-hold-tap"; label = "Hold Tap"; #binding-cells = <2>; - tapping_term_ms = <200>; + tapping-term-ms = <200>; flavor = "tap-preferred"; bindings = <&kp>, <&kp>; }; diff --git a/app/boards/shields/tidbit/Kconfig.defconfig b/app/boards/shields/tidbit/Kconfig.defconfig index 30cf3c22..177e2675 100644 --- a/app/boards/shields/tidbit/Kconfig.defconfig +++ b/app/boards/shields/tidbit/Kconfig.defconfig @@ -6,7 +6,6 @@ if SHIELD_TIDBIT config ZMK_KEYBOARD_NAME default "tidbit" -endif if ZMK_DISPLAY @@ -43,3 +42,5 @@ choice LVGL_COLOR_DEPTH endchoice endif # LVGL + +endif diff --git a/app/boards/shields/tidbit/boards/proton_c.conf b/app/boards/shields/tidbit/boards/proton_c.conf index ab2b50b6..e69de29b 100644 --- a/app/boards/shields/tidbit/boards/proton_c.conf +++ b/app/boards/shields/tidbit/boards/proton_c.conf @@ -1 +0,0 @@ -CONFIG_SENSOR=y \ No newline at end of file diff --git a/app/drivers/kscan/Kconfig b/app/drivers/kscan/Kconfig index dc3580d5..555b7b98 100644 --- a/app/drivers/kscan/Kconfig +++ b/app/drivers/kscan/Kconfig @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT config ZMK_KSCAN_GPIO_DRIVER - bool "Enable GPIO kscan driver to simulate key presses" + bool "Enable GPIO kscan driver to detect key presses" default y select GPIO diff --git a/app/drivers/kscan/kscan_gpio_direct.c b/app/drivers/kscan/kscan_gpio_direct.c index 0802d0e5..b68e4fc5 100644 --- a/app/drivers/kscan/kscan_gpio_direct.c +++ b/app/drivers/kscan/kscan_gpio_direct.c @@ -83,7 +83,7 @@ static int kscan_gpio_config_interrupts(const struct device *dev, gpio_flags_t f int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags); if (err) { - LOG_ERR("Unable to enable matrix GPIO interrupt"); + LOG_ERR("Unable to enable direct GPIO interrupt"); return err; } } diff --git a/app/drivers/sensor/battery_voltage_divider/battery_voltage_divider.c b/app/drivers/sensor/battery_voltage_divider/battery_voltage_divider.c index 4939461b..c56dab6b 100644 --- a/app/drivers/sensor/battery_voltage_divider/battery_voltage_divider.c +++ b/app/drivers/sensor/battery_voltage_divider/battery_voltage_divider.c @@ -93,7 +93,7 @@ static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan) &val); uint16_t millivolts = val * (uint64_t)drv_cfg->full_ohm / drv_cfg->output_ohm; - LOG_DBG("ADC raw %d ~ %d mV => %d mV\n", drv_data->adc_raw, val, millivolts); + LOG_DBG("ADC raw %d ~ %d mV => %d mV", drv_data->adc_raw, val, millivolts); uint8_t percent = lithium_ion_mv_to_pct(millivolts); LOG_DBG("Percent: %d", percent); diff --git a/app/dts/behaviors/layer_tap.dtsi b/app/dts/behaviors/layer_tap.dtsi index b4539b35..21fd9d56 100644 --- a/app/dts/behaviors/layer_tap.dtsi +++ b/app/dts/behaviors/layer_tap.dtsi @@ -11,7 +11,7 @@ label = "LAYER_TAP"; #binding-cells = <2>; flavor = "tap-preferred"; - tapping_term_ms = <200>; + tapping-term-ms = <200>; bindings = <&mo>, <&kp>; }; }; diff --git a/app/dts/behaviors/mod_tap.dtsi b/app/dts/behaviors/mod_tap.dtsi index 79ce10c2..7a98713c 100644 --- a/app/dts/behaviors/mod_tap.dtsi +++ b/app/dts/behaviors/mod_tap.dtsi @@ -11,7 +11,7 @@ label = "MOD_TAP"; #binding-cells = <2>; flavor = "hold-preferred"; - tapping_term_ms = <200>; + tapping-term-ms = <200>; bindings = <&kp>, <&kp>; }; }; diff --git a/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml b/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml index 56f0cc27..f46b36a4 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml @@ -11,11 +11,15 @@ properties: bindings: type: phandles required: true - tapping_term_ms: + tapping-term-ms: type: int - quick_tap_ms: + tapping_term_ms: # deprecated + type: int + quick-tap-ms: type: int default: -1 + quick_tap_ms: # deprecated + type: int flavor: type: string required: false @@ -24,3 +28,5 @@ properties: - "hold-preferred" - "balanced" - "tap-preferred" + retro-tap: + type: boolean diff --git a/app/dts/bindings/zmk,ext-power-generic.yaml b/app/dts/bindings/zmk,ext-power-generic.yaml index acaf9ccc..ddb4b3de 100644 --- a/app/dts/bindings/zmk,ext-power-generic.yaml +++ b/app/dts/bindings/zmk,ext-power-generic.yaml @@ -15,3 +15,7 @@ properties: label: type: string required: true + init-delay-ms: + type: int + description: Number of milliseconds to delay after initializing driver + required: false diff --git a/app/include/drivers/behavior.h b/app/include/drivers/behavior.h index bc135fd3..2bdffea5 100644 --- a/app/include/drivers/behavior.h +++ b/app/include/drivers/behavior.h @@ -27,6 +27,7 @@ typedef int (*behavior_sensor_keymap_binding_callback_t)(struct zmk_behavior_bin int64_t timestamp); __subsystem struct behavior_driver_api { + behavior_keymap_binding_callback_t binding_convert_central_state_dependent_params; behavior_keymap_binding_callback_t binding_pressed; behavior_keymap_binding_callback_t binding_released; behavior_sensor_keymap_binding_callback_t sensor_binding_triggered; @@ -35,6 +36,30 @@ __subsystem struct behavior_driver_api { * @endcond */ +/** + * @brief Handle the keymap binding which needs to be converted from relative "toggle" to absolute + * "turn on" + * @param binding Pointer to the details so of the binding + * @param event The event that triggered use of the binding + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +__syscall int behavior_keymap_binding_convert_central_state_dependent_params( + struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event); + +static inline int z_impl_behavior_keymap_binding_convert_central_state_dependent_params( + struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; + + if (api->binding_convert_central_state_dependent_params == NULL) { + return 0; + } + + return api->binding_convert_central_state_dependent_params(binding, event); +} + /** * @brief Handle the keymap binding being pressed * @param dev Pointer to the device structure for the driver instance. diff --git a/app/include/dt-bindings/zmk/rgb.h b/app/include/dt-bindings/zmk/rgb.h index 1f7b44ec..95aa5029 100644 --- a/app/include/dt-bindings/zmk/rgb.h +++ b/app/include/dt-bindings/zmk/rgb.h @@ -5,19 +5,23 @@ */ #define RGB_TOG_CMD 0 -#define RGB_HUI_CMD 1 -#define RGB_HUD_CMD 2 -#define RGB_SAI_CMD 3 -#define RGB_SAD_CMD 4 -#define RGB_BRI_CMD 5 -#define RGB_BRD_CMD 6 -#define RGB_SPI_CMD 7 -#define RGB_SPD_CMD 8 -#define RGB_EFF_CMD 9 -#define RGB_EFR_CMD 10 -#define RGB_COLOR_HSB_CMD 11 +#define RGB_ON_CMD 1 +#define RGB_OFF_CMD 2 +#define RGB_HUI_CMD 3 +#define RGB_HUD_CMD 4 +#define RGB_SAI_CMD 5 +#define RGB_SAD_CMD 6 +#define RGB_BRI_CMD 7 +#define RGB_BRD_CMD 8 +#define RGB_SPI_CMD 9 +#define RGB_SPD_CMD 10 +#define RGB_EFF_CMD 11 +#define RGB_EFR_CMD 12 +#define RGB_COLOR_HSB_CMD 13 #define RGB_TOG RGB_TOG_CMD 0 +#define RGB_ON RGB_ON_CMD 0 +#define RGB_OFF RGB_OFF_CMD 0 #define RGB_HUI RGB_HUI_CMD 0 #define RGB_HUD RGB_HUD_CMD 0 #define RGB_SAI RGB_SAI_CMD 0 @@ -28,5 +32,6 @@ #define RGB_SPD RGB_SPD_CMD 0 #define RGB_EFF RGB_EFF_CMD 0 #define RGB_EFR RGB_EFR_CMD 0 -#define RGB_COLOR_HSB(h, s, v) RGB_COLOR_HSB_CMD(((h) << 16) + ((s) << 8) + (v)) +#define RGB_COLOR_HSB_VAL(h, s, v) (((h) << 16) + ((s) << 8) + (v)) +#define RGB_COLOR_HSB(h, s, v) RGB_COLOR_HSB_CMD##(RGB_COLOR_HSB_VAL(h, s, v)) #define RGB_COLOR_HSV RGB_COLOR_HSB \ No newline at end of file diff --git a/app/include/zmk/battery.h b/app/include/zmk/battery.h new file mode 100644 index 00000000..f62219c1 --- /dev/null +++ b/app/include/zmk/battery.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +uint8_t zmk_battery_state_of_charge(); diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index 7fcd42dd..4d452a6f 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -6,10 +6,22 @@ #pragma once +struct zmk_led_hsb { + uint16_t h; + uint8_t s; + uint8_t b; +}; + int zmk_rgb_underglow_toggle(); +int zmk_rgb_underglow_get_state(bool *state); +int zmk_rgb_underglow_on(); +int zmk_rgb_underglow_off(); int zmk_rgb_underglow_cycle_effect(int direction); +struct zmk_led_hsb zmk_rgb_underglow_calc_hue(int direction); +struct zmk_led_hsb zmk_rgb_underglow_calc_sat(int direction); +struct zmk_led_hsb zmk_rgb_underglow_calc_brt(int direction); int zmk_rgb_underglow_change_hue(int direction); int zmk_rgb_underglow_change_sat(int direction); int zmk_rgb_underglow_change_brt(int direction); int zmk_rgb_underglow_change_spd(int direction); -int zmk_rgb_underglow_set_hsb(uint16_t hue, uint8_t saturation, uint8_t brightness); \ No newline at end of file +int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color); \ No newline at end of file diff --git a/app/src/battery.c b/app/src/battery.c index 5a7c57b0..c63008e6 100644 --- a/app/src/battery.c +++ b/app/src/battery.c @@ -15,10 +15,15 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include +#include #include const struct device *battery; +static uint8_t last_state_of_charge = 0; + +uint8_t zmk_battery_state_of_charge() { return last_state_of_charge; } + static int zmk_battery_update(const struct device *battery) { struct sensor_value state_of_charge; @@ -36,17 +41,23 @@ static int zmk_battery_update(const struct device *battery) { return rc; } - LOG_DBG("Setting BAS GATT battery level to %d.", state_of_charge.val1); + if (last_state_of_charge != state_of_charge.val1) { + last_state_of_charge = state_of_charge.val1; - rc = bt_bas_set_battery_level(state_of_charge.val1); + LOG_DBG("Setting BAS GATT battery level to %d.", last_state_of_charge); - if (rc != 0) { - LOG_WRN("Failed to set BAS GATT battery level (err %d)", rc); - return rc; + rc = bt_bas_set_battery_level(last_state_of_charge); + + if (rc != 0) { + LOG_WRN("Failed to set BAS GATT battery level (err %d)", rc); + return rc; + } + + rc = ZMK_EVENT_RAISE(new_zmk_battery_state_changed( + (struct zmk_battery_state_changed){.state_of_charge = last_state_of_charge})); } - return ZMK_EVENT_RAISE(new_zmk_battery_state_changed( - (struct zmk_battery_state_changed){.state_of_charge = state_of_charge.val1})); + return rc; } static void zmk_battery_work(struct k_work *work) { diff --git a/app/src/behaviors/behavior_ext_power.c b/app/src/behaviors/behavior_ext_power.c index 659cde56..3ce1e747 100644 --- a/app/src/behaviors/behavior_ext_power.c +++ b/app/src/behaviors/behavior_ext_power.c @@ -18,6 +18,22 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +static int +on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *ext_power = device_get_binding("EXT_POWER"); + if (ext_power == NULL) { + LOG_ERR("Unable to retrieve ext_power device: %d", binding->param1); + return -EIO; + } + + if (binding->param1 == EXT_POWER_TOGGLE_CMD) { + binding->param1 = ext_power_get(ext_power) > 0 ? EXT_POWER_OFF_CMD : EXT_POWER_ON_CMD; + } + + return 0; +} + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { const struct device *ext_power = device_get_binding("EXT_POWER"); @@ -51,6 +67,8 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, static int behavior_ext_power_init(const struct device *dev) { return 0; }; static const struct behavior_driver_api behavior_ext_power_driver_api = { + .binding_convert_central_state_dependent_params = + on_keymap_binding_convert_central_state_dependent_params, .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, }; diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c index a7185fb0..c83305de 100644 --- a/app/src/behaviors/behavior_hold_tap.c +++ b/app/src/behaviors/behavior_hold_tap.c @@ -31,9 +31,24 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define ZMK_BHV_HOLD_TAP_POSITION_NOT_USED 9999 enum flavor { - ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED = 0, - ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED = 1, - ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED = 2, + FLAVOR_HOLD_PREFERRED, + FLAVOR_BALANCED, + FLAVOR_TAP_PREFERRED, +}; + +enum status { + STATUS_UNDECIDED, + STATUS_TAP, + STATUS_HOLD_INTERRUPT, + STATUS_HOLD_TIMER, +}; + +enum decision_moment { + HT_KEY_UP, + HT_OTHER_KEY_DOWN, + HT_OTHER_KEY_UP, + HT_TIMER_EVENT, + HT_QUICK_TAP, }; struct behavior_hold_tap_config { @@ -42,17 +57,16 @@ struct behavior_hold_tap_config { char *tap_behavior_dev; int quick_tap_ms; enum flavor flavor; + bool retro_tap; }; // this data is specific for each hold-tap struct active_hold_tap { int32_t position; - // todo: move these params into the config->behaviors->tap and uint32_t param_hold; uint32_t param_tap; int64_t timestamp; - bool is_decided; - bool is_hold; + enum status status; const struct behavior_hold_tap_config *config; struct k_delayed_work work; bool work_is_cancelled; @@ -187,8 +201,7 @@ static struct active_hold_tap *store_hold_tap(uint32_t position, uint32_t param_ continue; } active_hold_taps[i].position = position; - active_hold_taps[i].is_decided = false; - active_hold_taps[i].is_hold = false; + active_hold_taps[i].status = STATUS_UNDECIDED; active_hold_taps[i].config = config; active_hold_taps[i].param_hold = param_hold; active_hold_taps[i].param_tap = param_tap; @@ -200,34 +213,24 @@ static struct active_hold_tap *store_hold_tap(uint32_t position, uint32_t param_ static void clear_hold_tap(struct active_hold_tap *hold_tap) { hold_tap->position = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED; - hold_tap->is_decided = false; - hold_tap->is_hold = false; + hold_tap->status = STATUS_UNDECIDED; hold_tap->work_is_cancelled = false; } -enum decision_moment { - HT_KEY_UP = 0, - HT_OTHER_KEY_DOWN = 1, - HT_OTHER_KEY_UP = 2, - HT_TIMER_EVENT = 3, - HT_QUICK_TAP = 4, -}; - static void decide_balanced(struct active_hold_tap *hold_tap, enum decision_moment event) { switch (event) { case HT_KEY_UP: - hold_tap->is_hold = 0; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_TAP; + return; case HT_OTHER_KEY_UP: + hold_tap->status = STATUS_HOLD_INTERRUPT; + return; case HT_TIMER_EVENT: - hold_tap->is_hold = 1; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_HOLD_TIMER; + return; case HT_QUICK_TAP: - hold_tap->is_hold = 0; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_TAP; + return; default: return; } @@ -236,17 +239,14 @@ static void decide_balanced(struct active_hold_tap *hold_tap, enum decision_mome static void decide_tap_preferred(struct active_hold_tap *hold_tap, enum decision_moment event) { switch (event) { case HT_KEY_UP: - hold_tap->is_hold = 0; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_TAP; + return; case HT_TIMER_EVENT: - hold_tap->is_hold = 1; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_HOLD_TIMER; + return; case HT_QUICK_TAP: - hold_tap->is_hold = 0; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_TAP; + return; default: return; } @@ -255,37 +255,113 @@ static void decide_tap_preferred(struct active_hold_tap *hold_tap, enum decision static void decide_hold_preferred(struct active_hold_tap *hold_tap, enum decision_moment event) { switch (event) { case HT_KEY_UP: - hold_tap->is_hold = 0; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_TAP; + return; case HT_OTHER_KEY_DOWN: + hold_tap->status = STATUS_HOLD_INTERRUPT; + return; case HT_TIMER_EVENT: - hold_tap->is_hold = 1; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_HOLD_TIMER; + return; case HT_QUICK_TAP: - hold_tap->is_hold = 0; - hold_tap->is_decided = true; - break; + hold_tap->status = STATUS_TAP; + return; default: return; } } -static inline char *flavor_str(enum flavor flavor) { +static inline const char *flavor_str(enum flavor flavor) { switch (flavor) { - case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED: + case FLAVOR_HOLD_PREFERRED: return "hold-preferred"; - case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED: + case FLAVOR_BALANCED: return "balanced"; - case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED: + case FLAVOR_TAP_PREFERRED: return "tap-preferred"; + default: + return "UNKNOWN FLAVOR"; } - return "UNKNOWN FLAVOR"; } -static void decide_hold_tap(struct active_hold_tap *hold_tap, enum decision_moment event_type) { - if (hold_tap->is_decided) { +static inline const char *status_str(enum status status) { + switch (status) { + case STATUS_UNDECIDED: + return "undecided"; + case STATUS_HOLD_TIMER: + return "hold-timer"; + case STATUS_HOLD_INTERRUPT: + return "hold-interrupt"; + case STATUS_TAP: + return "tap"; + default: + return "UNKNOWN STATUS"; + } +} + +static inline const char *decision_moment_str(enum decision_moment decision_moment) { + switch (decision_moment) { + case HT_KEY_UP: + return "key-up"; + case HT_OTHER_KEY_DOWN: + return "other-key-down"; + case HT_OTHER_KEY_UP: + return "other-key-up"; + case HT_QUICK_TAP: + return "quick-tap"; + case HT_TIMER_EVENT: + return "timer"; + default: + return "UNKNOWN STATUS"; + } +} + +static int press_binding(struct active_hold_tap *hold_tap) { + if (hold_tap->config->retro_tap && hold_tap->status == STATUS_HOLD_TIMER) { + return 0; + } + + struct zmk_behavior_binding_event event = { + .position = hold_tap->position, + .timestamp = hold_tap->timestamp, + }; + + struct zmk_behavior_binding binding = {0}; + if (hold_tap->status == STATUS_HOLD_TIMER || hold_tap->status == STATUS_HOLD_INTERRUPT) { + binding.behavior_dev = hold_tap->config->hold_behavior_dev; + binding.param1 = hold_tap->param_hold; + } else { + binding.behavior_dev = hold_tap->config->tap_behavior_dev; + binding.param1 = hold_tap->param_tap; + store_last_tapped(hold_tap); + } + return behavior_keymap_binding_pressed(&binding, event); +} + +static int release_binding(struct active_hold_tap *hold_tap) { + if (hold_tap->config->retro_tap && hold_tap->status == STATUS_HOLD_TIMER) { + return 0; + } + + struct zmk_behavior_binding_event event = { + .position = hold_tap->position, + .timestamp = hold_tap->timestamp, + }; + + struct zmk_behavior_binding binding = {0}; + if (hold_tap->status == STATUS_HOLD_TIMER || hold_tap->status == STATUS_HOLD_INTERRUPT) { + binding.behavior_dev = hold_tap->config->hold_behavior_dev; + binding.param1 = hold_tap->param_hold; + } else { + binding.behavior_dev = hold_tap->config->tap_behavior_dev; + binding.param1 = hold_tap->param_tap; + } + return behavior_keymap_binding_released(&binding, event); +} + +static void decide_hold_tap(struct active_hold_tap *hold_tap, + enum decision_moment decision_moment) { + if (hold_tap->status != STATUS_UNDECIDED) { return; } @@ -295,42 +371,55 @@ static void decide_hold_tap(struct active_hold_tap *hold_tap, enum decision_mome } switch (hold_tap->config->flavor) { - case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED: - decide_hold_preferred(hold_tap, event_type); - case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED: - decide_balanced(hold_tap, event_type); - case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED: - decide_tap_preferred(hold_tap, event_type); + case FLAVOR_HOLD_PREFERRED: + decide_hold_preferred(hold_tap, decision_moment); + case FLAVOR_BALANCED: + decide_balanced(hold_tap, decision_moment); + case FLAVOR_TAP_PREFERRED: + decide_tap_preferred(hold_tap, decision_moment); } - if (!hold_tap->is_decided) { + if (hold_tap->status == STATUS_UNDECIDED) { return; } - LOG_DBG("%d decided %s (%s event %d)", hold_tap->position, hold_tap->is_hold ? "hold" : "tap", - flavor_str(hold_tap->config->flavor), event_type); + LOG_DBG("%d decided %s (%s decision moment %s)", hold_tap->position, + status_str(hold_tap->status), flavor_str(hold_tap->config->flavor), + decision_moment_str(decision_moment)); undecided_hold_tap = NULL; - - struct zmk_behavior_binding_event event = { - .position = hold_tap->position, - .timestamp = hold_tap->timestamp, - }; - - struct zmk_behavior_binding binding; - if (hold_tap->is_hold) { - binding.behavior_dev = hold_tap->config->hold_behavior_dev; - binding.param1 = hold_tap->param_hold; - binding.param2 = 0; - } else { - binding.behavior_dev = hold_tap->config->tap_behavior_dev; - binding.param1 = hold_tap->param_tap; - binding.param2 = 0; - store_last_tapped(hold_tap); - } - behavior_keymap_binding_pressed(&binding, event); + press_binding(hold_tap); release_captured_events(); } +static void decide_retro_tap(struct active_hold_tap *hold_tap) { + if (!hold_tap->config->retro_tap) { + return; + } + if (hold_tap->status == STATUS_HOLD_TIMER) { + release_binding(hold_tap); + LOG_DBG("%d retro tap", hold_tap->position); + hold_tap->status = STATUS_TAP; + press_binding(hold_tap); + return; + } +} + +static void update_hold_status_for_retro_tap(uint32_t ignore_position) { + for (int i = 0; i < ZMK_BHV_HOLD_TAP_MAX_HELD; i++) { + struct active_hold_tap *hold_tap = &active_hold_taps[i]; + if (hold_tap->position == ignore_position || + hold_tap->position == ZMK_BHV_HOLD_TAP_POSITION_NOT_USED || + hold_tap->config->retro_tap == false) { + continue; + } + if (hold_tap->status == STATUS_HOLD_TIMER) { + LOG_DBG("Update hold tap %d status to hold-interrupt", hold_tap->position); + hold_tap->status = STATUS_HOLD_INTERRUPT; + press_binding(hold_tap); + } + } +} + static int on_hold_tap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { const struct device *dev = device_get_binding(binding->behavior_dev); @@ -362,6 +451,8 @@ static int on_hold_tap_binding_pressed(struct zmk_behavior_binding *binding, int32_t tapping_term_ms_left = (hold_tap->timestamp + cfg->tapping_term_ms) - k_uptime_get(); if (tapping_term_ms_left > 0) { k_delayed_work_submit(&hold_tap->work, K_MSEC(tapping_term_ms_left)); + } else { + decide_hold_tap(hold_tap, HT_TIMER_EVENT); } return ZMK_BEHAVIOR_OPAQUE; @@ -383,25 +474,8 @@ static int on_hold_tap_binding_released(struct zmk_behavior_binding *binding, } decide_hold_tap(hold_tap, HT_KEY_UP); - - // todo: set up the binding and data items inside of the - // active_hhold_tap->config->behaviors->tap.behavior_dev;old_tap struct - struct zmk_behavior_binding_event sub_behavior_data = { - .position = hold_tap->position, - .timestamp = hold_tap->timestamp, - }; - - struct zmk_behavior_binding sub_behavior_binding; - if (hold_tap->is_hold) { - sub_behavior_binding.behavior_dev = hold_tap->config->hold_behavior_dev; - sub_behavior_binding.param1 = hold_tap->param_hold; - sub_behavior_binding.param2 = 0; - } else { - sub_behavior_binding.behavior_dev = hold_tap->config->tap_behavior_dev; - sub_behavior_binding.param1 = hold_tap->param_tap; - sub_behavior_binding.param2 = 0; - } - behavior_keymap_binding_released(&sub_behavior_binding, sub_behavior_data); + decide_retro_tap(hold_tap); + release_binding(hold_tap); if (work_cancel_result == -EINPROGRESS) { // let the timer handler clean up @@ -424,6 +498,8 @@ static const struct behavior_driver_api behavior_hold_tap_driver_api = { static int position_state_changed_listener(const zmk_event_t *eh) { struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh); + update_hold_status_for_retro_tap(ev->position); + if (undecided_hold_tap == NULL) { LOG_DBG("%d bubble (no undecided hold_tap active)", ev->position); return ZMK_EV_EVENT_BUBBLE; @@ -531,6 +607,7 @@ static struct behavior_hold_tap_data behavior_hold_tap_data; .tap_behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(n, bindings, 1)), \ .quick_tap_ms = DT_INST_PROP(n, quick_tap_ms), \ .flavor = DT_ENUM_IDX(DT_DRV_INST(n), flavor), \ + .retro_tap = DT_INST_PROP(n, retro_tap), \ }; \ DEVICE_AND_API_INIT(behavior_hold_tap_##n, DT_INST_LABEL(n), behavior_hold_tap_init, \ &behavior_hold_tap_data, &behavior_hold_tap_config_##n, APPLICATION, \ diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index eb4680e1..79592cac 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -20,11 +20,81 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static int behavior_rgb_underglow_init(const struct device *dev) { return 0; } +static int +on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + switch (binding->param1) { + case RGB_TOG_CMD: { + bool state; + int err = zmk_rgb_underglow_get_state(&state); + if (err) { + LOG_ERR("Failed to get RGB underglow state (err %d)", err); + return err; + } + + binding->param1 = state ? RGB_OFF_CMD : RGB_ON_CMD; + break; + } + case RGB_BRI_CMD: { + struct zmk_led_hsb color = zmk_rgb_underglow_calc_brt(1); + + binding->param1 = RGB_COLOR_HSB_CMD; + binding->param2 = RGB_COLOR_HSB_VAL(color.h, color.s, color.b); + break; + } + case RGB_BRD_CMD: { + struct zmk_led_hsb color = zmk_rgb_underglow_calc_brt(-1); + + binding->param1 = RGB_COLOR_HSB_CMD; + binding->param2 = RGB_COLOR_HSB_VAL(color.h, color.s, color.b); + break; + } + case RGB_HUI_CMD: { + struct zmk_led_hsb color = zmk_rgb_underglow_calc_hue(1); + + binding->param1 = RGB_COLOR_HSB_CMD; + binding->param2 = RGB_COLOR_HSB_VAL(color.h, color.s, color.b); + break; + } + case RGB_HUD_CMD: { + struct zmk_led_hsb color = zmk_rgb_underglow_calc_hue(-1); + + binding->param1 = RGB_COLOR_HSB_CMD; + binding->param2 = RGB_COLOR_HSB_VAL(color.h, color.s, color.b); + break; + } + case RGB_SAI_CMD: { + struct zmk_led_hsb color = zmk_rgb_underglow_calc_sat(1); + + binding->param1 = RGB_COLOR_HSB_CMD; + binding->param2 = RGB_COLOR_HSB_VAL(color.h, color.s, color.b); + break; + } + case RGB_SAD_CMD: { + struct zmk_led_hsb color = zmk_rgb_underglow_calc_sat(-1); + + binding->param1 = RGB_COLOR_HSB_CMD; + binding->param2 = RGB_COLOR_HSB_VAL(color.h, color.s, color.b); + break; + } + default: + return 0; + } + + LOG_DBG("RGB relative convert to absolute (%d/%d)", binding->param1, binding->param2); + + return 0; +}; + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { switch (binding->param1) { case RGB_TOG_CMD: return zmk_rgb_underglow_toggle(); + case RGB_ON_CMD: + return zmk_rgb_underglow_on(); + case RGB_OFF_CMD: + return zmk_rgb_underglow_off(); case RGB_HUI_CMD: return zmk_rgb_underglow_change_hue(1); case RGB_HUD_CMD: @@ -46,8 +116,9 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, case RGB_EFR_CMD: return zmk_rgb_underglow_cycle_effect(-1); case RGB_COLOR_HSB_CMD: - return zmk_rgb_underglow_set_hsb((binding->param2 >> 16) & 0xFFFF, - (binding->param2 >> 8) & 0xFF, binding->param2 & 0xFF); + return zmk_rgb_underglow_set_hsb((struct zmk_led_hsb){.h = (binding->param2 >> 16) & 0xFFFF, + .s = (binding->param2 >> 8) & 0xFF, + .b = binding->param2 & 0xFF}); } return -ENOTSUP; @@ -59,6 +130,8 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, } static const struct behavior_driver_api behavior_rgb_underglow_driver_api = { + .binding_convert_central_state_dependent_params = + on_keymap_binding_convert_central_state_dependent_params, .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, }; diff --git a/app/src/combo.c b/app/src/combo.c index b50a0f61..72535b29 100644 --- a/app/src/combo.c +++ b/app/src/combo.c @@ -192,7 +192,7 @@ static inline bool candidate_is_completely_pressed(struct combo_cfg *candidate) return pressed_keys[candidate->key_position_len - 1] != NULL; } -static void cleanup(); +static int cleanup(); static int filter_timed_out_candidates(int64_t timestamp) { int num_candidates = 0; @@ -224,7 +224,7 @@ static int clear_candidates() { } static int capture_pressed_key(const zmk_event_t *ev) { - for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) { + for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO; i++) { if (pressed_keys[i] != NULL) { continue; } @@ -236,23 +236,25 @@ static int capture_pressed_key(const zmk_event_t *ev) { const struct zmk_listener zmk_listener_combo; -static void release_pressed_keys() { - // release the first key that was pressed - if (pressed_keys[0] == NULL) { - return; - } - ZMK_EVENT_RELEASE(pressed_keys[0]) - pressed_keys[0] = NULL; - - // reprocess events (see tests/combo/fully-overlapping-combos-3 for why this is needed) - for (int i = 1; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) { - if (pressed_keys[i] == NULL) { - return; - } +static int release_pressed_keys() { + for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO; i++) { const zmk_event_t *captured_event = pressed_keys[i]; + if (pressed_keys[i] == NULL) { + return i; + } pressed_keys[i] = NULL; - ZMK_EVENT_RAISE(captured_event); + if (i == 0) { + LOG_DBG("combo: releasing position event %d", + as_zmk_position_state_changed(captured_event)->position); + ZMK_EVENT_RELEASE(captured_event) + } else { + // reprocess events (see tests/combo/fully-overlapping-combos-3 for why this is needed) + LOG_DBG("combo: reraising position event %d", + as_zmk_position_state_changed(captured_event)->position); + ZMK_EVENT_RAISE(captured_event); + } } + return CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO; } static inline int press_combo_behavior(struct combo_cfg *combo, int32_t timestamp) { @@ -360,14 +362,14 @@ static bool release_combo_key(int32_t position, int64_t timestamp) { return false; } -static void cleanup() { +static int cleanup() { k_delayed_work_cancel(&timeout_task); clear_candidates(); if (fully_pressed_combo != NULL) { activate_combo(fully_pressed_combo); fully_pressed_combo = NULL; } - release_pressed_keys(); + return release_pressed_keys(); } static void update_timeout_task() { @@ -399,6 +401,7 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_ update_timeout_task(); struct combo_cfg *candidate_combo = candidates[0].combo; + LOG_DBG("combo: capturing position event %d", data->position); int ret = capture_pressed_key(ev); switch (num_candidates) { case 0: @@ -418,13 +421,18 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_ } } -static int position_state_up(struct zmk_position_state_changed *ev) { - cleanup(); - if (release_combo_key(ev->position, ev->timestamp)) { +static int position_state_up(const zmk_event_t *ev, struct zmk_position_state_changed *data) { + int released_keys = cleanup(); + if (release_combo_key(data->position, data->timestamp)) { return ZMK_EV_EVENT_HANDLED; - } else { - return 0; } + if (released_keys > 1) { + // The second and further key down events are re-raised. To preserve + // correct order for e.g. hold-taps, reraise the key up event too. + ZMK_EVENT_RAISE(ev); + return ZMK_EV_EVENT_CAPTURED; + } + return 0; } static void combo_timeout_handler(struct k_work *item) { @@ -447,7 +455,7 @@ static int position_state_changed_listener(const zmk_event_t *ev) { if (data->state) { // keydown return position_state_down(ev, data); } else { // keyup - return position_state_up(data); + return position_state_up(ev, data); } } diff --git a/app/src/display/main.c b/app/src/display/main.c index 55dd1ce2..b8b4bf5b 100644 --- a/app/src/display/main.c +++ b/app/src/display/main.c @@ -38,12 +38,20 @@ void display_timer_cb() { k_work_submit(&display_tick_work); } K_TIMER_DEFINE(display_timer, display_timer_cb, NULL); static void start_display_updates() { + if (display == NULL) { + return; + } + display_blanking_off(display); k_timer_start(&display_timer, K_MSEC(10), K_MSEC(10)); } static void stop_display_updates() { + if (display == NULL) { + return; + } + display_blanking_on(display); k_timer_stop(&display_timer); diff --git a/app/src/event_manager.c b/app/src/event_manager.c index 0399ca80..eef5d839 100644 --- a/app/src/event_manager.c +++ b/app/src/event_manager.c @@ -22,24 +22,25 @@ int zmk_event_manager_handle_from(zmk_event_t *event, uint8_t start_index) { uint8_t len = __event_subscriptions_end - __event_subscriptions_start; for (int i = start_index; i < len; i++) { struct zmk_event_subscription *ev_sub = __event_subscriptions_start + i; - if (ev_sub->event_type == event->event) { - ret = ev_sub->listener->callback(event); - if (ret < 0) { - LOG_DBG("Listener returned an error: %d", ret); - goto release; - } else if (ret > 0) { - switch (ret) { - case ZMK_EV_EVENT_HANDLED: - LOG_DBG("Listener handled the event"); - ret = 0; - goto release; - case ZMK_EV_EVENT_CAPTURED: - LOG_DBG("Listener captured the event"); - event->last_listener_index = i; - // Listeners are expected to free events they capture - return 0; - } - } + if (ev_sub->event_type != event->event) { + continue; + } + ret = ev_sub->listener->callback(event); + switch (ret) { + case ZMK_EV_EVENT_BUBBLE: + continue; + case ZMK_EV_EVENT_HANDLED: + LOG_DBG("Listener handled the event"); + ret = 0; + goto release; + case ZMK_EV_EVENT_CAPTURED: + LOG_DBG("Listener captured the event"); + event->last_listener_index = i; + // Listeners are expected to free events they capture + return 0; + default: + LOG_DBG("Listener returned an error: %d", ret); + goto release; } } diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c index d8e7844e..1f2e0d79 100644 --- a/app/src/ext_power_generic.c +++ b/app/src/ext_power_generic.c @@ -23,6 +23,7 @@ struct ext_power_generic_config { const char *label; const uint8_t pin; const uint8_t flags; + const uint16_t init_delay_ms; }; struct ext_power_generic_data { @@ -171,6 +172,10 @@ static int ext_power_generic_init(const struct device *dev) { ext_power_enable(dev); #endif + if (config->init_delay_ms) { + k_msleep(config->init_delay_ms); + } + return 0; } @@ -210,7 +215,8 @@ static int ext_power_generic_pm_control(const struct device *dev, uint32_t ctrl_ static const struct ext_power_generic_config config = { .label = DT_INST_GPIO_LABEL(0, control_gpios), .pin = DT_INST_GPIO_PIN(0, control_gpios), - .flags = DT_INST_GPIO_FLAGS(0, control_gpios)}; + .flags = DT_INST_GPIO_FLAGS(0, control_gpios), + .init_delay_ms = DT_INST_PROP_OR(0, init_delay_ms, 0)}; static struct ext_power_generic_data data = { .status = false, @@ -223,13 +229,15 @@ static const struct ext_power_api api = {.enable = ext_power_generic_enable, .disable = ext_power_generic_disable, .get = ext_power_generic_get}; +#define ZMK_EXT_POWER_INIT_PRIORITY 81 + #ifdef CONFIG_DEVICE_POWER_MANAGEMENT DEVICE_DEFINE(ext_power_generic, DT_INST_LABEL(0), ext_power_generic_init, - &ext_power_generic_pm_control, &data, &config, APPLICATION, - CONFIG_APPLICATION_INIT_PRIORITY, &api); + &ext_power_generic_pm_control, &data, &config, POST_KERNEL, + ZMK_EXT_POWER_INIT_PRIORITY, &api); #else DEVICE_AND_API_INIT(ext_power_generic, DT_INST_LABEL(0), ext_power_generic_init, &data, &config, - APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, &api); + POST_KERNEL, ZMK_EXT_POWER_INIT_PRIORITY, &api); #endif /* CONFIG_DEVICE_POWER_MANAGEMENT */ #endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/keymap.c b/app/src/keymap.c index 75ec6bf4..1643f647 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -153,7 +153,9 @@ const char *zmk_keymap_layer_label(uint8_t layer) { } int zmk_keymap_apply_position_state(int layer, uint32_t position, bool pressed, int64_t timestamp) { - struct zmk_behavior_binding *binding = &zmk_keymap[layer][position]; + // We want to make a copy of this, since it may be converted from + // relative to absolute before being invoked + struct zmk_behavior_binding binding = zmk_keymap[layer][position]; const struct device *behavior; struct zmk_behavior_binding_event event = { .layer = layer, @@ -162,19 +164,25 @@ int zmk_keymap_apply_position_state(int layer, uint32_t position, bool pressed, }; LOG_DBG("layer: %d position: %d, binding name: %s", layer, position, - log_strdup(binding->behavior_dev)); + log_strdup(binding.behavior_dev)); - behavior = device_get_binding(binding->behavior_dev); + behavior = device_get_binding(binding.behavior_dev); if (!behavior) { LOG_DBG("No behavior assigned to %d on layer %d", position, layer); return 1; } + int err = behavior_keymap_binding_convert_central_state_dependent_params(&binding, event); + if (err) { + LOG_ERR("Failed to convert relative to absolute behavior binding (err %d)", err); + return err; + } + if (pressed) { - return behavior_keymap_binding_pressed(binding, event); + return behavior_keymap_binding_pressed(&binding, event); } else { - return behavior_keymap_binding_released(binding, event); + return behavior_keymap_binding_released(&binding, event); } } diff --git a/app/src/kscan.c b/app/src/kscan.c index 0c128b2d..3c9c201e 100644 --- a/app/src/kscan.c +++ b/app/src/kscan.c @@ -47,7 +47,7 @@ void zmk_kscan_process_msgq(struct k_work *item) { while (k_msgq_get(&zmk_kscan_msgq, &ev, K_NO_WAIT) == 0) { bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED); uint32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column); - LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s\n", ev.row, ev.column, position, + LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position, (pressed ? "true" : "false")); ZMK_EVENT_RAISE(new_zmk_position_state_changed((struct zmk_position_state_changed){ .state = pressed, .position = position, .timestamp = k_uptime_get()})); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index f3bd7a9d..5f38aba7 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -17,11 +17,17 @@ #include #include +#include + LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define STRIP_LABEL DT_LABEL(DT_CHOSEN(zmk_underglow)) #define STRIP_NUM_PIXELS DT_PROP(DT_CHOSEN(zmk_underglow), chain_length) +#define HUE_MAX 360 +#define SAT_MAX 100 +#define BRT_MAX 100 + enum rgb_underglow_effect { UNDERGLOW_EFFECT_SOLID, UNDERGLOW_EFFECT_BREATHE, @@ -30,16 +36,8 @@ enum rgb_underglow_effect { UNDERGLOW_EFFECT_NUMBER // Used to track number of underglow effects }; -struct led_hsb { - uint16_t h; - uint8_t s; - uint8_t b; -}; - struct rgb_underglow_state { - uint16_t hue; - uint8_t saturation; - uint8_t brightness; + struct zmk_led_hsb color; uint8_t animation_speed; uint8_t current_effect; uint16_t animation_step; @@ -56,13 +54,13 @@ static struct rgb_underglow_state state; static const struct device *ext_power; #endif -static struct led_rgb hsb_to_rgb(struct led_hsb hsb) { +static struct led_rgb hsb_to_rgb(struct zmk_led_hsb hsb) { double r, g, b; uint8_t i = hsb.h / 60; - double v = hsb.b / 100.0; - double s = hsb.s / 100.0; - double f = hsb.h / 360.0 * 6 - i; + double v = hsb.b / ((float)BRT_MAX); + double s = hsb.s / ((float)SAT_MAX); + double f = hsb.h / ((float)HUE_MAX) * 6 - i; double p = v * (1 - s); double q = v * (1 - f * s); double t = v * (1 - (1 - f) * s); @@ -105,33 +103,16 @@ static struct led_rgb hsb_to_rgb(struct led_hsb hsb) { return rgb; } -static void zmk_rgb_underglow_off() { - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; - } - - led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); -} - static void zmk_rgb_underglow_effect_solid() { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - int hue = state.hue; - int sat = state.saturation; - int brt = state.brightness; - - struct led_hsb hsb = {hue, sat, brt}; - - pixels[i] = hsb_to_rgb(hsb); + pixels[i] = hsb_to_rgb(state.color); } } static void zmk_rgb_underglow_effect_breathe() { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - int hue = state.hue; - int sat = state.saturation; - int brt = abs(state.animation_step - 1200) / 12; - - struct led_hsb hsb = {hue, sat, brt}; + struct zmk_led_hsb hsb = state.color; + hsb.b = abs(state.animation_step - 1200) / 12; pixels[i] = hsb_to_rgb(hsb); } @@ -145,32 +126,26 @@ static void zmk_rgb_underglow_effect_breathe() { static void zmk_rgb_underglow_effect_spectrum() { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - int hue = state.animation_step; - int sat = state.saturation; - int brt = state.brightness; - - struct led_hsb hsb = {hue, sat, brt}; + struct zmk_led_hsb hsb = state.color; + hsb.h = state.animation_step; pixels[i] = hsb_to_rgb(hsb); } state.animation_step += state.animation_speed; - state.animation_step = state.animation_step % 360; + state.animation_step = state.animation_step % HUE_MAX; } static void zmk_rgb_underglow_effect_swirl() { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - int hue = (360 / STRIP_NUM_PIXELS * i + state.animation_step) % 360; - int sat = state.saturation; - int brt = state.brightness; - - struct led_hsb hsb = {hue, sat, brt}; + struct zmk_led_hsb hsb = state.color; + hsb.h = (HUE_MAX / STRIP_NUM_PIXELS * i + state.animation_step) % HUE_MAX; pixels[i] = hsb_to_rgb(hsb); } state.animation_step += state.animation_speed * 2; - state.animation_step = state.animation_step % 360; + state.animation_step = state.animation_step % HUE_MAX; } static void zmk_rgb_underglow_tick(struct k_work *work) { @@ -196,10 +171,6 @@ K_WORK_DEFINE(underglow_work, zmk_rgb_underglow_tick); static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) { if (!state.on) { - zmk_rgb_underglow_off(); - - k_timer_stop(timer); - return; } @@ -255,9 +226,11 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { #endif state = (struct rgb_underglow_state){ - hue : CONFIG_ZMK_RGB_UNDERGLOW_HUE_START, - saturation : CONFIG_ZMK_RGB_UNDERGLOW_SAT_START, - brightness : CONFIG_ZMK_RGB_UNDERGLOW_BRT_START, + color : { + h : CONFIG_ZMK_RGB_UNDERGLOW_HUE_START, + s : CONFIG_ZMK_RGB_UNDERGLOW_SAT_START, + b : CONFIG_ZMK_RGB_UNDERGLOW_BRT_START, + }, animation_speed : CONFIG_ZMK_RGB_UNDERGLOW_SPD_START, current_effect : CONFIG_ZMK_RGB_UNDERGLOW_EFF_START, animation_step : 0, @@ -292,20 +265,65 @@ int zmk_rgb_underglow_save_state() { #endif } +int zmk_rgb_underglow_get_state(bool *on_off) { + if (!led_strip) + return -ENODEV; + + *on_off = state.on; + return 0; +} + +int zmk_rgb_underglow_on() { + if (!led_strip) + return -ENODEV; + +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) + if (ext_power != NULL) { + int rc = ext_power_enable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to enable EXT_POWER: %d", rc); + } + } +#endif + + state.on = true; + state.animation_step = 0; + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + + return zmk_rgb_underglow_save_state(); +} + +int zmk_rgb_underglow_off() { + if (!led_strip) + return -ENODEV; + +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) + if (ext_power != NULL) { + int rc = ext_power_disable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to disable EXT_POWER: %d", rc); + } + } +#endif + + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } + + led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + + k_timer_stop(&underglow_tick); + state.on = false; + + return zmk_rgb_underglow_save_state(); +} + int zmk_rgb_underglow_cycle_effect(int direction) { if (!led_strip) return -ENODEV; - if (state.current_effect == 0 && direction < 0) { - state.current_effect = UNDERGLOW_EFFECT_NUMBER - 1; - return 0; - } - - state.current_effect += direction; - - if (state.current_effect >= UNDERGLOW_EFFECT_NUMBER) { - state.current_effect = 0; - } + state.current_effect += UNDERGLOW_EFFECT_NUMBER + direction; + state.current_effect %= UNDERGLOW_EFFECT_NUMBER; state.animation_step = 0; @@ -313,63 +331,61 @@ int zmk_rgb_underglow_cycle_effect(int direction) { } int zmk_rgb_underglow_toggle() { - if (!led_strip) - return -ENODEV; - - state.on = !state.on; - -#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - if (ext_power != NULL) { - int rc; - - if (state.on) { - rc = ext_power_enable(ext_power); - } else { - rc = ext_power_disable(ext_power); - } - - if (rc != 0) { - LOG_ERR("Unable to toggle EXT_POWER: %d", rc); - } - } -#endif - - if (state.on) { - state.animation_step = 0; - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); - } else { - zmk_rgb_underglow_off(); - - k_timer_stop(&underglow_tick); - } - - return zmk_rgb_underglow_save_state(); + return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on(); } -int zmk_rgb_underglow_set_hsb(uint16_t hue, uint8_t saturation, uint8_t brightness) { - if (hue > 360 || saturation > 100 || brightness > 100) { +int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color) { + if (color.h > HUE_MAX || color.s > SAT_MAX || color.b > BRT_MAX) { return -ENOTSUP; } - state.hue = hue; - state.saturation = saturation; - state.brightness = brightness; + state.color = color; return 0; } +struct zmk_led_hsb zmk_rgb_underglow_calc_hue(int direction) { + struct zmk_led_hsb color = state.color; + + color.h += HUE_MAX + (direction * CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP); + color.h %= HUE_MAX; + + return color; +} + +struct zmk_led_hsb zmk_rgb_underglow_calc_sat(int direction) { + struct zmk_led_hsb color = state.color; + + int s = color.s + (direction * CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP); + if (s < 0) { + s = 0; + } else if (s > SAT_MAX) { + s = SAT_MAX; + } + color.s = s; + + return color; +} + +struct zmk_led_hsb zmk_rgb_underglow_calc_brt(int direction) { + struct zmk_led_hsb color = state.color; + + int b = color.b + (direction * CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP); + if (b < 0) { + b = 0; + } else if (b > BRT_MAX) { + b = BRT_MAX; + } + color.b = b; + + return color; +} + int zmk_rgb_underglow_change_hue(int direction) { if (!led_strip) return -ENODEV; - if (state.hue == 0 && direction < 0) { - state.hue = 360 - CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP; - return 0; - } - - state.hue += direction * CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP; - - state.hue = state.hue % 360; + state.color = zmk_rgb_underglow_calc_hue(direction); return zmk_rgb_underglow_save_state(); } @@ -378,15 +394,7 @@ int zmk_rgb_underglow_change_sat(int direction) { if (!led_strip) return -ENODEV; - if (state.saturation == 0 && direction < 0) { - return 0; - } - - state.saturation += direction * CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP; - - if (state.saturation > 100) { - state.saturation = 100; - } + state.color = zmk_rgb_underglow_calc_sat(direction); return zmk_rgb_underglow_save_state(); } @@ -395,15 +403,7 @@ int zmk_rgb_underglow_change_brt(int direction) { if (!led_strip) return -ENODEV; - if (state.brightness == 0 && direction < 0) { - return 0; - } - - state.brightness += direction * CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP; - - if (state.brightness > 100) { - state.brightness = 100; - } + state.color = zmk_rgb_underglow_calc_brt(direction); return zmk_rgb_underglow_save_state(); } diff --git a/app/tests/combo/combos-and-holdtaps-0/events.patterns b/app/tests/combo/combos-and-holdtaps-0/events.patterns index b90d7863..b1342af4 100644 --- a/app/tests/combo/combos-and-holdtaps-0/events.patterns +++ b/app/tests/combo/combos-and-holdtaps-0/events.patterns @@ -1,2 +1 @@ s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file diff --git a/app/tests/combo/combos-and-holdtaps-1/events.patterns b/app/tests/combo/combos-and-holdtaps-1/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/combos-and-holdtaps-1/events.patterns +++ b/app/tests/combo/combos-and-holdtaps-1/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/combos-and-holdtaps-2/events.patterns b/app/tests/combo/combos-and-holdtaps-2/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/combos-and-holdtaps-2/events.patterns +++ b/app/tests/combo/combos-and-holdtaps-2/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/layer-filter-0/events.patterns b/app/tests/combo/layer-filter-0/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/layer-filter-0/events.patterns +++ b/app/tests/combo/layer-filter-0/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/layer-filter-1/events.patterns b/app/tests/combo/layer-filter-1/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/layer-filter-1/events.patterns +++ b/app/tests/combo/layer-filter-1/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/overlapping-combos-0/events.patterns b/app/tests/combo/overlapping-combos-0/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/overlapping-combos-0/events.patterns +++ b/app/tests/combo/overlapping-combos-0/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/overlapping-combos-1/events.patterns b/app/tests/combo/overlapping-combos-1/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/overlapping-combos-1/events.patterns +++ b/app/tests/combo/overlapping-combos-1/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/overlapping-combos-2/events.patterns b/app/tests/combo/overlapping-combos-2/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/overlapping-combos-2/events.patterns +++ b/app/tests/combo/overlapping-combos-2/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/overlapping-combos-3/events.patterns b/app/tests/combo/overlapping-combos-3/events.patterns index b90d7863..833100f6 100644 --- a/app/tests/combo/overlapping-combos-3/events.patterns +++ b/app/tests/combo/overlapping-combos-3/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo//p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/press-release-long-combo/events.patterns b/app/tests/combo/press-release-long-combo/events.patterns new file mode 100644 index 00000000..b1342af4 --- /dev/null +++ b/app/tests/combo/press-release-long-combo/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode_//p diff --git a/app/tests/combo/press-release-long-combo/keycode_events.snapshot b/app/tests/combo/press-release-long-combo/keycode_events.snapshot new file mode 100644 index 00000000..e7c0cb12 --- /dev/null +++ b/app/tests/combo/press-release-long-combo/keycode_events.snapshot @@ -0,0 +1,4 @@ +pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/combo/press-release-long-combo/native_posix.keymap b/app/tests/combo/press-release-long-combo/native_posix.keymap new file mode 100644 index 00000000..68736d8f --- /dev/null +++ b/app/tests/combo/press-release-long-combo/native_posix.keymap @@ -0,0 +1,35 @@ +#include +#include +#include + +/ { + combos { + compatible = "zmk,combos"; + combo_one { + timeout-ms = <80>; + key-positions = <0 1 2 3>; + bindings = <&kp Z>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &kp A &kp B + &kp C &kp D + >; + }; + }; +}; + +&kscan { + events = < + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_RELEASE(0,1,100) + ZMK_MOCK_RELEASE(1,1,100) + >; +}; \ No newline at end of file diff --git a/app/tests/combo/press1-press2-release1-release2/events.patterns b/app/tests/combo/press1-press2-release1-release2/events.patterns index 5f3e4cf7..833100f6 100644 --- a/app/tests/combo/press1-press2-release1-release2/events.patterns +++ b/app/tests/combo/press1-press2-release1-release2/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo/combo/p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/press1-press2-release2-release1/events.patterns b/app/tests/combo/press1-press2-release2-release1/events.patterns index b54b66b6..b1342af4 100644 --- a/app/tests/combo/press1-press2-release2-release1/events.patterns +++ b/app/tests/combo/press1-press2-release2-release1/events.patterns @@ -1,2 +1 @@ s/.*hid_listener_keycode_//p -s/.*combo/combo/p diff --git a/app/tests/combo/press1-release1-press2-release2/events.patterns b/app/tests/combo/press1-release1-press2-release2/events.patterns index 5f3e4cf7..833100f6 100644 --- a/app/tests/combo/press1-release1-press2-release2/events.patterns +++ b/app/tests/combo/press1-release1-press2-release2/events.patterns @@ -1,2 +1 @@ -s/.*hid_listener_keycode_//p -s/.*combo/combo/p \ No newline at end of file +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/hold-tap/balanced/1-dn-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/1-dn-up/keycode_events.snapshot index 41344422..76a8ee5f 100644 --- a/app/tests/hold-tap/balanced/1-dn-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/1-dn-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (balanced event 0) +ht_decide: 0 decided tap (balanced decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/balanced/2-dn-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/2-dn-timer-up/keycode_events.snapshot index f83b4086..926174b4 100644 --- a/app/tests/hold-tap/balanced/2-dn-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/2-dn-timer-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (balanced event 3) +ht_decide: 0 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/keycode_events.snapshot index ca744aac..22c24df2 100644 --- a/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/3a-moddn-dn-modup-up/keycode_events.snapshot @@ -1,6 +1,6 @@ kp_pressed: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (balanced event 0) +ht_decide: 0 decided tap (balanced decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/keycode_events.snapshot index c2bddc26..0c7ea497 100644 --- a/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/3b-moddn-dn-modup-timer-up/keycode_events.snapshot @@ -1,6 +1,6 @@ kp_pressed: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (balanced event 3) +ht_decide: 0 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/keycode_events.snapshot index 6bef3460..38bceb97 100644 --- a/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/3c-kcdn-dn-kcup-up/keycode_events.snapshot @@ -1,7 +1,7 @@ kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 -ht_decide: 0 decided tap (balanced event 0) +ht_decide: 0 decided tap (balanced decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot index 5354ff74..afbb52cf 100644 --- a/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot @@ -1,7 +1,7 @@ kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 -ht_decide: 0 decided hold (balanced event 3) +ht_decide: 0 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/keycode_events.snapshot index 10380651..9c91aa5d 100644 --- a/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/4a-dn-htdn-timer-htup-up/keycode_events.snapshot @@ -1,8 +1,8 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (balanced event 3) +ht_decide: 0 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 1 new undecided hold_tap -ht_decide: 1 decided tap (balanced event 0) +ht_decide: 1 decided tap (balanced decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 1 cleaning up hold-tap diff --git a/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot index 9d5a9e83..242b31f2 100644 --- a/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (balanced event 3) +ht_decide: 0 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot index 190d95e0..55fd0854 100644 --- a/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (balanced event 2) +ht_decide: 0 decided hold-interrupt (balanced decision moment other-key-up) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/keycode_events.snapshot index 190d95e0..55fd0854 100644 --- a/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/4c-dn-kcdn-kcup-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (balanced event 2) +ht_decide: 0 decided hold-interrupt (balanced decision moment other-key-up) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot index 31089abf..d06cd1ca 100644 --- a/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (balanced event 0) +ht_decide: 0 decided tap (balanced decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot b/app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot index a1b18d1c..4d0cdd83 100644 --- a/app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/5-quick-tap/keycode_events.snapshot @@ -1,10 +1,10 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (balanced event 0) +ht_decide: 0 decided tap (balanced decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (balanced event 4) +ht_decide: 0 decided tap (balanced decision moment quick-tap) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/balanced/6-retro-tap/events.patterns b/app/tests/hold-tap/balanced/6-retro-tap/events.patterns new file mode 100644 index 00000000..4db21917 --- /dev/null +++ b/app/tests/hold-tap/balanced/6-retro-tap/events.patterns @@ -0,0 +1,6 @@ +s/.*hid_listener_keycode/kp/p +s/.*mo_keymap_binding/mo/p +s/.*on_hold_tap_binding/ht_binding/p +s/.*decide_hold_tap/ht_decide/p +s/.*update_hold_status_for_retro_tap/update_hold_status_for_retro_tap/p +s/.*decide_retro_tap/decide_retro_tap/p \ No newline at end of file diff --git a/app/tests/hold-tap/balanced/6-retro-tap/keycode_events.snapshot b/app/tests/hold-tap/balanced/6-retro-tap/keycode_events.snapshot new file mode 100644 index 00000000..628625d7 --- /dev/null +++ b/app/tests/hold-tap/balanced/6-retro-tap/keycode_events.snapshot @@ -0,0 +1,19 @@ +ht_binding_pressed: 0 new undecided hold_tap +ht_decide: 0 decided tap (balanced decision moment key-up) +kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +ht_binding_released: 0 cleaning up hold-tap +ht_binding_pressed: 0 new undecided hold_tap +ht_decide: 0 decided hold-timer (balanced decision moment timer) +decide_retro_tap: 0 retro tap +kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +ht_binding_released: 0 cleaning up hold-tap +ht_binding_pressed: 0 new undecided hold_tap +ht_decide: 0 decided hold-timer (balanced decision moment timer) +update_hold_status_for_retro_tap: Update hold tap 0 status to hold-interrupt +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/balanced/6-retro-tap/native_posix.keymap b/app/tests/hold-tap/balanced/6-retro-tap/native_posix.keymap new file mode 100644 index 00000000..706ca540 --- /dev/null +++ b/app/tests/hold-tap/balanced/6-retro-tap/native_posix.keymap @@ -0,0 +1,45 @@ +#include +#include +#include + +/ { + behaviors { + ht_bal: behavior_balanced { + compatible = "zmk,behavior-hold-tap"; + label = "MOD_TAP"; + #binding-cells = <2>; + flavor = "balanced"; + tapping_term_ms = <300>; + bindings = <&kp>, <&kp>; + retro-tap; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &ht_bal LEFT_SHIFT F &none + &kp D &none>; + }; + }; +}; + + +&kscan { + events = < + /* tap */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + /* retro tap */ + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_RELEASE(0,0,10) + /* hold */ + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/hold-tap/balanced/behavior_keymap.dtsi b/app/tests/hold-tap/balanced/behavior_keymap.dtsi index 972b606f..d62be888 100644 --- a/app/tests/hold-tap/balanced/behavior_keymap.dtsi +++ b/app/tests/hold-tap/balanced/behavior_keymap.dtsi @@ -9,8 +9,8 @@ label = "HOLD_TAP_BALANCED"; #binding-cells = <2>; flavor = "balanced"; - tapping_term_ms = <300>; - quick_tap_ms = <200>; + tapping-term-ms = <300>; + quick-tap-ms = <200>; bindings = <&kp>, <&kp>; }; }; diff --git a/app/tests/hold-tap/balanced/many-nested/keycode_events.snapshot b/app/tests/hold-tap/balanced/many-nested/keycode_events.snapshot index 4bd40a91..489fe477 100644 --- a/app/tests/hold-tap/balanced/many-nested/keycode_events.snapshot +++ b/app/tests/hold-tap/balanced/many-nested/keycode_events.snapshot @@ -1,16 +1,16 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (balanced event 3) +ht_decide: 0 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 1 new undecided hold_tap -ht_decide: 1 decided hold (balanced event 3) +ht_decide: 1 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 2 new undecided hold_tap ht_binding_released: 0 cleaning up hold-tap -ht_decide: 2 decided hold (balanced event 3) +ht_decide: 2 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe3 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 3 new undecided hold_tap ht_binding_released: 1 cleaning up hold-tap -ht_decide: 3 decided hold (balanced event 3) +ht_decide: 3 decided hold-timer (balanced decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe2 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/balanced/many-nested/native_posix.keymap b/app/tests/hold-tap/balanced/many-nested/native_posix.keymap index 7941e4d3..2698f055 100644 --- a/app/tests/hold-tap/balanced/many-nested/native_posix.keymap +++ b/app/tests/hold-tap/balanced/many-nested/native_posix.keymap @@ -9,7 +9,7 @@ label = "HOLD_TAP_BALANCED"; #binding-cells = <2>; flavor = "balanced"; - tapping_term_ms = <300>; + tapping-term-ms = <300>; bindings = <&kp>, <&kp>; }; }; diff --git a/app/tests/hold-tap/hold-preferred/1-dn-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/1-dn-up/keycode_events.snapshot index 12c1a548..36dc281a 100644 --- a/app/tests/hold-tap/hold-preferred/1-dn-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/1-dn-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (hold-preferred event 0) +ht_decide: 0 decided tap (hold-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/hold-preferred/2-dn-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/keycode_events.snapshot index 291d17b1..827ac986 100644 --- a/app/tests/hold-tap/hold-preferred/2-dn-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/2-dn-timer-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (hold-preferred event 3) +ht_decide: 0 decided hold-timer (hold-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot index 2f8b72e7..84789bee 100644 --- a/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot @@ -1,6 +1,6 @@ kp_pressed: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (hold-preferred event 0) +ht_decide: 0 decided tap (hold-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot index 73308626..4bac227b 100644 --- a/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot @@ -1,6 +1,6 @@ kp_pressed: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (hold-preferred event 3) +ht_decide: 0 decided hold-timer (hold-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot index 45964250..a5b9f134 100644 --- a/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot @@ -1,7 +1,7 @@ kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 -ht_decide: 0 decided tap (hold-preferred event 0) +ht_decide: 0 decided tap (hold-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot index 5d6d637e..8ae35a38 100644 --- a/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot @@ -1,7 +1,7 @@ kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 -ht_decide: 0 decided hold (hold-preferred event 3) +ht_decide: 0 decided hold-timer (hold-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot index 92886a8c..865a985a 100644 --- a/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot @@ -1,8 +1,8 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (hold-preferred event 1) +ht_decide: 0 decided hold-interrupt (hold-preferred decision moment other-key-down) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 1 new undecided hold_tap -ht_decide: 1 decided tap (hold-preferred event 0) +ht_decide: 1 decided tap (hold-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 1 cleaning up hold-tap diff --git a/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot index e2feeefc..1d2b827e 100644 --- a/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (hold-preferred event 1) +ht_decide: 0 decided hold-interrupt (hold-preferred decision moment other-key-down) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot index e2feeefc..1d2b827e 100644 --- a/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (hold-preferred event 1) +ht_decide: 0 decided hold-interrupt (hold-preferred decision moment other-key-down) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot index e2feeefc..1d2b827e 100644 --- a/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (hold-preferred event 1) +ht_decide: 0 decided hold-interrupt (hold-preferred decision moment other-key-down) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot index da5b8265..ef3ea562 100644 --- a/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (hold-preferred event 1) +ht_decide: 0 decided hold-interrupt (hold-preferred decision moment other-key-down) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot index c3caf870..704cf41c 100644 --- a/app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot +++ b/app/tests/hold-tap/hold-preferred/5-quick-tap/keycode_events.snapshot @@ -1,10 +1,10 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (hold-preferred event 0) +ht_decide: 0 decided tap (hold-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (hold-preferred event 4) +ht_decide: 0 decided tap (hold-preferred decision moment quick-tap) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/hold-preferred/6-retro-tap/events.patterns b/app/tests/hold-tap/hold-preferred/6-retro-tap/events.patterns new file mode 100644 index 00000000..4db21917 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/6-retro-tap/events.patterns @@ -0,0 +1,6 @@ +s/.*hid_listener_keycode/kp/p +s/.*mo_keymap_binding/mo/p +s/.*on_hold_tap_binding/ht_binding/p +s/.*decide_hold_tap/ht_decide/p +s/.*update_hold_status_for_retro_tap/update_hold_status_for_retro_tap/p +s/.*decide_retro_tap/decide_retro_tap/p \ No newline at end of file diff --git a/app/tests/hold-tap/hold-preferred/6-retro-tap/keycode_events.snapshot b/app/tests/hold-tap/hold-preferred/6-retro-tap/keycode_events.snapshot new file mode 100644 index 00000000..dba93dba --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/6-retro-tap/keycode_events.snapshot @@ -0,0 +1,19 @@ +ht_binding_pressed: 0 new undecided hold_tap +ht_decide: 0 decided tap (hold-preferred decision moment key-up) +kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +ht_binding_released: 0 cleaning up hold-tap +ht_binding_pressed: 0 new undecided hold_tap +ht_decide: 0 decided hold-timer (hold-preferred decision moment timer) +decide_retro_tap: 0 retro tap +kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 +ht_binding_released: 0 cleaning up hold-tap +ht_binding_pressed: 0 new undecided hold_tap +ht_decide: 0 decided hold-timer (hold-preferred decision moment timer) +update_hold_status_for_retro_tap: Update hold tap 0 status to hold-interrupt +kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 +ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/hold-preferred/6-retro-tap/native_posix.keymap b/app/tests/hold-tap/hold-preferred/6-retro-tap/native_posix.keymap new file mode 100644 index 00000000..314b7334 --- /dev/null +++ b/app/tests/hold-tap/hold-preferred/6-retro-tap/native_posix.keymap @@ -0,0 +1,45 @@ +#include +#include +#include + +/ { + behaviors { + hp: behavior_hold_preferred { + compatible = "zmk,behavior-hold-tap"; + label = "MOD_TAP"; + #binding-cells = <2>; + flavor = "hold-preferred"; + tapping_term_ms = <300>; + bindings = <&kp>, <&kp>; + retro-tap; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label ="Default keymap"; + + default_layer { + bindings = < + &hp LEFT_SHIFT F &none + &kp D &none>; + }; + }; +}; + + +&kscan { + events = < + /* tap */ + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + /* retro tap */ + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_RELEASE(0,0,10) + /* hold */ + ZMK_MOCK_PRESS(0,0,400) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,0,10) + >; +}; \ No newline at end of file diff --git a/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi b/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi index 2b35f890..41c84e1e 100644 --- a/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi +++ b/app/tests/hold-tap/hold-preferred/behavior_keymap.dtsi @@ -11,8 +11,8 @@ label = "hold_hold_tap"; #binding-cells = <2>; flavor = "hold-preferred"; - tapping_term_ms = <300>; - quick_tap_ms = <200>; + tapping-term-ms = <300>; + quick-tap-ms = <200>; bindings = <&kp>, <&kp>; }; }; diff --git a/app/tests/hold-tap/tap-preferred/1-dn-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/1-dn-up/keycode_events.snapshot index 93b5ce29..d1f01261 100644 --- a/app/tests/hold-tap/tap-preferred/1-dn-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/1-dn-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (tap-preferred event 0) +ht_decide: 0 decided tap (tap-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/tap-preferred/2-dn-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/keycode_events.snapshot index 3dccfd31..4bf3e7ca 100644 --- a/app/tests/hold-tap/tap-preferred/2-dn-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/2-dn-timer-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (tap-preferred event 3) +ht_decide: 0 decided hold-timer (tap-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot index 09d2f10e..e6a1450f 100644 --- a/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/3a-moddn-dn-modup-up/keycode_events.snapshot @@ -1,6 +1,6 @@ kp_pressed: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (tap-preferred event 0) +ht_decide: 0 decided tap (tap-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot index c89aecfa..b7f4e669 100644 --- a/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/3b-moddn-dn-modup-timer-up/keycode_events.snapshot @@ -1,6 +1,6 @@ kp_pressed: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (tap-preferred event 3) +ht_decide: 0 decided hold-timer (tap-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe4 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot index 4211e851..72e3755a 100644 --- a/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/3c-kcdn-dn-kcup-up/keycode_events.snapshot @@ -1,7 +1,7 @@ kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 -ht_decide: 0 decided tap (tap-preferred event 0) +ht_decide: 0 decided tap (tap-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot index c149544a..cdb1ee9a 100644 --- a/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/3d-kcdn-dn-kcup-timer-up/keycode_events.snapshot @@ -1,7 +1,7 @@ kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 0 new undecided hold_tap kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 -ht_decide: 0 decided hold (tap-preferred event 3) +ht_decide: 0 decided hold-timer (tap-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot index cf7db305..54ac986b 100644 --- a/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/4a-dn-htdn-timer-htup-up/keycode_events.snapshot @@ -1,8 +1,8 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (tap-preferred event 3) +ht_decide: 0 decided hold-timer (tap-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 ht_binding_pressed: 1 new undecided hold_tap -ht_decide: 1 decided tap (tap-preferred event 0) +ht_decide: 1 decided tap (tap-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 1 cleaning up hold-tap diff --git a/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot index 95573b9b..2d985568 100644 --- a/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/4a-dn-kcdn-timer-kcup-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (tap-preferred event 3) +ht_decide: 0 decided hold-timer (tap-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot index 95573b9b..2d985568 100644 --- a/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/4b-dn-kcdn-kcup-timer-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided hold (tap-preferred event 3) +ht_decide: 0 decided hold-timer (tap-preferred decision moment timer) kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot index 38cce941..93fa43be 100644 --- a/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/4c-dn-kcdn-kcup-up/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (tap-preferred event 0) +ht_decide: 0 decided tap (tap-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot index 8749a46c..e10f263e 100644 --- a/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/4d-dn-kcdn-timer-up-kcup/keycode_events.snapshot @@ -1,5 +1,5 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (tap-preferred event 0) +ht_decide: 0 decided tap (tap-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot b/app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot index e89ccf34..3e8ec42b 100644 --- a/app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot +++ b/app/tests/hold-tap/tap-preferred/5-quick-tap/keycode_events.snapshot @@ -1,10 +1,10 @@ ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (tap-preferred event 0) +ht_decide: 0 decided tap (tap-preferred decision moment key-up) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap ht_binding_pressed: 0 new undecided hold_tap -ht_decide: 0 decided tap (tap-preferred event 4) +ht_decide: 0 decided tap (tap-preferred decision moment quick-tap) kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00 ht_binding_released: 0 cleaning up hold-tap diff --git a/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi b/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi index 80d6b0af..cdeb9596 100644 --- a/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi +++ b/app/tests/hold-tap/tap-preferred/behavior_keymap.dtsi @@ -9,8 +9,8 @@ label = "MOD_TAP"; #binding-cells = <2>; flavor = "tap-preferred"; - tapping_term_ms = <300>; - quick_tap_ms = <200>; + tapping-term-ms = <300>; + quick-tap-ms = <200>; bindings = <&kp>, <&kp>; }; }; diff --git a/docs/docs/assets/features/beta-testing/pr-repo-branch.png b/docs/docs/assets/features/beta-testing/pr-repo-branch.png new file mode 100644 index 00000000..68856d0b Binary files /dev/null and b/docs/docs/assets/features/beta-testing/pr-repo-branch.png differ diff --git a/docs/docs/assets/features/beta-testing/repo-branch.png b/docs/docs/assets/features/beta-testing/repo-branch.png new file mode 100644 index 00000000..741e69a4 Binary files /dev/null and b/docs/docs/assets/features/beta-testing/repo-branch.png differ diff --git a/docs/docs/assets/features/beta-testing/repo-url.png b/docs/docs/assets/features/beta-testing/repo-url.png new file mode 100644 index 00000000..aed37137 Binary files /dev/null and b/docs/docs/assets/features/beta-testing/repo-url.png differ diff --git a/docs/docs/behaviors/hold-tap.md b/docs/docs/behaviors/hold-tap.md index c148fa37..957744b3 100644 --- a/docs/docs/behaviors/hold-tap.md +++ b/docs/docs/behaviors/hold-tap.md @@ -23,9 +23,9 @@ We call this the 'hold-preferred' flavor of hold-taps. While this flavor may wor #### Flavors -- The 'hold-preferred' flavor triggers the hold behavior when the `tapping_term_ms` has expired or another key is pressed. -- The 'balanced' flavor will trigger the hold behavior when the `tapping_term_ms` has expired or another key is pressed and released. -- The 'tap-preferred' flavor triggers the hold behavior when the `tapping_term_ms` has expired. It triggers the tap behavior when another key is pressed. +- The 'hold-preferred' flavor triggers the hold behavior when the `tapping-term-ms` has expired or another key is pressed. +- The 'balanced' flavor will trigger the hold behavior when the `tapping-term-ms` has expired or another key is pressed and released. +- The 'tap-preferred' flavor triggers the hold behavior when the `tapping-term-ms` has expired. It triggers the tap behavior when another key is pressed. When the hold-tap key is released and the hold behavior has not been triggered, the tap behavior will trigger. @@ -37,7 +37,7 @@ For basic usage, please see [mod-tap](./mod-tap.md) and [layer-tap](./layers.md) ### Advanced Configuration -#### `tapping_term_ms` +#### `tapping-term-ms` Defines how long a key must be pressed to trigger Hold behavior. @@ -47,6 +47,18 @@ If you press a tapped hold-tap again within `quick_tap_ms` milliseconds, it will In QMK, unlike ZMK, this functionality is enabled by default, and you turn it off using `TAPPING_FORCE_HOLD`. +#### `retro-tap` + +If retro tap is enabled, the tap behavior is triggered when releasing the hold-tap key if no other key was pressed in the meantime. + +For example, if you press `&mt LEFT_SHIFT A` and then release it without pressing another key, it will output `a`. + +``` +&mt { + retro-tap; +} +``` + #### Home row mods This example configures a hold-tap that works well for homerow mods: @@ -61,7 +73,7 @@ This example configures a hold-tap that works well for homerow mods: compatible = "zmk,behavior-hold-tap"; label = "HOMEROW_MODS"; #binding-cells = <2>; - tapping_term_ms = <150>; + tapping-term-ms = <150>; quick_tap_ms = <0>; flavor = "tap-preferred"; bindings = <&kp>, <&kp>; @@ -81,7 +93,7 @@ This example configures a hold-tap that works well for homerow mods: ``` -If this config does not work for you, try the flavor "balanced" with a medium `tapping_term_ms` such as 200ms. +If this config does not work for you, try the flavor "balanced" with a medium `tapping-term-ms` such as 200ms. #### Comparison to QMK diff --git a/docs/docs/behaviors/mod-tap.md b/docs/docs/behaviors/mod-tap.md index 6404a220..bd08a38d 100644 --- a/docs/docs/behaviors/mod-tap.md +++ b/docs/docs/behaviors/mod-tap.md @@ -34,7 +34,7 @@ You can configure a different tapping term in your keymap: ``` &mt { - tapping_term_ms = <400>; + tapping-term-ms = <400>; }; / { @@ -46,4 +46,4 @@ You can configure a different tapping term in your keymap: ### Additional information -The mod-tap is a [hold-tap](./hold-tap.md) under the hood with the "balanced" flavor and tapping_term_ms 200. +The mod-tap is a [hold-tap](./hold-tap.md) under the hood with the "balanced" flavor and tapping-term-ms 200. diff --git a/docs/docs/development/ide-integration.md b/docs/docs/development/ide-integration.md new file mode 100644 index 00000000..7576af55 --- /dev/null +++ b/docs/docs/development/ide-integration.md @@ -0,0 +1,159 @@ +--- +title: IDE Integration +sidebar_label: IDE Integration +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +export const OsTabs = (props) => ({props.children}); + +## Visual Studio Code + +Visual Studio Code needs to know some things about the project such as include +paths and compiler paths before features such as code completion, go to definition, +and graying out disabled code blocks will work. Fortunately, CMake can generate +that configuration for us automatically. + +### Create a Compilation Database + +To configure `west` to tell CMake to generate a compilation database, open a +terminal to the ZMK repository and run the following command: + +```sh +west config build.cmake-args -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +``` + +Every [build](build-flash#building) will now update the database. You will +need to build once to create the database before code completion will work. +We'll tell Visual Studio Code where to find the database in the next step. + +:::note +If you have set any other CMake arguments such as the path to your zmk-config, the +above command will overwrite them. You should instead provide the flag to export +compile commands and all other arguments surrounded by quotes. For example: + +```sh +west config build.cmake-args -- "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DZMK_CONFIG=/path/to/zmk-config/config" +``` + +::: + +### Create a C/C++ Configuration + +Install the [C/C++ extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools), +then run **F1 > C/C++: Edit Configurations (UI)**. It should automatically create +a new configuration for you, but if the text box under **Configuration name** is empty, +click **Add Configuration**, enter a name, and click **OK**. + +Change these options: + +| Option | Value | +| ------------------------------------- | ---------------------------------------------------- | +| Compiler path | Path to your toolchain's GCC binary (see below) | +| IntelliSense mode | gcc-arm | +| Advanced Settings > Compiler commands | `${workspaceFolder}/app/build/compile_commands.json` | + +#### Compiler Path + + + + +Open VS Code's integrated terminal and run the following commands. It will print +your compiler path. + +```sh +source zephyr/zephyr-env.sh +echo ${ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc +``` + +:::note +You will need to update this path any time you switch to a new version of the Zephyr SDK. +::: + + + + +Your compiler path is + +``` +${env:GNUARMEMB_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc.exe +``` + +This assumes `GNUARMEMB_TOOLCHAIN_PATH` is set in your system or user environment variables. +If not, you will need to list the full path instead of using the `${env}` placeholder. + + + + +Open VS Code's integrated terminal and run the following command. It will print +your compiler path. + +```sh +echo ${GNUARMEMB_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc +``` + + + + +Your compiler path is + +``` +/usr/bin/arm-none-eabi-gcc +``` + + + + +Open VS Code's integrated terminal and run the following commands. It will print +your compiler path. + +```sh +source zephyr/zephyr-env.sh +echo ${ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc +``` + +:::note +You will need to update this path any time you switch to a new version of the Zephyr SDK. +::: + + + + +Your compiler path is + +``` +${env:ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc +``` + + + + +#### Compiler Commands Path + +When building with all default options, the path to the compilation database file +is `${workspaceFolder}/app/build/compile_commands.json` as shown in the table above, +however some arguments to `west build` can change this path. + +The `-d` or `--build-dir` option lets you change the build directory to something +other than `build`. Replace `build` in the above path with what you set this to. +For example, if you build with `-d build/my_shield`, the path is +`${workspaceFolder}/app/build/my_shield/compile_commands.json`. If you use this +to keep builds for multiple keyboards separate, you may want to create a separate +C/C++ configuration for each one in VS Code. + +You can also build from the root folder of the project instead of the `app` +folder by adding `-S app` to your CMake arguments. In this case, simply remove +`app` from the path to `compile_commands.json`, for example, +`${workspaceFolder}/build/compile_commands.json`. diff --git a/docs/docs/features/beta-testing.md b/docs/docs/features/beta-testing.md new file mode 100644 index 00000000..4328ccbf --- /dev/null +++ b/docs/docs/features/beta-testing.md @@ -0,0 +1,100 @@ +--- +title: Beta Testing +sidebar_label: Beta Testing +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +You may find that ZMK does not support a feature or keyboard that you are interesting in using. You may find that someone +has already taken the time to submit the feature you need as a [Pull Request](https://github.com/zmkfirmware/zmk/pulls). If you find the feature you need as a pull request, +this page is for you! + +## Developer Repositories and Branches + +For a developer to submit a pull request to ZMK, they must first clone the original ZMK repository. After they have a copy +of the source code, they may create a feature branch to work within. When they have finished, they will publish the feature +branch and create the pull request. + +### Finding the Repository Page from the Pull Request + +![PR Repository](../assets/features/beta-testing/pr-repo-branch.png) + +### Finding the Repository URL + +![Repository URL](../assets/features/beta-testing/repo-url.png) + +### Finding the Repository Branch + +![Repository URL](../assets/features/beta-testing/repo-branch.png) + +## Testing features + +Testing features will require you to modify the `west.yml` file. You will need to add a new remote for the pull request you +would like to test, and change the selected remote and revision (or branch) for the `zmk` project. + +### Examples + + + + +``` +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + projects: + - name: zmk + remote: zmkfirmware + revision: main + import: app/west.yml + self: + path: config +``` + + + + +``` +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + - name: okke-formsma + url-base: https://github.com/okke-formsma + projects: + - name: zmk + remote: okke-formsma + revision: macros + import: app/west.yml + self: + path: config +``` + + + + +``` +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + - name: mcrosson + url-base: https://github.com/mcrosson + projects: + - name: zmk + remote: mcrosson + revision: feat-behavior-sleep + import: app/west.yml + self: + path: config +``` + + + diff --git a/docs/docs/features/encoders.md b/docs/docs/features/encoders.md index 01610775..cf86ea95 100644 --- a/docs/docs/features/encoders.md +++ b/docs/docs/features/encoders.md @@ -41,4 +41,4 @@ Here, the left encoder is configured to control volume up and down while the rig ## Adding Encoder Support -See the [New Keyboard Shield](../development/new-shield#encoders) documentation for how to add or modify additional encoders to your shield. +See the [New Keyboard Shield](/docs/development/new-shield#encoders) documentation for how to add or modify additional encoders to your shield. diff --git a/docs/docs/features/underglow.md b/docs/docs/features/underglow.md index 3692a6de..a093ecac 100644 --- a/docs/docs/features/underglow.md +++ b/docs/docs/features/underglow.md @@ -55,10 +55,16 @@ If you have a shield with RGB underglow, you must add a `boards/` directory with Inside the `boards/` folder, you define a `.overlay` for each different board. For example, the Kyria shield has a `boards/nice_nano.overlay` file that defines the RGB underglow for the `nice_nano` board specifically. -The first step to adding support for underglow is to select you SPI output. With nRF52 boards, you can just use `&spi1` and define the pins you want to use. -For other boards, you must select an SPI definition that has the `MOSI` pin as your data pin going to your LED strip. +### nRF52-based boards -Here's an example of an nRF52 SPI definition: +With nRF52 boards, you can just use `&spi1` and define the pins you want to use. + +To identify which pin number you need to put in the config you need do to a bit of math. You need the hardware port and run it through a function. +**32 \* X + Y** = `` where X is first part of the hardware port "PX.01" and Y is the second part of the hardware port "P1.Y". + +(_P1.13_ would give you _32 \* 1 + 13_ = `<45>` and P0.15 would give you _32 \* 0 + 15_ = `<15>`) + +Here's an example on a definition that uses P0.06: ``` &spi1 { @@ -87,11 +93,15 @@ Here's an example of an nRF52 SPI definition: :::info -If you are configuring SPI for an nRF52840 (or other nRF52) based board, double check that you are using pins that aren't restricted to low frequency I/O. -Ignoring these restrictions may result in poor wireless performance. You can find the list of low frequency I/O pins [here](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fpin.html&cp=4_0_0_6_0). +If you are configuring SPI for an nRF52 based board, double check that you are using pins that aren't restricted to low frequency I/O. +Ignoring these restrictions may result in poor wireless performance. You can find the list of low frequency I/O pins for the nRF52840 [here](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fpin.html&cp=4_0_0_6_0). ::: +### Other boards + +For other boards, you must select an SPI definition that has the `MOSI` pin as your data pin going to your LED strip. + Here's another example for a non-nRF52 board on `spi1`: ``` diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 45aadfdd..2ff6062b 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -15,14 +15,14 @@ ZMK is currently missing some features found in other popular firmware. This tab | ------------------------------------------------------------------------------------------------------------------------- | :-: | :-------: | :-: | | Low Latency BLE Support | ✅ | ✅ | | | Multi-Device BLE Support | ✅ | | | -| [USB Connectivity](behaviors/outputs) | ✅ | | ✅ | +| [USB Connectivity](behaviors/outputs) | ✅ | ✅ | ✅ | | User Configuration Repositories | ✅ | | | | Split Keyboard Support | ✅ | ✅ | ✅ | | [Keymaps and Layers](behaviors/layers) | ✅ | ✅ | ✅ | | [Hold-Tap](behaviors/hold-tap) (which includes [Mod-Tap](behaviors/mod-tap) and [Layer-Tap](behaviors/layers/#layer-tap)) | ✅ | ✅ | ✅ | | [Keyboard Codes](codes/#keyboard) | ✅ | ✅ | ✅ | | [Media](codes/#media-controls) & [Consumer](codes/#consumer-controls) Codes | ✅ | ✅ | ✅ | -| [Encoders](features/encoders)[^1] | ✅ | | ✅ | +| [Encoders](features/encoders)[^1] | ✅ | ✅ | ✅ | | [Display Support](features/displays)[^2] | 🚧 | 🚧 | ✅ | | [RGB Underglow](features/underglow) | ✅ | ✅ | ✅ | | One Shot Keys | ✅ | ✅ | ✅ | @@ -31,7 +31,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | Mouse Keys | 💡 | ✅ | ✅ | | Low Active Power Usage | ✅ | | | | Low Power Sleep States | ✅ | ✅ | | -| [Low Power Mode (VCC Shutoff)](behaviors/power) | ✅ | | | +| [Low Power Mode (VCC Shutoff)](behaviors/power) | ✅ | ✅ | | | Battery Reporting | ✅ | ✅ | | | Shell over BLE | 💡 | | | | Realtime Keymap Updating | 💡 | | ✅ | diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index f22610a0..ab7ec12d 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -29,6 +29,11 @@ module.exports = { position: "left", }, { to: "blog", label: "Blog", position: "left" }, + { + to: "power-profiler", + label: "Power Profiler", + position: "left", + }, { href: "https://github.com/zmkfirmware/zmk", label: "GitHub", diff --git a/docs/sidebars.js b/docs/sidebars.js index 98ffb19e..6761b19b 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -14,6 +14,7 @@ module.exports = { "features/displays", "features/encoders", "features/underglow", + "features/beta-testing", ], Behaviors: [ "behaviors/key-press", @@ -49,6 +50,7 @@ module.exports = { "development/posix-board", "development/tests", "development/usb-logging", + "development/ide-integration", { type: "category", label: "Guides", diff --git a/docs/src/components/custom-board-form.js b/docs/src/components/custom-board-form.js new file mode 100644 index 00000000..0279f6b1 --- /dev/null +++ b/docs/src/components/custom-board-form.js @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +import React from "react"; +import PropTypes from "prop-types"; + +function CustomBoardForm({ + bindPsuType, + bindOutputV, + bindEfficiency, + bindQuiescentMicroA, + bindOtherQuiescentMicroA, +}) { + return ( +
+

Custom Board

+
+
+
+ + +
+
+
+
+ + + {parseFloat(bindOutputV.value).toFixed(1)}V +
+ {bindPsuType.value === "SWITCHING" && ( +
+ + + {Math.round(bindEfficiency.value * 100)}% +
+ )} +
+
+
+ +
+ + µA +
+
+
+ +
+ + µA +
+
+
+
+
+ ); +} + +CustomBoardForm.propTypes = { + bindPsuType: PropTypes.Object, + bindOutputV: PropTypes.Object, + bindEfficiency: PropTypes.Object, + bindQuiescentMicroA: PropTypes.Object, + bindOtherQuiescentMicroA: PropTypes.Object, +}; + +export default CustomBoardForm; diff --git a/docs/src/components/power-estimate.js b/docs/src/components/power-estimate.js new file mode 100644 index 00000000..26198626 --- /dev/null +++ b/docs/src/components/power-estimate.js @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +import React from "react"; +import PropTypes from "prop-types"; +import { displayPower, underglowPower, zmkBase } from "../data/power"; +import "../css/power-estimate.css"; + +// Average monthly discharge percent +const lithiumIonMonthlyDischargePercent = 5; +// Average voltage of a lithium ion battery based of discharge graphs +const lithiumIonAverageVoltage = 3.8; +// Average discharge efficiency of li-ion https://en.wikipedia.org/wiki/Lithium-ion_battery +const lithiumIonDischargeEfficiency = 0.85; +// Range of the discharge efficiency +const lithiumIonDischargeEfficiencyRange = 0.05; + +// Proportion of time spent typing (keys being pressed down and scanning). Estimated to 2%. +const timeSpentTyping = 0.02; + +// Nordic power profiler kit accuracy +const measurementAccuracy = 0.2; + +const batVolt = lithiumIonAverageVoltage; + +const palette = [ + "#bbdefb", + "#90caf9", + "#64b5f6", + "#42a5f5", + "#2196f3", + "#1e88e5", + "#1976d2", +]; + +function formatUsage(microWatts) { + if (microWatts > 1000) { + return (microWatts / 1000).toFixed(1) + "mW"; + } + + return Math.round(microWatts) + "µW"; +} + +function voltageEquivalentCalc(powerSupply) { + if (powerSupply.type === "LDO") { + return batVolt; + } else if (powerSupply.type === "SWITCHING") { + return powerSupply.outputVoltage / powerSupply.efficiency; + } +} + +function formatMinutes(minutes, precision, floor) { + let message = ""; + let count = 0; + + let units = ["year", "month", "week", "day", "hour", "minute"]; + let multiples = [60 * 24 * 365, 60 * 24 * 30, 60 * 24 * 7, 60 * 24, 60, 1]; + + for (let i = 0; i < units.length; i++) { + if (minutes >= multiples[i]) { + const timeCount = floor + ? Math.floor(minutes / multiples[i]) + : Math.ceil(minutes / multiples[i]); + minutes -= timeCount * multiples[i]; + count++; + message += + timeCount + (timeCount > 1 ? ` ${units[i]}s ` : ` ${units[i]} `); + } + + if (count == precision) return message; + } + + return message || "0 minutes"; +} + +function PowerEstimate({ + board, + splitType, + batteryMilliAh, + usage, + underglow, + display, +}) { + if (!board || !board.powerSupply.type || !batteryMilliAh) { + return ( +
+

+ {splitType !== "standalone" ? splitType + ": " : " "}... +

+
+
+
+
+ ); + } + + const powerUsage = []; + let totalUsage = 0; + + const voltageEquivalent = voltageEquivalentCalc(board.powerSupply); + + // Lithium ion self discharge + const lithiumMonthlyDischargemAh = + parseInt(batteryMilliAh) * (lithiumIonMonthlyDischargePercent / 100); + const lithiumDischargeMicroA = (lithiumMonthlyDischargemAh * 1000) / 30 / 24; + const lithiumDischargeMicroW = lithiumDischargeMicroA * batVolt; + + totalUsage += lithiumDischargeMicroW; + powerUsage.push({ + title: "Battery Self Discharge", + usage: lithiumDischargeMicroW, + }); + + // Quiescent current + const quiescentMicroATotal = + parseInt(board.powerSupply.quiescentMicroA) + + parseInt(board.otherQuiescentMicroA); + const quiescentMicroW = quiescentMicroATotal * voltageEquivalent; + + totalUsage += quiescentMicroW; + powerUsage.push({ + title: "Board Quiescent Usage", + usage: quiescentMicroW, + }); + + // ZMK overall usage + const zmkMicroA = + zmkBase[splitType].idle + + (splitType !== "peripheral" ? zmkBase.hostConnection * usage.bondedQty : 0); + + const zmkMicroW = zmkMicroA * voltageEquivalent; + const zmkUsage = zmkMicroW * (1 - usage.percentAsleep); + + totalUsage += zmkUsage; + powerUsage.push({ + title: "ZMK Base Usage", + usage: zmkUsage, + }); + + // ZMK typing usage + const zmkTypingMicroA = zmkBase[splitType].typing * timeSpentTyping; + + const zmkTypingMicroW = zmkTypingMicroA * voltageEquivalent; + const zmkTypingUsage = zmkTypingMicroW * (1 - usage.percentAsleep); + + totalUsage += zmkTypingUsage; + powerUsage.push({ + title: "ZMK Typing Usage", + usage: zmkTypingUsage, + }); + + if (underglow.glowEnabled) { + const underglowAverageLedMicroA = + underglow.glowBrightness * + (underglowPower.ledOn - underglowPower.ledOff) + + underglowPower.ledOff; + + const underglowMicroA = + underglowPower.firmware + + underglow.glowQuantity * underglowAverageLedMicroA; + + const underglowMicroW = underglowMicroA * voltageEquivalent; + + const underglowUsage = underglowMicroW * (1 - usage.percentAsleep); + + totalUsage += underglowUsage; + powerUsage.push({ + title: "RGB Underglow", + usage: underglowUsage, + }); + } + + if (display.displayEnabled && display.displayType) { + const { activePercent, active, sleep } = displayPower[display.displayType]; + + const displayMicroA = active * activePercent + sleep * (1 - activePercent); + const displayMicroW = displayMicroA * voltageEquivalent; + const displayUsage = displayMicroW * (1 - usage.percentAsleep); + + totalUsage += displayUsage; + powerUsage.push({ + title: "Display", + usage: displayUsage, + }); + } + + // Calculate the average minutes of use + const estimatedAvgEffectiveMicroWH = + batteryMilliAh * batVolt * lithiumIonDischargeEfficiency * 1000; + + const estimatedAvgMinutes = Math.round( + (estimatedAvgEffectiveMicroWH / totalUsage) * 60 + ); + + // Calculate worst case for battery life + const worstLithiumIonDischargeEfficiency = + lithiumIonDischargeEfficiency - lithiumIonDischargeEfficiencyRange; + + const estimatedWorstEffectiveMicroWH = + batteryMilliAh * batVolt * worstLithiumIonDischargeEfficiency * 1000; + + const highestTotalUsage = totalUsage * (1 + measurementAccuracy); + + const estimatedWorstMinutes = Math.round( + (estimatedWorstEffectiveMicroWH / highestTotalUsage) * 60 + ); + + // Calculate range (+-) of minutes using average - worst + const estimatedRange = estimatedAvgMinutes - estimatedWorstMinutes; + + return ( +
+

+ {splitType !== "standalone" ? splitType + ": " : " "} + {formatMinutes(estimatedAvgMinutes, 2, true)} (± + {formatMinutes(estimatedRange, 1, false).trim()}) +

+
+ {powerUsage.map((p, i) => ( +
1 ? " rightSection" : "") + } + style={{ + width: (p.usage / totalUsage) * 100 + "%", + background: palette[i], + }} + > +
+
+
+ {p.title} - {Math.round((p.usage / totalUsage) * 100)}% +
+
+ ~{formatUsage(p.usage)} estimated avg. consumption +
+
+
+
+ ))} +
+
+ ); +} + +PowerEstimate.propTypes = { + board: PropTypes.Object, + splitType: PropTypes.string, + batteryMilliAh: PropTypes.number, + usage: PropTypes.Object, + underglow: PropTypes.Object, + display: PropTypes.Object, +}; + +export default PowerEstimate; diff --git a/docs/src/css/power-estimate.css b/docs/src/css/power-estimate.css new file mode 100644 index 00000000..e876ec28 --- /dev/null +++ b/docs/src/css/power-estimate.css @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +.powerEstimate { + margin: 20px 0; +} + +.powerEstimate > h3 > span { + text-transform: capitalize; +} + +.powerEstimateBar { + height: 64px; + width: 100%; + box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px, + rgba(0, 0, 0, 0.1) 0px 1px 4px 0px; + border-radius: 64px; + display: flex; + justify-content: flex-start; + overflow: hidden; +} + +.powerEstimateBarSection { + transition: all 0.2s ease; + flex-grow: 1; +} + +.powerEstimateBarSection.rightSection { + display: flex; + justify-content: flex-end; +} + +.powerEstimateTooltipWrap { + position: absolute; + visibility: hidden; + opacity: 0; + transform: translateY(calc(-100% - 8px)); + transition: opacity 0.2s ease; +} + +.powerEstimateBarSection:hover .powerEstimateTooltipWrap { + visibility: visible; + opacity: 1; +} + +.powerEstimateTooltip { + display: block; + position: relative; + box-shadow: var(--ifm-global-shadow-tl); + width: 260px; + padding: 10px; + border-radius: 4px; + background: var(--ifm-background-surface-color); + transform: translateX(-15px); +} + +.rightSection .powerEstimateTooltip { + transform: translateX(15px); +} + +.powerEstimateTooltip:after { + content: ""; + position: absolute; + top: 100%; + left: 27px; + margin-left: -8px; + width: 0; + height: 0; + border-top: 8px solid var(--ifm-background-surface-color); + border-right: 8px solid transparent; + border-left: 8px solid transparent; +} + +.rightSection .powerEstimateTooltip:after { + left: unset; + right: 27px; + margin-right: -8px; +} diff --git a/docs/src/css/power-profiler.css b/docs/src/css/power-profiler.css new file mode 100644 index 00000000..94c4a5dd --- /dev/null +++ b/docs/src/css/power-profiler.css @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +.profilerSection { + margin: 10px 0; + padding: 10px 20px; + background: var(--ifm-background-surface-color); + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px, + rgba(0, 0, 0, 0.1) 0px 1px 4px 0px; +} + +.profilerInput { + margin-bottom: 12px; +} + +.profilerInput label { + display: block; +} + +.profilerDisclaimer { + padding: 20px 0; + font-size: 14px; +} + +span[tooltip] { + position: relative; +} + +span[tooltip]::before { + content: attr(tooltip); + font-size: 13px; + padding: 5px 10px; + position: absolute; + width: 220px; + border-radius: 4px; + background: var(--ifm-background-surface-color); + opacity: 0; + visibility: hidden; + box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px, + rgba(0, 0, 0, 0.1) 0px 1px 4px 0px; + transition: opacity 0.2s ease; + transform: translate(-50%, -100%); + left: 50%; +} + +span[tooltip]::after { + content: ""; + position: absolute; + border-top: 8px solid var(--ifm-background-surface-color); + border-right: 8px solid transparent; + border-left: 8px solid transparent; + width: 0; + height: 0; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease; + transform: translateX(-50%); + left: 50%; +} + +span[tooltip]:hover::before { + opacity: 1; + visibility: visible; +} + +span[tooltip]:hover::after { + opacity: 1; + visibility: visible; +} + +input[type="checkbox"].toggleInput { + display: none; +} + +input[type="checkbox"] + .toggle { + margin: 6px 2px; + height: 20px; + width: 48px; + background: rgba(0, 0, 0, 0.5); + border-radius: 20px; + transition: all 0.2s ease; + user-select: none; +} + +input[type="checkbox"] + .toggle > .toggleThumb { + height: 16px; + border-radius: 20px; + transform: translate(2px, 2px); + width: 16px; + background: var(--ifm-color-white); + box-shadow: var(--ifm-global-shadow-lw); + transition: all 0.2s ease; +} + +input[type="checkbox"]:checked + .toggle { + background: var(--ifm-color-primary); +} + +input[type="checkbox"]:checked + .toggle > .toggleThumb { + transform: translate(30px, 2px); +} + +select { + border: solid 1px rgba(0, 0, 0, 0.5); + border-radius: 4px; + display: flex; + height: 34px; + width: 200px; + + background: inherit; + color: inherit; + font-size: inherit; + line-height: inherit; + margin: 0; + padding: 3px 5px; + outline: none; +} + +select > option { + background: var(--ifm-background-surface-color); +} + +.inputBox { + border: solid 1px rgba(0, 0, 0, 0.5); + border-radius: 4px; + display: flex; + width: 200px; +} + +.inputBox > input { + background: inherit; + color: inherit; + font-size: inherit; + line-height: inherit; + margin: 0; + padding: 3px 10px; + border: none; + width: 100%; + min-width: 0; + text-align: right; + outline: none; +} + +.inputBox > span { + background: rgba(0, 0, 0, 0.05); + border-left: solid 1px rgba(0, 0, 0, 0.5); + padding: 3px 10px; +} + +/* Chrome, Safari, Edge, Opera */ +.inputBox > input::-webkit-outer-spin-button, +.inputBox > input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +.inputBox > input[type="number"] { + -moz-appearance: textfield; +} + +.disclaimerHolder { + position: absolute; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + z-index: 99; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; +} + +.disclaimer { + padding: 20px 20px; + background: var(--ifm-background-surface-color); + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px, + rgba(0, 0, 0, 0.1) 0px 1px 4px 0px; + width: 500px; +} + +.disclaimer > button { + border: none; + background: var(--ifm-color-primary); + color: var(--ifm-color-white); + cursor: pointer; + border-radius: 4px; + padding: 5px 15px; +} diff --git a/docs/src/data/power.js b/docs/src/data/power.js new file mode 100644 index 00000000..bf34f17d --- /dev/null +++ b/docs/src/data/power.js @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/** + * This file holds all current measurements related to ZMK features and hardware + * All current measurements are in micro amps. Measurements were taken on a Nordic Power Profiler Kit + * The test device to get these values was three nice!nanos (nRF52840). + */ + +export const zmkBase = { + hostConnection: 23, // How much current it takes to have an idle host connection + standalone: { + idle: 0, // No extra idle current + typing: 315, // Current while holding down a key. Represents polling+BLE notification power + }, + central: { + idle: 490, // Idle current for connection to right half + typing: 380, // Current while holding down a key. Represents polling+BLE notification power + }, + peripheral: { + idle: 20, // Idle current for connection to left half + typing: 365, // Current while holding down a key. Represents polling+BLE notification power + }, +}; + +/** + * ZMK board power measurements + * + * Power supply can be an LDO or switching + * Quiescent and other quiescent are measured in micro amps + * + * Switching efficiency represents the efficiency of converting from + * 3.8V (average li-ion voltage) to the output voltage of the power supply + */ +export const zmkBoards = { + "nice!nano": { + name: "nice!nano", + powerSupply: { + type: "LDO", + outputVoltage: 3.3, + quiescentMicroA: 55, + }, + otherQuiescentMicroA: 4, + }, + "nice!60": { + powerSupply: { + type: "SWITCHING", + outputVoltage: 3.3, + efficiency: 0.95, + quiescentMicroA: 4, + }, + otherQuiescentMicroA: 4, + }, +}; + +export const underglowPower = { + firmware: 60, // ZMK power usage while underglow feature is turned on (SPIM mostly) + ledOn: 20000, // Estimated power consumption of a WS2812B at 100% (can be anywhere from 10mA to 30mA) + ledOff: 460, // Quiescent current of a WS2812B +}; + +export const displayPower = { + // Based on GoodDisplay's 1.02in epaper + EPAPER: { + activePercent: 0.05, // Estimated one refresh per minute taking three seconds + active: 1500, // Power draw during refresh + sleep: 5, // Idle power draw of an epaper + }, + // 128x32 SSD1306 + OLED: { + activePercent: 0.5, // Estimated sleeping half the time (based on idle) + active: 10000, // Estimated power draw when about half the pixels are on + sleep: 7, // Deep sleep power draw (display off) + }, +}; diff --git a/docs/src/pages/power-profiler.js b/docs/src/pages/power-profiler.js new file mode 100644 index 00000000..ca46f5ca --- /dev/null +++ b/docs/src/pages/power-profiler.js @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +import React, { useState } from "react"; +import classnames from "classnames"; +import Layout from "@theme/Layout"; +import styles from "./styles.module.css"; +import PowerEstimate from "../components/power-estimate"; +import CustomBoardForm from "../components/custom-board-form"; +import { useInput } from "../utils/hooks"; +import { zmkBoards } from "../data/power"; +import "../css/power-profiler.css"; + +const Disclaimer = `This profiler makes many assumptions about typing + activity, battery characteristics, hardware behavior, and + doesn't account for error of user inputs. For example battery + mAh, which is often incorrectly advertised higher than it's actual capacity. + While it tries to estimate power usage using real power readings of ZMK, + every person will have different results that may be worse or even + better than the estimation given here.`; + +function PowerProfiler() { + const { value: board, bind: bindBoard } = useInput(""); + const { value: split, bind: bindSplit } = useInput(false); + const { value: batteryMilliAh, bind: bindBatteryMilliAh } = useInput(110); + + const { value: psuType, bind: bindPsuType } = useInput(""); + const { value: outputV, bind: bindOutputV } = useInput(3.3); + const { value: quiescentMicroA, bind: bindQuiescentMicroA } = useInput(55); + const { + value: otherQuiescentMicroA, + bind: bindOtherQuiescentMicroA, + } = useInput(0); + const { value: efficiency, bind: bindEfficiency } = useInput(0.9); + + const { value: bondedQty, bind: bindBondedQty } = useInput(1); + const { value: percentAsleep, bind: bindPercentAsleep } = useInput(0.5); + + const { value: glowEnabled, bind: bindGlowEnabled } = useInput(false); + const { value: glowQuantity, bind: bindGlowQuantity } = useInput(10); + const { value: glowBrightness, bind: bindGlowBrightness } = useInput(1); + + const { value: displayEnabled, bind: bindDisplayEnabled } = useInput(false); + const { value: displayType, bind: bindDisplayType } = useInput(""); + + const [disclaimerAcknowledged, setDisclaimerAcknowledged] = useState( + typeof window !== "undefined" + ? localStorage.getItem("zmkPowerProfilerDisclaimer") === "true" + : false + ); + + const currentBoard = + board === "custom" + ? { + powerSupply: { + type: psuType, + outputVoltage: outputV, + quiescentMicroA: quiescentMicroA, + efficiency, + }, + otherQuiescentMicroA: otherQuiescentMicroA, + } + : zmkBoards[board]; + + return ( + +
+
+

ZMK Power Profiler

+

+ {"Estimate your keyboard's power usage and battery life on ZMK."} +

+
+
+
+
+
+

Keyboard Specifications

+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + mAh +
+
+
+
+
+ + {board === "custom" && ( + + )} + +
+

Usage Values

+
+
+
+ + + {bondedQty} +
+
+
+
+ + + {Math.round(percentAsleep * 100)}% +
+
+
+
+ +
+

Features

+
+
+
+ + +
+
+
+ + +
+
+
+ {split ? ( + <> + + + + ) : ( + + )} +
+
+ Disclaimer: {Disclaimer} +
+
+
+
+ {!disclaimerAcknowledged && ( +
+
+

Disclaimer

+

{Disclaimer}

+ +
+
+ )} +
+ ); +} + +export default PowerProfiler; diff --git a/docs/src/utils/hooks.js b/docs/src/utils/hooks.js new file mode 100644 index 00000000..b8fb27b8 --- /dev/null +++ b/docs/src/utils/hooks.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +import { useState } from "react"; + +export const useInput = (initialValue) => { + const [value, setValue] = useState(initialValue); + + return { + value, + setValue, + bind: { + value, + onChange: (event) => { + const target = event.target; + setValue(target.type === "checkbox" ? target.checked : target.value); + }, + }, + }; +};