# Position window at largest unused space

Before I attempt this from scratch I would like to know if it has been done already or if anyone has any good ideas. Is there a way of positioning a new window in the largest free space on the screen?

I would imagine it would involve:

1. Get the dimensions of the screen
2. Get the size and position of every open window
3. Math
4. Create new window in “ideal” location.

Nigel Garvey and DJ Bazzie Wazzie have made great contributions to solving this problem in this thread : http://macscripter.net/viewtopic.php?id=40137

I was thinking of something like this to get the bounds of every window:

``````
set theBounds to {}

tell application "System Events" to set theApps to name of every application process whose visible = true
repeat with anApp in theApps
tell application anApp
try
set appWindows to its every window
repeat with aWindow in appWindows
set end of theBounds to aWindow's bounds
end repeat
end try
end tell
end repeat
``````

Let’s first do it with a pen and paper, to understand.

1. Draw some rectangles with different sizes on a piece of paper with some overlap to other rectangle(s) allowed.
2. Now extend the lines of the rectangle to ends of the paper so you’re seeing a grid with all kind of different size cells.
3. Now mark the cells that are overlapped/inside the initially drawn rectangles in step 1.
4. At last try to find the biggest rectangle by using only non-marked (marked in step 3) rectangles.

When we do it by hand we find in a very few steps the largest rectangle because we don’t think/work in pixels but in rectangles right away. The same approach should be used here, now you don’t have grid of 1920 x 1200 but only a grid with a few to a hundred cells instead of millions.

The technique to find the biggest cell can be done with some minor modifications to Nigel’s brute force script. Note: You need to sum up the sizes of the cells in the grid because they are different. To save yourself some unnecessary calculation time you can remove the rectangles first that fits inside another rectangle (or couple of rectangles) as the first step.

Also another thing is that the menu bar is 22 pixels in height and the dock (when is set to permanently up) should be considered as used space as well.

Change the repeat with theWindow in every window of process “AppleScript Editor” into repeat with the window in every window of every process.

Here an better version:

``````set ymin to 22 --height of system menu, retina or not it is always 22 pixels
set xmin to 0 ----you could subtract height of dock from ymax if it is permanent visible

tell application "Finder"
tell bounds of window of the desktop as list to set {xmax, ymax} to {item 3, item 4}
end tell

set windowRects to {} --the array where are used rectangles are in (windows)
set rowOffsets to {ymin, ymax} -- already add the boundaries in it

--fill the windowRects array with the rectangles of the windows. Also fill in the grid lines
tell application "System Events"
repeat with theWindow in (every window of process "AppleScript Editor" whose value of its attribute "AXMinimized" is false) --change this line to whatever group of UI elements you want
set s to theWindow's size
set l to theWindow's position

tell (item 1 of l)
if it is not in columnOffsets and it > xmin and it < xmax then set end of columnOffsets to it
end tell
tell (item 1 of l) + (item 1 of s)
if it is not in columnOffsets and it > xmin and it < xmax then set end of columnOffsets to it
end tell
tell (item 2 of l)
if it is not in rowOffsets and it > ymin and it < ymax then set end of rowOffsets to it
end tell
tell (item 2 of l) + (item 2 of s)
if it is not in rowOffsets and it > ymin and it < ymax then set end of rowOffsets to it
end tell

set end of windowRects to my newRect(item 1 of l, item 2 of l, (item 1 of s) + (item 1 of l), (item 2 of s) + (item 2 of l))
end repeat
end tell

--sort the grid lines from low to high. This needed to build up a grid where the index actually matches the rectangles
set rowOffsets to bubblesort(rowOffsets)
set columnOffsets to bubblesort(columnOffsets)

