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 |