Skip to content
Open
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
12 changes: 7 additions & 5 deletions doc-dev/reference-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,11 @@ COMMAND = set secondaryRole.advanced.timeout <ms, 0-500 (INT)>
COMMAND = set secondaryRole.advanced.timeoutAction { primary | secondary }
COMMAND = set secondaryRole.advanced.safetyMargin <ms, higher value adjusts sensitivity towards primary role -50-50 (INT)>
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.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.doubletapToPrimary <hold primary on doubletap (BOOL)>
COMMAND = set secondaryRole.advanced.doubletapTime <ms, 0-500 (INT)>
COMMAND = set secondaryRole.advanced.primaryFromSameHalf <prevents secondary role keys from triggering as secondary when the triggering key is from the same half of the keyboard (BOOL)>
COMMAND = set mouseKeys.{move|scroll}.initialSpeed <px/s, ~100/20 (INT)>
COMMAND = set mouseKeys.{move|scroll}.baseSpeed <px/s, ~800/20 (INT)>
COMMAND = set mouseKeys.{move|scroll}.initialAcceleration <px/s, ~1700/20 (INT)>
Expand Down Expand Up @@ -191,7 +192,7 @@ CONDITION = if (EXPRESSION)
CONDITION = else
CONDITION = {ifShortcut | ifNotShortcut} [IFSHORTCUT_OPTIONS]* [KEYID]+
CONDITION = {ifGesture | ifNotGesture} [IFSHORTCUT_OPTIONS]* [KEYID]+
CONDITION = {ifPrimary | ifSecondary} [ simpleStrategy | advancedStrategy ]
CONDITION = {ifPrimary | ifSecondary} [ simpleStrategy | advancedStrategy | primaryFromSameHalf | secondaryFromSameHalf ]
CONDITION = {ifHold | ifTap}
CONDITION = {ifDoubletap | ifNotDoubletap}
CONDITION = {ifInterrupted | ifNotInterrupted}
Expand Down Expand Up @@ -473,7 +474,7 @@ Commands:
- `holdLayer LAYERID` mostly corresponds to the sequence `toggleLayer <layer>; delayUntilRelease; untoggleLayer`, except for more elaborate conflict resolution (releasing holds in incorrect order).
- `holdKeymapLayer KEYMAPID LAYERID` just as holdLayer, but allows referring to layer of different keymap. This reloads the entire keymap, so it may be very inefficient.
- `holdLayerMax/holdKeymapLayerMax` will timeout after <timeout> ms if no action is performed in that time.
- `ifPrimary/ifSecondary [ simpleStrategy | advancedStrategy ] ... COMMAND` will wait until the firmware can distinguish whether primary or secondary action should be activated and then either execute `COMMAND` or skip it.
- `ifPrimary/ifSecondary [ simpleStrategy | advancedStrategy | primaryFromSameHalf | secondaryFromSameHalf ]... COMMAND` will wait until the firmware can distinguish whether primary or secondary action should be activated and then either execute `COMMAND` or skip it.

### Layer/Keymap loading manipulation / shared layers:

Expand All @@ -497,7 +498,7 @@ We allow postponing key activations in order to allow deciding between some scen
- `ifKeyPendingAt/ifNotKeyPendingAt <idx> <keyId>` looks into postponing queue at `idx`th waiting key and compares it to the `keyId`.
- `consumePending <n>` will remove n records from the queue.
- `activateKeyPostponed KEYID` will add tap of KEYID at the end of queue. If `atLayer LAYERID` is specified, action will be taken from that layer rather than current one. If `prepend` option is specified, event will be place at the beginning of the queue.
- `ifPrimary/ifSecondary [ simpleStrategy | advancedStrategy ] ... COMMAND` will wait until the firmware can distinguish whether primary or secondary action should be activated and then either execute `COMMAND` or skip it.
- `ifPrimary/ifSecondary [ simpleStrategy | advancedStrategy | primaryFromSameHalf | secondaryFromSameHalf ] ... COMMAND` will wait until the firmware can distinguish whether primary or secondary action should be activated and then either execute `COMMAND` or skip it.
- `ifHold/ifTap COMMAND` will wait until the key that activated the macro will be released or until holdTimeout elapses. Then it will either execute the command or skip it.
- `ifShortcut/ifNotShortcut/ifGesture/ifNotGesture [IFSHORTCUT_OPTIONS]* [KEYID]*` will wait for next keypresses until sufficient number of keys has been pressed. If the next keypresses correspond to the provided arguments (hardware ids), the keypresses are consumed and the condition is performed. Consuming takes place in both `if` and `ifNot` versions if the full list is matched. E.g., `ifShortcut 090 089 final tapKey C-V; holdKey v`.
- `Shortcut` requires continual press of keys (e.g., Ctrl+c). By default, it timeouts with the activation key release.
Expand Down Expand Up @@ -537,7 +538,7 @@ Conditions are checked before processing the rest of the command. If the conditi
- `ifRecording/ifNotRecording` and `ifRecordingId/ifNotRecordingId MACROID` test if the runtime macro recorder is in the recording state.
- `ifShortcut/ifNotShortcut [IFSHORTCUT_OPTIONS]* [KEYID]*` will wait for future keypresses and compare them to the argument. See the postponer mechanism section.
- `ifGesture/ifNotGesture [IFSHORTCUT_OPTIONS]* [KEYID]*` just as `ifShortcut`, but breaks after 1000ms instead of when the key is released. See the postponer mechanism section.
- `ifPrimary/ifSecondary [ simpleStrategy | advancedStrategy ] ... COMMAND` will wait until the firmware can distinguish whether primary or secondary action should be activated and then either execute `COMMAND` or skip it.
- `ifPrimary/ifSecondary [ simpleStrategy | advancedStrategy | primaryFromSameHalf | secondaryFromSameHalf ] ... COMMAND` will wait until the firmware can distinguish whether primary or secondary action should be activated and then either execute `COMMAND` or skip it.

