Merge pull request #2954 from mRemoteNG/copilot/add-wsl-connection-protocol

Add WSL protocol support with installation check
This commit is contained in:
Dimitrij
2025-10-19 21:55:47 +01:00
committed by GitHub
13 changed files with 195 additions and 1 deletions

View File

@@ -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();

View File

@@ -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:

View File

@@ -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:

View File

@@ -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);
}
}

View File

@@ -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
}
}

BIN
mRemoteNG/Icons/WSL.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -2190,4 +2190,7 @@ Le canal nightly inclut les versions alpha, beta et release candidates.</value>
<data name="VaultOpenbaoSecretEngineLDAPStatic" type="System.Resources.ResXNullRef, System.Windows.Forms">
<value />
</data>
<data name="Wsl" xml:space="preserve">
<value>WSL</value>
</data>
</root>

View File

@@ -2396,4 +2396,7 @@ Kanał nocny obejmuje wersje alfa, beta i RC (gotowe do wydania).</value>
<data name="VaultOpenbaoSecretEngineLDAPStatic" type="System.Resources.ResXNullRef, System.Windows.Forms">
<value />
</data>
<data name="Wsl" xml:space="preserve">
<value>WSL</value>
</data>
</root>

View File

@@ -2550,4 +2550,7 @@ Nightly Channel includes Alphas, Betas &amp; Release Candidates.</value>
<data name="FrameColorPurple" xml:space="preserve">
<value>Purple (Custom)</value>
</data>
<data name="Wsl" xml:space="preserve">
<value>WSL</value>
</data>
</root>

View File

@@ -2453,4 +2453,7 @@
<data name="VaultOpenbaoSecretEngineLDAPStatic" type="System.Resources.ResXNullRef, System.Windows.Forms">
<value />
</data>
<data name="Wsl" xml:space="preserve">
<value>WSL</value>
</data>
</root>

View File

@@ -2117,4 +2117,7 @@ mRemoteNG 将退出并安装更新。</value>
<data name="VaultOpenbaoSecretEngineLDAPStatic" type="System.Resources.ResXNullRef, System.Windows.Forms">
<value />
</data>
<data name="Wsl" xml:space="preserve">
<value>WSL</value>
</data>
</root>

View File

@@ -399,6 +399,9 @@
<None Update="Icons\PowerShell.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Icons\WSL.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Icons\Production.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

View File

@@ -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[]
{