This script differs from that in post 4 above in that the user can set the width of a window column as a percentage of the usable display width. To simplify somewhat, the script only supports a tiling grid two columns wide and three rows high. All of the user settings are contained in the first few lines of the getWindowBounds and getAppData handlers.
-- Revised 2022.02.06
use framework "AppKit"
use framework "Foundation"
use scripting additions
on main()
set {appCount, openApps, tileAllApps} to getAppData()
set windowBounds to getWindowBounds(appCount)
tileApps(openApps, tileAllApps, windowBounds)
end main
on getAppData()
set doNotTile to {} -- intended for use with apps that do not have a visible window
set theWorkspace to current application's NSWorkspace's sharedWorkspace()
set activeApp to theWorkspace's frontmostApplication()
set openApps to theWorkspace's runningApplications()
set thePredicates to current application's NSPredicate's predicateWithFormat:"activationPolicy == 0 AND NOT localizedName IN %@" argumentArray:{doNotTile}
set openApps to (openApps's filteredArrayUsingPredicate:thePredicates)'s mutableCopy()
tell application "Finder" to set finderWindow to (exists Finder window 1)
if finderWindow = false then openApps's removeObjectAtIndex:0
set appCountOne to openApps's |count|()
if appCountOne < 2 then errorAlert("One or no active apps were found")
if (openApps's containsObject:activeApp) as boolean = true then
openApps's removeObject:activeApp
openApps's insertObject:activeApp atIndex:0
end if
set dialogList to (openApps's valueForKey:"localizedName") as list
set selectedApps to (choose from list dialogList with prompt "Select two or more apps to tile:" default items dialogList with multiple selections allowed)
if selectedApps = false then error number -128
set thePredicate to current application's NSPredicate's predicateWithFormat:"localizedName IN %@" argumentArray:{selectedApps}
set openApps to ((openApps's filteredArrayUsingPredicate:thePredicate)'s valueForKey:"processIdentifier")
set appCountTwo to openApps's |count|()
if appCountTwo < 2 or appCountTwo > 6 then errorAlert("One or more than 6 apps were selected")
set tileAllApps to appCountOne = appCountTwo
set openApps to openApps as list
return {appCountTwo, openApps, tileAllApps}
end getAppData
on getWindowBounds(windowCount)
set wB to 5 -- window buffer
set windowSetting to {{1, 1}, {1, 2}, {2, 2}, {2, 3}, {3, 3}} -- tiling grid for 2 thru 6 apps
set windowRatio to {55, 55, 50, 55, 50} -- column width percent for 2 thru 6 apps
set windowSetting to item (windowCount - 1) of windowSetting
set windowRatio to item (windowCount - 1) of windowRatio
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
set wLW to ((x2 - x1) - (wB * 3)) * windowRatio div 100 -- window widths
set wRW to ((x2 - x1) - (wB * 3)) * (100 - windowRatio) div 100
set windowBounds to {}
set {x, y} to {x1, y1}
repeat with i from 1 to 2 -- loop through columns
repeat with j from 1 to item i of windowSetting -- loop through windows in a column
set columnWindowCount to item i of windowSetting
if i = 1 then
if columnWindowCount = 1 then
set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wLW))), (y + (j * wB) + ((j - 1) * wFH))}, {wLW, wFH}}
else if columnWindowCount = 2 then
set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wLW))), (y + (j * wB) + ((j - 1) * wHH))}, {wLW, wHH}}
else
set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wLW))), (y + (j * wB) + ((j - 1) * wTH))}, {wLW, wTH}}
end if
else
if columnWindowCount = 1 then
set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wLW))), (y + (j * wB) + ((j - 1) * wFH))}, {wRW, wFH}}
else if columnWindowCount = 2 then
set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wLW))), (y + (j * wB) + ((j - 1) * wHH))}, {wRW, wHH}}
else
set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wLW))), (y + (j * wB) + ((j - 1) * wTH))}, {wRW, wTH}}
end if
end if
end repeat
end repeat
return windowBounds
end getWindowBounds
on tileApps(openApps, tileAllApps, windowBounds)
tell application "System Events"
repeat with i from (count openApps) to 1 by -1
tell (first process whose unix id is (item i of openApps))
tell every window
set position to item 1 of item i of windowBounds
set size to item 2 of item i of windowBounds
end tell
if tileAllApps = false then
set frontmost to true
delay 0.1 -- test different values
end if
end tell
end repeat
end tell
end tileApps
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 following script is similar to that above, the primary difference being that apps not selected by the user in the dialog are hidden rather than placed behind the tiled apps.
use framework "AppKit"
use framework "Foundation"
use scripting additions
on main()
set {appCount, openApps, skipApps} to getAppData()
set windowBounds to getWindowBounds(appCount)
tileApps(openApps, windowBounds, skipApps)
end main
on getAppData()
set doNotTile to {} -- intended for use with apps that do not have a visible window
set theWorkspace to current application's NSWorkspace's sharedWorkspace()
set activeApp to theWorkspace's frontmostApplication()
set openApps to theWorkspace's runningApplications()
set thePredicates to current application's NSPredicate's predicateWithFormat:"activationPolicy == 0 AND NOT localizedName IN %@" argumentArray:{doNotTile}
set openApps to (openApps's filteredArrayUsingPredicate:thePredicates)'s mutableCopy()
tell application "Finder"
if (exists Finder window 1) then
set finderWindow to true
else if visible = false then
set finderWindow to true
else
set finderWindow to false
end if
end tell
if finderWindow = false then openApps's removeObjectAtIndex:0
if (openApps's |count|()) < 2 then errorAlert("One or no active apps were found")
if (openApps's containsObject:activeApp) as boolean = true then
openApps's removeObject:activeApp
openApps's insertObject:activeApp atIndex:0
end if
set dialogList to (openApps's valueForKey:"localizedName") as list
set selectedApps to (choose from list dialogList with prompt "Select two or more apps to tile:" default items dialogList with multiple selections allowed)
if selectedApps = false then error number -128
set appCount to (count selectedApps)
if appCount < 2 or appCount > 6 then errorAlert("One or more than 6 apps were selected")
set thePredicate to current application's NSPredicate's predicateWithFormat:"NOT localizedName IN %@" argumentArray:{selectedApps}
set skipApps to ((openApps's filteredArrayUsingPredicate:thePredicate)'s valueForKey:"processIdentifier")
set thePredicate to current application's NSPredicate's predicateWithFormat:"localizedName IN %@" argumentArray:{selectedApps}
set openApps to ((openApps's filteredArrayUsingPredicate:thePredicate)'s valueForKey:"processIdentifier")
return {appCount, openApps as list, skipApps as list}
end getAppData
on getWindowBounds(windowCount)
set wB to 5 -- window buffer
set windowSetting to {{1, 1}, {1, 2}, {2, 2}, {2, 3}, {3, 3}} -- tiling grid for 2 thru 6 apps
set windowRatio to {55, 55, 50, 55, 50} -- percent column width for 2 thru 6 apps
set windowSetting to item (windowCount - 1) of windowSetting
set windowRatio to item (windowCount - 1) of windowRatio
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
set wLW to ((x2 - x1) - (wB * 3)) * windowRatio div 100 -- window widths
set wRW to ((x2 - x1) - (wB * 3)) * (100 - windowRatio) div 100
set windowBounds to {}
repeat with i from 1 to 2 -- loop through columns
repeat with j from 1 to item i of windowSetting -- loop through windows in a column
set columnWindowCount to item i of windowSetting
if i = 1 then
if columnWindowCount = 1 then
set end of windowBounds to {{(x1 + wB), (y1 + wB)}, {wLW, wFH}}
else if columnWindowCount = 2 then
set end of windowBounds to {{(x1 + wB), (y1 + (j * wB) + ((j - 1) * wHH))}, {wLW, wHH}}
else
set end of windowBounds to {{(x1 + wB), (y1 + (j * wB) + ((j - 1) * wTH))}, {wLW, wTH}}
end if
else
if columnWindowCount = 1 then
set end of windowBounds to {{(x1 + (i * wB) + ((i - 1) * (wLW))), (y1 + wB)}, {wRW, wFH}}
else if columnWindowCount = 2 then
set end of windowBounds to {{(x1 + (i * wB) + ((i - 1) * (wLW))), (y1 + (j * wB) + ((j - 1) * wHH))}, {wRW, wHH}}
else
set end of windowBounds to {{(x1 + (i * wB) + ((i - 1) * (wLW))), (y1 + (j * wB) + ((j - 1) * wTH))}, {wRW, wTH}}
end if
end if
end repeat
end repeat
return windowBounds
end getWindowBounds
on tileApps(openApps, windowBounds, skipApps)
tell application "System Events"
repeat with i from 1 to (count skipApps)
tell (first process whose unix id is (item i of skipApps))
set visible to false
delay 0.1
end tell
end repeat
repeat with i from (count openApps) to 1 by -1
tell (first process whose unix id is (item i of openApps))
tell every window
set position to item 1 of item i of windowBounds
set size to item 2 of item i of windowBounds
end tell
if (visible = false) then set visible to true
end tell
end repeat
end tell
end tileApps
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()