The definitive guide to screen resolution for macOS Ventura (13+)

Hi everyone

Apparently, Apple changed the design of com.apple.windowserver.plist and its information about screen sizes and configurations in macOS 13.x (Ventura). With some help from the getScreenResolution() discussion to get the correct plist file. I wrote a displayProperties() function returning properties of all displays connected.

As a result you get a list of display properties, including OriginX and OriginY containing their position relative to the main display. (Whereas the main display has OriginX and OriginY set to 0.0.)

Each display property item holds the following properties:

  • High: Height of the display in points (i.e virtual pixels)
  • Wide: Width of the display in points
  • Scale: Resolution of the display (calculate hardware pixels with points Ă— scale)
  • OriginX: Horizontal position of the display relative to the main display in points
  • OriginY: Vertical position of the display relative to the main display in points
  • Hz: Refresh rate of the display in Hz

And some properties with unkonwn meaning:

  • isLink
  • Depth: Maybe color depth?
  • IsVRR: Maybe true for internal displays?

Here is the function:

set myDisplayProperties to displayProperties()

on displayProperties()
	-- Moritz Zimmer, moritz@zimmer.partners, 31.7.2023
	set monitorConfigs to {}
	set windowserverPrefFilePath to do shell script "find " & (POSIX path of ((path to preferences from user domain as Unicode text) & "ByHost:")) & " -name 'com.apple.windowserver*'"
	tell application "System Events"
		set myDisplayConfigs to DisplayConfig of item 1 of Configs of (item 2 of (get value of property list items of property list file windowserverPrefFilePath))
		-- set logVariable to toString(myDisplayConfigs) of me
		repeat with myDisplayConfigCounter from 1 to length of myDisplayConfigs
			copy (CurrentInfo of item myDisplayConfigCounter of myDisplayConfigs) to end of monitorConfigs
		end repeat
	end tell
	return monitorConfigs
end displayProperties

Please note, that the design of com.apple.windowserver.plist might change in future macOS releases and you probably need to start over reverse engineering its JSON structure to adapt the reference in item 1 of Configs of (item 2 of (get value of property list items of property list file windowserverPrefFilePath) . (You could use mklement0’s logging functions to output the content of the resulting myDisplayConfigs variable.)

Have fun!
Moritz

I reverse engineered Window Server’s Plist even further and happen to learn, that it might be saved to System’s Library in some circumstances and hold not only one, but many screen configurations. (It’s a probably a store for all screens and their configurations ever connected to a Mac.) I already have a new version of the script above taking other plist file locations into account and exposing all the screen configurations and their screen sizes and properties. I have to debug and optimize it a little further. I keep you posted!

So, here we go; this is am extended version of the script reading display properties from Windows Server’s plist. It first tries to read the System’s configurations, then the User’s.

-- getDisplayConfigs() by moritz@zimmer.partners
-- License: CC BY 4.0 https://creativecommons.org/licenses/by/4.0/

set displayConfigs to getDisplayConfigs()
return displayConfigs

on getDisplayConfigs()
	-- Moritz Zimmer, moritz@zimmer.partners, 31.7.2023
	set monitorConfigs to {}
	set windowserverPrefFilePath to do shell script "find " & (POSIX path of (path to preferences from local domain as Unicode text)) & " -name 'com.apple.windowserver*'"
	if windowserverPrefFilePath is missing value or windowserverPrefFilePath is "" then
		set windowserverPrefFilePath to do shell script "find " & (POSIX path of ((path to preferences from user domain as Unicode text) & "ByHost:")) & " -name 'com.apple.windowserver*'"
		set rootNodeWindowserverPref to "DisplaySets"
	else
		set rootNodeWindowserverPref to "DisplayAnyUserSets"
	end if
	-- return "find " & (POSIX path of ((path to preferences from user domain as Unicode text) & "ByHost:")) & " -name 'com.apple.windowserver*'"
	if windowserverPrefFilePath is not missing value and windowserverPrefFilePath is not "" then
		tell application "System Events"
			set myWindowServerPref to (property list items of (property list items of ((property list items of property list file windowserverPrefFilePath) whose name is rootNodeWindowserverPref) whose name is "Configs"))
			try
				set myConfigs to value of item 1 of item 1 of myWindowServerPref
				repeat with myConfig in myConfigs
					set myDiplayConfigs to {}
					repeat with myDisplayConfig in DisplayConfig of myConfig
						copy (CurrentInfo of myDisplayConfig) to end of myDiplayConfigs
					end repeat
					-- return myDiplayConfigs
					copy myDiplayConfigs to end of monitorConfigs
				end repeat
			on error
				-- WIP: Add other heuristics to get screen properties
			end try
		end tell
	else
		-- WIP: Add other heuristics to get screen properties
	end if
	return monitorConfigs
end getDisplayConfigs

As you probably guessed reading the on error clause, I might add other methods to get screen resolutions if we fail to read them from Window Server config later on.

Wow! Digging though Apple’s documentation I found NSScreen exposing all current screens (desktops) and their resolutions and other information including how they are position in relation to the main screen. Here’s the AppleScript using NSScreen to read all connected screen’s resolution and other properties and return them as list of records:

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

set screenRecords to screenRecords(false)
return screenRecords

on screenRecords(mainScreenOnly)
	-- Get the array of screens using Objective-C
	set screens to current application's NSScreen's screens()
	-- Convert the NSScreen objects to AppleScript records
	set screenRecords to {}
	repeat with screen in screens
		set screenBounds to screen's frame()
		set screenVisibleBounds to screen's visibleFrame()
		set screenName to screen's localizedName() as string
		set screenBackingScaleFactor to screen's backingScaleFactor() as string
		if (item 1 of item 1 of screenBounds is 0 and item 2 of item 1 of screenBounds is 0) then
			set screenRecord to {name:screenName, frame:{|left|:item 1 of item 1 of screenBounds, top:item 2 of item 1 of screenBounds, width:item 1 of item 2 of screenBounds, height:item 2 of item 2 of screenBounds}, visibleFrame:{|left|:item 1 of item 1 of screenVisibleBounds, top:item 2 of item 1 of screenVisibleBounds, width:item 1 of item 2 of screenVisibleBounds, height:item 2 of item 2 of screenVisibleBounds}, scaleFactor:screenBackingScaleFactor, isMainScreen:false}
			if mainScreenOnly is true then
				return screenRecord
			end if
		else
			set screenRecord to {name:screenName, frame:{|left|:item 1 of item 1 of screenBounds, top:item 2 of item 1 of screenBounds, width:item 1 of item 2 of screenBounds, height:item 2 of item 2 of screenBounds}, visibleFrame:{|left|:item 1 of item 1 of screenVisibleBounds, top:item 2 of item 1 of screenVisibleBounds, width:item 1 of item 2 of screenVisibleBounds, height:item 2 of item 2 of screenVisibleBounds}, scaleFactor:screenBackingScaleFactor, isMainScreen:false}
		end if
		set end of screenRecords to screenRecord
	end repeat
	return screenRecords
end screenRecords

Every resulting item holds the following properties (i.e. that you can retrieve by get height of frame of item 1 of screenRecords etc.):

  • frame (resolution of the display)
    • top
    • left
    • height
    • width
  • visibleFrame (area available for apps)
    • top
    • left
    • height
    • width
  • isMainScreen
  • name
  • scaleFactor (eg. 2.0 for high resolution displays and 1.0 for normal ones)

That’s probably all you want!

Have fun,
Moritz

1 Like

moritzz. Thanks for the NSScreen script, which works well.

For forum members not familiar with NSScreen, it’s probably worth noting that the point of origin is the lower-left corner of the screen, and that’s why your script returns 0 for the top of the visible screen on my computer. FWIW, the following converts the point of origin of the main screen to the upper-left corner.

use framework "AppKit"
use framework "Foundation"
use scripting additions

set visibleDisplayPositionSize to getVisibleDisplayPositionSize() --> {0, 25, 1920, 1055}

on getVisibleDisplayPositionSize()
	set theScreen to current application's NSScreen's mainScreen()
	set {{x1, y1}, {w1, h1}} to theScreen's frame()
	set {{x2, y2}, {w2, h2}} to theScreen's visibleFrame()
	return {x2 as integer, (h1 - h2 - y2) as integer, w2 as integer, h2 as integer}
end getVisibleDisplayPositionSize
1 Like

Thanks a lot,

@peavine for you input.Your right! I forgot this. I’ll update my script as soon as I have my office setup with more than one display connected available. I will update my function to automatically transform all x and y coordinates from bottom to top.

Cheers,
Moritz