HRM/Secondary Role - Yet another way. With examples!

Given agent/packages/usb/README.md at master · UltimateHackingKeyboard/agent · GitHub, I think that step is just outdated and you can ignore it.

This is way too complicated. See, @kareltucek, that’s another reason why I am not touching the C code. I would not be able to produce a build!

I would say that the Readme of the firmware repo definitely is overdue for an update with improved instructions.
I would have given up on this if Karell had not explicitly said that he was ready to provide support. I would generally presume that people managing such a repo are busy and not ready to provide feedback on short notice and in a back-and-forth manner like this otherwise.

All of that being said, the same-half lockout works so much better when implemented in firmware! Now I just need to make it configurable through Agent on a per-key basis, preferably in a way which allows usage with macros as main actions, or some other method of managing this behavior with ifPrimary/ifSecondary.

3 Likes

Definitely.

Here is my stab at it: DOCS: improve build instructions. by kareltucek · Pull Request #1361 · UltimateHackingKeyboard/firmware · GitHub

Would you have another stab at it yourself, since you have fresh dev setup experience in fresh memory? (Review, comment, edit, …)

Please elaborate on this part.

I fear the “Now I just need to make it configurable through Agent on a per-key basis” part. Due to various constraints (such as memory footprint or code complexity, clutter and maintenance), so this should be discussed before you start turning the codebase inside out :wink: .

As I suggested in Homerow mods · Issue #797 · UltimateHackingKeyboard/firmware · GitHub I would like to see:

An additional option to ifSecondary/ifPrimary (avoidSameHand or primaryWhenSameHand?) that will trigger the primary resolution when a key of the same hand (keyboard half) is pressed, unless the secondary timeout is reached already (then it will use secondary resolution even with keys from the same hand)

I would suggest to implement this part in firmware and macro language first. A discussion would be needed also whether this includes thumb keys, or whether the list of keys need to be configurable.

Corresponding options for Agent would come later.

I will be happy to go over the new version and try it out for myself tonight and provide feedback.

I did reach the same conclusion: It’s not feasible without increasing the size of the configuration by a non-trivial amount.
The need for some configureability boils down to the following: Same-half lock-out is great on the HRMs, but not for something like a thumb modifier. I want to activate Mod and Arrow key around with my right hand while picking my nose…shaped coffee cup up and drinking with the left hand. So some way of enabling it selectively would be needed. Right now I have just hard coded for the three “home rows” to be eligible for it. It could be a radio selection thing between Off/Homerow/Typingkeys/Allkeys or something in config.

If it could also be an optional parameter for ifPrimary/ifSecondary like simpleStrategy/advancedStrategy is, that would be awesome, but not necessary.

I have also implemented a minimum hold time for secondary activation, meaning that a quick tap will not trigger secondary regardless of how many keys were hammered in that short time. It does wonders to remove those last few false activations. I also want that to be configurable.

Am I right in my assumption that a config migration will be needed, with no backward compatibility, to add such two fields to the advanced strategy section?

1 Like

If it could also be an optional parameter for ifPrimary/ifSecondary like simpleStrategy/advancedStrategy is, that would be awesome, but not necessary.

That’s perfect!

(I was afraid it might be a property of the “action key” rather than the “dual role key”.)

In any case, if passing configuration around turns out to be difficult or anything, feel free to leave that to me.

I have also implemented a minimum hold time for secondary activation, meaning that a quick tap will not trigger secondary regardless of how many keys were hammered in that short time.

Sounds good!

Am I right in my assumption that a config migration will be needed, with no backward compatibility, to add such two fields to the advanced strategy section?

Just to add them into the native part of the config, and thus into the Typing behavior tab.

However the practice is to make settings like these configurable only via macro commands at first ($onInit), and only add them into Agent later when it is convenient.

There are actually already two such secondary role attributes waiting to be added into Agent:

# these are available in the agent
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.triggerByRelease <trigger secondary role if action key is released before dual role (BOOL)
COMMAND = set secondaryRole.advanced.doubletapTime <ms, 0-500 (INT)>
COMMAND = set secondaryRole.advanced.doubletapToPrimary <hold primary on doubletap (BOOL)>

