Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions Terminal.Gui/Drivers/AnsiHandling/KittyKeyboardPattern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,16 @@ public class KittyKeyboardPattern : AnsiKeyboardParserPattern
return null;
}

string baseKeyCode = "";

if (key.KeyCode < KeyCode.CharMask && (key.KeyCode & (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask)) == 0)
{
baseKeyCode = new Rune ((uint)key.KeyCode).ToString ();
}

// Extract alternate key codes (kitty flag 4: report alternate keys)
var shiftedKeyCode = KeyCode.Null;
var baseLayoutKeyCode = KeyCode.Null;
KeyCode shiftedKeyCode = KeyCode.Null;
KeyCode baseLayoutKeyCode = KeyCode.Null;

if (match.Groups [2].Success && int.TryParse (match.Groups [2].Value, CultureInfo.InvariantCulture, out int shiftedCode) && shiftedCode > 0)
{
Expand All @@ -105,7 +112,7 @@ public class KittyKeyboardPattern : AnsiKeyboardParserPattern
baseLayoutKeyCode = (KeyCode)baseCode;
}

var associatedText = string.Empty;
string associatedText = string.Empty;

if (match.Groups [5].Success)
{
Expand All @@ -117,8 +124,8 @@ public class KittyKeyboardPattern : AnsiKeyboardParserPattern
key = new Key (key) { ShiftedKeyCode = shiftedKeyCode, BaseLayoutKeyCode = baseLayoutKeyCode, AssociatedText = associatedText };
}

string modifierField = match.Groups [4].Value;
modifierField = ApplyImplicitModifierState (key, modifierField);
string originalModifierField = match.Groups [4].Value;
string modifierField = ApplyImplicitModifierState (key, originalModifierField);

