Bläddra i källkod

Cleans up key handling in drivers (was "fixes VkeyPacketSimulator is broken") (#3078)

* Fixes #3054. VkeyPacketSimulator scenario is broken.

* Fix some key handle and unit tests.

* Remove unnecessary conditional.

* Improves key handling.

* Also allow map capslock to shift with accented characters.

* Change to MemberData.

* Remove unnecessary using.

* Fix merge errors.

* Fixes #3095. WindowsDriver should return the mask keys to IsShift, IsAlt and IsCtrl return the right value.

* Modifiers keys are valid to be handled on key down and key up.

* Map KeyCode.Enter to ConsoleKey.Enter and vice versa.

* Updated ScanCodeMapping table with readable constants

* Documented bugs

* Implemented mapping using MapVirtualKeyEx

* Implemented mapping using MapVirtualKeyEx

* Changed KeyCode special keys to match ConsoleKey values + max unicode codepoint

* Fixed bogus CollectionNavigator impl and tests

* Nuked DeleteChar. renamed InsertChar to Insert

* KeyCode.Enter = ConsoleKey.Enter, not \n

* Code cleanup

* Added diag for keyboard layout name

* Fixed AltGr support (hopefully)

* Simplified code

* Simplified KeyCode by removing ShiftKeys

* Fixed TextView

* Code cleanup

* Fixes cursesdriver (somewhat)

* Code cleanup

* netdriver wip

* Fixed netdriver under WSL

* Turned off debug spew

* Removed old code

---------

Co-authored-by: Tig Kindel <[email protected]>
Co-authored-by: Tig Kindel <[email protected]>
BDisp 1 år sedan
förälder
incheckning
0484fc8bf9
32 ändrade filer med 4563 tillägg och 3150 borttagningar
  1. 2 0
      Terminal.Gui/Configuration/KeyJsonConverter.cs
  2. 156 120
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  3. 1665 541
      Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
  4. 71 87
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  5. 1 1
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  6. 22 22
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  7. 298 319
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  8. 157 167
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  9. 64 60
      Terminal.Gui/Input/Key.cs
  10. 1 0
      Terminal.Gui/Terminal.Gui.csproj
  11. 179 182
      Terminal.Gui/Text/CollectionNavigatorBase.cs
  12. 9 3
      Terminal.Gui/View/ViewKeyboard.cs
  13. 1 1
      Terminal.Gui/Views/DateField.cs
  14. 1 1
      Terminal.Gui/Views/FileDialog.cs
  15. 3 4
      Terminal.Gui/Views/TextField.cs
  16. 1 1
      Terminal.Gui/Views/TextValidateField.cs
  17. 1307 1317
      Terminal.Gui/Views/TextView.cs
  18. 1 1
      Terminal.Gui/Views/TimeField.cs
  19. 3 3
      UICatalog/Scenarios/CsvEditor.cs
  20. 1 1
      UICatalog/Scenarios/InteractiveTree.cs
  21. 1 1
      UICatalog/Scenarios/ListColumns.cs
  22. 1 1
      UICatalog/Scenarios/TableEditor.cs
  23. 55 69
      UICatalog/Scenarios/VkeyPacketSimulator.cs
  24. 415 148
      UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs
  25. 81 31
      UnitTests/Input/KeyTests.cs
  26. 17 18
      UnitTests/Text/CollectionNavigatorTests.cs
  27. 12 12
      UnitTests/Text/TextFormatterTests.cs
  28. 12 12
      UnitTests/View/HotKeyTests.cs
  29. 1 1
      UnitTests/Views/DateFieldTests.cs
  30. 5 5
      UnitTests/Views/TextFieldTests.cs
  31. 19 20
      UnitTests/Views/TextViewTests.cs
  32. 1 1
      UnitTests/Views/TimeFieldTests.cs

+ 2 - 0
Terminal.Gui/Configuration/KeyJsonConverter.cs

@@ -8,7 +8,9 @@ namespace Terminal.Gui;
 /// Support for <see cref="Key"/> in JSON in the form of "Ctrl-X" or "Alt-Shift-F1".
 /// </summary>
 public class KeyJsonConverter : JsonConverter<Key> {
+	/// <inheritdoc />
 	public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Key.TryParse (reader.GetString (), out var key) ? key : Key.Empty;
 
+	/// <inheritdoc />
 	public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options) => writer.WriteStringValue (value.ToString ());
 }

+ 156 - 120
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System;
 using System.Diagnostics;
 using System.Linq;
