diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Process.cs b/samples/ConPTY/MiniTerm/MiniTerm/Process.cs index 3756077e2..40a703f63 100644 --- a/samples/ConPTY/MiniTerm/MiniTerm/Process.cs +++ b/samples/ConPTY/MiniTerm/MiniTerm/Process.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.InteropServices; using static MiniTerm.Native.ProcessApi; -using static MiniTerm.Native.PseudoConsoleApi; namespace MiniTerm { @@ -11,16 +10,16 @@ namespace MiniTerm /// /// Possible to replace with managed code? The key is being able to provide the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute /// - static class Process + static class ProcessFactory { /// - /// Start and configure a process. The return value should be considered opaque, and eventually disposed. + /// Start and configure a process. The return value represents the process and should be disposed. /// - internal static ProcessResources Start(string command, IntPtr attributes, IntPtr hPC) + internal static Process Start(string command, IntPtr attributes, IntPtr hPC) { var startupInfo = ConfigureProcessThread(hPC, attributes); var processInfo = RunProcess(ref startupInfo, "cmd.exe"); - return new ProcessResources(startupInfo, processInfo); + return new Process(startupInfo, processInfo); } private static STARTUPINFOEX ConfigureProcessThread(IntPtr hPC, IntPtr attributes) @@ -93,70 +92,73 @@ namespace MiniTerm return pInfo; } + } - internal sealed class ProcessResources : IDisposable + /// + /// Represents an instance of a process + /// + internal sealed class Process : IDisposable + { + public Process(STARTUPINFOEX startupInfo, PROCESS_INFORMATION processInfo) { - public ProcessResources(STARTUPINFOEX startupInfo, PROCESS_INFORMATION processInfo) - { - StartupInfo = startupInfo; - ProcessInfo = processInfo; - } - - STARTUPINFOEX StartupInfo { get; } - PROCESS_INFORMATION ProcessInfo { get; } - - #region IDisposable Support - - private bool disposedValue = false; // To detect redundant calls - - void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // dispose managed state (managed objects). - } - - // dispose unmanaged state - - // Free the attribute list - if (StartupInfo.lpAttributeList != IntPtr.Zero) - { - DeleteProcThreadAttributeList(StartupInfo.lpAttributeList); - Marshal.FreeHGlobal(StartupInfo.lpAttributeList); - } - - // Close process and thread handles - if (ProcessInfo.hProcess != IntPtr.Zero) - { - CloseHandle(ProcessInfo.hProcess); - } - if (ProcessInfo.hThread != IntPtr.Zero) - { - CloseHandle(ProcessInfo.hThread); - } - - disposedValue = true; - } - } - - ~ProcessResources() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false); - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // use the following line if the finalizer is overridden above. - GC.SuppressFinalize(this); - } - - #endregion + StartupInfo = startupInfo; + ProcessInfo = processInfo; } + + public STARTUPINFOEX StartupInfo { get; } + public PROCESS_INFORMATION ProcessInfo { get; } + + #region IDisposable Support + + private bool disposedValue = false; // To detect redundant calls + + void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects). + } + + // dispose unmanaged state + + // Free the attribute list + if (StartupInfo.lpAttributeList != IntPtr.Zero) + { + DeleteProcThreadAttributeList(StartupInfo.lpAttributeList); + Marshal.FreeHGlobal(StartupInfo.lpAttributeList); + } + + // Close process and thread handles + if (ProcessInfo.hProcess != IntPtr.Zero) + { + CloseHandle(ProcessInfo.hProcess); + } + if (ProcessInfo.hThread != IntPtr.Zero) + { + CloseHandle(ProcessInfo.hThread); + } + + disposedValue = true; + } + } + + ~Process() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(false); + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // use the following line if the finalizer is overridden above. + GC.SuppressFinalize(this); + } + + #endregion } } diff --git a/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsolePipe.cs b/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsolePipe.cs index 604afb2d1..c5a8898a4 100644 --- a/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsolePipe.cs +++ b/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsolePipe.cs @@ -13,8 +13,8 @@ namespace MiniTerm /// internal sealed class PseudoConsolePipe : IDisposable { - public SafeFileHandle ReadSide; - public SafeFileHandle WriteSide; + public readonly SafeFileHandle ReadSide; + public readonly SafeFileHandle WriteSide; public PseudoConsolePipe() { diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs b/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs index 8de735628..2077fb4c8 100644 --- a/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs +++ b/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using static MiniTerm.Native.ConsoleApi; @@ -49,17 +50,16 @@ namespace MiniTerm using (var inputPipe = new PseudoConsolePipe()) using (var outputPipe = new PseudoConsolePipe()) using (var pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, (short)Console.WindowWidth, (short)Console.WindowHeight)) - using (var process = Process.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle)) + using (var process = ProcessFactory.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle)) { - // set up a background task to copy all pseudoconsole output to stdout + // copy all pseudoconsole output to stdout Task.Run(() => CopyPipeToOutput(outputPipe.ReadSide)); - + // prompt for stdin input and send the result to the pseudoconsole + Task.Run(() => CopyInputToPipe(inputPipe.WriteSide)); // free resources in case the console is ungracefully closed (e.g. by the 'x' in the window titlebar) OnClose(() => DisposeResources(process, pseudoConsole, outputPipe, inputPipe)); - // prompt for stdin input and send the result to the pipe. - // blocks until the user types "exit" - CopyInputToPipe(inputPipe.WriteSide); + WaitForExit(process).WaitOne(Timeout.Infinite); } } @@ -75,25 +75,11 @@ namespace MiniTerm writer.AutoFlush = true; writer.WriteLine(@"cd \"); - StringBuilder buffer = new StringBuilder(); while (true) { // send input character-by-character to the pipe char key = Console.ReadKey(intercept: true).KeyChar; writer.Write(key); - - // stop the input loop if 'exit' was sent - // TODO: if we have nested cmd.exe process, this will kill all of them which is wrong. - // could we somehow detect when the top-level cmd.exe process has ended and remove this logic? - buffer.Append(key); - if (key == '\r') - { - if (buffer.ToString() == ExitCommand) - { - break; - } - buffer.Clear(); - } } } } @@ -123,6 +109,15 @@ namespace MiniTerm } } + /// + /// Get an AutoResetEvent that signals when the process exits + /// + private static AutoResetEvent WaitForExit(ProcessFactory.Process process) => + new AutoResetEvent(false) + { + SafeWaitHandle = new SafeWaitHandle(process.ProcessInfo.hProcess, false) + }; + /// /// Set a callback for when the terminal is closed (e.g. via the "X" window decoration button). /// Intended for resource cleanup logic.