Mac OS XのGlobal Event Monitorなどの一部のAPIはシステム環境設定にあるアクセシビリティの許可がされていない状態では機能しない。
恐ろしいことにアクセシビリティの許可がされていない状態でGlobal Event MonitorなどのAPI呼び出しを行ってもスルーされるだけでエラーも何も発生しない。 この挙動は確実に半年くらいしてどんな実装だったかを忘れたころにトラブルになり、エラーも出ないのでデバッグも捗らず原因不明のバグに悩まされ、デスマーチ突入は決定的である。
これを回避するためアプリ起動時にアクセシビリティの許可がされているかを確認し、未許可であれば警告を出す必要がある。
目次
実装:TCC.db
アクセシビリティの設定は「/Library/Application Support/com.apple.TCC/TCC.db」というファイルに保存されている。このファイルの中身はSQLite3のデータベースとなっており、sqlite3コマンドで普通に開ける。ただしroot権限が必要となる。
$ sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db
TCC.dbのテーブル構成は以下のようになっている。
sqlite> .schema
CREATE TABLE admin (key TEXT PRIMARY KEY NOT NULL, value INTEGER NOT NULL);
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));
CREATE TABLE access_times (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, last_used_time INTEGER NOT NULL, CONSTRAINT key PRIMARY KEY (service, client, client_type));
CREATE TABLE access_overrides (service TEXT PRIMARY KEY NOT NULL);
このうちaccessというテーブルにアクセシビリティの設定値が保存されている。
select * from access;
実装:ApplicationServicesフレームワークを使用する
ApplicationServicesという標準フレームワークのAPIを叩くことで実現できる。 管理者権限は必要ない。
なお、Mac OS X 10.8(Mountain Lion)以前はAXAPIEnabledメソッドを使用し、Mac OS X 10.9(Mavericks)以降はAXIsProcessTrustedWithOptionsメソッドを使用するようになっており、OSのバージョンにより叩くべきメソッドが異なる。これはアクセシビリティの設定が、Mac OS X 10.8まではOSにひとつの許可設定であったが、Mac OS X 10.9からはアプリ単位の許可設定となったためだと思われる。
Xamarin.MacにはApplicationServicesフレームワーク関連のライブラリが無いようなので直接叩いてやる。
using System;
using System.Runtime.InteropServices;
using MonoMac.Foundation;
namespace PrivacyAccessibility
{
public static class ApplicationServices
{
public const string DllName = "/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices";
[DllImport(DllName)]
public extern static bool AXIsProcessTrustedWithOptions(IntPtr option);
[DllImport(DllName)]
public extern static bool AXAPIEnabled();
public static bool IsCurrentProcessTrusted()
{
var dic = NSDictionary.FromFile(@"/System/Library/CoreServices/SystemVersion.plist");
var osVersion = new Version(dic["ProductVersion"].ToString());
Console.WriteLine(osVersion);
if (new Version(10,9) <= osVersion)
{// 10.9 Mavericks以降
var options = IntPtr.Zero;
return AXIsProcessTrustedWithOptions(options);
}
else
{// 10.8 Mountain Lion以前
return AXAPIEnabled();
}
}
}
}
AXIsProcessTrustedWithOptionsメソッドの引数に「AXTrustedCheckOptionPrompt」に「1」を設定して渡してやると、アプリのアクセシビリティが未許可の場合にシステム環境設定を開くかを確認するダイアログが表示される。なお、ダイアログが表示されても、そこでスレッドは止まらずに即座に戻り値「false」が返されて続行される。
var options = NSDictionary.FromObjectAndKey(new NSNumber(1), new NSString("AXTrustedCheckOptionPrompt"));
AXIsProcessTrustedWithOptions(options.Handle);
また、古いOSの場合は自前でダイアログを表示する必要がある。新旧に対応し、かつ統一感を出すには自前で警告ダイアログを実装した方がいいかもしれない。これらの理由によりアクセシビリティの許可設定の確認と未許可時の警告ダイアログの表示は別々に処理した方が良いと思われる。