Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

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