環境設定パネルから常駐プログラムを制御する時の覚え書き、その2。
PFKeyAvailerPrefは、ファンクションキーにアプリケーションや書類を登録して、キー一発で起動できるようにするシステム環境設定パネルです。
その1の動作概要に沿って具体的な処理を記載します。
まずは、「起動からホットキー処理までの流れ」です。
PFKeyAvailerPrefが、リソース中のPFKeyAvailerXd(常駐プログラム)を起動する処理は以下のとおりです。
1 起動からホットキー処理までの流れ
1.1 ユーザーが、システム環境設定上でPFKeyAvailerPrefを起動する。
PFKeyAvailerPrefは、起動されるとデリゲートの mainViewDidLoad で、まず次の処理を行います。
ソース:PFKeyAvailerXPrefPref.m
// バージョンアップ時にデーモンを更新するため、デーモンを一度終了し、plistに従い起動する
if ( [myData1 keyAvailOnOffState] == NSOnState ) {
[self quit_daemon];
[self start_daemon];
}
これは、PFKeyAvailerXd が起動されたままの状態で、PFKeyAvailerPref がバージョンアップ等でシステム環境設定に登録し直された場合に対応するためです。
既に動作しているPFKeyAvailerXdを終了させて(起動してないかもしれませんが)、再度起動させます。これによって、新たに登録された PFKeyAvailerPrefのリソース内のPFKeyAvailerXdが起動することになります。
1.2 ユーザーが、「ファンクションキーを有効にする」のチェックボックスをチェックする。
PFKeyAvailerPrefの「ファンクションキーを有効にする」のチェックボックスがチェックされるとバインドされたアクション myAvailBox: が呼ばれます。
ソース:PFKeyAvailerXPrefPref.m
// ファンクションキー有効/無効チェックボックス
- (IBAction)myAvailBox:(id)sender
{
// 初期設定ファイルに書き込み
[myData1 setKeyAvailOnOffState:[keyAvail state]];
if ( [sender state] == NSOnState ) {
[self start_daemon]; // PFKeyAvailerXdを起動する
[self register_startup_daemon]; // 起動項目にPFKeyAvailerXdを登録する
} else {
[self unregister_startup_daemon]; // 起動項目からPFKeyAvailerXdを削除する
[self quit_daemon]; // PFKeyAvailerXdを終了させる
}
}
まず、チェックボックスがチェックされた(NSOnState)ことを初期設定ファイルに記録します。
次に、チェックされた場合は、PFKeyAvailerXdを起動し、起動項目に登録します(次項以降の処理)。
else 以降は、チェックが外された時の処理です。
1.3 PFKeyAvailerPrefが、リソース中のPFKeyAvailerXd(常駐プログラム)を起動する。
常駐プログラムの起動は、NSTaskのlaunchメソッドを使用してopenコマンドを発行しています。
ソース:PFKeyAvailerXPrefPref.m
// PFKeyAvailerXdを起動する
- (void)start_daemon
{
NSTask* task = [NSTask new]; // タスクを作成
[task setLaunchPath:@"/usr/bin/open"]; // コマンドパスセット
[task setArguments :[NSArray arrayWithObject:[self getPrefFilePath:kDaemon]]]; // 引数セット
[task launch]; // 起動
[task waitUntilExit]; // 終了まで待つ
[task release ];
}
getPrefFilePath: は、引数に応じてPFKeyAvailerPref自身やPFKeyAvailerXdのファイルパスを返却します。
ソース:PFKeyAvailerXPrefPref.m
// userとsystemのPFKeyAvailerPrefのパス
#define USER_PREF_FILE @"~/Library/PreferencePanes/PFKeyAvailerPref.prefPane"
#define SYSTEM_PREF_FILE @"/Library/PreferencePanes/PFKeyAvailerPref.prefPane"
// userとsystemのPFKeyAvailerXd.appパス
#define USER_DAEMON_FILE @"~/Library/PreferencePanes/PFKeyAvailerPref.prefPane/Contents/Resources/PFKeyAvailerXd.app"
#define SYSTEM_DAEMON_FILE @"/Library/PreferencePanes/PFKeyAvailerPref.prefPane/Contents/Resources/PFKeyAvailerXd.app"
// Fileのパスを取得する
- (NSString *)getPrefFilePath:(int)kind
{
NSString *fpath, *target_user, *target_system;
switch (kind) {
case kPref:
target_user = USER_PREF_FILE;
target_system = SYSTEM_PREF_FILE;
break;
case kDaemon:
target_user = USER_DAEMON_FILE;
target_system = SYSTEM_DAEMON_FILE;
break;
default:
return nil;
}
fpath = [NSString stringWithString:target_user];
fpath = [fpath stringByExpandingTildeInPath];
if ( [[NSFileManager defaultManager] fileExistsAtPath: fpath] == NO ) {
fpath = [NSString stringWithString:target_system];
if ( [[NSFileManager defaultManager] fileExistsAtPath: fpath] == NO ) {
fpath = nil;
}
}
return fpath;
}
※システム環境設定パネルは、特定ユーザーのみにインストールされている場合と、すべてのユーザー用にインストールされている場合があるので、順に fileExistsAtPath: メソッドでファイルパスが存在するかをチェックして、存在するファイルパスを返却しています。
1.4 PFKeyAvailerPrefが、ユーザーの起動項目にPFKeyAvailerXdを登録する。
AppleScriptで、カレントユーザーの起動項目にPFKeyAvailerXdを登録します。
ソース:PFKeyAvailerXPrefPref.m
// AppleScriptで起動項目にPFKeyAvailerXdを登録する
- (void)register_startup_daemon
{
NSString *fpath;
NSMutableString *script;
fpath = [self getPrefFilePath:kDaemon];
script = [[[NSMutableString alloc] initWithString:@"tell application \"System Events\"\n make new login item at end with properties {path:\""] autorelease];
[script appendString:fpath];
[script appendString:@"\", hidden:false} \n end tell"];
{
NSDictionary *asErrDic = nil; // エラー情報
NSAppleScript *as = [[[ NSAppleScript alloc ] initWithSource : script ] autorelease]; // 初期化
[ as executeAndReturnError : &asErrDic ]; // 実行
}
}
※起動項目に登録するのはフルパスです。
システム環境設定パネルの場合は、インストール先が固定で、後から変更されることがないので問題ありません。
しかし、通常のアプリケーションは起動項目に登録してから、アプリケーションの位置を(別のフォルダーなどに)移動すると、次回のログイン時にアプリケーションが見つからないことになります。
ここから先は、起動されたPFKeyAvailerXd(常駐プログラム)の処理です。
1.5 起動されたPFKeyAvailerXdは、初期設定ファイルを読み込む。
1.6 PFKeyAvailerXdは、内容に従ってホットキー(ファンクションキー)をシステムに登録する。
ソース:PFKeyAvailerXdAppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Distributed Notification組み込み
// システム環境設定から変更の通知を受け取る
NSString *observedObject = @"com.KyasuSoft.PFKeyAvailerX";
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(callbackWithNotification:)
name:@"PFKeyAvailerX Notification"
object:observedObject];
// 初期設定読み込み
[myData1 loadPref];
// ホットキー用イベントハンドラ組み込み
EventTypeSpec eventTypeSpecList[] ={
{ kEventClassKeyboard, kEventHotKeyPressed }
};
InstallApplicationEventHandler(&hotKeyHandler,
GetEventTypeCount(eventTypeSpecList),
eventTypeSpecList,
self, /* void* userData to hotKeyHandler */
NULL);
// ホットキーをすべて削除
[self unregisterHotKey];
// ホットキーを登録
[self registerHotKey];
// システム環境設定からPFKeyAvailerPrefが削除されたかどうかをチェックためのタイマー
_tmTimer = [NSTimer scheduledTimerWithTimeInterval:CHECK_INTERVAL
target:self
selector:@selector(checkPrefExist)
userInfo:nil
repeats:YES];
}
最初の [center addObserver:] は、PFKeyAvailerPrefから初期設定が変更された通知を受け取るためのオブザーバーの登録です。
通知があると callbackWithNotification: が呼び出されます。
次に、 [myData1 loadPref] で初期設定を読み込み、各ファンクションキー(PF01~PF19)に登録されたファイル名をNSStringの配列で保持します。
次に、ホットキー用のイベントハンドラをシステムに登録します。登録したホットキーイベントが発生すると、hotKeyHandler() がシステムから呼び出されます。
次に、既に登録されているホットキーをすべて(PF01~PF19)削除します。もちろん未登録であれば削除処理はしません。削除は、UnregisterEventHotKey() にhotKeyRefを渡す処理をループさせています。
次に、改めてホットキーを登録します。[self registerHotKey] の中で次の関数をループさせています。
// Make hot key ID
keyId.signature = 'Mky1';
keyId.id = i;
// Register hot key
status = RegisterEventHotKey(keyCode,
modifier,
keyId,
GetApplicationEventTarget(),
0,
&_hotKeyRef[i]);
今回はPF01~PF19の19種類のホットキーを登録します。この区別のために、keyID.id に通し番号を設定してます。
また、ホットキー毎に返却される hotKeyRef(削除の時に必要)を、EventHotKeyRefの配列で保持します。
keyCode は、Events.h で定義されている kVK_F1 ~ kVK_F19 を順に渡します。
modifierは、今回は Shiftキーの併用だけなので、modifier=shiftKey または modifier=0 を渡します。
最後の [NSTimer scheduledTimerWithTimeInterva:] は、繰り返しのタイマー登録で checkPrefExist を呼び出します。
checkPrefExist はシステム環境設定からPFKeyAvailerPrefが削除されたかどうかをチェックするためのメソッドです。
1.7 ユーザーが、ホットキー(ファンクションキー)を押すとシステムからPFKeyAvailerXdに通知され、PFKeyAvailerXdが対応するアプリケーションや書類を起動する。
ユーザーがファンクションキーを押すと、登録されたホットキーイベントが発生し、システムがPFKeyAvailerXdの hotKeyHandler() を呼び出します。
ソース:PFKeyAvailerXdAppDelegate.m
// ホットキーイベント処理
OSStatus hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData)
{
EventHotKeyID hotKeyID;
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL,
sizeof(hotKeyID), NULL, &hotKeyID);
if (hotKeyID.signature == 'Mky1') {
[(id)userData LaunchFiles:hotKeyID.id];
}
return noErr;
}
まず、GetEventParameter() で、登録時に一意にした hotKeyId を取得します。
次に、この hoKeyId の id(PF01~PF19の番号)により、初期設定に登録されたファイルを起動します。