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- (CGRect)sessionViewportFrame;
33- (void)showConnectingViewWithText:(NSString *)text;
34- (void)hideConnectingView;
35- (void)endBackgroundTask;
36- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position;
37- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position;
38- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position;
39- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position;
40- (void)handleMouseMoveForPosition:(CGPoint)position;
41- (CGPoint)currentCursorViewPosition;
42- (void)moveCursorByViewportDelta:(CGPoint)delta;
43- (void)moveCursorToSessionViewPosition:(CGPoint)position;
44- (void)sendMouseButtonEvent:(
int)event;
45- (BOOL)isKeyboardActive;
47- (CGFloat)advancedKeyboardHeight;
48- (void)resetTextInput;
49- (void)commitTextInput;
53@interface RDPSessionTextField : UITextField
56@implementation RDPSessionTextField
61 if ([[
self text] length] == 0)
62 [[
RDPKeyboard getSharedRDPKeyboard] sendBackspaceKeyStroke];
63 [
super deleteBackward];
70#pragma mark class methods
72- (id)initWithNibName:(NSString *)nibNameOrNil
73 bundle:(NSBundle *)nibBundleOrNil
76 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
79 _session = [session retain];
80 [_session setDelegate:self];
81 _session_initilized = NO;
83 _advanced_keyboard_view = nil;
84 _sent_text = [@"" retain];
85 _last_session_viewport_size = CGSizeZero;
87 _session_toolbar_visible = NO;
89 _cursor_view_position = CGPointZero;
90 _last_mouse_pan_location = CGPointZero;
91 _has_cursor_view_position = NO;
92 _has_user_moved_cursor = NO;
93 _mouse_pan_active = NO;
94 _long_press_active = NO;
95 _mouse_drag_active = NO;
96 _pointer_is_indirect = NO;
111 if (RDPSupportLiquidGlass() == NO)
114 UIToolbarAppearance *appearance = [[[UIToolbarAppearance alloc] init] autorelease];
117 [appearance configureWithTransparentBackground];
118 [_session_toolbar setStandardAppearance:appearance];
119 [_session_toolbar setCompactAppearance:appearance];
120 [_session_toolbar setScrollEdgeAppearance:appearance];
121 [_session_toolbar setCompactScrollEdgeAppearance:appearance];
126 [_dummy_textfield setInputAccessoryView:_keyboard_toolbar];
129 [_dummy_textfield setAutocapitalizationType:UITextAutocapitalizationTypeNone];
130 [_dummy_textfield setSpellCheckingType:UITextSpellCheckingTypeNo];
131 [_dummy_textfield setSmartQuotesType:UITextSmartQuotesTypeNo];
132 [_dummy_textfield setSmartDashesType:UITextSmartDashesTypeNo];
133 [_dummy_textfield setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo];
134 [_dummy_textfield addTarget:self
135 action:@selector(textInputChanged:)
136 forControlEvents:UIControlEventEditingChanged];
140 [[_dummy_textfield inputAssistantItem] setLeadingBarButtonGroups:@[]];
141 [[_dummy_textfield inputAssistantItem] setTrailingBarButtonGroups:@[]];
144 [
self initGestureRecognizers];
148 setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[
self view] bounds].size.width, TOOLBAR_HEIGHT)];
157 [[
self view] setBackgroundColor:[UIColor blackColor]];
159 [_session_scrollview setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
160 [_session_scrollview setContentInset:UIEdgeInsetsZero];
161 [_session_scrollview setScrollIndicatorInsets:UIEdgeInsetsZero];
162 [_session_scrollview setShowsHorizontalScrollIndicator:NO];
163 [_session_scrollview setShowsVerticalScrollIndicator:NO];
164 [_session_scrollview setAlwaysBounceHorizontal:NO];
165 [_session_scrollview setAlwaysBounceVertical:NO];
166 [_session_scrollview setBounces:NO];
169- (void)viewDidLayoutSubviews
171 [
super viewDidLayoutSubviews];
173 CGRect viewportFrame = [
self sessionViewportFrame];
174 [_session_scrollview setFrame:viewportFrame];
176 CGSize viewportSize = [_session_scrollview bounds].size;
177 if (!CGSizeEqualToSize(viewportSize, _last_session_viewport_size))
178 [
self fitSessionViewToViewport];
180 [
self centerSessionViewInViewport];
183- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
188- (BOOL)prefersStatusBarHidden
193- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
195 return UIStatusBarAnimationSlide;
198- (void)viewWillTransitionToSize:(CGSize)size
199 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator
201 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
205 animateAlongsideTransition:nil
206 completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
207 if (!_advanced_keyboard_view)
209 CGRect frame = [_advanced_keyboard_view frame];
210 frame.size.height = [
self advancedKeyboardHeight];
211 [_advanced_keyboard_view setFrame:frame];
212 [_dummy_textfield reloadInputViews];
216- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
218 (void)fromInterfaceOrientation;
219 [
self centerSessionViewInViewport];
222- (void)didReceiveMemoryWarning
225 [
super didReceiveMemoryWarning];
232 [
super viewDidUnload];
237- (void)viewWillAppear:(BOOL)animated
239 [
super viewWillAppear:animated];
242 [
self setNeedsStatusBarAppearanceUpdate];
243 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
244 [[
self navigationController] setNavigationBarHidden:YES animated:animated];
245 if (@available(iOS 18.0, *))
246 [[
self tabBarController] setTabBarHidden:YES animated:animated];
248 [[[
self tabBarController] tabBar] setHidden:YES];
251 if ([_session isSuspended])
252 [
self sessionBitmapContextWillChange:_session];
255 [[
RDPKeyboard getSharedRDPKeyboard] initWithSession:_session delegate:self];
258- (void)viewDidAppear:(BOOL)animated
260 [
super viewDidAppear:animated];
262 if (!_session_initilized)
264 if ([_session isSuspended])
267 [
self sessionBitmapContextDidChange:_session];
272 _session_initilized = YES;
276- (void)viewWillDisappear:(BOOL)animated
278 [
super viewWillDisappear:animated];
279 if (_mouse_drag_active)
280 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
281 _mouse_drag_active = NO;
282 _mouse_pan_active = NO;
283 _long_press_active = NO;
285 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
286 [[
self navigationController] setNavigationBarHidden:NO animated:animated];
287 if (@available(iOS 18.0, *))
288 [[
self tabBarController] setTabBarHidden:NO animated:animated];
290 [[[
self tabBarController] tabBar] setHidden:NO];
296 [
self showSessionToolbar:NO];
297 [_dummy_textfield resignFirstResponder];
303 [[NSNotificationCenter defaultCenter] removeObserver:self];
306 [_session setDelegate:nil];
308 [_advanced_keyboard_view release];
309 [_keyboard_toolbar release];
310 [_sent_text release];
316#pragma mark ScrollView delegate methods
318- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
320 return _session_view;
323- (void)scrollViewDidZoom:(UIScrollView *)scrollView
325 [
self centerSessionViewInViewport];
328- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView
329 withView:(UIView *)view
330 atScale:(CGFloat)scale
332 NSLog(
@"New zoom scale: %f", scale);
333 [
self centerSessionViewInViewport];
334 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
338#pragma mark TextField delegate methods
339- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
344- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
346 [
self destroyAdvancedKeyboard];
350- (void)textFieldDidEndEditing:(UITextField *)textField
352 [
self commitTextInput];
355- (BOOL)textFieldShouldReturn:(UITextField *)textField
357 [
self commitTextInput];
358 [[
RDPKeyboard getSharedRDPKeyboard] sendEnterKeyStroke];
365- (void)textInputChanged:(UITextField *)textField
369 if ([textField markedTextRange] != nil)
375 BOOL modifierPressed = [keyboard ctrlPressed] || [keyboard altPressed] ||
376 [keyboard winPressed] || [keyboard altGrPressed];
378 NSString *text = [textField text] != nil ? [textField text] :
@"";
379 NSUInteger length = MIN([text length], [_sent_text length]);
380 NSUInteger common = 0;
382 while (common < length)
384 if ([text characterAtIndex:common] != [_sent_text characterAtIndex:common])
392 if (common < [_sent_text length])
393 common = MIN(common, [_sent_text rangeOfComposedCharacterSequenceAtIndex:common].location);
394 if (common < [text length])
395 common = MIN(common, [text rangeOfComposedCharacterSequenceAtIndex:common].location);
397 for (NSUInteger i = common; i < [_sent_text length];)
399 NSRange range = [_sent_text rangeOfComposedCharacterSequenceAtIndex:i];
400 [keyboard sendBackspaceKeyStroke];
401 i = NSMaxRange(range);
404 for (NSUInteger i = common; i < [text length]; i++)
406 [keyboard sendUnicode:[text characterAtIndex:i]];
409 BOOL isLastCharLetter = NO;
410 if ([text length] > 0)
412 isLastCharLetter = [[NSCharacterSet letterCharacterSet]
413 characterIsMember:[text characterAtIndex:[text length] - 1]];
418 if (modifierPressed || !isLastCharLetter)
420 [
self resetTextInput];
424 [_sent_text release];
425 _sent_text = [text copy];
430#pragma mark AdvancedKeyboardDelegate functions
431- (void)advancedKeyPressedVKey:(NSInteger)key
433 [
self commitTextInput];
434 [[
RDPKeyboard getSharedRDPKeyboard] sendVirtualKeyCode:key];
437#pragma mark - RDP keyboard handler
439- (void)modifiersChangedForKeyboard:(
RDPKeyboard *)keyboard
441 [_keyboard_toolbar updateModifiersWithKeyboard:keyboard];
445#pragma mark RDPSessionDelegate functions
447- (void)session:(
RDPSession *)session didFailToConnect:(
int)reason
449 [
self hideConnectingView];
452 [[
self navigationController] popViewControllerAnimated:YES];
455- (void)sessionWillConnect:(
RDPSession *)session
457 [
self showConnectingViewWithText:NSLocalizedString(@"Connecting",
458 @"Connecting progress view - label")];
461- (void)sessionWillReconnect:(
RDPSession *)session
464 [
self showConnectingViewWithText:NSLocalizedString(
466 @"Connecting progress view - reconnect label")];
469- (void)sessionDidReconnect:(
RDPSession *)session
472 [
self hideConnectingView];
475- (void)sessionDidConnect:(
RDPSession *)session
478 [[NSNotificationCenter defaultCenter] addObserver:self
479 selector:@selector(keyboardWillShow:)
480 name:UIKeyboardWillShowNotification
482 [[NSNotificationCenter defaultCenter] addObserver:self
483 selector:@selector(keyboardWillHide:)
484 name:UIKeyboardWillHideNotification
486 [[NSNotificationCenter defaultCenter] addObserver:self
487 selector:@selector(keyboardDidHide:)
488 name:UIKeyboardDidHideNotification
492 [[NSNotificationCenter defaultCenter] addObserver:self
493 selector:@selector(hardwareKeyboardChanged:)
494 name:GCKeyboardDidConnectNotification
496 [[NSNotificationCenter defaultCenter] addObserver:self
497 selector:@selector(hardwareKeyboardChanged:)
498 name:GCKeyboardDidDisconnectNotification
500 [
self hardwareKeyboardChanged:nil];
503 [[NSNotificationCenter defaultCenter] addObserver:self
504 selector:@selector(localClipboardChanged:)
505 name:UIApplicationDidBecomeActiveNotification
507 [
self localClipboardChanged:nil];
511 [[NSNotificationCenter defaultCenter] addObserver:self
512 selector:@selector(applicationDidEnterBackground:)
513 name:UIApplicationDidEnterBackgroundNotification
515 [[NSNotificationCenter defaultCenter] addObserver:self
516 selector:@selector(applicationWillEnterForeground:)
517 name:UIApplicationWillEnterForegroundNotification
520 [
self hideConnectingView];
526 rdpSettings *sess_params = [session getSessionParams];
531 if (([orig_params intForKey:
@"width"] != width &&
532 [orig_params intForKey:
@"width"] != (width + 1ul)) ||
533 [orig_params intForKey:
@"height"] != height || [orig_params intForKey:
@"colors"] != depth)
537 [NSString stringWithFormat:NSLocalizedString(
538 @"The server changed the screen settings to %dx%dx%d",
539 @"Screen settings not supported message with width, "
540 @"height and colors parameter"),
541 width, height, depth];
542 [[
self view] makeToast:message duration:ToastDurationNormal position:@"bottom"];
546- (void)sessionWillDisconnect:(
RDPSession *)session
550- (void)sessionDidDisconnect:(
RDPSession *)session
552 [
self hideConnectingView];
553 [
self endBackgroundTask];
556 [[
self navigationController] popViewControllerAnimated:YES];
559- (void)sessionBitmapContextWillChange:(
RDPSession *)session
561 [_session_view prepareForBitmapContextChange];
564 rdpSettings *sess_params = [session getSessionParams];
570 [_session_scrollview setZoomScale:1.0];
571 [_session_view setFrame:view_rect];
572 [_session_scrollview setContentSize:view_rect.size];
573 _has_cursor_view_position = NO;
574 _has_user_moved_cursor = NO;
575 _last_session_viewport_size = CGSizeZero;
576 [
self fitSessionViewToViewport];
579 [_session setToolbarVisible:YES];
580 [
self showSessionToolbar:[_session toolbarVisible]];
583- (void)sessionBitmapContextDidChange:(
RDPSession *)session
586 [_session_view setSession:session];
587 [_session_view setDefaultRemoteCursor];
588 if (!_has_cursor_view_position)
589 (void)[
self currentCursorViewPosition];
592 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
595- (void)session:(
RDPSession *)session needsRedrawInRect:(CGRect)rect
597 [_session_view setNeedsDisplayInRemoteRect:rect];
603 [_session_view setRemoteCursor:cursor];
605 if (!_has_user_moved_cursor &&
606 (!_has_cursor_view_position || CGPointEqualToPoint(_cursor_view_position, CGPointZero)))
608 _has_cursor_view_position = NO;
609 (void)[
self currentCursorViewPosition];
613- (void)session:(
RDPSession *)session didMoveRemoteCursor:(CGPoint)position
616 if (_mouse_pan_active || _long_press_active)
618 CGPoint viewPosition = [
self sessionViewPositionForRemotePosition:position];
619 if (!_has_user_moved_cursor && CGPointEqualToPoint(position, CGPointZero) &&
620 _has_cursor_view_position && !CGPointEqualToPoint(_cursor_view_position, CGPointZero))
623 _cursor_view_position = viewPosition;
624 _has_cursor_view_position = YES;
625 [_session_view setRemoteCursorPosition:viewPosition];
628- (void)sessionDidHideRemoteCursor:(
RDPSession *)session
631 [_session_view hideRemoteCursor];
634- (void)sessionDidSetDefaultRemoteCursor:(
RDPSession *)session
637 [_session_view setDefaultRemoteCursor];
640- (void)session:(
RDPSession *)session didChangeRemoteClipboardText:(NSString *)text
643 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
644 [pasteboard setString:text];
647 _pasteboard_change_count = [pasteboard changeCount];
650- (void)session:(
RDPSession *)session requestsClipboardTextWithParams:(NSMutableDictionary *)params
653 NSString *text = [[UIPasteboard generalPasteboard] string];
655 [params setObject:text forKey:@"text"];
658- (void)session:(
RDPSession *)session requestsAuthenticationWithParams:(NSMutableDictionary *)params
661 __block UIAlertController *alert = [UIAlertController
662 alertControllerWithTitle:NSLocalizedString(@"Credentials", @"Credentials title")
663 message:NSLocalizedString(@"Please provide the missing user information.",
664 @"Credentials input view message")
665 preferredStyle:UIAlertControllerStyleAlert];
668 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
670 setPlaceholder:NSLocalizedString(@"Username", @"Credentials Input Username hint")];
671 [textField setText:[params valueForKey:@"username"]];
672 [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
673 [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
677 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
679 setPlaceholder:NSLocalizedString(@"Password", @"Credentials Input Password hint")];
680 [textField setText:[params valueForKey:@"password"]];
681 [textField setSecureTextEntry:YES];
685 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
686 [textField setPlaceholder:NSLocalizedString(@"Domain", @"Credentials Input Domain hint")];
687 [textField setText:[params valueForKey:@"domain"]];
688 [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
689 [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
693 UIAlertAction *cancelAction =
694 [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
695 style:UIAlertActionStyleCancel
696 handler:^(UIAlertAction *action) {
697 [params setValue:[NSNumber numberWithBool:NO] forKey:@"result"];
698 [[session uiRequestCompleted] signal];
700 [alert addAction:cancelAction];
703 UIAlertAction *loginAction = [UIAlertAction
704 actionWithTitle:NSLocalizedString(@"Login", @"Login Button")
705 style:UIAlertActionStyleDefault
706 handler:^(UIAlertAction *action) {
707 NSArray *fields = [alert textFields];
708 [params setValue:[[fields objectAtIndex:0] text] forKey:@"username"];
709 [params setValue:[[fields objectAtIndex:1] text] forKey:@"password"];
710 [params setValue:[[fields objectAtIndex:2] text] forKey:@"domain"];
711 [params setValue:[NSNumber numberWithBool:YES] forKey:@"result"];
712 [[session uiRequestCompleted] signal];
714 [alert addAction:loginAction];
716 [alert setPreferredAction:loginAction];
718 [
self presentViewController:alert animated:YES completion:nil];
721- (void)session:(
RDPSession *)session verifyCertificateWithParams:(NSMutableDictionary *)params
723 NSString *message = [NSString
724 stringWithFormat:@"%@\n\n%@ %@",
725 NSLocalizedString(@"The identity of the remote computer cannot be "
726 @"verified. Do you want to connect anyway?",
727 @"Verify certificate view message"),
728 NSLocalizedString(@"Issuer:", @"Verify certificate view issuer label"),
729 [params valueForKey:@"issuer"]];
730 UIAlertController *alert =
731 [UIAlertController alertControllerWithTitle:nil
733 preferredStyle:UIAlertControllerStyleAlert];
735 [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"No", @"No Button")
736 style:UIAlertActionStyleCancel
737 handler:^(UIAlertAction *action) {
738 [params setValue:[NSNumber numberWithBool:NO]
740 [[session uiRequestCompleted] signal];
743 [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", @"Yes Button")
744 style:UIAlertActionStyleDefault
745 handler:^(UIAlertAction *action) {
746 [params setValue:[NSNumber numberWithBool:YES]
748 [[session uiRequestCompleted] signal];
751 [
self presentViewController:alert animated:YES completion:nil];
754- (CGSize)sizeForFitScreenForSession:(
RDPSession *)session
757 CGSize size = [
self sessionViewportFrame].size;
758 UIScreen *screen = [[
self view] window] ? [[[
self view] window] screen] : [UIScreen mainScreen];
760 [screen respondsToSelector:@selector(nativeScale)] ? [screen nativeScale] : [screen scale];
764 size.width = ceilf(size.width * scale);
765 size.height = ceilf(size.height * scale);
767 CGFloat maxDimension = MAX(size.width, size.height);
768 if (maxDimension > 4096.0f)
770 CGFloat downscale = 4096.0f / maxDimension;
771 size.width = floorf(size.width * downscale);
772 size.height = floorf(size.height * downscale);
775 size.width = MAX(64.0f, size.width);
776 size.height = MAX(64.0f, size.height);
780#pragma mark - Keyboard Toolbar Handlers
782- (void)showAdvancedKeyboardAnimated
785 if (_advanced_keyboard_view)
788 CGRect rect = CGRectMake(0, 0, [[
self view] bounds].size.width, [
self advancedKeyboardHeight]);
791 [_dummy_textfield setInputView:_advanced_keyboard_view];
792 [_dummy_textfield reloadInputViews];
793 [_keyboard_toolbar setExtendedKeyboardActive:YES];
796- (void)destroyAdvancedKeyboard
799 if (!_advanced_keyboard_view)
802 [_dummy_textfield setInputView:nil];
803 [_advanced_keyboard_view autorelease];
804 _advanced_keyboard_view = nil;
805 [_keyboard_toolbar setExtendedKeyboardActive:NO];
809- (IBAction)toggleKeyboardWhenOtherVisible:(
id)sender
811 if (!_advanced_keyboard_view)
814 [
self showAdvancedKeyboardAnimated];
819 [
self destroyAdvancedKeyboard];
820 [_dummy_textfield reloadInputViews];
824- (IBAction)toggleWinKey:(
id)sender
829- (IBAction)toggleShiftKey:(
id)sender
831 [[
RDPKeyboard getSharedRDPKeyboard] toggleShiftKey];
834- (IBAction)toggleCtrlKey:(
id)sender
836 [[
RDPKeyboard getSharedRDPKeyboard] toggleCtrlKey];
839- (IBAction)toggleAltKey:(
id)sender
844- (IBAction)toggleAltGrKey:(
id)sender
846 [[
RDPKeyboard getSharedRDPKeyboard] toggleAltGrKey];
849- (IBAction)pressEscKey:(
id)sender
851 [
self commitTextInput];
852 [[
RDPKeyboard getSharedRDPKeyboard] sendEscapeKeyStroke];
855- (IBAction)pressDeleteKey:(
id)sender
857 [
self commitTextInput];
858 [[
RDPKeyboard getSharedRDPKeyboard] sendDeleteKeyStroke];
861- (IBAction)pressTabKey:(
id)sender
863 [
self commitTextInput];
864 [[
RDPKeyboard getSharedRDPKeyboard] sendTabKeyStroke];
868#pragma mark event handlers
870- (IBAction)switchSession:(
id)sender
872 [
self suspendSession];
875- (IBAction)toggleKeyboard:(
id)sender
877 if (![
self isKeyboardActive])
878 [_dummy_textfield becomeFirstResponder];
880 [_dummy_textfield resignFirstResponder];
883- (IBAction)disconnectSession:(
id)sender
885 [_session disconnect];
888- (IBAction)cancelButtonPressed:(
id)sender
890 [_session disconnect];
894#pragma mark iOS Keyboard Notification Handlers
896- (void)keyboardWillShow:(NSNotification *)notification
899 if (!_advanced_keyboard_view)
902 [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
903 CGFloat height = frame.size.height - [_keyboard_toolbar bounds].size.height;
908 if ([
self isLandscape])
909 _keyboard_height_landscape = height;
911 _keyboard_height_portrait = height;
915 [
self centerSessionViewInViewport];
918 CGRect keyboardFrame =
919 [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
920 keyboardFrame = [[
self view] convertRect:keyboardFrame fromView:nil];
922 CGRect coveredArea = CGRectIntersection([_session_scrollview frame], keyboardFrame);
924 [
self setKeyboardOverlap:CGRectGetHeight(coveredArea)];
927- (void)keyboardWillHide:(NSNotification *)notification
930 [
self centerSessionViewInViewport];
931 [
self setKeyboardOverlap:0.0];
934- (void)keyboardDidHide:(NSNotification *)notification
937 if (![
self isKeyboardActive])
938 [
self destroyAdvancedKeyboard];
941 if (![
self isKeyboardActive] && [_session_view hardwareKeyboardActive])
942 [_session_view becomeFirstResponder];
945- (void)hardwareKeyboardChanged:(NSNotification *)notification
947 BOOL connected = (GCKeyboard.coalescedKeyboard != nil);
948 [_session_view setHardwareKeyboardActive:connected];
950 if (connected && ![
self isKeyboardActive])
951 [_session_view becomeFirstResponder];
953 [_session_view resignFirstResponder];
956- (void)localClipboardChanged:(NSNotification *)notification
959 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
960 if ([pasteboard changeCount] == _pasteboard_change_count)
963 _pasteboard_change_count = [pasteboard changeCount];
964 [_session sendClipboardHasText:[pasteboard hasStrings]];
967- (void)applicationDidEnterBackground:(NSNotification *)notification
971 [
self endBackgroundTask];
973 [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
974 [
self endBackgroundTask];
978- (void)applicationWillEnterForeground:(NSNotification *)notification
980 [
self endBackgroundTask];
984#pragma mark Gesture handlers
986- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
988 if (_pointer_is_indirect)
989 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
990 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
991 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
994- (void)handleSecondaryTap:(UITapGestureRecognizer *)gesture
996 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
997 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
998 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
1001- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
1003 if ([gesture state] == UIGestureRecognizerStateBegan)
1005 _long_press_active = YES;
1006 _mouse_drag_active = NO;
1008 else if ([gesture state] == UIGestureRecognizerStateEnded ||
1009 [gesture state] == UIGestureRecognizerStateCancelled ||
1010 [gesture state] == UIGestureRecognizerStateFailed)
1012 if (_mouse_drag_active)
1013 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
1014 else if ([gesture state] == UIGestureRecognizerStateEnded)
1016 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
1017 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
1020 _mouse_drag_active = NO;
1021 _long_press_active = NO;
1025- (void)handleMousePan:(UIPanGestureRecognizer *)gesture
1029 if (_pointer_is_indirect)
1031 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
1033 if ([gesture state] == UIGestureRecognizerStateBegan)
1035 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
1036 _mouse_drag_active = YES;
1038 else if ([gesture state] == UIGestureRecognizerStateEnded ||
1039 [gesture state] == UIGestureRecognizerStateCancelled ||
1040 [gesture state] == UIGestureRecognizerStateFailed)
1042 if (_mouse_drag_active)
1043 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
1044 _mouse_drag_active = NO;
1049 CGPoint location = [gesture locationInView:_session_scrollview];
1051 if ([gesture state] == UIGestureRecognizerStateBegan)
1053 _mouse_pan_active = YES;
1054 _last_mouse_pan_location = location;
1055 if (_long_press_active && !_mouse_drag_active)
1057 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
1058 _mouse_drag_active = YES;
1061 else if ([gesture state] == UIGestureRecognizerStateChanged)
1063 if (_long_press_active && !_mouse_drag_active)
1065 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
1066 _mouse_drag_active = YES;
1069 CGPoint delta = CGPointMake(location.x - _last_mouse_pan_location.x,
1070 location.y - _last_mouse_pan_location.y);
1071 [
self moveCursorByViewportDelta:delta];
1072 _last_mouse_pan_location = location;
1074 else if ([gesture state] == UIGestureRecognizerStateEnded ||
1075 [gesture state] == UIGestureRecognizerStateCancelled ||
1076 [gesture state] == UIGestureRecognizerStateFailed)
1078 _mouse_pan_active = NO;
1082- (void)handleHover:(UIHoverGestureRecognizer *)gesture
1084 if ([gesture state] != UIGestureRecognizerStateBegan &&
1085 [gesture state] != UIGestureRecognizerStateChanged)
1088 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
1091- (void)handleDoubleLongPress:(UILongPressGestureRecognizer *)gesture
1095 CGPoint pos = [gesture locationInView:_session_scrollview];
1096 CGPoint session_view_pos = [
self currentCursorViewPosition];
1098 if ([gesture state] == UIGestureRecognizerStateBegan)
1099 _prev_long_press_position = pos;
1100 else if ([gesture state] == UIGestureRecognizerStateChanged)
1102 int delta = _prev_long_press_position.y - pos.y;
1104 if (delta > GetScrollGestureDelta())
1106 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(YES)
1107 position:session_view_pos]];
1108 _prev_long_press_position = pos;
1110 else if (delta < -GetScrollGestureDelta())
1112 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(NO)
1113 position:session_view_pos]];
1114 _prev_long_press_position = pos;
1119- (void)handleScroll:(UIPanGestureRecognizer *)gesture
1121 CGFloat delta = [gesture translationInView:_session_view].y;
1122 if (fabs(delta) < GetScrollGestureDelta())
1125 CGPoint position = [
self currentCursorViewPosition];
1126 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(delta > 0)
1127 position:position]];
1128 [gesture setTranslation:CGPointZero inView:_session_view];
1131- (void)handleSingle3FingersTap:(UITapGestureRecognizer *)gesture
1133 [_session setToolbarVisible:![_session toolbarVisible]];
1134 [
self showSessionToolbar:[_session toolbarVisible]];
1137- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction
1138 styleForRegion:(UIPointerRegion *)region
1140 return [UIPointerStyle hiddenPointerStyle];
1143- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1144 shouldReceiveTouch:(UITouch *)touch
1147 if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
1148 [(UIPanGestureRecognizer *)gestureRecognizer allowedScrollTypesMask] != 0)
1151 _pointer_is_indirect = ([touch type] == UITouchTypeIndirectPointer);
1155 if (_pointer_is_indirect &&
1156 [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
1162- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1163 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
1166 ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
1167 [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) ||
1168 ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] &&
1169 [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]);
1175@implementation RDPSessionViewController (Private)
1178#pragma mark Helper functions
1180- (void)showConnectingViewWithText:(NSString *)text
1182 [
self hideConnectingView];
1185 [[NSBundle mainBundle] loadNibNamed:@"RDPConnectingView" owner:self options:nil];
1188 [_lbl_connecting setText:text];
1189 [_cancel_connect_button setTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
1190 forState:UIControlStateNormal];
1193 [_connecting_view setCenter:[[
self view] center]];
1194 [[_connecting_view layer] setCornerRadius:10];
1197 [[
self view] addSubview:_connecting_view];
1198 [_connecting_indicator_view startAnimating];
1201- (void)hideConnectingView
1203 if (!_connecting_view)
1207 [_connecting_indicator_view stopAnimating];
1208 [_connecting_view removeFromSuperview];
1209 [_connecting_view autorelease];
1210 _connecting_view = nil;
1213- (void)endBackgroundTask
1215 if (_background_task == UIBackgroundTaskInvalid)
1218 [[UIApplication sharedApplication] endBackgroundTask:_background_task];
1219 _background_task = UIBackgroundTaskInvalid;
1222- (CGRect)sessionViewportFrame
1225 if (IsPad() || ![[NSUserDefaults standardUserDefaults] boolForKey:
@"ui.avoid_screen_edges"])
1226 return [[self view] bounds];
1229 return UIEdgeInsetsInsetRect([[
self view] bounds], [[
self view] safeAreaInsets]);
1232- (void)fitSessionViewToViewport
1234 CGSize viewportSize = [_session_scrollview bounds].size;
1235 CGSize remoteSize = [_session_view bounds].size;
1236 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f || remoteSize.width <= 0.0f ||
1237 remoteSize.height <= 0.0f)
1241 MIN(viewportSize.width / remoteSize.width, viewportSize.height / remoteSize.height);
1242 [_session_scrollview setMinimumZoomScale:fitScale];
1243 [_session_scrollview setMaximumZoomScale:MAX(2.0f, fitScale)];
1244 [_session_scrollview setZoomScale:fitScale animated:NO];
1245 [_session_scrollview setContentOffset:CGPointZero animated:NO];
1246 _last_session_viewport_size = viewportSize;
1247 [
self centerSessionViewInViewport];
1250- (void)centerSessionViewInViewport
1252 CGSize viewportSize = [_session_scrollview bounds].size;
1253 CGRect sessionFrame = [_session_view frame];
1254 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f ||
1255 sessionFrame.size.width <= 0.0f || sessionFrame.size.height <= 0.0f)
1258 sessionFrame.origin.x = MAX((viewportSize.width - sessionFrame.size.width) * 0.5f, 0.0f);
1259 sessionFrame.origin.y = MAX((viewportSize.height - sessionFrame.size.height) * 0.5f, 0.0f);
1260 [_session_view setFrame:sessionFrame];
1262 [_session_scrollview
1263 setContentSize:CGSizeMake(MAX(viewportSize.width, sessionFrame.size.width),
1264 MAX(viewportSize.height, sessionFrame.size.height))];
1267- (void)setKeyboardOverlap:(CGFloat)overlap
1270 UIEdgeInsets inset = [_session_scrollview contentInset];
1271 inset.bottom = overlap;
1272 [_session_scrollview setContentInset:inset];
1274 CGPoint offset = [_session_scrollview contentOffset];
1275 CGFloat viewportHeight = [_session_scrollview bounds].size.height;
1278 if (overlap > 0.0 && _has_cursor_view_position)
1280 CGPoint cursor = [_session_scrollview convertPoint:_cursor_view_position
1281 fromView:_session_view];
1282 CGFloat visibleBottom = offset.y + viewportHeight - overlap;
1283 CGFloat hiddenHeight = cursor.y + KEYBOARD_CURSOR_MARGIN - visibleBottom;
1284 if (hiddenHeight > 0.0)
1285 offset.y += hiddenHeight;
1289 CGFloat maxOffsetY = [_session_scrollview contentSize].height + overlap - viewportHeight;
1290 offset.y = MAX(MIN(offset.y, maxOffsetY), 0.0);
1291 [_session_scrollview setContentOffset:offset animated:YES];
1294- (void)showSessionToolbar:(BOOL)show
1297 if (_session_toolbar_visible == show)
1303 UIEdgeInsets safe = [[
self view] safeAreaInsets];
1306 safe.top = MAX(safe.top, [
self systemMinimumLayoutMargins].leading);
1307 CGFloat toolbarWidth = [[
self view] bounds].size.width - safe.left - safe.right;
1311 [UIView animateWithDuration:0.4
1313 options:UIViewAnimationOptionCurveLinear
1315 [_session_toolbar setFrame:CGRectMake(safe.left, safe.top,
1316 toolbarWidth, TOOLBAR_HEIGHT)];
1319 _session_toolbar_visible = YES;
1323 [UIView animateWithDuration:0.4
1325 options:UIViewAnimationOptionCurveLinear
1327 [_session_toolbar setFrame:CGRectMake(safe.left, -TOOLBAR_HEIGHT,
1328 toolbarWidth, TOOLBAR_HEIGHT)];
1331 _session_toolbar_visible = NO;
1335- (void)initGestureRecognizers
1339 UITapGestureRecognizer *singleTapRecognizer =
1340 [[[UITapGestureRecognizer alloc] initWithTarget:self
1341 action:@selector(handleSingleTap:)] autorelease];
1342 [singleTapRecognizer setNumberOfTouchesRequired:1];
1343 [singleTapRecognizer setNumberOfTapsRequired:1];
1344 [singleTapRecognizer setDelegate:self];
1347 UILongPressGestureRecognizer *longPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1349 action:@selector(handleLongPress:)] autorelease];
1350 [longPressRecognizer setMinimumPressDuration:0.3];
1351 [longPressRecognizer setAllowableMovement:12.0];
1352 [longPressRecognizer setDelegate:self];
1355 UIPanGestureRecognizer *mousePanRecognizer =
1356 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1357 action:@selector(handleMousePan:)] autorelease];
1358 [mousePanRecognizer setMinimumNumberOfTouches:1];
1359 [mousePanRecognizer setMaximumNumberOfTouches:1];
1360 [mousePanRecognizer setDelegate:self];
1363 UIHoverGestureRecognizer *hoverRecognizer =
1364 [[[UIHoverGestureRecognizer alloc] initWithTarget:self
1365 action:@selector(handleHover:)] autorelease];
1366 [hoverRecognizer setDelegate:self];
1370 UITapGestureRecognizer *secondaryTapRecognizer = [[[UITapGestureRecognizer alloc]
1372 action:@selector(handleSecondaryTap:)] autorelease];
1373 [secondaryTapRecognizer setButtonMaskRequired:UIEventButtonMaskSecondary];
1374 [secondaryTapRecognizer setAllowedTouchTypes:@[@(UITouchTypeIndirectPointer)]];
1375 [secondaryTapRecognizer setDelegate:self];
1379 UIPanGestureRecognizer *scrollRecognizer =
1380 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1381 action:@selector(handleScroll:)] autorelease];
1382 [scrollRecognizer setAllowedScrollTypesMask:UIScrollTypeMaskAll];
1383 [scrollRecognizer setDelegate:self];
1386 UILongPressGestureRecognizer *doubleLongPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1388 action:@selector(handleDoubleLongPress:)] autorelease];
1389 [doubleLongPressRecognizer setNumberOfTouchesRequired:2];
1390 [doubleLongPressRecognizer setMinimumPressDuration:0.3];
1393 UITapGestureRecognizer *single3FingersTapRecognizer = [[[UITapGestureRecognizer alloc]
1395 action:@selector(handleSingle3FingersTap:)] autorelease];
1396 [single3FingersTapRecognizer setNumberOfTapsRequired:1];
1397 [single3FingersTapRecognizer setNumberOfTouchesRequired:3];
1398 [singleTapRecognizer requireGestureRecognizerToFail:longPressRecognizer];
1401 [[_session_scrollview panGestureRecognizer] setMinimumNumberOfTouches:2];
1404 [_session_scrollview addGestureRecognizer:singleTapRecognizer];
1405 [_session_scrollview addGestureRecognizer:longPressRecognizer];
1406 [_session_scrollview addGestureRecognizer:mousePanRecognizer];
1407 [_session_scrollview addGestureRecognizer:hoverRecognizer];
1408 [_session_scrollview addGestureRecognizer:secondaryTapRecognizer];
1409 [_session_scrollview addGestureRecognizer:scrollRecognizer];
1410 [_session_scrollview addGestureRecognizer:doubleLongPressRecognizer];
1411 [_session_scrollview addGestureRecognizer:single3FingersTapRecognizer];
1414 UIPointerInteraction *pointerInteraction =
1415 [[[UIPointerInteraction alloc] initWithDelegate:self] autorelease];
1416 [_session_view addInteraction:pointerInteraction];
1419- (void)suspendSession
1425 [[
self navigationController] popViewControllerAnimated:YES];
1428- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position
1430 CGPoint remote_position = [
self remotePositionForSessionViewPosition:position];
1431 return [NSDictionary
1432 dictionaryWithObjectsAndKeys:@"mouse", @"type", [NSNumber numberWithUnsignedShort:event],
1434 [NSNumber numberWithUnsignedShort:lrintf(remote_position.x)],
1436 [NSNumber numberWithUnsignedShort:lrintf(remote_position.y)],
1440- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position
1442 rdpSettings *settings = [_session getSessionParams];
1443 CGSize viewSize = [_session_view bounds].size;
1444 CGFloat desktopWidth =
1446 CGFloat desktopHeight =
1449 if ((viewSize.width > 0.0f) && (desktopWidth > 0.0f))
1450 position.x = position.x * desktopWidth / viewSize.width;
1451 if ((viewSize.height > 0.0f) && (desktopHeight > 0.0f))
1452 position.y = position.y * desktopHeight / viewSize.height;
1454 if (desktopWidth > 0.0f)
1455 position.x = MIN(MAX(position.x, 0.0f), desktopWidth - 1.0f);
1456 if (desktopHeight > 0.0f)
1457 position.y = MIN(MAX(position.y, 0.0f), desktopHeight - 1.0f);
1462- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position
1464 rdpSettings *settings = [_session getSessionParams];
1465 CGSize viewSize = [_session_view bounds].size;
1466 CGFloat desktopWidth =
1468 CGFloat desktopHeight =
1471 if ((desktopWidth > 0.0f) && (viewSize.width > 0.0f))
1472 position.x = position.x * viewSize.width / desktopWidth;
1473 if ((desktopHeight > 0.0f) && (viewSize.height > 0.0f))
1474 position.y = position.y * viewSize.height / desktopHeight;
1476 return [
self clampedSessionViewCursorPosition:position];
1479- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position
1481 CGSize viewSize = [_session_view bounds].size;
1482 if (viewSize.width > 0.0f)
1483 position.x = MIN(MAX(position.x, 0.0f), viewSize.width - 1.0f);
1484 if (viewSize.height > 0.0f)
1485 position.y = MIN(MAX(position.y, 0.0f), viewSize.height - 1.0f);
1489- (CGPoint)currentCursorViewPosition
1491 if (!_has_cursor_view_position)
1493 CGSize viewSize = [_session_view bounds].size;
1494 _cursor_view_position = CGPointMake(MAX(viewSize.width - 1.0, 0.0) * 0.5,
1495 MAX(viewSize.height - 1.0, 0.0) * 0.5);
1496 _has_cursor_view_position = YES;
1497 [_session_view setRemoteCursorPosition:_cursor_view_position];
1499 [_session_view showRemoteCursor];
1501 return _cursor_view_position;
1504- (void)moveCursorByViewportDelta:(CGPoint)delta
1506 CGPoint position = [
self currentCursorViewPosition];
1507 CGFloat zoomScale = [_session_scrollview zoomScale];
1508 if (zoomScale <= 0.0)
1511 position.x += delta.x / zoomScale;
1512 position.y += delta.y / zoomScale;
1513 position = [
self clampedSessionViewCursorPosition:position];
1514 _cursor_view_position = position;
1515 _has_cursor_view_position = YES;
1516 _has_user_moved_cursor = YES;
1519 [_session_view setRemoteCursorPosition:position];
1520 [
self handleMouseMoveForPosition:position];
1523- (void)moveCursorToSessionViewPosition:(CGPoint)position
1525 position = [
self clampedSessionViewCursorPosition:position];
1526 _cursor_view_position = position;
1527 _has_cursor_view_position = YES;
1528 _has_user_moved_cursor = YES;
1531 [_session_view setRemoteCursorPosition:position];
1532 [_session_view showRemoteCursor];
1533 [
self handleMouseMoveForPosition:position];
1536- (void)sendMouseButtonEvent:(
int)event
1539 [
self commitTextInput];
1540 CGPoint position = [
self currentCursorViewPosition];
1541 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:event position:position]];
1544- (void)handleMouseMoveForPosition:(CGPoint)position
1546 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:PTR_FLAGS_MOVE position:position]];
1549- (BOOL)isKeyboardActive
1551 return [_dummy_textfield isFirstResponder];
1554- (void)resetTextInput
1556 [_dummy_textfield setText:@""];
1557 [_sent_text release];
1558 _sent_text = [@"" retain];
1561- (void)commitTextInput
1563 if ([[_dummy_textfield text] length] == 0)
1567 [_dummy_textfield unmarkText];
1568 [
self textInputChanged:_dummy_textfield];
1569 [
self resetTextInput];
1574 CGSize size = [[
self view] bounds].size;
1575 return size.width > size.height;
1578- (CGFloat)advancedKeyboardHeight
1581 if ([
self isLandscape])
1582 return (_keyboard_height_landscape > 0.0) ? _keyboard_height_landscape
1583 : (IsPad() ? 400.0 : 200.0);
1584 return (_keyboard_height_portrait > 0.0) ? _keyboard_height_portrait
1585 : (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.