sources/Google.Solutions.Terminal/Controls/PseudoTerminalClientBase.cs (98 lines of code) (raw):

// // Copyright 2024 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Common.Util; using Google.Solutions.Platform.IO; using System; using System.ComponentModel; using System.Diagnostics; using System.Threading.Tasks; using System.Windows.Forms; namespace Google.Solutions.Terminal.Controls { /// <summary> /// Base class for a client that connects a virtual terminal /// to a pseudo-terminal. /// </summary> public abstract class PseudoTerminalClientBase : ClientBase { public PseudoTerminalClientBase() { this.Terminal = new VirtualTerminal() { Dock = DockStyle.Fill }; this.Terminal.DeviceClosed += OnDeviceClosed; this.Terminal.DeviceError += OnDeviceError; this.Controls.Add(this.Terminal); } [Browsable(true)] public VirtualTerminal Terminal { get; } //---------------------------------------------------------------------- // Pty events. //---------------------------------------------------------------------- private void OnDeviceError(object sender, VirtualTerminalErrorEventArgs e) { Debug.Assert( this.State == ConnectionState.Connecting || this.State == ConnectionState.Connected || this.State == ConnectionState.LoggedOn); // // Propagate event so that the hosting form can show // an error message. // if (IsCausedByConnectionTimeout(e.Exception)) { OnConnectionClosed(DisconnectReason.SessionTimeout); } else { OnConnectionFailed(e.Exception); } } private void OnDeviceClosed(object sender, EventArgs e) { Debug.Assert(this.Terminal.Device != null); // // This is an orderly close. // OnConnectionClosed(DisconnectReason.DisconnectedByUser); // // Dispose the device as it might use unmanaged resources. // this.Terminal.Device!.Dispose(); this.Terminal.Device = null; } //---------------------------------------------------------------------- // Parentable control events. //---------------------------------------------------------------------- protected override void OnFormClosing(object sender, FormClosingEventArgs args) { if (this.State == ConnectionState.Disconnecting) { // // Form is being closed as a result of a disconnect // (not the other way round). // } else if ( this.State == ConnectionState.Connecting || this.State == ConnectionState.Connected || this.State == ConnectionState.LoggedOn) { // // Initiate a disconnect. // // NB. Disposing the pty doesn't invoke OnDeviceClosed. // OnBeforeDisconnect(); Debug.Assert(this.Terminal.Device != null); this.Terminal.Device?.Dispose(); OnConnectionClosed(DisconnectReason.FormClosed); } base.OnFormClosing(sender, args); } //---------------------------------------------------------------------- // ClientBase overrides. //---------------------------------------------------------------------- public override void Connect() { Debug.Assert(!this.Terminal.IsDisposed); ExpectState(ConnectionState.NotConnected); Precondition.Expect(this.IsHandleCreated, "Control must be created first"); // // NB. We must initialize the pseudo-terminal with // the right dimensions. Now that the window has been // shown, we know these. // Debug.Assert(this.Terminal.Dimensions.Width > 0); Debug.Assert(this.Terminal.Dimensions.Height > 0); // // Reset state in case we're connecting for the second time. // OnBeforeConnect(); // // Connect the terminal to the (new) pty. // _ = ContinueConnectAysnc(); async Task ContinueConnectAysnc() { try { this.Terminal.Device = await ConnectCoreAsync(this.Terminal.Dimensions) .ConfigureAwait(true); // Back to caller thread. // // The distinction between the Connected and LoggedOn // states isn't relevant for Pty clients, so we skip // straight to LoggedOn. // OnAfterConnect(); OnAfterLogin(); } catch (Exception e) { OnConnectionFailed(e); } } } public override void SendText(string text) { ExpectState(ConnectionState.LoggedOn); this.Terminal.SimulateSend(text); } //---------------------------------------------------------------------- // Abstract methods. //---------------------------------------------------------------------- /// <summary> /// Create a pty. /// </summary> protected abstract Task<IPseudoTerminal> ConnectCoreAsync( PseudoTerminalSize initialSize); /// <summary> /// Determine if the exception is caused by a timeout. /// </summary> protected abstract bool IsCausedByConnectionTimeout(Exception e); } }