Compare commits

..

1 Commits

Author SHA1 Message Date
Shawn Yuan (from Dev Box)
097cf7414b init 2026-01-13 12:27:21 +08:00
58 changed files with 313 additions and 926 deletions

View File

@@ -10,7 +10,7 @@ parameters:
default: {}
steps:
- task: EsrpCodeSigning@6
- task: EsrpCodeSigning@5
displayName: 🔏 ${{ parameters.displayName }}
inputs:
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}

View File

@@ -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" />

View File

@@ -665,7 +665,6 @@
</Project>
</Folder>
<Folder Name="/modules/LightSwitch/">
<Project Path="src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj" Id="79267138-2895-4346-9021-21408d65379f" />
<Project Path="src/modules/LightSwitch/LightSwitchModuleInterface/LightSwitchModuleInterface.vcxproj" Id="38177d56-6ad1-4adf-88c9-2843a7932166" />
<Project Path="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" Id="08e71c67-6a7e-4ca1-b04e-2fb336410bac" />
</Folder>

View File

@@ -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",
};

View File

@@ -66,10 +66,5 @@ namespace PowerToys.GPOWrapperProjection
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue();
}
public static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue()
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredLightSwitchEnabledValue();
}
}
}

View File

@@ -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="&#xE759;" />
<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="&#xE712;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
@@ -523,8 +535,8 @@
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</ScrollViewer>
</ContentDialog>

View File

@@ -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;
}
}
}
}

View File

@@ -113,7 +113,7 @@ namespace Hosts.UITests
this.Find<NavigationViewItem>("Hosts File Editor").Click();
this.Find<ToggleSwitch>("Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);

View File

@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{79267138-2895-4346-9021-21408d65379f}</ProjectGuid>
<RootNamespace>LightSwitchLib</RootNamespace>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<ProjectName>LightSwitchLib</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>
./;
..\..\..\common;
..\..\..\common\logger;
..\..\..\common\utils;
..\..\..\..\deps\spdlog\include;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ThemeHelper.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
</Project>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ThemeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -1,10 +0,0 @@
#pragma once
inline constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
inline constexpr wchar_t NIGHT_LIGHT_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate";
void SetSystemTheme(bool isLight);
void SetAppsTheme(bool isLight);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();
bool IsNightLightEnabled();

View File

@@ -1 +0,0 @@
#include "pch.h"

View File

@@ -1,5 +0,0 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <vector>

View File

@@ -1,22 +0,0 @@
#include "pch.h"
#include "ThemeHelper.h"
extern "C" __declspec(dllexport) void __cdecl LightSwitch_SetSystemTheme(bool isLight)
{
SetSystemTheme(isLight);
}
extern "C" __declspec(dllexport) void __cdecl LightSwitch_SetAppsTheme(bool isLight)
{
SetAppsTheme(isLight);
}
extern "C" __declspec(dllexport) bool __cdecl LightSwitch_GetCurrentSystemTheme()
{
return GetCurrentSystemTheme();
}
extern "C" __declspec(dllexport) bool __cdecl LightSwitch_GetCurrentAppsTheme()
{
return GetCurrentAppsTheme();
}

View File

@@ -166,17 +166,17 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\LightSwitchLib;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="ExportedFunctions.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
@@ -187,6 +187,7 @@
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
@@ -202,9 +203,6 @@
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\LightSwitchLib\LightSwitchLib.vcxproj">
<Project>{79267138-2895-4346-9021-21408d65379f}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -0,0 +1,106 @@
#include "pch.h"
#include <windows.h>
#include "ThemeHelper.h"
// Controls changing the themes.
static void ResetColorPrevalence()
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = 0; // back to default value
RegSetValueEx(hKey, L"ColorPrevalence", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_DWMCOLORIZATIONCOLORCHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetAppsTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetSystemTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
if (mode) // if are changing to light mode
{
ResetColorPrevalence();
}
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
bool GetCurrentSystemTheme()
{
HKEY hKey;
DWORD value = 1; // default = light
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}
bool GetCurrentAppsTheme()
{
HKEY hKey;
DWORD value = 1;
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}

View File

@@ -0,0 +1,5 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();

View File

@@ -55,7 +55,6 @@
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>
./../;
..\LightSwitchLib;
..\..\..\common;
..\..\..\common\logger;
..\..\..\common\utils;
@@ -79,6 +78,7 @@
<ClCompile Include="LightSwitchStateManager.cpp" />
<ClCompile Include="NightLightRegistryObserver.cpp" />
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="ThemeScheduler.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="WinHookEventIDs.cpp" />
@@ -93,6 +93,7 @@
<ClInclude Include="NightLightRegistryObserver.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="ThemeScheduler.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="WinHookEventIDs.h" />
@@ -110,9 +111,6 @@
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
</ProjectReference>
<ProjectReference Include="..\LightSwitchLib\LightSwitchLib.vcxproj">
<Project>{79267138-2895-4346-9021-21408d65379f}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
@@ -120,4 +118,4 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
</Project>
</Project>

View File

@@ -12,3 +12,6 @@ enum class SettingId
ChangeSystem,
ChangeApps
};
constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr wchar_t NIGHT_LIGHT_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate";

View File

@@ -1,6 +1,9 @@
#include "pch.h"
#include "ThemeHelper.h"
#include <windows.h>
#include <logger/logger_settings.h>
#include <logger/logger.h>
#include <utils/logger_helper.h>
#include "ThemeHelper.h"
#include <SettingsConstants.h>
// Controls changing the themes.
@@ -60,7 +63,7 @@ void SetSystemTheme(bool mode)
if (mode) // if are changing to light mode
{
ResetColorPrevalence();
Logger::info(L"[LightSwitchLib] Reset ColorPrevalence to default when switching to light mode.");
Logger::info(L"[LightSwitchService] Reset ColorPrevalence to default when switching to light mode.");
}
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
@@ -133,4 +136,4 @@ bool IsNightLightEnabled()
RegCloseKey(hKey);
return data[23] == 0x10 && data[24] == 0x00;
}
}

