とにかくMacBook(旧式黒)のメニューバーに5秒間隔で書き込みたくてたまらない

MacプログラムでAppKit使ったら負けだと思ってる
理由:アプリの容量およびメモリ使用量は100MBを超え破綻するのは火を見るより明かだ。

ありえねー(苦笑)
と、MacBook(旧式黒)のファンからありえない異音が聞こえてきましたの続編です。
メニューを付けるのではなく広大なメニューバーの余白にCPU温度とFan回転数を定期で記したいだけです。
ので、NSApplicationのdelegate methodを活用して以下をよろしく頼みます。

  • シグナル受け
  • RpmMinMaxControllerインスタンス生成
  • タイマー仕込み
  • StatusBar仕込み(プロパティーで結線)
  • 終了時にはタイマー止め
  • 終了時にはRpmMinMaxControllerのインスタンスを確実に破壊(Fan回転数の上限下限をデフォルト値的な値に戻す)

シグナル云々はInfo.plistに

	<key>LSUIElement</key>
	<true/>

を追記してDockやForce Quit windowに現れないようにする気満々なので登場しています。

#import <Cocoa/Cocoa.h>
#import "RpmMinMaxController.h"

@interface AppDelegate : NSObject {
	NSTimer *theTimer;
	RpmMinMaxController *theRpmMaxController;
}
@end
#import "AppDelegate.h"

static void SIGTERMsHandler(int sig)
{
	[[NSApplication sharedApplication] terminate:nil];
}

@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{	
	struct sigaction sa_sig;
	memset(&sa_sig, 0, sizeof(sa_sig));
	sa_sig.sa_handler = SIGTERMsHandler;
	sa_sig.sa_flags = SA_RESTART;
	if (sigaction(SIGTERM, &sa_sig, NULL) < 0) [[NSApplication sharedApplication] terminate:self];
	if (sigaction(SIGINT , &sa_sig, NULL) < 0) [[NSApplication sharedApplication] terminate:self];

	theRpmMaxController = [[RpmMinMaxController alloc] init];

	theTimer = [theRpmMaxController setTimer];
	[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];

	NSStatusBar *bar = [NSStatusBar systemStatusBar]; 
	theRpmMaxController.stBarItem = [bar statusItemWithLength:NSVariableStatusItemLength]; 
	[theRpmMaxController.stBarItem setTitle:[theRpmMaxController firstTimeStatus]];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
	if (theTimer) [theTimer invalidate];
	if (theRpmMaxController) [theRpmMaxController release];
}
@end


タイマー発火時に実行されるメソッド内で以下のようにしてメニューバー上のCPU温度とFan回転数の表示を書き換えます

	[stBarItem setTitle:[NSString stringWithFormat:@"%.0f%drpm", t, rpmActual]];

以下は毎度おなじみの?ほぼhttp://d.hatena.ne.jp/bootblack/20090610/p1です
プロパティと上記のメニューバー(に定義されたステータスメニュー?)書き込み行が追加されています。
deallocも生きです。
(放置した問題:5秒は長いのか短いのか=毎周期SMCOpen()/SMCClose()すべきか最初と最後だけでよいのか)

2009.7.25版

#import <Cocoa/Cocoa.h>
#import <asl.h>
#import "smc.h"

@interface RpmMinMaxController : NSObject {
	NSStatusItem *stBarItem;
	int		rpmMax;
	int		rpmMin;
	double	old_t;
}
- (NSString *)firstTimeStatus;
- (NSTimer *)setTimer;

@property (retain) NSStatusItem *stBarItem;
@end
#import "RpmMinMaxController.h"

static const double BoilingPoint_t			= 70.0;
static const double secondBoilingPoint_t		= 80.0;
static const double thirdBoilingPoint_t		= 85.0;
static const double DropPoint_t			= 55.0;
static int LowestRpm = 2800; //less than 4500.

@implementation RpmMinMaxController
- (void)SetFanRpmMax:(int)rpm
{
	kern_return_t result = SMCSetFanRpm("F0Mx", rpm);
	if (result == kIOReturnSuccess) {
		rpmMax = rpm;
	} else {
		asl_log(NULL, NULL, ASL_LEVEL_ERR, "Error: rpmMax = %d set failed.", rpm);
	}
}

- (void)SetFanRpmMin:(int)rpm
{
	kern_return_t result = SMCSetFanRpm(SMC_KEY_FAN0_RPM_MIN, rpm);
	if (result == kIOReturnSuccess) {
		rpmMin = rpm;
	} else {
		asl_log(NULL, NULL, ASL_LEVEL_ERR, "Error: rpmMin = %d set failed.", rpm);
	}
}

