diff --git a/BeefLibs/corlib/src/Diagnostics/ProcessStartInfo.bf b/BeefLibs/corlib/src/Diagnostics/ProcessStartInfo.bf index 4a267466..4dcac267 100644 --- a/BeefLibs/corlib/src/Diagnostics/ProcessStartInfo.bf +++ b/BeefLibs/corlib/src/Diagnostics/ProcessStartInfo.bf @@ -10,6 +10,7 @@ namespace System.Diagnostics bool mRedirectStandardOutput = false; bool mRedirectStandardError = false; bool mCreateNoWindow = false; + bool mActivateWindow = false; public bool ErrorDialog; //public Windows.Handle ErrorDialogParentHandle; //public ProcessWindowStyle WindowStyle; @@ -26,6 +27,7 @@ namespace System.Diagnostics public bool RedirectStandardOutput { get { return mRedirectStandardOutput; } set { mRedirectStandardOutput = value; } }; public bool RedirectStandardError { get { return mRedirectStandardError; } set { mRedirectStandardError = value; } }; public bool CreateNoWindow { get { return mCreateNoWindow; } set { mCreateNoWindow = value; } }; + public bool ActivateWindow { get { return mActivateWindow; } set { mActivateWindow = value; } }; Encoding StandardOutputEncoding; Encoding StandardErrorEncoding; diff --git a/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf b/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf index 1fcb5015..675e469c 100644 --- a/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf +++ b/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf @@ -70,6 +70,8 @@ namespace System.Diagnostics } if (startInfo.CreateNoWindow) spawnFlags |= .NoWindow; + if (!startInfo.ActivateWindow) + spawnFlags |= .NoActivateWindow; if (startInfo.RedirectStandardInput) spawnFlags |= .RedirectStdInput; if (startInfo.RedirectStandardOutput) diff --git a/BeefLibs/corlib/src/IO/File.bf b/BeefLibs/corlib/src/IO/File.bf index 7152bfd0..e0d5a219 100644 --- a/BeefLibs/corlib/src/IO/File.bf +++ b/BeefLibs/corlib/src/IO/File.bf @@ -24,6 +24,7 @@ namespace System.IO case OpenError(FileOpenError); case ReadError(FileReadError); case SeekError; + case PipeListening; } static class File diff --git a/BeefLibs/corlib/src/IO/FileStream.bf b/BeefLibs/corlib/src/IO/FileStream.bf index a18312ed..c95f2472 100644 --- a/BeefLibs/corlib/src/IO/FileStream.bf +++ b/BeefLibs/corlib/src/IO/FileStream.bf @@ -72,6 +72,8 @@ namespace System.IO { case .Timeout: return .Err(.ReadError(.Timeout)); + case .PipeListening: + return .Err(.PipeListening); default: return .Err(.ReadError(.Unknown)); } diff --git a/BeefLibs/corlib/src/IO/MemoryStream.bf b/BeefLibs/corlib/src/IO/MemoryStream.bf index 57351052..ac947ffd 100644 --- a/BeefLibs/corlib/src/IO/MemoryStream.bf +++ b/BeefLibs/corlib/src/IO/MemoryStream.bf @@ -109,5 +109,11 @@ namespace System.IO return .Ok; } + + public void Clear() + { + mMemory.Clear(); + mPosition = 0; + } } } diff --git a/BeefLibs/corlib/src/IO/Pipe.bf b/BeefLibs/corlib/src/IO/Pipe.bf index 145ea9da..35b409c2 100644 --- a/BeefLibs/corlib/src/IO/Pipe.bf +++ b/BeefLibs/corlib/src/IO/Pipe.bf @@ -26,6 +26,8 @@ namespace System.IO public Result Create(StringView machineName, StringView pipeName, PipeOptions options) { + Close(); + Runtime.Assert(mBfpFile == null); String path = scope String(); @@ -64,6 +66,8 @@ namespace System.IO public Result Open(StringView machineName, StringView pipeName, PipeOptions options) { + Close(); + Runtime.Assert(mBfpFile == null); String path = scope String(); diff --git a/BeefLibs/corlib/src/Platform.bf b/BeefLibs/corlib/src/Platform.bf index 87e788bc..38063ff3 100644 --- a/BeefLibs/corlib/src/Platform.bf +++ b/BeefLibs/corlib/src/Platform.bf @@ -31,7 +31,8 @@ namespace System PartialData, TempFileError, Timeout, - NotEmpty + NotEmpty, + PipeListening }; public struct BfpSpawn {} @@ -263,6 +264,7 @@ namespace System ErrorDialog = 0x400, Window_Hide = 0x800, Window_Maximized = 0x1000, + NoActivateWindow = 0x2000 }; public enum BfpKillFlags : int32 @@ -455,6 +457,7 @@ namespace System InsufficientBuffer = (int)Result.InsufficientBuffer, Timeout = (int)Result.Timeout, NotEmpty = (int)Result.NotEmpty, + PipeListening = (int)Result.PipeListening, }; #if !BF_RUNTIME_DISABLE diff --git a/BeefTools/BeefCon/BeefProj.toml b/BeefTools/BeefCon/BeefProj.toml new file mode 100644 index 00000000..8f196d56 --- /dev/null +++ b/BeefTools/BeefCon/BeefProj.toml @@ -0,0 +1,21 @@ +FileVersion = 1 +Dependencies = {corlib = "*", Beefy2D = "*"} + +[Project] +Name = "BeefCon" +TargetType = "BeefGUIApplication" +StartupObject = "BeefCon.Program" + +[Configs.Debug.Win64] +TargetDirectory = "$(WorkspaceDir)\\..\\..\\IDE\\dist" +TargetName = "$(ProjectName)_d" +BeefLibType = "DynamicDebug" +DebugCommandArguments = "123 1 Powershell" + +[Configs.Release.Win64] +TargetDirectory = "$(WorkspaceDir)\\..\\..\\IDE\\dist" +DebugCommandArguments = "123 1 Powershell" + +[[ProjectFolder.Items]] +Type = "Source" +Path = "../../IDE/src/util/ConsoleProvider.bf" diff --git a/BeefTools/BeefCon/BeefSpace.toml b/BeefTools/BeefCon/BeefSpace.toml new file mode 100644 index 00000000..5207daa6 --- /dev/null +++ b/BeefTools/BeefCon/BeefSpace.toml @@ -0,0 +1,8 @@ +FileVersion = 1 +Projects = {BeefCon = {Path = "."}, Beefy2D = "*"} + +[Workspace] +StartupProject = "BeefCon" + +[Configs.Release.Win64] +BfOptimizationLevel = "OgPlus" diff --git a/BeefTools/BeefCon/src/Program.bf b/BeefTools/BeefCon/src/Program.bf new file mode 100644 index 00000000..23b7a2a7 --- /dev/null +++ b/BeefTools/BeefCon/src/Program.bf @@ -0,0 +1,167 @@ +using System; +using System.Threading; +using System.IO; +using IDE.util; +using System.Diagnostics; +using Beefy.widgets; + +namespace BeefCon; + +class Program +{ + BeefConConsoleProvider.Pipe mPipe ~ delete _; + WinNativeConsoleProvider mProvider ~ delete _; + int32 mPid; + int32 mConid; + String mExecStr = new .() ~ delete _; + SpawnedProcess mSpawnedProcess ~ delete _; + + public ~this() + { + mSpawnedProcess.Kill(); + mSpawnedProcess.WaitFor(); + } + + static mixin GET(var ptr) + { + *((T*)(ptr += sizeof(T)) - 1) + } + + public void MessageLoop() + { + while (true) + { + switch (mPipe.ReadMessage(-1)) + { + case .Ok(let msg): + uint8* ptr = msg.Ptr + 1; + switch (*(BeefConConsoleProvider.Message*)msg.Ptr) + { + case .GetData: + mPipe.StartMessage(BeefConConsoleProvider.Message.Data); + mPipe.Stream.Write((int32)mProvider.Width); + mPipe.Stream.Write((int32)mProvider.Height); + mPipe.Stream.Write((int32)mProvider.BufferHeight); + mPipe.Stream.Write((int32)mProvider.ScrollTop); + mPipe.Stream.Write(mProvider.CursorVisible); + mPipe.Stream.Write(mProvider.CursorHeight); + mPipe.Stream.Write(mProvider.CursorPos); + for (int i < 16) + mPipe.Stream.Write(mProvider.GetColor(i)); + + for (int row < mProvider.Height) + { + for (int col < mProvider.Width) + { + var cell = mProvider.GetCell(col, row); + mPipe.Stream.Write(cell.mChar); + mPipe.Stream.Write(cell.mAttributes); + } + } + mPipe.EndMessage(); + case .Resize: + int32 cols = GET!(ptr); + int32 rows = GET!(ptr); + bool resizeContent = GET!(ptr); + mProvider.Resize(cols, rows, resizeContent); + case .KeyDown: + KeyCode keyCode = GET!(ptr); + KeyFlags keyFlags = GET!(ptr); + mProvider.KeyDown(keyCode, keyFlags); + case .KeyUp: + KeyCode keyCode = GET!(ptr); + mProvider.KeyUp(keyCode); + case .InputString: + int32 strLen = GET!(ptr); + StringView str = .((.)ptr, strLen); + mProvider.SendInput(str); + case .MouseDown: + int32 col = GET!(ptr); + int32 row = GET!(ptr); + int32 btnState = GET!(ptr); + int32 btnCount = GET!(ptr); + KeyFlags keyFlags = GET!(ptr); + mProvider.MouseDown(col, row, btnState, btnCount, keyFlags); + case .MouseMove: + int32 col = GET!(ptr); + int32 row = GET!(ptr); + int32 btnState = GET!(ptr); + KeyFlags keyFlags = GET!(ptr); + mProvider.MouseMove(col, row, btnState, keyFlags); + case .MouseUp: + int32 col = GET!(ptr); + int32 row = GET!(ptr); + int32 btnState = GET!(ptr); + KeyFlags keyFlags = GET!(ptr); + mProvider.MouseUp(col, row, btnState, keyFlags); + case .MouseWheel: + int32 col = GET!(ptr); + int32 row = GET!(ptr); + int32 dy = GET!(ptr); + mProvider.MouseWheel(col, row, dy); + default: + } + case .Err(let err): + return; + } + } + } + + public void Run() + { + mPipe = new .(); + mPipe.Listen(mPid, mConid); + + mProvider = new .(); + //mProvider.mHideNativeConsole = false; + mProvider.Attach(); + + ProcessStartInfo procInfo = scope ProcessStartInfo(); + procInfo.UseShellExecute = false; + procInfo.SetFileName(mExecStr); + + mSpawnedProcess = new SpawnedProcess(); + if (mSpawnedProcess.Start(procInfo) case .Err) + return; + + while (true) + { + mProvider.Update(); + + var process = Platform.BfpProcess_GetById(null, mPid, null); + if (process == null) + { + Console.Error.WriteLine("Process closed"); + return; + } + Platform.BfpProcess_Release(process); + MessageLoop(); + + if (mPipe.mFailed) + return; + + if (!mPipe.mConnected) + Thread.Sleep(20); + + if (mSpawnedProcess.WaitFor(0)) + return; + } + } + + public static int Main(String[] args) + { + if (args.Count < 2) + { + Console.Error.WriteLine("Usage: BeefCon "); + return 1; + } + + Program pg = scope .(); + pg.mPid = int32.Parse(args[0]); + pg.mConid = int32.Parse(args[1]); + pg.mExecStr.Set(args[2]); + pg.Run(); + + return 0; + } +} \ No newline at end of file diff --git a/BeefySysLib/platform/PlatformInterface.h b/BeefySysLib/platform/PlatformInterface.h index cdafc966..8ca887f2 100644 --- a/BeefySysLib/platform/PlatformInterface.h +++ b/BeefySysLib/platform/PlatformInterface.h @@ -54,7 +54,8 @@ enum BfpResult BfpResult_PartialData, BfpResult_TempFileError, BfpResult_Timeout, - BfpResult_NotEmpty + BfpResult_NotEmpty, + BfpResult_PipeListening }; enum BfpSystemResult @@ -77,7 +78,8 @@ enum BfpFileResult BfpFileResult_PartialData = BfpResult_PartialData, BfpFileResult_InsufficientBuffer = BfpResult_InsufficientBuffer, BfpFileResult_Timeout = BfpResult_Timeout, - BfpFileResult_NotEmpty = BfpResult_NotEmpty + BfpFileResult_NotEmpty = BfpResult_NotEmpty, + BfpFileResult_PipeListening = BfpResult_PipeListening }; typedef void(*BfpCrashInfoFunc)(); @@ -197,6 +199,7 @@ enum BfpSpawnFlags BfpSpawnFlag_ErrorDialog = 0x400, BfpSpawnFlag_Window_Hide = 0x800, BfpSpawnFlag_Window_Maximized = 0x1000, + BfpSpawnFlag_NoActivateWindow = 0x2000, }; enum BfpSpawnResult @@ -420,7 +423,7 @@ enum BfpFileStdKind BFP_EXPORT BfpFile* BFP_CALLTYPE BfpFile_Create(const char* name, BfpFileCreateKind createKind, BfpFileCreateFlags createFlags, BfpFileAttributes createdFileAttr, BfpFileResult* outResult); BFP_EXPORT BfpFile* BFP_CALLTYPE BfpFile_GetStd(BfpFileStdKind kind, BfpFileResult* outResult); -BFP_EXPORT intptr BFP_CALLTYPE BfpFile_GetSystemHandle(BfpFile* file); +BFP_EXPORT BfpFile* BFP_CALLTYPE BfpFile_GetFromHandle(intptr handle, BfpFileResult* outResult); BFP_EXPORT void BFP_CALLTYPE BfpFile_Release(BfpFile* file); BFP_EXPORT void BFP_CALLTYPE BfpFile_Close(BfpFile* file, BfpFileResult* outResult); BFP_EXPORT intptr BFP_CALLTYPE BfpFile_Write(BfpFile* file, const void* buffer, intptr size, int timeoutMS, BfpFileResult* outResult); diff --git a/BeefySysLib/platform/win/Platform.cpp b/BeefySysLib/platform/win/Platform.cpp index 27bb6bb5..ad9a122d 100644 --- a/BeefySysLib/platform/win/Platform.cpp +++ b/BeefySysLib/platform/win/Platform.cpp @@ -1689,6 +1689,13 @@ public: creationFlags |= CREATE_NO_WINDOW; // set up the environment block parameter + if ((flags & BfpSpawnFlag_NoActivateWindow) != 0) + { + startupInfo.dwFlags |= STARTF_USESHOWWINDOW; + startupInfo.wShowWindow = SW_SHOWNOACTIVATE; + } + // set up the environment block parameter + WCHAR* targetStrPtr = NULL; UTF16String targetStrW; if ((flags & BfpSpawnFlag_ArgsIncludesTarget) != 0) @@ -1726,7 +1733,7 @@ public: String str8(env, envSize); envW = UTF8Decode(str8); envVoidPtr = (void*)envW.c_str(); - startupInfo.dwFlags |= CREATE_UNICODE_ENVIRONMENT; + creationFlags |= CREATE_UNICODE_ENVIRONMENT; } else { @@ -2998,6 +3005,11 @@ BFP_EXPORT BfpFile* BFP_CALLTYPE BfpFile_Create(const char* path, BfpFileCreateK return bfpFile; } +BFP_EXPORT BfpFile* BFP_CALLTYPE BfpFile_GetFromHandle(intptr handle, BfpFileResult* outResult) +{ + return new BfpFile((HANDLE)handle); +} + BFP_EXPORT BfpFile* BFP_CALLTYPE BfpFile_GetStd(BfpFileStdKind kind, BfpFileResult* outResult) { HANDLE h = INVALID_HANDLE_VALUE; @@ -3235,6 +3247,9 @@ BFP_EXPORT intptr BFP_CALLTYPE BfpFile_Read(BfpFile* file, void* buffer, intptr int lastError = ::GetLastError(); switch (lastError) { + case ERROR_PIPE_LISTENING: + OUTRESULT(BfpFileResult_PipeListening); + break; case ERROR_BROKEN_PIPE: // Just an EOF OUTRESULT(BfpFileResult_Ok); break; diff --git a/IDE/src/Debugger/DebugManager.bf b/IDE/src/Debugger/DebugManager.bf index 173f48c0..14cf86f3 100644 --- a/IDE/src/Debugger/DebugManager.bf +++ b/IDE/src/Debugger/DebugManager.bf @@ -138,6 +138,14 @@ namespace IDE.Debugger Allocations = 2 } + public enum OpenFileFlags + { + None, + RedirectStdInput = 1, + RedirectStdOutput = 2, + RedirectStdError = 4 + } + public List mBreakpointList = new List(); public Dictionary mStepFilterList = new Dictionary(); @@ -157,7 +165,7 @@ namespace IDE.Debugger static extern bool Debugger_OpenMiniDump(char8* filename); [CallingConvention(.Stdcall),CLink] - static extern bool Debugger_OpenFile(char8* launchPath, char8* targetPath, char8* args, char8* workingDir, void* envBlockPtr, int32 envBlockLen, bool hotSwapEnabled); + static extern bool Debugger_OpenFile(char8* launchPath, char8* targetPath, char8* args, char8* workingDir, void* envBlockPtr, int32 envBlockLen, bool hotSwapEnabled, OpenFileFlags openFileFlags); [CallingConvention(.Stdcall),CLink] static extern bool Debugger_ComptimeAttach(void* bfCompiler); @@ -165,6 +173,9 @@ namespace IDE.Debugger [CallingConvention(.Stdcall),CLink] static extern bool Debugger_Attach(int32 processId, AttachFlags attachFlags); + [CallingConvention(.Stdcall),CLink] + public static extern void Debugger_GetStdHandles(Platform.BfpFile** outStdIn, Platform.BfpFile** outStdOut, Platform.BfpFile** outStdErr); + [CallingConvention(.Stdcall),CLink] static extern void Debugger_Run(); @@ -473,7 +484,7 @@ namespace IDE.Debugger Debugger_FullReportMemory(); } - public bool OpenFile(String launchPath, String targetPath, String args, String workingDir, Span envBlock, bool isCompiled, bool hotSwapEnabled) + public bool OpenFile(String launchPath, String targetPath, String args, String workingDir, Span envBlock, bool isCompiled, bool hotSwapEnabled, OpenFileFlags openFileFlags) { DeleteAndNullify!(mRunningPath); mRunningPath = new String(launchPath); @@ -481,7 +492,7 @@ namespace IDE.Debugger mIsComptimeDebug = false; mIsRunningCompiled = isCompiled; mIsRunningWithHotSwap = hotSwapEnabled; - return Debugger_OpenFile(launchPath, targetPath, args, workingDir, envBlock.Ptr, (int32)envBlock.Length, hotSwapEnabled); + return Debugger_OpenFile(launchPath, targetPath, args, workingDir, envBlock.Ptr, (int32)envBlock.Length, hotSwapEnabled, openFileFlags); } public bool ComptimeAttach(BfCompiler compiler) @@ -1175,6 +1186,11 @@ namespace IDE.Debugger return Debugger_Attach(process.Id, attachFlags); } + public void GetStdHandles(Platform.BfpFile** outStdIn, Platform.BfpFile** outStdOut, Platform.BfpFile** outStdErr) + { + Debugger_GetStdHandles(outStdIn, outStdOut, outStdErr); + } + public DbgProfiler StartProfiling(int threadId, String desc, int sampleRate) { DbgProfiler profiler = new DbgProfiler(Debugger_StartProfiling(threadId, desc, (.)sampleRate)); diff --git a/IDE/src/IDEApp.bf b/IDE/src/IDEApp.bf index 54e5eb71..64c3357a 100644 --- a/IDE/src/IDEApp.bf +++ b/IDE/src/IDEApp.bf @@ -345,6 +345,9 @@ namespace IDE bool mProfileCompile = false; ProfileInstance mProfileCompileProfileId; + Monitor mDebugOutputMonitor = new .() ~ delete _; + String mDebugOutput = new .() ~ delete _; + #if !CLI public IPCHelper mIPCHelper ~ delete _; public bool mIPCHadFocus; @@ -3779,7 +3782,7 @@ namespace IDE } // Always write to STDOUT even if we're running as a GUI, allowing cases like RunAndWait to pass us a stdout handle - Console.Error.WriteLine("ERROR: {0}", text); + Console.Error.WriteLine("ERROR: {0}", text).IgnoreError(); #if CLI mFailed = true; @@ -3806,7 +3809,7 @@ namespace IDE mFailed = true; OutputLineSmart("ERROR: {0}", text); - Console.Error.WriteLine("ERROR: {0}", text); + Console.Error.WriteLine("ERROR: {0}", text).IgnoreError(); return null; } @@ -8250,8 +8253,11 @@ namespace IDE NOP!(); } - mConsolePanel.SysKeyDown(evt); - //mTerminalPanel.SysKeyDown(evt); + if (!evt.mKeyFlags.HeldKeys.HasFlag(.Alt)) + { + mConsolePanel.SysKeyDown(evt); + mTerminalPanel.SysKeyDown(evt); + } if (evt.mHandled) return; @@ -8426,8 +8432,8 @@ namespace IDE void SysKeyUp(KeyCode keyCode) { - //mTerminalPanel.SysKeyUp(keyCode); mConsolePanel.SysKeyUp(keyCode); + mTerminalPanel.SysKeyUp(keyCode); } void ShowOpenFileInSolutionDialog() @@ -8786,8 +8792,10 @@ namespace IDE return; StreamReader streamReader = scope StreamReader(fileStream, null, false, 4096); + int count = 0; while (true) { + count++; var buffer = scope String(); if (streamReader.ReadLine(buffer) case .Err) break; @@ -8816,6 +8824,60 @@ namespace IDE } } + void ReadDebugOutputThread(Object obj) + { + FileStream fileStream = (.)obj; + + int count = 0; + Loop: while (true) + { + uint8[4096] data = ?; + switch (fileStream.TryRead(data, -1)) + { + case .Ok(let len): + if (len == 0) + break Loop; + using (mDebugOutputMonitor.Enter()) + { + for (int i < len) + mDebugOutput.Append((char8)data[i]); + } + case .Err: + break Loop; + } + + /*var buffer = scope String(); + if (streamReader.Read(buffer) case .Err) + break; + using (mDebugOutputMonitor.Enter()) + mDebugOutput.Add(new String(buffer));*/ + + count++; + } + + delete fileStream; + } + + /*static void ReadDebugErrorThread(Object obj) + { + ExecutionInstance executionInstance = (ExecutionInstance)obj; + + FileStream fileStream = scope FileStream(); + if (executionInstance.mProcess.AttachStandardError(fileStream) case .Err) + return; + StreamReader streamReader = scope StreamReader(fileStream, null, false, 4096); + + while (true) + { + var buffer = scope String(); + if (streamReader.ReadLine(buffer) case .Err) + break; + + using (IDEApp.sApp.mMonitor.Enter()) + executionInstance.mDeferredOutput.Add(new String(buffer)); + } + }*/ + public enum RunFlags { None, @@ -11943,8 +12005,26 @@ namespace IDE return true; } - if (!mDebugger.OpenFile(launchPath, targetPath, arguments, workingDir, envBlock, wasCompiled, workspaceOptions.mAllowHotSwapping)) + if (mSettings.mDebugConsoleKind == .Embedded) + { + ShowConsole(); + mConsolePanel.Attach(); + } + + if (mSettings.mDebugConsoleKind == .RedirectToImmediate) + { + ShowImmediatePanel(); + } + + DebugManager.OpenFileFlags openFileFlags = .None; + + if ((mSettings.mDebugConsoleKind == .RedirectToImmediate) || (mSettings.mDebugConsoleKind == .RedirectToOutput)) + openFileFlags |= .RedirectStdOutput | .RedirectStdError; + + if (!mDebugger.OpenFile(launchPath, targetPath, arguments, workingDir, envBlock, wasCompiled, workspaceOptions.mAllowHotSwapping, openFileFlags)) { + if (!mSettings.mAlwaysEnableConsole) + mConsolePanel.Detach(); DeleteAndNullify!(mCompileAndRunStopwatch); return false; } @@ -12301,8 +12381,10 @@ namespace IDE mOutputPanel = new OutputPanel(true); mOutputPanel.mAutoDelete = false; mTerminalPanel = new TerminalPanel(); + mTerminalPanel .Init(); mTerminalPanel.mAutoDelete = false; mConsolePanel = new ConsolePanel(); + mConsolePanel.Init(); mConsolePanel.mAutoDelete = false; mImmediatePanel = new ImmediatePanel(); mImmediatePanel.mAutoDelete = false; @@ -12963,6 +13045,37 @@ namespace IDE { mDebugger.Update(); + Platform.BfpFile* stdOut = null; + Platform.BfpFile* stdError = null; + mDebugger.GetStdHandles(null, &stdOut, &stdError); + if (stdOut != null) + { + FileStream fileStream = new FileStream(); + fileStream.Attach(stdOut); + Thread thread = new Thread(new => ReadDebugOutputThread); + thread.Start(fileStream, true); + } + if (stdError != null) + { + FileStream fileStream = new FileStream(); + fileStream.Attach(stdError); + Thread thread = new Thread(new => ReadDebugOutputThread); + thread.Start(fileStream, true); + } + + using (mDebugOutputMonitor.Enter()) + { + if (!mDebugOutput.IsEmpty) + { + mDebugOutput.Replace("\r", ""); + if (mSettings.mDebugConsoleKind == .RedirectToOutput) + mOutputPanel.Write(mDebugOutput); + if (mSettings.mDebugConsoleKind == .RedirectToImmediate) + mImmediatePanel.Write(mDebugOutput); + mDebugOutput.Clear(); + } + } + runState = mDebugger.GetRunState(); mDebuggerPerformingTask = (runState == .DebugEval) || (runState == .DebugEval_Done) || (runState == .SearchingSymSrv); @@ -13514,6 +13627,8 @@ namespace IDE var disassemblyPanel = TryGetDisassemblyPanel(false); if (disassemblyPanel != null) disassemblyPanel.Disable(); + if (!mSettings.mAlwaysEnableConsole) + mConsolePanel.Detach(); mDebugger.DisposeNativeBreakpoints(); mDebugger.Detach(); mDebugger.mIsRunning = false; @@ -14539,6 +14654,16 @@ namespace IDE RefreshRate = 60; } + if (mTerminalPanel != null) + { + // Detach terminal if the panel is closed + var terminalTabButton = GetTab(mTerminalPanel); + if (terminalTabButton == null) + { + mTerminalPanel.Detach(); + } + } + bool hasFocus = false; for (let window in mWindows) { @@ -14622,6 +14747,15 @@ namespace IDE if (mScriptManager != null) mScriptManager.Update(); + if (mConsolePanel != null) + { + if ((mSettings.mAlwaysEnableConsole) || + ((mSettings.mDebugConsoleKind == .Embedded) && (mDebugger.mIsRunning))) + mConsolePanel.Attach(); + else + mConsolePanel.Detach(); + } + if (mTestManager != null) { mTestManager.Update(); diff --git a/IDE/src/Settings.bf b/IDE/src/Settings.bf index 95a5c108..f487edcb 100644 --- a/IDE/src/Settings.bf +++ b/IDE/src/Settings.bf @@ -898,8 +898,10 @@ namespace IDE Add("Show Output", "Ctrl+Alt+O"); Add("Show Profiler", "Ctrl+Alt+P"); Add("Show QuickWatch", "Shift+Alt+W"); - Add("Show Threads", "Ctrl+Alt+T"); + Add("Show Threads", "Ctrl+Alt+H"); Add("Show Watches", "Ctrl+Alt+W"); + Add("Show Console", "Ctrl+Alt+N"); + Add("Show Terminal", "Ctrl+Alt+T"); Add("Show Workspace Explorer", "Ctrl+Alt+S"); Add("Start Debugging", "F5"); Add("Start Without Debugging", "Ctrl+F5"); @@ -1097,6 +1099,14 @@ namespace IDE public bool mDependencies; } + public enum ConsoleKind + { + Native, + Embedded, + RedirectToOutput, + RedirectToImmediate, + } + public bool mLoadedSettings; public String mSettingFileText ~ delete _; public DateTime mSettingFileDateTime; @@ -1110,6 +1120,8 @@ namespace IDE public RecentFiles mRecentFiles = new RecentFiles() ~ delete _; public String mWakaTimeKey = new .() ~ delete _; public String mWindowsTerminal = new .("Powershell") ~ delete _; + public ConsoleKind mDebugConsoleKind; + public bool mAlwaysEnableConsole; public String mEmscriptenPath = new .() ~ delete _; public bool mEnableDevMode; public TutorialsFinished mTutorialsFinished = .(); @@ -1167,8 +1179,12 @@ namespace IDE mDebuggerSettings.Serialize(sd); using (sd.CreateObject("VisualStudio")) mVSSettings.Serialize(sd); - using (sd.CreateObject("Terminal")) + using (sd.CreateObject("Console")) + { sd.Add("WindowsTerminal", mWindowsTerminal); + sd.Add("DebugConsole", mDebugConsoleKind); + sd.Add("AlwaysEnableConsole", mAlwaysEnableConsole); + } using (sd.CreateObject("Wasm")) sd.Add("EmscriptenPath", mEmscriptenPath); @@ -1258,8 +1274,12 @@ namespace IDE mDebuggerSettings.Deserialize(sd); using (sd.Open("VisualStudio")) mVSSettings.Deserialize(sd); - using (sd.Open("Terminal")) + using (sd.Open("Console")) + { sd.Get("WindowsTerminal", mWindowsTerminal); + mDebugConsoleKind = sd.GetEnum("DebugConsole", .Native); + mAlwaysEnableConsole = sd.GetBool("AlwaysEnableConsole"); + } using (sd.Open("Wasm")) sd.Get("EmscriptenPath", mEmscriptenPath); diff --git a/IDE/src/TestManager.bf b/IDE/src/TestManager.bf index 32e83435..c2d4cd2c 100644 --- a/IDE/src/TestManager.bf +++ b/IDE/src/TestManager.bf @@ -614,7 +614,7 @@ namespace IDE var envBlock = scope List(); Environment.EncodeEnvironmentVariables(envVars, envBlock); - if (!gApp.mDebugger.OpenFile(curProjectInfo.mTestExePath, curProjectInfo.mTestExePath, mTestInstance.mArgs, mTestInstance.mWorkingDir, envBlock, true, false)) + if (!gApp.mDebugger.OpenFile(curProjectInfo.mTestExePath, curProjectInfo.mTestExePath, mTestInstance.mArgs, mTestInstance.mWorkingDir, envBlock, true, false, .None)) { QueueOutputLine("ERROR: Failed debug '{0}'", curProjectInfo.mTestExePath); TestFailed(); diff --git a/IDE/src/ui/ConsolePanel.bf b/IDE/src/ui/ConsolePanel.bf index ca273eb1..69f83346 100644 --- a/IDE/src/ui/ConsolePanel.bf +++ b/IDE/src/ui/ConsolePanel.bf @@ -11,155 +11,17 @@ using Beefy.events; using System.Diagnostics; using Beefy.utils; using System.Threading; +using IDE.util; namespace IDE.ui; class ConsolePanel : Panel { - [CRepr] - struct CONSOLE_SCREEN_BUFFER_INFOEX - { - public uint32 mSize; - public int16 mWidth; - public int16 mHeight; - public uint16 mCursorX; - public uint16 mCursorY; - public uint16 wAttributes; - public RECT mWindowRect; - public POINT mMaximumWindowSize; - public uint16 mPopupAttributes; - public Windows.IntBool mFullscreenSupported; - public uint32[16] mColorTable; - - public this() - { - this = default; - mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); - } - } - - [CRepr] - struct POINT : this(int16 x, int16 y) - { - } - - [CRepr] - struct RECT : this(int16 left, int16 top, int16 right, int16 bottom) - { - public int16 Width => right - left; - public int16 Height => bottom - top; - } - - [CRepr] - struct CHAR_INFO - { - public char16 mChar; - public uint16 mAttributes; - } - - [CRepr] - struct CONSOLE_FONT_INFO - { - public uint32 mNumFont; - public POINT mSize; - } - - [CRepr] - struct CONSOLE_CURSOR_INFO - { - public uint32 mSize; - public uint32 mVisible; - } - - [CRepr] - struct CONSOLE_SELECTION_INFO - { - public uint32 mFlags; - public POINT mSelectionAnchor; - public RECT mSelection; - } - - [CRepr] - struct KEY_EVENT_RECORD - { - public int32 mKeyDown; - public uint16 mRepeatCount; - public uint16 mVirtualKeyCode; - public uint16 mVirtualScanCode; - public char16 mChar; - public uint32 mControlKeyState; - } - - [CRepr] - struct MOUSE_EVENT_RECORD - { - public POINT mMousePosition; - public uint32 mButtonState; - public uint32 mControlKeyState; - public uint32 mEventFlags; - } - - [CRepr] - struct INPUT_RECORD - { - public uint16 mEventType; - public INPUT_RECORD_DATA mEventData; - } - - [Union] - struct INPUT_RECORD_DATA - { - public KEY_EVENT_RECORD mKeyEvent; - public MOUSE_EVENT_RECORD mMouseEvent; - } - -#if BF_PLATFORM_WINDOWS - [CLink, CallingConvention(.Stdcall)] - public static extern void AllocConsole(); - - [CLink, CallingConvention(.Stdcall)] - public static extern void AttachConsole(int processId); - - [CLink, CallingConvention(.Stdcall)] - public static extern void FreeConsole(); - - [CLink, CallingConvention(.Stdcall)] - public static extern Windows.IntBool GetConsoleScreenBufferInfoEx(Windows.Handle handle, ref CONSOLE_SCREEN_BUFFER_INFOEX info); - - [CLink, CallingConvention(.Stdcall)] - public static extern Windows.IntBool SetConsoleScreenBufferInfoEx(Windows.Handle handle, ref CONSOLE_SCREEN_BUFFER_INFOEX info); - - [CLink] - public static extern Windows.IntBool ReadConsoleOutputW(Windows.Handle handle, void* buffer, POINT bufferSize, POINT bufferCoord, ref RECT readRegion); - - [CLink] - public static extern Windows.IntBool SetConsoleScreenBufferSize(Windows.Handle handle, POINT bufferSize); - - [CLink] - public static extern Windows.IntBool SetConsoleWindowInfo(Windows.Handle handle, Windows.IntBool absolute, in RECT window); - - [CLink] - public static extern Windows.HWnd GetConsoleWindow(); - - [CLink] - public static extern Windows.IntBool GetCurrentConsoleFont(Windows.Handle handle, Windows.IntBool maxWindow, out CONSOLE_FONT_INFO fontInfo); - - [CLink] - public static extern Windows.IntBool GetConsoleCursorInfo(Windows.Handle handle, out CONSOLE_CURSOR_INFO cursorInfo); - - [CLink] - public static extern Windows.IntBool GetConsoleSelectionInfo(out CONSOLE_SELECTION_INFO selectionInfo); - - [CLink] - public static extern Windows.IntBool WriteConsoleInputW(Windows.Handle handle, INPUT_RECORD* eventsPtr, int32 eventCount, out int32 numEventsWritten); - - [CLink] - public static extern Windows.IntBool ReadConsoleInputW(Windows.Handle handle, INPUT_RECORD* eventsPtr, int32 eventCount, out int32 numEventsRead); -#endif - class View : Widget { public ConsolePanel mConsolePanel; + public Vector2? mMousePos; + public int32 mClickCount; public this(ConsolePanel ConsolePanel) { @@ -170,29 +32,35 @@ class ConsolePanel : Panel { base.MouseDown(x, y, btn, btnCount); + mConsolePanel.mSelection = null; + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); - if (mConsolePanel.mMousePassThrough) + if (mConsolePanel.MousePassThrough) { var cell = mConsolePanel.GetCell(x, y); - INPUT_RECORD input = default; - input.mEventType = 2 /*MOUSE_EVENT */; - input.mEventData.mMouseEvent.mButtonState = (.)mMouseFlags; - if (btnCount > 1) - input.mEventData.mMouseEvent.mEventFlags |= 2; - input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); - input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags(false)); - WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + mConsolePanel.mConsoleProvider.MouseDown((.)cell.mCol, (.)cell.mRow, (.)mMouseFlags, btnCount, mWidgetWindow.GetKeyFlags(false)); } else { if (btn == 0) { + var cell = mConsolePanel.GetCell(x, y); + mConsolePanel.mSelection = null; + mConsolePanel.mClickPos = cell; + mClickCount = btnCount; + if (mClickCount > 1) + { + // Force selection + MouseMove(x, y); + } } else if (btn == 1) { var text = gApp.GetClipboardText(.. scope .()); - for (var c in text.DecodedChars) + mConsolePanel.mConsoleProvider.SendInput(text); + + /*for (var c in text.DecodedChars) { INPUT_RECORD input = default; input.mEventType = 1 /*KEY_EVENT */; @@ -203,7 +71,7 @@ class ConsolePanel : Panel //input.mEventData.mKeyEvent.mControlKeyState = GetControlKeyState(keyEvent.mKeyFlags); input.mEventData.mKeyEvent.mChar = (.)c; WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); - } + }*/ } } @@ -223,49 +91,113 @@ class ConsolePanel : Panel //Windows.SendMessageW(window, 0x0100, 0, 0); }*/ + + mMousePos = .(x, y); } public override void MouseMove(float x, float y) { base.MouseMove(x, y); - - var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); var cell = mConsolePanel.GetCell(x, y); - INPUT_RECORD input = default; - input.mEventType = 2 /*MOUSE_EVENT */; - input.mEventData.mMouseEvent.mButtonState = (.)mMouseFlags; - input.mEventData.mMouseEvent.mEventFlags |= 1; /* MOUSE_MOVED */ - input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); - input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags(false)); - WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + mConsolePanel.mConsoleProvider.MouseMove((.)cell.mCol, (.)cell.mRow, (.)mMouseFlags, mWidgetWindow.GetKeyFlags(false)); - /*var window = GetConsoleWindow(); - Windows.SendMessageW(window, 0x0200 /*WM_MOUSEMOVE*/, 0, (int)x | ((int)y << 16));*/ + if (mMouseFlags.HasFlag(.Left)) + { + if (mConsolePanel.mClickPos != null) + { + mConsolePanel.mSelection = (mConsolePanel.mClickPos.Value, cell); + + if (mClickCount > 1) + { + bool reversed = false; + int startIdx = mConsolePanel.mSelection.Value.start.GetIndex(mConsolePanel.mConsoleProvider.Width, 0); + int endIdx = mConsolePanel.mSelection.Value.end.GetIndex(mConsolePanel.mConsoleProvider.Width, 0); + + if (startIdx > endIdx) + { + reversed = true; + Swap!(startIdx, endIdx); + } + + if (mClickCount > 1) + { + while (true) + { + int checkStartIdx = startIdx - 1; + var checkCell = mConsolePanel.mConsoleProvider.GetCell(checkStartIdx); + if (!checkCell.mChar.IsLetterOrDigit) + break; + startIdx = checkStartIdx; + } + + while (true) + { + int checkEndIdx = endIdx + 1; + var checkCell = mConsolePanel.mConsoleProvider.GetCell(checkEndIdx); + if (!checkCell.mChar.IsLetterOrDigit) + break; + endIdx = checkEndIdx; + } + } + + mConsolePanel.mSelection = ( + Position.FromIndex(reversed ? endIdx : startIdx, mConsolePanel.mConsoleProvider.Width), + Position.FromIndex(reversed ? startIdx : endIdx, mConsolePanel.mConsoleProvider.Width)); + } + } + } + + mMousePos = .(x, y); } public override void MouseUp(float x, float y, int32 btn) { base.MouseUp(x, y, btn); - - var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); var cell = mConsolePanel.GetCell(x, y); - INPUT_RECORD input = default; - input.mEventType = 2 /*MOUSE_EVENT */; - input.mEventData.mMouseEvent.mButtonState = (.)mMouseFlags; - //input.mEventData.mMouseEvent.mEventFlags |= 1; /* MOUSE_MOVED */ - input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); - input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags(false)); - WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + mConsolePanel.mConsoleProvider.MouseUp((.)cell.mCol, (.)cell.mRow, (.)mMouseFlags, mWidgetWindow.GetKeyFlags(false)); - /*var window = GetConsoleWindow(); - Windows.SendMessageW(window, 0x0202 /*WM_LBUTTONUP*/, 0, (int)x | ((int)y << 16));*/ + if (btn == 0) + { + if (mConsolePanel.mSelection != null) + { + int32 numVisibleCols = (.)mConsolePanel.mConsoleProvider.Width; + + int selStart = mConsolePanel.mSelection.Value.start.GetIndex(numVisibleCols, 0); + int selEnd = mConsolePanel.mSelection.Value.end.GetIndex(numVisibleCols, 0); + + if (selStart > selEnd) + Swap!(selStart, selEnd); + + String str = scope .(); + for (int i in selStart ... selEnd) + { + var cellInfo = mConsolePanel.mConsoleProvider.GetCell(i); + + if ((i % numVisibleCols == 0) && (i != selStart)) + { + // Start of new line + while ((str.Length > 0) && (str[str.Length - 1] == ' ')) + str.RemoveFromEnd(1); + str.Append("\n"); + } + + str.Append(cellInfo.mChar); + } + + gApp.SetClipboardText(str); + } + + mConsolePanel.mClickPos = null; + } + + mMousePos = null; } public override void KeyDown(KeyDownEvent keyEvent) { base.KeyDown(keyEvent); - if (keyEvent.mKeyCode == .Insert) + /*if (keyEvent.mKeyCode == .Insert) { ProcessStartInfo procInfo = scope ProcessStartInfo(); procInfo.UseShellExecute = false; @@ -278,23 +210,25 @@ class ConsolePanel : Panel if (keyEvent.mKeyCode == .Tilde) { - if (mConsolePanel.mHasConsole) + if (mConsolePanel.mConsoleProvider.Attached) { - mConsolePanel.Detach(); + mConsolePanel.mConsoleProvider.Detach(); } else { - mConsolePanel.Attach(); + mConsolePanel.mConsoleProvider.Attach(); ProcessStartInfo procInfo = scope ProcessStartInfo(); procInfo.UseShellExecute = false; procInfo.SetFileName("Powershell.exe"); String resultStr = scope String(); - mConsolePanel.mExecSpawn = new SpawnedProcess(); - mConsolePanel.mExecSpawn.Start(procInfo); + var spawn = scope SpawnedProcess(); + spawn.Start(procInfo); + + mConsolePanel.ResizeComponents(); } - } + }*/ } public override void KeyUp(KeyCode keyCode) @@ -312,82 +246,97 @@ class ConsolePanel : Panel public override void MouseWheel(MouseEvent evt) { - if ((mConsolePanel.mPaused) || (mConsolePanel.mHasConsole)) + if ((mConsolePanel.Paused) || (!mConsolePanel.mConsoleProvider.Attached)) { base.MouseWheel(evt); return; } - /*var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); var cell = mConsolePanel.GetCell(evt.mX, evt.mY); - INPUT_RECORD input = default; - input.mEventType = 2 /*MOUSE_EVENT */; - input.mEventData.mMouseEvent.mButtonState = (.)((int32)mMouseFlags | ((int32)evt.mWheelDeltaY << 16)); - input.mEventData.mMouseEvent.mEventFlags |= 4 /* MOUSE_WHEELED */; - input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); - input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags()); - WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten);*/ - - float x = evt.mX; - float y = evt.mY; - - var window = GetConsoleWindow(); - Windows.SendMessageW(window, 0x0007, 0, 0); // WM_SETFOCUS - //Windows.SendMessageW(window, 0x0006, 0, 0); // WM_ACTIVATE - - - Windows.SendMessageW(window, 0x0200 /*WM_MOUSEMOVE*/, 0, (int)x | ((int)y << 16)); - Windows.SendMessageW(window, 0x020A /*WM_MOUSEWHEEL*/, (int32)(120 * evt.mWheelDeltaY) << 16, (int)x | ((int)y << 16)); + mConsolePanel.mConsoleProvider.MouseWheel((.)cell.mCol, (.)cell.mRow, (.)evt.mWheelDeltaY); } } - class ScreenInfo + public struct Position { - public CONSOLE_SCREEN_BUFFER_INFOEX mInfo; - public CONSOLE_CURSOR_INFO mCursorInfo; - public CONSOLE_SELECTION_INFO mSelectionInfo; - public int32 mScrollTop; - public CHAR_INFO* mCharInfo; - public CHAR_INFO* mFullCharInfo; + public int32 mCol; + public int32 mRow; - public int32 WindowWidth => mInfo.mWindowRect.Width; - public int32 WindowHeight => mInfo.mWindowRect.Height; - - public ~this() + public this(int32 col, int32 row) { - delete mCharInfo; - delete mFullCharInfo; + mCol = col; + mRow = row; } - public int GetHashCode() + public int32 GetIndex(int width, int32 rowOfs) { - MD5 md5 = scope .(); - md5.Update(.((.)&mInfo, sizeof(CONSOLE_SCREEN_BUFFER_INFOEX))); - md5.Update(.((.)&mSelectionInfo, sizeof(CONSOLE_SELECTION_INFO))); - md5.Update(.((.)&mCursorInfo, sizeof(CONSOLE_CURSOR_INFO))); - md5.Update(.((.)mCharInfo, (int32)mInfo.mWindowRect.Width * mInfo.mWindowRect.Height * sizeof(CHAR_INFO))); - var hash = md5.Finish(); - return hash.GetHashCode(); + return (.)((mRow + rowOfs) * width + mCol); + } + + public static Position FromIndex(int index, int width) + { + return .((.)(index % width), (.)(index / width)); } } - public int mLastDrawnHashCode; + public ConsoleProvider mConsoleProvider ~ delete _; + public DarkCheckBox mMousePassthroughCB; + public DarkCheckBox mPauseCB; public DarkScrollbar mScrollbar; public ScrollableWidget mScrollableWidget; public int32 mCellWidth; public int32 mCellHeight; - public bool mPaused; - ScreenInfo mScreenInfo ~ delete _; + public (Position start, Position end)? mSelection; + public Position? mClickPos; + + public bool Paused + { + get + { + return mPauseCB.Checked || (mSelection != null); + } + + set + { + mPauseCB.Checked = value; + } + } + + public bool MousePassThrough + { + get + { + return mMousePassthroughCB.Checked; + } + + set + { + mMousePassthroughCB.Checked = value; + } + } + View mView; int mCursorBlinkTicks; - SpawnedProcess mCmdSpawn ~ delete _; - SpawnedProcess mExecSpawn ~ delete _; - bool mHasConsole; - bool mMousePassThrough; - (POINT start, POINT end)? mSelection; + public this() { + mMousePassthroughCB = new DarkCheckBox(); + mMousePassthroughCB.Label = "Mouse Passthrough"; + AddWidget(mMousePassthroughCB); + + mPauseCB = new DarkCheckBox(); + mPauseCB.Label = "Pause"; + AddWidget(mPauseCB); + + /*mMouseComboBox = new DarkComboBox(); + mMouseComboBox.mFrameKind = .Frameless; + mMouseComboBox.mPopulateMenuAction.Add(new (menu) => + { + + }); + AddWidget(mMouseComboBox);*/ + /*mScrollbar = new DarkScrollbar(); mScrollbar.mOrientation = .Vert; mScrollbar.Init(); @@ -404,14 +353,13 @@ class ConsolePanel : Panel mScrollableWidget.mVertScrollbar.mOnScrollEvent.Add(new (evt) => { - mPaused = true; + Paused = true; }); } - public ~this() + public virtual void Init() { - mCmdSpawn?.Kill(); - mExecSpawn?.Kill(); + mConsoleProvider = new WinNativeConsoleProvider(); } public override void Serialize(StructuredData data) @@ -427,80 +375,61 @@ class ConsolePanel : Panel } - public void Attach() - { - if (mHasConsole) - return; - - mHasConsole = true; - -#if BF_PLATFORM_WINDOWS - //AllocConsole(); - - /*ProcessStartInfo procInfo = scope ProcessStartInfo(); - procInfo.UseShellExecute = false; - procInfo.SetFileName(scope $"{gApp.mInstallDir}/BeefCon_d.exe"); - procInfo.SetArguments(scope $"{Process.CurrentId}"); - - String resultStr = scope String(); - mCmdSpawn = new SpawnedProcess(); - mCmdSpawn.Start(procInfo); - - Thread.Sleep(2000); - - var processId = mCmdSpawn.ProcessId; - if (processId > 0) - AttachConsole(processId); - else*/ - AllocConsole(); - - var window = GetConsoleWindow(); - Windows.SetWindowPos(window, default, 0, 0, 0, 0, 0x290 /* SWP_NOACTIVATE | SWP_NOREPOSITION | SWP_HIDEWINDOW */); - - ResizeComponents(); -#endif - } - - public void Detach() - { - if (!mHasConsole) - return; - - mHasConsole = false; - -#if BF_PLATFORM_WINDOWS - FreeConsole(); -#endif - - mCmdSpawn?.Kill(); - DeleteAndNullify!(mCmdSpawn); - mExecSpawn?.Kill(); - DeleteAndNullify!(mExecSpawn); - } public override void Update() { base.Update(); - if (mScrollableWidget.mVertScrollbar.mThumb.mMouseDown) - mPaused = true; + bool scrollQuantize = true; - if (!mPaused) + if ((mView.mMousePos != null) && (mView.mMouseFlags.HasFlag(.Left)) && (mSelection != null)) { - ScreenInfo newScreenInfo = new .(); - if (GetScreenInfo(newScreenInfo)) + mWidgetWindow.RehupMouse(true); + mView.SelfToParentTranslate(mView.mMousePos.Value.mX, mView.mMousePos.Value.mY, var parentX, var parentY); + + float scrollDelta = 0; + if (parentY < 0) { - delete mScreenInfo; - mScreenInfo = newScreenInfo; + scrollDelta = parentY; } - else + else if (parentY > mView.mParent.mHeight) { - Detach(); - delete newScreenInfo; + scrollDelta = parentY - mView.mParent.mHeight; + } + + if (scrollDelta != 0) + { + scrollQuantize = false; + mScrollableWidget.VertScrollTo(mScrollableWidget.mVertPos.mDest + scrollDelta * 0.15f); } } - if (mScreenInfo != null) + if (mScrollableWidget.mVertScrollbar.mThumb.mMouseDown) + { + Paused = true; + scrollQuantize = false; + } + + if (Paused) + { + if (mUpdateCnt % 30 == 0) + { + mPauseCB.Label = "Paused"; + for (int i < (mUpdateCnt / 30) % 4) + mPauseCB.mLabel.Append("."); + MarkDirty(); + } + } + else + mPauseCB.Label = "Paused"; + + if (!Paused) + { + if (mConsoleProvider.Update() case .Dirty) + MarkDirty(); + } + + /*if (mScreenInfo != null) { if ((mPaused) || (!mHasConsole)) { @@ -512,6 +441,19 @@ class ConsolePanel : Panel UpdateScreenInfo(mScreenInfo); } + }*/ + + if (Paused || !mConsoleProvider.Attached) + { + int scrollTop = (.)(mScrollableWidget.mVertScrollbar.mContentPos / mCellHeight); + float wantScrollPos = scrollTop * mCellHeight; + if ((wantScrollPos != mScrollableWidget.mVertScrollbar.mContentPos) && (scrollQuantize)) + mScrollableWidget.VertScrollTo(wantScrollPos, true); + mConsoleProvider.ScrollTo(scrollTop); + } + else + { + mScrollableWidget.VertScrollTo(mConsoleProvider.ScrollTop * mCellHeight); } //mPaused = false; @@ -550,13 +492,7 @@ class ConsolePanel : Panel } } - int hashCode = (mScreenInfo?.GetHashCode()).GetValueOrDefault(); - - if (hashCode != mLastDrawnHashCode) - { - mLastDrawnHashCode = hashCode; - MarkDirty(); - } + //float height = mScreenInfo.mInfo.mHeight * mCellHeight; //mScrollableWidget.mScrollContent.Resize(0, 0, 0, height); @@ -567,84 +503,15 @@ class ConsolePanel : Panel MarkDirty(); } - public bool GetScreenInfo(ScreenInfo screenInfo) - { - var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); - - CONSOLE_SCREEN_BUFFER_INFOEX info = default; - info.mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); - -#if BF_PLATFORM_WINDOWS - if (!GetConsoleScreenBufferInfoEx(outHandle, ref info)) - return false; -#endif - info.mWindowRect.right++; - info.mWindowRect.bottom++; - screenInfo.mInfo = info; - - screenInfo.mScrollTop = info.mWindowRect.top; - - mScrollableWidget.VertScrollTo(screenInfo.mInfo.mWindowRect.top * mCellHeight); - - int width = info.mWindowRect.Width; - int height = info.mWindowRect.Height; - - POINT bufferSize = .(info.mWindowRect.Width, info.mWindowRect.Height); - screenInfo.mCharInfo = new .[(int32)info.mWindowRect.Width * info.mWindowRect.Height]*; - RECT readRegion = .(screenInfo.mInfo.mWindowRect.left, (.)screenInfo.mScrollTop, screenInfo.mInfo.mWindowRect.right, (.)(screenInfo.mScrollTop + screenInfo.mInfo.mWindowRect.Height - 1)); -#if BF_PLATFORM_WINDOWS - ReadConsoleOutputW(outHandle, screenInfo.mCharInfo, bufferSize, POINT(0, 0), ref readRegion); - - GetConsoleCursorInfo(outHandle, out screenInfo.mCursorInfo); - GetConsoleSelectionInfo(out screenInfo.mSelectionInfo); -#endif - return true; - } - - public bool GetFullScreenInfo(ScreenInfo screenInfo) - { - if (screenInfo.mFullCharInfo != null) - return true; - - if (screenInfo.mCharInfo == null) - { - if (!GetScreenInfo(screenInfo)) - return false; - } - - var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); - POINT bufferSize = .(screenInfo.mInfo.mWidth, screenInfo.mInfo.mHeight); - screenInfo.mFullCharInfo = new .[(int32)screenInfo.mInfo.mWidth * screenInfo.mInfo.mHeight]*; - RECT readRegion = .(0, 0, screenInfo.mInfo.mWidth, screenInfo.mInfo.mHeight); -#if BF_PLATFORM_WINDOWS - ReadConsoleOutputW(outHandle, screenInfo.mFullCharInfo, bufferSize, POINT(0, 0), ref readRegion); -#endif - - return true; - } - - public bool UpdateScreenInfo(ScreenInfo screenInfo) - { - if (screenInfo.mFullCharInfo == null) - { - if (!GetFullScreenInfo(screenInfo)) - return false; - } - - Internal.MemCpy(screenInfo.mCharInfo, - screenInfo.mFullCharInfo + screenInfo.mScrollTop * screenInfo.mInfo.mWidth, - screenInfo.mInfo.mWindowRect.Width * screenInfo.mInfo.mWindowRect.Height * sizeof(CHAR_INFO)); - return true; - } public Vector2 GetCoord(int col, int row) { return .(col * mCellWidth + GS!(6), row * mCellHeight + GS!(4)); } - public Vector2 GetCell(float x, float y) + public Position GetCell(float x, float y) { - return .((x - GS!(6)) / mCellWidth, (y - GS!(4)) / mCellHeight); + return .((.)((x - GS!(6)) / mCellWidth), (.)((y - GS!(4)) / mCellHeight)); } public override void Draw(Graphics g) @@ -654,20 +521,8 @@ class ConsolePanel : Panel var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); String str = scope .(" "); - uint32[16] colorTable = .(0xFF000000, ); - if (mScreenInfo != null) - { - for (int i < 16) - { - colorTable[i] = 0xFF000000 | - ((mScreenInfo.mInfo.mColorTable[i] >> 16) & 0x0000FF) | - ((mScreenInfo.mInfo.mColorTable[i] ) & 0x00FF00) | - ((mScreenInfo.mInfo.mColorTable[i] << 16) & 0xFF0000); - } - } - g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.EditBox), 0, 0, mWidth, mScrollableWidget.mHeight); - using (g.PushColor(colorTable[0])) + using (g.PushColor(mConsoleProvider.GetColor(0))) g.FillRect(GS!(2), GS!(2), mScrollableWidget.mVertScrollbar.mX - GS!(0), mScrollableWidget.mHeight - GS!(4)); if (mView.mHasFocus) { @@ -675,135 +530,161 @@ class ConsolePanel : Panel g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Outline), 0, 0, mWidth, mHeight); } - if (mScreenInfo != null) + int32 topOfs = (.)mConsoleProvider.ScrollTop; + var cursorPos = mConsoleProvider.CursorPos; + + g.SetFont(gApp.mTermFont); + using (g.PushClip(0, 0, mScrollableWidget.mVertScrollbar.mX, mScrollableWidget.mHeight - GS!(2))) { - g.SetFont(gApp.mTermFont); - using (g.PushClip(0, 0, mScrollableWidget.mVertScrollbar.mX, mScrollableWidget.mHeight - GS!(2))) + int32 numVisibleCols = (.)mConsoleProvider.Width; + int32 numVisibleRows = (.)mConsoleProvider.Height; + + int32 selStart = -1; + int32 selEnd = -1; + + if (mSelection != null) { - int32 numVisibleCols = mScreenInfo.WindowWidth; - int32 numVisibleRows = mScreenInfo.WindowHeight; + selStart = mSelection.Value.start.GetIndex(numVisibleCols, -topOfs); + selEnd = mSelection.Value.end.GetIndex(numVisibleCols, -topOfs); - for (int32 row < numVisibleRows) + if (selStart > selEnd) + Swap!(selStart, selEnd); + + /*if (mSelection.Value.start.GetIndex(numVisibleCols, -topOfs) < mSelection.Value.end.GetIndex(numVisibleCols, -topOfs)) { - for (int32 col < numVisibleCols) - { - int srcRow = row + mScreenInfo.mScrollTop; - - var coord = GetCoord(col, row); - var cInfo = mScreenInfo.mCharInfo[row * mScreenInfo.WindowWidth + col]; - - int32 attrs = cInfo.mAttributes; - - if (mScreenInfo.mSelectionInfo.mFlags != 0) - { - //TODO: Fix rendering, listen to flags. - - bool selected = false; - if (srcRow == mScreenInfo.mSelectionInfo.mSelection.top) - { - selected = (col >= mScreenInfo.mSelectionInfo.mSelection.left); - if (srcRow == mScreenInfo.mSelectionInfo.mSelection.bottom) - selected &= (col <= mScreenInfo.mSelectionInfo.mSelection.right); - } - else if ((srcRow > mScreenInfo.mSelectionInfo.mSelection.top) && (srcRow < mScreenInfo.mSelectionInfo.mSelection.bottom)) - { - selected = true; - } - else if (srcRow == mScreenInfo.mSelectionInfo.mSelection.bottom) - { - selected = (col <= mScreenInfo.mSelectionInfo.mSelection.right); - } - - if (selected) - attrs ^= 0xFF; - } - - uint32 fgColor = colorTable[(attrs & 0xF)]; - uint32 bgColor = colorTable[(attrs >> 4)]; - - - using (g.PushColor(bgColor)) - { - int32 fillX = (.)coord.mX; - int32 fillY = (.)coord.mY; - int32 fillWidth = mCellWidth; - int32 fillHeight = mCellHeight; - - /*if (col == 0) - { - fillX -= GS!(4); - fillWidth += GS!(4); - } - - if (row == 0) - { - fillY -= GS!(2); - fillHeight += GS!(2); - } - - if (row == numVisibleRows - 1) - { - - } - - g.FillRect( - fillX, fillY, - (col == numVisibleCols - 1) ? (mScrollableWidget.mVertScrollbar.mX - coord.mX + 1) : fillWidth, - (row == numVisibleRows - 1) ? (mView.mHeight - coord.mY) : fillHeight - );*/ - g.FillRect(fillX, fillY, fillWidth, fillHeight); - } - - if (cInfo.mChar > .(32)) - { - str[0] = (.)cInfo.mChar; - using (g.PushColor(fgColor)) - gApp.mTermFont.Draw(g, str, coord.mX, coord.mY); - } - } + } - - if ((mView.mHasFocus) && (mHasConsole) && (!mPaused)) + else { - float brightness = (float)Math.Cos(Math.Max(0.0f, mCursorBlinkTicks - 20) / 9.0f); - brightness = Math.Clamp(brightness * 2.0f + 1.6f, 0, 1); - if (mScrollableWidget.mVertPos.IsMoving) - brightness = 0; // When we animate a pgup or pgdn, it's weird seeing the cursor scrolling around + selStart = mSelection.Value.end.GetIndex(numVisibleCols, -topOfs); + selEnd = mSelection.Value.start.GetIndex(numVisibleCols, -topOfs); + }*/ + } - if (brightness > 0) + for (int32 row < numVisibleRows) + { + for (int32 col < numVisibleCols) + { + int32 cellIdx = row * numVisibleCols + col; + + int srcRow = row + mConsoleProvider.ScrollTop; + + var coord = GetCoord(col, row); + var cInfo = mConsoleProvider.GetCell(col, row); + + int32 attrs = cInfo.mAttributes; + + if ((cellIdx >= selStart) && (cellIdx <= selEnd)) + attrs ^= 0xFF; + + /*if (mScreenInfo.mSelectionInfo.mFlags != 0) { - using (g.PushColor(Color.Get(brightness))) + //TODO: Fix rendering, listen to flags. + + bool selected = false; + if (srcRow == mScreenInfo.mSelectionInfo.mSelection.top) { - var cursorCoord = GetCoord(mScreenInfo.mInfo.mCursorX, mScreenInfo.mInfo.mCursorY - mScreenInfo.mScrollTop); - if (mScreenInfo.mCursorInfo.mVisible != 0) - { - int32 cursorHeight = (int32)mScreenInfo.mCursorInfo.mSize * mCellHeight / 100; - g.FillRect(cursorCoord.mX, cursorCoord.mY + mCellHeight - cursorHeight, mCellWidth, cursorHeight); - } + selected = (col >= mScreenInfo.mSelectionInfo.mSelection.left); + if (srcRow == mScreenInfo.mSelectionInfo.mSelection.bottom) + selected &= (col <= mScreenInfo.mSelectionInfo.mSelection.right); } + else if ((srcRow > mScreenInfo.mSelectionInfo.mSelection.top) && (srcRow < mScreenInfo.mSelectionInfo.mSelection.bottom)) + { + selected = true; + } + else if (srcRow == mScreenInfo.mSelectionInfo.mSelection.bottom) + { + selected = (col <= mScreenInfo.mSelectionInfo.mSelection.right); + } + + if (selected) + attrs ^= 0xFF; + }*/ + + uint32 fgColor = mConsoleProvider.GetColor(attrs & 0xF); + uint32 bgColor = mConsoleProvider.GetColor(attrs >> 4); + + using (g.PushColor(bgColor)) + { + int32 fillX = (.)coord.mX; + int32 fillY = (.)coord.mY; + int32 fillWidth = mCellWidth; + int32 fillHeight = mCellHeight; + + /*if (col == 0) + { + fillX -= GS!(4); + fillWidth += GS!(4); + } + + if (row == 0) + { + fillY -= GS!(2); + fillHeight += GS!(2); + } + + if (row == numVisibleRows - 1) + { + + } + + g.FillRect( + fillX, fillY, + (col == numVisibleCols - 1) ? (mScrollableWidget.mVertScrollbar.mX - coord.mX + 1) : fillWidth, + (row == numVisibleRows - 1) ? (mView.mHeight - coord.mY) : fillHeight + );*/ + g.FillRect(fillX, fillY, fillWidth, fillHeight); + } + + if (cInfo.mChar > .(32)) + { + str[0] = (.)cInfo.mChar; + using (g.PushColor(fgColor)) + gApp.mTermFont.Draw(g, str, coord.mX, coord.mY); } } } + if ((mView.mHasFocus) && (mConsoleProvider.Connected) && (!Paused)) + { + float brightness = (float)Math.Cos(Math.Max(0.0f, mCursorBlinkTicks - 20) / 9.0f); + brightness = Math.Clamp(brightness * 2.0f + 1.6f, 0, 1); + if (mScrollableWidget.mVertPos.IsMoving) + brightness = 0; // When we animate a pgup or pgdn, it's weird seeing the cursor scrolling around - g.SetFont(DarkTheme.sDarkTheme.mSmallFont); - g.DrawString(scope $"Ln {mScreenInfo.mInfo.mCursorY + 1}", mWidth - GS!(120), mHeight - GS!(21)); - g.DrawString(scope $"Col {mScreenInfo.mInfo.mCursorX + 1}", mWidth - GS!(60), mHeight - GS!(21)); + if (brightness > 0) + { + using (g.PushColor(Color.Get(brightness))) + { + var cursorCoord = GetCoord(cursorPos.col, cursorPos.row - mConsoleProvider.ScrollTop); + if (mConsoleProvider.CursorVisible) + { + int32 cursorHeight = (int32)(mConsoleProvider.CursorHeight * mCellHeight); + g.FillRect(cursorCoord.mX, cursorCoord.mY + mCellHeight - cursorHeight, mCellWidth, cursorHeight); + } + } + } + } } + + g.SetFont(DarkTheme.sDarkTheme.mSmallFont); + g.DrawString(scope $"Ln {cursorPos.row + 1}", mWidth - GS!(120), mHeight - GS!(21)); + g.DrawString(scope $"Col {cursorPos.col + 1}", mWidth - GS!(60), mHeight - GS!(21)); } public override void DrawAll(Graphics g) { - if (!mHasConsole) + if (!mConsoleProvider.Attached) { - using (g.PushColor(0x80FFFFFF)) + //using (g.PushColor(0x80FFFFFF)) + using (g.PushColor(0xFFA0A0A0)) { base.DrawAll(g); } } - else if (mPaused) + else if (mPauseCB.Checked) { - using (g.PushColor(0xA0FFFFFF)) + using (g.PushColor(0xFFD0D0D0)) { base.DrawAll(g); } @@ -816,13 +697,6 @@ class ConsolePanel : Panel public void ResizeComponents() { - var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); - CONSOLE_SCREEN_BUFFER_INFOEX info = default; - info.mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); -#if BF_PLATFORM_WINDOWS - GetConsoleScreenBufferInfoEx(outHandle, ref info); -#endif - mCellWidth = (.)gApp.mTermFont.GetWidth('W'); mCellHeight = (.)gApp.mTermFont.GetLineSpacing(); @@ -831,33 +705,14 @@ class ConsolePanel : Panel int32 cols = (.)((mWidth - GS!(2)) / mCellWidth); int32 rows = (.)((mScrollableWidget.mHeight - GS!(8)) / mCellHeight); - mView.Resize(0, 0, mWidth, Math.Max(info.mHeight * mCellHeight, mHeight)); + mView.Resize(0, 0, mWidth, Math.Max(mConsoleProvider.BufferHeight * mCellHeight, mHeight)); mScrollableWidget.RehupSize(); - info.mWindowRect.right = (.)(info.mWindowRect.left + cols); - info.mWindowRect.bottom = (.)(info.mWindowRect.top + rows); - //SetConsoleScreenBufferInfoEx(outHandle, ref info); + mConsoleProvider.Resize(cols, rows, Paused || !mConsoleProvider.Attached); - //SetConsoleScreenBufferSize(outHandle, .((.)cols, (.)rows)); - - //SetConsoleWindowInfo(outHandle, true, info.mWindowRect); - -#if BF_PLATFORM_WINDOWS - GetCurrentConsoleFont(outHandle, false, var fontInfo); - - var window = GetConsoleWindow(); - - uint32 style = (.)Windows.GetWindowLong(window, Windows.GWL_STYLE); - uint32 styleEx = (.)Windows.GetWindowLong(window, Windows.GWL_EXSTYLE); - - Windows.Rect rect = .(0, 0, (.)(cols * fontInfo.mSize.x), (.)(rows * fontInfo.mSize.y)); - Windows.AdjustWindowRectEx(ref rect, style, false, styleEx); - - Windows.SetWindowPos(window, default, 0, 0, rect.Width, rect.Height, - 0x10 /* SWP_NOACTIVATE */ - //0x90 /* SWP_HIDEWINDOW | SWP_NOACTIVATE */ - ); -#endif + //mMouseComboBox.Resize(GS!(8), mHeight - GS!(22), GS!(100), GS!(24)); + mMousePassthroughCB.Resize(GS!(8), mHeight - GS!(20), GS!(140), GS!(24)); + mPauseCB.Resize(GS!(170), mHeight - GS!(20), GS!(140), GS!(24)); } public override void Resize(float x, float y, float width, float height) @@ -872,32 +727,12 @@ class ConsolePanel : Panel SetFocus(); } - static uint8[256*5] sKeyCharMap = .(0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, - 0, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 61, 44, 45, 46, 47, - 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 92, 93, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 33, 64, 35, 36, 37, 94, 38, 42, 40, 0, 0, 0, 0, 0, 0, - 0, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 43, 60, 95, 62, 63, - 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 124, 125, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, - 0, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 61, 44, 45, 46, 47, - 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 92, 93, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 33, 64, 35, 36, 37, 94, 38, 42, 40, 0, 0, 0, 0, 0, 0, - 0, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 43, 60, 95, 62, 63, - 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 124, 125, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 28, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - public void SysKeyDown(KeyDownEvent keyEvent) { - if (mPaused) + if (Paused) { - mPaused = false; + mSelection = null; + Paused = false; return; } @@ -905,117 +740,21 @@ class ConsolePanel : Panel { mCursorBlinkTicks = 0; + mConsoleProvider.KeyDown(keyEvent.mKeyCode, keyEvent.mKeyFlags); + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); if (keyEvent.mKeyCode != .Shift) { } - - INPUT_RECORD input = default; - - if (keyEvent.mKeyCode == .F1) - { - Debug.WriteLine("Key Events:"); - while (true) - { - ReadConsoleInputW(inHandle, &input, 1, var numEventsRead); - - if (input.mEventType == 1) - { - if (input.mEventData.mKeyEvent.mChar != 0) - { - int keyMod = default; - if ((input.mEventData.mKeyEvent.mControlKeyState & 8) != 0) // Ctrl - { - keyMod |= 4; - } - else - { - if ((input.mEventData.mKeyEvent.mControlKeyState & 0x10) != 0) // Shift - keyMod |= 1; - if ((input.mEventData.mKeyEvent.mControlKeyState & 0x80) != 0) // Caps Lock - keyMod |= 2; - } - - /*if ((input.mEventData.mKeyEvent.mControlKeyState & 2) != 0) // Alt - flags |= .Alt;*/ - - Debug.WriteLine($"{input.mEventData.mKeyEvent.mVirtualKeyCode} {keyMod} : {(int)input.mEventData.mKeyEvent.mChar} {input.mEventData.mKeyEvent.mChar}"); - - uint16 keyState = ((uint16)keyMod << 8) + (uint16)input.mEventData.mKeyEvent.mVirtualKeyCode; - sKeyCharMap[keyState] = (uint8)input.mEventData.mKeyEvent.mChar; - } - - if (input.mEventData.mKeyEvent.mChar == '?') - { - for (int i < sKeyCharMap.Count) - { - if (i % 64 == 0) - Debug.WriteLine(); - Debug.Write($"{sKeyCharMap[i]}, "); - } - Debug.WriteLine(); - } - } - else if (input.mEventType == 2) - { - - } - } - return; - } - - input.mEventType = 1 /*KEY_EVENT */; - input.mEventData.mKeyEvent.mKeyDown = 1; - input.mEventData.mKeyEvent.mRepeatCount = 1; - input.mEventData.mKeyEvent.mVirtualKeyCode = (.)keyEvent.mKeyCode; - //input.mEventData.mKeyEvent.mVirtualScanCode = 61; - - int keyMod = 0; - if (keyEvent.mKeyFlags.HasFlag(.Ctrl)) - { - keyMod |= 4; - } - else - { - if (keyEvent.mKeyFlags.HasFlag(.Shift)) - keyMod |= 1; - if (keyEvent.mKeyFlags.HasFlag(.CapsLock)) - keyMod |= 2; - } - - input.mEventData.mKeyEvent.mControlKeyState = GetControlKeyState(keyEvent.mKeyFlags); - input.mEventData.mKeyEvent.mChar = (.)sKeyCharMap[(keyMod << 8) | (int)keyEvent.mKeyCode]; - - var result = WriteConsoleInputW(inHandle, &input, 1, var numEventsWritten); - int32 err = Windows.GetLastError(); } } - - public uint32 GetControlKeyState(KeyFlags keyFlags) - { - uint16 controlKeyState = 0; - if (keyFlags.HasFlag(.Alt)) - controlKeyState |= 1; - if (keyFlags.HasFlag(.Ctrl)) - controlKeyState |= 4; - if (keyFlags.HasFlag(.Shift)) - controlKeyState |= 0x10; - if (keyFlags.HasFlag(.CapsLock)) - controlKeyState |= 0x80; - return controlKeyState; - } - + public void SysKeyUp(KeyCode keyCode) { if (mView.mHasFocus) { - var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); - - INPUT_RECORD input = default; - input.mEventType = 1 /*KEY_EVENT */; - input.mEventData.mKeyEvent.mVirtualKeyCode = (.)keyCode; - WriteConsoleInputW(inHandle, &input, 1, var numEventsWritten); + mConsoleProvider.KeyUp(keyCode); } } @@ -1028,4 +767,24 @@ class ConsolePanel : Panel { //base.MouseWheel(x, y, deltaX, deltaY); } + + public void Attach() + { + if (mConsoleProvider.Attached) + return; + mConsoleProvider.Attach(); + ResizeComponents(); + } + + public void Detach() + { + if (!mConsoleProvider.Attached) + return; + mConsoleProvider.Detach(); + } + + public override void FocusForKeyboard() + { + mView.SetFocus(); + } } \ No newline at end of file diff --git a/IDE/src/ui/LaunchDialog.bf b/IDE/src/ui/LaunchDialog.bf index cfbbad3e..87dc5762 100644 --- a/IDE/src/ui/LaunchDialog.bf +++ b/IDE/src/ui/LaunchDialog.bf @@ -332,7 +332,7 @@ namespace IDE.ui var envBlock = scope List(); Environment.EncodeEnvironmentVariables(envVars, envBlock); - if (!gApp.mDebugger.OpenFile(targetPath, targetPath, arguments, workingDir, envBlock, false, false)) + if (!gApp.mDebugger.OpenFile(targetPath, targetPath, arguments, workingDir, envBlock, false, false, .None)) { gApp.Fail(scope String()..AppendF("Unable to open executable for debugging: {0}", targetPath)); return; diff --git a/IDE/src/ui/ProjectPanel.bf b/IDE/src/ui/ProjectPanel.bf index af7735ab..95e341ae 100644 --- a/IDE/src/ui/ProjectPanel.bf +++ b/IDE/src/ui/ProjectPanel.bf @@ -2991,7 +2991,7 @@ namespace IDE.ui } }); - item = folderItem.AddItem("Terminal"); + item = folderItem.AddItem("External Terminal"); item.mOnMenuItemSelected.Add(new (menu) => { let projectItem = GetSelectedProjectItem(); @@ -3023,6 +3023,34 @@ namespace IDE.ui process.Start(psi).IgnoreError(); } }); + + item = folderItem.AddItem("Embedded Terminal"); + item.mOnMenuItemSelected.Add(new (menu) => + { + let projectItem = GetSelectedProjectItem(); + String path = scope String(); + if (projectItem == null) + { + path.Set(gApp.mWorkspace.mDir); + } + else if (let projectFolder = projectItem as ProjectFolder) + { + if (projectFolder.mParentFolder == null) + { + path.Set(projectFolder.mProject.mProjectDir); + } + else + projectFolder.GetFullImportPath(path); + } + else + projectItem.mParentFolder.GetFullImportPath(path); + + if (!path.IsWhiteSpace) + { + gApp.ShowTerminal(); + gApp.mTerminalPanel.OpenDirectory(path); + } + }); } if (projectItem == null) diff --git a/IDE/src/ui/SettingsDialog.bf b/IDE/src/ui/SettingsDialog.bf index cb008659..9eae565f 100644 --- a/IDE/src/ui/SettingsDialog.bf +++ b/IDE/src/ui/SettingsDialog.bf @@ -62,7 +62,7 @@ namespace IDE.ui AddCategoryItem(root, "Compiler"); AddCategoryItem(root, "Debugger"); AddCategoryItem(root, "Visual Studio"); - AddCategoryItem(root, "Terminal"); + AddCategoryItem(root, "Console"); AddCategoryItem(root, "Wasm"); if (!gApp.mSettings.mVSSettings.IsConfigured()) @@ -170,7 +170,7 @@ namespace IDE.ui category.Open(true, true); } - void PopulateTerminalOptions() + void PopulateConsoleOptions() { mCurPropertiesTarget = gApp.mSettings; @@ -179,6 +179,8 @@ namespace IDE.ui category.mIsBold = true; category.mTextColor = Color.Mult(DarkTheme.COLOR_TEXT, cHeaderColor); AddPropertiesItem(category, "Windows Terminal", "mWindowsTerminal"); + AddPropertiesItem(category, "Debug Console", "mDebugConsoleKind"); + AddPropertiesItem(category, "Always Enable Console", "mAlwaysEnableConsole"); category.Open(true, true); } @@ -438,7 +440,7 @@ namespace IDE.ui case .VisualStudio: PopulateVSOptions(); case .Terminal: - PopulateTerminalOptions(); + PopulateConsoleOptions(); case .Wasm: PopulateWasmOptions(); default: diff --git a/IDE/src/ui/TerminalPanel.bf b/IDE/src/ui/TerminalPanel.bf index d7a10990..9df25db1 100644 --- a/IDE/src/ui/TerminalPanel.bf +++ b/IDE/src/ui/TerminalPanel.bf @@ -10,15 +10,51 @@ using Beefy.widgets; using Beefy.events; using System.Diagnostics; using Beefy.utils; +using IDE.util; namespace IDE.ui; -class TerminalPanel : Panel +class TerminalPanel : ConsolePanel { public override void Serialize(StructuredData data) { - base.Serialize(data); - data.Add("Type", "TerminalPanel"); } + + public override void Init() + { + var consoleProvider = new BeefConConsoleProvider(); + consoleProvider.mBeefConExePath = new $"{gApp.mInstallDir}/BeefCon.exe"; + consoleProvider.mTerminalExe = new .(gApp.mSettings.mWindowsTerminal); + + mConsoleProvider = consoleProvider; + } + + public override void AddedToParent() + { + var consoleProvider = (BeefConConsoleProvider)mConsoleProvider; + consoleProvider.mTerminalExe.Set(gApp.mSettings.mWindowsTerminal); + consoleProvider.mWorkingDir.Set(gApp.mWorkspace.mDir); + mConsoleProvider.Attach(); + } + + public override void RemovedFromParent(Widget previousParent, WidgetWindow window) + { + + } + + public override void Update() + { + base.Update(); + + + } + + public void OpenDirectory(StringView path) + { + var consoleProvider = (BeefConConsoleProvider)mConsoleProvider; + consoleProvider.mWorkingDir.Set(path); + consoleProvider.Detach(); + consoleProvider.Attach(); + } } \ No newline at end of file diff --git a/IDE/src/util/ConsoleProvider.bf b/IDE/src/util/ConsoleProvider.bf index 5fa97a3f..8cc93911 100644 --- a/IDE/src/util/ConsoleProvider.bf +++ b/IDE/src/util/ConsoleProvider.bf @@ -1,20 +1,1233 @@ +#pragma warning disable 168 +using Beefy.geom; +using System; +using Beefy.widgets; +using System.Security.Cryptography; +using System.Diagnostics; +using System.IO; +using System.Threading; namespace IDE.util; class ConsoleProvider { - public struct Cell + public enum UpdateState { - public char32 mChar; - public uint32 mFgColor; - public uint32 mBgColor; + None, + Dirty } - public virtual void Get(int col, int row) + public struct Cell + { + public char16 mChar; + public uint16 mAttributes; + } + + public virtual int Width => 0; + public virtual int Height => 0; + public virtual int BufferHeight => 0; + public virtual bool Attached => false; + public virtual bool Connected => Attached; + public virtual int ScrollTop => 0; + public virtual bool CursorVisible => true; + public virtual float CursorHeight => 1.0f; + + public virtual (int32 col, int32 row) CursorPos + { + get + { + return default; + } + + set + { + + } + } + + public virtual Cell GetCell(int col, int row) + { + return default; + } + + public Cell GetCell(int idx) + { + int width = Math.Max(Width, 1); + int scrollTop = ScrollTop; + return GetCell(idx % width, idx / width - scrollTop); + } + + public virtual void Resize(int cols, int rows, bool resizeContent) { } + + + public virtual void ScrollTo(int row) + { + + } + + public virtual void Attach() + { + + } + + public virtual void Detach() + { + + } + + public virtual void MouseDown(int col, int row, int btnState, int btnCount, KeyFlags keyFlags) + { + + } + + public virtual void MouseMove(int col, int row, int btnState, KeyFlags keyFlags) + { + + } + + public virtual void MouseUp(int col, int row, int btnState, KeyFlags keyFlags) + { + + } + + public virtual void MouseWheel(int col, int row, int dy) + { + + } + + public virtual void KeyDown(KeyCode keyCode, KeyFlags keyFlags) + { + + } + + public virtual void KeyUp(KeyCode keyCode) + { + + } + + public virtual void SendInput(StringView str) + { + + } + + public virtual UpdateState Update() => .None; + + public virtual uint32 GetColor(int i) => 0xFF000000; +} + +class WinNativeConsoleProvider : ConsoleProvider +{ + [CRepr] + struct CONSOLE_SCREEN_BUFFER_INFOEX + { + public uint32 mSize; + public int16 mWidth; + public int16 mHeight; + public uint16 mCursorX; + public uint16 mCursorY; + public uint16 wAttributes; + public RECT mWindowRect; + public POINT mMaximumWindowSize; + public uint16 mPopupAttributes; + public Windows.IntBool mFullscreenSupported; + public uint32[16] mColorTable; + + public this() + { + this = default; + mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + } + } + + [CRepr] + struct POINT : this(int16 x, int16 y) + { + } + + [CRepr] + struct RECT : this(int16 left, int16 top, int16 right, int16 bottom) + { + public int16 Width => right - left; + public int16 Height => bottom - top; + } + + [CRepr] + struct CHAR_INFO + { + public char16 mChar; + public uint16 mAttributes; + } + + [CRepr] + struct CONSOLE_FONT_INFO + { + public uint32 mNumFont; + public POINT mSize; + } + + [CRepr] + struct CONSOLE_CURSOR_INFO + { + public uint32 mSize; + public uint32 mVisible; + } + + [CRepr] + struct CONSOLE_SELECTION_INFO + { + public uint32 mFlags; + public POINT mSelectionAnchor; + public RECT mSelection; + } + + [CRepr] + struct KEY_EVENT_RECORD + { + public int32 mKeyDown; + public uint16 mRepeatCount; + public uint16 mVirtualKeyCode; + public uint16 mVirtualScanCode; + public char16 mChar; + public uint32 mControlKeyState; + } + + [CRepr] + struct MOUSE_EVENT_RECORD + { + public POINT mMousePosition; + public uint32 mButtonState; + public uint32 mControlKeyState; + public uint32 mEventFlags; + } + + [CRepr] + struct INPUT_RECORD + { + public uint16 mEventType; + public INPUT_RECORD_DATA mEventData; + } + + [Union] + struct INPUT_RECORD_DATA + { + public KEY_EVENT_RECORD mKeyEvent; + public MOUSE_EVENT_RECORD mMouseEvent; + } + + class ScreenInfo + { + public CONSOLE_SCREEN_BUFFER_INFOEX mInfo; + public CONSOLE_CURSOR_INFO mCursorInfo; + public CONSOLE_SELECTION_INFO mSelectionInfo; + public int32 mScrollTop; + public CHAR_INFO* mCharInfo; + public CHAR_INFO* mFullCharInfo; + public uint32[16] mColorTable = .(0xFF000000, ); + + public int32 WindowWidth => mInfo.mWindowRect.Width; + public int32 WindowHeight => mInfo.mWindowRect.Height; + + public ~this() + { + delete mCharInfo; + delete mFullCharInfo; + } + + public int GetHashCode() + { + MD5 md5 = scope .(); + md5.Update(.((.)&mInfo, sizeof(CONSOLE_SCREEN_BUFFER_INFOEX))); + md5.Update(.((.)&mSelectionInfo, sizeof(CONSOLE_SELECTION_INFO))); + md5.Update(.((.)&mCursorInfo, sizeof(CONSOLE_CURSOR_INFO))); + if (mCharInfo != null) + md5.Update(.((.)mCharInfo, (int32)mInfo.mWindowRect.Width * mInfo.mWindowRect.Height * sizeof(CHAR_INFO))); + var hash = md5.Finish(); + return hash.GetHashCode(); + } + } + +#if BF_PLATFORM_WINDOWS + [CLink, CallingConvention(.Stdcall)] + public static extern void AllocConsole(); + + [CLink, CallingConvention(.Stdcall)] + public static extern void AttachConsole(int processId); + + [CLink, CallingConvention(.Stdcall)] + public static extern void FreeConsole(); + + [CLink, CallingConvention(.Stdcall)] + public static extern Windows.IntBool GetConsoleScreenBufferInfoEx(Windows.Handle handle, ref CONSOLE_SCREEN_BUFFER_INFOEX info); + + [CLink, CallingConvention(.Stdcall)] + public static extern Windows.IntBool SetConsoleScreenBufferInfoEx(Windows.Handle handle, ref CONSOLE_SCREEN_BUFFER_INFOEX info); + + [CLink] + public static extern Windows.IntBool ReadConsoleOutputW(Windows.Handle handle, void* buffer, POINT bufferSize, POINT bufferCoord, ref RECT readRegion); + + [CLink] + public static extern Windows.IntBool SetConsoleScreenBufferSize(Windows.Handle handle, POINT bufferSize); + + [CLink] + public static extern Windows.IntBool SetConsoleWindowInfo(Windows.Handle handle, Windows.IntBool absolute, in RECT window); + + [CLink] + public static extern Windows.HWnd GetConsoleWindow(); + + [CLink] + public static extern Windows.IntBool GetCurrentConsoleFont(Windows.Handle handle, Windows.IntBool maxWindow, out CONSOLE_FONT_INFO fontInfo); + + [CLink] + public static extern Windows.IntBool GetConsoleCursorInfo(Windows.Handle handle, out CONSOLE_CURSOR_INFO cursorInfo); + + [CLink] + public static extern Windows.IntBool GetConsoleSelectionInfo(out CONSOLE_SELECTION_INFO selectionInfo); + + [CLink] + public static extern Windows.IntBool WriteConsoleInputW(Windows.Handle handle, INPUT_RECORD* eventsPtr, int32 eventCount, out int32 numEventsWritten); + + [CLink] + public static extern Windows.IntBool ReadConsoleInputW(Windows.Handle handle, INPUT_RECORD* eventsPtr, int32 eventCount, out int32 numEventsRead); +#endif + + ScreenInfo mScreenInfo ~ delete _; + public bool mDirty; + bool mHasConsole; + SpawnedProcess mCmdSpawn ~ delete _; + SpawnedProcess mExecSpawn ~ delete _; + public int mLastDrawnHashCode; + public bool mHideNativeConsole = true; + + static uint8[256*5] sKeyCharMap = .(0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, + 0, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 61, 44, 45, 46, 47, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 92, 93, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 33, 64, 35, 36, 37, 94, 38, 42, 40, 0, 0, 0, 0, 0, 0, + 0, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 43, 60, 95, 62, 63, + 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 124, 125, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, + 0, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 61, 44, 45, 46, 47, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 92, 93, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 33, 64, 35, 36, 37, 94, 38, 42, 40, 0, 0, 0, 0, 0, 0, + 0, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 43, 60, 95, 62, 63, + 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 124, 125, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 28, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + + public override int Width => (mScreenInfo != null) ? (mScreenInfo.mInfo.mWindowRect.Width) : 0; + public override int Height => (mScreenInfo != null) ? (mScreenInfo.mInfo.mWindowRect.Height) : 0; + public override int BufferHeight + { + get + { + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFOEX info = default; + info.mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); +#if BF_PLATFORM_WINDOWS + GetConsoleScreenBufferInfoEx(outHandle, ref info); +#endif + return info.mHeight; + } + } + public override bool Attached => mHasConsole; + public override int ScrollTop => (mScreenInfo != null) ? mScreenInfo.mScrollTop : 0; + public override bool CursorVisible => (mScreenInfo != null) ? (mScreenInfo.mCursorInfo.mVisible != 0) : true; + public override float CursorHeight => (mScreenInfo != null) ? (mScreenInfo.mCursorInfo.mSize / 100.0f) : 0.15f; + + public ~this() + { + mCmdSpawn?.Kill(); + mExecSpawn?.Kill(); + } + + + public override (int32 col, int32 row) CursorPos + { + get + { + if (mScreenInfo != null) + return (mScreenInfo.mInfo.mCursorX, mScreenInfo.mInfo.mCursorY); + return default; + } + + set + { + + } + } + + public override Cell GetCell(int col, int row) + { + if ((row < 0) || (row >= mScreenInfo.mInfo.mWindowRect.Height)) + { + GetFullScreenInfo(mScreenInfo); + } + + if (mScreenInfo.mFullCharInfo != null) + { + int bufRow = row + mScreenInfo.mScrollTop; + if (col >= mScreenInfo.mInfo.mWidth) + return default; + if (bufRow >= mScreenInfo.mInfo.mHeight) + return default; + + var info = mScreenInfo.mFullCharInfo[bufRow * mScreenInfo.mInfo.mWidth + col]; + return .() { mChar = info.mChar, mAttributes = info.mAttributes }; + } + + var info = mScreenInfo.mCharInfo[row * mScreenInfo.mInfo.mWindowRect.Width + col]; + return .() { mChar = info.mChar, mAttributes = info.mAttributes }; + } + + public bool GetScreenInfo(ScreenInfo screenInfo) + { + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + + CONSOLE_SCREEN_BUFFER_INFOEX info = default; + info.mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + +#if BF_PLATFORM_WINDOWS + if (!GetConsoleScreenBufferInfoEx(outHandle, ref info)) + return false; +#endif + info.mWindowRect.right++; + info.mWindowRect.bottom++; + screenInfo.mInfo = info; + + screenInfo.mScrollTop = info.mWindowRect.top; + + //mScrollableWidget.VertScrollTo(screenInfo.mInfo.mWindowRect.top * mCellHeight); + + //int width = info.mWindowRect.Width; + //int height = info.mWindowRect.Height; + + POINT bufferSize = .(info.mWindowRect.Width, info.mWindowRect.Height); + screenInfo.mCharInfo = new .[(int32)info.mWindowRect.Width * info.mWindowRect.Height]*; + RECT readRegion = .(screenInfo.mInfo.mWindowRect.left, (.)screenInfo.mScrollTop, screenInfo.mInfo.mWindowRect.right, (.)(screenInfo.mScrollTop + screenInfo.mInfo.mWindowRect.Height - 1)); +#if BF_PLATFORM_WINDOWS + ReadConsoleOutputW(outHandle, screenInfo.mCharInfo, bufferSize, POINT(0, 0), ref readRegion); + + GetConsoleCursorInfo(outHandle, out screenInfo.mCursorInfo); + GetConsoleSelectionInfo(out screenInfo.mSelectionInfo); +#endif + for (int i < 16) + { + screenInfo.mColorTable[i] = 0xFF000000 | + ((screenInfo.mInfo.mColorTable[i] >> 16) & 0x0000FF) | + ((screenInfo.mInfo.mColorTable[i] ) & 0x00FF00) | + ((screenInfo.mInfo.mColorTable[i] << 16) & 0xFF0000); + } + + return true; + } + + public bool GetFullScreenInfo(ScreenInfo screenInfo) + { + if (screenInfo.mFullCharInfo != null) + return true; + + if (screenInfo.mCharInfo == null) + { + if (!GetScreenInfo(screenInfo)) + return false; + } + + DeleteAndNullify!(screenInfo.mCharInfo); + + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + POINT bufferSize = .(screenInfo.mInfo.mWidth, screenInfo.mInfo.mHeight); + screenInfo.mFullCharInfo = new .[(int32)screenInfo.mInfo.mWidth * screenInfo.mInfo.mHeight]*; + RECT readRegion = .(0, 0, screenInfo.mInfo.mWidth, screenInfo.mInfo.mHeight); +#if BF_PLATFORM_WINDOWS + ReadConsoleOutputW(outHandle, screenInfo.mFullCharInfo, bufferSize, POINT(0, 0), ref readRegion); +#endif + + return true; + } + + public bool UpdateScreenInfo(ScreenInfo screenInfo) + { + if (screenInfo.mFullCharInfo == null) + { + if (!GetFullScreenInfo(screenInfo)) + return false; + } + + Internal.MemCpy(screenInfo.mCharInfo, + screenInfo.mFullCharInfo + screenInfo.mScrollTop * screenInfo.mInfo.mWidth, + screenInfo.mInfo.mWindowRect.Width * screenInfo.mInfo.mWindowRect.Height * sizeof(CHAR_INFO)); + return true; + } + + public override void ScrollTo(int row) + { + if (mScreenInfo == null) + return; + + GetFullScreenInfo(mScreenInfo); + + mScreenInfo.mScrollTop = (.)row; + int windowHeight = mScreenInfo.mInfo.mWindowRect.Height; + mScreenInfo.mInfo.mWindowRect.top = (.)mScreenInfo.mScrollTop; + mScreenInfo.mInfo.mWindowRect.bottom = (.)(mScreenInfo.mScrollTop + windowHeight); + } + + public override void Resize(int cols, int rows, bool resizeContent) + { + if (resizeContent) + { + if (mScreenInfo != null) + { + GetFullScreenInfo(mScreenInfo); + mScreenInfo.mInfo.mWindowRect.right = (.)(mScreenInfo.mInfo.mWindowRect.left + cols); + mScreenInfo.mInfo.mWindowRect.bottom = (.)(mScreenInfo.mInfo.mWindowRect.top + rows); + } + } + + if (!mHasConsole) + return; + + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFOEX info = default; + info.mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); +#if BF_PLATFORM_WINDOWS + GetConsoleScreenBufferInfoEx(outHandle, ref info); +#endif + + info.mWindowRect.right = (.)(info.mWindowRect.left + cols); + info.mWindowRect.bottom = (.)(info.mWindowRect.top + rows); + //SetConsoleScreenBufferInfoEx(outHandle, ref info); + + //SetConsoleScreenBufferSize(outHandle, .((.)cols, (.)rows)); + + //SetConsoleWindowInfo(outHandle, true, info.mWindowRect); + +#if BF_PLATFORM_WINDOWS + GetCurrentConsoleFont(outHandle, false, var fontInfo); + + var window = GetConsoleWindow(); + + uint32 style = (.)Windows.GetWindowLong(window, Windows.GWL_STYLE); + uint32 styleEx = (.)Windows.GetWindowLong(window, Windows.GWL_EXSTYLE); + + Windows.Rect rect = .(0, 0, (.)(cols * fontInfo.mSize.x), (.)(rows * fontInfo.mSize.y)); + Windows.AdjustWindowRectEx(ref rect, style, false, styleEx); + + Windows.SetWindowPos(window, default, 0, 0, rect.Width, rect.Height, + 0x10 /* SWP_NOACTIVATE */ + //0x90 /* SWP_HIDEWINDOW | SWP_NOACTIVATE */ + ); +#endif + } + + public override void Attach() + { + if (mHasConsole) + return; + + mHasConsole = true; + +#if BF_PLATFORM_WINDOWS + //AllocConsole(); + + /*ProcessStartInfo procInfo = scope ProcessStartInfo(); + procInfo.UseShellExecute = false; + procInfo.SetFileName(scope $"{gApp.mInstallDir}/BeefCon_d.exe"); + procInfo.SetArguments(scope $"{Process.CurrentId}"); + + String resultStr = scope String(); + mCmdSpawn = new SpawnedProcess(); + mCmdSpawn.Start(procInfo); + + Thread.Sleep(2000); + + var processId = mCmdSpawn.ProcessId; + if (processId > 0) + AttachConsole(processId); + else*/ + AllocConsole(); + + var window = GetConsoleWindow(); + + if (mHideNativeConsole) + Windows.SetWindowPos(window, default, 0, 0, 0, 0, 0x290 /* SWP_NOACTIVATE | SWP_NOREPOSITION | SWP_HIDEWINDOW */); + + //ResizeComponents(); +#endif + } + + public override void Detach() + { + if (!mHasConsole) + return; + + if (mScreenInfo != null) + GetFullScreenInfo(mScreenInfo); + + mHasConsole = false; + +#if BF_PLATFORM_WINDOWS + FreeConsole(); +#endif + + mCmdSpawn?.Kill(); + DeleteAndNullify!(mCmdSpawn); + mExecSpawn?.Kill(); + DeleteAndNullify!(mExecSpawn); + } + + public uint32 GetControlKeyState(KeyFlags keyFlags) + { + uint16 controlKeyState = 0; + if (keyFlags.HasFlag(.Alt)) + controlKeyState |= 1; + if (keyFlags.HasFlag(.Ctrl)) + controlKeyState |= 4; + if (keyFlags.HasFlag(.Shift)) + controlKeyState |= 0x10; + if (keyFlags.HasFlag(.CapsLock)) + controlKeyState |= 0x80; + return controlKeyState; + } + + public override void MouseDown(int col, int row, int btnState, int btnCount, KeyFlags keyFlags) + { + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + INPUT_RECORD input = default; + input.mEventType = 2 /*MOUSE_EVENT */; + input.mEventData.mMouseEvent.mButtonState = (.)btnState; + if (btnCount > 1) + input.mEventData.mMouseEvent.mEventFlags |= 2; + input.mEventData.mMouseEvent.mMousePosition = .((.)col, (.)row); + input.mEventData.mMouseEvent.mControlKeyState = GetControlKeyState(keyFlags); + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + } + + public override void MouseMove(int col, int row, int btnState, KeyFlags keyFlags) + { + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + INPUT_RECORD input = default; + input.mEventType = 2 /*MOUSE_EVENT */; + input.mEventData.mMouseEvent.mEventFlags |= 1; /* MOUSE_MOVED */ + input.mEventData.mMouseEvent.mButtonState = (.)btnState; + input.mEventData.mMouseEvent.mMousePosition = .((.)col, (.)row); + input.mEventData.mMouseEvent.mControlKeyState = GetControlKeyState(keyFlags); + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + } + + public override void MouseUp(int col, int row, int btnState, KeyFlags keyFlags) + { + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + INPUT_RECORD input = default; + input.mEventType = 2 /*MOUSE_EVENT */; + input.mEventData.mMouseEvent.mButtonState = (.)btnState; + input.mEventData.mMouseEvent.mMousePosition = .((.)col, (.)row); + input.mEventData.mMouseEvent.mControlKeyState = GetControlKeyState(keyFlags); + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + } + + public override void MouseWheel(int col, int row, int dy) + { + var window = GetConsoleWindow(); + Windows.SendMessageW(window, 0x0007, 0, 0); // WM_SETFOCUS + //Windows.SendMessageW(window, 0x0006, 0, 0); // WM_ACTIVATE + + float x = col; + float y = row; + + Windows.SendMessageW(window, 0x0200 /*WM_MOUSEMOVE*/, 0, (int)x | ((int)y << 16)); + Windows.SendMessageW(window, 0x020A /*WM_MOUSEWHEEL*/, (int32)(120 * dy) << 16, (int)x | ((int)y << 16)); + } + + public override void KeyDown(KeyCode keyCode, KeyFlags keyFlags) + { + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + INPUT_RECORD input = default; + + /*if (keyEvent.mKeyCode == .F1) + { + Debug.WriteLine("Key Events:"); + while (true) + { + ReadConsoleInputW(inHandle, &input, 1, var numEventsRead); + + if (input.mEventType == 1) + { + if (input.mEventData.mKeyEvent.mChar != 0) + { + int keyMod = default; + if ((input.mEventData.mKeyEvent.mControlKeyState & 8) != 0) // Ctrl + { + keyMod |= 4; + } + else + { + if ((input.mEventData.mKeyEvent.mControlKeyState & 0x10) != 0) // Shift + keyMod |= 1; + if ((input.mEventData.mKeyEvent.mControlKeyState & 0x80) != 0) // Caps Lock + keyMod |= 2; + } + + /*if ((input.mEventData.mKeyEvent.mControlKeyState & 2) != 0) // Alt + flags |= .Alt;*/ + + Debug.WriteLine($"{input.mEventData.mKeyEvent.mVirtualKeyCode} {keyMod} : {(int)input.mEventData.mKeyEvent.mChar} {input.mEventData.mKeyEvent.mChar}"); + + uint16 keyState = ((uint16)keyMod << 8) + (uint16)input.mEventData.mKeyEvent.mVirtualKeyCode; + sKeyCharMap[keyState] = (uint8)input.mEventData.mKeyEvent.mChar; + } + + if (input.mEventData.mKeyEvent.mChar == '?') + { + for (int i < sKeyCharMap.Count) + { + if (i % 64 == 0) + Debug.WriteLine(); + Debug.Write($"{sKeyCharMap[i]}, "); + } + Debug.WriteLine(); + } + } + else if (input.mEventType == 2) + { + + } + } + return; + }*/ + + input.mEventType = 1 /*KEY_EVENT */; + input.mEventData.mKeyEvent.mKeyDown = 1; + input.mEventData.mKeyEvent.mRepeatCount = 1; + input.mEventData.mKeyEvent.mVirtualKeyCode = (.)keyCode; + //input.mEventData.mKeyEvent.mVirtualScanCode = 61; + + int keyMod = 0; + if (keyFlags.HasFlag(.Ctrl)) + { + keyMod |= 4; + } + else + { + if (keyFlags.HasFlag(.Shift)) + keyMod |= 1; + if (keyFlags.HasFlag(.CapsLock)) + keyMod |= 2; + } + + input.mEventData.mKeyEvent.mControlKeyState = GetControlKeyState(keyFlags); + input.mEventData.mKeyEvent.mChar = (.)sKeyCharMap[(keyMod << 8) | (int)keyCode]; + + WriteConsoleInputW(inHandle, &input, 1, var numEventsWritten); + } + + public override void KeyUp(KeyCode keyCode) + { + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + INPUT_RECORD input = default; + input.mEventType = 1 /*KEY_EVENT */; + input.mEventData.mKeyEvent.mVirtualKeyCode = (.)keyCode; + WriteConsoleInputW(inHandle, &input, 1, var numEventsWritten); + } + + public override void SendInput(StringView str) + { + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + for (var c in str.DecodedChars) + { + INPUT_RECORD input = default; + input.mEventType = 1 /*KEY_EVENT */; + input.mEventData.mKeyEvent.mKeyDown = 1; + input.mEventData.mKeyEvent.mRepeatCount = 1; + input.mEventData.mKeyEvent.mChar = (.)c; + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + } + } + + public override UpdateState Update() + { + ScreenInfo newScreenInfo = new .(); + if (GetScreenInfo(newScreenInfo)) + { + delete mScreenInfo; + mScreenInfo = newScreenInfo; + } + else + { + Detach(); + delete newScreenInfo; + } + + int hashCode = (mScreenInfo?.GetHashCode()).GetValueOrDefault(); + + if (hashCode != mLastDrawnHashCode) + { + mLastDrawnHashCode = hashCode; + mDirty = true; + } + + if (mDirty) + { + mDirty = false; + return .Dirty; + } + return .None; + } + + public override uint32 GetColor(int i) + { + return (mScreenInfo != null) ? mScreenInfo.mColorTable[i] : 0xFF000000; + } } -class WinNativeConsoleProvider +class BeefConConsoleProvider : ConsoleProvider { + public enum Message + { + None, + GetData, + Resize, + Data, + InputString, + KeyDown, + KeyUp, + MouseDown, + MouseMove, + MouseUp, + MouseWheel + } + + public class Pipe + { + public NamedPipe mSendPipe = new .() ~ delete _; + public NamedPipe mRecvPipe = new .() ~ delete _; + public MemoryStream mRecvStream = new .() ~ delete _; + public MemoryStream mSendStream = new .() ~ delete _; + public MemoryStream Stream => mSendStream; + public bool mFailed; + public bool mConnected; + public Thread mThread ~ delete _; + public Monitor mDataMonitor = new .() ~ delete _; + public int mPendingReadClear; + public bool mExiting; + + public this() + { + + } + + public ~this() + { + mExiting = true; + using (mDataMonitor.Enter()) + { + mSendPipe.Close(); + mRecvPipe.Close(); + } + if (mThread != null) + { + mThread.Join(); + } + } + + void ThreadProc() + { + while (!mExiting) + { + uint8[4096] data = ?; + + using (mDataMonitor.Enter()) + { + if (mExiting) + return; + } + + switch (mRecvPipe.TryRead(.(&data, 4096), -1)) + { + case .Ok(int len): + if (len == 0) + { + mFailed = true; + return; + } + mConnected = true; + using (mDataMonitor.Enter()) + { + mRecvStream.TryWrite(.(&data, len)); + } + case .Err(let err): + if ((err == .PipeListening) && (!mConnected)) + { + Thread.Sleep(20); + break; + } + mFailed = true; + return; + } + } + } + + void StartThread() + { + mThread = new Thread(new => ThreadProc); + mThread.Start(false); + } + + public Result Connect(int processId, int conId) + { + if (mSendPipe.Open(".", scope $"BEEFCON_{processId}_{conId}_A", .None) case .Err) + return .Err; + if (mRecvPipe.Open(".", scope $"BEEFCON_{processId}_{conId}_B", .None) case .Err) + return .Err; + StartThread(); + mConnected = true; + return .Ok; + } + + public Result Listen(int processId, int conId) + { + if (mRecvPipe.Create(".", scope $"BEEFCON_{processId}_{conId}_A", .None) case .Err) + return .Err; + if (mSendPipe.Create(".", scope $"BEEFCON_{processId}_{conId}_B", .None) case .Err) + return .Err; + StartThread(); + return .Ok; + } + + public void StartMessage(Message message) + { + Debug.Assert(Stream.Length == 0); + mSendStream.Write(message); + } + + public void EndMessage() + { + defer mSendStream.Clear(); + + if (!mConnected) + return; + + if (mSendPipe.Write((int32)mSendStream.Length) case .Err) + { + mFailed = true; + return; + } + + Span span = .(mSendStream.Memory.Ptr, mSendStream.Length); + while (span.Length > 0) + { + switch (mSendPipe.TryWrite(span)) + { + case .Ok(int len): + span.RemoveFromStart(len); + case .Err: + mFailed = true; + return; + } + } + } + + public Result> ReadMessage(int timeoutMS) + { + using (mDataMonitor.Enter()) + { + if (mPendingReadClear > 0) + { + mRecvStream.Memory.RemoveRange(0, mPendingReadClear); + mRecvStream.Position = mRecvStream.Position - mPendingReadClear; + mPendingReadClear = 0; + } + + if (mRecvStream.Length < 4) + return .Err; + + int wantTotalLen = *(int32*)mRecvStream.Memory.Ptr + 4; + if (mRecvStream.Length < wantTotalLen) + return .Err; + + mPendingReadClear = wantTotalLen; + return .Ok(.(mRecvStream.Memory.Ptr + 4, wantTotalLen - 4)); + } + } + } + + public static int sConId = 0; + + public SpawnedProcess mBeefConProcess ~ delete _; + public int mConId = ++sConId; + public String mWorkingDir = new .() ~ delete _; + public String mBeefConExePath ~ delete _; + public String mTerminalExe ~ delete _; + public Pipe mPipe = new .() ~ delete _; + public int mProcessId = Process.CurrentId; + public bool mAttached; + + public int32 mResizedWidth; + public int32 mResizedHeight; + public int32 mWidth; + public int32 mHeight; + public int32 mBufferHeight; + public int32 mScrollTop; + public bool mCursorVisible; + public float mCursorHeight; + public Cell* mCells ~ delete _; + public (int32 col, int32 row) mCursorPos; + public uint32[16] mColors; + public int mLastDrawnHashCode; + public bool mDirty; + public int32 mFailDelay; + + public override bool Connected => mPipe.mConnected && !mPipe.mFailed; + public override int Width => mWidth; + public override int Height => mHeight; + public override int BufferHeight => mBufferHeight; + public override bool Attached => mAttached; + public override int ScrollTop => mScrollTop; + public override bool CursorVisible => mCursorVisible; + public override float CursorHeight => mCursorHeight; + public override (int32 col, int32 row) CursorPos + { + get + { + return mCursorPos; + } + + set + { + mCursorPos = value; + } + } + + public override Cell GetCell(int col, int row) + { + return mCells[row * mWidth + col]; + } + + public override uint32 GetColor(int i) + { + return mColors[i]; + } + + public override void Attach() + { + if (mAttached) + return; + + if (mBeefConProcess != null) + { + mBeefConProcess.Kill(); + DeleteAndNullify!(mBeefConProcess); + } + + if (mBeefConExePath != null) + { + ProcessStartInfo procInfo = scope ProcessStartInfo(); + procInfo.UseShellExecute = false; + procInfo.SetFileName(mBeefConExePath); + procInfo.SetWorkingDirectory(mWorkingDir); + procInfo.SetArguments(scope $"{Process.CurrentId} {sConId} {mTerminalExe}"); + + String resultStr = scope String(); + mBeefConProcess = new SpawnedProcess(); + mBeefConProcess.Start(procInfo).IgnoreError(); + } + + mDirty = true; + mAttached = true; + } + + public override void Detach() + { + if (!mAttached) + return; + + mWidth = 0; + mHeight = 0; + mAttached = false; + + DeleteAndNullify!(mPipe); + mPipe = new .(); + + if (mBeefConProcess != null) + { + delete mBeefConProcess; + mBeefConProcess = null; + } + } + + static mixin GET(var ptr) + { + *((T*)(ptr += sizeof(T)) - 1) + } + + public override UpdateState Update() + { + if (!mAttached) + return .None; + + if (mPipe.mFailed) + { + if (mFailDelay == 0) + { + mFailDelay = 120; + return .None; + } + else + { + if (--mFailDelay == 0) + { + Detach(); + Attach(); + } + return .None; + } + } + + if (!mPipe.mConnected) + { + if (mPipe.Connect((mBeefConProcess != null) ? mProcessId : 123, mConId) case .Err) + return .None; + Resize(mResizedWidth, mResizedHeight, false); + } + + mPipe.StartMessage(.GetData); + mPipe.EndMessage(); + + MessageLoop: while (true) + { + switch (mPipe.ReadMessage(0)) + { + case .Ok(let msg): + uint8* ptr = msg.Ptr + 1; + switch (*(Message*)msg.Ptr) + { + case .Data: + mWidth = GET!(ptr); + mHeight = GET!(ptr); + mBufferHeight = GET!(ptr); + mScrollTop = GET!(ptr); + mCursorVisible = GET!(ptr); + mCursorHeight = GET!(ptr); + mCursorPos = GET!<(int32 col, int32 row)>(ptr); + for (int i < 16) + { + uint32 color = GET!(ptr); + mColors[i] = 0xFF000000 | + ((color >> 16) & 0x0000FF) | + ((color ) & 0x00FF00) | + ((color << 16) & 0xFF0000); + mColors[i] = color; + } + delete mCells; + mCells = new Cell[mWidth * mHeight]*; + for (int row < mHeight) + { + for (int col < mWidth) + { + mCells[row * mWidth + col] = GET!(ptr); + } + } + + int hashCode = MD5.Hash(msg).GetHashCode(); + if (hashCode != mLastDrawnHashCode) + { + mLastDrawnHashCode = hashCode; + mDirty = true; + } + default: + } + case .Err: + break MessageLoop; + } + } + + if (mDirty) + { + mDirty = false; + return .Dirty; + } + return .None; + } + + public override void Resize(int cols, int rows, bool resizeContent) + { + mResizedWidth = (.)cols; + mResizedHeight = (.)rows; + + mPipe.StartMessage(.Resize); + mPipe.Stream.Write((int32)cols); + mPipe.Stream.Write((int32)rows); + mPipe.Stream.Write(resizeContent); + mPipe.EndMessage(); + } + + public override void KeyDown(KeyCode keyCode, KeyFlags keyFlags) + { + mPipe.StartMessage(.KeyDown); + mPipe.Stream.Write(keyCode); + mPipe.Stream.Write(keyFlags); + mPipe.EndMessage(); + } + + public override void KeyUp(KeyCode keyCode) + { + mPipe.StartMessage(.KeyUp); + mPipe.Stream.Write(keyCode); + mPipe.EndMessage(); + } + + public override void SendInput(StringView str) + { + mPipe.StartMessage(.InputString); + mPipe.Stream.WriteStrSized32(str); + mPipe.EndMessage(); + } + + public override void MouseDown(int col, int row, int btnState, int btnCount, KeyFlags keyFlags) + { + mPipe.StartMessage(.MouseDown); + mPipe.Stream.Write((int32)col); + mPipe.Stream.Write((int32)row); + mPipe.Stream.Write((int32)btnState); + mPipe.Stream.Write((int32)btnCount); + mPipe.Stream.Write(keyFlags); + mPipe.EndMessage(); + } + + public override void MouseMove(int col, int row, int btnState, KeyFlags keyFlags) + { + mPipe.StartMessage(.MouseMove); + mPipe.Stream.Write((int32)col); + mPipe.Stream.Write((int32)row); + mPipe.Stream.Write((int32)btnState); + mPipe.Stream.Write(keyFlags); + mPipe.EndMessage(); + } + + public override void MouseUp(int col, int row, int btnState, KeyFlags keyFlags) + { + mPipe.StartMessage(.MouseUp); + mPipe.Stream.Write((int32)col); + mPipe.Stream.Write((int32)row); + mPipe.Stream.Write((int32)btnState); + mPipe.Stream.Write(keyFlags); + mPipe.EndMessage(); + } + + public override void MouseWheel(int col, int row, int dy) + { + mPipe.StartMessage(.MouseWheel); + mPipe.Stream.Write((int32)col); + mPipe.Stream.Write((int32)row); + mPipe.Stream.Write((int32)dy); + mPipe.EndMessage(); + } } \ No newline at end of file diff --git a/IDEHelper/Compiler/CeDebugger.cpp b/IDEHelper/Compiler/CeDebugger.cpp index ad2fe1cd..ea918206 100644 --- a/IDEHelper/Compiler/CeDebugger.cpp +++ b/IDEHelper/Compiler/CeDebugger.cpp @@ -328,7 +328,7 @@ bool CeDebugger::CanOpen(const StringImpl& fileName, DebuggerResult* outResult) return false; } -void CeDebugger::OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled) +void CeDebugger::OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled, DbgOpenFileFlags openFileFlags) { } @@ -337,6 +337,10 @@ bool CeDebugger::Attach(int processId, BfDbgAttachFlags attachFlags) return false; } +void CeDebugger::GetStdHandles(BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr) +{ +} + void CeDebugger::Run() { } diff --git a/IDEHelper/Compiler/CeDebugger.h b/IDEHelper/Compiler/CeDebugger.h index f37743e1..61f218bb 100644 --- a/IDEHelper/Compiler/CeDebugger.h +++ b/IDEHelper/Compiler/CeDebugger.h @@ -314,8 +314,9 @@ public: virtual void OutputRawMessage(const StringImpl& msg) override; virtual int GetAddrSize() override; virtual bool CanOpen(const StringImpl& fileName, DebuggerResult* outResult) override; - virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled) override; + virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled, DbgOpenFileFlags openFileFlags) override; virtual bool Attach(int processId, BfDbgAttachFlags attachFlags) override; + virtual void GetStdHandles(BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr) override; virtual void Run() override; virtual void HotLoad(const Array& objectFiles, int hotIdx) override; virtual void InitiateHotResolve(DbgHotResolveFlags flags) override; diff --git a/IDEHelper/DebugManager.cpp b/IDEHelper/DebugManager.cpp index 834b9ce4..385417a3 100644 --- a/IDEHelper/DebugManager.cpp +++ b/IDEHelper/DebugManager.cpp @@ -754,7 +754,7 @@ BF_EXPORT int BF_CALLTYPE Debugger_GetAddrSize() return gDebugger->GetAddrSize(); } -BF_EXPORT bool BF_CALLTYPE Debugger_OpenFile(const char* launchPath, const char* targetPath, const char* args, const char* workingDir, void* envBlockPtr, int envBlockSize, bool hotSwapEnabled) +BF_EXPORT bool BF_CALLTYPE Debugger_OpenFile(const char* launchPath, const char* targetPath, const char* args, const char* workingDir, void* envBlockPtr, int envBlockSize, bool hotSwapEnabled, DbgOpenFileFlags openFileFlags) { BF_ASSERT(gDebugger == NULL); @@ -784,7 +784,7 @@ BF_EXPORT bool BF_CALLTYPE Debugger_OpenFile(const char* launchPath, const char* envBlock.Insert(0, (uint8*)envBlockPtr, envBlockSize); } - gDebugger->OpenFile(launchPath, targetPath, args, workingDir, envBlock, hotSwapEnabled); + gDebugger->OpenFile(launchPath, targetPath, args, workingDir, envBlock, hotSwapEnabled, openFileFlags); return true; } @@ -911,6 +911,12 @@ BF_EXPORT bool BF_CALLTYPE Debugger_Attach(int processId, BfDbgAttachFlags attac return false; } +BF_EXPORT void Debugger_GetStdHandles(BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr) +{ + if (gDebugger != NULL) + gDebugger->GetStdHandles(outStdIn, outStdOut, outStdErr); +} + BF_EXPORT void BF_CALLTYPE Debugger_Run() { gDebugger->Run(); diff --git a/IDEHelper/Debugger.h b/IDEHelper/Debugger.h index 34a41159..a58701d8 100644 --- a/IDEHelper/Debugger.h +++ b/IDEHelper/Debugger.h @@ -200,6 +200,14 @@ enum DbgMemoryFlags : uint8 DbgMemoryFlags_Execute = 4 }; +enum DbgOpenFileFlags : uint8 +{ + DbgOpenFileFlag_None = 0, + DbgOpenFileFlag_RedirectStdInput = 1, + DbgOpenFileFlag_RedirectStdOutput = 2, + DbgOpenFileFlag_RedirectStdError = 4 +}; + class DbgModuleMemoryCache { public: @@ -266,8 +274,9 @@ public: virtual void OutputRawMessage(const StringImpl& msg) = 0; virtual int GetAddrSize() = 0; virtual bool CanOpen(const StringImpl& fileName, DebuggerResult* outResult) = 0; - virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled) = 0; + virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled, DbgOpenFileFlags openFileFlags) = 0; virtual bool Attach(int processId, BfDbgAttachFlags attachFlags) = 0; + virtual void GetStdHandles(BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr) = 0; virtual void Run() = 0; virtual bool HasLoadedTargetBinary() { return true; } virtual void HotLoad(const Array& objectFiles, int hotIdx) = 0; diff --git a/IDEHelper/WinDebugger.cpp b/IDEHelper/WinDebugger.cpp index 4b79183a..87d1ec05 100644 --- a/IDEHelper/WinDebugger.cpp +++ b/IDEHelper/WinDebugger.cpp @@ -79,6 +79,11 @@ static void FilterThreadName(String& name) } } +static bool IsHandleValid(HANDLE handle) +{ + return (handle != NULL) && (handle != INVALID_HANDLE_VALUE); +} + ////////////////////////////////////////////////////////////////////////// WdBreakpointCondition::~WdBreakpointCondition() @@ -508,6 +513,9 @@ WinDebugger::WinDebugger(DebugManager* debugManager) : mDbgSymSrv(this) mOrigStepType = StepType_None; mLastValidStepIntoPC = 0; mActiveSymSrvRequest = NULL; + mStdInputPipe = INVALID_HANDLE_VALUE; + mStdOutputPipe = INVALID_HANDLE_VALUE; + mStdErrorPipe = INVALID_HANDLE_VALUE; mStoredReturnValueAddr = 0; #ifdef BF_DBG_32 @@ -528,6 +536,8 @@ WinDebugger::WinDebugger(DebugManager* debugManager) : mDbgSymSrv(this) mDbgProcessId = 0; mDbgHeapData = NULL; mIsPartialCallStack = true; + mHotSwapEnabled = false; + mOpenFileFlags = DbgOpenFileFlag_None; for (int i = 0; i < 4; i++) { @@ -919,7 +929,7 @@ void WinDebugger::DebugThreadProc() if (!IsMiniDumpDebugger()) { - if (!DoOpenFile(mLaunchPath, mArgs, mWorkingDir, mEnvBlock)) + if (!DoOpenFile(mLaunchPath, mArgs, mWorkingDir, mEnvBlock, mOpenFileFlags)) { if (mDbgProcessId != 0) OutputRawMessage("error Unable to attach to process"); @@ -1005,7 +1015,7 @@ bool WinDebugger::CanOpen(const StringImpl& fileName, DebuggerResult* outResult) return canRead; } -void WinDebugger::OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled) +void WinDebugger::OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled, DbgOpenFileFlags openFileFlags) { BF_ASSERT(!mIsRunning); mLaunchPath = launchPath; @@ -1014,6 +1024,7 @@ void WinDebugger::OpenFile(const StringImpl& launchPath, const StringImpl& targe mWorkingDir = workingDir; mEnvBlock = envBlock; mHotSwapEnabled = hotSwapEnabled; + mOpenFileFlags = openFileFlags; mDebugTarget = new DebugTarget(this); } @@ -1059,6 +1070,29 @@ bool WinDebugger::Attach(int processId, BfDbgAttachFlags attachFlags) return true; } +void WinDebugger::GetStdHandles(BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr) +{ + AutoCrit autoCrit(mDebugManager->mCritSect); + + if ((outStdIn != NULL) && (IsHandleValid(mStdInputPipe))) + { + *outStdIn = BfpFile_GetFromHandle((intptr)mStdInputPipe, NULL); + mStdInputPipe = 0; + } + + if ((outStdOut != NULL) && (IsHandleValid(mStdOutputPipe))) + { + *outStdOut = BfpFile_GetFromHandle((intptr)mStdOutputPipe, NULL); + mStdOutputPipe = 0; + } + + if ((outStdErr != NULL) && (IsHandleValid(mStdErrorPipe))) + { + *outStdErr = BfpFile_GetFromHandle((intptr)mStdErrorPipe, NULL); + mStdErrorPipe = 0; + } +} + void WinDebugger::Run() { mIsRunning = true; @@ -1262,7 +1296,50 @@ String WinDebugger::GetDbgAllocInfo() return result; } -bool WinDebugger::DoOpenFile(const StringImpl& fileName, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock) +static bool CreatePipeWithSecurityAttributes(HANDLE& hReadPipe, HANDLE& hWritePipe, SECURITY_ATTRIBUTES* lpPipeAttributes, int32 nSize) +{ + hReadPipe = 0; + hWritePipe = 0; + bool ret = ::CreatePipe(&hReadPipe, &hWritePipe, lpPipeAttributes, nSize); + if (!ret || (hReadPipe == INVALID_HANDLE_VALUE) || (hWritePipe == INVALID_HANDLE_VALUE)) + return false; + return true; +} + +static bool CreatePipe(HANDLE& parentHandle, HANDLE& childHandle, bool parentInputs) +{ + SECURITY_ATTRIBUTES securityAttributesParent = { 0 }; + securityAttributesParent.bInheritHandle = 1; + + HANDLE hTmp = INVALID_HANDLE_VALUE; + if (parentInputs) + CreatePipeWithSecurityAttributes(childHandle, hTmp, &securityAttributesParent, 0); + else + CreatePipeWithSecurityAttributes(hTmp, childHandle, &securityAttributesParent, 0); + + HANDLE dupHandle = 0; + + // Duplicate the parent handle to be non-inheritable so that the child process + // doesn't have access. This is done for correctness sake, exact reason is unclear. + // One potential theory is that child process can do something brain dead like + // closing the parent end of the pipe and there by getting into a blocking situation + // as parent will not be draining the pipe at the other end anymore. + if (!::DuplicateHandle(GetCurrentProcess(), hTmp, + GetCurrentProcess(), &dupHandle, + 0, false, DUPLICATE_SAME_ACCESS)) + { + return false; + } + + parentHandle = dupHandle; + + if (hTmp != INVALID_HANDLE_VALUE) + ::CloseHandle(hTmp); + + return true; +} + +bool WinDebugger::DoOpenFile(const StringImpl& fileName, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, DbgOpenFileFlags openFileFlags) { BP_ZONE("WinDebugger::DoOpenFile"); @@ -1274,6 +1351,33 @@ bool WinDebugger::DoOpenFile(const StringImpl& fileName, const StringImpl& args, si.cb = sizeof(si); ZeroMemory(&mProcessInfo, sizeof(mProcessInfo)); + DWORD flags = DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS | CREATE_DEFAULT_ERROR_MODE; + BOOL inheritHandles = false; + + // set up the streams + if ((openFileFlags & (DbgOpenFileFlag_RedirectStdInput | DbgOpenFileFlag_RedirectStdOutput | DbgOpenFileFlag_RedirectStdError)) != 0) + { + if ((openFileFlags & DbgOpenFileFlag_RedirectStdInput) != 0) + CreatePipe(mStdInputPipe, si.hStdInput, true); + else if (::GetConsoleWindow() != NULL) + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + else + si.hStdInput = INVALID_HANDLE_VALUE; + + if ((openFileFlags & DbgOpenFileFlag_RedirectStdOutput) != 0) + CreatePipe(mStdOutputPipe, si.hStdOutput, false); + else + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + + if ((openFileFlags & DbgOpenFileFlag_RedirectStdError) != 0) + CreatePipe(mStdErrorPipe, si.hStdError, false); + else + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + flags |= CREATE_NO_WINDOW; + si.dwFlags = STARTF_USESTDHANDLES; + inheritHandles = true; + } + if (mDbgProcessId != 0) { BOOL success = ::DebugActiveProcess(mDbgProcessId); @@ -1287,8 +1391,7 @@ bool WinDebugger::DoOpenFile(const StringImpl& fileName, const StringImpl& args, BP_ZONE("DoOpenFile_CreateProcessW"); UTF16String envW; - - DWORD flags = DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS | CREATE_DEFAULT_ERROR_MODE; + void* envPtr = NULL; if (!envBlock.IsEmpty()) { @@ -1318,11 +1421,34 @@ bool WinDebugger::DoOpenFile(const StringImpl& fileName, const StringImpl& args, cmdLine += args; } - BOOL worked = CreateProcessW(NULL, (WCHAR*)UTF8Decode(cmdLine).c_str(), NULL, NULL, FALSE, + BOOL worked = CreateProcessW(NULL, (WCHAR*)UTF8Decode(cmdLine).c_str(), NULL, NULL, inheritHandles, flags, envPtr, (WCHAR*)UTF8Decode(workingDir).c_str(), &si, &mProcessInfo); + if ((openFileFlags & DbgOpenFileFlag_RedirectStdInput) != 0) + ::CloseHandle(si.hStdInput); + if ((openFileFlags & DbgOpenFileFlag_RedirectStdOutput) != 0) + ::CloseHandle(si.hStdOutput); + if ((openFileFlags & DbgOpenFileFlag_RedirectStdError) != 0) + ::CloseHandle(si.hStdError); + if (!worked) { + if (IsHandleValid(mStdInputPipe)) + { + ::CloseHandle(mStdInputPipe); + mStdInputPipe = 0; + } + if (IsHandleValid(mStdOutputPipe)) + { + ::CloseHandle(mStdOutputPipe); + mStdOutputPipe = 0; + } + if (IsHandleValid(mStdErrorPipe)) + { + ::CloseHandle(mStdErrorPipe); + mStdErrorPipe = 0; + } + auto lastError = ::GetLastError(); if (lastError == ERROR_DIRECTORY) { @@ -1503,6 +1629,18 @@ void WinDebugger::Detach() mBreakpointAddrMap.Clear(); gDebugUpdateCnt = 0; + + if (IsHandleValid(mStdInputPipe)) + ::CloseHandle(mStdInputPipe); + mStdInputPipe = INVALID_HANDLE_VALUE; + + if (IsHandleValid(mStdOutputPipe)) + ::CloseHandle(mStdOutputPipe); + mStdOutputPipe = INVALID_HANDLE_VALUE; + + if (IsHandleValid(mStdErrorPipe)) + ::CloseHandle(mStdErrorPipe); + mStdErrorPipe = INVALID_HANDLE_VALUE; } Profiler* WinDebugger::StartProfiling() @@ -2926,13 +3064,33 @@ static BOOL CALLBACK WdEnumWindowsProc(HWND hwnd, LPARAM lParam) if (processId != ((WinDebugger*)gDebugger)->mProcessInfo.dwProcessId) return TRUE; - - SetForegroundWindow(hwnd); + + while (true) + { + HWND parentHWnd = GetParent(hwnd); + if (parentHWnd != NULL) + { + hwnd = parentHWnd; + continue; + } + SetForegroundWindow(hwnd); + break; + } + return TRUE; } void WinDebugger::ForegroundTarget() { + HWND hwnd = ::GetForegroundWindow(); + if (hwnd != INVALID_HANDLE_VALUE) + { + DWORD processId = 0; + GetWindowThreadProcessId(hwnd, &processId); + if (processId == ((WinDebugger*)gDebugger)->mProcessInfo.dwProcessId) + return; // Already good + } + EnumWindows(WdEnumWindowsProc, 0); } diff --git a/IDEHelper/WinDebugger.h b/IDEHelper/WinDebugger.h index 3843b698..9feb2784 100644 --- a/IDEHelper/WinDebugger.h +++ b/IDEHelper/WinDebugger.h @@ -389,6 +389,7 @@ public: String mArgs; String mWorkingDir; bool mHotSwapEnabled; + DbgOpenFileFlags mOpenFileFlags; Array mEnvBlock; DebugTarget* mEmptyDebugTarget; DebugTarget* mDebugTarget; @@ -400,6 +401,11 @@ public: DWORD mDbgProcessId; HANDLE mDbgProcessHandle; HANDLE mDbgThreadHandle; + + HANDLE mStdInputPipe; + HANDLE mStdOutputPipe; + HANDLE mStdErrorPipe; + bool mIsDebuggerWaiting; bool mWantsDebugContinue; bool mNeedsRehupBreakpoints; @@ -553,7 +559,7 @@ public: void ModuleChanged(DbgModule* dbgModule); bool DoUpdate(); void DebugThreadProc(); - bool DoOpenFile(const StringImpl& fileName, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock); + bool DoOpenFile(const StringImpl& fileName, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, DbgOpenFileFlags openFileFlags); DbgTypedValue GetRegister(const StringImpl& regName, DbgLanguage language, CPURegisters* registers, Array* regForms = NULL); void FixupLineData(DbgCompileUnit* compileUnit); @@ -581,8 +587,9 @@ public: virtual void OutputRawMessage(const StringImpl& msg) override; virtual int GetAddrSize() override; virtual bool CanOpen(const StringImpl& fileName, DebuggerResult* outResult) override; - virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled) override; + virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock, bool hotSwapEnabled, DbgOpenFileFlags openFileFlags) override; virtual bool Attach(int processId, BfDbgAttachFlags attachFlags) override; + virtual void GetStdHandles(BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr) override; virtual void Run() override; virtual bool HasLoadedTargetBinary() override; virtual void HotLoad(const Array& objectFiles, int hotIdx) override;