Fixing two memory leaks and some inefficiency in iTunesRemote
[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 *)title
11 {
12     return @"iTunes Plug-in";
13 }
14
15 - (NSString *)information;
16 {
17     return @"Default MenuTunes plugin to control iTunes. Written by iThink Software.";
18 }
19
20 - (NSImage *)icon
21 {
22     return nil;
23 }
24
25 - (BOOL)begin
26 {
27     iTunesPSN = [self iTunesPSN];
28     
29     //We won't need this once we're pure AEs
30     asComponent = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);
31     
32     //Register for application termination in NSWorkspace
33     [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
34     [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(applicationTerminated:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];
35     
36     return YES;
37 }
38
39 - (BOOL)halt
40 {
41     iTunesPSN.highLongOfPSN = kNoProcess;
42     
43     //We won't need this once we're pure AEs
44     CloseComponent(asComponent);
45     
46     //Unregister for application termination in NSWorkspace
47     [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
48     
49     return YES;
50 }
51
52 - (BOOL)isAppRunning
53 {
54     NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
55     int i,count = [apps count];
56     
57     for (i = 0; i < count; i++) {
58         if ([[[apps objectAtIndex:i] objectForKey:@"NSApplicationName"]
59                 isEqualToString:@"iTunes"]) {
60             return YES;
61         }
62     }
63     return NO;
64 }
65
66 - (PlayerState)playerState
67 {
68     NSString *result = [self runScriptAndReturnResult:@"get player state"];
69     
70     if ([result isEqualToString:@"playing"]) {
71         return playing;
72     } else if ([result isEqualToString:@"paused"]) {
73         return paused;
74     } else if ([result isEqualToString:@"stopped"]) {
75         return stopped;
76     } else if ([result isEqualToString:@"rewinding"]) {
77         return rewinding;
78     } else if ([result isEqualToString:@"fast forwarding"]) {
79         return forwarding;
80     }
81     
82     return stopped;
83 }
84
85 - (NSArray *)playlists
86 {
87     int i;
88     int numPresets = [[self runScriptAndReturnResult:@"get number of playlists"] intValue];
89     NSMutableArray *presets = [[NSMutableArray alloc] init];
90     for (i = 1; i <= numPresets; i++) {
91         [presets addObject:[self runScriptAndReturnResult:[NSString stringWithFormat:@"get name of playlist %i", i]]];
92     }
93     
94     return [presets autorelease];
95 }
96
97 - (int)numberOfSongsInPlaylistAtIndex:(int)index
98 {
99     NSString *result = [self runScriptAndReturnResult:[NSString stringWithFormat:@"get number of tracks in playlist %i", index]];
100     return [result intValue];
101 }
102
103 - (NSString *)classOfPlaylistAtIndex:(int)index
104 {
105     //Not working yet. It returns the 4 character code instead of a name.
106     /*NSString *result;
107     result = [[ITAppleEventCenter sharedCenter]
108                 sendTwoTierAEWithRequestedKey:@"pcls"
109                 fromObjectByKey:@"pPla" eventClass:@"core" eventID:@"getd"
110                 appPSN:[self iTunesPSN]];*/
111     NSString *result = [self runScriptAndReturnResult:[NSString stringWithFormat:@"get class of playlist %i", index]];
112     return result;
113 }
114
115 - (int)currentPlaylistIndex
116 {
117     int result;
118     result = [[ITAppleEventCenter sharedCenter]
119                 sendTwoTierAEWithRequestedKeyForNumber:@"pidx"
120                 fromObjectByKey:@"pPla" eventClass:@"core" eventID:@"getd"
121                 appPSN:[self iTunesPSN]];
122     return result;
123 }
124
125 - (NSString *)songTitleAtIndex:(int)index
126 {
127     NSString *result = [self runScriptAndReturnResult:[NSString stringWithFormat:@"get name of track %i of current playlist", index]];
128     return result;
129 }
130
131 - (int)currentSongIndex
132 {
133     int result;
134     result = [[ITAppleEventCenter sharedCenter]
135                 sendTwoTierAEWithRequestedKeyForNumber:@"pidx"
136                 fromObjectByKey:@"pTrk" eventClass:@"core" eventID:@"getd"
137                 appPSN:[self iTunesPSN]];
138     return result;
139 }
140
141 - (NSString *)currentSongTitle
142 {
143     return [[ITAppleEventCenter sharedCenter] sendTwoTierAEWithRequestedKey:@"pnam"
144                 fromObjectByKey:@"pTrk" eventClass:@"core" eventID:@"getd"
145                 appPSN:[self iTunesPSN]];
146 }
147
148 - (NSString *)currentSongArtist
149 {
150     return [[ITAppleEventCenter sharedCenter] sendTwoTierAEWithRequestedKey:@"pArt"
151                 fromObjectByKey:@"pTrk" eventClass:@"core" eventID:@"getd"
152                 appPSN:[self iTunesPSN]];
153 }
154
155 - (NSString *)currentSongAlbum
156 {
157     return [[ITAppleEventCenter sharedCenter] sendTwoTierAEWithRequestedKey:@"pAlb"
158                 fromObjectByKey:@"pTrk" eventClass:@"core" eventID:@"getd"
159                 appPSN:[self iTunesPSN]];
160 }
161
162 - (NSString *)currentSongGenre
163 {
164     return [[ITAppleEventCenter sharedCenter] sendTwoTierAEWithRequestedKey:@"pGen"
165                 fromObjectByKey:@"pTrk" eventClass:@"core" eventID:@"getd"
166                 appPSN:[self iTunesPSN]];
167 }
168
169 - (NSString *)currentSongLength
170 {
171     return [[ITAppleEventCenter sharedCenter] sendTwoTierAEWithRequestedKey:@"pTim"
172                 fromObjectByKey:@"pTrk" eventClass:@"core" eventID:@"getd"
173                 appPSN:[self iTunesPSN]];
174 }
175
176 - (NSString *)currentSongRemaining
177 {
178     long duration = [[ITAppleEventCenter sharedCenter]
179                         sendTwoTierAEWithRequestedKeyForNumber:@"pDur"
180                         fromObjectByKey:@"pTrk" eventClass:@"core" eventID:@"getd"
181                         appPSN:[self iTunesPSN]];
182     long current = [[ITAppleEventCenter sharedCenter]
183                         sendAEWithRequestedKeyForNumber:@"pPos"
184                         eventClass:@"core" eventID:@"getd"
185                         appPSN:[self iTunesPSN]];
186     
187     return [[NSNumber numberWithLong:duration - current] stringValue];
188 }
189
190 - (NSArray *)eqPresets;
191 {
192     int i;
193     int numPresets = [[self runScriptAndReturnResult:@"get number of EQ presets"] intValue];
194     NSMutableArray *presets = [[NSMutableArray alloc] init];
195     
196     for (i = 1; i <= numPresets; i++) {
197         [presets addObject:[self runScriptAndReturnResult:[NSString stringWithFormat:@"get name of EQ preset %i", i]]];
198     }
199     
200     return [presets autorelease];
201 }
202
203 - (int)currentEQPresetIndex
204 {
205     int result;
206     result = [[ITAppleEventCenter sharedCenter]
207                 sendTwoTierAEWithRequestedKeyForNumber:@"pidx"
208                 fromObjectByKey:@"pEQP" eventClass:@"core" eventID:@"getd"
209                 appPSN:[self iTunesPSN]];
210     return result;
211 }
212
213 - (BOOL)play
214 {
215     [[ITAppleEventCenter sharedCenter] sendAEWithEventClass:@"hook" eventID:@"Play"
216             appPSN:[self iTunesPSN]];
217     return YES;
218 }
219
220 - (BOOL)pause
221 {
222     [[ITAppleEventCenter sharedCenter] sendAEWithEventClass:@"hook" eventID:@"Paus"
223             appPSN:[self iTunesPSN]];
224     return YES;
225 }
226
227 - (BOOL)goToNextSong
228 {
229     [[ITAppleEventCenter sharedCenter] sendAEWithEventClass:@"hook" eventID:@"Next"
230             appPSN:[self iTunesPSN]];
231     return YES;
232 }
233
234 - (BOOL)goToPreviousSong
235 {
236     [[ITAppleEventCenter sharedCenter] sendAEWithEventClass:@"hook" eventID:@"Prev"
237             appPSN:[self iTunesPSN]];
238     return YES;
239 }
240
241 - (BOOL)fastForward
242 {
243     [[ITAppleEventCenter sharedCenter] sendAEWithEventClass:@"hook" eventID:@"Fast"
244             appPSN:[self iTunesPSN]];
245     return YES;
246 }
247
248 - (BOOL)rewind
249 {
250     [[ITAppleEventCenter sharedCenter] sendAEWithEventClass:@"hook" eventID:@"Rwnd"
251             appPSN:[self iTunesPSN]];
252     return YES;
253 }
254
255
256 - (BOOL)switchToPlaylistAtIndex:(int)index
257 {
258     [self runScriptAndReturnResult:[NSString stringWithFormat:
259         @"play playlist %i", index]];
260     return NO;
261 }
262
263 - (BOOL)switchToSongAtIndex:(int)index
264 {
265     [self runScriptAndReturnResult:[NSString stringWithFormat:
266         @"play track %i of current playlist", index]];
267     return NO;
268 }
269
270 - (BOOL)switchToEQAtIndex:(int)index
271 {
272     [self runScriptAndReturnResult:[NSString stringWithFormat:
273         @"set current EQ preset to EQ preset %i", index]];
274     [self runScriptAndReturnResult:@"set EQ enabled to 1"];
275     return NO;
276 }
277
278 - (ProcessSerialNumber)iTunesPSN
279 {
280     NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
281     ProcessSerialNumber number;
282     int i;
283     
284     number.highLongOfPSN = kNoProcess;
285     
286     for (i = 0; i < [apps count]; i++)
287     {
288         NSDictionary *curApp = [apps objectAtIndex:i];
289         
290         if ([[curApp objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"])
291         {
292             number.highLongOfPSN = [[curApp objectForKey:@"NSApplicationProcessSerialNumberHigh"] intValue];
293             number.lowLongOfPSN = [[curApp objectForKey:@"NSApplicationProcessSerialNumberLow"] intValue];
294         }
295     }
296     return number;
297 }
298
299 - (void)applicationLaunched:(NSNotification *)note
300 {
301     NSDictionary *info = [note userInfo];
302     
303     if ([[info objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"]) {
304         iTunesPSN.highLongOfPSN = [[info objectForKey:@"NSApplicationProcessSerialNumberHigh"] longValue];
305         iTunesPSN.lowLongOfPSN = [[info objectForKey:@"NSApplicationProcessSerialNumberLow"] longValue];
306         
307         [[NSNotificationCenter defaultCenter] postNotificationName:@"ITMTRemoteAppDidLaunchNotification" object:nil];
308     }
309 }
310
311 - (void)applicationTerminated:(NSNotification *)note
312 {
313     NSDictionary *info = [note userInfo];
314     
315     if ([[info objectForKey:@"NSApplicationName"] isEqualToString:@"iTunes"]) {
316         iTunesPSN.highLongOfPSN = kNoProcess;
317         [[NSNotificationCenter defaultCenter] postNotificationName:@"ITMTRemoteAppDidTerminateNotification" object:nil];
318     }
319 }
320
321 //This is just temporary
322 - (NSString *)runScriptAndReturnResult:(NSString *)script
323 {
324     AEDesc scriptDesc, resultDesc;
325     Size length;
326     NSString *result;
327     Ptr buffer;
328     
329     script = [NSString stringWithFormat:@"tell application \"iTunes\"\n%@\nend tell", script];
330     
331     AECreateDesc(typeChar, [script cString], [script cStringLength], 
332 &scriptDesc);
333     
334     OSADoScript(asComponent, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
335     
336     length = AEGetDescDataSize(&resultDesc);
337     buffer = malloc(length);
338     
339     AEGetDescData(&resultDesc, buffer, length);
340     AEDisposeDesc(&scriptDesc);
341     AEDisposeDesc(&resultDesc);
342     result = [NSString stringWithCString:buffer length:length];
343     if ( (! [result isEqualToString:@""])      &&
344          ([result characterAtIndex:0] == '\"') &&
345          ([result characterAtIndex:[result length] - 1] == '\"') ) {
346         result = [result substringWithRange:NSMakeRange(1, [result length] - 2)];
347     }
348     free(buffer);
349     buffer = nil;
350     return result;
351 }
352
353 @end