iOS: multiple controller/rumble support. GL: fix black screen after RTT

iOS:
* multiple controllers support. Rumble support.
* fix audio recording
* declare CHD/GDI/CUE/CDI content types
* start game when opening file URL
* Use safe area for UI and virtual gamepad
* Better icons
This commit is contained in:
Flyinghead 2021-08-23 14:02:12 +02:00
parent 8b2ed736a6
commit e3e3229612
45 changed files with 1105 additions and 587 deletions

View File

@ -1131,7 +1131,7 @@ if(NOT LIBRETRO)
shell/apple/emulator-ios/emulator/AppDelegate.h
shell/apple/emulator-ios/emulator/AppDelegate.mm
shell/apple/emulator-ios/emulator/ios_main.mm
shell/apple/emulator-ios/emulator/ios_mouse.h
shell/apple/emulator-ios/emulator/ios_gamepad.h
shell/apple/emulator-ios/emulator/FlycastViewController.h
shell/apple/emulator-ios/emulator/FlycastViewController.mm
shell/apple/emulator-ios/emulator/PadViewController.h
@ -1184,13 +1184,15 @@ if(NOT LIBRETRO)
find_library(GLKIT GLKit)
find_library(GAMECONTROLLER GameController)
find_library(AUDIOTOOLBOX AudioToolbox)
find_library(AVFOUNDATION AVFoundation)
target_link_libraries(${PROJECT_NAME} PRIVATE
${UIKIT}
${FOUNDATION}
${OPENGLES}
${GLKIT}
${GAMECONTROLLER}
${AUDIOTOOLBOX})
${AUDIOTOOLBOX}
${AVFOUNDATION})
add_custom_target(Flycast.IPA ALL
DEPENDS ${PROJECT_NAME}

View File

@ -34,7 +34,7 @@ public:
const std::string& api_name() { return _api_name; }
const std::string& name() { return _name; }
int maple_port() const { return _maple_port; }
void set_maple_port(int port) { _maple_port = port; }
virtual void set_maple_port(int port) { _maple_port = port; }
const std::string& unique_id() { return _unique_id; }
virtual bool gamepad_btn_input(u32 code, bool pressed);
bool gamepad_axis_input(u32 code, int value);

View File

@ -37,19 +37,19 @@ bool MiniUPnP::Init()
#endif
if (devlist == nullptr)
{
INFO_LOG(MODEM, "UPnP discover failed: error %d", error);
WARN_LOG(MODEM, "UPnP discover failed: error %d", error);
return false;
}
error = UPNP_GetValidIGD(devlist, &urls, &data, lanAddress, sizeof(lanAddress));
freeUPNPDevlist(devlist);
if (error != 1)
{
INFO_LOG(MODEM, "Internet Gateway not found: error %d", error);
WARN_LOG(MODEM, "Internet Gateway not found: error %d", error);
return false;
}
wanAddress[0] = 0;
if (UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, wanAddress) != 0)
INFO_LOG(MODEM, "Cannot determine external IP address");
WARN_LOG(MODEM, "Cannot determine external IP address");
return true;
}
@ -75,7 +75,7 @@ bool MiniUPnP::AddPortMapping(int port, bool tcp)
"86400"); // port map lease duration (in seconds) or zero for "as long as possible"
if (error != 0)
{
INFO_LOG(MODEM, "Port %d redirection failed: error %d", port, error);
WARN_LOG(MODEM, "Port %d redirection failed: error %d", port, error);
return false;
}
mappedPorts.emplace_back(portStr, tcp);

View File

@ -80,7 +80,6 @@ static void coreaudio_init()
#else
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
//desc.componentSubType = kAudioUnitSubType_GenericOutput;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
@ -194,7 +193,7 @@ static void coreaudio_term_record()
static bool coreaudio_init_record(u32 sampling_freq)
{
AudioStreamBasicDescription desc;
AudioStreamBasicDescription desc{};
desc.mFormatID = kAudioFormatLinearPCM;
desc.mSampleRate = (double)sampling_freq;
desc.mChannelsPerFrame = 1;
@ -213,7 +212,7 @@ static bool coreaudio_init_record(u32 sampling_freq)
&recordQueue);
if (err != noErr)
{
INFO_LOG(AUDIO, "AudioQueueNewInput failed: %d", err);
ERROR_LOG(AUDIO, "AudioQueueNewInput failed: %d", err);
return false;
}
@ -230,11 +229,11 @@ static bool coreaudio_init_record(u32 sampling_freq)
err = AudioQueueStart(recordQueue, nullptr);
if (err != noErr)
{
INFO_LOG(AUDIO, "AudioQueue init failed: %d", err);
ERROR_LOG(AUDIO, "AudioQueue init failed: %d", err);
coreaudio_term_record();
return false;
}
DEBUG_LOG(AUDIO, "AudioQueue initialized - sample rate %f", desc.mSampleRate);
INFO_LOG(AUDIO, "AudioQueue initialized - sample rate %f", desc.mSampleRate);
return true;
}

View File

@ -671,7 +671,7 @@ static void resize(int w, int h)
}
gl4CreateTextures(max_image_width, max_image_height);
reshapeABuffer(max_image_width, max_image_height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, gl.ofbo.origFbo);
}
}
@ -684,6 +684,12 @@ static bool RenderFrame(int width, int height)
const glm::mat4& scissor_mat = matrices.GetScissorMatrix();
ViewportMatrix = matrices.GetViewportMatrix();
#ifdef LIBRETRO
gl.ofbo.origFbo = glsm_get_current_framebuffer();
#else
gl.ofbo.origFbo = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&gl.ofbo.origFbo);
#endif
if (!is_rtt)
gcflip = 0;
else

View File

