mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
Add a tvOS build of the app
Shares the iOS frontend, with camera, location, motion, photo/document pickers, share sheet, clipboard, microphone, battery and rotation compiled out. The view controller is a GCEventViewController, so controllers only reach us through GCController, and the Siri Remote is read as a micro gamepad and sent as arrow/enter/escape/back keys. tvOS has no persistent storage besides NSUserDefaults, so the memory stick lives in Caches for now. Configure with -DIOS_PLATFORM=TVOS. The MoltenVK submodule gets the tvOS slice from the same stock v1.4.2 release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
20d1a58806
commit
3a8a1799ee
13 files changed
+412
-32
No files matched your search
+42
-10
@@ -953,18 +953,26 @@ elseif(IOS AND NOT LIBRETRO)
|
||||
ios/iOSCoreAudio.h
|
||||
ios/IAPManager.mm
|
||||
ios/IAPManager.h
|
||||
ios/CameraHelper.mm
|
||||
ios/CameraHelper.h
|
||||
ios/LocationHelper.mm
|
||||
ios/LocationHelper.h
|
||||
Common/Battery/AppleBatteryClient.m
|
||||
)
|
||||
# tvOS has no camera, location, motion, battery or photo picker.
|
||||
if(NOT TVOS)
|
||||
list(APPEND targetExtra
|
||||
ios/CameraHelper.mm
|
||||
ios/CameraHelper.h
|
||||
ios/LocationHelper.mm
|
||||
ios/LocationHelper.h
|
||||
Common/Battery/AppleBatteryClient.m
|
||||
)
|
||||
endif()
|
||||
|
||||
list(APPEND targetExtraLibs "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework CoreMotion -framework QuartzCore -framework UIKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreText -framework CoreVideo -framework CoreMedia -framework CoreServices -framework Metal -framework IOSurface -framework Photos" )
|
||||
list(APPEND targetExtraLibs "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework OpenAL -framework AVFoundation -framework CoreText -framework CoreVideo -framework CoreMedia -framework CoreServices -framework Metal -framework IOSurface" )
|
||||
if(NOT TVOS)
|
||||
list(APPEND targetExtraLibs "-framework CoreMotion -framework CoreLocation -framework Photos")
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework")
|
||||
list(APPEND targetExtraLibs "-weak_framework GameController")
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/PhotosUI.framework")
|
||||
if(NOT TVOS AND EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/PhotosUI.framework")
|
||||
list(APPEND targetExtraLibs "-weak_framework PhotosUI")
|
||||
endif()
|
||||
list(APPEND targetExtraLibs "-framework UniformTypeIdentifiers")
|
||||
@@ -1496,7 +1504,9 @@ if(TargetBin)
|
||||
set_source_files_properties(${WEB_UPLOAD_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/assets/upload")
|
||||
endif()
|
||||
|
||||
if(IOS)
|
||||
if(TVOS)
|
||||
add_executable(${TargetBin} MACOSX_BUNDLE ${NativeAssets} ${BigFontAssets} ${UIImages} ${SHADER_FILES} ${THEME_FILE} ${DEBUGGER_FILES} ${WEB_UPLOAD_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource})
|
||||
elseif(IOS)
|
||||
set(AssetCatalog "${CMAKE_SOURCE_DIR}/ios/assets.xcassets")
|
||||
add_executable(${TargetBin} MACOSX_BUNDLE ${NativeAssets} ${BigFontAssets} ${UIImages} ${AssetCatalog} ${SHADER_FILES} ${THEME_FILE} ${DEBUGGER_FILES} ${WEB_UPLOAD_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource} "ios/Launch Screen.storyboard")
|
||||
else()
|
||||
@@ -1556,6 +1566,11 @@ if(IOS AND NOT LIBRETRO)
|
||||
if(IOS_DEBUG)
|
||||
file(INSTALL pspautotests DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/assets)
|
||||
endif()
|
||||
if(TVOS)
|
||||
set(MOLTENVK_SLICE tvos-arm64)
|
||||
else()
|
||||
set(MOLTENVK_SLICE ios-arm64)
|
||||
endif()
|
||||
set(RSRC_XIB_FILES "Launch Screen.storyboard" ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets.xcassets)
|
||||
|
||||
set_source_files_properties(${RSRC_XIB_FILES}
|
||||
@@ -1571,12 +1586,16 @@ if(IOS AND NOT LIBRETRO)
|
||||
|
||||
# Can't figure out properly using .xcframework from CMake, so just linking directly to the .a file.
|
||||
target_link_libraries(${TargetBin}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/ios/MoltenVK/MoltenVK.xcframework/ios-arm64/libMoltenVK.a"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/ios/MoltenVK/MoltenVK.xcframework/${MOLTENVK_SLICE}/libMoltenVK.a"
|
||||
)
|
||||
|
||||
set(MACOSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET})
|
||||
set(PRODUCT_NAME "PPSSPP")
|
||||
set(BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/PPSSPP-Info.plist")
|
||||
if(TVOS)
|
||||
set(BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/tvOS-Info.plist")
|
||||
else()
|
||||
set(BUNDLE_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/PPSSPP-Info.plist")
|
||||
endif()
|
||||
set(BUNDLE_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/App.entitlements")
|
||||
if(GOLD)
|
||||
if(IOS_APP_STORE)
|
||||
@@ -1638,6 +1657,19 @@ if(IOS AND NOT LIBRETRO)
|
||||
XCODE_ATTRIBUTE_SKIP_INSTALL NO
|
||||
XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
|
||||
)
|
||||
if(TVOS)
|
||||
# The launch storyboard and the asset catalog's icons are iPhone/iPad only. tvOS needs a
|
||||
# layered App Icon & Top Shelf brand asset, which we don't have yet.
|
||||
set_target_properties(${TargetBin} PROPERTIES
|
||||
RESOURCE ""
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME ""
|
||||
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ""
|
||||
XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "3"
|
||||
XCODE_ATTRIBUTE_SDKROOT appletvos
|
||||
XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS appletvos
|
||||
)
|
||||
endif()
|
||||
if(IOS_APP_STORE)
|
||||
# https://stackoverflow.com/questions/40664125/cmake-and-code-signing-in-xcode-8-for-ios-projects
|
||||
set_target_properties(${TargetBin} PROPERTIES
|
||||
|
||||
@@ -34,10 +34,6 @@
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
|
||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
@property DarwinDirectoryPanelCallback panelCallback;
|
||||
@end
|
||||
|
||||
static std::map<std::string, void*> activeAccessTokens;
|
||||
|
||||
void *DarwinFileSystemServices::__pickerDelegate = nullptr;
|
||||
@@ -48,6 +44,14 @@ void DarwinFileSystemServices::ClearDelegate() {
|
||||
__pickerDelegate = NULL;
|
||||
}
|
||||
|
||||
static void addBookmark(NSURL *url);
|
||||
|
||||
// tvOS has no document picker.
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
@property DarwinDirectoryPanelCallback panelCallback;
|
||||
@end
|
||||
|
||||
@implementation DocumentPickerDelegate
|
||||
-(instancetype)initWithCallback: (DarwinDirectoryPanelCallback)panelCallback {
|
||||
if (self = [super init]) {
|
||||
@@ -56,8 +60,6 @@ void DarwinFileSystemServices::ClearDelegate() {
|
||||
return self;
|
||||
}
|
||||
|
||||
static void addBookmark(NSURL *url);
|
||||
|
||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||
NSURL *url = urls.firstObject;
|
||||
if (!url) {
|
||||
@@ -91,6 +93,7 @@ static void addBookmark(NSURL *url);
|
||||
[sharedViewController hideKeyboard];
|
||||
}
|
||||
@end
|
||||
#endif // !PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
// Internal helper to generate and store a bookmark for a given URL.
|
||||
// Also activates the security scope for the current session.
|
||||
@@ -279,6 +282,9 @@ void DarwinFileSystemServices::presentDirectoryPanel(
|
||||
WARN_LOG(Log::System, "Mac: Received unknown responsde from modal");
|
||||
panelCallback(false, Path());
|
||||
}
|
||||
#elif PPSSPP_PLATFORM(TVOS)
|
||||
// No document picker. SYSPROP_HAS_FILE_BROWSER/FOLDER_BROWSER are false, so we shouldn't get here.
|
||||
panelCallback(false, Path());
|
||||
#elif PPSSPP_PLATFORM(IOS)
|
||||
// The window's root view controller.
|
||||
UIViewController *rootViewController = sharedViewController;
|
||||
@@ -310,7 +316,13 @@ Path DarwinFileSystemServices::appropriateMemoryStickDirectoryToUse() {
|
||||
}
|
||||
|
||||
Path DarwinFileSystemServices::defaultMemoryStickPath() {
|
||||
#if PPSSPP_PLATFORM(IOS)
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// tvOS apps get no persistent storage beyond NSUserDefaults. Everything else has to be
|
||||
// purgeable, so the memory stick lives in Caches, and the system may clear it when space runs low.
|
||||
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)
|
||||
objectAtIndex:0];
|
||||
return Path(cachesPath.UTF8String);
|
||||
#elif PPSSPP_PLATFORM(IOS)
|
||||
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)
|
||||
objectAtIndex:0];
|
||||
return Path(documentsPath.UTF8String);
|
||||
|
||||
@@ -50,6 +50,9 @@ if(NOT DEFINED IOS_PLATFORM)
|
||||
set(IOS_PLATFORM "OS")
|
||||
endif()
|
||||
set(IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform")
|
||||
if(IOS_PLATFORM STREQUAL "TVOS")
|
||||
set(TVOS ON)
|
||||
endif()
|
||||
|
||||
# Check the platform selection and setup for developer root
|
||||
if(IOS_PLATFORM STREQUAL "OS")
|
||||
|
||||
+5
-1
@@ -214,6 +214,7 @@ static std::vector<std::string> GatherGameLibrary() {
|
||||
});
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
|
||||
switch (g_Config.iScreenRotation) {
|
||||
case ROTATION_LOCKED_HORIZONTAL:
|
||||
@@ -235,6 +236,7 @@ static std::vector<std::string> GatherGameLibrary() {
|
||||
return UIInterfaceOrientationMaskAll;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -254,8 +256,10 @@ void copyDeepLinkForPath(std::string_view filePath) {
|
||||
// Format: scheme://host?query
|
||||
NSString *deepLink = [NSString stringWithFormat:@"ppsspp://open?path=%@", encodedPath];
|
||||
|
||||
// 4. Copy to the iOS System Clipboard (UIPasteboard)
|
||||
// 4. Copy to the iOS System Clipboard (UIPasteboard). tvOS has none.
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
[UIPasteboard generalPasteboard].string = deepLink;
|
||||
#endif
|
||||
|
||||
// Optional: Log it for debugging
|
||||
NSLog(@"Deep link copied: %@", deepLink);
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#import <GameController/GameController.h>
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#endif
|
||||
|
||||
#include "Common/Input/InputState.h"
|
||||
|
||||
@@ -13,6 +17,11 @@
|
||||
bool InitController(GCController *controller);
|
||||
void ShutdownController(GCController *controller);
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// Returns false unless the controller is a Siri Remote (a micro gamepad profile and no extended one).
|
||||
bool InitSiriRemote(GCController *controller);
|
||||
#endif
|
||||
|
||||
struct TouchTracker {
|
||||
public:
|
||||
void Began(NSSet *touches, UIView *view);
|
||||
@@ -25,7 +34,9 @@ private:
|
||||
UITouch *touches_[10]{};
|
||||
};
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
void ProcessAccelerometerData(CMAccelerometerData *accData);
|
||||
#endif
|
||||
InputKeyCode HIDUsageToInputKeyCode(UIKeyboardHIDUsage usage);
|
||||
|
||||
void KeyboardPressesBegan(NSSet<UIPress *> *presses, UIPressesEvent *event);
|
||||
|
||||
@@ -200,6 +200,58 @@ void ShutdownController(GCController *controller) {
|
||||
KeyMap::NotifyPadDisconnected(DEVICE_ID_PAD_0);
|
||||
}
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
static void remoteButtonPressed(BOOL pressed, InputKeyCode keyCode) {
|
||||
// As keyboard keys, which the UI already understands (arrows, enter, escape), and which have
|
||||
// default in-game mappings.
|
||||
KeyInput key;
|
||||
key.deviceId = DEVICE_ID_KEYBOARD;
|
||||
key.flags = pressed ? KeyInputFlags::DOWN : KeyInputFlags::UP;
|
||||
key.keyCode = keyCode;
|
||||
NativeKey(key);
|
||||
}
|
||||
|
||||
bool InitSiriRemote(GCController *controller) {
|
||||
GCMicroGamepad *micro = controller.microGamepad;
|
||||
if (micro == nil || controller.extendedGamepad != nil) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Swipes on the touch surface move relative to where the finger went down, one step per swipe.
|
||||
micro.reportsAbsoluteDpadValues = NO;
|
||||
micro.allowsRotation = NO;
|
||||
|
||||
micro.dpad.up.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
remoteButtonPressed(pressed, NKCODE_DPAD_UP);
|
||||
};
|
||||
micro.dpad.down.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
remoteButtonPressed(pressed, NKCODE_DPAD_DOWN);
|
||||
};
|
||||
micro.dpad.left.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
remoteButtonPressed(pressed, NKCODE_DPAD_LEFT);
|
||||
};
|
||||
micro.dpad.right.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
remoteButtonPressed(pressed, NKCODE_DPAD_RIGHT);
|
||||
};
|
||||
// Clicking the touch surface.
|
||||
micro.buttonA.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
remoteButtonPressed(pressed, NKCODE_ENTER);
|
||||
};
|
||||
// Play/pause. Escape opens the pause menu in-game.
|
||||
micro.buttonX.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
remoteButtonPressed(pressed, NKCODE_ESCAPE);
|
||||
};
|
||||
// The Menu (or Back) button.
|
||||
micro.buttonMenu.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
remoteButtonPressed(pressed, NKCODE_BACK);
|
||||
};
|
||||
INFO_LOG(Log::System, "Siri Remote set up: %s", [controller.vendorName ?: @"Remote" UTF8String]);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void TouchTracker::SendTouchEvent(CGPoint point, UIView *view, int code, int pointerId) {
|
||||
// From points to dp. The view covers the whole display.
|
||||
CGSize size = view.bounds.size;
|
||||
@@ -281,6 +333,7 @@ void TouchTracker::Cancelled(NSSet *touches, UIView *view) {
|
||||
}
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
void ProcessAccelerometerData(CMAccelerometerData *accData) {
|
||||
CMAcceleration acc = accData.acceleration;
|
||||
// INFO_LOG(SYSTEM, "%f %f %f", acc.x, acc.y, acc.z);
|
||||
@@ -288,6 +341,7 @@ void ProcessAccelerometerData(CMAccelerometerData *accData) {
|
||||
// Might need to change these for portrait or inverse landscape
|
||||
NativeAccelerometer(-acc.x, -acc.y, -acc.z);
|
||||
}
|
||||
#endif
|
||||
|
||||
void SendKeyboardChars(std::string_view str) {
|
||||
UTF8 chars(str);
|
||||
|
||||
+1
-1
Submodule ios/MoltenVK updated: 69cc97ff02...b77380b5c8.
@@ -167,7 +167,12 @@ static NSString *ExtractGameInfoScheme(NSURL *url) {
|
||||
break;
|
||||
}
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// No persistent storage on tvOS, see DarwinFileSystemServices::defaultMemoryStickPath().
|
||||
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
#else
|
||||
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
#endif
|
||||
NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"];
|
||||
// Caches the system may clear when space runs low, and the zips ShareFilesAsZip hands to other apps.
|
||||
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
|
||||
+15
-1
@@ -1,10 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <PhotosUI/PhotosUI.h>
|
||||
|
||||
#import <GameController/GameController.h>
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
// The one view controller. Renders with Vulkan (MoltenVK) to a CAMetalLayer.
|
||||
// A GCEventViewController, so controller input (the Siri Remote included) reaches us only through
|
||||
// GCController, instead of also driving UIKit's focus engine.
|
||||
@interface PPSSPPViewController : GCEventViewController
|
||||
|
||||
#else
|
||||
|
||||
#import <PhotosUI/PhotosUI.h>
|
||||
|
||||
#import "CameraHelper.h"
|
||||
#import "LocationHelper.h"
|
||||
|
||||
@@ -14,6 +26,8 @@
|
||||
CameraFrameDelegate, LocationHandlerDelegate,
|
||||
UIGestureRecognizerDelegate>
|
||||
|
||||
#endif
|
||||
|
||||
- (void)hideKeyboard;
|
||||
- (void)showKeyboard;
|
||||
- (void)shareText:(NSString *)text;
|
||||
|
||||
+98
-9
@@ -1,8 +1,10 @@
|
||||
#import "ios/CameraHelper.h"
|
||||
#import "ios/ViewController.h"
|
||||
#import "ios/Controls.h"
|
||||
#import "ios/IAPManager.h"
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
#import "ios/CameraHelper.h"
|
||||
#import <Photos/Photos.h>
|
||||
#endif
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#import <objc/runtime.h>
|
||||
#include <atomic>
|
||||
@@ -135,23 +137,31 @@ static void VulkanRenderLoop(GraphicsContext *graphicsContext, CAMetalLayer *met
|
||||
|
||||
@interface PPSSPPViewController () {
|
||||
GraphicsContext *graphicsContext;
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
CameraHelper *cameraHelper;
|
||||
LocationHelper *locationHelper;
|
||||
TouchTracker g_touchTracker;
|
||||
#endif
|
||||
}
|
||||
|
||||
@property (strong, nonatomic) NSOperationQueue *accelerometerQueue;
|
||||
@property (nonatomic) GCController *gameController;
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
@property (strong, nonatomic) NSOperationQueue *accelerometerQueue;
|
||||
@property (strong, nonatomic) CMMotionManager *motionManager;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@implementation PPSSPPViewController {
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
UIScreenEdgePanGestureRecognizer *mBackGestureRecognizer;
|
||||
#endif
|
||||
CGSize lastLayoutSize_;
|
||||
KeyboardInputView *keyboardInputView_;
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
// Strange idiom for generating unique IDs (within the process, at least).
|
||||
static const void *kPickerFilenameKey = &kPickerFilenameKey;
|
||||
static const void *kPickerRequestIdKey = &kPickerRequestIdKey;
|
||||
@@ -180,6 +190,7 @@ static int GetPickerRequestId(id picker) {
|
||||
g_requestManager.PostSystemFailure(requestId);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
@@ -190,6 +201,10 @@ static int GetPickerRequestId(id picker) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// Already the default. Should become YES at the top level menu, so the Menu button goes home.
|
||||
self.controllerUserInteractionEnabled = NO;
|
||||
#else
|
||||
// Observe orientation changes
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onOrientationChanged)
|
||||
@@ -199,6 +214,7 @@ static int GetPickerRequestId(id picker) {
|
||||
self.accelerometerQueue = [[NSOperationQueue alloc] init];
|
||||
self.accelerometerQueue.name = @"AccelerometerQueue";
|
||||
self.accelerometerQueue.maxConcurrentOperationCount = 1;
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -268,6 +284,7 @@ static int GetPickerRequestId(id picker) {
|
||||
exitRenderLoop = false;
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
- (BOOL)prefersHomeIndicatorAutoHidden {
|
||||
if (g_Config.iAppSwitchMode == (int)AppSwitchMode::DOUBLE_SWIPE_INDICATOR) {
|
||||
return NO;
|
||||
@@ -275,8 +292,10 @@ static int GetPickerRequestId(id picker) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)didBecomeActive {
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
if (self.motionManager.accelerometerAvailable) {
|
||||
self.motionManager.accelerometerUpdateInterval = 1.0 / 60.0;
|
||||
INFO_LOG(Log::G3D, "Starting accelerometer updates.");
|
||||
@@ -292,6 +311,7 @@ static int GetPickerRequestId(id picker) {
|
||||
} else {
|
||||
INFO_LOG(Log::G3D, "No accelerometer available, not starting updates.");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Spin up the render thread, which in turn starts the emu thread.
|
||||
[self updateResolutionWithView:self.view];
|
||||
@@ -301,11 +321,13 @@ static int GetPickerRequestId(id picker) {
|
||||
- (void)willResignActive {
|
||||
[self requestExitVulkanRenderLoop];
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
// Stop accelerometer updates
|
||||
if (self.motionManager.accelerometerActive) {
|
||||
INFO_LOG(Log::G3D, "Stopping accelerometer updates");
|
||||
[self.motionManager stopAccelerometerUpdates];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)appWillTerminate:(NSNotification *)notification {
|
||||
@@ -317,7 +339,9 @@ static int GetPickerRequestId(id picker) {
|
||||
INFO_LOG(Log::G3D, "viewDidAppear");
|
||||
// For hardware keyboard presses.
|
||||
[self becomeFirstResponder];
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
[self updateGesture];
|
||||
#endif
|
||||
|
||||
// This needs to be called really late during startup, unfortunately.
|
||||
#if PPSSPP_PLATFORM(IOS_APP_STORE)
|
||||
@@ -329,6 +353,7 @@ static int GetPickerRequestId(id picker) {
|
||||
#endif // IOS_APP_STORE
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
// Enables tapping for edge area.
|
||||
-(UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
|
||||
if (GetUIState() == UISTATE_INGAME) {
|
||||
@@ -343,11 +368,18 @@ static int GetPickerRequestId(id picker) {
|
||||
return UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeRight;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)controllerDidConnect:(NSNotification *)note
|
||||
{
|
||||
if (![[GCController controllers] containsObject:self.gameController]) self.gameController = nil;
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
if (InitSiriRemote((GCController *)note.object)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (self.gameController != nil) return; // already have a connected controller
|
||||
|
||||
[self setupController:(GCController *)note.object];
|
||||
@@ -358,10 +390,7 @@ static int GetPickerRequestId(id picker) {
|
||||
if (self.gameController == note.object) {
|
||||
ShutdownController(self.gameController);
|
||||
self.gameController = nil;
|
||||
|
||||
if ([[GCController controllers] count] > 0) {
|
||||
[self setupController:[[GCController controllers] firstObject]];
|
||||
}
|
||||
[self setupFirstController];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,6 +401,21 @@ static int GetPickerRequestId(id picker) {
|
||||
}
|
||||
}
|
||||
|
||||
// Picks the first gamepad. On tvOS, the Siri Remote is in the list too, and gets set up separately.
|
||||
- (void)setupFirstController {
|
||||
for (GCController *controller in [GCController controllers]) {
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
if (InitSiriRemote(controller)) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (self.gameController == nil) {
|
||||
[self setupController:controller];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
g_touchTracker.Began(touches, self.view);
|
||||
@@ -391,6 +435,24 @@ static int GetPickerRequestId(id picker) {
|
||||
{
|
||||
g_touchTracker.Cancelled(touches, self.view);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
- (void)pickPhoto:(NSString *)saveFilename requestId:(int)requestId {
|
||||
g_requestManager.PostSystemFailure(requestId);
|
||||
}
|
||||
|
||||
- (void)uiStateChanged {
|
||||
[self hideKeyboard];
|
||||
}
|
||||
|
||||
- (void)startVideo:(int)width height:(int)height {}
|
||||
- (void)stopVideo {}
|
||||
- (void)startLocation {}
|
||||
- (void)stopLocation {}
|
||||
|
||||
#else
|
||||
|
||||
- (void)pickPhoto:(NSString *)saveFilename requestId:(int)requestId {
|
||||
NSLog(@"Picking photo to save to %@ (id: %d)", saveFilename, requestId);
|
||||
@@ -500,6 +562,8 @@ static int GetPickerRequestId(id picker) {
|
||||
0 /* bearing */);
|
||||
}
|
||||
|
||||
#endif // PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
- (void)loadView {
|
||||
INFO_LOG(Log::G3D, "Creating metal view");
|
||||
// The view gets auto-resized later.
|
||||
@@ -511,6 +575,7 @@ static int GetPickerRequestId(id picker) {
|
||||
|
||||
INFO_LOG(Log::System, "viewDidLoad");
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
self.view.multipleTouchEnabled = YES;
|
||||
|
||||
cameraHelper = [[CameraHelper alloc] init];
|
||||
@@ -520,6 +585,7 @@ static int GetPickerRequestId(id picker) {
|
||||
[locationHelper setDelegate:self];
|
||||
|
||||
self.motionManager = [[CMMotionManager alloc] init];
|
||||
#endif
|
||||
|
||||
keyboardInputView_ = [[KeyboardInputView alloc] initWithFrame:CGRectZero];
|
||||
[self.view addSubview:keyboardInputView_];
|
||||
@@ -535,9 +601,7 @@ static int GetPickerRequestId(id picker) {
|
||||
|
||||
[self updateResolutionWithView:self.view];
|
||||
|
||||
if ([[GCController controllers] count] > 0) {
|
||||
[self setupController:[[GCController controllers] firstObject]];
|
||||
}
|
||||
[self setupFirstController];
|
||||
|
||||
INFO_LOG(Log::G3D, "Detected size: %dx%d", g_display.pixel_xres, g_display.pixel_yres);
|
||||
}
|
||||
@@ -550,9 +614,11 @@ static int GetPickerRequestId(id picker) {
|
||||
if (scene) {
|
||||
self.view.contentScaleFactor = scene.screen.nativeScale;
|
||||
}
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
if (@available(iOS 16.0, *)) {
|
||||
[self setNeedsUpdateOfSupportedInterfaceOrientations];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extern float g_safeInsetLeft;
|
||||
@@ -598,16 +664,23 @@ extern float g_safeInsetBottom;
|
||||
}
|
||||
|
||||
- (void)shareItems:(NSArray *)items {
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// No share sheet on tvOS. SYSPROP_SUPPORTS_SHARE_* is false there, so this shouldn't be reached.
|
||||
WARN_LOG(Log::System, "Sharing isn't available on tvOS");
|
||||
#else
|
||||
UIActivityViewController *viewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
// On iPad, the share sheet is a popover and needs an anchor.
|
||||
viewController.popoverPresentationController.sourceView = self.view;
|
||||
viewController.popoverPresentationController.sourceRect = CGRectMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds), 0, 0);
|
||||
viewController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
[self presentViewController:viewController animated:YES completion:nil];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)appSwitchModeChanged {
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
#endif
|
||||
}
|
||||
|
||||
// Main thread, like the rest of these.
|
||||
@@ -643,6 +716,13 @@ extern float g_safeInsetBottom;
|
||||
|
||||
#pragma mark - Status Bar Control
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
// No status bar or rotation on tvOS.
|
||||
- (void)immersiveModeChanged {}
|
||||
|
||||
#else
|
||||
|
||||
// The immersive mode setting is per-orientation, so we need to know which way we're facing.
|
||||
// Can't just use g_display for this, since it lags behind during rotation.
|
||||
// Note: Using viewIfLoaded, since this can get called before the view exists, and we don't want to force it into existence.
|
||||
@@ -682,6 +762,8 @@ extern float g_safeInsetBottom;
|
||||
[self setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
|
||||
#endif // PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
// Rotations and window resizes land here. UIKit lays out at the final size at the start of a
|
||||
// rotation animation, so resizing here (rather than when the rotation completes) keeps the
|
||||
// backbuffer matching the view while it animates.
|
||||
@@ -692,9 +774,11 @@ extern float g_safeInsetBottom;
|
||||
return;
|
||||
}
|
||||
lastLayoutSize_ = size;
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
if (self.view.window.windowScene) {
|
||||
[cameraHelper setInterfaceOrientation:self.view.window.windowScene.interfaceOrientation];
|
||||
}
|
||||
#endif
|
||||
INFO_LOG(Log::G3D, "Laid out at %dx%d", (int)size.width, (int)size.height);
|
||||
// Resized in place: NativeFrame picks up the new size and lets the graphics context resize.
|
||||
[self updateResolutionWithView:self.view];
|
||||
@@ -715,7 +799,12 @@ extern float g_safeInsetBottom;
|
||||
// The bounds are already corrected for orientation.
|
||||
CGSize size = view.bounds.size;
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// Seen from the couch. A 1920x1080 point screen comes out as 960x540 dp.
|
||||
float dpi = 480.0f * scale;
|
||||
#else
|
||||
float dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale;
|
||||
#endif
|
||||
PendingResolution res{
|
||||
(float)(size.width * scale), (float)(size.height * scale),
|
||||
240.0f / dpi,
|
||||
|
||||
@@ -55,6 +55,14 @@ void iOSCoreAudioUpdateSession() {
|
||||
NSError *error = nil;
|
||||
INFO_LOG(Log::Audio, "RespectSilentMode: %d MixWithOthers: %d Recording: %d", g_Config.bAudioRespectSilentMode, g_Config.bAudioMixWithOthers, g_micEnabled);
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// No microphone, and no silent switch.
|
||||
if (g_Config.bAudioMixWithOthers) {
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
|
||||
} else {
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:0 error:&error];
|
||||
}
|
||||
#else
|
||||
// Hacky hack to force iOS to re-evaluate.
|
||||
// Switching from CatogoryPlayback to CategoryPlayback with an option otherwise does nothing.
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAudioProcessing error:&error];
|
||||
@@ -82,6 +90,7 @@ void iOSCoreAudioUpdateSession() {
|
||||
}
|
||||
// Can't achieve exclusive + respect silent mode
|
||||
}
|
||||
#endif
|
||||
|
||||
if (error) {
|
||||
NSLog(@"%@", error);
|
||||
@@ -327,6 +336,10 @@ static void UpdateMicrophone() {
|
||||
|
||||
// Calls back on the main thread.
|
||||
static void WithRecordPermission(void (^then)(bool granted)) {
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// No microphone. System_AudioRecordingIsAvailable() says so, so we shouldn't get here.
|
||||
then(false);
|
||||
#else
|
||||
if (@available(iOS 17.0, *)) {
|
||||
switch ([AVAudioApplication sharedInstance].recordPermission) {
|
||||
case AVAudioApplicationRecordPermissionGranted:
|
||||
@@ -361,6 +374,7 @@ static void WithRecordPermission(void (^then)(bool granted)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void iOSCoreAudioStartRecording(int sampleRate) {
|
||||
|
||||
+74
-3
@@ -40,8 +40,9 @@
|
||||
#include "Common/Log/LogManager.h"
|
||||
#include "Core/Util/DarwinFileSystemServices.h"
|
||||
|
||||
// Compile out all the hackery in app store builds.
|
||||
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
|
||||
// Compile out all the hackery in app store builds. tvOS doesn't have posix_spawn, and there's no
|
||||
// jailbreak scene to speak of.
|
||||
#if !PPSSPP_PLATFORM(IOS_APP_STORE) && !PPSSPP_PLATFORM(TVOS)
|
||||
|
||||
struct cs_blob_index {
|
||||
uint32_t type;
|
||||
@@ -251,7 +252,11 @@ static std::string version;
|
||||
std::string System_GetProperty(SystemProperty prop) {
|
||||
switch (prop) {
|
||||
case SYSPROP_NAME:
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
return StringFromFormat("tvOS %s", version.c_str());
|
||||
#else
|
||||
return StringFromFormat("iOS %s", version.c_str());
|
||||
#endif
|
||||
case SYSPROP_LANGREGION:
|
||||
return [[[NSLocale currentLocale] objectForKey:NSLocaleIdentifier] UTF8String];
|
||||
case SYSPROP_BUILD_VERSION:
|
||||
@@ -269,20 +274,28 @@ std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
|
||||
}
|
||||
}
|
||||
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
extern "C" {
|
||||
int Apple_GetCurrentBatteryCapacity();
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t System_GetPropertyInt(SystemProperty prop) {
|
||||
switch (prop) {
|
||||
case SYSPROP_AUDIO_SAMPLE_RATE:
|
||||
return 44100;
|
||||
case SYSPROP_DEVICE_TYPE:
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
return DEVICE_TYPE_TV;
|
||||
#else
|
||||
return DEVICE_TYPE_MOBILE;
|
||||
#endif
|
||||
case SYSPROP_SYSTEMVERSION:
|
||||
return g_iosVersionMajor;
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
case SYSPROP_BATTERY_PERCENTAGE:
|
||||
return Apple_GetCurrentBatteryCapacity();
|
||||
#endif
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@@ -306,6 +319,31 @@ float System_GetPropertyFloat(SystemProperty prop) {
|
||||
}
|
||||
|
||||
bool System_GetPropertyBool(SystemProperty prop) {
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// No document picker, share sheet, clipboard, camera, location, motion, microphone, battery,
|
||||
// rotation or web browser. And no files we didn't put there ourselves, so no file browsing either.
|
||||
switch (prop) {
|
||||
case SYSPROP_HAS_FILE_BROWSER:
|
||||
case SYSPROP_HAS_FOLDER_BROWSER:
|
||||
case SYSPROP_HAS_IMAGE_BROWSER:
|
||||
case SYSPROP_HAS_ACCELEROMETER:
|
||||
case SYSPROP_HAS_KEYBOARD:
|
||||
case SYSPROP_KEYBOARD_IS_SOFT:
|
||||
case SYSPROP_SUPPORTS_SHARE_TEXT:
|
||||
case SYSPROP_SUPPORTS_SHARE_FILE:
|
||||
case SYSPROP_CAN_LAUNCH_URL:
|
||||
case SYSPROP_DISPLAY_HAS_CAMERA_CUTOUT:
|
||||
case SYSPROP_CAN_READ_BATTERY_PERCENTAGE:
|
||||
case SYSPROP_CAN_RESTRICT_ORIENTATION:
|
||||
case SYSPROP_MICROPHONE_NEEDS_POLLING:
|
||||
return false;
|
||||
case SYSPROP_HAS_BACK_BUTTON:
|
||||
// The remote's Menu/Back button.
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
switch (prop) {
|
||||
case SYSPROP_HAS_DEEP_LINKS:
|
||||
return true;
|
||||
@@ -406,6 +444,7 @@ void System_Notify(SystemNotification notification) {
|
||||
iOSCoreAudioUpdateSession();
|
||||
});
|
||||
break;
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
case SystemNotification::ROTATE_UPDATED:
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (sharedViewController) {
|
||||
@@ -418,12 +457,29 @@ void System_Notify(SystemNotification notification) {
|
||||
}
|
||||
});
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) {
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
switch (type) {
|
||||
case SystemRequestType::BROWSE_FOR_FILE:
|
||||
case SystemRequestType::BROWSE_FOR_FOLDER:
|
||||
case SystemRequestType::BROWSE_FOR_IMAGE:
|
||||
case SystemRequestType::CAMERA_COMMAND:
|
||||
case SystemRequestType::GPS_COMMAND:
|
||||
case SystemRequestType::MICROPHONE_COMMAND:
|
||||
case SystemRequestType::SHARE_TEXT:
|
||||
case SystemRequestType::SHARE_FILE:
|
||||
case SystemRequestType::COPY_TO_CLIPBOARD:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
switch (type) {
|
||||
case SystemRequestType::RESTART_APP:
|
||||
{
|
||||
@@ -583,6 +639,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
case SystemRequestType::COPY_TO_CLIPBOARD:
|
||||
{
|
||||
NSString *text = [NSString stringWithUTF8String:param1.c_str()];
|
||||
@@ -591,6 +648,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
|
||||
});
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
INFO_LOG(Log::System, "SET_KEEP_SCREEN_BRIGHT: %d", (int)param3);
|
||||
@@ -619,8 +677,12 @@ void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {
|
||||
}
|
||||
|
||||
bool System_AudioRecordingIsAvailable() {
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
return false;
|
||||
#else
|
||||
// Every iPhone and iPad has a microphone. Permission is asked for when recording starts.
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
PermissionStatus System_GetPermissionStatus(SystemPermission permission) {
|
||||
@@ -628,6 +690,7 @@ PermissionStatus System_GetPermissionStatus(SystemPermission permission) {
|
||||
}
|
||||
|
||||
void System_Vibrate(int mode) {
|
||||
#if !PPSSPP_PLATFORM(TVOS)
|
||||
// Comes from the UI code on the emu thread, and UIKit wants the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Does nothing on devices without a Taptic Engine.
|
||||
@@ -638,8 +701,16 @@ void System_Vibrate(int mode) {
|
||||
}
|
||||
[feedbackGenerator selectionChanged];
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if PPSSPP_PLATFORM(TVOS)
|
||||
// The iOS one is in CameraHelper.mm.
|
||||
std::vector<std::string> System_GetCameraDeviceList() {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
#endif
|
||||
|
||||
AudioBackend *System_CreateAudioBackend() {
|
||||
// Use legacy mechanisms.
|
||||
return nullptr;
|
||||
@@ -654,7 +725,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
g_logManager.EnableOutput(LogOutput::Stdio);
|
||||
|
||||
#if PPSSPP_PLATFORM(IOS_APP_STORE)
|
||||
#if PPSSPP_PLATFORM(IOS_APP_STORE) || PPSSPP_PLATFORM(TVOS)
|
||||
g_jitAvailable = false;
|
||||
#else
|
||||
// Hacky hacks to try to enable JIT by pretending to be a debugger.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Your local network needs to be accessed by PPSSPP for network multiplayer support.</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>PPSSPP</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>PPSSPP</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.ppsspp.ppsspp</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>PPSSPP</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>org.ppsspp.ppsspp</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ppsspp</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.games</string>
|
||||
<key>GCSupportedGameControllers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ProfileName</key>
|
||||
<string>ExtendedGamepad</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ProfileName</key>
|
||||
<string>MicroGamepad</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>SceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in new issue
Block a user