11#import <QuartzCore/QuartzCore.h>
12#import <GameController/GameController.h>
13#import <objc/runtime.h>
14#import "RDPSessionViewController.h"
15#import "RDPSessionToolbar.h"
16#import "RDPKeyboard.h"
18#import "Toast+UIView.h"
19#import "ConnectionParams.h"
20#import "CredentialsInputController.h"
21#import "VerifyCertificateController.h"
23#define TOOLBAR_HEIGHT 44
24#define ADVANCED_KEYBOARD_HEIGHT 200
26@interface RDPSessionViewController (Private)
27- (void)showSessionToolbar:(BOOL)show;
28- (UIToolbar *)keyboardToolbar;
29- (void)initGestureRecognizers;
30- (void)suspendSession;
31- (void)fitSessionViewToViewport;
32- (void)centerSessionViewInViewport;
33- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position;
34- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position;
35- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position;
36- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position;
37- (void)handleMouseMoveForPosition:(CGPoint)position;
38- (CGPoint)currentCursorViewPosition;
39- (void)moveCursorByViewportDelta:(CGPoint)delta;
40- (void)moveCursorToSessionViewPosition:(CGPoint)position;
41- (void)sendMouseButtonEvent:(
int)event;
42- (BOOL)isKeyboardActive;
47#pragma mark class methods
49- (id)initWithNibName:(NSString *)nibNameOrNil
50 bundle:(NSBundle *)nibBundleOrNil
53 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
56 _session = [session retain];
57 [_session setDelegate:self];
58 _session_initilized = NO;
60 _advanced_keyboard_view = nil;
61 _last_session_viewport_size = CGSizeZero;
63 _session_toolbar_visible = NO;
65 _cursor_view_position = CGPointZero;
66 _last_mouse_pan_location = CGPointZero;
67 _has_cursor_view_position = NO;
68 _has_user_moved_cursor = NO;
69 _mouse_pan_active = NO;
70 _long_press_active = NO;
71 _mouse_drag_active = NO;
72 _pointer_is_indirect = NO;
89 _keyboard_toolbar = [[
self keyboardToolbar] retain];
90 [_dummy_textfield setInputAccessoryView:_keyboard_toolbar];
93 [
self initGestureRecognizers];
97 setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[
self view] bounds].size.width, TOOLBAR_HEIGHT)];
105 [_session_scrollview setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
106 [_session_scrollview setContentInset:UIEdgeInsetsZero];
107 [_session_scrollview setScrollIndicatorInsets:UIEdgeInsetsZero];
108 [_session_scrollview setShowsHorizontalScrollIndicator:NO];
109 [_session_scrollview setShowsVerticalScrollIndicator:NO];
110 [_session_scrollview setAlwaysBounceHorizontal:NO];
111 [_session_scrollview setAlwaysBounceVertical:NO];
112 [_session_scrollview setBounces:NO];
115- (void)viewDidLayoutSubviews
117 [
super viewDidLayoutSubviews];
119 CGRect viewportFrame = [[
self view] bounds];
120 [_session_scrollview setFrame:viewportFrame];
122 CGSize viewportSize = [_session_scrollview bounds].size;
123 if (!CGSizeEqualToSize(viewportSize, _last_session_viewport_size))
124 [
self fitSessionViewToViewport];
126 [
self centerSessionViewInViewport];
129- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
134- (BOOL)prefersStatusBarHidden
139- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
141 return UIStatusBarAnimationSlide;
144- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
146 (void)fromInterfaceOrientation;
147 [
self centerSessionViewInViewport];
150- (void)didReceiveMemoryWarning
153 [
super didReceiveMemoryWarning];
160 [
super viewDidUnload];
165- (void)viewWillAppear:(BOOL)animated
167 [
super viewWillAppear:animated];
170 [
self setNeedsStatusBarAppearanceUpdate];
171 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
172 [[
self navigationController] setNavigationBarHidden:YES animated:animated];
173 if (@available(iOS 18.0, *))
174 [[
self tabBarController] setTabBarHidden:YES animated:animated];
176 [[[
self tabBarController] tabBar] setHidden:YES];
179 if ([_session isSuspended])
180 [
self sessionBitmapContextWillChange:_session];
183 [[
RDPKeyboard getSharedRDPKeyboard] initWithSession:_session delegate:self];
186- (void)viewDidAppear:(BOOL)animated
188 [
super viewDidAppear:animated];
190 if (!_session_initilized)
192 if ([_session isSuspended])
195 [
self sessionBitmapContextDidChange:_session];
200 _session_initilized = YES;
204- (void)viewWillDisappear:(BOOL)animated
206 [
super viewWillDisappear:animated];
207 if (_mouse_drag_active)
208 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
209 _mouse_drag_active = NO;
210 _mouse_pan_active = NO;
211 _long_press_active = NO;
213 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
214 [[
self navigationController] setNavigationBarHidden:NO animated:animated];
215 if (@available(iOS 18.0, *))
216 [[
self tabBarController] setTabBarHidden:NO animated:animated];
218 [[[
self tabBarController] tabBar] setHidden:NO];
224 [
self showSessionToolbar:NO];
225 [_dummy_textfield resignFirstResponder];
231 [[NSNotificationCenter defaultCenter] removeObserver:self];
234 [_session setDelegate:nil];
236 [_advanced_keyboard_view release];
237 [_keyboard_toolbar release];
243#pragma mark ScrollView delegate methods
245- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
247 return _session_view;
250- (void)scrollViewDidZoom:(UIScrollView *)scrollView
252 [
self centerSessionViewInViewport];
255- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView
256 withView:(UIView *)view
257 atScale:(CGFloat)scale
259 NSLog(
@"New zoom scale: %f", scale);
260 [
self centerSessionViewInViewport];
261 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
265#pragma mark TextField delegate methods
266- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
271- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
273 [
self destroyAdvancedKeyboard];
277- (BOOL)textField:(UITextField *)textField
278 shouldChangeCharactersInRange:(NSRange)range
279 replacementString:(NSString *)string
281 if ([
string length] > 0)
283 for (
int i = 0; i < [string length]; i++)
285 unichar curChar = [string characterAtIndex:i];
289 [[
RDPKeyboard getSharedRDPKeyboard] sendEnterKeyStroke];
291 [[
RDPKeyboard getSharedRDPKeyboard] sendUnicode:curChar];
296 [[
RDPKeyboard getSharedRDPKeyboard] sendBackspaceKeyStroke];
303#pragma mark AdvancedKeyboardDelegate functions
304- (void)advancedKeyPressedVKey:(NSInteger)key
306 [[
RDPKeyboard getSharedRDPKeyboard] sendVirtualKeyCode:key];
309- (void)advancedKeyPressedUnicode:(NSInteger)key
311 [[
RDPKeyboard getSharedRDPKeyboard] sendUnicode:key];
314#pragma mark - RDP keyboard handler
316- (void)modifiersChangedForKeyboard:(
RDPKeyboard *)keyboard
318 UIBarButtonItem *curItem;
325 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
326 [curItem setStyle:[keyboard shiftPressed] ? UIBarButtonItemStyleDone
327 : UIBarButtonItemStylePlain];
332 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
334 setStyle:[keyboard ctrlPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
338 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
339 [curItem setStyle:[keyboard winPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
343 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
344 [curItem setStyle:[keyboard altPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
348#pragma mark RDPSessionDelegate functions
350- (void)session:(
RDPSession *)session didFailToConnect:(
int)reason
353 [_connecting_indicator_view stopAnimating];
354 [_connecting_view removeFromSuperview];
355 [_connecting_view autorelease];
358 [[
self navigationController] popViewControllerAnimated:YES];
361- (void)sessionWillConnect:(
RDPSession *)session
364 [[NSBundle mainBundle] loadNibNamed:@"RDPConnectingView" owner:self options:nil];
367 [_lbl_connecting setText:NSLocalizedString(@"Connecting", @"Connecting progress view - label")];
368 [_cancel_connect_button setTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
369 forState:UIControlStateNormal];
372 [_connecting_view setCenter:[[
self view] center]];
373 [[_connecting_view layer] setCornerRadius:10];
376 [[
self view] addSubview:_connecting_view];
377 [_connecting_indicator_view startAnimating];
380- (void)sessionDidConnect:(
RDPSession *)session
383 [[NSNotificationCenter defaultCenter] addObserver:self
384 selector:@selector(keyboardWillShow:)
385 name:UIKeyboardWillShowNotification
387 [[NSNotificationCenter defaultCenter] addObserver:self
388 selector:@selector(keyboardWillHide:)
389 name:UIKeyboardWillHideNotification
391 [[NSNotificationCenter defaultCenter] addObserver:self
392 selector:@selector(keyboardDidHide:)
393 name:UIKeyboardDidHideNotification
397 [[NSNotificationCenter defaultCenter] addObserver:self
398 selector:@selector(hardwareKeyboardChanged:)
399 name:GCKeyboardDidConnectNotification
401 [[NSNotificationCenter defaultCenter] addObserver:self
402 selector:@selector(hardwareKeyboardChanged:)
403 name:GCKeyboardDidDisconnectNotification
405 [
self hardwareKeyboardChanged:nil];
408 [_connecting_indicator_view stopAnimating];
409 [_connecting_view removeFromSuperview];
410 [_connecting_view autorelease];
416 rdpSettings *sess_params = [session getSessionParams];
421 if (([orig_params intForKey:
@"width"] != width &&
422 [orig_params intForKey:
@"width"] != (width + 1ul)) ||
423 [orig_params intForKey:
@"height"] != height || [orig_params intForKey:
@"colors"] != depth)
427 [NSString stringWithFormat:NSLocalizedString(
428 @"The server changed the screen settings to %dx%dx%d",
429 @"Screen settings not supported message with width, "
430 @"height and colors parameter"),
431 width, height, depth];
432 [[
self view] makeToast:message duration:ToastDurationNormal position:@"bottom"];
436- (void)sessionWillDisconnect:(
RDPSession *)session
440- (void)sessionDidDisconnect:(
RDPSession *)session
443 [[
self navigationController] popViewControllerAnimated:YES];
446- (void)sessionBitmapContextWillChange:(
RDPSession *)session
448 [_session_view prepareForBitmapContextChange];
451 rdpSettings *sess_params = [session getSessionParams];
457 [_session_scrollview setZoomScale:1.0];
458 [_session_view setFrame:view_rect];
459 [_session_scrollview setContentSize:view_rect.size];
460 _has_cursor_view_position = NO;
461 _has_user_moved_cursor = NO;
462 _last_session_viewport_size = CGSizeZero;
463 [
self fitSessionViewToViewport];
467 setToolbarVisible:![[NSUserDefaults standardUserDefaults] boolForKey:@"ui.hide_tool_bar"]];
468 [
self showSessionToolbar:[_session toolbarVisible]];
471- (void)sessionBitmapContextDidChange:(
RDPSession *)session
474 [_session_view setSession:session];
475 [_session_view setDefaultRemoteCursor];
476 if (!_has_cursor_view_position)
477 (void)[
self currentCursorViewPosition];
480 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
483- (void)session:(
RDPSession *)session needsRedrawInRect:(CGRect)rect
485 [_session_view setNeedsDisplayInRemoteRect:rect];
491 [_session_view setRemoteCursor:cursor];
493 if (!_has_user_moved_cursor &&
494 (!_has_cursor_view_position || CGPointEqualToPoint(_cursor_view_position, CGPointZero)))
496 _has_cursor_view_position = NO;
497 (void)[
self currentCursorViewPosition];
501- (void)session:(
RDPSession *)session didMoveRemoteCursor:(CGPoint)position
504 if (_mouse_pan_active || _long_press_active)
506 CGPoint viewPosition = [
self sessionViewPositionForRemotePosition:position];
507 if (!_has_user_moved_cursor && CGPointEqualToPoint(position, CGPointZero) &&
508 _has_cursor_view_position && !CGPointEqualToPoint(_cursor_view_position, CGPointZero))
511 _cursor_view_position = viewPosition;
512 _has_cursor_view_position = YES;
513 [_session_view setRemoteCursorPosition:viewPosition];
516- (void)sessionDidHideRemoteCursor:(
RDPSession *)session
519 [_session_view hideRemoteCursor];
522- (void)sessionDidSetDefaultRemoteCursor:(
RDPSession *)session
525 [_session_view setDefaultRemoteCursor];
528- (void)session:(
RDPSession *)session requestsAuthenticationWithParams:(NSMutableDictionary *)params
534 params:params] autorelease];
535 [
self presentViewController:view_controller animated:YES completion:nil];
538- (void)session:(
RDPSession *)session verifyCertificateWithParams:(NSMutableDictionary *)params
544 params:params] autorelease];
545 [
self presentViewController:view_controller animated:YES completion:nil];
548- (CGSize)sizeForFitScreenForSession:(
RDPSession *)session
551 CGSize size = [
self view].bounds.size;
552 UIScreen *screen = [[
self view] window] ? [[[
self view] window] screen] : [UIScreen mainScreen];
554 [screen respondsToSelector:@selector(nativeScale)] ? [screen nativeScale] : [screen scale];
558 size.width = ceilf(size.width * scale);
559 size.height = ceilf(size.height * scale);
561 CGFloat maxDimension = MAX(size.width, size.height);
562 if (maxDimension > 4096.0f)
564 CGFloat downscale = 4096.0f / maxDimension;
565 size.width = floorf(size.width * downscale);
566 size.height = floorf(size.height * downscale);
569 size.width = MAX(64.0f, size.width);
570 size.height = MAX(64.0f, size.height);
574#pragma mark - Keyboard Toolbar Handlers
576- (void)showAdvancedKeyboardAnimated
579 if (_advanced_keyboard_view)
582 CGRect rect = CGRectMake(0, 0, [[
self view] bounds].size.width, ADVANCED_KEYBOARD_HEIGHT);
585 [_dummy_textfield setInputView:_advanced_keyboard_view];
586 [_dummy_textfield reloadInputViews];
589- (void)destroyAdvancedKeyboard
592 if (!_advanced_keyboard_view)
595 [_dummy_textfield setInputView:nil];
596 [_advanced_keyboard_view autorelease];
597 _advanced_keyboard_view = nil;
601- (IBAction)toggleKeyboardWhenOtherVisible:(
id)sender
603 if (!_advanced_keyboard_view)
606 [
self showAdvancedKeyboardAnimated];
611 [
self destroyAdvancedKeyboard];
612 [_dummy_textfield reloadInputViews];
616- (IBAction)toggleWinKey:(
id)sender
621- (IBAction)toggleShiftKey:(
id)sender
623 [[
RDPKeyboard getSharedRDPKeyboard] toggleShiftKey];
626- (IBAction)toggleCtrlKey:(
id)sender
628 [[
RDPKeyboard getSharedRDPKeyboard] toggleCtrlKey];
631- (IBAction)toggleAltKey:(
id)sender
636- (IBAction)pressEscKey:(
id)sender
638 [[
RDPKeyboard getSharedRDPKeyboard] sendEscapeKeyStroke];
642#pragma mark event handlers
644- (IBAction)switchSession:(
id)sender
646 [
self suspendSession];
649- (IBAction)toggleKeyboard:(
id)sender
651 if (![
self isKeyboardActive])
652 [_dummy_textfield becomeFirstResponder];
654 [_dummy_textfield resignFirstResponder];
657- (IBAction)toggleExtKeyboard:(
id)sender
660 if ([
self isKeyboardActive] && !_advanced_keyboard_view)
661 [
self toggleKeyboardWhenOtherVisible:nil];
664 if (![
self isKeyboardActive])
665 [
self showAdvancedKeyboardAnimated];
666 [
self toggleKeyboard:nil];
670- (IBAction)disconnectSession:(
id)sender
672 [_session disconnect];
675- (IBAction)cancelButtonPressed:(
id)sender
677 [_session disconnect];
681#pragma mark iOS Keyboard Notification Handlers
683- (void)keyboardWillShow:(NSNotification *)notification
686 [
self centerSessionViewInViewport];
689- (void)keyboardWillHide:(NSNotification *)notification
692 [
self centerSessionViewInViewport];
695- (void)keyboardDidHide:(NSNotification *)notification
698 if (![
self isKeyboardActive])
699 [
self destroyAdvancedKeyboard];
702 if (![
self isKeyboardActive] && [_session_view hardwareKeyboardActive])
703 [_session_view becomeFirstResponder];
706- (void)hardwareKeyboardChanged:(NSNotification *)notification
708 BOOL connected = (GCKeyboard.coalescedKeyboard != nil);
709 [_session_view setHardwareKeyboardActive:connected];
711 if (connected && ![
self isKeyboardActive])
712 [_session_view becomeFirstResponder];
714 [_session_view resignFirstResponder];
718#pragma mark Gesture handlers
720- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
722 if (_pointer_is_indirect)
723 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
724 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
725 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
728- (void)handleSecondaryTap:(UITapGestureRecognizer *)gesture
730 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
731 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
732 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
735- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
737 if ([gesture state] == UIGestureRecognizerStateBegan)
739 _long_press_active = YES;
740 _mouse_drag_active = NO;
742 else if ([gesture state] == UIGestureRecognizerStateEnded ||
743 [gesture state] == UIGestureRecognizerStateCancelled ||
744 [gesture state] == UIGestureRecognizerStateFailed)
746 if (_mouse_drag_active)
747 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
748 else if ([gesture state] == UIGestureRecognizerStateEnded)
750 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
751 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
754 _mouse_drag_active = NO;
755 _long_press_active = NO;
759- (void)handleMousePan:(UIPanGestureRecognizer *)gesture
763 if (_pointer_is_indirect)
765 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
767 if ([gesture state] == UIGestureRecognizerStateBegan)
769 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
770 _mouse_drag_active = YES;
772 else if ([gesture state] == UIGestureRecognizerStateEnded ||
773 [gesture state] == UIGestureRecognizerStateCancelled ||
774 [gesture state] == UIGestureRecognizerStateFailed)
776 if (_mouse_drag_active)
777 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
778 _mouse_drag_active = NO;
783 CGPoint location = [gesture locationInView:_session_scrollview];
785 if ([gesture state] == UIGestureRecognizerStateBegan)
787 _mouse_pan_active = YES;
788 _last_mouse_pan_location = location;
789 if (_long_press_active && !_mouse_drag_active)
791 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
792 _mouse_drag_active = YES;
795 else if ([gesture state] == UIGestureRecognizerStateChanged)
797 if (_long_press_active && !_mouse_drag_active)
799 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
800 _mouse_drag_active = YES;
803 CGPoint delta = CGPointMake(location.x - _last_mouse_pan_location.x,
804 location.y - _last_mouse_pan_location.y);
805 [
self moveCursorByViewportDelta:delta];
806 _last_mouse_pan_location = location;
808 else if ([gesture state] == UIGestureRecognizerStateEnded ||
809 [gesture state] == UIGestureRecognizerStateCancelled ||
810 [gesture state] == UIGestureRecognizerStateFailed)
812 _mouse_pan_active = NO;
816- (void)handleHover:(UIHoverGestureRecognizer *)gesture
818 if ([gesture state] != UIGestureRecognizerStateBegan &&
819 [gesture state] != UIGestureRecognizerStateChanged)
822 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
825- (void)handleDoubleLongPress:(UILongPressGestureRecognizer *)gesture
829 CGPoint pos = [gesture locationInView:_session_scrollview];
830 CGPoint session_view_pos = [
self currentCursorViewPosition];
832 if ([gesture state] == UIGestureRecognizerStateBegan)
833 _prev_long_press_position = pos;
834 else if ([gesture state] == UIGestureRecognizerStateChanged)
836 int delta = _prev_long_press_position.y - pos.y;
838 if (delta > GetScrollGestureDelta())
840 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(YES)
841 position:session_view_pos]];
842 _prev_long_press_position = pos;
844 else if (delta < -GetScrollGestureDelta())
846 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(NO)
847 position:session_view_pos]];
848 _prev_long_press_position = pos;
853- (void)handleScroll:(UIPanGestureRecognizer *)gesture
855 CGFloat delta = [gesture translationInView:_session_view].y;
856 if (fabs(delta) < GetScrollGestureDelta())
859 CGPoint position = [
self currentCursorViewPosition];
860 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(delta > 0)
862 [gesture setTranslation:CGPointZero inView:_session_view];
865- (void)handleSingle3FingersTap:(UITapGestureRecognizer *)gesture
867 [_session setToolbarVisible:![_session toolbarVisible]];
868 [
self showSessionToolbar:[_session toolbarVisible]];
871- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction
872 styleForRegion:(UIPointerRegion *)region
874 return [UIPointerStyle hiddenPointerStyle];
877- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
878 shouldReceiveTouch:(UITouch *)touch
881 if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
882 [(UIPanGestureRecognizer *)gestureRecognizer allowedScrollTypesMask] != 0)
885 _pointer_is_indirect = ([touch type] == UITouchTypeIndirectPointer);
889 if (_pointer_is_indirect &&
890 [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
896- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
897 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
900 ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
901 [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) ||
902 ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] &&
903 [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]);
909@implementation RDPSessionViewController (Private)
912#pragma mark Helper functions
914- (void)fitSessionViewToViewport
916 CGSize viewportSize = [_session_scrollview bounds].size;
917 CGSize remoteSize = [_session_view bounds].size;
918 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f || remoteSize.width <= 0.0f ||
919 remoteSize.height <= 0.0f)
923 MIN(viewportSize.width / remoteSize.width, viewportSize.height / remoteSize.height);
924 [_session_scrollview setMinimumZoomScale:fitScale];
925 [_session_scrollview setMaximumZoomScale:MAX(2.0f, fitScale)];
926 [_session_scrollview setZoomScale:fitScale animated:NO];
927 [_session_scrollview setContentOffset:CGPointZero animated:NO];
928 _last_session_viewport_size = viewportSize;
929 [
self centerSessionViewInViewport];
932- (void)centerSessionViewInViewport
934 CGSize viewportSize = [_session_scrollview bounds].size;
935 CGRect sessionFrame = [_session_view frame];
936 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f ||
937 sessionFrame.size.width <= 0.0f || sessionFrame.size.height <= 0.0f)
940 sessionFrame.origin.x = MAX((viewportSize.width - sessionFrame.size.width) * 0.5f, 0.0f);
941 sessionFrame.origin.y = MAX((viewportSize.height - sessionFrame.size.height) * 0.5f, 0.0f);
942 [_session_view setFrame:sessionFrame];
945 setContentSize:CGSizeMake(MAX(viewportSize.width, sessionFrame.size.width),
946 MAX(viewportSize.height, sessionFrame.size.height))];
949- (void)showSessionToolbar:(BOOL)show
952 if (_session_toolbar_visible == show)
958 UIEdgeInsets safe = [[
self view] safeAreaInsets];
959 CGFloat toolbarWidth = [[
self view] bounds].size.width - safe.left - safe.right;
963 [UIView animateWithDuration:0.4
965 options:UIViewAnimationOptionCurveLinear
967 [_session_toolbar setFrame:CGRectMake(safe.left, safe.top,
968 toolbarWidth, TOOLBAR_HEIGHT)];
971 _session_toolbar_visible = YES;
975 [UIView animateWithDuration:0.4
977 options:UIViewAnimationOptionCurveLinear
979 [_session_toolbar setFrame:CGRectMake(safe.left, -TOOLBAR_HEIGHT,
980 toolbarWidth, TOOLBAR_HEIGHT)];
983 _session_toolbar_visible = NO;
987- (UIToolbar *)keyboardToolbar
989 UIToolbar *keyboard_toolbar = [[[UIToolbar alloc] initWithFrame:CGRectNull] autorelease];
990 [keyboard_toolbar setBarStyle:UIBarStyleBlack];
992 UIBarButtonItem *esc_btn =
993 [[[UIBarButtonItem alloc] initWithTitle:@"Esc"
994 style:UIBarButtonItemStylePlain
996 action:@selector(pressEscKey:)] autorelease];
998 [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"toolbar_icon_win"
1000 UIBarButtonItem *win_btn =
1001 [[[UIBarButtonItem alloc] initWithImage:win_icon
1002 style:UIBarButtonItemStylePlain
1004 action:@selector(toggleWinKey:)] autorelease];
1005 UIBarButtonItem *ctrl_btn =
1006 [[[UIBarButtonItem alloc] initWithTitle:@"Ctrl"
1007 style:UIBarButtonItemStylePlain
1009 action:@selector(toggleCtrlKey:)] autorelease];
1010 UIBarButtonItem *alt_btn =
1011 [[[UIBarButtonItem alloc] initWithTitle:@"Alt"
1012 style:UIBarButtonItemStylePlain
1014 action:@selector(toggleAltKey:)] autorelease];
1015 UIBarButtonItem *ext_btn = [[[UIBarButtonItem alloc]
1016 initWithTitle:@"Ext"
1017 style:UIBarButtonItemStylePlain
1019 action:@selector(toggleKeyboardWhenOtherVisible:)] autorelease];
1020 UIBarButtonItem *done_btn = [[[UIBarButtonItem alloc]
1021 initWithBarButtonSystemItem:UIBarButtonSystemItemDone
1023 action:@selector(toggleKeyboard:)] autorelease];
1024 UIBarButtonItem *flex_spacer =
1025 [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
1027 action:nil] autorelease];
1033 UIBarButtonItem *shift_btn =
1034 [[[UIBarButtonItem alloc] initWithTitle:@"Shift"
1035 style:UIBarButtonItemStylePlain
1037 action:@selector(toggleShiftKey:)] autorelease];
1038 items = [NSArray arrayWithObjects:esc_btn, flex_spacer, shift_btn, flex_spacer, ctrl_btn,
1039 flex_spacer, win_btn, flex_spacer, alt_btn, flex_spacer,
1040 ext_btn, flex_spacer, done_btn, nil];
1044 items = [NSArray arrayWithObjects:esc_btn, flex_spacer, ctrl_btn, flex_spacer, win_btn,
1045 flex_spacer, alt_btn, flex_spacer, ext_btn, flex_spacer,
1049 [keyboard_toolbar setItems:items];
1050 [keyboard_toolbar sizeToFit];
1051 return keyboard_toolbar;
1054- (void)initGestureRecognizers
1058 UITapGestureRecognizer *singleTapRecognizer =
1059 [[[UITapGestureRecognizer alloc] initWithTarget:self
1060 action:@selector(handleSingleTap:)] autorelease];
1061 [singleTapRecognizer setNumberOfTouchesRequired:1];
1062 [singleTapRecognizer setNumberOfTapsRequired:1];
1063 [singleTapRecognizer setDelegate:self];
1066 UILongPressGestureRecognizer *longPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1068 action:@selector(handleLongPress:)] autorelease];
1069 [longPressRecognizer setMinimumPressDuration:0.45];
1070 [longPressRecognizer setAllowableMovement:12.0];
1071 [longPressRecognizer setDelegate:self];
1074 UIPanGestureRecognizer *mousePanRecognizer =
1075 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1076 action:@selector(handleMousePan:)] autorelease];
1077 [mousePanRecognizer setMinimumNumberOfTouches:1];
1078 [mousePanRecognizer setMaximumNumberOfTouches:1];
1079 [mousePanRecognizer setDelegate:self];
1082 UIHoverGestureRecognizer *hoverRecognizer =
1083 [[[UIHoverGestureRecognizer alloc] initWithTarget:self
1084 action:@selector(handleHover:)] autorelease];
1085 [hoverRecognizer setDelegate:self];
1089 UITapGestureRecognizer *secondaryTapRecognizer = [[[UITapGestureRecognizer alloc]
1091 action:@selector(handleSecondaryTap:)] autorelease];
1092 [secondaryTapRecognizer setButtonMaskRequired:UIEventButtonMaskSecondary];
1093 [secondaryTapRecognizer setAllowedTouchTypes:@[@(UITouchTypeIndirectPointer)]];
1094 [secondaryTapRecognizer setDelegate:self];
1098 UIPanGestureRecognizer *scrollRecognizer =
1099 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1100 action:@selector(handleScroll:)] autorelease];
1101 [scrollRecognizer setAllowedScrollTypesMask:UIScrollTypeMaskAll];
1102 [scrollRecognizer setDelegate:self];
1105 UILongPressGestureRecognizer *doubleLongPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1107 action:@selector(handleDoubleLongPress:)] autorelease];
1108 [doubleLongPressRecognizer setNumberOfTouchesRequired:2];
1109 [doubleLongPressRecognizer setMinimumPressDuration:0.5];
1112 UITapGestureRecognizer *single3FingersTapRecognizer = [[[UITapGestureRecognizer alloc]
1114 action:@selector(handleSingle3FingersTap:)] autorelease];
1115 [single3FingersTapRecognizer setNumberOfTapsRequired:1];
1116 [single3FingersTapRecognizer setNumberOfTouchesRequired:3];
1117 [singleTapRecognizer requireGestureRecognizerToFail:longPressRecognizer];
1120 [[_session_scrollview panGestureRecognizer] setMinimumNumberOfTouches:2];
1123 [_session_scrollview addGestureRecognizer:singleTapRecognizer];
1124 [_session_scrollview addGestureRecognizer:longPressRecognizer];
1125 [_session_scrollview addGestureRecognizer:mousePanRecognizer];
1126 [_session_scrollview addGestureRecognizer:hoverRecognizer];
1127 [_session_scrollview addGestureRecognizer:secondaryTapRecognizer];
1128 [_session_scrollview addGestureRecognizer:scrollRecognizer];
1129 [_session_scrollview addGestureRecognizer:doubleLongPressRecognizer];
1130 [_session_scrollview addGestureRecognizer:single3FingersTapRecognizer];
1133 UIPointerInteraction *pointerInteraction =
1134 [[[UIPointerInteraction alloc] initWithDelegate:self] autorelease];
1135 [_session_view addInteraction:pointerInteraction];
1138- (void)suspendSession
1144 [[
self navigationController] popViewControllerAnimated:YES];
1147- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position
1149 CGPoint remote_position = [
self remotePositionForSessionViewPosition:position];
1150 return [NSDictionary
1151 dictionaryWithObjectsAndKeys:@"mouse", @"type", [NSNumber numberWithUnsignedShort:event],
1153 [NSNumber numberWithUnsignedShort:lrintf(remote_position.x)],
1155 [NSNumber numberWithUnsignedShort:lrintf(remote_position.y)],
1159- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position
1161 rdpSettings *settings = [_session getSessionParams];
1162 CGSize viewSize = [_session_view bounds].size;
1163 CGFloat desktopWidth =
1165 CGFloat desktopHeight =
1168 if ((viewSize.width > 0.0f) && (desktopWidth > 0.0f))
1169 position.x = position.x * desktopWidth / viewSize.width;
1170 if ((viewSize.height > 0.0f) && (desktopHeight > 0.0f))
1171 position.y = position.y * desktopHeight / viewSize.height;
1173 if (desktopWidth > 0.0f)
1174 position.x = MIN(MAX(position.x, 0.0f), desktopWidth - 1.0f);
1175 if (desktopHeight > 0.0f)
1176 position.y = MIN(MAX(position.y, 0.0f), desktopHeight - 1.0f);
1181- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position
1183 rdpSettings *settings = [_session getSessionParams];
1184 CGSize viewSize = [_session_view bounds].size;
1185 CGFloat desktopWidth =
1187 CGFloat desktopHeight =
1190 if ((desktopWidth > 0.0f) && (viewSize.width > 0.0f))
1191 position.x = position.x * viewSize.width / desktopWidth;
1192 if ((desktopHeight > 0.0f) && (viewSize.height > 0.0f))
1193 position.y = position.y * viewSize.height / desktopHeight;
1195 return [
self clampedSessionViewCursorPosition:position];
1198- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position
1200 CGSize viewSize = [_session_view bounds].size;
1201 if (viewSize.width > 0.0f)
1202 position.x = MIN(MAX(position.x, 0.0f), viewSize.width - 1.0f);
1203 if (viewSize.height > 0.0f)
1204 position.y = MIN(MAX(position.y, 0.0f), viewSize.height - 1.0f);
1208- (CGPoint)currentCursorViewPosition
1210 if (!_has_cursor_view_position)
1212 CGSize viewSize = [_session_view bounds].size;
1213 _cursor_view_position = CGPointMake(MAX(viewSize.width - 1.0, 0.0) * 0.5,
1214 MAX(viewSize.height - 1.0, 0.0) * 0.5);
1215 _has_cursor_view_position = YES;
1216 [_session_view setRemoteCursorPosition:_cursor_view_position];
1218 [_session_view showRemoteCursor];
1220 return _cursor_view_position;
1223- (void)moveCursorByViewportDelta:(CGPoint)delta
1225 CGPoint position = [
self currentCursorViewPosition];
1226 CGFloat zoomScale = [_session_scrollview zoomScale];
1227 if (zoomScale <= 0.0)
1230 position.x += delta.x / zoomScale;
1231 position.y += delta.y / zoomScale;
1232 position = [
self clampedSessionViewCursorPosition:position];
1233 _cursor_view_position = position;
1234 _has_cursor_view_position = YES;
1235 _has_user_moved_cursor = YES;
1238 [_session_view setRemoteCursorPosition:position];
1239 [
self handleMouseMoveForPosition:position];
1242- (void)moveCursorToSessionViewPosition:(CGPoint)position
1244 position = [
self clampedSessionViewCursorPosition:position];
1245 _cursor_view_position = position;
1246 _has_cursor_view_position = YES;
1247 _has_user_moved_cursor = YES;
1250 [_session_view setRemoteCursorPosition:position];
1251 [_session_view showRemoteCursor];
1252 [
self handleMouseMoveForPosition:position];
1255- (void)sendMouseButtonEvent:(
int)event
1257 CGPoint position = [
self currentCursorViewPosition];
1258 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:event position:position]];
1261- (void)handleMouseMoveForPosition:(CGPoint)position
1263 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:PTR_FLAGS_MOVE position:position]];
1266- (BOOL)isKeyboardActive
1268 return [_dummy_textfield isFirstResponder];
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.