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

HRMs are hard to get right. I tried to use them a couple of times, but every time, I ended up stopping again because it was simply too hard to avoid triggering something accidentally. I have ideas of how to fix all of that, and so I will throw my two cents into the Scrooge McDuck money pit sized pile of pennies that is the opinions on HRMs.

The UHK has the most important resolution rule: Trigger by release. This one solves most of the issues. But it just isn’t enough! To get to a stage where accidental mod triggers are truly rare, while intentional triggers are still easy and effortless, something else is needed: Only trigger from activation from keys from the other half of the keyboard! This one feature would elevate HRM usage on the UHK to a whole new level!

So if the Ultimate Hacking Keyboard crew could just implement that as a configuration option, that would be awesome! :wink:

Thank you for coming to my Ted Talk.

Now, I speak with such confidence because I have tried cross-half activated Alt Role Keys, and I have seen the Light! And now you can too! Below is a Smart Macro which implements this exact functionality!

It has the following features:

  • Every feature that the current UHK resolution strategy has, simple and advanced, including tie-in so the values are pulled from the UI configuration. The only exception is separately configurable double-tap timeout. Double-tap for these keys uses the general time-out.
  • Optional (default) same-half lock-out, meaning a key release (or presses for simple resolution) from the same keyboard half as the held key will cause the held key to trigger primary rather than secondary. Once a key has resolved to secondary, through time-out or key release from other half, it stays secondary until release.
  • Optional resolution override for each half individually to trigger keys directly as primary or secondary. Great for gaming. An additional macro is provided for cycling the override.
  • Works perfectly fine with run-time macros as far as I can tell.
  • Could be easily modified to allow specific actions for each of the event types: Primary (normal press), secondary (triggered by other key release), timeout (instead of primary/secondary), and double-tap.
  • Works well across layers, just set up the right logic for it, as per my mod layer example

It has the following limitations:

  • Requires the blocking scheduler, but it seems like that is the default one by now, if not the only one available, so really a complete non-issue.
  • Has a running macro for every active key, so limited by the limited active macro slots. Never had this be an issue in practice.
  • Takes up one of the very valuable variable slots. Only one, though.
  • It’s quite large and, due to a necessity for tight control of macro and queue execution, not refactorable since macro calls yield and macro scopes are in short supply. This also means that it probably won’t work nicely with any background macro services which might be running.
  • Not the easiest to set up, especially with keys whose secondary roles are to hold layers, or which are to not have board-half lockout
  • All keys and actions for thumb modules are considered left half of the keyboard. This essentially only means something for keys on the mouse modules.
  • A lot of the statements are long enough that editing them directly in Agent will freeze Agent, so development in another editor is more or less mandatory.
  • My work-around for Issue #1331 is not completely air-tight and does very rarely cause false triggers on queue-released presses which then time out. Once that bug is fixed, this should stop happening without any harmful effects from my work-around.

To use the system, modify the macro AltRoleKey. At the relevant places, put in the actions desired for each key you plan to bind. These are the places where it says primary resolution and secondary resolution. Primary resolution is straightforward, just press the keys you want for each key, or possibly more. Secondary resolution is generally equally straightforward unless you toggle mod layers. If so, you have to toggle back to the main layer near the end of the macro because we don’t have a “pressLayer” sort of function. For all keys presses, always use pressKey, NOT holdKey, as the macro takes very tight control of the flow of queue unrolls and the resolution happens under an active postponeKeys. The same goes for layers, don’t holdLayer, toggle it and release explicitly. For layers with multiple simultaneus activation possibilities, remember to check for simultaneus activation before toggling back to main layer, as per example in the macro (Key 28 and 94)

Of course, you might want alt role keys which are not on the home row, and for which secondary resolution for keys on the same half is wanted. Thumb keys are an example. These can be configured at the comment about “Board-half agnostic keys”, keys 28 and 94 in the example. Such keys will also ignore the override configurations and will always work as alt-role keys. This could be split such that ignoring override and ignoring board-half is configured separately, but I have not yet had a need to.

Once all of the desired key actions are defined, just bind the macro to all of the keys, setVar hrmState 0 in $onInit, and type away.

