Miryoku And homerow mods

I tried different timings, but none worked perfect. Now I am still trying the mods on the bottom-row. That seems to be working good so far. Likely I will keep it that way. That has also the advantage that Shift is in the same row, which I do not want as a home-row mod, like explained above:

image

Back to experimenting with homerow mods. I still get spurious activations when I do it on the UHK.

I have also tried Kanata and seem to have a bit more success with it. Kanata allows a lot of finetuning for secondary activations (tap-hold) for each key.

How would I best implement these behaviours with the UHK?

Here are key actions that Kanata offers. I think UHK can do the first few easily:

tap-hold tap-timeout hold-timeout tap-action hold-action

  • key pressed shorter than hold-timeout, then released => tap-action
  • key pressed longer than hold-timeout => hold-action
  • key pressed shorter than hold-timeout and released, and within tap-timeout pressed again and held => tap-action activated twice, the second time it is held (e.g. auto-repeat)

tap-hold-press tap-timeout hold-timeout tap-action hold-action

  • like tap-hold, but if an additional key is pressed, hold-action activates even before hold-timeout is reached

tap-hold-release tap-timeout hold-timeout tap-action hold-action

  • like tap-hold, but if an additional key is pressed+released, hold-action activates even before hold-timeout is reached

tap-hold-press-timeout tap-timeout hold-timeout tap-action hold-action timeout-action
tap-hold-release-timeout tap-timeout hold-timeout tap-action hold-action timeout-action

  • an additional 5th parameter defines the action to activate when the hold-timeout is reached
  • hold-action is only triggered by press/release of a second key
  • previous commands (without the 5th parameter) use hold-action for both cases

Here’s my first question:

What if I wanted different timings for different home row mods? Some of my fingers are faster than others… How would I do this on the UHK? I guess all of this could be constructed with individual macros for each key, either using ifPrimary/ifSecondary, or rolling it manually with ifPlaytime/ifInterrupted.

Now, Kanata also allows these:

tap-hold-release-keys tap-timeout hold-timeout tap-action hold-action key-list

  • 5th parameter contains a list of keys which will trigger an early tap-action. Otherwise it behaves as tap-hold-release

tap-hold-except-keys tap-timeout hold-timeout tap-action hold-action key-list

  • 5th parameter is a list of keys that always trigger a tap, but the output only happens after the key is released or any other key is pressed.
  • Sounds complicated; I am not sure what this is useful for.

Anyway, with tap-hold-release-keys you can define some keys which will always trigger the tap (unless you hold the initial key beyond the hold-timeout before pressing the second key). This allows construction of home-row mods which won’t trigger mod behaviour for quick rolls with certain defined keys. For example, you could make a left shift which won’t trigger with keys on the left half, and right shift which won’t trigger with keys on the right half.

So my second question:

How could I - on the UHK - create secondary behaviour which only triggers together with certain keys, or which does not trigger with a defined list of keys?

I have quite a spinning brain now that I’ve seen all these possibilities…!

I will add another question. I am using home-row mods now (on the bottom row) and that works for me without problems as far I can see now.

But when using combos I find the order in which I press the key matters. I only have Gui, Alt and Ctrl as a homerow mod, but use shift as a one-shot key in combination with those. When I need the shortcut Shift+Ctrl I need to hold the Ctrl (homerow mod) key first and then add the Shift key. When I hold the Shift key first the Ctrl function is not triggered. Is there a way that the order of the held keys is not important?

HRM = homerow-mod

HRM-ctrl held, then Shift held → combo recognized
(one-shot) Shift held, then HRM-ctrl held → combo not recognized

Home row mods - a new approach

I have revamped the way I have implemented homerow mods (HRM). The base functionality is still triggered as a secondary function, but out of a macro (via ifSecondary), and I added a timer that disables the mods during fast typing streaks.

Unfortunately, this requires a separate macro for each home row key, plus a few more. On the other hand, it again shows the power of UHK macros :wink:

On the positive side, I no longer get spurious activations during fast typing.

To keep it modular, I have moved the home row keys onto a separate keymap (HRM) which is used to overlay my regular keymap.

