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