|
David@0
|
1 |
/* |
|
David@0
|
2 |
* Adium is the legal property of its developers, whose names are listed in the copyright file included |
|
David@0
|
3 |
* with this source distribution. |
|
David@0
|
4 |
* |
|
David@0
|
5 |
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU |
|
David@0
|
6 |
* General Public License as published by the Free Software Foundation; either version 2 of the License, |
|
David@0
|
7 |
* or (at your option) any later version. |
|
David@0
|
8 |
* |
|
David@0
|
9 |
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
|
David@0
|
10 |
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
David@0
|
11 |
* Public License for more details. |
|
David@0
|
12 |
* |
|
David@0
|
13 |
* You should have received a copy of the GNU General Public License along with this program; if not, |
|
David@0
|
14 |
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
David@0
|
15 |
*/ |
|
David@0
|
16 |
|
|
David@0
|
17 |
#import "AdiumSound.h" |
|
David@0
|
18 |
#import "AISoundController.h" |
|
David@0
|
19 |
#import <AIUtilities/AIDictionaryAdditions.h> |
|
David@0
|
20 |
#import <AIUtilities/AISleepNotification.h> |
|
catfish@2027
|
21 |
#import <CoreAudio/AudioHardware.h> |
|
Evan@2544
|
22 |
#import <CoreServices/CoreServices.h> |
|
David@0
|
23 |
#import <sys/sysctl.h> |
|
David@0
|
24 |
|
|
David@0
|
25 |
#define SOUND_DEFAULT_PREFS @"SoundPrefs" |
|
Evan@2544
|
26 |
#define MAX_CACHED_SOUNDS 4 //Max cached sounds |
|
David@0
|
27 |
|
|
David@84
|
28 |
@interface AdiumSound () |
|
Evan@2544
|
29 |
- (void)_stopAndReleaseAllSounds; |
|
Evan@2544
|
30 |
- (void)_setVolumeOfAllSoundsTo:(CGFloat)inVolume; |
|
Evan@2544
|
31 |
- (void)cachedPlaySound:(NSString *)inPath; |
|
Evan@2544
|
32 |
- (void)_uncacheLeastRecentlyUsedSound; |
|
Evan@2544
|
33 |
- (NSString *)systemAudioDeviceID; |
|
Evan@2544
|
34 |
- (void)configureAudioContextForSound:(NSSound *)sound; |
|
Evan@2544
|
35 |
- (NSArray *)allSounds; |
|
David@0
|
36 |
@end |
|
David@0
|
37 |
|
|
David@0
|
38 |
@interface NSProcessInfo (AIProcessorInfoAdditions) |
|
David@0
|
39 |
- (BOOL)processorFamilyIsG5; |
|
David@0
|
40 |
@end |
|
David@0
|
41 |
|
|
David@0
|
42 |
static OSStatus systemOutputDeviceDidChange(AudioHardwarePropertyID property, void *refcon); |
|
David@0
|
43 |
|
|
David@0
|
44 |
@implementation AdiumSound |
|
David@0
|
45 |
|
|
David@0
|
46 |
/*! |
|
David@0
|
47 |
* @brief Init |
|
David@0
|
48 |
*/ |
|
David@0
|
49 |
- (id)init |
|
David@0
|
50 |
{ |
|
David@0
|
51 |
if ((self = [super init])) { |
|
Evan@2544
|
52 |
soundCacheDict = [[NSMutableDictionary alloc] init]; |
|
Evan@2544
|
53 |
soundCacheArray = [[NSMutableArray alloc] init]; |
|
Evan@2544
|
54 |
soundCacheCleanupTimer = nil; |
|
David@0
|
55 |
soundsAreMuted = NO; |
|
David@0
|
56 |
|
|
David@0
|
57 |
//Observe workspace activity changes so we can mute sounds as necessary |
|
David@0
|
58 |
NSNotificationCenter *workspaceCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; |
|
David@0
|
59 |
|
|
David@0
|
60 |
[workspaceCenter addObserver:self |
|
David@0
|
61 |
selector:@selector(workspaceSessionDidBecomeActive:) |
|
David@0
|
62 |
name:NSWorkspaceSessionDidBecomeActiveNotification |
|
David@0
|
63 |
object:nil]; |
|
David@0
|
64 |
|
|
David@0
|
65 |
[workspaceCenter addObserver:self |
|
David@0
|
66 |
selector:@selector(workspaceSessionDidResignActive:) |
|
David@0
|
67 |
name:NSWorkspaceSessionDidResignActiveNotification |
|
David@0
|
68 |
object:nil]; |
|
David@0
|
69 |
|
|
David@0
|
70 |
//Monitor system sleep so we can stop sounds before sleeping; otherwise, we may crash while waking |
|
David@0
|
71 |
[[NSNotificationCenter defaultCenter] addObserver:self |
|
David@0
|
72 |
selector:@selector(systemWillSleep:) |
|
David@0
|
73 |
name:AISystemWillSleep_Notification |
|
David@0
|
74 |
object:nil]; |
|
David@0
|
75 |
|
|
David@0
|
76 |
/* Sign up for notification when the user changes the system output device in the Sound pane of System Preferences. |
|
David@0
|
77 |
* |
|
David@0
|
78 |
* However, we avoid doing this on G5 machines. G5s spew a continuous stream of |
|
David@0
|
79 |
* kAudioHardwarePropertyDefaultSystemOutputDevice notifications without the device actually changing; |
|
David@0
|
80 |
* rather than stutter our audio and eat CPU continuously, we just won't try to update. |
|
David@0
|
81 |
*/ |
|
David@0
|
82 |
if (![[NSProcessInfo processInfo] processorFamilyIsG5]) { |
|
David@0
|
83 |
OSStatus err = AudioHardwareAddPropertyListener(kAudioHardwarePropertyDefaultSystemOutputDevice, systemOutputDeviceDidChange, /*refcon*/ self); |
|
David@0
|
84 |
if (err != noErr) |
|
David@0
|
85 |
NSLog(@"%s: Couldn't sign up for system-output-device-changed notification, because AudioHardwareAddPropertyListener returned %i. Adium will not know when the default system audio device changes.", __PRETTY_FUNCTION__, err); |
|
David@0
|
86 |
} else { |
|
David@0
|
87 |
//We won't be updating automatically, so reconfigure before a sound is played again |
|
David@0
|
88 |
reconfigureAudioContextBeforeEachPlay = YES; |
|
David@0
|
89 |
} |
|
David@0
|
90 |
} |
|
David@0
|
91 |
|
|
David@0
|
92 |
return self; |
|
David@0
|
93 |
} |
|
David@0
|
94 |
|
|
David@0
|
95 |
- (void)controllerDidLoad |
|
David@0
|
96 |
{ |
|
David@0
|
97 |
//Register our default preferences and observe changes |
|
David@95
|
98 |
[adium.preferenceController registerDefaults:[NSDictionary dictionaryNamed:SOUND_DEFAULT_PREFS forClass:[self class]] |
|
David@0
|
99 |
forGroup:PREF_GROUP_SOUNDS]; |
|
David@95
|
100 |
[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_SOUNDS]; |
|
David@0
|
101 |
} |
|
David@0
|
102 |
|
|
David@0
|
103 |
- (void)dealloc |
|
David@0
|
104 |
{ |
|
David@95
|
105 |
[adium.preferenceController unregisterPreferenceObserver:self]; |
|
David@0
|
106 |
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; |
|
David@0
|
107 |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
David@0
|
108 |
|
|
Evan@2544
|
109 |
[self _stopAndReleaseAllSounds]; |
|
Evan@2544
|
110 |
|
|
Evan@2544
|
111 |
[soundCacheDict release]; soundCacheDict = nil; |
|
Evan@2544
|
112 |
[soundCacheArray release]; soundCacheArray = nil; |
|
Evan@2544
|
113 |
[soundCacheCleanupTimer invalidate]; [soundCacheCleanupTimer release]; soundCacheCleanupTimer = nil; |
|
David@0
|
114 |
|
|
David@0
|
115 |
[super dealloc]; |
|
David@0
|
116 |
} |
|
David@0
|
117 |
|
|
Evan@2544
|
118 |
- (void)playSoundAtPath:(NSString *)inPath |
|
Evan@2544
|
119 |
{ |
|
Evan@2544
|
120 |
if (inPath && customVolume != 0.0 && !soundsAreMuted) { |
|
Evan@2544
|
121 |
[self cachedPlaySound:inPath]; |
|
Evan@2544
|
122 |
} |
|
Evan@2544
|
123 |
} |
|
Evan@2544
|
124 |
|
|
Evan@2544
|
125 |
- (void)stopPlayingSoundAtPath:(NSString *)inPath |
|
Evan@2544
|
126 |
{ |
|
Evan@2544
|
127 |
NSSound *sound = [soundCacheDict objectForKey:inPath]; |
|
Evan@2544
|
128 |
if (sound) { |
|
Evan@2544
|
129 |
[sound stop]; |
|
Evan@2544
|
130 |
} |
|
Evan@2544
|
131 |
} |
|
Evan@2544
|
132 |
|
|
David@0
|
133 |
/*! |
|
David@0
|
134 |
* @brief Preferences changed, adjust to the new values |
|
David@0
|
135 |
*/ |
|
Evan@2544
|
136 |
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key |
|
Evan@2544
|
137 |
object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime |
|
David@0
|
138 |
{ |
|
David@3
|
139 |
CGFloat newVolume = [[prefDict objectForKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL] doubleValue]; |
|
David@0
|
140 |
|
|
Evan@2544
|
141 |
//If sound volume has changed, we must update all existing sounds to the new volume |
|
Evan@2544
|
142 |
if (customVolume != newVolume) { |
|
Evan@2544
|
143 |
[self _setVolumeOfAllSoundsTo:newVolume]; |
|
David@0
|
144 |
} |
|
David@0
|
145 |
|
|
David@0
|
146 |
//Load the new preferences |
|
David@0
|
147 |
customVolume = newVolume; |
|
David@0
|
148 |
} |
|
David@0
|
149 |
|
|
David@0
|
150 |
/*! |
|
David@0
|
151 |
* @brief Stop and release all cached sounds |
|
David@0
|
152 |
*/ |
|
Evan@2544
|
153 |
- (void)_stopAndReleaseAllSounds |
|
David@0
|
154 |
{ |
|
Evan@2544
|
155 |
[[soundCacheDict allValues] makeObjectsPerformSelector:@selector(stop)]; |
|
Evan@2544
|
156 |
[soundCacheDict removeAllObjects]; |
|
Evan@2544
|
157 |
[soundCacheArray removeAllObjects]; |
|
Evan@2544
|
158 |
} |
|
Evan@2544
|
159 |
|
|
Evan@2544
|
160 |
/*! |
|
Evan@2544
|
161 |
* @brief Update the volume of all cached sounds |
|
Evan@2544
|
162 |
*/ |
|
Evan@2544
|
163 |
- (void)_setVolumeOfAllSoundsTo:(CGFloat)inVolume |
|
Evan@2544
|
164 |
{ |
|
Evan@2544
|
165 |
for (NSSound *sound in [soundCacheDict objectEnumerator]) { |
|
Evan@2544
|
166 |
[sound setVolume:inVolume]; |
|
Evan@2544
|
167 |
} |
|
David@0
|
168 |
} |
|
David@0
|
169 |
|
|
David@0
|
170 |
/*! |
|
catfish@2027
|
171 |
* @brief Play an NSSound, possibly cached |
|
David@0
|
172 |
* |
|
Evan@2544
|
173 |
* @param inPath path to the sound file |
|
David@0
|
174 |
*/ |
|
Evan@2544
|
175 |
- (void)cachedPlaySound:(NSString *)inPath |
|
David@0
|
176 |
{ |
|
Evan@2544
|
177 |
NSSound *sound = [soundCacheDict objectForKey:inPath]; |
|
David@0
|
178 |
|
|
Evan@2544
|
179 |
//Load the sound if necessary |
|
Evan@2544
|
180 |
if (!sound) { |
|
Evan@2544
|
181 |
//If the cache is full, remove the least recently used cached sound |
|
Evan@2544
|
182 |
if ([soundCacheDict count] >= MAX_CACHED_SOUNDS) { |
|
Evan@2544
|
183 |
[self _uncacheLeastRecentlyUsedSound]; |
|
Evan@2544
|
184 |
} |
|
Evan@2544
|
185 |
|
|
Evan@2544
|
186 |
//Load and cache the sound |
|
Evan@2544
|
187 |
NSError *error = nil; |
|
Evan@2544
|
188 |
sound = [[NSSound alloc] initWithContentsOfFile:inPath byReference:NO]; |
|
Evan@2544
|
189 |
if (sound) { |
|
Evan@2544
|
190 |
//Insert the player at the front of our cache |
|
Evan@2544
|
191 |
[soundCacheArray insertObject:inPath atIndex:0]; |
|
Evan@2544
|
192 |
[soundCacheDict setObject:sound forKey:inPath]; |
|
Evan@2544
|
193 |
[sound release]; |
|
Evan@2544
|
194 |
|
|
Evan@2544
|
195 |
//Set the volume (otherwise #2283 happens) |
|
Evan@2544
|
196 |
[sound setVolume:customVolume]; |
|
Evan@2544
|
197 |
|
|
Evan@2544
|
198 |
[self configureAudioContextForSound:sound]; |
|
Evan@2544
|
199 |
} else { |
|
Evan@2544
|
200 |
AILogWithSignature(@"Error loading %@: %@", inPath, error); |
|
Evan@2544
|
201 |
} |
|
Evan@2544
|
202 |
|
|
Evan@2544
|
203 |
} else { |
|
Evan@2544
|
204 |
//Move this sound to the front of the cache (This will naturally move lesser used sounds to the back for removal) |
|
Evan@2544
|
205 |
[soundCacheArray removeObject:inPath]; |
|
Evan@2544
|
206 |
[soundCacheArray insertObject:inPath atIndex:0]; |
|
Evan@2544
|
207 |
|
|
Evan@2544
|
208 |
if (reconfigureAudioContextBeforeEachPlay) { |
|
Evan@2544
|
209 |
[sound stop]; |
|
Evan@2544
|
210 |
[self configureAudioContextForSound:sound]; |
|
Evan@2544
|
211 |
} |
|
Evan@2544
|
212 |
} |
|
David@0
|
213 |
|
|
catfish@2027
|
214 |
//Engage! |
|
catfish@2027
|
215 |
if (sound) { |
|
catfish@2027
|
216 |
[sound setCurrentTime:0.0]; |
|
Evan@2544
|
217 |
|
|
Evan@2544
|
218 |
//This only has an effect if the sound is not already playing. It won't stop it, and it won't start it over (the latter is what setCurrentTime: is for). |
|
catfish@2027
|
219 |
[sound play]; |
|
catfish@2027
|
220 |
} |
|
David@0
|
221 |
} |
|
David@0
|
222 |
|
|
Evan@2544
|
223 |
/*! |
|
Evan@2544
|
224 |
* @brief Remove the least recently used sound from the cache |
|
Evan@2544
|
225 |
*/ |
|
Evan@2544
|
226 |
- (void)_uncacheLeastRecentlyUsedSound |
|
David@0
|
227 |
{ |
|
Evan@2544
|
228 |
NSString *lastCachedPath = [soundCacheArray lastObject]; |
|
Evan@2544
|
229 |
NSSound *sound = [soundCacheDict objectForKey:lastCachedPath]; |
|
Evan@2544
|
230 |
|
|
Evan@2544
|
231 |
//Remove it from the cache only if it is not playing. |
|
Evan@2544
|
232 |
if (![sound isPlaying]) { |
|
Evan@2544
|
233 |
[soundCacheDict removeObjectForKey:lastCachedPath]; |
|
Evan@2544
|
234 |
[soundCacheArray removeLastObject]; |
|
Evan@2544
|
235 |
} |
|
David@0
|
236 |
} |
|
David@0
|
237 |
|
|
catfish@2027
|
238 |
- (NSString *)systemAudioDeviceID |
|
David@0
|
239 |
{ |
|
Evan@2544
|
240 |
OSStatus err; |
|
Evan@2544
|
241 |
UInt32 dataSize; |
|
Evan@2544
|
242 |
|
|
Evan@2544
|
243 |
//First, obtain the device itself. |
|
Evan@2544
|
244 |
AudioDeviceID systemOutputDevice = 0; |
|
Evan@2544
|
245 |
dataSize = sizeof(systemOutputDevice); |
|
Evan@2544
|
246 |
err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultSystemOutputDevice, &dataSize, &systemOutputDevice); |
|
Evan@2544
|
247 |
if (err != noErr) { |
|
Evan@2544
|
248 |
NSLog(@"%s: Could not get the system output device: AudioHardwareGetProperty returned error %i", __PRETTY_FUNCTION__, err); |
|
Evan@2544
|
249 |
return NULL; |
|
David@0
|
250 |
} |
|
Evan@2544
|
251 |
|
|
Evan@2544
|
252 |
//Now get its UID. We'll need to release this. |
|
Evan@2544
|
253 |
CFStringRef deviceUID = NULL; |
|
Evan@2544
|
254 |
dataSize = sizeof(deviceUID); |
|
Evan@2544
|
255 |
err = AudioDeviceGetProperty(systemOutputDevice, /*channel*/ 0, /*isInput*/ false, kAudioDevicePropertyDeviceUID, &dataSize, &deviceUID); |
|
Evan@2544
|
256 |
if (err != noErr) { |
|
Evan@2544
|
257 |
NSLog(@"%s: Could not get the device UID for device %p: AudioDeviceGetProperty returned error %i", __PRETTY_FUNCTION__, systemOutputDevice, err); |
|
Evan@2544
|
258 |
return NULL; |
|
David@0
|
259 |
} |
|
Evan@2544
|
260 |
[(NSString *)deviceUID autorelease]; |
|
catfish@2027
|
261 |
|
|
Evan@2544
|
262 |
return (NSString *)deviceUID; |
|
Evan@2544
|
263 |
} |
|
Evan@2544
|
264 |
|
|
Evan@2544
|
265 |
- (void)configureAudioContextForSound:(NSSound *)sound |
|
Evan@2544
|
266 |
{ |
|
Evan@2544
|
267 |
[sound pause]; |
|
David@0
|
268 |
|
|
Evan@2544
|
269 |
//Exchange the audio context for a new one with the new device. |
|
Evan@2544
|
270 |
NSString *deviceUID = [self systemAudioDeviceID]; |
|
Evan@2544
|
271 |
|
|
Evan@2544
|
272 |
[sound setPlaybackDeviceIdentifier:deviceUID]; |
|
Evan@2544
|
273 |
|
|
Evan@2544
|
274 |
//Resume playback, now on the new device. |
|
Evan@2544
|
275 |
[sound resume]; |
|
Evan@2544
|
276 |
} |
|
Evan@2544
|
277 |
|
|
Evan@2544
|
278 |
- (NSArray *)allSounds |
|
Evan@2544
|
279 |
{ |
|
Evan@2544
|
280 |
return [soundCacheDict allValues]; |
|
David@0
|
281 |
} |
|
David@0
|
282 |
|
|
David@0
|
283 |
/*! |
|
David@0
|
284 |
* @brief Workspace activated (Computer switched to our user) |
|
David@0
|
285 |
*/ |
|
David@0
|
286 |
- (void)workspaceSessionDidBecomeActive:(NSNotification *)notification |
|
David@0
|
287 |
{ |
|
David@0
|
288 |
[self setSoundsAreMuted:NO]; |
|
David@0
|
289 |
} |
|
David@0
|
290 |
|
|
David@0
|
291 |
/*! |
|
David@0
|
292 |
* @brief Workspace resigned (Computer switched to another user) |
|
David@0
|
293 |
*/ |
|
David@0
|
294 |
- (void)workspaceSessionDidResignActive:(NSNotification *)notification |
|
David@0
|
295 |
{ |
|
David@0
|
296 |
[self setSoundsAreMuted:YES]; |
|
David@0
|
297 |
} |
|
David@0
|
298 |
|
|
David@0
|
299 |
- (void)systemWillSleep:(NSNotification *)notification |
|
David@0
|
300 |
{ |
|
Evan@2544
|
301 |
[self _stopAndReleaseAllSounds]; |
|
David@0
|
302 |
} |
|
David@0
|
303 |
|
|
David@0
|
304 |
- (void)setSoundsAreMuted:(BOOL)mute |
|
David@0
|
305 |
{ |
|
David@0
|
306 |
AILog(@"setSoundsAreMuted: %i",mute); |
|
David@0
|
307 |
if (soundsAreMuted > 0 && !mute) |
|
David@0
|
308 |
soundsAreMuted--; |
|
David@0
|
309 |
else if (mute) |
|
David@0
|
310 |
soundsAreMuted++; |
|
David@0
|
311 |
|
|
David@0
|
312 |
if (soundsAreMuted == 1) |
|
Evan@2544
|
313 |
[self _stopAndReleaseAllSounds]; |
|
David@0
|
314 |
} |
|
David@0
|
315 |
|
|
David@0
|
316 |
- (void)systemOutputDeviceDidChange |
|
David@0
|
317 |
{ |
|
Evan@2544
|
318 |
for (NSSound *sound in [self allSounds]) { |
|
Evan@2544
|
319 |
[self configureAudioContextForSound:sound]; |
|
David@0
|
320 |
} |
|
David@0
|
321 |
} |
|
David@0
|
322 |
|
|
David@0
|
323 |
@end |
|
David@0
|
324 |
|
|
David@0
|
325 |
static OSStatus systemOutputDeviceDidChange(AudioHardwarePropertyID property, void *refcon) |
|
David@0
|
326 |
{ |
|
David@0
|
327 |
#pragma unused(property) |
|
David@0
|
328 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
|
David@0
|
329 |
|
|
David@0
|
330 |
AdiumSound *self = (id)refcon; |
|
David@0
|
331 |
NSCAssert1(self, @"AudioHardware property listener function %s called with nil refcon, which we expected to be the AdiumSound instance", __PRETTY_FUNCTION__); |
|
David@0
|
332 |
|
|
David@0
|
333 |
[self performSelectorOnMainThread:@selector(systemOutputDeviceDidChange) |
|
David@0
|
334 |
withObject:nil |
|
David@0
|
335 |
waitUntilDone:NO]; |
|
David@0
|
336 |
[pool release]; |
|
David@0
|
337 |
|
|
David@0
|
338 |
return noErr; |
|
David@0
|
339 |
} |
|
David@0
|
340 |
|
|
David@0
|
341 |
@implementation NSProcessInfo (AIProcessorInfoAdditions) |
|
David@0
|
342 |
|
|
David@0
|
343 |
- (BOOL)processorFamilyIsG5 |
|
David@0
|
344 |
{ |
|
David@0
|
345 |
/* Credit to http://www.cocoadev.com/index.pl?MacintoshModels */ |
|
David@0
|
346 |
BOOL isG5 = NO; |
|
David@0
|
347 |
char buffer[128]; |
|
David@0
|
348 |
size_t length = sizeof(buffer); |
|
David@0
|
349 |
if (sysctlbyname("hw.model", &buffer, &length, NULL, 0) == 0) { |
|
David@0
|
350 |
NSString *hardwareModel = [NSString stringWithUTF8String:buffer]; |
|
David@0
|
351 |
NSArray *knownG5Macs = [NSArray arrayWithObjects:@"PowerMac11,2" /* G5 PCIe */, @"PowerMac12,1" /* iMac G5 (iSight) */, |
|
David@0
|
352 |
@"PowerMac7,2" /* PowerMac G5 */, @"PowerMac7,3" /* PowerMac G5 */, @"PowerMac8,1" /* iMac G5 */, |
|
David@0
|
353 |
@"PowerMac8,2" /* iMac G5 Ambient Light Sensor */, @"PowerMac9,1" /* Power Mac G5 (Late 2004) */, |
|
David@0
|
354 |
@"RackMac3,1" /* Xserve G5 */, nil]; |
|
David@0
|
355 |
|
|
David@0
|
356 |
if ([knownG5Macs containsObject:hardwareModel]) { |
|
David@0
|
357 |
AILogWithSignature(@"On a G5 Mac."); |
|
David@0
|
358 |
isG5 = YES; |
|
David@0
|
359 |
} |
|
David@0
|
360 |
} |
|
David@0
|
361 |
|
|
David@0
|
362 |
return isG5; |
|
David@0
|
363 |
} |
|
David@0
|
364 |
|
|
David@0
|
365 |
@end |