// // AppDelegate.m // Pennyworth // // Created by Chris Karr on 12/24/07. // Copyright 2007 Chris J. Karr. All rights reserved. // #import #import "AnalyticsLogger.h" #import "AppDelegate.h" #import "AppleScriptPrediction.h" #import "Observation.h" #include "LoginItemsAE.h" #define START_AT_LOGIN @"start_at_login" @implementation AppDelegate - (AppDelegate *) init { if (self = [super init]) sensorsLoaded = NO; return self; } - (void) activateStatusMenu { NSStatusBar * bar = [NSStatusBar systemStatusBar]; theItem = [bar statusItemWithLength:NSVariableStatusItemLength]; [theItem retain]; [theItem setTitle:nil]; [theItem setImage:[NSImage imageNamed:@"bell-small"]]; [theItem setAlternateImage:[NSImage imageNamed:@"bell-small-alt"]]; [theItem setHighlightMode:YES]; [theItem setMenu:menu]; } - (void) hotKeyReleased:(NDHotKeyEvent *) aHotKey { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Prediction Correction (Hot Key)", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; [trainingManager correctTraining:nil]; } - (void) awakeFromNib { if (!sensorsLoaded) { sensorsLoaded = YES; [NDHotKeyEvent setSignature:'PWth']; hotKey = [[NDHotKeyEvent hotKeyWithKeyCode:8 character:99 modifierFlags:(NSControlKeyMask|NSCommandKeyMask)] retain]; [hotKey setTarget:self selectorReleased:@selector(hotKeyReleased:) selectorPressed:@selector(hotKeyReleased:)]; [hotKey setEnabled:YES]; [self activateStatusMenu]; if (![NSBundle loadNibNamed:@"Sensors" owner:self]) NSLog (@"Warning! Could not load Sensors XIB file.\n"); NSSortDescriptor * descriptor = [[NSSortDescriptor alloc] initWithKey:@"sensor" ascending:YES]; [observations setSortDescriptors:[NSArray arrayWithObject:descriptor]]; [descriptor release]; } [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:START_AT_LOGIN options:0 context:NULL]; [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"Pennyworth Launched" object:[[NSBundle mainBundle] bundleIdentifier]]; } - (void) startAtLogin { BOOL enabled = [[NSUserDefaults standardUserDefaults] boolForKey:START_AT_LOGIN]; OSStatus status; CFArrayRef loginItems = NULL; NSURL * url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; int existingLoginItemIndex = -1; status = LIAECopyLoginItems (&loginItems); if (status == noErr) { NSEnumerator * enumerator = [(NSArray *) loginItems objectEnumerator]; NSDictionary * loginItemDict; while ((loginItemDict = [enumerator nextObject])) { if ([[loginItemDict objectForKey:(NSString *) kLIAEURL] isEqual:url]) { existingLoginItemIndex = [(NSArray *) loginItems indexOfObjectIdenticalTo:loginItemDict]; break; } } } if (enabled && (existingLoginItemIndex == -1)) { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Enabled Start At Login", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; LIAEAddURLAtEnd ((CFURLRef) url, false); } else if (!enabled && (existingLoginItemIndex != -1)) { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Disabled Start At Login", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; LIAERemove (existingLoginItemIndex); } if (loginItems) CFRelease (loginItems); } - (void) observeValueForKeyPath: (NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqual:START_AT_LOGIN]) [self startAtLogin]; } - (IBAction) predictions:(id) sender { if ([predictionPanel isKeyWindow]) [predictionPanel orderOut:sender]; else { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Opened Predictions Window", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; [NSApp activateIgnoringOtherApps:YES]; [predictionPanel makeKeyAndOrderFront:sender]; } } - (IBAction) observations:(id) sender { if ([observationWindow isKeyWindow]) [observationWindow orderOut:sender]; else { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Opened Observations Window", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; [NSApp activateIgnoringOtherApps:YES]; [observationWindow makeKeyAndOrderFront:sender]; } } - (IBAction) preferences:(id) sender { if ([preferencesWindow isKeyWindow]) [preferencesWindow orderOut:sender]; else { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Opened Preferences Window", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; [NSApp activateIgnoringOtherApps:YES]; [preferencesWindow makeKeyAndOrderFront:sender]; } } - (IBAction) learners:(id) sender { [learnerView refresh:sender]; if ([learnersWindow isKeyWindow]) [learnersWindow orderOut:sender]; else { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Opened Learners Window", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; [NSApp activateIgnoringOtherApps:YES]; [learnersWindow makeKeyAndOrderFront:sender]; } } - (IBAction) rules:(id) sender { if ([rulesWindow isKeyWindow]) [rulesWindow orderOut:sender]; else { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Opened Rules Window", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; [NSApp activateIgnoringOtherApps:YES]; [rulesWindow makeKeyAndOrderFront:sender]; } } - (IBAction) logs:(id) sender { if ([logWindow isKeyWindow]) [logWindow orderOut:sender]; else { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Opened Logs Window", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; [NSApp activateIgnoringOtherApps:YES]; [logWindow makeKeyAndOrderFront:sender]; } } - (BOOL) application:(NSApplication *) sender delegateHandlesKey:(NSString *) key { if ([key isEqualToString:@"observations"]) return YES; else if ([key isEqualToString:@"predictions"]) return YES; else if ([key isEqualToString:@"log"]) return YES; return NO; } - (NSArray *) getObservations { NSMutableArray * obsArray = [NSMutableArray array]; for (Observation * observation in [observations arrangedObjects]) { AppleScriptObservation * o = [[AppleScriptObservation alloc] init]; [o setName:[observation sensor]]; NSObject * ov = [observation observation]; if ([ov isKindOfClass:[NSArray class]]) { NSMutableString * valueString = [NSMutableString string]; for (NSObject * obj in ((NSArray *) ov)) { if ([valueString length] != 0) [valueString appendString:@";"]; [valueString appendString:[obj description]]; } [o setValue:valueString]; } else [o setValue:[ov description]]; [obsArray addObject:o]; } return obsArray; } - (NSArray *) getPredictions { NSMutableArray * predictions = [NSMutableArray array]; AppleScriptPrediction * location = [[AppleScriptPrediction alloc] init]; [location setName:@"Location"]; [location setValue:user.location]; [predictions addObject:location]; [location release]; AppleScriptPrediction * social = [[AppleScriptPrediction alloc] init]; [social setName:@"Social Context"]; [social setValue:user.social]; [predictions addObject:social]; [social release]; AppleScriptPrediction * activity = [[AppleScriptPrediction alloc] init]; [activity setName:@"Activity"]; [activity setValue:user.activity]; [predictions addObject:activity]; [activity release]; return predictions; } - (void) logObservation:(NSScriptCommand *) command { // NSLog (@"log observation"); } - (void)applicationWillTerminate:(NSNotification *)aNotification { [[NSNotificationCenter defaultCenter] postNotificationName:TERMINATE_NOTIFICATION object:self]; } - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) sender { [NSApp activateIgnoringOtherApps:YES]; if (NSAlertAlternateReturn == NSRunAlertPanel (@"Quit Pennyworth?", @"Do you want to stop running Pennyworth? No new context predictions can be made after Pennyworth quits.", @"No", @"Yes", nil)) { NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Terminated Application", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; return YES; } NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Cancelled Application Termination", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject]; return NO; } - (NSString *) notePassword { UInt32 length = 256; void * password = NULL; OSStatus status = SecKeychainFindGenericPassword (NULL, strlen ("Pennyworth"), "Pennyworth", strlen ("Default"), "Default", &length, &password, NULL); NSString * kcPassword = nil; if (status == errSecItemNotFound) NSLog (@"No password. returning nil."); else if (status == noErr) { NSData * passwordData = [NSData dataWithBytes:password length:length]; kcPassword = [[[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding] autorelease]; } else NSLog (@"error retrieving keychain password %d", status); SecKeychainItemFreeContent (NULL, password); return kcPassword; } - (void) setNotePassword:(NSString *) newPassword { if (newPassword == nil) newPassword = @""; if ([self notePassword] == nil) SecKeychainAddGenericPassword (NULL, strlen ("Pennyworth"), "Pennyworth", strlen ("Default"), "Default", [newPassword length], [newPassword UTF8String], NULL); else { SecKeychainItemRef itemRef = NULL; SecKeychainFindGenericPassword (NULL, strlen ("Pennyworth"), "Pennyworth", strlen ("Default"), "Default", 0, NULL, &itemRef); SecKeychainItemModifyAttributesAndData (itemRef, NULL, [newPassword length], [newPassword UTF8String]); } } @end