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.