MacDisplaySettings

>Psychtoolbox>PsychContributed>MacDisplaySettings

[oldSettings,errorMsg] = MacDisplaySettings([screenNumber,][newSettings])

% MacDisplaySettings uses an applescript subroutine of the same name to
allow you to peek and poke seven settings in the macOS System
Preferences:Displays panel. You use corresponding fields in
MacDisplaySettings’s input and output arguments newSettings and
oldSettings. You poke by setting one or more fields in the newSettings
struct, and you peek by looking at fields in the oldSettings struct. This
allows you to temporarily override (and later restore) any user
customization of your display (via the Displays System Preference), so
you can calibrate the display and test users with stable display
settings. (To be clear, we help you handle the macOS built-in System
Preferences; we do nothing about the zillion third-party apps and
extensions that your users might install.) All the parameters refer only
to the screen selected by screenNumber (optional argument with default 0,
the main screen). When you have multiple displays, Apple’s Display panel
seems to always offer the Color Profile options for each display. But the
Display panel’s support for non-Apple displays is limited, so you
probably won’t have Brightness, True Tone, or Night Shift controls for
your external display unless it’s made by Apple.

% DISPLAY
brightness the Brightness slider
automatically the “Automatically adjust brightness” checkbox
trueTone the “True Tone” checkbox
% COLOR
showProfilesForThisDisplayOnly the checkbox
profileRow row # of selection in Color Profile menu
profile name of selection in Color Profile menu
profileFilename name of file
profileFolder path of folder
% NIGHT SHIFT
nightShiftSchedule the Night Shift Schedule pop up menu
nightShiftManual the Night Shift Manual checkbox

% INPUT ARGS. Both arguments are optional: the integer screenNumber
and the struct newSettings. You can provide either, neither, or both.
It’s ok to provide just “newSettings”, omitting the “screenNumber”
(default is 0, the main screen). The struct “newSettings” can include
any number, from none to all, of the eight allowed fields:

“brightness” (in the range 0.0 to 1.0)
“automatically” (true or false)
“trueTone” (true or false)
“nightShiftSchedule” (‘Off’,’Custom’, or ‘Sunset to Sunrise’)
“nighShiftManual” (true or false)
“showProfilesForThisDisplayOnly” (true or false)
“profileRow” (integer)
“profile” (text name)

If newSettings.profileRow is specified then newSettings.profile is
ignored. True Tone is not available on Macs manufactured before 2018.

% OUTPUT ARGS: The struct oldSettings uses the fields listed above to
report the prior state of all available parameters. errorMsg is a second,
optional, output argument. If everything worked then errorMsg is an empty
string. Otherwise it will describe one failure, even if there were
several. In peeking, the fields corresponding to a parameter that could
not be read will be empty [], and that is not considered an error. In
poking, if you got an error (nonempty errorMsg), you might call
MacDisplaySettings again to compare the new peek with what you poked.

% EXAMPLES. Typical uses of MacDisplaySettings include just typing the
function name to learn the current settings:

MacDisplaySettings

ans =

struct with fields:

                     brightness: 0.8700  
                  automatically: 0  
                       trueTone: []  
             nightShiftSchedule: 'Off'  
               nightShiftManual: 0  
 showProfilesForThisDisplayOnly: 0  
                     profileRow: 1  
                        profile: 'Color LCD'  
                profileFilename: 'Color LCD-1FF8F5E7-4684-FAAF-396B-715F8AE0AA7F.icc'  
                  profileFolder: '/Library/[ColorSync](ColorSync)/Profiles/Displays'  

In one call to MacDisplaySettings you can both peek the old settings and
poke new settings.

newSettings.brightness=0.87;
newSettings.automatically=false;
newSettings.trueTone=false;
newSettings.nightShiftSchedule=’Off’;
newSettings.nightShiftManual=false;
newSettings.showProfilesForThisDisplayOnly=false;
newSettings.profileRow=1; % Specify Profile by row.
newSettings.profile=’Display P3’; % Or, select Profile by name.

[oldSettings,errorMsg]=MacDisplaySettings(screen,newSettings)

Now use the display to run your experiment, and then restore the old
settings as you found them. You can omit the “screen” specifier if you’re
working on the main screen (i.e. 0).

MacDisplaySettings(screen,oldSettings);

% PRESERVING THE DISPLAY STATE. Apple invites Macintosh users to adjust
many parameters in the System Preferences Displays panel to customize
their display color and brightness including the enabling of dynamic
adjustments of all displayed images in response to personal preference,
ambient lighting, and time of day. Many users enjoy this, but, unless
reliably overriden, those adjustments defeat our efforts to calibrate the
display one day and, at a later date, use our calibrations to reliably
present an accurately specified stimulus. MacDisplaySettings aims to
satisfy everyone, by allowing your calibration and test programs to use
the computer in a fixed state, unaffected by individual user whims,
ambient lighting, and time of day, while saving and later restoring
whatever custom states the users have selected. MacDisplaySettings
reports and controls seven settings. It allows you to read their current
state, set them to standard values for your critical work, and, when
you’re done, restore them to their original values.

% ERROR REPORTING. If everything worked the optional output argument
errorMsg is an empty string. Otherwise errorMsg will contain an error
message string, just one, even if there are mutiple faults.

% ERROR CHECKING. Most of the controls are straightforward.
You are just peeking and poking a logical value (true or false), a small
integer with a known range, a float with a known range, or a string.
Brightness and Profile are more subtle, so MacDisplaySettings always
checks by peeking immediately after poking Brightness or Profile (whether
by name or by row). A discrepancy will be flagged by a nonempty string in
errorMsg. Note that you provide a float to Brightness but within the
macOS it’s quantized to roughly 18-bit precision. In my testing on a
MacBook and a MacBook Pro, poking random numbers between 0.0 and 1.0, the
discrepancy between peek and poke is uniformly distributed over the range
-5e-6 to +5e-6, provided you wait at least 0.1 s after the latest poke.
(When you move the slider, the macOS does a slow fade to the new value.)
For MacDisplaySettings’s built-in peek-after-poke test,
MacDisplaySettings accepts the peek of Brightness if it is within 0.001
of what we poked, otherwise it waits 0.1 s and peeks again, and if the
second peek is still out of range, then reports the discrepancy in
errorMsg.

% RELIABLE. MacDisplaySettings is reliable,
unlike my previous applescript efforts: AutoBrightness.m, Brightness.m,
and ScreenProfile.m. The improvement results from discovering, first,
that the applescript operations proceed MUCH more quickly while System
Preferences is frontmost (so we now bring it to the front), and, second,
we now follow the example of pros and have wait loops in the applescript
to make sure each object is available before accessing it. With these
enhancements, it now reliably takes 2 s on MacBook Pro and 8 s on
MacBook, instead of the long 60 s delays, and occasional timeout errors,
that afflicted the old routines.

% INPUT ARGUMENT RANGE.
newSettings.brightness has range 0.0 to 1.0; “automatically”, trueTone,
nightShiftManual, and showProfilesForThisDisplayOnly are logical (true or
false); nightShiftSchedule is a text field corresponding to any of the
items in the Displays pop up menu (‘Off’, ‘Custom’, ‘Sunset to Sunrise’).
profile (a text string) specifies the desired Color Profile by name, and
profileRow (an integer) specifies it by row.

% INTERNATIONAL. nightShiftSchedule is compatible with macOS international
localization, provided you use the English field names when calling
MacDisplaySettings.m. The profile row number will work internationally. I
don’t know whether the profile names change with international
localization. Just use whatever profile names you see in the System
Preference: Display panel.

% PROFILE ROW NUMBERING. Note that when you look at the list of profiles
in System Preferences:Displays:Color there is a line separating the top
and bottom sections of the list. Apple assigns a row number to that line,
but trying to select that row has no effect and returns an error in
errorMsg.

% Your screen’s display profile is a video lookup table, it
affects the color and luminance of everything you display. Apple allows
programmers to read and write the current color profile, which is in
memory, and I think that there are several consumer apps that do that (in
much the same spirit as Apple’s Night Shift and TrueTone)). System
Preferences: Displays is unaware of such changes. Clicking on the profile
name that is currently in use has no effect. Clicking on any other
profile causes it to be loaded, fresh from the disk master. So, when you
ask MacDisplaySettings to activate a profile that is already current, it
plays safe and first clicks another profile and then clicks on the one
you specified, to be sure that it loads fresh from disk.

