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