Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

Tile then restore the active app's window positions and sizes Apps
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.
  • 'Enable access for assistive devices' in the Universal Access System Preferences panel must be enabled for the script to work.
  • Use a launcher to run the script for best results; I use Spark (Quicksilver doesn't remember globals/properties between runs, anyone know why?)
  • Make sure you edit the script to set the proper screen size for your machine.
  • This script uses System Events to move the windows. System Events seems to ignore invisible windows, but has good control over many apps' windows.
  • The script works in lots of apps. It doesn't like floating windows, or X11 or OpenOffice. Some apps that seem to work fine include Finder, TextEdit, Preview, Safari, Terminal, iTunes, QuickTime, and Script Editor.
  • Minimized windows are tiled while minimized! There'll be holes in your tiles, but un-minimizing will fit them nicely into the holes.
  • The tiled window positions are not remembered. Click the toolbars to stick them.
  • If two or more windows have the same name, it knows which is which. (I don't think System Events does.)
  • Close or open another window and the script will be reset such that the current window positions will be the starting point.
  • Calling the script from one app while it's active in another also resets the script.
  • If between uses, you close a window then open it again, the window ID changes. As a result, the script doesn't know if this is the same window with a new ID or a different window with the same name, so the script resets.
  • The script remembers all the window bounds no matter which window is frontmost.
  • It tiles one to twelve windows evenly across my 1280x800 screen.
[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.]
    •    
  • Currently 2.20 / 5
  • 1
  • 2
  • 3
  • 4
  • 5
  (5 votes cast)
 
[8,895 views]  

Tile then restore the active app's window positions and sizes | 12 comments | Create New Account
Click here to return to the 'Tile then restore the active app's window positions and sizes' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
Tile then restore the active app's window positions and sizes
Authored by: walidvb on May 11, '09 08:06:42AM

Anyway to make it work with Spotlight? Or do you really need an actual Launcher?

Anyway, real good idea, been looking for something like this for quite a while now!

Thanks!



[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: philostein on May 12, '09 03:44:07AM
I don't think so, Spotlight opens scripts in Script Editor instead of running them (10.4.11). If you saved the script as an app, it would only check the windows of ITSELF - which kinda defeats the purpose.

May I humbly refer you to my previous hint: http://www.macosxhints.com/article.php?story=20080315064646576
This explains how to set up the script menu in the menu bar, so you can quickly run any script.

Cheers, PhilHints.

[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: thyvillageidiot on May 11, '09 09:18:03AM

I like this script, except my dock is on the left hand side and takes up 80 pixels of my 1280x800 display. I set the parameters to be 1200x800, however when I run the script the windows leave a 80 pixel gap on the right instead of on the left.

How can I run the script to prefer the right hand side of the screen to keep my doc free?



[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: philostein on May 11, '09 04:27:27PM

Change the _screenWidth variable to 1200, then use this code in place of the current code:

-- Makes an almost screen size window with a bit of space to drag n' drop files to windows behind.
if _count is 1 then
set position of window 1 of _process to {80, 22}
set size of window 1 of _process to {1200, 778}
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)) + 80, 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)) + 80, 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))) + 80, ((_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


Basically, I just put a ( )+80 wrapper around the horizontal part of the 'set position of window' lines.

Hope it works OK,

PhilHints.



[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: solsang on May 11, '09 12:34:12PM

Nothing happens if used with keyboard maestro as launcher...



[ Reply to This | # ]
cant process...
Authored by: solsang on May 11, '09 12:44:19PM

with spark i get the "cant process finder.app"



[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: NaOH-Lye on May 11, '09 01:26:52PM

Using Keyboard Maestro (version 2.1.3) on 10.5.6 it's working splendidly for me. My thanks to the tip submitter for a great script.



[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: osxpounder on May 11, '09 03:03:52PM

Very good idea. Thanks for sharing your discovery.



[ Reply to This | # ]
Doesn't work from menu bar
Authored by: billclinton on May 11, '09 09:23:34PM

I launch this script from the Applescript menu which I keep in the menu bar and it does nothing. Actually, one window will appear to lose focus but then it (focus) quickly returns. No windows are moved or otherwise modified.



[ Reply to This | # ]
Doesn't work from menu bar
Authored by: philostein on May 11, '09 10:27:16PM

Try opening the script in Script Editor and clicking 'Run'.
This should tile the Script Editor windows.
Are any error messages generated? (This may help, although the messages are pretty obscure sometimes.)

Cheers, PhilHints.



[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: dcottle on Jun 30, '09 09:27:56AM

I'm working with two screens. I tried it on my second monitor and it moved everything to the first monitor. I'm not great at apple script. What would I change to target the second monitor?



[ Reply to This | # ]
Tile then restore the active app's window positions and sizes
Authored by: philostein on Apr 30, '10 04:05:49PM
The script above has been updated.

In Applescript, System Events no longer handles process names with '.app' on the end - I've corrected the bug in my script. It handles app windows in different spaces better. Works with the Quick Look window in Finder. The script will speak the error if any is encountered. (Better, I think, than 'It doesn't work' when used from a launcher.)

Remember to change _screenWidth and _screenHeight in the script code to reflect your screen size.
Also, 'Enable access for assistive devices' in the Universal Access System Preferences panel must be enabled for the script to work.

This version was tested on Snow Leopard 10.6.3, and written for use with one display.

Cheers, PhilHints

[ Reply to This | # ]