• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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