To toggle the override for a keyboard half, just bind the macro AltRoleToggleOverride to a key on one or both keyboard halves. Pressing the key will then cycle the resolution strategy for the half on which the key is placed through Auto, Primary and Secondary,

I think I have ironed out all of the kinks in the macro by now. Issue #1331 was a particularly fun one. I sometimes see missing cross-half activations, but it’s rare enough to not be a big issue, and much less harmful than unintended secondary activations anyway. I think it’s down to execution time of the loop.

The macro is developed on, and works well on, the UHK60 v1 with firmware 15.1.0. It should work on all UHKs with recent/latest firmware, but I can’t promise that.

I am open to questions and possibly even feature requests. I could still squeeze 7 more bits into the state variable I think, so still lots of room to play.

Linked is also my full keyboard config for if someone wants to just play with the feel of the HRMs without doing any work on setting it up first. The layout can be viewed visually at https://yuzukeycaps.com/c/c13727e1-6834-45a1-8a33-b608ad442318. The base key colors mean nothing right now, but the symbol colors are white for base layer, yellow for mod layer, and orange for secondary role. Mod is accessed as secondary on the thumb keys. It’s presumes US International keymap on the computer.

By the way, in light of the limited working memory, some bitwise operations for the macro language would be awesome, such as bitwise operators and shifts. Just an idle request.

And now for the actual macro: Feast your eyes on AltRoleKey

 // Memory layout - 31 bits, so much room for activities!
 // From LSB   Multiplier  Capacity  Use
 // 0-3        0           16        Iterator and resolution
 // 4-11       16          256       Time shift and queued tap work-around
 // 12         4096        2         Consecutive press indicator to filter false positive doublepresses
 // 13         8192        2         Side-aware run
 // 14-21      16384       256       Last key, for consecutive press indicator
 // 22-23      4194304     4         Right half override
 // 24-25      16777216    4         Left half override
 // 26-30      67108864    32        It is the waste of space, the waste of space!
 // 31         2147483648  2         No-man's land.  The int is signed, and mod and division don't work as bit shift tools with negatives, so this bit is off-limits!

