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
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 thespace
character literally. Note thatspace
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 aboveMain 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