1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22
23/* This file takes care of command line argument parsing, and stdio redirection
24   in the MacOS environment. (stdio/stderr is *not* directed for Mach-O builds)
25 */
26
27#if defined(__APPLE__) && defined(__MACH__)
28#include <Carbon/Carbon.h>
29#elif TARGET_API_MAC_CARBON && (UNIVERSAL_INTERFACES_VERSION > 0x0335)
30#include <Carbon.h>
31#else
32#include <Dialogs.h>
33#include <Fonts.h>
34#include <Events.h>
35#include <Resources.h>
36#include <Folders.h>
37#endif
38
39/* Include the SDL main definition header */
40#include "SDL.h"
41#include "SDL_main.h"
42#ifdef main
43#undef main
44#endif
45
46#if !(defined(__APPLE__) && defined(__MACH__))
47/* The standard output files */
48#define STDOUT_FILE	"stdout.txt"
49#define STDERR_FILE	"stderr.txt"
50#endif
51
52#if !defined(__MWERKS__) && !TARGET_API_MAC_CARBON
53	/* In MPW, the qd global has been removed from the libraries */
54	QDGlobals qd;
55#endif
56
57/* Structure for keeping prefs in 1 variable */
58typedef struct {
59    Str255  command_line;
60    Str255  video_driver_name;
61    Boolean output_to_file;
62}  PrefsRecord;
63
64/* See if the command key is held down at startup */
65static Boolean CommandKeyIsDown(void)
66{
67	KeyMap  theKeyMap;
68
69	GetKeys(theKeyMap);
70
71	if (((unsigned char *) theKeyMap)[6] & 0x80) {
72		return(true);
73	}
74	return(false);
75}
76
77#if !(defined(__APPLE__) && defined(__MACH__))
78
79/* Parse a command line buffer into arguments */
80static int ParseCommandLine(char *cmdline, char **argv)
81{
82	char *bufp;
83	int argc;
84
85	argc = 0;
86	for ( bufp = cmdline; *bufp; ) {
87		/* Skip leading whitespace */
88		while ( SDL_isspace(*bufp) ) {
89			++bufp;
90		}
91		/* Skip over argument */
92		if ( *bufp == '"' ) {
93			++bufp;
94			if ( *bufp ) {
95				if ( argv ) {
96					argv[argc] = bufp;
97				}
98				++argc;
99			}
100			/* Skip over word */
101			while ( *bufp && (*bufp != '"') ) {
102				++bufp;
103			}
104		} else {
105			if ( *bufp ) {
106				if ( argv ) {
107					argv[argc] = bufp;
108				}
109				++argc;
110			}
111			/* Skip over word */
112			while ( *bufp && ! SDL_isspace(*bufp) ) {
113				++bufp;
114			}
115		}
116		if ( *bufp ) {
117			if ( argv ) {
118				*bufp = '\0';
119			}
120			++bufp;
121		}
122	}
123	if ( argv ) {
124		argv[argc] = NULL;
125	}
126	return(argc);
127}
128
129/* Remove the output files if there was no output written */
130static void cleanup_output(void)
131{
132	FILE *file;
133	int empty;
134
135	/* Flush the output in case anything is queued */
136	fclose(stdout);
137	fclose(stderr);
138
139	/* See if the files have any output in them */
140	file = fopen(STDOUT_FILE, "rb");
141	if ( file ) {
142		empty = (fgetc(file) == EOF) ? 1 : 0;
143		fclose(file);
144		if ( empty ) {
145			remove(STDOUT_FILE);
146		}
147	}
148	file = fopen(STDERR_FILE, "rb");
149	if ( file ) {
150		empty = (fgetc(file) == EOF) ? 1 : 0;
151		fclose(file);
152		if ( empty ) {
153			remove(STDERR_FILE);
154		}
155	}
156}
157
158#endif //!(defined(__APPLE__) && defined(__MACH__))
159
160static int getCurrentAppName (StrFileName name) {
161
162    ProcessSerialNumber process;
163    ProcessInfoRec      process_info;
164    FSSpec              process_fsp;
165
166    process.highLongOfPSN = 0;
167    process.lowLongOfPSN  = kCurrentProcess;
168    process_info.processInfoLength = sizeof (process_info);
169    process_info.processName    = NULL;
170    process_info.processAppSpec = &process_fsp;
171
172    if ( noErr != GetProcessInformation (&process, &process_info) )
173       return 0;
174
175    SDL_memcpy(name, process_fsp.name, process_fsp.name[0] + 1);
176    return 1;
177}
178
179static int getPrefsFile (FSSpec *prefs_fsp, int create) {
180
181    /* The prefs file name is the application name, possibly truncated, */
182    /* plus " Preferences */
183
184    #define  SUFFIX   " Preferences"
185    #define  MAX_NAME 19             /* 31 - strlen (SUFFIX) */
186
187    short  volume_ref_number;
188    long   directory_id;
189    StrFileName  prefs_name;
190    StrFileName  app_name;
191
192    /* Get Preferences folder - works with Multiple Users */
193    if ( noErr != FindFolder ( kOnSystemDisk, kPreferencesFolderType, kDontCreateFolder,
194                               &volume_ref_number, &directory_id) )
195        exit (-1);
196
197    if ( ! getCurrentAppName (app_name) )
198        exit (-1);
199
200    /* Truncate if name is too long */
201    if (app_name[0] > MAX_NAME )
202        app_name[0] = MAX_NAME;
203
204    SDL_memcpy(prefs_name + 1, app_name + 1, app_name[0]);
205    SDL_memcpy(prefs_name + app_name[0] + 1, SUFFIX, strlen (SUFFIX));
206    prefs_name[0] = app_name[0] + strlen (SUFFIX);
207
208    /* Make the file spec for prefs file */
209    if ( noErr != FSMakeFSSpec (volume_ref_number, directory_id, prefs_name, prefs_fsp) ) {
210        if ( !create )
211            return 0;
212        else {
213            /* Create the prefs file */
214            SDL_memcpy(prefs_fsp->name, prefs_name, prefs_name[0] + 1);
215            prefs_fsp->parID   = directory_id;
216            prefs_fsp->vRefNum = volume_ref_number;
217
218            FSpCreateResFile (prefs_fsp, 0x3f3f3f3f, 'pref', 0); // '????' parsed as trigraph
219
220            if ( noErr != ResError () )
221                return 0;
222        }
223     }
224    return 1;
225}
226
227static int readPrefsResource (PrefsRecord *prefs) {
228
229    Handle prefs_handle;
230
231    prefs_handle = Get1Resource( 'CLne', 128 );
232
233	if (prefs_handle != NULL) {
234		int offset = 0;
235//		int j      = 0;
236
237		HLock(prefs_handle);
238
239		/* Get command line string */
240		SDL_memcpy(prefs->command_line, *prefs_handle, (*prefs_handle)[0]+1);
241
242		/* Get video driver name */
243		offset += (*prefs_handle)[0] + 1;
244		SDL_memcpy(prefs->video_driver_name, *prefs_handle + offset, (*prefs_handle)[offset] + 1);
245
246		/* Get save-to-file option (1 or 0) */
247		offset += (*prefs_handle)[offset] + 1;
248		prefs->output_to_file = (*prefs_handle)[offset];
249
250		ReleaseResource( prefs_handle );
251
252        return ResError() == noErr;
253    }
254
255    return 0;
256}
257
258static int writePrefsResource (PrefsRecord *prefs, short resource_file) {
259
260    Handle prefs_handle;
261
262    UseResFile (resource_file);
263
264    prefs_handle = Get1Resource ( 'CLne', 128 );
265    if (prefs_handle != NULL)
266        RemoveResource (prefs_handle);
267
268    prefs_handle = NewHandle ( prefs->command_line[0] + prefs->video_driver_name[0] + 4 );
269    if (prefs_handle != NULL) {
270
271        int offset;
272
273        HLock (prefs_handle);
274
275        /* Command line text */
276        offset = 0;
277        SDL_memcpy(*prefs_handle, prefs->command_line, prefs->command_line[0] + 1);
278
279        /* Video driver name */
280        offset += prefs->command_line[0] + 1;
281        SDL_memcpy(*prefs_handle + offset, prefs->video_driver_name, prefs->video_driver_name[0] + 1);
282
283        /* Output-to-file option */
284        offset += prefs->video_driver_name[0] + 1;
285        *( *((char**)prefs_handle) + offset)     = (char)prefs->output_to_file;
286        *( *((char**)prefs_handle) + offset + 1) = 0;
287
288        AddResource   (prefs_handle, 'CLne', 128, "\pCommand Line");
289        WriteResource (prefs_handle);
290        UpdateResFile (resource_file);
291        DisposeHandle (prefs_handle);
292
293        return ResError() == noErr;
294    }
295
296    return 0;
297}
298
299static int readPreferences (PrefsRecord *prefs) {
300
301    int    no_error = 1;
302    FSSpec prefs_fsp;
303
304    /* Check for prefs file first */
305    if ( getPrefsFile (&prefs_fsp, 0) ) {
306
307        short  prefs_resource;
308
309        prefs_resource = FSpOpenResFile (&prefs_fsp, fsRdPerm);
310        if ( prefs_resource == -1 ) /* this shouldn't happen, but... */
311            return 0;
312
313        UseResFile   (prefs_resource);
314        no_error = readPrefsResource (prefs);
315        CloseResFile (prefs_resource);
316    }
317
318    /* Fall back to application's resource fork (reading only, so this is safe) */
319    else {
320
321          no_error = readPrefsResource (prefs);
322     }
323
324    return no_error;
325}
326
327static int writePreferences (PrefsRecord *prefs) {
328
329    int    no_error = 1;
330    FSSpec prefs_fsp;
331
332    /* Get prefs file, create if it doesn't exist */
333    if ( getPrefsFile (&prefs_fsp, 1) ) {
334
335        short  prefs_resource;
336
337        prefs_resource = FSpOpenResFile (&prefs_fsp, fsRdWrPerm);
338        if (prefs_resource == -1)
339            return 0;
340        no_error = writePrefsResource (prefs, prefs_resource);
341        CloseResFile (prefs_resource);
342    }
343
344    return no_error;
345}
346
347/* This is where execution begins */
348int main(int argc, char *argv[])
349{
350
351#if !(defined(__APPLE__) && defined(__MACH__))
352#pragma unused(argc, argv)
353#endif
354
355#define DEFAULT_ARGS "\p"                /* pascal string for default args */
356#define DEFAULT_VIDEO_DRIVER "\ptoolbox" /* pascal string for default video driver name */
357#define DEFAULT_OUTPUT_TO_FILE 1         /* 1 == output to file, 0 == no output */
358
359#define VIDEO_ID_DRAWSPROCKET 1          /* these correspond to popup menu choices */
360#define VIDEO_ID_TOOLBOX      2
361
362    PrefsRecord prefs = { DEFAULT_ARGS, DEFAULT_VIDEO_DRIVER, DEFAULT_OUTPUT_TO_FILE };
363
364#if !(defined(__APPLE__) && defined(__MACH__))
365	int     nargs;
366	char   **args;
367	char   *commandLine;
368
369	StrFileName  appNameText;
370#endif
371	int     videodriver     = VIDEO_ID_TOOLBOX;
372    int     settingsChanged = 0;
373
374    long	i;
375
376	/* Kyle's SDL command-line dialog code ... */
377#if !TARGET_API_MAC_CARBON
378	InitGraf    (&qd.thePort);
379	InitFonts   ();
380	InitWindows ();
381	InitMenus   ();
382	InitDialogs (nil);
383#endif
384	InitCursor ();
385	FlushEvents(everyEvent,0);
386#if !TARGET_API_MAC_CARBON
387	MaxApplZone ();
388#endif
389	MoreMasters ();
390	MoreMasters ();
391#if 0
392	/* Intialize SDL, and put up a dialog if we fail */
393	if ( SDL_Init (0) < 0 ) {
394
395#define kErr_OK		1
396#define kErr_Text	2
397
398        DialogPtr errorDialog;
399        short	  dummyType;
400    	Rect	  dummyRect;
401	    Handle    dummyHandle;
402	    short     itemHit;
403
404		errorDialog = GetNewDialog (1001, nil, (WindowPtr)-1);
405		if (errorDialog == NULL)
406		    return -1;
407		DrawDialog (errorDialog);
408
409		GetDialogItem (errorDialog, kErr_Text, &dummyType, &dummyHandle, &dummyRect);
410		SetDialogItemText (dummyHandle, "\pError Initializing SDL");
411
412#if TARGET_API_MAC_CARBON
413		SetPort (GetDialogPort(errorDialog));
414#else
415		SetPort (errorDialog);
416#endif
417		do {
418			ModalDialog (nil, &itemHit);
419		} while (itemHit != kErr_OK);
420
421		DisposeDialog (errorDialog);
422		exit (-1);
423	}
424	atexit(cleanup_output);
425	atexit(SDL_Quit);
426#endif
427
428/* Set up SDL's QuickDraw environment  */
429#if !TARGET_API_MAC_CARBON
430	SDL_InitQuickDraw(&qd);
431#endif
432
433	 if ( readPreferences (&prefs) ) {
434
435        if (SDL_memcmp(prefs.video_driver_name+1, "DSp", 3) == 0)
436            videodriver = 1;
437        else if (SDL_memcmp(prefs.video_driver_name+1, "toolbox", 7) == 0)
438            videodriver = 2;
439	 }
440
441	if ( CommandKeyIsDown() ) {
442
443#define kCL_OK		1
444#define kCL_Cancel	2
445#define kCL_Text	3
446#define kCL_File	4
447#define kCL_Video   6
448
449        DialogPtr commandDialog;
450        short	  dummyType;
451        Rect	  dummyRect;
452        Handle    dummyHandle;
453        short     itemHit;
454   #if TARGET_API_MAC_CARBON
455        ControlRef control;
456   #endif
457
458        /* Assume that they will change settings, rather than do exhaustive check */
459        settingsChanged = 1;
460
461        /* Create dialog and display it */
462        commandDialog = GetNewDialog (1000, nil, (WindowPtr)-1);
463    #if TARGET_API_MAC_CARBON
464        SetPort ( GetDialogPort(commandDialog) );
465    #else
466        SetPort (commandDialog);
467     #endif
468
469        /* Setup controls */
470    #if TARGET_API_MAC_CARBON
471        GetDialogItemAsControl(commandDialog, kCL_File, &control);
472        SetControlValue (control, prefs.output_to_file);
473    #else
474        GetDialogItem   (commandDialog, kCL_File, &dummyType, &dummyHandle, &dummyRect); /* MJS */
475        SetControlValue ((ControlHandle)dummyHandle, prefs.output_to_file );
476    #endif
477
478        GetDialogItem     (commandDialog, kCL_Text, &dummyType, &dummyHandle, &dummyRect);
479        SetDialogItemText (dummyHandle, prefs.command_line);
480
481    #if TARGET_API_MAC_CARBON
482        GetDialogItemAsControl(commandDialog, kCL_Video, &control);
483        SetControlValue (control, videodriver);
484   #else
485        GetDialogItem   (commandDialog, kCL_Video, &dummyType, &dummyHandle, &dummyRect);
486        SetControlValue ((ControlRef)dummyHandle, videodriver);
487     #endif
488
489        SetDialogDefaultItem (commandDialog, kCL_OK);
490        SetDialogCancelItem  (commandDialog, kCL_Cancel);
491
492        do {
493
494        	ModalDialog(nil, &itemHit); /* wait for user response */
495
496            /* Toggle command-line output checkbox */
497        	if ( itemHit == kCL_File ) {
498        #if TARGET_API_MAC_CARBON
499        		GetDialogItemAsControl(commandDialog, kCL_File, &control);
500        		SetControlValue (control, !GetControlValue(control));
501        #else
502        		GetDialogItem(commandDialog, kCL_File, &dummyType, &dummyHandle, &dummyRect); /* MJS */
503        		SetControlValue((ControlHandle)dummyHandle, !GetControlValue((ControlHandle)dummyHandle) );
504        #endif
505        	}
506
507        } while (itemHit != kCL_OK && itemHit != kCL_Cancel);
508
509        /* Get control values, even if they did not change */
510        GetDialogItem     (commandDialog, kCL_Text, &dummyType, &dummyHandle, &dummyRect); /* MJS */
511        GetDialogItemText (dummyHandle, prefs.command_line);
512
513    #if TARGET_API_MAC_CARBON
514        GetDialogItemAsControl(commandDialog, kCL_File, &control);
515        prefs.output_to_file = GetControlValue(control);
516	#else
517        GetDialogItem (commandDialog, kCL_File, &dummyType, &dummyHandle, &dummyRect); /* MJS */
518        prefs.output_to_file = GetControlValue ((ControlHandle)dummyHandle);
519 	#endif
520
521    #if TARGET_API_MAC_CARBON
522        GetDialogItemAsControl(commandDialog, kCL_Video, &control);
523        videodriver = GetControlValue(control);
524    #else
525        GetDialogItem (commandDialog, kCL_Video, &dummyType, &dummyHandle, &dummyRect);
526        videodriver = GetControlValue ((ControlRef)dummyHandle);
527     #endif
528
529        DisposeDialog (commandDialog);
530
531        if (itemHit == kCL_Cancel ) {
532        	exit (0);
533        }
534	}
535
536    /* Set pseudo-environment variables for video driver, update prefs */
537	switch ( videodriver ) {
538	   case VIDEO_ID_DRAWSPROCKET:
539	      SDL_putenv("SDL_VIDEODRIVER=DSp");
540	      SDL_memcpy(prefs.video_driver_name, "\pDSp", 4);
541	      break;
542	   case VIDEO_ID_TOOLBOX:
543	      SDL_putenv("SDL_VIDEODRIVER=toolbox");
544	      SDL_memcpy(prefs.video_driver_name, "\ptoolbox", 8);
545	      break;
546	}
547
548#if !(defined(__APPLE__) && defined(__MACH__))
549    /* Redirect standard I/O to files */
550	if ( prefs.output_to_file ) {
551		freopen (STDOUT_FILE, "w", stdout);
552		freopen (STDERR_FILE, "w", stderr);
553	} else {
554		fclose (stdout);
555		fclose (stderr);
556	}
557#endif
558
559    if (settingsChanged) {
560        /* Save the prefs, even if they might not have changed (but probably did) */
561        if ( ! writePreferences (&prefs) )
562            fprintf (stderr, "WARNING: Could not save preferences!\n");
563    }
564
565#if !(defined(__APPLE__) && defined(__MACH__))
566    appNameText[0] = 0;
567    getCurrentAppName (appNameText); /* check for error here ? */
568
569    commandLine = (char*) malloc (appNameText[0] + prefs.command_line[0] + 2);
570    if ( commandLine == NULL ) {
571       exit(-1);
572    }
573
574    /* Rather than rewrite ParseCommandLine method, let's replace  */
575    /* any spaces in application name with underscores,            */
576    /* so that the app name is only 1 argument                     */
577    for (i = 1; i < 1+appNameText[0]; i++)
578        if ( appNameText[i] == ' ' ) appNameText[i] = '_';
579
580    /* Copy app name & full command text to command-line C-string */
581    SDL_memcpy(commandLine, appNameText + 1, appNameText[0]);
582    commandLine[appNameText[0]] = ' ';
583    SDL_memcpy(commandLine + appNameText[0] + 1, prefs.command_line + 1, prefs.command_line[0]);
584    commandLine[ appNameText[0] + 1 + prefs.command_line[0] ] = '\0';
585
586    /* Parse C-string into argv and argc */
587    nargs = ParseCommandLine (commandLine, NULL);
588    args = (char **)malloc((nargs+1)*(sizeof *args));
589    if ( args == NULL ) {
590		exit(-1);
591	}
592	ParseCommandLine (commandLine, args);
593
594	/* Run the main application code */
595	SDL_main(nargs, args);
596	free (args);
597	free (commandLine);
598
599   	/* Remove useless stdout.txt and stderr.txt */
600   	cleanup_output ();
601#else // defined(__APPLE__) && defined(__MACH__)
602	SDL_main(argc, argv);
603#endif
604
605	/* Exit cleanly, calling atexit() functions */
606	exit (0);
607
608	/* Never reached, but keeps the compiler quiet */
609	return (0);
610}
611