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 "RDPOverlayStyle.h"
17#import "RDPKeyboard.h"
19#import "Toast+UIView.h"
20#import "ConnectionParams.h"
22#define TOOLBAR_HEIGHT 44
23#define KEYBOARD_CURSOR_MARGIN 44.0
25@interface RDPSessionViewController (Private)
26- (void)showSessionToolbar:(BOOL)show;
27- (void)initGestureRecognizers;
28- (void)suspendSession;
29- (void)fitSessionViewToViewport;
30- (void)centerSessionViewInViewport;
31- (void)setKeyboardOverlap:(CGFloat)overlap;
32- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position;
33- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position;
34- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position;
35- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position;
36- (void)handleMouseMoveForPosition:(CGPoint)position;
37- (CGPoint)currentCursorViewPosition;
38- (void)moveCursorByViewportDelta:(CGPoint)delta;
39- (void)moveCursorToSessionViewPosition:(CGPoint)position;
40- (void)sendMouseButtonEvent:(
int)event;
41- (BOOL)isKeyboardActive;
43- (CGFloat)advancedKeyboardHeight;
44- (void)resetTextInput;
45- (void)commitTextInput;
49@interface RDPSessionTextField : UITextField
52@implementation RDPSessionTextField
57 if ([[
self text] length] == 0)
58 [[
RDPKeyboard getSharedRDPKeyboard] sendBackspaceKeyStroke];
59 [
super deleteBackward];
66#pragma mark class methods
68- (id)initWithNibName:(NSString *)nibNameOrNil
69 bundle:(NSBundle *)nibBundleOrNil
72 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
75 _session = [session retain];
76 [_session setDelegate:self];
77 _session_initilized = NO;
79 _advanced_keyboard_view = nil;
80 _sent_text = [@"" retain];
81 _last_session_viewport_size = CGSizeZero;
83 _session_toolbar_visible = NO;
85 _cursor_view_position = CGPointZero;
86 _last_mouse_pan_location = CGPointZero;
87 _has_cursor_view_position = NO;
88 _has_user_moved_cursor = NO;
89 _mouse_pan_active = NO;
90 _long_press_active = NO;
91 _mouse_drag_active = NO;
92 _pointer_is_indirect = NO;
107 if (RDPSupportLiquidGlass() == NO)
110 UIToolbarAppearance *appearance = [[[UIToolbarAppearance alloc] init] autorelease];
113 [appearance configureWithTransparentBackground];
114 [_session_toolbar setStandardAppearance:appearance];
115 [_session_toolbar setCompactAppearance:appearance];
116 [_session_toolbar setScrollEdgeAppearance:appearance];
117 [_session_toolbar setCompactScrollEdgeAppearance:appearance];
122 [_dummy_textfield setInputAccessoryView:_keyboard_toolbar];
125 [_dummy_textfield setAutocapitalizationType:UITextAutocapitalizationTypeNone];
126 [_dummy_textfield setSpellCheckingType:UITextSpellCheckingTypeNo];
127 [_dummy_textfield setSmartQuotesType:UITextSmartQuotesTypeNo];
128 [_dummy_textfield setSmartDashesType:UITextSmartDashesTypeNo];
129 [_dummy_textfield setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo];
130 [_dummy_textfield addTarget:self
131 action:@selector(textInputChanged:)
132 forControlEvents:UIControlEventEditingChanged];
136 [[_dummy_textfield inputAssistantItem] setLeadingBarButtonGroups:@[]];
137 [[_dummy_textfield inputAssistantItem] setTrailingBarButtonGroups:@[]];
140 [
self initGestureRecognizers];
144 setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[
self view] bounds].size.width, TOOLBAR_HEIGHT)];
152 [_session_scrollview setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
153 [_session_scrollview setContentInset:UIEdgeInsetsZero];
154 [_session_scrollview setScrollIndicatorInsets:UIEdgeInsetsZero];
155 [_session_scrollview setShowsHorizontalScrollIndicator:NO];
156 [_session_scrollview setShowsVerticalScrollIndicator:NO];
157 [_session_scrollview setAlwaysBounceHorizontal:NO];
158 [_session_scrollview setAlwaysBounceVertical:NO];
159 [_session_scrollview setBounces:NO];
162- (void)viewDidLayoutSubviews
164 [
super viewDidLayoutSubviews];
166 CGRect viewportFrame = [[
self view] bounds];
167 [_session_scrollview setFrame:viewportFrame];
169 CGSize viewportSize = [_session_scrollview bounds].size;
170 if (!CGSizeEqualToSize(viewportSize, _last_session_viewport_size))
171 [
self fitSessionViewToViewport];
173 [
self centerSessionViewInViewport];
176- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
181- (BOOL)prefersStatusBarHidden
186- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
188 return UIStatusBarAnimationSlide;
191- (void)viewWillTransitionToSize:(CGSize)size
192 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator
194 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
198 animateAlongsideTransition:nil
199 completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
200 if (!_advanced_keyboard_view)
202 CGRect frame = [_advanced_keyboard_view frame];
203 frame.size.height = [
self advancedKeyboardHeight];
204 [_advanced_keyboard_view setFrame:frame];
205 [_dummy_textfield reloadInputViews];
209- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
211 (void)fromInterfaceOrientation;
212 [
self centerSessionViewInViewport];
215- (void)didReceiveMemoryWarning
218 [
super didReceiveMemoryWarning];
225 [
super viewDidUnload];
230- (void)viewWillAppear:(BOOL)animated
232 [
super viewWillAppear:animated];
235 [
self setNeedsStatusBarAppearanceUpdate];
236 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
237 [[
self navigationController] setNavigationBarHidden:YES animated:animated];
238 if (@available(iOS 18.0, *))
239 [[
self tabBarController] setTabBarHidden:YES animated:animated];
241 [[[
self tabBarController] tabBar] setHidden:YES];
244 if ([_session isSuspended])
245 [
self sessionBitmapContextWillChange:_session];
248 [[
RDPKeyboard getSharedRDPKeyboard] initWithSession:_session delegate:self];
251- (void)viewDidAppear:(BOOL)animated
253 [
super viewDidAppear:animated];
255 if (!_session_initilized)
257 if ([_session isSuspended])
260 [
self sessionBitmapContextDidChange:_session];
265 _session_initilized = YES;
269- (void)viewWillDisappear:(BOOL)animated
271 [
super viewWillDisappear:animated];
272 if (_mouse_drag_active)
273 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
274 _mouse_drag_active = NO;
275 _mouse_pan_active = NO;
276 _long_press_active = NO;
278 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
279 [[
self navigationController] setNavigationBarHidden:NO animated:animated];
280 if (@available(iOS 18.0, *))
281 [[
self tabBarController] setTabBarHidden:NO animated:animated];
283 [[[
self tabBarController] tabBar] setHidden:NO];
289 [
self showSessionToolbar:NO];
290 [_dummy_textfield resignFirstResponder];
296 [[NSNotificationCenter defaultCenter] removeObserver:self];
299 [_session setDelegate:nil];
301 [_advanced_keyboard_view release];
302 [_keyboard_toolbar release];
303 [_sent_text release];
309#pragma mark ScrollView delegate methods
311- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
313 return _session_view;
316- (void)scrollViewDidZoom:(UIScrollView *)scrollView
318 [
self centerSessionViewInViewport];
321- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView
322 withView:(UIView *)view
323 atScale:(CGFloat)scale
325 NSLog(
@"New zoom scale: %f", scale);
326 [
self centerSessionViewInViewport];
327 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
331#pragma mark TextField delegate methods
332- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
337- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
339 [
self destroyAdvancedKeyboard];
343- (void)textFieldDidEndEditing:(UITextField *)textField
345 [
self commitTextInput];
348- (BOOL)textFieldShouldReturn:(UITextField *)textField
350 [
self commitTextInput];
351 [[
RDPKeyboard getSharedRDPKeyboard] sendEnterKeyStroke];
358- (void)textInputChanged:(UITextField *)textField
362 if ([textField markedTextRange] != nil)
368 BOOL modifierPressed = [keyboard ctrlPressed] || [keyboard altPressed] ||
369 [keyboard winPressed] || [keyboard altGrPressed];
371 NSString *text = [textField text] != nil ? [textField text] :
@"";
372 NSUInteger length = MIN([text length], [_sent_text length]);
373 NSUInteger common = 0;
375 while (common < length)
377 if ([text characterAtIndex:common] != [_sent_text characterAtIndex:common])
385 if (common < [_sent_text length])
386 common = MIN(common, [_sent_text rangeOfComposedCharacterSequenceAtIndex:common].location);
387 if (common < [text length])
388 common = MIN(common, [text rangeOfComposedCharacterSequenceAtIndex:common].location);
390 for (NSUInteger i = common; i < [_sent_text length];)
392 NSRange range = [_sent_text rangeOfComposedCharacterSequenceAtIndex:i];
393 [keyboard sendBackspaceKeyStroke];
394 i = NSMaxRange(range);
397 for (NSUInteger i = common; i < [text length]; i++)
399 [keyboard sendUnicode:[text characterAtIndex:i]];
402 BOOL isLastCharLetter = NO;
403 if ([text length] > 0)
405 isLastCharLetter = [[NSCharacterSet letterCharacterSet]
406 characterIsMember:[text characterAtIndex:[text length] - 1]];
411 if (modifierPressed || !isLastCharLetter)
413 [
self resetTextInput];
417 [_sent_text release];
418 _sent_text = [text copy];
423#pragma mark AdvancedKeyboardDelegate functions
424- (void)advancedKeyPressedVKey:(NSInteger)key
426 [
self commitTextInput];
427 [[
RDPKeyboard getSharedRDPKeyboard] sendVirtualKeyCode:key];
430#pragma mark - RDP keyboard handler
432- (void)modifiersChangedForKeyboard:(
RDPKeyboard *)keyboard
434 [_keyboard_toolbar updateModifiersWithKeyboard:keyboard];
438#pragma mark RDPSessionDelegate functions
440- (void)session:(
RDPSession *)session didFailToConnect:(
int)reason
443 [_connecting_indicator_view stopAnimating];
444 [_connecting_view removeFromSuperview];
445 [_connecting_view autorelease];
448 [[
self navigationController] popViewControllerAnimated:YES];
451- (void)sessionWillConnect:(
RDPSession *)session
454 [[NSBundle mainBundle] loadNibNamed:@"RDPConnectingView" owner:self options:nil];
457 [_lbl_connecting setText:NSLocalizedString(@"Connecting", @"Connecting progress view - label")];
458 [_cancel_connect_button setTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
459 forState:UIControlStateNormal];
462 [_connecting_view setCenter:[[
self view] center]];
463 [[_connecting_view layer] setCornerRadius:10];
466 [[
self view] addSubview:_connecting_view];
467 [_connecting_indicator_view startAnimating];
470- (void)sessionDidConnect:(
RDPSession *)session
473 [[NSNotificationCenter defaultCenter] addObserver:self
474 selector:@selector(keyboardWillShow:)
475 name:UIKeyboardWillShowNotification
477 [[NSNotificationCenter defaultCenter] addObserver:self
478 selector:@selector(keyboardWillHide:)
479 name:UIKeyboardWillHideNotification
481 [[NSNotificationCenter defaultCenter] addObserver:self
482 selector:@selector(keyboardDidHide:)
483 name:UIKeyboardDidHideNotification
487 [[NSNotificationCenter defaultCenter] addObserver:self
488 selector:@selector(hardwareKeyboardChanged:)
489 name:GCKeyboardDidConnectNotification
491 [[NSNotificationCenter defaultCenter] addObserver:self
492 selector:@selector(hardwareKeyboardChanged:)
493 name:GCKeyboardDidDisconnectNotification
495 [
self hardwareKeyboardChanged:nil];
498 [_connecting_indicator_view stopAnimating];
499 [_connecting_view removeFromSuperview];
500 [_connecting_view autorelease];
506 rdpSettings *sess_params = [session getSessionParams];
511 if (([orig_params intForKey:
@"width"] != width &&
512 [orig_params intForKey:
@"width"] != (width + 1ul)) ||
513 [orig_params intForKey:
@"height"] != height || [orig_params intForKey:
@"colors"] != depth)
517 [NSString stringWithFormat:NSLocalizedString(
518 @"The server changed the screen settings to %dx%dx%d",
519 @"Screen settings not supported message with width, "
520 @"height and colors parameter"),
521 width, height, depth];
522 [[
self view] makeToast:message duration:ToastDurationNormal position:@"bottom"];
526- (void)sessionWillDisconnect:(
RDPSession *)session
530- (void)sessionDidDisconnect:(
RDPSession *)session
533 [[
self navigationController] popViewControllerAnimated:YES];
536- (void)sessionBitmapContextWillChange:(
RDPSession *)session
538 [_session_view prepareForBitmapContextChange];
541 rdpSettings *sess_params = [session getSessionParams];
547 [_session_scrollview setZoomScale:1.0];
548 [_session_view setFrame:view_rect];
549 [_session_scrollview setContentSize:view_rect.size];
550 _has_cursor_view_position = NO;
551 _has_user_moved_cursor = NO;
552 _last_session_viewport_size = CGSizeZero;
553 [
self fitSessionViewToViewport];
556 [_session setToolbarVisible:YES];
557 [
self showSessionToolbar:[_session toolbarVisible]];
560- (void)sessionBitmapContextDidChange:(
RDPSession *)session
563 [_session_view setSession:session];
564 [_session_view setDefaultRemoteCursor];
565 if (!_has_cursor_view_position)
566 (void)[
self currentCursorViewPosition];
569 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
572- (void)session:(
RDPSession *)session needsRedrawInRect:(CGRect)rect
574 [_session_view setNeedsDisplayInRemoteRect:rect];
580 [_session_view setRemoteCursor:cursor];
582 if (!_has_user_moved_cursor &&
583 (!_has_cursor_view_position || CGPointEqualToPoint(_cursor_view_position, CGPointZero)))
585 _has_cursor_view_position = NO;
586 (void)[
self currentCursorViewPosition];
590- (void)session:(
RDPSession *)session didMoveRemoteCursor:(CGPoint)position
593 if (_mouse_pan_active || _long_press_active)
595 CGPoint viewPosition = [
self sessionViewPositionForRemotePosition:position];
596 if (!_has_user_moved_cursor && CGPointEqualToPoint(position, CGPointZero) &&
597 _has_cursor_view_position && !CGPointEqualToPoint(_cursor_view_position, CGPointZero))
600 _cursor_view_position = viewPosition;
601 _has_cursor_view_position = YES;
602 [_session_view setRemoteCursorPosition:viewPosition];
605- (void)sessionDidHideRemoteCursor:(
RDPSession *)session
608 [_session_view hideRemoteCursor];
611- (void)sessionDidSetDefaultRemoteCursor:(
RDPSession *)session
614 [_session_view setDefaultRemoteCursor];
617- (void)session:(
RDPSession *)session requestsAuthenticationWithParams:(NSMutableDictionary *)params
620 __block UIAlertController *alert = [UIAlertController
621 alertControllerWithTitle:NSLocalizedString(@"Credentials", @"Credentials title")
622 message:NSLocalizedString(@"Please provide the missing user information.",
623 @"Credentials input view message")
624 preferredStyle:UIAlertControllerStyleAlert];
627 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
629 setPlaceholder:NSLocalizedString(@"Username", @"Credentials Input Username hint")];
630 [textField setText:[params valueForKey:@"username"]];
631 [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
632 [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
636 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
638 setPlaceholder:NSLocalizedString(@"Password", @"Credentials Input Password hint")];
639 [textField setText:[params valueForKey:@"password"]];
640 [textField setSecureTextEntry:YES];
644 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
645 [textField setPlaceholder:NSLocalizedString(@"Domain", @"Credentials Input Domain hint")];
646 [textField setText:[params valueForKey:@"domain"]];
647 [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
648 [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
652 UIAlertAction *cancelAction =
653 [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
654 style:UIAlertActionStyleCancel
655 handler:^(UIAlertAction *action) {
656 [params setValue:[NSNumber numberWithBool:NO] forKey:@"result"];
657 [[session uiRequestCompleted] signal];
659 [alert addAction:cancelAction];
662 UIAlertAction *loginAction = [UIAlertAction
663 actionWithTitle:NSLocalizedString(@"Login", @"Login Button")
664 style:UIAlertActionStyleDefault
665 handler:^(UIAlertAction *action) {
666 NSArray *fields = [alert textFields];
667 [params setValue:[[fields objectAtIndex:0] text] forKey:@"username"];
668 [params setValue:[[fields objectAtIndex:1] text] forKey:@"password"];
669 [params setValue:[[fields objectAtIndex:2] text] forKey:@"domain"];
670 [params setValue:[NSNumber numberWithBool:YES] forKey:@"result"];
671 [[session uiRequestCompleted] signal];
673 [alert addAction:loginAction];
675 [alert setPreferredAction:loginAction];
677 [
self presentViewController:alert animated:YES completion:nil];
680- (void)session:(
RDPSession *)session verifyCertificateWithParams:(NSMutableDictionary *)params
682 NSString *message = [NSString
683 stringWithFormat:@"%@\n\n%@ %@",
684 NSLocalizedString(@"The identity of the remote computer cannot be "
685 @"verified. Do you want to connect anyway?",
686 @"Verify certificate view message"),
687 NSLocalizedString(@"Issuer:", @"Verify certificate view issuer label"),
688 [params valueForKey:@"issuer"]];
689 UIAlertController *alert =
690 [UIAlertController alertControllerWithTitle:nil
692 preferredStyle:UIAlertControllerStyleAlert];
694 [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"No", @"No Button")
695 style:UIAlertActionStyleCancel
696 handler:^(UIAlertAction *action) {
697 [params setValue:[NSNumber numberWithBool:NO]
699 [[session uiRequestCompleted] signal];
702 [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", @"Yes Button")
703 style:UIAlertActionStyleDefault
704 handler:^(UIAlertAction *action) {
705 [params setValue:[NSNumber numberWithBool:YES]
707 [[session uiRequestCompleted] signal];
710 [
self presentViewController:alert animated:YES completion:nil];
713- (CGSize)sizeForFitScreenForSession:(
RDPSession *)session
716 CGSize size = [
self view].bounds.size;
717 UIScreen *screen = [[
self view] window] ? [[[
self view] window] screen] : [UIScreen mainScreen];
719 [screen respondsToSelector:@selector(nativeScale)] ? [screen nativeScale] : [screen scale];
723 size.width = ceilf(size.width * scale);
724 size.height = ceilf(size.height * scale);
726 CGFloat maxDimension = MAX(size.width, size.height);
727 if (maxDimension > 4096.0f)
729 CGFloat downscale = 4096.0f / maxDimension;
730 size.width = floorf(size.width * downscale);
731 size.height = floorf(size.height * downscale);
734 size.width = MAX(64.0f, size.width);
735 size.height = MAX(64.0f, size.height);
739#pragma mark - Keyboard Toolbar Handlers
741- (void)showAdvancedKeyboardAnimated
744 if (_advanced_keyboard_view)
747 CGRect rect = CGRectMake(0, 0, [[
self view] bounds].size.width, [
self advancedKeyboardHeight]);
750 [_dummy_textfield setInputView:_advanced_keyboard_view];
751 [_dummy_textfield reloadInputViews];
752 [_keyboard_toolbar setExtendedKeyboardActive:YES];
755- (void)destroyAdvancedKeyboard
758 if (!_advanced_keyboard_view)
761 [_dummy_textfield setInputView:nil];
762 [_advanced_keyboard_view autorelease];
763 _advanced_keyboard_view = nil;
764 [_keyboard_toolbar setExtendedKeyboardActive:NO];
768- (IBAction)toggleKeyboardWhenOtherVisible:(
id)sender
770 if (!_advanced_keyboard_view)
773 [
self showAdvancedKeyboardAnimated];
778 [
self destroyAdvancedKeyboard];
779 [_dummy_textfield reloadInputViews];
783- (IBAction)toggleWinKey:(
id)sender
788- (IBAction)toggleShiftKey:(
id)sender
790 [[
RDPKeyboard getSharedRDPKeyboard] toggleShiftKey];
793- (IBAction)toggleCtrlKey:(
id)sender
795 [[
RDPKeyboard getSharedRDPKeyboard] toggleCtrlKey];
798- (IBAction)toggleAltKey:(
id)sender
803- (IBAction)toggleAltGrKey:(
id)sender
805 [[
RDPKeyboard getSharedRDPKeyboard] toggleAltGrKey];
808- (IBAction)pressEscKey:(
id)sender
810 [
self commitTextInput];
811 [[
RDPKeyboard getSharedRDPKeyboard] sendEscapeKeyStroke];
814- (IBAction)pressDeleteKey:(
id)sender
816 [
self commitTextInput];
817 [[
RDPKeyboard getSharedRDPKeyboard] sendDeleteKeyStroke];
820- (IBAction)pressTabKey:(
id)sender
822 [
self commitTextInput];
823 [[
RDPKeyboard getSharedRDPKeyboard] sendTabKeyStroke];
827#pragma mark event handlers
829- (IBAction)switchSession:(
id)sender
831 [
self suspendSession];
834- (IBAction)toggleKeyboard:(
id)sender
836 if (![
self isKeyboardActive])
837 [_dummy_textfield becomeFirstResponder];
839 [_dummy_textfield resignFirstResponder];
842- (IBAction)disconnectSession:(
id)sender
844 [_session disconnect];
847- (IBAction)cancelButtonPressed:(
id)sender
849 [_session disconnect];
853#pragma mark iOS Keyboard Notification Handlers
855- (void)keyboardWillShow:(NSNotification *)notification
858 if (!_advanced_keyboard_view)
861 [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
862 CGFloat height = frame.size.height - [_keyboard_toolbar bounds].size.height;
867 if ([
self isLandscape])
868 _keyboard_height_landscape = height;
870 _keyboard_height_portrait = height;
874 [
self centerSessionViewInViewport];
877 CGRect keyboardFrame =
878 [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
879 keyboardFrame = [[
self view] convertRect:keyboardFrame fromView:nil];
880 CGRect coveredArea = CGRectIntersection([[
self view] bounds], keyboardFrame);
882 [
self setKeyboardOverlap:CGRectGetHeight(coveredArea)];
885- (void)keyboardWillHide:(NSNotification *)notification
888 [
self centerSessionViewInViewport];
889 [
self setKeyboardOverlap:0.0];
892- (void)keyboardDidHide:(NSNotification *)notification
895 if (![
self isKeyboardActive])
896 [
self destroyAdvancedKeyboard];
899 if (![
self isKeyboardActive] && [_session_view hardwareKeyboardActive])
900 [_session_view becomeFirstResponder];
903- (void)hardwareKeyboardChanged:(NSNotification *)notification
905 BOOL connected = (GCKeyboard.coalescedKeyboard != nil);
906 [_session_view setHardwareKeyboardActive:connected];
908 if (connected && ![
self isKeyboardActive])
909 [_session_view becomeFirstResponder];
911 [_session_view resignFirstResponder];
915#pragma mark Gesture handlers
917- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
919 if (_pointer_is_indirect)
920 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
921 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
922 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
925- (void)handleSecondaryTap:(UITapGestureRecognizer *)gesture
927 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
928 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
929 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
932- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
934 if ([gesture state] == UIGestureRecognizerStateBegan)
936 _long_press_active = YES;
937 _mouse_drag_active = NO;
939 else if ([gesture state] == UIGestureRecognizerStateEnded ||
940 [gesture state] == UIGestureRecognizerStateCancelled ||
941 [gesture state] == UIGestureRecognizerStateFailed)
943 if (_mouse_drag_active)
944 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
945 else if ([gesture state] == UIGestureRecognizerStateEnded)
947 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
948 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
951 _mouse_drag_active = NO;
952 _long_press_active = NO;
956- (void)handleMousePan:(UIPanGestureRecognizer *)gesture
960 if (_pointer_is_indirect)
962 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
964 if ([gesture state] == UIGestureRecognizerStateBegan)
966 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
967 _mouse_drag_active = YES;
969 else if ([gesture state] == UIGestureRecognizerStateEnded ||
970 [gesture state] == UIGestureRecognizerStateCancelled ||
971 [gesture state] == UIGestureRecognizerStateFailed)
973 if (_mouse_drag_active)
974 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
975 _mouse_drag_active = NO;
980 CGPoint location = [gesture locationInView:_session_scrollview];
982 if ([gesture state] == UIGestureRecognizerStateBegan)
984 _mouse_pan_active = YES;
985 _last_mouse_pan_location = location;
986 if (_long_press_active && !_mouse_drag_active)
988 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
989 _mouse_drag_active = YES;
992 else if ([gesture state] == UIGestureRecognizerStateChanged)
994 if (_long_press_active && !_mouse_drag_active)
996 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
997 _mouse_drag_active = YES;
1000 CGPoint delta = CGPointMake(location.x - _last_mouse_pan_location.x,
1001 location.y - _last_mouse_pan_location.y);
1002 [
self moveCursorByViewportDelta:delta];
1003 _last_mouse_pan_location = location;
1005 else if ([gesture state] == UIGestureRecognizerStateEnded ||
1006 [gesture state] == UIGestureRecognizerStateCancelled ||
1007 [gesture state] == UIGestureRecognizerStateFailed)
1009 _mouse_pan_active = NO;
1013- (void)handleHover:(UIHoverGestureRecognizer *)gesture
1015 if ([gesture state] != UIGestureRecognizerStateBegan &&
1016 [gesture state] != UIGestureRecognizerStateChanged)
1019 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
1022- (void)handleDoubleLongPress:(UILongPressGestureRecognizer *)gesture
1026 CGPoint pos = [gesture locationInView:_session_scrollview];
1027 CGPoint session_view_pos = [
self currentCursorViewPosition];
1029 if ([gesture state] == UIGestureRecognizerStateBegan)
1030 _prev_long_press_position = pos;
1031 else if ([gesture state] == UIGestureRecognizerStateChanged)
1033 int delta = _prev_long_press_position.y - pos.y;
1035 if (delta > GetScrollGestureDelta())
1037 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(YES)
1038 position:session_view_pos]];
1039 _prev_long_press_position = pos;
1041 else if (delta < -GetScrollGestureDelta())
1043 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(NO)
1044 position:session_view_pos]];
1045 _prev_long_press_position = pos;
1050- (void)handleScroll:(UIPanGestureRecognizer *)gesture
1052 CGFloat delta = [gesture translationInView:_session_view].y;
1053 if (fabs(delta) < GetScrollGestureDelta())
1056 CGPoint position = [
self currentCursorViewPosition];
1057 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(delta > 0)
1058 position:position]];
1059 [gesture setTranslation:CGPointZero inView:_session_view];
1062- (void)handleSingle3FingersTap:(UITapGestureRecognizer *)gesture
1064 [_session setToolbarVisible:![_session toolbarVisible]];
1065 [
self showSessionToolbar:[_session toolbarVisible]];
1068- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction
1069 styleForRegion:(UIPointerRegion *)region
1071 return [UIPointerStyle hiddenPointerStyle];
1074- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1075 shouldReceiveTouch:(UITouch *)touch
1078 if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
1079 [(UIPanGestureRecognizer *)gestureRecognizer allowedScrollTypesMask] != 0)
1082 _pointer_is_indirect = ([touch type] == UITouchTypeIndirectPointer);
1086 if (_pointer_is_indirect &&
1087 [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
1093- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1094 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
1097 ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
1098 [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) ||
1099 ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] &&
1100 [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]);
1106@implementation RDPSessionViewController (Private)
1109#pragma mark Helper functions
1111- (void)fitSessionViewToViewport
1113 CGSize viewportSize = [_session_scrollview bounds].size;
1114 CGSize remoteSize = [_session_view bounds].size;
1115 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f || remoteSize.width <= 0.0f ||
1116 remoteSize.height <= 0.0f)
1120 MIN(viewportSize.width / remoteSize.width, viewportSize.height / remoteSize.height);
1121 [_session_scrollview setMinimumZoomScale:fitScale];
1122 [_session_scrollview setMaximumZoomScale:MAX(2.0f, fitScale)];
1123 [_session_scrollview setZoomScale:fitScale animated:NO];
1124 [_session_scrollview setContentOffset:CGPointZero animated:NO];
1125 _last_session_viewport_size = viewportSize;
1126 [
self centerSessionViewInViewport];
1129- (void)centerSessionViewInViewport
1131 CGSize viewportSize = [_session_scrollview bounds].size;
1132 CGRect sessionFrame = [_session_view frame];
1133 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f ||
1134 sessionFrame.size.width <= 0.0f || sessionFrame.size.height <= 0.0f)
1137 sessionFrame.origin.x = MAX((viewportSize.width - sessionFrame.size.width) * 0.5f, 0.0f);
1138 sessionFrame.origin.y = MAX((viewportSize.height - sessionFrame.size.height) * 0.5f, 0.0f);
1139 [_session_view setFrame:sessionFrame];
1141 [_session_scrollview
1142 setContentSize:CGSizeMake(MAX(viewportSize.width, sessionFrame.size.width),
1143 MAX(viewportSize.height, sessionFrame.size.height))];
1146- (void)setKeyboardOverlap:(CGFloat)overlap
1149 UIEdgeInsets inset = [_session_scrollview contentInset];
1150 inset.bottom = overlap;
1151 [_session_scrollview setContentInset:inset];
1153 CGPoint offset = [_session_scrollview contentOffset];
1154 CGFloat viewportHeight = [_session_scrollview bounds].size.height;
1157 if (overlap > 0.0 && _has_cursor_view_position)
1159 CGPoint cursor = [_session_scrollview convertPoint:_cursor_view_position
1160 fromView:_session_view];
1161 CGFloat visibleBottom = offset.y + viewportHeight - overlap;
1162 CGFloat hiddenHeight = cursor.y + KEYBOARD_CURSOR_MARGIN - visibleBottom;
1163 if (hiddenHeight > 0.0)
1164 offset.y += hiddenHeight;
1168 CGFloat maxOffsetY = [_session_scrollview contentSize].height + overlap - viewportHeight;
1169 offset.y = MAX(MIN(offset.y, maxOffsetY), 0.0);
1170 [_session_scrollview setContentOffset:offset animated:YES];
1173- (void)showSessionToolbar:(BOOL)show
1176 if (_session_toolbar_visible == show)
1182 UIEdgeInsets safe = [[
self view] safeAreaInsets];
1185 safe.top = MAX(safe.top, [
self systemMinimumLayoutMargins].leading);
1186 CGFloat toolbarWidth = [[
self view] bounds].size.width - safe.left - safe.right;
1190 [UIView animateWithDuration:0.4
1192 options:UIViewAnimationOptionCurveLinear
1194 [_session_toolbar setFrame:CGRectMake(safe.left, safe.top,
1195 toolbarWidth, TOOLBAR_HEIGHT)];
1198 _session_toolbar_visible = YES;
1202 [UIView animateWithDuration:0.4
1204 options:UIViewAnimationOptionCurveLinear
1206 [_session_toolbar setFrame:CGRectMake(safe.left, -TOOLBAR_HEIGHT,
1207 toolbarWidth, TOOLBAR_HEIGHT)];
1210 _session_toolbar_visible = NO;
1214- (void)initGestureRecognizers
1218 UITapGestureRecognizer *singleTapRecognizer =
1219 [[[UITapGestureRecognizer alloc] initWithTarget:self
1220 action:@selector(handleSingleTap:)] autorelease];
1221 [singleTapRecognizer setNumberOfTouchesRequired:1];
1222 [singleTapRecognizer setNumberOfTapsRequired:1];
1223 [singleTapRecognizer setDelegate:self];
1226 UILongPressGestureRecognizer *longPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1228 action:@selector(handleLongPress:)] autorelease];
1229 [longPressRecognizer setMinimumPressDuration:0.3];
1230 [longPressRecognizer setAllowableMovement:12.0];
1231 [longPressRecognizer setDelegate:self];
1234 UIPanGestureRecognizer *mousePanRecognizer =
1235 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1236 action:@selector(handleMousePan:)] autorelease];
1237 [mousePanRecognizer setMinimumNumberOfTouches:1];
1238 [mousePanRecognizer setMaximumNumberOfTouches:1];
1239 [mousePanRecognizer setDelegate:self];
1242 UIHoverGestureRecognizer *hoverRecognizer =
1243 [[[UIHoverGestureRecognizer alloc] initWithTarget:self
1244 action:@selector(handleHover:)] autorelease];
1245 [hoverRecognizer setDelegate:self];
1249 UITapGestureRecognizer *secondaryTapRecognizer = [[[UITapGestureRecognizer alloc]
1251 action:@selector(handleSecondaryTap:)] autorelease];
1252 [secondaryTapRecognizer setButtonMaskRequired:UIEventButtonMaskSecondary];
1253 [secondaryTapRecognizer setAllowedTouchTypes:@[@(UITouchTypeIndirectPointer)]];
1254 [secondaryTapRecognizer setDelegate:self];
1258 UIPanGestureRecognizer *scrollRecognizer =
1259 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1260 action:@selector(handleScroll:)] autorelease];
1261 [scrollRecognizer setAllowedScrollTypesMask:UIScrollTypeMaskAll];
1262 [scrollRecognizer setDelegate:self];
1265 UILongPressGestureRecognizer *doubleLongPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1267 action:@selector(handleDoubleLongPress:)] autorelease];
1268 [doubleLongPressRecognizer setNumberOfTouchesRequired:2];
1269 [doubleLongPressRecognizer setMinimumPressDuration:0.3];
1272 UITapGestureRecognizer *single3FingersTapRecognizer = [[[UITapGestureRecognizer alloc]
1274 action:@selector(handleSingle3FingersTap:)] autorelease];
1275 [single3FingersTapRecognizer setNumberOfTapsRequired:1];
1276 [single3FingersTapRecognizer setNumberOfTouchesRequired:3];
1277 [singleTapRecognizer requireGestureRecognizerToFail:longPressRecognizer];
1280 [[_session_scrollview panGestureRecognizer] setMinimumNumberOfTouches:2];
1283 [_session_scrollview addGestureRecognizer:singleTapRecognizer];
1284 [_session_scrollview addGestureRecognizer:longPressRecognizer];
1285 [_session_scrollview addGestureRecognizer:mousePanRecognizer];
1286 [_session_scrollview addGestureRecognizer:hoverRecognizer];
1287 [_session_scrollview addGestureRecognizer:secondaryTapRecognizer];
1288 [_session_scrollview addGestureRecognizer:scrollRecognizer];
1289 [_session_scrollview addGestureRecognizer:doubleLongPressRecognizer];
1290 [_session_scrollview addGestureRecognizer:single3FingersTapRecognizer];
1293 UIPointerInteraction *pointerInteraction =
1294 [[[UIPointerInteraction alloc] initWithDelegate:self] autorelease];
1295 [_session_view addInteraction:pointerInteraction];
1298- (void)suspendSession
1304 [[
self navigationController] popViewControllerAnimated:YES];
1307- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position
1309 CGPoint remote_position = [
self remotePositionForSessionViewPosition:position];
1310 return [NSDictionary
1311 dictionaryWithObjectsAndKeys:@"mouse", @"type", [NSNumber numberWithUnsignedShort:event],
1313 [NSNumber numberWithUnsignedShort:lrintf(remote_position.x)],
1315 [NSNumber numberWithUnsignedShort:lrintf(remote_position.y)],
1319- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position
1321 rdpSettings *settings = [_session getSessionParams];
1322 CGSize viewSize = [_session_view bounds].size;
1323 CGFloat desktopWidth =
1325 CGFloat desktopHeight =
1328 if ((viewSize.width > 0.0f) && (desktopWidth > 0.0f))
1329 position.x = position.x * desktopWidth / viewSize.width;
1330 if ((viewSize.height > 0.0f) && (desktopHeight > 0.0f))
1331 position.y = position.y * desktopHeight / viewSize.height;
1333 if (desktopWidth > 0.0f)
1334 position.x = MIN(MAX(position.x, 0.0f), desktopWidth - 1.0f);
1335 if (desktopHeight > 0.0f)
1336 position.y = MIN(MAX(position.y, 0.0f), desktopHeight - 1.0f);
1341- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position
1343 rdpSettings *settings = [_session getSessionParams];
1344 CGSize viewSize = [_session_view bounds].size;
1345 CGFloat desktopWidth =
1347 CGFloat desktopHeight =
1350 if ((desktopWidth > 0.0f) && (viewSize.width > 0.0f))
1351 position.x = position.x * viewSize.width / desktopWidth;
1352 if ((desktopHeight > 0.0f) && (viewSize.height > 0.0f))
1353 position.y = position.y * viewSize.height / desktopHeight;
1355 return [
self clampedSessionViewCursorPosition:position];
1358- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position
1360 CGSize viewSize = [_session_view bounds].size;
1361 if (viewSize.width > 0.0f)
1362 position.x = MIN(MAX(position.x, 0.0f), viewSize.width - 1.0f);
1363 if (viewSize.height > 0.0f)
1364 position.y = MIN(MAX(position.y, 0.0f), viewSize.height - 1.0f);
1368- (CGPoint)currentCursorViewPosition
1370 if (!_has_cursor_view_position)
1372 CGSize viewSize = [_session_view bounds].size;
1373 _cursor_view_position = CGPointMake(MAX(viewSize.width - 1.0, 0.0) * 0.5,
1374 MAX(viewSize.height - 1.0, 0.0) * 0.5);
1375 _has_cursor_view_position = YES;
1376 [_session_view setRemoteCursorPosition:_cursor_view_position];
1378 [_session_view showRemoteCursor];
1380 return _cursor_view_position;
1383- (void)moveCursorByViewportDelta:(CGPoint)delta
1385 CGPoint position = [
self currentCursorViewPosition];
1386 CGFloat zoomScale = [_session_scrollview zoomScale];
1387 if (zoomScale <= 0.0)
1390 position.x += delta.x / zoomScale;
1391 position.y += delta.y / zoomScale;
1392 position = [
self clampedSessionViewCursorPosition:position];
1393 _cursor_view_position = position;
1394 _has_cursor_view_position = YES;
1395 _has_user_moved_cursor = YES;
1398 [_session_view setRemoteCursorPosition:position];
1399 [
self handleMouseMoveForPosition:position];
1402- (void)moveCursorToSessionViewPosition:(CGPoint)position
1404 position = [
self clampedSessionViewCursorPosition:position];
1405 _cursor_view_position = position;
1406 _has_cursor_view_position = YES;
1407 _has_user_moved_cursor = YES;
1410 [_session_view setRemoteCursorPosition:position];
1411 [_session_view showRemoteCursor];
1412 [
self handleMouseMoveForPosition:position];
1415- (void)sendMouseButtonEvent:(
int)event
1418 [
self commitTextInput];
1419 CGPoint position = [
self currentCursorViewPosition];
1420 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:event position:position]];
1423- (void)handleMouseMoveForPosition:(CGPoint)position
1425 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:PTR_FLAGS_MOVE position:position]];
1428- (BOOL)isKeyboardActive
1430 return [_dummy_textfield isFirstResponder];
1433- (void)resetTextInput
1435 [_dummy_textfield setText:@""];
1436 [_sent_text release];
1437 _sent_text = [@"" retain];
1440- (void)commitTextInput
1442 if ([[_dummy_textfield text] length] == 0)
1446 [_dummy_textfield unmarkText];
1447 [
self textInputChanged:_dummy_textfield];
1448 [
self resetTextInput];
1453 CGSize size = [[
self view] bounds].size;
1454 return size.width > size.height;
1457- (CGFloat)advancedKeyboardHeight
1460 if ([
self isLandscape])
1461 return (_keyboard_height_landscape > 0.0) ? _keyboard_height_landscape
1462 : (IsPad() ? 400.0 : 200.0);
1463 return (_keyboard_height_portrait > 0.0) ? _keyboard_height_portrait
1464 : (IsPad() ? 320.0 : 290.0);
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.