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