42

I want to enable access for assistive devices programatically on 10.9. On 10.8 and lower I was using following Applescript to enable access for assistive devices:

tell application "System Events"
if UI elements enabled is false then
    set UI elements enabled to true
end if
end tell

With 10.9, Apple has moved the accessibility options to System Preferences ➞ Security & Privacy ➞ Privacy ➞ Accessibility. Unlike previous versions of OS X, which used a universal checkbox for all applications, the new functionality in 10.9 allows users to individually choose which apps can gain control of the system to perform their various scripted functions.

The new system preferences regarding accessibility

Apple has NOT provided any API to developers to programmatically enable accessibility for an app. So Mac OS 10.9 will prompt a dialog for end user permission to enable Accessibility when application uses accessibility APIs. Additionally User has to Relaunch the application after enabling Accessibility.

Default prompt dialog put up by 10.9 OS for Xcode

Can we enable access for assistive devices programmatically on 10.9 using Applescript or any other APIs? Any help to fix this issue would be greatly appreciated.

zoul
  • 102,279
  • 44
  • 260
  • 354
Vinpai
  • 1,689
  • 3
  • 16
  • 18
  • 5
    No, there is no way to circumvent the need for visiting this screen. It is one of the operating system's base protections. Any way that is found to circumvent this will almost certainly be patched out. – MobA11y Jul 17 '13 at 13:15
  • @ChrisCM Prompting the user to enable Accessibility for the application and restarting the application is not acceptable solution. – Vinpai Jul 18 '13 at 04:53
  • 2
    I believe this is very intentional behavior that can't be circumvented. When you have access to accessibility you can copy text from text boxes, randomly click things, and just in general do some pretty sketchy stuff. While that stuff is super useful in some applications they don't want it to happen without the user knowing. But this is obviously a side affect of their Sandboxing efforts to make things more 'secure' – Keith Smiley Jul 26 '13 at 22:13
  • @Vinpai In my tests you do not have to restart the application you can call `AXAPIEnabled()` again and it will correctly report the value. – Keith Smiley Jul 26 '13 at 22:14
  • 1
    You can "accept" it or not, but this is the way it is. I agree with the original commenter, if you did find a way to circumvent this, it would be eliminated ASAP. – ipmcc Aug 07 '13 at 12:25
  • 1
    @KeithSmiley: `AXIsTrustedProcess()` will indeed start reporting YES as soon as the checkbox for the app is checked in Security & Privacy Preferences: but in my testing, new event taps will still silently fail to tap keyup/keydown events until the process is restarted. (Which is consistent with how `AXMakeProcessTrusted()` used to work.) – Alun Bestor Oct 08 '13 at 17:35
  • 1) How an app is added into list of apps (figure 1) since there is no "Add" button and 2) how to trigger dialog "MyApp.app would like to control this computer" (figure 2). Thanks! – rjobidon Oct 26 '13 at 17:57
  • 2
    @rjobidon I (finally!) figured out that you can drag an app from you Applications folder into the list. Triggering the "...would like to control this computer" dialog is covered in zoul's answer to this question. – pkamb Oct 26 '13 at 20:22
  • 1
    I cannot add my application to this list. Should it have some special Build Settings in order to be added? I cannot add Xcode also. I unlocked the panel, but dragging the application there does nothing. Can somebody help? – Nava Carmon May 21 '14 at 22:46

9 Answers9

47

This doesn’t answer your question, but it’s good to know about a new API call that appeared in 10.9 and lets you display the authorization screen or bypass it:

NSDictionary *options = @{(id)kAXTrustedCheckOptionPrompt: @YES};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);

Passing YES will force the authorization screen to appear, passing NO will silently skip it. The return value is the same as the one returned by AXAPIEnabled(), which is getting deprecated in 10.9. To make sure that the function is available on your system, just compare it to NULL:

if (AXIsProcessTrustedWithOptions != NULL) {
    // 10.9 and later
} else {
    // 10.8 and older
}