--now build the grid with the rectangles
set b to item 1 of rowOffsets
set gridView to {}
repeat with h from 1 to (count rowOffsets) - 1
set thisRow to {}
set a to item 1 of columnOffsets
set y to item (h + 1) of rowOffsets
repeat with v from 1 to (count columnOffsets) - 1
set x to item (v + 1) of columnOffsets
set theRect to newRect(a, b, x, y)
repeat with usedRect in windowRects
--check if this rectangles is in a used spot or not.
if theRect's location's xpos â‰¥ usedRect's location's xpos then
if theRect's location's ypos â‰¥ usedRect's location's ypos then
if theRect's endpoint's xpos â‰¤ usedRect's endpoint's xpos then
if theRect's endpoint's ypos â‰¤ usedRect's endpoint's ypos then
set theRect's inUse to true
end if
end if
end if
end if
end repeat
set end of thisRow to theRect
set a to x
end repeat
set b to y
set end of gridView to thisRow
end repeat

--now we have a grid we can start looking for the biggest rectangle the brute force way
set bigRect to newRect(0, 0, 0, 0)
repeat with a from 1 to count gridView
repeat with b from 1 to count item 1 of gridView
set currentRect to newRect(0, 0, 0, 0)
set maxWidth to (count item 1 of gridView) - b + 2
set location of currentRect to location of item b of item a of gridView
set endpoint of currentRect to endpoint of item b of item a of gridView
repeat with x from a to count gridView
repeat with y from b to count item 1 of gridView
log bigRect
if inUse of item y of item x of gridView or y = maxWidth then
set maxWidth to y
exit repeat
end if
set currentRect's endpoint to endpoint of item y of item x of gridView
if getSizeFromRect(currentRect) > getSizeFromRect(bigRect) then copy currentRect to bigRect
end repeat
end repeat
end repeat
end repeat

--just for the show create the a new window in the biggest largest available rectangle on you screen.
tell bigRect to set {a, b, x, y} to {it's location's xpos, it's location's ypos, it's endpoint's xpos, it's endpoint's ypos}
tell application "AppleScript Editor"
set theDoc to make new document
tell theDoc's window to set it's bounds to {a, b, x, y}
end tell

--return the biggest avilable rectangle.
return bigRect

on newPoint(x, y)
return {xpos:x, ypos:y}
end newPoint

on newRect(x, y, h, w)
return {location:newPoint(x, y), endpoint:newPoint(h, w), inUse:false}
end newRect

on getSizeFromRect(rect)
tell rect
return ((it's endpoint's xpos) - (it's location's xpos)) * ((it's endpoint's ypos) - (it's location's ypos))
end tell
end getSizeFromRect

on bubblesort(theList)
script o
property lst : theList
end script

repeat with i from (count theList) to 2 by -1
set a to beginning of o's lst
repeat with j from 2 to i
set b to item j of o's lst
if (a > b) then
set item (j - 1) of o's lst to b
set item j of o's lst to a
else
set a to b
end if
end repeat
end repeat
return o's lst
end bubblesort
``````

Thanks DJ.

``````tell application "System Events"
set theApps to every window of every application process
end tell
``````

→ System Events got an error: Access for assistive devices is disabled.

However, Access for assistive devices is NOT disabled

This works most of the time… although I can’t pinpoint what makes it fail…

``````
set ymin to 22 --height of system menu, retina or not it is always 22 pixels
set xmin to 0 ----you could subtract height of dock from ymax if it is permanent visible

tell application "Finder"
tell bounds of window of the desktop as list to set {xmax, ymax} to {item 3, item 4}
end tell

set windowRects to {} --the array where are used rectangles are in (windows)
set rowOffsets to {ymin, ymax} -- already add the boundaries in it

set myWindows to {}
tell application "System Events"
set theApps to every application process whose visible = true
repeat with anApp in theApps
set appWindows to every window of anApp
repeat with aWindow in appWindows
set end of myWindows to contents of aWindow
end repeat
end repeat
end tell

--fill the windowRects array with the rectangles of the windows. Also fill in the grid lines
tell application "System Events"
repeat with theWindow in myWindows
set s to theWindow's size
set l to theWindow's position

tell (item 1 of l)
if it is not in columnOffsets and it > xmin and it < xmax then set end of columnOffsets to it
end tell
tell (item 1 of l) + (item 1 of s)
if it is not in columnOffsets and it > xmin and it < xmax then set end of columnOffsets to it
end tell
tell (item 2 of l)
if it is not in rowOffsets and it > ymin and it < ymax then set end of rowOffsets to it
end tell
tell (item 2 of l) + (item 2 of s)
if it is not in rowOffsets and it > ymin and it < ymax then set end of rowOffsets to it
end tell

