Compare commits

...

5 Commits

Author SHA1 Message Date
Shawn Yuan (from Dev Box)
47dbb44a2c fixed icon missing issue 2026-02-11 17:14:00 +08:00
Shawn Yuan (from Dev Box)
ba1b97951c fix crash issue 2026-02-11 15:16:19 +08:00
Shawn Yuan (from Dev Box)
ad4a5cdfe7 fix compiling issue 2026-02-11 14:54:03 +08:00
Shawn Yuan (from Dev Box)
58f9b007a6 published settings aot 2026-02-11 11:37:22 +08:00
Shawn Yuan (from Dev Box)
1c538a781f init, need to resolve publish errors.
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
2026-02-10 14:34:29 +08:00
67 changed files with 767 additions and 178 deletions

View File

@@ -16,8 +16,8 @@ namespace ManagedCommon
{
public static void WaitForPowerToysRunner(int powerToysPID, Action act, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
var stackTrace = new StackTrace();
var assembly = Assembly.GetCallingAssembly().GetName();
// AOT-compatible: Use GetExecutingAssembly instead of GetCallingAssembly (not supported in AOT)
var assembly = Assembly.GetExecutingAssembly().GetName();
PowerToysTelemetry.Log.WriteEvent(new DebugEvent() { Message = $"[{assembly}][{memberName}]WaitForPowerToysRunner waiting for Event powerToysPID={powerToysPID}" });
Task.Run(() =>
{

View File

@@ -216,7 +216,7 @@ namespace Microsoft.PowerToys.UITest
Rectangle bounds = new Rectangle(0, 0, screenWidth, screenHeight);
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb))
{
using (Graphics g = Graphics.FromImage(bitmap))
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap))
{
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);

View File

@@ -6,11 +6,14 @@
<OutputType>Library</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<!-- UITestAutomation is a test library and should not use AOT -->
<PublishAot>false</PublishAot>
<InvariantGlobalization>false</InvariantGlobalization>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<PublishTrimmed>false</PublishTrimmed>
<!-- Suppress code analysis warnings for test library -->
<NoWarn>$(NoWarn);CA1305;CA1310;CA2101</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -19,6 +22,7 @@
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.Text.RegularExpressions" />
<PackageReference Include="CoenM.ImageSharp.ImageHash" />
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>

View File

@@ -62,11 +62,12 @@
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
<!-- Only enable Native AOT for Release builds to avoid System.Private.CoreLib.dll version conflicts during development -->
<!-- Temporarily disable Native AOT until C++ Desktop Development workload is installed -->
<!-- TODO: Re-enable after installing Desktop Development for C++ in Visual Studio -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
<PublishAot>true</PublishAot>
<PublishTrimmed>false</PublishTrimmed>
<PublishSingleFile>false</PublishSingleFile>
<PublishAot>false</PublishAot>
</PropertyGroup>
<!-- For Debug builds, use standard JIT compilation -->

View File

@@ -3,6 +3,13 @@
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<!-- Force WebView2 to use Win32/WinForms mode instead of WinRT mode -->
<!-- Required because Settings.UI.Library has UseWinUI=true which triggers WebView2UseWinRT -->
<PropertyGroup>
<WebView2UseWinRT>false</WebView2UseWinRT>
<WebView2EnableCsWinRTProjection>false</WebView2EnableCsWinRTProjection>
</PropertyGroup>
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<UseWindowsForms>true</UseWindowsForms>

View File

@@ -3,6 +3,13 @@
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<!-- Force WebView2 to use Win32/WinForms mode instead of WinRT mode -->
<!-- Required because Settings.UI.Library has UseWinUI=true which triggers WebView2UseWinRT -->
<PropertyGroup>
<WebView2UseWinRT>false</WebView2UseWinRT>
<WebView2EnableCsWinRTProjection>false</WebView2EnableCsWinRTProjection>
</PropertyGroup>
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<UseWindowsForms>true</UseWindowsForms>

View File

@@ -2,6 +2,12 @@
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<!-- Force WebView2 to use Win32/WinForms mode for unit tests -->
<PropertyGroup>
<WebView2UseWinRT>false</WebView2UseWinRT>
<WebView2EnableCsWinRTProjection>false</WebView2EnableCsWinRTProjection>
</PropertyGroup>
<PropertyGroup>
<AssemblyTitle>UnitTests-SvgPreviewHandler</AssemblyTitle>
<AssemblyDescription>PowerToys UnitTests-SvgPreviewHandler</AssemblyDescription>

View File

@@ -5,7 +5,7 @@
using System; // For Action
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.Controls
{

View File

@@ -2,7 +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.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.UI.Xaml;

View File

@@ -24,8 +24,8 @@
<DataTemplate x:DataType="local:QuickAccessItem">
<local:FlyoutMenuButton
AutomationProperties.Name="{x:Bind Title}"
Command="{x:Bind Command}"
CommandParameter="{x:Bind CommandParameter}"
Click="FlyoutMenuButton_Click"
Tag="{x:Bind}"
Visibility="{x:Bind Visible, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<local:FlyoutMenuButton.Content>
<TextBlock

View File

@@ -22,5 +22,13 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(QuickAccessList), new PropertyMetadata(null));
private void FlyoutMenuButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is QuickAccessItem item)
{
item.Command?.Execute(item.CommandParameter);
}
}
}
}

View File

