1//
2//  DisplayManager.m
3//  native
4//
5//  Created by xieyi on 2019/6/9.
6//
7
8#import "DisplayManager.h"
9#import "ViewController.h"
10#import "AppDelegate.h"
11#include "Common/System/Display.h"
12#include "Common/System/System.h"
13#include "Common/System/NativeApp.h"
14#include "Core/System.h"
15#import <AVFoundation/AVFoundation.h>
16
17#define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
18
19@interface DisplayManager ()
20
21@property BOOL listenerActive;
22@property (atomic, retain) NSMutableArray<UIScreen *> *extDisplays;
23@property CGRect originalFrame;
24@property CGRect originalBounds;
25@property CGAffineTransform originalTransform;
26
27- (void)updateScreen:(UIScreen *)screen;
28
29@end
30
31@implementation DisplayManager
32
33- (instancetype)init
34{
35	self = [super init];
36	if (self) {
37		[self setListenerActive:NO];
38		[self setExtDisplays:[[NSMutableArray<UIScreen *> alloc] init]];
39	}
40	return self;
41}
42
43+ (DisplayManager *)shared {
44	static DisplayManager *sharedInstance = nil;
45	static dispatch_once_t onceToken;
46	dispatch_once(&onceToken, ^{
47		sharedInstance = [[DisplayManager alloc] init];
48	});
49	return sharedInstance;
50}
51
52- (void)setupDisplayListener {
53	// Disable external display by default
54	if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enable_external_display"] == NO) {
55		return;
56	}
57	if ([self listenerActive]) {
58		NSLog(@"setupDisplayListener already called");
59		return;
60	}
61	NSLog(@"Setting up display manager");
62	[self setMainScreen:[UIScreen mainScreen]];
63	UIWindow *gameWindow = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window];
64	[self setOriginalFrame: [gameWindow frame]];
65	[self setOriginalBounds:[gameWindow bounds]];
66	[self setOriginalTransform:[gameWindow transform]];
67	// Display connected
68	[[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) {
69		UIScreen *screen = (UIScreen *) notification.object;
70		NSLog(@"New display connected: %@", [screen debugDescription]);
71		[[self extDisplays] addObject:screen];
72		// Do not switch to second connected display
73		if ([self mainScreen] != [UIScreen mainScreen]) {
74			return;
75		}
76		// Ignore mute switch when connected to external display
77		NSError *error = nil;
78		[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
79		[self updateScreen:screen];
80	}];
81	// Display disconnected
82	[[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidDisconnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) {
83		UIScreen *screen = (UIScreen *) notification.object;
84		NSLog(@"Display disconnected: %@", [screen debugDescription]);
85		if ([[self extDisplays] containsObject:screen]) {
86			[[self extDisplays] removeObject:screen];
87		}
88		if ([[self extDisplays] count] > 0) {
89			UIScreen *newScreen = [[self extDisplays] lastObject];
90			[self updateScreen:newScreen];
91		} else {
92			NSError *error = nil;
93			[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&error];
94			[self updateScreen:[UIScreen mainScreen]];
95		}
96	}];
97	[self setListenerActive:YES];
98}
99
100- (void)updateScreen:(UIScreen *)screen {
101	[self setMainScreen:screen];
102	UIWindow *gameWindow = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window];
103	// Hide before moving window to external display, otherwise iPhone won't switch to it
104	[gameWindow setHidden:YES];
105	[gameWindow setScreen:screen];
106	// Set optimal resolution
107	// Dispatch later to prevent "no window is preset" error
108	dispatch_async(dispatch_get_main_queue(), ^{
109		if (screen != [UIScreen mainScreen]) {
110			NSUInteger count = [[screen availableModes] count];
111			UIScreenMode* mode = [screen availableModes][count - 1];
112			[screen setCurrentMode:mode];
113			mode = [screen currentMode];
114			// Fix overscan
115			// TODO: Hacky solution. Screen is still scaled even if UIScreenOverscanCompensationNone is set.
116			[screen setOverscanCompensation:UIScreenOverscanCompensationNone];
117			CGSize fullSize = mode.size;
118			UIEdgeInsets insets = [screen overscanCompensationInsets];
119			fullSize.width -= insets.left + insets.right;
120			fullSize.height -= insets.top + insets.bottom;
121			[gameWindow setFrame:CGRectMake(insets.left, insets.top, fullSize.width, fullSize.height)];
122			[gameWindow setBounds:CGRectMake(0, 0, fullSize.width, fullSize.height)];
123			[self updateResolution:screen];
124			[gameWindow setTransform:CGAffineTransformMakeScale(mode.size.width / fullSize.width, mode.size.height / fullSize.height)];
125		} else {
126			[gameWindow setTransform:[self originalTransform]];
127			[gameWindow setFrame:[self originalFrame]];
128			[gameWindow setBounds:[self originalBounds]];
129			[self updateResolution:screen];
130		}
131		[gameWindow setHidden:NO];
132	});
133}
134
135- (void)updateResolution:(UIScreen *)screen {
136	float scale = screen.scale;
137
138	if ([screen respondsToSelector:@selector(nativeScale)]) {
139		scale = screen.nativeScale;
140	}
141
142	CGSize size = screen.applicationFrame.size;
143
144	if (size.height > size.width) {
145		float h = size.height;
146		size.height = size.width;
147		size.width = h;
148	}
149
150	if (screen == [UIScreen mainScreen]) {
151		g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale;
152	} else {
153		float diagonal = sqrt(size.height * size.height + size.width * size.width);
154		g_dpi = diagonal * scale * 0.1f;
155	}
156	g_dpi_scale_x = 240.0f / g_dpi;
157	g_dpi_scale_y = 240.0f / g_dpi;
158	g_dpi_scale_real_x = g_dpi_scale_x;
159	g_dpi_scale_real_y = g_dpi_scale_y;
160	pixel_xres = size.width * scale;
161	pixel_yres = size.height * scale;
162
163	dp_xres = pixel_xres * g_dpi_scale_x;
164	dp_yres = pixel_yres * g_dpi_scale_y;
165
166	pixel_in_dps_x = (float)pixel_xres / (float)dp_xres;
167	pixel_in_dps_y = (float)pixel_yres / (float)dp_yres;
168
169	[[sharedViewController view] setContentScaleFactor:scale];
170
171	// PSP native resize
172	PSP_CoreParameter().pixelWidth = pixel_xres;
173	PSP_CoreParameter().pixelHeight = pixel_yres;
174
175	NativeResized();
176
177	NSLog(@"Updated display resolution: (%d, %d) @%.1fx", pixel_xres, pixel_yres, scale);
178}
179
180@end
181