Browse Source

Enable gamepad events on visionOS

Normally the gamepad is used for navigation on visionOS, but when the controller subsystem is enabled we want to receive gamepad input as gamepad events instead.
Sam Lantinga 3 weeks ago
parent
commit
a1ade13f1e

+ 9 - 0
src/joystick/apple/SDL_mfijoystick.m

@@ -26,6 +26,7 @@
 #include "../hidapi/SDL_hidapijoystick_c.h"
 #include "../usb_ids.h"
 #include "../../events/SDL_events_c.h"
+#include "../../video/uikit/SDL_uikitvideo.h"
 
 #include "SDL_mfijoystick_c.h"
 
@@ -790,6 +791,10 @@ static bool IOS_JoystickInit(void)
                                                SDL_UnlockJoysticks();
                                              }];
 #endif // SDL_JOYSTICK_MFI
+
+#ifdef SDL_VIDEO_DRIVER_UIKIT
+        UIKit_SetGameControllerInteraction(true);
+#endif
     }
 
     return true;
@@ -1581,6 +1586,10 @@ static void IOS_JoystickQuit(void)
         while (deviceList != NULL) {
             IOS_RemoveJoystickDevice(deviceList);
         }
+
+#ifdef SDL_VIDEO_DRIVER_UIKIT
+        UIKit_SetGameControllerInteraction(false);
+#endif
     }
 
     numjoysticks = 0;

+ 11 - 6
src/video/uikit/SDL_uikitvideo.h

@@ -36,19 +36,24 @@
 @end
 
 #ifdef SDL_PLATFORM_VISIONOS
-CGRect UIKit_ComputeViewFrame(SDL_Window *window);
+extern CGRect UIKit_ComputeViewFrame(SDL_Window *window);
 #else
-CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen);
+extern CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen);
 #endif
 
+extern API_AVAILABLE(ios(13.0)) UIWindowScene *UIKit_GetActiveWindowScene(void);
+
+extern void UIKit_SetGameControllerInteraction(bool enabled);
+extern void UIKit_SetViewGameControllerInteraction(UIView *view, bool enabled);
+
 #endif // __OBJC__
 
-bool UIKit_SuspendScreenSaver(SDL_VideoDevice *_this);
+extern bool UIKit_SuspendScreenSaver(SDL_VideoDevice *_this);
 
-void UIKit_ForceUpdateHomeIndicator(void);
+extern void UIKit_ForceUpdateHomeIndicator(void);
 
-bool UIKit_IsSystemVersionAtLeast(double version);
+extern bool UIKit_IsSystemVersionAtLeast(double version);
 
-SDL_SystemTheme UIKit_GetSystemTheme(void);
+extern SDL_SystemTheme UIKit_GetSystemTheme(void);
 
 #endif // SDL_uikitvideo_h_

+ 76 - 2
src/video/uikit/SDL_uikitvideo.m

@@ -23,6 +23,7 @@
 #ifdef SDL_VIDEO_DRIVER_UIKIT
 
 #import <UIKit/UIKit.h>
+#import <GameController/GameController.h>
 
 #include "../SDL_sysvideo.h"
 #include "../SDL_pixels_c.h"
@@ -210,7 +211,8 @@ SDL_SystemTheme UIKit_GetSystemTheme(void)
 }
 
 #ifdef SDL_PLATFORM_VISIONOS
-CGRect UIKit_ComputeViewFrame(SDL_Window *window){
+CGRect UIKit_ComputeViewFrame(SDL_Window *window)
+{
     return CGRectMake(window->x, window->y, window->w, window->h);
 }
 #else
@@ -251,8 +253,80 @@ CGRect UIKit_ComputeViewFrame(SDL_Window *window, UIScreen *screen)
 
     return frame;
 }
+#endif // SDL_PLATFORM_VISIONOS
 