set end of windowRects to my newRect(item 1 of l, item 2 of l, (item 1 of s) + (item 1 of l), (item 2 of s) + (item 2 of l))

end repeat
end tell

--sort the grid lines from low to high. This needed to build up a grid where the index actually matches the rectangles
set rowOffsets to bubblesort(rowOffsets)
set columnOffsets to bubblesort(columnOffsets)

--now build the grid with the rectangles
set b to item 1 of rowOffsets
set gridView to {}
repeat with h from 1 to (count rowOffsets) - 1
set thisRow to {}
set a to item 1 of columnOffsets
set y to item (h + 1) of rowOffsets
repeat with v from 1 to (count columnOffsets) - 1
set x to item (v + 1) of columnOffsets
set theRect to newRect(a, b, x, y)
repeat with usedRect in windowRects
--check if this rectangles is in a used spot or not.
if theRect's location's xpos â‰¥ usedRect's location's xpos then
if theRect's location's ypos â‰¥ usedRect's location's ypos then
if theRect's endpoint's xpos â‰¤ usedRect's endpoint's xpos then
if theRect's endpoint's ypos â‰¤ usedRect's endpoint's ypos then
set theRect's inUse to true
end if
end if
end if
end if
end repeat
set end of thisRow to theRect
set a to x
end repeat
set b to y
set end of gridView to thisRow
end repeat

--now we have a grid we can start looking for the biggest rectangle the brute force way
set bigRect to newRect(0, 0, 0, 0)
repeat with a from 1 to count gridView
repeat with b from 1 to count item 1 of gridView
set currentRect to newRect(0, 0, 0, 0)
set maxWidth to (count item 1 of gridView) - b + 2
set location of currentRect to location of item b of item a of gridView
set endpoint of currentRect to endpoint of item b of item a of gridView
repeat with x from a to count gridView
repeat with y from b to count item 1 of gridView
log bigRect
if inUse of item y of item x of gridView or y = maxWidth then
set maxWidth to y
exit repeat
end if
set currentRect's endpoint to endpoint of item y of item x of gridView
if getSizeFromRect(currentRect) > getSizeFromRect(bigRect) then set bigRect to currentRect
end repeat
end repeat
end repeat
end repeat

--just for the show create the a new window in the biggest largest available rectangle on you screen.
tell bigRect to set {a, b, x, y} to {it's location's xpos, it's location's ypos, it's endpoint's xpos, it's endpoint's ypos}
tell application "AppleScript Editor"
set theDoc to make new document
tell theDoc's window to set it's bounds to {a, b, x, y}
end tell

--return the biggest avilable rectangle.
return bigRect

on newPoint(x, y)
return {xpos:x, ypos:y}
end newPoint

on newRect(x, y, h, w)
return {location:newPoint(x, y), endpoint:newPoint(h, w), inUse:false}
end newRect

on getSizeFromRect(rect)
tell rect
return ((it's endpoint's xpos) - (it's location's xpos)) * ((it's endpoint's ypos) - (it's location's ypos))
end tell
end getSizeFromRect

on bubblesort(theList)
script o
property lst : theList
end script

repeat with i from (count theList) to 2 by -1
set a to beginning of o's lst
repeat with j from 2 to i
set b to item j of o's lst
if (a > b) then
set item (j - 1) of o's lst to b
set item j of o's lst to a
else
set a to b
end if
end repeat
end repeat
return o's lst
end bubblesort
``````

This does not completely fill the empty space:

Weird, that code runs perfectly fine on my machine (behind an SL at the moment). Too bad because the code is easy adjustable and works smooth (fast) so I don’t know how to help you further from this point because the error doesn’t seems to be syntax related.

edit
: code above working now. It should copy the record to bigrect instead of setting it.

I think I found the problem. The script was still treating minimized windows as if they were visible and excluding the space they “should have” used. Thanks for the time guys!

FWIW, here’s a different approach. It involves subtracting each window from a list of available rectangles, returning the remaining free rectangles.

I’ve used ASObjC Runner for the screen to take into account the Dock and menu bar. I’ve also set minimum values for a space to be considered viable.

