I'd like to be able to set the system preference for cursor size (As seen in the Accessibility Preferences) on a Mac from within my program, then set it back once the program quits.
Is there a way to set the cursor size (specifically) or system preferences in general from an app?
First, if you're just trying to get a larger cursor when the cursor is pointing at your window/view/widget, you're going about this the wrong say. Read Introduction to Cursor Manager for the right way.
Second, even if you think you actually want to set the system-wide cursor while you're program is running, think about it more carefully before you go forward. The cursor will stay large even if your app is in the background, or hidden. If you've made any moves at all toward the transparent lifecycle idea (that a user shouldn't usually notice, or care about, the difference between your app not being visible and your app having quit), this will be even more confusing. If two apps try to do this, what should happen? And so on. (Needless to say, Apple would reject any app from the App Store that did this.)
Third, setting the system preference doesn't actually do anything, until the new time the system reads that preference. And there's no guarantee on when that will happen. So, unless your app is content to change a preference that may not take effect until the user, say, logs out and back in again (and then change it back after you quit), it's not all that useful.
But if this is really what you want to do…
It's very easy to set the system preference. Most of the values modified by System Preferences are in the defaults storage. Most of the values in the Accessibility pane are in the com.apple.universalaccess
domain. The particular key for the cursor size is mouseDriverCursorSize
.
So, to change the cursor to max size from bash:
defaults write com.apple.universalaccess mouseDriverCursorSize 4.0
It's a bit more tedious from ObjC, but something like this (untested):
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *olddict = [defaults persistentDomainForName:@"com.apple.universalaccess"];
NSMutableDictionary *newdict = [olddict mutableCopy];
[newdict setObject:@4.0 forKey:@"mouseDriverCursorSize"];
[defaults setPersistentDomain:newdict forName:@"com.apple.universalaccess"];
[defaults synchronize]
So, what if you want to set the preference, and then force the system to notice the change? Obviously the System Preferences app is doing something, and you could always trace it do see exactly what it does.
More often than not, what it does is call some private function that isn't documented or exposed. And it may be different between different OS versions. And what it does may not be the best thing to do anyway. But from a quick test:
It looks like calling CGSShowCursor
works, as long as it's acceptable to un-hide the cursor if it was hidden. Calling CGSGetGlobalCursorData
twice in a row also seems to work, although I have no idea why it should.
Of course these are CGSPrivate functions that aren't documented or exposed, but at least other people have reverse engineered them, so you don't have to. All you have to do is borrow the code from some open source project (iTerm2 has one of the more complete sets of headers), and test after every new minor OS release from Apple, and debug the black magic that doesn't work for 25% of your users even though it works for the other 75% (without having access to the machines those 25% are getting, and usually without even being able to get decent questions or answers from them).
If you want to trace System Preferences, and you have no experience tracing processes in OS X, the easiest way is through the GUI tool Instruments:
- Launch System Preferences and navigate to Accessibility, Display.
- Launch Xcode 4.4 or later, go to the "Xcode" menu, select "Open Developer Tool", then "Instruments".
- In Instruments, choose the "Mac OS X | All" section, then "System Trace".
- In the "Target" pulldown, attach to the "System Preferences" process.
- Click the "Record" button, and wait a few seconds for it to stop beachballing.
- Drag the Cursor Size slider.
- Click the "Stop" button, and wait even longer for it to finish analyzing.
- Read the extension documentation on Instruments to figure out how to find what you want.
However, keep in mind that System Preferences might not be calling a special syscall to do what it needs; it may be, e.g., sending a specific mach message to the Window Server task. Fortunately, you can walk backward from anything that seems likely. By doing this, I found that it seems to be calling UACursorSetScale
in UniversalAccessCore, which calls UAPreferencesSetValue
in /System/Library/PrivateFrameworks/UniversalAccess.framework/Versions/A/Libraries/libUAPreferences.dylib
, a function which seems to do a CFPreferencesSetValue
and send a CFNotificationCenterPostNotification
. Maybe it's just that notification that matters? You could test that by putting breakpoints on the relevant functions in Xcode/gdb/lldb and seeing what the parameters are. Or you could just figure out how to call UAPreferencesSetValue
yourself (my first guess would be that the params are the same as CFPreferencesSetValue
).
As a quick check: the notification it sends is "UniversalAccessDomainMouseSettingsDidChangeNotification" with a nil
object
and a userInfo
dictionary like @{@"mouseDriverCursorSize": @1.8327533, @"pid": @12345}
to the default distributed notification center, and doing the same thing yourself after changing the NSUserDefaults
preference has no effect. Also, UAPreferencesSetValue
apparently takes different params than CFPreferencesSetValue
, because if you pass the obvious values you get a crash within CFNotificationCenterPostNotification
, so you'll probably need to breakpoint the call in System Preferences to see what it sends.
If you're comfortable moving forward from this start, great. If not, you have a whole lot to learn before you should consider trying to make this work.
Another way to go about this is by scripting. If you can make the System Preferences app do the same thing the mouse makes it do, you're set, right?
As long as UI scripting is enabled (see the "Enable access for assistive devices" checkbox in the same pane you're already looking at in System Preferences, or google how to turn it on and off programmatically if you have root access), that's tedious, but easy, through System Events.
In fact, although System Preferences doesn't expose enough detail to actually change anything, it does expose enough to get us navigated to the right pane, which saves a lot of UI scripting steps. So, here's the AppleScript to do what you want:
tell application "System Preferences"
reveal anchor "Seeing_Display" of pane id "com.apple.preference.universalaccess"
end tell
tell application "System Events"
set theSlider to slider "Cursor Size:" of group 1 of window 1 of application process "System Preferences"
set stash to value of theSlider
set value of theSlider to 4.0
stash
end tell
Run that from ObjC with NSAppleScript
—or, if you prefer, translate it to ScriptingBridge
, Appscript
, or something else that you can run natively—and you're done.