Q3Controller.m (15184B)
1 /* 2 =========================================================================== 3 Copyright (C) 1999-2005 Id Software, Inc. 4 5 This file is part of Quake III Arena source code. 6 7 Quake III Arena source code is free software; you can redistribute it 8 and/or modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the License, 10 or (at your option) any later version. 11 12 Quake III Arena source code is distributed in the hope that it will be 13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with Foobar; if not, write to the Free Software 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 =========================================================================== 21 */ 22 23 #import "Q3Controller.h" 24 25 #import <Foundation/Foundation.h> 26 #import <AppKit/AppKit.h> 27 28 #include "client.h" 29 #include "macosx_local.h" 30 //#include "GameRanger SDK/gameranger.h" 31 #ifdef OMNI_TIMER 32 #import "macosx_timers.h" 33 #endif 34 35 #define MAX_ARGC 1024 36 37 static qboolean Sys_IsProcessingTerminationRequest = qfalse; 38 static void Sys_CreatePathToFile(NSString *path, NSDictionary *attributes); 39 40 @interface Q3Controller (Private) 41 - (void)quakeMain; 42 @end 43 44 @implementation Q3Controller 45 46 #ifndef DEDICATED 47 48 - (void)applicationDidFinishLaunching:(NSNotification *)notification; 49 { 50 NS_DURING { 51 [self quakeMain]; 52 } NS_HANDLER { 53 Sys_Error("%@", [localException reason]); 54 } NS_ENDHANDLER; 55 Sys_Quit(); 56 } 57 58 - (void)applicationDidUnhide:(NSNotification *)notification; 59 { 60 // Don't reactivate the game if we are asking whether to quit 61 if (Sys_IsProcessingTerminationRequest) 62 return; 63 64 if (!Sys_Unhide()) 65 // Didn't work -- hide again so we should get another chance to unhide later 66 [NSApp hide: nil]; 67 } 68 69 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; 70 { 71 int choice; 72 73 if (!Sys_IsHidden) { 74 // We're terminating via -terminate: 75 return NSTerminateNow; 76 } 77 78 // Avoid reactivating GL when we unhide due to this panel 79 Sys_IsProcessingTerminationRequest = qtrue; 80 choice = NSRunAlertPanel(nil, @"Quit without saving?", @"Don't Quit", @"Quit", nil); 81 Sys_IsProcessingTerminationRequest = qfalse; 82 83 if (choice == NSAlertAlternateReturn) 84 return NSTerminateNow; 85 86 // Make sure we get re-hidden 87 [NSApp hide:nil]; 88 89 return NSTerminateCancel; 90 } 91 92 // Actions 93 94 - (IBAction)paste:(id)sender; 95 { 96 int shiftWasDown, insertWasDown; 97 unsigned int currentTime; 98 99 currentTime = Sys_Milliseconds(); 100 // Save the original keyboard state 101 shiftWasDown = keys[K_SHIFT].down; 102 insertWasDown = keys[K_INS].down; 103 // Fake a Shift-Insert keyboard event 104 keys[K_SHIFT].down = qtrue; 105 Sys_QueEvent(currentTime, SE_KEY, K_INS, qtrue, 0, NULL); 106 Sys_QueEvent(currentTime, SE_KEY, K_INS, qfalse, 0, NULL); 107 // Restore the original keyboard state 108 keys[K_SHIFT].down = shiftWasDown; 109 keys[K_INS].down = insertWasDown; 110 } 111 112 extern void CL_Quit_f(void); 113 114 115 - (IBAction)requestTerminate:(id)sender; 116 { 117 Com_Quit_f(); 118 // UI_QuitMenu(); 119 } 120 121 - (void)showBanner; 122 { 123 static BOOL hasShownBanner = NO; 124 125 if (!hasShownBanner) { 126 cvar_t *showBanner; 127 128 hasShownBanner = YES; 129 showBanner = Cvar_Get("cl_showBanner", "1", 0); 130 if (showBanner->integer != 0) { 131 NSPanel *splashPanel; 132 NSImage *bannerImage; 133 NSRect bannerRect; 134 NSImageView *bannerImageView; 135 136 bannerImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@"banner.jpg"]]; 137 bannerRect = NSMakeRect(0.0, 0.0, [bannerImage size].width, [bannerImage size].height); 138 139 splashPanel = [[NSPanel alloc] initWithContentRect:bannerRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 140 141 bannerImageView = [[NSImageView alloc] initWithFrame:bannerRect]; 142 [bannerImageView setImage:bannerImage]; 143 [splashPanel setContentView:bannerImageView]; 144 [bannerImageView release]; 145 146 [splashPanel center]; 147 [splashPanel setHasShadow:YES]; 148 [splashPanel orderFront: nil]; 149 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.5]]; 150 [splashPanel close]; 151 152 [bannerImage release]; 153 } 154 } 155 } 156 157 // Services 158 159 - (void)connectToServer:(NSPasteboard *)pasteboard userData:(NSString *)data error:(NSString **)error; 160 { 161 NSArray *pasteboardTypes; 162 163 pasteboardTypes = [pasteboard types]; 164 if ([pasteboardTypes containsObject:NSStringPboardType]) { 165 NSString *requestedServer; 166 167 requestedServer = [pasteboard stringForType:NSStringPboardType]; 168 if (requestedServer) { 169 Cbuf_AddText(va("connect %s\n", [requestedServer cString])); 170 return; 171 } 172 } 173 *error = @"Unable to connect to server: could not find string on pasteboard"; 174 } 175 176 - (void)performCommand:(NSPasteboard *)pasteboard userData:(NSString *)data error:(NSString **)error; 177 { 178 NSArray *pasteboardTypes; 179 180 pasteboardTypes = [pasteboard types]; 181 if ([pasteboardTypes containsObject:NSStringPboardType]) { 182 NSString *requestedCommand; 183 184 requestedCommand = [pasteboard stringForType:NSStringPboardType]; 185 if (requestedCommand) { 186 Cbuf_AddText(va("%s\n", [requestedCommand cString])); 187 return; 188 } 189 } 190 *error = @"Unable to perform command: could not find string on pasteboard"; 191 } 192 193 #endif 194 195 - (void)quakeMain; 196 { 197 NSAutoreleasePool *pool; 198 int argc = 0; 199 const char *argv[MAX_ARGC]; 200 NSProcessInfo *processInfo; 201 NSArray *arguments; 202 unsigned int argumentIndex, argumentCount; 203 NSFileManager *defaultManager; 204 unsigned int commandLineLength; 205 NSString *installationPathKey, *installationPath; 206 char *cmdline; 207 BOOL foundDirectory; 208 NSString *appName, *demoAppName, *selectButton; 209 int count = 0; 210 pool = [[NSAutoreleasePool alloc] init]; 211 212 [NSApp setServicesProvider:self]; 213 214 processInfo = [NSProcessInfo processInfo]; 215 arguments = [processInfo arguments]; 216 argumentCount = [arguments count]; 217 for (argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) { 218 NSString *arg; 219 220 arg = [arguments objectAtIndex:argumentIndex]; 221 // Don't pass the Process Serial Number command line arg that the Window Server/Finder invokes us with 222 if ([arg hasPrefix: @"-psn_"]) 223 continue; 224 225 argv[argc++] = strdup([arg cString]); 226 } 227 228 // Figure out where the level data is stored. 229 installationPathKey = @"RetailInstallationPath"; 230 231 installationPath = [[NSUserDefaults standardUserDefaults] objectForKey:installationPathKey]; 232 if (!installationPath) { 233 // Default to the directory containing the executable (which is where most users will want to put it 234 installationPath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]; 235 } 236 237 #if !defined(DEDICATED) 238 appName = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"CFBundleName"]; 239 #else 240 // We are hard coding the app name here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck. 241 appName = @"Quake3"; 242 #endif 243 demoAppName = appName; 244 245 while (YES) { 246 NSString *dataPath; 247 NSOpenPanel *openPanel; 248 int result; 249 250 foundDirectory = NO; 251 defaultManager = [NSFileManager defaultManager]; 252 //NSLog(@"Candidate installation path = %@", installationPath); 253 dataPath = [installationPath stringByAppendingPathComponent: @"baseq3"]; 254 255 if ([defaultManager fileExistsAtPath: dataPath]) { 256 // Check that the data directory contains at least one .pk3 file. We don't know what it will be named, so don't hard code a name (for example it might be named 'french.pk3' for a French release 257 NSArray *files; 258 unsigned int fileIndex; 259 260 files = [defaultManager directoryContentsAtPath: dataPath]; 261 fileIndex = [files count]; 262 while (fileIndex--) { 263 if ([[files objectAtIndex: fileIndex] hasSuffix: @"pk3"]) { 264 //NSLog(@"Found %@.", [files objectAtIndex: fileIndex]); 265 foundDirectory = YES; 266 break; 267 } 268 } 269 } 270 271 if (foundDirectory) 272 break; 273 274 #ifdef DEDICATED 275 break; 276 #warning TJW: We are hard coding the app name and default domain here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck. 277 NSLog(@"Unable to determine installation directory. Please move the executable into the '%@' installation directory or add a '%@' key in the 'Q3DedicatedServer' defaults domain.", appName, installationPathKey, [[NSBundle mainBundle] bundleIdentifier]); 278 Sys_Quit(); 279 exit(1); 280 #else 281 selectButton = @"Select Retail Installation..."; 282 283 result = NSRunAlertPanel(demoAppName, @"You need to select the installation directory for %@ (not any directory inside of it -- the installation directory itself).", selectButton, @"Quit", nil, appName); 284 switch (result) { 285 case NSAlertDefaultReturn: 286 break; 287 default: 288 Sys_Quit(); 289 break; 290 } 291 292 openPanel = [NSOpenPanel openPanel]; 293 [openPanel setAllowsMultipleSelection:NO]; 294 [openPanel setCanChooseDirectories:YES]; 295 [openPanel setCanChooseFiles:NO]; 296 result = [openPanel runModalForDirectory:nil file:nil]; 297 if (result == NSOKButton) { 298 NSArray *filenames; 299 300 filenames = [openPanel filenames]; 301 if ([filenames count] == 1) { 302 installationPath = [filenames objectAtIndex:0]; 303 [[NSUserDefaults standardUserDefaults] setObject:installationPath forKey:installationPathKey]; 304 [[NSUserDefaults standardUserDefaults] synchronize]; 305 } 306 } 307 #endif 308 } 309 310 // Create the application support directory if it doesn't exist already 311 do { 312 NSArray *results; 313 NSString *libraryPath, *homePath, *filePath; 314 NSDictionary *attributes; 315 316 results = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); 317 if (![results count]) 318 break; 319 320 libraryPath = [results objectAtIndex: 0]; 321 homePath = [libraryPath stringByAppendingPathComponent: @"Application Support"]; 322 homePath = [homePath stringByAppendingPathComponent: appName]; 323 filePath = [homePath stringByAppendingPathComponent: @"foo"]; 324 325 attributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt: 0750], NSFilePosixPermissions, nil]; 326 NS_DURING { 327 Sys_CreatePathToFile(filePath, attributes); 328 Sys_SetDefaultHomePath([homePath fileSystemRepresentation]); 329 } NS_HANDLER { 330 NSLog(@"Exception: %@", localException); 331 #ifndef DEDICATED 332 NSRunAlertPanel(nil, @"Unable to create '%@'. Please make sure that you have permission to write to this folder and re-run the game.", @"OK", nil, nil, homePath); 333 #endif 334 Sys_Quit(); 335 } NS_ENDHANDLER; 336 } while(0); 337 338 // Provoke the CD scanning code into looking up the CD. 339 Sys_CheckCD(); 340 341 // Let the filesystem know where our local install is 342 Sys_SetDefaultInstallPath([installationPath cString]); 343 344 cmdline = NULL; 345 #if 0 346 if (GRCheckFileForCmd()) { 347 GRGetWaitingCmd(); 348 if (GRHasProperty( 'Exec' )) { 349 NSString *cfgPath, *grCfg; 350 cfgPath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]; 351 cfgPath = [cfgPath stringByAppendingPathComponent: [NSString stringWithCString: GRGetPropertyStr( 'Exec' )]]; 352 grCfg = [NSString stringWithContentsOfFile: cfgPath]; 353 cmdline = malloc(strlen([grCfg cString])+1); 354 [grCfg getCString: cmdline]; 355 } 356 } 357 #endif 358 if (!cmdline) { 359 // merge the command line, this is kinda silly 360 for (commandLineLength = 1, argumentIndex = 1; argumentIndex < argc; argumentIndex++) 361 commandLineLength += strlen(argv[argumentIndex]) + 1; 362 cmdline = malloc(commandLineLength); 363 *cmdline = '\0'; 364 for (argumentIndex = 1; argumentIndex < argc; argumentIndex++) { 365 if (argumentIndex > 1) 366 strcat(cmdline, " "); 367 strcat(cmdline, argv[argumentIndex]); 368 } 369 } 370 Com_Printf("command line: %s\n", cmdline); 371 372 Com_Init(cmdline); 373 374 #ifndef DEDICATED 375 [NSApp activateIgnoringOtherApps:YES]; 376 #endif 377 378 while (1) { 379 Com_Frame(); 380 381 if ((count & 15)==0) { 382 // We should think about doing this less frequently than every frame 383 [pool release]; 384 pool = [[NSAutoreleasePool alloc] init]; 385 } 386 } 387 388 [pool release]; 389 } 390 391 @end 392 393 394 395 // Creates any directories needed to be able to create a file at the specified path. Raises an exception on failure. 396 static void Sys_CreatePathToFile(NSString *path, NSDictionary *attributes) 397 { 398 NSArray *pathComponents; 399 unsigned int dirIndex, dirCount; 400 unsigned int startingIndex; 401 NSFileManager *manager; 402 403 manager = [NSFileManager defaultManager]; 404 pathComponents = [path pathComponents]; 405 dirCount = [pathComponents count] - 1; 406 407 startingIndex = 0; 408 for (dirIndex = startingIndex; dirIndex < dirCount; dirIndex++) { 409 NSString *partialPath; 410 BOOL fileExists; 411 412 partialPath = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange(0, dirIndex + 1)]]; 413 414 // Don't use the 'fileExistsAtPath:isDirectory:' version since it doesn't traverse symlinks 415 fileExists = [manager fileExistsAtPath:partialPath]; 416 if (!fileExists) { 417 if (![manager createDirectoryAtPath:partialPath attributes:attributes]) { 418 [NSException raise:NSGenericException format:@"Unable to create a directory at path: %@", partialPath]; 419 } 420 } else { 421 NSDictionary *attributes; 422 423 attributes = [manager fileAttributesAtPath:partialPath traverseLink:YES]; 424 if (![[attributes objectForKey:NSFileType] isEqualToString: NSFileTypeDirectory]) { 425 [NSException raise:NSGenericException format:@"Unable to write to path \"%@\" because \"%@\" is not a directory", 426 path, partialPath]; 427 } 428 } 429 } 430 } 431 432 #ifdef DEDICATED 433 void S_ClearSoundBuffer( void ) { 434 } 435 #endif