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 */,