mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-29 15:36:57 +01:00
Compare commits
2 Commits
user/yeela
...
shawn/sett
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0754a3e25 | ||
|
|
b69b991d4b |
@@ -8,6 +8,9 @@
|
||||
|
||||
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
|
||||
<!-- Suppress CA1416 for Windows-specific APIs that are used in PowerToys which only runs on Windows 10.0.19041.0+ -->
|
||||
<WarningsNotAsErrors>IL2081;CsWinRT1028;CA1416;$(WarningsNotAsErrors)</WarningsNotAsErrors>
|
||||
<!-- Suppress IL2026/IL3050 for JSON serialization in specific scenarios (backup/restore, CLI commands) -->
|
||||
<!-- Suppress IL2067/IL2070/IL2072/IL2075/IL2087/IL2098 for reflection in CLI/DSC command utilities -->
|
||||
<!-- Suppress IL3000/IL3002 for Assembly.Location and Marshal.GetHINSTANCE in single-file/AOT scenarios -->
|
||||
<WarningsNotAsErrors>IL2026;IL2067;IL2070;IL2072;IL2075;IL2081;IL2087;IL2098;IL3000;IL3002;IL3050;CsWinRT1028;CA1416;$(WarningsNotAsErrors)</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Command types for IPC protocol.
|
||||
/// Must match client-side enum in Settings.UI\Helpers\MouseWithoutBordersIpcClient.cs
|
||||
/// </summary>
|
||||
internal enum IpcCommandType : byte
|
||||
{
|
||||
Shutdown = 1,
|
||||
Reconnect = 2,
|
||||
GenerateNewKey = 3,
|
||||
ConnectToMachine = 4,
|
||||
RequestMachineSocketState = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AOT-compatible IPC server for MouseWithoutBorders Settings communication.
|
||||
/// Replaces StreamJsonRpc with manual NamedPipe protocol.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a single client connection
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a length-prefixed UTF-8 string
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a length-prefixed UTF-8 string
|
||||
/// </summary>
|
||||
private static void WriteString(BinaryWriter writer, string value)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(value);
|
||||
writer.Write(bytes.Length);
|
||||
writer.Write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for handling IPC commands.
|
||||
/// Implemented by SettingsSyncHelper in Program.cs
|
||||
/// </summary>
|
||||
internal interface ISettingsSyncHandler
|
||||
{
|
||||
void Shutdown();
|
||||
|
||||
void Reconnect();
|
||||
|
||||
void GenerateNewKey();
|
||||
|
||||
void ConnectToMachine(string machineName, string securityKey);
|
||||
|
||||
Task<MachineSocketState[]> RequestMachineSocketStateAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Machine socket state for serialization.
|
||||
/// Uses SocketStatus from SocketStuff.cs in MouseWithoutBorders.Class namespace.
|
||||
/// </summary>
|
||||
public struct MachineSocketState
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public MouseWithoutBorders.Class.SocketStatus Status { get; set; }
|
||||
}
|
||||
@@ -19,6 +19,7 @@ using System.Globalization;
|
||||
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 @@ namespace MouseWithoutBorders.Class
|
||||
Task<MachineSocketState[]> RequestMachineSocketStateAsync();
|
||||
}
|
||||
|
||||
private sealed class SettingsSyncHelper : ISettingsSyncHelper
|
||||
private sealed class SettingsSyncHelper : ISettingsSyncHelper, ISettingsSyncHandler
|
||||
{
|
||||
public Task<ISettingsSyncHelper.MachineSocketState[]> RequestMachineSocketStateAsync()
|
||||
{
|
||||
@@ -299,6 +300,28 @@ namespace MouseWithoutBorders.Class
|
||||
return Task.FromResult(machineStates.Select((state) => new ISettingsSyncHelper.MachineSocketState { Name = state.Key, Status = state.Value }).ToArray());
|
||||
}
|
||||
|
||||
// ISettingsSyncHandler implementation (AOT-compatible)
|
||||
Task<MachineSocketState[]> ISettingsSyncHandler.RequestMachineSocketStateAsync()
|
||||
{
|
||||
var machineStates = new Dictionary<string, SocketStatus>();
|
||||
if (Common.Sk == null || Common.Sk.TcpSockets == null)
|
||||
{
|
||||
return Task.FromResult(Array.Empty<MachineSocketState>());
|
||||
}
|
||||
|
||||
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 @@ namespace MouseWithoutBorders.Class
|
||||
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<SettingsSyncHelper>.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()
|
||||
|
||||
@@ -51,7 +51,11 @@ using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
namespace MouseWithoutBorders.Class
|
||||
{
|
||||
internal enum SocketStatus : int
|
||||
/// <summary>
|
||||
/// Socket status enumeration - made public for IPC serialization.
|
||||
/// Must match Settings.UI.Library\MouseWithoutBordersIpcModels.cs
|
||||
/// </summary>
|
||||
public enum SocketStatus : int
|
||||
{
|
||||
NA = 0,
|
||||
Resolving = 1,
|
||||
|
||||
@@ -24,6 +24,7 @@ using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Exceptions;
|
||||
|
||||
using Clipboard = MouseWithoutBorders.Core.Clipboard;
|
||||
using SocketStatus = MouseWithoutBorders.Class.SocketStatus;
|
||||
using Thread = MouseWithoutBorders.Core.Thread;
|
||||
|
||||
// Log is enough
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\Common.SelfContained.props" />
|
||||
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup Condition="'$(EnableSettingsAOT)' == 'true'">
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
||||
@@ -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<T> : ICmdLineRepresentable
|
||||
public class GenericProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : ICmdLineRepresentable
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public T Value { get; set; }
|
||||
|
||||
@@ -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 interface ICmdLineRepresentable
|
||||
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 interface ICmdLineRepresentable
|
||||
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 interface ICmdLineRepresentable
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Socket status enumeration for MouseWithoutBorders machine connections.
|
||||
/// Must match the enum in MouseWithoutBorders\App\Class\Program.cs
|
||||
/// </summary>
|
||||
public enum SocketStatus : int
|
||||
{
|
||||
NA = 0,
|
||||
Resolving = 1,
|
||||
Connecting = 2,
|
||||
Handshaking = 3,
|
||||
Error = 4,
|
||||
ForceClosed = 5,
|
||||
InvalidKey = 6,
|
||||
Timeout = 7,
|
||||
SendError = 8,
|
||||
Connected = 9,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the connection state of a machine in the MouseWithoutBorders network.
|
||||
/// Used for IPC communication between Settings UI and MouseWithoutBorders service.
|
||||
/// </summary>
|
||||
public struct MachineSocketState
|
||||
{
|
||||
[JsonPropertyName("Name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("Status")]
|
||||
public SocketStatus Status { get; set; }
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\Common.SelfContained.props" />
|
||||
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
<PropertyGroup>
|
||||
<Description>PowerToys Settings UI Library</Description>
|
||||
<AssemblyName>PowerToys.Settings.UI.Lib</AssemblyName>
|
||||
|
||||
@@ -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,88 +11,111 @@ using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When adding a new PowerToys module, add it to both InitializeFactories() and InitializeTypes() methods.
|
||||
/// </remarks>
|
||||
public class SettingsFactory
|
||||
{
|
||||
private readonly SettingsUtils _settingsUtils;
|
||||
private readonly Dictionary<string, Func<IHotkeyConfig>> _settingsFactories;
|
||||
private readonly Dictionary<string, Type> _settingsTypes;
|
||||
|
||||
public SettingsFactory(SettingsUtils settingsUtils)
|
||||
{
|
||||
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
|
||||
_settingsTypes = DiscoverSettingsTypes();
|
||||
_settingsFactories = InitializeFactories();
|
||||
_settingsTypes = InitializeTypes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically discovers all Settings types that implement IHotkeyConfig
|
||||
/// Static registry of all module settings factories.
|
||||
/// IMPORTANT: When adding a new module, add it here.
|
||||
/// </summary>
|
||||
private Dictionary<string, Type> DiscoverSettingsTypes()
|
||||
private Dictionary<string, Func<IHotkeyConfig>> InitializeFactories()
|
||||
{
|
||||
var settingsTypes = new Dictionary<string, Type>();
|
||||
|
||||
// Get the Settings.UI.Library assembly
|
||||
var assembly = Assembly.GetAssembly(typeof(IHotkeyConfig));
|
||||
if (assembly == null)
|
||||
return new Dictionary<string, Func<IHotkeyConfig>>
|
||||
{
|
||||
return settingsTypes;
|
||||
["GeneralSettings"] = () => SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["AdvancedPaste"] = () => SettingsRepository<AdvancedPasteSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["AlwaysOnTop"] = () => SettingsRepository<AlwaysOnTopSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["ColorPicker"] = () => SettingsRepository<ColorPickerSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["CropAndLock"] = () => SettingsRepository<CropAndLockSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["CursorWrap"] = () => SettingsRepository<CursorWrapSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["FindMyMouse"] = () => SettingsRepository<FindMyMouseSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["LightSwitch"] = () => SettingsRepository<LightSwitchSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["MeasureTool"] = () => SettingsRepository<MeasureToolSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["MouseHighlighter"] = () => SettingsRepository<MouseHighlighterSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["MouseJump"] = () => SettingsRepository<MouseJumpSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["MousePointerCrosshairs"] = () => SettingsRepository<MousePointerCrosshairsSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["MouseWithoutBorders"] = () => SettingsRepository<MouseWithoutBordersSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["Peek"] = () => SettingsRepository<PeekSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["PowerLauncher"] = () => SettingsRepository<PowerLauncherSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["PowerOCR"] = () => SettingsRepository<PowerOcrSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["ShortcutGuide"] = () => SettingsRepository<ShortcutGuideSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
["Workspaces"] = () => SettingsRepository<WorkspacesSettings>.GetInstance(_settingsUtils).SettingsConfig,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static registry of module name to settings type mapping.
|
||||
/// IMPORTANT: When adding a new module, add it here.
|
||||
/// </summary>
|
||||
private Dictionary<string, Type> InitializeTypes()
|
||||
{
|
||||
return new Dictionary<string, Type>
|
||||
{
|
||||
["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),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a settings instance for the specified module using SettingsRepository.
|
||||
/// AOT-compatible: uses static factory lookup instead of reflection.
|
||||
/// </summary>
|
||||
/// <param name="moduleKey">The module key/name</param>
|
||||
/// <returns>The settings instance implementing IHotkeyConfig, or null if not found</returns>
|
||||
public IHotkeyConfig GetSettings(string moduleKey)
|
||||
{
|
||||
if (!_settingsFactories.TryGetValue(moduleKey, out var factory))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
return factory();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error scanning assembly {assembly.FullName}: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"Error getting Settings for {moduleKey}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return settingsTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets fresh settings from disk for the specified module.
|
||||
/// AOT-compatible: uses static type dispatch instead of MakeGenericMethod.
|
||||
/// </summary>
|
||||
public IHotkeyConfig GetFreshSettings(string moduleKey)
|
||||
{
|
||||
if (!_settingsTypes.TryGetValue(moduleKey, out var settingsType))
|
||||
@@ -104,20 +125,8 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
|
||||
try
|
||||
{
|
||||
// Create a generic method call to _settingsUtils.GetSettingsOrDefault<T>(moduleKey)
|
||||
var getSettingsMethod = typeof(SettingsUtils).GetMethod("GetSettingsOrDefault", new[] { typeof(string), typeof(string) });
|
||||
var genericMethod = getSettingsMethod?.MakeGenericMethod(settingsType);
|
||||
|
||||
// Call GetSettingsOrDefault<T>(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;
|
||||
string actualModuleKey = moduleKey == "GeneralSettings" ? string.Empty : moduleKey;
|
||||
return GetFreshSettingsForType(settingsType, actualModuleKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -127,35 +136,33 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a settings instance for the specified module using SettingsRepository
|
||||
/// Static dispatch for GetSettingsOrDefault using pattern matching.
|
||||
/// Replaces reflection-based MakeGenericMethod/Invoke pattern.
|
||||
/// </summary>
|
||||
/// <param name="moduleKey">The module key/name</param>
|
||||
/// <returns>The settings instance implementing IHotkeyConfig, or null if not found</returns>
|
||||
public IHotkeyConfig GetSettings(string moduleKey)
|
||||
private IHotkeyConfig GetFreshSettingsForType(Type settingsType, string moduleKey)
|
||||
{
|
||||
if (!_settingsTypes.TryGetValue(moduleKey, out var settingsType))
|
||||
return settingsType.Name switch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error getting Settings for {moduleKey}: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
nameof(GeneralSettings) => _settingsUtils.GetSettingsOrDefault<GeneralSettings>(moduleKey, "settings.json"),
|
||||
nameof(AdvancedPasteSettings) => _settingsUtils.GetSettingsOrDefault<AdvancedPasteSettings>(moduleKey, "settings.json"),
|
||||
nameof(AlwaysOnTopSettings) => _settingsUtils.GetSettingsOrDefault<AlwaysOnTopSettings>(moduleKey, "settings.json"),
|
||||
nameof(ColorPickerSettings) => _settingsUtils.GetSettingsOrDefault<ColorPickerSettings>(moduleKey, "settings.json"),
|
||||
nameof(CropAndLockSettings) => _settingsUtils.GetSettingsOrDefault<CropAndLockSettings>(moduleKey, "settings.json"),
|
||||
nameof(CursorWrapSettings) => _settingsUtils.GetSettingsOrDefault<CursorWrapSettings>(moduleKey, "settings.json"),
|
||||
nameof(FindMyMouseSettings) => _settingsUtils.GetSettingsOrDefault<FindMyMouseSettings>(moduleKey, "settings.json"),
|
||||
nameof(LightSwitchSettings) => _settingsUtils.GetSettingsOrDefault<LightSwitchSettings>(moduleKey, "settings.json"),
|
||||
nameof(MeasureToolSettings) => _settingsUtils.GetSettingsOrDefault<MeasureToolSettings>(moduleKey, "settings.json"),
|
||||
nameof(MouseHighlighterSettings) => _settingsUtils.GetSettingsOrDefault<MouseHighlighterSettings>(moduleKey, "settings.json"),
|
||||
nameof(MouseJumpSettings) => _settingsUtils.GetSettingsOrDefault<MouseJumpSettings>(moduleKey, "settings.json"),
|
||||
nameof(MousePointerCrosshairsSettings) => _settingsUtils.GetSettingsOrDefault<MousePointerCrosshairsSettings>(moduleKey, "settings.json"),
|
||||
nameof(MouseWithoutBordersSettings) => _settingsUtils.GetSettingsOrDefault<MouseWithoutBordersSettings>(moduleKey, "settings.json"),
|
||||
nameof(PeekSettings) => _settingsUtils.GetSettingsOrDefault<PeekSettings>(moduleKey, "settings.json"),
|
||||
nameof(PowerLauncherSettings) => _settingsUtils.GetSettingsOrDefault<PowerLauncherSettings>(moduleKey, "settings.json"),
|
||||
nameof(PowerOcrSettings) => _settingsUtils.GetSettingsOrDefault<PowerOcrSettings>(moduleKey, "settings.json"),
|
||||
nameof(ShortcutGuideSettings) => _settingsUtils.GetSettingsOrDefault<ShortcutGuideSettings>(moduleKey, "settings.json"),
|
||||
nameof(WorkspacesSettings) => _settingsUtils.GetSettingsOrDefault<WorkspacesSettings>(moduleKey, "settings.json"),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -164,7 +171,7 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
/// <returns>List of module names</returns>
|
||||
public List<string> GetAvailableModuleNames()
|
||||
{
|
||||
return _settingsTypes.Keys.ToList();
|
||||
return new List<string>(_settingsTypes.Keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<SndPowerRenameSettings>))]
|
||||
[JsonSerializable(typeof(SndModuleSettings<SndShortcutGuideSettings>))]
|
||||
|
||||
// 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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// AOT-compatible command line utilities.
|
||||
/// Uses static type mapping instead of AppDomain reflection.
|
||||
/// </summary>
|
||||
public class CommandLineUtils
|
||||
{
|
||||
private static Type GetSettingsConfigType(string moduleName, Assembly settingsLibraryAssembly)
|
||||
private static readonly Dictionary<string, Type> _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 = null)
|
||||
{
|
||||
if (!_settingsTypes.TryGetValue(moduleName, out var settingsType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetSettingsConfigFor(settingsType, settingsUtils);
|
||||
}
|
||||
|
||||
public static ISettingsConfig GetSettingsConfigFor(string moduleName, SettingsUtils settingsUtils, Assembly settingsLibraryAssembly)
|
||||
{
|
||||
return GetSettingsConfigFor(GetSettingsConfigType(moduleName, settingsLibraryAssembly), settingsUtils);
|
||||
}
|
||||
|
||||
/// Executes SettingsRepository<moduleSettingsType>.GetInstance(settingsUtils).SettingsConfig
|
||||
/// <summary>
|
||||
/// Gets settings config for a given type using static dispatch.
|
||||
/// AOT-compatible: replaces MakeGenericType/GetMethod/Invoke pattern.
|
||||
/// </summary>
|
||||
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<GeneralSettings>.GetInstance));
|
||||
var settingsRepository = getInstanceInfo.Invoke(null, new object[] { settingsUtils });
|
||||
var settingsConfigProperty = getInstanceInfo.ReturnType.GetProperty(nameof(SettingsRepository<GeneralSettings>.SettingsConfig));
|
||||
return settingsConfigProperty.GetValue(settingsRepository) as ISettingsConfig;
|
||||
}
|
||||
|
||||
public static Assembly GetSettingsAssembly()
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.FirstOrDefault(a => a.GetName().Name == "PowerToys.Settings.UI.Lib");
|
||||
}
|
||||
|
||||
public static object GetPropertyValue(string propertyName, ISettingsConfig settingsConfig)
|
||||
{
|
||||
var (settingInfo, properties) = LocateSetting(propertyName, settingsConfig);
|
||||
return settingInfo.GetValue(properties);
|
||||
return moduleSettingsType.Name switch
|
||||
{
|
||||
nameof(GeneralSettings) => SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(AdvancedPasteSettings) => SettingsRepository<AdvancedPasteSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(AlwaysOnTopSettings) => SettingsRepository<AlwaysOnTopSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(AwakeSettings) => SettingsRepository<AwakeSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(CmdNotFoundSettings) => SettingsRepository<CmdNotFoundSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(ColorPickerSettings) => SettingsRepository<ColorPickerSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(CropAndLockSettings) => SettingsRepository<CropAndLockSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(CursorWrapSettings) => SettingsRepository<CursorWrapSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(EnvironmentVariablesSettings) => SettingsRepository<EnvironmentVariablesSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(FancyZonesSettings) => SettingsRepository<FancyZonesSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(FileLocksmithSettings) => SettingsRepository<FileLocksmithSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(FindMyMouseSettings) => SettingsRepository<FindMyMouseSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(HostsSettings) => SettingsRepository<HostsSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(ImageResizerSettings) => SettingsRepository<ImageResizerSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(KeyboardManagerSettings) => SettingsRepository<KeyboardManagerSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(LightSwitchSettings) => SettingsRepository<LightSwitchSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(MeasureToolSettings) => SettingsRepository<MeasureToolSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(MouseHighlighterSettings) => SettingsRepository<MouseHighlighterSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(MouseJumpSettings) => SettingsRepository<MouseJumpSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(MousePointerCrosshairsSettings) => SettingsRepository<MousePointerCrosshairsSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(MouseWithoutBordersSettings) => SettingsRepository<MouseWithoutBordersSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(NewPlusSettings) => SettingsRepository<NewPlusSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(PeekSettings) => SettingsRepository<PeekSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(PowerAccentSettings) => SettingsRepository<PowerAccentSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(PowerLauncherSettings) => SettingsRepository<PowerLauncherSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(PowerOcrSettings) => SettingsRepository<PowerOcrSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(PowerRenameSettings) => SettingsRepository<PowerRenameSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(PowerPreviewSettings) => SettingsRepository<PowerPreviewSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(RegistryPreviewSettings) => SettingsRepository<RegistryPreviewSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(ShortcutGuideSettings) => SettingsRepository<ShortcutGuideSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(WorkspacesSettings) => SettingsRepository<WorkspacesSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
nameof(ZoomItSettings) => SettingsRepository<ZoomItSettings>.GetInstance(settingsUtils).SettingsConfig,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Properties object from a settings config.
|
||||
/// For GeneralSettings, returns the settings itself. For others, returns the Properties property.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets enabled state for a specific module.
|
||||
/// AOT-compatible: static dispatch instead of reflection.
|
||||
/// </summary>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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 class CommandLineUtils
|
||||
return (propertiesType.GetProperty(propertyName), properties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a property from a settings config.
|
||||
/// </summary>
|
||||
public static object GetPropertyValue(string propertyName, ISettingsConfig settingsConfig)
|
||||
{
|
||||
var (settingInfo, properties) = LocateSetting(propertyName, settingsConfig);
|
||||
return settingInfo?.GetValue(properties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PropertyInfo for a setting property.
|
||||
/// </summary>
|
||||
public static PropertyInfo GetSettingPropertyInfo(string propertyName, ISettingsConfig settingsConfig)
|
||||
{
|
||||
return LocateSetting(propertyName, settingsConfig).SettingInfo;
|
||||
|
||||
@@ -47,9 +47,7 @@ public sealed class GetSettingCommandLineCommand
|
||||
{
|
||||
var modulesSettings = new Dictionary<string, Dictionary<string, object>>();
|
||||
|
||||
var settingsAssembly = CommandLineUtils.GetSettingsAssembly();
|
||||
var settingsUtils = SettingsUtils.Default;
|
||||
|
||||
var enabledModules = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.Enabled;
|
||||
|
||||
foreach (var (moduleName, settings) in settingNamesForModules)
|
||||
@@ -57,10 +55,10 @@ public sealed class GetSettingCommandLineCommand
|
||||
var moduleSettings = new Dictionary<string, object>();
|
||||
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);
|
||||
|
||||
@@ -115,7 +115,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -246,10 +246,8 @@ public sealed class SetAdditionalSettingsCommandLineCommand
|
||||
|
||||
public static void Execute(string moduleName, JsonDocument settings, SettingsUtils settingsUtils)
|
||||
{
|
||||
Assembly settingsLibraryAssembly = CommandLineUtils.GetSettingsAssembly();
|
||||
|
||||
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsLibraryAssembly);
|
||||
var settingsConfigType = settingsConfig.GetType();
|
||||
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils);
|
||||
var settingsConfigType = settingsConfig?.GetType();
|
||||
|
||||
if (!SupportedAdditionalPropertiesInfoForModules.TryGetValue(moduleName, out var additionalPropertiesInfo))
|
||||
{
|
||||
|
||||
@@ -27,11 +27,9 @@ public sealed class SetSettingCommandLineCommand
|
||||
|
||||
public static void Execute(string settingName, string settingValue, SettingsUtils settingsUtils)
|
||||
{
|
||||
Assembly settingsLibraryAssembly = CommandLineUtils.GetSettingsAssembly();
|
||||
|
||||
var (moduleName, propertyName) = ParseSettingName(settingName);
|
||||
|
||||
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsLibraryAssembly);
|
||||
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils);
|
||||
|
||||
var propertyInfo = CommandLineUtils.GetSettingPropertyInfo(propertyName, settingsConfig);
|
||||
if (propertyInfo == null)
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// AOT-compatible IPC client for MouseWithoutBorders service communication.
|
||||
/// Replaces StreamJsonRpc with manual NamedPipe protocol using length-prefixed messages.
|
||||
/// </summary>
|
||||
public sealed class MouseWithoutBordersIpcClient : IDisposable
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly BinaryWriter _writer;
|
||||
private readonly BinaryReader _reader;
|
||||
private readonly object _lock = new();
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Command types for IPC protocol.
|
||||
/// Must match server-side enum in MouseWithoutBorders Program.cs
|
||||
/// </summary>
|
||||
private enum CommandType : byte
|
||||
{
|
||||
Shutdown = 1,
|
||||
Reconnect = 2,
|
||||
GenerateNewKey = 3,
|
||||
ConnectToMachine = 4,
|
||||
RequestMachineSocketState = 5,
|
||||
}
|
||||
|
||||
public MouseWithoutBordersIpcClient(Stream stream)
|
||||
{
|
||||
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
_writer = new BinaryWriter(_stream, Encoding.UTF8, leaveOpen: true);
|
||||
_reader = new BinaryReader(_stream, Encoding.UTF8, leaveOpen: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends shutdown command to MouseWithoutBorders service
|
||||
/// </summary>
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
await SendCommandAsync(CommandType.Shutdown);
|
||||
await FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends reconnect command to MouseWithoutBorders service
|
||||
/// </summary>
|
||||
public async Task ReconnectAsync()
|
||||
{
|
||||
await SendCommandAsync(CommandType.Reconnect);
|
||||
await FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests generation of a new security key
|
||||
/// </summary>
|
||||
public async Task GenerateNewKeyAsync()
|
||||
{
|
||||
await SendCommandAsync(CommandType.GenerateNewKey);
|
||||
await FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests connection to a specific machine
|
||||
/// </summary>
|
||||
public async Task ConnectToMachineAsync(string machineName, string securityKey)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_writer.Write((byte)CommandType.ConnectToMachine);
|
||||
|
||||
// Write machine name (length-prefixed string)
|
||||
WriteString(machineName ?? string.Empty);
|
||||
|
||||
// Write security key (length-prefixed string)
|
||||
WriteString(securityKey ?? string.Empty);
|
||||
}
|
||||
|
||||
await FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests current state of all connected machines
|
||||
/// </summary>
|
||||
public async Task<MachineSocketState[]> RequestMachineSocketStateAsync()
|
||||
{
|
||||
// Send command
|
||||
await SendCommandAsync(CommandType.RequestMachineSocketState);
|
||||
await FlushAsync();
|
||||
|
||||
// Read response
|
||||
var jsonResponse = await ReadStringAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(jsonResponse))
|
||||
{
|
||||
return Array.Empty<MachineSocketState>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Use source-generated JSON serialization
|
||||
return JsonSerializer.Deserialize(jsonResponse, SettingsSerializationContext.Default.MachineSocketStateArray)
|
||||
?? Array.Empty<MachineSocketState>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to deserialize MachineSocketState: {ex.Message}");
|
||||
return Array.Empty<MachineSocketState>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the underlying stream asynchronously
|
||||
/// </summary>
|
||||
public async Task FlushAsync()
|
||||
{
|
||||
await _stream.FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a simple command without parameters
|
||||
/// </summary>
|
||||
private Task SendCommandAsync(CommandType command)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_writer.Write((byte)command);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a length-prefixed UTF-8 string
|
||||
/// </summary>
|
||||
private void WriteString(string value)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(value);
|
||||
_writer.Write(bytes.Length); // 4-byte length prefix
|
||||
_writer.Write(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a length-prefixed UTF-8 string asynchronously
|
||||
/// </summary>
|
||||
private async Task<string> ReadStringAsync()
|
||||
{
|
||||
var lengthBytes = new byte[4];
|
||||
var bytesRead = await _stream.ReadAsync(lengthBytes.AsMemory(0, 4));
|
||||
|
||||
if (bytesRead != 4)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var length = BitConverter.ToInt32(lengthBytes, 0);
|
||||
|
||||
// Max 1MB to prevent memory exhaustion
|
||||
if (length <= 0 || length > 1024 * 1024)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var stringBytes = new byte[length];
|
||||
bytesRead = await _stream.ReadAsync(stringBytes.AsMemory(0, length));
|
||||
|
||||
if (bytesRead != length)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(stringBytes);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_writer?.Dispose();
|
||||
_reader?.Dispose();
|
||||
|
||||
// Note: Do not dispose _stream as it's owned by the caller (NamedPipeClientStream)
|
||||
}
|
||||
}
|
||||
45
src/settings-ui/Settings.UI/Helpers/TypePreservation.cs
Normal file
45
src/settings-ui/Settings.UI/Helpers/TypePreservation.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Preserves types required by XAML for Native AOT compilation.
|
||||
/// Called from App constructor when BUILD_INFO_PUBLISH_AOT is defined.
|
||||
/// </summary>
|
||||
internal static class TypePreservation
|
||||
{
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.FontIconSource))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.PathIcon))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.SymbolIcon))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.SymbolIconSource))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ImageIcon))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.BitmapIcon))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.DataTemplate))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.DataTemplateSelector))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.NavigationView))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.NavigationViewItem))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.Frame))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.Page))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ContentControl))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ListView))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ListViewItem))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.Grid))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.StackPanel))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.Border))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.TextBlock))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.TextBox))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.Button))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ComboBox))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.CheckBox))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.UI.Xaml.Controls.ToggleSwitch))]
|
||||
public static void PreserveTypes()
|
||||
{
|
||||
// This method exists only to hold DynamicDependency attributes.
|
||||
// Called from App constructor to ensure types aren't trimmed during AOT compilation.
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\Common.SelfContained.props" />
|
||||
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
@@ -18,6 +19,31 @@
|
||||
<OutputPath>..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>PowerToys.Settings.pri</ProjectPriFileName>
|
||||
|
||||
<!-- .NET Performance Optimizations -->
|
||||
<TieredCompilation>true</TieredCompilation>
|
||||
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<ReadyToRunUseCrossgen2>true</ReadyToRunUseCrossgen2>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Additional optimizations for Release builds -->
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
|
||||
<PropertyGroup>
|
||||
<EnableSettingsAOT>true</EnableSettingsAOT>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(EnableSettingsAOT)' == 'true'">
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<DisableRuntimeMarshalling>false</DisableRuntimeMarshalling>
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Settings\Icons\Models\Azure.svg" />
|
||||
@@ -85,12 +111,12 @@
|
||||
<PackageReference Include="System.Private.Uri" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
<PackageReference Include="WinUIEx" />
|
||||
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
|
||||
<PackageReference Include="MessagePack" />
|
||||
<!-- StreamJsonRpc and MessagePack are excluded in AOT builds due to reflection dependencies -->
|
||||
<PackageReference Include="MessagePack" Condition="'$(EnableSettingsAOT)' != 'true'" />
|
||||
<PackageReference Include="StreamJsonRpc" Condition="'$(EnableSettingsAOT)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
|
||||
<PackageReference Include="StreamJsonRpc" />
|
||||
<!-- HACK: Microsoft.Extensions.Hosting is referenced, even if it is not used, to force dll versions to be the same as in other projects. Really only needed since the Experimentation APIs that are added in CI reference some net standard 2.0 assemblies. -->
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<!-- HACK: To make sure the version pulled in by Microsoft.Extensions.Hosting is current. -->
|
||||
@@ -134,10 +160,9 @@
|
||||
|
||||
<!-- No RID/Platform plumbing needed here. XamlIndexBuilder handles generation after its own Build. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- TODO: fix issues and reenable -->
|
||||
<PropertyGroup Condition="'$(EnableSettingsAOT)' != 'true'">
|
||||
<!-- These are caused by streamjsonrpc dependency on Microsoft.VisualStudio.Threading.Analyzers -->
|
||||
<!-- We might want to add that to the project and fix the issues as well -->
|
||||
<!-- Only relevant when StreamJsonRpc is included (non-AOT builds) -->
|
||||
<NoWarn>VSTHRD002;VSTHRD110;VSTHRD100;VSTHRD200;VSTHRD101</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -218,4 +243,9 @@
|
||||
<Message Importance="high" Text="[Settings] Building XamlIndexBuilder prior to compile. Views='$(MSBuildProjectDirectory)\SettingsXAML\Views' Out='$(GeneratedJsonFile)'" />
|
||||
<MSBuild Projects="..\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj" Targets="Build" Properties="Configuration=$(Configuration);Platform=Any CPU;TargetFramework=net9.0;XamlViewsDir=$(MSBuildProjectDirectory)\SettingsXAML\Views;GeneratedJsonFile=$(GeneratedJsonFile)" />
|
||||
</Target>
|
||||
|
||||
<!-- Build information defines -->
|
||||
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);BUILD_INFO_PUBLISH_AOT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -49,8 +49,12 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
|
||||
private const int RequiredArgumentsLaunchedFromRunnerQty = 10;
|
||||
|
||||
// IPC message queue for messages sent before IPC manager is initialized
|
||||
private static readonly System.Collections.Concurrent.ConcurrentQueue<string> PendingIPCMessages = new System.Collections.Concurrent.ConcurrentQueue<string>();
|
||||
|
||||
// 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 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
#if BUILD_INFO_PUBLISH_AOT
|
||||
Helpers.TypePreservation.PreserveTypes();
|
||||
#endif
|
||||
Logger.InitializeLogger(@"\Settings\Logs");
|
||||
|
||||
string appLanguage = LanguageHelper.LoadLanguage();
|
||||
@@ -207,19 +214,37 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
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.Start();
|
||||
ipcmanager = new TwoWayPipeMessageIPCManaged(settingsPipeName, ptPipeName, (string message) =>
|
||||
{
|
||||
if (IPCMessageReceivedCallback != null && message.Length > 0)
|
||||
{
|
||||
IPCMessageReceivedCallback(message);
|
||||
}
|
||||
});
|
||||
ipcmanager.Start();
|
||||
|
||||
GlobalHotkeyConflictManager.Initialize(message =>
|
||||
{
|
||||
ipcmanager.Send(message);
|
||||
return 0;
|
||||
// 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}");
|
||||
}
|
||||
});
|
||||
|
||||
if (!ShowOobe && !ShowScoobe)
|
||||
@@ -235,6 +260,9 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
// 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 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
// 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());
|
||||
@@ -319,6 +350,33 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
return ipcmanager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an IPC message, queuing it if the IPC manager is not yet initialized.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process all pending IPC messages after the IPC manager is initialized.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
@@ -27,133 +27,177 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
var bootTime = new System.Diagnostics.Stopwatch();
|
||||
bootTime.Start();
|
||||
|
||||
this.Activated += Window_Activated_SetIcon;
|
||||
|
||||
App.ThemeService.ThemeChanged += OnThemeChanged;
|
||||
App.ThemeService.ApplyTheme();
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
// Initialize remaining components asynchronously
|
||||
_ = InitializeAsync(hWnd, createHidden, bootTime);
|
||||
}
|
||||
|
||||
var loader = ResourceLoaderInstance.ResourceLoader;
|
||||
Title = App.IsElevated ? loader.GetString("SettingsWindow_AdminTitle") : loader.GetString("SettingsWindow_Title");
|
||||
|
||||
// send IPC Message
|
||||
ShellPage.SetDefaultSndMessageCallback(msg =>
|
||||
private async Task InitializeAsync(IntPtr hWnd, bool createHidden, System.Diagnostics.Stopwatch bootTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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
|
||||
});
|
||||
|
||||
// send IPC Message
|
||||
ShellPage.SetCheckForUpdatesMessageCallback(msg =>
|
||||
{
|
||||
App.GetTwoWayIPCManager()?.Send(msg);
|
||||
});
|
||||
|
||||
// open main window
|
||||
ShellPage.SetOpenMainWindowCallback(type =>
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
|
||||
App.OpenSettingsWindow(type));
|
||||
});
|
||||
|
||||
// open main window
|
||||
ShellPage.SetUpdatingGeneralSettingsCallback((ModuleType moduleType, bool isEnabled) =>
|
||||
{
|
||||
SettingsRepository<GeneralSettings> repository = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default);
|
||||
GeneralSettings generalSettingsConfig = repository.SettingsConfig;
|
||||
bool needToUpdate = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType) != isEnabled;
|
||||
|
||||
if (needToUpdate)
|
||||
// Load window placement asynchronously (non-blocking file I/O)
|
||||
if (!createHidden)
|
||||
{
|
||||
ModuleHelper.SetIsModuleEnabled(generalSettingsConfig, moduleType, isEnabled);
|
||||
var outgoing = new OutGoingGeneralSettings(generalSettingsConfig);
|
||||
|
||||
// Save settings to file
|
||||
SettingsUtils.Default.SaveSettings(generalSettingsConfig.ToJsonString());
|
||||
|
||||
// Send IPC message asynchronously to avoid blocking UI and potential recursive calls
|
||||
Task.Run(() =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
ShellPage.SendDefaultIPCMessage(outgoing.ToString());
|
||||
var placement = WindowHelper.DeserializePlacementOrDefault(hWnd);
|
||||
NativeMethods.SetWindowPlacement(hWnd, ref placement);
|
||||
});
|
||||
}
|
||||
|
||||
// Set up IPC callbacks on UI thread
|
||||
DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
|
||||
{
|
||||
// send IPC Message
|
||||
ShellPage.SetDefaultSndMessageCallback(msg =>
|
||||
{
|
||||
// Use SendIPCMessage which handles queuing if IPC is not yet initialized
|
||||
App.SendIPCMessage(msg);
|
||||
});
|
||||
|
||||
ShellPage.ShellHandler?.SignalGeneralDataUpdate();
|
||||
}
|
||||
|
||||
return needToUpdate;
|
||||
});
|
||||
|
||||
// open oobe
|
||||
ShellPage.SetOpenOobeCallback(() =>
|
||||
{
|
||||
if (App.GetOobeWindow() == null)
|
||||
{
|
||||
App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.Overview));
|
||||
}
|
||||
|
||||
App.GetOobeWindow().Activate();
|
||||
});
|
||||
|
||||
// open whats new window
|
||||
ShellPage.SetOpenWhatIsNewCallback(() =>
|
||||
{
|
||||
if (App.GetScoobeWindow() == null)
|
||||
{
|
||||
App.SetScoobeWindow(new ScoobeWindow());
|
||||
}
|
||||
|
||||
App.GetScoobeWindow().Activate();
|
||||
});
|
||||
|
||||
this.InitializeComponent();
|
||||
SetAppTitleBar();
|
||||
|
||||
// receive IPC Message
|
||||
App.IPCMessageReceivedCallback = (string msg) =>
|
||||
{
|
||||
if (ShellPage.ShellHandler.IPCResponseHandleList != null)
|
||||
{
|
||||
var success = JsonObject.TryParse(msg, out JsonObject json);
|
||||
if (success)
|
||||
// send IPC Message
|
||||
ShellPage.SetRestartAdminSndMessageCallback(msg =>
|
||||
{
|
||||
foreach (Action<JsonObject> handle in ShellPage.ShellHandler.IPCResponseHandleList)
|
||||
App.SendIPCMessage(msg);
|
||||
Environment.Exit(0); // close application
|
||||
});
|
||||
|
||||
// send IPC Message
|
||||
ShellPage.SetCheckForUpdatesMessageCallback(msg =>
|
||||
{
|
||||
App.SendIPCMessage(msg);
|
||||
});
|
||||
|
||||
// open main window
|
||||
ShellPage.SetOpenMainWindowCallback(type =>
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
|
||||
App.OpenSettingsWindow(type));
|
||||
});
|
||||
|
||||
// open main window
|
||||
ShellPage.SetUpdatingGeneralSettingsCallback((ModuleType moduleType, bool isEnabled) =>
|
||||
{
|
||||
SettingsRepository<GeneralSettings> repository = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default);
|
||||
GeneralSettings generalSettingsConfig = repository.SettingsConfig;
|
||||
bool needToUpdate = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType) != isEnabled;
|
||||
|
||||
if (needToUpdate)
|
||||
{
|
||||
handle(json);
|
||||
ModuleHelper.SetIsModuleEnabled(generalSettingsConfig, moduleType, isEnabled);
|
||||
var outgoing = new OutGoingGeneralSettings(generalSettingsConfig);
|
||||
|
||||
// Save settings to file
|
||||
SettingsUtils.Default.SaveSettings(generalSettingsConfig.ToJsonString());
|
||||
|
||||
// Send IPC message asynchronously to avoid blocking UI and potential recursive calls
|
||||
Task.Run(() =>
|
||||
{
|
||||
ShellPage.SendDefaultIPCMessage(outgoing.ToString());
|
||||
});
|
||||
|
||||
ShellPage.ShellHandler?.SignalGeneralDataUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
return needToUpdate;
|
||||
});
|
||||
|
||||
// open oobe
|
||||
ShellPage.SetOpenOobeCallback(() =>
|
||||
{
|
||||
Logger.LogError("Failed to parse JSON from IPC message.");
|
||||
}
|
||||
}
|
||||
};
|
||||
if (App.GetOobeWindow() == null)
|
||||
{
|
||||
App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.Overview));
|
||||
}
|
||||
|
||||
bootTime.Stop();
|
||||
App.GetOobeWindow().Activate();
|
||||
});
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
|
||||
// open whats new window
|
||||
ShellPage.SetOpenWhatIsNewCallback(() =>
|
||||
{
|
||||
if (App.GetScoobeWindow() == null)
|
||||
{
|
||||
App.SetScoobeWindow(new ScoobeWindow());
|
||||
}
|
||||
|
||||
App.GetScoobeWindow().Activate();
|
||||
});
|
||||
|
||||
// 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<JsonObject> 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()
|
||||
|
||||
@@ -16,7 +16,6 @@ using Microsoft.PowerToys.Settings.UI.Library;
|
||||
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;
|
||||
|
||||
@@ -24,7 +24,9 @@ using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
#if !BUILD_INFO_PUBLISH_AOT
|
||||
using StreamJsonRpc;
|
||||
#endif
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
@@ -235,20 +237,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
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 +264,73 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
Task<MachineSocketState[]> 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<SyncHelper> 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 +349,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private static NamedPipeClientStream syncHelperStream;
|
||||
|
||||
private async Task<SyncHelper> GetSettingsSyncHelperAsync()
|
||||
{
|
||||
try
|
||||
@@ -337,88 +385,154 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
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<ISettingsSyncHelper.MachineSocketState[]> PollMachineSocketStateAsync()
|
||||
private async Task<MachineSocketState[]> PollMachineSocketStateAsync()
|
||||
{
|
||||
using (await _ipcSemaphore.EnterAsync())
|
||||
await _ipcSemaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
using (var syncHelper = await GetSettingsSyncHelperAsync())
|
||||
{
|
||||
var task = syncHelper?.Endpoint?.RequestMachineSocketStateAsync();
|
||||
if (task != null)
|
||||
if (syncHelper != null)
|
||||
{
|
||||
return await task;
|
||||
}
|
||||
else
|
||||
{
|
||||
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<MachineSocketState>();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ipcSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private MouseWithoutBordersSettings Settings { get; set; }
|
||||
@@ -464,14 +578,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
Dictionary<string, ISettingsSyncHelper.MachineSocketState> states = null;
|
||||
Dictionary<string, MachineSocketState> 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user