diff --git a/CMakeLists.txt b/CMakeLists.txt index 749f788478..e0d117728b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1290,6 +1290,8 @@ elseif(IOS AND NOT LIBRETRO) ios/AppDelegate.h ios/DisplayManager.h ios/DisplayManager.mm + ios/Controls.h + ios/Controls.mm ios/ViewController.mm ios/ViewController.h ios/iOSCoreAudio.mm diff --git a/ios/Controls.h b/ios/Controls.h new file mode 100644 index 0000000000..cf4112f9d2 --- /dev/null +++ b/ios/Controls.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#import +#include "iCade/iCadeState.h" +#include "Common/Input/InputState.h" + +// Code extracted from ViewController.mm, in order to modularize +// and share it between multiple view controllers. + +bool SetupController(GCController *controller); + +struct TouchTracker { +public: + void Began(NSSet *touches, UIView *view); + void Moved(NSSet *touches, UIView *view); + void Ended(NSSet *touches, UIView *view); + void Cancelled(NSSet *touches, UIView *view); +private: + void SendTouchEvent(float x, float y, int code, int pointerId); + int ToTouchID(UITouch *uiTouch, bool allowAllocate); + UITouch *touches_[10]{}; +}; + +// Can probably get rid of this, but let's keep it for now. +struct ICadeTracker { +public: + void ButtonDown(iCadeState button); + void ButtonUp(iCadeState button); + void InitKeyMap(); +private: + bool simulateAnalog = false; + bool iCadeConnectNotified = false; + + std::map iCadeToKeyMap; + + double lastSelectPress = 0.0f; + double lastStartPress = 0.0f; +}; diff --git a/ios/Controls.mm b/ios/Controls.mm new file mode 100644 index 0000000000..2807d9ea31 --- /dev/null +++ b/ios/Controls.mm @@ -0,0 +1,358 @@ +#include "Controls.h" + +#include "Common/Log.h" +#include "Common/TimeUtil.h" +#include "Common/Input/InputState.h" +#include "Common/System/NativeApp.h" +#include "Common/System/Display.h" +#include "Core/KeyMap.h" + +static void controllerButtonPressed(BOOL pressed, InputKeyCode keyCode) { + KeyInput key; + key.deviceId = DEVICE_ID_PAD_0; + key.flags = pressed ? KEY_DOWN : KEY_UP; + key.keyCode = keyCode; + NativeKey(key); +} + +bool SetupController(GCController *controller) { + GCExtendedGamepad *extendedProfile = controller.extendedGamepad; + if (extendedProfile == nil) { + return false; + } + + controller.controllerPausedHandler = ^(GCController *controller) { + KeyInput key; + key.flags = KEY_DOWN; + key.keyCode = NKCODE_ESCAPE; + key.deviceId = DEVICE_ID_KEYBOARD; + NativeKey(key); + }; + + extendedProfile.buttonA.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_2); // Cross + }; + + extendedProfile.buttonB.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_3); // Circle + }; + + extendedProfile.buttonX.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_4); // Square + }; + + extendedProfile.buttonY.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_1); // Triangle + }; + + extendedProfile.leftShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_7); // LTrigger + }; + + extendedProfile.rightShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_8); // RTrigger + }; + + extendedProfile.dpad.up.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_DPAD_UP); + }; + + extendedProfile.dpad.down.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_DPAD_DOWN); + }; + + extendedProfile.dpad.left.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_DPAD_LEFT); + }; + + extendedProfile.dpad.right.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_DPAD_RIGHT); + }; + + extendedProfile.leftTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_9); // Select + }; + + extendedProfile.rightTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_10); // Start + }; + +#if defined(__IPHONE_12_1) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_1 + if ([extendedProfile respondsToSelector:@selector(leftThumbstickButton)] && extendedProfile.leftThumbstickButton != nil) { + extendedProfile.leftThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_11); + }; + } + if ([extendedProfile respondsToSelector:@selector(rightThumbstickButton)] && extendedProfile.rightThumbstickButton != nil) { + extendedProfile.rightThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_12); + }; + } +#endif +#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 + if ([extendedProfile respondsToSelector:@selector(buttonOptions)] && extendedProfile.buttonOptions != nil) { + extendedProfile.buttonOptions.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_13); + }; + } + if ([extendedProfile respondsToSelector:@selector(buttonMenu)] && extendedProfile.buttonMenu != nil) { + extendedProfile.buttonMenu.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_14); + }; + } +#endif +#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + if ([extendedProfile respondsToSelector:@selector(buttonHome)] && extendedProfile.buttonHome != nil) { + extendedProfile.buttonHome.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + controllerButtonPressed(pressed, NKCODE_BUTTON_15); + }; + } +#endif + + extendedProfile.leftThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { + AxisInput axisInput; + axisInput.deviceId = DEVICE_ID_PAD_0; + axisInput.axisId = JOYSTICK_AXIS_X; + axisInput.value = value; + NativeAxis(&axisInput, 1); + }; + + extendedProfile.leftThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { + AxisInput axisInput; + axisInput.deviceId = DEVICE_ID_PAD_0; + axisInput.axisId = JOYSTICK_AXIS_Y; + axisInput.value = -value; + NativeAxis(&axisInput, 1); + }; + + // Map right thumbstick as another analog stick, particularly useful for controllers + // like the DualShock 3/4 when connected to an iOS device + extendedProfile.rightThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { + AxisInput axisInput; + axisInput.deviceId = DEVICE_ID_PAD_0; + axisInput.axisId = JOYSTICK_AXIS_Z; + axisInput.value = value; + NativeAxis(&axisInput, 1); + }; + + extendedProfile.rightThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { + AxisInput axisInput; + axisInput.deviceId = DEVICE_ID_PAD_0; + axisInput.axisId = JOYSTICK_AXIS_RZ; + axisInput.value = -value; + NativeAxis(&axisInput, 1); + }; + + return true; +} + +void TouchTracker::SendTouchEvent(float x, float y, int code, int pointerId) { + float scale = [UIScreen mainScreen].scale; + if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) { + scale = [UIScreen mainScreen].nativeScale; + } + + float dp_xscale = (float)g_display.dp_xres / (float)g_display.pixel_xres; + float dp_yscale = (float)g_display.dp_yres / (float)g_display.pixel_yres; + + float scaledX = (int)(x * dp_xscale) * scale; + float scaledY = (int)(y * dp_yscale) * scale; + + TouchInput input; + input.x = scaledX; + input.y = scaledY; + switch (code) { + case 1: input.flags = TOUCH_DOWN; break; + case 2: input.flags = TOUCH_UP; break; + default: input.flags = TOUCH_MOVE; break; + } + input.id = pointerId; + NativeTouch(input); +} + +int TouchTracker::ToTouchID(UITouch *uiTouch, bool allowAllocate) { + // Find the id for the touch. + for (int localId = 0; localId < (int)ARRAY_SIZE(touches_); ++localId) { + if (touches_[localId] == uiTouch) { + return localId; + } + } + + // Allocate a new one, perhaps? + if (allowAllocate) { + for (int localId = 0; localId < (int)ARRAY_SIZE(touches_); ++localId) { + if (touches_[localId] == 0) { + touches_[localId] = uiTouch; + return localId; + } + } + + // None were free. Ignore? + return 0; + } + + return -1; +} + +void TouchTracker::Began(NSSet *touches, UIView *view) { + for (UITouch* touch in touches) { + CGPoint point = [touch locationInView:view]; + int touchId = ToTouchID(touch, true); + SendTouchEvent(point.x, point.y, 1, touchId); + } +} + +void TouchTracker::Moved(NSSet *touches, UIView *view) { + for (UITouch* touch in touches) { + CGPoint point = [touch locationInView:view]; + int touchId = ToTouchID(touch, true); + SendTouchEvent(point.x, point.y, 0, touchId); + } +} + +void TouchTracker::Ended(NSSet *touches, UIView *view) { + for (UITouch* touch in touches) { + CGPoint point = [touch locationInView:view]; + int touchId = ToTouchID(touch, false); + if (touchId >= 0) { + SendTouchEvent(point.x, point.y, 2, touchId); + touches_[touchId] = nullptr; + } + } +} + +void TouchTracker::Cancelled(NSSet *touches, UIView *view) { + for (UITouch* touch in touches) { + CGPoint point = [touch locationInView:view]; + int touchId = ToTouchID(touch, false); + if (touchId >= 0) { + SendTouchEvent(point.x, point.y, 2, touchId); + touches_[touchId] = nullptr; + } + } +} + +void ICadeTracker::InitKeyMap() { + iCadeToKeyMap[iCadeJoystickUp] = NKCODE_DPAD_UP; + iCadeToKeyMap[iCadeJoystickRight] = NKCODE_DPAD_RIGHT; + iCadeToKeyMap[iCadeJoystickDown] = NKCODE_DPAD_DOWN; + iCadeToKeyMap[iCadeJoystickLeft] = NKCODE_DPAD_LEFT; + iCadeToKeyMap[iCadeButtonA] = NKCODE_BUTTON_9; // Select + iCadeToKeyMap[iCadeButtonB] = NKCODE_BUTTON_7; // LTrigger + iCadeToKeyMap[iCadeButtonC] = NKCODE_BUTTON_10; // Start + iCadeToKeyMap[iCadeButtonD] = NKCODE_BUTTON_8; // RTrigger + iCadeToKeyMap[iCadeButtonE] = NKCODE_BUTTON_4; // Square + iCadeToKeyMap[iCadeButtonF] = NKCODE_BUTTON_2; // Cross + iCadeToKeyMap[iCadeButtonG] = NKCODE_BUTTON_1; // Triangle + iCadeToKeyMap[iCadeButtonH] = NKCODE_BUTTON_3; // Circle +} + +void ICadeTracker::ButtonDown(iCadeState button) { + if (simulateAnalog && + ((button == iCadeJoystickUp) || + (button == iCadeJoystickDown) || + (button == iCadeJoystickLeft) || + (button == iCadeJoystickRight))) { + AxisInput axis; + switch (button) { + case iCadeJoystickUp : + axis.axisId = JOYSTICK_AXIS_Y; + axis.value = -1.0f; + break; + + case iCadeJoystickDown : + axis.axisId = JOYSTICK_AXIS_Y; + axis.value = 1.0f; + break; + + case iCadeJoystickLeft : + axis.axisId = JOYSTICK_AXIS_X; + axis.value = -1.0f; + break; + + case iCadeJoystickRight : + axis.axisId = JOYSTICK_AXIS_X; + axis.value = 1.0f; + break; + + default: + break; + } + axis.deviceId = DEVICE_ID_PAD_0; + NativeAxis(&axis, 1); + } else { + KeyInput key; + key.flags = KEY_DOWN; + key.keyCode = iCadeToKeyMap[button]; + key.deviceId = DEVICE_ID_PAD_0; + NativeKey(key); + } +} + +void ICadeTracker::ButtonUp(iCadeState button) { + if (!iCadeConnectNotified) { + iCadeConnectNotified = true; + KeyMap::NotifyPadConnected(DEVICE_ID_PAD_0, "iCade"); + } + + if (button == iCadeButtonA) { + // Pressing Select twice within 1 second toggles the DPad between + // normal operation and simulating the Analog stick. + if ((lastSelectPress + 1.0f) > time_now_d()) + simulateAnalog = !simulateAnalog; + lastSelectPress = time_now_d(); + } + + if (button == iCadeButtonC) { + // Pressing Start twice within 1 second will take to the Emu menu + if ((lastStartPress + 1.0f) > time_now_d()) { + KeyInput key; + key.flags = KEY_DOWN; + key.keyCode = NKCODE_ESCAPE; + key.deviceId = DEVICE_ID_KEYBOARD; + NativeKey(key); + return; + } + lastStartPress = time_now_d(); + } + + if (simulateAnalog && + ((button == iCadeJoystickUp) || + (button == iCadeJoystickDown) || + (button == iCadeJoystickLeft) || + (button == iCadeJoystickRight))) { + AxisInput axis; + switch (button) { + case iCadeJoystickUp : + axis.axisId = JOYSTICK_AXIS_Y; + axis.value = 0.0f; + break; + + case iCadeJoystickDown : + axis.axisId = JOYSTICK_AXIS_Y; + axis.value = 0.0f; + break; + + case iCadeJoystickLeft : + axis.axisId = JOYSTICK_AXIS_X; + axis.value = 0.0f; + break; + + case iCadeJoystickRight : + axis.axisId = JOYSTICK_AXIS_X; + axis.value = 0.0f; + break; + + default: + break; + } + axis.deviceId = DEVICE_ID_PAD_0; + NativeAxis(&axis, 1); + } else { + KeyInput key; + key.flags = KEY_UP; + key.keyCode = iCadeToKeyMap[button]; + key.deviceId = DEVICE_ID_PAD_0; + NativeKey(key); + } +} diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index ce669d5dbd..02c795de9f 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -139,14 +139,14 @@ scale = screen.nativeScale; } - CGSize size = screen.applicationFrame.size; + CGSize size = screen.bounds.size; if (size.height > size.width) { float h = size.height; size.height = size.width; size.width = h; } - + if (screen == [UIScreen mainScreen]) { g_display.dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; } else { diff --git a/ios/ViewController.h b/ios/ViewController.h index 0e91161453..1e1678685d 100644 --- a/ios/ViewController.h +++ b/ios/ViewController.h @@ -2,9 +2,7 @@ #import #import -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 #import -#endif #import "iCade/iCadeReaderView.h" #import "CameraHelper.h" #import "LocationHelper.h" diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 10dd5bf70d..169cc32de9 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -8,6 +8,7 @@ #import "AppDelegate.h" #import "ViewController.h" #import "DisplayManager.h" +#include "Controls.h" #import "iOSCoreAudio.h" #import @@ -87,32 +88,26 @@ private: GLRenderManager *renderManager_; }; -static float dp_xscale = 1.0f; -static float dp_yscale = 1.0f; - -static double lastSelectPress = 0.0f; -static double lastStartPress = 0.0f; -static bool simulateAnalog = false; -static bool iCadeConnectNotified = false; static bool threadEnabled = true; static bool threadStopped = false; -static UITouch *g_touches[10]; id sharedViewController; -static GraphicsContext *graphicsContext; + +// TODO: Reach these through sharedViewController static CameraHelper *cameraHelper; static LocationHelper *locationHelper; @interface PPSSPPViewControllerGL () { - std::map iCadeToKeyMap; + ICadeTracker g_iCadeTracker; + TouchTracker g_touchTracker; + + GraphicsContext *graphicsContext; } @property (nonatomic, strong) EAGLContext* context; //@property (nonatomic) iCadeReaderView* iCadeView; -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 @property (nonatomic) GCController *gameController __attribute__((weak_import)); -#endif @end @@ -122,30 +117,15 @@ static LocationHelper *locationHelper; self = [super init]; if (self) { sharedViewController = self; - memset(g_touches, 0, sizeof(g_touches)); - - iCadeToKeyMap[iCadeJoystickUp] = NKCODE_DPAD_UP; - iCadeToKeyMap[iCadeJoystickRight] = NKCODE_DPAD_RIGHT; - iCadeToKeyMap[iCadeJoystickDown] = NKCODE_DPAD_DOWN; - iCadeToKeyMap[iCadeJoystickLeft] = NKCODE_DPAD_LEFT; - iCadeToKeyMap[iCadeButtonA] = NKCODE_BUTTON_9; // Select - iCadeToKeyMap[iCadeButtonB] = NKCODE_BUTTON_7; // LTrigger - iCadeToKeyMap[iCadeButtonC] = NKCODE_BUTTON_10; // Start - iCadeToKeyMap[iCadeButtonD] = NKCODE_BUTTON_8; // RTrigger - iCadeToKeyMap[iCadeButtonE] = NKCODE_BUTTON_4; // Square - iCadeToKeyMap[iCadeButtonF] = NKCODE_BUTTON_2; // Cross - iCadeToKeyMap[iCadeButtonG] = NKCODE_BUTTON_1; // Triangle - iCadeToKeyMap[iCadeButtonH] = NKCODE_BUTTON_3; // Circle + g_iCadeTracker.InitKeyMap(); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil]; -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 if ([GCController class]) // Checking the availability of a GameController framework { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil]; } -#endif } return self; } @@ -170,7 +150,6 @@ extern float g_safeInsetBottom; - (void)viewSafeAreaInsetsDidChange { if (@available(iOS 11.0, *)) { [super viewSafeAreaInsetsDidChange]; - char safeArea[100]; // we use 0.0f instead of safeAreaInsets.bottom because the bottom overlay isn't disturbing (for now) g_safeInsetLeft = self.view.safeAreaInsets.left; g_safeInsetRight = self.view.safeAreaInsets.right; @@ -181,6 +160,7 @@ extern float g_safeInsetBottom; - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + [self hideKeyboard]; } - (void)viewDidLoad { @@ -217,21 +197,16 @@ extern float g_safeInsetBottom; graphicsContext->ThreadStart(); - dp_xscale = (float)g_display.dp_xres / (float)g_display.pixel_xres; - dp_yscale = (float)g_display.dp_yres / (float)g_display.pixel_yres; - /*self.iCadeView = [[iCadeReaderView alloc] init]; [self.view addSubview:self.iCadeView]; self.iCadeView.delegate = self; self.iCadeView.active = YES;*/ -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 if ([GCController class]) { if ([[GCController controllers] count] > 0) { [self setupController:[[GCController controllers] firstObject]]; } } -#endif cameraHelper = [[CameraHelper alloc] init]; [cameraHelper setDelegate:self]; @@ -305,11 +280,9 @@ extern float g_safeInsetBottom; [[NSNotificationCenter defaultCenter] removeObserver:self]; -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 if ([GCController class]) { self.gameController = nil; } -#endif if (graphicsContext) { graphicsContext->Shutdown(); @@ -325,13 +298,6 @@ extern float g_safeInsetBottom; [self shutdown]; } -// For iOS before 6.0 -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation -{ - return UIInterfaceOrientationIsLandscape(toInterfaceOrientation); -} - -// For iOS 6.0 and up - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskLandscape; @@ -343,106 +309,24 @@ extern float g_safeInsetBottom; graphicsContext->ThreadFrame(); } -- (void)touchX:(float)x y:(float)y code:(int)code pointerId:(int)pointerId -{ - float scale = [UIScreen mainScreen].scale; - - if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) { - scale = [UIScreen mainScreen].nativeScale; - } - - float scaledX = (int)(x * dp_xscale) * scale; - float scaledY = (int)(y * dp_yscale) * scale; - - TouchInput input; - input.x = scaledX; - input.y = scaledY; - switch (code) { - case 1 : - input.flags = TOUCH_DOWN; - break; - - case 2 : - input.flags = TOUCH_UP; - break; - - default : - input.flags = TOUCH_MOVE; - break; - } - input.id = pointerId; - NativeTouch(input); -} - -int ToTouchID(UITouch *uiTouch, bool allowAllocate) { - // Find the id for the touch. - for (int localId = 0; localId < (int)ARRAY_SIZE(g_touches); ++localId) { - if (g_touches[localId] == uiTouch) { - return localId; - } - } - - // Allocate a new one, perhaps? - if (allowAllocate) { - for (int localId = 0; localId < (int)ARRAY_SIZE(g_touches); ++localId) { - if (g_touches[localId] == 0) { - g_touches[localId] = uiTouch; - return localId; - } - } - - // None were free. Ignore? - return 0; - } - - return -1; -} - - - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - for(UITouch* touch in touches) - { - CGPoint point = [touch locationInView:self.view]; - int touchId = ToTouchID(touch, true); - [self touchX:point.x y:point.y code:1 pointerId:touchId]; - } + g_touchTracker.Began(touches, self.view); } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - for(UITouch* touch in touches) - { - CGPoint point = [touch locationInView:self.view]; - int touchId = ToTouchID(touch, true); - [self touchX:point.x y:point.y code:0 pointerId: touchId]; - } + g_touchTracker.Moved(touches, self.view); } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - for(UITouch* touch in touches) - { - CGPoint point = [touch locationInView:self.view]; - int touchId = ToTouchID(touch, false); - if (touchId >= 0) { - [self touchX:point.x y:point.y code:2 pointerId: touchId]; - g_touches[touchId] = nullptr; - } - } + g_touchTracker.Ended(touches, self.view); } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - for(UITouch* touch in touches) - { - CGPoint point = [touch locationInView:self.view]; - int touchId = ToTouchID(touch, false); - if (touchId >= 0) { - [self touchX:point.x y:point.y code:2 pointerId: touchId]; - g_touches[touchId] = nullptr; - } - } + g_touchTracker.Cancelled(touches, self.view); } - (void)bindDefaultFBO @@ -452,118 +336,14 @@ int ToTouchID(UITouch *uiTouch, bool allowAllocate) { - (void)buttonDown:(iCadeState)button { - if (simulateAnalog && - ((button == iCadeJoystickUp) || - (button == iCadeJoystickDown) || - (button == iCadeJoystickLeft) || - (button == iCadeJoystickRight))) { - AxisInput axis; - switch (button) { - case iCadeJoystickUp : - axis.axisId = JOYSTICK_AXIS_Y; - axis.value = -1.0f; - break; - - case iCadeJoystickDown : - axis.axisId = JOYSTICK_AXIS_Y; - axis.value = 1.0f; - break; - - case iCadeJoystickLeft : - axis.axisId = JOYSTICK_AXIS_X; - axis.value = -1.0f; - break; - - case iCadeJoystickRight : - axis.axisId = JOYSTICK_AXIS_X; - axis.value = 1.0f; - break; - - default: - break; - } - axis.deviceId = DEVICE_ID_PAD_0; - NativeAxis(&axis, 1); - } else { - KeyInput key; - key.flags = KEY_DOWN; - key.keyCode = iCadeToKeyMap[button]; - key.deviceId = DEVICE_ID_PAD_0; - NativeKey(key); - } + g_iCadeTracker.ButtonDown(button); } - (void)buttonUp:(iCadeState)button { - if (!iCadeConnectNotified) { - iCadeConnectNotified = true; - KeyMap::NotifyPadConnected(DEVICE_ID_PAD_0, "iCade"); - } - - if (button == iCadeButtonA) { - // Pressing Select twice within 1 second toggles the DPad between - // normal operation and simulating the Analog stick. - if ((lastSelectPress + 1.0f) > time_now_d()) - simulateAnalog = !simulateAnalog; - lastSelectPress = time_now_d(); - } - - if (button == iCadeButtonC) { - // Pressing Start twice within 1 second will take to the Emu menu - if ((lastStartPress + 1.0f) > time_now_d()) { - KeyInput key; - key.flags = KEY_DOWN; - key.keyCode = NKCODE_ESCAPE; - key.deviceId = DEVICE_ID_KEYBOARD; - NativeKey(key); - return; - } - lastStartPress = time_now_d(); - } - - if (simulateAnalog && - ((button == iCadeJoystickUp) || - (button == iCadeJoystickDown) || - (button == iCadeJoystickLeft) || - (button == iCadeJoystickRight))) { - AxisInput axis; - switch (button) { - case iCadeJoystickUp : - axis.axisId = JOYSTICK_AXIS_Y; - axis.value = 0.0f; - break; - - case iCadeJoystickDown : - axis.axisId = JOYSTICK_AXIS_Y; - axis.value = 0.0f; - break; - - case iCadeJoystickLeft : - axis.axisId = JOYSTICK_AXIS_X; - axis.value = 0.0f; - break; - - case iCadeJoystickRight : - axis.axisId = JOYSTICK_AXIS_X; - axis.value = 0.0f; - break; - - default: - break; - } - axis.deviceId = DEVICE_ID_PAD_0; - NativeAxis(&axis, 1); - } else { - KeyInput key; - key.flags = KEY_UP; - key.keyCode = iCadeToKeyMap[button]; - key.deviceId = DEVICE_ID_PAD_0; - NativeKey(key); - } - + g_iCadeTracker.ButtonUp(button); } -#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 - (void)controllerDidConnect:(NSNotification *)note { if (![[GCController controllers] containsObject:self.gameController]) self.gameController = nil; @@ -584,15 +364,6 @@ int ToTouchID(UITouch *uiTouch, bool allowAllocate) { } } -- (void)controllerButtonPressed:(BOOL)pressed keyCode:(InputKeyCode)keyCode -{ - KeyInput key; - key.deviceId = DEVICE_ID_PAD_0; - key.flags = pressed ? KEY_DOWN : KEY_UP; - key.keyCode = keyCode; - NativeKey(key); -} - // Enables tapping for edge area. -(UIRectEdge)preferredScreenEdgesDeferringSystemGestures { @@ -606,139 +377,10 @@ int ToTouchID(UITouch *uiTouch, bool allowAllocate) { - (void)setupController:(GCController *)controller { self.gameController = controller; - - GCGamepad *baseProfile = self.gameController.gamepad; - if (baseProfile == nil) { + if (!SetupController(controller)) { self.gameController = nil; - return; } - - self.gameController.controllerPausedHandler = ^(GCController *controller) { - KeyInput key; - key.flags = KEY_DOWN; - key.keyCode = NKCODE_ESCAPE; - key.deviceId = DEVICE_ID_KEYBOARD; - NativeKey(key); - }; - - baseProfile.buttonA.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_2]; // Cross - }; - - baseProfile.buttonB.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_3]; // Circle - }; - - baseProfile.buttonX.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_4]; // Square - }; - - baseProfile.buttonY.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_1]; // Triangle - }; - - baseProfile.leftShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_7]; // LTrigger - }; - - baseProfile.rightShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_8]; // RTrigger - }; - - baseProfile.dpad.up.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_UP]; - }; - - baseProfile.dpad.down.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_DOWN]; - }; - - baseProfile.dpad.left.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_LEFT]; - }; - - baseProfile.dpad.right.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_RIGHT]; - }; - - GCExtendedGamepad *extendedProfile = self.gameController.extendedGamepad; - if (extendedProfile == nil) - return; // controller doesn't support extendedGamepad profile - - extendedProfile.leftTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_9]; // Select - }; - - extendedProfile.rightTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_10]; // Start - }; - -#if defined(__IPHONE_12_1) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_1 - if ([extendedProfile respondsToSelector:@selector(leftThumbstickButton)] && extendedProfile.leftThumbstickButton != nil) { - extendedProfile.leftThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_11]; - }; - } - if ([extendedProfile respondsToSelector:@selector(rightThumbstickButton)] && extendedProfile.rightThumbstickButton != nil) { - extendedProfile.rightThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_12]; - }; - } -#endif -#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 - if ([extendedProfile respondsToSelector:@selector(buttonOptions)] && extendedProfile.buttonOptions != nil) { - extendedProfile.buttonOptions.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_13]; - }; - } - if ([extendedProfile respondsToSelector:@selector(buttonMenu)] && extendedProfile.buttonMenu != nil) { - extendedProfile.buttonMenu.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_14]; - }; - } -#endif -#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 - if ([extendedProfile respondsToSelector:@selector(buttonHome)] && extendedProfile.buttonHome != nil) { - extendedProfile.buttonHome.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { - [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_15]; - }; - } -#endif - - extendedProfile.leftThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { - AxisInput axisInput; - axisInput.deviceId = DEVICE_ID_PAD_0; - axisInput.axisId = JOYSTICK_AXIS_X; - axisInput.value = value; - NativeAxis(&axisInput, 1); - }; - - extendedProfile.leftThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { - AxisInput axisInput; - axisInput.deviceId = DEVICE_ID_PAD_0; - axisInput.axisId = JOYSTICK_AXIS_Y; - axisInput.value = -value; - NativeAxis(&axisInput, 1); - }; - - // Map right thumbstick as another analog stick, particularly useful for controllers like the DualShock 3/4 when connected to an iOS device - extendedProfile.rightThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { - AxisInput axisInput; - axisInput.deviceId = DEVICE_ID_PAD_0; - axisInput.axisId = JOYSTICK_AXIS_Z; - axisInput.value = value; - NativeAxis(&axisInput, 1); - }; - - extendedProfile.rightThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) { - AxisInput axisInput; - axisInput.deviceId = DEVICE_ID_PAD_0; - axisInput.axisId = JOYSTICK_AXIS_RZ; - axisInput.value = -value; - NativeAxis(&axisInput, 1); - }; } -#endif void setCameraSize(int width, int height) { [cameraHelper setCameraSize: width h:height]; @@ -824,14 +466,6 @@ void stopLocation() { @end -void System_LaunchUrl(LaunchUrlType urlType, char const* url) -{ - NSURL *nsUrl = [NSURL URLWithString:[NSString stringWithCString:url encoding:NSStringEncodingConversionAllowLossy]]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] openURL:nsUrl options:@{} completionHandler:nil]; - }); -} - void bindDefaultFBO() { [sharedViewController bindDefaultFBO]; diff --git a/ios/iOSCoreAudio.mm b/ios/iOSCoreAudio.mm index 8743ab7953..8129cc9afc 100644 --- a/ios/iOSCoreAudio.mm +++ b/ios/iOSCoreAudio.mm @@ -25,10 +25,9 @@ #include #import - #define SAMPLE_RATE 44100 -AudioComponentInstance audioInstance = nil; +static AudioComponentInstance audioInstance = nil; int NativeMix(short *audio, int numSamples, int sampleRate); @@ -77,83 +76,85 @@ void iOSCoreAudioInit() } } - if (!audioInstance) { - OSErr err; - - // first, grab the default output - AudioComponentDescription defaultOutputDescription; - defaultOutputDescription.componentType = kAudioUnitType_Output; - defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO; - defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; - defaultOutputDescription.componentFlags = 0; - defaultOutputDescription.componentFlagsMask = 0; - AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); - - // create our instance - err = AudioComponentInstanceNew(defaultOutput, &audioInstance); - if (err != noErr) { - audioInstance = nil; - return; - } - - // create our callback so we can give it the audio data - AURenderCallbackStruct input; - input.inputProc = iOSCoreAudioCallback; - input.inputProcRefCon = NULL; - err = AudioUnitSetProperty(audioInstance, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, - 0, - &input, - sizeof(input)); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // setup the audio format we'll be using (stereo pcm) - AudioStreamBasicDescription streamFormat; - memset(&streamFormat, 0, sizeof(streamFormat)); - streamFormat.mSampleRate = SAMPLE_RATE; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - streamFormat.mBitsPerChannel = sizeof(short) * 8; - streamFormat.mChannelsPerFrame = 2; - streamFormat.mFramesPerPacket = 1; - streamFormat.mBytesPerFrame = (streamFormat.mBitsPerChannel / 8) * streamFormat.mChannelsPerFrame; - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; - err = AudioUnitSetProperty(audioInstance, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 0, - &streamFormat, - sizeof(AudioStreamBasicDescription)); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // k, all setup, so init - err = AudioUnitInitialize(audioInstance); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // finally start playback - err = AudioOutputUnitStart(audioInstance); - if (err != noErr) { - AudioUnitUninitialize(audioInstance); - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // we're good to go + if (audioInstance) { + // Already running + return; } + OSErr err; + + // first, grab the default output + AudioComponentDescription defaultOutputDescription; + defaultOutputDescription.componentType = kAudioUnitType_Output; + defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO; + defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + defaultOutputDescription.componentFlags = 0; + defaultOutputDescription.componentFlagsMask = 0; + AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); + + // create our instance + err = AudioComponentInstanceNew(defaultOutput, &audioInstance); + if (err != noErr) { + audioInstance = nil; + return; + } + + // create our callback so we can give it the audio data + AURenderCallbackStruct input; + input.inputProc = iOSCoreAudioCallback; + input.inputProcRefCon = NULL; + err = AudioUnitSetProperty(audioInstance, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof(input)); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // setup the audio format we'll be using (stereo pcm) + AudioStreamBasicDescription streamFormat; + memset(&streamFormat, 0, sizeof(streamFormat)); + streamFormat.mSampleRate = SAMPLE_RATE; + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + streamFormat.mBitsPerChannel = sizeof(short) * 8; + streamFormat.mChannelsPerFrame = 2; + streamFormat.mFramesPerPacket = 1; + streamFormat.mBytesPerFrame = (streamFormat.mBitsPerChannel / 8) * streamFormat.mChannelsPerFrame; + streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; + err = AudioUnitSetProperty(audioInstance, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(AudioStreamBasicDescription)); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // k, all setup, so init + err = AudioUnitInitialize(audioInstance); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // finally start playback + err = AudioOutputUnitStart(audioInstance); + if (err != noErr) { + AudioUnitUninitialize(audioInstance); + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // we're good to go } void iOSCoreAudioShutdown() diff --git a/ios/main.mm b/ios/main.mm index ae369324df..0466c67000 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -489,7 +489,17 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string void System_Toast(std::string_view text) {} void System_AskForPermission(SystemPermission permission) {} -PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; } +void System_LaunchUrl(LaunchUrlType urlType, const char *url) +{ + NSURL *nsUrl = [NSURL URLWithString:[NSString stringWithCString:url encoding:NSStringEncodingConversionAllowLossy]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] openURL:nsUrl options:@{} completionHandler:nil]; + }); +} + +PermissionStatus System_GetPermissionStatus(SystemPermission permission) { + return PERMISSION_STATUS_GRANTED; +} #if !PPSSPP_PLATFORM(IOS_APP_STORE) FOUNDATION_EXTERN void AudioServicesPlaySystemSoundWithVibration(unsigned long, objc_object*, NSDictionary*);