0 votes
by (270 points)

We want to create an advanced SSH console that can analyze input and output from the terminal and provide advices for the user.
Can it be implemented in terminal control?
Looks like event "DataReceived" is good to analyze output, but how to get commands which user input to the terminal?

1 Answer

0 votes
by (70.4k points)
selected by
Best answer

There are two possible ways to monitor user input:

  1. Register the KeyDown event to monitor what the user is typing.

  2. Plug your code into data transport layer and monitor what data are sent to the remote host. Please note that you will see the encoded data sent by the terminal, which means that you will see escape sequences for function keys. For example, you can see 0x1B 0x5B 0x44 for Left-arrow-key, 0x1B 0x4F 0x50 for F1-key, etc.

The second approach has a benefit, you can also monitor the received data.

To plug into data transport layer, make your own IShellChannelFactory implementation. It can look like this:

public class MyChannelFactory : IShellChannelFactory
    private IShellChannelFactory _factory;
    private Action<byte[], int, int> _dataReceived;
    private Action<byte[], int, int> _dataSent;

    public MyChannelFactory(IShellChannelFactory factory,
                            Action<byte[], int, int> dataReceived = null,
                            Action<byte[], int, int> dataSent = null)
        _factory = factory ?? throw new ArgumentNullException("factory");
        _dataReceived = dataReceived;
        _dataSent = dataSent;

    public ShellChannel CreateShellChannel(TerminalOptions options, int columns, int rows)
        return new MyChannel(this, _factory.CreateShellChannel(options, columns, rows));

    private class MyChannel : ShellChannel
        private MyChannelFactory _owner;
        private ShellChannel _channel;

        public MyChannel(MyChannelFactory owner, ShellChannel channel)
            _owner = owner;
            _channel = channel;

        public override int Receive(byte[] buffer, int offset, int count)
            var n = _channel.Receive(buffer, offset, count);
            _owner._dataReceived?.Invoke(buffer, offset, n);
            return n;

        public override int Send(byte[] buffer, int offset, int count)
            var n = _channel.Send(buffer, offset, count);
            _owner._dataSent?.Invoke(buffer, offset, n);
            return n;

        public override ShellChannelOptions Options { get { return _channel.Options; } }
        public override int Available { get { return _channel.Available; } }
        public override bool Connected { get { return _channel.Connected; } }
        public override int TerminalWidth { get { return _channel.TerminalWidth; } }
        public override int TerminalHeight { get { return _channel.TerminalHeight; } }
        public override void Close() { _channel.Close(); }
        public override ShellChannelState GetConnectionState() { return _channel.GetConnectionState(); }
        public override PollResult Poll(int microSeconds) { return _channel.Poll(microSeconds); }
        public override void SetTerminalSize(int width, int height) { _channel.SetTerminalSize(width, height); }

And it can be used like this:

var ssh = new Ssh();
ssh.Login("demo", "password");
terminal.Bind(new MyChannelFactory(ssh, receivedDataHandle, sentDataHandle);