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"
21#define TOOLBAR_HEIGHT 44
22#define ADVANCED_KEYBOARD_HEIGHT 200
24@interface RDPSessionViewController (Private)
25- (void)showSessionToolbar:(BOOL)show;
26- (UIToolbar *)keyboardToolbar;
27- (void)initGestureRecognizers;
28- (void)suspendSession;
29- (void)fitSessionViewToViewport;
30- (void)centerSessionViewInViewport;
31- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position;
32- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position;
33- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position;
34- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position;
35- (void)handleMouseMoveForPosition:(CGPoint)position;
36- (CGPoint)currentCursorViewPosition;
37- (void)moveCursorByViewportDelta:(CGPoint)delta;
38- (void)moveCursorToSessionViewPosition:(CGPoint)position;
39- (void)sendMouseButtonEvent:(
int)event;
40- (BOOL)isKeyboardActive;
45#pragma mark class methods
47- (id)initWithNibName:(NSString *)nibNameOrNil
48 bundle:(NSBundle *)nibBundleOrNil
51 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
54 _session = [session retain];
55 [_session setDelegate:self];
56 _session_initilized = NO;
58 _advanced_keyboard_view = nil;
59 _last_session_viewport_size = CGSizeZero;
61 _session_toolbar_visible = NO;
63 _cursor_view_position = CGPointZero;
64 _last_mouse_pan_location = CGPointZero;
65 _has_cursor_view_position = NO;
66 _has_user_moved_cursor = NO;
67 _mouse_pan_active = NO;
68 _long_press_active = NO;
69 _mouse_drag_active = NO;
70 _pointer_is_indirect = NO;
87 _keyboard_toolbar = [[
self keyboardToolbar] retain];
88 [_dummy_textfield setInputAccessoryView:_keyboard_toolbar];
91 [
self initGestureRecognizers];
95 setFrame:CGRectMake(0.0, -TOOLBAR_HEIGHT, [[
self view] bounds].size.width, TOOLBAR_HEIGHT)];
103 [_session_scrollview setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
104 [_session_scrollview setContentInset:UIEdgeInsetsZero];
105 [_session_scrollview setScrollIndicatorInsets:UIEdgeInsetsZero];
106 [_session_scrollview setShowsHorizontalScrollIndicator:NO];
107 [_session_scrollview setShowsVerticalScrollIndicator:NO];
108 [_session_scrollview setAlwaysBounceHorizontal:NO];
109 [_session_scrollview setAlwaysBounceVertical:NO];
110 [_session_scrollview setBounces:NO];
113- (void)viewDidLayoutSubviews
115 [
super viewDidLayoutSubviews];
117 CGRect viewportFrame = [[
self view] bounds];
118 [_session_scrollview setFrame:viewportFrame];
120 CGSize viewportSize = [_session_scrollview bounds].size;
121 if (!CGSizeEqualToSize(viewportSize, _last_session_viewport_size))
122 [
self fitSessionViewToViewport];
124 [
self centerSessionViewInViewport];
127 const CGFloat margin = [
self systemMinimumLayoutMargins].leading;
128 [[[_keyboard_toolbar items] firstObject] setWidth:margin];
129 [[[_keyboard_toolbar items] lastObject] setWidth:margin];
132- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
137- (BOOL)prefersStatusBarHidden
142- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
144 return UIStatusBarAnimationSlide;
147- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
149 (void)fromInterfaceOrientation;
150 [
self centerSessionViewInViewport];
153- (void)didReceiveMemoryWarning
156 [
super didReceiveMemoryWarning];
163 [
super viewDidUnload];
168- (void)viewWillAppear:(BOOL)animated
170 [
super viewWillAppear:animated];
173 [
self setNeedsStatusBarAppearanceUpdate];
174 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
175 [[
self navigationController] setNavigationBarHidden:YES animated:animated];
176 if (@available(iOS 18.0, *))
177 [[
self tabBarController] setTabBarHidden:YES animated:animated];
179 [[[
self tabBarController] tabBar] setHidden:YES];
182 if ([_session isSuspended])
183 [
self sessionBitmapContextWillChange:_session];
186 [[
RDPKeyboard getSharedRDPKeyboard] initWithSession:_session delegate:self];
189- (void)viewDidAppear:(BOOL)animated
191 [
super viewDidAppear:animated];
193 if (!_session_initilized)
195 if ([_session isSuspended])
198 [
self sessionBitmapContextDidChange:_session];
203 _session_initilized = YES;
207- (void)viewWillDisappear:(BOOL)animated
209 [
super viewWillDisappear:animated];
210 if (_mouse_drag_active)
211 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
212 _mouse_drag_active = NO;
213 _mouse_pan_active = NO;
214 _long_press_active = NO;
216 [[
self navigationController] setNeedsStatusBarAppearanceUpdate];
217 [[
self navigationController] setNavigationBarHidden:NO animated:animated];
218 if (@available(iOS 18.0, *))
219 [[
self tabBarController] setTabBarHidden:NO animated:animated];
221 [[[
self tabBarController] tabBar] setHidden:NO];
227 [
self showSessionToolbar:NO];
228 [_dummy_textfield resignFirstResponder];
234 [[NSNotificationCenter defaultCenter] removeObserver:self];
237 [_session setDelegate:nil];
239 [_advanced_keyboard_view release];
240 [_keyboard_toolbar release];
246#pragma mark ScrollView delegate methods
248- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
250 return _session_view;
253- (void)scrollViewDidZoom:(UIScrollView *)scrollView
255 [
self centerSessionViewInViewport];
258- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView
259 withView:(UIView *)view
260 atScale:(CGFloat)scale
262 NSLog(
@"New zoom scale: %f", scale);
263 [
self centerSessionViewInViewport];
264 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
268#pragma mark TextField delegate methods
269- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
274- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
276 [
self destroyAdvancedKeyboard];
280- (BOOL)textField:(UITextField *)textField
281 shouldChangeCharactersInRange:(NSRange)range
282 replacementString:(NSString *)string
284 if ([
string length] > 0)
286 for (
int i = 0; i < [string length]; i++)
288 unichar curChar = [string characterAtIndex:i];
292 [[
RDPKeyboard getSharedRDPKeyboard] sendEnterKeyStroke];
294 [[
RDPKeyboard getSharedRDPKeyboard] sendUnicode:curChar];
299 [[
RDPKeyboard getSharedRDPKeyboard] sendBackspaceKeyStroke];
306#pragma mark AdvancedKeyboardDelegate functions
307- (void)advancedKeyPressedVKey:(NSInteger)key
309 [[
RDPKeyboard getSharedRDPKeyboard] sendVirtualKeyCode:key];
312- (void)advancedKeyPressedUnicode:(NSInteger)key
314 [[
RDPKeyboard getSharedRDPKeyboard] sendUnicode:key];
317#pragma mark - RDP keyboard handler
319- (void)modifiersChangedForKeyboard:(
RDPKeyboard *)keyboard
321 UIBarButtonItem *curItem;
328 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
329 [curItem setStyle:[keyboard shiftPressed] ? UIBarButtonItemStyleDone
330 : UIBarButtonItemStylePlain];
335 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
337 setStyle:[keyboard ctrlPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
341 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
342 [curItem setStyle:[keyboard winPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
346 curItem = (UIBarButtonItem *)[[_keyboard_toolbar items] objectAtIndex:objectIdx];
347 [curItem setStyle:[keyboard altPressed] ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
351#pragma mark RDPSessionDelegate functions
353- (void)session:(
RDPSession *)session didFailToConnect:(
int)reason
356 [_connecting_indicator_view stopAnimating];
357 [_connecting_view removeFromSuperview];
358 [_connecting_view autorelease];
361 [[
self navigationController] popViewControllerAnimated:YES];
364- (void)sessionWillConnect:(
RDPSession *)session
367 [[NSBundle mainBundle] loadNibNamed:@"RDPConnectingView" owner:self options:nil];
370 [_lbl_connecting setText:NSLocalizedString(@"Connecting", @"Connecting progress view - label")];
371 [_cancel_connect_button setTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
372 forState:UIControlStateNormal];
375 [_connecting_view setCenter:[[
self view] center]];
376 [[_connecting_view layer] setCornerRadius:10];
379 [[
self view] addSubview:_connecting_view];
380 [_connecting_indicator_view startAnimating];
383- (void)sessionDidConnect:(
RDPSession *)session
386 [[NSNotificationCenter defaultCenter] addObserver:self
387 selector:@selector(keyboardWillShow:)
388 name:UIKeyboardWillShowNotification
390 [[NSNotificationCenter defaultCenter] addObserver:self
391 selector:@selector(keyboardWillHide:)
392 name:UIKeyboardWillHideNotification
394 [[NSNotificationCenter defaultCenter] addObserver:self
395 selector:@selector(keyboardDidHide:)
396 name:UIKeyboardDidHideNotification
400 [[NSNotificationCenter defaultCenter] addObserver:self
401 selector:@selector(hardwareKeyboardChanged:)
402 name:GCKeyboardDidConnectNotification
404 [[NSNotificationCenter defaultCenter] addObserver:self
405 selector:@selector(hardwareKeyboardChanged:)
406 name:GCKeyboardDidDisconnectNotification
408 [
self hardwareKeyboardChanged:nil];
411 [_connecting_indicator_view stopAnimating];
412 [_connecting_view removeFromSuperview];
413 [_connecting_view autorelease];
419 rdpSettings *sess_params = [session getSessionParams];
424 if (([orig_params intForKey:
@"width"] != width &&
425 [orig_params intForKey:
@"width"] != (width + 1ul)) ||
426 [orig_params intForKey:
@"height"] != height || [orig_params intForKey:
@"colors"] != depth)
430 [NSString stringWithFormat:NSLocalizedString(
431 @"The server changed the screen settings to %dx%dx%d",
432 @"Screen settings not supported message with width, "
433 @"height and colors parameter"),
434 width, height, depth];
435 [[
self view] makeToast:message duration:ToastDurationNormal position:@"bottom"];
439- (void)sessionWillDisconnect:(
RDPSession *)session
443- (void)sessionDidDisconnect:(
RDPSession *)session
446 [[
self navigationController] popViewControllerAnimated:YES];
449- (void)sessionBitmapContextWillChange:(
RDPSession *)session
451 [_session_view prepareForBitmapContextChange];
454 rdpSettings *sess_params = [session getSessionParams];
460 [_session_scrollview setZoomScale:1.0];
461 [_session_view setFrame:view_rect];
462 [_session_scrollview setContentSize:view_rect.size];
463 _has_cursor_view_position = NO;
464 _has_user_moved_cursor = NO;
465 _last_session_viewport_size = CGSizeZero;
466 [
self fitSessionViewToViewport];
470 setToolbarVisible:![[NSUserDefaults standardUserDefaults] boolForKey:@"ui.hide_tool_bar"]];
471 [
self showSessionToolbar:[_session toolbarVisible]];
474- (void)sessionBitmapContextDidChange:(
RDPSession *)session
477 [_session_view setSession:session];
478 [_session_view setDefaultRemoteCursor];
479 if (!_has_cursor_view_position)
480 (void)[
self currentCursorViewPosition];
483 [_session_view setNeedsDisplayInRemoteRect:[_session_view bounds]];
486- (void)session:(
RDPSession *)session needsRedrawInRect:(CGRect)rect
488 [_session_view setNeedsDisplayInRemoteRect:rect];
494 [_session_view setRemoteCursor:cursor];
496 if (!_has_user_moved_cursor &&
497 (!_has_cursor_view_position || CGPointEqualToPoint(_cursor_view_position, CGPointZero)))
499 _has_cursor_view_position = NO;
500 (void)[
self currentCursorViewPosition];
504- (void)session:(
RDPSession *)session didMoveRemoteCursor:(CGPoint)position
507 if (_mouse_pan_active || _long_press_active)
509 CGPoint viewPosition = [
self sessionViewPositionForRemotePosition:position];
510 if (!_has_user_moved_cursor && CGPointEqualToPoint(position, CGPointZero) &&
511 _has_cursor_view_position && !CGPointEqualToPoint(_cursor_view_position, CGPointZero))
514 _cursor_view_position = viewPosition;
515 _has_cursor_view_position = YES;
516 [_session_view setRemoteCursorPosition:viewPosition];
519- (void)sessionDidHideRemoteCursor:(
RDPSession *)session
522 [_session_view hideRemoteCursor];
525- (void)sessionDidSetDefaultRemoteCursor:(
RDPSession *)session
528 [_session_view setDefaultRemoteCursor];
531- (void)session:(
RDPSession *)session requestsAuthenticationWithParams:(NSMutableDictionary *)params
534 __block UIAlertController *alert = [UIAlertController
535 alertControllerWithTitle:NSLocalizedString(@"Credentials", @"Credentials title")
536 message:NSLocalizedString(@"Please provide the missing user information.",
537 @"Credentials input view message")
538 preferredStyle:UIAlertControllerStyleAlert];
541 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
543 setPlaceholder:NSLocalizedString(@"Username", @"Credentials Input Username hint")];
544 [textField setText:[params valueForKey:@"username"]];
545 [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
546 [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
550 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
552 setPlaceholder:NSLocalizedString(@"Password", @"Credentials Input Password hint")];
553 [textField setText:[params valueForKey:@"password"]];
554 [textField setSecureTextEntry:YES];
558 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
559 [textField setPlaceholder:NSLocalizedString(@"Domain", @"Credentials Input Domain hint")];
560 [textField setText:[params valueForKey:@"domain"]];
561 [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
562 [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
566 UIAlertAction *cancelAction =
567 [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel Button")
568 style:UIAlertActionStyleCancel
569 handler:^(UIAlertAction *action) {
570 [params setValue:[NSNumber numberWithBool:NO] forKey:@"result"];
571 [[session uiRequestCompleted] signal];
573 [alert addAction:cancelAction];
576 UIAlertAction *loginAction = [UIAlertAction
577 actionWithTitle:NSLocalizedString(@"Login", @"Login Button")
578 style:UIAlertActionStyleDefault
579 handler:^(UIAlertAction *action) {
580 NSArray *fields = [alert textFields];
581 [params setValue:[[fields objectAtIndex:0] text] forKey:@"username"];
582 [params setValue:[[fields objectAtIndex:1] text] forKey:@"password"];
583 [params setValue:[[fields objectAtIndex:2] text] forKey:@"domain"];
584 [params setValue:[NSNumber numberWithBool:YES] forKey:@"result"];
585 [[session uiRequestCompleted] signal];
587 [alert addAction:loginAction];
589 [alert setPreferredAction:loginAction];
591 [
self presentViewController:alert animated:YES completion:nil];
594- (void)session:(
RDPSession *)session verifyCertificateWithParams:(NSMutableDictionary *)params
596 NSString *message = [NSString
597 stringWithFormat:@"%@\n\n%@ %@",
598 NSLocalizedString(@"The identity of the remote computer cannot be "
599 @"verified. Do you want to connect anyway?",
600 @"Verify certificate view message"),
601 NSLocalizedString(@"Issuer:", @"Verify certificate view issuer label"),
602 [params valueForKey:@"issuer"]];
603 UIAlertController *alert =
604 [UIAlertController alertControllerWithTitle:nil
606 preferredStyle:UIAlertControllerStyleAlert];
608 [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"No", @"No Button")
609 style:UIAlertActionStyleCancel
610 handler:^(UIAlertAction *action) {
611 [params setValue:[NSNumber numberWithBool:NO]
613 [[session uiRequestCompleted] signal];
616 [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", @"Yes Button")
617 style:UIAlertActionStyleDefault
618 handler:^(UIAlertAction *action) {
619 [params setValue:[NSNumber numberWithBool:YES]
621 [[session uiRequestCompleted] signal];
624 [
self presentViewController:alert animated:YES completion:nil];
627- (CGSize)sizeForFitScreenForSession:(
RDPSession *)session
630 CGSize size = [
self view].bounds.size;
631 UIScreen *screen = [[
self view] window] ? [[[
self view] window] screen] : [UIScreen mainScreen];
633 [screen respondsToSelector:@selector(nativeScale)] ? [screen nativeScale] : [screen scale];
637 size.width = ceilf(size.width * scale);
638 size.height = ceilf(size.height * scale);
640 CGFloat maxDimension = MAX(size.width, size.height);
641 if (maxDimension > 4096.0f)
643 CGFloat downscale = 4096.0f / maxDimension;
644 size.width = floorf(size.width * downscale);
645 size.height = floorf(size.height * downscale);
648 size.width = MAX(64.0f, size.width);
649 size.height = MAX(64.0f, size.height);
653#pragma mark - Keyboard Toolbar Handlers
655- (void)showAdvancedKeyboardAnimated
658 if (_advanced_keyboard_view)
661 CGRect rect = CGRectMake(0, 0, [[
self view] bounds].size.width, ADVANCED_KEYBOARD_HEIGHT);
664 [_dummy_textfield setInputView:_advanced_keyboard_view];
665 [_dummy_textfield reloadInputViews];
668- (void)destroyAdvancedKeyboard
671 if (!_advanced_keyboard_view)
674 [_dummy_textfield setInputView:nil];
675 [_advanced_keyboard_view autorelease];
676 _advanced_keyboard_view = nil;
680- (IBAction)toggleKeyboardWhenOtherVisible:(
id)sender
682 if (!_advanced_keyboard_view)
685 [
self showAdvancedKeyboardAnimated];
690 [
self destroyAdvancedKeyboard];
691 [_dummy_textfield reloadInputViews];
695- (IBAction)toggleWinKey:(
id)sender
700- (IBAction)toggleShiftKey:(
id)sender
702 [[
RDPKeyboard getSharedRDPKeyboard] toggleShiftKey];
705- (IBAction)toggleCtrlKey:(
id)sender
707 [[
RDPKeyboard getSharedRDPKeyboard] toggleCtrlKey];
710- (IBAction)toggleAltKey:(
id)sender
715- (IBAction)pressEscKey:(
id)sender
717 [[
RDPKeyboard getSharedRDPKeyboard] sendEscapeKeyStroke];
721#pragma mark event handlers
723- (IBAction)switchSession:(
id)sender
725 [
self suspendSession];
728- (IBAction)toggleKeyboard:(
id)sender
730 if (![
self isKeyboardActive])
731 [_dummy_textfield becomeFirstResponder];
733 [_dummy_textfield resignFirstResponder];
736- (IBAction)toggleExtKeyboard:(
id)sender
739 if ([
self isKeyboardActive] && !_advanced_keyboard_view)
740 [
self toggleKeyboardWhenOtherVisible:nil];
743 if (![
self isKeyboardActive])
744 [
self showAdvancedKeyboardAnimated];
745 [
self toggleKeyboard:nil];
749- (IBAction)disconnectSession:(
id)sender
751 [_session disconnect];
754- (IBAction)cancelButtonPressed:(
id)sender
756 [_session disconnect];
760#pragma mark iOS Keyboard Notification Handlers
762- (void)keyboardWillShow:(NSNotification *)notification
765 [
self centerSessionViewInViewport];
768- (void)keyboardWillHide:(NSNotification *)notification
771 [
self centerSessionViewInViewport];
774- (void)keyboardDidHide:(NSNotification *)notification
777 if (![
self isKeyboardActive])
778 [
self destroyAdvancedKeyboard];
781 if (![
self isKeyboardActive] && [_session_view hardwareKeyboardActive])
782 [_session_view becomeFirstResponder];
785- (void)hardwareKeyboardChanged:(NSNotification *)notification
787 BOOL connected = (GCKeyboard.coalescedKeyboard != nil);
788 [_session_view setHardwareKeyboardActive:connected];
790 if (connected && ![
self isKeyboardActive])
791 [_session_view becomeFirstResponder];
793 [_session_view resignFirstResponder];
797#pragma mark Gesture handlers
799- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
801 if (_pointer_is_indirect)
802 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
803 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
804 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
807- (void)handleSecondaryTap:(UITapGestureRecognizer *)gesture
809 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
810 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
811 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
814- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
816 if ([gesture state] == UIGestureRecognizerStateBegan)
818 _long_press_active = YES;
819 _mouse_drag_active = NO;
821 else if ([gesture state] == UIGestureRecognizerStateEnded ||
822 [gesture state] == UIGestureRecognizerStateCancelled ||
823 [gesture state] == UIGestureRecognizerStateFailed)
825 if (_mouse_drag_active)
826 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
827 else if ([gesture state] == UIGestureRecognizerStateEnded)
829 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(YES)];
830 [
self sendMouseButtonEvent:GetRightMouseButtonClickEvent(NO)];
833 _mouse_drag_active = NO;
834 _long_press_active = NO;
838- (void)handleMousePan:(UIPanGestureRecognizer *)gesture
842 if (_pointer_is_indirect)
844 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
846 if ([gesture state] == UIGestureRecognizerStateBegan)
848 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
849 _mouse_drag_active = YES;
851 else if ([gesture state] == UIGestureRecognizerStateEnded ||
852 [gesture state] == UIGestureRecognizerStateCancelled ||
853 [gesture state] == UIGestureRecognizerStateFailed)
855 if (_mouse_drag_active)
856 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(NO)];
857 _mouse_drag_active = NO;
862 CGPoint location = [gesture locationInView:_session_scrollview];
864 if ([gesture state] == UIGestureRecognizerStateBegan)
866 _mouse_pan_active = YES;
867 _last_mouse_pan_location = location;
868 if (_long_press_active && !_mouse_drag_active)
870 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
871 _mouse_drag_active = YES;
874 else if ([gesture state] == UIGestureRecognizerStateChanged)
876 if (_long_press_active && !_mouse_drag_active)
878 [
self sendMouseButtonEvent:GetLeftMouseButtonClickEvent(YES)];
879 _mouse_drag_active = YES;
882 CGPoint delta = CGPointMake(location.x - _last_mouse_pan_location.x,
883 location.y - _last_mouse_pan_location.y);
884 [
self moveCursorByViewportDelta:delta];
885 _last_mouse_pan_location = location;
887 else if ([gesture state] == UIGestureRecognizerStateEnded ||
888 [gesture state] == UIGestureRecognizerStateCancelled ||
889 [gesture state] == UIGestureRecognizerStateFailed)
891 _mouse_pan_active = NO;
895- (void)handleHover:(UIHoverGestureRecognizer *)gesture
897 if ([gesture state] != UIGestureRecognizerStateBegan &&
898 [gesture state] != UIGestureRecognizerStateChanged)
901 [
self moveCursorToSessionViewPosition:[gesture locationInView:_session_view]];
904- (void)handleDoubleLongPress:(UILongPressGestureRecognizer *)gesture
908 CGPoint pos = [gesture locationInView:_session_scrollview];
909 CGPoint session_view_pos = [
self currentCursorViewPosition];
911 if ([gesture state] == UIGestureRecognizerStateBegan)
912 _prev_long_press_position = pos;
913 else if ([gesture state] == UIGestureRecognizerStateChanged)
915 int delta = _prev_long_press_position.y - pos.y;
917 if (delta > GetScrollGestureDelta())
919 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(YES)
920 position:session_view_pos]];
921 _prev_long_press_position = pos;
923 else if (delta < -GetScrollGestureDelta())
925 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(NO)
926 position:session_view_pos]];
927 _prev_long_press_position = pos;
932- (void)handleScroll:(UIPanGestureRecognizer *)gesture
934 CGFloat delta = [gesture translationInView:_session_view].y;
935 if (fabs(delta) < GetScrollGestureDelta())
938 CGPoint position = [
self currentCursorViewPosition];
939 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:GetMouseWheelEvent(delta > 0)
941 [gesture setTranslation:CGPointZero inView:_session_view];
944- (void)handleSingle3FingersTap:(UITapGestureRecognizer *)gesture
946 [_session setToolbarVisible:![_session toolbarVisible]];
947 [
self showSessionToolbar:[_session toolbarVisible]];
950- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction
951 styleForRegion:(UIPointerRegion *)region
953 return [UIPointerStyle hiddenPointerStyle];
956- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
957 shouldReceiveTouch:(UITouch *)touch
960 if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
961 [(UIPanGestureRecognizer *)gestureRecognizer allowedScrollTypesMask] != 0)
964 _pointer_is_indirect = ([touch type] == UITouchTypeIndirectPointer);
968 if (_pointer_is_indirect &&
969 [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
975- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
976 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
979 ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
980 [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) ||
981 ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] &&
982 [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]);
988@implementation RDPSessionViewController (Private)
991#pragma mark Helper functions
993- (void)fitSessionViewToViewport
995 CGSize viewportSize = [_session_scrollview bounds].size;
996 CGSize remoteSize = [_session_view bounds].size;
997 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f || remoteSize.width <= 0.0f ||
998 remoteSize.height <= 0.0f)
1002 MIN(viewportSize.width / remoteSize.width, viewportSize.height / remoteSize.height);
1003 [_session_scrollview setMinimumZoomScale:fitScale];
1004 [_session_scrollview setMaximumZoomScale:MAX(2.0f, fitScale)];
1005 [_session_scrollview setZoomScale:fitScale animated:NO];
1006 [_session_scrollview setContentOffset:CGPointZero animated:NO];
1007 _last_session_viewport_size = viewportSize;
1008 [
self centerSessionViewInViewport];
1011- (void)centerSessionViewInViewport
1013 CGSize viewportSize = [_session_scrollview bounds].size;
1014 CGRect sessionFrame = [_session_view frame];
1015 if (viewportSize.width <= 0.0f || viewportSize.height <= 0.0f ||
1016 sessionFrame.size.width <= 0.0f || sessionFrame.size.height <= 0.0f)
1019 sessionFrame.origin.x = MAX((viewportSize.width - sessionFrame.size.width) * 0.5f, 0.0f);
1020 sessionFrame.origin.y = MAX((viewportSize.height - sessionFrame.size.height) * 0.5f, 0.0f);
1021 [_session_view setFrame:sessionFrame];
1023 [_session_scrollview
1024 setContentSize:CGSizeMake(MAX(viewportSize.width, sessionFrame.size.width),
1025 MAX(viewportSize.height, sessionFrame.size.height))];
1028- (void)showSessionToolbar:(BOOL)show
1031 if (_session_toolbar_visible == show)
1037 UIEdgeInsets safe = [[
self view] safeAreaInsets];
1040 safe.top = MAX(safe.top, [
self systemMinimumLayoutMargins].leading);
1041 CGFloat toolbarWidth = [[
self view] bounds].size.width - safe.left - safe.right;
1045 [UIView animateWithDuration:0.4
1047 options:UIViewAnimationOptionCurveLinear
1049 [_session_toolbar setFrame:CGRectMake(safe.left, safe.top,
1050 toolbarWidth, TOOLBAR_HEIGHT)];
1053 _session_toolbar_visible = YES;
1057 [UIView animateWithDuration:0.4
1059 options:UIViewAnimationOptionCurveLinear
1061 [_session_toolbar setFrame:CGRectMake(safe.left, -TOOLBAR_HEIGHT,
1062 toolbarWidth, TOOLBAR_HEIGHT)];
1065 _session_toolbar_visible = NO;
1069- (UIToolbar *)keyboardToolbar
1071 UIToolbar *keyboard_toolbar = [[[UIToolbar alloc] initWithFrame:CGRectNull] autorelease];
1072 [keyboard_toolbar setBarStyle:UIBarStyleBlack];
1074 UIBarButtonItem *esc_btn =
1075 [[[UIBarButtonItem alloc] initWithTitle:@"Esc"
1076 style:UIBarButtonItemStylePlain
1078 action:@selector(pressEscKey:)] autorelease];
1080 [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"toolbar_icon_win"
1082 UIBarButtonItem *win_btn =
1083 [[[UIBarButtonItem alloc] initWithImage:win_icon
1084 style:UIBarButtonItemStylePlain
1086 action:@selector(toggleWinKey:)] autorelease];
1087 UIBarButtonItem *ctrl_btn =
1088 [[[UIBarButtonItem alloc] initWithTitle:@"Ctrl"
1089 style:UIBarButtonItemStylePlain
1091 action:@selector(toggleCtrlKey:)] autorelease];
1092 UIBarButtonItem *alt_btn =
1093 [[[UIBarButtonItem alloc] initWithTitle:@"Alt"
1094 style:UIBarButtonItemStylePlain
1096 action:@selector(toggleAltKey:)] autorelease];
1097 UIBarButtonItem *ext_btn = [[[UIBarButtonItem alloc]
1098 initWithTitle:@"Ext"
1099 style:UIBarButtonItemStylePlain
1101 action:@selector(toggleKeyboardWhenOtherVisible:)] autorelease];
1102 UIBarButtonItem *flex_spacer =
1103 [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
1105 action:nil] autorelease];
1107 UIBarButtonItem *leading_spacer = [UIBarButtonItem fixedSpaceItemOfWidth:0];
1108 UIBarButtonItem *trailing_spacer = [UIBarButtonItem fixedSpaceItemOfWidth:0];
1114 UIBarButtonItem *shift_btn =
1115 [[[UIBarButtonItem alloc] initWithTitle:@"Shift"
1116 style:UIBarButtonItemStylePlain
1118 action:@selector(toggleShiftKey:)] autorelease];
1119 items = [NSArray arrayWithObjects:leading_spacer, esc_btn, flex_spacer, shift_btn,
1120 flex_spacer, ctrl_btn, flex_spacer, win_btn, flex_spacer,
1121 alt_btn, flex_spacer, ext_btn, trailing_spacer, nil];
1125 items = [NSArray arrayWithObjects:leading_spacer, esc_btn, flex_spacer, ctrl_btn,
1126 flex_spacer, win_btn, flex_spacer, alt_btn, flex_spacer,
1127 ext_btn, trailing_spacer, nil];
1130 [keyboard_toolbar setItems:items];
1131 [keyboard_toolbar sizeToFit];
1132 return keyboard_toolbar;
1135- (void)initGestureRecognizers
1139 UITapGestureRecognizer *singleTapRecognizer =
1140 [[[UITapGestureRecognizer alloc] initWithTarget:self
1141 action:@selector(handleSingleTap:)] autorelease];
1142 [singleTapRecognizer setNumberOfTouchesRequired:1];
1143 [singleTapRecognizer setNumberOfTapsRequired:1];
1144 [singleTapRecognizer setDelegate:self];
1147 UILongPressGestureRecognizer *longPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1149 action:@selector(handleLongPress:)] autorelease];
1150 [longPressRecognizer setMinimumPressDuration:0.45];
1151 [longPressRecognizer setAllowableMovement:12.0];
1152 [longPressRecognizer setDelegate:self];
1155 UIPanGestureRecognizer *mousePanRecognizer =
1156 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1157 action:@selector(handleMousePan:)] autorelease];
1158 [mousePanRecognizer setMinimumNumberOfTouches:1];
1159 [mousePanRecognizer setMaximumNumberOfTouches:1];
1160 [mousePanRecognizer setDelegate:self];
1163 UIHoverGestureRecognizer *hoverRecognizer =
1164 [[[UIHoverGestureRecognizer alloc] initWithTarget:self
1165 action:@selector(handleHover:)] autorelease];
1166 [hoverRecognizer setDelegate:self];
1170 UITapGestureRecognizer *secondaryTapRecognizer = [[[UITapGestureRecognizer alloc]
1172 action:@selector(handleSecondaryTap:)] autorelease];
1173 [secondaryTapRecognizer setButtonMaskRequired:UIEventButtonMaskSecondary];
1174 [secondaryTapRecognizer setAllowedTouchTypes:@[@(UITouchTypeIndirectPointer)]];
1175 [secondaryTapRecognizer setDelegate:self];
1179 UIPanGestureRecognizer *scrollRecognizer =
1180 [[[UIPanGestureRecognizer alloc] initWithTarget:self
1181 action:@selector(handleScroll:)] autorelease];
1182 [scrollRecognizer setAllowedScrollTypesMask:UIScrollTypeMaskAll];
1183 [scrollRecognizer setDelegate:self];
1186 UILongPressGestureRecognizer *doubleLongPressRecognizer = [[[UILongPressGestureRecognizer alloc]
1188 action:@selector(handleDoubleLongPress:)] autorelease];
1189 [doubleLongPressRecognizer setNumberOfTouchesRequired:2];
1190 [doubleLongPressRecognizer setMinimumPressDuration:0.5];
1193 UITapGestureRecognizer *single3FingersTapRecognizer = [[[UITapGestureRecognizer alloc]
1195 action:@selector(handleSingle3FingersTap:)] autorelease];
1196 [single3FingersTapRecognizer setNumberOfTapsRequired:1];
1197 [single3FingersTapRecognizer setNumberOfTouchesRequired:3];
1198 [singleTapRecognizer requireGestureRecognizerToFail:longPressRecognizer];
1201 [[_session_scrollview panGestureRecognizer] setMinimumNumberOfTouches:2];
1204 [_session_scrollview addGestureRecognizer:singleTapRecognizer];
1205 [_session_scrollview addGestureRecognizer:longPressRecognizer];
1206 [_session_scrollview addGestureRecognizer:mousePanRecognizer];
1207 [_session_scrollview addGestureRecognizer:hoverRecognizer];
1208 [_session_scrollview addGestureRecognizer:secondaryTapRecognizer];
1209 [_session_scrollview addGestureRecognizer:scrollRecognizer];
1210 [_session_scrollview addGestureRecognizer:doubleLongPressRecognizer];
1211 [_session_scrollview addGestureRecognizer:single3FingersTapRecognizer];
1214 UIPointerInteraction *pointerInteraction =
1215 [[[UIPointerInteraction alloc] initWithDelegate:self] autorelease];
1216 [_session_view addInteraction:pointerInteraction];
1219- (void)suspendSession
1225 [[
self navigationController] popViewControllerAnimated:YES];
1228- (NSDictionary *)eventDescriptorForMouseEvent:(
int)event position:(CGPoint)position
1230 CGPoint remote_position = [
self remotePositionForSessionViewPosition:position];
1231 return [NSDictionary
1232 dictionaryWithObjectsAndKeys:@"mouse", @"type", [NSNumber numberWithUnsignedShort:event],
1234 [NSNumber numberWithUnsignedShort:lrintf(remote_position.x)],
1236 [NSNumber numberWithUnsignedShort:lrintf(remote_position.y)],
1240- (CGPoint)remotePositionForSessionViewPosition:(CGPoint)position
1242 rdpSettings *settings = [_session getSessionParams];
1243 CGSize viewSize = [_session_view bounds].size;
1244 CGFloat desktopWidth =
1246 CGFloat desktopHeight =
1249 if ((viewSize.width > 0.0f) && (desktopWidth > 0.0f))
1250 position.x = position.x * desktopWidth / viewSize.width;
1251 if ((viewSize.height > 0.0f) && (desktopHeight > 0.0f))
1252 position.y = position.y * desktopHeight / viewSize.height;
1254 if (desktopWidth > 0.0f)
1255 position.x = MIN(MAX(position.x, 0.0f), desktopWidth - 1.0f);
1256 if (desktopHeight > 0.0f)
1257 position.y = MIN(MAX(position.y, 0.0f), desktopHeight - 1.0f);
1262- (CGPoint)sessionViewPositionForRemotePosition:(CGPoint)position
1264 rdpSettings *settings = [_session getSessionParams];
1265 CGSize viewSize = [_session_view bounds].size;
1266 CGFloat desktopWidth =
1268 CGFloat desktopHeight =
1271 if ((desktopWidth > 0.0f) && (viewSize.width > 0.0f))
1272 position.x = position.x * viewSize.width / desktopWidth;
1273 if ((desktopHeight > 0.0f) && (viewSize.height > 0.0f))
1274 position.y = position.y * viewSize.height / desktopHeight;
1276 return [
self clampedSessionViewCursorPosition:position];
1279- (CGPoint)clampedSessionViewCursorPosition:(CGPoint)position
1281 CGSize viewSize = [_session_view bounds].size;
1282 if (viewSize.width > 0.0f)
1283 position.x = MIN(MAX(position.x, 0.0f), viewSize.width - 1.0f);
1284 if (viewSize.height > 0.0f)
1285 position.y = MIN(MAX(position.y, 0.0f), viewSize.height - 1.0f);
1289- (CGPoint)currentCursorViewPosition
1291 if (!_has_cursor_view_position)
1293 CGSize viewSize = [_session_view bounds].size;
1294 _cursor_view_position = CGPointMake(MAX(viewSize.width - 1.0, 0.0) * 0.5,
1295 MAX(viewSize.height - 1.0, 0.0) * 0.5);
1296 _has_cursor_view_position = YES;
1297 [_session_view setRemoteCursorPosition:_cursor_view_position];
1299 [_session_view showRemoteCursor];
1301 return _cursor_view_position;
1304- (void)moveCursorByViewportDelta:(CGPoint)delta
1306 CGPoint position = [
self currentCursorViewPosition];
1307 CGFloat zoomScale = [_session_scrollview zoomScale];
1308 if (zoomScale <= 0.0)
1311 position.x += delta.x / zoomScale;
1312 position.y += delta.y / zoomScale;
1313 position = [
self clampedSessionViewCursorPosition:position];
1314 _cursor_view_position = position;
1315 _has_cursor_view_position = YES;
1316 _has_user_moved_cursor = YES;
1319 [_session_view setRemoteCursorPosition:position];
1320 [
self handleMouseMoveForPosition:position];
1323- (void)moveCursorToSessionViewPosition:(CGPoint)position
1325 position = [
self clampedSessionViewCursorPosition:position];
1326 _cursor_view_position = position;
1327 _has_cursor_view_position = YES;
1328 _has_user_moved_cursor = YES;
1331 [_session_view setRemoteCursorPosition:position];
1332 [_session_view showRemoteCursor];
1333 [
self handleMouseMoveForPosition:position];
1336- (void)sendMouseButtonEvent:(
int)event
1338 CGPoint position = [
self currentCursorViewPosition];
1339 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:event position:position]];
1342- (void)handleMouseMoveForPosition:(CGPoint)position
1344 [_session sendInputEvent:[
self eventDescriptorForMouseEvent:PTR_FLAGS_MOVE position:position]];
1347- (BOOL)isKeyboardActive
1349 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.