From ab19baae8ea82dc462480f9239a52fb494b26a78 Mon Sep 17 00:00:00 2001 From: dedouwe26 <63008025+dedouwe26@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:47:22 +0100 Subject: [PATCH] quick --- .../TerminalBackend.cs} | 133 ++++---- Terminal/Backend/WindowsBackend.cs | 283 ++++++++++++++++++ Terminal/Terminal.cs | 4 +- Terminal/Window/WinTerminalWindow.cs | 266 ---------------- Terminal/Window/Window.cs | 13 + 5 files changed, 365 insertions(+), 334 deletions(-) rename Terminal/{Window/TerminalWindow.cs => Backend/TerminalBackend.cs} (67%) create mode 100644 Terminal/Backend/WindowsBackend.cs delete mode 100644 Terminal/Window/WinTerminalWindow.cs create mode 100644 Terminal/Window/Window.cs diff --git a/Terminal/Window/TerminalWindow.cs b/Terminal/Backend/TerminalBackend.cs similarity index 67% rename from Terminal/Window/TerminalWindow.cs rename to Terminal/Backend/TerminalBackend.cs index eb8c257..382592b 100644 --- a/Terminal/Window/TerminalWindow.cs +++ b/Terminal/Backend/TerminalBackend.cs @@ -1,54 +1,71 @@ using System.Text; -namespace OxDED.Terminal.Window; +namespace OxDED.Terminal.Backend; /// -/// Represents a terminal window that can be used. +/// Represents an interface of common methods of a terminal. /// -public abstract class TerminalWindow : IDisposable { +public interface ITerminalBackend : IDisposable { /// - /// Sets default values. + /// The data stream for reading from the terminal. /// - protected TerminalWindow() { - IsDisposed = true; - } - - /// - /// The title of the Terminal window. - /// - public abstract string Title { get; set;} - /// - /// The out (to terminal) stream. - /// - public abstract TextWriter Out {get;} - /// - /// The in (from terminal) stream. - /// - public abstract TextReader In {get;} + public TextReader StandardInput { get; } /// - /// The error (to terminal) stream. + /// The data stream for writing to the terminal. /// - public abstract TextWriter Error {get;} + public TextWriter StandardOutput { get; } /// - /// Hides or shows terminal cursor. + /// The data stream for writing errors to the terminal. /// - public abstract bool HideCursor {get; set;} + public TextWriter StandardError { get; } /// - /// The width (in characters) of the terminal. + /// The encoding used for the stream (default: UTF-8). /// - public abstract int Width {get;} + public Encoding InputEncoding { get; set; } /// - /// The height (in characters) of the terminal. + /// The encoding used for the stream (default: UTF-8). /// - public abstract int Height {get;} + public Encoding OutputEncoding { get; set; } /// - /// The encoding used for the in stream (default: UTF-8). + /// The encoding used for the stream (default: UTF-8). /// - public abstract Encoding InEncoding {get; set;} + public Encoding ErrorEncoding { get; set; } /// - /// The encoding used for the error and out streams (default: UTF-8). + /// The width and the height (in characters) of the terminal. /// - public abstract Encoding OutEncoding {get; set;} + 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(); + + /// /// Writes something () to the terminal, with a style. /// @@ -56,7 +73,7 @@ protected TerminalWindow() { /// The thing to write to the terminal. /// The text decoration to use. public virtual void Write(T? text, Style? style = null) { - Out.Write((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + StandardOutput.Write((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Writes something () to the terminal, with a style. @@ -65,7 +82,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) { - Out.WriteLine((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + StandardOutput.WriteLine((style ?? new Style()).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Writes something () to the error stream, with a style. @@ -74,7 +91,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) { - Error.WriteLine((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + StandardError.WriteLine((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Writes something () to the error stream, with a style. @@ -83,7 +100,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) { - Error.Write((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); + StandardError.Write((style ?? new Style {ForegroundColor = Colors.Red}).ToANSI()+text?.ToString()+ANSI.Styles.ResetAll); } /// /// Sets the cursor to that position. @@ -91,9 +108,11 @@ public virtual void WriteError(T? text, Style? style = null) { /// The position. /// public virtual void Goto((int x, int y) pos) { - 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)); + 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)); } /// /// Gets the cursor position. @@ -103,7 +122,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. @@ -115,7 +134,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. @@ -128,14 +147,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 In.Read(); + return StandardInput.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 In.ReadLine(); + return StandardInput.ReadLine(); } /// /// Waits until a key is pressed. @@ -162,7 +181,7 @@ protected void KeyPress(ConsoleKey key, char keyChar, bool alt, bool shift, bool /// public virtual void Clear() { Goto((0,0)); - Out.Write(ANSI.EraseScreenFromCursor); + StandardOutput.Write(ANSI.EraseScreenFromCursor); } /// /// Clears screen from the position to end of the screen. @@ -170,7 +189,7 @@ public virtual void Clear() { /// The start position. public virtual void ClearFrom((int x, int y) pos) { Goto(pos); - Out.Write(ANSI.EraseLineFromCursor); + StandardOutput.Write(ANSI.EraseLineFromCursor); } /// /// Clears (deletes) a line. @@ -178,7 +197,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)); - Out.Write(ANSI.EraseLine); + StandardOutput.Write(ANSI.EraseLine); } /// /// Clears the line from the position to the end of the line. @@ -186,7 +205,7 @@ public virtual void ClearLine(int line) { /// The start position. public virtual void ClearLineFrom((int x, int y) pos) { Goto(pos); - Out.Write(ANSI.EraseLineFromCursor); + StandardOutput.Write(ANSI.EraseLineFromCursor); } /// /// The thread that is running . @@ -210,27 +229,9 @@ 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/Backend/WindowsBackend.cs b/Terminal/Backend/WindowsBackend.cs new file mode 100644 index 0000000..834ef8c --- /dev/null +++ b/Terminal/Backend/WindowsBackend.cs @@ -0,0 +1,283 @@ +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 1eb0c21..27e71e7 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 CreateWindow(string title) { + public static TerminalWindow CreateBackend(string title) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return new WinTerminalWindow(title); + return new WindowsBackend(title); } else { throw new PlatformNotSupportedException("No window implementation for your platform."); } diff --git a/Terminal/Window/WinTerminalWindow.cs b/Terminal/Window/WinTerminalWindow.cs deleted file mode 100644 index 52bf4d1..0000000 --- a/Terminal/Window/WinTerminalWindow.cs +++ /dev/null @@ -1,266 +0,0 @@ -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 new file mode 100644 index 0000000..08130ec --- /dev/null +++ b/Terminal/Window/Window.cs @@ -0,0 +1,13 @@ +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