1/*************************************************************************/
2/*  app_delegate.mm                                                      */
3/*************************************************************************/
4/*                       This file is part of:                           */
5/*                           GODOT ENGINE                                */
6/*                      https://godotengine.org                          */
7/*************************************************************************/
8/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
10/*                                                                       */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the       */
13/* "Software"), to deal in the Software without restriction, including   */
14/* without limitation the rights to use, copy, modify, merge, publish,   */
15/* distribute, sublicense, and/or sell copies of the Software, and to    */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions:                                             */
18/*                                                                       */
19/* The above copyright notice and this permission notice shall be        */
20/* included in all copies or substantial portions of the Software.       */
21/*                                                                       */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29/*************************************************************************/
30#import "app_delegate.h"
31
32#include "audio_driver_iphone.h"
33#include "core/globals.h"
34#import "gl_view.h"
35#include "main/main.h"
36#include "os_iphone.h"
37
38#import "GameController/GameController.h"
39
40#define kFilteringFactor 0.1
41#define kRenderingFrequency 60
42#define kAccelerometerFrequency 100.0 // Hz
43
44Error _shell_open(String);
45void _set_keep_screen_on(bool p_enabled);
46
47Error _shell_open(String p_uri) {
48	NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
49
50	if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]])
51		return ERR_CANT_OPEN;
52
53	printf("opening url %ls\n", p_uri.c_str());
54	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
55	[url release];
56	return OK;
57};
58
59void _set_keep_screen_on(bool p_enabled) {
60	[[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
61};
62
63@implementation AppDelegate
64
65@synthesize window;
66
67extern int gargc;
68extern char **gargv;
69extern int iphone_main(int, int, int, char **);
70extern void iphone_finish();
71
72CMMotionManager *motionManager;
73bool motionInitialised;
74
75static ViewController *mainViewController = nil;
76+ (ViewController *)getViewController {
77	return mainViewController;
78}
79
80NSMutableDictionary *ios_joysticks = nil;
81
82- (GCControllerPlayerIndex)getFreePlayerIndex {
83	bool have_player_1 = false;
84	bool have_player_2 = false;
85	bool have_player_3 = false;
86	bool have_player_4 = false;
87
88	if (ios_joysticks == nil) {
89		NSArray *keys = [ios_joysticks allKeys];
90		for (NSNumber *key in keys) {
91			GCController *controller = [ios_joysticks objectForKey:key];
92			if (controller.playerIndex == GCControllerPlayerIndex1) {
93				have_player_1 = true;
94			} else if (controller.playerIndex == GCControllerPlayerIndex2) {
95				have_player_2 = true;
96			} else if (controller.playerIndex == GCControllerPlayerIndex3) {
97				have_player_3 = true;
98			} else if (controller.playerIndex == GCControllerPlayerIndex4) {
99				have_player_4 = true;
100			};
101		};
102	};
103
104	if (!have_player_1) {
105		return GCControllerPlayerIndex1;
106	} else if (!have_player_2) {
107		return GCControllerPlayerIndex2;
108	} else if (!have_player_3) {
109		return GCControllerPlayerIndex3;
110	} else if (!have_player_4) {
111		return GCControllerPlayerIndex4;
112	} else {
113		return GCControllerPlayerIndexUnset;
114	};
115};
116
117- (void)controllerWasConnected:(NSNotification *)notification {
118	// create our dictionary if we don't have one yet
119	if (ios_joysticks == nil) {
120		ios_joysticks = [[NSMutableDictionary alloc] init];
121	};
122
123	// get our controller
124	GCController *controller = (GCController *)notification.object;
125	if (controller == nil) {
126		printf("Couldn't retrieve new controller\n");
127	} else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
128		printf("Controller is already registered\n");
129	} else {
130		// get a new id for our controller
131		int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
132		if (joy_id != -1) {
133			// assign our player index
134			if (controller.playerIndex == GCControllerPlayerIndexUnset) {
135				controller.playerIndex = [self getFreePlayerIndex];
136			};
137
138			// tell Godot about our new controller
139			OSIPhone::get_singleton()->joy_connection_changed(
140					joy_id, true, [controller.vendorName UTF8String]);
141
142			// add it to our dictionary, this will retain our controllers
143			[ios_joysticks setObject:controller
144							  forKey:[NSNumber numberWithInt:joy_id]];
145
146			// set our input handler
147			[self setControllerInputHandler:controller];
148		} else {
149			printf("Couldn't retrieve new joy id\n");
150		};
151	};
152};
153
154- (void)controllerWasDisconnected:(NSNotification *)notification {
155	if (ios_joysticks != nil) {
156		// find our joystick, there should be only one in our dictionary
157		GCController *controller = (GCController *)notification.object;
158		NSArray *keys = [ios_joysticks allKeysForObject:controller];
159		for (NSNumber *key in keys) {
160			// tell Godot this joystick is no longer there
161			int joy_id = [key intValue];
162			OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
163
164			// and remove it from our dictionary
165			[ios_joysticks removeObjectForKey:key];
166		};
167	};
168};
169
170- (int)getJoyIdForController:(GCController *)controller {
171	if (ios_joysticks != nil) {
172		// find our joystick, there should be only one in our dictionary
173		NSArray *keys = [ios_joysticks allKeysForObject:controller];
174		for (NSNumber *key in keys) {
175			int joy_id = [key intValue];
176			return joy_id;
177		};
178	};
179
180	return -1;
181};
182
183- (void)setControllerInputHandler:(GCController *)controller {
184	// Hook in the callback handler for the correct gamepad profile.
185	// This is a bit of a weird design choice on Apples part.
186	// You need to select the most capable gamepad profile for the
187	// gamepad attached.
188	if (controller.extendedGamepad != nil) {
189		// The extended gamepad profile has all the input you could possibly find on
190		// a gamepad but will only be active if your gamepad actually has all of
191		// these...
192		controller.extendedGamepad.valueChangedHandler = ^(
193				GCExtendedGamepad *gamepad, GCControllerElement *element) {
194			int joy_id = [self getJoyIdForController:controller];
195
196			if (element == gamepad.buttonA) {
197				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
198						gamepad.buttonA.isPressed);
199			} else if (element == gamepad.buttonB) {
200				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
201						gamepad.buttonB.isPressed);
202			} else if (element == gamepad.buttonX) {
203				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
204						gamepad.buttonX.isPressed);
205			} else if (element == gamepad.buttonY) {
206				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
207						gamepad.buttonY.isPressed);
208			} else if (element == gamepad.leftShoulder) {
209				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
210						gamepad.leftShoulder.isPressed);
211			} else if (element == gamepad.rightShoulder) {
212				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
213						gamepad.rightShoulder.isPressed);
214			} else if (element == gamepad.leftTrigger) {
215				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2,
216						gamepad.leftTrigger.isPressed);
217			} else if (element == gamepad.rightTrigger) {
218				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2,
219						gamepad.rightTrigger.isPressed);
220			} else if (element == gamepad.dpad) {
221				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
222						gamepad.dpad.up.isPressed);
223				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
224						gamepad.dpad.down.isPressed);
225				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
226						gamepad.dpad.left.isPressed);
227				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
228						gamepad.dpad.right.isPressed);
229			};
230
231			InputDefault::JoyAxis jx;
232			jx.min = -1;
233			if (element == gamepad.leftThumbstick) {
234				jx.value = gamepad.leftThumbstick.xAxis.value;
235				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_0_X, jx);
236				jx.value = -gamepad.leftThumbstick.yAxis.value;
237				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_0_Y, jx);
238			} else if (element == gamepad.rightThumbstick) {
239				jx.value = gamepad.rightThumbstick.xAxis.value;
240				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_1_X, jx);
241				jx.value = -gamepad.rightThumbstick.yAxis.value;
242				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_1_Y, jx);
243			} else if (element == gamepad.leftTrigger) {
244				jx.value = gamepad.leftTrigger.value;
245				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx);
246			} else if (element == gamepad.rightTrigger) {
247				jx.value = gamepad.rightTrigger.value;
248				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx);
249			};
250		};
251	} else if (controller.gamepad != nil) {
252		// gamepad is the standard profile with 4 buttons, shoulder buttons and a
253		// D-pad
254		controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
255				GCControllerElement *element) {
256			int joy_id = [self getJoyIdForController:controller];
257
258			if (element == gamepad.buttonA) {
259				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
260						gamepad.buttonA.isPressed);
261			} else if (element == gamepad.buttonB) {
262				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
263						gamepad.buttonB.isPressed);
264			} else if (element == gamepad.buttonX) {
265				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
266						gamepad.buttonX.isPressed);
267			} else if (element == gamepad.buttonY) {
268				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
269						gamepad.buttonY.isPressed);
270			} else if (element == gamepad.leftShoulder) {
271				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
272						gamepad.leftShoulder.isPressed);
273			} else if (element == gamepad.rightShoulder) {
274				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
275						gamepad.rightShoulder.isPressed);
276			} else if (element == gamepad.dpad) {
277				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
278						gamepad.dpad.up.isPressed);
279				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
280						gamepad.dpad.down.isPressed);
281				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
282						gamepad.dpad.left.isPressed);
283				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
284						gamepad.dpad.right.isPressed);
285			};
286		};
287#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+ and we're still compiling for iOS 7+
288	} else if (controller.microGamepad != nil) {
289		// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
290		controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
291			int joy_id = [self getJoyIdForController:controller];
292
293			if (element == gamepad.buttonA) {
294				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
295						gamepad.buttonA.isPressed);
296			} else if (element == gamepad.buttonX) {
297				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
298						gamepad.buttonX.isPressed);
299			} else if (element == gamepad.dpad) {
300				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
301						gamepad.dpad.up.isPressed);
302				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
303						gamepad.dpad.down.isPressed);
304				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
305						gamepad.dpad.left.isPressed);
306				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
307						gamepad.dpad.right.isPressed);
308			};
309		};
310#endif
311	};
312
313	///@TODO need to add support for controller.motion which gives us access to
314	/// the orientation of the device (if supported)
315
316	///@TODO need to add support for controllerPausedHandler which should be a
317	/// toggle
318};
319
320- (void)initGameControllers {
321	// get told when controllers connect, this will be called right away for
322	// already connected controllers
323	[[NSNotificationCenter defaultCenter]
324			addObserver:self
325			   selector:@selector(controllerWasConnected:)
326				   name:GCControllerDidConnectNotification
327				 object:nil];
328
329	// get told when controllers disconnect
330	[[NSNotificationCenter defaultCenter]
331			addObserver:self
332			   selector:@selector(controllerWasDisconnected:)
333				   name:GCControllerDidDisconnectNotification
334				 object:nil];
335};
336
337- (void)deinitGameControllers {
338	[[NSNotificationCenter defaultCenter]
339			removeObserver:self
340					  name:GCControllerDidConnectNotification
341					object:nil];
342	[[NSNotificationCenter defaultCenter]
343			removeObserver:self
344					  name:GCControllerDidDisconnectNotification
345					object:nil];
346
347	if (ios_joysticks != nil) {
348		[ios_joysticks dealloc];
349		ios_joysticks = nil;
350	};
351};
352
353static int frame_count = 0;
354- (void)drawView:(GLView *)view;
355{
356
357	switch (frame_count) {
358		case 0: {
359			int backingWidth;
360			int backingHeight;
361			glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
362					GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
363			glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
364					GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
365
366			OS::VideoMode vm;
367			vm.fullscreen = true;
368			vm.width = backingWidth;
369			vm.height = backingHeight;
370			vm.resizable = false;
371			OS::get_singleton()->set_video_mode(vm);
372
373			if (!OS::get_singleton()) {
374				exit(0);
375			};
376			++frame_count;
377
378			NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
379					NSUserDomainMask, YES);
380			NSString *documentsDirectory = [paths objectAtIndex:0];
381			// NSString *documentsDirectory = [[[NSFileManager defaultManager]
382			// URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]
383			// lastObject];
384			OSIPhone::get_singleton()->set_data_dir(
385					String::utf8([documentsDirectory UTF8String]));
386
387			NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
388			OSIPhone::get_singleton()->set_locale(
389					String::utf8([locale_code UTF8String]));
390
391			NSString *uuid;
392			if ([[UIDevice currentDevice]
393						respondsToSelector:@selector(identifierForVendor)]) {
394				uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
395			} else {
396
397				// before iOS 6, so just generate an identifier and store it
398				uuid = [[NSUserDefaults standardUserDefaults]
399						objectForKey:@"identiferForVendor"];
400				if (!uuid) {
401					CFUUIDRef cfuuid = CFUUIDCreate(NULL);
402					uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
403					CFRelease(cfuuid);
404					[[NSUserDefaults standardUserDefaults]
405							setObject:uuid
406							   forKey:@"identifierForVendor"];
407				}
408			}
409
410			OSIPhone::get_singleton()->set_unique_ID(String::utf8([uuid UTF8String]));
411
412		}; break;
413
414		case 1: {
415
416			Main::setup2();
417			++frame_count;
418
419			// this might be necessary before here
420			NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
421			for (NSString *key in dict) {
422				NSObject *value = [dict objectForKey:key];
423				String ukey = String::utf8([key UTF8String]);
424
425				// we need a NSObject to Variant conversor
426
427				if ([value isKindOfClass:[NSString class]]) {
428					NSString *str = (NSString *)value;
429					String uval = String::utf8([str UTF8String]);
430
431					Globals::get_singleton()->set("Info.plist/" + ukey, uval);
432
433				} else if ([value isKindOfClass:[NSNumber class]]) {
434
435					NSNumber *n = (NSNumber *)value;
436					double dval = [n doubleValue];
437
438					Globals::get_singleton()->set("Info.plist/" + ukey, dval);
439				};
440				// do stuff
441			}
442
443		}; break;
444
445		case 2: {
446
447			Main::start();
448			++frame_count;
449
450		}; break; // no fallthrough
451
452		default: {
453			if (OSIPhone::get_singleton()) {
454				if (motionInitialised) {
455					// Just using polling approach for now, we can set this up so it sends
456					// data to us in intervals, might be better. See Apple reference pages
457					// for more details:
458					// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
459
460					// Apple splits our accelerometer date into a gravity and user movement
461					// component. We add them back together
462					CMAcceleration gravity = motionManager.deviceMotion.gravity;
463					CMAcceleration acceleration =
464							motionManager.deviceMotion.userAcceleration;
465
466					///@TODO We don't seem to be getting data here, is my device broken or
467					/// is this code incorrect?
468					CMMagneticField magnetic =
469							motionManager.deviceMotion.magneticField.field;
470
471					///@TODO we can access rotationRate as a CMRotationRate variable
472					///(processed date) or CMGyroData (raw data), have to see what works
473					/// best
474					CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
475
476					// Adjust for screen orientation.
477					// [[UIDevice currentDevice] orientation] changes even if we've fixed
478					// our orientation which is not a good thing when you're trying to get
479					// your user to move the screen in all directions and want consistent
480					// output
481
482					///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
483					/// is a bit of a hack. Godot obviously knows the orientation so maybe
484					/// we
485					// can use that instead? (note that left and right seem swapped)
486
487					switch ([[UIApplication sharedApplication] statusBarOrientation]) {
488						case UIDeviceOrientationLandscapeLeft: {
489							OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
490									gravity.z);
491							OSIPhone::get_singleton()->update_accelerometer(
492									-(acceleration.y + gravity.y), (acceleration.x + gravity.x),
493									acceleration.z + gravity.z);
494							OSIPhone::get_singleton()->update_magnetometer(
495									-magnetic.y, magnetic.x, magnetic.z);
496							OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
497									rotation.z);
498						}; break;
499						case UIDeviceOrientationLandscapeRight: {
500							OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
501									gravity.z);
502							OSIPhone::get_singleton()->update_accelerometer(
503									(acceleration.y + gravity.y), -(acceleration.x + gravity.x),
504									acceleration.z + gravity.z);
505							OSIPhone::get_singleton()->update_magnetometer(
506									magnetic.y, -magnetic.x, magnetic.z);
507							OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
508									rotation.z);
509						}; break;
510						case UIDeviceOrientationPortraitUpsideDown: {
511							OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
512									gravity.z);
513							OSIPhone::get_singleton()->update_accelerometer(
514									-(acceleration.x + gravity.x), (acceleration.y + gravity.y),
515									acceleration.z + gravity.z);
516							OSIPhone::get_singleton()->update_magnetometer(
517									-magnetic.x, magnetic.y, magnetic.z);
518							OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
519									rotation.z);
520						}; break;
521						default: { // assume portrait
522							OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
523									gravity.z);
524							OSIPhone::get_singleton()->update_accelerometer(
525									acceleration.x + gravity.x, acceleration.y + gravity.y,
526									acceleration.z + gravity.z);
527							OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
528									magnetic.z);
529							OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
530									rotation.z);
531						}; break;
532					};
533				}
534
535				bool quit_request = OSIPhone::get_singleton()->iterate();
536			};
537		};
538	};
539};
540
541- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
542	OS::get_singleton()->get_main_loop()->notification(
543			MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
544};
545
546- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
547	CGRect rect = [[UIScreen mainScreen] bounds];
548
549	[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
550	// disable idle timer
551	// application.idleTimerDisabled = YES;
552
553	// Create a full-screen window
554	window = [[UIWindow alloc] initWithFrame:rect];
555	// window.autoresizesSubviews = YES;
556	//[window setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
557	// UIViewAutoresizingFlexibleWidth];
558
559	// Create the OpenGL ES view and add it to the window
560	GLView *glView = [[GLView alloc] initWithFrame:rect];
561	printf("glview is %p\n", glView);
562	//[window addSubview:glView];
563	glView.delegate = self;
564	// glView.autoresizesSubviews = YES;
565	//[glView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
566	// UIViewAutoresizingFlexibleWidth];
567
568	int backingWidth;
569	int backingHeight;
570	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
571			GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
572	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
573			GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
574
575	iphone_main(backingWidth, backingHeight, gargc, gargv);
576
577	view_controller = [[ViewController alloc] init];
578	view_controller.view = glView;
579	window.rootViewController = view_controller;
580
581	_set_keep_screen_on(bool(GLOBAL_DEF("display/keep_screen_on", true)) ? YES : NO);
582	glView.useCADisplayLink =
583			bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
584	printf("cadisaplylink: %d", glView.useCADisplayLink);
585	glView.animationInterval = 1.0 / kRenderingFrequency;
586	[glView startAnimation];
587
588	// Show the window
589	[window makeKeyAndVisible];
590
591	if (!motionInitialised) {
592		motionManager = [[CMMotionManager alloc] init];
593		if (motionManager.deviceMotionAvailable) {
594			motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
595			[motionManager startDeviceMotionUpdatesUsingReferenceFrame:
596								   CMAttitudeReferenceFrameXMagneticNorthZVertical];
597			motionInitialised = YES;
598		};
599	};
600
601	[self initGameControllers];
602
603	// OSIPhone::screen_width = rect.size.width - rect.origin.x;
604	// OSIPhone::screen_height = rect.size.height - rect.origin.y;
605
606	mainViewController = view_controller;
607
608	// prevent to stop music in another background app
609	[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
610
611	return TRUE;
612};
613
614- (void)applicationWillTerminate:(UIApplication *)application {
615	[self deinitGameControllers];
616
617	if (motionInitialised) {
618		///@TODO is this the right place to clean this up?
619		[motionManager stopDeviceMotionUpdates];
620		[motionManager release];
621		motionManager = nil;
622		motionInitialised = NO;
623	};
624
625	iphone_finish();
626};
627
628- (void)applicationDidEnterBackground:(UIApplication *)application {
629	///@TODO maybe add pause motionManager? and where would we unpause it?
630
631	if (OS::get_singleton()->get_main_loop())
632		OS::get_singleton()->get_main_loop()->notification(
633				MainLoop::NOTIFICATION_WM_FOCUS_OUT);
634
635	[view_controller.view stopAnimation];
636
637	if (OS::get_singleton()->native_video_is_playing()) {
638		OSIPhone::get_singleton()->native_video_focus_out();
639	};
640}
641
642- (void)applicationWillEnterForeground:(UIApplication *)application {
643	// OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
644	[view_controller.view startAnimation];
645}
646
647- (void)applicationWillResignActive:(UIApplication *)application {
648	// OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
649	[view_controller.view
650					stopAnimation]; // FIXME: pause seems to be recommended elsewhere
651}
652
653- (void)applicationDidBecomeActive:(UIApplication *)application {
654	if (OS::get_singleton()->get_main_loop())
655		OS::get_singleton()->get_main_loop()->notification(
656				MainLoop::NOTIFICATION_WM_FOCUS_IN);
657
658	[view_controller.view
659					startAnimation]; // FIXME: resume seems to be recommended elsewhere
660
661	if (OSIPhone::get_singleton()->native_video_is_playing()) {
662		OSIPhone::get_singleton()->native_video_unpause();
663	};
664
665	// Fixed audio can not resume if it is interrupted cause by an incoming phone call
666	if (AudioDriverIphone::get_singleton() != NULL)
667		AudioDriverIphone::get_singleton()->start();
668}
669
670- (void)dealloc {
671	[window release];
672	[super dealloc];
673}
674
675@end
676