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