kareltucek / firmware

This is fork of UHK's firmware featuring extended macro engine.
Other
82 stars 9 forks source link

How to reset delayUntil action/switch keymap after delay. #60

Closed MatCyg closed 3 years ago

MatCyg commented 3 years ago

I am trying to move my configuration from karabiner-elements to UHK. I played a bit with the commands and I thought I have found a way I could achieve it. Unfortunately, it doesn't seem to work. Wanted to ask, if it is even possible to achieve what I want. Thank you for any input.

So far I am using the following configuration with karabiner-elements: Two layers - writing_layer and navigation_layer

navigation_layer:

writing_layer:

While in writing_layer if there's no button pressed for 600ms, it switches back to navigation_layer. This is achieved by using a delayed action on all the buttons on writing_layer. This delayed action is cleared if any other button is pressed before the 600ms timeout. I figured I could do the same with UHK macros:

On NAVigation_layer each button (besides jkl;) would switch to WRiTing_layer and would have a delayed action to go back to NAVigation_layer

$switchKeymap WRT
$tapKey a
$delayUntil 600
$switchKeymap NAV

On WRiTing_layer each button would stopAllMacros - disabling the delayUntil action to switch back to NAVigation_layer

$stopAllMacros
$tapKey a
$delayUntil 600
$switchKeymap NAV

Unfortunately, the stopAllMacros command doesn't stop the delayUntil action. I also tried with layers instead of keymaps, but the result was the same. Is there any way to clear the delayUntil action? Is there any other/better way of achieving the above?

--Edit-- My bad, I missed ifInterrupted command. Using that instead of stopAllMacros works as expected. Sorry for bothering you. Can be closed.

$tapKey a
$delayUntil 600
$ifInterrupted break
$switchKeymap NAV
kareltucek commented 3 years ago

Wow, cool usecase :-).

Unfortunately, the stopAllMacros command doesn't stop the delayUntil action

Sounds like a bug. Therefore, let's leave this open...

Is there any other/better way of achieving the above?

Your approach requires defining a macro for each key of the keyboard, doesn't it?

It might be possible to program this using call MACRONAME, ifInterrupted, activateKeyPostponed and some register signaling. But this is without a guarantee - these are pretty niche commands and therefore might contain bugs or might be removed in future...

Also, adding a macro event for onKeyPress as part of https://github.com/UltimateHackingKeyboard/firmware/issues/351 might help you.

kareltucek commented 3 years ago

(Also, with this level of complexity you might also consider digging straight into the firmware C code.)

MatCyg commented 3 years ago

Wow, cool usecase :-).

I was tired of holding MOD key all the time 😉

Your approach requires defining a macro for each key of the keyboard, doesn't it?

Yeah, that's the worrying part. Currently, it requires around 120-130 macros (2 per key).

(Also, with this level of complexity you might also consider digging straight into the firmware C code.)

I am considering this right now. I am afraid 'macro based' approach will eat too much memory. Any hint where to start looking to implement this? Had a look at the code before but felt a bit lost so I tried using this firmware.

Also, adding a macro event for onKeyPress as part of UltimateHackingKeyboard#351 might help you.

I am afraid the official smart macros feature will take months before it is completed. Would you consider accepting a PR with a feature simplifying my use case? onKeyPress might be hard to implement, but something like $tapSameKeyFromKeymap WRT could be much simpler. With that, I would need ~3 macros.

kareltucek commented 3 years ago

I am considering this right now. I am afraid 'macro based' approach will eat too much memory. Any hint where to start looking to implement this? Had a look at the code before but felt a bit lost so I tried using this firmware.

Basically all keyboard logic is implemented in usb_report_updater.c. Specifically the top level UpdateUsbReports function and its call to updateActiveUsbReports. (Don't let the name fool you - the file has nothing to do with USB protocols.)

I am afraid the official smart macros feature will take months before it is completed.

I really hope not, although considering my inability to reserve time for UHK in last months, I cannot rule it out.

Would you consider accepting a PR with a feature simplifying my use case? onKeyPress might be hard to implement, but something like $tapSameKeyFromKeymap WRT could be much simpler. With that, I would need ~3 macros.

That's where activateKeyPostponed can help you - it basically enqueues a hardware key for execution in next (couple) update cycle(s), so you can switch to that layer and enqueue the key (#key in place of a number serves as self-reference) for activation.

Regarding new command:

So consider yes, but actually accepting probably not. On the other hand, if you can come up with more intuitive naming or some sort of syntactic sugar that would simplify achieving your usecase with current features (i.e., activation via postponer queue), I will be more than happy to accept a PR or implement it.

kareltucek commented 3 years ago

Unfortunately, the stopAllMacros command doesn't stop the delayUntil action. I also tried with layers instead of keymaps, but the result was the same. Is there any way to clear the delayUntil action? Is there any other/better way of achieving the above?

I cannot reproduce this.

Using:

$delayUntil 1000
$tapKeyA
$stopAllMacros
MatCyg commented 3 years ago

Have a look at this UHK configuration.

There are two implementations one with ifInterrupted and one with stopAllMacros. Expected behavior: We start with Navigation (NAV) keymap. Once any letter is pressed, the keymap is changed to Writing (WRT). If we don't press any button on the WRT keymap it should change back to NAV.

  1. ifInterrupted implementation: On NAV press n and immediately after press m. Continue pressing m. The keyboard will stay in WRT mode for as long as you press the m letter. If you stop, it will go back to the NAV keymap after a 1-second delay.

  2. stopAllMacros implementation: On NAV press a and immediately after press s. Continue pressing s. Even though the first command in the WRT_S macro is $stopAllMacros, your keymap will switch back to NAV.

Let me know if this is enough of an explanation. If not I can provide a better description.

kareltucek commented 3 years ago

Alright, found the problems. Two actually:

1) Macro stopping was done via a flag that would signal a macro to stop. The ("stopped") macro would later tidy after itself as if it stopped of its own will, except it would allow one more tick to be executed (which was not intended). This probably didn't affect your usecase, but definitely was not correct.

2) When a delay command was interrupted this way, it would not clean after itself. Original author of that code was not very defensive and did not initialize his state variables (of the delay mechanism) with each macro start - rather assumed things would work with clockwork precision. Which it did, until I came and started extending the engine :-).

Should be fixed in uhk-firmware-8.10.10.kt.2.tar.gz (E: published as prerelease, so it may not show on the main page) . (That release may be a bit unstable - I will be grateful for more bug reports if you encounter any problems.)

Thanks!

kareltucek commented 3 years ago

I am afraid the official smart macros feature will take months before it is completed. Would you consider accepting a PR with a feature simplifying my use case? onKeyPress might be hard to implement, but something like $tapSameKeyFromKeymap WRT could be much simpler. With that, I would need ~3 macros

I've been thinking about this, and activating a different-layer-key without leaving a layer is actually quite a strong feature, which can allow for a number of usecases which would otherwise have to be implemented in much hackier way.

I think I will implement it eventually, maybe by modification of activateKeyPostponed. But I probably won't get to that soon.

If you decide to dig into it yourself, a PR is welcome. In that case, you probably want to base it on branch common_trunk_upstream. Also, there is an omprovement to the activateKeyPostponed so that it works reliably even when the key in question is active - which I guess might have caused some troubles to you...

Regarding implementation - the idea is to push a new key event into postponer's queue from a macro (just like current activateKeyPostponed, and extend postponer state to contain an optional flag denoting layer at which the key should be activated. Then, the piece of code that updates action cache will need to be updated to take the new flag into consideration. I think it should be safe to simply respect current postponer record flags if postponer is currently active.