forked from robterrell/TVOutManager
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTVOutManager.m
More file actions
334 lines (283 loc) · 10.6 KB
/
Copy pathTVOutManager.m
File metadata and controls
334 lines (283 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
//
// TVOutManager.m
// TVOutOS4Test
//
// Created by Rob Terrell (rob@touchcentric.com) on 8/16/10.
// Copyright 2010 TouchCentric LLC. All rights reserved.
//
// http://www.touchcentric.com/blog/
#import <QuartzCore/QuartzCore.h>
#import "TVOutManager.h"
#define kFPS 15
//
// Warning: once again, we can't use UIGetScreenImage for shipping apps (as of late July 2010)
// however, it gives a better result (shows the status bar, UIKit transitions, better fps) so
// you may want to use it for non-app-store builds (i.e. private demo, trade show build, etc.)
// Just uncomment both lines below.
//
//#define USE_UIGETSCREENIMAGE
//CGImageRef UIGetScreenImage();
//
@implementation TVOutManager
@synthesize tvSafeMode;
@synthesize implementation;
+ (TVOutManager *)sharedInstance
{
static TVOutManager *sharedInstance;
@synchronized(self)
{
if (!sharedInstance)
sharedInstance = [[TVOutManager alloc] init];
return sharedInstance;
}
}
- (id) init
{
self = [super init];
if (self) {
// can't imagine why, but just in case
[[NSNotificationCenter defaultCenter] removeObserver: self];
// catch screen-related notifications
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(screenDidConnectNotification:) name: UIScreenDidConnectNotification object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(screenDidDisconnectNotification:) name: UIScreenDidDisconnectNotification object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(screenModeDidChangeNotification:) name: UIScreenModeDidChangeNotification object: nil];
// catch orientation notifications
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(deviceOrientationDidChange:) name: UIDeviceOrientationDidChangeNotification object: nil];
}
return self;
}
-(void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
[super dealloc];
}
-(void) setTvSafeMode:(BOOL) val
{
if (tvoutWindow) {
if (tvSafeMode == YES && val == NO) {
[UIView beginAnimations:@"zoomIn" context: nil];
tvoutWindow.transform = CGAffineTransformScale(tvoutWindow.transform, 1.25, 1.25);
[UIView commitAnimations];
[tvoutWindow setNeedsDisplay];
}
else if (tvSafeMode == NO && val == YES) {
[UIView beginAnimations:@"zoomOut" context: nil];
tvoutWindow.transform = CGAffineTransformScale(tvoutWindow.transform, .8, .8);
[UIView commitAnimations];
[tvoutWindow setNeedsDisplay];
}
}
tvSafeMode = val;
}
- (void) setImplementation:(TVOutImplementation) theImplementation
{
if(tvoutWindow && theImplementation != implementation)
{
NSLog(@"TVOutManager: TVOutManager Started. Can't change implementations while running.");
}
else
{
implementation = theImplementation;
}
}
- (TVOutImplementation) implementation
{
return implementation;
}
- (void) startTVOut
{
// you need to have a main window already open when you call start
if ([[UIApplication sharedApplication] keyWindow] == nil) return;
NSArray* screens = [UIScreen screens];
if ([screens count] <= 1) {
NSLog(@"TVOutManager: startTVOut failed (no external screens detected)");
return;
}
if (tvoutWindow) {
// tvoutWindow already exists, so this is a re-connected cable, or a mode chane
[tvoutWindow release], tvoutWindow = nil;
}
if (!tvoutWindow) {
deviceWindow = [[UIApplication sharedApplication] keyWindow];
CGSize max;
max.width = 0;
max.height = 0;
UIScreenMode *maxScreenMode = nil;
UIScreen *external = [[UIScreen screens] objectAtIndex: 1];
for(int i = 0; i < [[external availableModes] count]; i++)
{
UIScreenMode *current = [[[[UIScreen screens] objectAtIndex:1] availableModes] objectAtIndex: i];
if (current.size.width > max.width)
{
max = current.size;
maxScreenMode = current;
}
}
external.currentMode = maxScreenMode;
tvoutWindow = [[UIWindow alloc] initWithFrame: CGRectMake(0,0, max.width, max.height)];
tvoutWindow.userInteractionEnabled = NO;
tvoutWindow.screen = external;
// size the mirrorView to expand to fit the external screen
CGRect mirrorRect = [[UIScreen mainScreen] bounds];
CGFloat horiz = max.width / CGRectGetWidth(mirrorRect);
CGFloat vert = max.height / CGRectGetHeight(mirrorRect);
CGFloat bigScale = horiz < vert ? horiz : vert;
mirrorRect = CGRectMake(mirrorRect.origin.x, mirrorRect.origin.y, mirrorRect.size.width * bigScale, mirrorRect.size.height * bigScale);
mirrorView = [[UIImageView alloc] initWithFrame: mirrorRect];
mirrorView.center = tvoutWindow.center;
// TV safe area -- scale the window by 20% -- for composite / component, not needed for VGA output
if (tvSafeMode) tvoutWindow.transform = CGAffineTransformScale(tvoutWindow.transform, .8, .8);
[tvoutWindow addSubview: mirrorView];
[mirrorView release];
[tvoutWindow makeKeyAndVisible];
tvoutWindow.hidden = NO;
tvoutWindow.backgroundColor = [UIColor darkGrayColor];
// orient the view properly
if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft) {
mirrorView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, M_PI * 1.5);
} else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight) {
mirrorView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, M_PI * -1.5);
}
startingTransform = mirrorView.transform;
[deviceWindow makeKeyAndVisible];
[self updateTVOut];
if (self.implementation == kTVOutImplementationBackgroundThread)
{
[NSThread detachNewThreadSelector:@selector(updateLoop) toTarget:self withObject:nil];
}
else if(self.implementation == kTVOutImplementationMainThread)
{
updateTimer = [NSTimer scheduledTimerWithTimeInterval: (1.0/kFPS) target: self selector: @selector(updateTVOut) userInfo: nil repeats: YES];
[updateTimer retain];
}
else if(self.implementation == kTVOutImplementationCADisplayLink)
{
[self performSelectorInBackground:@selector(caDisplayLinkWorker) withObject:nil];
}
else {
NSLog(@"TVOutManager: Unknown implementation. Defaulting to kTVOutImplementationMainThread");
[NSThread detachNewThreadSelector:@selector(updateLoop) toTarget:self withObject:nil];
}
}
}
- (void) caDisplayLinkWorker{
done = NO;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateTVOutForCADisplayLink)];
[displayLink setFrameInterval:(60 / kFPS)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[displayLink retain];
while (!done) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]];
}
[displayLink invalidate];
[displayLink release];
displayLink = nil;
[pool release];
}
- (void) stopTVOut;
{
done = YES;
if (updateTimer) {
[updateTimer invalidate];
[updateTimer release], updateTimer = nil;
}
if (tvoutWindow) {
[tvoutWindow release], tvoutWindow = nil;
mirrorView = nil;
}
}
- (void) updateTVOutForCADisplayLink
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self updateTVOut];
[pool release];
}
- (void) updateTVOut;
{
#ifdef USE_UIGETSCREENIMAGE
// UIGetScreenImage() is no longer allowed in shipping apps, see https://devforums.apple.com/thread/61338
// however, it's better for demos, since it includes the status bar and captures animated transitions
CGImageRef cgScreen = UIGetScreenImage();
if (cgScreen) image = [UIImage imageWithCGImage:cgScreen];
mirrorView.image = image;
CGImageRelease(cgScreen);
#else
// from http://developer.apple.com/iphone/library/qa/qa2010/qa1703.html
// bonus, this works in the simulator; sadly, it doesn't capture the status bar
//
// if you are making an OpenGL app, use UIGetScreenImage() above or switch the
// following code to match Apple's sample at http://developer.apple.com/iphone/library/qa/qa2010/qa1704.html
// note that you'll need to pass in a reference to your eaglview to get that to work.
if(UIGraphicsBeginImageContextWithOptions != NULL)
{
UIGraphicsBeginImageContextWithOptions(deviceWindow.bounds.size, NO, 0);
}
else
{
UIGraphicsBeginImageContext(deviceWindow.bounds.size);
}
CGContextRef context = UIGraphicsGetCurrentContext();
// get every window's contents (i.e. so you can see alerts, ads, etc.)
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![window respondsToSelector:@selector(screen)] || [window screen] == [UIScreen mainScreen])
{
CGContextSaveGState(context);
CGContextTranslateCTM(context, [window center].x, [window center].y);
CGContextConcatCTM(context, [window transform]);
CGContextTranslateCTM(context, -[window bounds].size.width * window.layer.anchorPoint.x, -[window bounds].size.height * window.layer.anchorPoint.y);
[[window layer] renderInContext:context];
CGContextRestoreGState(context);
}
}
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
mirrorView.image = image;
#endif
}
- (void)updateLoop;
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
done = NO;
while ( ! done )
{
[self performSelectorOnMainThread:@selector(updateTVOut) withObject:nil waitUntilDone:NO];
[NSThread sleepForTimeInterval: (1.0/kFPS) ];
}
[pool release];
}
-(void) screenDidConnectNotification: (NSNotification*) notification
{
NSLog(@"Screen connected: %@", [notification object]);
[self startTVOut];
}
-(void) screenDidDisconnectNotification: (NSNotification*) notification
{
NSLog(@"Screen disconnected: %@", [notification object]);
[self stopTVOut];
}
-(void) screenModeDidChangeNotification: (NSNotification*) notification
{
NSLog(@"Screen mode changed: %@", [notification object]);
[self startTVOut];
}
-(void) deviceOrientationDidChange: (NSNotification*) notification
{
if (mirrorView == nil || done == YES) return;
if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft) {
[UIView beginAnimations:@"turnLeft" context:nil];
mirrorView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, M_PI * 1.5);
[UIView commitAnimations];
} else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight) {
[UIView beginAnimations:@"turnRight" context:nil];
mirrorView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, M_PI * -1.5);
[UIView commitAnimations];
} else {
[UIView beginAnimations:@"turnUp" context:nil];
mirrorView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
}
@end