Parametrizable smart macros: brainstorm for future specification

There is a kind of usecases that have been popping up again and again, that could be (at least partially) solved by assigning the same macro to the entire base layer. The trouble is that all these keys/macros, despite doing basically the same thing, have some default action (usually a scancode tap/hold) that needs to be customized for each key. This makes the use of macro engine very clumsy for these usecases.

One of these usecases is Autoshift. Another is having multiple macro runtime slots (that one can be solved using $thisKeyId).

There is the activateKeyPostponed, which allows queuing a key action from another layer for execution, but in practice, this is very clumsy, so this is a call for a general brainstorm for a simple-to-implement-yet-powerful-in-practice solution.

My takes:

  • Make runtime variables accept scancode abbreviation via (e.g.) $scancode.a. Make tapKey/holdKey/... accept generic integer as a scancode (this conflicts with numbers as key parameters :expressionless:). Furthermore, introduce a new generic variable, e.g., $keyScancode.LAYER.KEYID.

    • pros: allows arithmetics over scancodes, is simple to implement as this would internally use the already present integer variable type
    • cons: doesn’t support modifiers
  • The same, but with full shortcuts (i.e., scancode plus modifier mask).

    • pros: more powerful
    • cons: A trouble is that current full shortcut specification allows specifying full set of input/output/sticky modifiers, as well as action type (tap/hold/press/release), which means that this doesn’t fit the 4 bytes that are dedicated to variables.
  • Add a general way to parametrize key actions with generic arguments. (How exactly?

    • yet more general than the above, allowing e.g., using one macro to set multiple sets of parameters: e.g., binding the same macro to 6 different keys to set led brightness to 0%, 20%, 40%, 60%, 80%, 100%
    • cons: (a huge cons) this is complicated, requiring Agent support and serialization support.

So far I am inclined to the first one, as it is basically a simple, yet sufficient, subset of the second one, and I can make it reality without requiring huge efforts from Laszlo and Eric.

Please, lets confine ourselves to the boundaries of practicable and user-friendly things (@maexx), since if this blows up into a pile of overly-complex ideas that no-one except for its creators is going to understand, then this will probably not get implemented at all.

@mlac, please step in whenever you like to set boundaries of what you consider reasonable.

(Note: this would normally be a github ticket, but I hope that forum thread will get more attention.)

1 Like

I have a poor understanding of the use cases, and I don’t belong to the target audience of these features. I don’t have much to add, except that I don’t want to expose more complexity via Agent for niche features.

1 Like

When designing more complex macro scenarios, I’ve felt a few times that being able to pass a single parameter to a call command (basically, using a macro as a subroutine with one parameter) would be helpful. Once that is possible, then the next logical step for me would be that every execution of a macro directly from a keypress (as bound by Agent) would get the KEYID or KEYID_ABBREV as the one parameter.

I don’t understand how your suggestion would work with scancodes and not KEYIDs. When you bind a macro to a key, you are not assigning a scancode to it. So how would the macro invocation know which scancode?

Would we then need a function to convert KEYIDs to scancodes? But what table would this function use?

I am not sure, @kareltucek, if this is helpful, because I seem to be the niche person, but it’s my opinion.

Right now I cannot think of the particular use cases where I had stumbled across this. I am sure I will come across them again, and then I can add them here.

1 Like

When designing more complex macro scenarios, I’ve felt a few times that being able to pass a single parameter to a call command (basically, using a macro as a subroutine with one parameter) would be helpful.

Well, this would require general support for local variables. While it is generally a reasonable request, I don’t feel a strong need to have them.

(After all, you can pass arguments to call via global variables.)

Once that is possible, then the next logical step for me would be that every execution of a macro directly from a keypress (as bound by Agent) would get the KEYID or KEYID_ABBREV as the one parameter.

KEYID_ABBREV’s are just aliases for corresponding KEYID. I.e., from the point of view of the variable system, they don’t exist.

As for KEYIDs, the $thisKeyId virtual variable is available in every macro (of course except those that are not bound to activation keys.

I don’t understand how your suggestion would work with scancodes and not KEYIDs. When you bind a macro to a key, you are not assigning a scancode to it. So how would the macro invocation know which scancode?

It would be again accessed via a virtual variable from another layer. Something like $keyScancode.fn2.a, or maybe $keymapAction.fn2.a.scancode

Would we then need a function to convert KEYIDs to scancodes? But what table would this function use?

No.

I am not sure, @kareltucek, if this is helpful, because I seem to be the niche person, but it’s my opinion.

Thanks for it!

Right now I cannot think of the particular use cases where I had stumbled across this. I am sure I will come across them again, and then I can add them here.

If you do, please let me know!

3 Likes

It’s great to hear that you’re considering parameterizable smart macros! I myself would love to have them. Here’s one thing it would allow me to do (i.e. I cannot do it now), followed by one that is more of a nice-to-have.

  1. Can’t do now: I currently trigger my terminal bell macro from my computer via the hidraw interface, thereby displaying the static “BEL” LED text. I would love to be able to set that LED text via parameter. That would allow me to, among other things, display shell exit codes in LED text. Just last week I thought about making separate macros for all 256 exit codes…and promptly decided against it.
  2. Deduplication: I currently have several different macros that simply call holdLayer fn{2,3,4}. Would be nice to DRY that out and only have one macro.
2 Likes

Regarding 1, you should already be able to do that by executing setLedTxt directly over usb :wink:.

As @kareltucek already suggested, why don’t you run the setLedTxt command directly from your uhkcmd script. You can set any text there, and it’s all shell scriptable.

Ah jeez, you guys are right, thanks for setting me straight. Can’t believe I overlooked that most simple solution >.< I have the whole palette of macro commands at my and my shell’s disposal…

Regardless, consider me a +1 on the parametrizable macro thing :slight_smile: I’m not half as heavy a macro user as many people on this forum, but it’s the kind of feature that imo changes the way you engage with things.

Couple of ideas:


A slightly modified suggestion from @maexxx in My thoughts as a new user (UHK80) - #30 by maexxx

In this case, macros would allow referencing custom strings.

[original by Max, edited by me] You would bind something like “Macro: send_windows_unicode” with “Macro parameter 1: 0 0 a 1” to a key. For different keys, it would always map to the same macro, but different hex code parameter. In the macro, you’d need to be able to do tapKeySeq CS-u $macroArg.1 enter.

This way tapKeySeq CS-u $macroArg.1 enter would be interpretted as tapKeySeq CS-u 0 0 a 1 enter.

  • Pros: simple to implement, simple to use, cheap to evaluate (probably), extremely powerful
  • Cons: imagine I use this approach to bind greek alphabet via alt codes. Now I have a keymap full of hex values with no way to identify them and no way to add any explanatory note to them.

My notes:

  • I am thinking about extending the argument with some kind of “type” information. This would allow attaching a label to any key (another popular feature), It would also allow us to have types like “Unicode char as hex key sequence”, for which Agent could take a unicode character (e.g., emoji), but would serialize it as the relevant key sequence (e.g., 0 0 a 1), thus allowing Agent to show it as an actual unicode char.

  • Does anyone have a strong opinion on correctness of the words “param” vs “arg” in these contexts? (I take them as interchangeable, but am not sure how correct that is)

I like this idea in this modified form, but am wondering if we can come up with some ways to improve it and get rid of the drawbacks of this solution.


Laszlo’s suggestion:

i envision a fixed number of typed macro arguments per macro, which would be described as one line per argument in the beginning of the macros. agent would parse this format to construct the gui for arguments, making their usage very user friendly

@arg keyId first
@arg string second
randomCommand @first
// rest of the macro
  • Pros: user friendliness
  • Cons:
    • it is not clear what the “type” should be referencing. I would like the solution to be more general than what a macro variable can contain, so this should not be the macro variable type.
    • named nature of the arguments adds an indirection layer, which translates to complexity of implementation as well as costly evaluation.
    • assume the user has already bound and parametrized many keys with such a macro. Now, what does happen / should happen when the user changes order, or types, or names of the variable declarations?

At the moment, I am not finding this feasible. Too complex on both Agent and firmware side, contains inconsistencies and unsolved scenarios. But I like some of its aspects, and so have posted it for inspiration.


Any opinions or new ideas?

1 Like

I think it would be cool if the macro could at least declare how many parameters it can accept. The Agent GUI would then be able to show the correct number of Args fields.

At a later stage we could then think about argument types.

Example 1:

// This is the same as `set unicode_hexstring $macroArg.1` but 
// lets Agent unterstand that the macro needs 1 argument:
@arg unicode_hexstring
tapKeySeq CS-u $unicode_hexstring enter

When you select this macro as the key action in Agent, it would present you with the Argument field “Argument 1: unicode_hexstring” and you would fill it with “0 0 a 6”.

Example 2: adding argument types:

// This is the same as `set unicode_hexstring $macroArg.1` but 
// lets Agent unterstand that the macro needs 1 argument. Also, 
// because the argument is a tapsequence, spaces will be inserted 
// between each character in the string.
@arg unicode_hexstring tapsequence
tapKeySeq CS-u $unicode_hexstring enter

When you select this macro as the key action in Agent, it would present you with the Argument field “Argument 1: unicode_hexstring” and you would fill it with “00a6”. As a “tapsequence” argument, spaces will be inserted when the argument is parsed into the macro (at execution time).

I don’t know if “tapsequence” space insertion makes sense; I am using it as an example. You could also just use the write command instead, I guess:

// This is the same as `set unicode_hexstring $macroArg.1` but 
// lets Agent unterstand that the macro needs 1 argument:
@arg unicode_hexstring
tapKey CS-u 
write $unicode_hexstring 
tapKey enter

Now you also just need to fill the argument field in Agent with the string “00a6”, and there is no special “tapsequence” logic needed.

Maybe instead of argument type, the remaining text in the @arg declaration could be a help text explaining the argument usage, e.g.

@arg unicode_hexstring Enter the unicode hex digits for the character, e.g. 00a6

Agent would display this help text for each argument field.

Some more thoughts on the second proposal (by Laszlo).

Maybe. Or compile to byte code and the name will be reduced to a byte code referencing the n-th argument anyway.

Change order: bindings will be screwed up. User has to go in and manually edit the bindings.
Change type: bindings will still “work”, but newly entered data might not validate. User may need to go and manually adjust some bindings.
Change name: same as changing a variable name. It’s all contained within the macro.