You'll need to add ApplicationServices.framework to your project, and import to your .m or .h file:

#import <ApplicationServices/ApplicationServices.h>

It’s quite a pity that the authorization screen doesn’t let the user to authorize the app directly, it just opens the right part of the System Preferences. Which, by the way, you can do directly without going through the useless system dialogue:

tell application "System Preferences"
    set securityPane to pane id "com.apple.preference.security"
    tell securityPane to reveal anchor "Privacy_Accessibility"
    activate
end tell

or using Objective C:

NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];

This can be paired with the first code snippet to test whether accessibilityEnabled by passing @NO to kAXTrustedCheckOptionPrompt while preventing the system pop-up to appear and instead opening the Accessibility preferences pane directly:

NSDictionary *options = @{(id)kAXTrustedCheckOptionPrompt: @NO};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
if (!accessibilityEnabled) {
    NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
}
notedible
  • 983
  • 7
  • 9
zoul
  • 102,279
  • 44
  • 260
  • 354
  • I thought "AXIsProcessTrustedWithOptions" was only introduced in 10.9? So with the above code, wouldn't you have to be targeting 10.9 at least to get this to compile? Or have it in a conditional code block to target 10.9? – Brad Parks Jul 07 '14 at 01:05
  • ahh.... i see now - weak linking ;-) http://stackoverflow.com/questions/17193066/cocoa-check-if-function-exists/17205070#17205070 – Brad Parks Jul 07 '14 at 01:40
  • @BradParks Can you post the final code you used with weak linking? And could anyone post the correct way to get this to compile with ARC? NOTE: To compile, you'll need to add `ApplicationServices.framework` to your project, and add a `#import ` line to your code. – Stan James Dec 24 '14 at 22:40
  • @StanJames - I don't know for sure if this will do it, but I *think* you can fix it by simply going to the "Build Settings" tab and setting "Base SDK" to "10.9", but leaving "General | Deployment Target" to "10.8"... I came across this when trying to help a guy get his project to compile for 10.8 (https://github.com/sdegutis/mjolnir/issues/156#issuecomment-48133485) – Brad Parks Dec 25 '14 at 02:45
  • Just want to add a note here. If you set AppSandbox YES in entitlements file. The request prompt will not be shown. Read more: https://developer.apple.com/forums/thread/24288 – Tran Quan Dec 13 '20 at 23:53
17

For a native approach I'd recommend against using all the sqlite3 and AppleScript hacks as they might stop working in the future, there's also just a proper api for this.

To add on to this, you can actually monitor if the user clicks the accessibility setting for your app so you can do some actions when the user grants the permission.

(Swift 5, tested on Mojave, Catalina, Big Sur)

reading privileges:

private func readPrivileges(prompt: Bool) -> Bool {
    let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: prompt]
    let status = AXIsProcessTrustedWithOptions(options)
    return status
}

Monitoring for changes in accessibility:

DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name("com.apple.accessibility.api"), object: nil, queue: nil) { _ in
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    self.updatePrivileges()
  }
}

It is best to read the privileges again after getting the notification as the notification itself doesn't really work in my experience. So inside the updatePrivileges(), run readPrivileges() to get the new status.

You need the delay because it takes some time for the changes to be reflected.

Another thing you need to keep in mind while monitoring is that a notification will be fired for any app that gets different permissions, so if the user grants or revokes a different app you'll still get a notification.

Also, don't forget to remove the observer when you don't need it anymore.

Source: Accessbility Testbench by Piddlesoft

JoniVR
  • 1,839
  • 1
  • 22
  • 36
10

While @user2865860's answer works well, I though I'd post the entire code sample that works perfectly on 10.9 to save others some time. You need to get root privileges, so it will prompt a user to enter the password.

