How Do I Manipulate Windows in AppleScript?

HI, I've made an applescript that runs fine in script editor or Script debugger, but when I paste the same script in KM, it asks "Where is osascript?"

Here's the script:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

set myApplication to name of current application
set screenWidth to 2560 --1920
set screenHeight to 1440
tell application myApplication
	
	if (count of windows) ≤ 2 then
		set myWidth to screenWidth / 2
		set bounds of window 1 to {0, 0, myWidth, screenHeight}
		try
			set bounds of window 2 to {myWidth, 0, myWidth * 2, screenHeight}
		end try
	end if
end tell

My goal here is I want to resize the windows of the front app but with conditionals.

If I have 1 or 2, set them to half the width of the screen.
if I have 3 or 4, set them at half the width and half the height, each in a corner
if I have 5 or 6, set them at 1/3 the width and half the height
if I have 7 or 8, set them at 1/4 the width and half the height

ideally, it would work for the windows on a specific monitor in a dual monitor setup. I have an idea of how to do all the conditionals in Applescript. Will likely get stomped on the getting the info of which monitor the windows is in at some point, but since I can't get past the osascript message. I can't go any further.

Since KM can do a LOT, it might be able to do all this on its own, but I haven't figured out how to count window of the front app.

Any help appriciated:-)

Are you putting the script in an Execute an AppleScript action?

IAC, when you are trying to manipulate windows in AppleScript, you generally need to use "System Events" and the frontmost process. This revised script works for me:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

set myApplication to name of current application
set screenWidth to 2560 --1920
set screenHeight to 1440

tell application "System Events"
  set frontProcess to first process whose frontmost is true
  tell frontProcess
    
    if (count of windows) ≤ 2 then
      set myWidth to screenWidth / 2
      tell window 1
        set position to {0, 0}
        set size to {myWidth, screenHeight}
      end tell
      
      tell window 2
        set position to {myWidth, 0}
        set size to {myWidth * 2, screenHeight}
      end tell
      
    end if
  end tell
end tell

BTW, when posting a script, please put it in a Code Block.

See WINDOWCOUNT function.

Thanks @JMichaelTX,

Yes I was putting my script in an execute an applescript action. I also tried to run the file but with the same result. I have found, like you that you have to go through System Events to get the frontmost app.

Sorry about the code block I thought it was for HTML content.

I have made this script that works for now.

applescript
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application "System Events"
	set myApplication to name of first application process whose frontmost is true
end tell
set screenWidth to 2560 --1920
set screenHeight to 1440
tell application myApplication
	set myWindowsCount to count of windows
	if myWindowsCount = 0 then
		display dialog "You don't have any windows open!" buttons "OK" default button 1
	else if myWindowsCount ≤ 2 then
		set myWidth to screenWidth / 2
		set bounds of window 1 to {0, 0, myWidth, screenHeight}
		try
			set bounds of window 2 to {myWidth, 0, myWidth * 2, screenHeight}
		end try
	else if myWindowsCount ≥ 3 and myWindowsCount ≤ 4 then
		set myWidth to screenWidth / 2
		set myHeight to (screenHeight - 22) / 2
		set bounds of window 1 to {0, 0, myWidth, myHeight}
		set bounds of window 2 to {myWidth, 0, myWidth * 2, myHeight}
		set bounds of window 3 to {0, myHeight + 22, myWidth, (myHeight * 2) + 22}
		try
			set bounds of window 4 to {myWidth, myHeight + 22, myWidth * 2, (myHeight * 2) + 22}
		end try
	else if myWindowsCount ≥ 5 and myWindowsCount ≤ 6 then
		set myWidth to screenWidth / 3
		set myHeight to (screenHeight - 22) / 2
		set bounds of window 1 to {0, 0, myWidth, myHeight}
		set bounds of window 2 to {myWidth, 0, myWidth * 2, myHeight}
		set bounds of window 3 to {myWidth * 2, 0, myWidth * 3, myHeight}
		set bounds of window 4 to {0, myHeight + 22, myWidth, (myHeight * 2) + 22}
		set bounds of window 5 to {myWidth, myHeight + 22, myWidth * 2, (myHeight * 2) + 22}
		try
			set bounds of window 6 to {myWidth * 2, myHeight + 22, myWidth * 3, (myHeight * 2) + 22}
		end try
	else if myWindowsCount ≥ 7 and myWindowsCount ≤ 8 then
		set myWidth to screenWidth / 4
		set myHeight to (screenHeight - 22) / 2
		set bounds of window 1 to {0, 0, myWidth, myHeight}
		set bounds of window 2 to {myWidth, 0, myWidth * 2, myHeight}
		set bounds of window 3 to {myWidth * 2, 0, myWidth * 3, myHeight}
		set bounds of window 4 to {myWidth * 3, 0, myWidth * 4, myHeight}
		set bounds of window 5 to {0, myHeight + 22, myWidth, (myHeight * 2) + 22}
		set bounds of window 6 to {myWidth, myHeight + 22, myWidth * 2, (myHeight * 2) + 22}
		set bounds of window 7 to {myWidth * 2, myHeight + 22, myWidth * 3, (myHeight * 2) + 22}
		try
			set bounds of window 8 to {myWidth * 3, myHeight + 22, myWidth * 4, (myHeight * 2) + 22}
		end try
	end if
