diff --git a/PowerToys.slnx b/PowerToys.slnx
index 1f2a1fdbe9b8..7bbf7ac4179a 100644
--- a/PowerToys.slnx
+++ b/PowerToys.slnx
@@ -234,6 +234,7 @@
+
diff --git a/publish-settings-dependencies.ps1 b/publish-settings-dependencies.ps1
new file mode 100644
index 000000000000..3aacfd53b840
--- /dev/null
+++ b/publish-settings-dependencies.ps1
@@ -0,0 +1,38 @@
+# Publish dependencies for Settings.UI with RuntimeIdentifier=win-x64
+
+. "$PSScriptRoot\tools\build\build-common.ps1"
+
+# Initialize Visual Studio dev environment
+if (-not (Ensure-VsDevEnvironment)) {
+ Write-Error "Failed to initialize VS dev environment"
+ exit 1
+}
+
+$Platform = "x64"
+$Configuration = "Release"
+$RuntimeId = "win-x64"
+
+$projects = @(
+ "src\common\Common.Search\Common.Search.csproj",
+ "src\common\LanguageModelProvider\LanguageModelProvider.csproj",
+ "src\common\AllExperiments\AllExperiments.csproj",
+ "src\common\Common.UI\Common.UI.csproj",
+ "src\common\ManagedCommon\ManagedCommon.csproj",
+ "src\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj"
+)
+
+foreach ($project in $projects) {
+ $projectName = Split-Path $project -Leaf
+ Write-Host "Publishing $projectName..." -ForegroundColor Cyan
+
+ $args = "-t:Publish -p:Configuration=$Configuration -p:Platform=$Platform -p:RuntimeIdentifier=$RuntimeId"
+ RunMSBuild $project $args $Platform $Configuration
+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to publish $projectName"
+ exit $LASTEXITCODE
+ }
+}
+
+Write-Host "`nAll dependencies published successfully!" -ForegroundColor Green
+Write-Host "You can now publish Settings.UI" -ForegroundColor Green
diff --git a/src/Common.Dotnet.AotCompatibility.props b/src/Common.Dotnet.AotCompatibility.props
index bebb88428c43..489535ef7bf8 100644
--- a/src/Common.Dotnet.AotCompatibility.props
+++ b/src/Common.Dotnet.AotCompatibility.props
@@ -8,6 +8,9 @@
- IL2081;CsWinRT1028;CA1416;$(WarningsNotAsErrors)
+
+
+
+ IL2026;IL2067;IL2070;IL2072;IL2075;IL2081;IL2087;IL2098;IL3000;IL3002;IL3050;CsWinRT1028;CA1416;$(WarningsNotAsErrors)
diff --git a/src/modules/MouseWithoutBorders/App/Class/MouseWithoutBordersIpcServer.cs b/src/modules/MouseWithoutBorders/App/Class/MouseWithoutBordersIpcServer.cs
new file mode 100644
index 000000000000..70182358d628
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Class/MouseWithoutBordersIpcServer.cs
@@ -0,0 +1,170 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Pipes;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using MouseWithoutBorders.Class;
+using Logger = MouseWithoutBorders.Core.Logger;
+
+#pragma warning disable SA1649 // File name should match first type name
+
+namespace MouseWithoutBorders.Class;
+
+///
+/// Command types for IPC protocol.
+/// Must match client-side enum in Settings.UI\Helpers\MouseWithoutBordersIpcClient.cs
+///
+internal enum IpcCommandType : byte
+{
+ Shutdown = 1,
+ Reconnect = 2,
+ GenerateNewKey = 3,
+ ConnectToMachine = 4,
+ RequestMachineSocketState = 5,
+}
+
+///
+/// AOT-compatible IPC server for MouseWithoutBorders Settings communication.
+/// Replaces StreamJsonRpc with manual NamedPipe protocol.
+///
+internal sealed class MouseWithoutBordersIpcServer
+{
+ private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { WriteIndented = false };
+
+ private readonly ISettingsSyncHandler _handler;
+
+ public MouseWithoutBordersIpcServer(ISettingsSyncHandler handler)
+ {
+ _handler = handler ?? throw new ArgumentNullException(nameof(handler));
+ }
+
+ ///
+ /// Handles a single client connection
+ ///
+ public async Task HandleClientAsync(Stream stream, CancellationToken cancellationToken)
+ {
+ using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
+ using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
+
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested && stream.CanRead)
+ {
+ // Read command type (1 byte)
+ var commandByte = reader.ReadByte();
+ var command = (IpcCommandType)commandByte;
+
+ switch (command)
+ {
+ case IpcCommandType.Shutdown:
+ _handler.Shutdown();
+ break;
+
+ case IpcCommandType.Reconnect:
+ _handler.Reconnect();
+ break;
+
+ case IpcCommandType.GenerateNewKey:
+ _handler.GenerateNewKey();
+ break;
+
+ case IpcCommandType.ConnectToMachine:
+ {
+ var machineName = ReadString(reader);
+ var securityKey = ReadString(reader);
+ _handler.ConnectToMachine(machineName, securityKey);
+ }
+
+ break;
+
+ case IpcCommandType.RequestMachineSocketState:
+ {
+ var states = await _handler.RequestMachineSocketStateAsync();
+ var json = JsonSerializer.Serialize(states, JsonOptions);
+ WriteString(writer, json);
+ await stream.FlushAsync(cancellationToken);
+ }
+
+ break;
+
+ default:
+ Logger.Log($"Unknown IPC command: {commandByte}");
+ return; // Invalid command, close connection
+ }
+ }
+ }
+ catch (EndOfStreamException)
+ {
+ // Client disconnected, normal termination
+ }
+ catch (IOException)
+ {
+ // Pipe broken, normal termination
+ }
+ catch (Exception ex)
+ {
+ Logger.Log($"IPC error: {ex}");
+ }
+ }
+
+ ///
+ /// Reads a length-prefixed UTF-8 string
+ ///
+ private static string ReadString(BinaryReader reader)
+ {
+ var length = reader.ReadInt32();
+ if (length <= 0 || length > 1024 * 1024)
+ {
+ return string.Empty;
+ }
+
+ var bytes = reader.ReadBytes(length);
+ return Encoding.UTF8.GetString(bytes);
+ }
+
+ ///
+ /// Writes a length-prefixed UTF-8 string
+ ///
+ private static void WriteString(BinaryWriter writer, string value)
+ {
+ var bytes = Encoding.UTF8.GetBytes(value);
+ writer.Write(bytes.Length);
+ writer.Write(bytes);
+ }
+}
+
+///
+/// Interface for handling IPC commands.
+/// Implemented by SettingsSyncHelper in Program.cs
+///
+internal interface ISettingsSyncHandler
+{
+ void Shutdown();
+
+ void Reconnect();
+
+ void GenerateNewKey();
+
+ void ConnectToMachine(string machineName, string securityKey);
+
+ Task RequestMachineSocketStateAsync();
+}
+
+///
+/// Machine socket state for serialization.
+/// Uses SocketStatus from SocketStuff.cs in MouseWithoutBorders.Class namespace.
+///
+public struct MachineSocketState
+{
+ public string Name { get; set; }
+
+ public MouseWithoutBorders.Class.SocketStatus Status { get; set; }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Class/Program.cs b/src/modules/MouseWithoutBorders/App/Class/Program.cs
index 23513e1515e2..a5a4cb7b00e4 100644
--- a/src/modules/MouseWithoutBorders/App/Class/Program.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/Program.cs
@@ -19,6 +19,7 @@
using System.IO;
using System.IO.Pipes;
using System.Linq;
+using System.Security.AccessControl;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Principal;
using System.ServiceModel.Channels;
@@ -276,7 +277,7 @@ public struct MachineSocketState
Task RequestMachineSocketStateAsync();
}
- private sealed class SettingsSyncHelper : ISettingsSyncHelper
+ private sealed class SettingsSyncHelper : ISettingsSyncHelper, ISettingsSyncHandler
{
public Task RequestMachineSocketStateAsync()
{
@@ -299,6 +300,28 @@ private sealed class SettingsSyncHelper : ISettingsSyncHelper
return Task.FromResult(machineStates.Select((state) => new ISettingsSyncHelper.MachineSocketState { Name = state.Key, Status = state.Value }).ToArray());
}
+ // ISettingsSyncHandler implementation (AOT-compatible)
+ Task ISettingsSyncHandler.RequestMachineSocketStateAsync()
+ {
+ var machineStates = new Dictionary();
+ if (Common.Sk == null || Common.Sk.TcpSockets == null)
+ {
+ return Task.FromResult(Array.Empty());
+ }
+
+ foreach (var client in Common.Sk.TcpSockets
+ .Where(t => t != null && t.IsClient && !string.IsNullOrEmpty(t.MachineName)))
+ {
+ var exists = machineStates.TryGetValue(client.MachineName, out var existingStatus);
+ if (!exists || existingStatus == SocketStatus.NA)
+ {
+ machineStates[client.MachineName] = client.Status;
+ }
+ }
+
+ return Task.FromResult(machineStates.Select((state) => new MachineSocketState { Name = state.Key, Status = state.Value }).ToArray());
+ }
+
public void ConnectToMachine(string pcName, string securityKey)
{
Setting.Values.PauseInstantSaving = true;
@@ -379,7 +402,64 @@ internal static void StartSettingSyncThread()
var serverTaskCancellationSource = new CancellationTokenSource();
CancellationToken cancellationToken = serverTaskCancellationSource.Token;
+ // Use AOT-compatible IPC server if available, otherwise use StreamJsonRpc
+#if BUILD_INFO_PUBLISH_AOT || true // Enable for all builds
+ StartAotCompatibleIpcServer("MouseWithoutBorders/SettingsSync", cancellationToken);
+#else
IpcChannel.StartIpcServer("MouseWithoutBorders/SettingsSync", cancellationToken);
+#endif
+ }
+
+ private static void StartAotCompatibleIpcServer(string pipeName, CancellationToken cancellationToken)
+ {
+ var handler = new SettingsSyncHelper();
+ var server = new MouseWithoutBordersIpcServer(handler);
+
+ _ = Task.Factory.StartNew(
+ async () =>
+ {
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ using (var serverPipe = NamedPipeServerStreamAcl.Create(
+ pipeName,
+ PipeDirection.InOut,
+ NamedPipeServerStream.MaxAllowedServerInstances,
+ PipeTransmissionMode.Byte,
+ PipeOptions.Asynchronous,
+ 0,
+ 0,
+ CreatePipeSecurity()))
+ {
+ await serverPipe.WaitForConnectionAsync(cancellationToken);
+ await server.HandleClientAsync(serverPipe, cancellationToken);
+ }
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ // Normal shutdown
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+ },
+ cancellationToken,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
+ }
+
+ private static PipeSecurity CreatePipeSecurity()
+ {
+ var securityIdentifier = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
+ var pipeSecurity = new PipeSecurity();
+ pipeSecurity.AddAccessRule(new PipeAccessRule(
+ securityIdentifier,
+ PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance,
+ AccessControlType.Allow));
+ return pipeSecurity;
}
internal static void StartInputCallbackThread()
diff --git a/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs b/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs
index 575c9582df65..b7095f59cdcc 100644
--- a/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs
@@ -51,7 +51,11 @@
namespace MouseWithoutBorders.Class
{
- internal enum SocketStatus : int
+ ///
+ /// Socket status enumeration - made public for IPC serialization.
+ /// Must match Settings.UI.Library\MouseWithoutBordersIpcModels.cs
+ ///
+ public enum SocketStatus : int
{
NA = 0,
Resolving = 1,
diff --git a/src/modules/MouseWithoutBorders/App/Core/Common.cs b/src/modules/MouseWithoutBorders/App/Core/Common.cs
index 3c2206f2ab11..0bce717e42f6 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Common.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Common.cs
@@ -24,6 +24,7 @@
using MouseWithoutBorders.Exceptions;
using Clipboard = MouseWithoutBorders.Core.Clipboard;
+using SocketStatus = MouseWithoutBorders.Class.SocketStatus;
using Thread = MouseWithoutBorders.Core.Thread;
// Log is enough
diff --git a/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj b/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
index ea4586f2175d..96af8c0ee827 100644
--- a/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
+++ b/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
@@ -1,6 +1,11 @@
+
+
+
+ true
+
WinExe
diff --git a/src/settings-ui/Settings.UI.Library/GenericProperty`1.cs b/src/settings-ui/Settings.UI.Library/GenericProperty`1.cs
index 8a522228068e..0d13bb750cc7 100644
--- a/src/settings-ui/Settings.UI.Library/GenericProperty`1.cs
+++ b/src/settings-ui/Settings.UI.Library/GenericProperty`1.cs
@@ -2,12 +2,13 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace Microsoft.PowerToys.Settings.UI.Library
{
- public class GenericProperty : ICmdLineRepresentable
+ public class GenericProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : ICmdLineRepresentable
{
[JsonPropertyName("value")]
public T Value { get; set; }
diff --git a/src/settings-ui/Settings.UI.Library/Interfaces/ICmdLineRepresentable.cs b/src/settings-ui/Settings.UI.Library/Interfaces/ICmdLineRepresentable.cs
index 67b90674cd31..5a2cb2d8e56b 100644
--- a/src/settings-ui/Settings.UI.Library/Interfaces/ICmdLineRepresentable.cs
+++ b/src/settings-ui/Settings.UI.Library/Interfaces/ICmdLineRepresentable.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
@@ -17,7 +18,7 @@ public interface ICmdLineRepresentable
public abstract bool TryToCmdRepresentable(out string result);
- public static sealed bool TryToCmdRepresentableFor(Type type, object value, out string result)
+ public static sealed bool TryToCmdRepresentableFor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, object value, out string result)
{
result = null;
if (!typeof(ICmdLineRepresentable).IsAssignableFrom(type))
@@ -36,7 +37,7 @@ public static sealed bool TryToCmdRepresentableFor(Type type, object value, out
return false;
}
- public static sealed bool TryParseFromCmdFor(Type type, string cmd, out object result)
+ public static sealed bool TryParseFromCmdFor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string cmd, out object result)
{
result = null;
if (!typeof(ICmdLineRepresentable).IsAssignableFrom(type))
@@ -55,7 +56,7 @@ public static sealed bool TryParseFromCmdFor(Type type, string cmd, out object r
return false;
}
- public static sealed object ParseFor(Type type, string cmdRepr)
+ public static sealed object ParseFor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string cmdRepr)
{
if (type.IsEnum)
{
@@ -98,7 +99,7 @@ public static sealed object ParseFor(Type type, string cmdRepr)
throw new NotImplementedException($"Parsing type {type} is not supported yet");
}
- public static string ToCmdRepr(Type type, object value)
+ public static string ToCmdRepr([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, object value)
{
if (type.IsEnum || type.IsPrimitive)
{
diff --git a/src/settings-ui/Settings.UI.Library/MouseWithoutBordersIpcModels.cs b/src/settings-ui/Settings.UI.Library/MouseWithoutBordersIpcModels.cs
new file mode 100644
index 000000000000..a32952fa4ed4
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/MouseWithoutBordersIpcModels.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json.Serialization;
+
+#pragma warning disable SA1649 // File name should match first type name
+
+namespace Microsoft.PowerToys.Settings.UI.Library;
+
+///
+/// Socket status enumeration for MouseWithoutBorders machine connections.
+/// Must match the enum in MouseWithoutBorders\App\Class\Program.cs
+///
+public enum SocketStatus : int
+{
+ NA = 0,
+ Resolving = 1,
+ Connecting = 2,
+ Handshaking = 3,
+ Error = 4,
+ ForceClosed = 5,
+ InvalidKey = 6,
+ Timeout = 7,
+ SendError = 8,
+ Connected = 9,
+}
+
+///
+/// Represents the connection state of a machine in the MouseWithoutBorders network.
+/// Used for IPC communication between Settings UI and MouseWithoutBorders service.
+///
+public struct MachineSocketState
+{
+ [JsonPropertyName("Name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("Status")]
+ public SocketStatus Status { get; set; }
+}
diff --git a/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj b/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj
index c3832b11c508..1727f8287320 100644
--- a/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj
+++ b/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj
@@ -2,6 +2,7 @@
+
PowerToys Settings UI Library
PowerToys.Settings.UI.Lib
diff --git a/src/settings-ui/Settings.UI.Library/SettingsFactory.cs b/src/settings-ui/Settings.UI.Library/SettingsFactory.cs
index 6e53204e339e..d66a677ee3df 100644
--- a/src/settings-ui/Settings.UI.Library/SettingsFactory.cs
+++ b/src/settings-ui/Settings.UI.Library/SettingsFactory.cs
@@ -1,11 +1,9 @@
-// Copyright (c) Microsoft Corporation
+// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
@@ -13,125 +11,112 @@
namespace Microsoft.PowerToys.Settings.UI.Services
{
///
- /// Factory service for getting PowerToys module Settings that implement IHotkeyConfig
+ /// AOT-compatible factory service for PowerToys module Settings.
+ /// Uses static type registration instead of reflection-based discovery.
///
+ ///
+ /// When adding a new PowerToys module, add it to both InitializeFactories() and InitializeTypes() methods.
+ ///
public class SettingsFactory
{
private readonly SettingsUtils _settingsUtils;
+ private readonly Dictionary> _settingsFactories;
private readonly Dictionary _settingsTypes;
public SettingsFactory(SettingsUtils settingsUtils)
{
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
- _settingsTypes = DiscoverSettingsTypes();
+ _settingsFactories = InitializeFactories();
+ _settingsTypes = InitializeTypes();
}
///
- /// Dynamically discovers all Settings types that implement IHotkeyConfig
+ /// Static registry of all module settings factories.
+ /// IMPORTANT: When adding a new module, add it here.
///
- private Dictionary DiscoverSettingsTypes()
+ private Dictionary> InitializeFactories()
{
- var settingsTypes = new Dictionary();
-
- // Get the Settings.UI.Library assembly
- var assembly = Assembly.GetAssembly(typeof(IHotkeyConfig));
- if (assembly == null)
+ return new Dictionary>
{
- return settingsTypes;
- }
-
- try
- {
- // Find all types that implement IHotkeyConfig and ISettingsConfig
- var hotkeyConfigTypes = assembly.GetTypes()
- .Where(type =>
- type.IsClass &&
- !type.IsAbstract &&
- typeof(IHotkeyConfig).IsAssignableFrom(type) &&
- typeof(ISettingsConfig).IsAssignableFrom(type))
- .ToList();
-
- foreach (var type in hotkeyConfigTypes)
- {
- // Try to get the ModuleName using SettingsRepository
- try
- {
- var repositoryType = typeof(SettingsRepository<>).MakeGenericType(type);
- var getInstanceMethod = repositoryType.GetMethod("GetInstance", BindingFlags.Public | BindingFlags.Static);
- var repository = getInstanceMethod?.Invoke(null, new object[] { _settingsUtils });
-
- if (repository != null)
- {
- var settingsConfigProperty = repository.GetType().GetProperty("SettingsConfig");
- var settingsInstance = settingsConfigProperty?.GetValue(repository) as ISettingsConfig;
-
- if (settingsInstance != null)
- {
- var moduleName = settingsInstance.GetModuleName();
- if (string.IsNullOrEmpty(moduleName) && type == typeof(GeneralSettings))
- {
- moduleName = "GeneralSettings";
- }
+ ["GeneralSettings"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["AdvancedPaste"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["AlwaysOnTop"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["ColorPicker"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["CropAndLock"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["CursorWrap"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["FindMyMouse"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["LightSwitch"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["MeasureTool"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["MouseHighlighter"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["MouseJump"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["MousePointerCrosshairs"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["MouseWithoutBorders"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["Peek"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["PowerLauncher"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["PowerOCR"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["ShortcutGuide"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ ["Workspaces"] = () => SettingsRepository.GetInstance(_settingsUtils).SettingsConfig,
+ };
+ }
- if (!string.IsNullOrEmpty(moduleName))
- {
- settingsTypes[moduleName] = type;
- System.Diagnostics.Debug.WriteLine($"Discovered settings type: {type.Name} for module: {moduleName}");
- }
- }
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error getting module name for {type.Name}: {ex.Message}");
- }
- }
- }
- catch (Exception ex)
+ ///
+ /// Static registry of module name to settings type mapping.
+ /// IMPORTANT: When adding a new module, add it here.
+ ///
+ private Dictionary InitializeTypes()
+ {
+ return new Dictionary
{
- System.Diagnostics.Debug.WriteLine($"Error scanning assembly {assembly.FullName}: {ex.Message}");
- }
-
- return settingsTypes;
+ ["GeneralSettings"] = typeof(GeneralSettings),
+ ["AdvancedPaste"] = typeof(AdvancedPasteSettings),
+ ["AlwaysOnTop"] = typeof(AlwaysOnTopSettings),
+ ["ColorPicker"] = typeof(ColorPickerSettings),
+ ["CropAndLock"] = typeof(CropAndLockSettings),
+ ["CursorWrap"] = typeof(CursorWrapSettings),
+ ["FindMyMouse"] = typeof(FindMyMouseSettings),
+ ["LightSwitch"] = typeof(LightSwitchSettings),
+ ["MeasureTool"] = typeof(MeasureToolSettings),
+ ["MouseHighlighter"] = typeof(MouseHighlighterSettings),
+ ["MouseJump"] = typeof(MouseJumpSettings),
+ ["MousePointerCrosshairs"] = typeof(MousePointerCrosshairsSettings),
+ ["MouseWithoutBorders"] = typeof(MouseWithoutBordersSettings),
+ ["Peek"] = typeof(PeekSettings),
+ ["PowerLauncher"] = typeof(PowerLauncherSettings),
+ ["PowerOCR"] = typeof(PowerOcrSettings),
+ ["ShortcutGuide"] = typeof(ShortcutGuideSettings),
+ ["Workspaces"] = typeof(WorkspacesSettings),
+ };
}
- public IHotkeyConfig GetFreshSettings(string moduleKey)
+ ///
+ /// Gets a settings instance for the specified module using SettingsRepository.
+ /// AOT-compatible: uses static factory lookup instead of reflection.
+ ///
+ /// The module key/name
+ /// The settings instance implementing IHotkeyConfig, or null if not found
+ public IHotkeyConfig GetSettings(string moduleKey)
{
- if (!_settingsTypes.TryGetValue(moduleKey, out var settingsType))
+ if (!_settingsFactories.TryGetValue(moduleKey, out var factory))
{
return null;
}
try
{
- // Create a generic method call to _settingsUtils.GetSettingsOrDefault(moduleKey)
- var getSettingsMethod = typeof(SettingsUtils).GetMethod("GetSettingsOrDefault", new[] { typeof(string), typeof(string) });
- var genericMethod = getSettingsMethod?.MakeGenericMethod(settingsType);
-
- // Call GetSettingsOrDefault(moduleKey) to get fresh settings from file
- string actualModuleKey = moduleKey;
- if (moduleKey == "GeneralSettings")
- {
- actualModuleKey = string.Empty;
- }
-
- var freshSettings = genericMethod?.Invoke(_settingsUtils, new object[] { actualModuleKey, "settings.json" });
-
- return freshSettings as IHotkeyConfig;
+ return factory();
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine($"Error getting fresh settings for {moduleKey}: {ex.Message}");
+ System.Diagnostics.Debug.WriteLine($"Error getting Settings for {moduleKey}: {ex.Message}");
return null;
}
}
///
- /// Gets a settings instance for the specified module using SettingsRepository
+ /// Gets fresh settings from disk for the specified module.
+ /// AOT-compatible: uses static type dispatch instead of MakeGenericMethod.
///
- /// The module key/name
- /// The settings instance implementing IHotkeyConfig, or null if not found
- public IHotkeyConfig GetSettings(string moduleKey)
+ public IHotkeyConfig GetFreshSettings(string moduleKey)
{
if (!_settingsTypes.TryGetValue(moduleKey, out var settingsType))
{
@@ -140,22 +125,44 @@ public IHotkeyConfig GetSettings(string moduleKey)
try
{
- var repositoryType = typeof(SettingsRepository<>).MakeGenericType(settingsType);
- var getInstanceMethod = repositoryType.GetMethod("GetInstance", BindingFlags.Public | BindingFlags.Static);
- var repository = getInstanceMethod?.Invoke(null, new object[] { _settingsUtils });
-
- if (repository != null)
- {
- var settingsConfigProperty = repository.GetType().GetProperty("SettingsConfig");
- return settingsConfigProperty?.GetValue(repository) as IHotkeyConfig;
- }
+ string actualModuleKey = moduleKey == "GeneralSettings" ? string.Empty : moduleKey;
+ return GetFreshSettingsForType(settingsType, actualModuleKey);
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine($"Error getting Settings for {moduleKey}: {ex.Message}");
+ System.Diagnostics.Debug.WriteLine($"Error getting fresh settings for {moduleKey}: {ex.Message}");
+ return null;
}
+ }
- return null;
+ ///
+ /// Static dispatch for GetSettingsOrDefault using pattern matching.
+ /// Replaces reflection-based MakeGenericMethod/Invoke pattern.
+ ///
+ private IHotkeyConfig GetFreshSettingsForType(Type settingsType, string moduleKey)
+ {
+ return settingsType.Name switch
+ {
+ nameof(GeneralSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(AdvancedPasteSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(AlwaysOnTopSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(ColorPickerSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(CropAndLockSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(CursorWrapSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(FindMyMouseSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(LightSwitchSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(MeasureToolSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(MouseHighlighterSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(MouseJumpSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(MousePointerCrosshairsSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(MouseWithoutBordersSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(PeekSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(PowerLauncherSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(PowerOcrSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(ShortcutGuideSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ nameof(WorkspacesSettings) => _settingsUtils.GetSettingsOrDefault(moduleKey, "settings.json"),
+ _ => null,
+ };
}
///
@@ -164,7 +171,7 @@ public IHotkeyConfig GetSettings(string moduleKey)
/// List of module names
public List GetAvailableModuleNames()
{
- return _settingsTypes.Keys.ToList();
+ return new List(_settingsTypes.Keys);
}
///
diff --git a/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs b/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs
index 2fab97c5387b..cbb8182af332 100644
--- a/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs
+++ b/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs
@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
using System.Text.Json.Serialization;
using SettingsUILibrary = Settings.UI.Library;
using SettingsUILibraryHelpers = Settings.UI.Library.Helpers;
@@ -167,6 +168,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonSerializable(typeof(SndModuleSettings))]
[JsonSerializable(typeof(SndModuleSettings))]
+ // CLI/DSC command support types
+ [JsonSerializable(typeof(PowerLauncherPluginSettings))]
+ [JsonSerializable(typeof(PowerLauncherPluginSettings[]))]
+
+ // MouseWithoutBorders IPC types
+ [JsonSerializable(typeof(MachineSocketState))]
+ [JsonSerializable(typeof(MachineSocketState[]))]
+ [JsonSerializable(typeof(SocketStatus))]
+
public partial class SettingsSerializationContext : JsonSerializerContext
{
}
diff --git a/src/settings-ui/Settings.UI.Library/Utilities/CommandLineUtils.cs b/src/settings-ui/Settings.UI.Library/Utilities/CommandLineUtils.cs
index 3b10d0d63f50..82f999ac809c 100644
--- a/src/settings-ui/Settings.UI.Library/Utilities/CommandLineUtils.cs
+++ b/src/settings-ui/Settings.UI.Library/Utilities/CommandLineUtils.cs
@@ -1,69 +1,181 @@
-// Copyright (c) Microsoft Corporation
+// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
-using System.Linq;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
-
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library;
+///
+/// AOT-compatible command line utilities.
+/// Uses static type mapping instead of AppDomain reflection.
+///
public class CommandLineUtils
{
- private static Type GetSettingsConfigType(string moduleName, Assembly settingsLibraryAssembly)
+ private static readonly Dictionary _settingsTypes = new()
{
- var settingsClassName = moduleName == "GeneralSettings" ? moduleName : moduleName + "Settings";
- return settingsLibraryAssembly.GetType(typeof(CommandLineUtils).Namespace + "." + settingsClassName);
- }
+ ["GeneralSettings"] = typeof(GeneralSettings),
+ ["AdvancedPaste"] = typeof(AdvancedPasteSettings),
+ ["AlwaysOnTop"] = typeof(AlwaysOnTopSettings),
+ ["Awake"] = typeof(AwakeSettings),
+ ["CmdNotFound"] = typeof(CmdNotFoundSettings),
+ ["ColorPicker"] = typeof(ColorPickerSettings),
+ ["CropAndLock"] = typeof(CropAndLockSettings),
+ ["CursorWrap"] = typeof(CursorWrapSettings),
+ ["EnvironmentVariables"] = typeof(EnvironmentVariablesSettings),
+ ["FancyZones"] = typeof(FancyZonesSettings),
+ ["FileLocksmith"] = typeof(FileLocksmithSettings),
+ ["FindMyMouse"] = typeof(FindMyMouseSettings),
+ ["Hosts"] = typeof(HostsSettings),
+ ["ImageResizer"] = typeof(ImageResizerSettings),
+ ["KeyboardManager"] = typeof(KeyboardManagerSettings),
+ ["LightSwitch"] = typeof(LightSwitchSettings),
+ ["MeasureTool"] = typeof(MeasureToolSettings),
+ ["MouseHighlighter"] = typeof(MouseHighlighterSettings),
+ ["MouseJump"] = typeof(MouseJumpSettings),
+ ["MousePointerCrosshairs"] = typeof(MousePointerCrosshairsSettings),
+ ["MouseWithoutBorders"] = typeof(MouseWithoutBordersSettings),
+ ["NewPlus"] = typeof(NewPlusSettings),
+ ["Peek"] = typeof(PeekSettings),
+ ["PowerAccent"] = typeof(PowerAccentSettings),
+ ["PowerLauncher"] = typeof(PowerLauncherSettings),
+ ["PowerOCR"] = typeof(PowerOcrSettings),
+ ["PowerRename"] = typeof(PowerRenameSettings),
+ ["PowerPreview"] = typeof(PowerPreviewSettings),
+ ["RegistryPreview"] = typeof(RegistryPreviewSettings),
+ ["ShortcutGuide"] = typeof(ShortcutGuideSettings),
+ ["Workspaces"] = typeof(WorkspacesSettings),
+ ["ZoomIt"] = typeof(ZoomItSettings),
+ };
- public static ISettingsConfig GetSettingsConfigFor(string moduleName, SettingsUtils settingsUtils, Assembly settingsLibraryAssembly)
+ public static ISettingsConfig GetSettingsConfigFor(string moduleName, SettingsUtils settingsUtils, Assembly settingsLibraryAssembly = null)
{
- return GetSettingsConfigFor(GetSettingsConfigType(moduleName, settingsLibraryAssembly), settingsUtils);
- }
-
- /// Executes SettingsRepository.GetInstance(settingsUtils).SettingsConfig
- public static ISettingsConfig GetSettingsConfigFor(Type moduleSettingsType, SettingsUtils settingsUtils)
- {
- var genericSettingsRepositoryType = typeof(SettingsRepository<>);
- var moduleSettingsRepositoryType = genericSettingsRepositoryType.MakeGenericType(moduleSettingsType);
-
- // Note: GeneralSettings is only used here only to satisfy nameof constrains, i.e. the choice of this particular type doesn't have any special significance.
- var getInstanceInfo = moduleSettingsRepositoryType.GetMethod(nameof(SettingsRepository.GetInstance));
- var settingsRepository = getInstanceInfo.Invoke(null, new object[] { settingsUtils });
- var settingsConfigProperty = getInstanceInfo.ReturnType.GetProperty(nameof(SettingsRepository.SettingsConfig));
- return settingsConfigProperty.GetValue(settingsRepository) as ISettingsConfig;
- }
+ if (!_settingsTypes.TryGetValue(moduleName, out var settingsType))
+ {
+ return null;
+ }
- public static Assembly GetSettingsAssembly()
- {
- return AppDomain.CurrentDomain.GetAssemblies()
- .FirstOrDefault(a => a.GetName().Name == "PowerToys.Settings.UI.Lib");
+ return GetSettingsConfigFor(settingsType, settingsUtils);
}
- public static object GetPropertyValue(string propertyName, ISettingsConfig settingsConfig)
+ ///
+ /// Gets settings config for a given type using static dispatch.
+ /// AOT-compatible: replaces MakeGenericType/GetMethod/Invoke pattern.
+ ///
+ public static ISettingsConfig GetSettingsConfigFor(Type moduleSettingsType, SettingsUtils settingsUtils)
{
- var (settingInfo, properties) = LocateSetting(propertyName, settingsConfig);
- return settingInfo.GetValue(properties);
+ return moduleSettingsType.Name switch
+ {
+ nameof(GeneralSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(AdvancedPasteSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(AlwaysOnTopSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(AwakeSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(CmdNotFoundSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(ColorPickerSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(CropAndLockSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(CursorWrapSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(EnvironmentVariablesSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(FancyZonesSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(FileLocksmithSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(FindMyMouseSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(HostsSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(ImageResizerSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(KeyboardManagerSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(LightSwitchSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(MeasureToolSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(MouseHighlighterSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(MouseJumpSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(MousePointerCrosshairsSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(MouseWithoutBordersSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(NewPlusSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(PeekSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(PowerAccentSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(PowerLauncherSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(PowerOcrSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(PowerRenameSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(PowerPreviewSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(RegistryPreviewSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(ShortcutGuideSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(WorkspacesSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ nameof(ZoomItSettings) => SettingsRepository.GetInstance(settingsUtils).SettingsConfig,
+ _ => null,
+ };
}
+ ///
+ /// Gets the Properties object from a settings config.
+ /// For GeneralSettings, returns the settings itself. For others, returns the Properties property.
+ ///
public static object GetProperties(ISettingsConfig settingsConfig)
{
+ // Use reflection fallback for all settings types to preserve compatibility
+ // This is needed because not all settings have static patterns
var settingsType = settingsConfig.GetType();
if (settingsType == typeof(GeneralSettings))
{
return settingsConfig;
}
- var settingsConfigInfo = settingsType.GetProperty("Properties");
- return settingsConfigInfo.GetValue(settingsConfig);
+ var propertiesProperty = settingsType.GetProperty("Properties");
+ return propertiesProperty?.GetValue(settingsConfig);
+ }
+
+ ///
+ /// Gets enabled state for a specific module.
+ /// AOT-compatible: static dispatch instead of reflection.
+ ///
+ public static bool GetEnabledModuleValue(string moduleName, EnabledModules enabled)
+ {
+ return moduleName switch
+ {
+ "AdvancedPaste" => enabled.AdvancedPaste,
+ "AlwaysOnTop" => enabled.AlwaysOnTop,
+ "Awake" => enabled.Awake,
+ "CmdNotFound" => enabled.CmdNotFound,
+ "ColorPicker" => enabled.ColorPicker,
+ "CropAndLock" => enabled.CropAndLock,
+ "CursorWrap" => enabled.CursorWrap,
+ "EnvironmentVariables" => enabled.EnvironmentVariables,
+ "FancyZones" => enabled.FancyZones,
+ "FileLocksmith" => enabled.FileLocksmith,
+ "FindMyMouse" => enabled.FindMyMouse,
+ "Hosts" => enabled.Hosts,
+ "ImageResizer" => enabled.ImageResizer,
+ "KeyboardManager" => enabled.KeyboardManager,
+ "LightSwitch" => enabled.LightSwitch,
+ "MeasureTool" => enabled.MeasureTool,
+ "MouseHighlighter" => enabled.MouseHighlighter,
+ "MouseJump" => enabled.MouseJump,
+ "MousePointerCrosshairs" => enabled.MousePointerCrosshairs,
+ "MouseWithoutBorders" => enabled.MouseWithoutBorders,
+ "NewPlus" => enabled.NewPlus,
+ "Peek" => enabled.Peek,
+ "PowerAccent" => enabled.PowerAccent,
+ "PowerLauncher" => enabled.PowerLauncher,
+ "PowerOcr" => enabled.PowerOcr,
+ "PowerRename" => enabled.PowerRename,
+ "RegistryPreview" => enabled.RegistryPreview,
+ "ShortcutGuide" => enabled.ShortcutGuide,
+ "Workspaces" => enabled.Workspaces,
+ "ZoomIt" => enabled.ZoomIt,
+ _ => false,
+ };
}
+ ///
+ /// Locates a setting property and returns both the PropertyInfo and the properties object.
+ /// Uses reflection on properties which is preserved via DynamicallyAccessedMembers on property types.
+ ///
public static (PropertyInfo SettingInfo, object Properties) LocateSetting(string propertyName, ISettingsConfig settingsConfig)
{
var properties = GetProperties(settingsConfig);
var propertiesType = properties.GetType();
+
+ // Special handling for GeneralSettings.Enabled.*
if (propertiesType == typeof(GeneralSettings) && propertyName.StartsWith("Enabled.", StringComparison.InvariantCulture))
{
var moduleNameToToggle = propertyName.Replace("Enabled.", string.Empty);
@@ -75,6 +187,18 @@ public static (PropertyInfo SettingInfo, object Properties) LocateSetting(string
return (propertiesType.GetProperty(propertyName), properties);
}
+ ///
+ /// Gets the value of a property from a settings config.
+ ///
+ public static object GetPropertyValue(string propertyName, ISettingsConfig settingsConfig)
+ {
+ var (settingInfo, properties) = LocateSetting(propertyName, settingsConfig);
+ return settingInfo?.GetValue(properties);
+ }
+
+ ///
+ /// Gets the PropertyInfo for a setting property.
+ ///
public static PropertyInfo GetSettingPropertyInfo(string propertyName, ISettingsConfig settingsConfig)
{
return LocateSetting(propertyName, settingsConfig).SettingInfo;
diff --git a/src/settings-ui/Settings.UI.Library/Utilities/GetSettingCommandLineCommand.cs b/src/settings-ui/Settings.UI.Library/Utilities/GetSettingCommandLineCommand.cs
index 5780b95392c7..ecbbc91d0154 100644
--- a/src/settings-ui/Settings.UI.Library/Utilities/GetSettingCommandLineCommand.cs
+++ b/src/settings-ui/Settings.UI.Library/Utilities/GetSettingCommandLineCommand.cs
@@ -47,9 +47,7 @@ public static string Execute(Dictionary> settingNamesForMod
{
var modulesSettings = new Dictionary>();
- var settingsAssembly = CommandLineUtils.GetSettingsAssembly();
var settingsUtils = SettingsUtils.Default;
-
var enabledModules = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Enabled;
foreach (var (moduleName, settings) in settingNamesForModules)
@@ -57,10 +55,10 @@ public static string Execute(Dictionary> settingNamesForMod
var moduleSettings = new Dictionary();
if (moduleName != nameof(GeneralSettings))
{
- moduleSettings.Add("Enabled", typeof(EnabledModules).GetProperty(moduleName).GetValue(enabledModules));
+ moduleSettings.Add("Enabled", CommandLineUtils.GetEnabledModuleValue(moduleName, enabledModules));
}
- var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsAssembly);
+ var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils);
foreach (var settingName in settings)
{
var value = CommandLineUtils.GetPropertyValue(settingName, settingsConfig);
diff --git a/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs b/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs
index 3fa4479b79e7..aa6d28a1708c 100644
--- a/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs
+++ b/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs
@@ -115,7 +115,8 @@ public static string LocalApplicationDataFolder()
public static string GetPowerToysInstallationFolder()
{
// PowerToys.exe is in the parent folder relative to Settings.
- var settingsPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
+ // Use AppContext.BaseDirectory for AOT/single-file compatibility
+ var settingsPath = AppContext.BaseDirectory;
return Directory.GetParent(settingsPath).FullName;
}
diff --git a/src/settings-ui/Settings.UI.Library/Utilities/SetAdditionalSettingsCommandLineCommand.cs b/src/settings-ui/Settings.UI.Library/Utilities/SetAdditionalSettingsCommandLineCommand.cs
index b6ba04dec8e7..c37164faafe4 100644
--- a/src/settings-ui/Settings.UI.Library/Utilities/SetAdditionalSettingsCommandLineCommand.cs
+++ b/src/settings-ui/Settings.UI.Library/Utilities/SetAdditionalSettingsCommandLineCommand.cs
@@ -246,10 +246,8 @@ private static Type GetUnderlyingTypeOfCollection(IEnumerable
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/Properties/PublishProfiles/InstallationPublishProfile.pubxml b/src/settings-ui/Settings.UI/Properties/PublishProfiles/InstallationPublishProfile.pubxml
index cff222baf4b3..170f2086a1df 100644
--- a/src/settings-ui/Settings.UI/Properties/PublishProfiles/InstallationPublishProfile.pubxml
+++ b/src/settings-ui/Settings.UI/Properties/PublishProfiles/InstallationPublishProfile.pubxml
@@ -1,18 +1,23 @@
-
+
FileSystem
- net9.0-windows10.0.22621.0
+ net9.0-windows10.0.26100.0
10.0.19041.0
10.0.19041.0
- $(PowerToysRoot)\$(Platform)\$(Configuration)\WinUI3Apps
- win-$(Platform)
+ C:\Users\shuaiyuan\source\repos\PowerToys\x64\Release\WinUI3Apps\Publish
+ win-x64
true
- False
- False
+ true
+ true
+ false
+ false
false
+ Release
+ x64
+ true
-
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
index 7f14f1809ed3..21551f7a1b64 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
@@ -49,8 +49,12 @@ private enum Arguments
private const int RequiredArgumentsLaunchedFromRunnerQty = 10;
+ // IPC message queue for messages sent before IPC manager is initialized
+ private static readonly System.Collections.Concurrent.ConcurrentQueue PendingIPCMessages = new System.Collections.Concurrent.ConcurrentQueue();
+
// Create an instance of the IPC wrapper.
private static TwoWayPipeMessageIPCManaged ipcmanager;
+ private static bool isIPCInitialized;
public static bool IsElevated { get; set; }
@@ -75,6 +79,9 @@ private enum Arguments
///
public App()
{
+#if BUILD_INFO_PUBLISH_AOT
+ Helpers.TypePreservation.PreserveTypes();
+#endif
Logger.InitializeLogger(@"\Settings\Logs");
string appLanguage = LanguageHelper.LoadLanguage();
@@ -207,19 +214,37 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
Environment.Exit(0);
});
- ipcmanager = new TwoWayPipeMessageIPCManaged(cmdArgs[(int)Arguments.SettingsPipeName], cmdArgs[(int)Arguments.PTPipeName], (string message) =>
+ // Initialize IPC manager asynchronously to avoid blocking window creation
+ string settingsPipeName = cmdArgs[(int)Arguments.SettingsPipeName];
+ string ptPipeName = cmdArgs[(int)Arguments.PTPipeName];
+ _ = Task.Run(() =>
{
- if (IPCMessageReceivedCallback != null && message.Length > 0)
+ try
{
- IPCMessageReceivedCallback(message);
+ ipcmanager = new TwoWayPipeMessageIPCManaged(settingsPipeName, ptPipeName, (string message) =>
+ {
+ if (IPCMessageReceivedCallback != null && message.Length > 0)
+ {
+ IPCMessageReceivedCallback(message);
+ }
+ });
+ ipcmanager.Start();
+
+ // Mark as initialized and process any pending messages
+ isIPCInitialized = true;
+ ProcessPendingIPCMessages();
+
+ // Initialize GlobalHotkeyConflictManager after IPC is ready
+ GlobalHotkeyConflictManager.Initialize(message =>
+ {
+ SendIPCMessage(message);
+ return 0;
+ });
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"Error initializing IPC manager: {ex.Message}");
}
- });
- ipcmanager.Start();
-
- GlobalHotkeyConflictManager.Initialize(message =>
- {
- ipcmanager.Send(message);
- return 0;
});
if (!ShowOobe && !ShowScoobe)
@@ -235,6 +260,9 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
// https://github.com/microsoft/microsoft-ui-xaml/issues/8948 - A window's top border incorrectly
// renders as black on Windows 10.
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
+
+ // Warm up search index in the background to avoid delay on first search
+ _ = Task.Run(() => SearchIndexService.BuildIndex());
}
else
{
@@ -243,6 +271,9 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
// the Settings from the tray icon.
settingsWindow = new MainWindow(true);
+ // Warm up search index in the background
+ _ = Task.Run(() => SearchIndexService.BuildIndex());
+
if (ShowOobe)
{
PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent());
@@ -290,8 +321,8 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
}
else
{
-#if DEBUG
- // For debugging purposes
+#if DEBUG || STANDALONE
+ // For debugging purposes or standalone mode
// Window is also needed to show MessageDialog
settingsWindow = new MainWindow();
settingsWindow.ExtendsContentIntoTitleBar = true;
@@ -299,10 +330,10 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
settingsWindow.Activate();
settingsWindow.NavigateToSection(StartupPage);
- // In DEBUG mode, we might not have IPC set up, so provide a dummy implementation
+ // In DEBUG/STANDALONE mode, we might not have IPC set up, so provide a dummy implementation
GlobalHotkeyConflictManager.Initialize(message =>
{
- // In debug mode, just log or do nothing
+ // In standalone mode, just log or do nothing
System.Diagnostics.Debug.WriteLine($"IPC Message: {message}");
return 0;
});
@@ -319,6 +350,33 @@ public static TwoWayPipeMessageIPCManaged GetTwoWayIPCManager()
return ipcmanager;
}
+ ///
+ /// Sends an IPC message, queuing it if the IPC manager is not yet initialized.
+ ///
+ public static void SendIPCMessage(string message)
+ {
+ if (isIPCInitialized && ipcmanager != null)
+ {
+ ipcmanager.Send(message);
+ }
+ else
+ {
+ // Queue the message to be sent after IPC manager is initialized
+ PendingIPCMessages.Enqueue(message);
+ }
+ }
+
+ ///
+ /// Process all pending IPC messages after the IPC manager is initialized.
+ ///
+ private static void ProcessPendingIPCMessages()
+ {
+ while (PendingIPCMessages.TryDequeue(out string message))
+ {
+ ipcmanager?.Send(message);
+ }
+ }
+
public static bool IsDarkTheme()
{
return ThemeService.Theme == ElementTheme.Dark || (ThemeService.Theme == ElementTheme.Default && ThemeHelpers.GetAppTheme() == AppTheme.Dark);
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs
index e85633f9e8f0..a66f0bb4d1d4 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs
@@ -27,133 +27,177 @@ public MainWindow(bool createHidden = false)
var bootTime = new System.Diagnostics.Stopwatch();
bootTime.Start();
- this.Activated += Window_Activated_SetIcon;
+ // Initialize UI components immediately for faster visual feedback
+ this.InitializeComponent();
+ this.ExtendsContentIntoTitleBar = true;
+ SetAppTitleBar();
+ // Set up critical event handlers
+ this.Activated += Window_Activated_SetIcon;
App.ThemeService.ThemeChanged += OnThemeChanged;
- App.ThemeService.ApplyTheme();
-
- this.ExtendsContentIntoTitleBar = true;
+ // Set elevation status immediately (required for UI)
ShellPage.SetElevationStatus(App.IsElevated);
ShellPage.SetIsUserAnAdmin(App.IsUserAnAdmin);
+ // Apply theme immediately
+ App.ThemeService.ApplyTheme();
+
+ // Set window title immediately
+ var loader = ResourceLoaderInstance.ResourceLoader;
+ Title = App.IsElevated ? loader.GetString("SettingsWindow_AdminTitle") : loader.GetString("SettingsWindow_Title");
+
+ // Handle window visibility
var hWnd = WindowNative.GetWindowHandle(this);
- var placement = WindowHelper.DeserializePlacementOrDefault(hWnd);
if (createHidden)
{
- placement.ShowCmd = NativeMethods.SW_HIDE;
+ var placement = new WINDOWPLACEMENT
+ {
+ ShowCmd = NativeMethods.SW_HIDE,
+ };
+ NativeMethods.SetWindowPlacement(hWnd, ref placement);
// Restore the last known placement on the first activation
this.Activated += Window_Activated;
}
- NativeMethods.SetWindowPlacement(hWnd, ref placement);
-
- var loader = ResourceLoaderInstance.ResourceLoader;
- Title = App.IsElevated ? loader.GetString("SettingsWindow_AdminTitle") : loader.GetString("SettingsWindow_Title");
-
- // send IPC Message
- ShellPage.SetDefaultSndMessageCallback(msg =>
- {
- // IPC Manager is null when launching runner directly
- App.GetTwoWayIPCManager()?.Send(msg);
- });
-
- // send IPC Message
- ShellPage.SetRestartAdminSndMessageCallback(msg =>
- {
- App.GetTwoWayIPCManager()?.Send(msg);
- Environment.Exit(0); // close application
- });
+ // Initialize remaining components asynchronously
+ _ = InitializeAsync(hWnd, createHidden, bootTime);
+ }
- // send IPC Message
- ShellPage.SetCheckForUpdatesMessageCallback(msg =>
+ private async Task InitializeAsync(IntPtr hWnd, bool createHidden, System.Diagnostics.Stopwatch bootTime)
+ {
+ try
{
- App.GetTwoWayIPCManager()?.Send(msg);
- });
+ // Load window placement asynchronously (non-blocking file I/O)
+ if (!createHidden)
+ {
+ await Task.Run(() =>
+ {
+ var placement = WindowHelper.DeserializePlacementOrDefault(hWnd);
+ NativeMethods.SetWindowPlacement(hWnd, ref placement);
+ });
+ }
- // open main window
- ShellPage.SetOpenMainWindowCallback(type =>
- {
+ // Set up IPC callbacks on UI thread
DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
- App.OpenSettingsWindow(type));
- });
-
- // open main window
- ShellPage.SetUpdatingGeneralSettingsCallback((ModuleType moduleType, bool isEnabled) =>
- {
- SettingsRepository repository = SettingsRepository.GetInstance(SettingsUtils.Default);
- GeneralSettings generalSettingsConfig = repository.SettingsConfig;
- bool needToUpdate = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType) != isEnabled;
-
- if (needToUpdate)
{
- ModuleHelper.SetIsModuleEnabled(generalSettingsConfig, moduleType, isEnabled);
- var outgoing = new OutGoingGeneralSettings(generalSettingsConfig);
+ // send IPC Message
+ ShellPage.SetDefaultSndMessageCallback(msg =>
+ {
+ // Use SendIPCMessage which handles queuing if IPC is not yet initialized
+ App.SendIPCMessage(msg);
+ });
- // Save settings to file
- SettingsUtils.Default.SaveSettings(generalSettingsConfig.ToJsonString());
+ // send IPC Message
+ ShellPage.SetRestartAdminSndMessageCallback(msg =>
+ {
+ App.SendIPCMessage(msg);
+ Environment.Exit(0); // close application
+ });
- // Send IPC message asynchronously to avoid blocking UI and potential recursive calls
- Task.Run(() =>
+ // send IPC Message
+ ShellPage.SetCheckForUpdatesMessageCallback(msg =>
{
- ShellPage.SendDefaultIPCMessage(outgoing.ToString());
+ App.SendIPCMessage(msg);
});
- ShellPage.ShellHandler?.SignalGeneralDataUpdate();
- }
+ // open main window
+ ShellPage.SetOpenMainWindowCallback(type =>
+ {
+ DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
+ App.OpenSettingsWindow(type));
+ });
- return needToUpdate;
- });
+ // open main window
+ ShellPage.SetUpdatingGeneralSettingsCallback((ModuleType moduleType, bool isEnabled) =>
+ {
+ SettingsRepository repository = SettingsRepository.GetInstance(SettingsUtils.Default);
+ GeneralSettings generalSettingsConfig = repository.SettingsConfig;
+ bool needToUpdate = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType) != isEnabled;
- // open oobe
- ShellPage.SetOpenOobeCallback(() =>
- {
- if (App.GetOobeWindow() == null)
- {
- App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.Overview));
- }
+ if (needToUpdate)
+ {
+ ModuleHelper.SetIsModuleEnabled(generalSettingsConfig, moduleType, isEnabled);
+ var outgoing = new OutGoingGeneralSettings(generalSettingsConfig);
- App.GetOobeWindow().Activate();
- });
+ // Save settings to file
+ SettingsUtils.Default.SaveSettings(generalSettingsConfig.ToJsonString());
- // open whats new window
- ShellPage.SetOpenWhatIsNewCallback(() =>
- {
- if (App.GetScoobeWindow() == null)
- {
- App.SetScoobeWindow(new ScoobeWindow());
- }
+ // Send IPC message asynchronously to avoid blocking UI and potential recursive calls
+ Task.Run(() =>
+ {
+ ShellPage.SendDefaultIPCMessage(outgoing.ToString());
+ });
- App.GetScoobeWindow().Activate();
- });
+ ShellPage.ShellHandler?.SignalGeneralDataUpdate();
+ }
- this.InitializeComponent();
- SetAppTitleBar();
+ return needToUpdate;
+ });
- // receive IPC Message
- App.IPCMessageReceivedCallback = (string msg) =>
- {
- if (ShellPage.ShellHandler.IPCResponseHandleList != null)
- {
- var success = JsonObject.TryParse(msg, out JsonObject json);
- if (success)
+ // open oobe
+ ShellPage.SetOpenOobeCallback(() =>
{
- foreach (Action handle in ShellPage.ShellHandler.IPCResponseHandleList)
+ if (App.GetOobeWindow() == null)
{
- handle(json);
+ App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.Overview));
}
- }
- else
+
+ App.GetOobeWindow().Activate();
+ });
+
+ // open whats new window
+ ShellPage.SetOpenWhatIsNewCallback(() =>
{
- Logger.LogError("Failed to parse JSON from IPC message.");
- }
- }
- };
+ if (App.GetScoobeWindow() == null)
+ {
+ App.SetScoobeWindow(new ScoobeWindow());
+ }
- bootTime.Stop();
+ App.GetScoobeWindow().Activate();
+ });
- PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
+ // receive IPC Message
+ App.IPCMessageReceivedCallback = (string msg) =>
+ {
+ // Ignore empty or whitespace-only messages
+ if (string.IsNullOrWhiteSpace(msg))
+ {
+ return;
+ }
+
+ if (ShellPage.ShellHandler.IPCResponseHandleList != null)
+ {
+ var success = JsonObject.TryParse(msg, out JsonObject json);
+ if (success)
+ {
+ foreach (Action handle in ShellPage.ShellHandler.IPCResponseHandleList)
+ {
+ handle(json);
+ }
+ }
+ else
+ {
+ // Log with message preview for debugging (limit to 100 chars to avoid log spam)
+ var msgPreview = msg.Length > 100 ? string.Concat(msg.AsSpan(0, 100), "...") : msg;
+ Logger.LogError($"Failed to parse JSON from IPC message. Message preview: {msgPreview}");
+ }
+ }
+ };
+ });
+
+ // Record telemetry asynchronously
+ bootTime.Stop();
+ await Task.Run(() =>
+ {
+ PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
+ });
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"Error during async initialization: {ex.Message}");
+ }
}
private void SetAppTitleBar()
diff --git a/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs
index 05aec49c9a8d..6e8530c2f747 100644
--- a/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/LightSwitchViewModel.cs
@@ -16,7 +16,6 @@
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
-using Newtonsoft.Json.Linq;
using PowerToys.GPOWrapper;
using Settings.UI.Library;
using Settings.UI.Library.Helpers;
diff --git a/src/settings-ui/Settings.UI/ViewModels/MouseWithoutBordersViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/MouseWithoutBordersViewModel.cs
index 447c62a6dd3e..36290b75368c 100644
--- a/src/settings-ui/Settings.UI/ViewModels/MouseWithoutBordersViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/MouseWithoutBordersViewModel.cs
@@ -24,7 +24,10 @@
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media;
+#if !BUILD_INFO_PUBLISH_AOT
using StreamJsonRpc;
+using Newtonsoft.Json;
+#endif
using Windows.ApplicationModel.DataTransfer;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
@@ -235,20 +238,8 @@ public bool IsEnabledGpoConfigured
get => _enabledStateIsGPOConfigured;
}
- private enum SocketStatus : int
- {
- NA = 0,
- Resolving = 1,
- Connecting = 2,
- Handshaking = 3,
- Error = 4,
- ForceClosed = 5,
- InvalidKey = 6,
- Timeout = 7,
- SendError = 8,
- Connected = 9,
- }
-
+ // SocketStatus enum is now defined in Settings.UI.Library\MouseWithoutBordersIpcModels.cs
+#if !BUILD_INFO_PUBLISH_AOT
private interface ISettingsSyncHelper
{
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
@@ -274,13 +265,73 @@ public struct MachineSocketState
Task RequestMachineSocketStateAsync();
}
+#endif
private static CancellationTokenSource _cancellationTokenSource;
private static Task _machinePollingThreadTask;
- private static VisualStudio.Threading.AsyncSemaphore _ipcSemaphore = new VisualStudio.Threading.AsyncSemaphore(1);
+ private static SemaphoreSlim _ipcSemaphore = new SemaphoreSlim(1, 1);
+ private static NamedPipeClientStream syncHelperStream;
+
+#if BUILD_INFO_PUBLISH_AOT
+ // AOT-compatible IPC client wrapper
+ private sealed partial class SyncHelper : IDisposable
+ {
+ public SyncHelper(NamedPipeClientStream stream)
+ {
+ Stream = stream;
+ Client = new MouseWithoutBordersIpcClient(stream);
+ }
+
+ public NamedPipeClientStream Stream { get; }
+
+ public MouseWithoutBordersIpcClient Client { get; private set; }
+
+ public void Dispose()
+ {
+ Client?.Dispose();
+ }
+ }
+
+ private async Task GetSettingsSyncHelperAsync()
+ {
+ try
+ {
+ var recreateStream = false;
+ if (syncHelperStream == null)
+ {
+ recreateStream = true;
+ }
+ else
+ {
+ if (!syncHelperStream.IsConnected || !syncHelperStream.CanWrite)
+ {
+ await syncHelperStream.DisposeAsync();
+ recreateStream = true;
+ }
+ }
+
+ if (recreateStream)
+ {
+ syncHelperStream = new NamedPipeClientStream(".", "MouseWithoutBorders/SettingsSync", PipeDirection.InOut, PipeOptions.Asynchronous);
+ await syncHelperStream.ConnectAsync(10000);
+ }
+
+ return new SyncHelper(syncHelperStream);
+ }
+ catch (Exception ex)
+ {
+ if (IsEnabled)
+ {
+ Logger.LogError($"Couldn't create SettingsSync (AOT): {ex}");
+ }
+ return null;
+ }
+ }
+#else
+ // StreamJsonRpc-based IPC client wrapper (non-AOT builds)
private sealed partial class SyncHelper : IDisposable
{
public SyncHelper(NamedPipeClientStream stream)
@@ -299,8 +350,6 @@ public void Dispose()
}
}
- private static NamedPipeClientStream syncHelperStream;
-
private async Task GetSettingsSyncHelperAsync()
{
try
@@ -337,88 +386,154 @@ private async Task GetSettingsSyncHelperAsync()
return null;
}
}
+#endif
public async Task SubmitShutdownRequestAsync()
{
- using (await _ipcSemaphore.EnterAsync())
+ await _ipcSemaphore.WaitAsync();
+ try
{
using (var syncHelper = await GetSettingsSyncHelperAsync())
{
- syncHelper?.Endpoint?.Shutdown();
- var task = syncHelper?.Stream.FlushAsync();
- if (task != null)
+ if (syncHelper != null)
{
- await task;
+#if BUILD_INFO_PUBLISH_AOT
+ await syncHelper.Client.ShutdownAsync();
+#else
+ syncHelper.Endpoint?.Shutdown();
+ var task = syncHelper.Stream.FlushAsync();
+ if (task != null)
+ {
+ await task;
+ }
+#endif
}
}
}
+ finally
+ {
+ _ipcSemaphore.Release();
+ }
}
public async Task SubmitReconnectRequestAsync()
{
- using (await _ipcSemaphore.EnterAsync())
+ await _ipcSemaphore.WaitAsync();
+ try
{
using (var syncHelper = await GetSettingsSyncHelperAsync())
{
- syncHelper?.Endpoint?.Reconnect();
- var task = syncHelper?.Stream.FlushAsync();
- if (task != null)
+ if (syncHelper != null)
{
- await task;
+#if BUILD_INFO_PUBLISH_AOT
+ await syncHelper.Client.ReconnectAsync();
+#else
+ syncHelper.Endpoint?.Reconnect();
+ var task = syncHelper.Stream.FlushAsync();
+ if (task != null)
+ {
+ await task;
+ }
+#endif
}
}
}
+ finally
+ {
+ _ipcSemaphore.Release();
+ }
}
public async Task SubmitNewKeyRequestAsync()
{
- using (await _ipcSemaphore.EnterAsync())
+ await _ipcSemaphore.WaitAsync();
+ try
{
using (var syncHelper = await GetSettingsSyncHelperAsync())
{
- syncHelper?.Endpoint?.GenerateNewKey();
- var task = syncHelper?.Stream.FlushAsync();
- if (task != null)
+ if (syncHelper != null)
{
- await task;
+#if BUILD_INFO_PUBLISH_AOT
+ await syncHelper.Client.GenerateNewKeyAsync();
+#else
+ syncHelper.Endpoint?.GenerateNewKey();
+ var task = syncHelper.Stream.FlushAsync();
+ if (task != null)
+ {
+ await task;
+ }
+#endif
}
}
}
+ finally
+ {
+ _ipcSemaphore.Release();
+ }
}
public async Task SubmitConnectionRequestAsync(string pcName, string securityKey)
{
- using (await _ipcSemaphore.EnterAsync())
+ await _ipcSemaphore.WaitAsync();
+ try
{
using (var syncHelper = await GetSettingsSyncHelperAsync())
{
- syncHelper?.Endpoint?.ConnectToMachine(pcName, securityKey);
- var task = syncHelper?.Stream.FlushAsync();
- if (task != null)
+ if (syncHelper != null)
{
- await task;
+#if BUILD_INFO_PUBLISH_AOT
+ await syncHelper.Client.ConnectToMachineAsync(pcName, securityKey);
+#else
+ syncHelper.Endpoint?.ConnectToMachine(pcName, securityKey);
+ var task = syncHelper.Stream.FlushAsync();
+ if (task != null)
+ {
+ await task;
+ }
+#endif
}
}
}
+ finally
+ {
+ _ipcSemaphore.Release();
+ }
}
- private async Task PollMachineSocketStateAsync()
+ private async Task PollMachineSocketStateAsync()
{
- using (await _ipcSemaphore.EnterAsync())
+ await _ipcSemaphore.WaitAsync();
+ try
{
using (var syncHelper = await GetSettingsSyncHelperAsync())
{
- var task = syncHelper?.Endpoint?.RequestMachineSocketStateAsync();
- if (task != null)
- {
- return await task;
- }
- else
+ if (syncHelper != null)
{
- return null;
+#if BUILD_INFO_PUBLISH_AOT
+ return await syncHelper.Client.RequestMachineSocketStateAsync();
+#else
+ var task = syncHelper.Endpoint?.RequestMachineSocketStateAsync();
+ if (task != null)
+ {
+ var oldStates = await task;
+
+ // Convert from ISettingsSyncHelper.MachineSocketState to MachineSocketState
+ return oldStates.Select(s => new MachineSocketState
+ {
+ Name = s.Name,
+ Status = (SocketStatus)s.Status,
+ }).ToArray();
+ }
+#endif
}
+
+ return Array.Empty();
}
}
+ finally
+ {
+ _ipcSemaphore.Release();
+ }
}
private MouseWithoutBordersSettings Settings { get; set; }
@@ -464,14 +579,14 @@ private Task StartMachineStatusPollingThread(Task previousThreadTask, Cancellati
while (!token.IsCancellationRequested)
{
- Dictionary states = null;
+ Dictionary states = null;
try
{
states = (await PollMachineSocketStateAsync())?.ToDictionary(s => s.Name, StringComparer.OrdinalIgnoreCase);
}
catch (Exception ex)
{
- Logger.LogInfo($"Poll ISettingsSyncHelper.MachineSocketState error: {ex}");
+ Logger.LogInfo($"Poll MachineSocketState error: {ex}");
continue;
}