Reputation: 22379
I am trying to write an AppleScript to be triggered by hotkey in Keyboard Maestro on MacOS
What I want to do, and please do not suggest ANYTHING ELSE, such as hide all, or hide functionality. I am looking for minimize all open windows functionality, including the animation.
I can already do hide all.
So what I want to do is iterate all open windows, and trigger the menu option Window -> Minimize or if possible trigger minimize in a different manner.
Here is what I have so far but it is not fully working, only partially:
tell application "System Events"
set currentProcesses to name of every application process whose visible = true
end tell
repeat with tmpProcess in currentProcesses
tell application "System Events" to tell process tmpProcess
set frontmost to true
---activate
tell menu bar item "Window" of menu bar 1
click menu item "Minimize" of menu 1
end tell
end tell
delay 1
end repeat
Note, some applications will have a Minimise without a z for Minimize. . Would be good if the solution triggered some global minimize action rather than go through the menu system.
Upvotes: 0
Views: 2391
Reputation: 6102
Here is my original answer to your proposed problem, that was stated ought to work in fairly recent(ish) versions of MacOS:
use application "System Events"
set _P to a reference to (every process whose class of windows contains window)
set _W to a reference to (windows of _P whose value of attribute "AXMinimized" is false)
set value of attribute "AXMinimized" of _W to true
What I could have added was that, if we plug the value of each variable into the next line where it's referenced, and (for simplicity) choose to ignore the filtering used against the processes and windows, it becomes apparent that those four lines will evaluate at compile time to this single line of code:
tell application "System Events" to set value of attribute "AXMinimized" of windows of processes to false
However, you (the OP) reported that it was slow and not always reliable, and likely didn't use an iterative method but a recursive one.
I'm awaiting details of the circumstances under which the script above was found not to be reliable.
You asked whether I could produce code of a similarly-iterative nature/structure to one of your proposed methods.
This is the script that blends the functionality of my original script with the code structure taken from yours:
use application "System Events"
get the name of every application process whose class of windows contains window
repeat with P in the result
get (every window of process (contents of P) whose value of attribute "AXMinimized" is false)
repeat with W in the result
set the value of attribute "AXMinimized" of (contents of W) to true
end repeat
end repeat
Just as your script does, the one here explicitly iterates through all of the application processes retrieved against a filter (the filter in yours is the property visible
for each process
, whilst the filter in mine is collection of window
objects of each process
). You are free to change if you feel the need to.
However, once inside the repeat
loop that iterates through the processes
, what your script neglected to do was to iterate through every window that belongs to the each process. This is why you needed the fallback statement that sets the visible
property to false
, which is simply hiding the application process (something you explicitly stated in your OP that you did not want to do).
Without the visible
property being set, the only other action performed is clicking the Minimize
menu item once for each process. This will trigger the front window (i.e. the one that currently has focus) of that application process to minimise, but all other windows belonging to it remain unaffected, meaning only one window for each application process will get minimised. The rest are all just hidden by the visibility toggle.
The remedy for this was to set up a second repeat
loop nested inside the first, which iterates through all the windows of a specific process retrieved against a filter (which, as before, I chose to be the AXMinimized
attribute).
Then it's just a simple matter of setting the AXMinimized
attribute for each window to true
.
As you pointed out, one advantage of your script is that it appears to minimise the windows faster than mine. From what I can tell, it appears, for some reason, that the actual minimise animation is swifter when triggered using the menu than when triggered through the setting of an attribute value. So I then went ahead and created a script that doesn't try and set the attributes of the windows, but instead uses your mechanism of clicking the Minimize
menu item:
use application "System Events"
set _P to a reference to (every application process whose visible is true)
repeat with P in (_P as list)
set frontmost of P to true
set _M to (a reference to menu 1 of menu bar item "Window" of menu bar 1 of P)
set _minimise to (a reference to menu item "Minimise" of _M)
set _minimize to (a reference to menu item "Minimize" of _M)
set _W to windows of P
set n to count _W
if n > 0 then perform action "AXRaise" of _W's front item
repeat n times
if (_minimize exists) and (_minimize is enabled) then
click _minimize
else if (_minimise exists) and (_minimise is enabled) then
click _minimise
end if
end repeat
end repeat
This script is essentially the reworked version of yours, using the same filtering criteria for its processes (whose visible is false
) and clicking the menu item Minimize
(or Minimise
) to minimise the windows. It contains both the outside repeat
loop for the processes
, and the inside repeat
loop for the windows
.
Honestly, it's hard to say which appears to minimise windows more swiftly than the other, so I'll let you be the judge of that.
However, recalling what you stated in your original brief, you specifically asked:
Being frank about it, there's still only one solution that satisfies all of these criteria, and that's the original answer that I posted. Other solutions you found either don't work or rely on hiding the application windows; the last contribution I provided above works, but utilises the menu items, which is not your ideal method; and the script before that, which first demonstrated the nesting of two repeat
loops in order to iterate through both processes and windows can be demonstrated through inductive reasoning to be an identical script to my original solution, which, despite not have explicit iterative statements you're used to seeing in a programming language, does still iterate through the process
and window
objects behind the scenes.
You can, of course, choose whichever method you wish, as it is your project and your ultimate satisfaction that is important in the creation of a tool that you'll be the one utilising from day-to-day. But, to give my honest and final opinion on the best method to achieve what you're setting out to do, I would strongly suggest you choose the compacted form of my first answer, which is this very elegant and simple line of code:
tell application "System Events" to set value of attribute "AXMinimized" of windows of processes to false
Upvotes: 3
Reputation: 22379
Another working example, best so far.
tell application "System Events"
set currentProcesses to name of every application process whose visible = true
end tell
repeat with tmpProcess in currentProcesses
tell application "System Events" to tell process tmpProcess
set frontmost to true
try
tell menu bar item "Window" of menu bar 1
try
click menu item "Minimize" of menu 1
on error
try
click menu item "Minimise" of menu 1
end try
end try
end tell
end try
delay 0.1
set visible to false
end tell
end repeat
Upvotes: -1
Reputation: 22379
Another slower solution found online:
tell application "System Events"
repeat with appProc in (every application process whose visible is true)
click (first button of every window of appProc whose role description is "minimize button")
end repeat
end tell
Upvotes: 1
Reputation: 22379
I believe I was able to at least get a working version from the one above.
I think I got some errors when the menu minimize did not exist, so I added try catch for that, and a fallback to hide.
This script should work:
tell application "System Events"
set currentProcesses to name of every application process whose visible = true
end tell
repeat with tmpProcess in currentProcesses
tell application "System Events" to tell process tmpProcess
set frontmost to true
try
tell menu bar item "Window" of menu bar 1
try
click menu item "Minimize" of menu 1
on error
try
click menu item "Minimise" of menu 1
on error
--- Optional, fallback
set visible to false
end try
end try
end tell
on error
--- Optional, fallback
set visible to false
end try
end tell
delay 0.005
end repeat
Upvotes: 0