### Modifiers

Expand Down Expand Up @@ -690,6 +691,7 @@ Internally, values are saved in one of the following types, and types are automa
- `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).
- `set secondaryRole.advanced.primaryFromSameHalf <(BOOL)>` enables or disables whether secondary role keys can trigger their secondary role if the triggering key is from the same keyboard half. `ifPrimary/ifSecondary` can specify explicitly whether to trigger primary from same half using the argument `primaryFromSameHalf/secondaryFromSameHalf`.

- `macroEngine`
- terminology:
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.primaryFromSameHalf 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 @@ -221,6 +221,7 @@ const config_t DefaultCfg = (config_t){
.SecondaryRoles_AdvancedStrategyTriggerByMouse = false,
.SecondaryRoles_AdvancedStrategyDoubletapToPrimary = true,
.SecondaryRoles_AdvancedStrategyTimeoutAction = SecondaryRoleState_Secondary,
.SecondaryRoles_AdvancedStrategyPrimaryFromSameHalf = false,
.SecondaryRoles_Strategy = SecondaryRoleStrategy_Simple,
.StickyModifierStrategy = Stick_Smart,
.Macros_Scheduler = Scheduler_Blocking,
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 @@ -29,6 +29,7 @@

// secondary roles
secondary_role_strategy_t SecondaryRoles_Strategy;
bool SecondaryRoles_AdvancedStrategyPrimaryFromSameHalf;
uint16_t SecondaryRoles_AdvancedStrategyDoubletapTimeout;
uint16_t SecondaryRoles_AdvancedStrategyTimeout;
int16_t SecondaryRoles_AdvancedStrategySafetyMargin;
Expand Down
25 changes: 19 additions & 6 deletions right/src/macros/commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -994,12 +994,25 @@ static macro_result_t processIfSecondaryCommand(parser_context_t* ctx, bool nega
{
secondary_role_strategy_t strategy = Cfg.SecondaryRoles_Strategy;
bool originalPostponing = S->ls->as.modifierPostpone;
secondary_role_same_half_t fromSameHalf = SecondaryRole_DefaultFromSameHalf;

if (ConsumeToken(ctx, "simpleStrategy")) {
strategy = SecondaryRoleStrategy_Simple;
}
else if (ConsumeToken(ctx, "advancedStrategy")) {
strategy = SecondaryRoleStrategy_Advanced;
while(true) {
if (ConsumeToken(ctx, "simpleStrategy")) {
strategy = SecondaryRoleStrategy_Simple;
}
else if (ConsumeToken(ctx, "advancedStrategy")) {
strategy = SecondaryRoleStrategy_Advanced;
}

if (ConsumeToken(ctx, "primaryFromSameHalf")) {
fromSameHalf = SecondaryRole_PrimaryFromSameHalf;
}
else if (ConsumeToken(ctx, "secondaryFromSameHalf")) {
fromSameHalf = SecondaryRole_SecondaryFromSameHalf;
}
else {
break;
}
}

if (Macros_DryRun) {
Expand All @@ -1015,7 +1028,7 @@ static macro_result_t processIfSecondaryCommand(parser_context_t* ctx, bool nega
}

postponeCurrentCycle();
secondary_role_result_t res = SecondaryRoles_ResolveState(S->ms.currentMacroKey, 0, strategy, !S->as.actionActive, true);
secondary_role_result_t res = SecondaryRoles_ResolveState(S->ms.currentMacroKey, strategy, !S->as.actionActive, true, fromSameHalf);

S->as.actionActive = res.state == SecondaryRoleState_DontKnowYet;

Expand Down
3 changes: 3 additions & 0 deletions right/src/macros/set_command.c
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ 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, "primaryFromSameHalf")) {
ASSIGN_BOOL(Cfg.SecondaryRoles_AdvancedStrategyPrimaryFromSameHalf);
}
else {
Macros_ReportError("Parameter not recognized:", ctx->at, ctx->end);
}
Expand Down
43 changes: 40 additions & 3 deletions right/src/secondary_role_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "macros/core.h"
#include "postponer.h"
#include "timer.h"
#include "utils.h"
#ifndef __ZEPHYR__
#include "led_display.h"
#endif
Expand Down Expand Up @@ -58,6 +59,7 @@
*/

static bool resolutionCallerIsMacroEngine = false;
static bool primaryFromSameHalf = false;
static key_state_t *resolutionKey;
static secondary_role_state_t resolutionState;
static uint32_t resolutionStartTime;
Expand Down Expand Up @@ -132,7 +134,10 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout()
postponer_buffer_record_type_t *actionRelease;

PostponerQuery_InfoByKeystate(resolutionKey, &dummy, &dualRoleRelease);
PostponerQuery_InfoByQueueIdx(0, &actionPress, &actionRelease);
uint8_t lastKeyQueuePos = PostponerQuery_PendingKeypressCount() - 1;
PostponerQuery_InfoByQueueIdx(lastKeyQueuePos, &actionPress, &actionRelease);
uint16_t actionKeyId = PostponerExtended_PendingId(lastKeyQueuePos);
uint16_t dualRoleId = Utils_KeyStateToKeyId(resolutionKey);
Comment on lines +137 to +140
Copy link
Collaborator

@kareltucek kareltucek Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last key in the queue, or the action key keyid? You may get scenarios where there are more keys after the action key, in which case this is a major change to the logic of the entire driver, isn't it?

If you are just trying to get the action key keyid, then I would expect this to do the trick:

Edit: remove the suggestion.

Copy link
Author

@firngrod firngrod Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostponerQuery_InfoByQueueIdx(0, &actionPress, &actionRelease); gives me the first key pressed after the secondary-role key under evaluation, not necessarily the one which triggered the evaluation. For trigger on press, that's fine, but it breaks with Trigger on release especially when we care which key triggered the resolution.
If I press Ctrl/O+Alt/S on my right half and Delete on my left, Ctrl/O, PostponerQuery_InfoByQueueIdx(0, &actionPress, &actionRelease); gives me the Alt/S key every time the Ctrl/O evaluates, never the Delete, and it doesn't work.
Maybe I should pass the action key down all the way from SecondaryRoles_ResolveState? I'm going to try that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, that was the resolution key.

Copy link
Collaborator

@kareltucek kareltucek Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please elaborate on the above scenario.

What is the exact scenario, what do you expect to happen and why?

So far the way I see it, when you press O+S, with primaryFromTheSameHalf enabled, it should resolve as primary action for O (by definition of primaryFromTheSameHalf), and perform the O primary action. Next, you get S press applied, which triggers a new dual role resolution on S+Delete.

What am I getting wrong?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's trigger on release and O and S are being held. I didn't make that clear. Neither O or S should trigger before timeout or a key has been released. And since Delete is released first, O should trigger secondary because it was from the other half. That is why I need information on the last postponed key, the Delete key. Realistically, I should loop over the queue to find the first released key. I think I'm going to try that to see if it makes any difference.

Copy link
Collaborator

@kareltucek kareltucek Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(The queue lookups are sorted by press times, right?)

So the scenario is:

  • press O
  • press S
  • press Delete
  • release Delete
  • we are now examining O secondary resolution caused by release of the delete key.
  • your intention is to activate a secondary O+Delete role

In what real life scenario are you triggering a secondary role with another intermediate tap in between of the secondary key and the action key?

I mean, I would expect that the S tap is an evidence that you are in the middle of writing and so that the O+whatever would be an undesired O secondary role activation.


Realistically, I should loop over the queue to find the first released key.

Well, you have no guarantee that secondary evaluation takes place on every wake event, so you will indeed have to loop over the queue.

But let's not get ahead of ourselves. I still don't understand the scenario.

Copy link
Author

@firngrod firngrod Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My conceptual idea of trigger by release is that when an alt role key is being held, the release of ANY key pressed after it should trigger resolution, not just the next key. This allows the very important possibility of combining alt role keys in a multi-mod combo, like the above mentioned Ctrl+Alt+Del. Press O, press N, press Delete, release Delete, release O and N any order.
On release of Del, I want O to trigger Secondary for Ctrl because of that Delete release trigger, then N to trigger secondary to Alt because of that Delete trigger, then Delete with those mods, then the mods release.

What happens in the current main branch logic is that once I release the combos, I either release O or N first. If I release N first, O triggers Ctrl because of N release, then N triggers Alt because of the previous Delete release, then Delete happens and mods are released in the proper order. If I release O first, first O triggers primary because N is still held, then N triggers secondary because Delete was previously released, and then things unroll from there. This is largely mitigated by the Trigger safety margin, and generally it works as I want it to anyway, and it's all acceptable.

However, that logic will never work properly once we start caring which key caused the resolution to happen, in this case whether N or Delete caused the O resolution by releasing first. Without looking through the queue for the first released button, multi-mod combos can only be made by utilizing the timeout to trigger the first mod as secondary.

In general, taking the last key seems to work fine, but I do sometimes see what I feel are false negatives, although I cannot be sure. It may be that I press a secondary Shift, then press the one I want to Capitalize, then press the next key immediately before I release the to-be-capitalized key. I want to see if that is the case.

Copy link
Collaborator

@kareltucek kareltucek Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ctrl+Alt+Del

Ah, right, I didn't realize this was a multi-mod scenario.

I see now the reasoning and fully agree.

It may be that I press a secondary Shift, then press the one I want to Capitalize, then press the next key immediately before I release the to-be-capitalized key. I want to see if that is the case.

Correct. That's why you can't blindly take the last key.

With a proper implementation things still should work if you press a postponeKeys delayUntilRelease macro, then tap your secondary scenario, then tap another key or two, then release the postponeKeys macro.

So to sum up, my conclusion is that:

  • For release triggers, you need to take the first-by-time released key in the queue, that also has the corresponding press in the queue. (This should be implemented in the postponer as a new function that does all the work of finding such a key.)

  • For the press trigger, you still need to take the first-by-order pressed key in the queue, as there may be no released key in the queue at all. (I.e., according to the old algorithm.)

(Ofc., let me know if there is anything suspicious in my reasoning whatsoever.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I will get on that when I have the time. Hopefully tonight or tomorrow night.


//handle doubletap logic
if (
Expand Down Expand Up @@ -173,6 +178,12 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout()

//handle trigger by press
if (Cfg.SecondaryRoles_AdvancedStrategyTriggerByPress) {
bool actionKeyWasPressedAndFromTheSameHalf = primaryFromSameHalf && actionPress != NULL && Utils_AreKeysOnTheSameHalf(actionKeyId, dualRoleId);

if (actionKeyWasPressedAndFromTheSameHalf) {
return SecondaryRoleState_Primary;
}

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) {
Expand All @@ -192,6 +203,12 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout()

//handle trigger by release
if (Cfg.SecondaryRoles_AdvancedStrategyTriggerByRelease) {
bool actionKeyWasReleasedAndFromSameHalf = primaryFromSameHalf && actionRelease != NULL && Utils_AreKeysOnTheSameHalf(actionKeyId, dualRoleId);

if (actionKeyWasReleasedAndFromSameHalf) {
return SecondaryRoleState_Primary;
}

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;

Expand Down Expand Up @@ -231,6 +248,13 @@ static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowTimeout()
static secondary_role_state_t resolveCurrentKeyRoleIfDontKnowSimple()
{
if (PostponerQuery_PendingKeypressCount() > 0 && !PostponerQuery_IsKeyReleased(resolutionKey)) {
if (primaryFromSameHalf) {
uint16_t actionKeyId = PostponerExtended_PendingId(0);
uint16_t dualRoleId = Utils_KeyStateToKeyId(resolutionKey);
if (Utils_AreKeysOnTheSameHalf(actionKeyId, dualRoleId)) {
return SecondaryRoleState_Primary;
}
}
KEY_TIMING(KeyTiming_RecordComment(resolutionKey, "SK"));
return SecondaryRoleState_Secondary;
} else if (PostponerQuery_IsKeyReleased(resolutionKey) /*assume PostponerQuery_PendingKeypressCount() == 0, but gather race conditions too*/) {
Expand Down Expand Up @@ -277,7 +301,9 @@ static secondary_role_state_t startResolution(key_state_t *keyState, secondary_r

switch (strategy) {
case SecondaryRoleStrategy_Simple:
PostponerExtended_BlockMouse();
if(Cfg.SecondaryRoles_AdvancedStrategyTriggerByMouse) {
PostponerExtended_BlockMouse();
}
break;
default:
case SecondaryRoleStrategy_Advanced:
Expand All @@ -296,7 +322,7 @@ void SecondaryRoles_ActivateSecondaryImmediately() {
}
}

secondary_role_result_t SecondaryRoles_ResolveState(key_state_t* keyState, secondary_role_t rolePreview, secondary_role_strategy_t strategy, bool isNewResolution, bool isMacroResolution)
secondary_role_result_t SecondaryRoles_ResolveState(key_state_t* keyState, secondary_role_strategy_t strategy, bool isNewResolution, bool isMacroResolution, secondary_role_same_half_t actionFromSameHalf)
{
// Since postponer is active during resolutions, KeyState_ActivatedNow can happen only after previous
// resolution has finished - i.e., if primary action has been activated, carried out and
Expand All @@ -306,6 +332,17 @@ secondary_role_result_t SecondaryRoles_ResolveState(key_state_t* keyState, secon
if (isNewResolution) {
//start new resolution
resolutionCallerIsMacroEngine = isMacroResolution;
switch (actionFromSameHalf) {
case SecondaryRole_PrimaryFromSameHalf:
primaryFromSameHalf = true;
break;
case SecondaryRole_SecondaryFromSameHalf:
primaryFromSameHalf = false;
break;
default:
primaryFromSameHalf = Cfg.SecondaryRoles_AdvancedStrategyPrimaryFromSameHalf;
break;
}
resolutionState = startResolution(keyState, strategy);
resolutionState = resolveCurrentKey(strategy);
bool resolvedNow = resolutionState != SecondaryRoleState_DontKnowYet;
Expand Down
8 changes: 7 additions & 1 deletion right/src/secondary_role_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,17 @@
bool activatedNow;
} secondary_role_result_t;

typedef enum {
SecondaryRole_DefaultFromSameHalf,
SecondaryRole_PrimaryFromSameHalf,
SecondaryRole_SecondaryFromSameHalf,
} secondary_role_same_half_t;

// Variables:

// Functions:

secondary_role_result_t SecondaryRoles_ResolveState(key_state_t* keyState, secondary_role_t rolePreview, secondary_role_strategy_t strategy, bool isNewResolution, bool isMacroResolution);
secondary_role_result_t SecondaryRoles_ResolveState(key_state_t* keyState, secondary_role_strategy_t strategy, bool isNewResolution, bool isMacroResolution, secondary_role_same_half_t fromSameHalf);
void SecondaryRoles_FakeActivation(secondary_role_result_t res);
void SecondaryRoles_ActivateSecondaryImmediately();

Expand Down
2 changes: 1 addition & 1 deletion right/src/usb_report_updater.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ static void applyKeystroke(key_state_t *keyState, key_action_cached_t *cachedAct
{
key_action_t* action = &cachedAction->action;
if (action->keystroke.secondaryRole) {
secondary_role_result_t res = SecondaryRoles_ResolveState(keyState, action->keystroke.secondaryRole, Cfg.SecondaryRoles_Strategy, KeyState_ActivatedNow(keyState), false);
secondary_role_result_t res = SecondaryRoles_ResolveState(keyState, Cfg.SecondaryRoles_Strategy, KeyState_ActivatedNow(keyState), false, SecondaryRole_DefaultFromSameHalf);
if (res.activatedNow) {
SecondaryRoles_FakeActivation(res);
}
Expand Down
10 changes: 10 additions & 0 deletions right/src/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,13 @@ bool ShouldResendReport(bool statusOk, uint8_t* counter) {
return false;
}
}

static uint8_t isRightHalfOrModule(uint16_t id)
{
return id < 64 || id >= 192;
}

bool Utils_AreKeysOnTheSameHalf(uint16_t oneKey, uint16_t anotherKey)
{
return isRightHalfOrModule(oneKey) == isRightHalfOrModule(anotherKey);
}
1 change: 1 addition & 0 deletions right/src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ if (reentrancyGuard_active) { \
key_coordinates_t Utils_KeyIdToKeyCoordinates(uint16_t keyId);
const char* Utils_KeyAbbreviation(key_state_t* keyState);
bool ShouldResendReport(bool statusOk, uint8_t* counter);
bool Utils_AreKeysOnTheSameHalf(uint16_t oneKey, uint16_t anotherKey);


#endif /* SRC_UTILS_H_ */
Loading