@@ -3,21 +3,58 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
public class HotkeyConflictGroupData
public partial class HotkeyConflictGroupData : INotifyPropertyChanged
{
private bool _conflictIgnored;
private bool _isSystemConflict;
public HotkeyData Hotkey { get; set; }
public bool IsSystemConflict { get; set; }
public bool IsSystemConflict
{
get => _isSystemConflict;
set
{
if (_isSystemConflict != value)
{
_isSystemConflict = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShouldShowSysConflict));
}
}
}
public bool ConflictIgnored { get; set; }
public bool ConflictIgnored
{
get => _conflictIgnored;
set
{
if (_conflictIgnored != value)
{
_conflictIgnored = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ConflictVisible));
OnPropertyChanged(nameof(ShouldShowSysConflict));
}
}
}
public bool ConflictVisible => !ConflictIgnored;
public bool ShouldShowSysConflict => !ConflictIgnored && IsSystemConflict;
public List<ModuleHotkeyData> Modules { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,14 @@
// 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.
namespace Microsoft.PowerToys.Settings.UI.Library
{
/// <summary>
/// ICommand interface for WinUI 3 applications, compatible with Native AOT.
/// Extends System.Windows.Input.ICommand which is also recognized by WinUI3 XAML bindings.
/// </summary>
public interface ICommand : System.Windows.Input.ICommand
{
}
}

View File

@@ -13,7 +13,6 @@ using System.Management;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Windows.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;

View File

@@ -5,6 +5,8 @@
<PropertyGroup>
<Description>PowerToys Settings UI Library</Description>
<AssemblyName>PowerToys.Settings.UI.Lib</AssemblyName>
<!-- Enable WinUI to access Microsoft.UI.Xaml.Input.ICommand for AOT-compatible command binding -->
<UseWinUI>true</UseWinUI>
</PropertyGroup>
<ItemGroup>
@@ -15,6 +17,7 @@
<ItemGroup>
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
</ItemGroup>
<ItemGroup>

View File

@@ -153,6 +153,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonSerializable(typeof(List<VcpValueInfo>))]
[JsonSerializable(typeof(SettingsUILibraryHelpers.SearchLocation))]
// Search Index Types (for AOT compatibility)
[JsonSerializable(typeof(SettingsUILibrary.SettingEntry))]
[JsonSerializable(typeof(SettingsUILibrary.SettingEntry[]))]
[JsonSerializable(typeof(SettingsUILibrary.EntryType))]
// AdvancedPaste AI Provider Types (for AOT compatibility)
[JsonSerializable(typeof(PasteAIConfiguration))]
[JsonSerializable(typeof(PasteAIProviderDefinition))]

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands
{

View File

@@ -2,32 +2,36 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Windows.Input;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands
{
public class RelayCommand : ICommand
// Preserve for AOT - ensure command execution methods are not trimmed
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)]
public class RelayCommand : Microsoft.PowerToys.Settings.UI.Library.ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
private readonly Func<bool>? _canExecute;
public event EventHandler CanExecuteChanged;
public event EventHandler? CanExecuteChanged;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
public RelayCommand(Action execute, Func<bool>? canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute();
public bool CanExecute(object? parameter) => _canExecute == null || _canExecute();
public void Execute(object parameter) => _execute();
public void Execute(object? parameter) => _execute();
public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

View File

@@ -2,33 +2,47 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Windows.Input;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands
{
public class RelayCommand<T> : ICommand
// Preserve for AOT - ensure command execution methods are not trimmed
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)]
public class RelayCommand<T> : Microsoft.PowerToys.Settings.UI.Library.ICommand
{
private readonly Action<T> execute;
private readonly Func<T, bool> canExecute;
private readonly Func<T, bool>? canExecute;
public event EventHandler CanExecuteChanged;
public event EventHandler? CanExecuteChanged;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
public RelayCommand(Action<T> execute, Func<T, bool>? canExecute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
this.canExecute = canExecute;
}
public bool CanExecute(object parameter) => canExecute == null || canExecute((T)parameter);
public bool CanExecute(object? parameter)
{
// AOT-friendly: no reflection, just direct cast
// The null-forgiving operator is safe here because we're just checking CanExecute
return canExecute == null || canExecute((T)parameter!);
}
public void Execute(object parameter) => execute((T)parameter);
public void Execute(object? parameter)
{
// AOT-friendly: simple cast, no reflection or type checking
// This matches the original main branch behavior exactly
execute((T)parameter!);
}
public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Windows;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;

View File

@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
@@ -30,7 +29,7 @@ public sealed partial class ImageResizerFitToStringConverter : IValueConverter
fitText;
}
return DependencyProperty.UnsetValue;
return Microsoft.UI.Xaml.DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
@@ -38,7 +37,7 @@ public sealed partial class ImageResizerSizeToAccessibleTextConverter : IValueCo
{
(string presetName, string nameId) => FormatNameText(presetName, nameId),
(ImageSize preset, string _) => FormatDescriptionText(preset),
_ => DependencyProperty.UnsetValue,
_ => Microsoft.UI.Xaml.DependencyProperty.UnsetValue,
};
}
@@ -46,14 +45,14 @@ public sealed partial class ImageResizerSizeToAccessibleTextConverter : IValueCo
{
return AccessibilityFormats.TryGetValue(nameId, out string format) ?
string.Format(CultureInfo.CurrentCulture, format, presetName) :
DependencyProperty.UnsetValue;
Microsoft.UI.Xaml.DependencyProperty.UnsetValue;
}
private object FormatDescriptionText(ImageSize preset)
{
if (preset == null)
{
return DependencyProperty.UnsetValue;
return Microsoft.UI.Xaml.DependencyProperty.UnsetValue;
}
string fitText = _fitConverter.Convert(preset.Fit, typeof(string), null, null) as string;

View File

@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
@@ -31,7 +30,7 @@ public sealed partial class ImageResizerUnitToStringConverter : IValueConverter
unitText;
}
return DependencyProperty.UnsetValue;
return Microsoft.UI.Xaml.DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -20,7 +20,8 @@ namespace Microsoft.PowerToys.Settings.UI.Converters
}
else
{
return (Microsoft.UI.Xaml.Controls.InfoBarSeverity)Enum.Parse(typeof(Microsoft.UI.Xaml.Controls.InfoBarSeverity), (string)value, true);
// Use generic overload for AOT compatibility (IL2026)
return Enum.Parse<Microsoft.UI.Xaml.Controls.InfoBarSeverity>((string)value, ignoreCase: true);
}
}
catch

View File

