Timing Question: "Focused Window Trigger" vs. "Space Changed Trigger"

I'm working on macros that may be dependent on the timing of operation between the Space Changed Trigger and the Focused Window Trigger.

Clearly, whenever the Space Changed Trigger is activated, the Focused Window Trigger will be activated too. Well, maybe not always. When you are dragging a window from one Desktop Workspace to another, the Active Space will change, but the Focused Window will not, so that's a special case.

Most of the time, when the Active Space changes, triggering any macro that uses the Space Changed Trigger, the Focused Window Trigger will be activated too. The issue is, in which order?

My concern is about the timing. Can I count on them happening in that order: first the macro triggered by the Space Changed Trigger, then the macros triggered by the Focused Window Trigger? It seems likely that that would be the case, since the Space Change can happen before knowing what's in the new space to be the Focused Window. On the other hand, a change of Focused Window can be what causes the Space Change, so that's known before the new space is active. But it happens in a different OS context.

I know that I cannot presume that multiple macros triggered by the same trigger will happen in any particular order, but in this case these are separate OS functions. I imagine it could go either way, depending on how things work both inside KBM and inside the OS and depending on the actual triggering scenario.

If I can count on the execution order, then inside the Space Changed macro I can add the current SpaceName to the RecentSpaces pushdown stack and when the Focused Window macro executes, it can presume that the current SpaceName is already there and simply add a new Focused Window to the top RecentSpaces entry.

But if the Focused Window macro cannot presume that the Space Changed macro has completed, then it cannot presume that the top entry in RecentSpaces is accurate. So the Focused Window macro will have to test and wait and that will slow it down a bunch.

My hope is that Space Change always happens first, because that will simplify the function of the Focused Window macro. But I don't know how to tell.

Does anyone (@peternlewis ?) know how this works under the hood well enough to say, or does anyone have any ideas for a simple test that I might run (like doing 100 or 1000 Space Changes and seeing what happens to the Focused Window Trigger each time)? Designing and programming this kind of testing is a long way from my strong suit and I know there are folks here who are adept at this. So brainstormed ideas are welcome.

The answer to this is virtually always “no”.

It is best to design your macros to work properly irrespective of the order of the triggers.

Thanks for the confirmation. I was afraid of that.

Any suggestions for how to get the macro that "should" always run second to wait until the other macro completes? What kind of flag can I create that has minimum overhead on the overall operation?

I guess I'm asking about "best practices" here. Any suggestions? What are some things people have tried that work poorly, so I don't have to find them myself?

Not a good example:
"If you get there first, make a chalk mark on the sidewalk. If I get there first, I'll rub it out." -- The Marx Brothers

I don't fully understand what you're trying to achieve with spaces and windows (because I never use spaces), but couldn't you do this with global "flag" variables and pause until actions?

As the first step in each macro, use a Pause Until action with a timeout set to reasonable value—five seconds or whatever you think you need. Have it pause until the value of the corresponding flag (i.e. check the space changed flag in the window changed macro) is true. If it times out, end the macro.

If it doesn't time out, reset the flag variable and continue running.

You would set the global flag variable as the last step in each macro, so that it's not set until that macro is finished executing. If necessary, add whatever conditions you can use to check spaces/windows have done whatever you think they should have done before setting the variable.

This may be completely wrong, but if I were writing a set of interdependent macros with unknown timing, that's how I'd probably try to solve the problem.

-rob.

2 Likes

Hi, @August. Here's the approach I'd use.

Download: Run First.kmmacros (2.9 KB)

Macro-Image


Macro-Notes
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
    • The user must ensure the macro is enabled.
    • The user must also ensure the macro's parent macro-group is enabled.

System Information
  • macOS 15.5 (24F74)
  • Keyboard Maestro v11.0.4

Download: Run Second.kmmacros (5.6 KB)

Macro-Image


Macro-Notes
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
    • The user must ensure the macro is enabled.
    • The user must also ensure the macro's parent macro-group is enabled.

System Information
  • macOS 15.5 (24F74)
  • Keyboard Maestro v11.0.4

3 Likes

Thanks @griffman Rob, thanks @_jims Jim,

I'm unfamiliar with Semaphores and Dictionaries, so it's going to take me a while to trace out how those sample macros work. First, what's going on that both macros are trying to Lock the same Semaphore? Neither is reading it? OK, I looked at examples in the Wiki and I think I get it. I haven't found yet if there's a way to determine if a semaphore lock is currently active. It just queues things, right?

As for the context of what I'm trying to do, I tried to describe it in the OP. But it was pretty wordy.

What I want starts with a list of Spaces visited, a pushdown stack so the top list element is always the Current Space and the second element is the Previous Space. I have that working. After a Space Change trigger, a macro updates a pushdown list with the name of the current Space.

