5 // Created by Frédéric D'HAEYER on 22/12/09.
6 // Copyright 2009 Parrot. All rights reserved.
10 #ifdef ENABLE_AUTO_TVOUT
11 #import <QuartzCore/QuartzCore.h>
12 #import <CoreGraphics/CoreGraphics.h>
14 NSString * const UIApplicationDidSetupScreenMirroringNotification = @"UIApplicationDidSetupScreenMirroringNotification";
15 NSString * const UIApplicationDidDisableScreenMirroringNotification = @"UIApplicationDidDisableScreenMirroringNotification";
17 // Assuming CA loops at 60.0 fps (which is true on iPhone OS 3 : iPhone, iPad...)
18 #define CORE_ANIMATION_MAX_FRAMES_PER_SECOND (60)
20 CGImageRef UIGetScreenImage(); // Not so private API anymore
22 static CFTimeInterval startTime = 0;
23 static NSUInteger frames = 0;
25 @interface TVOut (ScreenMirroringPrivate)
27 - (void) setupMirroringForScreen:(UIScreen *)anExternalScreen;
28 - (void) disableMirroringOnCurrentScreen;
29 - (void) updateMirroredScreenOnDisplayLink;
35 static double targetFramesPerSecond = 10.0;
36 static CADisplayLink *displayLink = nil;
37 static UIScreen *mirroredScreen = nil;
38 static UIWindow *mirroredScreenWindow = nil;
39 static UIImageView *mirroredImageView = nil;
41 - (BOOL) isScreenMirroringActive
43 return (displayLink && !displayLink.paused);
46 - (UIScreen *) currentMirroringScreen
48 return mirroredScreen;
51 - (void) setupScreenMirroring
53 [self setupScreenMirroringWithFramesPerSecond:targetFramesPerSecond];
56 - (void) setupScreenMirroringWithFramesPerSecond:(double)fps
58 // Set the desired frame rate
59 targetFramesPerSecond = fps;
61 // Register for screen notifications
62 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
63 [center addObserver:self selector:@selector(screenDidConnect:) name:UIScreenDidConnectNotification object:nil];
64 [center addObserver:self selector:@selector(screenDidDisconnect:) name:UIScreenDidDisconnectNotification object:nil];
65 [center addObserver:self selector:@selector(screenModeDidChange:) name:UIScreenModeDidChangeNotification object:nil];
67 // Register for interface orientation changes (so we don't need to query on every frame refresh)
68 [center addObserver:self selector:@selector(interfaceOrientationWillChange:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
70 // Setup screen mirroring for an existing screen
71 NSArray *connectedScreens = [UIScreen screens];
72 if ([connectedScreens count] > 1) {
73 UIScreen *mainScreen = [UIScreen mainScreen];
74 for (UIScreen *aScreen in connectedScreens) {
75 if (aScreen != mainScreen) {
76 // We've found an external screen !
77 [self setupMirroringForScreen:aScreen];
84 - (void) disableScreenMirroring
86 // Unregister from screen notifications
87 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
88 [center removeObserver:self name:UIScreenDidConnectNotification object:nil];
89 [center removeObserver:self name:UIScreenDidDisconnectNotification object:nil];
90 [center removeObserver:self name:UIScreenModeDidChangeNotification object:nil];
93 [center removeObserver:self name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
96 [self disableMirroringOnCurrentScreen];
100 #pragma mark UIScreen notifications
102 - (void) screenDidConnect:(NSNotification *)aNotification
104 NSLog(@"A new screen got connected: %@", [aNotification object]);
105 [self setupMirroringForScreen:[aNotification object]];
108 - (void) screenDidDisconnect:(NSNotification *)aNotification
110 NSLog(@"A screen got disconnected: %@", [aNotification object]);
111 [self disableMirroringOnCurrentScreen];
114 - (void) screenModeDidChange:(NSNotification *)aNotification
116 UIScreen *someScreen = [aNotification object];
117 NSLog(@"The screen mode for a screen did change: %@", [someScreen currentMode]);
119 // Disable, then reenable with new config
120 [self disableMirroringOnCurrentScreen];
121 [self setupMirroringForScreen:[aNotification object]];
125 #pragma mark Interface orientation changes notification
127 - (void) updateMirroredWindowTransformForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
129 // Grab the secondary window layer
130 CALayer *mirrorLayer = mirroredScreenWindow.layer;
132 // Rotate the screenshot to match interface orientation
133 switch (interfaceOrientation) {
134 case UIInterfaceOrientationPortrait:
135 mirrorLayer.transform = CATransform3DIdentity;
137 case UIInterfaceOrientationLandscapeLeft:
138 mirrorLayer.transform = CATransform3DMakeRotation(M_PI / 2, 0.0f, 0.0f, 1.0f);
140 case UIInterfaceOrientationLandscapeRight:
141 mirrorLayer.transform = CATransform3DMakeRotation(-(M_PI / 2), 0.0f, 0.0f, 1.0f);
143 case UIInterfaceOrientationPortraitUpsideDown:
144 mirrorLayer.transform = CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f);
151 - (void) interfaceOrientationWillChange:(NSNotification *)aNotification
153 NSDictionary *userInfo = [aNotification userInfo];
154 UIInterfaceOrientation newInterfaceOrientation = (UIInterfaceOrientation) [[userInfo objectForKey:UIApplicationStatusBarOrientationUserInfoKey] unsignedIntegerValue];
155 [self updateMirroredWindowTransformForInterfaceOrientation:newInterfaceOrientation];
159 #pragma mark Screen mirroring
161 - (void) setupMirroringForScreen:(UIScreen *)anExternalScreen
164 startTime = CFAbsoluteTimeGetCurrent();
167 // Set the new screen to mirror
169 UIScreenMode *mainScreenMode = [UIScreen mainScreen].currentMode;
170 for (UIScreenMode *externalScreenMode in anExternalScreen.availableModes) {
171 if (CGSizeEqualToSize(externalScreenMode.size, mainScreenMode.size)) {
172 // Select a screen that matches the main screen
173 anExternalScreen.currentMode = externalScreenMode;
179 if (!done && [anExternalScreen.availableModes count]) {
180 anExternalScreen.currentMode = [anExternalScreen.availableModes objectAtIndex:0];
183 [mirroredScreen release];
184 mirroredScreen = [anExternalScreen retain];
186 // Setup window in external screen
187 UIWindow *newWindow = [[UIWindow alloc] initWithFrame:mirroredScreen.bounds];
188 newWindow.opaque = YES;
189 newWindow.hidden = NO;
190 newWindow.backgroundColor = [UIColor blackColor];
191 newWindow.layer.contentsGravity = kCAGravityResizeAspect;
192 [mirroredScreenWindow release];
193 mirroredScreenWindow = [newWindow retain];
194 mirroredScreenWindow.screen = mirroredScreen;
197 // Apply transform on mirrored window to match device's interface orientation
198 [self updateMirroredWindowTransformForInterfaceOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
200 // Setup periodic callbacks
201 [displayLink invalidate];
202 [displayLink release], displayLink = nil;
204 // Setup display link sync
205 displayLink = [[CADisplayLink displayLinkWithTarget:self selector:@selector(updateMirroredScreenOnDisplayLink)] retain];
206 [displayLink setFrameInterval:(targetFramesPerSecond >= CORE_ANIMATION_MAX_FRAMES_PER_SECOND) ? 1 : (CORE_ANIMATION_MAX_FRAMES_PER_SECOND / targetFramesPerSecond)];
208 // We MUST add ourselves in the commons run loop in order to mirror during UITrackingRunLoopMode.
209 // Otherwise, the display won't be updated while fingering are touching the screen.
210 // This has a major impact on performance though...
211 [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
213 // Post notification advertisting that we're setting up mirroring for the external screen
214 [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidSetupScreenMirroringNotification object:anExternalScreen];
217 - (void) disableMirroringOnCurrentScreen
219 // Post notification advertisting that we're tearing down mirroring
220 [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidDisableScreenMirroringNotification object:mirroredScreen];
222 [displayLink invalidate];
223 [displayLink release], displayLink = nil;
225 [mirroredScreen release], mirroredScreen = nil;
226 [mirroredScreenWindow release], mirroredScreenWindow = nil;
227 [mirroredImageView release], mirroredImageView = nil;
230 - (void) updateMirroredScreenOnDisplayLink
232 // Get a screenshot of the main window
233 CGImageRef mainWindowScreenshot = UIGetScreenImage();
234 if (mainWindowScreenshot) {
235 // Copy to secondary screen
236 mirroredScreenWindow.layer.contents = (id) mainWindowScreenshot;
237 // Clean up as UIGetScreenImage does NOT respect retain / release semantics
238 CFRelease(mainWindowScreenshot);
244 #endif // ENABLE_AUTO_TVOUT