Initial Apple TV / tvOS support.

The Apple TV remote is currently exposed as a joystick with its touch surface treated as two axes. Key presses are also generated when its buttons and touch surface are used.

A new hint has been added to help deal with deciding whether to background the app when the remote's menu button is pressed: SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS.
This commit is contained in:
Alex Szpakowski
2016-09-13 22:18:06 -03:00
parent 86708c3cd8
commit f050576665
21 changed files with 1191 additions and 62 deletions

View File

@@ -25,7 +25,6 @@
- (instancetype)init;
- (void)loadView;
- (NSUInteger)supportedInterfaceOrientations;
@end

View File

@@ -76,6 +76,7 @@ SDL_IdleTimerDisabledChanged(void *userdata, const char *name, const char *oldVa
[UIApplication sharedApplication].idleTimerDisabled = disable;
}
#if !TARGET_OS_TV
/* Load a launch image using the old UILaunchImageFile-era naming rules. */
static UIImage *
SDL_LoadLaunchImageNamed(NSString *name, int screenh)
@@ -114,6 +115,15 @@ SDL_LoadLaunchImageNamed(NSString *name, int screenh)
return image;
}
#endif /* !TARGET_OS_TV */
@interface SDLLaunchScreenController ()
#if !TARGET_OS_TV
- (NSUInteger)supportedInterfaceOrientations;
#endif
@end
@implementation SDLLaunchScreenController
@@ -140,6 +150,7 @@ SDL_LoadLaunchImageNamed(NSString *name, int screenh)
}
if (!self.view) {
#if !TARGET_OS_TV
NSArray *launchimages = [bundle objectForInfoDictionaryKey:@"UILaunchImages"];
UIInterfaceOrientation curorient = [UIApplication sharedApplication].statusBarOrientation;
NSString *imagename = nil;
@@ -244,6 +255,9 @@ SDL_LoadLaunchImageNamed(NSString *name, int screenh)
self.view = view;
}
#else /* !TARGET_OS_TV */
return nil;
#endif
}
return self;
@@ -254,6 +268,7 @@ SDL_LoadLaunchImageNamed(NSString *name, int screenh)
/* Do nothing. */
}
#if !TARGET_OS_TV
- (BOOL)shouldAutorotate
{
/* If YES, the launch image will be incorrectly rotated in some cases. */
@@ -267,6 +282,7 @@ SDL_LoadLaunchImageNamed(NSString *name, int screenh)
* the ones set here (it will cause an exception in that case.) */
return UIInterfaceOrientationMaskAll;
}
#endif /* !TARGET_OS_TV */
@end
@@ -381,6 +397,7 @@ SDL_LoadLaunchImageNamed(NSString *name, int screenh)
SDL_SendAppEvent(SDL_APP_LOWMEMORY);
}
#if !TARGET_OS_TV
- (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation
{
BOOL isLandscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation);
@@ -408,6 +425,7 @@ SDL_LoadLaunchImageNamed(NSString *name, int screenh)
}
}
}
#endif
- (void)applicationWillResignActive:(UIApplication*)application
{

View File

@@ -30,15 +30,22 @@
int
UIKit_SetClipboardText(_THIS, const char *text)
{
#if TARGET_OS_TV
return SDL_SetError("The clipboard is not available on tvOS");
#else
@autoreleasepool {
[UIPasteboard generalPasteboard].string = @(text);
return 0;
}
#endif
}
char *
UIKit_GetClipboardText(_THIS)
{
#if TARGET_OS_TV
return SDL_strdup(""); // Unsupported.
#else
@autoreleasepool {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSString *string = pasteboard.string;
@@ -49,15 +56,18 @@ UIKit_GetClipboardText(_THIS)
return SDL_strdup("");
}
}
#endif
}
SDL_bool
UIKit_HasClipboardText(_THIS)
{
@autoreleasepool {
#if !TARGET_OS_TV
if ([UIPasteboard generalPasteboard].string != nil) {
return SDL_TRUE;
}
#endif
return SDL_FALSE;
}
}
@@ -65,6 +75,7 @@ UIKit_HasClipboardText(_THIS)
void
UIKit_InitClipboard(_THIS)
{
#if !TARGET_OS_TV
@autoreleasepool {
SDL_VideoData *data = (__bridge SDL_VideoData *) _this->driverdata;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
@@ -78,6 +89,7 @@ UIKit_InitClipboard(_THIS)
data.pasteboardObserver = observer;
}
#endif
}
void