Right now I mostly use the top two list elements in macros. I also generate a Prompt With List of the Space names in chronological order, which helps when go down a rabbit hole and ask "What was I working on?"

So far, so good.

When I make a manual change by hotkey, it works by opening the little ID window that I've created in each Space. Thus that lD window becomes frontmost. I've been trying to figure out (for a while) how to restore the previously frontmost app window. When my ID windows is frontmost, the one I want will be second, but there seems to be no easy way of finding out what that is just from the existing view. I can find out what the previously active app was, but that may not be in the current Space, and for any app I can find out what the order of its windows is. But the general case, which is frequent, appears to be difficult. Of course the OS knows, but it doesn't make it public.

And it's a bit of a pain point for me. Over and over, I switch into a different Space and I think the window I'm looking at is frontmost so I'll paste the text or image that I went elsewhere to grab, and it ends up in the ID window instead of the window that I intend. And sometimes I don't notice because the ID window is tiny. And then when I do notice, cleaning it up is an interruption.

So I'm working on a different approach. Instead of trying to figure out what the order of windows of different apps is on the screen, after I've moved to a new Space, I'm looking at keeping a separate pushdown stack list for each Space, by adding an entry (and removing any older, duplicate entries) to that stack, essentially at every Focused Window change. Because I'll be saving an entry for every Focused Window change, I need to keep the process very brief.

It appears to only take a few milliseconds to update that stack, and what it buys me is that when I leave a Space, the frontmost app of that space is already in its app list. So when I return to that Space, I can just look in its app list and know which app window I should now make frontmost.

There's a wide variety of ways that the Space can be changed, my hotkeys, Mission Control, apps returning to a previously active window from their Recent Files list, and more. In many of those ways, the frontmost app is already the appropriate one, either because it's the app that initiated the Space and Window change or because Mission Control automatically makes the appropriate app window frontmost.

With my own hotkeys, I'm in control of picking the new window after the Space change. But when the Space is changing by any means other than my hotkey macros, there's this timing issue. An app or Mission Control or whatever is bringing some app widow in the new Space to be frontmost and it's also changing the Space at the same time. An app is presumably picking the window first and the Space automatically follows, while Mission Control is picking the Space first the former frontmost app window automatically follows.

Normally, without a Space change, a Focused Window Trigger macro can save the new window name in the app list for the current space, ready to be identified as the frontmost again if I leave this Space while it's still frontmost, and then sometime later return to this Space.

Because I will be updating the current Space's app list at every Focused Window change, I really cannot afford to wait — every time — more than a fraction of a second to see if there might be a Space change happening at the same time. I don't think.

But I do need to know if a Space change is happening to know which Space's app list to update. Simultaneous Space and Focused Window changes means the new window name belongs in the new Space's list. If there is no Space change going on at the same time, then the new app window name goes in the current Space's list.

So maybe I need to figure out how to handle it in each of the various cases. Maybe semaphores are the answer, I don't know yet. In my current thinking, I think I would need each of the two macros to be able to determine whether or not there was a semaphore already locked and after waiting for the other macro to complete, then have different behavior according to whether it is running first or second. Is that possible?

Sorry, that also turned out to be wordy, but it's a different set of words than before. Maybe I'm getting the concept across. And maybe I'm homing in on how to use semaphores.

Thanks for your efforts in trying to figure out what I'm trying to describe.

Easy-mode answer -- instead of using a trigger, have one of the final actions of the first macro execute the second macro asynchronously. To prevent any execution overlap include a semaphore lock immediately before the "Execute a macro" action in macro 1 and as the first action of macro 2.

If you want to use triggers on both it's a more complicated problem -- you need to:

  1. Stop macro 2 from executing until the already-executing macro 1 completes -- easy enough to do with a semaphore lock
  2. Stop macro 2 from starting (more accurately, checking the semaphore) until macro 1 has activated the semaphore lock

If you don't mind using a global, something like:

One problem with which is that you can't run macro 2 independently unless, when you want to, you set the global some other way.

But you could pause macro 2 on a check of %ExecutingInstances% instead. Because you can't guarantee that concurrent macros will interleave even the fastest of actions (i.e. an order of execution of macro1 action 1, macro 2 action 1, macro 1 action 2, etc) you have to build some wiggle-room into your handling of the second case, otherwise there's the chance that macro 2 "sees" macro 1 executing and continues to the semaphore before macro 1 reaches it. Something like:

Macro 1:

Totally untested Macro 2:

Macro 2.kmmacros (7.5 KB)

Image

....which has the advantage that, because it continues execution either when macro 1 is running or after a short delay, it can be executed independently.

