アプリケーションが使用しているデフォルトキーを調べる

一般的な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