zmk
zmk copied to clipboard
feat(behaviors): Add dynamic macros
Adds the dynamic macro behavior, allowing users to create macros on the fly.
I am testing this and I can say that on the BT60 (nrf52840) the feature crashes if more than 30 keystrokes are entered. Otherwise, it works as expected and shows great flexibility in recording even keystroke combinations. If there is a memory hard limit that can be determined per processor, or an overall safe limit, it might be worth adding that because when going over the limit there are various odd things that happen.
I would suggest that the documentation be amended to say explicitly that RECORD is pressed a second time to stop the recording. This may be obvious, but there will be people confused.
If there is a memory hard limit that can be determined per processor, or an overall safe limit, it might be worth adding that because when going over the limit there are various odd things that happen.
I think I'm going to add a config option to set the limit. Do you think it should stay in the recording state but not actually record any more binds when it hits the limit?
If there is a memory hard limit that can be determined per processor, or an overall safe limit, it might be worth adding that because when going over the limit there are various odd things that happen.
I think I'm going to add a config option to set the limit. Do you think it should stay in the recording state but not actually record any more binds when it hits the limit?
It is a bit confusing because there is no way of telling whether the recording mode is still engaged. It one hit the limit, then there could be a problem that trying to stop the record mode would only engage it again. Perhaps the best would be to leave it in the recording state so that pressing record again would finish recording instead of start recording again.
Hi, I tried the feature and, although I like it, I would like to share some feedback:
- I believe the recording should also capture the time between keystrokes and replay them as they were captured. I've found myself in situations where I recorded a macro, but it was played too fast or too slow.
- Also, if I record some keys, and then spam 'play', at some point the replayed keystrokes get messed up. Ideally I think if I hit play while playback is already ongoing, it should be a no-op.
- I think the keyboard should not send keystrokes to the host during recording. It will be a nice way to tell if a recording is ongoing. Maybe this can be made configurable (e.g. two different bindings for recording, or globally through a config option).
Additionally, although I am personally very new to ZMK in general, I'd love to help getting this merged. Let me know if there is something I can help with :)
Hi, I tried the feature and, although I like it, I would like to share some feedback:
- I believe the recording should also capture the time between keystrokes and replay them as they were captured. I've found myself in situations where I recorded a macro, but it was played too fast or too slow.
- Also, if I record some keys, and then spam 'play', at some point the replayed keystrokes get messed up. Ideally I think if I hit play while playback is already ongoing, it should be a no-op.
- I think the keyboard should not send keystrokes to the host during recording. It will be a nice way to tell if a recording is ongoing. Maybe this can be made configurable (e.g. two different bindings for recording, or globally through a config option).
Additionally, although I am personally very new to ZMK in general, I'd love to help getting this merged. Let me know if there is something I can help with :)
By default, the dynamic macro should play back at the speed it was recorded at. Make sure you DON'T have wait-ms
set.
Good idea on setting play to no-op when recording. I'll add that here shortly.
I can probably add the no-output pretty easily, but not sending the keystrokes to the host would make it hard to tell if you're inputting correctly, no?
Hi, I tried the feature and, although I like it, I would like to share some feedback:
- I believe the recording should also capture the time between keystrokes and replay them as they were captured. I've found myself in situations where I recorded a macro, but it was played too fast or too slow.
- Also, if I record some keys, and then spam 'play', at some point the replayed keystrokes get messed up. Ideally I think if I hit play while playback is already ongoing, it should be a no-op.
- I think the keyboard should not send keystrokes to the host during recording. It will be a nice way to tell if a recording is ongoing. Maybe this can be made configurable (e.g. two different bindings for recording, or globally through a config option).
Additionally, although I am personally very new to ZMK in general, I'd love to help getting this merged. Let me know if there is something I can help with :)
By default, the dynamic macro should play back at the speed it was recorded at. Make sure you DON'T have
wait-ms
set.Good idea on setting play to no-op when recording. I'll add that here shortly.
I can probably add the no-output pretty easily, but not sending the keystrokes to the host would make it hard to tell if you're inputting correctly, no?
I've added all these now, to have no output set no-output
in the definition for the dynamic macro!
@nickconway
dynamic-macros.md: a macro will play back at the speed it was recorded
Wow I totally missed that part on the description 😃
Regarding no-output
, in retrospect I believe you are right. I am glad you added the option to configure it though! Could be useful for others.
@nickconway some additional notes:
- The no-op suggestion was about hitting play while the playback of a macro is ongoing (not the recording). The one implemented indeed seems like a welcome bug-fix, but what is your view on this one?
- I also think there is a bug somewhere... I tried recording
test123
and during playback it flipped the output totest312
or similar. I re-flashed and I cannot reproduce it now. Will let you know if I can manage to properly reproduce this, just wanted to bring it to your attention.
*edited to add details about a possible bug
- The no-op suggestion was about hitting play while the playback of a macro is ongoing (not the recording). The one implemented indeed seems like a welcome bug-fix, but what is your view on this one?
So queuing up the macro to play back while it's already playing will probably have some weird effects. This happens with regular macros too. It's possible that we could keep track of the total runtime and not let it play again within that time, but since this is an issue with all macros, we should try to fix the root problem first if needed, which probably isn't trivial.
- I also think there is a bug somewhere... I tried recording
test123
and during playback it flipped the output totest312
or similar. I re-flashed and I cannot reproduce it now. Will let you know if I can manage to properly reproduce this, just wanted to bring it to your attention.
If you can reproduce it and get a log file that'd be great.
How can I add this for my keyboard build?
@idesignstuff hi, a good resource on how to configure your CI builds to use the branch that belongs to this PR is the following docs page: Beta Testing | ZMK Firmware.
You can also take a look at my config here for a complete implementation.
@nickconway still new to ZMK so I may be wrong, but it looks like (as this is a macro) play and record can't be on the same key? I'd like to set it so hold records and tapping plays using tap-dance or hold-tap but it doesn't look like it's possible?
Is there any possibility that &key_repeat
can repeat the last played macro? I suppose this applies to any macro, not just dynamic ones. My usecase is that I have PLAY/RECORD nested in a util layer, but if I want to easily execute the macro many times, I can play the macro but then spam repeat key on my base layer (which spams PLAY).
Does not seem to be working when merged with the latest updates to Zephyr 3.5. It builds but throws these messages:
212 | static const struct behavior_driver_api behavior_macro_driver_api = {
| ^~~~~~~~~~~~~~~~~~~~~~~~~
/home/pc/zmk/app/src/behaviors/behavior_macro.c:110:12: warning: 'behavior_macro_init' defined but not used [-Wunused-function]
110 | static int behavior_macro_init(const struct device *dev) {
| ^~~~~~~~~~~~~~~~~~~
[33/310] Building C object CMakeFiles/app.dir/src/behaviors/behavior_dynamic_macro.c.obj
/home/pc/zmk/app/src/behaviors/behavior_dynamic_macro.c:218:13: warning: APPLICATION device driver level is deprecated
218 | DT_INST_FOREACH_STATUS_OKAY(DYNAMIC_MACRO_INST)
Any ideas? It still worked on zephyr 3.2
After the recent changes, I still cannot make it work with the latest zmk branch (3.5), although if I change APPLICATION in line 218 of the behaviors/behavior_dynamic_macro.c to POST_KERNEL, no errors are thrown.
I am building my BT60 repo against my zmk repo locally: https://github.com/claviger-pc/zmk https://github.com/claviger-pc/bt60_v1_clav
After the recent changes, I still cannot make it work with the latest zmk branch (3.5), although if I change APPLICATION in line 218 of the behaviors/behavior_dynamic_macro.c to POST_KERNEL, no errors are thrown.
I am building my BT60 repo against my zmk repo locally: https://github.com/claviger-pc/zmk https://github.com/claviger-pc/bt60_v1_clav
Should be fixed now!
After the recent changes, I still cannot make it work with the latest zmk branch (3.5), although if I change APPLICATION in line 218 of the behaviors/behavior_dynamic_macro.c to POST_KERNEL, no errors are thrown. I am building my BT60 repo against my zmk repo locally: https://github.com/claviger-pc/zmk https://github.com/claviger-pc/bt60_v1_clav
Should be fixed now!
I am sorry that I did not make my problem clearer. Changing APPLICATION
to POST_KERNEL
did stop the error when building, but it did not fix anything when trying to build with the Zephyr 3.5 updates. In fact, when I was trying to build before, the mapped places on the keymap for RECORD
and PLAY
were transparent rather than non-functional (I have them on a secondary layer). Now, however, I made sure I am building according to zmk master and your dynamic-macro commits. It build with no errors, and the keys are not longer transparent, but seemed to do nothing. Then I decided to see what logging would tell me, and I find that in fact the recording and playback do seem to work, but there is no output, that is, the actual keypresses are not registered (MacOS).
[00:07:55.233,520] <dbg> zmk: on_dynamic_macro_binding_pressed: Recording Status: 1
[00:07:55.233,520] <dbg> zmk: on_dynamic_macro_binding_pressed: Recording new macro: 15
[00:07:56.952,545] <dbg> zmk: on_dynamic_macro_binding_pressed: Recording Status: 0
[00:07:57.216,247] <dbg> zmk: kscan_matrix_read: Sending event at 1,1 state off
[00:07:57.963,531] <dbg> zmk: on_dynamic_macro_binding_pressed: Playing Dynamic Macro
[00:07:57.963,531] <dbg> zmk: queue_dynamic_macro: Iterating dynamic macro bindings - count: 8
[00:07:57.963,592] <dbg> zmk: behavior_queue_process_next: Invoking KEY_PRESS: 0x70004 0x00
[00:07:57.963,623] <dbg> zmk: behavior_queue_process_next: Processing next queued behavior in 48ms
[00:07:58.011,749] <dbg> zmk: behavior_queue_process_next: Invoking KEY_PRESS: 0x70016 0x00
[00:07:58.011,779] <dbg> zmk: behavior_queue_process_next: Processing next queued behavior in 44ms
[00:07:58.055,908] <dbg> zmk: behavior_queue_process_next: Invoking KEY_PRESS: 0x70007 0x00
[00:07:58.055,969] <dbg> zmk: behavior_queue_process_next: Processing next queued behavior in 94ms
@claviger-pc How close is this feature to being complete? It's one of the key feature differences (in my opinion) between ZMK and QMK, and I'd love to implement it into my project.
@claviger-pc How close is this feature to being complete? It's one of the key feature differences (in my opinion) between ZMK and QMK, and I'd love to implement it into my project.
This feature is also available in QMK: https://github.com/qmk/qmk_firmware/blob/master/docs/feature_dynamic_macros.md
Unfortunately this PR does not seem to work on the latest version of ZMK, see my comments above.
@claviger-pc How close is this feature to being complete? It's one of the key feature differences (in my opinion) between ZMK and QMK, and I'd love to implement it into my project.
This feature is also available in QMK: https://github.com/qmk/qmk_firmware/blob/master/docs/feature_dynamic_macros.md
Unfortunately this PR does not seem to work on the latest version of ZMK, see my comments above.
That is unfortunate, hopefully this can be resolved in the coming month or so.
@nickconway -- I tried again today to build against your branch and the same thing happens: there is no output. Is this working for you? The only thing I can find in my setup that is non standard is that I had defined wait-ms
as 10ms. No errors in building, and the logs show as above, but there is no output. I also tried it without the wait-ms
and it did the same