I am trying to make a macro that moves my mouse cursor to the front window, if the front window changes or if the frame changes. I have it mostly working the way that I want, except for one detail.
Some applications seem to create new windows in situations that do not visually appear to be new windows. For example, Keyboard Maestro's own GUI does this. When I open the Actions List, the overlay that appears is apparently a new window because the The focused window changes event fires and the new window is named New Action:
This is not the only example I have seen, but it is one that I think everyone can replicate!
In any case, I don't think I want my cursor moved in these cases, so I am looking for ways to differentiate between this type of new window and more "standard" types of new windows.
I know that it should be possible since this Hammerspoon Spoon does not trigger when a sub-window appears. It does nothing when the Keyboard Maestro actions list appears, and that I what I would like to replicate.
Unfortunately, the section where that macro handles edge cases appears to be doing it manually on a case-by-case basis. Ideally, I'd like something a bit more fundamental that provides a general solution.
This seems very tricky, because (I assume) you don't want to ignore a new window opening, as opposed to a pop-over window opening, right? The problem is that they're both windows, so programmatically, I can't think of an easy way you can tell them apart.
Maybe you could check the size of the new window, and if it doesn't match an existing window (as new windows typically open at the size of the current window), ignore it. But I'm sure there are apps where this would fail.
Or ignore windows whose bounds put them entirely within another window of the same app. But that will require a fair bit of window layout math, and again, probably fails for some "real" new windows you'd want to track.
The Macro you’re talking about was posted long time ago … the only thing I now from only remembering that this is a Macro Set of two Macros where one Macro does the Work moving the Mouse for you depending on information that was defined using its companion Macro.
I don’t remember it actually how everything works … but you’ll have to find out how this macro was written and then modify it to suit your cases.
It is a Macro which has an overall and general approach that is able to work with everything KM is able to detect correctly.
You’ll have to give it the data it needs to do the work for you.
Can you test something with that Spoon? With the KM Editor frontmost and the Actions pane closed, position the pointer outside the window and then ⌘K to open the Actions pane.
If I have the KM editor frontmost with the actions pane closed and the cursor outside of the KM editor, pressing ⌘K to open the actions pane does not move the cursor.
However, pressing ⌘K again to close the actions pane does move the cursor to the middle of the KM editor window.
So, to me that seems like further evidence that there must be some way of differentiating the actions pane window vs the KM editor window based.
But 100% reliably, as you've shown. My guess from your test is that the Spoon ignores windows with (or lacking) certain properties. That's why it doesn't fire when the Action pane appears but does when you close it. You could do similar with an AppleScript using "System Events", but that will be slower than using simple KM actions.
I think your best, generalised, bet is to log current window and app names to a global and compare last to current values, since (for KM anyway) the window name stays the same when you open or close the "sub-window".
I just tested it briefly, and it looks promising. However, it seems that some apps put tab names into the name of the window. For example, PyCharm has a problematic drop-down like window that triggers a focused window changes event, but their window name is <ProjectName> – <TabName>==mySep==PyCharm. Maybe there is a way for me to get the tab name and remove that from the string before comparing? Otherwise, I'll have some problems if I have changed tabs since I first focused on the current window and I then trigger a pop-over window of some sort.
Out of curiosity, why would using an AppleScript be slow? Is the script itself slow, or is it slow to have KM run the script?
I'm also browsing this page to see if I can get any clues: NSWindow | Apple Developer Documentation, since it seems like there should be some sort of window property that could make this very reliable.
It takes time to instantiate an AppleScript instance, and System Events is relatively slow as well. Anything you find in the NSWindow docs will have the same instantiation delay.
You can handle tab name inclusion -- but then you're back to special-casing apps, which is what you didn't like about the other macro.
My turn now Out of curiosity, why do you want to do this? Putting the pointer in the middle of the window every time there's a switch would drive me mad -- it would rarely be where I actually wanted it! What I do have is a macro that moves the pointer to front window that I can manually trigger whenever I need to.
Oh interesting. Are tabs handled differently in each app? I was imagining something fairly universal like compare the window name to the tab name. If the tab name is a substring in the window name, then remove it before comparing or saving to a global variable.
That's a good question! I'm trying to see if I can do something clever so that it doesn't cause headaches, but also runs automatically when it would be beneficial. I don't know yet if I will succeed.
Currently, the logic implemented moves the cursor automatically if:
The mouse cursor is not already over the new window
No mouse buttons are currently pressed
The new window has a non-empty name.
If it passes those tests, then it:
Moves the cursor to the middle of the focused window if the trigger is due to the window changing.
Moves the cursor to the same relative position within the window if the trigger is due to the front window's frame changing.
I might need a key modifier to temporarily disable it if I'm just switching applications to reference content but have no intention of interacting with the content there. But otherwise, the hope is that my cursor will always end up close to where I want it regardless of whether it re-positions it for me or not.
It sounds like you have a nice (and simple!) system already, but if you are interested I could share a draft of what I have put together so far.
I'm having some problems with false positives when something like the KM action panel triggers a front window changes event, but in the GUI's design it feels like it shouldn't be counted as a separate window. Because the panel is smaller than the "main" window it is attached to, my cursor jumps to the panel if it isn't already positioned where the panel appears (which is unlikely).
PyCharm and TeamViewer have some similarly problematic panels to deal with, and I'm guessing I will probably run across more examples as I use the macro more.
Yeah, it just gets really messy that way. I think I'm still hung up on the fact that Hammerspoon is doing something to differentiate. After reading their docs more carefully, I did get a clue.
Apparently windows in MacOS can have different accessibility roles (for example, window, dialog, etc.). If Keyboard Maestro could expose that to me, or if I can figure out how to get an AppleScript to show that to me, then I might be able to filter triggers based on that!
Unfortunately, my attempts at AppleScript always return AXWindow. I haven't been able to determine if that is because they all have that accessibility role, or if I'm not selecting the window properly.
Here is my script:
set windowRole to ""
tell application "System Events"
set frontApp to first application process whose frontmost is true
set frontAppName to name of frontApp
tell process frontAppName
tell (1st window whose value of attribute "AXMain" is true)
set windowRole to value of attribute "AXRole"
display dialog windowRole
end tell
end tell
end tell
tell application "System Events"
tell process "Keyboard Maestro"
set frontWindow to front window
set winRole to value of attribute "AXRole" of frontWindow
set winSubrole to value of attribute "AXSubrole" of frontWindow
end tell
end tell
return {role:winRole, subrole:winSubrole}
For the normal KM window, it returns {role:"AXWindow", subrole:"AXStandardWindow"}, but when the Actions window is frontmost, it returns {role:"AXWindow", subrole:"AXUnknown"}. So maybe your macro could ignore AXUnknown windows, though there are probably other types you need to avoid. Experiment using Script Editor to find out.
It’s like I said earlier… you’ll have to modify the Macro to suit your needs. I searched my own Macro Documentation for notes about the Macro I referred to and found something that is quite obvious… everything that is in these Exceptions will be left behind by the Macro. The Macro will become a star by using the Companion to create the List of Windows where it’s allowed to move the mouse for you.
Also to note is that this Macro is a early state one which maybe was never developed further by Jim… we will never know about that since he passed away a few years ago.
So everything this Macro does not handle does not mean the macro will not be able to catch up on as long it isn’t developed any further than the current state.
May I ask you why you are referring to AppleScript and Objective-C API‘s ?!
If you would build up on the Base I gave you as a starter you maybe would need AppleScript only in some Edge Cases - but the most is doable with KM‘s native Actions, Functions, Tokens and Triggers.
That was the reason for my comment about window properties earlier. But these will be unreliable across applications (so you're back to special-casing) and won't work for tabs.
Are you sure it isn't just jumping to the centre of the "main" window, which just happens to be where the panel is?
Windows that the OS differentiates for trigger purposes, windows detected via AppleScript and System Events, and windows reported by applications via KM's Window tokens are different. I think you can use that by treating %WindowFrame%1% as a proxy -- if it remains the same then the window hasn't changed, even if you've switched/added/removed tabs or popped the KM Actions pane.
There's a chance of false negatives if you switch between two windows that have the same, pixel-exact, position and size.
This seems to work for me in KM, Finder, Terminal, and Safari.