Răsfoiți Sursa

Embedded console / terminal support

Brian Fiete 1 an în urmă
părinte
comite
20a8e3327c

+ 2 - 0
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;

+ 2 - 0
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)

+ 1 - 0
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

+ 2 - 0
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));
 				}

+ 6 - 0
BeefLibs/corlib/src/IO/MemoryStream.bf

@@ -109,5 +109,11 @@ namespace System.IO
 
 			return .Ok;
         }
+
+		public void Clear()
+		{
+			mMemory.Clear();
+			mPosition = 0;
+		}
 	}
 }

+ 4 - 0
BeefLibs/corlib/src/IO/Pipe.bf

@@ -26,6 +26,8 @@ namespace System.IO
 
 		public Result<void, FileOpenError> 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<void, FileOpenError> Open(StringView machineName, StringView pipeName, PipeOptions options)
 		{
+			Close();
+
 			Runtime.Assert(mBfpFile == null);
 
 			String path = scope String();

+ 4 - 1
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

+ 21 - 0
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"

+ 8 - 0
BeefTools/BeefCon/BeefSpace.toml

@@ -0,0 +1,8 @@
+FileVersion = 1
+Projects = {BeefCon = {Path = "."}, Beefy2D = "*"}
+
+[Workspace]
+StartupProject = "BeefCon"
+
+[Configs.Release.Win64]
+BfOptimizationLevel = "OgPlus"

+ 167 - 0
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<T>(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!<int32>(ptr);
+					int32 rows = GET!<int32>(ptr);
+					bool resizeContent = GET!<bool>(ptr);
+					mProvider.Resize(cols, rows, resizeContent);
+				case .KeyDown:
+					KeyCode keyCode = GET!<KeyCode>(ptr);
+					KeyFlags keyFlags = GET!<KeyFlags>(ptr);
+					mProvider.KeyDown(keyCode, keyFlags);
+				case .KeyUp:
+					KeyCode keyCode = GET!<KeyCode>(ptr);
+					mProvider.KeyUp(keyCode);
+				case .InputString:
+					int32 strLen = GET!<int32>(ptr);
+					StringView str = .((.)ptr, strLen);
+					mProvider.SendInput(str);
+				case .MouseDown:
+					int32 col = GET!<int32>(ptr);
+					int32 row = GET!<int32>(ptr);
+					int32 btnState = GET!<int32>(ptr);
+					int32 btnCount = GET!<int32>(ptr);
+					KeyFlags keyFlags = GET!<KeyFlags>(ptr);
+					mProvider.MouseDown(col, row, btnState, btnCount, keyFlags);
+				case .MouseMove:
+					int32 col = GET!<int32>(ptr);
+					int32 row = GET!<int32>(ptr);
+					int32 btnState = GET!<int32>(ptr);
+					KeyFlags keyFlags = GET!<KeyFlags>(ptr);
+					mProvider.MouseMove(col, row, btnState, keyFlags);
+				case .MouseUp:
+					int32 col = GET!<int32>(ptr);
+					int32 row = GET!<int32>(ptr);
+					int32 btnState = GET!<int32>(ptr);
+					KeyFlags keyFlags = GET!<KeyFlags>(ptr);
+					mProvider.MouseUp(col, row, btnState, keyFlags);
+				case .MouseWheel:
+					int32 col = GET!<int32>(ptr);
+					int32 row = GET!<int32>(ptr);
+					int32 dy = GET!<int32>(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 <pid> <conid> <exe>");
+			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;
+	}
+}

+ 6 - 3
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);

+ 16 - 1
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;

+ 19 - 3
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<Breakpoint> mBreakpointList = new List<Breakpoint>();
 		public Dictionary<String, StepFilter> mStepFilterList = new Dictionary<String, StepFilter>();
 
@@ -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<char8> envBlock, bool isCompiled, bool hotSwapEnabled)
+		public bool OpenFile(String launchPath, String targetPath, String args, String workingDir, Span<char8> 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));

+ 140 - 6
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();

+ 23 - 3
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<ConsoleKind>("DebugConsole", .Native);
+				mAlwaysEnableConsole = sd.GetBool("AlwaysEnableConsole");
+			}
 			using (sd.Open("Wasm"))
 				sd.Get("EmscriptenPath", mEmscriptenPath);
 

+ 1 - 1
IDE/src/TestManager.bf

@@ -614,7 +614,7 @@ namespace IDE
 
 					var envBlock = scope List<char8>();
 					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();

Fișier diff suprimat deoarece este prea mare
+ 354 - 473
IDE/src/ui/ConsolePanel.bf


+ 1 - 1
IDE/src/ui/LaunchDialog.bf

@@ -332,7 +332,7 @@ namespace IDE.ui
 			var envBlock = scope List<char8>();
 			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;

+ 29 - 1
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)

+ 5 - 3
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:

+ 39 - 3
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();
+	}
 }

+ 1218 - 5
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 enum UpdateState
+	{
+		None,
+		Dirty
+	}
+
 	public struct Cell
 	{
-		public char32 mChar;
-		public uint32 mFgColor;
-		public uint32 mBgColor;
+		public char16 mChar;
+		public uint16 mAttributes;
 	}
 
-	public virtual void Get(int col, int row)
+	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
+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 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<void> 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<void> 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<uint8> 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<Span<uint8>> 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<T>(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!<int32>(ptr);
+					mHeight = GET!<int32>(ptr);
+					mBufferHeight = GET!<int32>(ptr);
+					mScrollTop = GET!<int32>(ptr);
+					mCursorVisible = GET!<bool>(ptr);
+					mCursorHeight = GET!<float>(ptr);
+					mCursorPos = GET!<(int32 col, int32 row)>(ptr);
+					for (int i < 16)
+					{
+						uint32 color = GET!<uint32>(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!<Cell>(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();
+	}
 }

+ 5 - 1
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<uint8>& envBlock, bool hotSwapEnabled)
+void CeDebugger::OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array<uint8>& 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()
 {
 }

+ 2 - 1
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<uint8>& envBlock, bool hotSwapEnabled) override;
+	virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array<uint8>& 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<String>& objectFiles, int hotIdx) override;
 	virtual void InitiateHotResolve(DbgHotResolveFlags flags) override;

+ 8 - 2
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();

+ 10 - 1
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<uint8>& envBlock, bool hotSwapEnabled) = 0;
+	virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array<uint8>& 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<String>& objectFiles, int hotIdx) = 0;

+ 166 - 8
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<uint8>& envBlock, bool hotSwapEnabled)
+void WinDebugger::OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array<uint8>& 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<uint8>& 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<uint8>& 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);
 }
 

+ 9 - 2
IDEHelper/WinDebugger.h

@@ -389,6 +389,7 @@ public:
 	String mArgs;
 	String mWorkingDir;
 	bool mHotSwapEnabled;
+	DbgOpenFileFlags mOpenFileFlags;
 	Array<uint8> 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<uint8>& envBlock);
+	bool DoOpenFile(const StringImpl& fileName, const StringImpl& args, const StringImpl& workingDir, const Array<uint8>& envBlock, DbgOpenFileFlags openFileFlags);
 
 	DbgTypedValue GetRegister(const StringImpl& regName, DbgLanguage language, CPURegisters* registers, Array<RegForm>* 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<uint8>& envBlock, bool hotSwapEnabled) override;
+	virtual void OpenFile(const StringImpl& launchPath, const StringImpl& targetPath, const StringImpl& args, const StringImpl& workingDir, const Array<uint8>& 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<String>& objectFiles, int hotIdx) override;

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff