Get sizes of monitor(s) via AppleScript?

asobjc

#1

For many years, I’ve used the dspsizes utility described in this thread to get the sizes of the display (or displays):

https://macscripter.net/viewtopic.php?id=27595

The days of this utility are numbered, because it isn’t 64-bit, and I wasn’t able to recompile it with a 64-bit architecture (possibly because I don’t know what I’m doing).

The author of the utility hasn’t been active on that forum for a while, and I see that I can use AsObjC instead, but I haven’t been able to figure out the syntax. What I’m trying to do is find the size and width of the current monitor or of the largest of multiple monitors and offer the user a different set of options depending on what those numbers are.

Can anyone point the way to getting those numbers in AppleScript?


(Shane Stanley) #2

See this thread:


#3

Shane, That does seem to be a good starting point, but I just found this, which seems to be functionally identical to the capsizes utility (though I have only one monitor, and can’t be certain):

on getResolutions()
	set resolutions to {}
	repeat with p in paragraphs of ¬
		(do shell script "system_profiler SPDisplaysDataType | awk '/Resolution:/{ printf \"%s %s\\n\", $2, $4 }'")
		set resolutions to resolutions & {{word 1 of p as number, word 2 of p as number}}
	end repeat
	# `resolutions` now contains a list of size lists;
	# e.g., with 2 displays, something like {{2560, 1440}, {1920, 1200}}
end getResolutions

(Sorry I haven’t figured out how to format that as a script…)


(Shane Stanley) #4

You format a script by beginning with a line containing three backquote characters, and ending the same way. I’ve edited your post.

But it’s an awfully inefficient way of doing things.

This will give you frame of every screen. The frame is a list of the position in x,y coordinates of the bottom-left, plus the size:

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

set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") as list

One other thing: Your script also returns pixels rather than points, which is probably not what you want.


#5

That’s perfect, Shane. Better than I hoped. Thank you!


(Jim Underwood) #6

Thanks Shane. Very handy.

Is there an easy way in ASObjC to convert the results to a simple list of lists, rather than a list of records? I’m looking for {x,y,width,height}

Thanks.


#7

Perhaps you could get that in the return type, either as a rearranged record, or as a list ?

-- screenXYWH :: Bool -> Bool -> 
--     {X :: Int, Y :: Int, W :: Int, H :: Int}
on screenXYWH(blnActiveScreen, blnVisibleOnly)
    tell current application's NSScreen
        if blnActiveScreen then
            set scr to mainScreen
        else
            set scr to firstObject() of screens()
        end if
    end tell
    tell scr
        if blnVisibleOnly then
            set rec to visibleFrame()
        else
            set rec to frame()
        end if
    end tell
    set {xy, wh} to {origin, |size|} of rec
    return {X:X of xy, Y:Y of xy, W:width of wh, H:height of wh}
end screenXYWH

(Jim Underwood) #8

Yep, it’s not hard using standard AppleScript, I just thought that might be a cool ASObjC trick to collapse a record to a list.

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

set screenList to (current application's NSScreen's screens()'s valueForKey:"frame") as list
repeat with oScreen in screenList
  set {xy, wh} to {origin, |size|} of oScreen
  set contents of oScreen to {X of xy, Y of xy, width of wh, height of wh}
end repeat
-->{{0.0, 0.0, 2048.0, 1152.0}, {2048.0, 0.0, 2048.0, 1152.0}}

(Shane Stanley) #9

I realised some time after I posted that script that it was problematic, and you’ve raised the issue. When run under 10.13, as I am here, it returns {{x, y}, {width, height}}, when it should really be returning the record form you’re seeing. So to be generally useful, a script should be able to handle either.

This is one of the issues I covered here:

In this case, the approach depends a bit on what you want. If you only wanted the dimensions, you could do this:

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

set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") 
repeat with aFrame in allFrames
	set contents of aFrame to {current application's NSWidth(aFrame), current application's NSHeight(aFrame)}
end repeat

If you want{x, y, width, height}, you could use this:

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

set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") as list
repeat with aFrame in allFrames
	set contents of aFrame to {current application's NSWidth(aFrame), current application's NSHeight(aFrame), current application's NSMinX(aFrame), current application's NSMinY(aFrame)}
end repeat

Or a tad more concisely:

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

set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") as list
repeat with aFrame in allFrames
	set contents of aFrame to current application's {NSWidth(aFrame), NSHeight(aFrame), NSMinX(aFrame), NSMinY(aFrame)}
end repeat

And if best performance is imperative, you could probably use something a bit more quick and dirty using record-to-list coercions, like this:

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

set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") as list
repeat with aFrame in allFrames
	set aList to aFrame as list
	set contents of aFrame to ((item 1 of aList) as list) & (item 2 of aList) as list
end repeat
return allFrames

It’s easy to convert a dictionary to an array, but what’s returned here is an AppleScript list. Converting that to a dictionary first would just be extra work. More importantly, whereas coercing a record to a list is reliable in terms of the order of items in AppleScript (in practice, not by definition), that’s not the case with dictionaries/arrays in Objective-C.


#10

As always, many thanks for all the advice here. The script that I’m modifying is designed for people still using 10.8 or 10.9, so I need to use a non-ASObjC solution (I think).

I do need pixels not points, here. At the risk of boring everyone, here is what this is for: the script sets up the SheepShaver emulator, and has an option that writes the size of SheepShaver screen (window) into the SheepShaver prefs file. I show a menu that offers the user a range of sizes - but I want to make sure that the largest size that I offer is one that will fit into the users’s actual monitor. The old dspsizes utility did this very well, but I wasn’t able to build a 64-bit version, so I’ll need a standard AppleScript solution for the next two years or so. Then I’ll stop supporting 10.8 and 10.9 and will use these very cool ASObjC methods, which I’ve written into my script for future reference.


(Shane Stanley) #11

FYI, 10.9 can use ASObjC as long as you confine it to a script library. You can also get pixels either by asking for the screen’s backingScaleFactor() and doing the conversion yourself, or converting a frame something like this:

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

set allScreens to current application's NSScreen's screens() as list
repeat with aScreen in allScreens
	set aFrame to aScreen's frame()
	set pixelFrame to (aScreen's convertRectToBacking:aFrame)
	set contents of aScreen to {current application's NSWidth(pixelFrame), current application's NSHeight(pixelFrame)}
end repeat

#12

I used ASObjC Runner under 10.9, but I’m more or less committed to supporting 10.8 for at least another year. Most of my projects are involved with emulators and other ways to use old software and old formats, so the users I work with aren’t rushing to update things…