char *command= "/usr/bin/sqlite3";
char *args[] = {"/Library/Application Support/com.apple.TCC/TCC.db", "INSERT or REPLACE INTO access  VALUES('kTCCServiceAccessibility','com.yourapp',0,1,0,NULL);", nil};
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status == errAuthorizationSuccess) {
    status = AuthorizationExecuteWithPrivileges(authRef, command, kAuthorizationFlagDefaults, args, NULL);
    AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
    if(status != 0){
        //handle errors...
    }
}
Max Al Farakh
  • 4,386
  • 4
  • 29
  • 56
  • 2
    To help clarify this some, _CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, CONSTRAINT key PRIMARY KEY (service, client, client_type));_ The first 0 is for "client_type," which seems to be 1 if you're referencing the binary, 0 if you're referencing the bundle name. The next is enabled, which should be 1 for us. The final is prompt_count, no idea :) – Joseph Lennox Mar 17 '14 at 01:51
  • 5
    As of Sierra, this no longer works. The database file is now readonly even for root. You can turn off the OS's "System Integrity Protection" (https://support.apple.com/en-us/HT204899) and then you can make that file writable again, but this isn't exactly recommended, and would never make it passed App Review if you tried to get the user to do this in a Mac App. – Cameron E Apr 16 '17 at 06:08
  • There is no primary key so `INSERT or REPLACE into` will always insert a new record. – vaughan Dec 17 '20 at 04:00
10

I have found the following code snippet which properly requests Accessibility permissions in OS X 10.9:

if (AXIsProcessTrustedWithOptions != NULL) {
    // 10.9 and later
    const void * keys[] = { kAXTrustedCheckOptionPrompt };
    const void * values[] = { kCFBooleanTrue };

    CFDictionaryRef options = CFDictionaryCreate(
            kCFAllocatorDefault,
            keys,
            values,
            sizeof(keys) / sizeof(*keys),
            &kCFCopyStringDictionaryKeyCallBacks,
            &kCFTypeDictionaryValueCallBacks);

    return AXIsProcessTrustedWithOptions(options);
}

// OS X 10.8 and older
Sergey L.
  • 21,822
  • 5
  • 49
  • 75
9

You can edit the TCC.db file in directly. I had to do this in order to make Divvy install without user interaction. Just replace com.mizage.divvy with your program.

sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES('kTCCServiceAccessibility','com.mizage.divvy',0,1,1,NULL);" 

To remove the entry:

sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "delete from access where client='com.mizage.divvy';"
zoul
  • 102,279
  • 44
  • 260
  • 354
user2865860
  • 99
  • 1
  • 1
  • 1
    +1, though this wouldn’t work for a sandboxed app distributed through the Mac App Store, right? (It’s useful anyway, I’m just trying to get things clear.) Also, where did you learn about `TCC.db`? – zoul Oct 23 '13 at 06:48
  • does one need to restart? – jamespick Nov 02 '14 at 19:29
  • 2
    No possible anymore since sierra – Silve2611 Jan 23 '17 at 22:10
  • This database has been read only for a while but if you have SIP disabled temporarily you can modify it through the command line in this manner. Your average Apple user SHOULD NOT leave SIP disabled for more than the time it takes to make the change and enable SIP and reboot again. This is most useful if you are automating things like isolated virtual machine build agents in Anka and need to bypass the prompts that no human will see anyways. – dragon788 Apr 12 '19 at 16:23
3

I was struggling with this myself and after a bit of a research I found the following:

  1. Hacking the sqlite DB has the major drawback in using authorization services. First this will pop-up a dialog telling user that an application wants to install a utility helper (even though it is just one off launchd submission using SMJobSubmit). Second, it does not work for sandboxed apps and thus no app store.

  2. @Max Al Faeakh uses AuthorizationExecuteWithPrivileges which is deprecated. You need to use launchd with the above SMJobSubmit. Anyway, this still requires authorization. It also requires an auxiliary application like this one.

I guess the best is to use either:

NSDictionary *options = @{(id)kAXTrustedCheckOptionPrompt: @YES};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);

or

NSDictionary *options = @{(id)kAXTrustedCheckOptionPrompt: @NO};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);

