When the focused window changes, is it possible to tell the difference between selecting a new window and closing the front window?

I guess you could have a Global variable, and your JXA app could check that. But it would have to check -- there no "this variable has changed", only "has this variable changed?" -- and how happy will the Engine (and your system in general) be with the programming equivalent of a toddler in the back of the car going...

"Are we there yet? Are we there yet? Are we there yet? Are we there yet? Are we there yet? Are we there yet? Are we there yet? Are we there yet? Are we there yet? Are we there yet?"

IMO it's less about the speed of each of those requests, more about the overall load of so many requests, most of which are unnecessary. You could reduce the request rate, but the more you do the more you risk missing an event.

But test KM's JXA instantiation/destruction yourself -- on my M1 Air it uses <10% of a single core for ~50ms, so it'll hardly break the (power) bank.

Thanks Nige and Rob,

That's very interesting to know. However, it's not so useful in my case as the situation could very likely be that I close a Finder window and I want to know the next window under the Finder, but there are still other Finder windows in the same Desktop. Or Preview, Chrome, Brave, Notes, Bear, Typora, TextEdit, ... — it seems there are several apps where I often can have more than one window for the app open in a single Desktop, in which cases the WindowCount will not be zero.

With similar testing I'm showing a consistent 0.341 sec to 0.666 sec difference between the Focused Window Change and the Space Change. Which has me concerned that if I trigger listing the desktop windows by the Focused Window Change, I may get the list for the previous desktop. I can imagine that the OS reports the Focused Window Change as soon as it gets the instruction to open the new window, well before it actually finds the window and begins opening it, and that opening is what triggers the Space Change, and there are a lot of steps to the Space Change, and the Space Change Trigger is based on NSWorkspace.activeSpaceDidChangeNotification — past tense, the change has completely completed.

I have seen, most recently in some HammerSpoon doc about changing spaces, implications that this notification does not happen until all the Space-Changing activity, including Accessibility settings of some kind (according to HammerSpoon) have completed. I'm pretty sure (but still guessing) that's the bulk of those 600ms that I'm logging.

So maybe to get the window list for right desktop, I need to wait 700ms after the Focused Window Change. If that can be done in the "background", then I probably wouldn't notice if that listing was being updated at every Focused Window Change. It could possibly get out of sync if I changed windows less than 1.0 sec apart followed by a Space Change within less than another 1.0 sec. Theoretically possible but not likely in practice and if it doesn't affect my workflow look and feel, it's a potential strategy.

I don't know, I'm still banging away on this. Your comments and observations are most welcome.

The original question is in the thread title, and that's what this is addressing. You'll need to keep a running record of the frontmost app and the window count for all open apps -- if, after a window change, the frontmost app is still the same and the app's window count has decreased then you have closed a window.

@griffman raised the case of apps that auto-quit when you close their last window, but it turned out that was also covered because Actions (early enough in the macro) getting the front app and its window count hit the quitting app before it goes, returning its name and 0. That matches our test and we know we closed the window.

This works on a non-Spaces setup.

In the "application space" the next window under the Finder is the next Finder window, i.e. the one that had an index of 2 before you closed the window, or the Desktop if you closed the last (because Finder is a special case).

If you have interleaved windows in the UI, e.g. a Finder window, a TextEdit window behind that, another Finder window behind that, and want to get the next window in "UI order" then you are going to have to find a way to query the "display space".

Because of your use of Spaces, this could be a nightmare without some low-ish level coding -- particularly if you want to handle the "interleaved apps" case (which, I'd point out, your OS doesn't bother with...). It also looks like there will be considerable execution time, if only because of the number of KM Actions and system API calls needed (unless, again, you put all that into code).

While it's a fun exercise, first check to see if this isn't already a solved problem. Give Moom a try -- the AI search summary suggests it works within the active Space, so if you can get it to save your current window arrangement before you switch Spaces you should be able to reset the windows when you go back. No need to be constantly updating lists on every window change.

I know others here are more qualified to speak on Moom than me, and I'll be quickly corrected if wrong :wink:

I have just discovered that window closing can be a specific trigger in HammerSpoon.

hs.window.filter.windowDestroyed is a constant event in Hammerspoon's Hammerspoon Docs hs.window.filter module. It triggers when a tracked window is destroyed or closed, allowing scripts to run custom callback functions via :subscribe().

Have fun with that! All sorts of goodness awaits...

For those who haven't looked at Hammerspoon (and I'll confess it's been a while for me), at the simplest level

It bridges various system level APIs into a Lua scripting engine, allowing you to have powerful effects on your system by writing Lua scripts.

...and there's also a load of pre-made Spoons (plugins) you can use.

As an example, here's the "More complex window movement" script from HS's "Getting Started" page, followed by a KM equivalent. Both move the frontmost window by 10 pixels in the directions determined by the hot key used:

y   k   u
h       l
b   j   n
HS Script
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "Y", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.x = f.x - 10
  f.y = f.y - 10
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "K", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.y = f.y - 10
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "U", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.x = f.x + 10
  f.y = f.y - 10
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "H", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.x = f.x - 10
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "L", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.x = f.x + 10
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "B", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.x = f.x - 10
  f.y = f.y + 10
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "J", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.y = f.y + 10
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "N", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()

  f.x = f.x + 10
  f.y = f.y + 10
  win:setFrame(f)
end)
KM Macro

Move Window (HS comparison).kmmacros (7.9 KB)

Putting Hammerspoon back on my list of things to (eventually) play with -- thanks for the reminder, @August!

Yes, within a Space, Moom can easily save and restore an arrangement of windows. And it has basic AppleScript support, so your Keyboard Maestro macro would do something like:

  1. Tell Moom to create (or update) a layout in the current Space via AS in the macro
  2. Do whatever etc in that and other Spaces
  3. After returning to that Space, use AS in the macro to restore the saved layout

You could do this on each Space, saving and rewriting the layouts as necessary, with distinctive names.

I will be 100% honest here, though, and say that because August's use case is probably the most complicated I've ever seen, I have no idea if it will work exactly as he needs it to work. If it does, might save a ton of time, though.

-rob.

This is a great example, comparing how to do the same UI task in both systems. It deserves to be called out in a separate post — I'd put it in Tips and Tutorials — instead of burying it as Comment #25 in an obscure thread.

To flesh it out one extra step, I would add how to invoke HammerSpoon from within KBM, I presume from a Shell script. (Please don't take my "I-statements" as volunteering, I can barely invoke HammerSpoon from its console at this point.)

I'll leave that to someone who knows what they're doing!

And anyway, a more honest comparison would be a KM Group with 8 single-trigger, single-Action macros -- one for each move. E.g.

But I'm afraid I have a (rather silly) preference for fewer, more complicated, macros :wink: