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 |