The base layer contains regular key mappings without mod functionality.

The mod layer maps home-row-mod macros onto each of the home row keys.

My home row mappings are:
a - left alt
s - left gui
d - left shift
f - left control
g - left gui
h - right gui
j - right control
k - right shift
l - right gui
;: - left alt
\| (ISO) - right alt
/? - right alt

Note that I made sure both Alt and AltGr are available on both hands.

Each of the homerow keys now has a macro like this:

hrm-LS-d:

if ($hrm_active > 0) ifSecondary final holdKey leftShift
setVar hrm_tick 0
holdKey d

Adapt holdKey leftShift and holdKey d for each of the keys.

There are two new concepts here:

  1. The secondary function is only checked if $hrm_active is set. You can turn home row mod functionality on and off by toggling that variable (0 = hrm off, 1 = hrm on).
  2. A tick counter is reset each time the primary function is triggered (normal typing).

In addition, a timer macro in the background keeps incrementing the tick counter. Depending on how many ticks have passed, it will change the hrm_active state. Basically, when the tick is reset by a primary key activation, hrms are turned off for a certain interval (configurable, I use 40ms). As long as more primary keys are typed during that time, the interval gets extended. Once there are no primary keys for more than the interval, hrms are turned on again. This will protect the hrms from spontaneous incorrect activation during typing streaks.

hrm-timer:

setVar hrm_timer_active 1
setVar hrm_active_state $hrm_active
setLedTxt 800 '+T+'
// if tick is < tick_active, hrm should be inactive
// if tick is >= tick_active, hrm should be active
// primary key activations will reset tick => fast typing streak keeps hrm inactive
loop:
    setVar hrm_tick ($hrm_tick + 1)
    if ($hrm_active > 0) {
        if ($hrm_tick < $hrm_tick_active) setVar hrm_active 0
    }
    else {
        if ($hrm_tick >= $hrm_tick_active) setVar hrm_active 1
    }
    // the next section is just about showing state changes on LED display
    if ($hrm_active_state == 0) {
        if ($hrm_active > 0) {
            setLedTxt 0 'H++'
            setVar hrm_active_state 1
        }
    }
    else {
        if ($hrm_active == 0) {
            setLedTxt 0 'H--'
            setVar hrm_active_state 0
        }
    }
    // --- ^^^ ---
    delayUntil $hrm_tick_delay
if ($hrm_timer_active > 0) goTo loop
setLedTxt 800 '-T-'

Finally, some macros to initialise the system.

hrm-on:

setVar hrm_timer_active 0
setVar hrm_active 1
overlayLayer base HRM mod
setLedTxt 666 'H++'

hrm-off:

setVar hrm_timer_active 0
setVar hrm_active 0
overlayLayer base HRM base
setLedTxt 666 'H--'

hrm-auto:

setVar hrm_active 1
setVar hrm_timer_active 0
setVar hrm_tick 0
setVar hrm_tick_delay 10
setVar hrm_tick_active 4
overlayLayer base HRM mod
setLedTxt 666 'H+-'
if ($hrm_timer_active == 0) fork hrm-timer

Bind hrm-on, hrm-off, and hrm-auto onto some keys. I put them on some keys on my regular fn layer:

If you do not run any of these three activation macros, your normal keymap will work as before.

hrm-on turns home row mods constantly on. They will work just like the normal secondary function of UHK. No background timer process. The LED display will briefly show H++ as they turn on.

hrm-off turns home row mods off. No background timer process. The LED display will briefly show H-- as they turn off.

hrm-auto switches to “auto mode”. The timer process will be active, and home row mods will turn on and off automatically depending on your typing. The LED display will briefly show H+-, and +T+ when the timer process is started. Afterwards, the display will toggle between H++ and H-- whenever the home row mods are activated or deactivated. During fast typing, you should see brief spurs of H-- (it’s really very short), immediately switching back to H++ whenever you are not typing.

Timing configuration

First, the regular secondary mode configuration of UHK applies. This is what I use; I set it in $onInit:

set keystrokeDelay 10
set secondaryRole.defaultStrategy advanced
set secondaryRole.advanced.timeout 250
set secondaryRole.advanced.timeoutAction secondary
set secondaryRole.advanced.triggerByRelease 1
set secondaryRole.advanced.safetyMargin +5

