diff --git a/Limelight/Input/StreamView.m.orig b/Limelight/Input/StreamView.m.orig deleted file mode 100644 index 2ba52acc..00000000 --- a/Limelight/Input/StreamView.m.orig +++ /dev/null @@ -1,1078 +0,0 @@ -// -// StreamView.m -// Moonlight -// -// Created by Cameron Gutman on 10/19/14. -// Copyright (c) 2014 Moonlight Stream. All rights reserved. -// - -#import "StreamView.h" -#include -#import "DataManager.h" -#import "ControllerSupport.h" -#import "KeyboardSupport.h" -#import "RelativeTouchHandler.h" -#import "AbsoluteTouchHandler.h" -#import "KeyboardInputField.h" -#import "zlib.h" //to implement crc32 checksum - -static const double X1_MOUSE_SPEED_DIVISOR = 2.5; - -@implementation StreamView { - OnScreenControls* onScreenControls; - - KeyboardInputField* keyInputField; - BOOL isInputingText; - NSMutableSet* keysDown; - - float streamAspectRatio; - - // iOS 13.4 mouse support - NSInteger lastMouseButtonMask; - float lastMouseX; - float lastMouseY; - CGPoint lastScrollTranslation; - - // Citrix X1 mouse support - X1Mouse* x1mouse; - double accumulatedMouseDeltaX; - double accumulatedMouseDeltaY; - - UIResponder* touchHandler; - - id interactionDelegate; - NSTimer* interactionTimer; - BOOL hasUserInteracted; - - NSDictionary *dictCodes; -} - -- (void) setupStreamView:(ControllerSupport*)controllerSupport - interactionDelegate:(id)interactionDelegate - config:(StreamConfiguration*)streamConfig { - self->interactionDelegate = interactionDelegate; - self->streamAspectRatio = (float)streamConfig.width / (float)streamConfig.height; - - TemporarySettings* settings = [[[DataManager alloc] init] getSettings]; - - keysDown = [[NSMutableSet alloc] init]; - keyInputField = [[KeyboardInputField alloc] initWithFrame:CGRectZero]; - [keyInputField setKeyboardType:UIKeyboardTypeDefault]; - [keyInputField setAutocorrectionType:UITextAutocorrectionTypeNo]; - [keyInputField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; - [keyInputField setSpellCheckingType:UITextSpellCheckingTypeNo]; - [self addSubview:keyInputField]; - -#if TARGET_OS_TV - // tvOS requires RelativeTouchHandler to manage Apple Remote input - self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self]; -#else - // iOS uses RelativeTouchHandler or AbsoluteTouchHandler depending on user preference - if (settings.absoluteTouchMode) { - self->touchHandler = [[AbsoluteTouchHandler alloc] initWithView:self]; - } - else { - self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self]; - } - - onScreenControls = [[OnScreenControls alloc] initWithView:self controllerSup:controllerSupport streamConfig:streamConfig]; - OnScreenControlsLevel level = (OnScreenControlsLevel)[settings.onscreenControls integerValue]; - if (settings.absoluteTouchMode) { - Log(LOG_I, @"On-screen controls disabled in absolute touch mode"); - [onScreenControls setLevel:OnScreenControlsLevelOff]; - } - else if (level == OnScreenControlsLevelAuto) { - [controllerSupport initAutoOnScreenControlMode:onScreenControls]; - } - else { - Log(LOG_I, @"Setting manual on-screen controls level: %d", (int)level); - [onScreenControls setLevel:level]; - } - // It would be nice to just use GCMouse on iOS 14+ and the older API on iOS 13 - // but unfortunately that isn't possible today. GCMouse doesn't recognize many - // mice correctly, but UIKit does. We will register for both and ignore UIKit - // events if a GCMouse is connected. - if (@available(iOS 13.4, *)) { -// [self addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]]; -// -// UIPanGestureRecognizer *discreteMouseWheelRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mouseWheelMovedDiscrete:)]; -// discreteMouseWheelRecognizer.maximumNumberOfTouches = 0; -// discreteMouseWheelRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete; -// discreteMouseWheelRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)]; -// [self addGestureRecognizer:discreteMouseWheelRecognizer]; -// -// UIPanGestureRecognizer *continuousMouseWheelRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mouseWheelMovedContinuous:)]; -// continuousMouseWheelRecognizer.maximumNumberOfTouches = 0; -// continuousMouseWheelRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous; -// continuousMouseWheelRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)]; -// [self addGestureRecognizer:continuousMouseWheelRecognizer]; - } - -#if defined(__IPHONE_16_1) || defined(__TVOS_16_1) - if (@available(iOS 16.1, *)) { - UIHoverGestureRecognizer *stylusHoverRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(sendStylusHoverEvent:)]; - stylusHoverRecognizer.allowedTouchTypes = @[@(UITouchTypePencil)]; - [self addGestureRecognizer:stylusHoverRecognizer]; - } -#endif -#endif - - x1mouse = [[X1Mouse alloc] init]; - x1mouse.delegate = self; - - if (settings.btMouseSupport) { - [x1mouse start]; - } - - // This is critical to ensure keyboard events are delivered to this - // StreamView and not our parent UIView, especially on tvOS. - [self becomeFirstResponder]; -} - -- (void)startInteractionTimer { - // Restart user interaction tracking - hasUserInteracted = NO; - - BOOL timerAlreadyRunning = interactionTimer != nil; - - // Start/restart the timer - [interactionTimer invalidate]; - interactionTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 - target:self - selector:@selector(interactionTimerExpired:) - userInfo:nil - repeats:NO]; - - // Notify the delegate if this was a new user interaction - if (!timerAlreadyRunning) { - [interactionDelegate userInteractionBegan]; - } -} - -- (void)interactionTimerExpired:(NSTimer *)timer { - if (!hasUserInteracted) { - // User has finished touching the screen - interactionTimer = nil; - [interactionDelegate userInteractionEnded]; - } - else { - // User is still touching the screen. Restart the timer. - [self startInteractionTimer]; - } -} - -- (void) showOnScreenControls { -#if !TARGET_OS_TV - [onScreenControls show]; -#endif -} - -- (OnScreenControlsLevel) getCurrentOscState { - if (onScreenControls == nil) { - return OnScreenControlsLevelOff; - } - else { - return [onScreenControls getLevel]; - } -} - -- (CGSize) getVideoAreaSize { - if (self.bounds.size.width > self.bounds.size.height * streamAspectRatio) { - return CGSizeMake(self.bounds.size.height * streamAspectRatio, self.bounds.size.height); - } else { - return CGSizeMake(self.bounds.size.width, self.bounds.size.width / streamAspectRatio); - } -} - -- (CGPoint) adjustCoordinatesForVideoArea:(CGPoint)point { - // These are now relative to the StreamView, however we need to scale them - // further to make them relative to the actual video portion. - float x = point.x - self.bounds.origin.x; - float y = point.y - self.bounds.origin.y; - - // For some reason, we don't seem to always get to the bounds of the window - // so we'll subtract 1 pixel if we're to the left/below of the origin and - // and add 1 pixel if we're to the right/above. It should be imperceptible - // to the user but it will allow activation of gestures that require contact - // with the edge of the screen (like Aero Snap). - if (x < self.bounds.size.width / 2) { - x--; - } - else { - x++; - } - if (y < self.bounds.size.height / 2) { - y--; - } - else { - y++; - } - - // This logic mimics what iOS does with AVLayerVideoGravityResizeAspect - CGSize videoSize = [self getVideoAreaSize]; - CGPoint videoOrigin = CGPointMake(self.bounds.size.width / 2 - videoSize.width / 2, - self.bounds.size.height / 2 - videoSize.height / 2); - - // Confine the cursor to the video region. We don't just discard events outside - // the region because we won't always get one exactly when the mouse leaves the region. - return CGPointMake(MIN(MAX(x, videoOrigin.x), videoOrigin.x + videoSize.width) - videoOrigin.x, - MIN(MAX(y, videoOrigin.y), videoOrigin.y + videoSize.height) - videoOrigin.y); -} - -#if !TARGET_OS_TV - -- (uint16_t)getRotationFromAzimuthAngle:(float)azimuthAngle { - // iOS reports azimuth of 0 when the stylus is pointing west, but Moonlight expects - // rotation of 0 to mean the stylus is pointing north. Rotate the azimuth angle - // clockwise by 90 degrees to convert from iOS to Moonlight rotation conventions. - int32_t rotationAngle = (azimuthAngle - M_PI_2) * (180.f / M_PI); - if (rotationAngle < 0) { - rotationAngle += 360; - } - return (uint16_t)rotationAngle; -} - -- (uint8_t)getTiltFromAltitudeAngle:(float)altitudeAngle { - // iOS reports an altitude of 0 when the stylus is parallel to the touch surface, - // while Moonlight expects a tilt of 0 when the stylus is perpendicular to the surface. - // Subtract the tilt angle from 90 to convert from iOS to Moonlight tilt conventions. - uint8_t altitudeDegs = abs((int16_t)(altitudeAngle * (180.f / M_PI))); - return 90 - MIN(90, altitudeDegs); -} - -<<<<<<< HEAD -// convert 64bit pointer to CRC32 checksum -- (uint32_t)crc32OfUint64:(uint64_t)value { - // uint64_t可以被视为字符数组来计算CRC32 - char data[sizeof(value)]; - memcpy(data, &value, sizeof(value)); - uint32_t crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, (const Bytef *)data, sizeof(value)); - return crc; -} - -- (void)trySendTouchEvent:(UITouch*)event index:(int)index{ - uint8_t type; - // NSLog(@"trySendTouchEvent %ld,%d",(long)event.phase,(uint32_t)event); -//#define LI_TOUCH_EVENT_HOVER 0x00 -//#define LI_TOUCH_EVENT_DOWN 0x01 -//#define LI_TOUCH_EVENT_UP 0x02 -//#define LI_TOUCH_EVENT_MOVE 0x03 -//#define LI_TOUCH_EVENT_CANCEL 0x04 -//#define LI_TOUCH_EVENT_BUTTON_ONLY 0x05 -//#define LI_TOUCH_EVENT_HOVER_LEAVE 0x06 -//#define LI_TOUCH_EVENT_CANCEL_ALL 0x07 -//#define LI_ROT_UNKNOWN 0xFFFF - - -// UITouchPhaseBegan, // whenever a finger touches the surface. -// UITouchPhaseMoved, // whenever a finger moves on the surface. -// UITouchPhaseStationary, // whenever a finger is touching the surface but hasn't moved since the previous event. -// UITouchPhaseEnded, // whenever a finger leaves the surface. -// UITouchPhaseCancelled, // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face) -// UITouchPhaseRegionEntered API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos), // whenever a touch is entering the region of a user interface -// UITouchPhaseRegionMoved API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos), // when a touch is inside the region of a user interface, but hasn’t yet made contact or left the region -// UITouchPhaseRegionExited API_AVAILABLE(ios(13.4), tvos(13.4)) - - switch (event.phase) { - case UITouchPhaseBegan://开始触摸 - type = LI_TOUCH_EVENT_DOWN; - break; - case UITouchPhaseMoved://移动 - case UITouchPhaseStationary: - type = LI_TOUCH_EVENT_MOVE; - break; - case UITouchPhaseEnded://触摸结束 - type = LI_TOUCH_EVENT_UP; - break; - case UITouchPhaseCancelled://触摸取消 - type = LI_TOUCH_EVENT_CANCEL; - break; - case UITouchPhaseRegionEntered://停留 - case UITouchPhaseRegionMoved://停留 - type = LI_TOUCH_EVENT_HOVER; - break; - default: - return; - } -// switch (event.phase) { -// case UITouchPhaseBegan: -// type = LI_TOUCH_EVENT_DOWN; -// break; -// case UITouchPhaseMoved: -// type = LI_TOUCH_EVENT_MOVE; -// break; -// case UITouchPhaseEnded: -// type = LI_TOUCH_EVENT_UP; -// break; -// case UITouchPhaseCancelled: -// type = LI_TOUCH_EVENT_CANCEL; -// break; -// default: -// return; -// } - uint32_t pointerId = [self crc32OfUint64:((uint64_t)event)]; // convert pointer event(64bit) to CRC32 checksum as touch pointer ID. - // There's no pointerId like Android in definition of iOS touch event "UITouch", we use pointer to UITouch to generate touch pointer ID. - // Since touch pointer ID is defined to be uint32_t, I implement CRC32 checksum to the 64bit address, which avoids touch pointer ID repetition. - CGPoint location = [self adjustCoordinatesForVideoArea:[event locationInView:self]]; - CGSize videoSize = [self getVideoAreaSize]; - LiSendTouchEvent(type,pointerId,location.x / videoSize.width, location.y / videoSize.height,(event.force / event.maximumPossibleForce) / sin(event.altitudeAngle),0.0f, 0.0f,[self getRotationFromAzimuthAngle:[event azimuthAngleInView:self]]); -} - - - -- (void)sendStylusEvent:(UITouch*)event { -======= -- (BOOL)sendStylusEvent:(UITouch*)event { ->>>>>>> Moonlight-iOS-Official9.0.2 - uint8_t type; - - // Don't touch stylus events if the host doesn't support them. We want to pass - // them as normal touches for legacy hosts that don't understand pen events. - if (!(LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS)) { - return NO; - } - - switch (event.phase) { - case UITouchPhaseBegan: - type = LI_TOUCH_EVENT_DOWN; - break; - case UITouchPhaseMoved: - type = LI_TOUCH_EVENT_MOVE; - break; - case UITouchPhaseEnded: - type = LI_TOUCH_EVENT_UP; - break; - case UITouchPhaseCancelled: - type = LI_TOUCH_EVENT_CANCEL; - break; - default: - return YES; - } - - CGPoint location = [self adjustCoordinatesForVideoArea:[event locationInView:self]]; - CGSize videoSize = [self getVideoAreaSize]; - - return LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height, - (event.force / event.maximumPossibleForce) / sin(event.altitudeAngle), - 0.0f, 0.0f, - [self getRotationFromAzimuthAngle:[event azimuthAngleInView:self]], - [self getTiltFromAltitudeAngle:event.altitudeAngle]) != LI_ERR_UNSUPPORTED; -} - -- (void)sendStylusHoverEvent:(UIHoverGestureRecognizer*)gesture API_AVAILABLE(ios(13.0)) { - uint8_t type; - - switch (gesture.state) { - case UIGestureRecognizerStateBegan: - case UIGestureRecognizerStateChanged: - type = LI_TOUCH_EVENT_HOVER; - break; - - case UIGestureRecognizerStateEnded: - type = LI_TOUCH_EVENT_HOVER_LEAVE; - break; - - default: - return; - } - - CGPoint location = [self adjustCoordinatesForVideoArea:[gesture locationInView:self]]; - CGSize videoSize = [self getVideoAreaSize]; - - float distance = 0.0f; -#if defined(__IPHONE_16_1) || defined(__TVOS_16_1) - if (@available(iOS 16.1, *)) { - distance = gesture.zOffset; - } -#endif - - uint16_t rotationAngle = LI_ROT_UNKNOWN; - uint8_t tiltAngle = LI_TILT_UNKNOWN; -#if defined(__IPHONE_16_4) || defined(__TVOS_16_4) - if (@available(iOS 16.4, *)) { - rotationAngle = [self getRotationFromAzimuthAngle:[gesture azimuthAngleInView:self]]; - tiltAngle = [self getTiltFromAltitudeAngle:gesture.altitudeAngle]; - } -#endif - - LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height, - distance, 0.0f, 0.0f, rotationAngle, tiltAngle); -} - -#endif - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self handleMouseButtonEvent:BUTTON_ACTION_PRESS - forTouches:touches - withEvent:event]) { - // If it's a mouse event, we're done - return; - } - - Log(LOG_D, @"Touch down"); - - // Notify of user interaction and start expiration timer - [self startInteractionTimer]; - -#if !TARGET_OS_TV - if (@available(iOS 13.4, *)) { -// NSArray *arr1 = [[event allTouches] allObjects]; -//// NSLog(@"touchesBegan - %lu",[[event allTouches] count]); -//// NSLog(@"touchesBegan - %lu",arr1.count); -// -// for(int i=0;i *)presses withEvent:(UIPressesEvent *)event { - BOOL handled = NO; - - if (@available(iOS 13.4, tvOS 13.4, *)) { - for (UIPress* press in presses) { - // For now, we'll treated it as handled if we handle at least one of the - // UIPress events inside the set. - if ([KeyboardSupport sendKeyEventForPress:press down:YES]) { - // This will prevent the legacy UITextField from receiving the event - handled = YES; - } - } - } - - if (!handled) { - [super pressesBegan:presses withEvent:event]; - } -} - -- (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { - BOOL handled = NO; - - if (@available(iOS 13.4, tvOS 13.4, *)) { - for (UIPress* press in presses) { - // For now, we'll treated it as handled if we handle at least one of the - // UIPress events inside the set. - if ([KeyboardSupport sendKeyEventForPress:press down:NO]) { - // This will prevent the legacy UITextField from receiving the event - handled = YES; - } - } - } - - if (!handled) { - [super pressesEnded:presses withEvent:event]; - } -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self handleMouseButtonEvent:BUTTON_ACTION_RELEASE - forTouches:touches - withEvent:event]) { - // If it's a mouse event, we're done - return; - } - - Log(LOG_D, @"Touch up"); - - hasUserInteracted = YES; - -#if !TARGET_OS_TV - if (@available(iOS 13.4, *)) { - for (UITouch* touch in touches) { - if (touch.type == UITouchTypePencil) { - if ([self sendStylusEvent:touch]) { - return; - } - } - TemporarySettings* settings = [[[DataManager alloc] init] getSettings]; - if (settings.absoluteTouchMode) { - [self trySendTouchEvent:touch index:0]; -// return; - } - } - } -#endif - - if (![onScreenControls handleTouchUpEvent:touches]) { - [touchHandler touchesEnded:touches withEvent:event]; - } -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - [touchHandler touchesCancelled:touches withEvent:event]; - [self handleMouseButtonEvent:BUTTON_ACTION_RELEASE - forTouches:touches - withEvent:event]; -#if !TARGET_OS_TV - if (@available(iOS 13.4, *)) { - for (UITouch* touch in touches) { - if (touch.type == UITouchTypePencil) { - [self sendStylusEvent:touch]; - } - TemporarySettings* settings = [[[DataManager alloc] init] getSettings]; - if (settings.absoluteTouchMode) { - [self trySendTouchEvent:touch index:0]; -// return; - } - } - } -#endif -} - -#if !TARGET_OS_TV -- (void) updateCursorLocation:(CGPoint)location isMouse:(BOOL)isMouse { - CGPoint normalizedLocation = [self adjustCoordinatesForVideoArea:location]; - CGSize videoSize = [self getVideoAreaSize]; - - // Send the mouse position relative to the video region if it has changed - // if we're receiving coordinates from a real mouse. - // - // NB: It is important for functionality (not just optimization) to only - // send it if the value has changed. We will receive one of these events - // any time the user presses a modifier key, which can result in errant - // mouse motion when using a Citrix X1 mouse. - if (normalizedLocation.x != lastMouseX || normalizedLocation.y != lastMouseY || !isMouse) { - if (lastMouseX != 0 || lastMouseY != 0 || !isMouse) { - LiSendMousePositionEvent(normalizedLocation.x, normalizedLocation.y, videoSize.width, videoSize.height); - } - - if (isMouse) { - lastMouseX = normalizedLocation.x; - lastMouseY = normalizedLocation.y; - } - } -} - -- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction - regionForRequest:(UIPointerRegionRequest *)request - defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)) { - if (@available(iOS 14.0, *)) { - if ([GCMouse current] != nil) { - // We'll handle this with GCMouse. Do nothing here. - return nil; - } - } - - // This logic mimics what iOS does with AVLayerVideoGravityResizeAspect - CGSize videoSize; - CGPoint videoOrigin; - if (self.bounds.size.width > self.bounds.size.height * streamAspectRatio) { - videoSize = CGSizeMake(self.bounds.size.height * streamAspectRatio, self.bounds.size.height); - } else { - videoSize = CGSizeMake(self.bounds.size.width, self.bounds.size.width / streamAspectRatio); - } - videoOrigin = CGPointMake(self.bounds.size.width / 2 - videoSize.width / 2, - self.bounds.size.height / 2 - videoSize.height / 2); - - // Move the cursor on the host if no buttons are pressed. - // Motion with buttons pressed in handled in touchesMoved: - if (lastMouseButtonMask == 0) { - [self updateCursorLocation:request.location isMouse:YES]; - } - - // The pointer interaction should cover the video region only - return [UIPointerRegion regionWithRect:CGRectMake(videoOrigin.x, videoOrigin.y, videoSize.width, videoSize.height) identifier:nil]; -} - -- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)) { - // Always hide the mouse cursor over our stream view - return [UIPointerStyle hiddenPointerStyle]; -} - -- (void)mouseWheelMovedContinuous:(UIPanGestureRecognizer *)gesture { - switch (gesture.state) { - case UIGestureRecognizerStateBegan: - case UIGestureRecognizerStateChanged: - break; - - case UIGestureRecognizerStateEnded: - default: - // Ignore recognition failure and other states - lastScrollTranslation = CGPointMake(0, 0); - return; - } - - CGPoint currentScrollTranslation = [gesture translationInView:self]; - const short translationMultiplier = 120 * 20; // WHEEL_DELTA * 20 - - { - short translationDeltaY = ((currentScrollTranslation.y - lastScrollTranslation.y) / self.bounds.size.height) * translationMultiplier; - if (translationDeltaY != 0) { - LiSendHighResScrollEvent(translationDeltaY); - lastScrollTranslation = currentScrollTranslation; - } - } - - { - short translationDeltaX = ((currentScrollTranslation.x - lastScrollTranslation.x) / self.bounds.size.width) * translationMultiplier; - if (translationDeltaX != 0) { - // Direction is reversed from vertical scrolling - LiSendHighResHScrollEvent(-translationDeltaX); - lastScrollTranslation = currentScrollTranslation; - } - } -} - -- (void)mouseWheelMovedDiscrete:(UIPanGestureRecognizer *)gesture { - switch (gesture.state) { - case UIGestureRecognizerStateBegan: - case UIGestureRecognizerStateChanged: - break; - - case UIGestureRecognizerStateEnded: - default: - // Ignore recognition failure and other states - lastScrollTranslation = CGPointMake(0, 0); - return; - } - - // Using velocityInView is 0 for discrete scroll events - // when scrolling very slowly, but translationInView does work. - CGPoint currentScrollTranslation = [gesture translationInView:self]; - - { - short translationDeltaY = currentScrollTranslation.y - lastScrollTranslation.y; - if (translationDeltaY != 0) { - LiSendScrollEvent(translationDeltaY > 0 ? 1 : -1); - } - } - - { - short translationDeltaX = currentScrollTranslation.x - lastScrollTranslation.x; - if (translationDeltaX != 0) { - // Direction is reversed from vertical scrolling - LiSendHScrollEvent(translationDeltaX < 0 ? 1 : -1); - } - } - - lastScrollTranslation = currentScrollTranslation; -} - -#endif - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { - if (@available(iOS 13.0, *)) { - // Disable the 3 finger tap gestures that trigger the copy/paste/undo toolbar on iOS 13+ - return gestureRecognizer.name == nil || ![gestureRecognizer.name hasPrefix:@"kbProductivity."]; - } - else { - return YES; - } -} - -- (BOOL)textFieldShouldReturn:(UITextField *)textField { - // This method is called when the "Return" key is pressed. - LiSendKeyboardEvent(0x0d, KEY_ACTION_DOWN, 0); - usleep(50 * 1000); - LiSendKeyboardEvent(0x0d, KEY_ACTION_UP, 0); - return NO; -} - -- (void)textFieldDidEndEditing:(UITextField *)textField { - for (NSNumber* keyCode in keysDown) { - LiSendKeyboardEvent([keyCode shortValue], KEY_ACTION_UP, 0); - } - [keysDown removeAllObjects]; -} - -- (void)onKeyboardPressed:(UITextField *)textField { - NSString* inputText = textField.text; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - // If the text became empty, we know the user pressed the backspace key. - if ([inputText isEqual:@""]) { - LiSendKeyboardEvent(0x08, KEY_ACTION_DOWN, 0); - usleep(50 * 1000); - LiSendKeyboardEvent(0x08, KEY_ACTION_UP, 0); - } else { - // Character 0 will be our known sentinel value - - // Check if any characters exist which can't be represented in a basic key event - for (int i = 1; i < [inputText length]; i++) { - struct KeyEvent event = [KeyboardSupport translateKeyEvent:[inputText characterAtIndex:i] withModifierFlags:0]; - if (event.keycode == 0) { - // We found an unknown key, so send the entire string as UTF-8 - const char* utf8String = [inputText UTF8String]; - - // Skip the first character which is our sentinel - LiSendUtf8TextEvent(utf8String + 1, (int)strlen(utf8String) - 1); - return; - } - } - - // We didn't find any unknown characters, so send them all as basic key events - for (int i = 1; i < [inputText length]; i++) { - struct KeyEvent event = [KeyboardSupport translateKeyEvent:[inputText characterAtIndex:i] withModifierFlags:0]; - assert(event.keycode != 0); - [self sendLowLevelEvent:event]; - } - } - }); - - // Reset text field back to known state - textField.text = @"0"; - - // Move the insertion point back to the end of the text box - UITextRange *textRange = [textField textRangeFromPosition:textField.endOfDocument toPosition:textField.endOfDocument]; - [textField setSelectedTextRange:textRange]; -} - -- (void)specialCharPressed:(UIKeyCommand *)cmd { - struct KeyEvent event = [KeyboardSupport translateKeyEvent:0x20 withModifierFlags:[cmd modifierFlags]]; - event.keycode = [[dictCodes valueForKey:[cmd input]] intValue]; - [self sendLowLevelEvent:event]; -} - -- (void)keyPressed:(UIKeyCommand *)cmd { - struct KeyEvent event = [KeyboardSupport translateKeyEvent:[[cmd input] characterAtIndex:0] withModifierFlags:[cmd modifierFlags]]; - [self sendLowLevelEvent:event]; -} - -- (void)sendLowLevelEvent:(struct KeyEvent)event { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - // When we want to send a modified key (like uppercase letters) we need to send the - // modifier ("shift") seperately from the key itself. - if (event.modifier != 0) { - LiSendKeyboardEvent(event.modifierKeycode, KEY_ACTION_DOWN, event.modifier); - } - // Let the host know these are not (necessarily) normalized to US English scancodes - LiSendKeyboardEvent2(event.keycode, KEY_ACTION_DOWN, event.modifier, SS_KBE_FLAG_NON_NORMALIZED); - usleep(50 * 1000); - LiSendKeyboardEvent2(event.keycode, KEY_ACTION_UP, event.modifier, SS_KBE_FLAG_NON_NORMALIZED); - if (event.modifier != 0) { - LiSendKeyboardEvent(event.modifierKeycode, KEY_ACTION_UP, event.modifier); - } - }); -} - -- (BOOL)canBecomeFirstResponder { - return YES; -} - -- (NSArray *)keyCommands -{ - NSString *charset = @"qwertyuiopasdfghjklzxcvbnm1234567890\t§[]\\'\"/.,`<>-´ç+`¡'º;ñ= "; - - NSMutableArray * commands = [NSMutableArray array]; - dictCodes = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt: 0x0d], @"\r", [NSNumber numberWithInt: 0x08], @"\b", [NSNumber numberWithInt: 0x1b], UIKeyInputEscape, [NSNumber numberWithInt: 0x28], UIKeyInputDownArrow, [NSNumber numberWithInt: 0x26], UIKeyInputUpArrow, [NSNumber numberWithInt: 0x25], UIKeyInputLeftArrow, [NSNumber numberWithInt: 0x27], UIKeyInputRightArrow, nil]; - - [charset enumerateSubstringsInRange:NSMakeRange(0, charset.length) - options:NSStringEnumerationByComposedCharacterSequences - usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - [commands addObject:[UIKeyCommand keyCommandWithInput:substring modifierFlags:0 action:@selector(keyPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:substring modifierFlags:UIKeyModifierShift action:@selector(keyPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:substring modifierFlags:UIKeyModifierControl action:@selector(keyPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:substring modifierFlags:UIKeyModifierAlternate action:@selector(keyPressed:)]]; - }]; - - for (NSString *c in [dictCodes keyEnumerator]) { - [commands addObject:[UIKeyCommand keyCommandWithInput:c - modifierFlags:0 - action:@selector(specialCharPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:c - modifierFlags:UIKeyModifierShift - action:@selector(specialCharPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:c - modifierFlags:UIKeyModifierShift | UIKeyModifierAlternate - action:@selector(specialCharPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:c - modifierFlags:UIKeyModifierShift | UIKeyModifierControl - action:@selector(specialCharPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:c - modifierFlags:UIKeyModifierControl - action:@selector(specialCharPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:c - modifierFlags:UIKeyModifierControl | UIKeyModifierAlternate - action:@selector(specialCharPressed:)]]; - [commands addObject:[UIKeyCommand keyCommandWithInput:c - modifierFlags:UIKeyModifierAlternate - action:@selector(specialCharPressed:)]]; - } - - return commands; -} - -- (void)connectedStateDidChangeWithIdentifier:(NSUUID * _Nonnull)identifier isConnected:(BOOL)isConnected { - NSLog(@"Citrix X1 mouse state change: %@ -> %s", - identifier, isConnected ? "connected" : "disconnected"); -} - -- (void)mouseDidMoveWithIdentifier:(NSUUID * _Nonnull)identifier deltaX:(int16_t)deltaX deltaY:(int16_t)deltaY { - accumulatedMouseDeltaX += deltaX / X1_MOUSE_SPEED_DIVISOR; - accumulatedMouseDeltaY += deltaY / X1_MOUSE_SPEED_DIVISOR; - - short shortX = (short)accumulatedMouseDeltaX; - short shortY = (short)accumulatedMouseDeltaY; - - if (shortX == 0 && shortY == 0) { - return; - } - - LiSendMouseMoveEvent(shortX, shortY); - - accumulatedMouseDeltaX -= shortX; - accumulatedMouseDeltaY -= shortY; -} - -- (int) buttonFromX1ButtonCode:(enum X1MouseButton)button { - switch (button) { - case X1MouseButtonLeft: - return BUTTON_LEFT; - case X1MouseButtonRight: - return BUTTON_RIGHT; - case X1MouseButtonMiddle: - return BUTTON_MIDDLE; - default: - return -1; - } -} - -- (void)mouseDownWithIdentifier:(NSUUID * _Nonnull)identifier button:(enum X1MouseButton)button { - LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, [self buttonFromX1ButtonCode:button]); -} - -- (void)mouseUpWithIdentifier:(NSUUID * _Nonnull)identifier button:(enum X1MouseButton)button { - LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, [self buttonFromX1ButtonCode:button]); -} - -- (void)wheelDidScrollWithIdentifier:(NSUUID * _Nonnull)identifier deltaZ:(int8_t)deltaZ { - LiSendScrollEvent(deltaZ); -} - -#if !TARGET_OS_TV -- (BOOL)isMultipleTouchEnabled { - return YES; -} -#endif - -@end