From e4b4400aa5df8d2c93a559229f249c52c3b044d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:22:29 +0000 Subject: [PATCH 1/7] Initial plan From 435e6f46c10f223d78516d6c2e4904f8894ffc9e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:29:42 +0000 Subject: [PATCH 2/7] Add Terminal protocol support to mRemoteNG Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- .../Connection/AbstractConnectionRecord.cs | 2 +- mRemoteNG/Connection/ConnectionInfo.cs | 3 + .../Connection/Protocol/ProtocolFactory.cs | 3 + mRemoteNG/Connection/Protocol/ProtocolType.cs | 5 +- .../Terminal/Connection.Protocol.Terminal.cs | 130 ++++++++++++++++++ mRemoteNG/Language/Language.resx | 3 + 6 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs diff --git a/mRemoteNG/Connection/AbstractConnectionRecord.cs b/mRemoteNG/Connection/AbstractConnectionRecord.cs index 6b6b6178..c58fbdab 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.Terminal)] public string Domain { get => GetPropertyValue("Domain", _domain).Trim(); diff --git a/mRemoteNG/Connection/ConnectionInfo.cs b/mRemoteNG/Connection/ConnectionInfo.cs index 1ae22813..a81e779d 100644 --- a/mRemoteNG/Connection/ConnectionInfo.cs +++ b/mRemoteNG/Connection/ConnectionInfo.cs @@ -8,6 +8,7 @@ using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol.ARD; using mRemoteNG.Connection.Protocol.Http; using mRemoteNG.Connection.Protocol.PowerShell; +using mRemoteNG.Connection.Protocol.Terminal; using mRemoteNG.Connection.Protocol.RAW; using mRemoteNG.Connection.Protocol.RDP; using mRemoteNG.Connection.Protocol.Rlogin; @@ -273,6 +274,8 @@ namespace mRemoteNG.Connection return (int)ProtocolHTTPS.Defaults.Port; case ProtocolType.PowerShell: return (int)ProtocolPowerShell.Defaults.Port; + case ProtocolType.Terminal: + return (int)ProtocolTerminal.Defaults.Port; case ProtocolType.IntApp: return (int)IntegratedProgram.Defaults.Port; } diff --git a/mRemoteNG/Connection/Protocol/ProtocolFactory.cs b/mRemoteNG/Connection/Protocol/ProtocolFactory.cs index b6d7fbba..b2761220 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.Terminal; using mRemoteNG.Resources.Language; using System.Runtime.Versioning; @@ -47,6 +48,8 @@ namespace mRemoteNG.Connection.Protocol return new ProtocolHTTPS(connectionInfo.RenderingEngine); case ProtocolType.PowerShell: return new ProtocolPowerShell(connectionInfo); + case ProtocolType.Terminal: + return new ProtocolTerminal(connectionInfo); case ProtocolType.IntApp: if (connectionInfo.ExtApp == "") { diff --git a/mRemoteNG/Connection/Protocol/ProtocolType.cs b/mRemoteNG/Connection/Protocol/ProtocolType.cs index c795cd8e..be856518 100644 --- a/mRemoteNG/Connection/Protocol/ProtocolType.cs +++ b/mRemoteNG/Connection/Protocol/ProtocolType.cs @@ -38,6 +38,9 @@ namespace mRemoteNG.Connection.Protocol [LocalizedAttributes.LocalizedDescription(nameof(Language.Ard))] ARD = 11, + [LocalizedAttributes.LocalizedDescription(nameof(Language.Terminal))] + Terminal = 12, + [LocalizedAttributes.LocalizedDescription(nameof(Language.ExternalTool))] IntApp = 20 } @@ -46,7 +49,7 @@ namespace mRemoteNG.Connection.Protocol { public static bool SupportBlankHostname(ProtocolType protocolType) { - return (protocolType == ProtocolType.IntApp || protocolType == ProtocolType.PowerShell); + return (protocolType == ProtocolType.IntApp || protocolType == ProtocolType.PowerShell || protocolType == ProtocolType.Terminal); } } } \ No newline at end of file diff --git a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs new file mode 100644 index 00000000..f66137c8 --- /dev/null +++ b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs @@ -0,0 +1,130 @@ +using System; +using System.Drawing; +using System.Runtime.Versioning; +using System.Windows.Forms; +using mRemoteNG.App; +using mRemoteNG.Messages; +using mRemoteNG.Resources.Language; + +namespace mRemoteNG.Connection.Protocol.Terminal +{ + [SupportedOSPlatform("windows")] + public class ProtocolTerminal(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 + { + Runtime.MessageCollector?.AddMessage(MessageClass.InformationMsg, "Attempting to start Windows Terminal session.", true); + + _consoleControl = new ConsoleControl.ConsoleControl + { + Dock = DockStyle.Fill, + BackColor = ColorTranslator.FromHtml("#012456"), + ForeColor = Color.White, + IsInputEnabled = true, + Padding = new Padding(0, 20, 0, 0) + }; + + // Path to Windows Terminal executable + string terminalExe = @"%LocalAppData%\Microsoft\WindowsApps\wt.exe"; + + // Expand environment variables + terminalExe = Environment.ExpandEnvironmentVariables(terminalExe); + + // Setup arguments based on whether hostname is provided + string arguments = ""; + string hostname = _connectionInfo.Hostname.Trim().ToLower(); + bool useLocalHost = hostname == "" || hostname.Equals("localhost"); + + if (!useLocalHost) + { + // If hostname is provided, try to connect via SSH + string username = _connectionInfo.Username; + if (!string.IsNullOrEmpty(_connectionInfo.Domain)) + { + username = $"{_connectionInfo.Domain}\\{username}"; + } + + if (!string.IsNullOrEmpty(username)) + { + arguments = $"ssh {username}@{_connectionInfo.Hostname}"; + } + else + { + arguments = $"ssh {_connectionInfo.Hostname}"; + } + } + // If no hostname or localhost, just open a local terminal session + + _consoleControl.StartProcess(terminalExe, 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; + } + } + + 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 = 22 + } + + #endregion + } +} diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx index 6b803b06..a2fdf733 100644 --- a/mRemoteNG/Language/Language.resx +++ b/mRemoteNG/Language/Language.resx @@ -2194,6 +2194,9 @@ Nightly Channel includes Alphas, Betas & Release Candidates. PowerShell + + Terminal + Changelog From 895e16d4cbdcafae86a0e53f8eeb4cbcb307df30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:33:20 +0000 Subject: [PATCH 3/7] Add Terminal language entries to all language files Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- mRemoteNG/Language/Language.fr.resx | 3 +++ mRemoteNG/Language/Language.pl.resx | 3 +++ mRemoteNG/Language/Language.ta.resx | 3 +++ mRemoteNG/Language/Language.zh-CN.resx | 3 +++ 4 files changed, 12 insertions(+) diff --git a/mRemoteNG/Language/Language.fr.resx b/mRemoteNG/Language/Language.fr.resx index ea7ed8ff..0ae353b5 100644 --- a/mRemoteNG/Language/Language.fr.resx +++ b/mRemoteNG/Language/Language.fr.resx @@ -2105,6 +2105,9 @@ Le canal nightly inclut les versions alpha, beta et release candidates. PowerShell + + Terminal + Historique des modifications diff --git a/mRemoteNG/Language/Language.pl.resx b/mRemoteNG/Language/Language.pl.resx index a9c3fb64..b4c3b8d3 100644 --- a/mRemoteNG/Language/Language.pl.resx +++ b/mRemoteNG/Language/Language.pl.resx @@ -2152,6 +2152,9 @@ Kanał nocny obejmuje wersje alfa, beta i RC (gotowe do wydania). PowerShell + + Terminal + Dziennik zmian diff --git a/mRemoteNG/Language/Language.ta.resx b/mRemoteNG/Language/Language.ta.resx index c2aec0f8..08d2406d 100644 --- a/mRemoteNG/Language/Language.ta.resx +++ b/mRemoteNG/Language/Language.ta.resx @@ -2155,6 +2155,9 @@ ஆற்றல்ஓடு + + Terminal + மாற்றபதிவு diff --git a/mRemoteNG/Language/Language.zh-CN.resx b/mRemoteNG/Language/Language.zh-CN.resx index 20d5517d..683a3ee1 100644 --- a/mRemoteNG/Language/Language.zh-CN.resx +++ b/mRemoteNG/Language/Language.zh-CN.resx @@ -2074,6 +2074,9 @@ mRemoteNG 将退出并安装更新。 PowerShell + + Terminal + 网关 From 0a381e0a4458665e824cb1e9cb79584b9a6e01c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:34:22 +0000 Subject: [PATCH 4/7] Improve Terminal protocol to use cmd.exe instead of wt.exe Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- .../Terminal/Connection.Protocol.Terminal.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs index f66137c8..636a5aaa 100644 --- a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs +++ b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs @@ -25,7 +25,7 @@ namespace mRemoteNG.Connection.Protocol.Terminal { try { - Runtime.MessageCollector?.AddMessage(MessageClass.InformationMsg, "Attempting to start Windows Terminal session.", true); + Runtime.MessageCollector?.AddMessage(MessageClass.InformationMsg, "Attempting to start Terminal session.", true); _consoleControl = new ConsoleControl.ConsoleControl { @@ -36,11 +36,9 @@ namespace mRemoteNG.Connection.Protocol.Terminal Padding = new Padding(0, 20, 0, 0) }; - // Path to Windows Terminal executable - string terminalExe = @"%LocalAppData%\Microsoft\WindowsApps\wt.exe"; - - // Expand environment variables - terminalExe = Environment.ExpandEnvironmentVariables(terminalExe); + // Path to command prompt or PowerShell - can be configured through options + // Using cmd.exe as default for Terminal protocol + string terminalExe = @"C:\Windows\System32\cmd.exe"; // Setup arguments based on whether hostname is provided string arguments = ""; @@ -58,14 +56,18 @@ namespace mRemoteNG.Connection.Protocol.Terminal if (!string.IsNullOrEmpty(username)) { - arguments = $"ssh {username}@{_connectionInfo.Hostname}"; + arguments = $"/K ssh {username}@{_connectionInfo.Hostname}"; } else { - arguments = $"ssh {_connectionInfo.Hostname}"; + arguments = $"/K ssh {_connectionInfo.Hostname}"; } } - // If no hostname or localhost, just open a local terminal session + else + { + // For local sessions, just start cmd with /K to keep it open + arguments = "/K"; + } _consoleControl.StartProcess(terminalExe, arguments); From 8fc3682cc955ce27130492628572e3dca547666a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:36:52 +0000 Subject: [PATCH 5/7] Address code review feedback - improve Terminal protocol implementation Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- .../Connection/AbstractConnectionRecord.cs | 2 +- .../Terminal/Connection.Protocol.Terminal.cs | 24 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/mRemoteNG/Connection/AbstractConnectionRecord.cs b/mRemoteNG/Connection/AbstractConnectionRecord.cs index c58fbdab..6b6b6178 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, ProtocolType.Terminal)] + AttributeUsedInProtocol(ProtocolType.RDP, ProtocolType.IntApp, ProtocolType.PowerShell)] public string Domain { get => GetPropertyValue("Domain", _domain).Trim(); diff --git a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs index 636a5aaa..d9c1c0c9 100644 --- a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs +++ b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs @@ -36,9 +36,9 @@ namespace mRemoteNG.Connection.Protocol.Terminal Padding = new Padding(0, 20, 0, 0) }; - // Path to command prompt or PowerShell - can be configured through options - // Using cmd.exe as default for Terminal protocol - string terminalExe = @"C:\Windows\System32\cmd.exe"; + // Path to command prompt - dynamically determined from system + // Using COMSPEC environment variable which points to the system's command processor + string terminalExe = Environment.GetEnvironmentVariable("COMSPEC") ?? @"C:\Windows\System32\cmd.exe"; // Setup arguments based on whether hostname is provided string arguments = ""; @@ -48,20 +48,30 @@ namespace mRemoteNG.Connection.Protocol.Terminal if (!useLocalHost) { // If hostname is provided, try to connect via SSH + // Note: Domain field is not used for SSH as it's Windows-specific + // SSH authentication will use standard SSH mechanisms (password prompt, keys, etc.) string username = _connectionInfo.Username; - if (!string.IsNullOrEmpty(_connectionInfo.Domain)) + int port = _connectionInfo.Port; + + // Build SSH command + string sshCommand = "ssh"; + + // Add port if it's not the default SSH port (22) + if (port > 0 && port != 22) { - username = $"{_connectionInfo.Domain}\\{username}"; + sshCommand += $" -p {port}"; } if (!string.IsNullOrEmpty(username)) { - arguments = $"/K ssh {username}@{_connectionInfo.Hostname}"; + sshCommand += $" {username}@{_connectionInfo.Hostname}"; } else { - arguments = $"/K ssh {_connectionInfo.Hostname}"; + sshCommand += $" {_connectionInfo.Hostname}"; } + + arguments = $"/K {sshCommand}"; } else { From a8efba1ed4722874e8d7f05fdbd4afa7cedd7f1d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:38:36 +0000 Subject: [PATCH 6/7] Fix handle creation wait loop in Terminal protocol Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- .../Terminal/Connection.Protocol.Terminal.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs index d9c1c0c9..d9542d2a 100644 --- a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs +++ b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs @@ -81,7 +81,20 @@ namespace mRemoteNG.Connection.Protocol.Terminal _consoleControl.StartProcess(terminalExe, arguments); - while (!_consoleControl.IsHandleCreated) break; + // Wait for the console control to create its handle + int maxWaitMs = 5000; // 5 seconds timeout + int startTicks = Environment.TickCount; + while (!_consoleControl.IsHandleCreated && + Environment.TickCount < startTicks + maxWaitMs) + { + System.Threading.Thread.Sleep(10); + } + + if (!_consoleControl.IsHandleCreated) + { + throw new Exception("Console control failed to create handle within timeout period"); + } + _handle = _consoleControl.Handle; NativeMethods.SetParent(_handle, InterfaceControl.Handle); From f983f5de02e4b9427ad44647eda586ba3b2e8ca2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:40:22 +0000 Subject: [PATCH 7/7] Improve timeout handling and error messaging in Terminal protocol Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- .../Protocol/Terminal/Connection.Protocol.Terminal.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs index d9542d2a..4116975a 100644 --- a/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs +++ b/mRemoteNG/Connection/Protocol/Terminal/Connection.Protocol.Terminal.cs @@ -83,16 +83,16 @@ namespace mRemoteNG.Connection.Protocol.Terminal // Wait for the console control to create its handle int maxWaitMs = 5000; // 5 seconds timeout - int startTicks = Environment.TickCount; + long startTicks = Environment.TickCount64; while (!_consoleControl.IsHandleCreated && - Environment.TickCount < startTicks + maxWaitMs) + Environment.TickCount64 < startTicks + maxWaitMs) { - System.Threading.Thread.Sleep(10); + System.Threading.Thread.Sleep(50); } if (!_consoleControl.IsHandleCreated) { - throw new Exception("Console control failed to create handle within timeout period"); + throw new Exception("Failed to initialize terminal console within 5 seconds. This may indicate system resource constraints or permission issues."); } _handle = _consoleControl.Handle;