diff --git a/mRemoteNG/App/Startup.cs b/mRemoteNG/App/Startup.cs
index 5ac62825..f00b85a6 100644
--- a/mRemoteNG/App/Startup.cs
+++ b/mRemoteNG/App/Startup.cs
@@ -1,5 +1,4 @@
using System;
-using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Versioning;
@@ -8,6 +7,7 @@ using mRemoteNG.App.Info;
using mRemoteNG.App.Initialization;
using mRemoteNG.App.Update;
using mRemoteNG.Config.Connections.Multiuser;
+using mRemoteNG.Config.Settings.Registry;
using mRemoteNG.Connection;
using mRemoteNG.Messages;
using mRemoteNG.Properties;
@@ -22,6 +22,7 @@ namespace mRemoteNG.App
[SupportedOSPlatform("windows")]
public class Startup
{
+ private RegistryLoader _RegistryLoader;
private AppUpdater _appUpdate;
private readonly ConnectionIconLoader _connectionIconLoader;
private readonly FrmMain _frmMain = FrmMain.Default;
@@ -30,7 +31,8 @@ namespace mRemoteNG.App
private Startup()
{
- _appUpdate = new AppUpdater();
+ _RegistryLoader = RegistryLoader.Instance; //created instance
+ _appUpdate = new AppUpdater();
_connectionIconLoader = new ConnectionIconLoader(GeneralAppInfo.HomePath + "\\Icons\\");
}
diff --git a/mRemoteNG/Config/Settings/Registry/RegistryLoader.cs b/mRemoteNG/Config/Settings/Registry/RegistryLoader.cs
new file mode 100644
index 00000000..90d0688f
--- /dev/null
+++ b/mRemoteNG/Config/Settings/Registry/RegistryLoader.cs
@@ -0,0 +1,187 @@
+using mRemoteNG.App;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.Versioning;
+using System.Threading.Tasks;
+
+namespace mRemoteNG.Config.Settings.Registry
+{
+ [SupportedOSPlatform("windows")]
+ public class RegistryLoader : IDisposable
+ {
+ ///
+ /// This instance is used to load registry settings at program startup.cs, ensuring that
+ /// these settings are applied to regular configurations and can override them as needed.
+ /// Once the settings are loaded, this lazy instance will primarily be used for configuring
+ /// the options page. After this initialization phase, the lazy instance may no longer be necessary and get disposed.
+ ///
+ private static readonly Lazy instance =
+ new(() => new RegistryLoader());
+
+ ///
+ /// Singleton instance of the RegistryMultiLoader class.
+ ///
+ public static RegistryLoader Instance => instance.Value;
+
+ ///
+ /// Dictionary to store settings for all registry pages.
+ ///
+ public static ConcurrentDictionary RegistrySettings { get; private set; }
+
+ ///
+ /// Static constructor that initializes the registry settings dictionary
+ /// and starts loading settings asynchronously at startup.
+ ///
+ static RegistryLoader()
+ {
+ RegistrySettings = new ConcurrentDictionary();
+ // Start loading asynchronously on startup
+ _ = LoadAllAsync();
+ }
+
+ ///
+ /// Cleans up the registry settings by removing entries of a specified type.
+ ///
+ /// The type of entries to remove from the dictionary.
+ ///
+ /// If the dictionary becomes empty after removal and the singleton instance has been created,
+ /// it disposes of the instance to free resources.
+ ///
+ public static void Cleanup(Type deleteEntries)
+ {
+ // Remove the registry setting from the dictionary
+ RegistrySettings?.TryRemove(deleteEntries, out _);
+
+ if (RegistrySettings.IsEmpty && instance.IsValueCreated)
+ {
+ Instance.Dispose();
+ }
+ }
+
+ ///
+ /// Asynchronously loads and applies multiple registry classes in parallel.
+ ///
+ /// A task representing the asynchronous operation.
+ ///
+ /// This method creates a list of tasks, each responsible for initializing
+ /// and applying a specific registry class. It awaits the completion of all tasks
+ /// using Task.WhenAll.
+ ///
+ private static async Task LoadAllAsync()
+ {
+#if DEBUG
+ Stopwatch stopwatch = Stopwatch.StartNew();
+#endif
+
+ try
+ {
+ // Create a list of tasks to initialize and apply each registry class asynchronously
+ var tasks = new List
+ {
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync(),
+ LoadAndApplyAsync()
+ };
+
+ // Await all tasks to complete
+ await Task.WhenAll(tasks);
+
+ Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
+ $"Registry settings loaded and applied asynchronously in {typeof(RegistryLoader).Name}", true);
+ }
+ catch (Exception ex)
+ {
+ Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
+ $"Registry settings error during load: {ex.Message}", true);
+ }
+
+#if DEBUG
+ stopwatch.Stop();
+ Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
+ $"Registry settings total async load time: {stopwatch.ElapsedMilliseconds} ms", true);
+#endif
+ }
+
+ ///
+ /// Asynchronously initializes and applies registry settings for a specified type.
+ ///
+ /// The type of the registry settings class, which must have a parameterless constructor.
+ /// A task representing the asynchronous operation.
+ ///
+ /// This method creates an instance of the specified type T, stores it in a dictionary
+ /// using its type as the key, and completes the task.
+ ///
+ private static async Task LoadAndApplyAsync() where T : new()
+ {
+ try
+ {
+ // create an instance of setting
+ var instance = new T();
+
+ // Store the instance in the dictionary using its type as key
+ RegistrySettings[typeof(T)] = instance;
+
+ // complete task
+ await Task.CompletedTask;
+ }
+ catch (Exception ex)
+ {
+ Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
+ $"Registry settings error during load {typeof(T).Name}: {ex.Message}", true);
+ }
+ }
+
+ #region IDisposable Support
+
+ ///
+ /// Implements the IDisposable pattern for resource cleanup.
+ ///
+ private bool disposedValue = false; // To detect redundant calls
+
+ ///
+ /// Releases the resources used by the object.
+ ///
+ public void Dispose()
+ {
+ if (disposedValue)
+ return;
+
+ Dispose(true);
+ GC.SuppressFinalize(this);
+
+ disposedValue = true;
+
+ Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
+ $"Registry settings lazy instance of {typeof(RegistryLoader).Name} has been disposed.", true);
+ }
+
+ ///
+ /// Performs the actual resource cleanup.
+ ///
+ /// True if managed resources should be released.
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ RegistrySettings?.Clear();
+ }
+
+ ///
+ /// Finalizer that is called to free unmanaged resources.
+ ///
+ ~RegistryLoader()
+ {
+ Dispose(false);
+ }
+
+ #endregion IDisposable Support
+ }
+}
diff --git a/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs b/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs
index 6e764943..ad3985b4 100644
--- a/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs
+++ b/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs
@@ -300,6 +300,9 @@ namespace mRemoteNG.Tools.WindowsRegistry
AllowedValues = allowedValues;
}
+ if (ElementType == typeof(string) && privateValue != null)
+ privateValue = EnforceStringInputValidity(privateValue);
+
return this;
}