1/*
2 ** i_main.mm
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2012-2015 Alexey Lysiuk
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 **    notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 **    notice, this list of conditions and the following disclaimer in the
16 **    documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 **    derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 */
33
34#include "i_common.h"
35
36#include <sys/sysctl.h>
37#include <unistd.h>
38
39// Avoid collision between DObject class and Objective-C
40#define Class ObjectClass
41
42#include "c_console.h"
43#include "c_cvars.h"
44#include "cmdlib.h"
45#include "d_main.h"
46#include "doomerrors.h"
47#include "i_system.h"
48#include "m_argv.h"
49#include "s_sound.h"
50#include "st_console.h"
51#include "version.h"
52
53#undef Class
54
55
56#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE))
57
58
59// ---------------------------------------------------------------------------
60
61
62EXTERN_CVAR(Int,  vid_defwidth )
63EXTERN_CVAR(Int,  vid_defheight)
64EXTERN_CVAR(Bool, vid_vsync    )
65EXTERN_CVAR(Bool, fullscreen   )
66
67
68// ---------------------------------------------------------------------------
69
70
71namespace
72{
73
74// The maximum number of functions that can be registered with atterm.
75const size_t MAX_TERMS = 64;
76
77void      (*TermFuncs[MAX_TERMS])();
78const char *TermNames[MAX_TERMS];
79size_t      NumTerms;
80
81void call_terms()
82{
83	while (NumTerms > 0)
84	{
85		TermFuncs[--NumTerms]();
86	}
87}
88
89} // unnamed namespace
90
91
92void addterm(void (*func)(), const char *name)
93{
94	// Make sure this function wasn't already registered.
95
96	for (size_t i = 0; i < NumTerms; ++i)
97	{
98		if (TermFuncs[i] == func)
99		{
100			return;
101		}
102	}
103
104	if (NumTerms == MAX_TERMS)
105	{
106		func();
107		I_FatalError("Too many exit functions registered.");
108	}
109
110	TermNames[NumTerms] = name;
111	TermFuncs[NumTerms] = func;
112
113	++NumTerms;
114}
115
116void popterm()
117{
118	if (NumTerms)
119	{
120		--NumTerms;
121	}
122}
123
124
125void Mac_I_FatalError(const char* const message)
126{
127	I_SetMainWindowVisible(false);
128
129	FConsoleWindow::GetInstance().ShowFatalError(message);
130}
131
132
133DArgs* Args; // command line arguments
134
135
136namespace
137{
138
139const int ARGC_MAX = 64;
140
141int   s_argc;
142char* s_argv[ARGC_MAX];
143
144TArray<FString> s_argvStorage;
145
146bool s_restartedFromWADPicker;
147
148
149void NewFailure()
150{
151	I_FatalError("Failed to allocate memory from system heap");
152}
153
154
155int OriginalMain(int argc, char** argv)
156{
157	printf(GAMENAME" %s - %s - Cocoa version\nCompiled on %s\n\n",
158		GetVersionString(), GetGitTime(), __DATE__);
159
160	seteuid(getuid());
161	std::set_new_handler(NewFailure);
162
163	// Set LC_NUMERIC environment variable in case some library decides to
164	// clear the setlocale call at least this will be correct.
165	// Note that the LANG environment variable is overridden by LC_*
166	setenv("LC_NUMERIC", "C", 1);
167	setlocale(LC_ALL, "C");
168
169	// Set reasonable default values for video settings
170
171	const NSSize screenSize = [[NSScreen mainScreen] frame].size;
172	vid_defwidth  = static_cast<int>(screenSize.width);
173	vid_defheight = static_cast<int>(screenSize.height);
174	vid_vsync     = true;
175	fullscreen    = true;
176
177	try
178	{
179		Args = new DArgs(argc, argv);
180
181		/*
182		 killough 1/98:
183
184		 This fixes some problems with exit handling
185		 during abnormal situations.
186
187		 The old code called I_Quit() to end program,
188		 while now I_Quit() is installed as an exit
189		 handler and exit() is called to exit, either
190		 normally or abnormally. Seg faults are caught
191		 and the error handler is used, to prevent
192		 being left in graphics mode or having very
193		 loud SFX noise because the sound card is
194		 left in an unstable state.
195		 */
196
197		atexit(call_terms);
198		atterm(I_Quit);
199
200		NSString* exePath = [[NSBundle mainBundle] executablePath];
201		progdir = [[exePath stringByDeletingLastPathComponent] UTF8String];
202		progdir += "/";
203
204		C_InitConsole(80 * 8, 25 * 8, false);
205		D_DoomMain();
206	}
207	catch(const CDoomError& error)
208	{
209		const char* const message = error.GetMessage();
210
211		if (NULL != message)
212		{
213			fprintf(stderr, "%s\n", message);
214			Mac_I_FatalError(message);
215		}
216
217		exit(-1);
218	}
219	catch(...)
220	{
221		call_terms();
222		throw;
223	}
224
225	return 0;
226}
227
228} // unnamed namespace
229
230
231// ---------------------------------------------------------------------------
232
233
234@interface ApplicationController : NSResponder
235#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
236	<NSFileManagerDelegate>
237#endif
238{
239}
240
241- (void)keyDown:(NSEvent*)theEvent;
242- (void)keyUp:(NSEvent*)theEvent;
243
244- (void)applicationDidBecomeActive:(NSNotification*)aNotification;
245- (void)applicationWillResignActive:(NSNotification*)aNotification;
246
247- (void)applicationDidFinishLaunching:(NSNotification*)aNotification;
248
249- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename;
250
251- (void)processEvents:(NSTimer*)timer;
252
253@end
254
255
256ApplicationController* appCtrl;
257
258
259@implementation ApplicationController
260
261- (void)keyDown:(NSEvent*)theEvent
262{
263	// Empty but present to avoid playing of 'beep' alert sound
264
265	ZD_UNUSED(theEvent);
266}
267
268- (void)keyUp:(NSEvent*)theEvent
269{
270	// Empty but present to avoid playing of 'beep' alert sound
271
272	ZD_UNUSED(theEvent);
273}
274
275
276- (void)applicationDidBecomeActive:(NSNotification*)aNotification
277{
278	ZD_UNUSED(aNotification);
279
280	S_SetSoundPaused(1);
281}
282
283- (void)applicationWillResignActive:(NSNotification*)aNotification
284{
285	ZD_UNUSED(aNotification);
286
287	S_SetSoundPaused(0);
288}
289
290
291- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
292{
293	// When starting from command line with real executable path, e.g. ZDoom.app/Contents/MacOS/ZDoom
294	// application remains deactivated for an unknown reason.
295	// The following call resolves this issue
296	[NSApp activateIgnoringOtherApps:YES];
297
298	// Setup timer for custom event loop
299
300	NSTimer* timer = [NSTimer timerWithTimeInterval:0
301											 target:self
302										   selector:@selector(processEvents:)
303										   userInfo:nil
304											repeats:YES];
305	[[NSRunLoop currentRunLoop] addTimer:timer
306								 forMode:NSDefaultRunLoopMode];
307
308	FConsoleWindow::CreateInstance();
309	atterm(FConsoleWindow::DeleteInstance);
310
311	exit(OriginalMain(s_argc, s_argv));
312}
313
314
315- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename
316{
317	ZD_UNUSED(theApplication);
318
319	if (s_restartedFromWADPicker
320		|| 0 == [filename length]
321		|| s_argc + 2 >= ARGC_MAX)
322	{
323		return FALSE;
324	}
325
326	// Some parameters from command line are passed to this function
327	// These parameters need to be skipped to avoid duplication
328	// Note: SDL has different approach to fix this issue, see the same method in SDLMain.m
329
330	const char* const charFileName = [filename UTF8String];
331
332	for (int i = 0; i < s_argc; ++i)
333	{
334		if (0 == strcmp(s_argv[i], charFileName))
335		{
336			return FALSE;
337		}
338	}
339
340	s_argvStorage.Push("-file");
341	s_argv[s_argc++] = s_argvStorage.Last().LockBuffer();
342
343	s_argvStorage.Push([filename UTF8String]);
344	s_argv[s_argc++] = s_argvStorage.Last().LockBuffer();
345
346	return TRUE;
347}
348
349
350- (void)processEvents:(NSTimer*)timer
351{
352	ZD_UNUSED(timer);
353
354	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
355
356    while (true)
357    {
358        NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
359											untilDate:[NSDate dateWithTimeIntervalSinceNow:0]
360											   inMode:NSDefaultRunLoopMode
361											  dequeue:YES];
362        if (nil == event)
363        {
364            break;
365        }
366
367		I_ProcessEvent(event);
368
369		[NSApp sendEvent:event];
370	}
371
372    [NSApp updateWindows];
373
374	[pool release];
375}
376
377@end
378
379
380// ---------------------------------------------------------------------------
381
382
383namespace
384{
385
386NSMenuItem* CreateApplicationMenu()
387{
388	NSMenu* menu = [NSMenu new];
389
390	[menu addItemWithTitle:[@"About " stringByAppendingString:@GAMENAME]
391					   action:@selector(orderFrontStandardAboutPanel:)
392				keyEquivalent:@""];
393	[menu addItem:[NSMenuItem separatorItem]];
394	[menu addItemWithTitle:[@"Hide " stringByAppendingString:@GAMENAME]
395					   action:@selector(hide:)
396				keyEquivalent:@"h"];
397	[[menu addItemWithTitle:@"Hide Others"
398						action:@selector(hideOtherApplications:)
399				 keyEquivalent:@"h"]
400	 setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask];
401	[menu addItemWithTitle:@"Show All"
402					   action:@selector(unhideAllApplications:)
403				keyEquivalent:@""];
404	[menu addItem:[NSMenuItem separatorItem]];
405	[menu addItemWithTitle:[@"Quit " stringByAppendingString:@GAMENAME]
406					   action:@selector(terminate:)
407				keyEquivalent:@"q"];
408
409	NSMenuItem* menuItem = [NSMenuItem new];
410	[menuItem setSubmenu:menu];
411
412	if ([NSApp respondsToSelector:@selector(setAppleMenu:)])
413	{
414		[NSApp performSelector:@selector(setAppleMenu:) withObject:menu];
415	}
416
417	return menuItem;
418}
419
420NSMenuItem* CreateEditMenu()
421{
422	NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Edit"];
423
424	[menu addItemWithTitle:@"Undo"
425						action:@selector(undo:)
426				 keyEquivalent:@"z"];
427	[menu addItemWithTitle:@"Redo"
428						action:@selector(redo:)
429				 keyEquivalent:@"Z"];
430	[menu addItem:[NSMenuItem separatorItem]];
431	[menu addItemWithTitle:@"Cut"
432						action:@selector(cut:)
433				 keyEquivalent:@"x"];
434	[menu addItemWithTitle:@"Copy"
435						action:@selector(copy:)
436				 keyEquivalent:@"c"];
437	[menu addItemWithTitle:@"Paste"
438						action:@selector(paste:)
439				 keyEquivalent:@"v"];
440	[menu addItemWithTitle:@"Delete"
441						action:@selector(delete:)
442				 keyEquivalent:@""];
443	[menu addItemWithTitle:@"Select All"
444						action:@selector(selectAll:)
445				 keyEquivalent:@"a"];
446
447	NSMenuItem* menuItem = [NSMenuItem new];
448	[menuItem setSubmenu:menu];
449
450	return menuItem;
451}
452
453NSMenuItem* CreateWindowMenu()
454{
455	NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Window"];
456	[NSApp setWindowsMenu:menu];
457
458	[menu addItemWithTitle:@"Minimize"
459					action:@selector(performMiniaturize:)
460			 keyEquivalent:@"m"];
461	[menu addItemWithTitle:@"Zoom"
462					action:@selector(performZoom:)
463			 keyEquivalent:@""];
464	[menu addItem:[NSMenuItem separatorItem]];
465	[menu addItemWithTitle:@"Bring All to Front"
466					action:@selector(arrangeInFront:)
467			 keyEquivalent:@""];
468
469	NSMenuItem* menuItem = [NSMenuItem new];
470	[menuItem setSubmenu:menu];
471
472	return menuItem;
473}
474
475void CreateMenu()
476{
477	NSMenu* menuBar = [NSMenu new];
478	[menuBar addItem:CreateApplicationMenu()];
479	[menuBar addItem:CreateEditMenu()];
480	[menuBar addItem:CreateWindowMenu()];
481
482	[NSApp setMainMenu:menuBar];
483}
484
485void ReleaseApplicationController()
486{
487	if (NULL != appCtrl)
488	{
489		[NSApp setDelegate:nil];
490		[NSApp deactivate];
491
492		[appCtrl release];
493		appCtrl = NULL;
494	}
495}
496
497} // unnamed namespace
498
499
500int main(int argc, char** argv)
501{
502	for (int i = 0; i <= argc; ++i)
503	{
504		const char* const argument = argv[i];
505
506		if (NULL == argument || '\0' == argument[0])
507		{
508			continue;
509		}
510
511		if (0 == strcmp(argument, "-wad_picker_restart"))
512		{
513			s_restartedFromWADPicker = true;
514		}
515		else
516		{
517			s_argvStorage.Push(argument);
518			s_argv[s_argc++] = s_argvStorage.Last().LockBuffer();
519		}
520	}
521
522	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
523
524	[NSApplication sharedApplication];
525
526	// The following code isn't mandatory,
527	// but it enables to run the application without a bundle
528	if ([NSApp respondsToSelector:@selector(setActivationPolicy:)])
529	{
530		[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
531	}
532
533	CreateMenu();
534
535	atterm(ReleaseApplicationController);
536
537	appCtrl = [ApplicationController new];
538	[NSApp setDelegate:appCtrl];
539	[NSApp run];
540
541	[pool release];
542
543	return EXIT_SUCCESS;
544}
545