sources/Google.Solutions.IapDesktop.Extensions.Session/ToolWindows/Session/ClientViewBase.cs (141 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.Apis.Locator; using Google.Solutions.Common.Util; using Google.Solutions.IapDesktop.Application.Profile.Settings; using Google.Solutions.IapDesktop.Application.Windows; using Google.Solutions.IapDesktop.Application.Windows.Dialog; using Google.Solutions.IapDesktop.Core.ObjectModel; using Google.Solutions.Mvvm.Binding; using Google.Solutions.Terminal.Controls; using System; using System.Drawing; using System.Windows.Forms; namespace Google.Solutions.IapDesktop.Extensions.Session.ToolWindows.Session { public abstract class ClientViewBase<TClient> : SessionViewBase where TClient : ClientBase, new() { private readonly IExceptionDialog exceptionDialog; private readonly IEventQueue eventQueue; private readonly IBindingContext bindingContext; protected TClient? Client { get; private set; } public bool IsClosing { get; private set; } = false; protected ClientViewBase( IMainWindow mainWindow, ToolWindowStateRepository stateRepository, IEventQueue eventQueue, IExceptionDialog exceptionDialog, IBindingContext bindingContext) : base(mainWindow, stateRepository, bindingContext) { this.exceptionDialog = exceptionDialog; this.eventQueue = eventQueue; this.bindingContext = bindingContext; } public void Connect() { Precondition.Expect(this.Client == null, "View has been connected before"); // // Do initialization here (as opposed to in the constructor) // to ensure that we have a window handle. // SuspendLayout(); this.Client = new TClient() { Size = this.Size, Dock = DockStyle.Fill }; this.Controls.Add(this.Client); this.Client.ConnectionClosed += OnClientConnectionClosed; this.Client.ConnectionFailed += OnClientConnectionFailed; this.Client.StateChanged += OnClientStateChanged; this.Client.Bind(this.bindingContext); ResumeLayout(false); ConnectCore(); } public bool IsConnected { get => this.Client != null && ( this.Client.State == ConnectionState.Connected || this.Client.State == ConnectionState.LoggedOn); } //--------------------------------------------------------------------- // State tracking event handlers. //--------------------------------------------------------------------- private void OnClientStateChanged(object sender, System.EventArgs e) { if (this.Client!.State == ConnectionState.Connected) { _ = this.eventQueue.PublishAsync(new SessionStartedEvent(this.Instance)); } } private void OnClientConnectionFailed(object sender, Mvvm.Controls.ExceptionEventArgs e) { OnFatalError(e.Exception); _ = this.eventQueue.PublishAsync(new SessionAbortedEvent(this.Instance, e.Exception)); } private void OnClientConnectionClosed(object sender, ClientBase.ConnectionClosedEventArgs e) { switch (e.Reason) { case ClientBase.DisconnectReason.ReconnectInitiatedByUser: // // User initiated a reconnect -- leave everything as is. // break; case RdpClient.DisconnectReason.FormClosed: // // User closed the form. // break; case RdpClient.DisconnectReason.DisconnectedByUser: // // User-initiated signout. // Close(); break; default: // // Something else - allow user to reconnect. // break; } } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); if (this.Client != null) { // // NB. Docking does not work reliably with the OCX, so keep the size // in sync programmatically. // this.Client.Size = this.Size; } } protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); // // Mark this pane as being in closing state even though it is still // visible at this point. The flag ensures that this pane is // not considered by TryGetExistingPane anymore. // this.IsClosing = true; _ = this.eventQueue.PublishAsync(new SessionEndedEvent(this.Instance)); } //--------------------------------------------------------------------- // Drag/docking. // // The client control must always have a parent. But when a document is // dragged to become a floating window, or when a window is re-docked, // then its parent is temporarily set to null. // // To "rescue" the client control in these situations, we temporarily // move the the control to a rescue form when the drag begins, and // restore it when it ends. //--------------------------------------------------------------------- private Form? rescueWindow = null; protected override Size DefaultFloatWindowClientSize => this.Size; protected override void OnDockBegin() { // // NB. It's possible that another rescue operation is still in // progress. So don't create a window if there is one already. // if (this.rescueWindow == null && this.Client != null) { this.rescueWindow = new Form(); this.Client.Parent = this.rescueWindow; } base.OnDockBegin(); } protected override void OnDockEnd() { if (this.rescueWindow != null && this.Client != null) { this.Client.Parent = this; this.Client.Size = this.Size; this.rescueWindow.Close(); this.rescueWindow = null; } base.OnDockEnd(); } //--------------------------------------------------------------------- // Abstract and virtual methods for deriving class to override. //--------------------------------------------------------------------- /// <summary> /// Initialize the client and connect. /// </summary> protected abstract void ConnectCore(); /// <summary> /// Get instance that this session connects to. /// </summary> public abstract InstanceLocator Instance { get; } /// <summary> /// Display a non-fatal error. /// </summary> protected virtual void OnError(string caption, Exception e) { if (e.IsCancellation()) { // // The user cancelled, nervemind. // return; } this.exceptionDialog.Show(this, caption, e); } /// <summary> /// Display a fatal error. /// </summary> protected virtual void OnFatalError(Exception e) { if (e.IsCancellation()) { // // The user cancelled, nervemind. // return; } this.exceptionDialog.Show(this, "Session disconnected", e); } } }