| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
| 6 #error "This file requires ARC support." | |
| 7 #endif | |
| 8 | |
| 9 #import "remoting/ios/ui/host_view_controller.h" | |
| 10 | |
| 11 #include <OpenGLES/ES2/gl.h> | |
| 12 | |
| 13 #import "remoting/ios/data_store.h" | |
| 14 | |
| 15 namespace { | |
| 16 | |
| 17 // TODO (aboone) Some of the layout is not yet set in stone, so variables have | |
| 18 // been used to position and turn items on and off. Eventually these may be | |
| 19 // stabilized and removed. | |
| 20 | |
| 21 // Scroll speed multiplier for mouse wheel | |
| 22 const static int kMouseWheelSensitivity = 20; | |
| 23 | |
| 24 // Area the navigation bar consumes when visible in pixels | |
| 25 const static int kTopMargin = 20; | |
| 26 // Area the footer consumes when visible (no footer currently exists) | |
| 27 const static int kBottomMargin = 0; | |
| 28 | |
| 29 } // namespace | |
| 30 | |
| 31 @interface HostViewController (Private) | |
| 32 - (void)setupGL; | |
| 33 - (void)tearDownGL; | |
| 34 - (void)goBack; | |
| 35 - (void)updateLabels; | |
| 36 - (BOOL)isToolbarHidden; | |
| 37 - (void)updatePanVelocityShouldCancel:(bool)canceled; | |
| 38 - (void)orientationChanged:(NSNotification*)note; | |
| 39 - (void)applySceneChange:(CGPoint)translation scaleBy:(float)ratio; | |
| 40 - (void)showToolbar:(BOOL)visible; | |
| 41 @end | |
| 42 | |
| 43 @implementation HostViewController | |
| 44 | |
| 45 @synthesize host = _host; | |
| 46 @synthesize userEmail = _userEmail; | |
| 47 @synthesize userAuthorizationToken = _userAuthorizationToken; | |
| 48 | |
| 49 // Override UIViewController | |
| 50 - (void)viewDidLoad { | |
| 51 [super viewDidLoad]; | |
| 52 | |
| 53 _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; | |
| 54 DCHECK(_context); | |
| 55 static_cast<GLKView*>(self.view).context = _context; | |
| 56 | |
| 57 [_keyEntryView setDelegate:self]; | |
| 58 | |
| 59 _clientToHostProxy = [[HostProxy alloc] init]; | |
| 60 | |
| 61 // There is a 1 pixel top border which is actually the background not being | |
| 62 // covered. There is no obvious way to remove that pixel 'border'. Set the | |
| 63 // background clear, and also reset the backgroundimage and shawdowimage to an | |
| 64 // empty image any time the view is moved. | |
| 65 _hiddenToolbar.backgroundColor = [UIColor clearColor]; | |
| 66 if ([_hiddenToolbar respondsToSelector:@selector(setBackgroundImage: | |
| 67 forToolbarPosition: | |
| 68 barMetrics:)]) { | |
| 69 [_hiddenToolbar setBackgroundImage:[UIImage new] | |
| 70 forToolbarPosition:UIToolbarPositionAny | |
| 71 barMetrics:UIBarMetricsDefault]; | |
| 72 } | |
| 73 if ([_hiddenToolbar | |
| 74 respondsToSelector:@selector(setShadowImage:forToolbarPosition:)]) { | |
| 75 [_hiddenToolbar setShadowImage:[UIImage new] | |
| 76 forToolbarPosition:UIToolbarPositionAny]; | |
| 77 } | |
| 78 | |
| 79 // 1/2 circle rotation for an icon ~ 180 degree ~ 1 radian | |
| 80 _barBtnNavigation.imageView.transform = CGAffineTransformMakeRotation(M_PI); | |
| 81 | |
| 82 _scene = [[SceneView alloc] init]; | |
| 83 [_scene setMarginsFromLeft:0 right:0 top:kTopMargin bottom:kBottomMargin]; | |
| 84 _desktop = [[DesktopTexture alloc] init]; | |
| 85 _mouse = [[CursorTexture alloc] init]; | |
| 86 | |
| 87 _glBufferLock = [[NSLock alloc] init]; | |
| 88 _glCursorLock = [[NSLock alloc] init]; | |
| 89 | |
| 90 [_scene | |
| 91 setContentSize:[Utility getOrientatedSize:self.view.bounds.size | |
| 92 shouldWidthBeLongestSide:[Utility isInLandscapeMode]]]; | |
| 93 [self showToolbar:YES]; | |
| 94 [self updateLabels]; | |
| 95 | |
| 96 [self setupGL]; | |
| 97 | |
| 98 [_singleTapRecognizer requireGestureRecognizerToFail:_twoFingerTapRecognizer]; | |
| 99 [_twoFingerTapRecognizer | |
| 100 requireGestureRecognizerToFail:_threeFingerTapRecognizer]; | |
| 101 //[_pinchRecognizer requireGestureRecognizerToFail:_twoFingerTapRecognizer]; | |
| 102 [_panRecognizer requireGestureRecognizerToFail:_singleTapRecognizer]; | |
| 103 [_threeFingerPanRecognizer | |
| 104 requireGestureRecognizerToFail:_threeFingerTapRecognizer]; | |
| 105 //[_pinchRecognizer requireGestureRecognizerToFail:_threeFingerPanRecognizer]; | |
| 106 | |
| 107 // Subscribe to changes in orientation | |
| 108 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; | |
| 109 [[NSNotificationCenter defaultCenter] | |
| 110 addObserver:self | |
| 111 selector:@selector(orientationChanged:) | |
| 112 name:UIDeviceOrientationDidChangeNotification | |
| 113 object:[UIDevice currentDevice]]; | |
| 114 } | |
| 115 | |
| 116 - (void)setupGL { | |
| 117 [EAGLContext setCurrentContext:_context]; | |
| 118 | |
| 119 _effect = [[GLKBaseEffect alloc] init]; | |
| 120 [Utility logGLErrorCode:@"setupGL begin"]; | |
| 121 | |
| 122 // Initialize each texture | |
| 123 [_desktop bindToEffect:[_effect texture2d0]]; | |
| 124 [_mouse bindToEffect:[_effect texture2d1]]; | |
| 125 [Utility logGLErrorCode:@"setupGL textureComplete"]; | |
| 126 } | |
| 127 | |
| 128 // Override UIViewController | |
| 129 - (void)viewDidUnload { | |
| 130 [super viewDidUnload]; | |
| 131 [self tearDownGL]; | |
| 132 | |
| 133 if ([EAGLContext currentContext] == _context) { | |
| 134 [EAGLContext setCurrentContext:nil]; | |
| 135 } | |
| 136 _context = nil; | |
| 137 } | |
| 138 | |
| 139 - (void)tearDownGL { | |
| 140 [EAGLContext setCurrentContext:_context]; | |
| 141 | |
| 142 // Release Textures | |
| 143 [_desktop releaseTexture]; | |
| 144 [_mouse releaseTexture]; | |
| 145 } | |
| 146 | |
| 147 // Override UIViewController | |
| 148 - (void)viewWillAppear:(BOOL)animated { | |
| 149 [super viewWillAppear:NO]; | |
| 150 [self.navigationController setNavigationBarHidden:YES animated:YES]; | |
| 151 [self updateLabels]; | |
| 152 if (![_clientToHostProxy isConnected]) { | |
| 153 [_busyIndicator startAnimating]; | |
| 154 | |
| 155 [_clientToHostProxy connectToHost:_userEmail | |
| 156 authToken:_userAuthorizationToken | |
| 157 jabberId:_host.jabberId | |
| 158 hostId:_host.hostId | |
| 159 publicKey:_host.publicKey | |
| 160 delegate:self]; | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 // Override UIViewController | |
| 165 - (void)viewWillDisappear:(BOOL)animated { | |
| 166 [super viewWillDisappear:NO]; | |
| 167 NSArray* viewControllers = self.navigationController.viewControllers; | |
| 168 if (viewControllers.count > 1 && | |
| 169 [viewControllers objectAtIndex:viewControllers.count - 2] == self) { | |
| 170 // View is disappearing because a new view controller was pushed onto the | |
| 171 // stack | |
| 172 } else if ([viewControllers indexOfObject:self] == NSNotFound) { | |
| 173 // View is disappearing because it was popped from the stack | |
| 174 [_clientToHostProxy disconnectFromHost]; | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 // "Back" goes to the root controller for now | |
| 179 - (void)goBack { | |
| 180 [self.navigationController popToRootViewControllerAnimated:YES]; | |
| 181 } | |
| 182 | |
| 183 // @protocol PinEntryViewControllerDelegate | |
| 184 // Return the PIN input by User, indicate if the User should be prompted to | |
| 185 // re-enter the pin in the future | |
| 186 - (void)connectToHostWithPin:(UIViewController*)controller | |
| 187 hostPin:(NSString*)hostPin | |
| 188 shouldPrompt:(BOOL)shouldPrompt { | |
| 189 const HostPreferences* hostPrefs = | |
| 190 [[DataStore sharedStore] getHostForId:_host.hostId]; | |
| 191 if (!hostPrefs) { | |
| 192 hostPrefs = [[DataStore sharedStore] createHost:_host.hostId]; | |
| 193 } | |
| 194 if (hostPrefs) { | |
| 195 hostPrefs.hostPin = hostPin; | |
| 196 hostPrefs.askForPin = [NSNumber numberWithBool:shouldPrompt]; | |
| 197 [[DataStore sharedStore] saveChanges]; | |
| 198 } | |
| 199 | |
| 200 [[controller presentingViewController] dismissViewControllerAnimated:NO | |
| 201 completion:nil]; | |
| 202 | |
| 203 [_clientToHostProxy authenticationResponse:hostPin createPair:!shouldPrompt]; | |
| 204 } | |
| 205 | |
| 206 // @protocol PinEntryViewControllerDelegate | |
| 207 // Returns if the user canceled while entering their PIN | |
| 208 - (void)cancelledConnectToHostWithPin:(UIViewController*)controller { | |
| 209 [[controller presentingViewController] dismissViewControllerAnimated:NO | |
| 210 completion:nil]; | |
| 211 | |
| 212 [self goBack]; | |
| 213 } | |
| 214 | |
| 215 - (void)setHostDetails:(Host*)host | |
| 216 userEmail:(NSString*)userEmail | |
| 217 authorizationToken:(NSString*)authorizationToken { | |
| 218 DCHECK(host.jabberId); | |
| 219 _host = host; | |
| 220 _userEmail = userEmail; | |
| 221 _userAuthorizationToken = authorizationToken; | |
| 222 } | |
| 223 | |
| 224 // Set various labels on the form for iPad vs iPhone, and orientation | |
| 225 - (void)updateLabels { | |
| 226 if (![Utility isPad] && ![Utility isInLandscapeMode]) { | |
| 227 [_barBtnDisconnect setTitle:@"" forState:(UIControlStateNormal)]; | |
| 228 [_barBtnCtrlAltDel setTitle:@"CtAtD" forState:UIControlStateNormal]; | |
| 229 } else { | |
| 230 [_barBtnCtrlAltDel setTitle:@"Ctrl+Alt+Del" forState:UIControlStateNormal]; | |
| 231 | |
| 232 NSString* hostStatus = _host.hostName; | |
| 233 if (![_statusMessage isEqual:@"Connected"]) { | |
| 234 hostStatus = [NSString | |
| 235 stringWithFormat:@"%@ - %@", _host.hostName, _statusMessage]; | |
| 236 } | |
| 237 [_barBtnDisconnect setTitle:hostStatus forState:UIControlStateNormal]; | |
| 238 } | |
| 239 | |
| 240 [_barBtnDisconnect sizeToFit]; | |
| 241 [_barBtnCtrlAltDel sizeToFit]; | |
| 242 } | |
| 243 | |
| 244 // Resize the view of the desktop - Zoom in/out. This can occur during a Pan. | |
| 245 - (IBAction)pinchGestureTriggered:(UIPinchGestureRecognizer*)sender { | |
| 246 if ([sender state] == UIGestureRecognizerStateChanged) { | |
| 247 [self applySceneChange:CGPointMake(0.0, 0.0) scaleBy:sender.scale]; | |
| 248 | |
| 249 sender.scale = 1.0; // reset scale so next iteration is a relative ratio | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 - (IBAction)tapGestureTriggered:(UITapGestureRecognizer*)sender { | |
| 254 if ([_scene containsTouchPoint:[sender locationInView:self.view]]) { | |
| 255 [Utility leftClickOn:_clientToHostProxy at:_scene.mousePosition]; | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 // Change position of scene. This can occur during a pinch or longpress. | |
| 260 // Or perform a Mouse Wheel Scroll | |
| 261 - (IBAction)panGestureTriggered:(UIPanGestureRecognizer*)sender { | |
| 262 CGPoint translation = [sender translationInView:self.view]; | |
| 263 | |
| 264 // If we start with 2 touches, and the pinch gesture is not in progress yet, | |
| 265 // then disable it, so mouse scrolling and zoom do not occur at the same | |
| 266 // time. | |
| 267 if ([sender numberOfTouches] == 2 && | |
| 268 [sender state] == UIGestureRecognizerStateBegan && | |
| 269 !(_pinchRecognizer.state == UIGestureRecognizerStateBegan || | |
| 270 _pinchRecognizer.state == UIGestureRecognizerStateChanged)) { | |
| 271 _pinchRecognizer.enabled = NO; | |
| 272 } | |
| 273 | |
| 274 if (!_pinchRecognizer.enabled) { | |
| 275 // Began with 2 touches, so this is a scroll event | |
| 276 translation.x *= kMouseWheelSensitivity; | |
| 277 translation.y *= kMouseWheelSensitivity; | |
| 278 [Utility mouseScroll:_clientToHostProxy | |
| 279 at:_scene.mousePosition | |
| 280 delta:webrtc::DesktopVector(translation.x, translation.y)]; | |
| 281 } else { | |
| 282 // Did not begin with 2 touches, doing a pan event | |
| 283 if ([sender state] == UIGestureRecognizerStateChanged) { | |
| 284 CGPoint translation = [sender translationInView:self.view]; | |
| 285 | |
| 286 [self applySceneChange:translation scaleBy:1.0]; | |
| 287 | |
| 288 } else if ([sender state] == UIGestureRecognizerStateEnded) { | |
| 289 // After user removes their fingers from the screen, apply an acceleration | |
| 290 // effect | |
| 291 [_scene setPanVelocity:[sender velocityInView:self.view]]; | |
| 292 } | |
| 293 } | |
| 294 | |
| 295 // Finished the event chain | |
| 296 if (!([sender state] == UIGestureRecognizerStateBegan || | |
| 297 [sender state] == UIGestureRecognizerStateChanged)) { | |
| 298 _pinchRecognizer.enabled = YES; | |
| 299 } | |
| 300 | |
| 301 // Reset translation so next iteration is relative. | |
| 302 [sender setTranslation:CGPointZero inView:self.view]; | |
| 303 } | |
| 304 | |
| 305 // Click-Drag mouse operation. This can occur during a Pan. | |
| 306 - (IBAction)longPressGestureTriggered:(UILongPressGestureRecognizer*)sender { | |
| 307 | |
| 308 if ([sender state] == UIGestureRecognizerStateBegan) { | |
| 309 [_clientToHostProxy mouseAction:_scene.mousePosition | |
| 310 wheelDelta:webrtc::DesktopVector(0, 0) | |
| 311 whichButton:1 | |
| 312 buttonDown:YES]; | |
| 313 } else if (!([sender state] == UIGestureRecognizerStateBegan || | |
| 314 [sender state] == UIGestureRecognizerStateChanged)) { | |
| 315 [_clientToHostProxy mouseAction:_scene.mousePosition | |
| 316 wheelDelta:webrtc::DesktopVector(0, 0) | |
| 317 whichButton:1 | |
| 318 buttonDown:NO]; | |
| 319 } | |
| 320 } | |
| 321 | |
| 322 - (IBAction)twoFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { | |
| 323 if ([_scene containsTouchPoint:[sender locationInView:self.view]]) { | |
| 324 [Utility rightClickOn:_clientToHostProxy at:_scene.mousePosition]; | |
| 325 } | |
| 326 } | |
| 327 | |
| 328 - (IBAction)threeFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { | |
| 329 | |
| 330 if ([_scene containsTouchPoint:[sender locationInView:self.view]]) { | |
| 331 [Utility middleClickOn:_clientToHostProxy at:_scene.mousePosition]; | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 - (IBAction)threeFingerPanGestureTriggered:(UIPanGestureRecognizer*)sender { | |
| 336 if ([sender state] == UIGestureRecognizerStateChanged) { | |
| 337 CGPoint translation = [sender translationInView:self.view]; | |
| 338 if (translation.y > 0) { | |
| 339 // Swiped down | |
| 340 [self showToolbar:YES]; | |
| 341 } else if (translation.y < 0) { | |
| 342 // Swiped up | |
| 343 [_keyEntryView becomeFirstResponder]; | |
| 344 [self updateLabels]; | |
| 345 } | |
| 346 [sender setTranslation:CGPointZero inView:self.view]; | |
| 347 } | |
| 348 } | |
| 349 | |
| 350 - (IBAction)barBtnNavigationBackPressed:(id)sender { | |
| 351 [self goBack]; | |
| 352 } | |
| 353 | |
| 354 - (IBAction)barBtnKeyboardPressed:(id)sender { | |
| 355 if ([_keyEntryView isFirstResponder]) { | |
| 356 [_keyEntryView endEditing:NO]; | |
| 357 } else { | |
| 358 [_keyEntryView becomeFirstResponder]; | |
| 359 } | |
| 360 | |
| 361 [self updateLabels]; | |
| 362 } | |
| 363 | |
| 364 - (IBAction)barBtnToolBarHidePressed:(id)sender { | |
| 365 [self showToolbar:[self isToolbarHidden]]; // Toolbar is either on | |
| 366 // screen or off screen | |
| 367 } | |
| 368 | |
| 369 - (IBAction)barBtnCtrlAltDelPressed:(id)sender { | |
| 370 [_keyEntryView ctrlAltDel]; | |
| 371 } | |
| 372 | |
| 373 // Override UIResponder | |
| 374 // When any gesture begins, remove any acceleration effects currently being | |
| 375 // applied. Example, Panning view and let it shoot off into the distance, but | |
| 376 // then I see a spot I'm interested in so I will touch to capture that locations | |
| 377 // focus. | |
| 378 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { | |
| 379 [self updatePanVelocityShouldCancel:YES]; | |
| 380 [super touchesBegan:touches withEvent:event]; | |
| 381 } | |
| 382 | |
| 383 // @protocol UIGestureRecognizerDelegate | |
| 384 // Allow panning and zooming to occur simultaneously. | |
| 385 // Allow panning and long press to occur simultaneously. | |
| 386 // Pinch requires 2 touches, and long press requires a single touch, so they are | |
| 387 // mutually exclusive regardless of if panning is the initiating gesture | |
| 388 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer | |
| 389 shouldRecognizeSimultaneouslyWithGestureRecognizer: | |
| 390 (UIGestureRecognizer*)otherGestureRecognizer { | |
| 391 if (gestureRecognizer == _pinchRecognizer || | |
| 392 (gestureRecognizer == _panRecognizer)) { | |
| 393 if (otherGestureRecognizer == _pinchRecognizer || | |
| 394 otherGestureRecognizer == _panRecognizer) { | |
| 395 return YES; | |
| 396 } | |
| 397 } | |
| 398 | |
| 399 if (gestureRecognizer == _longPressRecognizer || | |
| 400 gestureRecognizer == _panRecognizer) { | |
| 401 if (otherGestureRecognizer == _longPressRecognizer || | |
| 402 otherGestureRecognizer == _panRecognizer) { | |
| 403 return YES; | |
| 404 } | |
| 405 } | |
| 406 return NO; | |
| 407 } | |
| 408 | |
| 409 // @protocol ClientControllerDelegate | |
| 410 // Prompt the user for their PIN if pairing has not already been established | |
| 411 - (void)requestHostPin:(BOOL)pairingSupported { | |
| 412 BOOL requestPin = YES; | |
| 413 const HostPreferences* hostPrefs = | |
| 414 [[DataStore sharedStore] getHostForId:_host.hostId]; | |
| 415 if (hostPrefs) { | |
| 416 requestPin = [hostPrefs.askForPin boolValue]; | |
| 417 if (!requestPin) { | |
| 418 if (hostPrefs.hostPin == nil || hostPrefs.hostPin.length == 0) { | |
| 419 requestPin = YES; | |
| 420 } | |
| 421 } | |
| 422 } | |
| 423 if (requestPin == YES) { | |
| 424 PinEntryViewController* pinEntry = [[PinEntryViewController alloc] init]; | |
| 425 [pinEntry setDelegate:self]; | |
| 426 [pinEntry setHostName:_host.hostName]; | |
| 427 [pinEntry setShouldPrompt:YES]; | |
| 428 [pinEntry setPairingSupported:pairingSupported]; | |
| 429 | |
| 430 [self presentViewController:pinEntry animated:YES completion:nil]; | |
| 431 } else { | |
| 432 [_clientToHostProxy authenticationResponse:hostPrefs.hostPin | |
| 433 createPair:pairingSupported]; | |
| 434 } | |
| 435 } | |
| 436 | |
| 437 // @protocol ClientControllerDelegate | |
| 438 // Occurs when a connection to a HOST is established successfully | |
| 439 - (void)connected { | |
| 440 // Everything is good, nothing to do | |
| 441 } | |
| 442 | |
| 443 // @protocol ClientControllerDelegate | |
| 444 - (void)connectionStatus:(NSString*)statusMessage { | |
| 445 _statusMessage = statusMessage; | |
| 446 | |
| 447 if ([_statusMessage isEqual:@"Connection closed"]) { | |
| 448 [self goBack]; | |
| 449 } else { | |
| 450 [self updateLabels]; | |
| 451 } | |
| 452 } | |
| 453 | |
| 454 // @protocol ClientControllerDelegate | |
| 455 // Occurs when a connection to a HOST has failed | |
| 456 - (void)connectionFailed:(NSString*)errorMessage { | |
| 457 [_busyIndicator stopAnimating]; | |
| 458 NSString* errorMsg; | |
| 459 if ([_clientToHostProxy isConnected]) { | |
| 460 errorMsg = @"Lost Connection"; | |
| 461 } else { | |
| 462 errorMsg = @"Unable to connect"; | |
| 463 } | |
| 464 [Utility showAlert:errorMsg message:errorMessage]; | |
| 465 [self goBack]; | |
| 466 } | |
| 467 | |
| 468 // @protocol ClientControllerDelegate | |
| 469 // Copy the updated regions to a backing store to be consumed by the GL Context | |
| 470 // on a different thread. A region is stored in disjoint memory locations, and | |
| 471 // must be transformed to a contiguous memory buffer for a GL Texture write. | |
| 472 // /-----\ | |
| 473 // | 2-4| This buffer is 5x3 bytes large, a region exists at bytes 2 to 4 and | |
| 474 // | 7-9| bytes 7 to 9. The region is extracted to a new contiguous buffer | |
| 475 // | | of 6 bytes in length. | |
| 476 // \-----/ | |
| 477 // More than 1 region may exist in the frame from each call, in which case a new | |
| 478 // buffer is created for each region | |
| 479 - (void)applyFrame:(const webrtc::DesktopSize&)size | |
| 480 stride:(NSInteger)stride | |
| 481 data:(uint8_t*)data | |
| 482 regions:(const std::vector<webrtc::DesktopRect>&)regions { | |
| 483 [_glBufferLock lock]; // going to make changes to |_glRegions| | |
| 484 | |
| 485 if (!_scene.frameSize.equals(size)) { | |
| 486 // When this is the initial frame, the busyIndicator is still spinning. Now | |
| 487 // is a good time to stop it. | |
| 488 [_busyIndicator stopAnimating]; | |
| 489 | |
| 490 // If the |_toolbar| is still showing, hide it. | |
| 491 [self showToolbar:NO]; | |
| 492 [_scene setContentSize: | |
| 493 [Utility getOrientatedSize:self.view.bounds.size | |
| 494 shouldWidthBeLongestSide:[Utility isInLandscapeMode]]]; | |
| 495 [_scene setFrameSize:size]; | |
| 496 [_desktop setTextureSize:size]; | |
| 497 [_mouse setTextureSize:size]; | |
| 498 } | |
| 499 | |
| 500 uint32_t src_stride = stride; | |
| 501 | |
| 502 for (uint32_t i = 0; i < regions.size(); i++) { | |
| 503 scoped_ptr<GLRegion> region(new GLRegion()); | |
| 504 | |
| 505 if (region.get()) { | |
| 506 webrtc::DesktopRect rect = regions.at(i); | |
| 507 | |
| 508 webrtc::DesktopSize(rect.width(), rect.height()); | |
| 509 region->offset.reset(new webrtc::DesktopVector(rect.left(), rect.top())); | |
| 510 region->image.reset(new webrtc::BasicDesktopFrame( | |
| 511 webrtc::DesktopSize(rect.width(), rect.height()))); | |
| 512 | |
| 513 if (region->image->data()) { | |
| 514 uint32_t bytes_per_row = | |
| 515 region->image->kBytesPerPixel * region->image->size().width(); | |
| 516 | |
| 517 uint32_t offset = | |
| 518 (src_stride * region->offset->y()) + // row | |
| 519 (region->offset->x() * region->image->kBytesPerPixel); // column | |
| 520 | |
| 521 uint8_t* src_buffer = data + offset; | |
| 522 uint8_t* dst_buffer = region->image->data(); | |
| 523 | |
| 524 // row by row copy | |
| 525 for (uint32_t j = 0; j < region->image->size().height(); j++) { | |
| 526 memcpy(dst_buffer, src_buffer, bytes_per_row); | |
| 527 dst_buffer += bytes_per_row; | |
| 528 src_buffer += src_stride; | |
| 529 } | |
| 530 _glRegions.push_back(region.release()); | |
| 531 } | |
| 532 } | |
| 533 } | |
| 534 [_glBufferLock unlock]; // done making changes to |_glRegions| | |
| 535 } | |
| 536 | |
| 537 // @protocol ClientControllerDelegate | |
| 538 // Copy the delivered cursor to a backing store to be consumed by the GL Context | |
| 539 // on a different thread. Note only the most recent cursor is of importance, | |
| 540 // discard the previous cursor. | |
| 541 - (void)applyCursor:(const webrtc::DesktopSize&)size | |
| 542 hotspot:(const webrtc::DesktopVector&)hotspot | |
| 543 cursorData:(uint8_t*)data { | |
| 544 | |
| 545 [_glCursorLock lock]; // going to make changes to |_cursor| | |
| 546 | |
| 547 // MouseCursor takes ownership of DesktopFrame | |
| 548 [_mouse setCursor:new webrtc::MouseCursor(new webrtc::BasicDesktopFrame(size), | |
| 549 hotspot)]; | |
| 550 | |
| 551 if (_mouse.cursor.image().data()) { | |
| 552 memcpy(_mouse.cursor.image().data(), | |
| 553 data, | |
| 554 size.width() * size.height() * _mouse.cursor.image().kBytesPerPixel); | |
| 555 } else { | |
| 556 [_mouse setCursor:NULL]; | |
| 557 } | |
| 558 | |
| 559 [_glCursorLock unlock]; // done making changes to |_cursor| | |
| 560 } | |
| 561 | |
| 562 // @protocol GLKViewDelegate | |
| 563 // There is quite a few gotchas involved in working with this function. For | |
| 564 // sanity purposes, I've just assumed calls to the function are on a different | |
| 565 // thread which I've termed GL Context. Any variables consumed by this function | |
| 566 // should be thread safe. | |
| 567 // | |
| 568 // Clear Screen, update desktop, update cursor, define position, and finally | |
| 569 // present | |
| 570 // | |
| 571 // In general, avoid expensive work in this function to maximize frame rate. | |
| 572 - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { | |
| 573 [self updatePanVelocityShouldCancel:NO]; | |
| 574 | |
| 575 // Clear to black, to give the background color | |
| 576 glClearColor(0.0, 0.0, 0.0, 1.0); | |
| 577 glClear(GL_COLOR_BUFFER_BIT); | |
| 578 | |
| 579 [Utility logGLErrorCode:@"drawInRect bindBuffer"]; | |
| 580 | |
| 581 if (_glRegions.size() > 0 || [_desktop needDraw]) { | |
| 582 [_glBufferLock lock]; | |
| 583 | |
| 584 for (uint32_t i = 0; i < _glRegions.size(); i++) { | |
| 585 // |_glRegions[i].data| has been properly ordered by [self applyFrame] | |
| 586 [_desktop drawRegion:_glRegions[i] rect:rect]; | |
| 587 } | |
| 588 | |
| 589 _glRegions.clear(); | |
| 590 [_glBufferLock unlock]; | |
| 591 } | |
| 592 | |
| 593 if ([_mouse needDrawAtPosition:_scene.mousePosition]) { | |
| 594 [_glCursorLock lock]; | |
| 595 [_mouse drawWithMousePosition:_scene.mousePosition]; | |
| 596 [_glCursorLock unlock]; | |
| 597 } | |
| 598 | |
| 599 [_effect transform].projectionMatrix = _scene.projectionMatrix; | |
| 600 [_effect transform].modelviewMatrix = _scene.modelViewMatrix; | |
| 601 [_effect prepareToDraw]; | |
| 602 | |
| 603 [Utility logGLErrorCode:@"drawInRect prepareToDrawComplete"]; | |
| 604 | |
| 605 [_scene draw]; | |
| 606 } | |
| 607 | |
| 608 // @protocol KeyInputDelegate | |
| 609 - (void)keyboardDismissed { | |
| 610 [self updateLabels]; | |
| 611 } | |
| 612 | |
| 613 // @protocol KeyInputDelegate | |
| 614 // Send keyboard input to HOST | |
| 615 - (void)keyboardActionKeyCode:(uint32_t)keyPressed isKeyDown:(BOOL)keyDown { | |
| 616 [_clientToHostProxy keyboardAction:keyPressed keyDown:keyDown]; | |
| 617 } | |
| 618 | |
| 619 - (BOOL)isToolbarHidden { | |
| 620 return (_toolbar.frame.origin.y < 0); | |
| 621 } | |
| 622 | |
| 623 // Update the scene acceleration vector | |
| 624 - (void)updatePanVelocityShouldCancel:(bool)canceled { | |
| 625 if (canceled) { | |
| 626 [_scene setPanVelocity:CGPointMake(0, 0)]; | |
| 627 } | |
| 628 BOOL inMotion = [_scene tickPanVelocity]; | |
| 629 | |
| 630 _singleTapRecognizer.enabled = !inMotion; | |
| 631 _longPressRecognizer.enabled = !inMotion; | |
| 632 } | |
| 633 | |
| 634 - (void)applySceneChange:(CGPoint)translation scaleBy:(float)ratio { | |
| 635 [_scene panAndZoom:translation scaleBy:ratio]; | |
| 636 // Notify HOST that the mouse moved | |
| 637 [Utility moveMouse:_clientToHostProxy at:_scene.mousePosition]; | |
| 638 } | |
| 639 | |
| 640 // Callback from NSNotificationCenter when the User changes orientation | |
| 641 - (void)orientationChanged:(NSNotification*)note { | |
| 642 [_scene | |
| 643 setContentSize:[Utility getOrientatedSize:self.view.bounds.size | |
| 644 shouldWidthBeLongestSide:[Utility isInLandscapeMode]]]; | |
| 645 [self showToolbar:![self isToolbarHidden]]; | |
| 646 [self updateLabels]; | |
| 647 } | |
| 648 | |
| 649 // Animate |_toolbar| by moving it on or offscreen | |
| 650 - (void)showToolbar:(BOOL)visible { | |
| 651 CGRect frame = [_toolbar frame]; | |
| 652 | |
| 653 _toolBarYPosition.constant = -frame.size.height; | |
| 654 int topOffset = kTopMargin; | |
| 655 | |
| 656 if (visible) { | |
| 657 topOffset += frame.size.height; | |
| 658 _toolBarYPosition.constant = kTopMargin; | |
| 659 } | |
| 660 | |
| 661 _hiddenToolbarYPosition.constant = topOffset; | |
| 662 [_scene setMarginsFromLeft:0 right:0 top:topOffset bottom:kBottomMargin]; | |
| 663 | |
| 664 // hidden when |_toolbar| is |visible| | |
| 665 _hiddenToolbar.hidden = (visible == YES); | |
| 666 | |
| 667 [UIView animateWithDuration:0.5 | |
| 668 animations:^{ [self.view layoutIfNeeded]; } | |
| 669 completion:^(BOOL finished) {// Nothing to do for now | |
| 670 }]; | |
| 671 | |
| 672 // Center view if needed for any reason. | |
| 673 // Specificallly, if the top anchor is active. | |
| 674 [self applySceneChange:CGPointMake(0.0, 0.0) scaleBy:1.0]; | |
| 675 } | |
| 676 @end | |
| OLD | NEW |