More fixes on timeout poop
[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         ITDebugLog(@"No sources.");
172         return nil;
173     }
174     
175     for (k = 1; k <= numSources ; k++) {
176         SInt32 numPlaylists = [ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cPly'), '----':obj { form:'indx', want:type('cSrc'), seld:long(%u), from:() }",k], 'core', 'cnte', &savedPSN) int32Value];
177         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];
178         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];
179         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];
180         unsigned long class;
181         if (sourceName) {
182             NSMutableArray *aSource = [[NSMutableArray alloc] init];
183             [aSource addObject:sourceName];
184             switch (fourcc) {
185                 case 'kTun':
186                     class = ITMTRemoteRadioSource;
187                     break;
188                 case 'kDev':
189                     class = ITMTRemoteGenericDeviceSource;
190                     break;
191                 case 'kPod':
192                     class = ITMTRemoteiPodSource;
193                     break;
194                 case 'kMCD':
195                 case 'kACD':
196                     class = ITMTRemoteCDSource;
197                     break;
198                 case 'kShd':
199                     class = ITMTRemoteSharedLibrarySource;
200                     break;
201                 case 'kUnk':
202                 case 'kLib':
203                 default:
204                     class = ITMTRemoteLibrarySource;
205                     break;
206             }
207             ITDebugLog(@"Adding source %@ of type %i at index %i", sourceName, class, index);
208             [aSource addObject:[NSNumber numberWithInt:class]];
209             [aSource addObject:[NSNumber numberWithInt:index]];
210             for (i = 1; i <= numPlaylists; i++) {
211                 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];
212                 NSString *theObj = [ITSendAEWithString(sendStr, 'core', 'getd', &savedPSN) stringValue];
213                 ITDebugLog(@" - Adding playlist %@", theObj);
214                 if (theObj) {
215                     [aSource addObject:theObj];
216                 }
217             }
218             [allSources addObject:[aSource autorelease]];
219         } else {
220             ITDebugLog(@"Source at index %i disappeared.", k);
221         }
222     }
223     ITDebugLog(@"Finished getting playlists.");
224     return [allSources autorelease];
225 }
226
227 - (NSArray *)artists
228 {
229     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);
230     int i;
231     NSMutableArray *array = [[NSMutableArray alloc] init];
232     NSArray *returnArray;
233     for (i = 1; i <= [rawr numberOfItems]; i++) {
234         NSString *artist = [[rawr descriptorAtIndex:i] stringValue];
235         if (artist && [artist length] && ![array containsObject:artist]) {
236             [array addObject:artist];
237         }
238     }
239     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
240     returnArray = [NSArray arrayWithArray:array];
241     [array release];
242     return returnArray;
243 }
244
245 - (NSArray *)albums
246 {
247     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);
248     int i;
249     NSMutableArray *array = [[NSMutableArray alloc] init];
250     NSArray *returnArray;
251     for (i = 1; i <= [rawr numberOfItems]; i++) {
252         NSString *album = [[rawr descriptorAtIndex:i] stringValue];
253         if (album && [album length] && ![array containsObject:album]) {
254             [array addObject:album];
255         }
256     }
257     [array sortUsingSelector:@selector(caseInsensitiveCompare:)];
258     returnArray = [NSArray arrayWithArray:array];
259     [array release];
260     return returnArray;
261 }
262
263 - (int)numberOfSongsInPlaylistAtIndex:(int)index
264 {
265         /*
266                 This method only returns the proper number if there's something playing.
267                 This is because it gets the container of the current playlist so that it
268                 gets the playlist index from the current source. Operating this way is fine,
269                 since MT only ever calls this method when there is something playlist.
270                 A working version of this that works in just the main source is in the
271                 makePlaylistWithTerm:ofType: method.
272         */
273     int temp1;
274     ITDebugLog(@"Getting number of songs in playlist at index %i", index);
275     temp1 = (int)[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) int32Value];
276     ITDebugLog(@"Getting number of songs in playlist at index %i done", index);
277     return temp1;
278 }
279
280 - (ITMTRemotePlayerSource)currentSource
281 {
282     SInt32 fourcc;
283
284     ITDebugLog(@"Getting current source.");   
285     
286     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];
287     
288     switch (fourcc) {
289         case 'kTun':
290             ITDebugLog(@"Getting current source done. Source: Radio.");
291             return ITMTRemoteRadioSource;
292             break;
293         case 'kDev':
294             ITDebugLog(@"Getting current source done. Source: Generic Device.");
295             return ITMTRemoteGenericDeviceSource;
296         case 'kPod':
297             ITDebugLog(@"Getting current source done. Source: iPod.");
298             return ITMTRemoteiPodSource; //this is stupid
299             break;
300         case 'kMCD':
301         case 'kACD':
302             ITDebugLog(@"Getting current source done. Source: CD.");
303             return ITMTRemoteCDSource;
304             break;
305         case 'kShd':
306             ITDebugLog(@"Getting current source done. Source: Shared Library.");
307             return ITMTRemoteSharedLibrarySource;
308             break;
309         case 'kUnk':
310         case 'kLib':
311         default:
312             ITDebugLog(@"Getting current source done. Source: Library.");
313             return ITMTRemoteLibrarySource;
314             break;
315     }
316 }
317
318 - (int)currentSourceIndex
319 {
320     ITDebugLog(@"Getting current source.");
321     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];
322 }
323
324 - (ITMTRemotePlayerPlaylistClass)currentPlaylistClass
325 {
326     SInt32 realResult;
327     ITDebugLog(@"Getting current playlist class");
328     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];
329     switch (realResult)
330            {
331            case 'cLiP':
332                ITDebugLog(@"Getting current playlist class done. Class: Library.");
333                return ITMTRemotePlayerLibraryPlaylist;
334                break;
335            case 'cRTP':
336                ITDebugLog(@"Getting current playlist class done. Class: Radio.");
337                return ITMTRemotePlayerRadioPlaylist;
338                break;
339            default:
340                ITDebugLog(@"Getting current playlist class done. Class: Standard playlist.");
341                return ITMTRemotePlayerPlaylist;
342            }
343 }
344
345 - (int)currentPlaylistIndex
346 {  
347     int temp1;
348     ITDebugLog(@"Getting current playlist index.");
349     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];
350     ITDebugLog(@"Getting current playlist index done.");
351     return temp1;
352 }
353
354 - (NSString *)songTitleAtIndex:(int)index
355 {
356     NSString *temp1;
357     ITDebugLog(@"Getting song title at index %i.", index);
358     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];
359     ITDebugLog(@"Getting song title at index %i done.", index);
360     return ( ([temp1 length]) ? temp1 : nil ) ;
361 }
362
363 - (int)currentAlbumTrackCount
364 {
365     int temp1;
366     ITDebugLog(@"Getting current album track count.");
367     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];
368     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
369     ITDebugLog(@"Getting current album track count done.");
370     return temp1;
371 }
372
373 - (int)currentSongTrack
374 {
375     int temp1;
376     ITDebugLog(@"Getting current song track.");
377     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];
378     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = 0; }
379     ITDebugLog(@"Getting current song track done.");
380     return temp1;
381 }
382
383 - (NSString *)playerStateUniqueIdentifier
384 {
385     NSString *temp1;
386     ITDebugLog(@"Getting current unique identifier.");
387         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);
388         if (descriptor == nil) {
389                 return nil;
390         }
391     SInt32 cls = [descriptor int32Value];
392     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (cls == 'cURT') ) {
393                 NSString *bad = [NSString stringWithUTF8String:"浳湧"];
394         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
395         if ([temp1 isEqualToString:bad]) {
396             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];
397         }
398     } else {
399         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]];
400     }
401     ITDebugLog(@"Getting current unique identifier done.");
402     return ( ([temp1 length]) ? temp1 : nil ) ;
403 }
404
405 - (int)currentSongIndex
406 {
407     int temp1;
408     ITDebugLog(@"Getting current song index.");
409     temp1 = [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];
410     ITDebugLog(@"Getting current song index done.");
411     return temp1;
412 }
413
414 - (NSString *)currentSongTitle
415 {
416     NSString *temp1;
417     ITDebugLog(@"Getting current song title.");
418     
419     //If we're listening to the radio.
420     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') {
421         NSString *bad = [NSString stringWithUTF8String:"浳湧"];
422         temp1 = [ITSendAEWithKey('pStT', 'core', 'getd', &savedPSN) stringValue];
423         if ([temp1 isEqualToString:bad]) {
424             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];
425         }
426         temp1 = [temp1 stringByAppendingString:@" (Stream)"];
427     } else {
428         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];
429     }
430     ITDebugLog(@"Getting current song title done.");
431     return ( ([temp1 length]) ? temp1 : nil ) ;
432 }
433
434 - (NSString *)currentSongArtist
435 {
436     NSString *temp1;
437     ITDebugLog(@"Getting current song artist.");
438     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
439         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];
440     } else {
441         temp1 = @"";
442     }
443     ITDebugLog(@"Getting current song artist done.");
444     return ( ([temp1 length]) ? temp1 : nil ) ;
445 }
446
447 - (NSString *)currentSongComposer
448 {
449     NSString *temp1;
450     ITDebugLog(@"Getting current song artist.");
451     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
452         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];
453     } else {
454         temp1 = @"";
455     }
456     ITDebugLog(@"Getting current song artist done.");
457     return ( ([temp1 length]) ? temp1 : nil ) ;
458 }
459
460 - (NSString *)currentSongAlbum
461 {
462     NSString *temp1;
463     ITDebugLog(@"Getting current song album.");
464     if ( [self currentPlaylistClass] != ITMTRemotePlayerRadioPlaylist ) {
465         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];
466     } else {
467         temp1 = @"";
468     }
469     ITDebugLog(@"Getting current song album done.");
470     return ( ([temp1 length]) ? temp1 : nil ) ;
471 }
472
473 - (NSString *)currentSongGenre
474 {
475     NSString *temp1;
476     ITDebugLog(@"Getting current song genre.");
477     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];
478     ITDebugLog(@"Getting current song genre done.");
479     return ( ([temp1 length]) ? temp1 : nil ) ;
480 }
481
482 - (NSString *)currentSongLength
483 {
484     SInt32 temp1;
485     NSString *temp2;
486     ITDebugLog(@"Getting current song length.");
487     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];
488     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];
489     if ( ([self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist) || (temp1 == 'cURT') ) { temp2 = @"Continuous"; }
490     ITDebugLog(@"Getting current song length done.");
491     return temp2;
492 }
493
494 - (NSString *)currentSongRemaining
495 {
496     SInt32 duration, current, final;
497     NSString *finalString;
498     
499     ITDebugLog(@"Getting current song remaining time.");
500     
501     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];
502     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];
503     final = duration - current;
504     finalString = [self formatTimeInSeconds:final];
505     
506     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { finalString = nil; }
507     
508     ITDebugLog(@"Getting current song remaining time done.");
509     
510     return finalString;
511 }
512
513 - (NSString *)currentSongElapsed
514 {
515     long final;
516     NSString *finalString;
517     
518     ITDebugLog(@"Getting current song elapsed time.");
519         final = (long)[ITSendAEWithKey('pPos', 'core', 'getd', &savedPSN) int32Value];
520     finalString = [self formatTimeInSeconds:final];
521     ITDebugLog(@"Getting current song elapsed time done.");
522     return finalString;
523 }
524
525 - (NSImage *)currentSongAlbumArt
526 {
527     ITDebugLog(@"Getting current song album art.");
528     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];
529     ITDebugLog(@"Getting current song album art done.");    
530     if (data) {
531         return [[[NSImage alloc] initWithData:data] autorelease];
532     } else {
533         return nil;
534     }
535 }
536
537 - (int)currentSongPlayCount
538 {
539     int count;
540     ITDebugLog(@"Getting current song play count.");
541     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];
542     ITDebugLog(@"Getting current song play count done.");
543     return count;
544 }
545
546 - (float)currentSongRating
547 {
548     float temp1;
549     ITDebugLog(@"Getting current song rating.");
550     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);
551     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { temp1 = -1.0; }
552     ITDebugLog(@"Getting current song rating done.");
553     return temp1;
554 }
555
556 - (BOOL)setCurrentSongRating:(float)rating
557 {
558     ITDebugLog(@"Setting current song rating to %f.", rating);
559     if ( [self currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ) { return NO; }
560         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);
561     ITDebugLog(@"Setting current song rating to %f done.", rating);
562     return YES;
563 }
564
565 - (BOOL)equalizerEnabled
566 {
567     ITDebugLog(@"Getting equalizer enabled status.");
568     int thingy = (int)[ITSendAEWithKey('pEQ ', 'core', 'getd', &savedPSN) int32Value];
569     ITDebugLog(@"Done getting equalizer enabled status.");
570     return (thingy != 0) ? YES : NO;
571 }
572
573 - (BOOL)setEqualizerEnabled:(BOOL)enabled
574 {
575     ITDebugLog(@"Setting equalizer enabled to %i.", enabled);
576         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pEQ '), from:'null'() }", enabled], 'core', 'setd', &savedPSN);
577     ITDebugLog(@"Done setting equalizer enabled to %i.", enabled);
578     return YES;
579 }
580
581 - (NSArray *)eqPresets
582 {
583     int i;
584     SInt32 numPresets = [ITSendAEWithString(@"kocl:type('cEQP'), '----':(), &subj:()", 'core', 'cnte', &savedPSN) int32Value];
585     NSMutableArray *presets = [[NSMutableArray alloc] initWithCapacity:numPresets];
586     ITDebugLog(@"Getting EQ presets");
587     for (i = 1; i <= numPresets; i++) {
588         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];
589         if (theObj) {
590             ITDebugLog(@"Adding preset %@", theObj);
591             [presets addObject:theObj];
592         }
593     }
594     ITDebugLog(@"Done getting EQ presets");
595     return [presets autorelease];
596 }
597
598 - (int)currentEQPresetIndex
599 {
600     int result;
601     ITDebugLog(@"Getting current EQ preset index.");
602     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];
603     ITDebugLog(@"Getting current EQ preset index done.");
604     return result;
605 }
606
607 - (float)volume
608 {
609     ITDebugLog(@"Getting volume.");
610     ITDebugLog(@"Getting volume done.");
611     return (float)[ITSendAEWithKey('pVol', 'core', 'getd', &savedPSN) int32Value] / 100;
612 }
613
614 - (BOOL)setVolume:(float)volume
615 {
616     ITDebugLog(@"Setting volume to %f.", volume);
617         ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pVol'), from:'null'() }", (long)(volume * 100)], 'core', 'setd', &savedPSN);
618     ITDebugLog(@"Setting volume to %f done.", volume);
619     return YES;
620 }
621
622 - (BOOL)shuffleEnabled
623 {
624     ITDebugLog(@"Getting shuffle enabled status.");
625     BOOL final;
626     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];
627     if (result != 0) {
628         final = YES;
629     } else {
630         final = NO;
631     }
632     ITDebugLog(@"Getting shuffle enabled status done.");
633     return final;
634 }
635
636 - (BOOL)setShuffleEnabled:(BOOL)enabled
637 {
638     ITDebugLog(@"Set shuffle enabled to %i", enabled);
639         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);
640     ITDebugLog(@"Set shuffle enabled to %i done", enabled);
641     return YES;
642 }
643
644 - (ITMTRemotePlayerRepeatMode)repeatMode
645 {
646     FourCharCode m00f = 0;
647     int result = 0;
648     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];
649     ITDebugLog(@"Getting repeat mode.");
650     switch (m00f)
651     {
652         //case 'kRp0':
653         case 1800564815:
654             ITDebugLog(@"Repeat off");
655             result = ITMTRemotePlayerRepeatOff;
656             break;
657         case 'kRp1':
658             ITDebugLog(@"Repeat one");
659             result = ITMTRemotePlayerRepeatOne;
660             break;
661         case 'kRpA':
662             ITDebugLog(@"Repeat all");
663             result = ITMTRemotePlayerRepeatAll;
664             break;
665     }
666     ITDebugLog(@"Getting repeat mode done.");
667     return result;
668 }
669
670 - (BOOL)setRepeatMode:(ITMTRemotePlayerRepeatMode)repeatMode
671 {
672     char *m00f;
673     ITDebugLog(@"Setting repeat mode to %i", repeatMode);
674     switch (repeatMode)
675     {
676         case ITMTRemotePlayerRepeatOne:
677             m00f = "kRp1";
678             break;
679         case ITMTRemotePlayerRepeatAll:
680             m00f = "kRpA";
681             break;
682         case ITMTRemotePlayerRepeatOff:
683         default:
684             m00f = "kRp0";
685             break;
686     }
687         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);
688     ITDebugLog(@"Setting repeat mode to %c done", m00f);
689     return YES;
690 }
691
692 - (BOOL)play
693 {
694     ITDebugLog(@"Play");
695         ITSendAE('hook', 'Play', &savedPSN);
696     ITDebugLog(@"Play done");
697     return YES;
698 }
699
700 - (BOOL)pause
701 {
702     ITDebugLog(@"Pause");
703     ITSendAE('hook', 'Paus', &savedPSN);
704     ITDebugLog(@"Pause done");
705     return YES;
706 }
707
708 - (BOOL)goToNextSong
709 {
710     ITDebugLog(@"Go to next track");
711     ITSendAE('hook', 'Next', &savedPSN);
712     ITDebugLog(@"Go to next track done");
713     return YES;
714 }
715
716 - (BOOL)goToPreviousSong
717 {
718     ITDebugLog(@"Go to previous track");
719     ITSendAE('hook', 'Prev', &savedPSN);
720     ITDebugLog(@"Go to previous track done");
721     return YES;
722 }
723
724 - (BOOL)forward
725 {
726     ITDebugLog(@"Fast forward action");
727     ITSendAE('hook', 'Fast', &savedPSN);
728     ITDebugLog(@"Fast forward action done");
729     return YES;
730 }
731
732 - (BOOL)rewind
733 {
734     ITDebugLog(@"Rewind action");
735     ITSendAE('hook', 'Rwnd', &savedPSN);
736     ITDebugLog(@"Rewind action done");
737     return YES;
738 }
739
740 - (BOOL)switchToPlaylistAtIndex:(int)index
741 {
742     ITDebugLog(@"Switching to playlist at index %i", index);
743         ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'indx', want:type('cPly'), seld:long(%lu), from:() }", index], 'hook', 'Play', &savedPSN);
744     ITDebugLog(@"Done switching to playlist at index %i", index);
745     return YES;
746 }
747
748 - (BOOL)switchToPlaylistAtIndex:(int)index ofSourceAtIndex:(int)index2
749 {
750     ITDebugLog(@"Switching to playlist at index %i of source %i", index, index2);
751         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);
752     ITDebugLog(@"Done switching to playlist at index %i of source %i", index, index2);
753     return YES;
754 }
755
756 - (BOOL)switchToSongAtIndex:(int)index
757 {
758     ITDebugLog(@"Switching to track at index %i", index);
759         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);
760     ITDebugLog(@"Done switching to track at index %i", index);
761     return YES;
762 }
763
764 - (BOOL)switchToEQAtIndex:(int)index
765 {
766     ITDebugLog(@"Switching to EQ preset at index %i", index);
767     // index should count from 0, but itunes counts from 1, so let's add 1.
768     [self setEqualizerEnabled:YES];
769         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);
770     ITDebugLog(@"Done switching to EQ preset at index %i", index);
771     return YES;
772 }
773
774 - (BOOL)makePlaylistWithTerm:(NSString *)term ofType:(int)type
775 {
776     int i;
777         
778     //Get fixed indexing status
779     BOOL fixed = [ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'getd', &savedPSN) booleanValue];
780     
781     //Enabled fixed indexing
782     ITSendAEWithString(@"data:long(1), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", 'core', 'setd', &savedPSN);
783     
784     //Search for the term
785     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);
786     
787     //If MenuTunes playlist exists
788     if ([ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'doex', &savedPSN) booleanValue]) {
789         //Clear old MenuTunes playlist
790                 int numSongs = [ITSendAEWithString(@"kocl:type('cTrk'), '----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'core', 'cnte', &savedPSN) int32Value];
791         for (i = 1; i <= numSongs; i++) {
792             ITSendAEWithString(@"'----':obj { form:'indx', want:type('cTrk'), seld:long(1), from:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() } }", 'core', 'delo', &savedPSN);
793         }
794     } else {
795         //Create MenuTunes playlist
796         ITSendAEWithString(@"prdt:{ pnam:\"MenuTunes\" }, kocl:type('cPly'), &subj:()", 'core', 'crel', &savedPSN);
797     }
798     
799     //Duplicate search results to playlist
800     for (i = 1; i <= [searchResults numberOfItems]; i++) {
801         ITSendAEWithStringAndObject(@"insh:obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", [[searchResults descriptorAtIndex:i] aeDesc], 'core', 'clon', &savedPSN);
802     }
803     //Reset fixed indexing
804     ITSendAEWithString([NSString stringWithFormat:@"data:long(%i), '----':obj { form:'prop', want:type('prop'), seld:type('pFix'), from:'null'() }", fixed], 'core', 'setd', &savedPSN);
805     
806     //Play MenuTunes playlist
807     ITSendAEWithString(@"'----':obj { form:'name', want:type('cPly'), seld:\"MenuTunes\", from:'null'() }", 'hook', 'Play', &savedPSN);
808     
809     return YES;
810 }
811
812 - (ProcessSerialNumber)iTunesPSN
813 {
814     /*NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
815     ProcessSerialNumber number;
816     int i;
817     int count = [apps count];
818     
819     number.highLongOfPSN = kNoProcess;
820     
821     for (i = 0; i < count; i++)
822     {
823         NSDictionary *curApp = [apps objectAtIndex:i];
824         
825         if ([[curApp objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"])
826         {
827             number.highLongOfPSN = [[curApp objectForKey:
828                 @"NSApplicationProcessSerialNumberHigh"] intValue];
829             number.lowLongOfPSN = [[curApp objectForKey:
830                 @"NSApplicationProcessSerialNumberLow"] intValue];
831         }
832     }
833     return number;*/
834     ProcessSerialNumber number;
835     number.highLongOfPSN = kNoProcess;
836     number.lowLongOfPSN = 0;
837     ITDebugLog(@"Getting iTunes' PSN.");
838     while ( (GetNextProcess(&number) == noErr) ) 
839     {
840         CFStringRef name;
841         if ( (CopyProcessName(&number, &name) == noErr) )
842         {
843             if ([(NSString *)name isEqualToString:@"iTunes"])
844             {
845                 ITDebugLog(@"iTunes' highLPongOfPSN: %lu.", number.highLongOfPSN);
846                 ITDebugLog(@"iTunes' lowLongOfPSN: %lu.", number.lowLongOfPSN);
847                 ITDebugLog(@"Done getting iTunes' PSN.");
848                 return number;
849             }
850             [(NSString *)name release];
851         }
852     }
853     ITDebugLog(@"Failed getting iTunes' PSN.");
854     return number;
855 }
856
857 - (NSString*)formatTimeInSeconds:(long)seconds {
858     long final = seconds;
859     NSString *finalString;
860     if (final >= 60) {
861         if (final > 3600) {
862             finalString = [NSString stringWithFormat:@"%i:%@:%@",(final / 3600),[self zeroSixty:(int)((final % 3600) / 60)],[self zeroSixty:(int)((final % 3600) % 60)]];
863         } else {
864             finalString = [NSString stringWithFormat:@"%i:%@",(final / 60),[self zeroSixty:(int)(final % 60)]];
865         }
866     } else {
867         finalString = [NSString stringWithFormat:@"0:%@",[self zeroSixty:(int)final]];
868     }
869     return finalString;
870 }
871 - (NSString*)zeroSixty:(int)seconds {
872     if ( (seconds < 10) && (seconds > 0) ) {
873         return [NSString stringWithFormat:@"0%i",seconds];
874     } else if ( (seconds == 0) ) {
875         return [NSString stringWithFormat:@"00"];
876     } else {
877         return [NSString stringWithFormat:@"%i",seconds];
878     }
879 }
880
881 @end