if (!string.IsNullOrEmpty (modifierField))
{
Expand All @@ -127,10 +134,10 @@ public class KittyKeyboardPattern : AnsiKeyboardParserPattern

if (!string.IsNullOrEmpty (modifierField))
{
key = ApplyModifiersAndEventType (modifierField, key);
key = ApplyModifiersAndEventType (MaxModifierFieldValue (originalModifierField, modifierField), key);
}

if ((key.IsAlt || key.IsCtrl) && !string.IsNullOrEmpty (key.AssociatedText))
if ((key.IsAlt || key.IsCtrl) && (key.ShiftedKeyCode != KeyCode.Null || baseKeyCode.Equals (key.AssociatedText, StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrEmpty (key.AssociatedText))
Comment thread
BDisp marked this conversation as resolved.
{
key = new Key (key) { AssociatedText = string.Empty };
}
Expand Down Expand Up @@ -222,6 +229,38 @@ private static string ApplyImplicitModifierState (Key key, string modifierField)
return string.Join (':', parts);
}

private static string MaxModifierFieldValue (string modifierField1, string modifierField2)
{
if (string.IsNullOrEmpty (modifierField1))
{
return modifierField2;
}

if (string.IsNullOrEmpty (modifierField2))
{
return modifierField1;
}

string [] parts1 = modifierField1.Split (':');
string [] parts2 = modifierField2.Split (':');

if (parts1.Length == 0 || parts2.Length == 0)
{
return modifierField1;
}

if (!int.TryParse (parts1 [0], CultureInfo.InvariantCulture, out int encodedModifiers1)
|| !int.TryParse (parts2 [0], CultureInfo.InvariantCulture, out int encodedModifiers2))
{
return modifierField1;
}

int maxEncodedModifiers = Math.Max (encodedModifiers1, encodedModifiers2);
parts1 [0] = maxEncodedModifiers.ToString (CultureInfo.InvariantCulture);

return string.Join (':', parts1);
}

private static (Key Key, string ModifierField) NormalizeShiftedPrintableKey (Key key, string modifierField)
{
string [] parts = modifierField.Split (':');
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Input/Keyboard/Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public string AsGrapheme

if (IsShift && ShiftedKeyCode != KeyCode.Null)
{
Rune shiftedRune = ToRune (ShiftedKeyCode);
Rune shiftedRune = ToRune (ShiftedKeyCode | KeyCode.ShiftMask);

if (shiftedRune != default (Rune))
{
Expand Down Expand Up @@ -278,7 +278,7 @@ public Rune AsRune

if (IsShift && ShiftedKeyCode != KeyCode.Null)
{
Rune shiftedRune = ToRune (ShiftedKeyCode);
Rune shiftedRune = ToRune (ShiftedKeyCode | KeyCode.ShiftMask);

if (shiftedRune != default (Rune))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ protected override bool OnKeyDownNotHandled (Key a)
return false;
}

if (a.IsAlt || a.IsCtrl)
// Never insert modified keys, except for AltGr combinations with associated text of the unmodified key.
// This allows users to input characters that require AltGr (e.g. '@' on a Portuguese keyboard which is AltGr+2 with associated text "@"),
// while still preventing most modified keys from being inserted into the TextField.
if ((a.IsAlt && string.IsNullOrEmpty (a.AsGrapheme)) || a.IsCtrl)
{
// Never insert modified keys
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ protected override bool OnKeyDownNotHandled (Key a)
return Autocomplete.ProcessKey (a);
}

if (a.IsAlt || a.IsCtrl)
// Never insert modified keys, except for AltGr combinations with associated text of the unmodified key.
// This allows users to input characters that require AltGr (e.g. '@' on a Portuguese keyboard which is AltGr+2 with associated text "@"),
// while still preventing most modified keys from being inserted into the TextView.
if ((a.IsAlt && string.IsNullOrEmpty (a.AsGrapheme)) || a.IsCtrl)
{
// Never insert modified keys
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void ViewKeyDown_ShiftedAndBase_Preserved ()

// View level — same alternate key fields must arrive
Assert.Single (viewDown);
Assert.Equal (new Key ('@'), viewDown [0]);
Assert.Equal (new Key ('@').WithShift, viewDown [0]);
Assert.Equal ((KeyCode)64, viewDown [0].ShiftedKeyCode);
Assert.Equal (KeyCode.Null, viewDown [0].BaseLayoutKeyCode);
Assert.Equal ("@", viewDown [0].AssociatedText);
Expand All @@ -125,7 +125,7 @@ public void ViewKeyDown_AssociatedText_Preserved ()
Assert.Equal ("!", appDown [0].GetPrintableText ());

Assert.Single (viewDown);
Assert.Equal (new Key ('!'), viewDown [0]);
Assert.Equal (new Key ('!').WithShift, viewDown [0]);
Assert.Equal ("!", viewDown [0].AssociatedText);
Assert.Equal ("!", viewDown [0].GetPrintableText ());
}
Expand Down Expand Up @@ -178,7 +178,7 @@ public void ViewKeyDown_WithModifiersAndEventType_Preserved ()
(_, List<Key> viewDown, _) = InjectRawSequenceToView ("\x1b[50:64;6:1u");

Assert.Single (viewDown);
Assert.Equal (new Key ('@').WithCtrl, viewDown [0]);
Assert.Equal (new Key ('@').WithCtrl.WithShift, viewDown [0]);
Assert.Equal ((KeyCode)64, viewDown [0].ShiftedKeyCode);
Assert.Equal (KeyCode.Null, viewDown [0].BaseLayoutKeyCode);
Assert.Equal (KeyEventType.Press, viewDown [0].EventType);
Expand All @@ -194,7 +194,7 @@ public void ViewKeyUp_AlternateKeys_Preserved ()
// Release events go to KeyUp, not KeyDown
Assert.Empty (viewDown);
Assert.Single (viewUp);
Assert.Equal (new Key ('@'), viewUp [0]);
Assert.Equal (new Key ('@').WithShift, viewUp [0]);
Assert.Equal ((KeyCode)64, viewUp [0].ShiftedKeyCode);
Assert.Equal (KeyCode.Null, viewUp [0].BaseLayoutKeyCode);
Assert.Equal (KeyEventType.Release, viewUp [0].EventType);
Expand Down
Loading
Loading