diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index 28cad34..5aef4eb 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -23,7 +23,7 @@ @end -@interface StreamView : OSView +@interface StreamView : OSView @property (nonatomic, retain) IBOutlet UITextField* keyInputField; diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index 8f41148..a1cb01a 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -10,7 +10,7 @@ #include #import "DataManager.h" #import "ControllerSupport.h" -#import "KeyboardSupport.h" +#import "TextFieldKeyboardDelegate.h" @implementation StreamView { CGPoint touchLocation, originalLocation; @@ -35,7 +35,7 @@ NSTimer* interactionTimer; BOOL hasUserInteracted; - NSDictionary *dictCodes; + TextFieldKeyboardDelegate* textFieldDelegate; } - (void) setMouseDeltaFactors:(float)x y:(float)y { @@ -75,6 +75,8 @@ } #endif + textFieldDelegate = [[TextFieldKeyboardDelegate alloc] initWithTextField:_keyInputField]; + x1mouse = [[X1Mouse alloc] init]; x1mouse.delegate = self; [x1mouse start]; @@ -229,10 +231,8 @@ } 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]; @@ -316,118 +316,14 @@ return NO; } -- (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 { + return [textFieldDelegate keyCommands]; } - (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 new file mode 100644 index 0000000..aa36fbb --- /dev/null +++ b/Limelight/Input/TextFieldKeyboardDelegate.h @@ -0,0 +1,18 @@ +// +// 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 new file mode 100644 index 0000000..e7d6481 --- /dev/null +++ b/Limelight/Input/TextFieldKeyboardDelegate.m @@ -0,0 +1,136 @@ +// +// 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 5651f7e..31e50a6 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ 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 */; }; @@ -173,6 +175,8 @@ 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 = ""; }; @@ -532,6 +536,8 @@ FB1A674C2131E65900507771 /* KeyboardSupport.m */, 9897B6A0221260EF00966419 /* Controller.m */, 9897B6A32212610800966419 /* Controller.h */, + 98EB5207242AC4DF007127D3 /* TextFieldKeyboardDelegate.m */, + 98EB5209242AC4F3007127D3 /* TextFieldKeyboardDelegate.h */, ); path = Input; sourceTree = ""; @@ -967,6 +973,7 @@ 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 */, @@ -1005,6 +1012,7 @@ 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 */,