From 5abad38956b46e02a3023fbc08fa237e7c00bc0a Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 20 Apr 2020 16:31:21 -0700 Subject: [PATCH] Revert "Refactor legacy UITextField-based keyboard support out of StreamView" This refactoring breaks support for hardware keyboards on iOS 13.3 and below. The root cause of the breakage is not currently known. This reverts commit 69d1ff15a1399d0afe43026dbca39cc14bff1022. --- Limelight/Input/StreamView.h | 4 +- Limelight/Input/StreamView.m | 115 ++++++++++++++++- Limelight/Input/TextFieldKeyboardDelegate.h | 18 --- Limelight/Input/TextFieldKeyboardDelegate.m | 136 -------------------- Moonlight.xcodeproj/project.pbxproj | 8 -- 5 files changed, 111 insertions(+), 170 deletions(-) delete mode 100644 Limelight/Input/TextFieldKeyboardDelegate.h delete mode 100644 Limelight/Input/TextFieldKeyboardDelegate.m diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index 3ca1c9f..60b4a03 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -25,9 +25,9 @@ @end #if TARGET_OS_TV -@interface StreamView : OSView +@interface StreamView : OSView #else -@interface StreamView : OSView +@interface StreamView : OSView #endif @property (nonatomic, retain) IBOutlet UITextField* keyInputField; diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index f6df148..b24b2ec 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -11,7 +11,6 @@ #import "DataManager.h" #import "ControllerSupport.h" #import "KeyboardSupport.h" -#import "TextFieldKeyboardDelegate.h" static const double X1_MOUSE_SPEED_DIVISOR = 2.5; @@ -43,7 +42,7 @@ static const int REFERENCE_HEIGHT = 720; NSTimer* interactionTimer; BOOL hasUserInteracted; - TextFieldKeyboardDelegate* textFieldDelegate; + NSDictionary *dictCodes; } - (void) setupStreamView:(ControllerSupport*)controllerSupport @@ -85,8 +84,6 @@ static const int REFERENCE_HEIGHT = 720; } #endif - textFieldDelegate = [[TextFieldKeyboardDelegate alloc] initWithTextField:_keyInputField]; - x1mouse = [[X1Mouse alloc] init]; x1mouse.delegate = self; @@ -343,8 +340,10 @@ static const int REFERENCE_HEIGHT = 720; } else { Log(LOG_D, @"Opening the keyboard"); // Prepare the textbox used to capture keyboard events. + _keyInputField.delegate = self; _keyInputField.text = @"0"; [_keyInputField becomeFirstResponder]; + [_keyInputField addTarget:self action:@selector(onKeyboardPressed:) forControlEvents:UIControlEventEditingChanged]; // Undo causes issues for our state management, so turn it off [_keyInputField.undoManager disableUndoRegistration]; @@ -512,14 +511,118 @@ static const int REFERENCE_HEIGHT = 720; return NO; } -- (NSArray *)keyCommands { - return [textFieldDelegate keyCommands]; +- (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)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 + for (int i = 1; i < [inputText length]; i++) { + struct KeyEvent event = [KeyboardSupport translateKeyEvent:[inputText characterAtIndex:i] withModifierFlags:0]; + if (event.keycode == 0) { + // If we don't know the code, don't send anything. + Log(LOG_W, @"Unknown key code: [%c]", [inputText characterAtIndex:i]); + continue; + } + [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); + } + LiSendKeyboardEvent(event.keycode, KEY_ACTION_DOWN, event.modifier); + usleep(50 * 1000); + LiSendKeyboardEvent(event.keycode, KEY_ACTION_UP, event.modifier); + 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"); diff --git a/Limelight/Input/TextFieldKeyboardDelegate.h b/Limelight/Input/TextFieldKeyboardDelegate.h deleted file mode 100644 index aa36fbb..0000000 --- a/Limelight/Input/TextFieldKeyboardDelegate.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// TextFieldKeyboardDelegate.h -// Moonlight -// -// Created by Cameron Gutman on 3/24/20. -// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved. -// - -#pragma once - -#import - -@interface TextFieldKeyboardDelegate : NSObject - -- (id)initWithTextField:(UITextField*)textField; -- (NSArray *)keyCommands; - -@end diff --git a/Limelight/Input/TextFieldKeyboardDelegate.m b/Limelight/Input/TextFieldKeyboardDelegate.m deleted file mode 100644 index e7d6481..0000000 --- a/Limelight/Input/TextFieldKeyboardDelegate.m +++ /dev/null @@ -1,136 +0,0 @@ -// -// TextFieldKeyboardDelegate.m -// Moonlight -// -// Created by Cameron Gutman on 3/24/20. -// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved. -// - -#import "TextFieldKeyboardDelegate.h" -#import "KeyboardSupport.h" - -#include - -@implementation TextFieldKeyboardDelegate { - NSDictionary *dictCodes; -} - -- (id)initWithTextField:(UITextField*)textField { - self = [self init]; - - [textField addTarget:self action:@selector(onKeyboardPressed:) forControlEvents:UIControlEventEditingChanged]; - - textField.delegate = self; - - return self; -} - -- (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)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 - for (int i = 1; i < [inputText length]; i++) { - struct KeyEvent event = [KeyboardSupport translateKeyEvent:[inputText characterAtIndex:i] withModifierFlags:0]; - if (event.keycode == 0) { - // If we don't know the code, don't send anything. - Log(LOG_W, @"Unknown key code: [%c]", [inputText characterAtIndex:i]); - continue; - } - [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); - } - LiSendKeyboardEvent(event.keycode, KEY_ACTION_DOWN, event.modifier); - usleep(50 * 1000); - LiSendKeyboardEvent(event.keycode, KEY_ACTION_UP, event.modifier); - if (event.modifier != 0) { - LiSendKeyboardEvent(event.modifierKeycode, KEY_ACTION_UP, event.modifier); - } - }); -} - -- (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; -} - -@end diff --git a/Moonlight.xcodeproj/project.pbxproj b/Moonlight.xcodeproj/project.pbxproj index b5bd6ef..1436e1e 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -24,8 +24,6 @@ 98CFB82F1CAD481B0048EF74 /* libmoonlight-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98AB2E841CAD46840089BB98 /* libmoonlight-common.a */; }; 98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 98D5856C1C0EA79600F6CC00 /* TemporaryHost.m */; }; 98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 98D5856F1C0ED0E800F6CC00 /* TemporarySettings.m */; }; - 98EB5208242AC4DF007127D3 /* TextFieldKeyboardDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 98EB5207242AC4DF007127D3 /* TextFieldKeyboardDelegate.m */; }; - 98EB520A242AC86C007127D3 /* TextFieldKeyboardDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 98EB5207242AC4DF007127D3 /* TextFieldKeyboardDelegate.m */; }; DC1F5A07206436B20037755F /* ConnectionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DC1F5A06206436B20037755F /* ConnectionHelper.m */; }; FB1A674D2131E65900507771 /* KeyboardSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1A674C2131E65900507771 /* KeyboardSupport.m */; }; FB1A67602132419700507771 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB1A675E2132419700507771 /* Main.storyboard */; }; @@ -176,8 +174,6 @@ 98D5856C1C0EA79600F6CC00 /* TemporaryHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporaryHost.m; path = Database/TemporaryHost.m; sourceTree = ""; }; 98D5856E1C0ED0E800F6CC00 /* TemporarySettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporarySettings.h; path = Database/TemporarySettings.h; sourceTree = ""; }; 98D5856F1C0ED0E800F6CC00 /* TemporarySettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporarySettings.m; path = Database/TemporarySettings.m; sourceTree = ""; }; - 98EB5207242AC4DF007127D3 /* TextFieldKeyboardDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TextFieldKeyboardDelegate.m; sourceTree = ""; }; - 98EB5209242AC4F3007127D3 /* TextFieldKeyboardDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextFieldKeyboardDelegate.h; sourceTree = ""; }; D4746EEA1CBC740C006FB401 /* Moonlight-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Moonlight-Bridging-Header.h"; path = "Input/Moonlight-Bridging-Header.h"; sourceTree = ""; }; DC1F5A05206436B10037755F /* ConnectionHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionHelper.h; sourceTree = ""; }; DC1F5A06206436B20037755F /* ConnectionHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConnectionHelper.m; sourceTree = ""; }; @@ -537,8 +533,6 @@ FB1A674C2131E65900507771 /* KeyboardSupport.m */, 9897B6A0221260EF00966419 /* Controller.m */, 9897B6A32212610800966419 /* Controller.h */, - 98EB5207242AC4DF007127D3 /* TextFieldKeyboardDelegate.m */, - 98EB5209242AC4F3007127D3 /* TextFieldKeyboardDelegate.h */, ); path = Input; sourceTree = ""; @@ -974,7 +968,6 @@ FB1A67D1213245F800507771 /* TemporaryHost.m in Sources */, FB1A67D3213245F800507771 /* TemporarySettings.m in Sources */, FB1A67C3213245EA00507771 /* AppAssetManager.m in Sources */, - 98EB520A242AC86C007127D3 /* TextFieldKeyboardDelegate.m in Sources */, FB1A67C5213245EA00507771 /* AppAssetRetriever.m in Sources */, FB1A67C7213245EA00507771 /* PairManager.m in Sources */, FB1A67C9213245EA00507771 /* WakeOnLanManager.m in Sources */, @@ -1013,7 +1006,6 @@ FB9AFD321A7D867C00872C98 /* AppAssetRetriever.m in Sources */, FB4678FF1A565DAC00377732 /* WakeOnLanManager.m in Sources */, FB53E1431BE5DC4400CD6ECE /* IdManager.m in Sources */, - 98EB5208242AC4DF007127D3 /* TextFieldKeyboardDelegate.m in Sources */, 9897B6A1221260EF00966419 /* Controller.m in Sources */, FB89462919F646E200339C8A /* mkcert.c in Sources */, FB9AFD281A7C84ED00872C98 /* HttpResponse.m in Sources */,