SDL_uikitappdelegate.m (17439B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20 */ 21 #include "../../SDL_internal.h" 22 23 #if SDL_VIDEO_DRIVER_UIKIT 24 25 #include "../SDL_sysvideo.h" 26 #include "SDL_hints.h" 27 #include "SDL_system.h" 28 #include "SDL_main.h" 29 30 #import "SDL_uikitappdelegate.h" 31 #import "SDL_uikitmodes.h" 32 #import "SDL_uikitwindow.h" 33 34 #include "../../events/SDL_events_c.h" 35 36 #ifdef main 37 #undef main 38 #endif 39 40 static SDL_main_func forward_main; 41 static int forward_argc; 42 static char **forward_argv; 43 static int exit_status; 44 45 int SDL_UIKitRunApp(int argc, char *argv[], SDL_main_func mainFunction) 46 { 47 int i; 48 49 /* store arguments */ 50 forward_main = mainFunction; 51 forward_argc = argc; 52 forward_argv = (char **)malloc((argc+1) * sizeof(char *)); 53 for (i = 0; i < argc; i++) { 54 forward_argv[i] = malloc( (strlen(argv[i])+1) * sizeof(char)); 55 strcpy(forward_argv[i], argv[i]); 56 } 57 forward_argv[i] = NULL; 58 59 /* Give over control to run loop, SDLUIKitDelegate will handle most things from here */ 60 @autoreleasepool { 61 UIApplicationMain(argc, argv, nil, [SDLUIKitDelegate getAppDelegateClassName]); 62 } 63 64 /* free the memory we used to hold copies of argc and argv */ 65 for (i = 0; i < forward_argc; i++) { 66 free(forward_argv[i]); 67 } 68 free(forward_argv); 69 70 return exit_status; 71 } 72 73 static void SDLCALL 74 SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldValue, const char *hint) 75 { 76 BOOL disable = (hint && *hint != '0'); 77 [UIApplication sharedApplication].idleTimerDisabled = disable; 78 } 79 80 #if !TARGET_OS_TV 81 /* Load a launch image using the old UILaunchImageFile-era naming rules. */ 82 static UIImage * 83 SDL_LoadLaunchImageNamed(NSString *name, int screenh) 84 { 85 UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation; 86 UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; 87 UIImage *image = nil; 88 89 if (idiom == UIUserInterfaceIdiomPhone && screenh == 568) { 90 /* The image name for the iPhone 5 uses its height as a suffix. */ 91 image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-568h", name]]; 92 } else if (idiom == UIUserInterfaceIdiomPad) { 93 /* iPad apps can launch in any orientation. */ 94 if (UIInterfaceOrientationIsLandscape(curorient)) { 95 if (curorient == UIInterfaceOrientationLandscapeLeft) { 96 image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-LandscapeLeft", name]]; 97 } else { 98 image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-LandscapeRight", name]]; 99 } 100 if (!image) { 101 image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-Landscape", name]]; 102 } 103 } else { 104 if (curorient == UIInterfaceOrientationPortraitUpsideDown) { 105 image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-PortraitUpsideDown", name]]; 106 } 107 if (!image) { 108 image = [UIImage imageNamed:[NSString stringWithFormat:@"%@-Portrait", name]]; 109 } 110 } 111 } 112 113 if (!image) { 114 image = [UIImage imageNamed:name]; 115 } 116 117 return image; 118 } 119 #endif /* !TARGET_OS_TV */ 120 121 @interface SDLLaunchScreenController () 122 123 #if !TARGET_OS_TV 124 - (NSUInteger)supportedInterfaceOrientations; 125 #endif 126 127 @end 128 129 @implementation SDLLaunchScreenController 130 131 - (instancetype)init 132 { 133 return [self initWithNibName:nil bundle:[NSBundle mainBundle]]; 134 } 135 136 - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 137 { 138 if (!(self = [super initWithNibName:nil bundle:nil])) { 139 return nil; 140 } 141 142 NSString *screenname = nibNameOrNil; 143 NSBundle *bundle = nibBundleOrNil; 144 BOOL atleastiOS8 = UIKit_IsSystemVersionAtLeast(8.0); 145 146 /* Launch screens were added in iOS 8. Otherwise we use launch images. */ 147 if (screenname && atleastiOS8) { 148 @try { 149 self.view = [bundle loadNibNamed:screenname owner:self options:nil][0]; 150 } 151 @catch (NSException *exception) { 152 /* If a launch screen name is specified but it fails to load, iOS 153 * displays a blank screen rather than falling back to an image. */ 154 return nil; 155 } 156 } 157 158 if (!self.view) { 159 NSArray *launchimages = [bundle objectForInfoDictionaryKey:@"UILaunchImages"]; 160 NSString *imagename = nil; 161 UIImage *image = nil; 162 163 int screenw = (int)([UIScreen mainScreen].bounds.size.width + 0.5); 164 int screenh = (int)([UIScreen mainScreen].bounds.size.height + 0.5); 165 166 #if !TARGET_OS_TV 167 UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation; 168 169 /* We always want portrait-oriented size, to match UILaunchImageSize. */ 170 if (screenw > screenh) { 171 int width = screenw; 172 screenw = screenh; 173 screenh = width; 174 } 175 #endif 176 177 /* Xcode 5 introduced a dictionary of launch images in Info.plist. */ 178 if (launchimages) { 179 for (NSDictionary *dict in launchimages) { 180 NSString *minversion = dict[@"UILaunchImageMinimumOSVersion"]; 181 NSString *sizestring = dict[@"UILaunchImageSize"]; 182 183 /* Ignore this image if the current version is too low. */ 184 if (minversion && !UIKit_IsSystemVersionAtLeast(minversion.doubleValue)) { 185 continue; 186 } 187 188 /* Ignore this image if the size doesn't match. */ 189 if (sizestring) { 190 CGSize size = CGSizeFromString(sizestring); 191 if ((int)(size.width + 0.5) != screenw || (int)(size.height + 0.5) != screenh) { 192 continue; 193 } 194 } 195 196 #if !TARGET_OS_TV 197 UIInterfaceOrientationMask orientmask = UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; 198 NSString *orientstring = dict[@"UILaunchImageOrientation"]; 199 200 if (orientstring) { 201 if ([orientstring isEqualToString:@"PortraitUpsideDown"]) { 202 orientmask = UIInterfaceOrientationMaskPortraitUpsideDown; 203 } else if ([orientstring isEqualToString:@"Landscape"]) { 204 orientmask = UIInterfaceOrientationMaskLandscape; 205 } else if ([orientstring isEqualToString:@"LandscapeLeft"]) { 206 orientmask = UIInterfaceOrientationMaskLandscapeLeft; 207 } else if ([orientstring isEqualToString:@"LandscapeRight"]) { 208 orientmask = UIInterfaceOrientationMaskLandscapeRight; 209 } 210 } 211 212 /* Ignore this image if the orientation doesn't match. */ 213 if ((orientmask & (1 << curorient)) == 0) { 214 continue; 215 } 216 #endif 217 218 imagename = dict[@"UILaunchImageName"]; 219 } 220 221 if (imagename) { 222 image = [UIImage imageNamed:imagename]; 223 } 224 } 225 #if !TARGET_OS_TV 226 else { 227 imagename = [bundle objectForInfoDictionaryKey:@"UILaunchImageFile"]; 228 229 if (imagename) { 230 image = SDL_LoadLaunchImageNamed(imagename, screenh); 231 } 232 233 if (!image) { 234 image = SDL_LoadLaunchImageNamed(@"Default", screenh); 235 } 236 } 237 #endif 238 239 if (image) { 240 UIImageView *view = [[UIImageView alloc] initWithFrame:[UIScreen mainScreen].bounds]; 241 UIImageOrientation imageorient = UIImageOrientationUp; 242 243 #if !TARGET_OS_TV 244 /* Bugs observed / workaround tested in iOS 8.3, 7.1, and 6.1. */ 245 if (UIInterfaceOrientationIsLandscape(curorient)) { 246 if (atleastiOS8 && image.size.width < image.size.height) { 247 /* On iOS 8, portrait launch images displayed in forced- 248 * landscape mode (e.g. a standard Default.png on an iPhone 249 * when Info.plist only supports landscape orientations) need 250 * to be rotated to display in the expected orientation. */ 251 if (curorient == UIInterfaceOrientationLandscapeLeft) { 252 imageorient = UIImageOrientationRight; 253 } else if (curorient == UIInterfaceOrientationLandscapeRight) { 254 imageorient = UIImageOrientationLeft; 255 } 256 } else if (!atleastiOS8 && image.size.width > image.size.height) { 257 /* On iOS 7 and below, landscape launch images displayed in 258 * landscape mode (e.g. landscape iPad launch images) need 259 * to be rotated to display in the expected orientation. */ 260 if (curorient == UIInterfaceOrientationLandscapeLeft) { 261 imageorient = UIImageOrientationLeft; 262 } else if (curorient == UIInterfaceOrientationLandscapeRight) { 263 imageorient = UIImageOrientationRight; 264 } 265 } 266 } 267 #endif 268 269 /* Create the properly oriented image. */ 270 view.image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:imageorient]; 271 272 self.view = view; 273 } 274 } 275 276 return self; 277 } 278 279 - (void)loadView 280 { 281 /* Do nothing. */ 282 } 283 284 #if !TARGET_OS_TV 285 - (BOOL)shouldAutorotate 286 { 287 /* If YES, the launch image will be incorrectly rotated in some cases. */ 288 return NO; 289 } 290 291 - (NSUInteger)supportedInterfaceOrientations 292 { 293 /* We keep the supported orientations unrestricted to avoid the case where 294 * there are no common orientations between the ones set in Info.plist and 295 * the ones set here (it will cause an exception in that case.) */ 296 return UIInterfaceOrientationMaskAll; 297 } 298 #endif /* !TARGET_OS_TV */ 299 300 @end 301 302 @implementation SDLUIKitDelegate { 303 UIWindow *launchWindow; 304 } 305 306 /* convenience method */ 307 + (id)sharedAppDelegate 308 { 309 /* the delegate is set in UIApplicationMain(), which is guaranteed to be 310 * called before this method */ 311 return [UIApplication sharedApplication].delegate; 312 } 313 314 + (NSString *)getAppDelegateClassName 315 { 316 /* subclassing notice: when you subclass this appdelegate, make sure to add 317 * a category to override this method and return the actual name of the 318 * delegate */ 319 return @"SDLUIKitDelegate"; 320 } 321 322 - (void)hideLaunchScreen 323 { 324 UIWindow *window = launchWindow; 325 326 if (!window || window.hidden) { 327 return; 328 } 329 330 launchWindow = nil; 331 332 /* Do a nice animated fade-out (roughly matches the real launch behavior.) */ 333 [UIView animateWithDuration:0.2 animations:^{ 334 window.alpha = 0.0; 335 } completion:^(BOOL finished) { 336 window.hidden = YES; 337 UIKit_ForceUpdateHomeIndicator(); /* Wait for launch screen to hide so settings are applied to the actual view controller. */ 338 }]; 339 } 340 341 - (void)postFinishLaunch 342 { 343 /* Hide the launch screen the next time the run loop is run. SDL apps will 344 * have a chance to load resources while the launch screen is still up. */ 345 [self performSelector:@selector(hideLaunchScreen) withObject:nil afterDelay:0.0]; 346 347 /* run the user's application, passing argc and argv */ 348 SDL_iPhoneSetEventPump(SDL_TRUE); 349 exit_status = forward_main(forward_argc, forward_argv); 350 SDL_iPhoneSetEventPump(SDL_FALSE); 351 352 if (launchWindow) { 353 launchWindow.hidden = YES; 354 launchWindow = nil; 355 } 356 357 /* exit, passing the return status from the user's application */ 358 /* We don't actually exit to support applications that do setup in their 359 * main function and then allow the Cocoa event loop to run. */ 360 /* exit(exit_status); */ 361 } 362 363 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 364 { 365 NSBundle *bundle = [NSBundle mainBundle]; 366 367 #if SDL_IPHONE_LAUNCHSCREEN 368 /* The normal launch screen is displayed until didFinishLaunching returns, 369 * but SDL_main is called after that happens and there may be a noticeable 370 * delay between the start of SDL_main and when the first real frame is 371 * displayed (e.g. if resources are loaded before SDL_GL_SwapWindow is 372 * called), so we show the launch screen programmatically until the first 373 * time events are pumped. */ 374 UIViewController *vc = nil; 375 NSString *screenname = nil; 376 377 /* tvOS only uses a plain launch image. */ 378 #if !TARGET_OS_TV 379 screenname = [bundle objectForInfoDictionaryKey:@"UILaunchStoryboardName"]; 380 381 if (screenname && UIKit_IsSystemVersionAtLeast(8.0)) { 382 @try { 383 /* The launch storyboard is actually a nib in some older versions of 384 * Xcode. We'll try to load it as a storyboard first, as it's more 385 * modern. */ 386 UIStoryboard *storyboard = [UIStoryboard storyboardWithName:screenname bundle:bundle]; 387 vc = [storyboard instantiateInitialViewController]; 388 } 389 @catch (NSException *exception) { 390 /* Do nothing (there's more code to execute below). */ 391 } 392 } 393 #endif 394 395 if (vc == nil) { 396 vc = [[SDLLaunchScreenController alloc] initWithNibName:screenname bundle:bundle]; 397 } 398 399 if (vc.view) { 400 launchWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 401 402 /* We don't want the launch window immediately hidden when a real SDL 403 * window is shown - we fade it out ourselves when we're ready. */ 404 launchWindow.windowLevel = UIWindowLevelNormal + 1.0; 405 406 /* Show the window but don't make it key. Events should always go to 407 * other windows when possible. */ 408 launchWindow.hidden = NO; 409 410 launchWindow.rootViewController = vc; 411 } 412 #endif 413 414 /* Set working directory to resource path */ 415 [[NSFileManager defaultManager] changeCurrentDirectoryPath:[bundle resourcePath]]; 416 417 /* register a callback for the idletimer hint */ 418 SDL_AddHintCallback(SDL_HINT_IDLE_TIMER_DISABLED, 419 SDL_IdleTimerDisabledChanged, NULL); 420 421 SDL_SetMainReady(); 422 [self performSelector:@selector(postFinishLaunch) withObject:nil afterDelay:0.0]; 423 424 return YES; 425 } 426 427 - (UIWindow *)window 428 { 429 SDL_VideoDevice *_this = SDL_GetVideoDevice(); 430 if (_this) { 431 SDL_Window *window = NULL; 432 for (window = _this->windows; window != NULL; window = window->next) { 433 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 434 if (data != nil) { 435 return data.uiwindow; 436 } 437 } 438 } 439 return nil; 440 } 441 442 - (void)setWindow:(UIWindow *)window 443 { 444 /* Do nothing. */ 445 } 446 447 #if !TARGET_OS_TV 448 - (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation 449 { 450 SDL_OnApplicationDidChangeStatusBarOrientation(); 451 } 452 #endif 453 454 - (void)applicationWillTerminate:(UIApplication *)application 455 { 456 SDL_OnApplicationWillTerminate(); 457 } 458 459 - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application 460 { 461 SDL_OnApplicationDidReceiveMemoryWarning(); 462 } 463 464 - (void)applicationWillResignActive:(UIApplication*)application 465 { 466 SDL_OnApplicationWillResignActive(); 467 } 468 469 - (void)applicationDidEnterBackground:(UIApplication*)application 470 { 471 SDL_OnApplicationDidEnterBackground(); 472 } 473 474 - (void)applicationWillEnterForeground:(UIApplication*)application 475 { 476 SDL_OnApplicationWillEnterForeground(); 477 } 478 479 - (void)applicationDidBecomeActive:(UIApplication*)application 480 { 481 SDL_OnApplicationDidBecomeActive(); 482 } 483 484 - (void)sendDropFileForURL:(NSURL *)url 485 { 486 NSURL *fileURL = url.filePathURL; 487 if (fileURL != nil) { 488 SDL_SendDropFile(NULL, fileURL.path.UTF8String); 489 } else { 490 SDL_SendDropFile(NULL, url.absoluteString.UTF8String); 491 } 492 SDL_SendDropComplete(NULL); 493 } 494 495 #if TARGET_OS_TV || (defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0) 496 497 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options 498 { 499 /* TODO: Handle options */ 500 [self sendDropFileForURL:url]; 501 return YES; 502 } 503 504 #else 505 506 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 507 { 508 [self sendDropFileForURL:url]; 509 return YES; 510 } 511 512 #endif 513 514 @end 515 516 #endif /* SDL_VIDEO_DRIVER_UIKIT */ 517 518 /* vi: set ts=4 sw=4 expandtab: */