There are two variables in hrm-auto that are meant to be adjusted:

setVar hrm_tick_delay 10
setVar hrm_tick_active 4

hrm_tick_delay is the resolution of the tick timer in ms. Every $hrm_tick_delay ms the timer will increment the tick.
hrm_tick_active is the tick count after which hrm are activated again (in auto mode).

So the total interval that hrms get disabled during fast typing is about $hrm_tick_delay * $hrm_tick_active. It’s not totally precise because of macro execution speed etc. but it seems to be good enough.

For me, a tick delay of 10 and a tick active of 4 work well, resulting in about 40 ms of deactivation interval. I type approximately 80-90 wpm with bursts up to 110 wpm on good days.

I’ve used this configuration in auto mode for a while now, and I am definitely happier than without.

Possible future extensions:

  • Currently, only primary keystrokes on hrm keys trigger the deactivation interval. This could be extended to all alphanumeric keys, but it would need one macro for each key. I think that’s more a topic for an actual firmware implementation.
  • Unique deactivation intervals per hrm key are feasible (although I don’t think that is needed). In the key’s hrm macro, you’d need to check for the value of $hrm_tick instead of checking $hrm_active. Additional adaptions are then also needed for “continuous on” mode.

Interested in feedback. Does this work for you? Is it better?

1 Like

Home row mods - a new approach, part 2

Avoiding same-hand triggers

There is one more improvement that can be done to home row mods: avoiding triggering a mod if it is typed together with a key on the same hand.

This can now be added to the system from the previous post. Each of the macros bound to each of the hrm keys can be extended with an exclusion list. When the key is typed together with a key from the exclusion list, the primary function is activated immediately.

Note: You can still type hrm mods with keys of the same hand, but you will have to hold down the mod long enough (more than 100 ms in the macro example).

For a left-hand modifier:

hrm-LS-d:

ifShortcut timeoutIn 100 orGate noConsume q w e r t a s d f g z x c v b goTo primary
if ($hrm_active > 0) ifSecondary final holdKey leftShift
primary:
setVar hrm_tick 0
holdKey d

For a right-hand modifier:

hrm-RS-k:

ifShortcut timeoutIn 100 orGate noConsume y u i o p h j k l semicolonAndColon n m goTo primary
if ($hrm_active > 0) ifSecondary final holdKey rightShift
primary:
setVar hrm_tick 0
holdKey k

Again, feedback appreciated. This works extremely well for me!

Home row mods - a new approach, part 3

Extending auto-deactivation to all alpha keys

Extending the macro code for hrm-auto mode to trigger the deactivation interval on all alpha keys, my HRM keymap mod layer now looks like this:

Please also map all the corresponding keys in the HRM base layer to their regular keypress (no macro).

Each of the non-mod keys is mapped to a macro like this:

hrm-z:

setVar hrm_tick 0
holdKey z

This makes sure the deactivation timer tick is reset for all alpha keys, giving an even more consistent handling of the home-row mods.

Yeah, more macros needed… :person_shrugging:

It’s all for a good cause!

Max, that is really great you seem to have found a way to tame HRM on the UHK (like Achordion for QMK and similar settings for Kanata allow).

I personally did not took the time to replicate your approach on my UHK, because it seems quite involved and I do not have the urgent need for it. In my approach I use the HRM on the bottom row, where I do not get any wrong triggers. I got not many, but a few while testing them on the home-row. But for my layout the bottom row is a great place, still I see that the home-row itself is surely super interesting, when one gets it working without any problems and wrong triggers.

I see your work as a solution if one is really urgently wanting to get HRM working on the UHK. The relatively complicated setup is likely the reason that there is not so much feedback to your cool solution. For many it is likely more a proof of concept and should be integrated into the firmware and be exposed in such a way in Agent that one can set a few switches or general parameters to fine-tune the HRM for personal use.

