/* * FanControl * * Copyright (c) 2006-2012 Hendrik Holtmann * Portions Copyright (c) 2013 Michael Wilber * * FanControl.m - MacBook(Pro) FanControl application * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #import "FanControl.h" #import "MachineDefaults.h" #import #import #import #import #import "SystemVersion.h" @interface FanControl () +(void)copyMachinesIfNecessary; - (BOOL)isInAutoStart; - (void) setStartAtLogin:(BOOL)enabled; @end @implementation FanControl // Number of fans reported by the hardware. int g_numFans = 0; NSUserDefaults *defaults; #pragma mark **Init-Methods** +(void) initialize { //avoid Zombies when starting external app signal(SIGCHLD, SIG_IGN); [FanControl copyMachinesIfNecessary]; //check owner and suid rights [FanControl setRights]; //talk to smc [smcWrapper init]; //app in foreground for update notifications [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } +(void)copyMachinesIfNecessary { NSString *path = [[[NSFileManager defaultManager] applicationSupportDirectory] stringByAppendingPathComponent:@"Machines.plist"]; if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { [[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle] pathForResource:@"Machines" ofType:@"plist"] toPath:path error:nil]; } } -(void)upgradeFavorites { //upgrade favorites NSArray *rfavorites = [FavoritesController arrangedObjects]; int j; int i; for (i=0;i<[rfavorites count];i++) { BOOL selected = NO; NSArray *fans = [[rfavorites objectAtIndex:i] objectForKey:@"FanData"]; for (j=0;j<[fans count];j++) { if ([[[fans objectAtIndex:j] objectForKey:@"menu"] boolValue] == YES ) { selected = YES; } } if (selected==NO) { [[[[rfavorites objectAtIndex:i] objectForKey:@"FanData"] objectAtIndex:0] setObject:[NSNumber numberWithBool:YES] forKey:@"menu"]; } } } -(void) awakeFromNib { s_sed = nil; pw=[[Power alloc] init]; [pw setDelegate:self]; [pw registerForSleepWakeNotification]; [pw registerForPowerChange]; //load defaults [DefaultsController setAppliesImmediately:NO]; mdefaults=[[MachineDefaults alloc] init:nil]; s_sed=[mdefaults get_machine_defaults]; NSMutableArray *favorites=[NSMutableArray arrayWithObjects: [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Default", @"Title", [s_sed objectForKey:@"Fans"], @"FanData",nil],nil]; NSRange range=[[MachineDefaults computerModel] rangeOfString:@"MacBook"]; if (range.length>0) { //for macbooks add a second default MachineDefaults *msdefaults=[[MachineDefaults alloc] init:nil]; NSMutableDictionary *sec_fav=[NSMutableDictionary dictionaryWithObjectsAndKeys:@"Higher RPM", @"Title", [[msdefaults get_machine_defaults] objectForKey:@"Fans"], @"FanData",nil]; [favorites addObject:sec_fav]; int i; for (i=0;i<[[s_sed objectForKey:@"Fans"] count];i++) { int min_value=([[[[s_sed objectForKey:@"Fans"] objectAtIndex:i] valueForKey:@"Minspeed"] intValue])*2; [[[[favorites objectAtIndex:1] objectForKey:@"FanData"] objectAtIndex:i] setObject:[NSNumber numberWithInt:min_value] forKey:@"selspeed"]; } [msdefaults release]; } //sync option for Macbook Pro's NSRange range_mbp=[[MachineDefaults computerModel] rangeOfString:@"MacBookPro"]; if (range_mbp.length>0) { [sync setHidden:NO]; } NSString *feedURL = nil; if ([SystemVersion isTiger]) { feedURL = @"http://www.eidac.de/smcfancontrol/smcfancontrol_tiger.xml"; } else { feedURL = @"http://www.eidac.de/smcfancontrol/smcfancontrol.xml"; } //load user defaults defaults = [NSUserDefaults standardUserDefaults]; [defaults registerDefaults: [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:0], @"Unit", [NSNumber numberWithInt:0], @"SelDefault", [NSNumber numberWithBool:NO], @"AutoStart", [NSNumber numberWithBool:NO],@"AutomaticChange", [NSNumber numberWithInt:0],@"selbatt", [NSNumber numberWithInt:0],@"selac", [NSNumber numberWithInt:0],@"selload", [NSNumber numberWithInt:0],@"MenuBar", @"TC0D",@"TSensor", feedURL,@"SUFeedURL", [NSArchiver archivedDataWithRootObject:[NSColor blackColor]],@"MenuColor", favorites,@"Favorites", nil]]; g_numFans = [smcWrapper get_fan_num]; s_menus=[[NSMutableArray alloc] init]; [s_menus autorelease]; int i; for(i=0;i0) { [autochange setEnabled:true]; } else { [autochange setEnabled:false]; } [faqText replaceCharactersInRange:NSMakeRange(0,0) withRTF: [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"F.A.Q" ofType:@"rtf"]]]; [self apply_settings:nil controllerindex:[[defaults objectForKey:@"SelDefault"] intValue]]; [[[[theMenu itemWithTag:1] submenu] itemAtIndex:[[defaults objectForKey:@"SelDefault"] intValue]] setState:NSOnState]; [[sliderCell dataCell] setControlSize:NSSmallControlSize]; [self changeMenu:nil]; //seting toolbar image menu_image=[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"smc" ofType:@"png"]]; menu_image_alt=[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"smcover" ofType:@"png"]]; //release MachineDefaults class first call //add timer for reading to RunLoop _readTimer = [NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(readFanData:) userInfo:nil repeats:YES]; if ([_readTimer respondsToSelector:@selector(setTolerance:)]) { [_readTimer setTolerance:2.0]; } [_readTimer fire]; //autoapply settings if valid [self upgradeFavorites]; //autostart [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:[self isInAutoStart]] forKey:@"AutoStart"]; } -(void)init_statusitem{ statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength] retain]; [statusItem setMenu: theMenu]; [statusItem setEnabled: YES]; [statusItem setHighlightMode:YES]; [statusItem setTitle:@"smc..."]; int i; for(i=0;i<[s_menus count];i++) { [theMenu insertItem:[s_menus objectAtIndex:i] atIndex:i]; }; // Sign up for menuNeedsUpdate call // so that the fan speeds in the menu can be updated // only when needed. [theMenu setDelegate:self]; } #pragma mark **Action-Methods** - (IBAction)loginItem:(id)sender{ if ([sender state]==NSOnState) { [self setStartAtLogin:YES]; } else { [self setStartAtLogin:NO]; } } - (IBAction)add_favorite:(id)sender{ [[NSApplication sharedApplication] beginSheet:newfavoritewindow modalForWindow: mainwindow modalDelegate: nil didEndSelector: nil contextInfo: nil]; } - (IBAction)close_favorite:(id)sender{ [newfavoritewindow close]; [[NSApplication sharedApplication] endSheet:newfavoritewindow]; } - (IBAction)save_favorite:(id)sender{ MachineDefaults *msdefaults=[[MachineDefaults alloc] init:nil]; if ([[newfavorite_title stringValue] length]>0) { NSMutableDictionary *toinsert=[[NSMutableDictionary alloc] initWithObjectsAndKeys:[newfavorite_title stringValue],@"Title",[[msdefaults get_machine_defaults] objectForKey:@"Fans"],@"FanData",nil]; //default as template [toinsert setValue:[NSNumber numberWithInt:0] forKey:@"Standard"]; [FavoritesController addObject:toinsert]; [toinsert release]; [newfavoritewindow close]; [[NSApplication sharedApplication] endSheet:newfavoritewindow]; } [msdefaults release]; [self upgradeFavorites]; } -(void) check_deletion:(id)combo{ if ([FavoritesController selectionIndex]==[[defaults objectForKey:combo] intValue]) { [defaults setObject:[NSNumber numberWithInt:0] forKey:combo]; } } - (void) deleteAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; { if (returnCode==0) { //delete favorite, but resets presets before [self check_deletion:@"selbatt"]; [self check_deletion:@"selac"]; [self check_deletion:@"selload"]; [FavoritesController removeObjects:[FavoritesController selectedObjects]]; } } - (IBAction)delete_favorite:(id)sender{ NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Delete favorite",nil) defaultButton:NSLocalizedString(@"No",nil) alternateButton:NSLocalizedString(@"Yes",nil) otherButton:nil informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Do you really want to delete the favorite %@?",nil), [ [ [FavoritesController arrangedObjects] objectAtIndex:[FavoritesController selectionIndex]] objectForKey:@"Title"] ]]; [alert beginSheetModalForWindow:mainwindow modalDelegate:self didEndSelector:@selector(deleteAlertDidEnd:returnCode:contextInfo:) contextInfo:NULL]; } // Called via a timer mechanism. This is where all the temp / RPM reading is done. //reads fan data and updates the gui -(void) readFanData:(NSTimer*)timer{ int i = 0; //on init handling if (s_sed==nil) { return; } // Determine what data is actually needed to keep the energy impact // as low as possible. bool bNeedTemp = false; bool bNeedRpm = false; const int menuBarSetting = [[defaults objectForKey:@"MenuBar"] intValue]; switch (menuBarSetting) { default: case 1: bNeedTemp = true; bNeedRpm = true; break; case 2: bNeedTemp = true; bNeedRpm = true; break; case 3: bNeedTemp = true; bNeedRpm = false; break; case 4: bNeedTemp = false; bNeedRpm = true; break; } NSString *temp = nil; NSString *fan = nil; float c_temp = 0.0f; int selectedRpm = 0; if (bNeedRpm == true) { // Read the current fan speed for the desired fan and format text for display in the menubar. NSArray *fans = [[[FavoritesController arrangedObjects] objectAtIndex:[FavoritesController selectionIndex]] objectForKey:@"FanData"]; for (i=0; i1 ) { if (bind==YES) { [[[FanController arrangedObjects] objectAtIndex:1] bind:@"selspeed" toObject:[[FanController arrangedObjects] objectAtIndex:0] withKeyPath:@"selspeed" options:nil]; [[[FanController arrangedObjects] objectAtIndex:0] bind:@"selspeed" toObject:[[FanController arrangedObjects] objectAtIndex:1] withKeyPath:@"selspeed" options:nil]; } else { [[[FanController arrangedObjects] objectAtIndex:1] unbind:@"selspeed"]; [[[FanController arrangedObjects] objectAtIndex:0] unbind:@"selspeed"]; } } } #pragma mark **Power Watchdog-Methods** - (void)systemDidWakeFromSleep:(id)sender{ [self apply_settings:nil controllerindex:[[defaults objectForKey:@"SelDefault"] intValue]]; } - (void)powerChangeToBattery:(id)sender{ if ([[defaults objectForKey:@"AutomaticChange"] boolValue]==YES) { [self apply_settings:nil controllerindex:[[defaults objectForKey:@"selbatt"] intValue]]; } } - (void)powerChangeToAC:(id)sender{ if ([[defaults objectForKey:@"AutomaticChange"] boolValue]==YES) { [self apply_settings:nil controllerindex:[[defaults objectForKey:@"selac"] intValue]]; } } - (void)powerChangeToACLoading:(id)sender{ if ([[defaults objectForKey:@"AutomaticChange"] boolValue]==YES) { [self apply_settings:nil controllerindex:[[defaults objectForKey:@"selload"] intValue]]; } } #pragma mark - #pragma mark Start-at-login control - (BOOL)isInAutoStart { BOOL found = NO; LSSharedFileListRef loginItems = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, /*options*/ NULL); NSString *path = [[NSBundle mainBundle] bundlePath]; CFURLRef URLToToggle = (CFURLRef)[NSURL fileURLWithPath:path]; LSSharedFileListItemRef existingItem = NULL; UInt32 seed = 0U; NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease]; for (id itemObject in currentLoginItems) { LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject; UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; CFURLRef URL = NULL; OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL); if (err == noErr) { Boolean foundIt = CFEqual(URL, URLToToggle); CFRelease(URL); if (foundIt) { existingItem = item; found = YES; break; } } } return found; } - (void) setStartAtLogin:(BOOL)enabled { LSSharedFileListRef loginItems = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, /*options*/ NULL); NSString *path = [[NSBundle mainBundle] bundlePath]; OSStatus status; CFURLRef URLToToggle = (CFURLRef)[NSURL fileURLWithPath:path]; LSSharedFileListItemRef existingItem = NULL; UInt32 seed = 0U; NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease]; for (id itemObject in currentLoginItems) { LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject; UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; CFURLRef URL = NULL; OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL); if (err == noErr) { Boolean foundIt = CFEqual(URL, URLToToggle); CFRelease(URL); if (foundIt) { existingItem = item; break; } } } if (enabled && (existingItem == NULL)) { NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:path]; IconRef icon = NULL; FSRef ref; Boolean gotRef = CFURLGetFSRef(URLToToggle, &ref); if (gotRef) { status = GetIconRefFromFileInfo(&ref, /*fileNameLength*/ 0, /*fileName*/ NULL, kFSCatInfoNone, /*catalogInfo*/ NULL, kIconServicesNormalUsageFlag, &icon, /*outLabel*/ NULL); if (status != noErr) icon = NULL; } LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, (CFStringRef)displayName, icon, URLToToggle, /*propertiesToSet*/ NULL, /*propertiesToClear*/ NULL); } else if (!enabled && (existingItem != NULL)) LSSharedFileListItemRemove(loginItems, existingItem); } #pragma mark **SMC-Binary Owner/Right Check** //TODO: It looks like this function is called inefficiently. //call smc binary with sudo rights and apply +(void)setRights{ NSString *smcpath = [[NSBundle mainBundle] pathForResource:@"smc" ofType:@""]; NSFileManager *fmanage=[NSFileManager defaultManager]; NSDictionary *fdic = [fmanage attributesOfItemAtPath:smcpath error:nil]; if ([[fdic valueForKey:@"NSFileOwnerAccountName"] isEqualToString:@"root"] && [[fdic valueForKey:@"NSFileGroupOwnerAccountName"] isEqualToString:@"admin"] && ([[fdic valueForKey:@"NSFilePosixPermissions"] intValue]==3437)) { // If the SMC binary has already been modified to run as root, then do nothing. return; } //TODO: Is the usage of commPipe safe? FILE *commPipe; AuthorizationRef authorizationRef; AuthorizationItem gencitem = { "system.privilege.admin", 0, NULL, 0 }; AuthorizationRights gencright = { 1, &gencitem }; int flags = kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed; OSStatus status = AuthorizationCreate(&gencright, kAuthorizationEmptyEnvironment, flags, &authorizationRef); NSString *tool=@"/usr/sbin/chown"; NSArray *argsArray = [NSArray arrayWithObjects: @"root:admin",smcpath,nil]; int i; char *args[255]; for(i = 0;i < [argsArray count];i++){ args[i] = (char *)[[argsArray objectAtIndex:i]cString]; } args[i] = NULL; status=AuthorizationExecuteWithPrivileges(authorizationRef,[tool UTF8String],0,args,&commPipe); //second call for suid-bit tool=@"/bin/chmod"; argsArray = [NSArray arrayWithObjects: @"6555",smcpath,nil]; for(i = 0;i < [argsArray count];i++){ args[i] = (char *)[[argsArray objectAtIndex:i]cString]; } args[i] = NULL; status=AuthorizationExecuteWithPrivileges(authorizationRef,[tool UTF8String],0,args,&commPipe); } @end @implementation NSNumber (NumberAdditions) - (NSString*) tohex{ return [NSString stringWithFormat:@"%0.4x",[self intValue]<<2]; } - (NSNumber*) celsius_fahrenheit{ float celsius=[self floatValue]; float fahrenheit=(celsius*9)/5+32; return [NSNumber numberWithFloat:fahrenheit]; } @end