@@ -2,34 +2,35 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
internal sealed partial class AsyncCommand : ICommand
internal sealed partial class AsyncCommand : Microsoft.PowerToys.Settings.UI.Library.ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private readonly Func<bool>? _canExecute;
public event EventHandler CanExecuteChanged;
public event EventHandler? CanExecuteChanged;
public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null)
public AsyncCommand(Func<Task> execute, Func<bool>? canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
public bool CanExecute(object? parameter)
{
return _canExecute == null || _canExecute();
}
public async void Execute(object parameter)
public async void Execute(object? parameter)
{
await _execute();
}

View File

@@ -16,7 +16,8 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class CHOOSEFONT
{
public int lStructSize = Marshal.SizeOf(typeof(CHOOSEFONT));
// Use generic overload for AOT compatibility (IL3050)
public int lStructSize = Marshal.SizeOf<CHOOSEFONT>();
public IntPtr hwndOwner = IntPtr.Zero;
public IntPtr hDC = IntPtr.Zero;
public IntPtr lpLogFont = IntPtr.Zero;

View File

@@ -2,59 +2,60 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Windows.Input;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
public partial class RelayCommand : ICommand
public partial class RelayCommand : Microsoft.PowerToys.Settings.UI.Library.ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
private readonly Func<bool>? _canExecute;
public event EventHandler CanExecuteChanged;
public event EventHandler? CanExecuteChanged;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
public RelayCommand(Action execute, Func<bool>? canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute();
public bool CanExecute(object? parameter) => _canExecute == null || _canExecute();
public void Execute(object parameter) => _execute();
public void Execute(object? parameter) => _execute();
public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "abstract T and abstract")]
public partial class RelayCommand<T> : ICommand
public partial class RelayCommand<T> : Microsoft.PowerToys.Settings.UI.Library.ICommand
{
private readonly Action<T> execute;
private readonly Func<T, bool> canExecute;
private readonly Func<T, bool>? canExecute;
public event EventHandler CanExecuteChanged;
public event EventHandler? CanExecuteChanged;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
public RelayCommand(Action<T> execute, Func<T, bool>? canExecute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
this.canExecute = canExecute;
}
public bool CanExecute(object parameter) => canExecute == null || canExecute((T)parameter);
public bool CanExecute(object? parameter) => canExecute == null || canExecute((T)parameter!);
public void Execute(object parameter) => execute((T)parameter);
public void Execute(object? parameter) => execute((T)parameter!);
public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

View File

@@ -0,0 +1,161 @@
// 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.
// This is a local copy of Common.UI.SettingsDeepLink for AOT compatibility.
// Common.UI cannot be referenced in AOT builds because it depends on WPF.
#if BUILD_INFO_PUBLISH_AOT
using System;
using System.Diagnostics;
using System.IO;
using ManagedCommon;
namespace Common.UI
{
public static class SettingsDeepLink
{
public enum SettingsWindow
{
Dashboard = 0,
Overview,
AlwaysOnTop,
Awake,
ColorPicker,
CmdNotFound,
LightSwitch,
FancyZones,
FileLocksmith,
Run,
ImageResizer,
KBM,
MouseUtils,
MouseWithoutBorders,
Peek,
PowerAccent,
PowerLauncher,
PowerPreview,
PowerRename,
FileExplorer,
ShortcutGuide,
Hosts,
MeasureTool,
PowerOCR,
Workspaces,
RegistryPreview,
CropAndLock,
EnvironmentVariables,
AdvancedPaste,
NewPlus,
CmdPal,
ZoomIt,
PowerDisplay,
}
private static string SettingsWindowNameToString(SettingsWindow value)
{
switch (value)
{
case SettingsWindow.Dashboard:
return "Dashboard";
case SettingsWindow.Overview:
return "Overview";
case SettingsWindow.AlwaysOnTop:
return "AlwaysOnTop";
case SettingsWindow.Awake:
return "Awake";
case SettingsWindow.ColorPicker:
return "ColorPicker";
case SettingsWindow.CmdNotFound:
return "CmdNotFound";
case SettingsWindow.LightSwitch:
return "LightSwitch";
case SettingsWindow.FancyZones:
return "FancyZones";
case SettingsWindow.FileLocksmith:
return "FileLocksmith";
case SettingsWindow.Run:
return "Run";
case SettingsWindow.ImageResizer:
return "ImageResizer";
case SettingsWindow.KBM:
return "KBM";
case SettingsWindow.MouseUtils:
return "MouseUtils";
case SettingsWindow.MouseWithoutBorders:
return "MouseWithoutBorders";
case SettingsWindow.Peek:
return "Peek";
case SettingsWindow.PowerAccent:
return "PowerAccent";
case SettingsWindow.PowerLauncher:
return "PowerLauncher";
case SettingsWindow.PowerPreview:
return "PowerPreview";
case SettingsWindow.PowerRename:
return "PowerRename";
case SettingsWindow.FileExplorer:
return "FileExplorer";
case SettingsWindow.ShortcutGuide:
return "ShortcutGuide";
case SettingsWindow.Hosts:
return "Hosts";
case SettingsWindow.MeasureTool:
return "MeasureTool";
case SettingsWindow.PowerOCR:
return "PowerOcr";
case SettingsWindow.Workspaces:
return "Workspaces";
case SettingsWindow.RegistryPreview:
return "RegistryPreview";
case SettingsWindow.CropAndLock:
return "CropAndLock";
case SettingsWindow.EnvironmentVariables:
return "EnvironmentVariables";
case SettingsWindow.AdvancedPaste:
return "AdvancedPaste";
case SettingsWindow.NewPlus:
return "NewPlus";
case SettingsWindow.CmdPal:
return "CmdPal";
case SettingsWindow.ZoomIt:
return "ZoomIt";
case SettingsWindow.PowerDisplay:
return "PowerDisplay";
default:
{
return string.Empty;
}
}
}
public static void OpenSettings(SettingsWindow window)
{
try
{
var exePath = Path.Combine(
PowerToysPathResolver.GetPowerToysInstallPath(),
"PowerToys.exe");
if (exePath == null || !File.Exists(exePath))
{
Logger.LogError($"Failed to find powertoys exe path, {exePath}");
return;
}
var args = "--open-settings=" + SettingsWindowNameToString(window);
Process.Start(new ProcessStartInfo
{
FileName = exePath,
Arguments = args,
UseShellExecute = false,
});
}
catch (Exception ex)
{
Logger.LogError(ex.Message);
}
}
}
}
#endif

View File

@@ -6,8 +6,9 @@ using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.Management.Deployment;
using Windows.System;
@@ -16,7 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
/// <summary>
/// Helper class to manage installation status and installation command for a Microsoft Store extension.
/// </summary>
public class StoreExtensionHelper : INotifyPropertyChanged
public partial class StoreExtensionHelper : INotifyPropertyChanged
{
private readonly string _packageFamilyName;
private readonly string _storeUri;

View File

@@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.Dotnet.AotCompatibility.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<RootNamespace>Microsoft.PowerToys.Settings.UI</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
@@ -19,6 +21,25 @@
<!-- 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>
</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>
<!-- Use environmental tools to bypass VS version detection issue with VS 2026 -->
<IlcUseEnvironmentalTools>true</IlcUseEnvironmentalTools>
<!-- Trim is mandatory for AOT, but disable reflection removal to preserve WPF compatibility -->
<IlcDisableReflection>false</IlcDisableReflection>
<!-- Suppress trim/AOT warnings from third-party assemblies and XAML Command binding type checks -->
<NoWarn>$(NoWarn);IL2026;IL2060;IL2070;IL2071;IL2072;IL2075;IL2076;IL2104;IL3000;IL3002;IL3050;IL3053;WMC1121</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\Settings\Icons\Models\Azure.svg" />
<None Remove="Assets\Settings\Icons\Models\FoundryLocal.svg" />
@@ -62,6 +83,14 @@
</ItemGroup>
<ItemGroup>
<!-- Navigation Icons - PNG files (required for AOT publish) -->
<Content Include="Assets\Settings\Icons\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- Module Images - PNG files (required for AOT publish) -->
<Content Include="Assets\Settings\Modules\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- AI Model Provider Icons - SVG files -->
<Content Include="Assets\Settings\Icons\Models\*.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -114,7 +143,7 @@
<ItemGroup>
<!-- HACK: Common.UI is referenced, even if it is not used, to force dll versions to be the same as in other projects that use it. It's still unclear why this is the case, but this is need for flattening the install directory. -->
<ProjectReference Include="..\..\common\Common.Search\Common.Search.csproj" />
<ProjectReference Include="..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Condition="'$(EnableSettingsAOT)' != 'true'" Include="..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\common\AllExperiments\AllExperiments.csproj" />
<ProjectReference Include="..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\common\interop\PowerToys.Interop.vcxproj" />
@@ -139,7 +168,7 @@
<!-- TODO: fix issues and reenable -->
<!-- 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 -->
<NoWarn>VSTHRD002;VSTHRD110;VSTHRD100;VSTHRD200;VSTHRD101</NoWarn>
<NoWarn>$(NoWarn);VSTHRD002;VSTHRD110;VSTHRD100;VSTHRD200;VSTHRD101</NoWarn>
</PropertyGroup>
<!-- Removed hard-coded resource exclusion. -->
@@ -219,4 +248,15 @@
<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 for AOT -->
<PropertyGroup Condition=" '$(PublishAot)' == 'true' ">
<DefineConstants>$(DefineConstants);BUILD_INFO_PUBLISH_AOT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(PublishTrimmed)' == 'true' ">
<DefineConstants>$(DefineConstants);BUILD_INFO_PUBLISH_TRIMMED</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(CIBuild)' == 'true' ">
<DefineConstants>$(DefineConstants);BUILD_INFO_CIBUILD</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -17,6 +17,7 @@ using System.Threading;
using System.Threading.Tasks;
using Common.Search.FuzzSearch;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.Windows.ApplicationModel.Resources;
using Settings.UI.Library;
@@ -33,7 +34,6 @@ namespace Microsoft.PowerToys.Settings.UI.Services
private static bool _isIndexBuilt;
private static bool _isIndexBuilding;
private const string PrebuiltIndexResourceName = "Microsoft.PowerToys.Settings.UI.Assets.search.index.json";
private static JsonSerializerOptions _serializerOptions = new() { PropertyNameCaseInsensitive = true };
public static ImmutableArray<SettingEntry> Index
{
@@ -121,7 +121,8 @@ namespace Microsoft.PowerToys.Settings.UI.Services
return;
}
metadataList = JsonSerializer.Deserialize<SettingEntry[]>(json, _serializerOptions);
// Use source-generated deserializer for AOT compatibility (IL2026/IL3050)
metadataList = JsonSerializer.Deserialize(json, SettingsSerializationContext.Default.SettingEntryArray);
}
catch (Exception ex)
{
@@ -262,6 +263,7 @@ namespace Microsoft.PowerToys.Settings.UI.Services
.ToList();
}
// AOT-compatible type lookup using switch expression instead of reflection (IL2026)
private static Type GetPageTypeFromName(string pageTypeName)
{
if (string.IsNullOrEmpty(pageTypeName))
@@ -276,8 +278,43 @@ namespace Microsoft.PowerToys.Settings.UI.Services
return cached;
}
var assembly = typeof(GeneralPage).Assembly;
var type = assembly.GetType($"Microsoft.PowerToys.Settings.UI.Views.{pageTypeName}");
// Use compile-time known types instead of Assembly.GetType for AOT compatibility
var type = pageTypeName switch
{
nameof(DashboardPage) => typeof(DashboardPage),
nameof(GeneralPage) => typeof(GeneralPage),
nameof(AdvancedPastePage) => typeof(AdvancedPastePage),
nameof(AlwaysOnTopPage) => typeof(AlwaysOnTopPage),
nameof(AwakePage) => typeof(AwakePage),
nameof(CmdNotFoundPage) => typeof(CmdNotFoundPage),
nameof(CmdPalPage) => typeof(CmdPalPage),
nameof(ColorPickerPage) => typeof(ColorPickerPage),
nameof(CropAndLockPage) => typeof(CropAndLockPage),
nameof(EnvironmentVariablesPage) => typeof(EnvironmentVariablesPage),
nameof(FancyZonesPage) => typeof(FancyZonesPage),
nameof(FileLocksmithPage) => typeof(FileLocksmithPage),
nameof(HostsPage) => typeof(HostsPage),
nameof(ImageResizerPage) => typeof(ImageResizerPage),
nameof(KeyboardManagerPage) => typeof(KeyboardManagerPage),
nameof(LightSwitchPage) => typeof(LightSwitchPage),
nameof(MeasureToolPage) => typeof(MeasureToolPage),
nameof(MouseUtilsPage) => typeof(MouseUtilsPage),
nameof(MouseWithoutBordersPage) => typeof(MouseWithoutBordersPage),
nameof(NewPlusPage) => typeof(NewPlusPage),
nameof(PeekPage) => typeof(PeekPage),
nameof(PowerAccentPage) => typeof(PowerAccentPage),
nameof(PowerLauncherPage) => typeof(PowerLauncherPage),
nameof(PowerOcrPage) => typeof(PowerOcrPage),
nameof(PowerPreviewPage) => typeof(PowerPreviewPage),
nameof(PowerRenamePage) => typeof(PowerRenamePage),
nameof(PowerDisplayPage) => typeof(PowerDisplayPage),
nameof(RegistryPreviewPage) => typeof(RegistryPreviewPage),
nameof(ShortcutGuidePage) => typeof(ShortcutGuidePage),
nameof(WorkspacesPage) => typeof(WorkspacesPage),
nameof(ZoomItPage) => typeof(ZoomItPage),
_ => null,
};
_pageTypeCache[pageTypeName] = type;
return type;
}

View File

@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Windows.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;

View File

@@ -2,6 +2,8 @@
// 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.ComponentModel;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.Views;
@@ -9,11 +11,36 @@ using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls
{
public sealed partial class CheckUpdateControl : UserControl
public sealed partial class CheckUpdateControl : UserControl, INotifyPropertyChanged
{
public bool UpdateAvailable { get; set; }
private bool _updateAvailable;
private UpdatingSettings _updateSettingsConfig;
public UpdatingSettings UpdateSettingsConfig { get; set; }
public bool UpdateAvailable
{
get => _updateAvailable;
set
{
if (_updateAvailable != value)
{
_updateAvailable = value;
OnPropertyChanged();
}
}
}
public UpdatingSettings UpdateSettingsConfig
{
get => _updateSettingsConfig;
set
{
if (_updateSettingsConfig != value)
{
_updateSettingsConfig = value;
OnPropertyChanged();
}
}
}
public CheckUpdateControl()
{
@@ -26,5 +53,12 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
NavigationService.Navigate(typeof(GeneralPage));
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -230,7 +230,8 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
NavigationViewItem selectedItem = this.navigationView.SelectedItem as NavigationViewItem;
if (selectedItem != null)
{
Modules[(int)(PowerToysModules)Enum.Parse(typeof(PowerToysModules), (string)selectedItem.Tag, true)].LogClosingModuleEvent();
// Use generic overload for AOT compatibility (IL2026)
Modules[(int)Enum.Parse<PowerToysModules>((string)selectedItem.Tag, ignoreCase: true)].LogClosingModuleEvent();
}
}

View File

@@ -10,7 +10,6 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using LanguageModelProvider;
using Microsoft.PowerToys.Settings.UI.Controls;
@@ -681,7 +680,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
await LoadFoundryLocalModelsAsync();
}
private sealed class FoundryDownloadableModel : INotifyPropertyChanged
private sealed partial class FoundryDownloadableModel : INotifyPropertyChanged
{
private readonly List<string> _deviceTags;
private double _progress;

View File

@@ -55,14 +55,14 @@
Glyph="&#xEC61;" />
<HyperlinkButton
x:Uid="CmdNotFound_UninstallButton"
Command="{x:Bind ViewModel.UninstallModuleEventHandler}"
Click="UninstallButton_Click"
IsEnabled="{x:Bind ViewModel.IsModuleGpoEnabled, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
</StackPanel>
</tkcontrols:Case>
<tkcontrols:Case Value="False">
<Button
x:Uid="CmdNotFound_InstallButton"
Command="{x:Bind ViewModel.InstallModuleEventHandler}"
Click="InstallButton_Click"
IsEnabled="{x:Bind ViewModel.IsModuleGpoDisabled, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
Style="{StaticResource AccentButtonStyle}" />
</tkcontrols:Case>
@@ -76,7 +76,7 @@
IsClosable="False"
IsOpen="True">
<InfoBar.ActionButton>
<HyperlinkButton x:Uid="CmdNotFound_CheckCompatibility" Command="{x:Bind ViewModel.CheckRequirementsEventHandler}">
<HyperlinkButton x:Uid="CmdNotFound_CheckCompatibility" Click="CheckRequirementsButton_Click">
<ToolTipService.ToolTip>
<TextBlock x:Uid="CmdNotFound_CheckCompatibilityTooltip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
@@ -112,7 +112,7 @@
Glyph="&#xEB90;" />
<Button
x:Uid="CmdNotFound_InstallButton"
Command="{x:Bind ViewModel.InstallPowerShell7EventHandler}"
Click="InstallPowerShell7Button_Click"
IsEnabled="{x:Bind ViewModel.IsModuleGpoDisabled, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
</StackPanel>
</tkcontrols:Case>
@@ -145,7 +145,7 @@
Glyph="&#xEB90;" />
<Button
x:Uid="CmdNotFound_InstallButton"
Command="{x:Bind ViewModel.InstallWinGetClientModuleEventHandler}"
Click="InstallWinGetButton_Click"
IsEnabled="{x:Bind ViewModel.IsModuleGpoDisabled, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
</StackPanel>
</tkcontrols:Case>

View File

@@ -18,5 +18,30 @@ namespace Microsoft.PowerToys.Settings.UI.Views
DataContext = ViewModel;
InitializeComponent();
}
private void UninstallButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.UninstallModuleEventHandler?.Execute(null);
}
private void InstallButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.InstallModuleEventHandler?.Execute(null);
}
private void CheckRequirementsButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.CheckRequirementsEventHandler?.Execute(null);
}
private void InstallPowerShell7Button_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.InstallPowerShell7EventHandler?.Execute(null);
}
private void InstallWinGetButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.InstallWinGetClientModuleEventHandler?.Execute(null);
}
}
}

