Terminal.cs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. using Microsoft.Win32.SafeHandles;
  2. using System;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using MiniTerm.Native;
  8. namespace MiniTerm
  9. {
  10. /// <summary>
  11. /// The UI of the terminal. It's just a normal console window, but we're managing the input/output.
  12. /// In a "real" project this could be some other UI.
  13. /// </summary>
  14. public sealed class Terminal : IDisposable
  15. {
  16. private PseudoConsolePipe inputPipe;
  17. private PseudoConsolePipe outputPipe;
  18. private PseudoConsole pseudoConsole;
  19. private Process process;
  20. private FileStream writer;
  21. private FileStream reader;
  22. public Terminal(string command, int windowWidth, int windowHeight)
  23. {
  24. inputPipe = new PseudoConsolePipe();
  25. outputPipe = new PseudoConsolePipe();
  26. pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, windowWidth, windowHeight);
  27. process = ProcessFactory.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle);
  28. writer = new FileStream(inputPipe.WriteSide, FileAccess.Write);
  29. reader = new FileStream(outputPipe.ReadSide, FileAccess.Read);
  30. }
  31. public event EventHandler<byte[]> DataReceived;
  32. public event EventHandler<uint> CloseReceived;
  33. /// <summary>
  34. /// Start the psuedoconsole and run the process as shown in
  35. /// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole
  36. /// </summary>
  37. public void Run()
  38. {
  39. // copy all pseudoconsole output to stdout
  40. Task.Run(() =>
  41. {
  42. var proc = System.Diagnostics.Process.GetProcessById(process.ProcessInfo.dwProcessId);
  43. var buf = new byte[1024];
  44. while (!proc.HasExited)
  45. {
  46. var length = reader.Read(buf, 0, buf.Length);
  47. if (length == 0)
  48. break;
  49. DataReceived?.Invoke(this, buf.Take(length).ToArray());
  50. }
  51. CloseReceived?.Invoke(this, 0);
  52. });
  53. }
  54. public void OnInput(byte[] data)
  55. {
  56. writer.Write(data, 0, data.Length);
  57. writer.Flush();
  58. }
  59. public void OnClose()
  60. {
  61. writer.WriteByte(0x03);
  62. writer.Flush();
  63. }
  64. private void DisposeResources(params IDisposable[] disposables)
  65. {
  66. foreach (var disposable in disposables)
  67. {
  68. disposable.Dispose();
  69. }
  70. }
  71. public void Dispose()
  72. {
  73. DisposeResources(reader, writer, process, pseudoConsole, outputPipe, inputPipe);
  74. }
  75. public void ChangeWindowSize(short width, short height)
  76. {
  77. PseudoConsoleApi.ResizePseudoConsole(pseudoConsole.Handle, new PseudoConsoleApi.COORD { X = width, Y = height });
  78. }
  79. }
  80. }