6

我正在开发一个应用程序,其中使用全局按键事件将是其操作的要求。此外,我计划通过 App Store 严格分发它。(这是一个 Mac 应用程序,而不是 iOS。)我得到了一个通过 addGlobalMonitorForEventsMatchingMask 监听全局事件的示例,但有一些警告。

注意:我选择使用现代 API,而不是依赖早期的 Carbon 热键方法。如果它们最终被弃用,我不想稍后再解决这个问题。

主要问题是必须信任应用程序才能检测到全局事件。否则,必须为所有应用程序启用可访问性。当我启用可访问性时,会成功检测到事件。此处记录了此要求,https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html

我希望我的用户不必启用可访问性。根据我所做的其他研究,您可以通过调用 AXMakeProcessTrusted,然后重新启动应用程序来获得信任的应用程序。

在我使用的代码中,我没有收到身份验证提示。该应用程序将重新启动,但仍不受信任(可能是因为我没有收到身份验证提示)。这是我这部分的代码:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    if (!AXAPIEnabled() && !AXIsProcessTrusted()) {

        NSString *appPath = [[NSBundle mainBundle] bundlePath];
        AXError error = AXMakeProcessTrusted( (CFStringRef)CFBridgingRetain(appPath) );

        [self restartApp];
    }
}

- (void)restartApp{

    NSTask *task = [[NSTask alloc] init];
    NSMutableArray *args = [NSMutableArray array];
    [args addObject:@"-c"];
    [args addObject:[NSString stringWithFormat:@"sleep %d; open \"%@\"", 3, [[NSBundle mainBundle] bundlePath]]];
    [task setLaunchPath:@"/bin/sh"];
    [task setArguments:args];
    [task launch];
    [NSApp terminate:nil];
}

此外,我在这里查看了授权服务任务的文档https://developer.apple.com/library/archive/documentation/Security/Conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/ TP30000995-CH206-BCIGAIAG

首先让我担心的是这个信息框,“重要的是,应用沙箱中不支持授权服务 API,因为它允许权限升级。”

如果需要此 API 在重新启动应用程序之前获取身份验证提示,则似乎我可能无法在不启用辅助功能的情况下获取全局事件。

总之,我的具体问题是:

  1. 我的示例代码中是否有关于如何显示身份验证提示的错误?

  2. 为了让身份验证提示出现,我是否需要使用授权服务 API?

  3. 是否有可能拥有一个可以访问全局事件的沙盒应用程序?

4

3 回答 3

5

首先,您无法自动允许应用程序使用可在沙盒环境中运行的可访问性 API,从而在应用程序商店中运行。推荐的方法是简单地引导用户,以便他们可以轻松地自己启用它。新的 API 调用AXIsProcessTrustedWithOptions正是为此:

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

现在,对于您的第一个和第二个问题(只是为了完整起见 - 它在沙盒中也不起作用):背后的想法AXMakeProcessTrusted是您实际上创建了一个新的辅助应用程序,您从主应用程序以 root 身份运行。然后,此实用程序调用AXMakeProcessTrusted传入主应用程序的可执行文件。最后,您必须重新启动主应用程序。API 调用在 OSX 10.9 中已被弃用。

要以 root 身份生成新进程,您必须使用launchdusing SMJobSubmit。这将通过身份验证提示提示用户,说明应用程序正在尝试安装帮助工具以及是否应允许。具体来说:

    + (BOOL)makeTrustedWithError:(NSError **)error {
        NSString *label = FMTStr(@"%@.%@", kShiftItAppBundleId, @"mktrusted");
        NSString *command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"mktrusted"];
        AuthorizationItem authItem = {kSMRightModifySystemDaemons, 0, NULL, 0};
        AuthorizationRights authRights = {1, &authItem};
        AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
        AuthorizationRef auth;

        if (AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &auth) == errAuthorizationSuccess) {
           // this is actually important - if from any reason the job was not removed, it won't relaunch
           // to check for the running jobs use: sudo launchctl list
           // the sudo is important since this job runs under root
           SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
           // this is actually the launchd plist for a new process
           // https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist
           NSDictionary *plist = @{
                   @"Label" : label,
                   @"RunAtLoad" : @YES,
                   @"ProgramArguments" : @[command],
                   @"Debug" : @YES
           };
           BOOL ret;
           if (SMJobSubmit(kSMDomainSystemLaunchd, (CFDictionaryRef) plist, auth, (CFErrorRef *) error)) {
               FMTLogDebug(@"Executed %@", command);
               ret = YES;
           } else {
               FMTLogError(@"Failed to execute %@ as priviledged process: %@", command, *error);
               ret = NO;
           }
           // From whatever reason this did not work very well
           // seems like it removed the job before it was executed
           // SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
           AuthorizationFree(auth, 0);
           return ret;
        } else {
           FMTLogError(@"Unable to create authorization object");
           return NO;
        }
    }

至于重新启动,这通常也使用外部实用程序来完成,等待主应用程序完成并再次启动它(通过使用 PID)。如果您使用sparkle 框架,您可以重用现有的框架:

     + (void) relaunch {
         NSString *relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:@"relaunch" ofType:@""];
         NSString *path = [[NSBundle mainBundle] bundlePath];
         NSString *pid = FMTStr(@"%d", [[NSProcessInfo processInfo] processIdentifier]);
         [NSTask launchedTaskWithLaunchPath:relaunch arguments:@[path, pid]];
         [NSApp terminate:self];
    }

另一种选择是破解/Library/Application Support/com.apple.TCC/TCC.dbsqlite 数据库,使用辅助助手手动添加权限:

    NSString *sqlite = @"/usr/bin/sqlite3";
    NSString *sql = FMTStr(@"INSERT or REPLACE INTO access values ('kTCCServiceAccessibility', '%@', 1, 1, 1, NULL);", MY_BUNDLE_ID); 
    NSArray *args = @[@"/Library/Application Support/com.apple.TCC/TCC.db", sql];
    NSTask *task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
    [task waitUntilExit];

但是,这将取消该应用程序成为应用程序商店的资格。更重要的是,它实际上只是一个 hack,并且 db / schema 可以随时更改。一些应用程序(例如,用于执行此操作的 Divvy.app)在应用程序安装程序安装后脚本中使用了此 hack。这样可以防止对话框告诉应用程序正在请求安装辅助工具。

于 2014-06-07T18:45:26.053 回答
1

基本上,MAS 限制将要求您让 tge 用户为所有人打开 AX。

于 2013-08-04T09:00:10.137 回答
0

我在 GitHub 上找到了一个潜在的解决方案。

https://github.com/K8TIY/CW-Station

它有一个辅助应用程序,它将在根目录下运行以请求对主应用程序的访问。它有点过时并且正在使用一些已被弃用的功能,所以我正在努力对其进行现代化改造。这看起来是一个很好的起点。

于 2013-08-04T03:59:43.637 回答