From a3a0ef7527d666c57562d77beeadc3cea02f47e6 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 15 Oct 2020 10:13:44 -0700 Subject: [PATCH] Added support for low latency mouse and keyboard handling in iOS 14 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. --- src/video/uikit/SDL_uikitevents.h | 8 + src/video/uikit/SDL_uikitevents.m | 248 ++++++++++++++++++++++ src/video/uikit/SDL_uikitvideo.m | 7 + src/video/uikit/SDL_uikitview.m | 109 +++++----- src/video/uikit/SDL_uikitviewcontroller.m | 4 +- 5 files changed, 325 insertions(+), 51 deletions(-) diff --git a/src/video/uikit/SDL_uikitevents.h b/src/video/uikit/SDL_uikitevents.h index f4ae9c792..86b12a8bf 100644 --- a/src/video/uikit/SDL_uikitevents.h +++ b/src/video/uikit/SDL_uikitevents.h @@ -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: */ diff --git a/src/video/uikit/SDL_uikitevents.m b/src/video/uikit/SDL_uikitevents.m index c9645c4b0..8206d741c 100644 --- a/src/video/uikit/SDL_uikitevents.m +++ b/src/video/uikit/SDL_uikitevents.m @@ -30,6 +30,13 @@ #import +#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000) +#import + +#define ENABLE_GCKEYBOARD +#define ENABLE_GCMOUSE +#endif + static BOOL UIKit_EventPumpEnabled = YES; void @@ -70,6 +77,247 @@ UIKit_PumpEvents(_THIS) #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: */ diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m index 45bda2d79..8adaaffae 100644 --- a/src/video/uikit/SDL_uikitvideo.m +++ b/src/video/uikit/SDL_uikitvideo.m @@ -158,12 +158,19 @@ UIKit_VideoInit(_THIS) if (UIKit_InitModes(_this) < 0) { return -1; } + + SDL_InitGCKeyboard(); + SDL_InitGCMouse(); + return 0; } void UIKit_VideoQuit(_THIS) { + SDL_QuitGCKeyboard(); + SDL_QuitGCMouse(); + UIKit_QuitModes(_this); } diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m index 995307277..2057471ec 100644 --- a/src/video/uikit/SDL_uikitview.m +++ b/src/video/uikit/SDL_uikitview.m @@ -29,9 +29,10 @@ #include "../../events/SDL_touch_c.h" #include "../../events/SDL_events_c.h" -#import "SDL_uikitappdelegate.h" -#import "SDL_uikitmodes.h" -#import "SDL_uikitwindow.h" +#include "SDL_uikitappdelegate.h" +#include "SDL_uikitevents.h" +#include "SDL_uikitmodes.h" +#include "SDL_uikitwindow.h" /* The maximum number of mouse buttons we support */ #define MAX_MOUSE_BUTTONS 5 @@ -159,7 +160,7 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; #if !TARGET_OS_TV && defined(__IPHONE_13_4) - (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)){ - if (request != nil) { + if (request != nil && !SDL_HasGCMouse()) { CGPoint origin = self.bounds.origin; CGPoint point = request.location; @@ -236,27 +237,29 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; #if !TARGET_OS_TV && defined(__IPHONE_13_4) if (@available(iOS 13.4, *)) { if (touch.type == UITouchTypeIndirectPointer) { - int i; + if (!SDL_HasGCMouse()) { + int i; - for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { - if (event.buttonMask & SDL_BUTTON(i)) { - Uint8 button; + for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { + if ((event.buttonMask & SDL_BUTTON(i)) != 0) { + Uint8 button; - switch (i) { - case 1: - button = SDL_BUTTON_LEFT; - break; - case 2: - button = SDL_BUTTON_RIGHT; - break; - case 3: - button = SDL_BUTTON_MIDDLE; - break; - default: - button = (Uint8)i; - break; + switch (i) { + case 1: + button = SDL_BUTTON_LEFT; + break; + case 2: + button = SDL_BUTTON_RIGHT; + break; + case 3: + button = SDL_BUTTON_MIDDLE; + break; + default: + button = (Uint8)i; + break; + } + SDL_SendMouseButton(sdlwindow, 0, SDL_PRESSED, button); } - SDL_SendMouseButton(sdlwindow, 0, SDL_PRESSED, button); } } handled = YES; @@ -289,27 +292,29 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; #if !TARGET_OS_TV && defined(__IPHONE_13_4) if (@available(iOS 13.4, *)) { if (touch.type == UITouchTypeIndirectPointer) { - int i; + if (!SDL_HasGCMouse()) { + int i; - for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { - if (!(event.buttonMask & SDL_BUTTON(i))) { - Uint8 button; + for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) { + if ((event.buttonMask & SDL_BUTTON(i)) != 0) { + Uint8 button; - switch (i) { - case 1: - button = SDL_BUTTON_LEFT; - break; - case 2: - button = SDL_BUTTON_RIGHT; - break; - case 3: - button = SDL_BUTTON_MIDDLE; - break; - default: - button = (Uint8)i; - break; + switch (i) { + case 1: + button = SDL_BUTTON_LEFT; + break; + case 2: + button = SDL_BUTTON_RIGHT; + break; + case 3: + button = SDL_BUTTON_MIDDLE; + break; + default: + button = (Uint8)i; + break; + } + SDL_SendMouseButton(sdlwindow, 0, SDL_RELEASED, button); } - SDL_SendMouseButton(sdlwindow, 0, SDL_RELEASED, button); } } handled = YES; @@ -411,27 +416,33 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick; - (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event { - for (UIPress *press in presses) { - SDL_Scancode scancode = [self scancodeFromPress:press]; - SDL_SendKeyboardKey(SDL_PRESSED, scancode); + if (!SDL_HasGCKeyboard()) { + for (UIPress *press in presses) { + SDL_Scancode scancode = [self scancodeFromPress:press]; + SDL_SendKeyboardKey(SDL_PRESSED, scancode); + } } [super pressesBegan:presses withEvent:event]; } - (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { - for (UIPress *press in presses) { - SDL_Scancode scancode = [self scancodeFromPress:press]; - SDL_SendKeyboardKey(SDL_RELEASED, scancode); + if (!SDL_HasGCKeyboard()) { + for (UIPress *press in presses) { + SDL_Scancode scancode = [self scancodeFromPress:press]; + SDL_SendKeyboardKey(SDL_RELEASED, scancode); + } } [super pressesEnded:presses withEvent:event]; } - (void)pressesCancelled:(NSSet *)presses withEvent:(UIPressesEvent *)event { - for (UIPress *press in presses) { - SDL_Scancode scancode = [self scancodeFromPress:press]; - SDL_SendKeyboardKey(SDL_RELEASED, scancode); + if (!SDL_HasGCKeyboard()) { + for (UIPress *press in presses) { + SDL_Scancode scancode = [self scancodeFromPress:press]; + SDL_SendKeyboardKey(SDL_RELEASED, scancode); + } } [super pressesCancelled:presses withEvent:event]; } diff --git a/src/video/uikit/SDL_uikitviewcontroller.m b/src/video/uikit/SDL_uikitviewcontroller.m index bda8bf144..45e8a5faf 100644 --- a/src/video/uikit/SDL_uikitviewcontroller.m +++ b/src/video/uikit/SDL_uikitviewcontroller.m @@ -430,7 +430,7 @@ SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *o } if (mod & KMOD_SHIFT) { - /* If character uses shift, press shift down */ + /* If character uses shift, press shift */ SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT); } @@ -439,7 +439,7 @@ SDL_HideHomeIndicatorHintChanged(void *userdata, const char *name, const char *o SDL_SendKeyboardKey(SDL_RELEASED, code); if (mod & KMOD_SHIFT) { - /* If character uses shift, press shift back up */ + /* If character uses shift, release shift */ SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT); } }