51087015cd32735f34d2cddcc9f877480c620dad
[MenuTunes.git] / iTunesRemote.m
1 #import "iTunesRemote.h"
2
3 @implementation iTunesRemote
4
5 + (id)remote
6 {
7     return [[[iTunesRemote alloc] init] autorelease];
8 }
9
10 - (NSString *)remoteTitle
11 {
12     return @"iTunes Remote";
13 }
14
15 - (NSString *)remoteInformation
16 {
17     return @"Default MenuTunes plugin to control iTunes, by iThink Software.";
18 }
19
20 - (NSImage *)remoteIcon
21 {
22     return nil;
23 }
24
25 - (BOOL)begin
26 {
27     ITDebugLog(@"iTunesRemote begun");
28     savedPSN = [self iTunesPSN];
29         [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationHandler:) name:@"com.apple.iTunes.playerInfo" object:@"com.apple.iTunes.player"];
30     return YES;
31 }
32
33 - (BOOL)halt
34 {
35     ITDebugLog(@"iTunesRemote halted");
36         [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
37     return YES;
38 }
39
40 - (NSString *)playerFullName
41 {
42     return @"iTunes";
43 }
44
45 - (NSString *)playerSimpleName
46 {
47     return @"iTunes";
48 }
49
50 - (NSDictionary *)capabilities
51 {
52     return [NSDictionary dictionaryWithObjectsAndKeys:
53                 [NSNumber numberWithBool: YES], @"Remote",
54                 [NSNumber numberWithBool: YES], @"Basic Track Control",
55                 [NSNumber numberWithBool: YES], @"Track Information",
56                 [NSNumber numberWithBool: YES], @"Track Navigation",
57                 [NSNumber numberWithBool: YES], @"Upcoming Songs",
58                 [NSNumber numberWithBool: YES], @"Playlists",
59                 [NSNumber numberWithBool: YES], @"Volume",
60                 [NSNumber numberWithBool: YES], @"Shuffle",
61                 [NSNumber numberWithBool: YES], @"Repeat Modes",
62                 [NSNumber numberWithBool: YES], @"Equalizer",
63                 [NSNumber numberWithBool: YES], @"Track Rating",
64                 nil];
65 }
66
67 - (BOOL)showPrimaryInterface
68 {
69     ITDebugLog(@"Showing player primary interface.");
70     
71     if ([self playerRunningState] == ITMTRemotePlayerRunning) {
72         ITDebugLog(@"Showing player interface.");
73         //If not minimized and visible
74         if ( ([ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } }", 'core', 'getd', &savedPSN) booleanValue] == 0) &&
75                          ([ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pvis'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } }", 'core', 'getd', &savedPSN) booleanValue] != 0) &&
76              [[[[NSWorkspace sharedWorkspace] activeApplication] objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"] ) {
77             //set minimized of browser window 1 to true
78                         ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:long(1), from:'null'() } }", 'core', 'setd', &savedPSN);
79         } else {
80             //set minimized of browser window 1 to false
81                         ITSendAEWithString(@"data:long(0), '----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:long(1), from:'null'() } }", 'core', 'setd', &savedPSN);
82         }
83         //set visible of browser window 1 to true
84                 ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pvis'), from:obj { form:'indx', want:type('cBrW'), seld:long(1), from:'null'() } }", 'core', 'setd', &savedPSN);
85         //active iTunes
86                 ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pisf'), from:'null'() }", 'core', 'setd', &savedPSN);
87         ITDebugLog(@"Done showing player primary interface.");
88         return YES;
89     } else {
90         NSString *path;
91         ITDebugLog(@"Launching player.");
92         if ( (path = [[NSUserDefaults standardUserDefaults] stringForKey:@"CustomPlayerPath"]) ) {
93         } else {
94             path = [self playerFullName];
95         }
96         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
97             ITDebugLog(@"Error Launching Player");
98             return NO;
99         }
100         return YES;
101     }
102 }
103
104 - (ITMTRemotePlayerRunningState)playerRunningState
105 {
106     NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
107     int i;
108     int count = [apps count];
109     
110     for (i = 0; i < count; i++) {
111         if ([[[apps objectAtIndex:i] objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"]) {
112             ITDebugLog(@"Player running state: 1");
113             return ITMTRemotePlayerRunning;
114         }
115     }
116     ITDebugLog(@"Player running state: 0");
117     return ITMTRemotePlayerNotRunning;
118 }
119
120 - (ITMTRemotePlayerPlayingState)playerPlayingState
121 {
122     SInt32 result;
123     
124     ITDebugLog(@"Getting player playing state");
125     result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPlS'), from:'null'() }", 'core', 'getd', &savedPSN) int32Value];
126     switch (result)
127     {
128         case 'kPSP':
129             ITDebugLog(@"Getting player playing state done. Player state: Playing");
130             return ITMTRemotePlayerPlaying;
131         case 'kPSp':
132             ITDebugLog(@"Getting player playing state done. Player state: Paused");
133             return ITMTRemotePlayerPaused;
134         case 'kPSR':
135             ITDebugLog(@"Getting player playing state done. Player state: Rewinding");
136             return ITMTRemotePlayerRewinding;
137         case 'kPSF':
138             ITDebugLog(@"Getting player playing state done. Player state: Forwarding");
139             return ITMTRemotePlayerForwarding;
140         case 'kPSS':
141         default:
142             ITDebugLog(@"Getting player playing state done. Player state: Stopped");
143             return ITMTRemotePlayerStopped;
144     }
145     ITDebugLog(@"Getting player playing state done. Player state: Stopped");
146     return ITMTRemotePlayerStopped;
147 }
148
149 /*- (NSArray *)playlists
150 {
151     long i = 0;
152     const signed long numPlaylists = [[ITAppleEventCenter sharedCenter] sendAEWithSendStringForNumber:@"kocl:type('cPly'), '----':()" eventClass:@"core" eventID:@"cnte" appPSN:savedPSN];
153     NSMutableArray *playlists = [[NSMutableArray alloc] initWithCapacity:numPlaylists];
154     
155     for (i = 1; i <= numPlaylists; i++) {
156         const long j = i;
157         NSString *sendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cPly'), seld:long(%lu), from:'null'() } }",(unsigned long)j];
158         NSString *theObj = [[ITAppleEventCenter sharedCenter] sendAEWithSendString:sendStr eventClass:@"core" eventID:@"getd" appPSN:savedPSN];
159         [playlists addObject:theObj];
160     }
161     return [playlists autorelease];
162 }*/
163
164 //Full source awareness
165 - (NSArray *)playlists
166 {
167     unsigned long i, k;
168     SInt32 numSources = [ITSendAEWithString(@"kocl:type('cSrc'), '----':()", 'core', 'cnte', &savedPSN) int32Value];
169     NSMutableArray *allSources = [[NSMutableArray alloc] init];
170     
171     ITDebugLog(@"Getting playlists.");
172     if (numSources == 0) {
173                 [allSources release];
174         ITDebugLog(@"No sources.");
175         return nil;
176     }
177     
178     for (k = 1; k <= numSources ; k++) {
179         SInt32 numPlaylists = [ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cPly'), '----':obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() }",k], 'core', 'cnte', &savedPSN) int32Value];
180         SInt32 fourcc = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pKnd'), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } }",k], 'core', 'getd', &savedPSN) int32Value];
181         NSString *sourceName = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } }",k], 'core', 'getd', &savedPSN) stringValue];
182         SInt32 index = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } }",k], 'core', 'getd', &savedPSN) int32Value];
183         unsigned long class;
184         if (sourceName) {
185             NSMutableArray *aSource = [[NSMutableArray alloc] init];
186             [aSource addObject:sourceName];
187             switch (fourcc) {
188                 case 'kTun':
189                     class = ITMTRemoteRadioSource;
190                     break;
191                 case 'kDev':
192                     class = ITMTRemoteGenericDeviceSource;
193                     break;
194                 case 'kPod':
195                     class = ITMTRemoteiPodSource;
196                     break;
197                 case 'kMCD':
198                 case 'kACD':
199                     class = ITMTRemoteCDSource;
200                     break;
201                 case 'kShd':
202                     class = ITMTRemoteSharedLibrarySource;
203                     break;
204                 case 'kUnk':
205                 case 'kLib':
206                 default:
207                     class = ITMTRemoteLibrarySource;
208                     break;
209             }
210             ITDebugLog(@"Adding source %@ of type %i at index %i", sourceName, class, index);
211             [aSource addObject:[NSNumber numberWithInt:class]];
212             [aSource addObject:[NSNumber numberWithInt:index]];
213             for (i = 1; i <= numPlaylists; i++) {
214                 NSString *sendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cPly'), seld:long(%u), from:obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() } } }",i,k];
215                 NSString *theObj = [ITSendAEWithString(sendStr, 'core', 'getd', &savedPSN) stringValue];
216                 ITDebugLog(@" - Adding playlist %@", theObj);
217                 if (theObj) {
218                     [aSource addObject:theObj];
219                 }
220             }
221             [allSources addObject:[aSource autorelease]];
222         } else {
223             ITDebugLog(@"Source at index %i disappeared.", k);
224         }
225     }
226     ITDebugLog(@"Finished getting playlists.");
227     return [allSources autorelease];
228 }
229
230 - (NSArray *)artists
231 {
232     NSAppleEventDescriptor *rawr = ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pArt'), from:obj { form:'indx', want:type('cTrk'), seld:abso($616C6C20$), from:obj { form:'indx', want:type('cPly'), seld:long(1), from:obj { form:'indx', want:type('cSrc'), seld:long(1), from:() } } } }", 'core', 'getd', &savedPSN);
233     int i;
234     NSMutableArray *array = [[NSMutableArray alloc] init];
235     NSArray *returnArray;
236     for (i = 1; i <= [rawr numberOfItems]; i++) {
237         NSString *artist = [[rawr descriptorAtIndex:i] stringValue];
238         if (artist && [artist length] && ![array containsObject:artist]) {
239             [array addObject:artist];
240         }
241     }
242     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
243     returnArray = [NSArray arrayWithArray:array];
244     [array release];
245     return returnArray;
246 }
247
248 - (NSArray *)albums
249 {
250     NSAppleEventDescriptor *rawr = ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pAlb'), from:obj { form:'indx', want:type('cTrk'), seld:abso($616C6C20$), from:obj { form:'indx', want:type('cPly'), seld:long(1), from:obj { form:'indx', want:type('cSrc'), seld:long(1), from:() } } } }", 'core', 'getd', &savedPSN);
251     int i;
252     NSMutableArray *array = [[NSMutableArray alloc] init];
253     NSArray *returnArray;
254     for (i = 1; i <= [rawr numberOfItems]; i++) {
255         NSString *album = [[rawr descriptorAtIndex:i] stringValue];
256         if (album && [album length] && ![array containsObject:album]) {
257             [array addObject:album];
258         }
259     }
260     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
261     returnArray = [NSArray arrayWithArray:array];
262     [array release];
263     return returnArray;
264 }
265
266 - (int)numberOfSongsInPlaylistAtIndex:(int)index
267 {
268         /*
269                 This method only returns the proper number if there's something playing.
270                 This is because it gets the container of the current playlist so that it
271                 gets the playlist index from the current source. Operating this way is fine,
272                 since MT only ever calls this method when there is something playlist.
273                 A working version of this that works in just the main source is in the
274                 makePlaylistWithTerm:ofType: method.
275         */
276     int temp1;
277         NSAppleEventDescriptor *result;
278     ITDebugLog(@"Getting number of songs in playlist at index %i", index);
279         result = ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cTrk'), '----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('ctnr'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }", index], 'core', 'cnte', &savedPSN);
280         temp1 = (result == nil) ? -1 : (int)[result int32Value];
281     ITDebugLog(@"Getting number of songs in playlist at index %i done", index);
282     return temp1;
283 }
284
285 - (ITMTRemotePlayerSource)currentSource
286 {
287     SInt32 fourcc;
288
289     ITDebugLog(@"Getting current source.");   
290     
291     fourcc = ([self isPlaying]) ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pKnd'), from:obj { form:'prop', want:type('prop'), seld:type('ctnr'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }", 'core', 'getd', &savedPSN) int32Value] : 'kLib';
292     
293     switch (fourcc) {
294         case 'kTun':
295             ITDebugLog(@"Getting current source done. Source: Radio.");
296             return ITMTRemoteRadioSource;
297             break;
298         case 'kDev':
299             ITDebugLog(@"Getting current source done. Source: Generic Device.");
300             return ITMTRemoteGenericDeviceSource;
301         case 'kPod':
302             ITDebugLog(@"Getting current source done. Source: iPod.");
303             return ITMTRemoteiPodSource; //this is stupid
304             break;
305         case 'kMCD':
306         case 'kACD':
307             ITDebugLog(@"Getting current source done. Source: CD.");
308             return ITMTRemoteCDSource;
309             break;
310         case 'kShd':
311             ITDebugLog(@"Getting current source done. Source: Shared Library.");
312             return ITMTRemoteSharedLibrarySource;
313             break;
314         case 'kUnk':
315         case 'kLib':
316         default:
317             ITDebugLog(@"Getting current source done. Source: Library.");
318             return ITMTRemoteLibrarySource;
319             break;
320     }
321 }
322
323 - (int)currentSourceIndex
324 {
325     ITDebugLog(@"Getting current source.");
326     return [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('ctnr'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }", 'core', 'getd', &savedPSN) int32Value];
327 }
328
329 - (ITMTRemotePlayerPlaylistClass)currentPlaylistClass
330 {
331     SInt32 realResult;
332     ITDebugLog(@"Getting current playlist class");
333     realResult = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
334     switch (realResult)
335            {
336            case 'cLiP':
337                ITDebugLog(@"Getting current playlist class done. Class: Library.");
338                return ITMTRemotePlayerLibraryPlaylist;
339                break;
340            case 'cRTP':
341                ITDebugLog(@"Getting current playlist class done. Class: Radio.");
342                return ITMTRemotePlayerRadioPlaylist;
343                break;
344            default:
345                ITDebugLog(@"Getting current playlist class done. Class: Standard playlist.");
346                return ITMTRemotePlayerPlaylist;
347            }
348 }
349
350 - (int)currentPlaylistIndex
351 {  
352     int temp1;
353     ITDebugLog(@"Getting current playlist index.");
354     temp1 = ([self isPlaying] ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] : -1);
355     ITDebugLog(@"Getting current playlist index done.");
356     return temp1;
357 }
358
359 - (NSString *)songTitleAtIndex:(int)index
360 {
361     NSString *temp1;
362     ITDebugLog(@"Getting song title at index %i.", index);
363     temp1 = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cTrk'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }",index], 'core', 'getd', &savedPSN) stringValue];
364     ITDebugLog(@"Getting song title at index %i done.", index);
365     return ( ([temp1 length]) ? temp1 : nil ) ;
366 }
367
368 - (int)currentAlbumTrackCount
369 {
370     int temp1;
371     ITDebugLog(@"Getting current album track count.");
372     temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pTrC'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
373     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
374     ITDebugLog(@"Getting current album track count done.");
375     return temp1;
376 }
377
378 - (int)currentSongTrack
379 {
380     int temp1;
381     ITDebugLog(@"Getting current song track.");
382     temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pTrN'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
383     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
384     ITDebugLog(@"Getting current song track done.");
385     return temp1;
386 }
387
388 - (NSString *)playerStateUniqueIdentifier
389 {
390     NSString *temp1;
391     ITDebugLog(@"Getting current unique identifier.");
392         NSAppleEventDescriptor *descriptor = ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN);
393         if ([descriptor int32Value] == 'prop') {
394                 return @"0-0";
395         } else if (descriptor == nil) {
396                 return nil;
397         }
398     SInt32 cls = [descriptor int32Value];
399     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (cls == 'cURT') ) {
400                 NSString *bad = [NSString stringWithUTF8String:"浳湧"];
401         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
402         if ([temp1 isEqualToString:bad]) {
403             temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
404         }
405     } else {
406         temp1 = [NSString stringWithFormat:@"%i-%i", [self currentPlaylistIndex], [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pDID'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value]];
407     }
408     ITDebugLog(@"Getting current unique identifier done.");
409     return ( ([temp1 length]) ? temp1 : nil ) ;
410 }
411
412 - (int)currentSongIndex
413 {
414     int temp1;
415     ITDebugLog(@"Getting current song index.");
416         temp1 = ([self isPlaying] ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] : -1);
417     ITDebugLog(@"Getting current song index done.");
418     return temp1;
419 }
420
421 - (NSString *)currentSongTitle
422 {
423     NSString *temp1;
424     ITDebugLog(@"Getting current song title.");
425     SInt32 result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
426         
427     //If we're listening to the radio.
428     if (result == 'cURT') {
429         NSString *bad = [NSString stringWithUTF8String:"浳湧"];
430         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
431         if ([temp1 isEqualToString:bad]) {
432             temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
433         }
434         temp1 = [temp1 stringByAppendingString:@" (Stream)"];
435     } else if (result == 'prop') {
436                 temp1 = nil;
437         } else {
438         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
439     }
440     ITDebugLog(@"Getting current song title done.");
441     return ( ([temp1 length]) ? temp1 : nil ) ;
442 }
443
444 - (NSString *)currentSongArtist
445 {
446     NSString *temp1;
447     ITDebugLog(@"Getting current song artist.");
448     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
449         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pArt'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
450     } else {
451         temp1 = @"";
452     }
453     ITDebugLog(@"Getting current song artist done.");
454     return ( ([temp1 length]) ? temp1 : nil ) ;
455 }
456
457 - (NSString *)currentSongComposer
458 {
459     NSString *temp1;
460     ITDebugLog(@"Getting current song artist.");
461     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
462         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pCmp'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
463     } else {
464         temp1 = @"";
465     }
466     ITDebugLog(@"Getting current song artist done.");
467     return ( ([temp1 length]) ? temp1 : nil ) ;
468 }
469
470 - (NSString *)currentSongAlbum
471 {
472     NSString *temp1;
473     ITDebugLog(@"Getting current song album.");
474     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
475         temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pAlb'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
476     } else {
477         temp1 = @"";
478     }
479     ITDebugLog(@"Getting current song album done.");
480     return ( ([temp1 length]) ? temp1 : nil ) ;
481 }
482
483 - (NSString *)currentSongGenre
484 {
485     NSString *temp1;
486     ITDebugLog(@"Getting current song genre.");
487     temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pGen'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
488     ITDebugLog(@"Getting current song genre done.");
489     return ( ([temp1 length]) ? temp1 : nil ) ;
490 }
491
492 - (NSString *)currentSongLength
493 {
494     SInt32 temp1;
495     NSString *temp2;
496     ITDebugLog(@"Getting current song length.");
497     temp1 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
498     temp2 = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pTim'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) stringValue];
499     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (temp1 == 'cURT') ) { temp2 = @"Continuous"; }
500     ITDebugLog(@"Getting current song length done.");
501     return temp2;
502 }
503
504 - (NSString *)currentSongRemaining
505 {
506     SInt32 duration, current, final;
507     NSString *finalString;
508     
509     ITDebugLog(@"Getting current song remaining time.");
510     
511     duration = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pDur'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
512     current = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPos'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
513     final = duration - current;
514     finalString = [self formatTimeInSeconds:final];
515     
516     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { finalString = nil; }
517     
518     ITDebugLog(@"Getting current song remaining time done.");
519     
520     return finalString;
521 }
522
523 - (NSString *)currentSongElapsed
524 {
525     long final;
526     NSString *finalString;
527     
528     ITDebugLog(@"Getting current song elapsed time.");
529         final = (long)[ITSendAEWithKey('pPos', 'core', 'getd', &savedPSN) int32Value];
530     finalString = [self formatTimeInSeconds:final];
531     ITDebugLog(@"Getting current song elapsed time done.");
532     return finalString;
533 }
534
535 - (NSImage *)currentSongAlbumArt
536 {
537     ITDebugLog(@"Getting current song album art.");
538     NSData *data = ([self isPlaying]) ? [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPCT'), from:obj { form:'indx', want:type('cArt'), seld:long(1), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } } }", 'core', 'getd', &savedPSN) data] : nil;
539     ITDebugLog(@"Getting current song album art done.");    
540     if (data) {
541         return [[[NSImage alloc] initWithData:data] autorelease];
542     } else {
543         return nil;
544     }
545 }
546
547 - (int)currentSongPlayCount
548 {
549     int count;
550     ITDebugLog(@"Getting current song play count.");
551     count = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPlC'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
552     ITDebugLog(@"Getting current song play count done.");
553     return count;
554 }
555
556 - (float)currentSongRating
557 {
558     float temp1;
559     ITDebugLog(@"Getting current song rating.");
560     temp1 = (![self isPlaying] || ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist)) ? -1.0 : ((float)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pRte'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] / 100.0);
561     ITDebugLog(@"Getting current song rating done.");
562     return temp1;
563 }
564
565 - (BOOL)setCurrentSongRating:(float)rating
566 {
567     ITDebugLog(@"Setting current song rating to %f.", rating);
568     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { return NO; }
569         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pRte'), from:obj { form:'indx', want:type('cTrk'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } } }",(long)(rating*100), [self currentSongIndex]], 'core', 'setd', &savedPSN);
570     ITDebugLog(@"Setting current song rating to %f done.", rating);
571     return YES;
572 }
573
574 - (BOOL)equalizerEnabled
575 {
576     ITDebugLog(@"Getting equalizer enabled status.");
577     int thingy = (int)[ITSendAEWithKey('pEQ ', 'core', 'getd', &savedPSN) int32Value];
578     ITDebugLog(@"Done getting equalizer enabled status.");
579     return (thingy != 0) ? YES : NO;
580 }
581
582 - (BOOL)setEqualizerEnabled:(BOOL)enabled
583 {
584     ITDebugLog(@"Setting equalizer enabled to %i.", enabled);
585         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pEQ '), from:'null'() }", enabled], 'core', 'setd', &savedPSN);
586     ITDebugLog(@"Done setting equalizer enabled to %i.", enabled);
587     return YES;
588 }
589
590 - (NSArray *)eqPresets
591 {
592     int i;
593     SInt32 numPresets = [ITSendAEWithString(@"kocl:type('cEQP'), '----':(), &subj:()", 'core', 'cnte', &savedPSN) int32Value];
594     NSMutableArray *presets = [[NSMutableArray alloc] initWithCapacity:numPresets];
595     ITDebugLog(@"Getting EQ presets");
596     for (i = 1; i <= numPresets; i++) {
597         NSString *theObj = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cEQP'), seld:long(%lu), from:'null'() } }", i], 'core', 'getd', &savedPSN) stringValue];
598         if (theObj) {
599             ITDebugLog(@"Adding preset %@", theObj);
600             [presets addObject:theObj];
601         }
602     }
603     ITDebugLog(@"Done getting EQ presets");
604     return [presets autorelease];
605 }
606
607 - (int)currentEQPresetIndex
608 {
609     int result;
610     ITDebugLog(@"Getting current EQ preset index.");
611     result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pEQP'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
612     ITDebugLog(@"Getting current EQ preset index done.");
613     return result;
614 }
615
616 - (float)volume
617 {
618     ITDebugLog(@"Getting volume.");
619     ITDebugLog(@"Getting volume done.");
620     return (float)[ITSendAEWithKey('pVol', 'core', 'getd', &savedPSN) int32Value] / 100;
621 }
622
623 - (BOOL)setVolume:(float)volume
624 {
625     ITDebugLog(@"Setting volume to %f.", volume);
626         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pVol'), from:'null'() }", (long)(volume * 100)], 'core', 'setd', &savedPSN);
627     ITDebugLog(@"Setting volume to %f done.", volume);
628     return YES;
629 }
630
631 - (BOOL)shuffleEnabled
632 {
633     ITDebugLog(@"Getting shuffle enabled status.");
634     BOOL final;
635     int result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('pPla'), seld:type('pEQP'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
636     if (result != 0) {
637         final = YES;
638     } else {
639         final = NO;
640     }
641     ITDebugLog(@"Getting shuffle enabled status done.");
642     return final;
643 }
644
645 - (BOOL)setShuffleEnabled:(BOOL)enabled
646 {
647     ITDebugLog(@"Set shuffle enabled to %i", enabled);
648         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", (unsigned long)enabled], 'core', 'setd', &savedPSN);
649     ITDebugLog(@"Set shuffle enabled to %i done", enabled);
650     return YES;
651 }
652
653 - (ITMTRemotePlayerRepeatMode)repeatMode
654 {
655     FourCharCode m00f = 0;
656     int result = 0;
657     m00f = (FourCharCode)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('pPla'), seld:type('pEQP'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
658     ITDebugLog(@"Getting repeat mode.");
659     switch (m00f)
660     {
661         //case 'kRp0':
662         case 1800564815:
663             ITDebugLog(@"Repeat off");
664             result = ITMTRemotePlayerRepeatOff;
665             break;
666         case 'kRp1':
667             ITDebugLog(@"Repeat one");
668             result = ITMTRemotePlayerRepeatOne;
669             break;
670         case 'kRpA':
671             ITDebugLog(@"Repeat all");
672             result = ITMTRemotePlayerRepeatAll;
673             break;
674     }
675     ITDebugLog(@"Getting repeat mode done.");
676     return result;
677 }
678
679 - (BOOL)setRepeatMode:(ITMTRemotePlayerRepeatMode)repeatMode
680 {
681     char *m00f;
682     ITDebugLog(@"Setting repeat mode to %i", repeatMode);
683     switch (repeatMode)
684     {
685         case ITMTRemotePlayerRepeatOne:
686             m00f = "kRp1";
687             break;
688         case ITMTRemotePlayerRepeatAll:
689             m00f = "kRpA";
690             break;
691         case ITMTRemotePlayerRepeatOff:
692         default:
693             m00f = "kRp0";
694             break;
695     }
696         ITSendAEWithString([NSString stringWithFormat:@"data:'%s', '----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:() } }", m00f], 'core', 'setd', &savedPSN);
697     ITDebugLog(@"Setting repeat mode to %c done", m00f);
698     return YES;
699 }
700
701 - (BOOL)play
702 {
703     ITDebugLog(@"Play");
704         ITSendAE('hook', 'Play', &savedPSN);
705     ITDebugLog(@"Play done");
706     return YES;
707 }
708
709 - (BOOL)pause
710 {
711     ITDebugLog(@"Pause");
712     ITSendAE('hook', 'Paus', &savedPSN);
713     ITDebugLog(@"Pause done");
714     return YES;
715 }
716
717 - (BOOL)goToNextSong
718 {
719     ITDebugLog(@"Go to next track");
720     ITSendAE('hook', 'Next', &savedPSN);
721     ITDebugLog(@"Go to next track done");
722     return YES;
723 }
724
725 - (BOOL)goToPreviousSong
726 {
727     ITDebugLog(@"Go to previous track");
728     ITSendAE('hook', 'Back', &savedPSN);
729     ITDebugLog(@"Go to previous track done");
730     return YES;
731 }
732
733 - (BOOL)forward
734 {
735     ITDebugLog(@"Fast forward action");
736     ITSendAE('hook', 'Fast', &savedPSN);
737     ITDebugLog(@"Fast forward action done");
738     return YES;
739 }
740
741 - (BOOL)rewind
742 {
743     ITDebugLog(@"Rewind action");
744     ITSendAE('hook', 'Rwnd', &savedPSN);
745     ITDebugLog(@"Rewind action done");
746     return YES;
747 }
748
749 - (BOOL)switchToPlaylistAtIndex:(int)index
750 {
751     ITDebugLog(@"Switching to playlist at index %i", index);
752         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from:() }", index], 'hook', 'Play', &savedPSN);
753     ITDebugLog(@"Done switching to playlist at index %i", index);
754     return YES;
755 }
756
757 - (BOOL)switchToPlaylistAtIndex:(int)index ofSourceAtIndex:(int)index2
758 {
759     ITDebugLog(@"Switching to playlist at index %i of source %i", index, index2);
760         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from: obj { form:'indx', want:type('cSrc'), seld:long(%lu), from:'null'() } }", index - 1, index2 + 1], 'hook', 'Play', &savedPSN);
761     ITDebugLog(@"Done switching to playlist at index %i of source %i", index, index2);
762     return YES;
763 }
764
765 - (BOOL)switchToSongAtIndex:(int)index
766 {
767     ITDebugLog(@"Switching to track at index %i", index);
768         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cTrk'), seld:long(%lu), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:() } }", index], 'hook' ,'Play', &savedPSN);
769     ITDebugLog(@"Done switching to track at index %i", index);
770     return YES;
771 }
772
773 - (BOOL)switchToEQAtIndex:(int)index
774 {
775     ITDebugLog(@"Switching to EQ preset at index %i", index);
776     // index should count from 0, but itunes counts from 1, so let's add 1.
777     [self setEqualizerEnabled:YES];
778         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pEQP'), from:'null'() }, data:obj { form:'indx', want:type('cEQP'), seld:long(%lu), from:'null'() }", (index+1)], 'core', 'setd', &savedPSN);
779     ITDebugLog(@"Done switching to EQ preset at index %i", index);
780     return YES;
781 }
782
783 - (BOOL)makePlaylistWithTerm:(NSString *)term ofType:(int)type
784 {
785     int i;
786         
787     //Get fixed indexing status
788     BOOL fixed = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'getd', &savedPSN) booleanValue];
789     
790     //Enabled fixed indexing
791     ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'setd', &savedPSN);
792     
793     //Search for the term
794     NSAppleEventDescriptor *searchResults = ITSendAEWithString([NSString stringWithFormat:@"pTrm:\"%@\", pAre:'%@', '----':obj { form:'indx', want:type('cPly'), seld:long(1), from:obj { form:'indx', want:type('cSrc'), seld:long(1), from:'null'() } }", term, ((type == 1) ? @"kSrR" : @"kSrL")], 'hook', 'Srch', &savedPSN);
795     
796     //If MenuTunes playlist exists
797     if ([ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'doex', &savedPSN) booleanValue]) {
798         //Clear old MenuTunes playlist
799                 int numSongs = [ITSendAEWithString(@"kocl:type('cTrk'), '----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'cnte', &savedPSN) int32Value];
800         for (i = 1; i <= numSongs; i++) {
801             ITSendAEWithString(@"'----':obj { form:'indx', want:type('cTrk'), seld:long(1), from:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() } }", 'core', 'delo', &savedPSN);
802         }
803     } else {
804         //Create MenuTunes playlist
805         ITSendAEWithString(@"prdt:{ pnam:\"MenuTunes\" }, kocl:type('cPly'), &subj:()", 'core', 'crel', &savedPSN);
806     }
807     
808     //Duplicate search results to playlist
809     for (i = 1; i <= [searchResults numberOfItems]; i++) {
810                 //NSLog(@"%@", ITSendAEWithStringAndParameter(@"'----':obj { form:'prop', want:type('prop'), seld:prop('pnam'), from:aevt(@) }", *[[searchResults descriptorAtIndex:i] aeDesc], 'core', 'getd', &savedPSN));
811         ITSendAEWithStringAndObject(@"insh:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", [[searchResults descriptorAtIndex:i] aeDesc], 'core', 'clon', &savedPSN);
812     }
813     //Reset fixed indexing
814     ITSendAEWithString([NSString stringWithFormat:@"data:long(%i), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", fixed], 'core', 'setd', &savedPSN);
815     
816     //Play MenuTunes playlist
817     ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'hook', 'Play', &savedPSN);
818     
819     return YES;
820 }
821
822 - (BOOL)isPlaying
823 {
824         return ([ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pcls'), from:obj { form:'prop', want:type('prop'), seld:type('pTrk'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value] != 'prop');
825 }
826
827 - (void)notificationHandler:(NSNotification *)note
828 {
829         ITDebugLog(@"Received notification: %@", note);
830         [[NSNotificationCenter defaultCenter] postNotificationName:@"ITMTTrackChanged" object:self userInfo:[note userInfo]];
831         ITDebugLog(@"Handled notification.");
832 }
833
834 - (ProcessSerialNumber)iTunesPSN
835 {
836     /*NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
837     ProcessSerialNumber number;
838     int i;
839     int count = [apps count];
840     
841     number.highLongOfPSN = kNoProcess;
842     
843     for (i = 0; i < count; i++)
844     {
845         NSDictionary *curApp = [apps objectAtIndex:i];
846         
847         if ([[curApp objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"])
848         {
849             number.highLongOfPSN = [[curApp objectForKey:
850                 @"NSApplicationProcessSerialNumberHigh"] intValue];
851             number.lowLongOfPSN = [[curApp objectForKey:
852                 @"NSApplicationProcessSerialNumberLow"] intValue];
853         }
854     }
855     return number;*/
856     ProcessSerialNumber number;
857     number.highLongOfPSN = kNoProcess;
858     number.lowLongOfPSN = 0;
859     ITDebugLog(@"Getting iTunes' PSN.");
860     while ( (GetNextProcess(&number) == noErr) ) 
861     {
862         CFStringRef name;
863         if ( (CopyProcessName(&number, &name) == noErr) )
864         {
865             if ([(NSString *)name isEqualToString:@"iTunes"])
866             {
867                 ITDebugLog(@"iTunes' highLPongOfPSN: %lu.", number.highLongOfPSN);
868                 ITDebugLog(@"iTunes' lowLongOfPSN: %lu.", number.lowLongOfPSN);
869                 ITDebugLog(@"Done getting iTunes' PSN.");
870                 return number;
871             }
872             [(NSString *)name release];
873         }
874     }
875     ITDebugLog(@"Failed getting iTunes' PSN.");
876     return number;
877 }
878
879 - (NSString*)formatTimeInSeconds:(long)seconds {
880     long final = seconds;
881     NSString *finalString;
882     if (final >= 60) {
883         if (final > 3600) {
884             finalString = [NSString stringWithFormat:@"%i:%@:%@",(final / 3600),[self zeroSixty:(int)((final % 3600) / 60)],[self zeroSixty:(int)((final % 3600) % 60)]];
885         } else {
886             finalString = [NSString stringWithFormat:@"%i:%@",(final / 60),[self zeroSixty:(int)(final % 60)]];
887         }
888     } else {
889         finalString = [NSString stringWithFormat:@"0:%@",[self zeroSixty:(int)final]];
890     }
891     return finalString;
892 }
893 - (NSString*)zeroSixty:(int)seconds {
894     if ( (seconds < 10) && (seconds > 0) ) {
895         return [NSString stringWithFormat:@"0%i",seconds];
896     } else if ( (seconds == 0) ) {
897         return [NSString stringWithFormat:@"00"];
898     } else {
899         return [NSString stringWithFormat:@"%i",seconds];
900     }
901 }
902
903 @end