postponeKeys {
 // initiate working memory.  We store if it's a consecutive same key, and current keyId for next key to do the same test, plus indicator that it's a side-specific key
 // we also put in half of the work-around for https://github.com/UltimateHackingKeyboard/firmware/issues/1331 here, the other half is at the end of the macro, read the explanation there.
 setVar hrmState ($hrmState / 4194304 * 4194304 + ($thisKeyId == $hrmState / 16384 % 256) * 4096 + $thisKeyId * 16384 + ($thisKeyId == $hrmState % 4096 / 16) + 8192)

 // Remove the indicator that the key should trigger primary for same-half releases for a set of specific keys.  Thumb keys and such
 if ($thisKeyId == 94 || $thisKeyId == 28) setVar hrmState ($hrmState - 8192)

 if($hrmState % 16384 >= 8192 && $hrmState / 4194304 / (($thisKeyId >= 64) * 3 + 1) % 4) { // this half has override resolution, set it directly
  setVar hrmState ($hrmState + $hrmState / 4194304 / (($thisKeyId >= 64) * 3 + 1) % 4) // note that this fature only applies to side-aware keys
 }

 while ($hrmState % 16 == 0) {
  ifPlaytime $secondaryRole.advanced.timeout setVar hrmState ($hrmState + 3 - $secondaryRole.advanced.timeoutAction) // we are at timeout, resolve what the user wants
  else if($secondaryRole.advanced.doubletapToPrimary) ifDoubletap if ($hrmState % 8192 >= 4096) setVar hrmState ($hrmState + 1) // doubletap to primaryNote that double tap triggers from the macro id alone, regardless of which key ran it, so we have to verify that last macro run was the same key
  if ($hrmState % 16 == 0) ifPending 1 { // other keys have been pressed.
   if (!$secondaryRole.advanced.triggerByRelease) { // direct resolution as per user request.
    if ($hrmState % 16384 >= 8192 && ($thisKeyId >= 64) == ($queuedKeyId.0 >= 64)) setVar hrmState ($hrmState + 1) // we are side-aware and the key is from this side, resolve primary
    else setVar hrmState ($hrmState + 2) // otherwise, resolve secondary
   }
   else if ($hrmState % 4096 == 0 || $secondaryRole.advanced.safetyMargin < 0) while (1) {   // if we are not already timing out, or are timing out on primary
    ifPendingKeyReleased ($hrmState % 16) { // A pending key has been released, we can resolve
     if ($hrmState % 16384 >= 8192 && ($thisKeyId >= 64) == ($queuedKeyId.($hrmState % 16) >= 64)) setVar hrmState ($hrmState / 16 * 16 + 1) // released key is from same side of the keyboard as this key, and we are side aware, so we trigger primary
     else { // if time-shifting towards primary role, we don't resolve right now, but set the time-out after which we will resolve, if we haven't already
      if ($secondaryRole.advanced.triggerByRelease && $secondaryRole.advanced.safetyMargin > 0 && $hrmState % 4096 / 16 == 0) setVar hrmState ($hrmState / 16 * 16 + $currentTime % 256 * 16)
      else if (!$secondaryRole.advanced.triggerByRelease || $secondaryRole.advanced.safetyMargin <= 0) setVar hrmState ($hrmState / 16 * 16 + 2) // otherwise, we resolve now
     }
     break
    }
    else ifPending ($hrmState % 16 + 2) setVar hrmState ($hrmState + 1) // check next key
    else {
     setVar hrmState ($hrmState / 16 * 16) // no more keys to check, clear working memory
     break
    }
   }
   if (1) noOp // https://github.com/UltimateHackingKeyboard/firmware/issues/1330
  }
  if ($hrmState % 16 == 0) ifReleased {
   // if time-shifting towards secondary role, we don't resolve right now, but start the time-out to resolve if we haven't already
   if ($secondaryRole.advanced.triggerByRelease && $secondaryRole.advanced.safetyMargin < 0 && $hrmState % 4096 == 0) setVar hrmState ($hrmState + $currentTime % 256 * 16)
   else if (!$secondaryRole.advanced.triggerByRelease || $secondaryRole.advanced.safetyMargin >= 0) setVar hrmState ($hrmState + 1) // not time-shifting, resolve now
  }
  // check if we are time-shifting and have timed out, only if we have no other resolution
  if ($hrmState % 16 == 0 && $secondaryRole.advanced.triggerByRelease && $hrmState % 4096 != 0 && ($currentTime - $hrmState % 4096 / 16) % 256 > $secondaryRole.advanced.safetyMargin && ($currentTime - $hrmState % 4096 / 16) % 256 > -$secondaryRole.advanced.safetyMargin) {
   // time shift has timed out, so we resolve according to which action was shifted
   if ($secondaryRole.advanced.safetyMargin < 0) {
    setVar hrmState ($hrmState + 1)
   }
   else setVar hrmState ($hrmState + 2)
  }
 }
 if (1) noOp // https://github.com/UltimateHackingKeyboard/firmware/issues/1330

 // act on resolution.  We start the resolution under the postponeKeys because otherwise mods won't reliably apply to the first key pressed
 if ($hrmState % 16 == 1) {
  // layer agnostic key primaries
  if ($thisKeyId == 86) pressKey dotAndGreaterThanSign
  else if ($thisKeyId == 94) {
   ifCapsLockOn tapKey capsLock
   ifLayer mod ifNotKeyActive 28 toggleLayer base
   pressKey enter
  }
  else if ($thisKeyId == 28) {
   ifCapsLockOn final tapKey capsLock
   ifLayer mod ifNotKeyActive 94 final toggleLayer base
   else pressKey escape
  }
  else ifLayer base { // base layer key primaries
   if ($thisKeyId == 78) pressKey a
   else if ($thisKeyId == 18) pressKey n
   else if ($thisKeyId == 17) pressKey o
   else if ($thisKeyId == 79) pressKey t
   else if ($thisKeyId == 80) pressKey e
   else if ($thisKeyId == 81) pressKey r
   else if ($thisKeyId == 16) pressKey i
   else if ($thisKeyId == 19) pressKey s
   else if ($thisKeyId == 24) pressKey w
  }
  else ifLayer mod { // mod layer key primaries
   if ($thisKeyId == 78) pressKey slashAndQuestionMark
   else if ($thisKeyId == 79) pressKey openingBracketAndOpeningBrace
   else if ($thisKeyId == 80) pressKey closingBracketAndClosingBrace
   else if ($thisKeyId == 81) pressKey minusAndUnderscore
   else if ($thisKeyId == 16) pressKey downArrow
   else if ($thisKeyId == 17) pressKey upArrow
   else if ($thisKeyId == 18) pressKey leftArrow
   else if ($thisKeyId == 19) pressKey rightArrow
   else if ($thisKeyId == 24) pressKey home
  }
 }
 else { // secondary resolution
  // general template for modifier key action - the same mod can be on different keys
  if ($thisKeyId == 86 || $thisKeyId == 24) pressKey rightAlt
  if ($thisKeyId == 79 || $thisKeyId == 18) pressKey leftAlt
  if ($thisKeyId == 78) pressKey leftGui
  if ($thisKeyId == 80) pressKey leftControl
  if ($thisKeyId == 81) pressKey leftShift
  if ($thisKeyId == 16) pressKey rightShift
  if ($thisKeyId == 17) pressKey rightControl
  if ($thisKeyId == 19) pressKey rightGui
  if ($thisKeyId == 94 || $thisKeyId == 28) {
   toggleLayer mod // toggle layer, remember to release it, see bottom of macro
  }
 }
}
if ($hrmState % 16 == 1) {
 // The other half of the work-around for https://github.com/UltimateHackingKeyboard/firmware/issues/1331
 // We know that ifReleased won't work for any key which was released while it was being postponed,
 // so if the next key on the queue is released, note that for the next macro run and it will know it's been released that way
 ifPendingKeyReleased 0 setVar hrmState ($hrmState / 16384 * 16384 + $queuedKeyId.0 * 16)
 // beyond this point, consider the working memory lost to the next key.
 delayUntilRelease
}
else {
 // The other half of the work-around for https://github.com/UltimateHackingKeyboard/firmware/issues/1331
 // We know that ifReleased won't work for any key which was released while it was being postponed,
 // so if the next key on the queue is released, note that for the next macro run and it will know it's been released that way
 ifPendingKeyReleased 0 setVar hrmState ($hrmState / 16384 * 16384 + $queuedKeyId.0 * 16)
 // beyond this point, consider the working memory lost to the next key.
 delayUntilRelease
 while (1) { // ensure queue unroll before releasing
  delayUntil max($keystrokeDelay, 4)
  ifNotPending 1 break
 }
 if (1) noOp // // swallow the bug from https://github.com/UltimateHackingKeyboard/firmware/issues/1330

 // layer toggles require explicit reset since we don't have a "pressLayer" style function
 if ($thisKeyId == 94) ifNotKeyActive 28 toggleLayer base
 if ($thisKeyId == 28) ifNotKeyActive 94 toggleLayer base
}

