diff --git a/CMakeLists.txt b/CMakeLists.txt index 89ec8303c5..558fa7b90c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -735,6 +735,8 @@ elseif(IOS) ios/main.mm ios/AppDelegate.mm ios/AppDelegate.h + ios/DisplayManager.h + ios/DisplayManager.mm ios/ViewController.mm ios/ViewController.h ios/iOSCoreAudio.mm @@ -1986,6 +1988,7 @@ if(TargetBin) endif() if(IOS) + list(APPEND NativeAssets ios/Settings.bundle) add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource} "ios/Launch Screen.storyboard") else() add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource}) @@ -2042,6 +2045,7 @@ if(IOS) set_target_properties(${TargetBin} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/PPSSPP-Info.plist" RESOURCE "ios/Launch Screen.storyboard" + RESOURCE "ios/Settings.bundle" XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "iPhone/iPad" XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES diff --git a/ios/AppDelegate.h b/ios/AppDelegate.h index 263e85d369..feb65efbf4 100644 --- a/ios/AppDelegate.h +++ b/ios/AppDelegate.h @@ -7,6 +7,7 @@ @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; +@property (strong, nonatomic) UIScreen *screen; @property (strong, nonatomic) ViewController *viewController; diff --git a/ios/DisplayManager.h b/ios/DisplayManager.h new file mode 100644 index 0000000000..fb46707a19 --- /dev/null +++ b/ios/DisplayManager.h @@ -0,0 +1,24 @@ +// +// DisplayManager.h +// native +// +// Created by xieyi on 2019/6/9. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DisplayManager : NSObject + +- (void)setupDisplayListener; +- (void)updateResolution:(UIScreen *)screen; + +@property (nonatomic, strong) UIScreen *mainScreen; + +@property (class, readonly, strong) DisplayManager *shared; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm new file mode 100644 index 0000000000..495353a6df --- /dev/null +++ b/ios/DisplayManager.mm @@ -0,0 +1,179 @@ +// +// DisplayManager.m +// native +// +// Created by xieyi on 2019/6/9. +// + +#import "DisplayManager.h" +#import "ViewController.h" +#import "AppDelegate.h" +#include "base/display.h" +#include "base/NativeApp.h" +#include "Core/System.h" +#import + +#define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) + +@interface DisplayManager () + +@property BOOL listenerActive; +@property (atomic, retain) NSMutableArray *extDisplays; +@property CGRect originalFrame; +@property CGRect originalBounds; +@property CGAffineTransform originalTransform; + +- (void)updateScreen:(UIScreen *)screen; + +@end + +@implementation DisplayManager + +- (instancetype)init +{ + self = [super init]; + if (self) { + [self setListenerActive:NO]; + [self setExtDisplays:[[NSMutableArray alloc] init]]; + } + return self; +} + ++ (DisplayManager *)shared { + static DisplayManager *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[DisplayManager alloc] init]; + }); + return sharedInstance; +} + +- (void)setupDisplayListener { + // Disable external display by default + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enable_external_display"] == NO) { + return; + } + if ([self listenerActive]) { + NSLog(@"setupDisplayListener already called"); + return; + } + NSLog(@"Setting up display manager"); + [self setMainScreen:[UIScreen mainScreen]]; + UIWindow *gameWindow = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window]; + [self setOriginalFrame: [gameWindow frame]]; + [self setOriginalBounds:[gameWindow bounds]]; + [self setOriginalTransform:[gameWindow transform]]; + // Display connected + [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + UIScreen *screen = (UIScreen *) notification.object; + NSLog(@"New display connected: %@", [screen debugDescription]); + [[self extDisplays] addObject:screen]; + // Do not switch to second connected display + if ([self mainScreen] != [UIScreen mainScreen]) { + return; + } + // Ignore mute switch when connected to external display + NSError *error = nil; + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]; + [self updateScreen:screen]; + }]; + // Display disconnected + [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidDisconnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + UIScreen *screen = (UIScreen *) notification.object; + NSLog(@"Display disconnected: %@", [screen debugDescription]); + if ([[self extDisplays] containsObject:screen]) { + [[self extDisplays] removeObject:screen]; + } + if ([[self extDisplays] count] > 0) { + UIScreen *newScreen = [[self extDisplays] lastObject]; + [self updateScreen:newScreen]; + } else { + NSError *error = nil; + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&error]; + [self updateScreen:[UIScreen mainScreen]]; + } + }]; + [self setListenerActive:YES]; +} + +- (void)updateScreen:(UIScreen *)screen { + [self setMainScreen:screen]; + UIWindow *gameWindow = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window]; + // Hide before moving window to external display, otherwise iPhone won't switch to it + [gameWindow setHidden:YES]; + [gameWindow setScreen:screen]; + // Set optimal resolution + // Dispatch later to prevent "no window is preset" error + dispatch_async(dispatch_get_main_queue(), ^{ + if (screen != [UIScreen mainScreen]) { + NSUInteger count = [[screen availableModes] count]; + UIScreenMode* mode = [screen availableModes][count - 1]; + [screen setCurrentMode:mode]; + mode = [screen currentMode]; + // Fix overscan + // TODO: Hacky solution. Screen is still scaled even if UIScreenOverscanCompensationNone is set. + [screen setOverscanCompensation:UIScreenOverscanCompensationNone]; + CGSize fullSize = mode.size; + UIEdgeInsets insets = [screen overscanCompensationInsets]; + fullSize.width -= insets.left + insets.right; + fullSize.height -= insets.top + insets.bottom; + [gameWindow setFrame:CGRectMake(insets.left, insets.top, fullSize.width, fullSize.height)]; + [gameWindow setBounds:CGRectMake(0, 0, fullSize.width, fullSize.height)]; + [self updateResolution:screen]; + [gameWindow setTransform:CGAffineTransformMakeScale(mode.size.width / fullSize.width, mode.size.height / fullSize.height)]; + } else { + [gameWindow setTransform:[self originalTransform]]; + [gameWindow setFrame:[self originalFrame]]; + [gameWindow setBounds:[self originalBounds]]; + [self updateResolution:screen]; + } + [gameWindow setHidden:NO]; + }); +} + +- (void)updateResolution:(UIScreen *)screen { + float scale = screen.scale; + + if ([screen respondsToSelector:@selector(nativeScale)]) { + scale = screen.nativeScale; + } + + CGSize size = screen.applicationFrame.size; + + if (size.height > size.width) { + float h = size.height; + size.height = size.width; + size.width = h; + } + + if (screen == [UIScreen mainScreen]) { + g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; + } else { + float diagonal = sqrt(size.height * size.height + size.width * size.width); + g_dpi = diagonal * scale * 0.1f; + } + g_dpi_scale_x = 240.0f / g_dpi; + g_dpi_scale_y = 240.0f / g_dpi; + g_dpi_scale_real_x = g_dpi_scale_x; + g_dpi_scale_real_y = g_dpi_scale_y; + pixel_xres = size.width * scale; + pixel_yres = size.height * scale; + + dp_xres = pixel_xres * g_dpi_scale_x; + dp_yres = pixel_yres * g_dpi_scale_y; + + pixel_in_dps_x = (float)pixel_xres / (float)dp_xres; + pixel_in_dps_y = (float)pixel_yres / (float)dp_yres; + + [[sharedViewController view] setContentScaleFactor:scale]; + + // PSP native resize + PSP_CoreParameter().pixelWidth = pixel_xres; + PSP_CoreParameter().pixelHeight = pixel_yres; + + NativeResized(); + + NSLog(@"Updated display resolution: (%d, %d) @%.1fx", pixel_xres, pixel_yres, scale); +} + +@end diff --git a/ios/Settings.bundle/Root.plist b/ios/Settings.bundle/Root.plist new file mode 100644 index 0000000000..627d9c50de --- /dev/null +++ b/ios/Settings.bundle/Root.plist @@ -0,0 +1,21 @@ + + + + + StringsTable + Root + PreferenceSpecifiers + + + Type + PSToggleSwitchSpecifier + Title + enable_external_display + Key + enable_external_display + DefaultValue + + + + + diff --git a/ios/Settings.bundle/en.lproj/Root.strings b/ios/Settings.bundle/en.lproj/Root.strings new file mode 100644 index 0000000000..cc9f6b61f9 Binary files /dev/null and b/ios/Settings.bundle/en.lproj/Root.strings differ diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 5eba171e2b..ad334f0d8d 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -5,7 +5,9 @@ // Modified by xSacha // +#import "AppDelegate.h" #import "ViewController.h" +#import "DisplayManager.h" #import "SubtleVolume.h" #import #include @@ -143,8 +145,10 @@ static GraphicsContext *graphicsContext; - (void)viewDidLoad { [super viewDidLoad]; + [[DisplayManager shared] setupDisplayListener]; - self.view.frame = [[UIScreen mainScreen] bounds]; + UIScreen* screen = [(AppDelegate*)[UIApplication sharedApplication].delegate screen]; + self.view.frame = [screen bounds]; self.view.multipleTouchEnabled = YES; self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; @@ -158,36 +162,7 @@ static GraphicsContext *graphicsContext; [EAGLContext setCurrentContext:self.context]; self.preferredFramesPerSecond = 60; - // Might be useful for a speed boot, sacrificing resolution: - // view.contentScaleFactor = 1.0; - - float scale = [UIScreen mainScreen].scale; - - if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) { - scale = [UIScreen mainScreen].nativeScale; - } - - CGSize size = [[UIApplication sharedApplication].delegate window].frame.size; - - if (size.height > size.width) { - float h = size.height; - size.height = size.width; - size.width = h; - } - - g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; - g_dpi_scale_x = 240.0f / g_dpi; - g_dpi_scale_y = 240.0f / g_dpi; - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; - pixel_xres = size.width * scale; - pixel_yres = size.height * scale; - - dp_xres = pixel_xres * g_dpi_scale_x; - dp_yres = pixel_yres * g_dpi_scale_y; - - pixel_in_dps_x = (float)pixel_xres / (float)dp_xres; - pixel_in_dps_y = (float)pixel_yres / (float)dp_yres; + [[DisplayManager shared] updateResolution:[UIScreen mainScreen]]; graphicsContext = new IOSGraphicsContext(); @@ -238,7 +213,6 @@ static GraphicsContext *graphicsContext; time_update(); } - threadStopped = true; ILOG("Emulation thread shutting down\n"); NativeShutdownGraphics(); @@ -246,6 +220,8 @@ static GraphicsContext *graphicsContext; // Also ask the main thread to stop, so it doesn't hang waiting for a new frame. ILOG("Emulation thread stopping\n"); graphicsContext->StopThread(); + + threadStopped = true; }); } diff --git a/ios/main.mm b/ios/main.mm index ce1803e023..e52f3f8bf9 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -61,7 +61,10 @@ bool System_GetPropertyBool(SystemProperty prop) { void System_SendMessage(const char *command, const char *parameter) { if (!strcmp(command, "finish")) { - exit(0); + dispatch_async(dispatch_get_main_queue(), ^{ + [sharedViewController shutdown]; + exit(0); + }); } }