# these are not exposed in the GUI atm:
COMMAND = set secondaryRole.advanced.triggerByPress <trigger immediately on action key press (BOOL)>
COMMAND = set secondaryRole.advanced.triggerByMouse <trigger secondary role immediately on mouse move (BOOL)

(The nice thing about macro engine is that it doesn’t need to care about Agent compatibility. Actually, the grammar for Agent macro autocomplete is pulled from github, so the autocomplete is accurate even when using macro syntax that is newer than the Agent itself.)

Oh, I assumed it would be the other way around. I am perfectly content with that.

That TriggerByMouse was actually something I wanted to request, so that’s awesome!
Now I’m down to only needing optional per-half override resolution, effectively disable/enable secondary role behavior on a per-half basis, for gaming purposes, as if I game at all these days, and configuration of the features through macros. Then I have all I want from secondary roles for now.

By the way, what is the difference between triggerByPress and just a lack of triggerByRelease? Just syntax for toggling?

Hmm, I think I have made some mistake with the minimum key press time implementation, I sometimes get keyboard freezes. I think I left a gap in my implementation of sleep/scheduler activation. What happens if I schedule or sleep till a time in the past?

Now I’m down to only needing optional per-half override resolution, effectively disable/enable secondary role behavior on a per-half basis, for gaming purposes, as if I game at all these days, and configuration of the features through macros. Then I have all I want from secondary roles for now.

That can be tricky with the same-hand-lockout behavior :-).

One way may be to use overlayLayer, or set keymapAction.LAYERID.KEYID ACTION to rebind those HRM keys in RAM to different variant of the macros.

By the way, what is the difference between triggerByPress and just a lack of triggerByRelease? Just syntax for toggling?

  • triggerByPress triggers when the other key is pressed while the dual role key is still held. It is the simple resolution strategy.
  • lack of both triggerByRelease and triggerByPress triggers only by hold duration of the dual role key..

What happens if I schedule or sleep till a time in the past?

I think it should be executed in the next eventloop iteration (aka “immediately”), but there might be some bug.

I’ll find a way to make it work. I’m thinking a macro assignable config variable like leftHalfHrmOverride or something.

No, it’s probably some issue in my code. I don’t see how the timeout logic couldn’t cause the same issue if that was the cause.

Please try to solve this in userspace (like with action rebinding, or a normal setVar myVariable true and then if ($myVariable) ..., not a dedicated config value).

I didn’t know. That’s really cool. Respect, @kareltucek!

2 Likes

I have a bunch of notes, but I’m not through yet.

EDIT: The errors I mentioned yesterday were probably because my local branch was on master after the checkout.

The error is in the doubletap logic it seems. On stock firmware. Bind a key with a secondary role, enable doubletap to primary, spam the key a few times, try to move the mouse.

Will see if I can fix it while I’m in there.

Lunchbreak fun: It was the triggerByMouse which did not unsuspend the mouse on immediate resolution, as is the case with doubletap. I only mention it so you don’t spend time looking Karel.

EDIT: The errors I mentioned yesterday were probably because my local branch was on master after the checkout.

I have found some more, and pushed a fix!

Lunchbreak fun: It was the triggerByMouse which did not unsuspend the mouse on immediate resolution, as is the case with doubletap. I only mention it so you don’t spend time looking Karel.

Parse error: insufficient context.

Please elaborate in case this needs my attention.

SecondaryRoles_ResolveState, if (isNewResolution) does not PostponerExtended_UnblockMouse if it resolves.

Right.

I guess this should fix it? Would you give it a review, test, etc? At the moment you are the one who knows that part of the codebase best :see_no_evil_monkey:.

Request for comment:
I’m running into this quite a bit when I type camelCase. If I tap a HRM to type the letter, and then hold it to use it’s secondary function, it will often trigger double tap to primary.
Double tap to primary has the main functionality to me to allow holding a primary key if timeout action is secondary. I want to make it so that double tap to primary only makes the timeout action primary for that particular tap. Does anyone else feel strongly one way or the other on this?