Tile scriptable app windows

This script tiles all open windows of the application that has the focus. Note should be made that:

  • The application that has the focus must be scriptable.

  • The script may not work properly if tested in a script editor.

  • The user can modify the tiling pattern by editing the windowSetting list. For example, the list for 3 windows can be {1, 1, 1} or {1, 2} or {2, 1} or {3}.

  • Script Editor contains a bug which may cause this script to tile windows in an unexpected fashion.

  • The calculations made in setting windowBounds are based, in part, on those posted by One208 in the thread linked at the bottom of this post.

-- Revised 2022.02.14

use framework "AppKit"
use framework "Foundation"
use scripting additions

on main()
	set {activeApp, windowCount} to getAppData()
	set windowBounds to getWindowData(windowCount)
	tileWindows(activeApp, windowBounds, windowCount)
end main

on getAppData()
	set activeApp to current application's NSWorkspace's sharedWorkspace()'s frontmostApplication()'s localizedName as text
	try
		tell application activeApp to set windowCount to (count windows)
	on error
		errorAlert(activeApp & " may not be scriptable")
	end try
	if windowCount < 2 or windowCount > 9 then errorAlert(activeApp & " appears to have less than 2 or more than 9 open windows")
	return {activeApp, windowCount}
end getAppData

on getWindowData(windowCount)
	set wB to 5 -- window buffer
	set windowSetting to {{1, 1}, {1, 2}, {2, 2}, {2, 3}, {3, 3}, {2, 2, 3}, {2, 3, 3}, {3, 3, 3}}
	set windowSetting to item (windowCount - 1) of windowSetting
	set columnCount to (count windowSetting)
	set {x1, y1, x2, y2} to getDisplayBounds() -- usable display bounds
	
	set wFH to ((y2 - y1) - (wB * 2)) div 1 -- window heights
	set wHH to ((y2 - y1) - (wB * 3)) div 2
	set wTH to ((y2 - y1) - (wB * 4)) div 3
	
	if columnCount = 2 then -- window widths
		set wW to ((x2 - x1) - (wB * 3)) div 2
	else
		set wW to ((x2 - x1) - (wB * 4)) div 3
	end if
	
	set windowBounds to {}
	repeat with i from 1 to columnCount -- loop through columns
		repeat with j from 1 to item i of windowSetting -- loop through windows in a column
			if item i of windowSetting = 1 then
				set end of windowBounds to {(x1 + (i * wB) + ((i - 1) * (wW))), (y1 + (j * wB) + ((j - 1) * wFH)), (x1 + (i * wB) + (i * wW)), (y1 + (j * wB) + (j * wFH))}
			else if item i of windowSetting = 2 then
				set end of windowBounds to {(x1 + (i * wB) + ((i - 1) * (wW))), (y1 + (j * wB) + ((j - 1) * wHH)), (x1 + (i * wB) + (i * wW)), (y1 + (j * wB) + (j * wHH))}
			else
				set end of windowBounds to {(x1 + (i * wB) + ((i - 1) * (wW))), (y1 + (j * wB) + ((j - 1) * wTH)), (x1 + (i * wB) + (i * wW)), (y1 + (j * wB) + (j * wTH))}
			end if
		end repeat
	end repeat
	
	return windowBounds
end getWindowData

on tileWindows(activeApp, windowBounds, windowCount)
	tell application activeApp
		repeat with i from windowCount to 1 by -1
			set bounds of window i to (item i of windowBounds)
		end repeat
	end tell
	
	tell application "System Events" to tell process activeApp -- app and process names must be same
		click menu item "Bring All to Front" of menu "Window" of menu bar 1
	end tell
end tileWindows

on getDisplayBounds()
	set theScreen to current application's NSScreen's mainScreen()
	set {{aF, bF}, {cF, dF}} to theScreen's frame()
	set {{aV, bV}, {cV, dV}} to theScreen's visibleFrame()
	return {aV as integer, (dF - bV - dV) as integer, (aV + cV) as integer, (dF - bV) as integer}
end getDisplayBounds