View File

@@ -196,6 +196,7 @@
x:Name="ColorFormatDialog"
x:Uid="ColorFormatDialog"
Closed="ColorFormatDialog_Closed"
PrimaryButtonClick="ColorFormatDialog_PrimaryButtonClick"
IsPrimaryButtonEnabled="{Binding IsValid, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}">
<ContentDialog.DataContext>

View File

@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Windows.Input;
using CommunityToolkit.WinUI.Controls;
using Microsoft.PowerToys.Settings.UI.Helpers;
@@ -25,6 +24,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views
private ResourceLoader resourceLoader = ResourceLoaderInstance.ResourceLoader;
private enum DialogActionMode
{
None,
Add,
Update,
}
private DialogActionMode _dialogActionMode = DialogActionMode.None;
public ColorPickerPage()
{
var settingsUtils = SettingsUtils.Default;
@@ -129,7 +137,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ColorFormatDialog.Tag = string.Empty;
ColorFormatDialog.PrimaryButtonText = resourceLoader.GetString("ColorFormatSave");
ColorFormatDialog.PrimaryButtonCommand = AddCommand;
// Use Click event instead of Command for AOT compatibility
_dialogActionMode = DialogActionMode.Add;
await ColorFormatDialog.ShowAsync();
}
@@ -142,7 +152,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ColorFormatDialog.Tag = new KeyValuePair<string, string>(colorFormatModel.Name, colorFormatModel.Format);
ColorFormatDialog.PrimaryButtonText = resourceLoader.GetString("ColorFormatUpdate");
ColorFormatDialog.PrimaryButtonCommand = UpdateCommand;
// Use Click event instead of Command for AOT compatibility
_dialogActionMode = DialogActionMode.Update;
await ColorFormatDialog.ShowAsync();
}
@@ -156,6 +168,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ViewModel.RefreshEnabledState();
}
private void ColorFormatDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_dialogActionMode == DialogActionMode.Add)
{
Add();
}
else if (_dialogActionMode == DialogActionMode.Update)
{
Update();
}
}
private void ColorFormatDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
if (args.Result != ContentDialogResult.Primary && ColorFormatDialog.Tag is KeyValuePair<string, string>)