View File

@@ -156,9 +156,12 @@ UIKit_AddDisplay(UIScreen *uiscreen)
SDL_bool
UIKit_IsDisplayLandscape(UIScreen *uiscreen)
{
#if !TARGET_OS_TV
if (uiscreen == [UIScreen mainScreen]) {
return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation);
} else {
} else
#endif /* !TARGET_OS_TV */
{
CGSize size = uiscreen.bounds.size;
return (size.width > size.height);
}
@@ -187,6 +190,14 @@ UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
SDL_bool isLandscape = UIKit_IsDisplayLandscape(data.uiscreen);
SDL_bool addRotation = (data.uiscreen == [UIScreen mainScreen]);
CGFloat scale = data.uiscreen.scale;
NSArray *availableModes = nil;
#if TARGET_OS_TV
addRotation = SDL_FALSE;
availableModes = @[data.uiscreen.currentMode];
#else
availableModes = data.uiscreen.availableModes;
#endif
#ifdef __IPHONE_8_0
/* The UIScreenMode of an iPhone 6 Plus should be 1080x1920 rather than
@@ -196,7 +207,7 @@ UIKit_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
}
#endif
for (UIScreenMode *uimode in data.uiscreen.availableModes) {
for (UIScreenMode *uimode in availableModes) {
/* The size of a UIScreenMode is in pixels, but we deal exclusively
* in points (except in SDL_GL_GetDrawableSize.) */
int w = (int)(uimode.size.width / scale);
@@ -219,9 +230,11 @@ UIKit_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
{
@autoreleasepool {
SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)mode->driverdata;
#if !TARGET_OS_TV
SDL_DisplayModeData *modedata = (__bridge SDL_DisplayModeData *)mode->driverdata;
[data.uiscreen setCurrentMode:modedata.uiscreenmode];
#endif
if (data.uiscreen == [UIScreen mainScreen]) {
/* [UIApplication setStatusBarOrientation:] no longer works reliably
@@ -245,20 +258,30 @@ UIKit_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
int
UIKit_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
{
/* the default function iterates displays to make a fake offset,
as if all the displays were side-by-side, which is fine for iOS. */
const int displayIndex = (int) (display - _this->displays);
if (SDL_GetDisplayBounds(displayIndex, rect) < 0) {
return -1;
@autoreleasepool {
int displayIndex = (int) (display - _this->displays);
SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
/* the default function iterates displays to make a fake offset,
as if all the displays were side-by-side, which is fine for iOS. */
if (SDL_GetDisplayBounds(displayIndex, rect) < 0) {
return -1;
}
CGRect frame = data.uiscreen.bounds;
#if !TARGET_OS_TV
if (!UIKit_IsSystemVersionAtLeast(7.0)) {
frame = [data.uiscreen applicationFrame];
}
#endif
rect->x += frame.origin.x;
rect->y += frame.origin.y;
rect->w = frame.size.width;
rect->h = frame.size.height;
}
SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
const CGRect frame = [data.uiscreen applicationFrame];
const float scale = (float) data.uiscreen.scale;
rect->x += (int) (frame.origin.x * scale);
rect->y += (int) (frame.origin.y * scale);
rect->w = (int) (frame.size.width * scale);
rect->h = (int) (frame.size.height * scale);
return 0;
}

View File

@@ -176,6 +176,7 @@ UIKit_IsSystemVersionAtLeast(double version)
CGRect
UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen)
{
#if !TARGET_OS_TV && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0)
BOOL hasiOS7 = UIKit_IsSystemVersionAtLeast(7.0);
if (hasiOS7 || (window->flags & (SDL_WINDOW_BORDERLESS|SDL_WINDOW_FULLSCREEN))) {
@@ -184,6 +185,9 @@ UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen)
} else {
return screen.applicationFrame;
}
#else
return screen.bounds;
#endif
}
/*

View File

@@ -45,7 +45,9 @@
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.autoresizesSubviews = YES;
#if !TARGET_OS_TV
self.multipleTouchEnabled = YES;
#endif
touchId = 1;
SDL_AddTouch(touchId, "");
@@ -197,6 +199,69 @@
}
}
#if TARGET_OS_TV || defined(__IPHONE_9_1)
- (SDL_Scancode)scancodeFromPressType:(UIPressType)presstype
{
switch (presstype) {
case UIPressTypeUpArrow:
return SDL_SCANCODE_UP;
case UIPressTypeDownArrow:
return SDL_SCANCODE_DOWN;
case UIPressTypeLeftArrow:
return SDL_SCANCODE_LEFT;
case UIPressTypeRightArrow:
return SDL_SCANCODE_RIGHT;
case UIPressTypeSelect:
/* HIG says: "primary button behavior" */
return SDL_SCANCODE_SELECT;
case UIPressTypeMenu:
/* HIG says: "returns to previous screen" */
return SDL_SCANCODE_MENU;
case UIPressTypePlayPause:
/* HIG says: "secondary button behavior" */
return SDL_SCANCODE_PAUSE;
default:
return SDL_SCANCODE_UNKNOWN;
}
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
for (UIPress *press in presses) {
SDL_Scancode scancode = [self scancodeFromPressType:press.type];
SDL_SendKeyboardKey(SDL_PRESSED, scancode);
}
[super pressesBegan:presses withEvent:event];
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
for (UIPress *press in presses) {
SDL_Scancode scancode = [self scancodeFromPressType:press.type];
SDL_SendKeyboardKey(SDL_RELEASED, scancode);
}
[super pressesEnded:presses withEvent:event];
}
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
for (UIPress *press in presses) {
SDL_Scancode scancode = [self scancodeFromPressType:press.type];
SDL_SendKeyboardKey(SDL_RELEASED, scancode);
}
[super pressesCancelled:presses withEvent:event];
}
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
/* This is only called when the force of a press changes. */
[super pressesChanged:presses withEvent:event];
}
#endif /* TARGET_OS_TV || defined(__IPHONE_9_1) */
@end
#endif /* SDL_VIDEO_DRIVER_UIKIT */

