X-Git-Url: http://git.ithinksw.org/MenuTunes.git/blobdiff_plain/6162e11fb9ec9ca0f4add4d5838e98a31ead4a21..d2a384947ca515dfbdc114c326e7ac4667c0397e:/PreferencesController.m?ds=inline diff --git a/PreferencesController.m b/PreferencesController.m index 76bbc23..61e0c11 100755 --- a/PreferencesController.m +++ b/PreferencesController.m @@ -7,13 +7,15 @@ #import "StatusWindowController.h" #import "CustomMenuTableView.h" +#import + #import #import #import #import #import -#import +#import #import #import @@ -31,6 +33,8 @@ #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO) +#define AUDIOSCROBBLER_KEYCHAIN_SERVICE "MenuTunes: Audioscrobbler" +#define AUDIOSCROBBLER_KEYCHAIN_KIND "application password" /*************************************************************************/ #pragma mark - @@ -38,10 +42,17 @@ /*************************************************************************/ @interface PreferencesController (Private) ++ (SecKeychainItemRef)keychainItemForUser:(NSString *)user; ++ (BOOL)keychainItemExistsForUser:(NSString *)user; ++ (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password; ++ (BOOL)deleteKeychainItemForUser:(NSString *)user; ++ (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user; + - (void)setupWindow; - (void)setupCustomizationTables; - (void)setupMenuItems; - (void)setupUI; +- (void)setupScreenPopup; - (void)setStatusWindowEntryEffect:(Class)effectClass; - (void)setStatusWindowExitEffect:(Class)effectClass; - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update; @@ -61,6 +72,139 @@ static PreferencesController *prefs = nil; +/*************************************************************************/ +#pragma mark - +#pragma mark STATIC KEYCHAIN SUPPORT METHODS +/*************************************************************************/ + ++ (SecKeychainItemRef)keychainItemForUser:(NSString *)user +{ + SecKeychainSearchRef search; + SecKeychainItemRef item; + OSStatus status; + SecKeychainAttribute attributes[3]; + SecKeychainAttributeList list; + + ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user); + attributes[0].tag = kSecAccountItemAttr; + attributes[0].data = (char *)[user UTF8String]; + attributes[0].length = [user length]; + attributes[1].tag = kSecDescriptionItemAttr; + attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND; + attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND); + attributes[2].tag = kSecLabelItemAttr; + attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE; + attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE); + list.count = 3; + list.attr = attributes; + + status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search); + + if (status != noErr) { + ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status); + } + + status = SecKeychainSearchCopyNext(search, &item); + + if (status != noErr) { + ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status); + item = nil; + } + + CFRelease(search); + return item; +} + ++ (BOOL)keychainItemExistsForUser:(NSString *)user +{ + SecKeychainItemRef item = [PreferencesController keychainItemForUser:user]; + BOOL exists = (item != nil); + if (item) { + CFRelease(item); + } + return exists; +} + ++ (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password +{ + SecKeychainItemRef item; + OSStatus status; + SecKeychainAttribute attributes[3]; + SecKeychainAttributeList list; + + ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user); + attributes[0].tag = kSecAccountItemAttr; + attributes[0].data = (char *)[user UTF8String]; + attributes[0].length = [user length]; + attributes[1].tag = kSecDescriptionItemAttr; + attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND; + attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND); + attributes[2].tag = kSecLabelItemAttr; + attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE; + attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE); + list.count = 3; + list.attr = attributes; + + status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item); + if (status != noErr) { + ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status); + } + return (status == noErr); +} + ++ (BOOL)deleteKeychainItemForUser:(NSString *)user +{ + OSStatus status = errSecNotAvailable; + SecKeychainItemRef item = [PreferencesController keychainItemForUser:user]; + if (item != nil) { + status = SecKeychainItemDelete(item); + if (status != noErr) { + ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status); + } + CFRelease(item); + } + return (status == noErr); +} + ++ (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user +{ + OSStatus status = errSecNotAvailable; + SecKeychainItemRef item = [PreferencesController keychainItemForUser:user]; + if (item != nil) { + status = SecKeychainItemModifyContent(item, NULL, [password length], [password cString]); + if (status != noErr) { + ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status); + } + CFRelease(item); + } + return (status == noErr); +} + ++ (NSString *)getKeychainItemPasswordForUser:(NSString *)user +{ + OSStatus status = errSecNotAvailable; + SecKeychainItemRef item = [PreferencesController keychainItemForUser:user]; + NSString *pass = nil; + if (item != nil) { + UInt32 length; + char *buffer; + status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer); + if (status != noErr) { + ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status); + } else { + if ([NSString respondsToSelector:@selector(stringWithCString:encoding:)]) { + pass = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding]; + } else { + pass = [NSString stringWithCString:buffer]; + } + } + if (status != noErr) { + ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status); + } + CFRelease(item); + } + return pass; +} /*************************************************************************/ #pragma mark - @@ -97,7 +241,14 @@ static PreferencesController *prefs = nil; @"DecrementRating", @"ToggleShuffle", @"ToggleLoop", + @"ToggleShufflability", @"PopupMenu", + @"SetRating0", + @"SetRating1", + @"SetRating2", + @"SetRating3", + @"SetRating4", + @"SetRating5", nil]; hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause", @@ -114,7 +265,14 @@ static PreferencesController *prefs = nil; @"Decrement Rating", @"Toggle Shuffle", @"Toggle Loop", + @"Toggle Song Included In Shuffle", @"Pop-up status menu", + [NSString stringWithUTF8String:"Set Rating: ☆☆☆☆☆"], + [NSString stringWithUTF8String:"Set Rating: ★☆☆☆☆"], + [NSString stringWithUTF8String:"Set Rating: ★★☆☆☆"], + [NSString stringWithUTF8String:"Set Rating: ★★★☆☆"], + [NSString stringWithUTF8String:"Set Rating: ★★★★☆"], + [NSString stringWithUTF8String:"Set Rating: ★★★★★"], nil]; hotKeysDictionary = [[NSMutableDictionary alloc] init]; controller = nil; @@ -194,6 +352,7 @@ static PreferencesController *prefs = nil; [self setupUI]; // Sets up additional UI [window setDelegate:self]; [menuTableView reloadData]; + [hotKeysTableView reloadData]; [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)]; //Change the launch player checkbox to the proper name @@ -207,7 +366,9 @@ static PreferencesController *prefs = nil; [self resetRemotePlayerTextFields]; [launchAtLoginCheckbox becomeFirstResponder]; [NSApp activateIgnoringOtherApps:YES]; - [window center]; + if (![window isVisible]) { + [window center]; + } [window orderFrontRegardless]; [window makeKeyWindow]; } @@ -254,7 +415,40 @@ static PreferencesController *prefs = nil; } else if ( [sender tag] == 1120) { mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744); [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]]; - } + } else if ( [sender tag] == 6010) { + //Toggle the other Audioscrobbler options + [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"]; + [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE]; + [audioscrobblerUserTextField setEnabled:SENDER_STATE]; + [audioscrobblerPasswordTextField setEnabled:SENDER_STATE]; + } else if ( [sender tag ] == 6015) { + //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared. + NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue]; + if ([newAccount length] == 0) { + [PreferencesController deleteKeychainItemForUser:currentAccount]; + } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) { + [df setObject:newAccount forKey:@"audioscrobblerUser"]; + if ([PreferencesController keychainItemExistsForUser:currentAccount]) { + //Delete the current keychain item if there is one + [PreferencesController deleteKeychainItemForUser:currentAccount]; + } + [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]]; + } + } else if ( [sender tag ] == 6030) { + //Here we set the password for an existing keychain item or we create a new keychain item. + if ([[audioscrobblerUserTextField stringValue] length] > 0) { + NSString *account = [df stringForKey:@"audioscrobblerUser"]; + if ([PreferencesController keychainItemExistsForUser:account]) { + //Update the current keychain item + [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account]; + } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) { + //Create a new keychain item + [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]]; + } + } + } else if ( [sender tag] == 6045) { + [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"]; + } [df synchronize]; } @@ -277,15 +471,15 @@ static PreferencesController *prefs = nil; //Set the server password const char *instring = [[sender stringValue] UTF8String]; const char *password = "p4s5w0rdMT1.2"; - unsigned char *result; + char *result; NSData *hashedPass, *passwordStringHash; if ([[sender stringValue] length] == 0) { [df setObject:[NSData data] forKey:@"sharedPlayerPassword"]; return; } - result = SHA1(instring, strlen(instring), NULL); + result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL); hashedPass = [NSData dataWithBytes:result length:strlen(result)]; - result = SHA1(password, strlen(password), NULL); + result = (char *)SHA1((unsigned char *)password, strlen(password), NULL); passwordStringHash = [NSData dataWithBytes:result length:strlen(result)]; if (![hashedPass isEqualToData:passwordStringHash]) { [df setObject:hashedPass forKey:@"sharedPlayerPassword"]; @@ -356,8 +550,8 @@ static PreferencesController *prefs = nil; } } else if ( [sender tag] == 5150 ) { const char *instring = [[sender stringValue] UTF8String]; - unsigned char *result; - result = SHA1(instring, strlen(instring), NULL); + char *result; + result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL); [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"]; } else if ( [sender tag] == 5110 ) { //Cancel @@ -396,8 +590,8 @@ static PreferencesController *prefs = nil; } else if ( [sender tag] == 6020 ) { //OK password entry, retry connect const char *instring = [[passwordPanelTextField stringValue] UTF8String]; - unsigned char *result; - result = SHA1(instring, strlen(instring), NULL); + char *result; + result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL); [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"]; [passwordPanel orderOut:nil]; [NSApp stopModalWithCode:1]; @@ -451,7 +645,10 @@ static PreferencesController *prefs = nil; } else if ( [sender tag] == 2020) { // Update screen selection. - + [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]]; + [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"]; + [(MainController *)controller showCurrentTrackInfo]; + } else if ( [sender tag] == 2030) { [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]]; @@ -540,6 +737,7 @@ static PreferencesController *prefs = nil; @"upcomingSongs", @"separator", @"preferences", + @"about", @"quit", nil] forKey:@"menu"]; @@ -560,6 +758,7 @@ static PreferencesController *prefs = nil; [df setFloat:4.0 forKey:@"statusWindowVanishDelay"]; [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"]; [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"]; + [df setInteger:0 forKey:@"statusWindowScreenIndex"]; [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom]; [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft]; [df setBool:YES forKey:@"showSongInfoOnChange"]; @@ -575,7 +774,7 @@ static PreferencesController *prefs = nil; - (void)autoLaunchOK { - [[StatusWindow sharedWindow] setLocked:NO]; + [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO]; [[StatusWindow sharedWindow] vanish:self]; [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES]; @@ -584,7 +783,7 @@ static PreferencesController *prefs = nil; - (void)autoLaunchCancel { - [[StatusWindow sharedWindow] setLocked:NO]; + [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO]; [[StatusWindow sharedWindow] vanish:self]; [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES]; } @@ -726,6 +925,8 @@ static PreferencesController *prefs = nil; @"trackInfo", @"upcomingSongs", @"playlists", + @"artists", + @"albums", @"eqPresets", @"songRating", @"playPause", @@ -734,6 +935,7 @@ static PreferencesController *prefs = nil; @"fastForward", @"rewind", @"showPlayer", + @"about", @"quit", nil]; @@ -752,6 +954,8 @@ static PreferencesController *prefs = nil; submenuItems = [[NSArray alloc] initWithObjects: @"upcomingSongs", @"playlists", + @"artists", + @"albums", @"eqPresets", @"songRating", nil]; @@ -764,7 +968,7 @@ static PreferencesController *prefs = nil; NSData *colorData; int selectedBGStyle; id anItem; - + [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"]; ITDebugLog(@"Setting up preferences UI."); @@ -783,6 +987,9 @@ static PreferencesController *prefs = nil; } } + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil]; + [self setupScreenPopup]; + ITDebugLog(@"Setting up track info checkboxes."); // Check current track info buttons [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState]; @@ -879,6 +1086,27 @@ static PreferencesController *prefs = nil; [selectSharedPlayerButton setEnabled:YES]; } + //Setup the Audioscrobbler controls + if ([df boolForKey:@"audioscrobblerEnabled"]) { + [audioscrobblerEnabledCheckbox setState:NSOnState]; + [audioscrobblerUserTextField setEnabled:YES]; + [audioscrobblerPasswordTextField setEnabled:YES]; + [audioscrobblerUseCacheCheckbox setEnabled:YES]; + } else { + [audioscrobblerUserTextField setEnabled:NO]; + [audioscrobblerPasswordTextField setEnabled:NO]; + [audioscrobblerUseCacheCheckbox setEnabled:NO]; + } + NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"]; + if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) { + NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser]; + [audioscrobblerUserTextField setStringValue:audioscrobblerUser]; + if (password != nil) { + [audioscrobblerPasswordTextField setStringValue:password]; + } + } + [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]]; + [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil]; serverName = [df stringForKey:@"sharedPlayerName"]; @@ -906,6 +1134,30 @@ static PreferencesController *prefs = nil; } } +- (void)setupScreenPopup +{ + ITDebugLog(@"Setting up screen popup"); + NSArray *screens = [NSScreen screens]; + if ([screens count] > 1) { + int i, index = [df integerForKey:@"statusWindowScreenIndex"]; + [screenPopup setEnabled:YES]; + for (i = 0; i < [screens count]; i++) { + NSScreen *screen = [screens objectAtIndex:i]; + if (![screen isEqual:[NSScreen mainScreen]]) { + [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]]; + } + } + [screenPopup selectItemAtIndex:index]; + [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]]; + } else { + while ([screenPopup numberOfItems] > 1) { + [screenPopup removeItemAtIndex:1]; + } + [screenPopup setEnabled:NO]; + [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]]; + } +} + - (void)setStatusWindowEntryEffect:(Class)effectClass { StatusWindow *sw = [StatusWindow sharedWindow]; @@ -1003,6 +1255,8 @@ static PreferencesController *prefs = nil; [df setObject:myItems forKey:@"menu"]; [df synchronize]; + [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0]; + //If we're connected over a network, refresh the menu immediately if ([[NetworkController sharedController] isConnectedToServer]) { [controller timerUpdate]; @@ -1201,6 +1455,7 @@ static PreferencesController *prefs = nil; - (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; [hotKeysArray release]; [hotKeysDictionary release]; [effectClasses release]; @@ -1211,6 +1466,7 @@ static PreferencesController *prefs = nil; [submenuItems release]; [myItems release]; [df release]; + [super dealloc]; } @end