View File

@@ -26,7 +26,7 @@
Name="EnvironmentVariablesLaunchButtonControl"
x:Uid="EnvironmentVariables_LaunchButtonControl"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
Command="{x:Bind ViewModel.LaunchEventHandler}"
Click="LaunchButton_Click"
HeaderIcon="{ui:FontIcon Glyph=&#xEA37;}"
IsClickEnabled="True" />
<tkcontrols:SettingsCard

View File

@@ -24,5 +24,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
ViewModel.RefreshEnabledState();
}
private void LaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.LaunchEventHandler?.Execute(null);
}
}
}

View File

@@ -31,7 +31,7 @@
x:Uid="FancyZones_LaunchEditorButtonControl"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
AutomationProperties.AutomationId="LaunchLayoutEditorButton"
Command="{x:Bind ViewModel.LaunchEditorEventHandler}"
Click="LaunchEditorButton_Click"
HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
IsClickEnabled="True" />

View File

@@ -31,5 +31,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
ViewModel.RefreshEnabledState();
}
private void LaunchEditorButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.LaunchEditorEventHandler?.Execute(null);
}
}
}

View File

@@ -41,7 +41,7 @@
Name="HostsLaunchButtonControl"
x:Uid="Hosts_LaunchButtonControl"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
Command="{x:Bind ViewModel.LaunchEventHandler}"
Click="LaunchButton_Click"
HeaderIcon="{ui:FontIcon Glyph=&#xEA37;}"
IsClickEnabled="True" />
<tkcontrols:SettingsCard
@@ -115,7 +115,7 @@
</TextBlock>
<Button
Grid.Column="1"
Command="{x:Bind ViewModel.SelectBackupPathEventHandler}"
Click="SelectBackupPathButton_Click"
Content="&#xe8da;"
FontFamily="{ThemeResource SymbolThemeFontFamily}">
<ToolTipService.ToolTip>

View File

@@ -28,5 +28,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
ViewModel.RefreshEnabledState();
}
private void LaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.LaunchEventHandler?.Execute(null);
}
private void SelectBackupPathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.SelectBackupPathEventHandler?.Execute(null);
}
}
}

View File

@@ -8,7 +8,6 @@ using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Common.UI;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;

View File

@@ -56,7 +56,7 @@
Width="248" />
<Button
x:Uid="MouseWithoutBorders_Connect"
Command="{x:Bind ConnectCommand, Mode=OneTime}"
Click="ConnectButton_Click"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
</tkcontrols:SettingsCard>
@@ -65,11 +65,11 @@
<TextBox IsReadOnly="True" Text="{x:Bind ViewModel.SecurityKey, Mode=TwoWay}" />
<Button
x:Uid="MouseWithoutBorders_NewKey"
Command="{x:Bind GenerateNewKeyCommand, Mode=OneTime}"
Click="GenerateNewKeyButton_Click"
Style="{StaticResource AccentButtonStyle}" />
<Button
x:Uid="MouseWithoutBorders_Connect"
Command="{x:Bind ShowConnectFieldsCommand, Mode=OneTime}"
Click="ShowConnectFieldsButton_Click"
Style="{StaticResource AccentButtonStyle}"
Visibility="{x:Bind Path=ViewModel.ConnectFieldsVisible, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}, ConverterParameter=True}" />
</StackPanel>
@@ -86,7 +86,7 @@
Width="32"
Height="32"
Padding="4"
Command="{x:Bind CopyPCNameCommand, Mode=OneTime}"
Click="CopyPCNameButton_Click"
Content="&#xE8C8;"
FontFamily="{StaticResource SymbolThemeFontFamily}">
<ToolTipService.ToolTip>
@@ -160,7 +160,7 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button HorizontalAlignment="Right" Command="{x:Bind ReconnectCommand, Mode=OneTime}">
<Button HorizontalAlignment="Right" Click="ReconnectButton_Click">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MouseWithoutBorders_ReconnectTooltip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
@@ -210,7 +210,7 @@
Name="MouseWithoutBordersUninstallService"
x:Uid="MouseWithoutBorders_UninstallService"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
Command="{x:Bind ViewModel.UninstallServiceEventHandler}"
Click="UninstallServiceButton_Click"
IsClickEnabled="{x:Bind ViewModel.CanUninstallService, Mode=OneWay}"
IsEnabled="{x:Bind ViewModel.CanUninstallService, Mode=OneWay}" />
</controls:SettingsGroup>
@@ -476,7 +476,7 @@
Name="MouseWithoutBordersAddFirewallRuleButtonControl"
x:Uid="MouseWithoutBorders_AddFirewallRuleButtonControl"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
Command="{x:Bind ViewModel.AddFirewallRuleEventHandler}"
Click="AddFirewallRuleButton_Click"
IsClickEnabled="True" />
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.ShowPolicyConfiguredInfoForOriginalUiSetting, Mode=OneWay}">
<tkcontrols:SettingsCard

View File

