在10.9上以编程方式启用辅助设备的访问

时间:2013-07-17 07:11:16

标签: objective-c macos cocoa accessibility accessibility-api

我想在10.9上以编程方式启用对辅助设备的访问。在10.8及更低版本中,我使用以下Applescript来启用辅助设备访问:

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

凭借10.9,Apple已将可访问性选项移至系统偏好设置➞安全性&隐私➞隐私➞可访问性。与以前版本的OS X(对所有应用程序使用通用复选框)不同,10.9中的新功能允许用户单独选择哪些应用程序可以控制系统以执行各种脚本功能。

The new system preferences regarding accessibility

Apple尚未向开发人员提供任何API,以编程方式为应用程序启用辅助功能。因此,当应用程序使用辅助功能API时,Mac OS 10.9将提示最终用户权限的对话框以启用辅助功能。此外,用户必须在启用辅助功能后重新启动应用程序。

Default prompt dialog put up by 10.9 OS for Xcode

我们可以使用Applescript或任何其他API在10.9上以编程方式启用对辅助设备的访问吗?任何帮助解决这个问题的人都将不胜感激。

9 个答案:

答案 0 :(得分:41)

这不能解答您的问题,但了解10.9中出现的新API调用并让您显示授权屏幕或绕过它是很好的:

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

传递YES将强制显示授权屏幕,传递NO将默认跳过它。返回值与AXAPIEnabled()返回的值相同,后者在10.9中被弃用。要确保该功能在您的系统上可用,只需将其与NULL

进行比较即可
if (AXIsProcessTrustedWithOptions != NULL) {
    // 10.9 and later
} else {
    // 10.8 and older
}

您需要将ApplicationServices.framework添加到项目中,然后导入.m或.h文件:

#import <ApplicationServices/ApplicationServices.h>

很遗憾,授权屏幕不允许用户直接授权应用程序,它只是打开系统偏好设置的右侧部分。顺便说一句,你可以直接做,而无需通过无用的系统对话:

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

或使用Objective C:

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

这可以与第一个代码段配对,通过将accessibilityEnabled传递给@NO来测试是否kAXTrustedCheckOptionPrompt,同时阻止系统弹出显示,而是直接打开辅助功能偏好设置面板:

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]];
}

答案 1 :(得分:10)

虽然@ user2865860的答案效果很好,但我发布的整个代码示例在10.9上完美地运行以节省其他时间。您需要获得root权限,因此它会提示用户输入密码。

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...
    }
}

答案 2 :(得分:9)

您可以直接编辑TCC.db文件。为了在没有用户交互的情况下安装Divvy,我必须这样做。只需将com.mizage.divvy替换为您的程序。

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

删除条目:

sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "delete from access where client='com.mizage.divvy';"

答案 3 :(得分:9)

我找到了以下代码片段,它在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

答案 4 :(得分:3)

我自己正在努力解决这个问题,经过一番研究后我发现了以下内容:

  1. 黑客攻击sqlite数据库在使用授权服务方面存在主要缺点。首先,这将弹出一个对话框告诉用户应用程序想要安装实用程序帮助程序(即使它只是使用SMJobSubmit启动提交的一次)。其次,它不适用于沙盒应用,因此也不适用于应用商店。

  2. @Max Al Faeakh使用不推荐使用的AuthorizationExecuteWithPrivileges。您需要将launchd与上面的SMJobSubmit一起使用。无论如何,这仍然需要授权。它还需要一个辅助应用程序,如one

  3. 我想最好的方法是使用:

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

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

    并使用例如脚本桥接框架手动打开首选项窗格:

    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];
    

    SBSystemPreferencesPane类来自SBSystemPreferences.h文件,可以生成:

    sdef "/Applications/System Preferences.app" | sdp -fh --basename SBSystemPreferences -o SBSystemPreferences.h
    

答案 5 :(得分:2)

感谢来自@NightFlight的这个shell脚本示例,它们非常有用。我在Python应用程序中使用AppleScript,如下所示:

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

在Python代码中,它对我来说很有用。

编辑(2014年11月7日):

如果您想在AppleScript编辑器中尝试此操作,请使用稍微不同的字符转义符,如下所示:

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

对于10.9之前的Mac OS X,它甚至更简单:

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

只需触摸一个文件即可。上面Python代码中提到的AppleScript也可以用于其他语言。

答案 6 :(得分:2)

要对此进行补充,您实际上可以监视用户是否单击了应用程序的可访问性设置,以便您可以在用户点击后自动重新启动它。

(Swift 5,在Mojave上运行)

读取值(Mojave的正确方法):

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
}

监控更改

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

获取通知后,最好再次阅读特权,因为按照我的经验,通知本身并没有真正起作用。因此,在updatePrivileges()内部,运行readPrivileges()以获取新状态。

您需要延迟,因为更改要花费一些时间才能体现出来。

在监视过程中还需要牢记的另一件事是,将为任何具有不同权限的应用程序触发通知,因此,如果用户授予或撤消其他应用程序,您仍然会收到通知。

此外,当您不再需要观察者时,请不要忘记删除它。

编辑:

来源:Accessbility Testbench by Piddlesoft

答案 7 :(得分:1)

sqlite3“hack”很棒。

我必须使用权限“1,1,1”(无论这意味着什么)来完成这项工作。

请注意,权限组合而非客户端(即程序名称)是唯一的数据库密钥。

答案 8 :(得分:1)

谢谢大家。

我从登录窗口发出以下命令,以确保仅对每个会话所需的项目进行控制:

# 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)"