Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added function key support and key repeat for external keyboards #1718

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
148 changes: 141 additions & 7 deletions app/TerminalView.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ - (void)userContentController:(WKUserContentController *)userContentController d
@interface TerminalView ()

@property (nonatomic) NSMutableArray<UIKeyCommand *> *keyCommands;
@property (nonatomic) NSMutableArray *functionKeys;
@property ScrollbarView *scrollbarView;
@property (nonatomic) BOOL terminalFocused;

Expand Down Expand Up @@ -294,6 +295,12 @@ - (void)insertText:(NSString *)text {
[self.terminal sendInput:data];
}

// This method is used with text that requires no further processing; like the escape sequences from function keys
- (void)insertRawText:(NSString *)text {
NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding];
[self.terminal sendInput:data];
}

- (void)insertControlChar:(char)ch {
if (strchr(controlKeys, ch) != NULL) {
if (ch == ' ') ch = '\0';
Expand All @@ -305,6 +312,22 @@ - (void)insertControlChar:(char)ch {
}
}


- (NSString *)setControlChar:(char)ch {
if (strchr(controlKeys, ch) != NULL) {
if (ch == ' ') ch = '\0';
if (ch == '2') ch = '@';
if (ch == '6') ch = '^';
if (ch != '\0')
ch = toupper(ch) ^ 0x40;
} else {
ch = '\0';
}
return [NSString stringWithFormat:@"%c", ch];
}



- (void)deleteBackward {
[self insertText:@"\x7f"];
}
Expand Down Expand Up @@ -434,6 +457,12 @@ - (UITextAutocorrectionType)autocorrectionType {

- (void)handleKeyCommand:(UIKeyCommand *)command {
NSString *key = command.input;
if (@available(iOS 13.0, *)) {
if ( command.propertyList != nil ) {
[self insertRawText:command.propertyList];
return;
}
}
if (command.modifierFlags == 0) {
if ([key isEqualToString:@"`"] && UserPreferences.shared.backtickMapEscape)
key = UIKeyInputEscape;
Expand Down Expand Up @@ -476,9 +505,49 @@ - (void)handleKeyCommand:(UIKeyCommand *)command {
return _keyCommands;
_keyCommands = [NSMutableArray new];
[self addKeys:controlKeys withModifiers:UIKeyModifierControl];
for (NSString *specialKey in @[UIKeyInputEscape, UIKeyInputUpArrow, UIKeyInputDownArrow,

if (@available(iOS 13.4, *)) {
[self addFunctionKey:UIKeyInputUpArrow withName:@"Up" withNormalEscapeSequence:@"\x1b[A" withShiftEscapeSequence:@"\x1b[1;2A" withControlEscapeSequence:@"\x1b[1;5A"];
[self addFunctionKey:UIKeyInputDownArrow withName:@"Down" withNormalEscapeSequence:@"\x1b[B" withShiftEscapeSequence:@"\x1b[1;2B" withControlEscapeSequence:@"\x1b[1;5B"];
[self addFunctionKey:UIKeyInputRightArrow withName:@"Right" withNormalEscapeSequence:@"\x1b[C" withShiftEscapeSequence:@"\x1b[1;2C" withControlEscapeSequence:@"\x1b[1;5C"];
[self addFunctionKey:UIKeyInputLeftArrow withName:@"Left" withNormalEscapeSequence:@"\x1b[D" withShiftEscapeSequence:@"\x1b[1;2D" withControlEscapeSequence:@"\x1b[1;5D"];
[self addFunctionKey:@"\t" withName:@"Tab" withNormalEscapeSequence:@"\t" withShiftEscapeSequence:@"\x1b[Z" withControlEscapeSequence:NULL];

[self addFunctionKey:UIKeyInputEscape withName:@"Esc" withNormalEscapeSequence:@"\x1b" withShiftEscapeSequence:NULL withControlEscapeSequence:NULL];
// Navigation keys

/*
* Now UIKey equivalent for Ins/help keys presumably because Apple Keyboards don't have Ins key :-( Have to handle these in pressesbegan
[self addFunctionKey:UIKeyInputInsert withNormalEscapeSequence:@"\x1b[2~" withShiftEscapeSequence:@"\x1b[2;2~" withControlEscapeSequence:@"\x1b[2;5~"];
[self addFunctionKey:UIKeyInputHelp withNormalEscapeSequence:@"\x1b[2~" withShiftEscapeSequence:@"\x1b[2;2~" withControlEscapeSequence:@"\x1b[2;5~"];
*/
if (@available(iOS 15.0, *)) {
[self addFunctionKey:UIKeyInputDelete withName:@"Del" withNormalEscapeSequence:@"\x1b[3~" withShiftEscapeSequence:@"\x1b[3;2~" withControlEscapeSequence:@"\x1b[3;5~"];
}
[self addFunctionKey:UIKeyInputPageUp withName:@"PgUp" withNormalEscapeSequence:@"\x1b[5~" withShiftEscapeSequence:@"\x1b[5;2~" withControlEscapeSequence:@"\x1b[5;5~"];
[self addFunctionKey:UIKeyInputPageDown withName:@"PgDn" withNormalEscapeSequence:@"\x1b[6~" withShiftEscapeSequence:@"\x1b[6;2~" withControlEscapeSequence:@"\x1b[6;5~"];
[self addFunctionKey:UIKeyInputHome withName:@"Home" withNormalEscapeSequence:@"\x1bOH" withShiftEscapeSequence:@"\x1b[1;2H" withControlEscapeSequence:@"\x1b[1;5H"];
[self addFunctionKey:UIKeyInputEnd withName:@"End" withNormalEscapeSequence:@"\x1bOF" withShiftEscapeSequence:@"\x1b[1;2F" withControlEscapeSequence:@"\x1b[1;5F"];
// Function keys
[self addFunctionKey:UIKeyInputF1 withName:@"F1" withNormalEscapeSequence:@"\x1bOP" withShiftEscapeSequence:@"\x1b[1;2P" withControlEscapeSequence:@"\x1b[1;5P"];
[self addFunctionKey:UIKeyInputF2 withName:@"F2" withNormalEscapeSequence:@"\x1bOQ" withShiftEscapeSequence:@"\x1b[1;2Q" withControlEscapeSequence:@"\x1b[1;5Q"];
[self addFunctionKey:UIKeyInputF3 withName:@"F3" withNormalEscapeSequence:@"\x1bOR" withShiftEscapeSequence:@"\x1b[1;2R" withControlEscapeSequence:@"\x1b[1;5R"];
[self addFunctionKey:UIKeyInputF4 withName:@"F4" withNormalEscapeSequence:@"\x1bOS" withShiftEscapeSequence:@"\x1b[1;2S" withControlEscapeSequence:@"\x1b[1;5S"];
[self addFunctionKey:UIKeyInputF5 withName:@"F5" withNormalEscapeSequence:@"\x1b[15~" withShiftEscapeSequence:@"\x1b[15;2~" withControlEscapeSequence:@"\x1b[15;5~"];
// Yes, @"\x1b[16~" is missing; it is meant to be
[self addFunctionKey:UIKeyInputF6 withName:@"F6" withNormalEscapeSequence:@"\x1b[17~" withShiftEscapeSequence:@"\x1b[17;2~" withControlEscapeSequence:@"\x1b[17;5~"];
[self addFunctionKey:UIKeyInputF7 withName:@"F7" withNormalEscapeSequence:@"\x1b[18~" withShiftEscapeSequence:@"\x1b[18;2~" withControlEscapeSequence:@"\x1b[18;5~"];
[self addFunctionKey:UIKeyInputF8 withName:@"F8" withNormalEscapeSequence:@"\x1b[19~" withShiftEscapeSequence:@"\x1b[19;2~" withControlEscapeSequence:@"\x1b[19;5~"];
[self addFunctionKey:UIKeyInputF9 withName:@"F9" withNormalEscapeSequence:@"\x1b[20~" withShiftEscapeSequence:@"\x1b[20;2~" withControlEscapeSequence:@"\x1b[20;5~"];
[self addFunctionKey:UIKeyInputF10 withName:@"F10" withNormalEscapeSequence:@"\x1b[21~" withShiftEscapeSequence:@"\x1b[21;2~" withControlEscapeSequence:@"\x1b[21;5~"];
// Yes, @"\x1b[22~" is missing; it is meant to be
[self addFunctionKey:UIKeyInputF11 withName:@"F11" withNormalEscapeSequence:@"\x1b[23~" withShiftEscapeSequence:@"\x1b[23;2~" withControlEscapeSequence:@"\x1b[23;5~"];
[self addFunctionKey:UIKeyInputF12 withName:@"F12" withNormalEscapeSequence:@"\x1b[24~" withShiftEscapeSequence:@"\x1b[24;2~" withControlEscapeSequence:@"\x1b[24;5~"];
} else {
for (NSString *specialKey in @[UIKeyInputEscape, UIKeyInputUpArrow, UIKeyInputDownArrow,
UIKeyInputLeftArrow, UIKeyInputRightArrow, @"\t"]) {
[self addKey:specialKey withModifiers:0];
}
}
if (UserPreferences.shared.capsLockMapping != CapsLockMapNone) {
if (@available(iOS 13, *)); else {
Expand All @@ -498,6 +567,7 @@ - (void)handleKeyCommand:(UIKeyCommand *)command {
modifierFlags:UIKeyModifierCommand|UIKeyModifierShift
action:@selector(clearScrollback:)
discoverabilityTitle:@"Clear Scrollback"]];

return _keyCommands;
}

Expand All @@ -514,6 +584,30 @@ - (void)addKey:(NSString *)key withModifiers:(UIKeyModifierFlags)modifiers {
if (@available(iOS 15, *)) {
command.wantsPriorityOverSystemBehavior = YES;
}

[_keyCommands addObject:command];
}

- (void)addFunctionKey:(NSString *)keyName withName:(NSString *)keyTitle withNormalEscapeSequence:(NSString *)normalEscapeSequence withShiftEscapeSequence:(NSString *)shiftEscapeSequence withControlEscapeSequence:(NSString *)controlEscapeSequence API_AVAILABLE(ios(13.4)) {

UIKeyCommand *command;

command = [UIKeyCommand commandWithTitle: @"" image: nil action:@selector(handleKeyCommand:) input: keyName modifierFlags:0 propertyList:normalEscapeSequence];
if (@available(iOS 15, *)) {
command.wantsPriorityOverSystemBehavior = YES;
}
[_keyCommands addObject:command];

command = [UIKeyCommand commandWithTitle: @"" image: nil action:@selector(handleKeyCommand:) input: keyName modifierFlags:UIKeyModifierShift propertyList:shiftEscapeSequence];
if (@available(iOS 15, *)) {
command.wantsPriorityOverSystemBehavior = YES;
}
[_keyCommands addObject:command];

command = [UIKeyCommand commandWithTitle: @"" image: nil action:@selector(handleKeyCommand:) input: keyName modifierFlags:UIKeyModifierControl propertyList:controlEscapeSequence];
if (@available(iOS 15, *)) {
command.wantsPriorityOverSystemBehavior = YES;
}
[_keyCommands addObject:command];
}

Expand Down Expand Up @@ -546,15 +640,55 @@ - (void)handleCapsLockWithCommand:(UIKeyCommand *)command {
}

- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
bool handled = false;
UIKeyModifierFlags modifier;

if (@available(iOS 13.4, *)) {
UIKey *key = presses.anyObject.key;
if (UserPreferences.shared.overrideControlSpace &&
key.keyCode == UIKeyboardHIDUsageKeyboardSpacebar &&
key.modifierFlags & UIKeyModifierControl) {
return [self insertControlChar:' '];
// this is all to handle Ins/Help key as Apple don't support that key using UIKey interface
UIKey *key;

for (UIPress *aPress in presses) {
key = aPress.key;
handled = false;
// Use of UIKeyboardHID was introduced in 13.4

// ignore modifier keys by themselves
if ( key.keyCode == UIKeyboardHIDUsageKeyboardLeftShift || key.keyCode == UIKeyboardHIDUsageKeyboardLeftControl || key.keyCode == UIKeyboardHIDUsageKeyboardLeftAlt || key.keyCode == UIKeyboardHIDUsageKeyboardRightShift || key.keyCode == UIKeyboardHIDUsageKeyboardRightControl || key.keyCode == UIKeyboardHIDUsageKeyboardRightAlt || key.keyCode == UIKeyboardHIDUsageKeyboardRightGUI ) {
continue;
}

modifier = key.modifierFlags;
if ( modifier & UIKeyModifierNumericPad ) {
modifier &= ~UIKeyModifierNumericPad;
}
if ( modifier & UIKeyModifierAlphaShift) {
modifier &= ~UIKeyModifierAlphaShift;
}
if ( key.keyCode == UIKeyboardHIDUsageKeyboardInsert || key.keyCode == UIKeyboardHIDUsageKeyboardHelp ) {
if ( modifier == 0) {
[self insertRawText:@"\x1b[2~"];
handled = true;
break;
}
if ( modifier & UIKeyModifierShift ) {
[self insertRawText:@"\x1b[2;2~"];
handled = true;
break;
}
if ( modifier & UIKeyModifierControl ) {
[self insertRawText:@"\x1b[2;5~"];
handled = true;
break;
}
}
}
}
return [super pressesBegan:presses withEvent:event];
if ( !handled) {
return [super pressesBegan:presses withEvent:event];
}
}

- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
}

#pragma mark UITextInput stubs
Expand Down