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