- (NSTimer *)setTimer
{
	return [NSTimer	scheduledTimerWithTimeInterval:5.0
					target:self
					selector:@selector(RpmMinMaxController_main)
					userInfo:nil
					repeats:YES];						
}

 -(void)dealloc
{
	SMCOpen();
	if (rpmMin != LowestRpm) [self SetFanRpmMin:LowestRpm];
	if (rpmMax != 6200) [self SetFanRpmMax:6200];
	SMCClose();
	asl_log(NULL, NULL, ASL_LEVEL_ERR, "RpmMinMaxController dealloc");

	[stBarItem release];
	[super dealloc];
}

- (NSString *)firstTimeStatus
{
	SMCOpen();
	int rpmActual = SMCGetFanRpm(SMC_KEY_FAN0_RPM_CUR);
	SMCClose();

	return [NSString stringWithFormat:@"%.0f%drpm", old_t, rpmActual];
}

- (void)SetFanRpmLow
{
	[self SetFanRpmMin:LowestRpm];
	[self SetFanRpmMax:4500];
}

- (id)init
{
	self = [super init];
	if (self != nil) {
		SMCOpen();
		[self SetFanRpmLow];
		old_t = SMCGetTemperature(SMC_KEY_CPU_TEMP);
		SMCClose();
	}
	return self;
}

- (void)RpmMinMaxController_main
{
	SMCOpen();

	double t  = SMCGetTemperature(SMC_KEY_CPU_TEMP);
	int rpmActual = SMCGetFanRpm(SMC_KEY_FAN0_RPM_CUR);

	if (t <= 0) {
		asl_log(NULL, NULL, ASL_LEVEL_ERR, 
				"Error: SMCGetTemperature() failed. (t = %.0f)", t);
	} else if (t > thirdBoilingPoint_t && rpmMax != 6200) {
		[self SetFanRpmMax:6200];
		[self SetFanRpmMin:6200];
	} else {
		switch (rpmMax) {
			case 6200:
				if ((rpmActual >= 4800 && t < DropPoint_t) 
					|| ((rpmActual < 4800) && (t < (BoilingPoint_t - 4.0))))
				{
					[self SetFanRpmLow];
				} else {
					if (rpmMin < 6000) [self SetFanRpmMin:6000];
				}
				break;
			case 6000:
				if ((rpmActual >= 4800 && t < DropPoint_t) 
					|| ((rpmActual < 4800) && (t < (BoilingPoint_t - 4.0))))
				{
					[self SetFanRpmLow];
				} else {
					if (rpmMin == 5800) {
						if (t > secondBoilingPoint_t) [self SetFanRpmMax:6200];
					} else if (t > BoilingPoint_t && rpmActual > 5600) {
						[self SetFanRpmMin:5800];			
					}
				}
				break;
			case 4500:
				if (old_t > BoilingPoint_t && t > old_t) {
					[self SetFanRpmMax:6000];
					int l = LowestRpm + 1000;
					if (l < 6000) [self SetFanRpmMin:l];
				} else {
					if (rpmMin != LowestRpm) [self SetFanRpmMin:LowestRpm];
				}
				break;
			default:
				break;		
		}
	}

	SMCClose();

	old_t = t;
	[stBarItem setTitle:[NSString stringWithFormat:@"%.0f%drpm", t, rpmActual]];
}

@synthesize stBarItem;
@end

今回はrootでアプリを実行しなければならないので

sudo chown -R root:wheel /Applications/Utilities/アップリ.app
sudo chmod u+s /Applications/Utilities/アップリ.app/Contents/MacOS/アップリ

wheelなのは特に理由はないですが俺伝統でそうなっています。adminとかですかね?
上記のrootでu+sって常駐モノでは御法度とされているんじゃないですか?
が、今回は生命維持装置なので、だれでもいい、とにかく起動させてくれという有事です。
のでさらに押し進めて無限ループも構築
/Library/LaunchAgents/にroot:wheel で以下のplistも作成。アプリの格納場所は何となくそこです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>nf.mb.FanControl</string>
	<key>ProgramArguments</key>
	<array>
		<string>/Applications/Utilities/アップリ.app/Contents/MacOS/アップリ</string>
	</array>
	<key>KeepAlive</key>
	<true/>
</dict>
</plist>

あと、アプリの重複起動でお悩みの際はhttp://d.hatena.ne.jp/bootblack/20090720/p1