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