end tell

I'll explore how I could do it in KM and if I can specify the monitor I want this to work on. Right now, it only works on the Main monitor.

KM makes window/screen manipulation easy.
You can use the KM token `%Screen%Front% to get the frame bounds of the current active screen/monitor.

Here are some resources:

Good luck.

Yeah, right;-)
I got an answer from Peter about using For each when dealing with windows but in his example, he just moves the windows in a cascading style. That's not what I'm looking for and I can't figure it out. Since I want the layout to change depending on how many windows there is, I don't know where to start. I know I'll have a need for If else action, but don't know how to work them out with the For Each item action. Add the fact that I want only the windows on the main monitor to be affected and it gets complicated really quick:-(

I just posted this macro, which I hope will help:

MACRO: Cascade All Windows for An App V2 [Example]

I just realized that it is also cascading windows like Peter's example, but maybe it will better lend itself to repurposing. It does make use of the current front (active) screen, and the number of windows in the app.

If you have any questions about the macro, please post them in that topic.

@JeffLambert670, I hope you don't mind that I have revised your topic title to better reflect the question you have asked.

FROM:
Applescripts needs Osascript

TO:
How Do I Manipulate Windows in AppleScript?

This will greatly help you attract more experienced users to help solve your problem, and will help future readers find your question, and the solution.

In addition to my above macro, here are some other examples:

Did you take a look at:

Thanks, @JMichaelTX, my initial question was concerning the fact that my script was only running in Script editor or Script debugger but not in KM or from the menu bar. It has since morphed into how to manipulate windows be it with AppleScript OR with keyboard maestro.

Hi @JeffLambert670,

I had a quick look at your script and noticed it could be modified in a couple of ways that you might find useful.

Firstly, rather than sending your commands to the application itself via tell application my Application (which limits you to controlling windows only belonging to those applications that are AppleScript-able), sending the commands through System Events allows you to control the windows of (almost) any application. You would then use size and position properties of the application process's windows rather than the bounds property of the application's windows.

Secondly, since we're already using System Events, it means we can also easily obtain the screen's width and height programmatically, instead of having to supply the dimensions ourselves.

Lastly, the positioning and sizing of windows follows a distinct, consistent mathematical pattern, which allows us to streamline the process that sets these properties and apply it to an arbitrary number of windows without having to examine if...then...else clauses:

use application "System Events"

set frontApp to the name of the first process whose frontmost is true

tell process "Finder"
	set [screenWidth, screenHeight] to the size of scroll area 1
	set [null, menubarHeight] to the size of menu bar 1
end tell

tell the process named frontApp
	set myWindowsCount to the number of windows
	set N to 0.5 * myWindowsCount as integer
	set myWidth to screenWidth / N as integer
	set myHeight to (screenHeight - menubarHeight) / 2 as integer
	repeat with i from 1 to myWindowsCount
		script win
			property a : (i - 1) mod N
			property b : (i - 1) div N
			property rect : [¬
				myWidth * a, ¬
				myHeight * b + menubarHeight, ¬
				myWidth * (a + 1), ¬
				myHeight * b + myHeight + menubarHeight] as bounding rectangle
			
			on width()
				(rect's item 3) - (rect's item 1)
			end width
			
			on height()
				(rect's item 4) - (rect's item 2)
			end height
		end script
		set window i's position to win's rect as point
		set window i's size to [win's width(), win's height()]
	end repeat
end tell

OH man, I was so sure there was a better way to do it!

But unfortunately, your script doesn't do anything:-( I've tried to save it and run it via execute script file, and execute text script in KM and nothing happens.

Works for me. Run it in Script Editor and see if it organises Script Editor's windows.

I also tested it in KM, having created a quick macro like so:

Again, seems to do what it's supposed to.

AppleScript code
use application "System Events"

set frontApp to the name of the first process whose frontmost is true

tell process "Finder"
	set [screenWidth, screenHeight] to the size of scroll area 1
	set [null, menubarHeight] to the size of menu bar 1
end tell

tell the process named frontApp
	set myWindowsCount to the number of windows
	set N to 0.5 * myWindowsCount as integer
	set myWidth to screenWidth / N as integer
	set myHeight to (screenHeight - menubarHeight) / 2 as integer
	repeat with i from 1 to myWindowsCount
		script win
			property a : (i - 1) mod N
			property b : (i - 1) div N
			property rect : [¬
				myWidth * a, ¬
				myHeight * b + menubarHeight, ¬
				myWidth * (a + 1), ¬
				myHeight * b + myHeight + menubarHeight] as rectangle
			
			on width()
				(rect's item 3) - (rect's item 1)
			end width
			
			on height()
				(rect's item 4) - (rect's item 2)
			end height
		end script
		set window i's position to win's rect as point
		set window i's size to [win's width(), win's height()]
	end repeat
end tell

Thanks CJK,

I seem to have not copied it correctly the first time, the closing bracket was after as rectangle.

It works, but it puts the tiling in the wrong orientation if you have 2 windows, meaning, they are split horizontally, one on top, one on the bottom instead of side to side. Also, if you have a number like 5 it does the layout as 4 windows, not six -1, which is weird because 3 and 7 work fine.

Thanks for the feedback. It was a quick draft that I, admittedly, hadn't thoroughly tested. Let me attend to those bits and update you. The principle is there, though. The issue with 5 is AppleScript being annoying, and rounding 2.5 down to 2 instead of up to 3. That fix, at least, is an easy one.

Here you go, @JeffLambert670. This should address the issues you encountered. Essentially, the two-window scenario is a special case.

Revised AppleScript
use application "System Events"

set frontApp to the name of the first process whose frontmost is true

tell process "Finder"
	set [screenWidth, screenHeight] to the size of scroll area 1
	set [null, menubarHeight] to the size of menu bar 1
end tell

tell the process named frontApp
	set myWindowsCount to the number of windows
	set N to 0.5 * myWindowsCount + 0.01 as integer
	set myWidth to screenWidth / N as integer
	set myHeight to (screenHeight - menubarHeight) / 2 as integer
	
	if myWindowsCount ≤ 2 then
		set N to 2
		set myHeight to myHeight * 2
		set myWidth to myWidth / myWindowsCount
	end if
	
	repeat with i from 1 to myWindowsCount
		script win
			property a : (i - 1) mod N
			property b : (i - 1) div N
			property rect : [¬
				myWidth * a, ¬
				myHeight * b + menubarHeight, ¬
				myWidth * (a + 1), ¬
				myHeight * b + myHeight + menubarHeight] as bounding rectangle
			
			on width()
				(rect's item 3) - (rect's item 1)
			end width
			
			on height()
				(rect's item 4) - (rect's item 2)
			end height
		end script
		set window i's position to win's rect as point
		set window i's size to [win's width(), win's height()]
	end repeat
end tell

Thanks for your help CJK, if I may add a few comments.

If you run the script on 7 windows or more, the width of some windows is a bit larger than what they should. I've tested this with Safari. If I take my script, it can resize those windows fine, so it's not that those have a minimum width. If you have more than 9, none of them have the right width. Maybe the math work up to 6?

Also, how would you isolate windows that are only on the main monitor, but not count and resize windows on a second monitor (which is on the left of the first monitor)? Again, thanks for your help with this.

There's nothing special about the number 6 that would cause the maths to falter. And, in testing this further—specifically the cases you mentioned, with 7 or more and 9 or more windows—I can't reproduce the error you're seeing.

Here's a screenshot with 10 windows of TextEdit having just run the script:

I'm afraid I don't have experience working with multiple monitors, so can't assist you in this regard. My apologies.

that's weird, I was doing my testing with Safari. I get the wrong size in it. If I do it with BBEDIT or TextEdit, it works. If I do it with Word, it does until 8, at 9 it doesn't because word will have a minimum width window size. What is weird is that it works for my script in Safari when it's 7 and 8 windows, but not yours, at least not consistently. At 9 and 10 windows, I hit the minimum width of Safari's window. Oh but wait, I'm trying this on my Macbook pro which is not connected to my second monitor at the moment and has a 1920X1200 resolution. If you're running this on a 5k iMac, you won't have the same problem.

@JeffLambert670, it sounds like the limits imposed on window size are the issue rather than the script. It's odd that the limits seems to be imposed differently for your script versus mine, but we have used two different means of setting the window size.

If you want to have your cake and eat it too, then this script might solve the problem:

Revised AppleScript with handlers
tell application "System Events"
	set frontApp to the name of the first process whose frontmost is true
	
	tell process "Finder"
		set [screenWidth, screenHeight] to the size of scroll area 1
		set [null, menubarHeight] to the size of menu bar 1
	end tell
	
	if the process named frontApp's has scripting terminology then
		my targetScriptableApp:frontApp
	else
		my targetSystemEvents:frontApp
	end if
end tell

to targetScriptableApp:frontApp
	global screenWidth, screenHeight, menubarHeight
	
	tell the application named frontApp
		set myWindowsCount to the number of windows
		set N to 0.5 * myWindowsCount + 0.01 as integer
		set myWidth to screenWidth / N as integer
		set myHeight to (screenHeight - menubarHeight) / 2 as integer
		
		if myWindowsCount ≤ 2 then
			set N to 2
			set myHeight to myHeight * 2
			set myWidth to myWidth / myWindowsCount
		end if
		
		repeat with i from 1 to myWindowsCount
			set a to (i - 1) mod N
			set b to (i - 1) div N
			set window i's bounds to [¬
				myWidth * a, ¬
				myHeight * b + menubarHeight, ¬
				myWidth * (a + 1), ¬
				myHeight * b + myHeight + menubarHeight]
		end repeat
	end tell
end targetScriptableApp:

to targetSystemEvents:frontApp
	global screenWidth, screenHeight, menubarHeight
	
	tell application "System Events" to tell the process named frontApp
		set myWindowsCount to the number of windows
		set N to 0.5 * myWindowsCount + 0.01 as integer
		set myWidth to screenWidth / N as integer
		set myHeight to (screenHeight - menubarHeight) / 2 as integer
		
		if myWindowsCount ≤ 2 then
			set N to 2
			set myHeight to myHeight * 2
			set myWidth to myWidth / myWindowsCount
		end if
		
		repeat with i from 1 to myWindowsCount
			script win
				property a : (i - 1) mod N
				property b : (i - 1) div N
				property rect : [¬
					myWidth * a, ¬
					myHeight * b + menubarHeight, ¬
					myWidth * (a + 1), ¬
					myHeight * b + myHeight + menubarHeight] as rectangle
				
				on width()
					(rect's item 3) - (rect's item 1)
				end width
				
				on height()
					(rect's item 4) - (rect's item 2)
				end height
			end script
			set window i's position to win's rect as point
			set window i's size to [win's width(), win's height()]
		end repeat
	end tell
end targetSystemEvents:

It has one handler that sizes windows using System Events, and one handler that sizes windows using the scriptable components of the application in question. It picks the most appropriate handler to use depending on whether or not the application is AppleScript-able.