mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-30 07:40:13 +01:00
3bae352e33
CA-153347: Send Ctrl-Alt-Del is available for dom0 console and for non-running VMs
1645 lines
63 KiB
C#
1645 lines
63 KiB
C#
/* Copyright (c) Citrix Systems Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
using System.Threading;
|
|
using XenAdmin.Core;
|
|
using XenAdmin.Network;
|
|
using XenAPI;
|
|
using XenAdmin.Commands;
|
|
using XenAdmin.Dialogs;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Net;
|
|
|
|
namespace XenAdmin.ConsoleView
|
|
{
|
|
public partial class VNCTabView : UserControl
|
|
{
|
|
private static readonly string UseRDP = Messages.VNC_RDESKTOP;
|
|
private static readonly string enableRDP = Messages.VNC_RDESKTOP_TURN_ON;
|
|
// public only for the automated tests.
|
|
public static readonly string UseVNC = Messages.VNC_VIRTUAL_CONSOLE;
|
|
public static readonly string UseXVNC = Messages.VNC_X_CONSOLE;
|
|
private static readonly string UseStandardDesktop = Messages.VNC_DEFAULT_DESKTOP;
|
|
|
|
public const int INS_KEY_TIMEOUT = 500;
|
|
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private readonly XSVNCScreen vncScreen;
|
|
private readonly VNCView parentVNCView;
|
|
private readonly VM source;
|
|
private readonly Host targetHost;
|
|
private VM_guest_metrics guestMetrics = null;
|
|
private Form fullscreenForm = null;
|
|
private Form fullscreenHint = null;
|
|
private Size LastDesktopSize;
|
|
private bool switchOnTabOpened = false;
|
|
|
|
/// <summary>
|
|
/// Whether to ignore VNC resize events. We turn this on when changing
|
|
/// the scaling settings, because this in turn triggers a VNC resize.
|
|
/// </summary>
|
|
private bool ignoringResizes = false;
|
|
|
|
private bool ignoreScaleChange = false;
|
|
|
|
internal readonly ConsoleKeyHandler KeyHandler = new ConsoleKeyHandler();
|
|
|
|
private bool hasRDP { get { return source != null ? source.HasRDP : false; } }
|
|
|
|
private bool RDPEnabled { get { return source != null ? source.RDPEnabled : false; } }
|
|
|
|
private bool RDPControlEnabled { get { return source != null ? source.RDPControlEnabled : false; } }
|
|
|
|
public bool IsRDPControlEnabled() { return RDPControlEnabled; }
|
|
|
|
public VNCTabView(VNCView parent, VM source, string elevatedUsername, string elevatedPassword)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
InitializeComponent();
|
|
|
|
HostLabel.Font = Program.HeaderGradientFont;
|
|
HostLabel.ForeColor = Program.HeaderGradientForeColor;
|
|
multipleDvdIsoList1.SetTextColor(Program.HeaderGradientForeColor);
|
|
|
|
#pragma warning disable 0219
|
|
// Force the handle to be created, because resize events
|
|
// could be fired before this component is placed on-screen.
|
|
IntPtr _ = Handle;
|
|
#pragma warning restore 0219
|
|
|
|
this.parentVNCView = parent;
|
|
this.scaleCheckBox.Checked = false;
|
|
this.source = source;
|
|
this.guestMetrics = source.Connection.Resolve(source.guest_metrics);
|
|
if (this.guestMetrics != null)
|
|
guestMetrics.PropertyChanged += guestMetrics_PropertyChanged;
|
|
log.DebugFormat("'{0}' console: Register Server_PropertyChanged event listener on {0}", this.source.Name);
|
|
this.source.PropertyChanged += Server_PropertyChanged;
|
|
Host_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(Host_CollectionChanged);
|
|
VM_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(VM_CollectionChanged);
|
|
source.Connection.Cache.RegisterCollectionChanged<VM>(VM_CollectionChangedWithInvoke);
|
|
|
|
if (source.is_control_domain)
|
|
{
|
|
Host host = source.Connection.Resolve(source.resident_on);
|
|
if (host != null)
|
|
{
|
|
log.DebugFormat("'{0}' console: Register Server_PropertyChanged event listener on {1}", this.source.Name, host.Name);
|
|
host.PropertyChanged += Server_PropertyChanged;
|
|
|
|
Host_metrics hostMetrics = source.Connection.Resolve(host.metrics);
|
|
if (hostMetrics != null)
|
|
{
|
|
log.DebugFormat("'{0}' console: Register Server_PropertyChanged event listener on host metrics", this.source.Name);
|
|
hostMetrics.PropertyChanged += Server_PropertyChanged;
|
|
}
|
|
|
|
HostLabel.Text = string.Format(Messages.CONSOLE_HOST, host.Name);
|
|
HostLabel.Visible = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
source.Connection.Cache.RegisterCollectionChanged<Host>(Host_CollectionChangedWithInvoke);
|
|
targetHost = source.GetStorageHost(false);
|
|
|
|
foreach (Host cachedHost in source.Connection.Cache.Hosts)
|
|
{
|
|
log.DebugFormat("'{0}' console: Register Server_EnabledPropertyChanged event listener on {1}",
|
|
source.Name, cachedHost.Name);
|
|
cachedHost.PropertyChanged += Server_EnabledPropertyChanged;
|
|
}
|
|
|
|
HostLabel.Visible = false;
|
|
}
|
|
|
|
log.DebugFormat("'{0}' console: Update power state (on VNCTabView constructor)", this.source.Name);
|
|
updatePowerState();
|
|
this.vncScreen = new XSVNCScreen(source, new EventHandler(RDPorVNCResizeHandler), this, elevatedUsername, elevatedPassword);
|
|
ShowGpuWarningIfRequired();
|
|
|
|
if (source.is_control_domain || source.IsHVM && !hasRDP) //Linux HVM guests should only have one console: the console switch button vanishes altogether.
|
|
{
|
|
toggleConsoleButton.Visible = false;
|
|
}
|
|
else
|
|
{
|
|
toggleConsoleButton.Visible = true;
|
|
this.vncScreen.OnDetectRDP = this.OnDetectRDP;
|
|
this.vncScreen.OnDetectVNC = this.OnDetectVNC;
|
|
this.vncScreen.UserCancelledAuth += this.OnUserCancelledAuth;
|
|
this.vncScreen.VncConnectionAttemptCancelled += this.OnVncConnectionAttemptCancelled;
|
|
}
|
|
|
|
LastDesktopSize = vncScreen.DesktopSize;
|
|
|
|
this.insKeyTimer = new System.Threading.Timer(new TimerCallback(notInsKeyPressed));
|
|
|
|
Properties.Settings.Default.PropertyChanged += Default_PropertyChanged;
|
|
|
|
registerShortCutKeys();
|
|
|
|
//
|
|
// Ctlr - Alt - Ins send Ctrl - Alt - Delete, and cancels and pending full screen.
|
|
//
|
|
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.CTRL_ALT_INS, cancelWaitForInsKeyAndSendCAD);
|
|
|
|
this.vncScreen.Parent = this.contentPanel;
|
|
this.vncScreen.Dock = DockStyle.Fill;
|
|
|
|
this.Dock = DockStyle.Fill;
|
|
|
|
string rdpLabel = GuessNativeConsoleLabel(source);
|
|
this.toggleConsoleButton.Text = rdpLabel;
|
|
|
|
UpdateFullScreenButton();
|
|
|
|
UpdateDockButton();
|
|
|
|
setupCD();
|
|
|
|
UpdateParentMinimumSize();
|
|
|
|
UpdateButtons();
|
|
|
|
UpdateOpenSSHConsoleButtonState();
|
|
|
|
toggleConsoleButton.EnabledChanged += toggleConsoleButton_EnabledChanged;
|
|
|
|
//If RDP enabled and AutoSwitchToRDP selected, switch RDP connection when open the tab.
|
|
//This change is only for Cream, because RDP port scan was removed in Cream.
|
|
if ( Helpers.CreamOrGreater(source.Connection) && Properties.Settings.Default.AutoSwitchToRDP && RDPEnabled )
|
|
switchOnTabOpened = true;
|
|
}
|
|
|
|
//CA-75479 - add to aid debugging
|
|
private void toggleConsoleButton_EnabledChanged(object sender, EventArgs e)
|
|
{
|
|
ButtonBase button = sender as ButtonBase;
|
|
if(button == null)
|
|
return;
|
|
|
|
string format = "Console tab 'Switch to...' button disabled for VM '{0}'";
|
|
|
|
if (button.Enabled)
|
|
format = "Console tab 'Switch to...' button enabled for VM '{0}'";
|
|
|
|
log.DebugFormat(format, source == null ? "unknown/null" : source.name_label);
|
|
|
|
}
|
|
|
|
private void VM_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
if (e.Action == CollectionChangeAction.Remove)
|
|
{
|
|
VM vm = e.Element as VM;
|
|
if (source != null && vm.uuid == source.uuid)
|
|
{
|
|
// the VM we are looking at has gone away. We should redock if necessary, otherwise it
|
|
// avoids the destroy (and re-create in the case of dom0) when the tab itself goes.
|
|
if (!parentVNCView.isDocked)
|
|
parentVNCView.DockUnDock();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Host_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
if (source.is_control_domain)
|
|
return;
|
|
|
|
Host host = e.Element as Host;
|
|
if (host != null)
|
|
{
|
|
if (e.Action == CollectionChangeAction.Add)
|
|
{
|
|
log.DebugFormat("'{0}' console: Register Server_EnabledPropertyChanged event listener on {1}",
|
|
source.Name, host.Name);
|
|
host.PropertyChanged -= Server_EnabledPropertyChanged;
|
|
host.PropertyChanged += Server_EnabledPropertyChanged;
|
|
}
|
|
else if (e.Action == CollectionChangeAction.Remove)
|
|
{
|
|
log.DebugFormat("'{0}' console: Unregister Server_EnabledPropertyChanged event listener on {1}",
|
|
source.Name, host.Name);
|
|
host.PropertyChanged -= Server_EnabledPropertyChanged;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UnregisterEventListeners()
|
|
{
|
|
Properties.Settings.Default.PropertyChanged -= new PropertyChangedEventHandler(Default_PropertyChanged);
|
|
|
|
if (source == null)
|
|
return;
|
|
|
|
log.DebugFormat("'{0}' console: Unregister Server_PropertyChanged event listener on {0}", source.Name);
|
|
source.PropertyChanged -= new PropertyChangedEventHandler(Server_PropertyChanged);
|
|
source.Connection.Cache.DeregisterCollectionChanged<VM>(VM_CollectionChangedWithInvoke);
|
|
|
|
if (this.guestMetrics != null)
|
|
this.guestMetrics.PropertyChanged -= guestMetrics_PropertyChanged;
|
|
|
|
if (source.is_control_domain)
|
|
{
|
|
Host host = source.Connection.Resolve<Host>(source.resident_on);
|
|
if (host != null)
|
|
{
|
|
log.DebugFormat("'{0}' console: Unregister Server_PropertyChanged event listener on {1}",
|
|
source.Name, host.Name);
|
|
host.PropertyChanged -= Server_PropertyChanged;
|
|
|
|
Host_metrics hostMetrics = source.Connection.Resolve<Host_metrics>(host.metrics);
|
|
if (hostMetrics != null)
|
|
{
|
|
log.DebugFormat("'{0}' console: Unregister Server_PropertyChanged event listener on host metrics",
|
|
source.Name);
|
|
hostMetrics.PropertyChanged -= Server_PropertyChanged;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
source.Connection.Cache.DeregisterCollectionChanged<Host>(Host_CollectionChangedWithInvoke);
|
|
|
|
foreach (Host cachedHost in source.Connection.Cache.Hosts)
|
|
{
|
|
log.DebugFormat("'{0}' console: Unregister Server_EnabledPropertyChanged event listener on {1}",
|
|
source.Name, cachedHost.Name);
|
|
cachedHost.PropertyChanged -= Server_EnabledPropertyChanged;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when one of the properties in the preferences / options window changes.
|
|
/// </summary>
|
|
private void Default_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
Program.Invoke(this, delegate
|
|
{
|
|
deregisterShortCutKeys();
|
|
registerShortCutKeys();
|
|
UpdateParentMinimumSize();
|
|
});
|
|
}
|
|
|
|
internal void UpdateParentMinimumSize()
|
|
{
|
|
if (Parent != null)
|
|
{
|
|
int[] bottomTableWidths = tableLayoutPanel1.GetColumnWidths();
|
|
int bottomPanelWidth = bottomTableWidths.Where((t, i) => tableLayoutPanel1.ColumnStyles[i].SizeType != SizeType.Percent).Sum();
|
|
Parent.MinimumSize = new Size(bottomPanelWidth + 20, 400);
|
|
}
|
|
}
|
|
|
|
private void registerShortCutKeys()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (vncScreen == null)
|
|
return;
|
|
|
|
if (Properties.Settings.Default.FullScreenShortcutKey == 0)
|
|
{
|
|
// Ctrl + Alt
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.CTRL_ALT, waitForInsKey);
|
|
}
|
|
else if (Properties.Settings.Default.FullScreenShortcutKey == 1)
|
|
{
|
|
// Ctrl + Alt + F
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.CTRL_ALT_F, toggleFullscreen);
|
|
}
|
|
else if (Properties.Settings.Default.FullScreenShortcutKey == 2)
|
|
{
|
|
// F12
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.F12, toggleFullscreen);
|
|
}
|
|
else if (Properties.Settings.Default.FullScreenShortcutKey == 3)
|
|
{
|
|
// Ctrl + Enter
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.CTRL_ENTER, toggleFullscreen);
|
|
}
|
|
|
|
UpdateFullScreenButton();
|
|
|
|
// CA-10943
|
|
if (Properties.Settings.Default.DockShortcutKey == 1)
|
|
{
|
|
// Alt + Shift + U
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.ALT_SHIFT_U, toggleDockUnDock);}
|
|
else if (Properties.Settings.Default.DockShortcutKey == 2)
|
|
{
|
|
// F11
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.F11, toggleDockUnDock);
|
|
}
|
|
else if (Properties.Settings.Default.DockShortcutKey == 0)
|
|
{
|
|
// <none>
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.ALT_SHIFT_U);
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.F11);
|
|
}
|
|
|
|
UpdateDockButton();
|
|
|
|
// Uncapture keyboard and mouse Key
|
|
if (Properties.Settings.Default.UncaptureShortcutKey == 0)
|
|
{
|
|
// Right Ctrl
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.RIGHT_CTRL, ToogleConsoleFocus);
|
|
}
|
|
else if (Properties.Settings.Default.UncaptureShortcutKey == 1)
|
|
{
|
|
// Left Alt
|
|
KeyHandler.AddKeyHandler(ConsoleShortcutKey.LEFT_ALT, ToogleConsoleFocus);
|
|
}
|
|
}
|
|
|
|
private void deregisterShortCutKeys()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (vncScreen == null)
|
|
return;
|
|
|
|
if (Properties.Settings.Default.FullScreenShortcutKey != 0)
|
|
{
|
|
// Ctrl + Alt
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.CTRL_ALT);
|
|
}
|
|
|
|
if (Properties.Settings.Default.FullScreenShortcutKey != 1)
|
|
{
|
|
// Ctrl + Alt + F
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.CTRL_ALT_F);
|
|
}
|
|
|
|
if (Properties.Settings.Default.FullScreenShortcutKey != 2)
|
|
{
|
|
// F12
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.F12);
|
|
}
|
|
if (Properties.Settings.Default.FullScreenShortcutKey != 3)
|
|
{
|
|
// Ctrl + Enter
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.CTRL_ENTER);
|
|
}
|
|
|
|
if (Properties.Settings.Default.DockShortcutKey != 1)
|
|
{
|
|
// Alt + Shift + U
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.ALT_SHIFT_U);
|
|
}
|
|
|
|
if (Properties.Settings.Default.DockShortcutKey != 2)
|
|
{
|
|
// F11
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.F11);
|
|
}
|
|
|
|
// Uncapture keyboard and mouse Key
|
|
if (Properties.Settings.Default.UncaptureShortcutKey != 0)
|
|
{
|
|
// Right Ctrl
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.RIGHT_CTRL);
|
|
}
|
|
if (Properties.Settings.Default.UncaptureShortcutKey != 1)
|
|
{
|
|
// Left Alt
|
|
KeyHandler.RemoveKeyHandler(ConsoleShortcutKey.LEFT_ALT);
|
|
}
|
|
}
|
|
|
|
public void UpdateDockButton()
|
|
{
|
|
dockButton.Text = parentVNCView.isDocked ? Messages.VNC_UNDOCK : Messages.VNC_REDOCK;
|
|
if (Properties.Settings.Default.DockShortcutKey == 1)
|
|
{
|
|
dockButton.Text += Messages.VNC_DOCK_ALT_SHIFT_U;
|
|
}
|
|
else if (Properties.Settings.Default.DockShortcutKey == 2)
|
|
{
|
|
dockButton.Text += Messages.VNC_DOCK_F11;
|
|
}
|
|
dockButton.Image = parentVNCView.isDocked ? Properties.Resources.detach_24 : Properties.Resources.attach_24;
|
|
}
|
|
|
|
public void UpdateFullScreenButton()
|
|
{
|
|
switch (Properties.Settings.Default.FullScreenShortcutKey)
|
|
{
|
|
case 0:
|
|
fullscreenButton.Text = Messages.VNC_FULLSCREEN_CTRL_ALT;
|
|
break;
|
|
case 1:
|
|
fullscreenButton.Text = Messages.VNC_FULLSCREEN_CTRL_ALT_F;
|
|
break;
|
|
case 2:
|
|
fullscreenButton.Text = Messages.VNC_FULLSCREEN_F12;
|
|
break;
|
|
default:
|
|
fullscreenButton.Text = Messages.VNC_FULLSCREEN_CTRL_ENTER;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void Server_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "power_state"
|
|
|| e.PropertyName == "live"
|
|
|| e.PropertyName == "allowed_operations")
|
|
{
|
|
log.DebugFormat("'{0}' console: Update power state, after receiving property change notification, PropertyName='{1}'", sender.ToString(), e.PropertyName);
|
|
updatePowerState();
|
|
}
|
|
else if (e.PropertyName == "VBDs")
|
|
{
|
|
//The CD device may have changed
|
|
Program.Invoke(this, setupCD);
|
|
}
|
|
else if (e.PropertyName == "guest_metrics")
|
|
{
|
|
var newGuestMetrics = source.Connection.Resolve(source.guest_metrics);
|
|
|
|
//unsubscribing from the previous instance's event
|
|
if (this.guestMetrics != null)
|
|
this.guestMetrics.PropertyChanged -= guestMetrics_PropertyChanged;
|
|
|
|
this.guestMetrics = newGuestMetrics;
|
|
if (this.guestMetrics != null)
|
|
guestMetrics.PropertyChanged += guestMetrics_PropertyChanged;
|
|
|
|
EnableRDPIfCapable();
|
|
|
|
UpdateOpenSSHConsoleButtonState(); //guest_metrics change when there is an IP address change on a VIF
|
|
}
|
|
else if (e.PropertyName == "VIFs" || e.PropertyName == "PIFs")
|
|
{
|
|
UpdateOpenSSHConsoleButtonState();
|
|
}
|
|
|
|
if (source.is_control_domain && e.PropertyName == "name_label")
|
|
{
|
|
HostLabel.Text = string.Format(Messages.CONSOLE_HOST, source.AffinityServerString);
|
|
if (parentVNCView != null && parentVNCView.undockedForm != null)
|
|
parentVNCView.undockedForm.Text = source.AffinityServerString;
|
|
}
|
|
}
|
|
|
|
private void guestMetrics_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "other")
|
|
{
|
|
if (RDPEnabled)
|
|
{// If current connection is VNC, will enable RDP connection as following conditions:
|
|
// 1. Click "Turn on RDP which cause tryToConnectRDP = true"
|
|
// 2. RDP status changed by turned on RDP in-guest and with "Automatically switch to the Remote
|
|
// Desktop console when it becomes available" is on. But if user already choose connection type by click "Turn on/Switch to Remote Desktop"
|
|
// or "Switch to Default desktop", we will take AutoSwitchToRDP as no effect
|
|
if (vncScreen.UseVNC && (tryToConnectRDP || (!vncScreen.UserWantsToSwitchProtocol && Properties.Settings.Default.AutoSwitchToRDP)))
|
|
{
|
|
tryToConnectRDP = false;
|
|
|
|
ThreadPool.QueueUserWorkItem(TryToConnectRDP);
|
|
}
|
|
}
|
|
else
|
|
EnableRDPIfCapable();
|
|
UpdateButtons();
|
|
}
|
|
}
|
|
|
|
private void EnableRDPIfCapable()
|
|
{
|
|
if (!toggleConsoleButton.Visible && hasRDP)
|
|
{
|
|
// The toogle button is not visible now, because RDP had not been enabled on the VM when we started the console.
|
|
// However, the current guest_metrics indicates that RDP is now supported (HasRDP==true). (eg. XenTools has been installed in the meantime.)
|
|
// This means that now we should show and enable the toogle RDP button and start polling (if allowed) RDP as well.
|
|
|
|
log.DebugFormat( "'{0}' console: Enabling RDP button, because RDP capability has appeared.", source);
|
|
|
|
if (Properties.Settings.Default.EnableRDPPolling)
|
|
{
|
|
log.DebugFormat("'{0}' console: Starting RDP polling. (RDP polling is enabled in settings.)", source);
|
|
toggleConsoleButton.Visible = true;
|
|
if(Helpers.CreamOrGreater(source.Connection) && RDPControlEnabled)
|
|
toggleConsoleButton.Enabled = true;
|
|
else
|
|
toggleConsoleButton.Enabled = false;
|
|
ThreadPool.QueueUserWorkItem(TryToConnectRDP);
|
|
}
|
|
else
|
|
{
|
|
log.DebugFormat("'{0}' console: Not starting polling. (RDP polling is diabled in settings.)", source);
|
|
toggleConsoleButton.Visible = true;
|
|
toggleConsoleButton.Enabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Server_EnabledPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName != "enabled" || source.is_control_domain)
|
|
return;
|
|
|
|
Host host = sender as Host;
|
|
if (host == null)
|
|
return;
|
|
|
|
if (targetHost == null || targetHost.Equals(host))
|
|
{
|
|
log.DebugFormat(
|
|
"'{0}' console: Update power state, after receiving property change notification, PropertyName='{1}'",
|
|
sender.ToString(), e.PropertyName);
|
|
updatePowerState();
|
|
}
|
|
}
|
|
|
|
public void setupCD()
|
|
{
|
|
multipleDvdIsoList1.VM = source;
|
|
}
|
|
|
|
private void cdLabel_Click(object sender, EventArgs e)
|
|
{
|
|
new InstallToolsCommand(Program.MainWindow, source, this).Execute();
|
|
}
|
|
|
|
private void updatePowerState()
|
|
{
|
|
if (source.is_control_domain)
|
|
{
|
|
Host host = source.Connection.Resolve<Host>(source.resident_on);
|
|
if (host == null)
|
|
return;
|
|
|
|
Host_metrics hostMetrics = source.Connection.Resolve<Host_metrics>(host.metrics);
|
|
if (hostMetrics == null)
|
|
return;
|
|
|
|
if (hostMetrics.live)
|
|
{
|
|
Program.Invoke(this, showTopBarContents);
|
|
}
|
|
else
|
|
{
|
|
Program.Invoke(this, hideTopBarContents);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (source.power_state)
|
|
{
|
|
case vm_power_state.Halted:
|
|
case vm_power_state.Paused:
|
|
case vm_power_state.Suspended:
|
|
Program.Invoke(this, hideTopBarContents);
|
|
break;
|
|
case vm_power_state.Running:
|
|
Program.Invoke(this, showTopBarContents);
|
|
Program.Invoke(this, maybeEnableButton);
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateOpenSSHConsoleButtonState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// CA-8966: No way to get from graphical to text console if vm's networking is broken on startup
|
|
/// </summary>
|
|
private void maybeEnableButton()
|
|
{
|
|
if (vncScreen != null && (!vncScreen.UseVNC || !vncScreen.UseSource))
|
|
{
|
|
toggleConsoleButton.Enabled = true;
|
|
}
|
|
}
|
|
|
|
private void EnablePowerStateLabel(String label)
|
|
{
|
|
powerStateLabel.Enabled = true;
|
|
powerStateLabel.Text = label;
|
|
powerStateLabel.Cursor = Cursors.Hand;
|
|
}
|
|
|
|
private void DisablePowerStateLabel(String label)
|
|
{
|
|
powerStateLabel.Enabled = false;
|
|
powerStateLabel.Text = label;
|
|
powerStateLabel.Cursor = Cursors.Default;
|
|
}
|
|
|
|
private void hideTopBarContents()
|
|
{
|
|
VMPowerOff();
|
|
if (source.is_control_domain)
|
|
{
|
|
log.DebugFormat("'{0}' console: Hide top bar contents, server is unavailable", source.Name);
|
|
DisablePowerStateLabel(Messages.CONSOLE_HOST_DEAD);
|
|
}
|
|
else
|
|
{
|
|
log.DebugFormat("'{0}' console: Hide top bar contents, powerstate='{1}'", source.Name, vm_power_state_helper.ToString(source.power_state));
|
|
if (source.power_state == vm_power_state.Halted)
|
|
{
|
|
if (source.allowed_operations.Contains(vm_operations.start) &&
|
|
Helpers.EnabledTargetExists(targetHost, source.Connection))
|
|
{
|
|
EnablePowerStateLabel(Messages.CONSOLE_POWER_STATE_HALTED_START);
|
|
}
|
|
else
|
|
{
|
|
DisablePowerStateLabel(Messages.CONSOLE_POWER_STATE_HALTED);
|
|
}
|
|
}
|
|
else if (source.power_state == vm_power_state.Paused)
|
|
{
|
|
if (source.allowed_operations.Contains(vm_operations.unpause))
|
|
{
|
|
//EnablePowerStateLabel(Messages.CONSOLE_POWER_STATE_PAUSED_UNPAUSE);
|
|
// CA-12637: Pause/UnPause is not supported in the GUI. Comment out
|
|
// the EnablePowerStateLabel because it gives the appearance that we
|
|
// support unpause via the GUI.
|
|
DisablePowerStateLabel(Messages.CONSOLE_POWER_STATE_PAUSED);
|
|
}
|
|
else
|
|
{
|
|
DisablePowerStateLabel(Messages.CONSOLE_POWER_STATE_PAUSED);
|
|
}
|
|
}
|
|
else if (source.power_state == vm_power_state.Suspended)
|
|
{
|
|
if (source.allowed_operations.Contains(vm_operations.resume) &&
|
|
Helpers.EnabledTargetExists(targetHost, source.Connection))
|
|
{
|
|
EnablePowerStateLabel(Messages.CONSOLE_POWER_STATE_SUSPENDED_RESUME);
|
|
}
|
|
else
|
|
{
|
|
DisablePowerStateLabel(Messages.CONSOLE_POWER_STATE_SUSPENDED);
|
|
}
|
|
}
|
|
}
|
|
powerStateLabel.Show();
|
|
}
|
|
|
|
private void showTopBarContents()
|
|
{
|
|
log.DebugFormat("'{0}' console: Show top bar contents, source is running", source.Name);
|
|
Program.AssertOnEventThread();
|
|
VMPowerOn();
|
|
powerStateLabel.Hide();
|
|
}
|
|
|
|
private void powerStateLabel_Click(object sender, EventArgs e)
|
|
{
|
|
if (!powerStateLabel.Enabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (source.power_state)
|
|
{
|
|
case vm_power_state.Halted:
|
|
DisablePowerStateLabel(Messages.CONSOLE_POWER_STATE_HALTED);
|
|
if (source.allowed_operations.Contains(vm_operations.start))
|
|
{
|
|
DisablePowerStateLabel(powerStateLabel.Text);
|
|
|
|
new StartVMCommand(Program.MainWindow, source).Execute();
|
|
}
|
|
break;
|
|
case vm_power_state.Paused:
|
|
DisablePowerStateLabel(Messages.CONSOLE_POWER_STATE_PAUSED);
|
|
//if(source.allowed_operations.Contains(vm_operations.unpause))
|
|
// new Actions.VmAction(source.Connection, VmActionKind.UnpauseActiveView, source, null).RunAsync();
|
|
break;
|
|
case vm_power_state.Suspended:
|
|
DisablePowerStateLabel(Messages.CONSOLE_POWER_STATE_SUSPENDED);
|
|
if (source.allowed_operations.Contains(vm_operations.resume))
|
|
{
|
|
DisablePowerStateLabel(powerStateLabel.Text);
|
|
new ResumeVMCommand(Program.MainWindow, source).Execute();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public bool isPaused
|
|
{
|
|
get
|
|
{
|
|
if (vncScreen != null && !isFullscreen)
|
|
return vncScreen.wasPaused;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
if (vncScreen != null && !isFullscreen)
|
|
vncScreen.Pause();
|
|
}
|
|
|
|
public void Unpause()
|
|
{
|
|
if (vncScreen != null)
|
|
vncScreen.Unpause();
|
|
}
|
|
|
|
private bool CanEnableRDPOnCreamOrGreater(IXenConnection conn)
|
|
{
|
|
return (RDPControlEnabled && !RDPEnabled && Helpers.CreamOrGreater(conn));
|
|
}
|
|
|
|
// Make the 'enable RDP' button show something sensible if we can...
|
|
private string GuessNativeConsoleLabel(VM source)
|
|
{
|
|
string label = Messages.VNC_LOOKING;
|
|
|
|
if (source == null)
|
|
return label;
|
|
|
|
XenRef<VM_guest_metrics> gm = source.guest_metrics;
|
|
if (gm == null)
|
|
return label;
|
|
|
|
if (Program.MainWindow.SelectionManager.Selection.GetConnectionOfFirstItem() == null)
|
|
return label;
|
|
|
|
VM_guest_metrics gmo = Program.MainWindow.SelectionManager.Selection.GetConnectionOfFirstItem().Resolve<VM_guest_metrics>(gm);
|
|
if (gmo == null)
|
|
return label;
|
|
|
|
if (gmo != null && gmo.os_version != null)
|
|
{
|
|
if (gmo.os_version.ContainsKey("name"))
|
|
{
|
|
string osString = gmo.os_version["name"];
|
|
if (osString != null)
|
|
{
|
|
if (osString.Contains("Microsoft"))
|
|
label = (CanEnableRDPOnCreamOrGreater(source.Connection)) ? enableRDP : UseRDP;
|
|
else
|
|
label = UseXVNC;
|
|
}
|
|
}
|
|
}
|
|
|
|
return label;
|
|
}
|
|
|
|
private void RDPorVNCResizeHandler(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (vncScreen == null)
|
|
return; // This is the first event when vncScreen is created.
|
|
|
|
VNCResizeHandler_();
|
|
}
|
|
|
|
private void VNCResizeHandler_()
|
|
{
|
|
if (!ignoringResizes &&
|
|
DesktopSizeHasChanged() &&
|
|
!scaleCheckBox.Checked)
|
|
{
|
|
MaybeScale();
|
|
}
|
|
}
|
|
|
|
public void MaybeScale()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (vncScreen.DesktopSize.Width > 10 &&
|
|
contentPanel.Width < vncScreen.DesktopSize.Width)
|
|
{
|
|
if (!Properties.Settings.Default.PreserveScaleWhenSwitchBackToVNC)
|
|
{
|
|
scaleCheckBox.Checked = true;
|
|
}
|
|
else
|
|
{
|
|
scaleCheckBox.Checked = oldScaleValue || firstTime;
|
|
firstTime = false;
|
|
}
|
|
}
|
|
else if (Properties.Settings.Default.PreserveScaleWhenSwitchBackToVNC)
|
|
{
|
|
scaleCheckBox.Checked = oldScaleValue;
|
|
}
|
|
scaleCheckBox_CheckedChanged(null, null);
|
|
}
|
|
|
|
private bool DesktopSizeHasChanged()
|
|
{
|
|
if (!LastDesktopSize.Equals(vncScreen.DesktopSize))
|
|
{
|
|
LastDesktopSize = vncScreen.DesktopSize;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public Size GrowToFit()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
ignoringResizes = true;
|
|
try
|
|
{
|
|
scaleCheckBox.Checked = false;
|
|
Size working_area = Screen.FromControl(this).WorkingArea.Size;
|
|
if (vncScreen.DesktopSize.Width > 10 &&
|
|
vncScreen.DesktopSize.Width < working_area.Width &&
|
|
vncScreen.DesktopSize.Height < working_area.Height)
|
|
{
|
|
int twoTimeBorderPadding = VNCGraphicsClient.BORDER_PADDING * 2;
|
|
|
|
return new Size(vncScreen.DesktopSize.Width + twoTimeBorderPadding,
|
|
vncScreen.DesktopSize.Height + buttonPanel.Height + bottomPanel.Height + twoTimeBorderPadding);
|
|
}
|
|
else
|
|
{
|
|
scaleCheckBox.Checked = true;
|
|
return new Size((working_area.Width * 2) / 3, (working_area.Height * 2) / 3);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ignoringResizes = false;
|
|
}
|
|
}
|
|
|
|
private void scaleCheckBox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
if (ignoreScaleChange)
|
|
return;
|
|
|
|
try
|
|
{
|
|
ignoringResizes = true;
|
|
this.vncScreen.Scaling = this.scaleCheckBox.Checked;
|
|
}
|
|
finally
|
|
{
|
|
ignoringResizes = false;
|
|
}
|
|
|
|
FocusVNC();
|
|
}
|
|
|
|
private void sendCAD_Click(object sender, EventArgs e)
|
|
{
|
|
this.vncScreen.SendCAD();
|
|
FocusVNC();
|
|
}
|
|
|
|
private void dockButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (isFullscreen)
|
|
return;
|
|
this.parentVNCView.DockUnDock();
|
|
}
|
|
|
|
private void fullscreenButton_Click(object sender, EventArgs e)
|
|
{
|
|
toggleFullscreen();
|
|
}
|
|
|
|
private System.Threading.Timer insKeyTimer;
|
|
|
|
private void waitForInsKey()
|
|
{
|
|
lock (this.insKeyTimer)
|
|
{
|
|
this.insKeyTimer.Change(INS_KEY_TIMEOUT, System.Threading.Timeout.Infinite);
|
|
}
|
|
}
|
|
|
|
private void cancelWaitForInsKeyAndSendCAD()
|
|
{
|
|
lock (this.insKeyTimer)
|
|
{
|
|
// We have seen the INS key, so lets cancel the timer and send CAD
|
|
|
|
this.insKeyTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
|
this.vncScreen.SendCAD();
|
|
}
|
|
}
|
|
|
|
private void notInsKeyPressed(Object o)
|
|
{
|
|
Program.AssertOffEventThread();
|
|
|
|
Program.Invoke(this, delegate()
|
|
{
|
|
lock (this.insKeyTimer)
|
|
{
|
|
// We have not seen the INS key, so lets toggleFullscreen and cancel the timer
|
|
|
|
this.toggleFullscreen();
|
|
this.insKeyTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
|
}
|
|
});
|
|
}
|
|
|
|
private bool isFullscreen
|
|
{
|
|
get
|
|
{
|
|
return fullscreenForm != null;
|
|
}
|
|
}
|
|
|
|
// CA-10943
|
|
private bool inToggleDockUnDock = false;
|
|
private void toggleDockUnDock()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (inToggleDockUnDock)
|
|
return;
|
|
|
|
inToggleDockUnDock = true;
|
|
dockButton_Click(null, null);
|
|
inToggleDockUnDock = false;
|
|
}
|
|
|
|
private bool inToggleFullscreen = false;
|
|
private void toggleFullscreen()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (inToggleFullscreen)
|
|
return;
|
|
|
|
inToggleFullscreen = true;
|
|
|
|
if (!isFullscreen)
|
|
{
|
|
if (vncScreen.showConnectionBar)
|
|
fullscreenForm = new XenAdmin.Controls.ConsoleTab.FullScreenForm(vncScreen);
|
|
else
|
|
fullscreenForm = new Form();
|
|
fullscreenForm.ShowIcon = false;
|
|
fullscreenForm.ShowInTaskbar = false;
|
|
|
|
fullscreenForm.FormBorderStyle = FormBorderStyle.None;
|
|
fullscreenForm.FormClosing += new FormClosingEventHandler(
|
|
delegate(Object o, FormClosingEventArgs a)
|
|
{
|
|
toggleFullscreen();
|
|
});
|
|
//fullscreenForm.Deactivate += new EventHandler(
|
|
// delegate(Object o, EventArgs e)
|
|
// {
|
|
// toggleFullscreen();
|
|
// });
|
|
if (source != null && source.Connection != null)
|
|
source.Connection.BeforeConnectionEnd += Connection_BeforeConnectionEnd;
|
|
|
|
vncScreen.Parent = fullscreenForm is Controls.ConsoleTab.FullScreenForm
|
|
? (Control) ((Controls.ConsoleTab.FullScreenForm) fullscreenForm).contentPanel
|
|
: fullscreenForm;
|
|
vncScreen.DisplayFocusRectangle = false;
|
|
|
|
Screen screen = Screen.FromControl(this);
|
|
fullscreenForm.StartPosition = FormStartPosition.Manual;
|
|
fullscreenForm.Location = screen.WorkingArea.Location;
|
|
fullscreenForm.Size = screen.Bounds.Size;
|
|
|
|
fullscreenHint = new Controls.ConsoleTab.FullScreenHint(GetFullScreenMessage());
|
|
|
|
fullscreenHint.Show(fullscreenForm);
|
|
fullscreenForm.Show();
|
|
|
|
FocusVNC();
|
|
vncScreen.CaptureKeyboardAndMouse();
|
|
}
|
|
else
|
|
{
|
|
if (source != null && source.Connection != null)
|
|
source.Connection.BeforeConnectionEnd -= Connection_BeforeConnectionEnd;
|
|
|
|
vncScreen.Parent = this.contentPanel;
|
|
vncScreen.DisplayFocusRectangle = true;
|
|
FocusVNC();
|
|
vncScreen.CaptureKeyboardAndMouse();
|
|
|
|
fullscreenForm.Hide();
|
|
fullscreenForm.Dispose();
|
|
fullscreenForm = null;
|
|
}
|
|
|
|
//Everytime we toggle full screen I'm going to force an unpause to make sure we don't acidentally undock / dock a pause VNC
|
|
vncScreen.Unpause();
|
|
|
|
inToggleFullscreen = false;
|
|
|
|
// CA-30477: This refresh stops a scroll bar being painted on the fullscreen form under vista
|
|
if (fullscreenForm != null)
|
|
fullscreenForm.Refresh();
|
|
}
|
|
|
|
string GetFullScreenMessage()
|
|
{
|
|
switch (Properties.Settings.Default.FullScreenShortcutKey)
|
|
{
|
|
case 0:
|
|
return Messages.VNC_FULLSCREEN_MESSAGE_CTRL_ALT;
|
|
case 1:
|
|
return Messages.VNC_FULLSCREEN_MESSAGE_CTRL_ALT_F;
|
|
case 2:
|
|
return Messages.VNC_FULLSCREEN_MESSAGE_F12;
|
|
default:
|
|
return Messages.VNC_FULLSCREEN_MESSAGE_CTRL_ENTER;
|
|
}
|
|
}
|
|
void Connection_BeforeConnectionEnd(object sender, EventArgs e)
|
|
{
|
|
Program.Invoke(this, toggleFullscreen);
|
|
}
|
|
|
|
private const bool RDP = true;
|
|
private const bool XVNC = false;
|
|
private bool toggleToXVNCorRDP = RDP;
|
|
|
|
public void DisableToggleVNCButton()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
toggleConsoleButton.Enabled = false;
|
|
}
|
|
|
|
public void EnableToggleVNCButton()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
toggleConsoleButton.Enabled = true;
|
|
}
|
|
|
|
private void OnDetectRDP()
|
|
{
|
|
Program.Invoke(this, OnDetectRDP_);
|
|
}
|
|
|
|
private void OnDetectRDP_()
|
|
{
|
|
try
|
|
{
|
|
log.DebugFormat("RDP detected for VM '{0}'", source == null ? "unknown/null" : source.name_label);
|
|
this.toggleToXVNCorRDP = RDP;
|
|
if (vncScreen.UseVNC)
|
|
if (CanEnableRDPOnCreamOrGreater(source.Connection))
|
|
this.toggleConsoleButton.Text = enableRDP;
|
|
else
|
|
this.toggleConsoleButton.Text = UseRDP;
|
|
this.toggleConsoleButton.Enabled = true;
|
|
tip.SetToolTip(this.toggleConsoleButton, null);
|
|
if (!vncScreen.UserWantsToSwitchProtocol&& Properties.Settings.Default.AutoSwitchToRDP)
|
|
{
|
|
if (Program.MainWindow.TheTabControl.SelectedTab == Program.MainWindow.TabPageConsole)
|
|
toggleConsoleButton_Click(null, null);
|
|
else
|
|
switchOnTabOpened = true;
|
|
}
|
|
}
|
|
catch (InvalidOperationException exn)
|
|
{
|
|
log.Warn(exn, exn);
|
|
}
|
|
}
|
|
|
|
private void OnDetectVNC()
|
|
{
|
|
Program.Invoke(this, OnDetectVNC_);
|
|
}
|
|
|
|
private void OnDetectVNC_()
|
|
{
|
|
try
|
|
{
|
|
log.DebugFormat("VNC detected for VM '{0}'", source == null ? "unknown/null" : source.name_label);
|
|
this.toggleToXVNCorRDP = XVNC;
|
|
this.toggleConsoleButton.Text = vncScreen.UseSource ? UseXVNC : UseVNC;
|
|
this.toggleConsoleButton.Enabled = true;
|
|
tip.SetToolTip(this.toggleConsoleButton, null);
|
|
}
|
|
catch (InvalidOperationException exn)
|
|
{
|
|
log.Warn(exn, exn);
|
|
}
|
|
}
|
|
|
|
private bool firstTime = true;
|
|
private bool oldScaleValue = false;
|
|
private bool tryToConnectRDP = false; // This parameter will be set after click "TURN ON Rremote Desktop" and will connect RDP when RDP status changed.
|
|
|
|
/// <summary>
|
|
/// Switch between graphical and text consoles.
|
|
/// </summary>
|
|
private void toggleConsoleButton_Click(object sender, EventArgs e)
|
|
{
|
|
bool rdp = (toggleToXVNCorRDP == RDP);
|
|
|
|
try
|
|
{
|
|
if (rdp)
|
|
{
|
|
if (vncScreen.UseVNC)
|
|
oldScaleValue = scaleCheckBox.Checked;
|
|
|
|
vncScreen.UseVNC = !vncScreen.UseVNC;
|
|
vncScreen.UserWantsToSwitchProtocol = true;
|
|
|
|
if (CanEnableRDPOnCreamOrGreater(source.Connection))
|
|
{
|
|
ThreeButtonDialog d = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(System.Drawing.SystemIcons.Question, Messages.FORCE_ENABLE_RDP),
|
|
"EnableRDPonVM",
|
|
new ThreeButtonDialog.TBDButton(Messages.YES, DialogResult.Yes),
|
|
new ThreeButtonDialog.TBDButton(Messages.NO, DialogResult.No));
|
|
if (d.ShowDialog(Program.MainWindow) == DialogResult.Yes)
|
|
{
|
|
Session session = source.Connection.DuplicateSession();
|
|
Dictionary<string, string> _arguments = new Dictionary<string, string>();
|
|
XenAPI.VM.call_plugin(session, source.opaque_ref, "guest-agent-operation", "request-rdp-on", _arguments);
|
|
tryToConnectRDP = true;
|
|
}
|
|
}
|
|
|
|
if (vncScreen.rdpIP == null && vncScreen.UseVNC && Properties.Settings.Default.EnableRDPPolling && (!(Helpers.CreamOrGreater(source.Connection) && RDPControlEnabled) || tryToConnectRDP))
|
|
{
|
|
toggleConsoleButton.Enabled = false;
|
|
}
|
|
else
|
|
{
|
|
if (vncScreen.rdpIP == null) // disable toggleConsoleButton; it will be re-enabled in TryToConnectRDP() when rdp port polling is complete (CA-102755)
|
|
toggleConsoleButton.Enabled = false;
|
|
ThreadPool.QueueUserWorkItem(TryToConnectRDP);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oldScaleValue = scaleCheckBox.Checked;
|
|
vncScreen.UseSource = !vncScreen.UseSource;
|
|
|
|
if (vncScreen.vncIP == null && vncScreen.UseSource && Properties.Settings.Default.EnableRDPPolling)
|
|
{
|
|
toggleConsoleButton.Enabled = false;
|
|
}
|
|
}
|
|
Unpause();
|
|
UpdateButtons();
|
|
}
|
|
catch(COMException ex)
|
|
{
|
|
log.DebugFormat("Disabling toggle-console button as COM related exception thrown: {0}", ex.Message);
|
|
toggleConsoleButton.Enabled = false;
|
|
}
|
|
}
|
|
|
|
private void UpdateButtons()
|
|
{
|
|
bool rdp = (toggleToXVNCorRDP == RDP);
|
|
if (rdp)
|
|
toggleConsoleButton.Text = vncScreen.UseVNC ? (CanEnableRDPOnCreamOrGreater(source.Connection) ? enableRDP : UseRDP) : UseStandardDesktop;
|
|
else
|
|
toggleConsoleButton.Text = vncScreen.UseSource ? UseXVNC : UseVNC;
|
|
|
|
if (Helpers.CreamOrGreater(source.Connection))
|
|
{
|
|
if (RDPEnabled || RDPControlEnabled)
|
|
tip.SetToolTip(this.toggleConsoleButton, null);
|
|
}
|
|
|
|
scaleCheckBox.Visible = !rdp || vncScreen.UseVNC;
|
|
|
|
sendCAD.Enabled = source != null && !source.is_control_domain && source.power_state == vm_power_state.Running && (!rdp || vncScreen.UseVNC);
|
|
|
|
FocusVNC();
|
|
ignoreScaleChange = true;
|
|
if (!Properties.Settings.Default.PreserveScaleWhenSwitchBackToVNC)
|
|
{
|
|
scaleCheckBox.Checked = false;
|
|
}
|
|
ignoreScaleChange = false;
|
|
scaleCheckBox_CheckedChanged(null, null); // make sure scale setting is now correct: CA-84324
|
|
}
|
|
|
|
|
|
private void TryToConnectRDP(object x)
|
|
{
|
|
bool hasToReconnect = vncScreen.rdpIP == null;
|
|
vncScreen.rdpIP = vncScreen.PollPort(XSVNCScreen.RDP_PORT, true);
|
|
Program.Invoke(this, (MethodInvoker)(() =>
|
|
{
|
|
if (hasToReconnect)
|
|
{
|
|
vncScreen.UseVNC = true;
|
|
vncScreen.UseVNC = false;
|
|
}
|
|
Unpause();
|
|
UpdateButtons();
|
|
toggleConsoleButton.Enabled = true; // make sure the toggleConsoleButton is enabled after rdp port polling (CA-102755)
|
|
}));
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the user cancelled VNC authentication.
|
|
/// </summary>
|
|
private void OnUserCancelledAuth(object sender, EventArgs e)
|
|
{
|
|
// Switch back to the text console
|
|
toggleConsoleButton_Click(null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the a connection attempt to VNC is cancelled.
|
|
/// </summary>
|
|
private void OnVncConnectionAttemptCancelled(object sender, EventArgs e)
|
|
{
|
|
// Switch back to the text console
|
|
toggleConsoleButton_Click(null, null);
|
|
}
|
|
|
|
protected override void OnGotFocus(EventArgs e)
|
|
{
|
|
base.OnGotFocus(e);
|
|
FocusVNC();
|
|
}
|
|
|
|
internal void SendCAD()
|
|
{
|
|
if (this.vncScreen != null)
|
|
this.vncScreen.SendCAD();
|
|
}
|
|
|
|
internal void focus_vnc()
|
|
{
|
|
if (this.vncScreen != null)
|
|
FocusVNC();
|
|
}
|
|
|
|
// Focus the VNC screen, as long as we're in the right place at the moment.
|
|
// Otherwise ignore the request to focus or it will steal the keyboard from
|
|
// the rightful owner: see CA-41120.
|
|
private void FocusVNC()
|
|
{
|
|
if (Program.MainWindow.ContainsFocus && Program.MainWindow.TheTabControl.SelectedTab == Program.MainWindow.TabPageConsole)
|
|
vncScreen.Focus();
|
|
}
|
|
|
|
internal void VMPowerOff()
|
|
{
|
|
toggleConsoleButton.Enabled = false;
|
|
|
|
VBD cddrive = source.FindVMCDROM();
|
|
bool allowEject = cddrive != null ? cddrive.allowed_operations.Contains(vbd_operations.eject) : false;
|
|
bool allowInsert = cddrive != null ? cddrive.allowed_operations.Contains(vbd_operations.insert) : false;
|
|
multipleDvdIsoList1.Enabled = (source.power_state == vm_power_state.Halted) && (allowEject || allowInsert);
|
|
|
|
sendCAD.Enabled = false;
|
|
}
|
|
|
|
internal void VMPowerOn()
|
|
{
|
|
//No need to reenable toggleConsoleButton, polling will do it.
|
|
multipleDvdIsoList1.Enabled = true;
|
|
sendCAD.Enabled = source != null && !source.is_control_domain && source.power_state == vm_power_state.Running;
|
|
}
|
|
|
|
internal void RdpDisconnectedHandler(object sender, EventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (vncScreen == null)
|
|
return; // This is the first event when vncScreen is created.
|
|
|
|
rdpDisconnected();
|
|
}
|
|
|
|
private void rdpDisconnected()
|
|
{
|
|
if (!vncScreen.UseVNC)
|
|
toggleConsoleButton_Click(null, null);
|
|
|
|
if (!Helpers.CreamOrGreater(source.Connection) || !RDPControlEnabled)
|
|
toggleConsoleButton.Enabled = false;
|
|
|
|
vncScreen.imediatelyPollForConsole();
|
|
}
|
|
|
|
internal void SwitchIfRequired()
|
|
{
|
|
if (switchOnTabOpened)
|
|
{
|
|
toggleConsoleButton_Click(null, null);
|
|
switchOnTabOpened = false;
|
|
}
|
|
}
|
|
|
|
bool droppedDown = false;
|
|
private readonly CollectionChangeEventHandler Host_CollectionChangedWithInvoke;
|
|
private readonly CollectionChangeEventHandler VM_CollectionChangedWithInvoke;
|
|
|
|
private void LifeCycleButton_MouseClick(object sender, EventArgs e)
|
|
{
|
|
if (droppedDown)
|
|
{
|
|
LifeCycleMenuStrip.Hide();
|
|
return;
|
|
}
|
|
if (source == null)
|
|
{
|
|
return;
|
|
}
|
|
Host host = source.Connection.Resolve<Host>(source.resident_on);
|
|
if (host == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ContextMenuItemCollection contextMenuItems = new ContextMenuItemCollection();
|
|
|
|
if (source.is_control_domain)
|
|
{
|
|
// We're looking at the host console
|
|
if (host.Connection.IsConnected)
|
|
{
|
|
contextMenuItems.Add(new ShutDownHostCommand(Program.MainWindow, host, this));
|
|
contextMenuItems.Add(new RebootHostCommand(Program.MainWindow, host, this));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
contextMenuItems.AddIfEnabled(new ShutDownVMCommand(Program.MainWindow, source, this));
|
|
contextMenuItems.AddIfEnabled(new RebootVMCommand(Program.MainWindow, source, this));
|
|
contextMenuItems.AddIfEnabled(new SuspendVMCommand(Program.MainWindow, source, this));
|
|
contextMenuItems.AddIfEnabled(new InstallToolsCommand(Program.MainWindow, source, this));
|
|
contextMenuItems.AddIfEnabled(new ForceVMShutDownCommand(Program.MainWindow, source, this));
|
|
contextMenuItems.AddIfEnabled(new ForceVMRebootCommand(Program.MainWindow, source, this));
|
|
}
|
|
|
|
LifeCycleMenuStrip.Items.Clear();
|
|
LifeCycleMenuStrip.Items.AddRange(contextMenuItems.ToArray());
|
|
LifeCycleMenuStrip.Show(pictureBox1, pictureBox1.Left, pictureBox1.Bottom);
|
|
}
|
|
|
|
public void showHeaderBar(bool showHeaderBar, bool showLifecycleIcon)
|
|
{
|
|
panel2.Visible = showHeaderBar;
|
|
pictureBox1.Visible = showLifecycleIcon;
|
|
}
|
|
|
|
private void pictureBox1_MouseEnter(object sender, EventArgs e)
|
|
{
|
|
pictureBox1.Image = Properties.Resources.lifecycle_hot;
|
|
}
|
|
|
|
private void pictureBox1_MouseLeave(object sender, EventArgs e)
|
|
{
|
|
if (droppedDown)
|
|
pictureBox1.Image = Properties.Resources.lifecycle_pressed;
|
|
else
|
|
pictureBox1.Image = Properties.Resources._001_LifeCycle_h32bit_24;
|
|
}
|
|
|
|
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
pictureBox1.Image = Properties.Resources.lifecycle_pressed;
|
|
}
|
|
|
|
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
|
|
{
|
|
if (droppedDown)
|
|
pictureBox1.Image = Properties.Resources.lifecycle_pressed;
|
|
else
|
|
pictureBox1.Image = Properties.Resources.lifecycle_hot;
|
|
}
|
|
|
|
private void LifeCycleMenuStrip_Opened(object sender, EventArgs e)
|
|
{
|
|
droppedDown = true;
|
|
}
|
|
|
|
private void LifeCycleMenuStrip_Closing(object sender, ToolStripDropDownClosingEventArgs e)
|
|
{
|
|
if (e.CloseReason != ToolStripDropDownCloseReason.AppClicked || !pictureBox1.ClientRectangle.Contains(this.PointToClient(MousePosition)))
|
|
{
|
|
droppedDown = false;
|
|
pictureBox1.Image = Properties.Resources._001_LifeCycle_h32bit_24;
|
|
}
|
|
else
|
|
{
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
|
|
internal Image Snapshot()
|
|
{
|
|
if (vncScreen != null)
|
|
{
|
|
return vncScreen.Snapshot();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private bool inToogleConsoleFocus = false;
|
|
private void ToogleConsoleFocus()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (inToogleConsoleFocus)
|
|
return;
|
|
|
|
inToogleConsoleFocus = true;
|
|
|
|
if (vncScreen.Focused && vncScreen.ActiveControl == null)
|
|
vncScreen.CaptureKeyboardAndMouse(); // focus console
|
|
else
|
|
{
|
|
vncScreen.UncaptureKeyboardAndMouse(); // defocus console
|
|
vncScreen.Refresh();
|
|
}
|
|
|
|
inToogleConsoleFocus = false;
|
|
}
|
|
|
|
internal void ShowGpuWarningIfRequired()
|
|
{
|
|
dedicatedGpuWarning.Visible = vncScreen != null && (vncScreen.UseVNC || string.IsNullOrEmpty(vncScreen.rdpIP)) &&
|
|
vncScreen.Source.HasGPUPassthrough && vncScreen.Source.power_state == vm_power_state.Running;
|
|
}
|
|
|
|
internal bool IsVNC
|
|
{
|
|
get { return vncScreen.UseVNC; }
|
|
}
|
|
|
|
#region SSH Console methods
|
|
|
|
private void buttonSSH_Click(object sender, EventArgs e)
|
|
{
|
|
if (CanLaunchSSHConsole)
|
|
{
|
|
var puttyPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "putty.exe");
|
|
|
|
try
|
|
{
|
|
var startInfo = new ProcessStartInfo(puttyPath, GetIPAddressForSSH());
|
|
Process.Start(startInfo);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("Error starting PuTTY.", ex);
|
|
|
|
new ThreeButtonDialog(new ThreeButtonDialog.Details(SystemIcons.Error, Messages.ERROR_PUTTY_LAUNCHING, Messages.XENCENTER)).ShowDialog(Parent);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateOpenSSHConsoleButtonState()
|
|
{
|
|
buttonSSH.Visible = CanLaunchSSHConsole;
|
|
}
|
|
|
|
public bool CanLaunchSSHConsole
|
|
{
|
|
get
|
|
{
|
|
//Not enabled for Windows VMs
|
|
if (!source.is_control_domain && source.power_state == vm_power_state.Running)
|
|
{
|
|
if (guestMetrics != null && guestMetrics.os_version != null && guestMetrics.os_version.ContainsKey("name"))
|
|
{
|
|
string osDistro = guestMetrics.os_version["distro"];
|
|
if (osDistro != null && osDistro.ToLower().Trim() == "windows")
|
|
return false;
|
|
|
|
string osString = guestMetrics.os_version["name"];
|
|
if (osString != null && osString.Contains("Microsoft"))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (source.is_control_domain)
|
|
{
|
|
Host host = source.Connection.Resolve<Host>(source.resident_on);
|
|
if (host == null)
|
|
return false;
|
|
|
|
Host_metrics hostMetrics = source.Connection.Resolve<Host_metrics>(host.metrics);
|
|
if (hostMetrics == null)
|
|
return false;
|
|
|
|
if (!hostMetrics.live)
|
|
return false;
|
|
}
|
|
|
|
return
|
|
source.power_state == vm_power_state.Running &&
|
|
!string.IsNullOrEmpty(GetIPAddressForSSH());
|
|
}
|
|
}
|
|
|
|
private string GetIPAddressForSSH()
|
|
{
|
|
List<string> ipAddresses = new List<string>();
|
|
|
|
if (!source.is_control_domain) //vm
|
|
{
|
|
List<VIF> vifs = source.Connection.ResolveAll(source.VIFs);
|
|
vifs.Sort();
|
|
|
|
foreach (var vif in vifs)
|
|
{
|
|
if (!vif.currently_attached)
|
|
continue;
|
|
|
|
var network = vif.Connection.Resolve(vif.network);
|
|
if (network != null && network.IsGuestInstallerNetwork)
|
|
continue;
|
|
|
|
ipAddresses.AddRange(vif.IPAddresses);
|
|
}
|
|
}
|
|
else //control domain
|
|
{
|
|
List<PIF> pifList = new List<PIF>(source.Connection.Cache.PIFs);
|
|
pifList.Sort(); // This sort ensures that the primary PIF comes before other management PIFs
|
|
|
|
foreach (PIF pif in pifList)
|
|
{
|
|
if (pif.host.opaque_ref != source.resident_on.opaque_ref || !pif.currently_attached)
|
|
continue;
|
|
|
|
if (pif.IsManagementInterface(false))
|
|
{
|
|
ipAddresses.Add(pif.IP);
|
|
}
|
|
}
|
|
}
|
|
|
|
//find first IPv4 address and return it - we would use it if there is one
|
|
IPAddress addr;
|
|
foreach (string addrString in ipAddresses)
|
|
if (IPAddress.TryParse(addrString, out addr) && addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
|
return addrString;
|
|
|
|
//return the first address (this will not be IPv4)
|
|
return ipAddresses.FirstOrDefault() ?? string.Empty;
|
|
}
|
|
|
|
#endregion SSH Console methods
|
|
}
|
|
}
|