From 1d11df6231e0768c58ac2655fd6824f0a022d4c2 Mon Sep 17 00:00:00 2001 From: Eran Markus Date: Sat, 6 Jun 2026 12:48:22 +0300 Subject: [PATCH] feat: allow disabling RDP idle timeout (set to 0 = never disconnect) The RDP idle timeout was clamped to 10-240, and the config layer's getter forced any value below 10 up to 10, so entering 0 or -1 silently became 10. Users running long-lived sessions had no way to stop idle disconnects. Treat 0 as "disabled": RdpTimeOutOptions.IdleTimeout and FavoriteConfigurationElement.IdleTimeout now allow 0 to round-trip (it maps to MsRdpClient AdvancedSettings.MinutesToIdleTimeout = 0, i.e. no timeout). Positive values are still clamped to the MsRdpClient range 10-240. Also aligned the config getter's upper bound to 240 (was an inconsistent 600). The RDP Extended settings label now reads "Idle Timeout (0=off)". Adds unit tests for both clamping layers (0/-1 -> 0, <10 -> 10, >240 -> 240, in-range preserved, default 240). Fixes #225 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../FavoriteConfigurationElement.cs | 13 +++-- .../RdpExtendedSettingsControl.Designer.cs | 4 +- .../RdpTimeOutOptions.cs | 8 ++- .../FavoriteConfigurationElementTests.cs | 32 +++++++++++ .../Connections/RdpTimeOutOptionsTests.cs | 56 +++++++++++++++++++ Source/Tests/Tests.csproj | 1 + 6 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 Source/Tests/Connections/RdpTimeOutOptionsTests.cs diff --git a/Source/Terminals.Common/Configuration/FavoriteConfigurationElement.cs b/Source/Terminals.Common/Configuration/FavoriteConfigurationElement.cs index b7880e25..4f7483a3 100644 --- a/Source/Terminals.Common/Configuration/FavoriteConfigurationElement.cs +++ b/Source/Terminals.Common/Configuration/FavoriteConfigurationElement.cs @@ -297,20 +297,21 @@ public Int32 IdleTimeout get { Int32 val = (Int32)this["idleTimeout"]; - if (val > 600) - val = 600; + if (val > 240) + val = 240; - if (val < 10) - val = 10; + // 0 means the idle timeout is disabled (never disconnect), so keep it as is. + if (val < 0) + val = 0; return val; } set { - if (value > 240) + if (value > 240) value = 240; - if (value < 0) + if (value < 0) value = 0; this["idleTimeout"] = value; diff --git a/Source/Terminals.Plugins.Rdp/RdpExtendedSettingsControl.Designer.cs b/Source/Terminals.Plugins.Rdp/RdpExtendedSettingsControl.Designer.cs index 64d9dcde..e4e47817 100644 --- a/Source/Terminals.Plugins.Rdp/RdpExtendedSettingsControl.Designer.cs +++ b/Source/Terminals.Plugins.Rdp/RdpExtendedSettingsControl.Designer.cs @@ -122,9 +122,9 @@ private void InitializeComponent() this.label26.AutoSize = true; this.label26.Location = new System.Drawing.Point(239, 167); this.label26.Name = "label26"; - this.label26.Size = new System.Drawing.Size(68, 13); + this.label26.Size = new System.Drawing.Size(116, 13); this.label26.TabIndex = 33; - this.label26.Text = "Idle Timeout:"; + this.label26.Text = "Idle Timeout (0=off):"; // // IdleTimeoutMinutesTextBox // diff --git a/Source/Terminals.Plugins.Rdp/RdpTimeOutOptions.cs b/Source/Terminals.Plugins.Rdp/RdpTimeOutOptions.cs index f1915c66..43553856 100644 --- a/Source/Terminals.Plugins.Rdp/RdpTimeOutOptions.cs +++ b/Source/Terminals.Plugins.Rdp/RdpTimeOutOptions.cs @@ -55,17 +55,19 @@ public Int32 ConnectionTimeout private int idleTimeout = 240; /// - /// Gets or sets the value in range 10 - 600, default is 240 + /// Gets or sets the idle timeout in minutes. Valid range is 10 - 240, default is 240. + /// A value of 0 disables the idle timeout, so the session is never disconnected for + /// being idle (maps to MsRdpClient AdvancedSettings.MinutesToIdleTimeout = 0). /// public Int32 IdleTimeout { get { - return CorrectValueToInterval(10, 240, idleTimeout); + return idleTimeout <= 0 ? 0 : CorrectValueToInterval(10, 240, idleTimeout); } set { - idleTimeout = CorrectValueToInterval(10, 240, value); + idleTimeout = value <= 0 ? 0 : CorrectValueToInterval(10, 240, value); } } diff --git a/Source/Tests/Configuration/FavoriteConfigurationElementTests.cs b/Source/Tests/Configuration/FavoriteConfigurationElementTests.cs index 172e5eb1..8ab936c5 100644 --- a/Source/Tests/Configuration/FavoriteConfigurationElementTests.cs +++ b/Source/Tests/Configuration/FavoriteConfigurationElementTests.cs @@ -110,6 +110,38 @@ public void WithCredential_ResolveUserName_ReturnsCredentialUserName() Assert.AreEqual(EXPECTED_USER, favoriteSecurity.ResolveUserName(), "UserName is primary resolved from Credential."); } + [TestMethod] + public void IdleTimeoutZero_SetGet_StaysZeroToDisableTimeout() + { + FavoriteConfigurationElement favorite = this.CreateFavorite(); + favorite.IdleTimeout = 0; + Assert.AreEqual(0, favorite.IdleTimeout, "0 means the idle timeout is disabled and must survive a set/get round-trip (issue #225)."); + } + + [TestMethod] + public void IdleTimeoutNegative_SetGet_NormalizedToZero() + { + FavoriteConfigurationElement favorite = this.CreateFavorite(); + favorite.IdleTimeout = -1; + Assert.AreEqual(0, favorite.IdleTimeout, "Negative idle timeout is normalized to 0 (disabled), not forced up to the old minimum of 10."); + } + + [TestMethod] + public void IdleTimeoutAboveMaximum_SetGet_ClampedToMaximum() + { + FavoriteConfigurationElement favorite = this.CreateFavorite(); + favorite.IdleTimeout = 1000; + Assert.AreEqual(240, favorite.IdleTimeout, "Idle timeout is clamped to the MsRdpClient maximum of 240 minutes."); + } + + [TestMethod] + public void IdleTimeoutWithinRange_SetGet_KeepsValue() + { + FavoriteConfigurationElement favorite = this.CreateFavorite(); + favorite.IdleTimeout = 120; + Assert.AreEqual(120, favorite.IdleTimeout, "A valid idle timeout has to be preserved."); + } + private FavoriteConfigurationSecurity CreateFavoriteConfigurationSecurity() { FavoriteConfigurationElement favorite = this.CreteFavoriteWithCredential(); diff --git a/Source/Tests/Connections/RdpTimeOutOptionsTests.cs b/Source/Tests/Connections/RdpTimeOutOptionsTests.cs new file mode 100644 index 00000000..edc1c8d5 --- /dev/null +++ b/Source/Tests/Connections/RdpTimeOutOptionsTests.cs @@ -0,0 +1,56 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Terminals.Data; + +namespace Tests.Connections +{ + /// + /// Tests for issue #225: it has to be possible to disable the RDP idle timeout + /// (never disconnect an idle session) by setting it to 0. Previously any value + /// below 10 was forced up to 10, so 0 and -1 silently became 10. + /// + [TestClass] + public class RdpTimeOutOptionsTests + { + [TestMethod] + public void IdleTimeoutZero_SetGet_StaysZeroToDisableTimeout() + { + var options = new RdpTimeOutOptions { IdleTimeout = 0 }; + Assert.AreEqual(0, options.IdleTimeout, "0 disables the idle timeout and must not be forced up to the minimum."); + } + + [TestMethod] + public void IdleTimeoutNegative_SetGet_NormalizedToZero() + { + var options = new RdpTimeOutOptions { IdleTimeout = -5 }; + Assert.AreEqual(0, options.IdleTimeout, "A negative idle timeout is normalized to 0 (disabled)."); + } + + [TestMethod] + public void IdleTimeoutBelowMinimumButPositive_SetGet_ClampedToMinimum() + { + var options = new RdpTimeOutOptions { IdleTimeout = 5 }; + Assert.AreEqual(10, options.IdleTimeout, "A positive value below the minimum is still clamped to 10; only 0 disables the timeout."); + } + + [TestMethod] + public void IdleTimeoutAboveMaximum_SetGet_ClampedToMaximum() + { + var options = new RdpTimeOutOptions { IdleTimeout = 1000 }; + Assert.AreEqual(240, options.IdleTimeout, "Idle timeout is clamped to the MsRdpClient maximum of 240 minutes."); + } + + [TestMethod] + public void IdleTimeoutWithinRange_SetGet_KeepsValue() + { + var options = new RdpTimeOutOptions { IdleTimeout = 120 }; + Assert.AreEqual(120, options.IdleTimeout, "A valid idle timeout has to be preserved."); + } + + [TestMethod] + public void DefaultIdleTimeout_IsTwoHundredForty() + { + var options = new RdpTimeOutOptions(); + Assert.AreEqual(240, options.IdleTimeout, "The default idle timeout is unchanged at 240 minutes."); + } + } +} diff --git a/Source/Tests/Tests.csproj b/Source/Tests/Tests.csproj index 4c76e091..875e9fb0 100644 --- a/Source/Tests/Tests.csproj +++ b/Source/Tests/Tests.csproj @@ -131,6 +131,7 @@ + Component