mjs
mjs

Reputation: 22379

AppleScript - Iterate all open applications / process and execute a menu command

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

Answers (4)

CJK
CJK

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:

  • For a "global minimize action rather than [to] go through the menu system";
  • Not to offer other suggestions "such as 'hide all', or 'hide' functionality. I am looking for [a] 'minimize-all-open-windows' functionality";
  • To "[include] the animation."

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

mjs
mjs

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

mjs
mjs

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

mjs
mjs

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

Related Questions