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