Enable access for assistive devices programmatically on 10.9

前端 未结 9 2066
庸人自扰
庸人自扰 2020-11-27 11:27

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:

         


        
相关标签:
9条回答
  • 2020-11-27 11:44

    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.

    0 讨论(0)
  • 2020-11-27 11:45

    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';"
    
    0 讨论(0)
  • 2020-11-27 11:50

    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...
        }
    }
    
    0 讨论(0)
  • 2020-11-27 11:51

    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 and Catalina)

    reading values:

    private func readPrivileges(prompt: Bool) -> Bool {
        let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: prompt]
        let status = AXIsProcessTrustedWithOptions(options)
        os_log("Reading Accessibility privileges - Current access status %{public}@", type: .info, String(status))
        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.

    edit:

    Source: Accessbility Testbench by Piddlesoft

    0 讨论(0)
  • 2020-11-27 11:53

    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
    
    0 讨论(0)
  • 2020-11-27 11:56

    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)"
    
    0 讨论(0)
提交回复
热议问题