Skip to content

Commit

Permalink
Added support for low latency mouse and keyboard handling in iOS 14
Browse files Browse the repository at this point in the history
The mouse support in iOS 14.0 has a bug with accumulating duplicate mouse deltas that won't be fixed until iOS 14.1, so we don't enable it until then.
  • Loading branch information
slouken committed Oct 15, 2020
1 parent d9aea0c commit a3a0ef7
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 55 deletions.
8 changes: 8 additions & 0 deletions src/video/uikit/SDL_uikitevents.h
Expand Up @@ -25,6 +25,14 @@

extern void UIKit_PumpEvents(_THIS);

extern void SDL_InitGCKeyboard(void);
extern SDL_bool SDL_HasGCKeyboard(void);
extern void SDL_QuitGCKeyboard(void);

extern void SDL_InitGCMouse(void);
extern SDL_bool SDL_HasGCMouse(void);
extern void SDL_QuitGCMouse(void);

#endif /* SDL_uikitevents_h_ */

/* vi: set ts=4 sw=4 expandtab: */
248 changes: 248 additions & 0 deletions src/video/uikit/SDL_uikitevents.m
Expand Up @@ -30,6 +30,13 @@

#import <Foundation/Foundation.h>

#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000)
#import <GameController/GameController.h>

#define ENABLE_GCKEYBOARD
#define ENABLE_GCMOUSE
#endif

static BOOL UIKit_EventPumpEnabled = YES;

void
Expand Down Expand Up @@ -70,6 +77,247 @@ touch events to get processed (which is important to get certain
#endif
}

#ifdef ENABLE_GCKEYBOARD

static SDL_bool keyboard_connected = SDL_FALSE;
static id keyboard_connect_observer = nil;
static id keyboard_disconnect_observer = nil;

static void OnGCKeyboardConnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
{
keyboard_connected = SDL_TRUE;
keyboard.keyboardInput.keyChangedHandler = ^(GCKeyboardInput *keyboard, GCControllerButtonInput *key, GCKeyCode keyCode, BOOL pressed)
{
SDL_SendKeyboardKey(pressed ? SDL_PRESSED : SDL_RELEASED, (SDL_Scancode)keyCode);
};
}

static void OnGCKeyboardDisconnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
{
keyboard.keyboardInput.keyChangedHandler = nil;
keyboard_connected = SDL_FALSE;
}

void SDL_InitGCKeyboard(void)
{
@autoreleasepool {
if (@available(iOS 14.0, tvOS 14.0, *)) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

keyboard_connect_observer = [center addObserverForName:GCKeyboardDidConnectNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
GCKeyboard *keyboard = note.object;
OnGCKeyboardConnected(keyboard);
}];

keyboard_disconnect_observer = [center addObserverForName:GCKeyboardDidDisconnectNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
GCKeyboard *keyboard = note.object;
OnGCKeyboardDisconnected(keyboard);
}];

if (GCKeyboard.coalescedKeyboard != nil) {
OnGCKeyboardConnected(GCKeyboard.coalescedKeyboard);
}
}
}
}

SDL_bool SDL_HasGCKeyboard(void)
{
return keyboard_connected;
}

void SDL_QuitGCKeyboard(void)
{
@autoreleasepool {
if (@available(iOS 14.0, tvOS 14.0, *)) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

if (keyboard_connect_observer) {
[center removeObserver:keyboard_connect_observer name:GCKeyboardDidConnectNotification object:nil];
keyboard_connect_observer = nil;
}

if (keyboard_disconnect_observer) {
[center removeObserver:keyboard_disconnect_observer name:GCKeyboardDidDisconnectNotification object:nil];
keyboard_disconnect_observer = nil;
}

if (GCKeyboard.coalescedKeyboard != nil) {
OnGCKeyboardDisconnected(GCKeyboard.coalescedKeyboard);
}
}
}
}

#else

void SDL_InitGCKeyboard(void)
{
}

SDL_bool SDL_HasGCKeyboard(void)
{
return SDL_FALSE;
}

void SDL_QuitGCKeyboard(void)
{
}

#endif /* ENABLE_GCKEYBOARD */


#ifdef ENABLE_GCMOUSE

static int mice_connected = 0;
static id mouse_connect_observer = nil;
static id mouse_disconnect_observer = nil;