And the macro to toggle the resolution override, AltRoleToggleOverride:

setVar hrmState ($hrmState + 4194304 * (($thisKeyId >= 64) * 3 + 1))
if ($hrmState / 4194304 / (($thisKeyId >= 64) * 3 + 1) % 4 == 3) setVar hrmState ($hrmState - 4194304 * 3 * (($thisKeyId >= 64) * 3 + 1))
goTo ($currentAddress + 1 + $hrmState / 4194304 / (($thisKeyId >= 64) * 3 + 1) % 4)
final setLedTxt 1000 "AUT"
final setLedTxt 1000 "PRI"
final setLedTxt 1000 "SEC"

And finally a link to download my current keyboard config: Proton Drive

2 Likes

:face_with_spiral_eyes:
Uhhhh… What? :sweat_smile:

1 Like

Yeah, it’s a bit large, but half of it is just action declarations for each of the 12 keys I have bound across two layers, and half of the rest is comments. And it really does work quite well, I think.
Also, I have thought of a feature that I want to try: Gated time-out actions. So if I hold the key for half a second and release, it triggers secondary role tap, but if I change my mind about the whole thing, I can hold it for more than a second (or more), and it will do nothing on release. Should still trigger secondary on other key activation while held for that long of course. I do have some cases where I end up triggering Alt or Gui without really wanting to because I changed my mind.

1 Like

I’m lost for words and a “gaping mouth wide open” smiley. I need to take some time to digest this.

1 Like

I think the problem is that my brain is too small. :sweat_smile:

I’m absolutely astonished with what little I do understand of it. I can’t wait to see what kind of magic will emerge from this… :man_mage: :magic_wand:

I am also aware that the bitpacking makes the code a lot (!) harder to read. I made the decision to limit myself to one variable once I noticed that the variable limit was easily reachable. By using 7-10 variables instead of the bitpacking, the macro could become a lot more easily readable. It might also improve performance, I am not sure how expensive all that modding and dividing is. I suspect that variable assignment and accessing is more expensive and takes the loin’s share of the execution time along with the conditionals.
For sure, the macro is pushing up against performance limits, with loop times approaching 20ms with queue lengths of 5+ keys.

Generally, It would really be appreciated if you guys went for a robust native C implementation instead. That could be actually merged into the upstream and in the end benefit a wide user base rather than just a minority of macro-literate nerds.

This implementation exceeds the intended scope of the macro language by many orders of magnitude, and is inevitably going to cause problems down the road.

I would also like to warn you that since the recent uart issues caused by maxes hrm imolementation, I don’t guarantee consistent behavior for 20ms long macros! While the actual limit is set to some 5ms iirc, I am reluctant to support or advise usage of anything that takes more than roughly 1ms of evaluation.

Well, I did not expect to be reprimanded for the macro, but I absolutely get your points. I also expected this to ultimately be a temporary solution, a proof of concept.
I am also sorry if my joke about “Just implement that, kthxbai” completely missed the mark. I assure you I meant no offense. I know how such comments can hit.
I may sit down and make a version of this in the firmware at some point, but right now, I am swamped. This is also why it took me so long to even get this touched up and put here in the forum since I first mentioned it on Github weeks ago.

However, I would argue that this is exactly what the macro engine is great for! It allowed me to create a proof of concept which, more or less, exactly demonstrates what I want from a possible feature, including the chance to actually feel out the feature before it’s even implemented. It’s an easy, low-barrier point of entry to toy around with these things, even if the result pushes beyond the limits of what is sustainable as daily usage of the Macro engine!
And it can be done without getting into compilation and firmware flashing and so on, allowing for a lower time from coding to testing, while also being a lot safer in terms of recovery from faulty code, I suspect. It is easy for the novice to make fatal flaws in C, especially when it comes to memory work.
Finally, to a lot of users, I would expect that copy-pasting a macro into Agent is more accessible than downloading/compiling and flashing a custom firmware, so it allows for more feedback easier.

I remain in awe of your work in the creation and maintenance of the macro engine, Karel. :smiley:

1 Like

The obstacle for me is the effort required to understand how the firmware is structured and where to start adding/changing code. It’s easy for you, @kareltucek , as you already invested that learning effort and know the firmware inside out. Coming from outside, it’s much simpler to start with macros. Also, as @Firngrod already commented, the update / compile / flash cycle just takes much longer when playing around and trying “how would it work if I did this change?”

I have suggested a few firmware changes which will make HRM implementation in macros much simpler and concise, and would remove the load created by my HRM timer macro. I do hope some (or all) of it can get implemented at some point in time.

Well, I did not expect to be reprimanded for the macro, but I absolutely get your points.

Sorry, I didn’t want to be unsupportive, just wanted to be clear regarding the above points.

However, I would argue that this is exactly what the macro engine is great for! It allowed me to create a proof of concept which, more or less, exactly demonstrates what I want from a possible feature …

Great and totally valid points!

A small sidenote is that the macro engine doesn’t expose a lot of potentially useful information.

I am also sorry if my joke about “Just implement that, kthxbai” completely missed the mark. I assure you I meant no offense. I know how such comments can hit.

None taken. I have actually skimmed the text so briefly that I had mised those two paragraphs entirelly (Oops! Please, feel free to tag me in cases like these!).

To get to a stage where accidental mod triggers are truly rare, while intentional triggers are still easy and effortless, something else is needed: Only trigger from activation from keys from the other half of the keyboard! This one feature would elevate HRM usage on the UHK to a whole new level!

Thanks for stating that loud and clear! It will be done! (Give me a week or two. On vacation (afk) atm :sweat_smile:.)

If there are other such gems hidden in the text, please point them out in some clearly visible manner (like tagging me, or opening a github issue with a short and concise proposal).

