home *** CD-ROM | disk | FTP | other *** search
- #import "Launcher.h"
- #import "ConsoleView.h"
- #include <stdlib.h>
- #include <unistd.h> /* unlink */
- #include <util.h> /* forkpty */
-
- #define kMaxDisplays 16
-
- // unless you want strings with "(null)" in them :-/
- @interface NSUserDefaults(Extras)
- - (NSString*)nonNullStringForKey:(NSString*)key;
- @end
-
- @implementation NSUserDefaults(Extras)
- - (NSString*)nonNullStringForKey:(NSString*)key {
- NSString *result = [self stringForKey:key];
- return (result?result:@"");
- }
- @end
-
-
-
- @interface Map : NSObject {
- NSString *path;
- }
- @end
-
- @implementation Map
- - (id)initWithPath:(NSString*)aPath
- {
- if((self = [super init]))
- path = [[aPath stringByDeletingPathExtension] retain];
- return self;
- }
- - (void)dealloc
- {
- [path release];
- [super dealloc];
- }
- - (NSString*)path { return path; }
- - (NSString*)name { return [path lastPathComponent]; }
- - (NSImage*)image { return [[NSImage alloc] initWithContentsOfFile:[path stringByAppendingString:@".jpg"]]; }
- - (NSString*)text
- {
- NSString *text = [NSString alloc];
- if(![text respondsToSelector:@selector(initWithContentsOfFile:encoding:error:)])
- return [text initWithContentsOfFile:[path stringByAppendingString:@".txt"]]; //deprecated in 10.4
- NSError *error;
- return [text initWithContentsOfFile:[path stringByAppendingString:@".txt"] encoding:NSASCIIStringEncoding error:&error];
- }
- - (NSString*)tickIfExists:(NSString*)ext
- {
- unichar tickCh = 0x2713;
- return [[NSFileManager defaultManager] fileExistsAtPath:[path stringByAppendingString:ext]] ? [NSString stringWithCharacters:&tickCh length:1] : @"";
- }
- - (NSString*)hasImage { return [self tickIfExists:@".jpg"]; }
- - (NSString*)hasText { return [self tickIfExists:@".txt"]; }
- - (NSString*)hasCfg { return [self tickIfExists:@".cfg"]; }
- @end
-
-
-
- static int numberForKey(CFDictionaryRef desc, CFStringRef key)
- {
- CFNumberRef value;
- int num = 0;
- if ((value = CFDictionaryGetValue(desc, key)) == NULL)
- return 0;
- CFNumberGetValue(value, kCFNumberIntType, &num);
- return num;
- }
-
- @implementation Launcher
-
- - (void)switchViews:(NSToolbarItem *)item
- {
- NSView *prefsView;
- switch([item tag])
- {
- case 1: prefsView = view1; break;
- case 2: prefsView = view2; break;
- case 3: prefsView = view3; break;
- //extend as see fit...
- default: return;
- }
-
- //to stop flicker, we make a temp blank view.
- NSView *tempView = [[NSView alloc] initWithFrame:[[window contentView] frame]];
- [window setContentView:tempView];
- [tempView release];
-
- //mojo to get the right frame for the new window.
- NSRect newFrame = [window frame];
- newFrame.size.height = [prefsView frame].size.height + ([window frame].size.height - [[window contentView] frame].size.height);
- newFrame.size.width = [prefsView frame].size.width;
- newFrame.origin.y += ([[window contentView] frame].size.height - [prefsView frame].size.height);
-
- //set the frame to newFrame and animate it.
- [window setFrame:newFrame display:YES animate:YES];
- //set the main content view to the new view we have picked through the if structure above.
- [window setContentView:prefsView];
- [window setContentMinSize:[prefsView bounds].size];
- }
-
- - (NSToolbarItem*)addToolBarItem:(NSString*)name
- {
- int n = [toolBarItems count] + 1;
- NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:[NSString stringWithFormat:@"%0d", n]];
- [item setTag:n];
- [item setTarget:self];
- [item setAction:@selector(switchViews:)];
- [toolBarItems setObject:item forKey:[item itemIdentifier]];
- [item setLabel:NSLocalizedString(name, @"")];
- [item setImage:[NSImage imageNamed:name]];
- [item release];
- return item;
- }
-
- - (void)initViews
- {
- toolBarItems = [[NSMutableDictionary alloc] init];
- NSToolbarItem *first = [self addToolBarItem:@"Main"];
- [self addToolBarItem:@"Keys"];
- [self addToolBarItem:@"Server"];
-
- NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier];
- [toolBarItems setObject:item forKey:[item itemIdentifier]];
- [item release];
-
- [[self addToolBarItem:@"Help"] setAction:@selector(helpAction:)];
-
- NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"PreferencePanes"];
- [toolbar setDelegate:self];
- [toolbar setAllowsUserCustomization:NO];
- [toolbar setAutosavesConfiguration:NO];
- [window setToolbar:toolbar];
- [toolbar release];
- if([window respondsToSelector:@selector(setShowsToolbarButton:)]) [window setShowsToolbarButton:NO]; //10.4+
-
- //Make it select the first by default
- [toolbar setSelectedItemIdentifier:[first itemIdentifier]];
- [self switchViews:first];
- }
-
- /*
- * toolbar delegate methods
- */
- - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
- {
- return [toolBarItems objectForKey:itemIdentifier];
- }
-
- - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)theToolbar
- {
- return [self toolbarDefaultItemIdentifiers:theToolbar];
- }
-
- - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)theToolbar
- {
- return [NSArray arrayWithObjects:@"1", @"2", @"3", NSToolbarFlexibleSpaceItemIdentifier, @"5", nil];
- }
-
- - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar {
- return [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
- }
-
-
-
-
-
-
- - (void)addResolutionsForDisplay:(CGDirectDisplayID)dspy
- {
- CFIndex i, cnt;
- CFArrayRef modeList = CGDisplayAvailableModes(dspy);
- if(modeList == NULL) return;
- cnt = CFArrayGetCount(modeList);
- for(i = 0; i < cnt; i++) {
- CFDictionaryRef mode = CFArrayGetValueAtIndex(modeList, i);
- NSString *title = [NSString stringWithFormat:@"%i x %i", numberForKey(mode, kCGDisplayWidth), numberForKey(mode, kCGDisplayHeight)];
- if(![resolutions itemWithTitle:title]) [resolutions addItemWithTitle:title];
- }
- }
-
- - (void)initResolutions
- {
- CGDirectDisplayID display[kMaxDisplays];
- CGDisplayCount numDisplays;
- [resolutions removeAllItems];
- if(CGGetActiveDisplayList(kMaxDisplays, display, &numDisplays) == CGDisplayNoErr)
- {
- CGDisplayCount i;
- for (i = 0; i < numDisplays; i++)
- [self addResolutionsForDisplay:display[i]];
- }
- [resolutions selectItemAtIndex: [[NSUserDefaults standardUserDefaults] integerForKey:@"resolution"]];
- }
-
-
-
-
- /* directory where the executable lives */
- -(NSString *)cwd
- {
- return [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"assaultcube"];
- }
-
- /* build key array from config data */
- -(NSArray *)getKeys:(NSDictionary *)dict
- {
- NSMutableArray *arr = [[NSMutableArray alloc] init];
- NSEnumerator *e = [dict keyEnumerator];
- NSString *key;
- while ((key = [e nextObject]))
- {
- NSString *trig;
- if([key hasPrefix:@"editbind"])
- trig = [key substringFromIndex:9];
- else if([key hasPrefix:@"bind"])
- trig = [key substringFromIndex:5];
- else
- continue;
- [arr addObject:[NSDictionary dictionaryWithObjectsAndKeys: //keys used in nib
- trig, @"key",
- [key hasPrefix:@"editbind"]?@"edit":@"", @"mode",
- [dict objectForKey:key], @"action",
- nil]];
- }
- return arr;
- }
-
-
- /*
- * extract a dictionary from the config files containing:
- * - name, gamma strings
- * - bind/editbind '.' key strings
- */
- -(NSDictionary *)readConfigFiles
- {
- NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
- [dict setObject:@"" forKey:@"name"]; //ensure these entries are never nil
-
- NSString *files[] = {@"config.cfg", @"autoexec.cfg"};
- int i;
- for(i = 0; i < sizeof(files)/sizeof(NSString*); i++)
- {
- NSString *file = [[self cwd] stringByAppendingPathComponent:files[i]];
- NSArray *lines = [[NSString stringWithContentsOfFile:file] componentsSeparatedByString:@"\n"];
-
- if(i==0 && !lines) // ugh - special case when first run...
- {
- file = [[self cwd] stringByAppendingPathComponent:@"config/defaults.cfg"];
- lines = [[NSString stringWithContentsOfFile:file] componentsSeparatedByString:@"\n"];
- }
-
- NSString *line;
- NSEnumerator *e = [lines objectEnumerator];
- while(line = [e nextObject])
- {
- NSRange r; // more flexible to do this manually rather than via NSScanner...
- int j = 0;
- while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
- r.location = j;
- while(j < [line length] && [line characterAtIndex:j] > ' ') j++; //until white
- r.length = j - r.location;
- NSString *type = [line substringWithRange:r];
-
- while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
- if(j < [line length] && [line characterAtIndex:j] == '"')
- {
- r.location = ++j;
- while(j < [line length] && [line characterAtIndex:j] != '"') j++; //until close quote
- r.length = (j++) - r.location;
- } else {
- r.location = j;
- while(j < [line length] && [line characterAtIndex:j] > ' ') j++; //until white
- r.length = j - r.location;
- }
- NSString *value = [line substringWithRange:r];
-
- while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
- NSString *remainder = [line substringFromIndex:j];
-
- if([type isEqual:@"name"] || [type isEqual:@"gamma"])
- [dict setObject:value forKey:type];
- else if([type isEqual:@"bind"] || [type isEqual:@"editbind"])
- [dict setObject:remainder forKey:[NSString stringWithFormat:@"%@.%@", type,value]];
- }
- }
- return dict;
- }
-
- -(void)updateAutoexecFile:(NSDictionary *)updates
- {
- NSString *file = [[self cwd] stringByAppendingPathComponent:@"config/autoexec.cfg"];
- //build the data
- NSString *result = nil;
- NSArray *lines = [[NSString stringWithContentsOfFile:file] componentsSeparatedByString:@"\n"];
- if(lines)
- {
- NSString *line;
- NSEnumerator *e = [lines objectEnumerator];
- while(line = [e nextObject])
- {
- NSScanner *scanner = [NSScanner scannerWithString:line];
- NSString *type;
- if([scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] intoString:&type])
- if([updates objectForKey:type]) continue; //skip things declared in updates
- result = (result) ? [NSString stringWithFormat:@"%@\n%@", result, line] : line;
- }
- }
- NSEnumerator *e = [updates keyEnumerator];
- NSString *type;
- while(type = [e nextObject])
- {
- id value = [updates objectForKey:type];
- if([type isEqual:@"name"]) value = [NSString stringWithFormat:@"\"%@\"", value];
- NSString *line = [NSString stringWithFormat:@"%@ %@", type, value];
- result = (result) ? [NSString stringWithFormat:@"%@\n%@", result, line] : line;
- }
- //backup
- NSFileManager *fm = [NSFileManager defaultManager];
- NSString *backupfile = nil;
- if([fm fileExistsAtPath:file])
- {
- backupfile = [file stringByAppendingString:@".bak"];
- if(![fm movePath:file toPath:backupfile handler:nil]) return; //can't create backup
- }
- //write the new file
- if(![fm createFileAtPath:file contents:[result dataUsingEncoding:NSASCIIStringEncoding] attributes:nil]) return; //can't create new file
- //remove the backup
- if(backupfile) [fm removeFileAtPath:backupfile handler:nil];
- }
-
- - (void)serverTerminated
- {
- if(server==-1) return;
- server = -1;
- [multiplayer setTitle:@"Start"];
- [console appendText:@"\n \n"];
- }
-
- - (void)setServerActive:(BOOL)start
- {
- if((server==-1) != start) return;
-
- if(!start)
- { //STOP
-
- //damn server, terminate isn't good enough for you - die, die, die...
- if((server!=-1) && (server!=0)) kill(server, SIGKILL); //@WARNING - you do not want a 0 or -1 to be accidentally sent a kill!
- [self serverTerminated];
- }
- else
- { //START
- NSString *cwd = [self cwd];
- NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
-
- NSArray *opts = [[defs nonNullStringForKey:@"server_options"] componentsSeparatedByString:@" "];
-
- const char *childCwd = [cwd fileSystemRepresentation];
- const char *childPath = [[cwd stringByAppendingPathComponent:@"actioncube.app/Contents/MacOS/actioncube"] fileSystemRepresentation];
- const char **args = (const char**)malloc(sizeof(char*)*([opts count] + 3 + 3)); //3 = {path, -d, NULL}, and +3 again for optional settings...
- int i, fdm, argc = 0;
-
- args[argc++] = childPath;
- args[argc++] = "-d";
-
- for(i = 0; i < [opts count]; i++)
- {
- NSString *opt = [opts objectAtIndex:i];
- if([opt length] == 0) continue; //skip empty
- args[argc++] = [opt UTF8String];
- }
-
- NSString *desc = [[NSUserDefaults standardUserDefaults] nonNullStringForKey:@"server_description"];
- if (![desc isEqualToString:@""]) args[argc++] = [[NSString stringWithFormat:@"-n%@", desc] UTF8String];
-
- NSString *pass = [[NSUserDefaults standardUserDefaults] nonNullStringForKey:@"server_password"];
- if (![pass isEqualToString:@""]) args[argc++] = [[NSString stringWithFormat:@"-x%@", pass] UTF8String];
-
- int clients = [[NSUserDefaults standardUserDefaults] integerForKey:@"server_maxclients"];
- if (clients > 0) args[argc++] = [[NSString stringWithFormat:@"-c%d", clients] UTF8String];
-
- args[argc++] = NULL;
-
- switch ( (server = forkpty(&fdm, NULL, NULL, NULL)) ) // forkpty so we can reliably grab SDL console
- {
- case -1:
- [console appendLine:@"Error - can't launch server"];
- [self serverTerminated];
- break;
- case 0: // child
- chdir(childCwd);
- execv(childPath, (char*const*)args);
- fprintf(stderr, "Error - can't launch server\n");
- _exit(0);
- default: // parent
- free(args);
- //fprintf(stderr, "fdm=%d\n", slave_name, fdm);
- [multiplayer setTitle:@"Stop"];
-
- NSFileHandle *taskOutput = [[NSFileHandle alloc] initWithFileDescriptor:fdm];
- NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
- [nc addObserver:self selector:@selector(serverDataAvailable:) name:NSFileHandleReadCompletionNotification object:taskOutput];
- [taskOutput readInBackgroundAndNotify];
- break;
- }
- }
- }
-
- - (void)serverDataAvailable:(NSNotification *)note
- {
- NSFileHandle *taskOutput = [note object];
- NSData *data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem];
-
- if (data && [data length])
- {
- NSString *text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
- [console appendText:text];
- [text release];
- [taskOutput readInBackgroundAndNotify]; //wait for more data
- }
- else
- {
- NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
- [nc removeObserver:self name:NSFileHandleReadCompletionNotification object:taskOutput];
- close([taskOutput fileDescriptor]);
- [self setServerActive:NO];
- }
- }
-
- - (BOOL)playFile
- {
- NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
-
- NSArray *res = [[resolutions titleOfSelectedItem] componentsSeparatedByString:@" x "];
- NSMutableArray *args = [[NSMutableArray alloc] init];
- NSString *cwd = [self cwd];
-
- //suppose could use this to update gamma and keys too, but can't be bothered...
- [self updateAutoexecFile:[NSDictionary dictionaryWithObjectsAndKeys:
- [defs nonNullStringForKey:@"name"], @"name",
- nil]];
-
- [args addObject:[NSString stringWithFormat:@"-w%@", [res objectAtIndex:0]]];
- [args addObject:[NSString stringWithFormat:@"-h%@", [res objectAtIndex:1]]];
- [args addObject:@"-z32"];
-
- if([defs integerForKey:@"fullscreen"] == 0) [args addObject:@"-t"];
- [args addObject:[NSString stringWithFormat:@"-a%d", [defs integerForKey:@"fsaa"]]];
- [args addObject:[NSString stringWithFormat:@"-f%d", [defs integerForKey:@"shader"]]];
-
- NSString *adv = [defs nonNullStringForKey:@"advancedOptions"];
- if(![adv isEqual:@""]) [args addObjectsFromArray:[adv componentsSeparatedByString:@" "]];
-
- NSTask *task = [[NSTask alloc] init];
- [task setCurrentDirectoryPath:cwd];
- [task setLaunchPath:[cwd stringByAppendingPathComponent:@"actioncube.app/Contents/MacOS/actioncube"]];
- [task setArguments:args];
- [task setEnvironment:[NSDictionary dictionaryWithObjectsAndKeys:
- @"1", @"SDL_SINGLEDISPLAY",
- @"1", @"SDL_ENABLEAPPEVENTS", nil
- ]]; // makes Command-H, Command-M and Command-Q work at least when not in fullscreen
- [args release];
-
- BOOL okay = YES;
-
- NS_DURING
- [task launch];
- if(server==-1) [NSApp terminate:self]; //if there is a server then don't exit!
- NS_HANDLER
- //NSLog(@"%@", localException);
- NSBeginCriticalAlertSheet(
- @"Can't start AssaultCube", nil, nil, nil,
- window, nil, nil, nil, nil,
- @"Please move the directory containing AssaultCube to a path that doesn't contain weird characters or start AssaultCube manually.");
- okay = NO;
- NS_ENDHANDLER
-
- return okay;
- }
-
- - (void)awakeFromNib
- {
- [self initViews];
-
- NSDictionary *dict = [self readConfigFiles];
- [keys addObjects:[self getKeys:dict]];
- NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
- if([[defs nonNullStringForKey:@"name"] isEqual:@""])
- {
- NSString *name = [dict objectForKey:@"name"];
- if([name isEqual:@""] || [name isEqual:@"unnamed"]) name = NSUserName();
- [defs setValue:name forKey:@"name"];
- }
-
- [self initResolutions];
- server = -1;
- [window setDelegate:self]; // so can catch the window close
- [NSApp setDelegate:self]; //so can catch the double-click & dropped files
- }
-
- -(void)windowWillClose:(NSNotification *)notification
- {
- [self setServerActive:NO];
- [NSApp terminate:self];
- }
-
- /*
- * Interface actions
- */
- - (IBAction)multiplayerAction:(id)sender
- {
- [window makeFirstResponder:window]; //ensure fields are exited and committed
- [self setServerActive:(server==-1)];
- }
-
- - (IBAction)playAction:(id)sender
- {
- [window makeFirstResponder:window]; //ensure fields are exited and committed
- [self playFile];
- }
-
- - (IBAction)helpAction:(id)sender
- {
- NSString *file = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"README.html"];
- [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:file]];
- }
- @end