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#import <UIKit/UIKit.h>
26
27#include "SDL_video.h"
28#include "SDL_mouse.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#include "SDL_uikitopengles.h"
39#include "SDL_uikitclipboard.h"
40#include "SDL_uikitvulkan.h"
41#include "SDL_uikitmetalview.h"
42
43#define UIKITVID_DRIVER_NAME "uikit"
44
45@implementation SDL_VideoData
46
47@end
48
49/* Initialization/Query functions */
50static int UIKit_VideoInit(_THIS);
51static void UIKit_VideoQuit(_THIS);
52
53/* DUMMY driver bootstrap functions */
54
55static int
56UIKit_Available(void)
57{
58    return 1;
59}
60
61static void UIKit_DeleteDevice(SDL_VideoDevice * device)
62{
63    @autoreleasepool {
64        CFRelease(device->driverdata);
65        SDL_free(device);
66    }
67}
68
69static SDL_VideoDevice *
70UIKit_CreateDevice(int devindex)
71{
72    @autoreleasepool {
73        SDL_VideoDevice *device;
74        SDL_VideoData *data;
75
76        /* Initialize all variables that we clean on shutdown */
77        device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
78        if (device) {
79            data = [SDL_VideoData new];
80        } else {
81            SDL_free(device);
82            SDL_OutOfMemory();
83            return (0);
84        }
85
86        device->driverdata = (void *) CFBridgingRetain(data);
87
88        /* Set the function pointers */
89        device->VideoInit = UIKit_VideoInit;
90        device->VideoQuit = UIKit_VideoQuit;
91        device->GetDisplayModes = UIKit_GetDisplayModes;
92        device->SetDisplayMode = UIKit_SetDisplayMode;
93        device->PumpEvents = UIKit_PumpEvents;
94        device->SuspendScreenSaver = UIKit_SuspendScreenSaver;
95        device->CreateSDLWindow = UIKit_CreateWindow;
96        device->SetWindowTitle = UIKit_SetWindowTitle;
97        device->ShowWindow = UIKit_ShowWindow;
98        device->HideWindow = UIKit_HideWindow;
99        device->RaiseWindow = UIKit_RaiseWindow;
100        device->SetWindowBordered = UIKit_SetWindowBordered;
101        device->SetWindowFullscreen = UIKit_SetWindowFullscreen;
102        device->DestroyWindow = UIKit_DestroyWindow;
103        device->GetWindowWMInfo = UIKit_GetWindowWMInfo;
104        device->GetDisplayUsableBounds = UIKit_GetDisplayUsableBounds;
105        device->GetDisplayDPI = UIKit_GetDisplayDPI;
106
107#if SDL_IPHONE_KEYBOARD
108        device->HasScreenKeyboardSupport = UIKit_HasScreenKeyboardSupport;
109        device->ShowScreenKeyboard = UIKit_ShowScreenKeyboard;
110        device->HideScreenKeyboard = UIKit_HideScreenKeyboard;
111        device->IsScreenKeyboardShown = UIKit_IsScreenKeyboardShown;
112        device->SetTextInputRect = UIKit_SetTextInputRect;
113#endif
114
115        device->SetClipboardText = UIKit_SetClipboardText;
116        device->GetClipboardText = UIKit_GetClipboardText;
117        device->HasClipboardText = UIKit_HasClipboardText;
118
119        /* OpenGL (ES) functions */
120#if SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
121        device->GL_MakeCurrent      = UIKit_GL_MakeCurrent;
122        device->GL_GetDrawableSize  = UIKit_GL_GetDrawableSize;
123        device->GL_SwapWindow       = UIKit_GL_SwapWindow;
124        device->GL_CreateContext    = UIKit_GL_CreateContext;
125        device->GL_DeleteContext    = UIKit_GL_DeleteContext;
126        device->GL_GetProcAddress   = UIKit_GL_GetProcAddress;
127        device->GL_LoadLibrary      = UIKit_GL_LoadLibrary;
128#endif
129        device->free = UIKit_DeleteDevice;
130
131#if SDL_VIDEO_VULKAN
132        device->Vulkan_LoadLibrary = UIKit_Vulkan_LoadLibrary;
133        device->Vulkan_UnloadLibrary = UIKit_Vulkan_UnloadLibrary;
134        device->Vulkan_GetInstanceExtensions
135                                     = UIKit_Vulkan_GetInstanceExtensions;
136        device->Vulkan_CreateSurface = UIKit_Vulkan_CreateSurface;
137        device->Vulkan_GetDrawableSize = UIKit_Vulkan_GetDrawableSize;
138#endif
139
140#if SDL_VIDEO_METAL
141        device->Metal_CreateView = UIKit_Metal_CreateView;
142        device->Metal_DestroyView = UIKit_Metal_DestroyView;
143        device->Metal_GetLayer = UIKit_Metal_GetLayer;
144        device->Metal_GetDrawableSize = UIKit_Metal_GetDrawableSize;
145#endif
146
147        device->gl_config.accelerated = 1;
148
149        return device;
150    }
151}
152
153VideoBootStrap UIKIT_bootstrap = {
154    UIKITVID_DRIVER_NAME, "SDL UIKit video driver",
155    UIKit_Available, UIKit_CreateDevice
156};
157
158
159int
160UIKit_VideoInit(_THIS)
161{
162    _this->gl_config.driver_loaded = 1;
163
164    if (UIKit_InitModes(_this) < 0) {
165        return -1;
166    }
167    return 0;
168}
169
170void
171UIKit_VideoQuit(_THIS)
172{
173    UIKit_QuitModes(_this);
174}
175
176void
177UIKit_SuspendScreenSaver(_THIS)
178{
179    @autoreleasepool {
180        /* Ignore ScreenSaver API calls if the idle timer hint has been set. */
181        /* FIXME: The idle timer hint should be deprecated for SDL 2.1. */
182        if (!SDL_GetHintBoolean(SDL_HINT_IDLE_TIMER_DISABLED, SDL_FALSE)) {
183            UIApplication *app = [UIApplication sharedApplication];
184
185            /* Prevent the display from dimming and going to sleep. */
186            app.idleTimerDisabled = (_this->suspend_screensaver != SDL_FALSE);
187        }
188    }
189}
190
191SDL_bool
192UIKit_IsSystemVersionAtLeast(double version)
193{
194    return [[UIDevice currentDevice].systemVersion doubleValue] >= version;
195}
196
197CGRect
198UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen)
199{
200    SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
201    CGRect frame = screen.bounds;
202
203    /* Use the UIWindow bounds instead of the UIScreen bounds, when possible.
204     * The uiwindow bounds may be smaller than the screen bounds when Split View
205     * is used on an iPad. */
206    if (data != nil && data.uiwindow != nil) {
207        frame = data.uiwindow.bounds;
208    }
209
210#if !TARGET_OS_TV && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0)
211    BOOL hasiOS7 = UIKit_IsSystemVersionAtLeast(7.0);
212
213    /* The view should always show behind the status bar in iOS 7+. */
214    if (!hasiOS7 && !(window->flags & (SDL_WINDOW_BORDERLESS|SDL_WINDOW_FULLSCREEN))) {
215        frame = screen.applicationFrame;
216    }
217#endif
218
219#if !TARGET_OS_TV
220    /* iOS 10 seems to have a bug where, in certain conditions, putting the
221     * device to sleep with the a landscape-only app open, re-orienting the
222     * device to portrait, and turning it back on will result in the screen
223     * bounds returning portrait orientation despite the app being in landscape.
224     * This is a workaround until a better solution can be found.
225     * https://bugzilla.libsdl.org/show_bug.cgi?id=3505
226     * https://bugzilla.libsdl.org/show_bug.cgi?id=3465
227     * https://forums.developer.apple.com/thread/65337 */
228    if (UIKit_IsSystemVersionAtLeast(8.0)) {
229        UIInterfaceOrientation orient = [UIApplication sharedApplication].statusBarOrientation;
230        BOOL landscape = UIInterfaceOrientationIsLandscape(orient);
231        BOOL fullscreen = CGRectEqualToRect(screen.bounds, frame);
232
233        /* The orientation flip doesn't make sense when the window is smaller
234         * than the screen (iPad Split View, for example). */
235        if (fullscreen && (landscape != (frame.size.width > frame.size.height))) {
236            float height = frame.size.width;
237            frame.size.width = frame.size.height;
238            frame.size.height = height;
239        }
240    }
241#endif
242
243    return frame;
244}
245
246void
247UIKit_ForceUpdateHomeIndicator()
248{
249#if !TARGET_OS_TV
250    /* Force the main SDL window to re-evaluate home indicator state */
251    SDL_Window *focus = SDL_GetFocusWindow();
252    if (focus) {
253        SDL_WindowData *data = (__bridge SDL_WindowData *) focus->driverdata;
254        if (data != nil) {
255#pragma clang diagnostic push
256#pragma clang diagnostic ignored "-Wunguarded-availability-new"
257            if ([data.viewcontroller respondsToSelector:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden)]) {
258                [data.viewcontroller performSelectorOnMainThread:@selector(setNeedsUpdateOfHomeIndicatorAutoHidden) withObject:nil waitUntilDone:NO];
259                [data.viewcontroller performSelectorOnMainThread:@selector(setNeedsUpdateOfScreenEdgesDeferringSystemGestures) withObject:nil waitUntilDone:NO];
260            }
261#pragma clang diagnostic pop
262        }
263    }
264#endif /* !TARGET_OS_TV */
265}
266
267/*
268 * iOS log support.
269 *
270 * This doesn't really have aything to do with the interfaces of the SDL video
271 *  subsystem, but we need to stuff this into an Objective-C source code file.
272 *
273 * NOTE: This is copypasted from src/video/cocoa/SDL_cocoavideo.m! Thus, if
274 *  Cocoa is supported, we use that one instead. Be sure both versions remain
275 *  identical!
276 */
277
278#if !defined(SDL_VIDEO_DRIVER_COCOA)
279void SDL_NSLog(const char *text)
280{
281    NSLog(@"%s", text);
282}
283#endif /* SDL_VIDEO_DRIVER_COCOA */
284
285/*
286 * iOS Tablet detection
287 *
288 * This doesn't really have aything to do with the interfaces of the SDL video
289 * subsystem, but we need to stuff this into an Objective-C source code file.
290 */
291SDL_bool SDL_IsIPad(void)
292{
293    return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad);
294}
295
296#endif /* SDL_VIDEO_DRIVER_UIKIT */
297
298/* vi: set ts=4 sw=4 expandtab: */
299