アプリケーションが使用しているデフォルトキーを調べる
一般的なCocoaアプリケーションでは、各アプリケーションのメニューの「環境設定」で設定した値は、ユーザーデフォルトデータベースに格納され、次回起動時にはその値を読み込んで使用することで、アプリケーションの挙動をユーザ毎に変更できるようにしている。
各アプリケーションがどういう設定項目(キー)を持っているのかを調べるにはどうすれば良いだろう?基本的な方法の一つとしては、実際に設定してみて、defaultsコマンド等でその内容を表示するという方法がある。
daphne:~ terazzo$ defaults read com.apple.TextEdit { CheckSpellingWhileTyping = 0; CorrectSpellingAutomatically = 0; Encodings = ( 10, 4, ...
但し、環境設定から変更できないような、隠し設定項目みたいなものがあった場合、この方法では調べることは出来ない。
そこで、SIMBLプラグインを作って、NSUserDefaultsクラスのregisterDefaults:やobjectForKey:などのメソッドの処理を上書きして、キーを取得出来ないかやってみた。
環境はOSバージョンは10.7.2(Lion)、SIMBLのバージョンは0.9.9のものを使用した。あと、良く分からないでやった場合に最悪ログイン出来なくなるかもしれないので、実験用に別ユーザを作って試すか、ssh等でリモートログインできる環境を用意しておくのをオススメする。
参考リンク:
ソースコード
// UserDefaultsExtension.h #import <Foundation/Foundation.h> @interface UserDefaultsExtension: NSObject + (void)replaceMethods; @end
// UserDefaultsExtension.m #import <objc/runtime.h> #import <Foundation/Foundation.h> #import "UserDefaultsExtension.h" @implementation UserDefaultsExtension static IMP imp_registerDefaults; static IMP imp_objectForKey; static BOOL hasInitialized = NO; + (void)initialize { if (!hasInitialized) { [self replaceMethods]; hasInitialized = YES; } } + (void)replaceMethods { Method method_registerDefault = class_getInstanceMethod([NSUserDefaults class], @selector(registerDefaults:)); imp_registerDefaults = method_getImplementation(method_registerDefault); Method method_objectForKey = class_getInstanceMethod([NSUserDefaults class], @selector(objectForKey:)); imp_objectForKey = method_getImplementation(method_objectForKey); method_exchangeImplementations(method_registerDefault, class_getInstanceMethod(self, @selector(registerDefaults:))); method_exchangeImplementations(method_objectForKey, class_getInstanceMethod(self, @selector(objectForKey:))); NSLog(@"UserDefaultsExtension prepared."); } - (void)registerDefaults:(NSDictionary *)registrationDictionary { NSLog(@"UserDefaultsExtension: -registerDefaults:@%@", registrationDictionary); imp_registerDefaults(self, _cmd, registrationDictionary); } - (id)objectForKey:(NSString *)defaultName { NSLog(@"UserDefaultsExtension: -objectForKey:@\"%@\"", defaultName); return imp_objectForKey(self, _cmd, defaultName); } @end
+initializeはクラス使用開始時に呼ばれるクラスメソッド。その中で+replaceMethodsを呼び、NSUserDefaultsの-registerDefaults:と-objectForKey:を自分自身のクラスのメソッドで置換している。
-registerDefaults:と-objectForKey:では、呼ばれた場合にログを吐いた上で元のメソッドを呼んでいる。元の処理を使用する必要があるので、初期化時にimp_registerDefaults、imp_objectForKeyという名前で元のIMPを保持している。(ちなみに「_cmd」はメソッド自身のselectorが入っている暗黙の変数。)
このクラスが読み込まれると、以降NSUserDefaultsに対する-registerDefaults:、-objectForKey:が呼ばる度にログが出力される。出力されたログはConsoleアプリケーションで見ることが出来る。
プロジェクトの作成とインストール
では、参考リンクの先のページを参考にしながらSIMBLバンドルを作成する。
まず、Xcodeで「New」>「New Project...」でプロジェクトを作成。名前は何でも良いけど、今回は「DefaultsLogger」とした。適当にフォルダを決めてプロジェクトを作成する。
次に「New」>「New File...」で上のクラスUserDefaultsExtensionを新規作成して中身を実装する。
SIMBLに呼んでもらえるように、Info.plist(今回ならSupporting Filesの下のDefaultsLogger-Info.plist)に設定を追加する。
... <key>NSPrincipalClass</key> <string>UserDefaultsExtension</string> <key>SIMBLTargetApplications</key> <array> <dict> <key>BundleIdentifier</key> <string>*</string> <key>MaxBundleVersion</key> <string>*</string> <key>MinBundleVersion</key> <string>*</string> </dict> </array> </dict> </plist>
SIMBLTargetApplicationsにはアプリケーションのIdentifierを指定するらしいけど、Maximizerとか見ると「*」を指定しておくと全部のアプリケーションで読んでくれるようだ。(もちろん個別に対象アプリのIdentifierを調べて設定しても良いし、その方が安全。)
クラスファイルが確実にロードされるように、一応NSPrincipalClassにUserDefaultsExtensionを指定している。
素早くデプロイ出来るように、アプリケーションの設定を変更してインストール先を変更しておく。Build SettingsのDeploymentの以下の設定項目を変更する
- Deployment Location (DEPLOYMENT_LOCATION)
- 「YES」に変更
- Installation Buiuld Products Location (DSTROOT)
- 「/」
- Installation Directory (INSTALL_PATH)
- 「$(USER_LIBRARY_DIR)/Application Support/SIMBL/Plugins/」
Dittoというコマンドがエラーを吐くかもしれないので、その場合は適当に「これ足りないよ」って言われたディレクトリを作れば良いっぽい。*1
これでビルドすると、ホームディレクトリの下のLibrary/Application Support/SIMBL/Plugins/の下にDefaultsLogger.bundleというプラグインが出来る。
あと、対象アプリによってはGCの方式が違うと読み込まれなかったりするので、その場合は「Objective-C Garbage Collection」(GCC_ENABLE_OBJC_GC)を 「supported」に変える必要があるかも。
出力
Console.appを起動すると、各アプリが使用しているデフォルトキーが続々と表示……はあんまりされないな……
12/01/10 0:22:31.402 Terminal UserDefaultsExtension prepared. 12/01/10 0:22:31.402 Terminal UserDefaultsExtension: -objectForKey:@"SIMBLLogLevel" 12/01/10 0:22:31.403 Terminal UserDefaultsExtension: -objectForKey:@"SIMBLLogLevel" 12/01/10 0:22:31.403 Terminal UserDefaultsExtension: -objectForKey:@"SIMBLLogLevel" 12/01/10 0:22:31.497 Terminal UserDefaultsExtension: -objectForKey:@"NSPersistentUISnapshotWindows" 12/01/10 0:22:31.508 Terminal UserDefaultsExtension: -objectForKey:@"ShouldLimitRestoreScrollback" 12/01/10 0:22:31.508 Terminal UserDefaultsExtension: -objectForKey:@"RestoreScrollbackLines" 12/01/10 0:22:31.533 Terminal UserDefaultsExtension: -objectForKey:@"ShouldLimitRestoreScrollback" 12/01/10 0:22:31.533 Terminal UserDefaultsExtension: -objectForKey:@"RestoreScrollbackLines" 12/01/10 0:22:31.657 Terminal UserDefaultsExtension: -objectForKey:@"ShouldLimitRestoreScrollback" 12/01/10 0:22:31.659 Terminal UserDefaultsExtension: -objectForKey:@"RestoreScrollbackLines" 12/01/10 0:22:31.660 Terminal UserDefaultsExtension: -objectForKey:@"ShouldLimitRestoreScrollback" 12/01/10 0:22:31.661 Terminal UserDefaultsExtension: -objectForKey:@"RestoreScrollbackLines" 12/01/10 0:22:31.662 Terminal UserDefaultsExtension: -objectForKey:@"ShouldLimitRestoreScrollback" 12/01/10 0:22:31.662 Terminal UserDefaultsExtension: -objectForKey:@"RestoreScrollbackLines" 12/01/10 0:22:31.664 Terminal UserDefaultsExtension: -objectForKey:@"NSTrackMenuValidation" 12/01/10 0:22:31.666 Terminal UserDefaultsExtension: -objectForKey:@"NSOverlayScrollerHideDuration" 12/01/10 0:22:32.751 System Preferences UserDefaultsExtension prepared. 12/01/10 0:22:32.752 System Preferences UserDefaultsExtension: -objectForKey:@"SIMBLLogLevel" 12/01/10 0:22:32.752 System Preferences UserDefaultsExtension: -objectForKey:@"SIMBLLogLevel" 12/01/10 0:22:32.752 System Preferences UserDefaultsExtension: -objectForKey:@"SIMBLLogLevel" 12/01/10 0:22:33.030 System Preferences UserDefaultsExtension: -objectForKey:@"NSQuitAlwaysKeepsWindows" 12/01/10 0:22:33.033 System Preferences UserDefaultsExtension: -objectForKey:@"NSDebugServices" 12/01/10 0:22:33.033 System Preferences UserDefaultsExtension: -objectForKey:@"NSEnableAllServices" 12/01/10 0:22:33.034 System Preferences UserDefaultsExtension: -objectForKey:@"NSAddServiceFilters" 12/01/10 0:22:33.038 System Preferences UserDefaultsExtension: -objectForKey:@"NSTrackMenuValidation" 12/01/10 0:22:33.047 System Preferences UserDefaultsExtension: -objectForKey:@"NSApplicationQuitWithoutSuddenTermination" 12/01/10 0:22:33.061 System Preferences UserDefaultsExtension: -objectForKey:@"NSAutomaticWindowAnimationsEnabled" 12/01/10 0:22:33.062 System Preferences UserDefaultsExtension: -objectForKey:@"NSTerminateAfterLastWindowClosedDelay" 12/01/10 0:22:33.065 System Preferences UserDefaultsExtension: -objectForKey:@"NSAutomaticWindowAnimationsEnabled" 12/01/10 0:22:33.071 Terminal UserDefaultsExtension: -objectForKey:@"NewWindowSettingsBehavior" 12/01/10 0:22:33.071 Terminal UserDefaultsExtension: -objectForKey:@"NewWindowSettingsBehavior" 12/01/10 0:22:33.072 Terminal UserDefaultsExtension: -objectForKey:@"NewTabSettingsBehavior" 12/01/10 0:22:33.075 Terminal UserDefaultsExtension: -objectForKey:@"NewWindowSettingsBehavior" 12/01/10 0:22:33.076 Terminal UserDefaultsExtension: -objectForKey:@"NewWindowSettingsBehavior" 12/01/10 0:22:33.076 Terminal UserDefaultsExtension: -objectForKey:@"NewTabSettingsBehavior" 12/01/10 0:22:33.076 Terminal UserDefaultsExtension: -objectForKey:@"SecureKeyboardEntry" 12/01/10 0:22:33.320 Terminal UserDefaultsExtension: -objectForKey:@"NSCursorProlongCursorLifetimesForCider" 12/01/10 0:22:35.597 Terminal UserDefaultsExtension: -objectForKey:@"ShouldLimitRestoreScrollback" 12/01/10 0:22:35.597 Terminal UserDefaultsExtension: -objectForKey:@"RestoreScrollbackLines" 12/01/10 0:23:40.898 Terminal UserDefaultsExtension: -objectForKey:@"SecureKeyboardEntry" 12/01/10 0:23:40.899 Terminal UserDefaultsExtension: -objectForKey:@"NewWindowSettingsBehavior"
なんかCocoaの仕組みを使っているアプリ自体が少ないのかも……?
*1:自分でやってもなんか再現しないので、もし何も言われなければそれでOK