sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_uikitmodes.m (18141B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 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 SDL_VIDEO_DRIVER_UIKIT
     24 
     25 #include "SDL_system.h"
     26 #include "SDL_uikitmodes.h"
     27 
     28 #include "../../events/SDL_events_c.h"
     29 
     30 #import <sys/utsname.h>
     31 
     32 @implementation SDL_DisplayData
     33 
     34 - (instancetype)initWithScreen:(UIScreen*)screen
     35 {
     36     if (self = [super init]) {
     37         self.uiscreen = screen;
     38 
     39         /*
     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
     42          */
     43         NSDictionary* devices = @{
     44             @"iPhone1,1": @163,
     45             @"iPhone1,2": @163,
     46             @"iPhone2,1": @163,
     47             @"iPhone3,1": @326,
     48             @"iPhone3,2": @326,
     49             @"iPhone3,3": @326,
     50             @"iPhone4,1": @326,
     51             @"iPhone5,1": @326,
     52             @"iPhone5,2": @326,
     53             @"iPhone5,3": @326,
     54             @"iPhone5,4": @326,
     55             @"iPhone6,1": @326,
     56             @"iPhone6,2": @326,
     57             @"iPhone7,1": @401,
     58             @"iPhone7,2": @326,
     59             @"iPhone8,1": @326,
     60             @"iPhone8,2": @401,
     61             @"iPhone8,4": @326,
     62             @"iPhone9,1": @326,
     63             @"iPhone9,2": @401,
     64             @"iPhone9,3": @326,
     65             @"iPhone9,4": @401,
     66             @"iPhone10,1": @326,
     67             @"iPhone10,2": @401,
     68             @"iPhone10,3": @458,
     69             @"iPhone10,4": @326,
     70             @"iPhone10,5": @401,
     71             @"iPhone10,6": @458,
     72             @"iPhone11,2": @458,
     73             @"iPhone11,4": @458,
     74             @"iPhone11,6": @458,
     75             @"iPhone11,8": @326,
     76             @"iPhone12,1": @326,
     77             @"iPhone12,3": @458,
     78             @"iPhone12,5": @458,
     79             @"iPad1,1": @132,
     80             @"iPad2,1": @132,
     81             @"iPad2,2": @132,
     82             @"iPad2,3": @132,
     83             @"iPad2,4": @132,
     84             @"iPad2,5": @163,
     85             @"iPad2,6": @163,
     86             @"iPad2,7": @163,
     87             @"iPad3,1": @264,
     88             @"iPad3,2": @264,
     89             @"iPad3,3": @264,
     90             @"iPad3,4": @264,
     91             @"iPad3,5": @264,
     92             @"iPad3,6": @264,
     93             @"iPad4,1": @264,
     94             @"iPad4,2": @264,
     95             @"iPad4,3": @264,
     96             @"iPad4,4": @326,
     97             @"iPad4,5": @326,
     98             @"iPad4,6": @326,
     99             @"iPad4,7": @326,
    100             @"iPad4,8": @326,
    101             @"iPad4,9": @326,
    102             @"iPad5,1": @326,
    103             @"iPad5,2": @326,
    104             @"iPad5,3": @264,
    105             @"iPad5,4": @264,
    106             @"iPad6,3": @264,
    107             @"iPad6,4": @264,
    108             @"iPad6,7": @264,
    109             @"iPad6,8": @264,
    110             @"iPad6,11": @264,
    111             @"iPad6,12": @264,
    112             @"iPad7,1": @264,
    113             @"iPad7,2": @264,
    114             @"iPad7,3": @264,
    115             @"iPad7,4": @264,
    116             @"iPad7,5": @264,
    117             @"iPad7,6": @264,
    118             @"iPad7,11": @264,
    119             @"iPad7,12": @264,
    120             @"iPad8,1": @264,
    121             @"iPad8,2": @264,
    122             @"iPad8,3": @264,
    123             @"iPad8,4": @264,
    124             @"iPad8,5": @264,
    125             @"iPad8,6": @264,
    126             @"iPad8,7": @264,
    127             @"iPad8,8": @264,
    128             @"iPad11,1": @326,
    129             @"iPad11,2": @326,
    130             @"iPad11,3": @326,
    131             @"iPad11,4": @326,
    132             @"iPod1,1": @163,
    133             @"iPod2,1": @163,
    134             @"iPod3,1": @163,
    135             @"iPod4,1": @326,
    136             @"iPod5,1": @326,
    137             @"iPod7,1": @326,
    138             @"iPod9,1": @326,
    139         };
    140 
    141         struct utsname systemInfo;
    142         uname(&systemInfo);
    143         NSString* deviceName =
    144             [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
    145         id foundDPI = devices[deviceName];
    146         if (foundDPI) {
    147             self.screenDPI = (float)[foundDPI integerValue];
    148         } else {
    149             /*
    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)
    152              */
    153     #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
    154             float scale = (float)screen.nativeScale;
    155     #else
    156             float scale = (float)screen.scale;
    157     #endif
    158             float defaultDPI;
    159             if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    160                 defaultDPI = 132.0f;
    161             } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
    162                 defaultDPI = 163.0f;
    163             } else {
    164                 defaultDPI = 160.0f;
    165             }
    166             self.screenDPI = scale * defaultDPI;
    167         }
    168     }
    169     return self;
    170 }
    171 
    172 @synthesize uiscreen;
    173 @synthesize screenDPI;
    174 
    175 @end
    176 
    177 @implementation SDL_DisplayModeData
    178 
    179 @synthesize uiscreenmode;
    180 
    181 @end
    182 
    183 @interface SDL_DisplayWatch : NSObject
    184 @end
    185 
    186 @implementation SDL_DisplayWatch
    187 
    188 + (void)start
    189 {
    190     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    191 
    192     [center addObserver:self selector:@selector(screenConnected:)
    193             name:UIScreenDidConnectNotification object:nil];
    194     [center addObserver:self selector:@selector(screenDisconnected:)
    195             name:UIScreenDidDisconnectNotification object:nil];
    196 }
    197 
    198 + (void)stop
    199 {
    200     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    201 
    202     [center removeObserver:self
    203             name:UIScreenDidConnectNotification object:nil];
    204     [center removeObserver:self
    205             name:UIScreenDidDisconnectNotification object:nil];
    206 }
    207 
    208 + (void)screenConnected:(NSNotification*)notification
    209 {
    210     UIScreen *uiscreen = [notification object];
    211     UIKit_AddDisplay(uiscreen, SDL_TRUE);
    212 }
    213 
    214 + (void)screenDisconnected:(NSNotification*)notification
    215 {
    216     UIScreen *uiscreen = [notification object];
    217     UIKit_DelDisplay(uiscreen);
    218 }
    219 
    220 @end
    221 
    222 static int
    223 UIKit_AllocateDisplayModeData(SDL_DisplayMode * mode,
    224     UIScreenMode * uiscreenmode)
    225 {
    226     SDL_DisplayModeData *data = nil;
    227 
    228     if (uiscreenmode != nil) {
    229         /* Allocate the display mode data */
    230         data = [[SDL_DisplayModeData alloc] init];
    231         if (!data) {
    232             return SDL_OutOfMemory();
    233         }
    234 
    235         data.uiscreenmode = uiscreenmode;
    236     }
    237 
    238     mode->driverdata = (void *) CFBridgingRetain(data);
    239 
    240     return 0;
    241 }
    242 
    243 static void
    244 UIKit_FreeDisplayModeData(SDL_DisplayMode * mode)
    245 {
    246     if (mode->driverdata != NULL) {
    247         CFRelease(mode->driverdata);
    248         mode->driverdata = NULL;
    249     }
    250 }
    251 
    252 static NSUInteger
    253 UIKit_GetDisplayModeRefreshRate(UIScreen *uiscreen)
    254 {
    255 #ifdef __IPHONE_10_3
    256     if ([uiscreen respondsToSelector:@selector(maximumFramesPerSecond)]) {
    257         return uiscreen.maximumFramesPerSecond;
    258     }
    259 #endif
    260     return 0;
    261 }
    262 
    263 static int
    264 UIKit_AddSingleDisplayMode(SDL_VideoDisplay * display, int w, int h,
    265     UIScreen * uiscreen, UIScreenMode * uiscreenmode)
    266 {
    267     SDL_DisplayMode mode;
    268     SDL_zero(mode);
    269 
    270     if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) {
    271         return -1;
    272     }
    273 
    274     mode.format = SDL_PIXELFORMAT_ABGR8888;
    275     mode.refresh_rate = (int) UIKit_GetDisplayModeRefreshRate(uiscreen);
    276     mode.w = w;
    277     mode.h = h;
    278 
    279     if (SDL_AddDisplayMode(display, &mode)) {
    280         return 0;
    281     } else {
    282         UIKit_FreeDisplayModeData(&mode);
    283         return -1;
    284     }
    285 }
    286 
    287 static int
    288 UIKit_AddDisplayMode(SDL_VideoDisplay * display, int w, int h, UIScreen * uiscreen,
    289                      UIScreenMode * uiscreenmode, SDL_bool addRotation)
    290 {
    291     if (UIKit_AddSingleDisplayMode(display, w, h, uiscreen, uiscreenmode) < 0) {
    292         return -1;
    293     }
    294 
    295     if (addRotation) {
    296         /* Add the rotated version */
    297         if (UIKit_AddSingleDisplayMode(display, h, w, uiscreen, uiscreenmode) < 0) {
    298             return -1;
    299         }
    300     }
    301 
    302     return 0;
    303 }
    304 
    305 int
    306 UIKit_AddDisplay(UIScreen *uiscreen, SDL_bool send_event)
    307 {
    308     UIScreenMode *uiscreenmode = uiscreen.currentMode;
    309     CGSize size = uiscreen.bounds.size;
    310     SDL_VideoDisplay display;
    311     SDL_DisplayMode mode;
    312     SDL_zero(mode);
    313 
    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;
    319     }
    320 
    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;
    325 
    326     if (UIKit_AllocateDisplayModeData(&mode, uiscreenmode) < 0) {
    327         return -1;
    328     }
    329 
    330     SDL_zero(display);
    331     display.desktop_mode = mode;
    332     display.current_mode = mode;
    333 
    334     /* Allocate the display data */
    335     SDL_DisplayData *data = [[SDL_DisplayData alloc] initWithScreen:uiscreen];
    336     if (!data) {
    337         UIKit_FreeDisplayModeData(&display.desktop_mode);
    338         return SDL_OutOfMemory();
    339     }
    340 
    341     display.driverdata = (void *) CFBridgingRetain(data);
    342     SDL_AddVideoDisplay(&display, send_event);
    343 
    344     return 0;
    345 }
    346 
    347 void
    348 UIKit_DelDisplay(UIScreen *uiscreen)
    349 {
    350     int i;
    351 
    352     for (i = 0; i < SDL_GetNumVideoDisplays(); ++i) {
    353         SDL_DisplayData *data = (__bridge SDL_DisplayData *)SDL_GetDisplayDriverData(i);
    354 
    355         if (data && data.uiscreen == uiscreen) {
    356             CFRelease(SDL_GetDisplayDriverData(i));
    357             SDL_DelVideoDisplay(i);
    358             return;
    359         }
    360     }
    361 }
    362 
    363 SDL_bool
    364 UIKit_IsDisplayLandscape(UIScreen *uiscreen)
    365 {
    366 #if !TARGET_OS_TV
    367     if (uiscreen == [UIScreen mainScreen]) {
    368         return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
    369     } else
    370 #endif /* !TARGET_OS_TV */
    371     {
    372         CGSize size = uiscreen.bounds.size;
    373         return (size.width > size.height);
    374     }
    375 }
    376 
    377 int
    378 UIKit_InitModes(_THIS)
    379 {
    380     @autoreleasepool {
    381         for (UIScreen *uiscreen in [UIScreen screens]) {
    382             if (UIKit_AddDisplay(uiscreen, SDL_FALSE) < 0) {
    383                 return -1;
    384             }
    385         }
    386 #if !TARGET_OS_TV
    387         SDL_OnApplicationDidChangeStatusBarOrientation();
    388 #endif
    389 
    390         [SDL_DisplayWatch start];
    391     }
    392 
    393     return 0;
    394 }
    395 
    396 void
    397 UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
    398 {
    399     @autoreleasepool {
    400         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
    401 
    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;
    406 
    407 #if TARGET_OS_TV
    408         addRotation = SDL_FALSE;
    409         availableModes = @[data.uiscreen.currentMode];
    410 #else
    411         availableModes = data.uiscreen.availableModes;
    412 #endif
    413 
    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.)
    417              *
    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);
    428 
    429             /* Make sure the width/height are oriented correctly */
    430             if (isLandscape != (w > h)) {
    431                 int tmp = w;
    432                 w = h;
    433                 h = tmp;
    434             }
    435 
    436             UIKit_AddDisplayMode(display, w, h, data.uiscreen, uimode, addRotation);
    437         }
    438     }
    439 }
    440 
    441 int
    442 UIKit_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi)
    443 {
    444     @autoreleasepool {
    445         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
    446         float dpi = data.screenDPI;
    447 
    448         if (ddpi) {
    449             *ddpi = dpi * (float)SDL_sqrt(2.0);
    450         }
    451         if (hdpi) {
    452             *hdpi = dpi;
    453         }
    454         if (vdpi) {
    455             *vdpi = dpi;
    456         }
    457     }
    458 
    459     return 0;
    460 }
    461 
    462 int
    463 UIKit_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
    464 {
    465     @autoreleasepool {
    466         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
    467 
    468 #if !TARGET_OS_TV
    469         SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)mode->driverdata;
    470         [data.uiscreen setCurrentMode:modedata.uiscreenmode];
    471 #endif
    472 
    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");
    480                 }
    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");
    484                 }
    485             }
    486         }
    487     }
    488 
    489     return 0;
    490 }
    491 
    492 int
    493 UIKit_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
    494 {
    495     @autoreleasepool {
    496         int displayIndex = (int) (display - _this->displays);
    497         SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
    498         CGRect frame = data.uiscreen.bounds;
    499 
    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) {
    503             return -1;
    504         }
    505 
    506 #if !TARGET_OS_TV && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
    507         if (!UIKit_IsSystemVersionAtLeast(7.0)) {
    508             frame = [data.uiscreen applicationFrame];
    509         }
    510 #endif
    511 
    512         rect->x += frame.origin.x;
    513         rect->y += frame.origin.y;
    514         rect->w = frame.size.width;
    515         rect->h = frame.size.height;
    516     }
    517 
    518     return 0;
    519 }
    520 
    521 void
    522 UIKit_QuitModes(_THIS)
    523 {
    524     [SDL_DisplayWatch stop];
    525 
    526     /* Release Objective-C objects, so higher level doesn't free() them. */
    527     int i, j;
    528     @autoreleasepool {
    529         for (i = 0; i < _this->num_displays; i++) {
    530             SDL_VideoDisplay *display = &_this->displays[i];
    531 
    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);
    536             }
    537 
    538             if (display->driverdata != NULL) {
    539                 CFRelease(display->driverdata);
    540                 display->driverdata = NULL;
    541             }
    542         }
    543     }
    544 }
    545 
    546 #if !TARGET_OS_TV
    547 void SDL_OnApplicationDidChangeStatusBarOrientation()
    548 {
    549     BOOL isLandscape = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
    550     SDL_VideoDisplay *display = SDL_GetDisplay(0);
    551 
    552     if (display) {
    553         SDL_DisplayMode *desktopmode = &display->desktop_mode;
    554         SDL_DisplayMode *currentmode = &display->current_mode;
    555         SDL_DisplayOrientation orientation = SDL_ORIENTATION_UNKNOWN;
    556 
    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;
    565         }
    566 
    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;
    572         }
    573 
    574         switch ([UIApplication sharedApplication].statusBarOrientation) {
    575         case UIInterfaceOrientationPortrait:
    576             orientation = SDL_ORIENTATION_PORTRAIT;
    577             break;
    578         case UIInterfaceOrientationPortraitUpsideDown:
    579             orientation = SDL_ORIENTATION_PORTRAIT_FLIPPED;
    580             break;
    581         case UIInterfaceOrientationLandscapeLeft:
    582             /* Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 */
    583             orientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED;
    584             break;
    585         case UIInterfaceOrientationLandscapeRight:
    586             /* Bug: UIInterfaceOrientationLandscapeLeft/Right are reversed - http://openradar.appspot.com/7216046 */
    587             orientation = SDL_ORIENTATION_LANDSCAPE;
    588             break;
    589         default:
    590             break;
    591         }
    592         SDL_SendDisplayEvent(display, SDL_DISPLAYEVENT_ORIENTATION, orientation);
    593     }
    594 }
    595 #endif /* !TARGET_OS_TV */
    596 
    597 #endif /* SDL_VIDEO_DRIVER_UIKIT */
    598 
    599 /* vi: set ts=4 sw=4 expandtab: */