The obstacle for me is the effort required to understand how the firmware is structured and where to start adding/changing code.

I am more than happy to provide support in that respect.

When it comes to a secondary role driver, it should suffice to duplicate and tweak this one function: firmware/right/src/secondary_role_driver.c at master · UltimateHackingKeyboard/firmware · GitHub . If additional nontrivial context is needed, I am happy to implement apis for it.


Admittedly, since we have switched to zephyr, installing a dev environment can be quite a pain.

1 Like

Glad to hear we’re all good! I would have hated to get off on a bad foot here.

I’m pretty sure it’s mainly that, and then the need to be able to enable/disable it on a per-key basis. I made the half lock-out part first, tried it out and then immediately made it optional because I found it intolerable on the thumb keys. Another example of why it was great to be able to make it and refine it in macros first, instead of it becoming a chain of requests for refinement in the firmware.

The resolution override is certainly nice for gaming, as I recall from a previous attempt of mine to use HRMs (as if I have time for gaming these days,) but it can be approximated with multiple keymaps, and simplified using macros with the overlay functionality.

I’ll have a think about it and make a formal feature request on Github soon, if I don’t just try implementing it in firmware myself, now that you have pointed the way to where to start digging. Don’t feel the need to rush on this, I have something to tide me over while I wait. :wink:

As for any ideas or comments anyone else may have about how the concept works until then, maybe we can try them out in the macro and see how it pans out, then take it from there.

I’m pretty sure it’s mainly that, and then the need to be able to enable/disable it on a per-key basis.

I don’t understand, please elaborate or provide a concrete example.

I’ll make a proper feature request on Github later with the necessary examples and clarifications.

1 Like

I know I shouldn’t, but the macro felt sluggish in a way it hadn’t for most of the time I spent developing, so I switched up the logic a little, and now it works a lot better.
The changes are only in the main loop, about the order in which the conditionals are evaluated

 while ($hrmState % 16 == 0) {
  ifPlaytime $secondaryRole.advanced.timeout setVar hrmState ($hrmState + 3 - $secondaryRole.advanced.timeoutAction) // we are at timeout, resolve what the user wants
  else ifDoubletap if ($hrmState % 8192 >= 4096 && $secondaryRole.advanced.doubletapToPrimary) setVar hrmState ($hrmState + 1) // doubletap to primary Note that double tap triggers from the macro id alone, regardless of which key ran it, so we have to verify that last macro run was the same key
  ifPending 1 if ($hrmState % 16 == 0) { // other keys have been pressed.
   if (!$secondaryRole.advanced.triggerByRelease) { // direct resolution as per user request.
    if ($hrmState % 16384 >= 8192 && ($thisKeyId >= 64) == ($queuedKeyId.0 >= 64)) setVar hrmState ($hrmState + 1) // we are side-aware and the key is from this side, resolve primary
    else setVar hrmState ($hrmState + 2) // otherwise, resolve secondary
   }
   else if ($hrmState % 4096 == 0 || $secondaryRole.advanced.safetyMargin < 0) while (1) {   // if we are not already timing out, or are timing out on primary
    ifPendingKeyReleased ($hrmState % 16) { // A pending key has been released, we can resolve
     if ($hrmState % 16384 >= 8192 && ($thisKeyId >= 64) == ($queuedKeyId.($hrmState % 16) >= 64)) setVar hrmState ($hrmState / 16 * 16 + 1) // released key is from same side of the keyboard as this key, and we are side aware, so we trigger primary
     else { // if time-shifting towards primary role, we don't resolve right now, but set the time-out after which we will resolve, if we haven't already
      if ($secondaryRole.advanced.triggerByRelease && $secondaryRole.advanced.safetyMargin > 0 && $hrmState % 4096 / 16 == 0) setVar hrmState ($hrmState / 16 * 16 + $currentTime % 256 * 16)
      else if (!$secondaryRole.advanced.triggerByRelease || $secondaryRole.advanced.safetyMargin <= 0) setVar hrmState ($hrmState / 16 * 16 + 2) // otherwise, we resolve now
     }
     break
    }
    else ifPending ($hrmState % 16 + 2) setVar hrmState ($hrmState + 1) // check next key
    else {
     setVar hrmState ($hrmState / 16 * 16) // no more keys to check, clear working memory
     break
    }
   }
   if (1) noOp // https://github.com/UltimateHackingKeyboard/firmware/issues/1330
  }
  ifReleased if ($hrmState % 16 == 0) {
   // if time-shifting towards secondary role, we don't resolve right now, but start the time-out to resolve if we haven't already
   if ($secondaryRole.advanced.triggerByRelease && $secondaryRole.advanced.safetyMargin < 0 && $hrmState % 4096 == 0) setVar hrmState ($hrmState + $currentTime % 256 * 16)
   else if (!$secondaryRole.advanced.triggerByRelease || $secondaryRole.advanced.safetyMargin >= 0) setVar hrmState ($hrmState + 1) // not time-shifting, resolve now
  }
  // check if we are time-shifting and have timed out, only if we have no other resolution
  if ($hrmState % 16 == 0 && $secondaryRole.advanced.triggerByRelease && $hrmState % 4096 != 0 && ($currentTime - $hrmState % 4096 / 16) % 256 > $secondaryRole.advanced.safetyMargin && ($currentTime - $hrmState % 4096 / 16) % 256 > -$secondaryRole.advanced.safetyMargin) {
   // time shift has timed out, so we resolve according to which action was shifted
   if ($secondaryRole.advanced.safetyMargin < 0) {
    setVar hrmState ($hrmState + 1)
   }
   else setVar hrmState ($hrmState + 2)
  }
 }
 if (1) noOp // https://github.com/UltimateHackingKeyboard/firmware/issues/1330