@ -1197,6 +1197,12 @@ bool RenderFrame(int width, int height)
}
//setup render target first
#ifdef LIBRETRO
gl.ofbo.origFbo = glsm_get_current_framebuffer();
#else
gl.ofbo.origFbo = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&gl.ofbo.origFbo);
#endif
if (is_rtt)
{
if (BindRTT() == 0)

View File

@ -368,9 +368,7 @@ void ReadRTTBuffer()
}
gl.rtt.texAddress = ~0;
}
#ifdef LIBRETRO
glBindFramebuffer(GL_FRAMEBUFFER, hw_render.get_current_framebuffer());
#endif
glBindFramebuffer(GL_FRAMEBUFFER, gl.ofbo.origFbo);
}
static void readAsyncPixelBuffer(u32 addr)
@ -484,8 +482,6 @@ GLuint init_output_framebuffer(int width, int height)
gl.ofbo.width = width;
gl.ofbo.height = height;
}
gl.ofbo.origFbo = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&gl.ofbo.origFbo);
if (gl.ofbo.fbo == 0)
{

View File

@ -1,8 +1,24 @@
/*
Copyright (c) 2014 Lounge Katt. All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by Lounge Katt on 2/6/14.
// Copyright (c) 2014 Lounge Katt. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>

View File

@ -1,24 +1,48 @@
/*
Copyright 2021 flyinghead
Copyright (c) 2014 Lounge Katt. All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by Lounge Katt on 2/6/14.
// Copyright (c) 2014 Lounge Katt. All rights reserved.
//
#import "AppDelegate.h"
#import <AVFoundation/AVFoundation.h>
#include "emulator.h"
#include "log/LogManager.h"
#include "cfg/option.h"
#include "rend/gui.h"
static bool emulatorRunning;
@implementation AppDelegate
@implementation AppDelegate {
NSURL *openedURL;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([UIDevice currentDevice].systemVersion.floatValue >= 7.0f) // TODO: consider using the black variant for iOS 5.
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
else
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];
// Override point for customization after application launch.
// Allow audio playing AND recording
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
[session setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&error];
return YES;
}
@ -61,4 +85,21 @@ static bool emulatorRunning;
LogManager::Shutdown();
}
- (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
if (!url.fileURL)
return false;
if (openedURL != nil)
{
[openedURL stopAccessingSecurityScopedResource];
openedURL = nil;
}
if ([url startAccessingSecurityScopedResource])
openedURL = url;
gui_state = GuiState::Closed;
gui_start_game(url.fileSystemRepresentation);
return true;
}
@end

View File

@ -1,12 +1,24 @@
//
// Copyright (c) 2015 reicast. All rights reserved.
//
/*
Copyright 2021 flyinghead
Copyright (c) 2015 reicast. All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#import <GLKit/GLKit.h>
#include "ios_mouse.h"
@interface EmulatorView : GLKView
@property IOSMouse *mouse;
@end

View File

@ -1,19 +1,44 @@
//
// Copyright (c) 2015 reicast. All rights reserved.
//
/*
Copyright 2021 flyinghead
Copyright (c) 2015 reicast. All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#import "EmulatorView.h"
#include "types.h"
#include "rend/gui.h"
#include "ios_gamepad.h"
@implementation EmulatorView
@implementation EmulatorView {
std::shared_ptr<IOSMouse> mouse;
}
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
mouse = std::make_shared<IOSMouse>();
GamepadDevice::Register(mouse);
}
- (void)touchLocation:(UITouch*)touch;
{
float scale = self.contentScaleFactor;
CGPoint location = [touch locationInView:touch.view];
_mouse->setAbsPos(location.x * scale, location.y * scale, self.bounds.size.width * scale, self.bounds.size.height * scale);
mouse->setAbsPos(location.x * scale, location.y * scale, self.bounds.size.width * scale, self.bounds.size.height * scale);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
@ -21,7 +46,7 @@
UITouch *touch = [touches anyObject];
[self touchLocation:touch];
if (gui_is_open())
_mouse->setButton(Mouse::LEFT_BUTTON, true);
mouse->setButton(Mouse::LEFT_BUTTON, true);
[super touchesBegan:touches withEvent:event];
}
@ -30,7 +55,7 @@
UITouch *touch = [touches anyObject];
[self touchLocation:touch];
if (gui_is_open())
_mouse->setButton(Mouse::LEFT_BUTTON, false);
mouse->setButton(Mouse::LEFT_BUTTON, false);
[super touchesEnded:touches withEvent:event];
}

View File

@ -1,13 +1,25 @@
/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#import "iCade-iOS/iCadeReaderView.h"
#include "ios_mouse.h"
@interface FlycastViewController : GLKViewController <iCadeEventDelegate>
- (void)handleKeyDown:(enum IOSButton)button;
- (void)handleKeyUp:(enum IOSButton)button;
@end

View File

@ -1,10 +1,25 @@
//
// Copyright (c) 2014 Karen Tsai (angelXwind). All rights reserved.
//
/*
Copyright 2021 flyinghead
Copyright (c) 2014 Karen Tsai (angelXwind). All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#import "FlycastViewController.h"
#import <GameController/GameController.h>
#import <Network/Network.h>
//#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
@ -14,24 +29,23 @@
#import "EmulatorView.h"
#include "types.h"
#include "input/gamepad_device.h"
#include "wsi/context.h"
#include "rend/mainui.h"
#include "emulator.h"
#include "log/LogManager.h"
#include "stdclass.h"
#include "cfg/option.h"
#include "ios_mouse.h"
#include "rend/gui.h"
#include "ios_gamepad.h"
//@import AltKit;
#import "AltKit/AltKit-Swift.h"
std::string iosJitStatus;
static bool iosJitAuthorized;
static std::shared_ptr<IOSMouse> mouse;
static __unsafe_unretained FlycastViewController *flycastViewController;
std::map<GCController *, std::shared_ptr<IOSGamepad>> IOSGamepad::controllers;
void common_linux_setup();
@interface FlycastViewController () <UIDocumentPickerDelegate>
@ -40,7 +54,6 @@ void common_linux_setup();
@property (strong, nonatomic) PadViewController *padController;
@property (nonatomic) iCadeReaderView* iCadeReader;
@property (nonatomic) GCController *gController __attribute__((weak_import));
@property (nonatomic, strong) id connectObserver;
@property (nonatomic, strong) id disconnectObserver;
@ -113,31 +126,32 @@ extern int screen_dpi;
[EAGLContext setCurrentContext:self.context];
self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// GCController *controller = (GCController *)note.object;
// IOSGame::addController(controller);
if (GCController.controllers.count > 0) {
[self toggleHardwareController:YES];
}
GCController *controller = note.object;
IOSGamepad::addController(controller);
#if !TARGET_OS_TV
if (IOSGamepad::controllerConnected())
[self.padController hideController];
#endif
}];
self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// GCController *controller = (GCController *)note.object;
// IOSGame::removeController(controller);
if (GCController.controllers.count == 0) {
[self toggleHardwareController:NO];
}
GCController *controller = note.object;
IOSGamepad::removeController(controller);
#if !TARGET_OS_TV
if (!IOSGamepad::controllerConnected())
[self.padController showController:self.view];
#endif
}];
if ([[GCController controllers] count]) {
[self toggleHardwareController:YES];
}
for (GCController *controller in [GCController controllers])
IOSGamepad::addController(controller);
#if !TARGET_OS_TV
[self addChildViewController:self.padController];
self.padController.view.frame = self.view.bounds;
self.padController.view.translatesAutoresizingMaskIntoConstraints = YES;
self.padController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth;
self.padController.handler = self;
[self.padController hideController];
self.padController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if (IOSGamepad::controllerConnected())
[self.padController hideController];
#endif
self.iCadeReader = [[iCadeReaderView alloc] init];
@ -159,10 +173,19 @@ extern int screen_dpi;
mainui_init();
mainui_enabled = true;
emuView.mouse = ::mouse.get();
[self altKitStart];
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
-(UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
return UIRectEdgeAll;
@ -228,6 +251,14 @@ extern int screen_dpi;
nw_path_monitor_start(self.monitor);
}
- (void)viewSafeAreaInsetsDidChange
{
[super viewSafeAreaInsetsDidChange];
float scale = self.view.contentScaleFactor;
gui_set_insets(self.view.safeAreaInsets.left * scale, self.view.safeAreaInsets.right * scale,
self.view.safeAreaInsets.top * scale, self.view.safeAreaInsets.bottom * scale);
}
#pragma mark - GLKView and GLKViewController delegate methods
- (void)update
@ -235,323 +266,29 @@ extern int screen_dpi;
}
- (void)toggleHardwareController:(BOOL)useHardware
{
if (useHardware)
{
[self.padController hideController];
#if TARGET_OS_TV
for (GCController*c in GCController.controllers) {
if ((c.gamepad != nil || c.extendedGamepad != nil) && c != _gController) {
self.gController = c;
break;
}
}
#else
self.gController = [GCController controllers].firstObject;
#endif
// TODO: Add multi player using gController.playerIndex and iterate all controllers
if (self.gController.extendedGamepad)
{
[self.gController.extendedGamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_A];
else
[self handleKeyUp:IOS_BTN_A];
}];
[self.gController.extendedGamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_B];
else
[self handleKeyUp:IOS_BTN_B];
}];
[self.gController.extendedGamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_X];
else
[self handleKeyUp:IOS_BTN_X];
}];
[self.gController.extendedGamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_Y];
else
[self handleKeyUp:IOS_BTN_Y];
}];
[self.gController.extendedGamepad.rightTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
rt[0] = (int)std::roundf(255.f * value);
}];
[self.gController.extendedGamepad.leftTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
lt[0] = (int)std::roundf(255.f * value);
}];
if (@available(iOS 13.0, *)) {
[self.gController.extendedGamepad.buttonOptions setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_OPTIONS];
else
[self handleKeyUp:IOS_BTN_OPTIONS];
}];
[self.gController.extendedGamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_L1];
else
[self handleKeyUp:IOS_BTN_L1];
}];
} else {
// Left shoulder for options/menu
[self.gController.extendedGamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_OPTIONS];
else
[self handleKeyUp:IOS_BTN_OPTIONS];
}];
}
if (@available(iOS 13.0, *)) {
[self.gController.extendedGamepad.buttonMenu setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_MENU];
else
[self handleKeyUp:IOS_BTN_MENU];
}];
[self.gController.extendedGamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_R1];
else
[self handleKeyUp:IOS_BTN_R1];
}];
} else {
// Right shoulder for menu/start
[self.gController.extendedGamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_MENU];
else
[self handleKeyUp:IOS_BTN_MENU];
}];
}
[self.gController.extendedGamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
if (dpad.right.isPressed)
[self handleKeyDown:IOS_BTN_RIGHT];
else
[self handleKeyUp:IOS_BTN_RIGHT];
if (dpad.left.isPressed)
[self handleKeyDown:IOS_BTN_LEFT];
else
[self handleKeyUp:IOS_BTN_LEFT];
if (dpad.up.isPressed)
[self handleKeyDown:IOS_BTN_UP];
else
[self handleKeyUp:IOS_BTN_UP];
if (dpad.down.isPressed)
[self handleKeyDown:IOS_BTN_DOWN];
else
[self handleKeyUp:IOS_BTN_DOWN];
}];
[self.gController.extendedGamepad.leftThumbstick.xAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
s8 v = (s8)(value * 127); //-127 ... + 127 range
joyx[0] = v;
}];
[self.gController.extendedGamepad.leftThumbstick.yAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
s8 v = (s8)(value * 127 * -1); //-127 ... + 127 range
joyy[0] = v;
}];
[self.gController.extendedGamepad.rightThumbstick.xAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
s8 v = (s8)(value * 127); //-127 ... + 127 range
joyrx[0] = v;
}];
[self.gController.extendedGamepad.rightThumbstick.yAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
s8 v = (s8)(value * 127 * -1); //-127 ... + 127 range
joyry[0] = v;
}];
}
else if (self.gController.gamepad) {
[self.gController.gamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_A];
else
[self handleKeyUp:IOS_BTN_A];
}];
[self.gController.gamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_B];
else
[self handleKeyUp:IOS_BTN_B];
}];
[self.gController.gamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_X];
else
[self handleKeyUp:IOS_BTN_X];
}];
[self.gController.gamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed)
[self handleKeyDown:IOS_BTN_Y];
else
[self handleKeyUp:IOS_BTN_Y];
}];
[self.gController.gamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue){
if (dpad.right.isPressed)
[self handleKeyDown:IOS_BTN_RIGHT];
else
[self handleKeyUp:IOS_BTN_RIGHT];
if (dpad.left.isPressed)
[self handleKeyDown:IOS_BTN_LEFT];
else
[self handleKeyUp:IOS_BTN_LEFT];
if (dpad.up.isPressed)
[self handleKeyDown:IOS_BTN_UP];
else
[self handleKeyUp:IOS_BTN_UP];
if (dpad.down.isPressed)
[self handleKeyDown:IOS_BTN_DOWN];
else
[self handleKeyUp:IOS_BTN_DOWN];
}];
[self.gController.gamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed) {
if (self.gController.gamepad.leftShoulder.pressed)
[self handleKeyDown:IOS_BTN_MENU];
else
[self handleKeyDown:IOS_BTN_R2];
}
else {
[self handleKeyUp:IOS_BTN_R2];
[self handleKeyUp:IOS_BTN_MENU];
}
}];
[self.gController.gamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed) {
if (self.gController.gamepad.rightShoulder.pressed)
[self handleKeyDown:IOS_BTN_MENU];
else
[self handleKeyDown:IOS_BTN_L2];
}
else {
[self handleKeyUp:IOS_BTN_L2];
[self handleKeyUp:IOS_BTN_MENU];
}
}];
//Add controller pause handler here
}
} else {
self.gController = nil;
[self.padController showController:self.view];
}
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
if (dc_is_running() != [self.padController isControllerVisible] && self.gController == nil)
#if !TARGET_OS_TV
if (dc_is_running() != [self.padController isControllerVisible] && !IOSGamepad::controllerConnected())
{
if (dc_is_running())
[self.padController showController:self.view];
else
[self.padController hideController];
}
#endif
mainui_rend_frame();
}
static DreamcastKey IosToDCKey[IOS_BTN_MAX] {
EMU_BTN_NONE, // none
DC_BTN_A,
DC_BTN_B,
DC_BTN_X,
DC_BTN_Y,
DC_DPAD_UP,
DC_DPAD_DOWN,
DC_DPAD_LEFT,
DC_DPAD_RIGHT,
DC_BTN_START, // menu
EMU_BTN_MENU, // options
EMU_BTN_NONE, // home
EMU_BTN_NONE, // L1
EMU_BTN_NONE, // R1
EMU_BTN_NONE, // L3
EMU_BTN_NONE, // R3
EMU_BTN_TRIGGER_LEFT, // L2
EMU_BTN_TRIGGER_RIGHT, // R2
};
- (void)handleKeyDown:(enum IOSButton)button;
{
DreamcastKey dcKey = IosToDCKey[button];
switch (dcKey) {
case EMU_BTN_NONE:
break;
case EMU_BTN_MENU:
gui_open_settings();
break;
case EMU_BTN_TRIGGER_LEFT:
lt[0] = 0xff;
break;
case EMU_BTN_TRIGGER_RIGHT:
rt[0] = 0xff;
break;
default:
if (dcKey < EMU_BTN_TRIGGER_LEFT)
kcode[0] &= ~dcKey;
break;
}
// Open menu with UP + DOWN or LEFT + RIGHT
if ((kcode[0] & (DC_DPAD_UP | DC_DPAD_DOWN)) == 0
|| (kcode[0] & (DC_DPAD_LEFT | DC_DPAD_RIGHT)) == 0) {
kcode[0] = ~0;
gui_open_settings();
}
// Arcade shortcuts
if (rt[0] > 0)
{
if ((kcode[0] & DC_BTN_A) == 0)
// RT + A -> D (coin)
kcode[0] &= ~DC_BTN_D;
if ((kcode[0] & DC_BTN_B) == 0)
// RT + B -> C (service)
kcode[0] &= ~DC_BTN_C;
if ((kcode[0] & DC_BTN_X) == 0)
// RT + X -> Z (test)
kcode[0] &= ~DC_BTN_Z;
}
}
- (void)handleKeyUp:(enum IOSButton)button;
{
DreamcastKey dcKey = IosToDCKey[button];
switch (dcKey) {
case EMU_BTN_NONE:
break;
case EMU_BTN_TRIGGER_LEFT:
lt[0] = 0;
break;
case EMU_BTN_TRIGGER_RIGHT:
rt[0] = 0;
break;
default:
if (dcKey < EMU_BTN_TRIGGER_LEFT)
kcode[0] |= dcKey;
break;
}
if (rt[0] == 0)
kcode[0] |= DC_BTN_D | DC_BTN_C | DC_BTN_Z;
else
{
if ((kcode[0] & DC_BTN_A) != 0)
kcode[0] |= DC_BTN_D;
if ((kcode[0] & DC_BTN_B) != 0)
kcode[0] |= DC_BTN_C;
if ((kcode[0] & DC_BTN_X) != 0)
kcode[0] |= DC_BTN_Z;
}
}
/*
- (void)pickIosFile
{
UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"com.flyinghead.flycast.disk-image", @"com.pkware.zip-archive"] inMode:UIDocumentPickerModeOpen];
picker.delegate = self;
picker.allowsMultipleSelection = true;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)pickIosFolder
{
if (@available(iOS 14.0, *)) {
@ -559,31 +296,29 @@ static DreamcastKey IosToDCKey[IOS_BTN_MAX] {
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
} else {
// Fallback on earlier versions
NSLog(@"UIDocumentPickerViewController no iOS 14 :(");
}
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
for (NSURL *url in urls) {
std::string path { url.absoluteString.UTF8String };
if (path.substr(0, 8) == "file:///")
config::ContentPath.get().push_back(path.substr(7));
if (url.fileURL)
{
[url startAccessingSecurityScopedResource];
gui_add_content_path(url.fileSystemRepresentation);
}
}
}
*/
@end
void os_SetupInput()
{
mouse = std::make_shared<IOSMouse>();
GamepadDevice::Register(mouse);
}
void pickIosFolder()
{
// [flycastViewController pickIosFolder];
}
void pickIosFile()
{
// [flycastViewController pickIosFile];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,100 +1,116 @@
{
"images" : [
{
"filename" : "40.png",
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "60.png",
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "58-1.png",
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "87.png",
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "80.png",
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "120.png",
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "120-1.png",
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
"scale" : "2x",
"size" : "60x60"
},
{
"size" : "60x60",
"filename" : "180.png",
"idiom" : "iphone",
"filename" : "Icon-180.png",
"scale" : "3x"
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "20.png",
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "40-1.png",
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "29.png",
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "58.png",
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "40-2.png",
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "80-1.png",
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "76.png",
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
"scale" : "1x",
"size" : "76x76"
},
{
"size" : "76x76",
"filename" : "152.png",
"idiom" : "ipad",
"filename" : "Icon-152.png",
"scale" : "2x"
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "167.png",
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "appstore.png",
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

View File

@ -22,7 +22,7 @@
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="hPj-CF-OCp"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="0.75086514261744963" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="38V-np-Q7Z" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>

View File

@ -1,10 +1,26 @@
/*
Copyright 2021 flyinghead
Copyright (c) 2015 reicast. All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by Lounge Katt on 8/25/15.
// Copyright (c) 2015 reicast. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FlycastViewController.h"
@interface PadViewController : UIViewController
@ -13,8 +29,6 @@
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *joyXConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *joyYConstraint;
@property (nonatomic, strong) FlycastViewController *handler;
- (void) showController:(UIView *)parentView;
- (void) hideController;
- (BOOL) isControllerVisible;

View File

@ -1,20 +1,45 @@
/*
Copyright 2021 flyinghead
Copyright (c) 2015 reicast. All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by Lounge Katt on 8/25/15.
// Copyright (c) 2015 reicast. All rights reserved.
//
#import "PadViewController.h"
#import "EmulatorView.h"
#include "ios_gamepad.h"
@interface PadViewController () {
UITouch *joyTouch;
CGPoint joyBias;
std::shared_ptr<IOSVirtualGamepad> virtualGamepad;
}
@end
@implementation PadViewController
- (void)viewDidLoad
{
[super viewDidLoad];
virtualGamepad = std::make_shared<IOSVirtualGamepad>();
GamepadDevice::Register(virtualGamepad);
}
- (void)showController:(UIView *)parentView
{
[parentView addSubview:self.view];
@ -22,6 +47,7 @@
- (void)hideController
{
[self resetTouch];
[self.view removeFromSuperview];
}
@ -31,12 +57,21 @@
- (IBAction)keycodeDown:(id)sender
{
[self.handler handleKeyDown:(enum IOSButton)((UIButton *)sender).tag];
virtualGamepad->gamepad_btn_input((u32)((UIButton *)sender).tag, true);
}
- (IBAction)keycodeUp:(id)sender
{
[self.handler handleKeyUp:(enum IOSButton)((UIButton *)sender).tag];
virtualGamepad->gamepad_btn_input((u32)((UIButton *)sender).tag, false);
}
- (void)resetTouch
{
joyTouch = nil;
self.joyXConstraint.constant = 0;
self.joyYConstraint.constant = 0;
virtualGamepad->gamepad_axis_input(IOS_AXIS_LX, 0);
virtualGamepad->gamepad_axis_input(IOS_AXIS_LY, 0);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
@ -47,8 +82,8 @@
if ([self.joystickBackground pointInside:loc withEvent:event]) {
joyTouch = touch;
joyBias = loc;
joyx[0] = 0;
joyy[0] = 0;
virtualGamepad->gamepad_axis_input(IOS_AXIS_LX, 0);
virtualGamepad->gamepad_axis_input(IOS_AXIS_LY, 0);
break;
}
}
@ -61,11 +96,7 @@
if (joyTouch != nil) {
for (UITouch *touch in touches) {
if (touch == joyTouch) {
joyTouch = nil;
self.joyXConstraint.constant = 0;
self.joyYConstraint.constant = 0;
joyx[0] = 0;
joyy[0] = 0;
[self resetTouch];
break;
}
}
@ -83,15 +114,10 @@
pos.y -= joyBias.y;
pos.x = std::max<CGFloat>(std::min<CGFloat>(25.0, pos.x), -25.0);
pos.y = std::max<CGFloat>(std::min<CGFloat>(25.0, pos.y), -25.0);
// 10% dead zone
if (pos.x * pos.x + pos.y * pos.y < 2.5 * 2.5) {
pos.x = 0;
pos.y = 0;
}
self.joyXConstraint.constant = pos.x;
self.joyYConstraint.constant = pos.y;
joyx[0] = (s8)round(pos.x * 127.0 / 25.0);
joyy[0] = (s8)round(pos.y * 127.0 / 25.0);
virtualGamepad->gamepad_axis_input(IOS_AXIS_LX, (s8)std::round(pos.x * 127.0 / 25.0));
virtualGamepad->gamepad_axis_input(IOS_AXIS_LY, (s8)std::round(pos.y * 127.0 / 25.0));
break;
}
}
@ -104,11 +130,7 @@
if (joyTouch != nil) {
for (UITouch *touch in touches) {
if (touch == joyTouch) {
joyTouch = nil;
self.joyXConstraint.constant = 0;
self.joyYConstraint.constant = 0;
joyx[0] = 0;
joyy[0] = 0;
[self resetTouch];
break;
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina5_5" orientation="landscape" appearance="light"/>
<device id="retina5_9" orientation="landscape" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
@ -19,10 +19,10 @@
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view multipleTouchEnabled="YES" alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3M7-1s-N5r">
<rect key="frame" x="0.0" y="0.0" width="736" height="414"/>
<rect key="frame" x="0.0" y="0.0" width="812" height="375"/>
<subviews>
<button opaque="NO" multipleTouchEnabled="YES" tag="16" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8Gl-Iv-u8L" userLabel="LT-Button">
<rect key="frame" x="566" y="185" width="80" height="40"/>
<button opaque="NO" multipleTouchEnabled="YES" tag="14" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8Gl-Iv-u8L" userLabel="LT-Button">
<rect key="frame" x="598" y="146" width="80" height="40"/>
<state key="normal">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
@ -32,14 +32,14 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="RTrigger" translatesAutoresizingMaskIntoConstraints="NO" id="Cjn-zx-eSs">
<rect key="frame" x="656" y="185" width="80" height="40"/>
<rect key="frame" x="688" y="146" width="80" height="40"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="rBv-DR-Gwq"/>
<constraint firstAttribute="height" constant="40" id="uA3-z1-wS7"/>
</constraints>
</imageView>
<button opaque="NO" multipleTouchEnabled="YES" tag="17" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="V8J-vG-dlF" userLabel="RT-Button">
<rect key="frame" x="656" y="185" width="80" height="40"/>
<button opaque="NO" multipleTouchEnabled="YES" tag="15" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="V8J-vG-dlF" userLabel="RT-Button">
<rect key="frame" x="688" y="146" width="80" height="40"/>
<state key="normal">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
@ -49,24 +49,24 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="JoystickButton" translatesAutoresizingMaskIntoConstraints="NO" id="ivh-8r-bw3" userLabel="JoystickThumbpad">
<rect key="frame" x="20" y="294" width="100" height="100"/>
<rect key="frame" x="64" y="255" width="100" height="100"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="0eo-Wy-by0"/>
<constraint firstAttribute="height" constant="100" id="hQl-cG-b0g"/>
</constraints>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="JoystickBackground" translatesAutoresizingMaskIntoConstraints="NO" id="OMP-L6-n0A">
<rect key="frame" x="10" y="284" width="120" height="120"/>
<rect key="frame" x="54" y="245" width="120" height="120"/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="DPad" translatesAutoresizingMaskIntoConstraints="NO" id="FLe-Gr-hny">
<rect key="frame" x="0.0" y="124" width="140" height="140"/>
<rect key="frame" x="44" y="85" width="140" height="140"/>
<constraints>
<constraint firstAttribute="width" constant="140" id="EF0-T5-Nk9"/>
<constraint firstAttribute="height" constant="140" id="fly-c3-Ajo"/>
</constraints>
</imageView>
<button opaque="NO" multipleTouchEnabled="YES" tag="7" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rp6-Nd-1qa" userLabel="L-Button">
<rect key="frame" x="0.0" y="174" width="46" height="40"/>
<rect key="frame" x="44" y="135" width="46" height="40"/>
<constraints>
<constraint firstAttribute="width" constant="46" id="MX4-af-OJg"/>
<constraint firstAttribute="height" constant="40" id="yGw-c7-7x8"/>
@ -80,7 +80,7 @@
</connections>
</button>
<button opaque="NO" multipleTouchEnabled="YES" tag="8" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CVH-hw-R8F" userLabel="R-Button">
<rect key="frame" x="94" y="174" width="46" height="40"/>
<rect key="frame" x="138" y="135" width="46" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="9OW-42-b64"/>
<constraint firstAttribute="width" constant="46" id="grQ-i7-YHe"/>
@ -94,7 +94,7 @@
</connections>
</button>
<button opaque="NO" multipleTouchEnabled="YES" tag="5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WMD-Fv-ibu" userLabel="U-Button">
<rect key="frame" x="50" y="124" width="40" height="46"/>
<rect key="frame" x="94" y="85" width="40" height="46"/>
<constraints>
<constraint firstAttribute="height" constant="46" id="2gU-xW-ddx"/>
<constraint firstAttribute="width" constant="40" id="oUv-ex-E9I"/>
@ -108,7 +108,7 @@
</connections>
</button>
<button opaque="NO" multipleTouchEnabled="YES" tag="6" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="s7g-nq-lRU" userLabel="D-Button">
<rect key="frame" x="50" y="218" width="40" height="46"/>
<rect key="frame" x="94" y="179" width="40" height="46"/>
<constraints>
<constraint firstAttribute="width" constant="40" id="3IG-k8-ER3"/>
<constraint firstAttribute="height" constant="46" id="4vu-3O-H8J"/>
@ -122,14 +122,14 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ABXYPad" translatesAutoresizingMaskIntoConstraints="NO" id="xbP-E4-fCE">
<rect key="frame" x="576" y="244" width="160" height="160"/>
<rect key="frame" x="608" y="205" width="160" height="160"/>
<constraints>
<constraint firstAttribute="width" constant="160" id="QFX-3I-lXd"/>
<constraint firstAttribute="height" constant="160" id="lVn-RY-tWm"/>
</constraints>
</imageView>
<button opaque="NO" multipleTouchEnabled="YES" tag="3" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iwO-7q-c8H" userLabel="X-Button">
<rect key="frame" x="576" y="294" width="60" height="60"/>
<rect key="frame" x="608" y="255" width="60" height="60"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="4Yf-ri-Ccb"/>
<constraint firstAttribute="width" constant="60" id="mnO-1S-Phd"/>
@ -143,7 +143,7 @@
</connections>
</button>
<button opaque="NO" multipleTouchEnabled="YES" tag="2" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7LB-OY-vh3" userLabel="B-Button">
<rect key="frame" x="676" y="294" width="60" height="60"/>
<rect key="frame" x="708" y="255" width="60" height="60"/>
<constraints>
<constraint firstAttribute="width" constant="60" id="1YC-G4-kwg"/>
<constraint firstAttribute="height" constant="60" id="LHC-wi-Yap"/>
@ -157,7 +157,7 @@
</connections>
</button>
<button opaque="NO" multipleTouchEnabled="YES" tag="4" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hGZ-v7-VA5" userLabel="Y-Button">
<rect key="frame" x="626" y="244" width="60" height="60"/>
<rect key="frame" x="658" y="205" width="60" height="60"/>
<constraints>
<constraint firstAttribute="width" constant="60" id="gv4-it-7ly"/>
<constraint firstAttribute="height" constant="60" id="hx2-N9-Lba"/>
@ -171,7 +171,7 @@
</connections>
</button>
<button opaque="NO" multipleTouchEnabled="YES" tag="1" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iKO-3z-Ias" userLabel="A-Button">
<rect key="frame" x="626" y="344" width="60" height="60"/>
<rect key="frame" x="658" y="305" width="60" height="60"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="Dgi-6d-cHy"/>
<constraint firstAttribute="width" constant="60" id="L1S-2O-QBB"/>
@ -185,14 +185,14 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Start" translatesAutoresizingMaskIntoConstraints="NO" id="9K0-cV-7zu">
<rect key="frame" x="328" y="364" width="80" height="40"/>
<rect key="frame" x="366" y="310" width="80" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="2xE-Lt-nRt"/>
<constraint firstAttribute="width" constant="80" id="BpH-iH-but"/>
</constraints>
</imageView>
<button opaque="NO" multipleTouchEnabled="YES" tag="9" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VtI-tC-PSX" userLabel="S-Button">
<rect key="frame" x="341" y="364" width="54" height="40"/>
<rect key="frame" x="379" y="310" width="54" height="40"/>
<constraints>
<constraint firstAttribute="width" constant="54" id="HjR-fP-Q1s"/>
<constraint firstAttribute="height" constant="40" id="MK6-rm-vpj"/>
@ -206,7 +206,7 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LTrigger" translatesAutoresizingMaskIntoConstraints="NO" id="H57-MD-elm">
<rect key="frame" x="566" y="185" width="80" height="40"/>
<rect key="frame" x="598" y="146" width="80" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="3TN-BZ-DkR"/>
<constraint firstAttribute="width" constant="80" id="Pj7-YU-3Ze"/>
@ -218,7 +218,7 @@
<constraints>
<constraint firstItem="s7g-nq-lRU" firstAttribute="centerX" secondItem="FLe-Gr-hny" secondAttribute="centerX" id="4bj-Mc-SN7"/>
<constraint firstItem="V8J-vG-dlF" firstAttribute="height" secondItem="Cjn-zx-eSs" secondAttribute="height" id="77t-Hb-AxC"/>
<constraint firstAttribute="trailing" secondItem="xbP-E4-fCE" secondAttribute="trailing" id="84h-PU-NtS"/>
<constraint firstItem="DJj-LJ-xnv" firstAttribute="trailing" secondItem="xbP-E4-fCE" secondAttribute="trailing" id="84h-PU-NtS"/>
<constraint firstItem="OMP-L6-n0A" firstAttribute="top" secondItem="FLe-Gr-hny" secondAttribute="bottom" constant="20" id="9o9-R0-BDC"/>
<constraint firstItem="ivh-8r-bw3" firstAttribute="centerY" secondItem="OMP-L6-n0A" secondAttribute="centerY" id="AGO-kZ-c5L"/>
<constraint firstItem="8Gl-Iv-u8L" firstAttribute="height" secondItem="H57-MD-elm" secondAttribute="height" id="BMW-0T-VdJ"/>
@ -247,10 +247,10 @@
<constraint firstItem="Cjn-zx-eSs" firstAttribute="centerX" secondItem="V8J-vG-dlF" secondAttribute="centerX" id="lmY-iZ-lW5"/>
<constraint firstAttribute="bottom" secondItem="xbP-E4-fCE" secondAttribute="bottom" constant="10" id="ner-TB-GTz"/>
<constraint firstItem="ivh-8r-bw3" firstAttribute="centerX" secondItem="OMP-L6-n0A" secondAttribute="centerX" id="o7b-NF-aWi"/>
<constraint firstAttribute="trailing" secondItem="Cjn-zx-eSs" secondAttribute="trailing" id="qTc-91-kbO"/>
<constraint firstItem="DJj-LJ-xnv" firstAttribute="trailing" secondItem="Cjn-zx-eSs" secondAttribute="trailing" id="qTc-91-kbO"/>
<constraint firstItem="H57-MD-elm" firstAttribute="centerX" secondItem="8Gl-Iv-u8L" secondAttribute="centerX" id="sNB-9U-qXY"/>
<constraint firstItem="V8J-vG-dlF" firstAttribute="width" secondItem="Cjn-zx-eSs" secondAttribute="width" id="sxe-fC-pvB"/>
<constraint firstAttribute="bottom" secondItem="9K0-cV-7zu" secondAttribute="bottom" constant="10" id="wWR-HB-aLq"/>
<constraint firstItem="DJj-LJ-xnv" firstAttribute="bottom" secondItem="9K0-cV-7zu" secondAttribute="bottom" constant="4" id="wWR-HB-aLq"/>
<constraint firstItem="WMD-Fv-ibu" firstAttribute="centerX" secondItem="FLe-Gr-hny" secondAttribute="centerX" id="xqT-1Z-AQx"/>
<constraint firstItem="rp6-Nd-1qa" firstAttribute="leading" secondItem="FLe-Gr-hny" secondAttribute="leading" id="zVb-6D-06l"/>
<constraint firstItem="hGZ-v7-VA5" firstAttribute="top" secondItem="xbP-E4-fCE" secondAttribute="top" id="zgF-U5-iWT"/>

View File

@ -60,5 +60,65 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIRequiresFullScreen</key>
<true/>
<key>NSMicrophoneUsageDescription</key>
<string>Flycast requires microphone access to emulate the Dreamcast microphone</string>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Disk image</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.flyinghead.flycast.disk-image</string>
</array>
</dict>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Zip archive</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.pkware.zip-archive</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Disk image</string>
<key>UTTypeIdentifier</key>
<string>com.flyinghead.flycast.disk-image</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>chd</string>
<string>gdi</string>
<string>cue</string>
<string>cdi</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

View File

@ -0,0 +1,538 @@
/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#import <GameController/GameController.h>
#import <CoreHaptics/CoreHaptics.h>
#include <cmath>
#include "input/gamepad_device.h"
#include "rend/gui.h"
enum IOSButton {
IOS_BTN_A = 1,
IOS_BTN_B,
IOS_BTN_X,
IOS_BTN_Y,
IOS_BTN_UP,
IOS_BTN_DOWN,
IOS_BTN_LEFT,
IOS_BTN_RIGHT,
IOS_BTN_MENU, // aka Start
IOS_BTN_OPTIONS, // aka Back (xbox), Select (dualshock)
IOS_BTN_HOME,
IOS_BTN_L1,
IOS_BTN_R1,
IOS_BTN_L2,
IOS_BTN_R2,
IOS_BTN_L3,
IOS_BTN_R3,
IOS_BTN_SHARE,
IOS_BTN_PADDLE1,
IOS_BTN_PADDLE2,
IOS_BTN_PADDLE3,
IOS_BTN_PADDLE4,
IOS_BTN_TOUCHPAD,
IOS_BTN_MAX
};
enum IOSAxis {
IOS_AXIS_L1 = 1,
IOS_AXIS_R1,
IOS_AXIS_L2,
IOS_AXIS_R2,
IOS_AXIS_LX,
IOS_AXIS_LY,
IOS_AXIS_RX,
IOS_AXIS_RY,
};
static NSString *GCInputXboxShareButton = @"Button Share";
class DefaultIOSMapping : public InputMapping
{
public:
DefaultIOSMapping()
{
name = "Default";
set_button(DC_BTN_A, IOS_BTN_A);
set_button(DC_BTN_B, IOS_BTN_B);
set_button(DC_BTN_X, IOS_BTN_X);
set_button(DC_BTN_Y, IOS_BTN_Y);
set_button(DC_DPAD_UP, IOS_BTN_UP);
set_button(DC_DPAD_DOWN, IOS_BTN_DOWN);
set_button(DC_DPAD_LEFT, IOS_BTN_LEFT);
set_button(DC_DPAD_RIGHT, IOS_BTN_RIGHT);
set_button(DC_BTN_START, IOS_BTN_MENU);
set_button(EMU_BTN_MENU, IOS_BTN_OPTIONS);
set_axis(DC_AXIS_X, IOS_AXIS_LX, false);
set_axis(DC_AXIS_Y, IOS_AXIS_LY, false);
set_axis(DC_AXIS_X2, IOS_AXIS_RX, false);
set_axis(DC_AXIS_Y2, IOS_AXIS_RY, false);
set_axis(DC_AXIS_LT, IOS_AXIS_L2, false);
set_axis(DC_AXIS_RT, IOS_AXIS_R2, false);
dirty = false;
}
};
class IOSGamepad : public GamepadDevice
{
public:
IOSGamepad(int port, GCController *controller) : GamepadDevice(port, "iOS"), gcController(controller)
{
set_maple_port(port);
if (gcController.vendorName != nullptr)
_name = [gcController.vendorName UTF8String];
else
_name = "MFi Gamepad";
//_unique_id = ?
INFO_LOG(INPUT, "iOS: Opened joystick %d: '%s'", port, _name.c_str());
loadMapping();
if (gcController.extendedGamepad != nullptr)
{
[gcController.extendedGamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_A, pressed);
}];
[gcController.extendedGamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_B, pressed);
}];
[gcController.extendedGamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_X, pressed);
}];
[gcController.extendedGamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_Y, pressed);
}];
[gcController.extendedGamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
gamepad_btn_input(IOS_BTN_RIGHT, dpad.right.isPressed);
gamepad_btn_input(IOS_BTN_LEFT, dpad.left.isPressed);
gamepad_btn_input(IOS_BTN_UP, dpad.up.isPressed);
gamepad_btn_input(IOS_BTN_DOWN, dpad.down.isPressed);
}];
if (@available(iOS 12.1, *)) {
[gcController.extendedGamepad.rightThumbstickButton setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_R3, pressed);
}];
[gcController.extendedGamepad.leftThumbstickButton setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_L3, pressed);
}];
}
if (@available(iOS 13.0, *)) {
[gcController.extendedGamepad.buttonOptions setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_OPTIONS, pressed);
}];
[gcController.extendedGamepad.buttonMenu setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_MENU, pressed);
}];
[gcController.extendedGamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_L1, pressed);
}];
[gcController.extendedGamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_R1, pressed);
}];
}
else
{
// Left shoulder for options/menu
[gcController.extendedGamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_OPTIONS, pressed);
}];
// Right shoulder for menu/start
[gcController.extendedGamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_MENU, pressed);
}];
}
if (@available(iOS 14.0, *)) {
[gcController.extendedGamepad.buttonHome setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_HOME, pressed);
}];
}
[gcController.extendedGamepad.rightTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_R2, pressed);
gamepad_axis_input(IOS_AXIS_R2, (int)std::roundf(255.f * value));
}];
[gcController.extendedGamepad.leftTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_L2, pressed);
gamepad_axis_input(IOS_AXIS_L2, (int)std::roundf(255.f * value));
}];
[gcController.extendedGamepad.leftThumbstick.xAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
gamepad_axis_input(IOS_AXIS_LX, (int)std::roundf(127.f * value));
}];
[gcController.extendedGamepad.leftThumbstick.yAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
gamepad_axis_input(IOS_AXIS_LY, (int)std::roundf(-127.f * value));
}];
[gcController.extendedGamepad.rightThumbstick.xAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
gamepad_axis_input(IOS_AXIS_RX, (int)std::roundf(127.f * value));
}];
[gcController.extendedGamepad.rightThumbstick.yAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value) {
gamepad_axis_input(IOS_AXIS_RY, (int)std::roundf(-127.f * value));
}];
}
else
{
// Legacy gamepad
[gcController.gamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_A, pressed);
}];
[gcController.gamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_B, pressed);
}];
[gcController.gamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_X, pressed);
}];
[gcController.gamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_Y, pressed);
}];
[gcController.gamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
gamepad_btn_input(IOS_BTN_RIGHT, dpad.right.isPressed);
gamepad_btn_input(IOS_BTN_LEFT, dpad.left.isPressed);
gamepad_btn_input(IOS_BTN_UP, dpad.up.isPressed);
gamepad_btn_input(IOS_BTN_DOWN, dpad.down.isPressed);
}];
[gcController.gamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed) {
if (gcController != nil && gcController.gamepad.leftShoulder.pressed)
gamepad_btn_input(IOS_BTN_MENU, true);
else
gamepad_btn_input(IOS_BTN_R2, true);
}
else {
gamepad_btn_input(IOS_BTN_R2, false);
gamepad_btn_input(IOS_BTN_MENU, false);
}
}];
[gcController.gamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
if (pressed) {
if (gcController != nil && gcController.gamepad.rightShoulder.pressed)
gamepad_btn_input(IOS_BTN_MENU, true);
else
gamepad_btn_input(IOS_BTN_L2, true);
}
else {
gamepad_btn_input(IOS_BTN_L2, false);
gamepad_btn_input(IOS_BTN_MENU, false);
}
}];
}
if (@available(iOS 14.0, *)) {
if (gcController.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
[gcController.physicalInputProfile.buttons[GCInputXboxPaddleOne] setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_PADDLE1, pressed);
}];
}
if (gcController.physicalInputProfile.buttons[GCInputXboxPaddleTwo] != nil) {
[gcController.physicalInputProfile.buttons[GCInputXboxPaddleTwo] setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_PADDLE2, pressed);
}];
}
if (gcController.physicalInputProfile.buttons[GCInputXboxPaddleThree] != nil) {
[gcController.physicalInputProfile.buttons[GCInputXboxPaddleThree] setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_PADDLE3, pressed);
}];
}
if (gcController.physicalInputProfile.buttons[GCInputXboxPaddleFour] != nil) {
[gcController.physicalInputProfile.buttons[GCInputXboxPaddleFour] setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_PADDLE4, pressed);
}];
}
if (gcController.physicalInputProfile.buttons[GCInputXboxShareButton] != nil) {
[gcController.physicalInputProfile.buttons[GCInputXboxShareButton] setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_SHARE, pressed);
}];
}
if (gcController.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
[gcController.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
gamepad_btn_input(IOS_BTN_TOUCHPAD, pressed);
}];
}
if (gcController.haptics != nullptr)
{
CHHapticEngine *hapticEngine = [gcController.haptics createEngineWithLocality:GCHapticsLocalityDefault];
NSError *error = nil;
[hapticEngine startAndReturnError:&error];
if (error != nullptr)
NSLog(@"Haptic engine error: \(error)");
else {
this->hapticEngine = hapticEngine;
_rumble_enabled = true;
}
}
}
}
~IOSGamepad() {
if (hapticEngine != nullptr)
[hapticEngine stopWithCompletionHandler:^(NSError * _Nullable error) {}];
}
void set_maple_port(int port) override
{
GamepadDevice::set_maple_port(port);
if (port <= 3 && gcController != nil)
gcController.playerIndex = (GCControllerPlayerIndex)port;
}
const char *get_button_name(u32 code) override
{
switch ((IOSButton)code) {
case IOS_BTN_A:
return "A";
case IOS_BTN_B:
return "B";
case IOS_BTN_X:
return "X";
case IOS_BTN_Y:
return "Y";
case IOS_BTN_UP:
return "DPad Up";
case IOS_BTN_DOWN:
return "DPad Down";
case IOS_BTN_LEFT:
return "DPad Left";
case IOS_BTN_RIGHT:
return "DPad Right";
case IOS_BTN_MENU:
return "Menu";
case IOS_BTN_OPTIONS:
return "Options";
case IOS_BTN_HOME:
return "Home";
case IOS_BTN_L1:
return "L Shoulder";
case IOS_BTN_R1:
return "R Shoulder";
case IOS_BTN_L2:
return "L Trigger";
case IOS_BTN_R2:
return "R Trigger";
case IOS_BTN_L3:
return "L Thumbstick";
case IOS_BTN_R3:
return "R Thumbstick";
case IOS_BTN_SHARE:
return "Share";
case IOS_BTN_PADDLE1:
return "Paddle 1";
case IOS_BTN_PADDLE2:
return "Paddle 2";
case IOS_BTN_PADDLE3:
return "Paddle 3";
case IOS_BTN_PADDLE4:
return "Paddle 4";
case IOS_BTN_TOUCHPAD:
return "Touchpad";
default:
return nullptr;
}
}
const char *get_axis_name(u32 code) override
{
switch ((IOSAxis)code) {
case IOS_AXIS_L1:
return "L Shoulder";
case IOS_AXIS_R1:
return "R Shoulder";
case IOS_AXIS_L2:
return "L Trigger";
case IOS_AXIS_R2:
return "R Trigger";
case IOS_AXIS_LX:
return "L Stick X";
case IOS_AXIS_LY:
return "L Stick Y";
case IOS_AXIS_RX:
return "R Stick X";
case IOS_AXIS_RY:
return "R Stick Y";
default:
return nullptr;
}
}
std::shared_ptr<InputMapping> getDefaultMapping() override {
return std::make_shared<DefaultIOSMapping>();
}
void rumble(float power, float inclination, u32 duration_ms) override
{
NOTICE_LOG(INPUT, "rumble %.1f inc %f duration %d", power, inclination, duration_ms);
if (@available(iOS 13.0, *)) {
CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous
parameters:@[
[[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:power]]
relativeTime:0.0
duration:duration_ms / 1000.0];
NSError *error = nil;
CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:@[event] parameters:@[] error:&error];
if (error != nil)
return;
if (hapticPlayer != nil)
[hapticPlayer stopAtTime:0 error:&error];
hapticPlayer = [hapticEngine createPlayerWithPattern:pattern error:&error];
if (error != nil)
return;
[hapticPlayer startAtTime:0 error:&error];
}
}
static void addController(GCController *controller)
{
if (controllers.count(controller) > 0)
return;
if (controller.extendedGamepad == nullptr && controller.gamepad == nullptr)
return;
int port = std::min((int)controllers.size(), 3);
controllers[controller] = std::make_shared<IOSGamepad>(port, controller);
GamepadDevice::Register(controllers[controller]);
}
static void removeController(GCController *controller)
{
auto it = controllers.find(controller);
if (it == controllers.end())
return;
GamepadDevice::Unregister(it->second);
controllers.erase(it);
}
static bool controllerConnected() {
return !controllers.empty();
}
protected:
void load_axis_min_max(u32 axis) override
{
if (axis == IOS_AXIS_L1 || axis == IOS_AXIS_R1 || axis == IOS_AXIS_L2 || axis == IOS_AXIS_R2)
{
axis_min_values[axis] = 0;
axis_ranges[axis] = 0xff;
}
else
{
axis_min_values[axis] = -127;
axis_ranges[axis] = 254;
}
}
private:
GCController * __weak gcController = nullptr;
CHHapticEngine *hapticEngine = nullptr;
id<CHHapticPatternPlayer> hapticPlayer;
static std::map<GCController *, std::shared_ptr<IOSGamepad>> controllers;
};
class IOSVirtualGamepad : public GamepadDevice
{
public:
IOSVirtualGamepad() : GamepadDevice(0, "iOS", false) {
_name = "Virtual Gamepad";
_unique_id = "ios-virtual-gamepad";
input_mapper = getDefaultMapping();
}
bool is_virtual_gamepad() override { return true; }
std::shared_ptr<InputMapping> getDefaultMapping() override {
return std::make_shared<DefaultIOSMapping>();
}
bool gamepad_btn_input(u32 code, bool pressed) override
{
if (pressed)
buttonState |= 1 << code;
else
buttonState &= ~(1 << code);
switch (code)
{
case IOS_BTN_L2:
gamepad_axis_input(IOS_AXIS_L2, pressed ? 0xff : 0);
return true;
case IOS_BTN_R2:
if (!pressed && maple_port() >= 0 && maple_port() <= 3)
kcode[maple_port()] |= DC_BTN_C | DC_BTN_D | DC_BTN_Z;
gamepad_axis_input(IOS_AXIS_R2, pressed ? 0xff : 0);
return true;
default:
if ((buttonState & ((1 << IOS_BTN_UP) | (1 << IOS_BTN_DOWN))) == ((1 << IOS_BTN_UP) | (1 << IOS_BTN_DOWN))
|| (buttonState & ((1 << IOS_BTN_LEFT) | (1 << IOS_BTN_RIGHT))) == ((1 << IOS_BTN_LEFT) | (1 << IOS_BTN_RIGHT)))
{
GamepadDevice::gamepad_btn_input(IOS_BTN_UP, false);
GamepadDevice::gamepad_btn_input(IOS_BTN_DOWN, false);
GamepadDevice::gamepad_btn_input(IOS_BTN_LEFT, false);
GamepadDevice::gamepad_btn_input(IOS_BTN_RIGHT, false);
buttonState = 0;
gui_open_settings();
return true;
}
// Arcade shortcuts
if ((buttonState & (1 << IOS_BTN_R2)) != 0 && maple_port() >= 0 && maple_port() <= 3)
{
u32& keycode = kcode[maple_port()];
switch (code) {
case IOS_BTN_A:
// RT + A -> D (coin)
keycode = pressed ? keycode & ~DC_BTN_D : keycode | DC_BTN_D;
break;
case IOS_BTN_B:
// RT + B -> C (service)
keycode = pressed ? keycode & ~DC_BTN_C : keycode | DC_BTN_C;
break;
case IOS_BTN_X:
// RT + X -> Z (test)
keycode = pressed ? keycode & ~DC_BTN_Z : keycode | DC_BTN_Z;
break;
default:
break;
}
}
return GamepadDevice::gamepad_btn_input(code, pressed);
}
}
protected:
void load_axis_min_max(u32 axis) override
{
if (axis == IOS_AXIS_L1 || axis == IOS_AXIS_R1 || axis == IOS_AXIS_L2 || axis == IOS_AXIS_R2)
{
axis_min_values[axis] = 0;
axis_ranges[axis] = 0xff;
}
else
{
axis_min_values[axis] = -127;
axis_ranges[axis] = 254;
}
}
private:
u32 buttonState = 0;
};
class IOSMouse : public SystemMouse
{
public:
IOSMouse() : SystemMouse("iOS")
{
_unique_id = "ios_mouse";
loadMapping();
}
};

View File

@ -1,7 +1,22 @@
//
// Copyright (c) 2014 Karen Tsai (angelXwind). All rights reserved.
//
/*
Copyright 2021 flyinghead
Copyright (c) 2014 Karen Tsai (angelXwind). All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#import <Foundation/Foundation.h>
#include <string>
@ -32,6 +47,9 @@ void os_CreateWindow() {
void UpdateInputState() {
}
void os_SetupInput() {
}
std::string os_Locale(){
return [[[NSLocale preferredLanguages] objectAtIndex:0] UTF8String];
}

View File

@ -1,74 +0,0 @@
#pragma once
#import <GameController/GameController.h>
#include "input/gamepad_device.h"
enum IOSButton {
IOS_BTN_A = 1,
IOS_BTN_B = 2,
IOS_BTN_X = 3,
IOS_BTN_Y = 4,
IOS_BTN_UP = 5,
IOS_BTN_DOWN = 6,
IOS_BTN_LEFT = 7,
IOS_BTN_RIGHT = 8,
IOS_BTN_MENU = 9,
IOS_BTN_OPTIONS = 10,
IOS_BTN_HOME = 11,
IOS_BTN_L1 = 12,
IOS_BTN_R1 = 13,
IOS_BTN_L3 = 14,
IOS_BTN_R3 = 15,
IOS_BTN_L2 = 16,
IOS_BTN_R2 = 17,
IOS_BTN_MAX
};
class IOSGamePad : public GamepadDevice
{
public:
IOSGamePad(int port, GCController *controller) : GamepadDevice(port, "iOS"), gcController(controller)
{
gcController.playerIndex = (GCControllerPlayerIndex)port;
if (gcController.vendorName != nullptr)
_name = [gcController.vendorName UTF8String];
//_unique_id = ?
INFO_LOG(INPUT, "iOS: Opened joystick %d: '%s' unique_id=%s", port, _name.c_str(), _unique_id.c_str());
loadMapping();
}
static void addController(GCController *controller)
{
if (controllers.count(controller) > 0)
return;
if (controller.extendedGamepad == nil && controller.gamepad == nil)
return;
int port = std::min((int)controllers.size(), 3);
controllers[controller] = std::make_shared<IOSGamePad>(port, controller);
GamepadDevice::Register(controllers[controller]);
}
static void removeController(GCController *controller)
{
auto it = controllers.find(controller);
if (it == controllers.end())
return;
GamepadDevice::Unregister(it->second);
controllers.erase(it);
}
private:
GCController *gcController = nullptr;
static std::map<GCController *, std::shared_ptr<IOSGamePad>> controllers;
};
class IOSMouse : public SystemMouse
{
public:
IOSMouse() : SystemMouse("iOS")
{
_unique_id = "ios_mouse";
loadMapping();
}
};

View File

@ -1,8 +1,24 @@
/*
Copyright (c) 2014 Lounge Katt. All rights reserved.
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by Lounge Katt on 2/6/14.
// Copyright (c) 2014 Lounge Katt. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

View File

@ -70,5 +70,59 @@
<string>Flycast requires microphone access to emulate the Dreamcast microphone</string>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Disk image</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.flyinghead.flycast.disk-image</string>
</array>
</dict>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Zip archive</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.pkware.zip-archive</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Disk image</string>
<key>UTTypeIdentifier</key>
<string>com.flyinghead.flycast.disk-image</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>chd</string>
<string>gdi</string>
<string>cue</string>
<string>cdi</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

View File

@ -405,6 +405,7 @@
AE32949826BAEF4300B2F53C /* FlycastStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE32949726BAEF4300B2F53C /* FlycastStoryboard.storyboard */; };
AE49B4D826C1BAC300FA182B /* unwind_info.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE49B4D726C1BAC300FA182B /* unwind_info.cpp */; };
AE6AA6FF26BF2E91004B9D5F /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = AE6AA6FE26BF2E91004B9D5F /* AltKit */; };
AE837B8026D164BD00BC7A2E /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE837B7F26D164BD00BC7A2E /* AVFoundation.framework */; };
AE9F17E326BC800F00B8C6D0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE9F17E226BC800F00B8C6D0 /* LaunchScreen.storyboard */; };
EBDF375A1BB96ECD001191B5 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EBDF37591BB96ECD001191B5 /* AudioToolbox.framework */; };
/* End PBXBuildFile section */
@ -1414,12 +1415,13 @@
AE32949226BAE58F00B2F53C /* FlycastViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = FlycastViewController.mm; path = emulator/FlycastViewController.mm; sourceTree = "<group>"; };
AE32949426BAE5B900B2F53C /* FlycastViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FlycastViewController.h; path = emulator/FlycastViewController.h; sourceTree = "<group>"; };
AE32949726BAEF4300B2F53C /* FlycastStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = FlycastStoryboard.storyboard; sourceTree = "<group>"; };
AE32949926BAFA3000B2F53C /* ios_mouse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ios_mouse.h; sourceTree = "<group>"; };
AE32949926BAFA3000B2F53C /* ios_gamepad.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ios_gamepad.h; sourceTree = "<group>"; };
AE49B4D726C1BAC300FA182B /* unwind_info.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unwind_info.cpp; sourceTree = "<group>"; };
AE49B51626C2E8FC00FA182B /* xbyak_mnemonic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xbyak_mnemonic.h; sourceTree = "<group>"; };
AE49B51726C2E8FC00FA182B /* xbyak_bin2hex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xbyak_bin2hex.h; sourceTree = "<group>"; };
AE49B51826C2E8FC00FA182B /* xbyak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xbyak.h; sourceTree = "<group>"; };
AE49B51926C2E8FC00FA182B /* xbyak_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xbyak_util.h; sourceTree = "<group>"; };
AE837B7F26D164BD00BC7A2E /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
AE9F17E226BC800F00B8C6D0 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
EBDF37571BB96E75001191B5 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; };
EBDF37591BB96ECD001191B5 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
@ -1432,6 +1434,7 @@
files = (
EBDF375A1BB96ECD001191B5 /* AudioToolbox.framework in Frameworks */,
87D92F4E1B7A1B5700D8FD9E /* GameController.framework in Frameworks */,
AE837B8026D164BD00BC7A2E /* AVFoundation.framework in Frameworks */,
AE6AA6FF26BF2E91004B9D5F /* AltKit in Frameworks */,
87C4AA561A4414070048DBF4 /* AssetsLibrary.framework in Frameworks */,
87078A8F18A47FE90034C7A0 /* OpenGLES.framework in Frameworks */,
@ -1466,6 +1469,7 @@
87078A8518A47FE90034C7A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
AE837B7F26D164BD00BC7A2E /* AVFoundation.framework */,
EBDF37591BB96ECD001191B5 /* AudioToolbox.framework */,
EBDF37571BB96E75001191B5 /* AudioUnit.framework */,
87D92F4D1B7A1B5700D8FD9E /* GameController.framework */,
@ -1484,7 +1488,7 @@
children = (
878B0CFB1B8BB5B400A8D1C5 /* Images.xcassets */,
87D92EA71B7839E600D8FD9E /* ios_main.mm */,
AE32949926BAFA3000B2F53C /* ios_mouse.h */,
AE32949926BAFA3000B2F53C /* ios_gamepad.h */,
AE32949726BAEF4300B2F53C /* FlycastStoryboard.storyboard */,
AE9F17E226BC800F00B8C6D0 /* LaunchScreen.storyboard */,
87078A9918A47FE90034C7A0 /* AppDelegate.h */,
@ -1781,15 +1785,15 @@
AE3258B926BAA8E100B2F53C /* maple */ = {
isa = PBXGroup;
children = (
AE3258BF26BAA8E100B2F53C /* maple_cfg.cpp */,
AE3258BE26BAA8E100B2F53C /* maple_cfg.h */,
AE3258C226BAA8E100B2F53C /* maple_devs.cpp */,
AE3258C026BAA8E100B2F53C /* maple_devs.h */,
AE3258BD26BAA8E100B2F53C /* maple_helper.cpp */,
AE3258BA26BAA8E100B2F53C /* maple_helper.h */,
AE3258BB26BAA8E100B2F53C /* maple_if.cpp */,
AE3258BC26BAA8E100B2F53C /* maple_jvs.cpp */,
AE3258BD26BAA8E100B2F53C /* maple_helper.cpp */,
AE3258BE26BAA8E100B2F53C /* maple_cfg.h */,
AE3258BF26BAA8E100B2F53C /* maple_cfg.cpp */,
AE3258C026BAA8E100B2F53C /* maple_devs.h */,
AE3258C126BAA8E100B2F53C /* maple_if.h */,
AE3258C226BAA8E100B2F53C /* maple_devs.cpp */,
AE3258BC26BAA8E100B2F53C /* maple_jvs.cpp */,
);
path = maple;
sourceTree = "<group>";
@ -2966,22 +2970,22 @@
AE32797E26BAA90900B2F53C /* oslib */ = {
isa = PBXGroup;
children = (
AE32798E26BAA90A00B2F53C /* audiobackend_alsa.cpp */,
AE32798326BAA90A00B2F53C /* audiobackend_coreaudio.cpp */,
AE32798C26BAA90A00B2F53C /* audiobackend_directsound.cpp */,
AE32798B26BAA90A00B2F53C /* audiobackend_libao.cpp */,
AE32798D26BAA90A00B2F53C /* audiobackend_null.cpp */,
AE32798826BAA90A00B2F53C /* audiobackend_oboe.cpp */,
AE32798126BAA90900B2F53C /* audiobackend_omx.cpp */,
AE32798A26BAA90A00B2F53C /* audiobackend_oss.cpp */,
AE32797F26BAA90900B2F53C /* audiobackend_pulseaudio.cpp */,
AE32798026BAA90900B2F53C /* audiobackend_sdl2.cpp */,
AE32798126BAA90900B2F53C /* audiobackend_omx.cpp */,
AE32798226BAA90A00B2F53C /* oslib.h */,
AE32798326BAA90A00B2F53C /* audiobackend_coreaudio.cpp */,
AE32798426BAA90A00B2F53C /* audiostream.cpp */,
AE32798526BAA90A00B2F53C /* host_context.h */,
AE32798626BAA90A00B2F53C /* audiostream.h */,
AE32798726BAA90A00B2F53C /* oslib.cpp */,
AE32798826BAA90A00B2F53C /* audiobackend_oboe.cpp */,
AE32798926BAA90A00B2F53C /* directory.h */,
AE32798A26BAA90A00B2F53C /* audiobackend_oss.cpp */,
AE32798B26BAA90A00B2F53C /* audiobackend_libao.cpp */,
AE32798C26BAA90A00B2F53C /* audiobackend_directsound.cpp */,
AE32798D26BAA90A00B2F53C /* audiobackend_null.cpp */,
AE32798E26BAA90A00B2F53C /* audiobackend_alsa.cpp */,
AE32798526BAA90A00B2F53C /* host_context.h */,
AE32798726BAA90A00B2F53C /* oslib.cpp */,
AE32798226BAA90A00B2F53C /* oslib.h */,
);
path = oslib;
sourceTree = "<group>";