From ea199d1f85bc3b28e6f54bc89a56bd08158b724f Mon Sep 17 00:00:00 2001 From: Robin Hovind Date: Sat, 30 Aug 2025 20:46:04 +0200 Subject: [PATCH 1/3] feat: first stab at adding cross-platform support for the console/input handler --- ConsoleEngine/GameBase.cs | 4 + ConsoleEngine/Infrastructure/Inputs/Input.cs | 7 +- .../ConsoleEngine.Windows.csproj | 5 +- .../CrossPlatformConsoleHandler.cs | 73 +++++++++++++++++++ .../CrossPlatformInputHandler.cs | 52 +++++++++++++ .../LowLevel/Kernel32.cs | 21 ++++++ 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 Libs/ConsoleEngine.Windows/CrossPlatformConsoleHandler.cs create mode 100644 Libs/ConsoleEngine.Windows/CrossPlatformInputHandler.cs diff --git a/ConsoleEngine/GameBase.cs b/ConsoleEngine/GameBase.cs index 49de7e7..b154d4f 100644 --- a/ConsoleEngine/GameBase.cs +++ b/ConsoleEngine/GameBase.cs @@ -32,7 +32,11 @@ protected GameBase(int width, int height, int fontWidth, int fontHeight) protected GameBase(int width, int height, FontInfo fontInfo) { +#if WINDOWS_PLATFORM _console = new RenderConsole(new ConsoleHandler(width, height, fontInfo)); +#else + _console = new RenderConsole(new CrossPlatformConsoleHandler(width, height, fontInfo)); +#endif Name = "Game"; Scenes = new SceneManager(this); } diff --git a/ConsoleEngine/Infrastructure/Inputs/Input.cs b/ConsoleEngine/Infrastructure/Inputs/Input.cs index b5a15bc..f9742c8 100644 --- a/ConsoleEngine/Infrastructure/Inputs/Input.cs +++ b/ConsoleEngine/Infrastructure/Inputs/Input.cs @@ -13,8 +13,13 @@ public class Input //********************************************************** public static readonly Input Instance = new(); + +#if WINDOWS_PLATFORM private static readonly IInputHandler Handler = new InputHandler(); - +#else + private static readonly IInputHandler Handler = new CrossPlatformInputHandler(); +#endif + //********************************************************** //** ctor: //********************************************************** diff --git a/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj b/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj index d2ff72f..7c4ebcb 100644 --- a/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj +++ b/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj @@ -8,8 +8,11 @@ AnyCPU True - + + $(DefineConstants);WINDOWS_PLATFORM + + diff --git a/Libs/ConsoleEngine.Windows/CrossPlatformConsoleHandler.cs b/Libs/ConsoleEngine.Windows/CrossPlatformConsoleHandler.cs new file mode 100644 index 0000000..cf213c0 --- /dev/null +++ b/Libs/ConsoleEngine.Windows/CrossPlatformConsoleHandler.cs @@ -0,0 +1,73 @@ +using System; +using System.Runtime.InteropServices; +using ConsoleEngine.Abstractions.Rendering; +using ConsoleEngine.Native.LowLevel; + +namespace ConsoleEngine.Native; + +using static Kernel32; +using static User32; + +public sealed class CrossPlatformConsoleHandler : IConsoleHandler +{ + private readonly FontInfo _fontInfo; + public CrossPlatformConsoleHandler(int width, int height, FontInfo fontInfo) + { + _fontInfo = fontInfo; + Width = width; + Height = height; + } + + public int Width { get; } + public int Height { get; } + + public void InitializeConsole() + { + Console.Clear(); + SetFont(_fontInfo); + } + + public void SetTitle(string title) + { + Console.Title = title; + } + + public void SetCursorVisible(bool visible) + { + try { Console.CursorVisible = visible; } catch { } + } + + public void Resizable(bool resizable) + { + // Not yet supported + } + + public void SetFont(FontInfo fontInfo) + { + // Not yet supported + } + + public void Render(Span pixels) + { + Console.SetCursorPosition(0, 0); + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + int i = y * Width + x; + if (i < pixels.Length) + Console.Write(pixels[i].Char); + else + Console.Write(' '); + } + Console.WriteLine(); + } + } + + public void Close() + { + // Not yet supported + } + + private static ushort GetColorValue(ConsoleColor fg, ConsoleColor bg) => (ushort)((int)fg | ((int)bg<<4)); +} \ No newline at end of file diff --git a/Libs/ConsoleEngine.Windows/CrossPlatformInputHandler.cs b/Libs/ConsoleEngine.Windows/CrossPlatformInputHandler.cs new file mode 100644 index 0000000..92ef612 --- /dev/null +++ b/Libs/ConsoleEngine.Windows/CrossPlatformInputHandler.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ConsoleEngine.Abstractions.Inputs; + +namespace ConsoleEngine.Native; + +using static LowLevel.Kernel32; + +public class CrossPlatformInputHandler : IInputHandler +{ + private readonly int[] _previousState = new int[256]; + private readonly int[] _newState = new int[256]; + private readonly KeyState[] _keyStates = new KeyState[256]; + + public KeyState GetKey(int id) + { + return _keyStates[id]; + } + + public IEnumerable GetPressedKeyCodes() => _keyStates.Where(k => k.Pressed).Select(k => k.Index); + + public void Update() + { + bool[] pressedThisFrame = new bool[_keyStates.Length]; + while (Console.KeyAvailable) + { + var keyInfo = Console.ReadKey(true); + int key = (int)keyInfo.Key; + _keyStates[key].Pressed = true; + _keyStates[key].Held = true; + _keyStates[key].Index = key; + pressedThisFrame[key] = true; + } + for (int i = 0; i < _keyStates.Length; i++) + { + if (!pressedThisFrame[i]) + { + if (_keyStates[i].Held) + { + _keyStates[i].Released = true; + } + else + { + _keyStates[i].Released = false; + } + _keyStates[i].Held = false; + _keyStates[i].Pressed = false; + } + } + } +} \ No newline at end of file diff --git a/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs b/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs index cd347c0..1ee526a 100644 --- a/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs +++ b/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs @@ -11,6 +11,7 @@ namespace ConsoleEngine.Native.LowLevel; /// internal static class Kernel32 { +#if WINDOWS_PLATFORM [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern IntPtr GetStdHandle(StdHandle nStdHandle); @@ -67,4 +68,24 @@ internal static class Kernel32 [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseHandle(IntPtr hObject); +#else + // Unix/macOS/Linux + // Can´t deal with Kernel32 calls, so void them + internal static IntPtr GetStdHandle(StdHandle nStdHandle) => IntPtr.Zero; + internal static bool SetConsoleActiveScreenBuffer(IntPtr hConsoleOutput) => false; + internal static IntPtr CreateConsoleScreenBuffer(GenericRights dwDesiredAccess, uint dwShareMode, IntPtr securityAttributes, uint flags, IntPtr screenBufferData) => IntPtr.Zero; + internal static bool WriteConsoleOutputCharacter(IntPtr screenBuffer, string characters, uint length, Coord writeCoord, out UInt32 numCharsWritten) { numCharsWritten = 0; return false; } + internal static bool WriteConsoleOutput(IntPtr hConsoleOutput, CharInfo[] lpBuffer, Coord dwBufferSize, Coord dwBufferCoord, ref SmallRect lpWriteRegion) => false; + internal static IntPtr GetConsoleWindow() => IntPtr.Zero; + internal static int SetCurrentConsoleFontEx(IntPtr consoleOutput, bool maximumWindow, ref ConsoleFontInfoEx consoleCurrentFontEx) => 0; + internal static bool SetConsoleWindowInfo(IntPtr hConsoleOutput, bool bAbsolute, ref SmallRect lpConsoleWindow) => false; + internal static bool SetConsoleScreenBufferSize(IntPtr hConsoleOutput, Coord dwSize) => false; + internal static int GetAsyncKeyState(int vKeys) => 0; + internal static bool SetConsoleOutputCP(uint wCodePageID) => false; + internal static bool SetConsoleCP(uint wCodePageID) => false; + internal static bool SetConsoleTitle(char[] lpConsoleTitle) { try { Console.Title = new string(lpConsoleTitle); return true; } catch { return false; } } + internal static bool GetConsoleCursorInfo(IntPtr hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo) { lpConsoleCursorInfo = default; return false; } + internal static bool SetConsoleCursorInfo(IntPtr hConsoleOutput, ref ConsoleCursorInfo lpConsoleCursorInfo) => false; + internal static bool CloseHandle(IntPtr hObject) => false; +#endif } \ No newline at end of file From 4c6a83d8e5ac3dcf2e64fcb5e7ee638880cc51d9 Mon Sep 17 00:00:00 2001 From: Robin Hovind Date: Mon, 1 Sep 2025 08:24:36 +0200 Subject: [PATCH 2/3] fix(refactor): move crossplatform input/rendering into separate cross-platform project --- ConsoleEngine.sln | 171 +++++++++++++++--- ConsoleEngine/ConsoleEngine.csproj | 4 + ConsoleEngine/GameBase.cs | 1 + ConsoleEngine/Infrastructure/Inputs/Input.cs | 1 + .../ConsoleEngine.CrossPlatform.csproj | 18 ++ .../CrossPlatformConsoleHandler.cs | 7 +- .../CrossPlatformInputHandler.cs | 4 +- 7 files changed, 174 insertions(+), 32 deletions(-) create mode 100644 Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj rename Libs/{ConsoleEngine.Windows => ConsoleEngine.CrossPlatform}/CrossPlatformConsoleHandler.cs (90%) rename Libs/{ConsoleEngine.Windows => ConsoleEngine.CrossPlatform}/CrossPlatformInputHandler.cs (95%) diff --git a/ConsoleEngine.sln b/ConsoleEngine.sln index 76e8fb4..5dac93b 100644 --- a/ConsoleEngine.sln +++ b/ConsoleEngine.sln @@ -32,69 +32,194 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerraForM.Tests", "Examples EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples.Tests", "Examples.Tests", "{106CCD57-88BD-4B0A-A67F-A5DA7D8FCA3C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pong", "Examples\Pong\Pong.csproj", "{DE96766F-0853-439F-A054-A8B2EDC10DA6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleEngine.CrossPlatform", "Libs\ConsoleEngine.CrossPlatform\ConsoleEngine.CrossPlatform.csproj", "{F5F065A9-F0B1-45B6-B4BC-5912BAB09803}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.Build.0 = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|Any CPU.Build.0 = Release|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x64.ActiveCfg = Release|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x64.Build.0 = Release|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x86.ActiveCfg = Release|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x86.Build.0 = Release|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.Build.0 = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.Build.0 = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x64.ActiveCfg = Debug|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x64.Build.0 = Debug|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x86.Build.0 = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.Build.0 = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x64.ActiveCfg = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x64.Build.0 = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x86.ActiveCfg = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x86.Build.0 = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x64.ActiveCfg = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x64.Build.0 = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x86.ActiveCfg = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x86.Build.0 = Debug|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|Any CPU.Build.0 = Release|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x64.ActiveCfg = Release|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x64.Build.0 = Release|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x86.ActiveCfg = Release|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x86.Build.0 = Release|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x64.ActiveCfg = Debug|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x64.Build.0 = Debug|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x86.ActiveCfg = Debug|Any CPU + {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x86.Build.0 = Debug|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|Any CPU.Build.0 = Release|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x64.ActiveCfg = Release|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x64.Build.0 = Release|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x86.ActiveCfg = Release|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x86.Build.0 = Release|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.Build.0 = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.Build.0 = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x64.Build.0 = Debug|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x86.Build.0 = Debug|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|Any CPU.Build.0 = Release|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x64.ActiveCfg = Release|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x64.Build.0 = Release|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x86.ActiveCfg = Release|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x86.Build.0 = Release|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x64.ActiveCfg = Debug|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x64.Build.0 = Debug|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x86.ActiveCfg = Debug|Any CPU + {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x86.Build.0 = Debug|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|Any CPU.Build.0 = Release|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x64.ActiveCfg = Release|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x64.Build.0 = Release|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x86.ActiveCfg = Release|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x86.Build.0 = Release|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x64.ActiveCfg = Debug|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x64.Build.0 = Debug|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x86.ActiveCfg = Debug|Any CPU + {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x86.Build.0 = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.Build.0 = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x64.ActiveCfg = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x64.Build.0 = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x86.ActiveCfg = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x86.Build.0 = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x64.Build.0 = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x86.Build.0 = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.Build.0 = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x64.ActiveCfg = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x64.Build.0 = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x86.ActiveCfg = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x86.Build.0 = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x64.Build.0 = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x86.ActiveCfg = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x86.Build.0 = Debug|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.Build.0 = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x64.ActiveCfg = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x64.Build.0 = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x86.ActiveCfg = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x86.Build.0 = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x64.ActiveCfg = Debug|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x64.Build.0 = Debug|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x86.ActiveCfg = Debug|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x86.Build.0 = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.Build.0 = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x64.ActiveCfg = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x64.Build.0 = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x86.ActiveCfg = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x86.Build.0 = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x64.ActiveCfg = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x64.Build.0 = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x86.Build.0 = Debug|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|Any CPU.ActiveCfg = Release|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|Any CPU.Build.0 = Release|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x64.ActiveCfg = Release|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x64.Build.0 = Release|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x86.ActiveCfg = Release|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x86.Build.0 = Release|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x64.ActiveCfg = Debug|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x64.Build.0 = Debug|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x86.ActiveCfg = Debug|Any CPU + {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x86.Build.0 = Debug|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|Any CPU.Build.0 = Release|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x64.ActiveCfg = Release|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x64.Build.0 = Release|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x86.ActiveCfg = Release|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x86.Build.0 = Release|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x64.Build.0 = Debug|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x86.Build.0 = Debug|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|Any CPU.Build.0 = Release|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x64.ActiveCfg = Release|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x64.Build.0 = Release|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x86.ActiveCfg = Release|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x86.Build.0 = Release|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x64.ActiveCfg = Debug|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x64.Build.0 = Debug|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x86.ActiveCfg = Debug|Any CPU + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x86.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} {99309207-1847-4EB9-A009-C7B92BC2CE64} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} + {CA22425D-FB6C-45C6-A14C-561DF6D969B6} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} {33AA545F-7515-406C-A749-EB0A02668F4A} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} {DE0311EB-079F-41B3-9D87-604AAB6F2F65} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {BC3C7768-0401-47F4-931A-76BADFE6DC26} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {276379A8-897A-4703-A18D-9DB3A08A09AA} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} - {87EB68ED-18DD-41C9-8A33-B305C4DF210F} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} - {CA22425D-FB6C-45C6-A14C-561DF6D969B6} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} {A4209E56-1ECD-4616-BB13-DF77864F462C} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} + {87EB68ED-18DD-41C9-8A33-B305C4DF210F} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {0CDE38D4-12F4-426B-9252-E57820FF8D9B} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA} = {106CCD57-88BD-4B0A-A67F-A5DA7D8FCA3C} + {DE96766F-0853-439F-A054-A8B2EDC10DA6} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} + {F5F065A9-F0B1-45B6-B4BC-5912BAB09803} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} EndGlobalSection EndGlobal diff --git a/ConsoleEngine/ConsoleEngine.csproj b/ConsoleEngine/ConsoleEngine.csproj index 4d61738..d0559d3 100644 --- a/ConsoleEngine/ConsoleEngine.csproj +++ b/ConsoleEngine/ConsoleEngine.csproj @@ -15,8 +15,12 @@ + + + $(DefineConstants);WINDOWS_PLATFORM + diff --git a/ConsoleEngine/GameBase.cs b/ConsoleEngine/GameBase.cs index b154d4f..652e8e4 100644 --- a/ConsoleEngine/GameBase.cs +++ b/ConsoleEngine/GameBase.cs @@ -6,6 +6,7 @@ using ConsoleEngine.Infrastructure.Rendering; using ConsoleEngine.Infrastructure.Scenery; using ConsoleEngine.Native; +using ConsoleEngine.CrossPlatform; namespace ConsoleEngine; diff --git a/ConsoleEngine/Infrastructure/Inputs/Input.cs b/ConsoleEngine/Infrastructure/Inputs/Input.cs index f9742c8..c36f917 100644 --- a/ConsoleEngine/Infrastructure/Inputs/Input.cs +++ b/ConsoleEngine/Infrastructure/Inputs/Input.cs @@ -3,6 +3,7 @@ using System.Linq; using ConsoleEngine.Abstractions.Inputs; using ConsoleEngine.Native; +using ConsoleEngine.CrossPlatform; namespace ConsoleEngine.Infrastructure.Inputs; diff --git a/Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj b/Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj new file mode 100644 index 0000000..f41b3d3 --- /dev/null +++ b/Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + 12 + ConsoleEngine.CrossPlatform + Release;Debug + AnyCPU + True + + + $(DefineConstants);WINDOWS_PLATFORM + + + + + + diff --git a/Libs/ConsoleEngine.Windows/CrossPlatformConsoleHandler.cs b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformConsoleHandler.cs similarity index 90% rename from Libs/ConsoleEngine.Windows/CrossPlatformConsoleHandler.cs rename to Libs/ConsoleEngine.CrossPlatform/CrossPlatformConsoleHandler.cs index cf213c0..26c272d 100644 --- a/Libs/ConsoleEngine.Windows/CrossPlatformConsoleHandler.cs +++ b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformConsoleHandler.cs @@ -1,13 +1,8 @@ using System; -using System.Runtime.InteropServices; using ConsoleEngine.Abstractions.Rendering; -using ConsoleEngine.Native.LowLevel; -namespace ConsoleEngine.Native; +namespace ConsoleEngine.CrossPlatform; -using static Kernel32; -using static User32; - public sealed class CrossPlatformConsoleHandler : IConsoleHandler { private readonly FontInfo _fontInfo; diff --git a/Libs/ConsoleEngine.Windows/CrossPlatformInputHandler.cs b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformInputHandler.cs similarity index 95% rename from Libs/ConsoleEngine.Windows/CrossPlatformInputHandler.cs rename to Libs/ConsoleEngine.CrossPlatform/CrossPlatformInputHandler.cs index 92ef612..2fafa64 100644 --- a/Libs/ConsoleEngine.Windows/CrossPlatformInputHandler.cs +++ b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformInputHandler.cs @@ -3,10 +3,8 @@ using System.Linq; using ConsoleEngine.Abstractions.Inputs; -namespace ConsoleEngine.Native; +namespace ConsoleEngine.CrossPlatform; -using static LowLevel.Kernel32; - public class CrossPlatformInputHandler : IInputHandler { private readonly int[] _previousState = new int[256]; From 06b2b4964770d45ad52bdbd850c5eb8dc73c7ebd Mon Sep 17 00:00:00 2001 From: Robin Hovind Date: Thu, 22 Oct 2026 09:13:19 +0200 Subject: [PATCH 3/3] feat: runtime platform detection, ANSI renderer and macOS keyboard input (Claude Opus 5.5) - Pick Windows/cross-platform handlers at runtime (OperatingSystem.IsWindows) instead of compile time $(OS) defines; revert the Kernel32 stubs - Rewrite CrossPlatformConsoleHandler: alternate screen, no autowrap, diffed frames written in a single write, synchronized updates, truecolor/16 color output, clipping to the terminal size, terminal restored on exit/Ctrl+C - Read real key up/down state on macOS through CoreGraphics, calibrated against terminal input so it falls back to the terminal when macOS lacks Input Monitoring permission - Terminal input fallback that treats keys as held until auto repeat stops - Launch the logger in a new Terminal window on macOS, don't crash if it can't start - Remove the missing Pong project from the solution - Build and test on windows, macos and ubuntu in CI Code made by Claude (Opus 5.5) --- .github/workflows/build-and-test.yml | 7 +- ConsoleEngine.sln | 164 ++--------- ConsoleEngine/ConsoleEngine.csproj | 3 - ConsoleEngine/GameBase.cs | 11 +- ConsoleEngine/Infrastructure/Inputs/Input.cs | 9 +- ConsoleEngine/Infrastructure/Logging/Log.cs | 38 ++- .../ConsoleEngine.CrossPlatform.csproj | 3 - .../CrossPlatformConsoleHandler.cs | 276 ++++++++++++++++-- .../CrossPlatformInputHandler.cs | 161 ++++++++-- .../MacKeyboard.cs | 144 +++++++++ .../ConsoleEngine.Windows.csproj | 5 +- .../LowLevel/Kernel32.cs | 21 -- README.md | 16 + 13 files changed, 629 insertions(+), 229 deletions(-) create mode 100644 Libs/ConsoleEngine.CrossPlatform/MacKeyboard.cs diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 55b1271..bea0897 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,7 +9,12 @@ on: jobs: build: - runs-on: windows-latest + strategy: + fail-fast: false + matrix: + os: [ windows-latest, macos-latest, ubuntu-latest ] + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/ConsoleEngine.sln b/ConsoleEngine.sln index 5dac93b..a0b46c8 100644 --- a/ConsoleEngine.sln +++ b/ConsoleEngine.sln @@ -32,194 +32,76 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerraForM.Tests", "Examples EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples.Tests", "Examples.Tests", "{106CCD57-88BD-4B0A-A67F-A5DA7D8FCA3C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pong", "Examples\Pong\Pong.csproj", "{DE96766F-0853-439F-A054-A8B2EDC10DA6}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleEngine.CrossPlatform", "Libs\ConsoleEngine.CrossPlatform\ConsoleEngine.CrossPlatform.csproj", "{F5F065A9-F0B1-45B6-B4BC-5912BAB09803}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.Build.0 = Release|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|Any CPU.Build.0 = Release|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x64.ActiveCfg = Release|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x64.Build.0 = Release|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x86.ActiveCfg = Release|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Release|x86.Build.0 = Release|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x64.ActiveCfg = Debug|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x64.Build.0 = Debug|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x86.ActiveCfg = Debug|Any CPU - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F}.Debug|x86.Build.0 = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.Build.0 = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x64.ActiveCfg = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x64.Build.0 = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x86.ActiveCfg = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|x86.Build.0 = Release|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x64.ActiveCfg = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x64.Build.0 = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x86.ActiveCfg = Debug|Any CPU - {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|x86.Build.0 = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.Build.0 = Release|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.Build.0 = Release|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|Any CPU.Build.0 = Release|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x64.ActiveCfg = Release|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x64.Build.0 = Release|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x86.ActiveCfg = Release|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Release|x86.Build.0 = Release|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x64.ActiveCfg = Debug|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x64.Build.0 = Debug|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x86.ActiveCfg = Debug|Any CPU - {33AA545F-7515-406C-A749-EB0A02668F4A}.Debug|x86.Build.0 = Debug|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|Any CPU.Build.0 = Release|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x64.ActiveCfg = Release|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x64.Build.0 = Release|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x86.ActiveCfg = Release|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Release|x86.Build.0 = Release|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x64.ActiveCfg = Debug|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x64.Build.0 = Debug|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x86.ActiveCfg = Debug|Any CPU - {DE0311EB-079F-41B3-9D87-604AAB6F2F65}.Debug|x86.Build.0 = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Release|Any CPU.Build.0 = Release|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99309207-1847-4EB9-A009-C7B92BC2CE64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.Build.0 = Release|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|Any CPU.Build.0 = Release|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x64.ActiveCfg = Release|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x64.Build.0 = Release|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x86.ActiveCfg = Release|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Release|x86.Build.0 = Release|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x64.ActiveCfg = Debug|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x64.Build.0 = Debug|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x86.ActiveCfg = Debug|Any CPU - {BC3C7768-0401-47F4-931A-76BADFE6DC26}.Debug|x86.Build.0 = Debug|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|Any CPU.Build.0 = Release|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x64.ActiveCfg = Release|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x64.Build.0 = Release|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x86.ActiveCfg = Release|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Release|x86.Build.0 = Release|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x64.ActiveCfg = Debug|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x64.Build.0 = Debug|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x86.ActiveCfg = Debug|Any CPU - {276379A8-897A-4703-A18D-9DB3A08A09AA}.Debug|x86.Build.0 = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|Any CPU.Build.0 = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x64.ActiveCfg = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x64.Build.0 = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x86.ActiveCfg = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Release|x86.Build.0 = Release|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x64.ActiveCfg = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x64.Build.0 = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x86.ActiveCfg = Debug|Any CPU - {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC}.Debug|x86.Build.0 = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|Any CPU.Build.0 = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x64.ActiveCfg = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x64.Build.0 = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x86.ActiveCfg = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Release|x86.Build.0 = Release|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x64.ActiveCfg = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x64.Build.0 = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x86.ActiveCfg = Debug|Any CPU - {A4209E56-1ECD-4616-BB13-DF77864F462C}.Debug|x86.Build.0 = Debug|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|Any CPU.Build.0 = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x64.ActiveCfg = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x64.Build.0 = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x86.ActiveCfg = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Release|x86.Build.0 = Release|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x64.ActiveCfg = Debug|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x64.Build.0 = Debug|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x86.ActiveCfg = Debug|Any CPU - {87EB68ED-18DD-41C9-8A33-B305C4DF210F}.Debug|x86.Build.0 = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|Any CPU.Build.0 = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x64.ActiveCfg = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x64.Build.0 = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x86.ActiveCfg = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Release|x86.Build.0 = Release|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x64.ActiveCfg = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x64.Build.0 = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x86.ActiveCfg = Debug|Any CPU - {0CDE38D4-12F4-426B-9252-E57820FF8D9B}.Debug|x86.Build.0 = Debug|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|Any CPU.ActiveCfg = Release|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|Any CPU.Build.0 = Release|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x64.ActiveCfg = Release|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x64.Build.0 = Release|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x86.ActiveCfg = Release|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Release|x86.Build.0 = Release|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x64.ActiveCfg = Debug|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x64.Build.0 = Debug|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x86.ActiveCfg = Debug|Any CPU - {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA}.Debug|x86.Build.0 = Debug|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|Any CPU.Build.0 = Release|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x64.ActiveCfg = Release|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x64.Build.0 = Release|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x86.ActiveCfg = Release|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Release|x86.Build.0 = Release|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x64.ActiveCfg = Debug|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x64.Build.0 = Debug|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x86.ActiveCfg = Debug|Any CPU - {DE96766F-0853-439F-A054-A8B2EDC10DA6}.Debug|x86.Build.0 = Debug|Any CPU {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|Any CPU.Build.0 = Release|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x64.ActiveCfg = Release|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x64.Build.0 = Release|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x86.ActiveCfg = Release|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Release|x86.Build.0 = Release|Any CPU {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x64.ActiveCfg = Debug|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x64.Build.0 = Debug|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x86.ActiveCfg = Debug|Any CPU - {F5F065A9-F0B1-45B6-B4BC-5912BAB09803}.Debug|x86.Build.0 = Debug|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {CC763EA2-7C5E-45C2-948C-BA0279D3C04F} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} {99309207-1847-4EB9-A009-C7B92BC2CE64} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} - {CA22425D-FB6C-45C6-A14C-561DF6D969B6} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} {33AA545F-7515-406C-A749-EB0A02668F4A} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} {DE0311EB-079F-41B3-9D87-604AAB6F2F65} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {BC3C7768-0401-47F4-931A-76BADFE6DC26} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {276379A8-897A-4703-A18D-9DB3A08A09AA} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {4C3C2CB2-D212-4DBF-8247-C3C75A22D6EC} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} - {A4209E56-1ECD-4616-BB13-DF77864F462C} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} {87EB68ED-18DD-41C9-8A33-B305C4DF210F} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} + {CA22425D-FB6C-45C6-A14C-561DF6D969B6} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} + {CC763EA2-7C5E-45C2-948C-BA0279D3C04F} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} + {A4209E56-1ECD-4616-BB13-DF77864F462C} = {12D2B48F-E7F6-4BEC-8315-9EC8F41A059A} {0CDE38D4-12F4-426B-9252-E57820FF8D9B} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {58BE9E1E-1B58-423F-B2BF-9DB66E0CFACA} = {106CCD57-88BD-4B0A-A67F-A5DA7D8FCA3C} - {DE96766F-0853-439F-A054-A8B2EDC10DA6} = {028D7499-6A31-4A9F-868A-908AD53A8BD0} {F5F065A9-F0B1-45B6-B4BC-5912BAB09803} = {CA22425D-FB6C-45C6-A14C-561DF6D969B6} EndGlobalSection EndGlobal diff --git a/ConsoleEngine/ConsoleEngine.csproj b/ConsoleEngine/ConsoleEngine.csproj index d0559d3..7ac882e 100644 --- a/ConsoleEngine/ConsoleEngine.csproj +++ b/ConsoleEngine/ConsoleEngine.csproj @@ -20,7 +20,4 @@ - - $(DefineConstants);WINDOWS_PLATFORM - diff --git a/ConsoleEngine/GameBase.cs b/ConsoleEngine/GameBase.cs index 652e8e4..e603304 100644 --- a/ConsoleEngine/GameBase.cs +++ b/ConsoleEngine/GameBase.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using ConsoleEngine.Abstractions.Rendering; using ConsoleEngine.Infrastructure; using ConsoleEngine.Infrastructure.Inputs; @@ -33,11 +34,9 @@ protected GameBase(int width, int height, int fontWidth, int fontHeight) protected GameBase(int width, int height, FontInfo fontInfo) { -#if WINDOWS_PLATFORM - _console = new RenderConsole(new ConsoleHandler(width, height, fontInfo)); -#else - _console = new RenderConsole(new CrossPlatformConsoleHandler(width, height, fontInfo)); -#endif + _console = new RenderConsole(OperatingSystem.IsWindows() + ? new ConsoleHandler(width, height, fontInfo) + : new CrossPlatformConsoleHandler(width, height, fontInfo)); Name = "Game"; Scenes = new SceneManager(this); } diff --git a/ConsoleEngine/Infrastructure/Inputs/Input.cs b/ConsoleEngine/Infrastructure/Inputs/Input.cs index c36f917..4f4ea16 100644 --- a/ConsoleEngine/Infrastructure/Inputs/Input.cs +++ b/ConsoleEngine/Infrastructure/Inputs/Input.cs @@ -14,12 +14,9 @@ public class Input //********************************************************** public static readonly Input Instance = new(); - -#if WINDOWS_PLATFORM - private static readonly IInputHandler Handler = new InputHandler(); -#else - private static readonly IInputHandler Handler = new CrossPlatformInputHandler(); -#endif + private static readonly IInputHandler Handler = OperatingSystem.IsWindows() + ? new InputHandler() + : new CrossPlatformInputHandler(); //********************************************************** //** ctor: diff --git a/ConsoleEngine/Infrastructure/Logging/Log.cs b/ConsoleEngine/Infrastructure/Logging/Log.cs index 4c941ae..4164f14 100644 --- a/ConsoleEngine/Infrastructure/Logging/Log.cs +++ b/ConsoleEngine/Infrastructure/Logging/Log.cs @@ -1,5 +1,7 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Diagnostics; +using System.IO; using System.Net; using System.Net.Sockets; using System.Text; @@ -77,16 +79,42 @@ internal static void Start(GameBase game) Socket.Listen(10); LoggerThread.Start(); + StartLoggerProcess(); + } + + private static void StartLoggerProcess() + { + var logger = Path.Combine(AppContext.BaseDirectory, OperatingSystem.IsWindows() ? "ConsoleEngine.Logger.exe" : "ConsoleEngine.Logger"); + _loggerProcess = new Process(); - _loggerProcess.StartInfo.FileName = "ConsoleEngine.Logger.exe"; - _loggerProcess.StartInfo.UseShellExecute = true; - _loggerProcess.Start(); + if (OperatingSystem.IsMacOS()) + { + // The game owns the current terminal, open the logger in a new Terminal window. + _loggerProcess.StartInfo.FileName = "open"; + _loggerProcess.StartInfo.ArgumentList.Add("-a"); + _loggerProcess.StartInfo.ArgumentList.Add("Terminal"); + _loggerProcess.StartInfo.ArgumentList.Add(logger); + } + else + { + _loggerProcess.StartInfo.FileName = logger; + _loggerProcess.StartInfo.UseShellExecute = true; + } + + try + { + _loggerProcess.Start(); + } + catch (Exception) + { + // No way to open a new console window here (e.g. linux without a desktop). Run ConsoleEngine.Logger manually to see the log. + } } internal static void Stop() { _running = false; - _loggerConnection.Dispose(); + _loggerConnection?.Dispose(); Socket.Dispose(); Messages.Clear(); } diff --git a/Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj b/Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj index f41b3d3..6781ef3 100644 --- a/Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj +++ b/Libs/ConsoleEngine.CrossPlatform/ConsoleEngine.CrossPlatform.csproj @@ -8,9 +8,6 @@ AnyCPU True - - $(DefineConstants);WINDOWS_PLATFORM - diff --git a/Libs/ConsoleEngine.CrossPlatform/CrossPlatformConsoleHandler.cs b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformConsoleHandler.cs index 26c272d..1c0982e 100644 --- a/Libs/ConsoleEngine.CrossPlatform/CrossPlatformConsoleHandler.cs +++ b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformConsoleHandler.cs @@ -1,16 +1,40 @@ using System; +using System.IO; +using System.Text; using ConsoleEngine.Abstractions.Rendering; namespace ConsoleEngine.CrossPlatform; +/// +/// Renders to any ANSI/VT capable terminal (macOS Terminal, iTerm2, kitty, WezTerm, Ghostty, Linux terminals ...). +/// Each frame is diffed against the previous one and flushed to stdout in a single write. +/// public sealed class CrossPlatformConsoleHandler : IConsoleHandler { + private const string Esc = "\u001b"; + private readonly FontInfo _fontInfo; + private readonly Pixel[] _previous; + private readonly StringBuilder _frame = new(); + private readonly bool _trueColor; + private readonly object _writeLock = new(); + private Stream _stdout; + private byte[] _bytes = Array.Empty(); + private bool _fullRedraw = true; + private bool _initialized; + private bool _restored; + private int _visibleWidth; + private int _visibleHeight; + private int _originalWidth; + private int _originalHeight; + public CrossPlatformConsoleHandler(int width, int height, FontInfo fontInfo) { _fontInfo = fontInfo; Width = width; Height = height; + _previous = new Pixel[width * height]; + _trueColor = DetectTrueColor(); } public int Width { get; } @@ -18,51 +42,267 @@ public CrossPlatformConsoleHandler(int width, int height, FontInfo fontInfo) public void InitializeConsole() { - Console.Clear(); + Console.OutputEncoding = Encoding.UTF8; + _stdout = Console.OpenStandardOutput(); + _originalWidth = SafeWindowSize(() => Console.WindowWidth, 0); + _originalHeight = SafeWindowSize(() => Console.WindowHeight, 0); + _initialized = true; + + AppDomain.CurrentDomain.ProcessExit += (_, _) => RestoreTerminal(); + Console.CancelKeyPress += (_, _) => RestoreTerminal(); + + Write( + $"{Esc}[?1049h" + // alternate screen buffer, keeps the user's scrollback intact + $"{Esc}[?7l" + // disable auto wrap so drawing the bottom right cell never scrolls + $"{Esc}[8;{Height};{Width}t" + // ask the terminal to resize itself (ignored by terminals that don't support it) + $"{Esc}[0m{Esc}[2J"); + SetFont(_fontInfo); } public void SetTitle(string title) { - Console.Title = title; + try { Console.Title = title; } catch { /* not supported by every terminal */ } } public void SetCursorVisible(bool visible) { - try { Console.CursorVisible = visible; } catch { } + if (_initialized) + Write(visible ? $"{Esc}[?25h" : $"{Esc}[?25l"); + else + try { Console.CursorVisible = visible; } catch { /* ignore */ } } public void Resizable(bool resizable) { - // Not yet supported + // Terminal windows are owned by the terminal emulator, can't be locked from here. } public void SetFont(FontInfo fontInfo) { - // Not yet supported + // Font face and size are owned by the terminal emulator, can't be changed from here. } public void Render(Span pixels) { - Console.SetCursorPosition(0, 0); - for (int y = 0; y < Height; y++) + if (!_initialized) + return; + + var visibleWidth = Math.Min(Width, SafeWindowSize(() => Console.WindowWidth, Width)); + var visibleHeight = Math.Min(Height, SafeWindowSize(() => Console.WindowHeight, Height)); + if (visibleWidth != _visibleWidth || visibleHeight != _visibleHeight) { - for (int x = 0; x < Width; x++) + _visibleWidth = visibleWidth; + _visibleHeight = visibleHeight; + _fullRedraw = true; + } + + _frame.Clear(); + _frame.Append(Esc).Append("[?2026h"); // begin synchronized update (prevents tearing where supported) + + if (_fullRedraw) + _frame.Append(Esc).Append("[0m").Append(Esc).Append("[2J"); + + var currentFg = -1; + var currentBg = -1; + var changed = _fullRedraw; + + for (var y = 0; y < visibleHeight; y++) + { + var cursorX = -1; + for (var x = 0; x < visibleWidth; x++) + { + var i = y * Width + x; + if (i >= pixels.Length) + break; + + var pixel = pixels[i]; + var c = Sanitize(pixel.Char); + ref var previous = ref _previous[i]; + + if (!_fullRedraw + && previous.Char == c + && previous.ForegroundColor == pixel.ForegroundColor + && previous.BackgroundColor == pixel.BackgroundColor) + continue; + + changed = true; + previous.Char = c; + previous.ForegroundColor = pixel.ForegroundColor; + previous.BackgroundColor = pixel.BackgroundColor; + + if (cursorX != x) + _frame.Append(Esc).Append('[').Append(y + 1).Append(';').Append(x + 1).Append('H'); + + var fg = (int)pixel.ForegroundColor & 0xF; + var bg = (int)pixel.BackgroundColor & 0xF; + if (fg != currentFg || bg != currentBg) + { + AppendColors(fg, bg); + currentFg = fg; + currentBg = bg; + } + + _frame.Append(c); + cursorX = x + 1; + } + } + + if (!changed) + return; + + _frame.Append(Esc).Append("[0m"); + _frame.Append(Esc).Append("[?2026l"); // end synchronized update + _fullRedraw = false; + + Write(_frame); + } + + public void Close() => RestoreTerminal(); + + //********************************************************** + //** private: + //********************************************************** + + private void RestoreTerminal() + { + if (!_initialized) + return; + + lock (_writeLock) + { + if (_restored) + return; + + try + { + var restoreSize = _originalWidth > 0 && _originalHeight > 0 ? $"{Esc}[8;{_originalHeight};{_originalWidth}t" : ""; + _stdout.Write(Encoding.UTF8.GetBytes($"{Esc}[?2026l{Esc}[0m{Esc}[?7h{Esc}[?25h{Esc}[?1049l{restoreSize}")); + _stdout.Flush(); + } + catch { - int i = y * Width + x; - if (i < pixels.Length) - Console.Write(pixels[i].Char); - else - Console.Write(' '); + // stdout might already be gone during shutdown } - Console.WriteLine(); + + _restored = true; } } - public void Close() + private void Write(string value) { - // Not yet supported + lock (_writeLock) + { + if (_restored) + return; + + _stdout.Write(Encoding.UTF8.GetBytes(value)); + _stdout.Flush(); + } } - private static ushort GetColorValue(ConsoleColor fg, ConsoleColor bg) => (ushort)((int)fg | ((int)bg<<4)); -} \ No newline at end of file + private void Write(StringBuilder value) + { + lock (_writeLock) + { + if (_restored) + return; + + var maxBytes = Encoding.UTF8.GetMaxByteCount(value.Length); + if (_bytes.Length < maxBytes) + _bytes = new byte[maxBytes * 2]; + + var count = 0; + foreach (var chunk in value.GetChunks()) + count += Encoding.UTF8.GetBytes(chunk.Span, _bytes.AsSpan(count)); + + _stdout.Write(_bytes, 0, count); + _stdout.Flush(); + } + } + + private void AppendColors(int fg, int bg) + { + if (_trueColor) + { + var (fr, fgG, fb) = Palette[fg]; + var (br, bgG, bb) = Palette[bg]; + _frame.Append(Esc).Append("[38;2;").Append(fr).Append(';').Append(fgG).Append(';').Append(fb) + .Append(";48;2;").Append(br).Append(';').Append(bgG).Append(';').Append(bb).Append('m'); + } + else + { + _frame.Append(Esc).Append('[').Append(AnsiColor[fg]).Append(';').Append(AnsiColor[bg] + 10).Append('m'); + } + } + + /// + /// Windows renders NUL and control characters as blanks, terminals don't (they'd shift the rest of the line). + /// + private static char Sanitize(char c) => c < ' ' || c == '\u007f' || char.IsSurrogate(c) ? ' ' : c; + + private static int SafeWindowSize(Func getter, int fallback) + { + try + { + var size = getter(); + return size > 0 ? size : fallback; + } + catch + { + return fallback; + } + } + + private static bool DetectTrueColor() + { + var setting = Environment.GetEnvironmentVariable("CONSOLE_ENGINE_COLORS"); + if (!string.IsNullOrEmpty(setting)) + return setting.Equals("truecolor", StringComparison.OrdinalIgnoreCase); + + var colorTerm = Environment.GetEnvironmentVariable("COLORTERM"); + return colorTerm is "truecolor" or "24bit"; + } + + /// ConsoleColor (index) to ANSI SGR foreground color. + private static readonly int[] AnsiColor = + [ + 30, // Black + 34, // DarkBlue + 32, // DarkGreen + 36, // DarkCyan + 31, // DarkRed + 35, // DarkMagenta + 33, // DarkYellow + 37, // Gray + 90, // DarkGray + 94, // Blue + 92, // Green + 96, // Cyan + 91, // Red + 95, // Magenta + 93, // Yellow + 97, // White + ]; + + /// ConsoleColor (index) to RGB, the Windows console (Campbell) palette so games look the same as on Windows. + private static readonly (byte R, byte G, byte B)[] Palette = + [ + (0, 0, 0), // Black + (0, 55, 218), // DarkBlue + (19, 161, 14), // DarkGreen + (58, 150, 221), // DarkCyan + (197, 15, 31), // DarkRed + (136, 23, 152), // DarkMagenta + (193, 156, 0), // DarkYellow + (204, 204, 204), // Gray + (118, 118, 118), // DarkGray + (59, 120, 255), // Blue + (22, 198, 12), // Green + (97, 214, 214), // Cyan + (231, 72, 86), // Red + (180, 0, 158), // Magenta + (249, 241, 165), // Yellow + (242, 242, 242), // White + ]; +} diff --git a/Libs/ConsoleEngine.CrossPlatform/CrossPlatformInputHandler.cs b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformInputHandler.cs index 2fafa64..04ab8c4 100644 --- a/Libs/ConsoleEngine.CrossPlatform/CrossPlatformInputHandler.cs +++ b/Libs/ConsoleEngine.CrossPlatform/CrossPlatformInputHandler.cs @@ -1,15 +1,49 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using ConsoleEngine.Abstractions.Inputs; namespace ConsoleEngine.CrossPlatform; +/// +/// Keyboard input for non Windows platforms. Key codes are Windows virtual key codes, same as . +/// +/// On macOS the real keyboard state is read through CoreGraphics. Everywhere else (or if macOS refuses to +/// report key state) keys are read from the terminal, which only reports presses and auto repeats. A key +/// read from the terminal is then considered held until the auto repeat stops. +/// public class CrossPlatformInputHandler : IInputHandler { - private readonly int[] _previousState = new int[256]; - private readonly int[] _newState = new int[256]; + // A terminal key is held until no new press/repeat arrives within this window. The first window has to + // outlast the OS' "delay until repeat", the following ones only the repeat interval. + private static readonly long InitialHoldTicks = Stopwatch.Frequency * 550 / 1000; + private static readonly long RepeatHoldTicks = Stopwatch.Frequency * 150 / 1000; + + // How far apart a terminal key press and the native key down may be to count as the same key press. + private static readonly long CalibrationTicks = Stopwatch.Frequency * 300 / 1000; + private const int MaxCalibrationMisses = 2; + private readonly KeyState[] _keyStates = new KeyState[256]; + private readonly bool[] _down = new bool[256]; + private readonly bool[] _nativeDown = new bool[256]; + private readonly long[] _terminalHeldUntil = new long[256]; + private readonly long[] _terminalLastEvent = new long[256]; + private readonly bool[] _terminalPressed = new bool[256]; + private readonly long[] _nativeLastDown = new long[256]; + private readonly List<(int Key, long Time)> _pendingCalibration = new(); + private bool _nativeSupported; + private bool _nativeTrusted; + private int _calibrationMisses; + + public CrossPlatformInputHandler() + { + var mode = Environment.GetEnvironmentVariable("CONSOLE_ENGINE_INPUT"); + _nativeSupported = !string.Equals(mode, "terminal", StringComparison.OrdinalIgnoreCase) && MacKeyboard.IsSupported(); + + for (var i = 0; i < _keyStates.Length; i++) + _keyStates[i].Index = i; + } public KeyState GetKey(int id) { @@ -20,31 +54,116 @@ public KeyState GetKey(int id) public void Update() { - bool[] pressedThisFrame = new bool[_keyStates.Length]; - while (Console.KeyAvailable) + var now = Stopwatch.GetTimestamp(); + + // Always drain the terminal, otherwise key presses pile up in stdin (and get echoed when the game exits). + ReadTerminalKeys(now); + + if (_nativeSupported) + { + MacKeyboard.Poll(_nativeDown); + for (var i = 0; i < _nativeDown.Length; i++) + if (_nativeDown[i]) + _nativeLastDown[i] = now; + + if (!_nativeTrusted) + Calibrate(now); + } + + if (_nativeTrusted) + { + Array.Copy(_nativeDown, _down, _down.Length); + } + else { - var keyInfo = Console.ReadKey(true); - int key = (int)keyInfo.Key; - _keyStates[key].Pressed = true; - _keyStates[key].Held = true; - _keyStates[key].Index = key; - pressedThisFrame[key] = true; + for (var i = 0; i < _down.Length; i++) + _down[i] = _terminalHeldUntil[i] > now; } - for (int i = 0; i < _keyStates.Length; i++) + + for (var i = 0; i < _keyStates.Length; i++) + { + var wasHeld = _keyStates[i].Held; + var isDown = _down[i]; + + // A key tapped again within its terminal hold window is still a new press. + var repressed = !_nativeTrusted && _terminalPressed[i] && wasHeld; + + _keyStates[i].Pressed = (isDown && !wasHeld) || repressed; + _keyStates[i].Released = !isDown && wasHeld; + _keyStates[i].Held = isDown; + } + } + + /// + /// Without the Input Monitoring permission macOS still reports modifier keys, but every other key as up. + /// So the native key state is only trusted once it agrees with a key press read from the terminal, and + /// dropped if it keeps missing them. Until then the terminal is used. + /// + private void Calibrate(long now) + { + for (var i = 0; i < _terminalPressed.Length; i++) + if (_terminalPressed[i] && MacKeyboard.IsMapped(i)) + _pendingCalibration.Add((i, now)); + + for (var i = _pendingCalibration.Count - 1; i >= 0; i--) { - if (!pressedThisFrame[i]) + var (key, time) = _pendingCalibration[i]; + if (Math.Abs(_nativeLastDown[key] - time) < CalibrationTicks) { - if (_keyStates[i].Held) - { - _keyStates[i].Released = true; - } - else + _nativeTrusted = true; + _pendingCalibration.Clear(); + return; + } + + if (now - time > CalibrationTicks) + { + _pendingCalibration.RemoveAt(i); + if (++_calibrationMisses >= MaxCalibrationMisses) { - _keyStates[i].Released = false; + _nativeSupported = false; + _pendingCalibration.Clear(); + return; } - _keyStates[i].Held = false; - _keyStates[i].Pressed = false; } } } -} \ No newline at end of file + + private void ReadTerminalKeys(long now) + { + Array.Clear(_terminalPressed); + + if (Console.IsInputRedirected) + return; + + try + { + while (Console.KeyAvailable) + { + var keyInfo = Console.ReadKey(intercept: true); + var vk = ToVirtualKey(keyInfo); + if (vk <= 0 || vk >= _terminalHeldUntil.Length) + continue; + + // Auto repeat events arrive in a rapid stream, anything slower is a new key press. + var isRepeat = now - _terminalLastEvent[vk] < RepeatHoldTicks; + _terminalLastEvent[vk] = now; + _terminalHeldUntil[vk] = now + (isRepeat ? RepeatHoldTicks : InitialHoldTicks); + _terminalPressed[vk] |= !isRepeat; + } + } + catch (InvalidOperationException) + { + // No console attached + } + } + + private static int ToVirtualKey(ConsoleKeyInfo keyInfo) + { + // ConsoleKey values are Windows virtual key codes. + if (keyInfo.Key != 0) + return (int)keyInfo.Key; + + var c = char.ToUpperInvariant(keyInfo.KeyChar); + return c is ' ' or >= '0' and <= '9' or >= 'A' and <= 'Z' ? c : 0; + } +} diff --git a/Libs/ConsoleEngine.CrossPlatform/MacKeyboard.cs b/Libs/ConsoleEngine.CrossPlatform/MacKeyboard.cs new file mode 100644 index 0000000..1d64e96 --- /dev/null +++ b/Libs/ConsoleEngine.CrossPlatform/MacKeyboard.cs @@ -0,0 +1,144 @@ +using System; +using System.Runtime.InteropServices; +using ConsoleEngine.Abstractions.Inputs; + +namespace ConsoleEngine.CrossPlatform; + +/// +/// Reads the real up/down state of the keyboard on macOS through CoreGraphics, the macOS equivalent of +/// Win32 GetAsyncKeyState. Terminals only report key presses (plus auto repeat), never releases, so this +/// is what makes Held/Released behave like on Windows. +/// +internal static class MacKeyboard +{ + private const string CoreGraphics = "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics"; + private const int CombinedSessionState = 0; // kCGEventSourceStateCombinedSessionState + + [DllImport(CoreGraphics)] + [return: MarshalAs(UnmanagedType.U1)] + private static extern bool CGEventSourceKeyState(int stateId, ushort key); + + /// Windows virtual key code (index) to macOS virtual key codes (kVK_*). + private static readonly ushort[][] KeyMap = BuildKeyMap(); + + public static bool IsSupported() + { + if (!OperatingSystem.IsMacOS()) + return false; + + try + { + CGEventSourceKeyState(CombinedSessionState, 0); + return true; + } + catch (Exception e) when (e is DllNotFoundException or EntryPointNotFoundException) + { + return false; + } + } + + public static bool IsMapped(int vk) => vk >= 0 && vk < KeyMap.Length && KeyMap[vk].Length > 0; + + /// Writes the current down state of every mapped key into , indexed by Windows virtual key code. + /// true if any key is down. + public static bool Poll(bool[] down) + { + var any = false; + for (var vk = 0; vk < KeyMap.Length && vk < down.Length; vk++) + { + var isDown = false; + foreach (var macKey in KeyMap[vk]) + { + if (CGEventSourceKeyState(CombinedSessionState, macKey)) + { + isDown = true; + break; + } + } + + down[vk] = isDown; + any |= isDown; + } + + return any; + } + + private static ushort[][] BuildKeyMap() + { + var map = new ushort[256][]; + for (var i = 0; i < map.Length; i++) + map[i] = Array.Empty(); + + void Map(int vk, params ushort[] macKeys) => map[vk] = macKeys; + + // Letters (ANSI positions) + Map((int)Key.A, 0x00); Map((int)Key.S, 0x01); Map((int)Key.D, 0x02); Map((int)Key.F, 0x03); + Map((int)Key.H, 0x04); Map((int)Key.G, 0x05); Map((int)Key.Z, 0x06); Map((int)Key.X, 0x07); + Map((int)Key.C, 0x08); Map((int)Key.V, 0x09); Map((int)Key.B, 0x0B); Map((int)Key.Q, 0x0C); + Map((int)Key.W, 0x0D); Map((int)Key.E, 0x0E); Map((int)Key.R, 0x0F); Map((int)Key.Y, 0x10); + Map((int)Key.T, 0x11); Map((int)Key.O, 0x1F); Map((int)Key.U, 0x20); Map((int)Key.I, 0x22); + Map((int)Key.P, 0x23); Map((int)Key.L, 0x25); Map((int)Key.J, 0x26); Map((int)Key.K, 0x28); + Map((int)Key.N, 0x2D); Map((int)Key.M, 0x2E); + + // Digits + Map((int)Key.K1, 0x12); Map((int)Key.K2, 0x13); Map((int)Key.K3, 0x14); Map((int)Key.K4, 0x15); + Map((int)Key.K5, 0x17); Map((int)Key.K6, 0x16); Map((int)Key.K7, 0x1A); Map((int)Key.K8, 0x1C); + Map((int)Key.K9, 0x19); Map((int)Key.K0, 0x1D); + + // Function keys + Map((int)Key.F1, 0x7A); Map((int)Key.F2, 0x78); Map((int)Key.F3, 0x63); Map((int)Key.F4, 0x76); + Map((int)Key.F5, 0x60); Map((int)Key.F6, 0x61); Map((int)Key.F7, 0x62); Map((int)Key.F8, 0x64); + Map((int)Key.F9, 0x65); Map((int)Key.F10, 0x6D); Map((int)Key.F11, 0x67); Map((int)Key.F12, 0x6F); + + // Navigation / editing + Map((int)Key.ENTER, 0x24, 0x4C); // return, keypad enter + Map((int)Key.ESCAPE, 0x35); + Map((int)Key.SPACE, 0x31); + Map((int)Key.BACKSPACE, 0x33); + Map((int)Key.DELETE, 0x75); + Map((int)Key.INSERT, 0x72); // help key sits where insert is on full size keyboards + Map((int)Key.HOME, 0x73); + Map((int)Key.END, 0x77); + Map((int)Key.PRIOR, 0x74); + Map((int)Key.NEXT, 0x79); + Map((int)Key.LEFT, 0x7B); + Map((int)Key.RIGHT, 0x7C); + Map((int)Key.DOWN, 0x7D); + Map((int)Key.UP, 0x7E); + Map(0x09, 0x30); // VK_TAB + + // Modifiers + Map(0x10, 0x38, 0x3C); // VK_SHIFT + Map(0x11, 0x3B, 0x3E); // VK_CONTROL + Map(0x12, 0x3A, 0x3D); // VK_MENU (option) + Map(0x14, 0x39); // VK_CAPITAL + Map(0x5B, 0x37); // VK_LWIN (command) + Map(0x5C, 0x36); // VK_RWIN (right command) + Map(0xA0, 0x38); Map(0xA1, 0x3C); // VK_LSHIFT, VK_RSHIFT + Map(0xA2, 0x3B); Map(0xA3, 0x3E); // VK_LCONTROL, VK_RCONTROL + Map(0xA4, 0x3A); Map(0xA5, 0x3D); // VK_LMENU, VK_RMENU + + // Numpad + Map((int)Key.NUMPAD0, 0x52); Map((int)Key.NUMPAD1, 0x53); Map((int)Key.NUMPAD2, 0x54); + Map((int)Key.NUMPAD3, 0x55); Map((int)Key.NUMPAD4, 0x56); Map((int)Key.NUMPAD5, 0x57); + Map((int)Key.NUMPAD6, 0x58); Map((int)Key.NUMPAD7, 0x59); Map((int)Key.NUMPAD8, 0x5B); + Map((int)Key.NUMPAD9, 0x5C); + Map((int)Key.MULTIPLY, 0x43); Map((int)Key.ADD, 0x45); Map((int)Key.SUBTRACT, 0x4E); + Map((int)Key.DECIMAL, 0x41); Map((int)Key.DIVIDE, 0x4B); + + // OEM / punctuation (US layout positions) + Map(0xBA, 0x29); // ; + Map(0xBB, 0x18); // = + Map(0xBC, 0x2B); // , + Map(0xBD, 0x1B); // - + Map(0xBE, 0x2F); // . + Map(0xBF, 0x2C); // / + Map(0xC0, 0x32); // ` + Map(0xDB, 0x21); // [ + Map(0xDC, 0x2A); // \ + Map(0xDD, 0x1E); // ] + Map(0xDE, 0x27); // ' + + return map; + } +} diff --git a/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj b/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj index 7c4ebcb..d2ff72f 100644 --- a/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj +++ b/Libs/ConsoleEngine.Windows/ConsoleEngine.Windows.csproj @@ -8,11 +8,8 @@ AnyCPU True - - $(DefineConstants);WINDOWS_PLATFORM - + - diff --git a/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs b/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs index 1ee526a..cd347c0 100644 --- a/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs +++ b/Libs/ConsoleEngine.Windows/LowLevel/Kernel32.cs @@ -11,7 +11,6 @@ namespace ConsoleEngine.Native.LowLevel; /// internal static class Kernel32 { -#if WINDOWS_PLATFORM [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern IntPtr GetStdHandle(StdHandle nStdHandle); @@ -68,24 +67,4 @@ internal static class Kernel32 [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseHandle(IntPtr hObject); -#else - // Unix/macOS/Linux - // Can´t deal with Kernel32 calls, so void them - internal static IntPtr GetStdHandle(StdHandle nStdHandle) => IntPtr.Zero; - internal static bool SetConsoleActiveScreenBuffer(IntPtr hConsoleOutput) => false; - internal static IntPtr CreateConsoleScreenBuffer(GenericRights dwDesiredAccess, uint dwShareMode, IntPtr securityAttributes, uint flags, IntPtr screenBufferData) => IntPtr.Zero; - internal static bool WriteConsoleOutputCharacter(IntPtr screenBuffer, string characters, uint length, Coord writeCoord, out UInt32 numCharsWritten) { numCharsWritten = 0; return false; } - internal static bool WriteConsoleOutput(IntPtr hConsoleOutput, CharInfo[] lpBuffer, Coord dwBufferSize, Coord dwBufferCoord, ref SmallRect lpWriteRegion) => false; - internal static IntPtr GetConsoleWindow() => IntPtr.Zero; - internal static int SetCurrentConsoleFontEx(IntPtr consoleOutput, bool maximumWindow, ref ConsoleFontInfoEx consoleCurrentFontEx) => 0; - internal static bool SetConsoleWindowInfo(IntPtr hConsoleOutput, bool bAbsolute, ref SmallRect lpConsoleWindow) => false; - internal static bool SetConsoleScreenBufferSize(IntPtr hConsoleOutput, Coord dwSize) => false; - internal static int GetAsyncKeyState(int vKeys) => 0; - internal static bool SetConsoleOutputCP(uint wCodePageID) => false; - internal static bool SetConsoleCP(uint wCodePageID) => false; - internal static bool SetConsoleTitle(char[] lpConsoleTitle) { try { Console.Title = new string(lpConsoleTitle); return true; } catch { return false; } } - internal static bool GetConsoleCursorInfo(IntPtr hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo) { lpConsoleCursorInfo = default; return false; } - internal static bool SetConsoleCursorInfo(IntPtr hConsoleOutput, ref ConsoleCursorInfo lpConsoleCursorInfo) => false; - internal static bool CloseHandle(IntPtr hObject) => false; -#endif } \ No newline at end of file diff --git a/README.md b/README.md index f78c258..da17fef 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,19 @@ [![Build and test](https://github.com/stho01/console-engine/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/stho01/console-engine/actions/workflows/build-and-test.yml) + +## Platforms + +On Windows the engine talks to the Win32 console directly. On macOS and Linux it renders with ANSI escape sequences, so any +modern terminal works (Terminal.app, iTerm2, kitty, WezTerm, Ghostty, ...). The platform is picked at runtime, so the same +build runs everywhere. + +macOS/Linux notes: + +- Font face/size and window resizing can't be controlled from the game. Set them in your terminal; the game asks the terminal + to resize to fit, which Terminal.app and iTerm2 honor. If the terminal is smaller than the game, the output is clipped. +- Colors use 24-bit RGB when `COLORTERM` is `truecolor`/`24bit`, otherwise the terminal's 16 color palette. + Override with `CONSOLE_ENGINE_COLORS=truecolor|16`. +- On macOS key up/down state is read from the OS (like `GetAsyncKeyState` on Windows), which gives precise held/released keys. + Like on Windows it sees keys pressed while another window is focused. Elsewhere, or with `CONSOLE_ENGINE_INPUT=terminal`, keys + are read from the terminal; terminals only report presses and auto repeat, so a key counts as held until its repeat stops.