+using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 
@@ -31,7 +32,6 @@ public abstract class ConsoleDriver {
 	internal static bool RunningUnitTests { get; set; }
 
 	#region Setup & Teardown
-
 	/// <summary>
 	/// Initializes the driver
 	/// </summary>
@@ -42,7 +42,6 @@ public abstract class ConsoleDriver {
 	/// Ends the execution of the console driver.
 	/// </summary>
 	internal abstract void End ();
-
 	#endregion
 
 	/// <summary>
@@ -129,10 +128,7 @@ public abstract class ConsoleDriver {
 	/// <param name="rune"></param>
 	/// <returns><see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver
 	/// does not support displaying this rune.</returns>
-	public virtual bool IsRuneSupported (Rune rune)
-	{
-		return Rune.IsValid (rune.Value);
-	}
+	public virtual bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value);
 
 	/// <summary>
 	/// Adds the specified rune to the display at the current cursor position. 
@@ -151,7 +147,7 @@ public abstract class ConsoleDriver {
 	public void AddRune (Rune rune)
 	{
 		int runeWidth = -1;
-		var validLocation = IsValidLocation (Col, Row);
+		bool validLocation = IsValidLocation (Col, Row);
 		if (validLocation) {
 			rune = rune.MakePrintable ();
 			runeWidth = rune.GetColumns ();
@@ -275,7 +271,7 @@ public abstract class ConsoleDriver {
 	public void AddStr (string str)
 	{
 		var runes = str.EnumerateRunes ().ToList ();
-		for (var i = 0; i < runes.Count; i++) {
+		for (int i = 0; i < runes.Count; i++) {
 			//if (runes [i].IsCombiningMark()) {
 
 			//	// Attempt to normalize
@@ -361,8 +357,8 @@ public abstract class ConsoleDriver {
 		lock (Contents) {
 			// Can raise an exception while is still resizing.
 			try {
-				for (var row = 0; row < Rows; row++) {
-					for (var c = 0; c < Cols; c++) {
+				for (int row = 0; row < Rows; row++) {
+					for (int c = 0; c < Cols; c++) {
 						Contents [row, c] = new Cell () {
 							Rune = (Rune)' ',
 							Attribute = new Attribute (Color.White, Color.Black),
@@ -381,11 +377,10 @@ public abstract class ConsoleDriver {
 	public abstract void UpdateScreen ();
 
 	#region Color Handling
-
 	/// <summary>
 	/// Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.
 	/// </summary>
-	public virtual bool SupportsTrueColor { get => true; }
+	public virtual bool SupportsTrueColor => true;
 
 	/// <summary>
 	/// Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors. See <see cref="Application.Force16Colors"/>
@@ -399,7 +394,7 @@ public abstract class ConsoleDriver {
 	/// </remarks>
 	internal virtual bool Force16Colors {
 		get => Application.Force16Colors || !SupportsTrueColor;
-		set => Application.Force16Colors = (value || !SupportsTrueColor);
+		set => Application.Force16Colors = value || !SupportsTrueColor;
 	}
 
 	Attribute _currentAttribute;
@@ -447,17 +442,13 @@ public abstract class ConsoleDriver {
 	/// <param name="foreground">The foreground color.</param>
 	/// <param name="background">The background color.</param>
 	/// <returns>The attribute for the foreground and background colors.</returns>
-	public virtual Attribute MakeColor (Color foreground, Color background)
-	{
+	public virtual Attribute MakeColor (Color foreground, Color background) =>
 		// Encode the colors into the int value.
-		return new Attribute (
-			platformColor: 0, // only used by cursesdriver!
-			foreground: foreground,
-			background: background
+		new (
+			0, // only used by cursesdriver!
+			foreground,
+			background
 		);
-	}
-
-
 	#endregion
 
 	#region Mouse and Keyboard
@@ -509,7 +500,6 @@ public abstract class ConsoleDriver {
 	/// <param name="alt">If <see langword="true"/> simulates the Alt key being pressed.</param>
 	/// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
 	public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
-
 	#endregion
 
 	/// <summary>
@@ -521,16 +511,18 @@ public abstract class ConsoleDriver {
 		/// All diagnostics off
 		/// </summary>
 		Off = 0b_0000_0000,
+
 		/// <summary>
 		/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
 		/// ruler in the frame for any side with a padding value greater than 0.
 		/// </summary>
 		FrameRuler = 0b_0000_0001,
+
 		/// <summary>
 		/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
 		/// 'L', 'R', 'T', and 'B' when clearing <see cref="Thickness"/>'s instead of ' '.
 		/// </summary>
-		FramePadding = 0b_0000_0010,
+		FramePadding = 0b_0000_0010
 	}
 
 	/// <summary>
@@ -552,8 +544,8 @@ public abstract class ConsoleDriver {
 	/// <param name="rune"></param>
 	public void FillRect (Rect rect, Rune rune = default)
 	{
-		for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
-			for (var c = rect.X; c < rect.X + rect.Width; c++) {
+		for (int r = rect.Y; r < rect.Y + rect.Height; r++) {
+			for (int c = rect.X; c < rect.X + rect.Width; c++) {
 				Application.Driver.Move (c, r);
 				Application.Driver.AddRune (rune == default ? new Rune (' ') : rune);
 			}
@@ -575,7 +567,6 @@ public abstract class ConsoleDriver {
 	public virtual string GetVersionInfo () => GetType ().Name;
 }
 
-
 /// <summary>
 /// Terminal Cursor Visibility settings.
 /// </summary>
@@ -631,10 +622,9 @@ public enum CursorVisibility {
 	///	Cursor caret is displayed a block ▉
 	/// </summary>
 	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
-	BoxFix = 0x02020164,
+	BoxFix = 0x02020164
 }
 
-
 /// <summary>
 /// The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a consistent
 /// way for application code to specify keys and receive key events. 
@@ -671,72 +661,59 @@ public enum CursorVisibility {
 [Flags]
 public enum KeyCode : uint {
 	/// <summary>
-	/// Mask that indicates that this is a character value, values outside this range
-	/// indicate special characters like Alt-key combinations or special keys on the
-	/// keyboard like function keys, arrows keys and so on.
+	/// Mask that indicates that the key is a unicode codepoint. Values outside this range
+	/// indicate the key has shift modifiers or is a special key like function keys, arrows keys and so on.
 	/// </summary>
-	CharMask = 0xfffff,
+	CharMask = 0x_f_ffff,
 
 	/// <summary>
 	/// If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
 	/// otherwise, the value is in the the lower bits (as extracted by <see cref="CharMask"/>).
 	/// </summary>
-	SpecialMask = 0xfff00000,
+	SpecialMask = 0x_fff0_0000,
 
 	/// <summary>
-	/// The key code representing null or empty
-	/// </summary>
-	Null = '\0',
-
-	/// <summary>
-	/// Backspace key.
+	/// When this value is set, the Key encodes the sequence Shift-KeyValue.
+	/// The actual value must be extracted by removing the ShiftMask.
 	/// </summary>
-	Backspace = 8,
+	ShiftMask = 0x_1000_0000,
 
 	/// <summary>
-	/// The key code for the tab key (forwards tab key).
+	/// When this value is set, the Key encodes the sequence Alt-KeyValue.
+	/// The actual value must be extracted by removing the AltMask.
 	/// </summary>
-	Tab = 9,
+	AltMask = 0x_8000_0000,
 
 	/// <summary>
-	/// The key code for the return key.
+	/// When this value is set, the Key encodes the sequence Ctrl-KeyValue.
+	/// The actual value must be extracted by removing the CtrlMask.
 	/// </summary>
-	Enter = '\n',
+	CtrlMask = 0x_4000_0000,
 
 	/// <summary>
-	/// The key code for the clear key.
+	/// The key code representing an invalid or empty key.
 	/// </summary>
-	Clear = 12,
+	Null = 0,
 
 	/// <summary>
-	/// The key code for the Shift key.
+	/// Backspace key.
 	/// </summary>
-	ShiftKey = 16,
+	Backspace = 8,
 
 	/// <summary>
-	/// The key code for the Ctrl key.
+	/// The key code for the tab key (forwards tab key).
 	/// </summary>
-	CtrlKey = 17,
+	Tab = 9,
 
 	/// <summary>
-	/// The key code for the Alt key.
+	/// The key code for the return key.
 	/// </summary>
-	AltKey = 18,
+	Enter = ConsoleKey.Enter,
 
 	/// <summary>
-	/// The key code for the CapsLock key.
+	/// The key code for the clear key.
 	/// </summary>
-	CapsLock = 20,
-
-	///// <summary>
-	///// The key code for the NumLock key.
-	///// </summary>
-	//NumLock = 144,
-
-	///// <summary>
-	///// The key code for the ScrollLock key.
-	///// </summary>
-	//ScrollLock = 145,
+	Clear = 12,
 
 	/// <summary>
 	/// The key code for the escape key.
@@ -752,38 +729,47 @@ public enum KeyCode : uint {
 	/// Digit 0.
 	/// </summary>
 	D0 = 48,
+
 	/// <summary>
 	/// Digit 1.
 	/// </summary>
 	D1,
+
 	/// <summary>
 	/// Digit 2.
 	/// </summary>
 	D2,
+
 	/// <summary>
 	/// Digit 3.
 	/// </summary>
 	D3,
+
 	/// <summary>
 	/// Digit 4.
 	/// </summary>
 	D4,
+
 	/// <summary>
 	/// Digit 5.
 	/// </summary>
 	D5,
+
 	/// <summary>
 	/// Digit 6.
 	/// </summary>
 	D6,
+
 	/// <summary>
 	/// Digit 7.
 	/// </summary>
 	D7,
+
 	/// <summary>
 	/// Digit 8.
 	/// </summary>
 	D8,
+
 	/// <summary>
 	/// Digit 9.
 	/// </summary>
@@ -793,271 +779,321 @@ public enum KeyCode : uint {
 	/// The key code for the A key
 	/// </summary>
 	A = 65,
+
 	/// <summary>
 	/// The key code for the B key
 	/// </summary>
 	B,
+
 	/// <summary>
 	/// The key code for the C key
 	/// </summary>
 	C,
+
 	/// <summary>
 	/// The key code for the D key
 	/// </summary>
 	D,
+
 	/// <summary>
 	/// The key code for the E key
 	/// </summary>
 	E,
+
 	/// <summary>
 	/// The key code for the F key
 	/// </summary>
 	F,
+
 	/// <summary>
 	/// The key code for the G key
 	/// </summary>
 	G,
+
 	/// <summary>
 	/// The key code for the H key
 	/// </summary>
 	H,
+
 	/// <summary>
 	/// The key code for the I key
 	/// </summary>
 	I,
+
 	/// <summary>
 	/// The key code for the J key
 	/// </summary>
 	J,
+
 	/// <summary>
 	/// The key code for the K key
 	/// </summary>
 	K,
+
 	/// <summary>
 	/// The key code for the L key
 	/// </summary>
 	L,
+
 	/// <summary>
 	/// The key code for the M key
 	/// </summary>
 	M,
+
 	/// <summary>
 	/// The key code for the N key
 	/// </summary>
 	N,
+
 	/// <summary>
 	/// The key code for the O key
 	/// </summary>
 	O,
+
 	/// <summary>
 	/// The key code for the P key
 	/// </summary>
 	P,
+
 	/// <summary>
 	/// The key code for the Q key
 	/// </summary>
 	Q,
+
 	/// <summary>
 	/// The key code for the R key
 	/// </summary>
 	R,
+
 	/// <summary>
 	/// The key code for the S key
 	/// </summary>
 	S,
+
 	/// <summary>
 	/// The key code for the T key
 	/// </summary>
 	T,
+
 	/// <summary>
 	/// The key code for the U key
 	/// </summary>
 	U,
+
 	/// <summary>
 	/// The key code for the V key
 	/// </summary>
 	V,
+
 	/// <summary>
 	/// The key code for the W key
 	/// </summary>
 	W,
+
 	/// <summary>
 	/// The key code for the X key
 	/// </summary>
 	X,
+
 	/// <summary>
 	/// The key code for the Y key
 	/// </summary>
 	Y,
+
 	/// <summary>
 	/// The key code for the Z key
 	/// </summary>
 	Z,
-	/// <summary>
-	/// The key code for the Delete key.
-	/// </summary>
-	Delete = 127,
 
-	/// <summary>
-	/// When this value is set, the Key encodes the sequence Shift-KeyValue.
-	/// </summary>
-	ShiftMask = 0x10000000,
+	///// <summary>
+	///// The key code for the Delete key.
+	///// </summary>
+	//Delete = 127,
 
-	/// <summary>
-	///   When this value is set, the Key encodes the sequence Alt-KeyValue.
-	///   And the actual value must be extracted by removing the AltMask.
-	/// </summary>
-	AltMask = 0x80000000,
+	// --- Special keys ---
+	// The values below are common non-alphanum keys. Their values are 
+	// based on the .NET ConsoleKey values, which, in-turn are based on the
+	// VK_ values from the Windows API. 
+	// We add MaxCodePoint to avoid conflicts with the Unicode values.
 
 	/// <summary>
-	///   When this value is set, the Key encodes the sequence Ctrl-KeyValue.
-	///   And the actual value must be extracted by removing the CtrlMask.
+	/// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control
+	/// keys. 
 	/// </summary>
-	CtrlMask = 0x40000000,
+	MaxCodePoint = 0x10FFFF,
 
 	/// <summary>
 	/// Cursor up key
 	/// </summary>
-	CursorUp = 0x100000,
+	CursorUp = MaxCodePoint + ConsoleKey.UpArrow,
+
 	/// <summary>
 	/// Cursor down key.
 	/// </summary>
-	CursorDown,
+	CursorDown = MaxCodePoint + ConsoleKey.DownArrow,
+
 	/// <summary>
 	/// Cursor left key.
 	/// </summary>
-	CursorLeft,
+	CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow,
+
 	/// <summary>
 	/// Cursor right key.
 	/// </summary>
-	CursorRight,
+	CursorRight = MaxCodePoint + ConsoleKey.RightArrow,
+
 	/// <summary>
 	/// Page Up key.
 	/// </summary>
-	PageUp,
+	PageUp = MaxCodePoint + ConsoleKey.PageUp,
+
 	/// <summary>
 	/// Page Down key.
 	/// </summary>
-	PageDown,
+	PageDown = MaxCodePoint + ConsoleKey.PageDown,
+
 	/// <summary>
 	/// Home key.
 	/// </summary>
-	Home,
+	Home = MaxCodePoint + ConsoleKey.Home,
+
 	/// <summary>
 	/// End key.
 	/// </summary>
-	End,
+	End = MaxCodePoint + ConsoleKey.End,
 
 	/// <summary>
-	/// Insert character key.
+	/// Insert (INS) key.
 	/// </summary>
-	InsertChar,
+	Insert = MaxCodePoint + ConsoleKey.Insert,
 
 	/// <summary>
-	/// Delete character key.
+	/// Delete (DEL) key.
 	/// </summary>
-	DeleteChar,
+	Delete = MaxCodePoint + ConsoleKey.Delete,
 
 	/// <summary>
 	/// Print screen character key.
 	/// </summary>
-	PrintScreen,
+	PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen,
 
 	/// <summary>
 	/// F1 key.
 	/// </summary>
-	F1,
+	F1 = MaxCodePoint + ConsoleKey.F1,
+
 	/// <summary>
 	/// F2 key.
 	/// </summary>
-	F2,
+	F2 = MaxCodePoint + ConsoleKey.F2,
+
 	/// <summary>
 	/// F3 key.
 	/// </summary>
-	F3,
+	F3 = MaxCodePoint + ConsoleKey.F3,
+
 	/// <summary>
 	/// F4 key.
 	/// </summary>
-	F4,
+	F4 = MaxCodePoint + ConsoleKey.F4,
+
 	/// <summary>
 	/// F5 key.
 	/// </summary>
-	F5,
+	F5 = MaxCodePoint + ConsoleKey.F5,
+
 	/// <summary>
 	/// F6 key.
 	/// </summary>
-	F6,
+	F6 = MaxCodePoint + ConsoleKey.F6,
+
 	/// <summary>
 	/// F7 key.
 	/// </summary>
-	F7,
+	F7 = MaxCodePoint + ConsoleKey.F7,
+
 	/// <summary>
 	/// F8 key.
 	/// </summary>
-	F8,
+	F8 = MaxCodePoint + ConsoleKey.F8,
+
 	/// <summary>
 	/// F9 key.
 	/// </summary>
-	F9,
+	F9 = MaxCodePoint + ConsoleKey.F9,
+
 	/// <summary>
 	/// F10 key.
 	/// </summary>
-	F10,
+	F10 = MaxCodePoint + ConsoleKey.F10,
+
 	/// <summary>
 	/// F11 key.
 	/// </summary>
-	F11,
+	F11 = MaxCodePoint + ConsoleKey.F11,
+
 	/// <summary>
 	/// F12 key.
 	/// </summary>
-	F12,
+	F12 = MaxCodePoint + ConsoleKey.F12,
+
 	/// <summary>
 	/// F13 key.
 	/// </summary>
-	F13,
+	F13 = MaxCodePoint + ConsoleKey.F13,
+
 	/// <summary>
 	/// F14 key.
 	/// </summary>
-	F14,
+	F14 = MaxCodePoint + ConsoleKey.F14,
+
 	/// <summary>
 	/// F15 key.
 	/// </summary>
-	F15,
+	F15 = MaxCodePoint + ConsoleKey.F15,
+
 	/// <summary>
 	/// F16 key.
 	/// </summary>
-	F16,
+	F16 = MaxCodePoint + ConsoleKey.F16,
+
 	/// <summary>
 	/// F17 key.
 	/// </summary>
-	F17,
+	F17 = MaxCodePoint + ConsoleKey.F17,
+
 	/// <summary>
 	/// F18 key.
 	/// </summary>
-	F18,
+	F18 = MaxCodePoint + ConsoleKey.F18,
+
 	/// <summary>
 	/// F19 key.
 	/// </summary>
-	F19,
+	F19 = MaxCodePoint + ConsoleKey.F19,
+
 	/// <summary>
 	/// F20 key.
 	/// </summary>
-	F20,
+	F20 = MaxCodePoint + ConsoleKey.F20,
+
 	/// <summary>
 	/// F21 key.
 	/// </summary>
-	F21,
+	F21 = MaxCodePoint + ConsoleKey.F21,
+
 	/// <summary>
 	/// F22 key.
 	/// </summary>
-	F22,
+	F22 = MaxCodePoint + ConsoleKey.F22,
+
 	/// <summary>
 	/// F23 key.
 	/// </summary>
-	F23,
+	F23 = MaxCodePoint + ConsoleKey.F23,
+
 	/// <summary>
 	/// F24 key.
 	/// </summary>
-	F24,
-}
-
+	F24 = MaxCodePoint + ConsoleKey.F24,
+}

+ 1665 - 541
Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs

@@ -2,597 +2,1721 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.ConsoleDrivers;
+
+/// <summary>
+/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
+/// </summary>
+public static class ConsoleKeyMapping {
+
+#if !WT_ISSUE_8871_FIXED // https://github.com/microsoft/terminal/issues/8871
 	/// <summary>
-	/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
+	/// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
 	/// </summary>
-	public static class ConsoleKeyMapping {
-		class ScanCodeMapping : IEquatable<ScanCodeMapping> {
-			public uint ScanCode;
-			public uint VirtualKey;
-			public ConsoleModifiers Modifiers;
-			public uint UnicodeChar;
-
-			public ScanCodeMapping (uint scanCode, uint virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
-			{
-				ScanCode = scanCode;
-				VirtualKey = virtualKey;
-				Modifiers = modifiers;
-				UnicodeChar = unicodeChar;
-			}
+	/// <param name="vk"></param>
+	/// <param name="uMapType">
+	/// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an un-shifted
+	/// character value in the low order word of the return value. 
+	/// </param>
+	/// <param name="dwhkl"></param>
+	/// <returns>An un-shifted character value in the low order word of the return value. Dead keys (diacritics)
+	/// are indicated by setting the top bit of the return value. If there is no translation,
+	/// the function returns 0. See Remarks.</returns>
+	[DllImport ("user32.dll", EntryPoint = "MapVirtualKeyExW", CharSet = CharSet.Unicode)]
+	extern static uint MapVirtualKeyEx (VK vk, uint uMapType, IntPtr dwhkl);
 
-			public bool Equals (ScanCodeMapping other)
-			{
-				return ScanCode.Equals (other.ScanCode) &&
-					VirtualKey.Equals (other.VirtualKey) &&
-					Modifiers.Equals (other.Modifiers) &&
-					UnicodeChar.Equals (other.UnicodeChar);
-			}
-		}
+	/// <summary>
+	/// Retrieves the active input locale identifier (formerly called the keyboard layout).
+	/// </summary>
+	/// <param name="idThread">0 for current thread</param>
+	/// <returns>The return value is the input locale identifier for the thread.
+	/// The low word contains a Language Identifier for the input language
+	/// and the high word contains a device handle to the physical layout of the keyboard.
+	/// </returns>
+	[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayout", CharSet = CharSet.Unicode)]
+	extern static IntPtr GetKeyboardLayout (IntPtr idThread);
 
-		static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
-		{
-			if (modifiers.HasFlag (ConsoleModifiers.Shift)
-			&& !modifiers.HasFlag (ConsoleModifiers.Alt)
-			&& !modifiers.HasFlag (ConsoleModifiers.Control)) {
-				return ConsoleModifiers.Shift;
-			} else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-				return modifiers;
-			}
+	//[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayoutNameW", CharSet = CharSet.Unicode)]
+	//extern static uint GetKeyboardLayoutName (uint idThread);
+
+	[DllImport ("user32.dll")]
+	extern static IntPtr GetForegroundWindow ();
 
+	[DllImport ("user32.dll")]
+	extern static IntPtr GetWindowThreadProcessId (IntPtr hWnd, IntPtr ProcessId);
+
+	/// <summary>
+	/// Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or characters using
+	/// the Win32 API MapVirtualKey.
+	/// </summary>
+	/// <param name="vk"></param>
+	/// <returns>An un-shifted character value in the low order word of the return value. Dead keys (diacritics)
+	/// are indicated by setting the top bit of the return value. If there is no translation,
+	/// the function returns 0.</returns>
+	public static uint MapVKtoChar (VK vk)
+	{
+		if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
 			return 0;
 		}
+		var tid = GetWindowThreadProcessId (GetForegroundWindow (), 0);
+		var hkl = GetKeyboardLayout (tid);
+		return MapVirtualKeyEx (vk, 2, hkl);
+	}
+#else
+	/// <summary>
+	/// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
+	/// </summary>
+	/// <param name="vk"></param>
+	/// <param name="uMapType">
+	/// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an unshifted
+	/// character value in the low order word of the return value. 
+	/// </param>
+	/// <returns>An unshifted character value in the low order word of the return value. Dead keys (diacritics)
+	/// are indicated by setting the top bit of the return value. If there is no translation,
+	/// the function returns 0. See Remarks.</returns>
+	[DllImport ("user32.dll", EntryPoint = "MapVirtualKeyW", CharSet = CharSet.Unicode)]
+	extern static uint MapVirtualKey (VK vk, uint uMapType = 2);
+
+	uint MapVKtoChar (VK vk) => MapVirtualKeyToCharEx (vk);
+#endif
+	/// <summary>
+	/// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread.
+	/// </summary>
+	/// <param name="pwszKLID"></param>
+	/// <returns></returns>
+	[DllImport ("user32.dll")]
+	extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID);
+
+	public static string GetKeyboardLayoutName ()
+	{
+		if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
+			return "none";
+		}
 
-		static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
-		{
-			switch (propName) {
-			case "UnicodeChar":
-				var sCode = scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
-				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-					return scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0);
-				}
-				return sCode;
-			case "VirtualKey":
-				sCode = scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == modifiers);
-				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-					return scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == 0);
-				}
-				return sCode;
-			}
+		StringBuilder klidSB = new StringBuilder ();
+		GetKeyboardLayoutName (klidSB);
+		return klidSB.ToString ();
+	}
 
-			return null;
+	class ScanCodeMapping : IEquatable<ScanCodeMapping> {
+		public uint ScanCode;
+		public VK VirtualKey;
+		public ConsoleModifiers Modifiers;
+		public uint UnicodeChar;
+
+		public ScanCodeMapping (uint scanCode, VK virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
+		{
+			ScanCode = scanCode;
+			VirtualKey = virtualKey;
+			Modifiers = modifiers;
+			UnicodeChar = unicodeChar;
 		}
 
-		/// <summary>
-		/// Gets the <see cref="ConsoleKey"/> from the provided <see cref="KeyCode"/>.
-		/// </summary>
-		/// <param name="key"></param>
-		/// <returns></returns>
-		public static ConsoleKeyInfo GetConsoleKeyFromKey (KeyCode key)
+		public bool Equals (ScanCodeMapping other)
 		{
-			var mod = new ConsoleModifiers ();
-			if (key.HasFlag (KeyCode.ShiftMask)) {
-				mod |= ConsoleModifiers.Shift;
-			}
-			if (key.HasFlag (KeyCode.AltMask)) {
-				mod |= ConsoleModifiers.Alt;
+			return ScanCode.Equals (other.ScanCode) &&
+				VirtualKey.Equals (other.VirtualKey) &&
+				Modifiers.Equals (other.Modifiers) &&
+				UnicodeChar.Equals (other.UnicodeChar);
+		}
+	}
+
+	static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
+	{
+		if (modifiers.HasFlag (ConsoleModifiers.Shift)
+		&& !modifiers.HasFlag (ConsoleModifiers.Alt)
+		&& !modifiers.HasFlag (ConsoleModifiers.Control)) {
+			return ConsoleModifiers.Shift;
+		} else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+			return modifiers;
+		}
+
+		return 0;
+	}
+
+	static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
+	{
+		switch (propName) {
+		case "UnicodeChar":
+			var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
+			if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+				return _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0);
 			}
-			if (key.HasFlag (KeyCode.CtrlMask)) {
-				mod |= ConsoleModifiers.Control;
+			return sCode;
+		case "VirtualKey":
+			sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == modifiers);
+			if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+				return _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == 0);
 			}
-			return GetConsoleKeyFromKey ((uint)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask), mod, out _);
+			return sCode;
 		}
 
-		/// <summary>
-		/// Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask).
-		/// </summary>
-		/// <param name="keyValue">The key as a unicode codepoint.</param>
-		/// <param name="modifiers">The modifier keys.</param>
-		/// <param name="scanCode">The resulting scan code.</param>
-		/// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
-		public static ConsoleKeyInfo GetConsoleKeyFromKey (uint keyValue, ConsoleModifiers modifiers, out uint scanCode)
-		{
-			scanCode = 0;
-			uint outputChar = keyValue;
-			if (keyValue == 0) {
-				return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
+		return null;
+	}
+
+	// BUGBUG: This API is not correct. It is only used by WindowsDriver in VKPacket scenarios
+	/// <summary>
+	/// Get the scan code from a <see cref="ConsoleKeyInfo"/>.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key info.</param>
+	/// <returns>The value if apply.</returns>
+	public static uint GetScanCodeFromConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+	{
+		var mod = GetModifiers (consoleKeyInfo.Modifiers);
+		ScanCodeMapping scode = GetScanCode ("VirtualKey", (uint)consoleKeyInfo.Key, mod);
+		if (scode != null) {
+			return scode.ScanCode;
+		}
+
+		return 0;
+	}
+
+	// BUGBUG: This API is not correct. It is only used by FakeDriver and VkeyPacketSimulator
+	/// <summary>
+	/// Gets the <see cref="ConsoleKeyInfo"/> from the provided <see cref="KeyCode"/>.
+	/// </summary>
+	/// <param name="key">The key code.</param>
+	/// <returns>The console key info.</returns>
+	public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
+	{
+		var modifiers = MapToConsoleModifiers (key);
+		var keyValue = MapKeyCodeToConsoleKey (key, out bool isConsoleKey);
+		if (isConsoleKey) {
+			var mod = GetModifiers (modifiers);
+			var scode = GetScanCode ("VirtualKey", (uint)keyValue, mod);
+			if (scode != null) {
+				return new ConsoleKeyInfo ((char)scode.UnicodeChar, (ConsoleKey)scode.VirtualKey, modifiers.HasFlag (ConsoleModifiers.Shift),
+					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+			}
+		} else {
+			var keyChar = GetKeyCharFromUnicodeChar ((uint)keyValue, modifiers, out uint consoleKey, out _, isConsoleKey);
+			if (consoleKey != 0) {
+				return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
 					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
 			}
+		}
+
+		return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
+			modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+	}
+
+	/// <summary>
+	/// Map existing <see cref="KeyCode"/> modifiers to <see cref="ConsoleModifiers"/>.
+	/// </summary>
+	/// <param name="key">The key code.</param>
+	/// <returns>The console modifiers.</returns>
+	public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
+	{
+		var modifiers = new ConsoleModifiers ();
+		if (key.HasFlag (KeyCode.ShiftMask)) {
+			modifiers |= ConsoleModifiers.Shift;
+		}
+		if (key.HasFlag (KeyCode.AltMask)) {
+			modifiers |= ConsoleModifiers.Alt;
+		}
+		if (key.HasFlag (KeyCode.CtrlMask)) {
+			modifiers |= ConsoleModifiers.Control;
+		}
 
-			uint consoleKey = (uint)MapKeyToConsoleKey ((KeyCode)keyValue, modifiers, out bool mappable);
-			if (mappable) {
-				var mod = GetModifiers (modifiers);
-				var scode = GetScanCode ("UnicodeChar", keyValue, mod);
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					scanCode = scode.ScanCode;
-					outputChar = scode.UnicodeChar;
+		return modifiers;
+	}
+
+	/// <summary>
+	/// Gets <see cref="ConsoleModifiers"/> from <see cref="bool"/> modifiers.
+	/// </summary>
+	/// <param name="shift">The shift key.</param>
+	/// <param name="alt">The alt key.</param>
+	/// <param name="control">The control key.</param>
+	/// <returns>The console modifiers.</returns>
+	public static ConsoleModifiers GetModifiers (bool shift, bool alt, bool control)
+	{
+		var modifiers = new ConsoleModifiers ();
+		if (shift) {
+			modifiers |= ConsoleModifiers.Shift;
+		}
+		if (alt) {
+			modifiers |= ConsoleModifiers.Alt;
+		}
+		if (control) {
+			modifiers |= ConsoleModifiers.Control;
+		}
+
+		return modifiers;
+	}
+
+	/// <summary>
+	/// Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask).
+	/// </summary>
+	/// <param name="keyValue">The key as a unicode codepoint.</param>
+	/// <param name="modifiers">The modifier keys.</param>
+	/// <param name="scanCode">The resulting scan code.</param>
+	/// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
+	static ConsoleKeyInfo GetConsoleKeyInfoFromKeyChar (uint keyValue, ConsoleModifiers modifiers, out uint scanCode)
+	{
+		scanCode = 0;
+		if (keyValue == 0) {
+			return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
+				modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+		}
+
+		uint outputChar = keyValue;
+		uint consoleKey;
+		if (keyValue > byte.MaxValue) {
+			var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue);
+			if (sCode == null) {
+				consoleKey = (byte)(keyValue & byte.MaxValue);
+				sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)consoleKey);
+				if (sCode == null) {
+					consoleKey = 0;
+					outputChar = keyValue;
 				} else {
-					// If the consoleKey is < 255, retain the lower 8 bits of the key value and set the upper bits to 0xff.
-					// This is a shifted value that will be used by the GetKeyCharFromConsoleKey to do the correct action
-					// because keyValue maybe a UnicodeChar or a ConsoleKey, e.g. for PageUp is passed the ConsoleKey.PageUp
-					consoleKey = consoleKey < 0xff ? consoleKey & 0xff | 0xff << 8 : consoleKey;
-					outputChar = GetKeyCharFromConsoleKey (consoleKey, modifiers, out consoleKey, out scanCode);
+					outputChar = (char)(keyValue >> 8);
 				}
 			} else {
-				var mod = GetModifiers (modifiers);
-				var scode = GetScanCode ("VirtualKey", consoleKey, mod);
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					scanCode = scode.ScanCode;
-					outputChar = scode.UnicodeChar;
-				}
+				consoleKey = (byte)sCode.VirtualKey;
+				outputChar = keyValue;
 			}
+		} else {
+			consoleKey = (byte)keyValue;
+			outputChar = '\0';
+		}
 
-			return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
-					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+		return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
+			modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+	}
+
+	// Used only by unit tests
+	internal static uint GetKeyChar (uint keyValue, ConsoleModifiers modifiers)
+	{
+		if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'A' and <= 'Z') {
+			return keyValue - 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue is >= 'A' and <= 'Z') {
+			return keyValue + 32;
 		}
 
-		/// <summary>
-		/// Get the output character from the <see cref="ConsoleKey"/>, the correct <see cref="ConsoleKey"/>
-		/// and the scan code used on <see cref="WindowsDriver"/>.
-		/// </summary>
-		/// <param name="unicodeChar">The unicode character.</param>
-		/// <param name="modifiers">The modifiers keys.</param>
-		/// <param name="consoleKey">The resulting console key.</param>
-		/// <param name="scanCode">The resulting scan code.</param>
-		/// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
-		static uint GetKeyCharFromConsoleKey (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode)
-		{
-			uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
-			uint keyChar = decodedChar;
-			consoleKey = 0;
-			var mod = GetModifiers (modifiers);
-			scanCode = 0;
-			var scode = unicodeChar != 0 && unicodeChar >> 8 != 0xff ? GetScanCode ("VirtualKey", decodedChar, mod) : null;
+		if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'À' and <= 'Ý') {
+			return keyValue - 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue is >= 'À' and <= 'Ý') {
+			return keyValue + 32;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '0') {
+			return keyValue + 13;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 13 is '0') {
+			return keyValue - 13;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is >= '1' and <= '9' and not '7') {
+			return keyValue - 16;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 16 is >= '1' and <= '9' and not '7') {
+			return keyValue + 16;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '7') {
+			return keyValue - 8;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 8 is '7') {
+			return keyValue + 8;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\'') {
+			return keyValue + 24;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 24 is '\'') {
+			return keyValue - 24;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '«') {
+			return keyValue + 16;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 16 is '«') {
+			return keyValue - 16;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\\') {
+			return keyValue + 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 32 is '\\') {
+			return keyValue - 32;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '+') {
+			return keyValue - 1;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 1 is '+') {
+			return keyValue + 1;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '´') {
+			return keyValue - 84;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 84 is '´') {
+			return keyValue + 84;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is 'º') {
+			return keyValue - 16;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 16 is 'º') {
+			return keyValue + 16;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '~') {
+			return keyValue - 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 32 is '~') {
+			return keyValue + 32;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '<') {
+			return keyValue + 2;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 2 is '<') {
+			return keyValue - 2;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is ',') {
+			return keyValue + 15;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 15 is ',') {
+			return keyValue - 15;
+		}
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '.') {
+			return keyValue + 12;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 12 is '.') {
+			return keyValue - 12;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '-') {
+			return keyValue + 50;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 50 is '-') {
+			return keyValue - 50;
+		}
+
+		return keyValue;
+	}
+
+	/// <summary>
+	/// Get the output character from the <see cref="GetConsoleKeyInfoFromKeyCode"/>, with the correct <see cref="ConsoleKey"/>
+	/// and the scan code used on <see cref="WindowsDriver"/>.
+	/// </summary>
+	/// <param name="unicodeChar">The unicode character.</param>
+	/// <param name="modifiers">The modifiers keys.</param>
+	/// <param name="consoleKey">The resulting console key.</param>
+	/// <param name="scanCode">The resulting scan code.</param>
+	/// <param name="isConsoleKey">Indicates if the <paramref name="unicodeChar"/> is a <see cref="ConsoleKey"/>.</param>
+	/// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
+	/// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
+	internal static uint GetKeyCharFromUnicodeChar (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode, bool isConsoleKey = false)
+	{
+		uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
+		uint keyChar = decodedChar;
+		consoleKey = 0;
+		var mod = GetModifiers (modifiers);
+		scanCode = 0;
+		ScanCodeMapping scode = null;
+		if (unicodeChar != 0 && unicodeChar >> 8 != 0xff && isConsoleKey) {
+			scode = GetScanCode ("VirtualKey", decodedChar, mod);
+		}
+		if (isConsoleKey && scode != null) {
+			consoleKey = (uint)scode.VirtualKey;
+			keyChar = scode.UnicodeChar;
+			scanCode = scode.ScanCode;
+		}
+		if (scode == null) {
+			scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
 			if (scode != null) {
-				consoleKey = scode.VirtualKey;
+				consoleKey = (uint)scode.VirtualKey;
 				keyChar = scode.UnicodeChar;
 				scanCode = scode.ScanCode;
 			}
-			if (scode == null) {
-				scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					keyChar = scode.UnicodeChar;
-					scanCode = scode.ScanCode;
-				}
-			}
-			if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) {
-				string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD);
-				for (int i = 0; i < stFormD.Length; i++) {
-					var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
-					if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) {
-						consoleKey = char.ToUpper (stFormD [i]);
-						scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
-						if (scode != null) {
-							scanCode = scode.ScanCode;
-						}
+		}
+		if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) {
+			string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD);
+			for (int i = 0; i < stFormD.Length; i++) {
+				var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
+				if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) {
+					consoleKey = char.ToUpper (stFormD [i]);
+					scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
+					if (scode != null) {
+						scanCode = scode.ScanCode;
 					}
 				}
 			}
+		}
+		if (keyChar < 255 && consoleKey == 0 && scanCode == 0) {
+			scode = GetScanCode ("VirtualKey", keyChar, mod);
+			if (scode != null) {
+				consoleKey = (uint)scode.VirtualKey;
+				keyChar = scode.UnicodeChar;
+				scanCode = scode.ScanCode;
+			}
+		}
+
+		return keyChar;
+	}
+
+	/// <summary>
+	/// Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.
+	/// </summary>
+	/// <param name="keyValue">The key value.</param>
+	/// <param name="isConsoleKey">Indicates if the <paramref name="keyValue"/> is a <see cref="ConsoleKey"/>.
+	/// <see langword="true"/> means the return value is in the ConsoleKey enum.
+	/// <see langword="false"/> means the return value can be mapped to a valid unicode character.
+	/// </param>
+	/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
+	/// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
+	internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsoleKey)
+	{
+		isConsoleKey = true;
+		keyValue = keyValue & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
+
+		switch (keyValue) {
+		case KeyCode.Enter:
+			return (uint)ConsoleKey.Enter;
+		case KeyCode.CursorUp:
+			return (uint)ConsoleKey.UpArrow;
+		case KeyCode.CursorDown:
+			return (uint)ConsoleKey.DownArrow;
+		case KeyCode.CursorLeft:
+			return (uint)ConsoleKey.LeftArrow;
+		case KeyCode.CursorRight:
+			return (uint)ConsoleKey.RightArrow;
+		case KeyCode.PageUp:
+			return (uint)ConsoleKey.PageUp;
+		case KeyCode.PageDown:
+			return (uint)ConsoleKey.PageDown;
+		case KeyCode.Home:
+			return (uint)ConsoleKey.Home;
+		case KeyCode.End:
+			return (uint)ConsoleKey.End;
+		case KeyCode.Insert:
+			return (uint)ConsoleKey.Insert;
+		case KeyCode.Delete:
+			return (uint)ConsoleKey.Delete;
+		case KeyCode.F1:
+			return (uint)ConsoleKey.F1;
+		case KeyCode.F2:
+			return (uint)ConsoleKey.F2;
+		case KeyCode.F3:
+			return (uint)ConsoleKey.F3;
+		case KeyCode.F4:
+			return (uint)ConsoleKey.F4;
+		case KeyCode.F5:
+			return (uint)ConsoleKey.F5;
+		case KeyCode.F6:
+			return (uint)ConsoleKey.F6;
+		case KeyCode.F7:
+			return (uint)ConsoleKey.F7;
+		case KeyCode.F8:
+			return (uint)ConsoleKey.F8;
+		case KeyCode.F9:
+			return (uint)ConsoleKey.F9;
+		case KeyCode.F10:
+			return (uint)ConsoleKey.F10;
+		case KeyCode.F11:
+			return (uint)ConsoleKey.F11;
+		case KeyCode.F12:
+			return (uint)ConsoleKey.F12;
+		case KeyCode.F13:
+			return (uint)ConsoleKey.F13;
+		case KeyCode.F14:
+			return (uint)ConsoleKey.F14;
+		case KeyCode.F15:
+			return (uint)ConsoleKey.F15;
+		case KeyCode.F16:
+			return (uint)ConsoleKey.F16;
+		case KeyCode.F17:
+			return (uint)ConsoleKey.F17;
+		case KeyCode.F18:
+			return (uint)ConsoleKey.F18;
+		case KeyCode.F19:
+			return (uint)ConsoleKey.F19;
+		case KeyCode.F20:
+			return (uint)ConsoleKey.F20;
+		case KeyCode.F21:
+			return (uint)ConsoleKey.F21;
+		case KeyCode.F22:
+			return (uint)ConsoleKey.F22;
+		case KeyCode.F23:
+			return (uint)ConsoleKey.F23;
+		case KeyCode.F24:
+			return (uint)ConsoleKey.F24;
+		case KeyCode.Tab | KeyCode.ShiftMask:
+			return (uint)ConsoleKey.Tab;
+		}
+
+		isConsoleKey = false;
+		return (uint)keyValue;
+	}
+
+	/// <summary>
+	/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key.</param>
+	/// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
+	public static KeyCode MapConsoleKeyInfoToKeyCode (ConsoleKeyInfo consoleKeyInfo)
+	{
+		KeyCode keyCode;
+
+		switch (consoleKeyInfo.Key) {
+		case ConsoleKey.Enter:
+			keyCode = KeyCode.Enter;
+			break;
+		case ConsoleKey.Delete:
+			keyCode = KeyCode.Delete;
+			break;
+		case ConsoleKey.UpArrow:
+			keyCode = KeyCode.CursorUp;
+			break;
+		case ConsoleKey.DownArrow:
+			keyCode = KeyCode.CursorDown;
+			break;
+		case ConsoleKey.LeftArrow:
+			keyCode = KeyCode.CursorLeft;
+			break;
+		case ConsoleKey.RightArrow:
+			keyCode = KeyCode.CursorRight;
+			break;
+		case ConsoleKey.PageUp:
+			keyCode = KeyCode.PageUp;
+			break;
+		case ConsoleKey.PageDown:
+			keyCode = KeyCode.PageDown;
+			break;
+		case ConsoleKey.Home:
+			keyCode = KeyCode.Home;
+			break;
+		case ConsoleKey.End:
+			keyCode = KeyCode.End;
+			break;
+		case ConsoleKey.Insert:
+			keyCode = KeyCode.Insert;
+			break;
+		case ConsoleKey.F1:
+			keyCode = KeyCode.F1;
+			break;
+		case ConsoleKey.F2:
+			keyCode = KeyCode.F2;
+			break;
+		case ConsoleKey.F3:
+			keyCode = KeyCode.F3;
+			break;
+		case ConsoleKey.F4:
+			keyCode = KeyCode.F4;
+			break;
+		case ConsoleKey.F5:
+			keyCode = KeyCode.F5;
+			break;
+		case ConsoleKey.F6:
+			keyCode = KeyCode.F6;
+			break;
+		case ConsoleKey.F7:
+			keyCode = KeyCode.F7;
+			break;
+		case ConsoleKey.F8:
+			keyCode = KeyCode.F8;
+			break;
+		case ConsoleKey.F9:
+			keyCode = KeyCode.F9;
+			break;
+		case ConsoleKey.F10:
+			keyCode = KeyCode.F10;
+			break;
+		case ConsoleKey.F11:
+			keyCode = KeyCode.F11;
+			break;
+		case ConsoleKey.F12:
+			keyCode = KeyCode.F12;
+			break;
+		case ConsoleKey.F13:
+			keyCode = KeyCode.F13;
+			break;
+		case ConsoleKey.F14:
+			keyCode = KeyCode.F14;
+			break;
+		case ConsoleKey.F15:
+			keyCode = KeyCode.F15;
+			break;
+		case ConsoleKey.F16:
+			keyCode = KeyCode.F16;
+			break;
+		case ConsoleKey.F17:
+			keyCode = KeyCode.F17;
+			break;
+		case ConsoleKey.F18:
+			keyCode = KeyCode.F18;
+			break;
+		case ConsoleKey.F19:
+			keyCode = KeyCode.F19;
+			break;
+		case ConsoleKey.F20:
+			keyCode = KeyCode.F20;
+			break;
+		case ConsoleKey.F21:
+			keyCode = KeyCode.F21;
+			break;
+		case ConsoleKey.F22:
+			keyCode = KeyCode.F22;
+			break;
+		case ConsoleKey.F23:
+			keyCode = KeyCode.F23;
+			break;
+		case ConsoleKey.F24:
+			keyCode = KeyCode.F24;
+			break;
+		case ConsoleKey.Tab:
+			keyCode = KeyCode.Tab;
+			break;
+		default:
+			keyCode = (KeyCode)consoleKeyInfo.KeyChar;
+			break;
+		}
+		keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode);
+
+		return keyCode;
+	}
 
-			return keyChar;
+	/// <summary>
+	/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
+	/// </summary>
+	/// <param name="modifiers">The console modifiers.</param>
+	/// <param name="key">The key code.</param>
+	/// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
+	public static KeyCode MapToKeyCodeModifiers (ConsoleModifiers modifiers, KeyCode key)
+	{
+		var keyMod = new KeyCode ();
+		if ((modifiers & ConsoleModifiers.Shift) != 0) {
+			keyMod = KeyCode.ShiftMask;
+		}
+		if ((modifiers & ConsoleModifiers.Control) != 0) {
+			keyMod |= KeyCode.CtrlMask;
 		}
+		if ((modifiers & ConsoleModifiers.Alt) != 0) {
+			keyMod |= KeyCode.AltMask;
+		}
+
+		return keyMod != KeyCode.Null ? keyMod | key : key;
+	}
 
+	/// <summary>
+	/// Generated from winuser.h. See https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
+	/// </summary>
+	public enum VK : ushort {
 		/// <summary>
-		/// Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.
+		/// Left mouse button.
 		/// </summary>
-		/// <param name="keyValue">The key value.</param>
-		/// <param name="modifiers">The modifiers keys.</param>
-		/// <param name="isMappable">
-		/// <see langword="true"/> means the return value can be mapped to a valid unicode character.
-		/// <see langword="false"/> means the return value is in the ConsoleKey enum.
-		/// </param>
-		/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
-		public static ConsoleKey MapKeyToConsoleKey (KeyCode keyValue, ConsoleModifiers modifiers, out bool isMappable)
-		{
-			isMappable = false;
-
-			switch (keyValue) {
-			case KeyCode.Delete:
-				return ConsoleKey.Delete;
-			case KeyCode.CursorUp:
-				return ConsoleKey.UpArrow;
-			case KeyCode.CursorDown:
-				return ConsoleKey.DownArrow;
-			case KeyCode.CursorLeft:
-				return ConsoleKey.LeftArrow;
-			case KeyCode.CursorRight:
-				return ConsoleKey.RightArrow;
-			case KeyCode.PageUp:
-				return ConsoleKey.PageUp;
-			case KeyCode.PageDown:
-				return ConsoleKey.PageDown;
-			case KeyCode.Home:
-				return ConsoleKey.Home;
-			case KeyCode.End:
-				return ConsoleKey.End;
-			case KeyCode.InsertChar:
-				return ConsoleKey.Insert;
-			case KeyCode.DeleteChar:
-				return ConsoleKey.Delete;
-			case KeyCode.F1:
-				return ConsoleKey.F1;
-			case KeyCode.F2:
-				return ConsoleKey.F2;
-			case KeyCode.F3:
-				return ConsoleKey.F3;
-			case KeyCode.F4:
-				return ConsoleKey.F4;
-			case KeyCode.F5:
-				return ConsoleKey.F5;
-			case KeyCode.F6:
-				return ConsoleKey.F6;
-			case KeyCode.F7:
-				return ConsoleKey.F7;
-			case KeyCode.F8:
-				return ConsoleKey.F8;
-			case KeyCode.F9:
-				return ConsoleKey.F9;
-			case KeyCode.F10:
-				return ConsoleKey.F10;
-			case KeyCode.F11:
-				return ConsoleKey.F11;
-			case KeyCode.F12:
-				return ConsoleKey.F12;
-			case KeyCode.F13:
-				return ConsoleKey.F13;
-			case KeyCode.F14:
-				return ConsoleKey.F14;
-			case KeyCode.F15:
-				return ConsoleKey.F15;
-			case KeyCode.F16:
-				return ConsoleKey.F16;
-			case KeyCode.F17:
-				return ConsoleKey.F17;
-			case KeyCode.F18:
-				return ConsoleKey.F18;
-			case KeyCode.F19:
-				return ConsoleKey.F19;
-			case KeyCode.F20:
-				return ConsoleKey.F20;
-			case KeyCode.F21:
-				return ConsoleKey.F21;
-			case KeyCode.F22:
-				return ConsoleKey.F22;
-			case KeyCode.F23:
-				return ConsoleKey.F23;
-			case KeyCode.F24:
-				return ConsoleKey.F24;
-			case KeyCode.Tab | KeyCode.ShiftMask:
-				return ConsoleKey.Tab;
-			}
+		LBUTTON = 0x01,
 
-			isMappable = true;
+		/// <summary>
+		/// Right mouse button.
+		/// </summary>
+		RBUTTON = 0x02,
 
-			if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= KeyCode.A and <= KeyCode.Z) {
-				return (ConsoleKey)(keyValue - 32);
-			} else if (modifiers == ConsoleModifiers.None && keyValue is >= KeyCode.A and <= KeyCode.Z) {
-				return (ConsoleKey)(keyValue + 32);
-			}
-			if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= (KeyCode)'À' and <= (KeyCode)'Ý') {
-				return (ConsoleKey)(keyValue - 32);
-			} else if (modifiers == ConsoleModifiers.None && keyValue is >= (KeyCode)'À' and <= (KeyCode)'Ý') {
-				return (ConsoleKey)(keyValue + 32);
-			}
+		/// <summary>
+		/// Control-break processing.
+		/// </summary>
+		CANCEL = 0x03,
 
-			return (ConsoleKey)keyValue;
-		}
+		/// <summary>
+		/// Middle mouse button (three-button mouse).
+		/// </summary>
+		MBUTTON = 0x04,
 
 		/// <summary>
-		/// Maps a <see cref="ConsoleKey"/> to a <see cref="KeyCode"/>.
+		/// X1 mouse button.
 		/// </summary>
-		/// <param name="consoleKey">The console key.</param>
-		/// <param name="isMappable">If <see langword="true"/> is mapped to a valid character, otherwise <see langword="false"/>.</param>
-		/// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKey"/>.</returns>
-		public static KeyCode MapConsoleKeyToKey (ConsoleKey consoleKey, out bool isMappable)
-		{
-			isMappable = false;
-
-			switch (consoleKey) {
-			case ConsoleKey.Delete:
-				return KeyCode.Delete;
-			case ConsoleKey.UpArrow:
-				return KeyCode.CursorUp;
-			case ConsoleKey.DownArrow:
-				return KeyCode.CursorDown;
-			case ConsoleKey.LeftArrow:
-				return KeyCode.CursorLeft;
-			case ConsoleKey.RightArrow:
-				return KeyCode.CursorRight;
-			case ConsoleKey.PageUp:
-				return KeyCode.PageUp;
-			case ConsoleKey.PageDown:
-				return KeyCode.PageDown;
-			case ConsoleKey.Home:
-				return KeyCode.Home;
-			case ConsoleKey.End:
-				return KeyCode.End;
-			case ConsoleKey.Insert:
-				return KeyCode.InsertChar;
-			case ConsoleKey.F1:
-				return KeyCode.F1;
-			case ConsoleKey.F2:
-				return KeyCode.F2;
-			case ConsoleKey.F3:
-				return KeyCode.F3;
-			case ConsoleKey.F4:
-				return KeyCode.F4;
-			case ConsoleKey.F5:
-				return KeyCode.F5;
-			case ConsoleKey.F6:
-				return KeyCode.F6;
-			case ConsoleKey.F7:
-				return KeyCode.F7;
-			case ConsoleKey.F8:
-				return KeyCode.F8;
-			case ConsoleKey.F9:
-				return KeyCode.F9;
-			case ConsoleKey.F10:
-				return KeyCode.F10;
-			case ConsoleKey.F11:
-				return KeyCode.F11;
-			case ConsoleKey.F12:
-				return KeyCode.F12;
-			case ConsoleKey.F13:
-				return KeyCode.F13;
-			case ConsoleKey.F14:
-				return KeyCode.F14;
-			case ConsoleKey.F15:
-				return KeyCode.F15;
-			case ConsoleKey.F16:
-				return KeyCode.F16;
-			case ConsoleKey.F17:
-				return KeyCode.F17;
-			case ConsoleKey.F18:
-				return KeyCode.F18;
-			case ConsoleKey.F19:
-				return KeyCode.F19;
-			case ConsoleKey.F20:
-				return KeyCode.F20;
-			case ConsoleKey.F21:
-				return KeyCode.F21;
-			case ConsoleKey.F22:
-				return KeyCode.F22;
-			case ConsoleKey.F23:
-				return KeyCode.F23;
-			case ConsoleKey.F24:
-				return KeyCode.F24;
-			case ConsoleKey.Tab:
-				return KeyCode.Tab;
-			}
-			isMappable = true;
+		XBUTTON1 = 0x05,
 
-			if (consoleKey is >= ConsoleKey.A and <= ConsoleKey.Z) {
-				return (KeyCode)(consoleKey + 32);
-			}
+		/// <summary>
+		/// X2 mouse button.
+		/// </summary>
+		XBUTTON2 = 0x06,
 
-			return (KeyCode)consoleKey;
-		}
+		/// <summary>
+		/// BACKSPACE key.
+		/// </summary>
+		BACK = 0x08,
 
 		/// <summary>
-		/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
+		/// TAB key.
 		/// </summary>
-		/// <param name="keyInfo">The console key info.</param>
-		/// <param name="key">The key.</param>
-		/// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
-		public static KeyCode MapKeyModifiers (ConsoleKeyInfo keyInfo, KeyCode key)
-		{
-			var keyMod = new KeyCode ();
-			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
-				keyMod = KeyCode.ShiftMask;
-			}
-			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
-				keyMod |= KeyCode.CtrlMask;
-			}
-			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
-				keyMod |= KeyCode.AltMask;
-			}
+		TAB = 0x09,
 
-			return keyMod != KeyCode.Null ? keyMod | key : key;
-		}
-
-		static HashSet<ScanCodeMapping> scanCodes = new HashSet<ScanCodeMapping> {
-			new ScanCodeMapping (1, 27, 0, 27), // Escape
-			new ScanCodeMapping (1, 27, ConsoleModifiers.Shift, 27),
-			new ScanCodeMapping (2, 49, 0, 49), // D1
-			new ScanCodeMapping (2, 49, ConsoleModifiers.Shift, 33),
-			new ScanCodeMapping (3, 50, 0, 50), // D2
-			new ScanCodeMapping (3, 50, ConsoleModifiers.Shift, 34),
-			new ScanCodeMapping (3, 50, ConsoleModifiers.Alt | ConsoleModifiers.Control, 64),
-			new ScanCodeMapping (4, 51, 0, 51), // D3
-			new ScanCodeMapping (4, 51, ConsoleModifiers.Shift, 35),
-			new ScanCodeMapping (4, 51, ConsoleModifiers.Alt | ConsoleModifiers.Control, 163),
-			new ScanCodeMapping (5, 52, 0, 52), // D4
-			new ScanCodeMapping (5, 52, ConsoleModifiers.Shift, 36),
-			new ScanCodeMapping (5, 52, ConsoleModifiers.Alt | ConsoleModifiers.Control, 167),
-			new ScanCodeMapping (6, 53, 0, 53), // D5
-			new ScanCodeMapping (6, 53, ConsoleModifiers.Shift, 37),
-			new ScanCodeMapping (6, 53, ConsoleModifiers.Alt | ConsoleModifiers.Control, 8364),
-			new ScanCodeMapping (7, 54, 0, 54), // D6
-			new ScanCodeMapping (7, 54, ConsoleModifiers.Shift, 38),
-			new ScanCodeMapping (8, 55, 0, 55), // D7
-			new ScanCodeMapping (8, 55, ConsoleModifiers.Shift, 47),
-			new ScanCodeMapping (8, 55, ConsoleModifiers.Alt | ConsoleModifiers.Control, 123),
-			new ScanCodeMapping (9, 56, 0, 56), // D8
-			new ScanCodeMapping (9, 56, ConsoleModifiers.Shift, 40),
-			new ScanCodeMapping (9, 56, ConsoleModifiers.Alt | ConsoleModifiers.Control, 91),
-			new ScanCodeMapping (10, 57, 0, 57), // D9
-			new ScanCodeMapping (10, 57, ConsoleModifiers.Shift, 41),
-			new ScanCodeMapping (10, 57, ConsoleModifiers.Alt | ConsoleModifiers.Control, 93),
-			new ScanCodeMapping (11, 48, 0, 48), // D0
-			new ScanCodeMapping (11, 48, ConsoleModifiers.Shift, 61),
-			new ScanCodeMapping (11, 48, ConsoleModifiers.Alt | ConsoleModifiers.Control, 125),
-			new ScanCodeMapping (12, 219, 0, 39), // Oem4
-			new ScanCodeMapping (12, 219, ConsoleModifiers.Shift, 63),
-			new ScanCodeMapping (13, 221, 0, 171), // Oem6
-			new ScanCodeMapping (13, 221, ConsoleModifiers.Shift, 187),
-			new ScanCodeMapping (14, 8, 0, 8), // Backspace
-			new ScanCodeMapping (14, 8, ConsoleModifiers.Shift, 8),
-			new ScanCodeMapping (15, 9, 0, 9), // Tab
-			new ScanCodeMapping (15, 9, ConsoleModifiers.Shift, 15),
-			new ScanCodeMapping (16, 81, 0, 113), // Q
-			new ScanCodeMapping (16, 81, ConsoleModifiers.Shift, 81),
-			new ScanCodeMapping (17, 87, 0, 119), // W
-			new ScanCodeMapping (17, 87, ConsoleModifiers.Shift, 87),
-			new ScanCodeMapping (18, 69, 0, 101), // E
-			new ScanCodeMapping (18, 69, ConsoleModifiers.Shift, 69),
-			new ScanCodeMapping (19, 82, 0, 114), // R
-			new ScanCodeMapping (19, 82, ConsoleModifiers.Shift, 82),
-			new ScanCodeMapping (20, 84, 0, 116), // T
-			new ScanCodeMapping (20, 84, ConsoleModifiers.Shift, 84),
-			new ScanCodeMapping (21, 89, 0, 121), // Y
-			new ScanCodeMapping (21, 89, ConsoleModifiers.Shift, 89),
-			new ScanCodeMapping (22, 85, 0, 117), // U
-			new ScanCodeMapping (22, 85, ConsoleModifiers.Shift, 85),
-			new ScanCodeMapping (23, 73, 0, 105), // I
-			new ScanCodeMapping (23, 73, ConsoleModifiers.Shift, 73),
-			new ScanCodeMapping (24, 79, 0, 111), // O
-			new ScanCodeMapping (24, 79, ConsoleModifiers.Shift, 79),
-			new ScanCodeMapping (25, 80, 0, 112), // P
-			new ScanCodeMapping (25, 80, ConsoleModifiers.Shift, 80),
-			new ScanCodeMapping (26, 187, 0, 43), // OemPlus
-			new ScanCodeMapping (26, 187, ConsoleModifiers.Shift, 42),
-			new ScanCodeMapping (26, 187, ConsoleModifiers.Alt | ConsoleModifiers.Control, 168),
-			new ScanCodeMapping (27, 186, 0, 180), // Oem1
-			new ScanCodeMapping (27, 186, ConsoleModifiers.Shift, 96),
-			new ScanCodeMapping (28, 13, 0, 13), // Enter
-			new ScanCodeMapping (28, 13, ConsoleModifiers.Shift, 13),
-			new ScanCodeMapping (29, 17, 0, 0), // Control
-			new ScanCodeMapping (29, 17, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (scanCode: 30, virtualKey: 65, modifiers: 0, unicodeChar: 97), // VK = A, UC = 'a'
-			new ScanCodeMapping (30, 65, ConsoleModifiers.Shift, 65),  // VK = A | Shift, UC = 'A'
-			new ScanCodeMapping (31, 83, 0, 115), // S
-			new ScanCodeMapping (31, 83, ConsoleModifiers.Shift, 83),
-			new ScanCodeMapping (32, 68, 0, 100), // D
-			new ScanCodeMapping (32, 68, ConsoleModifiers.Shift, 68),
-			new ScanCodeMapping (33, 70, 0, 102), // F
-			new ScanCodeMapping (33, 70, ConsoleModifiers.Shift, 70),
-			new ScanCodeMapping (34, 71, 0, 103), // G
-			new ScanCodeMapping (34, 71, ConsoleModifiers.Shift, 71),
-			new ScanCodeMapping (35, 72, 0, 104), // H
-			new ScanCodeMapping (35, 72, ConsoleModifiers.Shift, 72),
-			new ScanCodeMapping (36, 74, 0, 106), // J
-			new ScanCodeMapping (36, 74, ConsoleModifiers.Shift, 74),
-			new ScanCodeMapping (37, 75, 0, 107), // K
-			new ScanCodeMapping (37, 75, ConsoleModifiers.Shift, 75),
-			new ScanCodeMapping (38, 76, 0, 108), // L
-			new ScanCodeMapping (38, 76, ConsoleModifiers.Shift, 76),
-			new ScanCodeMapping (39, 192, 0, 231), // Oem3
-			new ScanCodeMapping (39, 192, ConsoleModifiers.Shift, 199),
-			new ScanCodeMapping (40, 222, 0, 186), // Oem7
-			new ScanCodeMapping (40, 222, ConsoleModifiers.Shift, 170),
-			new ScanCodeMapping (41, 220, 0, 92), // Oem5
-			new ScanCodeMapping (41, 220, ConsoleModifiers.Shift, 124),
-			new ScanCodeMapping (42, 16, 0, 0), // LShift
-			new ScanCodeMapping (42, 16, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (43, 191, 0, 126), // Oem2
-			new ScanCodeMapping (43, 191, ConsoleModifiers.Shift, 94),
-			new ScanCodeMapping (44, 90, 0, 122), // Z
-			new ScanCodeMapping (44, 90, ConsoleModifiers.Shift, 90),
-			new ScanCodeMapping (45, 88, 0, 120), // X
-			new ScanCodeMapping (45, 88, ConsoleModifiers.Shift, 88),
-			new ScanCodeMapping (46, 67, 0, 99), // C
-			new ScanCodeMapping (46, 67, ConsoleModifiers.Shift, 67),
-			new ScanCodeMapping (47, 86, 0, 118), // V
-			new ScanCodeMapping (47, 86, ConsoleModifiers.Shift, 86),
-			new ScanCodeMapping (48, 66, 0, 98), // B
-			new ScanCodeMapping (48, 66, ConsoleModifiers.Shift, 66),
-			new ScanCodeMapping (49, 78, 0, 110), // N
-			new ScanCodeMapping (49, 78, ConsoleModifiers.Shift, 78),
-			new ScanCodeMapping (50, 77, 0, 109), // M
-			new ScanCodeMapping (50, 77, ConsoleModifiers.Shift, 77),
-			new ScanCodeMapping (51, 188, 0, 44), // OemComma
-			new ScanCodeMapping (51, 188, ConsoleModifiers.Shift, 59),
-			new ScanCodeMapping (52, 190, 0, 46), // OemPeriod
-			new ScanCodeMapping (52, 190, ConsoleModifiers.Shift, 58),
-			new ScanCodeMapping (53, 189, 0, 45), // OemMinus
-			new ScanCodeMapping (53, 189, ConsoleModifiers.Shift, 95),
-			new ScanCodeMapping (54, 16, 0, 0), // RShift
-			new ScanCodeMapping (54, 16, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (55, 44, 0, 0), // PrintScreen
-			new ScanCodeMapping (55, 44, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (56, 18, 0, 0), // Alt
-			new ScanCodeMapping (56, 18, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (57, 32, 0, 32), // Spacebar
-			new ScanCodeMapping (57, 32, ConsoleModifiers.Shift, 32),
-			new ScanCodeMapping (58, 20, 0, 0), // Caps
-			new ScanCodeMapping (58, 20, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (59, 112, 0, 0), // F1
-			new ScanCodeMapping (59, 112, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (60, 113, 0, 0), // F2
-			new ScanCodeMapping (60, 113, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (61, 114, 0, 0), // F3
-			new ScanCodeMapping (61, 114, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (62, 115, 0, 0), // F4
-			new ScanCodeMapping (62, 115, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (63, 116, 0, 0), // F5
-			new ScanCodeMapping (63, 116, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (64, 117, 0, 0), // F6
-			new ScanCodeMapping (64, 117, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (65, 118, 0, 0), // F7
-			new ScanCodeMapping (65, 118, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (66, 119, 0, 0), // F8
-			new ScanCodeMapping (66, 119, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (67, 120, 0, 0), // F9
-			new ScanCodeMapping (67, 120, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (68, 121, 0, 0), // F10
-			new ScanCodeMapping (68, 121, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (69, 144, 0, 0), // Num
-			new ScanCodeMapping (69, 144, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (70, 145, 0, 0), // Scroll
-			new ScanCodeMapping (70, 145, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (71, 36, 0, 0), // Home
-			new ScanCodeMapping (71, 36, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (72, 38, 0, 0), // UpArrow
-			new ScanCodeMapping (72, 38, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (73, 33, 0, 0), // PageUp
-			new ScanCodeMapping (73, 33, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (74, 109, 0, 45), // Subtract
-			new ScanCodeMapping (74, 109, ConsoleModifiers.Shift, 45),
-			new ScanCodeMapping (75, 37, 0, 0), // LeftArrow
-			new ScanCodeMapping (75, 37, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (76, 12, 0, 0), // Center
-			new ScanCodeMapping (76, 12, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (77, 39, 0, 0), // RightArrow
-			new ScanCodeMapping (77, 39, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (78, 107, 0, 43), // Add
-			new ScanCodeMapping (78, 107, ConsoleModifiers.Shift, 43),
-			new ScanCodeMapping (79, 35, 0, 0), // End
-			new ScanCodeMapping (79, 35, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (80, 40, 0, 0), // DownArrow
-			new ScanCodeMapping (80, 40, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (81, 34, 0, 0), // PageDown
-			new ScanCodeMapping (81, 34, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (82, 45, 0, 0), // Insert
-			new ScanCodeMapping (82, 45, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (83, 46, 0, 0), // Delete
-			new ScanCodeMapping (83, 46, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (86, 226, 0, 60), // OEM 102
-			new ScanCodeMapping (86, 226, ConsoleModifiers.Shift, 62),
-			new ScanCodeMapping (87, 122, 0, 0), // F11
-			new ScanCodeMapping (87, 122, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (88, 123, 0, 0), // F12
-			new ScanCodeMapping (88, 123, ConsoleModifiers.Shift, 0)
-		};
-
-		/// <summary>
-		/// Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.
-		/// </summary>
-		/// <param name="consoleKeyInfo">The console key info.</param>
-		/// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
-		/// <remarks>If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be
-		/// a <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
-		/// </remarks>
-		public static ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-		{
-			if (consoleKeyInfo.Key != ConsoleKey.Packet) {
-				return consoleKeyInfo;
-			}
+		/// <summary>
+		/// CLEAR key.
+		/// </summary>
+		CLEAR = 0x0C,
 
-			return GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
-		}
+		/// <summary>
+		/// ENTER key.
+		/// </summary>
+		RETURN = 0x0D,
+
+		/// <summary>
+		/// SHIFT key.
+		/// </summary>
+		SHIFT = 0x10,
+
+		/// <summary>
+		/// CTRL key.
+		/// </summary>
+		CONTROL = 0x11,
+
+		/// <summary>
+		/// ALT key.
+		/// </summary>
+		MENU = 0x12,
+
+		/// <summary>
+		/// PAUSE key.
+		/// </summary>
+		PAUSE = 0x13,
+
+		/// <summary>
+		/// CAPS LOCK key.
+		/// </summary>
+		CAPITAL = 0x14,
+
+		/// <summary>
+		/// IME Kana mode.
+		/// </summary>
+		KANA = 0x15,
+
+		/// <summary>
+		/// IME Hangul mode.
+		/// </summary>
+		HANGUL = 0x15,
+
+		/// <summary>
+		/// IME Junja mode.
+		/// </summary>
+		JUNJA = 0x17,
+
+		/// <summary>
+		/// IME final mode.
+		/// </summary>
+		FINAL = 0x18,
+
+		/// <summary>
+		/// IME Hanja mode.
+		/// </summary>
+		HANJA = 0x19,
+
+		/// <summary>
+		/// IME Kanji mode.
+		/// </summary>
+		KANJI = 0x19,
+
+		/// <summary>
+		/// ESC key.
+		/// </summary>
+		ESCAPE = 0x1B,
+
+		/// <summary>
+		/// IME convert.
+		/// </summary>
+		CONVERT = 0x1C,
+
+		/// <summary>
+		/// IME nonconvert.
+		/// </summary>
+		NONCONVERT = 0x1D,
+
+		/// <summary>
+		/// IME accept.
+		/// </summary>
+		ACCEPT = 0x1E,
+
+		/// <summary>
+		/// IME mode change request.
+		/// </summary>
+		MODECHANGE = 0x1F,
+
+		/// <summary>
+		/// SPACEBAR.
+		/// </summary>
+		SPACE = 0x20,
+
+		/// <summary>
+		/// PAGE UP key.
+		/// </summary>
+		PRIOR = 0x21,
+
+		/// <summary>
+		/// PAGE DOWN key.
+		/// </summary>
+		NEXT = 0x22,
+
+		/// <summary>
+		/// END key.
+		/// </summary>
+		END = 0x23,
+
+		/// <summary>
+		/// HOME key.
+		/// </summary>
+		HOME = 0x24,
+
+		/// <summary>
+		/// LEFT ARROW key.
+		/// </summary>
+		LEFT = 0x25,
+
+		/// <summary>
+		/// UP ARROW key.
+		/// </summary>
+		UP = 0x26,
+
+		/// <summary>
+		/// RIGHT ARROW key.
+		/// </summary>
+		RIGHT = 0x27,
+
+		/// <summary>
+		/// DOWN ARROW key.
+		/// </summary>
+		DOWN = 0x28,
+
+		/// <summary>
+		/// SELECT key.
+		/// </summary>
+		SELECT = 0x29,
+
+		/// <summary>
+		/// PRINT key.
+		/// </summary>
+		PRINT = 0x2A,
+
+		/// <summary>
+		/// EXECUTE key
+		/// </summary>
+		EXECUTE = 0x2B,
+
+		/// <summary>
+		/// PRINT SCREEN key
+		/// </summary>
+		SNAPSHOT = 0x2C,
+
+		/// <summary>
+		/// INS key
+		/// </summary>
+		INSERT = 0x2D,
+
+		/// <summary>
+		/// DEL key
+		/// </summary>
+		DELETE = 0x2E,
+
+		/// <summary>
+		/// HELP key
+		/// </summary>
+		HELP = 0x2F,
+
+		/// <summary>
+		/// Left Windows key (Natural keyboard)
+		/// </summary>
+		LWIN = 0x5B,
+
+		/// <summary>
+		/// Right Windows key (Natural keyboard)
+		/// </summary>
+		RWIN = 0x5C,
+
+		/// <summary>
+		/// Applications key (Natural keyboard)
+		/// </summary>
+		APPS = 0x5D,
+
+		/// <summary>
+		/// Computer Sleep key
+		/// </summary>
+		SLEEP = 0x5F,
+
+		/// <summary>
+		/// Numeric keypad 0 key
+		/// </summary>
+		NUMPAD0 = 0x60,
+
+		/// <summary>
+		/// Numeric keypad 1 key
+		/// </summary>
+		NUMPAD1 = 0x61,
+
+		/// <summary>
+		/// Numeric keypad 2 key
+		/// </summary>
+		NUMPAD2 = 0x62,
+
+		/// <summary>
+		/// Numeric keypad 3 key
+		/// </summary>
+		NUMPAD3 = 0x63,
+
+		/// <summary>
+		/// Numeric keypad 4 key
+		/// </summary>
+		NUMPAD4 = 0x64,
+
+		/// <summary>
+		/// Numeric keypad 5 key
+		/// </summary>
+		NUMPAD5 = 0x65,
+
+		/// <summary>
+		/// Numeric keypad 6 key
+		/// </summary>
+		NUMPAD6 = 0x66,
+
+		/// <summary>
+		/// Numeric keypad 7 key
+		/// </summary>
+		NUMPAD7 = 0x67,
+
+		/// <summary>
+		/// Numeric keypad 8 key
+		/// </summary>
+		NUMPAD8 = 0x68,
+
+		/// <summary>
+		/// Numeric keypad 9 key
+		/// </summary>
+		NUMPAD9 = 0x69,
+
+		/// <summary>
+		/// Multiply key
+		/// </summary>
+		MULTIPLY = 0x6A,
+
+		/// <summary>
+		/// Add key
+		/// </summary>
+		ADD = 0x6B,
+
+		/// <summary>
+		/// Separator key
+		/// </summary>
+		SEPARATOR = 0x6C,
+
+		/// <summary>
+		/// Subtract key
+		/// </summary>
+		SUBTRACT = 0x6D,
+
+		/// <summary>
+		/// Decimal key
+		/// </summary>
+		DECIMAL = 0x6E,
+
+		/// <summary>
+		/// Divide key
+		/// </summary>
+		DIVIDE = 0x6F,
+
+		/// <summary>
+		/// F1 key
+		/// </summary>
+		F1 = 0x70,
+
+		/// <summary>
+		/// F2 key
+		/// </summary>
+		F2 = 0x71,
+
+		/// <summary>
+		/// F3 key
+		/// </summary>
+		F3 = 0x72,
+
+		/// <summary>
+		/// F4 key
+		/// </summary>
+		F4 = 0x73,
+
+		/// <summary>
+		/// F5 key
+		/// </summary>
+		F5 = 0x74,
+
+		/// <summary>
+		/// F6 key
+		/// </summary>
+		F6 = 0x75,
+
+		/// <summary>
+		/// F7 key
+		/// </summary>
+		F7 = 0x76,
+
+		/// <summary>
+		/// F8 key
+		/// </summary>
+		F8 = 0x77,
+
+		/// <summary>
+		/// F9 key
+		/// </summary>
+		F9 = 0x78,
+
+		/// <summary>
+		/// F10 key
+		/// </summary>
+		F10 = 0x79,
+
+		/// <summary>
+		/// F11 key
+		/// </summary>
+		F11 = 0x7A,
+
+		/// <summary>
+		/// F12 key
+		/// </summary>
+		F12 = 0x7B,
+
+		/// <summary>
+		/// F13 key
+		/// </summary>
+		F13 = 0x7C,
+
+		/// <summary>
+		/// F14 key
+		/// </summary>
+		F14 = 0x7D,
+
+		/// <summary>
+		/// F15 key
+		/// </summary>
+		F15 = 0x7E,
+
+		/// <summary>
+		/// F16 key
+		/// </summary>
+		F16 = 0x7F,
+
+		/// <summary>
+		/// F17 key
+		/// </summary>
+		F17 = 0x80,
+
+		/// <summary>
+		/// F18 key
+		/// </summary>
+		F18 = 0x81,
+
+		/// <summary>
+		/// F19 key
+		/// </summary>
+		F19 = 0x82,
+
+		/// <summary>
+		/// F20 key
+		/// </summary>
+		F20 = 0x83,
+
+		/// <summary>
+		/// F21 key
+		/// </summary>
+		F21 = 0x84,
+
+		/// <summary>
+		/// F22 key
+		/// </summary>
+		F22 = 0x85,
+
+		/// <summary>
+		/// F23 key
+		/// </summary>
+		F23 = 0x86,
+
+		/// <summary>
+		/// F24 key
+		/// </summary>
+		F24 = 0x87,
+
+		/// <summary>
+		/// NUM LOCK key
+		/// </summary>
+		NUMLOCK = 0x90,
+
+		/// <summary>
+		/// SCROLL LOCK key
+		/// </summary>
+		SCROLL = 0x91,
+
+		/// <summary>
+		/// NEC PC-9800 kbd definition: '=' key on numpad
+		/// </summary>
+		OEM_NEC_EQUAL = 0x92,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Dictionary' key
+		/// </summary>
+		OEM_FJ_JISHO = 0x92,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Unregister word' key
+		/// </summary>
+		OEM_FJ_MASSHOU = 0x93,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Register word' key
+		/// </summary>
+		OEM_FJ_TOUROKU = 0x94,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Left OYAYUBI' key
+		/// </summary>
+		OEM_FJ_LOYA = 0x95,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Right OYAYUBI' key
+		/// </summary>
+		OEM_FJ_ROYA = 0x96,
+
+		/// <summary>
+		/// Left SHIFT key
+		/// </summary>
+		LSHIFT = 0xA0,
+
+		/// <summary>
+		/// Right SHIFT key
+		/// </summary>
+		RSHIFT = 0xA1,
+
+		/// <summary>
+		/// Left CONTROL key
+		/// </summary>
+		LCONTROL = 0xA2,
+
+		/// <summary>
+		/// Right CONTROL key
+		/// </summary>
+		RCONTROL = 0xA3,
+
+		/// <summary>
+		/// Left MENU key (Left Alt key)
+		/// </summary>
+		LMENU = 0xA4,
+
+		/// <summary>
+		/// Right MENU key (Right Alt key)
+		/// </summary>
+		RMENU = 0xA5,
+
+		/// <summary>
+		/// Browser Back key
+		/// </summary>
+		BROWSER_BACK = 0xA6,
+
+		/// <summary>
+		/// Browser Forward key
+		/// </summary>
+		BROWSER_FORWARD = 0xA7,
+
+		/// <summary>
+		/// Browser Refresh key
+		/// </summary>
+		BROWSER_REFRESH = 0xA8,
+
+		/// <summary>
+		/// Browser Stop key
+		/// </summary>
+		BROWSER_STOP = 0xA9,
+
+		/// <summary>
+		/// Browser Search key
+		/// </summary>
+		BROWSER_SEARCH = 0xAA,
+
+		/// <summary>
+		/// Browser Favorites key
+		/// </summary>
+		BROWSER_FAVORITES = 0xAB,
+
+		/// <summary>
+		/// Browser Home key
+		/// </summary>
+		BROWSER_HOME = 0xAC,
+
+		/// <summary>
+		/// Volume Mute key
+		/// </summary>
+		VOLUME_MUTE = 0xAD,
+
+		/// <summary>
+		/// Volume Down key
+		/// </summary>
+		VOLUME_DOWN = 0xAE,
+
+		/// <summary>
+		/// Volume Up key
+		/// </summary>
+		VOLUME_UP = 0xAF,
+
+		/// <summary>
+		/// Next Track key
+		/// </summary>
+		MEDIA_NEXT_TRACK = 0xB0,
+
+		/// <summary>
+		/// Previous Track key
+		/// </summary>
+		MEDIA_PREV_TRACK = 0xB1,
+
+		/// <summary>
+		/// Stop Media key
+		/// </summary>
+		MEDIA_STOP = 0xB2,
+
+		/// <summary>
+		/// Play/Pause Media key
+		/// </summary>
+		MEDIA_PLAY_PAUSE = 0xB3,
+
+		/// <summary>
+		/// Start Mail key
+		/// </summary>
+		LAUNCH_MAIL = 0xB4,
+
+		/// <summary>
+		/// Select Media key
+		/// </summary>
+		LAUNCH_MEDIA_SELECT = 0xB5,
+
+		/// <summary>
+		/// Start Application 1 key
+		/// </summary>
+		LAUNCH_APP1 = 0xB6,
+
+		/// <summary>
+		/// Start Application 2 key
+		/// </summary>
+		LAUNCH_APP2 = 0xB7,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key
+		/// </summary>
+		OEM_1 = 0xBA,
+
+		/// <summary>
+		/// For any country/region, the '+' key
+		/// </summary>
+		OEM_PLUS = 0xBB,
+
+		/// <summary>
+		/// For any country/region, the ',' key
+		/// </summary>
+		OEM_COMMA = 0xBC,
+
+		/// <summary>
+		/// For any country/region, the '-' key
+		/// </summary>
+		OEM_MINUS = 0xBD,
+
+		/// <summary>
+		/// For any country/region, the '.' key
+		/// </summary>
+		OEM_PERIOD = 0xBE,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key
+		/// </summary>
+		OEM_2 = 0xBF,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key
+		/// </summary>
+		OEM_3 = 0xC0,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key
+		/// </summary>
+		OEM_4 = 0xDB,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key
+		/// </summary>
+		OEM_5 = 0xDC,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key
+		/// </summary>
+		OEM_6 = 0xDD,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key
+		/// </summary>
+		OEM_7 = 0xDE,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard.
+		/// </summary>
+		OEM_8 = 0xDF,
+
+		/// <summary>
+		/// 'AX' key on Japanese AX kbd
+		/// </summary>
+		OEM_AX = 0xE1,
+
+		/// <summary>
+		/// Either the angle bracket key or the backslash key on the RT 102-key keyboard
+		/// </summary>
+		OEM_102 = 0xE2,
+
+		/// <summary>
+		/// Help key on ICO
+		/// </summary>
+		ICO_HELP = 0xE3,
+
+		/// <summary>
+		/// 00 key on ICO
+		/// </summary>
+		ICO_00 = 0xE4,
+
+		/// <summary>
+		/// Process key
+		/// </summary>
+		PROCESSKEY = 0xE5,
+
+		/// <summary>
+		/// Clear key on ICO
+		/// </summary>
+		ICO_CLEAR = 0xE6,
+
+		/// <summary>
+		/// Packet key to be used to pass Unicode characters as if they were keystrokes
+		/// </summary>
+		PACKET = 0xE7,
+
+		/// <summary>
+		/// Reset key
+		/// </summary>
+		OEM_RESET = 0xE9,
+
+		/// <summary>
+		/// Jump key
+		/// </summary>
+		OEM_JUMP = 0xEA,
+
+		/// <summary>
+		/// PA1 key
+		/// </summary>
+		OEM_PA1 = 0xEB,
+
+		/// <summary>
+		/// PA2 key
+		/// </summary>
+		OEM_PA2 = 0xEC,
+
+		/// <summary>
+		/// PA3 key
+		/// </summary>
+		OEM_PA3 = 0xED,
+
+		/// <summary>
+		/// WsCtrl key
+		/// </summary>
+		OEM_WSCTRL = 0xEE,
+
+		/// <summary>
+		/// CuSel key
+		/// </summary>
+		OEM_CUSEL = 0xEF,
+
+		/// <summary>
+		/// Attn key
+		/// </summary>
+		OEM_ATTN = 0xF0,
+
+		/// <summary>
+		/// Finish key
+		/// </summary>
+		OEM_FINISH = 0xF1,
+
+		/// <summary>
+		/// Copy key
+		/// </summary>
+		OEM_COPY = 0xF2,
+
+		/// <summary>
+		/// Auto key
+		/// </summary>
+		OEM_AUTO = 0xF3,
+
+		/// <summary>
+		/// Enlw key
+		/// </summary>
+		OEM_ENLW = 0xF4,
+
+		/// <summary>
+		/// BackTab key
+		/// </summary>
+		OEM_BACKTAB = 0xF5,
+
+		/// <summary>
+		/// Attn key
+		/// </summary>
+		ATTN = 0xF6,
+
+		/// <summary>
+		/// CrSel key
+		/// </summary>
+		CRSEL = 0xF7,
+
+		/// <summary>
+		/// ExSel key
+		/// </summary>
+		EXSEL = 0xF8,
+
+		/// <summary>
+		/// Erase EOF key
+		/// </summary>
+		EREOF = 0xF9,
+
+		/// <summary>
+		/// Play key
+		/// </summary>
+		PLAY = 0xFA,
+
+		/// <summary>
+		/// Zoom key
+		/// </summary>
+		ZOOM = 0xFB,
+
+		/// <summary>
+		/// Reserved
+		/// </summary>
+		NONAME = 0xFC,
+
+		/// <summary>
+		/// PA1 key
+		/// </summary>
+		PA1 = 0xFD,
+
+		/// <summary>
+		/// Clear key
+		/// </summary>
+		OEM_CLEAR = 0xFE
+	}
+
+	// BUGBUG: This database makes no sense. It is not possible to map a VK code to a character without knowing the keyboard layout
+	//         It should be deleted.
+	static HashSet<ScanCodeMapping> _scanCodes = new HashSet<ScanCodeMapping> {
+		new (1, VK.ESCAPE, 0, '\u001B'), // Escape
+		new (1, VK.ESCAPE, ConsoleModifiers.Shift, '\u001B'),
+		new (2, (VK)'1', 0, '1'), // D1
+		new (2, (VK)'1', ConsoleModifiers.Shift, '!'),
+		new (3, (VK)'2', 0, '2'), // D2
+		new (3, (VK)'2', ConsoleModifiers.Shift, '\"'), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (")
+		new (3, (VK)'2', ConsoleModifiers.Alt | ConsoleModifiers.Control, '@'),
+		new (4, (VK)'3', 0, '3'), // D3
+		new (4, (VK)'3', ConsoleModifiers.Shift, '#'),
+		new (4, (VK)'3', ConsoleModifiers.Alt | ConsoleModifiers.Control, '£'),
+		new (5, (VK)'4', 0, '4'), // D4
+		new (5, (VK)'4', ConsoleModifiers.Shift, '$'),
+		new (5, (VK)'4', ConsoleModifiers.Alt | ConsoleModifiers.Control, '§'),
+		new (6, (VK)'5', 0, '5'), // D5
+		new (6, (VK)'5', ConsoleModifiers.Shift, '%'),
+		new (6, (VK)'5', ConsoleModifiers.Alt | ConsoleModifiers.Control, '€'),
+		new (7, (VK)'6', 0, '6'), // D6
+		new (7, (VK)'6', ConsoleModifiers.Shift, '&'),
+		new (8, (VK)'7', 0, '7'), // D7
+		new (8, (VK)'7', ConsoleModifiers.Shift, '/'),
+		new (8, (VK)'7', ConsoleModifiers.Alt | ConsoleModifiers.Control, '{'),
+		new (9, (VK)'8', 0, '8'), // D8
+		new (9, (VK)'8', ConsoleModifiers.Shift, '('),
+		new (9, (VK)'8', ConsoleModifiers.Alt | ConsoleModifiers.Control, '['),
+		new (10, (VK)'9', 0, '9'), // D9
+		new (10, (VK)'9', ConsoleModifiers.Shift, ')'),
+		new (10, (VK)'9', ConsoleModifiers.Alt | ConsoleModifiers.Control, ']'),
+		new (11, (VK)'0', 0, '0'), // D0
+		new (11, (VK)'0', ConsoleModifiers.Shift, '='),
+		new (11, (VK)'0', ConsoleModifiers.Alt | ConsoleModifiers.Control, '}'),
+		new (12, VK.OEM_4, 0, '\''), // Oem4
+		new (12, VK.OEM_4, ConsoleModifiers.Shift, '?'),
+		new (13, VK.OEM_6, 0, '+'), // Oem6
+		new (13, VK.OEM_6, ConsoleModifiers.Shift, '*'),
+		new (14, VK.BACK, 0, '\u0008'), // Backspace
+		new (14, VK.BACK, ConsoleModifiers.Shift, '\u0008'),
+		new (15, VK.TAB, 0, '\u0009'), // Tab
+		new (15, VK.TAB, ConsoleModifiers.Shift, '\u000F'),
+		new (16, (VK)'Q', 0, 'q'), // Q
+		new (16, (VK)'Q', ConsoleModifiers.Shift, 'Q'),
+		new (17, (VK)'W', 0, 'w'), // W
+		new (17, (VK)'W', ConsoleModifiers.Shift, 'W'),
+		new (18, (VK)'E', 0, 'e'), // E
+		new (18, (VK)'E', ConsoleModifiers.Shift, 'E'),
+		new (19, (VK)'R', 0, 'r'), // R
+		new (19, (VK)'R', ConsoleModifiers.Shift, 'R'),
+		new (20, (VK)'T', 0, 't'), // T
+		new (20, (VK)'T', ConsoleModifiers.Shift, 'T'),
+		new (21, (VK)'Y', 0, 'y'), // Y
+		new (21, (VK)'Y', ConsoleModifiers.Shift, 'Y'),
+		new (22, (VK)'U', 0, 'u'), // U
+		new (22, (VK)'U', ConsoleModifiers.Shift, 'U'),
+		new (23, (VK)'I', 0, 'i'), // I
+		new (23, (VK)'I', ConsoleModifiers.Shift, 'I'),
+		new (24, (VK)'O', 0, 'o'), // O
+		new (24, (VK)'O', ConsoleModifiers.Shift, 'O'),
+		new (25, (VK)'P', 0, 'p'), // P
+		new (25, (VK)'P', ConsoleModifiers.Shift, 'P'),
+		new (26, VK.OEM_PLUS, 0, '+'), // OemPlus
+		new (26, VK.OEM_PLUS, ConsoleModifiers.Shift, '*'),
+		new (26, VK.OEM_PLUS, ConsoleModifiers.Alt | ConsoleModifiers.Control, '¨'),
+		new (27, VK.OEM_1, 0, '´'), // Oem1
+		new (27, VK.OEM_1, ConsoleModifiers.Shift, '`'),
+		new (28, VK.RETURN, 0, '\u000D'), // Enter
+		new (28, VK.RETURN, ConsoleModifiers.Shift, '\u000D'),
+		new (29, VK.CONTROL, 0, '\0'), // Control
+		new (29, VK.CONTROL, ConsoleModifiers.Shift, '\0'),
+		new (30, (VK)'A', 0, 'a'), // A
+		new (30, (VK)'A', ConsoleModifiers.Shift, 'A'),
+		new (31, (VK)'S', 0, 's'), // S
+		new (31, (VK)'S', ConsoleModifiers.Shift, 'S'),
+		new (32, (VK)'D', 0, 'd'), // D
+		new (32, (VK)'D', ConsoleModifiers.Shift, 'D'),
+		new (33, (VK)'F', 0, 'f'), // F
+		new (33, (VK)'F', ConsoleModifiers.Shift, 'F'),
+		new (34, (VK)'G', 0, 'g'), // G
+		new (34, (VK)'G', ConsoleModifiers.Shift, 'G'),
+		new (35, (VK)'H', 0, 'h'), // H
+		new (35, (VK)'H', ConsoleModifiers.Shift, 'H'),
+		new (36, (VK)'J', 0, 'j'), // J
+		new (36, (VK)'J', ConsoleModifiers.Shift, 'J'),
+		new (37, (VK)'K', 0, 'k'), // K
+		new (37, (VK)'K', ConsoleModifiers.Shift, 'K'),
+		new (38, (VK)'L', 0, 'l'), // L
+		new (38, (VK)'L', ConsoleModifiers.Shift, 'L'),
+		new (39, VK.OEM_3, 0, '`'), // Oem3 (Backtick/Grave)
+		new (39, VK.OEM_3, ConsoleModifiers.Shift, '~'),
+		new (40, VK.OEM_7, 0, '\''), // Oem7 (Single Quote)
+		new (40, VK.OEM_7, ConsoleModifiers.Shift, '\"'),
+		new (41, VK.OEM_5, 0, '\\'), // Oem5 (Backslash)
+		new (41, VK.OEM_5, ConsoleModifiers.Shift, '|'),
+		new (42, VK.LSHIFT, 0, '\0'), // Left Shift
+		new (42, VK.LSHIFT, ConsoleModifiers.Shift, '\0'),
+		new (43, VK.OEM_2, 0, '/'), // Oem2 (Forward Slash)
+		new (43, VK.OEM_2, ConsoleModifiers.Shift, '?'),
+		new (44, (VK)'Z', 0, 'z'), // Z
+		new (44, (VK)'Z', ConsoleModifiers.Shift, 'Z'),
+		new (45, (VK)'X', 0, 'x'), // X
+		new (45, (VK)'X', ConsoleModifiers.Shift, 'X'),
+		new (46, (VK)'C', 0, 'c'), // C
+		new (46, (VK)'C', ConsoleModifiers.Shift, 'C'),
+		new (47, (VK)'V', 0, 'v'), // V
+		new (47, (VK)'V', ConsoleModifiers.Shift, 'V'),
+		new (48, (VK)'B', 0, 'b'), // B
+		new (48, (VK)'B', ConsoleModifiers.Shift, 'B'),
+		new (49, (VK)'N', 0, 'n'), // N
+		new (49, (VK)'N', ConsoleModifiers.Shift, 'N'),
+		new (50, (VK)'M', 0, 'm'), // M
+		new (50, (VK)'M', ConsoleModifiers.Shift, 'M'),
+		new (51, VK.OEM_COMMA, 0, ','), // OemComma
+		new (51, VK.OEM_COMMA, ConsoleModifiers.Shift, '<'),
+		new (52, VK.OEM_PERIOD, 0, '.'), // OemPeriod
+		new (52, VK.OEM_PERIOD, ConsoleModifiers.Shift, '>'),
+		new (53, VK.OEM_MINUS, 0, '-'), // OemMinus
+		new (53, VK.OEM_MINUS, ConsoleModifiers.Shift, '_'),
+		new (54, VK.RSHIFT, 0, '\0'), // Right Shift
+		new (54, VK.RSHIFT, ConsoleModifiers.Shift, '\0'),
+		new (55, VK.PRINT, 0, '\0'), // Print Screen
+		new (55, VK.PRINT, ConsoleModifiers.Shift, '\0'),
+		new (56, VK.LMENU, 0, '\0'), // Alt
+		new (56, VK.LMENU, ConsoleModifiers.Shift, '\0'),
+		new (57, VK.SPACE, 0, ' '), // Spacebar
+		new (57, VK.SPACE, ConsoleModifiers.Shift, ' '),
+		new (58, VK.CAPITAL, 0, '\0'), // Caps Lock
+		new (58, VK.CAPITAL, ConsoleModifiers.Shift, '\0'),
+		new (59, VK.F1, 0, '\0'), // F1
+		new (59, VK.F1, ConsoleModifiers.Shift, '\0'),
+		new (60, VK.F2, 0, '\0'), // F2
+		new (60, VK.F2, ConsoleModifiers.Shift, '\0'),
+		new (61, VK.F3, 0, '\0'), // F3
+		new (61, VK.F3, ConsoleModifiers.Shift, '\0'),
+		new (62, VK.F4, 0, '\0'), // F4
+		new (62, VK.F4, ConsoleModifiers.Shift, '\0'),
+		new (63, VK.F5, 0, '\0'), // F5
+		new (63, VK.F5, ConsoleModifiers.Shift, '\0'),
+		new (64, VK.F6, 0, '\0'), // F6
+		new (64, VK.F6, ConsoleModifiers.Shift, '\0'),
+		new (65, VK.F7, 0, '\0'), // F7
+		new (65, VK.F7, ConsoleModifiers.Shift, '\0'),
+		new (66, VK.F8, 0, '\0'), // F8
+		new (66, VK.F8, ConsoleModifiers.Shift, '\0'),
+		new (67, VK.F9, 0, '\0'), // F9
+		new (67, VK.F9, ConsoleModifiers.Shift, '\0'),
+		new (68, VK.F10, 0, '\0'), // F10
+		new (68, VK.F10, ConsoleModifiers.Shift, '\0'),
+		new (69, VK.NUMLOCK, 0, '\0'), // Num Lock
+		new (69, VK.NUMLOCK, ConsoleModifiers.Shift, '\0'),
+		new (70, VK.SCROLL, 0, '\0'), // Scroll Lock
+		new (70, VK.SCROLL, ConsoleModifiers.Shift, '\0'),
+		new (71, VK.HOME, 0, '\0'), // Home
+		new (71, VK.HOME, ConsoleModifiers.Shift, '\0'),
+		new (72, VK.UP, 0, '\0'), // Up Arrow
+		new (72, VK.UP, ConsoleModifiers.Shift, '\0'),
+		new (73, VK.PRIOR, 0, '\0'), // Page Up
+		new (73, VK.PRIOR, ConsoleModifiers.Shift, '\0'),
+		new (74, VK.SUBTRACT, 0, '-'), // Subtract (Num Pad '-')
+		new (74, VK.SUBTRACT, ConsoleModifiers.Shift, '-'),
+		new (75, VK.LEFT, 0, '\0'), // Left Arrow
+		new (75, VK.LEFT, ConsoleModifiers.Shift, '\0'),
+		new (76, VK.CLEAR, 0, '\0'), // Center key (Num Pad 5 with Num Lock off)
+		new (76, VK.CLEAR, ConsoleModifiers.Shift, '\0'),
+		new (77, VK.RIGHT, 0, '\0'), // Right Arrow
+		new (77, VK.RIGHT, ConsoleModifiers.Shift, '\0'),
+		new (78, VK.ADD, 0, '+'), // Add (Num Pad '+')
+		new (78, VK.ADD, ConsoleModifiers.Shift, '+'),
+		new (79, VK.END, 0, '\0'), // End
+		new (79, VK.END, ConsoleModifiers.Shift, '\0'),
+		new (80, VK.DOWN, 0, '\0'), // Down Arrow
+		new (80, VK.DOWN, ConsoleModifiers.Shift, '\0'),
+		new (81, VK.NEXT, 0, '\0'), // Page Down
+		new (81, VK.NEXT, ConsoleModifiers.Shift, '\0'),
+		new (82, VK.INSERT, 0, '\0'), // Insert
+		new (82, VK.INSERT, ConsoleModifiers.Shift, '\0'),
+		new (83, VK.DELETE, 0, '\0'), // Delete
+		new (83, VK.DELETE, ConsoleModifiers.Shift, '\0'),
+		new (86, VK.OEM_102, 0, '<'), // OEM 102 (Typically '<' or '|' key next to Left Shift)
+		new (86, VK.OEM_102, ConsoleModifiers.Shift, '>'),
+		new (87, VK.F11, 0, '\0'), // F11
+		new (87, VK.F11, ConsoleModifiers.Shift, '\0'),
+		new (88, VK.F12, 0, '\0'), // F12
+		new (88, VK.F12, ConsoleModifiers.Shift, '\0')
+	};
+
+	/// <summary>
+	/// Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key info.</param>
+	/// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
+	/// <remarks>If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be
+	/// a <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
+	/// </remarks>
+	public static ConsoleKeyInfo DecodeVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+	{
+		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
+			return consoleKeyInfo;
+		}
+
+		return GetConsoleKeyInfoFromKeyChar (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
+	}
+
+	/// <summary>
+	/// Encode the <see cref="ConsoleKeyInfo.KeyChar"/> with the <see cref="ConsoleKeyInfo.Key"/>
+	/// if the first a byte length, otherwise only the KeyChar is considered and searched on the database.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key info.</param>
+	/// <returns>The encoded KeyChar with the Key if both can be shifted, otherwise only the KeyChar.</returns>
+	/// <remarks>This is useful to use with the <see cref="ConsoleKey.Packet"/>.</remarks>
+	public static char EncodeKeyCharForVKPacket (ConsoleKeyInfo consoleKeyInfo)
+	{
+		char keyChar = consoleKeyInfo.KeyChar;
+		ConsoleKey consoleKey = consoleKeyInfo.Key;
+		if (keyChar != 0 && consoleKeyInfo.KeyChar < byte.MaxValue && consoleKey == ConsoleKey.None) {
+			// try to get the ConsoleKey
+			var scode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyChar);
+			if (scode != null) {
+				consoleKey = (ConsoleKey)scode.VirtualKey;
+			}
+		}
+		if (keyChar < byte.MaxValue && consoleKey != ConsoleKey.None) {
+			keyChar = (char)(consoleKeyInfo.KeyChar << 8 | (byte)consoleKey);
+		}
+
+		return keyChar;
 	}
-}
+}

+ 71 - 87
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -14,12 +14,12 @@ namespace Terminal.Gui;
 /// <summary>
 /// This is the Curses driver for the gui.cs/Terminal framework.
 /// </summary>
-internal class CursesDriver : ConsoleDriver {
-
+class CursesDriver : ConsoleDriver {
 	public override int Cols {
 		get => Curses.Cols;
 		internal set => Curses.Cols = value;
 	}
+
 	public override int Rows {
 		get => Curses.Lines;
 		internal set => Curses.Lines = value;
@@ -30,6 +30,7 @@ internal class CursesDriver : ConsoleDriver {
 
 	public override string GetVersionInfo () => $"{Curses.curses_version ()}";
 	UnixMainLoop _mainLoopDriver = null;
+
 	public override bool SupportsTrueColor => false;
 
 	object _processInputToken;
@@ -132,11 +133,9 @@ internal class CursesDriver : ConsoleDriver {
 		}
 	}
 
-	public override bool IsRuneSupported (Rune rune)
-	{
+	public override bool IsRuneSupported (Rune rune) =>
 		// See Issue #2615 - CursesDriver is broken with non-BMP characters
-		return base.IsRuneSupported (rune) && rune.IsBmp;
-	}
+		base.IsRuneSupported (rune) && rune.IsBmp;
 
 	public override void Refresh ()
 	{
@@ -153,7 +152,6 @@ internal class CursesDriver : ConsoleDriver {
 	}
 
 	#region Color Handling
-
 	/// <summary>
 	/// Creates an Attribute from the provided curses-based foreground and background color numbers
 	/// </summary>
@@ -162,16 +160,17 @@ internal class CursesDriver : ConsoleDriver {
 	/// <returns></returns>
 	static Attribute MakeColor (short foreground, short background)
 	{
-		var v = (short)((int)foreground | background << 4);
+		short v = (short)((int)foreground | background << 4);
 
 		// TODO: for TrueColor - Use InitExtendedPair
 		Curses.InitColorPair (v, foreground, background);
 		return new Attribute (
-			platformColor: Curses.ColorPair (v),
-			foreground: CursesColorNumberToColorName (foreground),
-			background: CursesColorNumberToColorName (background));
+			Curses.ColorPair (v),
+			CursesColorNumberToColorName (foreground),
+			CursesColorNumberToColorName (background));
 	}
 
+	/// <inheritdoc/>
 	/// <remarks>
 	/// In the CursesDriver, colors are encoded as an int. 
 	/// The foreground color is stored in the most significant 4 bits, 
@@ -184,9 +183,9 @@ internal class CursesDriver : ConsoleDriver {
 			return MakeColor (ColorNameToCursesColorNumber (foreground.ColorName), ColorNameToCursesColorNumber (background.ColorName));
 		} else {
 			return new Attribute (
-				platformColor: 0,
-				foreground: foreground,
-				background: background);
+				0,
+				foreground,
+				background);
 		}
 	}
 
@@ -267,7 +266,6 @@ internal class CursesDriver : ConsoleDriver {
 		}
 		throw new ArgumentException ("Invalid curses color code");
 	}
-
 	#endregion
 
 	public override void UpdateCursor ()
@@ -367,8 +365,8 @@ internal class CursesDriver : ConsoleDriver {
 		case Curses.KeyEnd: return KeyCode.End;
 		case Curses.KeyNPage: return KeyCode.PageDown;
 		case Curses.KeyPPage: return KeyCode.PageUp;
-		case Curses.KeyDeleteChar: return KeyCode.DeleteChar;
-		case Curses.KeyInsertChar: return KeyCode.InsertChar;
+		case Curses.KeyDeleteChar: return KeyCode.Delete;
+		case Curses.KeyInsertChar: return KeyCode.Insert;
 		case Curses.KeyTab: return KeyCode.Tab;
 		case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
 		case Curses.KeyBackspace: return KeyCode.Backspace;
@@ -423,12 +421,12 @@ internal class CursesDriver : ConsoleDriver {
 	internal void ProcessInput ()
 	{
 		int wch;
-		var code = Curses.get_wch (out wch);
+		int code = Curses.get_wch (out wch);
 		//System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
 		if (code == Curses.ERR) {
 			return;
 		}
-		KeyCode k = KeyCode.Null;
+		var k = KeyCode.Null;
 
 		if (code == Curses.KEY_CODE_YES) {
 			while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) {
@@ -443,11 +441,11 @@ internal class CursesDriver : ConsoleDriver {
 
 				while (wch2 == Curses.KeyMouse) {
 					Key kea = null;
-					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
-							new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false),
-							new ConsoleKeyInfo ('[', 0, false, false, false),
-							new ConsoleKeyInfo ('<', 0, false, false, false)
-						};
+					var cki = new ConsoleKeyInfo [] {
+						new ((char)KeyCode.Esc, 0, false, false, false),
+						new ('[', 0, false, false, false),
+						new ('<', 0, false, false, false)
+					};
 					code = 0;
 					HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
 				}
@@ -503,27 +501,27 @@ internal class CursesDriver : ConsoleDriver {
 				} else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9) {
 					k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
 				} else if (wch2 == Curses.KeyCSI) {
-					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
-							new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false),
-							new ConsoleKeyInfo ('[', 0, false, false, false)
-						};
+					var cki = new ConsoleKeyInfo [] {
+						new ((char)KeyCode.Esc, 0, false, false, false),
+						new ('[', 0, false, false, false)
+					};
 					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
 					return;
 				} else {
 					// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
 					if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) {
-						k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~((int)KeyCode.CtrlMask)));
+						k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask));
 					}
 					if (wch2 == 0) {
 						k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
 					} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
 						k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
 					} else if (wch2 < 256) {
-						k = (KeyCode)wch2 | KeyCode.AltMask;
+						k = (KeyCode)wch2;// | KeyCode.AltMask;
 					} else {
 						k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
 					}
-				}
+				} 
 				key = new Key (k);
 			} else {
 				key = new Key (KeyCode.Esc);
@@ -545,9 +543,11 @@ internal class CursesDriver : ConsoleDriver {
 				}
 			} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
 				k = (KeyCode)wch | KeyCode.ShiftMask;
-			} else if (wch <= 'z') {
-				k = (KeyCode)wch & ~KeyCode.Space;
 			} 
+			
+			if (wch == '\n' || wch == '\r') {
+				k = KeyCode.Enter;
+			}
 			OnKeyDown (new Key (k));
 			OnKeyUp (new Key (k));
 		}
@@ -561,7 +561,7 @@ internal class CursesDriver : ConsoleDriver {
 			code = Curses.get_wch (out wch2);
 			var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
 			if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) {
-				EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List<MouseFlags> mouseFlags, out Point pos, out _, ProcessMouseEvent);
+				EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out var mouseFlags, out var pos, out _, ProcessMouseEvent);
 				if (isKeyMouse) {
 					foreach (var mf in mouseFlags) {
 						ProcessMouseEvent (mf, pos);
@@ -572,9 +572,8 @@ internal class CursesDriver : ConsoleDriver {
 							false, false, false), cki);
 					}
 				} else {
-					k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
-					k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
-					keyEventArgs = new (k);
+					k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
+					keyEventArgs = new Key (k);
 					OnKeyDown (keyEventArgs);
 				}
 			} else {
@@ -587,36 +586,27 @@ internal class CursesDriver : ConsoleDriver {
 
 	void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
 	{
-		bool WasButtonReleased (MouseFlags flag)
-		{
-			return flag.HasFlag (MouseFlags.Button1Released) ||
-				flag.HasFlag (MouseFlags.Button2Released) ||
-				flag.HasFlag (MouseFlags.Button3Released) ||
-				flag.HasFlag (MouseFlags.Button4Released);
-		}
-
-		bool IsButtonNotPressed (MouseFlags flag)
-		{
-			return !flag.HasFlag (MouseFlags.Button1Pressed) &&
-				!flag.HasFlag (MouseFlags.Button2Pressed) &&
-				!flag.HasFlag (MouseFlags.Button3Pressed) &&
-				!flag.HasFlag (MouseFlags.Button4Pressed);
-		}
-
-		bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
-		{
-			return flag.HasFlag (MouseFlags.Button1Clicked) ||
-				flag.HasFlag (MouseFlags.Button2Clicked) ||
-				flag.HasFlag (MouseFlags.Button3Clicked) ||
-				flag.HasFlag (MouseFlags.Button4Clicked) ||
-				flag.HasFlag (MouseFlags.Button1DoubleClicked) ||
-				flag.HasFlag (MouseFlags.Button2DoubleClicked) ||
-				flag.HasFlag (MouseFlags.Button3DoubleClicked) ||
-				flag.HasFlag (MouseFlags.Button4DoubleClicked);
-		}
-
-		if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) ||
-			(IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0)) {
+		bool WasButtonReleased (MouseFlags flag) => flag.HasFlag (MouseFlags.Button1Released) ||
+							flag.HasFlag (MouseFlags.Button2Released) ||
+							flag.HasFlag (MouseFlags.Button3Released) ||
+							flag.HasFlag (MouseFlags.Button4Released);
+
+		bool IsButtonNotPressed (MouseFlags flag) => !flag.HasFlag (MouseFlags.Button1Pressed) &&
+								!flag.HasFlag (MouseFlags.Button2Pressed) &&
+								!flag.HasFlag (MouseFlags.Button3Pressed) &&
+								!flag.HasFlag (MouseFlags.Button4Pressed);
+
+		bool IsButtonClickedOrDoubleClicked (MouseFlags flag) => flag.HasFlag (MouseFlags.Button1Clicked) ||
+									flag.HasFlag (MouseFlags.Button2Clicked) ||
+									flag.HasFlag (MouseFlags.Button3Clicked) ||
+									flag.HasFlag (MouseFlags.Button4Clicked) ||
+									flag.HasFlag (MouseFlags.Button1DoubleClicked) ||
+									flag.HasFlag (MouseFlags.Button2DoubleClicked) ||
+									flag.HasFlag (MouseFlags.Button3DoubleClicked) ||
+									flag.HasFlag (MouseFlags.Button4DoubleClicked);
+
+		if (WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags) ||
+		IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0) {
 			return;
 		}
 
@@ -638,7 +628,7 @@ internal class CursesDriver : ConsoleDriver {
 		//	// If xclip is installed on Linux under WSL, this will return true.
 		//	return false;
 		//}
-		var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
+		(int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
 		if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
 			return true;
 		}
@@ -675,8 +665,9 @@ internal class CursesDriver : ConsoleDriver {
 	{
 		visibility = CursorVisibility.Invisible;
 
-		if (!_currentCursorVisibility.HasValue)
+		if (!_currentCursorVisibility.HasValue) {
 			return false;
+		}
 
 		visibility = _currentCursorVisibility.Value;
 
@@ -691,11 +682,11 @@ internal class CursesDriver : ConsoleDriver {
 		}
 
 		if (!RunningUnitTests) {
-			Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
+			Curses.curs_set ((int)visibility >> 16 & 0x000000FF);
 		}
 
 		if (visibility != CursorVisibility.Invisible) {
-			Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF)));
+			Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)((int)visibility >> 24 & 0xFF)));
 		}
 
 		_currentCursorVisibility = visibility;
@@ -704,17 +695,14 @@ internal class CursesDriver : ConsoleDriver {
 	}
 
 	/// <inheritdoc/>
-	public override bool EnsureCursorVisibility ()
-	{
-		return false;
-	}
+	public override bool EnsureCursorVisibility () => false;
 
 	public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
 	{
 		KeyCode key;
 
 		if (consoleKey == ConsoleKey.Packet) {
-			ConsoleModifiers mod = new ConsoleModifiers ();
+			var mod = new ConsoleModifiers ();
 			if (shift) {
 				mod |= ConsoleModifiers.Shift;
 			}
@@ -724,11 +712,9 @@ internal class CursesDriver : ConsoleDriver {
 			if (control) {
 				mod |= ConsoleModifiers.Control;
 			}
-			var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyChar, mod, out _);
-			key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)cKeyInfo.Key, out bool mappable);
-			if (mappable) {
-				key = (KeyCode)cKeyInfo.KeyChar;
-			}
+			var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
+			cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
+			key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo);
 		} else {
 			key = (KeyCode)keyChar;
 		}
@@ -737,16 +723,14 @@ internal class CursesDriver : ConsoleDriver {
 		OnKeyUp (new Key (key));
 		//OnKeyPressed (new KeyEventArgsEventArgs (key));
 	}
-
-
 }
 
-internal static class Platform {
+static class Platform {
 	[DllImport ("libc")]
-	static extern int uname (IntPtr buf);
+	extern static int uname (IntPtr buf);
 
 	[DllImport ("libc")]
-	static extern int killpg (int pgrp, int pid);
+	extern static int killpg (int pgrp, int pid);
 
 	static int _suspendSignal;
 
@@ -793,7 +777,7 @@ internal static class Platform {
 	/// Suspends the process by sending SIGTSTP to itself
 	/// </summary>
 	/// <returns>The suspend.</returns>
-	static public bool Suspend ()
+	public static bool Suspend ()
 	{
 		int signal = GetSuspendSignal ();
 		if (signal == -1) {

+ 1 - 1
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -800,7 +800,7 @@ public static class FakeConsole {
 	{
 		MockKeyPresses.Push (new ConsoleKeyInfo (
 			(char)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask),
-			ConsoleKeyMapping.GetConsoleKeyFromKey (key).Key,
+			ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key).Key,
 			key.HasFlag (KeyCode.ShiftMask),
 			key.HasFlag (KeyCode.AltMask),
 			key.HasFlag (KeyCode.CtrlMask)));

+ 22 - 22
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -205,39 +205,39 @@ public class FakeDriver : ConsoleDriver {
 	{
 		switch (keyInfo.Key) {
 		case ConsoleKey.Escape:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Esc);
 		case ConsoleKey.Tab:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Tab);
 		case ConsoleKey.Clear:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Clear);
 		case ConsoleKey.Home:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Home);
 		case ConsoleKey.End:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.End);
 		case ConsoleKey.LeftArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorLeft);
 		case ConsoleKey.RightArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorRight);
 		case ConsoleKey.UpArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorUp);
 		case ConsoleKey.DownArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorDown);
 		case ConsoleKey.PageUp:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageUp);
 		case ConsoleKey.PageDown:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageDown);
 		case ConsoleKey.Enter:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Enter);
 		case ConsoleKey.Spacebar:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
 		case ConsoleKey.Backspace:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Backspace);
 		case ConsoleKey.Delete:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Delete);
 		case ConsoleKey.Insert:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Insert);
 		case ConsoleKey.PrintScreen:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PrintScreen);
 
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
@@ -256,25 +256,25 @@ public class FakeDriver : ConsoleDriver {
 				return KeyCode.Null;
 			}
 
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
 		}
 
 		var key = keyInfo.Key;
 		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
 			var delta = key - ConsoleKey.A;
 			if (keyInfo.KeyChar != (uint)key) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)keyInfo.KeyChar);
+				return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
 			}
 			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)
 			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
 			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
+				return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.A + delta));
 			}
 			var alphaBase = ((keyInfo.Modifiers != ConsoleModifiers.Shift)) ? 'A' : 'a';
 			return (KeyCode)((uint)alphaBase + delta);
 		}
 
-		return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+		return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
 	}
 
 	private CursorVisibility _savedCursorVisibility;
@@ -282,7 +282,7 @@ public class FakeDriver : ConsoleDriver {
 	void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo)
 	{
 		if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-			consoleKeyInfo = ConsoleKeyMapping.FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+			consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 		}
 
 		var map = MapKey (consoleKeyInfo);

+ 298 - 319
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -7,13 +7,15 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using System.Text;
-using Terminal.Gui.ConsoleDrivers;
+using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.NetEvents;
+using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
+
 class NetWinVTConsole {
 	IntPtr _inputHandle, _outputHandle, _errorHandle;
 	uint _originalInputConsoleMode, _originalOutputConsoleMode, _originalErrorConsoleMode;
@@ -49,7 +51,7 @@ class NetWinVTConsole {
 			throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}.");
 		}
 		_originalErrorConsoleMode = mode;
-		if ((mode & (DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) {
+		if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN) {
 			mode |= DISABLE_NEWLINE_AUTO_RETURN;
 			if (!SetConsoleMode (_errorHandle, mode)) {
 				throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}.");
@@ -93,28 +95,28 @@ class NetWinVTConsole {
 	const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
 
 	[DllImport ("kernel32.dll", SetLastError = true)]
-	static extern IntPtr GetStdHandle (int nStdHandle);
+	extern static IntPtr GetStdHandle (int nStdHandle);
 
 	[DllImport ("kernel32.dll")]
-	static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
+	extern static bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
 
 	[DllImport ("kernel32.dll")]
-	static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
+	extern static bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
 
 	[DllImport ("kernel32.dll")]
-	static extern uint GetLastError ();
+	extern static uint GetLastError ();
 }
 
-internal class NetEvents : IDisposable {
-	readonly ManualResetEventSlim _inputReady = new ManualResetEventSlim (false);
+class NetEvents : IDisposable {
+	readonly ManualResetEventSlim _inputReady = new (false);
 	CancellationTokenSource _inputReadyCancellationTokenSource;
 
-	readonly ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false);
+	readonly ManualResetEventSlim _waitForStart = new (false);
 	//CancellationTokenSource _waitForStartCancellationTokenSource;
 
-	readonly ManualResetEventSlim _winChange = new ManualResetEventSlim (false);
+	readonly ManualResetEventSlim _winChange = new (false);
 
-	readonly Queue<InputResult?> _inputQueue = new Queue<InputResult?> ();
+	readonly Queue<InputResult?> _inputQueue = new ();
 
 	readonly ConsoleDriver _consoleDriver;
 	ConsoleKeyInfo [] _cki;
@@ -123,7 +125,7 @@ internal class NetEvents : IDisposable {
 #if PROCESS_REQUEST
 		bool _neededProcessRequest;
 #endif
-	public EscSeqRequests EscSeqRequests { get; } = new EscSeqRequests ();
+	public EscSeqRequests EscSeqRequests { get; } = new ();
 
 	public NetEvents (ConsoleDriver consoleDriver)
 	{
@@ -209,17 +211,19 @@ internal class NetEvents : IDisposable {
 					} catch (OperationCanceledException) {
 						return;
 					}
-					if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
-						|| (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) {
+					if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq
+					|| consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) {
 
 						if (_cki == null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) {
 							_cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)KeyCode.Esc, 0,
-							    false, false, false), _cki);
+								false, false, false), _cki);
 						}
 						_isEscSeq = true;
 						newConsoleKeyInfo = consoleKeyInfo;
 						_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
-						if (Console.KeyAvailable) continue;
+						if (Console.KeyAvailable) {
+							continue;
+						}
 						ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
 						_cki = null;
 						_isEscSeq = false;
@@ -270,10 +274,10 @@ internal class NetEvents : IDisposable {
 					buffWidth = _consoleDriver.Cols;
 				}
 				if (EnqueueWindowSizeEvent (
-				    Math.Max (Console.WindowHeight, 0),
-				    Math.Max (Console.WindowWidth, 0),
-				    buffHeight,
-				    buffWidth)) {
+					Math.Max (Console.WindowHeight, 0),
+					Math.Max (Console.WindowWidth, 0),
+					buffHeight,
+					buffWidth)) {
 
 					return;
 				}
@@ -306,9 +310,11 @@ internal class NetEvents : IDisposable {
 	/// <returns></returns>
 	bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
 	{
-		if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) return false;
-		var w = Math.Max (winWidth, 0);
-		var h = Math.Max (winHeight, 0);
+		if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) {
+			return false;
+		}
+		int w = Math.Max (winWidth, 0);
+		int h = Math.Max (winHeight, 0);
 		_inputQueue.Enqueue (new InputResult () {
 			EventType = EventType.WindowSize,
 			WindowSizeEvent = new WindowSizeEvent () {
@@ -323,10 +329,10 @@ internal class NetEvents : IDisposable {
 	{
 		// isMouse is true if it's CSI<, false otherwise
 		EscSeqUtils.DecodeEscSeq (EscSeqRequests, ref newConsoleKeyInfo, ref key, cki, ref mod,
-		    out var c1Control, out var code, out var values, out var terminating,
-		    out var isMouse, out var mouseFlags,
-		    out var pos, out var isReq,
-		    (f, p) => HandleMouseEvent (MapMouseFlags (f), p));
+			out string c1Control, out string code, out string [] values, out string terminating,
+			out bool isMouse, out var mouseFlags,
+			out var pos, out bool isReq,
+			(f, p) => HandleMouseEvent (MapMouseFlags (f), p));
 
 		if (isMouse) {
 			foreach (var mf in mouseFlags) {
@@ -343,7 +349,7 @@ internal class NetEvents : IDisposable {
 	MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
 	{
 		MouseButtonState mbs = default;
-		foreach (var flag in Enum.GetValues (mouseFlags.GetType ())) {
+		foreach (object flag in Enum.GetValues (mouseFlags.GetType ())) {
 			if (mouseFlags.HasFlag ((MouseFlags)flag)) {
 				switch (flag) {
 				case MouseFlags.Button1Pressed:
@@ -446,7 +452,7 @@ internal class NetEvents : IDisposable {
 		switch (terminating) {
 		// BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
 		case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator:
-			Point point = new Point {
+			var point = new Point {
 				X = int.Parse (values [1]) - 1,
 				Y = int.Parse (values [0]) - 1
 			};
@@ -469,10 +475,10 @@ internal class NetEvents : IDisposable {
 			switch (values [0]) {
 			case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue:
 				EnqueueWindowSizeEvent (
-				    Math.Max (int.Parse (values [1]), 0),
-				    Math.Max (int.Parse (values [2]), 0),
-				    Math.Max (int.Parse (values [1]), 0),
-				    Math.Max (int.Parse (values [2]), 0));
+					Math.Max (int.Parse (values [1]), 0),
+					Math.Max (int.Parse (values [2]), 0),
+					Math.Max (int.Parse (values [1]), 0),
+					Math.Max (int.Parse (values [2]), 0));
 				break;
 			default:
 				EnqueueRequestResponseEvent (c1Control, code, values, terminating);
@@ -489,7 +495,7 @@ internal class NetEvents : IDisposable {
 
 	void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
 	{
-		EventType eventType = EventType.RequestResponse;
+		var eventType = EventType.RequestResponse;
 		var requestRespEv = new RequestResponseEvent () {
 			ResultTuple = (c1Control, code, values, terminating)
 		};
@@ -501,9 +507,9 @@ internal class NetEvents : IDisposable {
 
 	void HandleMouseEvent (MouseButtonState buttonState, Point pos)
 	{
-		MouseEvent mouseEvent = new MouseEvent () {
+		var mouseEvent = new MouseEvent () {
 			Position = pos,
-			ButtonState = buttonState,
+			ButtonState = buttonState
 		};
 
 		_inputQueue.Enqueue (new InputResult () {
@@ -581,11 +587,40 @@ internal class NetEvents : IDisposable {
 		public WindowSizeEvent WindowSizeEvent;
 		public WindowPositionEvent WindowPositionEvent;
 		public RequestResponseEvent RequestResponseEvent;
+
+		public override readonly string ToString ()
+		{
+			return EventType switch {
+				EventType.Key => ToString (ConsoleKeyInfo),
+				EventType.Mouse => MouseEvent.ToString (),
+				//EventType.WindowSize => WindowSize.ToString (),
+				//EventType.RequestResponse => RequestResponse.ToString (),
+				_ => "Unknown event type: " + EventType
+			};
+		}
+
+		/// <summary>
+		/// Prints a ConsoleKeyInfoEx structure
+		/// </summary>
+		/// <param name="cki"></param>
+		/// <returns></returns>
+		public readonly string ToString (ConsoleKeyInfo cki)
+		{
+			var ke = new Key ((KeyCode)cki.KeyChar);
+			var sb = new StringBuilder ();
+			sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
+			sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
+			sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
+			sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
+			sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
+			var s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
+			return $"[ConsoleKeyInfo({s})]";
+		}
 	}
 
 	void HandleKeyboardEvent (ConsoleKeyInfo cki)
 	{
-		InputResult inputResult = new InputResult {
+		var inputResult = new InputResult {
 			EventType = EventType.Key,
 			ConsoleKeyInfo = cki
 		};
@@ -611,7 +646,7 @@ internal class NetEvents : IDisposable {
 	}
 }
 
-internal class NetDriver : ConsoleDriver {
+class NetDriver : ConsoleDriver {
 	const int COLOR_BLACK = 30;
 	const int COLOR_RED = 31;
 	const int COLOR_GREEN = 32;
@@ -631,9 +666,10 @@ internal class NetDriver : ConsoleDriver {
 
 	NetMainLoop _mainLoopDriver = null;
 
-	public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931);
+	public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || IsWinPlatform && Environment.OSVersion.Version.Build >= 14931;
 
 	public NetWinVTConsole NetWinConsole { get; private set; }
+
 	public bool IsWinPlatform { get; private set; }
 
 	internal override MainLoop Init ()
@@ -709,6 +745,21 @@ internal class NetDriver : ConsoleDriver {
 		}
 	}
 
+
+	#region Size and Position Handling
+	volatile bool _winSizeChanging;
+
+	void SetWindowPosition (int col, int row)
+	{
+		if (!RunningUnitTests) {
+			Top = Console.WindowTop;
+			Left = Console.WindowLeft;
+		} else {
+			Top = row;
+			Left = col;
+		}
+	}
+
 	public virtual void ResizeScreen ()
 	{
 		// Not supported on Unix.
@@ -727,7 +778,7 @@ internal class NetDriver : ConsoleDriver {
 					Console.SetBufferSize (Cols, Rows);
 				}
 #pragma warning restore CA1416
-			} catch (System.IO.IOException) {
+			} catch (IOException) {
 				Clip = new Rect (0, 0, Cols, Rows);
 			} catch (ArgumentOutOfRangeException) {
 				Clip = new Rect (0, 0, Cols, Rows);
@@ -739,6 +790,7 @@ internal class NetDriver : ConsoleDriver {
 
 		Clip = new Rect (0, 0, Cols, Rows);
 	}
+	#endregion
 
 	public override void Refresh ()
 	{
@@ -752,18 +804,18 @@ internal class NetDriver : ConsoleDriver {
 			return;
 		}
 
-		var top = 0;
-		var left = 0;
-		var rows = Rows;
-		var cols = Cols;
-		System.Text.StringBuilder output = new System.Text.StringBuilder ();
-		Attribute redrawAttr = new Attribute ();
-		var lastCol = -1;
+		int top = 0;
+		int left = 0;
+		int rows = Rows;
+		int cols = Cols;
+		var output = new StringBuilder ();
+		var redrawAttr = new Attribute ();
+		int lastCol = -1;
 
 		var savedVisibitity = _cachedCursorVisibility;
 		SetCursorVisibility (CursorVisibility.Invisible);
 
-		for (var row = top; row < rows; row++) {
+		for (int row = top; row < rows; row++) {
 			if (Console.WindowHeight < 1) {
 				return;
 			}
@@ -775,9 +827,9 @@ internal class NetDriver : ConsoleDriver {
 			}
 			_dirtyLines [row] = false;
 			output.Clear ();
-			for (var col = left; col < cols; col++) {
+			for (int col = left; col < cols; col++) {
 				lastCol = -1;
-				var outputWidth = 0;
+				int outputWidth = 0;
 				for (; col < cols; col++) {
 					if (!Contents [row, col].IsDirty) {
 						if (output.Length > 0) {
@@ -795,7 +847,7 @@ internal class NetDriver : ConsoleDriver {
 						lastCol = col;
 					}
 
-					Attribute attr = Contents [row, col].Attribute.Value;
+					var attr = Contents [row, col].Attribute.Value;
 					// Performance: Only send the escape sequence if the attribute has changed.
 					if (attr != redrawAttr) {
 						redrawAttr = attr;
@@ -823,7 +875,7 @@ internal class NetDriver : ConsoleDriver {
 						//	output.Append (combMark);
 						//}
 						// WriteToConsole (output, ref lastCol, row, ref outputWidth);
-					} else if ((rune.IsSurrogatePair () && rune.GetColumns () < 2)) {
+					} else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) {
 						WriteToConsole (output, ref lastCol, row, ref outputWidth);
 						SetCursorPosition (col - 1, row);
 					}
@@ -850,37 +902,33 @@ internal class NetDriver : ConsoleDriver {
 	}
 
 	#region Color Handling
-
 	// Cache the list of ConsoleColor values.
-	private static readonly HashSet<int> ConsoleColorValues = new HashSet<int> (
-	    Enum.GetValues (typeof (ConsoleColor)).OfType<ConsoleColor> ().Select (c => (int)c)
+	static readonly HashSet<int> ConsoleColorValues = new (
+		Enum.GetValues (typeof (ConsoleColor)).OfType<ConsoleColor> ().Select (c => (int)c)
 	);
 
 	// Dictionary for mapping ConsoleColor values to the values used by System.Net.Console.
-	private static Dictionary<ConsoleColor, int> colorMap = new Dictionary<ConsoleColor, int> {
-	{ ConsoleColor.Black, COLOR_BLACK },
-	{ ConsoleColor.DarkBlue, COLOR_BLUE },
-	{ ConsoleColor.DarkGreen, COLOR_GREEN },
-	{ ConsoleColor.DarkCyan, COLOR_CYAN },
-	{ ConsoleColor.DarkRed, COLOR_RED },
-	{ ConsoleColor.DarkMagenta, COLOR_MAGENTA },
-	{ ConsoleColor.DarkYellow, COLOR_YELLOW },
-	{ ConsoleColor.Gray, COLOR_WHITE },
-	{ ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
-	{ ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
-	{ ConsoleColor.Green, COLOR_BRIGHT_GREEN },
-	{ ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
-	{ ConsoleColor.Red, COLOR_BRIGHT_RED },
-	{ ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
-	{ ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
-	{ ConsoleColor.White, COLOR_BRIGHT_WHITE }
-    };
+	static Dictionary<ConsoleColor, int> colorMap = new () {
+		{ ConsoleColor.Black, COLOR_BLACK },
+		{ ConsoleColor.DarkBlue, COLOR_BLUE },
+		{ ConsoleColor.DarkGreen, COLOR_GREEN },
+		{ ConsoleColor.DarkCyan, COLOR_CYAN },
+		{ ConsoleColor.DarkRed, COLOR_RED },
+		{ ConsoleColor.DarkMagenta, COLOR_MAGENTA },
+		{ ConsoleColor.DarkYellow, COLOR_YELLOW },
+		{ ConsoleColor.Gray, COLOR_WHITE },
+		{ ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
+		{ ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
+		{ ConsoleColor.Green, COLOR_BRIGHT_GREEN },
+		{ ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
+		{ ConsoleColor.Red, COLOR_BRIGHT_RED },
+		{ ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
+		{ ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
+		{ ConsoleColor.White, COLOR_BRIGHT_WHITE }
+	};
 
 	// Map a ConsoleColor to a platform dependent value.
-	int MapColors (ConsoleColor color, bool isForeground = true)
-	{
-		return colorMap.TryGetValue (color, out var colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
-	}
+	int MapColors (ConsoleColor color, bool isForeground = true) => colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
 
 	///// <remarks>
 	///// In the NetDriver, colors are encoded as an int. 
@@ -938,7 +986,7 @@ internal class NetDriver : ConsoleDriver {
 	public override bool SetCursorVisibility (CursorVisibility visibility)
 	{
 		_cachedCursorVisibility = visibility;
-		var isVisible = RunningUnitTests ? visibility == CursorVisibility.Default : Console.CursorVisible = visibility == CursorVisibility.Default;
+		bool isVisible = RunningUnitTests ? visibility == CursorVisibility.Default : Console.CursorVisible = visibility == CursorVisibility.Default;
 		Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
 		return isVisible;
 	}
@@ -946,7 +994,7 @@ internal class NetDriver : ConsoleDriver {
 	public override bool EnsureCursorVisibility ()
 	{
 		if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) {
-			GetCursorVisibility (out CursorVisibility cursorVisibility);
+			GetCursorVisibility (out var cursorVisibility);
 			_cachedCursorVisibility = cursorVisibility;
 			SetCursorVisibility (CursorVisibility.Invisible);
 			return false;
@@ -957,22 +1005,7 @@ internal class NetDriver : ConsoleDriver {
 	}
 	#endregion
 
-	#region Size and Position Handling
-
-	void SetWindowPosition (int col, int row)
-	{
-		if (!RunningUnitTests) {
-			Top = Console.WindowTop;
-			Left = Console.WindowLeft;
-		} else {
-			Top = row;
-			Left = col;
-		}
-	}
-
-	#endregion
-
-
+	#region Mouse Handling
 	public void StartReportingMouseMoves ()
 	{
 		if (!RunningUnitTests) {
@@ -987,6 +1020,106 @@ internal class NetDriver : ConsoleDriver {
 		}
 	}
 
+	MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
+	{
+		//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
+
+		MouseFlags mouseFlag = 0;
+
+		if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button1Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1Released) != 0) {
+			mouseFlag |= MouseFlags.Button1Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button1Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button1DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button1TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button2Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2Released) != 0) {
+			mouseFlag |= MouseFlags.Button2Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button2Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button2DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button2TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button3Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3Released) != 0) {
+			mouseFlag |= MouseFlags.Button3Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button3Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button3DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button3TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0) {
+			mouseFlag |= MouseFlags.WheeledUp;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0) {
+			mouseFlag |= MouseFlags.WheeledDown;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0) {
+			mouseFlag |= MouseFlags.WheeledLeft;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0) {
+			mouseFlag |= MouseFlags.WheeledRight;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button4Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4Released) != 0) {
+			mouseFlag |= MouseFlags.Button4Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button4Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button4DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button4TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0) {
+			mouseFlag |= MouseFlags.ReportMousePosition;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonShift) != 0) {
+			mouseFlag |= MouseFlags.ButtonShift;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0) {
+			mouseFlag |= MouseFlags.ButtonCtrl;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0) {
+			mouseFlag |= MouseFlags.ButtonAlt;
+		}
+
+		return new MouseEvent () {
+			X = me.Position.X,
+			Y = me.Position.Y,
+			Flags = mouseFlag
+		};
+	}
+	#endregion Mouse Handling
+
+	#region Keyboard Handling
 	ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
 	{
 		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
@@ -994,11 +1127,11 @@ internal class NetDriver : ConsoleDriver {
 		}
 
 		var mod = consoleKeyInfo.Modifiers;
-		var shift = (mod & ConsoleModifiers.Shift) != 0;
-		var alt = (mod & ConsoleModifiers.Alt) != 0;
-		var control = (mod & ConsoleModifiers.Control) != 0;
+		bool shift = (mod & ConsoleModifiers.Shift) != 0;
+		bool alt = (mod & ConsoleModifiers.Alt) != 0;
+		bool control = (mod & ConsoleModifiers.Control) != 0;
 
-		var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
+		var cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 
 		return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
 	}
@@ -1006,37 +1139,11 @@ internal class NetDriver : ConsoleDriver {
 	KeyCode MapKey (ConsoleKeyInfo keyInfo)
 	{
 		switch (keyInfo.Key) {
-		case ConsoleKey.Escape:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
-		case ConsoleKey.Tab:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
-		case ConsoleKey.Home:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
-		case ConsoleKey.End:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
-		case ConsoleKey.LeftArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
-		case ConsoleKey.RightArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
-		case ConsoleKey.UpArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
-		case ConsoleKey.DownArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
-		case ConsoleKey.PageUp:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
-		case ConsoleKey.PageDown:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
-		case ConsoleKey.Enter:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
-		case ConsoleKey.Spacebar:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
-		case ConsoleKey.Backspace:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
-		case ConsoleKey.Delete:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
-		case ConsoleKey.Insert:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
-
+		case ConsoleKey.OemPeriod:
+		case ConsoleKey.OemComma:
+		case ConsoleKey.OemPlus:
+		case ConsoleKey.OemMinus:
+		case ConsoleKey.Packet:
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
 		case ConsoleKey.Oem3:
@@ -1046,99 +1153,78 @@ internal class NetDriver : ConsoleDriver {
 		case ConsoleKey.Oem7:
 		case ConsoleKey.Oem8:
 		case ConsoleKey.Oem102:
-			var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
-			if (ret.HasFlag (KeyCode.ShiftMask)) {
-				ret &= ~KeyCode.ShiftMask;
+			if (keyInfo.KeyChar == 0) {
+				// If the keyChar is 0, keyInfo.Key value is not a printable character. 
+
+				return KeyCode.Null;// MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
+			} else {
+				if (keyInfo.Modifiers != ConsoleModifiers.Shift) {
+					// If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
+					return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
+				}
+
+				// Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
+				// and passing on Shift would be redundant.
+				return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
 			}
-			return ret;
+			break;
 
-		case ConsoleKey.OemPeriod:
-		case ConsoleKey.OemComma:
-		case ConsoleKey.OemPlus:
-		case ConsoleKey.OemMinus:
-			return (KeyCode)((uint)keyInfo.KeyChar);
+
+			return (KeyCode)(uint)keyInfo.KeyChar;
 		}
 
 		var key = keyInfo.Key;
-		if (key is >= ConsoleKey.A and <= ConsoleKey.Z) {
-			var delta = key - ConsoleKey.A;
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-				}
+		// A..Z are special cased:
+		// - Alone, they represent lowercase a...z
+		// - With ShiftMask they are A..Z
+		// - If CapsLock is on the above is reversed.
+		// - If Alt and/or Ctrl are present, treat as upper case
+		if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) {
+			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
 			}
 
-			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) /*^ (keyInfoEx.CapsLock)*/)) {
-				if (keyInfo.KeyChar <= (uint)KeyCode.Z) {
-					return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask;
+			if (keyInfo.Modifiers == ConsoleModifiers.Shift) {
+				// If ShiftMask is on  add the ShiftMask
+				if (char.IsUpper (keyInfo.KeyChar)) {
+					return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask;
 				}
 			}
-			// This is buggy because is converting a lower case to a upper case and mustn't
-			//if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == KeyCode.Space) {
-			//	return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
-			//}
 			return (KeyCode)(uint)keyInfo.KeyChar;
 		}
-		if (key is >= ConsoleKey.D0 and <= ConsoleKey.D9) {
-			var delta = key - ConsoleKey.D0;
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
-				}
-			}
-			return (KeyCode)((uint)keyInfo.KeyChar);
-		}
-		if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) {
-			var delta = key - ConsoleKey.F1;
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta));
-			}
 
-			return (KeyCode)((uint)KeyCode.F1 + delta);
+		// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
+		if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) {
+			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.Key));
 		}
 
-		// Is it a key between a..z?
-		if ((char)keyInfo.KeyChar is >= 'a' and <= 'z') {
-			// 'a' should be Key.A
-			return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
+		// Handle control keys (e.g. CursorUp)
+		if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) {
+			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
 		}
 
-		// Is it a key between A..Z?
-		if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) {
-			// It's Key.A...Z.  Make it Key.A | Key.ShiftMask
-			return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space | KeyCode.ShiftMask;
-		}
 
 		return (KeyCode)(uint)keyInfo.KeyChar;
 	}
+	#endregion Keyboard Handling
 
-	volatile bool _winSizeChanging;
-
-	void ProcessInput (NetEvents.InputResult inputEvent)
+	void ProcessInput (InputResult inputEvent)
 	{
 		switch (inputEvent.EventType) {
 		case NetEvents.EventType.Key:
-			ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
-			if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-				consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-			}
+			var consoleKeyInfo = inputEvent.ConsoleKeyInfo;
+			//if (consoleKeyInfo.Key == ConsoleKey.Packet) {
+			//	consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+			//}
+
+			//Debug.WriteLine ($"event: {inputEvent}");
+
 			var map = MapKey (consoleKeyInfo);
 
+			if (map == KeyCode.Null) {
+				break;
+			}
+
 			OnKeyDown (new Key (map));
 			OnKeyUp (new Key (map));
 			break;
@@ -1150,16 +1236,14 @@ internal class NetDriver : ConsoleDriver {
 			Top = 0;
 			Left = 0;
 			Cols = inputEvent.WindowSizeEvent.Size.Width;
-			Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); ;
+			Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0);
+			;
 			ResizeScreen ();
 			ClearContents ();
 			_winSizeChanging = false;
 			OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
 			break;
 		case NetEvents.EventType.RequestResponse:
-			// BUGBUG: What is this for? It does not seem to be used anywhere. 
-			// It is also not clear what it does. View.Data is documented as "This property is not used internally"
-			Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple;
 			break;
 		case NetEvents.EventType.WindowPosition:
 			break;
@@ -1168,107 +1252,9 @@ internal class NetDriver : ConsoleDriver {
 		}
 	}
 
-	MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
-	{
-		//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
-
-		MouseFlags mouseFlag = 0;
-
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button1Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Released) != 0) {
-			mouseFlag |= MouseFlags.Button1Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button1Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button1DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button1TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button2Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Released) != 0) {
-			mouseFlag |= MouseFlags.Button2Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button2Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button2DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button2TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button3Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Released) != 0) {
-			mouseFlag |= MouseFlags.Button3Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button3Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button3DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button3TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledUp) != 0) {
-			mouseFlag |= MouseFlags.WheeledUp;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledDown) != 0) {
-			mouseFlag |= MouseFlags.WheeledDown;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledLeft) != 0) {
-			mouseFlag |= MouseFlags.WheeledLeft;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledRight) != 0) {
-			mouseFlag |= MouseFlags.WheeledRight;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button4Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Released) != 0) {
-			mouseFlag |= MouseFlags.Button4Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button4Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button4DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button4TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ReportMousePosition) != 0) {
-			mouseFlag |= MouseFlags.ReportMousePosition;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonShift) != 0) {
-			mouseFlag |= MouseFlags.ButtonShift;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonCtrl) != 0) {
-			mouseFlag |= MouseFlags.ButtonCtrl;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonAlt) != 0) {
-			mouseFlag |= MouseFlags.ButtonAlt;
-		}
-
-		return new MouseEvent () {
-			X = me.Position.X,
-			Y = me.Position.Y,
-			Flags = mouseFlag
-		};
-	}
-
 	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
 	{
-		NetEvents.InputResult input = new NetEvents.InputResult {
+		var input = new InputResult {
 			EventType = NetEvents.EventType.Key,
 			ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control)
 		};
@@ -1280,12 +1266,8 @@ internal class NetDriver : ConsoleDriver {
 
 
 	#region Not Implemented
-	public override void Suspend ()
-	{
-		throw new NotImplementedException ();
-	}
+	public override void Suspend () => throw new NotImplementedException ();
 	#endregion
-
 }
 
 /// <summary>
@@ -1296,19 +1278,19 @@ internal class NetDriver : ConsoleDriver {
 /// <remarks>
 /// This implementation is used for NetDriver.
 /// </remarks>
-internal class NetMainLoop : IMainLoopDriver {
-	readonly ManualResetEventSlim _eventReady = new ManualResetEventSlim (false);
-	readonly ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false);
-	readonly Queue<NetEvents.InputResult?> _resultQueue = new Queue<NetEvents.InputResult?> ();
+class NetMainLoop : IMainLoopDriver {
+	readonly ManualResetEventSlim _eventReady = new (false);
+	readonly ManualResetEventSlim _waitForProbe = new (false);
+	readonly Queue<InputResult?> _resultQueue = new ();
 	MainLoop _mainLoop;
-	CancellationTokenSource _eventReadyTokenSource = new CancellationTokenSource ();
-	readonly CancellationTokenSource _inputHandlerTokenSource = new CancellationTokenSource ();
+	CancellationTokenSource _eventReadyTokenSource = new ();
+	readonly CancellationTokenSource _inputHandlerTokenSource = new ();
 	internal NetEvents _netEvents;
 
 	/// <summary>
 	/// Invoked when a Key is pressed.
 	/// </summary>
-	internal Action<NetEvents.InputResult> ProcessInput;
+	internal Action<InputResult> ProcessInput;
 
 	/// <summary>
 	/// Initializes the class with the console driver.
@@ -1367,16 +1349,13 @@ internal class NetMainLoop : IMainLoopDriver {
 		Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
 	}
 
-	void IMainLoopDriver.Wakeup ()
-	{
-		_eventReady.Set ();
-	}
+	void IMainLoopDriver.Wakeup () => _eventReady.Set ();
 
 	bool IMainLoopDriver.EventsPending ()
 	{
 		_waitForProbe.Set ();
 
-		if (_mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout)) {
+		if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) {
 			return true;
 		}
 

+ 157 - 167
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -24,6 +24,7 @@ using System.Threading.Tasks;
 using System.Diagnostics;
 using Terminal.Gui.ConsoleDrivers;
 using static Unix.Terminal.Delegates;
+using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui;
 
@@ -371,7 +372,7 @@ internal class WindowsConsole {
 		[FieldOffset (4), MarshalAs (UnmanagedType.U2)]
 		public ushort wRepeatCount;
 		[FieldOffset (6), MarshalAs (UnmanagedType.U2)]
-		public ushort wVirtualKeyCode;
+		public VK wVirtualKeyCode;
 		[FieldOffset (8), MarshalAs (UnmanagedType.U2)]
 		public ushort wVirtualScanCode;
 		[FieldOffset (10)]
@@ -901,66 +902,31 @@ internal class WindowsDriver : ConsoleDriver {
 	}
 #endif
 
+
 	KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
 	{
 		var keyInfo = keyInfoEx.ConsoleKeyInfo;
 		switch (keyInfo.Key) {
-		case ConsoleKey.Escape:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
-		case ConsoleKey.Tab:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
-		case ConsoleKey.Clear:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear);
-		case ConsoleKey.Home:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
-		case ConsoleKey.End:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
-		case ConsoleKey.LeftArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
-		case ConsoleKey.RightArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
-		case ConsoleKey.UpArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
-		case ConsoleKey.DownArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
-		case ConsoleKey.PageUp:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
-		case ConsoleKey.PageDown:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
-		case ConsoleKey.Enter:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
-		case ConsoleKey.Spacebar:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
-		case ConsoleKey.Backspace:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
-		case ConsoleKey.Delete:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
-		case ConsoleKey.Insert:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
-		case ConsoleKey.PrintScreen:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen);
-
-		//case ConsoleKey.NumPad0:
-		//	return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar;
-		//case ConsoleKey.NumPad1:
-		//	return keyInfoEx.NumLock ? Key.D1 : Key.End;
-		//case ConsoleKey.NumPad2:
-		//	return keyInfoEx.NumLock ? Key.D2 : Key.CursorDown;
-		//case ConsoleKey.NumPad3:
-		//	return keyInfoEx.NumLock ? Key.D3 : Key.PageDown;
-		//case ConsoleKey.NumPad4:
-		//	return keyInfoEx.NumLock ? Key.D4 : Key.CursorLeft;
-		//case ConsoleKey.NumPad5:
-		//	return keyInfoEx.NumLock ? Key.D5 : (Key)((uint)keyInfo.KeyChar);
-		//case ConsoleKey.NumPad6:
-		//	return keyInfoEx.NumLock ? Key.D6 : Key.CursorRight;
-		//case ConsoleKey.NumPad7:
-		//	return keyInfoEx.NumLock ? Key.D7 : Key.Home;
-		//case ConsoleKey.NumPad8:
-		//	return keyInfoEx.NumLock ? Key.D8 : Key.CursorUp;
-		//case ConsoleKey.NumPad9:
-		//	return keyInfoEx.NumLock ? Key.D9 : Key.PageUp;
-
+		case ConsoleKey.D0:
+		case ConsoleKey.D1:
+		case ConsoleKey.D2:
+		case ConsoleKey.D3:
+		case ConsoleKey.D4:
+		case ConsoleKey.D5:
+		case ConsoleKey.D6:
+		case ConsoleKey.D7:
+		case ConsoleKey.D8:
+		case ConsoleKey.D9:
+		case ConsoleKey.NumPad0:
+		case ConsoleKey.NumPad1:
+		case ConsoleKey.NumPad2:
+		case ConsoleKey.NumPad3:
+		case ConsoleKey.NumPad4:
+		case ConsoleKey.NumPad5:
+		case ConsoleKey.NumPad6:
+		case ConsoleKey.NumPad7:
+		case ConsoleKey.NumPad8:
+		case ConsoleKey.NumPad9:
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
 		case ConsoleKey.Oem3:
@@ -970,148 +936,168 @@ internal class WindowsDriver : ConsoleDriver {
 		case ConsoleKey.Oem7:
 		case ConsoleKey.Oem8:
 		case ConsoleKey.Oem102:
-			var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
-			if (ret.HasFlag (KeyCode.ShiftMask)) {
-				ret &= ~KeyCode.ShiftMask;
-			}
-			return ret;
-
+		case ConsoleKey.Multiply:
+		case ConsoleKey.Add:
+		case ConsoleKey.Separator:
+		case ConsoleKey.Subtract:
+		case ConsoleKey.Decimal:
+		case ConsoleKey.Divide:
 		case ConsoleKey.OemPeriod:
 		case ConsoleKey.OemComma:
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemMinus:
-			return (KeyCode)((uint)keyInfo.KeyChar);
-		}
-
-		var key = keyInfo.Key;
-
-		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
-			var delta = key - ConsoleKey.A;
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-				}
+			// These virtual key codes are mapped differently depending on the keyboard layout in use.
+			// We use the Win32 API to map them to the correct character.
+			var mapResult = MapVKtoChar ((VK)keyInfo.Key);
+			if (mapResult == 0) {
+				// There is no mapping - this should not happen
+				Debug.Assert (mapResult != 0, $@"Unable to map the virtual key code {keyInfo.Key}.");
+				return KeyCode.Null;
 			}
 
-			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock))) {
-				if (keyInfo.KeyChar <= (uint)KeyCode.Z) {
-					return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask;
+			// An un-shifted character value is in the low order word of the return value.
+			var mappedChar = (char)(mapResult & 0x0000FFFF);
+
+			if (keyInfo.KeyChar == 0) {
+				// If the keyChar is 0, keyInfo.Key value is not a printable character. 
+
+				// Dead keys (diacritics) are indicated by setting the top bit of the return value. 
+				if ((mapResult & 0x80000000) != 0) {
+					// Dead key (e.g. Oem2 '~'/'^' on POR keyboard)
+					// Option 1: Throw it out. 
+					//    - Apps will never see the dead keys
+					//    - If user presses a key that can be combined with the dead key ('a'), the right thing happens (app will see 'ã').
+					//      - NOTE: With Dead Keys, KeyDown != KeyUp. The KeyUp event will have just the base char ('a').
+					//    - If user presses dead key again, the right thing happens (app will see `~~`)
+					//    - This is what Notepad etc... appear to do
+					// Option 2: Expand the API to indicate the KeyCode is a dead key
+					//    - Enables apps to do their own dead key processing
+					//    - Adds complexity; no dev has asked for this (yet).
+					// We choose Option 1 for now.
+					return KeyCode.Null;
+
+					// Note: Ctrl-Deadkey (like Oem3 '`'/'~` on ENG) can't be supported.
+					// Sadly, the charVal is just the deadkey and subsequent key events do not contain
+					// any info that the previous event was a deadkey.
+					// Note WT does not support Ctrl-Deadkey either.
 				}
-			}
 
-			if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == 0) {
-				return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
-			}
+				if (keyInfo.Modifiers != 0) {
+					// These Oem keys have well defined chars. We ensure the representative char is used.
+					// If we don't do this, then on some keyboard layouts the wrong char is 
+					// returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important
+					// for key persistence ("Ctrl++" vs. "Ctrl+=").
+					mappedChar = keyInfo.Key switch {
+						ConsoleKey.OemPeriod => '.',
+						ConsoleKey.OemComma => ',',
+						ConsoleKey.OemPlus => '+',
+						ConsoleKey.OemMinus => '-',
+						_ => mappedChar
+					};
+				}
 
-			if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) != 0) {
-				if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) == (KeyCode)keyInfo.Key) {
-					return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
+				// Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down
+				// we should keep it
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar);
+			} else {
+				// KeyChar is printable
+				if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) && keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+					// AltGr support - AltGr is equivalent to Ctrl+Alt - the correct char is in KeyChar
+					return (KeyCode)keyInfo.KeyChar;
 				}
-				return (KeyCode)((uint)keyInfo.KeyChar);
-			}
 
-			return (KeyCode)(uint)keyInfo.KeyChar;
+				if (keyInfo.Modifiers != ConsoleModifiers.Shift) {
+					// If Shift wasn't down we don't need to do anything but return the mappedChar
+					return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(mappedChar));
+				}
 
+				// Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
+				// and passing on Shift would be redundant.
+				return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
+			}
 		}
 
-		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
-			var delta = key - ConsoleKey.D0;
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta));
+		// A..Z are special cased:
+		// - Alone, they represent lowercase a...z
+		// - With ShiftMask they are A..Z
+		// - If CapsLock is on the above is reversed.
+		// - If Alt and/or Ctrl are present, treat as upper case
+		if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) {
+			if (keyInfo.KeyChar == 0) {
+				// KeyChar is not printable - possibly an AltGr key?
+				// AltGr support - AltGr is equivalent to Ctrl+Alt
+				if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) && keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+					return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
+				}
 			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
+
+			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
 			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
+
+			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock))) {
+				// If (ShiftMask is on and CapsLock is off) or (ShiftMask is off and CapsLock is on) add the ShiftMask
+				if (char.IsUpper (keyInfo.KeyChar)) {
+					return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask;
 				}
 			}
-			return (KeyCode)((uint)keyInfo.KeyChar);
+			return (KeyCode)(uint)keyInfo.KeyChar;
 		}
 
-		if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
-			var delta = key - ConsoleKey.F1;
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta));
+		// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
+		if (Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) {
+			// If the key is JUST a modifier, return it as just that key
+			if (keyInfo.Key == (ConsoleKey)VK.SHIFT) { // Shift 16
+				return KeyCode.ShiftMask;
 			}
 
-			return (KeyCode)((uint)KeyCode.F1 + delta);
-		}
+			if (keyInfo.Key == (ConsoleKey)VK.CONTROL) { // Ctrl 17
+				return KeyCode.CtrlMask;
+			}
 
-		// If the key is JUST a modifier, return it as that key
-		if (key == (ConsoleKey)16) { // Shift
-			return KeyCode.ShiftKey;
-		}
+			if (keyInfo.Key == (ConsoleKey)VK.MENU) { // Alt 18
+				return KeyCode.AltMask;
+			}
 
-		if (key == (ConsoleKey)17) { // Ctrl
-			return KeyCode.CtrlKey;
+			if (keyInfo.KeyChar == 0) {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
+			} else {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)(keyInfo.KeyChar));
+			}
 		}
 
-		if (key == (ConsoleKey)18) { // Alt
-			return KeyCode.AltKey;
+		// Handle control keys (e.g. CursorUp)
+		if (Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) {
+			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
 		}
 
-		return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+		return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
 	}
 
-	bool _altDown = false;
-
 	internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
 	{
 		switch (inputEvent.EventType) {
 		case WindowsConsole.EventType.Key:
-			var fromPacketKey = inputEvent.KeyEvent.wVirtualKeyCode == (uint)ConsoleKey.Packet;
-			if (fromPacketKey) {
+			if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet) {
+				// Used to pass Unicode characters as if they were keystrokes.
+				// The VK_PACKET key is the low word of a 32-bit
+				// Virtual Key value used for non-keyboard input methods.
 				inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
 			}
 			var keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent);
-			//Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
-
+			//Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
 
 			var map = MapKey (keyInfo);
 
+			if (map == KeyCode.Null) {
+				break;
+			}
+
 			if (inputEvent.KeyEvent.bKeyDown) {
-				_altDown = keyInfo.ConsoleKeyInfo.Modifiers == ConsoleModifiers.Alt;
-				// Avoid sending repeat keydowns
+				// Avoid sending repeat key down events
 				OnKeyDown (new Key (map));
 			} else {
-				var keyPressedEventArgs = new Key (map);
-
-				// PROTOTYPE: This logic enables `Alt` key presses (down, up, pressed).
-				// However, if while the 'Alt' key is down, if another key is pressed and
-				// released, there will be a keypressed event for that and the
-				// keypressed event for just `Alt` will be suppressed. 
-				// This allows MenuBar to have `Alt` as a keybinding
-				if (map != KeyCode.AltMask) {
-					if (keyInfo.ConsoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
-						if (_altDown) {
-							_altDown = false;
-							OnKeyUp (new Key (map));
-						}
-
-					}
-					_altDown = false;
-					// KeyUp of an Alt-key press. 
-					OnKeyUp (keyPressedEventArgs);
-				} else {
-					OnKeyUp (keyPressedEventArgs);
-					if (_altDown) {
-						_altDown = false;
-					}
-				}
+				OnKeyUp (new Key (map));
 			}
 
 			break;
@@ -1422,7 +1408,7 @@ internal class WindowsDriver : ConsoleDriver {
 
 	public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
 	{
-		if (keyEvent.wVirtualKeyCode != (uint)ConsoleKey.Packet) {
+		if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet) {
 			return keyEvent;
 		}
 
@@ -1438,14 +1424,17 @@ internal class WindowsDriver : ConsoleDriver {
 		    keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) {
 			mod |= ConsoleModifiers.Control;
 		}
-		var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyEvent.UnicodeChar, mod, out uint scanCode);
+		var cKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode,
+			mod.HasFlag (ConsoleModifiers.Shift), mod.HasFlag (ConsoleModifiers.Alt), mod.HasFlag (ConsoleModifiers.Control));
+		cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
+		var scanCode = GetScanCodeFromConsoleKeyInfo (cKeyInfo);
 
 		return new WindowsConsole.KeyEventRecord {
 			UnicodeChar = cKeyInfo.KeyChar,
 			bKeyDown = keyEvent.bKeyDown,
 			dwControlKeyState = keyEvent.dwControlKeyState,
 			wRepeatCount = keyEvent.wRepeatCount,
-			wVirtualKeyCode = (ushort)cKeyInfo.Key,
+			wVirtualKeyCode = (VK)cKeyInfo.Key,
 			wVirtualScanCode = (ushort)scanCode
 		};
 	}
@@ -1591,19 +1580,19 @@ internal class WindowsDriver : ConsoleDriver {
 		if (shift) {
 			controlKey |= WindowsConsole.ControlKeyState.ShiftPressed;
 			keyEvent.UnicodeChar = '\0';
-			keyEvent.wVirtualKeyCode = 16;
+			keyEvent.wVirtualKeyCode = VK.SHIFT;
 		}
 		if (alt) {
 			controlKey |= WindowsConsole.ControlKeyState.LeftAltPressed;
 			controlKey |= WindowsConsole.ControlKeyState.RightAltPressed;
 			keyEvent.UnicodeChar = '\0';
-			keyEvent.wVirtualKeyCode = 18;
+			keyEvent.wVirtualKeyCode = VK.MENU;
 		}
 		if (control) {
 			controlKey |= WindowsConsole.ControlKeyState.LeftControlPressed;
 			controlKey |= WindowsConsole.ControlKeyState.RightControlPressed;
 			keyEvent.UnicodeChar = '\0';
-			keyEvent.wVirtualKeyCode = 17;
+			keyEvent.wVirtualKeyCode = VK.CONTROL;
 		}
 		keyEvent.dwControlKeyState = controlKey;
 
@@ -1614,11 +1603,12 @@ internal class WindowsDriver : ConsoleDriver {
 		}
 
 		keyEvent.UnicodeChar = keyChar;
-		if ((uint)key < 255) {
-			keyEvent.wVirtualKeyCode = (ushort)key;
-		} else {
-			keyEvent.wVirtualKeyCode = '\0';
-		}
+		//if ((uint)key < 255) {
+		//	keyEvent.wVirtualKeyCode = (ushort)key;
+		//} else {
+		//	keyEvent.wVirtualKeyCode = '\0';
+		//}
+		keyEvent.wVirtualKeyCode = (VK)key;
 
 		input.KeyEvent = keyEvent;
 

+ 64 - 60
Terminal.Gui/Input/Key.cs

@@ -132,7 +132,8 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// The encoded key value. 
 	/// </summary>
 	/// <para>
-	/// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
+	/// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and
+	/// 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
 	/// are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII
 	/// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
 	/// </para>
@@ -147,24 +148,36 @@ public class Key : EventArgs, IEquatable<Key> {
 	public KeyBindingScope Scope { get; set; } = KeyBindingScope.Focused;
 
 	/// <summary>
-	/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
+	/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers. Useful
+	/// for determining if a key represents is a printable character.
 	/// </summary>
 	/// <remarks>
 	/// <para>
-	/// If the key is a letter (a-z or A-Z), the Rune be the upper or lower case letter depending on whether
-	/// the shift key was pressed.
+	/// Keys with Ctrl or Alt modifiers will return <see langword="default"/>. 
+	/// </para>
+	/// <para>
+	/// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
+	/// <see cref="KeyCode.ShiftMask"/> is set.
+	/// </para>
+	/// <para>
 	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be <see langword="default"/>.
 	/// </para>
 	/// </remarks>
 	public Rune AsRune => ToRune (KeyCode);
 
 	/// <summary>
-	/// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>.
+	/// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>. Useful
+	/// for determining if a key represents is a printable character.
 	/// </summary>
 	/// <remarks>
 	/// <para>
-	/// If the key is a letter (a-z or A-Z), the Rune be the upper or lower case letter depending on whether
-	/// the shift key was pressed.
+	/// Keys with Ctrl or Alt modifiers will return <see langword="default"/>. 
+	/// </para>
+	/// <para>
+	/// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
+	/// <see cref="KeyCode.ShiftMask"/> is set.
+	/// </para>
+	/// <para>
 	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be <see langword="default"/>.
 	/// </para>
 	/// </remarks>
@@ -176,23 +189,26 @@ public class Key : EventArgs, IEquatable<Key> {
 			return default;
 		}
 
-		// Extract the base key (removing modifier flags)
-		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
+		// Extract the base key code
+		var baseKey = key;
+		if (baseKey.HasFlag(KeyCode.ShiftMask)) {
+			baseKey &= ~KeyCode.ShiftMask;
+		}
 
 		switch (baseKey) {
 		case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
-			return new Rune ((char)(baseKey + 32));
-		case >= KeyCode.A and <= KeyCode.Z:
-			return new Rune ((char)baseKey);
+			return new Rune ((uint)(baseKey + 32));
+		case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
+			return new Rune ((uint)baseKey);
 		case > KeyCode.Null and < KeyCode.A:
-			return new Rune ((char)baseKey);
+			return new Rune ((uint)baseKey);
 		}
 
 		if (Enum.IsDefined (typeof (KeyCode), baseKey)) {
 			return default;
 		}
 
-		return new Rune ((char)baseKey);
+		return new Rune ((uint)baseKey);
 	}
 
 	/// <summary>
@@ -214,7 +230,9 @@ public class Key : EventArgs, IEquatable<Key> {
 	public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0;
 
 	/// <summary>
-	/// Gets a value indicating whether the KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key.
+	/// Gets a value indicating whether the key represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
+	/// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which are
+	/// special cased.
 	/// </summary>
 	/// <remarks>
 	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
@@ -224,7 +242,9 @@ public class Key : EventArgs, IEquatable<Key> {
 	public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode);
 
 	/// <summary>
-	/// Tests if a KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key.
+	/// Tests if a KeyCode represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
+	/// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which are
+	/// special cased.
 	/// </summary>
 	/// <remarks>
 	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
@@ -303,7 +323,8 @@ public class Key : EventArgs, IEquatable<Key> {
 
 	#region Operators
 	/// <summary>
-	/// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy. 
+	/// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy because properties
+	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
 	/// </summary>
 	/// <remarks>
 	/// Uses <see cref="AsRune"/>.
@@ -311,14 +332,17 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// <param name="kea"></param>
 	public static explicit operator Rune (Key kea) => kea.AsRune;
 
+	// BUGBUG: (Tig) I do not think this cast operator is really needed. 
 	/// <summary>
-	/// Explicitly cast <see cref="Key"/> to a <see langword="char"/>. The conversion is lossy. 
+	/// Explicitly cast <see cref="Key"/> to a <see langword="uint"/>. The conversion is lossy because properties
+	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
 	/// </summary>
 	/// <param name="kea"></param>
-	public static explicit operator char (Key kea) => (char)kea.AsRune.Value;
+	public static explicit operator uint (Key kea) => (uint)kea.KeyCode;
 
 	/// <summary>
-	/// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy. 
+	/// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy because properties
+	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
 	/// </summary>
 	/// <param name="key"></param>
 	public static explicit operator KeyCode (Key key) => key.KeyCode;
@@ -365,6 +389,7 @@ public class Key : EventArgs, IEquatable<Key> {
 	public override int GetHashCode () => (int)KeyCode;
 
 	/// <summary>
+	/// Compares two <see cref="Key"/>s for equality.
 	/// </summary>
 	/// <param name="a"></param>
 	/// <param name="b"></param>
@@ -372,6 +397,7 @@ public class Key : EventArgs, IEquatable<Key> {
 	public static bool operator == (Key a, Key b) => a?.KeyCode == b?.KeyCode;
 
 	/// <summary>
+	/// Compares two <see cref="Key"/>s for not equality.
 	/// </summary>
 	/// <param name="a"></param>
 	/// <param name="b"></param>
@@ -427,15 +453,15 @@ public class Key : EventArgs, IEquatable<Key> {
 		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
 
 		if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z) {
-			return ((char)(key + 32)).ToString ();
+			return ((Rune)(uint)(key + 32)).ToString ();
 		}
 
-		if (key is >= KeyCode.Space and < KeyCode.A) {
-			return ((char)key).ToString ();
+		if (key is > KeyCode.Space and < KeyCode.A) {
+			return ((Rune)(uint)key).ToString ();
 		}
 
 		string keyName = Enum.GetName (typeof (KeyCode), key);
-		return !string.IsNullOrEmpty (keyName) ? keyName : ((char)key).ToString ();
+		return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)key).ToString ();
 	}
 
 
@@ -454,7 +480,7 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// <returns>The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned.</returns>
 	public static string ToString (KeyCode key, Rune separator)
 	{
-		if (key is KeyCode.Null || (key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask) == 0) {
+		if (key is KeyCode.Null) {
 			// Same as Key.IsValid
 			return @"Null";
 		}
@@ -489,21 +515,19 @@ public class Key : EventArgs, IEquatable<Key> {
 			}
 		}
 
-		string result = sb.ToString ();
-		result = TrimEndRune (result, separator);
-		return result;
+		return TrimEndSeparator (sb.ToString (), separator);
 	}
 
-	static string TrimEndRune (string input, Rune runeToTrim)
+	static string TrimEndSeparator (string input, Rune separator)
 	{
-		// Convert the Rune to a string (which may be one or two chars)
-		string runeString = runeToTrim.ToString ();
+		// Trim the trailing separator (+). Unless there are two separators at the end.
+		// "+" (don't trim)
+		// "Ctrl+" (trim)
+		// "Ctrl++" (trim)
 
-		if (input.EndsWith (runeString)) {
-			// Remove the rune from the end of the string
-			return input.Substring (0, input.Length - runeString.Length);
+		if (input.Length > 1 && new Rune(input [^1]) == separator && new Rune(input [^2]) != separator) {
+			return input [..^1];
 		}
-
 		return input;
 	}
 
@@ -543,13 +567,13 @@ public class Key : EventArgs, IEquatable<Key> {
 		if (parts.Length == 1) {
 			switch (parts [0]) {
 			case "Ctrl":
-				key = new Key (KeyCode.CtrlKey);
+				key = new Key (KeyCode.CtrlMask);
 				return true;
 			case "Alt":
-				key = new Key (KeyCode.AltKey);
+				key = new Key (KeyCode.AltMask);
 				return true;
 			case "Shift":
-				key = new Key (KeyCode.ShiftKey);
+				key = new Key (KeyCode.ShiftMask);
 				return true;
 			}
 		}
@@ -655,26 +679,6 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// </summary>
 	public static Key Clear => new (KeyCode.Clear);
 
-	/// <summary>
-	/// The <see cref="Key"/> object for the Shift key.
-	/// </summary>
-	public static Key Shift => new (KeyCode.ShiftKey);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Ctrl key.
-	/// </summary>
-	public static Key Ctrl => new (KeyCode.CtrlKey);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Alt key.
-	/// </summary>
-	public static Key Alt => new (KeyCode.AltKey);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the CapsLock key.
-	/// </summary>
-	public static Key CapsLock => new (KeyCode.CapsLock);
-
 	/// <summary>
 	/// The <see cref="Key"/> object for the Escape key.
 	/// </summary>
@@ -913,12 +917,12 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// <summary>
 	/// The <see cref="Key"/> object for Insert Character key.
 	/// </summary>
-	public static Key InsertChar => new (KeyCode.InsertChar);
+	public static Key InsertChar => new (KeyCode.Insert);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Delete Character key.
 	/// </summary>
-	public static Key DeleteChar => new (KeyCode.DeleteChar);
+	public static Key DeleteChar => new (KeyCode.Delete);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Print Screen key.

+ 1 - 0
Terminal.Gui/Terminal.Gui.csproj

@@ -113,5 +113,6 @@
     <EnableSourceLink>true</EnableSourceLink>
     <!--<DebugType>Embedded</DebugType>-->
     <Authors>Miguel de Icaza, Tig Kindel (@tig), @BDisp</Authors>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 </Project>

+ 179 - 182
Terminal.Gui/Text/CollectionNavigatorBase.cs

@@ -1,217 +1,214 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
+
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. 
+/// The <see cref="SearchString"/> is used to find the next item in the collection that matches the search string
+/// when <see cref="GetNextMatchingItem(int, char)"/> is called.
+/// <para>
+/// If the user types keystrokes that can't be found in the collection, 
+/// the search string is cleared and the next item is found that starts with the last keystroke.
+/// </para>
+/// <para>
+/// If the user pauses keystrokes for a short time (see <see cref="TypingDelay"/>), the search string is cleared.
+/// </para>
+/// </summary>
+public abstract class CollectionNavigatorBase {
+	DateTime _lastKeystroke = DateTime.Now;
 
-namespace Terminal.Gui {
 	/// <summary>
-	/// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. 
-	/// The <see cref="SearchString"/> is used to find the next item in the collection that matches the search string
-	/// when <see cref="GetNextMatchingItem(int, char)"/> is called.
-	/// <para>
-	/// If the user types keystrokes that can't be found in the collection, 
-	/// the search string is cleared and the next item is found that starts with the last keystroke.
-	/// </para>
-	/// <para>
-	/// If the user pauses keystrokes for a short time (see <see cref="TypingDelay"/>), the search string is cleared.
-	/// </para>
+	/// Gets or sets the number of milliseconds to delay before clearing the search string. The delay is
+	/// reset on each call to <see cref="GetNextMatchingItem(int, char)"/>. The default is 500ms.
 	/// </summary>
-	public abstract class CollectionNavigatorBase {
-
-		DateTime lastKeystroke = DateTime.Now;
-		/// <summary>
-		/// Gets or sets the number of milliseconds to delay before clearing the search string. The delay is
-		/// reset on each call to <see cref="GetNextMatchingItem(int, char)"/>. The default is 500ms.
-		/// </summary>
-		public int TypingDelay { get; set; } = 500;
-
-		/// <summary>
-		/// The compararer function to use when searching the collection.
-		/// </summary>
-		public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
-
-		/// <summary>
-		/// This event is invoked when <see cref="SearchString"/>  changes. Useful for debugging.
-		/// </summary>
-		public event EventHandler<KeystrokeNavigatorEventArgs> SearchStringChanged;
-
-		private string _searchString = "";
-		/// <summary>
-		/// Gets the current search string. This includes the set of keystrokes that have been pressed
-		/// since the last unsuccessful match or after <see cref="TypingDelay"/>) milliseconds. Useful for debugging.
-		/// </summary>
-		public string SearchString {
-			get => _searchString;
-			private set {
-				_searchString = value;
-				OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value));
-			}
-		}
+	public int TypingDelay { get; set; } = 500;
 
-		/// <summary>
-		/// Invoked when the <see cref="SearchString"/> changes. Useful for debugging. Invokes the <see cref="SearchStringChanged"/> event.
-		/// </summary>
-		/// <param name="e"></param>
-		public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e)
-		{
-			SearchStringChanged?.Invoke (this, e);
-		}
+	/// <summary>
+	/// The comparer function to use when searching the collection.
+	/// </summary>
+	public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
 
-		/// <summary>
-		/// Gets the index of the next item in the collection that matches the current <see cref="SearchString"/> plus the provided character (typically
-		/// from a key press).
-		/// </summary>
-		/// <param name="currentIndex">The index in the collection to start the search from.</param>
-		/// <param name="keyStruck">The character of the key the user pressed.</param>
-		/// <returns>The index of the item that matches what the user has typed. 
-		/// Returns <see langword="-1"/> if no item in the collection matched.</returns>
-		public int GetNextMatchingItem (int currentIndex, char keyStruck)
-		{
-			if (!char.IsControl (keyStruck)) {
-
-				// maybe user pressed 'd' and now presses 'd' again.
-				// a candidate search is things that begin with "dd"
-				// but if we find none then we must fallback on cycling
-				// d instead and discard the candidate state
-				string candidateState = "";
-
-				// is it a second or third (etc) keystroke within a short time
-				if (SearchString.Length > 0 && DateTime.Now - lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) {
-					// "dd" is a candidate
-					candidateState = SearchString + keyStruck;
-				} else {
-					// its a fresh keystroke after some time
-					// or its first ever key press
-					SearchString = new string (keyStruck, 1);
-				}
+	/// <summary>
+	/// This event is invoked when <see cref="SearchString"/>  changes. Useful for debugging.
+	/// </summary>
+	public event EventHandler<KeystrokeNavigatorEventArgs> SearchStringChanged;
 
-				var idxCandidate = GetNextMatchingItem (currentIndex, candidateState,
-					// prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart"
-					candidateState.Length > 1);
+	string _searchString = "";
 
-				if (idxCandidate != -1) {
-					// found "dd" so candidate searchstring is accepted
-					lastKeystroke = DateTime.Now;
-					SearchString = candidateState;
-					return idxCandidate;
-				}
+	/// <summary>
+	/// Gets the current search string. This includes the set of keystrokes that have been pressed
+	/// since the last unsuccessful match or after <see cref="TypingDelay"/>) milliseconds. Useful for debugging.
+	/// </summary>
+	public string SearchString {
+		get => _searchString;
+		private set {
+			_searchString = value;
+			OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value));
+		}
+	}
 
-				//// nothing matches "dd" so discard it as a candidate
-				//// and just cycle "d" instead
-				lastKeystroke = DateTime.Now;
-				idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
-
-				// if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z'
-				// instead of "can" + 'd').
-				if (SearchString.Length > 1 && idxCandidate == -1) {
-					// ignore it since we're still within the typing delay
-					// don't add it to SearchString either
-					return currentIndex;
-				}
+	/// <summary>
+	/// Invoked when the <see cref="SearchString"/> changes. Useful for debugging. Invokes the <see cref="SearchStringChanged"/> event.
+	/// </summary>
+	/// <param name="e"></param>
+	public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e) => SearchStringChanged?.Invoke (this, e);
 
-				// if no changes to current state manifested
-				if (idxCandidate == currentIndex || idxCandidate == -1) {
-					// clear history and treat as a fresh letter
-					ClearSearchString ();
+	/// <summary>
+	/// Gets the index of the next item in the collection that matches the current <see cref="SearchString"/> plus the provided character (typically
+	/// from a key press).
+	/// </summary>
+	/// <param name="currentIndex">The index in the collection to start the search from.</param>
+	/// <param name="keyStruck">The character of the key the user pressed.</param>
+	/// <returns>The index of the item that matches what the user has typed. 
+	/// Returns <see langword="-1"/> if no item in the collection matched.</returns>
+	public int GetNextMatchingItem (int currentIndex, char keyStruck)
+	{
+		if (!char.IsControl (keyStruck)) {
+
+			// maybe user pressed 'd' and now presses 'd' again.
+			// a candidate search is things that begin with "dd"
+			// but if we find none then we must fallback on cycling
+			// d instead and discard the candidate state
+			string candidateState = "";
+
+			// is it a second or third (etc) keystroke within a short time
+			if (SearchString.Length > 0 && DateTime.Now - _lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) {
+				// "dd" is a candidate
+				candidateState = SearchString + keyStruck;
+			} else {
+				// its a fresh keystroke after some time
+				// or its first ever key press
+				SearchString = new string (keyStruck, 1);
+			}
 
-					// match on the fresh letter alone
-					SearchString = new string (keyStruck, 1);
-					idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
-					return idxCandidate == -1 ? currentIndex : idxCandidate;
-				}
+			int idxCandidate = GetNextMatchingItem (currentIndex, candidateState,
+				// prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart"
+				candidateState.Length > 1);
 
-				// Found another "d" or just leave index as it was
+			if (idxCandidate != -1) {
+				// found "dd" so candidate search string is accepted
+				_lastKeystroke = DateTime.Now;
+				SearchString = candidateState;
 				return idxCandidate;
+			}
 
-			} else {
-				// clear state because keypress was a control char
-				ClearSearchString ();
+			//// nothing matches "dd" so discard it as a candidate
+			//// and just cycle "d" instead
+			_lastKeystroke = DateTime.Now;
+			idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
 
-				// control char indicates no selection
-				return -1;
+			// if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z'
+			// instead of "can" + 'd').
+			if (SearchString.Length > 1 && idxCandidate == -1) {
+				// ignore it since we're still within the typing delay
+				// don't add it to SearchString either
+				return currentIndex;
 			}
-		}
 
-		/// <summary>
-		/// Gets the index of the next item in the collection that matches <paramref name="search"/>. 
-		/// </summary>
-		/// <param name="currentIndex">The index in the collection to start the search from.</param>
-		/// <param name="search">The search string to use.</param>
-		/// <param name="minimizeMovement">Set to <see langword="true"/> to stop the search on the first match
-		/// if there are multiple matches for <paramref name="search"/>.
-		/// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If <see langword="false"/> (the default), 
-		/// the next matching item will be returned, even if it is above in the collection.
-		/// </param>
-		/// <returns>The index of the next matching item or <see langword="-1"/> if no match was found.</returns>
-		internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)
-		{
-			if (string.IsNullOrEmpty (search)) {
-				return -1;
+			// if no changes to current state manifested
+			if (idxCandidate == currentIndex || idxCandidate == -1) {
+				// clear history and treat as a fresh letter
+				ClearSearchString ();
+
+				// match on the fresh letter alone
+				SearchString = new string (keyStruck, 1);
+				idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
+				return idxCandidate == -1 ? currentIndex : idxCandidate;
 			}
 
-			var collectionLength = GetCollectionLength ();
+			// Found another "d" or just leave index as it was
+			return idxCandidate;
 
-			if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex))) {
-				// we are already at a match
-				if (minimizeMovement) {
-					// if we would rather not jump around (e.g. user is typing lots of text to get this match)
-					return currentIndex;
-				}
+		} else {
+			// clear state because keypress was a control char
+			ClearSearchString ();
 
-				for (int i = 1; i < collectionLength; i++) {
-					//circular
-					var idxCandidate = (i + currentIndex) % collectionLength;
-					if (IsMatch (search, ElementAt (idxCandidate))) {
-						return idxCandidate;
-					}
-				}
+			// control char indicates no selection
+			return -1;
+		}
+	}
+
+	/// <summary>
+	/// Gets the index of the next item in the collection that matches <paramref name="search"/>. 
+	/// </summary>
+	/// <param name="currentIndex">The index in the collection to start the search from.</param>
+	/// <param name="search">The search string to use.</param>
+	/// <param name="minimizeMovement">Set to <see langword="true"/> to stop the search on the first match
+	/// if there are multiple matches for <paramref name="search"/>.
+	/// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If <see langword="false"/> (the default), 
+	/// the next matching item will be returned, even if it is above in the collection.
+	/// </param>
+	/// <returns>The index of the next matching item or <see langword="-1"/> if no match was found.</returns>
+	internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)
+	{
+		if (string.IsNullOrEmpty (search)) {
+			return -1;
+		}
 
-				// nothing else starts with the search term
+		int collectionLength = GetCollectionLength ();
+
+		if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex))) {
+			// we are already at a match
+			if (minimizeMovement) {
+				// if we would rather not jump around (e.g. user is typing lots of text to get this match)
 				return currentIndex;
-			} else {
-				// search terms no longer match the current selection or there is none
-				for (int i = 0; i < collectionLength; i++) {
-					if (IsMatch (search, ElementAt (i))) {
-						return i;
-					}
+			}
+
+			for (int i = 1; i < collectionLength; i++) {
+				//circular
+				int idxCandidate = (i + currentIndex) % collectionLength;
+				if (IsMatch (search, ElementAt (idxCandidate))) {
+					return idxCandidate;
 				}
+			}
 
-				// Nothing matches
-				return -1;
+			// nothing else starts with the search term
+			return currentIndex;
+		} else {
+			// search terms no longer match the current selection or there is none
+			for (int i = 0; i < collectionLength; i++) {
+				if (IsMatch (search, ElementAt (i))) {
+					return i;
+				}
 			}
+
+			// Nothing matches
+			return -1;
 		}
+	}
 
-		/// <summary>
-		/// Return the number of elements in the collection
-		/// </summary>
-		protected abstract int GetCollectionLength ();
+	/// <summary>
+	/// Return the number of elements in the collection
+	/// </summary>
+	protected abstract int GetCollectionLength ();
 
-		private bool IsMatch (string search, object value)
-		{
-			return value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false;
-		}
+	bool IsMatch (string search, object value) => value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false;
 
-		/// <summary>
-		/// Returns the collection being navigated element at <paramref name="idx"/>.
-		/// </summary>
-		/// <returns></returns>
-		protected abstract object ElementAt (int idx);
+	/// <summary>
+	/// Returns the collection being navigated element at <paramref name="idx"/>.
+	/// </summary>
+	/// <returns></returns>
+	protected abstract object ElementAt (int idx);
 
-		private void ClearSearchString ()
-		{
-			SearchString = "";
-			lastKeystroke = DateTime.Now;
-		}
+	void ClearSearchString ()
+	{
+		SearchString = "";
+		_lastKeystroke = DateTime.Now;
+	}
 
-		/// <summary>
-		/// Returns true if <paramref name="a"/> is a searchable key
-		/// (e.g. letters, numbers, etc) that are valid to pass to this
-		/// class for search filtering.
-		/// </summary>
-		/// <param name="a"></param>
-		/// <returns></returns>
-		public static bool IsCompatibleKey (Key a)
-		{
-			return !a.IsAlt && !a.IsCtrl;
-		}
+	/// <summary>
+	/// Returns true if <paramref name="a"/> is a searchable key
+	/// (e.g. letters, numbers, etc) that are valid to pass to this
+	/// class for search filtering.
+	/// </summary>
+	/// <param name="a"></param>
+	/// <returns></returns>
+	public static bool IsCompatibleKey (Key a)
+	{
+		var rune = a.AsRune;
+		return rune != default && !Rune.IsControl (rune);
 	}
-}
+}

+ 9 - 3
Terminal.Gui/View/ViewKeyboard.cs

@@ -330,6 +330,10 @@ public partial class View {
 		}
 
 		// During (this is what can be cancelled)
+		InvokingKeyBindings?.Invoke (this, keyEvent);
+		if (keyEvent.Handled) {
+			return true;
+		}
 		var handled = OnInvokingKeyBindings (keyEvent);
 		if (handled != null && (bool)handled) {
 			return true;
@@ -553,9 +557,11 @@ public partial class View {
 	{
 		// fire event
 		// BUGBUG: KeyEventArgs doesn't include scope, so the event never sees it.
-		InvokingKeyBindings?.Invoke (this, keyEvent);
-		if (keyEvent.Handled) {
-			return true;
+		if (keyEvent.Scope == KeyBindingScope.Application || keyEvent.Scope == KeyBindingScope.HotKey) {
+			InvokingKeyBindings?.Invoke (this, keyEvent);
+			if (keyEvent.Handled) {
+				return true;
+			}
 		}
 
 		// * If no key binding was found, `InvokeKeyBindings` returns `null`.

+ 1 - 1
Terminal.Gui/Views/DateField.cs

@@ -92,7 +92,7 @@ public class DateField : TextField {
 		AddCommand (Command.Right, () => MoveRight ());
 
 		// Default keybindings for this view
-		KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+		KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 		KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
 
 		KeyBindings.Add (Key.Delete, Command.DeleteCharLeft);

+ 1 - 1
Terminal.Gui/Views/FileDialog.cs

@@ -963,7 +963,7 @@ namespace Terminal.Gui {
 				return this.history.Forward ();
 			}
 
-			if (keyEvent.KeyCode == KeyCode.DeleteChar) {
+			if (keyEvent.KeyCode == KeyCode.Delete) {
 
 				Delete ();
 				return true;

+ 3 - 4
Terminal.Gui/Views/TextField.cs

@@ -143,10 +143,9 @@ namespace Terminal.Gui {
 
 			// Default keybindings for this view
 			// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
-			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
-			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);
 			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 
 			KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
@@ -201,9 +200,9 @@ namespace Terminal.Gui {
 			KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
 			KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight);
 
-			KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask, Command.KillWordForwards);
+			KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards);
 			KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
-			KeyBindings.Add (KeyCode.InsertChar, Command.ToggleOverwrite);
+			KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
 			KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
 			KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
 			KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste);

+ 1 - 1
Terminal.Gui/Views/TextValidateField.cs

@@ -404,7 +404,7 @@ namespace Terminal.Gui {
 			KeyBindings.Add (KeyCode.End, Command.RightEnd);
 
 			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
-			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 
 			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1307 - 1317
Terminal.Gui/Views/TextView.cs


+ 1 - 1
Terminal.Gui/Views/TimeField.cs

@@ -87,7 +87,7 @@ namespace Terminal.Gui {
 			AddCommand (Command.Right, () => MoveRight ());
 
 			// Default keybindings for this view
-			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
 			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);

+ 3 - 3
UICatalog/Scenarios/CsvEditor.cs

@@ -466,9 +466,9 @@ public class CsvEditor : Scenario {
 
 	}
 
-	void TableViewKeyPress (object sender, Key e)
-	{
-		if (e.KeyCode == KeyCode.DeleteChar) {
+		private void TableViewKeyPress (object sender, Key e)
+		{
+			if (e.KeyCode == KeyCode.Delete) {
 
 			if (tableView.FullRowSelect) {
 				// Delete button deletes all rows when in full row mode

+ 1 - 1
UICatalog/Scenarios/InteractiveTree.cs

@@ -45,7 +45,7 @@ namespace UICatalog.Scenarios {
 
 		private void TreeView_KeyPress (object sender, Key obj)
 		{
-			if (obj.KeyCode == KeyCode.DeleteChar) {
+			if (obj.KeyCode == KeyCode.Delete) {
 
 				var toDelete = treeView.SelectedObject;
 

+ 1 - 1
UICatalog/Scenarios/ListColumns.cs

@@ -154,7 +154,7 @@ namespace UICatalog.Scenarios {
 
 		private void TableViewKeyPress (object sender, Key e)
 		{
-			if (e.KeyCode == KeyCode.DeleteChar) {
+			if (e.KeyCode == KeyCode.Delete) {
 
 				// set all selected cells to null
 				foreach (var pt in listColView.GetAllSelectedCells ()) {

+ 1 - 1
UICatalog/Scenarios/TableEditor.cs

@@ -391,7 +391,7 @@ namespace UICatalog.Scenarios {
 				return;
 			}
 
-			if (e.KeyCode == KeyCode.DeleteChar) {
+			if (e.KeyCode == KeyCode.Delete) {
 
 				if (tableView.FullRowSelect) {
 					// Delete button deletes all rows when in full row mode

+ 55 - 69
UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -5,12 +5,12 @@ using System.Threading.Tasks;
 using Terminal.Gui;
 using Terminal.Gui.ConsoleDrivers;
 
-namespace UICatalog.Scenarios; 
+namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("VkeyPacketSimulator", "Simulates the Virtual Key Packet")]
 [ScenarioCategory ("Mouse and Keyboard")]
 public class VkeyPacketSimulator : Scenario {
-	List<int> _keyboardStrokes = new ();
+	List<KeyCode> _keyboardStrokes = new ();
 	bool _outputStarted = false;
 	bool _wasUnknown = false;
 	static ManualResetEventSlim _stopOutput = new (false);
@@ -94,54 +94,55 @@ public class VkeyPacketSimulator : Scenario {
 
 		// Detect unknown keys and reject them.
 		tvOutput.KeyDown += (s, e) => {
-			//System.Diagnostics.Debug.WriteLine ($"Output - KeyDown: {e.Key}");
-			//e.Handled = true;
-			if (e == Key.Empty) {
+			//System.Diagnostics.Debug.WriteLine ($"Output - KeyDown: {e.KeyCode}");
+			if (e.NoAlt.NoCtrl.NoShift == KeyCode.Null) {
 				_wasUnknown = true;
+				e.Handled = true;
+				return;
 			}
-		};
 
-		tvOutput.KeyUp += (s, e) => {
 			//System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
-			if (_outputStarted && _keyboardStrokes.Count > 0) {
-				//// TODO: Tig: I don't understand what this is trying to do
-				//if (!tvOutput.ProcessKeyDown (e)) {
-				//	Application.Invoke (() => {
-				//		MessageBox.Query ("Keys", $"'{KeyEventArgs.ToString (e.ConsoleDriverKey, MenuBar.ShortcutDelimiter)}' pressed!", "Ok");
-				//	});
-				//}
-				e.Handled = true;
-				_stopOutput.Set ();
+			if (_outputStarted) {
+				// If the key wasn't handled by the TextView will popup a Dialog with the keys pressed.
+				var handled = tvOutput.OnInvokingKeyBindings (e);
+				if (handled == null || handled == false) {
+					if (!tvOutput.OnProcessKeyDown (e)) {
+						Application.Invoke (() => MessageBox.Query ("Keys", $"'{Key.ToString (e.KeyCode, MenuBar.ShortcutDelimiter)}' pressed!", "Ok"));
+					}
+				}
 			}
-			//System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
+			e.Handled = true;
+			_stopOutput.Set ();
 		};
 
 		Win.Add (tvOutput);
 
-		Key unknownChar = null;
+		tvInput.KeyDown += (s, e) => {
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyDown: {e.KeyCode.Key}");
+			if (e.KeyCode == Key.Empty) {
+				_wasUnknown = true;
+				e.Handled = true;
+			} else {
+				_wasUnknown = false;
+			}
+		};
 
-		tvInput.KeyUp += (s, e) => {
-			//System.Diagnostics.Debug.WriteLine ($"Input - KeyUp: {e.Key}");
-			//var ke = e;
+		tvInput.InvokingKeyBindings += (s, e) => {
+			var ev = e;
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyPress: {ev}");
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
 
-			if (e == Key.Empty) {
+			if (!e.IsValid) {
 				_wasUnknown = true;
 				e.Handled = true;
 				return;
 			}
-			if (_wasUnknown && _keyboardStrokes.Count == 1) {
-				_wasUnknown = false;
-			} else if (_wasUnknown && char.IsLetter ((char)e.KeyCode)) {
-				_wasUnknown = false;
-			}
-			if (_keyboardStrokes.Count == 0) {
-				AddKeyboardStrokes (e);
-			} else {
-				_keyboardStrokes.Insert (0, 0);
-			}
-			if (_wasUnknown && (int)e.KeyCode - (int)(e.KeyCode & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask)) != 0) {
-				unknownChar = e;
-			}
+
+			_keyboardStrokes.Add (e.KeyCode);
+		};
+
+		tvInput.KeyUp += (s, e) => {
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyUp: {e.Key}");
 			e.Handled = true;
 			if (!_wasUnknown && _keyboardStrokes.Count > 0) {
 				_outputStarted = true;
@@ -152,38 +153,31 @@ public class VkeyPacketSimulator : Scenario {
 				Task.Run (() => {
 					while (_outputStarted) {
 						try {
-							var mod = new ConsoleModifiers ();
-							if (e.KeyCode.HasFlag (KeyCode.ShiftMask)) {
-								mod |= ConsoleModifiers.Shift;
-							}
-							if (e.KeyCode.HasFlag (KeyCode.AltMask)) {
-								mod |= ConsoleModifiers.Alt;
-							}
-							if (e.KeyCode.HasFlag (KeyCode.CtrlMask)) {
-								mod |= ConsoleModifiers.Control;
-							}
-							for (int i = 0; i < _keyboardStrokes.Count; i++) {
-								var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey ((uint)_keyboardStrokes [i], mod, out _);
-								Application.Driver.SendKeys (consoleKeyInfo.KeyChar, ConsoleKey.Packet, mod.HasFlag (ConsoleModifiers.Shift),
-									mod.HasFlag (ConsoleModifiers.Alt), mod.HasFlag (ConsoleModifiers.Control));
+							while (_keyboardStrokes.Count > 0) {
+								if (_keyboardStrokes [0] == KeyCode.Null) {
+									continue;
+								}
+								var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (_keyboardStrokes [0]);
+								var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
+								Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift),
+									consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
+
+								_stopOutput.Wait ();
+								_stopOutput.Reset ();
+								_keyboardStrokes.RemoveAt (0);
+								Application.Invoke (() => {
+									tvOutput.ReadOnly = true;
+									tvInput.SetFocus ();
+								});
 							}
-							//}
+							_outputStarted = false;
+
 						} catch (Exception) {
 							Application.Invoke (() => {
 								MessageBox.ErrorQuery ("Error", "Couldn't send the keystrokes!", "Ok");
 								Application.RequestStop ();
 							});
 						}
-						_stopOutput.Wait ();
-						_stopOutput.Reset ();
-						_keyboardStrokes.RemoveAt (0);
-						if (_keyboardStrokes.Count == 0) {
-							_outputStarted = false;
-							Application.Invoke (() => {
-								tvOutput.ReadOnly = true;
-								tvInput.SetFocus ();
-							});
-						}
 					}
 					//System.Diagnostics.Debug.WriteLine ($"_outputStarted: {_outputStarted}");
 				});
@@ -214,12 +208,4 @@ public class VkeyPacketSimulator : Scenario {
 
 		Win.LayoutComplete += Win_LayoutComplete;
 	}
-
-	void AddKeyboardStrokes (Key e)
-	{
-		int keyChar = (int)e.KeyCode;
-		int mK = (int)(e.KeyCode & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask));
-		keyChar &= ~mK;
-		_keyboardStrokes.Add (keyChar);
-	}
-}
+}

+ 415 - 148
UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs

@@ -1,46 +1,57 @@
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using Xunit;
+using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui.ConsoleDrivers;
 public class ConsoleKeyMappingTests {
+
+#if ENABLE_VK_PACKET_NON_WINDOWS
+	// This test (and the GetConsoleKeyInfoFromKeyCode API) are bogus. They make no sense outside of
+	// the context of Windows and knowing they keyboard layout. They should be removed.
 	[Theory]
-	[InlineData ((KeyCode)'a' | KeyCode.ShiftMask, ConsoleKey.A, KeyCode.A, 'A')]
-	[InlineData ((KeyCode)'A', ConsoleKey.A, (KeyCode)'a', 'a')]
-	[InlineData ((KeyCode)'à' | KeyCode.ShiftMask, ConsoleKey.A, (KeyCode)'À', 'À')]
-	[InlineData ((KeyCode)'À', ConsoleKey.A, (KeyCode)'à', 'à')]
-	[InlineData ((KeyCode)'ü' | KeyCode.ShiftMask, ConsoleKey.U, (KeyCode)'Ü', 'Ü')]
-	[InlineData ((KeyCode)'Ü', ConsoleKey.U, (KeyCode)'ü', 'ü')]
-	[InlineData ((KeyCode)'ý' | KeyCode.ShiftMask, ConsoleKey.Y, (KeyCode)'Ý', 'Ý')]
-	[InlineData ((KeyCode)'Ý', ConsoleKey.Y, (KeyCode)'ý', 'ý')]
+	[InlineData (KeyCode.A | KeyCode.ShiftMask, ConsoleKey.A, KeyCode.A, 'A')]
+	[InlineData ((KeyCode)'a', ConsoleKey.A, (KeyCode)'a', 'a')]
+	[InlineData ((KeyCode)'À' | KeyCode.ShiftMask, ConsoleKey.A, (KeyCode)'À', 'À')]
+	[InlineData ((KeyCode)'à', ConsoleKey.A, (KeyCode)'à', 'à')]
+	[InlineData ((KeyCode)'Ü' | KeyCode.ShiftMask, ConsoleKey.U, (KeyCode)'Ü', 'Ü')]
+	[InlineData ((KeyCode)'ü', ConsoleKey.U, (KeyCode)'ü', 'ü')]
+	[InlineData ((KeyCode)'Ý' | KeyCode.ShiftMask, ConsoleKey.Y, (KeyCode)'Ý', 'Ý')]
+	[InlineData ((KeyCode)'ý', ConsoleKey.Y, (KeyCode)'ý', 'ý')]
 	[InlineData ((KeyCode)'!' | KeyCode.ShiftMask, ConsoleKey.D1, (KeyCode)'!', '!')]
+	[InlineData (KeyCode.D1 | KeyCode.ShiftMask, ConsoleKey.D1, (KeyCode)'!', '!')]
 	[InlineData (KeyCode.D1, ConsoleKey.D1, KeyCode.D1, '1')]
-	[InlineData ((KeyCode)'/' | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')]
+	[InlineData ((KeyCode)'/' | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')] // BUGBUG: This is incorrect for ENG keyboards. Shift-7 should be &.
+	[InlineData (KeyCode.D7 | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')]
 	[InlineData (KeyCode.D7, ConsoleKey.D7, KeyCode.D7, '7')]
+	[InlineData ((KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, ConsoleKey.D7, (KeyCode)'{', '{')]
+	[InlineData ((KeyCode)'?' | KeyCode.ShiftMask, ConsoleKey.Oem4, (KeyCode)'?', '?')]
+	[InlineData ((KeyCode)'\'', ConsoleKey.Oem4, (KeyCode)'\'', '\'')]
 	[InlineData (KeyCode.PageDown | KeyCode.ShiftMask, ConsoleKey.PageDown, KeyCode.Null, '\0')]
 	[InlineData (KeyCode.PageDown, ConsoleKey.PageDown, KeyCode.Null, '\0')]
-
-	public void TestIfEqual (KeyCode key, ConsoleKey expectedConsoleKey, KeyCode expectedKey, char expectedChar)
+	[InlineData ((KeyCode)'q', ConsoleKey.Q, (KeyCode)'q', 'q')]
+	[InlineData (KeyCode.F2, ConsoleKey.F2, KeyCode.Null, '\0')]
+	[InlineData ((KeyCode)'英', ConsoleKey.None, (KeyCode)'英', '英')]
+	public void GetConsoleKeyInfoFromKeyCode_Tests (KeyCode keyCode, ConsoleKey expectedConsoleKey, KeyCode expectedKeyCode, char expectedKeyChar)
 	{
-		var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (key);
+		var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (keyCode);
 		Assert.Equal (consoleKeyInfo.Key, expectedConsoleKey);
-		Assert.Equal ((char)expectedKey, expectedChar);
-		Assert.Equal (consoleKeyInfo.KeyChar, expectedChar);
+		Assert.Equal ((char)expectedKeyCode, expectedKeyChar);
+		Assert.Equal (consoleKeyInfo.KeyChar, expectedKeyChar);
 	}
 
 	static object packetLock = new object ();
 
 	/// <summary>
 	/// Sometimes when using remote tools EventKeyRecord sends 'virtual keystrokes'.
-	/// These are indicated with the wVirtualKeyCode of 231. When we see this code
+	/// These are indicated with the wVirtualKeyCode of 231 (VK_PACKET). When we see this code
 	/// then we need to look to the unicode character (UnicodeChar) instead of the key
 	/// when telling the rest of the framework what button was pressed. For full details
 	/// see: https://github.com/gui-cs/Terminal.Gui/issues/2008
 	/// </summary>
 	[Theory]
 	[AutoInitShutdown]
-	[ClassData (typeof (PacketTest))]
+	[MemberData (nameof (VKPacket))]
 	public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey,
 				uint initialScanCode, KeyCode expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
 	{
@@ -48,20 +59,17 @@ public class ConsoleKeyMappingTests {
 			Application._forceFakeConsole = true;
 			Application.Init ();
 
-			var modifiers = new ConsoleModifiers ();
-			if (shift) {
-				modifiers |= ConsoleModifiers.Shift;
-			}
-			if (alt) {
-				modifiers |= ConsoleModifiers.Alt;
-			}
-			if (control) {
-				modifiers |= ConsoleModifiers.Control;
-			}
-			ConsoleKeyInfo consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode);
+			ConsoleKeyInfo originalConsoleKeyInfo = new ConsoleKeyInfo ((char)unicodeCharacter, (ConsoleKey)initialVirtualKey, shift, alt, control);
+			var encodedChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (originalConsoleKeyInfo);
+			ConsoleKeyInfo packetConsoleKeyInfo = new ConsoleKeyInfo (encodedChar, ConsoleKey.Packet, shift, alt, control);
+			ConsoleKeyInfo consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (packetConsoleKeyInfo);
 
-			Assert.Equal ((uint)consoleKeyInfo.Key, initialVirtualKey);
+			Assert.Equal (originalConsoleKeyInfo, consoleKeyInfo);
 
+			var modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control);
+			var scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (consoleKeyInfo);
+
+			Assert.Equal ((uint)consoleKeyInfo.Key, initialVirtualKey);
 
 			if (scanCode > 0 && consoleKeyInfo.KeyChar == 0) {
 				Assert.Equal (0, (double)consoleKeyInfo.KeyChar);
@@ -85,7 +93,8 @@ public class ConsoleKeyMappingTests {
 			Application.Iteration += (s, a) => {
 				iterations++;
 				if (iterations == 0) {
-					Application.Driver.SendKeys (consoleKeyInfo.KeyChar, ConsoleKey.Packet, shift, alt, control);
+					var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
+					Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, shift, alt, control);
 				}
 			};
 			Application.Run ();
@@ -93,126 +102,384 @@ public class ConsoleKeyMappingTests {
 		}
 	}
 
-	public class PacketTest : IEnumerable, IEnumerable<object []> {
-		public IEnumerator<object []> GetEnumerator ()
-		{
-			lock (packetLock) {
-				// unicodeCharacter, shift, alt, control, initialVirtualKey, initialScanCode, expectedRemapping, expectedVirtualKey, expectedScanCode
-				yield return new object [] { 'a', false, false, false, 'A', 30, KeyCode.A, 'A', 30 };
-				yield return new object [] { 'A', true, false, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask, 'A', 30 };
-				yield return new object [] { 'A', true, true, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask, 'A', 30 };
-				yield return new object [] { 'A', true, true, true, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'A', 30 };
-				yield return new object [] { 'z', false, false, false, 'Z', 44, KeyCode.Z, 'Z', 44 };
-				yield return new object [] { 'Z', true, false, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask, 'Z', 44 };
-				yield return new object [] { 'Z', true, true, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask, 'Z', 44 };
-				yield return new object [] { 'Z', true, true, true, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'Z', 44 };
-				yield return new object [] { '英', false, false, false, '\0', 0, (KeyCode)'英', '\0', 0 };
-				yield return new object [] { '英', true, false, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask, '\0', 0 };
-				yield return new object [] { '英', true, true, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask, '\0', 0 };
-				yield return new object [] { '英', true, true, true, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '\0', 0 };
-				yield return new object [] { '+', false, false, false, 187, 26, (KeyCode)'+', 187, 26 };
-				yield return new object [] { '*', true, false, false, 187, 26, (KeyCode)'*' | KeyCode.ShiftMask, 187, 26 };
-				yield return new object [] { '+', true, true, false, 187, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask, 187, 26 };
-				yield return new object [] { '+', true, true, true, 187, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 187, 26 };
-				yield return new object [] { '1', false, false, false, '1', 2, KeyCode.D1, '1', 2 };
-				yield return new object [] { '!', true, false, false, '1', 2, (KeyCode)'!' | KeyCode.ShiftMask, '1', 2 };
-				yield return new object [] { '1', true, true, false, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask, '1', 2 };
-				yield return new object [] { '1', true, true, true, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
-				yield return new object [] { '1', false, true, true, '1', 2, KeyCode.D1 | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
-				yield return new object [] { '2', false, false, false, '2', 3, KeyCode.D2, '2', 3 };
-				yield return new object [] { '"', true, false, false, '2', 3, (KeyCode)'"' | KeyCode.ShiftMask, '2', 3 };
-				yield return new object [] { '2', true, true, false, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask, '2', 3 };
-				yield return new object [] { '2', true, true, true, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
-				yield return new object [] { '@', false, true, true, '2', 3, (KeyCode)'@' | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
-				yield return new object [] { '3', false, false, false, '3', 4, KeyCode.D3, '3', 4 };
-				yield return new object [] { '#', true, false, false, '3', 4, (KeyCode)'#' | KeyCode.ShiftMask, '3', 4 };
-				yield return new object [] { '3', true, true, false, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask, '3', 4 };
-				yield return new object [] { '3', true, true, true, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
-				yield return new object [] { '£', false, true, true, '3', 4, (KeyCode)'£' | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
-				yield return new object [] { '4', false, false, false, '4', 5, KeyCode.D4, '4', 5 };
-				yield return new object [] { '$', true, false, false, '4', 5, (KeyCode)'$' | KeyCode.ShiftMask, '4', 5 };
-				yield return new object [] { '4', true, true, false, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask, '4', 5 };
-				yield return new object [] { '4', true, true, true, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
-				yield return new object [] { '§', false, true, true, '4', 5, (KeyCode)'§' | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
-				yield return new object [] { '5', false, false, false, '5', 6, KeyCode.D5, '5', 6 };
-				yield return new object [] { '%', true, false, false, '5', 6, (KeyCode)'%' | KeyCode.ShiftMask, '5', 6 };
-				yield return new object [] { '5', true, true, false, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask, '5', 6 };
-				yield return new object [] { '5', true, true, true, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
-				yield return new object [] { '€', false, true, true, '5', 6, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
-				yield return new object [] { '6', false, false, false, '6', 7, KeyCode.D6, '6', 7 };
-				yield return new object [] { '&', true, false, false, '6', 7, (KeyCode)'&' | KeyCode.ShiftMask, '6', 7 };
-				yield return new object [] { '6', true, true, false, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask, '6', 7 };
-				yield return new object [] { '6', true, true, true, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
-				yield return new object [] { '6', false, true, true, '6', 7, KeyCode.D6 | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
-				yield return new object [] { '7', false, false, false, '7', 8, KeyCode.D7, '7', 8 };
-				yield return new object [] { '/', true, false, false, '7', 8, (KeyCode)'/' | KeyCode.ShiftMask, '7', 8 };
-				yield return new object [] { '7', true, true, false, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask, '7', 8 };
-				yield return new object [] { '7', true, true, true, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
-				yield return new object [] { '{', false, true, true, '7', 8, (KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
-				yield return new object [] { '8', false, false, false, '8', 9, KeyCode.D8, '8', 9 };
-				yield return new object [] { '(', true, false, false, '8', 9, (KeyCode)'(' | KeyCode.ShiftMask, '8', 9 };
-				yield return new object [] { '8', true, true, false, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask, '8', 9 };
-				yield return new object [] { '8', true, true, true, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
-				yield return new object [] { '[', false, true, true, '8', 9, (KeyCode)'[' | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
-				yield return new object [] { '9', false, false, false, '9', 10, KeyCode.D9, '9', 10 };
-				yield return new object [] { ')', true, false, false, '9', 10, (KeyCode)')' | KeyCode.ShiftMask, '9', 10 };
-				yield return new object [] { '9', true, true, false, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask, '9', 10 };
-				yield return new object [] { '9', true, true, true, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
-				yield return new object [] { ']', false, true, true, '9', 10, (KeyCode)']' | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
-				yield return new object [] { '0', false, false, false, '0', 11, KeyCode.D0, '0', 11 };
-				yield return new object [] { '=', true, false, false, '0', 11, (KeyCode)'=' | KeyCode.ShiftMask, '0', 11 };
-				yield return new object [] { '0', true, true, false, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask, '0', 11 };
-				yield return new object [] { '0', true, true, true, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
-				yield return new object [] { '}', false, true, true, '0', 11, (KeyCode)'}' | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
-				yield return new object [] { '\'', false, false, false, 219, 12, (KeyCode)'\'', 219, 12 };
-				yield return new object [] { '?', true, false, false, 219, 12, (KeyCode)'?' | KeyCode.ShiftMask, 219, 12 };
-				yield return new object [] { '\'', true, true, false, 219, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask, 219, 12 };
-				yield return new object [] { '\'', true, true, true, 219, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 219, 12 };
-				yield return new object [] { '«', false, false, false, 221, 13, (KeyCode)'«', 221, 13 };
-				yield return new object [] { '»', true, false, false, 221, 13, (KeyCode)'»' | KeyCode.ShiftMask, 221, 13 };
-				yield return new object [] { '«', true, true, false, 221, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask, 221, 13 };
-				yield return new object [] { '«', true, true, true, 221, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 221, 13 };
-				yield return new object [] { 'á', false, false, false, 'A', 30, (KeyCode)'á', 'A', 30 };
-				yield return new object [] { 'Á', true, false, false, 'A', 30, (KeyCode)'Á' | KeyCode.ShiftMask, 'A', 30 };
-				yield return new object [] { 'à', false, false, false, 'A', 30, (KeyCode)'à', 'A', 30 };
-				yield return new object [] { 'À', true, false, false, 'A', 30, (KeyCode)'À' | KeyCode.ShiftMask, 'A', 30 };
-				yield return new object [] { 'é', false, false, false, 'E', 18, (KeyCode)'é', 'E', 18 };
-				yield return new object [] { 'É', true, false, false, 'E', 18, (KeyCode)'É' | KeyCode.ShiftMask, 'E', 18 };
-				yield return new object [] { 'è', false, false, false, 'E', 18, (KeyCode)'è', 'E', 18 };
-				yield return new object [] { 'È', true, false, false, 'E', 18, (KeyCode)'È' | KeyCode.ShiftMask, 'E', 18 };
-				yield return new object [] { 'í', false, false, false, 'I', 23, (KeyCode)'í', 'I', 23 };
-				yield return new object [] { 'Í', true, false, false, 'I', 23, (KeyCode)'Í' | KeyCode.ShiftMask, 'I', 23 };
-				yield return new object [] { 'ì', false, false, false, 'I', 23, (KeyCode)'ì', 'I', 23 };
-				yield return new object [] { 'Ì', true, false, false, 'I', 23, (KeyCode)'Ì' | KeyCode.ShiftMask, 'I', 23 };
-				yield return new object [] { 'ó', false, false, false, 'O', 24, (KeyCode)'ó', 'O', 24 };
-				yield return new object [] { 'Ó', true, false, false, 'O', 24, (KeyCode)'Ó' | KeyCode.ShiftMask, 'O', 24 };
-				yield return new object [] { 'ò', false, false, false, 'O', 24, (KeyCode)'ò', 'O', 24 };
-				yield return new object [] { 'Ò', true, false, false, 'O', 24, (KeyCode)'Ò' | KeyCode.ShiftMask, 'O', 24 };
-				yield return new object [] { 'ú', false, false, false, 'U', 22, (KeyCode)'ú', 'U', 22 };
-				yield return new object [] { 'Ú', true, false, false, 'U', 22, (KeyCode)'Ú' | KeyCode.ShiftMask, 'U', 22 };
-				yield return new object [] { 'ù', false, false, false, 'U', 22, (KeyCode)'ù', 'U', 22 };
-				yield return new object [] { 'Ù', true, false, false, 'U', 22, (KeyCode)'Ù' | KeyCode.ShiftMask, 'U', 22 };
-				yield return new object [] { 'ö', false, false, false, 'O', 24, (KeyCode)'ö', 'O', 24 };
-				yield return new object [] { 'Ö', true, false, false, 'O', 24, (KeyCode)'Ö' | KeyCode.ShiftMask, 'O', 24 };
-				yield return new object [] { '<', false, false, false, 226, 86, (KeyCode)'<', 226, 86 };
-				yield return new object [] { '>', true, false, false, 226, 86, (KeyCode)'>' | KeyCode.ShiftMask, 226, 86 };
-				yield return new object [] { '<', true, true, false, 226, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask, 226, 86 };
-				yield return new object [] { '<', true, true, true, 226, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 226, 86 };
-				yield return new object [] { 'ç', false, false, false, 192, 39, (KeyCode)'ç', 192, 39 };
-				yield return new object [] { 'Ç', true, false, false, 192, 39, (KeyCode)'Ç' | KeyCode.ShiftMask, 192, 39 };
-				yield return new object [] { 'ç', true, true, false, 192, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask, 192, 39 };
-				yield return new object [] { 'ç', true, true, true, 192, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 192, 39 };
-				yield return new object [] { '¨', false, true, true, 187, 26, (KeyCode)'¨' | KeyCode.AltMask | KeyCode.CtrlMask, 187, 26 };
-				yield return new object [] { KeyCode.PageUp, false, false, false, 33, 73, KeyCode.Null, 33, 73 };
-				yield return new object [] { KeyCode.PageUp, true, false, false, 33, 73, KeyCode.Null | KeyCode.ShiftMask, 33, 73 };
-				yield return new object [] { KeyCode.PageUp, true, true, false, 33, 73, KeyCode.Null | KeyCode.ShiftMask | KeyCode.AltMask, 33, 73 };
-				yield return new object [] { KeyCode.PageUp, true, true, true, 33, 73, KeyCode.Null | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 33, 73 };
-			}
+	public static IEnumerable<object []> VKPacket ()
+	{
+		lock (packetLock) {
+			// unicodeCharacter, shift, alt, control, initialVirtualKey, initialScanCode, expectedRemapping, expectedVirtualKey, expectedScanCode
+			yield return new object [] { 'a', false, false, false, 'A', 30, KeyCode.A, 'A', 30 };
+			yield return new object [] { 'A', true, false, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask, 'A', 30 };
+			yield return new object [] { 'A', true, true, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask, 'A', 30 };
+			yield return new object [] { 'A', true, true, true, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'A', 30 };
+			yield return new object [] { 'z', false, false, false, 'Z', 44, KeyCode.Z, 'Z', 44 };
+			yield return new object [] { 'Z', true, false, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask, 'Z', 44 };
+			yield return new object [] { 'Z', true, true, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask, 'Z', 44 };
+			yield return new object [] { 'Z', true, true, true, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'Z', 44 };
+			yield return new object [] { '英', false, false, false, '\0', 0, (KeyCode)'英', '\0', 0 };
+			yield return new object [] { '英', true, false, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask, '\0', 0 };
+			yield return new object [] { '英', true, true, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask, '\0', 0 };
+			yield return new object [] { '英', true, true, true, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '\0', 0 };
+			yield return new object [] { '+', false, false, false, VK.OEM_PLUS, 26, (KeyCode)'+', VK.OEM_PLUS, 26 };
+			yield return new object [] { '*', true, false, false, VK.OEM_PLUS, 26, (KeyCode)'*' | KeyCode.ShiftMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '+', true, true, false, VK.OEM_PLUS, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '+', true, true, true, VK.OEM_PLUS, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '1', false, false, false, '1', 2, KeyCode.D1, '1', 2 };
+			yield return new object [] { '!', true, false, false, '1', 2, (KeyCode)'!' | KeyCode.ShiftMask, '1', 2 };
+			yield return new object [] { '1', true, true, false, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask, '1', 2 };
+			yield return new object [] { '1', true, true, true, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
+			yield return new object [] { '1', false, true, true, '1', 2, KeyCode.D1 | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
+			yield return new object [] { '2', false, false, false, '2', 3, KeyCode.D2, '2', 3 };
+			yield return new object [] { '"', true, false, false, '2', 3, (KeyCode)'"' | KeyCode.ShiftMask, '2', 3 };
+			yield return new object [] { '2', true, true, false, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask, '2', 3 };
+			yield return new object [] { '2', true, true, true, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
+			yield return new object [] { '@', false, true, true, '2', 3, (KeyCode)'@' | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
+			yield return new object [] { '3', false, false, false, '3', 4, KeyCode.D3, '3', 4 };
+			yield return new object [] { '#', true, false, false, '3', 4, (KeyCode)'#' | KeyCode.ShiftMask, '3', 4 };
+			yield return new object [] { '3', true, true, false, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask, '3', 4 };
+			yield return new object [] { '3', true, true, true, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
+			yield return new object [] { '£', false, true, true, '3', 4, (KeyCode)'£' | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
+			yield return new object [] { '4', false, false, false, '4', 5, KeyCode.D4, '4', 5 };
+			yield return new object [] { '$', true, false, false, '4', 5, (KeyCode)'$' | KeyCode.ShiftMask, '4', 5 };
+			yield return new object [] { '4', true, true, false, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask, '4', 5 };
+			yield return new object [] { '4', true, true, true, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
+			yield return new object [] { '§', false, true, true, '4', 5, (KeyCode)'§' | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
+			yield return new object [] { '5', false, false, false, '5', 6, KeyCode.D5, '5', 6 };
+			yield return new object [] { '%', true, false, false, '5', 6, (KeyCode)'%' | KeyCode.ShiftMask, '5', 6 };
+			yield return new object [] { '5', true, true, false, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask, '5', 6 };
+			yield return new object [] { '5', true, true, true, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
+			yield return new object [] { '€', false, true, true, '5', 6, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
+			yield return new object [] { '6', false, false, false, '6', 7, KeyCode.D6, '6', 7 };
+			yield return new object [] { '&', true, false, false, '6', 7, (KeyCode)'&' | KeyCode.ShiftMask, '6', 7 };
+			yield return new object [] { '6', true, true, false, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask, '6', 7 };
+			yield return new object [] { '6', true, true, true, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
+			yield return new object [] { '6', false, true, true, '6', 7, KeyCode.D6 | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
+			yield return new object [] { '7', false, false, false, '7', 8, KeyCode.D7, '7', 8 };
+			yield return new object [] { '/', true, false, false, '7', 8, (KeyCode)'/' | KeyCode.ShiftMask, '7', 8 }; // BUGBUG: This is not true for ENG keyboards. Shift-7 is &.
+			yield return new object [] { '7', true, true, false, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask, '7', 8 };
+			yield return new object [] { '7', true, true, true, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
+			yield return new object [] { '{', false, true, true, '7', 8, (KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
+			yield return new object [] { '8', false, false, false, '8', 9, KeyCode.D8, '8', 9 };
+			yield return new object [] { '(', true, false, false, '8', 9, (KeyCode)'(' | KeyCode.ShiftMask, '8', 9 };
+			yield return new object [] { '8', true, true, false, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask, '8', 9 };
+			yield return new object [] { '8', true, true, true, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
+			yield return new object [] { '[', false, true, true, '8', 9, (KeyCode)'[' | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
+			yield return new object [] { '9', false, false, false, '9', 10, KeyCode.D9, '9', 10 };
+			yield return new object [] { ')', true, false, false, '9', 10, (KeyCode)')' | KeyCode.ShiftMask, '9', 10 };
+			yield return new object [] { '9', true, true, false, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask, '9', 10 };
+			yield return new object [] { '9', true, true, true, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
+			yield return new object [] { ']', false, true, true, '9', 10, (KeyCode)']' | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
+			yield return new object [] { '0', false, false, false, '0', 11, KeyCode.D0, '0', 11 };
+			yield return new object [] { '=', true, false, false, '0', 11, (KeyCode)'=' | KeyCode.ShiftMask, '0', 11 };
+			yield return new object [] { '0', true, true, false, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask, '0', 11 };
+			yield return new object [] { '0', true, true, true, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
+			yield return new object [] { '}', false, true, true, '0', 11, (KeyCode)'}' | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
+			yield return new object [] { '\'', false, false, false, VK.OEM_4, 12, (KeyCode)'\'', VK.OEM_4, 12 };
+			yield return new object [] { '?', true, false, false, VK.OEM_4, 12, (KeyCode)'?' | KeyCode.ShiftMask, VK.OEM_4, 12 };
+			yield return new object [] { '\'', true, true, false, VK.OEM_4, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_4, 12 };
+			yield return new object [] { '\'', true, true, true, VK.OEM_4, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_4, 12 };
+			yield return new object [] { '«', false, false, false, VK.OEM_6, 13, (KeyCode)'«', VK.OEM_6, 13 };
+			yield return new object [] { '»', true, false, false, VK.OEM_6, 13, (KeyCode)'»' | KeyCode.ShiftMask, VK.OEM_6, 13 };
+			yield return new object [] { '«', true, true, false, VK.OEM_6, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_6, 13 };
+			yield return new object [] { '«', true, true, true, VK.OEM_6, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_6, 13 };
+			yield return new object [] { 'á', false, false, false, 'A', 30, (KeyCode)'á', 'A', 30 };
+			yield return new object [] { 'Á', true, false, false, 'A', 30, (KeyCode)'Á' | KeyCode.ShiftMask, 'A', 30 };
+			yield return new object [] { 'à', false, false, false, 'A', 30, (KeyCode)'à', 'A', 30 };
+			yield return new object [] { 'À', true, false, false, 'A', 30, (KeyCode)'À' | KeyCode.ShiftMask, 'A', 30 };
+			yield return new object [] { 'é', false, false, false, 'E', 18, (KeyCode)'é', 'E', 18 };
+			yield return new object [] { 'É', true, false, false, 'E', 18, (KeyCode)'É' | KeyCode.ShiftMask, 'E', 18 };
+			yield return new object [] { 'è', false, false, false, 'E', 18, (KeyCode)'è', 'E', 18 };
+			yield return new object [] { 'È', true, false, false, 'E', 18, (KeyCode)'È' | KeyCode.ShiftMask, 'E', 18 };
+			yield return new object [] { 'í', false, false, false, 'I', 23, (KeyCode)'í', 'I', 23 };
+			yield return new object [] { 'Í', true, false, false, 'I', 23, (KeyCode)'Í' | KeyCode.ShiftMask, 'I', 23 };
+			yield return new object [] { 'ì', false, false, false, 'I', 23, (KeyCode)'ì', 'I', 23 };
+			yield return new object [] { 'Ì', true, false, false, 'I', 23, (KeyCode)'Ì' | KeyCode.ShiftMask, 'I', 23 };
+			yield return new object [] { 'ó', false, false, false, 'O', 24, (KeyCode)'ó', 'O', 24 };
+			yield return new object [] { 'Ó', true, false, false, 'O', 24, (KeyCode)'Ó' | KeyCode.ShiftMask, 'O', 24 };
+			yield return new object [] { 'ò', false, false, false, 'O', 24, (KeyCode)'ò', 'O', 24 };
+			yield return new object [] { 'Ò', true, false, false, 'O', 24, (KeyCode)'Ò' | KeyCode.ShiftMask, 'O', 24 };
+			yield return new object [] { 'ú', false, false, false, 'U', 22, (KeyCode)'ú', 'U', 22 };
+			yield return new object [] { 'Ú', true, false, false, 'U', 22, (KeyCode)'Ú' | KeyCode.ShiftMask, 'U', 22 };
+			yield return new object [] { 'ù', false, false, false, 'U', 22, (KeyCode)'ù', 'U', 22 };
+			yield return new object [] { 'Ù', true, false, false, 'U', 22, (KeyCode)'Ù' | KeyCode.ShiftMask, 'U', 22 };
+			yield return new object [] { 'ö', false, false, false, 'O', 24, (KeyCode)'ö', 'O', 24 };
+			yield return new object [] { 'Ö', true, false, false, 'O', 24, (KeyCode)'Ö' | KeyCode.ShiftMask, 'O', 24 };
+			yield return new object [] { '<', false, false, false, VK.OEM_102, 86, (KeyCode)'<', VK.OEM_102, 86 };
+			yield return new object [] { '>', true, false, false, VK.OEM_102, 86, (KeyCode)'>' | KeyCode.ShiftMask, VK.OEM_102, 86 };
+			yield return new object [] { '<', true, true, false, VK.OEM_102, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_102, 86 };
+			yield return new object [] { '<', true, true, true, VK.OEM_102, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_102, 86 };
+			yield return new object [] { 'ç', false, false, false, VK.OEM_3, 39, (KeyCode)'ç', VK.OEM_3, 39 };
+			yield return new object [] { 'Ç', true, false, false, VK.OEM_3, 39, (KeyCode)'Ç' | KeyCode.ShiftMask, VK.OEM_3, 39 };
+			yield return new object [] { 'ç', true, true, false, VK.OEM_3, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_3, 39 };
+			yield return new object [] { 'ç', true, true, true, VK.OEM_3, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_3, 39 };
+			yield return new object [] { '¨', false, true, true, VK.OEM_PLUS, 26, (KeyCode)'¨' | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '\0', false, false, false, VK.PRIOR, 73, KeyCode.PageUp, VK.PRIOR, 73 };
+			yield return new object [] { '\0', true, false, false, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask, VK.PRIOR, 73 };
+			yield return new object [] { '\0', true, true, false, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask, VK.PRIOR, 73 };
+			yield return new object [] { '\0', true, true, true, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.PRIOR, 73 };
+			yield return new object [] { '~', false, false, false, VK.SPACE, 57, (KeyCode)'~', VK.SPACE, 57 };
+			yield return new object [] { '^', false, false, false, VK.SPACE, 57, (KeyCode)'^', VK.SPACE, 57 };
 		}
+	}
+
+	[Theory]
+	[InlineData ('a', ConsoleKey.A, 'a', ConsoleKey.A)]
+	[InlineData ('A', ConsoleKey.A, 'A', ConsoleKey.A)]
+	[InlineData ('á', ConsoleKey.A, 'á', ConsoleKey.A)]
+	[InlineData ('Á', ConsoleKey.A, 'Á', ConsoleKey.A)]
+	[InlineData ('à', ConsoleKey.A, 'à', ConsoleKey.A)]
+	[InlineData ('À', ConsoleKey.A, 'À', ConsoleKey.A)]
+	[InlineData ('5', ConsoleKey.D5, '5', ConsoleKey.D5)]
+	[InlineData ('%', ConsoleKey.D5, '%', ConsoleKey.D5)]
+	[InlineData ('€', ConsoleKey.D5, '€', ConsoleKey.D5)]
+	[InlineData ('?', ConsoleKey.Oem4, '?', ConsoleKey.Oem4)]
+	[InlineData ('\'', ConsoleKey.Oem4, '\'', ConsoleKey.Oem4)]
+	[InlineData ('q', ConsoleKey.Q, 'q', ConsoleKey.Q)]
+	[InlineData ('\0', ConsoleKey.F2, '\0', ConsoleKey.F2)]
+	[InlineData ('英', ConsoleKey.None, '英', ConsoleKey.None)]
+	[InlineData ('´', ConsoleKey.None, '´', ConsoleKey.Oem1)]
+	[InlineData ('`', ConsoleKey.None, '`', ConsoleKey.Oem1)]
+	//[InlineData ('~', ConsoleKey.None, '~', ConsoleKey.Oem2)]
+	//[InlineData ('^', ConsoleKey.None, '^', ConsoleKey.Oem2)] // BUGBUG: '^' is Shift-6 on ENG keyboard,not Oem2.
+                                                                  // For the US standard keyboard, Oem2 is the /? key
+	public void EncodeKeyCharForVKPacket_DecodeVKPacketToKConsoleKeyInfo (char keyChar, ConsoleKey consoleKey, char expectedChar, ConsoleKey expectedConsoleKey)
+	{
+		var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, false, false, false);
+		var encodedKeyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
+		var encodedConsoleKeyInfo = new ConsoleKeyInfo (encodedKeyChar, ConsoleKey.Packet, false, false, false);
+		var decodedConsoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (encodedConsoleKeyInfo);
+		Assert.Equal (consoleKeyInfo.Key, consoleKey);
+		Assert.Equal (expectedConsoleKey, decodedConsoleKeyInfo.Key);
+		Assert.Equal (expectedChar, decodedConsoleKeyInfo.KeyChar);
+	}
 
-		IEnumerator IEnumerable.GetEnumerator ()
-		{
-			return GetEnumerator ();
+	
+	[Theory]
+	[InlineData ((KeyCode)'a', false, ConsoleKey.A, 'a')]
+	[InlineData (KeyCode.A | KeyCode.ShiftMask, false, ConsoleKey.A, 'A')]
+	[InlineData ((KeyCode)'á', false, ConsoleKey.A, 'á')]
+	[InlineData ((KeyCode)'Á' | KeyCode.ShiftMask, false, ConsoleKey.A, 'Á')]
+	[InlineData ((KeyCode)'à', false, ConsoleKey.A, 'à')]
+	[InlineData ((KeyCode)'À' | KeyCode.ShiftMask, false, ConsoleKey.A, 'À')]
+	[InlineData (KeyCode.D5, false, ConsoleKey.D5, '5')]
+	[InlineData ((KeyCode)'%' | KeyCode.ShiftMask, false, ConsoleKey.D5, '%')]
+	//[InlineData ((KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, false, ConsoleKey.D5, '€')] // Bogus test. This is not true on ENG keyboard layout.
+	[InlineData ((KeyCode)'?' | KeyCode.ShiftMask, false, ConsoleKey.Oem4, '?')]
+	[InlineData ((KeyCode)'\'', false, ConsoleKey.Oem4, '\'')]
+	[InlineData ((KeyCode)'q', false, ConsoleKey.Q, 'q')]
+	[InlineData (KeyCode.F2, true, ConsoleKey.F2, 'q')]
+	[InlineData ((KeyCode)'英', false, ConsoleKey.None, '英')]
+	[InlineData (KeyCode.Enter, true, ConsoleKey.Enter, '\r')]
+	public void MapKeyCodeToConsoleKey_GetKeyCharFromUnicodeChar (KeyCode keyCode, bool expectedIsConsoleKey, ConsoleKey expectedConsoleKey, char expectedConsoleKeyChar)
+	{
+		var modifiers = ConsoleKeyMapping.MapToConsoleModifiers (keyCode);
+		var consoleKey = ConsoleKeyMapping.MapKeyCodeToConsoleKey (keyCode, out bool isConsoleKey);
+		if (isConsoleKey) {
+			Assert.True (isConsoleKey == expectedIsConsoleKey);
+			Assert.Equal (expectedConsoleKey , (ConsoleKey)consoleKey);
+			Assert.Equal (expectedConsoleKeyChar, consoleKey);
+		} else {
+			var keyChar = ConsoleKeyMapping.GetKeyCharFromUnicodeChar (consoleKey, modifiers, out consoleKey, out _, isConsoleKey);
+			Assert.True (isConsoleKey == expectedIsConsoleKey);
+			Assert.Equal (expectedConsoleKey , (ConsoleKey)consoleKey);
+			Assert.Equal (expectedConsoleKeyChar, keyChar);
 		}
 	}
+#endif
+	
+	[Theory]
+	[InlineData ('a', ConsoleKey.A, false, false, false, (KeyCode)'a')]
+	[InlineData ('A', ConsoleKey.A, true, false, false, KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('á', ConsoleKey.A, false, false, false, (KeyCode)'á')]
+	[InlineData ('Á', ConsoleKey.A, true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)]
+	[InlineData ('à', ConsoleKey.A, false, false, false, (KeyCode)'à')]
+	[InlineData ('À', ConsoleKey.A, true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)]
+	[InlineData ('5', ConsoleKey.D5, false, false, false, KeyCode.D5)]
+	[InlineData ('%', ConsoleKey.D5, true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)]
+	[InlineData ('€', ConsoleKey.D5, false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)]
+	[InlineData ('?', ConsoleKey.Oem4, true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)]
+	[InlineData ('\'', ConsoleKey.Oem4, false, false, false, (KeyCode)'\'')]
+	[InlineData ('q', ConsoleKey.Q, false, false, false, (KeyCode)'q')]
+	[InlineData ('\0', ConsoleKey.F2, false, false, false, KeyCode.F2)]
+	[InlineData ('英', ConsoleKey.None, false, false, false, (KeyCode)'英')]
+	[InlineData ('\r', ConsoleKey.Enter, false, false, false, KeyCode.Enter)]
+	public void MapConsoleKeyInfoToKeyCode_Also_Return_Modifiers (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control, KeyCode expectedKeyCode)
+	{
+		var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
+		var keyCode = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
+
+		Assert.Equal (keyCode, expectedKeyCode);
+	}
+
+	[Theory]
+	[InlineData ('a', false, false, false, (KeyCode)'a')]
+	[InlineData ('A', true, false, false, KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('á', false, false, false, (KeyCode)'á')]
+	[InlineData ('Á', true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)]
+	[InlineData ('à', false, false, false, (KeyCode)'à')]
+	[InlineData ('À', true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)]
+	[InlineData ('5', false, false, false, KeyCode.D5)]
+	[InlineData ('%', true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)]
+	[InlineData ('€', false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)]
+	[InlineData ('?', true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)]
+	[InlineData ('\'', false, false, false, (KeyCode)'\'')]
+	[InlineData ('q', false, false, false, (KeyCode)'q')]
+	[InlineData ((uint)KeyCode.F2, false, false, false, KeyCode.F2)]
+	[InlineData ('英', false, false, false, (KeyCode)'英')]
+	[InlineData ('\r', false, false, false, KeyCode.Enter)]
+	[InlineData ('\n', false, false, false, (KeyCode)'\n')]
+	public void MapToKeyCodeModifiers_Tests (uint keyChar, bool shift, bool alt, bool control, KeyCode excpectedKeyCode)
+	{
+		var modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control);
+		KeyCode keyCode = (KeyCode)keyChar;
+		keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
+
+		Assert.Equal (keyCode, excpectedKeyCode);
+	}
+
+	[Theory]
+	[InlineData ('a', ConsoleKey.A, false, false, false, 30)]
+	[InlineData ('A', ConsoleKey.A, true, false, false, 30)]
+	[InlineData ('á', ConsoleKey.A, false, false, false, 30)]
+	[InlineData ('Á', ConsoleKey.A, true, false, false, 30)]
+	[InlineData ('à', ConsoleKey.A, false, false, false, 30)]
+	[InlineData ('À', ConsoleKey.A, true, false, false, 30)]
+	[InlineData ('0', ConsoleKey.D0, false, false, false, 11)]
+	[InlineData ('=', ConsoleKey.D0, true, false, false, 11)]
+	[InlineData ('}', ConsoleKey.D0, false, true, true, 11)]
+	[InlineData ('1', ConsoleKey.D1, false, false, false, 2)]
+	[InlineData ('!', ConsoleKey.D1, true, false, false, 2)]
+	[InlineData ('2', ConsoleKey.D2, false, false, false, 3)]
+	[InlineData ('"', ConsoleKey.D2, true, false, false, 3)]
+	[InlineData ('@', ConsoleKey.D2, false, true, true, 3)]
+	[InlineData ('3', ConsoleKey.D3, false, false, false, 4)]
+	[InlineData ('#', ConsoleKey.D3, true, false, false, 4)]
+	[InlineData ('£', ConsoleKey.D3, false, true, true, 4)]
+	[InlineData ('4', ConsoleKey.D4, false, false, false, 5)]
+	[InlineData ('$', ConsoleKey.D4, true, false, false, 5)]
+	[InlineData ('§', ConsoleKey.D4, false, true, true, 5)]
+	[InlineData ('5', ConsoleKey.D5, false, false, false, 6)]
+	[InlineData ('%', ConsoleKey.D5, true, false, false, 6)]
+	[InlineData ('€', ConsoleKey.D5, false, true, true, 6)]
+	[InlineData ('6', ConsoleKey.D6, false, false, false, 7)]
+	[InlineData ('&', ConsoleKey.D6, true, false, false, 7)]
+	[InlineData ('7', ConsoleKey.D7, false, false, false, 8)]
+	[InlineData ('/', ConsoleKey.D7, true, false, false, 8)]
+	[InlineData ('{', ConsoleKey.D7, false, true, true, 8)]
+	[InlineData ('8', ConsoleKey.D8, false, false, false, 9)]
+	[InlineData ('(', ConsoleKey.D8, true, false, false, 9)]
+	[InlineData ('[', ConsoleKey.D8, false, true, true, 9)]
+	[InlineData ('9', ConsoleKey.D9, false, false, false, 10)]
+	[InlineData (')', ConsoleKey.D9, true, false, false, 10)]
+	[InlineData (']', ConsoleKey.D9, false, true, true, 10)]
+	[InlineData ('´', ConsoleKey.Oem1, false, false, false, 27)]
+	[InlineData ('`', ConsoleKey.Oem1, true, false, false, 27)]
+	[InlineData ('~', ConsoleKey.Oem2, false, false, false, 43)]
+	[InlineData ('^', ConsoleKey.Oem2, true, false, false, 43)]
+	[InlineData ('ç', ConsoleKey.Oem3, false, false, false, 39)]
+	[InlineData ('Ç', ConsoleKey.Oem3, true, false, false, 39)]
+	[InlineData ('\'', ConsoleKey.Oem4, false, false, false, 12)]
+	[InlineData ('?', ConsoleKey.Oem4, true, false, false, 12)]
+	[InlineData ('\\', ConsoleKey.Oem5, false, true, true, 41)]
+	[InlineData ('|', ConsoleKey.Oem5, true, false, false, 41)]
+	[InlineData ('«', ConsoleKey.Oem6, false, true, true, 13)]
+	[InlineData ('»', ConsoleKey.Oem6, true, false, false, 13)]
+	[InlineData ('º', ConsoleKey.Oem7, false, true, true, 40)]
+	[InlineData ('ª', ConsoleKey.Oem7, true, false, false, 40)]
+	[InlineData ('+', ConsoleKey.OemPlus, false, true, true, 26)]
+	[InlineData ('*', ConsoleKey.OemPlus, true, false, false, 26)]
+	[InlineData ('¨', ConsoleKey.OemPlus, false, true, true, 26)]
+	[InlineData (',', ConsoleKey.OemComma, false, true, true, 51)]
+	[InlineData (';', ConsoleKey.OemComma, true, false, false, 51)]
+	[InlineData ('.', ConsoleKey.OemPeriod, false, true, true, 52)]
+	[InlineData (':', ConsoleKey.OemPeriod, true, false, false, 52)]
+	[InlineData ('-', ConsoleKey.OemMinus, false, true, true, 53)]
+	[InlineData ('_', ConsoleKey.OemMinus, true, false, false, 53)]
+	[InlineData ('q', ConsoleKey.Q, false, false, false, 16)]
+	[InlineData ('\0', ConsoleKey.F2, false, false, false, 60)]
+	[InlineData ('英', ConsoleKey.None, false, false, false, 0)]
+	[InlineData ('英', ConsoleKey.None, true, false, false, 0)]
+	public void GetScanCodeFromConsoleKeyInfo_Tests (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control, uint expectedScanCode)
+	{
+		var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
+		var scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (consoleKeyInfo);
+
+		Assert.Equal (scanCode, expectedScanCode);
+	}
+
+	[Theory]
+	[InlineData ('a', 'A', KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('z', 'Z', KeyCode.Z | KeyCode.ShiftMask)]
+	[InlineData ('á', 'Á', (KeyCode)'Á' | KeyCode.ShiftMask)]
+	[InlineData ('à', 'À', (KeyCode)'À' | KeyCode.ShiftMask)]
+	[InlineData ('ý', 'Ý', (KeyCode)'Ý' | KeyCode.ShiftMask)]
+	[InlineData ('1', '!', (KeyCode)'!' | KeyCode.ShiftMask)]
+	[InlineData ('2', '"', (KeyCode)'"' | KeyCode.ShiftMask)]
+	[InlineData ('3', '#', (KeyCode)'#' | KeyCode.ShiftMask)]
+	[InlineData ('4', '$', (KeyCode)'$' | KeyCode.ShiftMask)]
+	[InlineData ('5', '%', (KeyCode)'%' | KeyCode.ShiftMask)]
+	[InlineData ('6', '&', (KeyCode)'&' | KeyCode.ShiftMask)]
+	[InlineData ('7', '/', (KeyCode)'/' | KeyCode.ShiftMask)]
+	[InlineData ('8', '(', (KeyCode)'(' | KeyCode.ShiftMask)]
+	[InlineData ('9', ')', (KeyCode)')' | KeyCode.ShiftMask)]
+	[InlineData ('0', '=', (KeyCode)'=' | KeyCode.ShiftMask)]
+	[InlineData ('\\', '|', (KeyCode)'|' | KeyCode.ShiftMask)]
+	[InlineData ('\'', '?', (KeyCode)'?' | KeyCode.ShiftMask)]
+	[InlineData ('«', '»', (KeyCode)'»' | KeyCode.ShiftMask)]
+	[InlineData ('+', '*', (KeyCode)'*' | KeyCode.ShiftMask)]
+	[InlineData ('´', '`', (KeyCode)'`' | KeyCode.ShiftMask)]
+	[InlineData ('º', 'ª', (KeyCode)'ª' | KeyCode.ShiftMask)]
+	[InlineData ('~', '^', (KeyCode)'^' | KeyCode.ShiftMask)]
+	[InlineData ('<', '>', (KeyCode)'>' | KeyCode.ShiftMask)]
+	[InlineData (',', ';', (KeyCode)';' | KeyCode.ShiftMask)]
+	[InlineData ('.', ':', (KeyCode)':' | KeyCode.ShiftMask)]
+	[InlineData ('-', '_', (KeyCode)'_' | KeyCode.ShiftMask)]
+	public void GetKeyChar_Shifted_Char_From_UnShifted_Char (char unicodeChar, char expectedKeyChar, KeyCode excpectedKeyCode)
+	{
+		var modifiers = ConsoleKeyMapping.GetModifiers (true, false, false);
+		var keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers);
+		Assert.Equal (keyChar, expectedKeyChar);
+
+		KeyCode keyCode = (KeyCode)keyChar;
+		keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
+
+		Assert.Equal (keyCode, excpectedKeyCode);
+	}
+
+	[Theory]
+	[InlineData ('A', 'a', (KeyCode)'a')]
+	[InlineData ('Z', 'z', (KeyCode)'z')]
+	[InlineData ('Á', 'á', (KeyCode)'á')]
+	[InlineData ('À', 'à', (KeyCode)'à')]
+	[InlineData ('Ý', 'ý', (KeyCode)'ý')]
+	[InlineData ('!', '1', KeyCode.D1)]
+	[InlineData ('"', '2', KeyCode.D2)]
+	[InlineData ('#', '3', KeyCode.D3)]
+	[InlineData ('$', '4', KeyCode.D4)]
+	[InlineData ('%', '5', KeyCode.D5)]
+	[InlineData ('&', '6', KeyCode.D6)]
+	[InlineData ('/', '7', KeyCode.D7)]
+	[InlineData ('(', '8', KeyCode.D8)]
+	[InlineData (')', '9', KeyCode.D9)]
+	[InlineData ('=', '0', KeyCode.D0)]
+	[InlineData ('|', '\\', (KeyCode)'\\')]
+	[InlineData ('?', '\'', (KeyCode)'\'')]
+	[InlineData ('»', '«', (KeyCode)'«')]
+	[InlineData ('*', '+', (KeyCode)'+')]
+	[InlineData ('`', '´', (KeyCode)'´')]
+	[InlineData ('ª', 'º', (KeyCode)'º')]
+	[InlineData ('^', '~', (KeyCode)'~')]
+	[InlineData ('>', '<', (KeyCode)'<')]
+	[InlineData (';', ',', (KeyCode)',')]
+	[InlineData (':', '.', (KeyCode)'.')]
+	[InlineData ('_', '-', (KeyCode)'-')]
+	public void GetKeyChar_UnShifted_Char_From_Shifted_Char (char unicodeChar, char expectedKeyChar, KeyCode excpectedKeyCode)
+	{
+		var modifiers = ConsoleKeyMapping.GetModifiers (false, false, false);
+		var keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers);
+		Assert.Equal (keyChar, expectedKeyChar);
+
+		KeyCode keyCode = (KeyCode)keyChar;
+		keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
+
+		Assert.Equal (keyCode, excpectedKeyCode);
+	}
 }

+ 81 - 31
UnitTests/Input/KeyTests.cs

@@ -35,9 +35,10 @@ public class KeyTests {
 	[InlineData (' ', KeyCode.Space)]
 	[InlineData ('1', KeyCode.D1)]
 	[InlineData ('!', (KeyCode)'!')]
-	[InlineData ('\n', KeyCode.Enter)]
+	[InlineData ('\r', KeyCode.Enter)]
 	[InlineData ('\t', KeyCode.Tab)]
 	[InlineData ('\r', (KeyCode)13)]
+	[InlineData ('\n', (KeyCode)10)]
 	[InlineData ('ó', (KeyCode)'ó')]
 	[InlineData ('Ó', (KeyCode)'Ó')]
 	[InlineData ('❿', (KeyCode)'❿')]
@@ -78,6 +79,10 @@ public class KeyTests {
 	[InlineData ("Ctrl+Alt+Tab", KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask)]
 	[InlineData ("", KeyCode.Null)]
 	[InlineData (" ", KeyCode.Space)]
+	[InlineData ("Space", KeyCode.Space)]
+	[InlineData ("Shift+Space", KeyCode.Space | KeyCode.ShiftMask)]
+	[InlineData ("Ctrl+Space", KeyCode.Space | KeyCode.CtrlMask)]
+	[InlineData ("Alt+Space", KeyCode.Space | KeyCode.AltMask)]
 	[InlineData ("Shift+ ", KeyCode.Space | KeyCode.ShiftMask)]
 	[InlineData ("Ctrl+ ", KeyCode.Space | KeyCode.CtrlMask)]
 	[InlineData ("Alt+ ", KeyCode.Space | KeyCode.AltMask)]
@@ -87,8 +92,8 @@ public class KeyTests {
 	[InlineData ("D0", KeyCode.D0)]
 	[InlineData ("65", KeyCode.A | KeyCode.ShiftMask)]
 	[InlineData ("97", KeyCode.A)]
-	[InlineData ("Shift", KeyCode.ShiftKey)]
-	[InlineData ("Ctrl", KeyCode.CtrlKey)]
+	[InlineData ("Shift", KeyCode.ShiftMask)]
+	[InlineData ("Ctrl", KeyCode.CtrlMask)]
 	[InlineData ("Ctrl-A", KeyCode.A | KeyCode.CtrlMask)]
 	[InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)]
 	[InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)]
@@ -114,9 +119,10 @@ public class KeyTests {
 	[InlineData (' ', KeyCode.Space)]
 	[InlineData ('1', KeyCode.D1)]
 	[InlineData ('!', (KeyCode)'!')]
-	[InlineData ('\n', KeyCode.Enter)]
+	[InlineData ('\r', KeyCode.Enter)]
 	[InlineData ('\t', KeyCode.Tab)]
 	[InlineData ('\r', (KeyCode)13)]
+	[InlineData ('\n', (KeyCode)10)]
 	[InlineData ('ó', (KeyCode)'ó')]
 	[InlineData ('Ó', (KeyCode)'Ó')]
 	[InlineData ('❿', (KeyCode)'❿')]
@@ -157,7 +163,7 @@ public class KeyTests {
 	[InlineData (KeyCode.CtrlMask, false)]
 	[InlineData (KeyCode.AltMask, false)]
 	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask, false)]
-	public void IsValid (Key key, bool expected) => Assert.Equal (expected, key.IsValid);
+	public void IsValid (KeyCode key, bool expected) => Assert.Equal (expected, ((Key)key).IsValid);
 
 	[Fact]
 	public void HandledProperty_ShouldBeFalseByDefault ()
@@ -173,15 +179,15 @@ public class KeyTests {
 	[InlineData (KeyCode.A | KeyCode.ShiftMask, KeyCode.A | KeyCode.ShiftMask)]
 	[InlineData (KeyCode.Z, (KeyCode)'z')]
 	[InlineData (KeyCode.Space, KeyCode.Space)]
-	public void Cast_KeyCode_To_Key (KeyCode cdk, Key expected)
+	public void Cast_KeyCode_To_Key (KeyCode cdk, KeyCode expected)
 	{
 		// explicit
 		var key = (Key)cdk;
-		Assert.Equal (expected.ToString (), key.ToString ());
+		Assert.Equal (((Key)expected).ToString (), key.ToString ());
 
 		// implicit
 		key = cdk;
-		Assert.Equal (expected.ToString (), key.ToString ());
+		Assert.Equal (((Key)expected).ToString (), key.ToString ());
 	}
 
 	[Fact]
@@ -256,7 +262,7 @@ public class KeyTests {
 	[InlineData (KeyCode.F1, '\0')]
 	[InlineData (KeyCode.ShiftMask | KeyCode.F1, '\0')]
 	[InlineData (KeyCode.CtrlMask | KeyCode.F1, '\0')]
-	[InlineData (KeyCode.Enter, '\n')]
+	[InlineData (KeyCode.Enter, '\r')]
 	[InlineData (KeyCode.Tab, '\t')]
 	[InlineData (KeyCode.Esc, 0x1b)]
 	[InlineData (KeyCode.Space, ' ')]
@@ -265,10 +271,10 @@ public class KeyTests {
 	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, '\0')]
 	[InlineData (KeyCode.CharMask, '\0')]
 	[InlineData (KeyCode.SpecialMask, '\0')]
-	public void AsRune_ShouldReturnCorrectIntValue (KeyCode key, Rune expected)
+	public void AsRune_ShouldReturnCorrectIntValue (KeyCode key, uint expected)
 	{
 		var eventArgs = new Key (key);
-		Assert.Equal (expected, eventArgs.AsRune);
+		Assert.Equal ((Rune)expected, eventArgs.AsRune);
 	}
 
 	[Theory]
@@ -316,7 +322,7 @@ public class KeyTests {
 	}
 
 	// TODO: Create equality operator for KeyCode
-	//Assert.Equal (KeyCode.Delete, Key.Delete);
+	//Assert.Equal (KeyCode.DeleteChar, Key.Delete);
 
 	// Similar tests for IsShift and IsCtrl
 	[Fact]
@@ -333,11 +339,11 @@ public class KeyTests {
 
 	[Theory]
 	[InlineData ((KeyCode)'☑', "☑")]
-	//[InlineData ((ConsoleDriverKey)'英', "英")]
-	//[InlineData ((ConsoleDriverKey)'{', "{")]
+	[InlineData ((KeyCode)'英', "英")]
+	[InlineData ((KeyCode)'{', "{")]
 	[InlineData ((KeyCode)'\'', "\'")]
 	[InlineData ((KeyCode)'ó', "ó")]
-	[InlineData ((KeyCode)'ó' | KeyCode.ShiftMask, "Shift+ó")] // is this right???
+	[InlineData ((KeyCode)'Ó' | KeyCode.ShiftMask, "Shift+Ó")] // TODO: This is not correct, it should be Shift+ó or just Ó
 	[InlineData ((KeyCode)'Ó', "Ó")]
 	[InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Shift+ç")]
 	[InlineData ((KeyCode)'a', "a")] // 97 or Key.Space | Key.A
@@ -397,19 +403,33 @@ public class KeyTests {
 	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CursorUp, "Alt+Shift+CursorUp")]
 	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.CursorUp, "Ctrl+Alt+CursorUp")]
 	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp, "Ctrl+Alt+Shift+CursorUp")]
+	[InlineData (KeyCode.Space, "Space")]
 	[InlineData (KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltKey, "AltKey")]
-	[InlineData (KeyCode.CtrlKey, "CtrlKey")]
-	[InlineData (KeyCode.ShiftKey, "ShiftKey")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.Null, "Shift")]
+	[InlineData (KeyCode.CtrlMask | KeyCode.Null, "Ctrl")]
+	[InlineData (KeyCode.AltMask | KeyCode.Null, "Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.Null, "Ctrl+Shift")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Null, "Alt+Shift")]
+	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.Null, "Ctrl+Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, "Ctrl+Alt+Shift")]
+#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to
+	[InlineData (KeyCode.ShiftMask, "Shift")]
+	[InlineData (KeyCode.CtrlMask, "Ctrl")]
+	[InlineData (KeyCode.AltMask, "Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, "Ctrl+Shift")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask, "Alt+Shift")]
+	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask, "Ctrl+Alt+Shift")]
+#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to
+	[InlineData (KeyCode.AltMask, "Alt")]
+	[InlineData (KeyCode.CtrlMask, "Ctrl")]
+	[InlineData (KeyCode.ShiftMask, "Shift")]
 	[InlineData (KeyCode.CharMask, "CharMask")]
 	[InlineData (KeyCode.SpecialMask, "Ctrl+Alt+Shift")]
+	[InlineData ((KeyCode)'+', "+")]
+	[InlineData ((KeyCode)'+' | KeyCode.ShiftMask, "Shift++")]
+	[InlineData ((KeyCode)'+' | KeyCode.CtrlMask, "Ctrl++")] 
+	[InlineData ((KeyCode)'+' | KeyCode.ShiftMask | KeyCode.CtrlMask, "Ctrl+Shift++")]
 	public void ToString_ShouldReturnFormattedString (KeyCode key, string expected) => Assert.Equal (expected, Key.ToString (key));
 
 	// TryParse
@@ -436,6 +456,10 @@ public class KeyTests {
 	[InlineData ("Ctrl+Alt+Tab", KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask)]
 	[InlineData ("", KeyCode.Null)]
 	[InlineData (" ", KeyCode.Space)]
+	[InlineData ("Space", KeyCode.Space)]
+	[InlineData ("Shift+Space", KeyCode.Space | KeyCode.ShiftMask)]
+	[InlineData ("Ctrl+Space", KeyCode.Space | KeyCode.CtrlMask)]
+	[InlineData ("Alt+Space", KeyCode.Space | KeyCode.AltMask)]
 	[InlineData ("Shift+ ", KeyCode.Space | KeyCode.ShiftMask)]
 	[InlineData ("Ctrl+ ", KeyCode.Space | KeyCode.CtrlMask)]
 	[InlineData ("Alt+ ", KeyCode.Space | KeyCode.AltMask)]
@@ -445,17 +469,17 @@ public class KeyTests {
 	[InlineData ("D0", KeyCode.D0)]
 	[InlineData ("65", KeyCode.A | KeyCode.ShiftMask)]
 	[InlineData ("97", KeyCode.A)]
-	[InlineData ("Shift", KeyCode.ShiftKey)]
-	[InlineData ("Ctrl", KeyCode.CtrlKey)]
+	[InlineData ("Shift", KeyCode.ShiftMask)]
+	[InlineData ("Ctrl", KeyCode.CtrlMask)]
 	[InlineData ("Ctrl-A", KeyCode.A | KeyCode.CtrlMask)]
 	[InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)]
 	[InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)]
 	[InlineData ("Alt-A-Ctrl", KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)]
-	public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, Key expected)
+	public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, KeyCode expected)
 	{
-		Key key;
-		Assert.True (Key.TryParse (keyString, out key));
-		Assert.Equal (((Key)expected).ToString (), key.ToString ());
+		Assert.True (Key.TryParse (keyString, out Key key));
+		Key expectedKey = (Key)expected;
+		Assert.Equal (expectedKey.ToString (), key.ToString ());
 	}
 
 	[Theory]
@@ -471,4 +495,30 @@ public class KeyTests {
 	[InlineData ("0x99")]
 	[InlineData ("Ctrl-Ctrl")]
 	public void TryParse_ShouldReturnFalse_On_InvalidKey (string keyString) => Assert.False (Key.TryParse (keyString, out var _));
+	
+	[Theory]
+	[InlineData (KeyCode.ShiftMask, true, false, false)]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask, true, true, false)]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, true, true, true)]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, true, false, true)]
+	[InlineData (KeyCode.AltMask, false, true, false)]
+	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask, false, true, true)]
+	[InlineData (KeyCode.CtrlMask, false, false, true)]
+	public void IsShift_IsAlt_IsCtrl (KeyCode keyCode, bool isShift, bool isAlt, bool isCtrl)
+	{
+		Assert.Equal (((Key)keyCode).IsShift, isShift);
+		Assert.Equal (((Key)keyCode).IsAlt, isAlt);
+		Assert.Equal (((Key)keyCode).IsCtrl, isCtrl);
+	}
+
+	[Theory]
+	[InlineData (KeyCode.A, KeyCode.A)]
+	[InlineData (KeyCode.F1, KeyCode.F1)]
+	public void Casting_Between_Key_And_KeyCode (KeyCode keyCode, KeyCode key)
+	{
+		Assert.Equal (keyCode, (Key)key);
+		Assert.NotEqual (keyCode, ((Key)key).WithShift);
+		Assert.Equal ((uint)keyCode, (uint)(Key)key);
+		Assert.NotEqual ((uint)keyCode, (uint)((Key)key).WithShift);
+	}
 }

+ 17 - 18
UnitTests/Text/CollectionNavigatorTests.cs

@@ -383,22 +383,21 @@ public class CollectionNavigatorTests {
 
 		Assert.Equal (-1, current = n.GetNextMatchingItem (current, "x", true));
 	}
-
-	[Fact]
-	public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys ()
-	{
-		// test all Keys
-		foreach (KeyCode key in Enum.GetValues (typeof (KeyCode))) {
-			var ke = new Key (key);
-			_output.WriteLine ($"Testing {key}");
-			if (key == KeyCode.AltMask || key == KeyCode.CtrlMask || key == KeyCode.SpecialMask) {
-				Assert.False (CollectionNavigator.IsCompatibleKey (ke));
-			} else {
-				Assert.True (CollectionNavigator.IsCompatibleKey (ke));
-			}
-		}
-
-		// test Capslock, Numlock and Scrolllock
-		Assert.True (CollectionNavigator.IsCompatibleKey (new (KeyCode.Null)));
-	}
+	
+	[Theory]
+	[InlineData (KeyCode.A, true)]
+	[InlineData (KeyCode.Z, true)]
+	[InlineData (KeyCode.D0, true)]
+	[InlineData (KeyCode.A | KeyCode.ShiftMask, true)]
+	[InlineData (KeyCode.Z | KeyCode.ShiftMask, true)]
+	[InlineData (KeyCode.Space, true)]
+
+	[InlineData (KeyCode.Z | KeyCode.CtrlMask, false)]
+	[InlineData (KeyCode.Z | KeyCode.AltMask, false)]
+	[InlineData (KeyCode.F1, false)]
+	[InlineData (KeyCode.Delete, false)]
+	[InlineData (KeyCode.Delete, false)]
+	[InlineData (KeyCode.Esc, false)]
+	[InlineData (KeyCode.ShiftMask, false)]
+	public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys (KeyCode keyCode, bool compatible) => Assert.Equal (compatible, CollectionNavigatorBase.IsCompatibleKey (keyCode));
 }

+ 12 - 12
UnitTests/Text/TextFormatterTests.cs

@@ -249,7 +249,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("After K_", false, -1, KeyCode.Null, true)]
 		[InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)]
 		[InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false)
+		public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
 		{
 			Rune hotKeySpecifier = (Rune)'_';
 
@@ -261,7 +261,7 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 
 		[Theory]
@@ -277,7 +277,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("After k_", false, -1, KeyCode.Null, true)]
 		[InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)]
 		[InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false)
+		public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
 		{
 			Rune hotKeySpecifier = (Rune)'_';
 
@@ -289,7 +289,7 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 
 		[Theory]
@@ -303,7 +303,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("Last _1", true, 5, (KeyCode)'1', true)]
 		[InlineData ("After 1_", false, -1, KeyCode.Null, true)]
 		[InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)]
-		public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false)
+		public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
 		{
 			Rune hotKeySpecifier = (Rune)'_';
 
@@ -315,7 +315,7 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 
 		[Theory]
@@ -324,7 +324,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("last K", true, 5, (KeyCode)'K')]
 		[InlineData ("multiple K and R", true, 9, (KeyCode)'K')]
 		[InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
-		public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey)
+		public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey)
 		{
 			var supportFirstUpperCase = true;
 
@@ -338,25 +338,25 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 		
 		[Theory]
-		//[InlineData ("_\"k before", false, Key.Null)] // BUGBUG: Not sure why this fails
+		[InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char
 		[InlineData ("\"_k before", true, KeyCode.K)]
 		[InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')]
 		[InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')]
-		//[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (Key)'_')] // BUGBUG: Not sure why this fails
+		[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second
 		[InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
 		[InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
 		[InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к)
-		public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, Key expected)
+		public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected)
 		{
 			var hotKeySpecifier = (Rune)'_';
 
 			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out int _, out var hotKey);
 			Assert.Equal (found, result);
-			Assert.Equal (expected, hotKey);
+			Assert.Equal ((Key)expected, hotKey);
 		}
 
 		[Theory]

+ 12 - 12
UnitTests/View/HotKeyTests.cs

@@ -51,37 +51,37 @@ public class HotKeyTests {
 	[InlineData ((KeyCode)'х')]  // Cyrillic x
 	[InlineData ((KeyCode)'你')] // Chinese ni
 	[InlineData ((KeyCode)'ö')] // German o umlaut
-	public void Set_SetsKeyBindings (Key key)
+	public void Set_SetsKeyBindings (KeyCode key)
 	{
 		var view = new View ();
-		view.HotKey = key;
+		view.HotKey = (Key)key;
 		Assert.Equal (string.Empty, view.Title);
-		Assert.Equal (key, view.HotKey);
+		Assert.Equal ((Key)key, view.HotKey);
 
 		// Verify key bindings were set
 
 		// As passed
-		var commands = view.KeyBindings.GetCommands (key);
+		var commands = view.KeyBindings.GetCommands ((Key)key);
 		Assert.Contains (Command.Accept, commands);
 
-		var baseKey = key.NoShift;
+		var baseKey = ((Key)key).NoShift;
 		// If A...Z, with and without shift
 		if (baseKey.IsKeyCodeAtoZ) {
-			commands = view.KeyBindings.GetCommands (key.WithShift);
+			commands = view.KeyBindings.GetCommands (((Key)key).WithShift);
 			Assert.Contains (Command.Accept, commands);
-			commands = view.KeyBindings.GetCommands (key.NoShift);
+			commands = view.KeyBindings.GetCommands (((Key)key).NoShift);
 			Assert.Contains (Command.Accept, commands);
-			commands = view.KeyBindings.GetCommands (key.WithAlt);
+			commands = view.KeyBindings.GetCommands (((Key)key).WithAlt);
 			Assert.Contains (Command.Accept, commands);
-			commands = view.KeyBindings.GetCommands (key.NoShift.WithAlt);
+			commands = view.KeyBindings.GetCommands (((Key)key).NoShift.WithAlt);
 			Assert.Contains (Command.Accept, commands);
 		} else {
 			// Non A..Z keys should not have shift bindings
-			if (key.IsShift) {
-				commands = view.KeyBindings.GetCommands (key.NoShift);
+			if (((Key)key).IsShift) {
+				commands = view.KeyBindings.GetCommands (((Key)key).NoShift);
 				Assert.Empty (commands);
 			} else {
-				commands = view.KeyBindings.GetCommands (key.WithShift);
+				commands = view.KeyBindings.GetCommands (((Key)key).WithShift);
 				Assert.Empty (commands);
 			}
 		}

+ 1 - 1
UnitTests/Views/DateFieldTests.cs

@@ -64,7 +64,7 @@ namespace Terminal.Gui.ViewsTests {
 			CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
 			DateField df = new DateField (DateTime.Parse ("12/12/1971"));
 			df.ReadOnly = true;
-			Assert.True (df.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.Delete)));
 			Assert.Equal (" 12/12/1971", df.Text);
 			df.ReadOnly = false;
 			Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask)));

+ 5 - 5
UnitTests/Views/TextFieldTests.cs

@@ -950,10 +950,10 @@ public class TextFieldTests {
 		Assert.Equal (15, tf.CursorPosition);
 		Assert.False (tf.ReadOnly);
 
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("This is a test.", tf.Text);
 		tf.CursorPosition = 0;
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("his is a test.", tf.Text);
 		tf.ReadOnly = true;
 		Assert.True (tf.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask)));
@@ -1114,7 +1114,7 @@ public class TextFieldTests {
 		Assert.Equal ("is is a t", tf.Text);
 		Assert.Equal (9, tf.CursorPosition);
 		Assert.True (tf.Used);
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.InsertChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Insert)));
 		Assert.Equal ("is is a t", tf.Text);
 		Assert.Equal (9, tf.CursorPosition);
 		Assert.False (tf.Used);
@@ -1137,7 +1137,7 @@ public class TextFieldTests {
 		Assert.Equal ("is is a", Clipboard.Contents);
 		tf.Text = "TAB to jump between text fields.";
 		Assert.Equal (0, tf.CursorPosition);
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("to jump between text fields.", tf.Text);
 		tf.CursorPosition = tf.Text.Length;
 		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Backspace | KeyCode.CtrlMask)));
@@ -1212,7 +1212,7 @@ public class TextFieldTests {
 		tf.CursorPosition = 2;
 		Assert.Equal (1, tf.SelectedLength);
 		Assert.Equal ("1", tf.SelectedText);
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("-", newText);
 		Assert.Equal ("-1", oldText);
 		Assert.Equal ("-", tf.Text);

+ 19 - 20
UnitTests/Views/TextViewTests.cs

@@ -1007,7 +1007,7 @@ public class TextViewTests {
 				Assert.Equal ("This is the first line.", Clipboard.Contents);
 				break;
 			case 1:
-				_textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask));
+				_textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask));
 				Assert.Equal (0, _textView.CursorPosition.X);
 				Assert.Equal (0, _textView.CursorPosition.Y);
 				Assert.Equal ("This is the second line.", _textView.Text);
@@ -1085,7 +1085,7 @@ public class TextViewTests {
 		bool iterationsFinished = false;
 
 		while (!iterationsFinished) {
-			_textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask));
+			_textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask));
 			switch (iteration) {
 			case 0:
 				Assert.Equal (0, _textView.CursorPosition.X);
@@ -1180,7 +1180,7 @@ public class TextViewTests {
 		bool iterationsFinished = false;
 
 		while (!iterationsFinished) {
-			_textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask));
+			_textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask));
 			switch (iteration) {
 			case 0:
 				Assert.Equal (0, _textView.CursorPosition.X);
@@ -2500,7 +2500,6 @@ line.
 	[Theory]
 	[TextViewTestsAutoInitShutdown]
 	[InlineData (KeyCode.Delete)]
-	[InlineData (KeyCode.DeleteChar)]
 	public void WordWrap_Draw_Typed_Keys_After_Text_Is_Deleted (KeyCode del)
 	{
 		Application.Top.Add (_textView);
@@ -2744,13 +2743,13 @@ Line 2.", _output);
 		Assert.Equal (0, tv.SelectedLength);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.True (tv.Selecting);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.False (tv.Selecting);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ($"his is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
@@ -2762,7 +2761,7 @@ Line 2.", _output);
 		Assert.True (tv.NewKeyDownEvent (new (KeyCode.End)));
 		Assert.Equal ($"is is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (21, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Backspace)));
 		Assert.Equal ($"is is the first line{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (20, 0), tv.CursorPosition);
 		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Backspace)));
@@ -2808,7 +2807,7 @@ Line 2.", _output);
 		Assert.False (tv.Selecting);
 		Assert.Equal ("is is the first lin", Clipboard.Contents);
 		tv.CursorPosition = Point.Empty;
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask)));
 		Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
@@ -2964,7 +2963,7 @@ Line 2.", _output);
 		Assert.Equal (0, tv.SelectedLength);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.False (tv.Selecting);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ($"This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
@@ -3020,7 +3019,7 @@ Line 2.", _output);
 		Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ", tv.SelectedText);
 		Assert.True (tv.Selecting);
 		Assert.True (tv.Used);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.InsertChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Insert)));
 		Assert.False (tv.Used);
 		Assert.True (tv.AllowsTab);
 		Assert.Equal (new Point (18, 2), tv.CursorPosition);
@@ -4691,7 +4690,7 @@ Line 2.", _output);
 		Assert.False (tv.IsDirty);
 		Assert.False (tv.HasHistoryChanges);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("", tv.Text);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.Equal (1, tv.Lines);
@@ -5182,28 +5181,28 @@ Line 2.", _output);
 		var text = "First line.\nSecond line.";
 		var tv = new TextView () { Text = text };
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.Equal (2, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
 		Assert.Equal (2, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("Second line.", tv.Text);
 		Assert.Equal (1, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("line.", tv.Text);
 		Assert.Equal (1, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("", tv.Text);
 		Assert.Equal (1, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
@@ -6397,7 +6396,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (2, 0);
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -6407,7 +6406,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (22, 0);
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -6454,7 +6453,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (2, 0);
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -6464,7 +6463,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (22, 0);
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"

+ 1 - 1
UnitTests/Views/TimeFieldTests.cs

@@ -61,7 +61,7 @@ namespace Terminal.Gui.ViewsTests {
 		{
 			TimeField tf = new TimeField (TimeSpan.Parse ("12:12:19"));
 			tf.ReadOnly = true;
-			Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+			Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 			Assert.Equal (" 12:12:19", tf.Text);
 			tf.ReadOnly = false;
 			Assert.True (tf.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask)));

Vissa filer visades inte eftersom för många filer har ändrats