FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
RDPSessionViewController.m
1/*
2 RDP Session View Controller
3
4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5
6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7 If a copy of the MPL was not distributed with this file, You can obtain one at
8 http://mozilla.org/MPL/2.0/.
9 */
10
11#import <QuartzCore/QuartzCore.h>
12#import "RDPSessionViewController.h"
13#import "RDPKeyboard.h"
14#import "Utils.h"
15#import "Toast+UIView.h"
16#import "ConnectionParams.h"
17#import "CredentialsInputController.h"
18#import "VerifyCertificateController.h"
19#import "BlockAlertView.h"
20
21#define TOOLBAR_HEIGHT 30
22
23#define AUTOSCROLLDISTANCE 20
24#define AUTOSCROLLTIMEOUT 0.05
25
26@interface RDPSessionViewController (Private)
27- (void)showSessionToolbar:(BOOL)show;
28- (UIToolbar *)keyboardToolbar;
29- (void)initGestureRecognizers;
30- (void)suspendSession;
31- (NSDictionary *)eventDescriptorForMouseEvent:(int)event position:(CGPoint)position;
32- (void)handleMouseMoveForPosition:(CGPoint)position;
33@end
34
35@implementation RDPSessionViewController
36
37#pragma mark class methods
38
39- (id)initWithNibName:(NSString *)nibNameOrNil
40 bundle:(NSBundle *)nibBundleOrNil
41 session:(RDPSession *)session
42{
43 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
44 if (self)
45 {
46 _session = [session retain];
47 [_session setDelegate:self];
48 _session_initilized = NO;
49
50 _mouse_move_events_skipped = 0;
51 _mouse_move_event_timer = nil;
52
53 _advanced_keyboard_view = nil;
54 _advanced_keyboard_visible = NO;
55 _requesting_advanced_keyboard = NO;
56 _keyboard_last_height = 0;
57
58 _session_toolbar_visible = NO;
59
60 _toggle_mouse_button = NO;
61
62 _autoscroll_with_touchpointer =
63 [[NSUserDefaults standardUserDefaults] boolForKey:@"ui.auto_scroll_touchpointer"];
64 _is_autoscrolling = NO;
65
66 [UIView setAnimationDelegate:self];
67 [UIView setAnimationDidStopSelector:@selector(animationStopped:finished:context:)];
68 }
69
70 return self;
71}
72
73// Implement loadView to create a view hierarchy programmatically, without using a nib.
74- (void)loadView
75{
76 // load default view and set background color and resizing mask
77 [super loadView];
78
79 // init keyboard handling vars
80 _keyboard_visible = NO;
81
82 // init keyboard toolbar
83 _keyboard_toolbar = [[self keyboardToolbar] retain];
84 [_dummy_textfield setInputAccessoryView:_keyboard_toolbar];
85
86 // init gesture recognizers
87 [self initGestureRecognizers];
88
89 // hide session toolbar
90 [_session_toolbar
91 setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[self view] bounds].size.width, TOOLBAR_HEIGHT)];
92}
93
94// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
95- (void)viewDidLoad
96{
97 [super viewDidLoad];
98}
99
100- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
101{
102 return YES;
103}
104
105- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
106{
107 if (![_touchpointer_view isHidden])
108 [_touchpointer_view ensurePointerIsVisible];
109}
110
111- (void)didReceiveMemoryWarning
112{
113 // Releases the view if it doesn't have a superview.
114 [super didReceiveMemoryWarning];
115
116 // Release any cached data, images, etc. that aren't in use.
117}
118
119- (void)viewDidUnload
120{
121 [super viewDidUnload];
122 // Release any retained subviews of the main view.
123 // e.g. self.myOutlet = nil;
124}
125
126- (void)viewWillAppear:(BOOL)animated
127{
128 [super viewWillAppear:animated];
129
130 // hide navigation bar and (if enabled) the status bar
131 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ui.hide_status_bar"])
132 {
133 if (animated == YES)
134 [[UIApplication sharedApplication] setStatusBarHidden:YES
135 withAnimation:UIStatusBarAnimationSlide];
136 else
137 [[UIApplication sharedApplication] setStatusBarHidden:YES
138 withAnimation:UIStatusBarAnimationNone];
139 }
140 [[self navigationController] setNavigationBarHidden:YES animated:animated];
141
142 // if session is suspended - notify that we got a new bitmap context
143 if ([_session isSuspended])
144 [self sessionBitmapContextWillChange:_session];
145
146 // init keyboard
147 [[RDPKeyboard getSharedRDPKeyboard] initWithSession:_session delegate:self];
148}
149
150- (void)viewDidAppear:(BOOL)animated
151{
152 [super viewDidAppear:animated];
153
154 if (!_session_initilized)
155 {
156 if ([_session isSuspended])
157 {
158 [_session resume];
159 [self sessionBitmapContextDidChange:_session];
160 [_session_view setNeedsDisplay];
161 }
162 else
163 [_session connect];
164
165 _session_initilized = YES;
166 }
167}
168
169- (void)viewWillDisappear:(BOOL)animated
170{
171 [super viewWillDisappear:animated];
172
173 // show navigation and status bar again
174 if (animated == YES)
175 [[UIApplication sharedApplication] setStatusBarHidden:NO
176 withAnimation:UIStatusBarAnimationSlide];
177 else
178 [[UIApplication sharedApplication] setStatusBarHidden:NO
179 withAnimation:UIStatusBarAnimationNone];
180 [[self navigationController] setNavigationBarHidden:NO animated:animated];
181
182 // reset all modifier keys on rdp keyboard
183 [[RDPKeyboard getSharedRDPKeyboard] reset];
184
185 // hide toolbar and keyboard
186 [self showSessionToolbar:NO];
187 [_dummy_textfield resignFirstResponder];
188}
189
190- (void)dealloc
191{
192 // remove any observers
193 [[NSNotificationCenter defaultCenter] removeObserver:self];
194
195 // the session lives on longer so set the delegate to nil
196 [_session setDelegate:nil];
197
198 [_advanced_keyboard_view release];
199 [_keyboard_toolbar release];
200 [_session release];
201 [super dealloc];
202}
203
204#pragma mark -
205#pragma mark ScrollView delegate methods
206
207- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
208{
209 return _session_view;
210}
211
212- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView
213 withView:(UIView *)view
214 atScale:(float)scale
215{
216 NSLog(@"New zoom scale: %f", scale);
217 [_session_view setNeedsDisplay];
218}
219
220#pragma mark -
221#pragma mark TextField delegate methods
222- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
223{
224 _keyboard_visible = YES;
225 _advanced_keyboard_visible = NO;
226 return YES;
227}
228
229- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
230{
231 _keyboard_visible = NO;
232 _advanced_keyboard_visible = NO;
233 return YES;
234}
235
236- (BOOL)textField:(UITextField *)textField
237 shouldChangeCharactersInRange:(NSRange)range
238 replacementString:(NSString *)string
239{
240 if ([string length] > 0)
241 {
242 for (int i = 0; i < [string length]; i++)
243 {
244 unichar curChar = [string characterAtIndex:i];
245
246 // special handling for return/enter key
247 if (curChar == '\n')
248 [[RDPKeyboard getSharedRDPKeyboard] sendEnterKeyStroke];
249 else
250 [[RDPKeyboard getSharedRDPKeyboard] sendUnicode:curChar];
251 }
252 }
253 else
254 {
255 [[RDPKeyboard getSharedRDPKeyboard] sendBackspaceKeyStroke];
256 }
257
258 return NO;
259}
260
261#pragma mark -
262#pragma mark AdvancedKeyboardDelegate functions
263- (void)advancedKeyPressedVKey:(int)key
264{
265 [[RDPKeyboard getSharedRDPKeyboard] sendVirtualKeyCode:key];
266}
267
268- (void)advancedKeyPressedUnicode:(int)key
269{
270 [[RDPKeyboard getSharedRDPKeyboard] sendUnicode:key];
271}
272
273#pragma mark - RDP keyboard handler
274
275- (void)modifiersChangedForKeyboard:(RDPKeyboard *)keyboard
276{
277 UIBarButtonItem *curItem;
278
279 // shift button (only on iPad)
280 int objectIdx = 0;
281 if (IsPad())
282 {
283 objectIdx = 2;
284 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
285 [curItem setStyle:[keyboard shiftPressed] ? UIBarButtonItemStyleDone
286 : UIBarButtonItemStyleBordered];
287 }
288
289 // ctrl button
290 objectIdx += 2;
291 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
292 [curItem
293 setStyle:[keyboard ctrlPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStyleBordered];
294
295 // win button
296 objectIdx += 2;
297 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
298 [curItem
299 setStyle:[keyboard winPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStyleBordered];
300
301 // alt button
302 objectIdx += 2;
303 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
304 [curItem
305 setStyle:[keyboard altPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStyleBordered];
306}
307
308#pragma mark -
309#pragma mark RDPSessionDelegate functions
310
311- (void)session:(RDPSession *)session didFailToConnect:(int)reason
312{
313 // remove and release connecting view
314 [_connecting_indicator_view stopAnimating];
315 [_connecting_view removeFromSuperview];
316 [_connecting_view autorelease];
317
318 // return to bookmark list
319 [[self navigationController] popViewControllerAnimated:YES];
320}
321
322- (void)sessionWillConnect:(RDPSession *)session
323{
324 // load connecting view
325 [[NSBundle mainBundle] loadNibNamed:@"RDPConnectingView" owner:self options:nil];
326
327 // set strings
328 [_lbl_connecting setText:NSLocalizedString(@"Connecting", @"Connecting progress view - label")];
329 [_cancel_connect_button setTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
330 forState:UIControlStateNormal];
331
332 // center view and give it round corners
333 [_connecting_view setCenter:[[self view] center]];
334 [[_connecting_view layer] setCornerRadius:10];
335
336 // display connecting view and start indicator
337 [[self view] addSubview:_connecting_view];
338 [_connecting_indicator_view startAnimating];
339}
340
341- (void)sessionDidConnect:(RDPSession *)session
342{
343 // register keyboard notification handlers
344 [[NSNotificationCenter defaultCenter] addObserver:self
345 selector:@selector(keyboardWillShow:)
346 name:UIKeyboardWillShowNotification
347 object:nil];
348 [[NSNotificationCenter defaultCenter] addObserver:self
349 selector:@selector(keyboardDidShow:)
350 name:UIKeyboardDidShowNotification
351 object:nil];
352 [[NSNotificationCenter defaultCenter] addObserver:self
353 selector:@selector(keyboardWillHide:)
354 name:UIKeyboardWillHideNotification
355 object:nil];
356 [[NSNotificationCenter defaultCenter] addObserver:self
357 selector:@selector(keyboardDidHide:)
358 name:UIKeyboardDidHideNotification
359 object:nil];
360
361 // remove and release connecting view
362 [_connecting_indicator_view stopAnimating];
363 [_connecting_view removeFromSuperview];
364 [_connecting_view autorelease];
365
366 // check if session settings changed ...
367 // The 2nd width check is to ignore changes in resolution settings due to the RDVH display bug
368 // (refer to RDPSEssion.m for more details)
369 ConnectionParams *orig_params = [session params];
370 rdpSettings *sess_params = [session getSessionParams];
371 if (([orig_params intForKey:@"width"] != sess_params->DesktopWidth &&
372 [orig_params intForKey:@"width"] != (sess_params->DesktopWidth + 1)) ||
373 [orig_params intForKey:@"height"] != sess_params->DesktopHeight ||
374 [orig_params intForKey:@"colors"] != sess_params->ColorDepth)
375 {
376 // display notification that the session params have been changed by the server
377 NSString *message =
378 [NSString stringWithFormat:NSLocalizedString(
379 @"The server changed the screen settings to %dx%dx%d",
380 @"Screen settings not supported message with width, "
381 @"height and colors parameter"),
382 sess_params->DesktopWidth, sess_params->DesktopHeight,
383 sess_params->ColorDepth];
384 [[self view] makeToast:message duration:ToastDurationNormal position:@"bottom"];
385 }
386}
387
388- (void)sessionWillDisconnect:(RDPSession *)session
389{
390}
391
392- (void)sessionDidDisconnect:(RDPSession *)session
393{
394 // return to bookmark list
395 [[self navigationController] popViewControllerAnimated:YES];
396}
397
398- (void)sessionBitmapContextWillChange:(RDPSession *)session
399{
400 // calc new view frame
401 rdpSettings *sess_params = [session getSessionParams];
402 CGRect view_rect = CGRectMake(0, 0, sess_params->DesktopWidth, sess_params->DesktopHeight);
403
404 // reset zoom level and update content size
405 [_session_scrollview setZoomScale:1.0];
406 [_session_scrollview setContentSize:view_rect.size];
407
408 // set session view size
409 [_session_view setFrame:view_rect];
410
411 // show/hide toolbar
412 [_session
413 setToolbarVisible:![[NSUserDefaults standardUserDefaults] boolForKey:@"ui.hide_tool_bar"]];
414 [self showSessionToolbar:[_session toolbarVisible]];
415}
416
417- (void)sessionBitmapContextDidChange:(RDPSession *)session
418{
419 // associate view with session
420 [_session_view setSession:session];
421
422 // issue an update (this might be needed in case we had a resize for instance)
423 [_session_view setNeedsDisplay];
424}
425
426- (void)session:(RDPSession *)session needsRedrawInRect:(CGRect)rect
427{
428 [_session_view setNeedsDisplayInRect:rect];
429}
430
431- (void)session:(RDPSession *)session requestsAuthenticationWithParams:(NSMutableDictionary *)params
432{
433 CredentialsInputController *view_controller =
434 [[[CredentialsInputController alloc] initWithNibName:@"CredentialsInputView"
435 bundle:nil
436 session:_session
437 params:params] autorelease];
438 [self presentModalViewController:view_controller animated:YES];
439}
440
441- (void)session:(RDPSession *)session verifyCertificateWithParams:(NSMutableDictionary *)params
442{
443 VerifyCertificateController *view_controller =
444 [[[VerifyCertificateController alloc] initWithNibName:@"VerifyCertificateView"
445 bundle:nil
446 session:_session
447 params:params] autorelease];
448 [self presentModalViewController:view_controller animated:YES];
449}
450
451- (CGSize)sizeForFitScreenForSession:(RDPSession *)session
452{
453 if (IsPad())
454 return [self view].bounds.size;
455 else
456 {
457 // on phones make a resolution that has a 16:10 ratio with the phone's height
458 CGSize size = [self view].bounds.size;
459 CGFloat maxSize = (size.width > size.height) ? size.width : size.height;
460 return CGSizeMake(maxSize * 1.6f, maxSize);
461 }
462}
463
464#pragma mark - Keyboard Toolbar Handlers
465
466- (void)showAdvancedKeyboardAnimated
467{
468 // calc initial and final rect of the advanced keyboard view
469 CGRect rect = [[_keyboard_toolbar superview] bounds];
470 rect.origin.y = [_keyboard_toolbar bounds].size.height;
471 rect.size.height -= rect.origin.y;
472
473 // create new view (hidden) and add to host-view of keyboard toolbar
474 _advanced_keyboard_view = [[AdvancedKeyboardView alloc]
475 initWithFrame:CGRectMake(rect.origin.x, [[_keyboard_toolbar superview] bounds].size.height,
476 rect.size.width, rect.size.height)
477 delegate:self];
478 [[_keyboard_toolbar superview] addSubview:_advanced_keyboard_view];
479 // we set autoresize to YES for the keyboard toolbar's superview so that our adv. keyboard view
480 // gets properly resized
481 [[_keyboard_toolbar superview] setAutoresizesSubviews:YES];
482
483 // show view with animation
484 [UIView beginAnimations:nil context:NULL];
485 [_advanced_keyboard_view setFrame:rect];
486 [UIView commitAnimations];
487}
488
489- (IBAction)toggleKeyboardWhenOtherVisible:(id)sender
490{
491 if (_advanced_keyboard_visible == NO)
492 {
493 [self showAdvancedKeyboardAnimated];
494 }
495 else
496 {
497 // hide existing view
498 [UIView beginAnimations:@"hide_advanced_keyboard_view" context:NULL];
499 CGRect rect = [_advanced_keyboard_view frame];
500 rect.origin.y = [[_keyboard_toolbar superview] bounds].size.height;
501 [_advanced_keyboard_view setFrame:rect];
502 [UIView commitAnimations];
503
504 // the view is released in the animationDidStop selector registered in init
505 }
506
507 // toggle flag
508 _advanced_keyboard_visible = !_advanced_keyboard_visible;
509}
510
511- (IBAction)toggleWinKey:(id)sender
512{
513 [[RDPKeyboard getSharedRDPKeyboard] toggleWinKey];
514}
515
516- (IBAction)toggleShiftKey:(id)sender
517{
518 [[RDPKeyboard getSharedRDPKeyboard] toggleShiftKey];
519}
520
521- (IBAction)toggleCtrlKey:(id)sender
522{
523 [[RDPKeyboard getSharedRDPKeyboard] toggleCtrlKey];
524}
525
526- (IBAction)toggleAltKey:(id)sender
527{
528 [[RDPKeyboard getSharedRDPKeyboard] toggleAltKey];
529}
530
531- (IBAction)pressEscKey:(id)sender
532{
533 [[RDPKeyboard getSharedRDPKeyboard] sendEscapeKeyStroke];
534}
535
536#pragma mark -
537#pragma mark event handlers
538
539- (void)animationStopped:(NSString *)animationID
540 finished:(NSNumber *)finished
541 context:(void *)context
542{
543 if ([animationID isEqualToString:@"hide_advanced_keyboard_view"])
544 {
545 // cleanup advanced keyboard view
546 [_advanced_keyboard_view removeFromSuperview];
547 [_advanced_keyboard_view autorelease];
548 _advanced_keyboard_view = nil;
549 }
550}
551
552- (IBAction)switchSession:(id)sender
553{
554 [self suspendSession];
555}
556
557- (IBAction)toggleKeyboard:(id)sender
558{
559 if (!_keyboard_visible)
560 [_dummy_textfield becomeFirstResponder];
561 else
562 [_dummy_textfield resignFirstResponder];
563}
564
565- (IBAction)toggleExtKeyboard:(id)sender
566{
567 // if the sys kb is shown but not the advanced kb then toggle the advanced kb
568 if (_keyboard_visible && !_advanced_keyboard_visible)
569 [self toggleKeyboardWhenOtherVisible:nil];
570 else
571 {
572 // if not visible request the advanced keyboard view
573 if (_advanced_keyboard_visible == NO)
574 _requesting_advanced_keyboard = YES;
575 [self toggleKeyboard:nil];
576 }
577}
578
579- (IBAction)toggleTouchPointer:(id)sender
580{
581 BOOL toggle_visibilty = ![_touchpointer_view isHidden];
582 [_touchpointer_view setHidden:toggle_visibilty];
583 if (toggle_visibilty)
584 [_session_scrollview setContentInset:UIEdgeInsetsZero];
585 else
586 [_session_scrollview setContentInset:[_touchpointer_view getEdgeInsets]];
587}
588
589- (IBAction)disconnectSession:(id)sender
590{
591 [_session disconnect];
592}
593
594- (IBAction)cancelButtonPressed:(id)sender
595{
596 [_session disconnect];
597}
598
599#pragma mark -
600#pragma mark iOS Keyboard Notification Handlers
601
602// the keyboard is given in a portrait frame of reference
603- (BOOL)isLandscape
604{
605
606 UIInterfaceOrientation ori = [[UIApplication sharedApplication] statusBarOrientation];
607 return (ori == UIInterfaceOrientationLandscapeLeft ||
608 ori == UIInterfaceOrientationLandscapeRight);
609}
610
611- (void)shiftKeyboard:(NSNotification *)notification
612{
613
614 CGRect keyboardEndFrame =
615 [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
616
617 CGFloat previousHeight = _keyboard_last_height;
618
619 if ([self isLandscape])
620 {
621 // landscape has the keyboard based on x, so x can go negative
622 _keyboard_last_height = keyboardEndFrame.size.width + keyboardEndFrame.origin.x;
623 }
624 else
625 {
626 // portrait has the keyboard based on the difference of the height and the frames y.
627 CGFloat height = [[UIScreen mainScreen] bounds].size.height;
628 _keyboard_last_height = height - keyboardEndFrame.origin.y;
629 }
630
631 CGFloat shiftHeight = _keyboard_last_height - previousHeight;
632
633 [UIView beginAnimations:nil context:NULL];
634 [UIView setAnimationCurve:[[[notification userInfo]
635 objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
636 [UIView
637 setAnimationDuration:[[[notification userInfo]
638 objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
639 CGRect frame = [_session_scrollview frame];
640 frame.size.height -= shiftHeight;
641 [_session_scrollview setFrame:frame];
642 [_touchpointer_view setFrame:frame];
643 [UIView commitAnimations];
644}
645
646- (void)keyboardWillShow:(NSNotification *)notification
647{
648 [self shiftKeyboard:notification];
649
650 [_touchpointer_view ensurePointerIsVisible];
651}
652
653- (void)keyboardDidShow:(NSNotification *)notification
654{
655 if (_requesting_advanced_keyboard)
656 {
657 [self showAdvancedKeyboardAnimated];
658 _advanced_keyboard_visible = YES;
659 _requesting_advanced_keyboard = NO;
660 }
661}
662
663- (void)keyboardWillHide:(NSNotification *)notification
664{
665
666 [self shiftKeyboard:notification];
667}
668
669- (void)keyboardDidHide:(NSNotification *)notification
670{
671 // release adanced keyboard view
672 if (_advanced_keyboard_visible == YES)
673 {
674 _advanced_keyboard_visible = NO;
675 [_advanced_keyboard_view removeFromSuperview];
676 [_advanced_keyboard_view autorelease];
677 _advanced_keyboard_view = nil;
678 }
679}
680
681#pragma mark -
682#pragma mark Gesture handlers
683
684- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
685{
686 CGPoint pos = [gesture locationInView:_session_view];
687 if (_toggle_mouse_button)
688 {
689 [_session
690 sendInputEvent:[self eventDescriptorForMouseEvent:GetRightMouseButtonClickEvent(YES)
691 position:pos]];
692 [_session
693 sendInputEvent:[self eventDescriptorForMouseEvent:GetRightMouseButtonClickEvent(NO)
694 position:pos]];
695 }
696 else
697 {
698 [_session
699 sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES)
700 position:pos]];
701 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO)
702 position:pos]];
703 }
704
705 _toggle_mouse_button = NO;
706}
707
708- (void)handleDoubleTap:(UITapGestureRecognizer *)gesture
709{
710 CGPoint pos = [gesture locationInView:_session_view];
711 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES)
712 position:pos]];
713 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO)
714 position:pos]];
715 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES)
716 position:pos]];
717 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO)
718 position:pos]];
719 _toggle_mouse_button = NO;
720}
721
722- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
723{
724 CGPoint pos = [gesture locationInView:_session_view];
725
726 if ([gesture state] == UIGestureRecognizerStateBegan)
727 [_session
728 sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES)
729 position:pos]];
730 else if ([gesture state] == UIGestureRecognizerStateChanged)
731 [self handleMouseMoveForPosition:pos];
732 else if ([gesture state] == UIGestureRecognizerStateEnded)
733 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO)
734 position:pos]];
735}
736
737- (void)handleDoubleLongPress:(UILongPressGestureRecognizer *)gesture
738{
739 // this point is mapped against the scroll view because we want to have relative movement to the
740 // screen/scrollview
741 CGPoint pos = [gesture locationInView:_session_scrollview];
742
743 if ([gesture state] == UIGestureRecognizerStateBegan)
744 _prev_long_press_position = pos;
745 else if ([gesture state] == UIGestureRecognizerStateChanged)
746 {
747 int delta = _prev_long_press_position.y - pos.y;
748
749 if (delta > GetScrollGestureDelta())
750 {
751 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetMouseWheelEvent(YES)
752 position:pos]];
753 _prev_long_press_position = pos;
754 }
755 else if (delta < -GetScrollGestureDelta())
756 {
757 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetMouseWheelEvent(NO)
758 position:pos]];
759 _prev_long_press_position = pos;
760 }
761 }
762}
763
764- (void)handleSingle2FingersTap:(UITapGestureRecognizer *)gesture
765{
766 _toggle_mouse_button = !_toggle_mouse_button;
767}
768
769- (void)handleSingle3FingersTap:(UITapGestureRecognizer *)gesture
770{
771 [_session setToolbarVisible:![_session toolbarVisible]];
772 [self showSessionToolbar:[_session toolbarVisible]];
773}
774
775#pragma mark -
776#pragma mark Touch Pointer delegates
777// callback if touch pointer should be closed
778- (void)touchPointerClose
779{
780 [self toggleTouchPointer:nil];
781}
782
783// callback for a left click action
784- (void)touchPointerLeftClick:(CGPoint)pos down:(BOOL)down
785{
786 CGPoint session_view_pos = [_touchpointer_view convertPoint:pos toView:_session_view];
787 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(down)
788 position:session_view_pos]];
789}
790
791// callback for a right click action
792- (void)touchPointerRightClick:(CGPoint)pos down:(BOOL)down
793{
794 CGPoint session_view_pos = [_touchpointer_view convertPoint:pos toView:_session_view];
795 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetRightMouseButtonClickEvent(down)
796 position:session_view_pos]];
797}
798
799- (void)doAutoScrolling
800{
801 int scrollX = 0;
802 int scrollY = 0;
803 CGPoint curPointerPos = [_touchpointer_view getPointerPosition];
804 CGRect viewBounds = [_touchpointer_view bounds];
805 CGRect scrollBounds = [_session_view bounds];
806
807 // add content insets to scroll bounds
808 scrollBounds.size.width += [_session_scrollview contentInset].right;
809 scrollBounds.size.height += [_session_scrollview contentInset].bottom;
810
811 // add zoom factor
812 scrollBounds.size.width *= [_session_scrollview zoomScale];
813 scrollBounds.size.height *= [_session_scrollview zoomScale];
814
815 if (curPointerPos.x > (viewBounds.size.width - [_touchpointer_view getPointerWidth]))
816 scrollX = AUTOSCROLLDISTANCE;
817 else if (curPointerPos.x < 0)
818 scrollX = -AUTOSCROLLDISTANCE;
819
820 if (curPointerPos.y > (viewBounds.size.height - [_touchpointer_view getPointerHeight]))
821 scrollY = AUTOSCROLLDISTANCE;
822 else if (curPointerPos.y < (_session_toolbar_visible ? TOOLBAR_HEIGHT : 0))
823 scrollY = -AUTOSCROLLDISTANCE;
824
825 CGPoint newOffset = [_session_scrollview contentOffset];
826 newOffset.x += scrollX;
827 newOffset.y += scrollY;
828
829 // if offset is going off screen - stop scrolling in that direction
830 if (newOffset.x < 0)
831 {
832 scrollX = 0;
833 newOffset.x = 0;
834 }
835 else if (newOffset.x > (scrollBounds.size.width - viewBounds.size.width))
836 {
837 scrollX = 0;
838 newOffset.x = MAX(scrollBounds.size.width - viewBounds.size.width, 0);
839 }
840 if (newOffset.y < 0)
841 {
842 scrollY = 0;
843 newOffset.y = 0;
844 }
845 else if (newOffset.y > (scrollBounds.size.height - viewBounds.size.height))
846 {
847 scrollY = 0;
848 newOffset.y = MAX(scrollBounds.size.height - viewBounds.size.height, 0);
849 }
850
851 // perform scrolling
852 [_session_scrollview setContentOffset:newOffset];
853
854 // continue scrolling?
855 if (scrollX != 0 || scrollY != 0)
856 [self performSelector:@selector(doAutoScrolling)
857 withObject:nil
858 afterDelay:AUTOSCROLLTIMEOUT];
859 else
860 _is_autoscrolling = NO;
861}
862
863// callback for a right click action
864- (void)touchPointerMove:(CGPoint)pos
865{
866 CGPoint session_view_pos = [_touchpointer_view convertPoint:pos toView:_session_view];
867 [self handleMouseMoveForPosition:session_view_pos];
868
869 if (_autoscroll_with_touchpointer && !_is_autoscrolling)
870 {
871 _is_autoscrolling = YES;
872 [self performSelector:@selector(doAutoScrolling)
873 withObject:nil
874 afterDelay:AUTOSCROLLTIMEOUT];
875 }
876}
877
878// callback if scrolling is performed
879- (void)touchPointerScrollDown:(BOOL)down
880{
881 [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetMouseWheelEvent(down)
882 position:CGPointZero]];
883}
884
885// callback for toggling the standard keyboard
886- (void)touchPointerToggleKeyboard
887{
888 if (_advanced_keyboard_visible)
889 [self toggleKeyboardWhenOtherVisible:nil];
890 else
891 [self toggleKeyboard:nil];
892}
893
894// callback for toggling the extended keyboard
895- (void)touchPointerToggleExtendedKeyboard
896{
897 [self toggleExtKeyboard:nil];
898}
899
900// callback for reset view
901- (void)touchPointerResetSessionView
902{
903 [_session_scrollview setZoomScale:1.0 animated:YES];
904}
905
906@end
907
908@implementation RDPSessionViewController (Private)
909
910#pragma mark -
911#pragma mark Helper functions
912
913- (void)showSessionToolbar:(BOOL)show
914{
915 // already shown or hidden?
916 if (_session_toolbar_visible == show)
917 return;
918
919 if (show)
920 {
921 [UIView beginAnimations:@"showToolbar" context:nil];
922 [UIView setAnimationDuration:.4];
923 [UIView setAnimationCurve:UIViewAnimationCurveLinear];
924 [_session_toolbar
925 setFrame:CGRectMake(0.0, 0.0, [[self view] bounds].size.width, TOOLBAR_HEIGHT)];
926 [UIView commitAnimations];
927 _session_toolbar_visible = YES;
928 }
929 else
930 {
931 [UIView beginAnimations:@"hideToolbar" context:nil];
932 [UIView setAnimationDuration:.4];
933 [UIView setAnimationCurve:UIViewAnimationCurveLinear];
934 [_session_toolbar setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[self view] bounds].size.width,
935 TOOLBAR_HEIGHT)];
936 [UIView commitAnimations];
937 _session_toolbar_visible = NO;
938 }
939}
940
941- (UIToolbar *)keyboardToolbar
942{
943 UIToolbar *keyboard_toolbar = [[[UIToolbar alloc] initWithFrame:CGRectNull] autorelease];
944 [keyboard_toolbar setBarStyle:UIBarStyleBlackOpaque];
945
946 UIBarButtonItem *esc_btn =
947 [[[UIBarButtonItem alloc] initWithTitle:@"Esc"
948 style:UIBarButtonItemStyleBordered
949 target:self
950 action:@selector(pressEscKey:)] autorelease];
951 UIImage *win_icon =
952 [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"toolbar_icon_win"
953 ofType:@"png"]];
954 UIBarButtonItem *win_btn =
955 [[[UIBarButtonItem alloc] initWithImage:win_icon
956 style:UIBarButtonItemStyleBordered
957 target:self
958 action:@selector(toggleWinKey:)] autorelease];
959 UIBarButtonItem *ctrl_btn =
960 [[[UIBarButtonItem alloc] initWithTitle:@"Ctrl"
961 style:UIBarButtonItemStyleBordered
962 target:self
963 action:@selector(toggleCtrlKey:)] autorelease];
964 UIBarButtonItem *alt_btn =
965 [[[UIBarButtonItem alloc] initWithTitle:@"Alt"
966 style:UIBarButtonItemStyleBordered
967 target:self
968 action:@selector(toggleAltKey:)] autorelease];
969 UIBarButtonItem *ext_btn = [[[UIBarButtonItem alloc]
970 initWithTitle:@"Ext"
971 style:UIBarButtonItemStyleBordered
972 target:self
973 action:@selector(toggleKeyboardWhenOtherVisible:)] autorelease];
974 UIBarButtonItem *done_btn = [[[UIBarButtonItem alloc]
975 initWithBarButtonSystemItem:UIBarButtonSystemItemDone
976 target:self
977 action:@selector(toggleKeyboard:)] autorelease];
978 UIBarButtonItem *flex_spacer =
979 [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
980 target:nil
981 action:nil] autorelease];
982
983 // iPad gets a shift button, iphone doesn't (there's just not enough space ...)
984 NSArray *items;
985 if (IsPad())
986 {
987 UIBarButtonItem *shift_btn =
988 [[[UIBarButtonItem alloc] initWithTitle:@"Shift"
989 style:UIBarButtonItemStyleBordered
990 target:self
991 action:@selector(toggleShiftKey:)] autorelease];
992 items = [NSArray arrayWithObjects:esc_btn, flex_spacer, shift_btn, flex_spacer, ctrl_btn,
993 flex_spacer, win_btn, flex_spacer, alt_btn, flex_spacer,
994 ext_btn, flex_spacer, done_btn, nil];
995 }
996 else
997 {
998 items = [NSArray arrayWithObjects:esc_btn, flex_spacer, ctrl_btn, flex_spacer, win_btn,
999 flex_spacer, alt_btn, flex_spacer, ext_btn, flex_spacer,
1000 done_btn, nil];
1001 }
1002
1003 [keyboard_toolbar setItems:items];
1004 [keyboard_toolbar sizeToFit];
1005 return keyboard_toolbar;
1006}
1007
1008- (void)initGestureRecognizers
1009{
1010 // single and double tap recognizer
1011 UITapGestureRecognizer *doubleTapRecognizer =
1012 [[[UITapGestureRecognizer alloc] initWithTarget:self
1013 action:@selector(handleDoubleTap:)] autorelease];
1014 [doubleTapRecognizer setNumberOfTouchesRequired:1];
1015 [doubleTapRecognizer setNumberOfTapsRequired:2];
1016
1017 UITapGestureRecognizer *singleTapRecognizer =
1018 [[[UITapGestureRecognizer alloc] initWithTarget:self
1019 action:@selector(handleSingleTap:)] autorelease];
1020 [singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
1021 [singleTapRecognizer setNumberOfTouchesRequired:1];
1022 [singleTapRecognizer setNumberOfTapsRequired:1];
1023
1024 // 2 fingers - tap recognizer
1025 UITapGestureRecognizer *single2FingersTapRecognizer = [[[UITapGestureRecognizer alloc]
1026 initWithTarget:self
1027 action:@selector(handleSingle2FingersTap:)] autorelease];
1028 [single2FingersTapRecognizer setNumberOfTouchesRequired:2];
1029 [single2FingersTapRecognizer setNumberOfTapsRequired:1];
1030
1031 // long press gesture recognizer
1032 UILongPressGestureRecognizer *longPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1033 initWithTarget:self
1034 action:@selector(handleLongPress:)] autorelease];
1035 [longPressRecognizer setMinimumPressDuration:0.5];
1036
1037 // double long press gesture recognizer
1038 UILongPressGestureRecognizer *doubleLongPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1039 initWithTarget:self
1040 action:@selector(handleDoubleLongPress:)] autorelease];
1041 [doubleLongPressRecognizer setNumberOfTouchesRequired:2];
1042 [doubleLongPressRecognizer setMinimumPressDuration:0.5];
1043
1044 // 3 finger, single tap gesture for showing/hiding the toolbar
1045 UITapGestureRecognizer *single3FingersTapRecognizer = [[[UITapGestureRecognizer alloc]
1046 initWithTarget:self
1047 action:@selector(handleSingle3FingersTap:)] autorelease];
1048 [single3FingersTapRecognizer setNumberOfTapsRequired:1];
1049 [single3FingersTapRecognizer setNumberOfTouchesRequired:3];
1050
1051 // add gestures to scroll view
1052 [_session_scrollview addGestureRecognizer:singleTapRecognizer];
1053 [_session_scrollview addGestureRecognizer:doubleTapRecognizer];
1054 [_session_scrollview addGestureRecognizer:single2FingersTapRecognizer];
1055 [_session_scrollview addGestureRecognizer:longPressRecognizer];
1056 [_session_scrollview addGestureRecognizer:doubleLongPressRecognizer];
1057 [_session_scrollview addGestureRecognizer:single3FingersTapRecognizer];
1058}
1059
1060- (void)suspendSession
1061{
1062 // suspend session and pop navigation controller
1063 [_session suspend];
1064
1065 // pop current view controller
1066 [[self navigationController] popViewControllerAnimated:YES];
1067}
1068
1069- (NSDictionary *)eventDescriptorForMouseEvent:(int)event position:(CGPoint)position
1070{
1071 return [NSDictionary
1072 dictionaryWithObjectsAndKeys:@"mouse", @"type", [NSNumber numberWithUnsignedShort:event],
1073 @"flags",
1074 [NSNumber numberWithUnsignedShort:lrintf(position.x)],
1075 @"coord_x",
1076 [NSNumber numberWithUnsignedShort:lrintf(position.y)],
1077 @"coord_y", nil];
1078}
1079
1080- (void)sendDelayedMouseEventWithTimer:(NSTimer *)timer
1081{
1082 _mouse_move_event_timer = nil;
1083 NSDictionary *event = [timer userInfo];
1084 [_session sendInputEvent:event];
1085 [timer autorelease];
1086}
1087
1088- (void)handleMouseMoveForPosition:(CGPoint)position
1089{
1090 NSDictionary *event = [self eventDescriptorForMouseEvent:PTR_FLAGS_MOVE position:position];
1091
1092 // cancel pending mouse move events
1093 [_mouse_move_event_timer invalidate];
1094 _mouse_move_events_skipped++;
1095
1096 if (_mouse_move_events_skipped >= 5)
1097 {
1098 [_session sendInputEvent:event];
1099 _mouse_move_events_skipped = 0;
1100 }
1101 else
1102 {
1103 [_mouse_move_event_timer autorelease];
1104 _mouse_move_event_timer =
1105 [[NSTimer scheduledTimerWithTimeInterval:0.05
1106 target:self
1107 selector:@selector(sendDelayedMouseEventWithTimer:)
1108 userInfo:event
1109 repeats:NO] retain];
1110 }
1111}
1112
1113@end