@@ -5,7 +5,6 @@
using System;
using System.IO.Abstractions;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
@@ -171,5 +170,40 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
ViewModel.RefreshEnabledState();
}
private void ConnectButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ConnectCommand?.Execute(null);
}
private void GenerateNewKeyButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
GenerateNewKeyCommand?.Execute(null);
}
private void ShowConnectFieldsButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ShowConnectFieldsCommand?.Execute(null);
}
private void CopyPCNameButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
CopyPCNameCommand?.Execute(null);
}
private void ReconnectButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ReconnectCommand?.Execute(null);
}
private void UninstallServiceButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.UninstallServiceEventHandler?.Execute(null);
}
private void AddFirewallRuleButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.AddFirewallRuleEventHandler?.Execute(null);
}
}
}

View File

@@ -25,5 +25,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
ViewModel.RefreshEnabledState();
}
private void InstallTemplatePackageButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.OpenCurrentNewTemplateFolder?.Execute(null);
}
private void UninstallTemplatePackageButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.PickAnotherNewTemplateFolder?.Execute(null);
}
}
}

View File

@@ -80,6 +80,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
}
}
// AOT-compatible type lookup using switch expression instead of reflection (IL2026)
private Type GetPageTypeFromName(string pageTypeName)
{
if (string.IsNullOrEmpty(pageTypeName))
@@ -87,8 +88,41 @@ namespace Microsoft.PowerToys.Settings.UI.Views
return null;
}
var assembly = typeof(GeneralPage).Assembly;
return assembly.GetType($"Microsoft.PowerToys.Settings.UI.Views.{pageTypeName}");
return pageTypeName switch
{
nameof(DashboardPage) => typeof(DashboardPage),
nameof(GeneralPage) => typeof(GeneralPage),
nameof(AdvancedPastePage) => typeof(AdvancedPastePage),
nameof(AlwaysOnTopPage) => typeof(AlwaysOnTopPage),
nameof(AwakePage) => typeof(AwakePage),
nameof(CmdNotFoundPage) => typeof(CmdNotFoundPage),
nameof(CmdPalPage) => typeof(CmdPalPage),
nameof(ColorPickerPage) => typeof(ColorPickerPage),
nameof(CropAndLockPage) => typeof(CropAndLockPage),
nameof(EnvironmentVariablesPage) => typeof(EnvironmentVariablesPage),
nameof(FancyZonesPage) => typeof(FancyZonesPage),
nameof(FileLocksmithPage) => typeof(FileLocksmithPage),
nameof(HostsPage) => typeof(HostsPage),
nameof(ImageResizerPage) => typeof(ImageResizerPage),
nameof(KeyboardManagerPage) => typeof(KeyboardManagerPage),
nameof(LightSwitchPage) => typeof(LightSwitchPage),
nameof(MeasureToolPage) => typeof(MeasureToolPage),
nameof(MouseUtilsPage) => typeof(MouseUtilsPage),
nameof(MouseWithoutBordersPage) => typeof(MouseWithoutBordersPage),
nameof(NewPlusPage) => typeof(NewPlusPage),
nameof(PeekPage) => typeof(PeekPage),
nameof(PowerAccentPage) => typeof(PowerAccentPage),
nameof(PowerLauncherPage) => typeof(PowerLauncherPage),
nameof(PowerOcrPage) => typeof(PowerOcrPage),
nameof(PowerPreviewPage) => typeof(PowerPreviewPage),
nameof(PowerRenamePage) => typeof(PowerRenamePage),
nameof(PowerDisplayPage) => typeof(PowerDisplayPage),
nameof(RegistryPreviewPage) => typeof(RegistryPreviewPage),
nameof(ShortcutGuidePage) => typeof(ShortcutGuidePage),
nameof(WorkspacesPage) => typeof(WorkspacesPage),
nameof(ZoomItPage) => typeof(ZoomItPage),
_ => null,
};
}
}

View File

@@ -138,6 +138,7 @@
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
IsTitleBarAutoPaddingEnabled="False"
ItemInvoked="NavigationView_ItemInvoked"
PaneClosed="NavigationView_PaneClosed"
PaneOpened="NavigationView_PaneOpened"
SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}"
@@ -447,11 +448,7 @@
Visibility="{x:Bind ViewModel.ShowCloseMenu, Mode=OneWay}" />
</StackPanel>
</NavigationView.PaneFooter>
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="ItemInvoked">
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ItemInvokedCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
<!-- Direct event handler instead of InvokeCommandAction for AOT compatibility -->
<Frame x:Name="shellFrame" />
</NavigationView>
<ContentDialog

View File

@@ -338,6 +338,19 @@ namespace Microsoft.PowerToys.Settings.UI.Views
OpenWhatIsNewWindowCallback();
}
private void NavigationView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
// Direct event handler for AOT compatibility (InvokeCommandAction doesn't work reliably with AOT)
if (args?.InvokedItemContainer != null)
{
var pageType = args.InvokedItemContainer.GetValue(NavHelper.NavigateToProperty) as Type;
if (pageType != null)
{
NavigationService.Navigate(pageType);
}
}
}
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
NavigationViewItem selectedItem = args.SelectedItem as NavigationViewItem;
@@ -535,6 +548,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
}
}
// AOT-compatible type lookup using switch expression instead of reflection (IL2026)
private static Type GetPageTypeFromName(string pageTypeName)
{
if (string.IsNullOrEmpty(pageTypeName))
@@ -542,8 +556,41 @@ namespace Microsoft.PowerToys.Settings.UI.Views
return null;
}
var assembly = typeof(GeneralPage).Assembly;
return assembly.GetType($"Microsoft.PowerToys.Settings.UI.Views.{pageTypeName}");
return pageTypeName switch
{
nameof(DashboardPage) => typeof(DashboardPage),
nameof(GeneralPage) => typeof(GeneralPage),
nameof(AdvancedPastePage) => typeof(AdvancedPastePage),
nameof(AlwaysOnTopPage) => typeof(AlwaysOnTopPage),
nameof(AwakePage) => typeof(AwakePage),
nameof(CmdNotFoundPage) => typeof(CmdNotFoundPage),
nameof(CmdPalPage) => typeof(CmdPalPage),
nameof(ColorPickerPage) => typeof(ColorPickerPage),
nameof(CropAndLockPage) => typeof(CropAndLockPage),
nameof(EnvironmentVariablesPage) => typeof(EnvironmentVariablesPage),
nameof(FancyZonesPage) => typeof(FancyZonesPage),
nameof(FileLocksmithPage) => typeof(FileLocksmithPage),
nameof(HostsPage) => typeof(HostsPage),
nameof(ImageResizerPage) => typeof(ImageResizerPage),
nameof(KeyboardManagerPage) => typeof(KeyboardManagerPage),
nameof(LightSwitchPage) => typeof(LightSwitchPage),
nameof(MeasureToolPage) => typeof(MeasureToolPage),
nameof(MouseUtilsPage) => typeof(MouseUtilsPage),
nameof(MouseWithoutBordersPage) => typeof(MouseWithoutBordersPage),
nameof(NewPlusPage) => typeof(NewPlusPage),
nameof(PeekPage) => typeof(PeekPage),
nameof(PowerAccentPage) => typeof(PowerAccentPage),
nameof(PowerLauncherPage) => typeof(PowerLauncherPage),
nameof(PowerOcrPage) => typeof(PowerOcrPage),
nameof(PowerPreviewPage) => typeof(PowerPreviewPage),
nameof(PowerRenamePage) => typeof(PowerRenamePage),
nameof(PowerDisplayPage) => typeof(PowerDisplayPage),
nameof(RegistryPreviewPage) => typeof(RegistryPreviewPage),
nameof(ShortcutGuidePage) => typeof(ShortcutGuidePage),
nameof(WorkspacesPage) => typeof(WorkspacesPage),
nameof(ZoomItPage) => typeof(ZoomItPage),
_ => null,
};
}
private void CtrlF_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)