``````set xMin to 200 -- ignore if width is below this
set yMin to 200 -- ignore if height is below this

-- get screen size (ignore secondary screens)
tell application id "au.com.myriad-com.ASObjC-Runner" -- ASObjC Runner.app
set {theWidth, theHeight} to usable dimensions of screen 1
set {x1, y1} to usable offset of screen 1
end tell
set usableBounds to {x1, y1, x1 + theWidth, y1 + theHeight}
set availableRects to {usableBounds}

set existingWindows to {}
tell application "System Events"
repeat with theWindow in every window of process "AppleScript Editor" --change this line to whatever group of UI elements you want
set {theWidth, theHeight} to theWindow's size
set {x1, y1} to theWindow's position
set end of existingWindows to {x1, y1, x1 + theWidth, y1 + theHeight}
end repeat
end tell

repeat with i from 1 to count of existingWindows
set newList to {}
repeat with j from 1 to count of availableRects
set m to item j of availableRects
set n to item i of existingWindows
set newList to newList & subtractArea_from_(n, m)
end repeat
copy checkSize(xMin, yMin, newList) to availableRects -- remove any that are too small
end repeat

set theBounds to findBiggest(availableRects)
if theBounds = missing value then
display dialog "Not enough room left."
else
tell application "AppleScript Editor"
set theDoc to make new document
tell theDoc's window to set it's bounds to theBounds
end tell
end if

on subtractArea_from_({xMinus1, yMinus1, xMinus2, yMinus2}, {x1, y1, x2, y2})
if xMinus1 > x2 or xMinus2 < x1 or yMinus1 > y2 or yMinus2 < y1 then
-- rects don't intersect, so return full area in list
return {{x1, y1, x2, y2}}
end if
set theResult to {}
if xMinus1 > x1 and xMinus1 < x2 then
set end of theResult to {x1, y1, xMinus1, y2}
end if
if xMinus2 < x2 and xMinus2 > x1 then
set end of theResult to {xMinus2, y1, x2, y2}
end if
if yMinus1 > y1 and yMinus1 < y2 then
set end of theResult to {x1, y1, x2, yMinus1}
end if
if yMinus2 < y2 and yMinus2 > y1 then
set end of theResult to {x1, yMinus2, x2, y2}
end if
return theResult
end subtractArea_from_

on checkSize(xMin, yMin, newList) -- remove any rects that are too small
repeat with i from 1 to count of newList
set oneValue to item i of newList
if (item 3 of oneValue) - (item 1 of oneValue) > xMin and (item 4 of oneValue) - (item 2 of oneValue) > yMin then
set end of newerList to oneValue
end if
end repeat
end checkSize

on findBiggest(newList)
local oneValue, theArea, maxArea, maxValue
set maxArea to 0
set maxValue to missing value
repeat with i from 1 to count of newList
set oneValue to item i of newList
set theArea to ((item 3 of oneValue) - (item 1 of oneValue)) * ((item 4 of oneValue) - (item 2 of oneValue))
if theArea > maxArea then
set maxArea to theArea
set maxValue to oneValue
end if
end repeat
return maxValue
end findBiggest

``````

The problem you mention in your screenshot was because bigrect was a reference to currentrect. currentrect is a variable that keeps expanding it’s range until it fails and will be compared with bigrect (self). I haven’t noticed this because when there is a window in the upper left corner of your screen there is no problem. So to avoid this I’ve replaced the setter for bigrect with a copy command so it’s no longer a reference but an object on it’s own, which I thought it was from the beginning.

I’ve changed the code above so it will ignore miniaturized windows (like i said you can easily change the repeat in system events).

These two versions should take into account windows from all applications and not consider those that are minimized. Thank you Nigel Garvey, DJ Bazzie Wazzie, and Shane Stanley for a great resource!

DJ Bazzie Wazzie’s modified script:

``````set ymin to 22 --height of system menu, retina or not it is always 22 pixels
set xmin to 0 ----you could subtract height of dock from ymax if it is permanent visible

tell application "Finder"
tell bounds of window of the desktop as list to set {xmax, ymax} to {item 3, item 4}
end tell

set windowRects to {} --the array where are used rectangles are in (windows)
set rowOffsets to {ymin, ymax} -- already add the boundaries in it

set myWindows to {}
tell application "System Events"
set theWindows to (every window of (every process whose visible is true) whose value of its attribute "AXMinimized" is false)
repeat with aWindow in theWindows
if contents of aWindow â‰  {} then set myWindows to myWindows & contents of aWindow
end repeat
end tell

--fill the windowRects array with the rectangles of the windows. Also fill in the grid lines
tell application "System Events"
repeat with theWindow in myWindows
set s to theWindow's size
set l to theWindow's position

tell (item 1 of l)
if it is not in columnOffsets and it > xmin and it < xmax then set end of columnOffsets to it
end tell
tell (item 1 of l) + (item 1 of s)
if it is not in columnOffsets and it > xmin and it < xmax then set end of columnOffsets to it
end tell
tell (item 2 of l)
if it is not in rowOffsets and it > ymin and it < ymax then set end of rowOffsets to it
end tell
tell (item 2 of l) + (item 2 of s)
if it is not in rowOffsets and it > ymin and it < ymax then set end of rowOffsets to it
end tell

set end of windowRects to my newRect(item 1 of l, item 2 of l, (item 1 of s) + (item 1 of l), (item 2 of s) + (item 2 of l))
end repeat
end tell

--sort the grid lines from low to high. This needed to build up a grid where the index actually matches the rectangles
set rowOffsets to bubblesort(rowOffsets)
set columnOffsets to bubblesort(columnOffsets)

--now build the grid with the rectangles
set b to item 1 of rowOffsets
set gridView to {}
repeat with h from 1 to (count rowOffsets) - 1
set thisRow to {}
set a to item 1 of columnOffsets
set y to item (h + 1) of rowOffsets
repeat with v from 1 to (count columnOffsets) - 1
set x to item (v + 1) of columnOffsets
set theRect to newRect(a, b, x, y)
repeat with usedRect in windowRects
--check if this rectangles is in a used spot or not.
if theRect's location's xpos â‰¥ usedRect's location's xpos then
if theRect's location's ypos â‰¥ usedRect's location's ypos then
if theRect's endpoint's xpos â‰¤ usedRect's endpoint's xpos then
if theRect's endpoint's ypos â‰¤ usedRect's endpoint's ypos then
set theRect's inUse to true
end if
end if
end if
end if
end repeat
set end of thisRow to theRect
set a to x
end repeat
set b to y
set end of gridView to thisRow
end repeat

--now we have a grid we can start looking for the biggest rectangle the brute force way
set bigRect to newRect(0, 0, 0, 0)
repeat with a from 1 to count gridView
repeat with b from 1 to count item 1 of gridView
set currentRect to newRect(0, 0, 0, 0)
set maxWidth to (count item 1 of gridView) - b + 2
set location of currentRect to location of item b of item a of gridView
set endpoint of currentRect to endpoint of item b of item a of gridView
repeat with x from a to count gridView
repeat with y from b to count item 1 of gridView
log bigRect
if inUse of item y of item x of gridView or y = maxWidth then
set maxWidth to y
exit repeat
end if
set currentRect's endpoint to endpoint of item y of item x of gridView
if getSizeFromRect(currentRect) > getSizeFromRect(bigRect) then copy currentRect to bigRect
end repeat
end repeat
end repeat
end repeat

--just for the show create the a new window in the biggest largest available rectangle on you screen.
tell bigRect to set {a, b, x, y} to {it's location's xpos, it's location's ypos, it's endpoint's xpos, it's endpoint's ypos}
tell application "AppleScript Editor"
set theDoc to make new document
tell theDoc's window to set it's bounds to {a, b, x, y}
end tell

--return the biggest avilable rectangle.
return bigRect

on newPoint(x, y)
return {xpos:x, ypos:y}
end newPoint

on newRect(x, y, h, w)
return {location:newPoint(x, y), endpoint:newPoint(h, w), inUse:false}
end newRect

on getSizeFromRect(rect)
tell rect
return ((it's endpoint's xpos) - (it's location's xpos)) * ((it's endpoint's ypos) - (it's location's ypos))
end tell
end getSizeFromRect

