Atlas - SDL_uikitmodes.m

Home / ext / SDL / src / video / uikit Lines: 1 | Size: 16574 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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{ 275 CGSize size = CGSizeMake(SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT); 276 SDL_VideoDisplay display; 277 SDL_DisplayMode mode; 278 279 SDL_zero(mode); 280 mode.w = (int)size.width; 281 mode.h = (int)size.height; 282 mode.pixel_density = 2; 283 mode.format = SDL_PIXELFORMAT_ABGR8888; 284 mode.refresh_rate = 90.0f; 285 286 display.natural_orientation = SDL_ORIENTATION_LANDSCAPE; 287 288 display.desktop_mode = mode; 289 290 SDL_UIKitDisplayData *data = [[SDL_UIKitDisplayData alloc] init]; 291 292 if (!data) { 293 UIKit_FreeDisplayModeData(&display.desktop_mode); 294 return SDL_OutOfMemory(); 295 } 296 297 display.internal = (SDL_DisplayData *)CFBridgingRetain(data); 298 if (SDL_AddVideoDisplay(&display, send_event) == 0) { 299 return false; 300 } 301 return true; 302} 303#endif 304 305#ifndef SDL_PLATFORM_VISIONOS 306 307void UIKit_DelDisplay(UIScreen *uiscreen, bool send_event) 308{ 309 SDL_DisplayID *displays; 310 int i; 311 312 displays = SDL_GetDisplays(NULL); 313 if (displays) { 314 for (i = 0; displays[i]; ++i) { 315 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]); 316 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; 317 318 if (data && data.uiscreen == uiscreen) { 319 CFRelease(display->internal); 320 display->internal = NULL; 321 SDL_DelVideoDisplay(displays[i], send_event); 322 break; 323 } 324 } 325 SDL_free(displays); 326 } 327} 328 329bool UIKit_IsDisplayLandscape(UIScreen *uiscreen) 330{ 331#ifndef SDL_PLATFORM_TVOS 332 if (uiscreen == [UIScreen mainScreen]) { 333 return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation); 334 } else 335#endif // !SDL_PLATFORM_TVOS 336 { 337 CGSize size = uiscreen.bounds.size; 338 return (size.width > size.height); 339 } 340} 341#endif 342bool UIKit_InitModes(SDL_VideoDevice *_this) 343{ 344 @autoreleasepool { 345#ifdef SDL_PLATFORM_VISIONOS 346 UIKit_AddDisplay(false); 347#else 348 for (UIScreen *uiscreen in [UIScreen screens]) { 349 if (!UIKit_AddDisplay(uiscreen, false)) { 350 return false; 351 } 352 } 353#endif 354 355#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) 356 SDL_OnApplicationDidChangeStatusBarOrientation(); 357#endif 358 359#ifndef SDL_PLATFORM_VISIONOS 360 [SDL_DisplayWatch start]; 361#endif 362 } 363 364 return true; 365} 366 367bool UIKit_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) 368{ 369#ifndef SDL_PLATFORM_VISIONOS 370 @autoreleasepool { 371 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; 372 373 bool isLandscape = UIKit_IsDisplayLandscape(data.uiscreen); 374 bool addRotation = (data.uiscreen == [UIScreen mainScreen]); 375 NSArray *availableModes = nil; 376 377#ifdef SDL_PLATFORM_TVOS 378 addRotation = false; 379 availableModes = @[ data.uiscreen.currentMode ]; 380#else 381 availableModes = data.uiscreen.availableModes; 382#endif 383 384 for (UIScreenMode *uimode in availableModes) { 385 CGSize size = GetUIScreenModeSize(data.uiscreen, uimode); 386 int w = (int)size.width; 387 int h = (int)size.height; 388 389 // Make sure the width/height are oriented correctly 390 if (isLandscape != (w > h)) { 391 int tmp = w; 392 w = h; 393 h = tmp; 394 } 395 396 UIKit_AddDisplayMode(display, w, h, data.uiscreen, uimode, addRotation); 397 } 398 } 399#endif 400 return true; 401} 402 403bool UIKit_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode) 404{ 405#ifndef SDL_PLATFORM_VISIONOS 406 @autoreleasepool { 407 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; 408 409#ifndef SDL_PLATFORM_TVOS 410 SDL_UIKitDisplayModeData *modedata = (__bridge SDL_UIKitDisplayModeData *)mode->internal; 411 [data.uiscreen setCurrentMode:modedata.uiscreenmode]; 412#endif 413 414 if (data.uiscreen == [UIScreen mainScreen]) { 415 /* [UIApplication setStatusBarOrientation:] no longer works reliably 416 * in recent iOS versions, so we can't rotate the screen when setting 417 * the display mode. */ 418 if (mode->w > mode->h) { 419 if (!UIKit_IsDisplayLandscape(data.uiscreen)) { 420 return SDL_SetError("Screen orientation does not match display mode size"); 421 } 422 } else if (mode->w < mode->h) { 423 if (UIKit_IsDisplayLandscape(data.uiscreen)) { 424 return SDL_SetError("Screen orientation does not match display mode size"); 425 } 426 } 427 } 428 } 429#endif 430 return true; 431} 432 433bool UIKit_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) 434{ 435 @autoreleasepool { 436 SDL_UIKitDisplayData *data = (__bridge SDL_UIKitDisplayData *)display->internal; 437#ifdef SDL_PLATFORM_VISIONOS 438 CGRect frame = CGRectMake(0, 0, SDL_XR_SCREENWIDTH, SDL_XR_SCREENHEIGHT); 439#else 440 CGRect frame = data.uiscreen.bounds; 441#endif 442 443 /* the default function iterates displays to make a fake offset, 444 as if all the displays were side-by-side, which is fine for iOS. */ 445 if (!SDL_GetDisplayBounds(display->id, rect)) { 446 return false; 447 } 448 449 rect->x += (int)frame.origin.x; 450 rect->y += (int)frame.origin.y; 451 rect->w = (int)frame.size.width; 452 rect->h = (int)frame.size.height; 453 } 454 455 return true; 456} 457 458void UIKit_QuitModes(SDL_VideoDevice *_this) 459{ 460#ifndef SDL_PLATFORM_VISIONOS 461 [SDL_DisplayWatch stop]; 462#endif 463 464 // Release Objective-C objects, so higher level doesn't free() them. 465 int i, j; 466 @autoreleasepool { 467 for (i = 0; i < _this->num_displays; i++) { 468 SDL_VideoDisplay *display = _this->displays[i]; 469 470 UIKit_FreeDisplayModeData(&display->desktop_mode); 471 for (j = 0; j < display->num_fullscreen_modes; j++) { 472 SDL_DisplayMode *mode = &display->fullscreen_modes[j]; 473 UIKit_FreeDisplayModeData(mode); 474 } 475 476 if (display->internal != NULL) { 477 CFRelease(display->internal); 478 display->internal = NULL; 479 } 480 } 481 } 482} 483 484#if !defined(SDL_PLATFORM_TVOS) && !defined(SDL_PLATFORM_VISIONOS) 485void SDL_OnApplicationDidChangeStatusBarOrientation(void) 486{ 487 BOOL isLandscape = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation); 488 SDL_VideoDisplay *display = SDL_GetVideoDisplay(SDL_GetPrimaryDisplay()); 489 490 if (display) { 491 SDL_DisplayMode *mode = &display->desktop_mode; 492 SDL_DisplayOrientation orientation = SDL_ORIENTATION_UNKNOWN; 493 int i; 494 495 /* The desktop display mode should be kept in sync with the screen 496 * orientation so that updating a window's fullscreen state to 497 * fullscreen desktop keeps the window dimensions in the 498 * correct orientation. */ 499 if (isLandscape != (mode->w > mode->h)) { 500 SDL_DisplayMode new_mode; 501 SDL_copyp(&new_mode, mode); 502 new_mode.w = mode->h; 503 new_mode.h = mode->w; 504 505 // Make sure we don't free the current display mode data 506 mode->internal = NULL; 507 508 SDL_SetDesktopDisplayMode(display, &new_mode); 509 } 510 511 // Same deal with the fullscreen modes 512 for (i = 0; i < display->num_fullscreen_modes; ++i) { 513 mode = &display->fullscreen_modes[i]; 514 if (isLandscape != (mode->w > mode->h)) { 515 int height = mode->w; 516 mode->w = mode->h; 517 mode->h = height; 518 } 519 } 520 521 switch ([UIApplication sharedApplication].statusBarOrientation) { 522 case UIInterfaceOrientationPortrait: 523 orientation = SDL_ORIENTATION_PORTRAIT; 524 break; 525 case UIInterfaceOrientationPortraitUpsideDown: 526 orientation = SDL_ORIENTATION_PORTRAIT_FLIPPED; 527 break; 528 case UIInterfaceOrientationLandscapeLeft: 529 // Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 530 orientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED; 531 break; 532 case UIInterfaceOrientationLandscapeRight: 533 // Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 534 orientation = SDL_ORIENTATION_LANDSCAPE; 535 break; 536 default: 537 break; 538 } 539 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ORIENTATION, orientation, 0); 540 } 541} 542#endif // !SDL_PLATFORM_TVOS 543 544#endif // SDL_VIDEO_DRIVER_UIKIT 545
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.