diff --git a/mRemoteNG/Connection/AbstractConnectionRecord.cs b/mRemoteNG/Connection/AbstractConnectionRecord.cs index 6b6b61786..ba68d1cb2 100644 --- a/mRemoteNG/Connection/AbstractConnectionRecord.cs +++ b/mRemoteNG/Connection/AbstractConnectionRecord.cs @@ -295,7 +295,7 @@ namespace mRemoteNG.Connection [LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2), LocalizedAttributes.LocalizedDisplayName(nameof(Language.Domain)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionDomain)), - AttributeUsedInProtocol(ProtocolType.RDP, ProtocolType.IntApp, ProtocolType.PowerShell)] + AttributeUsedInProtocol(ProtocolType.RDP, ProtocolType.IntApp, ProtocolType.PowerShell, ProtocolType.WSL)] public string Domain { get => GetPropertyValue("Domain", _domain).Trim(); diff --git a/mRemoteNG/Connection/ConnectionInfo.cs b/mRemoteNG/Connection/ConnectionInfo.cs index a81e779d3..147e50b97 100644 --- a/mRemoteNG/Connection/ConnectionInfo.cs +++ b/mRemoteNG/Connection/ConnectionInfo.cs @@ -274,6 +274,8 @@ namespace mRemoteNG.Connection return (int)ProtocolHTTPS.Defaults.Port; case ProtocolType.PowerShell: return (int)ProtocolPowerShell.Defaults.Port; + case ProtocolType.WSL: + return (int)ProtocolWSL.Defaults.Port; case ProtocolType.Terminal: return (int)ProtocolTerminal.Defaults.Port; case ProtocolType.IntApp: diff --git a/mRemoteNG/Connection/Protocol/ProtocolFactory.cs b/mRemoteNG/Connection/Protocol/ProtocolFactory.cs index b2761220e..110a4b5db 100644 --- a/mRemoteNG/Connection/Protocol/ProtocolFactory.cs +++ b/mRemoteNG/Connection/Protocol/ProtocolFactory.cs @@ -8,6 +8,7 @@ using mRemoteNG.Connection.Protocol.VNC; using mRemoteNG.Connection.Protocol.ARD; using System; using mRemoteNG.Connection.Protocol.PowerShell; +using mRemoteNG.Connection.Protocol.WSL; using mRemoteNG.Connection.Protocol.Terminal; using mRemoteNG.Resources.Language; using System.Runtime.Versioning; @@ -48,6 +49,8 @@ namespace mRemoteNG.Connection.Protocol return new ProtocolHTTPS(connectionInfo.RenderingEngine); case ProtocolType.PowerShell: return new ProtocolPowerShell(connectionInfo); + case ProtocolType.WSL: + return new ProtocolWSL(connectionInfo); case ProtocolType.Terminal: return new ProtocolTerminal(connectionInfo); case ProtocolType.IntApp: diff --git a/mRemoteNG/Connection/Protocol/ProtocolType.cs b/mRemoteNG/Connection/Protocol/ProtocolType.cs index be856518d..20af662bd 100644 --- a/mRemoteNG/Connection/Protocol/ProtocolType.cs +++ b/mRemoteNG/Connection/Protocol/ProtocolType.cs @@ -38,6 +38,8 @@ namespace mRemoteNG.Connection.Protocol [LocalizedAttributes.LocalizedDescription(nameof(Language.Ard))] ARD = 11, + [LocalizedAttributes.LocalizedDescription(nameof(Language.Wsl))] + WSL = 12, [LocalizedAttributes.LocalizedDescription(nameof(Language.Terminal))] Terminal = 12, @@ -49,6 +51,7 @@ namespace mRemoteNG.Connection.Protocol { public static bool SupportBlankHostname(ProtocolType protocolType) { + return (protocolType == ProtocolType.IntApp || protocolType == ProtocolType.PowerShell || protocolType == ProtocolType.WSL); return (protocolType == ProtocolType.IntApp || protocolType == ProtocolType.PowerShell || protocolType == ProtocolType.Terminal); } } diff --git a/mRemoteNG/Connection/Protocol/WSL/Connection.Protocol.WSL.cs b/mRemoteNG/Connection/Protocol/WSL/Connection.Protocol.WSL.cs new file mode 100644 index 000000000..3a48882a8 --- /dev/null +++ b/mRemoteNG/Connection/Protocol/WSL/Connection.Protocol.WSL.cs @@ -0,0 +1,160 @@ +using System; +using System.Drawing; +using System.IO; +using System.Runtime.Versioning; +using System.Windows.Forms; +using mRemoteNG.App; +using mRemoteNG.Messages; +using mRemoteNG.Resources.Language; + +namespace mRemoteNG.Connection.Protocol.WSL +{ + [SupportedOSPlatform("windows")] + public class ProtocolWSL(ConnectionInfo connectionInfo) : ProtocolBase + { + #region Private Fields + + private IntPtr _handle; + private readonly ConnectionInfo _connectionInfo = connectionInfo; + private ConsoleControl.ConsoleControl _consoleControl; + + #endregion + + #region Public Methods + + public override bool Connect() + { + try + { + // Check if WSL is installed + if (!IsWslInstalled()) + { + Runtime.MessageCollector?.AddMessage(MessageClass.ErrorMsg, + "WSL is not installed on this system. Please install WSL to use this protocol.", true); + return false; + } + + Runtime.MessageCollector?.AddMessage(MessageClass.InformationMsg, + "Attempting to start WSL session.", true); + + _consoleControl = new ConsoleControl.ConsoleControl + { + Dock = DockStyle.Fill, + BackColor = ColorTranslator.FromHtml("#300A24"), // Ubuntu terminal color + ForeColor = Color.White, + IsInputEnabled = true, + Padding = new Padding(0, 20, 0, 0) + }; + + // Path to wsl.exe + string wslExe = @"C:\Windows\System32\wsl.exe"; + + // Build arguments based on connection info + string arguments = BuildWslArguments(); + + _consoleControl.StartProcess(wslExe, arguments); + + while (!_consoleControl.IsHandleCreated) break; + _handle = _consoleControl.Handle; + NativeMethods.SetParent(_handle, InterfaceControl.Handle); + + Resize(this, new EventArgs()); + base.Connect(); + return true; + } + catch (Exception ex) + { + Runtime.MessageCollector?.AddExceptionMessage(Language.ConnectionFailed, ex); + return false; + } + } + + private bool IsWslInstalled() + { + try + { + // Check if wsl.exe exists + string wslPath = @"C:\Windows\System32\wsl.exe"; + if (!File.Exists(wslPath)) + { + return false; + } + + // Additional check: Try to execute wsl.exe --status to verify it's properly installed + // For now, just check if the file exists + return true; + } + catch + { + return false; + } + } + + private string BuildWslArguments() + { + string arguments = ""; + + // If a hostname is specified, treat it as a distribution name + if (!string.IsNullOrEmpty(_connectionInfo.Hostname)) + { + string hostname = _connectionInfo.Hostname.Trim(); + // Check if it's not localhost (WSL doesn't use localhost as a distribution name) + if (!hostname.Equals("localhost", StringComparison.OrdinalIgnoreCase)) + { + arguments = $"-d {hostname}"; + } + } + + // If username is specified, we can try to use it + if (!string.IsNullOrEmpty(_connectionInfo.Username)) + { + arguments += $" -u {_connectionInfo.Username}"; + } + + return arguments.Trim(); + } + + public override void Focus() + { + try + { + NativeMethods.SetForegroundWindow(_handle); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionMessage(Language.IntAppFocusFailed, ex); + } + } + + protected override void Resize(object sender, EventArgs e) + { + try + { + if (InterfaceControl.Size == Size.Empty) return; + // Use ClientRectangle to account for padding (for connection frame color) + Rectangle clientRect = InterfaceControl.ClientRectangle; + NativeMethods.MoveWindow(_handle, + clientRect.X - SystemInformation.FrameBorderSize.Width, + clientRect.Y - (SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height), + clientRect.Width + SystemInformation.FrameBorderSize.Width * 2, + clientRect.Height + SystemInformation.CaptionHeight + + SystemInformation.FrameBorderSize.Height * 2, true); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionMessage(Language.IntAppResizeFailed, ex); + } + } + + #endregion + + #region Enumerations + + public enum Defaults + { + Port = 0 // WSL doesn't use a traditional port + } + + #endregion + } +} diff --git a/mRemoteNG/Icons/WSL.ico b/mRemoteNG/Icons/WSL.ico new file mode 100644 index 000000000..cb8f13a90 Binary files /dev/null and b/mRemoteNG/Icons/WSL.ico differ diff --git a/mRemoteNG/Language/Language.fr.resx b/mRemoteNG/Language/Language.fr.resx index 0ae353b53..926095900 100644 --- a/mRemoteNG/Language/Language.fr.resx +++ b/mRemoteNG/Language/Language.fr.resx @@ -2190,4 +2190,7 @@ Le canal nightly inclut les versions alpha, beta et release candidates. + + WSL + \ No newline at end of file diff --git a/mRemoteNG/Language/Language.pl.resx b/mRemoteNG/Language/Language.pl.resx index b4c3b8d3a..b48f4b455 100644 --- a/mRemoteNG/Language/Language.pl.resx +++ b/mRemoteNG/Language/Language.pl.resx @@ -2396,4 +2396,7 @@ Kanał nocny obejmuje wersje alfa, beta i RC (gotowe do wydania). + + WSL + \ No newline at end of file diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx index a2fdf733b..83b9c5c7f 100644 --- a/mRemoteNG/Language/Language.resx +++ b/mRemoteNG/Language/Language.resx @@ -2550,4 +2550,7 @@ Nightly Channel includes Alphas, Betas & Release Candidates. Purple (Custom) + + WSL + \ No newline at end of file diff --git a/mRemoteNG/Language/Language.ta.resx b/mRemoteNG/Language/Language.ta.resx index 08d2406d3..b6b7befb2 100644 --- a/mRemoteNG/Language/Language.ta.resx +++ b/mRemoteNG/Language/Language.ta.resx @@ -2453,4 +2453,7 @@ + + WSL + \ No newline at end of file diff --git a/mRemoteNG/Language/Language.zh-CN.resx b/mRemoteNG/Language/Language.zh-CN.resx index 683a3ee19..5d23e814c 100644 --- a/mRemoteNG/Language/Language.zh-CN.resx +++ b/mRemoteNG/Language/Language.zh-CN.resx @@ -2117,4 +2117,7 @@ mRemoteNG 将退出并安装更新。 + + WSL + \ No newline at end of file diff --git a/mRemoteNG/mRemoteNG.csproj b/mRemoteNG/mRemoteNG.csproj index c5a2931e5..f92b19332 100644 --- a/mRemoteNG/mRemoteNG.csproj +++ b/mRemoteNG/mRemoteNG.csproj @@ -399,6 +399,9 @@ Always + + Always + Always diff --git a/mRemoteNGTests/UI/Window/ConfigWindowTests/ConfigWindowGeneralTests.cs b/mRemoteNGTests/UI/Window/ConfigWindowTests/ConfigWindowGeneralTests.cs index 3b7675b8c..a617b2f08 100644 --- a/mRemoteNGTests/UI/Window/ConfigWindowTests/ConfigWindowGeneralTests.cs +++ b/mRemoteNGTests/UI/Window/ConfigWindowTests/ConfigWindowGeneralTests.cs @@ -332,6 +332,14 @@ namespace mRemoteNGTests.UI.Window.ConfigWindowTests nameof(ConnectionInfo.Port), }); break; + case ProtocolType.WSL: + expectedProperties.AddRange(new[] + { + nameof(ConnectionInfo.Password), + nameof(ConnectionInfo.Domain), + nameof(ConnectionInfo.Port), + }); + break; case ProtocolType.IntApp: expectedProperties.AddRange(new[] {