Skip to content

Add string macro advanced key support#22

Open
yiancar wants to merge 4 commits into
peppapighs:devfrom
Norbauer-Co:feature/string-macro
Open

Add string macro advanced key support#22
yiancar wants to merge 4 commits into
peppapighs:devfrom
Norbauer-Co:feature/string-macro

Conversation

@yiancar
Copy link
Copy Markdown
Contributor

@yiancar yiancar commented May 12, 2026

As discussed here is a first draft for the string macro:)

I used AI to help make a UI for testing but it looks quite smart:
peppapighs/hmkconf#7

@peppapighs
Copy link
Copy Markdown
Owner

Oh I thought we want to implement the single linked list in case we want to support full macro in the future?

@yiancar
Copy link
Copy Markdown
Contributor Author

yiancar commented May 14, 2026

Yes you are right this was simpler for me to write 😢
If you like I can go back and try the linked list approach even tho its a bit more complex.
I know right now this is not the best utilization of space as we might have dead space in the middle of macros, especially if macros get deleted and readded.
What do we mean by full macro?

@peppapighs
Copy link
Copy Markdown
Owner

By full macro, I meant macro system where we support setting delay for each action, and possibly pointing the event to the next one to create a loop etc.

I don't really have the full picture of how it will look like, but I imagine either the web configurator or the firmware to walk through the macro and compact/garbage collect the linked list buffer if it is full. Each node can have a key, delay in matrix cycle/milliseconds (potentially 0 so we can press a character and shift and the same time), and the next node.

For the first simple version, I think maybe we can start with just a linked list of characters to support sending string for now, and keep the UI you proposed for hmkconf since the UI for the full macro system probably requires a lot of thought.

For the garbage collection, I meant something like:

  • We have a linked list buffer with a pointer to track the number of element, and the list of macro, which is just an array of indices into the buffer.
  • When adding a new linked list, we just modify the nodes that the pointer points to and increment the pointer forward. Keep doing this until the end of macro.
  • When deleting a macro, we simply just remove the index from the list of macro. This will result in some unused space in the buffer, but we don't care about it for now.
  • Eventually, the buffer is full. We loop through each macro in the list, and compact them (kinda like disk defragmentation), so we claim back the unused elements in the array.

Might be too complex but I'm open for suggestions. Sorry for the back and forth, but it's kinda sad that if one day, we implement this macro system then its functionality will overlap with this PR.

@yiancar
Copy link
Copy Markdown
Contributor Author

yiancar commented May 15, 2026

No issues I love the conversation!
Yes this makes a lot more sense to me right now. In the current implementation there is a delay associated with each key but indeed you cannot press two keys at the same time.

The proposed cleanup is quite sophisticated but also very bulletproof!
I will work on this:)

@yiancar
Copy link
Copy Markdown
Contributor Author

yiancar commented May 15, 2026

Hello! I added another 2 bytes per "step" as a pointer to the next item in the list.
I have tested the patch and its working as expected.

BTW I dont think we need garbage collection neceserily.
Currently if a macro is removed (or modified, which rewrites the macro) the UI will find all the empty elements in the list and populate them. This way we get full use of our space even.

One disadvantage is that now with 512 byte space we only get around 100 total macro "steps" were before we were at 170. But this does make our list much more scalable.

Let me know what you think.
A few questions as well:

  1. Currently if we have 2 keycodes with delay 0, the delay is not actually 0 but rather, next matrix scan cycle. I personally I am ok with this as it keeps the firmware cleaner but if you like I could make them register on the same cycle? If so maybe only on the Press or Release and not on TAP? (as tap clearly doesnt make sense).

  2. Would you like me to give a go on making loops? Maybe I could make a thing where from element X to element Y can be repeated Z times?

Cheers!

@peppapighs
Copy link
Copy Markdown
Owner

Thanks for making the changes. I'm sick right now, so not sure if you can review this soon though.

@yiancar
Copy link
Copy Markdown
Contributor Author

yiancar commented May 17, 2026

Hey Pep!
Sorry to hear. Take your time and get better. Let me know if I can help in any way:)

Copy link
Copy Markdown
Owner

@peppapighs peppapighs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments from my first glance at the code. Let me know your thought and thank you for writing this PR!

As for your questions:

  1. I agree with keeping the firmware clean. It probably doesn't matter in practice anyway whether we do it in the same cycle or not.
  2. I think let's leave that aside for now. I still don't have a good picture of how the UI/UX would be like if we support repeating/looping. I suppose the implementation should not be too difficult since we are essentially implementing a tiny state machine. Open for some ideas though.

Comment thread include/commands.h Outdated
Comment thread include/eeconfig.h Outdated
Comment thread include/commands.h Outdated
Comment thread src/advanced_keys.c
break;

case STRING_MACRO_ACTION_TAP:
layout_register(state->key, node->keycode);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I'm missing something but maybe we can use deferred action here for tapping, so that the tick rate config is in effect for this too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this and I dont think we can use deferred tap here.
Mainly because the macro needs to run in order. In the situation where we have "PRESS shift, TAP a, RELEASE shift"
The release shift might happen (due to the tick of the macro) before the deferred tap has finished.

If you really prefer using deferred tap I suggest we do either:
A) hard wire a wait after macro TAP based on the tick time of deferred macro before moving to the next item in the list.
B) Add a callback from the deferred macro function to know that it has finished before moving to the next item on the list.

In both this situations the disadvantage is that the taps of macros will not be as fast as possible (currently next matrix scan cycle) but this might be acceptable. Let me know your thoughts on this.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm generally ok with A) to keep it consistent with tap-hold and DKS. I acknowledge the disadvantage here, but it's probably doesn't matter in practice.

@yiancar
Copy link
Copy Markdown
Contributor Author

yiancar commented May 23, 2026

Done except the last point which I would like to discuss a bit more.
I also changed the UI to have macro instead of string macro:)

I have some UI ideas about making loops etc but maybe we can leave that for later indeed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants