How can you find which monitor has the menu bar in AppleScript?

Playing with AppleScript I want to manipulate the location of a few windows with position but I'm having an issue defining the monitor. Currently I have several different size and brand monitors I connect to with my MacBook during my travels. The only repeating occurrence with all four monitors I connect to is that the monitor I want to manipulate includes the menu bar.

When testing with do shell script "system_profiler SPDisplaysDataType I'm not sure if the returned text of Main Display: Yes considers the main to be where the menu bar resides from the extraction:

Graphics/Displays:

    Intel Iris Pro:

      Chipset Model: Intel Iris Pro
      Type: GPU
      Bus: Built-In
      VRAM (Dynamic, Max): 1536 MB
      Metal: Supported
      Displays:
        Color LCD:
          Display Type: Built-In Retina LCD
          Resolution: 2880 x 1800 Retina
          Mirror: Off
          Online: Yes
          Rotation: Supported
          Automatically Adjust Brightness: Yes
          Connection Type: DisplayPort
        Thunderbolt Display:
          Display Type: LCD
          Resolution: 2560 x 1440
          Main Display: Yes
          Mirror: Off
          Online: Yes
          Rotation: Supported
          Automatically Adjust Brightness: No
          Connection Type: DisplayPort

If my assumption of the Main Display is correct how should I properly grep the resolution of the identified main display so I can manipulate the windows?

When I research it would appear some commonly just step into each display:

  • Positioning a window with AppleScript using dual monitors
  • Run AppleScript for Specific Monitor
  • Cannot get or set the window size of some apps using AppleScript?
  • Set position of window with AppleScript

So I don't know if this would be two questions in one. I'm somewhat confused on how the sequence is determined when connecting to multiple monitors (such as today I might be connected to a thunderbolt display but tomorrow I might be connected to two Dells monitors through my two DisplayPorts) and if there is a way to target a monitor in particular based on the menu bar.

I can already detect the app and it's size with:

tell application "System Events" to tell application process "Notes" to set theSize to get size of window 1
set theWidth to item 1 of theSize
set theHeight to item 2 of theSize

but my issue falls in determining the resolution so I can calculate the position and bounds to move the app window.


Solution 1:

UPDATE

Removed my original answer. Here is a completely different approach which I prefer over my original solution. This version actually uses “Image Events” scripting addition.

This script will get the names of the displays connected to your computer. The returned values of “display "Display 1" will always be “which monitor has the menu bar in AppleScript?”

property activeDesktop : missing value
property activeDesktopResolution : missing value

set displayNames to {}

tell application "Image Events"
    set theDisplays to displays
    repeat with i from 1 to number of items in theDisplays
        set this_item to item i of theDisplays
        set theName to name of display profile of item i of theDisplays
        set end of displayNames to theName
        set activeDesktop to item 1 of displayNames
    end repeat
end tell

tell application "Finder"
    set activeDesktopResolution to bounds of window of desktop
    set activeDesktopResolution to items 3 thru 4 of activeDesktopResolution
end tell

set theResultz to display dialog activeDesktop & "  " & item 1 of activeDesktopResolution & " x " & item 2 of activeDesktopResolution ¬
    buttons ¬
    "OK" with title ¬
    "Your Current Display and Its Resolution" with icon 1 ¬
    giving up after 10

enter image description here enter image description here enter image description here enter image description here enter image description here

Solution 2:

In my testing, you are correct that the main display is the one with the menu assigned in Displays System Prefs. I tested by moving the menu bar from one display to the other and running your script. The results suggest that wherever the menu bar is, that display will have "Main Display: Yes" in its information.

To parse the resolution, I would use the -xml argument to the system_profiler command to get some consistent text to look for, and then use Apple's text item delimiters to parse the data. I wrote this script, and when I switch the menu back and forth, it returns the resolution of the current main display. You may need to tweak the delimiters depending on what your OS returns for the system profiler report.

I added inline comments in the code below to explain what the script is doing.

set pfl to do shell script "system_profiler -xml SPDisplaysDataType"

set tid to text item delimiters
set text item delimiters to "</data><key>_name</key>"--in my reports, each display section starts with this xml code

ignoring white space --allows us to not worry about returns, tabs, spaces, etc.
    repeat with i from 2 to count of pfl's text items
        set aDisplay to pfl's text item i
        if aDisplay contains "<key>spdisplays_main</key><string>spdisplays_yes</string>" then--check if this display is the main
            set text item delimiters to "<key>_spdisplays_resolution</key><string>"--this is the xml code for the resolution
            set displayRes to aDisplay's text item 2
            set text item delimiters to "</string"--remove the ending xml code
            set displayRes to displayRes's text item 1
        end if
    end repeat
end ignoring
set text item delimiters to tid --always text text item delimiters back to default
return displayRes

EDIT: Because you posted the regular form of the system_profiler above, I re-wrote the script to parse that output. It seems a bit riskier to me than using the xml, but it should compile and run correctly on your system without any tweaks.

set pfl to do shell script "system_profiler SPDisplaysDataType"
set tid to text item delimiters
set text item delimiters to "Displays:"
set pfl to text item 3 of pfl
set text item delimiters to "Mirror:"
repeat with i from 1 to count of pfl's text items
    set aDisplay to pfl's text item i
    ignoring white space
        if aDisplay contains "Main Display: Yes" then
            if aDisplay contains "Resolution:" then
                set text item delimiters to "Resolution: "
                set displayRes to text item 2 of aDisplay
                considering white space
                    set text item delimiters to "\r" --line breaks should be \r
                    set displayRes to text item 1 of displayRes
                    set text item delimiters to "\n" --or they might be \n
                    set displayRes to text item 1 of displayRes
                end considering
            end if
        end if
    end ignoring
    set text item delimiters to "Mirror:"
end repeat
set text item delimiters to tid
return displayRes

Solution 3:

Tested on my MacBook Pro with a Thunderbolt Display attached and the Menu Bar on it, the following worked:

set theMainDisplayName to do shell script "system_profiler SPDisplaysDataType | grep -B 5 'Main Display:' | awk '{sub(/^[ \t]+/, \"\"); print $0; exit}'"
return theMainDisplayName

Result:
"Thunderbolt Display:"

If I switch the Menu Bar to the MacBook Pro, it returns the name of the Display of it, instead of the name of the Thunderbolt Display.

For the Resolution of my Thunderbolt Display:

set theMainDisplayResolution to do shell script "system_profiler SPDisplaysDataType | grep -B 3 'Main Display:' | awk '/Resolution:/{print $2,$3,$4}'"
return theMainDisplayResolution

Result:
"2560 x 1440"

If I switch the Menu Bar to the MacBook Pro, it returns the resolution of the Display of it, instead of the resolution of the Thunderbolt Display.


Also, have a look at my answers to, Script to relocate the menu-bar on dual monitors set-up and Change display arrangement in OS X programmatically for a couple of third-party utilities you might also find useful.

Note: I have no affiliation with the developers of the utilities mentioned and used in my answers in the links above.


Understanding a bit about the shell commands used in the do shell script commands:

Command portion of the do shell script command:

system_profiler SPDisplaysDataType | grep -B 5 'Main Display:' | awk '{sub(/^[ \t]+/, \"\"); print $0; exit}'
  • system_profiler - Reports system hardware and software configuration.
    • SPDisplaysDataType - Generates a text report containing only about Graphics and Displays.
  • | Pipe to direct output of proceeding command to input of next command.
  • grep - File pattern searcher. The grep utility searches any given input files, selecting lines that match one or more patterns.
    • −B num - Print num lines of leading context before each match.
    • 'Main Display:' Pattern to match.
  • | Pipe to direct output of proceeding command to input of next command.
  • awk - Pattern-directed scanning and processing language.
    • sub(/^[ \t]+/, \"\") - Substitute String Function - sub(regexp, replacement) - Used in this instance to remove leading whitespace.
      • ^ - Asserts position at start of the string.
      • [ \t] - Match a single character present in the list.
        • space - Matches the space character literally. Note that space is used here to denote a single space character (ASCII 32).
        • \t - Matches a tab character (ASCII 9).
      • + - Matches between one and unlimited times, as many times as possible, giving back as needed (greedy).
      • \"\" - Replacement is with nothing (""). The "" are escaped, \, so the command can be compiled and passed to the shell. If entered directly on the command line the "" would not need to be escaped.
    • print $0 - Prints the full record. - In this case it prints the fifth line above Main Display:, san the leading whitespace.
    • exit - Exits after printing the first record.

Command portion of the do shell script command:

system_profiler SPDisplaysDataType | grep -B 3 'Main Display:' | awk '/Resolution:/{print $2,$3,$4}'

In this do shell script command the awk '/Resolution:/{print $2,$3,$4}' portion does the following:

The output of e.g.:

$ system_profiler SPDisplaysDataType | grep -B 3 'Main Display:'
          Resolution: 2560 x 1440
          Pixel Depth: 32-Bit Color (ARGB8888)
          Display Serial Number: C08Z4120F6FB
          Main Display: Yes
$

Gets piped to awk and it searches the input for a record containing Resolution: which finds the following record match:

          Resolution: 2560 x 1440

The print $2,$3,$4 command, prints the second, third and fourth fields of the record. The , comma between the $n fields is to insert a space. The output of which is:

2560 x 1440