That brings me to a few questions to ask to you – as now being or becoming the master of HRM :smiley: :

  1. Are you completely free of getting wrong triggers or having any downsides (fast and slow typing or just lingering with your keys on the keyboard or whatever situation) with your solution? So is it 100 % foolproof or “only” 99 or 99,9 %? Any timing issues with your approach or can a universal timing setup work for 30 wpm up to 150 wpm? That would really be great and make it easier to integrate in the firmware and Agent.

  2. Is avoiding same-hand triggers not enough? Likely not, when you have additional means to tame the HRM. Which problems persist after not allowing same-hand triggers?

I tried this out and can’t seem to get it to work. when I start typing, it behaves as if I have the Mod layer held down the entire time.

Tried messing with it a bit but couldn’t get it to work as expected… please advise.

For reference, my Mod layer controls all my desktops/workspaces, each accessed by a combination of modifer keys + a number key (1-4)

Can you share your config? I’m happy to take a look.

I agree. It’s just much easier for me to play around with macros, testing various ideas and timings until eventually I can suggest what to add to the firmware for better HRM support.

I get no wrong triggers now as fast typing and same hands will prevent the secondary from triggering.

I sometimes miss a shift and get an incorrect primary e or s (that’s where my shift mods sit), but it seems that is not because HRM are turned off, but because I do not type it well and I release the mod too early. This would trigger inadvert primaries even without my macros. So that’s down to my typing and there is no way the keyboard could distinguish what I intended to type.

The only enhancement I could think of for this is extending the secondaryRole.advanced.safetyMargin dynamically after a space has been typed – then it’s more likely that I intend to Uppercase the next letter. But you also need to change it back after that next letter has been typed. It could in theory all be worked into the macros, but needs changes everywhere… Lots of work, and for now I am trying to train myself to be more exact with my typing.

In the big scheme of things, maybe every HRM key should allow individual settings for all its timings. Everything that is now configured globally in the secondaryRole.advanced namespace should be allowed being overridden in parameters of the ifSecondary command. Super advanced users could then adjust this per key (using macros).

I don’t think so. You can still type fast with alternating hands.

My macros are primarily about avoiding false triggers. I think they do a decent job for that – at least for me.

Now I just want to find out why it didn’t work for @nacho. I suspect a config error; it’s really too complicated to set up, and you can make subtle errors in many places.

One reason for me to get away from hold modifiers. I did not had many problems with Shift, very seldom (due practice with years of typing), but I believe strongly that character layers do not belong on a held layer, but should be one-shot keys. The whole hold-down-a-key concept just came from really shifting the drum-roll (or the type-mechanism) on a typewriter. As qwerty it is an anachronism not making any sense any longer.

But accessing a layer from the home-row is also nice of course, so I understand the wish to get the layer shift onto the HR. There is just no perfect solution.

That, need to need to define too many exceptions, timings and whatever was what led me away from the HRM. So it sounds like a universal good setting for all users being the same is not that easy too find (if possible at all). Maybe I will try to add Ctrl on the home-row in addition. Most shortcuts are Ctrl-… shortcuts. On the other hand the really important ones I already have on my MOD/Navigation layer, so the need is also less.

Good luck in your adventure finding the perfect HRM solution, which you seem to be close or already have reached. Not only you are benefitting, but possibly all UHK users. Thanks for making this effort! :smiley:

You are welcome! I am enjoying this journey!

@nacho OK, I took a quick look.

The idea of the HRM keymap is that it is NOT a complete keymap with all layers. You also don’t switch to it manually. It’s base and mod layers will be used by the hrm-on, hrm-off, and hrm-auto macros to overlay your normal keymap.

So to create the HRM keymap, start with an empty keymap, then only configure the alpha keys. On the base layer, configure what you have on your alpha keys when you don’t use HRM. On the mod layer, configure the hrm macros that you’ve created for all the alpha keys.

Then just stay on your normal keymap (recurva). Add keys to trigger hrm-on, hrm-off, hrm-auto there. Then trigger these macros from your normal keymap. The macros will read the HRM keymap and overload keys on your normal keymap (in memory). Overlay means, those keys that are defined on HRM will be used, and all other keys will just stay as defined on your recurva keymap.

I think you were trying to use the HRM keymap as a complete keymap. That won’t work; it wasn’t designed that way.

