summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m')
-rw-r--r--contrib/SDL-3.2.8/src/video/uikit/SDL_uikitmodes.m543
1 files changed, 543 insertions, 0 deletions
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