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