mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-27 00:07:58 +01:00
387 lines
12 KiB
C#
387 lines
12 KiB
C#
namespace Terminal.Gui.Drivers;
|
|
|
|
/// <summary>Helper class to handle mapping between <see cref="KeyCode"/> and <see cref="ConsoleKeyInfo"/>.</summary>
|
|
public static class ConsoleKeyMapping
|
|
{
|
|
/// <summary>
|
|
/// Gets a <see cref="ConsoleKeyInfo"/> from a <see cref="KeyCode"/>.
|
|
/// </summary>
|
|
/// <param name="key">The key code to convert.</param>
|
|
/// <returns>A ConsoleKeyInfo representing the key.</returns>
|
|
/// <remarks>
|
|
/// This method is primarily used for test simulation via <see cref="IKeyConverter{T}.ToKeyInfo"/>.
|
|
/// It produces a keyboard-layout-agnostic "best effort" ConsoleKeyInfo suitable for testing.
|
|
/// For shifted characters (e.g., Shift+2), the character returned is US keyboard layout (Shift+2 = '@').
|
|
/// This is acceptable for test simulation but may not match the user's actual keyboard layout.
|
|
/// </remarks>
|
|
public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
|
|
{
|
|
ConsoleModifiers modifiers = MapToConsoleModifiers (key);
|
|
KeyCode keyWithoutModifiers = key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
|
|
|
|
// Map to ConsoleKey enum
|
|
(ConsoleKey consoleKey, char keyChar) = MapToConsoleKeyAndChar (keyWithoutModifiers, modifiers);
|
|
|
|
return new (
|
|
keyChar,
|
|
consoleKey,
|
|
modifiers.HasFlag (ConsoleModifiers.Shift),
|
|
modifiers.HasFlag (ConsoleModifiers.Alt),
|
|
modifiers.HasFlag (ConsoleModifiers.Control)
|
|
);
|
|
}
|
|
|
|
/// <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>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.Clear:
|
|
keyCode = KeyCode.Clear;
|
|
|
|
break;
|
|
case ConsoleKey.Tab:
|
|
keyCode = KeyCode.Tab;
|
|
|
|
break;
|
|
case ConsoleKey.Spacebar:
|
|
keyCode = KeyCode.Space;
|
|
|
|
break;
|
|
case ConsoleKey.Backspace:
|
|
keyCode = KeyCode.Backspace;
|
|
|
|
break;
|
|
default:
|
|
if ((int)consoleKeyInfo.KeyChar is >= 1 and <= 26)
|
|
{
|
|
keyCode = (KeyCode)(consoleKeyInfo.KeyChar + 64);
|
|
}
|
|
else
|
|
{
|
|
keyCode = (KeyCode)consoleKeyInfo.KeyChar;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode);
|
|
|
|
return keyCode;
|
|
}
|
|
|
|
/// <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 ();
|
|
|
|
// BUGFIX: Only set Shift if ShiftMask is explicitly set.
|
|
// KeyCode.A-Z (65-90) represent UNSHIFTED keys, even though their numeric values
|
|
// match uppercase ASCII characters. Do NOT check char.IsUpper!
|
|
if (key.HasFlag (KeyCode.ShiftMask))
|
|
{
|
|
modifiers |= ConsoleModifiers.Shift;
|
|
}
|
|
|
|
if (key.HasFlag (KeyCode.AltMask))
|
|
{
|
|
modifiers |= ConsoleModifiers.Alt;
|
|
}
|
|
|
|
if (key.HasFlag (KeyCode.CtrlMask))
|
|
{
|
|
modifiers |= ConsoleModifiers.Control;
|
|
}
|
|
|
|
return modifiers;
|
|
}
|
|
|
|
/// <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>
|
|
/// Maps a KeyCode to its corresponding ConsoleKey and character representation.
|
|
/// </summary>
|
|
private static (ConsoleKey consoleKey, char keyChar) MapToConsoleKeyAndChar (KeyCode key, ConsoleModifiers modifiers)
|
|
{
|
|
var keyValue = (uint)key;
|
|
|
|
// Check if this is a special key (value > MaxCodePoint means it's offset by MaxCodePoint)
|
|
if (keyValue > (uint)KeyCode.MaxCodePoint)
|
|
{
|
|
var specialKey = (ConsoleKey)(keyValue - (uint)KeyCode.MaxCodePoint);
|
|
|
|
// Special keys don't have printable characters
|
|
char specialChar = specialKey switch
|
|
{
|
|
ConsoleKey.Enter => '\r',
|
|
ConsoleKey.Tab => '\t',
|
|
ConsoleKey.Escape => '\u001B',
|
|
ConsoleKey.Backspace => '\b',
|
|
ConsoleKey.Spacebar => ' ',
|
|
_ => '\0' // Function keys, arrows, etc. have no character
|
|
};
|
|
|
|
return (specialKey, specialChar);
|
|
}
|
|
|
|
// Handle letter keys (A-Z)
|
|
if (keyValue >= (uint)KeyCode.A && keyValue <= (uint)KeyCode.Z)
|
|
{
|
|
var letterKey = (ConsoleKey)keyValue;
|
|
var letterChar = (char)('a' + (keyValue - (uint)KeyCode.A));
|
|
|
|
if (modifiers.HasFlag (ConsoleModifiers.Shift))
|
|
{
|
|
letterChar = char.ToUpper (letterChar);
|
|
}
|
|
|
|
return (letterKey, letterChar);
|
|
}
|
|
|
|
// Handle number keys (D0-D9) with US keyboard layout
|
|
if (keyValue >= (uint)KeyCode.D0 && keyValue <= (uint)KeyCode.D9)
|
|
{
|
|
var numberKey = (ConsoleKey)keyValue;
|
|
char numberChar;
|
|
|
|
if (modifiers.HasFlag (ConsoleModifiers.Shift))
|
|
{
|
|
// US keyboard layout: Shift+0-9 produces )!@#$%^&*(
|
|
numberChar = ")!@#$%^&*(" [(int)(keyValue - (uint)KeyCode.D0)];
|
|
}
|
|
else
|
|
{
|
|
numberChar = (char)('0' + (keyValue - (uint)KeyCode.D0));
|
|
}
|
|
|
|
return (numberKey, numberChar);
|
|
}
|
|
|
|
// Handle other standard keys
|
|
var standardKey = (ConsoleKey)keyValue;
|
|
|
|
if (Enum.IsDefined (typeof (ConsoleKey), (int)keyValue))
|
|
{
|
|
char standardChar = standardKey switch
|
|
{
|
|
ConsoleKey.Enter => '\r',
|
|
ConsoleKey.Tab => '\t',
|
|
ConsoleKey.Escape => '\u001B',
|
|
ConsoleKey.Backspace => '\b',
|
|
ConsoleKey.Spacebar => ' ',
|
|
ConsoleKey.Clear => '\0',
|
|
_ when keyValue <= 0x1F => '\0', // Control characters
|
|
_ => (char)keyValue
|
|
};
|
|
|
|
return (standardKey, standardChar);
|
|
}
|
|
|
|
// For printable Unicode characters, return character with ConsoleKey.None
|
|
if (keyValue <= 0x10FFFF && !char.IsControl ((char)keyValue))
|
|
{
|
|
return (ConsoleKey.None, (char)keyValue);
|
|
}
|
|
|
|
// Fallback
|
|
return (ConsoleKey.None, (char)keyValue);
|
|
}
|
|
}
|