Friday, May 27, 2022

#1 2022-01-15 09:49:57 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1327

Tile visible apps on active display

This script tiles apps on the active display. To test this script, open and run it in a script editor with at least one other visible app. Note should be made that:

* The user can change the tiling grid pattern by editing the windowSetting list and can change the buffer between apps by editing the wB variable.
* The user can exclude apps from being tiled by adding them to the doNotTile list.
* Other than those in the doNotTile list, all apps including those without a visible window are tiled.
* Some apps have minimum/maximum window sizes and may not tile as expected.
   

Applescript:

-- Revised 2022.05.22

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

on main()
   set {appCount, openApps} to getAppData()
   set windowBounds to getWindowData(appCount)
   tileApps(openApps, 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 appCount to openApps's |count|()
   
   if appCount < 2 or appCount > 9 then
       display alert "An error has occurred" message "Less than 2 or more than 9 apps are open" as critical
       error number -128
   end if
   
   if (openApps's containsObject:activeApp) as boolean = true then -- tile active app first
       openApps's removeObject:activeApp
       openApps's insertObject:activeApp atIndex:0
   end if
   set openApps to (openApps's valueForKey:"processIdentifier") as list
   
   return {appCount, openApps}
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 visibleDisplayBounds()
   
   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 -- column 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 {}
   set {x, y} to {x1, y1}
   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 {{(x + (i * wB) + ((i - 1) * (wW))), (y + (j * wB) + ((j - 1) * wFH))}, {wW, wFH}}
           else if item i of windowSetting = 2 then
               set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wW))), (y + (j * wB) + ((j - 1) * wHH))}, {wW, wHH}}
           else
               set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wW))), (y + (j * wB) + ((j - 1) * wTH))}, {wW, wTH}}
           end if
       end repeat
   end repeat
   
   return windowBounds
end getWindowData

on tileApps(openApps, 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)) to 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
       end repeat
   end tell
end tileApps

on visibleDisplayBounds()
   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 visibleDisplayBounds

main()

Last edited by peavine (2022-05-23 09:33:19 am)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#2 2022-01-21 05:43:10 pm

Micker
Member
Registered: 2020-01-04
Posts: 1

Re: Tile visible apps on active display

For windows that are "active" but do not show (e.g. Boinc, AAMPS, etc) on the desktop, what modifications would be necessary to include them or exclude them in the script?

Offline

 

#3 2022-01-22 02:21:19 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1327

Re: Tile visible apps on active display

Micker wrote:

For windows that are "active" but do not show (e.g. Boinc, AAMPS, etc) on the desktop, what modifications would be necessary to include them or exclude them in the script?



I've modified the script in post 1 to include a doNotTile list.


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#4 2022-01-26 10:53:21 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1327

Re: Tile visible apps on active display

This script is similar to that above, differing in that it displays a dialog in which the user can select the apps to tile. It should be noted that:

* If one of the selected apps has the focus, that app will be tiled in the first position at the upper-left of the screen.

* The order of the apps shown in the dialog is the order in which they will be tiled.

* The script has a do-not-tile list but this is only intended for use with apps that do not have visible windows.

Applescript:

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

on main()
   set {appCount, openApps, tileAllApps} to getAppData()
   set windowBounds to getWindowData(appCount)
   tileApps(openApps, windowBounds, tileAllApps)
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 or appCountOne > 9 then errorAlert("Less than 2 or more than 9 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 (item 1 of dialogList) with multiple selections allowed) -- delete item 1 of to preselect all apps
   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 > 9 then errorAlert("One or more than 9 apps were selected")
   set tileAllApps to appCountOne = appCountTwo
   set openApps to openApps as list
   
   return {appCountTwo, openApps, tileAllApps}
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 -- column 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 {}
   set {x, y} to {x1, y1}
   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 {{(x + (i * wB) + ((i - 1) * (wW))), (y + (j * wB) + ((j - 1) * wFH))}, {wW, wFH}}
           else if item i of windowSetting = 2 then
               set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wW))), (y + (j * wB) + ((j - 1) * wHH))}, {wW, wHH}}
           else
               set end of windowBounds to {{(x + (i * wB) + ((i - 1) * (wW))), (y + (j * wB) + ((j - 1) * wTH))}, {wW, wTH}}
           end if
       end repeat
   end repeat
   
   return windowBounds
end getWindowData

on tileApps(openApps, windowBounds, tileAllApps)
   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
               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()


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#5 2022-01-30 06:55:03 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1327

Re: Tile visible apps on active display

Just as a side note, for those looking for a simpler solution, recent version of macOS have the ability to tile windows to the left and right side of the screen. And, for those running Monterey, the Shortcuts app includes "Get Organized" shortcuts that will tile apps. After trying these, I decided to use the second script above, but in the end this is just a matter of personal preference.

Last edited by peavine (2022-02-01 07:41:34 am)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#6 2022-02-04 07:32:06 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1327

Re: Tile visible apps on active display

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.

Applescript:

-- 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.

Applescript:

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()

Last edited by peavine (2022-02-10 01:23:26 pm)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)