-#endif
+UIWindowScene *UIKit_GetActiveWindowScene(void)
+{
+    if (@available(iOS 13.0, tvOS 13.0, *)) {
+        NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
+
+        // First, try to find an active foreground scene
+        for (UIScene *scene in connectedScenes) {
+            if ([scene isKindOfClass:[UIWindowScene class]]) {
+                UIWindowScene *windowScene = (UIWindowScene *)scene;
+                if (windowScene.activationState == UISceneActivationStateForegroundActive) {
+                    return windowScene;
+                }
+            }
+        }
+
+        // If no active scene, return any foreground scene
+        for (UIScene *scene in connectedScenes) {
+            if ([scene isKindOfClass:[UIWindowScene class]]) {
+                UIWindowScene *windowScene = (UIWindowScene *)scene;
+                if (windowScene.activationState == UISceneActivationStateForegroundInactive) {
+                    return windowScene;
+                }
+            }
+        }
+
+        // Last resort: return first window scene
+        for (UIScene *scene in connectedScenes) {
+            if ([scene isKindOfClass:[UIWindowScene class]]) {
+                return (UIWindowScene *)scene;
+            }
+        }
+    }
+
+    return nil;
+}
+
+void UIKit_SetGameControllerInteraction(bool enabled)
+{
+    if (@available(iOS 13.0, tvOS 13.0, *)) {
+        UIWindowScene *scene = UIKit_GetActiveWindowScene();
+        if (scene == nil) {
+            return;
+        }
+
+        for (UIWindow *window in scene.windows) {
+            UIKit_SetViewGameControllerInteraction(window, enabled);
+        }
+    }
+}
+
+void UIKit_SetViewGameControllerInteraction(UIView *view, bool enabled)
+{
+#ifndef SDL_PLATFORM_TVOS
+    if (@available(iOS 18.0, visionOS 2.0, *)) {
+        if (enabled) {
+            GCEventInteraction *interaction = [[GCEventInteraction alloc] init];
+            interaction.handledEventTypes = GCUIEventTypeGamepad;
+            [view addInteraction:interaction];
+        } else {
+            for (id<UIInteraction> entry in view.interactions) {
+                if ([entry isKindOfClass:[GCEventInteraction class]]) {
+                    GCEventInteraction *interaction = (GCEventInteraction *)entry;
+                    if (interaction.handledEventTypes == GCUIEventTypeGamepad) {
+                        [view removeInteraction:interaction];
+                        break;
+                    }
+                }
+            }
+        }
+    }
+#endif // !SDL_PLATFORM_TVOS
+}
 
 void UIKit_ForceUpdateHomeIndicator(void)
 {

+ 4 - 0
src/video/uikit/SDL_uikitviewcontroller.m

@@ -341,6 +341,10 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char
 {
     [super setView:view];
 
+    if (SDL_WasInit(SDL_INIT_JOYSTICK)) {
+        UIKit_SetViewGameControllerInteraction(view, true);
+    }
+
     [view addSubview:textField];
 
     if (textFieldFocused) {

+ 1 - 38
src/video/uikit/SDL_uikitwindow.m

@@ -126,43 +126,6 @@ static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, UIWindow
     return true;
 }
 
-API_AVAILABLE(ios(13.0))
-static UIWindowScene *GetActiveWindowScene(void)
-{
-    if (@available(iOS 13.0, tvOS 13.0, *)) {
-        NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
-
-        // First, try to find an active foreground scene
-        for (UIScene *scene in connectedScenes) {
-            if ([scene isKindOfClass:[UIWindowScene class]]) {
-                UIWindowScene *windowScene = (UIWindowScene *)scene;
-                if (windowScene.activationState == UISceneActivationStateForegroundActive) {
-                    return windowScene;
-                }
-            }
-        }
-
-        // If no active scene, return any foreground scene
-        for (UIScene *scene in connectedScenes) {
-            if ([scene isKindOfClass:[UIWindowScene class]]) {
-                UIWindowScene *windowScene = (UIWindowScene *)scene;
-                if (windowScene.activationState == UISceneActivationStateForegroundInactive) {
-                    return windowScene;
-                }
-            }
-        }
-
-        // Last resort: return first window scene
-        for (UIScene *scene in connectedScenes) {
-            if ([scene isKindOfClass:[UIWindowScene class]]) {
-                return (UIWindowScene *)scene;
-            }
-        }
-    }
-
-    return nil;
-}
-
 bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
 {
     @autoreleasepool {
@@ -210,7 +173,7 @@ bool UIKit_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properti
 
         UIWindow *uiwindow = nil;
         if (@available(iOS 13.0, tvOS 13.0, *)) {
-            UIWindowScene *scene = GetActiveWindowScene();
+            UIWindowScene *scene = UIKit_GetActiveWindowScene();
             if (scene) {
                 uiwindow = [[UIWindow alloc] initWithWindowScene:scene];
             }