@ -1,5 +1,5 @@
/*
/*
* Copyright ( c ) 2020 Cody McGinnis , Okke Formsma
* Copyright ( c ) 2020 The ZMK Contributors
*
*
* SPDX - License - Identifier : MIT
* SPDX - License - Identifier : MIT
*/
*/
@ -29,36 +29,35 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
// increase if you have keyboard with more keys.
// increase if you have keyboard with more keys.
# define ZMK_BHV_HOLD_TAP_POSITION_NOT_USED 9999
# define ZMK_BHV_HOLD_TAP_POSITION_NOT_USED 9999
enum flavor {
enum flavor {
ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED = 0 ,
ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED = 0 ,
ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED = 1 ,
ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED = 1 ,
ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED = 2 ,
ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED = 2 ,
} ;
} ;
struct behavior_hold_tap_behaviors {
struct behavior_hold_tap_behaviors {
struct zmk_behavior_binding tap ;
struct zmk_behavior_binding tap ;
struct zmk_behavior_binding hold ;
struct zmk_behavior_binding hold ;
} ;
} ;
typedef k_timeout_t ( * timer_func ) ( ) ;
typedef k_timeout_t ( * timer_func ) ( ) ;
struct behavior_hold_tap_config {
struct behavior_hold_tap_config {
timer_func tapping_term_ms ;
timer_func tapping_term_ms ;
struct behavior_hold_tap_behaviors * behaviors ;
struct behavior_hold_tap_behaviors * behaviors ;
enum flavor flavor ;
enum flavor flavor ;
} ;
} ;
// this data is specific for each hold-tap
// this data is specific for each hold-tap
struct active_hold_tap {
struct active_hold_tap {
s32_t position ;
s32_t position ;
u32_t param_hold ;
u32_t param_hold ;
u32_t param_tap ;
u32_t param_tap ;
bool is_decided ;
bool is_decided ;
bool is_hold ;
bool is_hold ;
const struct behavior_hold_tap_config * config ;
const struct behavior_hold_tap_config * config ;
struct k_delayed_work work ;
struct k_delayed_work work ;
bool work_is_cancelled ;
bool work_is_cancelled ;
} ;
} ;
// The undecided hold tap is the hold tap that needs to be decided before
// The undecided hold tap is the hold tap that needs to be decided before
@ -71,371 +70,366 @@ struct active_hold_tap active_hold_taps[ZMK_BHV_HOLD_TAP_MAX_HELD] = {};
// We capture most position_state_changed events and some modifiers_state_changed events.
// We capture most position_state_changed events and some modifiers_state_changed events.
const struct zmk_event_header * captured_events [ ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ] = { } ;
const struct zmk_event_header * captured_events [ ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ] = { } ;
static int capture_event ( const struct zmk_event_header * event )
static int capture_event ( const struct zmk_event_header * event ) {
{
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ; i + + ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ; i + + ) {
if ( captured_events [ i ] = = NULL ) {
if ( captured_events [ i ] = = NULL ) {
captured_events [ i ] = event ;
captured_events [ i ] = event ;
return 0 ;
return 0 ;
}
}
}
}
return - ENOMEM ;
return - ENOMEM ;
}
}
static struct position_state_changed * find_captured_keydown_event ( u32_t position )
static struct position_state_changed * find_captured_keydown_event ( u32_t position ) {
{
struct position_state_changed * last_match = NULL ;
struct position_state_changed * last_match = NULL ;
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ; i + + ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ; i + + ) {
const struct zmk_event_header * eh = captured_events [ i ] ;
const struct zmk_event_header * eh = captured_events [ i ] ;
if ( eh = = NULL ) {
if ( eh = = NULL ) {
return last_match ;
return last_match ;
}
}
if ( ! is_position_state_changed ( eh ) ) {
if ( ! is_position_state_changed ( eh ) ) {
continue ;
continue ;
}
}
struct position_state_changed * position_event = cast_position_state_changed ( eh ) ;
struct position_state_changed * position_event = cast_position_state_changed ( eh ) ;
if ( position_event - > position = = position & & position_event - > state ) {
if ( position_event - > position = = position & & position_event - > state ) {
last_match = position_event ;
last_match = position_event ;
}
}
}
}
return last_match ;
return last_match ;
}
}
const struct zmk_listener zmk_listener_behavior_hold_tap ;
const struct zmk_listener zmk_listener_behavior_hold_tap ;
static void release_captured_events ( )
static void release_captured_events ( ) {
{
if ( undecided_hold_tap ! = NULL ) {
if ( undecided_hold_tap ! = NULL ) {
return ;
return ;
}
}
// We use a trick to prevent copying the captured_events array.
// We use a trick to prevent copying the captured_events array.
//
//
// Events for different mod-tap instances are separated by a NULL pointer.
// Events for different mod-tap instances are separated by a NULL pointer.
//
//
// The first event popped will never be catched by the next active hold-tap
// The first event popped will never be catched by the next active hold-tap
// because to start capturing a mod-tap-key-down event must first completely
// because to start capturing a mod-tap-key-down event must first completely
// go through the events queue.
// go through the events queue.
//
//
// Example of this release process;
// Example of this release process;
// [mt2_down, k1_down, k1_up, mt2_up, null, ...]
// [mt2_down, k1_down, k1_up, mt2_up, null, ...]
// ^
// ^
// mt2_down position event isn't captured because no hold-tap is active.
// mt2_down position event isn't captured because no hold-tap is active.
// mt2_down behavior event is handled, now we have an undecided hold-tap
// mt2_down behavior event is handled, now we have an undecided hold-tap
// [null, k1_down, k1_up, mt2_up, null, ...]
// [null, k1_down, k1_up, mt2_up, null, ...]
// ^
// ^
// k1_down is captured by the mt2 mod-tap
// k1_down is captured by the mt2 mod-tap
// !note that searches for find_captured_keydown_event by the mt2 behavior will stop at the
// !note that searches for find_captured_keydown_event by the mt2 behavior will stop at the first null encountered
// first null encountered [mt1_down, null, k1_up, mt2_up, null, ...]
// [mt1_down, null, k1_up, mt2_up, null, ...]
// ^
// ^
// k1_up event is captured by the new hold-tap:
// k1_up event is captured by the new hold-tap:
// [k1_down, k1_up, null, mt2_up, null, ...]
// [k1_down, k1_up, null, mt2_up, null, ...]
// ^
// ^
// mt2_up event is not captured but causes release of mt2 behavior
// mt2_up event is not captured but causes release of mt2 behavior
// [k1_down, k1_up, null, null, null, ...]
// [k1_down, k1_up, null, null, null, ...]
// now mt2 will start releasing it's own captured positions.
// now mt2 will start releasing it's own captured positions.
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ; i + + ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS ; i + + ) {
const struct zmk_event_header * captured_event = captured_events [ i ] ;
const struct zmk_event_header * captured_event = captured_events [ i ] ;
if ( captured_event = = NULL ) {
if ( captured_event = = NULL ) {
return ;
return ;
}
}
captured_events [ i ] = NULL ;
captured_events [ i ] = NULL ;
if ( undecided_hold_tap ! = NULL ) {
if ( undecided_hold_tap ! = NULL ) {
k_msleep ( 10 ) ;
k_msleep ( 10 ) ;
}
}
if ( is_position_state_changed ( captured_event ) ) {
if ( is_position_state_changed ( captured_event ) ) {
struct position_state_changed * position_event =
struct position_state_changed * position_event = cast_position_state_changed ( captured_event ) ;
cast_position_state_changed ( captured_event ) ;
LOG_DBG ( " Releasing key position event for position %d %s " , position_event - > position , ( position_event - > state ? " pressed " : " released " ) ) ;
LOG_DBG ( " Releasing key position event for position %d %s " , position_event - > position ,
} else {
( position_event - > state ? " pressed " : " released " ) ) ;
struct keycode_state_changed * modifier_event = cast_keycode_state_changed ( captured_event ) ;
} else {
LOG_DBG ( " Releasing mods changed event 0x%02X %s " , modifier_event - > keycode , ( modifier_event - > state ? " pressed " : " released " ) ) ;
struct keycode_state_changed * modifier_event =
}
cast_keycode_state_changed ( captured_event ) ;
ZMK_EVENT_RAISE_AT ( captured_event , behavior_hold_tap ) ;
LOG_DBG ( " Releasing mods changed event 0x%02X %s " , modifier_event - > keycode ,
}
( modifier_event - > state ? " pressed " : " released " ) ) ;
}
ZMK_EVENT_RAISE_AT ( captured_event , behavior_hold_tap ) ;
}
}
}
static struct active_hold_tap * find_hold_tap ( u32_t position )
static struct active_hold_tap * find_hold_tap ( u32_t position ) {
{
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_HELD ; i + + ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_HELD ; i + + ) {
if ( active_hold_taps [ i ] . position = = position ) {
if ( active_hold_taps [ i ] . position = = position ) {
return & active_hold_taps [ i ] ;
return & active_hold_taps [ i ] ;
}
}
}
}
return NULL ;
return NULL ;
}
}
static struct active_hold_tap * store_hold_tap ( u32_t position , u32_t param_hold , u32_t param_tap , const struct behavior_hold_tap_config * config )
static struct active_hold_tap * store_hold_tap ( u32_t position , u32_t param_hold , u32_t param_tap ,
{
const struct behavior_hold_tap_config * config ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_HELD ; i + + ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_HELD ; i + + ) {
if ( active_hold_taps [ i ] . position ! = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED ) {
if ( active_hold_taps [ i ] . position ! = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED ) {
continue ;
continue ;
}
}
active_hold_taps [ i ] . position = position ;
active_hold_taps [ i ] . position = position ;
active_hold_taps [ i ] . is_decided = false ;
active_hold_taps [ i ] . is_decided = false ;
active_hold_taps [ i ] . is_hold = false ;
active_hold_taps [ i ] . is_hold = false ;
active_hold_taps [ i ] . config = config ;
active_hold_taps [ i ] . config = config ;
active_hold_taps [ i ] . param_hold = param_hold ;
active_hold_taps [ i ] . param_hold = param_hold ;
active_hold_taps [ i ] . param_tap = param_tap ;
active_hold_taps [ i ] . param_tap = param_tap ;
return & active_hold_taps [ i ] ;
return & active_hold_taps [ i ] ;
}
}
return NULL ;
return NULL ;
}
}
static void clear_hold_tap ( struct active_hold_tap * hold_tap )
static void clear_hold_tap ( struct active_hold_tap * hold_tap ) {
{
hold_tap - > position = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED ;
hold_tap - > position = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED ;
hold_tap - > is_decided = false ;
hold_tap - > is_decided = false ;
hold_tap - > is_hold = false ;
hold_tap - > is_hold = false ;
hold_tap - > work_is_cancelled = false ;
hold_tap - > work_is_cancelled = false ;
}
}
enum decision_moment {
enum decision_moment {
HT_KEY_UP = 0 ,
HT_KEY_UP = 0 ,
HT_OTHER_KEY_DOWN = 1 ,
HT_OTHER_KEY_DOWN = 1 ,
HT_OTHER_KEY_UP = 2 ,
HT_OTHER_KEY_UP = 2 ,
HT_TIMER_EVENT = 3 ,
HT_TIMER_EVENT = 3 ,
} ;
} ;
static void decide_balanced ( struct active_hold_tap * hold_tap , enum decision_moment event )
static void decide_balanced ( struct active_hold_tap * hold_tap , enum decision_moment event ) {
{
switch ( event ) {
switch ( event ) {
case HT_KEY_UP :
case HT_KEY_UP :
hold_tap - > is_hold = 0 ;
hold_tap - > is_hold = 0 ;
hold_tap - > is_decided = true ;
hold_tap - > is_decided = true ;
break ;
break ;
case HT_OTHER_KEY_UP :
case HT_OTHER_KEY_UP :
case HT_TIMER_EVENT :
case HT_TIMER_EVENT :
hold_tap - > is_hold = 1 ;
hold_tap - > is_hold = 1 ;
hold_tap - > is_decided = true ;
hold_tap - > is_decided = true ;
break ;
break ;
default :
default : return ;
return ;
}
}
}
}
static void decide_tap_preferred ( struct active_hold_tap * hold_tap , enum decision_moment event )
static void decide_tap_preferred ( struct active_hold_tap * hold_tap , enum decision_moment event ) {
{
switch ( event ) {
switch ( event ) {
case HT_KEY_UP :
case HT_KEY_UP :
hold_tap - > is_hold = 0 ;
hold_tap - > is_hold = 0 ;
hold_tap - > is_decided = true ;
hold_tap - > is_decided = true ;
break ;
break ;
case HT_TIMER_EVENT :
case HT_TIMER_EVENT :
hold_tap - > is_hold = 1 ;
hold_tap - > is_hold = 1 ;
hold_tap - > is_decided = true ;
hold_tap - > is_decided = true ;
break ;
break ;
default :
default : return ;
return ;
}
}
}
}
static void decide_hold_preferred ( struct active_hold_tap * hold_tap , enum decision_moment event )
static void decide_hold_preferred ( struct active_hold_tap * hold_tap , enum decision_moment event ) {
{
switch ( event ) {
switch ( event ) {
case HT_KEY_UP :
case HT_KEY_UP :
hold_tap - > is_hold = 0 ;
hold_tap - > is_hold = 0 ;
hold_tap - > is_decided = true ;
hold_tap - > is_decided = true ;
break ;
break ;
case HT_OTHER_KEY_DOWN :
case HT_OTHER_KEY_DOWN :
case HT_TIMER_EVENT :
case HT_TIMER_EVENT :
hold_tap - > is_hold = 1 ;
hold_tap - > is_hold = 1 ;
hold_tap - > is_decided = true ;
hold_tap - > is_decided = true ;
break ;
break ;
default :
default : return ;
return ;
}
}
}
}
static inline char * flavor_str ( enum flavor flavor ) {
static inline char * flavor_str ( enum flavor flavor ) {
switch ( flavor ) {
switch ( flavor ) {
case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED :
case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED :
return " hold-preferred " ;
return " hold-preferred " ;
case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED :
case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED :
return " balanced " ;
return " balanced " ;
case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED :
case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED :
return " tap-preferred " ;
return " tap-preferred " ;
}
}
return " UNKNOWN FLAVOR " ;
return " UNKNOWN FLAVOR " ;
}
}
static void decide_hold_tap ( struct active_hold_tap * hold_tap , enum decision_moment event )
static void decide_hold_tap ( struct active_hold_tap * hold_tap , enum decision_moment event ) {
{
if ( hold_tap - > is_decided ) {
if ( hold_tap - > is_decided ) {
return ;
return ;
}
}
if ( hold_tap ! = undecided_hold_tap ) {
if ( hold_tap ! = undecided_hold_tap ) {
LOG_DBG ( " ERROR found undecided tap hold that is not the active tap hold " ) ;
LOG_DBG ( " ERROR found undecided tap hold that is not the active tap hold " ) ;
return ;
return ;
}
}
switch ( hold_tap - > config - > flavor ) {
switch ( hold_tap - > config - > flavor ) {
case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED :
case ZMK_BHV_HOLD_TAP_FLAVOR_HOLD_PREFERRED :
decide_hold_preferred ( hold_tap , event ) ;
decide_hold_preferred ( hold_tap , event ) ;
case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED :
case ZMK_BHV_HOLD_TAP_FLAVOR_BALANCED :
decide_balanced ( hold_tap , event ) ;
decide_balanced ( hold_tap , event ) ;
case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED :
case ZMK_BHV_HOLD_TAP_FLAVOR_TAP_PREFERRED :
decide_tap_preferred ( hold_tap , event ) ;
decide_tap_preferred ( hold_tap , event ) ;
}
}
if ( ! hold_tap - > is_decided ) {
if ( ! hold_tap - > is_decided ) {
return ;
return ;
}
}
LOG_DBG ( " %d decided %s (%s event %d) " , hold_tap - > position , hold_tap - > is_hold ? " hold " : " tap " ,
LOG_DBG ( " %d decided %s (%s event %d) " ,
flavor_str ( hold_tap - > config - > flavor ) , event ) ;
hold_tap - > position ,
undecided_hold_tap = NULL ;
hold_tap - > is_hold ? " hold " : " tap " ,
flavor_str ( hold_tap - > config - > flavor ) ,
struct zmk_behavior_binding * behavior ;
event ) ;
if ( hold_tap - > is_hold ) {
undecided_hold_tap = NULL ;
behavior = & hold_tap - > config - > behaviors - > hold ;
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
struct zmk_behavior_binding * behavior ;
behavior_keymap_binding_pressed ( behavior_device , hold_tap - > position , hold_tap - > param_hold ,
if ( hold_tap - > is_hold ) {
0 ) ;
behavior = & hold_tap - > config - > behaviors - > hold ;
} else {
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
behavior = & hold_tap - > config - > behaviors - > tap ;
behavior_keymap_binding_pressed ( behavior_device , hold_tap - > position , hold_tap - > param_hold , 0 ) ;
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
} else {
behavior_keymap_binding_pressed ( behavior_device , hold_tap - > position , hold_tap - > param_tap ,
behavior = & hold_tap - > config - > behaviors - > tap ;
0 ) ;
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
}
behavior_keymap_binding_pressed ( behavior_device , hold_tap - > position , hold_tap - > param_tap , 0 ) ;
release_captured_events ( ) ;
}
release_captured_events ( ) ;
}
}
static int on_hold_tap_binding_pressed ( struct device * dev , u32_t position , u32_t param_hold , u32_t param_tap )
static int on_hold_tap_binding_pressed ( struct device * dev , u32_t position , u32_t param_hold ,
{
u32_t param_tap ) {
const struct behavior_hold_tap_config * cfg = dev - > config_info ;
const struct behavior_hold_tap_config * cfg = dev - > config_info ;
if ( undecided_hold_tap ! = NULL ) {
if ( undecided_hold_tap ! = NULL ) {
LOG_DBG ( " ERROR another hold-tap behavior is undecided. " ) ;
LOG_DBG ( " ERROR another hold-tap behavior is undecided. " ) ;
// if this happens, make sure the behavior events occur AFTER other position events.
// if this happens, make sure the behavior events occur AFTER other position events.
return 0 ;
return 0 ;
}
}
struct active_hold_tap * hold_tap = store_hold_tap ( position , param_hold , param_tap , cfg ) ;
struct active_hold_tap * hold_tap = store_hold_tap ( position , param_hold , param_tap , cfg ) ;
if ( hold_tap = = NULL ) {
if ( hold_tap = = NULL ) {
LOG_ERR ( " unable to store hold-tap info, did you press more than %d hold-taps? " , ZMK_BHV_HOLD_TAP_MAX_HELD ) ;
LOG_ERR ( " unable to store hold-tap info, did you press more than %d hold-taps? " ,
return 0 ;
ZMK_BHV_HOLD_TAP_MAX_HELD ) ;
}
return 0 ;
}
LOG_DBG ( " %d new undecided hold_tap " , position ) ;
LOG_DBG ( " %d new undecided hold_tap " , position ) ;
undecided_hold_tap = hold_tap ;
undecided_hold_tap = hold_tap ;
k_delayed_work_submit ( & hold_tap - > work , cfg - > tapping_term_ms ( ) ) ;
k_delayed_work_submit ( & hold_tap - > work , cfg - > tapping_term_ms ( ) ) ;
// todo: once we get timing info for keypresses, start the timer relative to the original keypress
// todo: once we get timing info for keypresses, start the timer relative to the original
// don't forget to simulate a timer-event before the event after that time was handled.
// keypress don't forget to simulate a timer-event before the event after that time was handled.
return 0 ;
return 0 ;
}
}
static int on_hold_tap_binding_released ( struct device * dev , u32_t position , u32_t _ , u32_t __ )
static int on_hold_tap_binding_released ( struct device * dev , u32_t position , u32_t _ , u32_t __ ) {
{
struct active_hold_tap * hold_tap = find_hold_tap ( position ) ;
struct active_hold_tap * hold_tap = find_hold_tap ( position ) ;
if ( hold_tap = = NULL ) {
if ( hold_tap = = NULL ) {
LOG_ERR ( " ACTIVE_HOLD_TAP_CLEANED_UP_TOO_EARLY " ) ;
LOG_ERR ( " ACTIVE_HOLD_TAP_CLEANED_UP_TOO_EARLY " ) ;
return 0 ;
return 0 ;
}
}
int work_cancel_result = k_delayed_work_cancel ( & hold_tap - > work ) ;
int work_cancel_result = k_delayed_work_cancel ( & hold_tap - > work ) ;
decide_hold_tap ( hold_tap , HT_KEY_UP ) ;
decide_hold_tap ( hold_tap , HT_KEY_UP ) ;
struct zmk_behavior_binding * behavior ;
struct zmk_behavior_binding * behavior ;
if ( hold_tap - > is_hold ) {
if ( hold_tap - > is_hold ) {
behavior = & hold_tap - > config - > behaviors - > hold ;
behavior = & hold_tap - > config - > behaviors - > hold ;
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
behavior_keymap_binding_released ( behavior_device , hold_tap - > position , hold_tap - > param_hold ,
behavior_keymap_binding_released ( behavior_device , hold_tap - > position , hold_tap - > param_hold , 0 ) ;
0 ) ;
} else {
} else {
behavior = & hold_tap - > config - > behaviors - > tap ;
behavior = & hold_tap - > config - > behaviors - > tap ;
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
struct device * behavior_device = device_get_binding ( behavior - > behavior_dev ) ;
behavior_keymap_binding_released ( behavior_device , hold_tap - > position , hold_tap - > param_tap , 0 ) ;
behavior_keymap_binding_released ( behavior_device , hold_tap - > position , hold_tap - > param_tap ,
}
0 ) ;
}
if ( work_cancel_result = = - EINPROGRESS ) {
if ( work_cancel_result = = - EINPROGRESS ) {
// let the timer handler clean up
// let the timer handler clean up
// if we'd clear now, the timer may call back for an uninitialized active_hold_tap.
// if we'd clear now, the timer may call back for an uninitialized active_hold_tap.
LOG_DBG ( " %d hold-tap timer work in event queue " , position ) ;
LOG_DBG ( " %d hold-tap timer work in event queue " , position ) ;
hold_tap - > work_is_cancelled = true ;
hold_tap - > work_is_cancelled = true ;
} else {
} else {
LOG_DBG ( " %d cleaning up hold-tap " , position ) ;
LOG_DBG ( " %d cleaning up hold-tap " , position ) ;
clear_hold_tap ( hold_tap ) ;
clear_hold_tap ( hold_tap ) ;
}
}
return 0 ;
return 0 ;
}
}
static const struct behavior_driver_api behavior_hold_tap_driver_api = {
static const struct behavior_driver_api behavior_hold_tap_driver_api = {
. binding_pressed = on_hold_tap_binding_pressed ,
. binding_pressed = on_hold_tap_binding_pressed ,
. binding_released = on_hold_tap_binding_released ,
. binding_released = on_hold_tap_binding_released ,
} ;
} ;
static int position_state_changed_listener ( const struct zmk_event_header * eh ) {
static int position_state_changed_listener ( const struct zmk_event_header * eh )
struct position_state_changed * ev = cast_position_state_changed ( eh ) ;
{
struct position_state_changed * ev = cast_position_state_changed ( eh ) ;
if ( undecided_hold_tap = = NULL ) {
LOG_DBG ( " %d bubble (no undecided hold_tap active) " , ev - > position ) ;
if ( undecided_hold_tap = = NULL ) {
return 0 ;
LOG_DBG ( " %d bubble (no undecided hold_tap active) " , ev - > position ) ;
}
return 0 ;
}
if ( undecided_hold_tap - > position = = ev - > position ) {
if ( ev - > state ) { // keydown
if ( undecided_hold_tap - > position = = ev - > position ) {
LOG_ERR ( " hold-tap listener should be called before before most other listeners! " ) ;
if ( ev - > state ) { // keydown
return 0 ;
LOG_ERR ( " hold-tap listener should be called before before most other listeners! " ) ;
} else { // keyup
return 0 ;
LOG_DBG ( " %d bubble undecided hold-tap keyrelease event " , undecided_hold_tap - > position ) ;
} else { // keyup
return 0 ;
LOG_DBG ( " %d bubble undecided hold-tap keyrelease event " , undecided_hold_tap - > position ) ;
}
return 0 ;
}
}
}
if ( ! ev - > state & & find_captured_keydown_event ( ev - > position ) = = NULL ) {
// no keydown event has been captured, let it bubble.
if ( ! ev - > state & & find_captured_keydown_event ( ev - > position ) = = NULL ) {
// we'll catch modifiers later in modifier_state_changed_listener
// no keydown event has been captured, let it bubble.
LOG_DBG ( " %d bubbling %d %s event " , undecided_hold_tap - > position , ev - > position ,
// we'll catch modifiers later in modifier_state_changed_listener
ev - > state ? " down " : " up " ) ;
LOG_DBG ( " %d bubbling %d %s event " , undecided_hold_tap - > position , ev - > position , ev - > state ? " down " : " up " ) ;
return 0 ;
return 0 ;
}
}
LOG_DBG ( " %d capturing %d %s event " , undecided_hold_tap - > position , ev - > position ,
LOG_DBG ( " %d capturing %d %s event " , undecided_hold_tap - > position , ev - > position , ev - > state ? " down " : " up " ) ;
ev - > state ? " down " : " up " ) ;
capture_event ( eh ) ;
capture_event ( eh ) ;
decide_hold_tap ( undecided_hold_tap , ev - > state ? HT_OTHER_KEY_DOWN : HT_OTHER_KEY_UP ) ;
decide_hold_tap ( undecided_hold_tap , ev - > state ? HT_OTHER_KEY_DOWN : HT_OTHER_KEY_UP ) ;
return ZMK_EV_EVENT_CAPTURED ;
return ZMK_EV_EVENT_CAPTURED ;
}
}
static bool is_mod ( struct keycode_state_changed * ev )
static bool is_mod ( struct keycode_state_changed * ev ) {
{
return ev - > usage_page = = USAGE_KEYPAD & & ev - > keycode > = LCTL & & ev - > keycode < = RGUI ;
return ev - > usage_page = = USAGE_KEYPAD & & ev - > keycode > = LCTL & & ev - > keycode < = RGUI ;
}
}
static int keycode_state_changed_listener ( const struct zmk_event_header * eh )
static int keycode_state_changed_listener ( const struct zmk_event_header * eh ) {
{
// we want to catch layer-up events too... how?
// we want to catch layer-up events too... how?
struct keycode_state_changed * ev = cast_keycode_state_changed ( eh ) ;
struct keycode_state_changed * ev = cast_keycode_state_changed ( eh ) ;
if ( undecided_hold_tap = = NULL ) {
if ( undecided_hold_tap = = NULL ) {
// LOG_DBG("0x%02X bubble (no undecided hold_tap active)", ev->keycode);
// LOG_DBG("0x%02X bubble (no undecided hold_tap active)", ev->keycode);
return 0 ;
return 0 ;
}
}
if ( ! is_mod ( ev ) ) {
if ( ! is_mod ( ev ) ) {
// LOG_DBG("0x%02X bubble (not a mod)", ev->keycode);
// LOG_DBG("0x%02X bubble (not a mod)", ev->keycode);
return 0 ;
return 0 ;
}
}
// only key-up events will bubble through position_state_changed_listener
// only key-up events will bubble through position_state_changed_listener
// if a undecided_hold_tap is active.
// if a undecided_hold_tap is active.
LOG_DBG ( " %d capturing 0x%02X %s event " , undecided_hold_tap - > position , ev - > keycode ,
LOG_DBG ( " %d capturing 0x%02X %s event " , undecided_hold_tap - > position , ev - > keycode , ev - > state ? " down " : " up " ) ;
ev - > state ? " down " : " up " ) ;
capture_event ( eh ) ;
capture_event ( eh ) ;
return ZMK_EV_EVENT_CAPTURED ;
return ZMK_EV_EVENT_CAPTURED ;
}
}
int behavior_hold_tap_listener ( const struct zmk_event_header * eh ) {
int behavior_hold_tap_listener ( const struct zmk_event_header * eh )
if ( is_position_state_changed ( eh ) ) {
{
return position_state_changed_listener ( eh ) ;
if ( is_position_state_changed ( eh ) ) {
} else if ( is_keycode_state_changed ( eh ) ) {
return position_state_changed_listener ( eh ) ;
return keycode_state_changed_listener ( eh ) ;
} else if ( is_keycode_state_changed ( eh ) ) {
}
return keycode_state_changed_listener ( eh ) ;
return 0 ;
}
return 0 ;
}
}
ZMK_LISTENER ( behavior_hold_tap , behavior_hold_tap_listener ) ;
ZMK_LISTENER ( behavior_hold_tap , behavior_hold_tap_listener ) ;
@ -443,60 +437,56 @@ ZMK_SUBSCRIPTION(behavior_hold_tap, position_state_changed);
// this should be modifiers_state_changed, but unfrotunately that's not implemented yet.
// this should be modifiers_state_changed, but unfrotunately that's not implemented yet.
ZMK_SUBSCRIPTION ( behavior_hold_tap , keycode_state_changed ) ;
ZMK_SUBSCRIPTION ( behavior_hold_tap , keycode_state_changed ) ;
void behavior_hold_tap_timer_work_handler ( struct k_work * item )
void behavior_hold_tap_timer_work_handler ( struct k_work * item ) {
{
struct active_hold_tap * hold_tap = CONTAINER_OF ( item , struct active_hold_tap , work ) ;
struct active_hold_tap * hold_tap = CONTAINER_OF ( item , struct active_hold_tap , work ) ;
if ( hold_tap - > work_is_cancelled ) {
if ( hold_tap - > work_is_cancelled ) {
clear_hold_tap ( hold_tap ) ;
clear_hold_tap ( hold_tap ) ;
} else {
} else {
decide_hold_tap ( hold_tap , HT_TIMER_EVENT ) ;
decide_hold_tap ( hold_tap , HT_TIMER_EVENT ) ;
}
}
}
}
static int behavior_hold_tap_init ( struct device * dev )
static int behavior_hold_tap_init ( struct device * dev ) {
{
static bool init_first_run = true ;
static bool init_first_run = true ;
if ( init_first_run ) {
if ( init_first_run ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_HELD ; i + + ) {
for ( int i = 0 ; i < ZMK_BHV_HOLD_TAP_MAX_HELD ; i + + ) {
k_delayed_work_init ( & active_hold_taps [ i ] . work , behavior_hold_tap_timer_work_handler ) ;
k_delayed_work_init ( & active_hold_taps [ i ] . work , behavior_hold_tap_timer_work_handler ) ;
active_hold_taps [ i ] . position = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED ;
active_hold_taps [ i ] . position = ZMK_BHV_HOLD_TAP_POSITION_NOT_USED ;
}
}
}
}
init_first_run = false ;
init_first_run = false ;
return 0 ;
return 0 ;
}
}
struct behavior_hold_tap_data { } ;
struct behavior_hold_tap_data { } ;
static struct behavior_hold_tap_data behavior_hold_tap_data ;
static struct behavior_hold_tap_data behavior_hold_tap_data ;
# define _TRANSFORM_ENTRY(idx, node) \
# define _TRANSFORM_ENTRY(idx, node) \
{ \
{ \
. behavior_dev = DT_LABEL ( DT_INST_PHANDLE_BY_IDX ( node , bindings , idx ) ) , \
. 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 ) ) ) , \
. param1 = COND_CODE_0 ( DT_INST_PHA_HAS_CELL_AT_IDX ( node , bindings , idx , param1 ) , ( 0 ) , \
. 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 ) ) ) , \
( 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_hold_tap_config_ # # n # # _gettime ( ) { return K_MSEC ( DT_INST_PROP ( n , tapping_term_ms ) ) ; } \
static struct behavior_hold_tap_behaviors behavior_hold_tap_behaviors_ # # n = { \
# define KP_INST(n) \
. hold = _TRANSFORM_ENTRY ( 0 , n ) \
static k_timeout_t behavior_hold_tap_config_ # # n # # _gettime ( ) { \
. tap = _TRANSFORM_ENTRY ( 1 , n ) \
return K_MSEC ( DT_INST_PROP ( n , tapping_term_ms ) ) ; \
} ; \
} \
static struct behavior_hold_tap_config behavior_hold_tap_config_ # # n = { \
static struct behavior_hold_tap_behaviors behavior_hold_tap_behaviors_ # # n = { \
. behaviors = & behavior_hold_tap_behaviors_ # # n , \
. hold = _TRANSFORM_ENTRY ( 0 , n ) . tap = _TRANSFORM_ENTRY ( 1 , n ) } ; \
. tapping_term_ms = & behavior_hold_tap_config_ # # n # # _gettime , \
static struct behavior_hold_tap_config behavior_hold_tap_config_ # # n = { \
. flavor = DT_ENUM_IDX ( DT_DRV_INST ( n ) , flavor ) , \
. behaviors = & behavior_hold_tap_behaviors_ # # n , \
} ; \
. tapping_term_ms = & behavior_hold_tap_config_ # # n # # _gettime , \
DEVICE_AND_API_INIT ( \
. flavor = DT_ENUM_IDX ( DT_DRV_INST ( n ) , flavor ) , \
behavior_hold_tap_ # # n , DT_INST_LABEL ( n ) , behavior_hold_tap_init , \
} ; \
& behavior_hold_tap_data , \
DEVICE_AND_API_INIT ( behavior_hold_tap_ # # n , DT_INST_LABEL ( n ) , behavior_hold_tap_init , \
& behavior_hold_tap_config_ # # n , \
& behavior_hold_tap_data , & behavior_hold_tap_config_ # # n , APPLICATION , \
APPLICATION , CONFIG_KERNEL_INIT_PRIORITY_DEFAULT , \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT , & behavior_hold_tap_driver_api ) ;
& behavior_hold_tap_driver_api ) ;
DT_INST_FOREACH_STATUS_OKAY ( KP_INST )
DT_INST_FOREACH_STATUS_OKAY ( KP_INST )
# endif
# endif