View File

@@ -50,7 +50,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
private static LOGFONT PickFontDialog(LOGFONT font)
{
IntPtr pLogFont = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LOGFONT)));
// Use generic overload for AOT compatibility (IL3050)
IntPtr pLogFont = Marshal.AllocHGlobal(Marshal.SizeOf<LOGFONT>());
if (font != null)
{
font.lfHeight = -21;
@@ -72,12 +73,16 @@ namespace Microsoft.PowerToys.Settings.UI.Views
chooseFont.nSizeMin = 16;
chooseFont.nSizeMax = 16;
chooseFont.nFontType = 0x2000; // SCREEN_FONTTYPE as in the original ZoomIt source.
chooseFont.hInstance = Marshal.GetHINSTANCE(typeof(ZoomItPage).Module);
// Use IntPtr.Zero instead of Marshal.GetHINSTANCE for AOT compatibility (IL3002)
// This works fine for ChooseFont dialog as the system will use the process HINSTANCE
chooseFont.hInstance = IntPtr.Zero;
// TODO: chooseFont.lpTemplateName = FORMATDLGORD31; and CHOOSE_FONT_FLAGS.CF_ENABLETEMPLATE
chooseFont.lpLogFont = pLogFont;
IntPtr pChooseFont = Marshal.AllocHGlobal(Marshal.SizeOf(chooseFont));
// Use generic overload for AOT compatibility (IL3050)
IntPtr pChooseFont = Marshal.AllocHGlobal(Marshal.SizeOf<CHOOSEFONT>());
Marshal.StructureToPtr(chooseFont, pChooseFont, false);
bool callResult = NativeMethods.ChooseFont(pChooseFont);

View File

@@ -23,7 +23,7 @@ using Windows.Management.Deployment;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class CmdPalViewModel : PageViewModelBase
public partial class CmdPalViewModel : PageViewModelBase
{
protected override string ModuleName => "CmdPal";

View File

@@ -2,12 +2,14 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Helpers;
namespace Microsoft.PowerToys.Settings.UI.ViewModels.Commands
{
public partial class ButtonClickCommand : ICommand
public partial class ButtonClickCommand : Microsoft.PowerToys.Settings.UI.Library.ICommand
{
private readonly Action _execute;
@@ -17,16 +19,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels.Commands
}
// Occurs when changes occur that affect whether or not the command should execute.
public event EventHandler CanExecuteChanged;
public event EventHandler? CanExecuteChanged;
// Defines the method that determines whether the command can execute in its current state.
public bool CanExecute(object parameter)
public bool CanExecute(object? parameter)
{
return true;
}
// Defines the method to be called when the command is invoked.
public void Execute(object parameter)
public void Execute(object? parameter)
{
_execute();
}

View File

@@ -10,16 +10,17 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
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.Library.ViewModels.Commands;
using Microsoft.PowerToys.Settings.Utilities;
using Microsoft.Win32;
using LibraryRelayCommand = Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands.RelayCommand;
using NativeMethods = Microsoft.PowerToys.Settings.Utilities.NativeMethods;
using RelayCommand = Microsoft.PowerToys.Settings.UI.Helpers.RelayCommand;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{

View File

@@ -12,7 +12,6 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Windows.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;

View File

@@ -13,7 +13,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
/// <summary>
/// ViewModel for monitor selection in profile editor
/// </summary>
public class MonitorSelectionItem : INotifyPropertyChanged
public partial class MonitorSelectionItem : INotifyPropertyChanged
{
private bool _isSelected;
private int _brightness = 100;

View File

@@ -8,7 +8,6 @@ using System.Globalization;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;

View File

@@ -21,7 +21,7 @@ using Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class PeekViewModel : PageViewModelBase
public partial class PeekViewModel : PageViewModelBase
{
protected override string ModuleName => PeekSettings.ModuleName;

View File

@@ -10,7 +10,6 @@ using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Windows.Input;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;

View File

@@ -6,7 +6,6 @@ using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Input;
using global::PowerToys.GPOWrapper;
using ManagedCommon;

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
/// <summary>
/// ViewModel for Profile Editor Dialog
/// </summary>
public class ProfileEditorViewModel : INotifyPropertyChanged
public partial class ProfileEditorViewModel : INotifyPropertyChanged
{
private string _profileName = string.Empty;
private ObservableCollection<MonitorSelectionItem> _monitors;

View File

@@ -12,7 +12,7 @@ using Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class SearchResultsViewModel : INotifyPropertyChanged
public partial class SearchResultsViewModel : INotifyPropertyChanged
{
private ObservableCollection<SettingEntry> _moduleResults = new();
private ObservableCollection<SettingsGroup> _groupedSettingsResults = new();
@@ -97,7 +97,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
#pragma warning disable SA1402 // File may only contain a single type
public class SettingsGroup : INotifyPropertyChanged
public partial class SettingsGroup : INotifyPropertyChanged
#pragma warning restore SA1402 // File may only contain a single type
{
private string _groupName;

View File

@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;

View File

@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -12,7 +14,6 @@ using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Windows.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -25,11 +26,11 @@ using Microsoft.Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class ShortcutConflictViewModel : PageViewModelBase
public partial class ShortcutConflictViewModel : PageViewModelBase
{
private readonly SettingsFactory _settingsFactory;
private readonly Func<string, int> _ipcMSGCallBackFunc;
private readonly Dispatcher _dispatcher;
private readonly Microsoft.UI.Dispatching.DispatcherQueue? _dispatcherQueue;
private bool _disposed;
private AllHotkeyConflictsData _conflictsData = new();
@@ -41,7 +42,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
ISettingsRepository<GeneralSettings> settingsRepository,
Func<string, int> ipcMSGCallBackFunc)
{
_dispatcher = Dispatcher.CurrentDispatcher;
// Use WinUI 3 DispatcherQueue instead of WPF Dispatcher for AOT compatibility
_dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
_ipcMSGCallBackFunc = ipcMSGCallBackFunc ?? throw new ArgumentNullException(nameof(ipcMSGCallBackFunc));
resourceLoader = ResourceLoaderInstance.ResourceLoader;
@@ -63,7 +65,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public ObservableCollection<HotkeyConflictGroupData> ConflictItems
{
get => _conflictItems;
get => _conflictItems ?? new ObservableCollection<HotkeyConflictGroupData>();
private set => Set(ref _conflictItems, value);
}
@@ -117,13 +119,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading settings for {moduleKey}: {ex.Message}");
return null;
return null!; // Suppress nullable warning - caller handles null
}
}
protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
{
_dispatcher.BeginInvoke(() =>
// WinUI 3 DispatcherQueue uses TryEnqueue instead of BeginInvoke
_dispatcherQueue?.TryEnqueue(() =>
{
ConflictsData = e.Conflicts ?? new AllHotkeyConflictsData();
});
@@ -133,8 +136,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
var items = new ObservableCollection<HotkeyConflictGroupData>();
ProcessConflicts(ConflictsData?.InAppConflicts, false, items);
ProcessConflicts(ConflictsData?.SystemConflicts, true, items);
ProcessConflicts(ConflictsData?.InAppConflicts ?? Enumerable.Empty<HotkeyConflictGroupData>(), false, items);
ProcessConflicts(ConflictsData?.SystemConflicts ?? Enumerable.Empty<HotkeyConflictGroupData>(), true, items);
ConflictItems = items;
OnPropertyChanged(nameof(ConflictItems));
@@ -218,7 +221,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
settings.IsSystemConflict = isSystemConflict;
}
private void OnModuleHotkeyDataPropertyChanged(object sender, PropertyChangedEventArgs e)
private void OnModuleHotkeyDataPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is ModuleHotkeyData moduleData && e.PropertyName == nameof(ModuleHotkeyData.HotkeySettings))
{
@@ -272,10 +275,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
try
{
var jsonTypeInfo = GetJsonTypeInfo(settingsConfig.GetType());
var serializedSettings = jsonTypeInfo != null
? JsonSerializer.Serialize(settingsConfig, jsonTypeInfo)
: JsonSerializer.Serialize(settingsConfig);
// Use source-generated serializer for AOT compatibility (IL2026/IL3050)
var serializedSettings = SerializeSettings(settingsConfig);
string ipcMessage;
if (string.Equals(moduleName, "GeneralSettings", StringComparison.OrdinalIgnoreCase))
@@ -303,30 +304,43 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private JsonTypeInfo GetJsonTypeInfo(Type settingsType)
// AOT-compatible serialization using source-generated context
private string SerializeSettings(ISettingsConfig settingsConfig)
{
try
return settingsConfig switch
{
var contextType = typeof(SourceGenerationContextContext);
var defaultProperty = contextType.GetProperty("Default", BindingFlags.Public | BindingFlags.Static);
var defaultContext = defaultProperty?.GetValue(null) as JsonSerializerContext;
GeneralSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.GeneralSettings),
AdvancedPasteSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.AdvancedPasteSettings),
AlwaysOnTopSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.AlwaysOnTopSettings),
AwakeSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.AwakeSettings),
CmdNotFoundSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.CmdNotFoundSettings),
ColorPickerSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.ColorPickerSettings),
CropAndLockSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.CropAndLockSettings),
EnvironmentVariablesSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.EnvironmentVariablesSettings),
FancyZonesSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.FancyZonesSettings),
FileLocksmithSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.FileLocksmithSettings),
HostsSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.HostsSettings),
ImageResizerSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.ImageResizerSettings),
KeyboardManagerSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.KeyboardManagerSettings),
LightSwitchSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.LightSwitchSettings),
MeasureToolSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.MeasureToolSettings),
MouseWithoutBordersSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.MouseWithoutBordersSettings),
NewPlusSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.NewPlusSettings),
PeekSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.PeekSettings),
PowerAccentSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.PowerAccentSettings),
PowerDisplaySettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.PowerDisplaySettings),
PowerLauncherSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.PowerLauncherSettings),
PowerOcrSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.PowerOcrSettings),
PowerPreviewSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.PowerPreviewSettings),
PowerRenameSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.PowerRenameSettings),
RegistryPreviewSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.RegistryPreviewSettings),
ShortcutGuideSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.ShortcutGuideSettings),
WorkspacesSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.WorkspacesSettings),
ZoomItSettings s => JsonSerializer.Serialize(s, SettingsSerializationContext.Default.ZoomItSettings),
if (defaultContext != null)
{
var typeInfoProperty = contextType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(p => p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericTypeDefinition() == typeof(JsonTypeInfo<>) &&
p.PropertyType.GetGenericArguments()[0] == settingsType);
return typeInfoProperty?.GetValue(defaultContext) as JsonTypeInfo;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting JsonTypeInfo for {settingsType.Name}: {ex.Message}");
}
return null;
// If we hit this case, SettingsSerializationContext is incomplete - this should be caught in development
_ => throw new InvalidOperationException($"Settings type {settingsConfig.GetType().Name} is not registered in SettingsSerializationContext. Please add [JsonSerializable(typeof({settingsConfig.GetType().Name}))] to the context."),
};
}
private string GetHotkeyLocalizationHeader(string moduleName, int hotkeyID, string headerKey)

