diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 9fce3b64..b5e7b034 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -25,7 +25,6 @@ zephyr_linker_sources(RODATA include/linker/zmk-events.ld) target_include_directories(app PRIVATE include) target_sources(app PRIVATE src/kscan.c) target_sources(app PRIVATE src/matrix_transform.c) -target_sources(app PRIVATE src/keymap.c) target_sources(app PRIVATE src/hid.c) target_sources(app PRIVATE src/sensors.c) target_sources_ifdef(CONFIG_ZMK_DISPLAY app PRIVATE src/display.c) @@ -37,11 +36,13 @@ target_sources(app PRIVATE src/events/sensor_event.c) target_sources(app PRIVATE src/behaviors/behavior_key_press.c) target_sources(app PRIVATE src/behaviors/behavior_reset.c) target_sources(app PRIVATE src/behaviors/behavior_mod_tap.c) +target_sources(app PRIVATE src/behaviors/behavior_tap_hold.c) target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c) target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c) +target_sources(app PRIVATE src/keymap.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble_unpair_combo.c) diff --git a/app/dts/bindings/behaviors/zmk,behavior-tap-hold.yaml b/app/dts/bindings/behaviors/zmk,behavior-tap-hold.yaml new file mode 100644 index 00000000..d6d041b0 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-tap-hold.yaml @@ -0,0 +1,23 @@ +# Copyright (c) 2020, Cody McGinnis; Okke Formsma +# SPDX-License-Identifier: MIT + +description: Hold or Tap behavior + +compatible: "zmk,behavior-tap-hold" + +include: zero_param.yaml + +properties: + bindings: + type: phandle-array + required: true + tapping_term_ms: + type: int + flavor: + type: string + required: false + default: "hold-preferred" + enum: + - "hold-preferred" + - "balanced" + - "tap-preferred" \ No newline at end of file diff --git a/app/include/zmk/event-manager.h b/app/include/zmk/event-manager.h index 43d3f299..07c0aa98 100644 --- a/app/include/zmk/event-manager.h +++ b/app/include/zmk/event-manager.h @@ -78,6 +78,10 @@ struct zmk_event_subscription { #define ZMK_EVENT_RELEASE(ev) \ zmk_event_manager_release((struct zmk_event_header *)ev); +#define ZMK_EVENT_RELEASE_AGAIN(ev) \ + zmk_event_manager_release_again((struct zmk_event_header *)ev); + int zmk_event_manager_raise(struct zmk_event_header *event); int zmk_event_manager_raise_after(struct zmk_event_header *event, const struct zmk_listener *listener); int zmk_event_manager_release(struct zmk_event_header *event); +int zmk_event_manager_release_again(struct zmk_event_header *event); diff --git a/app/src/behaviors/behavior_tap_hold.c b/app/src/behaviors/behavior_tap_hold.c new file mode 100644 index 00000000..459d1c3c --- /dev/null +++ b/app/src/behaviors/behavior_tap_hold.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2020 Cody McGinnis, Okke Formsma + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_tap_hold + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_NODE_EXISTS(DT_DRV_INST(0)) + +/************************************************************ DATA SETUP */ +#define ZMK_BHV_TAP_HOLD_MAX_HELD 10 +#define ZMK_BHV_TAP_HOLD_MAX_CAPTURED_KC 40 + +#define TH_TYPE_MOD_PREFERRED 0 +#define TH_TYPE_BALANCED 1 +#define TH_TYPE_TAP_PREFERRED 2 + + +// increase if you have keyboard with more keys. +#define TH_POSITION_NOT_USED 9999 + +struct behavior_tap_hold_behaviors { + struct zmk_behavior_binding tap; + struct zmk_behavior_binding hold; +}; + +typedef k_timeout_t (*timer_func)(); + +struct behavior_tap_hold_config { + timer_func tapping_term_ms; + struct behavior_tap_hold_behaviors* behaviors; + char* flavor; +}; + +// this data is specific for each tap-hold +struct active_tap_hold { + s32_t position; + bool is_decided; + bool is_hold; + const struct behavior_tap_hold_config *config; + struct k_delayed_work work; + bool work_is_cancelled; +}; + +struct active_tap_hold* undecided_tap_hold = NULL; +struct active_tap_hold active_tap_holds[ZMK_BHV_TAP_HOLD_MAX_HELD] = {}; +struct position_state_changed* captured_position_events[ZMK_BHV_TAP_HOLD_MAX_CAPTURED_KC] = {}; + +/************************************************************ CAPTURED POSITION HELPER FUNCTIONS */ +int capture_position_event(struct position_state_changed* event) { + for (int i = 0; i < ZMK_BHV_TAP_HOLD_MAX_CAPTURED_KC; i++) { + if (captured_position_events[i] == NULL) { + captured_position_events[i] = event; + return 0; + } + } + return -ENOMEM; +} + +struct position_state_changed* find_captured_keydown_event(u32_t position) +{ + struct position_state_changed *last_match = NULL; + for (int i = 0; i < ZMK_BHV_TAP_HOLD_MAX_CAPTURED_KC; i++) { + struct position_state_changed* event = captured_position_events[i]; + if (event == NULL) { + return last_match; + } + + if (captured_position_events[i]->position == position && captured_position_events[i]->state) { + last_match = event; + } + } + + return last_match; +} + +int behavior_tap_hold_listener(const struct zmk_event_header *eh); +void release_captured_positions() { + if (undecided_tap_hold != NULL) { + return; + } + + // We use a trick to prevent copying the captured_position_events array. + // + // Events for different mod-tap instances are separated by a NULL pointer. + // + // The first event popped will never be catched by the next active tap-hold + // because to start capturing a mod-tap-key-down event must first completely + // go through the events queue. + // + // Example of this release process; + // [mt2_down, k1_down, k1_up, mt2_up, null, ...] + // ^ + // mt2_down position event isn't captured because no tap-hold is active. + // mt2_down behavior event is handled, now we have an undecided tap-hold + // [null, k1_down, k1_up, mt2_up, null, ...] + // ^ + // k1_down is captured by the mt2 mod-tap + // !note that searches for find_captured_position_event by the mt2 behavior will stop at the first null encountered + // [mt1_down, null, k1_up, mt2_up, null, ...] + // ^ + // k1_up event is captured by the new tap-hold: + // [k1_down, k1_up, null, mt2_up, null, ...] + // ^ + // mt2_up event is not captured but causes release of mt2 behavior + // [k1_down, k1_up, null, null, null, ...] + // now mt2 will start releasing it's own captured positions. + for(int i = 0; i < ZMK_BHV_TAP_HOLD_MAX_CAPTURED_KC; i++) { + struct position_state_changed* captured_position = captured_position_events[i]; + if(captured_position == NULL) { + return; + } + captured_position_events[i] = NULL; + if(undecided_tap_hold != NULL) { + k_msleep(10); + } + LOG_DBG("Releasing key position event for position %d %s", captured_position->position, (captured_position->state ? "pressed" : "released")); + ZMK_EVENT_RELEASE_AGAIN(captured_position); + } +} + + +/************************************************************ ACTIVE TAP HOLD HELPER FUNCTIONS */ + +struct active_tap_hold* find_tap_hold(u32_t position) +{ + for (int i = 0; i < ZMK_BHV_TAP_HOLD_MAX_HELD; i++) { + if (active_tap_holds[i].position == position) { + return &active_tap_holds[i]; + } + } + return NULL; +} + +struct active_tap_hold* store_tap_hold(u32_t position, const struct behavior_tap_hold_config* config) +{ + for (int i = 0; i < ZMK_BHV_TAP_HOLD_MAX_HELD; i++) { + if (active_tap_holds[i].position != TH_POSITION_NOT_USED) { + continue; + } + active_tap_holds[i].position = position; + active_tap_holds[i].is_decided = false; + active_tap_holds[i].is_hold = false; + active_tap_holds[i].config = config; + return &active_tap_holds[i]; + } + return NULL; +} + +void clear_tap_hold(struct active_tap_hold * tap_hold) +{ + tap_hold->position = TH_POSITION_NOT_USED; + tap_hold->is_decided = false; + tap_hold->is_hold = false; + tap_hold->work_is_cancelled= false; +} + +enum decision_moment{ + TH_KEY_UP = 0, + TH_OTHER_KEY_DOWN = 1, + TH_OTHER_KEY_UP = 2, + TH_TIMER_EVENT = 3, +}; + +static void decide_balanced(struct active_tap_hold * tap_hold, enum decision_moment event) { + switch(event) { + case TH_KEY_UP: + tap_hold->is_hold = 0; + tap_hold->is_decided = true; + break; + case TH_OTHER_KEY_UP: + tap_hold->is_hold = 1; + tap_hold->is_decided = true; + break; + case TH_TIMER_EVENT: + tap_hold->is_hold = 1; + tap_hold->is_decided = true; + break; + default: return; + } +} + +static void decide_tap_preferred(struct active_tap_hold * tap_hold, enum decision_moment event) { + switch(event) { + case TH_KEY_UP: + tap_hold->is_hold = 0; + tap_hold->is_decided = true; + break; + case TH_TIMER_EVENT: + tap_hold->is_hold = 1; + tap_hold->is_decided = true; + break; + default: return; + } +} + +static void decide_hold_preferred(struct active_tap_hold * tap_hold, enum decision_moment event) { + switch(event) { + case TH_KEY_UP: + tap_hold->is_hold = 0; + tap_hold->is_decided = true; + break; + case TH_OTHER_KEY_DOWN: + tap_hold->is_hold = 1; + tap_hold->is_decided = true; + break; + case TH_TIMER_EVENT: + tap_hold->is_hold = 1; + tap_hold->is_decided = true; + break; + default: return; + } +} + + +void decide_tap_hold(struct active_tap_hold * tap_hold, enum decision_moment event) +{ + if (tap_hold->is_decided) { + return; + } + + if(tap_hold != undecided_tap_hold) { + LOG_DBG("ERROR found undecided tap hold that is not the active tap hold"); + return; + } + + char* flavor = tap_hold->config->flavor; + if(strcmp(flavor, "balanced") == 0) { + decide_balanced(tap_hold, event); + } else if(strcmp(flavor, "tap-preferred") == 0) { + decide_tap_preferred(tap_hold, event); + } else if(strcmp(flavor, "hold-preferred") == 0) { + decide_hold_preferred(tap_hold, event); + } + + if(!tap_hold->is_decided) { + return; + } + + LOG_DBG("%d decided %s (%s event %d)", tap_hold->position, tap_hold->is_hold?"hold":"tap", flavor, event); + + undecided_tap_hold = NULL; + + struct zmk_behavior_binding *behavior; + if (tap_hold->is_hold) { + behavior = &tap_hold->config->behaviors->hold; + } else { + behavior = &tap_hold->config->behaviors->tap; + } + struct device *behavior_device = device_get_binding(behavior->behavior_dev); + behavior_keymap_binding_pressed(behavior_device, tap_hold->position, behavior->param1, behavior->param2); + release_captured_positions(); +} + +/************************************************************ tap_hold_binding and key handlers */ +static int on_tap_hold_binding_pressed(struct device *dev, u32_t position, u32_t _, u32_t __) +{ + const struct behavior_tap_hold_config *cfg = dev->config_info; + + if(undecided_tap_hold != NULL) { + LOG_DBG("ERROR another tap-hold behavior is undecided."); + // if this happens, make sure the behavior events occur AFTER other position events. + return 0; + } + + struct active_tap_hold *tap_hold = store_tap_hold(position, cfg); + if (tap_hold == NULL) { + LOG_ERR("unable to store tap-hold info, did you press more than %d tap-holds?", ZMK_BHV_TAP_HOLD_MAX_HELD); + return 0; + } + + LOG_DBG("%d new undecided tap_hold", position); + undecided_tap_hold = tap_hold; + k_delayed_work_submit(&tap_hold->work, cfg->tapping_term_ms()); + + //todo: once we get timing info for keypresses, start the timer relative to the original keypress + // don't forget to simulate a timer-event before the event after that time was handled. + + return 0; +} + +static int on_tap_hold_binding_released(struct device *dev, u32_t position, u32_t _, u32_t __) +{ + struct active_tap_hold *tap_hold = find_tap_hold(position); + if(tap_hold == NULL) { + LOG_ERR("ACTIVE_TAP_HOLD_CLEANED_UP_TOO_EARLY"); + return 0; + } + + int work_cancel_result = k_delayed_work_cancel(&tap_hold->work); + decide_tap_hold(tap_hold, TH_KEY_UP); + + struct zmk_behavior_binding *behavior; + if (tap_hold->is_hold) { + behavior = &tap_hold->config->behaviors->hold; + } else { + behavior = &tap_hold->config->behaviors->tap; + } + + struct device *behavior_device = device_get_binding(behavior->behavior_dev); + behavior_keymap_binding_released(behavior_device, tap_hold->position, behavior->param1, behavior->param2); + + if(work_cancel_result == -EINPROGRESS) { + // let the timer handler clean up + // if we'd clear now, the timer may call back for an uninitialized active_tap_hold. + LOG_DBG("%d tap-hold timer work in event queue", position); + tap_hold->work_is_cancelled = true; + } else { + LOG_DBG("%d cleaning up tap-hold", position); + clear_tap_hold(tap_hold); + } + + return 0; +} + +static const struct behavior_driver_api behavior_tap_hold_driver_api = { + .binding_pressed = on_tap_hold_binding_pressed, + .binding_released = on_tap_hold_binding_released, +}; + +int behavior_tap_hold_listener(const struct zmk_event_header *eh) +{ + if (!is_position_state_changed(eh)) { + return 0; + } + struct position_state_changed* ev = cast_position_state_changed(eh); + if(undecided_tap_hold == NULL) { + LOG_DBG("%d bubble (no undecided tap_hold active)", ev->position); + return 0; + } + + if(undecided_tap_hold->position == ev->position) { + if(ev->state) { + LOG_ERR("this listener should be called before before the behavior listeners!"); + return 0; + } else { + LOG_DBG("%d bubble undecided tap-hold keyrelease event", undecided_tap_hold->position); + return 0; + } + } + + LOG_DBG("%d capturing %d %s event", undecided_tap_hold->position, ev->position, ev->state?"down":"up"); + + capture_position_event(ev); + if (ev->state) { + decide_tap_hold(undecided_tap_hold, TH_OTHER_KEY_DOWN); + } else { + struct position_state_changed* captured_key_down = find_captured_keydown_event(ev->position); + if(captured_key_down == NULL) { + // todo: allow key-up events for non-mod keys pressed before the TH was pressed. + // see scenario 3c/3d vs 3a/3b. + } else { + decide_tap_hold(undecided_tap_hold, TH_OTHER_KEY_UP); + } + } + return ZMK_EV_EVENT_CAPTURED; +} + +ZMK_LISTENER(behavior_tap_hold, behavior_tap_hold_listener); +ZMK_SUBSCRIPTION(behavior_tap_hold, position_state_changed); + +/************************************************************ TIMER FUNCTIONS */ +void behavior_tap_hold_timer_work_handler(struct k_work *item) +{ + struct active_tap_hold *tap_hold = CONTAINER_OF(item, struct active_tap_hold, work); + if(tap_hold->work_is_cancelled) { + clear_tap_hold(tap_hold); + } else { + decide_tap_hold(tap_hold, TH_TIMER_EVENT); + } +} + +static int behavior_tap_hold_init(struct device *dev) +{ + static bool init_first_run = true; + if(init_first_run) { + for (int i = 0; i < ZMK_BHV_TAP_HOLD_MAX_HELD; i++) { + k_delayed_work_init(&active_tap_holds[i].work, behavior_tap_hold_timer_work_handler); + active_tap_holds[i].position = TH_POSITION_NOT_USED; + } + } + init_first_run = false; + return 0; +} + +struct behavior_tap_hold_data {}; +static struct behavior_tap_hold_data behavior_tap_hold_data; + +/************************************************************ NODE CONFIG */ +#define _TRANSFORM_ENTRY(idx, node) \ + { .behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \ + .param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), (DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \ + .param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), (DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \ + }, + +#define KP_INST(n) \ + static k_timeout_t behavior_tap_hold_config_##n##_gettime() { return K_MSEC(DT_INST_PROP(n, tapping_term_ms)); } \ + static struct behavior_tap_hold_behaviors behavior_tap_hold_behaviors_##n = { \ + .tap = _TRANSFORM_ENTRY(0, n) \ + .hold = _TRANSFORM_ENTRY(1, n) \ + }; \ + static struct behavior_tap_hold_config behavior_tap_hold_config_##n = { \ + .behaviors = &behavior_tap_hold_behaviors_##n, \ + .tapping_term_ms = &behavior_tap_hold_config_##n##_gettime, \ + .flavor = DT_INST_PROP(n, flavor), \ + }; \ + DEVICE_AND_API_INIT(behavior_tap_hold_##n, DT_INST_LABEL(n), behavior_tap_hold_init, \ + &behavior_tap_hold_data, \ + &behavior_tap_hold_config_##n, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_tap_hold_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + + +#endif \ No newline at end of file diff --git a/app/src/event_manager.c b/app/src/event_manager.c index c405176f..22b92bfd 100644 --- a/app/src/event_manager.c +++ b/app/src/event_manager.c @@ -24,24 +24,24 @@ int zmk_event_manager_handle_from(struct zmk_event_header *event, u8_t start_ind u8_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); + if (ev_sub->event_type != event->event) { + continue; + } + ret = ev_sub->listener->callback(event); + if (ret < 0) { + LOG_DBG("Listener returned an error: %d", ret); + goto release; + } + switch (ret) { + case ZMK_EV_EVENT_HANDLED: + LOG_DBG("Listener handled the event"); + ret = 0; 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; - } - } + 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; } } @@ -55,6 +55,22 @@ int zmk_event_manager_raise(struct zmk_event_header *event) return zmk_event_manager_handle_from(event, 0); } +int zmk_event_manager_raise_at(struct zmk_event_header *event, const struct zmk_listener *listener) +{ + u8_t len = __event_subscriptions_end - __event_subscriptions_start; + for (int i = 0; i < len; i++) { + struct zmk_event_subscription *ev_sub = __event_subscriptions_start + i; + + if (ev_sub->event_type == event->event && ev_sub->listener == listener) { + return zmk_event_manager_handle_from(event, i+1); + } + } + + LOG_WRN("Unable to find where to raise this after event"); + + return -EINVAL; +} + int zmk_event_manager_raise_after(struct zmk_event_header *event, const struct zmk_listener *listener) { u8_t len = __event_subscriptions_end - __event_subscriptions_start; @@ -74,4 +90,10 @@ int zmk_event_manager_raise_after(struct zmk_event_header *event, const struct z int zmk_event_manager_release(struct zmk_event_header *event) { return zmk_event_manager_handle_from(event, event->last_listener_index + 1); +} + + +int zmk_event_manager_release_again(struct zmk_event_header *event) +{ + return zmk_event_manager_handle_from(event, event->last_listener_index); } \ No newline at end of file