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.
This commit is contained in:
Sam Lantinga
2025-11-19 10:37:33 -08:00
parent 1e5cbbc1d0
commit a1ade13f1e
5 changed files with 101 additions and 46 deletions

View File

@@ -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;

View File

@@ -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_

View File

@@ -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)
{

View File

@@ -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) {

View File

@@ -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];
}