Skip to content
Draft

Hrm #1395

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc-dev/reference-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ COMMAND = set secondaryRole.advanced.safetyMargin <ms, higher value adjusts sens
COMMAND = set secondaryRole.advanced.triggerByPress <trigger immediately on action key press (BOOL)>
COMMAND = set secondaryRole.advanced.triggerByRelease <trigger secondary role if action key is released before dual role (BOOL)
COMMAND = set secondaryRole.advanced.triggerByMouse <trigger secondary role immediately on mouse move (BOOL)
COMMAND = set secondaryRole.advanced.minimumHoldTime <ms, minimum time a key must be held before it can trigger secondary role 0-255 (INT)>
COMMAND = set secondaryRole.advanced.doubletapToPrimary <hold primary on doubletap (BOOL)>
COMMAND = set secondaryRole.advanced.doubletapTime <ms, 0-500 (INT)>
COMMAND = set mouseKeys.{move|scroll}.initialSpeed <px/s, ~100/20 (INT)>
Expand Down Expand Up @@ -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 <ms, 0-255 (INT)>` sets the minimum time that a key must be held before it is allowed to trigger as secondary role.
- `set secondaryRole.advanced.safetyMargin <ms, -50 - 50 (INT)>` 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 <ms, 200 (INT)>` configures the above timeout (measured press-to-press).
Expand Down
1 change: 1 addition & 0 deletions doc-dev/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions right/src/config_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions right/src/config_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions right/src/macros/set_command.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
26 changes: 20 additions & 6 deletions right/src/secondary_role_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand All @@ -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) {
Expand All @@ -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;
}
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -224,7 +238,7 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout()
return SecondaryRoleState_Primary;
}

sleepTimeoutStrategy();
sleepTimeoutStrategy(Cfg.SecondaryRoles_AdvancedStrategyTimeout);
return SecondaryRoleState_DontKnowYet;
}

Expand Down