diff --git a/Limelight/Input/KeyboardSupport.h b/Limelight/Input/KeyboardSupport.h new file mode 100644 index 0000000..a1569f6 --- /dev/null +++ b/Limelight/Input/KeyboardSupport.h @@ -0,0 +1,21 @@ +// +// KeyboardSupport.h +// Moonlight +// +// Created by Diego Waxemberg on 8/25/18. +// Copyright © 2018 Moonlight Game Streaming Project. All rights reserved. +// + +#import + +@interface KeyboardSupport : NSObject + +struct KeyEvent { + u_short keycode; + u_short modifierKeycode; + u_char modifier; +}; + ++ (struct KeyEvent) translateKeyEvent:(unichar) inputChar; + +@end diff --git a/Limelight/Input/KeyboardSupport.m b/Limelight/Input/KeyboardSupport.m new file mode 100644 index 0000000..d07f895 --- /dev/null +++ b/Limelight/Input/KeyboardSupport.m @@ -0,0 +1,166 @@ +// +// KeyboardSupport.m +// Moonlight +// +// Created by Diego Waxemberg on 8/25/18. +// Copyright © 2018 Moonlight Game Streaming Project. All rights reserved. +// + +#import "KeyboardSupport.h" +#include + +@implementation KeyboardSupport + ++ (struct KeyEvent)translateKeyEvent:(unichar)inputChar { + struct KeyEvent event; + event.keycode = 0; + event.modifier = 0; + event.modifierKeycode = 0; + + if (inputChar >= 0x30 && inputChar <= 0x39) { + // Numbers 0-9 + event.keycode = inputChar; + } else if (inputChar >= 0x41 && inputChar <= 0x5A) { + // Capital letters + event.keycode = inputChar; + [KeyboardSupport addShiftModifier:&event]; + } else if (inputChar >= 0x61 && inputChar <= 0x7A) { + // Lower case letters + event.keycode = inputChar - (0x61 - 0x41); + } switch (inputChar) { + case ' ': // Spacebar + event.keycode = 0x20; + break; + case '-': // Hyphen '-' + event.keycode = 0xBD; + break; + case '/': // Forward slash '/' + event.keycode = 0xBF; + break; + case ':': // Colon ':' + event.keycode = 0xBA; + [KeyboardSupport addShiftModifier:&event]; + break; + case ';': // Semi-colon ';' + event.keycode = 0xBA; + break; + case '(': // Open parenthesis '(' + event.keycode = 0x39; // '9' + [KeyboardSupport addShiftModifier:&event]; + break; + case ')': // Close parenthesis ')' + event.keycode = 0x30; // '0' + [KeyboardSupport addShiftModifier:&event]; + break; + case '$': // Dollar sign '$' + event.keycode = 0x34; // '4' + [KeyboardSupport addShiftModifier:&event]; + break; + case '&': // Ampresand '&' + event.keycode = 0x37; // '7' + [KeyboardSupport addShiftModifier:&event]; + break; + case '@': // At-sign '@' + event.keycode = 0x32; // '2' + [KeyboardSupport addShiftModifier:&event]; + break; + case '"': + event.keycode = 0xDE; + [KeyboardSupport addShiftModifier:&event]; + break; + case '\'': + event.keycode = 0xDE; + break; + case '!': + event.keycode = 0x31; // '1' + [KeyboardSupport addShiftModifier:&event]; + break; + case '?': + event.keycode = 0xBF; // '/' + [KeyboardSupport addShiftModifier:&event]; + break; + case ',': + event.keycode = 0xBC; + break; + case '<': + event.keycode = 0xBC; + [KeyboardSupport addShiftModifier:&event]; + break; + case '.': + event.keycode = 0xBE; + break; + case '>': + event.keycode = 0xBE; + [KeyboardSupport addShiftModifier:&event]; + break; + case '[': + event.keycode = 0xDB; + break; + case ']': + event.keycode = 0xDD; + break; + case '{': + event.keycode = 0xDB; + [KeyboardSupport addShiftModifier:&event]; + break; + case '}': + event.keycode = 0xDD; + [KeyboardSupport addShiftModifier:&event]; + break; + case '#': + event.keycode = 0x33; // '3' + [KeyboardSupport addShiftModifier:&event]; + break; + case '%': + event.keycode = 0x35; // '5' + [KeyboardSupport addShiftModifier:&event]; + break; + case '^': + event.keycode = 0x36; // '6' + [KeyboardSupport addShiftModifier:&event]; + break; + case '*': + event.keycode = 0x38; // '8' + [KeyboardSupport addShiftModifier:&event]; + break; + case '+': + event.keycode = 0xBB; + [KeyboardSupport addShiftModifier:&event]; + break; + case '=': + event.keycode = 0xBB; + break; + case '_': + event.keycode = 0xBD; + [KeyboardSupport addShiftModifier:&event]; + break; + case '\\': + event.keycode = 0xDC; + break; + case '|': + event.keycode = 0xDC; + [KeyboardSupport addShiftModifier:&event]; + break; + case '~': + event.keycode = 0xC0; + [KeyboardSupport addShiftModifier:&event]; + break; + case '`': + event.keycode = 0xC0; + break; + case '\t': + event.keycode = 0x09; + break; + default: + break; + } + + return event; +} + ++ (void) addShiftModifier:(struct KeyEvent*)event { + event->modifier = MODIFIER_SHIFT; + event->modifierKeycode = 0x10; +} + +@end diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index dddb864..2b4e364 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -14,7 +14,9 @@ @end -@interface StreamView : OSView +@interface StreamView : OSView + +@property (nonatomic, retain) IBOutlet UITextField* keyInputField; - (void) setupOnScreenControls:(ControllerSupport*)controllerSupport swipeDelegate:(id)swipeDelegate; - (void) setMouseDeltaFactors:(float)x y:(float)y; diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index fc35c58..dc56107 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -11,12 +11,14 @@ #import "OnScreenControls.h" #import "DataManager.h" #import "ControllerSupport.h" +#import "KeyboardSupport.h" @implementation StreamView { CGPoint touchLocation, originalLocation; BOOL touchMoved; OnScreenControls* onScreenControls; + BOOL isInputingText; BOOL isDragging; NSTimer* dragTimer; @@ -129,9 +131,22 @@ if (isDragging) { isDragging = false; LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT); - } - else if (!touchMoved) { - if ([[event allTouches] count] == 2) { + } else if (!touchMoved) { + if ([[event allTouches] count] == 3) { + if (isInputingText) { + Log(LOG_D, @"Closing the keyboard"); + [_keyInputField resignFirstResponder]; + isInputingText = false; + } 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]; + isInputingText = true; + } + } else if ([[event allTouches] count] == 2) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ Log(LOG_D, @"Sending right mouse button press"); @@ -142,7 +157,7 @@ LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT); }); - } else { + } else if ([[event allTouches] count] == 1) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ if (!self->isDragging){ Log(LOG_D, @"Sending left mouse button press"); @@ -177,5 +192,45 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { } +- (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 { + struct KeyEvent event = [KeyboardSupport translateKeyEvent:[inputText characterAtIndex:1]]; + if (event.keycode == 0) { + // If we don't know the code, don't send anything. + Log(LOG_W, @"Unknown key code: [%c]", [inputText characterAtIndex:1]); + return; + } + + // 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); + usleep(50 * 1000); + } + 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); + } + } + }); + textField.text = @"0"; +} @end diff --git a/Moonlight.xcodeproj/project.pbxproj b/Moonlight.xcodeproj/project.pbxproj index d1ad2aa..80497ff 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ D46A73AD1CBC7D090039F1EE /* ControllerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46A73AC1CBC7D090039F1EE /* ControllerUnitTests.swift */; }; D4746EEC1CBC740C006FB401 /* Controller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4746EEB1CBC740C006FB401 /* Controller.swift */; }; DC1F5A07206436B20037755F /* ConnectionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DC1F5A06206436B20037755F /* ConnectionHelper.m */; }; + FB1A674D2131E65900507771 /* KeyboardSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1A674C2131E65900507771 /* KeyboardSupport.m */; }; FB1D59971BBCCB6400F482CA /* ComputerScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1D59961BBCCB6400F482CA /* ComputerScrollView.m */; }; FB1D599A1BBCCD7E00F482CA /* AppCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1D59991BBCCD7E00F482CA /* AppCollectionView.m */; }; FB290CF219B2C406004C83CF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF119B2C406004C83CF /* Foundation.framework */; }; @@ -125,6 +126,8 @@ D4746EEB1CBC740C006FB401 /* Controller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Controller.swift; 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 = ""; }; + FB1A674B2131E65900507771 /* KeyboardSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyboardSupport.h; sourceTree = ""; }; + FB1A674C2131E65900507771 /* KeyboardSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KeyboardSupport.m; sourceTree = ""; }; FB1D59951BBCCB6400F482CA /* ComputerScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ComputerScrollView.h; sourceTree = ""; }; FB1D59961BBCCB6400F482CA /* ComputerScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ComputerScrollView.m; sourceTree = ""; }; FB1D59981BBCCD7E00F482CA /* AppCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppCollectionView.h; sourceTree = ""; }; @@ -478,6 +481,8 @@ FB89460D19F646E200339C8A /* StreamView.m */, FB4678EB1A50C40900377732 /* OnScreenControls.h */, FB4678EC1A50C40900377732 /* OnScreenControls.m */, + FB1A674B2131E65900507771 /* KeyboardSupport.h */, + FB1A674C2131E65900507771 /* KeyboardSupport.m */, ); path = Input; sourceTree = ""; @@ -889,6 +894,7 @@ buildActionMask = 2147483647; files = ( FB290D0719B2C406004C83CF /* Limelight.xcdatamodeld in Sources */, + FB1A674D2131E65900507771 /* KeyboardSupport.m in Sources */, FB89463219F646E200339C8A /* VideoDecoderRenderer.m in Sources */, FB290D0419B2C406004C83CF /* AppDelegate.m in Sources */, FB9AFD401A7E127D00872C98 /* AppListResponse.m in Sources */, diff --git a/iPad.storyboard b/iPad.storyboard index 42985ea..4a32cfd 100644 --- a/iPad.storyboard +++ b/iPad.storyboard @@ -282,8 +282,20 @@ + + + + + + + + + + + + diff --git a/iPhone.storyboard b/iPhone.storyboard index 2f0dc30..b4e4e36 100644 --- a/iPhone.storyboard +++ b/iPhone.storyboard @@ -1,6 +1,6 @@ - + @@ -19,7 +19,7 @@ - + @@ -48,7 +48,7 @@