1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2016 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_syswm.h" 26#include "SDL_video.h" 27#include "SDL_mouse.h" 28#include "SDL_assert.h" 29#include "SDL_hints.h" 30#include "../SDL_sysvideo.h" 31#include "../SDL_pixels_c.h" 32#include "../../events/SDL_events_c.h" 33 34#include "SDL_uikitvideo.h" 35#include "SDL_uikitevents.h" 36#include "SDL_uikitmodes.h" 37#include "SDL_uikitwindow.h" 38#import "SDL_uikitappdelegate.h" 39 40#import "SDL_uikitview.h" 41#import "SDL_uikitopenglview.h" 42 43#include <Foundation/Foundation.h> 44 45@implementation SDL_WindowData 46 47@synthesize uiwindow; 48@synthesize viewcontroller; 49@synthesize views; 50 51- (instancetype)init 52{ 53 if ((self = [super init])) { 54 views = [NSMutableArray new]; 55 } 56 57 return self; 58} 59 60@end 61 62@interface SDL_uikitwindow : UIWindow 63 64- (void)layoutSubviews; 65 66@end 67 68@implementation SDL_uikitwindow 69 70- (void)layoutSubviews 71{ 72 /* Workaround to fix window orientation issues in iOS 8+. */ 73 self.frame = self.screen.bounds; 74 [super layoutSubviews]; 75} 76 77@end 78 79 80static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bool created) 81{ 82 SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); 83 SDL_DisplayData *displaydata = (__bridge SDL_DisplayData *) display->driverdata; 84 SDL_uikitview *view; 85 86 CGRect frame = UIKit_ComputeViewFrame(window, displaydata.uiscreen); 87 int width = (int) frame.size.width; 88 int height = (int) frame.size.height; 89 90 SDL_WindowData *data = [[SDL_WindowData alloc] init]; 91 if (!data) { 92 return SDL_OutOfMemory(); 93 } 94 95 window->driverdata = (void *) CFBridgingRetain(data); 96 97 data.uiwindow = uiwindow; 98 99 /* only one window on iOS, always shown */ 100 window->flags &= ~SDL_WINDOW_HIDDEN; 101 102 if (displaydata.uiscreen != [UIScreen mainScreen]) { 103 window->flags &= ~SDL_WINDOW_RESIZABLE; /* window is NEVER resizable */ 104 window->flags &= ~SDL_WINDOW_INPUT_FOCUS; /* never has input focus */ 105 window->flags |= SDL_WINDOW_BORDERLESS; /* never has a status bar. */ 106 } 107 108#if !TARGET_OS_TV 109 if (displaydata.uiscreen == [UIScreen mainScreen]) { 110 NSUInteger orients = UIKit_GetSupportedOrientations(window); 111 BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0; 112 BOOL supportsPortrait = (orients & (UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown)) != 0; 113 114 /* Make sure the width/height are oriented correctly */ 115 if ((width > height && !supportsLandscape) || (height > width && !supportsPortrait)) { 116 int temp = width; 117 width = height; 118 height = temp; 119 } 120 } 121#endif /* !TARGET_OS_TV */ 122 123 window->x = 0; 124 window->y = 0; 125 window->w = width; 126 window->h = height; 127 128 /* The View Controller will handle rotating the view when the device 129 * orientation changes. This will trigger resize events, if appropriate. */ 130 data.viewcontroller = [[SDL_uikitviewcontroller alloc] initWithSDLWindow:window]; 131 132 /* The window will initially contain a generic view so resizes, touch events, 133 * etc. can be handled without an active OpenGL view/context. */ 134 view = [[SDL_uikitview alloc] initWithFrame:frame]; 135 136 /* Sets this view as the controller's view, and adds the view to the window 137 * heirarchy. */ 138 [view setSDLWindow:window]; 139 140 /* Make this window the current mouse focus for touch input */ 141 if (displaydata.uiscreen == [UIScreen mainScreen]) { 142 SDL_SetMouseFocus(window); 143 SDL_SetKeyboardFocus(window); 144 } 145 146 return 0; 147} 148 149int 150UIKit_CreateWindow(_THIS, SDL_Window *window) 151{ 152 @autoreleasepool { 153 SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); 154 SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata; 155 156 /* SDL currently puts this window at the start of display's linked list. We rely on this. */ 157 SDL_assert(_this->windows == window); 158 159 /* We currently only handle a single window per display on iOS */ 160 if (window->next != NULL) { 161 return SDL_SetError("Only one window allowed per display."); 162 } 163 164 /* If monitor has a resolution of 0x0 (hasn't been explicitly set by the 165 * user, so it's in standby), try to force the display to a resolution 166 * that most closely matches the desired window size. */ 167#if !TARGET_OS_TV 168 const CGSize origsize = data.uiscreen.currentMode.size; 169 if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) { 170 if (display->num_display_modes == 0) { 171 _this->GetDisplayModes(_this, display); 172 } 173 174 int i; 175 const SDL_DisplayMode *bestmode = NULL; 176 for (i = display->num_display_modes; i >= 0; i--) { 177 const SDL_DisplayMode *mode = &display->display_modes[i]; 178 if ((mode->w >= window->w) && (mode->h >= window->h)) { 179 bestmode = mode; 180 } 181 } 182 183 if (bestmode) { 184 SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)bestmode->driverdata; 185 [data.uiscreen setCurrentMode:modedata.uiscreenmode]; 186 187 /* desktop_mode doesn't change here (the higher level will 188 * use it to set all the screens back to their defaults 189 * upon window destruction, SDL_Quit(), etc. */ 190 display->current_mode = *bestmode; 191 } 192 } 193 194 if (data.uiscreen == [UIScreen mainScreen]) { 195 if (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) { 196 [UIApplication sharedApplication].statusBarHidden = YES; 197 } else { 198 [UIApplication sharedApplication].statusBarHidden = NO; 199 } 200 } 201#endif /* !TARGET_OS_TV */ 202 203 /* ignore the size user requested, and make a fullscreen window */ 204 /* !!! FIXME: can we have a smaller view? */ 205 UIWindow *uiwindow = [[SDL_uikitwindow alloc] initWithFrame:data.uiscreen.bounds]; 206 207 /* put the window on an external display if appropriate. */ 208 if (data.uiscreen != [UIScreen mainScreen]) { 209 [uiwindow setScreen:data.uiscreen]; 210 } 211 212 if (SetupWindowData(_this, window, uiwindow, SDL_TRUE) < 0) { 213 return -1; 214 } 215 } 216 217 return 1; 218} 219 220void 221UIKit_SetWindowTitle(_THIS, SDL_Window * window) 222{ 223 @autoreleasepool { 224 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 225 data.viewcontroller.title = @(window->title); 226 } 227} 228 229void 230UIKit_ShowWindow(_THIS, SDL_Window * window) 231{ 232 @autoreleasepool { 233 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 234 [data.uiwindow makeKeyAndVisible]; 235 } 236} 237 238void 239UIKit_HideWindow(_THIS, SDL_Window * window) 240{ 241 @autoreleasepool { 242 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 243 data.uiwindow.hidden = YES; 244 } 245} 246 247void 248UIKit_RaiseWindow(_THIS, SDL_Window * window) 249{ 250 /* We don't currently offer a concept of "raising" the SDL window, since 251 * we only allow one per display, in the iOS fashion. 252 * However, we use this entry point to rebind the context to the view 253 * during OnWindowRestored processing. */ 254 _this->GL_MakeCurrent(_this, _this->current_glwin, _this->current_glctx); 255} 256 257static void 258UIKit_UpdateWindowBorder(_THIS, SDL_Window * window) 259{ 260 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 261 SDL_uikitviewcontroller *viewcontroller = data.viewcontroller; 262 263#if !TARGET_OS_TV 264 if (data.uiwindow.screen == [UIScreen mainScreen]) { 265 if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) { 266 [UIApplication sharedApplication].statusBarHidden = YES; 267 } else { 268 [UIApplication sharedApplication].statusBarHidden = NO; 269 } 270 271 /* iOS 7+ won't update the status bar until we tell it to. */ 272 if ([viewcontroller respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { 273 [viewcontroller setNeedsStatusBarAppearanceUpdate]; 274 } 275 } 276 277 /* Update the view's frame to account for the status bar change. */ 278 viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen); 279#endif /* !TARGET_OS_TV */ 280 281#ifdef SDL_IPHONE_KEYBOARD 282 /* Make sure the view is offset correctly when the keyboard is visible. */ 283 [viewcontroller updateKeyboard]; 284#endif 285 286 [viewcontroller.view setNeedsLayout]; 287 [viewcontroller.view layoutIfNeeded]; 288} 289 290void 291UIKit_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered) 292{ 293 @autoreleasepool { 294 UIKit_UpdateWindowBorder(_this, window); 295 } 296} 297 298void 299UIKit_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen) 300{ 301 @autoreleasepool { 302 UIKit_UpdateWindowBorder(_this, window); 303 } 304} 305 306void 307UIKit_DestroyWindow(_THIS, SDL_Window * window) 308{ 309 @autoreleasepool { 310 if (window->driverdata != NULL) { 311 SDL_WindowData *data = (SDL_WindowData *) CFBridgingRelease(window->driverdata); 312 NSArray *views = nil; 313 314 [data.viewcontroller stopAnimation]; 315 316 /* Detach all views from this window. We use a copy of the array 317 * because setSDLWindow will remove the object from the original 318 * array, which would be undesirable if we were iterating over it. */ 319 views = [data.views copy]; 320 for (SDL_uikitview *view in views) { 321 [view setSDLWindow:NULL]; 322 } 323 324 /* iOS may still hold a reference to the window after we release it. 325 * We want to make sure the SDL view controller isn't accessed in 326 * that case, because it would contain an invalid pointer to the old 327 * SDL window. */ 328 data.uiwindow.rootViewController = nil; 329 data.uiwindow.hidden = YES; 330 } 331 } 332 window->driverdata = NULL; 333} 334 335SDL_bool 336UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info) 337{ 338 @autoreleasepool { 339 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 340 341 if (info->version.major <= SDL_MAJOR_VERSION) { 342 int versionnum = SDL_VERSIONNUM(info->version.major, info->version.minor, info->version.patch); 343 344 info->subsystem = SDL_SYSWM_UIKIT; 345 info->info.uikit.window = data.uiwindow; 346 347 /* These struct members were added in SDL 2.0.4. */ 348 if (versionnum >= SDL_VERSIONNUM(2,0,4)) { 349 if ([data.viewcontroller.view isKindOfClass:[SDL_uikitopenglview class]]) { 350 SDL_uikitopenglview *glview = (SDL_uikitopenglview *)data.viewcontroller.view; 351 info->info.uikit.framebuffer = glview.drawableFramebuffer; 352 info->info.uikit.colorbuffer = glview.drawableRenderbuffer; 353 info->info.uikit.resolveFramebuffer = glview.msaaResolveFramebuffer; 354 } else { 355 info->info.uikit.framebuffer = 0; 356 info->info.uikit.colorbuffer = 0; 357 info->info.uikit.resolveFramebuffer = 0; 358 } 359 } 360 361 return SDL_TRUE; 362 } else { 363 SDL_SetError("Application not compiled with SDL %d.%d\n", 364 SDL_MAJOR_VERSION, SDL_MINOR_VERSION); 365 return SDL_FALSE; 366 } 367 } 368} 369 370#if !TARGET_OS_TV 371NSUInteger 372UIKit_GetSupportedOrientations(SDL_Window * window) 373{ 374 const char *hint = SDL_GetHint(SDL_HINT_ORIENTATIONS); 375 NSUInteger validOrientations = UIInterfaceOrientationMaskAll; 376 NSUInteger orientationMask = 0; 377 378 @autoreleasepool { 379 SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata; 380 UIApplication *app = [UIApplication sharedApplication]; 381 382 /* Get all possible valid orientations. If the app delegate doesn't tell 383 * us, we get the orientations from Info.plist via UIApplication. */ 384 if ([app.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) { 385 validOrientations = [app.delegate application:app supportedInterfaceOrientationsForWindow:data.uiwindow]; 386 } else if ([app respondsToSelector:@selector(supportedInterfaceOrientationsForWindow:)]) { 387 validOrientations = [app supportedInterfaceOrientationsForWindow:data.uiwindow]; 388 } 389 390 if (hint != NULL) { 391 NSArray *orientations = [@(hint) componentsSeparatedByString:@" "]; 392 393 if ([orientations containsObject:@"LandscapeLeft"]) { 394 orientationMask |= UIInterfaceOrientationMaskLandscapeLeft; 395 } 396 if ([orientations containsObject:@"LandscapeRight"]) { 397 orientationMask |= UIInterfaceOrientationMaskLandscapeRight; 398 } 399 if ([orientations containsObject:@"Portrait"]) { 400 orientationMask |= UIInterfaceOrientationMaskPortrait; 401 } 402 if ([orientations containsObject:@"PortraitUpsideDown"]) { 403 orientationMask |= UIInterfaceOrientationMaskPortraitUpsideDown; 404 } 405 } 406 407 if (orientationMask == 0 && (window->flags & SDL_WINDOW_RESIZABLE)) { 408 /* any orientation is okay. */ 409 orientationMask = UIInterfaceOrientationMaskAll; 410 } 411 412 if (orientationMask == 0) { 413 if (window->w >= window->h) { 414 orientationMask |= UIInterfaceOrientationMaskLandscape; 415 } 416 if (window->h >= window->w) { 417 orientationMask |= (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown); 418 } 419 } 420 421 /* Don't allow upside-down orientation on phones, so answering calls is in the natural orientation */ 422 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) { 423 orientationMask &= ~UIInterfaceOrientationMaskPortraitUpsideDown; 424 } 425 426 /* If none of the specified orientations are actually supported by the 427 * app, we'll revert to what the app supports. An exception would be 428 * thrown by the system otherwise. */ 429 if ((validOrientations & orientationMask) == 0) { 430 orientationMask = validOrientations; 431 } 432 } 433 434 return orientationMask; 435} 436#endif /* !TARGET_OS_TV */ 437 438int 439SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam) 440{ 441 if (!window || !window->driverdata) { 442 return SDL_SetError("Invalid window"); 443 } 444 445 @autoreleasepool { 446 SDL_WindowData *data = (__bridge SDL_WindowData *)window->driverdata; 447 [data.viewcontroller setAnimationCallback:interval 448 callback:callback 449 callbackParam:callbackParam]; 450 } 451 452 return 0; 453} 454 455#endif /* SDL_VIDEO_DRIVER_UIKIT */ 456 457/* vi: set ts=4 sw=4 expandtab: */ 458