View File

@@ -22,7 +22,7 @@ using Windows.Devices.Enumeration;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class ZoomItViewModel : Observable
public partial class ZoomItViewModel : Observable
{
private const string FormatGif = "GIF";
private const string FormatMp4 = "MP4";
@@ -66,12 +66,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
MaxDepth = 0,
IncludeFields = true,
};
public ZoomItViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, Func<string, string, string, int, string> pickFileDialog, Func<LOGFONT, LOGFONT> pickFontDialog)
{
ArgumentNullException.ThrowIfNull(settingsUtils);
@@ -84,7 +78,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
GeneralSettingsConfig = settingsRepository.SettingsConfig;
var zoomItSettings = global::PowerToys.ZoomItSettingsInterop.ZoomItSettings.LoadSettingsJson();
_zoomItSettings = JsonSerializer.Deserialize<ZoomItSettings>(zoomItSettings, _serializerOptions);
// Use source-generated deserializer for AOT compatibility (IL2026/IL3050)
_zoomItSettings = JsonSerializer.Deserialize(zoomItSettings, SettingsSerializationContext.Default.ZoomItSettings);
InitializeEnabledValue();
@@ -424,7 +420,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
var encodedFont = _zoomItSettings.Properties.Font.Value;
byte[] decodedFont = Convert.FromBase64String(encodedFont);
int size = Marshal.SizeOf(typeof(LOGFONT));
int size = Marshal.SizeOf<LOGFONT>();
if (size != decodedFont.Length)
{
throw new InvalidOperationException("Expected byte array from saved Settings doesn't match the LOGFONT structure size");
@@ -438,7 +434,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
Marshal.Copy(decodedFont, 0, ptr, size);
// Marshal the unmanaged memory back to a LOGFONT structure
return (LOGFONT)Marshal.PtrToStructure(ptr, typeof(LOGFONT));
// Use generic overload for AOT compatibility
return Marshal.PtrToStructure<LOGFONT>(ptr);
}
finally
{
@@ -450,7 +447,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
set
{
_typeFont = value;
int size = Marshal.SizeOf(typeof(LOGFONT));
int size = Marshal.SizeOf<LOGFONT>();
byte[] bytes = new byte[size];
// Allocate unmanaged memory for the LOGFONT structure
@@ -840,7 +837,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Reload settings to get the new format's scaling value
var reloadedSettings = global::PowerToys.ZoomItSettingsInterop.ZoomItSettings.LoadSettingsJson();
var reloaded = JsonSerializer.Deserialize<ZoomItSettings>(reloadedSettings, _serializerOptions);
// Use source-generated deserializer for AOT compatibility (IL2026/IL3050)
var reloaded = JsonSerializer.Deserialize(reloadedSettings, SettingsSerializationContext.Default.ZoomItSettings);
if (reloaded != null && reloaded.Properties != null)
{
_zoomItSettings.Properties.RecordScaling.Value = reloaded.Properties.RecordScaling.Value;
@@ -894,8 +893,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private void NotifySettingsChanged()
{
// Use source-generated serializer for AOT compatibility (IL2026/IL3050)
global::PowerToys.ZoomItSettingsInterop.ZoomItSettings.SaveSettingsJson(
JsonSerializer.Serialize(_zoomItSettings));
JsonSerializer.Serialize(_zoomItSettings, SettingsSerializationContext.Default.ZoomItSettings));
SendCustomAction("refresh_settings");
}