View File

@@ -18,6 +18,7 @@
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"
#import <UIKit/UIKit.h>
@@ -25,10 +26,17 @@
#include "SDL_touch.h"
#if SDL_IPHONE_KEYBOARD
@interface SDL_uikitviewcontroller : UIViewController <UITextFieldDelegate>
#if TARGET_OS_TV
#import <GameController/GameController.h>
#define SDLRootViewController GCEventViewController
#else
@interface SDL_uikitviewcontroller : UIViewController
#define SDLRootViewController UIViewController
#endif
#if SDL_IPHONE_KEYBOARD
@interface SDL_uikitviewcontroller : SDLRootViewController <UITextFieldDelegate>
#else
@interface SDL_uikitviewcontroller : SDLRootViewController
#endif
@property (nonatomic, assign) SDL_Window *window;
@@ -46,8 +54,11 @@
- (void)loadView;
- (void)viewDidLayoutSubviews;
#if !TARGET_OS_TV
- (NSUInteger)supportedInterfaceOrientations;
- (BOOL)prefersStatusBarHidden;
#endif
#if SDL_IPHONE_KEYBOARD
- (void)showKeyboard;

View File

@@ -39,6 +39,17 @@
#include "keyinfotable.h"
#endif
#if TARGET_OS_TV
static void
SDL_AppleTVControllerUIHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
@autoreleasepool {
SDL_uikitviewcontroller *viewcontroller = (__bridge SDL_uikitviewcontroller *) userdata;
viewcontroller.controllerUserInteractionEnabled = hint && (*hint != '0');
}
}
#endif
@implementation SDL_uikitviewcontroller {
CADisplayLink *displayLink;
int animationInterval;
@@ -60,6 +71,12 @@
#if SDL_IPHONE_KEYBOARD
[self initKeyboard];
#endif
#if TARGET_OS_TV
SDL_AddHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
SDL_AppleTVControllerUIHintChanged,
(__bridge void *) self);
#endif
}
return self;
}
@@ -69,6 +86,12 @@
#if SDL_IPHONE_KEYBOARD
[self deinitKeyboard];
#endif
#if TARGET_OS_TV
SDL_DelHintCallback(SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS,
SDL_AppleTVControllerUIHintChanged,
(__bridge void *) self);
#endif
}
- (void)setAnimationCallback:(int)interval
@@ -124,6 +147,7 @@
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, w, h);
}
#if !TARGET_OS_TV
- (NSUInteger)supportedInterfaceOrientations
{
return UIKit_GetSupportedOrientations(window);
@@ -138,6 +162,7 @@
{
return (window->flags & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_BORDERLESS)) != 0;
}
#endif
/*
---- Keyboard related functionality below this line ----
@@ -168,9 +193,11 @@
textField.hidden = YES;
keyboardVisible = NO;
#if !TARGET_OS_TV
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
#endif
}
- (void)setView:(UIView *)view
@@ -186,9 +213,11 @@
- (void)deinitKeyboard
{
#if !TARGET_OS_TV
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
#endif
}
/* reveal onscreen virtual keyboard */
@@ -209,6 +238,7 @@
- (void)keyboardWillShow:(NSNotification *)notification
{
#if !TARGET_OS_TV
CGRect kbrect = [[notification userInfo][UIKeyboardFrameBeginUserInfoKey] CGRectValue];
/* The keyboard rect is in the coordinate space of the screen/window, but we
@@ -216,6 +246,7 @@
kbrect = [self.view convertRect:kbrect fromView:nil];
[self setKeyboardHeight:(int)kbrect.size.height];
#endif
}
- (void)keyboardWillHide:(NSNotification *)notification

View File

@@ -107,6 +107,7 @@ static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bo
window->flags |= SDL_WINDOW_BORDERLESS; /* never has a status bar. */
}
#if !TARGET_OS_TV
if (displaydata.uiscreen == [UIScreen mainScreen]) {
NSUInteger orients = UIKit_GetSupportedOrientations(window);
BOOL supportsLandscape = (orients & UIInterfaceOrientationMaskLandscape) != 0;
@@ -119,6 +120,7 @@ static int SetupWindowData(_THIS, SDL_Window *window, UIWindow *uiwindow, SDL_bo
height = temp;
}
}
#endif /* !TARGET_OS_TV */
window->x = 0;
window->y = 0;
@@ -152,7 +154,6 @@ UIKit_CreateWindow(_THIS, SDL_Window *window)
@autoreleasepool {
SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
SDL_DisplayData *data = (__bridge SDL_DisplayData *) display->driverdata;
const CGSize origsize = data.uiscreen.currentMode.size;
/* SDL currently puts this window at the start of display's linked list. We rely on this. */
SDL_assert(_this->windows == window);
@@ -165,6 +166,8 @@ UIKit_CreateWindow(_THIS, SDL_Window *window)
/* If monitor has a resolution of 0x0 (hasn't been explicitly set by the
* user, so it's in standby), try to force the display to a resolution
* that most closely matches the desired window size. */
#if !TARGET_OS_TV
const CGSize origsize = data.uiscreen.currentMode.size;
if ((origsize.width == 0.0f) && (origsize.height == 0.0f)) {
if (display->num_display_modes == 0) {
_this->GetDisplayModes(_this, display);
@@ -197,6 +200,7 @@ UIKit_CreateWindow(_THIS, SDL_Window *window)
[UIApplication sharedApplication].statusBarHidden = NO;
}
}
#endif /* !TARGET_OS_TV */
/* ignore the size user requested, and make a fullscreen window */
/* !!! FIXME: can we have a smaller view? */
@@ -258,6 +262,7 @@ UIKit_UpdateWindowBorder(_THIS, SDL_Window * window)
SDL_WindowData *data = (__bridge SDL_WindowData *) window->driverdata;
SDL_uikitviewcontroller *viewcontroller = data.viewcontroller;
#if !TARGET_OS_TV
if (data.uiwindow.screen == [UIScreen mainScreen]) {
if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS)) {
[UIApplication sharedApplication].statusBarHidden = YES;
@@ -273,6 +278,7 @@ UIKit_UpdateWindowBorder(_THIS, SDL_Window * window)
/* Update the view's frame to account for the status bar change. */
viewcontroller.view.frame = UIKit_ComputeViewFrame(window, data.uiwindow.screen);
#endif /* !TARGET_OS_TV */
#ifdef SDL_IPHONE_KEYBOARD
/* Make sure the view is offset correctly when the keyboard is visible. */
@@ -363,6 +369,7 @@ UIKit_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
}
}
#if !TARGET_OS_TV
NSUInteger
UIKit_GetSupportedOrientations(SDL_Window * window)
{
@@ -428,6 +435,7 @@ UIKit_GetSupportedOrientations(SDL_Window * window)
return orientationMask;
}
#endif /* !TARGET_OS_TV */
int
SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam)