Ejemplo n.º 1
0
        /// <summary>
        /// Returns the <see cref="AutoResetEvent"/> that signals when the process exits.
        /// </summary>
        private static AutoResetEvent WaitForExit(Process process)
        {
            Covenant.Requires <ArgumentNullException>(process != null, nameof(process));

            return(new AutoResetEvent(false)
            {
                SafeWaitHandle = new SafeWaitHandle(process.ProcessInfo.hProcess, ownsHandle: false)
            });
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Starts a remote process by executing a command line and then wiring up a pseudo
        /// TTY that forwards keystrokes to the remote process and also receives VTx formatted
        /// output from the process and handle rendering on the local <see cref="Console"/>.
        /// </summary>
        /// <param name="command">
        /// <para>
        /// Specifies the local command to execute as the remote process.
        /// </para>
        /// <note>
        /// You must take care to quote the command executable path or any arguments that
        /// include spaces.
        /// </note>
        /// </param>
        /// <param name="keyMap">
        /// Optionally specifies the map to be used for translating keystrokes into
        /// <a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-48/">ECMA-48</a>
        /// (or other) control sequences.  This defaults to <see cref="DefaultKeyMap"/> but you
        /// may pass a custom map when required.
        /// </param>
        /// <remarks>
        /// <para>
        /// If the command path specifies an absolute or relative directory then the command
        /// will be execute from there, otherwise the method will first attempt executing the
        /// command from the current directory before searching the PATH for the command.
        /// </para>
        /// <para>
        /// You may omit the command file extension and the method will try <b>.exe</b>,
        /// <b>.cmd</b>, and <b>.bat</b> extensions in that order.
        /// </para>
        /// </remarks>
        public void Run(string command, IDictionary <ConsoleKeyInfo, string> keyMap = null)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(command), nameof(command));

            command = NormalizeCommand(command);

            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))
                        {
                            // Copy all output from the remote process to the console.

                            Task.Run(() => CopyPipeToOutput(outputPipe.ReadSide));

                            // Process user key presses as required and forward them to the remote process.

                            Task.Run(() => CopyInputToPipe(inputPipe.WriteSide, keyMap ?? DefaultKeyMap));

                            // Free resources in case the console is ungracefully closed (e.g. by the 'X' in the window titlebar).

                            OnClose(() => DisposeResources(process, pseudoConsole, outputPipe, inputPipe));

                            // We need to detect when the console is resized by the user and
                            // then resize the pseudo TTY to match.

                            var processExitEvent = WaitForExit(process);
                            var consoleWidth     = Console.WindowWidth;
                            var consoleHeight    = Console.WindowHeight;

                            while (!processExitEvent.WaitOne(500))
                            {
                                var newWidth  = Console.WindowWidth;
                                var newHeight = Console.WindowHeight;

                                if (consoleHeight != newHeight || consoleWidth != newWidth)
                                {
                                    pseudoConsole.Resize((short)newWidth, (short)newHeight);

                                    consoleWidth  = newWidth;
                                    consoleHeight = newHeight;
                                }
                            }
                        }
        }