src/video/uikit/SDL_uikitappdelegate.m
author Sam Lantinga <slouken@libsdl.org>
Sun, 01 Jan 2017 18:33:28 -0800
changeset 10737 3406a0f8b041
parent 10709 5c8870c092ed
child 10995 b2a06697db74
permissions -rw-r--r--
Updated copyright for 2017
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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 
    41 static int forward_argc;
    42 static char **forward_argv;
    43 static int exit_status;
    44 
    45 int 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 
    72 static void
    73 SDL_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. */
    81 static UIImage *
    82 SDL_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 - (UIWindow *)window
   426 {
   427     SDL_VideoDevice *_this = SDL_GetVideoDevice();
   428     if (_this) {
   429         SDL_Window *window = NULL;
   430         for (window = _this->windows; window != NULL; window = window->next) {
   431             SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
   432             if (data != nil) {
   433                 return data.uiwindow;
   434             }
   435         }
   436     }
   437     return nil;
   438 }
   439 
   440 - (void)setWindow:(UIWindow *)window
   441 {
   442     /* Do nothing. */
   443 }
   444 
   445 - (void)applicationWillTerminate:(UIApplication *)application
   446 {
   447     SDL_SendAppEvent(SDL_APP_TERMINATING);
   448 }
   449 
   450 - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
   451 {
   452     SDL_SendAppEvent(SDL_APP_LOWMEMORY);
   453 }
   454 
   455 #if !TARGET_OS_TV
   456 - (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation
   457 {
   458     BOOL isLandscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation);
   459     SDL_VideoDevice *_this = SDL_GetVideoDevice();
   460 
   461     if (_this && _this->num_displays > 0) {
   462         SDL_DisplayMode *desktopmode = &_this->displays[0].desktop_mode;
   463         SDL_DisplayMode *currentmode = &_this->displays[0].current_mode;
   464 
   465         /* The desktop display mode should be kept in sync with the screen
   466          * orientation so that updating a window's fullscreen state to
   467          * SDL_WINDOW_FULLSCREEN_DESKTOP keeps the window dimensions in the
   468          * correct orientation. */
   469         if (isLandscape != (desktopmode->w > desktopmode->h)) {
   470             int height = desktopmode->w;
   471             desktopmode->w = desktopmode->h;
   472             desktopmode->h = height;
   473         }
   474 
   475         /* Same deal with the current mode + SDL_GetCurrentDisplayMode. */
   476         if (isLandscape != (currentmode->w > currentmode->h)) {
   477             int height = currentmode->w;
   478             currentmode->w = currentmode->h;
   479             currentmode->h = height;
   480         }
   481     }
   482 }
   483 #endif
   484 
   485 - (void)applicationWillResignActive:(UIApplication*)application
   486 {
   487     SDL_VideoDevice *_this = SDL_GetVideoDevice();
   488     if (_this) {
   489         SDL_Window *window;
   490         for (window = _this->windows; window != NULL; window = window->next) {
   491             SDL_SendWindowEvent(window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   492             SDL_SendWindowEvent(window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   493         }
   494     }
   495     SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
   496 }
   497 
   498 - (void)applicationDidEnterBackground:(UIApplication*)application
   499 {
   500     SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
   501 }
   502 
   503 - (void)applicationWillEnterForeground:(UIApplication*)application
   504 {
   505     SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
   506 }
   507 
   508 - (void)applicationDidBecomeActive:(UIApplication*)application
   509 {
   510     SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
   511 
   512     SDL_VideoDevice *_this = SDL_GetVideoDevice();
   513     if (_this) {
   514         SDL_Window *window;
   515         for (window = _this->windows; window != nil; window = window->next) {
   516             SDL_SendWindowEvent(window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   517             SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   518         }
   519     }
   520 }
   521 
   522 - (void)sendDropFileForURL:(NSURL *)url
   523 {
   524     NSURL *fileURL = url.filePathURL;
   525     if (fileURL != nil) {
   526         SDL_SendDropFile(NULL, fileURL.path.UTF8String);
   527     } else {
   528         SDL_SendDropFile(NULL, url.absoluteString.UTF8String);
   529     }
   530     SDL_SendDropComplete(NULL);
   531 }
   532 
   533 #if TARGET_OS_TV
   534 /* TODO: Use this on iOS 9+ as well? */
   535 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
   536 {
   537     /* TODO: Handle options */
   538     [self sendDropFileForURL:url];
   539     return YES;
   540 }
   541 #endif /* TARGET_OS_TV */
   542 
   543 #if !TARGET_OS_TV
   544 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
   545 {
   546     [self sendDropFileForURL:url];
   547     return YES;
   548 }
   549 #endif /* !TARGET_OS_TV */
   550 
   551 @end
   552 
   553 #endif /* SDL_VIDEO_DRIVER_UIKIT */
   554 
   555 /* vi: set ts=4 sw=4 expandtab: */