on errorAlert(dialogMessage)
	display alert "An error has occurred" message dialogMessage as critical
	error number -128
end errorAlert

main()

The post mentioned above is at:

https://macscripter.net/viewtopic.php?id=48857

1 Like

The following is a minor rewrite of the script in post 1. It fixes a few issues and uses a faster (and hopefully reliable) method to bring windows to the front.

use framework "AppKit"
use framework "Foundation"
use scripting additions

on main()
	set {activeAppName, activeAppID, windowCount} to getAppData()
	set windowBounds to getWindowData(windowCount)
	tileWindows(activeAppName, activeAppID, windowBounds, windowCount)
end main

on getAppData()
	set activeApp to current application's NSWorkspace's sharedWorkspace()'s frontmostApplication()
	set activeAppName to activeApp's localizedName as text
	set activeAppID to activeApp's processIdentifier()
	try
		tell application activeAppName to set windowCount to (count windows)
	on error
		errorAlert(activeAppName & " may not be scriptable")
	end try
	if windowCount < 2 or windowCount > 9 then errorAlert(activeAppName & " appears to have less than 2 or more than 9 open windows")
	return {activeAppName, activeAppID, windowCount}
end getAppData

on getWindowData(windowCount)
	set wB to 5 -- window buffer
	set windowSetting to {{1, 1}, {1, 2}, {2, 2}, {2, 3}, {3, 3}, {2, 2, 3}, {2, 3, 3}, {3, 3, 3}}
	set windowSetting to item (windowCount - 1) of windowSetting
	set columnCount to (count windowSetting)
	set {x1, y1, x2, y2} to getDisplayBounds() -- usable display bounds
	
	set wFH to ((y2 - y1) - (wB * 2)) div 1 -- window heights
	set wHH to ((y2 - y1) - (wB * 3)) div 2
	set wTH to ((y2 - y1) - (wB * 4)) div 3
	
	if columnCount = 1 then -- window widths
		set wW to ((x2 - x1) - (wB * 2)) div 1
	else if columnCount = 2 then
		set wW to ((x2 - x1) - (wB * 3)) div 2
	else
		set wW to ((x2 - x1) - (wB * 4)) div 3
	end if
	
	set windowBounds to {}
	repeat with i from 1 to columnCount -- loop through columns
		repeat with j from 1 to item i of windowSetting -- loop through windows in a column
			if item i of windowSetting = 1 then
				set end of windowBounds to {(x1 + (i * wB) + ((i - 1) * (wW))), (y1 + (j * wB) + ((j - 1) * wFH)), (x1 + (i * wB) + (i * wW)), (y1 + (j * wB) + (j * wFH))}
			else if item i of windowSetting = 2 then
				set end of windowBounds to {(x1 + (i * wB) + ((i - 1) * (wW))), (y1 + (j * wB) + ((j - 1) * wHH)), (x1 + (i * wB) + (i * wW)), (y1 + (j * wB) + (j * wHH))}
			else
				set end of windowBounds to {(x1 + (i * wB) + ((i - 1) * (wW))), (y1 + (j * wB) + ((j - 1) * wTH)), (x1 + (i * wB) + (i * wW)), (y1 + (j * wB) + (j * wTH))}
			end if
		end repeat
	end repeat
	return windowBounds
end getWindowData

on tileWindows(activeAppName, activeAppID, windowBounds, windowCount)
	tell application activeAppName
		repeat with i from windowCount to 1 by -1
			set bounds of window i to (item i of windowBounds)
		end repeat
	end tell
	tell application "System Events" to tell (first process whose unix id is activeAppID) to set visible to true
end tileWindows

on getDisplayBounds()
	set theScreen to current application's NSScreen's mainScreen()
	set {{aF, bF}, {cF, dF}} to theScreen's frame()
	set {{aV, bV}, {cV, dV}} to theScreen's visibleFrame()
	return {aV as integer, (dF - bV - dV) as integer, (aV + cV) as integer, (dF - bV) as integer}
end getDisplayBounds

on errorAlert(dialogMessage)
	display alert "An error has occurred" message dialogMessage as critical
	error number -128
end errorAlert

main()