So if anyone tried it and felt it was sluggish, try it with that updated loop.
Also, if anyone tried it at all, I would love to hear any input from you guys before I make an official feature request. I want to make the request contain everything it needs to to make a solid, contained improvement to the keyboard, but not anything more than that either.

1 Like

I haven’t tried your macro (I have limited time available right now), I just glanced over it. What I don’t get is: what does it offer that my hrm macro solution does not do? I have same-half-triggers-primary, I have secondary with timeout, I also have fast typing streak detection (to avoid spurious mod activations). I don’t understand what features are better or more in our macros.

What I liked is that you derived the timing automatically from the normal primary/secondary configuration; that’s a great idea. I might give it a try to see how that could work with my macros.

Maybe I didn’t find your macro’s final form? The one I found, based on ifShortcut, had a trigger by press behavior, and therefore lacked the possibility of combining mods. My macro, when used with Trigger by release, allows any combination of mods from one hand with any key on the other hand. Unfortunately, that feature was a must for me, and I do not see that it can be done with ifShortcut as it is right now. Otherwise, I would have much preferred the simplicity of your implementation.
Your implementation is also conceptually simpler to deploy to an existing layout. One could use the same method of macro duplication for mine, except that it would take up a lot of disc space, and would be a nightmare to implement changes in.
I think I also like your idea of timing out mods for a short period after each primary trigger, but I don’t see how that can be done without either using another variable, or having a parallel long-running macro like you do, both of which I have picked as “optional challenges” to avoid for my mod implementation.
Maybe I will grab another variable and see how well it works, if it’s worth it to me.

Hi @Firngrod, I’m amazed with your macro. I’d like to watch your config to learn more about it. Could you please share with us the password for the link in Proton Drive?

The link should just work without a password. I also just now updated that file with the new version of the loop. The only thing of note in the configuration apart from that macro is how I record and play runtime macros. I postpone keys waiting for the next key, then use that key as the macro slot, like it works in Vim, rather than having to have multiple keys pre-determined as macro slots. Then there’s the actual layout. It’s just based on some keylogged data for a week or two’s usage to put the most used keys on home row and the others distributed to try and avoid keys which are usually conspicuously connected being on the same finger, such as h and t. Or h and g. Or h and p. Or h and w. Or h and s. Maybe I should have put H on a thumb key?

1 Like

Thanks @Firngrod , I was already able to download it!! I’ll check it out.

I am stuck on the west patch command. It complains about missing git in python although I have it globally installed with pacman -S python-gitpython I use Arch by the way.
I think Arch doesn’t play nice with Python. I should probably set up a virtual environment in Python, but now we’re already down a rabbit hole in Python before I even got to open a C file.