|
|
@@ -1,719 +1,25 @@
|
|
|
#nullable enable
|
|
|
-using System.Collections.Concurrent;
|
|
|
-using System.ComponentModel;
|
|
|
using System.Runtime.InteropServices;
|
|
|
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
|
-#pragma warning disable IDE1006// Naming rule violation: Prefix '_' is not expected
|
|
|
+
|
|
|
+// ReSharper disable InconsistentNaming
|
|
|
|
|
|
namespace Terminal.Gui.Drivers;
|
|
|
|
|
|
-public partial class WindowsConsole
|
|
|
-{
|
|
|
- private CancellationTokenSource? _inputReadyCancellationTokenSource;
|
|
|
- private readonly BlockingCollection<InputRecord> _inputQueue = new (new ConcurrentQueue<InputRecord> ());
|
|
|
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
|
|
|
|
- public const int STD_OUTPUT_HANDLE = -11;
|
|
|
+/// <summary>
|
|
|
+/// Definitions for Windows Console API structures and constants.
|
|
|
+/// </summary>
|
|
|
+public class WindowsConsole
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// Standard input handle constant.
|
|
|
+ /// </summary>
|
|
|
public const int STD_INPUT_HANDLE = -10;
|
|
|
|
|
|
- private readonly nint _inputHandle;
|
|
|
- private nint _outputHandle;
|
|
|
- private nint _screenBuffer;
|
|
|
- private readonly uint _originalConsoleMode;
|
|
|
- private CursorVisibility? _initialCursorVisibility;
|
|
|
- private CursorVisibility? _currentCursorVisibility;
|
|
|
- private CursorVisibility? _pendingCursorVisibility;
|
|
|
- private readonly StringBuilder _stringBuilder = new (256 * 1024);
|
|
|
- private string _lastWrite = string.Empty;
|
|
|
-
|
|
|
- public WindowsConsole ()
|
|
|
- {
|
|
|
- _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
|
|
|
- _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
|
|
|
- _originalConsoleMode = ConsoleMode;
|
|
|
- uint newConsoleMode = _originalConsoleMode;
|
|
|
- newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
|
|
|
- newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
|
|
|
- newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
|
|
|
- ConsoleMode = newConsoleMode;
|
|
|
-
|
|
|
- IsVirtualTerminal = GetConsoleMode (_outputHandle, out uint mode) && (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0;
|
|
|
-
|
|
|
- if (!IsVirtualTerminal)
|
|
|
- {
|
|
|
- CreateConsoleScreenBuffer ();
|
|
|
- Size bufferSize = GetConsoleBufferWindow (out _);
|
|
|
- SmallRect window = new ()
|
|
|
- {
|
|
|
- Top = 0,
|
|
|
- Left = 0,
|
|
|
- Bottom = (short)bufferSize.Height,
|
|
|
- Right = (short)bufferSize.Width
|
|
|
- };
|
|
|
-
|
|
|
- ReadFromConsoleOutput (bufferSize, new ((short)bufferSize.Width, (short)bufferSize.Height), ref window);
|
|
|
-
|
|
|
- if (!GetConsoleMode (_screenBuffer, out mode))
|
|
|
- {
|
|
|
- throw new ApplicationException ($"Failed to get screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
|
|
|
- }
|
|
|
-
|
|
|
- const uint ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002;
|
|
|
-
|
|
|
- mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; // Disable wrap
|
|
|
-
|
|
|
- if (!SetConsoleMode (_screenBuffer, mode))
|
|
|
- {
|
|
|
- throw new ApplicationException ($"Failed to set screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- SetInitialCursorVisibility ();
|
|
|
-
|
|
|
- _inputReadyCancellationTokenSource = new ();
|
|
|
- Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
|
|
|
- }
|
|
|
-
|
|
|
- private void CreateConsoleScreenBuffer ()
|
|
|
- {
|
|
|
- _screenBuffer = CreateConsoleScreenBuffer (
|
|
|
- DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
|
|
|
- ShareMode.FileShareRead | ShareMode.FileShareWrite,
|
|
|
- nint.Zero,
|
|
|
- 1,
|
|
|
- nint.Zero
|
|
|
- );
|
|
|
-
|
|
|
- if (_screenBuffer == INVALID_HANDLE_VALUE)
|
|
|
- {
|
|
|
- int err = Marshal.GetLastWin32Error ();
|
|
|
-
|
|
|
- if (err != 0)
|
|
|
- {
|
|
|
- throw new Win32Exception (err);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!SetConsoleActiveScreenBuffer (_screenBuffer))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public InputRecord? DequeueInput ()
|
|
|
- {
|
|
|
- while (_inputReadyCancellationTokenSource is { })
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- return _inputQueue.Take (_inputReadyCancellationTokenSource.Token);
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- public InputRecord? ReadConsoleInput ()
|
|
|
- {
|
|
|
- const int BUFFER_SIZE = 1;
|
|
|
- InputRecord inputRecord = default;
|
|
|
- uint numberEventsRead = 0;
|
|
|
-
|
|
|
- while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- // Peek to check if there is any input available
|
|
|
- if (PeekConsoleInput (_inputHandle, out _, BUFFER_SIZE, out uint eventsRead) && eventsRead > 0)
|
|
|
- {
|
|
|
- // Read the input since it is available
|
|
|
- ReadConsoleInput (
|
|
|
- _inputHandle,
|
|
|
- out inputRecord,
|
|
|
- BUFFER_SIZE,
|
|
|
- out numberEventsRead);
|
|
|
- }
|
|
|
-
|
|
|
- if (numberEventsRead > 0)
|
|
|
- {
|
|
|
- return inputRecord;
|
|
|
- }
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token);
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- if (ex is OperationCanceledException or ObjectDisposedException)
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- throw;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- private void ProcessInputQueue ()
|
|
|
- {
|
|
|
- while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- if (_inputQueue.Count == 0)
|
|
|
- {
|
|
|
- while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- InputRecord? inpRec = ReadConsoleInput ();
|
|
|
-
|
|
|
- if (inpRec is { })
|
|
|
- {
|
|
|
- _inputQueue.Add (inpRec.Value);
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
|
|
|
- private TextStyle _redrawTextStyle = TextStyle.None;
|
|
|
-
|
|
|
- private CharInfo []? _originalStdOutChars;
|
|
|
-
|
|
|
- private struct Run
|
|
|
- {
|
|
|
- public ushort attr;
|
|
|
- public string text;
|
|
|
-
|
|
|
- public Run (ushort attr, string text)
|
|
|
- {
|
|
|
- this.attr = attr;
|
|
|
- this.text = text;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
|
|
|
- {
|
|
|
- //Debug.WriteLine ("WriteToConsole");
|
|
|
-
|
|
|
- Attribute? prev = null;
|
|
|
- var result = false;
|
|
|
-
|
|
|
- if (force16Colors)
|
|
|
- {
|
|
|
- _stringBuilder.Clear ();
|
|
|
-
|
|
|
- var i = 0;
|
|
|
- List<Run> runs = [];
|
|
|
- Run? current = null;
|
|
|
- SetCursorPosition (new Coord (0, 0));
|
|
|
-
|
|
|
- foreach (ExtendedCharInfo info in charInfoBuffer)
|
|
|
- {
|
|
|
- if (IsVirtualTerminal)
|
|
|
- {
|
|
|
- Attribute attr = info.Attribute;
|
|
|
- AnsiColorCode fgColor = info.Attribute.Foreground.GetAnsiColorCode ();
|
|
|
- AnsiColorCode bgColor = info.Attribute.Background.GetAnsiColorCode ();
|
|
|
-
|
|
|
- if (attr != prev)
|
|
|
- {
|
|
|
- prev = attr;
|
|
|
- _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColor (fgColor));
|
|
|
- _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColor (bgColor));
|
|
|
-
|
|
|
- EscSeqUtils.CSI_AppendTextStyleChange (_stringBuilder, _redrawTextStyle, attr.Style);
|
|
|
- _redrawTextStyle = attr.Style;
|
|
|
- }
|
|
|
-
|
|
|
- if (info.Char [0] != '\x1b')
|
|
|
- {
|
|
|
- if (!info.Empty)
|
|
|
- {
|
|
|
- _stringBuilder.Append (info.Char);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _stringBuilder.Append (' ');
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- if (info.Empty)
|
|
|
- {
|
|
|
- i++;
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if (!info.Empty)
|
|
|
- {
|
|
|
- var attr = (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 ()
|
|
|
- | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4));
|
|
|
-
|
|
|
- // Start new run if needed
|
|
|
- if (current == null || attr != current.Value.attr)
|
|
|
- {
|
|
|
- if (current != null)
|
|
|
- {
|
|
|
- runs.Add (new (current.Value.attr, _stringBuilder.ToString ()));
|
|
|
- }
|
|
|
-
|
|
|
- _stringBuilder.Clear ();
|
|
|
- current = new Run (attr, "");
|
|
|
- }
|
|
|
-
|
|
|
- _stringBuilder!.Append (info.Char);
|
|
|
- }
|
|
|
-
|
|
|
- i++;
|
|
|
-
|
|
|
- if (i > 0 && i <= charInfoBuffer.Length && i % bufferSize.X == 0)
|
|
|
- {
|
|
|
- if (i < charInfoBuffer.Length)
|
|
|
- {
|
|
|
- _stringBuilder.AppendLine ();
|
|
|
- }
|
|
|
-
|
|
|
- runs.Add (new (current!.Value.attr, _stringBuilder.ToString ()));
|
|
|
- _stringBuilder.Clear ();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (IsVirtualTerminal)
|
|
|
- {
|
|
|
- _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
|
|
|
- _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
|
|
|
-
|
|
|
- var s = _stringBuilder.ToString ();
|
|
|
-
|
|
|
- // TODO: requires extensive testing if we go down this route
|
|
|
- // If console output has changed
|
|
|
- if (s != _lastWrite)
|
|
|
- {
|
|
|
- // supply console with the new content
|
|
|
- result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
|
|
|
- }
|
|
|
-
|
|
|
- _lastWrite = s;
|
|
|
-
|
|
|
- foreach (var sixel in Application.Sixel)
|
|
|
- {
|
|
|
- SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y));
|
|
|
- WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- foreach (var run in runs)
|
|
|
- {
|
|
|
- SetConsoleTextAttribute (IsVirtualTerminal ? _outputHandle : _screenBuffer, run.attr);
|
|
|
- result = WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, run.text, (uint)run.text.Length, out _, nint.Zero);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _stringBuilder.Clear ();
|
|
|
-
|
|
|
- _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
|
|
|
- EscSeqUtils.CSI_AppendCursorPosition (_stringBuilder, 0, 0);
|
|
|
-
|
|
|
- foreach (ExtendedCharInfo info in charInfoBuffer)
|
|
|
- {
|
|
|
- Attribute attr = info.Attribute;
|
|
|
-
|
|
|
- if (attr != prev)
|
|
|
- {
|
|
|
- prev = attr;
|
|
|
- EscSeqUtils.CSI_AppendForegroundColorRGB (_stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
|
|
|
- EscSeqUtils.CSI_AppendBackgroundColorRGB (_stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B);
|
|
|
- EscSeqUtils.CSI_AppendTextStyleChange (_stringBuilder, _redrawTextStyle, attr.Style);
|
|
|
- _redrawTextStyle = attr.Style;
|
|
|
- }
|
|
|
-
|
|
|
- if (info.Char [0] != '\x1b')
|
|
|
- {
|
|
|
- if (!info.Empty)
|
|
|
- {
|
|
|
- _stringBuilder.Append (info.Char);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _stringBuilder.Append (' ');
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
|
|
|
- _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
|
|
|
-
|
|
|
- var s = _stringBuilder.ToString ();
|
|
|
-
|
|
|
- // TODO: requires extensive testing if we go down this route
|
|
|
- // If console output has changed
|
|
|
- if (s != _lastWrite)
|
|
|
- {
|
|
|
- // supply console with the new content
|
|
|
- result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
|
|
|
- }
|
|
|
-
|
|
|
- _lastWrite = s;
|
|
|
-
|
|
|
- foreach (var sixel in Application.Sixel)
|
|
|
- {
|
|
|
- SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y));
|
|
|
- WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!result)
|
|
|
- {
|
|
|
- int err = Marshal.GetLastWin32Error ();
|
|
|
-
|
|
|
- if (err != 0)
|
|
|
- {
|
|
|
- throw new Win32Exception (err);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- internal bool WriteANSI (string ansi)
|
|
|
- {
|
|
|
- if (WriteConsole (_outputHandle, ansi, (uint)ansi.Length, out uint _, nint.Zero))
|
|
|
- {
|
|
|
- // Flush the output to make sure it's sent immediately
|
|
|
- return FlushFileBuffers (_outputHandle);
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window)
|
|
|
- {
|
|
|
- _originalStdOutChars = new CharInfo [size.Height * size.Width];
|
|
|
-
|
|
|
- if (!ReadConsoleOutput (_screenBuffer, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public bool SetCursorPosition (Coord position)
|
|
|
- {
|
|
|
- return SetConsoleCursorPosition (IsVirtualTerminal ? _outputHandle : _screenBuffer, position);
|
|
|
- }
|
|
|
-
|
|
|
- public void SetInitialCursorVisibility ()
|
|
|
- {
|
|
|
- if (_initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility))
|
|
|
- {
|
|
|
- _initialCursorVisibility = visibility;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public bool GetCursorVisibility (out CursorVisibility visibility)
|
|
|
- {
|
|
|
- if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
|
|
|
- {
|
|
|
- visibility = CursorVisibility.Invisible;
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (!GetConsoleCursorInfo (IsVirtualTerminal ? _outputHandle : _screenBuffer, out ConsoleCursorInfo info))
|
|
|
- {
|
|
|
- int err = Marshal.GetLastWin32Error ();
|
|
|
-
|
|
|
- if (err != 0)
|
|
|
- {
|
|
|
- throw new Win32Exception (err);
|
|
|
- }
|
|
|
-
|
|
|
- visibility = CursorVisibility.Default;
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (!info.bVisible)
|
|
|
- {
|
|
|
- visibility = CursorVisibility.Invisible;
|
|
|
- }
|
|
|
- else if (info.dwSize > 50)
|
|
|
- {
|
|
|
- visibility = CursorVisibility.Default;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- visibility = CursorVisibility.Default;
|
|
|
- }
|
|
|
-
|
|
|
- return visibility != CursorVisibility.Invisible;
|
|
|
- }
|
|
|
-
|
|
|
- public bool EnsureCursorVisibility ()
|
|
|
- {
|
|
|
- if (_initialCursorVisibility.HasValue && _pendingCursorVisibility.HasValue && SetCursorVisibility (_pendingCursorVisibility.Value))
|
|
|
- {
|
|
|
- _pendingCursorVisibility = null;
|
|
|
-
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- public void ForceRefreshCursorVisibility ()
|
|
|
- {
|
|
|
- if (_currentCursorVisibility.HasValue)
|
|
|
- {
|
|
|
- _pendingCursorVisibility = _currentCursorVisibility;
|
|
|
- _currentCursorVisibility = null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public bool SetCursorVisibility (CursorVisibility visibility)
|
|
|
- {
|
|
|
- if (_initialCursorVisibility.HasValue == false)
|
|
|
- {
|
|
|
- _pendingCursorVisibility = visibility;
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (_currentCursorVisibility.HasValue == false || _currentCursorVisibility.Value != visibility)
|
|
|
- {
|
|
|
- var info = new ConsoleCursorInfo
|
|
|
- {
|
|
|
- dwSize = (uint)visibility & 0x00FF,
|
|
|
- bVisible = ((uint)visibility & 0xFF00) != 0
|
|
|
- };
|
|
|
-
|
|
|
- if (!SetConsoleCursorInfo (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref info))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- _currentCursorVisibility = visibility;
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- public void Cleanup ()
|
|
|
- {
|
|
|
- if (_initialCursorVisibility.HasValue)
|
|
|
- {
|
|
|
- SetCursorVisibility (_initialCursorVisibility.Value);
|
|
|
- }
|
|
|
-
|
|
|
- //SetConsoleOutputWindow (out _);
|
|
|
-
|
|
|
- ConsoleMode = _originalConsoleMode;
|
|
|
-
|
|
|
- _outputHandle = CreateConsoleScreenBuffer (
|
|
|
- DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
|
|
|
- ShareMode.FileShareRead | ShareMode.FileShareWrite,
|
|
|
- nint.Zero,
|
|
|
- 1,
|
|
|
- nint.Zero
|
|
|
- );
|
|
|
-
|
|
|
- if (!SetConsoleActiveScreenBuffer (_outputHandle))
|
|
|
- {
|
|
|
- int err = Marshal.GetLastWin32Error ();
|
|
|
- Console.WriteLine ("Error: {0}", err);
|
|
|
- }
|
|
|
-
|
|
|
- if (_screenBuffer != nint.Zero)
|
|
|
- {
|
|
|
- CloseHandle (_screenBuffer);
|
|
|
- }
|
|
|
-
|
|
|
- _screenBuffer = nint.Zero;
|
|
|
-
|
|
|
- _inputReadyCancellationTokenSource?.Cancel ();
|
|
|
- _inputReadyCancellationTokenSource?.Dispose ();
|
|
|
- _inputReadyCancellationTokenSource = null;
|
|
|
- }
|
|
|
-
|
|
|
- internal Size GetConsoleBufferWindow (out Point position)
|
|
|
- {
|
|
|
- if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
|
|
|
- {
|
|
|
- position = Point.Empty;
|
|
|
-
|
|
|
- return Size.Empty;
|
|
|
- }
|
|
|
-
|
|
|
- var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
|
|
|
- csbi.cbSize = (uint)Marshal.SizeOf (csbi);
|
|
|
-
|
|
|
- if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
|
|
|
- {
|
|
|
- //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- position = Point.Empty;
|
|
|
-
|
|
|
- return Size.Empty;
|
|
|
- }
|
|
|
-
|
|
|
- Size sz = new (
|
|
|
- csbi.srWindow.Right - csbi.srWindow.Left + 1,
|
|
|
- csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
|
|
|
- position = new (csbi.srWindow.Left, csbi.srWindow.Top);
|
|
|
-
|
|
|
- return sz;
|
|
|
- }
|
|
|
-
|
|
|
- internal Size GetConsoleOutputWindow (out Point position)
|
|
|
- {
|
|
|
- var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
|
|
|
- csbi.cbSize = (uint)Marshal.SizeOf (csbi);
|
|
|
-
|
|
|
- if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
-
|
|
|
- Size sz = new (
|
|
|
- csbi.srWindow.Right - csbi.srWindow.Left + 1,
|
|
|
- csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
|
|
|
- position = new (csbi.srWindow.Left, csbi.srWindow.Top);
|
|
|
-
|
|
|
- return sz;
|
|
|
- }
|
|
|
-
|
|
|
- internal Size SetConsoleWindow (short cols, short rows)
|
|
|
- {
|
|
|
- var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
|
|
|
- csbi.cbSize = (uint)Marshal.SizeOf (csbi);
|
|
|
-
|
|
|
- if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
-
|
|
|
- Coord maxWinSize = GetLargestConsoleWindowSize (IsVirtualTerminal ? _outputHandle : _screenBuffer);
|
|
|
- short newCols = Math.Min (cols, maxWinSize.X);
|
|
|
- short newRows = Math.Min (rows, maxWinSize.Y);
|
|
|
- csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1));
|
|
|
- csbi.srWindow = new SmallRect (0, 0, newCols, newRows);
|
|
|
- csbi.dwMaximumWindowSize = new Coord (newCols, newRows);
|
|
|
-
|
|
|
- if (!SetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
-
|
|
|
- var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
|
|
|
-
|
|
|
- if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
|
|
|
- {
|
|
|
- //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- return new (cols, rows);
|
|
|
- }
|
|
|
-
|
|
|
- SetConsoleOutputWindow (csbi);
|
|
|
-
|
|
|
- return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
|
|
|
- }
|
|
|
-
|
|
|
- internal Size GetLargestConsoleWindowSize ()
|
|
|
- {
|
|
|
- Coord maxWinSize = GetLargestConsoleWindowSize (IsVirtualTerminal ? _outputHandle : _screenBuffer);
|
|
|
-
|
|
|
- return new (maxWinSize.X, maxWinSize.Y);
|
|
|
- }
|
|
|
-
|
|
|
- private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
|
|
|
- {
|
|
|
- if ((IsVirtualTerminal
|
|
|
- ? _outputHandle
|
|
|
- : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- internal Size SetConsoleOutputWindow (out Point position)
|
|
|
- {
|
|
|
- if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
|
|
|
- {
|
|
|
- position = Point.Empty;
|
|
|
-
|
|
|
- return Size.Empty;
|
|
|
- }
|
|
|
-
|
|
|
- var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
|
|
|
- csbi.cbSize = (uint)Marshal.SizeOf (csbi);
|
|
|
-
|
|
|
- if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
-
|
|
|
- Size sz = new (
|
|
|
- csbi.srWindow.Right - csbi.srWindow.Left + 1,
|
|
|
- Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0));
|
|
|
- position = new (csbi.srWindow.Left, csbi.srWindow.Top);
|
|
|
- SetConsoleOutputWindow (csbi);
|
|
|
- var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0));
|
|
|
-
|
|
|
- if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
-
|
|
|
- if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
|
|
|
- {
|
|
|
- throw new Win32Exception (Marshal.GetLastWin32Error ());
|
|
|
- }
|
|
|
-
|
|
|
- return sz;
|
|
|
- }
|
|
|
-
|
|
|
- internal bool IsVirtualTerminal { get; init; }
|
|
|
-
|
|
|
- private uint ConsoleMode
|
|
|
- {
|
|
|
- get
|
|
|
- {
|
|
|
- GetConsoleMode (_inputHandle, out uint v);
|
|
|
-
|
|
|
- return v;
|
|
|
- }
|
|
|
- set => SetConsoleMode (_inputHandle, value);
|
|
|
- }
|
|
|
-
|
|
|
+ /// <summary>
|
|
|
+ /// Windows Console mode flags.
|
|
|
+ /// </summary>
|
|
|
[Flags]
|
|
|
public enum ConsoleModes : uint
|
|
|
{
|
|
|
@@ -724,6 +30,9 @@ public partial class WindowsConsole
|
|
|
EnableExtendedFlags = 128
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Key event record structure.
|
|
|
+ /// </summary>
|
|
|
[StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
|
|
|
public struct KeyEventRecord
|
|
|
{
|
|
|
@@ -811,11 +120,9 @@ public partial class WindowsConsole
|
|
|
public readonly override string ToString () { return $"[Mouse{MousePosition},{ButtonState},{ControlKeyState},{EventFlags}]"; }
|
|
|
}
|
|
|
|
|
|
- public struct WindowBufferSizeRecord
|
|
|
+ public struct WindowBufferSizeRecord (short x, short y)
|
|
|
{
|
|
|
- public Coord _size;
|
|
|
-
|
|
|
- public WindowBufferSizeRecord (short x, short y) { _size = new Coord (x, y); }
|
|
|
+ public Coord _size = new (x, y);
|
|
|
|
|
|
public readonly override string ToString () { return $"[WindowBufferSize{_size}"; }
|
|
|
}
|
|
|
@@ -865,41 +172,17 @@ public partial class WindowsConsole
|
|
|
public readonly override string ToString ()
|
|
|
{
|
|
|
return (EventType switch
|
|
|
- {
|
|
|
- EventType.Focus => FocusEvent.ToString (),
|
|
|
- EventType.Key => KeyEvent.ToString (),
|
|
|
- EventType.Menu => MenuEvent.ToString (),
|
|
|
- EventType.Mouse => MouseEvent.ToString (),
|
|
|
- EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
|
|
|
- _ => "Unknown event type: " + EventType
|
|
|
- })!;
|
|
|
+ {
|
|
|
+ EventType.Focus => FocusEvent.ToString (),
|
|
|
+ EventType.Key => KeyEvent.ToString (),
|
|
|
+ EventType.Menu => MenuEvent.ToString (),
|
|
|
+ EventType.Mouse => MouseEvent.ToString (),
|
|
|
+ EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
|
|
|
+ _ => "Unknown event type: " + EventType
|
|
|
+ })!;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- [Flags]
|
|
|
- private enum ShareMode : uint
|
|
|
- {
|
|
|
- FileShareRead = 1,
|
|
|
- FileShareWrite = 2
|
|
|
- }
|
|
|
-
|
|
|
- [Flags]
|
|
|
- private enum DesiredAccess : uint
|
|
|
- {
|
|
|
- GenericRead = 2147483648,
|
|
|
- GenericWrite = 1073741824
|
|
|
- }
|
|
|
-
|
|
|
- [StructLayout (LayoutKind.Sequential)]
|
|
|
- public struct ConsoleScreenBufferInfo
|
|
|
- {
|
|
|
- public Coord dwSize;
|
|
|
- public Coord dwCursorPosition;
|
|
|
- public ushort wAttributes;
|
|
|
- public SmallRect srWindow;
|
|
|
- public Coord dwMaximumWindowSize;
|
|
|
- }
|
|
|
-
|
|
|
[StructLayout (LayoutKind.Sequential)]
|
|
|
public struct Coord
|
|
|
{
|
|
|
@@ -935,20 +218,6 @@ public partial class WindowsConsole
|
|
|
public ushort Attributes;
|
|
|
}
|
|
|
|
|
|
- public struct ExtendedCharInfo
|
|
|
- {
|
|
|
- public char [] Char { get; set; }
|
|
|
- public Attribute Attribute { get; set; }
|
|
|
- public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences
|
|
|
-
|
|
|
- public ExtendedCharInfo (char [] character, Attribute attribute)
|
|
|
- {
|
|
|
- Char = character;
|
|
|
- Attribute = attribute;
|
|
|
- Empty = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
[StructLayout (LayoutKind.Sequential)]
|
|
|
public struct SmallRect
|
|
|
{
|
|
|
@@ -1045,147 +314,19 @@ public partial class WindowsConsole
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern nint GetStdHandle (int nStdHandle);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool CloseHandle (nint handle);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- public static extern bool PeekConsoleInput (nint hConsoleInput, out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
|
|
|
- public static extern bool ReadConsoleInput (
|
|
|
- nint hConsoleInput,
|
|
|
- out InputRecord lpBuffer,
|
|
|
- uint nLength,
|
|
|
- out uint lpNumberOfEventsRead
|
|
|
- );
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
|
- private static extern bool ReadConsoleOutput (
|
|
|
- nint hConsoleOutput,
|
|
|
- [Out] CharInfo [] lpBuffer,
|
|
|
- Coord dwBufferSize,
|
|
|
- Coord dwBufferCoord,
|
|
|
- ref SmallRect lpReadRegion
|
|
|
- );
|
|
|
-
|
|
|
- // TODO: This API is obsolete. See https://learn.microsoft.com/en-us/windows/console/writeconsoleoutput
|
|
|
- [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutputW", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
|
- public static extern bool WriteConsoleOutput (
|
|
|
- nint hConsoleOutput,
|
|
|
- CharInfo [] lpBuffer,
|
|
|
- Coord dwBufferSize,
|
|
|
- Coord dwBufferCoord,
|
|
|
- ref SmallRect lpWriteRegion
|
|
|
- );
|
|
|
-
|
|
|
- [LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
|
|
- [return: MarshalAs (UnmanagedType.Bool)]
|
|
|
- private static partial bool WriteConsole (
|
|
|
- nint hConsoleOutput,
|
|
|
- ReadOnlySpan<char> lpbufer,
|
|
|
- uint NumberOfCharsToWriten,
|
|
|
- out uint lpNumberOfCharsWritten,
|
|
|
- nint lpReserved
|
|
|
- );
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool SetConsoleTextAttribute (
|
|
|
- nint hConsoleOutput,
|
|
|
- ushort wAttributes
|
|
|
- );
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool FlushFileBuffers (nint hFile);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll")]
|
|
|
- private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition);
|
|
|
-
|
|
|
[StructLayout (LayoutKind.Sequential)]
|
|
|
public struct ConsoleCursorInfo
|
|
|
{
|
|
|
/// <summary>
|
|
|
- /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100.
|
|
|
- /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal
|
|
|
- /// line at the bottom of the cell.
|
|
|
+ /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100.
|
|
|
+ /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal
|
|
|
+ /// line at the bottom of the cell.
|
|
|
/// </summary>
|
|
|
public uint dwSize;
|
|
|
- public bool bVisible;
|
|
|
- }
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool GetConsoleCursorInfo (nint hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll")]
|
|
|
- private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll")]
|
|
|
- private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern nint CreateConsoleScreenBuffer (
|
|
|
- DesiredAccess dwDesiredAccess,
|
|
|
- ShareMode dwShareMode,
|
|
|
- nint secutiryAttributes,
|
|
|
- uint flags,
|
|
|
- nint screenBufferData
|
|
|
- );
|
|
|
-
|
|
|
- internal static nint INVALID_HANDLE_VALUE = new (-1);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool SetConsoleActiveScreenBuffer (nint handle);
|
|
|
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool GetNumberOfConsoleInputEvents (nint handle, out uint lpcNumberOfEvents);
|
|
|
-
|
|
|
- internal uint GetNumberOfConsoleInputEvents ()
|
|
|
- {
|
|
|
- if (!GetNumberOfConsoleInputEvents (_inputHandle, out uint numOfEvents))
|
|
|
- {
|
|
|
- Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
|
|
|
-
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- return numOfEvents;
|
|
|
- }
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool FlushConsoleInputBuffer (nint handle);
|
|
|
-
|
|
|
- internal void FlushConsoleInputBuffer ()
|
|
|
- {
|
|
|
- if (!FlushConsoleInputBuffer (_inputHandle))
|
|
|
- {
|
|
|
- Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
|
|
|
- }
|
|
|
+ public bool bVisible;
|
|
|
}
|
|
|
|
|
|
-#if false // Not needed on the constructor. Perhaps could be used on resizing. To study.
|
|
|
- [DllImport ("kernel32.dll", ExactSpelling = true)]
|
|
|
- static extern IntPtr GetConsoleWindow ();
|
|
|
-
|
|
|
- [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
|
- static extern bool ShowWindow (IntPtr hWnd, int nCmdShow);
|
|
|
-
|
|
|
- public const int HIDE = 0;
|
|
|
- public const int MAXIMIZE = 3;
|
|
|
- public const int MINIMIZE = 6;
|
|
|
- public const int RESTORE = 9;
|
|
|
-
|
|
|
- internal void ShowWindow (int state)
|
|
|
- {
|
|
|
- IntPtr thisConsole = GetConsoleWindow ();
|
|
|
- ShowWindow (thisConsole, state);
|
|
|
- }
|
|
|
-#endif
|
|
|
-
|
|
|
// See: https://github.com/gui-cs/Terminal.Gui/issues/357
|
|
|
|
|
|
[StructLayout (LayoutKind.Sequential)]
|
|
|
@@ -1235,22 +376,4 @@ public partial class WindowsConsole
|
|
|
[FieldOffset (0)]
|
|
|
public uint Value;
|
|
|
}
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo);
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern bool SetConsoleWindowInfo (
|
|
|
- nint hConsoleOutput,
|
|
|
- bool bAbsolute,
|
|
|
- [In] ref SmallRect lpConsoleWindow
|
|
|
- );
|
|
|
-
|
|
|
- [DllImport ("kernel32.dll", SetLastError = true)]
|
|
|
- private static extern Coord GetLargestConsoleWindowSize (
|
|
|
- nint hConsoleOutput
|
|
|
- );
|
|
|
}
|