日めくりのver.3.4.7を公開しました。
今回の変更点は以下のとおりです。
- メモリーリークしていたのを修正しました。完全ではないかもですが、かなり良くなりました。
- CPUの使用量を減らしました。重い処理は元々ありませんが継続的に動作するアプリなので無駄な動作を極力排しました。
公開したアプリのサポートなど。 Mac OS X(Cocoa)のプログラミング過程でのメモなど。 This site can be translated into various languages using the "translate" option in your web browser.
日めくりのVer.3.4.5をApp Storeで公開しました。
今回の変更点は以下のとおりです。
p.s.
macOS 11では「背景なし」にする時は、システム環境設定->Docとメニューバー->時計で「曜日を表示」を選ぶと良いかなぁと思っています。

以前、複数のタブにColorWellを置いたときは、タブの番号で対象のColorWellを特定していました。今回はこの手が使えないので少し悩みました。@interface CfCalApp : NSObject {
IBOutlet id o_1st_colorWell; // 1個目のcolorWellへのアウトレット
IBOutlet id o_2nd_colorWell; // 2個目のcolorWellへのアウトレット
}
- (void)changeColor:(id)sender
{
NSColor *color = [sender color];
if ( [o_1st_colorWell isActive] ) {
// 1個目のColorWellに対応した処理
}
if ( [o_2nd_colorWell isActive] ) {
// 2個目のColorWellに対応した処理
}
}
環境設定パネルから常駐プログラムを制御する時の覚え書き、その4。
PFKeyAvailerPrefは、ファンクションキーにアプリケーションや書類を登録して、キー一発で起動できるようにするシステム環境設定パネルです。
その1の動作概要に沿って具体的な処理を記載します。
今回は、「終了時の流れ」です。
PFKeyAvailerPrefでファンクションキーをホットキーとして使用するのを停止する時の処理と、PFKeyAvailerPrefをシステム環境設定から削除する時の処理は、以下のとおりです。
3 終了時の流れ
3.1(PFKeyAvailerXdの停止)
PFKeyAvailerPrefの「ファンクションキーを有効にする」のチェックボックスからチェックが外された時の処理です。
ホットキーの登録を削除します。
3.1.1 ユーザーが、システム環境設定上でPFKeyAvailerPref起動する。
3.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を終了させる
}
}
まず、チェックボックスのチェックが外れた(NSOffState)ことを初期設定ファイルに記録します。
次に、else 以降の処理を行います。まず、起動項目からPFKeyAvailerXdを削除し、次にPFKeyAvailerXdを終了させます(次項以降の処理)。 …説明と順番が逆ですが(^^;
3.1.3 PFKeyAvailerPrefが、PFKeyAvailerXdを終了させる。
ソース:PFKeyAvailerXPrefPref.m
// PFKeyAvailerXdを終了させる
- (void)quit_daemon
{
// killallだとデーモンのapplicationWillTerminate:が呼ばれないので、
// AppleScriptでPFKeyAvailerXdを終了させる
NSMutableString *script2 = [[[NSMutableString alloc] initWithString:@"tell application \"PFKeyAvailerXd\"\n Quit\n end tell"] autorelease];
{
NSDictionary *asErrDic = nil; // エラー情報
NSAppleScript *as = [[[ NSAppleScript alloc ] initWithSource : script2 ] autorelease]; // 初期化
[ as executeAndReturnError : &asErrDic ];// 実行
}
}
AppleScriptで、PFKeyAvailerXd に Quit を送り終了させています。
アプリケーションを終了させるのにUnixのkillallコマンドを使うことも出来ますが、killallで終了させるとPFKeyAvailerXdのデリゲートのapplicationWillTerminate: が呼ばれないので、ここではAppleScriptを使用しています。
PFKeyAvailerXdのデリゲートでは、ホットキーの削除処理を行っています。
3.1.4 PFKeyAvailerPrefが、ユーザーの起動項目のPFKeyAvailerXdを削除する。
AppleScriptで、カレントユーザーの起動項目からPFKeyAvailerXdを削除します。
ソース:PFKeyAvailerXPrefPref.m
// AppleScriptで起動項目からPFKeyAvailerXdを削除する
- (void)unregister_startup_daemon
{
NSMutableString *script;
script = [[[NSMutableString alloc] initWithString:@"tell application \"System Events\"\n delete login item \"PFKeyAvailerXd\"\n end tell"] autorelease];
{
NSDictionary *asErrDic = nil; // エラー情報
NSAppleScript *as = [[[ NSAppleScript alloc ] initWithSource : script ] autorelease]; // 初期化
[ as executeAndReturnError : &asErrDic ];// 実行
}
3.2(PFKeyAvailerPrefの削除)
PFKeyAvailerPrefがシステム環境設定から削除された時の処理です。
システム環境設定からの環境設定パネルの削除は、すべてを表示させている状態で削除対象の環境設定パネルを右クリックして、コンテキストメニューから「"PFKeyAvailer"環境設定パネルを取り除く」を実行します。
※ここでのポイントは、上記削除時にはPFKeyAvailerPrefは起動しておらず、自分が削除されたことが認識できない!ということです。
つまり、常駐ファイルであるPFKeyAvailerXdを終了させてくれる人が居なくなるということになります。
このため、PFKeyAvailerXd自身が環境設定パネルが削除されたことを感知して、自分自身を終了させる必要があります。
(もちろん、PFKeyAvailerPrefを削除する前に、チェックボックスからチェックを外してPFKeyAvailerXdを終了すれば問題ないのですが、そこまでユーザーにお願いするのは酷だと思います)
3.2.1 ユーザーが、システム環境設定からPFKeyAvailerPrefを削除する。
3.2.2 PFKeyAvailerXdが常駐していなければ終わり。
3.2.3 PFKeyAvailerXdが常駐している場合は、PFKeyAvailerXdがシステム環境設定からPFKeyAvailerPrefが削除されたことを感知して、ユーザーの起動項目からPFKeyAvailerXdを削除する。
デリゲートの applicationDidFinishLaunching: にシステム環境設定からPFKeyAvailerPrefが削除されたかどうかをチェックするための繰り返しタイマーを登録しておきます。
ソース:PFKeyAvailerXdAppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
~~~~
// システム環境設定からPFKeyAvailerPrefが削除されたかどうかをチェックためのタイマー
_tmTimer = [NSTimer scheduledTimerWithTimeInterval:CHECK_INTERVAL
target:self
selector:@selector(checkPrefExist)
userInfo:nil
repeats:YES];
~~~~
}
タイマーによって、 checkPrefExist が一定時間毎に呼び出されます。
ソース:PFKeyAvailerXdAppDelegate.m
// NSTimerからのイベント処理
- (void)checkPrefExist
{
NSString *fpath;
fpath = [self getPrefFilePath:kPref];
if ( fpath == nil ) {
// AppleScriptで起動項目からPFKeyAvailerXdを削除する
[self unregister_startup_daemon];
// 初期設定をファンクションキー未使用に設定
[myData1 setPref_NoUse];
// 自分自身を終了させる
[NSApp terminate:self];
}
return;
}
まず、PFKeyAvailerPrefのファイルパスを取得します。
[self getPrefFilePath:] では、PFKeyAvailerPrefが特定ユーザーのみにインストールされている場合と、すべのユーザーにインストールされている場合の両方のファイルパスでのPFKeyAvailerPrefの存在確認を[[NSFileManager defaultManager] fileExistsAtPath:fpath] で行い、PFKeyAvailerPrefが存在すればそのフルパスを、無ければnilを返します。
[self getPrefFilePath:] で nil が返却された時は、PFKeyAvailerPrefが存在しない(=システム環境設定から削除された)ため、次の処理を行います。
まず、AppleScriptで起動項目からPFKeyAvailerXdを削除します。
次に、チェックボックスのチェックが外された状態(NSOffState)を初期設定ファイルに記録します。
3.2.4 PFKeyAvailerXd自身を終了させる。
最後に、PFKeyAvailerXd自身を終了させます。
環境設定パネルから常駐プログラムを制御する時の覚え書き、その3。
PFKeyAvailerPrefは、ファンクションキーにアプリケーションや書類を登録して、キー一発で起動できるようにするシステム環境設定パネルです。
その1の動作概要に沿って具体的な処理を記載します。
今回はは、「初期設定の変更時の流れ」です。
PFKeyAvailerPrefで、ユーザーがファンクションキーで起動する対象を変更した時の処理は以下のとおりです。
2 初期設定の変更時の流れ
2.1 ユーザーが、システム環境設定上でPFKeyAvailerPref起動する。
2.2 ユーザーが、ファンクションキーにアプリケーションや書類を登録する。
フィールドへのドロップや、ファイル選択シートによって得られた起動対象ファイルのフルパスをNSStringの配列にファンクションキーの番号に対応させて記憶します。
2.3 PFKeyAvailerPrefが、登録されたアプリケーションや書類を初期設定ファイルに書き込む。
PFKeyAvailerPrefの初期設定ファイルに、ファンクションキー毎に対応する起動対象ファイルのフルパスを書き込みます。
2.4 PFKeyAvailerPrefが、PFKeyAvailerXdに初期設定ファイルが変更されたことを通知する。
NSDistributedNotificationCenter でイベントを送信します。
ソース:PFKeyAvailerXPrefPref.m
NSDistributedNotificationCenter *center = [ NSDistributedNotificationCenter defaultCenter ];
// Distributed Notification(PFKeyAvailerXdにイベントを送信)
[ center postNotificationName : @"PFKeyAvailerX Notification"
object : @"com.KyasuSoft.PFKeyAvailerX"
userInfo : nil
deliverImmediately : YES ];
2.5 通知を受け取ったPFKeyAvailerXdが、初期設定ファイルを読み直しホットキーの登録を変更する。
PFKeyAvailerPrefからの、初期設定変更通知を受け取るためにPFKeyAvailXdは、applicationDidFinishLaunching: でオブザーバーを登録しておきます。
ソース: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];
~~~~
}
PFKeyAvailerPrefがイベントを送信すると、addObserverで登録した callbackWithNotification: が呼び出されます。
callbackWithNotification: では、まず変更のあった初期設定ファイルを読み直します。
次に、既に登録してあるホットキーをすべて削除し、新たに登録し直します。
// システム環境設定から変更の通知
- (void) callbackWithNotification:(NSNotification *)myNotification
{
// 初期設定読み込み
[myData1 loadPref];
// ホットキーをすべて削除
[self unregisterHotKey];
// ホットキーを登録
[self registerHotKey];
}
環境設定パネルから常駐プログラムを制御する時の覚え書き、その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の番号)により、初期設定に登録されたファイルを起動します。