static int SetGCMouseRelativeMode(SDL_bool enabled)
{
/* We'll always send relative motion and we can't warp, so nothing to do here */
return 0;
}

static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL pressed)
{
SDL_SendMouseButton(SDL_GetMouseFocus(), mouseID, pressed ? SDL_PRESSED : SDL_RELEASED, button);
}

static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
{
SDL_MouseID mouseID = mice_connected;

mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed)
{
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed);
};
mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed)
{
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed);
};
mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed)
{
OnGCMouseButtonChanged(mouseID, SDL_BUTTON_RIGHT, pressed);
};

int auxiliary_button = SDL_BUTTON_X1;
for (GCControllerButtonInput *button in mouse.mouseInput.auxiliaryButtons) {
button.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed)
{
OnGCMouseButtonChanged(mouseID, auxiliary_button, pressed);
};
++auxiliary_button;
}

mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput *mouse, float deltaX, float deltaY)
{
SDL_SendMouseMotion(SDL_GetMouseFocus(), mouseID, SDL_TRUE, (int)deltaX, -(int)deltaY);
};

++mice_connected;
}

static void OnGCMouseDisconnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
{
--mice_connected;

mouse.mouseInput.mouseMovedHandler = nil;

mouse.mouseInput.leftButton.pressedChangedHandler = nil;
mouse.mouseInput.middleButton.pressedChangedHandler = nil;
mouse.mouseInput.rightButton.pressedChangedHandler = nil;

for (GCControllerButtonInput *button in mouse.mouseInput.auxiliaryButtons) {
button.pressedChangedHandler = nil;
}
}

void SDL_InitGCMouse(void)
{
@autoreleasepool {
/* There is a bug where mouse accumulates duplicate deltas over time in iOS 14.0 */
if (@available(iOS 14.1, tvOS 14.1, *)) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

mouse_connect_observer = [center addObserverForName:GCMouseDidConnectNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
GCMouse *mouse = note.object;
OnGCMouseConnected(mouse);
}];

mouse_disconnect_observer = [center addObserverForName:GCMouseDidDisconnectNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
GCMouse *mouse = note.object;
OnGCMouseDisconnected(mouse);
}];

for (GCMouse *mouse in [GCMouse mice]) {
OnGCMouseConnected(mouse);
}

SDL_GetMouse()->SetRelativeMouseMode = SetGCMouseRelativeMode;
}
}
}

SDL_bool SDL_HasGCMouse(void)
{
return (mice_connected > 0);
}

void SDL_QuitGCMouse(void)
{
@autoreleasepool {
if (@available(iOS 14.0, tvOS 14.0, *)) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

if (mouse_connect_observer) {
[center removeObserver:mouse_connect_observer name:GCMouseDidConnectNotification object:nil];
mouse_connect_observer = nil;
}

if (mouse_disconnect_observer) {
[center removeObserver:mouse_disconnect_observer name:GCMouseDidDisconnectNotification object:nil];
mouse_disconnect_observer = nil;
}

for (GCMouse *mouse in [GCMouse mice]) {
OnGCMouseDisconnected(mouse);
}

SDL_GetMouse()->SetRelativeMouseMode = NULL;
}
}
}

#else

void SDL_InitGCMouse(void)
{
}

SDL_bool SDL_HasGCMouse(void)
{
return SDL_FALSE;
}

void SDL_QuitGCMouse(void)
{
}

#endif /* ENABLE_GCMOUSE */

#endif /* SDL_VIDEO_DRIVER_UIKIT */

/* vi: set ts=4 sw=4 expandtab: */
7 changes: 7 additions & 0 deletions src/video/uikit/SDL_uikitvideo.m
Expand Up @@ -158,12 +158,19 @@ static void UIKit_DeleteDevice(SDL_VideoDevice * device)
if (UIKit_InitModes(_this) < 0) {
return -1;
}

SDL_InitGCKeyboard();
SDL_InitGCMouse();

return 0;
}

void
UIKit_VideoQuit(_THIS)
{
SDL_QuitGCKeyboard();
SDL_QuitGCMouse();

UIKit_QuitModes(_this);
}

Expand Down

0 comments on commit a3a0ef7

Please sign in to comment.