1 Like

@maexxx Okay, thanks for your help. I’ve made the changes you’ve suggested and I think it is working fine. No issue from before.

Will give this a run and let you know if I run into any other issues.

Though, with my Recurva layout, I have home row keys as:
s n t c and h e a i
and home row mods as ctrl alt gui shift and shift gui alt ctrl

I am still running into the issue of misfiring the modifier keys, specifically when typing the word string. At the tr it fires the gui + t shortcut when I type too fast. I still need to slow down when typing anything with that tr chord.

Also, could you please clarify the differences, pros/cons, and when best do use hrm-on vs hrm-auto?

Edit: figured out the issue when typing string, I still had my secondary layer set on my Recurva keymap. Once I disabled this, I’m getting better results.

hrm-on turns on hrms as normal secondary. This should behave exactly the same as normal UHK secondary mods. The only exception is the same-hand avoidance, which is still active in this mode.

hrm-auto turns on the full automatic fast typing detection.

I now basically only use hrm-auto. I have the other modes so I can compare.

The goal of my macros is to avoid inadvertant activation of the mods – up to a point where you have to really slow down to get mods to activate.

To see what I mean you can play with hrm_tick_active, e.g. increase the blackout interval to see the effect. Try this (in hrm-auto):

setVar hrm_tick_delay 10
setVar hrm_tick_active 80

This will disable hrm for 800ms after regular keypresses. Then you’ll have to wait really long before being able to use any mods. And it will be basically impossible to have accidental activations.

So then I tuned hrm_tick_active down again to a value where I could type mods again without ridiculous pauses. Turned out something in the range 4-12 seems to work well for me.

1 Like

Home row mods - a new approach, part 4

Shift after space made easier

aka: allow sloppy shift after space

Warning: This part is highly experimental!

I’ve made my auto-disable period a bit larger now to definitely block out all spurious mods, so in hrm-auto I configured

setVar hrm_tick_active 30

resulting in blocking out hrms for 300ms after a primary keypress.

Now it gets somewhat difficult to start a new sentence with an Uppercase character because I have to wait 300ms before I can use the hrm Shift.

At the same time, I have this issue with my typing:

Let’s try to do that. :slight_smile: It’s a bit of a hack, but hey, all of these macros are clever hacks. I’m posting them here in the hope that others can steal some ideas and even improve upon!

This extension to the hrm macros will have two effects after typing a space:

  • hrms will immediately be re-enabled,
  • the safetyMargin will be reduced for Shift mods (and only for Shift) for up to 1 second after a space (or until that mod key is used).

Remember that the main purpose of the hrm macros is to avoid accidentally triggering secondary mods. Reenabling them immediately after space, and reducing the safetyMargin for shift means that it shifts (pun intended!) the probability towards triggering the shift mod after space, especially if the timing of your typing is not very precise. You might call this feature “allow sloppy shift after space”. On the downside, it may now accidentally trigger on words that start with your shift mod letter. You’ll need to play with the hrm_reduced_safety parameter.

Here is how it works.

Add three more initialisations to the setVar statements in hrm-auto:

setVar hrm_space 0
setVar hrm_reduced_safety 80
setVar safetyMargin $secondaryRole.advanced.safetyMargin

Set hrm_reduced_safety to how much you want to reduce the safety margin for shift mods (towards triggering shift) after having typed a space. This is in ms, not in ticks.

Map this to your Space key (left or right, whatever your main space is, or both).

hrm-space:

setVar hrm_tick $hrm_tick_active
setVar hrm_space 1
holdKey space
delayUntil 1000
setVar hrm_space 0

Also, change your hrm shift macros.

hrm-LS-d:

setVar had_secondary 0
ifShortcut timeoutIn 100 orGate noConsume q w e r t a s d f g z x c v b goTo primary
if ($hrm_space > 0) {
    set secondaryRole.advanced.safetyMargin ($safetyMargin - $hrm_reduced_safety)
    setLedTxt 0 'H+S'
}
if ($hrm_active > 0) ifSecondary setVar had_secondary 1
primary:
set secondaryRole.advanced.safetyMargin $safetyMargin
setVar hrm_space 0
if ($had_secondary) {
    holdKey leftShift
}
else {
    setVar hrm_tick 0
    holdKey d
}

hrm-RS-k:

setVar had_secondary 0
ifShortcut timeoutIn 100 orGate noConsume y u i o p h j k l semicolonAndColon n m goTo primary
if ($hrm_space > 0) {
    set secondaryRole.advanced.safetyMargin ($safetyMargin - $hrm_reduced_safety)
    setLedTxt 0 'H+S'
}
if ($hrm_active > 0) ifSecondary setVar had_secondary 1
primary:
set secondaryRole.advanced.safetyMargin $safetyMargin
setVar hrm_space 0
if ($had_secondary) {
    holdKey rightShift
}
else {
    setVar hrm_tick 0
    holdKey k
}

Make sure you run this in hrm-auto mode.

Yes, I know, it gets convoluted. I did say highly experimental, didn’t I?

A much simpler (downscaled) version

If you don’t want any of the safetyMargin reduction but just a quick re-enabling of hrms after a space, then you don’t need any of the above changes. Instead just map this macro to your space key(s):

hrm-space:

setVar hrm_tick $hrm_tick_active
holdKey space

Basically, setting hrm_tick to 0 turns off hrms via the hrm-timer, and setting it to $hrm_tick_active turns them on (again, via the hrm-timer).

It might be useful to bind a similar macro not only to space but also to other keys that often have an Uppercase letter afterwards, e.g. Return / Enter.

1 Like

Just added the hrm-space mod. Things seem to be going great, except I am unable to run the playMacro command, used in both of the following:

playMacro $thisKeyId

and

ifShift recordMacro $thisKeyId
ifNotShift playMacro $thisKeyId

Also, I seem to get some false errors about variables not being declared, but when I unplug/replug my device, the error goes away. Will running the command resetConfiguration work the same?

I also added fork hrm-auto to my $onInit but I am pretty sure I was getting the error before I added this.

When using hrm shift mod, I will sometimes get the output of hrm shift key + alpha letter to capitalize

So in my case, I’m often getting ci for when I want to type I or hs when I want S

I was experiencing this issue before adding the hrm-space mod.

By the way, thank you so much for your efforts in working on this! This is all amazing and I’m so happy that you’re getting this to work.

Edit: I got the playMacro working with this script

ifSecondary final recordMacro $thisKeyId
playMacro $thisKeyId

I don’t know what your issue with the playMacro is, or where you’ve bound it. I don’t see how that would be related to the hrm macros.

Yeah, with the space mod, you have to run hrm-auto or type space at least once before typing any hrm mod key, else some space vars are undefined. experimental!

I would not fork hrm-auto but instead call hrm-auto.

Sounds like one of two things are happening:

  1. You are typing fast and hrms are still turned off by the auto-deactivation interval.

    • Check how large you have set hrm_tick_active and try reducing it. Do you see H-- in the display (meaning hrms are off) while you are getting those false keys, or has the display already turned back to H++ (meaning hrms are on again)?
  2. You are releasing the mod too early. In this case, this has nothing to do with my hrm macros. You need to make sure that your fingers are typing:

    • mod down
    • key down
    • key up
    • mod up

    If you release mod too early, then the ifSecondary logic cannot distinguish this, and sees it as two primary keystrokes:

    • mod down
    • key down
    • mod up ← this will look like you are typing the mod character
    • key up

    You can adjust this behaviour with secondaryRole.advanced.safetyMargin (via macro code or via the Agent UI (Typing behaviour → Trigger safety margin). Negative numbers skew this to delay your “mod up” so that it looks to the engine that you released it later. If you make it too small, though, you will start to get accidental mods during fast typing. (The hrm macros can help with that with the auto-deactivation interval, but the root cause is a probability game in the timing of your typing.)

1 Like

Gotcha, thanks again for the support!

I think this had something to do with the ifShift and ifNotShift, but I just reconfigured the script in my post edit above and now it works.

Edit: just ran some typing tests and it’s indistinguishable from not having HRM! Meaning, there’s no misfiring or faulty inputs for me.

1 Like