SDL  2.0
SDL_uikitappdelegate.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2018 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 SDLCALL
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 */
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 
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 */
347  exit_status = SDL_main(forward_argc, forward_argv);
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 */
417  SDL_IdleTimerDisabledChanged, NULL);
418 
420  [self performSelector:@selector(postFinishLaunch) withObject:nil afterDelay:0.0];
421 
422  return YES;
423 }
424 
425 - (UIWindow *)window
426 {
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 #if !TARGET_OS_TV
446 - (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation
447 {
448  BOOL isLandscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation);
450 
451  if (_this && _this->num_displays > 0) {
452  SDL_DisplayMode *desktopmode = &_this->displays[0].desktop_mode;
453  SDL_DisplayMode *currentmode = &_this->displays[0].current_mode;
454 
455  /* The desktop display mode should be kept in sync with the screen
456  * orientation so that updating a window's fullscreen state to
457  * SDL_WINDOW_FULLSCREEN_DESKTOP keeps the window dimensions in the
458  * correct orientation. */
459  if (isLandscape != (desktopmode->w > desktopmode->h)) {
460  int height = desktopmode->w;
461  desktopmode->w = desktopmode->h;
462  desktopmode->h = height;
463  }
464 
465  /* Same deal with the current mode + SDL_GetCurrentDisplayMode. */
466  if (isLandscape != (currentmode->w > currentmode->h)) {
467  int height = currentmode->w;
468  currentmode->w = currentmode->h;
469  currentmode->h = height;
470  }
471  }
472 }
473 #endif
474 
475 - (void)applicationWillTerminate:(UIApplication *)application
476 {
478 }
479 
480 - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
481 {
483 }
484 
485 - (void)applicationWillResignActive:(UIApplication*)application
486 {
488 }
489 
490 - (void)applicationDidEnterBackground:(UIApplication*)application
491 {
493 }
494 
495 - (void)applicationWillEnterForeground:(UIApplication*)application
496 {
498 }
499 
500 - (void)applicationDidBecomeActive:(UIApplication*)application
501 {
503 }
504 
505 - (void)sendDropFileForURL:(NSURL *)url
506 {
507  NSURL *fileURL = url.filePathURL;
508  if (fileURL != nil) {
509  SDL_SendDropFile(NULL, fileURL.path.UTF8String);
510  } else {
511  SDL_SendDropFile(NULL, url.absoluteString.UTF8String);
512  }
514 }
515 
516 #if TARGET_OS_TV || (defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0)
517 
518 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
519 {
520  /* TODO: Handle options */
521  [self sendDropFileForURL:url];
522  return YES;
523 }
524 
525 #else
526 
527 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
528 {
529  [self sendDropFileForURL:url];
530  return YES;
531 }
532 
533 #endif
534 
535 @end
536 
537 #endif /* SDL_VIDEO_DRIVER_UIKIT */
538 
539 /* vi: set ts=4 sw=4 expandtab: */
SDL_Window * next
Definition: SDL_sysvideo.h:114
SDL_bool UIKit_IsSystemVersionAtLeast(double version)
void SDL_OnApplicationWillTerminate(void)
Definition: SDL_video.c:3925
GLuint id
void SDL_OnApplicationDidEnterBackground(void)
Definition: SDL_video.c:3947
GLeglImageOES image
Definition: SDL_opengl.h:2148
#define SDL_SetMainReady
int SDL_SendDropFile(SDL_Window *window, const char *file)
SDL_EventEntry * free
Definition: SDL_events.c:84
The structure that defines a display mode.
Definition: SDL_video.h:53
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
void SDL_OnApplicationDidBecomeActive(void)
Definition: SDL_video.c:3957
int SDL_SendDropComplete(SDL_Window *window)
GLuint const GLchar * name
GLint GLint GLsizei width
Definition: SDL_opengl.h:1572
static SDL_VideoDevice * _this
Definition: SDL_video.c:121
void SDL_OnApplicationDidReceiveMemoryWarning(void)
Definition: SDL_video.c:3930
NSString * getAppDelegateClassName()
SDL_DisplayMode current_mode
Definition: SDL_sysvideo.h:132
SDL_VideoDisplay * displays
Definition: SDL_sysvideo.h:312
SDL_Window * windows
Definition: SDL_sysvideo.h:313
UIWindow * uiwindow
#define SDL_HINT_IDLE_TIMER_DISABLED
A variable controlling whether the idle timer is disabled on iOS.
Definition: SDL_hints.h:329
GLsizeiptr size
void SDL_OnApplicationWillResignActive(void)
Definition: SDL_video.c:3935
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
#define NULL
Definition: begin_code.h:164
SDL_DisplayMode desktop_mode
Definition: SDL_sysvideo.h:131
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1572
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
C_LINKAGE SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[])
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
The type used to identify a window.
Definition: SDL_sysvideo.h:73
#define SDL_AddHintCallback
SDL_VideoDevice * SDL_GetVideoDevice(void)
Definition: SDL_video.c:586
#define main
Definition: SDL_main.h:111
void * driverdata
Definition: SDL_sysvideo.h:111
GLuint in
#define SDL_iPhoneSetEventPump
#define SDLCALL
Definition: SDL_internal.h:45
#define malloc
Definition: SDL_qsort.c:47
void SDL_OnApplicationWillEnterForeground(void)
Definition: SDL_video.c:3952