mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-15 08:46:37 +01:00
Compare commits
6 Commits
main
...
copilot/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
727837c34b | ||
|
|
9f26707d4f | ||
|
|
021d4bf188 | ||
|
|
3d510dc382 | ||
|
|
8ffd88fdf3 | ||
|
|
097cf7414b |
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -209,7 +209,6 @@ changecursor
|
||||
CHILDACTIVATE
|
||||
CHILDWINDOW
|
||||
CHOOSEFONT
|
||||
CIBUILD
|
||||
cidl
|
||||
CIELCh
|
||||
cim
|
||||
|
||||
@@ -10,7 +10,7 @@ parameters:
|
||||
default: {}
|
||||
|
||||
steps:
|
||||
- task: EsrpCodeSigning@6
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: 🔏 ${{ parameters.displayName }}
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
|
||||
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
||||
<PackageVersion Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
|
||||
<!-- Pin the SixLabors.ImageSharp version (a transitive dependency of CoenM.ImageSharp.ImageHash) to restore functionality and apply patches. -->
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.12" />
|
||||
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.2.250402" />
|
||||
|
||||
@@ -1549,7 +1549,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
}
|
||||
processes.resize(bytes / sizeof(processes[0]));
|
||||
|
||||
std::array<std::wstring_view, 44> processesToTerminate = {
|
||||
std::array<std::wstring_view, 42> processesToTerminate = {
|
||||
L"PowerToys.PowerLauncher.exe",
|
||||
L"PowerToys.Settings.exe",
|
||||
L"PowerToys.AdvancedPaste.exe",
|
||||
@@ -1584,14 +1584,12 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
L"PowerToys.MouseWithoutBordersService.exe",
|
||||
L"PowerToys.CropAndLock.exe",
|
||||
L"PowerToys.EnvironmentVariables.exe",
|
||||
L"PowerToys.QuickAccess.exe",
|
||||
L"PowerToys.WorkspacesSnapshotTool.exe",
|
||||
L"PowerToys.WorkspacesLauncher.exe",
|
||||
L"PowerToys.WorkspacesLauncherUI.exe",
|
||||
L"PowerToys.WorkspacesEditor.exe",
|
||||
L"PowerToys.WorkspacesWindowArranger.exe",
|
||||
L"Microsoft.CmdPal.UI.exe",
|
||||
L"Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
L"PowerToys.ZoomIt.exe",
|
||||
L"PowerToys.exe",
|
||||
};
|
||||
|
||||
@@ -61,16 +61,6 @@
|
||||
</RegistryKey>
|
||||
<File Source="$(var.RepoDir)\Notice.md" Id="Notice.md" />
|
||||
</Component>
|
||||
<Directory Id="SvgsFolder" Name="svgs">
|
||||
<Component Id="svgs_icons" Guid="A9B7C5D3-E1F2-4A6B-8C9D-0E1F2A3B4C5D" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="svgs_icons" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="icon.ico" Source="$(var.BinDir)svgs\icon.ico" />
|
||||
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)svgs\PowerToysWhite.ico" />
|
||||
<File Id="PowerToysDark.ico" Source="$(var.BinDir)svgs\PowerToysDark.ico" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
@@ -122,7 +112,6 @@
|
||||
<RemoveFolder Id="RemoveBaseApplicationsAssetsFolder" Directory="BaseApplicationsAssetsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveWinUI3AppsInstallFolder" Directory="WinUI3AppsInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveWinUI3AppsAssetsFolder" Directory="WinUI3AppsAssetsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveSvgsFolder" Directory="SvgsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveINSTALLFOLDER" Directory="INSTALLFOLDER" On="uninstall" />
|
||||
</Component>
|
||||
<ComponentRef Id="powertoys_exe" />
|
||||
@@ -131,7 +120,6 @@
|
||||
<ComponentRef Id="powertoys_toast_clsid" />
|
||||
<ComponentRef Id="License_rtf" />
|
||||
<ComponentRef Id="Notice_md" />
|
||||
<ComponentRef Id="svgs_icons" />
|
||||
<ComponentRef Id="DesktopShortcut" />
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
<ComponentRef Id="powertoys_env_path_user" />
|
||||
|
||||
@@ -66,10 +66,5 @@ namespace PowerToys.GPOWrapperProjection
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue();
|
||||
}
|
||||
|
||||
public static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue()
|
||||
{
|
||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace ManagedCommon
|
||||
{
|
||||
public static bool IsWindows10()
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build < 22000;
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Minor < 22000;
|
||||
}
|
||||
|
||||
public static bool IsWindows11()
|
||||
|
||||
@@ -466,27 +466,39 @@
|
||||
TextChanged="EditVariableDialogValueTxtBox_TextChanged"
|
||||
TextWrapping="Wrap" />
|
||||
<MenuFlyoutSeparator Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<ItemsControl
|
||||
<ListView
|
||||
x:Name="EditVariableValuesList"
|
||||
Margin="0,-8,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
AllowDrop="True"
|
||||
CanDragItems="True"
|
||||
CanReorderItems="True"
|
||||
DragItemsCompleted="EditVariableValuesList_DragItemsCompleted"
|
||||
ItemsSource="{Binding ValuesList, Mode=TwoWay}"
|
||||
Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="40" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Grid.Column="0"
|
||||
Margin="0,0,8,0"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
LostFocus="EditVariableValuesListTextBox_LostFocus"
|
||||
Text="{Binding Text}" />
|
||||
<Button
|
||||
x:Uid="More_Options_Button"
|
||||
Grid.Column="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
@@ -523,8 +535,8 @@
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace EnvironmentVariablesUILib
|
||||
{
|
||||
public sealed partial class EnvironmentVariablesMainPage : Page
|
||||
{
|
||||
private const string ValueListSeparator = ";";
|
||||
|
||||
private sealed class RelayCommandParameter
|
||||
{
|
||||
public RelayCommandParameter(Variable variable, VariablesSet set)
|
||||
@@ -440,7 +442,7 @@ namespace EnvironmentVariablesUILib
|
||||
variable.ValuesList.Move(index, index - 1);
|
||||
}
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
@@ -461,7 +463,7 @@ namespace EnvironmentVariablesUILib
|
||||
variable.ValuesList.Move(index, index + 1);
|
||||
}
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
@@ -476,7 +478,7 @@ namespace EnvironmentVariablesUILib
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
variable.ValuesList.Remove(listItem);
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
@@ -492,7 +494,7 @@ namespace EnvironmentVariablesUILib
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
variable.ValuesList.Insert(index, new Variable.ValuesListItem { Text = string.Empty });
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
@@ -510,7 +512,7 @@ namespace EnvironmentVariablesUILib
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
variable.ValuesList.Insert(index + 1, new Variable.ValuesListItem { Text = string.Empty });
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
@@ -532,7 +534,7 @@ namespace EnvironmentVariablesUILib
|
||||
listItem.Text = (sender as TextBox)?.Text;
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
@@ -548,5 +550,16 @@ namespace EnvironmentVariablesUILib
|
||||
CancelAddVariable();
|
||||
ConfirmAddVariableBtn.IsEnabled = false;
|
||||
}
|
||||
|
||||
private void EditVariableValuesList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
if (EditVariableDialog.DataContext is Variable variable && variable.ValuesList != null)
|
||||
{
|
||||
var newValues = string.Join(ValueListSeparator, variable.ValuesList.Select(x => x.Text));
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ public struct ApplicationWrapper
|
||||
{
|
||||
public struct WindowPositionWrapper
|
||||
{
|
||||
[JsonPropertyName("X")]
|
||||
[JsonPropertyName("x")]
|
||||
public int X { get; set; }
|
||||
|
||||
[JsonPropertyName("Y")]
|
||||
[JsonPropertyName("y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
[JsonPropertyName("width")]
|
||||
|
||||
@@ -547,15 +547,6 @@ public partial class MainListPage : DynamicListPage,
|
||||
// above "git" from "whatever"
|
||||
max = max + extensionTitleMatch;
|
||||
|
||||
// Apply a penalty to fallback items so they rank below direct matches.
|
||||
// Fallbacks that dynamically match queries (like RDP connections) should
|
||||
// appear after apps and direct command matches.
|
||||
if (isFallback && max > 1)
|
||||
{
|
||||
// Reduce fallback scores by 50% to prioritize direct matches
|
||||
max = max * 0.5;
|
||||
}
|
||||
|
||||
var matchSomething = max
|
||||
+ (isAliasMatch ? 9001 : (isAliasSubstringMatch ? 1 : 0));
|
||||
|
||||
|
||||
@@ -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.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
@@ -17,41 +18,19 @@ internal static class BuildInfo
|
||||
// Runtime AOT detection
|
||||
public static bool IsNativeAot => !RuntimeFeature.IsDynamicCodeSupported;
|
||||
|
||||
// build-time values
|
||||
public static bool PublishTrimmed
|
||||
{
|
||||
get
|
||||
{
|
||||
#if BUILD_INFO_PUBLISH_TRIMMED
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// From assembly metadata (build-time values)
|
||||
public static bool PublishTrimmed => GetBoolMetadata("PublishTrimmed", false);
|
||||
|
||||
// build-time values
|
||||
public static bool PublishAot
|
||||
{
|
||||
get
|
||||
{
|
||||
#if BUILD_INFO_PUBLISH_AOT
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// From assembly metadata (build-time values)
|
||||
public static bool PublishAot => GetBoolMetadata("PublishAot", false);
|
||||
|
||||
public static bool IsCiBuild
|
||||
{
|
||||
get
|
||||
{
|
||||
#if BUILD_INFO_CIBUILD
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
public static bool IsCiBuild => GetBoolMetadata("CIBuild", false);
|
||||
|
||||
private static string? GetMetadata(string key) =>
|
||||
Assembly.GetExecutingAssembly()
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.FirstOrDefault(a => a.Key == key)?.Value;
|
||||
|
||||
private static bool GetBoolMetadata(string key, bool defaultValue) =>
|
||||
bool.TryParse(GetMetadata(key), out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- This disables the auto-generated main, so we can be single-instanced -->
|
||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- BODGY: XES Versioning and WinAppSDK get into a fight about the app manifest, which breaks WinAppSDK. -->
|
||||
@@ -291,15 +291,24 @@
|
||||
</ItemGroup>
|
||||
<!-- </AdaptiveCardsWorkaround> -->
|
||||
|
||||
<!-- Build information -->
|
||||
<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>
|
||||
<!-- Metadata for build information -->
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>PublishTrimmed</_Parameter1>
|
||||
<_Parameter2>$(PublishTrimmed)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>PublishAot</_Parameter1>
|
||||
<_Parameter2>$(PublishAot)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>CIBuild</_Parameter1>
|
||||
<_Parameter2>$(CIBuild)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>CommandPaletteBranding</_Parameter1>
|
||||
<_Parameter2>$(CommandPaletteBranding)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -372,7 +372,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<value>Windows Command Palette</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_About_SettingsExpander.Description" xml:space="preserve">
|
||||
<value>© 2026. All rights reserved.</value>
|
||||
<value>© 2025. All rights reserved.</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_About_GithubLink_Hyperlink.Content" xml:space="preserve">
|
||||
<value>View GitHub repository</value>
|
||||
|
||||
@@ -55,7 +55,7 @@ public class BasicTests : CommandPaletteTestBase
|
||||
|
||||
SetTimeAndDaterExtensionSearchBox("year");
|
||||
|
||||
Assert.IsNotNull(this.Find<NavigationViewItem>("2026"));
|
||||
Assert.IsNotNull(this.Find<NavigationViewItem>("2025"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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.Globalization;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -42,19 +41,15 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
|
||||
public static Details BuildMonitorDetails(FancyZonesMonitorDescriptor monitor)
|
||||
{
|
||||
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
|
||||
|
||||
// Calculate physical resolution from logical pixels and DPI
|
||||
var scaleFactor = monitor.Data.Dpi > 0 ? monitor.Data.Dpi / 96.0 : 1.0;
|
||||
var physicalWidth = (int)Math.Round(monitor.Data.MonitorWidth * scaleFactor);
|
||||
var physicalHeight = (int)Math.Round(monitor.Data.MonitorHeight * scaleFactor);
|
||||
var resolution = $"{physicalWidth}\u00D7{physicalHeight}";
|
||||
|
||||
var tags = new List<IDetailsElement>
|
||||
{
|
||||
DetailTag(Resources.FancyZones_Monitor, monitor.Data.Monitor),
|
||||
DetailTag(Resources.FancyZones_Instance, monitor.Data.MonitorInstanceId),
|
||||
DetailTag(Resources.FancyZones_Serial, monitor.Data.MonitorSerialNumber),
|
||||
DetailTag(Resources.FancyZones_Number, monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
|
||||
DetailTag(Resources.FancyZones_VirtualDesktop, currentVirtualDesktop),
|
||||
DetailTag(Resources.FancyZones_Resolution, resolution),
|
||||
DetailTag(Resources.FancyZones_WorkArea, $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
|
||||
DetailTag(Resources.FancyZones_Resolution, $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
|
||||
DetailTag(Resources.FancyZones_DPI, monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
|
||||
};
|
||||
|
||||
|
||||
@@ -19,12 +19,8 @@ internal readonly record struct FancyZonesMonitorDescriptor(
|
||||
{
|
||||
get
|
||||
{
|
||||
// MonitorWidth/Height are logical (DPI-scaled) pixels, calculate physical resolution
|
||||
var scaleFactor = Data.Dpi > 0 ? Data.Dpi / 96.0 : 1.0;
|
||||
var physicalWidth = (int)Math.Round(Data.MonitorWidth * scaleFactor);
|
||||
var physicalHeight = (int)Math.Round(Data.MonitorHeight * scaleFactor);
|
||||
var size = $"{physicalWidth}×{physicalHeight}";
|
||||
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(scaleFactor * 100)) : "n/a";
|
||||
var size = $"{Data.MonitorWidth}×{Data.MonitorHeight}";
|
||||
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(Data.Dpi * 100 / 96.0)) : "n/a";
|
||||
return $"{size} \u2022 {scaling}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,21 +485,12 @@ internal static class FancyZonesThumbnailRenderer
|
||||
|
||||
private static List<NormalizedRect> GetFocusRects(int zoneCount)
|
||||
{
|
||||
// Focus layout parameters from FancyZonesEditor CanvasLayoutModel:
|
||||
// - DefaultOffset = 100px from top-left (normalized: ~0.05 for typical screen)
|
||||
// - OffsetShift = 50px per zone (normalized: ~0.025)
|
||||
// - ZoneSizeMultiplier = 0.4 (zones are 40% of screen)
|
||||
zoneCount = Math.Clamp(zoneCount, 1, 8);
|
||||
var rects = new List<NormalizedRect>(zoneCount);
|
||||
|
||||
const float defaultOffset = 0.05f; // ~100px on 1920px screen
|
||||
const float offsetShift = 0.025f; // ~50px on 1920px screen
|
||||
const float zoneSize = 0.4f; // 40% of screen
|
||||
|
||||
for (var i = 0; i < zoneCount; i++)
|
||||
{
|
||||
var offset = i * offsetShift;
|
||||
rects.Add(new NormalizedRect(defaultOffset + offset, defaultOffset + offset, zoneSize, zoneSize));
|
||||
var offset = i * 0.06f;
|
||||
rects.Add(new NormalizedRect(0.1f + offset, 0.1f + offset, 0.8f, 0.8f));
|
||||
}
|
||||
|
||||
return rects;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>PowerToysExtension</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>..\..\..\..\runner\svgs\icon.ico</ApplicationIcon>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<EnableMsixTooling>false</EnableMsixTooling>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.CommandLine.Invocation;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
using FancyZonesCLI.Utils;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using FancyZonesEditorCommon.Utils;
|
||||
|
||||
@@ -36,19 +35,13 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
|
||||
{
|
||||
// FancyZones running guard is handled by FancyZonesBaseCommand.
|
||||
int key = context.ParseResult.GetValueForArgument(_key);
|
||||
string layoutInput = context.ParseResult.GetValueForArgument(_layout);
|
||||
string layout = context.ParseResult.GetValueForArgument(_layout);
|
||||
|
||||
if (key < 0 || key > 9)
|
||||
{
|
||||
throw new InvalidOperationException(Properties.Resources.set_hotkey_error_invalid_key);
|
||||
}
|
||||
|
||||
// Normalize GUID to Windows format with braces (supports input with or without braces)
|
||||
if (!GuidHelper.TryNormalizeGuid(layoutInput, out string layout))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layoutInput));
|
||||
}
|
||||
|
||||
// Editor only allows assigning hotkeys to existing custom layouts.
|
||||
var customLayouts = FancyZonesDataIO.ReadCustomLayouts();
|
||||
|
||||
@@ -67,7 +60,7 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
|
||||
|
||||
if (!matchedLayout.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layoutInput));
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layout));
|
||||
}
|
||||
|
||||
string layoutName = matchedLayout.Value.Name;
|
||||
|
||||
@@ -140,12 +140,9 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize GUID to Windows format with braces (supports input with or without braces)
|
||||
string normalizedLayout = GuidHelper.NormalizeGuid(layout) ?? layout;
|
||||
|
||||
foreach (var customLayout in customLayouts.CustomLayouts)
|
||||
{
|
||||
if (customLayout.Uuid.Equals(normalizedLayout, StringComparison.OrdinalIgnoreCase))
|
||||
if (customLayout.Uuid.Equals(layout, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return customLayout;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace FancyZonesCLI.CommandLine;
|
||||
@@ -14,15 +13,15 @@ internal static class FancyZonesCliUsage
|
||||
public static void PrintUsage()
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.WriteLine(Properties.Resources.usage_title);
|
||||
Console.WriteLine("FancyZones CLI - Command line interface for FancyZones");
|
||||
Console.WriteLine();
|
||||
|
||||
var cmd = FancyZonesCliCommandFactory.CreateRootCommand();
|
||||
|
||||
Console.WriteLine(Properties.Resources.usage_syntax);
|
||||
Console.WriteLine("Usage: FancyZonesCLI [command] [options]");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine(Properties.Resources.usage_options);
|
||||
Console.WriteLine("Options:");
|
||||
foreach (var option in cmd.Options)
|
||||
{
|
||||
var aliases = string.Join(", ", option.Aliases);
|
||||
@@ -31,7 +30,7 @@ internal static class FancyZonesCliUsage
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.usage_commands);
|
||||
Console.WriteLine("Commands:");
|
||||
foreach (var command in cmd.Subcommands)
|
||||
{
|
||||
if (command.IsHidden)
|
||||
@@ -52,7 +51,7 @@ internal static class FancyZonesCliUsage
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.usage_examples);
|
||||
Console.WriteLine("Examples:");
|
||||
Console.WriteLine(" FancyZonesCLI --help");
|
||||
Console.WriteLine(" FancyZonesCLI --version");
|
||||
Console.WriteLine(" FancyZonesCLI get-monitors");
|
||||
@@ -60,135 +59,4 @@ internal static class FancyZonesCliUsage
|
||||
Console.WriteLine(" FancyZonesCLI set-layout <uuid> --monitor 1");
|
||||
Console.WriteLine(" FancyZonesCLI get-hotkeys");
|
||||
}
|
||||
|
||||
public static void PrintCommandUsage(string commandName)
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
|
||||
var rootCmd = FancyZonesCliCommandFactory.CreateRootCommand();
|
||||
|
||||
// Find matching subcommand by name or alias
|
||||
var subcommand = rootCmd.Subcommands.FirstOrDefault(c =>
|
||||
c.Aliases.Any(a => string.Equals(a, commandName, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
if (subcommand == null)
|
||||
{
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.usage_unknown_command, commandName));
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.usage_run_help);
|
||||
return;
|
||||
}
|
||||
|
||||
// Command name and description
|
||||
Console.WriteLine($"{Properties.Resources.usage_command} {subcommand.Name}");
|
||||
if (!string.IsNullOrEmpty(subcommand.Description))
|
||||
{
|
||||
Console.WriteLine($" {subcommand.Description}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Usage line
|
||||
string argsLabel = string.Join(" ", subcommand.Arguments.Select(a => $"<{a.Name}>"));
|
||||
string optionsLabel = subcommand.Options.Any() ? " [options]" : string.Empty;
|
||||
Console.WriteLine($"Usage: FancyZonesCLI {subcommand.Name} {argsLabel}{optionsLabel}".TrimEnd());
|
||||
Console.WriteLine();
|
||||
|
||||
// Aliases
|
||||
var aliases = subcommand.Aliases.Where(a => !string.Equals(a, subcommand.Name, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
if (aliases.Count > 0)
|
||||
{
|
||||
Console.WriteLine($"{Properties.Resources.usage_aliases} {string.Join(", ", aliases)}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// Arguments
|
||||
if (subcommand.Arguments.Any())
|
||||
{
|
||||
Console.WriteLine(Properties.Resources.usage_arguments);
|
||||
foreach (var arg in subcommand.Arguments)
|
||||
{
|
||||
var argDescription = arg.Description ?? string.Empty;
|
||||
Console.WriteLine($" <{arg.Name}>{(arg.Arity.MinimumNumberOfValues == 0 ? $" {Properties.Resources.usage_optional}" : string.Empty),-20} {argDescription}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// Options
|
||||
if (subcommand.Options.Any())
|
||||
{
|
||||
Console.WriteLine(Properties.Resources.usage_options);
|
||||
foreach (var option in subcommand.Options)
|
||||
{
|
||||
var optAliases = string.Join(", ", option.Aliases);
|
||||
var optDescription = option.Description ?? string.Empty;
|
||||
Console.WriteLine($" {optAliases,-25} {optDescription}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// Command-specific examples
|
||||
PrintCommandExamples(subcommand.Name);
|
||||
}
|
||||
|
||||
private static void PrintCommandExamples(string commandName)
|
||||
{
|
||||
Console.WriteLine(Properties.Resources.usage_examples);
|
||||
|
||||
switch (commandName.ToLowerInvariant())
|
||||
{
|
||||
case "get-monitors":
|
||||
Console.WriteLine(" FancyZonesCLI get-monitors");
|
||||
Console.WriteLine(" FancyZonesCLI m");
|
||||
break;
|
||||
|
||||
case "get-layouts":
|
||||
Console.WriteLine(" FancyZonesCLI get-layouts");
|
||||
Console.WriteLine(" FancyZonesCLI ls");
|
||||
break;
|
||||
|
||||
case "get-active-layout":
|
||||
Console.WriteLine(" FancyZonesCLI get-active-layout");
|
||||
Console.WriteLine(" FancyZonesCLI active");
|
||||
break;
|
||||
|
||||
case "set-layout":
|
||||
Console.WriteLine(" FancyZonesCLI set-layout focus");
|
||||
Console.WriteLine(" FancyZonesCLI set-layout columns --monitor 1");
|
||||
Console.WriteLine(" FancyZonesCLI set-layout {uuid} --all");
|
||||
Console.WriteLine(" FancyZonesCLI s rows -m 2");
|
||||
break;
|
||||
|
||||
case "open-editor":
|
||||
Console.WriteLine(" FancyZonesCLI open-editor");
|
||||
Console.WriteLine(" FancyZonesCLI e");
|
||||
break;
|
||||
|
||||
case "open-settings":
|
||||
Console.WriteLine(" FancyZonesCLI open-settings");
|
||||
Console.WriteLine(" FancyZonesCLI settings");
|
||||
break;
|
||||
|
||||
case "get-hotkeys":
|
||||
Console.WriteLine(" FancyZonesCLI get-hotkeys");
|
||||
Console.WriteLine(" FancyZonesCLI hk");
|
||||
break;
|
||||
|
||||
case "set-hotkey":
|
||||
Console.WriteLine(" FancyZonesCLI set-hotkey 1 {layout-uuid}");
|
||||
Console.WriteLine(" FancyZonesCLI shk 2 0CEBCBA9-9C32-4395-B93E-DC77485AD6D0");
|
||||
break;
|
||||
|
||||
case "remove-hotkey":
|
||||
Console.WriteLine(" FancyZonesCLI remove-hotkey 1");
|
||||
Console.WriteLine(" FancyZonesCLI rhk 2");
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($" FancyZonesCLI {commandName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace FancyZonesCLI;
|
||||
|
||||
internal sealed class Program
|
||||
{
|
||||
private static readonly string[] HelpFlags = ["--help", "-h", "-?"];
|
||||
|
||||
private static async Task<int> Main(string[] args)
|
||||
{
|
||||
Logger.InitializeLogger();
|
||||
@@ -23,17 +21,14 @@ internal sealed class Program
|
||||
NativeMethods.InitializeWindowMessages();
|
||||
|
||||
// Intercept help requests early and print custom usage.
|
||||
if (TryHandleHelpRequest(args))
|
||||
if (args.Any(a => string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(a, "-?", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
FancyZonesCliUsage.PrintUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Detect PowerShell script block expansion (when {} is interpreted as script block)
|
||||
if (DetectPowerShellScriptBlockArgs(args))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
RootCommand rootCommand = FancyZonesCliCommandFactory.CreateRootCommand();
|
||||
int exitCode = await rootCommand.InvokeAsync(args);
|
||||
|
||||
@@ -48,69 +43,4 @@ internal sealed class Program
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles help requests for root command and subcommands.
|
||||
/// </summary>
|
||||
/// <returns>True if help was printed, false otherwise.</returns>
|
||||
private static bool TryHandleHelpRequest(string[] args)
|
||||
{
|
||||
bool hasHelpFlag = args.Any(a => HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase)));
|
||||
if (!hasHelpFlag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get non-help arguments to identify subcommand
|
||||
var nonHelpArgs = args.Where(a => !HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase))).ToArray();
|
||||
|
||||
if (nonHelpArgs.Length == 0)
|
||||
{
|
||||
// Root help: fancyzones cli --help
|
||||
FancyZonesCliUsage.PrintUsage();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Subcommand help: fancyzones cli <command> --help
|
||||
string subcommandName = nonHelpArgs[0];
|
||||
FancyZonesCliUsage.PrintCommandUsage(subcommandName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects when PowerShell interprets {GUID} as a script block and converts it to encoded command args.
|
||||
/// This happens when users forget to quote GUIDs with braces in PowerShell.
|
||||
/// </summary>
|
||||
/// <returns>True if PowerShell script block args were detected, false otherwise.</returns>
|
||||
private static bool DetectPowerShellScriptBlockArgs(string[] args)
|
||||
{
|
||||
// PowerShell converts {scriptblock} to: -encodedCommand <base64> -inputFormat xml -outputFormat text
|
||||
bool hasEncodedCommand = args.Any(a => string.Equals(a, "-encodedCommand", StringComparison.OrdinalIgnoreCase));
|
||||
bool hasInputFormat = args.Any(a => string.Equals(a, "-inputFormat", StringComparison.OrdinalIgnoreCase));
|
||||
bool hasOutputFormat = args.Any(a => string.Equals(a, "-outputFormat", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (hasEncodedCommand || (hasInputFormat && hasOutputFormat))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_title);
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_explanation);
|
||||
Console.WriteLine(Properties.Resources.error_powershell_scriptblock_hint);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1}");
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1_example}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2}");
|
||||
Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2_example}");
|
||||
Console.WriteLine();
|
||||
|
||||
Logger.LogWarning("PowerShell script block expansion detected - user needs to quote GUID or omit braces");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,113 +349,5 @@ namespace FancyZonesCLI.Properties {
|
||||
return ResourceManager.GetString("editor_params_timeout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_title {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_explanation {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_explanation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_hint {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_hint", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option1 {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option1_example {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option1_example", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option2 {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string error_powershell_scriptblock_option2_example {
|
||||
get {
|
||||
return ResourceManager.GetString("error_powershell_scriptblock_option2_example", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_title {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_syntax {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_syntax", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_options {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_options", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_commands {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_commands", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_examples {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_examples", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_arguments {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_arguments", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_aliases {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_aliases", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_command {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_command", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_optional {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_optional", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_unknown_command {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_unknown_command", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string usage_run_help {
|
||||
get {
|
||||
return ResourceManager.GetString("usage_run_help", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,62 +230,4 @@ Tip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid',
|
||||
<data name="editor_params_timeout" xml:space="preserve">
|
||||
<value>Could not get current monitor information (timed out after {0}ms waiting for '{1}').</value>
|
||||
</data>
|
||||
|
||||
<!-- PowerShell Script Block Detection -->
|
||||
<data name="error_powershell_scriptblock_title" xml:space="preserve">
|
||||
<value>Error: Invalid GUID format detected.</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_explanation" xml:space="preserve">
|
||||
<value>PowerShell interprets curly braces {} as script blocks.</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_hint" xml:space="preserve">
|
||||
<value>Please quote your GUID or use it without braces:</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option1" xml:space="preserve">
|
||||
<value>Option 1 - Quote the GUID:</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option1_example" xml:space="preserve">
|
||||
<value>FancyZonesCLI shk 1 '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option2" xml:space="preserve">
|
||||
<value>Option 2 - Omit the braces (recommended):</value>
|
||||
</data>
|
||||
<data name="error_powershell_scriptblock_option2_example" xml:space="preserve">
|
||||
<value>FancyZonesCLI shk 1 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</value>
|
||||
</data>
|
||||
|
||||
<!-- CLI Usage -->
|
||||
<data name="usage_title" xml:space="preserve">
|
||||
<value>FancyZones CLI - Command line interface for FancyZones</value>
|
||||
</data>
|
||||
<data name="usage_syntax" xml:space="preserve">
|
||||
<value>Usage: FancyZonesCLI [command] [options]</value>
|
||||
</data>
|
||||
<data name="usage_options" xml:space="preserve">
|
||||
<value>Options:</value>
|
||||
</data>
|
||||
<data name="usage_commands" xml:space="preserve">
|
||||
<value>Commands:</value>
|
||||
</data>
|
||||
<data name="usage_examples" xml:space="preserve">
|
||||
<value>Examples:</value>
|
||||
</data>
|
||||
<data name="usage_arguments" xml:space="preserve">
|
||||
<value>Arguments:</value>
|
||||
</data>
|
||||
<data name="usage_aliases" xml:space="preserve">
|
||||
<value>Aliases:</value>
|
||||
</data>
|
||||
<data name="usage_command" xml:space="preserve">
|
||||
<value>Command:</value>
|
||||
</data>
|
||||
<data name="usage_optional" xml:space="preserve">
|
||||
<value>(optional)</value>
|
||||
</data>
|
||||
<data name="usage_unknown_command" xml:space="preserve">
|
||||
<value>Unknown command: {0}</value>
|
||||
</data>
|
||||
<data name="usage_run_help" xml:space="preserve">
|
||||
<value>Run 'FancyZonesCLI --help' to see available commands.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace FancyZonesCLI.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for normalizing GUID strings to Windows format with braces.
|
||||
/// Supports input with or without braces: both "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
/// and "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" are accepted.
|
||||
/// </summary>
|
||||
internal static class GuidHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Normalizes a GUID string to Windows format with braces.
|
||||
/// Returns null if the input is not a valid GUID.
|
||||
/// </summary>
|
||||
/// <param name="input">GUID string with or without braces.</param>
|
||||
/// <returns>GUID in "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" format, or null if invalid.</returns>
|
||||
public static string? NormalizeGuid(string? input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Guid.TryParse(input, out Guid guid))
|
||||
{
|
||||
// "B" format includes braces: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
return guid.ToString("B").ToUpperInvariant();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to normalize a GUID string to Windows format with braces.
|
||||
/// </summary>
|
||||
/// <param name="input">GUID string with or without braces.</param>
|
||||
/// <param name="normalizedGuid">The normalized GUID string, or the original input if normalization fails.</param>
|
||||
/// <returns>True if the input was successfully normalized; otherwise, false.</returns>
|
||||
public static bool TryNormalizeGuid(string? input, [NotNullWhen(true)] out string? normalizedGuid)
|
||||
{
|
||||
normalizedGuid = NormalizeGuid(input);
|
||||
return normalizedGuid != null;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
|
||||
@@ -24,10 +23,8 @@ namespace FancyZonesEditorCommon.Data
|
||||
{
|
||||
public struct CanvasZoneWrapper
|
||||
{
|
||||
[JsonPropertyName("X")]
|
||||
public int X { get; set; }
|
||||
|
||||
[JsonPropertyName("Y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
@@ -31,7 +31,7 @@ internal static class Program
|
||||
Console.InputEncoding = Encoding.Unicode;
|
||||
|
||||
// Initialize logger to file (same as other modules)
|
||||
CliLogger.Initialize("\\Image Resizer\\CLI");
|
||||
CliLogger.Initialize("\\ImageResizer\\Logs");
|
||||
CliLogger.Info($"ImageResizerCLI started with {args.Length} argument(s)");
|
||||
|
||||
try
|
||||
|
||||
@@ -126,10 +126,13 @@ namespace ImageResizer.Properties
|
||||
h => ncc.CollectionChanged -= h,
|
||||
() => settings.CustomSize = new CustomSize());
|
||||
|
||||
// Reset is used instead of Replace to avoid ArgumentOutOfRangeException
|
||||
// when notifying changes for virtual items (CustomSize/AiSize) that exist
|
||||
// outside the bounds of the underlying _sizes collection.
|
||||
Assert.AreEqual(NotifyCollectionChangedAction.Reset, result.Arguments.Action);
|
||||
Assert.AreEqual(NotifyCollectionChangedAction.Replace, result.Arguments.Action);
|
||||
Assert.AreEqual(1, result.Arguments.NewItems.Count);
|
||||
Assert.AreEqual(settings.CustomSize, result.Arguments.NewItems[0]);
|
||||
Assert.AreEqual(0, result.Arguments.NewStartingIndex);
|
||||
Assert.AreEqual(1, result.Arguments.OldItems.Count);
|
||||
Assert.AreEqual(originalCustomSize, result.Arguments.OldItems[0]);
|
||||
Assert.AreEqual(0, result.Arguments.OldStartingIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -216,15 +216,27 @@ namespace ImageResizer.Properties
|
||||
{
|
||||
if (e.PropertyName == nameof(Models.CustomSize))
|
||||
{
|
||||
var oldCustomSize = _customSize;
|
||||
_customSize = settings.CustomSize;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
OnCollectionChanged(
|
||||
new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace,
|
||||
_customSize,
|
||||
oldCustomSize,
|
||||
_sizes.Count));
|
||||
}
|
||||
else if (e.PropertyName == nameof(Models.AiSize))
|
||||
{
|
||||
var oldAiSize = _aiSize;
|
||||
_aiSize = settings.AiSize;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
OnCollectionChanged(
|
||||
new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace,
|
||||
_aiSize,
|
||||
oldAiSize,
|
||||
_sizes.Count + 1));
|
||||
}
|
||||
else if (e.PropertyName == nameof(Sizes))
|
||||
{
|
||||
|
||||
@@ -388,13 +388,6 @@ namespace Peek.UI
|
||||
IsErrorVisible = true;
|
||||
}
|
||||
|
||||
public void ShowError(string message)
|
||||
{
|
||||
IsErrorVisible = false;
|
||||
ErrorMessage = message;
|
||||
IsErrorVisible = true;
|
||||
}
|
||||
|
||||
private void NavigationThrottleTimer_Tick(object? sender, object e)
|
||||
{
|
||||
if (sender == null)
|
||||
|
||||
@@ -50,8 +50,7 @@
|
||||
Item="{x:Bind ViewModel.CurrentItem, Mode=OneWay}"
|
||||
NumberOfFiles="{x:Bind ViewModel.DisplayItemCount, Mode=OneWay}"
|
||||
PreviewSizeChanged="FilePreviewer_PreviewSizeChanged"
|
||||
ScalingFactor="{x:Bind ViewModel.ScalingFactor, Mode=OneWay}"
|
||||
Visibility="{x:Bind ContentVisibility(ViewModel.IsErrorVisible), Mode=OneWay}" />
|
||||
ScalingFactor="{x:Bind ViewModel.ScalingFactor, Mode=OneWay}" />
|
||||
|
||||
<InfoBar
|
||||
x:Name="ErrorInfoBar"
|
||||
@@ -60,7 +59,6 @@
|
||||
Grid.RowSpan="2"
|
||||
Margin="4,0,4,6"
|
||||
VerticalAlignment="Bottom"
|
||||
Closed="ErrorInfoBar_Closed"
|
||||
IsOpen="{x:Bind ViewModel.IsErrorVisible, Mode=TwoWay}"
|
||||
Message="{x:Bind ViewModel.ErrorMessage, Mode=OneWay}"
|
||||
Severity="Error" />
|
||||
|
||||
@@ -14,7 +14,6 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Peek.Common.Constants;
|
||||
using Peek.Common.Extensions;
|
||||
using Peek.Common.Helpers;
|
||||
using Peek.FilePreviewer.Models;
|
||||
using Peek.FilePreviewer.Previewers;
|
||||
using Peek.UI.Extensions;
|
||||
@@ -196,20 +195,6 @@ namespace Peek.UI
|
||||
bootTime.Start();
|
||||
|
||||
ViewModel.Initialize(selectedItem);
|
||||
|
||||
// If no files were found (e.g., in virtual folders like Home/Recent), show an error
|
||||
if (ViewModel.CurrentItem == null)
|
||||
{
|
||||
Logger.LogInfo("Peek: No files found to preview, showing error.");
|
||||
var errorMessage = ResourceLoaderInstance.ResourceLoader.GetString("NoFilesSelected");
|
||||
ViewModel.ShowError(errorMessage);
|
||||
|
||||
// Still show the window so user can see the warning
|
||||
this.Show();
|
||||
WindowHelpers.BringToForeground(this.GetWindowHandle());
|
||||
return;
|
||||
}
|
||||
|
||||
ViewModel.ScalingFactor = this.GetMonitorScale();
|
||||
this.Content.KeyUp += Content_KeyUp;
|
||||
|
||||
@@ -317,24 +302,5 @@ namespace Peek.UI
|
||||
{
|
||||
themeListener?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Visibility.Collapsed when error is showing, Visibility.Visible when not.
|
||||
/// </summary>
|
||||
public Visibility ContentVisibility(bool isErrorVisible)
|
||||
{
|
||||
return isErrorVisible ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle InfoBar closed - if there's no current item, close the window.
|
||||
/// </summary>
|
||||
private void ErrorInfoBar_Closed(InfoBar sender, InfoBarClosedEventArgs args)
|
||||
{
|
||||
if (ViewModel.CurrentItem == null)
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,10 +341,6 @@
|
||||
<value>No more files to preview.</value>
|
||||
<comment>The message to show when there are no files remaining to preview.</comment>
|
||||
</data>
|
||||
<data name="NoFilesSelected" xml:space="preserve">
|
||||
<value>No files selected or this folder is not supported for preview.</value>
|
||||
<comment>Displayed when Peek is activated in a virtual folder (like Home or Recent) where file selection cannot be retrieved.</comment>
|
||||
</data>
|
||||
<data name="DeleteFileError_NotFound" xml:space="preserve">
|
||||
<value>The file cannot be found. Please check if the file has been moved, renamed, or deleted.</value>
|
||||
<comment>Displayed if the file or path was not found</comment>
|
||||
|
||||
@@ -193,102 +193,6 @@ GeneralSettings get_general_settings()
|
||||
return settings;
|
||||
}
|
||||
|
||||
void apply_module_status_update(const json::JsonObject& module_config, bool save)
|
||||
{
|
||||
Logger::info(L"apply_module_status_update: {}", std::wstring{ module_config.ToString() });
|
||||
|
||||
// Expected format: {"ModuleName": true/false} - only one module per update
|
||||
auto iter = module_config.First();
|
||||
if (!iter.HasCurrent())
|
||||
{
|
||||
Logger::warn(L"apply_module_status_update: Empty module config");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& element = iter.Current();
|
||||
const auto value = element.Value();
|
||||
if (value.ValueType() != json::JsonValueType::Boolean)
|
||||
{
|
||||
Logger::warn(L"apply_module_status_update: Invalid value type for module status");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::wstring name{ element.Key().c_str() };
|
||||
if (modules().find(name) == modules().end())
|
||||
{
|
||||
Logger::warn(L"apply_module_status_update: Module {} not found", name);
|
||||
return;
|
||||
}
|
||||
|
||||
PowertoyModule& powertoy = modules().at(name);
|
||||
const bool module_inst_enabled = powertoy->is_enabled();
|
||||
bool target_enabled = value.GetBoolean();
|
||||
|
||||
auto gpo_rule = powertoy->gpo_policy_enabled_configuration();
|
||||
if (gpo_rule == powertoys_gpo::gpo_rule_configured_enabled || gpo_rule == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
// Apply the GPO Rule.
|
||||
target_enabled = gpo_rule == powertoys_gpo::gpo_rule_configured_enabled;
|
||||
}
|
||||
|
||||
if (module_inst_enabled == target_enabled)
|
||||
{
|
||||
Logger::info(L"apply_module_status_update: Module {} already in target state {}", name, target_enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_enabled)
|
||||
{
|
||||
Logger::info(L"apply_module_status_update: Enabling powertoy {}", name);
|
||||
powertoy->enable();
|
||||
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
|
||||
hkmng.EnableHotkeyByModule(name);
|
||||
|
||||
// Trigger AI capability detection when ImageResizer is enabled
|
||||
if (name == L"Image Resizer")
|
||||
{
|
||||
Logger::info(L"ImageResizer enabled, triggering AI capability detection");
|
||||
DetectAiCapabilitiesAsync(true); // Skip settings check since we know it's being enabled
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"apply_module_status_update: Disabling powertoy {}", name);
|
||||
powertoy->disable();
|
||||
auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
|
||||
hkmng.DisableHotkeyByModule(name);
|
||||
}
|
||||
// Sync the hotkey state with the module state, so it can be removed for disabled modules.
|
||||
powertoy.UpdateHotkeyEx();
|
||||
|
||||
if (save)
|
||||
{
|
||||
// Load existing settings and only update the specific module's enabled state
|
||||
json::JsonObject current_settings = PTSettingsHelper::load_general_settings();
|
||||
|
||||
json::JsonObject enabled;
|
||||
if (current_settings.HasKey(L"enabled"))
|
||||
{
|
||||
enabled = current_settings.GetNamedObject(L"enabled");
|
||||
}
|
||||
|
||||
// Check if the saved state is different from the requested state
|
||||
bool current_saved = enabled.HasKey(name) ? enabled.GetNamedBoolean(name, true) : true;
|
||||
|
||||
if (current_saved != target_enabled)
|
||||
{
|
||||
// Update only this module's enabled state
|
||||
enabled.SetNamedValue(name, json::value(target_enabled));
|
||||
current_settings.SetNamedValue(L"enabled", enabled);
|
||||
|
||||
PTSettingsHelper::save_general_settings(current_settings);
|
||||
|
||||
GeneralSettings settings_for_trace = get_general_settings();
|
||||
Trace::SettingsChanged(settings_for_trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apply_general_settings(const json::JsonObject& general_configs, bool save)
|
||||
{
|
||||
std::wstring old_settings_json_string;
|
||||
@@ -463,21 +367,11 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
|
||||
if (json::has(general_configs, L"show_theme_adaptive_tray_icon", json::JsonValueType::Boolean))
|
||||
{
|
||||
bool new_theme_adaptive = general_configs.GetNamedBoolean(L"show_theme_adaptive_tray_icon");
|
||||
Logger::info(L"apply_general_settings: show_theme_adaptive_tray_icon current={}, new={}",
|
||||
show_theme_adaptive_tray_icon, new_theme_adaptive);
|
||||
if (show_theme_adaptive_tray_icon != new_theme_adaptive)
|
||||
{
|
||||
show_theme_adaptive_tray_icon = new_theme_adaptive;
|
||||
set_tray_icon_theme_adaptive(show_theme_adaptive_tray_icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info(L"apply_general_settings: show_theme_adaptive_tray_icon unchanged, skipping update");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn(L"apply_general_settings: show_theme_adaptive_tray_icon not found in config");
|
||||
}
|
||||
|
||||
if (json::has(general_configs, L"ignored_conflict_properties", json::JsonValueType::Object))
|
||||
|
||||
@@ -38,5 +38,4 @@ struct GeneralSettings
|
||||
json::JsonObject load_general_settings();
|
||||
GeneralSettings get_general_settings();
|
||||
void apply_general_settings(const json::JsonObject& general_configs, bool save = true);
|
||||
void apply_module_status_update(const json::JsonObject& module_config, bool save = true);
|
||||
void start_enabled_powertoys();
|
||||
@@ -215,12 +215,6 @@ void dispatch_received_json(const std::wstring& json_to_parse)
|
||||
// current_settings_ipc->send(settings_string);
|
||||
// }
|
||||
}
|
||||
else if (name == L"module_status")
|
||||
{
|
||||
// Handle single module enable/disable update
|
||||
// Expected format: {"module_status": {"ModuleName": true/false}}
|
||||
apply_module_status_update(value.GetObjectW());
|
||||
}
|
||||
else if (name == L"powertoys")
|
||||
{
|
||||
dispatch_json_config_to_modules(value.GetObjectW());
|
||||
|
||||
@@ -273,19 +273,12 @@ static HICON get_icon(Theme theme)
|
||||
{
|
||||
std::wstring icon_path = get_module_folderpath();
|
||||
icon_path += theme == Theme::Dark ? L"\\svgs\\PowerToysWhite.ico" : L"\\svgs\\PowerToysDark.ico";
|
||||
Logger::trace(L"get_icon: Loading icon from path: {}", icon_path);
|
||||
|
||||
HICON icon = static_cast<HICON>(LoadImage(NULL,
|
||||
return static_cast<HICON>(LoadImage(NULL,
|
||||
icon_path.c_str(),
|
||||
IMAGE_ICON,
|
||||
0,
|
||||
0,
|
||||
LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED));
|
||||
if (!icon)
|
||||
{
|
||||
Logger::warn(L"get_icon: Failed to load icon from {}, error: {}", icon_path, GetLastError());
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
||||
@@ -381,45 +374,13 @@ void set_tray_icon_visible(bool shouldIconBeVisible)
|
||||
|
||||
void set_tray_icon_theme_adaptive(bool theme_adaptive)
|
||||
{
|
||||
Logger::info(L"set_tray_icon_theme_adaptive: Called with theme_adaptive={}, current theme_adaptive_enabled={}",
|
||||
theme_adaptive, theme_adaptive_enabled);
|
||||
|
||||
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
HICON icon = nullptr;
|
||||
|
||||
if (theme_adaptive)
|
||||
{
|
||||
icon = get_icon(theme_listener.AppTheme);
|
||||
if (!icon)
|
||||
{
|
||||
Logger::warn(L"set_tray_icon_theme_adaptive: Failed to load theme adaptive icon, falling back to default");
|
||||
}
|
||||
}
|
||||
|
||||
// If not requesting adaptive icon, or if adaptive icon failed to load, use default icon
|
||||
if (!icon)
|
||||
{
|
||||
icon = LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
|
||||
if (theme_adaptive && icon)
|
||||
{
|
||||
// We requested adaptive but had to fall back, so update the flag
|
||||
theme_adaptive = false;
|
||||
Logger::info(L"set_tray_icon_theme_adaptive: Using default icon as fallback");
|
||||
}
|
||||
}
|
||||
|
||||
theme_adaptive_enabled = theme_adaptive;
|
||||
|
||||
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
HICON const icon = theme_adaptive ? get_icon(theme_listener.AppTheme) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
|
||||
if (icon)
|
||||
{
|
||||
tray_icon_data.hIcon = icon;
|
||||
BOOL result = Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
|
||||
Logger::info(L"set_tray_icon_theme_adaptive: Icon updated, theme_adaptive_enabled={}, Shell_NotifyIcon result={}",
|
||||
theme_adaptive_enabled, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"set_tray_icon_theme_adaptive: Failed to load any icon");
|
||||
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.PowerToys.QuickAccess</RootNamespace>
|
||||
<AssemblyName>PowerToys.QuickAccess</AssemblyName>
|
||||
<ApplicationIcon>..\..\runner\svgs\icon.ico</ApplicationIcon>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
|
||||
@@ -15,10 +15,13 @@ namespace Microsoft.PowerToys.QuickAccess.Flyout;
|
||||
public sealed partial class AppsListPage : Page
|
||||
{
|
||||
private FlyoutNavigationContext? _context;
|
||||
private bool _isCleanedUp = false;
|
||||
|
||||
public AppsListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
NavigationCacheMode = NavigationCacheMode.Disabled;
|
||||
Unloaded += OnUnloaded;
|
||||
}
|
||||
|
||||
public AllAppsViewModel ViewModel { get; private set; } = default!;
|
||||
@@ -26,6 +29,7 @@ public sealed partial class AppsListPage : Page
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
_isCleanedUp = false;
|
||||
|
||||
if (e.Parameter is FlyoutNavigationContext context)
|
||||
{
|
||||
@@ -36,6 +40,37 @@ public sealed partial class AppsListPage : Page
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||
{
|
||||
base.OnNavigatingFrom(e);
|
||||
CleanupPage();
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CleanupPage();
|
||||
}
|
||||
|
||||
private void CleanupPage()
|
||||
{
|
||||
if (_isCleanedUp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isCleanedUp = true;
|
||||
|
||||
// Clear the collection before cleaning up to release all item references
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.FlyoutMenuItems.Clear();
|
||||
}
|
||||
|
||||
DataContext = null;
|
||||
ViewModel = null!;
|
||||
_context = null;
|
||||
}
|
||||
|
||||
private void BackButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_context == null || Frame == null)
|
||||
@@ -51,7 +86,6 @@ public sealed partial class AppsListPage : Page
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.DashboardSortOrder = DashboardSortOrder.Alphabetical;
|
||||
((ToggleMenuFlyoutItem)sender).IsChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +94,6 @@ public sealed partial class AppsListPage : Page
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.DashboardSortOrder = DashboardSortOrder.ByStatus;
|
||||
((ToggleMenuFlyoutItem)sender).IsChecked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed partial class LaunchPage : Page
|
||||
public LaunchPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
NavigationCacheMode = NavigationCacheMode.Disabled;
|
||||
}
|
||||
|
||||
public LauncherViewModel ViewModel { get; private set; } = default!;
|
||||
@@ -44,6 +45,12 @@ public sealed partial class LaunchPage : Page
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||
{
|
||||
base.OnNavigatingFrom(e);
|
||||
DataContext = null;
|
||||
}
|
||||
|
||||
private void SettingsBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_coordinator?.OpenSettings();
|
||||
|
||||
@@ -65,4 +65,16 @@ public sealed partial class ShellPage : Page
|
||||
appsListPage.ViewModel?.RefreshSettings();
|
||||
}
|
||||
}
|
||||
|
||||
internal void NavigateToLaunchIfNeeded()
|
||||
{
|
||||
// Always clear FlyoutMenuItems to release memory
|
||||
_allAppsViewModel?.FlyoutMenuItems.Clear();
|
||||
|
||||
// Navigate back to LaunchPage if on AppsListPage
|
||||
if (ContentFrame.Content is AppsListPage)
|
||||
{
|
||||
NavigateToLaunch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public sealed partial class MainWindow : WindowEx, IDisposable
|
||||
var token = _trimCts.Token;
|
||||
|
||||
// Delay the trim to avoid aggressive GC during quick toggles
|
||||
Task.Delay(2000, token).ContinueWith(
|
||||
Task.Delay(5000, token).ContinueWith(
|
||||
_ =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
@@ -152,7 +152,24 @@ public sealed partial class MainWindow : WindowEx, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
TrimMemory();
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
// Navigate back to LaunchPage before memory trim to release AppsListPage resources
|
||||
ShellHost.NavigateToLaunchIfNeeded();
|
||||
|
||||
// Give UI time to complete cleanup before forcing GC
|
||||
Task.Delay(500, token).ContinueWith(
|
||||
__ =>
|
||||
{
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(TrimMemory);
|
||||
}
|
||||
},
|
||||
token,
|
||||
TaskContinuationOptions.None,
|
||||
TaskScheduler.Default);
|
||||
});
|
||||
},
|
||||
token,
|
||||
TaskContinuationOptions.None,
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace Microsoft.PowerToys.QuickAccess.Services;
|
||||
|
||||
@@ -21,9 +19,9 @@ public interface IQuickAccessCoordinator
|
||||
|
||||
Task<bool> ShowDocumentationAsync();
|
||||
|
||||
bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled);
|
||||
void NotifyUserSettingsInteraction();
|
||||
|
||||
void SendSortOrderUpdate(GeneralSettings generalSettings);
|
||||
bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled);
|
||||
|
||||
void ReportBug();
|
||||
|
||||
|
||||
@@ -55,8 +55,37 @@ internal sealed class QuickAccessCoordinator : IQuickAccessCoordinator, IDisposa
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public void NotifyUserSettingsInteraction()
|
||||
{
|
||||
Logger.LogDebug("QuickAccessCoordinator.NotifyUserSettingsInteraction invoked.");
|
||||
}
|
||||
|
||||
public bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled)
|
||||
=> TrySendIpcMessage($"{{\"module_status\": {{\"{ModuleHelper.GetModuleKey(moduleType)}\": {isEnabled.ToString().ToLowerInvariant()}}}}}", "module status update");
|
||||
{
|
||||
GeneralSettings? updatedSettings = null;
|
||||
lock (_generalSettingsLock)
|
||||
{
|
||||
var repository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
|
||||
var generalSettings = repository.SettingsConfig;
|
||||
var current = ModuleHelper.GetIsModuleEnabled(generalSettings, moduleType);
|
||||
if (current == isEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ModuleHelper.SetIsModuleEnabled(generalSettings, moduleType, isEnabled);
|
||||
_settingsUtils.SaveSettings(generalSettings.ToJsonString());
|
||||
Logger.LogInfo($"QuickAccess updated module '{moduleType}' enabled state to {isEnabled}.");
|
||||
updatedSettings = generalSettings;
|
||||
}
|
||||
|
||||
if (updatedSettings != null)
|
||||
{
|
||||
SendGeneralSettingsUpdate(updatedSettings);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ReportBug()
|
||||
{
|
||||
@@ -102,10 +131,20 @@ internal sealed class QuickAccessCoordinator : IQuickAccessCoordinator, IDisposa
|
||||
Logger.LogDebug($"QuickAccessCoordinator received IPC payload: {message}");
|
||||
}
|
||||
|
||||
public void SendSortOrderUpdate(GeneralSettings generalSettings)
|
||||
private void SendGeneralSettingsUpdate(GeneralSettings updatedSettings)
|
||||
{
|
||||
var outgoing = new OutGoingGeneralSettings(generalSettings);
|
||||
TrySendIpcMessage(outgoing.ToString(), "sort order update");
|
||||
string payload;
|
||||
try
|
||||
{
|
||||
payload = new OutGoingGeneralSettings(updatedSettings).ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("QuickAccessCoordinator: failed to serialize general settings payload.", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
TrySendIpcMessage(payload, "general settings update");
|
||||
}
|
||||
|
||||
private bool TrySendIpcMessage(string payload, string operationDescription)
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Microsoft.PowerToys.QuickAccess.ViewModels;
|
||||
|
||||
public sealed class AllAppsViewModel : Observable
|
||||
{
|
||||
private readonly object _sortLock = new object();
|
||||
private readonly IQuickAccessCoordinator _coordinator;
|
||||
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
|
||||
private readonly SettingsUtils _settingsUtils;
|
||||
@@ -31,9 +30,6 @@ public sealed class AllAppsViewModel : Observable
|
||||
private readonly List<FlyoutMenuItem> _allFlyoutMenuItems = new();
|
||||
private GeneralSettings _generalSettings;
|
||||
|
||||
// Flag to prevent toggle operations during sorting to avoid race conditions.
|
||||
private bool _isSorting;
|
||||
|
||||
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
|
||||
|
||||
public DashboardSortOrder DashboardSortOrder
|
||||
@@ -44,9 +40,9 @@ public sealed class AllAppsViewModel : Observable
|
||||
if (_generalSettings.DashboardSortOrder != value)
|
||||
{
|
||||
_generalSettings.DashboardSortOrder = value;
|
||||
_coordinator.SendSortOrderUpdate(_generalSettings);
|
||||
_settingsUtils.SaveSettings(_generalSettings.ToJsonString(), _generalSettings.GetModuleName());
|
||||
OnPropertyChanged();
|
||||
SortFlyoutMenuItems();
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +54,7 @@ public sealed class AllAppsViewModel : Observable
|
||||
_settingsUtils = SettingsUtils.Default;
|
||||
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
|
||||
_generalSettings = _settingsRepository.SettingsConfig;
|
||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
_settingsRepository.SettingsChanged += OnSettingsChanged;
|
||||
|
||||
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
@@ -90,6 +87,7 @@ public sealed class AllAppsViewModel : Observable
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
_generalSettings = newSettings;
|
||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
OnPropertyChanged(nameof(DashboardSortOrder));
|
||||
RefreshFlyoutMenuItems();
|
||||
});
|
||||
@@ -122,55 +120,30 @@ public sealed class AllAppsViewModel : Observable
|
||||
}
|
||||
}
|
||||
|
||||
SortFlyoutMenuItems();
|
||||
}
|
||||
|
||||
private void SortFlyoutMenuItems()
|
||||
{
|
||||
if (_isSorting)
|
||||
var sortedItems = DashboardSortOrder switch
|
||||
{
|
||||
DashboardSortOrder.ByStatus => _allFlyoutMenuItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
|
||||
_ => _allFlyoutMenuItems.OrderBy(x => x.Label).ToList(),
|
||||
};
|
||||
|
||||
if (FlyoutMenuItems.Count == 0)
|
||||
{
|
||||
foreach (var item in sortedItems)
|
||||
{
|
||||
FlyoutMenuItems.Add(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_sortLock)
|
||||
for (int i = 0; i < sortedItems.Count; i++)
|
||||
{
|
||||
_isSorting = true;
|
||||
try
|
||||
var item = sortedItems[i];
|
||||
var oldIndex = FlyoutMenuItems.IndexOf(item);
|
||||
|
||||
if (oldIndex != -1 && oldIndex != i)
|
||||
{
|
||||
var sortedItems = DashboardSortOrder switch
|
||||
{
|
||||
DashboardSortOrder.ByStatus => _allFlyoutMenuItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
|
||||
_ => _allFlyoutMenuItems.OrderBy(x => x.Label).ToList(),
|
||||
};
|
||||
|
||||
if (FlyoutMenuItems.Count == 0)
|
||||
{
|
||||
foreach (var item in sortedItems)
|
||||
{
|
||||
FlyoutMenuItems.Add(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sortedItems.Count; i++)
|
||||
{
|
||||
var item = sortedItems[i];
|
||||
var oldIndex = FlyoutMenuItems.IndexOf(item);
|
||||
|
||||
if (oldIndex != -1 && oldIndex != i)
|
||||
{
|
||||
FlyoutMenuItems.Move(oldIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Use dispatcher to reset flag after UI updates complete
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
_isSorting = false;
|
||||
});
|
||||
FlyoutMenuItems.Move(oldIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,17 +151,17 @@ public sealed class AllAppsViewModel : Observable
|
||||
private void EnabledChangedOnUI(ModuleListItem item)
|
||||
{
|
||||
var flyoutItem = (FlyoutMenuItem)item;
|
||||
var isEnabled = flyoutItem.IsEnabled;
|
||||
|
||||
// Ignore toggle operations during sorting to prevent race conditions.
|
||||
// Revert the toggle state since UI already changed due to TwoWay binding.
|
||||
if (_isSorting)
|
||||
if (_coordinator.UpdateModuleEnabled(flyoutItem.Tag, flyoutItem.IsEnabled))
|
||||
{
|
||||
flyoutItem.UpdateStatus(!isEnabled);
|
||||
return;
|
||||
}
|
||||
_coordinator.NotifyUserSettingsInteraction();
|
||||
|
||||
_coordinator.UpdateModuleEnabled(flyoutItem.Tag, flyoutItem.IsEnabled);
|
||||
SortFlyoutMenuItems();
|
||||
// Trigger re-sort immediately when status changes on UI
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void ModuleEnabledChangedOnSettingsPage()
|
||||
{
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,13 +112,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
eventHandle.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
case ModuleType.LightSwitch:
|
||||
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.LightSwitchToggleEvent()))
|
||||
{
|
||||
eventHandle.Set();
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -64,7 +64,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
|
||||
AddFlyoutMenuItem(ModuleType.FancyZones);
|
||||
AddFlyoutMenuItem(ModuleType.Hosts);
|
||||
AddFlyoutMenuItem(ModuleType.LightSwitch);
|
||||
AddFlyoutMenuItem(ModuleType.PowerLauncher);
|
||||
AddFlyoutMenuItem(ModuleType.PowerOCR);
|
||||
AddFlyoutMenuItem(ModuleType.RegistryPreview);
|
||||
@@ -121,7 +120,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
{
|
||||
ModuleType.ColorPicker => SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
||||
ModuleType.FancyZones => SettingsRepository<FancyZonesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.ToString(),
|
||||
ModuleType.LightSwitch => SettingsRepository<LightSwitchSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ToggleThemeHotkey.Value.ToString(),
|
||||
ModuleType.PowerLauncher => SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.ToString(),
|
||||
ModuleType.PowerOCR => SettingsRepository<PowerOcrSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
||||
ModuleType.Workspaces => SettingsRepository<WorkspacesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.Hotkey.Value.ToString(),
|
||||
|
||||
@@ -117,47 +117,5 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
|
||||
case ModuleType.GeneralSettings: generalSettingsConfig.EnableQuickAccess = isEnabled; break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module key name used in IPC messages and settings JSON.
|
||||
/// These names match the JsonPropertyName attributes in EnabledModules class.
|
||||
/// </summary>
|
||||
public static string GetModuleKey(ModuleType moduleType)
|
||||
{
|
||||
return moduleType switch
|
||||
{
|
||||
ModuleType.AdvancedPaste => AdvancedPasteSettings.ModuleName,
|
||||
ModuleType.AlwaysOnTop => AlwaysOnTopSettings.ModuleName,
|
||||
ModuleType.Awake => AwakeSettings.ModuleName,
|
||||
ModuleType.CmdPal => "CmdPal", // No dedicated settings class
|
||||
ModuleType.ColorPicker => ColorPickerSettings.ModuleName,
|
||||
ModuleType.CropAndLock => CropAndLockSettings.ModuleName,
|
||||
ModuleType.CursorWrap => CursorWrapSettings.ModuleName,
|
||||
ModuleType.EnvironmentVariables => EnvironmentVariablesSettings.ModuleName,
|
||||
ModuleType.FancyZones => FancyZonesSettings.ModuleName,
|
||||
ModuleType.FileLocksmith => FileLocksmithSettings.ModuleName,
|
||||
ModuleType.FindMyMouse => FindMyMouseSettings.ModuleName,
|
||||
ModuleType.Hosts => HostsSettings.ModuleName,
|
||||
ModuleType.ImageResizer => ImageResizerSettings.ModuleName,
|
||||
ModuleType.KeyboardManager => KeyboardManagerSettings.ModuleName,
|
||||
ModuleType.LightSwitch => LightSwitchSettings.ModuleName,
|
||||
ModuleType.MouseHighlighter => MouseHighlighterSettings.ModuleName,
|
||||
ModuleType.MouseJump => MouseJumpSettings.ModuleName,
|
||||
ModuleType.MousePointerCrosshairs => MousePointerCrosshairsSettings.ModuleName,
|
||||
ModuleType.MouseWithoutBorders => MouseWithoutBordersSettings.ModuleName,
|
||||
ModuleType.NewPlus => NewPlusSettings.ModuleName,
|
||||
ModuleType.Peek => PeekSettings.ModuleName,
|
||||
ModuleType.PowerRename => PowerRenameSettings.ModuleName,
|
||||
ModuleType.PowerLauncher => PowerLauncherSettings.ModuleName,
|
||||
ModuleType.PowerAccent => PowerAccentSettings.ModuleName,
|
||||
ModuleType.RegistryPreview => RegistryPreviewSettings.ModuleName,
|
||||
ModuleType.MeasureTool => MeasureToolSettings.ModuleName,
|
||||
ModuleType.ShortcutGuide => ShortcutGuideSettings.ModuleName,
|
||||
ModuleType.PowerOCR => PowerOcrSettings.ModuleName,
|
||||
ModuleType.Workspaces => WorkspacesSettings.ModuleName,
|
||||
ModuleType.ZoomIt => ZoomItSettings.ModuleName,
|
||||
_ => moduleType.ToString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
namespace Settings.UI.Library
|
||||
{
|
||||
public class LightSwitchSettings : BasePTModuleSettings, ISettingsConfig, ICloneable, IHotkeyConfig
|
||||
{
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonSerializable(typeof(HostsSettings))]
|
||||
[JsonSerializable(typeof(ImageResizerSettings))]
|
||||
[JsonSerializable(typeof(KeyboardManagerSettings))]
|
||||
[JsonSerializable(typeof(LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(MeasureToolSettings))]
|
||||
[JsonSerializable(typeof(MouseHighlighterSettings))]
|
||||
[JsonSerializable(typeof(MouseJumpSettings))]
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
[JsonSerializable(typeof(FileLocksmithSettings))]
|
||||
[JsonSerializable(typeof(FindMyMouseSettings))]
|
||||
[JsonSerializable(typeof(IList<PowerToysReleaseInfo>))]
|
||||
[JsonSerializable(typeof(LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(SettingsUILibrary.LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(MeasureToolSettings))]
|
||||
[JsonSerializable(typeof(MouseHighlighterSettings))]
|
||||
[JsonSerializable(typeof(MouseJumpSettings))]
|
||||
|
||||
@@ -65,19 +65,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
private void SortAlphabetical_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.DashboardSortOrder = DashboardSortOrder.Alphabetical;
|
||||
if (sender is ToggleMenuFlyoutItem item)
|
||||
{
|
||||
item.IsChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SortByStatus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.DashboardSortOrder = DashboardSortOrder.ByStatus;
|
||||
if (sender is ToggleMenuFlyoutItem item)
|
||||
{
|
||||
item.IsChecked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
var darkSettings = this.moduleSettingsRepository.SettingsConfig;
|
||||
|
||||
// Pass them into the ViewModel
|
||||
this.ViewModel = new LightSwitchViewModel(this.generalSettingsRepository, darkSettings, ShellPage.SendDefaultIPCMessage);
|
||||
this.ViewModel = new LightSwitchViewModel(darkSettings, this.sendConfigMsg);
|
||||
this.ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
this.LoadSettings(this.generalSettingsRepository, this.moduleSettingsRepository);
|
||||
@@ -185,7 +185,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
// need to save the values
|
||||
this.ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
|
||||
this.ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);
|
||||
this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}°, {this.ViewModel.Longitude}°";
|
||||
this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}<EFBFBD>, {this.ViewModel.Longitude}<EFBFBD>";
|
||||
|
||||
var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
|
||||
|
||||
@@ -293,7 +293,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void UpdateEnabledState(bool recommendedState)
|
||||
{
|
||||
ViewModel.RefreshEnabledState();
|
||||
var enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
|
||||
if (enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
this.ViewModel.IsEnabledGpoConfigured = true;
|
||||
this.ViewModel.EnabledGPOConfiguration = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ViewModel.IsEnabled = recommendedState;
|
||||
}
|
||||
}
|
||||
|
||||
private async void SyncLocationButton_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -29,8 +29,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public partial class DashboardViewModel : PageViewModelBase
|
||||
{
|
||||
private readonly object _sortLock = new object();
|
||||
|
||||
protected override string ModuleName => "Dashboard";
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
@@ -53,9 +51,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// Flag to prevent circular updates when a UI toggle triggers settings changes.
|
||||
private bool _isUpdatingFromUI;
|
||||
|
||||
// Flag to prevent toggle operations during sorting to avoid race conditions.
|
||||
private bool _isSorting;
|
||||
|
||||
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
|
||||
|
||||
public AllHotkeyConflictsData AllHotkeyConflictsData
|
||||
@@ -85,17 +80,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
get => generalSettingsConfig.DashboardSortOrder;
|
||||
set
|
||||
{
|
||||
if (_dashboardSortOrder != value)
|
||||
if (Set(ref _dashboardSortOrder, value))
|
||||
{
|
||||
_dashboardSortOrder = value;
|
||||
generalSettingsConfig.DashboardSortOrder = value;
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettingsConfig);
|
||||
|
||||
// Save settings to file
|
||||
SettingsUtils.Default.SaveSettings(generalSettingsConfig.ToJsonString());
|
||||
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
|
||||
// Notify UI before sorting so menu updates its checked state
|
||||
OnPropertyChanged(nameof(DashboardSortOrder));
|
||||
|
||||
SortModuleList();
|
||||
}
|
||||
}
|
||||
@@ -110,7 +103,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
dispatcher = Dispatcher.CurrentDispatcher;
|
||||
_settingsRepository = settingsRepository;
|
||||
generalSettingsConfig = settingsRepository.SettingsConfig;
|
||||
|
||||
generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
_settingsRepository.SettingsChanged += OnSettingsChanged;
|
||||
|
||||
// Initialize dashboard sort order from settings
|
||||
@@ -135,14 +128,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
generalSettingsConfig = newSettings;
|
||||
|
||||
// Update local field and notify UI if sort order changed
|
||||
if (_dashboardSortOrder != generalSettingsConfig.DashboardSortOrder)
|
||||
{
|
||||
_dashboardSortOrder = generalSettingsConfig.DashboardSortOrder;
|
||||
OnPropertyChanged(nameof(DashboardSortOrder));
|
||||
}
|
||||
|
||||
generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
ModuleEnabledChangedOnSettingsPage();
|
||||
});
|
||||
}
|
||||
@@ -212,58 +198,40 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
/// Sorts the module list according to the current sort order and updates the AllModules collection.
|
||||
/// On first call, populates AllModules. On subsequent calls, uses Move() to reorder items in-place
|
||||
/// to avoid destroying and recreating UI elements.
|
||||
/// Temporarily disables interaction on all items during sorting to prevent race conditions.
|
||||
/// </summary>
|
||||
private void SortModuleList()
|
||||
{
|
||||
if (_isSorting)
|
||||
var sortedItems = (DashboardSortOrder switch
|
||||
{
|
||||
DashboardSortOrder.ByStatus => _moduleItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label),
|
||||
_ => _moduleItems.OrderBy(x => x.Label), // Default alphabetical
|
||||
}).ToList();
|
||||
|
||||
// If AllModules is empty (first load), just populate it.
|
||||
if (AllModules.Count == 0)
|
||||
{
|
||||
foreach (var item in sortedItems)
|
||||
{
|
||||
AllModules.Add(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_sortLock)
|
||||
// Otherwise, update the collection in place using Move to avoid UI glitches.
|
||||
for (int i = 0; i < sortedItems.Count; i++)
|
||||
{
|
||||
_isSorting = true;
|
||||
try
|
||||
var currentItem = sortedItems[i];
|
||||
var currentIndex = AllModules.IndexOf(currentItem);
|
||||
|
||||
if (currentIndex != -1 && currentIndex != i)
|
||||
{
|
||||
var sortedItems = (DashboardSortOrder switch
|
||||
{
|
||||
DashboardSortOrder.ByStatus => _moduleItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label),
|
||||
_ => _moduleItems.OrderBy(x => x.Label), // Default alphabetical
|
||||
}).ToList();
|
||||
|
||||
// If AllModules is empty (first load), just populate it.
|
||||
if (AllModules.Count == 0)
|
||||
{
|
||||
foreach (var item in sortedItems)
|
||||
{
|
||||
AllModules.Add(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, update the collection in place using Move to avoid UI glitches.
|
||||
for (int i = 0; i < sortedItems.Count; i++)
|
||||
{
|
||||
var currentItem = sortedItems[i];
|
||||
var currentIndex = AllModules.IndexOf(currentItem);
|
||||
|
||||
if (currentIndex != -1 && currentIndex != i)
|
||||
{
|
||||
AllModules.Move(currentIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Use dispatcher to reset flag after UI updates complete
|
||||
dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, () =>
|
||||
{
|
||||
_isSorting = false;
|
||||
});
|
||||
AllModules.Move(currentIndex, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify that DashboardSortOrder changed so the menu updates its checked state.
|
||||
OnPropertyChanged(nameof(DashboardSortOrder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -311,25 +279,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
var dashboardListItem = (DashboardListItem)item;
|
||||
var isEnabled = dashboardListItem.IsEnabled;
|
||||
|
||||
// Ignore toggle operations during sorting to prevent race conditions.
|
||||
// Revert the toggle state since UI already changed due to TwoWay binding.
|
||||
if (_isSorting)
|
||||
{
|
||||
dashboardListItem.UpdateStatus(!isEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
_isUpdatingFromUI = true;
|
||||
try
|
||||
{
|
||||
// Send optimized IPC message with only the module status update
|
||||
// Format: {"module_status": {"ModuleName": true/false}}
|
||||
string moduleKey = ModuleHelper.GetModuleKey(dashboardListItem.Tag);
|
||||
string moduleStatusJson = $"{{\"module_status\": {{\"{moduleKey}\": {isEnabled.ToString().ToLowerInvariant()}}}}}";
|
||||
SendConfigMSG(moduleStatusJson);
|
||||
|
||||
// Update local settings config to keep UI in sync
|
||||
ModuleHelper.SetIsModuleEnabled(generalSettingsConfig, dashboardListItem.Tag, isEnabled);
|
||||
Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, isEnabled);
|
||||
|
||||
if (dashboardListItem.Tag == ModuleType.NewPlus && isEnabled == true)
|
||||
{
|
||||
|
||||
@@ -14,10 +14,8 @@ 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.SerializationContext;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PowerToys.GPOWrapper;
|
||||
using Settings.UI.Library;
|
||||
using Settings.UI.Library.Helpers;
|
||||
|
||||
@@ -29,16 +27,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
public ObservableCollection<SearchLocation> SearchLocations { get; } = new();
|
||||
|
||||
public LightSwitchViewModel(ISettingsRepository<GeneralSettings> settingsRepository, LightSwitchSettings initialSettings = null, Func<string, int> ipcMSGCallBackFunc = null)
|
||||
public LightSwitchViewModel(LightSwitchSettings initialSettings = null, Func<string, int> ipcMSGCallBackFunc = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
InitializeEnabledValue();
|
||||
|
||||
_moduleSettings = initialSettings ?? new LightSwitchSettings();
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
@@ -66,21 +58,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return hotkeysDict;
|
||||
}
|
||||
|
||||
private void InitializeEnabledValue()
|
||||
{
|
||||
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredLightSwitchEnabledValue();
|
||||
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
_enabledStateIsGPOConfigured = true;
|
||||
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.LightSwitch;
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceLightNow()
|
||||
{
|
||||
Logger.LogInfo("Sending custom action: forceLight");
|
||||
@@ -116,26 +93,33 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
|
||||
set
|
||||
get
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
return _enabledGPOConfiguration;
|
||||
}
|
||||
|
||||
if (value != _isEnabled)
|
||||
else
|
||||
{
|
||||
return _isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
}
|
||||
|
||||
_isEnabled = value;
|
||||
|
||||
// Set the status in the general settings configuration
|
||||
GeneralSettingsConfig.Enabled.LightSwitch = value;
|
||||
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
RefreshEnabledState();
|
||||
|
||||
SendConfigMSG(snd.ToString());
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,16 +127,24 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
}
|
||||
|
||||
public GpoRuleConfigured EnabledGPOConfiguration
|
||||
{
|
||||
get => _enabledGpoRuleConfiguration;
|
||||
set
|
||||
{
|
||||
if (_enabledGpoRuleConfiguration != value)
|
||||
if (_enabledStateIsGPOConfigured != value)
|
||||
{
|
||||
_enabledGpoRuleConfiguration = value;
|
||||
_enabledStateIsGPOConfigured = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnabledGPOConfiguration
|
||||
{
|
||||
get => _enabledGPOConfiguration;
|
||||
set
|
||||
{
|
||||
if (_enabledGPOConfiguration != value)
|
||||
{
|
||||
_enabledGPOConfiguration = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
@@ -583,7 +575,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private bool _enabledGPOConfiguration;
|
||||
private LightSwitchSettings _moduleSettings;
|
||||
private bool _isEnabled;
|
||||
private HotkeySettings _toggleThemeHotkey;
|
||||
|
||||
Reference in New Issue
Block a user