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.
diff --git a/src/video/uikit/SDL_uikitevents.h b/src/video/uikit/SDL_uikitevents.h
index f4ae9c7..86b12a8 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 c9645c4..8206d74 100644
--- a/src/video/uikit/SDL_uikitevents.m
+++ b/src/video/uikit/SDL_uikitevents.m
@@ -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
@@ -70,6 +77,247 @@
#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 45bda2d..8adaaff 100644
--- a/src/video/uikit/SDL_uikitvideo.m
+++ b/src/video/uikit/SDL_uikitvideo.m
@@ -158,12 +158,19 @@
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 9953072..2057471 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 @@
#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 @@
#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 @@
#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 @@
- (void)pressesBegan:(NSSet<UIPress *> *)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<UIPress *> *)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<UIPress *> *)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 bda8bf1..45e8a5f 100644
--- a/src/video/uikit/SDL_uikitviewcontroller.m
+++ b/src/video/uikit/SDL_uikitviewcontroller.m
@@ -430,7 +430,7 @@
}
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_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);
}
}