on bubblesort(theList)
script o
property lst : theList
end script

repeat with i from (count theList) to 2 by -1
set a to beginning of o's lst
repeat with j from 2 to i
set b to item j of o's lst
if (a > b) then
set item (j - 1) of o's lst to b
set item j of o's lst to a
else
set a to b
end if
end repeat
end repeat
return o's lst
end bubblesort

``````

Shane Stanley’s modified script:

``````set xMin to 200 -- ignore if width is below this
set yMin to 200 -- ignore if height is below this

-- get screen size (ignore secondary screens)
tell application id "au.com.myriad-com.ASObjC-Runner" -- ASObjC Runner.app
set {theWidth, theHeight} to usable dimensions of screen 1
set {x1, y1} to usable offset of screen 1
end tell
set usableBounds to {x1, y1, x1 + theWidth, y1 + theHeight}
set availableRects to {usableBounds}

set myWindows to {}
tell application "System Events"
set theWindows to (every window of (every process whose visible is true) whose value of its attribute "AXMinimized" is false)
repeat with aWindow in theWindows
if contents of aWindow â‰  {} then set myWindows to myWindows & contents of aWindow
end repeat
end tell

set existingWindows to {}
tell application "System Events"
repeat with theWindow in myWindows
set {theWidth, theHeight} to theWindow's size
set {x1, y1} to theWindow's position
set end of existingWindows to {x1, y1, x1 + theWidth, y1 + theHeight}
end repeat
end tell

repeat with i from 1 to count of existingWindows
set newList to {}
repeat with j from 1 to count of availableRects
set m to item j of availableRects
set n to item i of existingWindows
set newList to newList & subtractArea_from_(n, m)
end repeat
copy checkSize(xMin, yMin, newList) to availableRects -- remove any that are too small
end repeat

set theBounds to findBiggest(availableRects)
if theBounds = missing value then
display dialog "Not enough room left."
else
tell application "AppleScript Editor"
set theDoc to make new document
tell theDoc's window to set it's bounds to theBounds
end tell
end if

on subtractArea_from_({xMinus1, yMinus1, xMinus2, yMinus2}, {x1, y1, x2, y2})
if xMinus1 > x2 or xMinus2 < x1 or yMinus1 > y2 or yMinus2 < y1 then
-- rects don't intersect, so return full area in list
return {{x1, y1, x2, y2}}
end if
set theResult to {}
if xMinus1 > x1 and xMinus1 < x2 then
set end of theResult to {x1, y1, xMinus1, y2}
end if
if xMinus2 < x2 and xMinus2 > x1 then
set end of theResult to {xMinus2, y1, x2, y2}
end if
if yMinus1 > y1 and yMinus1 < y2 then
set end of theResult to {x1, y1, x2, yMinus1}
end if
if yMinus2 < y2 and yMinus2 > y1 then
set end of theResult to {x1, yMinus2, x2, y2}
end if
return theResult
end subtractArea_from_

on checkSize(xMin, yMin, newList) -- remove any rects that are too small
repeat with i from 1 to count of newList
set oneValue to item i of newList
if (item 3 of oneValue) - (item 1 of oneValue) > xMin and (item 4 of oneValue) - (item 2 of oneValue) > yMin then
set end of newerList to oneValue
end if
end repeat
end checkSize

on findBiggest(newList)
local oneValue, theArea, maxArea, maxValue
set maxArea to 0
set maxValue to missing value
repeat with i from 1 to count of newList
set oneValue to item i of newList
set theArea to ((item 3 of oneValue) - (item 1 of oneValue)) * ((item 4 of oneValue) - (item 2 of oneValue))
if theArea > maxArea then
set maxArea to theArea
set maxValue to oneValue
end if
end repeat
return maxValue
end findBiggest
``````

A great extension of this thread would be how to deal with instances when the empty space is too small to be considered viable. Shane uses 200x200.

What is the best way of resizing and positioning all non-minimized windows that keeps each as close to its original size and position, yet makes room for a new window of at least the minimum size ? Nigel, please let me know if this would be best in its own thread.

Well I think you can take a look here.