From ddfe7fe9cee5f3036ac80be37ce430ebb141aec8 Mon Sep 17 00:00:00 2001 From: Christian Dirksen Date: Tue, 7 Oct 2025 21:27:46 +0200 Subject: [PATCH 1/2] Added option to set a minimum hold time for keys to be allowed to trigger as secondary --- doc-dev/reference-manual.md | 2 ++ doc-dev/user-guide.md | 1 + right/src/config_manager.c | 1 + right/src/config_manager.h | 1 + right/src/macros/set_command.c | 4 ++++ right/src/secondary_role_driver.c | 26 ++++++++++++++++++++------ 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/doc-dev/reference-manual.md b/doc-dev/reference-manual.md index 2a166020..11ff4397 100644 --- a/doc-dev/reference-manual.md +++ b/doc-dev/reference-manual.md @@ -151,6 +151,7 @@ COMMAND = set secondaryRole.advanced.safetyMargin COMMAND = set secondaryRole.advanced.triggerByRelease COMMAND = set secondaryRole.advanced.doubletapToPrimary COMMAND = set secondaryRole.advanced.doubletapTime COMMAND = set mouseKeys.{move|scroll}.initialSpeed @@ -687,6 +688,7 @@ Internally, values are saved in one of the following types, and types are automa - `set secondaryRole.advanced.triggerByRelease BOOL` if enabled, secondary role is chosen depending on the release order of the keys (`press-A, press-B, release-B, release-A` leads to secondary action; `press-A, press-B, release-A, release-B` leads to primary action). This is further modified by safetyMargin. - `set secondaryRole.advanced.triggerByPress BOOL` if enabled, secondary role is triggered when there is another press, simiarly to the simple strategy. Unlike simple strategy, this allows setting timeout behaviors, and also is modified by safetyMargin. - `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. - `set secondaryRole.advanced.safetyMargin ` finetunes sensitivity of the trigger-by-release and trigger-by-press behaviours, so that positive values favor primary role, while negative values favor secondary role. This works by adding the value to the action key (or subtracting from the dual role key). E.g., suppose trigger by release is active, and safetyMargin equal 50. Furthermore assume that dual-role key is released 30ms after the action key. Due to safety margin 50 being greater than 30, the dual-role key is still considered to be released first, and so primary role is activated. - `set secondaryRole.advanced.doubletapToPrimary BOOL` allows initiating hold of primary action by doubletap. (Useful if you want a dual key on space.) - `set secondaryRole.advanced.doubletapTime ` configures the above timeout (measured press-to-press). diff --git a/doc-dev/user-guide.md b/doc-dev/user-guide.md index d2f4c641..2b0c5cd7 100644 --- a/doc-dev/user-guide.md +++ b/doc-dev/user-guide.md @@ -485,6 +485,7 @@ set secondaryRole.advanced.triggerByPress 0 set secondaryRole.advanced.triggerByMouse 0 set secondaryRole.advanced.doubletapToPrimary 1 set secondaryRole.advanced.doubletapTime 200 +set secondaryRole.advanced.minimumHoldTime 0 ``` The above configuration will trigger the secondary role whenever the dual-role key is pressed for more than 200ms, i.e., just a very slightly prolonged activation will trigger the secondary role. diff --git a/right/src/config_manager.c b/right/src/config_manager.c index 98e3d477..11640fa3 100644 --- a/right/src/config_manager.c +++ b/right/src/config_manager.c @@ -219,6 +219,7 @@ const config_t DefaultCfg = (config_t){ .SecondaryRoles_AdvancedStrategyTriggerByRelease = true, .SecondaryRoles_AdvancedStrategyTriggerByPress = false, .SecondaryRoles_AdvancedStrategyTriggerByMouse = false, + .SecondaryRoles_AdvancedStrategyMinimumHoldTime = 0, .SecondaryRoles_AdvancedStrategyDoubletapToPrimary = true, .SecondaryRoles_AdvancedStrategyTimeoutAction = SecondaryRoleState_Secondary, .SecondaryRoles_Strategy = SecondaryRoleStrategy_Simple, diff --git a/right/src/config_manager.h b/right/src/config_manager.h index ef8ba083..c2328e44 100644 --- a/right/src/config_manager.h +++ b/right/src/config_manager.h @@ -37,6 +37,7 @@ bool SecondaryRoles_AdvancedStrategyTriggerByMouse; bool SecondaryRoles_AdvancedStrategyDoubletapToPrimary; secondary_role_state_t SecondaryRoles_AdvancedStrategyTimeoutAction; + uint8_t SecondaryRoles_AdvancedStrategyMinimumHoldTime; // mouse keys mouse_kinetic_state_t MouseMoveState; diff --git a/right/src/macros/set_command.c b/right/src/macros/set_command.c index 707b433d..34e770aa 100644 --- a/right/src/macros/set_command.c +++ b/right/src/macros/set_command.c @@ -312,6 +312,10 @@ static macro_variable_t secondaryRoleAdvanced(parser_context_t* ctx, set_command DEFINE_INT_LIMITS(0, 65535); ASSIGN_INT(Cfg.SecondaryRoles_AdvancedStrategyDoubletapTimeout); } + else if (ConsumeToken(ctx, "minimumHoldTime")) { + DEFINE_INT_LIMITS(0, 255); + ASSIGN_INT(Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime); + } else { Macros_ReportError("Parameter not recognized:", ctx->at, ctx->end); } diff --git a/right/src/secondary_role_driver.c b/right/src/secondary_role_driver.c index 01cb716b..b60fe7c9 100644 --- a/right/src/secondary_role_driver.c +++ b/right/src/secondary_role_driver.c @@ -112,13 +112,13 @@ void SecondaryRoles_FakeActivation(secondary_role_result_t res) * TriggerByRelease = false */ -static void sleepTimeoutStrategy() { +static void sleepTimeoutStrategy(uint16_t wakeTimeOffset) { //register wakeups if (resolutionCallerIsMacroEngine) { Macros_SleepTillKeystateChange(); - Macros_SleepTillTime(resolutionStartTime + Cfg.SecondaryRoles_AdvancedStrategyTimeout, "SecondaryRoles - Macros"); + Macros_SleepTillTime(resolutionStartTime + wakeTimeOffset, "SecondaryRoles - Macros"); } else { - EventScheduler_Schedule(resolutionStartTime + Cfg.SecondaryRoles_AdvancedStrategyTimeout, EventSchedulerEvent_NativeActions, "NativeActions - SecondaryRoles"); + EventScheduler_Schedule(resolutionStartTime + wakeTimeOffset, EventSchedulerEvent_NativeActions, "NativeActions - SecondaryRoles"); } } @@ -145,6 +145,11 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout() } int32_t activeTime = (dualRoleRelease == NULL ? Timer_GetCurrentTime() : dualRoleRelease->time) - dualRolePressTime; + bool dualRoleWasHeldLongEnoughToBeAllowedSecondary = activeTime >= Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime; + + if (dualRoleRelease != NULL && !dualRoleWasHeldLongEnoughToBeAllowedSecondary) { + return SecondaryRoleState_Primary; + } // handle timeout when action key is not pressed if (actionPress == NULL) { @@ -162,11 +167,11 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout() KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "SC")); return SecondaryRoleState_Secondary; default: - sleepTimeoutStrategy(); + sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyTimeout); return SecondaryRoleState_DontKnowYet; } } else { - sleepTimeoutStrategy(); + sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyTimeout); return SecondaryRoleState_DontKnowYet; } } @@ -175,7 +180,12 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout() if (Cfg.SecondaryRoles_AdvancedStrategyTriggerByPress) { 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; + if (actionKeyWasPressedButDualkeyNot || actionKeyWasPressedFirst) { + if(!dualRoleWasHeldLongEnoughToBeAllowedSecondary){ + sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime); + return SecondaryRoleState_DontKnowYet; + } KEY_TIMING2(actionKeyWasPressedButDualkeyNot, KeyTiming_RecordComment(resolutionKey, "SE")); KEY_TIMING2(actionKeyWasPressedFirst, KeyTiming_RecordComment(resolutionKey, "SF")); return SecondaryRoleState_Secondary; @@ -196,6 +206,10 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout() bool actionKeyWasReleasedFirst = actionRelease != NULL && dualRoleRelease != NULL && actionRelease->time <= dualRoleRelease->time - Cfg.SecondaryRoles_AdvancedStrategySafetyMargin; if (actionKeyWasReleasedFirst || actionKeyWasReleasedButDualkeyNot) { + if(!dualRoleWasHeldLongEnoughToBeAllowedSecondary){ + sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyMinimumHoldTime); + return SecondaryRoleState_DontKnowYet; + } KEY_TIMING2(actionKeyWasReleasedButDualkeyNot, KeyTiming_RecordComment(resolutionKey, "SG")); KEY_TIMING2(actionKeyWasReleasedFirst, KeyTiming_RecordComment(resolutionKey, "SH")); return SecondaryRoleState_Secondary; @@ -224,7 +238,7 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout() return SecondaryRoleState_Primary; } - sleepTimeoutStrategy(); + sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyTimeout); return SecondaryRoleState_DontKnowYet; } From 460e554db82d671c6cda51a8e9531b535201e816 Mon Sep 17 00:00:00 2001 From: Christian Dirksen Date: Sat, 29 Nov 2025 21:21:52 +0100 Subject: [PATCH 2/2] Update doc-dev/reference-manual.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Karel Tuček --- doc-dev/reference-manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc-dev/reference-manual.md b/doc-dev/reference-manual.md index 11ff4397..cda1044a 100644 --- a/doc-dev/reference-manual.md +++ b/doc-dev/reference-manual.md @@ -151,7 +151,7 @@ COMMAND = set secondaryRole.advanced.safetyMargin COMMAND = set secondaryRole.advanced.triggerByRelease +COMMAND = set secondaryRole.advanced.minimumHoldTime COMMAND = set secondaryRole.advanced.doubletapToPrimary COMMAND = set secondaryRole.advanced.doubletapTime COMMAND = set mouseKeys.{move|scroll}.initialSpeed