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