summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/uikit
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/uikit
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/uikit')
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.h45
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.m519
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.h33
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.m105
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.h40
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m461
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.h28
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.m154
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.h54
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.m140
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.h67
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m543
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.h40
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.m221
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.h62
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.m377
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.h39
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.m214
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.h52
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.m312
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.h52
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m587
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.h96
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.m736
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.h52
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.m265
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.h56
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.m471
28 files changed, 5821 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.h
new file mode 100644
index 0000000..77ccbfd
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitappdelegate.h
@@ -0,0 +1,45 @@
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
22#import <UIKit/UIKit.h>
23
24@interface SDLLaunchScreenController : UIViewController
25
26- (instancetype)init;
27- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
28- (void)loadView;
29
30@end
31
32@interface SDLUIKitDelegate : NSObject <UIApplicationDelegate>
33
34+ (id)sharedAppDelegate;
35+ (NSString *)getAppDelegateClassName;
36
37- (void)hideLaunchScreen;
38
39/* This property is marked as optional, and is only intended to be used when
40 * the app's UI is storyboard-based. SDL is not storyboard-based, however
41 * several major third-party ad APIs (e.g. Google admob) incorrectly assume this
42 * property always exists, and will crash if it doesn't. */
43@property(nonatomic) UIWindow *window;
44
45@end
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
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.h
new file mode 100644
index 0000000..1c538c2
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.h
@@ -0,0 +1,33 @@
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#ifndef SDL_uikitclipboard_h_
22#define SDL_uikitclipboard_h_
23
24#include "../SDL_sysvideo.h"
25
26extern bool UIKit_SetClipboardText(SDL_VideoDevice *_this, const char *text);
27extern char *UIKit_GetClipboardText(SDL_VideoDevice *_this);
28extern bool UIKit_HasClipboardText(SDL_VideoDevice *_this);
29
30extern void UIKit_InitClipboard(SDL_VideoDevice *_this);
31extern void UIKit_QuitClipboard(SDL_VideoDevice *_this);
32
33#endif // SDL_uikitclipboard_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.m
new file mode 100644
index 0000000..8ed4eb0
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitclipboard.m
@@ -0,0 +1,105 @@
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_uikitvideo.h"
26#include "../../events/SDL_clipboardevents_c.h"
27
28#import <UIKit/UIPasteboard.h>
29
30bool UIKit_SetClipboardText(SDL_VideoDevice *_this, const char *text)
31{
32#ifdef SDL_PLATFORM_TVOS
33 return SDL_SetError("The clipboard is not available on tvOS");
34#else
35 @autoreleasepool {
36 [UIPasteboard generalPasteboard].string = @(text);
37 return true;
38 }
39#endif
40}
41
42char *UIKit_GetClipboardText(SDL_VideoDevice *_this)
43{
44#ifdef SDL_PLATFORM_TVOS
45 return SDL_strdup(""); // Unsupported.
46#else
47 @autoreleasepool {
48 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
49 NSString *string = pasteboard.string;
50
51 if (string != nil) {
52 return SDL_strdup(string.UTF8String);
53 } else {
54 return SDL_strdup("");
55 }
56 }
57#endif
58}
59
60bool UIKit_HasClipboardText(SDL_VideoDevice *_this)
61{
62 @autoreleasepool {
63#ifndef SDL_PLATFORM_TVOS
64 if ([UIPasteboard generalPasteboard].string != nil) {
65 return true;
66 }
67#endif
68 return false;
69 }
70}
71
72void UIKit_InitClipboard(SDL_VideoDevice *_this)
73{
74#ifndef SDL_PLATFORM_TVOS
75 @autoreleasepool {
76 SDL_UIKitVideoData *data = (__bridge SDL_UIKitVideoData *)_this->internal;
77 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
78
79 id observer = [center addObserverForName:UIPasteboardChangedNotification
80 object:nil
81 queue:nil
82 usingBlock:^(NSNotification *note) {
83 // TODO: compute mime types
84 SDL_SendClipboardUpdate(false, NULL, 0);
85 }];
86
87 data.pasteboardObserver = observer;
88 }
89#endif
90}
91
92void UIKit_QuitClipboard(SDL_VideoDevice *_this)
93{
94 @autoreleasepool {
95 SDL_UIKitVideoData *data = (__bridge SDL_UIKitVideoData *)_this->internal;
96
97 if (data.pasteboardObserver != nil) {
98 [[NSNotificationCenter defaultCenter] removeObserver:data.pasteboardObserver];
99 }
100
101 data.pasteboardObserver = nil;
102 }
103}
104
105#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.h
new file mode 100644
index 0000000..c767fb1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.h
@@ -0,0 +1,40 @@
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#ifndef SDL_uikitevents_h_
22#define SDL_uikitevents_h_
23
24#import <UIKit/UIKit.h>
25
26#include "../SDL_sysvideo.h"
27
28extern void SDL_UpdateLifecycleObserver(void);
29
30extern Uint64 UIKit_GetEventTimestamp(NSTimeInterval nsTimestamp);
31extern void UIKit_PumpEvents(SDL_VideoDevice *_this);
32
33extern void SDL_InitGCKeyboard(void);
34extern void SDL_QuitGCKeyboard(void);
35
36extern void SDL_InitGCMouse(void);
37extern bool SDL_GCMouseRelativeMode(void);
38extern void SDL_QuitGCMouse(void);
39
40#endif // SDL_uikitevents_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m
new file mode 100644
index 0000000..86224f7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitevents.m
@@ -0,0 +1,461 @@
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 "../../events/SDL_events_c.h"
26#include "../../main/SDL_main_callbacks.h"
27
28#include "SDL_uikitevents.h"
29#include "SDL_uikitopengles.h"
30#include "SDL_uikitvideo.h"
31#include "SDL_uikitwindow.h"
32
33#import <Foundation/Foundation.h>
34#import <GameController/GameController.h>
35
36static BOOL UIKit_EventPumpEnabled = YES;
37
38@interface SDL_LifecycleObserver : NSObject
39@property(nonatomic, assign) BOOL isObservingNotifications;
40@end
41
42@implementation SDL_LifecycleObserver
43
44- (void)update
45{
46 NSNotificationCenter *notificationCenter = NSNotificationCenter.defaultCenter;
47 bool wants_observation = (UIKit_EventPumpEnabled || SDL_HasMainCallbacks());
48 if (!wants_observation) {
49 // Make sure no windows have active animation callbacks
50 int num_windows = 0;
51 SDL_free(SDL_GetWindows(&num_windows));
52 if (num_windows > 0) {
53 wants_observation = true;
54 }
55 }
56 if (wants_observation && !self.isObservingNotifications) {
57 self.isObservingNotifications = YES;
58 [notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
59 [notificationCenter addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil];
60 [notificationCenter addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
61 [notificationCenter addObserver:self selector:@selector(applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
62 [notificationCenter addObserver:self selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
63 [notificationCenter addObserver:self selector:@selector(applicationDidReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
64#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
65 [notificationCenter addObserver:self
66 selector:@selector(applicationDidChangeStatusBarOrientation)
67 name:UIApplicationDidChangeStatusBarOrientationNotification
68 object:nil];
69#endif
70 } else if (!wants_observation && self.isObservingNotifications) {
71 self.isObservingNotifications = NO;
72 [notificationCenter removeObserver:self];
73 }
74}
75
76- (void)applicationDidBecomeActive
77{
78 SDL_OnApplicationDidEnterForeground();
79}
80
81- (void)applicationWillResignActive
82{
83 SDL_OnApplicationWillEnterBackground();
84}
85
86- (void)applicationDidEnterBackground
87{
88 SDL_OnApplicationDidEnterBackground();
89}
90
91- (void)applicationWillEnterForeground
92{
93 SDL_OnApplicationWillEnterForeground();
94}
95
96- (void)applicationWillTerminate
97{
98 SDL_OnApplicationWillTerminate();
99}
100
101- (void)applicationDidReceiveMemoryWarning
102{
103 SDL_OnApplicationDidReceiveMemoryWarning();
104}
105
106#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
107- (void)applicationDidChangeStatusBarOrientation
108{
109 SDL_OnApplicationDidChangeStatusBarOrientation();
110}
111#endif
112
113@end
114
115void SDL_UpdateLifecycleObserver(void)
116{
117 static SDL_LifecycleObserver *lifecycleObserver;
118 static dispatch_once_t onceToken;
119 dispatch_once(&onceToken, ^{
120 lifecycleObserver = [SDL_LifecycleObserver new];
121 });
122 [lifecycleObserver update];
123}
124
125void SDL_SetiOSEventPump(bool enabled)
126{
127 UIKit_EventPumpEnabled = enabled;
128
129 SDL_UpdateLifecycleObserver();
130}
131
132Uint64 UIKit_GetEventTimestamp(NSTimeInterval nsTimestamp)
133{
134 static Uint64 timestamp_offset;
135 Uint64 timestamp = (Uint64)(nsTimestamp * SDL_NS_PER_SECOND);
136 Uint64 now = SDL_GetTicksNS();
137
138 if (!timestamp_offset) {
139 timestamp_offset = (now - timestamp);
140 }
141 timestamp += timestamp_offset;
142
143 if (timestamp > now) {
144 timestamp_offset -= (timestamp - now);
145 timestamp = now;
146 }
147 return timestamp;
148}
149
150void UIKit_PumpEvents(SDL_VideoDevice *_this)
151{
152 if (!UIKit_EventPumpEnabled) {
153 return;
154 }
155
156 /* Let the run loop run for a short amount of time: long enough for
157 touch events to get processed (which is important to get certain
158 elements of Game Center's GKLeaderboardViewController to respond
159 to touch input), but not long enough to introduce a significant
160 delay in the rest of the app.
161 */
162 const CFTimeInterval seconds = 0.000002;
163
164 // Pump most event types.
165 SInt32 result;
166 do {
167 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, TRUE);
168 } while (result == kCFRunLoopRunHandledSource);
169
170 // Make sure UIScrollView objects scroll properly.
171 do {
172 result = CFRunLoopRunInMode((CFStringRef)UITrackingRunLoopMode, seconds, TRUE);
173 } while (result == kCFRunLoopRunHandledSource);
174
175 // See the comment in the function definition.
176#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
177 UIKit_GL_RestoreCurrentContext();
178#endif
179}
180
181static id keyboard_connect_observer = nil;
182static id keyboard_disconnect_observer = nil;
183
184static void OnGCKeyboardConnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
185{
186 SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard;
187
188 SDL_AddKeyboard(keyboardID, NULL, true);
189
190 keyboard.keyboardInput.keyChangedHandler = ^(GCKeyboardInput *kbrd, GCControllerButtonInput *key, GCKeyCode keyCode, BOOL pressed) {
191 Uint64 timestamp = SDL_GetTicksNS();
192 SDL_SendKeyboardKey(timestamp, keyboardID, 0, (SDL_Scancode)keyCode, pressed);
193 };
194
195 dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.keyboard", DISPATCH_QUEUE_SERIAL);
196 dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
197 keyboard.handlerQueue = queue;
198}
199
200static void OnGCKeyboardDisconnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
201{
202 SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard;
203
204 SDL_RemoveKeyboard(keyboardID, true);
205
206 keyboard.keyboardInput.keyChangedHandler = nil;
207}
208
209void SDL_InitGCKeyboard(void)
210{
211 @autoreleasepool {
212 if (@available(iOS 14.0, tvOS 14.0, *)) {
213 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
214
215 keyboard_connect_observer = [center addObserverForName:GCKeyboardDidConnectNotification
216 object:nil
217 queue:nil
218 usingBlock:^(NSNotification *note) {
219 GCKeyboard *keyboard = note.object;
220 OnGCKeyboardConnected(keyboard);
221 }];
222
223 keyboard_disconnect_observer = [center addObserverForName:GCKeyboardDidDisconnectNotification
224 object:nil
225 queue:nil
226 usingBlock:^(NSNotification *note) {
227 GCKeyboard *keyboard = note.object;
228 OnGCKeyboardDisconnected(keyboard);
229 }];
230
231 if (GCKeyboard.coalescedKeyboard != nil) {
232 OnGCKeyboardConnected(GCKeyboard.coalescedKeyboard);
233 }
234 }
235 }
236}
237
238void SDL_QuitGCKeyboard(void)
239{
240 @autoreleasepool {
241 if (@available(iOS 14.0, tvOS 14.0, *)) {
242 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
243
244 if (keyboard_connect_observer) {
245 [center removeObserver:keyboard_connect_observer name:GCKeyboardDidConnectNotification object:nil];
246 keyboard_connect_observer = nil;
247 }
248
249 if (keyboard_disconnect_observer) {
250 [center removeObserver:keyboard_disconnect_observer name:GCKeyboardDidDisconnectNotification object:nil];
251 keyboard_disconnect_observer = nil;
252 }
253
254 if (GCKeyboard.coalescedKeyboard != nil) {
255 OnGCKeyboardDisconnected(GCKeyboard.coalescedKeyboard);
256 }
257 }
258 }
259}
260
261static id mouse_connect_observer = nil;
262static id mouse_disconnect_observer = nil;
263static bool mouse_relative_mode = false;
264static SDL_MouseWheelDirection mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL;
265
266static void UpdateScrollDirection(void)
267{
268#if 0 // This code doesn't work for some reason
269 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
270 if ([userDefaults boolForKey:@"com.apple.swipescrolldirection"]) {
271 mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED;
272 } else {
273 mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL;
274 }
275#else
276 Boolean keyExistsAndHasValidFormat = NO;
277 Boolean naturalScrollDirection = CFPreferencesGetAppBooleanValue(CFSTR("com.apple.swipescrolldirection"), kCFPreferencesAnyApplication, &keyExistsAndHasValidFormat);
278 if (!keyExistsAndHasValidFormat) {
279 // Couldn't read the preference, assume natural scrolling direction
280 naturalScrollDirection = YES;
281 }
282 if (naturalScrollDirection) {
283 mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED;
284 } else {
285 mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL;
286 }
287#endif
288}
289
290static void UpdatePointerLock(void)
291{
292 SDL_VideoDevice *_this = SDL_GetVideoDevice();
293 SDL_Window *window;
294
295 for (window = _this->windows; window != NULL; window = window->next) {
296 UIKit_UpdatePointerLock(_this, window);
297 }
298}
299
300static bool SetGCMouseRelativeMode(bool enabled)
301{
302 mouse_relative_mode = enabled;
303 UpdatePointerLock();
304 return true;
305}
306
307static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL pressed)
308{
309 Uint64 timestamp = SDL_GetTicksNS();
310 SDL_SendMouseButton(timestamp, SDL_GetMouseFocus(), mouseID, button, pressed);
311}
312
313static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
314{
315 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse;
316
317 SDL_AddMouse(mouseID, NULL, true);
318
319 mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
320 OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed);
321 };
322 mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
323 OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed);
324 };
325 mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
326 OnGCMouseButtonChanged(mouseID, SDL_BUTTON_RIGHT, pressed);
327 };
328
329 int auxiliary_button = SDL_BUTTON_X1;
330 for (GCControllerButtonInput *btn in mouse.mouseInput.auxiliaryButtons) {
331 btn.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
332 OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed);
333 };
334 ++auxiliary_button;
335 }
336
337 mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput *mouseInput, float deltaX, float deltaY) {
338 Uint64 timestamp = SDL_GetTicksNS();
339
340 if (SDL_GCMouseRelativeMode()) {
341 SDL_SendMouseMotion(timestamp, SDL_GetMouseFocus(), mouseID, true, deltaX, -deltaY);
342 }
343 };
344
345 mouse.mouseInput.scroll.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
346 Uint64 timestamp = SDL_GetTicksNS();
347
348 /* Raw scroll values come in here, vertical values in the first axis, horizontal values in the second axis.
349 * The vertical values are negative moving the mouse wheel up and positive moving it down.
350 * The horizontal values are negative moving the mouse wheel left and positive moving it right.
351 * The vertical values are inverted compared to SDL, and the horizontal values are as expected.
352 */
353 float vertical = -xValue;
354 float horizontal = yValue;
355
356 if (mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) {
357 // Since these are raw values, we need to flip them ourselves
358 vertical = -vertical;
359 horizontal = -horizontal;
360 }
361 SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), mouseID, horizontal, vertical, mouse_scroll_direction);
362 };
363 UpdateScrollDirection();
364
365 dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.mouse", DISPATCH_QUEUE_SERIAL);
366 dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
367 mouse.handlerQueue = queue;
368
369 UpdatePointerLock();
370}
371
372static void OnGCMouseDisconnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
373{
374 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse;
375
376 mouse.mouseInput.mouseMovedHandler = nil;
377
378 mouse.mouseInput.leftButton.pressedChangedHandler = nil;
379 mouse.mouseInput.middleButton.pressedChangedHandler = nil;
380 mouse.mouseInput.rightButton.pressedChangedHandler = nil;
381
382 for (GCControllerButtonInput *button in mouse.mouseInput.auxiliaryButtons) {
383 button.pressedChangedHandler = nil;
384 }
385
386 UpdatePointerLock();
387
388 SDL_RemoveMouse(mouseID, true);
389}
390
391void SDL_InitGCMouse(void)
392{
393 @autoreleasepool {
394 // There is a bug where mouse accumulates duplicate deltas over time in iOS 14.0
395 if (@available(iOS 14.1, tvOS 14.1, *)) {
396 /* iOS will not send the new pointer touch events if you don't have this key,
397 * and we need them to differentiate between mouse events and real touch events.
398 */
399 BOOL indirect_input_available = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"UIApplicationSupportsIndirectInputEvents"] boolValue];
400 if (indirect_input_available) {
401 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
402
403 mouse_connect_observer = [center addObserverForName:GCMouseDidConnectNotification
404 object:nil
405 queue:nil
406 usingBlock:^(NSNotification *note) {
407 GCMouse *mouse = note.object;
408 OnGCMouseConnected(mouse);
409 }];
410
411 mouse_disconnect_observer = [center addObserverForName:GCMouseDidDisconnectNotification
412 object:nil
413 queue:nil
414 usingBlock:^(NSNotification *note) {
415 GCMouse *mouse = note.object;
416 OnGCMouseDisconnected(mouse);
417 }];
418
419 for (GCMouse *mouse in [GCMouse mice]) {
420 OnGCMouseConnected(mouse);
421 }
422
423 SDL_GetMouse()->SetRelativeMouseMode = SetGCMouseRelativeMode;
424 } else {
425 NSLog(@"You need UIApplicationSupportsIndirectInputEvents in your Info.plist for mouse support");
426 }
427 }
428 }
429}
430
431bool SDL_GCMouseRelativeMode(void)
432{
433 return mouse_relative_mode;
434}
435
436void SDL_QuitGCMouse(void)
437{
438 @autoreleasepool {
439 if (@available(iOS 14.1, tvOS 14.1, *)) {
440 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
441
442 if (mouse_connect_observer) {
443 [center removeObserver:mouse_connect_observer name:GCMouseDidConnectNotification object:nil];
444 mouse_connect_observer = nil;
445 }
446
447 if (mouse_disconnect_observer) {
448 [center removeObserver:mouse_disconnect_observer name:GCMouseDidDisconnectNotification object:nil];
449 mouse_disconnect_observer = nil;
450 }
451
452 for (GCMouse *mouse in [GCMouse mice]) {
453 OnGCMouseDisconnected(mouse);
454 }
455
456 SDL_GetMouse()->SetRelativeMouseMode = NULL;
457 }
458 }
459}
460
461#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.h
new file mode 100644
index 0000000..5c21f7c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.h
@@ -0,0 +1,28 @@
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
25extern bool UIKit_ShowingMessageBox(void);
26extern bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID);
27
28#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.m
new file mode 100644
index 0000000..a57b3f7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmessagebox.m
@@ -0,0 +1,154 @@
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_uikitvideo.h"
26#include "SDL_uikitwindow.h"
27
28// Display a UIKit message box
29
30static bool s_showingMessageBox = false;
31
32bool UIKit_ShowingMessageBox(void)
33{
34 return s_showingMessageBox;
35}
36
37static void UIKit_WaitUntilMessageBoxClosed(const SDL_MessageBoxData *messageboxdata, int *clickedindex)
38{
39 *clickedindex = messageboxdata->numbuttons;
40
41 @autoreleasepool {
42 // Run the main event loop until the alert has finished
43 // Note that this needs to be done on the main thread
44 s_showingMessageBox = true;
45 while ((*clickedindex) == messageboxdata->numbuttons) {
46 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
47 }
48 s_showingMessageBox = false;
49 }
50}
51
52static BOOL UIKit_ShowMessageBoxAlertController(const SDL_MessageBoxData *messageboxdata, int *buttonID)
53{
54 int i;
55 int __block clickedindex = messageboxdata->numbuttons;
56 UIWindow *window = nil;
57 UIWindow *alertwindow = nil;
58
59 if (![UIAlertController class]) {
60 return NO;
61 }
62
63 UIAlertController *alert;
64 alert = [UIAlertController alertControllerWithTitle:@(messageboxdata->title)
65 message:@(messageboxdata->message)
66 preferredStyle:UIAlertControllerStyleAlert];
67
68 for (i = 0; i < messageboxdata->numbuttons; i++) {
69 UIAlertAction *action;
70 UIAlertActionStyle style = UIAlertActionStyleDefault;
71 const SDL_MessageBoxButtonData *sdlButton;
72
73 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) {
74 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i];
75 } else {
76 sdlButton = &messageboxdata->buttons[i];
77 }
78
79 if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
80 style = UIAlertActionStyleCancel;
81 }
82
83 action = [UIAlertAction actionWithTitle:@(sdlButton->text)
84 style:style
85 handler:^(UIAlertAction *alertAction) {
86 clickedindex = (int)(sdlButton - messageboxdata->buttons);
87 }];
88 [alert addAction:action];
89
90 if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
91 alert.preferredAction = action;
92 }
93 }
94
95 if (messageboxdata->window) {
96 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)messageboxdata->window->internal;
97 window = data.uiwindow;
98 }
99
100 if (window == nil || window.rootViewController == nil) {
101#ifdef SDL_PLATFORM_VISIONOS
102 alertwindow = [[UIWindow alloc] init];
103#else
104 alertwindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
105#endif
106 alertwindow.rootViewController = [UIViewController new];
107 alertwindow.windowLevel = UIWindowLevelAlert;
108
109 window = alertwindow;
110
111 [alertwindow makeKeyAndVisible];
112 }
113
114 [window.rootViewController presentViewController:alert animated:YES completion:nil];
115 UIKit_WaitUntilMessageBoxClosed(messageboxdata, &clickedindex);
116
117 if (alertwindow) {
118 alertwindow.hidden = YES;
119 }
120
121 UIKit_ForceUpdateHomeIndicator();
122
123 *buttonID = messageboxdata->buttons[clickedindex].buttonID;
124 return YES;
125}
126
127static void UIKit_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonID, int *result)
128{
129 @autoreleasepool {
130 if (UIKit_ShowMessageBoxAlertController(messageboxdata, buttonID)) {
131 *result = true;
132 } else {
133 *result = SDL_SetError("Could not show message box.");
134 }
135 }
136}
137
138bool UIKit_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
139{
140 @autoreleasepool {
141 __block int result = true;
142
143 if ([NSThread isMainThread]) {
144 UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result);
145 } else {
146 dispatch_sync(dispatch_get_main_queue(), ^{
147 UIKit_ShowMessageBoxImpl(messageboxdata, buttonID, &result);
148 });
149 }
150 return result;
151 }
152}
153
154#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.h
new file mode 100644
index 0000000..20bcf7c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.h
@@ -0,0 +1,54 @@
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
22/*
23 * @author Mark Callow, www.edgewise-consulting.com.
24 *
25 * Thanks to @slime73 on GitHub for their gist showing how to add a CAMetalLayer
26 * backed view.
27 */
28
29#ifndef SDL_uikitmetalview_h_
30#define SDL_uikitmetalview_h_
31
32#include "../SDL_sysvideo.h"
33#include "SDL_uikitwindow.h"
34
35#if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL))
36
37#import <UIKit/UIKit.h>
38#import <Metal/Metal.h>
39#import <QuartzCore/CAMetalLayer.h>
40
41@interface SDL_uikitmetalview : SDL_uikitview
42
43- (instancetype)initWithFrame:(CGRect)frame
44 scale:(CGFloat)scale;
45
46@end
47
48SDL_MetalView UIKit_Metal_CreateView(SDL_VideoDevice *_this, SDL_Window *window);
49void UIKit_Metal_DestroyView(SDL_VideoDevice *_this, SDL_MetalView view);
50void *UIKit_Metal_GetLayer(SDL_VideoDevice *_this, SDL_MetalView view);
51
52#endif // SDL_VIDEO_DRIVER_UIKIT && (SDL_VIDEO_VULKAN || SDL_VIDEO_METAL)
53
54#endif // SDL_uikitmetalview_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.m
new file mode 100644
index 0000000..010dae4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmetalview.m
@@ -0,0 +1,140 @@
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
22/*
23 * @author Mark Callow, www.edgewise-consulting.com.
24 *
25 * Thanks to @slime73 on GitHub for their gist showing how to add a CAMetalLayer
26 * backed view.
27 */
28
29#include "SDL_internal.h"
30
31#if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_VULKAN) || defined(SDL_VIDEO_METAL))
32
33#include "../SDL_sysvideo.h"
34#include "../../events/SDL_windowevents_c.h"
35
36#import "SDL_uikitwindow.h"
37#import "SDL_uikitmetalview.h"
38
39@implementation SDL_uikitmetalview
40
41// Returns a Metal-compatible layer.
42+ (Class)layerClass
43{
44 return [CAMetalLayer class];
45}
46
47- (instancetype)initWithFrame:(CGRect)frame
48 scale:(CGFloat)scale
49{
50 if ((self = [super initWithFrame:frame])) {
51 self.tag = SDL_METALVIEW_TAG;
52 self.layer.contentsScale = scale;
53 [self updateDrawableSize];
54 }
55
56 return self;
57}
58
59// Set the size of the metal drawables when the view is resized.
60- (void)layoutSubviews
61{
62 [super layoutSubviews];
63 [self updateDrawableSize];
64}
65
66- (void)updateDrawableSize
67{
68 CGSize size = self.bounds.size;
69 size.width *= self.layer.contentsScale;
70 size.height *= self.layer.contentsScale;
71
72 CAMetalLayer *metallayer = ((CAMetalLayer *)self.layer);
73 if (metallayer.drawableSize.width != size.width ||
74 metallayer.drawableSize.height != size.height) {
75 metallayer.drawableSize = size;
76 SDL_SendWindowEvent([self getSDLWindow], SDL_EVENT_WINDOW_METAL_VIEW_RESIZED, 0, 0);
77 }
78}
79
80@end
81
82SDL_MetalView UIKit_Metal_CreateView(SDL_VideoDevice *_this, SDL_Window *window)
83{
84 @autoreleasepool {
85 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
86 CGFloat scale = 1.0;
87 SDL_uikitmetalview *metalview;
88
89 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
90 /* Set the scale to the natural scale factor of the screen - then
91 * the backing dimensions of the Metal view will match the pixel
92 * dimensions of the screen rather than the dimensions in points
93 * yielding high resolution on retine displays.
94 */
95#ifndef SDL_PLATFORM_VISIONOS
96 scale = data.uiwindow.screen.nativeScale;
97#else
98 // VisionOS doesn't use the concept of "nativeScale" like other iOS devices.
99 // We use a fixed scale factor of 2.0 to achieve better pixel density.
100 // This is because VisionOS presents a virtual 1280x720 "screen", but we need
101 // to render at a higher resolution for optimal visual quality.
102 // TODO: Consider making this configurable or determining it dynamically
103 // based on the specific visionOS device capabilities.
104 scale = 2.0;
105#endif
106 }
107
108 metalview = [[SDL_uikitmetalview alloc] initWithFrame:data.uiwindow.bounds
109 scale:scale];
110 if (metalview == nil) {
111 SDL_OutOfMemory();
112 return NULL;
113 }
114
115 [metalview setSDLWindow:window];
116
117 return (void *)CFBridgingRetain(metalview);
118 }
119}
120
121void UIKit_Metal_DestroyView(SDL_VideoDevice *_this, SDL_MetalView view)
122{
123 @autoreleasepool {
124 SDL_uikitmetalview *metalview = CFBridgingRelease(view);
125
126 if ([metalview isKindOfClass:[SDL_uikitmetalview class]]) {
127 [metalview setSDLWindow:NULL];
128 }
129 }
130}
131
132void *UIKit_Metal_GetLayer(SDL_VideoDevice *_this, SDL_MetalView view)
133{
134 @autoreleasepool {
135 SDL_uikitview *uiview = (__bridge SDL_uikitview *)view;
136 return (__bridge void *)uiview.layer;
137 }
138}
139
140#endif // SDL_VIDEO_DRIVER_UIKIT && (SDL_VIDEO_VULKAN || SDL_VIDEO_METAL)
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.h
new file mode 100644
index 0000000..1b25cfd
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.h
@@ -0,0 +1,67 @@
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#ifndef SDL_uikitmodes_h_
24#define SDL_uikitmodes_h_
25
26#include "SDL_uikitvideo.h"
27
28@interface SDL_UIKitDisplayData : NSObject
29
30#ifndef SDL_PLATFORM_VISIONOS
31- (instancetype)initWithScreen:(UIScreen *)screen;
32@property(nonatomic, strong) UIScreen *uiscreen;
33#endif
34
35@end
36
37@interface SDL_UIKitDisplayModeData : NSObject
38#ifndef SDL_PLATFORM_VISIONOS
39@property(nonatomic, strong) UIScreenMode *uiscreenmode;
40#endif
41
42@end
43
44#ifndef SDL_PLATFORM_VISIONOS
45extern bool UIKit_IsDisplayLandscape(UIScreen *uiscreen);
46#endif
47
48extern bool UIKit_InitModes(SDL_VideoDevice *_this);
49#ifndef SDL_PLATFORM_VISIONOS
50extern bool UIKit_AddDisplay(UIScreen *uiscreen, bool send_event);
51extern void UIKit_DelDisplay(UIScreen *uiscreen, bool send_event);
52#endif
53extern bool UIKit_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
54extern bool UIKit_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
55extern void UIKit_QuitModes(SDL_VideoDevice *_this);
56extern bool UIKit_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
57
58// because visionOS does not have a screen
59// we create a fake display to maintain compatibility.
60// By default, a window measures 1280x720 pt.
61// https://developer.apple.com/design/human-interface-guidelines/windows#visionOS
62#ifdef SDL_PLATFORM_VISIONOS
63#define SDL_XR_SCREENWIDTH 1280
64#define SDL_XR_SCREENHEIGHT 720
65#endif
66
67#endif // SDL_uikitmodes_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m
new file mode 100644
index 0000000..d3247db
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m
@@ -0,0 +1,543 @@
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_uikitmodes.h"
26
27#include "../../events/SDL_events_c.h"
28
29#import <sys/utsname.h>
30
31@implementation SDL_UIKitDisplayData
32
33#ifndef SDL_PLATFORM_VISIONOS
34- (instancetype)initWithScreen:(UIScreen *)screen
35{
36 if (self = [super init]) {
37 self.uiscreen = screen;
38 }
39 return self;
40}
41@synthesize uiscreen;
42#endif
43
44@end
45
46@implementation SDL_UIKitDisplayModeData
47
48#ifndef SDL_PLATFORM_VISIONOS
49@synthesize uiscreenmode;
50#endif
51
52@end
53
54@interface SDL_DisplayWatch : NSObject
55@end
56
57#ifndef SDL_PLATFORM_VISIONOS
58@implementation SDL_DisplayWatch
59
60+ (void)start
61{
62 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
63
64 [center addObserver:self
65 selector:@selector(screenConnected:)
66 name:UIScreenDidConnectNotification
67 object:nil];
68 [center addObserver:self
69 selector:@selector(screenDisconnected:)
70 name:UIScreenDidDisconnectNotification
71 object:nil];
72}
73
74+ (void)stop
75{
76 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
77
78 [center removeObserver:self
79 name:UIScreenDidConnectNotification
80 object:nil];
81 [center removeObserver:self
82 name:UIScreenDidDisconnectNotification
83 object:nil];
84}
85
86+ (void)screenConnected:(NSNotification *)notification
87{
88 UIScreen *uiscreen = [notification object];
89 UIKit_AddDisplay(uiscreen, true);
90}
91
92+ (void)screenDisconnected:(NSNotification *)notification
93{
94 UIScreen *uiscreen = [notification object];
95 UIKit_DelDisplay(uiscreen, true);
96}
97
98@end
99#endif
100
101#ifndef SDL_PLATFORM_VISIONOS
102static bool UIKit_AllocateDisplayModeData(SDL_DisplayMode *mode,
103 UIScreenMode *uiscreenmode)
104{
105 SDL_UIKitDisplayModeData *data = nil;
106
107 if (uiscreenmode != nil) {
108 // Allocate the display mode data
109 data = [[SDL_UIKitDisplayModeData alloc] init];
110 if (!data) {
111 return SDL_OutOfMemory();
112 }
113
114 data.uiscreenmode = uiscreenmode;
115 }
116
117 mode->internal = (void *)CFBridgingRetain(data);
118
119 return true;
120}
121#endif
122
123static void UIKit_FreeDisplayModeData(SDL_DisplayMode *mode)
124{
125 if (mode->internal != NULL) {
126 CFRelease(mode->internal);
127 mode->internal = NULL;
128 }
129}
130
131#ifndef SDL_PLATFORM_VISIONOS
132static float UIKit_GetDisplayModeRefreshRate(UIScreen *uiscreen)
133{
134 return (float)uiscreen.maximumFramesPerSecond;
135}
136
137static bool UIKit_AddSingleDisplayMode(SDL_VideoDisplay *display, int w, int h,
138 UIScreen *uiscreen, UIScreenMode *uiscreenmode)
139{
140 SDL_DisplayMode mode;
141
142 SDL_zero(mode);
143 if (!UIKit_AllocateDisplayModeData(&mode, uiscreenmode)) {
144 return false;
145 }
146
147 mode.w = w;
148 mode.h = h;
149 mode.pixel_density = uiscreen.nativeScale;
150 mode.refresh_rate = UIKit_GetDisplayModeRefreshRate(uiscreen);
151 mode.format = SDL_PIXELFORMAT_ABGR8888;
152
153 if (SDL_AddFullscreenDisplayMode(display, &mode)) {
154 return true;
155 } else {
156 UIKit_FreeDisplayModeData(&mode);
157 return false;
158 }
159}
160
161static bool UIKit_AddDisplayMode(SDL_VideoDisplay *display, int w, int h,
162 UIScreen *uiscreen, UIScreenMode *uiscreenmode, bool addRotation)
163{
164 if (!UIKit_AddSingleDisplayMode(display, w, h, uiscreen, uiscreenmode)) {
165 return false;
166 }
167
168 if (addRotation) {
169 // Add the rotated version
170 if (!UIKit_AddSingleDisplayMode(display, h, w, uiscreen, uiscreenmode)) {
171 return false;
172 }
173 }
174
175 return true;
176}
177
178static CGSize GetUIScreenModeSize(UIScreen *uiscreen, UIScreenMode *mode)
179{
180 /* For devices such as iPhone 6/7/8 Plus, the UIScreenMode reported by iOS
181 * isn't the physical pixels of the display, but rather the point size times
182 * the scale. For example, on iOS 12.2 on iPhone 8 Plus the physical pixel
183 * resolution is 1080x1920, the size reported by mode.size is 1242x2208,
184 * the size in points is 414x736, the scale property is 3.0, and the
185 * nativeScale property is ~2.6087 (ie 1920.0 / 736.0).
186 *
187 * What we want for the mode size is the point size, and the pixel density
188 * is the native scale.
189 *
190 * Note that the iOS Simulator doesn't have this behavior for those devices.
191 * https://github.com/libsdl-org/SDL/issues/3220
192 */
193 CGSize size = mode.size;
194
195 size.width = SDL_round(size.width / uiscreen.scale);
196 size.height = SDL_round(size.height / uiscreen.scale);
197
198 return size;
199}
200
201bool UIKit_AddDisplay(UIScreen *uiscreen, bool send_event)
202{
203 UIScreenMode *uiscreenmode = uiscreen.currentMode;
204 CGSize size = GetUIScreenModeSize(uiscreen, uiscreenmode);
205 SDL_VideoDisplay display;
206 SDL_DisplayMode mode;
207
208 // Make sure the width/height are oriented correctly
209 if (UIKit_IsDisplayLandscape(uiscreen) != (size.width > size.height)) {
210 CGFloat height = size.width;
211 size.width = size.height;
212 size.height = height;
213 }
214
215 SDL_zero(mode);
216 mode.w = (int)size.width;
217 mode.h = (int)size.height;
218 mode.pixel_density = uiscreen.nativeScale;
219 mode.format = SDL_PIXELFORMAT_ABGR8888;
220 mode.refresh_rate = UIKit_GetDisplayModeRefreshRate(uiscreen);
221
222 if (!UIKit_AllocateDisplayModeData(&mode, uiscreenmode)) {
223 return false;
224 }
225
226 SDL_zero(display);
227#ifndef SDL_PLATFORM_TVOS
228 if (uiscreen == [UIScreen mainScreen]) {
229 // The natural orientation (used by sensors) is portrait
230 display.natural_orientation = SDL_ORIENTATION_PORTRAIT;
231 } else
232#endif
233 if (UIKit_IsDisplayLandscape(uiscreen)) {
234 display.natural_orientation = SDL_ORIENTATION_LANDSCAPE;
235 } else {
236 display.natural_orientation = SDL_ORIENTATION_PORTRAIT;
237 }
238 display.desktop_mode = mode;
239
240 display.HDR.SDR_white_level = 1.0f;
241 display.HDR.HDR_headroom = 1.0f;
242
243#ifndef SDL_PLATFORM_TVOS
244 if (@available(iOS 16.0, *)) {
245 if (uiscreen.currentEDRHeadroom > 1.0f) {
246 display.HDR.HDR_headroom = uiscreen.currentEDRHeadroom;
247 } else {
248 display.HDR.HDR_headroom = uiscreen.potentialEDRHeadroom;
249 }
250 }
251#endif // !SDL_PLATFORM_TVOS
252
253 // Allocate the display data
254#ifdef SDL_PLATFORM_VISIONOS
255 SDL_UIKitDisplayData *data = [[SDL_UIKitDisplayData alloc] init];
256#else
257 SDL_UIKitDisplayData *data = [[SDL_UIKitDisplayData alloc] initWithScreen:uiscreen];
258#endif
259 if (!data) {
260 UIKit_FreeDisplayModeData(&display.desktop_mode);
261 return SDL_OutOfMemory();
262 }
263
264 display.internal = (SDL_DisplayData *)CFBridgingRetain(data);
265 if (SDL_AddVideoDisplay(&display, send_event) == 0) {
266 return false;
267 }
268 return true;
269}
270#endif
271
272#ifdef SDL_PLATFORM_VISIONOS
273bool UIKit_AddDisplay(bool send_event){
274 CGSize size = CGSizeMake(SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT);
275 SDL_VideoDisplay display;
276 SDL_DisplayMode mode;
277
278 SDL_zero(mode);
279 mode.w = (int)size.width;
280 mode.h = (int)size.height;
281 mode.pixel_density = 1;
282 mode.format = SDL_PIXELFORMAT_ABGR8888;
283 mode.refresh_rate = 60.0f;
284
285 display.natural_orientation = SDL_ORIENTATION_LANDSCAPE;
286
287 display.desktop_mode = mode;
288
289 SDL_UIKitDisplayData *data = [[SDL_UIKitDisplayData alloc] init];
290
291 if (!data) {
292 UIKit_FreeDisplayModeData(&display.desktop_mode);
293 return SDL_OutOfMemory();
294 }
295
296 display.internal = (SDL_DisplayData *)CFBridgingRetain(data);
297 if (SDL_AddVideoDisplay(&display, send_event) == 0) {
298 return false;
299 }
300 return true;
301}
302#endif
303
304#ifndef SDL_PLATFORM_VISIONOS
305
306void UIKit_DelDisplay(UIScreen *uiscreen, bool send_event)
307{
308 SDL_DisplayID *displays;
309 int i;
310
311 displays = SDL_GetDisplays(NULL);
312 if (displays) {
313 for (i = 0; displays[i]; ++i) {
314 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
315 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal;
316
317 if (data && data.uiscreen == uiscreen) {
318 CFRelease(display->internal);
319 display->internal = NULL;
320 SDL_DelVideoDisplay(displays[i], send_event);
321 break;
322 }
323 }
324 SDL_free(displays);
325 }
326}
327
328bool UIKit_IsDisplayLandscape(UIScreen *uiscreen)
329{
330#ifndef SDL_PLATFORM_TVOS
331 if (uiscreen == [UIScreen mainScreen]) {
332 return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
333 } else
334#endif // !SDL_PLATFORM_TVOS
335 {
336 CGSize size = uiscreen.bounds.size;
337 return (size.width > size.height);
338 }
339}
340#endif
341bool UIKit_InitModes(SDL_VideoDevice *_this)
342{
343 @autoreleasepool {
344#ifdef SDL_PLATFORM_VISIONOS
345 UIKit_AddDisplay(false);
346#else
347 for (UIScreen *uiscreen in [UIScreen screens]) {
348 if (!UIKit_AddDisplay(uiscreen, false)) {
349 return false;
350 }
351 }
352#endif
353
354#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
355 SDL_OnApplicationDidChangeStatusBarOrientation();
356#endif
357
358#ifndef SDL_PLATFORM_VISIONOS
359 [SDL_DisplayWatch start];
360#endif
361 }
362
363 return true;
364}
365
366bool UIKit_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
367{
368#ifndef SDL_PLATFORM_VISIONOS
369 @autoreleasepool {
370 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal;
371
372 bool isLandscape = UIKit_IsDisplayLandscape(data.uiscreen);
373 bool addRotation = (data.uiscreen == [UIScreen mainScreen]);
374 NSArray *availableModes = nil;
375
376#ifdef SDL_PLATFORM_TVOS
377 addRotation = false;
378 availableModes = @[ data.uiscreen.currentMode ];
379#else
380 availableModes = data.uiscreen.availableModes;
381#endif
382
383 for (UIScreenMode *uimode in availableModes) {
384 CGSize size = GetUIScreenModeSize(data.uiscreen, uimode);
385 int w = (int)size.width;
386 int h = (int)size.height;
387
388 // Make sure the width/height are oriented correctly
389 if (isLandscape != (w > h)) {
390 int tmp = w;
391 w = h;
392 h = tmp;
393 }
394
395 UIKit_AddDisplayMode(display, w, h, data.uiscreen, uimode, addRotation);
396 }
397 }
398#endif
399 return true;
400}
401
402bool UIKit_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
403{
404#ifndef SDL_PLATFORM_VISIONOS
405 @autoreleasepool {
406 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal;
407
408#ifndef SDL_PLATFORM_TVOS
409 SDL_UIKitDisplayModeData *modedata = (__bridge SDL_UIKitDisplayModeData *)mode->internal;
410 [data.uiscreen setCurrentMode:modedata.uiscreenmode];
411#endif
412
413 if (data.uiscreen == [UIScreen mainScreen]) {
414 /* [UIApplication setStatusBarOrientation:] no longer works reliably
415 * in recent iOS versions, so we can't rotate the screen when setting
416 * the display mode. */
417 if (mode->w > mode->h) {
418 if (!UIKit_IsDisplayLandscape(data.uiscreen)) {
419 return SDL_SetError("Screen orientation does not match display mode size");
420 }
421 } else if (mode->w < mode->h) {
422 if (UIKit_IsDisplayLandscape(data.uiscreen)) {
423 return SDL_SetError("Screen orientation does not match display mode size");
424 }
425 }
426 }
427 }
428#endif
429 return true;
430}
431
432bool UIKit_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
433{
434 @autoreleasepool {
435 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal;
436#ifdef SDL_PLATFORM_VISIONOS
437 CGRect frame = CGRectMake(0, 0, SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT);
438#else
439 CGRect frame = data.uiscreen.bounds;
440#endif
441
442 /* the default function iterates displays to make a fake offset,
443 as if all the displays were side-by-side, which is fine for iOS. */
444 if (!SDL_GetDisplayBounds(display->id, rect)) {
445 return false;
446 }
447
448 rect->x += (int)frame.origin.x;
449 rect->y += (int)frame.origin.y;
450 rect->w = (int)frame.size.width;
451 rect->h = (int)frame.size.height;
452 }
453
454 return true;
455}
456
457void UIKit_QuitModes(SDL_VideoDevice *_this)
458{
459#ifndef SDL_PLATFORM_VISIONOS
460 [SDL_DisplayWatch stop];
461#endif
462
463 // Release Objective-C objects, so higher level doesn't free() them.
464 int i, j;
465 @autoreleasepool {
466 for (i = 0; i < _this->num_displays; i++) {
467 SDL_VideoDisplay *display = _this->displays[i];
468
469 UIKit_FreeDisplayModeData(&display->desktop_mode);
470 for (j = 0; j < display->num_fullscreen_modes; j++) {
471 SDL_DisplayMode *mode = &display->fullscreen_modes[j];
472 UIKit_FreeDisplayModeData(mode);
473 }
474
475 if (display->internal != NULL) {
476 CFRelease(display->internal);
477 display->internal = NULL;
478 }
479 }
480 }
481}
482
483#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
484void SDL_OnApplicationDidChangeStatusBarOrientation(void)
485{
486 BOOL isLandscape = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
487 SDL_VideoDisplay *display = SDL_GetVideoDisplay(SDL_GetPrimaryDisplay());
488
489 if (display) {
490 SDL_DisplayMode *mode = &display->desktop_mode;
491 SDL_DisplayOrientation orientation = SDL_ORIENTATION_UNKNOWN;
492 int i;
493
494 /* The desktop display mode should be kept in sync with the screen
495 * orientation so that updating a window's fullscreen state to
496 * fullscreen desktop keeps the window dimensions in the
497 * correct orientation. */
498 if (isLandscape != (mode->w > mode->h)) {
499 SDL_DisplayMode new_mode;
500 SDL_copyp(&new_mode, mode);
501 new_mode.w = mode->h;
502 new_mode.h = mode->w;
503
504 // Make sure we don't free the current display mode data
505 mode->internal = NULL;
506
507 SDL_SetDesktopDisplayMode(display, &new_mode);
508 }
509
510 // Same deal with the fullscreen modes
511 for (i = 0; i < display->num_fullscreen_modes; ++i) {
512 mode = &display->fullscreen_modes[i];
513 if (isLandscape != (mode->w > mode->h)) {
514 int height = mode->w;
515 mode->w = mode->h;
516 mode->h = height;
517 }
518 }
519
520 switch ([UIApplication sharedApplication].statusBarOrientation) {
521 case UIInterfaceOrientationPortrait:
522 orientation = SDL_ORIENTATION_PORTRAIT;
523 break;
524 case UIInterfaceOrientationPortraitUpsideDown:
525 orientation = SDL_ORIENTATION_PORTRAIT_FLIPPED;
526 break;
527 case UIInterfaceOrientationLandscapeLeft:
528 // Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046
529 orientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED;
530 break;
531 case UIInterfaceOrientationLandscapeRight:
532 // Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046
533 orientation = SDL_ORIENTATION_LANDSCAPE;
534 break;
535 default:
536 break;
537 }
538 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ORIENTATION, orientation, 0);
539 }
540}
541#endif // !SDL_PLATFORM_TVOS
542
543#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.h
new file mode 100644
index 0000000..cc259f7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.h
@@ -0,0 +1,40 @@
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#ifndef SDL_uikitopengles_
22#define SDL_uikitopengles_
23
24#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
25
26#include "../SDL_sysvideo.h"
27
28extern bool UIKit_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window,
29 SDL_GLContext context);
30extern bool UIKit_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window);
31extern SDL_GLContext UIKit_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window);
32extern bool UIKit_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context);
33extern SDL_FunctionPointer UIKit_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc);
34extern bool UIKit_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path);
35
36extern void UIKit_GL_RestoreCurrentContext(void);
37
38#endif // SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
39
40#endif // SDL_uikitopengles_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.m
new file mode 100644
index 0000000..a73588b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopengles.m
@@ -0,0 +1,221 @@
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#if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2))
24
25#include "SDL_uikitopengles.h"
26#import "SDL_uikitopenglview.h"
27#include "SDL_uikitmodes.h"
28#include "SDL_uikitwindow.h"
29#include "SDL_uikitevents.h"
30#include "../SDL_sysvideo.h"
31#include "../../events/SDL_keyboard_c.h"
32#include "../../events/SDL_mouse_c.h"
33#include "../../power/uikit/SDL_syspower.h"
34#include <dlfcn.h>
35
36@interface SDLEAGLContext : EAGLContext
37
38// The OpenGL ES context owns a view / drawable.
39@property(nonatomic, strong) SDL_uikitopenglview *sdlView;
40
41@end
42
43@implementation SDLEAGLContext
44
45- (void)dealloc
46{
47 /* When the context is deallocated, its view should be removed from any
48 * SDL window that it's attached to. */
49 [self.sdlView setSDLWindow:NULL];
50}
51
52@end
53
54SDL_FunctionPointer UIKit_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc)
55{
56 /* Look through all SO's for the proc symbol. Here's why:
57 * -Looking for the path to the OpenGL Library seems not to work in the iOS Simulator.
58 * -We don't know that the path won't change in the future. */
59 return dlsym(RTLD_DEFAULT, proc);
60}
61
62/*
63 note that SDL_GL_DestroyContext makes it current without passing the window
64*/
65bool UIKit_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context)
66{
67 @autoreleasepool {
68 SDLEAGLContext *eaglcontext = (__bridge SDLEAGLContext *)context;
69
70 if (![EAGLContext setCurrentContext:eaglcontext]) {
71 return SDL_SetError("Could not make EAGL context current");
72 }
73
74 if (eaglcontext) {
75 [eaglcontext.sdlView setSDLWindow:window];
76 }
77 }
78
79 return true;
80}
81
82bool UIKit_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path)
83{
84 /* We shouldn't pass a path to this function, since we've already loaded the
85 * library. */
86 if (path != NULL) {
87 return SDL_SetError("iOS GL Load Library just here for compatibility");
88 }
89 return true;
90}
91
92bool UIKit_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
93{
94 @autoreleasepool {
95 SDLEAGLContext *context = (__bridge SDLEAGLContext *)SDL_GL_GetCurrentContext();
96
97#ifdef SDL_POWER_UIKIT
98 // Check once a frame to see if we should turn off the battery monitor.
99 SDL_UIKit_UpdateBatteryMonitoring();
100#endif
101
102 [context.sdlView swapBuffers];
103
104 /* You need to pump events in order for the OS to make changes visible.
105 * We don't pump events here because we don't want iOS application events
106 * (low memory, terminate, etc.) to happen inside low level rendering. */
107 }
108 return true;
109}
110
111SDL_GLContext UIKit_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window)
112{
113 @autoreleasepool {
114 SDLEAGLContext *context = nil;
115 SDL_uikitopenglview *view;
116 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
117 CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
118 EAGLSharegroup *sharegroup = nil;
119 CGFloat scale = 1.0;
120 int samples = 0;
121 int major = _this->gl_config.major_version;
122 int minor = _this->gl_config.minor_version;
123
124 /* The EAGLRenderingAPI enum values currently map 1:1 to major GLES
125 * versions. */
126 EAGLRenderingAPI api = major;
127
128 // iOS currently doesn't support GLES >3.0.
129 if (major > 3 || (major == 3 && minor > 0)) {
130 SDL_SetError("OpenGL ES %d.%d context could not be created", major, minor);
131 return NULL;
132 }
133
134 if (_this->gl_config.multisamplebuffers > 0) {
135 samples = _this->gl_config.multisamplesamples;
136 }
137
138 if (_this->gl_config.share_with_current_context) {
139 EAGLContext *currContext = (__bridge EAGLContext *)SDL_GL_GetCurrentContext();
140 sharegroup = currContext.sharegroup;
141 }
142
143 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
144 /* Set the scale to the natural scale factor of the screen - the
145 * backing dimensions of the OpenGL view will match the pixel
146 * dimensions of the screen rather than the dimensions in points. */
147 scale = data.uiwindow.screen.nativeScale;
148 }
149
150 context = [[SDLEAGLContext alloc] initWithAPI:api sharegroup:sharegroup];
151 if (!context) {
152 SDL_SetError("OpenGL ES %d context could not be created", _this->gl_config.major_version);
153 return NULL;
154 }
155
156 // construct our view, passing in SDL's OpenGL configuration data
157 view = [[SDL_uikitopenglview alloc] initWithFrame:frame
158 scale:scale
159 retainBacking:_this->gl_config.retained_backing
160 rBits:_this->gl_config.red_size
161 gBits:_this->gl_config.green_size
162 bBits:_this->gl_config.blue_size
163 aBits:_this->gl_config.alpha_size
164 depthBits:_this->gl_config.depth_size
165 stencilBits:_this->gl_config.stencil_size
166 sRGB:_this->gl_config.framebuffer_srgb_capable
167 multisamples:samples
168 context:context];
169
170 if (!view) {
171 return NULL;
172 }
173
174 SDL_PropertiesID props = SDL_GetWindowProperties(window);
175 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_FRAMEBUFFER_NUMBER, view.drawableFramebuffer);
176 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_RENDERBUFFER_NUMBER, view.drawableRenderbuffer);
177 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_RESOLVE_FRAMEBUFFER_NUMBER, view.msaaResolveFramebuffer);
178
179 // The context owns the view / drawable.
180 context.sdlView = view;
181
182 if (!UIKit_GL_MakeCurrent(_this, window, (__bridge SDL_GLContext)context)) {
183 UIKit_GL_DestroyContext(_this, (SDL_GLContext)CFBridgingRetain(context));
184 return NULL;
185 }
186
187 /* We return a +1'd context. The window's internal owns the view (via
188 * MakeCurrent.) */
189 return (SDL_GLContext)CFBridgingRetain(context);
190 }
191}
192
193bool UIKit_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context)
194{
195 @autoreleasepool {
196 /* The context was retained in SDL_GL_CreateContext, so we release it
197 * here. The context's view will be detached from its window when the
198 * context is deallocated. */
199 CFRelease(context);
200 }
201 return true;
202}
203
204void UIKit_GL_RestoreCurrentContext(void)
205{
206 @autoreleasepool {
207 /* Some iOS system functionality (such as Dictation on the on-screen
208 keyboard) uses its own OpenGL ES context but doesn't restore the
209 previous one when it's done. This is a workaround to make sure the
210 expected SDL-created OpenGL ES context is active after the OS is
211 finished running its own code for the frame. If this isn't done, the
212 app may crash or have other nasty symptoms when Dictation is used.
213 */
214 EAGLContext *context = (__bridge EAGLContext *)SDL_GL_GetCurrentContext();
215 if (context != NULL && [EAGLContext currentContext] != context) {
216 [EAGLContext setCurrentContext:context];
217 }
218 }
219}
220
221#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.h
new file mode 100644
index 0000000..3286a16
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.h
@@ -0,0 +1,62 @@
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
22#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
23
24#import <UIKit/UIKit.h>
25#import <OpenGLES/EAGL.h>
26#import <OpenGLES/ES3/gl.h>
27
28#import "SDL_uikitview.h"
29#include "SDL_uikitvideo.h"
30
31@interface SDL_uikitopenglview : SDL_uikitview
32
33- (instancetype)initWithFrame:(CGRect)frame
34 scale:(CGFloat)scale
35 retainBacking:(BOOL)retained
36 rBits:(int)rBits
37 gBits:(int)gBits
38 bBits:(int)bBits
39 aBits:(int)aBits
40 depthBits:(int)depthBits
41 stencilBits:(int)stencilBits
42 sRGB:(BOOL)sRGB
43 multisamples:(int)multisamples
44 context:(EAGLContext *)glcontext;
45
46@property(nonatomic, readonly, weak) EAGLContext *context;
47
48// The width and height of the drawable in pixels (as opposed to points.)
49@property(nonatomic, readonly) int backingWidth;
50@property(nonatomic, readonly) int backingHeight;
51
52@property(nonatomic, readonly) GLuint drawableRenderbuffer;
53@property(nonatomic, readonly) GLuint drawableFramebuffer;
54@property(nonatomic, readonly) GLuint msaaResolveFramebuffer;
55
56- (void)swapBuffers;
57
58- (void)updateFrame;
59
60@end
61
62#endif // SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.m
new file mode 100644
index 0000000..71d167f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitopenglview.m
@@ -0,0 +1,377 @@
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#if defined(SDL_VIDEO_DRIVER_UIKIT) && (defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2))
24
25#include <OpenGLES/EAGLDrawable.h>
26#include <OpenGLES/ES2/glext.h>
27#import "SDL_uikitopenglview.h"
28#include "SDL_uikitwindow.h"
29
30@implementation SDL_uikitopenglview
31{
32 // The renderbuffer and framebuffer used to render to this layer.
33 GLuint viewRenderbuffer, viewFramebuffer;
34
35 // The depth buffer that is attached to viewFramebuffer, if it exists.
36 GLuint depthRenderbuffer;
37
38 GLenum colorBufferFormat;
39
40 // format of depthRenderbuffer
41 GLenum depthBufferFormat;
42
43 // The framebuffer and renderbuffer used for rendering with MSAA.
44 GLuint msaaFramebuffer, msaaRenderbuffer;
45
46 // The number of MSAA samples.
47 int samples;
48
49 BOOL retainedBacking;
50}
51
52@synthesize context;
53@synthesize backingWidth;
54@synthesize backingHeight;
55
56+ (Class)layerClass
57{
58 return [CAEAGLLayer class];
59}
60
61- (instancetype)initWithFrame:(CGRect)frame
62 scale:(CGFloat)scale
63 retainBacking:(BOOL)retained
64 rBits:(int)rBits
65 gBits:(int)gBits
66 bBits:(int)bBits
67 aBits:(int)aBits
68 depthBits:(int)depthBits
69 stencilBits:(int)stencilBits
70 sRGB:(BOOL)sRGB
71 multisamples:(int)multisamples
72 context:(EAGLContext *)glcontext
73{
74 if ((self = [super initWithFrame:frame])) {
75 const BOOL useStencilBuffer = (stencilBits != 0);
76 const BOOL useDepthBuffer = (depthBits != 0);
77 NSString *colorFormat = nil;
78
79 context = glcontext;
80 samples = multisamples;
81 retainedBacking = retained;
82
83 if (!context || ![EAGLContext setCurrentContext:context]) {
84 SDL_SetError("Could not create OpenGL ES drawable (could not make context current)");
85 return nil;
86 }
87
88 if (samples > 0) {
89 GLint maxsamples = 0;
90 glGetIntegerv(GL_MAX_SAMPLES, &maxsamples);
91
92 // Clamp the samples to the max supported count.
93 samples = SDL_min(samples, maxsamples);
94 }
95
96 if (sRGB) {
97 colorFormat = kEAGLColorFormatSRGBA8;
98 colorBufferFormat = GL_SRGB8_ALPHA8;
99 } else if (rBits >= 8 || gBits >= 8 || bBits >= 8 || aBits > 0) {
100 // if user specifically requests rbg888 or some color format higher than 16bpp
101 colorFormat = kEAGLColorFormatRGBA8;
102 colorBufferFormat = GL_RGBA8;
103 } else {
104 // default case (potentially faster)
105 colorFormat = kEAGLColorFormatRGB565;
106 colorBufferFormat = GL_RGB565;
107 }
108
109 CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
110
111 eaglLayer.opaque = YES;
112 eaglLayer.drawableProperties = @{
113 kEAGLDrawablePropertyRetainedBacking : @(retained),
114 kEAGLDrawablePropertyColorFormat : colorFormat
115 };
116
117 // Set the appropriate scale (for retina display support)
118 self.contentScaleFactor = scale;
119
120 // Create the color Renderbuffer Object
121 glGenRenderbuffers(1, &viewRenderbuffer);
122 glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
123
124 if (![context renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer]) {
125 SDL_SetError("Failed to create OpenGL ES drawable");
126 return nil;
127 }
128
129 // Create the Framebuffer Object
130 glGenFramebuffers(1, &viewFramebuffer);
131 glBindFramebuffer(GL_FRAMEBUFFER, viewFramebuffer);
132
133 // attach the color renderbuffer to the FBO
134 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderbuffer);
135
136 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
137 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
138
139 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
140 SDL_SetError("Failed creating OpenGL ES framebuffer");
141 return nil;
142 }
143
144 /* When MSAA is used we'll use a separate framebuffer for rendering to,
145 * since we'll need to do an explicit MSAA resolve before presenting. */
146 if (samples > 0) {
147 glGenFramebuffers(1, &msaaFramebuffer);
148 glBindFramebuffer(GL_FRAMEBUFFER, msaaFramebuffer);
149
150 glGenRenderbuffers(1, &msaaRenderbuffer);
151 glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer);
152
153 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight);
154
155 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRenderbuffer);
156 }
157
158 if (useDepthBuffer || useStencilBuffer) {
159 if (useStencilBuffer) {
160 // Apparently you need to pack stencil and depth into one buffer.
161 depthBufferFormat = GL_DEPTH24_STENCIL8_OES;
162 } else if (useDepthBuffer) {
163 /* iOS only uses 32-bit float (exposed as fixed point 24-bit)
164 * depth buffers. */
165 depthBufferFormat = GL_DEPTH_COMPONENT24_OES;
166 }
167
168 glGenRenderbuffers(1, &depthRenderbuffer);
169 glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
170
171 if (samples > 0) {
172 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight);
173 } else {
174 glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight);
175 }
176
177 if (useDepthBuffer) {
178 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
179 }
180 if (useStencilBuffer) {
181 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
182 }
183 }
184
185 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
186 SDL_SetError("Failed creating OpenGL ES framebuffer");
187 return nil;
188 }
189
190 glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
191
192 [self setDebugLabels];
193 }
194
195 return self;
196}
197
198- (GLuint)drawableRenderbuffer
199{
200 return viewRenderbuffer;
201}
202
203- (GLuint)drawableFramebuffer
204{
205 // When MSAA is used, the MSAA draw framebuffer is used for drawing.
206 if (msaaFramebuffer) {
207 return msaaFramebuffer;
208 } else {
209 return viewFramebuffer;
210 }
211}
212
213- (GLuint)msaaResolveFramebuffer
214{
215 /* When MSAA is used, the MSAA draw framebuffer is used for drawing and the
216 * view framebuffer is used as a MSAA resolve framebuffer. */
217 if (msaaFramebuffer) {
218 return viewFramebuffer;
219 } else {
220 return 0;
221 }
222}
223
224- (void)updateFrame
225{
226 GLint prevRenderbuffer = 0;
227 glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer);
228
229 glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
230 [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
231
232 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
233 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
234
235 if (msaaRenderbuffer != 0) {
236 glBindRenderbuffer(GL_RENDERBUFFER, msaaRenderbuffer);
237 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, colorBufferFormat, backingWidth, backingHeight);
238 }
239
240 if (depthRenderbuffer != 0) {
241 glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
242
243 if (samples > 0) {
244 glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depthBufferFormat, backingWidth, backingHeight);
245 } else {
246 glRenderbufferStorage(GL_RENDERBUFFER, depthBufferFormat, backingWidth, backingHeight);
247 }
248 }
249
250 glBindRenderbuffer(GL_RENDERBUFFER, prevRenderbuffer);
251}
252
253- (void)setDebugLabels
254{
255 if (viewFramebuffer != 0) {
256 glLabelObjectEXT(GL_FRAMEBUFFER, viewFramebuffer, 0, "context FBO");
257 }
258
259 if (viewRenderbuffer != 0) {
260 glLabelObjectEXT(GL_RENDERBUFFER, viewRenderbuffer, 0, "context color buffer");
261 }
262
263 if (depthRenderbuffer != 0) {
264 if (depthBufferFormat == GL_DEPTH24_STENCIL8_OES) {
265 glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth-stencil buffer");
266 } else {
267 glLabelObjectEXT(GL_RENDERBUFFER, depthRenderbuffer, 0, "context depth buffer");
268 }
269 }
270
271 if (msaaFramebuffer != 0) {
272 glLabelObjectEXT(GL_FRAMEBUFFER, msaaFramebuffer, 0, "context MSAA FBO");
273 }
274
275 if (msaaRenderbuffer != 0) {
276 glLabelObjectEXT(GL_RENDERBUFFER, msaaRenderbuffer, 0, "context MSAA renderbuffer");
277 }
278}
279
280- (void)swapBuffers
281{
282 if (msaaFramebuffer) {
283 const GLenum attachments[] = { GL_COLOR_ATTACHMENT0 };
284
285 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, viewFramebuffer);
286
287 /* OpenGL ES 3+ provides explicit MSAA resolves via glBlitFramebuffer.
288 * In OpenGL ES 1 and 2, MSAA resolves must be done via an extension. */
289 if (context.API >= kEAGLRenderingAPIOpenGLES3) {
290 int w = backingWidth;
291 int h = backingHeight;
292 glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
293
294 if (!retainedBacking) {
295 // Discard the contents of the MSAA drawable color buffer.
296 glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, attachments);
297 }
298 } else {
299 glResolveMultisampleFramebufferAPPLE();
300
301 if (!retainedBacking) {
302 glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, attachments);
303 }
304 }
305
306 /* We assume the "drawable framebuffer" (MSAA draw framebuffer) was
307 * previously bound... */
308 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msaaFramebuffer);
309 }
310
311 /* viewRenderbuffer should always be bound here. Code that binds something
312 * else is responsible for rebinding viewRenderbuffer, to reduce duplicate
313 * state changes. */
314 [context presentRenderbuffer:GL_RENDERBUFFER];
315}
316
317- (void)layoutSubviews
318{
319 [super layoutSubviews];
320
321 int width = (int)(self.bounds.size.width * self.contentScaleFactor);
322 int height = (int)(self.bounds.size.height * self.contentScaleFactor);
323
324 // Update the color and depth buffer storage if the layer size has changed.
325 if (width != backingWidth || height != backingHeight) {
326 EAGLContext *prevContext = [EAGLContext currentContext];
327 if (prevContext != context) {
328 [EAGLContext setCurrentContext:context];
329 }
330
331 [self updateFrame];
332
333 if (prevContext != context) {
334 [EAGLContext setCurrentContext:prevContext];
335 }
336 }
337}
338
339- (void)destroyFramebuffer
340{
341 if (viewFramebuffer != 0) {
342 glDeleteFramebuffers(1, &viewFramebuffer);
343 viewFramebuffer = 0;
344 }
345
346 if (viewRenderbuffer != 0) {
347 glDeleteRenderbuffers(1, &viewRenderbuffer);
348 viewRenderbuffer = 0;
349 }
350
351 if (depthRenderbuffer != 0) {
352 glDeleteRenderbuffers(1, &depthRenderbuffer);
353 depthRenderbuffer = 0;
354 }
355
356 if (msaaFramebuffer != 0) {
357 glDeleteFramebuffers(1, &msaaFramebuffer);
358 msaaFramebuffer = 0;
359 }
360
361 if (msaaRenderbuffer != 0) {
362 glDeleteRenderbuffers(1, &msaaRenderbuffer);
363 msaaRenderbuffer = 0;
364 }
365}
366
367- (void)dealloc
368{
369 if (context && context == [EAGLContext currentContext]) {
370 [self destroyFramebuffer];
371 [EAGLContext setCurrentContext:nil];
372 }
373}
374
375@end
376
377#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.h
new file mode 100644
index 0000000..1e75589
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.h
@@ -0,0 +1,39 @@
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
22#ifndef SDL_uikitpen_h_
23#define SDL_uikitpen_h_
24
25#include "SDL_uikitvideo.h"
26#include "SDL_uikitwindow.h"
27
28extern bool UIKit_InitPen(SDL_VideoDevice *_this);
29extern void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil);
30extern void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil);
31extern void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil);
32
33#if !defined(SDL_PLATFORM_TVOS)
34extern void UIKit_HandlePenHover(SDL_uikitview *view, UIHoverGestureRecognizer *recognizer) API_AVAILABLE(ios(13.0));
35#endif
36
37extern void UIKit_QuitPen(SDL_VideoDevice *_this);
38
39#endif // SDL_uikitpen_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.m
new file mode 100644
index 0000000..5209681
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitpen.m
@@ -0,0 +1,214 @@
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_uikitevents.h"
26#include "SDL_uikitpen.h"
27#include "SDL_uikitwindow.h"
28
29#include "../../events/SDL_pen_c.h"
30
31// Fix build errors when using an older SDK by defining these selectors
32#if !defined(SDL_PLATFORM_TVOS)
33
34@interface UITouch (SDL)
35#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 170500)
36@property (nonatomic, readonly) CGFloat rollAngle;
37#endif
38@end
39
40@interface UIHoverGestureRecognizer (SDL)
41#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 160100)
42@property (nonatomic, readonly) CGFloat zOffset;
43#endif
44#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 160400)
45- (CGFloat) azimuthAngleInView:(UIView *) view;
46
47@property (nonatomic, readonly) CGFloat altitudeAngle;
48#endif
49#if !(__IPHONE_OS_VERSION_MAX_ALLOWED >= 170500)
50@property (nonatomic, readonly) CGFloat rollAngle;
51#endif
52@end
53
54#endif // !SDL_PLATFORM_TVOS
55
56static SDL_PenID apple_pencil_id = 0;
57
58bool UIKit_InitPen(SDL_VideoDevice *_this)
59{
60 return true;
61}
62
63// we only have one Apple Pencil at a time, and it must be paired to the iOS device.
64// We only know about its existence when it first sends an event, so add an single SDL pen
65// device here if we haven't already.
66static SDL_PenID UIKit_AddPenIfNecesary()
67{
68 if (!apple_pencil_id) {
69 SDL_PenInfo info;
70 SDL_zero(info);
71 info.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT;
72 info.max_tilt = 90.0f;
73 info.num_buttons = 0;
74 info.subtype = SDL_PEN_TYPE_PENCIL;
75
76 if (@available(iOS 17.5, *)) { // need rollAngle method.
77 info.capabilities |= SDL_PEN_CAPABILITY_ROTATION;
78 }
79
80 if (@available(ios 16.1, *)) { // need zOffset method.
81 info.capabilities |= SDL_PEN_CAPABILITY_DISTANCE;
82 }
83
84 // Apple Pencil and iOS can report when the pencil is being "squeezed" but it's a boolean thing,
85 // so we can't use it for tangential pressure.
86
87 // There's only ever one Apple Pencil at most, so we just pass a non-zero value for the handle.
88 apple_pencil_id = SDL_AddPenDevice(0, "Apple Pencil", &info, (void *) (size_t) 0x1);
89 }
90
91 return apple_pencil_id;
92}
93
94static void UIKit_HandlePenAxes(SDL_Window *window, NSTimeInterval nstimestamp, float zOffset, const CGPoint *point, float force,
95 float maximumPossibleForce, float azimuthAngleInView, float altitudeAngle, float rollAngle)
96{
97 const SDL_PenID penId = UIKit_AddPenIfNecesary();
98 if (penId) {
99 const Uint64 timestamp = UIKit_GetEventTimestamp(nstimestamp);
100 const float radians_to_degrees = 180.0f / SDL_PI_F;
101
102 // Normalize force to 0.0f ... 1.0f range.
103 const float pressure = force / maximumPossibleForce;
104
105 // azimuthAngleInView is in radians, with 0 being the pen's back end pointing due east on the screen when the
106 // tip is touching the screen, and negative when heading north from there, positive to the south.
107 // So convert to degrees, 0 being due east, etc.
108 const float azimuth_angle = azimuthAngleInView * radians_to_degrees;
109
110 // altitudeAngle is in radians, with 0 being the pen laying flat on (parallel to) the device
111 // screen and PI/2 being it pointing straight up from (perpendicular to) the device screen.
112 // So convert to degrees, 0 being flat and 90 being straight up.
113 const float altitude_angle = altitudeAngle * radians_to_degrees;
114
115 // the azimuth_angle goes from -180 to 180 (with abs(angle) moving from 180 to 0, left to right), but SDL wants
116 // it from -90 (back facing west) to 90 (back facing east).
117 const float xtilt = (180.0f - SDL_fabsf(azimuth_angle)) - 90.0f;
118
119 // the altitude_angle goes from 0 to 90 regardless of which direction the pen is lifting off the device, but SDL wants
120 // it from -90 (flat facing north) to 90 (flat facing south).
121 const float ytilt = (azimuth_angle < 0.0f) ? -(90.0f - altitude_angle) : (90.0f - altitude_angle);
122
123 // rotation is in radians, and only available on a later iOS.
124 const float rotation = rollAngle * radians_to_degrees; // !!! FIXME: this might need adjustment, I don't have a pencil that supports it.
125
126 SDL_SendPenMotion(timestamp, penId, window, point->x, point->y);
127 SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_PRESSURE, pressure);
128 SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_XTILT, xtilt);
129 SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_YTILT, ytilt);
130 SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_ROTATION, rotation);
131 SDL_SendPenAxis(timestamp, penId, window, SDL_PEN_AXIS_DISTANCE, zOffset);
132 }
133}
134
135#if !defined(SDL_PLATFORM_TVOS)
136extern void UIKit_HandlePenHover(SDL_uikitview *view, UIHoverGestureRecognizer *recognizer)
137{
138 float zOffset = 0.0f;
139 if (@available(iOS 16.1, *)) {
140 zOffset = (float) [recognizer zOffset];
141 }
142
143 float azimuthAngleInView = 0.0f;
144 if (@available(iOS 16.4, *)) {
145 azimuthAngleInView = (float) [recognizer azimuthAngleInView:view];
146 }
147
148 float altitudeAngle = 0.0f;
149 if (@available(iOS 16.4, *)) {
150 altitudeAngle = (float) [recognizer altitudeAngle];
151 }
152
153 float rollAngle = 0.0f;
154 if (@available(iOS 17.5, *)) {
155 rollAngle = (float) [recognizer rollAngle];
156 }
157
158 SDL_Window *window = [view getSDLWindow];
159 const CGPoint point = [recognizer locationInView:view];
160
161 // force is zero here; if you're here, you're not touching.
162 // !!! FIXME: no timestamp on these...?
163 UIKit_HandlePenAxes(window, 0, zOffset, &point, 0.0f, 1.0f, azimuthAngleInView, altitudeAngle, rollAngle);
164}
165#endif
166
167static void UIKit_HandlePenAxesFromUITouch(SDL_uikitview *view, UITouch *pencil)
168{
169 float rollAngle = 0.0f;
170#if !defined(SDL_PLATFORM_TVOS)
171 if (@available(iOS 17.5, *)) {
172 rollAngle = (float) [pencil rollAngle];
173 }
174#endif
175
176 SDL_Window *window = [view getSDLWindow];
177 const CGPoint point = [pencil locationInView:view];
178
179 // zOffset is zero here; if you're here, you're touching.
180 UIKit_HandlePenAxes(window, [pencil timestamp], 0.0f, &point, [pencil force], [pencil maximumPossibleForce], [pencil azimuthAngleInView:view], [pencil altitudeAngle], rollAngle);
181}
182
183void UIKit_HandlePenMotion(SDL_uikitview *view, UITouch *pencil)
184{
185 UIKit_HandlePenAxesFromUITouch(view, pencil);
186}
187
188void UIKit_HandlePenPress(SDL_uikitview *view, UITouch *pencil)
189{
190 const SDL_PenID penId = UIKit_AddPenIfNecesary();
191 if (penId) {
192 UIKit_HandlePenAxesFromUITouch(view, pencil);
193 SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, true);
194 }
195}
196
197void UIKit_HandlePenRelease(SDL_uikitview *view, UITouch *pencil)
198{
199 const SDL_PenID penId = UIKit_AddPenIfNecesary();
200 if (penId) {
201 SDL_SendPenTouch(UIKit_GetEventTimestamp([pencil timestamp]), penId, [view getSDLWindow], false, false);
202 UIKit_HandlePenAxesFromUITouch(view, pencil);
203 }
204}
205
206void UIKit_QuitPen(SDL_VideoDevice *_this)
207{
208 if (apple_pencil_id) {
209 SDL_RemovePenDevice(0, apple_pencil_id);
210 apple_pencil_id = 0;
211 }
212}
213
214#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.h
new file mode 100644
index 0000000..927e646
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.h
@@ -0,0 +1,52 @@
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#ifndef SDL_uikitvideo_h_
22#define SDL_uikitvideo_h_
23
24#include "../SDL_sysvideo.h"
25
26#ifdef __OBJC__
27
28#include <UIKit/UIKit.h>
29
30@interface SDL_UIKitVideoData : NSObject
31
32@property(nonatomic, assign) id pasteboardObserver;
33
34@end
35
36#ifdef SDL_PLATFORM_VISIONOS
37CGRect UIKit_ComputeViewFrame(SDL_Window *window);
38#else
39CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen);
40#endif
41
42#endif // __OBJC__
43
44bool UIKit_SuspendScreenSaver(SDL_VideoDevice *_this);
45
46void UIKit_ForceUpdateHomeIndicator(void);
47
48bool UIKit_IsSystemVersionAtLeast(double version);
49
50SDL_SystemTheme UIKit_GetSystemTheme(void);
51
52#endif // SDL_uikitvideo_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.m
new file mode 100644
index 0000000..5c3987d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvideo.m
@@ -0,0 +1,312 @@
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#import <UIKit/UIKit.h>
26
27#include "../SDL_sysvideo.h"
28#include "../SDL_pixels_c.h"
29#include "../../events/SDL_events_c.h"
30
31#include "SDL_uikitvideo.h"
32#include "SDL_uikitevents.h"
33#include "SDL_uikitmodes.h"
34#include "SDL_uikitwindow.h"
35#include "SDL_uikitopengles.h"
36#include "SDL_uikitclipboard.h"
37#include "SDL_uikitvulkan.h"
38#include "SDL_uikitmetalview.h"
39#include "SDL_uikitmessagebox.h"
40
41#define UIKITVID_DRIVER_NAME "uikit"
42
43@implementation SDL_UIKitVideoData
44
45@end
46
47// Initialization/Query functions
48static bool UIKit_VideoInit(SDL_VideoDevice *_this);
49static void UIKit_VideoQuit(SDL_VideoDevice *_this);
50
51// DUMMY driver bootstrap functions
52
53static void UIKit_DeleteDevice(SDL_VideoDevice *device)
54{
55 @autoreleasepool {
56 if (device->internal){
57 CFRelease(device->internal);
58 }
59 SDL_free(device);
60 }
61}
62
63static SDL_VideoDevice *UIKit_CreateDevice(void)
64{
65 @autoreleasepool {
66 SDL_VideoDevice *device;
67 SDL_UIKitVideoData *data;
68
69 // Initialize all variables that we clean on shutdown
70 device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
71 if (!device) {
72 return NULL;
73 }
74
75 data = [SDL_UIKitVideoData new];
76
77 device->internal = (SDL_VideoData *)CFBridgingRetain(data);
78 device->system_theme = UIKit_GetSystemTheme();
79
80 // Set the function pointers
81 device->VideoInit = UIKit_VideoInit;
82 device->VideoQuit = UIKit_VideoQuit;
83 device->GetDisplayModes = UIKit_GetDisplayModes;
84 device->SetDisplayMode = UIKit_SetDisplayMode;
85 device->PumpEvents = UIKit_PumpEvents;
86 device->SuspendScreenSaver = UIKit_SuspendScreenSaver;
87 device->CreateSDLWindow = UIKit_CreateWindow;
88 device->SetWindowTitle = UIKit_SetWindowTitle;
89 device->ShowWindow = UIKit_ShowWindow;
90 device->HideWindow = UIKit_HideWindow;
91 device->RaiseWindow = UIKit_RaiseWindow;
92 device->SetWindowBordered = UIKit_SetWindowBordered;
93 device->SetWindowFullscreen = UIKit_SetWindowFullscreen;
94 device->DestroyWindow = UIKit_DestroyWindow;
95 device->GetDisplayUsableBounds = UIKit_GetDisplayUsableBounds;
96 device->GetWindowSizeInPixels = UIKit_GetWindowSizeInPixels;
97
98#ifdef SDL_IPHONE_KEYBOARD
99 device->HasScreenKeyboardSupport = UIKit_HasScreenKeyboardSupport;
100 device->StartTextInput = UIKit_StartTextInput;
101 device->StopTextInput = UIKit_StopTextInput;
102 device->SetTextInputProperties = UIKit_SetTextInputProperties;
103 device->IsScreenKeyboardShown = UIKit_IsScreenKeyboardShown;
104 device->UpdateTextInputArea = UIKit_UpdateTextInputArea;
105#endif
106
107 device->SetClipboardText = UIKit_SetClipboardText;
108 device->GetClipboardText = UIKit_GetClipboardText;
109 device->HasClipboardText = UIKit_HasClipboardText;
110
111 // OpenGL (ES) functions
112#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
113 device->GL_MakeCurrent = UIKit_GL_MakeCurrent;
114 device->GL_SwapWindow = UIKit_GL_SwapWindow;
115 device->GL_CreateContext = UIKit_GL_CreateContext;
116 device->GL_DestroyContext = UIKit_GL_DestroyContext;
117 device->GL_GetProcAddress = UIKit_GL_GetProcAddress;
118 device->GL_LoadLibrary = UIKit_GL_LoadLibrary;
119#endif
120 device->free = UIKit_DeleteDevice;
121
122#ifdef SDL_VIDEO_VULKAN
123 device->Vulkan_LoadLibrary = UIKit_Vulkan_LoadLibrary;
124 device->Vulkan_UnloadLibrary = UIKit_Vulkan_UnloadLibrary;
125 device->Vulkan_GetInstanceExtensions = UIKit_Vulkan_GetInstanceExtensions;
126 device->Vulkan_CreateSurface = UIKit_Vulkan_CreateSurface;
127 device->Vulkan_DestroySurface = UIKit_Vulkan_DestroySurface;
128#endif
129
130#ifdef SDL_VIDEO_METAL
131 device->Metal_CreateView = UIKit_Metal_CreateView;
132 device->Metal_DestroyView = UIKit_Metal_DestroyView;
133 device->Metal_GetLayer = UIKit_Metal_GetLayer;
134#endif
135
136 device->device_caps = VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS;
137
138 device->gl_config.accelerated = 1;
139
140 return device;
141 }
142}
143
144VideoBootStrap UIKIT_bootstrap = {
145 UIKITVID_DRIVER_NAME, "SDL UIKit video driver",
146 UIKit_CreateDevice,
147 UIKit_ShowMessageBox,
148 false
149};
150
151static bool UIKit_VideoInit(SDL_VideoDevice *_this)
152{
153 _this->gl_config.driver_loaded = 1;
154
155 if (!UIKit_InitModes(_this)) {
156 return false;
157 }
158
159 SDL_InitGCKeyboard();
160 SDL_InitGCMouse();
161
162 UIKit_InitClipboard(_this);
163
164 return true;
165}
166
167static void UIKit_VideoQuit(SDL_VideoDevice *_this)
168{
169 UIKit_QuitClipboard(_this);
170
171 SDL_QuitGCKeyboard();
172 SDL_QuitGCMouse();
173
174 UIKit_QuitModes(_this);
175}
176
177bool UIKit_SuspendScreenSaver(SDL_VideoDevice *_this)
178{
179 @autoreleasepool {
180 UIApplication *app = [UIApplication sharedApplication];
181
182 // Prevent the display from dimming and going to sleep.
183 app.idleTimerDisabled = (_this->suspend_screensaver != false);
184 }
185 return true;
186}
187
188bool UIKit_IsSystemVersionAtLeast(double version)
189{
190 return [[UIDevice currentDevice].systemVersion doubleValue] >= version;
191}
192
193SDL_SystemTheme UIKit_GetSystemTheme(void)
194{
195#ifndef SDL_PLATFORM_VISIONOS
196 if (@available(iOS 12.0, tvOS 10.0, *)) {
197 switch ([UIScreen mainScreen].traitCollection.userInterfaceStyle) {
198 case UIUserInterfaceStyleDark:
199 return SDL_SYSTEM_THEME_DARK;
200 case UIUserInterfaceStyleLight:
201 return SDL_SYSTEM_THEME_LIGHT;
202 default:
203 break;
204 }
205 }
206#endif
207 return SDL_SYSTEM_THEME_UNKNOWN;
208}
209
210#ifdef SDL_PLATFORM_VISIONOS
211CGRect UIKit_ComputeViewFrame(SDL_Window *window){
212 return CGRectMake(window->x, window->y, window->w, window->h);
213}
214#else
215CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen)
216{
217 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
218 CGRect frame = screen.bounds;
219
220 /* Use the UIWindow bounds instead of the UIScreen bounds, when possible.
221 * The uiwindow bounds may be smaller than the screen bounds when Split View
222 * is used on an iPad. */
223 if (data != nil && data.uiwindow != nil) {
224 frame = data.uiwindow.bounds;
225 }
226
227#ifndef SDL_PLATFORM_TVOS
228 /* iOS 10 seems to have a bug where, in certain conditions, putting the
229 * device to sleep with the a landscape-only app open, re-orienting the
230 * device to portrait, and turning it back on will result in the screen
231 * bounds returning portrait orientation despite the app being in landscape.
232 * This is a workaround until a better solution can be found.
233 * https://bugzilla.libsdl.org/show_bug.cgi?id=3505
234 * https://bugzilla.libsdl.org/show_bug.cgi?id=3465
235 * https://forums.developer.apple.com/thread/65337 */
236 UIInterfaceOrientation orient = [UIApplication sharedApplication].statusBarOrientation;
237 BOOL landscape = UIInterfaceOrientationIsLandscape(orient) ||
238 !(UIKit_GetSupportedOrientations(window) & (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown));
239 BOOL fullscreen = CGRectEqualToRect(screen.bounds, frame);
240
241 /* The orientation flip doesn't make sense when the window is smaller
242 * than the screen (iPad Split View, for example). */
243 if (fullscreen && (landscape != (frame.size.width > frame.size.height))) {
244 float height = frame.size.width;
245 frame.size.width = frame.size.height;
246 frame.size.height = height;
247 }
248#endif
249
250 return frame;
251}
252
253#endif
254
255void UIKit_ForceUpdateHomeIndicator(void)
256{
257#ifndef SDL_PLATFORM_TVOS
258 // Force the main SDL window to re-evaluate home indicator state
259 SDL_Window *focus = SDL_GetKeyboardFocus();
260 if (focus) {
261 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)focus->internal;
262 if (data != nil) {
263 [data.viewcontroller performSelectorOnMainThread:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden) withObject:nil waitUntilDone:NO];
264 [data.viewcontroller performSelectorOnMainThread:@selector(setNeedsUpdateOfScreenEdgesDeferringSystemGestures) withObject:nil waitUntilDone:NO];
265 }
266 }
267#endif // !SDL_PLATFORM_TVOS
268}
269
270/*
271 * iOS log support.
272 *
273 * This doesn't really have anything to do with the interfaces of the SDL video
274 * subsystem, but we need to stuff this into an Objective-C source code file.
275 *
276 * NOTE: This is copypasted from src/video/cocoa/SDL_cocoavideo.m! Thus, if
277 * Cocoa is supported, we use that one instead. Be sure both versions remain
278 * identical!
279 */
280
281#ifndef SDL_VIDEO_DRIVER_COCOA
282void SDL_NSLog(const char *prefix, const char *text)
283{
284 @autoreleasepool {
285 NSString *nsText = [NSString stringWithUTF8String:text];
286 if (prefix && *prefix) {
287 NSString *nsPrefix = [NSString stringWithUTF8String:prefix];
288 NSLog(@"%@%@", nsPrefix, nsText);
289 } else {
290 NSLog(@"%@", nsText);
291 }
292 }
293}
294#endif // SDL_VIDEO_DRIVER_COCOA
295
296/*
297 * iOS Tablet, etc, detection
298 *
299 * This doesn't really have anything to do with the interfaces of the SDL video
300 * subsystem, but we need to stuff this into an Objective-C source code file.
301 */
302bool SDL_IsIPad(void)
303{
304 return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad);
305}
306
307bool SDL_IsAppleTV(void)
308{
309 return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomTV);
310}
311
312#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.h
new file mode 100644
index 0000000..78c2bed
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.h
@@ -0,0 +1,52 @@
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
22#import <UIKit/UIKit.h>
23
24#include "../SDL_sysvideo.h"
25
26#if !defined(SDL_PLATFORM_TVOS)
27@interface SDL_uikitview : UIView <UIPointerInteractionDelegate>
28#else
29@interface SDL_uikitview : UIView
30#endif
31
32- (instancetype)initWithFrame:(CGRect)frame;
33
34- (void)setSDLWindow:(SDL_Window *)window;
35- (SDL_Window *)getSDLWindow;
36
37#if !defined(SDL_PLATFORM_TVOS)
38- (void)pencilHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.0));
39
40- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4));
41- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4));
42- (void)indirectPointerHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.4));
43#endif
44
45- (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize;
46- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
47- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
48- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
49
50- (void)safeAreaInsetsDidChange;
51
52@end
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m
new file mode 100644
index 0000000..ba3b09b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitview.m
@@ -0,0 +1,587 @@
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_uikitview.h"
26
27#include "../../events/SDL_mouse_c.h"
28#include "../../events/SDL_touch_c.h"
29#include "../../events/SDL_events_c.h"
30
31#include "SDL_uikitappdelegate.h"
32#include "SDL_uikitevents.h"
33#include "SDL_uikitmodes.h"
34#include "SDL_uikitpen.h"
35#include "SDL_uikitwindow.h"
36
37// The maximum number of mouse buttons we support
38#define MAX_MOUSE_BUTTONS 5
39
40// This is defined in SDL_sysjoystick.m
41#ifndef SDL_JOYSTICK_DISABLED
42extern int SDL_AppleTVRemoteOpenedAsJoystick;
43#endif
44
45@implementation SDL_uikitview
46{
47 SDL_Window *sdlwindow;
48
49 SDL_TouchID directTouchId;
50 SDL_TouchID indirectTouchId;
51
52#if !defined(SDL_PLATFORM_TVOS)
53 UIPointerInteraction *indirectPointerInteraction API_AVAILABLE(ios(13.4));
54#endif
55}
56
57- (instancetype)initWithFrame:(CGRect)frame
58{
59 if ((self = [super initWithFrame:frame])) {
60#ifdef SDL_PLATFORM_TVOS
61 // Apple TV Remote touchpad swipe gestures.
62 UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
63 swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
64 [self addGestureRecognizer:swipeUp];
65
66 UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
67 swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
68 [self addGestureRecognizer:swipeDown];
69
70 UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
71 swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
72 [self addGestureRecognizer:swipeLeft];
73
74 UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
75 swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
76 [self addGestureRecognizer:swipeRight];
77#endif
78
79 self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
80 self.autoresizesSubviews = YES;
81
82 directTouchId = 1;
83 indirectTouchId = 2;
84
85#ifndef SDL_PLATFORM_TVOS
86 self.multipleTouchEnabled = YES;
87 SDL_AddTouch(directTouchId, SDL_TOUCH_DEVICE_DIRECT, "");
88
89 if (@available(iOS 13.0, *)) {
90 UIHoverGestureRecognizer *pencilRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(pencilHovering:)];
91 pencilRecognizer.allowedTouchTypes = @[@(UITouchTypePencil)];
92 [self addGestureRecognizer:pencilRecognizer];
93 }
94
95 if (@available(iOS 13.4, *)) {
96 indirectPointerInteraction = [[UIPointerInteraction alloc] initWithDelegate:self];
97 [self addInteraction:indirectPointerInteraction];
98
99 UIHoverGestureRecognizer *indirectPointerRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(indirectPointerHovering:)];
100 indirectPointerRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)];
101 [self addGestureRecognizer:indirectPointerRecognizer];
102 }
103#endif // !defined(SDL_PLATFORM_TVOS)
104 }
105
106 return self;
107}
108
109- (void)setSDLWindow:(SDL_Window *)window
110{
111 SDL_UIKitWindowData *data = nil;
112
113 if (window == sdlwindow) {
114 return;
115 }
116
117 // Remove ourself from the old window.
118 if (sdlwindow) {
119 SDL_uikitview *view = nil;
120 data = (__bridge SDL_UIKitWindowData *)sdlwindow->internal;
121
122 [data.views removeObject:self];
123
124 [self removeFromSuperview];
125
126 // Restore the next-oldest view in the old window.
127 view = data.views.lastObject;
128
129 data.viewcontroller.view = view;
130
131 data.uiwindow.rootViewController = nil;
132 data.uiwindow.rootViewController = data.viewcontroller;
133
134 [data.uiwindow layoutIfNeeded];
135 }
136
137 sdlwindow = window;
138
139 // Add ourself to the new window.
140 if (window) {
141 data = (__bridge SDL_UIKitWindowData *)window->internal;
142
143 // Make sure the SDL window has a strong reference to this view.
144 [data.views addObject:self];
145
146 // Replace the view controller's old view with this one.
147 [data.viewcontroller.view removeFromSuperview];
148 data.viewcontroller.view = self;
149
150 /* The root view controller handles rotation and the status bar.
151 * Assigning it also adds the controller's view to the window. We
152 * explicitly re-set it to make sure the view is properly attached to
153 * the window. Just adding the sub-view if the root view controller is
154 * already correct causes orientation issues on iOS 7 and below. */
155 data.uiwindow.rootViewController = nil;
156 data.uiwindow.rootViewController = data.viewcontroller;
157
158 /* The view's bounds may not be correct until the next event cycle. That
159 * might happen after the current dimensions are queried, so we force a
160 * layout now to immediately update the bounds. */
161 [data.uiwindow layoutIfNeeded];
162 }
163}
164
165- (SDL_Window *)getSDLWindow
166{
167 return sdlwindow;
168}
169
170#if !defined(SDL_PLATFORM_TVOS)
171
172- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4))
173{
174 return [UIPointerRegion regionWithRect:self.bounds identifier:nil];
175}
176
177- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4))
178{
179 if (SDL_CursorVisible()) {
180 return nil;
181 } else {
182 return [UIPointerStyle hiddenPointerStyle];
183 }
184}
185
186- (void)indirectPointerHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.4))
187{
188 switch (recognizer.state) {
189 case UIGestureRecognizerStateBegan:
190 case UIGestureRecognizerStateChanged:
191 {
192 CGPoint point = [recognizer locationInView:self];
193 SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, point.x, point.y);
194 break;
195 }
196
197 default:
198 break;
199 }
200}
201
202- (void)indirectPointerMoving:(UITouch *)touch API_AVAILABLE(ios(13.4))
203{
204 CGPoint locationInView = [self touchLocation:touch shouldNormalize:NO];
205 SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, locationInView.x, locationInView.y);
206}
207
208- (void)indirectPointerPressed:(UITouch *)touch fromEvent:(UIEvent *)event API_AVAILABLE(ios(13.4))
209{
210 if (!SDL_HasMouse()) {
211 int i;
212
213 for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
214 if (event.buttonMask & SDL_BUTTON_MASK(i)) {
215 Uint8 button;
216
217 switch (i) {
218 case 1:
219 button = SDL_BUTTON_LEFT;
220 break;
221 case 2:
222 button = SDL_BUTTON_RIGHT;
223 break;
224 case 3:
225 button = SDL_BUTTON_MIDDLE;
226 break;
227 default:
228 button = (Uint8)i;
229 break;
230 }
231 SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, button, true);
232 }
233 }
234 }
235}
236
237- (void)indirectPointerReleased:(UITouch *)touch fromEvent:(UIEvent *)event API_AVAILABLE(ios(13.4))
238{
239 if (!SDL_HasMouse()) {
240 int i;
241 SDL_MouseButtonFlags buttons = SDL_GetMouseState(NULL, NULL);
242
243 for (i = 0; i < MAX_MOUSE_BUTTONS; ++i) {
244 if (buttons & SDL_BUTTON_MASK(i)) {
245 SDL_SendMouseButton(UIKit_GetEventTimestamp([touch timestamp]), sdlwindow, SDL_GLOBAL_MOUSE_ID, (Uint8)i, false);
246 }
247 }
248 }
249}
250
251- (void)pencilHovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.0))
252{
253 switch (recognizer.state) {
254 case UIGestureRecognizerStateBegan:
255 case UIGestureRecognizerStateChanged:
256 UIKit_HandlePenHover(self, recognizer);
257 break;
258
259 case UIGestureRecognizerStateEnded:
260 case UIGestureRecognizerStateCancelled:
261 // we track touches elsewhere, so if a hover "ends" we'll deal with that there.
262 break;
263
264 default:
265 break;
266 }
267}
268
269- (void)pencilMoving:(UITouch *)touch
270{
271 UIKit_HandlePenMotion(self, touch);
272}
273
274- (void)pencilPressed:(UITouch *)touch
275{
276 UIKit_HandlePenPress(self, touch);
277}
278
279- (void)pencilReleased:(UITouch *)touch
280{
281 UIKit_HandlePenRelease(self, touch);
282}
283
284#endif // !defined(SDL_PLATFORM_TVOS)
285
286- (SDL_TouchDeviceType)touchTypeForTouch:(UITouch *)touch
287{
288 if (touch.type == UITouchTypeIndirect) {
289 return SDL_TOUCH_DEVICE_INDIRECT_RELATIVE;
290 }
291 return SDL_TOUCH_DEVICE_DIRECT;
292}
293
294- (SDL_TouchID)touchIdForType:(SDL_TouchDeviceType)type
295{
296 switch (type) {
297 case SDL_TOUCH_DEVICE_DIRECT:
298 default:
299 return directTouchId;
300 case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE:
301 return indirectTouchId;
302 }
303}
304
305- (CGPoint)touchLocation:(UITouch *)touch shouldNormalize:(BOOL)normalize
306{
307 CGPoint point = [touch locationInView:self];
308
309 if (normalize) {
310 CGRect bounds = self.bounds;
311 point.x /= bounds.size.width;
312 point.y /= bounds.size.height;
313 }
314
315 return point;
316}
317
318- (float)pressureForTouch:(UITouch *)touch
319{
320 return (float)touch.force;
321}
322
323- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
324{
325 for (UITouch *touch in touches) {
326#if !defined(SDL_PLATFORM_TVOS)
327 if (@available(iOS 13.0, *)) {
328 if (touch.type == UITouchTypePencil) {
329 [self pencilPressed:touch];
330 continue;
331 }
332 }
333
334 if (@available(iOS 13.4, *)) {
335 if (touch.type == UITouchTypeIndirectPointer) {
336 [self indirectPointerPressed:touch fromEvent:event];
337 continue;
338 }
339 }
340#endif // !defined(SDL_PLATFORM_TVOS)
341
342 SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
343 SDL_TouchID touchId = [self touchIdForType:touchType];
344 float pressure = [self pressureForTouch:touch];
345
346 if (SDL_AddTouch(touchId, touchType, "") < 0) {
347 continue;
348 }
349
350 // FIXME, need to send: int clicks = (int) touch.tapCount; ?
351
352 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
353 SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]),
354 touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
355 SDL_EVENT_FINGER_DOWN, locationInView.x, locationInView.y, pressure);
356 }
357}
358
359- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
360{
361 for (UITouch *touch in touches) {
362#if !defined(SDL_PLATFORM_TVOS)
363 if (@available(iOS 13.0, *)) {
364 if (touch.type == UITouchTypePencil) {
365 [self pencilReleased:touch];
366 continue;
367 }
368 }
369
370 if (@available(iOS 13.4, *)) {
371 if (touch.type == UITouchTypeIndirectPointer) {
372 [self indirectPointerReleased:touch fromEvent:event];
373 continue;
374 }
375 }
376#endif // !defined(SDL_PLATFORM_TVOS)
377
378 SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
379 SDL_TouchID touchId = [self touchIdForType:touchType];
380 float pressure = [self pressureForTouch:touch];
381
382 if (SDL_AddTouch(touchId, touchType, "") < 0) {
383 continue;
384 }
385
386 // FIXME, need to send: int clicks = (int) touch.tapCount; ?
387
388 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
389 SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]),
390 touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
391 SDL_EVENT_FINGER_UP, locationInView.x, locationInView.y, pressure);
392 }
393}
394
395- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
396{
397 for (UITouch *touch in touches) {
398#if !defined(SDL_PLATFORM_TVOS)
399 if (@available(iOS 13.0, *)) {
400 if (touch.type == UITouchTypePencil) {
401 [self pencilReleased:touch];
402 continue;
403 }
404 }
405
406 if (@available(iOS 13.4, *)) {
407 if (touch.type == UITouchTypeIndirectPointer) {
408 [self indirectPointerReleased:touch fromEvent:event];
409 continue;
410 }
411 }
412#endif // !defined(SDL_PLATFORM_TVOS)
413
414 SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
415 SDL_TouchID touchId = [self touchIdForType:touchType];
416 float pressure = [self pressureForTouch:touch];
417
418 if (SDL_AddTouch(touchId, touchType, "") < 0) {
419 continue;
420 }
421
422 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
423 SDL_SendTouch(UIKit_GetEventTimestamp([event timestamp]),
424 touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
425 SDL_EVENT_FINGER_CANCELED, locationInView.x, locationInView.y, pressure);
426 }
427}
428
429- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
430{
431 for (UITouch *touch in touches) {
432#if !defined(SDL_PLATFORM_TVOS)
433 if (@available(iOS 13.0, *)) {
434 if (touch.type == UITouchTypePencil) {
435 [self pencilMoving:touch];
436 continue;
437 }
438 }
439
440 if (@available(iOS 13.4, *)) {
441 if (touch.type == UITouchTypeIndirectPointer) {
442 [self indirectPointerMoving:touch];
443 continue;
444 }
445 }
446#endif // !defined(SDL_PLATFORM_TVOS)
447
448 SDL_TouchDeviceType touchType = [self touchTypeForTouch:touch];
449 SDL_TouchID touchId = [self touchIdForType:touchType];
450 float pressure = [self pressureForTouch:touch];
451
452 if (SDL_AddTouch(touchId, touchType, "") < 0) {
453 continue;
454 }
455
456 CGPoint locationInView = [self touchLocation:touch shouldNormalize:YES];
457 SDL_SendTouchMotion(UIKit_GetEventTimestamp([event timestamp]),
458 touchId, (SDL_FingerID)(uintptr_t)touch, sdlwindow,
459 locationInView.x, locationInView.y, pressure);
460 }
461}
462
463- (void)safeAreaInsetsDidChange
464{
465 // Update the safe area insets
466 SDL_SetWindowSafeAreaInsets(sdlwindow,
467 (int)SDL_ceilf(self.safeAreaInsets.left),
468 (int)SDL_ceilf(self.safeAreaInsets.right),
469 (int)SDL_ceilf(self.safeAreaInsets.top),
470 (int)SDL_ceilf(self.safeAreaInsets.bottom));
471}
472
473- (SDL_Scancode)scancodeFromPress:(UIPress *)press
474{
475 if (press.key != nil) {
476 return (SDL_Scancode)press.key.keyCode;
477 }
478
479#ifndef SDL_JOYSTICK_DISABLED
480 // Presses from Apple TV remote
481 if (!SDL_AppleTVRemoteOpenedAsJoystick) {
482 switch (press.type) {
483 case UIPressTypeUpArrow:
484 return SDL_SCANCODE_UP;
485 case UIPressTypeDownArrow:
486 return SDL_SCANCODE_DOWN;
487 case UIPressTypeLeftArrow:
488 return SDL_SCANCODE_LEFT;
489 case UIPressTypeRightArrow:
490 return SDL_SCANCODE_RIGHT;
491 case UIPressTypeSelect:
492 // HIG says: "primary button behavior"
493 return SDL_SCANCODE_RETURN;
494 case UIPressTypeMenu:
495 // HIG says: "returns to previous screen"
496 return SDL_SCANCODE_ESCAPE;
497 case UIPressTypePlayPause:
498 // HIG says: "secondary button behavior"
499 return SDL_SCANCODE_PAUSE;
500 default:
501 break;
502 }
503 }
504#endif // !SDL_JOYSTICK_DISABLED
505
506 return SDL_SCANCODE_UNKNOWN;
507}
508
509- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
510{
511 if (!SDL_HasKeyboard()) {
512 for (UIPress *press in presses) {
513 SDL_Scancode scancode = [self scancodeFromPress:press];
514 SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_GLOBAL_KEYBOARD_ID, 0, scancode, true);
515 }
516 }
517 if (SDL_TextInputActive(sdlwindow)) {
518 [super pressesBegan:presses withEvent:event];
519 }
520}
521
522- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
523{
524 if (!SDL_HasKeyboard()) {
525 for (UIPress *press in presses) {
526 SDL_Scancode scancode = [self scancodeFromPress:press];
527 SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_GLOBAL_KEYBOARD_ID, 0, scancode, false);
528 }
529 }
530 if (SDL_TextInputActive(sdlwindow)) {
531 [super pressesEnded:presses withEvent:event];
532 }
533}
534
535- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
536{
537 if (!SDL_HasKeyboard()) {
538 for (UIPress *press in presses) {
539 SDL_Scancode scancode = [self scancodeFromPress:press];
540 SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_GLOBAL_KEYBOARD_ID, 0, scancode, false);
541 }
542 }
543 if (SDL_TextInputActive(sdlwindow)) {
544 [super pressesCancelled:presses withEvent:event];
545 }
546}
547
548- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
549{
550 // This is only called when the force of a press changes.
551 if (SDL_TextInputActive(sdlwindow)) {
552 [super pressesChanged:presses withEvent:event];
553 }
554}
555
556#ifdef SDL_PLATFORM_TVOS
557- (void)swipeGesture:(UISwipeGestureRecognizer *)gesture
558{
559 // Swipe gestures don't trigger begin states.
560 if (gesture.state == UIGestureRecognizerStateEnded) {
561#ifndef SDL_JOYSTICK_DISABLED
562 if (!SDL_AppleTVRemoteOpenedAsJoystick) {
563 /* Send arrow key presses for now, as we don't have an external API
564 * which better maps to swipe gestures. */
565 switch (gesture.direction) {
566 case UISwipeGestureRecognizerDirectionUp:
567 SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_UP);
568 break;
569 case UISwipeGestureRecognizerDirectionDown:
570 SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_DOWN);
571 break;
572 case UISwipeGestureRecognizerDirectionLeft:
573 SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_LEFT);
574 break;
575 case UISwipeGestureRecognizerDirectionRight:
576 SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_RIGHT);
577 break;
578 }
579 }
580#endif // !SDL_JOYSTICK_DISABLED
581 }
582}
583#endif // SDL_PLATFORM_TVOS
584
585@end
586
587#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.h
new file mode 100644
index 0000000..d52e478
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.h
@@ -0,0 +1,96 @@
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#import <UIKit/UIKit.h>
24
25#include "../SDL_sysvideo.h"
26
27#ifdef SDL_PLATFORM_TVOS
28#import <GameController/GameController.h>
29#define SDLRootViewController GCEventViewController
30#else
31#define SDLRootViewController UIViewController
32#endif
33
34@interface SDLUITextField : UITextField
35- (BOOL)canPerformAction:(SEL)action withSender:(id)sender;
36@end
37
38#ifdef SDL_IPHONE_KEYBOARD
39@interface SDL_uikitviewcontroller : SDLRootViewController <UITextFieldDelegate>
40#else
41@interface SDL_uikitviewcontroller : SDLRootViewController
42#endif
43
44@property(nonatomic, assign) SDL_Window *window;
45
46- (instancetype)initWithSDLWindow:(SDL_Window *)_window;
47
48- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
49
50- (void)setAnimationCallback:(int)interval
51 callback:(void (*)(void *))callback
52 callbackParam:(void *)callbackParam;
53
54- (void)startAnimation;
55- (void)stopAnimation;
56
57- (void)doLoop:(CADisplayLink *)sender;
58
59- (void)loadView;
60- (void)viewDidLayoutSubviews;
61
62#ifndef SDL_PLATFORM_TVOS
63- (NSUInteger)supportedInterfaceOrientations;
64- (BOOL)prefersStatusBarHidden;
65- (BOOL)prefersHomeIndicatorAutoHidden;
66- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures;
67
68@property(nonatomic, assign) int homeIndicatorHidden;
69#endif
70
71#ifdef SDL_IPHONE_KEYBOARD
72- (bool)startTextInput;
73- (bool)stopTextInput;
74- (void)initKeyboard;
75- (void)deinitKeyboard;
76
77- (void)keyboardWillShow:(NSNotification *)notification;
78- (void)keyboardWillHide:(NSNotification *)notification;
79
80- (void)updateKeyboard;
81
82@property(nonatomic, assign, getter=isTextFieldFocused) BOOL textFieldFocused;
83@property(nonatomic, assign) SDL_Rect textInputRect;
84@property(nonatomic, assign) int keyboardHeight;
85#endif
86
87@end
88
89#ifdef SDL_IPHONE_KEYBOARD
90bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
91bool UIKit_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
92bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
93void UIKit_SetTextInputProperties(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
94bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
95bool UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
96#endif
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.m
new file mode 100644
index 0000000..45f2d64
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitviewcontroller.m
@@ -0,0 +1,736 @@
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#include "../../events/SDL_events_c.h"
27
28#include "SDL_uikitviewcontroller.h"
29#include "SDL_uikitmessagebox.h"
30#include "SDL_uikitevents.h"
31#include "SDL_uikitvideo.h"
32#include "SDL_uikitmodes.h"
33#include "SDL_uikitwindow.h"
34#include "SDL_uikitopengles.h"
35
36#ifdef SDL_PLATFORM_TVOS
37static void SDLCALL SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
38{
39 @autoreleasepool {
40 SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *)userdata;
41 viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0');
42 }
43}
44#endif
45
46#ifndef SDL_PLATFORM_TVOS
47static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
48{
49 @autoreleasepool {
50 SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *)userdata;
51 viewcontroller.homeIndicatorHidden = (hint && *hint) ? SDL_atoi(hint) : -1;
52 [viewcontroller setNeedsUpdateOfHomeIndicatorAutoHidden];
53 [viewcontroller setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
54 }
55}
56#endif
57
58@implementation SDLUITextField : UITextField
59- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
60{
61 if (action == @selector(paste:)) {
62 return NO;
63 }
64
65 return [super canPerformAction:action withSender:sender];
66}
67@end
68
69@implementation SDL_uikitviewcontroller
70{
71 CADisplayLink *displayLink;
72 int animationInterval;
73 void (*animationCallback)(void *);
74 void *animationCallbackParam;
75
76#ifdef SDL_IPHONE_KEYBOARD
77 SDLUITextField *textField;
78 BOOL hidingKeyboard;
79 BOOL rotatingOrientation;
80 NSString *committedText;
81 NSString *obligateForBackspace;
82#endif
83}
84
85@synthesize window;
86
87- (instancetype)initWithSDLWindow:(SDL_Window *)_window
88{
89 if (self = [super initWithNibName:nil bundle:nil]) {
90 self.window = _window;
91
92#ifdef SDL_IPHONE_KEYBOARD
93 [self initKeyboard];
94 hidingKeyboard = NO;
95 rotatingOrientation = NO;
96#endif
97
98#ifdef SDL_PLATFORM_TVOS
99 SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
100 SDL_AppleTVControllerUIHintChanged,
101 (__bridge void *)self);
102#endif
103
104#ifndef SDL_PLATFORM_TVOS
105 SDL_AddHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
106 SDL_HideHomeIndicatorHintChanged,
107 (__bridge void *)self);
108#endif
109
110 // Enable high refresh rates on iOS
111 // To enable this on phones, you should add the following line to Info.plist:
112 // <key>CADisableMinimumFrameDurationOnPhone</key> <true/>
113 if (@available(iOS 15.0, tvOS 15.0, *)) {
114 const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
115 if (mode && mode->refresh_rate > 60.0f) {
116 int frame_rate = (int)mode->refresh_rate;
117 displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
118 displayLink.preferredFrameRateRange = CAFrameRateRangeMake((frame_rate * 2) / 3, frame_rate, frame_rate);
119 [displayLink addToRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
120 }
121 }
122 }
123 return self;
124}
125
126- (void)dealloc
127{
128#ifdef SDL_IPHONE_KEYBOARD
129 [self deinitKeyboard];
130#endif
131
132#ifdef SDL_PLATFORM_TVOS
133 SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
134 SDL_AppleTVControllerUIHintChanged,
135 (__bridge void *)self);
136#endif
137
138#ifndef SDL_PLATFORM_TVOS
139 SDL_RemoveHintCallback(SDL_HINT_IOS_HIDE_HOME_INDICATOR,
140 SDL_HideHomeIndicatorHintChanged,
141 (__bridge void *)self);
142#endif
143}
144
145- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
146{
147 SDL_SetSystemTheme(UIKit_GetSystemTheme());
148}
149
150- (void)setAnimationCallback:(int)interval
151 callback:(void (*)(void *))callback
152 callbackParam:(void *)callbackParam
153{
154 [self stopAnimation];
155
156 if (interval <= 0) {
157 interval = 1;
158 }
159 animationInterval = interval;
160 animationCallback = callback;
161 animationCallbackParam = callbackParam;
162
163 if (animationCallback) {
164 [self startAnimation];
165 }
166}
167
168- (void)startAnimation
169{
170 displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
171
172#ifdef SDL_PLATFORM_VISIONOS
173 displayLink.preferredFramesPerSecond = 90 / animationInterval; //TODO: Get frame max frame rate on visionOS
174#else
175 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
176
177 displayLink.preferredFramesPerSecond = data.uiwindow.screen.maximumFramesPerSecond / animationInterval;
178#endif
179
180 [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
181}
182
183- (void)stopAnimation
184{
185 [displayLink invalidate];
186 displayLink = nil;
187}
188
189- (void)doLoop:(CADisplayLink *)sender
190{
191 // Don't run the game loop while a messagebox is up
192 if (animationCallback && !UIKit_ShowingMessageBox()) {
193 // See the comment in the function definition.
194#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
195 UIKit_GL_RestoreCurrentContext();
196#endif
197
198 animationCallback(animationCallbackParam);
199 }
200}
201
202- (void)loadView
203{
204 // Do nothing.
205}
206
207- (void)viewDidLayoutSubviews
208{
209 const CGSize size = self.view.bounds.size;
210 int w = (int)size.width;
211 int h = (int)size.height;
212
213 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h);
214}
215
216#ifndef SDL_PLATFORM_TVOS
217- (NSUInteger)supportedInterfaceOrientations
218{
219 return UIKit_GetSupportedOrientations(window);
220}
221
222- (BOOL)prefersStatusBarHidden
223{
224 BOOL hidden = (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) != 0;
225 return hidden;
226}
227
228- (BOOL)prefersHomeIndicatorAutoHidden
229{
230 BOOL hidden = NO;
231 if (self.homeIndicatorHidden == 1) {
232 hidden = YES;
233 }
234 return hidden;
235}
236
237- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
238{
239 if (self.homeIndicatorHidden >= 0) {
240 if (self.homeIndicatorHidden == 2) {
241 return UIRectEdgeAll;
242 } else {
243 return UIRectEdgeNone;
244 }
245 }
246
247 // By default, fullscreen and borderless windows get all screen gestures
248 if ((window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) != 0) {
249 return UIRectEdgeAll;
250 } else {
251 return UIRectEdgeNone;
252 }
253}
254
255- (BOOL)prefersPointerLocked
256{
257 return SDL_GCMouseRelativeMode() ? YES : NO;
258}
259
260#endif // !SDL_PLATFORM_TVOS
261
262/*
263 ---- Keyboard related functionality below this line ----
264 */
265#ifdef SDL_IPHONE_KEYBOARD
266
267@synthesize textInputRect;
268@synthesize keyboardHeight;
269@synthesize textFieldFocused;
270
271// Set ourselves up as a UITextFieldDelegate
272- (void)initKeyboard
273{
274 obligateForBackspace = @" "; // 64 space
275 textField = [[SDLUITextField alloc] initWithFrame:CGRectZero];
276 textField.delegate = self;
277 // placeholder so there is something to delete!
278 textField.text = obligateForBackspace;
279 committedText = textField.text;
280
281 textField.hidden = YES;
282 textFieldFocused = NO;
283
284 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
285#ifndef SDL_PLATFORM_TVOS
286 [center addObserver:self
287 selector:@selector(keyboardWillShow:)
288 name:UIKeyboardWillShowNotification
289 object:nil];
290 [center addObserver:self
291 selector:@selector(keyboardWillHide:)
292 name:UIKeyboardWillHideNotification
293 object:nil];
294 [center addObserver:self
295 selector:@selector(keyboardDidHide:)
296 name:UIKeyboardDidHideNotification
297 object:nil];
298#endif
299 [center addObserver:self
300 selector:@selector(textFieldTextDidChange:)
301 name:UITextFieldTextDidChangeNotification
302 object:nil];
303}
304
305- (NSArray *)keyCommands
306{
307 NSMutableArray *commands = [[NSMutableArray alloc] init];
308 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
309 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
310 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
311 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:kNilOptions action:@selector(handleCommand:)]];
312 [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:kNilOptions action:@selector(handleCommand:)]];
313 return [NSArray arrayWithArray:commands];
314}
315
316- (void)handleCommand:(UIKeyCommand *)keyCommand
317{
318 SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
319 NSString *input = keyCommand.input;
320
321 if (input == UIKeyInputUpArrow) {
322 scancode = SDL_SCANCODE_UP;
323 } else if (input == UIKeyInputDownArrow) {
324 scancode = SDL_SCANCODE_DOWN;
325 } else if (input == UIKeyInputLeftArrow) {
326 scancode = SDL_SCANCODE_LEFT;
327 } else if (input == UIKeyInputRightArrow) {
328 scancode = SDL_SCANCODE_RIGHT;
329 } else if (input == UIKeyInputEscape) {
330 scancode = SDL_SCANCODE_ESCAPE;
331 }
332
333 if (scancode != SDL_SCANCODE_UNKNOWN) {
334 SDL_SendKeyboardKeyAutoRelease(0, scancode);
335 }
336}
337
338- (void)setView:(UIView *)view
339{
340 [super setView:view];
341
342 [view addSubview:textField];
343
344 if (textFieldFocused) {
345 /* startTextInput has been called before the text field was added to the view,
346 * call it again for the text field to actually become first responder. */
347 [self startTextInput];
348 }
349}
350
351- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
352{
353 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
354 rotatingOrientation = YES;
355 [coordinator
356 animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
357 }
358 completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
359 self->rotatingOrientation = NO;
360 }];
361}
362
363- (void)deinitKeyboard
364{
365 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
366#ifndef SDL_PLATFORM_TVOS
367 [center removeObserver:self
368 name:UIKeyboardWillShowNotification
369 object:nil];
370 [center removeObserver:self
371 name:UIKeyboardWillHideNotification
372 object:nil];
373 [center removeObserver:self
374 name:UIKeyboardDidHideNotification
375 object:nil];
376#endif
377 [center removeObserver:self
378 name:UITextFieldTextDidChangeNotification
379 object:nil];
380}
381
382- (void)setTextFieldProperties:(SDL_PropertiesID) props
383{
384 textField.secureTextEntry = NO;
385
386 switch (SDL_GetTextInputType(props)) {
387 default:
388 case SDL_TEXTINPUT_TYPE_TEXT:
389 textField.keyboardType = UIKeyboardTypeDefault;
390 textField.textContentType = nil;
391 break;
392 case SDL_TEXTINPUT_TYPE_TEXT_NAME:
393 textField.keyboardType = UIKeyboardTypeDefault;
394 textField.textContentType = UITextContentTypeName;
395 break;
396 case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
397 textField.keyboardType = UIKeyboardTypeEmailAddress;
398 textField.textContentType = UITextContentTypeEmailAddress;
399 break;
400 case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
401 textField.keyboardType = UIKeyboardTypeDefault;
402 textField.textContentType = UITextContentTypeUsername;
403 break;
404 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
405 textField.keyboardType = UIKeyboardTypeDefault;
406 textField.textContentType = UITextContentTypePassword;
407 textField.secureTextEntry = YES;
408 break;
409 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
410 textField.keyboardType = UIKeyboardTypeDefault;
411 textField.textContentType = UITextContentTypePassword;
412 break;
413 case SDL_TEXTINPUT_TYPE_NUMBER:
414 textField.keyboardType = UIKeyboardTypeDecimalPad;
415 textField.textContentType = nil;
416 break;
417 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
418 textField.keyboardType = UIKeyboardTypeNumberPad;
419 if (@available(iOS 12.0, tvOS 12.0, *)) {
420 textField.textContentType = UITextContentTypeOneTimeCode;
421 } else {
422 textField.textContentType = nil;
423 }
424 textField.secureTextEntry = YES;
425 break;
426 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
427 textField.keyboardType = UIKeyboardTypeNumberPad;
428 if (@available(iOS 12.0, tvOS 12.0, *)) {
429 textField.textContentType = UITextContentTypeOneTimeCode;
430 } else {
431 textField.textContentType = nil;
432 }
433 break;
434 }
435
436 switch (SDL_GetTextInputCapitalization(props)) {
437 default:
438 case SDL_CAPITALIZE_NONE:
439 textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
440 break;
441 case SDL_CAPITALIZE_LETTERS:
442 textField.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
443 break;
444 case SDL_CAPITALIZE_WORDS:
445 textField.autocapitalizationType = UITextAutocapitalizationTypeWords;
446 break;
447 case SDL_CAPITALIZE_SENTENCES:
448 textField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
449 break;
450 }
451
452 if (SDL_GetTextInputAutocorrect(props)) {
453 textField.autocorrectionType = UITextAutocorrectionTypeYes;
454 textField.spellCheckingType = UITextSpellCheckingTypeYes;
455 } else {
456 textField.autocorrectionType = UITextAutocorrectionTypeNo;
457 textField.spellCheckingType = UITextSpellCheckingTypeNo;
458 }
459
460 if (SDL_GetTextInputMultiline(props)) {
461 textField.enablesReturnKeyAutomatically = YES;
462 } else {
463 textField.enablesReturnKeyAutomatically = NO;
464 }
465
466 if (!textField.window) {
467 /* textField has not been added to the view yet,
468 we don't have to do anything. */
469 return;
470 }
471
472 // the text field needs to be re-added to the view in order to update correctly.
473 UIView *superview = textField.superview;
474 [textField removeFromSuperview];
475 [superview addSubview:textField];
476
477 if (SDL_TextInputActive(window)) {
478 [textField becomeFirstResponder];
479 }
480}
481
482/* requests the SDL text field to become focused and accept text input.
483 * also shows the onscreen virtual keyboard if no hardware keyboard is attached. */
484- (bool)startTextInput
485{
486 textFieldFocused = YES;
487 if (!textField.window) {
488 /* textField has not been added to the view yet,
489 * we will try again when that happens. */
490 return true;
491 }
492
493 return [textField becomeFirstResponder];
494}
495
496/* requests the SDL text field to lose focus and stop accepting text input.
497 * also hides the onscreen virtual keyboard if no hardware keyboard is attached. */
498- (bool)stopTextInput
499{
500 textFieldFocused = NO;
501 if (!textField.window) {
502 /* textField has not been added to the view yet,
503 * we will try again when that happens. */
504 return true;
505 }
506
507 [self resetTextState];
508 return [textField resignFirstResponder];
509}
510
511- (void)keyboardWillShow:(NSNotification *)notification
512{
513#ifndef SDL_PLATFORM_TVOS
514 CGRect kbrect = [[notification userInfo][UIKeyboardFrameEndUserInfoKey] CGRectValue];
515
516 /* The keyboard rect is in the coordinate space of the screen/window, but we
517 * want its height in the coordinate space of the view. */
518 kbrect = [self.view convertRect:kbrect fromView:nil];
519
520 [self setKeyboardHeight:(int)kbrect.size.height];
521#endif
522
523 /* A keyboard hide transition has been interrupted with a show (keyboardWillHide has been called but keyboardDidHide didn't).
524 * since text input was stopped by the hide, we have to start it again. */
525 if (hidingKeyboard) {
526 SDL_StartTextInput(window);
527 hidingKeyboard = NO;
528 }
529}
530
531- (void)keyboardWillHide:(NSNotification *)notification
532{
533 hidingKeyboard = YES;
534 [self setKeyboardHeight:0];
535
536 /* When the user dismisses the software keyboard by the "hide" button in the bottom right corner,
537 * we want to reflect that on SDL_TextInputActive by calling SDL_StopTextInput...on certain conditions */
538 if (SDL_TextInputActive(window)
539 /* keyboardWillHide gets called when a hardware keyboard is attached,
540 * keep text input state active if hiding while there is a hardware keyboard.
541 * if the hardware keyboard gets detached, the software keyboard will appear anyway. */
542 && !SDL_HasKeyboard()
543 /* When the device changes orientation, a sequence of hide and show transitions are triggered.
544 * keep text input state active in this case. */
545 && !rotatingOrientation) {
546 SDL_StopTextInput(window);
547 }
548}
549
550- (void)keyboardDidHide:(NSNotification *)notification
551{
552 hidingKeyboard = NO;
553}
554
555- (void)textFieldTextDidChange:(NSNotification *)notification
556{
557 if (textField.markedTextRange == nil) {
558 NSUInteger compareLength = SDL_min(textField.text.length, committedText.length);
559 NSUInteger matchLength;
560
561 // Backspace over characters that are no longer in the string
562 for (matchLength = 0; matchLength < compareLength; ++matchLength) {
563 if ([committedText characterAtIndex:matchLength] != [textField.text characterAtIndex:matchLength]) {
564 break;
565 }
566 }
567 if (matchLength < committedText.length) {
568 size_t deleteLength = SDL_utf8strlen([[committedText substringFromIndex:matchLength] UTF8String]);
569 while (deleteLength > 0) {
570 // Send distinct down and up events for each backspace action
571 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, true);
572 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, false);
573 --deleteLength;
574 }
575 }
576
577 if (matchLength < textField.text.length) {
578 NSString *pendingText = [textField.text substringFromIndex:matchLength];
579 if (!SDL_HardwareKeyboardKeyPressed()) {
580 /* Go through all the characters in the string we've been sent and
581 * convert them to key presses */
582 NSUInteger i;
583 for (i = 0; i < pendingText.length; i++) {
584 SDL_SendKeyboardUnicodeKey(0, [pendingText characterAtIndex:i]);
585 }
586 }
587 SDL_SendKeyboardText([pendingText UTF8String]);
588 }
589 committedText = textField.text;
590 }
591}
592
593- (void)updateKeyboard
594{
595 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *) window->internal;
596
597 CGAffineTransform t = self.view.transform;
598 CGPoint offset = CGPointMake(0.0, 0.0);
599#ifdef SDL_PLATFORM_VISIONOS
600 CGRect frame = UIKit_ComputeViewFrame(window);
601#else
602 CGRect frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
603#endif
604
605 if (self.keyboardHeight && self.textInputRect.h) {
606 int rectbottom = (int)(self.textInputRect.y + self.textInputRect.h);
607 int keybottom = (int)(self.view.bounds.size.height - self.keyboardHeight);
608 if (keybottom < rectbottom) {
609 offset.y = keybottom - rectbottom;
610 }
611 }
612
613 /* Apply this view's transform (except any translation) to the offset, in
614 * order to orient it correctly relative to the frame's coordinate space. */
615 t.tx = 0.0;
616 t.ty = 0.0;
617 offset = CGPointApplyAffineTransform(offset, t);
618
619 // Apply the updated offset to the view's frame.
620 frame.origin.x += offset.x;
621 frame.origin.y += offset.y;
622
623 self.view.frame = frame;
624}
625
626- (void)setKeyboardHeight:(int)height
627{
628 keyboardHeight = height;
629 [self updateKeyboard];
630}
631
632// UITextFieldDelegate method. Invoked when user types something.
633- (BOOL)textField:(UITextField *)_textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
634{
635 if (textField.markedTextRange == nil) {
636 if (textField.text.length < 16) {
637 [self resetTextState];
638 }
639 }
640 return YES;
641}
642
643// Terminates the editing session
644- (BOOL)textFieldShouldReturn:(UITextField *)_textField
645{
646 SDL_SendKeyboardKeyAutoRelease(0, SDL_SCANCODE_RETURN);
647 if (textFieldFocused &&
648 SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) {
649 SDL_StopTextInput(window);
650 }
651 return YES;
652}
653
654- (void)resetTextState
655{
656 textField.text = obligateForBackspace;
657 committedText = textField.text;
658}
659
660#endif
661
662@end
663
664// iPhone keyboard addition functions
665#ifdef SDL_IPHONE_KEYBOARD
666
667static SDL_uikitviewcontroller *GetWindowViewController(SDL_Window *window)
668{
669 if (!window || !window->internal) {
670 SDL_SetError("Invalid window");
671 return nil;
672 }
673
674 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
675
676 return data.viewcontroller;
677}
678
679bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
680{
681 return true;
682}
683
684bool UIKit_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
685{
686 @autoreleasepool {
687 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
688 return [vc startTextInput];
689 }
690}
691
692bool UIKit_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
693{
694 @autoreleasepool {
695 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
696 return [vc stopTextInput];
697 }
698}
699
700void UIKit_SetTextInputProperties(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
701{
702 @autoreleasepool {
703 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
704 [vc setTextFieldProperties:props];
705 }
706}
707
708bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window)
709{
710 @autoreleasepool {
711 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
712 if (vc != nil) {
713 return vc.textFieldFocused;
714 }
715 return false;
716 }
717}
718
719bool UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
720{
721 @autoreleasepool {
722 SDL_uikitviewcontroller *vc = GetWindowViewController(window);
723 if (vc != nil) {
724 vc.textInputRect = window->text_input_rect;
725
726 if (vc.textFieldFocused) {
727 [vc updateKeyboard];
728 }
729 }
730 }
731 return true;
732}
733
734#endif // SDL_IPHONE_KEYBOARD
735
736#endif // SDL_VIDEO_DRIVER_UIKIT
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.h
new file mode 100644
index 0000000..6957670
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.h
@@ -0,0 +1,52 @@
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
22/*
23 * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's
24 * SDL_x11vulkan.h.
25 */
26
27#include "SDL_internal.h"
28
29#ifndef SDL_uikitvulkan_h_
30#define SDL_uikitvulkan_h_
31
32#include "../SDL_vulkan_internal.h"
33#include "../SDL_sysvideo.h"
34
35#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_UIKIT)
36
37extern bool UIKit_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
38extern void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
39extern char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count);
40extern bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this,
41 SDL_Window *window,
42 VkInstance instance,
43 const struct VkAllocationCallbacks *allocator,
44 VkSurfaceKHR *surface);
45extern void UIKit_Vulkan_DestroySurface(SDL_VideoDevice *_this,
46 VkInstance instance,
47 VkSurfaceKHR surface,
48 const struct VkAllocationCallbacks *allocator);
49
50#endif
51
52#endif // SDL_uikitvulkan_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.m
new file mode 100644
index 0000000..332593b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitvulkan.m
@@ -0,0 +1,265 @@
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
22/*
23 * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's
24 * SDL_x11vulkan.c.
25 */
26
27#include "SDL_internal.h"
28
29#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_UIKIT)
30
31#include "SDL_uikitvideo.h"
32#include "SDL_uikitwindow.h"
33
34#include "SDL_uikitvulkan.h"
35#include "SDL_uikitmetalview.h"
36
37#include <dlfcn.h>
38
39const char *defaultPaths[] = {
40 "libvulkan.dylib",
41};
42
43/* Since libSDL is static, could use RTLD_SELF. Using RTLD_DEFAULT is future
44 * proofing. */
45#define DEFAULT_HANDLE RTLD_DEFAULT
46
47bool UIKit_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
48{
49 VkExtensionProperties *extensions = NULL;
50 Uint32 extensionCount = 0;
51 bool hasSurfaceExtension = false;
52 bool hasMetalSurfaceExtension = false;
53 bool hasIOSSurfaceExtension = false;
54 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
55
56 if (_this->vulkan_config.loader_handle) {
57 return SDL_SetError("Vulkan Portability library is already loaded.");
58 }
59
60 // Load the Vulkan loader library
61 if (!path) {
62 path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY);
63 }
64
65 if (!path) {
66 // Handle the case where Vulkan Portability is linked statically.
67 vkGetInstanceProcAddr =
68 (PFN_vkGetInstanceProcAddr)dlsym(DEFAULT_HANDLE,
69 "vkGetInstanceProcAddr");
70 }
71
72 if (vkGetInstanceProcAddr) {
73 _this->vulkan_config.loader_handle = DEFAULT_HANDLE;
74 } else {
75 const char **paths;
76 const char *foundPath = NULL;
77 int numPaths;
78 int i;
79
80 if (path) {
81 paths = &path;
82 numPaths = 1;
83 } else {
84 // Look for the .dylib packaged with the application instead.
85 paths = defaultPaths;
86 numPaths = SDL_arraysize(defaultPaths);
87 }
88
89 for (i = 0; i < numPaths && _this->vulkan_config.loader_handle == NULL; i++) {
90 foundPath = paths[i];
91 _this->vulkan_config.loader_handle = SDL_LoadObject(foundPath);
92 }
93
94 if (_this->vulkan_config.loader_handle == NULL) {
95 return SDL_SetError("Failed to load Vulkan Portability library");
96 }
97
98 SDL_strlcpy(_this->vulkan_config.loader_path, path,
99 SDL_arraysize(_this->vulkan_config.loader_path));
100 vkGetInstanceProcAddr =
101 (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
102 _this->vulkan_config.loader_handle,
103 "vkGetInstanceProcAddr");
104 }
105
106 if (!vkGetInstanceProcAddr) {
107 SDL_SetError("Failed to find %s in either executable or %s: %s",
108 "vkGetInstanceProcAddr",
109 "linked Vulkan Portability library",
110 (const char *)dlerror());
111 goto fail;
112 }
113
114 _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr;
115 _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
116 (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)(
117 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
118
119 if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
120 SDL_SetError("No vkEnumerateInstanceExtensionProperties found.");
121 goto fail;
122 }
123
124 extensions = SDL_Vulkan_CreateInstanceExtensionsList(
125 (PFN_vkEnumerateInstanceExtensionProperties)
126 _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
127 &extensionCount);
128
129 if (!extensions) {
130 goto fail;
131 }
132
133 for (Uint32 i = 0; i < extensionCount; i++) {
134 if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
135 hasSurfaceExtension = true;
136 } else if (SDL_strcmp(VK_EXT_METAL_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
137 hasMetalSurfaceExtension = true;
138 } else if (SDL_strcmp(VK_MVK_IOS_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
139 hasIOSSurfaceExtension = true;
140 }
141 }
142
143 SDL_free(extensions);
144
145 if (!hasSurfaceExtension) {
146 SDL_SetError("Installed Vulkan Portability doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension");
147 goto fail;
148 } else if (!hasMetalSurfaceExtension && !hasIOSSurfaceExtension) {
149 SDL_SetError("Installed Vulkan Portability doesn't implement the " VK_EXT_METAL_SURFACE_EXTENSION_NAME " or " VK_MVK_IOS_SURFACE_EXTENSION_NAME " extensions");
150 goto fail;
151 }
152
153 return true;
154
155fail:
156 _this->vulkan_config.loader_handle = NULL;
157 return false;
158}
159
160void UIKit_Vulkan_UnloadLibrary(SDL_VideoDevice *_this)
161{
162 if (_this->vulkan_config.loader_handle) {
163 if (_this->vulkan_config.loader_handle != DEFAULT_HANDLE) {
164 SDL_UnloadObject(_this->vulkan_config.loader_handle);
165 }
166 _this->vulkan_config.loader_handle = NULL;
167 }
168}
169
170char const* const* UIKit_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
171 Uint32 *count)
172{
173 static const char *const extensionsForUIKit[] = {
174 VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_METAL_SURFACE_EXTENSION_NAME
175 };
176 if(count) {
177 *count = SDL_arraysize(extensionsForUIKit);
178 }
179 return extensionsForUIKit;
180}
181
182bool UIKit_Vulkan_CreateSurface(SDL_VideoDevice *_this,
183 SDL_Window *window,
184 VkInstance instance,
185 const struct VkAllocationCallbacks *allocator,
186 VkSurfaceKHR *surface)
187{
188 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
189 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
190 PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT =
191 (PFN_vkCreateMetalSurfaceEXT)vkGetInstanceProcAddr(
192 (VkInstance)instance,
193 "vkCreateMetalSurfaceEXT");
194 PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK =
195 (PFN_vkCreateIOSSurfaceMVK)vkGetInstanceProcAddr(
196 (VkInstance)instance,
197 "vkCreateIOSSurfaceMVK");
198 VkResult result;
199 SDL_MetalView metalview;
200
201 if (!_this->vulkan_config.loader_handle) {
202 return SDL_SetError("Vulkan is not loaded");
203 }
204
205 if (!vkCreateMetalSurfaceEXT && !vkCreateIOSSurfaceMVK) {
206 return SDL_SetError(VK_EXT_METAL_SURFACE_EXTENSION_NAME " or " VK_MVK_IOS_SURFACE_EXTENSION_NAME
207 " extensions are not enabled in the Vulkan instance.");
208 }
209
210 metalview = UIKit_Metal_CreateView(_this, window);
211 if (metalview == NULL) {
212 return false;
213 }
214
215 if (vkCreateMetalSurfaceEXT) {
216 VkMetalSurfaceCreateInfoEXT createInfo = {};
217 createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
218 createInfo.pNext = NULL;
219 createInfo.flags = 0;
220 createInfo.pLayer = (__bridge const CAMetalLayer *)
221 UIKit_Metal_GetLayer(_this, metalview);
222 result = vkCreateMetalSurfaceEXT(instance, &createInfo, allocator, surface);
223 if (result != VK_SUCCESS) {
224 UIKit_Metal_DestroyView(_this, metalview);
225 return SDL_SetError("vkCreateMetalSurfaceEXT failed: %s", SDL_Vulkan_GetResultString(result));
226 }
227 } else {
228 VkIOSSurfaceCreateInfoMVK createInfo = {};
229 createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
230 createInfo.pNext = NULL;
231 createInfo.flags = 0;
232 createInfo.pView = (const void *)metalview;
233 result = vkCreateIOSSurfaceMVK(instance, &createInfo,
234 allocator, surface);
235 if (result != VK_SUCCESS) {
236 UIKit_Metal_DestroyView(_this, metalview);
237 return SDL_SetError("vkCreateIOSSurfaceMVK failed: %s", SDL_Vulkan_GetResultString(result));
238 }
239 }
240
241 /* Unfortunately there's no SDL_Vulkan_DestroySurface function we can call
242 * Metal_DestroyView from. Right now the metal view's ref count is +2 (one
243 * from returning a new view object in CreateView, and one because it's
244 * a subview of the window.) If we release the view here to make it +1, it
245 * will be destroyed when the window is destroyed.
246 *
247 * TODO: Now that we have SDL_Vulkan_DestroySurface someone with enough
248 * knowledge of Metal can proceed. */
249 CFBridgingRelease(metalview);
250
251 return true;
252}
253
254void UIKit_Vulkan_DestroySurface(SDL_VideoDevice *_this,
255 VkInstance instance,
256 VkSurfaceKHR surface,
257 const struct VkAllocationCallbacks *allocator)
258{
259 if (_this->vulkan_config.loader_handle) {
260 SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
261 // TODO: Add CFBridgingRelease(metalview) here perhaps?
262 }
263}
264
265#endif
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.h b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.h
new file mode 100644
index 0000000..da1fb63
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.h
@@ -0,0 +1,56 @@
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#ifndef SDL_uikitwindow_h_
22#define SDL_uikitwindow_h_
23
24#include "../SDL_sysvideo.h"
25#import "SDL_uikitvideo.h"
26#import "SDL_uikitview.h"
27#import "SDL_uikitviewcontroller.h"
28
29extern bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props);
30extern void UIKit_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window);
31extern void UIKit_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window);
32extern void UIKit_HideWindow(SDL_VideoDevice *_this, SDL_Window *window);
33extern void UIKit_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window);
34extern void UIKit_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered);
35extern SDL_FullscreenResult UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen);
36extern void UIKit_UpdatePointerLock(SDL_VideoDevice *_this, SDL_Window *window);
37extern void UIKit_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
38extern void UIKit_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h);
39
40extern NSUInteger UIKit_GetSupportedOrientations(SDL_Window *window);
41
42#define SDL_METALVIEW_TAG 255
43
44@class UIWindow;
45
46@interface SDL_UIKitWindowData : NSObject
47
48@property(nonatomic, strong) UIWindow *uiwindow;
49@property(nonatomic, strong) SDL_uikitviewcontroller *viewcontroller;
50
51// Array of SDL_uikitviews owned by this window.
52@property(nonatomic, copy) NSMutableArray *views;
53
54@end
55
56#endif // SDL_uikitwindow_h_
diff --git a/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.m b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.m
new file mode 100644
index 0000000..2b258b1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitwindow.m
@@ -0,0 +1,471 @@
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#include "../SDL_pixels_c.h"
27#include "../../events/SDL_events_c.h"
28
29#include "SDL_uikitvideo.h"
30#include "SDL_uikitevents.h"
31#include "SDL_uikitmodes.h"
32#include "SDL_uikitwindow.h"
33#include "SDL_uikitappdelegate.h"
34#include "SDL_uikitview.h"
35#include "SDL_uikitopenglview.h"
36
37#include <Foundation/Foundation.h>
38
39@implementation SDL_UIKitWindowData
40
41@synthesize uiwindow;
42@synthesize viewcontroller;
43@synthesize views;
44
45- (instancetype)init
46{
47 if ((self = [super init])) {
48 views = [NSMutableArray new];
49 }
50
51 return self;
52}
53
54@end
55
56@interface SDL_uikitwindow : UIWindow
57
58- (void)layoutSubviews;
59
60@end
61
62@implementation SDL_uikitwindow
63
64- (void)layoutSubviews
65{
66#ifndef SDL_PLATFORM_VISIONOS
67 // Workaround to fix window orientation issues in iOS 8.
68 /* As of July 1 2019, I haven't been able to reproduce any orientation
69 * issues with this disabled on iOS 12. The issue this is meant to fix might
70 * only happen on iOS 8, or it might have been fixed another way with other
71 * code... This code prevents split view (iOS 9+) from working on iPads, so
72 * we want to avoid using it if possible. */
73 if (!UIKit_IsSystemVersionAtLeast(9.0)) {
74 self.frame = self.screen.bounds;
75 }
76#endif
77 [super layoutSubviews];
78}
79
80@end
81
82static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow *uiwindow, bool created)
83{
84 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
85 SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal;
86 SDL_uikitview *view;
87
88#ifdef SDL_PLATFORM_VISIONOS
89 CGRect frame = UIKit_ComputeViewFrame(window);
90#else
91 CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen);
92#endif
93
94 int width = (int)frame.size.width;
95 int height = (int)frame.size.height;
96
97 SDL_UIKitWindowData *data = [[SDL_UIKitWindowData alloc] init];
98 if (!data) {
99 return SDL_OutOfMemory();
100 }
101
102 window->internal = (SDL_WindowData *)CFBridgingRetain(data);
103
104 data.uiwindow = uiwindow;
105
106#ifndef SDL_PLATFORM_VISIONOS
107 if (displaydata.uiscreen != [UIScreen mainScreen]) {
108 window->flags &= ~SDL_WINDOW_RESIZABLE; // window is NEVER resizable
109 window->flags &= ~SDL_WINDOW_INPUT_FOCUS; // never has input focus
110 window->flags |= SDL_WINDOW_BORDERLESS; // never has a status bar.
111 }
112#endif
113
114#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
115 if (displaydata.uiscreen == [UIScreen mainScreen]) {
116 NSUInteger orients = UIKit_GetSupportedOrientations(window);
117 BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0;
118 BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown)) != 0;
119
120 // Make sure the width/height are oriented correctly
121 if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) {
122 int temp = width;
123 width = height;
124 height = temp;
125 }
126 }
127#endif // !SDL_PLATFORM_TVOS
128
129#if 0 // Don't set the x/y position, it's already placed on a display
130 window->x = 0;
131 window->y = 0;
132#endif
133 window->w = width;
134 window->h = height;
135
136 /* The View Controller will handle rotating the view when the device
137 * orientation changes. This will trigger resize events, if appropriate. */
138 data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window];
139
140 /* The window will initially contain a generic view so resizes, touch events,
141 * etc. can be handled without an active OpenGL view/context. */
142 view = [[SDL_uikitview alloc] initWithFrame:frame];
143
144 /* Sets this view as the controller's view, and adds the view to the window
145 * hierarchy. */
146 [view setSDLWindow:window];
147
148 SDL_PropertiesID props = SDL_GetWindowProperties(window);
149 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, (__bridge void *)data.uiwindow);
150 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG);
151
152 return true;
153}
154
155bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
156{
157 @autoreleasepool {
158 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
159 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal;
160 SDL_Window *other;
161
162 // We currently only handle a single window per display on iOS
163 for (other = _this->windows; other; other = other->next) {
164 if (other != window && SDL_GetVideoDisplayForWindow(other) == display) {
165 return SDL_SetError("Only one window allowed per display.");
166 }
167 }
168
169 /* If monitor has a resolution of 0x0 (hasn't been explicitly set by the
170 * user, so it's in standby), try to force the display to a resolution
171 * that most closely matches the desired window size. */
172#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
173 const CGSize origsize = data.uiscreen.currentMode.size;
174 if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) {
175 SDL_DisplayMode bestmode;
176 bool include_high_density_modes = false;
177 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
178 include_high_density_modes = true;
179 }
180 if (SDL_GetClosestFullscreenDisplayMode(display->id, window->w, window->h, 0.0f, include_high_density_modes, &bestmode)) {
181 SDL_UIKitDisplayModeData *modedata = (__bridge SDL_UIKitDisplayModeData *)bestmode.internal;
182 [data.uiscreen setCurrentMode:modedata.uiscreenmode];
183
184 /* desktop_mode doesn't change here (the higher level will
185 * use it to set all the screens back to their defaults
186 * upon window destruction, SDL_Quit(), etc. */
187 SDL_SetCurrentDisplayMode(display, &bestmode);
188 }
189 }
190
191 if (data.uiscreen == [UIScreen mainScreen]) {
192 if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
193 [UIApplication sharedApplication].statusBarHidden = YES;
194 } else {
195 [UIApplication sharedApplication].statusBarHidden = NO;
196 }
197 }
198#endif // !SDL_PLATFORM_TVOS
199
200 // ignore the size user requested, and make a fullscreen window
201 // !!! FIXME: can we have a smaller view?
202#ifdef SDL_PLATFORM_VISIONOS
203 UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:CGRectMake(window->x, window->y, window->w, window->h)];
204#else
205 UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds];
206#endif
207
208 // put the window on an external display if appropriate.
209#ifndef SDL_PLATFORM_VISIONOS
210 if (data.uiscreen != [UIScreen mainScreen]) {
211 [uiwindow setScreen:data.uiscreen];
212 }
213#endif
214
215 if (!SetupWindowData(_this, window, uiwindow, true)) {
216 return false;
217 }
218 }
219
220 return true;
221}
222
223void UIKit_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
224{
225 @autoreleasepool {
226 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
227 data.viewcontroller.title = @(window->title);
228 }
229}
230
231void UIKit_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
232{
233 @autoreleasepool {
234 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
235 [data.uiwindow makeKeyAndVisible];
236
237 // Make this window the current mouse focus for touch input
238 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
239 SDL_UIKitDisplayData *displaydata = (__bridge SDL_UIKitDisplayData *)display->internal;
240#ifndef SDL_PLATFORM_VISIONOS
241 if (displaydata.uiscreen == [UIScreen mainScreen])
242#endif
243 {
244 SDL_SetMouseFocus(window);
245 SDL_SetKeyboardFocus(window);
246 }
247 }
248}
249
250void UIKit_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
251{
252 @autoreleasepool {
253 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
254 data.uiwindow.hidden = YES;
255 }
256}
257
258void UIKit_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
259{
260#if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
261 /* We don't currently offer a concept of "raising" the SDL window, since
262 * we only allow one per display, in the iOS fashion.
263 * However, we use this entry point to rebind the context to the view
264 * during OnWindowRestored processing. */
265 _this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx);
266#endif
267}
268
269static void UIKit_UpdateWindowBorder(SDL_VideoDevice *_this, SDL_Window *window)
270{
271 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
272 SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
273
274#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS)
275 if (data.uiwindow.screen == [UIScreen mainScreen]) {
276 if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
277 [UIApplication sharedApplication].statusBarHidden = YES;
278 } else {
279 [UIApplication sharedApplication].statusBarHidden = NO;
280 }
281
282 [viewcontroller setNeedsStatusBarAppearanceUpdate];
283 }
284
285 // Update the view's frame to account for the status bar change.
286 viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
287#endif // !SDL_PLATFORM_TVOS
288
289#ifdef SDL_IPHONE_KEYBOARD
290 // Make sure the view is offset correctly when the keyboard is visible.
291 [viewcontroller updateKeyboard];
292#endif
293
294 [viewcontroller.view setNeedsLayout];
295 [viewcontroller.view layoutIfNeeded];
296}
297
298void UIKit_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered)
299{
300 @autoreleasepool {
301 if (bordered) {
302 window->flags &= ~SDL_WINDOW_BORDERLESS;
303 } else {
304 window->flags |= SDL_WINDOW_BORDERLESS;
305 }
306 UIKit_UpdateWindowBorder(_this, window);
307 }
308}
309
310SDL_FullscreenResult UIKit_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
311{
312 @autoreleasepool {
313 SDL_SendWindowEvent(window, fullscreen ? SDL_EVENT_WINDOW_ENTER_FULLSCREEN : SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
314 UIKit_UpdateWindowBorder(_this, window);
315 }
316 return SDL_FULLSCREEN_SUCCEEDED;
317}
318
319void UIKit_UpdatePointerLock(SDL_VideoDevice *_this, SDL_Window *window)
320{
321#ifndef SDL_PLATFORM_TVOS
322 @autoreleasepool {
323 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
324 SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
325 if (@available(iOS 14.0, *)) {
326 [viewcontroller setNeedsUpdateOfPrefersPointerLocked];
327 }
328 }
329#endif // !SDL_PLATFORM_TVOS
330}
331
332void UIKit_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
333{
334 @autoreleasepool {
335 if (window->internal != NULL) {
336 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
337 NSArray *views = nil;
338
339 [data.viewcontroller stopAnimation];
340
341 /* Detach all views from this window. We use a copy of the array
342 * because setSDLWindow will remove the object from the original
343 * array, which would be undesirable if we were iterating over it. */
344 views = [data.views copy];
345 for (SDL_uikitview *view in views) {
346 [view setSDLWindow:NULL];
347 }
348
349 /* iOS may still hold a reference to the window after we release it.
350 * We want to make sure the SDL view controller isn't accessed in
351 * that case, because it would contain an invalid pointer to the old
352 * SDL window. */
353 data.uiwindow.rootViewController = nil;
354 data.uiwindow.hidden = YES;
355
356 CFRelease(window->internal);
357 window->internal = NULL;
358 }
359 }
360}
361
362void UIKit_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h)
363{
364 @autoreleasepool {
365 SDL_UIKitWindowData *windata = (__bridge SDL_UIKitWindowData *)window->internal;
366 UIView *view = windata.viewcontroller.view;
367 CGSize size = view.bounds.size;
368 CGFloat scale = 1.0;
369
370
371 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
372#ifndef SDL_PLATFORM_VISIONOS
373 scale = windata.uiwindow.screen.nativeScale;
374#else
375 scale = 2.0;
376#endif
377 }
378
379
380 /* Integer truncation of fractional values matches SDL_uikitmetalview and
381 * SDL_uikitopenglview. */
382 *w = (int)(size.width * scale);
383 *h = (int)(size.height * scale);
384 }
385}
386
387#ifndef SDL_PLATFORM_TVOS
388NSUInteger
389UIKit_GetSupportedOrientations(SDL_Window *window)
390{
391 const char *hint = SDL_GetHint(SDL_HINT_ORIENTATIONS);
392 NSUInteger validOrientations = UIInterfaceOrientationMaskAll;
393 NSUInteger orientationMask = 0;
394
395 @autoreleasepool {
396 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
397 UIApplication *app = [UIApplication sharedApplication];
398
399 /* Get all possible valid orientations. If the app delegate doesn't tell
400 * us, we get the orientations from Info.plist via UIApplication. */
401 if ([app.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) {
402 validOrientations = [app.delegate application:app supportedInterfaceOrientationsForWindow:data.uiwindow];
403 } else {
404 validOrientations = [app supportedInterfaceOrientationsForWindow:data.uiwindow];
405 }
406
407 if (hint != NULL) {
408 NSArray *orientations = [@(hint) componentsSeparatedByString:@" "];
409
410 if ([orientations containsObject:@"LandscapeLeft"]) {
411 orientationMask |= UIInterfaceOrientationMaskLandscapeLeft;
412 }
413 if ([orientations containsObject:@"LandscapeRight"]) {
414 orientationMask |= UIInterfaceOrientationMaskLandscapeRight;
415 }
416 if ([orientations containsObject:@"Portrait"]) {
417 orientationMask |= UIInterfaceOrientationMaskPortrait;
418 }
419 if ([orientations containsObject:@"PortraitUpsideDown"]) {
420 orientationMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
421 }
422 }
423
424 if (orientationMask == 0 && (window->flags & SDL_WINDOW_RESIZABLE)) {
425 // any orientation is okay.
426 orientationMask = UIInterfaceOrientationMaskAll;
427 }
428
429 if (orientationMask == 0) {
430 if (window->floating.w >= window->floating.h) {
431 orientationMask |= UIInterfaceOrientationMaskLandscape;
432 }
433 if (window->floating.h >= window->floating.w) {
434 orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
435 }
436 }
437
438 // Don't allow upside-down orientation on phones, so answering calls is in the natural orientation
439 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
440 orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown;
441 }
442
443 /* If none of the specified orientations are actually supported by the
444 * app, we'll revert to what the app supports. An exception would be
445 * thrown by the system otherwise. */
446 if ((validOrientations & orientationMask) == 0) {
447 orientationMask = validOrientations;
448 }
449 }
450
451 return orientationMask;
452}
453#endif // !SDL_PLATFORM_TVOS
454
455bool SDL_SetiOSAnimationCallback(SDL_Window *window, int interval, SDL_iOSAnimationCallback callback, void *callbackParam)
456{
457 if (!window || !window->internal) {
458 return SDL_SetError("Invalid window");
459 }
460
461 @autoreleasepool {
462 SDL_UIKitWindowData *data = (__bridge SDL_UIKitWindowData *)window->internal;
463 [data.viewcontroller setAnimationCallback:interval
464 callback:callback
465 callbackParam:callbackParam];
466 }
467
468 return true;
469}
470
471#endif // SDL_VIDEO_DRIVER_UIKIT