Finally fixed all the memory leaks, I hope. Fixed iTunesRemote to not
[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     return YES;
30 }
31
32 - (BOOL)halt
33 {
34     ITDebugLog(@"iTunesRemote halted");
35     return YES;
36 }
37
38 - (NSString *)playerFullName
39 {
40     return @"iTunes";
41 }
42
43 - (NSString *)playerSimpleName
44 {
45     return @"iTunes";
46 }
47
48 - (NSDictionary *)capabilities
49 {
50     return [NSDictionary dictionaryWithObjectsAndKeys:
51                 [NSNumber numberWithBool: YES], @"Remote",
52                 [NSNumber numberWithBool: YES], @"Basic Track Control",
53                 [NSNumber numberWithBool: YES], @"Track Information",
54                 [NSNumber numberWithBool: YES], @"Track Navigation",
55                 [NSNumber numberWithBool: YES], @"Upcoming Songs",
56                 [NSNumber numberWithBool: YES], @"Playlists",
57                 [NSNumber numberWithBool: YES], @"Volume",
58                 [NSNumber numberWithBool: YES], @"Shuffle",
59                 [NSNumber numberWithBool: YES], @"Repeat Modes",
60                 [NSNumber numberWithBool: YES], @"Equalizer",
61                 [NSNumber numberWithBool: YES], @"Track Rating",
62                 nil];
63 }
64
65 - (BOOL)showPrimaryInterface
66 {
67     ITDebugLog(@"Showing player primary interface.");
68     
69     if ([self playerRunningState] == ITMTRemotePlayerRunning) {
70         ITDebugLog(@"Showing player interface.");
71         //If not minimized and visible
72         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) &&
73                          ([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) &&
74              [[[[NSWorkspace sharedWorkspace] activeApplication] objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"] ) {
75             //set minimized of browser window 1 to true
76                         ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } }", 'core', 'setd', &savedPSN);
77         } else {
78             //set minimized of browser window 1 to false
79                         ITSendAEWithString(@"data:long(0), '----':obj { form:'prop', want:type('prop'), seld:type('pMin'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } }", 'core', 'setd', &savedPSN);
80         }
81         //set visible of browser window 1 to true
82                 ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pvis'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } }", 'core', 'setd', &savedPSN);
83         //active iTunes
84                 ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pisf'), from:'null'() }", 'core', 'setd', &savedPSN);
85         ITDebugLog(@"Done showing player primary interface.");
86         return YES;
87     } else {
88         NSString *path;
89         ITDebugLog(@"Launching player.");
90         if ( (path = [[NSUserDefaults standardUserDefaults] stringForKey:@"CustomPlayerPath"]) ) {
91         } else {
92             path = [self playerFullName];
93         }
94         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
95             ITDebugLog(@"Error Launching Player");
96             return NO;
97         }
98         return YES;
99     }
100 }
101
102 - (ITMTRemotePlayerRunningState)playerRunningState
103 {
104     NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
105     int i;
106     int count = [apps count];
107     
108     for (i = 0; i < count; i++) {
109         if ([[[apps objectAtIndex:i] objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"]) {
110             ITDebugLog(@"Player running state: 1");
111             return ITMTRemotePlayerRunning;
112         }
113     }
114     ITDebugLog(@"Player running state: 0");
115     return ITMTRemotePlayerNotRunning;
116 }
117
118 - (ITMTRemotePlayerPlayingState)playerPlayingState
119 {
120     SInt32 result;
121     
122     ITDebugLog(@"Getting player playing state");
123     result = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pPlS'), from:'null'() }", 'core', 'getd', &savedPSN) int32Value];
124     switch (result)
125     {
126         case 'kPSP':
127             ITDebugLog(@"Getting player playing state done. Player state: Playing");
128             return ITMTRemotePlayerPlaying;
129         case 'kPSp':
130             ITDebugLog(@"Getting player playing state done. Player state: Paused");
131             return ITMTRemotePlayerPaused;
132         case 'kPSR':
133             ITDebugLog(@"Getting player playing state done. Player state: Rewinding");
134             return ITMTRemotePlayerRewinding;
135         case 'kPSF':
136             ITDebugLog(@"Getting player playing state done. Player state: Forwarding");
137             return ITMTRemotePlayerForwarding;
138         case 'kPSS':
139         default:
140             ITDebugLog(@"Getting player playing state done. Player state: Stopped");
141             return ITMTRemotePlayerStopped;
142     }
143     ITDebugLog(@"Getting player playing state done. Player state: Stopped");
144     return ITMTRemotePlayerStopped;
145 }
146
147 /*- (NSArray *)playlists
148 {
149     long i = 0;
150     const signed long numPlaylists = [[ITAppleEventCenter sharedCenter] sendAEWithSendStringForNumber:@"kocl:type('cPly'), '----':()" eventClass:@"core" eventID:@"cnte" appPSN:savedPSN];
151     NSMutableArray *playlists = [[NSMutableArray alloc] initWithCapacity:numPlaylists];
152     
153     for (i = 1; i <= numPlaylists; i++) {
154         const long j = i;
155         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];
156         NSString *theObj = [[ITAppleEventCenter sharedCenter] sendAEWithSendString:sendStr eventClass:@"core" eventID:@"getd" appPSN:savedPSN];
157         [playlists addObject:theObj];
158     }
159     return [playlists autorelease];
160 }*/
161
162 //Full source awareness
163 - (NSArray *)playlists
164 {
165     unsigned long i, k;
166     SInt32 numSources = [ITSendAEWithString(@"kocl:type('cSrc'), '----':()", 'core', 'cnte', &savedPSN) int32Value];
167     NSMutableArray *allSources = [[NSMutableArray alloc] init];
168     
169     ITDebugLog(@"Getting playlists.");
170     if (numSources == 0) {
171                 [allSources release];
172         ITDebugLog(@"No sources.");
173         return nil;
174     }
175     
176     for (k = 1; k <= numSources ; k++) {
177         SInt32 numPlaylists = [ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cPly'), '----':obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() }",k], 'core', 'cnte', &savedPSN) int32Value];
178         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];
179         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];
180         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];
181         unsigned long class;
182         if (sourceName) {
183             NSMutableArray *aSource = [[NSMutableArray alloc] init];
184             [aSource addObject:sourceName];
185             switch (fourcc) {
186                 case 'kTun':
187                     class = ITMTRemoteRadioSource;
188                     break;
189                 case 'kDev':
190                     class = ITMTRemoteGenericDeviceSource;
191                     break;
192                 case 'kPod':
193                     class = ITMTRemoteiPodSource;
194                     break;
195                 case 'kMCD':
196                 case 'kACD':
197                     class = ITMTRemoteCDSource;
198                     break;
199                 case 'kShd':
200                     class = ITMTRemoteSharedLibrarySource;
201                     break;
202                 case 'kUnk':
203                 case 'kLib':
204                 default:
205                     class = ITMTRemoteLibrarySource;
206                     break;
207             }
208             ITDebugLog(@"Adding source %@ of type %i at index %i", sourceName, class, index);
209             [aSource addObject:[NSNumber numberWithInt:class]];
210             [aSource addObject:[NSNumber numberWithInt:index]];
211             for (i = 1; i <= numPlaylists; i++) {
212                 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];
213                 NSString *theObj = [ITSendAEWithString(sendStr, 'core', 'getd', &savedPSN) stringValue];
214                 ITDebugLog(@" - Adding playlist %@", theObj);
215                 if (theObj) {
216                     [aSource addObject:theObj];
217                 }
218             }
219             [allSources addObject:[aSource autorelease]];
220         } else {
221             ITDebugLog(@"Source at index %i disappeared.", k);
222         }
223     }
224     ITDebugLog(@"Finished getting playlists.");
225     return [allSources autorelease];
226 }
227
228 - (NSArray *)artists
229 {
230     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);
231     int i;
232     NSMutableArray *array = [[NSMutableArray alloc] init];
233     NSArray *returnArray;
234     for (i = 1; i <= [rawr numberOfItems]; i++) {
235         NSString *artist = [[rawr descriptorAtIndex:i] stringValue];
236         if (artist && [artist length] && ![array containsObject:artist]) {
237             [array addObject:artist];
238         }
239     }
240     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
241     returnArray = [NSArray arrayWithArray:array];
242     [array release];
243     return returnArray;
244 }
245
246 - (NSArray *)albums
247 {
248     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);
249     int i;
250     NSMutableArray *array = [[NSMutableArray alloc] init];
251     NSArray *returnArray;
252     for (i = 1; i <= [rawr numberOfItems]; i++) {
253         NSString *album = [[rawr descriptorAtIndex:i] stringValue];
254         if (album && [album length] && ![array containsObject:album]) {
255             [array addObject:album];
256         }
257     }
258     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
259     returnArray = [NSArray arrayWithArray:array];
260     [array release];
261     return returnArray;
262 }
263
264 - (int)numberOfSongsInPlaylistAtIndex:(int)index
265 {
266         /*
267                 This method only returns the proper number if there's something playing.
268                 This is because it gets the container of the current playlist so that it
269                 gets the playlist index from the current source. Operating this way is fine,
270                 since MT only ever calls this method when there is something playlist.
271                 A working version of this that works in just the main source is in the
272                 makePlaylistWithTerm:ofType: method.
273         */
274     int temp1;
275         NSAppleEventDescriptor *result;
276     ITDebugLog(@"Getting number of songs in playlist at index %i", index);
277         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);
278         temp1 = (result == nil) ? -1 : (int)[result int32Value];
279     ITDebugLog(@"Getting number of songs in playlist at index %i done", index);
280     return temp1;
281 }
282
283 - (ITMTRemotePlayerSource)currentSource
284 {
285     SInt32 fourcc;
286
287     ITDebugLog(@"Getting current source.");   
288     
289     fourcc = [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];
290     
291     switch (fourcc) {
292         case 'kTun':
293             ITDebugLog(@"Getting current source done. Source: Radio.");
294             return ITMTRemoteRadioSource;
295             break;
296         case 'kDev':
297             ITDebugLog(@"Getting current source done. Source: Generic Device.");
298             return ITMTRemoteGenericDeviceSource;
299         case 'kPod':
300             ITDebugLog(@"Getting current source done. Source: iPod.");
301             return ITMTRemoteiPodSource; //this is stupid
302             break;
303         case 'kMCD':
304         case 'kACD':
305             ITDebugLog(@"Getting current source done. Source: CD.");
306             return ITMTRemoteCDSource;
307             break;
308         case 'kShd':
309             ITDebugLog(@"Getting current source done. Source: Shared Library.");
310             return ITMTRemoteSharedLibrarySource;
311             break;
312         case 'kUnk':
313         case 'kLib':
314         default:
315             ITDebugLog(@"Getting current source done. Source: Library.");
316             return ITMTRemoteLibrarySource;
317             break;
318     }
319 }
320
321 - (int)currentSourceIndex
322 {
323     ITDebugLog(@"Getting current source.");
324     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];
325 }
326
327 - (ITMTRemotePlayerPlaylistClass)currentPlaylistClass
328 {
329     SInt32 realResult;
330     ITDebugLog(@"Getting current playlist class");
331     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];
332     switch (realResult)
333            {
334            case 'cLiP':
335                ITDebugLog(@"Getting current playlist class done. Class: Library.");
336                return ITMTRemotePlayerLibraryPlaylist;
337                break;
338            case 'cRTP':
339                ITDebugLog(@"Getting current playlist class done. Class: Radio.");
340                return ITMTRemotePlayerRadioPlaylist;
341                break;
342            default:
343                ITDebugLog(@"Getting current playlist class done. Class: Standard playlist.");
344                return ITMTRemotePlayerPlaylist;
345            }
346 }
347
348 - (int)currentPlaylistIndex
349 {  
350     int temp1;
351     ITDebugLog(@"Getting current playlist index.");
352     temp1 = [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];
353     ITDebugLog(@"Getting current playlist index done.");
354     return temp1;
355 }
356
357 - (NSString *)songTitleAtIndex:(int)index
358 {
359     NSString *temp1;
360     ITDebugLog(@"Getting song title at index %i.", index);
361     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];
362     ITDebugLog(@"Getting song title at index %i done.", index);
363     return ( ([temp1 length]) ? temp1 : nil ) ;
364 }
365
366 - (int)currentAlbumTrackCount
367 {
368     int temp1;
369     ITDebugLog(@"Getting current album track count.");
370     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];
371     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
372     ITDebugLog(@"Getting current album track count done.");
373     return temp1;
374 }
375
376 - (int)currentSongTrack
377 {
378     int temp1;
379     ITDebugLog(@"Getting current song track.");
380     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];
381     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
382     ITDebugLog(@"Getting current song track done.");
383     return temp1;
384 }
385
386 - (NSString *)playerStateUniqueIdentifier
387 {
388     NSString *temp1;
389     ITDebugLog(@"Getting current unique identifier.");
390         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);
391         if ((descriptor == nil) || ([descriptor int32Value] == 'prop')) {
392                 return @"0-0";
393         }
394     SInt32 cls = [descriptor int32Value];
395     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (cls == 'cURT') ) {
396                 NSString *bad = [NSString stringWithUTF8String:"浳湧"];
397         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
398         if ([temp1 isEqualToString:bad]) {
399             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];
400         }
401     } else {
402         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]];
403     }
404     ITDebugLog(@"Getting current unique identifier done.");
405     return ( ([temp1 length]) ? temp1 : nil ) ;
406 }
407
408 - (int)currentSongIndex
409 {
410     int temp1;
411     ITDebugLog(@"Getting current song index.");
412         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] != 'prop') ? [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);
413     ITDebugLog(@"Getting current song index done.");
414     return temp1;
415 }
416
417 - (NSString *)currentSongTitle
418 {
419     NSString *temp1;
420     ITDebugLog(@"Getting current song title.");
421     
422     //If we're listening to the radio.
423     if ([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] == 'cURT') {
424         NSString *bad = [NSString stringWithUTF8String:"浳湧"];
425         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
426         if ([temp1 isEqualToString:bad]) {
427             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];
428         }
429         temp1 = [temp1 stringByAppendingString:@" (Stream)"];
430     } else {
431         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];
432     }
433     ITDebugLog(@"Getting current song title done.");
434     return ( ([temp1 length]) ? temp1 : nil ) ;
435 }
436
437 - (NSString *)currentSongArtist
438 {
439     NSString *temp1;
440     ITDebugLog(@"Getting current song artist.");
441     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
442         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];
443     } else {
444         temp1 = @"";
445     }
446     ITDebugLog(@"Getting current song artist done.");
447     return ( ([temp1 length]) ? temp1 : nil ) ;
448 }
449
450 - (NSString *)currentSongComposer
451 {
452     NSString *temp1;
453     ITDebugLog(@"Getting current song artist.");
454     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
455         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];
456     } else {
457         temp1 = @"";
458     }
459     ITDebugLog(@"Getting current song artist done.");
460     return ( ([temp1 length]) ? temp1 : nil ) ;
461 }
462
463 - (NSString *)currentSongAlbum
464 {
465     NSString *temp1;
466     ITDebugLog(@"Getting current song album.");
467     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
468         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];
469     } else {
470         temp1 = @"";
471     }
472     ITDebugLog(@"Getting current song album done.");
473     return ( ([temp1 length]) ? temp1 : nil ) ;
474 }
475
476 - (NSString *)currentSongGenre
477 {
478     NSString *temp1;
479     ITDebugLog(@"Getting current song genre.");
480     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];
481     ITDebugLog(@"Getting current song genre done.");
482     return ( ([temp1 length]) ? temp1 : nil ) ;
483 }
484
485 - (NSString *)currentSongLength
486 {
487     SInt32 temp1;
488     NSString *temp2;
489     ITDebugLog(@"Getting current song length.");
490     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];
491     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];
492     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (temp1 == 'cURT') ) { temp2 = @"Continuous"; }
493     ITDebugLog(@"Getting current song length done.");
494     return temp2;
495 }
496
497 - (NSString *)currentSongRemaining
498 {
499     SInt32 duration, current, final;
500     NSString *finalString;
501     
502     ITDebugLog(@"Getting current song remaining time.");
503     
504     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];
505     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];
506     final = duration - current;
507     finalString = [self formatTimeInSeconds:final];
508     
509     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { finalString = nil; }
510     
511     ITDebugLog(@"Getting current song remaining time done.");
512     
513     return finalString;
514 }
515
516 - (NSString *)currentSongElapsed
517 {
518     long final;
519     NSString *finalString;
520     
521     ITDebugLog(@"Getting current song elapsed time.");
522         final = (long)[ITSendAEWithKey('pPos', 'core', 'getd', &savedPSN) int32Value];
523     finalString = [self formatTimeInSeconds:final];
524     ITDebugLog(@"Getting current song elapsed time done.");
525     return finalString;
526 }
527
528 - (NSImage *)currentSongAlbumArt
529 {
530     ITDebugLog(@"Getting current song album art.");
531     NSData *data = [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];
532     ITDebugLog(@"Getting current song album art done.");    
533     if (data) {
534         return [[[NSImage alloc] initWithData:data] autorelease];
535     } else {
536         return nil;
537     }
538 }
539
540 - (int)currentSongPlayCount
541 {
542     int count;
543     ITDebugLog(@"Getting current song play count.");
544     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];
545     ITDebugLog(@"Getting current song play count done.");
546     return count;
547 }
548
549 - (float)currentSongRating
550 {
551     float temp1;
552     ITDebugLog(@"Getting current song rating.");
553     temp1 = ((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);
554     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = -1.0; }
555     ITDebugLog(@"Getting current song rating done.");
556     return temp1;
557 }
558
559 - (BOOL)setCurrentSongRating:(float)rating
560 {
561     ITDebugLog(@"Setting current song rating to %f.", rating);
562     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { return NO; }
563         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);
564     ITDebugLog(@"Setting current song rating to %f done.", rating);
565     return YES;
566 }
567
568 - (BOOL)equalizerEnabled
569 {
570     ITDebugLog(@"Getting equalizer enabled status.");
571     int thingy = (int)[ITSendAEWithKey('pEQ ', 'core', 'getd', &savedPSN) int32Value];
572     ITDebugLog(@"Done getting equalizer enabled status.");
573     return (thingy != 0) ? YES : NO;
574 }
575
576 - (BOOL)setEqualizerEnabled:(BOOL)enabled
577 {
578     ITDebugLog(@"Setting equalizer enabled to %i.", enabled);
579         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pEQ '), from:'null'() }", enabled], 'core', 'setd', &savedPSN);
580     ITDebugLog(@"Done setting equalizer enabled to %i.", enabled);
581     return YES;
582 }
583
584 - (NSArray *)eqPresets
585 {
586     int i;
587     SInt32 numPresets = [ITSendAEWithString(@"kocl:type('cEQP'), '----':(), &subj:()", 'core', 'cnte', &savedPSN) int32Value];
588     NSMutableArray *presets = [[NSMutableArray alloc] initWithCapacity:numPresets];
589     ITDebugLog(@"Getting EQ presets");
590     for (i = 1; i <= numPresets; i++) {
591         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];
592         if (theObj) {
593             ITDebugLog(@"Adding preset %@", theObj);
594             [presets addObject:theObj];
595         }
596     }
597     ITDebugLog(@"Done getting EQ presets");
598     return [presets autorelease];
599 }
600
601 - (int)currentEQPresetIndex
602 {
603     int result;
604     ITDebugLog(@"Getting current EQ preset index.");
605     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];
606     ITDebugLog(@"Getting current EQ preset index done.");
607     return result;
608 }
609
610 - (float)volume
611 {
612     ITDebugLog(@"Getting volume.");
613     ITDebugLog(@"Getting volume done.");
614     return (float)[ITSendAEWithKey('pVol', 'core', 'getd', &savedPSN) int32Value] / 100;
615 }
616
617 - (BOOL)setVolume:(float)volume
618 {
619     ITDebugLog(@"Setting volume to %f.", volume);
620         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pVol'), from:'null'() }", (long)(volume * 100)], 'core', 'setd', &savedPSN);
621     ITDebugLog(@"Setting volume to %f done.", volume);
622     return YES;
623 }
624
625 - (BOOL)shuffleEnabled
626 {
627     ITDebugLog(@"Getting shuffle enabled status.");
628     BOOL final;
629     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];
630     if (result != 0) {
631         final = YES;
632     } else {
633         final = NO;
634     }
635     ITDebugLog(@"Getting shuffle enabled status done.");
636     return final;
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('pPla'), seld:type('pEQP'), 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', 'Prev', &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         ITSendAEWithStringAndObject(@"insh:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", [[searchResults descriptorAtIndex:i] aeDesc], 'core', 'clon', &savedPSN);
805     }
806     //Reset fixed indexing
807     ITSendAEWithString([NSString stringWithFormat:@"data:long(%i), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", fixed], 'core', 'setd', &savedPSN);
808     
809     //Play MenuTunes playlist
810     ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'hook', 'Play', &savedPSN);
811     
812     return YES;
813 }
814
815 - (ProcessSerialNumber)iTunesPSN
816 {
817     /*NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
818     ProcessSerialNumber number;
819     int i;
820     int count = [apps count];
821     
822     number.highLongOfPSN = kNoProcess;
823     
824     for (i = 0; i < count; i++)
825     {
826         NSDictionary *curApp = [apps objectAtIndex:i];
827         
828         if ([[curApp objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"])
829         {
830             number.highLongOfPSN = [[curApp objectForKey:
831                 @"NSApplicationProcessSerialNumberHigh"] intValue];
832             number.lowLongOfPSN = [[curApp objectForKey:
833                 @"NSApplicationProcessSerialNumberLow"] intValue];
834         }
835     }
836     return number;*/
837     ProcessSerialNumber number;
838     number.highLongOfPSN = kNoProcess;
839     number.lowLongOfPSN = 0;
840     ITDebugLog(@"Getting iTunes' PSN.");
841     while ( (GetNextProcess(&number) == noErr) ) 
842     {
843         CFStringRef name;
844         if ( (CopyProcessName(&number, &name) == noErr) )
845         {
846             if ([(NSString *)name isEqualToString:@"iTunes"])
847             {
848                 ITDebugLog(@"iTunes' highLPongOfPSN: %lu.", number.highLongOfPSN);
849                 ITDebugLog(@"iTunes' lowLongOfPSN: %lu.", number.lowLongOfPSN);
850                 ITDebugLog(@"Done getting iTunes' PSN.");
851                 return number;
852             }
853             [(NSString *)name release];
854         }
855     }
856     ITDebugLog(@"Failed getting iTunes' PSN.");
857     return number;
858 }
859
860 - (NSString*)formatTimeInSeconds:(long)seconds {
861     long final = seconds;
862     NSString *finalString;
863     if (final >= 60) {
864         if (final > 3600) {
865             finalString = [NSString stringWithFormat:@"%i:%@:%@",(final / 3600),[self zeroSixty:(int)((final % 3600) / 60)],[self zeroSixty:(int)((final % 3600) % 60)]];
866         } else {
867             finalString = [NSString stringWithFormat:@"%i:%@",(final / 60),[self zeroSixty:(int)(final % 60)]];
868         }
869     } else {
870         finalString = [NSString stringWithFormat:@"0:%@",[self zeroSixty:(int)final]];
871     }
872     return finalString;
873 }
874 - (NSString*)zeroSixty:(int)seconds {
875     if ( (seconds < 10) && (seconds > 0) ) {
876         return [NSString stringWithFormat:@"0%i",seconds];
877     } else if ( (seconds == 0) ) {
878         return [NSString stringWithFormat:@"00"];
879     } else {
880         return [NSString stringWithFormat:@"%i",seconds];
881     }
882 }
883
884 @end