2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../../SDL_internal.h"
23 #if SDL_VIDEO_DRIVER_UIKIT
25 #include "SDL_system.h"
26 #include "SDL_uikitmodes.h"
28 #include "../../events/SDL_events_c.h"
30 #import <sys/utsname.h>
32 @implementation SDL_DisplayData
34 - (instancetype)initWithScreen:(UIScreen*)screen
36 if (self = [super init]) {
37 self.uiscreen = screen;
40 * A well up to date list of device info can be found here:
41 * https://github.com/lmirosevic/GBDeviceInfo/blob/master/GBDeviceInfo/GBDeviceInfo_iOS.m
43 NSDictionary* devices = @{
141 struct utsname systemInfo;
143 NSString* deviceName =
144 [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
145 id foundDPI = devices[deviceName];
147 self.screenDPI = (float)[foundDPI integerValue];
150 * Estimate the DPI based on the screen scale multiplied by the base DPI for the device
151 * type (e.g. based on iPhone 1 and iPad 1)
153 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
154 float scale = (float)screen.nativeScale;
156 float scale = (float)screen.scale;
159 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
161 } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
166 self.screenDPI = scale * defaultDPI;
172 @synthesize uiscreen;
173 @synthesize screenDPI;
177 @implementation SDL_DisplayModeData
179 @synthesize uiscreenmode;
183 @interface SDL_DisplayWatch : NSObject
186 @implementation SDL_DisplayWatch
190 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
192 [center addObserver:self selector:@selector(screenConnected:)
193 name:UIScreenDidConnectNotification object:nil];
194 [center addObserver:self selector:@selector(screenDisconnected:)
195 name:UIScreenDidDisconnectNotification object:nil];
200 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
202 [center removeObserver:self
203 name:UIScreenDidConnectNotification object:nil];
204 [center removeObserver:self
205 name:UIScreenDidDisconnectNotification object:nil];
208 + (void)screenConnected:(NSNotification*)notification
210 UIScreen *uiscreen = [notification object];
211 UIKit_AddDisplay(uiscreen, SDL_TRUE);
214 + (void)screenDisconnected:(NSNotification*)notification
216 UIScreen *uiscreen = [notification object];
217 UIKit_DelDisplay(uiscreen);
223 UIKit_AllocateDisplayModeData(SDL_DisplayMode * mode,
224 UIScreenMode * uiscreenmode)
226 SDL_DisplayModeData *data = nil;
228 if (uiscreenmode != nil) {
229 /* Allocate the display mode data */
230 data = [[SDL_DisplayModeData alloc] init];
232 return SDL_OutOfMemory();
235 data.uiscreenmode = uiscreenmode;
238 mode->driverdata = (void *) CFBridgingRetain(data);
244 UIKit_FreeDisplayModeData(SDL_DisplayMode * mode)
246 if (mode->driverdata != NULL) {
247 CFRelease(mode->driverdata);
248 mode->driverdata = NULL;
253 UIKit_GetDisplayModeRefreshRate(UIScreen *uiscreen)
256 if ([uiscreen respondsToSelector:@selector(maximumFramesPerSecond)]) {
257 return uiscreen.maximumFramesPerSecond;
264 UIKit_AddSingleDisplayMode(SDL_VideoDisplay * display, int w, int h,
265 UIScreen * uiscreen, UIScreenMode * uiscreenmode)
267 SDL_DisplayMode mode;
270 if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) {
274 mode.format = SDL_PIXELFORMAT_ABGR8888;
275 mode.refresh_rate = (int) UIKit_GetDisplayModeRefreshRate(uiscreen);
279 if (SDL_AddDisplayMode(display, &mode)) {
282 UIKit_FreeDisplayModeData(&mode);
288 UIKit_AddDisplayMode(SDL_VideoDisplay * display, int w, int h, UIScreen * uiscreen,
289 UIScreenMode * uiscreenmode, SDL_bool addRotation)
291 if (UIKit_AddSingleDisplayMode(display, w, h, uiscreen, uiscreenmode) < 0) {
296 /* Add the rotated version */
297 if (UIKit_AddSingleDisplayMode(display, h, w, uiscreen, uiscreenmode) < 0) {
306 UIKit_AddDisplay(UIScreen *uiscreen, SDL_bool send_event)
308 UIScreenMode *uiscreenmode = uiscreen.currentMode;
309 CGSize size = uiscreen.bounds.size;
310 SDL_VideoDisplay display;
311 SDL_DisplayMode mode;
314 /* Make sure the width/height are oriented correctly */
315 if (UIKit_IsDisplayLandscape(uiscreen) != (size.width > size.height)) {
316 CGFloat height = size.width;
317 size.width = size.height;
318 size.height = height;
321 mode.format = SDL_PIXELFORMAT_ABGR8888;
322 mode.refresh_rate = (int) UIKit_GetDisplayModeRefreshRate(uiscreen);
323 mode.w = (int) size.width;
324 mode.h = (int) size.height;
326 if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) {
331 display.desktop_mode = mode;
332 display.current_mode = mode;
334 /* Allocate the display data */
335 SDL_DisplayData *data = [[SDL_DisplayData alloc] initWithScreen:uiscreen];
337 UIKit_FreeDisplayModeData(&display.desktop_mode);
338 return SDL_OutOfMemory();
341 display.driverdata = (void *) CFBridgingRetain(data);
342 SDL_AddVideoDisplay(&display, send_event);
348 UIKit_DelDisplay(UIScreen *uiscreen)
352 for (i = 0; i < SDL_GetNumVideoDisplays(); ++i) {
353 SDL_DisplayData *data = (__bridge SDL_DisplayData *)SDL_GetDisplayDriverData(i);
355 if (data && data.uiscreen == uiscreen) {
356 CFRelease(SDL_GetDisplayDriverData(i));
357 SDL_DelVideoDisplay(i);
364 UIKit_IsDisplayLandscape(UIScreen *uiscreen)
367 if (uiscreen == [UIScreen mainScreen]) {
368 return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
370 #endif /* !TARGET_OS_TV */
372 CGSize size = uiscreen.bounds.size;
373 return (size.width > size.height);
378 UIKit_InitModes(_THIS)
381 for (UIScreen *uiscreen in [UIScreen screens]) {
382 if (UIKit_AddDisplay(uiscreen, SDL_FALSE) < 0) {
387 SDL_OnApplicationDidChangeStatusBarOrientation();
390 [SDL_DisplayWatch start];
397 UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
400 SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
402 SDL_bool isLandscape = UIKit_IsDisplayLandscape(data.uiscreen);
403 SDL_bool addRotation = (data.uiscreen == [UIScreen mainScreen]);
404 CGFloat scale = data.uiscreen.scale;
405 NSArray *availableModes = nil;
408 addRotation = SDL_FALSE;
409 availableModes = @[data.uiscreen.currentMode];
411 availableModes = data.uiscreen.availableModes;
414 for (UIScreenMode *uimode in availableModes) {
415 /* The size of a UIScreenMode is in pixels, but we deal exclusively
416 * in points (except in SDL_GL_GetDrawableSize.)
418 * For devices such as iPhone 6/7/8 Plus, the UIScreenMode reported
419 * by iOS is not in physical pixels of the display, but rather the
420 * point size times the scale. For example, on iOS 12.2 on iPhone 8
421 * Plus the uimode.size is 1242x2208 and the uiscreen.scale is 3
422 * thus this will give the size in points which is 414x736. The code
423 * used to use the nativeScale, assuming UIScreenMode returned raw
424 * physical pixels (as suggested by its documentation, but in
425 * practice it is returning the retina pixels). */
426 int w = (int)(uimode.size.width / scale);
427 int h = (int)(uimode.size.height / scale);
429 /* Make sure the width/height are oriented correctly */
430 if (isLandscape != (w > h)) {
436 UIKit_AddDisplayMode(display, w, h, data.uiscreen, uimode, addRotation);
442 UIKit_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi)
445 SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
446 float dpi = data.screenDPI;
449 *ddpi = dpi * (float)SDL_sqrt(2.0);
463 UIKit_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
466 SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
469 SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)mode->driverdata;
470 [data.uiscreen setCurrentMode:modedata.uiscreenmode];
473 if (data.uiscreen == [UIScreen mainScreen]) {
474 /* [UIApplication setStatusBarOrientation:] no longer works reliably
475 * in recent iOS versions, so we can't rotate the screen when setting
476 * the display mode. */
477 if (mode->w > mode->h) {
478 if (!UIKit_IsDisplayLandscape(data.uiscreen)) {
479 return SDL_SetError("Screen orientation does not match display mode size");
481 } else if (mode->w < mode->h) {
482 if (UIKit_IsDisplayLandscape(data.uiscreen)) {
483 return SDL_SetError("Screen orientation does not match display mode size");
493 UIKit_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
496 int displayIndex = (int) (display - _this->displays);
497 SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
498 CGRect frame = data.uiscreen.bounds;
500 /* the default function iterates displays to make a fake offset,
501 as if all the displays were side-by-side, which is fine for iOS. */
502 if (SDL_GetDisplayBounds(displayIndex, rect) < 0) {
506 #if !TARGET_OS_TV && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
507 if (!UIKit_IsSystemVersionAtLeast(7.0)) {
508 frame = [data.uiscreen applicationFrame];
512 rect->x += frame.origin.x;
513 rect->y += frame.origin.y;
514 rect->w = frame.size.width;
515 rect->h = frame.size.height;
522 UIKit_QuitModes(_THIS)
524 [SDL_DisplayWatch stop];
526 /* Release Objective-C objects, so higher level doesn't free() them. */
529 for (i = 0; i < _this->num_displays; i++) {
530 SDL_VideoDisplay *display = &_this->displays[i];
532 UIKit_FreeDisplayModeData(&display->desktop_mode);
533 for (j = 0; j < display->num_display_modes; j++) {
534 SDL_DisplayMode *mode = &display->display_modes[j];
535 UIKit_FreeDisplayModeData(mode);
538 if (display->driverdata != NULL) {
539 CFRelease(display->driverdata);
540 display->driverdata = NULL;
547 void SDL_OnApplicationDidChangeStatusBarOrientation()
549 BOOL isLandscape = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
550 SDL_VideoDisplay *display = SDL_GetDisplay(0);
553 SDL_DisplayMode *desktopmode = &display->desktop_mode;
554 SDL_DisplayMode *currentmode = &display->current_mode;
555 SDL_DisplayOrientation orientation = SDL_ORIENTATION_UNKNOWN;
557 /* The desktop display mode should be kept in sync with the screen
558 * orientation so that updating a window's fullscreen state to
559 * SDL_WINDOW_FULLSCREEN_DESKTOP keeps the window dimensions in the
560 * correct orientation. */
561 if (isLandscape != (desktopmode->w > desktopmode->h)) {
562 int height = desktopmode->w;
563 desktopmode->w = desktopmode->h;
564 desktopmode->h = height;
567 /* Same deal with the current mode + SDL_GetCurrentDisplayMode. */
568 if (isLandscape != (currentmode->w > currentmode->h)) {
569 int height = currentmode->w;
570 currentmode->w = currentmode->h;
571 currentmode->h = height;
574 switch ([UIApplication sharedApplication].statusBarOrientation) {
575 case UIInterfaceOrientationPortrait:
576 orientation = SDL_ORIENTATION_PORTRAIT;
578 case UIInterfaceOrientationPortraitUpsideDown:
579 orientation = SDL_ORIENTATION_PORTRAIT_FLIPPED;
581 case UIInterfaceOrientationLandscapeLeft:
582 /* Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 */
583 orientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED;
585 case UIInterfaceOrientationLandscapeRight:
586 /* Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 */
587 orientation = SDL_ORIENTATION_LANDSCAPE;
592 SDL_SendDisplayEvent(display, SDL_DISPLAYEVENT_ORIENTATION, orientation);
595 #endif /* !TARGET_OS_TV */
597 #endif /* SDL_VIDEO_DRIVER_UIKIT */
599 /* vi: set ts=4 sw=4 expandtab: */