Note that it is the hard-coded "Pause" near the bottom of macro 2 that gives the wiggle-room mentioned above -- play with that and the milliseconds added in action 1 to make things as robust as possible but with minimum delay.

1 Like

A dictionary is not required; just my personal preference. (If you are interested, here's some related information: MACRO: Store Macro Settings in a Dictionary for Reuse)

As @griffman suggested you could use a global variable.

Here's a second version of the macro using a global variable rather than a dictionary.

Download: Run First v2.kmmacros (2.7 KB)

Macro-Image


Macro-Notes
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
    • The user must ensure the macro is enabled.
    • The user must also ensure the macro's parent macro-group is enabled.

System Information
  • macOS 15.5 (24F74)
  • Keyboard Maestro v11.0.4

Download: Run Second v2.kmmacros (5.2 KB)

Macro-Image


Macro-Notes
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
    • The user must ensure the macro is enabled.
    • The user must also ensure the macro's parent macro-group is enabled.

System Information
  • macOS 15.5 (24F74)
  • Keyboard Maestro v11.0.4

1 Like

Thanks for the examples and for the ideas. You folks are definitely helping me refine my understanding of the nature of the problem.

I'm realizing that I'm making a lot of presumptions here because I'm trying to solve imagined problems that may not actually be issues. So I think I need to do some experimentation.

I'm planning to (unfortunately, it may not be this week :frowning: ) create some simple test macros to log the system time when Space Changes and Focused Window Changes happen and probably create a simple way to add a comment line into the log as well, and then try a lot of different ways of initiating Space Changes: my hotkey macros, System > Keyboard > Shortcuts > Mission Control hotkeys, apps changing to windows already open in other Spaces, swipe up to reveal Mission Control Space thumbnails, etc., whatever I can think of.

After I've tried each method a dozen or so times, I'll examine the log file. It may be that Space Change is always triggered before an associated Focused Window change, because that's what the system always does to get there. Or it may be that way mostly but consistently the reverse for one or two particular types of intiating Space Changes.

I believe that some data will help me understand what I'm dealing with and might save me inventing a wheel when what I need is a boat. Or whatever.

Thanks again. I hope to be back to this in a few days.

I did. Results are manifestly not as expected.

No matter which method I used for initiating a Space change, the log entry for the Window change appeared before the Space change, usually with a surprisingly long delay, IMHO, at least longer than I had been expecting. I put the data into a spreadsheet, converted the date text to date values, and did some subtractions on every space change.

The shortest time between Window change and Space change was 0.666 seconds, in the specific situation where I held onto a Terminal window by the top bar and switched windows using the ^1, ^2, ad ^3 keys, which are defined in Settings > Keyboard > Shortcuts > Mission Control. If I did not drag the Terminal window along with the change and just used the hotkeys, the delay was longer, I suspect because the system had to first find the frontmost window on the new Space. Generally, in a variety of circumstances, the Window change log entry was between 0.8 and 1.4 seconds before the Space change log entry.

(Actually, I did record shorter times, when I was changing Spaces as rapidly as I could. The log showed that it could get as much as four changes behind, showing the initiation of four different windows that I know are in different Spaces, and then following that with four Space Change log entries, all showing that the same Space was now "current".

My current supposition is that the Window Change Trigger happens when the Window change has been initiated, before it's been fully displayed, before the system even knows whether or not a Space change is needed. And in contrast, I'm guessing that the Space Change Trigger, based as it is on NSWorkspaceActiveSpaceDidChangeNotification, happens when the Space change is fully completed, all "I"s dotted and "T"s crossed.

There may be some other factors. The time it takes on a Space Change Trigger for my macro to determine what the new Space number and name is might be significant and I'm getting the time as a token when I write to the log. To get around that, I could capture the timestamp in a variable as the very first action, then figure out where I am, and then write the log entry with the timestamp string and the location values.

But as for my initial issue, it looks like I can pretty much count on the Window change happening first, so if I'm keeping track of the frontmost window in each Space so that I will be able to restore it to the front when I return to that Space, then I'm going to have to pop that off of the stack for the previous Space and push it onto the stack for the new Space.

FYI. If anyone cares, let me know and I'll keep reporting progress. Otherwise I may wait until the whole system is more finished (at my current rate, sometime next year).

While leaving the testing macros running, I've encountered a couple of annoying permission problems. It seems that both Safari and Brave needed to have JavaScript control enabled in their respective Developer menus so that KBM could query them for the window title.

Annoyingly, the error message notifications did not mention KBM at all, only AppleScript and JavaScript, but a little googling on the error messages popped up answers that included phrases like "other apps such as Hammerspoon and Keyboard Maestro".