% ERROR REPORTING is strict. Out-of-range or unrecognized arguments
produce fatal errors if detected by MacDisplaySettings.m. When such
errors are detected in MacDisplaySettings.applescript they are merely
flagged by a message in the optional output argument errorMsg. When
throwing a fatal error, if Psychtoolbox is present on the MATLAB path,
then MacDisplaySettings first closes any open windows (by calling
Psychtoolbox “sca”), so the error message won’t be hidden behind your
window.

% REQUIREMENTS: macOS, MATLAB, and Psychtoolbox.
The current version of MacDisplaySettings has only been tested on macOS
Big Sur (11.3) localized for USA. The previous version was extensively
tested on Mojave. Based on earlier testing, I expect this to also
supported macOS 10.9 to 10.14. It’s designed to work internationally, but
that hasn’t been tested yet. It was tested on MATLAB 2019a and 2021a, and
probably works on any version of MATLAB new enough to include structs. I
think, but haven’t checked, that the MATLAB code is pure basic MATLAB (no
toolboxes). We use the Psychtoolbox to get the global rect of the main
screen, and to close all psychtoolbox windows in case of error. The
routine MacDisplaySettings.applescript only needs macOS. It works on any
screen, including an external monitor, but testing with external monitors
has been very limited.

% DEVELOPERS. To write Applescript like this, I strongly recommend that
you buy the Script Debugger app from Late Night Software.
https://latenightsw.com/
and the UI Browser app from UI Browser by PFiddlesoft.
https://pfiddlesoft.com/uibrowser/
The Script Debugger is the best Applescript editor and debugger. The UI
Browser allows you to discover the user interface targets in System
Preferences (or any macOS app) that your script will read and set. With
it you can do in an hour what would otherwise take days of trial and
error.

% APPLE PRIVACY. Unless MATLAB has the needed user-granted
permissions to control the computer, attempts by MacDisplaySettings to
change settings will be blocked by the macOS. The needed permissions
include Accessibility, Full Disk Access, and Automation, all in System
Preferences: Security & Privacy: Privacy. Here are Apple pages on
privacy in general, and accessibility in particular:
https://support.apple.com/guide/mac-help/change-privacy-preferences-on-mac-mh32356/mac
https://support.apple.com/guide/mac-help/allow-accessibility-apps-to-access-your-mac-mh43185/mac
New versions of macOS may demand more permissions. In some cases
MacDisplaySettings will detect the missing permission, open the
appropriate System Preference panel, and provide an error dialog window
asking the user to provide the permission. In other cases
MacDisplaySettings merely prints the macOS error message. The granting of
permission needs to be done only once for your specific MATLAB app. When
you upgrade MATLAB it typically has a new name, and will be treated by
macOS as a new app, requiring new granting of permissions. Only users
with administator privileges can grant permission. When you grant
permission, sometimes the macOS doesn’t seem to notice right away, and
keeps claiming MATLAB lacks permission. It may help to restart MATLAB or
reboot.

% WHAT “BRIGHTNESS” CONTROLS: Adjusting the “brightness” setting in an LCD
controls the luminance of the fluorescent light that is behind the liquid
crystal display. I believe that the “brightness” slider controls only the
luminance of the light source, and does not affect the liquid crystal
itsef, which is driven by the GPU. The luminance at the viewer’s eye is
presumably the product of the two factors: luminance of the source and
transmission of the liquid crystal, at each wavelength.

% MULTIPLE SCREENS: Seems to work, though not yet thoroughly tested.
For each screen, MacDisplaySettings provides access to all the controls
you see in the windows of System Preferences: Displays. However, Apple
provides fewer Display options for external monitors, especially
non-Apple monitors.

% HISTORY
June 25, 2017. denis.pelli@nyu.edu wrote “Brightness” for the
Psychtoolbox, and later “AutoBrightness”.

July 16,2019 Improved by looking at code here:
https://apple.stackexchange.com/questions/272531/dim-screen-brightness-of-mbp-using-applescript-and-while-using-a-secondary-mon/285907

April 2020. Wrote MacDisplaySettings, based on Brightness, but enhanced
to also support Automatically, True Tone, Night Shift, and Profile.

