xenadmin/XenAdmin/ConsoleView/VNCTabView.cs
Mihaela Stoica 42df02734a CA-249856: Fix console position and focus rectangle drawing on resize
Also ensuring that if the UpdateSessionDisplaySettings function (which we use for resize) is not supported, then we display the console centered in the parent window and with scrollbars if needed

Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
2017-04-25 14:07:29 +01:00

1578 lines
60 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 XenAdmin.Controls.ConsoleTab;
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 FullScreenForm fullscreenForm;
private FullScreenHint fullscreenHint;
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.LabelSingleDvdForeColor = Program.HeaderGradientForeColor;
multipleDvdIsoList1.LabelNewCdForeColor = Program.HeaderGradientForeColor;
multipleDvdIsoList1.LinkLabelLinkColor = Color.White;
#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(source.IsControlDomainZero ? Messages.CONSOLE_HOST : Messages.CONSOLE_HOST_NUTANIX, 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(vncScreen.MustConnectRemoteDesktop());
vncScreen.GpuStatusChanged += ShowGpuWarningIfRequired;
if (source.IsControlDomainZero || 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;
string rdpLabel = GuessNativeConsoleLabel(source);
this.toggleConsoleButton.Text = rdpLabel;
UpdateFullScreenButton();
UpdateDockButton();
setupCD();
UpdateParentMinimumSize();
UpdateTooltipOfToogleButton();
UpdateOpenSSHConsoleButtonState();
toggleConsoleButton.EnabledChanged += toggleConsoleButton_EnabledChanged;
//If RDP enabled and AutoSwitchToRDP selected, switch RDP connection will be done when VNC already get the correct screen resolution.
//This change is only for Cream, because RDP port scan was removed in Cream.
if ( Helpers.CreamOrGreater(source.Connection) && Properties.Settings.Default.AutoSwitchToRDP && RDPEnabled )
vncScreen.AutoSwitchRDPLater = true;
}
public bool IsScaled
{
get { return scaleCheckBox.Checked; }
set { scaleCheckBox.Checked = value; }
}
//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.IsControlDomainZero)
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.IsControlDomainZero)
{
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, ToggleConsoleFocus);
}
else if (Properties.Settings.Default.UncaptureShortcutKey == 1)
{
// Left Alt
KeyHandler.AddKeyHandler(ConsoleShortcutKey.LEFT_ALT, ToggleConsoleFocus);
}
}
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")
{
string text = string.Format(source.IsControlDomainZero ? Messages.CONSOLE_HOST : Messages.CONSOLE_HOST_NUTANIX, source.AffinityServerString);
HostLabel.Text = text;
if (parentVNCView != null && parentVNCView.undockedForm != null)
parentVNCView.undockedForm.Text = text;
}
}
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.IsControlDomainZero)
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 updatePowerState()
{
if (source.IsControlDomainZero)
{
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.IsControlDomainZero)
{
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 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 + gradientPanel1.Height + tableLayoutPanel1.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)
{
fullscreenForm = new FullScreenForm(this);
fullscreenForm.FormClosing += delegate { toggleFullscreen(); };
if (source != null && source.Connection != null)
source.Connection.BeforeConnectionEnd += Connection_BeforeConnectionEnd;
fullscreenForm.AttachVncScreen(vncScreen);
vncScreen.DisplayFocusRectangle = false;
fullscreenHint = new FullScreenHint();
fullscreenForm.Show();
fullscreenHint.Show(fullscreenForm);
FocusVNC();
vncScreen.CaptureKeyboardAndMouse();
UpdateRDPResolution(true);
}
else
{
if (source != null && source.Connection != null)
source.Connection.BeforeConnectionEnd -= Connection_BeforeConnectionEnd;
fullscreenForm.DetachVncScreen(vncScreen);
vncScreen.Parent = this.contentPanel;
vncScreen.DisplayFocusRectangle = true;
FocusVNC();
vncScreen.CaptureKeyboardAndMouse();
fullscreenForm.Hide();
fullscreenForm.Dispose();
fullscreenForm = null;
UpdateRDPResolution(false);
}
//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();
}
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))
{
DialogResult dialogResult;
using (ThreeButtonDialog dlg = 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)))
{
dialogResult = dlg.ShowDialog(Program.MainWindow);
}
if (dialogResult == 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;
UpdateTooltipOfToogleButton();
scaleCheckBox.Visible = !rdp || vncScreen.UseVNC;
sendCAD.Enabled = !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 UpdateTooltipOfToogleButton()
{
if (Helpers.CreamOrGreater(source.Connection))
{
if (RDPEnabled || RDPControlEnabled)
tip.SetToolTip(this.toggleConsoleButton, null);
}
}
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 = true;
}
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.IsControlDomainZero)
{
// 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)
{
gradientPanel1.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 inToggleConsoleFocus = false;
private void ToggleConsoleFocus()
{
Program.AssertOnEventThread();
if (inToggleConsoleFocus)
return;
inToggleConsoleFocus = true;
if (vncScreen.Focused && vncScreen.ActiveControl == null)
vncScreen.CaptureKeyboardAndMouse(); // focus console
else
{
vncScreen.UncaptureKeyboardAndMouse(); // defocus console
vncScreen.Refresh();
}
inToggleConsoleFocus = false;
}
private void ShowGpuWarningIfRequired(bool mustConnectRemoteDesktop)
{
dedicatedGpuWarning.Visible = mustConnectRemoteDesktop;
}
internal bool IsVNC
{
get { return vncScreen.UseVNC; }
}
public void UpdateRDPResolution(bool fullscreen = false)
{
if (vncScreen != null)
vncScreen.UpdateRDPResolution(fullscreen);
}
#region SSH Console methods
private void buttonSSH_Click(object sender, EventArgs e)
{
if (CanStartSSHConsole)
{
var puttyPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "putty.exe");
try
{
var startInfo = new ProcessStartInfo(puttyPath, source.IPAddressForSSH);
Process.Start(startInfo);
}
catch (Exception ex)
{
log.Error("Error starting PuTTY.", ex);
using (var dlg = new ThreeButtonDialog(new ThreeButtonDialog.Details(SystemIcons.Error, Messages.ERROR_PUTTY_LAUNCHING, Messages.XENCENTER)))
{
dlg.ShowDialog(Parent);
}
}
}
}
private void UpdateOpenSSHConsoleButtonState()
{
buttonSSH.Visible = IsSSHConsoleButtonShown;
buttonSSH.Enabled = CanStartSSHConsole;
}
private bool IsSSHConsoleButtonShown
{
get
{
return
IsSSHConsoleSupported && source.power_state != vm_power_state.Halted;
}
}
private bool IsSSHConsoleSupported
{
get
{
if (source.IsWindows)
return false;
if (source.IsControlDomainZero)
{
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 true;
}
}
private bool CanStartSSHConsole
{
get
{
return
IsSSHConsoleSupported &&
source.power_state == vm_power_state.Running &&
!string.IsNullOrEmpty(source.IPAddressForSSH);
}
}
#endregion SSH Console methods
}
}