using System.Diagnostics;
using System.Management;
using System.Runtime.InteropServices;
namespace Terminal.Gui;
///
/// Provides a platform-independent API for managing ANSI escape sequences.
///
///
/// Useful resources:
/// * https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
/// * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
/// * https://vt100.net/
///
public static class EscSeqUtils
{
///
/// Options for ANSI ESC "[xJ" - Clears part of the screen.
///
public enum ClearScreenOptions
{
///
/// If n is 0 (or missing), clear from cursor to end of screen.
///
CursorToEndOfScreen = 0,
///
/// If n is 1, clear from cursor to beginning of the screen.
///
CursorToBeginningOfScreen = 1,
///
/// If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS).
///
EntireScreen = 2,
///
/// If n is 3, clear entire screen and delete all lines saved in the scrollback buffer
///
EntireScreenAndScrollbackBuffer = 3
}
///
/// Escape key code (ASCII 27/0x1B).
///
public static readonly char KeyEsc = (char)KeyCode.Esc;
///
/// ESC [ - The CSI (Control Sequence Introducer).
///
public static readonly string CSI = $"{KeyEsc}[";
///
/// ESC [ ? 1047 h - Activate xterm alternative buffer (no backscroll)
///
///
/// From
/// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
/// Use Alternate Screen Buffer, xterm.
///
public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h";
///
/// ESC [ ? 1003 l - Disable any mouse event tracking.
///
public static readonly string CSI_DisableAnyEventMouse = CSI + "?1003l";
///
/// ESC [ ? 1006 l - Disable SGR (Select Graphic Rendition).
///
public static readonly string CSI_DisableSgrExtModeMouse = CSI + "?1006l";
///
/// ESC [ ? 1015 l - Disable URXVT (Unicode Extended Virtual Terminal).
///
public static readonly string CSI_DisableUrxvtExtModeMouse = CSI + "?1015l";
///
/// ESC [ ? 1003 h - Enable mouse event tracking.
///
public static readonly string CSI_EnableAnyEventMouse = CSI + "?1003h";
///
/// ESC [ ? 1006 h - Enable SGR (Select Graphic Rendition).
///
public static readonly string CSI_EnableSgrExtModeMouse = CSI + "?1006h";
///
/// ESC [ ? 1015 h - Enable URXVT (Unicode Extended Virtual Terminal).
///
public static readonly string CSI_EnableUrxvtExtModeMouse = CSI + "?1015h";
///
/// ESC [ ? 1047 l - Restore xterm working buffer (with backscroll)
///
///
/// From
/// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
/// Use Normal Screen Buffer, xterm. Clear the screen first if in the Alternate Screen Buffer.
///
public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l";
///
/// ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll)
///
///
/// From
/// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
/// Use Normal Screen Buffer and restore cursor as in DECRC, xterm.
/// resource.This combines the effects of the 1047 and 1048 modes.
///
public static readonly string CSI_RestoreCursorAndRestoreAltBufferWithBackscroll = CSI + "?1049l";
///
/// ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll)
///
///
/// From
/// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
/// Save cursor as in DECSC, xterm. After saving the cursor, switch to the Alternate Screen Buffer,
/// clearing it first.
/// This control combines the effects of the 1047 and 1048 modes.
/// Use this with terminfo-based applications rather than the 47 mode.
///
public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h";
//private static bool isButtonReleased;
private static bool isButtonClicked;
private static bool isButtonDoubleClicked;
//private static MouseFlags? lastMouseButtonReleased;
private static bool isButtonPressed;
private static bool isButtonTripleClicked;
private static MouseFlags? lastMouseButtonPressed;
private static Point? point;
///
/// Control sequence for disabling mouse events.
///
public static string CSI_DisableMouseEvents { get; set; } =
CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
///
/// Control sequence for enabling mouse events.
///
public static string CSI_EnableMouseEvents { get; set; } =
CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
///
/// ESC [ x J - Clears part of the screen. See .
///
///
///
public static string CSI_ClearScreen (ClearScreenOptions option) { return $"{CSI}{(int)option}J"; }
///
/// Decodes an ANSI escape sequence.
///
/// The which may contain a request.
/// The which may changes.
/// The which may changes.
/// The array.
/// The which may changes.
/// The control returned by the method.
/// The code returned by the method.
/// The values returned by the method.
/// The terminator returned by the method.
/// Indicates if the escape sequence is a mouse event.
/// The button state.
/// The position.
/// Indicates if the escape sequence is a response to a request.
/// The handler that will process the event.
public static void DecodeEscSeq (
EscSeqRequests escSeqRequests,
ref ConsoleKeyInfo newConsoleKeyInfo,
ref ConsoleKey key,
ConsoleKeyInfo [] cki,
ref ConsoleModifiers mod,
out string c1Control,
out string code,
out string [] values,
out string terminator,
out bool isMouse,
out List buttonState,
out Point pos,
out bool isResponse,
Action continuousButtonPressedHandler
)
{
char [] kChars = GetKeyCharArray (cki);
(c1Control, code, values, terminator) = GetEscapeResult (kChars);
isMouse = false;
buttonState = new List { 0 };
pos = default (Point);
isResponse = false;
switch (c1Control)
{
case "ESC":
if (values == null && string.IsNullOrEmpty (terminator))
{
key = ConsoleKey.Escape;
newConsoleKeyInfo = new ConsoleKeyInfo (
cki [0].KeyChar,
key,
(mod & ConsoleModifiers.Shift) != 0,
(mod & ConsoleModifiers.Alt) != 0,
(mod & ConsoleModifiers.Control) != 0);
}
else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26)
{
key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1);
newConsoleKeyInfo = new ConsoleKeyInfo (
cki [1].KeyChar,
key,
false,
true,
true);
}
else
{
if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122)
{
key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0];
}
else
{
key = (ConsoleKey)cki [1].KeyChar;
}
newConsoleKeyInfo = new ConsoleKeyInfo (
(char)key,
(ConsoleKey)Math.Min ((uint)key, 255),
false,
true,
false);
}
break;
case "SS3":
key = GetConsoleKey (terminator [0], values [0], ref mod);
newConsoleKeyInfo = new ConsoleKeyInfo (
'\0',
key,
(mod & ConsoleModifiers.Shift) != 0,
(mod & ConsoleModifiers.Alt) != 0,
(mod & ConsoleModifiers.Control) != 0);
break;
case "CSI":
if (!string.IsNullOrEmpty (code) && code == "<")
{
GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler);
isMouse = true;
return;
}
if (escSeqRequests != null && escSeqRequests.HasResponse (terminator))
{
isResponse = true;
escSeqRequests.Remove (terminator);
return;
}
if (!string.IsNullOrEmpty (terminator))
{
key = GetConsoleKey (terminator [0], values [0], ref mod);
if (key != 0 && values.Length > 1)
{
mod |= GetConsoleModifiers (values [1]);
}
newConsoleKeyInfo = new ConsoleKeyInfo (
'\0',
key,
(mod & ConsoleModifiers.Shift) != 0,
(mod & ConsoleModifiers.Alt) != 0,
(mod & ConsoleModifiers.Control) != 0);
}
else
{
// BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/2803
// This is caused by NetDriver depending on Console.KeyAvailable?
throw new InvalidOperationException ("CSI response, but there's no terminator");
//newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
// key,
// (mod & ConsoleModifiers.Shift) != 0,
// (mod & ConsoleModifiers.Alt) != 0,
// (mod & ConsoleModifiers.Control) != 0);
}
break;
}
}
///
/// Gets the c1Control used in the called escape sequence.
///
/// The char used.
/// The c1Control.
public static string GetC1ControlChar (char c)
{
// These control characters are used in the vtXXX emulation.
switch (c)
{
case 'D':
return "IND"; // Index
case 'E':
return "NEL"; // Next Line
case 'H':
return "HTS"; // Tab Set
case 'M':
return "RI"; // Reverse Index
case 'N':
return "SS2"; // Single Shift Select of G2 Character Set: affects next character only
case 'O':
return "SS3"; // Single Shift Select of G3 Character Set: affects next character only
case 'P':
return "DCS"; // Device Control String
case 'V':
return "SPA"; // Start of Guarded Area
case 'W':
return "EPA"; // End of Guarded Area
case 'X':
return "SOS"; // Start of String
case 'Z':
return "DECID"; // Return Terminal ID Obsolete form of CSI c (DA)
case '[':
return "CSI"; // Control Sequence Introducer
case '\\':
return "ST"; // String Terminator
case ']':
return "OSC"; // Operating System Command
case '^':
return "PM"; // Privacy Message
case '_':
return "APC"; // Application Program Command
default:
return ""; // Not supported
}
}
///
/// Gets the depending on terminating and value.
///
///
/// The terminator indicating a reply to or
/// .
///
/// The value.
/// The which may changes.
/// The and probably the .
public static ConsoleKey GetConsoleKey (char terminator, string value, ref ConsoleModifiers mod)
{
ConsoleKey key;
switch (terminator)
{
case 'A':
key = ConsoleKey.UpArrow;
break;
case 'B':
key = ConsoleKey.DownArrow;
break;
case 'C':
key = ConsoleKey.RightArrow;
break;
case 'D':
key = ConsoleKey.LeftArrow;
break;
case 'F':
key = ConsoleKey.End;
break;
case 'H':
key = ConsoleKey.Home;
break;
case 'P':
key = ConsoleKey.F1;
break;
case 'Q':
key = ConsoleKey.F2;
break;
case 'R':
key = ConsoleKey.F3;
break;
case 'S':
key = ConsoleKey.F4;
break;
case 'Z':
key = ConsoleKey.Tab;
mod |= ConsoleModifiers.Shift;
break;
case '~':
switch (value)
{
case "2":
key = ConsoleKey.Insert;
break;
case "3":
key = ConsoleKey.Delete;
break;
case "5":
key = ConsoleKey.PageUp;
break;
case "6":
key = ConsoleKey.PageDown;
break;
case "15":
key = ConsoleKey.F5;
break;
case "17":
key = ConsoleKey.F6;
break;
case "18":
key = ConsoleKey.F7;
break;
case "19":
key = ConsoleKey.F8;
break;
case "20":
key = ConsoleKey.F9;
break;
case "21":
key = ConsoleKey.F10;
break;
case "23":
key = ConsoleKey.F11;
break;
case "24":
key = ConsoleKey.F12;
break;
default:
key = 0;
break;
}
break;
default:
key = 0;
break;
}
return key;
}
///
/// Gets the from the value.
///
/// The value.
/// The or zero.
public static ConsoleModifiers GetConsoleModifiers (string value)
{
switch (value)
{
case "2":
return ConsoleModifiers.Shift;
case "3":
return ConsoleModifiers.Alt;
case "4":
return ConsoleModifiers.Shift | ConsoleModifiers.Alt;
case "5":
return ConsoleModifiers.Control;
case "6":
return ConsoleModifiers.Shift | ConsoleModifiers.Control;
case "7":
return ConsoleModifiers.Alt | ConsoleModifiers.Control;
case "8":
return ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control;
default:
return 0;
}
}
///
/// Gets all the needed information about a escape sequence.
///
/// The array with all chars.
///
/// The c1Control returned by , code, values and terminating.
///
public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar)
{
if (kChar == null || kChar.Length == 0)
{
return (null, null, null, null);
}
if (kChar [0] != KeyEsc)
{
throw new InvalidOperationException ("Invalid escape character!");
}
if (kChar.Length == 1)
{
return ("ESC", null, null, null);
}
if (kChar.Length == 2)
{
return ("ESC", null, null, kChar [1].ToString ());
}
string c1Control = GetC1ControlChar (kChar [1]);
string code = null;
int nSep = kChar.Count (x => x == ';') + 1;
var values = new string [nSep];
var valueIdx = 0;
var terminating = "";
for (var i = 2; i < kChar.Length; i++)
{
char c = kChar [i];
if (char.IsDigit (c))
{
values [valueIdx] += c.ToString ();
}
else if (c == ';')
{
valueIdx++;
}
else if (valueIdx == nSep - 1 || i == kChar.Length - 1)
{
terminating += c.ToString ();
}
else
{
code += c.ToString ();
}
}
return (c1Control, code, values, terminating);
}
///
/// A helper to get only the from the array.
///
///
/// The char array of the escape sequence.
public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
{
char [] kChar = { };
var length = 0;
foreach (ConsoleKeyInfo kc in cki)
{
length++;
Array.Resize (ref kChar, length);
kChar [length - 1] = kc.KeyChar;
}
return kChar;
}
///
/// Gets the mouse button flags and the position.
///
/// The array.
/// The mouse button flags.
/// The mouse position.
/// The handler that will process the event.
public static void GetMouse (
ConsoleKeyInfo [] cki,
out List mouseFlags,
out Point pos,
Action continuousButtonPressedHandler
)
{
MouseFlags buttonState = 0;
pos = new Point ();
var buttonCode = 0;
var foundButtonCode = false;
var foundPoint = 0;
var value = "";
char [] kChar = GetKeyCharArray (cki);
//System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
for (var i = 0; i < kChar.Length; i++)
{
char c = kChar [i];
if (c == '<')
{
foundButtonCode = true;
}
else if (foundButtonCode && c != ';')
{
value += c.ToString ();
}
else if (c == ';')
{
if (foundButtonCode)
{
foundButtonCode = false;
buttonCode = int.Parse (value);
}
if (foundPoint == 1)
{
pos.X = int.Parse (value) - 1;
}
value = "";
foundPoint++;
}
else if (foundPoint > 0 && c != 'm' && c != 'M')
{
value += c.ToString ();
}
else if (c == 'm' || c == 'M')
{
//pos.Y = int.Parse (value) + Console.WindowTop - 1;
pos.Y = int.Parse (value) - 1;
switch (buttonCode)
{
case 0:
case 8:
case 16:
case 24:
case 32:
case 36:
case 40:
case 48:
case 56:
buttonState = c == 'M'
? MouseFlags.Button1Pressed
: MouseFlags.Button1Released;
break;
case 1:
case 9:
case 17:
case 25:
case 33:
case 37:
case 41:
case 45:
case 49:
case 53:
case 57:
case 61:
buttonState = c == 'M'
? MouseFlags.Button2Pressed
: MouseFlags.Button2Released;
break;
case 2:
case 10:
case 14:
case 18:
case 22:
case 26:
case 30:
case 34:
case 42:
case 46:
case 50:
case 54:
case 58:
case 62:
buttonState = c == 'M'
? MouseFlags.Button3Pressed
: MouseFlags.Button3Released;
break;
case 35:
//// Needed for Windows OS
//if (isButtonPressed && c == 'm'
// && (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed
// || lastMouseEvent.ButtonState == MouseFlags.Button2Pressed
// || lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) {
// switch (lastMouseEvent.ButtonState) {
// case MouseFlags.Button1Pressed:
// buttonState = MouseFlags.Button1Released;
// break;
// case MouseFlags.Button2Pressed:
// buttonState = MouseFlags.Button2Released;
// break;
// case MouseFlags.Button3Pressed:
// buttonState = MouseFlags.Button3Released;
// break;
// }
//} else {
// buttonState = MouseFlags.ReportMousePosition;
//}
//break;
case 39:
case 43:
case 47:
case 51:
case 55:
case 59:
case 63:
buttonState = MouseFlags.ReportMousePosition;
break;
case 64:
buttonState = MouseFlags.WheeledUp;
break;
case 65:
buttonState = MouseFlags.WheeledDown;
break;
case 68:
case 72:
case 80:
buttonState = MouseFlags.WheeledLeft; // Shift/Ctrl+WheeledUp
break;
case 69:
case 73:
case 81:
buttonState = MouseFlags.WheeledRight; // Shift/Ctrl+WheeledDown
break;
}
// Modifiers.
switch (buttonCode)
{
case 8:
case 9:
case 10:
case 43:
buttonState |= MouseFlags.ButtonAlt;
break;
case 14:
case 47:
buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
break;
case 16:
case 17:
case 18:
case 51:
buttonState |= MouseFlags.ButtonCtrl;
break;
case 22:
case 55:
buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
break;
case 24:
case 25:
case 26:
case 59:
buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
break;
case 30:
case 63:
buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
break;
case 32:
case 33:
case 34:
buttonState |= MouseFlags.ReportMousePosition;
break;
case 36:
case 37:
buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift;
break;
case 39:
case 68:
case 69:
buttonState |= MouseFlags.ButtonShift;
break;
case 40:
case 41:
case 42:
buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt;
break;
case 45:
case 46:
buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
break;
case 48:
case 49:
case 50:
buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl;
break;
case 53:
case 54:
buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
break;
case 56:
case 57:
case 58:
buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
break;
case 61:
case 62:
buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
break;
}
}
}
mouseFlags = new List { MouseFlags.AllEvents };
if (lastMouseButtonPressed != null
&& !isButtonPressed
&& !buttonState.HasFlag (MouseFlags.ReportMousePosition)
&& !buttonState.HasFlag (MouseFlags.Button1Released)
&& !buttonState.HasFlag (MouseFlags.Button2Released)
&& !buttonState.HasFlag (MouseFlags.Button3Released)
&& !buttonState.HasFlag (MouseFlags.Button4Released))
{
lastMouseButtonPressed = null;
isButtonPressed = false;
}
if ((!isButtonClicked
&& !isButtonDoubleClicked
&& (buttonState == MouseFlags.Button1Pressed
|| buttonState == MouseFlags.Button2Pressed
|| buttonState == MouseFlags.Button3Pressed
|| buttonState == MouseFlags.Button4Pressed)
&& lastMouseButtonPressed == null)
|| (isButtonPressed && lastMouseButtonPressed != null && buttonState.HasFlag (MouseFlags.ReportMousePosition)))
{
mouseFlags [0] = buttonState;
lastMouseButtonPressed = buttonState;
isButtonPressed = true;
if (point is null)
{
point = pos;
}
if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0)
{
Application.MainLoop.AddIdle (
() =>
{
Task.Run (
async () => await ProcessContinuousButtonPressedAsync (
buttonState,
continuousButtonPressedHandler));
return false;
});
}
else if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition))
{
point = pos;
// The isButtonPressed must always be true, otherwise we can lose the feature
// If mouse flags has ReportMousePosition this feature won't run
// but is always prepared with the new location
//isButtonPressed = false;
}
}
else if (isButtonDoubleClicked
&& (buttonState == MouseFlags.Button1Pressed
|| buttonState == MouseFlags.Button2Pressed
|| buttonState == MouseFlags.Button3Pressed
|| buttonState == MouseFlags.Button4Pressed))
{
mouseFlags [0] = GetButtonTripleClicked (buttonState);
isButtonDoubleClicked = false;
isButtonTripleClicked = true;
}
else if (isButtonClicked
&& (buttonState == MouseFlags.Button1Pressed
|| buttonState == MouseFlags.Button2Pressed
|| buttonState == MouseFlags.Button3Pressed
|| buttonState == MouseFlags.Button4Pressed))
{
mouseFlags [0] = GetButtonDoubleClicked (buttonState);
isButtonClicked = false;
isButtonDoubleClicked = true;
Application.MainLoop.AddIdle (
() =>
{
Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
return false;
});
}
//else if (isButtonReleased && !isButtonClicked && buttonState == MouseFlags.ReportMousePosition) {
// mouseFlag [0] = GetButtonClicked ((MouseFlags)lastMouseButtonReleased);
// lastMouseButtonReleased = null;
// isButtonReleased = false;
// isButtonClicked = true;
// Application.MainLoop.AddIdle (() => {
// Task.Run (async () => await ProcessButtonClickedAsync ());
// return false;
// });
//}
else if (!isButtonClicked
&& !isButtonDoubleClicked
&& (buttonState == MouseFlags.Button1Released
|| buttonState == MouseFlags.Button2Released
|| buttonState == MouseFlags.Button3Released
|| buttonState == MouseFlags.Button4Released))
{
mouseFlags [0] = buttonState;
isButtonPressed = false;
if (isButtonTripleClicked)
{
isButtonTripleClicked = false;
}
else if (pos.X == point?.X && pos.Y == point?.Y)
{
mouseFlags.Add (GetButtonClicked (buttonState));
isButtonClicked = true;
Application.MainLoop.AddIdle (
() =>
{
Task.Run (async () => await ProcessButtonClickedAsync ());
return false;
});
}
point = pos;
//if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) {
// lastMouseButtonReleased = buttonState;
// isButtonPressed = false;
// isButtonReleased = true;
//} else {
// lastMouseButtonPressed = null;
// isButtonPressed = false;
//}
}
else if (buttonState == MouseFlags.WheeledUp)
{
mouseFlags [0] = MouseFlags.WheeledUp;
}
else if (buttonState == MouseFlags.WheeledDown)
{
mouseFlags [0] = MouseFlags.WheeledDown;
}
else if (buttonState == MouseFlags.WheeledLeft)
{
mouseFlags [0] = MouseFlags.WheeledLeft;
}
else if (buttonState == MouseFlags.WheeledRight)
{
mouseFlags [0] = MouseFlags.WheeledRight;
}
else if (buttonState == MouseFlags.ReportMousePosition)
{
mouseFlags [0] = MouseFlags.ReportMousePosition;
}
else
{
mouseFlags [0] = buttonState;
//foreach (var flag in buttonState.GetUniqueFlags()) {
// mouseFlag [0] |= flag;
//}
}
mouseFlags [0] = SetControlKeyStates (buttonState, mouseFlags [0]);
//buttonState = mouseFlags;
//System.Diagnostics.Debug.WriteLine ($"buttonState: {buttonState} X: {pos.X} Y: {pos.Y}");
//foreach (var mf in mouseFlags) {
// System.Diagnostics.Debug.WriteLine ($"mouseFlags: {mf} X: {pos.X} Y: {pos.Y}");
//}
}
// TODO: Move this out of here and into ConsoleDriver or somewhere else.
///
/// Get the terminal that holds the console driver.
///
/// The process.
/// If supported the executable console process, null otherwise.
public static Process GetParentProcess (Process process)
{
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
{
return null;
}
string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + process.Id;
using (var mos = new ManagementObjectSearcher (query))
{
foreach (ManagementObject mo in mos.Get ())
{
if (mo ["ParentProcessId"] != null)
{
try
{
var id = Convert.ToInt32 (mo ["ParentProcessId"]);
return Process.GetProcessById (id);
}
catch
{ }
}
}
}
return null;
}
///
/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
///
/// The .
/// The modified.
public static ConsoleKeyInfo MapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
{
ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
ConsoleKey key;
char keyChar = consoleKeyInfo.KeyChar;
switch ((uint)keyChar)
{
case 0:
if (consoleKeyInfo.Key == (ConsoleKey)64)
{ // Ctrl+Space in Windows.
newConsoleKeyInfo = new ConsoleKeyInfo (
' ',
ConsoleKey.Spacebar,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
}
break;
case uint n when n > 0 && n <= KeyEsc:
if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r')
{
key = ConsoleKey.Enter;
newConsoleKeyInfo = new ConsoleKeyInfo (
consoleKeyInfo.KeyChar,
key,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
}
else if (consoleKeyInfo.Key == 0)
{
key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1);
newConsoleKeyInfo = new ConsoleKeyInfo (
(char)key,
key,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
true);
}
break;
case 127: // DEL
newConsoleKeyInfo = new ConsoleKeyInfo (
consoleKeyInfo.KeyChar,
ConsoleKey.Backspace,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
break;
default:
newConsoleKeyInfo = consoleKeyInfo;
break;
}
return newConsoleKeyInfo;
}
///
/// A helper to resize the as needed.
///
/// The .
/// The array to resize.
/// The resized.
public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki)
{
Array.Resize (ref cki, cki == null ? 1 : cki.Length + 1);
cki [cki.Length - 1] = consoleKeyInfo;
return cki;
}
private static MouseFlags GetButtonClicked (MouseFlags mouseFlag)
{
MouseFlags mf = default;
switch (mouseFlag)
{
case MouseFlags.Button1Released:
mf = MouseFlags.Button1Clicked;
break;
case MouseFlags.Button2Released:
mf = MouseFlags.Button2Clicked;
break;
case MouseFlags.Button3Released:
mf = MouseFlags.Button3Clicked;
break;
}
return mf;
}
private static MouseFlags GetButtonDoubleClicked (MouseFlags mouseFlag)
{
MouseFlags mf = default;
switch (mouseFlag)
{
case MouseFlags.Button1Pressed:
mf = MouseFlags.Button1DoubleClicked;
break;
case MouseFlags.Button2Pressed:
mf = MouseFlags.Button2DoubleClicked;
break;
case MouseFlags.Button3Pressed:
mf = MouseFlags.Button3DoubleClicked;
break;
}
return mf;
}
private static MouseFlags GetButtonTripleClicked (MouseFlags mouseFlag)
{
MouseFlags mf = default;
switch (mouseFlag)
{
case MouseFlags.Button1Pressed:
mf = MouseFlags.Button1TripleClicked;
break;
case MouseFlags.Button2Pressed:
mf = MouseFlags.Button2TripleClicked;
break;
case MouseFlags.Button3Pressed:
mf = MouseFlags.Button3TripleClicked;
break;
}
return mf;
}
private static async Task ProcessButtonClickedAsync ()
{
await Task.Delay (300);
isButtonClicked = false;
}
private static async Task ProcessButtonDoubleClickedAsync ()
{
await Task.Delay (300);
isButtonDoubleClicked = false;
}
private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action continuousButtonPressedHandler)
{
while (isButtonPressed)
{
await Task.Delay (100);
View view = Application.WantContinuousButtonPressedView;
if (view == null)
{
break;
}
if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
{
Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, point ?? Point.Empty));
}
}
}
private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag)
{
if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0)
{
mouseFlag |= MouseFlags.ButtonCtrl;
}
if ((buttonState & MouseFlags.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0)
{
mouseFlag |= MouseFlags.ButtonShift;
}
if ((buttonState & MouseFlags.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0)
{
mouseFlag |= MouseFlags.ButtonAlt;
}
return mouseFlag;
}
#region Cursor
//ESC [ M - RI Reverse Index – Performs the reverse operation of \n, moves cursor up one line, maintains horizontal position, scrolls buffer if necessary*
///
/// ESC [ 7 - Save Cursor Position in Memory**
///
public static readonly string CSI_SaveCursorPosition = CSI + "7";
///
/// ESC [ 8 - DECSR Restore Cursor Position from Memory**
///
public static readonly string CSI_RestoreCursorPosition = CSI + "8";
///
/// ESC [ 8 ; height ; width t - Set Terminal Window Size
/// https://terminalguide.namepad.de/seq/csi_st-8/
///
public static string CSI_SetTerminalWindowSize (int height, int width) { return $"{CSI}8;{height};{width}t"; }
//ESC [ < n > A - CUU - Cursor Up Cursor up by < n >
//ESC [ < n > B - CUD - Cursor Down Cursor down by < n >
//ESC [ < n > C - CUF - Cursor Forward Cursor forward (Right) by < n >
//ESC [ < n > D - CUB - Cursor Backward Cursor backward (Left) by < n >
//ESC [ < n > E - CNL - Cursor Next Line - Cursor down < n > lines from current position
//ESC [ < n > F - CPL - Cursor Previous Line Cursor up < n > lines from current position
//ESC [ < n > G - CHA - Cursor Horizontal Absolute Cursor moves to < n > th position horizontally in the current line
//ESC [ < n > d - VPA - Vertical Line Position Absolute Cursor moves to the < n > th position vertically in the current column
///
/// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column
/// of the y line
///
/// Origin is (1,1).
/// Origin is (1,1).
///
public static string CSI_SetCursorPosition (int row, int col) { return $"{CSI}{row};{col}H"; }
//ESC [ ; f - HVP Horizontal Vertical Position* Cursor moves to; coordinate within the viewport, where is the column of the line
//ESC [ s - ANSISYSSC Save Cursor – Ansi.sys emulation **With no parameters, performs a save cursor operation like DECSC
//ESC [ u - ANSISYSRC Restore Cursor – Ansi.sys emulation **With no parameters, performs a restore cursor operation like DECRC
//ESC [ ? 12 h - ATT160 Text Cursor Enable Blinking Start the cursor blinking
//ESC [ ? 12 l - ATT160 Text Cursor Disable Blinking Stop blinking the cursor
///
/// ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show Show the cursor
///
public static readonly string CSI_ShowCursor = CSI + "?25h";
///
/// ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide Hide the cursor
///
public static readonly string CSI_HideCursor = CSI + "?25l";
//ESC [ ? 12 h - ATT160 Text Cursor Enable Blinking Start the cursor blinking
//ESC [ ? 12 l - ATT160 Text Cursor Disable Blinking Stop blinking the cursor
//ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show Show the cursor
//ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide Hide the cursor
///
/// Styles for ANSI ESC "[x q" - Set Cursor Style
///
public enum DECSCUSR_Style
{
///
/// DECSCUSR - User Shape - Default cursor shape configured by the user
///
UserShape = 0,
///
/// DECSCUSR - Blinking Block - Blinking block cursor shape
///
BlinkingBlock = 1,
///
/// DECSCUSR - Steady Block - Steady block cursor shape
///
SteadyBlock = 2,
///
/// DECSCUSR - Blinking Underline - Blinking underline cursor shape
///
BlinkingUnderline = 3,
///
/// DECSCUSR - Steady Underline - Steady underline cursor shape
///
SteadyUnderline = 4,
///
/// DECSCUSR - Blinking Bar - Blinking bar cursor shape
///
BlinkingBar = 5,
///
/// DECSCUSR - Steady Bar - Steady bar cursor shape
///
SteadyBar = 6
}
///
/// ESC [ n SP q - Select Cursor Style (DECSCUSR)
/// https://terminalguide.namepad.de/seq/csi_sq_t_space/
///
///
///
public static string CSI_SetCursorStyle (DECSCUSR_Style style) { return $"{CSI}{(int)style} q"; }
#endregion
#region Colors
///
/// ESC [ (n) m - SGR - Set Graphics Rendition - Set the format of the screen and text as specified by (n)
/// This command is special in that the (n) position can accept between 0 and 16 parameters separated by semicolons.
/// When no parameters are specified, it is treated the same as a single 0 parameter.
/// https://terminalguide.namepad.de/seq/csi_sm/
///
public static string CSI_SetGraphicsRendition (params int [] parameters) { return $"{CSI}{string.Join (";", parameters)}m"; }
///
/// ESC [ (n) m - Uses to set the foreground color.
///
/// One of the 16 color codes.
///
public static string CSI_SetForegroundColor (AnsiColorCode code) { return CSI_SetGraphicsRendition ((int)code); }
///
/// ESC [ (n) m - Uses to set the background color.
///
/// One of the 16 color codes.
///
public static string CSI_SetBackgroundColor (AnsiColorCode code) { return CSI_SetGraphicsRendition ((int)code + 10); }
///
/// ESC[38;5;{id}m - Set foreground color (256 colors)
///
public static string CSI_SetForegroundColor256 (int color) { return $"{CSI}38;5;{color}m"; }
///
/// ESC[48;5;{id}m - Set background color (256 colors)
///
public static string CSI_SetBackgroundColor256 (int color) { return $"{CSI}48;5;{color}m"; }
///
/// ESC[38;2;{r};{g};{b}m Set foreground color as RGB.
///
public static string CSI_SetForegroundColorRGB (int r, int g, int b) { return $"{CSI}38;2;{r};{g};{b}m"; }
///
/// ESC[48;2;{r};{g};{b}m Set background color as RGB.
///
public static string CSI_SetBackgroundColorRGB (int r, int g, int b) { return $"{CSI}48;2;{r};{g};{b}m"; }
#endregion
#region Requests
///
/// ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR)
/// https://terminalguide.namepad.de/seq/csi_sn__p-6/
///
public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n";
///
/// The terminal reply to . ESC [ ? (y) ; (x) R
///
public const string CSI_RequestCursorPositionReport_Terminator = "R";
///
/// ESC [ 0 c - Send Device Attributes (Primary DA)
/// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Application-Program-Command-functions
/// https://www.xfree86.org/current/ctlseqs.html
/// Windows Terminal v1.17 and below emits “\x1b[?1;0c”, indicating "VT101 with No Options".
/// Windows Terminal v1.18+ emits: \x1b[?61;6;7;22;23;24;28;32;42c"
/// See https://github.com/microsoft/terminal/pull/14906
/// 61 - The device conforms to level 1 of the character cell display architecture
/// (See https://github.com/microsoft/terminal/issues/15693#issuecomment-1633304497)
/// 6 = Selective erase
/// 7 = Soft fonts
/// 22 = Color text
/// 23 = Greek character sets
/// 24 = Turkish character sets
/// 28 = Rectangular area operations
/// 32 = Text macros
/// 42 = ISO Latin-2 character set
///
public static readonly string CSI_SendDeviceAttributes = CSI + "0c";
///
/// ESC [ > 0 c - Send Device Attributes (Secondary DA)
/// Windows Terminal v1.18+ emits: "\x1b[>0;10;1c" (vt100, firmware version 1.0, vt220)
///
public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c";
///
/// The terminator indicating a reply to or
///
///
public const string CSI_ReportDeviceAttributes_Terminator = "c";
///
/// CSI 1 8 t | yes | yes | yes | report window size in chars
/// https://terminalguide.namepad.de/seq/csi_st-18/
///
public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t";
///
/// The terminator indicating a reply to : ESC [ 8 ; height ; width t
///
public const string CSI_ReportTerminalSizeInChars_Terminator = "t";
///
/// The value of the response to indicating value 1 and 2 are the terminal
/// size in chars.
///
public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8";
#endregion
}