とにかくMacBook(旧式黒)のメニューバーに5秒間隔で書き込みたくてたまらない
MacプログラムでAppKit使ったら負けだと思ってる
理由:アプリの容量およびメモリ使用量は100MBを超え破綻するのは火を見るより明かだ。
ありえねー(苦笑)
と、MacBook(旧式黒)のファンからありえない異音が聞こえてきましたの続編です。
メニューを付けるのではなく広大なメニューバーの余白にCPU温度とFan回転数を定期で記したいだけです。
ので、NSApplicationのdelegate methodを活用して以下をよろしく頼みます。
- 終了時にはタイマー止め
- 終了時には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