Window Dimensions

Hi folks.

Just wondering what is the most convenient/best way to get window dimensions to put into my scripts. I often work with browser windows that I want to put on a specific monitor (I have 4 on my Mac Pro), with dimensions and location. Here’s an example:

tell application "Google Chrome"
	activate
	set myw1 to (make new window)
	set the URL of tab 1 of myw1 to "http://hq.local"
	set bounds of myw1 to {1, 1, 1140, 1600}
end tell

It’s a bit of a task getting those dimensions (I don’t always use {1,1} as a TL corner), then setting the proper width, etc., without trial and error.

Is there a better way? I’m sure someone has a tip. Fully appreciated.

Cheers

If you’re looking for the usable bounds of all your screens and are running Yosemite or later, the current wisdom would be to use something like this. It returns a list of records, each record containing an ID number and the usable bounds for a screen (“usable” meaning not impinging on the menu bar or Dock). I only have one screen per computer myself, so apologies if I’ve failed to allow for anything.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

-- Get the usable bounds of all attached screens, avoiding the menu bar and Dock.
on getUsableScreenBounds()
	set |⌘| to current application
	
	set theResult to {}
	set theScreens to |⌘|'s class "NSScreen"'s screens()
	repeat with thisScreen in theScreens
		set screenID to ((thisScreen's deviceDescription())'s valueForKey:("NSScreenNumber")) as integer
		set wholeFrame to thisScreen's frame()
		set visibleFrame to thisScreen's visibleFrame()
		
		-- In High Sierra, the frame rects are returned as lists instead of records, but the functions below work with either.
		set x1 to (|⌘|'s NSMinX(visibleFrame)) as integer
		-- The y origin in NSScreen is at the bottom of the screen. It needs to be converted to an origin at the top for AS.
		set y1 to ((|⌘|'s NSMaxY(wholeFrame)) - (|⌘|'s NSMaxY(visibleFrame))) as integer
		set x2 to (|⌘|'s NSMaxX(visibleFrame)) as integer
		set y2 to (y1 + (|⌘|'s NSHeight(visibleFrame))) as integer
		set end of theResult to {screenID:screenID, usableBounds:{x1, y1, x2, y2}}
	end repeat
	
	return theResult
end getUsableScreenBounds

getUsableScreenBounds()
--> {{screenID:69981780, usableBounds:{0, 23, 2560, 1440}}}

Thanks for the post.

Ya I was thinking something more pragmatic, like a third party app of some sort.

Now I’m just trying to copy the “measure first Chrome window’s bounds” to the clipboard.

I’m confused about what exactly you’re looking for, and I’m not clear if Nigel’s answer helped. It sounded to me more like you were trying to get the current bounds of the Chrome Window?


tell application "System Events"
	tell application process "Google Chrome"
		set frontmost to true
		set currentWindow to window 1
		set windowPositon to the position of currentWindow
		set windowSize to the size of currentWindow
	end tell
end tell

Here is a little script you could use for getting the bounds of all Chrome windows. Set the windows up on screen as you want them positioned and it will output a dialog with the name of the window and its bound values. You could then copy those out to your script.

set dimensionsList to {}
tell application "Google Chrome"
	set windowList to every window
	repeat with win in windowList
		tell application "System Events"
			tell application process "Google Chrome"
				set frontmost to true
				set tabName to name of win
				set windowPosition to the bounds of win
				set lastBound to item 4 of windowPosition as text
				set boundList to ""
				repeat with a in windowPosition
					set thisBound to a as text
					if thisBound is not equal to lastBound then
						set boundList to boundList & thisBound & ", " as text
					else
						set boundList to boundList & thisBound as text
					end if
				end repeat
				set end of dimensionsList to {tabName, boundList}
			end tell
		end tell
	end repeat
	
	set dialogText to ""
	repeat with b in dimensionsList
		set windowName to item 1 of b as text
		set windowBounds to item 2 of b as text
		set dialogText to dialogText & "Window Name: " & windowName & return & "Window Bounds: " & windowBounds & return & return
	end repeat
	
	display dialog dialogText buttons {"Cancel", "OK"} default button "OK"
end tell

If you don’t need it in dialog format and just want to read the values returned then you can eliminate the portion from “set dialogText to""” down.

Nigel;

Could you provide a short code snippet to illustrate how you intended your posted getUsableScreenBounds() routine to be used? I have two screens and would like to be able to resize application windows that I’ve opened on either monitor.

Hi GG.

I’ve only ever had one screen attached to any of my computers, so I wrote the handler in the hope that it would be useful to those with more than one!

It returns a list of records, each record corresponding to a screen and having screenID and usableBounds properties. With my one screen, there’s only one record and I don’t need to know the screen’s ID. I can simply get its usable bounds like this:

set screensInfo to getUsableScreenBounds()
set usableBounds to usableBounds of item 1 of screensInfo
--> {0, 23, 2560, 1440} for my screen.

With more than one screen, you’d need to know or be able to find out the ID(s) of the screen(s) of interest and loop through the list to find the relevant record(s). Obviously the exact form of the search and what it returned would depend on how many screens’ bounds you needed to know, but for a particular screen:

set myScreenID to 69981780 -- Or whatever. You'd need to know this or find it out.
set screensInfo to getUsableScreenBounds()

repeat with thisScreen in screensInfo
	if (thisScreen's screenID is myScreenID) then
		set usableBounds to thisScreen's usableBounds
		exit repeat
	end if
end repeat
usableBounds
--> {0, 23, 2560, 1440} for my screen.

It’s possible, with a different handler, to get the information specifically for the “main” screen, which is described in the Xcode documentation as “the screen object containing the window with the keyboard focus.”

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

on getMainScreenUsableBounds()
	set |⌘| to current application
	
	set mainScreen to |⌘|'s class "NSScreen"'s mainScreen()
	
	set wholeFrame to mainScreen's frame()
	set visibleFrame to mainScreen's visibleFrame()
	
	-- In High Sierra, the frame rects are returned as lists instead of records, but the functions below work with either.
	set x1 to (|⌘|'s NSMinX(visibleFrame)) as integer
	-- The y origin in NSScreen is at the bottom of the screen. It needs to be converted to an origin at the top for AS.
	set y1 to ((|⌘|'s NSMaxY(wholeFrame)) - (|⌘|'s NSMaxY(visibleFrame))) as integer
	set x2 to (|⌘|'s NSMaxX(visibleFrame)) as integer
	set y2 to (y1 + (|⌘|'s NSHeight(visibleFrame))) as integer
	
	return {x1, y1, x2, y2}
end getMainScreenUsableBounds

getMainScreenUsableBounds()
--> {0, 23, 2560, 1440}

Thank you both. The project is now starting to come together.

Nigel;

The routine for getting the usable bounds of the primary monitor is working for a multi-monitor configuration may not be working.

I’m getting the following returned: {-254, 23, 833.0, 540.0}

My primary desktop is my MacBook Pro (Retina) and my secondary screen is a 27 inch Dell monitor. I don’t have the screens mirrored, which may be an issue. (I’m also running Mojave).

I think the routine may be seeing the bounds differently in a multi-monitor setup. The following article seems to shed some light on this:

https://daringfireball.net/2006/12/display_size_applescript_the_lazy_way

Did a bit of reading on the subject of the monitor bounds, then checked it out.

The getMainScreenUsableBounds() routine (as written) will give you the bounds for the screen that has keyboard focus.

When I run it from my built-in retina screen, I get {0, 23, 720.0, 430.0} and get {-254, 23, 833.0, 540.0} from my external (27" Dell) screen. I get a negative for the first item of the secondary monitor, because my screen is in extended vertically, rather than being in mirrored mode. It seems as if the system looks at my two monitors as one big rectangular workspace with a “dead zone” on either side of the smaller (lower) monitor. (By the way; this would explain why the cursor can get hung up in the dead zones. It’s a heck of a thing to get it freed up.)

Sounds like there may be a bit more programming needed to use getMainScreenUsableBounds() and have it work as expected.

I think that the equations (that flip the coordinates) need to be adjusted, if there are two monitors detected in “extended workspace” mode. The adjustments would also need to take into account how the monitors are arranged. The end result would be that the whole workspace would look like one big screen and have one set of coordinates. If the monitors are mirrored, I believe the primary (built-in) monitor takes precedence and it just looks like a one monitor system.

A last important thing to keep in mind… While the system might look at the two monitors as one big workspace, it will only display things properly if the whole window is on one monitor or the other.

There’s something strange going on there. My handlers explicitly coerce all the numbers to integer, so there shouldn’t be any reals in the list. Have you altered any of the code?

Apart from that, as I’ve said above, I only have one monitor attached to my computer and can’t really help with any vagaries in multiple-screen situations.

Sorry about that Nigel. The last two items are the subroutine results divided by 2, so they may have come out as non-integer.

I think I can figure this out myself, providing I know on which which physical monitor the numbers are based i.e. the monitor ID.

Your original script returned the values for all screens. If I were to use that, I could get the usable bounds for the primary monitor. Then I’ll just open the windows on the primary screen, no matter how many monitors there are. Probably the safest solution.

@GG
Seems we been working on parallel projects. Maybe you find some inspiration (copypasta encouraged!) here: https://gitlab.com/therwe/corral-windows.

Hi looking_glass;

Thank you.

I had a quick peek at your code. While I’m not trying to do the widows staking on the primary screen in a generic way, I am trying to do it for the windows of a particular app. Will have a good look at the code. I think I can use it for what I have in mind.

Regards;
Gary

You can filter the app in the following line:

set visibProcs to a reference to (processes whose visible = true and name ≠ "Finder")

Just change ‘≠ “Finder”’ to ‘= “MyApp”’.
And you can throw out all code that treats Finder windows.

Thank you.

My pleasure!