View File

@@ -0,0 +1,6 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();
bool IsNightLightEnabled();

View File

@@ -19,9 +19,4 @@
<PackageReference Include="MSTest" />
<ProjectReference Include="..\..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
<Target Name="CopyNativeDll" AfterTargets="Build">
<Copy SourceFiles="$(SolutionDir)$(Platform)\$(Configuration)\PowerToys.LightSwitchModuleInterface.dll" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(SolutionDir)$(Platform)\$(Configuration)\LightSwitchLib\LightSwitchLib.lib" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true" Condition="Exists('$(SolutionDir)$(Platform)\$(Configuration)\LightSwitchLib\LightSwitchLib.lib')" ContinueOnError="true" />
</Target>
</Project>

View File

@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.PowerToys.UITest;
@@ -18,20 +17,6 @@ namespace LightSwitch.UITests
{
private static readonly string[] ShortcutSeparators = { " + ", "+", " " };
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void LightSwitch_SetSystemTheme(bool isLight);
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void LightSwitch_SetAppsTheme(bool isLight);
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool LightSwitch_GetCurrentSystemTheme();
[DllImport("PowerToys.LightSwitchModuleInterface.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool LightSwitch_GetCurrentAppsTheme();
/// <summary>
/// Performs common test initialization: navigate to settings, enable toggle, verify shortcut
/// </summary>
@@ -142,7 +127,8 @@ namespace LightSwitch.UITests
/// <param name="testBase">The test base instance</param>
public static void CleanupTest(UITestBase testBase)
{
CloseLightSwitch(testBase);
// TODO: Make sure the task kills?
// CloseLightSwitch(testBase);
// Ensure we're attached to settings after cleanup
try
@@ -155,51 +141,6 @@ namespace LightSwitch.UITests
}
}
/// <summary>
/// Switch to white/light theme for both system and apps
/// </summary>
/// <param name="testBase">The test base instance</param>
public static void CloseLightSwitch(UITestBase testBase)
{
// Kill LightSwitch process before setting themes
KillLightSwitchProcess();
// Set both themes to light (white)
SetSystemTheme(true);
SetAppsTheme(true);
}
/// <summary>
/// Kill the LightSwitch service process if it's running
/// </summary>
private static void KillLightSwitchProcess()
{
try
{
var processes = System.Diagnostics.Process.GetProcessesByName("PowerToys.LightSwitchService");
foreach (var process in processes)
{
try
{
process.Kill();
process.WaitForExit(2000);
}
catch
{
// Ignore errors killing individual processes
}
finally
{
process.Dispose();
}
}
}
catch
{
// Ignore errors enumerating processes
}
}
/// <summary>
/// Perform a update time test operation
/// </summary>
@@ -467,22 +408,24 @@ namespace LightSwitch.UITests
/* Helpers */
private static int GetSystemTheme()
{
return LightSwitch_GetCurrentSystemTheme() ? 1 : 0;
using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
if (key is null)
{
return 1;
}
return (int)key.GetValue("SystemUsesLightTheme", 1);
}
private static int GetAppsTheme()
{
return LightSwitch_GetCurrentAppsTheme() ? 1 : 0;
}
using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
if (key is null)
{
return 1;
}
private static void SetSystemTheme(bool isLight)
{
LightSwitch_SetSystemTheme(isLight);
}
private static void SetAppsTheme(bool isLight)
{
LightSwitch_SetAppsTheme(isLight);
return (int)key.GetValue("AppsUseLightTheme", 1);
}
private static string GetHelpTextValue(string helpText, string key)

View File

@@ -456,11 +456,10 @@ namespace MouseUtils.UITests
var groupAppearanceBehavior = foundCustom.Find(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseAppearanceBehavior));
if (groupAppearanceBehavior != null)
{
var expandState = groupAppearanceBehavior.Selected;
if (!expandState)
// groupAppearanceBehavior.Click();
if (foundCustom.FindAll(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity)).Count == 0)
{
groupAppearanceBehavior.Click();
Task.Delay(500).Wait();
}
// Set the BackGround color
@@ -542,6 +541,15 @@ namespace MouseUtils.UITests
Task.Delay(500).Wait();
spotlightColorButton.Click(false, 500, 1500);
// Set the overlay opacity to overlayOpacity%
var overlayOpacitySlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseOverlayOpacity));
Assert.IsNotNull(overlayOpacitySlider);
Assert.IsNotNull(settings.OverlayOpacity);
int overlayOpacityValue = int.Parse(settings.OverlayOpacity, CultureInfo.InvariantCulture);
overlayOpacitySlider.QuickSetValue(overlayOpacityValue);
Assert.AreEqual(settings.OverlayOpacity, overlayOpacitySlider.Text);
Task.Delay(1000).Wait();
// Set the Fade Initial zoom to 0
var spotlightInitialZoomSlider = foundCustom.Find<Slider>(By.AccessibilityId(MouseUtilsSettings.AccessibilityIds.FindMyMouseSpotlightZoom));
Assert.IsNotNull(spotlightInitialZoomSlider);
@@ -584,7 +592,7 @@ namespace MouseUtils.UITests
// Assert.IsNull(animationDisabledWarning);
if (foundElements.Count != 0)
{
var openSettingsLink = foundCustom.Find<Element>("Open animation settings");
var openSettingsLink = foundCustom.Find<Element>("Open settings");
Assert.IsNotNull(openSettingsLink);
openSettingsLink.Click(false, 500, 3000);

View File

@@ -32,6 +32,7 @@ namespace MouseUtils.UITests
public const string FindMyMouseExcludedApps = "MouseUtils_FindMyMouseExcludedAppsId";
public const string FindMyMouseBackgroundColor = "MouseUtils_FindMyMouseBackgroundColorId";
public const string FindMyMouseSpotlightColor = "MouseUtils_FindMyMouseSpotlightColorId";
public const string FindMyMouseOverlayOpacity = "MouseUtils_FindMyMouseOverlayOpacityId";
public const string FindMyMouseSpotlightZoom = "MouseUtils_FindMyMouseSpotlightZoomId";
public const string FindMyMouseSpotlightRadius = "MouseUtils_FindMyMouseSpotlightRadiusId";
public const string FindMyMouseAnimationDuration = "MouseUtils_FindMyMouseAnimationDurationId";
@@ -71,10 +72,10 @@ namespace MouseUtils.UITests
private static readonly Dictionary<MouseUtils, string> MouseUtilUIToggleMap = new()
{
[MouseUtils.MouseHighlighter] = @"Mouse Highlighter",
[MouseUtils.FindMyMouse] = @"Find My Mouse",
[MouseUtils.MousePointerCrosshairs] = @"Mouse Pointer Crosshairs",
[MouseUtils.MouseJump] = @"Mouse Jump",
[MouseUtils.MouseHighlighter] = @"Enable Mouse Highlighter",
[MouseUtils.FindMyMouse] = @"Enable Find My Mouse",
[MouseUtils.MousePointerCrosshairs] = @"Enable Mouse Pointer Crosshairs",
[MouseUtils.MouseJump] = @"Enable Mouse Jump",
};
public static string GetMouseUtilUIName(MouseUtils element)

View File

@@ -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")]

View File

@@ -57,7 +57,7 @@ public class WorkspacesSettingsTests : UITestBase
GoToSettingsPageAndEnable();
// Find the enable toggle
var enableToggle = Find<ToggleSwitch>("Workspaces");
var enableToggle = Find<ToggleSwitch>("Enable Workspaces");
Assert.IsNotNull(enableToggle, "Enable Workspaces toggle should exist");
Assert.IsTrue(enableToggle.IsOn, "Enable Workspaces toggle should be in the 'on' state");
@@ -80,7 +80,7 @@ public class WorkspacesSettingsTests : UITestBase
public void TestLaunchEditorByActivationShortcut()
{
// Ensure module is enabled
var enableToggle = Find<ToggleSwitch>("Workspaces");
var enableToggle = Find<ToggleSwitch>("Enable Workspaces");
if (!enableToggle.IsOn)
{
enableToggle.Click();
@@ -109,7 +109,7 @@ public class WorkspacesSettingsTests : UITestBase
public void TestDisabledModuleDoesNotLaunchByShortcut()
{
// Disable the module
var enableToggle = Find<ToggleSwitch>("Workspaces");
var enableToggle = Find<ToggleSwitch>("Enable Workspaces");
if (enableToggle.IsOn)
{
enableToggle.Click();
@@ -131,7 +131,7 @@ public class WorkspacesSettingsTests : UITestBase
RestartScopeExe();
NavigateToWorkspacesSettings();
enableToggle = Find<ToggleSwitch>("Workspaces");
enableToggle = Find<ToggleSwitch>("Enable Workspaces");
if (!enableToggle.IsOn)
{
enableToggle.Click();
@@ -174,7 +174,7 @@ public class WorkspacesSettingsTests : UITestBase
this.Find<NavigationViewItem>("Workspaces").Click();
var enableButton = this.Find<ToggleSwitch>("Workspaces");
var enableButton = this.Find<ToggleSwitch>("Enable Workspaces");
Assert.IsNotNull(enableButton, "Enable Workspaces toggle should exist");
if (!enableButton.IsOn)

View File

@@ -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)),
};

View File

@@ -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}";
}
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -584,7 +584,7 @@ namespace UITests_FancyZones
}
windowingElement.Find<Element>("FancyZones").Click();
Find<ToggleSwitch>(By.AccessibilityId("EnableFancyZonesToggleSwitch")).Toggle(true);
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
if (isMax == true)
{
this.Find<Button>("Maximize").Click(); // maximize the window
@@ -661,7 +661,7 @@ namespace UITests_FancyZones
this.Find<NavigationViewItem>("Hosts File Editor").Click();
Task.Delay(1000).Wait();
this.Find<ToggleSwitch>("Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);

View File

@@ -261,7 +261,7 @@ namespace UITests_FancyZones
}
this.Find<NavigationViewItem>("FancyZones").Click();
Find<ToggleSwitch>(By.AccessibilityId("EnableFancyZonesToggleSwitch")).Toggle(true);
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
this.Session.SetMainWindowSize(WindowSize.Large);
// fixed settings
@@ -322,7 +322,7 @@ namespace UITests_FancyZones
this.Find<NavigationViewItem>("Hosts File Editor").Click();
Task.Delay(1000).Wait();
this.Find<ToggleSwitch>("Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Open as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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; }

View File

@@ -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

View File

@@ -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]

View File

@@ -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))
{

View File

@@ -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)

