Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(behaviors): Add macro-pause-for-layer behavior #1804

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

aaronkollasch
Copy link

@aaronkollasch aaronkollasch commented May 17, 2023

Description

This PR adds a macro_pause_for_layer behavior that pauses the current macro and resumes it on the next layer change.

Motivation

The callum layout has a "swap win" key that sends Cmd+Tab and holds Cmd between presses.

In ZMK, binding the "swap win" key to LG(TAB) only allows swapping between the top two windows, as Cmd is released between presses.

Solution

The macro_pause_for_layer behavior allows "swap win" to be bound to a macro that presses Cmd, taps Tab, then waits for the Nav layer to change before releasing Cmd. With this binding, Nav+"swap win" behaves similarly to Cmd+Tab on a conventional keyboard.

Unlike on_macro_binding_released(), macro_layer_state_changed_listener() does not receive the current binding to resume. Therefore, the macro_pause_for_layer behavior instead saves a queue of scheduled macro states to resume (1 for each time the macro is pressed). Then, when the layer changes, all of these states are resumed. The queue size is 16 * 8 = 128 currently 16 * 20 = 320 bytes allocated statically.

If the queue size limit is reached, any subsequent &macro_pause_for_layers are bypassed and their macros processed immediately in full. This isn't an issue for the Cmd+Tab example as the number of macro_release calls equals the number of macro_press calls, and Cmd retains some macro_press calls until the layer changes and all the queued macro_release calls are executed.

Potential improvements

  • I wonder if behavior_macro_scheduled_state can get away with storing char* pointers for each behavior_dev? This would require less memory, simplify the code, and also prevent issues with truncated labels. (now added)
  • macro_pause_for_release and macro_pause_for_layer cannot be used in the same macro, but that should be possible if a separate release_state is kept for each trigger.

@aaronkollasch aaronkollasch force-pushed the feat/macro-pause-for-layer branch from d78c1c7 to 83b9a01 Compare May 18, 2023 04:35
@caksoylar
Copy link
Contributor

Dropping this here for reference: Use case sounds like #997, another possible solution is #1366.

@caksoylar caksoylar mentioned this pull request May 19, 2023
@caksoylar caksoylar added enhancement New feature or request behaviors labels May 30, 2023
@aaronkollasch aaronkollasch force-pushed the feat/macro-pause-for-layer branch from 83b9a01 to 13fbcb1 Compare June 17, 2023 05:29
@aaronkollasch aaronkollasch requested a review from a team as a code owner June 17, 2023 05:29
@@ -109,6 +109,23 @@ bindings
;
```

### Processing Continuation on Layer Change

The macro can also be paused so that part of the `bindings` list is processed when the layer changes.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think being more specific here will be nice: is it watching for the layer that the macro is on to be turned off? Or any change in the currently active set of layers?

Copy link
Author

Choose a reason for hiding this comment

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

Right now it's the latter: any change in the active set of layers will release all scheduled states. The former could be nice, but it would add some complexity to allow for storing scheduled states from different layers and releasing only some of them at a time.

Copy link
Author

@aaronkollasch aaronkollasch Jun 17, 2023

Choose a reason for hiding this comment

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

Question: if I wanted to get the layer the macro was in, would I store the zmk_behavior_binding_event->layer int passed into on_macro_binding_pressed()?

And then to check if the layer is turned off in macro_layer_state_changed_listener(), see if any bit in the stored layer int has been switched off?

Edit: never mind, I figured it out.

Copy link
Author

Choose a reason for hiding this comment

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

I added a commit that enables the first behavior you suggested - it now watches for the layer the macro is on to be turned off. I also edited the documentation to be more specific.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that's intuitive but either way could have been fine as long as it is documented.

One thing I am curious is how it behaves when the macro is not invoked directly from a layer binding, like

  • a combo
  • nested in another behavior (hold-tap, mod-morph, another macro etc.)

I am not sure it needs to work with combos since it doesn't make much sense, but maybe a warning note will be warranted.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, I added that behavior because I thought it would be more intuitive as well, so that adding another layer wouldn't trigger all continuations. However, it does limit macro_pause_for_layer to be used only on non-default layers.

I just tested with a combo and it seems to count as layer 0 even if a different layer is active at the time, so it would not work with the current behavior.

Perhaps one way a combo would make sense is where app switch could be repeatedly activated with a combo in the base layer, and momentarily entering any other layer would then exit the app switcher. Not the way I'm used to using Alt+Tab, but perhaps useful for someone who is averse to holding multiple keys.

This use would require going back to the original behavior of continuing on any layer change, rather than waiting for the current layer's deactivation.

@aaronkollasch aaronkollasch force-pushed the feat/macro-pause-for-layer branch from 13f879f to 28ca97f Compare June 20, 2023 04:15
@aaronkollasch
Copy link
Author

I've rebased the PR on main, including support for parameterized macros (#1232).

In order to support parameterized macros, as well as the zmk_behavior_binding* parameter in queue_macro() function, behavior_macro_scheduled_state now stores the full struct zmk_behavior_binding instead of just the char* behavior_dev. This increases the size of the queue from 196 to 320 bytes.

@aaronkollasch aaronkollasch force-pushed the feat/macro-pause-for-layer branch from 28ca97f to db1de28 Compare June 21, 2023 05:17
@aaronkollasch
Copy link
Author

Adding another force-push for the deprecated label property refactor (#2028).

@aaronkollasch aaronkollasch force-pushed the feat/macro-pause-for-layer branch from db1de28 to 4ac90dd Compare February 22, 2024 06:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
behaviors enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants