diff --git a/CHANGELOG.md b/CHANGELOG.md index 1de0838a0..e63bba606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - #2734: fix native build for Windows-x64 ### Added +- Add configurable connection tab colors to distinguish between different environments (e.g., Dev, Test, Live) - #2728 Add support for building mRemoteNG on Windows ARM64 - #2723: Read keyboardhook, gatewayaccesstoken and gatewaycredentialssource from RDP File - #2690: தமிழ் (ta) Translation update diff --git a/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer28.cs b/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer28.cs index 06bb61246..a12d8ed78 100644 --- a/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer28.cs +++ b/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer28.cs @@ -42,6 +42,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml element.Add(new XAttribute("Descr", connectionInfo.Description)); element.Add(new XAttribute("Icon", connectionInfo.Icon)); element.Add(new XAttribute("Panel", connectionInfo.Panel)); + element.Add(new XAttribute("TabColor", connectionInfo.TabColor)); element.Add(new XAttribute("Id", connectionInfo.ConstantID)); if (!Runtime.UseCredentialManager) @@ -187,6 +188,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml element.Add(new XAttribute("InheritIcon", inheritance.Icon.ToString().ToLowerInvariant())); if (inheritance.Panel) element.Add(new XAttribute("InheritPanel", inheritance.Panel.ToString().ToLowerInvariant())); + if (inheritance.TabColor) + element.Add(new XAttribute("InheritTabColor", inheritance.TabColor.ToString().ToLowerInvariant())); if (inheritance.Password) element.Add(new XAttribute("InheritPassword", inheritance.Password.ToString().ToLowerInvariant())); if (inheritance.Port) diff --git a/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs b/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs index ab6c15a05..7f053ea1d 100644 --- a/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs +++ b/mRemoteNG/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs @@ -328,6 +328,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml connectionInfo.Inheritance.DisplayWallpaper = xmlnode.GetAttributeAsBool("InheritDisplayWallpaper"); connectionInfo.Inheritance.Icon = xmlnode.GetAttributeAsBool("InheritIcon"); connectionInfo.Inheritance.Panel = xmlnode.GetAttributeAsBool("InheritPanel"); + connectionInfo.Inheritance.TabColor = xmlnode.GetAttributeAsBool("InheritTabColor"); connectionInfo.Inheritance.Port = xmlnode.GetAttributeAsBool("InheritPort"); connectionInfo.Inheritance.Protocol = xmlnode.GetAttributeAsBool("InheritProtocol"); connectionInfo.Inheritance.PuttySession = xmlnode.GetAttributeAsBool("InheritPuttySession"); @@ -350,6 +351,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml connectionInfo.Icon = xmlnode.GetAttributeAsString("Icon"); connectionInfo.Panel = xmlnode.GetAttributeAsString("Panel"); + connectionInfo.TabColor = xmlnode.GetAttributeAsString("TabColor"); } else { diff --git a/mRemoteNG/Config/Serializers/MiscSerializers/PortScanDeserializer.cs b/mRemoteNG/Config/Serializers/MiscSerializers/PortScanDeserializer.cs index f80081b8e..4fc09ae24 100644 --- a/mRemoteNG/Config/Serializers/MiscSerializers/PortScanDeserializer.cs +++ b/mRemoteNG/Config/Serializers/MiscSerializers/PortScanDeserializer.cs @@ -61,6 +61,10 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers if (host.Vnc) finalProtocol = ProtocolType.VNC; break; + case ProtocolType.ARD: + if (host.Vnc) + finalProtocol = ProtocolType.ARD; + break; default: protocolValid = false; break; diff --git a/mRemoteNG/Connection/AbstractConnectionRecord.cs b/mRemoteNG/Connection/AbstractConnectionRecord.cs index 9a9fe488b..c354a553d 100644 --- a/mRemoteNG/Connection/AbstractConnectionRecord.cs +++ b/mRemoteNG/Connection/AbstractConnectionRecord.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.ComponentModel; +using System.Drawing; using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol.Http; using mRemoteNG.Connection.Protocol.RDP; @@ -23,6 +24,7 @@ namespace mRemoteNG.Connection private string _icon; private string _panel; private string _color; + private string _tabColor; private string _hostname; private ExternalAddressProvider _externalAddressProvider; @@ -163,6 +165,13 @@ namespace mRemoteNG.Connection { get => GetPropertyValue("Color", _color); set => SetField(ref _color, value, "Color"); + LocalizedAttributes.LocalizedDisplayName(nameof(Language.TabColor)), + LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionTabColor)), + TypeConverter(typeof(MiscTools.TabColorConverter))] + public virtual string TabColor + { + get => GetPropertyValue("TabColor", _tabColor); + set => SetField(ref _tabColor, value, "TabColor"); } #endregion @@ -920,7 +929,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.Compression)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionCompression)), TypeConverter(typeof(MiscTools.EnumTypeConverter)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public ProtocolVNC.Compression VNCCompression { @@ -932,7 +941,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.Encoding)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionEncoding)), TypeConverter(typeof(MiscTools.EnumTypeConverter)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public ProtocolVNC.Encoding VNCEncoding { @@ -944,7 +953,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.AuthenticationMode)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionAuthenticationMode)), TypeConverter(typeof(MiscTools.EnumTypeConverter)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public ProtocolVNC.AuthMode VNCAuthMode { @@ -956,7 +965,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.ProxyType)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionVNCProxyType)), TypeConverter(typeof(MiscTools.EnumTypeConverter)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public ProtocolVNC.ProxyType VNCProxyType { @@ -967,7 +976,7 @@ namespace mRemoteNG.Connection [LocalizedAttributes.LocalizedCategory(nameof(Language.Proxy), 7), LocalizedAttributes.LocalizedDisplayName(nameof(Language.ProxyAddress)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionVNCProxyAddress)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public string VNCProxyIP { @@ -978,7 +987,7 @@ namespace mRemoteNG.Connection [LocalizedAttributes.LocalizedCategory(nameof(Language.Proxy), 7), LocalizedAttributes.LocalizedDisplayName(nameof(Language.ProxyPort)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionVNCProxyPort)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public int VNCProxyPort { @@ -989,7 +998,7 @@ namespace mRemoteNG.Connection [LocalizedAttributes.LocalizedCategory(nameof(Language.Proxy), 7), LocalizedAttributes.LocalizedDisplayName(nameof(Language.ProxyUsername)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionVNCProxyUsername)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public string VNCProxyUsername { @@ -1001,7 +1010,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.ProxyPassword)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionVNCProxyPassword)), PasswordPropertyText(true), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public string VNCProxyPassword { @@ -1013,7 +1022,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.Colors)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionColors)), TypeConverter(typeof(MiscTools.EnumTypeConverter)), - AttributeUsedInProtocol(ProtocolType.VNC), + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD), Browsable(false)] public ProtocolVNC.Colors VNCColors { @@ -1025,7 +1034,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.SmartSizeMode)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionSmartSizeMode)), TypeConverter(typeof(MiscTools.EnumTypeConverter)), - AttributeUsedInProtocol(ProtocolType.VNC)] + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD)] public ProtocolVNC.SmartSizeMode VNCSmartSizeMode { get => GetPropertyValue("VNCSmartSizeMode", _vncSmartSizeMode); @@ -1036,7 +1045,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDisplayName(nameof(Language.ViewOnly)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionViewOnly)), TypeConverter(typeof(MiscTools.YesNoTypeConverter)), - AttributeUsedInProtocol(ProtocolType.VNC)] + AttributeUsedInProtocol(ProtocolType.VNC, ProtocolType.ARD)] public bool VNCViewOnly { get => GetPropertyValue("VNCViewOnly", _vncViewOnly); diff --git a/mRemoteNG/Connection/ConnectionInfo.cs b/mRemoteNG/Connection/ConnectionInfo.cs index 56a2e8838..5e35c1229 100644 --- a/mRemoteNG/Connection/ConnectionInfo.cs +++ b/mRemoteNG/Connection/ConnectionInfo.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using mRemoteNG.App; using mRemoteNG.Connection.Protocol; +using mRemoteNG.Connection.Protocol.ARD; using mRemoteNG.Connection.Protocol.Http; using mRemoteNG.Connection.Protocol.PowerShell; using mRemoteNG.Connection.Protocol.RAW; @@ -254,6 +255,8 @@ namespace mRemoteNG.Connection return (int)RdpProtocol.Defaults.Port; case ProtocolType.VNC: return (int)ProtocolVNC.Defaults.Port; + case ProtocolType.ARD: + return (int)ProtocolARD.Defaults.Port; case ProtocolType.SSH1: return (int)ProtocolSSH1.Defaults.Port; case ProtocolType.SSH2: @@ -290,6 +293,7 @@ namespace mRemoteNG.Connection Icon = Settings.Default.ConDefaultIcon; Panel = Language.General; Color = string.Empty; + TabColor = string.Empty; } private void SetConnectionDefaults() diff --git a/mRemoteNG/Connection/ConnectionInfoInheritance.cs b/mRemoteNG/Connection/ConnectionInfoInheritance.cs index fcf0a2d73..1e0a28485 100644 --- a/mRemoteNG/Connection/ConnectionInfoInheritance.cs +++ b/mRemoteNG/Connection/ConnectionInfoInheritance.cs @@ -55,6 +55,10 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescriptionInherit(nameof(Language.PropertyDescriptionColor)), TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool Color { get; set; } + LocalizedAttributes.LocalizedDisplayNameInherit(nameof(Language.TabColor)), + LocalizedAttributes.LocalizedDescriptionInherit(nameof(Language.PropertyDescriptionTabColor)), + TypeConverter(typeof(MiscTools.YesNoTypeConverter))] + public bool TabColor { get; set; } #endregion diff --git a/mRemoteNG/Connection/Protocol/ARD/ProtocolARD.cs b/mRemoteNG/Connection/Protocol/ARD/ProtocolARD.cs new file mode 100644 index 000000000..d10a7ff9c --- /dev/null +++ b/mRemoteNG/Connection/Protocol/ARD/ProtocolARD.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.Versioning; +using mRemoteNG.Connection.Protocol.VNC; + +namespace mRemoteNG.Connection.Protocol.ARD +{ + [SupportedOSPlatform("windows")] + public class ProtocolARD : ProtocolVNC + { + public ProtocolARD() + { + } + + public new enum Defaults + { + Port = 5900 + } + } +} diff --git a/mRemoteNG/Connection/Protocol/ProtocolFactory.cs b/mRemoteNG/Connection/Protocol/ProtocolFactory.cs index 820724f89..b6d7fbbad 100644 --- a/mRemoteNG/Connection/Protocol/ProtocolFactory.cs +++ b/mRemoteNG/Connection/Protocol/ProtocolFactory.cs @@ -5,6 +5,7 @@ using mRemoteNG.Connection.Protocol.Rlogin; using mRemoteNG.Connection.Protocol.SSH; using mRemoteNG.Connection.Protocol.Telnet; using mRemoteNG.Connection.Protocol.VNC; +using mRemoteNG.Connection.Protocol.ARD; using System; using mRemoteNG.Connection.Protocol.PowerShell; using mRemoteNG.Resources.Language; @@ -28,6 +29,8 @@ namespace mRemoteNG.Connection.Protocol return rdp; case ProtocolType.VNC: return new ProtocolVNC(); + case ProtocolType.ARD: + return new ProtocolARD(); case ProtocolType.SSH1: return new ProtocolSSH1(); case ProtocolType.SSH2: diff --git a/mRemoteNG/Connection/Protocol/ProtocolType.cs b/mRemoteNG/Connection/Protocol/ProtocolType.cs index f4e1bd47e..c795cd8e6 100644 --- a/mRemoteNG/Connection/Protocol/ProtocolType.cs +++ b/mRemoteNG/Connection/Protocol/ProtocolType.cs @@ -35,6 +35,9 @@ namespace mRemoteNG.Connection.Protocol [LocalizedAttributes.LocalizedDescription(nameof(Language.PowerShell))] PowerShell = 10, + [LocalizedAttributes.LocalizedDescription(nameof(Language.Ard))] + ARD = 11, + [LocalizedAttributes.LocalizedDescription(nameof(Language.ExternalTool))] IntApp = 20 } diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx index 0a793b73c..fe2cd4579 100644 --- a/mRemoteNG/Language/Language.resx +++ b/mRemoteNG/Language/Language.resx @@ -159,6 +159,9 @@ Automatic update settings + + ARD (Apple Remote Desktop) + Aspect @@ -1022,6 +1025,8 @@ If you run into such an error, please create a new connection file! Sets the color for the connection or folder in the connections tree. Connections inherit this color from their parent folder. + + Sets the color of the connection tab. Leave empty for default theme color. Enter your password. @@ -1188,6 +1193,9 @@ If you run into such an error, please create a new connection file! Panel + + Tab Color + Password diff --git a/mRemoteNG/Tools/MiscTools.cs b/mRemoteNG/Tools/MiscTools.cs index d35af82a8..c1579691e 100644 --- a/mRemoteNG/Tools/MiscTools.cs +++ b/mRemoteNG/Tools/MiscTools.cs @@ -268,5 +268,131 @@ namespace mRemoteNG.Tools return svc; } } + + public class TabColorConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return sourceType == typeof(string) || sourceType == typeof(Color) || base.CanConvertFrom(context, sourceType); + } + + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + { + return destinationType == typeof(string) || destinationType == typeof(Color) || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object? value) + { + if (value == null || (value is string str && string.IsNullOrWhiteSpace(str))) + { + return string.Empty; + } + + if (value is string stringValue) + { + return stringValue; + } + + if (value is Color colorValue) + { + // Convert Color to string representation + // Use named color if it's a known color, otherwise use hex format + if (colorValue.IsNamedColor) + { + return colorValue.Name; + } + else + { + // Return hex format without alpha if fully opaque, otherwise include alpha + if (colorValue.A == 255) + { + return $"#{colorValue.R:X2}{colorValue.G:X2}{colorValue.B:X2}"; + } + else + { + return $"#{colorValue.A:X2}{colorValue.R:X2}{colorValue.G:X2}{colorValue.B:X2}"; + } + } + } + + return base.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (destinationType == typeof(string)) + { + if (value == null || (value is string str && string.IsNullOrWhiteSpace(str))) + { + return string.Empty; + } + return value.ToString() ?? string.Empty; + } + + if (destinationType == typeof(Color)) + { + if (value == null || (value is string str && string.IsNullOrWhiteSpace(str))) + { + return Color.Empty; + } + + if (value is string stringValue) + { + try + { + ColorConverter converter = new ColorConverter(); + return converter.ConvertFromString(stringValue) ?? Color.Empty; + } + catch + { + return Color.Empty; + } + } + } + + return base.ConvertTo(context, culture, value, destinationType) ?? throw new InvalidOperationException("Base conversion returned null."); + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) + { + return true; + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context) + { + // Provide a list of common colors for the dropdown + Color[] colors = + [ + Color.Red, + Color.Orange, + Color.Yellow, + Color.Green, + Color.Blue, + Color.Purple, + Color.Pink, + Color.Brown, + Color.Black, + Color.White, + Color.Gray, + Color.LightGray, + Color.DarkGray, + Color.Cyan, + Color.Magenta, + Color.Lime, + Color.Navy, + Color.Teal, + Color.Maroon, + Color.Olive + ]; + + return new StandardValuesCollection(colors); + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) + { + // Return false to allow custom values (hex codes or other color names) + return false; + } + } } } \ No newline at end of file diff --git a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs index fb53dd2cb..241c19d8e 100644 --- a/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs +++ b/mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ConnectionInfoPropertyGrid.cs @@ -168,6 +168,7 @@ namespace mRemoteNG.UI.Controls.ConnectionInfoPropertyGrid strHide.AddRange(SpecialRdpExclusions()); break; case ProtocolType.VNC: + case ProtocolType.ARD: strHide.AddRange(SpecialVncExclusions()); break; } diff --git a/mRemoteNG/UI/Tabs/DockPaneStripNG.cs b/mRemoteNG/UI/Tabs/DockPaneStripNG.cs index a149ebbfc..0360d64e6 100644 --- a/mRemoteNG/UI/Tabs/DockPaneStripNG.cs +++ b/mRemoteNG/UI/Tabs/DockPaneStripNG.cs @@ -991,8 +991,11 @@ namespace mRemoteNG.UI.Tabs rectText = DrawHelper.RtlTransform(this, rectText); rectIcon = DrawHelper.RtlTransform(this, rectIcon); - Color activeColor = DockPane.DockPanel.Theme.ColorPalette.TabSelectedActive.Background; - Color lostFocusColor = DockPane.DockPanel.Theme.ColorPalette.TabSelectedInactive.Background; + // Get custom tab color if available + Color? customTabColor = GetCustomTabColor(tab.Content); + + Color activeColor = customTabColor ?? DockPane.DockPanel.Theme.ColorPalette.TabSelectedActive.Background; + Color lostFocusColor = customTabColor ?? DockPane.DockPanel.Theme.ColorPalette.TabSelectedInactive.Background; Color inactiveColor = DockPane.DockPanel.Theme.ColorPalette.MainWindowActive.Background; Color mouseHoverColor = DockPane.DockPanel.Theme.ColorPalette.TabUnselectedHovered.Background; @@ -1056,6 +1059,31 @@ namespace mRemoteNG.UI.Tabs g.DrawIcon(tab.Content.DockHandler.Icon, rectIcon); } + private Color? GetCustomTabColor(IDockContent content) + { + try + { + if (content is ConnectionTab connectionTab) + { + InterfaceControl interfaceControl = InterfaceControl.FindInterfaceControl(connectionTab); + if (interfaceControl?.Info != null) + { + string tabColorStr = interfaceControl.Info.TabColor; + if (!string.IsNullOrEmpty(tabColorStr)) + { + ColorConverter converter = new ColorConverter(); + return (Color)converter.ConvertFromString(tabColorStr); + } + } + } + } + catch + { + // If there's any error parsing the color, just return null to use default + } + return null; + } + private bool m_isMouseDown; protected bool IsMouseDown diff --git a/mRemoteNG/UI/Window/ConnectionWindow.cs b/mRemoteNG/UI/Window/ConnectionWindow.cs index 4c1c16917..506e88e37 100644 --- a/mRemoteNG/UI/Window/ConnectionWindow.cs +++ b/mRemoteNG/UI/Window/ConnectionWindow.cs @@ -776,6 +776,7 @@ namespace mRemoteNG.UI.Window ProtocolBase protocolBase = sender as ProtocolBase; if (!(protocolBase?.InterfaceControl.Parent is ConnectionTab tabPage)) return; if (tabPage.Disposing || tabPage.IsDisposed) return; + if (IsDisposed || Disposing) return; tabPage.protocolClose = true; Invoke(new Action(() => tabPage.Close())); } diff --git a/mRemoteNGDocumentation/user_interface/connections.rst b/mRemoteNGDocumentation/user_interface/connections.rst index 84aa31e41..763730ea2 100644 --- a/mRemoteNGDocumentation/user_interface/connections.rst +++ b/mRemoteNGDocumentation/user_interface/connections.rst @@ -87,6 +87,26 @@ Icon The icon indicates the visual identifier for the connection. Clicking the icon will let you set a different icon for the connection. +Tab Color +--------- + +.. note:: + + The Tab Color property is available in the Display category of the connection properties. + +You can set a custom color for connection tabs to help distinguish between different environments (e.g., Development, Testing, Production). +This can be especially useful when working with critical systems like Live servers, where you want a clear visual reminder. + +To set a tab color: + +1. Select your connection in the Connections panel +2. In the Config panel, expand the **Display** category +3. Find the **Tab Color** property +4. Enter a color name (e.g., "Red", "Green", "Blue") or a hex color code (e.g., "#FF0000", "#00FF00") +5. Leave empty to use the default theme color + +The tab color will be applied when you open the connection. You can use inheritance to set the same color for multiple connections in a folder. + Status ------ diff --git a/mRemoteNGTests/Connection/ConnectionInfoTests.cs b/mRemoteNGTests/Connection/ConnectionInfoTests.cs index 681f772d6..6f8eeffe4 100644 --- a/mRemoteNGTests/Connection/ConnectionInfoTests.cs +++ b/mRemoteNGTests/Connection/ConnectionInfoTests.cs @@ -115,6 +115,7 @@ namespace mRemoteNGTests.Connection [TestCase(ProtocolType.SSH2, ExpectedResult = 22)] [TestCase(ProtocolType.Telnet, ExpectedResult = 23)] [TestCase(ProtocolType.VNC, ExpectedResult = 5900)] + [TestCase(ProtocolType.ARD, ExpectedResult = 5900)] public int GetDefaultPortReturnsCorrectPortForProtocol(ProtocolType protocolType) { _connectionInfo.Protocol = protocolType; diff --git a/mRemoteNGTests/Tools/TabColorConverterTests.cs b/mRemoteNGTests/Tools/TabColorConverterTests.cs new file mode 100644 index 000000000..36c089d5e --- /dev/null +++ b/mRemoteNGTests/Tools/TabColorConverterTests.cs @@ -0,0 +1,148 @@ +using System.Drawing; +using mRemoteNG.Tools; +using NUnit.Framework; + +namespace mRemoteNGTests.Tools +{ + public class TabColorConverterTests + { + private MiscTools.TabColorConverter _converter; + + [SetUp] + public void Setup() + { + _converter = new MiscTools.TabColorConverter(); + } + + [TestCase(typeof(string), true)] + [TestCase(typeof(Color), true)] + public void CanConvertFrom(Type typeToConvertFrom, bool expectedOutcome) + { + var actualOutcome = _converter.CanConvertFrom(typeToConvertFrom); + Assert.That(actualOutcome, Is.EqualTo(expectedOutcome)); + } + + [TestCase(typeof(string), true)] + [TestCase(typeof(Color), true)] + public void CanConvertTo(Type typeToConvertTo, bool expectedOutcome) + { + var actualOutcome = _converter.CanConvertTo(typeToConvertTo); + Assert.That(actualOutcome, Is.EqualTo(expectedOutcome)); + } + + [Test] + public void ConvertFromColorToStringNamedColor() + { + var color = Color.Red; + var result = _converter.ConvertFrom(color); + Assert.That(result, Is.EqualTo("Red")); + } + + [Test] + public void ConvertFromColorToStringCustomColor() + { + var color = Color.FromArgb(255, 128, 64, 32); + var result = _converter.ConvertFrom(color); + Assert.That(result, Is.EqualTo("#80401F")); + } + + [Test] + public void ConvertFromColorToStringCustomColorWithAlpha() + { + var color = Color.FromArgb(128, 255, 0, 0); + var result = _converter.ConvertFrom(color); + Assert.That(result, Is.EqualTo("#80FF0000")); + } + + [Test] + public void ConvertFromStringReturnsString() + { + var colorString = "Blue"; + var result = _converter.ConvertFrom(colorString); + Assert.That(result, Is.EqualTo("Blue")); + } + + [Test] + public void ConvertFromHexStringReturnsString() + { + var colorString = "#FF0000"; + var result = _converter.ConvertFrom(colorString); + Assert.That(result, Is.EqualTo("#FF0000")); + } + + [Test] + public void ConvertFromNullReturnsEmptyString() + { + var result = _converter.ConvertFrom(null); + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void ConvertFromEmptyStringReturnsEmptyString() + { + var result = _converter.ConvertFrom(""); + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void ConvertToStringFromString() + { + var colorString = "Green"; + var result = _converter.ConvertTo(colorString, typeof(string)); + Assert.That(result, Is.EqualTo("Green")); + } + + [Test] + public void ConvertToColorFromNamedString() + { + var colorString = "Red"; + var result = _converter.ConvertTo(colorString, typeof(Color)); + Assert.That(result, Is.EqualTo(Color.Red)); + } + + [Test] + public void ConvertToColorFromHexString() + { + var colorString = "#FF0000"; + var result = _converter.ConvertTo(colorString, typeof(Color)); + var expectedColor = Color.FromArgb(255, 255, 0, 0); + Assert.That(result, Is.EqualTo(expectedColor)); + } + + [Test] + public void ConvertToColorFromEmptyStringReturnsEmpty() + { + var result = _converter.ConvertTo("", typeof(Color)); + Assert.That(result, Is.EqualTo(Color.Empty)); + } + + [Test] + public void ConvertToColorFromNullReturnsEmpty() + { + var result = _converter.ConvertTo(null, typeof(Color)); + Assert.That(result, Is.EqualTo(Color.Empty)); + } + + [Test] + public void GetStandardValuesSupportedReturnsTrue() + { + var result = _converter.GetStandardValuesSupported(null); + Assert.That(result, Is.True); + } + + [Test] + public void GetStandardValuesReturnsColorList() + { + var result = _converter.GetStandardValues(null); + Assert.That(result, Is.Not.Null); + Assert.That(result.Count, Is.GreaterThan(0)); + } + + [Test] + public void GetStandardValuesExclusiveReturnsFalse() + { + var result = _converter.GetStandardValuesExclusive(null); + Assert.That(result, Is.False); + } + } +}