From 6b4aabd79bf347bc9068603c9556dee39bb78ce5 Mon Sep 17 00:00:00 2001 From: dedouwe26 <63008025+dedouwe26@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:42:28 +0100 Subject: [PATCH] Revert "quick" This reverts commit ab19baae8ea82dc462480f9239a52fb494b26a78. --- Terminal/Backend/WindowsBackend.cs | 283 ------------------ Terminal/Terminal.cs | 4 +- .../TerminalWindow.cs} | 133 ++++---- Terminal/Window/WinTerminalWindow.cs | 266 ++++++++++++++++ Terminal/Window/Window.cs | 13 - 5 files changed, 334 insertions(+), 365 deletions(-) delete mode 100644 Terminal/Backend/WindowsBackend.cs rename Terminal/{Backend/TerminalBackend.cs => Window/TerminalWindow.cs} (67%) create mode 100644 Terminal/Window/WinTerminalWindow.cs delete mode 100644 Terminal/Window/Window.cs diff --git a/Terminal/Backend/WindowsBackend.cs b/Terminal/Backend/WindowsBackend.cs deleted file mode 100644 index 834ef8c..0000000 --- a/Terminal/Backend/WindowsBackend.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Win32.SafeHandles; - -namespace OxDED.Terminal.Backend; - -internal static class Utility { - internal static Stream GetStream(WinAPI.StandardType type) { - SafeFileHandle fileHandle = new(WinAPI.GetStdHandle((int)type), false); - - if (fileHandle.IsInvalid) { - fileHandle.SetHandleAsInvalid(); - return Stream.Null; - } - - FileStream stream = new(fileHandle, type != WinAPI.StandardType.Input ? FileAccess.Write : FileAccess.Read); - - return stream; - } - internal static WinAPI.CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo(WindowsBackend backend) { - if (backend.outHandle == WinAPI.INVALID_HANDLE_VALUE) { - throw new Win32Exception("Invalid standard console output handle."); - } - - bool succeeded = WinAPI.GetConsoleScreenBufferInfo(backend.outHandle, out WinAPI.CONSOLE_SCREEN_BUFFER_INFO csbi); - if (!succeeded) { - succeeded = WinAPI.GetConsoleScreenBufferInfo(backend.errHandle, out csbi); - if (!succeeded) - succeeded = WinAPI.GetConsoleScreenBufferInfo(backend.inHandle, out csbi); - - if (!succeeded) { - int errorCode = Marshal.GetLastWin32Error(); - throw new Win32Exception(errorCode, "Tried to get the console screen buffer info."); - } - } - - return csbi; - } -} - -internal static partial class WinAPI { - internal enum StandardType : int { - Input = -10, - Output = -11, - Error = -12 - } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct KEY_EVENT_RECORD { - [MarshalAs(UnmanagedType.Bool)] - internal bool bKeyDown; - internal ushort wRepeatCount; - internal ushort wVirtualKeyCode; - internal ushort wVirtualScanCode; - private ushort _uChar; - internal uint dwControlKeyState; - internal readonly char UChar => (char)_uChar; - } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct INPUT_RECORD { - internal ushort EventType; - internal KEY_EVENT_RECORD keyEvent; - } - [StructLayout(LayoutKind.Sequential)] - internal struct COORD { - internal short X; - internal short Y; - } - [StructLayout(LayoutKind.Sequential)] - internal struct SMALL_RECT { - internal short Left; - internal short Top; - internal short Right; - internal short Bottom; - } - [StructLayout(LayoutKind.Sequential)] - internal struct CONSOLE_SCREEN_BUFFER_INFO { - internal COORD dwSize; - internal COORD dwCursorPosition; - internal short wAttributes; - internal SMALL_RECT srWindow; - internal COORD dwMaximumWindowSize; - } - internal const string KERNEL = "kernel32.dll"; - internal const nint INVALID_HANDLE_VALUE = -1; - - [LibraryImport(KERNEL, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool AllocConsole(); - - [LibraryImport(KERNEL, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool FreeConsole(); - - [LibraryImport(KERNEL)] - internal static partial nint GetConsoleWindow(); - [LibraryImport(KERNEL, SetLastError = true)] - internal static partial nint GetStdHandle(int nStdHandle); - - [LibraryImport(KERNEL, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool CloseHandle(nint handle); - - [LibraryImport(KERNEL, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool SetStdHandle(int nStdHandle, nint hConsoleOutput); - - [DllImport(KERNEL, CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)] - internal static extern SafeFileHandle CreateFile(string fileName, uint desiredAccess, int shareMode, nint securityAttributes, int creationDisposition, int flagsAndAttributes, nint templateFile); - - [DllImport(KERNEL, SetLastError = true, BestFitMapping = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool SetConsoleTitle(string title); - - [DllImport(KERNEL, SetLastError = true, BestFitMapping = true, CharSet = CharSet.Unicode)] - internal static extern uint GetConsoleTitle(out string lpConsoleTitle, uint nSize); - - [DllImport(KERNEL, CharSet=CharSet.Auto, SetLastError=true)] - internal static extern bool ReadConsoleInput(nint hConsoleInput, out INPUT_RECORD buffer, int numInputRecords_UseOne, out int numEventsRead); - - [LibraryImport(KERNEL, SetLastError=true)] - internal static partial uint GetConsoleOutputCP(); - - [LibraryImport(KERNEL, SetLastError=true)] - internal static partial uint GetConsoleCP(); - - [LibraryImport(KERNEL, SetLastError=true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool SetConsoleOutputCP(uint codePage); - - [LibraryImport(KERNEL, SetLastError=true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool SetConsoleCP(uint codePage); - - [LibraryImport(KERNEL, SetLastError =true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool GetConsoleScreenBufferInfo(nint hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); -} - -/// -/// A wrapper for on Windows. -/// -public class WindowsBackend : TerminalBackend { - /// - /// Creates a new Windows terminal window. - /// - /// - public WindowsBackend() { - outEnc = Encoding.UTF8; - inEnc = Encoding.UTF8; - outHandle = WinAPI.GetStdHandle((int)WinAPI.StandardType.Output); - inHandle = WinAPI.GetStdHandle((int)WinAPI.StandardType.Input); - errHandle = WinAPI.GetStdHandle((int)WinAPI.StandardType.Error); - TextWriter outStream = TextWriter.Synchronized(new StreamWriter(Utility.GetStream(WinAPI.StandardType.Output), outEnc, 256, true)); - TextReader inStream = TextReader.Synchronized(new StreamReader(Utility.GetStream(WinAPI.StandardType.Input), inEnc, false, 256, true)); - TextWriter errStream = TextWriter.Synchronized(new StreamWriter(Utility.GetStream(WinAPI.StandardType.Error), outEnc, 256, true)); - } - private TextWriter outStream; - private Encoding outEnc; - private TextReader inStream; - private Encoding inEnc; - private TextWriter errStream; - - internal nint outHandle; - internal nint inHandle; - internal nint errHandle; - - /// - public override (int Width, int Height) Size { - get { - WinAPI.CONSOLE_SCREEN_BUFFER_INFO csbi = Utility.GetBufferInfo(this); - return (csbi.srWindow.Right - csbi.srWindow.Left + 1, csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - } set { - throw new NotImplementedException(); - } - } - - /// - public override Encoding InputEncoding { get => inEnc; - set { - if (inEnc == value) return; - - if (!WinAPI.SetConsoleCP((uint)value.CodePage)) { - throw new Win32Exception("Failed to set console output code page."); - } - inEnc = value; - } - } - /// - public override Encoding OutputEncoding { get => outEnc; - set { - if (outEnc == value) return; - - outStream.Flush(); - errStream.Flush(); - - if (!WinAPI.SetConsoleOutputCP((uint)value.CodePage)) { - throw new Win32Exception("Failed to set console output code page."); - } - outEnc = value; - } - } - /// - public override Encoding ErrorEncoding { get => outEnc; set => OutputEncoding = value; } - - /// - public override TextReader StandardInput => inStream; - - /// - public override TextWriter StandardOutput => outStream; - - /// - public override TextWriter StandardError => errStream; - - /// - /// An event for when a key is released. - /// - public event KeyPressCallback? OnKeyRelease; - - // /// - // /// - // /// Gets the first 300 chars of the title. - // public override string Title { - // get { - // _ = WinAPI.GetConsoleTitle(out string title, 300); - // return title; - // } - // set { - // if (!WinAPI.SetConsoleTitle(value)) { - // throw new Win32Exception("Failed to set the title: "+Marshal.GetLastWin32Error()); - // } - // } - // } - - /// - /// - public override void Dispose() { - - } - - /// - /// - protected override void ListenForKeysMethod() { - while (listenForKeys) { - if (!WinAPI.ReadConsoleInput(consoleIn, out WinAPI.INPUT_RECORD ev, 1, out int eventsRead)) { - throw new Win32Exception("Failed to read console inputs: "+Marshal.GetLastWin32Error()); - } - - bool isKeyDown = ev.EventType == 0x0001 && ev.keyEvent.bKeyDown != false; - char ch = ev.keyEvent.UChar; - ushort keyCode = ev.keyEvent.wVirtualKeyCode; - - if (!isKeyDown) { - if (keyCode != 0x12) - continue; - } - if (ch == 0) { - if ((keyCode >= 0x10 && keyCode <= 0x12) || keyCode == 0x14 || keyCode == 0x90 || keyCode == 0x91) - continue; - } - ControlKeyState state = (ControlKeyState)ev.keyEvent.dwControlKeyState; - bool shift = (state & ControlKeyState.ShiftPressed) != 0; - bool alt = (state & (ControlKeyState.LeftAltPressed | ControlKeyState.RightAltPressed)) != 0; - bool control = (state & (ControlKeyState.LeftCtrlPressed | ControlKeyState.RightCtrlPressed)) != 0; - if (isKeyDown) { - KeyPress((ConsoleKey)keyCode, ch, alt, shift, control); - } else { - OnKeyRelease?.Invoke((ConsoleKey)keyCode, ch, alt, shift, control); - } - } - } - /// - public override (int x, int y) GetCursorPosition() { - throw new NotImplementedException(); - } - /// - /// - public override void WaitForKeyPress() { - if (!WinAPI.ReadConsoleInput(consoleIn, out _, 1, out _)) { - throw new Win32Exception("Failed to read console inputs: "+Marshal.GetLastWin32Error()); - } - } -} \ No newline at end of file diff --git a/Terminal/Terminal.cs b/Terminal/Terminal.cs index 27e71e7..1eb0c21 100644 --- a/Terminal/Terminal.cs +++ b/Terminal/Terminal.cs @@ -83,9 +83,9 @@ public static bool ListenForKeys {set { /// The name of the window /// /// - public static TerminalWindow CreateBackend(string title) { + public static TerminalWindow CreateWindow(string title) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return new WindowsBackend(title); + return new WinTerminalWindow(title); } else { throw new PlatformNotSupportedException("No window implementation for your platform."); } diff --git a/Terminal/Backend/TerminalBackend.cs b/Terminal/Window/TerminalWindow.cs similarity index 67% rename from Terminal/Backend/TerminalBackend.cs rename to Terminal/Window/TerminalWindow.cs index 382592b..eb8c257 100644 --- a/Terminal/Backend/TerminalBackend.cs +++ b/Terminal/Window/TerminalWindow.cs @@ -1,71 +1,54 @@ using System.Text; -namespace OxDED.Terminal.Backend; +namespace OxDED.Terminal.Window; /// -/// Represents an interface of common methods of a terminal. +/// Represents a terminal window that can be used. /// -public interface ITerminalBackend : IDisposable { +public abstract class TerminalWindow : IDisposable { /// - /// The data stream for reading from the terminal. + /// Sets default values. /// - public TextReader StandardInput { get; } + protected TerminalWindow() { + IsDisposed = true; + } + /// - /// The data stream for writing to the terminal. + /// The title of the Terminal window. /// - public TextWriter StandardOutput { get; } + public abstract string Title { get; set;} /// - /// The data stream for writing errors to the terminal. + /// The out (to terminal) stream. /// - public TextWriter StandardError { get; } + public abstract TextWriter Out {get;} /// - /// The encoding used for the stream (default: UTF-8). + /// The in (from terminal) stream. /// - public Encoding InputEncoding { get; set; } + public abstract TextReader In {get;} /// - /// The encoding used for the stream (default: UTF-8). + /// The error (to terminal) stream. /// - public Encoding OutputEncoding { get; set; } + public abstract TextWriter Error {get;} /// - /// The encoding used for the stream (default: UTF-8). + /// Hides or shows terminal cursor. /// - public Encoding ErrorEncoding { get; set; } + public abstract bool HideCursor {get; set;} /// - /// The width and the height (in characters) of the terminal. + /// The width (in characters) of the terminal. /// - public (int Width, int Height) Size { get; set; } - -} - -/// -/// Represents an interface of common methods of a terminal. -/// -public abstract class TerminalBackend : ITerminalBackend { - /// - public abstract TextReader StandardInput { get; } - - /// - public abstract TextWriter StandardOutput { get; } - - /// - public abstract TextWriter StandardError { get; } - - /// - public abstract Encoding InputEncoding { get; set; } - - /// - public abstract Encoding OutputEncoding { get; set; } - - /// - public abstract Encoding ErrorEncoding { get; set; } - - /// - public virtual (int Width, int Height) Size { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - /// - public abstract void Dispose(); - - + public abstract int Width {get;} + /// + /// The height (in characters) of the terminal. + /// + public abstract int Height {get;} + /// + /// The encoding used for the in stream (default: UTF-8). + /// + public abstract Encoding InEncoding {get; set;} + /// + /// The encoding used for the error and out streams (default: UTF-8). + /// + public abstract Encoding OutEncoding {get; set;} /// /// Writes something () to the terminal, with a style. /// @@ -73,7 +56,7 @@ public abstract class TerminalBackend : ITerminalBackend { /// The thing to write to the terminal. /// The text decoration to use. public virtual void Write(T? text, Style? style = null) { - StandardOutput.Write((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + Out.Write((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Writes something () to the terminal, with a style. @@ -82,7 +65,7 @@ public virtual void Write(T? text, Style? style = null) { /// The thing to write to the terminal. /// The text decoration to use. public virtual void WriteLine(T? text, Style? style = null) { - StandardOutput.WriteLine((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + Out.WriteLine((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Writes something () to the error stream, with a style. @@ -91,7 +74,7 @@ public virtual void WriteLine(T? text, Style? style = null) { /// The text to write to the error output stream. /// The style to use (default: with red foreground). public virtual void WriteErrorLine(T? text, Style? style = null) { - StandardError.WriteLine((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + Error.WriteLine((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Writes something () to the error stream, with a style. @@ -100,7 +83,7 @@ public virtual void WriteErrorLine(T? text, Style? style = null) { /// The text to write to the error output stream. /// The style to use (default: with red foreground). public virtual void WriteError(T? text, Style? style = null) { - StandardError.Write((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + Error.Write((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Sets the cursor to that position. @@ -108,11 +91,9 @@ public virtual void WriteError(T? text, Style? style = null) { /// The position. /// public virtual void Goto((int x, int y) pos) { - try { - if (pos.x >= Size.Width || pos.x < 0) { throw new ArgumentOutOfRangeException(nameof(pos), "pos x is higher than the width or is lower than 0."); } - if (pos.y >= Size.Height || pos.y < 0) { throw new ArgumentOutOfRangeException(nameof(pos), "pos y is higher than the height or is lower than 0."); } - } catch (NotImplementedException) { } - StandardOutput.Write(ANSI.MoveCursor(pos.x, pos.y)); + if (pos.x >= Width || pos.x < 0) { throw new ArgumentOutOfRangeException(nameof(pos), "pos x is higher than the width or is lower than 0."); } + if (pos.y >= Height || pos.y < 0) { throw new ArgumentOutOfRangeException(nameof(pos), "pos y is higher than the height or is lower than 0."); } + Out.Write(ANSI.MoveCursor(pos.x, pos.y)); } /// /// Gets the cursor position. @@ -122,7 +103,7 @@ public virtual void Goto((int x, int y) pos) { /// /// Sets the something () at a , with a . /// - /// The type of what to write. + /// /// The thing to set at to the terminal. /// The position to set at. /// The text decoration to use. @@ -134,7 +115,7 @@ public virtual void Set(T? text, (int x, int y) pos, Style? style = null) { /// /// Sets the something in the error stream () at a , with a . /// - /// The type of what to write. + /// /// The thing to set at to the terminal. /// The position to set at. /// The text decoration to use. @@ -147,14 +128,14 @@ public virtual void SetError(T? text, (int x, int y) pos, Style? style = null /// /// The character that has been read (-1 if everything has been read). public virtual int Read() { - return StandardInput.Read(); + return In.Read(); } /// /// Reads a line from the input stream. /// /// The line that has been read (null if everything has been read). public virtual string? ReadLine() { - return StandardInput.ReadLine(); + return In.ReadLine(); } /// /// Waits until a key is pressed. @@ -181,7 +162,7 @@ protected void KeyPress(ConsoleKey key, char keyChar, bool alt, bool shift, bool /// public virtual void Clear() { Goto((0,0)); - StandardOutput.Write(ANSI.EraseScreenFromCursor); + Out.Write(ANSI.EraseScreenFromCursor); } /// /// Clears screen from the position to end of the screen. @@ -189,7 +170,7 @@ public virtual void Clear() { /// The start position. public virtual void ClearFrom((int x, int y) pos) { Goto(pos); - StandardOutput.Write(ANSI.EraseLineFromCursor); + Out.Write(ANSI.EraseLineFromCursor); } /// /// Clears (deletes) a line. @@ -197,7 +178,7 @@ public virtual void ClearFrom((int x, int y) pos) { /// The y-axis of the line. public virtual void ClearLine(int line) { Goto((0, line)); - StandardOutput.Write(ANSI.EraseLine); + Out.Write(ANSI.EraseLine); } /// /// Clears the line from the position to the end of the line. @@ -205,7 +186,7 @@ public virtual void ClearLine(int line) { /// The start position. public virtual void ClearLineFrom((int x, int y) pos) { Goto(pos); - StandardOutput.Write(ANSI.EraseLineFromCursor); + Out.Write(ANSI.EraseLineFromCursor); } /// /// The thread that is running . @@ -229,9 +210,27 @@ public virtual bool ListenForKeys {set { } get { return listenForKeys; }} - /// /// Method in new thread that should call when a key is pressed. /// protected abstract void ListenForKeysMethod(); + + /// + /// If it already is disposed. + /// + public bool IsDisposed {get; protected set;} + /// + public virtual void Dispose() { + if (IsDisposed) { return; } + IsDisposed = true; + + GC.SuppressFinalize(this); + } + + /// + /// Disposes the window. + /// + ~TerminalWindow() { + Dispose(); + } } \ No newline at end of file diff --git a/Terminal/Window/WinTerminalWindow.cs b/Terminal/Window/WinTerminalWindow.cs new file mode 100644 index 0000000..52bf4d1 --- /dev/null +++ b/Terminal/Window/WinTerminalWindow.cs @@ -0,0 +1,266 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +namespace OxDED.Terminal.Window; + +internal static class Utils { + + internal static Stream GetStream(nint handle) { + SafeFileHandle fileHandle = new(handle, false); + FileStream stream = new(fileHandle, FileAccess.ReadWrite); + return stream; + } +} + +internal static partial class WinAPI { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct KEY_EVENT_RECORD { + [MarshalAs(UnmanagedType.Bool)] + internal bool bKeyDown; + internal ushort wRepeatCount; + internal ushort wVirtualKeyCode; + internal ushort wVirtualScanCode; + private ushort _uChar; + internal uint dwControlKeyState; + internal readonly char uChar => (char)_uChar; + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct INPUT_RECORD { + internal ushort EventType; + internal KEY_EVENT_RECORD keyEvent; + } + internal const int STD_OUTPUT_HANDLE = -11; + internal const int STD_INPUT_HANDLE = -10; + internal const int STD_ERROR_HANDLE = -12; + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool AllocConsole(); + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool FreeConsole(); + [LibraryImport("kernel32.dll")] + internal static partial nint GetConsoleWindow(); + [LibraryImport("kernel32.dll", SetLastError = true)] + internal static partial nint GetStdHandle(int nStdHandle); + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool CloseHandle(nint handle); + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool SetStdHandle(int nStdHandle, nint hConsoleOutput); + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern nint CreateFile(string fileName, uint desiredAccess, int shareMode, nint securityAttributes, int creationDisposition, int flagsAndAttributes, nint templateFile); + [DllImport("kernel32.dll", SetLastError = true, BestFitMapping = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetConsoleTitle(string title); + [DllImport("kernel32.dll", SetLastError = true, BestFitMapping = true, CharSet = CharSet.Auto)] + internal static extern uint GetConsoleTitle([MarshalAs(UnmanagedType.LPTStr)]out string lpConsoleTitle, uint nSize); + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ReadConsoleInput(nint hConsoleInput, out INPUT_RECORD lpBuffer, int nLength, out int numEventsRead); +} + +/// +/// A wrapper for on Windows. +/// +public class WinTerminalWindow : TerminalWindow { + private const string ConsoleIn = "CONIN$"; + private const string ConsoleOut = "CONOUT$"; + private const string ConsoleError = ConsoleOut; + + private nint consoleOut; + private nint consoleIn; + private nint consoleErr; + /// + /// Creates a new Windows terminal window. + /// + /// The name of the window. + /// + public WinTerminalWindow(string title) { + nint stdOut = WinAPI.GetStdHandle(WinAPI.STD_OUTPUT_HANDLE); + nint stdIn = WinAPI.GetStdHandle(WinAPI.STD_INPUT_HANDLE); + nint stdErr = WinAPI.GetStdHandle(WinAPI.STD_ERROR_HANDLE); + + if (WinAPI.GetConsoleWindow() == nint.Zero) { + if (!WinAPI.AllocConsole()) { + throw new Win32Exception("Failed to allocate a console: "+Marshal.GetLastWin32Error()); + } + } + + consoleOut = WinAPI.CreateFile(ConsoleOut, 0x80000000 | 0x40000000, 2, nint.Zero, 3, 0, nint.Zero); + consoleIn = WinAPI.CreateFile(ConsoleIn, 0x80000000 | 0x40000000, 1, nint.Zero, 3, 0, nint.Zero); + consoleErr = WinAPI.CreateFile(ConsoleError, 0x80000000 | 0x40000000, 2, nint.Zero, 3, 0, nint.Zero); + + if (!WinAPI.SetStdHandle(WinAPI.STD_OUTPUT_HANDLE, consoleOut)) { + throw new Win32Exception("Failed to set the handle for the console out stream: "+Marshal.GetLastWin32Error()); + } + + if (!WinAPI.SetStdHandle(WinAPI.STD_INPUT_HANDLE, consoleIn)) { + throw new Win32Exception("Failed to set the handle for the console in stream: "+Marshal.GetLastWin32Error()); + } + + if (!WinAPI.SetStdHandle(WinAPI.STD_ERROR_HANDLE, consoleErr)) { + throw new Win32Exception("Failed to set the handle for the console error stream: "+Marshal.GetLastWin32Error()); + } + + if (!WinAPI.CloseHandle(stdOut)) { + throw new Win32Exception("Failed to close the handle of stdOut: "+Marshal.GetLastWin32Error()); + } + if (!WinAPI.CloseHandle(stdIn)) { + throw new Win32Exception("Failed to close the handle of stdIn: "+Marshal.GetLastWin32Error()); + } + if (!WinAPI.CloseHandle(stdErr)) { + throw new Win32Exception("Failed to close the handle of stdErr: "+Marshal.GetLastWin32Error()); + } + + outStream = new StreamWriter(Utils.GetStream(consoleOut), Encoding.UTF8); + inStream = new StreamReader(Utils.GetStream(consoleIn), Encoding.UTF8); + errStream = new StreamWriter(Utils.GetStream(consoleErr), Encoding.UTF8); + + Title = title; + } + private StreamWriter outStream; + /// + public override TextWriter Out { get => outStream;} + private StreamReader inStream; + /// + public override TextReader In { get => inStream;} + private StreamWriter errStream; + /// + public override TextWriter Error { get => errStream;} + + /// + public override int Width => Console.WindowWidth; + + /// + public override int Height => Console.WindowHeight; + /// + public override Encoding InEncoding { get => inStream.CurrentEncoding; set { inStream = new StreamReader(Utils.GetStream(consoleIn), value); } } + /// + public override Encoding OutEncoding { get => outStream.Encoding; set { outStream = new StreamWriter(Utils.GetStream(consoleOut), value); errStream = new StreamWriter(Utils.GetStream(consoleErr), value); } } + /// + /// An event for when a key is released. + /// + public event KeyPressCallback? OnKeyRelease; + + /// + /// + /// Gets the first 300 chars of the title. + public override string Title { + get { + _ = WinAPI.GetConsoleTitle(out string title, 300); + return title; + } + set { + if (!WinAPI.SetConsoleTitle(value)) { + throw new Win32Exception("Failed to set the title: "+Marshal.GetLastWin32Error()); + } + } + } + + private bool isCursorHidden = false; + + /// + public override bool HideCursor { + set { + if (value) { + outStream.Write(ANSI.CursorInvisible); + } else { + outStream.Write(ANSI.CursorVisible); + } + isCursorHidden = value; + } get { + return isCursorHidden; + } + } + + /// + /// + public override void Dispose() { + if (IsDisposed) { return; } + + if (!WinAPI.CloseHandle(consoleOut)) { + throw new Win32Exception("Failed to close console out: "+Marshal.GetLastWin32Error()); + } + consoleOut = nint.Zero; + if (!WinAPI.CloseHandle(consoleIn)) { + throw new Win32Exception("Failed to close console in: "+Marshal.GetLastWin32Error()); + } + consoleIn = nint.Zero; + if (!WinAPI.CloseHandle(consoleErr)) { + throw new Win32Exception("Failed to close console err: "+Marshal.GetLastWin32Error()); + } + consoleErr = nint.Zero; + + if (!WinAPI.FreeConsole()) { + throw new Win32Exception("Failed to free the console window: "+Marshal.GetLastWin32Error()); + } + Console.SetError(new StreamWriter(Console.OpenStandardError())); + Console.SetIn(new StreamReader(Console.OpenStandardInput())); + Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); + + IsDisposed = true; + GC.SuppressFinalize(this); + } + + /// + /// + protected override void ListenForKeysMethod() { + while (listenForKeys) { + if (!WinAPI.ReadConsoleInput(consoleIn, out WinAPI.INPUT_RECORD ev, 1, out int eventsRead)) { + throw new Win32Exception("Failed to read console inputs: "+Marshal.GetLastWin32Error()); + } + + bool isKeyDown = ev.EventType == 0x0001 && ev.keyEvent.bKeyDown != false; + char ch = ev.keyEvent.uChar; + ushort keyCode = ev.keyEvent.wVirtualKeyCode; + + if (!isKeyDown) { + if (keyCode != 0x12) + continue; + } + if (ch == 0) { + if ((keyCode >= 0x10 && keyCode <= 0x12) || keyCode == 0x14 || keyCode == 0x90 || keyCode == 0x91) + continue; + } + ControlKeyState state = (ControlKeyState)ev.keyEvent.dwControlKeyState; + bool shift = (state & ControlKeyState.ShiftPressed) != 0; + bool alt = (state & (ControlKeyState.LeftAltPressed | ControlKeyState.RightAltPressed)) != 0; + bool control = (state & (ControlKeyState.LeftCtrlPressed | ControlKeyState.RightCtrlPressed)) != 0; + if (isKeyDown) { + KeyPress((ConsoleKey)keyCode, ch, alt, shift, control); + } else { + OnKeyRelease?.Invoke((ConsoleKey)keyCode, ch, alt, shift, control); + } + } + } + /// + public override (int x, int y) GetCursorPosition() { + throw new NotImplementedException(); + } + /// + /// + public override void WaitForKeyPress() { + if (!WinAPI.ReadConsoleInput(consoleIn, out _, 1, out _)) { + throw new Win32Exception("Failed to read console inputs: "+Marshal.GetLastWin32Error()); + } + } + + [Flags] + internal enum ControlKeyState { + RightAltPressed = 0x0001, + LeftAltPressed = 0x0002, + RightCtrlPressed = 0x0004, + LeftCtrlPressed = 0x0008, + ShiftPressed = 0x0010, + NumLockOn = 0x0020, + ScrollLockOn = 0x0040, + CapsLockOn = 0x0080, + EnhancedKey = 0x0100 + } +} \ No newline at end of file diff --git a/Terminal/Window/Window.cs b/Terminal/Window/Window.cs deleted file mode 100644 index 08130ec..0000000 --- a/Terminal/Window/Window.cs +++ /dev/null @@ -1,13 +0,0 @@ -using OxDED.Terminal.Backend; - -namespace OxDED.Terminal.Window; - -/// -/// Represents a terminal window. -/// -public interface IWindow : ITerminalBackend { - /// - /// The title of the terminal window. - /// - public string Title { get; set; } -} \ No newline at end of file