playerPlayingState works properly in 10.4 now. It should work in 10.3/10.2 fine also...
[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) typeCodeValue];
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         int result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
635     ITDebugLog(@"Getting shuffle enabled status done.");
636     return (result != 0);
637 }
638
639 - (BOOL)setShuffleEnabled:(BOOL)enabled
640 {
641     ITDebugLog(@"Set shuffle enabled to %i", enabled);
642         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);
643     ITDebugLog(@"Set shuffle enabled to %i done", enabled);
644     return YES;
645 }
646
647 - (ITMTRemotePlayerRepeatMode)repeatMode
648 {
649     FourCharCode m00f = 0;
650     int result = 0;
651     m00f = (FourCharCode)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPla'), from:'null'() } }", 'core', 'getd', &savedPSN) int32Value];
652     ITDebugLog(@"Getting repeat mode.");
653     switch (m00f)
654     {
655         //case 'kRp0':
656         case 1800564815:
657             ITDebugLog(@"Repeat off");
658             result = ITMTRemotePlayerRepeatOff;
659             break;
660         case 'kRp1':
661             ITDebugLog(@"Repeat one");
662             result = ITMTRemotePlayerRepeatOne;
663             break;
664         case 'kRpA':
665             ITDebugLog(@"Repeat all");
666             result = ITMTRemotePlayerRepeatAll;
667             break;
668     }
669     ITDebugLog(@"Getting repeat mode done.");
670     return result;
671 }
672
673 - (BOOL)setRepeatMode:(ITMTRemotePlayerRepeatMode)repeatMode
674 {
675     char *m00f;
676     ITDebugLog(@"Setting repeat mode to %i", repeatMode);
677     switch (repeatMode)
678     {
679         case ITMTRemotePlayerRepeatOne:
680             m00f = "kRp1";
681             break;
682         case ITMTRemotePlayerRepeatAll:
683             m00f = "kRpA";
684             break;
685         case ITMTRemotePlayerRepeatOff:
686         default:
687             m00f = "kRp0";
688             break;
689     }
690         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);
691     ITDebugLog(@"Setting repeat mode to %c done", m00f);
692     return YES;
693 }
694
695 - (BOOL)play
696 {
697     ITDebugLog(@"Play");
698         ITSendAE('hook', 'Play', &savedPSN);
699     ITDebugLog(@"Play done");
700     return YES;
701 }
702
703 - (BOOL)pause
704 {
705     ITDebugLog(@"Pause");
706     ITSendAE('hook', 'Paus', &savedPSN);
707     ITDebugLog(@"Pause done");
708     return YES;
709 }
710
711 - (BOOL)goToNextSong
712 {
713     ITDebugLog(@"Go to next track");
714     ITSendAE('hook', 'Next', &savedPSN);
715     ITDebugLog(@"Go to next track done");
716     return YES;
717 }
718
719 - (BOOL)goToPreviousSong
720 {
721     ITDebugLog(@"Go to previous track");
722     ITSendAE('hook', 'Back', &savedPSN);
723     ITDebugLog(@"Go to previous track done");
724     return YES;
725 }
726
727 - (BOOL)forward
728 {
729     ITDebugLog(@"Fast forward action");
730     ITSendAE('hook', 'Fast', &savedPSN);
731     ITDebugLog(@"Fast forward action done");
732     return YES;
733 }
734
735 - (BOOL)rewind
736 {
737     ITDebugLog(@"Rewind action");
738     ITSendAE('hook', 'Rwnd', &savedPSN);
739     ITDebugLog(@"Rewind action done");
740     return YES;
741 }
742
743 - (BOOL)switchToPlaylistAtIndex:(int)index
744 {
745     ITDebugLog(@"Switching to playlist at index %i", index);
746         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from:() }", index], 'hook', 'Play', &savedPSN);
747     ITDebugLog(@"Done switching to playlist at index %i", index);
748     return YES;
749 }
750
751 - (BOOL)switchToPlaylistAtIndex:(int)index ofSourceAtIndex:(int)index2
752 {
753     ITDebugLog(@"Switching to playlist at index %i of source %i", index, index2);
754         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);
755     ITDebugLog(@"Done switching to playlist at index %i of source %i", index, index2);
756     return YES;
757 }
758
759 - (BOOL)switchToSongAtIndex:(int)index
760 {
761     ITDebugLog(@"Switching to track at index %i", index);
762         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);
763     ITDebugLog(@"Done switching to track at index %i", index);
764     return YES;
765 }
766
767 - (BOOL)switchToEQAtIndex:(int)index
768 {
769     ITDebugLog(@"Switching to EQ preset at index %i", index);
770     // index should count from 0, but itunes counts from 1, so let's add 1.
771     [self setEqualizerEnabled:YES];
772         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);
773     ITDebugLog(@"Done switching to EQ preset at index %i", index);
774     return YES;
775 }
776
777 - (BOOL)makePlaylistWithTerm:(NSString *)term ofType:(int)type
778 {
779     int i;
780         
781     //Get fixed indexing status
782     BOOL fixed = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'getd', &savedPSN) booleanValue];
783     
784     //Enabled fixed indexing
785     ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'setd', &savedPSN);
786     
787     //Search for the term
788     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);
789     
790     //If MenuTunes playlist exists
791     if ([ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'doex', &savedPSN) booleanValue]) {
792         //Clear old MenuTunes playlist
793                 int numSongs = [ITSendAEWithString(@"kocl:type('cTrk'), '----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'cnte', &savedPSN) int32Value];
794         for (i = 1; i <= numSongs; i++) {
795             ITSendAEWithString(@"'----':obj { form:'indx', want:type('cTrk'), seld:long(1), from:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() } }", 'core', 'delo', &savedPSN);
796         }
797     } else {
798         //Create MenuTunes playlist
799         ITSendAEWithString(@"prdt:{ pnam:\"MenuTunes\" }, kocl:type('cPly'), &subj:()", 'core', 'crel', &savedPSN);
800     }
801     
802     //Duplicate search results to playlist
803     for (i = 1; i <= [searchResults numberOfItems]; i++) {
804                 //NSLog(@"%@", ITSendAEWithStringAndParameter(@"'----':obj { form:'prop', want:type('prop'), seld:prop('pnam'), from:aevt(@) }", *[[searchResults descriptorAtIndex:i] aeDesc], 'core', 'getd', &savedPSN));
805         ITSendAEWithStringAndObject(@"insh:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", [[searchResults descriptorAtIndex:i] aeDesc], 'core', 'clon', &savedPSN);
806     }
807     //Reset fixed indexing
808     ITSendAEWithString([NSString stringWithFormat:@"data:long(%i), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", fixed], 'core', 'setd', &savedPSN);
809     
810     //Play MenuTunes playlist
811     ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'hook', 'Play', &savedPSN);
812     
813     return YES;
814 }
815
816 - (BOOL)isPlaying
817 {
818         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');
819 }
820
821 - (void)notificationHandler:(NSNotification *)note
822 {
823         ITDebugLog(@"Received notification: %@", note);
824         [[NSNotificationCenter defaultCenter] postNotificationName:@"ITMTTrackChanged" object:self userInfo:[note userInfo]];
825         ITDebugLog(@"Handled notification.");
826 }
827
828 - (ProcessSerialNumber)iTunesPSN
829 {
830     /*NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
831     ProcessSerialNumber number;
832     int i;
833     int count = [apps count];
834     
835     number.highLongOfPSN = kNoProcess;
836     
837     for (i = 0; i < count; i++)
838     {
839         NSDictionary *curApp = [apps objectAtIndex:i];
840         
841         if ([[curApp objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"])
842         {
843             number.highLongOfPSN = [[curApp objectForKey:
844                 @"NSApplicationProcessSerialNumberHigh"] intValue];
845             number.lowLongOfPSN = [[curApp objectForKey:
846                 @"NSApplicationProcessSerialNumberLow"] intValue];
847         }
848     }
849     return number;*/
850     ProcessSerialNumber number;
851     number.highLongOfPSN = kNoProcess;
852     number.lowLongOfPSN = 0;
853     ITDebugLog(@"Getting iTunes' PSN.");
854     while ( (GetNextProcess(&number) == noErr) ) 
855     {
856         CFStringRef name;
857         if ( (CopyProcessName(&number, &name) == noErr) )
858         {
859             if ([(NSString *)name isEqualToString:@"iTunes"])
860             {
861                 ITDebugLog(@"iTunes' highLPongOfPSN: %lu.", number.highLongOfPSN);
862                 ITDebugLog(@"iTunes' lowLongOfPSN: %lu.", number.lowLongOfPSN);
863                 ITDebugLog(@"Done getting iTunes' PSN.");
864                 return number;
865             }
866             [(NSString *)name release];
867         }
868     }
869     ITDebugLog(@"Failed getting iTunes' PSN.");
870     return number;
871 }
872
873 - (NSString*)formatTimeInSeconds:(long)seconds {
874     long final = seconds;
875     NSString *finalString;
876     if (final >= 60) {
877         if (final > 3600) {
878             finalString = [NSString stringWithFormat:@"%i:%@:%@",(final / 3600),[self zeroSixty:(int)((final % 3600) / 60)],[self zeroSixty:(int)((final % 3600) % 60)]];
879         } else {
880             finalString = [NSString stringWithFormat:@"%i:%@",(final / 60),[self zeroSixty:(int)(final % 60)]];
881         }
882     } else {
883         finalString = [NSString stringWithFormat:@"0:%@",[self zeroSixty:(int)final]];
884     }
885     return finalString;
886 }
887 - (NSString*)zeroSixty:(int)seconds {
888     if ( (seconds < 10) && (seconds > 0) ) {
889         return [NSString stringWithFormat:@"0%i",seconds];
890     } else if ( (seconds == 0) ) {
891         return [NSString stringWithFormat:@"00"];
892     } else {
893         return [NSString stringWithFormat:@"%i",seconds];
894     }
895 }
896
897 @end