April 14, 2020. Added wait loops (in the applescript) to wait for “tab
group 1” before accessing it. This has nearly eliminated the occasional
time out failures, in which MacDisplaySettings.m returns [] for
brightness and automatic, but returns correct values for night shift.

May 3, 2020. In the Applescript, I now “activate” System Preferences at
the beginning (and reactivate the former app when we exit), and this runs
much faster. Formerly, delays of 60 s were common, with occasional time
outs. Now it reliably takes 2 s on MacBook Pro and 8 s on MacBook.

May 7, 2020. Shortened the help text, reducing redundancy. Check for
unrecognized fields in newSettings. Improved error reporting.

May 8, 2020. Enhanced to support arbitrary screenNumber, i.e. external
monitors.

May 9, 2020. Improved speed (by 30%) by replacing fixed delays in
applescript with wait loops. Enhanced the built-in peek of brightness
afer poking. Now if the peek differs by more than 0.001,
MacDisplaySettings waits 100 ms and tries again, to let the value settle,
as the visual effect is a slow fade. Then it reports in errorMsg if the
new peek differs by more than 0.001. In limited testing, waiting for a
good answer works: the peek-poke difference rarely exceeds +/-5e-6 and
never exceeds 0.001. It’s my impression that if we always waited 100 ms,
then the discrepancy would always be less than +/-5e-6.

May 9, 2020. APPLESCRIPT: Improved speed by replacing fixed delays in
applescript with wait loops. Enhanced the peek of brightness afer poking.
Now if the peek differs by more than 0.001, MacDisplaySettings waits 100
ms and tries again, to let the value settle, as the visual effect is a
slow fade. Then it reports in errorMsg if the new peek differs by more
than 0.001. In limited testing, waiting for a good answer works: the
peek-poke difference rarely exceeds +/-5e-6 and never exceeds 0.001. It’s
my impression that if we always waited 100 ms, then the discrepancy would
always be less than +/-5e-6.

May 14, 2020. Added to Psychtoolbox.

May 15, 2020. Now also pass a flag from MacDisplaySettings.m to
MacDisplaySettings.applescript indicating whether Psychtoolbox has a
window on the main screen. In that case, AppleScript will not try to
show a dialog. Added a loop in AppleScript to wait for System Preferences
window to open; this fixes a rare error.

May 15, 2020. APPLESCRIPT: Added a loop in AppleScript to wait for System
Preferences window to open; this fixes a rare error. Replaced every error
code with a message in errorMsg.

May 17, 2020. Improved handling of empty args, which are now replaced by
default values, just like missing args.

May 20, 2020. APPLESCRIPT: MacDisplaySettings hung up on my student Benji
Luo with the Night Shift panel showing. I suspect it was in an endless
loop waiting for the menu to pop up after a click. I rewrote the loop to
throw an error if the menu doesn’t appear after three attempts of
clicking and waiting up to 500 ms each time.

July 14, 2020. Added two new output fields, settings.profileFilename and
settings.profileFolder, which give the ColorSynch profile’s filename and
folder path. With these, you can use iccread to read the profile.

August 25, 2020. Save and restore current directory. Bug reported by Jan
Kurzawski.

February 17, 2021. Not yet compatible with macOS Catalina. If macOS is
too new we return a dummy result (and print a warning) that hopefully
will allow your code to run.

February 25, 2021. Partially compatible with macOS Catalina. If macOS is
Catalina or newer we print a warning and return a partial result.

May 13, 2021. Now fully compatible with Big Sur (macOS 10.3), and hopefully
also compatible with Catalina. We do not expect the enhancements to have
affected the well-tested compatiblity with earlier versions of macOS, Mojave
and before.

May 14, 2021. Polished the help text, above.

May 24, 2021. Now also compatible with macOS Catalina. Various releases
have been tested on every major version of macOS from 10.9 (Mavericks) to
11.3 (Big Sur). This release has been tested only on Catalina and Big
Sur. The changes are all in the applescript file coping with Apple’s
tendency, with each new major version of macOS, to change the hierarchy
of elements in System Preferences:Displays.

Path   Retrieve current version from GitHub | View changelog
Psychtoolbox/PsychContributed/MacDisplaySettings/MacDisplaySettings.m