and open preference pane manually using for example scripting bridge framework:

SBSystemPreferencesApplication *prefs = [SBApplication applicationWithBundleIdentifier:@"com.apple.systempreferences"];
[prefs activate];

SBSystemPreferencesPane *pane = [[prefs panes] find:^BOOL(SBSystemPreferencesPane *elem) {
  return [[elem id] isEqualToString:@"com.apple.preference.security"];
}];
SBSystemPreferencesAnchor *anchor = [[pane anchors] find:^BOOL(SBSystemPreferencesAnchor *elem) {
  return [[elem name] isEqualToString:@"Privacy_Accessibility"];
}];

[anchor reveal];

The SBSystemPreferencesPane class comes form a SBSystemPreferences.h file which can be generated:

sdef "/Applications/System Preferences.app" | sdp -fh --basename SBSystemPreferences -o SBSystemPreferences.h
fikovnik
  • 3,473
  • 3
  • 28
  • 29
2

Thanks for this shell script samples from @NightFlight, which are really helpful. I used this with AppleScript in a Python application, like the following:

set sh to "touch /private/var/db/.AccessibilityAPIEnabled && sqlite3 \\"/Library/Application Support/com.apple.TCC/TCC.db\\" \\"INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.godevnode',0,1,0,NULL);\\""
do shell script sh with administrator privileges

It worked well for me in Python code as a string.

Edit (Nov 7, 2014):

If you want to try this in AppleScript Editor, use a slightly different character escape as below:

set sh to "touch /private/var/db/.AccessibilityAPIEnabled && sqlite3 \"/Library/Application Support/com.apple.TCC/TCC.db\" \"INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.godevnode',0,1,0,NULL);\""
do shell script sh with administrator privileges

For Mac OS X before 10.9, it's even simpler:

accessibility_api_file = "/private/var/db/.AccessibilityAPIEnabled"

def __enable_accessibility_api():
    try:
        script = 'do shell script "touch %s" with administrator ' \
                 'privileges' % accessibility_api_file
        result = applescript.AppleScript(script).run()
        log.debug("Tried to enable accessibility api, result=" + result)
        return True
    except applescript.ScriptError as err:
        log.error(str(err))
    return False

Just need to touch one file. The AppleScript mentioned in the Python code above can also be used in other languages.

Jake W
  • 2,788
  • 34
  • 39
  • I copied and pasted this into AppleScript Editor but it says `an unknown token cant go after this identifier` and it highlights `com.` in `com.apple`. can you please share the AppleScript Editor code. Also is this compatible with pre 10.9? – Noitidart Oct 18 '14 at 01:29
  • 1
    It looks like character escape issue. Try this in AppleScript editor: set sh to "touch /private/var/db/.AccessibilityAPIEnabled && sqlite3 \"/Library/Application Support/com.apple.TCC/TCC.db\" \"INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.godevnode',0,1,0,NULL);\"" do shell script sh with administrator privileges – Jake W Nov 07 '14 at 07:14
  • Thanks man very much I'll test this out and report back. I'll open applescript and edit. I'm real busy this week and next week so I'll probably be a couple weeks please. – Noitidart Nov 07 '14 at 07:39
1

Thanks everyone.

I issue the following triggered from the login window to ensure control is given only to the items we want every session:

# Enable Service Accessibility for Textpander and others  
# Clear the acess table.
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "DELETE FROM access"

# Enter the access we wish to have.
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES ('kTCCServiceAccessibility','com.apple.systempreferences',0,1,1,NULL)"
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES ('kTCCServiceAccessibility','de.petermaurer.textpanderdaemon',0,1,1,NULL)"
NightFlight
  • 99
  • 1
  • 4
0

The sqlite3 "hack" is great.

I had to use permissions "1,1,1" (whatever that means) to make this work.

Note that the permission combination, not the client (ie. program name) is the unique database key.

Swati
  • 2,870
  • 7
  • 45
  • 87
a.out
  • 49
  • 3