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