diff --git a/doc-dev/reference-manual.md b/doc-dev/reference-manual.md index 8c000a940..a3662324d 100644 --- a/doc-dev/reference-manual.md +++ b/doc-dev/reference-manual.md @@ -148,6 +148,7 @@ COMMAND = set module.touchpad.holdContinuationTimeout <0-65535 (INT)> COMMAND = set secondaryRole.defaultStrategy { simple | advanced } COMMAND = set secondaryRole.advanced.timeout COMMAND = set secondaryRole.advanced.timeoutAction { primary | secondary | none } +COMMAND = set secondaryRole.advanced.timeoutType { active | passive } COMMAND = set secondaryRole.advanced.safetyMargin COMMAND = set secondaryRole.advanced.triggeringEvent { press | release | none } COMMAND = set secondaryRole.advanced.triggerByMouse ` if this timeout is reached, `timeoutAction` (secondary by default) role is activated. - `set secondaryRole.advanced.timeoutAction { primary | secondary | none}` defines whether the primary action or the secondary role, or no action at all, should be activated when timeout is reached + - `set secondaryRole.advanced.timeoutType { active | passive }` defines whether the secondary role should perform it's timeout action when the timeout is reached or when the key is released after having been pressed beyond timeout without triggering secondary - `set secondaryRole.advanced.triggeringEvent { press | release | none }` determines whether a secondary role key is triggered as secondary if another key is pressed while it's held, if another key is pressed and released while it's held, or not at all from other keys being held. The third option allows usage of the timeout feature without inter-key triggers. - `set secondaryRole.advanced.triggerByMouse BOOL` if enabled, any mouse (module) activity triggers secondary role immediately. - `set secondaryRole.advanced.minimumHoldTime ` sets the minimum time that a key must be held before it is allowed to trigger as secondary role. diff --git a/doc-dev/user-guide.md b/doc-dev/user-guide.md index 2578b4e9d..482142f1e 100644 --- a/doc-dev/user-guide.md +++ b/doc-dev/user-guide.md @@ -479,9 +479,10 @@ Firngrod's Home Row Mods (HRM) configuration (Recommended): set secondaryRole.defaultStrategy advanced set secondaryRole.advanced.triggeringEvent release set secondaryRole.advanced.timeout 300 -set secondaryRole.advanced.timeoutAction secondary +set secondaryRole.advanced.timeoutType passive +set secondaryRole.advanced.timeoutAction none set secondaryRole.advanced.doubletapToPrimary 1 -set secondaryRole.advanced.safetyMargin -100 +set secondaryRole.advanced.safetyMargin -40 set secondaryRole.advanced.triggerByMouse 1 set secondaryRole.advanced.minimumHoldTime 150 set secondaryRole.advanced.acceptTriggersFromSameHalf 0 @@ -491,8 +492,9 @@ Full set of advanced strategy config values follows (for copy-paste convenience) ``` set secondaryRole.defaultStrategy advanced -set secondaryRole.advanced.timeout 500 +set secondaryRole.advanced.timeout 200 set secondaryRole.advanced.timeoutAction secondary +set secondaryRole.advanced.timeoutType active set secondaryRole.advanced.safetyMargin 0 set secondaryRole.advanced.triggeringEvent press set secondaryRole.advanced.triggerByMouse 0 diff --git a/right/src/config_manager.c b/right/src/config_manager.c index 0e3de1371..2c7714580 100644 --- a/right/src/config_manager.c +++ b/right/src/config_manager.c @@ -221,6 +221,7 @@ const config_t DefaultCfg = (config_t){ .SecondaryRoles_AdvancedStrategyMinimumHoldTime = 0, .SecondaryRoles_AdvancedStrategyDoubletapToPrimary = true, .SecondaryRoles_AdvancedStrategyTimeoutAction = SecondaryRoleState_Secondary, + .SecondaryRoles_AdvancedStrategyTimeoutType = SecondaryRoleTimeoutType_Active, .SecondaryRoles_AdvancedStrategyAcceptTriggersFromSameHalf = true, .SecondaryRoles_Strategy = SecondaryRoleStrategy_Simple, .StickyModifierStrategy = Stick_Smart, diff --git a/right/src/config_manager.h b/right/src/config_manager.h index 265c827e5..7553576f5 100644 --- a/right/src/config_manager.h +++ b/right/src/config_manager.h @@ -38,6 +38,7 @@ bool SecondaryRoles_AdvancedStrategyTriggerByMouse; bool SecondaryRoles_AdvancedStrategyDoubletapToPrimary; secondary_role_state_t SecondaryRoles_AdvancedStrategyTimeoutAction; + secondary_role_timeout_type_t SecondaryRoles_AdvancedStrategyTimeoutType; uint8_t SecondaryRoles_AdvancedStrategyMinimumHoldTime; // mouse keys diff --git a/right/src/key_states.h b/right/src/key_states.h index e9e44e3d4..02df3e9a3 100644 --- a/right/src/key_states.h +++ b/right/src/key_states.h @@ -45,21 +45,21 @@ // Inline functions - static inline bool KeyState_Active(key_state_t* s) { return s->current; }; - static inline bool KeyState_Inactive(key_state_t* s) { return !s->current; }; - static inline bool KeyState_ActivatedNow(key_state_t* s) { return !s->previous && s->current; }; - static inline bool KeyState_DeactivatedNow(key_state_t* s) { return s->previous && !s->current; }; - static inline bool KeyState_ActivatedEarlier(key_state_t* s) { return s->previous && s->current; }; - static inline bool KeyState_DeactivatedEarlier(key_state_t* s) { return !s->previous && !s->current; }; - static inline bool KeyState_NonZero(key_state_t* s) { return s->previous || s->current; }; + static inline bool KeyState_Active(const key_state_t* s) { return s->current; }; + static inline bool KeyState_Inactive(const key_state_t* s) { return !s->current; }; + static inline bool KeyState_ActivatedNow(const key_state_t* s) { return !s->previous && s->current; }; + static inline bool KeyState_DeactivatedNow(const key_state_t* s) { return s->previous && !s->current; }; + static inline bool KeyState_ActivatedEarlier(const key_state_t* s) { return s->previous && s->current; }; + static inline bool KeyState_DeactivatedEarlier(const key_state_t* s) { return !s->previous && !s->current; }; + static inline bool KeyState_NonZero(const key_state_t* s) { return s->previous || s->current; }; - static inline bool KeyState_IsRightHalf(key_state_t* s) { return s < &KeyStates[1][0]; }; - static inline bool KeyState_IsLeftHalf(key_state_t* s) { return s < &KeyStates[2][0] && s >= &KeyStates[1][0]; }; - static inline bool KeyState_IsKeyCluster(key_state_t* s) { return s >= &KeyStates[2][0] && s < &KeyStates[3][0]; }; - static inline bool KeyState_IsMouseModule(key_state_t* s) { return s >= &KeyStates[3][0]; }; - static inline bool KeyState_IsModule(key_state_t* s) { return s >= &KeyStates[2][0]; }; - static inline bool KeyState_IsRightSide(key_state_t* s) { return KeyState_IsRightHalf(s) || KeyState_IsMouseModule(s); }; - static inline bool KeyState_IsLeftSide(key_state_t* s) { return s >= &KeyStates[1][0] && s < &KeyStates[3][0]; }; + static inline bool KeyState_IsRightHalf(const key_state_t* s) { return s < &KeyStates[1][0]; }; + static inline bool KeyState_IsLeftHalf(const key_state_t* s) { return s < &KeyStates[2][0] && s >= &KeyStates[1][0]; }; + static inline bool KeyState_IsKeyCluster(const key_state_t* s) { return s >= &KeyStates[2][0] && s < &KeyStates[3][0]; }; + static inline bool KeyState_IsMouseModule(const key_state_t* s) { return s >= &KeyStates[3][0]; }; + static inline bool KeyState_IsModule(const key_state_t* s) { return s >= &KeyStates[2][0]; }; + static inline bool KeyState_IsRightSide(const key_state_t* s) { return KeyState_IsRightHalf(s) || KeyState_IsMouseModule(s); }; + static inline bool KeyState_IsLeftSide(const key_state_t* s) { return s >= &KeyStates[1][0] && s < &KeyStates[3][0]; }; #endif diff --git a/right/src/macros/key_timing.c b/right/src/macros/key_timing.c index 3181aafa1..4d4a5b22e 100644 --- a/right/src/macros/key_timing.c +++ b/right/src/macros/key_timing.c @@ -1,6 +1,7 @@ #include "macros/key_timing.h" #include "key_states.h" #include "macros/status_buffer.h" +#include "secondary_role_driver.h" #include "utils.h" #include "timer.h" @@ -23,7 +24,7 @@ void KeyTiming_RecordReport(usb_basic_keyboard_report_t* report) Utils_PrintReport(" OUT", ActiveUsbBasicKeyboardReport); } -void KeyTiming_RecordComment(key_state_t* keyState, const char* comment) +void KeyTiming_RecordComment(key_state_t* keyState, secondary_role_state_t state, int32_t resolutionLine) { const char* keyAbbreviation = Utils_KeyAbbreviation(keyState); @@ -31,7 +32,23 @@ void KeyTiming_RecordComment(key_state_t* keyState, const char* comment) Macros_SetStatusChar(' '); Macros_SetStatusString(keyAbbreviation, NULL); Macros_SetStatusChar(' '); - Macros_SetStatusString(comment, NULL); + switch (state) { + case SecondaryRoleState_Primary: + Macros_SetStatusChar('P'); + break; + case SecondaryRoleState_Secondary: + Macros_SetStatusChar('S'); + break; + case SecondaryRoleState_NoOp: + Macros_SetStatusChar('N'); + break; + case SecondaryRoleState_DontKnowYet: + Macros_SetStatusChar('D'); + break; + } + Macros_SetStatusChar(':'); + Macros_SetStatusNumSpaced(resolutionLine, false); Macros_SetStatusChar('\n'); + } diff --git a/right/src/macros/key_timing.h b/right/src/macros/key_timing.h index 5b2a9bfda..4f6235e78 100644 --- a/right/src/macros/key_timing.h +++ b/right/src/macros/key_timing.h @@ -10,8 +10,6 @@ #define KEY_TIMING(code) if (RecordKeyTiming) { code; } -#define KEY_TIMING2(condition, code) if (RecordKeyTiming && condition) { code; } - // Typedefs: @@ -23,6 +21,6 @@ void KeyTiming_RecordKeystroke(key_state_t *keyState, bool active, uint32_t pressTime, uint32_t activationTime); void KeyTiming_RecordReport(usb_basic_keyboard_report_t* report); -void KeyTiming_RecordComment(key_state_t* keyState, const char* comment); +void KeyTiming_RecordComment(key_state_t* keyState, secondary_role_state_t state, int32_t resolutionLine); #endif diff --git a/right/src/macros/set_command.c b/right/src/macros/set_command.c index e6411590f..b740b3875 100644 --- a/right/src/macros/set_command.c +++ b/right/src/macros/set_command.c @@ -296,6 +296,10 @@ static macro_variable_t secondaryRoleAdvanced(parser_context_t* ctx, set_command DEFINE_NONE_LIMITS(); ASSIGN_CUSTOM(int32_t, intVar, Cfg.SecondaryRoles_AdvancedStrategyTimeoutAction, ConsumeSecondaryRoleTimeoutAction(ctx)); } + else if (ConsumeToken(ctx, "timeoutType")) { + DEFINE_NONE_LIMITS(); + ASSIGN_CUSTOM(int32_t, intVar, Cfg.SecondaryRoles_AdvancedStrategyTimeoutType, ConsumeSecondaryRoleTimeoutType(ctx)); + } else if (ConsumeToken(ctx, "safetyMargin")) { DEFINE_INT_LIMITS(-32768, 32767); ASSIGN_INT(Cfg.SecondaryRoles_AdvancedStrategySafetyMargin); diff --git a/right/src/postponer.c b/right/src/postponer.c index 071793218..aa5689b20 100644 --- a/right/src/postponer.c +++ b/right/src/postponer.c @@ -404,33 +404,22 @@ void PostponerQuery_InfoByKeystate(key_state_t* key, postponer_buffer_record_typ } } -void PostponerQuery_FindFirstPressed(postponer_buffer_record_type_t** press, postponer_buffer_record_type_t** release, - key_state_t *opposingKey) +void PostponerQuery_FindFirstPressed(const postponer_buffer_record_type_t** press, const key_state_t *opposingKey) { bool opposingIsRight = KeyState_IsRightSide(opposingKey); for ( int i = 0; i < bufferSize; i++ ) { *press = &buffer[POS(i)]; if ((*press)->event.type == PostponerEventType_PressKey) { if (opposingKey == NULL || opposingIsRight != KeyState_IsRightSide((*press)->event.key.keyState)) { - for ( int j = i + 1; j < bufferSize; j++ ) { - *release = &buffer[POS(j)]; - if ((*release)->event.type == PostponerEventType_ReleaseKey - && (*press)->event.key.keyState == (*release)->event.key.keyState ) { - return; - } - } - *release = NULL; return; } } } - *release = NULL; *press = NULL; return; } -void PostponerQuery_FindFirstReleased(postponer_buffer_record_type_t** press, postponer_buffer_record_type_t** release, - key_state_t *opposingKey) +void PostponerQuery_FindFirstReleased(const postponer_buffer_record_type_t** release, const key_state_t *opposingKey) { bool opposingIsRight = KeyState_IsRightSide(opposingKey); if (bufferSize > 1) { @@ -439,9 +428,9 @@ void PostponerQuery_FindFirstReleased(postponer_buffer_record_type_t** press, po if ((*release)->event.type == PostponerEventType_ReleaseKey) { if (opposingKey == NULL || opposingIsRight != KeyState_IsRightSide((*release)->event.key.keyState)) { for ( int j = 0; j < i; j++ ) { - *press = &buffer[POS(j)]; - if ((*press)->event.type == PostponerEventType_PressKey - && (*press)->event.key.keyState == (*release)->event.key.keyState ) { + const postponer_buffer_record_type_t * const press = &buffer[POS(j)]; + if (press->event.type == PostponerEventType_PressKey + && press->event.key.keyState == (*release)->event.key.keyState ) { return; } } @@ -449,7 +438,6 @@ void PostponerQuery_FindFirstReleased(postponer_buffer_record_type_t** press, po } } } - *press = NULL; *release = NULL; return; } diff --git a/right/src/postponer.h b/right/src/postponer.h index 0ff44cefc..b2816533c 100644 --- a/right/src/postponer.h +++ b/right/src/postponer.h @@ -88,8 +88,8 @@ bool PostponerQuery_IsActiveEventually(key_state_t* key); void PostponerQuery_InfoByKeystate(key_state_t* key, postponer_buffer_record_type_t** press, postponer_buffer_record_type_t** release); bool PostponerQuery_ContainsKeyId(uint8_t keyid); - void PostponerQuery_FindFirstPressed(postponer_buffer_record_type_t** press, postponer_buffer_record_type_t** release, key_state_t* opposingKey); - void PostponerQuery_FindFirstReleased(postponer_buffer_record_type_t** press, postponer_buffer_record_type_t** release, key_state_t* opposingKey); + void PostponerQuery_FindFirstPressed(const postponer_buffer_record_type_t** press, const key_state_t* opposingKey); + void PostponerQuery_FindFirstReleased(const postponer_buffer_record_type_t** release, const key_state_t* opposingKey); // Functions (Query APIs extended): uint16_t PostponerExtended_PendingId(uint16_t idx); diff --git a/right/src/secondary_role_driver.c b/right/src/secondary_role_driver.c index 16d56fc91..35c22001d 100644 --- a/right/src/secondary_role_driver.c +++ b/right/src/secondary_role_driver.c @@ -1,5 +1,6 @@ #include "secondary_role_driver.h" #include "macros/core.h" +#include "macros/status_buffer.h" #include "postponer.h" #include "timer.h" #include "utils.h" @@ -64,9 +65,10 @@ static bool resolutionCallerIsMacroEngine = false; static bool acceptTriggersFromSameHalf = true; static key_state_t *resolutionKey; static uint32_t resolutionStartTime; +static bool isDoubletap = false; +static bool currentlyResolving = false; + -static key_state_t *previousResolutionKey; -static uint32_t previousResolutionTime; static bool activateSecondaryImmediately; static inline void fakeActivation() @@ -97,143 +99,114 @@ static void sleepTimeoutStrategy(uint16_t wakeTimeOffset) { } } + +#define RESOLVED(resolution) KEY_TIMING(KeyTiming_RecordComment(resolutionKey, resolution, __LINE__)) \ + return resolution; +#define AWAITEVENT(timeout) sleepTimeoutStrategy(timeout); \ + return SecondaryRoleState_DontKnowYet; + static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout() { - //gather data postponer_buffer_record_type_t *dummy; postponer_buffer_record_type_t *dualRoleRelease; - postponer_buffer_record_type_t *actionPress = NULL; - postponer_buffer_record_type_t *actionRelease; - key_state_t* opposingKey = acceptTriggersFromSameHalf ? NULL : resolutionKey; - PostponerQuery_InfoByKeystate(resolutionKey, &dummy, &dualRoleRelease); - if (Cfg.SecondaryRoles_AdvancedStrategyTriggeringEvent == SecondaryRoleTriggeringEvent_Release) { - PostponerQuery_FindFirstReleased(&actionPress, &actionRelease, opposingKey); - - } - if (Cfg.SecondaryRoles_AdvancedStrategyTriggeringEvent != SecondaryRoleTriggeringEvent_None && actionPress == NULL) { - PostponerQuery_FindFirstPressed(&actionPress, &actionRelease, opposingKey); - } + const int32_t activeTime = (dualRoleRelease == NULL ? Timer_GetCurrentTime() : dualRoleRelease->time) - resolutionStartTime; - int32_t activeTime = (dualRoleRelease == NULL ? Timer_GetCurrentTime() : dualRoleRelease->time) - resolutionStartTime; - bool dualRoleWasHeldLongEnoughToBeAllowedSecondary = activeTime >= Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime; + // handle things we can know without another key + // doubletap and active timeout + const bool reachedTimeout = activeTime > Cfg.SecondaryRoles_AdvancedStrategyTimeout; + const bool isActiveTimeout = Cfg.SecondaryRoles_AdvancedStrategyTimeoutType == SecondaryRoleTimeoutType_Active; - if (dualRoleRelease != NULL && !dualRoleWasHeldLongEnoughToBeAllowedSecondary) { - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "PA")); - return SecondaryRoleState_Primary; - } - - // handle timeout when action key is not pressed - if (actionPress == NULL) { - if (dualRoleRelease != NULL && activeTime < Cfg.SecondaryRoles_AdvancedStrategyTimeout) { - //activate primary - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "PB")); - return SecondaryRoleState_Primary; - } else if (activeTime >= Cfg.SecondaryRoles_AdvancedStrategyTimeout) { - // doubletap logic - bool shouldPrimaryBecauseOfDoubletap = - Cfg.SecondaryRoles_AdvancedStrategyDoubletapToPrimary - && resolutionKey == previousResolutionKey - && resolutionStartTime - previousResolutionTime < Cfg.SecondaryRoles_AdvancedStrategyDoubletapTimeout; - if(shouldPrimaryBecauseOfDoubletap) { - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "PC")); - return SecondaryRoleState_Primary; - } - // activate configured timeout action - switch (Cfg.SecondaryRoles_AdvancedStrategyTimeoutAction) { - case SecondaryRoleState_Primary: - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "PD")); - return SecondaryRoleState_Primary; - case SecondaryRoleState_Secondary: - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "SA")); - return SecondaryRoleState_Secondary; - case SecondaryRoleState_NoOp: - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "NA")); - return SecondaryRoleState_NoOp; - default: - sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyTimeout); - return SecondaryRoleState_DontKnowYet; - } - } else { - sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyTimeout); - return SecondaryRoleState_DontKnowYet; + if (reachedTimeout) { + if (isDoubletap && Cfg.SecondaryRoles_AdvancedStrategyDoubletapToPrimary) { + RESOLVED(SecondaryRoleState_Primary); + } + if (isActiveTimeout) { + RESOLVED(Cfg.SecondaryRoles_AdvancedStrategyTimeoutAction); } } - //handle trigger by press - if (Cfg.SecondaryRoles_AdvancedStrategyTriggeringEvent == SecondaryRoleTriggeringEvent_Press) { - bool actionKeyWasPressedButDualkeyNot = actionPress != NULL && dualRoleRelease == NULL && (int32_t)(Timer_GetCurrentTime() - actionPress->time) > Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; - bool actionKeyWasPressedFirst = actionPress != NULL && dualRoleRelease != NULL && actionPress->time <= dualRoleRelease->time - Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; + // see if we have an activating key event + const postponer_buffer_record_type_t *actionEvent = NULL; + const key_state_t * const opposingKey = acceptTriggersFromSameHalf ? NULL : resolutionKey; - if (actionKeyWasPressedButDualkeyNot || actionKeyWasPressedFirst) { - if(!dualRoleWasHeldLongEnoughToBeAllowedSecondary){ - sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime); - return SecondaryRoleState_DontKnowYet; - } - KEY_TIMING2(actionKeyWasPressedButDualkeyNot, KeyTiming_RecordComment(resolutionKey, "SB")); - KEY_TIMING2(actionKeyWasPressedFirst, KeyTiming_RecordComment(resolutionKey, "SC")); - return SecondaryRoleState_Secondary; - } - - bool dualKeyWasPressedButActionKeyNot = dualRoleRelease != NULL && (actionPress == NULL && (int32_t)(Timer_GetCurrentTime() - dualRoleRelease->time) > -Cfg.SecondaryRoles_AdvancedStrategySafetyMargin); - bool dualKeyWasPressedFirst = actionPress != NULL && dualRoleRelease != NULL && actionPress->time >= dualRoleRelease->time - Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; - if (dualKeyWasPressedFirst || dualKeyWasPressedButActionKeyNot) { - KEY_TIMING2(dualKeyWasPressedButActionKeyNot, KeyTiming_RecordComment(resolutionKey, "PF")); - KEY_TIMING2(dualKeyWasPressedFirst, KeyTiming_RecordComment(resolutionKey, "PG")); - return SecondaryRoleState_Primary; - } + if (Cfg.SecondaryRoles_AdvancedStrategyTriggeringEvent == SecondaryRoleTriggeringEvent_Release) { + PostponerQuery_FindFirstReleased(&actionEvent, opposingKey); + } + else if (Cfg.SecondaryRoles_AdvancedStrategyTriggeringEvent == SecondaryRoleTriggeringEvent_Press) { + PostponerQuery_FindFirstPressed(&actionEvent, opposingKey); } - //handle trigger by release - if (Cfg.SecondaryRoles_AdvancedStrategyTriggeringEvent == SecondaryRoleTriggeringEvent_Release) { - bool actionKeyWasReleasedButDualkeyNot = actionRelease != NULL && (dualRoleRelease == NULL && (int32_t)(Timer_GetCurrentTime() - actionRelease->time) > Cfg.SecondaryRoles_AdvancedStrategySafetyMargin); - bool actionKeyWasReleasedFirst = actionRelease != NULL && dualRoleRelease != NULL && actionRelease->time <= dualRoleRelease->time - Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; + // handle positive safety margin part 1: is action key allowed to trigger secondary yet + const bool safetyWaitForRelease = dualRoleRelease == NULL && actionEvent != NULL + && (int32_t)(Timer_GetCurrentTime() - actionEvent->time) < Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; + if (safetyWaitForRelease) { + // prevent the action from triggering secondary + actionEvent = NULL; + } - if (actionKeyWasReleasedFirst || actionKeyWasReleasedButDualkeyNot) { - if(!dualRoleWasHeldLongEnoughToBeAllowedSecondary){ - sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime); - return SecondaryRoleState_DontKnowYet; - } - KEY_TIMING2(actionKeyWasReleasedButDualkeyNot, KeyTiming_RecordComment(resolutionKey, "SD")); - KEY_TIMING2(actionKeyWasReleasedFirst, KeyTiming_RecordComment(resolutionKey, "SE")); - return SecondaryRoleState_Secondary; + // handle release of the dual role key + const bool heldTooShortForSecondary = activeTime < Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime; + if (dualRoleRelease != NULL) { + // released before it was allowed to become secondary + // overrides any safety margin concerns + if (heldTooShortForSecondary) { + RESOLVED(SecondaryRoleState_Primary); } - bool dualKeyWasReleasedButActionKeyNot = dualRoleRelease != NULL && (actionRelease == NULL && (int32_t)(Timer_GetCurrentTime() - dualRoleRelease->time) > -Cfg.SecondaryRoles_AdvancedStrategySafetyMargin); - bool dualKeyWasReleasedFirst = actionRelease != NULL && dualRoleRelease != NULL && actionRelease->time >= dualRoleRelease->time - Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; - if (dualKeyWasReleasedFirst | dualKeyWasReleasedButActionKeyNot) { - KEY_TIMING2(dualKeyWasReleasedButActionKeyNot, KeyTiming_RecordComment(resolutionKey, "PI")); - KEY_TIMING2(dualKeyWasReleasedFirst, KeyTiming_RecordComment(resolutionKey, "PJ")); - return SecondaryRoleState_Primary; + // released before another key triggered activation + if (actionEvent == NULL) { + // if safety margin requires us to wait for a bit + // flipping signs to simplify checks + const bool safetyWaitForAction = + (int32_t)(dualRoleRelease->time - Timer_GetCurrentTime()) > Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; + if (safetyWaitForAction) { + AWAITEVENT(activeTime - Cfg.SecondaryRoles_AdvancedStrategySafetyMargin); + } + else { + // see if passive timeout alters release action + const secondary_role_state_t releaseAction = + reachedTimeout ? Cfg.SecondaryRoles_AdvancedStrategyTimeoutAction : SecondaryRoleState_Primary; + RESOLVED(releaseAction); + } } } - bool triggerBehaviorsActive = Cfg.SecondaryRoles_AdvancedStrategyTriggeringEvent != SecondaryRoleTriggeringEvent_None; - - // handle timeout when action key is pressed - if (activeTime >= Cfg.SecondaryRoles_AdvancedStrategyTimeout) { - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "SF")); - return SecondaryRoleState_Secondary; + // now we want to trigger secondary, but are we allowed? + // handle safety margin part 2: wait for the safety margin? + uint32_t waitUntil = safetyWaitForRelease ? activeTime + Cfg.SecondaryRoles_AdvancedStrategySafetyMargin : 0; + // wait for being allowed to trigger secondary? + if (heldTooShortForSecondary) { + // pick the longer wait time + // we're waiting to trigger secondary once both timespans have passed, and the only thing + // which can prevent that resolution is dual role release which triggers reevaluation on it's own + waitUntil = MAX(Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime, waitUntil); + } + // wait until we are allowed to go secondary + if (waitUntil != 0) { + AWAITEVENT(waitUntil); } - // handle primary press when action key is pressed and trigger behaviors are off - if (dualRoleRelease != NULL && !triggerBehaviorsActive && activeTime < Cfg.SecondaryRoles_AdvancedStrategyTimeout) { - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "PK")); - return SecondaryRoleState_Primary; + // handle action key activation + if (actionEvent != NULL) { + RESOLVED(SecondaryRoleState_Secondary); } - sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyTimeout); + // see if we should set a timer to wake up to actively time out + if (isActiveTimeout || isDoubletap) { + AWAITEVENT(Cfg.SecondaryRoles_AdvancedStrategyDoubletapTimeout); + } + + // otherwise, keep postponing until key action return SecondaryRoleState_DontKnowYet; } static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowSimple() { if (PostponerQuery_PendingKeypressCount() > 0 && !PostponerQuery_IsKeyReleased(resolutionKey)) { - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "SK")); - return SecondaryRoleState_Secondary; + RESOLVED(SecondaryRoleState_Secondary); } else if (PostponerQuery_IsKeyReleased(resolutionKey) /*assume PostponerQuery_PendingKeypressCount() == 0, but gather race conditions too*/) { - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "PK")); - return SecondaryRoleState_Primary; + RESOLVED(SecondaryRoleState_Primary); } else { if (resolutionCallerIsMacroEngine) { Macros_SleepTillKeystateChange(); @@ -246,8 +219,7 @@ static secondary_role_state_t resolveCurrentKey(secondary_role_strategy_t strate { if (activateSecondaryImmediately) { activateSecondaryImmediately = false; - KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "SM")); - return SecondaryRoleState_Secondary; + RESOLVED(SecondaryRoleState_Secondary); } switch (strategy) { case SecondaryRoleStrategy_Simple: @@ -265,6 +237,12 @@ static void startResolution( bool isMacroResolution, secondary_role_same_half_t actionFromSameHalf) { + // stored state is last resolution. detect doubletap here + isDoubletap = keyState == resolutionKey + && CurrentPostponedTime - resolutionStartTime < Cfg.SecondaryRoles_AdvancedStrategyDoubletapTimeout; + + // store current state + currentlyResolving = true; resolutionKey = keyState; resolutionStartTime = CurrentPostponedTime; resolutionCallerIsMacroEngine = isMacroResolution; @@ -298,17 +276,19 @@ static void startResolution( static void finishResolution(secondary_role_state_t res) { resolutionKey->secondaryState = res; - previousResolutionKey = resolutionKey; - previousResolutionTime = resolutionStartTime; if(!resolutionCallerIsMacroEngine) { fakeActivation(); } - resolutionKey = NULL; + currentlyResolving = false; PostponerExtended_UnblockMouse(); } void SecondaryRoles_ActivateSecondaryImmediately() { - if (resolutionKey != NULL) { + if (currentlyResolving && !activateSecondaryImmediately) { + if (RecordKeyTiming) { + Macros_SetStatusNumSpaced(Timer_GetCurrentTime(), false); + Macros_SetStatusString(" Got resolve request.\n", NULL); + } activateSecondaryImmediately = true; } } @@ -320,7 +300,7 @@ secondary_role_state_t SecondaryRoles_ResolveState(key_state_t* keyState, second // released, or if previous resolution has been resolved as secondary. Therefore, // it suffices to deal with the `resolutionKey` only. Any other queried key is a finished resoluton. - if (resolutionKey == NULL && keyState->secondaryState == SecondaryRoleState_DontKnowYet) { + if (!currentlyResolving && keyState->secondaryState == SecondaryRoleState_DontKnowYet) { startResolution(keyState, strategy, isMacroResolution, actionFromSameHalf); secondary_role_state_t res = resolveCurrentKey(strategy); if (res != SecondaryRoleState_DontKnowYet) { @@ -329,7 +309,7 @@ secondary_role_state_t SecondaryRoles_ResolveState(key_state_t* keyState, second return res; } else { - if (keyState == resolutionKey) { + if (currentlyResolving && keyState == resolutionKey) { secondary_role_state_t res = resolveCurrentKey(strategy); if (res != SecondaryRoleState_DontKnowYet) { finishResolution(res); diff --git a/right/src/secondary_role_driver.h b/right/src/secondary_role_driver.h index 3b719e898..a2f34755d 100644 --- a/right/src/secondary_role_driver.h +++ b/right/src/secondary_role_driver.h @@ -74,6 +74,10 @@ SecondaryRoleTriggeringEvent_Release, } secondary_role_triggering_event_t; + typedef enum { + SecondaryRoleTimeoutType_Active, + SecondaryRoleTimeoutType_Passive, + } secondary_role_timeout_type_t; // Variables: // Functions: diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 48213b282..0d906ce23 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -475,6 +475,20 @@ secondary_role_state_t ConsumeSecondaryRoleTimeoutAction(parser_context_t* ctx) } } +secondary_role_timeout_type_t ConsumeSecondaryRoleTimeoutType(parser_context_t* ctx) +{ + if (ConsumeToken(ctx, "active")) { + return SecondaryRoleTimeoutType_Active; + } + else if (ConsumeToken(ctx, "passive")) { + return SecondaryRoleTimeoutType_Passive; + } + else { + Macros_ReportError("Parameter not recognized:", ctx->at, ctx->end); + return SecondaryRoleTimeoutType_Active; + } +} + secondary_role_triggering_event_t ConsumeSecondaryRoleTriggeringEvent(parser_context_t* ctx) { if (ConsumeToken(ctx, "press")) { diff --git a/right/src/str_utils.h b/right/src/str_utils.h index 82614f432..64948b245 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -87,6 +87,7 @@ const char* TokEnd(const char* cmd, const char *cmdEnd); module_id_t ConsumeModuleId(parser_context_t* ctx); secondary_role_state_t ConsumeSecondaryRoleTimeoutAction(parser_context_t* ctx); + secondary_role_timeout_type_t ConsumeSecondaryRoleTimeoutType(parser_context_t* ctx); secondary_role_triggering_event_t ConsumeSecondaryRoleTriggeringEvent(parser_context_t* ctx); secondary_role_triggering_event_t ConsumeSecondaryRoleTriggerByPress(parser_context_t* ctx, secondary_role_triggering_event_t current); secondary_role_triggering_event_t ConsumeSecondaryRoleTriggerByRelease(parser_context_t* ctx, secondary_role_triggering_event_t current);