View File

@@ -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" />

View File

@@ -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();
}
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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(),

View File

@@ -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
{

View File

@@ -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))]

View File

@@ -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))]

View File

@@ -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)

View File

@@ -145,16 +145,10 @@
<tkcontrols:SettingsExpander.Items>
<!-- Overlay opacity removed; alpha now encoded in colors -->
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseBackgroundColor" x:Uid="MouseUtils_FindMyMouse_BackgroundColor">
<controls:ColorPickerButton
AutomationProperties.AutomationId="MouseUtils_FindMyMouseBackgroundColorId"
IsAlphaEnabled="True"
SelectedColor="{x:Bind Path=ViewModel.FindMyMouseBackgroundColor, Mode=TwoWay}" />
<controls:ColorPickerButton IsAlphaEnabled="True" SelectedColor="{x:Bind Path=ViewModel.FindMyMouseBackgroundColor, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseSpotlightColor" x:Uid="MouseUtils_FindMyMouse_SpotlightColor">
<controls:ColorPickerButton
AutomationProperties.AutomationId="MouseUtils_FindMyMouseSpotlightColorId"
IsAlphaEnabled="True"
SelectedColor="{x:Bind Path=ViewModel.FindMyMouseSpotlightColor, Mode=TwoWay}" />
<controls:ColorPickerButton IsAlphaEnabled="True" SelectedColor="{x:Bind Path=ViewModel.FindMyMouseSpotlightColor, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="MouseUtilsFindMyMouseSpotlightRadius" x:Uid="MouseUtils_FindMyMouse_SpotlightRadius">
<NumberBox

View File

@@ -5080,9 +5080,6 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="OpenSettings.Content" xml:space="preserve">
<value>Open settings</value>
</data>
<data name="OpenAnimationsSettings.Content" xml:space="preserve">
<value>Open animation settings</value>
</data>
<data name="LanguageHeader.Header" xml:space="preserve">
<value>Language</value>
</data>

View File

@@ -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;