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