Разрешить доступ для вспомогательных устройств программно на 10,9

Я хочу включить доступ для вспомогательных устройств программно на 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 позволяет пользователям индивидуально выбирать, какие приложения могут получить контроль над системой для выполнения их различных скриптовых функций.

Новые возможности настройки в отношении доступности

Apple НЕ предоставляет API для разработчиков, чтобы программно обеспечить доступность приложения. Поэтому Mac OS 10.9 предложит диалоговое окно для разрешения конечного пользователя, чтобы включить Accessibility, когда приложение использует API-интерфейсы специальных возможностей. Кроме того, пользователь должен перезапустить приложение после включения специальных возможностей.

Диалоговое окно приглашения по умолчанию, установленное ОС 10.9 для Xcode

Можем ли мы разрешить доступ для вспомогательных устройств программно на 10.9 с помощью Applescript или любых других API? Любая помощь для решения этой проблемы будет принята с благодарностью.

8 ответов

Решение

Это не отвечает на ваш вопрос, но полезно знать о новом вызове API, появившемся в 10.9, и позволяет отображать экран авторизации или обходить его:

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

или используя Цель C:

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

Это может быть связано с первым фрагментом кода, чтобы проверить, accessibilityEnabled мимоходом @NO в kAXTrustedCheckOptionPrompt при этом не допуская появления всплывающего окна системы и вместо этого непосредственно открывая панель настроек Accessibility:

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

Чтобы добавить к этому, вы можете фактически отслеживать, нажимает ли пользователь настройку доступности для вашего приложения, чтобы вы могли автоматически перезапустить его после того, как пользователь это сделает.

(Swift 5, работает на Мохаве)

чтение значений (правильный путь для Мохаве):

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() чтобы получить новый статус.

Вам нужна задержка, потому что для отражения изменений требуется некоторое время.

Еще одна вещь, которую вы должны иметь в виду при мониторинге, это то, что уведомление будет запущено для любого приложения, которое получает другие разрешения, поэтому, если пользователь предоставит или отменит другое приложение, вы все равно получите уведомление.

Кроме того, не забудьте удалить наблюдателя, когда он вам больше не нужен.

редактировать:

Источник: Accessibility Testbench от Piddlesoft

Я нашел следующий фрагмент кода, который правильно запрашивает разрешения доступа в 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

Хотя ответ @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...
    }
}

Вы можете редактировать файл 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';"

Я боролся с этим сам и после небольшого исследования я обнаружил следующее:

  1. Взлом базы данных sqlite имеет основной недостаток в использовании сервисов авторизации. Сначала появится всплывающее диалоговое окно, сообщающее пользователю, что приложение хочет установить вспомогательный помощник (даже если это только одна отправка при запуске с использованием SMJobSubmit). Во-вторых, он не работает для изолированных приложений и, следовательно, не для магазина приложений.

  2. @Max Al Faeakh использует AuthorizationExecuteWithPrivileges что устарело. Вы должны использовать launchd с вышеупомянутым SMJobSubmit, Во всяком случае, это все еще требует авторизации. Это также требует вспомогательного приложения, как этот.

Я думаю, что лучше всего использовать либо:

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

Спасибо за примеры сценариев оболочки от @NightFlight, которые действительно полезны. Я использовал это с AppleScript в приложении Python, например:

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 в виде строки.

Изменить (7 ноября 2014 г.):

Если вы хотите попробовать это в редакторе 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

Для Mac OS X до 10.9 это еще проще:

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

Просто нужно прикоснуться к одному файлу. AppleScript, упомянутый в приведенном выше коде Python, также может использоваться на других языках.

Спасибо всем.

Я запускаю следующий триггер из окна входа в систему, чтобы обеспечить управление только теми элементами, которые мы хотим видеть в каждом сеансе:

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

Sqlite3 "взломать" отлично.

Я должен был использовать разрешения "1,1,1" (что бы это ни значило), чтобы сделать эту работу.

Обратите внимание, что комбинация разрешений, а не клиент (т.е. имя программы) является уникальным ключом базы данных.

Другие вопросы по тегам