/* RDP Session View Controller Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #import #import "RDPSessionViewController.h" #import "RDPKeyboard.h" #import "Utils.h" #import "Toast+UIView.h" #import "ConnectionParams.h" #import "CredentialsInputController.h" #import "VerifyCertificateController.h" #import "BlockAlertView.h" #define TOOLBAR_HEIGHT 30 #define AUTOSCROLLDISTANCE 20 #define AUTOSCROLLTIMEOUT 0.05 @interface RDPSessionViewController (Private) - (void)showSessionToolbar:(BOOL)show; - (UIToolbar *)keyboardToolbar; - (void)initGestureRecognizers; - (void)suspendSession; - (NSDictionary *)eventDescriptorForMouseEvent:(int)event position:(CGPoint)position; - (void)handleMouseMoveForPosition:(CGPoint)position; @end @implementation RDPSessionViewController #pragma mark class methods - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil session:(RDPSession *)session { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { _session = [session retain]; [_session setDelegate:self]; _session_initilized = NO; _mouse_move_events_skipped = 0; _mouse_move_event_timer = nil; _advanced_keyboard_view = nil; _advanced_keyboard_visible = NO; _requesting_advanced_keyboard = NO; _keyboard_last_height = 0; _session_toolbar_visible = NO; _toggle_mouse_button = NO; _autoscroll_with_touchpointer = [[NSUserDefaults standardUserDefaults] boolForKey:@"ui.auto_scroll_touchpointer"]; _is_autoscrolling = NO; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(animationStopped:finished:context:)]; } return self; } // Implement loadView to create a view hierarchy programmatically, without using a nib. - (void)loadView { // load default view and set background color and resizing mask [super loadView]; // init keyboard handling vars _keyboard_visible = NO; // init keyboard toolbar _keyboard_toolbar = [[self keyboardToolbar] retain]; [_dummy_textfield setInputAccessoryView:_keyboard_toolbar]; // init gesture recognizers [self initGestureRecognizers]; // hide session toolbar [_session_toolbar setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[self view] bounds].size.width, TOOLBAR_HEIGHT)]; } // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { [super viewDidLoad]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return YES; } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { if (![_touchpointer_view isHidden]) [_touchpointer_view ensurePointerIsVisible]; } - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; // Release any cached data, images, etc. that aren't in use. } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // hide navigation bar and (if enabled) the status bar if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ui.hide_status_bar"]) { if (animated == YES) [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]; else [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; } [[self navigationController] setNavigationBarHidden:YES animated:animated]; // if sesssion is suspended - notify that we got a new bitmap context if ([_session isSuspended]) [self sessionBitmapContextWillChange:_session]; // init keyboard [[RDPKeyboard getSharedRDPKeyboard] initWithSession:_session delegate:self]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (!_session_initilized) { if ([_session isSuspended]) { [_session resume]; [self sessionBitmapContextDidChange:_session]; [_session_view setNeedsDisplay]; } else [_session connect]; _session_initilized = YES; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // show navigation and status bar again if (animated == YES) [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]; else [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; [[self navigationController] setNavigationBarHidden:NO animated:animated]; // reset all modifier keys on rdp keyboard [[RDPKeyboard getSharedRDPKeyboard] reset]; // hide toolbar and keyboard [self showSessionToolbar:NO]; [_dummy_textfield resignFirstResponder]; } - (void)dealloc { // remove any observers [[NSNotificationCenter defaultCenter] removeObserver:self]; // the session lives on longer so set the delegate to nil [_session setDelegate:nil]; [_advanced_keyboard_view release]; [_keyboard_toolbar release]; [_session release]; [super dealloc]; } #pragma mark - #pragma mark ScrollView delegate methods - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return _session_view; } - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale { NSLog(@"New zoom scale: %f", scale); [_session_view setNeedsDisplay]; } #pragma mark - #pragma mark TextField delegate methods - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { _keyboard_visible = YES; _advanced_keyboard_visible = NO; return YES; } - (BOOL)textFieldShouldEndEditing:(UITextField *)textField { _keyboard_visible = NO; _advanced_keyboard_visible = NO; return YES; } - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if ([string length] > 0) { for (int i = 0; i < [string length]; i++) { unichar curChar = [string characterAtIndex:i]; // special handling for return/enter key if (curChar == '\n') [[RDPKeyboard getSharedRDPKeyboard] sendEnterKeyStroke]; else [[RDPKeyboard getSharedRDPKeyboard] sendUnicode:curChar]; } } else { [[RDPKeyboard getSharedRDPKeyboard] sendBackspaceKeyStroke]; } return NO; } #pragma mark - #pragma mark AdvancedKeyboardDelegate functions - (void)advancedKeyPressedVKey:(int)key { [[RDPKeyboard getSharedRDPKeyboard] sendVirtualKeyCode:key]; } - (void)advancedKeyPressedUnicode:(int)key { [[RDPKeyboard getSharedRDPKeyboard] sendUnicode:key]; } #pragma mark - RDP keyboard handler - (void)modifiersChangedForKeyboard:(RDPKeyboard *)keyboard { UIBarButtonItem *curItem; // shift button (only on iPad) int objectIdx = 0; if (IsPad()) { objectIdx = 2; curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx]; [curItem setStyle:[keyboard shiftPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStyleBordered]; } // ctrl button objectIdx += 2; curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx]; [curItem setStyle:[keyboard ctrlPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStyleBordered]; // win button objectIdx += 2; curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx]; [curItem setStyle:[keyboard winPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStyleBordered]; // alt button objectIdx += 2; curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx]; [curItem setStyle:[keyboard altPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStyleBordered]; } #pragma mark - #pragma mark RDPSessionDelegate functions - (void)session:(RDPSession *)session didFailToConnect:(int)reason { // remove and release connecting view [_connecting_indicator_view stopAnimating]; [_connecting_view removeFromSuperview]; [_connecting_view autorelease]; // return to bookmark list [[self navigationController] popViewControllerAnimated:YES]; } - (void)sessionWillConnect:(RDPSession *)session { // load connecting view [[NSBundle mainBundle] loadNibNamed:@"RDPConnectingView" owner:self options:nil]; // set strings [_lbl_connecting setText:NSLocalizedString(@"Connecting", @"Connecting progress view - label")]; [_cancel_connect_button setTitle:NSLocalizedString(@"Cancel", @"Cancel Button") forState:UIControlStateNormal]; // center view and give it round corners [_connecting_view setCenter:[[self view] center]]; [[_connecting_view layer] setCornerRadius:10]; // display connecting view and start indicator [[self view] addSubview:_connecting_view]; [_connecting_indicator_view startAnimating]; } - (void)sessionDidConnect:(RDPSession *)session { // register keyboard notification handlers [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil]; // remove and release connecting view [_connecting_indicator_view stopAnimating]; [_connecting_view removeFromSuperview]; [_connecting_view autorelease]; // check if session settings changed ... // The 2nd width check is to ignore changes in resolution settings due to the RDVH display bug // (refer to RDPSEssion.m for more details) ConnectionParams *orig_params = [session params]; rdpSettings *sess_params = [session getSessionParams]; if (([orig_params intForKey:@"width"] != sess_params->DesktopWidth && [orig_params intForKey:@"width"] != (sess_params->DesktopWidth + 1)) || [orig_params intForKey:@"height"] != sess_params->DesktopHeight || [orig_params intForKey:@"colors"] != sess_params->ColorDepth) { // display notification that the session params have been changed by the server NSString *message = [NSString stringWithFormat:NSLocalizedString( @"The server changed the screen settings to %dx%dx%d", @"Screen settings not supported message with width, " @"height and colors parameter"), sess_params->DesktopWidth, sess_params->DesktopHeight, sess_params->ColorDepth]; [[self view] makeToast:message duration:ToastDurationNormal position:@"bottom"]; } } - (void)sessionWillDisconnect:(RDPSession *)session { } - (void)sessionDidDisconnect:(RDPSession *)session { // return to bookmark list [[self navigationController] popViewControllerAnimated:YES]; } - (void)sessionBitmapContextWillChange:(RDPSession *)session { // calc new view frame rdpSettings *sess_params = [session getSessionParams]; CGRect view_rect = CGRectMake(0, 0, sess_params->DesktopWidth, sess_params->DesktopHeight); // reset zoom level and update content size [_session_scrollview setZoomScale:1.0]; [_session_scrollview setContentSize:view_rect.size]; // set session view size [_session_view setFrame:view_rect]; // show/hide toolbar [_session setToolbarVisible:![[NSUserDefaults standardUserDefaults] boolForKey:@"ui.hide_tool_bar"]]; [self showSessionToolbar:[_session toolbarVisible]]; } - (void)sessionBitmapContextDidChange:(RDPSession *)session { // associate view with session [_session_view setSession:session]; // issue an update (this might be needed in case we had a resize for instance) [_session_view setNeedsDisplay]; } - (void)session:(RDPSession *)session needsRedrawInRect:(CGRect)rect { [_session_view setNeedsDisplayInRect:rect]; } - (void)session:(RDPSession *)session requestsAuthenticationWithParams:(NSMutableDictionary *)params { CredentialsInputController *view_controller = [[[CredentialsInputController alloc] initWithNibName:@"CredentialsInputView" bundle:nil session:_session params:params] autorelease]; [self presentModalViewController:view_controller animated:YES]; } - (void)session:(RDPSession *)session verifyCertificateWithParams:(NSMutableDictionary *)params { VerifyCertificateController *view_controller = [[[VerifyCertificateController alloc] initWithNibName:@"VerifyCertificateView" bundle:nil session:_session params:params] autorelease]; [self presentModalViewController:view_controller animated:YES]; } - (CGSize)sizeForFitScreenForSession:(RDPSession *)session { if (IsPad()) return [self view].bounds.size; else { // on phones make a resolution that has a 16:10 ratio with the phone's height CGSize size = [self view].bounds.size; CGFloat maxSize = (size.width > size.height) ? size.width : size.height; return CGSizeMake(maxSize * 1.6f, maxSize); } } #pragma mark - Keyboard Toolbar Handlers - (void)showAdvancedKeyboardAnimated { // calc initial and final rect of the advanced keyboard view CGRect rect = [[_keyboard_toolbar superview] bounds]; rect.origin.y = [_keyboard_toolbar bounds].size.height; rect.size.height -= rect.origin.y; // create new view (hidden) and add to host-view of keyboard toolbar _advanced_keyboard_view = [[AdvancedKeyboardView alloc] initWithFrame:CGRectMake(rect.origin.x, [[_keyboard_toolbar superview] bounds].size.height, rect.size.width, rect.size.height) delegate:self]; [[_keyboard_toolbar superview] addSubview:_advanced_keyboard_view]; // we set autoresize to YES for the keyboard toolbar's superview so that our adv. keyboard view // gets properly resized [[_keyboard_toolbar superview] setAutoresizesSubviews:YES]; // show view with animation [UIView beginAnimations:nil context:NULL]; [_advanced_keyboard_view setFrame:rect]; [UIView commitAnimations]; } - (IBAction)toggleKeyboardWhenOtherVisible:(id)sender { if (_advanced_keyboard_visible == NO) { [self showAdvancedKeyboardAnimated]; } else { // hide existing view [UIView beginAnimations:@"hide_advanced_keyboard_view" context:NULL]; CGRect rect = [_advanced_keyboard_view frame]; rect.origin.y = [[_keyboard_toolbar superview] bounds].size.height; [_advanced_keyboard_view setFrame:rect]; [UIView commitAnimations]; // the view is released in the animationDidStop selector registered in init } // toggle flag _advanced_keyboard_visible = !_advanced_keyboard_visible; } - (IBAction)toggleWinKey:(id)sender { [[RDPKeyboard getSharedRDPKeyboard] toggleWinKey]; } - (IBAction)toggleShiftKey:(id)sender { [[RDPKeyboard getSharedRDPKeyboard] toggleShiftKey]; } - (IBAction)toggleCtrlKey:(id)sender { [[RDPKeyboard getSharedRDPKeyboard] toggleCtrlKey]; } - (IBAction)toggleAltKey:(id)sender { [[RDPKeyboard getSharedRDPKeyboard] toggleAltKey]; } - (IBAction)pressEscKey:(id)sender { [[RDPKeyboard getSharedRDPKeyboard] sendEscapeKeyStroke]; } #pragma mark - #pragma mark event handlers - (void)animationStopped:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { if ([animationID isEqualToString:@"hide_advanced_keyboard_view"]) { // cleanup advanced keyboard view [_advanced_keyboard_view removeFromSuperview]; [_advanced_keyboard_view autorelease]; _advanced_keyboard_view = nil; } } - (IBAction)switchSession:(id)sender { [self suspendSession]; } - (IBAction)toggleKeyboard:(id)sender { if (!_keyboard_visible) [_dummy_textfield becomeFirstResponder]; else [_dummy_textfield resignFirstResponder]; } - (IBAction)toggleExtKeyboard:(id)sender { // if the sys kb is shown but not the advanced kb then toggle the advanced kb if (_keyboard_visible && !_advanced_keyboard_visible) [self toggleKeyboardWhenOtherVisible:nil]; else { // if not visible request the advanced keyboard view if (_advanced_keyboard_visible == NO) _requesting_advanced_keyboard = YES; [self toggleKeyboard:nil]; } } - (IBAction)toggleTouchPointer:(id)sender { BOOL toggle_visibilty = ![_touchpointer_view isHidden]; [_touchpointer_view setHidden:toggle_visibilty]; if (toggle_visibilty) [_session_scrollview setContentInset:UIEdgeInsetsZero]; else [_session_scrollview setContentInset:[_touchpointer_view getEdgeInsets]]; } - (IBAction)disconnectSession:(id)sender { [_session disconnect]; } - (IBAction)cancelButtonPressed:(id)sender { [_session disconnect]; } #pragma mark - #pragma mark iOS Keyboard Notification Handlers // the keyboard is given in a portrait frame of reference - (BOOL)isLandscape { UIInterfaceOrientation ori = [[UIApplication sharedApplication] statusBarOrientation]; return (ori == UIInterfaceOrientationLandscapeLeft || ori == UIInterfaceOrientationLandscapeRight); } - (void)shiftKeyboard:(NSNotification *)notification { CGRect keyboardEndFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGFloat previousHeight = _keyboard_last_height; if ([self isLandscape]) { // landscape has the keyboard based on x, so x can go negative _keyboard_last_height = keyboardEndFrame.size.width + keyboardEndFrame.origin.x; } else { // portrait has the keyboard based on the difference of the height and the frames y. CGFloat height = [[UIScreen mainScreen] bounds].size.height; _keyboard_last_height = height - keyboardEndFrame.origin.y; } CGFloat shiftHeight = _keyboard_last_height - previousHeight; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; [UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]]; CGRect frame = [_session_scrollview frame]; frame.size.height -= shiftHeight; [_session_scrollview setFrame:frame]; [_touchpointer_view setFrame:frame]; [UIView commitAnimations]; } - (void)keyboardWillShow:(NSNotification *)notification { [self shiftKeyboard:notification]; [_touchpointer_view ensurePointerIsVisible]; } - (void)keyboardDidShow:(NSNotification *)notification { if (_requesting_advanced_keyboard) { [self showAdvancedKeyboardAnimated]; _advanced_keyboard_visible = YES; _requesting_advanced_keyboard = NO; } } - (void)keyboardWillHide:(NSNotification *)notification { [self shiftKeyboard:notification]; } - (void)keyboardDidHide:(NSNotification *)notification { // release adanced keyboard view if (_advanced_keyboard_visible == YES) { _advanced_keyboard_visible = NO; [_advanced_keyboard_view removeFromSuperview]; [_advanced_keyboard_view autorelease]; _advanced_keyboard_view = nil; } } #pragma mark - #pragma mark Gesture handlers - (void)handleSingleTap:(UITapGestureRecognizer *)gesture { CGPoint pos = [gesture locationInView:_session_view]; if (_toggle_mouse_button) { [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetRightMouseButtonClickEvent(YES) position:pos]]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetRightMouseButtonClickEvent(NO) position:pos]]; } else { [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES) position:pos]]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO) position:pos]]; } _toggle_mouse_button = NO; } - (void)handleDoubleTap:(UITapGestureRecognizer *)gesture { CGPoint pos = [gesture locationInView:_session_view]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES) position:pos]]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO) position:pos]]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES) position:pos]]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO) position:pos]]; _toggle_mouse_button = NO; } - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture { CGPoint pos = [gesture locationInView:_session_view]; if ([gesture state] == UIGestureRecognizerStateBegan) [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(YES) position:pos]]; else if ([gesture state] == UIGestureRecognizerStateChanged) [self handleMouseMoveForPosition:pos]; else if ([gesture state] == UIGestureRecognizerStateEnded) [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(NO) position:pos]]; } - (void)handleDoubleLongPress:(UILongPressGestureRecognizer *)gesture { // this point is mapped against the scroll view because we want to have relative movement to the // screen/scrollview CGPoint pos = [gesture locationInView:_session_scrollview]; if ([gesture state] == UIGestureRecognizerStateBegan) _prev_long_press_position = pos; else if ([gesture state] == UIGestureRecognizerStateChanged) { int delta = _prev_long_press_position.y - pos.y; if (delta > GetScrollGestureDelta()) { [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetMouseWheelEvent(YES) position:pos]]; _prev_long_press_position = pos; } else if (delta < -GetScrollGestureDelta()) { [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetMouseWheelEvent(NO) position:pos]]; _prev_long_press_position = pos; } } } - (void)handleSingle2FingersTap:(UITapGestureRecognizer *)gesture { _toggle_mouse_button = !_toggle_mouse_button; } - (void)handleSingle3FingersTap:(UITapGestureRecognizer *)gesture { [_session setToolbarVisible:![_session toolbarVisible]]; [self showSessionToolbar:[_session toolbarVisible]]; } #pragma mark - #pragma mark Touch Pointer delegates // callback if touch pointer should be closed - (void)touchPointerClose { [self toggleTouchPointer:nil]; } // callback for a left click action - (void)touchPointerLeftClick:(CGPoint)pos down:(BOOL)down { CGPoint session_view_pos = [_touchpointer_view convertPoint:pos toView:_session_view]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetLeftMouseButtonClickEvent(down) position:session_view_pos]]; } // callback for a right click action - (void)touchPointerRightClick:(CGPoint)pos down:(BOOL)down { CGPoint session_view_pos = [_touchpointer_view convertPoint:pos toView:_session_view]; [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetRightMouseButtonClickEvent(down) position:session_view_pos]]; } - (void)doAutoScrolling { int scrollX = 0; int scrollY = 0; CGPoint curPointerPos = [_touchpointer_view getPointerPosition]; CGRect viewBounds = [_touchpointer_view bounds]; CGRect scrollBounds = [_session_view bounds]; // add content insets to scroll bounds scrollBounds.size.width += [_session_scrollview contentInset].right; scrollBounds.size.height += [_session_scrollview contentInset].bottom; // add zoom factor scrollBounds.size.width *= [_session_scrollview zoomScale]; scrollBounds.size.height *= [_session_scrollview zoomScale]; if (curPointerPos.x > (viewBounds.size.width - [_touchpointer_view getPointerWidth])) scrollX = AUTOSCROLLDISTANCE; else if (curPointerPos.x < 0) scrollX = -AUTOSCROLLDISTANCE; if (curPointerPos.y > (viewBounds.size.height - [_touchpointer_view getPointerHeight])) scrollY = AUTOSCROLLDISTANCE; else if (curPointerPos.y < (_session_toolbar_visible ? TOOLBAR_HEIGHT : 0)) scrollY = -AUTOSCROLLDISTANCE; CGPoint newOffset = [_session_scrollview contentOffset]; newOffset.x += scrollX; newOffset.y += scrollY; // if offset is going off screen - stop scrolling in that direction if (newOffset.x < 0) { scrollX = 0; newOffset.x = 0; } else if (newOffset.x > (scrollBounds.size.width - viewBounds.size.width)) { scrollX = 0; newOffset.x = MAX(scrollBounds.size.width - viewBounds.size.width, 0); } if (newOffset.y < 0) { scrollY = 0; newOffset.y = 0; } else if (newOffset.y > (scrollBounds.size.height - viewBounds.size.height)) { scrollY = 0; newOffset.y = MAX(scrollBounds.size.height - viewBounds.size.height, 0); } // perform scrolling [_session_scrollview setContentOffset:newOffset]; // continue scrolling? if (scrollX != 0 || scrollY != 0) [self performSelector:@selector(doAutoScrolling) withObject:nil afterDelay:AUTOSCROLLTIMEOUT]; else _is_autoscrolling = NO; } // callback for a right click action - (void)touchPointerMove:(CGPoint)pos { CGPoint session_view_pos = [_touchpointer_view convertPoint:pos toView:_session_view]; [self handleMouseMoveForPosition:session_view_pos]; if (_autoscroll_with_touchpointer && !_is_autoscrolling) { _is_autoscrolling = YES; [self performSelector:@selector(doAutoScrolling) withObject:nil afterDelay:AUTOSCROLLTIMEOUT]; } } // callback if scrolling is performed - (void)touchPointerScrollDown:(BOOL)down { [_session sendInputEvent:[self eventDescriptorForMouseEvent:GetMouseWheelEvent(down) position:CGPointZero]]; } // callback for toggling the standard keyboard - (void)touchPointerToggleKeyboard { if (_advanced_keyboard_visible) [self toggleKeyboardWhenOtherVisible:nil]; else [self toggleKeyboard:nil]; } // callback for toggling the extended keyboard - (void)touchPointerToggleExtendedKeyboard { [self toggleExtKeyboard:nil]; } // callback for reset view - (void)touchPointerResetSessionView { [_session_scrollview setZoomScale:1.0 animated:YES]; } @end @implementation RDPSessionViewController (Private) #pragma mark - #pragma mark Helper functions - (void)showSessionToolbar:(BOOL)show { // already shown or hidden? if (_session_toolbar_visible == show) return; if (show) { [UIView beginAnimations:@"showToolbar" context:nil]; [UIView setAnimationDuration:.4]; [UIView setAnimationCurve:UIViewAnimationCurveLinear]; [_session_toolbar setFrame:CGRectMake(0.0, 0.0, [[self view] bounds].size.width, TOOLBAR_HEIGHT)]; [UIView commitAnimations]; _session_toolbar_visible = YES; } else { [UIView beginAnimations:@"hideToolbar" context:nil]; [UIView setAnimationDuration:.4]; [UIView setAnimationCurve:UIViewAnimationCurveLinear]; [_session_toolbar setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[self view] bounds].size.width, TOOLBAR_HEIGHT)]; [UIView commitAnimations]; _session_toolbar_visible = NO; } } - (UIToolbar *)keyboardToolbar { UIToolbar *keyboard_toolbar = [[[UIToolbar alloc] initWithFrame:CGRectNull] autorelease]; [keyboard_toolbar setBarStyle:UIBarStyleBlackOpaque]; UIBarButtonItem *esc_btn = [[[UIBarButtonItem alloc] initWithTitle:@"Esc" style:UIBarButtonItemStyleBordered target:self action:@selector(pressEscKey:)] autorelease]; UIImage *win_icon = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"toolbar_icon_win" ofType:@"png"]]; UIBarButtonItem *win_btn = [[[UIBarButtonItem alloc] initWithImage:win_icon style:UIBarButtonItemStyleBordered target:self action:@selector(toggleWinKey:)] autorelease]; UIBarButtonItem *ctrl_btn = [[[UIBarButtonItem alloc] initWithTitle:@"Ctrl" style:UIBarButtonItemStyleBordered target:self action:@selector(toggleCtrlKey:)] autorelease]; UIBarButtonItem *alt_btn = [[[UIBarButtonItem alloc] initWithTitle:@"Alt" style:UIBarButtonItemStyleBordered target:self action:@selector(toggleAltKey:)] autorelease]; UIBarButtonItem *ext_btn = [[[UIBarButtonItem alloc] initWithTitle:@"Ext" style:UIBarButtonItemStyleBordered target:self action:@selector(toggleKeyboardWhenOtherVisible:)] autorelease]; UIBarButtonItem *done_btn = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(toggleKeyboard:)] autorelease]; UIBarButtonItem *flex_spacer = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease]; // iPad gets a shift button, iphone doesn't (there's just not enough space ...) NSArray *items; if (IsPad()) { UIBarButtonItem *shift_btn = [[[UIBarButtonItem alloc] initWithTitle:@"Shift" style:UIBarButtonItemStyleBordered target:self action:@selector(toggleShiftKey:)] autorelease]; items = [NSArray arrayWithObjects:esc_btn, flex_spacer, shift_btn, flex_spacer, ctrl_btn, flex_spacer, win_btn, flex_spacer, alt_btn, flex_spacer, ext_btn, flex_spacer, done_btn, nil]; } else { items = [NSArray arrayWithObjects:esc_btn, flex_spacer, ctrl_btn, flex_spacer, win_btn, flex_spacer, alt_btn, flex_spacer, ext_btn, flex_spacer, done_btn, nil]; } [keyboard_toolbar setItems:items]; [keyboard_toolbar sizeToFit]; return keyboard_toolbar; } - (void)initGestureRecognizers { // single and double tap recognizer UITapGestureRecognizer *doubleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)] autorelease]; [doubleTapRecognizer setNumberOfTouchesRequired:1]; [doubleTapRecognizer setNumberOfTapsRequired:2]; UITapGestureRecognizer *singleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)] autorelease]; [singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer]; [singleTapRecognizer setNumberOfTouchesRequired:1]; [singleTapRecognizer setNumberOfTapsRequired:1]; // 2 fingers - tap recognizer UITapGestureRecognizer *single2FingersTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingle2FingersTap:)] autorelease]; [single2FingersTapRecognizer setNumberOfTouchesRequired:2]; [single2FingersTapRecognizer setNumberOfTapsRequired:1]; // long press gesture recognizer UILongPressGestureRecognizer *longPressRecognizer = [[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)] autorelease]; [longPressRecognizer setMinimumPressDuration:0.5]; // double long press gesture recognizer UILongPressGestureRecognizer *doubleLongPressRecognizer = [[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleLongPress:)] autorelease]; [doubleLongPressRecognizer setNumberOfTouchesRequired:2]; [doubleLongPressRecognizer setMinimumPressDuration:0.5]; // 3 finger, single tap gesture for showing/hiding the toolbar UITapGestureRecognizer *single3FingersTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingle3FingersTap:)] autorelease]; [single3FingersTapRecognizer setNumberOfTapsRequired:1]; [single3FingersTapRecognizer setNumberOfTouchesRequired:3]; // add gestures to scroll view [_session_scrollview addGestureRecognizer:singleTapRecognizer]; [_session_scrollview addGestureRecognizer:doubleTapRecognizer]; [_session_scrollview addGestureRecognizer:single2FingersTapRecognizer]; [_session_scrollview addGestureRecognizer:longPressRecognizer]; [_session_scrollview addGestureRecognizer:doubleLongPressRecognizer]; [_session_scrollview addGestureRecognizer:single3FingersTapRecognizer]; } - (void)suspendSession { // suspend session and pop navigation controller [_session suspend]; // pop current view controller [[self navigationController] popViewControllerAnimated:YES]; } - (NSDictionary *)eventDescriptorForMouseEvent:(int)event position:(CGPoint)position { return [NSDictionary dictionaryWithObjectsAndKeys:@"mouse", @"type", [NSNumber numberWithUnsignedShort:event], @"flags", [NSNumber numberWithUnsignedShort:lrintf(position.x)], @"coord_x", [NSNumber numberWithUnsignedShort:lrintf(position.y)], @"coord_y", nil]; } - (void)sendDelayedMouseEventWithTimer:(NSTimer *)timer { _mouse_move_event_timer = nil; NSDictionary *event = [timer userInfo]; [_session sendInputEvent:event]; [timer autorelease]; } - (void)handleMouseMoveForPosition:(CGPoint)position { NSDictionary *event = [self eventDescriptorForMouseEvent:PTR_FLAGS_MOVE position:position]; // cancel pending mouse move events [_mouse_move_event_timer invalidate]; _mouse_move_events_skipped++; if (_mouse_move_events_skipped >= 5) { [_session sendInputEvent:event]; _mouse_move_events_skipped = 0; } else { [_mouse_move_event_timer autorelease]; _mouse_move_event_timer = [[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(sendDelayedMouseEventWithTimer:) userInfo:event repeats:NO] retain]; } } @end