Category Archives: Accessibility

Toggle System Grayscale Mode

A colleague recently asked whether it was possible to connect a custom keyboard shortcut to the system-wide “Use grayscale” setting in the macOS Voiceover system preferences:

Screenshot of macOS preference options for accessibility options inlcuding 'Use grayscale'

I could not find any easy way to do this, and searching the web for solutions revealed that most people are addressing this want by using GUI scripting to automate literally opening System Preferences and clicking the pertinent checkbox.

I thought there must be a way to do this in a more streamlined fashion. Couldn’t the option be automated via AppleScript or something? After some brief research, my conclusion was “no.”

At this point I put on my “hacker hat” and proceeded to analyze the System Preferences code that handles the configuration. It’s a binary in /System/Library/PreferencePanes, and the following Terminal command got me on the right path:

cd /System/Library/PreferencePanes/UniversalAccessPref.prefPane/Contents/MacOS/
nm UniversalAccessPref | grep gray

In short, that means “dump all the symbols (nm) from the VoiceOver preference pane, and search them (grep) for the word ‘gray'”. Here’s what it spits out:

0000000000057210 S _OBJC_IVAR_$_UAPDisplayViewController._grayscaleCheckbox
                 U _UAGrayscaleIsEnabled
                 U _UAGrayscaleKey
                 U _UAGrayscaleSetEnabled

These look to me like exactly the names of functions that the preference pane is calling in order to check the current state, and to set the updated state, of the “Use grayscale” checkbox. The capital “U” stands for “Unimplemented.” I.e. it expects to find these symbols, function names in this case, in another library. But which library?

otool -L UniversalAccessPref

The “otool -L” command will dump all the libraries that the preference pane “links to,” meaning the libraries it expects to load functions or data from. There’s a huge list of frameworks in the output, but the most interesting one to me is:

/System/Library/PrivateFrameworks/UniversalAccess.framework/Versions/A/UniversalAccess

The framework name “UniversalAccess” correlates strongly with the “UA” prefix on the pertinent function names we dug up above. Great, so how do we call these? They’re private system functions which means you should not rely on them for production code, but for a quick hack to make toggling grayscale easier? It’s a reasonable risk in my opinion. Here’s a simple C program that takes advantage of the private methods to simply toggle grayscale mode on or off, depending on the current setting.

If you wanted to assign this functionality to a keystroke, as originally suggested, the easiest way in my opinion is to use an app like my own FastScripts. You could drop the compiled binary above into your ~/Library/Scripts folder, and run it directly from FastScripts. Or, if you don’t want to fuss around with compiling a C program, just copy and paste this AppleScript:

-- Line up a Python script for dynamically loading 
-- the private framework  and invoking the required
-- private methods to get current grayscale mode
-- and set it to the opposite value.
set toggleGrayScript to "python -c 'from ctypes import cdll
lib = cdll.LoadLibrary(\"/System/Library/PrivateFrameworks/UniversalAccess.framework/UniversalAccess\")
lib.UAGrayscaleSetEnabled(lib.UAGrayscaleIsEnabled() == 0)
'"
do shell script toggleGrayScript

This script takes advantage of Python’s ability to dynamically load an arbitrary shared library and invoke its exported functions. I wondered if I might be able to use AppleScript’s own “use framework” functionality but I couldn’t quite figure it out.

Hopefully this has been instructive generally for folks who are interested in hacking at system frameworks, and specifically for folks who were looking for an AppleScript for quickly toggling macOS grayscale mode on and off.

Accessible Frames

I love the macOS system-wide dictionary lookup feature. If you’re not familiar with this, just hold down the Control, Command, and D keys while hovering with the mouse cursor over a word, and the definition appears. What’s amazing about this feature is it works virtually everwyhere on the system, even in apps where the developers made no special effort to support it.

Occasionally, while solving a crossword puzzle in my own Black Ink app, I use this functionality to shed some light on one of the words in a clue. As alluded to above, it “just works” without any special work on my part:

Screen shot of Black Ink's interface, with a highlighted word being defined by macOS system-wide dictionary lookup.

Look carefully at the screenshot above, and you can see that although the dictionary definition for “Smidgen” appears, it seems to have highlighted the word in the wrong location. What’s going on here?

The view in Black Ink that displays the word is a custom NSTextField subclass called RSAutosizingTextField. It employs a custom NSTextFieldCell subclass in order to automatically adjust the font size and display position to best “fill up” the available space. In short: it behaves a lot like UITextField on iOS.

To achieve this behavior, one of the things my custom cell does is override NSTextFieldCell’s drawingRect(forBounds:). This allows me to take advantage of most of NSTextField’s default drawing behavior, but to nudge things a bit as I see fit. It’s this mix of customized and default behaviors that leads to the drawing bug seen above. I’ve overridden the drawing location, but haven’t done anything to override the content hierarchy as it’s reflected by the accessibility frameworks.

What do the accessibility frameworks have to do with macOS system-wide dictionary lookup? A lot. Apparently it’s the “accessibilityFrame” property that dictates not only where the dictionary lookup’s highlighting will be drawn, but also whether the mouse will even be considered “on top of” a visible word or not. So in the screenshot above, if the mouse is hovering over the lower half of the word “Smidgen”, then the dictionary lookup doesn’t even work.

The fix is to add an override to NSTextField’s accessibilityFrame method:

public override func accessibilityFrame() -> NSRect {
	let parentFrame = super.accessibilityFrame()
	guard let autosizingCell = self.cell as? RSAutosizingTextFieldCell else { return parentFrame }

	let horizontalOffset = autosizingCell.horizontalAdjustment(for: parentFrame)

	// If we're flipped (likely for NSTextField, then the adjustments will be inverted from what we
	// want them to be for the screen coordinates this method returns.
	let flippedMultiplier = self.isFlipped ? -1.0 as CGFloat : 1.0 as CGFloat
	let verticalOffset = flippedMultiplier * autosizingCell.verticalAdjustment(for: parentFrame)

	return NSMakeRect(parentFrame.origin.x + horizontalOffset, parentFrame.origin.y + verticalOffset, parentFrame.width, parentFrame.height)
}

Effectively I take the default accessibility frame, and nudge it by the same amount that my custom autosizing text cell is nudging the content during drawing. The result is only subtly difference, but makes a nice visual refinement, and a big improvement to usability:

Screen shot of dictionary lookup UI with properly aligned word focus.

I thought this was an interesting example of the accessibility frameworks being leveraged to provide a service that benefits a very wide spectrum of Mac users. There’s a conventional wisdom about accessibility that emphasizing the accessibility of apps will make the app more usable specifically for users who take advantage of screen readers and other accommodations, but more generally for everybody who uses the app. This is a pretty powerful example of that being the case!