using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
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 {
///
/// Escape key code (ASCII 27/0x1B).
///
public static readonly char KeyEsc = (char)Key.Esc;
///
/// ESC [ - The CSI (Control Sequence Introducer).
///
public static readonly string CSI = $"{KeyEsc}[";
///
/// 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 [ ? 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";
///
/// Control sequence for enabling mouse events.
///
public static string CSI_EnableMouseEvents { get; set; } =
CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
///
/// Control sequence for disabling mouse events.
///
public static string CSI_DisableMouseEvents { get; set; } =
CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
///
/// ESC [ ? 1047 h - Activate xterm alternative buffer (no backscroll)
///
public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h";
///
/// ESC [ ? 1047 l - Restore xterm working buffer (with backscroll)
///
public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l";
///
/// ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll)
///
public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h";
///
/// ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll)
///
public static readonly string CSI_RestoreCursorAndActivateAltBufferWithBackscroll = CSI + "?1049l";
///
/// 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
}
///
/// ESC [ x J - Clears part of the screen. See .
///
///
///
public static string CSI_ClearScreen (ClearScreenOptions option) => $"{CSI}{(int)option}J";
#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) => $"{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
///
///
///
///
public static string CSI_SetCursorPosition (int x, int y) => $"{CSI}{y};{x}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) => $"{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) => $"{CSI}{string.Join (";", parameters)}m";
///
/// ESC[38;5;{id}m - Set foreground color (256 colors)
///
public static string CSI_SetForegroundColor (int id) => $"{CSI}38;5;{id}m";
///
/// ESC[48;5;{id}m - Set background color (256 colors)
///
public static string CSI_SetBackgroundColor (int id) => $"{CSI}48;5;{id}m";
///
/// ESC[38;2;{r};{g};{b}m Set foreground color as RGB.
///
public static string CSI_SetForegroundColorRGB (int r, int g, int b) => $"{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) => $"{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
///
/// 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;
var 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;
}
///
/// 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;
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;
} else 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 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;
string [] values = new string [nSep];
int valueIdx = 0;
string terminating = "";
for (int i = 2; i < kChar.Length; i++) {
var 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);
}
///
/// 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 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 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;
}
///
/// A helper to get only the from the array.
///
///
/// The char array of the escape sequence.
public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
{
char [] kChar = new char [] { };
var length = 0;
foreach (var kc in cki) {
length++;
Array.Resize (ref kChar, length);
kChar [length - 1] = kc.KeyChar;
}
return kChar;
}
private static MouseFlags? lastMouseButtonPressed;
//private static MouseFlags? lastMouseButtonReleased;
private static bool isButtonPressed;
//private static bool isButtonReleased;
private static bool isButtonClicked;
private static bool isButtonDoubleClicked;
private static bool isButtonTripleClicked;
private static Point point;
///
/// 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 ();
int buttonCode = 0;
bool foundButtonCode = false;
int foundPoint = 0;
string value = "";
var kChar = GetKeyCharArray (cki);
//System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
for (int i = 0; i < kChar.Length; i++) {
var 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 ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) {
point = new Point () {
X = pos.X,
Y = pos.Y
};
Application.MainLoop.AddIdle (() => {
Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler));
return false;
});
} else if (mouseFlags [0] == MouseFlags.ReportMousePosition) {
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}");
//}
}
private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action continuousButtonPressedHandler)
{
while (isButtonPressed) {
await Task.Delay (100);
//var me = new MouseEvent () {
// X = point.X,
// Y = point.Y,
// Flags = mouseFlag
//};
var view = Application.WantContinuousButtonPressedView;
if (view == null)
break;
if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point));
}
}
}
private static async Task ProcessButtonClickedAsync ()
{
await Task.Delay (300);
isButtonClicked = false;
}
private static async Task ProcessButtonDoubleClickedAsync ()
{
await Task.Delay (300);
isButtonDoubleClicked = false;
}
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 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;
}
// 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 (ManagementObjectSearcher 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;
}
}