fix exit behavior
old behavior was whenever the user types "exit" to stop the entire terminal, which is not correct (e.g. does not work correctly for nested cmd.exe sessions). Now we wait for the top-level process to exit, which I think is more correct. Also contains a minor rename, Process -> ProcessFactory, ProcessResources -> Process.
This commit is contained in:
parent
bf32b8d48f
commit
3a1ee61476
|
@ -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
|
|||
/// <remarks>
|
||||
/// Possible to replace with managed code? The key is being able to provide the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute
|
||||
/// </remarks>
|
||||
static class Process
|
||||
static class ProcessFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// Represents an instance of a process
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ namespace MiniTerm
|
|||
/// </remarks>
|
||||
internal sealed class PseudoConsolePipe : IDisposable
|
||||
{
|
||||
public SafeFileHandle ReadSide;
|
||||
public SafeFileHandle WriteSide;
|
||||
public readonly SafeFileHandle ReadSide;
|
||||
public readonly SafeFileHandle WriteSide;
|
||||
|
||||
public PseudoConsolePipe()
|
||||
{
|
||||
|
|
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an AutoResetEvent that signals when the process exits
|
||||
/// </summary>
|
||||
private static AutoResetEvent WaitForExit(ProcessFactory.Process process) =>
|
||||
new AutoResetEvent(false)
|
||||
{
|
||||
SafeWaitHandle = new SafeWaitHandle(process.ProcessInfo.hProcess, false)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Set a callback for when the terminal is closed (e.g. via the "X" window decoration button).
|
||||
/// Intended for resource cleanup logic.
|
||||
|
|
Loading…
Reference in a new issue