I’d like to make a script that performs the following two actions:
close frontmost window of active app (as if hitting Cmd+W)
if there are 0 open windows for current app, activate previous app (as if hitting Cmd+Tab)
Importantly:
step 1 must NOT involve simulating a keystroke event
step 1 must finish before step 2 begins executing
So far, I’m stuck at step 1 and unable to close the window. I’m triggering the script with a hotkey using BetterTouchTool, and each time I run it, I get the error System Events got an error: Can't get application "Obsidian" (or any other app name).
tell application "System Events"
set frontApp to name of first application process where it is frontmost
try
tell application frontApp to close its front window
on error errorMessage
display dialog "🚨 " & errorMessage
end try
end tell
njy2umyx2f. The Cmd+Q keyboard shortcut quits the frontmost app. I assume you meant Cmd+W?
The following should do what you want, but it will fail in many circumstances. I’ve included a dialog just for testing purposes. I don’t know anything about BetterTouchTool utility–I tested this script by running it by way of the Script Menu.
--this script must be run by a method that is not seen as the frontmost app
--note that the name of an application process and the name of the app are not always the same
--this script will fail if the frontmost app is not scriptable
--get names of app processes
tell application "System Events"
set openApps to name of every process whose background only is false
set activeApp to name of first process whose frontmost is true
end tell
--for testing only
set text item delimiters to {linefeed}
display dialog "The open apps are:" & linefeed & openApps as text
set text item delimiters to {""}
--close window 1 of frontmost app
try
tell application activeApp
set windowCount to count windows
close window 1
end tell
on error errorMessage
display alert "The window of the frontmost app could not be closed. This typically occurs when the app is not scriptable. The error message is shown below." message errorMessage
error number -128
end try
--activate prior app
if windowCount is less than 2 then
set priorApp to item -2 of openApps
tell application priorApp to activate
end if
EDIT: I need to edit this script to 1) get the item number of activeApp in openApps, and 2) set priorApp to that item number minus 1. I’ll fix that later today if someone doesn’t have a better suggestion.
Your script doesn’t get the list of apps in Front-to-Back order.
I believe it gets them in order of when they were opened.
to get them in front-to-back order try this…
on getFrontToBackProcessName()
local theInfo, processList, tid, i
set tid to text item delimiters
set text item delimiters to quote
set theInfo to text items of (do shell script "lsappinfo visibleProcessList")
set processList to {}
set text item delimiters to {" ", "_"}
repeat with i from 2 to count theInfo by 2
set end of processList to (text items of item i of theInfo) as text -- this replaces the "_" with a space
end repeat
set text item delimiters to tid
return processList
end getFrontToBackProcessName
There’s a thing called GUI scripting and it might be able to do what you want. That’s a topic I know little about, and perhaps another forum member will be able to help you. BTW, you commented that “step 1 must NOT involve simulating a keystroke event” and that’s essentially what GUI scripting does (except sometimes it simulates a mouse click).
I wanted to test Robert’s suggestion and wrote the following script on a proof-of-concept basis. There are a few instances where apps (like Script Editor) do not accurately return the count of open windows, and that needs to be fixed (probably by counting the number of open documents). Error correction and refinement are also needed.
--this script must be run by a method that is not seen as the active app (e.g. Script Menu)
--get running apps and active app
set runningApps to getApps()
set runningAppsCount to (count runningApps)
if runningAppsCount is 0 then error number -128
set activeApp to item 1 of runningApps
--close window 1 of activeApp
try
tell application activeApp
set windowCount to count (windows whose visible is true)
close window 1
end tell
on error --the active app is not scriptable and will be quit
set windowCount to 1
end try
--quit active app and activate prior app where applicable
if windowCount is less than 2 then
tell application activeApp to quit
if runningAppsCount is greater than 1 then tell application (item 2 of runningApps) to activate
end if
on getApps() --from robertfern but edited to remove "Finder"
set tid to text item delimiters
set text item delimiters to quote
set theInfo to text items of (do shell script "lsappinfo visibleProcessList")
set processList to {}
set text item delimiters to {" ", "_"}
repeat with i from 2 to count theInfo by 2
set anApp to (text items of item i of theInfo) as text -- this replaces the "_" with a space
if anApp is not "Finder" then set end of processList to anApp
end repeat
set text item delimiters to tid
return processList
end getApps
Yes, I tested with Notes, Script Editor, TextEdit, Shortcuts, Discord, etc from the Shortcuts menu bar and the Dock but not Raycast. This one will display alerts that show what it’s going to do: https://www.icloud.com/shortcuts/6f1379e9b1414de8aa15a2a729fa4b02
Just tested it a little more thoroughly. It works in some apps but not others. For example, when I’m in Shortcuts it does close the window, but if I’m in e.g. Telegram, it doesn’t. It also ends up re-focusing back on Telegram again afterwards. My best guess is that different apps handle closing in different ways.
Even when it does work (e.g. w/ Shortcuts open), the app it then focuses on is, in my case, not the previous (second-most recently used) window. For me, it focuses on HazeOver, which, albeit a running application, does not appear in the Cmd+Tab menu. In other words, hitting Cmd+Tab could never land me on HazeOver. I tried adding an “is visible” filter into the script, but nothing doing.
I just generally get the hunch that something more low-level than a Shortcuts script is required here. I wish I was better-versed in MacOS to figure this out.
I tried the script with “Subler”. When I run Subler and creat only 1 window, when I try to get count of windows it returns 3. For some reason “Subler” has two invisible windows. If I use “set windowCount to count (windows whose visible is true)”, I get the correct number
I just tested VLC media player. It has 4 invisible windows
also I just realized something, If you close front most application, the second frontmost becomes frontmost automatically. Why are you trying to script it to bring to front when it already is?
This one quits the frontmost app if its visible window count is one, instead of trying to close the frontmost window using AppleScript. Hopefully it works on apps like Telegram.
If it still switches to an unwanted app like HazeOver (even though I’ve added the Is Visible filter), you can exclude it by adding the App Name is not filter in the last Find Windows action.
Thanks Robert–that fixes a similar issue with Script Editor as well. I’ve edited my second script above to incorporate this change.
I tested and the prior app often has the focus after the frontmost app is quit, but that’s not always the case. So, the code line that activates the prior app is necessary in my script.
This quits the app entirely, which is not the behavior I need. There’s already Cmd+Q for that.
In essence, I’m trying to execute the equivalent of Cmd+W followed by Cmd+Tab, but only in cases where the initial Cmd+W leads to the (IMHO frustrating) situation where the app (whose last window has been closed) stays in focus with zero windows available. In 99% cases, the user will just have to either hit Cmd+Tab or click the blurred window of the next app they want to focus on.
Basically I dislike this default behavior enough to be trying to somehow tweak it & ask you guys for help (thanks btw).
Also I should probably explain why I specified that Step 1 should not involve a keystroke simulation. It’s because I’d like to eventually override Cmd+W with the desired behavior using BetterTouchTool. If there’s a smarter/hackier way to avoid recursion while overriding Cmd+W, I’d love to know about it.
P.S. Please don’t recommend that I instead make a habit of hitting Cmd+Q or Cmd+H. These are bad alternatives, and I can write a separate comment explaining why, which hopefully won’t be necessary.
njy2umyx2f. I think I understand now what you are attempting to accomplish, and the following script should work as you want. However, the script will only work with apps that are scriptable and will not work with every scriptable app. If the script doesn’t work for the foregoing reason, it does nothing.
I tested this script without issue on my Sequoia computer with Safari, Mail, Script Debugger, Shortcuts, TextEdit, Script Debugger, and Numbers. It did not, for example, work with the Stickies app because it is not scriptable, and it did not work with the Notes app because it does not support multiple windows. There are some apps that quit the app when the last window is closed (e.g. TextEdit), and there’s nothing a script can do about that.
When all is said and done, I question whether this or any script will be of much use to you. Different apps work in different ways, and an AppleScript can’t change that.
--this script must be run by a method that is not seen as the active app (e.g. Script Menu)
--this script will work with scriptable apps only and will not work with every scriptable app
--get running and active apps
set runningApps to getApps()
set runningAppsCount to (count runningApps)
if runningAppsCount is 0 then error number -128
set activeApp to item 1 of runningApps
--close frontmost window and activate prior app if active app has no open windows and prior app exists
set windowCount to 100 --100 is an arbitrary number
try
tell application activeApp
set windowCount to count (windows whose visible is true)
if windowCount is greater than 0 then close window 1
end tell
if windowCount is 1 and runningAppsCount is greater than 1 then tell application (item 2 of runningApps) to activate
end try
on getApps() --from robertfern but edited to remove "Finder"
set tid to text item delimiters
set text item delimiters to quote
set theInfo to text items of (do shell script "lsappinfo visibleProcessList")
set processList to {}
set text item delimiters to {" ", "_"}
repeat with i from 2 to count theInfo by 2
set anApp to (text items of item i of theInfo) as text -- this replaces the "_" with a space
if anApp is not "Finder" then set end of processList to anApp
end repeat
set text item delimiters to tid
return processList
end getApps