1/*************************************************************************/
2/*  app_delegate.mm                                                      */
3/*************************************************************************/
4/*                       This file is part of:                           */
5/*                           GODOT ENGINE                                */
6/*                      https://godotengine.org                          */
7/*************************************************************************/
8/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9/* Copyright (c) 2014-2020 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
31#import "app_delegate.h"
32
33#include "core/project_settings.h"
34#include "drivers/coreaudio/audio_driver_coreaudio.h"
35#import "gl_view.h"
36#include "main/main.h"
37#include "os_iphone.h"
38
39#import "GameController/GameController.h"
40#import <AudioToolbox/AudioServices.h>
41
42#define kFilteringFactor 0.1
43#define kRenderingFrequency 60
44#define kAccelerometerFrequency 100.0 // Hz
45
46Error _shell_open(String);
47void _set_keep_screen_on(bool p_enabled);
48
49Error _shell_open(String p_uri) {
50	NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
51
52	if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]]) {
53		[url release];
54		return ERR_CANT_OPEN;
55	}
56
57	printf("opening url %ls\n", p_uri.c_str());
58	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
59	[url release];
60	return OK;
61};
62
63void _set_keep_screen_on(bool p_enabled) {
64	[[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
65};
66
67void _vibrate() {
68	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
69};
70
71@implementation AppDelegate
72
73@synthesize window;
74
75extern int gargc;
76extern char **gargv;
77extern int iphone_main(int, int, int, char **, String);
78extern void iphone_finish();
79
80CMMotionManager *motionManager;
81bool motionInitialised;
82
83static ViewController *mainViewController = nil;
84+ (ViewController *)getViewController {
85	return mainViewController;
86}
87
88NSMutableDictionary *ios_joysticks = nil;
89NSMutableArray *pending_ios_joysticks = nil;
90
91- (GCControllerPlayerIndex)getFreePlayerIndex {
92	bool have_player_1 = false;
93	bool have_player_2 = false;
94	bool have_player_3 = false;
95	bool have_player_4 = false;
96
97	if (ios_joysticks == nil) {
98		NSArray *keys = [ios_joysticks allKeys];
99		for (NSNumber *key in keys) {
100			GCController *controller = [ios_joysticks objectForKey:key];
101			if (controller.playerIndex == GCControllerPlayerIndex1) {
102				have_player_1 = true;
103			} else if (controller.playerIndex == GCControllerPlayerIndex2) {
104				have_player_2 = true;
105			} else if (controller.playerIndex == GCControllerPlayerIndex3) {
106				have_player_3 = true;
107			} else if (controller.playerIndex == GCControllerPlayerIndex4) {
108				have_player_4 = true;
109			};
110		};
111	};
112
113	if (!have_player_1) {
114		return GCControllerPlayerIndex1;
115	} else if (!have_player_2) {
116		return GCControllerPlayerIndex2;
117	} else if (!have_player_3) {
118		return GCControllerPlayerIndex3;
119	} else if (!have_player_4) {
120		return GCControllerPlayerIndex4;
121	} else {
122		return GCControllerPlayerIndexUnset;
123	};
124};
125
126void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
127	// get a new id for our controller
128	int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
129	if (joy_id != -1) {
130		// assign our player index
131		if (controller.playerIndex == GCControllerPlayerIndexUnset) {
132			controller.playerIndex = [delegate getFreePlayerIndex];
133		};
134
135		// tell Godot about our new controller
136		OSIPhone::get_singleton()->joy_connection_changed(
137				joy_id, true, [controller.vendorName UTF8String]);
138
139		// add it to our dictionary, this will retain our controllers
140		[ios_joysticks setObject:controller
141						  forKey:[NSNumber numberWithInt:joy_id]];
142
143		// set our input handler
144		[delegate setControllerInputHandler:controller];
145	} else {
146		printf("Couldn't retrieve new joy id\n");
147	};
148}
149
150static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
151	if (!*is_focus_out) {
152		*is_focus_out = true;
153		if (OS::get_singleton()->get_main_loop())
154			OS::get_singleton()->get_main_loop()->notification(
155					MainLoop::NOTIFICATION_WM_FOCUS_OUT);
156
157		[view_controller.view stopAnimation];
158		if (OS::get_singleton()->native_video_is_playing()) {
159			OSIPhone::get_singleton()->native_video_focus_out();
160		}
161
162		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
163		if (audio)
164			audio->stop();
165	}
166}
167
168static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
169	if (*is_focus_out) {
170		*is_focus_out = false;
171		if (OS::get_singleton()->get_main_loop())
172			OS::get_singleton()->get_main_loop()->notification(
173					MainLoop::NOTIFICATION_WM_FOCUS_IN);
174
175		[view_controller.view startAnimation];
176		if (OSIPhone::get_singleton()->native_video_is_playing()) {
177			OSIPhone::get_singleton()->native_video_unpause();
178		}
179
180		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
181		if (audio)
182			audio->start();
183	}
184}
185
186- (void)controllerWasConnected:(NSNotification *)notification {
187	// create our dictionary if we don't have one yet
188	if (ios_joysticks == nil) {
189		ios_joysticks = [[NSMutableDictionary alloc] init];
190	};
191
192	// get our controller
193	GCController *controller = (GCController *)notification.object;
194	if (controller == nil) {
195		printf("Couldn't retrieve new controller\n");
196	} else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
197		printf("Controller is already registered\n");
198	} else if (frame_count > 1) {
199		_ios_add_joystick(controller, self);
200	} else {
201		if (pending_ios_joysticks == nil)
202			pending_ios_joysticks = [[NSMutableArray alloc] init];
203		[pending_ios_joysticks addObject:controller];
204	};
205};
206
207- (void)controllerWasDisconnected:(NSNotification *)notification {
208	if (ios_joysticks != nil) {
209		// find our joystick, there should be only one in our dictionary
210		GCController *controller = (GCController *)notification.object;
211		NSArray *keys = [ios_joysticks allKeysForObject:controller];
212		for (NSNumber *key in keys) {
213			// tell Godot this joystick is no longer there
214			int joy_id = [key intValue];
215			OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
216
217			// and remove it from our dictionary
218			[ios_joysticks removeObjectForKey:key];
219		};
220	};
221};
222
223- (int)getJoyIdForController:(GCController *)controller {
224	if (ios_joysticks != nil) {
225		// find our joystick, there should be only one in our dictionary
226		NSArray *keys = [ios_joysticks allKeysForObject:controller];
227		for (NSNumber *key in keys) {
228			int joy_id = [key intValue];
229			return joy_id;
230		};
231	};
232
233	return -1;
234};
235
236- (void)setControllerInputHandler:(GCController *)controller {
237	// Hook in the callback handler for the correct gamepad profile.
238	// This is a bit of a weird design choice on Apples part.
239	// You need to select the most capable gamepad profile for the
240	// gamepad attached.
241	if (controller.extendedGamepad != nil) {
242		// The extended gamepad profile has all the input you could possibly find on
243		// a gamepad but will only be active if your gamepad actually has all of
244		// these...
245		controller.extendedGamepad.valueChangedHandler = ^(
246				GCExtendedGamepad *gamepad, GCControllerElement *element) {
247			int joy_id = [self getJoyIdForController:controller];
248
249			if (element == gamepad.buttonA) {
250				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
251						gamepad.buttonA.isPressed);
252			} else if (element == gamepad.buttonB) {
253				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
254						gamepad.buttonB.isPressed);
255			} else if (element == gamepad.buttonX) {
256				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
257						gamepad.buttonX.isPressed);
258			} else if (element == gamepad.buttonY) {
259				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
260						gamepad.buttonY.isPressed);
261			} else if (element == gamepad.leftShoulder) {
262				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
263						gamepad.leftShoulder.isPressed);
264			} else if (element == gamepad.rightShoulder) {
265				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
266						gamepad.rightShoulder.isPressed);
267			} else if (element == gamepad.leftTrigger) {
268				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2,
269						gamepad.leftTrigger.isPressed);
270			} else if (element == gamepad.rightTrigger) {
271				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2,
272						gamepad.rightTrigger.isPressed);
273			} else if (element == gamepad.dpad) {
274				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
275						gamepad.dpad.up.isPressed);
276				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
277						gamepad.dpad.down.isPressed);
278				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
279						gamepad.dpad.left.isPressed);
280				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
281						gamepad.dpad.right.isPressed);
282			};
283
284			InputDefault::JoyAxis jx;
285			jx.min = -1;
286			if (element == gamepad.leftThumbstick) {
287				jx.value = gamepad.leftThumbstick.xAxis.value;
288				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx);
289				jx.value = -gamepad.leftThumbstick.yAxis.value;
290				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx);
291			} else if (element == gamepad.rightThumbstick) {
292				jx.value = gamepad.rightThumbstick.xAxis.value;
293				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx);
294				jx.value = -gamepad.rightThumbstick.yAxis.value;
295				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx);
296			} else if (element == gamepad.leftTrigger) {
297				jx.value = gamepad.leftTrigger.value;
298				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx);
299			} else if (element == gamepad.rightTrigger) {
300				jx.value = gamepad.rightTrigger.value;
301				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx);
302			};
303		};
304	} else if (controller.gamepad != nil) {
305		// gamepad is the standard profile with 4 buttons, shoulder buttons and a
306		// D-pad
307		controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
308				GCControllerElement *element) {
309			int joy_id = [self getJoyIdForController:controller];
310
311			if (element == gamepad.buttonA) {
312				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
313						gamepad.buttonA.isPressed);
314			} else if (element == gamepad.buttonB) {
315				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
316						gamepad.buttonB.isPressed);
317			} else if (element == gamepad.buttonX) {
318				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
319						gamepad.buttonX.isPressed);
320			} else if (element == gamepad.buttonY) {
321				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
322						gamepad.buttonY.isPressed);
323			} else if (element == gamepad.leftShoulder) {
324				OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
325						gamepad.leftShoulder.isPressed);
326			} else if (element == gamepad.rightShoulder) {
327				OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
328						gamepad.rightShoulder.isPressed);
329			} else if (element == gamepad.dpad) {
330				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
331						gamepad.dpad.up.isPressed);
332				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
333						gamepad.dpad.down.isPressed);
334				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
335						gamepad.dpad.left.isPressed);
336				OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
337						gamepad.dpad.right.isPressed);
338			};
339		};
340#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
341		// while we are setting that as the minimum, seems our
342		// build environment doesn't like it
343	} else if (controller.microGamepad != nil) {
344		// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
345		controller.microGamepad.valueChangedHandler =
346				^(GCMicroGamepad *gamepad, GCControllerElement *element) {
347					int joy_id = [self getJoyIdForController:controller];
348
349					if (element == gamepad.buttonA) {
350						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
351								gamepad.buttonA.isPressed);
352					} else if (element == gamepad.buttonX) {
353						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
354								gamepad.buttonX.isPressed);
355					} else if (element == gamepad.dpad) {
356						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
357								gamepad.dpad.up.isPressed);
358						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
359								gamepad.dpad.down.isPressed);
360						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
361								gamepad.dpad.left.isPressed);
362						OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
363								gamepad.dpad.right.isPressed);
364					};
365				};
366#endif
367	};
368
369	///@TODO need to add support for controller.motion which gives us access to
370	/// the orientation of the device (if supported)
371
372	///@TODO need to add support for controllerPausedHandler which should be a
373	/// toggle
374};
375
376- (void)initGameControllers {
377	// get told when controllers connect, this will be called right away for
378	// already connected controllers
379	[[NSNotificationCenter defaultCenter]
380			addObserver:self
381			   selector:@selector(controllerWasConnected:)
382				   name:GCControllerDidConnectNotification
383				 object:nil];
384
385	// get told when controllers disconnect
386	[[NSNotificationCenter defaultCenter]
387			addObserver:self
388			   selector:@selector(controllerWasDisconnected:)
389				   name:GCControllerDidDisconnectNotification
390				 object:nil];
391};
392
393- (void)deinitGameControllers {
394	[[NSNotificationCenter defaultCenter]
395			removeObserver:self
396					  name:GCControllerDidConnectNotification
397					object:nil];
398	[[NSNotificationCenter defaultCenter]
399			removeObserver:self
400					  name:GCControllerDidDisconnectNotification
401					object:nil];
402
403	if (ios_joysticks != nil) {
404		[ios_joysticks dealloc];
405		ios_joysticks = nil;
406	};
407
408	if (pending_ios_joysticks != nil) {
409		[pending_ios_joysticks dealloc];
410		pending_ios_joysticks = nil;
411	};
412};
413
414OS::VideoMode _get_video_mode() {
415	int backingWidth;
416	int backingHeight;
417	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
418			GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
419	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
420			GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
421
422	OS::VideoMode vm;
423	vm.fullscreen = true;
424	vm.width = backingWidth;
425	vm.height = backingHeight;
426	vm.resizable = false;
427	return vm;
428};
429
430static int frame_count = 0;
431- (void)drawView:(GLView *)view;
432{
433
434	switch (frame_count) {
435		case 0: {
436			OS::get_singleton()->set_video_mode(_get_video_mode());
437
438			if (!OS::get_singleton()) {
439				exit(0);
440			};
441			++frame_count;
442
443			NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
444			OSIPhone::get_singleton()->set_locale(
445					String::utf8([locale_code UTF8String]));
446
447			NSString *uuid;
448			if ([[UIDevice currentDevice]
449						respondsToSelector:@selector(identifierForVendor)]) {
450				uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
451			} else {
452				// before iOS 6, so just generate an identifier and store it
453				uuid = [[NSUserDefaults standardUserDefaults]
454						objectForKey:@"identiferForVendor"];
455				if (!uuid) {
456					CFUUIDRef cfuuid = CFUUIDCreate(NULL);
457					uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
458					CFRelease(cfuuid);
459					[[NSUserDefaults standardUserDefaults]
460							setObject:uuid
461							   forKey:@"identifierForVendor"];
462				}
463			}
464
465			OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
466
467		}; break;
468
469		case 1: {
470
471			Main::setup2();
472			++frame_count;
473
474			if (pending_ios_joysticks != nil) {
475				for (GCController *controller in pending_ios_joysticks) {
476					_ios_add_joystick(controller, self);
477				}
478				[pending_ios_joysticks dealloc];
479				pending_ios_joysticks = nil;
480			}
481
482			// this might be necessary before here
483			NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
484			for (NSString *key in dict) {
485				NSObject *value = [dict objectForKey:key];
486				String ukey = String::utf8([key UTF8String]);
487
488				// we need a NSObject to Variant conversor
489
490				if ([value isKindOfClass:[NSString class]]) {
491					NSString *str = (NSString *)value;
492					String uval = String::utf8([str UTF8String]);
493
494					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
495
496				} else if ([value isKindOfClass:[NSNumber class]]) {
497
498					NSNumber *n = (NSNumber *)value;
499					double dval = [n doubleValue];
500
501					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
502				};
503				// do stuff
504			}
505
506		}; break;
507
508		case 2: {
509
510			Main::start();
511			++frame_count;
512
513		}; break; // no fallthrough
514
515		default: {
516			if (OSIPhone::get_singleton()) {
517				// OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
518				// accel[2]);
519				if (motionInitialised) {
520					// Just using polling approach for now, we can set this up so it sends
521					// data to us in intervals, might be better. See Apple reference pages
522					// for more details:
523					// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
524
525					// Apple splits our accelerometer date into a gravity and user movement
526					// component. We add them back together
527					CMAcceleration gravity = motionManager.deviceMotion.gravity;
528					CMAcceleration acceleration =
529							motionManager.deviceMotion.userAcceleration;
530
531					///@TODO We don't seem to be getting data here, is my device broken or
532					/// is this code incorrect?
533					CMMagneticField magnetic =
534							motionManager.deviceMotion.magneticField.field;
535
536					///@TODO we can access rotationRate as a CMRotationRate variable
537					///(processed date) or CMGyroData (raw data), have to see what works
538					/// best
539					CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
540
541					// Adjust for screen orientation.
542					// [[UIDevice currentDevice] orientation] changes even if we've fixed
543					// our orientation which is not a good thing when you're trying to get
544					// your user to move the screen in all directions and want consistent
545					// output
546
547					///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
548					/// is a bit of a hack. Godot obviously knows the orientation so maybe
549					/// we
550					// can use that instead? (note that left and right seem swapped)
551
552					switch ([[UIApplication sharedApplication] statusBarOrientation]) {
553						case UIInterfaceOrientationLandscapeLeft: {
554							OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
555									gravity.z);
556							OSIPhone::get_singleton()->update_accelerometer(
557									-(acceleration.y + gravity.y), (acceleration.x + gravity.x),
558									acceleration.z + gravity.z);
559							OSIPhone::get_singleton()->update_magnetometer(
560									-magnetic.y, magnetic.x, magnetic.z);
561							OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
562									rotation.z);
563						}; break;
564						case UIInterfaceOrientationLandscapeRight: {
565							OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
566									gravity.z);
567							OSIPhone::get_singleton()->update_accelerometer(
568									(acceleration.y + gravity.y), -(acceleration.x + gravity.x),
569									acceleration.z + gravity.z);
570							OSIPhone::get_singleton()->update_magnetometer(
571									magnetic.y, -magnetic.x, magnetic.z);
572							OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
573									rotation.z);
574						}; break;
575						case UIInterfaceOrientationPortraitUpsideDown: {
576							OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
577									gravity.z);
578							OSIPhone::get_singleton()->update_accelerometer(
579									-(acceleration.x + gravity.x), (acceleration.y + gravity.y),
580									acceleration.z + gravity.z);
581							OSIPhone::get_singleton()->update_magnetometer(
582									-magnetic.x, magnetic.y, magnetic.z);
583							OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
584									rotation.z);
585						}; break;
586						default: { // assume portrait
587							OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
588									gravity.z);
589							OSIPhone::get_singleton()->update_accelerometer(
590									acceleration.x + gravity.x, acceleration.y + gravity.y,
591									acceleration.z + gravity.z);
592							OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
593									magnetic.z);
594							OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
595									rotation.z);
596						}; break;
597					};
598				}
599
600				OSIPhone::get_singleton()->iterate();
601			};
602
603		}; break;
604	};
605};
606
607- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
608	if (OS::get_singleton()->get_main_loop()) {
609		OS::get_singleton()->get_main_loop()->notification(
610				MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
611	}
612};
613
614- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
615	CGRect rect = [[UIScreen mainScreen] bounds];
616
617	is_focus_out = false;
618
619	// disable idle timer
620	// application.idleTimerDisabled = YES;
621
622	// Create a full-screen window
623	window = [[UIWindow alloc] initWithFrame:rect];
624
625	OS::VideoMode vm = _get_video_mode();
626
627	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
628			NSUserDomainMask, YES);
629	NSString *documentsDirectory = [paths objectAtIndex:0];
630
631	int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
632	if (err != 0) {
633		// bail, things did not go very well for us, should probably output a message on screen with our error code...
634		exit(0);
635		return FALSE;
636	};
637
638	// WARNING: We must *always* create the GLView after we have constructed the
639	// OS with iphone_main. This allows the GLView to access project settings so
640	// it can properly initialize the OpenGL context
641	GLView *glView = [[GLView alloc] initWithFrame:rect];
642	glView.delegate = self;
643
644	view_controller = [[ViewController alloc] init];
645	view_controller.view = glView;
646	window.rootViewController = view_controller;
647
648	_set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
649	glView.useCADisplayLink =
650			bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
651	printf("cadisaplylink: %d", glView.useCADisplayLink);
652	glView.animationInterval = 1.0 / kRenderingFrequency;
653	[glView startAnimation];
654
655	// Show the window
656	[window makeKeyAndVisible];
657
658	// Configure and start accelerometer
659	if (!motionInitialised) {
660		motionManager = [[CMMotionManager alloc] init];
661		if (motionManager.deviceMotionAvailable) {
662			motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
663			[motionManager startDeviceMotionUpdatesUsingReferenceFrame:
664								   CMAttitudeReferenceFrameXMagneticNorthZVertical];
665			motionInitialised = YES;
666		};
667	};
668
669	[self initGameControllers];
670
671	[[NSNotificationCenter defaultCenter]
672			addObserver:self
673			   selector:@selector(onAudioInterruption:)
674				   name:AVAudioSessionInterruptionNotification
675				 object:[AVAudioSession sharedInstance]];
676
677	// OSIPhone::screen_width = rect.size.width - rect.origin.x;
678	// OSIPhone::screen_height = rect.size.height - rect.origin.y;
679
680	mainViewController = view_controller;
681
682	// prevent to stop music in another background app
683	[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
684
685	return TRUE;
686};
687
688- (void)onAudioInterruption:(NSNotification *)notification {
689	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
690		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
691			NSLog(@"Audio interruption began");
692			on_focus_out(view_controller, &is_focus_out);
693		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
694			NSLog(@"Audio interruption ended");
695			on_focus_in(view_controller, &is_focus_out);
696		}
697	}
698};
699
700- (void)applicationWillTerminate:(UIApplication *)application {
701	[self deinitGameControllers];
702
703	if (motionInitialised) {
704		///@TODO is this the right place to clean this up?
705		[motionManager stopDeviceMotionUpdates];
706		[motionManager release];
707		motionManager = nil;
708		motionInitialised = NO;
709	};
710
711	iphone_finish();
712};
713
714// When application goes to background (e.g. user switches to another app or presses Home),
715// then applicationWillResignActive -> applicationDidEnterBackground are called.
716// When user opens the inactive app again,
717// applicationWillEnterForeground -> applicationDidBecomeActive are called.
718
719// There are cases when applicationWillResignActive -> applicationDidBecomeActive
720// sequence is called without the app going to background. For example, that happens
721// if you open the app list without switching to another app or open/close the
722// notification panel by swiping from the upper part of the screen.
723
724- (void)applicationWillResignActive:(UIApplication *)application {
725	on_focus_out(view_controller, &is_focus_out);
726}
727
728- (void)applicationDidBecomeActive:(UIApplication *)application {
729	on_focus_in(view_controller, &is_focus_out);
730}
731
732- (void)dealloc {
733	[window release];
734	[super dealloc];
735}
736
737@end
738