Tile then restore the active app's window positions and sizes

May 11, '09 07:30:00AM

Contributed by: philostein

Sometimes I have too many windows open in one app to see what's going on properly. Exposé is useful for seeing open windows, but it doesn't let me work in them at the same time. So I wrote the following AppleScript to tile up to 12 of the frontmost app's windows the first time you run the script. When you run the script again, it puts the windows back as they were, assuming they're the same set of windows.

Here's the code:

-- "Tile and toggle frontmost app window positions and sizes" script by PhilHints, updated April 2010.

-- This script is tailored for a screen size of 1280x800, hidden dock.
-- Modify _screenWidth and _screenHeight if your screen is not this size.

-- Change these if your screen is not 1280x800.  My dock and desktop are hidden. Think about leaving spaces for those...
property _screenWidth : 1280
property _screenHeight : 800

-- Change these if you wish to tile more windows...
property _windowsLimit : 12
property _windowsTileNumbers : {5, 6, 7, 8, 9, 10, 11, 12}

-- This remembers the window sizes from last time (if the they were the same windows).
property _boundsBefore : {}

-- This remembers the window names from last time. 
property _namesBefore : {}

global _count -- Count of all frontmost app windows on run.
global _namesNow -- Names of all frontmost app windows on run.
global _boundsNow -- Bounds of all frontmost app windows on run.
global _process -- Frontmost process for System Events.
global _idNow -- id of all frontmost app windows on run.
global _idQuickLook -- If Quick Look is open (doesn't have a window id)
property _idQuickLook : {}
global _boundsTiled -- Bounds of all frontmost app windows after first tiled.
global _frontMostApp -- Name of frontmost app.

property _detailsOriginal : {} -- Name,id,position,size of all frontmost app windows before first tiled.
property _idOriginal : {} -- id of all frontmost app windows before first tiled.
property _detailsTiled : {} -- Name,id,position,size of all frontmost app windows after first tiled.

property _sameId : false -- Check for same window ids as last run.
property _sameWindows : false -- Check for same window names as last run.
property _sameBounds : false -- If same windows, check for same window bounds as last run.

try
  set _frontMostApp to displayed name of (info for (path to frontmost application))
  if text -1 thru -4 of _frontMostApp = ".app" then
    set _atd to AppleScript's text item delimiters
    set AppleScript's text item delimiters to ".app"
    try
      set _frontMostApp to text item 1 of _frontMostApp
    end try
    set AppleScript's text item delimiters to _atd
  end if
  
  -- Using System Events, I  only get the VISIBLE app windows in the current space.
  tell application "System Events"
    set _process to process _frontMostApp
    tell _process to set _count to count of windows
    if _count = 0 then return
    tell _process to set _windowDescriptions to description of windows
    if _frontMostApp = "Finder" then
      -- Check for Quick Look, which has no window id…
      if _windowDescriptions contains "Quick Look Panel" then set _idQuickLook to {99999999}
    end if
  end tell
  
  -- I reckon System Events doesn't know the unique id number of the windows, but the frontmost app does.
  tell application _frontMostApp
    if _frontMostApp ≠ "Finder" then
      set _idNow to id of (windows whose visible = true)
    else
      set _idNow to _idQuickLook & id of windows
    end if
    
    -- The windows in the current space seem to be first in the _idNow list…
    copy (items 1 thru _count of _idNow) to _idNow
  end tell
  
  tell application "System Events"
    tell _process
      if _count ≤ _windowsLimit then
        -- Window names on run (_namesNow).
        set _namesNow to {}
        repeat with i from 1 to _count
          set _namesNow to _namesNow & name of item i of windows
        end repeat
        
        -- Bounds of all frontmost app windows on run (_boundsNow).
        set _boundsNow to {}
        repeat with i from 1 to _count
          set _boundsNow to _boundsNow & {{position of window i} & {size of window i}}
        end repeat
      else
        tell me
          tell application _frontMostApp to display dialog "Too many windows, " & _windowsLimit & " is the maximum." with title "Tile and Toggle Frontmost App Windows" giving up after 4
        end tell
        return
      end if
    end tell
  end tell
  
  -- Check to see if _idNow is the same as _idOriginal even if windows are in a different order.
  if (count of _idNow) = (count of _idOriginal) then
    set _sameId to true
    repeat with i from 1 to count of _idNow
      if item i of _idNow is not in _idOriginal then
        set _sameId to false
        exit repeat
      end if
    end repeat
    if _sameId = true then
      set _namesNow to _namesBefore
      -- Same windows, so check to see if _boundsNow is the same as _boundsBefore even if bounds are in a different order.
      set _sameBounds to true
      repeat with i from 1 to count of _boundsNow
        if {item i of _boundsNow} is not in _boundsBefore then
          set _sameBounds to false
          exit repeat
        end if
      end repeat
      if _sameBounds = true then
        set _boundsNow to _boundsBefore
      end if
    end if
  end if
  
  
  -- There are 3 CONDITIONS (if/then blocks) that call the 3 handlers:
  
  -- CONDITION 1
  -- If the names or ids of the windows are **different** from last time, the script 'resets'.
  -- The window bounds are remembered for the next run.
  -- Then the windows are tiled.
  if _namesNow ≠ _namesBefore or _sameId = false then
    _windowRoutine1() -- At the end of the script.
    tell application _frontMostApp to activate
    return
  end if
  
  -- CONDITION 2
  -- If the names of the windows are the **same** as last time, and if the windows **are** at their original bounds, the script tiles the windows.
  if _namesNow = _namesBefore and _boundsNow = _boundsBefore then
    _windowRoutine2()
    tell application _frontMostApp to activate
    return
  end if
  
  -- CONDITION 3
  -- If the names of the windows are the **same** as last time, and if the windows are **not** at their original bounds, the script puts the windows back to their original bounds.
  if _namesNow = _namesBefore and _boundsNow ≠ _boundsBefore then
    _windowRoutine3()
    tell application _frontMostApp to activate
    return
  end if
  
on error a number b
  say a
end try


on _windowRoutine2()
  tell application "System Events"
    tell _process
      -- if the names of the windows are the same, System Events doesn't seem to know which one to move.
      -- I use the window id number from the app to tell System Events which window to move.
      repeat with i from 1 to _count
        repeat with j from 1 to count of _detailsTiled
          item i of _idNow = (item 2 of item j of _detailsTiled)
          if result is true then
            set position of window i to (item 3 of item j of _detailsTiled)
            set size of window i to (item 4 of item j of _detailsTiled)
            exit repeat
          end if
        end repeat
      end repeat
    end tell
  end tell
end _windowRoutine2


on _windowRoutine3()
  tell application "System Events"
    tell _process
      -- if the names of the windows are the same, System Events doesn't seem to know which one to move.
      -- I use the window id number from the app to tell System Events which window to move.
      repeat with i from 1 to _count
        repeat with j from 1 to count of _detailsOriginal
          item i of _idNow = (item 2 of item j of _detailsOriginal)
          if result is true then
            set position of window i to (item 3 of item j of _detailsOriginal)
            set size of window i to (item 4 of item j of _detailsOriginal)
            exit repeat
          end if
        end repeat
      end repeat
    end tell
  end tell
end _windowRoutine3


on _windowRoutine1()
  tell application _frontMostApp
    if _frontMostApp ≠ "Finder" then
      set _idOriginal to id of (windows whose visible = true)
    else
      set _idOriginal to _idQuickLook & id of windows
    end if
    
    copy (items 1 thru _count of _idOriginal) to _idOriginal
  end tell
  
  tell application "System Events"
    tell _process
      copy contents of _boundsNow to _detailsOriginal
      
      -- Group window names, ids, positions and sizes together.
      repeat with i from 1 to _count
        set beginning of item i of _detailsOriginal to item i of _idOriginal
        set beginning of item i of _detailsOriginal to item i of _namesNow
      end repeat
      
      -- Remember original window bounds.
      set _boundsBefore to {}
      repeat with i from 1 to _count
        set _boundsBefore to _boundsNow
      end repeat
    end tell
    
    --Set  _namesBefore for the next run of the script.
    set _namesBefore to _namesNow
    
    
    -- The window sizes are my personal preferences. I'll explain some of the reasoning...
    
    -- Makes an almost screen size window, so files can still be dragged behind it.
    if _count is 1 then
      set position of window 1 of _process to {64, 22}
      set size of window 1 of _process to {_screenWidth - 128, _screenHeight - 22}
    end if
    
    -- If there are 2 or 3 or 4 windows, it's easy to split the windows across the width of the screen while retaining decent size windows.
    -- Change properties _screenWidth and _screenHeight for different size screens. (See top of script.)
    
    if {2, 3, 4} contains _count then
      repeat with i from 1 to _count
        set position of window i of _process to {(_screenWidth / _count) * (i - 1), 22}
        set size of window i of _process to {(_screenWidth / _count), (_screenHeight - 22)}
      end repeat
    end if
    
    -- Windows 5-12 are split vertically too.
    if _windowsTileNumbers contains _count then
      repeat with i from 1 to (_count / 2 round rounding up)
        set position of window i of _process to {(_screenWidth / (_count / 2 round rounding up)) * (i - 1), 22}
        set size of window i of _process to {(_screenWidth / (_count / 2 round rounding up)), ((_screenHeight - 22) / 2)}
      end repeat
      
      repeat with i from (_count / 2 round rounding up) + 1 to _count
        set position of window i of _process to {(_screenWidth / (_count / 2 round rounding up)) * (i - ((_count / 2 round rounding up) + 1)), ((_screenHeight - 22) / 2) + 22}
        set size of window i of _process to {(_screenWidth / (_count / 2 round rounding up)), ((_screenHeight - 22) / 2)}
      end repeat
    end if
    
    -- Remembers the new bounds of the windows once tiled (_boundsTiled).
    tell _process
      set _boundsTiled to {}
      repeat with i from 1 to _count
        set _boundsTiled to _boundsTiled & {{position of window i} & {size of window i}}
      end repeat
      
      copy contents of _boundsTiled to _detailsTiled
      repeat with i from 1 to _count
        set beginning of item i of _detailsTiled to item i of _idOriginal
        set beginning of item i of _detailsTiled to item i of _namesNow
      end repeat
    end tell
  end tell
end _windowRoutine1
Copy the above and paste it into Script Editor, then change _screenWidth and _screenHeight in the code to reflect your screen size (the script is set up for a 1280x800 screen); allow for the dock, too, if you don't leave it hidden. Save it as a script in your user's Library » Scripts folder. Read on for some usage notes about the script...

Notes, issues, etc.

[robg adds: I tested this by saving it as a Finder script (in my user's Library » Scripts » Applications » Finder folder, then opening a bunch of Finder windows in random locations before calling the script. It worked as described, and can be thought of as an app-specific Exposé that allows window interaction. The script has been tested in 10.4 (by the submitter) and in 10.5 on my machine.]

Comments (12)


Mac OS X Hints
http://hints.macworld.com/article.php?story=200905080554362